Giter Club home page Giter Club logo

ttf-parser's Introduction

ttf-parser

Build Status Crates.io Documentation Rust 1.42+

A high-level, safe, zero-allocation font parser for TrueType, OpenType, and AAT.

Can be used as a Rust or C library.

Features

  • A high-level API for most common properties, hiding all parsing and data resolving logic.
  • A low-level, but safe API to access TrueType tables data.
  • Highly configurable. You can disable most of the features, reducing binary size. You can also parse TrueType tables separately, without loading the whole font/face.
  • Zero heap allocations.
  • Zero unsafe.
  • Zero dependencies.
  • no_std/WASM compatible.
  • A basic C API.
  • Fast.
  • Stateless. All parsing methods are immutable.
  • Simple and maintainable code (no magic numbers).

Safety

  • The library must not panic. Any panic considered as a critical bug and should be reported.
  • The library forbids unsafe code.
  • No heap allocations, so crash due to OOM is not possible.
  • All recursive methods have a depth limit.
  • Technically, should use less than 64KiB of stack in the worst case scenario.
  • Most of arithmetic operations are checked.
  • Most of numeric casts are checked.

Alternatives

It's very hard to compare different libraries, so we are using table-based comparison. There are roughly three types of TrueType tables:

  • A table with a list of properties (like head, OS/2, etc.).
    If a library tries to parse it at all then we mark it as supported.
  • A table that contains a single type of data (glyf, CFF (kinda), hmtx, etc.).
    Can only be supported or not.
  • A table that contains multiple subtables (cmap, kern, GPOS, etc.).
    Can be partially supported and we note which subtables are actually supported.
Feature/Library ttf-parser FreeType stb_truetype
Memory safe
Thread safe ~ (mostly reentrant)
Zero allocation
Variable fonts
Rendering -1 ~ (very primitive)
ankr table
avar table
bdat table ~ (no 4)
bloc table
CBDT table ~ (no 8, 9)
CBLC table
COLR table ~ (only v0)
CPAL table ~ (only v0)
CFF  table ~ (no seac support)
CFF2 table
cmap table ~ (no 8) ~ (no 2,8,10,14; Unicode-only)
EBDT table ~ (no 8, 9)
EBLC table
feat table
fvar table
gasp table
GDEF table ~
glyf table ~2 ~2
GPOS table ~ (only 2)
GSUB table
gvar table
head table
hhea table
hmtx table
HVAR table
kern table ~ (only 0) ~ (only 0)
kerx table
MATH table
maxp table
morx table
MVAR table
name table
OS/2 table
post table
sbix table ~ (PNG only) ~ (PNG only)
SVG  table
trak table
vhea table
vmtx table
VORG table
VVAR table
Language Rust + C API C C
Tested version 0.17.0 2.12.0 1.24
License MIT / Apache-2.0 FTL / GPLv2 public domain

Legend:

  • ✓ - supported
  • ~ - partial
  • nothing - not supported

Notes:

  1. While ttf-parser doesn't support rendering by itself, there are multiple rendering libraries on top of it: rusttype, ab-glyph and fontdue.
  2. Matching points are not supported.

Performance

TrueType fonts designed for fast querying, so most of the methods are very fast. The main exception is glyph outlining. Glyphs can be stored using two different methods: using Glyph Data Format and Compact Font Format (pdf). The first one is fairly simple which makes it faster to process. The second one is basically a tiny language with a stack-based VM, which makes it way harder to process.

The benchmark tests how long it takes to outline all glyphs in a font.

x86 (AMD 3700X)

Table/Library ttf-parser FreeType stb_truetype
glyf 0.901 ms 1.171 ms 0.675 ms
gvar 2.972 ms 4.132 ms -
CFF 1.197 ms 5.647 ms 2.813 ms
CFF2 1.968 ms 6.392 ms -

ARM (Apple M1)

Table/Library ttf-parser FreeType stb_truetype
glyf 0.550 ms 0.854 ms 0.703 ms
gvar 2.270 ms 4.594 ms -
CFF 1.054 ms 5.223 ms 3.262 ms
CFF2 1.765 ms 5.995 ms -

Note: FreeType is surprisingly slow, so I'm worried that I've messed something up.

And here are some methods benchmarks:

test outline_glyph_276_from_cff2 ... bench:         867 ns/iter (+/- 15)
test from_data_otf_cff           ... bench:         968 ns/iter (+/- 13)
test from_data_otf_cff2          ... bench:         887 ns/iter (+/- 25)
test outline_glyph_276_from_cff  ... bench:         678 ns/iter (+/- 41)
test outline_glyph_276_from_glyf ... bench:         649 ns/iter (+/- 11)
test outline_glyph_8_from_cff2   ... bench:         534 ns/iter (+/- 14)
test from_data_ttf               ... bench:         467 ns/iter (+/- 11)
test glyph_name_post_276         ... bench:         223 ns/iter (+/- 5)
test outline_glyph_8_from_cff    ... bench:         315 ns/iter (+/- 13)
test outline_glyph_8_from_glyf   ... bench:         291 ns/iter (+/- 5)
test family_name                 ... bench:         183 ns/iter (+/- 102)
test glyph_name_cff_276          ... bench:          62 ns/iter (+/- 1)
test glyph_index_u41             ... bench:          16 ns/iter (+/- 0)
test glyph_name_cff_8            ... bench:           5 ns/iter (+/- 0)
test glyph_name_post_8           ... bench:           2 ns/iter (+/- 0)
test subscript_metrics           ... bench:           2 ns/iter (+/- 0)
test glyph_hor_advance           ... bench:           2 ns/iter (+/- 0)
test glyph_hor_side_bearing      ... bench:           2 ns/iter (+/- 0)
test glyph_name_8                ... bench:           1 ns/iter (+/- 0)
test ascender                    ... bench:           1 ns/iter (+/- 0)
test underline_metrics           ... bench:           1 ns/iter (+/- 0)
test strikeout_metrics           ... bench:           1 ns/iter (+/- 0)
test x_height                    ... bench:           1 ns/iter (+/- 0)
test units_per_em                ... bench:         0.5 ns/iter (+/- 0)
test width                       ... bench:         0.2 ns/iter (+/- 0)

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

ttf-parser's People

Contributors

csmulhern avatar dhardy avatar dzamkov avatar ebraminio avatar fschutt avatar fuzzyzilla avatar laurmaedje avatar leedehai avatar orionnebula avatar pietrek14 avatar razrfalcon avatar ruifengx avatar shubhamj280 avatar skalt avatar wezm avatar wjian23 avatar xnuk avatar yisibl avatar zimond avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ttf-parser's Issues

How to get maximum character width?

I'm trying to render a text entry box that allows for up to 30 characters to be entered. To calculate its width, I need to know the max possible width of a 30-character string. Assuming nothing funny happens with spacing between letters, I think I can just multiply 30 by the max width of any character in that font.

I found global_bounding_box, and so I'm trying this:

let font_id = ...;
let font_size = 21;
fontdb.with_face_data(font_id, |data, face_index| {
        let font = ttf_parser::Face::from_slice(data, face_index).unwrap();
        let scale = (font_size as f64) / (font.units_per_em().unwrap() as f64);
        scale * font.global_bounding_box().width() as f64
})

For the Overpass regular font, I get 30.4 from this, which seems visually to be too wide for most of the alphanumeric characters I'm trying out. Is there maybe an outlier character that's especially wide, or is this calculation incorrect? I'm not sure what units global_bounding_box returns, nor if my understanding of units_per_em is correct.

Thanks!

bbox fallback regression in 0.12.2

I have a regression test for #49 that's been broken by 0.12.2.

For example for airstrip GlyphId(89).

0.12.1

Rect {
    x_min: 0,
    y_min: -3,
    x_max: 1102,
    y_max: 1301,
}

0.12.2

Rect {
    x_min: 0,
    y_min: 0,
    x_max: 0,
    y_max: 0,
}

Providing a get_glyph_extents like API

Something equivalent to hb_font_get_glyph_extents from ttf-parser would be nice so I can use in benchmarking, there is ttfp_get_glyph_{hor,ver}_{advance,side_bearing} which I guess won't be good enough for comparison to that API.

Symbola.ttf invalid glyph bounds

This font has glyph(s) that produce return invalid bounding boxes, e.g. with y_min == y_max. I'd expect these to return None when calling outline_glyph.

Presumably this font is dodgy. Should ttf-parser handle this or should I downstream?

Reproduce

// [dependencies]
// ttf-parser = "0.12"

/// https://github.com/gearit/ttf-symbola/raw/master/Symbola.ttf
const FONT: &[u8] = include_bytes!("../Symbola.ttf");

fn main() {
    let face = ttf_parser::Face::from_slice(FONT, 0).unwrap();
    let gid = ttf_parser::GlyphId(893);

    let bounds = face.outline_glyph(gid, &mut OutlinePrinter);
    eprintln!("outline_glyph({:?}, _) = {:?}", gid, bounds);
    if let Some(b) = bounds {
        assert!(b.x_min < b.x_max && b.y_min < b.y_max);
    }
}

struct OutlinePrinter;

impl ttf_parser::OutlineBuilder for OutlinePrinter {
    fn move_to(&mut self, x: f32, y: f32) {
        eprintln!("outline-move_to({}, {})", x, y);
    }
    fn line_to(&mut self, x: f32, y: f32) {
        eprintln!("outline-line_to({}, {})", x, y);
    }
    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
        eprintln!("outline-quad_to({}, {}, {}, {})", x1, y1, x, y);
    }
    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
        eprintln!(
            "outline-curve_to({}, {}, {}, {}, {}, {})",
            x1, y1, x2, y2, x, y
        );
    }
    fn close(&mut self) {
        eprintln!("outline-close");
    }
}
outline-move_to(0, 1606)
outline-line_to(0, 1606)
outline-close
outline-move_to(2048, 1606)
outline-line_to(2048, 1606)
outline-close
outline_glyph(GlyphId(893), _) = Some(Rect { x_min: 0, y_min: 1606, x_max: 2048, y_max: 1606 })
thread 'main' panicked at 'assertion failed: b.x_min < b.x_max && b.y_min < b.y_max', src/main.rs:14:9

ab-glyph issue: alexheretic/ab-glyph#32

[CFF] OffSize value of zero used in tests is invalid

Hi, I am working on porting ttf-parser's CharString porting code to Allsorts. I noticed that in the CharString tests CFF fonts are built with an OffSize in the header of zero, which Allsorts rejects. This works in ttf-parser because this value is skipped. The comment on that line (and in the tests) suggest that perhaps is value has been misinterpreted as it's not an absolute offset but the size in bytes of absolute offsets with valid values being 1–4.

This is what the CFF1 reference says:

Screenshot from 2021-01-14 13-47-48

CFF2 says:

Name Range Description
OffSize 1 to 4 1-byte unsigned number specifies the size of an Offset field or fields

Anyway not a huge issue since the value is unused but I thought I'd mention it anyway.

Add glyf bbox fallback

Using airstrip.ttf https://dl.dafont.com/dl/?f=airstrip_four character v returns a zero bounding box result from outline_glyph which seems wrong.

Reproduce

const AIRSTRIP: &[u8] = include_bytes!("../airstrip.ttf");

fn main() {
    let airstrip = ttf_parser::Face::from_slice(AIRSTRIP, 0).unwrap();

    let v = airstrip.glyph_index('v').unwrap();
    let bounds = airstrip.outline_glyph(v, &mut OutlinePrinter);

    dbg!(bounds);
}

struct OutlinePrinter;

impl ttf_parser::OutlineBuilder for OutlinePrinter {
    fn move_to(&mut self, x: f32, y: f32) {
        eprintln!("outline-move_to({}, {})", x, y);
    }
    fn line_to(&mut self, x: f32, y: f32) {
        eprintln!("outline-line_to({}, {})", x, y);
    }
    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
        eprintln!("outline-quad_to({}, {}, {}, {})", x1, y1, x, y);
    }
    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
        eprintln!("outline-curve_to({}, {}, {}, {}, {}, {})", x1, y1, x2, y2, x, y);
    }
    fn close(&mut self) {
        eprintln!("outline-close");
    }
}
outline-move_to(955, 1301)
outline-quad_to(864, 1298, 833, 1247)
outline-quad_to(802, 1196, 552, 447)
outline-quad_to(300, 1196, 272.5, 1248.5)
outline-quad_to(245, 1301, 150, 1301)
outline-quad_to(0, 1301, 0, 1151)
outline-line_to(401, 99)
outline-quad_to(443, 14, 493.5, 5.5)
outline-quad_to(544, -3, 602.5, 2.5)
outline-quad_to(661, 8, 697, 99)
outline-line_to(1102, 1149)
outline-quad_to(1102, 1301, 955, 1301)
outline-close
[src/main.rs:11] bounds = Some(
    Rect {
        x_min: 0,
        y_min: 0,
        x_max: 0,
        y_max: 0,
    },
)

bug: ttf width is not parsed correctly

in os2.rs#width(), due to the default number in Rust is i32, here Stream::read_at will read 4 bytes instead of the length of width (which is u16, 2 bytes), leading to width never correctly parsed

Cmap ignores platform/encoding

You're ignoring the encoding ID but accepting a Unicode char as the lookup. This is incorrect behavior. If the encoding is some windows code page instead, and you lookup the Unicode 'a', whatever you get back is worthless. If you do get a mapping for it, it's definitely not for the letter a.

It's likely that fonts that use non-unicode encodings are rare in the wild, but this library will silently do the wrong thing as it stands.

make c wrapper no_std

I am getting

/usr/bin/ld: subprojects/ttf-parser/libttf_parser_capi.a(std-205127404fcba336.std.595pxchd-cgu.0.rcgu.o): undefined reference to symbol 'pthread_getattr_np@@GLIBC_2.2.5'

and

/usr/bin/ld: subprojects/ttf-parser/libttf_parser_capi.a(std-205127404fcba336.std.595pxchd-cgu.0.rcgu.o): in function 'std::sys::unix::weak::fetch': /rustc/49cae55760da0a43428eba73abcb659bb70cf2e4//src/libstd/sys/unix/weak.rs:61: undefined reference to 'dlsym'

if I don't pass thread_dep, cpp.find_library('dl') (which currently I am) and guess those are coming from rust's std, can c-api also become no_std so I can avoid passing https://github.com/harfbuzz/harfbuzz/pull/2510/files#diff-9356922ca3ded90602b144c3b5851dd2R8 pthread and libdl to it? Or is it something you are interested in at all so I can also have a look?

Thanks!

Fails to outline 'i' in Carlito font

I've been wondering why 'i' does not get rendered with this font (most glyphs do render). Is this hitting an unsupported feature?

Code: https://gist.github.com/dhardy/60ef7de0e291e54ef7b4e2f8b95e1819
Font: /usr/share/fonts/google-carlito-fonts/Carlito-Regular.ttf

Output:

ttf-parser index: 98
ttf-parser bounding box: Rect { x_min: 110, y_min: 0, x_max: 357, y_max: 1409 }
ttf-parser failed to outline glyph
fontdue index: 98
fontdue metrics: Metrics { xmin: 0, ymin: 0, width: 0, height: 0, advance_width: 9.1796875, advance_height: 0.0, bounds: OutlineBounds { xmin: 0.0, ymin: 0.0, width: 0.0, height: 0.0 } }

Are there plans to support COLR/CPAL tables?

COLR format fonts are part of the OpenType specification. It is the perfect companion for emoji and font icons in usage scenarios. This has become so popular that all major browsers support COLRv0.

image

And Chrome will soon ship COLRv1, which will bring many new features such as gradients, transforms and compositing.

2023-09-21

image

Port GPOS/GSUB from rustybuzz

These three tables are key to modern fonts, especially for non-latin scripts.

rustybuzz already parses these tables to do glyph substitution and positioning, so the parsing code simply needs to be refactored for use in this library.

(If there's some reason keep the parsing of said tables in rustybuzz, it should be documented in the README)

Why ttf_parser::Face need a lifetime?

https://github.com/RazrFalcon/ttf-parser/blob/master/src/lib.rs#L580

pub struct Face<'a> {
    ...
}
impl<'a> Face<'a> {
    pub fn from_slice(data: &'a [u8], index: u32) -> Result<Self, FaceParsingError> { 
    }
}

It seems Face need reference the bytes for a long time.
I can not understand of the lifetime here, why it is necessity?
Is font parse not one-time action?

For this reason, I can't simply define a struct attr like:

pub struct Renderer {
    font: Face,     // <---- error!!! must gave a lifetime
}

This puts a lot of restrictions on the code design.
But i really can't figure it out.

Iterating through all kerning values

The ttf_parser::kern::Subtable API allows you to get the kerning information for two ordered glyphs.

Given stateful kerning (format 1) is unsupported, would it be reasonable to add API to iterate through all kerning pairs in a subtable? This is definitely trivial for format 0, but I haven't looked into 2 and 3.

Point direction request

I ran into this font which defines and uses glyphs in the CFF and glyf tables: https://github.com/emilk/egui/blob/master/egui/fonts/Comfortaa-Regular.ttf

My rasterizer needs to know the point winding before it can run outline_glyph. For TrueType, a counter clockwise contour will always define the outside edge of a filled area, and a clockwise contour will always define the inside edge of a filled area. This is reversed for OpenType fonts.

Is there a way to know if for a given GlyphId, the glyph is from an OpenType table or TrueType table?

Support a check for when a glyph has no outline, ie is invisible

During glyph layout (in glyph_brush_layout) I check if a glyph is invisible or not which can impact whether a word can fit into a line or not.

This check is currently done by see if the glyph has a bounding box or not, which is fast for the ttf fonts that are currently supported. Looking forward to using .otf this will no longer be a generally fast method.

Is there a quick way of checking this in general for .otf?

Another related idea is to switching the full compute OutlineBuilder with an OutlineIterator. If we had that presumably it would be possible to call:

let invisible = g.outline_iter().next().is_none()

Font fallbacks

@RazrFalcon have you thought about font fallbacks? As I understand, there are two parts: selecting fallback fonts, and building a virtual font pulling glyphs from the first supporting font. The second part could be supported here or could be left to a higher-level library.

The Face struct has several classes of methods:

  • from_slice constructor
  • has_table, table_data, character_mapping_subtables, ... — low-level stuff which it doesn't make sense to expose in a virtual "fallback font"
  • is_regular, is_monospaced, weight, ascender, ... — properties which could be taken from the first font (or perhaps in some cases combined via a func like max)
  • glyph_index(char), glyph_hor_advance(glyph_id), outline_glyph(glyph_id, builder), ... — glyph-specific functionality abstracted over by the virtual fallback font

The GlyphId type currently uses a u16 which is likely insufficient for the fallback font — this type could be made an associated type of a trait or simply larger.

Given the above, it might make more sense to implement font fallback at a higher level, e.g. via the ab_glyph::Font trait (if @alexheretic wishes to support font fallbacks), but I thought it would make sense to ask here first since (a) you (@RazrFalcon) may have already considered this and (b) whatever choice is made here has implications for downstream libraries (e.g. whether GlyphId becomes an associated type).

Should typographic ascender/descender be default for fonts with OS/2 table version < 4?

Hello,

A recent of upgrade of the ttf-parser dependency in the femtovg crate has caused an issue with alignment of the icons (provided by characters in the entypo font), as can be seen here: femtovg/femtovg#56.

As noted in the issue, this problem has been tracked down to d32a5de, which added windows acender/ descender if the USE_TYPO_METRICS flag is not set.

Looking into this I think the problem may be in the is_use_typo_metrics() function here:

pub fn is_use_typo_metrics(&self) -> bool {
if self.version < 4 {
false
} else {
SelectionFlags(self.fs_selection()).use_typo_metrics()
}
}

This function returns false is the OS/2 table version is less than 4, which makes sense because the USE_TYPO_METRICS flag was only introduced in version 4, but this means that the above function will cause the selection of ascender/descender to default to windows. The entypo font has an OS/2 table which is version 3. Now I can't find anything in the docs to suggest what the default ascender/descender selection should be for fonts with a version less than 4, but since it seems to be broken I would guess that maybe it should default to typographic.

My knowledge of font tables is limited so let me know if I'm way off here and if the solution should be on the femtovg usage side of things. Thanks.

Outline utilities

Hi!

I'm playing around with replacing FreeType with tff-parser in one of my projects.
I've made a couple of changes to be able to embolden and slant an outline.
The embolden algorithm is ported directly from the FreeType sources. To change
as little as possible of the original source I added a new method (that does nothing by default)
on the OutlineBuilder trait. The rest of the functionality lives in it's own module.

You can check my commit in this branch if interested:

https://github.com/chrsan/ttf-parser/tree/outline_utils

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.