diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dc4fdff --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable + with: + components: rustfmt, clippy + - name: fmt + run: cargo fmt --all --check + - name: clippy + run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings + - name: test + run: cargo test --workspace --locked + - name: test (embed) + run: cargo test -p craft-fonts --features embed --locked diff --git a/.gitignore b/.gitignore index b25c15b..873d37a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *~ +/target diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md new file mode 100644 index 0000000..754f8d7 --- /dev/null +++ b/ATTRIBUTION.md @@ -0,0 +1,16 @@ +# Font attribution + +Every font in `fonts/`, with its source and licence. The files are byte-identical to the pinned +upstream sources (checked by `craft-fonts-tests`, which verifies each SHA-256 in +`fonts/manifest.txt`). + +| File | Family / style | Author | Source (pinned) | Licence | +|---|---|---|---|---| +| `fonts/shippori-mincho/ShipporiMincho-Regular.ttf` | Shippori Mincho Regular | The Shippori Mincho Project Authors (FONTDASU) | [google/fonts@9710da1](https://github.com/google/fonts/tree/9710da1eacb3be272583c3224dcb70f9da6eadbb/ofl/shipporimincho) | OFL-1.1 (`fonts/shippori-mincho/OFL.txt`) | +| `fonts/biz-ud-pgothic/BIZUDPGothic-Regular.ttf` | BIZ UDPGothic Regular | The BIZ UDGothic Project Authors (Morisawa Inc.) | [googlefonts/morisawa-biz-ud-gothic@18934af](https://github.com/googlefonts/morisawa-biz-ud-gothic/tree/18934af56b9c003ca58c54bffbf226848cb11032) | OFL-1.1 (`fonts/biz-ud-pgothic/OFL.txt`) | +| `fonts/biz-ud-pgothic/BIZUDPGothic-Bold.ttf` | BIZ UDPGothic Bold | The BIZ UDGothic Project Authors (Morisawa Inc.) | [googlefonts/morisawa-biz-ud-gothic@18934af](https://github.com/googlefonts/morisawa-biz-ud-gothic/tree/18934af56b9c003ca58c54bffbf226848cb11032) | OFL-1.1 (`fonts/biz-ud-pgothic/OFL.txt`) | +| `fonts/biz-ud-mincho/BIZUDMincho-Regular.ttf` | BIZ UDMincho Regular | The BIZ UDMincho Project Authors (Morisawa Inc., design TypeBank Co., Ltd.) | [google/fonts@63833b7](https://github.com/google/fonts/tree/63833b7d10bb1f86a8f0b91cba2d3ae1f68d1aa3/ofl/bizudmincho) | OFL-1.1 (`fonts/biz-ud-mincho/OFL.txt`) | + +The OFL allows these fonts to be bundled, embedded and redistributed with software, provided the +licence text travels with them and the fonts are not sold on their own. "Shippori Mincho" and +"BIZ UD" are the authors' names for their fonts; we use the files unmodified. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5238c96 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,251 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "craft-fonts" +version = "0.1.0" + +[[package]] +name = "craft-fonts-tests" +version = "0.1.0" +dependencies = [ + "craft-fonts", + "sha2", + "skrifa", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "font-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "read-fonts" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5b7bed3d16ab428d8fa533640ab805ff50e7ad8a4d00649de1259e70023282" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", + "yoke", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "skrifa" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5213b4825bac3587046330052d61bfac7bd3bd0d203c758d7c2ef726fcea034" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..de3b7a8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +resolver = "3" +members = ["crates/*"] + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT OR Apache-2.0" +repository = "https://github.com/storytold/craft-fonts" +rust-version = "1.90" + +[workspace.lints.rust] +unsafe_code = "forbid" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,177 @@ + + 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 diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..ac36eb7 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ArtCraft Team and the craft-fonts contributors + +Permission 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. diff --git a/README.md b/README.md index 16da069..dd2c0c7 100644 --- a/README.md +++ b/README.md @@ -14,5 +14,41 @@ This library is meant to have the following: more "biology-inspired" approach of copying innovations between repos as a form of lateral code/information exchange. +## What's here +| Path | What | +|---|---| +| `fonts/` | The font files, one directory per family, each with its licence. | +| `fonts/manifest.txt` | Machine-readable list of every font: family, style, file, scripts, licence, SHA-256, pinned source. | +| `ATTRIBUTION.md` | Authors, pinned sources and licences. | +| `crates/craft-fonts` | Rust: the manifest as a static table, finding a checkout (`CRAFT_FONTS_DIR`), loading it, and an `embed` feature that compiles the fonts in. | +| `crates/craft-fonts-tests` | Tests asserted on these fonts: checksums, licences, family names, Japanese coverage and outlines, vertical-text (`vert`/`vrt2`) support. | +| `docs/integration.md` | How an app uses craft-fonts as a fully optional build input. | +### Fonts + +| Family | Styles | For | Licence | +|---|---|---|---| +| BIZ UDPGothic | Regular, Bold | Japanese UI text (sans) | OFL-1.1 | +| Shippori Mincho | Regular | Japanese document text (serif) | OFL-1.1 | +| BIZ UDMincho | Regular | Japanese document text (serif) | OFL-1.1 | + +## How the apps use it + +**Fully optional, never required.** The apps don't list craft-fonts in `Cargo.toml` (Cargo would +fetch it for every build). Building with `CRAFT_FONTS_DIR=` makes an app +embed the fonts in `fonts/manifest.txt`; without it, the app builds and runs as before. Release +builds check out a pinned commit of this repo and set the option. See +[`docs/integration.md`](docs/integration.md). + +**Font assets for the Crafting Apps belong here**, not in the app repos: add the file, its licence, +a manifest line and an attribution row, and the tests check it. + +## Develop + +```sh +cargo test --workspace # all font assertions +cargo test -p craft-fonts --features embed # the embedded bytes match the files +``` + +Code is MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`); each font keeps its own licence. diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..4b6c79f --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true +allow-panic-in-tests = true +allow-indexing-slicing-in-tests = true diff --git a/crates/craft-fonts-tests/Cargo.toml b/crates/craft-fonts-tests/Cargo.toml new file mode 100644 index 0000000..a27ff7d --- /dev/null +++ b/crates/craft-fonts-tests/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "craft-fonts-tests" +description = "Assertions on the fonts themselves: checksums, licences, names, script coverage and vertical-text support." +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +publish = false + +[dependencies] +craft-fonts = { path = "../craft-fonts" } + +[dev-dependencies] +skrifa = "0.48" +sha2 = "0.11" + +[lints] +workspace = true diff --git a/crates/craft-fonts-tests/src/lib.rs b/crates/craft-fonts-tests/src/lib.rs new file mode 100644 index 0000000..747356f --- /dev/null +++ b/crates/craft-fonts-tests/src/lib.rs @@ -0,0 +1,16 @@ +//! Test-only crate: see `tests/`. It asserts things about the font files in `fonts/`, so a font +//! that is replaced, truncated or swapped for one without Japanese coverage fails CI here, not in +//! an app. +#![deny( + clippy::unwrap_used, + clippy::expect_used, + clippy::panic, + clippy::unimplemented, + clippy::todo, + clippy::unreachable +)] + +/// The repository root (where `fonts/` lives). +pub fn repo_root() -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} diff --git a/crates/craft-fonts-tests/tests/fonts.rs b/crates/craft-fonts-tests/tests/fonts.rs new file mode 100644 index 0000000..708fe9a --- /dev/null +++ b/crates/craft-fonts-tests/tests/fonts.rs @@ -0,0 +1,161 @@ +//! Assertions on the font files themselves. + +use craft_fonts::{FONTS, Font, for_script}; +use sha2::{Digest, Sha256}; +use skrifa::raw::TableProvider; +use skrifa::{FontRef, MetadataProvider, string::StringId}; + +fn bytes(f: &Font) -> Vec { + std::fs::read(craft_fonts_tests::repo_root().join(f.file)) + .unwrap_or_else(|e| panic!("{}: {e}", f.file)) +} + +#[test] +fn every_file_matches_its_manifest_checksum() { + for f in FONTS { + let digest = Sha256::digest(bytes(f)); + let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect(); + assert_eq!( + hex, f.sha256, + "{}: the file changed; update the manifest only after checking the new upstream source", + f.file + ); + } +} + +#[test] +fn every_font_ships_its_licence() { + for f in FONTS { + let text = std::fs::read_to_string(craft_fonts_tests::repo_root().join(f.licence_file)) + .unwrap_or_else(|e| panic!("{}: {e}", f.licence_file)); + if f.licence == "OFL-1.1" { + assert!( + text.contains("SIL OPEN FONT LICENSE Version 1.1"), + "{} is not the OFL 1.1 text", + f.licence_file + ); + } + } +} + +#[test] +fn every_font_is_the_family_the_manifest_says() { + for f in FONTS { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + let names: Vec = [StringId::TYPOGRAPHIC_FAMILY_NAME, StringId::FAMILY_NAME] + .into_iter() + .flat_map(|id| { + font.localized_strings(id) + .map(|s| s.to_string()) + .collect::>() + }) + .collect(); + assert!( + names.iter().any(|n| n == f.family), + "{}: name table has {names:?}, manifest says {:?}", + f.file, + f.family + ); + } +} + +/// Text every Japanese font must cover: kana, common kanji, Japanese punctuation, full-width +/// forms and basic Latin (UI strings mix them). +const JAPANESE: &str = concat!( + "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽぁぃぅぇぉっゃゅょ", + "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンガギグゲゴヴァィゥェォッャュョー", + "日本語漢字東京都新見書読学校会社電話開閉保存編集表示画像文字色線形層効果時間写真映画印刷設定終了取消削除選択複製", + "、。「」『』・…!?():;〜", + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", +); + +#[test] +fn japanese_fonts_cover_japanese_text() { + let jpan: Vec<&Font> = for_script("Jpan").collect(); + assert!(!jpan.is_empty(), "the manifest lists no Japanese font"); + for f in jpan { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + let charmap = font.charmap(); + let missing: String = JAPANESE + .chars() + .filter(|c| charmap.map(*c).is_none()) + .collect(); + assert!( + missing.is_empty(), + "{} {} has no glyph for {missing:?}", + f.family, + f.style + ); + } +} + +#[test] +fn japanese_glyphs_have_outlines() { + use skrifa::instance::{LocationRef, Size}; + use skrifa::outline::{DrawSettings, OutlinePen}; + struct Count(usize); + impl OutlinePen for Count { + fn move_to(&mut self, _: f32, _: f32) {} + fn line_to(&mut self, _: f32, _: f32) { + self.0 += 1; + } + fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { + self.0 += 1; + } + fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { + self.0 += 1; + } + fn close(&mut self) {} + } + for f in for_script("Jpan") { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + let outlines = font.outline_glyphs(); + for c in ['漢', 'あ', 'ア', '。'] { + let gid = font + .charmap() + .map(c) + .unwrap_or_else(|| panic!("{}: no glyph for {c}", f.file)); + let glyph = outlines + .get(gid) + .unwrap_or_else(|| panic!("{}: no outline for {c}", f.file)); + let mut pen = Count(0); + glyph + .draw( + DrawSettings::unhinted(Size::new(64.0), LocationRef::default()), + &mut pen, + ) + .unwrap_or_else(|e| panic!("{}: drawing {c}: {e}", f.file)); + assert!(pen.0 > 0, "{} {}: {c} draws nothing", f.family, f.style); + } + } +} + +#[test] +fn japanese_fonts_support_vertical_text() { + // Vertical type (tategaki) needs the vertical alternates for punctuation and long vowels. + for f in for_script("Jpan") { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + let gsub = font + .gsub() + .unwrap_or_else(|e| panic!("{}: no GSUB: {e}", f.file)); + let features = gsub + .feature_list() + .unwrap_or_else(|e| panic!("{}: GSUB features: {e}", f.file)); + let tags: Vec = features + .feature_records() + .iter() + .map(|r| r.feature_tag().to_string()) + .collect(); + assert!( + tags.iter().any(|t| t == "vert" || t == "vrt2"), + "{} {}: no vert/vrt2 GSUB feature (has {tags:?})", + f.family, + f.style + ); + } +} diff --git a/crates/craft-fonts/Cargo.toml b/crates/craft-fonts/Cargo.toml new file mode 100644 index 0000000..ac41111 --- /dev/null +++ b/crates/craft-fonts/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "craft-fonts" +description = "The Crafting Apps' shared font assets: the manifest, finding a craft-fonts checkout, and loading or embedding the fonts." +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +publish = false + +[features] +# Compile every font in the manifest into the binary (about 24 MB). +embed = [] + +[lints] +workspace = true diff --git a/crates/craft-fonts/build.rs b/crates/craft-fonts/build.rs new file mode 100644 index 0000000..0fa005d --- /dev/null +++ b/crates/craft-fonts/build.rs @@ -0,0 +1,64 @@ +//! Turns `fonts/manifest.txt` into a static table (and, with the `embed` feature, the font bytes). + +#[path = "src/parse.rs"] +#[allow(dead_code)] +mod parse; + +use std::fmt::Write as _; +use std::path::PathBuf; + +fn main() { + let manifest_dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap_or_default()); + let root = manifest_dir.join("../.."); + let manifest = root.join("fonts/manifest.txt"); + println!("cargo::rerun-if-changed={}", manifest.display()); + let text = std::fs::read_to_string(&manifest) + .unwrap_or_else(|e| fail(&format!("reading {}: {e}", manifest.display()))); + let entries = parse::parse_manifest(&text).unwrap_or_else(|e| fail(&e.to_string())); + let embed = std::env::var_os("CARGO_FEATURE_EMBED").is_some(); + + let mut src = String::from( + "/// Every font in `fonts/manifest.txt`, in manifest order.\npub static FONTS: &[Font] = &[\n", + ); + for e in &entries { + let scripts: Vec = e.scripts.iter().map(|s| format!("{s:?}")).collect(); + let _ = writeln!( + src, + " Font {{ family: {:?}, style: {:?}, file: {:?}, scripts: &[{}], licence: {:?}, licence_file: {:?}, sha256: {:?}, source: {:?} }},", + e.family, + e.style, + e.file, + scripts.join(", "), + e.licence, + e.licence_file, + e.sha256, + e.source + ); + } + src.push_str("];\n"); + if embed { + src.push_str("/// The bytes of each font in [`FONTS`], same order.\nstatic EMBEDDED: &[&[u8]] = &[\n"); + for e in &entries { + let path = root.join(&e.file); + println!("cargo::rerun-if-changed={}", path.display()); + if !path.is_file() { + fail(&format!( + "{} is listed in the manifest but missing", + path.display() + )); + } + let _ = writeln!(src, " include_bytes!({:?}),", path.display().to_string()); + } + src.push_str("];\n"); + } + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("manifest.rs"); + if let Err(e) = std::fs::write(&out, src) { + fail(&format!("writing {}: {e}", out.display())); + } +} + +/// Build scripts report errors by failing; this keeps the message readable. +#[allow(clippy::panic)] +fn fail(msg: &str) -> ! { + panic!("craft-fonts: {msg}") +} diff --git a/crates/craft-fonts/src/lib.rs b/crates/craft-fonts/src/lib.rs new file mode 100644 index 0000000..2121a5b --- /dev/null +++ b/crates/craft-fonts/src/lib.rs @@ -0,0 +1,177 @@ +//! The Crafting Apps' shared font assets. +//! +//! The fonts live in this repository's `fonts/` directory and are listed in `fonts/manifest.txt`. +//! This crate exposes that manifest ([`FONTS`]), finds a craft-fonts checkout at run time +//! ([`dir_from_env`]), loads fonts from one ([`load`]), and with the `embed` feature compiles them +//! into the binary ([`embedded`]). +//! +//! The apps do **not** depend on this crate: Cargo resolves even optional git dependencies, so a +//! dependency would make craft-fonts required for every build. Apps read `fonts/manifest.txt` +//! from their own `build.rs` when the `CRAFT_FONTS_DIR` build option is set; see +//! `docs/integration.md`. This crate is for tools and tests that can afford the dependency, and it +//! keeps the manifest format ([`parse_manifest`]) defined in one place. +#![deny( + clippy::unwrap_used, + clippy::expect_used, + clippy::panic, + clippy::unimplemented, + clippy::todo, + clippy::unreachable +)] + +mod parse; + +use std::path::{Path, PathBuf}; + +pub use parse::{Entry, ManifestError, parse_manifest}; + +/// One font file from `fonts/manifest.txt`, known at compile time. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Font { + /// Family name, as the font's own `name` table spells it. + pub family: &'static str, + /// Style within the family (`Regular`, `Bold`, …). + pub style: &'static str, + /// Path of the font file, relative to the repository root. + pub file: &'static str, + /// ISO 15924 script codes the font is meant for. + pub scripts: &'static [&'static str], + /// SPDX licence identifier. + pub licence: &'static str, + /// Path of the licence text, relative to the repository root. + pub licence_file: &'static str, + /// Lower-case hex SHA-256 of the file. + pub sha256: &'static str, + /// Pinned upstream source. + pub source: &'static str, +} + +include!(concat!(env!("OUT_DIR"), "/manifest.rs")); + +/// The fonts meant for an ISO 15924 script (e.g. `"Jpan"`), in manifest order. +pub fn for_script(script: &str) -> impl Iterator + '_ { + FONTS.iter().filter(move |f| f.scripts.contains(&script)) +} + +/// The compiled-in bytes of a font from [`FONTS`] (`embed` feature). `None` for any other value. +#[cfg(feature = "embed")] +pub fn embedded(font: &Font) -> Option<&'static [u8]> { + FONTS + .iter() + .position(|f| f == font) + .and_then(|i| EMBEDDED.get(i).copied()) +} + +/// The environment variable (and build option) naming a craft-fonts checkout. +pub const DIR_ENV: &str = "CRAFT_FONTS_DIR"; + +/// The craft-fonts checkout named by `CRAFT_FONTS_DIR`, if it is set and holds a manifest. +pub fn dir_from_env() -> Option { + let dir = PathBuf::from(std::env::var_os(DIR_ENV)?); + dir.join("fonts/manifest.txt").is_file().then_some(dir) +} + +/// Why fonts could not be loaded from a checkout. +#[derive(Debug)] +pub enum LoadError { + /// A file could not be read. + Io { + path: PathBuf, + error: std::io::Error, + }, + /// The manifest is malformed. + Manifest(ManifestError), +} + +impl std::fmt::Display for LoadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Io { path, error } => write!(f, "reading {}: {error}", path.display()), + Self::Manifest(e) => e.fmt(f), + } + } +} + +impl std::error::Error for LoadError {} + +/// Read the manifest of the checkout at `dir` and every font it lists, in manifest order. +pub fn load(dir: &Path) -> Result)>, LoadError> { + let manifest = dir.join("fonts/manifest.txt"); + let text = std::fs::read_to_string(&manifest).map_err(|error| LoadError::Io { + path: manifest, + error, + })?; + let entries = parse_manifest(&text).map_err(LoadError::Manifest)?; + entries + .into_iter() + .map(|e| { + let path = dir.join(&e.file); + std::fs::read(&path) + .map(|bytes| (e, bytes)) + .map_err(|error| LoadError::Io { path, error }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_manifest_parses_into_the_static_table() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let text = std::fs::read_to_string(root.join("fonts/manifest.txt")).unwrap(); + let entries = parse_manifest(&text).unwrap(); + assert_eq!(entries.len(), FONTS.len()); + for (e, f) in entries.iter().zip(FONTS) { + assert_eq!( + (e.family.as_str(), e.style.as_str(), e.file.as_str()), + (f.family, f.style, f.file) + ); + } + assert!(for_script("Jpan").count() >= 1); + } + + #[test] + fn malformed_lines_are_errors_not_panics() { + assert_eq!( + parse_manifest("a | b"), + Err(ManifestError::FieldCount { line: 1, found: 2 }) + ); + let blank_style = format!("A | | f | Jpan | OFL-1.1 | l | {} | s", "0".repeat(64)); + assert_eq!( + parse_manifest(&blank_style), + Err(ManifestError::EmptyField { + line: 1, + field: "style" + }) + ); + assert_eq!( + parse_manifest("A | B | f | Jpan | OFL-1.1 | l | XYZ | s"), + Err(ManifestError::BadSha256 { line: 1 }) + ); + assert_eq!(parse_manifest("# comment\n\n"), Ok(Vec::new())); + } + + #[test] + fn a_missing_checkout_is_an_error() { + assert!(matches!( + load(Path::new("/nonexistent/craft-fonts")), + Err(LoadError::Io { .. }) + )); + } + + #[cfg(feature = "embed")] + #[test] + fn embedded_bytes_match_the_files() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + for f in FONTS { + assert_eq!( + embedded(f).unwrap(), + std::fs::read(root.join(f.file)).unwrap().as_slice(), + "{}", + f.file + ); + } + } +} diff --git a/crates/craft-fonts/src/parse.rs b/crates/craft-fonts/src/parse.rs new file mode 100644 index 0000000..bcb417d --- /dev/null +++ b/crates/craft-fonts/src/parse.rs @@ -0,0 +1,131 @@ +//! The `fonts/manifest.txt` format, shared by `build.rs` and the library (`#[path]`-included by +//! both, so it has no dependencies). Apps that read the manifest from their own `build.rs` can +//! copy this file. + +/// One line of the manifest. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Entry { + /// Family name, as the font's own `name` table spells it (e.g. `Shippori Mincho`). + pub family: String, + /// Style within the family (e.g. `Regular`, `Bold`). + pub style: String, + /// Path of the font file, relative to the repository root. + pub file: String, + /// ISO 15924 script codes the font is meant for (e.g. `Jpan`, `Latn`). + pub scripts: Vec, + /// SPDX licence identifier. + pub licence: String, + /// Path of the licence text, relative to the repository root. + pub licence_file: String, + /// Lower-case hex SHA-256 of the font file. + pub sha256: String, + /// Where the file came from (a pinned upstream URL). + pub source: String, +} + +/// Why a manifest line could not be read. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ManifestError { + /// The line does not have the 8 `" | "`-separated fields. + FieldCount { line: usize, found: usize }, + /// A field is empty. + EmptyField { line: usize, field: &'static str }, + /// The SHA-256 is not 64 lower-case hex digits. + BadSha256 { line: usize }, +} + +impl std::fmt::Display for ManifestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::FieldCount { line, found } => write!( + f, + "manifest line {line}: expected 8 fields separated by \" | \", found {found}" + ), + Self::EmptyField { line, field } => { + write!(f, "manifest line {line}: the {field} field is empty") + } + Self::BadSha256 { line } => write!( + f, + "manifest line {line}: sha256 must be 64 lower-case hex digits" + ), + } + } +} + +impl std::error::Error for ManifestError {} + +const FIELDS: [&str; 8] = [ + "family", + "style", + "file", + "scripts", + "licence", + "licence file", + "sha256", + "source", +]; + +/// Parse the whole manifest. Blank lines and `#` comments are skipped; line numbers are 1-based. +pub fn parse_manifest(text: &str) -> Result, ManifestError> { + let mut out = Vec::new(); + for (i, raw) in text.lines().enumerate() { + let line = i + 1; + let trimmed = raw.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + let fields: Vec<&str> = trimmed.split(" | ").map(str::trim).collect(); + let [ + family, + style, + file, + scripts, + licence, + licence_file, + sha256, + source, + ] = fields.as_slice() + else { + return Err(ManifestError::FieldCount { + line, + found: fields.len(), + }); + }; + for (name, value) in FIELDS.iter().zip([ + family, + style, + file, + scripts, + licence, + licence_file, + sha256, + source, + ]) { + if value.is_empty() { + return Err(ManifestError::EmptyField { line, field: name }); + } + } + if sha256.len() != 64 + || !sha256 + .bytes() + .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) + { + return Err(ManifestError::BadSha256 { line }); + } + out.push(Entry { + family: (*family).to_string(), + style: (*style).to_string(), + file: (*file).to_string(), + scripts: scripts + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(), + licence: (*licence).to_string(), + licence_file: (*licence_file).to_string(), + sha256: (*sha256).to_string(), + source: (*source).to_string(), + }); + } + Ok(out) +} diff --git a/docs/integration.md b/docs/integration.md new file mode 100644 index 0000000..e5ca5cc --- /dev/null +++ b/docs/integration.md @@ -0,0 +1,142 @@ +# Using craft-fonts in a Crafting App + +craft-fonts is a **fully optional** source of fonts. An app must build, test and run without it; +with it, the app embeds the fonts listed in `fonts/manifest.txt` and uses them (today: Japanese +UI and document fallback). + +## Why not a Cargo dependency + +Cargo resolves every dependency in `Cargo.toml`, optional ones included, even when the feature +that enables them is off. A git dependency on craft-fonts would make every build (offline, in a +fork, on a mirror) need this repository. So apps never list craft-fonts in `Cargo.toml`. The build +option is an environment variable instead. + +## The build option: `CRAFT_FONTS_DIR` + +```sh +git clone https://github.com/storytold/craft-fonts ../craft-fonts +CRAFT_FONTS_DIR=../craft-fonts cargo build --release +``` + +- **Unset** (the default): nothing changes. The app uses its own bundled fonts and system fonts. +- **Set to a craft-fonts checkout:** the app's `build.rs` reads `fonts/manifest.txt` and embeds + every font it lists. +- **Set but not a checkout** (no manifest, missing file): the build prints a `cargo::warning` and + carries on without craft-fonts, unless `CRAFT_FONTS_REQUIRED=1` is also set, which turns it + into a build error. Release workflows set both, so a release can't silently ship without them. + +Use a relative or absolute path; `CRAFT_FONTS_DIR` is read by `build.rs`, so changing it rebuilds +only the crate that embeds the fonts. + +## The `build.rs` recipe + +Put this in the build script of the one crate that installs fonts (usually the UI or text crate). +It has no dependencies; the manifest format is defined in `crates/craft-fonts/src/parse.rs`. + +```rust +use std::fmt::Write as _; +use std::path::PathBuf; + +fn main() { + println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR"); + println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED"); + let mut src = String::from("pub static CRAFT_FONTS: &[CraftFont] = &[\n"); + if let Some(dir) = std::env::var_os("CRAFT_FONTS_DIR").map(PathBuf::from) { + match craft_fonts(&dir) { + Ok(entries) => src.push_str(&entries), + Err(e) if std::env::var_os("CRAFT_FONTS_REQUIRED").is_some() => { + println!("cargo::error=CRAFT_FONTS_DIR={}: {e}", dir.display()); + } + Err(e) => println!("cargo::warning=building without craft-fonts: CRAFT_FONTS_DIR={}: {e}", dir.display()), + } + } + src.push_str("];\n"); + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("craft_fonts.rs"); + if let Err(e) = std::fs::write(&out, src) { + println!("cargo::error=writing {}: {e}", out.display()); + } +} + +/// One `CraftFont { .. }` initialiser per manifest line. +fn craft_fonts(dir: &std::path::Path) -> Result { + let manifest = dir.join("fonts/manifest.txt"); + println!("cargo::rerun-if-changed={}", manifest.display()); + let text = std::fs::read_to_string(&manifest).map_err(|e| format!("{}: {e}", manifest.display()))?; + let mut out = String::new(); + for line in text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')) { + let f: Vec<&str> = line.split(" | ").map(str::trim).collect(); + let [family, style, file, scripts, ..] = f.as_slice() else { + return Err(format!("malformed manifest line: {line}")); + }; + let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?; + println!("cargo::rerun-if-changed={}", path.display()); + let scripts: Vec = scripts.split(',').map(|s| format!("{:?}", s.trim())).collect(); + let _ = writeln!( + out, + " CraftFont {{ family: {family:?}, style: {style:?}, scripts: &[{}], bytes: include_bytes!({:?}) }},", + scripts.join(", "), + path.display().to_string(), + ); + } + Ok(out) +} +``` + +And in the crate: + +```rust +/// A font from the optional craft-fonts build input (empty unless built with `CRAFT_FONTS_DIR`). +pub struct CraftFont { + pub family: &'static str, + pub style: &'static str, + /// ISO 15924 scripts the font is for, e.g. `"Jpan"`. + pub scripts: &'static [&'static str], + pub bytes: &'static [u8], +} + +include!(concat!(env!("OUT_DIR"), "/craft_fonts.rs")); +``` + +`CRAFT_FONTS` is always defined, so there is no `cfg` to thread through: an empty slice means the +app was built without craft-fonts. Code that uses it must work when it is empty. + +## Using the fonts + +- **UI (egui):** add each `Jpan` font as a fallback at the end of every font family, after the + app's own fonts and before system-font discovery, so Japanese text renders the same everywhere + the app was built with craft-fonts. +- **Document text engines:** register them as fallback faces for Japanese, after the document's + requested font and the app's bundled fonts. +- Prefer `BIZ UDPGothic` for UI text and `Shippori Mincho` / `BIZ UDMincho` for serif document + text. +- Tests that assert on these fonts' glyphs must skip (not fail) when `CRAFT_FONTS` is empty, and + CI should run them with `CRAFT_FONTS_DIR` set so they are exercised. + +## Releases and licences + +The fonts are OFL-1.1: they can be embedded and redistributed, but the licence text must go with +them. In `release.yml`, check out craft-fonts at a pinned commit and build with it: + +```yaml +- uses: actions/checkout@v4 + with: + repository: storytold/craft-fonts + ref: # bump deliberately + path: craft-fonts +# then, on the build steps: +env: + CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts + CRAFT_FONTS_REQUIRED: "1" +``` + +Packages built with craft-fonts must include each embedded font's licence file (copy +`$CRAFT_FONTS_DIR/fonts/*/OFL.txt` into the package's licences, named after the family) and the +app's `NOTICE` must say that builds made with `CRAFT_FONTS_DIR` embed the fonts listed in +craft-fonts' `ATTRIBUTION.md`. + +## Adding a font + +1. Put the file and its licence under `fonts//`, from a pinned upstream commit. +2. Add a manifest line (with its SHA-256) and an `ATTRIBUTION.md` row. +3. `cargo test --workspace`: `craft-fonts-tests` checks the checksum, licence, family name and, + for Japanese fonts, coverage and vertical-text support. Add assertions for any new script. diff --git a/fonts/biz-ud-mincho/BIZUDMincho-Regular.ttf b/fonts/biz-ud-mincho/BIZUDMincho-Regular.ttf new file mode 100644 index 0000000..b5a3f4c Binary files /dev/null and b/fonts/biz-ud-mincho/BIZUDMincho-Regular.ttf differ diff --git a/fonts/biz-ud-mincho/OFL.txt b/fonts/biz-ud-mincho/OFL.txt new file mode 100644 index 0000000..1664f9f --- /dev/null +++ b/fonts/biz-ud-mincho/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The BIZ UDMincho Project Authors (https://github.com/googlefonts/morisawa-biz-ud-mincho) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/biz-ud-pgothic/BIZUDPGothic-Bold.ttf b/fonts/biz-ud-pgothic/BIZUDPGothic-Bold.ttf new file mode 100644 index 0000000..8cfe345 Binary files /dev/null and b/fonts/biz-ud-pgothic/BIZUDPGothic-Bold.ttf differ diff --git a/fonts/biz-ud-pgothic/BIZUDPGothic-Regular.ttf b/fonts/biz-ud-pgothic/BIZUDPGothic-Regular.ttf new file mode 100644 index 0000000..0753da0 Binary files /dev/null and b/fonts/biz-ud-pgothic/BIZUDPGothic-Regular.ttf differ diff --git a/fonts/biz-ud-pgothic/OFL.txt b/fonts/biz-ud-pgothic/OFL.txt new file mode 100644 index 0000000..4df8604 --- /dev/null +++ b/fonts/biz-ud-pgothic/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The BIZ UDGothic Project Authors (https://github.com/googlefonts/morisawa-biz-ud-mincho) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/manifest.txt b/fonts/manifest.txt new file mode 100644 index 0000000..0113500 --- /dev/null +++ b/fonts/manifest.txt @@ -0,0 +1,8 @@ +# Font manifest for the Crafting Apps. One font file per line, fields separated by " | ": +# family | style | file (relative to the repo root) | scripts (ISO 15924, comma-separated) | licence | licence file | sha256 | source +# Lines starting with '#' and blank lines are ignored. Apps read this file from their build.rs when +# CRAFT_FONTS_DIR is set (see docs/integration.md), so keep the format this simple. +Shippori Mincho | Regular | fonts/shippori-mincho/ShipporiMincho-Regular.ttf | Jpan,Latn | OFL-1.1 | fonts/shippori-mincho/OFL.txt | 769b5269f0f9bc6534b352c0e6bd856a566e03ff788f107191c2d835863570b2 | https://github.com/google/fonts/tree/9710da1eacb3be272583c3224dcb70f9da6eadbb/ofl/shipporimincho +BIZ UDPGothic | Regular | fonts/biz-ud-pgothic/BIZUDPGothic-Regular.ttf | Jpan,Latn | OFL-1.1 | fonts/biz-ud-pgothic/OFL.txt | 258d7156c165f2ff774b6efee637c22c3b950de0d8a10e501137061bc8085d01 | https://github.com/googlefonts/morisawa-biz-ud-gothic/tree/18934af56b9c003ca58c54bffbf226848cb11032 +BIZ UDPGothic | Bold | fonts/biz-ud-pgothic/BIZUDPGothic-Bold.ttf | Jpan,Latn | OFL-1.1 | fonts/biz-ud-pgothic/OFL.txt | 30eba52fc837e8b62c97d4b82e6706583149fb7294e3712dd71a655eaea80a90 | https://github.com/googlefonts/morisawa-biz-ud-gothic/tree/18934af56b9c003ca58c54bffbf226848cb11032 +BIZ UDMincho | Regular | fonts/biz-ud-mincho/BIZUDMincho-Regular.ttf | Jpan,Latn | OFL-1.1 | fonts/biz-ud-mincho/OFL.txt | 468ee6d9b149ca144809e03841bf18740ecf014e055a00da6ecaf1aaf4165af2 | https://github.com/google/fonts/tree/63833b7d10bb1f86a8f0b91cba2d3ae1f68d1aa3/ofl/bizudmincho diff --git a/fonts/shippori-mincho/OFL.txt b/fonts/shippori-mincho/OFL.txt new file mode 100644 index 0000000..a3f24cb --- /dev/null +++ b/fonts/shippori-mincho/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Shippori Mincho Project Authors (https://github.com/fontdasu/ShipporiMincho) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/shippori-mincho/ShipporiMincho-Regular.ttf b/fonts/shippori-mincho/ShipporiMincho-Regular.ttf new file mode 100644 index 0000000..cfc25ce Binary files /dev/null and b/fonts/shippori-mincho/ShipporiMincho-Regular.ttf differ