Skip to content

Add support for CSS hyphens and hyphenate-character#66

Open
maitredede wants to merge 5 commits into
plutoprint:mainfrom
maitredede:feature/hyphens
Open

Add support for CSS hyphens and hyphenate-character#66
maitredede wants to merge 5 commits into
plutoprint:mainfrom
maitredede:feature/hyphens

Conversation

@maitredede

Copy link
Copy Markdown

Summary

Implements CSS hyphens (closes #28; addresses the hyphenation part of #16),
including the hyphenate-character property and automatic, dictionary-based
hyphenation via libhyphen as an optional dependency.

What's included

  • hyphens: none | manual — soft hyphens (U+00AD) are honored as break
    opportunities and now render a trailing hyphen glyph at the break; none
    suppresses hyphenation opportunities while keeping every other line break.
  • hyphenate-characterauto | <string>, inherited; resolves auto to
    U+2010 and is used as the glyph drawn at a hyphenation break.
  • hyphens: auto — dictionary hyphenation through libhyphen, wired into the
    line breaker so the overflowing word is broken at the last dictionary point
    that fits (to avoid overflow or to fill the line better than the word break).
  • Dictionary resolutionPLUTOBOOK_HYPHEN_PATH/usr/share/hyphen
    dictionaries embedded at build time. en-US is embedded by default; more can be
    selected with the hyphen-embed-dicts build option.

Dependency

libhyphen is optional (hyphen feature, auto-detected). When it is
absent, hyphens: auto transparently degrades to manual, and none/manual
work with no extra dependency. Docs updated in the Installation Guide.

Verification

Rendered end-to-end (bitmap output) against a real libhyphen install:

  • manual breaks multi&shy;plicationmulti- / plication; none keeps it whole.
  • hyphenate-character: '='multi= / plication.
  • auto, en-US via the embedded dictionary → extraor-/dinary respons-/bility hy-/phen-/ation.
  • auto, de via a system dictionary → Silbentren-/nung, Fußgängerü-/bergänge (validates the UTF-16↔UTF-8 offset mapping across multi-byte characters).
  • Building without libhyphen compiles cleanly and degrades as described.

Not included (follow-ups)

  • Applying auto hyphenation in computeIntrinsicWidths (min-content); today a
    hyphenatable word still contributes its full width to intrinsic sizing. This is
    a sizing fidelity gap, not a rendering bug.
  • Guards for complex/cursive scripts and ligature clusters; RTL is implemented
    but not exhaustively tested.

Opening as a draft for early feedback on the approach.

maitredede and others added 5 commits July 16, 2026 08:00
Implement the manual side of CSS `hyphens` (plutoprint#28): draw a trailing hyphen
glyph when a line breaks right after a soft hyphen (U+00AD), and suppress
soft-hyphen break opportunities for `hyphens: none` while keeping every
other break the line iterator reports.

`LineBreakIterator`'s break queries take an optional `skipSoftHyphen`
flag so `none` can drop only the hyphenation opportunities. `LineBreaker`
reads the inherited `hyphens` value, and `breakText` shapes a hyphen
glyph (U+2010) for the broken run, accounting for its advance in the line
fit. `TextLineBox` carries the hyphen shape and paints it at the visual
line-end (after the text in LTR, before it in RTL).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parse `hyphenate-character: auto | <string>`, inherit it, and expose it
as `BoxStyle::hyphenString()` which resolves `auto` to U+2010. The line
breaker now shapes the trailing hyphen from this value instead of a
hard-coded hyphen, so authors can pick the glyph shown at a hyphenation
break.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire `hyphens: auto` to a dictionary-based hyphenator built on libhyphen,
which is an optional dependency: when it is missing, `auto` transparently
degrades to `manual`.

`Hyphenator` loads a Hunspell/libhyphen dictionary per locale (cached,
thread-local), resolving it from the directories in `PLUTOBOOK_HYPHEN_PATH`
and then `/usr/share/hyphen`, and maps the UTF-8 break positions it returns
back to the UTF-16 offsets the line breaker uses. In `breakText`, the
overflowing word is hyphenated at the last dictionary break that fits,
either to avoid overflow or to fill the line better than the word boundary,
and the existing trailing-hyphen rendering covers the glyph.

The build probes `<hyphen.h>` and `libhyphen` directly since the library
ships no pkg-config file, guarded by the `hyphen` feature option.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a build-time pipeline that embeds selected libhyphen dictionaries into
the library, consulted by `Hyphenator` when no system dictionary is found.
`tools/embed_dicts.py` turns each dictionary chosen by the new
`hyphen-embed-dicts` option (en-US only by default) into a byte array, and
the embedded copy is loaded in memory via `fmemopen` + `hnj_hyphen_load_file`.
Lookup falls back from a full locale to any embedded dictionary for the same
language, so `lang="en"` resolves to the bundled en-US patterns.

The en-US patterns (dicts/hyph_en_US.dic, tri-licensed GPL-2+/LGPL-2.1+/
MPL-1.1+) are bundled under the MPL and credited in CREDITS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `hyphen` to the optional dependencies (and `libhyphen-dev` to the
Ubuntu/Debian command), and a "Hyphenation Dictionaries" section covering
the runtime dictionary search order (PLUTOBOOK_HYPHEN_PATH, /usr/share/hyphen,
embedded), installing system language packs, and embedding dictionaries at
build time via the `hyphen-embed-dicts` option.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sammycage
sammycage marked this pull request as ready for review July 18, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for CSS hyphens

1 participant