font-kit-0.11.0/.cargo_vcs_info.json0000644000000001360000000000100126460ustar { "git": { "sha1": "75f99cf648885c180c7ccfb9b6d55eae259e1d71" }, "path_in_vcs": "" }font-kit-0.11.0/.github/workflows/ci.yml000064400000000000000000000037020072674642500162030ustar 00000000000000name: Run CI on: push: branches: ["**"] pull_request: branches: ["**"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: env: RUST_BACKTRACE: 1 SHELL: /bin/bash jobs: ci-linux: name: CI (Linux) runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 2 - name: Setup Rust uses: actions-rs/toolchain@v1 with: toolchain: stable default: true override: true - name: Install run: | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections; sudo apt-get install ttf-mscorefonts-installer; - name: Build run: cargo build - name: Tests run: cargo test - name: Format run: cargo fmt --all -- --check ci-macos: name: CI (macOS) runs-on: macos-10.15 steps: - uses: actions/checkout@v2 with: fetch-depth: 2 - name: Setup Rust uses: actions-rs/toolchain@v1 with: toolchain: stable default: true override: true - name: Build run: cargo build - name: Tests run: cargo test ci-win: name: CI (Windows) runs-on: windows-2019 steps: - uses: actions/checkout@v2 with: fetch-depth: 2 - name: Setup Rust uses: actions-rs/toolchain@v1 with: toolchain: stable default: true override: true - name: Build run: cargo build - name: Tests run: cargo test build_result: name: homu build finished runs-on: ubuntu-latest needs: - "ci-linux" - "ci-macos" - "ci-win" steps: - name: Mark the job as successful run: exit 0 if: success() - name: Mark the job as unsuccessful run: exit 1 if: "!success()" font-kit-0.11.0/.gitignore000064400000000000000000000000610072674642500134530ustar 00000000000000/target **/*.rs.bk Cargo.lock /c/build /c/target font-kit-0.11.0/Cargo.lock0000644000000611410000000000100106240ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ "winapi 0.3.7", ] [[package]] name = "approx" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94" [[package]] name = "argon2rs" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" dependencies = [ "blake2-rfc", "scoped_threadpool", ] [[package]] name = "arrayvec" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" dependencies = [ "nodrop", ] [[package]] name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" dependencies = [ "libc", "termion", "winapi 0.3.7", ] [[package]] name = "autocfg" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ada4c783bb7e7443c14e0480f429ae2cc99da95065aeab7ee1b81ada0419404f" dependencies = [ "autocfg 0.1.4", "backtrace-sys", "cfg-if 0.1.9", "libc", "rustc-demangle", ] [[package]] name = "backtrace-sys" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" dependencies = [ "cc", "libc", ] [[package]] name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" [[package]] name = "blake2-rfc" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" dependencies = [ "arrayvec", "constant_time_eq", ] [[package]] name = "bstr" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "bytemuck" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" [[package]] name = "byteorder" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" [[package]] name = "cc" version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cgmath" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64a4b57c8f4e3a2e9ac07e0f6abc9c24b6fc9e1b54c3478cfb598f3d0023e51c" dependencies = [ "approx", "num-traits 0.1.43", "rand", ] [[package]] name = "clap" version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" dependencies = [ "ansi_term", "atty", "bitflags", "strsim", "textwrap", "unicode-width", "vec_map", ] [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ "bitflags", ] [[package]] name = "cmake" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" dependencies = [ "cc", ] [[package]] name = "colored" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cdb90b60f2927f8d76139c72dbde7e10c3a2bc47c8594c9c7a66529f2687c03" dependencies = [ "lazy_static", "winconsole", ] [[package]] name = "const-cstr" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" [[package]] name = "constant_time_eq" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" [[package]] name = "core-foundation" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b5ed8e7e76c45974e15e41bfa8d5b0483cd90191639e01d8f5f1e606299d3fb" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6" [[package]] name = "core-graphics" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6082396a349fa49674ba1bda4077332a18bf150e8fa75745ece07085e29a113" dependencies = [ "bitflags", "core-foundation", "core-graphics-types", "foreign-types", "libc", ] [[package]] name = "core-graphics-types" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e92f5d519093a4178296707dbaa3880eae85a5ef5386675f361a1cf25376e93c" dependencies = [ "bitflags", "core-foundation", "foreign-types", "libc", ] [[package]] name = "core-text" version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" dependencies = [ "core-foundation", "core-graphics", "foreign-types", "libc", ] [[package]] name = "csv" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" dependencies = [ "bstr", "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "dirs" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", "redox_users 0.3.0", "winapi 0.3.7", ] [[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if 1.0.0", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users 0.4.0", "winapi 0.3.7", ] [[package]] name = "dlib" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" dependencies = [ "libloading", ] [[package]] name = "dwrote" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", "winapi 0.3.7", "wio", ] [[package]] name = "encode_unicode" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd" [[package]] name = "failure" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" dependencies = [ "backtrace", "failure_derive", ] [[package]] name = "failure_derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "float-ord" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" [[package]] name = "font-kit" version = "0.11.0" dependencies = [ "bitflags", "byteorder", "clap", "colored", "core-foundation", "core-graphics", "core-text", "dirs-next", "dwrote", "float-ord", "freetype", "lazy_static", "libc", "log", "pathfinder_geometry", "pathfinder_simd", "pbr", "prettytable-rs", "walkdir", "winapi 0.3.7", "yeslogic-fontconfig-sys", ] [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "freetype" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" dependencies = [ "freetype-sys", "libc", ] [[package]] name = "freetype-sys" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" dependencies = [ "cmake", "libc", "pkg-config", ] [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "getrandom" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "libc", "wasi", ] [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" dependencies = [ "winapi 0.2.8", "winapi-build", ] [[package]] name = "lazy_static" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" [[package]] name = "libc" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" [[package]] name = "libloading" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.7", ] [[package]] name = "log" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" dependencies = [ "cfg-if 0.1.9", ] [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" [[package]] name = "num-traits" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" dependencies = [ "num-traits 0.2.12", ] [[package]] name = "num-traits" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ "autocfg 1.0.0", ] [[package]] name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "pathfinder_geometry" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" dependencies = [ "log", "pathfinder_simd", ] [[package]] name = "pathfinder_simd" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" dependencies = [ "rustc_version", ] [[package]] name = "pbr" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907" dependencies = [ "kernel32-sys", "libc", "termion", "time", "winapi 0.2.8", ] [[package]] name = "pest" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ "ucd-trie", ] [[package]] name = "pkg-config" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" [[package]] name = "prettytable-rs" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" dependencies = [ "atty", "csv", "encode_unicode", "lazy_static", "term", "unicode-width", ] [[package]] name = "proc-macro2" version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" dependencies = [ "fuchsia-cprng", "libc", "rand_core 0.3.1", "rdrand", "winapi 0.3.7", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ "rand_core 0.4.0", ] [[package]] name = "rand_core" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" [[package]] name = "rand_os" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ "cloudabi", "fuchsia-cprng", "libc", "rand_core 0.4.0", "rdrand", "winapi 0.3.7", ] [[package]] name = "rdrand" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" dependencies = [ "rand_core 0.3.1", ] [[package]] name = "redox_syscall" version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" [[package]] name = "redox_syscall" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" dependencies = [ "bitflags", ] [[package]] name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" dependencies = [ "redox_syscall 0.1.54", ] [[package]] name = "redox_users" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" dependencies = [ "argon2rs", "failure", "rand_os", "redox_syscall 0.1.54", ] [[package]] name = "redox_users" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom", "redox_syscall 0.2.8", ] [[package]] name = "regex-automata" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ "byteorder", ] [[package]] name = "rgb" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ef54b45ae131327a88597e2463fee4098ad6c88ba7b6af4b3987db8aad4098" dependencies = [ "bytemuck", ] [[package]] name = "rustc-demangle" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" [[package]] name = "rustc_version" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ "semver", ] [[package]] name = "ryu" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" [[package]] name = "same-file" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" dependencies = [ "winapi-util", ] [[package]] name = "scoped_threadpool" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" dependencies = [ "pest", ] [[package]] name = "serde" version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" version = "0.15.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "synstructure" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" dependencies = [ "proc-macro2", "quote", "syn", "unicode-xid", ] [[package]] name = "term" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", "dirs", "winapi 0.3.7", ] [[package]] name = "termion" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" dependencies = [ "libc", "numtoa", "redox_syscall 0.1.54", "redox_termios", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" dependencies = [ "libc", "redox_syscall 0.1.54", "winapi 0.3.7", ] [[package]] name = "ucd-trie" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unicode-width" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" [[package]] name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" [[package]] name = "walkdir" version = "2.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1" dependencies = [ "same-file", "winapi 0.3.7", "winapi-util", ] [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" [[package]] name = "winapi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" dependencies = [ "winapi 0.3.7", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winconsole" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef84b96d10db72dd980056666d7f1e7663ce93d82fa33b63e71c966f4cf5032" dependencies = [ "cgmath", "lazy_static", "rgb", "winapi 0.3.7", ] [[package]] name = "wio" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ "winapi 0.3.7", ] [[package]] name = "yeslogic-fontconfig-sys" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3f5a91c31bef6650d3a1b69192b4217fd88e4cfedc8101813e4dc3394ecbb8" dependencies = [ "const-cstr", "dlib", "once_cell", "pkg-config", ] font-kit-0.11.0/Cargo.toml0000644000000054560000000000100106560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "font-kit" version = "0.11.0" authors = ["Patrick Walton "] exclude = ["resources/**"] description = "A cross-platform font loading library" homepage = "https://github.com/servo/font-kit" readme = "README.md" license = "MIT/Apache-2.0" repository = "https://github.com/servo/font-kit" [dependencies.bitflags] version = "1" [dependencies.byteorder] version = "1.2" [dependencies.float-ord] version = "0.2" [dependencies.freetype] version = "0.7" optional = true [dependencies.lazy_static] version = "1.1" [dependencies.libc] version = "0.2" [dependencies.log] version = "0.4.4" [dependencies.pathfinder_geometry] version = "0.5" [dependencies.pathfinder_simd] version = "0.5.1" [dependencies.yeslogic-fontconfig-sys] version = "3.0.0" optional = true [dev-dependencies.clap] version = "2.32" [dev-dependencies.colored] version = "1.6" [dev-dependencies.pbr] version = "1.0" [dev-dependencies.prettytable-rs] version = "0.8" [features] default = ["source"] loader-freetype = ["freetype"] loader-freetype-default = ["loader-freetype"] source = [] source-fontconfig = ["yeslogic-fontconfig-sys"] source-fontconfig-default = ["source-fontconfig"] source-fontconfig-dlopen = ["yeslogic-fontconfig-sys/dlopen"] [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-foundation] version = "0.9" [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-graphics] version = "0.22" [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-text] version = "19.1.0" [target."cfg(not(any(target_arch = \"wasm32\", target_family = \"windows\", target_os = \"android\")))".dependencies.dirs-next] version = "2.0" [target."cfg(not(any(target_family = \"windows\", target_os = \"macos\", target_os = \"ios\")))".dependencies.freetype] version = "0.7" [target."cfg(not(any(target_family = \"windows\", target_os = \"macos\", target_os = \"ios\", target_arch = \"wasm32\")))".dependencies.yeslogic-fontconfig-sys] version = "3.0.0" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.walkdir] version = "2.1" [target."cfg(target_family = \"windows\")".dependencies.dwrote] version = "0.11" default-features = false [target."cfg(target_family = \"windows\")".dependencies.winapi] version = "0.3" features = ["dwrite", "minwindef", "sysinfoapi", "winbase", "winnt"] font-kit-0.11.0/Cargo.toml.orig000064400000000000000000000035540072674642500143640ustar 00000000000000[package] name = "font-kit" version = "0.11.0" authors = ["Patrick Walton "] description = "A cross-platform font loading library" license = "MIT/Apache-2.0" readme = "README.md" repository = "https://github.com/servo/font-kit" homepage = "https://github.com/servo/font-kit" exclude = ["resources/**"] edition = "2018" [features] default = ["source"] loader-freetype = ["freetype"] loader-freetype-default = ["loader-freetype"] source-fontconfig = ["yeslogic-fontconfig-sys"] source-fontconfig-dlopen = ["yeslogic-fontconfig-sys/dlopen"] source-fontconfig-default = ["source-fontconfig"] source = [] [dependencies] bitflags = "1" byteorder = "1.2" float-ord = "0.2" lazy_static = "1.1" libc = "0.2" log = "0.4.4" pathfinder_geometry = "0.5" pathfinder_simd = "0.5.1" [dependencies.freetype] version = "0.7" optional = true [dependencies.yeslogic-fontconfig-sys] version = "3.0.0" optional = true [dev-dependencies] clap = "2.32" colored = "1.6" pbr = "1.0" prettytable-rs = "0.8" [target.'cfg(target_family = "windows")'.dependencies] dwrote = { version = "0.11", default-features = false } [target.'cfg(target_family = "windows")'.dependencies.winapi] version = "0.3" features = ["dwrite", "minwindef", "sysinfoapi", "winbase", "winnt"] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] core-foundation = "0.9" core-graphics = "0.22" core-text = "19.1.0" [target.'cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))'.dependencies] freetype = "0.7" [target.'cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios", target_arch = "wasm32")))'.dependencies] yeslogic-fontconfig-sys = "3.0.0" [target.'cfg(not(any(target_arch = "wasm32", target_family = "windows", target_os = "android")))'.dependencies] dirs-next = "2.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] walkdir = "2.1" font-kit-0.11.0/LICENSE-APACHE000064400000000000000000000251370072674642500134220ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. font-kit-0.11.0/LICENSE-MIT000064400000000000000000000017770072674642500131360ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. font-kit-0.11.0/README.md000064400000000000000000000100300072674642500127370ustar 00000000000000# font-kit [![Build Status](https://travis-ci.org/servo/font-kit.svg?branch=master)](https://travis-ci.org/servo/font-kit) [![Crates.io](https://img.shields.io/crates/v/font-kit.svg)](https://crates.io/crates/font-kit) [![Documentation](https://docs.rs/font-kit/badge.svg)](https://docs.rs/font-kit) `font-kit` provides a common interface to the various system font libraries and provides services such as finding fonts on the system, performing nearest-font matching, and rasterizing glyphs. ## Synopsis ```rust let font = SystemSource::new() .select_by_postscript_name("ArialMT") .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char('A').unwrap(); let mut canvas = Canvas::new(&Size2D::new(32, 32), Format::A8); font.rasterize_glyph( &mut canvas, glyph_id, 32.0, &Point2D::new(0.0, 32.0), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); ``` ## Backends `font-kit` delegates to system libraries to perform tasks. It has two types of backends: a *source* and a *loader*. Sources are platform font databases; they allow lookup of installed fonts by name or attributes. Loaders are font loading libraries; they allow font files (TTF, OTF, etc.) to be loaded from a file on disk or from bytes in memory. Sources and loaders can be freely intermixed at runtime; fonts can be looked up via DirectWrite and rendered via FreeType, for example. Available loaders: * Core Text (macOS): The system font loader on macOS. Does not do hinting except when bilevel rendering is in use. * DirectWrite (Windows): The newer system framework for text rendering on Windows. Does vertical hinting but not full hinting. * FreeType (cross-platform): A full-featured font rendering framework. Available sources: * Core Text (macOS): The system font database on macOS. * DirectWrite (Windows): The newer API to query the system font database on Windows. * Fontconfig (cross-platform): A technically platform-neutral, but in practice Unix-specific, API to query and match fonts. * Filesystem (cross-platform): A simple source that reads fonts from a path on disk. This is the default on Android. * Memory (cross-platform): A source that reads from a fixed set of fonts in memory. * Multi (cross-platform): A source that allows multiple sources to be queried at once. On Windows and macOS, the FreeType loader and the Fontconfig source are not built by default. To build them, use the `loader-freetype` and `source-fontconfig` Cargo features respectively. If you want them to be the default, instead use the `loader-freetype-default` and `source-fontconfig-default` Cargo features respectively. Beware that `source-fontconfig-default` is rarely what you want on those two platforms! If you don't need to locate fonts on the system at all—for example, if all your fonts are stored with your app—then you can omit the default `source` feature and none of that code will be included. ## Features `font-kit` is capable of doing the following: * Loading fonts from files or memory. * Determining whether files on disk or in memory represent fonts. * Interoperating with native font APIs. * Querying various metadata about fonts. * Doing simple glyph-to-character mapping. (For more complex use cases, a shaper is required; proper shaping is beyond the scope of `font-kit`.) * Reading unhinted or hinted vector outlines from glyphs. * Calculating glyph and font metrics. * Looking up glyph advances and origins. * Rasterizing glyphs using the native rasterizer, optionally using hinting. (Custom rasterizers, such as Pathfinder, can be used in conjunction with the outline API.) * Looking up all fonts on the system. * Searching for specific fonts by family or PostScript name. * Performing font matching according to the [CSS Fonts Module Level 3] specification. ## Dependencies **Ubuntu Linux** `sudo apt install pkg-config libfreetype6-dev libfontconfig1-dev` ## License `font-kit` is licensed under the same terms as Rust itself. [CSS Fonts Module Level 3]: https://drafts.csswg.org/css-fonts-3/#font-matching-algorithm font-kit-0.11.0/build.rs000064400000000000000000000003630072674642500131350ustar 00000000000000fn main() { println!("cargo:rerun-if-env-changed=RUST_FONTCONFIG_DLOPEN"); let dlopen = std::env::var("RUST_FONTCONFIG_DLOPEN").is_ok(); if dlopen { println!("cargo:rustc-cfg=feature=\"source-fontconfig-dlopen\""); } } font-kit-0.11.0/examples/fallback.rs000064400000000000000000000041600072674642500154120ustar 00000000000000// font-kit/examples/fallback.rs // // Copyright © 2019 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. extern crate clap; extern crate font_kit; use clap::{App, Arg, ArgMatches}; use font_kit::loader::Loader; use font_kit::source::SystemSource; #[cfg(any(target_family = "windows", target_os = "macos"))] static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &'static str = "ArialMT"; #[cfg(not(any(target_family = "windows", target_os = "macos")))] static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &'static str = "DejaVuSans"; fn get_args() -> ArgMatches<'static> { let postscript_name_arg = Arg::with_name("POSTSCRIPT-NAME") .help("PostScript name of the font") .default_value(SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME) .index(1); let text_arg = Arg::with_name("TEXT") .help("Text to query") .default_value("A") .index(2); let locale_arg = Arg::with_name("LOCALE") .help("Locale for fallback query") .default_value("en-US") .index(3); App::new("fallback") .version("0.1") .arg(postscript_name_arg) .arg(text_arg) .arg(locale_arg) .get_matches() } fn main() { let matches = get_args(); let postscript_name = matches.value_of("POSTSCRIPT-NAME").unwrap(); let text = matches.value_of("TEXT").unwrap(); let locale = matches.value_of("LOCALE").unwrap(); let font = SystemSource::new() .select_by_postscript_name(&postscript_name) .expect("Font not found") .load() .unwrap(); println!("{}: text: {:?}", postscript_name, text); let fallback_result = font.get_fallbacks(text, locale); println!( "fallback valid substring length: {}", fallback_result.valid_len ); for font in &fallback_result.fonts { println!("font: {}", font.font.full_name()); } } font-kit-0.11.0/examples/list-fonts.rs000064400000000000000000000036130072674642500157570ustar 00000000000000// font-kit/examples/list-fonts.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Lists all fonts on the system. extern crate font_kit; extern crate pbr; extern crate prettytable; use font_kit::source::SystemSource; use pbr::ProgressBar; use prettytable::{Attr, Cell, Row, Table}; fn main() { let mut table = Table::new(); table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(Row::new(vec![ Cell::new("PostScript Name").with_style(Attr::Bold), Cell::new("Name").with_style(Attr::Bold), Cell::new("Family").with_style(Attr::Bold), Cell::new("Style").with_style(Attr::Bold), Cell::new("Weight").with_style(Attr::Bold), Cell::new("Stretch").with_style(Attr::Bold), ])); let source = SystemSource::new(); let fonts = source.all_fonts().unwrap(); let mut progress_bar = ProgressBar::new(fonts.len() as u64); progress_bar.message("Loading fonts… "); for font in fonts { if let Ok(font) = font.load() { let properties = font.properties(); table.add_row(Row::new(vec![ Cell::new(&font.postscript_name().unwrap_or_else(|| "".to_owned())), Cell::new(&font.full_name()), Cell::new(&font.family_name()), Cell::new(&properties.style.to_string()), Cell::new(&properties.weight.0.to_string()), Cell::new(&properties.stretch.0.to_string()), ])); } progress_bar.inc(); } progress_bar.finish_print(""); table.printstd(); } font-kit-0.11.0/examples/match-font.rs000064400000000000000000000037640072674642500157240ustar 00000000000000// font-kit/examples/match-font.rs // // Copyright © 2020 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Looks up fonts by name. extern crate font_kit; use font_kit::family_name::FamilyName; use font_kit::handle::Handle; use font_kit::properties::Properties; use font_kit::source::SystemSource; use std::env; fn main() -> Result<(), Box> { let args: Vec<_> = env::args().collect(); if args.len() != 2 { println!("Usage:\n\tmatch-font \"Times New Roman, Arial, serif\""); std::process::exit(1); } let mut families = Vec::new(); for family in args[1].split(',') { let family = family.replace('\'', ""); let family = family.trim(); families.push(match family { "serif" => FamilyName::Serif, "sans-serif" => FamilyName::SansSerif, "monospace" => FamilyName::Monospace, "cursive" => FamilyName::Cursive, "fantasy" => FamilyName::Fantasy, _ => FamilyName::Title(family.to_string()), }); } let properties = Properties::default(); let handle = SystemSource::new().select_best_match(&families, &properties)?; if let Handle::Path { ref path, font_index, } = handle { println!("Path: {}", path.display()); println!("Index: {}", font_index); } let font = handle.load()?; println!("Family name: {}", font.family_name()); println!( "PostScript name: {}", font.postscript_name().unwrap_or("?".to_string()) ); println!("Style: {:?}", font.properties().style); println!("Weight: {:?}", font.properties().weight); println!("Stretch: {:?}", font.properties().stretch); Ok(()) } font-kit-0.11.0/examples/render-glyph.rs000064400000000000000000000137400072674642500162570ustar 00000000000000// font-kit/examples/render-glyph.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. extern crate clap; extern crate colored; extern crate font_kit; extern crate pathfinder_geometry; use clap::{App, Arg, ArgGroup, ArgMatches}; use colored::Colorize; use font_kit::canvas::{Canvas, Format, RasterizationOptions}; use font_kit::hinting::HintingOptions; use font_kit::source::SystemSource; use pathfinder_geometry::transform2d::Transform2F; use std::fmt::Write; #[cfg(any(target_family = "windows", target_os = "macos"))] static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &'static str = "ArialMT"; #[cfg(not(any(target_family = "windows", target_os = "macos")))] static SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME: &'static str = "DejaVuSans"; fn get_args() -> ArgMatches<'static> { let postscript_name_arg = Arg::with_name("POSTSCRIPT-NAME") .help("PostScript name of the font") .default_value(SANS_SERIF_FONT_REGULAR_POSTSCRIPT_NAME) .index(1); let glyph_arg = Arg::with_name("GLYPH") .help("Character to render") .default_value("A") .index(2); let size_arg = Arg::with_name("SIZE") .help("Font size in blocks") .default_value("32") .index(3); let grayscale_arg = Arg::with_name("grayscale") .long("grayscale") .help("Use grayscale antialiasing (default)"); let bilevel_arg = Arg::with_name("bilevel") .help("Use bilevel (black & white) rasterization") .short("b") .long("bilevel"); let subpixel_arg = Arg::with_name("subpixel") .help("Use subpixel (LCD) rasterization") .short("s") .long("subpixel"); let hinting_arg = Arg::with_name("hinting") .help("Select hinting type") .short("H") .long("hinting") .takes_value(true) .possible_value("none") .possible_value("vertical") .possible_value("full") .value_names(&["TYPE"]); let transform_arg = Arg::with_name("transform") .help("Transform to apply to glyph when rendering") .long("transform") .number_of_values(4); let rasterization_mode_group = ArgGroup::with_name("rasterization-mode").args(&["grayscale", "bilevel", "subpixel"]); App::new("render-glyph") .version("0.1") .author("The Pathfinder Project Developers") .about("Simple example tool to render glyphs with `font-kit`") .arg(postscript_name_arg) .arg(glyph_arg) .arg(size_arg) .arg(grayscale_arg) .arg(bilevel_arg) .arg(subpixel_arg) .group(rasterization_mode_group) .arg(hinting_arg) .arg(transform_arg) .get_matches() } fn main() { let matches = get_args(); let postscript_name = matches.value_of("POSTSCRIPT-NAME").unwrap(); let character = matches.value_of("GLYPH").unwrap().chars().next().unwrap(); let size: f32 = matches.value_of("SIZE").unwrap().parse().unwrap(); let (canvas_format, rasterization_options) = if matches.is_present("bilevel") { (Format::A8, RasterizationOptions::Bilevel) } else if matches.is_present("subpixel") { (Format::Rgb24, RasterizationOptions::SubpixelAa) } else { (Format::A8, RasterizationOptions::GrayscaleAa) }; let mut transform = Transform2F::default(); if let Some(values) = matches.values_of("transform") { if let [Ok(a), Ok(b), Ok(c), Ok(d)] = values.map(|x| x.parse()).collect::>()[..] { transform = Transform2F::row_major(a, b, c, d, 0.0, 0.0) } } let hinting_options = match matches.value_of("hinting") { Some(value) if value == "vertical" => HintingOptions::Vertical(size), Some(value) if value == "full" => HintingOptions::Full(size), _ => HintingOptions::None, }; let font = SystemSource::new() .select_by_postscript_name(&postscript_name) .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char(character).unwrap(); let raster_rect = font .raster_bounds( glyph_id, size, transform, hinting_options, rasterization_options, ) .unwrap(); let mut canvas = Canvas::new(raster_rect.size(), canvas_format); font.rasterize_glyph( &mut canvas, glyph_id, size, Transform2F::from_translation(-raster_rect.origin().to_f32()) * transform, hinting_options, rasterization_options, ) .unwrap(); println!("glyph {}:", glyph_id); for y in 0..raster_rect.height() { let mut line = String::new(); let (row_start, row_end) = (y as usize * canvas.stride, (y + 1) as usize * canvas.stride); let row = &canvas.pixels[row_start..row_end]; for x in 0..raster_rect.width() { match canvas.format { Format::Rgba32 => unimplemented!(), Format::Rgb24 => { write!( &mut line, "{}{}{}", shade(row[x as usize * 3 + 0]).to_string().red(), shade(row[x as usize * 3 + 1]).to_string().green(), shade(row[x as usize * 3 + 2]).to_string().blue() ) .unwrap(); } Format::A8 => { let shade = shade(row[x as usize]); line.push(shade); line.push(shade); } } } println!("{}", line); } } fn shade(value: u8) -> char { match value { 0 => ' ', 1..=84 => '░', 85..=169 => '▒', 170..=254 => '▓', _ => '█', } } font-kit-0.11.0/src/canvas.rs000064400000000000000000000234370072674642500141070ustar 00000000000000// font-kit/src/canvas.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! An in-memory bitmap surface for glyph rasterization. use lazy_static::lazy_static; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::vector::Vector2I; use std::cmp; use std::fmt; use crate::utils; lazy_static! { static ref BITMAP_1BPP_TO_8BPP_LUT: [[u8; 8]; 256] = { let mut lut = [[0; 8]; 256]; for byte in 0..0x100 { let mut value = [0; 8]; for bit in 0..8 { if (byte & (0x80 >> bit)) != 0 { value[bit] = 0xff; } } lut[byte] = value } lut }; } /// An in-memory bitmap surface for glyph rasterization. pub struct Canvas { /// The raw pixel data. pub pixels: Vec, /// The size of the buffer, in pixels. pub size: Vector2I, /// The number of *bytes* between successive rows. pub stride: usize, /// The image format of the canvas. pub format: Format, } impl Canvas { /// Creates a new blank canvas with the given pixel size and format. /// /// Stride is automatically calculated from width. /// /// The canvas is initialized with transparent black (all values 0). #[inline] pub fn new(size: Vector2I, format: Format) -> Canvas { Canvas::with_stride( size, size.x() as usize * format.bytes_per_pixel() as usize, format, ) } /// Creates a new blank canvas with the given pixel size, stride (number of bytes between /// successive rows), and format. /// /// The canvas is initialized with transparent black (all values 0). pub fn with_stride(size: Vector2I, stride: usize, format: Format) -> Canvas { Canvas { pixels: vec![0; stride * size.y() as usize], size, stride, format, } } #[allow(dead_code)] pub(crate) fn blit_from_canvas(&mut self, src: &Canvas) { self.blit_from( Vector2I::default(), &src.pixels, src.size, src.stride, src.format, ) } #[allow(dead_code)] pub(crate) fn blit_from( &mut self, dst_point: Vector2I, src_bytes: &[u8], src_size: Vector2I, src_stride: usize, src_format: Format, ) { let dst_rect = RectI::new(dst_point, src_size); let dst_rect = dst_rect.intersection(RectI::new(Vector2I::default(), self.size)); let dst_rect = match dst_rect { Some(dst_rect) => dst_rect, None => return, }; match (self.format, src_format) { (Format::A8, Format::A8) | (Format::Rgb24, Format::Rgb24) | (Format::Rgba32, Format::Rgba32) => { self.blit_from_with::(dst_rect, src_bytes, src_stride, src_format) } (Format::A8, Format::Rgb24) => { self.blit_from_with::(dst_rect, src_bytes, src_stride, src_format) } (Format::Rgb24, Format::A8) => { self.blit_from_with::(dst_rect, src_bytes, src_stride, src_format) } (Format::Rgb24, Format::Rgba32) => self .blit_from_with::(dst_rect, src_bytes, src_stride, src_format), (Format::Rgba32, Format::Rgb24) => self .blit_from_with::(dst_rect, src_bytes, src_stride, src_format), (Format::Rgba32, Format::A8) | (Format::A8, Format::Rgba32) => unimplemented!(), } } #[allow(dead_code)] pub(crate) fn blit_from_bitmap_1bpp( &mut self, dst_point: Vector2I, src_bytes: &[u8], src_size: Vector2I, src_stride: usize, ) { if self.format != Format::A8 { unimplemented!() } let dst_rect = RectI::new(dst_point, src_size); let dst_rect = dst_rect.intersection(RectI::new(Vector2I::default(), self.size)); let dst_rect = match dst_rect { Some(dst_rect) => dst_rect, None => return, }; let size = dst_rect.size(); let dest_bytes_per_pixel = self.format.bytes_per_pixel() as usize; let dest_row_stride = size.x() as usize * dest_bytes_per_pixel; let src_row_stride = utils::div_round_up(size.x() as usize, 8); for y in 0..size.y() { let (dest_row_start, src_row_start) = ( (y + dst_rect.origin_y()) as usize * self.stride + dst_rect.origin_x() as usize * dest_bytes_per_pixel, y as usize * src_stride, ); let dest_row_end = dest_row_start + dest_row_stride; let src_row_end = src_row_start + src_row_stride; let dest_row_pixels = &mut self.pixels[dest_row_start..dest_row_end]; let src_row_pixels = &src_bytes[src_row_start..src_row_end]; for x in 0..src_row_stride { let pattern = &BITMAP_1BPP_TO_8BPP_LUT[src_row_pixels[x] as usize]; let dest_start = x * 8; let dest_end = cmp::min(dest_start + 8, dest_row_stride); let src = &pattern[0..(dest_end - dest_start)]; dest_row_pixels[dest_start..dest_end].clone_from_slice(src); } } } fn blit_from_with( &mut self, rect: RectI, src_bytes: &[u8], src_stride: usize, src_format: Format, ) { let src_bytes_per_pixel = src_format.bytes_per_pixel() as usize; let dest_bytes_per_pixel = self.format.bytes_per_pixel() as usize; for y in 0..rect.height() { let (dest_row_start, src_row_start) = ( (y + rect.origin_y()) as usize * self.stride + rect.origin_x() as usize * dest_bytes_per_pixel, y as usize * src_stride, ); let dest_row_end = dest_row_start + rect.width() as usize * dest_bytes_per_pixel; let src_row_end = src_row_start + rect.width() as usize * src_bytes_per_pixel; let dest_row_pixels = &mut self.pixels[dest_row_start..dest_row_end]; let src_row_pixels = &src_bytes[src_row_start..src_row_end]; B::blit(dest_row_pixels, src_row_pixels) } } } impl fmt::Debug for Canvas { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Canvas") .field("pixels", &self.pixels.len()) // Do not dump a vector content. .field("size", &self.size) .field("stride", &self.stride) .field("format", &self.format) .finish() } } /// The image format for the canvas. #[derive(Clone, Copy, Debug, PartialEq)] pub enum Format { /// Premultiplied R8G8B8A8, little-endian. Rgba32, /// R8G8B8, little-endian. Rgb24, /// A8. A8, } impl Format { /// Returns the number of bits per pixel that this image format corresponds to. #[inline] pub fn bits_per_pixel(self) -> u8 { match self { Format::Rgba32 => 32, Format::Rgb24 => 24, Format::A8 => 8, } } /// Returns the number of color channels per pixel that this image format corresponds to. #[inline] pub fn components_per_pixel(self) -> u8 { match self { Format::Rgba32 => 4, Format::Rgb24 => 3, Format::A8 => 1, } } /// Returns the number of bits per color channel that this image format contains. #[inline] pub fn bits_per_component(self) -> u8 { self.bits_per_pixel() / self.components_per_pixel() } /// Returns the number of bytes per pixel that this image format corresponds to. #[inline] pub fn bytes_per_pixel(self) -> u8 { self.bits_per_pixel() / 8 } } /// The antialiasing strategy that should be used when rasterizing glyphs. #[derive(Clone, Copy, Debug, PartialEq)] pub enum RasterizationOptions { /// "Black-and-white" rendering. Each pixel is either entirely on or off. Bilevel, /// Grayscale antialiasing. Only one channel is used. GrayscaleAa, /// Subpixel RGB antialiasing, for LCD screens. SubpixelAa, } trait Blit { fn blit(dest: &mut [u8], src: &[u8]); } struct BlitMemcpy; impl Blit for BlitMemcpy { #[inline] fn blit(dest: &mut [u8], src: &[u8]) { dest.clone_from_slice(src) } } struct BlitRgb24ToA8; impl Blit for BlitRgb24ToA8 { #[inline] fn blit(dest: &mut [u8], src: &[u8]) { // TODO(pcwalton): SIMD. for (dest, src) in dest.iter_mut().zip(src.chunks(3)) { *dest = src[1] } } } struct BlitA8ToRgb24; impl Blit for BlitA8ToRgb24 { #[inline] fn blit(dest: &mut [u8], src: &[u8]) { for (dest, src) in dest.chunks_mut(3).zip(src.iter()) { dest[0] = *src; dest[1] = *src; dest[2] = *src; } } } struct BlitRgba32ToRgb24; impl Blit for BlitRgba32ToRgb24 { #[inline] fn blit(dest: &mut [u8], src: &[u8]) { // TODO(pcwalton): SIMD. for (dest, src) in dest.chunks_mut(3).zip(src.chunks(4)) { dest.copy_from_slice(&src[0..3]) } } } struct BlitRgb24ToRgba32; impl Blit for BlitRgb24ToRgba32 { fn blit(dest: &mut [u8], src: &[u8]) { for (dest, src) in dest.chunks_mut(4).zip(src.chunks(3)) { dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2]; dest[3] = 255; } } } font-kit-0.11.0/src/error.rs000064400000000000000000000062560072674642500137650ustar 00000000000000// font-kit/src/error.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Various types of errors that `font-kit` can return. use std::convert::From; use std::error::Error; use std::io; macro_rules! impl_display { ($enum:ident, {$($variant:pat => $fmt_string:expr),+$(,)* }) => { impl ::std::fmt::Display for $enum { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { use self::$enum::*; match &self { $( $variant => write!(f, "{}", $fmt_string), )+ } } } }; } /// Reasons why a loader might fail to load a font. #[derive(Debug)] pub enum FontLoadingError { /// The data was of a format the loader didn't recognize. UnknownFormat, /// Attempted to load an invalid index in a TrueType or OpenType font collection. /// /// For example, if a `.ttc` file has 2 fonts in it, and you ask for the 5th one, you'll get /// this error. NoSuchFontInCollection, /// Attempted to load a malformed or corrupted font. Parse, /// Attempted to load a font from the filesystem, but there is no filesystem (e.g. in /// WebAssembly). NoFilesystem, /// A disk or similar I/O error occurred while attempting to load the font. Io(io::Error), } impl Error for FontLoadingError {} impl_display! { FontLoadingError, { UnknownFormat => "unknown format", NoSuchFontInCollection => "no such font in the collection", Parse => "parse error", NoFilesystem => "no filesystem present", Io(e) => format!("I/O error: {}", e), } } impl From for FontLoadingError { fn from(error: io::Error) -> FontLoadingError { FontLoadingError::Io(error) } } /// Reasons why a font might fail to load a glyph. #[derive(Clone, Copy, PartialEq, Debug)] pub enum GlyphLoadingError { /// The font didn't contain a glyph with that ID. NoSuchGlyph, /// A platform function returned an error. PlatformError, } impl Error for GlyphLoadingError {} impl_display! { GlyphLoadingError, { NoSuchGlyph => "no such glyph", PlatformError => "platform error", } } #[cfg(target_family = "windows")] impl From for GlyphLoadingError { fn from(_err: winapi::um::winnt::HRESULT) -> GlyphLoadingError { GlyphLoadingError::PlatformError } } /// Reasons why a source might fail to look up a font or fonts. #[derive(Clone, Copy, PartialEq, Debug)] pub enum SelectionError { /// No font matching the given query was found. NotFound, /// The source was inaccessible because of an I/O or similar error. CannotAccessSource, } impl Error for SelectionError {} impl_display! { SelectionError, { NotFound => "no font found", CannotAccessSource => "failed to access source", } } font-kit-0.11.0/src/family.rs000064400000000000000000000030660072674642500141110ustar 00000000000000// font-kit/src/family.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Defines a set of faces that vary in weight, width or slope. use crate::error::FontLoadingError; use crate::family_handle::FamilyHandle; use crate::font::Font; use crate::handle::Handle; use crate::loader::Loader; /// Defines a set of faces that vary in weight, width or slope. #[derive(Debug)] pub struct Family where F: Loader, { fonts: Vec, } impl Family where F: Loader, { pub(crate) fn from_font_handles<'a, I>(font_handles: I) -> Result, FontLoadingError> where I: Iterator, { let mut fonts = vec![]; for font_handle in font_handles { fonts.push(F::from_handle(font_handle)?) } Ok(Family { fonts }) } #[inline] pub(crate) fn from_handle(family_handle: &FamilyHandle) -> Result, FontLoadingError> { Family::from_font_handles(family_handle.fonts.iter()) } /// Returns the individual fonts in this family. #[inline] pub fn fonts(&self) -> &[F] { &self.fonts } /// Returns true if and only if this family is empty. #[inline] pub fn is_empty(&self) -> bool { self.fonts.is_empty() } } font-kit-0.11.0/src/family_handle.rs000064400000000000000000000027420072674642500154240ustar 00000000000000// font-kit/src/family_handle.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Encapsulates the information needed to locate and open the fonts in a family. use crate::handle::Handle; /// Encapsulates the information needed to locate and open the fonts in a family. #[derive(Debug)] pub struct FamilyHandle { pub(crate) fonts: Vec, } impl FamilyHandle { /// Creates an empty set of family handles. #[inline] pub fn new() -> FamilyHandle { FamilyHandle { fonts: vec![] } } /// Creates a set of font family handles. #[inline] pub fn from_font_handles(fonts: I) -> FamilyHandle where I: Iterator, { FamilyHandle { fonts: fonts.collect::>(), } } /// Adds a new handle to this set. #[inline] pub fn push(&mut self, font: Handle) { self.fonts.push(font) } /// Returns true if and only if this set has no fonts in it. #[inline] pub fn is_empty(&self) -> bool { self.fonts.is_empty() } /// Returns all the handles in this set. #[inline] pub fn fonts(&self) -> &[Handle] { &self.fonts } } font-kit-0.11.0/src/family_name.rs000064400000000000000000000032120072674642500151020ustar 00000000000000// font-kit/src/descriptor.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A possible value for the `font-family` CSS property. /// A possible value for the `font-family` CSS property. /// /// These descriptions are taken from CSS Fonts Level 3 § 3.1: /// https://drafts.csswg.org/css-fonts-3/#font-family-prop. /// /// TODO(pcwalton): `system-ui`, `emoji`, `math`, `fangsong` #[derive(Clone, Debug, PartialEq)] pub enum FamilyName { /// A specific font family, specified by name: e.g. "Arial", "times". Title(String), /// Serif fonts represent the formal text style for a script. Serif, /// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low contrast /// (vertical and horizontal stems have the close to the same thickness) and have stroke /// endings that are plain — without any flaring, cross stroke, or other ornamentation. SansSerif, /// The sole criterion of a monospace font is that all glyphs have the same fixed width. Monospace, /// Glyphs in cursive fonts generally use a more informal script style, and the result looks /// more like handwritten pen or brush writing than printed letterwork. Cursive, /// Fantasy fonts are primarily decorative or expressive fonts that contain decorative or /// expressive representations of characters. Fantasy, } font-kit-0.11.0/src/file_type.rs000064400000000000000000000014600072674642500146040ustar 00000000000000// font-kit/src/file_type.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! The type of a font file: either a single font or a TrueType/OpenType collection. /// The type of a font file: either a single font or a TrueType/OpenType collection. #[derive(Clone, Copy, Debug, PartialEq)] pub enum FileType { /// The font file represents a single font (`.ttf`, `.otf`, `.woff`, etc.) Single, /// The font file represents a collection of fonts (`.ttc`, `.otc`, etc.) Collection(u32), } font-kit-0.11.0/src/font.rs000064400000000000000000000010220072674642500135640ustar 00000000000000// font-kit/src/font.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A font face loaded into memory. //! //! The Font type in this crate represents the default loader. pub use crate::loaders::default::Font; font-kit-0.11.0/src/handle.rs000064400000000000000000000047370072674642500140710ustar 00000000000000// font-kit/src/handle.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Encapsulates the information needed to locate and open a font. //! //! This is either the path to the font or the raw in-memory font data. //! //! To open the font referenced by a handle, use a loader. use std::path::PathBuf; use std::sync::Arc; use crate::error::FontLoadingError; use crate::font::Font; /// Encapsulates the information needed to locate and open a font. /// /// This is either the path to the font or the raw in-memory font data. /// /// To open the font referenced by a handle, use a loader. #[derive(Debug, Clone)] pub enum Handle { /// A font on disk referenced by a path. Path { /// The path to the font. path: PathBuf, /// The index of the font, if the path refers to a collection. /// /// If the path refers to a single font, this value will be 0. font_index: u32, }, /// A font in memory. Memory { /// The raw TrueType/OpenType/etc. data that makes up this font. bytes: Arc>, /// The index of the font, if the memory consists of a collection. /// /// If the memory consists of a single font, this value will be 0. font_index: u32, }, } impl Handle { /// Creates a new handle from a path. /// /// `font_index` specifies the index of the font to choose if the path points to a font /// collection. If the path points to a single font file, pass 0. #[inline] pub fn from_path(path: PathBuf, font_index: u32) -> Handle { Handle::Path { path, font_index } } /// Creates a new handle from raw TTF/OTF/etc. data in memory. /// /// `font_index` specifies the index of the font to choose if the memory represents a font /// collection. If the memory represents a single font file, pass 0. #[inline] pub fn from_memory(bytes: Arc>, font_index: u32) -> Handle { Handle::Memory { bytes, font_index } } /// A convenience method to load this handle with the default loader, producing a Font. #[inline] pub fn load(&self) -> Result { Font::from_handle(self) } } font-kit-0.11.0/src/hinting.rs000064400000000000000000000041660072674642500142720ustar 00000000000000// font-kit/src/hinting.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Specifies how hinting (grid fitting) is to be performed (or not performed) for a glyph. //! //! This affects both outlines and rasterization. /// Specifies how hinting (grid fitting) is to be performed (or not performed) for a glyph. /// /// This affects both outlines and rasterization. #[derive(Clone, Copy, Debug, PartialEq)] pub enum HintingOptions { /// No hinting is performed unless absolutely necessary to assemble the glyph. /// /// This corresponds to what macOS and FreeType in its "no hinting" mode do. None, /// Hinting is performed only in the vertical direction. The specified point size is used for /// grid fitting. /// /// This corresponds to what DirectWrite and FreeType in its light hinting mode do. Vertical(f32), /// Hinting is performed only in the vertical direction, and further tweaks are applied to make /// subpixel antialiasing look better. The specified point size is used for grid fitting. /// /// This matches DirectWrite, GDI in its ClearType mode, and FreeType in its LCD hinting mode. VerticalSubpixel(f32), /// Hinting is performed in both horizontal and vertical directions. The specified point size /// is used for grid fitting. /// /// This corresponds to what GDI in non-ClearType modes and FreeType in its normal hinting mode /// do. Full(f32), } impl HintingOptions { /// Returns the point size that will be used for grid fitting, if any. #[inline] pub fn grid_fitting_size(&self) -> Option { match *self { HintingOptions::None => None, HintingOptions::Vertical(size) | HintingOptions::VerticalSubpixel(size) | HintingOptions::Full(size) => Some(size), } } } font-kit-0.11.0/src/lib.rs000064400000000000000000000126230072674642500133750ustar 00000000000000// font-kit/src/lib.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! `font-kit` provides a common interface to the various system font libraries and provides //! services such as finding fonts on the system, performing nearest-font matching, and rasterizing //! glyphs. //! //! ## Synopsis //! //! # extern crate font_kit; //! # extern crate pathfinder_geometry; //! # //! use font_kit::canvas::{Canvas, Format, RasterizationOptions}; //! use font_kit::family_name::FamilyName; //! use font_kit::hinting::HintingOptions; //! use font_kit::properties::Properties; //! use font_kit::source::SystemSource; //! use pathfinder_geometry::transform2d::Transform2F; //! use pathfinder_geometry::vector::{Vector2F, Vector2I}; //! //! let font = SystemSource::new().select_best_match(&[FamilyName::SansSerif], //! &Properties::new()) //! .unwrap() //! .load() //! .unwrap(); //! let glyph_id = font.glyph_for_char('A').unwrap(); //! let mut canvas = Canvas::new(Vector2I::splat(32), Format::A8); //! font.rasterize_glyph(&mut canvas, //! glyph_id, //! 32.0, //! Transform2F::from_translation(Vector2F::new(0.0, 32.0)), //! HintingOptions::None, //! RasterizationOptions::GrayscaleAa) //! .unwrap(); //! //! ## Backends //! //! `font-kit` delegates to system libraries to perform tasks. It has two types of backends: a //! *source* and a *loader*. Sources are platform font databases; they allow lookup of installed //! fonts by name or attributes. Loaders are font loading libraries; they allow font files (TTF, //! OTF, etc.) to be loaded from a file on disk or from bytes in memory. Sources and loaders can be //! freely intermixed at runtime; fonts can be looked up via DirectWrite and rendered via FreeType, //! for example. //! //! Available loaders: //! //! * Core Text (macOS): The system font loader on macOS. Does not do hinting except when bilevel //! rendering is in use. //! //! * DirectWrite (Windows): The newer system framework for text rendering on Windows. Does //! vertical hinting but not full hinting. //! //! * FreeType (cross-platform): A full-featured font rendering framework. //! //! Available sources: //! //! * Core Text (macOS): The system font database on macOS. //! //! * DirectWrite (Windows): The newer API to query the system font database on Windows. //! //! * Fontconfig (cross-platform): A technically platform-neutral, but in practice Unix-specific, //! API to query and match fonts. //! //! * Filesystem (cross-platform): A simple source that reads fonts from a path on disk. This is //! the default on Android. //! //! * Memory (cross-platform): A source that reads from a fixed set of fonts in memory. //! //! * Multi (cross-platform): A source that allows multiple sources to be queried at once. //! //! On Windows and macOS, the FreeType loader and the Fontconfig source are not built by default. //! To build them, use the `loader-freetype` and `source-fontconfig` Cargo features respectively. //! If you want them to be the default, instead use the `loader-freetype-default` and //! `source-fontconfig-default` Cargo features respectively. Beware that //! `source-fontconfig-default` is rarely what you want on those two platforms! //! //! ## Features //! //! `font-kit` is capable of doing the following: //! //! * Loading fonts from files or memory. //! //! * Determining whether files on disk or in memory represent fonts. //! //! * Interoperating with native font APIs. //! //! * Querying various metadata about fonts. //! //! * Doing simple glyph-to-character mapping. (For more complex use cases, a shaper is required; //! proper shaping is beyond the scope of `font-kit`.) //! //! * Reading unhinted or hinted vector outlines from glyphs. //! //! * Calculating glyph and font metrics. //! //! * Looking up glyph advances and origins. //! //! * Rasterizing glyphs using the native rasterizer, optionally using hinting. (Custom //! rasterizers, such as Pathfinder, can be used in conjuction with the outline API.) //! //! * Looking up all fonts on the system. //! //! * Searching for specific fonts by family or PostScript name. //! //! * Performing font matching according to the [CSS Fonts Module Level 3] specification. //! //! ## License //! //! `font-kit` is licensed under the same terms as Rust itself. //! //! [CSS Fonts Module Level 3]: https://drafts.csswg.org/css-fonts-3/#font-matching-algorithm #![warn(missing_docs)] #![warn(missing_debug_implementations)] #![warn(missing_copy_implementations)] #[macro_use] extern crate bitflags; pub mod canvas; pub mod error; pub mod family; pub mod family_handle; pub mod family_name; pub mod file_type; pub mod font; pub mod handle; pub mod hinting; pub mod loader; pub mod loaders; pub mod metrics; pub mod outline; pub mod properties; #[cfg(feature = "source")] pub mod source; #[cfg(feature = "source")] pub mod sources; mod matching; mod utils; font-kit-0.11.0/src/loader.rs000064400000000000000000000246600072674642500141010ustar 00000000000000// font-kit/src/loader.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Provides a common interface to the platform-specific API that loads, parses, and rasterizes //! fonts. use log::warn; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::Vector2F; use std::sync::Arc; use crate::canvas::{Canvas, RasterizationOptions}; use crate::error::{FontLoadingError, GlyphLoadingError}; use crate::file_type::FileType; use crate::handle::Handle; use crate::hinting::HintingOptions; use crate::metrics::Metrics; use crate::outline::OutlineSink; use crate::properties::Properties; #[cfg(not(target_arch = "wasm32"))] use std::fs::File; #[cfg(not(target_arch = "wasm32"))] use std::path::Path; /// Provides a common interface to the platform-specific API that loads, parses, and rasterizes /// fonts. pub trait Loader: Clone + Sized { /// The handle that the API natively uses to represent a font. type NativeFont; /// Loads a font from raw font data (the contents of a `.ttf`/`.otf`/etc. file). /// /// If the data represents a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index /// of the font to load from it. If the data represents a single font, pass 0 for `font_index`. fn from_bytes(font_data: Arc>, font_index: u32) -> Result; /// Loads a font from a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. #[cfg(not(target_arch = "wasm32"))] fn from_file(file: &mut File, font_index: u32) -> Result; /// Loads a font from the path to a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. #[cfg(not(target_arch = "wasm32"))] fn from_path

(path: P, font_index: u32) -> Result where P: AsRef, { Loader::from_file(&mut File::open(path)?, font_index) } /// Creates a font from a native API handle. unsafe fn from_native_font(native_font: Self::NativeFont) -> Self; /// Loads the font pointed to by a handle. fn from_handle(handle: &Handle) -> Result { match *handle { Handle::Memory { ref bytes, font_index, } => Self::from_bytes((*bytes).clone(), font_index), #[cfg(not(target_arch = "wasm32"))] Handle::Path { ref path, font_index, } => Self::from_path(path, font_index), #[cfg(target_arch = "wasm32")] Handle::Path { .. } => Err(FontLoadingError::NoFilesystem), } } /// Determines whether a blob of raw font data represents a supported font, and, if so, what /// type of font it is. fn analyze_bytes(font_data: Arc>) -> Result; /// Determines whether a file represents a supported font, and, if so, what type of font it is. #[cfg(not(target_arch = "wasm32"))] fn analyze_file(file: &mut File) -> Result; /// Determines whether a path points to a supported font, and, if so, what type of font it is. #[inline] #[cfg(not(target_arch = "wasm32"))] fn analyze_path

(path: P) -> Result where P: AsRef, { ::analyze_file(&mut File::open(path)?) } /// Returns the wrapped native font handle. fn native_font(&self) -> Self::NativeFont; /// Returns the PostScript name of the font. This should be globally unique. fn postscript_name(&self) -> Option; /// Returns the full name of the font (also known as "display name" on macOS). fn full_name(&self) -> String; /// Returns the name of the font family. fn family_name(&self) -> String; /// Returns true if and only if the font is monospace (fixed-width). fn is_monospace(&self) -> bool; /// Returns the values of various font properties, corresponding to those defined in CSS. fn properties(&self) -> Properties; /// Returns the number of glyphs in the font. /// /// Glyph IDs range from 0 inclusive to this value exclusive. fn glyph_count(&self) -> u32; /// Returns the usual glyph ID for a Unicode character. /// /// Be careful with this function; typographically correct character-to-glyph mapping must be /// done using a *shaper* such as HarfBuzz. This function is only useful for best-effort simple /// use cases like "what does character X look like on its own". fn glyph_for_char(&self, character: char) -> Option; /// Returns the glyph ID for the specified glyph name. #[inline] fn glyph_by_name(&self, _name: &str) -> Option { warn!("unimplemented"); None } /// Sends the vector path for a glyph to a sink. /// /// If `hinting_mode` is not None, this function performs grid-fitting as requested before /// sending the hinding outlines to the builder. /// /// TODO(pcwalton): What should we do for bitmap glyphs? fn outline( &self, glyph_id: u32, hinting_mode: HintingOptions, sink: &mut S, ) -> Result<(), GlyphLoadingError> where S: OutlineSink; /// Returns the boundaries of a glyph in font units. The origin of the coordinate /// space is at the bottom left. fn typographic_bounds(&self, glyph_id: u32) -> Result; /// Returns the distance from the origin of the glyph with the given ID to the next, in font /// units. fn advance(&self, glyph_id: u32) -> Result; /// Returns the amount that the given glyph should be displaced from the origin. fn origin(&self, glyph_id: u32) -> Result; /// Retrieves various metrics that apply to the entire font. fn metrics(&self) -> Metrics; /// Returns a handle to this font, if possible. /// /// This is useful if you want to open the font with a different loader. fn handle(&self) -> Option { // FIXME(pcwalton): This doesn't handle font collections! self.copy_font_data() .map(|font_data| Handle::from_memory(font_data, 0)) } /// Attempts to return the raw font data (contents of the font file). /// /// If this font is a member of a collection, this function returns the data for the entire /// collection. fn copy_font_data(&self) -> Option>>; /// Returns true if and only if the font loader can perform hinting in the requested way. /// /// Some APIs support only rasterizing glyphs with hinting, not retriving hinted outlines. If /// `for_rasterization` is false, this function returns true if and only if the loader supports /// retrieval of hinted *outlines*. If `for_rasterization` is true, this function returns true /// if and only if the loader supports *rasterizing* hinted glyphs. fn supports_hinting_options( &self, hinting_options: HintingOptions, for_rasterization: bool, ) -> bool; /// Returns the pixel boundaries that the glyph will take up when rendered using this loader's /// rasterizer at the given `point_size` and `transform`. The origin of the coordinate space is /// at the top left. fn raster_bounds( &self, glyph_id: u32, point_size: f32, transform: Transform2F, _: HintingOptions, _: RasterizationOptions, ) -> Result { let typographic_bounds = self.typographic_bounds(glyph_id)?; let typographic_raster_bounds = typographic_bounds * (point_size / self.metrics().units_per_em as f32); // Translate the origin to "origin is top left" coordinate system. let new_origin = Vector2F::new( typographic_raster_bounds.origin_x(), -typographic_raster_bounds.origin_y() - typographic_raster_bounds.height(), ); let typographic_raster_bounds = RectF::new(new_origin, typographic_raster_bounds.size()); Ok((transform * typographic_raster_bounds).round_out().to_i32()) } /// Rasterizes a glyph to a canvas with the given size and transform. /// /// Format conversion will be performed if the canvas format does not match the rasterization /// options. For example, if bilevel (black and white) rendering is requested to an RGBA /// surface, this function will automatically convert the 1-bit raster image to the 32-bit /// format of the canvas. Note that this may result in a performance penalty, depending on the /// loader. /// /// If `hinting_options` is not None, the requested grid fitting is performed. fn rasterize_glyph( &self, canvas: &mut Canvas, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result<(), GlyphLoadingError>; /// Get font fallback results for the given text and locale. /// /// The `locale` argument is a language tag such as `"en-US"` or `"zh-Hans-CN"`. fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult; /// Returns the OpenType font table with the given tag, if the table exists. fn load_font_table(&self, table_tag: u32) -> Option>; } /// The result of a fallback query. #[derive(Debug)] pub struct FallbackResult { /// A list of fallback fonts. pub fonts: Vec>, /// The fallback list is valid for this slice of the given text. pub valid_len: usize, } /// A single font record for a fallback query result. #[derive(Debug)] pub struct FallbackFont { /// The font. pub font: Font, /// A scale factor that should be applied to the fallback font. pub scale: f32, // TODO: add font simulation data } font-kit-0.11.0/src/loaders/core_text.rs000064400000000000000000001064160072674642500162600ustar 00000000000000// font-kit/src/loaders/core_text.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A loader that uses Apple's Core Text API to load and rasterize fonts. use byteorder::{BigEndian, ReadBytesExt}; use core_graphics::base::{kCGImageAlphaPremultipliedLast, CGFloat}; use core_graphics::color_space::CGColorSpace; use core_graphics::context::{CGContext, CGTextDrawingMode}; use core_graphics::font::{CGFont, CGGlyph}; use core_graphics::geometry::{CGAffineTransform, CGPoint, CGRect, CGSize}; use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CG_ZERO_POINT, CG_ZERO_SIZE}; use core_graphics::path::CGPathElementType; use core_text; use core_text::font::CTFont; use core_text::font_descriptor::kCTFontDefaultOrientation; use core_text::font_descriptor::{SymbolicTraitAccessors, TraitAccessors}; use log::warn; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::Vector2F; use pathfinder_simd::default::F32x4; use std::cmp::Ordering; use std::f32; use std::fmt::{self, Debug, Formatter}; use std::fs::File; use std::io::{Seek, SeekFrom}; use std::ops::Deref; use std::path::Path; use std::sync::Arc; use crate::canvas::{Canvas, Format, RasterizationOptions}; use crate::error::{FontLoadingError, GlyphLoadingError}; use crate::file_type::FileType; use crate::handle::Handle; use crate::hinting::HintingOptions; use crate::loader::{FallbackResult, Loader}; use crate::metrics::Metrics; use crate::outline::OutlineSink; use crate::properties::{Properties, Stretch, Style, Weight}; use crate::utils; const TTC_TAG: [u8; 4] = [b't', b't', b'c', b'f']; const OTTO_TAG: [u8; 4] = [b'O', b'T', b'T', b'O']; const OTTO_HEX: u32 = 0x4f54544f; // 'OTTO' const TRUE_HEX: u32 = 0x74727565; // 'true' const TYP1_HEX: u32 = 0x74797031; // 'typ1' const SFNT_HEX: u32 = 0x73666e74; // 'sfnt' #[allow(non_upper_case_globals)] const kCGImageAlphaOnly: u32 = 7; pub(crate) static FONT_WEIGHT_MAPPING: [f32; 9] = [-0.7, -0.5, -0.23, 0.0, 0.2, 0.3, 0.4, 0.6, 0.8]; /// Core Text's representation of a font. pub type NativeFont = CTFont; /// A loader that uses Apple's Core Text API to load and rasterize fonts. #[derive(Clone)] pub struct Font { core_text_font: CTFont, font_data: FontData, } impl Font { /// Loads a font from raw font data (the contents of a `.ttf`/`.otf`/etc. file). /// /// If the data represents a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index /// of the font to load from it. If the data represents a single font, pass 0 for `font_index`. pub fn from_bytes( mut font_data: Arc>, font_index: u32, ) -> Result { // Sadly, there's no API to load OpenType collections on macOS, I don't believe… // If not otf/ttf or otc/ttc, we unpack it as data fork font. if !font_is_single_otf(&*font_data) && !font_is_collection(&*font_data) { let mut new_font_data = (*font_data).clone(); unpack_data_fork_font(&mut new_font_data)?; font_data = Arc::new(new_font_data); } else if font_is_collection(&*font_data) { let mut new_font_data = (*font_data).clone(); unpack_otc_font(&mut new_font_data, font_index)?; font_data = Arc::new(new_font_data); } let core_text_font = match core_text::font::new_from_buffer(&*font_data) { Ok(ct_font) => ct_font, Err(_) => return Err(FontLoadingError::Parse), }; Ok(Font { core_text_font, font_data: FontData::Memory(font_data), }) } /// Loads a font from a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. pub fn from_file(file: &mut File, font_index: u32) -> Result { file.seek(SeekFrom::Start(0))?; let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?); Font::from_bytes(font_data, font_index) } /// Loads a font from the path to a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. #[inline] pub fn from_path>(path: P, font_index: u32) -> Result { ::from_path(path, font_index) } /// Creates a font from a native API handle. pub unsafe fn from_native_font(core_text_font: NativeFont) -> Font { Font::from_core_text_font(core_text_font) } unsafe fn from_core_text_font(core_text_font: NativeFont) -> Font { let mut font_data = FontData::Unavailable; match core_text_font.url() { None => warn!("No URL found for Core Text font!"), Some(url) => match url.to_path() { Some(path) => match File::open(path) { Ok(ref mut file) => match utils::slurp_file(file) { Ok(data) => font_data = FontData::Memory(Arc::new(data)), Err(_) => warn!("Couldn't read file data for Core Text font!"), }, Err(_) => warn!("Could not open file for Core Text font!"), }, None => warn!("Could not convert URL from Core Text font to path!"), }, } Font { core_text_font, font_data, } } /// Creates a font from a Core Graphics font handle. /// /// This function is only available on the Core Text backend. pub fn from_core_graphics_font(core_graphics_font: CGFont) -> Font { unsafe { Font::from_core_text_font(core_text::font::new_from_CGFont(&core_graphics_font, 16.0)) } } /// Loads the font pointed to by a handle. #[inline] pub fn from_handle(handle: &Handle) -> Result { ::from_handle(handle) } /// Determines whether a file represents a supported font, and if so, what type of font it is. pub fn analyze_bytes(font_data: Arc>) -> Result { if let Ok(font_count) = read_number_of_fonts_from_otc_header(&font_data) { return Ok(FileType::Collection(font_count)); } match core_text::font::new_from_buffer(&*font_data) { Ok(_) => Ok(FileType::Single), Err(_) => Err(FontLoadingError::Parse), } } /// Determines whether a file represents a supported font, and if so, what type of font it is. pub fn analyze_file(file: &mut File) -> Result { file.seek(SeekFrom::Start(0))?; let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?); if let Ok(font_count) = read_number_of_fonts_from_otc_header(&font_data) { return Ok(FileType::Collection(font_count)); } match core_text::font::new_from_buffer(&*font_data) { Ok(_) => Ok(FileType::Single), Err(_) => Err(FontLoadingError::Parse), } } /// Determines whether a path points to a supported font, and if so, what type of font it is. #[inline] pub fn analyze_path>(path: P) -> Result { ::analyze_path(path) } /// Returns the wrapped native font handle. #[inline] pub fn native_font(&self) -> NativeFont { self.core_text_font.clone() } /// Returns the PostScript name of the font. This should be globally unique. #[inline] pub fn postscript_name(&self) -> Option { Some(self.core_text_font.postscript_name()) } /// Returns the full name of the font (also known as "display name" on macOS). #[inline] pub fn full_name(&self) -> String { self.core_text_font.display_name() } /// Returns the name of the font family. #[inline] pub fn family_name(&self) -> String { self.core_text_font.family_name() } /// Returns the name of the font style, according to Core Text. /// /// NB: This function is only available on the Core Text backend. #[inline] pub fn style_name(&self) -> String { self.core_text_font.style_name() } /// Returns true if and only if the font is monospace (fixed-width). #[inline] pub fn is_monospace(&self) -> bool { self.core_text_font.symbolic_traits().is_monospace() } /// Returns the values of various font properties, corresponding to those defined in CSS. pub fn properties(&self) -> Properties { let symbolic_traits = self.core_text_font.symbolic_traits(); let all_traits = self.core_text_font.all_traits(); let style = if symbolic_traits.is_italic() { Style::Italic } else if all_traits.normalized_slant() > 0.0 { Style::Oblique } else { Style::Normal }; let weight = core_text_to_css_font_weight(all_traits.normalized_weight() as f32); let stretch = core_text_width_to_css_stretchiness(all_traits.normalized_width() as f32); Properties { style, weight, stretch, } } /// Returns the number of glyphs in the font. /// /// Glyph IDs range from 0 inclusive to this value exclusive. pub fn glyph_count(&self) -> u32 { self.core_text_font.glyph_count() as u32 } /// Returns the usual glyph ID for a Unicode character. /// /// Be careful with this function; typographically correct character-to-glyph mapping must be /// done using a *shaper* such as HarfBuzz. This function is only useful for best-effort simple /// use cases like "what does character X look like on its own". pub fn glyph_for_char(&self, character: char) -> Option { unsafe { let (mut dest, mut src) = ([0, 0], [0, 0]); let src = character.encode_utf16(&mut src); self.core_text_font .get_glyphs_for_characters(src.as_ptr(), dest.as_mut_ptr(), 2); let id = dest[0] as u32; if id != 0 { Some(id) } else { None } } } /// Returns the glyph ID for the specified glyph name. #[inline] pub fn glyph_by_name(&self, name: &str) -> Option { let code = self.core_text_font.get_glyph_with_name(name); Some(u32::from(code)) } /// Sends the vector path for a glyph to a path builder. /// /// If `hinting_mode` is not None, this function performs grid-fitting as requested before /// sending the hinding outlines to the builder. /// /// TODO(pcwalton): What should we do for bitmap glyphs? pub fn outline( &self, glyph_id: u32, _: HintingOptions, sink: &mut S, ) -> Result<(), GlyphLoadingError> where S: OutlineSink, { let path = match self .core_text_font .create_path_for_glyph(glyph_id as u16, &CG_AFFINE_TRANSFORM_IDENTITY) { Ok(path) => path, Err(_) => { // This will happen if the path is empty (rdar://42832439). To distinguish this // case from the case in which the glyph does not exist, call another API. drop(self.typographic_bounds(glyph_id)?); return Ok(()); } }; let units_per_point = self.units_per_point() as f32; path.apply(&|element| { let points = element.points(); match element.element_type { CGPathElementType::MoveToPoint => { sink.move_to(points[0].to_vector() * units_per_point) } CGPathElementType::AddLineToPoint => { sink.line_to(points[0].to_vector() * units_per_point) } CGPathElementType::AddQuadCurveToPoint => sink.quadratic_curve_to( points[0].to_vector() * units_per_point, points[1].to_vector() * units_per_point, ), CGPathElementType::AddCurveToPoint => { let ctrl = LineSegment2F::new(points[0].to_vector(), points[1].to_vector()) * units_per_point; sink.cubic_curve_to(ctrl, points[2].to_vector() * units_per_point) } CGPathElementType::CloseSubpath => sink.close(), } }); Ok(()) } /// Returns the boundaries of a glyph in font units. pub fn typographic_bounds(&self, glyph_id: u32) -> Result { let rect = self .core_text_font .get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph_id as u16]); let rect = RectF::new( Vector2F::new(rect.origin.x as f32, rect.origin.y as f32), Vector2F::new(rect.size.width as f32, rect.size.height as f32), ); Ok(rect * self.units_per_point() as f32) } /// Returns the distance from the origin of the glyph with the given ID to the next, in font /// units. pub fn advance(&self, glyph_id: u32) -> Result { // FIXME(pcwalton): Apple's docs don't say what happens when the glyph is out of range! unsafe { let (glyph_id, mut advance) = (glyph_id as u16, CG_ZERO_SIZE); self.core_text_font.get_advances_for_glyphs( kCTFontDefaultOrientation, &glyph_id, &mut advance, 1, ); let advance = Vector2F::new(advance.width as f32, advance.height as f32); Ok(advance * self.units_per_point() as f32) } } /// Returns the amount that the given glyph should be displaced from the origin. pub fn origin(&self, glyph_id: u32) -> Result { unsafe { // FIXME(pcwalton): Apple's docs don't say what happens when the glyph is out of range! let (glyph_id, mut translation) = (glyph_id as u16, CG_ZERO_SIZE); self.core_text_font.get_vertical_translations_for_glyphs( kCTFontDefaultOrientation, &glyph_id, &mut translation, 1, ); let translation = Vector2F::new(translation.width as f32, translation.height as f32); Ok(translation * self.units_per_point() as f32) } } /// Retrieves various metrics that apply to the entire font. pub fn metrics(&self) -> Metrics { let units_per_em = self.core_text_font.units_per_em(); let units_per_point = (units_per_em as f64) / self.core_text_font.pt_size(); let bounding_box = self.core_text_font.bounding_box(); let bounding_box = RectF::new( Vector2F::new(bounding_box.origin.x as f32, bounding_box.origin.y as f32), Vector2F::new( bounding_box.size.width as f32, bounding_box.size.height as f32, ), ); let bounding_box = bounding_box * units_per_point as f32; Metrics { units_per_em, ascent: (self.core_text_font.ascent() * units_per_point) as f32, descent: (-self.core_text_font.descent() * units_per_point) as f32, line_gap: (self.core_text_font.leading() * units_per_point) as f32, underline_position: (self.core_text_font.underline_position() * units_per_point) as f32, underline_thickness: (self.core_text_font.underline_thickness() * units_per_point) as f32, cap_height: (self.core_text_font.cap_height() * units_per_point) as f32, x_height: (self.core_text_font.x_height() * units_per_point) as f32, bounding_box, } } /// Returns a handle to this font, if possible. /// /// This is useful if you want to open the font with a different loader. #[inline] pub fn handle(&self) -> Option { ::handle(self) } /// Attempts to return the raw font data (contents of the font file). /// /// If this font is a member of a collection, this function returns the data for the entire /// collection. pub fn copy_font_data(&self) -> Option>> { match self.font_data { FontData::Unavailable => None, FontData::Memory(ref memory) => Some((*memory).clone()), } } /// Returns the pixel boundaries that the glyph will take up when rendered using this loader's /// rasterizer at the given size and transform. #[inline] pub fn raster_bounds( &self, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result { ::raster_bounds( self, glyph_id, point_size, transform, hinting_options, rasterization_options, ) } /// Rasterizes a glyph to a canvas with the given size and origin. /// /// Format conversion will be performed if the canvas format does not match the rasterization /// options. For example, if bilevel (black and white) rendering is requested to an RGBA /// surface, this function will automatically convert the 1-bit raster image to the 32-bit /// format of the canvas. Note that this may result in a performance penalty, depending on the /// loader. /// /// If `hinting_options` is not None, the requested grid fitting is performed. /// /// TODO(pcwalton): This is woefully incomplete. See WebRender's code for a more complete /// implementation. pub fn rasterize_glyph( &self, canvas: &mut Canvas, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result<(), GlyphLoadingError> { if canvas.size.x() == 0 || canvas.size.y() == 0 { return Ok(()); } let (cg_color_space, cg_image_format) = match format_to_cg_color_space_and_image_format(canvas.format) { None => { // Core Graphics doesn't support the requested image format. Allocate a // temporary canvas, then perform color conversion. // // FIXME(pcwalton): Could improve this by only allocating a canvas with a tight // bounding rect and blitting only that part. let mut temp_canvas = Canvas::new(canvas.size, Format::Rgba32); self.rasterize_glyph( &mut temp_canvas, glyph_id, point_size, transform, hinting_options, rasterization_options, )?; canvas.blit_from_canvas(&temp_canvas); return Ok(()); } Some(cg_color_space_and_format) => cg_color_space_and_format, }; let core_graphics_context = CGContext::create_bitmap_context( Some(canvas.pixels.as_mut_ptr() as *mut _), canvas.size.x() as usize, canvas.size.y() as usize, canvas.format.bits_per_component() as usize, canvas.stride, &cg_color_space, cg_image_format, ); match canvas.format { Format::Rgba32 | Format::Rgb24 => { core_graphics_context.set_rgb_fill_color(0.0, 0.0, 0.0, 0.0); } Format::A8 => core_graphics_context.set_gray_fill_color(0.0, 0.0), } let core_graphics_size = CGSize::new(canvas.size.x() as f64, canvas.size.y() as f64); core_graphics_context.fill_rect(CGRect::new(&CG_ZERO_POINT, &core_graphics_size)); match rasterization_options { RasterizationOptions::Bilevel => { core_graphics_context.set_allows_font_smoothing(false); core_graphics_context.set_should_smooth_fonts(false); core_graphics_context.set_should_antialias(false); } RasterizationOptions::GrayscaleAa | RasterizationOptions::SubpixelAa => { // FIXME(pcwalton): These shouldn't be handled the same! core_graphics_context.set_allows_font_smoothing(true); core_graphics_context.set_should_smooth_fonts(true); core_graphics_context.set_should_antialias(true); } } match canvas.format { Format::Rgba32 | Format::Rgb24 => { core_graphics_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); } Format::A8 => core_graphics_context.set_gray_fill_color(1.0, 1.0), } // CoreGraphics origin is in the bottom left. This makes behavior consistent. core_graphics_context.translate(0.0, canvas.size.y() as CGFloat); core_graphics_context.set_font(&self.core_text_font.copy_to_CGFont()); core_graphics_context.set_font_size(point_size as CGFloat); core_graphics_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); let matrix = transform.matrix.0 * F32x4::new(1.0, -1.0, -1.0, 1.0); core_graphics_context.set_text_matrix(&CGAffineTransform { a: matrix.x() as CGFloat, b: matrix.y() as CGFloat, c: matrix.z() as CGFloat, d: matrix.w() as CGFloat, tx: transform.vector.x() as CGFloat, ty: -transform.vector.y() as CGFloat, }); let origin = CGPoint::new(0.0, 0.0); core_graphics_context.show_glyphs_at_positions(&[glyph_id as CGGlyph], &[origin]); Ok(()) } /// Returns true if and only if the font loader can perform hinting in the requested way. /// /// Some APIs support only rasterizing glyphs with hinting, not retriving hinted outlines. If /// `for_rasterization` is false, this function returns true if and only if the loader supports /// retrieval of hinted *outlines*. If `for_rasterization` is true, this function returns true /// if and only if the loader supports *rasterizing* hinted glyphs. #[inline] pub fn supports_hinting_options(&self, hinting_options: HintingOptions, _: bool) -> bool { match hinting_options { HintingOptions::None => true, HintingOptions::Vertical(..) | HintingOptions::VerticalSubpixel(..) | HintingOptions::Full(..) => false, } } /// Get font fallback results for the given text and locale. /// /// Note: this is currently just a stub implementation, a proper implementation /// would use CTFontCopyDefaultCascadeListForLanguages. fn get_fallbacks(&self, text: &str, _locale: &str) -> FallbackResult { warn!("unsupported"); FallbackResult { fonts: Vec::new(), valid_len: text.len(), } } #[inline] fn units_per_point(&self) -> f64 { (self.core_text_font.units_per_em() as f64) / self.core_text_font.pt_size() } /// Returns the raw contents of the OpenType table with the given tag. /// /// Tags are four-character codes. A list of tags can be found in the [OpenType specification]. /// /// [OpenType specification]: https://docs.microsoft.com/en-us/typography/opentype/spec/ #[inline] pub fn load_font_table(&self, table_tag: u32) -> Option> { self.core_text_font .get_font_table(table_tag) .map(|data| data.bytes().into()) } } impl Loader for Font { type NativeFont = NativeFont; #[inline] fn from_bytes(font_data: Arc>, font_index: u32) -> Result { Font::from_bytes(font_data, font_index) } #[inline] fn from_file(file: &mut File, font_index: u32) -> Result { Font::from_file(file, font_index) } #[inline] unsafe fn from_native_font(native_font: Self::NativeFont) -> Self { Font::from_native_font(native_font) } #[inline] fn analyze_bytes(font_data: Arc>) -> Result { Font::analyze_bytes(font_data) } #[inline] fn analyze_file(file: &mut File) -> Result { Font::analyze_file(file) } #[inline] fn native_font(&self) -> Self::NativeFont { self.native_font() } #[inline] fn postscript_name(&self) -> Option { self.postscript_name() } #[inline] fn full_name(&self) -> String { self.full_name() } #[inline] fn family_name(&self) -> String { self.family_name() } #[inline] fn is_monospace(&self) -> bool { self.is_monospace() } #[inline] fn properties(&self) -> Properties { self.properties() } #[inline] fn glyph_for_char(&self, character: char) -> Option { self.glyph_for_char(character) } #[inline] fn glyph_by_name(&self, name: &str) -> Option { self.glyph_by_name(name) } #[inline] fn glyph_count(&self) -> u32 { self.glyph_count() } #[inline] fn outline( &self, glyph_id: u32, hinting_mode: HintingOptions, sink: &mut S, ) -> Result<(), GlyphLoadingError> where S: OutlineSink, { self.outline(glyph_id, hinting_mode, sink) } #[inline] fn typographic_bounds(&self, glyph_id: u32) -> Result { self.typographic_bounds(glyph_id) } #[inline] fn advance(&self, glyph_id: u32) -> Result { self.advance(glyph_id) } #[inline] fn origin(&self, glyph_id: u32) -> Result { self.origin(glyph_id) } #[inline] fn metrics(&self) -> Metrics { self.metrics() } #[inline] fn copy_font_data(&self) -> Option>> { self.copy_font_data() } #[inline] fn supports_hinting_options( &self, hinting_options: HintingOptions, for_rasterization: bool, ) -> bool { self.supports_hinting_options(hinting_options, for_rasterization) } #[inline] fn rasterize_glyph( &self, canvas: &mut Canvas, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result<(), GlyphLoadingError> { self.rasterize_glyph( canvas, glyph_id, point_size, transform, hinting_options, rasterization_options, ) } #[inline] fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult { self.get_fallbacks(text, locale) } #[inline] fn load_font_table(&self, table_tag: u32) -> Option> { self.load_font_table(table_tag) } } impl Debug for Font { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { self.full_name().fmt(fmt) } } #[derive(Clone)] enum FontData { Unavailable, Memory(Arc>), } impl Deref for FontData { type Target = [u8]; fn deref(&self) -> &[u8] { match *self { FontData::Unavailable => panic!("Font data unavailable!"), FontData::Memory(ref data) => &***data, } } } trait CGPointExt { fn to_vector(&self) -> Vector2F; } impl CGPointExt for CGPoint { #[inline] fn to_vector(&self) -> Vector2F { Vector2F::new(self.x as f32, self.y as f32) } } fn core_text_to_css_font_weight(core_text_weight: f32) -> Weight { let index = piecewise_linear_find_index(core_text_weight, &FONT_WEIGHT_MAPPING); Weight(index * 100.0 + 100.0) } fn core_text_width_to_css_stretchiness(core_text_width: f32) -> Stretch { Stretch(piecewise_linear_lookup( (core_text_width + 1.0) * 4.0, &Stretch::MAPPING, )) } fn font_is_collection(header: &[u8]) -> bool { header.len() >= 4 && header[0..4] == TTC_TAG } fn read_number_of_fonts_from_otc_header(header: &[u8]) -> Result { if !font_is_collection(header) { return Err(FontLoadingError::UnknownFormat); } Ok((&header[8..]).read_u32::()?) } fn get_slice_from_start(slice: &[u8], start: usize) -> Result<&[u8], FontLoadingError> { slice.get(start..).ok_or(FontLoadingError::Parse) } // Unpacks an OTC font "in-place". fn unpack_otc_font(data: &mut [u8], font_index: u32) -> Result<(), FontLoadingError> { if font_index >= read_number_of_fonts_from_otc_header(data)? { return Err(FontLoadingError::NoSuchFontInCollection); } let offset_table_pos_pos = 12 + 4 * font_index as usize; let offset_table_pos = get_slice_from_start(&data, offset_table_pos_pos)?.read_u32::()? as usize; debug_assert!(utils::SFNT_VERSIONS .iter() .any(|version| { data[offset_table_pos..(offset_table_pos + 4)] == *version })); let num_tables = get_slice_from_start(&data, offset_table_pos + 4)?.read_u16::()?; // Must copy forward in order to avoid problems with overlapping memory. let offset_table_and_table_record_size = 12 + (num_tables as usize) * 16; for offset in 0..offset_table_and_table_record_size { data[offset] = data[offset_table_pos + offset] } Ok(()) } // NB: This assumes little-endian, but that's true for all extant Apple hardware. fn format_to_cg_color_space_and_image_format(format: Format) -> Option<(CGColorSpace, u32)> { match format { Format::Rgb24 => { // Unsupported by Core Graphics. None } Format::Rgba32 => Some(( CGColorSpace::create_device_rgb(), kCGImageAlphaPremultipliedLast, )), Format::A8 => Some((CGColorSpace::create_device_gray(), kCGImageAlphaOnly)), } } fn font_is_single_otf(header: &[u8]) -> bool { header.len() >= 4 && ((&header[..4]).read_u32::().unwrap() == 0x00010000 || header[..4] == OTTO_TAG) } /// https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf#page=151 fn unpack_data_fork_font(data: &mut [u8]) -> Result<(), FontLoadingError> { let data_offset = (&data[..]).read_u32::()? as usize; let map_offset = get_slice_from_start(&data, 4)?.read_u32::()? as usize; let num_types = get_slice_from_start(&data, map_offset + 28)?.read_u16::()? as usize + 1; let mut font_data_offset = 0; let mut font_data_len = 0; let type_list_offset = get_slice_from_start(&data, map_offset + 24)?.read_u16::()? as usize + map_offset; for i in 0..num_types { let res_type = get_slice_from_start(&data, map_offset + 30 + i * 8)?.read_u32::()?; if res_type == SFNT_HEX { let ref_list_offset = get_slice_from_start(&data, map_offset + 30 + i * 8 + 6)? .read_u16::()? as usize; let res_data_offset = get_slice_from_start(&data, type_list_offset + ref_list_offset + 5)? .read_u24::()? as usize; font_data_len = get_slice_from_start(&data, data_offset + res_data_offset)? .read_u32::()? as usize; font_data_offset = data_offset + res_data_offset + 4; let sfnt_version = get_slice_from_start(&data, font_data_offset)?.read_u32::()?; // TrueType outline, 'OTTO', 'true', 'typ1' if sfnt_version == 0x00010000 || sfnt_version == OTTO_HEX || sfnt_version == TRUE_HEX || sfnt_version == TYP1_HEX { break; } } } if font_data_len == 0 { return Err(FontLoadingError::Parse); } for offset in 0..font_data_len { data[offset] = data[font_data_offset + offset]; } Ok(()) } #[cfg(test)] mod test { use super::Font; use crate::properties::{Stretch, Weight}; #[cfg(feature = "source")] use crate::source::SystemSource; static TEST_FONT_POSTSCRIPT_NAME: &'static str = "ArialMT"; #[cfg(feature = "source")] #[test] fn test_from_core_graphics_font() { let font0 = SystemSource::new() .select_by_postscript_name(TEST_FONT_POSTSCRIPT_NAME) .unwrap() .load() .unwrap(); let core_text_font = font0.native_font(); let core_graphics_font = core_text_font.copy_to_CGFont(); let font1 = Font::from_core_graphics_font(core_graphics_font); assert_eq!(font1.postscript_name().unwrap(), TEST_FONT_POSTSCRIPT_NAME); } #[test] fn test_core_text_to_css_font_weight() { // Exact matches assert_eq!(super::core_text_to_css_font_weight(-0.7), Weight(100.0)); assert_eq!(super::core_text_to_css_font_weight(0.0), Weight(400.0)); assert_eq!(super::core_text_to_css_font_weight(0.4), Weight(700.0)); assert_eq!(super::core_text_to_css_font_weight(0.8), Weight(900.0)); // Linear interpolation assert_eq!(super::core_text_to_css_font_weight(0.1), Weight(450.0)); } #[test] fn test_core_text_to_css_font_stretch() { // Exact matches assert_eq!( super::core_text_width_to_css_stretchiness(0.0), Stretch(1.0) ); assert_eq!( super::core_text_width_to_css_stretchiness(-1.0), Stretch(0.5) ); assert_eq!( super::core_text_width_to_css_stretchiness(1.0), Stretch(2.0) ); // Linear interpolation assert_eq!( super::core_text_width_to_css_stretchiness(0.85), Stretch(1.7) ); } } pub(crate) fn piecewise_linear_lookup(index: f32, mapping: &[f32]) -> f32 { let lower_value = mapping[f32::floor(index) as usize]; let upper_value = mapping[f32::ceil(index) as usize]; utils::lerp(lower_value, upper_value, f32::fract(index)) } pub(crate) fn piecewise_linear_find_index(query_value: f32, mapping: &[f32]) -> f32 { let upper_index = match mapping .binary_search_by(|value| value.partial_cmp(&query_value).unwrap_or(Ordering::Less)) { Ok(index) => return index as f32, Err(upper_index) => upper_index, }; if upper_index == 0 || upper_index >= mapping.len() { return upper_index as f32; } let lower_index = upper_index - 1; let (upper_value, lower_value) = (mapping[upper_index], mapping[lower_index]); let t = (query_value - lower_value) / (upper_value - lower_value); lower_index as f32 + t } font-kit-0.11.0/src/loaders/directwrite.rs000064400000000000000000001034370072674642500166110ustar 00000000000000// font-kit/src/loaders/directwrite.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A loader that uses the Windows DirectWrite API to load and rasterize fonts. use byteorder::{BigEndian, ReadBytesExt}; use dwrote::CustomFontCollectionLoaderImpl; use dwrote::Font as DWriteFont; use dwrote::FontCollection as DWriteFontCollection; use dwrote::FontFace as DWriteFontFace; use dwrote::FontFallback as DWriteFontFallback; use dwrote::FontFile as DWriteFontFile; use dwrote::FontMetrics as DWriteFontMetrics; use dwrote::FontStyle as DWriteFontStyle; use dwrote::GlyphOffset as DWriteGlyphOffset; use dwrote::GlyphRunAnalysis as DWriteGlyphRunAnalysis; use dwrote::InformationalStringId as DWriteInformationalStringId; use dwrote::OutlineBuilder as DWriteOutlineBuilder; use dwrote::{DWRITE_TEXTURE_ALIASED_1x1, DWRITE_TEXTURE_CLEARTYPE_3x1}; use dwrote::{DWRITE_GLYPH_RUN, DWRITE_MEASURING_MODE_NATURAL}; use dwrote::{DWRITE_RENDERING_MODE_ALIASED, DWRITE_RENDERING_MODE_NATURAL}; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::{Vector2F, Vector2I}; use std::borrow::Cow; use std::ffi::OsString; use std::fmt::{self, Debug, Formatter}; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom}; use std::os::windows::ffi::OsStringExt; use std::os::windows::io::AsRawHandle; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use winapi::shared::minwindef::{FALSE, MAX_PATH}; use winapi::um::dwrite::DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE; use winapi::um::dwrite::DWRITE_READING_DIRECTION; use winapi::um::dwrite::DWRITE_READING_DIRECTION_LEFT_TO_RIGHT; use winapi::um::fileapi; use crate::canvas::{Canvas, Format, RasterizationOptions}; use crate::error::{FontLoadingError, GlyphLoadingError}; use crate::file_type::FileType; use crate::handle::Handle; use crate::hinting::HintingOptions; use crate::loader::{FallbackFont, FallbackResult, Loader}; use crate::metrics::Metrics; use crate::outline::{OutlineBuilder, OutlineSink}; use crate::properties::{Properties, Stretch, Style, Weight}; const ERROR_BOUND: f32 = 0.0001; const OPENTYPE_TABLE_TAG_HEAD: u32 = 0x68656164; /// DirectWrite's representation of a font. #[allow(missing_debug_implementations)] pub struct NativeFont { /// The native DirectWrite font object. pub dwrite_font: DWriteFont, /// The native DirectWrite font face object. pub dwrite_font_face: DWriteFontFace, } /// A loader that uses the Windows DirectWrite API to load and rasterize fonts. pub struct Font { dwrite_font: DWriteFont, dwrite_font_face: DWriteFontFace, cached_data: Mutex>>>, } struct MyTextAnalysisSource { text_utf16_len: u32, locale: String, } impl dwrote::TextAnalysisSourceMethods for MyTextAnalysisSource { fn get_locale_name<'a>(&'a self, text_pos: u32) -> (Cow<'a, str>, u32) { (self.locale.as_str().into(), self.text_utf16_len - text_pos) } fn get_paragraph_reading_direction(&self) -> DWRITE_READING_DIRECTION { DWRITE_READING_DIRECTION_LEFT_TO_RIGHT } } impl Font { fn from_dwrite_font_file( font_file: DWriteFontFile, mut font_index: u32, font_data: Option>>, ) -> Result { let collection_loader = CustomFontCollectionLoaderImpl::new(&[font_file.clone()]); let collection = DWriteFontCollection::from_loader(collection_loader); let families = collection.families_iter(); for family in families { for family_font_index in 0..family.get_font_count() { if font_index > 0 { font_index -= 1; continue; } let dwrite_font = family.get_font(family_font_index); let dwrite_font_face = dwrite_font.create_font_face(); return Ok(Font { dwrite_font, dwrite_font_face, cached_data: Mutex::new(font_data), }); } } Err(FontLoadingError::NoSuchFontInCollection) } /// Loads a font from raw font data (the contents of a `.ttf`/`.otf`/etc. file). /// /// If the data represents a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index /// of the font to load from it. If the data represents a single font, pass 0 for `font_index`. pub fn from_bytes(font_data: Arc>, font_index: u32) -> Result { let font_file = DWriteFontFile::new_from_data(font_data.clone()).ok_or(FontLoadingError::Parse)?; Font::from_dwrite_font_file(font_file, font_index, Some(font_data)) } /// Loads a font from a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. pub fn from_file(file: &mut File, font_index: u32) -> Result { unsafe { let mut path = vec![0; MAX_PATH + 1]; let path_len = fileapi::GetFinalPathNameByHandleW( file.as_raw_handle(), path.as_mut_ptr(), path.len() as u32 - 1, 0, ); if path_len == 0 { return Err(FontLoadingError::Io(io::Error::last_os_error())); } path.truncate(path_len as usize); Font::from_path(PathBuf::from(OsString::from_wide(&path)), font_index) } } /// Loads a font from the path to a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. #[inline] pub fn from_path>(path: P, font_index: u32) -> Result { let font_file = DWriteFontFile::new_from_path(path).ok_or(FontLoadingError::Parse)?; Font::from_dwrite_font_file(font_file, font_index, None) } /// Creates a font from a native API handle. #[inline] pub unsafe fn from_native_font(native_font: NativeFont) -> Font { Font { dwrite_font: native_font.dwrite_font, dwrite_font_face: native_font.dwrite_font_face, cached_data: Mutex::new(None), } } /// Loads the font pointed to by a handle. #[inline] pub fn from_handle(handle: &Handle) -> Result { ::from_handle(handle) } /// Determines whether a blob of raw font data represents a supported font, and, if so, what /// type of font it is. pub fn analyze_bytes(font_data: Arc>) -> Result { match DWriteFontFile::analyze_data(font_data) { 0 => Err(FontLoadingError::Parse), 1 => Ok(FileType::Single), font_count => Ok(FileType::Collection(font_count)), } } /// Determines whether a file represents a supported font, and, if so, what type of font it is. pub fn analyze_file(file: &mut File) -> Result { let mut font_data = vec![]; file.seek(SeekFrom::Start(0)) .map_err(FontLoadingError::Io)?; match file.read_to_end(&mut font_data) { Err(io_error) => Err(FontLoadingError::Io(io_error)), Ok(_) => Font::analyze_bytes(Arc::new(font_data)), } } /// Returns the wrapped native font handle. pub fn native_font(&self) -> NativeFont { NativeFont { dwrite_font: self.dwrite_font.clone(), dwrite_font_face: self.dwrite_font_face.clone(), } } /// Determines whether a path points to a supported font, and, if so, what type of font it is. #[inline] pub fn analyze_path>(path: P) -> Result { ::analyze_path(path) } /// Returns the PostScript name of the font. This should be globally unique. #[inline] pub fn postscript_name(&self) -> Option { let dwrite_font = &self.dwrite_font; dwrite_font.informational_string(DWriteInformationalStringId::PostscriptName) } /// Returns the full name of the font (also known as "display name" on macOS). #[inline] pub fn full_name(&self) -> String { let dwrite_font = &self.dwrite_font; dwrite_font .informational_string(DWriteInformationalStringId::FullName) .unwrap_or_else(|| dwrite_font.family_name()) } /// Returns the name of the font family. #[inline] pub fn family_name(&self) -> String { self.dwrite_font.family_name() } /// Returns true if and only if the font is monospace (fixed-width). #[inline] pub fn is_monospace(&self) -> bool { self.dwrite_font.is_monospace().unwrap_or(false) } /// Returns the values of various font properties, corresponding to those defined in CSS. pub fn properties(&self) -> Properties { let dwrite_font = &self.dwrite_font; Properties { style: style_for_dwrite_style(dwrite_font.style()), stretch: Stretch(Stretch::MAPPING[(dwrite_font.stretch() as usize) - 1]), weight: Weight(dwrite_font.weight().to_u32() as f32), } } /// Returns the usual glyph ID for a Unicode character. /// /// Be careful with this function; typographically correct character-to-glyph mapping must be /// done using a *shaper* such as HarfBuzz. This function is only useful for best-effort simple /// use cases like "what does character X look like on its own". pub fn glyph_for_char(&self, character: char) -> Option { let chars = [character as u32]; self.dwrite_font_face .get_glyph_indices(&chars) .into_iter() .next() .and_then(|g| { // 0 means the char is not present in the font per // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getglyphindices if g != 0 { Some(g as u32) } else { None } }) } /// Returns the number of glyphs in the font. /// /// Glyph IDs range from 0 inclusive to this value exclusive. #[inline] pub fn glyph_count(&self) -> u32 { self.dwrite_font_face.get_glyph_count() as u32 } /// Sends the vector path for a glyph to a path builder. /// /// If `hinting_mode` is not None, this function performs grid-fitting as requested before /// sending the hinding outlines to the builder. /// /// TODO(pcwalton): What should we do for bitmap glyphs? pub fn outline( &self, glyph_id: u32, _: HintingOptions, sink: &mut S, ) -> Result<(), GlyphLoadingError> where S: OutlineSink, { let outline_sink = OutlineCanonicalizer::new(); self.dwrite_font_face.get_glyph_run_outline( self.metrics().units_per_em as f32, &[glyph_id as u16], None, None, false, false, Box::new(outline_sink.clone()), ); outline_sink .0 .lock() .unwrap() .builder .take_outline() .copy_to(&mut *sink); Ok(()) } /// Returns the boundaries of a glyph in font units. pub fn typographic_bounds(&self, glyph_id: u32) -> Result { let metrics = self .dwrite_font_face .get_design_glyph_metrics(&[glyph_id as u16], false); let metrics = &metrics[0]; let advance_width = metrics.advanceWidth as i32; let advance_height = metrics.advanceHeight as i32; let left_side_bearing = metrics.leftSideBearing as i32; let right_side_bearing = metrics.rightSideBearing as i32; let top_side_bearing = metrics.topSideBearing as i32; let bottom_side_bearing = metrics.bottomSideBearing as i32; let vertical_origin_y = metrics.verticalOriginY as i32; let y_offset = vertical_origin_y + bottom_side_bearing - advance_height; let width = advance_width - (left_side_bearing + right_side_bearing); let height = advance_height - (top_side_bearing + bottom_side_bearing); Ok(RectI::new( Vector2I::new(left_side_bearing, y_offset), Vector2I::new(width, height), ) .to_f32()) } /// Returns the distance from the origin of the glyph with the given ID to the next, in font /// units. pub fn advance(&self, glyph_id: u32) -> Result { let metrics = self .dwrite_font_face .get_design_glyph_metrics(&[glyph_id as u16], false); let metrics = &metrics[0]; Ok(Vector2F::new(metrics.advanceWidth as f32, 0.0)) } /// Returns the amount that the given glyph should be displaced from the origin. pub fn origin(&self, glyph: u32) -> Result { let metrics = self .dwrite_font_face .get_design_glyph_metrics(&[glyph as u16], false); Ok(Vector2I::new( metrics[0].leftSideBearing, metrics[0].verticalOriginY + metrics[0].bottomSideBearing, ) .to_f32()) } /// Retrieves various metrics that apply to the entire font. pub fn metrics(&self) -> Metrics { let dwrite_font = &self.dwrite_font; // Unfortunately, the bounding box info is Windows 8 only, so we need a fallback. First, // try to grab it from the font. If that fails, we try the `head` table. If there's no // `head` table, we give up. match dwrite_font.metrics() { DWriteFontMetrics::Metrics1(metrics) => Metrics { units_per_em: metrics.designUnitsPerEm as u32, ascent: metrics.ascent as f32, descent: -(metrics.descent as f32), line_gap: metrics.lineGap as f32, cap_height: metrics.capHeight as f32, x_height: metrics.xHeight as f32, underline_position: metrics.underlinePosition as f32, underline_thickness: metrics.underlineThickness as f32, bounding_box: RectI::new( Vector2I::new(metrics.glyphBoxLeft as i32, metrics.glyphBoxBottom as i32), Vector2I::new( metrics.glyphBoxRight as i32 - metrics.glyphBoxLeft as i32, metrics.glyphBoxTop as i32 - metrics.glyphBoxBottom as i32, ), ) .to_f32(), }, DWriteFontMetrics::Metrics0(metrics) => { let bounding_box = match self .dwrite_font_face .get_font_table(OPENTYPE_TABLE_TAG_HEAD.swap_bytes()) { Some(head) => { let mut reader = &head[36..]; let x_min = reader.read_i16::().unwrap(); let y_min = reader.read_i16::().unwrap(); let x_max = reader.read_i16::().unwrap(); let y_max = reader.read_i16::().unwrap(); RectI::new( Vector2I::new(x_min as i32, y_min as i32), Vector2I::new(x_max as i32 - x_min as i32, y_max as i32 - y_min as i32), ) .to_f32() } None => RectF::default(), }; Metrics { units_per_em: metrics.designUnitsPerEm as u32, ascent: metrics.ascent as f32, descent: -(metrics.descent as f32), line_gap: metrics.lineGap as f32, cap_height: metrics.capHeight as f32, x_height: metrics.xHeight as f32, underline_position: metrics.underlinePosition as f32, underline_thickness: metrics.underlineThickness as f32, bounding_box, } } } } /// Returns a handle to this font, if possible. /// /// This is useful if you want to open the font with a different loader. #[inline] pub fn handle(&self) -> Option { ::handle(self) } /// Attempts to return the raw font data (contents of the font file). /// /// If this font is a member of a collection, this function returns the data for the entire /// collection. pub fn copy_font_data(&self) -> Option>> { let mut font_data = self.cached_data.lock().unwrap(); if font_data.is_none() { let files = self.dwrite_font_face.get_files(); // FIXME(pcwalton): Is this right? When can a font have multiple files? if let Some(file) = files.get(0) { *font_data = Some(Arc::new(file.get_font_file_bytes())) } } (*font_data).clone() } /// Returns the pixel boundaries that the glyph will take up when rendered using this loader's /// rasterizer at the given size and origin. #[inline] pub fn raster_bounds( &self, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result { let dwrite_analysis = self.build_glyph_analysis( glyph_id, point_size, transform, hinting_options, rasterization_options, )?; let texture_type = match rasterization_options { RasterizationOptions::Bilevel => DWRITE_TEXTURE_ALIASED_1x1, RasterizationOptions::GrayscaleAa | RasterizationOptions::SubpixelAa => { DWRITE_TEXTURE_CLEARTYPE_3x1 } }; let texture_bounds = dwrite_analysis.get_alpha_texture_bounds(texture_type)?; let texture_width = texture_bounds.right - texture_bounds.left; let texture_height = texture_bounds.bottom - texture_bounds.top; Ok(RectI::new( Vector2I::new(texture_bounds.left, texture_bounds.top), Vector2I::new(texture_width, texture_height), )) } /// Rasterizes a glyph to a canvas with the given size and origin. /// /// Format conversion will be performed if the canvas format does not match the rasterization /// options. For example, if bilevel (black and white) rendering is requested to an RGBA /// surface, this function will automatically convert the 1-bit raster image to the 32-bit /// format of the canvas. Note that this may result in a performance penalty, depending on the /// loader. /// /// If `hinting_options` is not None, the requested grid fitting is performed. pub fn rasterize_glyph( &self, canvas: &mut Canvas, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result<(), GlyphLoadingError> { // TODO(pcwalton): This is woefully incomplete. See WebRender's code for a more complete // implementation. let dwrite_analysis = self.build_glyph_analysis( glyph_id, point_size, transform, hinting_options, rasterization_options, )?; let texture_type = match rasterization_options { RasterizationOptions::Bilevel => DWRITE_TEXTURE_ALIASED_1x1, RasterizationOptions::GrayscaleAa | RasterizationOptions::SubpixelAa => { DWRITE_TEXTURE_CLEARTYPE_3x1 } }; // TODO(pcwalton): Avoid a copy in some cases by writing directly to the canvas. let texture_bounds = dwrite_analysis.get_alpha_texture_bounds(texture_type)?; let texture_width = texture_bounds.right - texture_bounds.left; let texture_height = texture_bounds.bottom - texture_bounds.top; // 'Returns an empty rectangle if there are no glyphs of the specified texture type.' // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwriteglyphrunanalysis-getalphatexturebounds if texture_width == 0 || texture_height == 0 { return Ok(()); } let texture_format = if texture_type == DWRITE_TEXTURE_ALIASED_1x1 { Format::A8 } else { Format::Rgb24 }; let texture_bits_per_pixel = texture_format.bits_per_pixel(); let texture_bytes_per_pixel = texture_bits_per_pixel as usize / 8; let texture_size = Vector2I::new(texture_width, texture_height); let texture_stride = texture_width as usize * texture_bytes_per_pixel; let mut texture_bytes = dwrite_analysis.create_alpha_texture(texture_type, texture_bounds)?; canvas.blit_from( Vector2I::new(texture_bounds.left, texture_bounds.top), &mut texture_bytes, texture_size, texture_stride, texture_format, ); Ok(()) } /// Returns true if and only if the font loader can perform hinting in the requested way. /// /// Some APIs support only rasterizing glyphs with hinting, not retriving hinted outlines. If /// `for_rasterization` is false, this function returns true if and only if the loader supports /// retrieval of hinted *outlines*. If `for_rasterization` is true, this function returns true /// if and only if the loader supports *rasterizing* hinted glyphs. pub fn supports_hinting_options( &self, hinting_options: HintingOptions, for_rasterization: bool, ) -> bool { match (hinting_options, for_rasterization) { (HintingOptions::None, _) | (HintingOptions::Vertical(_), true) | (HintingOptions::VerticalSubpixel(_), true) => true, (HintingOptions::Vertical(_), false) | (HintingOptions::VerticalSubpixel(_), false) | (HintingOptions::Full(_), _) => false, } } fn build_glyph_analysis( &self, glyph_id: u32, point_size: f32, transform: Transform2F, _hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result { unsafe { let glyph_id = glyph_id as u16; let advance = 0.0; let offset = DWriteGlyphOffset { advanceOffset: 0.0, ascenderOffset: 0.0, }; let glyph_run = DWRITE_GLYPH_RUN { fontFace: self.dwrite_font_face.as_ptr(), fontEmSize: point_size, glyphCount: 1, glyphIndices: &glyph_id, glyphAdvances: &advance, glyphOffsets: &offset, isSideways: FALSE, bidiLevel: 0, }; let rendering_mode = match rasterization_options { RasterizationOptions::Bilevel => DWRITE_RENDERING_MODE_ALIASED, RasterizationOptions::GrayscaleAa | RasterizationOptions::SubpixelAa => { DWRITE_RENDERING_MODE_NATURAL } }; Ok(DWriteGlyphRunAnalysis::create( &glyph_run, 1.0, Some(dwrote::DWRITE_MATRIX { m11: transform.m11(), m12: transform.m12(), m21: transform.m21(), m22: transform.m22(), dx: transform.vector.x(), dy: transform.vector.y(), }), rendering_mode, DWRITE_MEASURING_MODE_NATURAL, 0.0, 0.0, )?) } } /// Get font fallback results for the given text and locale. /// /// The `locale` argument is a language tag such as `"en-US"` or `"zh-Hans-CN"`. /// /// Note: on Windows 10, the result is a single font. fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult { let sys_fallback = DWriteFontFallback::get_system_fallback(); if sys_fallback.is_none() { unimplemented!("Need Windows 7 method for font fallbacks") } let text_utf16: Vec = text.encode_utf16().collect(); let text_utf16_len = text_utf16.len() as u32; let number_subst = dwrote::NumberSubstitution::new(DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, locale, true); let text_analysis_source = MyTextAnalysisSource { text_utf16_len, locale: locale.to_owned(), }; let text_analysis = dwrote::TextAnalysisSource::from_text_and_number_subst( Box::new(text_analysis_source), text_utf16.into(), number_subst, ); let sys_fallback = sys_fallback.unwrap(); // TODO: I think the MapCharacters can take a null pointer, update // dwrote to accept an optional collection. This appears to be what // blink does. let collection = DWriteFontCollection::get_system(false); let fallback_result = sys_fallback.map_characters( &text_analysis, 0, text_utf16_len, &collection, Some(&self.dwrite_font.family_name()), self.dwrite_font.weight(), self.dwrite_font.style(), self.dwrite_font.stretch(), ); let valid_len = convert_len_utf16_to_utf8(text, fallback_result.mapped_length); let fonts = if let Some(dwrite_font) = fallback_result.mapped_font { let dwrite_font_face = dwrite_font.create_font_face(); let font = Font { dwrite_font, dwrite_font_face, cached_data: Mutex::new(None), }; let fallback_font = FallbackFont { font, scale: fallback_result.scale, }; vec![fallback_font] } else { vec![] }; FallbackResult { fonts, valid_len } } /// Returns the raw contents of the OpenType table with the given tag. /// /// Tags are four-character codes. A list of tags can be found in the [OpenType specification]. /// /// [OpenType specification]: https://docs.microsoft.com/en-us/typography/opentype/spec/ pub fn load_font_table(&self, table_tag: u32) -> Option> { self.dwrite_font_face .get_font_table(table_tag.swap_bytes()) .map(|v| v.into()) } } // There might well be a more efficient impl that doesn't fully decode the text, // just looks at the utf-8 bytes. fn convert_len_utf16_to_utf8(text: &str, len_utf16: usize) -> usize { let mut l_utf8 = 0; let mut l_utf16 = 0; let mut chars = text.chars(); while l_utf16 < len_utf16 { if let Some(c) = chars.next() { l_utf8 += c.len_utf8(); l_utf16 += c.len_utf16(); } else { break; } } l_utf8 } impl Clone for Font { #[inline] fn clone(&self) -> Font { Font { dwrite_font: self.dwrite_font.clone(), dwrite_font_face: self.dwrite_font_face.clone(), cached_data: Mutex::new((*self.cached_data.lock().unwrap()).clone()), } } } impl Debug for Font { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { self.family_name().fmt(fmt) } } impl Loader for Font { type NativeFont = NativeFont; #[inline] fn from_bytes(font_data: Arc>, font_index: u32) -> Result { Font::from_bytes(font_data, font_index) } #[inline] fn from_file(file: &mut File, font_index: u32) -> Result { Font::from_file(file, font_index) } fn from_path

(path: P, font_index: u32) -> Result where P: AsRef, { Font::from_path(path, font_index) } #[inline] unsafe fn from_native_font(native_font: Self::NativeFont) -> Self { Font::from_native_font(native_font) } #[inline] fn analyze_bytes(font_data: Arc>) -> Result { Font::analyze_bytes(font_data) } #[inline] fn analyze_file(file: &mut File) -> Result { Font::analyze_file(file) } #[inline] fn native_font(&self) -> Self::NativeFont { self.native_font() } #[inline] fn postscript_name(&self) -> Option { self.postscript_name() } #[inline] fn full_name(&self) -> String { self.full_name() } #[inline] fn family_name(&self) -> String { self.family_name() } #[inline] fn is_monospace(&self) -> bool { self.is_monospace() } #[inline] fn properties(&self) -> Properties { self.properties() } #[inline] fn glyph_for_char(&self, character: char) -> Option { self.glyph_for_char(character) } #[inline] fn glyph_count(&self) -> u32 { self.glyph_count() } #[inline] fn outline( &self, glyph_id: u32, hinting: HintingOptions, sink: &mut S, ) -> Result<(), GlyphLoadingError> where S: OutlineSink, { self.outline(glyph_id, hinting, sink) } #[inline] fn typographic_bounds(&self, glyph_id: u32) -> Result { self.typographic_bounds(glyph_id) } #[inline] fn advance(&self, glyph_id: u32) -> Result { self.advance(glyph_id) } #[inline] fn origin(&self, origin: u32) -> Result { self.origin(origin) } #[inline] fn metrics(&self) -> Metrics { self.metrics() } #[inline] fn supports_hinting_options( &self, hinting_options: HintingOptions, for_rasterization: bool, ) -> bool { self.supports_hinting_options(hinting_options, for_rasterization) } #[inline] fn copy_font_data(&self) -> Option>> { self.copy_font_data() } #[inline] fn rasterize_glyph( &self, canvas: &mut Canvas, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result<(), GlyphLoadingError> { self.rasterize_glyph( canvas, glyph_id, point_size, transform, hinting_options, rasterization_options, ) } #[inline] fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult { self.get_fallbacks(text, locale) } #[inline] fn load_font_table(&self, table_tag: u32) -> Option> { self.load_font_table(table_tag) } } #[derive(Clone)] struct OutlineCanonicalizer(Arc>); struct OutlineCanonicalizerInfo { builder: OutlineBuilder, last_position: Vector2F, } impl OutlineCanonicalizer { fn new() -> OutlineCanonicalizer { OutlineCanonicalizer(Arc::new(Mutex::new(OutlineCanonicalizerInfo { builder: OutlineBuilder::new(), last_position: Vector2F::default(), }))) } } impl DWriteOutlineBuilder for OutlineCanonicalizer { fn move_to(&mut self, to_x: f32, to_y: f32) { let to = Vector2F::new(to_x, -to_y); let mut this = self.0.lock().unwrap(); this.last_position = to; this.builder.move_to(to); } fn line_to(&mut self, to_x: f32, to_y: f32) { let to = Vector2F::new(to_x, -to_y); let mut this = self.0.lock().unwrap(); this.last_position = to; this.builder.line_to(to); } fn close(&mut self) { let mut this = self.0.lock().unwrap(); this.builder.close(); } fn curve_to( &mut self, ctrl0_x: f32, ctrl0_y: f32, ctrl1_x: f32, ctrl1_y: f32, to_x: f32, to_y: f32, ) { let ctrl = LineSegment2F::new( Vector2F::new(ctrl0_x, -ctrl0_y), Vector2F::new(ctrl1_x, -ctrl1_y), ); let to = Vector2F::new(to_x, -to_y); // This might be a degree-elevated quadratic curve. Try to detect that. // See Sederberg § 2.6, "Distance Between Two Bézier Curves". let mut this = self.0.lock().unwrap(); let baseline = LineSegment2F::new(this.last_position, to); let approx_ctrl = LineSegment2F((ctrl * 3.0).0 - baseline.0) * 0.5; let delta_ctrl = (approx_ctrl.to() - approx_ctrl.from()) * 2.0; let max_error = delta_ctrl.length() / 6.0; if max_error < ERROR_BOUND { // Round to nearest 0.5. let approx_ctrl = (approx_ctrl.midpoint() * 2.0).round() * 0.5; this.builder.quadratic_curve_to(approx_ctrl, to); } else { this.builder.cubic_curve_to(ctrl, to); } this.last_position = to; } } fn style_for_dwrite_style(style: DWriteFontStyle) -> Style { match style { DWriteFontStyle::Normal => Style::Normal, DWriteFontStyle::Oblique => Style::Oblique, DWriteFontStyle::Italic => Style::Italic, } } font-kit-0.11.0/src/loaders/freetype.rs000064400000000000000000001262320072674642500161050ustar 00000000000000// font-kit/src/loaders/freetype.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A cross-platform loader that uses the FreeType library to load and rasterize fonts. //! //! On macOS and Windows, the Cargo feature `loader-freetype-default` can be used to opt into this //! loader by default. use byteorder::{BigEndian, ReadBytesExt}; use freetype::freetype::{FT_Byte, FT_Done_Face, FT_Error, FT_Face, FT_FACE_FLAG_FIXED_WIDTH}; use freetype::freetype::{ FT_Fixed, FT_Get_Char_Index, FT_Get_Name_Index, FT_Get_Postscript_Name, FT_Pos, }; use freetype::freetype::{FT_Get_Sfnt_Table, FT_Init_FreeType, FT_LcdFilter, FT_Library}; use freetype::freetype::{FT_Library_SetLcdFilter, FT_Load_Glyph, FT_LOAD_DEFAULT}; use freetype::freetype::{FT_Load_Sfnt_Table, FT_Long, FT_Matrix, FT_New_Memory_Face}; use freetype::freetype::{FT_Reference_Face, FT_Set_Char_Size, FT_Set_Transform, FT_Sfnt_Tag}; use freetype::freetype::{FT_UInt, FT_ULong, FT_UShort, FT_Vector, FT_STYLE_FLAG_ITALIC}; use freetype::freetype::{FT_LOAD_MONOCHROME, FT_LOAD_NO_HINTING, FT_LOAD_RENDER}; use freetype::tt_os2::TT_OS2; use log::warn; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::{Vector2F, Vector2I}; use pathfinder_simd::default::F32x4; use std::f32; use std::ffi::{CStr, CString}; use std::fmt::{self, Debug, Formatter}; use std::io::{Seek, SeekFrom}; use std::iter; use std::mem; use std::os::raw::{c_char, c_void}; use std::ptr; use std::slice; use std::sync::Arc; use crate::canvas::{Canvas, Format, RasterizationOptions}; use crate::error::{FontLoadingError, GlyphLoadingError}; use crate::file_type::FileType; use crate::handle::Handle; use crate::hinting::HintingOptions; use crate::loader::{FallbackResult, Loader}; use crate::metrics::Metrics; use crate::outline::OutlineSink; use crate::properties::{Properties, Stretch, Style, Weight}; use crate::utils; #[cfg(not(target_arch = "wasm32"))] use std::fs::File; #[cfg(not(target_arch = "wasm32"))] use std::path::Path; const PS_DICT_FULL_NAME: u32 = 38; const TT_NAME_ID_FULL_NAME: u16 = 4; const TT_PLATFORM_APPLE_UNICODE: u16 = 0; const FT_POINT_TAG_ON_CURVE: c_char = 0x01; const FT_POINT_TAG_CUBIC_CONTROL: c_char = 0x02; const FT_RENDER_MODE_NORMAL: u32 = 0; const FT_RENDER_MODE_LIGHT: u32 = 1; const FT_RENDER_MODE_MONO: u32 = 2; const FT_RENDER_MODE_LCD: u32 = 3; const FT_LOAD_TARGET_LIGHT: u32 = (FT_RENDER_MODE_LIGHT & 15) << 16; const FT_LOAD_TARGET_LCD: u32 = (FT_RENDER_MODE_LCD & 15) << 16; const FT_LOAD_TARGET_MONO: u32 = (FT_RENDER_MODE_MONO & 15) << 16; const FT_LOAD_TARGET_NORMAL: u32 = (FT_RENDER_MODE_NORMAL & 15) << 16; const FT_PIXEL_MODE_MONO: u8 = 1; const FT_PIXEL_MODE_GRAY: u8 = 2; const FT_PIXEL_MODE_LCD: u8 = 5; const FT_PIXEL_MODE_LCD_V: u8 = 6; const OS2_FS_SELECTION_OBLIQUE: u16 = 1 << 9; // Not in our FreeType bindings, so we define these ourselves. #[allow(dead_code)] const BDF_PROPERTY_TYPE_NONE: BDF_PropertyType = 0; #[allow(dead_code)] const BDF_PROPERTY_TYPE_ATOM: BDF_PropertyType = 1; #[allow(dead_code)] const BDF_PROPERTY_TYPE_INTEGER: BDF_PropertyType = 2; #[allow(dead_code)] const BDF_PROPERTY_TYPE_CARDINAL: BDF_PropertyType = 3; thread_local! { static FREETYPE_LIBRARY: FT_Library = { unsafe { let mut library = ptr::null_mut(); assert_eq!(FT_Init_FreeType(&mut library), 0); FT_Library_SetLcdFilter(library, FT_LcdFilter::FT_LCD_FILTER_DEFAULT); library } }; } /// The handle that the FreeType API natively uses to represent a font. pub type NativeFont = FT_Face; // Not in our FreeType bindings, so we define this ourselves. #[allow(non_camel_case_types)] type BDF_PropertyType = i32; // Not in our FreeType bindings, so we define this ourselves. #[repr(C)] struct BDF_PropertyRec { property_type: BDF_PropertyType, value: *const c_char, } /// A cross-platform loader that uses the FreeType library to load and rasterize fonts. /// /// /// On macOS and Windows, the Cargo feature `loader-freetype-default` can be used to opt into this /// loader by default. pub struct Font { freetype_face: FT_Face, font_data: Arc>, } impl Font { /// Loads a font from raw font data (the contents of a `.ttf`/`.otf`/etc. file). /// /// If the data represents a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index /// of the font to load from it. If the data represents a single font, pass 0 for `font_index`. pub fn from_bytes(font_data: Arc>, font_index: u32) -> Result { FREETYPE_LIBRARY.with(|freetype_library| unsafe { let mut freetype_face = ptr::null_mut(); if FT_New_Memory_Face( *freetype_library, (*font_data).as_ptr(), font_data.len() as FT_Long, font_index as FT_Long, &mut freetype_face, ) != 0 { return Err(FontLoadingError::Parse); } setup_freetype_face(freetype_face); Ok(Font { freetype_face, font_data, }) }) } /// Loads a font from a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. #[cfg(not(target_arch = "wasm32"))] pub fn from_file(file: &mut File, font_index: u32) -> Result { file.seek(SeekFrom::Start(0))?; let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?); Font::from_bytes(font_data, font_index) } /// Loads a font from the path to a `.ttf`/`.otf`/etc. file. /// /// If the file is a collection (`.ttc`/`.otc`/etc.), `font_index` specifies the index of the /// font to load from it. If the file represents a single font, pass 0 for `font_index`. #[inline] #[cfg(not(target_arch = "wasm32"))] pub fn from_path

(path: P, font_index: u32) -> Result where P: AsRef, { // TODO(pcwalton): Perhaps use the native FreeType support for opening paths? ::from_path(path, font_index) } /// Creates a font from a native API handle. pub unsafe fn from_native_font(freetype_face: NativeFont) -> Font { // We make an in-memory copy of the underlying font data. This is because the native font // does not necessarily hold a strong reference to the memory backing it. const CHUNK_SIZE: usize = 4096; let mut font_data = vec![]; loop { font_data.extend(iter::repeat(0).take(CHUNK_SIZE)); let freetype_stream = (*freetype_face).stream; let n_read = ((*freetype_stream).read.unwrap())( freetype_stream, font_data.len() as FT_ULong, font_data.as_mut_ptr(), CHUNK_SIZE as FT_ULong, ); if n_read < CHUNK_SIZE as FT_ULong { break; } } Font::from_bytes(Arc::new(font_data), (*freetype_face).face_index as u32).unwrap() } /// Loads the font pointed to by a handle. #[inline] pub fn from_handle(handle: &Handle) -> Result { ::from_handle(handle) } /// Determines whether a blob of raw font data represents a supported font, and, if so, what /// type of font it is. pub fn analyze_bytes(font_data: Arc>) -> Result { FREETYPE_LIBRARY.with(|freetype_library| unsafe { let mut freetype_face = ptr::null_mut(); if FT_New_Memory_Face( *freetype_library, (*font_data).as_ptr(), font_data.len() as FT_Long, 0, &mut freetype_face, ) != 0 { return Err(FontLoadingError::Parse); } let font_type = match (*freetype_face).num_faces { 1 => FileType::Single, num_faces => FileType::Collection(num_faces as u32), }; FT_Done_Face(freetype_face); Ok(font_type) }) } /// Determines whether a file represents a supported font, and, if so, what type of font it is. #[cfg(not(target_arch = "wasm32"))] pub fn analyze_file(file: &mut File) -> Result { FREETYPE_LIBRARY.with(|freetype_library| unsafe { file.seek(SeekFrom::Start(0))?; let font_data = Arc::new(utils::slurp_file(file).map_err(FontLoadingError::Io)?); let mut freetype_face = ptr::null_mut(); if FT_New_Memory_Face( *freetype_library, (*font_data).as_ptr(), font_data.len() as FT_Long, 0, &mut freetype_face, ) != 0 { return Err(FontLoadingError::Parse); } let font_type = match (*freetype_face).num_faces { 1 => FileType::Single, num_faces => FileType::Collection(num_faces as u32), }; FT_Done_Face(freetype_face); Ok(font_type) }) } /// Determines whether a path points to a supported font, and, if so, what type of font it is. #[inline] #[cfg(not(target_arch = "wasm32"))] pub fn analyze_path

(path: P) -> Result where P: AsRef, { ::analyze_path(path) } /// Returns the wrapped native font handle. /// /// This function increments the reference count of the FreeType face before returning it. /// Therefore, it is the caller's responsibility to free it with `FT_Done_Face`. pub fn native_font(&self) -> NativeFont { unsafe { assert_eq!(FT_Reference_Face(self.freetype_face), 0); self.freetype_face } } /// Returns the PostScript name of the font. This should be globally unique. pub fn postscript_name(&self) -> Option { unsafe { let postscript_name = FT_Get_Postscript_Name(self.freetype_face); if !postscript_name.is_null() { return Some(CStr::from_ptr(postscript_name).to_str().unwrap().to_owned()); } let font_format = FT_Get_Font_Format(self.freetype_face); assert!(!font_format.is_null()); let font_format = CStr::from_ptr(font_format).to_str().unwrap(); if font_format != "BDF" && font_format != "PCF" { return None; } let mut property = mem::zeroed(); if FT_Get_BDF_Property( self.freetype_face, "_DEC_DEVICE_FONTNAMES\0".as_ptr() as *const c_char, &mut property, ) != 0 { return None; } if property.property_type != BDF_PROPERTY_TYPE_ATOM { return None; } let dec_device_fontnames = CStr::from_ptr(property.value).to_str().unwrap(); if !dec_device_fontnames.starts_with("PS=") { return None; } Some(dec_device_fontnames[3..].to_string()) } } /// Returns the full name of the font (also known as "display name" on macOS). pub fn full_name(&self) -> String { self.get_type_1_or_sfnt_name(PS_DICT_FULL_NAME, TT_NAME_ID_FULL_NAME) .unwrap_or_else(|| self.family_name()) } /// Returns the name of the font family. pub fn family_name(&self) -> String { unsafe { let ptr = (*self.freetype_face).family_name; // FreeType doesn't guarantee a non-null family name (see issue #5). if ptr.is_null() { String::new() } else { CStr::from_ptr(ptr).to_str().unwrap().to_owned() } } } /// Returns true if and only if the font is monospace (fixed-width). pub fn is_monospace(&self) -> bool { unsafe { (*self.freetype_face).face_flags & (FT_FACE_FLAG_FIXED_WIDTH as FT_Long) != 0 } } /// Returns the values of various font properties, corresponding to those defined in CSS. pub fn properties(&self) -> Properties { unsafe { let os2_table = self.get_os2_table(); let style = match os2_table { Some(os2_table) if ((*os2_table).fsSelection & OS2_FS_SELECTION_OBLIQUE) != 0 => { Style::Oblique } _ if ((*self.freetype_face).style_flags & (FT_STYLE_FLAG_ITALIC) as FT_Long) != 0 => { Style::Italic } _ => Style::Normal, }; let stretch = match os2_table { Some(os2_table) if (1..=9).contains(&(*os2_table).usWidthClass) => { Stretch(Stretch::MAPPING[((*os2_table).usWidthClass as usize) - 1]) } _ => Stretch::NORMAL, }; let weight = match os2_table { None => Weight::NORMAL, Some(os2_table) => Weight((*os2_table).usWeightClass as f32), }; Properties { style, stretch, weight, } } } /// Returns the usual glyph ID for a Unicode character. /// /// Be careful with this function; typographically correct character-to-glyph mapping must be /// done using a *shaper* such as HarfBuzz. This function is only useful for best-effort simple /// use cases like "what does character X look like on its own". #[inline] pub fn glyph_for_char(&self, character: char) -> Option { unsafe { let res = FT_Get_Char_Index(self.freetype_face, character as FT_ULong); match res { 0 => None, _ => Some(res), } } } /// Returns the glyph ID for the specified glyph name. #[inline] pub fn glyph_by_name(&self, name: &str) -> Option { if let Ok(ffi_name) = CString::new(name) { let code = unsafe { FT_Get_Name_Index(self.freetype_face, ffi_name.as_ptr() as *mut c_char) }; if code > 0 { return Some(u32::from(code)); } } None } /// Returns the number of glyphs in the font. /// /// Glyph IDs range from 0 inclusive to this value exclusive. #[inline] pub fn glyph_count(&self) -> u32 { unsafe { (*self.freetype_face).num_glyphs as u32 } } /// Sends the vector path for a glyph to a path builder. /// /// If `hinting_mode` is not None, this function performs grid-fitting as requested before /// sending the hinding outlines to the builder. /// /// TODO(pcwalton): What should we do for bitmap glyphs? pub fn outline( &self, glyph_id: u32, hinting: HintingOptions, sink: &mut S, ) -> Result<(), GlyphLoadingError> where S: OutlineSink, { unsafe { let rasterization_options = RasterizationOptions::GrayscaleAa; let load_flags = self .hinting_and_rasterization_options_to_load_flags(hinting, rasterization_options); let units_per_em = (*self.freetype_face).units_per_EM; let grid_fitting_size = hinting.grid_fitting_size(); if let Some(size) = grid_fitting_size { assert_eq!( FT_Set_Char_Size(self.freetype_face, size.f32_to_ft_fixed_26_6(), 0, 0, 0), 0 ); } if FT_Load_Glyph(self.freetype_face, glyph_id, load_flags as i32) != 0 { return Err(GlyphLoadingError::NoSuchGlyph); } let outline = &(*(*self.freetype_face).glyph).outline; let contours = slice::from_raw_parts((*outline).contours, (*outline).n_contours as usize); let point_positions = slice::from_raw_parts((*outline).points, (*outline).n_points as usize); let point_tags = slice::from_raw_parts((*outline).tags, (*outline).n_points as usize); let mut current_point_index = 0; for &last_point_index_in_contour in contours { let last_point_index_in_contour = last_point_index_in_contour as usize; let (mut first_point, first_tag) = get_point( &mut current_point_index, point_positions, point_tags, last_point_index_in_contour, grid_fitting_size, units_per_em, ); if (first_tag & FT_POINT_TAG_ON_CURVE) == 0 { // Rare, but can happen; e.g. with Inconsolata (see pathfinder#84). // // FIXME(pcwalton): I'm not sure this is right. let mut temp_point_index = last_point_index_in_contour; let (last_point, last_tag) = get_point( &mut temp_point_index, point_positions, point_tags, last_point_index_in_contour, grid_fitting_size, units_per_em, ); if (last_tag & FT_POINT_TAG_ON_CURVE) != 0 { first_point = last_point } else { first_point = last_point.lerp(first_point, 0.5) } // Back up so we properly process the first point as a control point. current_point_index -= 1; } sink.move_to(first_point); while current_point_index <= last_point_index_in_contour { let (mut point0, tag0) = get_point( &mut current_point_index, point_positions, point_tags, last_point_index_in_contour, grid_fitting_size, units_per_em, ); if (tag0 & FT_POINT_TAG_ON_CURVE) != 0 { sink.line_to(point0); continue; } loop { if current_point_index > last_point_index_in_contour { // The *last* point in the contour is off the curve. So we just need to // close the contour with a quadratic Bézier curve. sink.quadratic_curve_to(point0, first_point); break; } let (point1, tag1) = get_point( &mut current_point_index, point_positions, point_tags, last_point_index_in_contour, grid_fitting_size, units_per_em, ); if (tag0 & FT_POINT_TAG_CUBIC_CONTROL) != 0 { let ctrl = LineSegment2F::new(point0, point1); if current_point_index <= last_point_index_in_contour { // FIXME(pcwalton): Can we have implied on-curve points for cubic // control points too? let (point2, _) = get_point( &mut current_point_index, point_positions, point_tags, last_point_index_in_contour, grid_fitting_size, units_per_em, ); sink.cubic_curve_to(ctrl, point2); } else { // Last point on the contour. Use first_point as point2. sink.cubic_curve_to(ctrl, first_point); } break; } if (tag1 & FT_POINT_TAG_ON_CURVE) != 0 { sink.quadratic_curve_to(point0, point1); break; } // We have an implied on-curve point midway between the two consecutive // off-curve points. let point_half = point0.lerp(point1, 0.5); sink.quadratic_curve_to(point0, point_half); point0 = point1; } } sink.close(); } if hinting.grid_fitting_size().is_some() { reset_freetype_face_char_size((*self).freetype_face) } } return Ok(()); fn get_point( current_point_index: &mut usize, point_positions: &[FT_Vector], point_tags: &[c_char], last_point_index_in_contour: usize, grid_fitting_size: Option, units_per_em: u16, ) -> (Vector2F, c_char) { assert!(*current_point_index <= last_point_index_in_contour); let point_position = point_positions[*current_point_index]; let point_tag = point_tags[*current_point_index]; *current_point_index += 1; let point_position = Vector2I::new(point_position.x as i32, point_position.y as i32); let mut point_position = point_position.ft_fixed_26_6_to_f32(); if let Some(grid_fitting_size) = grid_fitting_size { point_position = point_position * (units_per_em as f32) / grid_fitting_size; } (point_position, point_tag) } } /// Returns the boundaries of a glyph in font units. pub fn typographic_bounds(&self, glyph_id: u32) -> Result { unsafe { if FT_Load_Glyph( self.freetype_face, glyph_id, (FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING) as i32, ) != 0 { return Err(GlyphLoadingError::NoSuchGlyph); } let metrics = &(*(*self.freetype_face).glyph).metrics; let rect = RectI::new( Vector2I::new( metrics.horiBearingX as i32, (metrics.horiBearingY - metrics.height) as i32, ), Vector2I::new(metrics.width as i32, metrics.height as i32), ); Ok(rect.ft_fixed_26_6_to_f32()) } } /// Returns the distance from the origin of the glyph with the given ID to the next, in font /// units. pub fn advance(&self, glyph_id: u32) -> Result { unsafe { if FT_Load_Glyph( self.freetype_face, glyph_id, (FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING) as i32, ) != 0 { return Err(GlyphLoadingError::NoSuchGlyph); } let advance = (*(*self.freetype_face).glyph).advance; Ok(Vector2I::new(advance.x as i32, advance.y as i32).ft_fixed_26_6_to_f32()) } } /// Returns the amount that the given glyph should be displaced from the origin. /// /// FIXME(pcwalton): This always returns zero on FreeType. pub fn origin(&self, _: u32) -> Result { warn!("unimplemented"); Ok(Vector2F::default()) } /// Retrieves various metrics that apply to the entire font. pub fn metrics(&self) -> Metrics { let os2_table = self.get_os2_table(); unsafe { let ascender = (*self.freetype_face).ascender; let descender = (*self.freetype_face).descender; let underline_position = (*self.freetype_face).underline_position; let underline_thickness = (*self.freetype_face).underline_thickness; let bbox = (*self.freetype_face).bbox; let bounding_box_origin = Vector2I::new(bbox.xMin as i32, bbox.yMin as i32); let bounding_box_lower_right = Vector2I::new(bbox.xMax as i32, bbox.yMax as i32); let bounding_box = RectI::from_points(bounding_box_origin, bounding_box_lower_right); Metrics { units_per_em: (*self.freetype_face).units_per_EM as u32, ascent: ascender as f32, descent: descender as f32, line_gap: ((*self.freetype_face).height + descender - ascender) as f32, underline_position: (underline_position + underline_thickness / 2) as f32, underline_thickness: underline_thickness as f32, cap_height: os2_table .map(|table| (*table).sCapHeight as f32) .unwrap_or(0.0), x_height: os2_table .map(|table| (*table).sxHeight as f32) .unwrap_or(0.0), bounding_box: bounding_box.to_f32(), } } } /// Returns true if and only if the font loader can perform hinting in the requested way. /// /// Some APIs support only rasterizing glyphs with hinting, not retriving hinted outlines. If /// `for_rasterization` is false, this function returns true if and only if the loader supports /// retrieval of hinted *outlines*. If `for_rasterization` is true, this function returns true /// if and only if the loader supports *rasterizing* hinted glyphs. #[inline] pub fn supports_hinting_options( &self, hinting_options: HintingOptions, for_rasterization: bool, ) -> bool { match (hinting_options, for_rasterization) { (HintingOptions::None, _) | (HintingOptions::Vertical(_), true) | (HintingOptions::VerticalSubpixel(_), true) | (HintingOptions::Full(_), true) => true, (HintingOptions::Vertical(_), false) | (HintingOptions::VerticalSubpixel(_), false) | (HintingOptions::Full(_), false) => false, } } fn get_type_1_or_sfnt_name(&self, type_1_id: u32, sfnt_id: u16) -> Option { unsafe { let ps_value_size = FT_Get_PS_Font_Value(self.freetype_face, type_1_id, 0, ptr::null_mut(), 0); if ps_value_size > 0 { let mut buffer = vec![0; ps_value_size as usize]; if FT_Get_PS_Font_Value( self.freetype_face, type_1_id, 0, buffer.as_mut_ptr() as *mut c_void, buffer.len() as FT_Long, ) == 0 { return String::from_utf8(buffer).ok(); } } let sfnt_name_count = FT_Get_Sfnt_Name_Count(self.freetype_face); let mut sfnt_name = mem::zeroed(); for sfnt_name_index in 0..sfnt_name_count { assert_eq!( FT_Get_Sfnt_Name(self.freetype_face, sfnt_name_index, &mut sfnt_name), 0 ); if sfnt_name.name_id != sfnt_id { continue; } match (sfnt_name.platform_id, sfnt_name.encoding_id) { (TT_PLATFORM_APPLE_UNICODE, _) => { let mut sfnt_name_bytes = slice::from_raw_parts(sfnt_name.string, sfnt_name.string_len as usize); let mut sfnt_name_string = Vec::with_capacity(sfnt_name_bytes.len() / 2); while !sfnt_name_bytes.is_empty() { sfnt_name_string.push(sfnt_name_bytes.read_u16::().unwrap()) } if let Ok(result) = String::from_utf16(&sfnt_name_string) { return Some(result); } } (platform_id, _) => { warn!( "get_type_1_or_sfnt_name(): found invalid platform ID {}", platform_id ); // TODO(pcwalton) } } } None } } fn get_os2_table(&self) -> Option<*const TT_OS2> { unsafe { let table = FT_Get_Sfnt_Table(self.freetype_face, FT_Sfnt_Tag::FT_SFNT_OS2); if table.is_null() { None } else { Some(table as *const TT_OS2) } } } /// Returns the pixel boundaries that the glyph will take up when rendered using this loader's /// rasterizer at the given size and origin. #[inline] pub fn raster_bounds( &self, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result { ::raster_bounds( self, glyph_id, point_size, transform, hinting_options, rasterization_options, ) } /// Rasterizes a glyph to a canvas with the given size and origin. /// /// Format conversion will be performed if the canvas format does not match the rasterization /// options. For example, if bilevel (black and white) rendering is requested to an RGBA /// surface, this function will automatically convert the 1-bit raster image to the 32-bit /// format of the canvas. Note that this may result in a performance penalty, depending on the /// loader. /// /// If `hinting_options` is not None, the requested grid fitting is performed. pub fn rasterize_glyph( &self, canvas: &mut Canvas, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result<(), GlyphLoadingError> { // TODO(pcwalton): This is woefully incomplete. See WebRender's code for a more complete // implementation. unsafe { let matrix = transform.matrix.0 * F32x4::new(65536.0, -65536.0, -65536.0, 65536.0); let matrix = matrix.to_i32x4(); let vector = transform.vector.f32_to_ft_fixed_26_6(); let mut delta = FT_Vector { x: vector.x() as FT_Pos, y: -vector.y() as FT_Pos, }; let mut ft_shape = FT_Matrix { xx: matrix.x() as FT_Fixed, xy: matrix.y() as FT_Fixed, yx: matrix.z() as FT_Fixed, yy: matrix.w() as FT_Fixed, }; FT_Set_Transform(self.freetype_face, &mut ft_shape, &mut delta); assert_eq!( FT_Set_Char_Size( self.freetype_face, point_size.f32_to_ft_fixed_26_6(), 0, 0, 0 ), 0 ); let mut load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; load_flags |= self.hinting_and_rasterization_options_to_load_flags( hinting_options, rasterization_options, ); if FT_Load_Glyph(self.freetype_face, glyph_id, load_flags as i32) != 0 { return Err(GlyphLoadingError::NoSuchGlyph); } // TODO(pcwalton): Use the FreeType "direct" API to save a copy here. Note that we will // need to keep this around for bilevel rendering, as the direct API doesn't work with // that mode. let bitmap = &(*(*self.freetype_face).glyph).bitmap; let bitmap_stride = (*bitmap).pitch as usize; let bitmap_width = (*bitmap).width as i32; let bitmap_height = (*bitmap).rows as i32; let bitmap_size = Vector2I::new(bitmap_width, bitmap_height); let bitmap_buffer = (*bitmap).buffer as *const i8 as *const u8; let bitmap_length = bitmap_stride * bitmap_height as usize; let buffer = slice::from_raw_parts(bitmap_buffer, bitmap_length); let dst_point = Vector2I::new( (*(*self.freetype_face).glyph).bitmap_left, -(*(*self.freetype_face).glyph).bitmap_top, ); // FIXME(pcwalton): This function should return a Result instead. match (*bitmap).pixel_mode { FT_PIXEL_MODE_GRAY => { canvas.blit_from(dst_point, buffer, bitmap_size, bitmap_stride, Format::A8); } FT_PIXEL_MODE_LCD | FT_PIXEL_MODE_LCD_V => { canvas.blit_from(dst_point, buffer, bitmap_size, bitmap_stride, Format::Rgb24); } FT_PIXEL_MODE_MONO => { canvas.blit_from_bitmap_1bpp(dst_point, buffer, bitmap_size, bitmap_stride); } _ => panic!("Unexpected FreeType pixel mode!"), } FT_Set_Transform(self.freetype_face, ptr::null_mut(), ptr::null_mut()); reset_freetype_face_char_size(self.freetype_face); Ok(()) } } fn hinting_and_rasterization_options_to_load_flags( &self, hinting: HintingOptions, rasterization: RasterizationOptions, ) -> u32 { let mut options = match (hinting, rasterization) { (HintingOptions::VerticalSubpixel(_), _) | (_, RasterizationOptions::SubpixelAa) => { FT_LOAD_TARGET_LCD } (HintingOptions::None, _) => FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING, (HintingOptions::Vertical(_), RasterizationOptions::Bilevel) | (HintingOptions::Full(_), RasterizationOptions::Bilevel) => FT_LOAD_TARGET_MONO, (HintingOptions::Vertical(_), _) => FT_LOAD_TARGET_LIGHT, (HintingOptions::Full(_), _) => FT_LOAD_TARGET_NORMAL, }; if rasterization == RasterizationOptions::Bilevel { options |= FT_LOAD_MONOCHROME } options } /// Returns a handle to this font, if possible. /// /// This is useful if you want to open the font with a different loader. #[inline] pub fn handle(&self) -> Option { ::handle(self) } /// Attempts to return the raw font data (contents of the font file). /// /// If this font is a member of a collection, this function returns the data for the entire /// collection. pub fn copy_font_data(&self) -> Option>> { Some(self.font_data.clone()) } /// Get font fallback results for the given text and locale. /// /// Note: this is currently just a stub implementation, a proper implementation /// would likely use FontConfig, at least on Linux. It's not clear what a /// FreeType loader with a non-FreeType source should do. fn get_fallbacks(&self, text: &str, _locale: &str) -> FallbackResult { warn!("unsupported"); FallbackResult { fonts: Vec::new(), valid_len: text.len(), } } /// Returns the raw contents of the OpenType table with the given tag. /// /// Tags are four-character codes. A list of tags can be found in the [OpenType specification]. /// /// [OpenType specification]: https://docs.microsoft.com/en-us/typography/opentype/spec/ pub fn load_font_table(&self, table_tag: u32) -> Option> { unsafe { let mut len = 0; if 0 != FT_Load_Sfnt_Table( self.freetype_face, table_tag as FT_ULong, 0, ptr::null_mut(), &mut len, ) { return None; } let mut buf = Box::<[u8]>::from(vec![0; len as usize]); if 0 != FT_Load_Sfnt_Table( self.freetype_face, table_tag as FT_ULong, 0, buf.as_mut_ptr() as *mut FT_Byte, &mut len, ) { return None; } Some(buf) } } } impl Clone for Font { fn clone(&self) -> Font { unsafe { assert_eq!(FT_Reference_Face(self.freetype_face), 0); Font { freetype_face: self.freetype_face, font_data: self.font_data.clone(), } } } } impl Drop for Font { fn drop(&mut self) { unsafe { if !self.freetype_face.is_null() { assert_eq!(FT_Done_Face(self.freetype_face), 0); } } } } impl Debug for Font { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { self.family_name().fmt(fmt) } } impl Loader for Font { type NativeFont = NativeFont; #[inline] fn from_bytes(font_data: Arc>, font_index: u32) -> Result { Font::from_bytes(font_data, font_index) } #[inline] #[cfg(not(target_arch = "wasm32"))] fn from_file(file: &mut File, font_index: u32) -> Result { Font::from_file(file, font_index) } #[inline] fn analyze_bytes(font_data: Arc>) -> Result { Font::analyze_bytes(font_data) } #[cfg(not(target_arch = "wasm32"))] fn analyze_file(file: &mut File) -> Result { Font::analyze_file(file) } #[inline] fn native_font(&self) -> Self::NativeFont { self.native_font() } #[inline] unsafe fn from_native_font(native_font: Self::NativeFont) -> Self { Font::from_native_font(native_font) } #[inline] fn postscript_name(&self) -> Option { self.postscript_name() } #[inline] fn full_name(&self) -> String { self.full_name() } #[inline] fn family_name(&self) -> String { self.family_name() } #[inline] fn is_monospace(&self) -> bool { self.is_monospace() } #[inline] fn properties(&self) -> Properties { self.properties() } #[inline] fn glyph_for_char(&self, character: char) -> Option { self.glyph_for_char(character) } #[inline] fn glyph_by_name(&self, name: &str) -> Option { self.glyph_by_name(name) } #[inline] fn glyph_count(&self) -> u32 { self.glyph_count() } #[inline] fn outline( &self, glyph_id: u32, hinting_mode: HintingOptions, sink: &mut S, ) -> Result<(), GlyphLoadingError> where S: OutlineSink, { self.outline(glyph_id, hinting_mode, sink) } #[inline] fn typographic_bounds(&self, glyph_id: u32) -> Result { self.typographic_bounds(glyph_id) } #[inline] fn advance(&self, glyph_id: u32) -> Result { self.advance(glyph_id) } #[inline] fn origin(&self, origin: u32) -> Result { self.origin(origin) } #[inline] fn metrics(&self) -> Metrics { self.metrics() } #[inline] fn copy_font_data(&self) -> Option>> { self.copy_font_data() } #[inline] fn supports_hinting_options( &self, hinting_options: HintingOptions, for_rasterization: bool, ) -> bool { self.supports_hinting_options(hinting_options, for_rasterization) } #[inline] fn rasterize_glyph( &self, canvas: &mut Canvas, glyph_id: u32, point_size: f32, transform: Transform2F, hinting_options: HintingOptions, rasterization_options: RasterizationOptions, ) -> Result<(), GlyphLoadingError> { self.rasterize_glyph( canvas, glyph_id, point_size, transform, hinting_options, rasterization_options, ) } #[inline] fn get_fallbacks(&self, text: &str, locale: &str) -> FallbackResult { self.get_fallbacks(text, locale) } #[inline] fn load_font_table(&self, table_tag: u32) -> Option> { self.load_font_table(table_tag) } } unsafe fn setup_freetype_face(face: FT_Face) { reset_freetype_face_char_size(face); } unsafe fn reset_freetype_face_char_size(face: FT_Face) { // Apple Color Emoji has 0 units per em. Whee! let units_per_em = (*face).units_per_EM as i64; if units_per_em > 0 { assert_eq!( FT_Set_Char_Size(face, ((*face).units_per_EM as FT_Long) << 6, 0, 0, 0), 0 ); } } #[repr(C)] struct FT_SfntName { platform_id: FT_UShort, encoding_id: FT_UShort, language_id: FT_UShort, name_id: FT_UShort, string: *mut FT_Byte, string_len: FT_UInt, } trait F32ToFtFixed { type Output; fn f32_to_ft_fixed_26_6(self) -> Self::Output; } trait FtFixedToF32 { type Output; fn ft_fixed_26_6_to_f32(self) -> Self::Output; } impl F32ToFtFixed for Vector2F { type Output = Vector2I; #[inline] fn f32_to_ft_fixed_26_6(self) -> Vector2I { (self * 64.0).to_i32() } } impl F32ToFtFixed for f32 { type Output = FT_Fixed; #[inline] fn f32_to_ft_fixed_26_6(self) -> FT_Fixed { (self * 64.0) as FT_Fixed } } impl FtFixedToF32 for Vector2I { type Output = Vector2F; #[inline] fn ft_fixed_26_6_to_f32(self) -> Vector2F { (self.to_f32() * (1.0 / 64.0)).round() } } impl FtFixedToF32 for RectI { type Output = RectF; #[inline] fn ft_fixed_26_6_to_f32(self) -> RectF { self.to_f32() * (1.0 / 64.0) } } extern "C" { fn FT_Get_Font_Format(face: FT_Face) -> *const c_char; fn FT_Get_BDF_Property( face: FT_Face, prop_name: *const c_char, aproperty: *mut BDF_PropertyRec, ) -> FT_Error; fn FT_Get_PS_Font_Value( face: FT_Face, key: u32, idx: FT_UInt, value: *mut c_void, value_len: FT_Long, ) -> FT_Long; fn FT_Get_Sfnt_Name(face: FT_Face, idx: FT_UInt, aname: *mut FT_SfntName) -> FT_Error; fn FT_Get_Sfnt_Name_Count(face: FT_Face) -> FT_UInt; } #[cfg(test)] mod test { use crate::loaders::freetype::Font; static PCF_FONT_PATH: &'static str = "resources/tests/times-roman-pcf/timR12.pcf"; static PCF_FONT_POSTSCRIPT_NAME: &'static str = "Times-Roman"; #[test] fn get_pcf_postscript_name() { let font = Font::from_path(PCF_FONT_PATH, 0).unwrap(); assert_eq!(font.postscript_name().unwrap(), PCF_FONT_POSTSCRIPT_NAME); } } font-kit-0.11.0/src/loaders/mod.rs000064400000000000000000000022730072674642500150370ustar 00000000000000// font-kit/src/loaders/mod.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! The different system services that can load and rasterize fonts. #[cfg(all( any(target_os = "macos", target_os = "ios"), not(feature = "loader-freetype-default") ))] pub use crate::loaders::core_text as default; #[cfg(all(target_family = "windows", not(feature = "loader-freetype-default")))] pub use crate::loaders::directwrite as default; #[cfg(any( not(any(target_os = "macos", target_os = "ios", target_family = "windows")), feature = "loader-freetype-default" ))] pub use crate::loaders::freetype as default; #[cfg(any(target_os = "macos", target_os = "ios"))] pub mod core_text; #[cfg(all(target_family = "windows"))] pub mod directwrite; #[cfg(any( not(any(target_os = "macos", target_os = "ios", target_family = "windows")), feature = "loader-freetype" ))] pub mod freetype; font-kit-0.11.0/src/matching.rs000064400000000000000000000134200072674642500144150ustar 00000000000000// font-kit/src/matching.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Determines the closest font matching a description per the CSS Fonts Level 3 specification. use float_ord::FloatOrd; use crate::error::SelectionError; use crate::properties::{Properties, Stretch, Style, Weight}; /// This follows CSS Fonts Level 3 § 5.2 [1]. /// /// https://drafts.csswg.org/css-fonts-3/#font-style-matching pub fn find_best_match( candidates: &[Properties], query: &Properties, ) -> Result { // Step 4. let mut matching_set: Vec = (0..candidates.len()).collect(); if matching_set.is_empty() { return Err(SelectionError::NotFound); } // Step 4a (`font-stretch`). let matching_stretch = if matching_set .iter() .any(|&index| candidates[index].stretch == query.stretch) { // Exact match. query.stretch } else if query.stretch <= Stretch::NORMAL { // Closest width, first checking narrower values and then wider values. match matching_set .iter() .filter(|&&index| candidates[index].stretch < query.stretch) .min_by_key(|&&index| FloatOrd(query.stretch.0 - candidates[index].stretch.0)) { Some(&matching_index) => candidates[matching_index].stretch, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| FloatOrd(candidates[index].stretch.0 - query.stretch.0)) .unwrap(); candidates[matching_index].stretch } } } else { // Closest width, first checking wider values and then narrower values. match matching_set .iter() .filter(|&&index| candidates[index].stretch > query.stretch) .min_by_key(|&&index| FloatOrd(candidates[index].stretch.0 - query.stretch.0)) { Some(&matching_index) => candidates[matching_index].stretch, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| FloatOrd(query.stretch.0 - candidates[index].stretch.0)) .unwrap(); candidates[matching_index].stretch } } }; matching_set.retain(|&index| candidates[index].stretch == matching_stretch); // Step 4b (`font-style`). let style_preference = match query.style { Style::Italic => [Style::Italic, Style::Oblique, Style::Normal], Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal], Style::Normal => [Style::Normal, Style::Oblique, Style::Italic], }; let matching_style = *style_preference .iter() .filter(|&query_style| { matching_set .iter() .any(|&index| candidates[index].style == *query_style) }) .next() .unwrap(); matching_set.retain(|&index| candidates[index].style == matching_style); // Step 4c (`font-weight`). // // The spec doesn't say what to do if the weight is between 400 and 500 exclusive, so we // just use 450 as the cutoff. let matching_weight = if matching_set .iter() .any(|&index| candidates[index].weight == query.weight) { query.weight } else if query.weight >= Weight(400.0) && query.weight < Weight(450.0) && matching_set .iter() .any(|&index| candidates[index].weight == Weight(500.0)) { // Check 500 first. Weight(500.0) } else if query.weight >= Weight(450.0) && query.weight <= Weight(500.0) && matching_set .iter() .any(|&index| candidates[index].weight == Weight(400.0)) { // Check 400 first. Weight(400.0) } else if query.weight <= Weight(500.0) { // Closest weight, first checking thinner values and then fatter ones. match matching_set .iter() .filter(|&&index| candidates[index].weight <= query.weight) .min_by_key(|&&index| FloatOrd(query.weight.0 - candidates[index].weight.0)) { Some(&matching_index) => candidates[matching_index].weight, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| FloatOrd(candidates[index].weight.0 - query.weight.0)) .unwrap(); candidates[matching_index].weight } } } else { // Closest weight, first checking fatter values and then thinner ones. match matching_set .iter() .filter(|&&index| candidates[index].weight >= query.weight) .min_by_key(|&&index| FloatOrd(candidates[index].weight.0 - query.weight.0)) { Some(&matching_index) => candidates[matching_index].weight, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| FloatOrd(query.weight.0 - candidates[index].weight.0)) .unwrap(); candidates[matching_index].weight } } }; matching_set.retain(|&index| candidates[index].weight == matching_weight); // Step 4d concerns `font-size`, but fonts in `font-kit` are unsized, so we ignore that. // Return the result. matching_set .into_iter() .next() .ok_or(SelectionError::NotFound) } font-kit-0.11.0/src/metrics.rs000064400000000000000000000042350072674642500142750ustar 00000000000000// font-kit/src/metrics.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Various metrics that apply to the entire font. //! //! For OpenType fonts, these mostly come from the `OS/2` table. use pathfinder_geometry::rect::RectF; /// Various metrics that apply to the entire font. /// /// For OpenType fonts, these mostly come from the `OS/2` table. #[derive(Clone, Copy, Debug)] pub struct Metrics { /// The number of font units per em. /// /// Font sizes are usually expressed in pixels per em; e.g. `12px` means 12 pixels per em. pub units_per_em: u32, /// The maximum amount the font rises above the baseline, in font units. pub ascent: f32, /// The maximum amount the font descends below the baseline, in font units. /// /// NB: This is typically a negative value to match the definition of `sTypoDescender` in the /// `OS/2` table in the OpenType specification. If you are used to using Windows or Mac APIs, /// beware, as the sign is reversed from what those APIs return. pub descent: f32, /// Distance between baselines, in font units. pub line_gap: f32, /// The suggested distance of the top of the underline from the baseline (negative values /// indicate below baseline), in font units. pub underline_position: f32, /// A suggested value for the underline thickness, in font units. pub underline_thickness: f32, /// The approximate amount that uppercase letters rise above the baseline, in font units. pub cap_height: f32, /// The approximate amount that non-ascending lowercase letters rise above the baseline, in /// font units. pub x_height: f32, /// A rectangle that surrounds all bounding boxes of all glyphs, in font units. /// /// This corresponds to the `xMin`/`xMax`/`yMin`/`yMax` values in the OpenType `head` table. pub bounding_box: RectF, } font-kit-0.11.0/src/outline.rs000064400000000000000000000136170072674642500143120ustar 00000000000000// font-kit/src/outline.rs // // Copyright © 2020 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Bézier paths. use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::vector::Vector2F; use std::mem; /// Receives Bézier path rendering commands. pub trait OutlineSink { /// Moves the pen to a point. fn move_to(&mut self, to: Vector2F); /// Draws a line to a point. fn line_to(&mut self, to: Vector2F); /// Draws a quadratic Bézier curve to a point. fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F); /// Draws a cubic Bézier curve to a point. fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F); /// Closes the path, returning to the first point in it. fn close(&mut self); } /// A glyph vector outline or path. #[derive(Clone, PartialEq, Debug)] pub struct Outline { /// The individual subpaths that make up this outline. pub contours: Vec, } /// A single curve or subpath within a glyph outline. #[derive(Clone, PartialEq, Debug)] pub struct Contour { /// Positions of each point. /// /// This must have the same length as the `flags` field. pub positions: Vec, /// Flags that specify what type of point the corresponding position represents. /// /// This must have the same length as the `positions` field. pub flags: Vec, } bitflags! { /// Flags that specify what type of point the corresponding position represents. pub struct PointFlags: u8 { /// This point is the control point of a quadratic Bézier curve or the first control point /// of a cubic Bézier curve. /// /// This flag is mutually exclusive with `CONTROL_POINT_1`. const CONTROL_POINT_0 = 0x01; /// This point is the second control point of a cubic Bézier curve. /// /// This flag is mutually exclusive with `CONTROL_POINT_0`. const CONTROL_POINT_1 = 0x02; } } /// Accumulates Bézier path rendering commands into an `Outline` structure. #[derive(Clone, Debug)] pub struct OutlineBuilder { outline: Outline, current_contour: Contour, } impl Outline { /// Creates a new empty outline. #[inline] pub fn new() -> Outline { Outline { contours: vec![] } } /// Sends this outline to an `OutlineSink`. pub fn copy_to(&self, sink: &mut S) where S: OutlineSink, { for contour in &self.contours { contour.copy_to(sink); } } } impl Contour { /// Creates a new empty contour. #[inline] pub fn new() -> Contour { Contour { positions: vec![], flags: vec![], } } /// Adds a new point with the given flags to the contour. #[inline] pub fn push(&mut self, position: Vector2F, flags: PointFlags) { self.positions.push(position); self.flags.push(flags); } /// Sends this contour to an `OutlineSink`. pub fn copy_to(&self, sink: &mut S) where S: OutlineSink, { debug_assert_eq!(self.positions.len(), self.flags.len()); if self.positions.is_empty() { return; } sink.move_to(self.positions[0]); let mut iter = self.positions[1..].iter().zip(self.flags[1..].iter()); while let Some((&position_0, &flags_0)) = iter.next() { if flags_0 == PointFlags::empty() { sink.line_to(position_0); continue; } let (&position_1, &flags_1) = iter.next().expect("Invalid outline!"); if flags_1 == PointFlags::empty() { sink.quadratic_curve_to(position_0, position_1); continue; } let (&position_2, &flags_2) = iter.next().expect("Invalid outline!"); debug_assert_eq!(flags_2, PointFlags::empty()); sink.cubic_curve_to(LineSegment2F::new(position_0, position_1), position_2); } sink.close(); } } impl OutlineBuilder { /// Creates a new empty `OutlineBuilder`. #[inline] pub fn new() -> OutlineBuilder { OutlineBuilder { outline: Outline::new(), current_contour: Contour::new(), } } /// Consumes this outline builder and returns the resulting outline. #[inline] pub fn into_outline(self) -> Outline { self.outline } /// Resets the outline builder and returns the old outline. #[inline] pub fn take_outline(&mut self) -> Outline { assert!(self.current_contour.positions.is_empty()); self.current_contour = Contour::new(); mem::replace(&mut self.outline, Outline::new()) } } impl OutlineSink for OutlineBuilder { #[inline] fn move_to(&mut self, to: Vector2F) { self.current_contour.push(to, PointFlags::empty()); } #[inline] fn line_to(&mut self, to: Vector2F) { self.current_contour.push(to, PointFlags::empty()); } #[inline] fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) { self.current_contour.push(ctrl, PointFlags::CONTROL_POINT_0); self.current_contour.push(to, PointFlags::empty()); } #[inline] fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) { self.current_contour .push(ctrl.from(), PointFlags::CONTROL_POINT_0); self.current_contour .push(ctrl.to(), PointFlags::CONTROL_POINT_1); self.current_contour.push(to, PointFlags::empty()); } #[inline] fn close(&mut self) { self.outline .contours .push(mem::replace(&mut self.current_contour, Contour::new())); } } font-kit-0.11.0/src/properties.rs000064400000000000000000000123610072674642500150220ustar 00000000000000// font-kit/src/properties.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Properties that specify which font in a family to use: e.g. style, weight, and stretchiness. //! //! Much of the documentation in this modules comes from the CSS 3 Fonts specification: //! https://drafts.csswg.org/css-fonts-3/ use std::fmt::{self, Debug, Display, Formatter}; /// Properties that specify which font in a family to use: e.g. style, weight, and stretchiness. /// /// This object supports a method chaining style for idiomatic initialization; e.g. /// /// # use font_kit::properties::{Properties, Style}; /// println!("{:?}", Properties::new().style(Style::Italic)); #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Properties { /// The font style, as defined in CSS. pub style: Style, /// The font weight, as defined in CSS. pub weight: Weight, /// The font stretchiness, as defined in CSS. pub stretch: Stretch, } impl Properties { /// Initializes a property set to its default values: normal style, normal weight, and normal /// stretchiness. #[inline] pub fn new() -> Properties { Properties::default() } /// Sets the value of the style property and returns this property set for method chaining. #[inline] pub fn style(&mut self, style: Style) -> &mut Properties { self.style = style; self } /// Sets the value of the weight property and returns this property set for method chaining. #[inline] pub fn weight(&mut self, weight: Weight) -> &mut Properties { self.weight = weight; self } /// Sets the value of the stretch property and returns this property set for method chaining. #[inline] pub fn stretch(&mut self, stretch: Stretch) -> &mut Properties { self.stretch = stretch; self } } /// Allows italic or oblique faces to be selected. #[derive(Clone, Copy, PartialEq, Debug)] pub enum Style { /// A face that is neither italic not obliqued. Normal, /// A form that is generally cursive in nature. Italic, /// A typically-sloped version of the regular face. Oblique, } impl Default for Style { fn default() -> Style { Style::Normal } } impl Display for Style { fn fmt(&self, f: &mut Formatter) -> fmt::Result { Debug::fmt(self, f) } } /// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0, /// with 400.0 as normal. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub struct Weight(pub f32); impl Default for Weight { #[inline] fn default() -> Weight { Weight::NORMAL } } impl Weight { /// Thin weight (100), the thinnest value. pub const THIN: Weight = Weight(100.0); /// Extra light weight (200). pub const EXTRA_LIGHT: Weight = Weight(200.0); /// Light weight (300). pub const LIGHT: Weight = Weight(300.0); /// Normal (400). pub const NORMAL: Weight = Weight(400.0); /// Medium weight (500, higher than normal). pub const MEDIUM: Weight = Weight(500.0); /// Semibold weight (600). pub const SEMIBOLD: Weight = Weight(600.0); /// Bold weight (700). pub const BOLD: Weight = Weight(700.0); /// Extra-bold weight (800). pub const EXTRA_BOLD: Weight = Weight(800.0); /// Black weight (900), the thickest value. pub const BLACK: Weight = Weight(900.0); } /// The width of a font as an approximate fraction of the normal width. /// /// Widths range from 0.5 to 2.0 inclusive, with 1.0 as the normal width. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub struct Stretch(pub f32); impl Default for Stretch { #[inline] fn default() -> Stretch { Stretch::NORMAL } } impl Stretch { /// Ultra-condensed width (50%), the narrowest possible. pub const ULTRA_CONDENSED: Stretch = Stretch(0.5); /// Extra-condensed width (62.5%). pub const EXTRA_CONDENSED: Stretch = Stretch(0.625); /// Condensed width (75%). pub const CONDENSED: Stretch = Stretch(0.75); /// Semi-condensed width (87.5%). pub const SEMI_CONDENSED: Stretch = Stretch(0.875); /// Normal width (100%). pub const NORMAL: Stretch = Stretch(1.0); /// Semi-expanded width (112.5%). pub const SEMI_EXPANDED: Stretch = Stretch(1.125); /// Expanded width (125%). pub const EXPANDED: Stretch = Stretch(1.25); /// Extra-expanded width (150%). pub const EXTRA_EXPANDED: Stretch = Stretch(1.5); /// Ultra-expanded width (200%), the widest possible. pub const ULTRA_EXPANDED: Stretch = Stretch(2.0); // Mapping from `usWidthClass` values to CSS `font-stretch` values. pub(crate) const MAPPING: [f32; 9] = [ Stretch::ULTRA_CONDENSED.0, Stretch::EXTRA_CONDENSED.0, Stretch::CONDENSED.0, Stretch::SEMI_CONDENSED.0, Stretch::NORMAL.0, Stretch::SEMI_EXPANDED.0, Stretch::EXPANDED.0, Stretch::EXTRA_EXPANDED.0, Stretch::ULTRA_EXPANDED.0, ]; } font-kit-0.11.0/src/source.rs000064400000000000000000000160520072674642500141270ustar 00000000000000// font-kit/src/source.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A database of installed fonts that can be queried. use crate::error::SelectionError; use crate::family::Family; use crate::family_handle::FamilyHandle; use crate::family_name::FamilyName; use crate::font::Font; use crate::handle::Handle; use crate::matching; use crate::properties::Properties; use std::any::Any; #[cfg(all( any(target_os = "macos", target_os = "ios"), not(feature = "loader-freetype-default") ))] pub use crate::sources::core_text::CoreTextSource as SystemSource; #[cfg(all(target_family = "windows", not(feature = "source-fontconfig-default")))] pub use crate::sources::directwrite::DirectWriteSource as SystemSource; #[cfg(any( not(any( target_os = "android", target_os = "macos", target_os = "ios", target_family = "windows", target_arch = "wasm32" )), feature = "source-fontconfig-default" ))] pub use crate::sources::fontconfig::FontconfigSource as SystemSource; #[cfg(all(target_os = "android", not(feature = "source-fontconfig-default")))] pub use crate::sources::fs::FsSource as SystemSource; // FIXME(pcwalton): These could expand to multiple fonts, and they could be language-specific. #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] const DEFAULT_FONT_FAMILY_SERIF: &'static str = "Times New Roman"; #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] const DEFAULT_FONT_FAMILY_SANS_SERIF: &'static str = "Arial"; #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] const DEFAULT_FONT_FAMILY_MONOSPACE: &'static str = "Courier New"; #[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))] const DEFAULT_FONT_FAMILY_CURSIVE: &'static str = "Comic Sans MS"; #[cfg(target_family = "windows")] const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Impact"; #[cfg(any(target_os = "macos", target_os = "ios"))] const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Papyrus"; #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] const DEFAULT_FONT_FAMILY_SERIF: &'static str = "serif"; #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] const DEFAULT_FONT_FAMILY_SANS_SERIF: &'static str = "sans-serif"; #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] const DEFAULT_FONT_FAMILY_MONOSPACE: &'static str = "monospace"; #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] const DEFAULT_FONT_FAMILY_CURSIVE: &'static str = "cursive"; #[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))] const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "fantasy"; /// A database of installed fonts that can be queried. /// /// This trait is object-safe. pub trait Source: Any { /// Returns paths of all fonts installed on the system. fn all_fonts(&self) -> Result, SelectionError>; /// Returns the names of all families installed on the system. fn all_families(&self) -> Result, SelectionError>; /// Looks up a font family by name and returns the handles of all the fonts in that family. fn select_family_by_name(&self, family_name: &str) -> Result; /// Selects a font by PostScript name, which should be a unique identifier. /// /// The default implementation, which is used by the DirectWrite and the filesystem backends, /// does a brute-force search of installed fonts to find the one that matches. fn select_by_postscript_name(&self, postscript_name: &str) -> Result { // TODO(pcwalton): Optimize this by searching for families with similar names first. for family_name in self.all_families()? { if let Ok(family_handle) = self.select_family_by_name(&family_name) { if let Ok(family) = Family::::from_handle(&family_handle) { for (handle, font) in family_handle.fonts().iter().zip(family.fonts().iter()) { if let Some(font_postscript_name) = font.postscript_name() { if font_postscript_name == postscript_name { return Ok((*handle).clone()); } } } } } } Err(SelectionError::NotFound) } // FIXME(pcwalton): This only returns one family instead of multiple families for the generic // family names. #[doc(hidden)] fn select_family_by_generic_name( &self, family_name: &FamilyName, ) -> Result { match *family_name { FamilyName::Title(ref title) => self.select_family_by_name(title), FamilyName::Serif => self.select_family_by_name(DEFAULT_FONT_FAMILY_SERIF), FamilyName::SansSerif => self.select_family_by_name(DEFAULT_FONT_FAMILY_SANS_SERIF), FamilyName::Monospace => self.select_family_by_name(DEFAULT_FONT_FAMILY_MONOSPACE), FamilyName::Cursive => self.select_family_by_name(DEFAULT_FONT_FAMILY_CURSIVE), FamilyName::Fantasy => self.select_family_by_name(DEFAULT_FONT_FAMILY_FANTASY), } } /// Performs font matching according to the CSS Fonts Level 3 specification and returns the /// handle. #[inline] fn select_best_match( &self, family_names: &[FamilyName], properties: &Properties, ) -> Result { for family_name in family_names { if let Ok(family_handle) = self.select_family_by_generic_name(family_name) { let candidates = self.select_descriptions_in_family(&family_handle)?; if let Ok(index) = matching::find_best_match(&candidates, properties) { return Ok(family_handle.fonts[index].clone()); } } } Err(SelectionError::NotFound) } #[doc(hidden)] fn select_descriptions_in_family( &self, family: &FamilyHandle, ) -> Result, SelectionError> { let mut fields = vec![]; for font_handle in family.fonts() { match Font::from_handle(font_handle) { Ok(font) => fields.push(font.properties()), Err(e) => log::warn!("Error loading font from handle: {:?}", e), } } Ok(fields) } /// Accesses this `Source` as `Any`, which allows downcasting back to a concrete type from a /// trait object. fn as_any(&self) -> &dyn Any; /// Accesses this `Source` as `Any`, which allows downcasting back to a concrete type from a /// trait object. fn as_mut_any(&mut self) -> &mut dyn Any; } font-kit-0.11.0/src/sources/core_text.rs000064400000000000000000000246340072674642500163130ustar 00000000000000// font-kit/src/sources/core_text.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A source that contains the installed fonts on macOS. use core_foundation::array::CFArray; use core_foundation::base::{CFType, TCFType}; use core_foundation::dictionary::CFDictionary; use core_foundation::string::CFString; use core_text::font_collection::{self, CTFontCollection}; use core_text::font_descriptor::{self, CTFontDescriptor}; use core_text::font_manager; use std::any::Any; use std::collections::HashMap; use std::f32; use std::fs::File; use std::path::PathBuf; use std::sync::Arc; use crate::error::SelectionError; use crate::family_handle::FamilyHandle; use crate::family_name::FamilyName; use crate::file_type::FileType; use crate::font::Font; use crate::handle::Handle; use crate::loaders::core_text::{self as core_text_loader, FONT_WEIGHT_MAPPING}; use crate::properties::{Properties, Stretch, Weight}; use crate::source::Source; use crate::utils; /// A source that contains the installed fonts on macOS. #[allow(missing_debug_implementations)] #[allow(missing_copy_implementations)] pub struct CoreTextSource; impl CoreTextSource { /// Opens a new connection to the system font source. /// /// (Note that this doesn't actually do any Mach communication to the font server; that is done /// lazily on demand by the Core Text/Core Graphics API.) #[inline] pub fn new() -> CoreTextSource { CoreTextSource } /// Returns paths of all fonts installed on the system. pub fn all_fonts(&self) -> Result, SelectionError> { let collection = font_collection::create_for_all_families(); create_handles_from_core_text_collection(collection) } /// Returns the names of all families installed on the system. pub fn all_families(&self) -> Result, SelectionError> { let core_text_family_names = font_manager::copy_available_font_family_names(); let mut families = Vec::with_capacity(core_text_family_names.len() as usize); for core_text_family_name in core_text_family_names.iter() { families.push(core_text_family_name.to_string()) } Ok(families) } /// Looks up a font family by name and returns the handles of all the fonts in that family. pub fn select_family_by_name(&self, family_name: &str) -> Result { let attributes: CFDictionary = CFDictionary::from_CFType_pairs(&[( CFString::new("NSFontFamilyAttribute"), CFString::new(family_name).as_CFType(), )]); let descriptor = font_descriptor::new_from_attributes(&attributes); let descriptors = CFArray::from_CFTypes(&[descriptor]); let collection = font_collection::new_from_descriptors(&descriptors); let handles = create_handles_from_core_text_collection(collection)?; Ok(FamilyHandle::from_font_handles(handles.into_iter())) } /// Selects a font by PostScript name, which should be a unique identifier. pub fn select_by_postscript_name( &self, postscript_name: &str, ) -> Result { let attributes: CFDictionary = CFDictionary::from_CFType_pairs(&[( CFString::new("NSFontNameAttribute"), CFString::new(postscript_name).as_CFType(), )]); let descriptor = font_descriptor::new_from_attributes(&attributes); let descriptors = CFArray::from_CFTypes(&[descriptor]); let collection = font_collection::new_from_descriptors(&descriptors); match collection.get_descriptors() { None => Err(SelectionError::NotFound), Some(descriptors) => create_handle_from_descriptor(&*descriptors.get(0).unwrap()), } } /// Performs font matching according to the CSS Fonts Level 3 specification and returns the /// handle. #[inline] pub fn select_best_match( &self, family_names: &[FamilyName], properties: &Properties, ) -> Result { ::select_best_match(self, family_names, properties) } } impl Source for CoreTextSource { fn all_fonts(&self) -> Result, SelectionError> { self.all_fonts() } fn all_families(&self) -> Result, SelectionError> { self.all_families() } fn select_family_by_name(&self, family_name: &str) -> Result { self.select_family_by_name(family_name) } fn select_by_postscript_name(&self, postscript_name: &str) -> Result { self.select_by_postscript_name(postscript_name) } #[inline] fn as_any(&self) -> &dyn Any { self } #[inline] fn as_mut_any(&mut self) -> &mut dyn Any { self } } #[allow(dead_code)] fn css_to_core_text_font_weight(css_weight: Weight) -> f32 { core_text_loader::piecewise_linear_lookup( f32::max(100.0, css_weight.0) / 100.0 - 1.0, &FONT_WEIGHT_MAPPING, ) } #[allow(dead_code)] fn css_stretchiness_to_core_text_width(css_stretchiness: Stretch) -> f32 { let css_stretchiness = utils::clamp(css_stretchiness.0, 0.5, 2.0); 0.25 * core_text_loader::piecewise_linear_find_index(css_stretchiness, &Stretch::MAPPING) - 1.0 } #[derive(Clone)] struct FontDataInfo { data: Arc>, file_type: FileType, } fn create_handles_from_core_text_collection( collection: CTFontCollection, ) -> Result, SelectionError> { let mut fonts = vec![]; if let Some(descriptors) = collection.get_descriptors() { let mut font_data_info_cache: HashMap = HashMap::new(); 'outer: for index in 0..descriptors.len() { let descriptor = descriptors.get(index).unwrap(); let font_path = descriptor.font_path().unwrap(); let data_info = if let Some(data_info) = font_data_info_cache.get(&font_path) { data_info.clone() } else { let mut file = if let Ok(file) = File::open(&font_path) { file } else { continue; }; let data = if let Ok(data) = utils::slurp_file(&mut file) { Arc::new(data) } else { continue; }; let file_type = match Font::analyze_bytes(Arc::clone(&data)) { Ok(file_type) => file_type, Err(_) => continue, }; let data_info = FontDataInfo { data, file_type }; font_data_info_cache.insert(font_path.clone(), data_info.clone()); data_info }; match data_info.file_type { FileType::Collection(font_count) => { let postscript_name = descriptor.font_name(); for font_index in 0..font_count { if let Ok(font) = Font::from_bytes(Arc::clone(&data_info.data), font_index) { if let Some(font_postscript_name) = font.postscript_name() { if postscript_name == font_postscript_name { fonts.push(Handle::from_memory(data_info.data, font_index)); continue 'outer; } } } } } FileType::Single => { fonts.push(Handle::from_memory(data_info.data, 0)); } } } } if fonts.is_empty() { Err(SelectionError::NotFound) } else { Ok(fonts) } } fn create_handle_from_descriptor(descriptor: &CTFontDescriptor) -> Result { let font_path = descriptor.font_path().unwrap(); let mut file = if let Ok(file) = File::open(&font_path) { file } else { return Err(SelectionError::CannotAccessSource); }; let font_data = if let Ok(font_data) = utils::slurp_file(&mut file) { Arc::new(font_data) } else { return Err(SelectionError::CannotAccessSource); }; match Font::analyze_bytes(Arc::clone(&font_data)) { Ok(FileType::Collection(font_count)) => { let postscript_name = descriptor.font_name(); for font_index in 0..font_count { if let Ok(font) = Font::from_bytes(Arc::clone(&font_data), font_index) { if let Some(font_postscript_name) = font.postscript_name() { if postscript_name == font_postscript_name { return Ok(Handle::from_memory(font_data, font_index)); } } } } Err(SelectionError::NotFound) } Ok(FileType::Single) => Ok(Handle::from_memory(font_data, 0)), Err(_) => Err(SelectionError::CannotAccessSource), } } #[cfg(test)] mod test { use crate::properties::{Stretch, Weight}; #[test] fn test_css_to_core_text_font_weight() { // Exact matches assert_eq!(super::css_to_core_text_font_weight(Weight(100.0)), -0.7); assert_eq!(super::css_to_core_text_font_weight(Weight(400.0)), 0.0); assert_eq!(super::css_to_core_text_font_weight(Weight(700.0)), 0.4); assert_eq!(super::css_to_core_text_font_weight(Weight(900.0)), 0.8); // Linear interpolation assert_eq!(super::css_to_core_text_font_weight(Weight(450.0)), 0.1); } #[test] fn test_css_to_core_text_font_stretch() { // Exact matches assert_eq!( super::css_stretchiness_to_core_text_width(Stretch(1.0)), 0.0 ); assert_eq!( super::css_stretchiness_to_core_text_width(Stretch(0.5)), -1.0 ); assert_eq!( super::css_stretchiness_to_core_text_width(Stretch(2.0)), 1.0 ); // Linear interpolation assert_eq!( super::css_stretchiness_to_core_text_width(Stretch(1.7)), 0.85 ); } } font-kit-0.11.0/src/sources/directwrite.rs000064400000000000000000000106400072674642500166340ustar 00000000000000// font-kit/src/sources/directwrite.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A source that contains the installed fonts on Windows. use dwrote::Font as DWriteFont; use dwrote::FontCollection as DWriteFontCollection; use std::any::Any; use crate::error::SelectionError; use crate::family_handle::FamilyHandle; use crate::family_name::FamilyName; use crate::handle::Handle; use crate::properties::Properties; use crate::source::Source; /// A source that contains the installed fonts on Windows. #[allow(missing_debug_implementations)] pub struct DirectWriteSource { system_font_collection: DWriteFontCollection, } impl DirectWriteSource { /// Opens the system font collection. pub fn new() -> DirectWriteSource { DirectWriteSource { system_font_collection: DWriteFontCollection::system(), } } /// Returns paths of all fonts installed on the system. pub fn all_fonts(&self) -> Result, SelectionError> { let mut handles = Vec::new(); for dwrite_family in self.system_font_collection.families_iter() { for font_index in 0..dwrite_family.get_font_count() { let dwrite_font = dwrite_family.get_font(font_index); handles.push(self.create_handle_from_dwrite_font(dwrite_font)) } } Ok(handles) } /// Returns the names of all families installed on the system. pub fn all_families(&self) -> Result, SelectionError> { Ok(self .system_font_collection .families_iter() .map(|dwrite_family| dwrite_family.name()) .collect()) } /// Looks up a font family by name and returns the handles of all the fonts in that family. /// /// TODO(pcwalton): Case-insensitivity. pub fn select_family_by_name(&self, family_name: &str) -> Result { let mut family = FamilyHandle::new(); let dwrite_family = match self .system_font_collection .get_font_family_by_name(family_name) { Some(dwrite_family) => dwrite_family, None => return Err(SelectionError::NotFound), }; for font_index in 0..dwrite_family.get_font_count() { let dwrite_font = dwrite_family.get_font(font_index); family.push(self.create_handle_from_dwrite_font(dwrite_font)) } Ok(family) } /// Selects a font by PostScript name, which should be a unique identifier. /// /// On the DirectWrite backend, this does a brute-force search of installed fonts to find the /// one that matches. pub fn select_by_postscript_name( &self, postscript_name: &str, ) -> Result { ::select_by_postscript_name(self, postscript_name) } /// Performs font matching according to the CSS Fonts Level 3 specification and returns the /// handle. #[inline] pub fn select_best_match( &self, family_names: &[FamilyName], properties: &Properties, ) -> Result { ::select_best_match(self, family_names, properties) } fn create_handle_from_dwrite_font(&self, dwrite_font: DWriteFont) -> Handle { let dwrite_font_face = dwrite_font.create_font_face(); let dwrite_font_files = dwrite_font_face.get_files(); Handle::Path { path: dwrite_font_files[0].get_font_file_path().unwrap(), font_index: dwrite_font_face.get_index(), } } } impl Source for DirectWriteSource { #[inline] fn all_fonts(&self) -> Result, SelectionError> { self.all_fonts() } #[inline] fn all_families(&self) -> Result, SelectionError> { self.all_families() } #[inline] fn select_family_by_name(&self, family_name: &str) -> Result { self.select_family_by_name(family_name) } #[inline] fn as_any(&self) -> &dyn Any { self } #[inline] fn as_mut_any(&mut self) -> &mut dyn Any { self } } font-kit-0.11.0/src/sources/fontconfig.rs000064400000000000000000000437520072674642500164550ustar 00000000000000// font-kit/src/sources/fontconfig.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A source that contains the fonts installed on the system, as reported by the Fontconfig //! library. //! //! On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig //! support. To prefer it over the native font source (only if you know what you're doing), use the //! `source-fontconfig-default` feature. use crate::error::SelectionError; use crate::family_handle::FamilyHandle; use crate::family_name::FamilyName; use crate::handle::Handle; use crate::properties::Properties; use crate::source::Source; use std::any::Any; /// A source that contains the fonts installed on the system, as reported by the Fontconfig /// library. /// /// On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig /// support. To prefer it over the native font source (only if you know what you're doing), use the /// `source-fontconfig-default` feature. #[allow(missing_debug_implementations)] pub struct FontconfigSource { config: fc::Config, } impl FontconfigSource { /// Initializes Fontconfig and prepares it for queries. pub fn new() -> FontconfigSource { FontconfigSource { config: fc::Config::new(), } } /// Returns paths of all fonts installed on the system. pub fn all_fonts(&self) -> Result, SelectionError> { let pattern = fc::Pattern::new(); // We want the family name. let mut object_set = fc::ObjectSet::new(); object_set.push_string(fc::Object::File); object_set.push_string(fc::Object::Index); let patterns = pattern .list(&self.config, object_set) .map_err(|_| SelectionError::NotFound)?; let mut handles = vec![]; for patt in patterns { let path = match patt.get_string(fc::Object::File) { Some(v) => v, None => continue, }; let index = match patt.get_integer(fc::Object::Index) { Some(v) => v, None => continue, }; handles.push(Handle::Path { path: path.into(), font_index: index as u32, }); } if !handles.is_empty() { Ok(handles) } else { Err(SelectionError::NotFound) } } /// Returns the names of all families installed on the system. pub fn all_families(&self) -> Result, SelectionError> { let pattern = fc::Pattern::new(); // We want the family name. let mut object_set = fc::ObjectSet::new(); object_set.push_string(fc::Object::Family); let patterns = pattern .list(&self.config, object_set) .map_err(|_| SelectionError::NotFound)?; let mut result_families = vec![]; for patt in patterns { if let Some(family) = patt.get_string(fc::Object::Family) { result_families.push(family); } } result_families.sort(); result_families.dedup(); if !result_families.is_empty() { Ok(result_families) } else { Err(SelectionError::NotFound) } } /// Looks up a font family by name and returns the handles of all the fonts in that family. pub fn select_family_by_name(&self, family_name: &str) -> Result { use std::borrow::Cow; let family_name = match family_name { "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" => { Cow::from(self.select_generic_font(family_name)?) } _ => Cow::from(family_name), }; let pattern = fc::Pattern::from_name(family_name.as_ref()); let mut object_set = fc::ObjectSet::new(); object_set.push_string(fc::Object::File); object_set.push_string(fc::Object::Index); let patterns = pattern .list(&self.config, object_set) .map_err(|_| SelectionError::NotFound)?; let mut handles = vec![]; for patt in patterns { let font_path = patt.get_string(fc::Object::File).unwrap(); let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32; let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index); handles.push(handle); } if !handles.is_empty() { Ok(FamilyHandle::from_font_handles(handles.into_iter())) } else { Err(SelectionError::NotFound) } } /// Selects a font by a generic name. /// /// Accepts: serif, sans-serif, monospace, cursive and fantasy. fn select_generic_font(&self, name: &str) -> Result { let mut pattern = fc::Pattern::from_name(name); pattern.config_substitute(fc::MatchKind::Pattern); pattern.default_substitute(); let patterns = pattern .sorted(&self.config) .map_err(|_| SelectionError::NotFound)?; if let Some(patt) = patterns.into_iter().next() { if let Some(family) = patt.get_string(fc::Object::Family) { return Ok(family); } } Err(SelectionError::NotFound) } /// Selects a font by PostScript name, which should be a unique identifier. /// /// The default implementation, which is used by the DirectWrite and the filesystem backends, /// does a brute-force search of installed fonts to find the one that matches. pub fn select_by_postscript_name( &self, postscript_name: &str, ) -> Result { let mut pattern = fc::Pattern::new(); pattern.push_string(fc::Object::PostScriptName, postscript_name.to_owned()); // We want the file path and the font index. let mut object_set = fc::ObjectSet::new(); object_set.push_string(fc::Object::File); object_set.push_string(fc::Object::Index); let patterns = pattern .list(&self.config, object_set) .map_err(|_| SelectionError::NotFound)?; if let Some(patt) = patterns.into_iter().next() { let font_path = patt.get_string(fc::Object::File).unwrap(); let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32; let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index); Ok(handle) } else { Err(SelectionError::NotFound) } } /// Performs font matching according to the CSS Fonts Level 3 specification and returns the /// handle. #[inline] pub fn select_best_match( &self, family_names: &[FamilyName], properties: &Properties, ) -> Result { ::select_best_match(self, family_names, properties) } } impl Source for FontconfigSource { #[inline] fn all_fonts(&self) -> Result, SelectionError> { self.all_fonts() } #[inline] fn all_families(&self) -> Result, SelectionError> { self.all_families() } #[inline] fn select_family_by_name(&self, family_name: &str) -> Result { self.select_family_by_name(family_name) } #[inline] fn select_by_postscript_name(&self, postscript_name: &str) -> Result { self.select_by_postscript_name(postscript_name) } #[inline] fn as_any(&self) -> &dyn Any { self } #[inline] fn as_mut_any(&mut self) -> &mut dyn Any { self } } // A minimal fontconfig wrapper. mod fc { #![allow(dead_code)] use fontconfig_sys as ffi; use fontconfig_sys::ffi_dispatch; #[cfg(feature = "source-fontconfig-dlopen")] use ffi::statics::LIB; #[cfg(not(feature = "source-fontconfig-dlopen"))] use ffi::*; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_uchar}; use std::ptr; #[derive(Clone, Copy)] pub enum Error { NoMatch, TypeMismatch, NoId, OutOfMemory, } #[derive(Clone, Copy)] pub enum MatchKind { Pattern, Font, Scan, } impl MatchKind { fn to_u32(&self) -> u32 { match self { MatchKind::Pattern => ffi::FcMatchPattern, MatchKind::Font => ffi::FcMatchFont, MatchKind::Scan => ffi::FcMatchScan, } } } // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/x19.html #[derive(Clone, Copy)] pub enum Object { Family, File, Index, PostScriptName, } impl Object { fn as_bytes(&self) -> &[u8] { match self { Object::Family => b"family\0", Object::File => b"file\0", Object::Index => b"index\0", Object::PostScriptName => b"postscriptname\0", } } fn as_ptr(&self) -> *const libc::c_char { self.as_bytes().as_ptr() as *const libc::c_char } } pub struct Config { d: *mut ffi::FcConfig, } impl Config { // FcInitLoadConfigAndFonts pub fn new() -> Self { unsafe { Config { d: ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcInitLoadConfigAndFonts, ), } } } } impl Drop for Config { fn drop(&mut self) { unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcConfigDestroy, self.d ); } } } pub struct Pattern { d: *mut ffi::FcPattern, c_strings: Vec, } impl Pattern { fn from_ptr(d: *mut ffi::FcPattern) -> Self { Pattern { d, c_strings: vec![], } } // FcPatternCreate pub fn new() -> Self { unsafe { Pattern::from_ptr(ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcPatternCreate, )) } } // FcNameParse pub fn from_name(name: &str) -> Self { let c_name = CString::new(name).unwrap(); unsafe { Pattern::from_ptr(ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcNameParse, c_name.as_ptr() as *mut c_uchar )) } } // FcPatternAddString pub fn push_string(&mut self, object: Object, value: String) { unsafe { let c_string = CString::new(value).unwrap(); ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcPatternAddString, self.d, object.as_ptr(), c_string.as_ptr() as *const c_uchar ); // We have to keep this string, because `FcPattern` has a pointer to it now. self.c_strings.push(c_string) } } // FcConfigSubstitute pub fn config_substitute(&mut self, match_kind: MatchKind) { unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcConfigSubstitute, ptr::null_mut(), self.d, match_kind.to_u32() ); } } // FcDefaultSubstitute pub fn default_substitute(&mut self) { unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcDefaultSubstitute, self.d ); } } // FcFontSort pub fn sorted(&self, config: &Config) -> Result { let mut res = ffi::FcResultMatch; let d = unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcFontSort, config.d, self.d, 1, ptr::null_mut(), &mut res ) }; match res { ffi::FcResultMatch => Ok(FontSet { d, idx: 0 }), ffi::FcResultTypeMismatch => Err(Error::TypeMismatch), ffi::FcResultNoId => Err(Error::NoId), ffi::FcResultOutOfMemory => Err(Error::OutOfMemory), _ => Err(Error::NoMatch), } } // FcFontList pub fn list(&self, config: &Config, set: ObjectSet) -> Result { let d = unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcFontList, config.d, self.d, set.d ) }; if !d.is_null() { Ok(FontSet { d, idx: 0 }) } else { Err(Error::NoMatch) } } } impl Drop for Pattern { #[inline] fn drop(&mut self) { unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcPatternDestroy, self.d ) } } } // A read-only `FcPattern` without a destructor. pub struct PatternRef { d: *mut ffi::FcPattern, } impl PatternRef { // FcPatternGetString pub fn get_string(&self, object: Object) -> Option { unsafe { let mut string = ptr::null_mut(); let res = ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcPatternGetString, self.d, object.as_ptr(), 0, &mut string ); if res != ffi::FcResultMatch { return None; } if string.is_null() { return None; } CStr::from_ptr(string as *const c_char) .to_str() .ok() .map(|string| string.to_owned()) } } // FcPatternGetInteger pub fn get_integer(&self, object: Object) -> Option { unsafe { let mut integer = 0; let res = ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcPatternGetInteger, self.d, object.as_ptr(), 0, &mut integer ); if res != ffi::FcResultMatch { return None; } Some(integer) } } } pub struct FontSet { d: *mut ffi::FcFontSet, idx: usize, } impl FontSet { pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn len(&self) -> usize { unsafe { (*self.d).nfont as usize } } } impl Iterator for FontSet { type Item = PatternRef; fn next(&mut self) -> Option { if self.idx == self.len() { return None; } let idx = self.idx; self.idx += 1; let d = unsafe { *(*self.d).fonts.offset(idx as isize) }; Some(PatternRef { d }) } fn size_hint(&self) -> (usize, Option) { (0, Some(self.len())) } } impl Drop for FontSet { fn drop(&mut self) { unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcFontSetDestroy, self.d ) } } } pub struct ObjectSet { d: *mut ffi::FcObjectSet, } impl ObjectSet { // FcObjectSetCreate pub fn new() -> Self { unsafe { ObjectSet { d: ffi_dispatch!(feature = "source-fontconfig-dlopen", LIB, FcObjectSetCreate,), } } } // FcObjectSetAdd pub fn push_string(&mut self, object: Object) { unsafe { // Returns `false` if the property name cannot be inserted // into the set (due to allocation failure). assert_eq!( ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcObjectSetAdd, self.d, object.as_ptr() ), 1 ); } } } impl Drop for ObjectSet { fn drop(&mut self) { unsafe { ffi_dispatch!( feature = "source-fontconfig-dlopen", LIB, FcObjectSetDestroy, self.d ) } } } } font-kit-0.11.0/src/sources/fs.rs000064400000000000000000000156150072674642500147260ustar 00000000000000// font-kit/src/sources/fs.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A source that loads fonts from a directory or directories on disk. //! //! This source uses the WalkDir abstraction from the `walkdir` crate to locate fonts. //! //! This is the native source on Android. use std::any::Any; use std::fs::File; use std::path::PathBuf; use walkdir::WalkDir; #[cfg(not(any(target_os = "android", target_family = "windows")))] use dirs_next; #[cfg(target_family = "windows")] use std::ffi::OsString; #[cfg(target_family = "windows")] use std::os::windows::ffi::OsStringExt; #[cfg(target_family = "windows")] use winapi::shared::minwindef::{MAX_PATH, UINT}; #[cfg(target_family = "windows")] use winapi::um::sysinfoapi; use crate::error::SelectionError; use crate::family_handle::FamilyHandle; use crate::family_name::FamilyName; use crate::file_type::FileType; use crate::font::Font; use crate::handle::Handle; use crate::properties::Properties; use crate::source::Source; use crate::sources::mem::MemSource; /// A source that loads fonts from a directory or directories on disk. /// /// This source uses the WalkDir abstraction from the `walkdir` crate to locate fonts. /// /// This is the native source on Android. #[allow(missing_debug_implementations)] pub struct FsSource { mem_source: MemSource, } impl FsSource { /// Opens the default set of directories on this platform and indexes the fonts found within. /// /// Do not rely on this function for systems other than Android. It makes a best effort to /// locate fonts in the typical platform directories, but it is too simple to pick up fonts /// that are stored in unusual locations but nevertheless properly installed. pub fn new() -> FsSource { let mut fonts = vec![]; for font_directory in default_font_directories() { for directory_entry in WalkDir::new(font_directory).into_iter() { let directory_entry = match directory_entry { Ok(directory_entry) => directory_entry, Err(_) => continue, }; let path = directory_entry.path(); let mut file = match File::open(path) { Err(_) => continue, Ok(file) => file, }; match Font::analyze_file(&mut file) { Err(_) => continue, Ok(FileType::Single) => fonts.push(Handle::from_path(path.to_owned(), 0)), Ok(FileType::Collection(font_count)) => { for font_index in 0..font_count { fonts.push(Handle::from_path(path.to_owned(), font_index)) } } } } } FsSource { mem_source: MemSource::from_fonts(fonts.into_iter()).unwrap(), } } /// Returns paths of all fonts installed on the system. pub fn all_fonts(&self) -> Result, SelectionError> { self.mem_source.all_fonts() } /// Returns the names of all families installed on the system. pub fn all_families(&self) -> Result, SelectionError> { self.mem_source.all_families() } /// Looks up a font family by name and returns the handles of all the fonts in that family. pub fn select_family_by_name(&self, family_name: &str) -> Result { self.mem_source.select_family_by_name(family_name) } /// Selects a font by PostScript name, which should be a unique identifier. /// /// This implementation does a brute-force search of installed fonts to find the one that /// matches. pub fn select_by_postscript_name( &self, postscript_name: &str, ) -> Result { self.mem_source.select_by_postscript_name(postscript_name) } /// Performs font matching according to the CSS Fonts Level 3 specification and returns the /// handle. #[inline] pub fn select_best_match( &self, family_names: &[FamilyName], properties: &Properties, ) -> Result { ::select_best_match(self, family_names, properties) } } impl Source for FsSource { #[inline] fn all_fonts(&self) -> Result, SelectionError> { self.all_fonts() } #[inline] fn all_families(&self) -> Result, SelectionError> { self.all_families() } fn select_family_by_name(&self, family_name: &str) -> Result { self.select_family_by_name(family_name) } fn select_by_postscript_name(&self, postscript_name: &str) -> Result { self.select_by_postscript_name(postscript_name) } #[inline] fn as_any(&self) -> &dyn Any { self } #[inline] fn as_mut_any(&mut self) -> &mut dyn Any { self } } #[cfg(target_os = "android")] fn default_font_directories() -> Vec { vec![PathBuf::from("/system/fonts")] } #[cfg(target_family = "windows")] fn default_font_directories() -> Vec { unsafe { let mut buffer = vec![0; MAX_PATH]; let len = sysinfoapi::GetWindowsDirectoryW(buffer.as_mut_ptr(), buffer.len() as UINT); assert!(len != 0); buffer.truncate(len as usize); let mut path = PathBuf::from(OsString::from_wide(&buffer)); path.push("Fonts"); vec![path] } } #[cfg(target_os = "macos")] fn default_font_directories() -> Vec { let mut directories = vec![ PathBuf::from("/System/Library/Fonts"), PathBuf::from("/Library/Fonts"), PathBuf::from("/Network/Library/Fonts"), ]; if let Some(mut path) = dirs_next::home_dir() { path.push("Library"); path.push("Fonts"); directories.push(path); } directories } #[cfg(not(any(target_os = "android", target_family = "windows", target_os = "macos")))] fn default_font_directories() -> Vec { let mut directories = vec![ PathBuf::from("/usr/share/fonts"), PathBuf::from("/usr/local/share/fonts"), PathBuf::from("/var/run/host/usr/share/fonts"), // Flatpak specific PathBuf::from("/var/run/host/usr/local/share/fonts"), ]; if let Some(path) = dirs_next::home_dir() { directories.push(path.join(".fonts")); // ~/.fonts is deprecated directories.push(path.join("local").join("share").join("fonts")); // Flatpak specific } if let Some(mut path) = dirs_next::data_dir() { path.push("fonts"); directories.push(path); } directories } font-kit-0.11.0/src/sources/mem.rs000064400000000000000000000151560072674642500150740ustar 00000000000000// font-kit/src/sources/mem.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A source that keeps fonts in memory. use crate::error::{FontLoadingError, SelectionError}; use crate::family_handle::FamilyHandle; use crate::family_name::FamilyName; use crate::font::Font; use crate::handle::Handle; use crate::properties::Properties; use crate::source::Source; use std::any::Any; /// A source that keeps fonts in memory. #[allow(missing_debug_implementations)] pub struct MemSource { families: Vec, } impl MemSource { /// Creates a new empty memory source. pub fn empty() -> MemSource { MemSource { families: vec![] } } /// Creates a new memory source that contains the given set of font handles. /// /// The fonts referenced by the handles are eagerly loaded into memory. pub fn from_fonts(fonts: I) -> Result where I: Iterator, { let mut families = vec![]; for handle in fonts { add_font(handle, &mut families)?; } families.sort_by(|a, b| a.family_name.cmp(&b.family_name)); Ok(MemSource { families }) } /// Add an existing font handle to a `MemSource`. /// /// Returns the font that was just added. /// /// Note that adding fonts to an existing `MemSource` is slower than creating a new one from a /// `Handle` iterator, since this method sorts after every addition, rather than once at the /// end. pub fn add_font(&mut self, handle: Handle) -> Result { let font = add_font(handle, &mut self.families)?; self.families .sort_by(|a, b| a.family_name.cmp(&b.family_name)); Ok(font) } /// Add a number of existing font handles to a `MemSource`. /// /// Note that adding fonts to an existing `MemSource` is slower than creating a new one from a /// `Handle` iterator, because extra unnecessary sorting with occur with every call to this /// method. pub fn add_fonts( &mut self, handles: impl Iterator, ) -> Result<(), FontLoadingError> { for handle in handles { add_font(handle, &mut self.families)?; } self.families .sort_by(|a, b| a.family_name.cmp(&b.family_name)); Ok(()) } /// Returns paths of all fonts installed on the system. pub fn all_fonts(&self) -> Result, SelectionError> { Ok(self .families .iter() .map(|family| family.font.clone()) .collect()) } /// Returns the names of all families installed on the system. pub fn all_families(&self) -> Result, SelectionError> { let mut families = vec![]; for family in &self.families { if families.last() == Some(&family.family_name) { continue; } families.push(family.family_name.clone()); } Ok(families) } /// Looks up a font family by name and returns the handles of all the fonts in that family. /// /// FIXME(pcwalton): Case-insensitive comparison. pub fn select_family_by_name(&self, family_name: &str) -> Result { let mut first_family_index = self .families .binary_search_by(|family| (&*family.family_name).cmp(family_name)) .map_err(|_| SelectionError::NotFound)?; while first_family_index > 0 && self.families[first_family_index - 1].family_name == family_name { first_family_index -= 1 } let mut last_family_index = first_family_index; while last_family_index + 1 < self.families.len() && self.families[last_family_index + 1].family_name == family_name { last_family_index += 1 } let families = &self.families[first_family_index..(last_family_index + 1)]; Ok(FamilyHandle::from_font_handles( families.iter().map(|family| family.font.clone()), )) } /// Selects a font by PostScript name, which should be a unique identifier. /// /// The default implementation, which is used by the DirectWrite and the filesystem backends, /// does a brute-force search of installed fonts to find the one that matches. pub fn select_by_postscript_name( &self, postscript_name: &str, ) -> Result { self.families .iter() .filter(|family_entry| family_entry.postscript_name == postscript_name) .map(|family_entry| family_entry.font.clone()) .next() .ok_or(SelectionError::NotFound) } /// Performs font matching according to the CSS Fonts Level 3 specification and returns the /// handle. #[inline] pub fn select_best_match( &self, family_names: &[FamilyName], properties: &Properties, ) -> Result { ::select_best_match(self, family_names, properties) } } impl Source for MemSource { #[inline] fn all_fonts(&self) -> Result, SelectionError> { self.all_fonts() } #[inline] fn all_families(&self) -> Result, SelectionError> { self.all_families() } fn select_family_by_name(&self, family_name: &str) -> Result { self.select_family_by_name(family_name) } fn select_by_postscript_name(&self, postscript_name: &str) -> Result { self.select_by_postscript_name(postscript_name) } #[inline] fn as_any(&self) -> &dyn Any { self } #[inline] fn as_mut_any(&mut self) -> &mut dyn Any { self } } /// Adds a font, but doesn't sort. Returns the font that was created to check for validity. fn add_font(handle: Handle, families: &mut Vec) -> Result { let font = Font::from_handle(&handle)?; if let Some(postscript_name) = font.postscript_name() { families.push(FamilyEntry { family_name: font.family_name(), postscript_name: postscript_name, font: handle, }) } Ok(font) } struct FamilyEntry { family_name: String, postscript_name: String, font: Handle, } font-kit-0.11.0/src/sources/mod.rs000064400000000000000000000021450072674642500150670ustar 00000000000000// font-kit/src/sources/mod.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Various databases of installed fonts that can be queried. //! //! The system-specific sources (Core Text, DirectWrite, and Fontconfig) contain the fonts that are //! installed on the system. The remaining databases (`fs`, `mem`, and `multi`) allow `font-kit` to //! query fonts not installed on the system. #[cfg(any(target_os = "macos", target_os = "ios"))] pub mod core_text; #[cfg(target_family = "windows")] pub mod directwrite; #[cfg(any( not(any( target_os = "macos", target_os = "ios", target_family = "windows", target_arch = "wasm32" )), feature = "source-fontconfig" ))] pub mod fontconfig; #[cfg(not(target_arch = "wasm32"))] pub mod fs; pub mod mem; pub mod multi; font-kit-0.11.0/src/sources/multi.rs000064400000000000000000000144160072674642500154460ustar 00000000000000// font-kit/src/sources/multi.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A source that encapsulates multiple sources and allows them to be queried as a group. //! //! This is useful when an application wants a library of fonts consisting of the installed system //! fonts plus some other application-supplied fonts. use crate::error::SelectionError; use crate::family_handle::FamilyHandle; use crate::family_name::FamilyName; use crate::handle::Handle; use crate::properties::Properties; use crate::source::Source; use std::{ any::Any, fmt, ops::{Index, IndexMut}, slice, }; /// A source that encapsulates multiple sources and allows them to be queried as a group. /// /// This is useful when an application wants a library of fonts consisting of the installed system /// fonts plus some other application-supplied fonts. #[allow(missing_debug_implementations)] pub struct MultiSource { subsources: Vec>, } impl MultiSource { /// Creates a new source that contains all the fonts in the supplied sources. pub fn from_sources(subsources: Vec>) -> MultiSource { MultiSource { subsources } } /// Returns paths of all fonts installed on the system. pub fn all_fonts(&self) -> Result, SelectionError> { let mut handles = vec![]; for subsource in &self.subsources { handles.extend(subsource.all_fonts()?.into_iter()) } Ok(handles) } /// Returns the names of all families installed on the system. pub fn all_families(&self) -> Result, SelectionError> { let mut families = vec![]; for subsource in &self.subsources { families.extend(subsource.all_families()?.into_iter()) } Ok(families) } /// Looks up a font family by name and returns the handles of all the fonts in that family. pub fn select_family_by_name(&self, family_name: &str) -> Result { for subsource in &self.subsources { match subsource.select_family_by_name(family_name) { Ok(family) => return Ok(family), Err(SelectionError::NotFound) => {} Err(err) => return Err(err), } } Err(SelectionError::NotFound) } /// Selects a font by PostScript name, which should be a unique identifier. pub fn select_by_postscript_name( &self, postscript_name: &str, ) -> Result { for subsource in &self.subsources { match subsource.select_by_postscript_name(postscript_name) { Ok(font) => return Ok(font), Err(SelectionError::NotFound) => {} Err(err) => return Err(err), } } Err(SelectionError::NotFound) } /// Performs font matching according to the CSS Fonts Level 3 specification and returns the /// handle. #[inline] pub fn select_best_match( &self, family_names: &[FamilyName], properties: &Properties, ) -> Result { ::select_best_match(self, family_names, properties) } /// Returns an iterator over the contained sources. #[inline] pub fn iter<'a>(&'a self) -> MultiIter<'a> { MultiIter(self.subsources.iter()) } /// Returns an iterator over the contained sources with mutable access. #[inline] pub fn iter_mut<'a>(&'a mut self) -> MultiIterMut<'a> { MultiIterMut(self.subsources.iter_mut()) } /// A convenience method to get the first source with the given type. /// /// Returns `None` if no source of the given type was found. pub fn find_source(&self) -> Option<&T> { self.iter().find_map(|v| v.as_any().downcast_ref()) } /// A convenience method to get the first source with the given type. /// /// Returns `None` if no source of the given type was found. pub fn find_source_mut(&mut self) -> Option<&mut T> { self.iter_mut().find_map(|v| v.as_mut_any().downcast_mut()) } } impl Source for MultiSource { #[inline] fn all_fonts(&self) -> Result, SelectionError> { self.all_fonts() } #[inline] fn all_families(&self) -> Result, SelectionError> { self.all_families() } #[inline] fn select_family_by_name(&self, family_name: &str) -> Result { self.select_family_by_name(family_name) } #[inline] fn select_by_postscript_name(&self, postscript_name: &str) -> Result { self.select_by_postscript_name(postscript_name) } fn as_any(&self) -> &dyn Any { self } fn as_mut_any(&mut self) -> &mut dyn Any { self } } impl Index for MultiSource { type Output = dyn Source; fn index(&self, idx: usize) -> &Self::Output { &*self.subsources[idx] } } impl IndexMut for MultiSource { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { &mut *self.subsources[idx] } } /// An iterator over the sources in a [`MultiSource`]. pub struct MultiIter<'a>(slice::Iter<'a, Box>); impl<'a> Iterator for MultiIter<'a> { type Item = &'a dyn Source; fn next(&mut self) -> Option { self.0.next().map(|v| &**v) } } impl fmt::Debug for MultiIter<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("MultiIter").finish() } } /// An iterator over the mutable sources in a [`MultiSource`]. pub struct MultiIterMut<'a>(slice::IterMut<'a, Box>); impl<'a> Iterator for MultiIterMut<'a> { type Item = &'a mut dyn Source; fn next(&mut self) -> Option { self.0.next().map(|v| &mut **v) } } impl fmt::Debug for MultiIterMut<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("MultiIterMut").finish() } } font-kit-0.11.0/src/utils.rs000064400000000000000000000024130072674642500137630ustar 00000000000000// font-kit/src/utils.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Miscellaneous utilities for use in this crate. #![allow(dead_code)] use std::fs::File; use std::io::{Error as IOError, Read}; pub(crate) static SFNT_VERSIONS: [[u8; 4]; 4] = [ [0x00, 0x01, 0x00, 0x00], [b'O', b'T', b'T', b'O'], [b't', b'r', b'u', b'e'], [b't', b'y', b'p', b'1'], ]; pub(crate) fn clamp(x: f32, min: f32, max: f32) -> f32 { if x < min { min } else if x > max { max } else { x } } #[inline] pub(crate) fn lerp(a: f32, b: f32, t: f32) -> f32 { a + (b - a) * t } #[inline] pub(crate) fn div_round_up(a: usize, b: usize) -> usize { (a + b - 1) / b } pub(crate) fn slurp_file(file: &mut File) -> Result, IOError> { let mut data = match file.metadata() { Ok(metadata) => Vec::with_capacity(metadata.len() as usize), Err(_) => vec![], }; file.read_to_end(&mut data)?; Ok(data) } font-kit-0.11.0/tests/select_font.rs000064400000000000000000000325470072674642500155160ustar 00000000000000// font-kit/tests/select-font.rs // // Copyright © 2019 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. extern crate font_kit; use font_kit::error::SelectionError; use font_kit::family_name::FamilyName; use font_kit::handle::Handle; use font_kit::properties::Properties; use std::ffi::OsStr; #[cfg(feature = "source")] use font_kit::source::SystemSource; macro_rules! match_handle { ($handle:expr, $path:expr, $index:expr) => { match $handle { Handle::Path { ref path, font_index, } => { assert_eq!(path, path); assert_eq!( font_index, $index, "expecting font index {} not {}", font_index, $index ); } Handle::Memory { bytes: _, font_index, } => { assert_eq!( font_index, $index, "expecting font index {} not {}", font_index, $index ); } } }; } #[inline(always)] fn check_filename(handle: &Handle, filename: &str) { match *handle { Handle::Path { ref path, font_index, } => { assert_eq!(path.file_name(), Some(OsStr::new(filename))); assert_eq!(font_index, 0); } _ => panic!("Expected path handle!"), } } #[cfg(all(feature = "source", target_os = "windows"))] mod test { use super::*; #[test] fn select_best_match_serif() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Serif], &Properties::default()) .unwrap(); match_handle!(handle, "C:\\WINDOWS\\FONTS\\TIMES.TTF", 0); } #[test] fn select_best_match_sans_serif() { let handle = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::default()) .unwrap(); match_handle!(handle, ":\\WINDOWS\\FONTS\\ARIAL.TTF", 0); } #[test] fn select_best_match_monospace() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Monospace], &Properties::default()) .unwrap(); match_handle!(handle, "C:\\WINDOWS\\FONTS\\COUR.TTF", 0); } #[test] fn select_best_match_cursive() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Cursive], &Properties::default()) .unwrap(); match_handle!(handle, "C:\\WINDOWS\\FONTS\\COMIC.TTF", 0); } // TODO: no such font in a Travis CI instance. // https://github.com/servo/font-kit/issues/62 // #[test] // fn select_best_match_fantasy() { // let handle = SystemSource::new() // .select_best_match(&[FamilyName::Fantasy], &Properties::default()) // .unwrap(); // match_handle!(handle, "C:\\WINDOWS\\FONTS\\IMPACT.TTF", 0); // } #[test] fn select_best_match_bold_sans_serif() { let handle = SystemSource::new() .select_best_match( &[FamilyName::SansSerif], &Properties { style: font_kit::properties::Style::Normal, weight: font_kit::properties::Weight::BOLD, stretch: font_kit::properties::Stretch::NORMAL, }, ) .unwrap(); match_handle!(handle, "C:\\WINDOWS\\FONTS\\ARIALBD.TTF", 0); } #[test] fn select_best_match_by_name_after_invalid() { let handle = SystemSource::new() .select_best_match( &[ FamilyName::Title("Invalid".to_string()), FamilyName::Title("Times New Roman".to_string()), ], &Properties::default(), ) .unwrap(); match_handle!(handle, "C:\\WINDOWS\\FONTS\\TIMES.TTF", 0); } #[test] fn select_family_by_name_arial() { let family = SystemSource::new().select_family_by_name("Arial").unwrap(); assert_eq!(family.fonts().len(), 8); match_handle!(family.fonts()[0], "C:\\WINDOWS\\FONTS\\ARIAL.TTF", 0); match_handle!(family.fonts()[1], "C:\\WINDOWS\\FONTS\\ARIALI.TTF", 0); match_handle!(family.fonts()[2], "C:\\WINDOWS\\FONTS\\ARIALBD.TTF", 0); match_handle!(family.fonts()[3], "C:\\WINDOWS\\FONTS\\ARIALBI.TTF", 0); match_handle!(family.fonts()[4], "C:\\WINDOWS\\FONTS\\ARIBLK.TTF", 0); match_handle!(family.fonts()[5], "C:\\WINDOWS\\FONTS\\ARIAL.TTF", 0); match_handle!(family.fonts()[6], "C:\\WINDOWS\\FONTS\\ARIALBD.TTF", 0); match_handle!(family.fonts()[7], "C:\\WINDOWS\\FONTS\\ARIBLK.TTF", 0); } #[allow(non_snake_case)] #[test] fn select_by_postscript_name_ArialMT() { let font = SystemSource::new() .select_by_postscript_name("ArialMT") .unwrap() .load() .unwrap(); assert_eq!(font.postscript_name().unwrap(), "ArialMT"); } #[test] fn select_by_postscript_name_invalid() { match SystemSource::new().select_by_postscript_name("zxhjfgkadsfhg") { Err(SelectionError::NotFound) => {} other => panic!("unexpected error: {:?}", other), } } // This test fails on TravisCI's Windows environment. #[test] #[ignore] fn select_localized_family_name() { let handle = SystemSource::new() .select_best_match( &[FamilyName::Title("MS ゴシック".to_string())], &Properties::default(), ) .unwrap(); match_handle!(handle, "C:\\WINDOWS\\FONTS\\msgothic.ttc", 0); } } #[cfg(all(feature = "source", target_os = "linux"))] mod test { use super::*; #[test] fn select_best_match_serif() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Serif], &Properties::default()) .unwrap(); check_filename(&handle, "DejaVuSerif.ttf"); } #[test] fn select_best_match_sans_serif() { let handle = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::default()) .unwrap(); check_filename(&handle, "DejaVuSans.ttf"); } #[test] fn select_best_match_monospace() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Monospace], &Properties::default()) .unwrap(); check_filename(&handle, "DejaVuSansMono.ttf"); } #[test] fn select_best_match_bold_sans_serif() { let handle = SystemSource::new() .select_best_match( &[FamilyName::SansSerif], &Properties { style: font_kit::properties::Style::Normal, weight: font_kit::properties::Weight::BOLD, stretch: font_kit::properties::Stretch::NORMAL, }, ) .unwrap(); check_filename(&handle, "DejaVuSans-Bold.ttf"); } #[test] fn select_best_match_by_name_after_invalid() { let handle = SystemSource::new() .select_best_match( &[ FamilyName::Title("Invalid".to_string()), FamilyName::Title("DejaVu Sans".to_string()), ], &Properties::default(), ) .unwrap(); check_filename(&handle, "DejaVuSans.ttf"); } #[test] fn select_family_by_name_dejavu() { let family = SystemSource::new() .select_family_by_name("DejaVu Sans") .unwrap(); let mut filenames: Vec = family .fonts() .iter() .map(|handle| match *handle { Handle::Path { ref path, font_index, } => { assert_eq!(font_index, 0); path.file_name() .expect("Where's the filename?") .to_string_lossy() .into_owned() } _ => panic!("Expected path handle!"), }) .collect(); assert!(filenames .iter() .filter(|name| &**name == "DejaVuSans-Bold.ttf") .next() .is_some()); assert!(filenames .iter() .filter(|name| &**name == "DejaVuSans.ttf") .next() .is_some()); } #[allow(non_snake_case)] #[test] fn select_by_postscript_name_ArialMT() { let font = SystemSource::new() .select_by_postscript_name("DejaVuSans") .unwrap() .load() .unwrap(); assert_eq!(font.postscript_name().unwrap(), "DejaVuSans"); } #[test] fn select_by_postscript_name_invalid() { match SystemSource::new().select_by_postscript_name("zxhjfgkadsfhg") { Err(SelectionError::NotFound) => {} other => panic!("unexpected error: {:?}", other), } } #[test] fn select_localized_family_name() { if let Ok(handle) = SystemSource::new().select_best_match( &[FamilyName::Title("さざなみゴシック".to_string())], &Properties::default(), ) { check_filename(&handle, "sazanami-gothic.ttf"); } } } #[cfg(all(feature = "source", target_os = "macos"))] mod test { use super::*; #[test] fn select_best_match_serif() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Serif], &Properties::default()) .unwrap(); match_handle!(handle, "/Library/Fonts/Times New Roman.ttf", 0); } #[test] fn select_best_match_sans_serif() { let handle = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::default()) .unwrap(); match_handle!(handle, "/Library/Fonts/Arial.ttf", 0); } #[test] fn select_best_match_monospace() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Monospace], &Properties::default()) .unwrap(); match_handle!(handle, "/Library/Fonts/Courier New.ttf", 0); } #[test] fn select_best_match_cursive() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Cursive], &Properties::default()) .unwrap(); match_handle!(handle, "/Library/Fonts/Comic Sans MS.ttf", 0); } #[test] fn select_best_match_fantasy() { let handle = SystemSource::new() .select_best_match(&[FamilyName::Fantasy], &Properties::default()) .unwrap(); match_handle!(handle, "/Library/Fonts/Papyrus.ttc", 1); } #[test] fn select_best_match_bold_sans_serif() { let handle = SystemSource::new() .select_best_match( &[FamilyName::SansSerif], &Properties { style: font_kit::properties::Style::Normal, weight: font_kit::properties::Weight::BOLD, stretch: font_kit::properties::Stretch::NORMAL, }, ) .unwrap(); match_handle!(handle, "/Library/Fonts/Arial Bold.ttf", 0); } #[test] fn select_best_match_by_name_after_invalid() { let handle = SystemSource::new() .select_best_match( &[ FamilyName::Title("Invalid".to_string()), FamilyName::Title("Times New Roman".to_string()), ], &Properties::default(), ) .unwrap(); match_handle!(handle, "/Library/Fonts/Times New Roman.ttf", 0); } #[test] fn select_family_by_name_arial() { let family = SystemSource::new().select_family_by_name("Arial").unwrap(); assert_eq!(family.fonts().len(), 4); match_handle!(family.fonts()[0], "/Library/Fonts/Arial.ttf", 0); match_handle!(family.fonts()[1], "/Library/Fonts/Arial Bold.ttf", 0); match_handle!(family.fonts()[2], "/Library/Fonts/Arial Bold Italic.ttf", 0); match_handle!(family.fonts()[3], "/Library/Fonts/Arial Italic.ttf", 0); } #[allow(non_snake_case)] #[test] fn select_by_postscript_name_ArialMT() { let font = SystemSource::new() .select_by_postscript_name("ArialMT") .unwrap() .load() .unwrap(); assert_eq!(font.postscript_name().unwrap(), "ArialMT"); } #[test] fn select_by_postscript_name_invalid() { match SystemSource::new().select_by_postscript_name("zxhjfgkadsfhg") { Err(SelectionError::NotFound) => {} other => panic!("unexpected error: {:?}", other), } } #[test] fn select_localized_family_name() { let handle = SystemSource::new() .select_best_match( &[FamilyName::Title("רעננה".to_string())], &Properties::default(), ) .unwrap(); match_handle!(handle, "/Library/Fonts/Raanana.ttc", 0); } } font-kit-0.11.0/tests/tests.rs000064400000000000000000001251700072674642500143460ustar 00000000000000// font-kit/tests/tests.rs // // Copyright © 2019 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // General tests. use font_kit::canvas::{Canvas, Format, RasterizationOptions}; use font_kit::family_name::FamilyName; use font_kit::file_type::FileType; use font_kit::font::Font; use font_kit::hinting::HintingOptions; use font_kit::outline::{Contour, Outline, OutlineBuilder, PointFlags}; use font_kit::properties::{Properties, Stretch, Weight}; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::{Vector2F, Vector2I}; use std::fs::File; use std::io::Read; use std::sync::Arc; #[cfg(feature = "source")] use font_kit::source::SystemSource; static TEST_FONT_FILE_PATH: &'static str = "resources/tests/eb-garamond/EBGaramond12-Regular.otf"; static TEST_FONT_POSTSCRIPT_NAME: &'static str = "EBGaramond12-Regular"; static TEST_FONT_COLLECTION_FILE_PATH: &'static str = "resources/tests/eb-garamond/EBGaramond12.otc"; static TEST_FONT_COLLECTION_POSTSCRIPT_NAME: [&'static str; 2] = ["EBGaramond12-Regular", "EBGaramond12-Italic"]; static FILE_PATH_EB_GARAMOND_TTF: &'static str = "resources/tests/eb-garamond/EBGaramond12-Regular.ttf"; static FILE_PATH_INCONSOLATA_TTF: &'static str = "resources/tests/inconsolata/Inconsolata-Regular.ttf"; #[cfg(not(target_os = "linux"))] static KNOWN_SYSTEM_FONT_NAME: &'static str = "Arial"; #[cfg(target_os = "linux")] static KNOWN_SYSTEM_FONT_NAME: &'static str = "DejaVu Sans"; static SFNT_VERSIONS: [[u8; 4]; 4] = [ [0x00, 0x01, 0x00, 0x00], [b'O', b'T', b'T', b'O'], [b't', b'r', b'u', b'e'], [b't', b'y', b'p', b'1'], ]; const OPENTYPE_TABLE_TAG_HEAD: u32 = 0x68656164; #[cfg(feature = "source")] #[test] pub fn get_font_full_name() { let font = SystemSource::new() .select_best_match( &[FamilyName::Title(KNOWN_SYSTEM_FONT_NAME.to_string())], &Properties::new(), ) .unwrap() .load() .unwrap(); assert_eq!(font.full_name(), KNOWN_SYSTEM_FONT_NAME); } #[cfg(feature = "source")] #[test] pub fn get_font_full_name_from_lowercase_family_name() { let font = SystemSource::new() .select_best_match( &[FamilyName::Title( KNOWN_SYSTEM_FONT_NAME.to_ascii_lowercase(), )], &Properties::new(), ) .unwrap() .load() .unwrap(); assert_eq!(font.full_name(), KNOWN_SYSTEM_FONT_NAME); } #[test] pub fn load_font_from_file() { let mut file = File::open(TEST_FONT_FILE_PATH).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); assert_eq!(font.postscript_name().unwrap(), TEST_FONT_POSTSCRIPT_NAME); } #[test] pub fn load_font_from_memory() { let mut file = File::open(TEST_FONT_FILE_PATH).unwrap(); let mut font_data = vec![]; file.read_to_end(&mut font_data).unwrap(); let font = Font::from_bytes(Arc::new(font_data), 0).unwrap(); assert_eq!(font.postscript_name().unwrap(), TEST_FONT_POSTSCRIPT_NAME); } #[test] pub fn analyze_file() { let mut file = File::open(TEST_FONT_FILE_PATH).unwrap(); assert_eq!(Font::analyze_file(&mut file).unwrap(), FileType::Single); } #[test] pub fn analyze_bytes() { let mut file = File::open(TEST_FONT_FILE_PATH).unwrap(); let mut font_data = vec![]; file.read_to_end(&mut font_data).unwrap(); assert_eq!( Font::analyze_bytes(Arc::new(font_data)).unwrap(), FileType::Single ); } #[cfg(feature = "source")] #[test] pub fn get_glyph_for_char() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('a').expect("No glyph for char!"); assert_eq!(glyph, 68); } #[cfg(all( feature = "source", any(target_family = "windows", target_os = "macos") ))] #[test] pub fn get_glyph_outline() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('i').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::None, &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!( outline, Outline { contours: vec![ Contour { positions: vec![ Vector2F::new(136.0, 1259.0), Vector2F::new(136.0, 1466.0), Vector2F::new(316.0, 1466.0), Vector2F::new(316.0, 1259.0), ], flags: vec![PointFlags::empty(); 4], }, Contour { positions: vec![ Vector2F::new(136.0, 0.0), Vector2F::new(136.0, 1062.0), Vector2F::new(316.0, 1062.0), Vector2F::new(316.0, 0.0), ], flags: vec![PointFlags::empty(); 4], }, ], } ); } #[cfg(all( feature = "source", not(any(target_family = "windows", target_os = "macos", target_os = "ios")) ))] #[test] pub fn get_glyph_outline() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('i').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::None, &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!( outline, Outline { contours: vec![ Contour { positions: vec![ Vector2F::new(193.0, 1120.0), Vector2F::new(377.0, 1120.0), Vector2F::new(377.0, 0.0), Vector2F::new(193.0, 0.0), ], flags: vec![PointFlags::empty(); 4], }, Contour { positions: vec![ Vector2F::new(193.0, 1556.0), Vector2F::new(377.0, 1556.0), Vector2F::new(377.0, 1323.0), Vector2F::new(193.0, 1323.0), ], flags: vec![PointFlags::empty(); 4], }, ], } ); } // Right now, only FreeType can do hinting. #[cfg(all( not(any(target_os = "macos", target_os = "ios", target_family = "windows")), feature = "loader-freetype-default", feature = "source" ))] #[test] pub fn get_vertically_hinted_glyph_outline() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('i').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::Vertical(16.0), &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!( outline, Outline { contours: vec![ Contour { positions: vec![ Vector2F::new(136.0, 1316.0), Vector2F::new(136.0, 1536.0), Vector2F::new(316.0, 1536.0), Vector2F::new(316.0, 1316.0), ], flags: vec![PointFlags::empty(); 4], }, Contour { positions: vec![ Vector2F::new(136.0, 0.0), Vector2F::new(136.0, 1152.0), Vector2F::new(316.0, 1152.0), Vector2F::new(316.0, 0.0), ], flags: vec![PointFlags::empty(); 4], }, ], } ); } #[cfg(all( feature = "source", not(any(target_os = "macos", target_os = "ios", target_family = "windows")) ))] #[test] pub fn get_vertically_hinted_glyph_outline() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('i').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::Vertical(16.0), &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!( outline, Outline { contours: vec![ Contour { positions: vec![ Vector2F::new(256.0, 1152.0), Vector2F::new(384.0, 1152.0), Vector2F::new(384.0, 0.0), Vector2F::new(256.0, 0.0), ], flags: vec![PointFlags::empty(); 4], }, Contour { positions: vec![ Vector2F::new(256.0, 1536.0), Vector2F::new(384.0, 1536.0), Vector2F::new(384.0, 1280.0), Vector2F::new(256.0, 1280.0), ], flags: vec![PointFlags::empty(); 4], }, ], } ); } // Right now, only FreeType can do hinting. #[cfg(all( not(any(target_os = "macos", target_os = "ios", target_family = "windows")), feature = "loader-freetype-default", feature = "source" ))] #[test] pub fn get_fully_hinted_glyph_outline() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('i').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::Full(10.0), &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!( outline, Outline { contours: vec![ Contour { positions: vec![ Vector2F::new(137.6, 1228.8), Vector2F::new(137.6, 1433.6), Vector2F::new(316.80002, 1433.6), Vector2F::new(316.80002, 1228.8), ], flags: vec![PointFlags::empty(); 4], }, Contour { positions: vec![ Vector2F::new(137.6, 0.0), Vector2F::new(137.6, 1024.0), Vector2F::new(316.80002, 1024.0), Vector2F::new(316.80002, 0.0), ], flags: vec![PointFlags::empty(); 4], }, ], } ); } #[cfg(all( feature = "source", not(any(target_os = "macos", target_os = "ios", target_family = "windows")) ))] #[test] pub fn get_fully_hinted_glyph_outline() { let mut file = File::open(FILE_PATH_INCONSOLATA_TTF).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); let glyph = font.glyph_for_char('i').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::Full(10.0), &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!( outline, Outline { contours: vec![ Contour { positions: vec![ Vector2F::new(100.0, 100.0), Vector2F::new(200.0, 100.0), Vector2F::new(200.0, 400.0), Vector2F::new(100.0, 400.0), Vector2F::new(100.0, 500.0), Vector2F::new(300.0, 500.0), Vector2F::new(300.0, 100.0), Vector2F::new(400.0, 100.0), Vector2F::new(400.0, 0.0), Vector2F::new(100.0, 0.0), ], flags: vec![PointFlags::empty(); 10], }, Contour { positions: vec![ Vector2F::new(200.0, 600.0), Vector2F::new(200.0, 600.0), Vector2F::new(200.0, 600.0), Vector2F::new(200.0, 600.0), Vector2F::new(200.0, 600.0), Vector2F::new(200.0, 700.0), Vector2F::new(200.0, 700.0), Vector2F::new(200.0, 700.0), Vector2F::new(200.0, 700.0), Vector2F::new(300.0, 700.0), Vector2F::new(300.0, 700.0), Vector2F::new(300.0, 700.0), Vector2F::new(300.0, 600.0), Vector2F::new(300.0, 600.0), Vector2F::new(300.0, 600.0), Vector2F::new(300.0, 600.0), Vector2F::new(200.0, 600.0), ], flags: vec![ PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), ], }, ], } ); } #[test] pub fn get_empty_glyph_outline() { let mut file = File::open(TEST_FONT_FILE_PATH).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); let glyph = font.glyph_for_char(' ').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::None, &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!(outline, Outline::new()); } // https://github.com/servo/font-kit/issues/141 #[test] pub fn get_glyph_raster_bounds() { let mut file = File::open(FILE_PATH_INCONSOLATA_TTF).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); let glyph = font.glyph_for_char('J').expect("No glyph for char!"); let transform = Transform2F::default(); let size = 32.0; let hinting_options = HintingOptions::None; let rasterization_options = RasterizationOptions::GrayscaleAa; #[cfg(all(not(target_family = "windows")))] let expected_rect = RectI::new(Vector2I::new(1, -20), Vector2I::new(14, 21)); #[cfg(target_family = "windows")] let expected_rect = RectI::new(Vector2I::new(1, -20), Vector2I::new(14, 20)); assert_eq!( font.raster_bounds( glyph, size, transform, hinting_options, rasterization_options ), Ok(expected_rect) ); } #[cfg(all( feature = "source", any(target_family = "windows", target_os = "macos", target_os = "ios") ))] #[test] pub fn get_glyph_typographic_bounds() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('a').expect("No glyph for char!"); assert_eq!( font.typographic_bounds(glyph), Ok(RectF::new( Vector2F::new(74.0, -24.0), Vector2F::new(978.0, 1110.0) )) ); } #[cfg(all( feature = "source", not(any(target_family = "windows", target_os = "macos", target_os = "ios")) ))] #[test] pub fn get_glyph_typographic_bounds() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('a').expect("No glyph for char!"); assert_eq!( font.typographic_bounds(glyph), Ok(RectF::new( Vector2F::new(123.0, -29.0), Vector2F::new(946.0, 1176.0) )) ); } #[cfg(all(feature = "source", target_family = "windows"))] #[test] pub fn get_glyph_advance_and_origin() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('a').expect("No glyph for char!"); assert_eq!(font.advance(glyph), Ok(Vector2F::new(1139.0, 0.0))); assert_eq!(font.origin(glyph), Ok(Vector2F::new(74.0, 1898.0))); } #[cfg(all(feature = "source", target_os = "macos"))] #[test] pub fn get_glyph_advance_and_origin() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('a').expect("No glyph for char!"); assert_eq!(font.advance(glyph), Ok(Vector2F::new(1139.0, 0.0))); assert_eq!(font.origin(glyph), Ok(Vector2F::default())); } #[cfg(all( feature = "source", not(any(target_family = "windows", target_os = "macos", target_os = "ios")) ))] #[test] pub fn get_glyph_advance_and_origin() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph = font.glyph_for_char('a').expect("No glyph for char!"); assert_eq!(font.advance(glyph), Ok(Vector2F::new(1255.0, 0.0))); assert_eq!(font.origin(glyph), Ok(Vector2F::default())); } #[cfg(all( feature = "source", any(target_family = "windows", target_os = "macos") ))] #[test] pub fn get_font_metrics() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let metrics = font.metrics(); assert_eq!(metrics.units_per_em, 2048); assert_eq!(metrics.ascent, 1854.0); assert_eq!(metrics.descent, -434.0); assert_eq!(metrics.line_gap, 67.0); assert_eq!(metrics.underline_position, -217.0); assert_eq!(metrics.underline_thickness, 150.0); assert_eq!(metrics.cap_height, 1467.0); assert_eq!(metrics.x_height, 1062.0); // Different versions of the font can have different max heights, so ignore that. let bounding_box = metrics.bounding_box; assert_eq!(bounding_box.origin(), Vector2F::new(-1361.0, -665.0)); assert_eq!(bounding_box.width(), 5457.0); } #[cfg(all( feature = "source", not(any(target_family = "windows", target_os = "macos", target_os = "ios")) ))] #[test] pub fn get_font_metrics() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let metrics = font.metrics(); assert_eq!(metrics.units_per_em, 2048); assert_eq!(metrics.ascent, 1901.0); assert_eq!(metrics.descent, -483.0); assert_eq!(metrics.line_gap, 0.0); // FIXME(pcwalton): Huh?! assert_eq!(metrics.underline_position, -130.0); assert_eq!(metrics.underline_thickness, 90.0); assert_eq!(metrics.cap_height, 0.0); // FIXME(pcwalton): Huh?! assert_eq!(metrics.x_height, 0.0); // FIXME(pcwalton): Huh?! assert_eq!( metrics.bounding_box, RectF::new( Vector2F::new(-2090.0, -948.0), Vector2F::new(5763.0, 3472.0) ) ); } #[cfg(feature = "source")] #[test] pub fn get_font_properties() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let properties = font.properties(); assert_eq!(properties.weight, Weight(400.0)); assert_eq!(properties.stretch, Stretch(1.0)); } #[cfg(feature = "source")] #[test] pub fn get_font_data() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let data = font.copy_font_data().unwrap(); debug_assert!(SFNT_VERSIONS.iter().any(|version| data[0..4] == *version)); } #[cfg(feature = "source")] #[test] pub fn load_font_table() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let head_table = font .load_font_table(OPENTYPE_TABLE_TAG_HEAD) .expect("Where's the `head` table?"); assert_eq!(&head_table[12..16], &[0x5f, 0x0f, 0x3c, 0xf5]); } #[cfg(feature = "source")] #[test] pub fn rasterize_glyph_with_grayscale_aa() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char('L').unwrap(); let size = 32.0; let raster_rect = font .raster_bounds( glyph_id, size, Transform2F::default(), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); let mut canvas = Canvas::new(raster_rect.size(), Format::A8); font.rasterize_glyph( &mut canvas, glyph_id, size, Transform2F::from_translation(-raster_rect.origin().to_f32()), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); check_L_shape(&canvas); } #[cfg(feature = "source")] #[test] pub fn rasterize_glyph_bilevel() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char('L').unwrap(); let size = 16.0; let raster_rect = font .raster_bounds( glyph_id, size, Transform2F::default(), HintingOptions::None, RasterizationOptions::Bilevel, ) .unwrap(); let mut canvas = Canvas::new(raster_rect.size(), Format::A8); font.rasterize_glyph( &mut canvas, glyph_id, size, Transform2F::from_translation(-raster_rect.origin().to_f32()), HintingOptions::None, RasterizationOptions::Bilevel, ) .unwrap(); assert!(canvas .pixels .iter() .all(|&value| value == 0 || value == 0xff)); check_L_shape(&canvas); } #[cfg(feature = "source")] #[test] pub fn rasterize_glyph_bilevel_offset() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char('L').unwrap(); let size = 32.0; let raster_rect = font .raster_bounds( glyph_id, size, Transform2F::from_translation(Vector2F::new(30.0, 100.0)), HintingOptions::None, RasterizationOptions::Bilevel, ) .unwrap(); let mut canvas = Canvas::new(raster_rect.size(), Format::A8); font.rasterize_glyph( &mut canvas, glyph_id, size, Transform2F::from_translation(-raster_rect.origin().to_f32() + Vector2F::new(30.0, 100.0)), HintingOptions::None, RasterizationOptions::Bilevel, ) .unwrap(); assert!(canvas .pixels .iter() .all(|&value| value == 0 || value == 0xff)); check_L_shape(&canvas); } #[cfg(all( feature = "source", any( not(any(target_os = "macos", target_os = "ios", target_family = "windows")), feature = "loader-freetype-default" ) ))] #[test] pub fn rasterize_glyph_with_full_hinting() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char('L').unwrap(); let size = 32.0; let raster_rect = font .raster_bounds( glyph_id, size, Transform2F::default(), HintingOptions::None, RasterizationOptions::Bilevel, ) .unwrap(); let origin = -raster_rect.origin().to_f32(); let mut canvas = Canvas::new(raster_rect.size(), Format::A8); font.rasterize_glyph( &mut canvas, glyph_id, size, Transform2F::from_translation(origin), HintingOptions::Full(size), RasterizationOptions::GrayscaleAa, ) .unwrap(); check_L_shape(&canvas); // Make sure the top and bottom (non-blank) rows have some fully black pixels in them. let mut top_row = &canvas.pixels[0..canvas.stride]; if top_row.iter().all(|&value| value == 0) { top_row = &canvas.pixels[(1 * canvas.stride)..(2 * canvas.stride)]; } assert!(top_row.iter().any(|&value| value == 0xff)); for y in (0..(canvas.size.y() as usize)).rev() { let bottom_row = &canvas.pixels[(y * canvas.stride)..((y + 1) * canvas.stride)]; if bottom_row.iter().all(|&value| value == 0) { continue; } assert!(bottom_row.iter().any(|&value| value == 0xff)); break; } } #[cfg(all(feature = "source", target_family = "windows"))] #[test] pub fn rasterize_glyph() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char('{').unwrap(); let size = 32.0; let raster_rect = font .raster_bounds( glyph_id, size, Transform2F::default(), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); let mut canvas = Canvas::new(raster_rect.size(), Format::A8); font.rasterize_glyph( &mut canvas, glyph_id, size, Transform2F::from_translation(-raster_rect.origin().to_f32()), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); check_curly_shape(&canvas); } // Tests that an empty glyph can be successfully rasterized to a 0x0 canvas (issue #7). #[cfg(feature = "source")] #[test] pub fn rasterize_empty_glyph() { let mut file = File::open(TEST_FONT_FILE_PATH).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); let glyph = font.glyph_for_char(' ').expect("No glyph for char!"); let mut canvas = Canvas::new(Vector2I::splat(16), Format::A8); font.rasterize_glyph( &mut canvas, glyph, 16.0, Transform2F::default(), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); } // Tests that an empty glyph can be successfully rasterized to a 0x0 canvas (issue #7). #[cfg(feature = "source")] #[test] pub fn rasterize_empty_glyph_on_empty_canvas() { let mut file = File::open(TEST_FONT_FILE_PATH).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); let glyph = font.glyph_for_char(' ').expect("No glyph for char!"); let size = 32.0; let raster_rect = font .raster_bounds( glyph, size, Transform2F::default(), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); let mut canvas = Canvas::new(raster_rect.size(), Format::A8); font.rasterize_glyph( &mut canvas, glyph, size, Transform2F::from_translation(-raster_rect.origin().to_f32()), HintingOptions::None, RasterizationOptions::GrayscaleAa, ) .unwrap(); } #[cfg(feature = "source")] #[test] pub fn font_transform() { let font = SystemSource::new() .select_best_match(&[FamilyName::SansSerif], &Properties::new()) .unwrap() .load() .unwrap(); let glyph_id = font.glyph_for_char('L').unwrap(); let size = 16.0; let raster_rect = font .raster_bounds( glyph_id, size, Transform2F::from_translation(Vector2F::splat(8.0)), HintingOptions::None, RasterizationOptions::Bilevel, ) .unwrap(); let raster_rect2 = font .raster_bounds( glyph_id, size, Transform2F::row_major(3.0, 0.0, 0.0, 3.0, 8.0, 8.0), HintingOptions::None, RasterizationOptions::Bilevel, ) .unwrap(); assert!((raster_rect2.width() - raster_rect.width() * 3).abs() <= 3); assert!((raster_rect2.height() - raster_rect.height() * 3).abs() <= 3); assert!((raster_rect2.origin_x() - ((raster_rect.origin_x() - 8) * 3 + 8)).abs() <= 3); assert!((raster_rect2.origin_y() - ((raster_rect.origin_y() - 8) * 3 + 8)).abs() <= 3); } #[test] fn load_fonts_from_opentype_collection() { let mut file = File::open(TEST_FONT_COLLECTION_FILE_PATH).unwrap(); { let font = Font::from_file(&mut file, 0).unwrap(); assert_eq!( font.postscript_name().unwrap(), TEST_FONT_COLLECTION_POSTSCRIPT_NAME[0] ); } let font = Font::from_file(&mut file, 1).unwrap(); assert_eq!( font.postscript_name().unwrap(), TEST_FONT_COLLECTION_POSTSCRIPT_NAME[1] ); } #[test] fn get_glyph_count() { let font = Font::from_path(TEST_FONT_FILE_PATH, 0).unwrap(); assert_eq!(font.glyph_count(), 3084); } // The initial off-curve point used to cause an assertion in the FreeType backend. #[test] fn get_glyph_outline_eb_garamond_exclam() { let mut file = File::open(FILE_PATH_EB_GARAMOND_TTF).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); let glyph = font.glyph_for_char('!').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::None, &mut outline_builder) .unwrap(); // The TrueType spec doesn't specify the rounding method for midpoints, as far as I can tell. // So we are lenient and accept either values rounded down (what Core Text provides if the // first point is off-curve, it seems) or precise floating-point values (what our FreeType // loader provides). let mut outline = outline_builder.into_outline(); for contour in &mut outline.contours { for position in &mut contour.positions { *position = position.floor(); } } println!("{:#?}", outline); assert_eq!( outline, Outline { contours: vec![ Contour { positions: vec![ Vector2F::new(114.0, 598.0), Vector2F::new(114.0, 619.0), Vector2F::new(127.0, 634.0), Vector2F::new(141.0, 649.0), Vector2F::new(161.0, 649.0), Vector2F::new(181.0, 649.0), Vector2F::new(193.0, 634.0), Vector2F::new(206.0, 619.0), Vector2F::new(206.0, 598.0), Vector2F::new(206.0, 526.0), Vector2F::new(176.0, 244.0), Vector2F::new(172.0, 205.0), Vector2F::new(158.0, 205.0), Vector2F::new(144.0, 205.0), Vector2F::new(140.0, 244.0), Vector2F::new(114.0, 491.0), Vector2F::new(114.0, 598.0), ], flags: vec![ PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), ], }, Contour { positions: vec![ Vector2F::new(117.0, 88.0), Vector2F::new(135.0, 106.0), Vector2F::new(160.0, 106.0), Vector2F::new(185.0, 106.0), Vector2F::new(202.0, 88.0), Vector2F::new(220.0, 71.0), Vector2F::new(220.0, 46.0), Vector2F::new(220.0, 21.0), Vector2F::new(202.0, 3.0), Vector2F::new(185.0, -14.0), Vector2F::new(160.0, -14.0), Vector2F::new(135.0, -14.0), Vector2F::new(117.0, 3.0), Vector2F::new(100.0, 21.0), Vector2F::new(100.0, 46.0), Vector2F::new(100.0, 71.0), Vector2F::new(117.0, 88.0), ], flags: vec![ PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), ], }, ], } ); } // https://github.com/pcwalton/pathfinder/issues/84 #[allow(non_snake_case)] #[test] fn get_glyph_outline_inconsolata_J() { let mut file = File::open(FILE_PATH_INCONSOLATA_TTF).unwrap(); let font = Font::from_file(&mut file, 0).unwrap(); let glyph = font.glyph_for_char('J').expect("No glyph for char!"); let mut outline_builder = OutlineBuilder::new(); font.outline(glyph, HintingOptions::None, &mut outline_builder) .unwrap(); let outline = outline_builder.into_outline(); assert_eq!( outline, Outline { contours: vec![Contour { positions: vec![ Vector2F::new(198.0, -11.0), Vector2F::new(106.0, -11.0), Vector2F::new(49.0, 58.0), Vector2F::new(89.0, 108.0), Vector2F::new(96.0, 116.0), Vector2F::new(101.0, 112.0), Vector2F::new(102.0, 102.0), Vector2F::new(106.0, 95.0), Vector2F::new(110.0, 88.0), Vector2F::new(122.0, 78.0), Vector2F::new(157.0, 51.0), Vector2F::new(196.0, 51.0), Vector2F::new(247.0, 51.0), Vector2F::new(269.5, 86.5), Vector2F::new(292.0, 122.0), Vector2F::new(292.0, 208.0), Vector2F::new(292.0, 564.0), Vector2F::new(172.0, 564.0), Vector2F::new(172.0, 623.0), Vector2F::new(457.0, 623.0), Vector2F::new(457.0, 564.0), Vector2F::new(361.0, 564.0), Vector2F::new(361.0, 209.0), Vector2F::new(363.0, 133.0), Vector2F::new(341.0, 84.0), Vector2F::new(319.0, 35.0), Vector2F::new(281.5, 12.0), Vector2F::new(244.0, -11.0), Vector2F::new(198.0, -11.0), ], flags: vec![ PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), PointFlags::CONTROL_POINT_0, PointFlags::empty(), ], }], } ); } // Makes sure that a canvas has an "L" shape in it. This is used to test rasterization. #[allow(non_snake_case)] fn check_L_shape(canvas: &Canvas) { // Find any empty rows at the start. let mut y = 0; while y < canvas.size.y() { let (row_start, row_end) = (canvas.stride * y as usize, canvas.stride * (y + 1) as usize); if canvas.pixels[row_start..row_end].iter().any(|&p| p != 0) { break; } y += 1; } assert!(y < canvas.size.y()); // Find the top part of the L. let mut top_stripe_width = None; while y < canvas.size.y() { let (row_start, row_end) = (canvas.stride * y as usize, canvas.stride * (y + 1) as usize); if let Some(stripe_width) = stripe_width(&canvas.pixels[row_start..row_end]) { if let Some(top_stripe_width) = top_stripe_width { if stripe_width > top_stripe_width { break; } assert_eq!(stripe_width, top_stripe_width); } top_stripe_width = Some(stripe_width); } y += 1; } assert!(y < canvas.size.y()); // Find the bottom part of the L. let mut bottom_stripe_width = None; while y < canvas.size.y() { let (row_start, row_end) = (canvas.stride * y as usize, canvas.stride * (y + 1) as usize); y += 1; if let Some(stripe_width) = stripe_width(&canvas.pixels[row_start..row_end]) { if let Some(bottom_stripe_width) = bottom_stripe_width { assert!(bottom_stripe_width > top_stripe_width.unwrap()); assert_eq!(stripe_width, bottom_stripe_width); } bottom_stripe_width = Some(stripe_width); } } // Find any empty rows at the end. while y < canvas.size.y() { let (row_start, row_end) = (canvas.stride * y as usize, canvas.stride * (y + 1) as usize); y += 1; if canvas.pixels[row_start..row_end].iter().any(|&p| p != 0) { break; } } // Make sure we made it to the end. assert_eq!(y, canvas.size.y()); } // Makes sure that a canvas has an "{" shape in it. This is used to test rasterization. #[cfg(target_family = "windows")] fn check_curly_shape(canvas: &Canvas) { let mut y = 0; let height = canvas.size.y(); // check the upper row and the lower rows are symmetrical while y < height / 2 { let (upper_row_start, upper_row_end) = (canvas.stride * y as usize, canvas.stride * (y + 1) as usize); let (lower_row_start, lower_row_end) = ( canvas.stride * (height - y - 1) as usize, canvas.stride * (height - y) as usize, ); let upper_row = &canvas.pixels[upper_row_start..upper_row_end]; let lower_row = &canvas.pixels[lower_row_start..lower_row_end]; let top_row_width = stripe_width(upper_row).unwrap(); let lower_row_width = stripe_width(lower_row).unwrap(); let upper_row_pixel_start = stride_pixel_start(upper_row).unwrap(); let lower_row_pixel_start = stride_pixel_start(lower_row).unwrap(); if top_row_width == lower_row_width { // check non-zero pixels start at the same index assert_eq!(upper_row_pixel_start, lower_row_pixel_start); } else { //if not, assert that the difference is not greater than 1 assert_eq!((lower_row_width as i32 - top_row_width as i32).abs(), 1); // and assert that the non-zero pixel index difference is not greater than 1 assert_eq!( (upper_row_pixel_start as i32 - lower_row_pixel_start as i32).abs(), 1 ); } y += 1; } } // return the first non-zero pixel index #[cfg(target_family = "windows")] fn stride_pixel_start(pixels: &[u8]) -> Option { let mut index = 0; for x in pixels { if *x != 0 { return Some(index); } index += 1; } None } fn stripe_width(pixels: &[u8]) -> Option { let mut x = 0; // Find the initial empty part. while x < pixels.len() && pixels[x] == 0 { x += 1 } if x == pixels.len() { return None; } // Find the stripe width. let mut stripe_width = 0; while x < pixels.len() && pixels[x] != 0 { x += 1; stripe_width += 1; } // Find the last empty part. while x < pixels.len() && pixels[x] == 0 { x += 1; } assert_eq!(x, pixels.len()); Some(stripe_width) }