import { Current , Percentage , RGB , System } from "@siteimprove/alfa-css" ; import { Device } from "@siteimprove/alfa-device" ; import { Element } from "@siteimprove/alfa-dom" ; import { Option , None } from "@siteimprove/alfa-option" ; import { Result , Err } from "@siteimprove/alfa-result" ; import { Context } from "@siteimprove/alfa-selector" ; import { Style } from "@siteimprove/alfa-style" ; import { hasInterposedDescendant , isPositioned } from "../predicate" ; type Color = RGB < Percentage , Percentage > ; namespace Color { export function resolve ( color , style ) { switch ( color . type ) { case "keyword" : if ( color . value === "currentcolor" ) { color = style . computed ( "color" ) . value ; if ( color . type === "color" ) { return Option . of ( RGB . of ( color . red , color . green , color . blue , Percentage . of ( color . alpha . value ) ) ) ; } } if ( color . value === "canvastext" ) { return Option . of ( RGB . of ( Percentage . of ( 0 ) , Percentage . of ( 0 ) , Percentage . of ( 0 ) , Percentage . of ( 1 ) ) ) ; } return None ; case "color" : return Option . of ( RGB . of ( color . red , color . green , color . blue , Percentage . of ( color . alpha . value ) ) ) ; } } /**
     *  https export function composite ( foreground , background , opacity ) { const foregroundOpacity = foreground . alpha . value * opacity ; if ( foregroundOpacity === 1 ) { return foreground ; } const alpha = background . alpha . value * ( 1 - foregroundOpacity ) ; const [ red , green , blue ] = [ [ foreground . red , background . red ] , [ foreground . green , background . green ] , [ foreground . blue , background . blue ] , ] . map ( ( [ a , b ] ) => a . value * foregroundOpacity + b . value * alpha ) ; return RGB . of ( Percentage . of ( red ) , Percentage . of ( green ) , Percentage . of ( blue ) , Percentage . of ( foregroundOpacity + alpha ) ) ; } } class Layer { public static of ( colors , opacity ) { return new Layer ( Array . from ( colors ) , opacity ) ; } private readonly _colors : ReadonlyArray < Color > ; private readonly _opacity : number ; private constructor ( colors , opacity ) { this . _colors = colors ; this . _opacity = opacity ; } public get colors ( ) : ReadonlyArray < Color > { return this . _colors ; } public get opacity ( ) : number { return this . _opacity ; } } namespace Layer { export enum Error { HasUnresolvableBackgroundColor = `Could not resolve background-color` , HasUnresolvableGradientStop = `Could not resolve gradient color stop` , HasBackgroundSize = `A background-size was encountered` , HasExternalBackgroundImage = `A background-image with a url() was encountered` , HasNonStaticPosition = `A non-statically positioned element was encountered` , HasInterposedDescendant = `An interposed descendant element was encountered` } } function getLayers ( element , device , context = Context . empty ( ) , opacity ? ) { const style = Style . from ( element , device , context ) ; const color = Color . resolve ( style . computed ( "background-color" ) . value , style ) ; opacity = opacity ?? style . computed ( "opacity" ) . value . value ; const layers = [ ] ; if ( color . isSome ( ) ) { layers . push ( Layer . of ( [ color . get ( ) ] , opacity ) ) ; } else { return Err . of ( Layer . Error . HasUnresolvableBackgroundColor ) ; } for ( const image of style . computed ( "background-image" ) . value ) { if ( image . type === "keyword" ) { continue ; } if ( image . image . type === "url" ) { return Err . of ( Layer . Error . HasExternalBackgroundImage ) ; } if ( ! style . computed ( "background-size" ) . value . equals ( style . initial ( "background-size" ) . value ) ) { return Err . of ( Layer . Error . HasBackgroundSize ) ; } const stops = [ ] ; for ( const item of image . image . items ) { if ( item . type === "stop" ) { const color = Color . resolve ( item . color , style ) ; if ( color . isSome ( ) ) { stops . push ( color . get ( ) ) ; } else { return Err . of ( Layer . Error . HasUnresolvableGradientStop ) ; } } } layers . push ( Layer . of ( stops , opacity ) ) ; } if ( layers . length > 0 && layers . every ( ( layer ) => layer . opacity === 1 && layer . colors . every ( ( color ) => color . alpha . value === 1 ) ) ) { return Result . of ( layers ) ; } if ( isPositioned ( device , "absolute" , "fixed" ) ( element ) ) { return Err . of ( Layer . Error . HasNonStaticPosition ) ; } if ( hasInterposedDescendant ( device ) ( element ) ) { return Err . of ( Layer . Error . HasInterposedDescendant ) ; } for ( const parent of element . parent ( { flattened : true , } ) . filter ( Element . isElement ) ) { return getLayers ( parent , device , context ) . map ( ( parentLayers ) => parentLayers . concat ( layers ) ) ; } return Result . of ( layers ) ; } export type Foreground = Array < Color > ; export namespace Foreground { export enum Error { HasUnresolvableColor = `Could not resolve color` } } export function getForeground ( element , device , context = Context . empty ( ) ) { const style = Style . from ( element , device , context ) ; const color = Color . resolve ( style . computed ( "color" ) . value , style ) ; if ( color . isNone ( ) ) { return Err . of ( Foreground . Error . HasUnresolvableColor ) ; } const opacity = style . computed ( "opacity" ) . value ; if ( color . get ( ) . alpha . value * opacity . value === 1 ) { return Result . of ( [ color . get ( ) ] ) ; } if ( hasInterposedDescendant ( device ) ( element ) ) { return Err . of ( Layer . Error . HasInterposedDescendant ) ; } const colors = getBackground ( element , device , context , 1 ) . map ( ( background ) => background . map ( ( backdrop ) => Color . composite ( color . get ( ) , backdrop , 1 ) ) ) ; for ( const parent of element . parent ( { flattened : true , } ) . filter ( Element . isElement ) ) { return colors . flatMap ( ( colors ) => getBackground ( parent , device , context ) . map ( ( background ) => colors . flatMap ( ( color ) => background . map ( ( backdrop ) => Color . composite ( color , backdrop , opacity . value ) ) ) ) ) ; } return colors ; } export type Background = Array < Color > ; export namespace Background { export enum Error { HasTextShadow = `A text-shadow was encountered` } } export function getBackground ( element , device , context = Context . empty ( ) , opacity ? ) { if ( Style . from ( element , device , context ) . computed ( "text-shadow" ) . value . type !== "keyword" ) { return Err . of ( Background . Error . HasTextShadow ) ; } if ( hasInterposedDescendant ( device ) ( element ) ) { return Err . of ( Layer . Error . HasInterposedDescendant ) ; } return getLayers ( element , device , context , opacity ) . map ( ( layers ) => layers . reduce ( ( backdrops , layer ) => layer . colors . reduce ( ( layers , color ) => layers . concat ( backdrops . map ( ( backdrop ) => Color . composite ( color , backdrop , layer . opacity ) ) ) , [ ] as Array < Color > ) , [ RGB . of ( Percentage . of ( 1 ) , Percentage . of ( 1 ) , Percentage . of ( 1 ) , Percentage . of ( 1 ) ) , ] ) ) ; }