shape

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-opentype/shape

CI pkg.go.dev coverage go license

A pure-Go, CGO=0, standard-library-only complex-text shaper — a HarfBuzz-lite for the go-opentype stack. It turns a run of Unicode text into positioned glyphs in visual order, so real Arabic (and Latin) renders correctly instead of as isolated, unattached glyphs.

It composes two siblings and adds nothing else: bidi reordering from go-opentype/bidi and GSUB/GPOS from go-opentype/opentype. No golang.org/x/*, no third-party modules; it builds for every Go target including GOOS=js GOARCH=wasm.

What it does

  • Bidirectional reordering — resolves UAX #9 embedding levels and lays the glyphs out left-to-right, so a right-to-left Arabic run is emitted in drawing order.
  • Arabic cursive joining — resolves each letter's joining form (isolated / initial / medial / final) and applies the font's isol/init/medi/fina GSUB features positionally, each only at the glyphs in that form. Joining forms are tracked through ccmp decomposition (the rasm-skeleton-plus-dots architecture real fonts such as Noto Sans Arabic use), so the joined glyphs actually connect.
  • Ligatures, mark attachment, kerning — GSUB ccmp/rlig/liga/calt then GPOS kern/mark/mkmk/curs, so diacritics sit on their base and pairs kern.

Usage

import (
    "github.com/go-opentype/opentype"
    "github.com/go-opentype/shape"
)

f, _ := opentype.Parse(ttf) // ttf is a []byte TrueType/OpenType blob
face := f.NewFace(32)        // 32px per em

for _, g := range shape.Shape(face, "بيت", shape.Options{}) {
    // g.GID      glyph to draw
    // g.Cluster  source rune index it derives from
    // g.XOffset, g.YOffset   placement relative to the pen (px)
    // g.XAdvance, g.YAdvance advance to move the pen by (px)
}

The base direction defaults to Auto (from the first strong character) and the script is auto-detected (any Arabic-block rune selects the Arabic shaper) unless you set Options.Script ("arab", "latn", "dflt") or Options.Direction.

API

type Glyph struct {
    GID      opentype.GlyphIndex
    Cluster  int
    XAdvance int
    YAdvance int
    XOffset  int
    YOffset  int
}

type Options struct {
    Direction bidi.Direction // LeftToRight, RightToLeft, Auto
    Script    string         // "arab", "latn", "dflt"; empty auto-detects
    Features  []string       // extra feature tags
}

func Shape(face *opentype.Face, text string, opts Options) []Glyph

Scope

Implemented: Arabic and Latin/default (Latin, Cyrillic, Greek, CJK, …) shaping — cursive joining, ligatures, mark attachment, kerning, bidi visual order.

Out of scope (future work): scripts that need glyph reordering or a state machine — Indic (Devanagari, …), Thai/Lao, Khmer, Myanmar and the Universal Shaping Engine. They currently shape through the default path (no reordering). Cluster indices are exact for one-to-one substitutions (the Arabic positional forms) and best-effort, monotonic, when a substitution changes the run length (ligatures, decomposition).

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package shape is a HarfBuzz-lite complex-text shaper for the go-opentype stack. It turns a run of Unicode text into positioned glyphs in visual (left-to-right) order, ready to blit, applying the three things a naive cmap-then-GSUB pass gets wrong for real text:

  • Bidirectional reordering (via github.com/go-opentype/bidi): resolve the UAX #9 embedding levels and lay the glyphs out left-to-right, so a right-to-left Arabic run is emitted in the order it is drawn.
  • Arabic cursive joining: each letter's Unicode joining form (isolated, initial, medial, final) is resolved, then the font's isol/init/medi/fina GSUB features are applied positionally — each only at the glyphs in that form — via opentype's ApplyMasked. Without this, Arabic renders as disconnected isolated letters.
  • Ligatures, mark attachment and kerning: GSUB ccmp/rlig/liga/calt then GPOS kern/mark/mkmk/curs, so diacritics sit on their base and pairs kern.

Usage

face := font.NewFace(32)
glyphs := shape.Shape(face, "بيت", shape.Options{})
for _, g := range glyphs {
	// g.GID is the glyph to draw; advance the pen by g.XAdvance,
	// offset the glyph by (g.XOffset, g.YOffset). All in pixels.
}

The base direction defaults to Auto (derived from the first strong character); the script is auto-detected from the text (any Arabic-block rune selects the Arabic shaper) unless Options.Script forces it.

Scope

Arabic and Latin/default (Latin, Cyrillic, Greek, CJK, ...) shaping are implemented. Scripts that need glyph reordering or a state machine — Indic (Devanagari, ...), Thai/Lao, Khmer, Myanmar and the Universal Shaping Engine — are out of scope and shape as the default path (no reordering); they are future work. Cluster indices are exact for one-to-one substitutions (the Arabic positional forms) and best-effort, monotonic, when a substitution changes the run length (ligatures, decomposition).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Glyph

type Glyph struct {
	GID      opentype.GlyphIndex
	Cluster  int
	XAdvance int
	YAdvance int
	XOffset  int
	YOffset  int
}

Glyph is one positioned glyph of a shaped run, in visual (left-to-right) order. GID is the glyph to draw; Cluster is the logical (rune) index in the source text the glyph derives from; the advances move the pen after drawing and the offsets place the glyph relative to the pen — all in whole pixels at the face's size.

func Shape

func Shape(face *opentype.Face, text string, opts Options) []Glyph

Shape turns text into a positioned glyph run in visual order. It resolves the bidirectional embedding levels, maps each rune to a glyph, applies GSUB (positionally for Arabic cursive joining, whole-run for ligatures and contextual alternates), positions the result with GPOS (kerning and mark attachment), and emits the glyphs left-to-right with per-glyph advances and offsets in pixels. An empty text, or a face whose font lacks GSUB/GPOS, simply skips the corresponding stage.

type Options

type Options struct {
	// Direction is the base paragraph direction (bidi.LeftToRight,
	// bidi.RightToLeft or bidi.Auto). The zero value is bidi.LeftToRight.
	Direction bidi.Direction
	// Script forces the shaping script: "arab" for Arabic, "latn"/"dflt" (or
	// any other value) for the default shaper. Empty auto-detects: any
	// Arabic-block rune selects "arab", otherwise "dflt".
	Script string
	// Features lists extra OpenType feature tags to activate, applied over the
	// whole run in both the substitution and positioning stages (a tag with no
	// matching lookups is a no-op).
	Features []string
}

Options configures a Shape call. The zero value shapes with an automatic base direction (from the first strong character), a script auto-detected from the text, and no extra features.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL