Skip to content

Commit 1ca9177

Browse files
matmenclaude
andcommitted
renderText: shape text in every script, bidirectional text, UAX #14 wrapping
A port of HarfBuzz 14.6's OpenType shaping (Old MIT, credited in the file headers and THIRD_PARTY_NOTICES.md): - buffer.ts: the in/out buffer with HarfBuzz's cluster merging - layout.ts: GDEF, script/language/feature selection with feature variations at the default instance, the feature map with stages and masks, every GSUB (1-8) and GPOS (1-9) lookup type with HarfBuzz's matching (ZWJ/ZWNJ, per-syllable, ligature components, mark attachment to ligature components, cursive chains) - shape.ts: unicode properties, dotted circles, native direction, mirroring, normalization (decompose/reorder/recompose by what the font has), fractions, space fallback, kern table, fallback mark positioning, default ignorables - shapers: Arabic (with presentation-form fallback and stch), Hebrew, Thai/Lao, Hangul, Indic, Khmer, Myanmar and the Universal Shaping Engine, their syllable grammars as longest-match NFA scanners - scripts/unicode.js also generates the Indic/USE categories (after HarfBuzz's generators, with Microsoft's USE data in scripts/ms-use), canonical decompositions, Arabic presentation forms, vowel constraints; property values are translated to short names Identical to HarfBuzz (glyphs, clusters, positions) on the UDHR in 29 scripts, ~300,000 random sequences over 30 Noto fonts, 15,000 emoji sequences in the color test fonts and 12,000 with the Latin test fonts. renderText now analyzes each paragraph (UAX #9 levels, UAX #29 graphemes for font fallback, script itemization with paired brackets, UAX #14 break opportunities), shapes runs of one level, script and font with surrounding context, wraps at break opportunities (graphemes for char wrapping and overlong words), and shapes and reorders every line by itself (L1/L2). Hinted glyphs keep their hinted advances with the positioning's adjustments rounded cumulatively. The old GSUB and GPOS kerning code is removed. Tests: tests/shaping.js compares 360 texts with HarfBuzz's output for subsets of Noto fonts (scripts/shaping-reference.py), tests/unicode.js adds GraphemeBreakTest, tests/font.js a bidirectional render test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJgRK5qpwJEbHPuFv6kgAQ
1 parent 6ebf2f1 commit 1ca9177

65 files changed

Lines changed: 7584 additions & 671 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,33 @@
2222
palettes (e.g. Twemoji), CBDT/CBLC bitmaps (e.g. Noto Color Emoji, which now loads at all, it has no outlines) and
2323
sbix bitmaps (Apple's format).
2424
Invisible characters like joiners and variation selectors take no room, as in browsers.
25-
- `Image.renderText` applies the fonts' glyph substitutions (GSUB) that browsers apply by default (`ccmp`, `rlig`,
26-
`liga`, `clig`, `calt`): emoji sequences (skin tones, flags, keycaps, families and other joined ones) become one
27-
glyph, and text fonts use their ligatures (fi, ffi, ...) and contextual alternates. Emoji sequences come from one
28-
font, the first that has all of their characters, and the emoji variation selector picks a color glyph (❤️ in an
29-
emoji font rather than the text font's ❤).
25+
- `Image.renderText` shapes text in every script like browsers do (a port of HarfBuzz's OpenType shaping, glyph for
26+
glyph and position for position the same as HarfBuzz 14.6 on the tested texts):
27+
- right to left and bidirectional text (the Unicode Bidirectional Algorithm): Arabic, Hebrew and others, mixed with
28+
left to right text and numbers, brackets mirrored
29+
- joining scripts (Arabic, Syriac, N'Ko, Mongolian, Adlam, ...), with presentation forms for fonts without shaping
30+
features
31+
- Indic scripts (Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam) with their
32+
conjuncts, reph and matra reordering, Khmer, Myanmar, Thai and Lao, Hangul syllables, and the scripts of the
33+
Universal Shaping Engine (Sinhala, Tibetan, Javanese, Balinese and many more)
34+
- all GSUB and GPOS lookups: mark positioning (diacritics, vowel signs), cursive attachment, kerning, contextual
35+
substitutions and positioning; the kern table for fonts without GPOS kerning, and marks placed by their combining
36+
class for fonts without mark positioning
37+
- emoji sequences (skin tones, flags, keycaps, families and other joined ones) become one glyph, from the first font
38+
that has all of their characters; the emoji variation selector picks a color glyph (❤️ in an emoji font rather than
39+
the text font's ❤)
40+
- text wraps at the line break opportunities of the Unicode Line Breaking Algorithm (also in CJK text and before
41+
or after punctuation, not only at spaces), words wider than a line between grapheme clusters; each line is
42+
shaped and ordered by itself
43+
The Unicode data (properties of version 18.0) with the bidirectional algorithm and line breaking add about 39 kB
44+
(gzipped) to the package, the shaping about 39 kB.
3045
- `Image.renderText` renders COLR version 1 color glyphs (e.g. Noto-COLRv1): solid colors, linear, radial and sweep
3146
gradients (pad, repeat, reflect), glyph outlines as masks, transforms and all composite modes; variable paints with
3247
their default values. They are rendered at four times the size and scaled down, so layers sharing an edge don't let the
3348
ones below show through. Fonts with both prefer them over their other color glyphs, like browsers.
3449
- `Image.renderText` composes combining sequences (e + ◌́ to é, as in text from macOS) where the font has the composed
35-
character, and decomposes characters the fonts lack where one has their parts, like HarfBuzz.
50+
character, and decomposes characters the fonts lack where one has their parts, like HarfBuzz. Fallback fonts are
51+
chosen per grapheme cluster (UAX #29).
3652
- Fallback fonts are only loaded when the text needs them, and glyphs (also decoded and scaled emoji bitmaps) are
3753
kept per font and size across calls.
3854
- `DecodeOptions.maxPixels` and `AnimationDecodeOptions.maxPixels` (default 268,402,689, that's 16383², like sharp):

‎README.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ deno add jsr:@matmen/imagescript # Deno (npm:imagescript works too)
3434
frames, durations and disposal modes
3535
- [Rendering SVGs](https://imagescript.matmen.dev/classes/Image.html#rendersvg) (shapes, paths, gradients, strokes, clip paths,
3636
masks, CSS; no text, images or filters)
37-
- [Rendering vector fonts](https://imagescript.matmen.dev/classes/Image.html#rendertext) (TrueType and OpenType, with kerning,
38-
ligatures, outlines, TrueType hinting, fallback fonts and color emoji)
37+
- [Rendering vector fonts](https://imagescript.matmen.dev/classes/Image.html#rendertext) (TrueType and OpenType, with
38+
complex text shaping in every script, right to left and bidirectional text, outlines, TrueType hinting, fallback
39+
fonts and color emoji)
3940
- [Resizing](https://imagescript.matmen.dev/classes/Image.html#resize) (bilinear, bicubic, area averaging and nearest neighbor)
4041
- Image manipulation functions ([crop](https://imagescript.matmen.dev/classes/Image.html#crop)
4142
, [rotate](https://imagescript.matmen.dev/classes/Image.html#rotate)
@@ -103,7 +104,9 @@ The JPEG codec is based in part on the work of the Independent JPEG Group (libjp
103104
[pdf.js](https://github.com/mozilla/pdf.js), the glyph rasterizer is translated from
104105
[font-rs](https://github.com/raphlinus/font-rs). The WebP codecs follow [RFC 9649](https://www.rfc-editor.org/rfc/rfc9649) and [RFC 6386](https://www.rfc-editor.org/rfc/rfc6386)
105106
and reproduce [libwebp](https://chromium.googlesource.com/webm/libwebp)'s color conversion, the TrueType hinting follows
106-
[FreeType](https://freetype.org)'s interpreter (portions of this software are copyright © 2026 The FreeType Project).
107+
[FreeType](https://freetype.org)'s interpreter (portions of this software are copyright © 2026 The FreeType Project),
108+
the text shaping is ported from [HarfBuzz](https://github.com/harfbuzz/harfbuzz) and uses data of the
109+
[Unicode Character Database](https://www.unicode.org/ucd/).
107110
See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for their licenses.
108111

109112
---

‎THIRD_PARTY_NOTICES.md‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ following projects, under their own licenses. Each derived file says so in its h
1515
| [libwebp](https://chromium.googlesource.com/webm/libwebp) | `src/codecs/vp8.ts` (YUV to RGB conversion, "fancy" chroma upsampling), `src/codecs/webp.ts` (animation frame blending) | BSD-3-Clause |
1616
| [FreeType](https://freetype.org) | `src/render/hinting.ts` (behavior and parts of the logic of the TrueType interpreter) | FreeType License |
1717
| [Pillow](https://github.com/python-pillow/Pillow) | `src/util/resample.ts` (filter coefficients, box, bilinear and bicubic filters) | MIT-CMU |
18+
| [HarfBuzz](https://github.com/harfbuzz/harfbuzz) | `src/render/buffer.ts`, `src/render/layout.ts`, `src/render/shape.ts`, `src/render/shapers/*` (OpenType shaping: buffer, GSUB/GPOS, normalization, fallback positioning, the script shapers and syllable grammars), `scripts/unicode.js` (shaping category tables) | Old MIT |
19+
| [Universal Shaping Engine data](https://github.com/harfbuzz/harfbuzz/tree/main/src/ms-use) by Microsoft | `scripts/ms-use` (category overrides, invalid vowel sequences), the tables generated from them in `src/render/unicode-data.ts` | MIT |
1820
| [Unicode Character Database](https://www.unicode.org/ucd/) | `src/render/unicode-data.ts` (character properties, generated by `scripts/unicode.js`) | Unicode License v3 |
1921

2022
## fflate, Fast CRC32 in JavaScript
@@ -206,3 +208,80 @@ Copyright © 1991-2026 Unicode, Inc.
206208
under the Unicode License v3, included as [licenses/Unicode-3.0.txt](licenses/Unicode-3.0.txt). The bidirectional
207209
algorithm (`src/render/bidi.ts`) and the line breaking (`src/render/linebreak.ts`) are written after UAX #9 and
208210
UAX #14.
211+
212+
## HarfBuzz
213+
214+
The shaping engine is ported from HarfBuzz 14.6.0; its license is included as
215+
[licenses/HarfBuzz.txt](licenses/HarfBuzz.txt). The copyright holders of the ported files are listed in the headers
216+
of the files derived from them.
217+
218+
```
219+
HarfBuzz is licensed under the so-called "Old MIT" license. Details follow.
220+
For parts of HarfBuzz that are licensed under different licenses see individual
221+
files names COPYING in subdirectories where applicable.
222+
223+
Copyright © 2010-2022 Google, Inc.
224+
Copyright © 2015-2020 Ebrahim Byagowi
225+
Copyright © 2019,2020 Facebook, Inc.
226+
Copyright © 2012,2015 Mozilla Foundation
227+
Copyright © 2011 Codethink Limited
228+
Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies)
229+
Copyright © 2009 Keith Stribley
230+
Copyright © 2011 Martin Hosken and SIL International
231+
Copyright © 2007 Chris Wilson
232+
Copyright © 2005,2006,2020,2021,2022,2023 Behdad Esfahbod
233+
Copyright © 2004,2007,2008,2009,2010,2013,2021,2022,2023 Red Hat, Inc.
234+
Copyright © 1998-2005 David Turner and Werner Lemberg
235+
Copyright © 2016 Igalia S.L.
236+
Copyright © 2022 Matthias Clasen
237+
Copyright © 2018,2021 Khaled Hosny
238+
Copyright © 2018,2019,2020 Adobe, Inc
239+
Copyright © 2013-2015 Alexei Podtelezhnikov
240+
241+
For full copyright notices consult the individual files in the package.
242+
243+
244+
Permission is hereby granted, without written agreement and without
245+
license or royalty fees, to use, copy, modify, and distribute this
246+
software and its documentation for any purpose, provided that the
247+
above copyright notice and the following two paragraphs appear in
248+
all copies of this software.
249+
250+
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
251+
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
252+
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
253+
IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
254+
DAMAGE.
255+
256+
THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
257+
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
258+
FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
259+
ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
260+
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
261+
```
262+
263+
## Universal Shaping Engine data
264+
265+
```
266+
MIT License
267+
268+
Copyright (c) Microsoft Corporation.
269+
270+
Permission is hereby granted, free of charge, to any person obtaining a copy
271+
of this software and associated documentation files (the "Software"), to deal
272+
in the Software without restriction, including without limitation the rights
273+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
274+
copies of the Software, and to permit persons to whom the Software is
275+
furnished to do so, subject to the following conditions:
276+
277+
The above copyright notice and this permission notice shall be included in all
278+
copies or substantial portions of the Software.
279+
280+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
281+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
282+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
283+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
284+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
285+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
286+
SOFTWARE
287+
```

‎licenses/HarfBuzz.txt‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
HarfBuzz is licensed under the so-called "Old MIT" license. Details follow.
2+
For parts of HarfBuzz that are licensed under different licenses see individual
3+
files names COPYING in subdirectories where applicable.
4+
5+
Copyright © 2010-2022 Google, Inc.
6+
Copyright © 2015-2020 Ebrahim Byagowi
7+
Copyright © 2019,2020 Facebook, Inc.
8+
Copyright © 2012,2015 Mozilla Foundation
9+
Copyright © 2011 Codethink Limited
10+
Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies)
11+
Copyright © 2009 Keith Stribley
12+
Copyright © 2011 Martin Hosken and SIL International
13+
Copyright © 2007 Chris Wilson
14+
Copyright © 2005,2006,2020,2021,2022,2023 Behdad Esfahbod
15+
Copyright © 2004,2007,2008,2009,2010,2013,2021,2022,2023 Red Hat, Inc.
16+
Copyright © 1998-2005 David Turner and Werner Lemberg
17+
Copyright © 2016 Igalia S.L.
18+
Copyright © 2022 Matthias Clasen
19+
Copyright © 2018,2021 Khaled Hosny
20+
Copyright © 2018,2019,2020 Adobe, Inc
21+
Copyright © 2013-2015 Alexei Podtelezhnikov
22+
23+
For full copyright notices consult the individual files in the package.
24+
25+
26+
Permission is hereby granted, without written agreement and without
27+
license or royalty fees, to use, copy, modify, and distribute this
28+
software and its documentation for any purpose, provided that the
29+
above copyright notice and the following two paragraphs appear in
30+
all copies of this software.
31+
32+
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
33+
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
34+
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
35+
IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
36+
DAMAGE.
37+
38+
THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
39+
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
40+
FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
41+
ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
42+
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

‎scripts/ms-use/COPYING‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Microsoft Corporation.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE

0 commit comments

Comments
 (0)