(import scheme) (cond-expand (chicken-4 (import chicken) (use ports srfi-1 test web-colors)) (else (import (chicken format) (chicken port) (srfi 1) web-colors test))) (: rad->deg (number --> number)) (define (rad->deg rad) (* rad 57.29577951308232)) (: same? (any any --> boolean)) (define (same? a b) (cond ((and (list? a) (list? b)) (and (= (length a) (length b)) (every same? a b))) ((and (number? a) (inexact? a) (number? b) (inexact? b)) (near? a b)) ((and (string? a) (string? b)) (string=? a b)) (else (equal? a b)))) (: near? (number number --> boolean)) (define (near? a b #!optional (e 1e-6)) (< (abs (- a b)) e)) (define-syntax batch-test (syntax-rules (=> error) ((batch-test fn) (begin)) ((batch-test fn input => error more ...) (begin (test-error (sprintf "~S => error" input) (fn input)) (batch-test fn more ...))) ((batch-test fn input => result more ...) (begin (test-assert (sprintf "~S => ~S" input result) (same? result (fn input))) (batch-test fn more ...))))) (test-group "examples" (batch-test parse-web-color "#B64926" => '(rgb 182 73 38 1) "rgb(93 152 121 / 51%)" => '(rgb 93 152 121 51/100) "rgba( 100%, 72.9%, 33%, 0.5 )" => '(rgb% 1 0.729 33/100 0.5) "hsl(210 51% 87% / 77%)" => '(hsl 210 51/100 87/100 77/100)) (batch-test web-color->string '(rgb 182 73 38 1) => "#b64926" '(rgb 93 152 121 51/100) => "rgba(93, 152, 121, 0.51)" '(rgb% 1 0.729 33/100 0.5) => "rgba(100%, 72.9%, 33%, 0.5)" '(hsl 210 51/100 87/100 77/100) => "hsla(210, 51%, 87%, 0.77)" '(rgb 75 0 130 1) => "#4b0082") (test '(rgb 75 0 130 1) (parse-web-color "indigo")) (test "indigo" (web-color-name '(rgb 75 0 130 1)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; PARSE (test-group "parse-web-color" (batch-test parse-web-color "#a1b" => '(rgb #xAA #x11 #xBB 1) "#a1b2" => '(rgb #xAA #x11 #xBB #x22/FF) "#a1b2c3" => '(rgb #xA1 #xB2 #xC3 1) "#a1b2c3d4" => '(rgb #xA1 #xB2 #xC3 #xD4/FF) "red" => '(rgb 255 0 0 1) "AliceBlue" => '(rgb 240 248 255 1) "YELLOWGREEN" => '(rgb 154 205 50 1) "rgb(88 41 67 / 0.42)" => '(rgb 88 41 67 0.42) "rgb(88,41,67)" => '(rgb 88 41 67 1) "rgb(88, 41, 67, 0.42)" => '(rgb 88 41 67 0.42) "rgba(27%, 81%, 13%)" => '(rgb% 27/100 81/100 13/100 1) "rgba(27%,81%,13%,0.5)" => '(rgb% 27/100 81/100 13/100 0.5) "hsl(88 41% 67% / 0.42)" => '(hsl 88 41/100 67/100 0.42) "hsl(88, 41%, 67%, 0.42)" => '(hsl 88 41/100 67/100 0.42) "hsla(88, 41%, 67%, 42%)" => '(hsl 88 41/100 67/100 42/100) ;; Invalid "" => error "#12345" => error "123" => error "rad" => error)) (test-group "parse-hex-color" (batch-test parse-hex-color ;; Allow only 3, 4, 6, or 8 digits. "" => error "#" => error "#1" => error "#12" => error "#123" => '(rgb #x11 #x22 #x33 1) "#1234" => '(rgb #x11 #x22 #x33 #x44/ff) "#12345" => error "#123456" => '(rgb #x12 #x34 #x56 1) "#1234567" => error "#12345678" => '(rgb #x12 #x34 #x56 #x78/ff) "#123456789" => error ;; Hex digits "#fab" => '(rgb #xFF #xAA #xBB 1) "#aced" => '(rgb #xAA #xCC #xEE #xDD/FF) "#beaded" => '(rgb #xBE #xAD #xED 1) "#abad1dea" => '(rgb #xAB #xAD #x1D #xEA/FF) "#ABAD1DEA" => '(rgb #xAB #xAD #x1D #xEA/FF) "#AbAd1dEa" => '(rgb #xAB #xAD #x1D #xEA/FF) ;; No "#" "123" => error "1234" => error "123456" => error "1234678" => error ;; Leading or trailing junk " #fff" => error ",#fff" => error "#fff " => error "#fff," => error)) (test-group "parse-named-color" (batch-test parse-named-color "red" => '(rgb 255 0 0 1) "aliceblue" => '(rgb 240 248 255 1) "yellowgreen" => '(rgb 154 205 50 1) "rebeccapurple" => '(rgb 102 51 153 1) "transparent" => '(rgb 0 0 0 0) ;; Case insensitivity "RED" => '(rgb 255 0 0 1) "AliceBlue" => '(rgb 240 248 255 1))) (test-group "parse-rgb-color" (batch-test parse-rgb-color "rgb(88, 41, 67, 0.42)" => '(rgb 88 41 67 0.42) "rgba(88, 41, 67, 0.42)" => '(rgb 88 41 67 0.42) "rgba(88, 41, 67, 42%)" => '(rgb 88 41 67 42/100) "rgb(27%, 81%, 13%)" => '(rgb% 27/100 81/100 13/100 1) "rgb(27%, 81%, 13%, 50%)" => '(rgb% 27/100 81/100 13/100 50/100) "rgb(27%, 81%, 13%, 0.5)" => '(rgb% 27/100 81/100 13/100 0.5) "rgba(27%, 81%, 13%)" => '(rgb% 27/100 81/100 13/100 1) "rgba(27%, 81%, 13%, 50%)" => '(rgb% 27/100 81/100 13/100 50/100) "rgba(27%, 81%, 13%, 0.5)" => '(rgb% 27/100 81/100 13/100 0.5) ;; Separators and whitespace "rgb(4,3,2,1)" => '(rgb 4 3 2 1) "rgb(4 3 2 1)" => '(rgb 4 3 2 1) "rgb( 4 3 2 1 )" => '(rgb 4 3 2 1) "rgb( 4 , 3 , 2 , 1 )" => '(rgb 4 3 2 1) "rgb( 4 3 , 2 1 )" => '(rgb 4 3 2 1) "rgb(4,,3,,2,,1)" => error "rgb(4,3,2,1,)" => error "rgb(4,3,2,)" => error "rgb(,4,3,2)" => error ;; rgb with slash "rgb(88 41 67 / 0.42)" => '(rgb 88 41 67 0.42) "rgb(88 41 67/0.42)" => '(rgb 88 41 67 0.42) "rgb(27% 81% 13% / 42%)" => '(rgb% 27/100 81/100 13/100 42/100) "rgb(27% 81% 13%/42%)" => '(rgb% 27/100 81/100 13/100 42/100) "rgba(88 41 67 / 0.42)" => '(rgb 88 41 67 0.42) "rgba(88 41 67/0.42)" => '(rgb 88 41 67 0.42) "rgba(27% 81% 13% / 42%)" => '(rgb% 27/100 81/100 13/100 42/100) "rgba(27% 81% 13%/42%)" => '(rgb% 27/100 81/100 13/100 42/100) ;; Not allowed with commas "rgb(88, 41, 67 / 0.42)" => error "rgba(88, 41, 67 / 0.42)" => error ;; Non-integer values "rgba(12.3%, 45.6%, 79.0%, 98.7%)" => '(rgb% 0.123 0.456 0.79 0.987) "rgba(12.3, 45.6, 78.9, .987)" => '(rgb 12.3 45.6 78.9 0.987) "rgba(.123e2, 456e-1, 78.9E0, 9.87e-1)" => '(rgb 12.3 45.6 78.9 0.987) ;; Case insensitivity "RGB(88, 41, 67, 0.42)" => '(rgb 88 41 67 0.42) "RGBA(27%, 81%, 13%)" => '(rgb% 27/100 81/100 13/100 1) "rGbA(27%, 81%, 13%)" => '(rgb% 27/100 81/100 13/100 1) ;; Cannot intermix % and non-% RGB values. "rgb(12,34,56)" => '(rgb 12 34 56 1) "rgb(12%,34,56)" => error "rgb(12,34%,56)" => error "rgb(12,34,56%)" => error "rgb(12%,34%,56)" => error "rgb(12,34%,56%)" => error "rgb(12%,34,56%)" => error "rgb(12%,34%,56%)" => '(rgb% 12/100 34/100 56/100 1))) (test-group "parse-hsl-color" (batch-test parse-hsl-color "hsl(88, 41%, 67%, 0.42)" => '(hsl 88 41/100 67/100 0.42) "hsla(88, 41%, 67%, 0.42)" => '(hsl 88 41/100 67/100 0.42) "hsla(88, 41%, 67%, 42%)" => '(hsl 88 41/100 67/100 42/100) ;; Separators and whitespace "hsl(4,3%,2%,1)" => '(hsl 4 3/100 2/100 1) "hsl(4 3% 2% 1)" => '(hsl 4 3/100 2/100 1) "hsl( 4 3% 2% 1 )" => '(hsl 4 3/100 2/100 1) "hsl( 4 , 3% , 2% , 1 )" => '(hsl 4 3/100 2/100 1) "hsl( 4 3% , 2% 1 )" => '(hsl 4 3/100 2/100 1) "hsl(4,,3%,,2%,,1)" => error "hsl(4,3%,2%,1,)" => error "hsl(4,3%,2%,)" => error "hsl(,4,3%,2%)" => error ;; hsl with slash "hsl(27 81% 13% / 42%)" => '(hsl 27 81/100 13/100 42/100) "hsl(27 81% 13%/42%)" => '(hsl 27 81/100 13/100 42/100) "hsla(27 81% 13% / 42%)" => '(hsl 27 81/100 13/100 42/100) "hsla(27 81% 13%/42%)" => '(hsl 27 81/100 13/100 42/100) ;; Not allowed with commas "hsl(88, 41, 67 / 0.42)" => error "hsla(88, 41, 67 / 0.42)" => error ;; Non-integer values "hsla(12.3, 45.6%, 79.0%, 98.7%)" => '(hsl 12.3 0.456 0.79 0.987) "hsla(12.3, 45.6%, 78.9%, .987)" => '(hsl 12.3 0.456 0.789 0.987) "hsla(12e-1, .45e2%, 7.8E0%, 9.8e-1)" => '(hsl 1.2 0.45 0.078 0.98) ;; Case insensitivity "HSL(88, 41%, 67%, 0.42)" => '(hsl 88 41/100 67/100 0.42) "HSLA(27, 81%, 13%)" => '(hsl 27 81/100 13/100 1) "hSlA(27, 81%, 13%)" => '(hsl 27 81/100 13/100 1) ;; Units "hsl(88deg, 41%, 67%)" => '(hsl 88 41/100 67/100 1) "hsl(3.14rad, 51%, 87%)" => `(hsl ,(rad->deg 3.14) 51/100 87/100 1) "hsl(50grad, 51%, 87%)" => `(hsl 45 51/100 87/100 1) "hsl(2turn, 51%, 87%)" => `(hsl 720 51/100 87/100 1) "hsla(4deg 3% 2% / 0.1)" => '(hsl 4 3/100 2/100 0.1) "hsla(1rad 2% 3% / 0.4)" => `(hsl ,(rad->deg 1) 2/100 3/100 0.4) "hsla(9grad 8% 7% / 0.6)" => `(hsl 81/10 8/100 7/100 0.6) "hsla(0.1turn 3% 4% / 0.5)" => `(hsl 36.0 3/100 4/100 0.5))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; PRINT (define (wrap-writer f) (lambda (x) (call-with-output-string (lambda (port) (f x port))))) (test-group "write-web-color" (batch-test (wrap-writer write-web-color) '(rgb 102 51 153 1) => "#663399" '(rgb 93 152 121 80/100) => "rgba(93, 152, 121, 0.8)" '(rgb% 1 0.729 33/100 1.0) => "rgb(100%, 72.9%, 33%)" '(hsl 210 51/100 87/100 77/100) => "hsla(210, 51%, 87%, 0.77)" '(hsl 179.9 51/100 87/100 1) => "hsl(179.9, 51%, 87%)")) (test-group "write-hex-color" (batch-test (wrap-writer write-hex-color) '(rgb -1 -2 -3 1) => "#000000" '(rgb 0 0 0 1) => "#000000" '(rgb 1 2 3 1) => "#010203" '(rgb 255 255 255 1) => "#ffffff" '(rgb -1 -2 -3 -4) => "#00000000" '(rgb 0 0 0 0) => "#00000000" '(rgb 1 2 3 42/100) => "#0102036b" '(rgb 255 255 255 0.5) => "#ffffff80" '(rgb% -0.1 -0.2 -0.3 1) => "#000000" '(rgb% 0 0 0 1) => "#000000" '(rgb% 0.1 0.2 0.3 1) => "#1a334c" '(rgb% 1 1 1 1) => "#ffffff" '(rgb% -0.1 -0.2 -0.3 -0.4) => "#00000000" '(rgb% 0 0 0 0.0) => "#00000000" '(rgb% 10/100 20/100 30/100 42/100) => "#1a334c6b" '(rgb% 1 1 1 0.5) => "#ffffff80" '(hsl 123 0.4 0.5 1) => error '(hsl 123 0.4 0.5 0.42) => error)) (test-group "write-rgb-color" (batch-test (wrap-writer write-rgb-color) '(rgb -1 -2 -3 1) => "rgb(-1, -2, -3)" '(rgb 0 0 0 1) => "rgb(0, 0, 0)" '(rgb 1 2 3 1) => "rgb(1, 2, 3)" '(rgb 255 255 255 1) => "rgb(255, 255, 255)" '(rgb -1 -2 -3 -4) => "rgba(-1, -2, -3, -4)" '(rgb 0 0 0 0) => "rgba(0, 0, 0, 0)" '(rgb 1 2e-8 3.45 42/100) => "rgba(1, 2e-08, 3.45, 0.42)" '(rgb 255 255 255 0.5) => "rgba(255, 255, 255, 0.5)" '(rgb% -0.1 -0.2 -0.3 1) => "rgb(-10%, -20%, -30%)" '(rgb% 0 0 0 1) => "rgb(0%, 0%, 0%)" '(rgb% 0.123 4.5e-10 0.3 1) => "rgb(12.3%, 4.5e-08%, 30%)" '(rgb% 1 1 1 1) => "rgb(100%, 100%, 100%)" '(rgb% -0.1 -0.2 -0.3 -0.4) => "rgba(-10%, -20%, -30%, -0.4)" '(rgb% 0 0 0 0) => "rgba(0%, 0%, 0%, 0)" '(rgb% 10/100 20/100 30/100 42/100) => "rgba(10%, 20%, 30%, 0.42)" '(rgb% 1 1 1 0.5) => "rgba(100%, 100%, 100%, 0.5)" '(hsl 123 0.4 0.5 1) => error '(hsl 123 0.4 0.5 0.42) => error)) (test-group "write-hsl-color" (batch-test (wrap-writer write-hsl-color) '(hsl 0 0 0 1) => "hsl(0, 0%, 0%)" '(hsl 123 0.4 0.5 1) => "hsl(123, 40%, 50%)" '(hsl -7.89 0.123 4.5e-10 1) => "hsl(-7.89, 12.3%, 4.5e-08%)" '(hsl 361 1.23 1.235 1) => "hsl(361, 123%, 123.5%)" '(hsl 0 0 0 0) => "hsla(0, 0%, 0%, 0)" '(hsl 123 0.4 0.5 0.42) => "hsla(123, 40%, 50%, 0.42)" '(hsl -7.89 0.123 4.5e-10 42e-2) => "hsla(-7.89, 12.3%, 4.5e-08%, 0.42)" '(hsl 361 1.23 1.235 1.5) => "hsla(361, 123%, 123.5%, 1.5)" '(rgb 255 255 255 1) => error '(rgb% 1 1 1 1) => error)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ->STRING (test-group "web-color->string" (batch-test web-color->string '(rgb 102 51 153 1) => "#663399" '(rgb 93 152 121 80/100) => "rgba(93, 152, 121, 0.8)" '(rgb% 1 0.729 33/100 1.0) => "rgb(100%, 72.9%, 33%)" '(hsl 210 51/100 87/100 77/100) => "hsla(210, 51%, 87%, 0.77)" '(hsl 179.9 51/100 87/100 1) => "hsl(179.9, 51%, 87%)")) (test-group "rgb-color->hex-string" (batch-test rgb-color->hex-string '(rgb 1 2 3 1) => "#010203" '(rgb 1 2 3 42/100) => "#0102036b" '(rgb% 0.1 0.2 0.3 1) => "#1a334c" '(rgb% 10/100 20/100 30/100 42/100) => "#1a334c6b" '(hsl 123 0.4 0.5 1) => error '(hsl 123 0.4 0.5 0.42) => error)) (test-group "rgb-color->string" (batch-test rgb-color->string '(rgb 1 2 3 1) => "rgb(1, 2, 3)" '(rgb 1 2e-8 3.45 42/100) => "rgba(1, 2e-08, 3.45, 0.42)" '(rgb% 0.123 4.5e-10 0.3 1) => "rgb(12.3%, 4.5e-08%, 30%)" '(rgb% 10/100 20/100 30/100 42/100) => "rgba(10%, 20%, 30%, 0.42)" '(hsl 123 0.4 0.5 1) => error '(hsl 123 0.4 0.5 0.42) => error)) (test-group "hsl-color->string" (batch-test hsl-color->string '(hsl 123 0.4 0.5 1) => "hsl(123, 40%, 50%)" '(hsl 123 0.4 0.5 0.42) => "hsla(123, 40%, 50%, 0.42)" '(rgb 255 255 255 1) => error '(rgb% 1 1 1 1) => error)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; OTHER (test-group "+web-color-types+" (test-assert (equal? '(rgb rgb% hsl) +web-color-types+))) (test-group "color-list?" (test-assert (color-list? '(rgb 1 2 3 4))) (test-assert (color-list? '(rgb% 1 2 3 4))) (test-assert (color-list? '(hsl 1 2 3 4))) ;; Specified types (test-assert (color-list? '(hsl 1 2 3 4) '(hsl))) (test-assert (not (color-list? '(hsl 1 2 3 4) '(rgb rgb%)))) ;; Too few numbers (test-assert (not (color-list? '(rgb 1 2 3)))) (test-assert (not (color-list? '(rgb% 1 2 3)))) (test-assert (not (color-list? '(hsl 1 2 3)))) ;; Too many numbers (test-assert (not (color-list? '(rgb 1 2 3 4 5)))) (test-assert (not (color-list? '(rgb% 1 2 3 4 5)))) (test-assert (not (color-list? '(hsl 1 2 3 4 5)))) ;; Unsupported type (test-assert (not (color-list? '(yuv 1 2 3 4)))) (test-assert (color-list? '(yuv 1 2 3 4) '(yuv)))) (test-group "*web-color-names*" (test-error (parse-named-color "amaranth")) (test-error (parse-web-color "amaranth")) (parameterize ((*web-color-names* `((amaranth . (rgb 229 43 80 1)) ; add new color (red . (rgb 9000 0 0 1)) ; override old color ,@(*web-color-names*)))) (test '(rgb 229 43 80 1) (parse-named-color "amaranth")) (test '(rgb 229 43 80 1) (parse-named-color "AMARANTH")) (test '(rgb 9000 0 0 1) (parse-named-color "red")) (test '(rgb 229 43 80 1) (parse-web-color "amaranth")) (test '(rgb 229 43 80 1) (parse-web-color "AMARANTH")) (test '(rgb 9000 0 0 1) (parse-web-color "red"))) (parameterize ((*web-color-names* '())) (test-error (parse-named-color "red")) (test-error (parse-web-color "red")))) (test-group "web-color-name" (batch-test web-color-name '(rgb 240 248 255 1) => "aliceblue" '(rgb 0 255 255 1) => "aqua" '(rgb 154 205 50 1) => "yellowgreen" '(rgb 102 51 153 1) => "rebeccapurple" '(rgb 0 0 0 0) => "transparent" '(rgb 1 2 3 4) => #f '(rgb% 1 2 3 4) => #f '(hsl 1 2 3 4) => #f) (parameterize ((*web-color-names* `((amaranth . (rgb 229 43 80 1)) ; add new color (red . (hsl 0 1 0.5 1)) ; override old color ,@(*web-color-names*)))) (test "amaranth" (web-color-name '(rgb 229 43 80 1))) (test "red" (web-color-name '(hsl 0 1 0.5 1))) (test "red" (web-color-name '(rgb 255 0 0 1))))) (test-group "normalize-web-color" (batch-test normalize-web-color '(rgb -1 -1 -1 -0.1) => '(rgb 0 0 0 0) '(rgb 0 0 0 0) => '(rgb 0 0 0 0) '(rgb 0.0 0.0 0.0 0.0) => '(rgb 0 0 0 0) '(rgb 1 2 3 0.5) => '(rgb 1 2 3 0.5) '(rgb 255 255 255 1) => '(rgb 255 255 255 1) '(rgb 255.0 255.0 255.0 1.0) => '(rgb 255 255 255 1) '(rgb 255.1 255.1 255.1 1.1) => '(rgb 255 255 255 1) '(rgb 256 256 256 1.1) => '(rgb 255 255 255 1) '(rgb% -0.1 -0.1 -0.1 -0.1) => '(rgb% 0 0 0 0) '(rgb% 0 0 0 0) => '(rgb% 0 0 0 0) '(rgb% 0.0 0.0 0.0 0.0) => '(rgb% 0 0 0 0) '(rgb% 0.1 0.2 0.3 0.4) => '(rgb% 0.1 0.2 0.3 0.4) '(rgb% 1/10 2/10 3/10 4/10) => '(rgb% 1/10 2/10 3/10 4/10) '(rgb% 1 1 1 1) => '(rgb% 1 1 1 1) '(rgb% 1/1 1/1 1/1 1/1) => '(rgb% 1 1 1 1) '(rgb% 1.0 1.0 1.0 1.0) => '(rgb% 1 1 1 1) '(rgb% 1.1 1.1 1.1 1.1) => '(rgb% 1 1 1 1) '(hsl -1 -0.1 -0.1 -0.1) => '(hsl 359 0 0 0) '(hsl 0 0 0 0) => '(hsl 0 0 0 0) '(hsl 0.0 0.0 0.0 0.0) => '(hsl 0 0 0 0) '(hsl 123 0.4 0.5 0.6) => '(hsl 123 0.4 0.5 0.6) '(hsl 123 4/10 5/10 6/10) => '(hsl 123 4/10 5/10 6/10) '(hsl 123.5 0.4 0.5 0.6) => '(hsl 124 0.4 0.5 0.6) '(hsl 359.0 1.0 1.0 1.0) => '(hsl 359 1 1 1) '(hsl 359.49 1.0 1.0 1.0) => '(hsl 359 1 1 1) '(hsl 359.5 1.0 1.0 1.0) => '(hsl 0 1 1 1) '(hsl 359.9 1.0 1.0 1.0) => '(hsl 0 1 1 1) '(hsl 359.9 1.1 1.1 1.1) => '(hsl 0 1 1 1) '(hsl 360 1.1 1.1 1.1) => '(hsl 0 1 1 1) '(hsl 361 1.1 1.1 1.1) => '(hsl 1 1 1 1))) (test-group "rgb-color->bytes" (batch-test rgb-color->bytes '(rgb 0 0 0 0) => '(0 0 0 0) '(rgb 255 255 255 1) => '(255 255 255 255) '(rgb 123 256 -1 50/100) => '(123 255 0 128) '(rgb% 0 0 0 0) => '(0 0 0 0) '(rgb% 1 1 1 1) => '(255 255 255 255) '(rgb% 0.4 1.1 -0.1 0.25) => '(102 255 0 64) '(hsl 123 0.4 0.5 0.6) => error)) (test-exit)