Skip to content

Commit 5c04267

Browse files
vmcjmeisterT
andcommitted
Move RGB/HEX functions to Utils
And add some more testing for those to also check for #123 format. The functions using the strings have moved from the TwigExtension to utils as the they don't really depend on Twig itself. This makes testing easier and can use this in the future for other elements. Co-authored-by: Tobias Werth <[email protected]>
1 parent d327250 commit 5c04267

File tree

3 files changed

+97
-57
lines changed

3 files changed

+97
-57
lines changed

webapp/src/Twig/TwigExtension.php

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,64 +1179,14 @@ public function fileTypeIcon(string $type): string
11791179
return 'fas fa-file-' . $iconName;
11801180
}
11811181

1182-
private function relativeLuminance(string $rgb): float
1183-
{
1184-
// See https://en.wikipedia.org/wiki/Relative_luminance
1185-
[$r, $g, $b] = Utils::parseHexColor($rgb);
1186-
1187-
[$lr, $lg, $lb] = [
1188-
pow($r / 255, 2.4),
1189-
pow($g / 255, 2.4),
1190-
pow($b / 255, 2.4),
1191-
];
1192-
1193-
return 0.2126 * $lr + 0.7152 * $lg + 0.0722 * $lb;
1194-
}
1195-
1196-
private function apcaContrast(string $fgColor, string $bgColor): float
1197-
{
1198-
// Based on WCAG 3.x (https://www.w3.org/TR/wcag-3.0/)
1199-
$luminanceForeground = $this->relativeLuminance($fgColor);
1200-
$luminanceBackground = $this->relativeLuminance($bgColor);
1201-
1202-
$contrast = ($luminanceBackground > $luminanceForeground)
1203-
? (pow($luminanceBackground, 0.56) - pow($luminanceForeground, 0.57)) * 1.14
1204-
: (pow($luminanceBackground, 0.65) - pow($luminanceForeground, 0.62)) * 1.14;
1205-
1206-
return round($contrast * 100, 2);
1207-
}
1208-
1209-
/**
1210-
* @return array{string, string}
1211-
*/
1212-
private function hexToForegroundAndBorder(string $rgb): array
1213-
{
1214-
$background = Utils::parseHexColor($rgb);
1215-
1216-
// Pick a border that's a bit darker.
1217-
$darker = $background;
1218-
$darker[0] = max($darker[0] - 64, 0);
1219-
$darker[1] = max($darker[1] - 64, 0);
1220-
$darker[2] = max($darker[2] - 64, 0);
1221-
$border = Utils::rgbToHex($darker);
1222-
1223-
// Pick the text color with the biggest absolute contrast.
1224-
$contrastWithWhite = $this->apcaContrast('#ffffff', $rgb);
1225-
$contrastWithBlack = $this->apcaContrast('#000000', $rgb);
1226-
1227-
$foreground = (abs($contrastWithBlack) > abs($contrastWithWhite)) ? '#000000' : '#ffffff';
1228-
1229-
return [$foreground, $border];
1230-
}
1231-
12321182
public function problemBadge(ContestProblem $problem, bool $grayedOut = false): string
12331183
{
12341184
$rgb = Utils::convertToHex($problem->getColor() ?? '#ffffff');
12351185
if ($grayedOut || empty($rgb)) {
12361186
$rgb = Utils::convertToHex('whitesmoke');
12371187
}
12381188

1239-
[$foreground, $border] = $this->hexToForegroundAndBorder($rgb);
1189+
[$foreground, $border] = Utils::hexToForegroundAndBorder($rgb);
12401190

12411191
if ($grayedOut) {
12421192
$foreground = 'silver';
@@ -1262,7 +1212,7 @@ public function problemBadgeMaybe(
12621212
$rgb = Utils::convertToHex('whitesmoke');
12631213
}
12641214

1265-
[$foreground, $border] = $this->hexToForegroundAndBorder($rgb);
1215+
[$foreground, $border] = Utils::hexToForegroundAndBorder($rgb);
12661216

12671217
if (!$matrixItem->isCorrect) {
12681218
$foreground = 'silver';

webapp/src/Utils/Utils.php

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ public static function timeStringDiff(string $time1, string $time2): string
290290
*/
291291
public static function convertToHex(string $color): ?string
292292
{
293-
if (preg_match('/^#[[:xdigit:]]{3,6}$/', $color)) {
293+
if (preg_match('/^#[[:xdigit:]]{3}(?:[[:xdigit:]]{3}){0,2}$/', $color)) {
294294
return $color;
295295
}
296296

@@ -375,7 +375,62 @@ public static function componentToHex(int $component): string
375375
*/
376376
public static function rgbToHex(array $color): string
377377
{
378-
return "#" . static::componentToHex($color[0]) . static::componentToHex($color[1]) . static::componentToHex($color[2]);
378+
$result = "#";
379+
for ($i=0; $i<count($color); $i++) {
380+
$result .= static::componentToHex($color[$i]);
381+
}
382+
return $result;
383+
}
384+
385+
public static function relativeLuminance(string $rgb): float
386+
{
387+
// See https://en.wikipedia.org/wiki/Relative_luminance
388+
[$r, $g, $b] = static::parseHexColor($rgb);
389+
390+
[$lr, $lg, $lb] = [
391+
pow($r / 255, 2.4),
392+
pow($g / 255, 2.4),
393+
pow($b / 255, 2.4),
394+
];
395+
396+
return 0.2126 * $lr + 0.7152 * $lg + 0.0722 * $lb;
397+
}
398+
399+
public static function apcaContrast(string $fgColor, string $bgColor): float
400+
{
401+
// Based on WCAG 3.x (https://www.w3.org/TR/wcag-3.0/)
402+
$luminanceForeground = static::relativeLuminance($fgColor);
403+
$luminanceBackground = static::relativeLuminance($bgColor);
404+
405+
$contrast = ($luminanceBackground > $luminanceForeground)
406+
? (pow($luminanceBackground, 0.56) - pow($luminanceForeground, 0.57)) * 1.14
407+
: (pow($luminanceBackground, 0.65) - pow($luminanceForeground, 0.62)) * 1.14;
408+
409+
return round($contrast * 100, 2);
410+
}
411+
412+
/**
413+
* @return array{string, string}
414+
*/
415+
public static function hexToForegroundAndBorder(string $rgb): array
416+
{
417+
$background = Utils::parseHexColor($rgb);
418+
419+
// Pick a border that's a bit darker.
420+
// We explicit keep the alpha channel as-is.
421+
$darker = $background;
422+
$darker[0] = max($darker[0] - 64, 0);
423+
$darker[1] = max($darker[1] - 64, 0);
424+
$darker[2] = max($darker[2] - 64, 0);
425+
$border = Utils::rgbToHex($darker);
426+
427+
// Pick the text color with the biggest absolute contrast.
428+
$contrastWithWhite = static::apcaContrast('#ffffff', $rgb);
429+
$contrastWithBlack = static::apcaContrast('#000000', $rgb);
430+
431+
$foreground = (abs($contrastWithBlack) > abs($contrastWithWhite)) ? '#000000' : '#ffffff';
432+
433+
return [$foreground, $border];
379434
}
380435

381436
/**

webapp/tests/Unit/Utils/UtilsTest.php

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,12 +317,17 @@ public function testConvertToHexConvert(): void
317317
self::assertEquals('#00BFFF', Utils::convertToHex('deep sky blue'));
318318
self::assertEquals('#FFD700', Utils::convertToHex('GOLD'));
319319
self::assertEquals('#B8860B', Utils::convertToHex('darkgoldenrod '));
320+
self::assertEquals('#aabbcc', Utils::convertToHex('#aabbcc'));
321+
self::assertEquals('#aaabbbccc', Utils::convertToHex('#aaabbbccc'));
322+
self::assertEquals('#def', Utils::convertToHex('#def'));
323+
self::assertEquals('#f1A', Utils::convertToHex('#f1A'));
320324
}
321325

322326
public function testParseHexColor(): void
323327
{
324328
self::assertEquals([255, 255, 255], Utils::parseHexColor('#ffffff'));
325329
self::assertEquals([0, 0, 0], Utils::parseHexColor('#000000'));
330+
self::assertEquals([0, 0, 0], Utils::parseHexColor('#000'));
326331
self::assertEquals([171, 205, 239], Utils::parseHexColor('#abcdef'));
327332
self::assertEquals([254, 220, 186], Utils::parseHexColor('#FEDCBA'));
328333
}
@@ -343,6 +348,34 @@ public function testRgbToHex(): void
343348
self::assertEquals('#fedcba', Utils::rgbToHex([254, 220, 186]));
344349
}
345350

351+
public function testRelativeLuminance(): void
352+
{
353+
self::assertEquals(0.0, Utils::relativeLuminance("#000000"));
354+
self::assertEquals(1.0, Utils::relativeLuminance("#FFFfff"));
355+
self::assertEquals(0.00751604342389449, Utils::relativeLuminance("#123"));
356+
}
357+
358+
/**
359+
* Test that the APCA contrast function returns the correct data
360+
*/
361+
public function testApcaContrast(): void
362+
{
363+
self::assertEquals(-114.0, Utils::apcaContrast("#fff", "#000000"));
364+
self::assertEquals(-114.0, Utils::apcaContrast("#ffffff", "#000000"));
365+
self::assertEquals(-114.0, Utils::apcaContrast("#fffffffff", "#000"));
366+
self::assertEquals(114.0, Utils::apcaContrast("#000000", "#ffffff"));
367+
self::assertEquals(0.0, Utils::apcaContrast("#fffFFF", "#FFFfff"));
368+
self::assertEquals(-0.36, Utils::apcaContrast("#111", "#111"));
369+
}
370+
371+
public function testHexToForegroundAndBorder(): void
372+
{
373+
self::assertEquals(["#000000", "#bfbd9d"], Utils::hexToForegroundAndBorder("#fffDDD"));
374+
self::assertEquals(["#ffffff", "#000000"], Utils::hexToForegroundAndBorder("#000000"));
375+
self::assertEquals(["#000000", "#6a7b8c"], Utils::hexToForegroundAndBorder("#ABC"));
376+
self::assertEquals(["#000000", "#6a7b8c"], Utils::hexToForegroundAndBorder("#AAABBBCCC"));
377+
}
378+
346379
/**
347380
* Test function that converts colour name to hex notation.
348381
* Returns null for unknown values.
@@ -351,7 +384,9 @@ public function testConvertToHexUnknown(): void
351384
{
352385
self::assertNull(Utils::convertToHex('doesnotexist'));
353386
self::assertNull(Utils::convertToHex('#aabbccdd'));
354-
self::assertNull(Utils::convertToHex('#12346h'));
387+
self::assertNull(Utils::convertToHex('#12345h'));
388+
self::assertNull(Utils::convertToHex('#1234'));
389+
self::assertNull(Utils::convertToHex('#12'));
355390
}
356391

357392
/**
@@ -770,7 +805,7 @@ public function testSanitizeSvg(): void
770805
<a xlink:href="javascript:alert(2)">test 2</a>
771806
<a href="#test3">test 3</a>
772807
<a xlink:href="#test">test 4</a>
773-
808+
774809
<a href="data:data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 5</a>
775810
<a xlink:href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 6</a>
776811
<use xlink:href="defs.svg#icon-1"/>
@@ -783,7 +818,7 @@ public function testSanitizeSvg(): void
783818
<this>shouldn't be here</this>
784819
<script>alert(1);</script>
785820
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="541.54" y1="299.573" x2="543.179" y2="536.458"/>
786-
821+
787822
</svg>
788823
EOF;
789824
$clean = Utils::sanitizeSvg($dirty);

0 commit comments

Comments
 (0)