Repository navigation
Releases
v1.2.0
1.2.0 (2026-11-05)
Added
muniSegmentIterator_FindBreaksAroundgives the boundaries around a
byte offset, the last at or before it and the first after it, without
reading the text from its start: what Backspace, the arrow keys and a
click on a word need. It answers as the iterator would, for grapheme
clusters, words, sentences and lines at every setting, class
overrides and a word segmenter included, and leaves the iterator as
it was. In ordinary text it reads a few code points, so on a long
paragraph of English prose it takes under half a microsecond for
clusters, words and lines, where stepping a fresh iterator from the
start takes milliseconds; sentences back up to the last terminator.
It needs an iterator given its whole text at init
(#278).
No results change: the Unicode data is still 18.0.0 and
MUNI_RESULTS_VERSION stays 2, so stored normalized keys, skeletons
and verdicts from 1.1.2 stay valid.
v1.1.2
1.1.2 (2026-11-05)
Fixed
- Every internal function that returns
boolnow carries
MUNI_NODISCARD, so a compiler warns where the library would drop
one of their answers, andtools/check_nodiscard.pykeeps new ones
marked. No call dropped one, so nothing a caller sees changes: same
API, same behavior. The header tests now also check that
MUNI_NODISCARDis not empty in C23
(#276).
No results change: the Unicode data is still 18.0.0 and
MUNI_RESULTS_VERSION stays 2, so stored normalized keys, skeletons
and verdicts from 1.1.1 stay valid.
v1.1.1
1.1.1 (2026-11-04)
Fixed
MUNI_NODISCARDwas empty under GCC 14 in C23, since GCC 14 reports
its C23 as__STDC_VERSION__202000L and the macro waited for
202311L, so a program built with GCC 14 could drop any status the
library returns without a warning. In C the macro now takes
[[nodiscard]]in any dialect after C17 whose compiler has the
attribute. A program that drops a status now gets
-Wunused-resultfrom GCC 14 too, which-Werrormakes an error
(#271).
No results change: the Unicode data is still 18.0.0 and
MUNI_RESULTS_VERSION stays 2, so stored normalized keys, skeletons
and verdicts from 1.1.0 stay valid.
v1.1.0
1.1.0 (2026-11-04)
Added
- Every call that takes a
muniSegmentIteratoror a
muniScriptIteratorback now checks it first, and refuses one no
init call set up withmuni_errorInvalid, leaving it as it was: a
zeroed iterator, one of stray bytes, or one whose counts, flags or
states are out of range. Such an iterator was undefined behavior
before, and a forged script iterator could overflow a buffer on the
stack.muniSegmentIterator_GetLineBreakStrictnessand
muniSegmentIterator_GetWritingSystemgive their defaults for it, as
for NULL. A copy of an initialized iterator is accepted. The
pointers an iterator keeps (the text, the overrides, the breaker)
cannot be checked, so an iterator changed there by hand stays outside
the contract
(#264,
#267).
Changed
- The check costs each iterator call a few nanoseconds: line breaking
over English prose takes 1 to 2% longer, and up to 20% where every
call reports a boundary two code points after the last. Neither
iterator changes size
(#264).
No results change: the Unicode data is still 18.0.0 and
MUNI_RESULTS_VERSION stays 2, so stored normalized keys, skeletons
and verdicts from 1.0.0 stay valid.
v1.0.0
1.0.0 (2026-11-03)
The API is settled. 1.0.0 has the API and the results of 0.13.0; what
changes is what a caller may rely on from here. The promise is in
What 1.0.0 promises.
Changed
- Within 1.x no function, type, enum value, field or macro of the
eleven public headers,harfbuzz.hincluded, is removed or renamed,
and no release is breaking: a change that would break 1.x is made
additive or does not ship. There will be no 2.0.0
(#257). - The numbers of the property enumerations in
properties.hare part
of the API, so data may store them; a value a new Unicode version
adds goes after the last
(#257). - The CMake packages
maul-unicodeandmaul-unicode-harfbuzzaccept
a request for any earlier 1.y, where 0.x accepted only its own
version; the shared library'sSOVERSIONis1.0, so a program
built against 1.0 loads any 1.0.z and is built again for 1.1
(#248).
No results change: the Unicode data is still 18.0.0 and
MUNI_RESULTS_VERSION stays 2, so stored normalized keys, skeletons
and verdicts from 0.13.0 stay valid.
v0.13.0
0.13.0 (2026-11-02)
Added
muniMeasureUtf8finds the longest prefix of UTF-8 text that ends at
a boundary of amuniTextUnitand holds at mostmaxBytesbytes and
at mostmaxUnitsunits, and gives its length in bytes and in units;
either output may be NULL. It turns a byte offset into a count of
units, turns a count of units into a byte offset, cuts text to fit a
buffer, and counts the grapheme clusters of a whole text or finds
where a count of them ends. A limit that falls inside a code point or
a cluster ends the prefix at the boundary before it, and a count of
UTF-16 units that falls inside a surrogate pair ends it before the
pair. A call that fits whole and asks for no count does not walk the
text
(#243).muni_unitUtf16, a newmuniTextUnit, counts UTF-16 units: one for
a code point up to U+FFFF, two for one past it, and one for a maximal
ill-formed subpart, as its U+FFFD. WithmuniMeasureUtf8it maps
UTF-8 byte offsets to the UTF-16 offsets that AppKit, UIKit and
Android take, and back
(#243).
Changed
- Breaking: on
muni_errorCapacity,muniConvertUtf8ToUtf16,
muniConvertUtf16ToUtf8,muniConvertUtf8ToUtf32and
muniConvertUtf32ToUtf8give the input offset of the first code
point not written, instead of the input length. The output holds the
conversion of the input before that offset, always whole code points,
andneededOutstill counts the whole conversion. A caller can keep
what was written, or convert the rest from the offset, instead of
converting again with less input until it fits. A strict-mode error
after the output filled up still returns that error and its offset.
Code that read the input length from the offset on
muni_errorCapacityuses its ownlength
(#241).
Removed
-
Breaking:
muniCutUtf8is removed.muniMeasureUtf8with
maxUnitsset toSIZE_MAXgives the same cut; the unit now comes
before the byte limit. No consumer called it
(#243).0.12.0 0.13.0 muniCutUtf8(text, length, budget, unit, &cut)muniMeasureUtf8(text, length, unit, budget, SIZE_MAX, &cut, NULL)
v0.12.0
0.12.0 (2026-11-02)
Added
muniSegmentIterator_GetWordKindtells what the segment ending at the
boundary a word iterator last reported holds, as amuniWordKind: no
word (spaces, punctuation, symbols), a number, letters, kana or
ideographs, the highest kind among its code points. Word movement and
double-click selection skip segments of kindmuni_wordNone. Letters
with digits are letters, and a superscript digit such as "²" makes no
number. ICU's word rule statuses agree on segments of letters or
digits alone; on a mixed segment ICU reports the last rule it matched,
so it calls "ab12" a number
(#231).
Changed
- Word breaking executes 4.5% to 6.7% more instructions, to keep the
kind of each segment
(#231).
v0.11.0
0.11.0 (2026-11-02)
Added
muniGetDecompositionwrites the full decomposition of one code
point, canonical (muni_nfd) or compatibility (muni_nfkd), every
mapping applied, in canonical order; Hangul syllables give their jamo
and a code point that does not decompose gives itself. It reports the
count it needs, withmuni_errorCapacitywhen the output is too
small.MUNI_MAX_DECOMPOSITION(18, for U+FDFA in NFKD), now public
innormalize.h, always suffices for this Unicode version; a release
with a new Unicode version may raise it
(#227).muniDecodeUtf16, the counterpart ofmuniDecodeUtf8, decodes the
code point at the start of UTF-16 units: a surrogate pair takes two
units, anything else one. An unpaired surrogate gives
muni_errorUtf16Surrogatewith U+FFFD and one unit, as
muniConvertUtf16ToUtf8replaces it, so a decoding loop always makes
progress
(#228).muniCutUtf8gives the length of the longest prefix of UTF-8 text
within a byte budget that ends at a boundary of a unit:
muni_unitCodePoint, where each maximal ill-formed subpart counts as
one, ormuni_unitGraphemeCluster, which keeps a letter with its
accents, an emoji ZWJ sequence or a flag whole, for text fields and
preedit strings. The unit is an enumeration so that a later release
can add words
(#230).
v0.10.0
0.10.0 (2026-11-02)
⚠ BREAKING CHANGES
- Line iterators take a strictness level and a writing system in place
of the CJ switch.muniSegmentIterator_EnableCjAsIdeographic(iterator, true)becomesmuniSegmentIterator_SetLineBreakStrictness(iterator, muni_lineBreakLoose), which also adds loose's other breaks.
muniSegmentIterator_IsCjAsIdeographicEnabledis removed;
muniSegmentIterator_GetLineBreakStrictnessreports the level. A new
iterator breaks as before
(#221).
Added
- Line iterators break at the levels of CSS's
line-breakproperty:
strict, normal (the default), loose and anywhere, set with
muniSegmentIterator_SetLineBreakStrictnessbefore the first break.
muniSegmentIterator_SetWritingSystemwith
muni_writingChineseJapaneseturns on the rules CSS keeps for Chinese
and Japanese. Loose breaks before small kana and the prolonged sound
mark (Line_Break=CJ), before iteration marks and between characters
such as two ellipses; anywhere breaks between any two grapheme
clusters. Normal and strict keep CJ with what precedes it, as the CSS
Text 3 Candidate Recommendation Draft of 14 August 2026 says; ICU4C's
normal rules and earlier CSS drafts break before CJ in normal. The
default level breaks as before, at nearly the same cost (0.15% more
instructions on the bench)
(#221). muniConvertCasetakes two more languages.muni_caseGreek
uppercases Greek as it is written in capitals: accents and breathings
go, a dialytika stays and appears where a dropped accent kept two
vowels apart ("Μάιος" to "ΜΑΪΟΣ"), the disjunctive eta "ή" keeps its
tonos when it stands alone, and an iota subscript becomes a capital
iota; it follows ICU's Greek rules.muni_caseArmenianuppercases
the ligature "և" to "ԵՎ" and titlecases it to "Եվ". Lowercasing,
titlecasing (but the ligature) and folding follow the default rules
(#223).
Changed
- The results version is 2: calls to
muniConvertCasewith
muni_caseGreekormuni_caseArmenian, which were refused, now give
results. No result of the earlier languages changes, so stored keys
and skeletons stay valid; a caller that stores the results version
sees it move once
(#223).
v0.9.1
0.9.1 (2026-11-01)
Changed
- The headers now say what two more refusals leave.
muniReorderBidiLineleavesrunsOutandcountOutas they were on
muni_errorInvalid, andmuniGetResolvedScriptsleavesscriptsOut
andcountOutas they were onmuni_errorInvalidand on a UTF-8
error. Both calls always behaved this way
(#204). base.hnow says what the results version tracks: the status and
offset of the calls it covers, and their output on success, but not
the bytes and counts a refusal leaves. This is why the change to
refused skeletons below keeps it at 1
(#206).
Fixed
muniGetBidiClassreturns Left_To_Right for every value past
U+10FFFF, asproperties.hsays. It returned Boundary_Neutral, the
class of the noncharacters U+10FFFE and U+10FFFF, which the table
carried past them. Only a caller that passes a value that is no code
point sees the change: bidi and skeletons read the class only of
code points decoded from valid text, so their results and the results
version are unchanged
(#209).muniGetSkeletonnow stops at the segment that hit the limit when it
refuses text withmuni_errorLimit(a run of combining marks past
MUNI_MAX_NORMALIZATION_SEGMENT). It writes the skeleton of the text
shown before that segment, as far astextOutholds it, and
neededOutgives its length in bytes; nothing from that segment on is
written. Before, it went on past the run, dropped the marks that did
not fit, wrote the skeleton of the rest of the text and set
neededOutto 0.muniNormalizeandmuniGetNfkcCasefoldalready
stopped this way. A skeleton that succeeds is unchanged, and so is
the results version
(#203).