-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathImageWithTextOverlayRule.kt
More file actions
57 lines (46 loc) · 2.3 KB
/
Copy pathImageWithTextOverlayRule.kt
File metadata and controls
57 lines (46 loc) · 2.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.composea11yscanner.rules
import com.composea11yscanner.core.model.A11yIssue
import com.composea11yscanner.core.model.A11yNode
import com.composea11yscanner.core.model.A11ySeverity
import com.composea11yscanner.core.rule.BaseScanRule
/**
* @param overlapThreshold Fraction of the text node's area that must intersect an image
* node before the pair is flagged (default 0.5 = 50%).
*/
class ImageWithTextOverlayRule(
private val overlapThreshold: Float = 0.5f,
) : BaseScanRule() {
override val ruleId = "image-text-overlay"
override val ruleName = "Image With Text Overlay"
override val severity = A11ySeverity.Warning
override val wcagReference = "WCAG 1.4.3 Contrast Minimum (Level AA)"
override fun evaluateAll(nodes: List<A11yNode>): List<A11yIssue> {
val textNodes = nodes.filter { it.composableName.contains("Text", ignoreCase = true) }
val imageNodes = nodes.filter { it.composableName.contains("Image", ignoreCase = true) }
return textNodes.mapNotNull { textNode ->
val overlaps = imageNodes.any { imageNode ->
overlapRatio(textNode.bounds, imageNode.bounds) > overlapThreshold
}
if (!overlaps) return@mapNotNull null
issue(
node = textNode,
message = "Text rendered over image may fail contrast requirements on different images.",
howToFix = "Add a semi-transparent scrim or solid background behind the text " +
"(e.g. Modifier.background(Color.Black.copy(alpha = 0.5f))), or verify " +
"the image always provides sufficient contrast (4.5:1 normal, 3:1 large text).",
)
}
}
private fun overlapRatio(text: Rect, image: Rect): Float {
val intLeft = maxOf(text.left, image.left)
val intTop = maxOf(text.top, image.top)
val intRight = minOf(text.right, image.right)
val intBottom = minOf(text.bottom, image.bottom)
if (intRight <= intLeft || intBottom <= intTop) return 0f
val textArea = text.width * text.height
if (textArea == 0) return 0f
val intersectionArea = (intRight - intLeft) * (intBottom - intTop)
return intersectionArea.toFloat() / textArea
}
}
private typealias Rect = com.composea11yscanner.core.model.Rect