From afc99bad57a4abd2556ba16366fb455be90fefc5 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 11:44:07 -0500 Subject: [PATCH 01/10] Add methods to convert an rgba background color to rgb --- HTMLCS.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/HTMLCS.js b/HTMLCS.js index 16b1a930..6f7d5db6 100644 --- a/HTMLCS.js +++ b/HTMLCS.js @@ -843,6 +843,49 @@ var HTMLCS = new function() return lum; } + /** + * Convert an rgba background to rgb, by traversing the dom and mixing colors as needed. + * + * @param element + * @returns {Object|*} + */ + this.rgbaBackgroundToRgb = function(element) { + var bgColour = this.style(element).backgroundColor; + var parent = element.parentNode; + var currentColour = this.colourStrToRGB(bgColour); + + while (currentColour.alpha != 1) { + if ((!parent) || (!parent.ownerDocument)) { + break; + } + + var parentStyle = this.style(parent); + var parentColourStr = parentStyle.backgroundColor; + var parentColour = this.colourStrToRGB(parentColourStr); + + if ((parentColourStr === 'transparent') || (parentColourStr === 'rgba(0, 0, 0, 0)')) { + //Skip totally transparent parents until we find a solid color. + parent = parent.parentNode; + continue; + } + + currentColour = this.mixColours(parentColour, currentColour); + + parent = parent.parentNode; + } + + return currentColour; + } + + this.mixColours = function(bg, fg) { + return { + red: Math.round(fg.alpha * (fg.red*255) + (1 - fg.alpha) * (bg.red*255)), + green: Math.round(fg.alpha * (fg.green*255) + (1 - fg.alpha) * (bg.green*255)), + blue: Math.round(fg.alpha * (fg.blue*255) + (1 - fg.alpha) * (bg.blue*255)), + alpha: bg.alpha + } + } + /** * Convert a colour string to a structure with red/green/blue elements. * @@ -863,7 +906,11 @@ var HTMLCS = new function() colour = { red: (matches[1] / 255), green: (matches[2] / 255), - blue: (matches[3] / 255) + blue: (matches[3] / 255), + alpha: 1 + } + if (matches[4]) { + colour.alpha = parseFloat(/^,\s*(.*)$/.exec(matches[4])[1]); } } else { // Hex digit format. @@ -878,7 +925,8 @@ var HTMLCS = new function() colour = { red: (parseInt(colour.substr(0, 2), 16) / 255), green: (parseInt(colour.substr(2, 2), 16) / 255), - blue: (parseInt(colour.substr(4, 2), 16) / 255) + blue: (parseInt(colour.substr(4, 2), 16) / 255), + alpha: 1 }; } From b3d28fc0a8ab6347d85cf2f52e6da937aba4b068 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 11:45:00 -0500 Subject: [PATCH 02/10] Convert rgba to a solid color before checking contrast --- .../Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js index 18f35e32..9dcce664 100644 --- a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js +++ b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js @@ -47,8 +47,9 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { var style = HTMLCS.util.style(node); if (style) { - var bgColour = style.backgroundColor; - var hasBgImg = false; + var bgColour = style.backgroundColor; + var bgElement = node; + var hasBgImg = false; if (style.backgroundImage !== 'none') { hasBgImg = true; @@ -79,6 +80,7 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { var parentStyle = HTMLCS.util.style(parent); var bgColour = parentStyle.backgroundColor; + var bgElement = parent; if (parentStyle.backgroundImage !== 'none') { hasBgImg = true; } @@ -86,6 +88,10 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { parent = parent.parentNode; }//end while + if (bgColour && bgColour.indexOf('rgba') === 0) { + bgColour = HTMLCS.util.rgbaBackgroundToRgb(bgElement); + } + if (hasBgImg === true) { // If we have a background image, skip the contrast ratio checks, // and push a warning instead. From b8d7684f42d7742e02ebfb551cde0740e8ea5110 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 11:45:27 -0500 Subject: [PATCH 03/10] Don't warn about rgba anymore No need to, we are doing the maths! --- Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3.js b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3.js index 3165f5ee..b071aa2e 100644 --- a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3.js +++ b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3.js @@ -73,10 +73,7 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3 = { recommendText = ' Recommendation: change ' + recommendText.join(', ') + '.'; } - if (bgColour && bgColour.indexOf('rgba') === 0) { - code += '.Alpha'; - HTMLCS.addMessage(HTMLCS.WARNING, element, 'This element\'s text is placed on a background that has an alpha transparency. Ensure the contrast ratio between the text and background color are at least ' + required + ':1.', code); - } else if (hasBgImg === true) { + if (hasBgImg === true) { code += '.BgImage'; HTMLCS.addMessage(HTMLCS.WARNING, element, 'This element\'s text is placed on a background image. Ensure the contrast ratio between the text and all covered parts of the image are at least ' + required + ':1.', code); } else { From eaa13096d77fd807a8492ab404fcd6e4ac8a01c4 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 12:12:39 -0500 Subject: [PATCH 04/10] Convert to a colour string The rest of the code expects bgColour to be a string --- .../WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js index 9dcce664..ab09d4b4 100644 --- a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js +++ b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js @@ -89,7 +89,7 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { }//end while if (bgColour && bgColour.indexOf('rgba') === 0) { - bgColour = HTMLCS.util.rgbaBackgroundToRgb(bgElement); + bgColour = HTMLCS.util.RGBtoColourStr(HTMLCS.util.rgbaBackgroundToRgb(bgElement)); } if (hasBgImg === true) { From e2159c572c02ff7da8977ae7fd0bf2a925cb0343 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 12:14:06 -0500 Subject: [PATCH 05/10] Return rgb channels as % Everything else expects them as %. Also, rework to reduce rounding problems. --- HTMLCS.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/HTMLCS.js b/HTMLCS.js index 6f7d5db6..1cf3a8aa 100644 --- a/HTMLCS.js +++ b/HTMLCS.js @@ -878,10 +878,18 @@ var HTMLCS = new function() } this.mixColours = function(bg, fg) { + //Convert colors to int values for mixing. + bg.red = Math.round(bg.red*255); + bg.green = Math.round(bg.green*255); + bg.blue = Math.round(bg.blue*255); + fg.red = Math.round(fg.red*255); + fg.green = Math.round(fg.green*255); + fg.blue = Math.round(fg.blue*255); + return { - red: Math.round(fg.alpha * (fg.red*255) + (1 - fg.alpha) * (bg.red*255)), - green: Math.round(fg.alpha * (fg.green*255) + (1 - fg.alpha) * (bg.green*255)), - blue: Math.round(fg.alpha * (fg.blue*255) + (1 - fg.alpha) * (bg.blue*255)), + red: Math.round(fg.alpha * fg.red + (1 - fg.alpha) * bg.red) / 255, + green: Math.round(fg.alpha * fg.green + (1 - fg.alpha) * bg.green) / 255, + blue: Math.round(fg.alpha * fg.blue + (1 - fg.alpha) * bg.blue) / 255, alpha: bg.alpha } } From e730b35477f31010fb53651a939923371bd2072f Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 12:24:47 -0500 Subject: [PATCH 06/10] Allow checking against any rgba color, not just the background --- HTMLCS.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/HTMLCS.js b/HTMLCS.js index 1cf3a8aa..e037f273 100644 --- a/HTMLCS.js +++ b/HTMLCS.js @@ -844,15 +844,15 @@ var HTMLCS = new function() } /** - * Convert an rgba background to rgb, by traversing the dom and mixing colors as needed. - * - * @param element - * @returns {Object|*} + * Convert an rgba colour to rgb, by traversing the dom and mixing colors as needed. + * + * @param element - the element to compare the rgba color against. + * @param colour - the starting rgba color to check. + * @returns {Colour Object} */ - this.rgbaBackgroundToRgb = function(element) { - var bgColour = this.style(element).backgroundColor; + this.rgbaBackgroundToRgb = function(colour, element) { var parent = element.parentNode; - var currentColour = this.colourStrToRGB(bgColour); + var currentColour = this.colourStrToRGB(colour); while (currentColour.alpha != 1) { if ((!parent) || (!parent.ownerDocument)) { From 36c217f97df319a0b3a90a78533d27fabdd1622f Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 12:25:39 -0500 Subject: [PATCH 07/10] Also convert the foreground (text) rgba color --- .../Principle1/Guideline1_4/1_4_3_Contrast.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js index ab09d4b4..61bea688 100644 --- a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js +++ b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_4/1_4_3_Contrast.js @@ -48,6 +48,7 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { if (style) { var bgColour = style.backgroundColor; + var foreColour = style.color; var bgElement = node; var hasBgImg = false; @@ -89,7 +90,11 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { }//end while if (bgColour && bgColour.indexOf('rgba') === 0) { - bgColour = HTMLCS.util.RGBtoColourStr(HTMLCS.util.rgbaBackgroundToRgb(bgElement)); + bgColour = HTMLCS.util.RGBtoColourStr(HTMLCS.util.rgbaBackgroundToRgb(bgColour, bgElement)); + } + + if (foreColour && foreColour.indexOf('rgba') === 0) { + foreColour = HTMLCS.util.RGBtoColourStr(HTMLCS.util.rgbaBackgroundToRgb(foreColour, node)); } if (hasBgImg === true) { @@ -97,7 +102,7 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { // and push a warning instead. failures.push({ element: node, - colour: style.color, + colour: foreColour, bgColour: undefined, value: undefined, required: reqRatio, @@ -111,12 +116,12 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_4_1_4_3_Contrast = { continue; } - var contrastRatio = HTMLCS.util.contrastRatio(bgColour, style.color); + var contrastRatio = HTMLCS.util.contrastRatio(bgColour, foreColour); if (contrastRatio < reqRatio) { - var recommendation = this.recommendColour(bgColour, style.color, reqRatio); + var recommendation = this.recommendColour(bgColour, foreColour, reqRatio); failures.push({ element: node, From 7ce911b6fc2a9890dbe55c3933eb0a71dc250397 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Fri, 27 Jun 2014 14:37:39 -0500 Subject: [PATCH 08/10] Correctly calculate layered transparencies Take this structure of backgrounds for example, 1 being the closest element: 1. rgba(10,10,10, .5); 2. rgba(10,10,50, .1); 3. rgb(10,10,250); We need to transform background 2 into a solid color before we transform background 1 into a solid colour. --- HTMLCS.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/HTMLCS.js b/HTMLCS.js index e037f273..2d4a6c21 100644 --- a/HTMLCS.js +++ b/HTMLCS.js @@ -852,9 +852,17 @@ var HTMLCS = new function() */ this.rgbaBackgroundToRgb = function(colour, element) { var parent = element.parentNode; - var currentColour = this.colourStrToRGB(colour); + var original = this.colourStrToRGB(colour); + var backgrounds = []; + var solidFound = false; - while (currentColour.alpha != 1) { + if (original.alpha == 1) { + //Return early if it is already solid. + return original; + } + + //Find all the background with transparancy until we get to a solid colour + while (solidFound == false) { if ((!parent) || (!parent.ownerDocument)) { break; } @@ -869,12 +877,22 @@ var HTMLCS = new function() continue; } - currentColour = this.mixColours(parentColour, currentColour); + backgrounds.push(parentColour); + + if (parentColour.alpha == 1) { + solidFound = true; + } parent = parent.parentNode; } - return currentColour; + //Now we need to start with the solid color that we found, and work our way up to the original color. + var solidColour = backgrounds.pop(); + while (backgrounds.length) { + solidColour = this.mixColours(solidColour, backgrounds.pop()); + } + + return this.mixColours(solidColour, original); } this.mixColours = function(bg, fg) { From 203031f5a224d1fee5ea95233e9fef9000614b10 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Wed, 9 Jul 2014 09:17:06 -0500 Subject: [PATCH 09/10] Assume a white background if no solid colors were found --- HTMLCS.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HTMLCS.js b/HTMLCS.js index 2d4a6c21..6aa3c100 100644 --- a/HTMLCS.js +++ b/HTMLCS.js @@ -864,6 +864,12 @@ var HTMLCS = new function() //Find all the background with transparancy until we get to a solid colour while (solidFound == false) { if ((!parent) || (!parent.ownerDocument)) { + //No parent was found, assume a solid white background. + backgrounds.push({ + red: 1, + green: 1, + blue: 1 + }); break; } From 338cd1d0d7d074509eac5098363a9a767e8bcc85 Mon Sep 17 00:00:00 2001 From: Michael Fairchild Date: Wed, 9 Jul 2014 09:19:30 -0500 Subject: [PATCH 10/10] Include an alpha --- HTMLCS.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HTMLCS.js b/HTMLCS.js index 6aa3c100..117e62ca 100644 --- a/HTMLCS.js +++ b/HTMLCS.js @@ -868,7 +868,8 @@ var HTMLCS = new function() backgrounds.push({ red: 1, green: 1, - blue: 1 + blue: 1, + alpha: 1 }); break; }