First off, thank you for considering contributing to Fontspector! We're excited you're here. Every contribution, from a small typo fix to a new feature, is valuable.
This document provides guidelines to help you through the contribution process.
This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior.
Fontspector is a Rust workspace containing multiple crates. To get started:
- Fork the repository on GitHub.
- Clone your fork locally:
git clone https://github.com/YOUR-USERNAME/fontspector.git cd fontspector - Build the project to ensure everything is set up correctly:
cargo build --all
Fontspector is a font testing framework written in Rust. It has a number of core concepts:
- A check is a single test that can be run on a font. It is implemented as a
Checkstructure, which brings together the check implementation (a function) and metadata about the check. The#[check]attribute macro is used to define checks. We use the term "check" rather than "test" to avoid confusion with the unit tests; we check a font, and we test the checks. TheCheckstructure is defined infontspector-checkapi/src/check.rs. - Each check has a check ID. The mapping between check IDs and their implementation files can be found in
checks.txt. - A profile is a collection of checks that can be run together. Individual font manufacturers
define which checks they would like to run on their fonts by creating a profile.
Profiles are implemented as Rust crates that depend on the
fontspector-checkapicrate. The check implementations have a "home" within a specific profile, but can be reused in other profiles. TheProfilestructure is defined infontspector-checkapi/src/profile.rs. - A check returns a
CheckResult. Fontspector will run multiple checks on multiple fonts, and
each check may discover one or more problems with the font.CheckResultis a structure which wraps up everything that you need to report the result of a check: which file it was run on, which check was run, metadata to be displayed about the check (such as the reasoning behind the check and a user-friendly title), together with all of the results of the check (which may be zero or more problems found). TheCheckResultstructure is defined infontspector-checkapi/src/checkresult.rs. - A
Statusis a single reported problem found by a check. A check may return zero or moreStatusitems, each of which has a severity (error, warning, info, or pass), optionally a message (to be reported to the user) and a code (a short string that identifies the specific problem found, for example for unit tests or for advanced users who know what to expect). - The severity mentioned above is represented by the
StatusCodeenum. We should probably have called itSeverity, and maybe one day we will.StatusandStatusCodeare defined infontspector-checkapi/src/status.rs. - We don't simply check fonts. Fontspector can check HTML files, metadata files, and so on. But obviously only some checks apply to certain file types. Checks declare which
FileTypethey apply to. - Files are wrapped up in
Testablestructs, which include their contents and file name. To determine if the file can be converted into aFileTypeto be run by a particular check, we call.from_testableon theFileTypeenum. If it returnsSome, we can run the check; if it returnsNone, we skip the check for that file. TheFileTypeConverttrait also tells us what kind of representation the file can be converted into. For example,Testables which are TTF files can be converted intoTestFonts. TestFontinfontspector-checkapi/src/font.rscontains a number of helper methods which tests can use to manipulate the font and extract data from it.- Each check runs in a
Context. (fontspector-checkapi/src/context.rs) The context contains a general-purpose cache that checks can use to avoid recomputing things, user-defined per-check configuration, some free-form metadata, andOverrides which change the return values of a check.
We export the Fontspector check runner to a Python module, and then use
pytest to run (a modified version of) the fontbakery test suite. To
do this:
pip3 install -U maturin
cd fontspector-py
python3 -m venv venv ; . venv/bin/activate
pip install maturin
maturin develop
pytest
We welcome many types of contributions, including:
- New checks and features
- Bug fixes
- Documentation improvements
- Performance enhancements
-
Create a branch for your changes:
git checkout -b feat/my-awesome-feature
-
Make your changes.
-
Ensure Code Quality: Before committing, please run the standard Rust formatting and linting tools across the entire workspace.
# Format your code cargo fmt --all # Run clippy to catch common mistakes and style issues cargo clippy --all -- -D warnings
-
Run Tests: Make sure all existing tests pass and, if you're adding a new feature, please add tests for it.
cargo test --all -
Commit Your Changes with Conventional Commits: We use Conventional Commits to automate changelogs and versioning. Your commit messages MUST follow this specification.
The commit message should be structured as follows:
<type>(<scope>): <short description> <BLANK LINE> <optional body> <BLANK LINE> <optional footer>Common types:
feat: A new feature.fix: A bug fix.docs: Documentation only changes.style: Changes that do not affect the meaning of the code (white-space, formatting, etc).refactor: A code change that neither fixes a bug nor adds a feature.perf: A code change that improves performance.test: Adding missing tests or correcting existing tests.chore: Changes to the build process or auxiliary tools.
Example:
feat(check-api): Add support for variable font axis checksUsing
cogfor commits: To simplify creating conventional commits, we strongly encourage usingcog(Cocogitto), which is configured for this project incog.toml. Instead ofgit commit, you can simply run:cog commit
This will guide you through creating a compliant commit message.
-
Push to your fork and open a Pull Request against the
mainbranch. -
PR Title: Because we squash and merge pull requests, the title of your PR must also be a valid Conventional Commit message. The title will become the commit message in the
mainbranch.
Please do not bump version numbers in Cargo.toml files or manually edit CHANGELOG.md files. This is handled automatically.
Our release process is automated using cargo-smart-release in a GitHub Action. When a release is triggered, the tool analyzes all Conventional Commit messages since the last tag. It then determines the correct semantic version bump (patch, minor, or major) for each affected crate and generates the corresponding changelog entries.
This is why your commit messages and PR titles are so important—they directly control the release process!
Thank you for contributing!