Skip to content

Commit e06c8bb

Browse files
JLHwungmathiasbynens
authored andcommitted
Assign supplementary private-use planes before Unicode 3.0.1
UnicodeData from Unicode 2.0 through 3.0.0 lists the Private Use High Surrogates but not the planes 15 and 16 private-use characters their surrogate pairs encode, so those planes were reported as unassigned. Unicode 3.0.1 added the ranges to the file, and its documentation describes them as the characters those surrogate pairs represent. Assisted-By: Claude Opus 5.5 Issue: #110
1 parent bd41213 commit e06c8bb

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

‎scripts/parse-blocks-scripts-properties.mjs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ const parseDerivedGeneralCategory = async (version) => {
195195
let flag = false;
196196
let first = 0;
197197
let lastCodePoint = -1;
198+
let hasPrivateUseHighSurrogates = false;
198199
// Unicode 1.1.5 lists only U+4E00 as a representative of the Unified
199200
// Han ideographs. The Unicode 1.1 Han cross-reference table assigns the
200201
// whole 4E00..9FA5 range:
@@ -241,6 +242,8 @@ const parseDerivedGeneralCategory = async (version) => {
241242
if (/<.+, First>/.test(name)) {
242243
flag = true;
243244
first = codePoint;
245+
hasPrivateUseHighSurrogates ||=
246+
name === '<Private Use High Surrogate, First>';
244247
} else {
245248
for (const category of categories) {
246249
categoryMap[category].addRange(codePoint, recordEnd);
@@ -266,6 +269,13 @@ const parseDerivedGeneralCategory = async (version) => {
266269
// Unicode 1.1 Han cross-reference table lists as F900..FA2D.
267270
unlistedRanges.push([0xf900, 0xfa2d, 'Lo']);
268271
}
272+
if (hasPrivateUseHighSurrogates && lastCodePoint < 0x10000) {
273+
// Before Unicode 3.0.1, UnicodeData lists the Private Use High
274+
// Surrogates but not the planes 15 and 16 private-use characters
275+
// their surrogate pairs encode. Unicode 3.0.1 added those ranges:
276+
// https://www.unicode.org/Public/3.0-Update1/UnicodeData-3.0.1.html
277+
unlistedRanges.push([0xf0000, 0xffffd, 'Co'], [0x100000, 0x10fffd, 'Co']);
278+
}
269279
for (const [start, end, generalCategory] of unlistedRanges) {
270280
// Undo the gap handling, which recorded the range as unassigned.
271281
for (const category of [

‎tests/parsers.mjs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,24 @@ test('Unicode 1.1.5 assigns the unlisted CJK compatibility ideographs', async (t
186186
}
187187
});
188188

189+
test('Supplementary private-use planes are assigned from Unicode 2.0', async (t) => {
190+
for (const version of ['2.0.14', '2.1.9', '3.0.0', '3.0.1', '3.1.0']) {
191+
const categories = await parseDerivedGeneralCategory(version);
192+
for (const cp of [0xf0000, 0xffffd, 0x100000, 0x10fffd]) {
193+
const message = `${version}: ${cp.toString(16)}`;
194+
t.assert.ok(categories.Private_Use.contains(cp), message);
195+
t.assert.ok(categories.Assigned.contains(cp), message);
196+
t.assert.ok(!categories.Unassigned.contains(cp), message);
197+
}
198+
for (const cp of [0xeffff, 0xffffe, 0xfffff, 0x10ffff]) {
199+
const message = `${version}: ${cp.toString(16)}`;
200+
t.assert.ok(categories.Unassigned.contains(cp), message);
201+
}
202+
}
203+
const categories = await parseDerivedGeneralCategory('1.1.5');
204+
t.assert.ok(categories.Unassigned.contains(0xf0000));
205+
});
206+
189207
test('Bidi class applies historical and current unassigned defaults', async (t) => {
190208
for (const version of [
191209
'3.0.0',

0 commit comments

Comments
 (0)