Skip to content

Commit

Permalink
Workflow and Readme
Browse files Browse the repository at this point in the history
  • Loading branch information
laurmaedje committed Jun 9, 2022
1 parent 3c6b2c8 commit 51c0eaf
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 17 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Continuous integration

on: [push, pull_request]

env:
CARGO_TERM_COLOR: always

jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable]

steps:
- name: Checkout source code
uses: actions/checkout@v2

- name: Build
run: cargo build --release

- name: Test
run: cargo test --release
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
[package]
name = "subsetter"
version = "0.1.0"
authors = ["Laurenz <[email protected]>"]
edition = "2021"
description = "Reduces the size and coverage of OpenType fonts."
repository = "https://github.com/typst/subsetter"
readme = "README.md"
license = "MIT OR Apache-2.0"
categories = ["compression", "encoding"]
keywords = ["subsetting", "OpenType", "PDF"]

[dev-dependencies]
ttf-parser = "0.15"
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# subsetter
Reduces the size and coverage of OpenType fonts.

Supports both TrueType and CFF outlines.

## Example
In the example below, we remove all glyphs except the ones with IDs 68, 69, 70.
Those correspond to the letters 'a', 'b' and 'c'.

```rust
use subsetter::{subset, Profile};

// Read the raw font data.
let data = std::fs::read("fonts/NotoSans-Regular.ttf")?;

// Keep only three glyphs and the OpenType tables
// required for embedding the font in a PDF file.
let glyphs = &[68, 69, 70];
let profile = Profile::pdf(glyphs);
let sub = subset(&data, 0, profile)?;

// Write the resulting file.
std::fs::write("target/Noto-Small.ttf", sub)?;
```

Notably, this subsetter does not really remove glyphs, just their outlines. This
means that you don't have to worry about changed glyphs IDs. However, it also
means that the resulting font won't always be as small as possible. To somewhat
remedy this, this crate sometimes at least zeroes out unused data that it cannot
fully remove. This helps if the font gets compressed, for example when embedding
it in a PDF file.

In the above example, the original font was 375 KB (188 KB zipped) while the
resulting font is 36 KB (5 KB zipped).

## Limitations
Currently, the library only subsets static outline fonts. Furthermore, it is
designed for use cases where text was already mapped to glyphs. Possible future
work includes:

- The option to pass variation coordinates which would make the subsetter create
a static instance of a variable font.
- Subsetting of bitmap, color and SVG tables.
- A profile which takes a char set instead of a glyph set and subsets the
layout tables.

## Safety and Dependencies
This crate forbids unsafe code and has zero dependencies.

## License
This crate is dual-licensed under the MIT and Apache 2.0 licenses.
5 changes: 3 additions & 2 deletions src/glyf.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::*;

/// A glyf + loca table.
struct Table<'a> {
loca: &'a [u8],
glyf: &'a [u8],
Expand Down Expand Up @@ -136,8 +137,8 @@ pub(crate) fn subset(ctx: &mut Context) -> Result<()> {
if ctx.subset.contains(&id) {
let data = table.glyph_data(id)?;
sub_glyf.give(data);
if data.len() % 2 != 0 {
sub_glyf.write::<u8>(0);
if !ctx.long_loca {
sub_glyf.align(2);
}
}
}
Expand Down
26 changes: 11 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
Reduce the size and coverage of OpenType fonts.
Reduces the size and coverage of OpenType fonts.
Supports both TrueType and CFF outlines.
Expand Down Expand Up @@ -30,8 +30,8 @@ Notably, this subsetter does not really remove glyphs, just their outlines. This
means that you don't have to worry about changed glyphs IDs. However, it also
means that the resulting font won't always be as small as possible. To somewhat
remedy this, this crate sometimes at least zeroes out unused data that it cannot
fully remove. This helps if the font is compressed (which it can be when
embedded into a PDF).
fully remove. This helps if the font gets compressed, for example when embedding
it in a PDF file.
In the above example, the original font was 375 KB (188 KB zipped) while the
resulting font is 36 KB (5 KB zipped).
Expand Down Expand Up @@ -71,9 +71,11 @@ impl<'a> Profile<'a> {
/// Keeps only the basic required tables plus either the TrueType-related or
/// CFF-related tables.
///
/// In particular, this also removes all non-outline glyph descriptions like
/// bitmaps, layered color glyphs and SVGs as these are not mentioned in the
/// PDF standard and not supported by most PDF readers.
/// In particular, this removes:
/// - all text-layout related tables like GSUB and GPOS as it is expected
/// that text was already mapped to glyphs.
/// - all non-outline glyph descriptions like bitmaps, layered color glyphs
/// and SVGs.
///
/// The subsetted font can be embedded in a PDF as a `FontFile3` with
/// Subtype `OpenType`. Alternatively:
Expand Down Expand Up @@ -110,15 +112,8 @@ pub fn subset(data: &[u8], index: u32, profile: Profile) -> Result<Vec<u8>> {
long_loca: true,
};

// Find out which glyphs are used.
match ctx.kind {
FontKind::TrueType => glyf::discover(&mut ctx)?,
FontKind::CFF => cff::discover(&mut ctx),
_ => {}
}

if ctx.kind == FontKind::TrueType {
// Writes glyf and loca table.
glyf::discover(&mut ctx)?;
ctx.process(Tag::GLYF)?;
ctx.process(Tag::CVT)?;
ctx.process(Tag::FPGM)?;
Expand All @@ -127,6 +122,7 @@ pub fn subset(data: &[u8], index: u32, profile: Profile) -> Result<Vec<u8>> {
}

if ctx.kind == FontKind::CFF {
cff::discover(&mut ctx);
ctx.process(Tag::CFF)?;
ctx.process(Tag::CFF2)?;
ctx.process(Tag::VORG)?;
Expand Down Expand Up @@ -294,9 +290,9 @@ impl<'a> Context<'a> {
Tag::GLYF => glyf::subset(self)?,
Tag::LOCA => panic!("handled by glyf"),
Tag::CFF => cff::subset(self)?,
Tag::HEAD => head::subset(self)?,
Tag::HMTX => hmtx::subset(self)?,
Tag::POST => post::subset(self)?,
Tag::HEAD => head::subset(self)?,
_ => self.push(tag, data),
}

Expand Down

0 comments on commit 51c0eaf

Please sign in to comment.