org_heigl/hyphenator
org_heigl/hyphenator is a PHP library for automatic hyphenation using TeX patterns. It supports multiple languages, preserves HTML markup, and improves typography in web pages, emails, PDFs, and generated documents.
composer require org_heigl/hyphenator:^3.2
As of v3.2.0 the package supports UTF-8 TeX pattern files and now applies single-letter hyphenation patterns. Those are the two release items that affect day-to-day use; the rest is CI/supply-chain housekeeping. The package still has no Laravel service provider, facade, or config file, so you wire it yourself. Where to look first, in order:
vendor/org_heigl/hyphenator/README.md — the canonical usage snippet. The API predates PSR conventions, so confirm the exact pattern-registration call here before writing wrappers.src/Org/Heigl/Hyphenator/Hyphenator.php — the entry point; hyphenate() is the method you will call from application code.src/Org/Heigl/Hyphenator/Options.php — minimum word length, minimum leading/trailing characters, and the hyphen character itself (soft hyphen vs. visible hyphen).src/Org/Heigl/Hyphenator/Pattern/ — wraps TeX .pat/.hyp files; this is what you point at a locale's pattern data. Confirm on the release tag whether your pattern file is read in UTF-8 mode (see Gotchas).src/Org/Heigl/Hyphenator/Tokenizer/ and Filter/ — how plain text is split into word/non-word tokens and how rules are applied. This is where you extend behavior.Minimal integration — bind a configured instance once and inject it:
// app/Providers/HyphenationServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Org\Heigl\Hyphenator\Hyphenator;
use Org\Heigl\Hyphenator\Options;
class HyphenationServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(Hyphenator::class, function ($app) {
$hyphenator = new Hyphenator();
// Register TeX pattern files for the locales you support.
// Confirm the exact Pattern constructor/registration call in the README.
// UTF-8 pattern data is supported since v3.2.0 — save the .pat files as UTF-8.
// $hyphenator->addPattern(new \Org\Heigl\Hyphenator\Pattern\Pattern(
// resource_path('hyphenation/hyph-en-us.pat.txt')
// ));
$options = new Options();
// Tune to taste — these prevent "a-bout" style breaks.
// $options->setMinWordLength(6);
// $options->setMinLeading(2);
// $options->setMinTrailing(2);
// $options->setHyphen('­');
// $hyphenator->setOptions($options);
return $hyphenator;
});
}
}
First use case: hyphenating a headline or article body for a PDF export (Dompdf, Snappy, Browsershot) where CSS hyphens is unreliable, or for a CMS that renders justified text and needs break opportunities in long German/Scandinavian compounds. With 3.2.0, accented locales (fr, es, pt, pl, cs, hu, …) are finally realistic because their .pat files can be shipped in UTF-8 instead of Latin-1.
Wrap it behind your own interface. The package is niche (still low adoption, effectively single-maintainer), so isolate it:
interface Hyphenator
{
public function hyphenate(string $text, string $locale = 'en_US'): string;
}
Bind the concrete adapter in the container; swap implementations later without touching call sites.
Per-locale instances. Hyphenation is language-specific. Keep a keyed map rather than one giant instance, and resolve the locale from the current request / model:
$this->app->singleton(HyphenationManager::class, fn () => new HyphenationManager(
locales: ['en_US', 'de_DE', 'sv_SE', 'fr_FR', 'pl_PL'],
patternPath: resource_path('hyphenation'),
));
Blade directive / helper. Hyphenation is a rendering concern:
Blade::directive('hyphenate', fn ($expression) => "<?php echo e(app(\App\Hyphenation\Hyphenator::class)->hyphenate($expression)); ?>");
Or a Str::macro('hyphenate', ...) so it composes with Str::of($title)->limit(60)->hyphenate().
Accessor / presenter layer. Add a model accessor for rendered output and keep the raw column untouched:
public function getHyphenatedTitleAttribute(): string
{
return app(\App\Hyphenation\Hyphenator::class)->hyphenate($this->title, $this->locale);
}
Cache per word. Long documents repeat words constantly. Memoize in Redis, and include the package version in the cache key prefix so a composer update invalidates output that 3.2.0 may legitimately change:
$key = "hyph:v3.2:{$locale}:".md5($word);
$word = Cache::remember($key, now()->addDay(), fn () => $this->hyphenateWord($word, $locale));
Queue bulk work. Hyphenating thousands of article bodies belongs in a job, not a request. Store the rendered variant alongside the source so the front end never computes it inline.
Custom filters for "don't touch this" content. The tokenizer/filter pipeline is the natural extension point: skip URLs, emails, product SKUs, code identifiers, all-caps acronyms, and strings containing digits. Implement a Filter rather than pre-regexing the input — you keep the package's token boundaries intact.
Tokenize before hyphenating HTML. Never pass markup into hyphenate(). Either strip tags, operate on text nodes only, or hyphenate at the field level and render with {!! !!} after escaping the text nodes individually.
PDF pipeline pattern. For Dompdf/Snappy: hyphenate plain text fields first, insert ­ (or a visible - if the renderer ignores soft hyphens), then build the HTML. Pair with hyphens: auto only as a fallback for languages where you have no patterns.
Golden-word regression test. Cheap and high-value: assert a handful of known words per locale (hyphenation → hy-phen-ation, Silbentrennung → Sil-ben-tren-nung). 3.2.0 changes output for single-letter patterns, so this test is what tells you an upgrade is safe.
minWordLength, minLeading, minTrailing if you see fragments you dislike — the options still gate the output..pat files for you. Audit the encoding of every shipped pattern/exception file (file --mime-encoding), normalize to UTF-8 without BOM, and re-test accented words (Silbentrennung, déshydratation) — mojibake here presents as silently wrong or missing breaks.­ (U+00AD) and the engine honors it. In plain text/PDF you often want a real -, so set the hyphen character explicitly in Options.&, ­) survive the pipeline.minWordLength, minLeading, minTrailing per language and add a filter to skip short/uppercase tokens.Hyphenator or re-read .pat files per request. Watch memory on very large documents — chunk long bodies by paragraph.^3.2), run it against your PHP/Laravel matrix, keep the characterization test above, and keep it behind your own interface so replacement is a one-class change. The 3.2.0 release does show outside contributors landing features and CI hardening (PRs on PRs, action pinning), which is a mild positive signal.bootstrap/providers.php (Laravel 11+) or config/app.php, and note that composer require alone does nothing.hyphenate('hyphenation') against the expected hy-phen-ation before blaming your integration.How can I help you explore Laravel packages today?