Skip to main content

opentype_shaper 0.3.2 copy "opentype_shaper: ^0.3.2" to clipboard
opentype_shaper: ^0.3.2 copied to clipboard

OpenType text shaping for Dart and Flutter: GSUB substitutions and GPOS mark positioning, returning glyph ids with per-glyph offsets.

opentype_shaper #

OpenType text shaping for Dart and Flutter.

Dart's PDF and text libraries do not run a font's OpenType layout tables. For a script that positions marks through GPOS, such as Hebrew with nikud and te'amim, that leaves every mark drawn at the pen instead of on its letter, and a glyph that only a GSUB rule can produce is never reached at all.

This package runs the shaping and hands back what a renderer or a PDF writer needs to place text itself: a glyph id per position, with the offset and advance the font's rules produced, in font units.

The engine is HarfRust, the Rust port of HarfBuzz, compiled into a small native library with no C or C++ dependencies. The Dart side talks to it over a plain C ABI, so there is no bridge runtime and nothing to initialise at startup.

Usage #

import 'package:opentype_shaper/opentype_shaper.dart';

final font = ShaperFont.register(fontBytes);

final run = font.shape('שָׁלוֹם', rtl: true, script: 'hebr');
for (var i = 0; i < run.glyphCount; i++) {
  // run.glyphId(i), run.xAdvance(i), run.xOffset(i), run.yOffset(i)
}

font.dispose();

Results are in font units; divide by font.metrics.unitsPerEm and multiply by the point size. Glyphs of a right-to-left run come back in visual order, so a consumer advances the pen positively while emitting them in sequence.

ShaperFont.attach(handle) lets a background isolate use a font the main isolate registered, by passing the integer handle rather than the bytes.

Embedding in a PDF #

A PDF that draws shaped glyphs addresses them by id (/CIDToGIDMap /Identity), so the font cannot be subset by character. subsetFontForPdf subsets by glyph id instead: the ids stay put, unused outlines become empty, components of composite glyphs are kept, and only the tables a PDF renderer reads survive. A variable font embeds its default instance, the one the shaper measured.

final program = subsetFontForPdf(fontBytes, usedGlyphIds);

For the fonts it was measured on, this is ten to thirty times smaller than the full file. CFF outlines are stripped of the other tables but not pruned glyph by glyph.

Building #

The Rust crate in rust/ reaches the app through the package's build hook (hook/build.dart), so it needs no platform plugin code and works with Swift Package Manager as well as CocoaPods. The hook downloads the signed binary published for the crate's content hash and compiles it with cargo only when none is published, which requires Rust. An app can force the source build:

hooks:
  user_defines:
    opentype_shaper:
      build_from_source: true

Published iOS device libraries target iOS 12, the framework minimum Flutter 3.38.1 writes, and the publisher checks the final Mach-O before signing so App Store validation accepts them. ARM64 simulator libraries target iOS 14, Rust's minimum for that target.

Tests need a font; flutter test builds the library itself:

OPENTYPE_SHAPER_TEST_FONT=/path/to/NotoSerifHebrew.ttf flutter test

The repository vendors no fonts. Continuous integration fetches an OFL font.

License #

MIT. See LICENSE.

5
likes
160
points
6.29k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

OpenType text shaping for Dart and Flutter: GSUB substitutions and GPOS mark positioning, returning glyph ids with per-glyph offsets.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

code_assets, crypto, ed25519_edwards, ffi, flutter, hooks, path

More

Packages that depend on opentype_shaper