How to contribute code, documentation, and improvements to Typf.
Be respectful. Be constructive.
- Rust 1.70+ (install via rustup)
uvfor Python development (install:curl -LsSf https://astral.sh/uv/install.sh | sh)- HarfBuzz support:
- macOS:
brew install harfbuzz - Linux:
sudo apt-get install libharfbuzz-dev - Windows: Included via
harfbuzz-sys
- macOS:
git clone https://github.com/fontlaborg/typf.git
cd typf
cargo build --workspace
cargo test --workspacecargo run --example basic
cargo run --example formats
cargo run --example harfbuzz --features shaping-hbREADME.md- What Typf does and quick startARCHITECTURE.md- Pipeline design and backend detailsTASKS.md- Roadmap and current prioritiesTODO.md- Active task list
Browse existing issues, comment on what you want to work on.
git checkout -b feature/your-feature-name
# or
git checkout -b fix/bug-descriptionFollow the style guidelines. Write tests for your code.
cargo test --workspace
cargo test --package typf-core test_name
cargo test --workspace --all-features
cargo fmt --all -- --check
cargo clippy --workspace --all-features -- -D warningscp .github/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commitAutomatically checks formatting, warnings, tests, and debug statements.
To bypass (not recommended):
git commit --no-verifyFollow the commit format below. Push your branch. Open a pull request.
-
Unit Tests: Individual functions/modules
cargo test --package typf-core -
Integration Tests: Complete workflows
cargo test --test integration_test -
Example Tests: Verify examples compile/run
cargo test --examples
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_feature_name() {
let input = create_test_data();
let result = function_to_test(input);
assert_eq!(result, expected_value);
}
}- ✅ Test edge cases (empty, None, negative values)
- ✅ Name tests clearly:
test_when_condition_then_outcome - ✅ Keep each test focused on one thing
- ✅ Use fixtures for complex setup
- ❌ Don't test how code works internally
- ❌ Don't write the same test twice
cargo fmt --allcargo clippy --workspace --all-features -- -D warnings- Crates:
typf-feature-name(kebab-case) - Modules:
module_name(snake_case) - Types:
TypeName(PascalCase) - Functions:
function_name(snake_case) - Constants:
CONSTANT_NAME(SCREAMING_SNAKE_CASE)
Add rustdoc comments to public items. Include examples. Document panics and errors.
/// Renders text using the specified font.
///
/// # Arguments
/// * `text` - The text to render
/// * `font` - Font reference
/// * `params` - Rendering parameters
///
/// # Returns
/// Returns a `RenderOutput` containing the rendered bitmap.
///
/// # Errors
/// Returns `RenderError::InvalidDimensions` if the output size is invalid.
///
/// # Examples
/// ```
/// use typf_core::*;
/// let rendered = render_text("Hello", font, ¶ms)?;
/// ```
pub fn render_text(text: &str, font: Arc<dyn FontRef>, params: &RenderParams) -> Result<RenderOutput> {
// Implementation
}- Functions: <20 lines
- Files: <200 lines
- Indentation: max 3 levels
- Extract complex logic into helpers
use thiserror::Error;
#[derive(Debug, Error)]
pub enum RenderError {
#[error("Invalid dimensions: {width}x{height}")]
InvalidDimensions { width: u32, height: u32 },
#[error("Rendering failed: {0}")]
RenderFailed(String),
}- Profile first, then optimize
- Use benchmarks to prove your improvements
- Mark performance-critical code clearly
- Don't optimize code that isn't slow
type(scope): what you changed
Why you changed it.
- Specific change 1
- Specific change 2
- feat: New feature
- fix: Bug fix
- docs: Documentation changes
- style: Code style changes
- refactor: Code restructuring
- perf: Performance improvements
- test: Adding/updating tests
- chore: Maintenance tasks
feat(export): add PNG export support
Added PNG export using the image crate. Supports RGBA, RGB, Gray8, and Gray1.
- Created PngExporter struct
- Added color space conversion
- Wrote 4 tests
fix(shaping): fix glyph ID mapping for TTC fonts
Glyph IDs were wrong for TrueType Collection fonts.
Fixes #123
- ✅ All tests pass
- ✅ Code formatted (
cargo fmt) - ✅ No clippy warnings
- ✅ Documentation updated
- ✅ CHANGELOG.md updated (if users will notice the change)
## Description
Brief description of changes.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
How changes were tested.
## Checklist
- [ ] Tests pass
- [ ] Code formatted
- [ ] Clippy clean
- [ ] Documentation updated
- [ ] CHANGELOG.md updated (if user-facing)- CI checks must pass
- A maintainer must approve
- Fix the feedback you get
- Squash commits if asked
- Maintainer merges
typf/
├── crates/
│ ├── typf/ # Main library
│ ├── typf-core/ # Core traits and types
│ ├── typf-input/ # Input parsing
│ ├── typf-unicode/ # Unicode processing
│ ├── typf-fontdb/ # Font management
│ ├── typf-export/ # Export formats
│ └── typf-cli/ # CLI application
├── backends/
│ ├── typf-shape-none/ # Minimal shaper
│ ├── typf-shape-hb/ # HarfBuzz shaper
│ └── typf-render-opixa/ # Opixa renderer
├── bindings/
│ └── python/ # Python bindings (PyO3)
├── examples/ # Usage examples
├── PLAN/ # Architecture documentation
└── docs/ # Additional documentation
- Create crate in
crates/orbackends/ - Add to workspace in root
Cargo.toml - Add feature flag if optional
- Update
ARCHITECTURE.md - Add tests and documentation
- Create crate in
backends/typf-{stage}-{name}/ - Implement appropriate trait (
Shaper,Renderer, etc.) - Add feature flag:
{stage}-{name} - Update backend registry
- Add comprehensive tests
- Document in
ARCHITECTURE.md
Typf uses caching at multiple levels. When adding or modifying backends:
Shapers can use the optional ShapingCache from typf-core:
use typf_core::shaping_cache::ShapingCache;
// Create shared cache (Arc-wrapped for thread-safety)
let cache = ShapingCache::new(1000); // capacity
// Check cache before shaping
let key = cache.key(text, font_id, ¶ms);
if let Some(result) = cache.get(&key) {
return Ok(result);
}
// Shape and cache result
let result = expensive_shaping_operation(text, font, ¶ms)?;
cache.insert(key, result.clone());Cache keys include: text, font ID, size, features, variations, language, script.
- ✅ Shaped glyph results (text → positioned glyphs)
- ✅ Font file parsing (expensive, often repeated)
- ❌ Rendered bitmaps (too large, rarely reused)
- ❌ Exported files (final output, not reused)
Shapers should expose cache stats for debugging:
pub fn cache_stats(&self) -> Option<CacheStats> {
self.cache.as_ref().map(|c| c.stats())
}The CLI's --shaping-cache-stats flag displays hit/miss ratios.
- Platform Backends: CoreText (macOS), DirectWrite (Windows)
- Opixa Renderer: Add anti-aliasing
- Documentation: API docs, user guides
- Testing: More coverage, property-based tests
- Skia Integration: Alternative rendering backend
- Variable Font Support: Font variations
- Color Font Support: COLR/CPAL tables
- Performance: SIMD optimizations for ARM
- Additional Exporters: PDF, WebP
- CLI Enhancements: REPL mode, rich output
- Benchmarks: Comprehensive benchmark suite
See RELEASING.md for the complete release process.
# 1. Set version
./scripts/set-version.sh 2.4.0
# 2. Verify
./test.sh
# 3. Commit, tag, push
git add -A && git commit -m "v2.4.0"
git tag v2.4.0
git push origin main --tagsGitHub Actions will build 8 Rust binaries + 40+ Python wheels and publish to crates.io and PyPI.
| Script | Purpose |
|---|---|
./test.sh |
Canonical verification entrypoint |
./scripts/build.sh |
Build workspace + wheel |
./scripts/test.sh |
Run fmt, clippy, tests |
./scripts/publish.sh --dry-run |
Test publishing |
- Questions: GitHub Discussions
- Bugs: Open an issue
- Chat: Discord (link in README)
Your contributions are licensed under MIT and Apache 2.0.