govuk_publishing_components 21.16.3 → 21.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/component_guide/accessibility-test.js +0 -6
- data/app/views/govuk_publishing_components/components/docs/machine_readable_metadata.yml +7 -0
- data/lib/govuk_publishing_components/presenters/machine_readable/dataset_schema.rb +34 -0
- data/lib/govuk_publishing_components/presenters/schema_org.rb +3 -0
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/axe-core/CHANGELOG.md +166 -2
- data/node_modules/axe-core/CONTRIBUTING.md +5 -5
- data/node_modules/axe-core/README.md +4 -4
- data/node_modules/axe-core/axe.d.ts +27 -11
- data/node_modules/axe-core/axe.js +9597 -2431
- data/node_modules/axe-core/axe.min.js +2 -2
- data/node_modules/axe-core/bower.json +1 -1
- data/node_modules/axe-core/doc/API.md +211 -128
- data/node_modules/axe-core/doc/accessibility-supported.md +1 -1
- data/node_modules/axe-core/doc/aria-supported.md +4 -13
- data/node_modules/axe-core/doc/backwards-compatibility-doc.md +93 -0
- data/node_modules/axe-core/doc/code-submission-guidelines.md +4 -4
- data/node_modules/axe-core/doc/developer-guide.md +27 -13
- data/node_modules/axe-core/doc/examples/chrome-debugging-protocol/package.json +5 -2
- data/node_modules/axe-core/doc/examples/jasmine/README.md +3 -5
- data/node_modules/axe-core/doc/examples/jasmine/karma.conf.js +29 -0
- data/node_modules/axe-core/doc/examples/jasmine/package.json +6 -5
- data/node_modules/axe-core/doc/examples/jest_react/README.md +1 -1
- data/node_modules/axe-core/doc/examples/jest_react/link.test.js +3 -3
- data/node_modules/axe-core/doc/examples/jest_react/package.json +9 -9
- data/node_modules/axe-core/doc/examples/jsdom/package.json +15 -0
- data/node_modules/axe-core/doc/examples/mocha/README.md +5 -7
- data/node_modules/axe-core/doc/examples/mocha/karma.conf.js +29 -0
- data/node_modules/axe-core/doc/examples/mocha/package.json +7 -6
- data/node_modules/axe-core/doc/examples/phantomjs/README.md +3 -3
- data/node_modules/axe-core/doc/examples/phantomjs/axe-phantom.js +4 -2
- data/node_modules/axe-core/doc/examples/phantomjs/package.json +3 -3
- data/node_modules/axe-core/doc/examples/puppeteer/package.json +5 -2
- data/node_modules/axe-core/doc/examples/qunit/README.md +2 -2
- data/node_modules/axe-core/doc/examples/qunit/package.json +2 -2
- data/node_modules/axe-core/doc/examples/test-examples.js +32 -0
- data/node_modules/axe-core/doc/plugins.md +10 -10
- data/node_modules/axe-core/doc/projects.md +12 -8
- data/node_modules/axe-core/doc/rule-descriptions.md +87 -79
- data/node_modules/axe-core/doc/rule-development.md +30 -2
- data/node_modules/axe-core/lib/checks/aria/allowed-attr.js +1 -1
- data/node_modules/axe-core/lib/checks/aria/aria-roledescription.js +14 -0
- data/node_modules/axe-core/lib/checks/aria/aria-roledescription.json +23 -0
- data/node_modules/axe-core/lib/checks/aria/no-implicit-explicit-label.js +21 -0
- data/node_modules/axe-core/lib/checks/aria/no-implicit-explicit-label.json +11 -0
- data/node_modules/axe-core/lib/checks/aria/required-attr.js +32 -7
- data/node_modules/axe-core/lib/checks/aria/required-children.js +40 -14
- data/node_modules/axe-core/lib/checks/aria/required-children.json +12 -1
- data/node_modules/axe-core/lib/checks/aria/required-parent.js +1 -1
- data/node_modules/axe-core/lib/checks/aria/unsupportedattr.js +1 -1
- data/node_modules/axe-core/lib/checks/aria/valid-attr-value.js +50 -17
- data/node_modules/axe-core/lib/checks/aria/valid-attr-value.json +2 -1
- data/node_modules/axe-core/lib/checks/aria/valid-attr.js +1 -1
- data/node_modules/axe-core/lib/checks/color/color-contrast.js +2 -2
- data/node_modules/axe-core/lib/checks/forms/autocomplete-appropriate.js +4 -4
- data/node_modules/axe-core/lib/checks/forms/autocomplete-valid.js +1 -1
- data/node_modules/axe-core/lib/checks/forms/fieldset.json +1 -0
- data/node_modules/axe-core/lib/checks/forms/group-labelledby.json +1 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-content.js +16 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-content.json +11 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-element.js +12 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-element.json +11 -0
- data/node_modules/axe-core/lib/checks/keyboard/tabindex.js +6 -1
- data/node_modules/axe-core/lib/checks/label/alt-space-value.js +3 -2
- data/node_modules/axe-core/lib/checks/label/duplicate-img-label.js +18 -15
- data/node_modules/axe-core/lib/checks/label/label-content-name-mismatch.js +13 -3
- data/node_modules/axe-core/lib/checks/label/label-content-name-mismatch.json +4 -0
- data/node_modules/axe-core/lib/checks/label/multiple-label.js +22 -12
- data/node_modules/axe-core/lib/checks/label/multiple-label.json +1 -1
- data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique-after.js +23 -0
- data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique.js +7 -0
- data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique.json +12 -0
- data/node_modules/axe-core/lib/checks/lists/listitem.js +1 -0
- data/node_modules/axe-core/lib/checks/lists/listitem.json +1 -1
- data/node_modules/axe-core/lib/checks/lists/only-listitems.js +0 -4
- data/node_modules/axe-core/lib/checks/mobile/css-orientation-lock.js +8 -6
- data/node_modules/axe-core/lib/checks/navigation/region.js +2 -19
- data/node_modules/axe-core/lib/checks/shared/avoid-inline-spacing.js +18 -0
- data/node_modules/axe-core/lib/checks/shared/avoid-inline-spacing.json +11 -0
- data/node_modules/axe-core/lib/checks/shared/exists.js +1 -1
- data/node_modules/axe-core/lib/checks/shared/exists.json +1 -1
- data/node_modules/axe-core/lib/checks/shared/has-alt.js +6 -4
- data/node_modules/axe-core/lib/checks/shared/non-empty-alt.js +1 -1
- data/node_modules/axe-core/lib/checks/tables/caption-faked.json +1 -1
- data/node_modules/axe-core/lib/checks/tables/td-has-header.js +5 -4
- data/node_modules/axe-core/lib/checks/tables/th-has-data-cells.js +19 -29
- data/node_modules/axe-core/lib/commons/aria/get-owned-virtual.js +1 -1
- data/node_modules/axe-core/lib/commons/aria/index.js +50 -46
- data/node_modules/axe-core/lib/commons/aria/is-accessible-ref.js +41 -37
- data/node_modules/axe-core/lib/commons/aria/label-virtual.js +2 -2
- data/node_modules/axe-core/lib/commons/aria/roles.js +1 -1
- data/node_modules/axe-core/lib/commons/aria/validate-attr-value.js +0 -1
- data/node_modules/axe-core/lib/commons/color/center-point-of-rect.js +30 -0
- data/node_modules/axe-core/lib/commons/color/contrast.js +7 -1
- data/node_modules/axe-core/lib/commons/color/element-has-image.js +36 -0
- data/node_modules/axe-core/lib/commons/color/get-background-color.js +332 -306
- data/node_modules/axe-core/lib/commons/color/get-foreground-color.js +35 -6
- data/node_modules/axe-core/lib/commons/color/get-own-background-color.js +22 -0
- data/node_modules/axe-core/lib/commons/dom/find-up.js +5 -5
- data/node_modules/axe-core/lib/commons/dom/get-scroll-offset.js +0 -1
- data/node_modules/axe-core/lib/commons/dom/get-tabbable-elements.js +1 -1
- data/node_modules/axe-core/lib/commons/dom/has-content-virtual.js +7 -5
- data/node_modules/axe-core/lib/commons/dom/is-focusable.js +8 -5
- data/node_modules/axe-core/lib/commons/dom/is-hidden-with-css.js +15 -2
- data/node_modules/axe-core/lib/commons/dom/is-in-text-block.js +1 -2
- data/node_modules/axe-core/lib/commons/dom/is-skip-link.js +45 -0
- data/node_modules/axe-core/lib/commons/dom/is-visible.js +43 -17
- data/node_modules/axe-core/lib/commons/dom/is-visual-content.js +0 -1
- data/node_modules/axe-core/lib/commons/dom/visually-contains.js +0 -1
- data/node_modules/axe-core/lib/commons/dom/visually-overlaps.js +0 -1
- data/node_modules/axe-core/lib/commons/forms/index.js +8 -0
- data/node_modules/axe-core/lib/commons/forms/is-aria-combobox.js +13 -0
- data/node_modules/axe-core/lib/commons/forms/is-aria-listbox.js +13 -0
- data/node_modules/axe-core/lib/commons/forms/is-aria-range.js +14 -0
- data/node_modules/axe-core/lib/commons/forms/is-aria-textbox.js +13 -0
- data/node_modules/axe-core/lib/commons/forms/is-native-select.js +13 -0
- data/node_modules/axe-core/lib/commons/forms/is-native-textbox.js +28 -0
- data/node_modules/axe-core/lib/commons/table/get-cell-position.js +2 -2
- data/node_modules/axe-core/lib/commons/table/get-headers.js +55 -11
- data/node_modules/axe-core/lib/commons/table/get-scope.js +1 -1
- data/node_modules/axe-core/lib/commons/table/is-data-table.js +0 -1
- data/node_modules/axe-core/lib/commons/table/to-grid.js +2 -2
- data/node_modules/axe-core/lib/commons/text/accessible-text-virtual.js +5 -5
- data/node_modules/axe-core/lib/commons/text/form-control-value.js +18 -30
- data/node_modules/axe-core/lib/commons/text/is-icon-ligature.js +210 -0
- data/node_modules/axe-core/lib/commons/text/is-valid-autocomplete.js +0 -1
- data/node_modules/axe-core/lib/commons/text/label-text.js +1 -1
- data/node_modules/axe-core/lib/commons/text/label-virtual.js +1 -1
- data/node_modules/axe-core/lib/commons/text/native-text-methods.js +1 -1
- data/node_modules/axe-core/lib/commons/text/subtree-text.js +3 -3
- data/node_modules/axe-core/lib/commons/text/unicode.js +15 -0
- data/node_modules/axe-core/lib/commons/text/visible-text-nodes.js +25 -0
- data/node_modules/axe-core/lib/commons/text/visible-virtual.js +1 -1
- data/node_modules/axe-core/lib/core/base/audit.js +90 -15
- data/node_modules/axe-core/lib/core/base/cache.js +33 -0
- data/node_modules/axe-core/lib/core/base/check.js +48 -1
- data/node_modules/axe-core/lib/core/base/context.js +15 -14
- data/node_modules/axe-core/lib/core/base/rule.js +223 -46
- data/node_modules/axe-core/lib/core/base/virtual-node/abstract-virtual-node.js +40 -0
- data/node_modules/axe-core/lib/core/base/virtual-node/serial-virtual-node.js +86 -0
- data/node_modules/axe-core/lib/core/base/virtual-node/virtual-node.js +85 -0
- data/node_modules/axe-core/lib/core/constants.js +10 -2
- data/node_modules/axe-core/lib/core/imports/index.js +28 -3
- data/node_modules/axe-core/lib/core/index.js +2 -4
- data/node_modules/axe-core/lib/core/public/configure.js +28 -1
- data/node_modules/axe-core/lib/core/public/run-rules.js +2 -0
- data/node_modules/axe-core/lib/core/public/run-virtual-rule.js +50 -0
- data/node_modules/axe-core/lib/core/public/run.js +13 -2
- data/node_modules/axe-core/lib/core/reporters/helpers/process-aggregate.js +1 -1
- data/node_modules/axe-core/lib/core/reporters/na.js +4 -0
- data/node_modules/axe-core/lib/core/reporters/raw-env.js +12 -0
- data/node_modules/axe-core/lib/core/reporters/raw.js +25 -1
- data/node_modules/axe-core/lib/core/utils/are-styles-set.js +4 -7
- data/node_modules/axe-core/lib/core/utils/assert.js +12 -0
- data/node_modules/axe-core/lib/core/utils/collect-results-from-frames.js +2 -2
- data/node_modules/axe-core/lib/core/utils/contains.js +27 -13
- data/node_modules/axe-core/lib/core/utils/css-parser.js +2 -0
- data/node_modules/axe-core/lib/core/utils/element-matches.js +5 -1
- data/node_modules/axe-core/lib/core/utils/escape-selector.js +1 -2
- data/node_modules/axe-core/lib/core/utils/flattened-tree.js +47 -61
- data/node_modules/axe-core/lib/core/utils/get-check-option.js +0 -1
- data/node_modules/axe-core/lib/core/utils/get-friendly-uri-end.js +1 -1
- data/node_modules/axe-core/lib/core/utils/get-node-attributes.js +21 -0
- data/node_modules/axe-core/lib/core/utils/get-scroll.js +39 -0
- data/node_modules/axe-core/lib/core/utils/get-selector.js +9 -6
- data/node_modules/axe-core/lib/core/utils/get-stylesheet-factory.js +51 -0
- data/node_modules/axe-core/lib/core/utils/get-xpath.js +0 -1
- data/node_modules/axe-core/lib/core/utils/is-hidden.js +16 -4
- data/node_modules/axe-core/lib/core/utils/is-html-element.js +5 -5
- data/node_modules/axe-core/lib/core/utils/is-shadow-root.js +3 -3
- data/node_modules/axe-core/lib/core/utils/memoize.js +17 -0
- data/node_modules/axe-core/lib/core/utils/parse-crossorigin-stylesheet.js +53 -0
- data/node_modules/axe-core/lib/core/utils/parse-sameorigin-stylesheet.js +96 -0
- data/node_modules/axe-core/lib/core/utils/parse-stylesheet.js +70 -0
- data/node_modules/axe-core/lib/core/utils/performance-timer.js +7 -2
- data/node_modules/axe-core/lib/core/utils/preload-cssom.js +77 -281
- data/node_modules/axe-core/lib/core/utils/preload.js +49 -23
- data/node_modules/axe-core/lib/core/utils/qsa.js +39 -50
- data/node_modules/axe-core/lib/core/utils/respondable.js +20 -3
- data/node_modules/axe-core/lib/core/utils/rule-should-run.js +0 -1
- data/node_modules/axe-core/lib/core/utils/scroll-state.js +12 -25
- data/node_modules/axe-core/lib/core/utils/select.js +12 -23
- data/node_modules/axe-core/lib/core/utils/uuid.js +1 -2
- data/node_modules/axe-core/lib/intro.stub +1 -1
- data/node_modules/axe-core/lib/misc/incomplete-fallback.json +1 -1
- data/node_modules/axe-core/lib/rules/aria-allowed-attr-matches.js +1 -1
- data/node_modules/axe-core/lib/rules/aria-form-field-name-matches.js +50 -0
- data/node_modules/axe-core/lib/rules/aria-has-attr-matches.js +1 -1
- data/node_modules/axe-core/lib/rules/aria-hidden-focus.json +1 -1
- data/node_modules/axe-core/lib/rules/aria-input-field-name.json +13 -0
- data/node_modules/axe-core/lib/rules/aria-roledescription.json +12 -0
- data/node_modules/axe-core/lib/rules/aria-toggle-field-name.json +18 -0
- data/node_modules/axe-core/lib/rules/autocomplete-matches.js +14 -10
- data/node_modules/axe-core/lib/rules/avoid-inline-spacing.json +12 -0
- data/node_modules/axe-core/lib/rules/button-name.json +3 -5
- data/node_modules/axe-core/lib/rules/checkboxgroup.json +2 -1
- data/node_modules/axe-core/lib/rules/color-contrast-matches.js +37 -22
- data/node_modules/axe-core/lib/rules/duplicate-id-active-matches.js +1 -1
- data/node_modules/axe-core/lib/rules/duplicate-id-misc-matches.js +2 -2
- data/node_modules/axe-core/lib/rules/form-field-multiple-labels.json +1 -1
- data/node_modules/axe-core/lib/rules/html-has-lang.json +1 -0
- data/node_modules/axe-core/lib/rules/image-alt.json +1 -1
- data/node_modules/axe-core/lib/rules/img-redundant-alt.json +3 -3
- data/node_modules/axe-core/lib/rules/input-button-name.json +26 -0
- data/node_modules/axe-core/lib/rules/landmark-unique-matches.js +41 -0
- data/node_modules/axe-core/lib/rules/landmark-unique.json +13 -0
- data/node_modules/axe-core/lib/rules/link-name.json +5 -4
- data/node_modules/axe-core/lib/rules/meta-refresh.json +8 -1
- data/node_modules/axe-core/lib/rules/radiogroup.json +2 -1
- data/node_modules/axe-core/lib/rules/role-img-alt.json +18 -0
- data/node_modules/axe-core/lib/rules/scrollable-region-focusable-matches.js +30 -0
- data/node_modules/axe-core/lib/rules/scrollable-region-focusable.json +12 -0
- data/node_modules/axe-core/lib/rules/skip-link-matches.js +1 -1
- data/node_modules/axe-core/lib/rules/skip-link.json +1 -1
- data/node_modules/axe-core/lib/rules/video-description.json +3 -1
- data/node_modules/axe-core/locales/de.json +1 -5
- data/node_modules/axe-core/locales/es.json +773 -0
- data/node_modules/axe-core/locales/fr.json +15 -19
- data/node_modules/axe-core/locales/ja.json +65 -11
- data/node_modules/axe-core/locales/ko.json +777 -0
- data/node_modules/axe-core/locales/nl.json +35 -35
- data/node_modules/axe-core/locales/pt_BR.json +773 -0
- data/node_modules/axe-core/package.json +56 -52
- data/node_modules/axe-core/sri-history.json +20 -0
- data/node_modules/axe-core/typings/axe-core/axe-core-tests.ts +5 -15
- data/node_modules/govuk-frontend/package.json +10 -10
- metadata +62 -4
- data/node_modules/axe-core/doc/axelogo2018.png +0 -0
- data/node_modules/axe-core/lib/rules/role-not-button-matches.js +0 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* global forms */
|
|
2
|
+
const rangeRoles = ['progressbar', 'scrollbar', 'slider', 'spinbutton'];
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Determines if an element is an aria range element
|
|
6
|
+
* @method isAriaRange
|
|
7
|
+
* @memberof axe.commons.forms
|
|
8
|
+
* @param {Element} node Node to determine if aria range
|
|
9
|
+
* @returns {Bool}
|
|
10
|
+
*/
|
|
11
|
+
forms.isAriaRange = function(node) {
|
|
12
|
+
const role = axe.commons.aria.getRole(node, { noImplicit: true });
|
|
13
|
+
return rangeRoles.includes(role);
|
|
14
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* global forms */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if an element is an aria textbox element
|
|
5
|
+
* @method isAriaTextbox
|
|
6
|
+
* @memberof axe.commons.forms
|
|
7
|
+
* @param {Element} node Node to determine if aria textbox
|
|
8
|
+
* @returns {Bool}
|
|
9
|
+
*/
|
|
10
|
+
forms.isAriaTextbox = function(node) {
|
|
11
|
+
const role = axe.commons.aria.getRole(node, { noImplicit: true });
|
|
12
|
+
return role === 'textbox';
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* global forms */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if an element is a native select element
|
|
5
|
+
* @method isNativeSelect
|
|
6
|
+
* @memberof axe.commons.forms
|
|
7
|
+
* @param {Element} node Node to determine if select
|
|
8
|
+
* @returns {Bool}
|
|
9
|
+
*/
|
|
10
|
+
forms.isNativeSelect = function(node) {
|
|
11
|
+
const nodeName = node.nodeName.toUpperCase();
|
|
12
|
+
return nodeName === 'SELECT';
|
|
13
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* global forms */
|
|
2
|
+
const nonTextInputTypes = [
|
|
3
|
+
'button',
|
|
4
|
+
'checkbox',
|
|
5
|
+
'color',
|
|
6
|
+
'file',
|
|
7
|
+
'hidden',
|
|
8
|
+
'image',
|
|
9
|
+
'password',
|
|
10
|
+
'radio',
|
|
11
|
+
'reset',
|
|
12
|
+
'submit'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Determines if an element is a native textbox element
|
|
17
|
+
* @method isNativeTextbox
|
|
18
|
+
* @memberof axe.commons.forms
|
|
19
|
+
* @param {Element} node Node to determine if textbox
|
|
20
|
+
* @returns {Bool}
|
|
21
|
+
*/
|
|
22
|
+
forms.isNativeTextbox = function(node) {
|
|
23
|
+
const nodeName = node.nodeName.toUpperCase();
|
|
24
|
+
return (
|
|
25
|
+
nodeName === 'TEXTAREA' ||
|
|
26
|
+
(nodeName === 'INPUT' && !nonTextInputTypes.includes(node.type))
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @param {HTMLTableCellElement} cell The table cell of which to get the position
|
|
9
9
|
* @return {Object} Object with `x` and `y` properties of the coordinates
|
|
10
10
|
*/
|
|
11
|
-
table.getCellPosition = function(cell, tableGrid) {
|
|
11
|
+
table.getCellPosition = axe.utils.memoize(function(cell, tableGrid) {
|
|
12
12
|
var rowIndex, index;
|
|
13
13
|
if (!tableGrid) {
|
|
14
14
|
tableGrid = table.toGrid(dom.findUp(cell, 'table'));
|
|
@@ -25,4 +25,4 @@ table.getCellPosition = function(cell, tableGrid) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
};
|
|
28
|
+
});
|
|
@@ -1,29 +1,73 @@
|
|
|
1
1
|
/* global table */
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Loop through the table grid looking for headers and caching the result.
|
|
5
|
+
* @param {String} headerType The type of header to look for ("row" or "col")
|
|
6
|
+
* @param {Object} position The position of the cell to start looking
|
|
7
|
+
* @param {Array} tablegrid A matrix of the table obtained using axe.commons.table.toGrid
|
|
8
|
+
* @return {Array<HTMLTableCellElement>} Array of HTMLTableCellElements that are headers
|
|
9
|
+
*/
|
|
10
|
+
function traverseForHeaders(headerType, position, tableGrid) {
|
|
11
|
+
const property = headerType === 'row' ? '_rowHeaders' : '_colHeaders';
|
|
12
|
+
const predicate =
|
|
13
|
+
headerType === 'row' ? table.isRowHeader : table.isColumnHeader;
|
|
14
|
+
const rowEnd = headerType === 'row' ? position.y : 0;
|
|
15
|
+
const colEnd = headerType === 'row' ? 0 : position.x;
|
|
16
|
+
|
|
17
|
+
let headers;
|
|
18
|
+
const cells = [];
|
|
19
|
+
for (let row = position.y; row >= rowEnd && !headers; row--) {
|
|
20
|
+
for (let col = position.x; col >= colEnd; col--) {
|
|
21
|
+
const cell = tableGrid[row] ? tableGrid[row][col] : undefined;
|
|
22
|
+
|
|
23
|
+
if (!cell) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// stop traversing once we've found a cache
|
|
28
|
+
const vNode = axe.utils.getNodeFromTree(cell);
|
|
29
|
+
if (vNode[property]) {
|
|
30
|
+
headers = vNode[property];
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
cells.push(cell);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// need to check that the cells we've traversed are headers
|
|
39
|
+
headers = (headers || []).concat(cells.filter(predicate));
|
|
40
|
+
|
|
41
|
+
// cache results
|
|
42
|
+
cells.forEach(tableCell => {
|
|
43
|
+
const vNode = axe.utils.getNodeFromTree(tableCell);
|
|
44
|
+
vNode[property] = headers;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return headers;
|
|
48
|
+
}
|
|
49
|
+
|
|
3
50
|
/**
|
|
4
51
|
* Get any associated table headers for a `HTMLTableCellElement`
|
|
5
52
|
* @method getHeaders
|
|
6
53
|
* @memberof axe.commons.table
|
|
7
54
|
* @instance
|
|
8
55
|
* @param {HTMLTableCellElement} cell The cell of which to get headers
|
|
56
|
+
* @param {Array} [tablegrid] A matrix of the table obtained using axe.commons.table.toGrid
|
|
9
57
|
* @return {Array<HTMLTableCellElement>} Array of headers associated to the table cell
|
|
10
58
|
*/
|
|
11
|
-
table.getHeaders = function(cell) {
|
|
59
|
+
table.getHeaders = function(cell, tableGrid) {
|
|
12
60
|
if (cell.hasAttribute('headers')) {
|
|
13
61
|
return commons.dom.idrefs(cell, 'headers');
|
|
14
62
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
63
|
+
if (!tableGrid) {
|
|
64
|
+
tableGrid = commons.table.toGrid(commons.dom.findUp(cell, 'table'));
|
|
65
|
+
}
|
|
66
|
+
const position = commons.table.getCellPosition(cell, tableGrid);
|
|
18
67
|
|
|
19
68
|
// TODO: RTL text
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.filter(cell => table.isRowHeader(cell));
|
|
23
|
-
|
|
24
|
-
var colHeaders = table
|
|
25
|
-
.traverse('up', position, tableGrid)
|
|
26
|
-
.filter(cell => table.isColumnHeader(cell));
|
|
69
|
+
const rowHeaders = traverseForHeaders('row', position, tableGrid);
|
|
70
|
+
const colHeaders = traverseForHeaders('col', position, tableGrid);
|
|
27
71
|
|
|
28
72
|
return [].concat(rowHeaders, colHeaders).reverse();
|
|
29
73
|
};
|
|
@@ -29,7 +29,7 @@ table.getScope = function(cell) {
|
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
31
|
var tableGrid = table.toGrid(dom.findUp(cell, 'table'));
|
|
32
|
-
var pos = table.getCellPosition(cell);
|
|
32
|
+
var pos = table.getCellPosition(cell, tableGrid);
|
|
33
33
|
|
|
34
34
|
// The element is in a row with all th elements, that makes it a column header
|
|
35
35
|
var headerRow = tableGrid[pos.y].reduce((headerRow, cell) => {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @param {HTMLTableElement} node The table to convert
|
|
9
9
|
* @return {Array<HTMLTableCellElement>} Array of HTMLTableCellElements
|
|
10
10
|
*/
|
|
11
|
-
table.toGrid = function(node) {
|
|
11
|
+
table.toGrid = axe.utils.memoize(function(node) {
|
|
12
12
|
var table = [];
|
|
13
13
|
var rows = node.rows;
|
|
14
14
|
for (var i = 0, rowLength = rows.length; i < rowLength; i++) {
|
|
@@ -32,7 +32,7 @@ table.toGrid = function(node) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
return table;
|
|
35
|
-
};
|
|
35
|
+
});
|
|
36
36
|
|
|
37
37
|
// This was the old name
|
|
38
38
|
table.toArray = table.toGrid;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @return {string}
|
|
12
12
|
*/
|
|
13
13
|
text.accessibleText = function accessibleText(element, context) {
|
|
14
|
-
|
|
14
|
+
const virtualNode = axe.utils.getNodeFromTree(element); // throws an exception on purpose if axe._tree not correct
|
|
15
15
|
return text.accessibleTextVirtual(virtualNode, context);
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -49,6 +49,10 @@ text.accessibleTextVirtual = function accessibleTextVirtual(
|
|
|
49
49
|
|
|
50
50
|
// Find the first step that returns a non-empty string
|
|
51
51
|
let accName = computationSteps.reduce((accName, step) => {
|
|
52
|
+
if (context.startNode === virtualNode) {
|
|
53
|
+
accName = text.sanitize(accName);
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
if (accName !== '') {
|
|
53
57
|
// yes, whitespace only a11y names halt the algorithm
|
|
54
58
|
return accName;
|
|
@@ -56,10 +60,6 @@ text.accessibleTextVirtual = function accessibleTextVirtual(
|
|
|
56
60
|
return step(virtualNode, context);
|
|
57
61
|
}, '');
|
|
58
62
|
|
|
59
|
-
if (context.startNode === virtualNode) {
|
|
60
|
-
accName = text.sanitize(accName);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
63
|
if (context.debug) {
|
|
64
64
|
axe.log(accName || '{empty-value}', actualNode, context);
|
|
65
65
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
/* global text, aria, dom */
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const controlValueRoles = [
|
|
3
|
+
'textbox',
|
|
4
|
+
'progressbar',
|
|
5
|
+
'scrollbar',
|
|
6
|
+
'slider',
|
|
7
|
+
'spinbutton',
|
|
8
|
+
'combobox',
|
|
9
|
+
'listbox'
|
|
10
|
+
];
|
|
5
11
|
|
|
6
12
|
text.formControlValueMethods = {
|
|
7
13
|
nativeTextboxValue,
|
|
@@ -62,24 +68,7 @@ text.formControlValue = function formControlValue(virtualNode, context = {}) {
|
|
|
62
68
|
*/
|
|
63
69
|
function nativeTextboxValue(node) {
|
|
64
70
|
node = node.actualNode || node;
|
|
65
|
-
|
|
66
|
-
'button',
|
|
67
|
-
'checkbox',
|
|
68
|
-
'file',
|
|
69
|
-
'hidden',
|
|
70
|
-
'image',
|
|
71
|
-
'password',
|
|
72
|
-
'radio',
|
|
73
|
-
'reset',
|
|
74
|
-
'submit',
|
|
75
|
-
'color'
|
|
76
|
-
];
|
|
77
|
-
const nodeName = node.nodeName.toUpperCase();
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
nodeName === 'TEXTAREA' ||
|
|
81
|
-
(nodeName === 'INPUT' && !nonTextInputTypes.includes(node.type))
|
|
82
|
-
) {
|
|
71
|
+
if (axe.commons.forms.isNativeTextbox(node)) {
|
|
83
72
|
return node.value || '';
|
|
84
73
|
}
|
|
85
74
|
return '';
|
|
@@ -93,7 +82,7 @@ function nativeTextboxValue(node) {
|
|
|
93
82
|
*/
|
|
94
83
|
function nativeSelectValue(node) {
|
|
95
84
|
node = node.actualNode || node;
|
|
96
|
-
if (
|
|
85
|
+
if (!axe.commons.forms.isNativeSelect(node)) {
|
|
97
86
|
return '';
|
|
98
87
|
}
|
|
99
88
|
return (
|
|
@@ -112,8 +101,7 @@ function nativeSelectValue(node) {
|
|
|
112
101
|
*/
|
|
113
102
|
function ariaTextboxValue(virtualNode) {
|
|
114
103
|
const { actualNode } = virtualNode;
|
|
115
|
-
|
|
116
|
-
if (role !== 'textbox') {
|
|
104
|
+
if (!axe.commons.forms.isAriaTextbox(actualNode)) {
|
|
117
105
|
return '';
|
|
118
106
|
}
|
|
119
107
|
if (!dom.isHiddenWithCSS(actualNode)) {
|
|
@@ -135,8 +123,7 @@ function ariaTextboxValue(virtualNode) {
|
|
|
135
123
|
*/
|
|
136
124
|
function ariaListboxValue(virtualNode, context) {
|
|
137
125
|
const { actualNode } = virtualNode;
|
|
138
|
-
|
|
139
|
-
if (role !== 'listbox') {
|
|
126
|
+
if (!axe.commons.forms.isAriaListbox(actualNode)) {
|
|
140
127
|
return '';
|
|
141
128
|
}
|
|
142
129
|
|
|
@@ -169,11 +156,10 @@ function ariaListboxValue(virtualNode, context) {
|
|
|
169
156
|
*/
|
|
170
157
|
function ariaComboboxValue(virtualNode, context) {
|
|
171
158
|
const { actualNode } = virtualNode;
|
|
172
|
-
const role = aria.getRole(actualNode, { noImplicit: true });
|
|
173
159
|
let listbox;
|
|
174
160
|
|
|
175
161
|
// For combobox, find the first owned listbox:
|
|
176
|
-
if (!
|
|
162
|
+
if (!axe.commons.forms.isAriaCombobox(actualNode)) {
|
|
177
163
|
return '';
|
|
178
164
|
}
|
|
179
165
|
listbox = aria
|
|
@@ -193,8 +179,10 @@ function ariaComboboxValue(virtualNode, context) {
|
|
|
193
179
|
*/
|
|
194
180
|
function ariaRangeValue(node) {
|
|
195
181
|
node = node.actualNode || node;
|
|
196
|
-
|
|
197
|
-
|
|
182
|
+
if (
|
|
183
|
+
!axe.commons.forms.isAriaRange(node) ||
|
|
184
|
+
!node.hasAttribute('aria-valuenow')
|
|
185
|
+
) {
|
|
198
186
|
return '';
|
|
199
187
|
}
|
|
200
188
|
// Validate the number, if not, return 0.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* global text */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if a given text node is an icon ligature
|
|
5
|
+
*
|
|
6
|
+
* @method isIconLigature
|
|
7
|
+
* @memberof axe.commons.text
|
|
8
|
+
* @instance
|
|
9
|
+
* @param {VirtualNode} textVNode The virtual text node
|
|
10
|
+
* @param {Number} occuranceThreshold Number of times the font is encountered before auto-assigning the font as a ligature or not
|
|
11
|
+
* @param {Number} differenceThreshold Percent of differences in pixel data or pixel width needed to determine if a font is a ligature font
|
|
12
|
+
* @return {Boolean}
|
|
13
|
+
*/
|
|
14
|
+
text.isIconLigature = function(
|
|
15
|
+
textVNode,
|
|
16
|
+
differenceThreshold = 0.15,
|
|
17
|
+
occuranceThreshold = 3
|
|
18
|
+
) {
|
|
19
|
+
/**
|
|
20
|
+
* Determine if the visible text is a ligature by comparing the
|
|
21
|
+
* first letters image data to the entire strings image data.
|
|
22
|
+
* If the two images are significantly different (typical set to 5%
|
|
23
|
+
* statistical significance, but we'll be using a slightly higher value
|
|
24
|
+
* of 15% to help keep the size of the canvas down) then we know the text
|
|
25
|
+
* has been replaced by a ligature.
|
|
26
|
+
*
|
|
27
|
+
* Example:
|
|
28
|
+
* If a text node was the string "File", looking at just the first
|
|
29
|
+
* letter "F" would produce the following image:
|
|
30
|
+
*
|
|
31
|
+
* ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
|
|
32
|
+
* │ │ │█│█│█│█│█│█│█│█│█│█│█│ │ │
|
|
33
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
34
|
+
* │ │ │█│█│█│█│█│█│█│█│█│█│█│ │ │
|
|
35
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
36
|
+
* │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
|
|
37
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
38
|
+
* │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
|
|
39
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
40
|
+
* │ │ │█│█│█│█│█│█│█│ │ │ │ │ │ │
|
|
41
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
42
|
+
* │ │ │█│█│█│█│█│█│█│ │ │ │ │ │ │
|
|
43
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
44
|
+
* │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
|
|
45
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
46
|
+
* │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
|
|
47
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
48
|
+
* │ │ │█│█│ │ │ │ │ │ │ │ │ │ │ │
|
|
49
|
+
* └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
|
|
50
|
+
*
|
|
51
|
+
* But if the entire string "File" produced an image which had at least
|
|
52
|
+
* a 15% difference in pixels, we would know that the string was replaced
|
|
53
|
+
* by a ligature:
|
|
54
|
+
*
|
|
55
|
+
* ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
|
|
56
|
+
* │ │█│█│█│█│█│█│█│█│█│█│ │ │ │ │
|
|
57
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
58
|
+
* │ │█│ │ │ │ │ │ │ │ │█│█│ │ │ │
|
|
59
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
60
|
+
* │ │█│ │█│█│█│█│█│█│ │█│ │█│ │ │
|
|
61
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
62
|
+
* │ │█│ │ │ │ │ │ │ │ │█│█│█│█│ │
|
|
63
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
64
|
+
* │ │█│ │█│█│█│█│█│█│ │ │ │ │█│ │
|
|
65
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
66
|
+
* │ │█│ │ │ │ │ │ │ │ │ │ │ │█│ │
|
|
67
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
68
|
+
* │ │█│ │█│█│█│█│█│█│█│█│█│ │█│ │
|
|
69
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
70
|
+
* │ │█│ │ │ │ │ │ │ │ │ │ │ │█│ │
|
|
71
|
+
* ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|
|
72
|
+
* │ │█│█│█│█│█│█│█│█│█│█│█│█│█│ │
|
|
73
|
+
* └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
|
|
74
|
+
*/
|
|
75
|
+
const nodeValue = textVNode.actualNode.nodeValue.trim();
|
|
76
|
+
|
|
77
|
+
// text with unicode or non-bmp letters cannot be ligature icons
|
|
78
|
+
if (
|
|
79
|
+
!text.sanitize(nodeValue) ||
|
|
80
|
+
text.hasUnicode(nodeValue, { emoji: true, nonBmp: true })
|
|
81
|
+
) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!axe._cache.get('canvasContext')) {
|
|
86
|
+
axe._cache.set(
|
|
87
|
+
'canvasContext',
|
|
88
|
+
document.createElement('canvas').getContext('2d')
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const canvasContext = axe._cache.get('canvasContext');
|
|
92
|
+
const canvas = canvasContext.canvas;
|
|
93
|
+
|
|
94
|
+
// keep track of each font encountered and the number of times it shows up
|
|
95
|
+
// as a ligature.
|
|
96
|
+
if (!axe._cache.get('fonts')) {
|
|
97
|
+
axe._cache.set('fonts', {});
|
|
98
|
+
}
|
|
99
|
+
const fonts = axe._cache.get('fonts');
|
|
100
|
+
|
|
101
|
+
const style = window.getComputedStyle(textVNode.parent.actualNode);
|
|
102
|
+
const fontFamily = style.getPropertyValue('font-family');
|
|
103
|
+
|
|
104
|
+
if (!fonts[fontFamily]) {
|
|
105
|
+
fonts[fontFamily] = {
|
|
106
|
+
occurances: 0,
|
|
107
|
+
numLigatures: 0
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const font = fonts[fontFamily];
|
|
111
|
+
|
|
112
|
+
// improve the performance by only comparing the image data of a font
|
|
113
|
+
// a certain number of times
|
|
114
|
+
if (font.occurances >= occuranceThreshold) {
|
|
115
|
+
// if the font has always been a ligature assume it's a ligature font
|
|
116
|
+
if (font.numLigatures / font.occurances === 1) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
// inversely, if it's never been a ligature assume it's not a ligature font
|
|
120
|
+
else if (font.numLigatures === 0) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// we could theoretically get into an odd middle ground scenario in which
|
|
125
|
+
// the font family is being used as normal text sometimes and as icons
|
|
126
|
+
// other times. in these cases we would need to always check the text
|
|
127
|
+
// to know if it's an icon or not
|
|
128
|
+
}
|
|
129
|
+
font.occurances++;
|
|
130
|
+
|
|
131
|
+
// 30px was chosen to account for common ligatures in normal fonts
|
|
132
|
+
// such as fi, ff, ffi. If a ligature would add a single column of
|
|
133
|
+
// pixels to a 30x30 grid, it would not meet the statistical significance
|
|
134
|
+
// threshold of 15% (30x30 = 900; 30/900 = 3.333%). this also allows for
|
|
135
|
+
// more than 1 column differences (60/900 = 6.666%) and things like
|
|
136
|
+
// extending the top of the f in the fi ligature.
|
|
137
|
+
let fontSize = 30;
|
|
138
|
+
let fontStyle = `${fontSize}px ${fontFamily}`;
|
|
139
|
+
|
|
140
|
+
// set the size of the canvas to the width of the first letter
|
|
141
|
+
canvasContext.font = fontStyle;
|
|
142
|
+
const firstChar = nodeValue.charAt(0);
|
|
143
|
+
let width = canvasContext.measureText(firstChar).width;
|
|
144
|
+
|
|
145
|
+
// ensure font meets the 30px width requirement (30px font-size doesn't
|
|
146
|
+
// necessarily mean its 30px wide when drawn)
|
|
147
|
+
if (width < 30) {
|
|
148
|
+
const diff = 30 / width;
|
|
149
|
+
width *= diff;
|
|
150
|
+
fontSize *= diff;
|
|
151
|
+
fontStyle = `${fontSize}px ${fontFamily}`;
|
|
152
|
+
}
|
|
153
|
+
canvas.width = width;
|
|
154
|
+
canvas.height = fontSize;
|
|
155
|
+
|
|
156
|
+
// changing the dimensions of a canvas resets all properties (include font)
|
|
157
|
+
// and clears it
|
|
158
|
+
canvasContext.font = fontStyle;
|
|
159
|
+
canvasContext.textAlign = 'left';
|
|
160
|
+
canvasContext.textBaseline = 'top';
|
|
161
|
+
canvasContext.fillText(firstChar, 0, 0);
|
|
162
|
+
const compareData = new Uint32Array(
|
|
163
|
+
canvasContext.getImageData(0, 0, width, fontSize).data.buffer
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// if the font doesn't even have character data for a single char then
|
|
167
|
+
// it has to be an icon ligature (e.g. Material Icon)
|
|
168
|
+
if (!compareData.some(pixel => pixel)) {
|
|
169
|
+
font.numLigatures++;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
canvasContext.clearRect(0, 0, width, fontSize);
|
|
174
|
+
canvasContext.fillText(nodeValue, 0, 0);
|
|
175
|
+
const compareWith = new Uint32Array(
|
|
176
|
+
canvasContext.getImageData(0, 0, width, fontSize).data.buffer
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// calculate the number of differences between the first letter and the
|
|
180
|
+
// entire string, ignoring color differences
|
|
181
|
+
const differences = compareData.reduce((diff, pixel, i) => {
|
|
182
|
+
if (pixel === 0 && compareWith[i] === 0) {
|
|
183
|
+
return diff;
|
|
184
|
+
}
|
|
185
|
+
if (pixel !== 0 && compareWith[i] !== 0) {
|
|
186
|
+
return diff;
|
|
187
|
+
}
|
|
188
|
+
return ++diff;
|
|
189
|
+
}, 0);
|
|
190
|
+
|
|
191
|
+
// calculate the difference between the width of each character and the
|
|
192
|
+
// combined with of all characters
|
|
193
|
+
const expectedWidth = nodeValue.split('').reduce((width, char) => {
|
|
194
|
+
return width + canvasContext.measureText(char).width;
|
|
195
|
+
}, 0);
|
|
196
|
+
const actualWidth = canvasContext.measureText(nodeValue).width;
|
|
197
|
+
|
|
198
|
+
const pixelDifference = differences / compareData.length;
|
|
199
|
+
const sizeDifference = 1 - actualWidth / expectedWidth;
|
|
200
|
+
|
|
201
|
+
if (
|
|
202
|
+
pixelDifference >= differenceThreshold &&
|
|
203
|
+
sizeDifference >= differenceThreshold
|
|
204
|
+
) {
|
|
205
|
+
font.numLigatures++;
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return false;
|
|
210
|
+
};
|