Merge pull request #16501 from keymanapp/fix/developer/16457-ldml-validate-mindevicewidth

fix(developer): improve validation of minDeviceWidth to match spec
This commit is contained in:
Marc Durdin 2026-09-03 21:48:03 +07:00 committed by GitHub
commit b6f528b4ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 116 additions and 12 deletions

View file

@ -20,19 +20,26 @@ export class LayrCompiler extends SectionCompiler {
let valid = true;
let totalLayerCount = 0;
let hardwareLayers = 0;
let touchLayers = 0;
let touchLayerCount = 0;
let hasNullDeviceWidth = false;
const deviceWidths = new Set<number>();
this.keyboard3.layers?.forEach((layers) => {
const { formId } = layers;
if (formId === 'touch') {
touchLayers++;
touchLayerCount++;
totalLayerCount += layers.layer?.length;
// TODO-LDML: CLDR-19754 spec does not require minDeviceWidth, but if multiple touch forms then it would be important for differentiation
const { minDeviceWidth } = layers;
if (!minDeviceWidth ||
if (minDeviceWidth === undefined || minDeviceWidth === null) {
if(hasNullDeviceWidth) {
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_MultipleTouchFormsWithoutMinDeviceWidth(layers));
}
hasNullDeviceWidth = true;
} else if (
(typeof minDeviceWidth === 'string' && (<string>minDeviceWidth).trim() === '') ||
Number.isNaN(Number(minDeviceWidth)) ||
minDeviceWidth < constants.layr_min_minDeviceWidth ||
minDeviceWidth > constants.layr_max_minDeviceWidth ||
Number.isNaN(Number(minDeviceWidth))) {
minDeviceWidth > constants.layr_max_minDeviceWidth
) {
valid = false;
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidLayerWidth({minDeviceWidth}, layers));
} else if (deviceWidths.has(minDeviceWidth)) {

View file

@ -277,7 +277,7 @@ export class LdmlCompilerMessages {
);
static ERROR_InvalidLayerWidth = SevError | 0x002D;
static Error_InvalidLayerWidth = (o: { minDeviceWidth: number }, compileContext?: ObjectWithCompileContext) => mx(
static Error_InvalidLayerWidth = (o: { minDeviceWidth: number | string }, compileContext?: ObjectWithCompileContext) => mx(
this.ERROR_InvalidLayerWidth, compileContext,
`Invalid Layers minDeviceWidth=${def(o.minDeviceWidth)}`,
`Width must be between 1-999 (millimeters), inclusive.` // sync with layr_max_minDeviceWidth / layr_max_maxDeviceWidth (from spec)
@ -328,6 +328,14 @@ export class LdmlCompilerMessages {
and should not have an \`id\` attribute.
`);
static HINT_MultipleTouchFormsWithoutMinDeviceWidth = SevHint | 0x0035;
static Hint_MultipleTouchFormsWithoutMinDeviceWidth = (compileContext?: ObjectWithCompileContext) => mx(
this.HINT_MultipleTouchFormsWithoutMinDeviceWidth, compileContext,
`When multiple touch forms are present, 'minDeviceWidth' is required to differentiate them`, `
Touch forms are differentiated by their minimum device width, so when there is
more than one, at most one form may omit the \`minDeviceWidth\` attribute.
`);
//
// Transform syntax errors begin at ...F00 (SevErrorTransform)

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
<info name="layr-error-custom-form"/>
<keys>
<key id="one" output="1" />
<key id="two" output="2" />
<key id="three" output="3" />
</keys>
<layers formId="iso">
<layer modifiers="none">
<row keys="one two three" />
</layer>
</layers>
<layers formId="touch" minDeviceWidth=""> <!-- a blank value is not permitted (instead, omit the attribute entirely) -->
<layer id="base">
<row keys="one" />
<row keys="two" />
<row keys="three" />
</layer>
</layers>
</keyboard3>

View file

@ -19,7 +19,7 @@
<row keys="three" />
</layer>
</layers>
<layers formId="touch" minDeviceWidth="0"> <!-- dup -->
<layers formId="touch" minDeviceWidth="0"> <!-- width 0 not permitted -->
<layer id="base">
<row keys="one" />
<row keys="two" />

View file

@ -19,7 +19,7 @@
<row keys="three" />
</layer>
</layers>
<layers formId="touch" minDeviceWidth="1024"> <!-- dup -->
<layers formId="touch" minDeviceWidth="1024"> <!-- max width 999mm -->
<layer id="base">
<row keys="one" />
<row keys="two" />

View file

@ -12,7 +12,7 @@
<row keys="one two three" />
</layer>
</layers>
<layers formId="touch" minDeviceWidth="1500">
<layers formId="touch" minDeviceWidth="1500"> <!-- max width 999mm -->
<layer id="base">
<row keys="one" />
<row keys="two" />

View file

@ -12,7 +12,7 @@
<row keys="one two three" />
</layer>
</layers>
<layers formId="touch" minDeviceWidth="x">
<layers formId="touch" minDeviceWidth="x"> <!-- not a number -->
<layer id="base">
<row keys="one" />
<row keys="two" />

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
<info name="layr-hint-multiple-touch-forms-without-min-device-width"/>
<keys>
<key id="one" output="1" />
<key id="two" output="2" />
<key id="three" output="3" />
</keys>
<layers formId="iso">
<layer modifiers="none">
<row keys="one two three" />
</layer>
</layers>
<layers formId="touch">
<layer id="base">
<row keys="one" />
<row keys="two" />
<row keys="three" />
</layer>
</layers>
<layers formId="touch"> <!-- two forms without minDeviceWidth -->
<layer id="base">
<row keys="one" />
<row keys="two" />
<row keys="three" />
</layer>
</layers>
</keyboard3>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
<info name="layr-multiple-forms-one-without-min-device-width"/>
<keys>
<key id="one" output="1" />
<key id="two" output="2" />
<key id="three" output="3" />
</keys>
<layers formId="iso">
<layer modifiers="none">
<row keys="one two three" />
</layer>
</layers>
<layers formId="touch"> <!-- okay: only one form without minDeviceWidth -->
<layer id="base">
<row keys="one" />
<row keys="two" />
<row keys="three" />
</layer>
</layers>
<layers formId="touch" minDeviceWidth="120">
<layer id="base">
<row keys="one" />
<row keys="two" />
<row keys="three" />
</layer>
</layers>
</keyboard3>

View file

@ -173,7 +173,16 @@ describe('layr', function () {
LdmlCompilerMessages.Error_DuplicateLayerWidth({ minDeviceWidth: 120}),
]
},
...[0, 1024, 1500, `x` as unknown as number].map(minDeviceWidth => ({
{
subpath: 'sections/layr/hint-multiple-touch-forms-without-min-device-width.xml',
warnings: [
LdmlCompilerMessages.Hint_MultipleTouchFormsWithoutMinDeviceWidth(),
]
},
{
subpath: 'sections/layr/multiple-forms-one-without-min-device-width.xml',
},
...<any>[0, 1024, 1500, `x`, ``].map(minDeviceWidth => ({
subpath: `sections/layr/error-bad-width-${minDeviceWidth}.xml`,
errors: [
//