Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
/.git* export-ignore
/.php-cs-fixer.dist.php export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/tests/ export-ignore
/tests/Fixtures/* -text
* text=auto eol=lf

/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/.php-cs-fixer.dist.php export-ignore
/docs export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore

/docs/** linguist-documentation
/tests/Fixtures/** -linguist-detectable
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/vendor/
/composer.lock
/.phpunit.cache/
/.phpunit.result.cache
/.php-cs-fixer.cache
/var/
/coverage/
/coverage.xml
/clover.xml
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## [Unreleased]

- Add documentation.

## [0.8.0]

- Support transformed WOFF2 `glyf`, `loca`, and `hmtx` tables.
Expand Down
184 changes: 121 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,145 @@
# alto/font
# ALTO Font

Font file reading and metadata for PHP: parse OpenType/TrueType/WOFF/WOFF2,
discover installed fonts, and expose per-glyph facts.
Read OpenType, TrueType, WOFF, and WOFF2 font files from PHP.

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![PHP](https://img.shields.io/badge/php-%3E%3D8.4-777bb4.svg)](composer.json)
  ![PHP Version](https://img.shields.io/badge/PHP-8.4%2B-00B7FF?logoColor=00B7FF&labelColor=050608)
  ![CI](https://img.shields.io/github/actions/workflow/status/altophp/font/CI.yml?branch=main&label=Tests&labelColor=050608&color=00B7FF)
  [![Packagist](https://img.shields.io/packagist/v/alto/font?label=Packagist&labelColor=050608&color=00B7FF)](https://packagist.org/packages/alto/font)
  ![License](https://img.shields.io/github/license/altophp/font?label=License&labelColor=050608&color=00B7FF)
  [![GitHub Sponsors](https://img.shields.io/github/sponsors/smnandre?logo=githubsponsors&logoColor=00B7FF&label=%20Sponsor&labelColor=050608&color=00B7FF)](https://github.com/sponsors/smnandre)

`alto/font` answers "what is this font, and what are the facts about this
glyph" -- metadata, discovery, per-glyph metrics and outlines. It does not
shape text, does not apply kerning, and does not draw anything. Turning a
string of text into positioned, drawn glyphs is a different package's job
(`alto/svg-font` and friends); this one only reads and reports facts.
ALTO Font answers what a font contains: names, descriptors, licensing metadata, face dimensions,
character maps, glyph metrics, outlines, collections, and variable-font axes. It deliberately does
not shape text, apply kerning, or render glyphs.

```php
use Alto\Font\Font;
use Alto\Font\FontFinder;
use Alto\Font\FontQuery;

$font = Font::fromFile(__DIR__.'/fonts/Inter.ttf');

$font->getDescriptor()->family; // 'Inter'
$font->face()->unitsPerEm; // 1000
$font->face()->ascender; // 968

$glyphId = $font->glyphIdForCodepoint(mb_ord('A'));
$font->glyphMetrics($glyphId)->advanceWidth;
$font->glyphOutline($glyphId); // raw Contour/PathCommand geometry

$finder = FontFinder::fromDirectories(__DIR__.'/fonts');
$bold = $finder->get(FontQuery::family('Inter')->weight(700));
echo $font->metadata()->family;
echo $font->face()->unitsPerEm;
echo $font->getMetrics('A')->advanceWidth;
```

## Features

- `Font::fromFile()` -- load an OpenType/TrueType/WOFF/WOFF2 font file.
- `FontFace` -- `unitsPerEm`, `ascender`, `descender`, `glyphCount`, raw table
access.
- `FontDescriptor` -- family, subfamily, full name, PostScript name, weight,
style, stretch, inferred from the `name`/subfamily strings.
- `FontMetadata` -- copyright, manufacturer, designer, version, license
description and URL, read from the `name` table.
- Per-glyph facts: `glyphIdForCodepoint()`, `glyphMetrics()` (advance width,
side bearings), `glyphOutline()` (contours as generic move/line/quad
commands, transformable, not tied to any output format).
- Variable fonts: `withVariations()` resolves `fvar`/`avar`/`gvar`/`HVAR` axes
and instances into concrete per-instance metrics and outlines.
- Discovery: `FontFinder::system()` / `::fromDirectories($dirs)` / `::fromLocator()`,
resolving a `FontQuery` (family/weight/style/stretch) to the best-matching `Font`
via `has()` / `find()` / `get()`. `Locator\FontLocatorInterface` is the one
injectable seam, for tests or custom/non-directory font sources.

## Format support

| Format | Extension(s) | Support |
| --- | --- | --- |
| TrueType (`glyf` outlines) | `.ttf` | Supported, including compound glyphs |
| OpenType with `glyf` outlines | `.otf` | Supported |
| WOFF v1 | `.woff` | Supported (`ext-zlib`, always available) |
| WOFF2 | `.woff2` | Supported, including transformed `glyf`/`loca` and `hmtx` tables. Requires `ext-brotli` or the `brotli` CLI binary on `PATH` |
| CFF/CFF2 outlines (Type 2 charstrings) | `.otf` | Not supported yet -- rejects cleanly |
| TrueType/OpenType collections | `.ttc`, `.otc` | Supported: `Font::fromFile($path, faceIndex: $n)` selects a face; `FontFace::$faceIndex`/`$faceCount` report the file's shape |
| Compressed (WOFF2) font collections | `.woff2` | Not supported yet -- rejects cleanly |
| Color glyph tables (`COLR`/`CPAL`, `sbix`, `SVG `, `CBDT`/`CBLC`) | `.ttf`, `.otf` | Not supported yet |
| Variable fonts (`fvar`/`avar`/`gvar`/`HVAR`) | `.ttf`, `.otf`, `.woff`, `.woff2` | Supported for `glyf`-based outlines |

Every unsupported case raises a typed exception (`UnsupportedFontException` or
`InvalidFontException`) instead of degrading silently.
The package has no PHP package runtime dependencies. Unsupported containers and font features fail
with typed exceptions instead of returning partial data.

## Installation

Install ALTO Font with Composer:

```bash
composer require alto/font
```

## Development
ALTO Font requires PHP 8.4 or later with Iconv and Zlib. Both extensions are included in most PHP
distributions. WOFF2 additionally requires the Brotli PHP extension or the `brotli` executable.

## Quick Start

Load a font and inspect its face, descriptor, and one glyph:

```php
use Alto\Font\Font;

$font = Font::fromFile(__DIR__.'/fonts/Inter-Regular.ttf');
$descriptor = $font->getDescriptor();

printf(
"%s %s, %d units per em\n",
$descriptor->family,
$descriptor->subfamily,
$font->face()->unitsPerEm,
);

$metrics = $font->getMetrics('A');
$outline = $font->glyphOutline($metrics->glyphId);
```

Metrics and outlines use the font's design units. Read [Getting started](docs/getting-started.md)
for scaling and outline inspection.

## Format Support

| Format | Support |
| --- | --- |
| TrueType and OpenType with `glyf` outlines | Supported |
| WOFF 1 | Supported |
| WOFF2 | Supported, including transformed `glyf`, `loca`, and `hmtx` |
| TTC and OTC collections | Supported with `faceIndex` |
| Variable `glyf` fonts | Supported through `fvar`, `avar`, `gvar`, and `HVAR` |
| CFF/CFF2 outlines, WOFF2 collections, and color glyph rendering | Not supported |

Read [Font formats](docs/formats.md) for requirements, boundaries, and failure types.

## Discovery

Find the closest face for a family, weight, style, and stretch query:

```php
use Alto\Font\FontFinder;
use Alto\Font\FontQuery;

$finder = FontFinder::fromDirectories(__DIR__.'/fonts');
$font = $finder->get(FontQuery::family('Inter')->weight(700)->italic());
```

ALTO Font can search application directories, system fonts, or a custom locator. Read
[Font discovery](docs/discovery.md) for matching and absence policies.

## Metadata and Glyphs

`FontFace` exposes structural metrics and table records. `FontDescriptor` provides names and
CSS-like matching values, while `FontMetadata` includes optional publisher and licensing fields.

Character lookup returns a font-specific glyph identifier. From it, retrieve metrics or neutral
contour geometry made of move, line, quadratic-curve, and close commands.

See [Font metadata](docs/metadata.md) and [Glyphs](docs/glyphs.md).

## Variable Fonts

Inspect axes and select immutable coordinates:

```php
$boldCondensed = $font->withVariations([
'wght' => 700,
'wdth' => 85,
]);
```

Selected coordinates affect supported glyph metrics and outlines. Read
[Variable fonts](docs/variations.md) for axes, named instances, clamping, and observable results.
The [complete guide](docs/index.md) links every topic.

## Contributing

Contributions of all kinds are welcome. Visit the
[project on GitHub](https://github.com/altophp/font) to
[report a bug](https://github.com/altophp/font/issues/new),
[suggest a feature](https://github.com/altophp/font/issues/new), or
[open a pull request](https://github.com/altophp/font/pulls).

Before submitting code, run:

```bash
composer install
composer check # phpstan + cs-check + test
composer test # phpunit only
composer cs-fix # apply coding-standard fixes
# Runs PHP CS Fixer, PHPStan, and PHPUnit
composer qa
```

Changes to public behavior should include tests and documentation.

## Support

ALTO Font is open source. You can support its continued development through
[GitHub Sponsors](https://github.com/sponsors/smnandre).

Sharing this package with others or
[starring it on GitHub](https://github.com/altophp/font) is also much
appreciated.

## License

Alto Font is available under the [MIT License](LICENSE).
ALTO Font is released by [ALTO PHP](https://altophp.com) under the
[MIT License](LICENSE).
36 changes: 21 additions & 15 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "alto/font",
"description": "Font file reading and metadata: parse OpenType/TrueType/WOFF/WOFF2, discover installed fonts, and expose per-glyph facts (metrics, outlines) without any drawing or shaping.",
"description": "Read OpenType, TrueType, WOFF, and WOFF2 font files from PHP.",
"keywords": [
"font",
"opentype",
"truetype",
"woff",
"woff2"
],
"homepage": "https://altophp.com/font/",
"homepage": "https://altophp.com/font",
"license": "MIT",
"type": "library",
"authors": [
Expand All @@ -19,22 +19,28 @@
],
"support": {
"issues": "https://github.com/altophp/font/issues",
"source": "https://github.com/altophp/font"
"source": "https://github.com/altophp/font",
"docs": "https://altophp.com/font"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/smnandre"
}
],
"require": {
"php": ">=8.4",
"php": "^8.4",
"ext-iconv": "*",
"ext-zlib": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.68",
"friendsofphp/php-cs-fixer": "^3.94",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^12"
"phpunit/phpunit": "^12.5"
},
"suggest": {
"ext-brotli": "Decompresses WOFF2 font data without shelling out to the brotli binary.",
"brotli": "Provides Brotli decompression for WOFF2 when ext-brotli is not installed."
"ext-brotli": "Decompresses WOFF2 font data without shelling out to the brotli binary."
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"Alto\\Font\\": "src/"
Expand All @@ -49,14 +55,14 @@
"sort-packages": true
},
"scripts": {
"check": [
"@phpstan",
"@cs-check",
"cs": "vendor/bin/php-cs-fixer fix --dry-run --diff --sequential",
"cs:fix": "vendor/bin/php-cs-fixer fix --sequential",
"qa": [
"@cs",
"@sa",
"@test"
],
"cs-check": "php-cs-fixer fix --dry-run --diff",
"cs-fix": "php-cs-fixer fix",
"phpstan": "phpstan analyse --no-progress",
"sa": "vendor/bin/phpstan analyse --memory-limit=-1",
"test": "phpunit"
}
}
Loading