http://mathling.com/svg/gradients  library module

http://mathling.com/svg/gradients


Module with functions providing some colouring support at the
algorithm level.

Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)

October 2021
Status: Active

Imports

http://mathling.com/core/random
import module namespace rand="http://mathling.com/core/random"
       at "../core/random.xqy"
http://mathling.com/colour/space
import module namespace cs="http://mathling.com/colour/space"
       at "../colourspace/colour-space.xqy"
http://mathling.com/geometric/path
import module namespace path="http://mathling.com/geometric/path"
       at "../geo/path.xqy"
http://mathling.com/geometric/edge
import module namespace edge="http://mathling.com/geometric/edge"
       at "../geo/edge.xqy"
http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy"
http://mathling.com/colour/tonemap
import module namespace tone="http://mathling.com/colour/tonemap"
       at "../colourspace/tonemap.xqy"
http://mathling.com/colour/stop
import module namespace stop="http://mathling.com/colour/stop"
       at "../colourspace/stop.xqy"
http://mathling.com/colour/xyz
import module namespace xyz="http://mathling.com/colour/xyz"
       at "../colourspace/xyz.xqy"
http://mathling.com/geometric/ellipse
import module namespace ellipse="http://mathling.com/geometric/ellipse"
       at "../geo/ellipse.xqy"
http://mathling.com/geometric/spline
import module namespace spline="http://mathling.com/geometric/spline"
       at "../geo/spline.xqy"
http://mathling.com/colour/rgb
import module namespace rgb="http://mathling.com/colour/rgb"
       at "../colourspace/rgb.xqy"
http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy"

Variables

Variable: $NAMED-GRADIENTS as xs:string*

Variable: $ALIASES as map(xs:string,xs:string)

Variable: $NAMED-ALIASES as xs:string*

Variable: $WHITES as xs:string*

Variable: $PRETTY-PALETTES as map(xs:string,xs:string*)

PrettyCols from https://nrennie.github.io/PrettyCols/ CC0

Variable: $WERNER-PALETTES as map(xs:string,xs:string*)

Werner colours from Werner's Nomenclature of Colours

Variable: $PALETTES as map(xs:string,xs:string*)

Variable: $NAMED-PALETTES as xs:string*

Variable: $SMALL-GRADIENTS as xs:string*

Variable: $SPLIT-GRADIENTS as xs:string*

Variable: $CYCLIC-GRADIENTS as xs:string*

Variable: $DIVERGING-GRADIENTS as xs:string*

Variable: $LINEAR-GRADIENTS as xs:string*

Functions

Function: starts-darker
declare function starts-darker($gradient as xs:string) as xs:boolean


starts-darker()
Does this gradient start with darker colours? i.e. dark-to-light
Return false for those that are dark-light-dark or light-dark-light or
scrambled

Params
  • gradient as xs:string
Returns
  • xs:boolean
declare function this:starts-darker($gradient as xs:string) as xs:boolean
{
  ($gradient = (
  "acton", "bamako", "batlow", "buda", 
  "davos", "devon", "hawaii", "imola", "lapaz", 
  "nuuk", "oleron", "oslo", "tokyo", "turku", 
  "actonS", "bamakoS", "batlowS", (:"berlinS",:) "bilbaoS", (:"brocS",:) "budaS",
  "inferno", "magma", "plasma", "viridis",
  "cividis",
  "twilight-blue", "twilight-red",
  "sawtooth","azimuth",
  "cube1_0","cubeYF","Linear_L",
  "bathymetry","bathymetryO","CDOM","chlorophyll","density",
  "freesurface-red","freesurface-blue","oxygen","PAR","salinity",
  "temperature","turbidity","velocity-blue","velocity-green",
  "vorticity-pink","vorticity-turquoise",

  "autumn","blackbody","bluered","bone","cool","copper",
  "cubehelix","earth","electric","greens","greys","hot",
  "jet","picnic","portland","rainbow",
  "summer","warm","winter","yignbu","yiorrd",
  "expanded-bone","expanded-copper",

  "CET-L9","CET-L8","CET-L7","CET-L6","CET-L5",
  "CET-L4","CET-L3","CET-L2","CET-L1","CET-L18",
  "CET-L16","CET-L15","CET-L14","CET-L13","CET-L11","CET-L10",
  "CET-CBTL2","CET-CBTL1",
  "CET-CBL2","CET-CBL1",

  "bluegold", "burn", "jungle", "sea", "blossom", "oasis", "blood", "forest",
  "deepsea",

  "cm-gray", "haline", "ice", "oxy",
  "solar", "thermal", "topo",
  "turbid",
  
  "simpleBlack",

  "browns", "olives", "sunsetland", "october", "farhills", "springhills",
  "mountains", "wotw", "carnival", "greens",

  "werner_reds", "werner_blues", "werner_browns",

  "Blues", "Purples", "Tangerines", "Greens", "Pinks", "Teals", "Yellows", "Reds"
  ))
  or
  (contains($gradient,"-reverse") and
   not(this:starts-darker(replace($gradient,"-reverse","")))
  )
}

Function: random-gradient
declare function random-gradient() as xs:string


random-gradient()
Return then name of a random well-known base gradient or its reverse.

Returns
  • xs:string
declare function this:random-gradient() as xs:string
{
  let $exclusion := "(^simple)"
  let $valid-names :=
    ($this:NAMED-GRADIENTS,$this:NAMED-ALIASES,$this:NAMED-PALETTES)[
      not(matches(.,$exclusion))
    ]
  let $gradient := rand:select-random($valid-names)
  return (
    if (empty($gradient) or $gradient="") then util:log("Empty gradient? "||string-join($valid-names,"+")) else (),
    util:assert(not(empty($gradient)), "__empty__"),
    util:assert($gradient ne "", "__empty string__"),
    if (rand:flip(50))
    then $gradient
    else $gradient||"-reverse"
  )
}

Function: random-gradient
declare function random-gradient($exclusion as xs:string) as xs:string


random-gradient()
Return then name of a random well-known base gradient or its reverse.

Params
  • exclusion as xs:string: regular expression of gradient names to exclude in addition to simpleXXX
Returns
  • xs:string
declare function this:random-gradient($exclusion as xs:string) as xs:string
{
  let $exclusion := "("||$exclusion||")|(^simple)"
  let $valid-names :=
    ($this:NAMED-GRADIENTS,$this:NAMED-ALIASES,$this:NAMED-PALETTES)[
      not(matches(.,$exclusion))
    ]
  let $gradient := rand:select-random($valid-names)
  return (
    if (rand:flip(50))
    then $gradient
    else $gradient||"-reverse"
  )
}

Function: fetch-gradient
declare function fetch-gradient($gradient as xs:string) as element()?


fetch-gradient()
Get the gradient definition of a known named gradient or its alias.
Empty otherwise.

Params
  • gradient as xs:string
Returns
  • element()?
declare function this:fetch-gradient($gradient as xs:string) as element()?
{
  if ($gradient = $this:NAMED-GRADIENTS) then (
    (: SLEAZY HACK ALERT :)
    let $def :=
      try {
        doc("../COLOURS/"||$gradient||".xsl")//xsl:param/*
      } catch * { () }
    return
      if (exists($def)) then $def else (
        try {
          doc("../COLOURS/"||$gradient||".svg")//svg:defs/*
        } catch * { () }
      )
  ) else if ($this:ALIASES($gradient) = $this:NAMED-GRADIENTS) then (
    this:fetch-gradient($this:ALIASES($gradient))
  ) else (
  )
}

Function: get-gradient-definition
declare function get-gradient-definition($gradient as xs:string) as element()?


get-gradient-definition()
Get the gradient definition of the name, allowing for variants for
flips, inversions, samples, etc.

Params
  • gradient as xs:string
Returns
  • element()?
declare function this:get-gradient-definition($gradient as xs:string) as element()?
{
  let $gradient-pieces := tokenize($gradient, "[·+]") (: middle dot :)
  let $do-interleave := contains($gradient,"+")
  let $gradients :=
    for $gradient in $gradient-pieces
    return (
    if ($gradient="random") then (
      this:get-gradient-definition(this:random-gradient())
    ) else if ($gradient = $this:NAMED-GRADIENTS) then (
      this:fetch-gradient($gradient)
    ) else if ($this:ALIASES($gradient) = $this:NAMED-GRADIENTS) then (
      let $def := this:fetch-gradient($this:ALIASES($gradient))
      return (
        <svg:linearGradient id="{$gradient}">{
          $def/(@* except @id),
          $def/*
        }</svg:linearGradient>
      )
    ) else if ($gradient = $this:NAMED-PALETTES) then (
      this:linear-gradient-definition($gradient, "linear", $this:PALETTES($gradient))
    ) else if (matches($gradient,'(-reverse|-random|-flip|-invert|-full|-anti|-outflow|-inflow|-slant|-fade|-[0-9]+)$')) then (
      let $root := replace($gradient,'(-reverse|-random|-flip|-invert|-full|-anti|-outflow|-inflow|-slant|-fade|-[0-9]+)$','')
      let $definition := this:get-gradient-definition($root)
      let $operation := substring-after($gradient, $root||"-")
      return switch ($operation)
      case "random" return this:gradient-random($definition)=>this:gradient-name($gradient)
      case "reverse" return this:gradient-reverse($definition)=>this:gradient-name($gradient)
      case "flip" return this:linear-flip($definition)=>this:gradient-name($gradient)
      case "invert" return this:linear-invert($definition)=>this:gradient-name($gradient)
      case "full" return this:linear-full($definition)=>this:gradient-name($gradient)
      case "anti" return this:linear-anti($definition)=>this:gradient-name($gradient)
      case "outflow" return this:radial-outflow($definition)=>this:gradient-name($gradient)
      case "inflow" return this:radial-inflow($definition)=>this:gradient-name($gradient)
      case "slant" return this:linear-slant($definition)=>this:gradient-name($gradient)
      case "fade" return this:gradient-fade($definition, 0.0, 1.0)=>this:gradient-name($gradient)
      default (: one of the samples :) return (
        let $n := xs:integer($operation)
        return this:gradient-sample($definition, $n)=>this:gradient-name($gradient)
      )
    ) else if (starts-with($gradient,"black-to-")) then (
      let $colour := this:colour(substring-after($gradient,"black-to-"))
      return
        <svg:linearGradient id="{this:safe($gradient)}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="0%" x2="100%" y1="0%" y2="0%">      
          <svg:stop offset="0%" stop-color="#000000"/>
          <svg:stop offset="100%" stop-color="{$colour}"/>
        </svg:linearGradient>
    ) else if (starts-with($gradient,"white-to-")) then (
      let $colour := this:colour(substring-after($gradient,'white-to-'))
      return
        <svg:linearGradient id="{this:safe($gradient)}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="0%" x2="100%" y1="0%" y2="0%">      
          <svg:stop offset="0%" stop-color="#FFFFFF"/>
          <svg:stop offset="100%" stop-color="{$colour}"/>
        </svg:linearGradient>
    ) else (
      (: util:log("gradient="||$gradient||" no definition") :)
    )
  )
  return (
    if (count($gradients) le 1) then $gradients
    else if (count($gradients) = count($gradient-pieces)) then (
      this:merge($gradient, $gradients, $do-interleave)
    ) else (
    )
  )
}

Function: gradient
declare function gradient($gradient as xs:string) as element()?

Params
  • gradient as xs:string
Returns
  • element()?
declare function this:gradient($gradient as xs:string) as element()?
{
  this:get-gradient-definition($gradient)
}

Function: random-colour
declare function random-colour($gradients as xs:string*) as xs:string

Params
  • gradients as xs:string*
Returns
  • xs:string
declare function this:random-colour($gradients as xs:string*) as xs:string
{
  rand:select-random(this:colours($gradients))
}

Function: random-colour
declare function random-colour($gradients as xs:string*, $luminance as xs:double) as xs:string

Params
  • gradients as xs:string*
  • luminance as xs:double
Returns
  • xs:string
declare function this:random-colour($gradients as xs:string*, $luminance as xs:double) as xs:string
{
  rand:select-random(this:colours($gradients))=>
    rgb:rgb()=>cs:rgb-to-xyz()=>
    tone:luminate($luminance)=>
    cs:xyz-to-rgb()=>rgb:to-string()
}

Function: random-colour
declare function random-colour($gradients as xs:string*, $low-luminance as xs:double, $high-luminance as xs:double) as xs:string

Params
  • gradients as xs:string*
  • low-luminance as xs:double
  • high-luminance as xs:double
Returns
  • xs:string
declare function this:random-colour($gradients as xs:string*, $low-luminance as xs:double, $high-luminance as xs:double) as xs:string
{
  let $colour := rand:select-random(this:colours($gradients))
  let $xyz := $colour=>rgb:rgb()=>cs:rgb-to-xyz()
  let $luminance := tone:luminance($xyz)
  return (
    if (util:twixt($luminance, $low-luminance, $high-luminance))
    then $colour
    else (
      $xyz=>tone:luminate(rand:uniform($low-luminance, $high-luminance))=>cs:xyz-to-rgb()=>rgb:to-string()
    )
  )
}

Function: colours
declare function colours($gradients as xs:string*) as xs:string*


colours()
Return the colour strings from the named gradients in order.

Params
  • gradients as xs:string*
Returns
  • xs:string*
declare function this:colours($gradients as xs:string*) as xs:string*
{
  for $gradient in $gradients return (
    let $definition := this:get-gradient-definition($gradient)
    let $colours :=
      if (empty($definition)) then $gradient
      else $definition/svg:stop/@stop-color
    return
      if (matches($gradient,'-reverse')) then (
        reverse($colours)
      ) else (
        $colours
      )
  )
}

Function: colours
declare function colours($gradients as xs:string*, $low as xs:double, $high as xs:double) as xs:string*


colours()
Return the colour strings from the named gradients in order, but reluminated
to ensure their luminance is in the given range.

Params
  • gradients as xs:string*
  • low as xs:double
  • high as xs:double
Returns
  • xs:string*
declare function this:colours(
  $gradients as xs:string*,
  $low as xs:double,
  $high as xs:double
) as xs:string*
{
  let $low := util:clamp-some($low, 0, ())
  let $high := util:clamp-some($high, (), 1)
  let $colours := this:colours($gradients)
  return (
    if ($low <= 0 and $high >= 1) then $colours else (
      for $c in $colours
      let $xyz := rgb:rgb($c)=>cs:rgb-to-xyz()
      let $luminance := tone:luminance($xyz)
      return (
        if ($luminance < $low)
        then tone:luminate($xyz, $low)=>cs:xyz-to-rgb()=>rgb:to-string()
        else if ($luminance > $high)
        then tone:luminate($xyz, $high)=>cs:xyz-to-rgb()=>rgb:to-string()
        else $c
      )
    )
  )
}

Function: gradient-colours
declare function gradient-colours($gradients as xs:string*) as xs:string*

Params
  • gradients as xs:string*
Returns
  • xs:string*
declare function this:gradient-colours($gradients as xs:string*) as xs:string*
{
  this:colours($gradients)
}

Function: gradient-stops
declare function gradient-stops($gradient as xs:string) as element(svg:stop )*


gradient-stops()
Return all the gradient stops, as svg:stop elements, from the given gradient.

Params
  • gradient as xs:string
Returns
  • element(svg:stop)*
declare function this:gradient-stops($gradient as xs:string) as element(svg:stop
)*
{
  this:get-gradient-definition($gradient)/svg:stop
}

Function: n-gradient-colours
declare function n-gradient-colours($gradient as xs:string) as xs:integer

Params
  • gradient as xs:string
Returns
  • xs:integer
declare function this:n-gradient-colours($gradient as xs:string) as xs:integer
{
  let $n := count(this:gradient-stops($gradient))
  return
    if ($n=0) then 1
    else $n
}

Function: n-colours
declare function n-colours($gradient as xs:string) as xs:integer

Params
  • gradient as xs:string
Returns
  • xs:integer
declare function this:n-colours($gradient as xs:string) as xs:integer
{
  let $n := count(this:gradient-stops($gradient))
  return
    if ($n=0) then 1
    else $n
}

Function: gradient-points
declare function gradient-points($gradient as xs:string) as map(xs:string,item()*)*


gradient-points()
Return the points in the gradient as gradient points. This assumes
the stop values are in "rgb(r,g,b)" or hex format. Opacity gives the
alpha channel.

A gradient point has an offset and a colour point. The set of
gradient points can be used for interpolations and suchlike.
The colour points returned from this are RGBA points.
TODO: select output colour space

Params
  • gradient as xs:string
Returns
  • map(xs:string,item()*)*
declare function this:gradient-points($gradient as xs:string) as map(xs:string,item()*)*
{
  for $stop in this:gradient-stops($gradient)
  let $offset :=
    if (ends-with($stop/@offset,"%"))
    then xs:double(substring-before($stop/@offset,"%")) div 100
    else xs:double($stop/@offset)
  let $alpha :=
    if (empty($stop/@stop-opacity)) then 1
    else if (ends-with($stop/@stop-opacity,"%"))
    then xs:double(substring-before($stop/@stop-opacity,"%")) div 100
    else xs:double($stop/@stop-opacity)
  let $rgba := rgb:rgba(rgb:rgb($stop/@stop-color), $alpha)
  return stop:stop("rgb", $offset, $rgba)
}

Function: id
declare function id($gradient as element()) as xs:string

Params
  • gradient as element()
Returns
  • xs:string
declare function this:id($gradient as element()) as xs:string
{
  $gradient/@id
}

Function: merge
declare function merge($name as xs:string, $gradients as element()+, $do-interleave as xs:boolean) as element()

Params
  • name as xs:string
  • gradients as element()+
  • do-interleave as xs:boolean
Returns
  • element()
declare function this:merge(
  $name as xs:string,
  $gradients as element()+,
  $do-interleave as xs:boolean
) as element()
{
  let $kind :=
    typeswitch(head($gradients))
    case element(svg:radialGradient) return "outflow"
    default return "linear"
  let $max-n := max(for $gradient in $gradients return count($gradient//svg:stop))
  let $stops :=
    if ($do-interleave) then (
      for $i in 1 to $max-n
      for $gradient in $gradients
      return (
        ($gradient//svg:stop)[$i]
      )
    ) else (
      for $gradient in $gradients return $gradient//svg:stop
    )
  return (
    this:gradient-definition($name, $kind,
      for $stop in $stops return $stop/@stop-opacity,
      for $stop in $stops return $stop/@stop-color
    )
  )
}

Function: sample
declare function sample($colours as xs:string*, $num as xs:integer) as xs:string*

Params
  • colours as xs:string*
  • num as xs:integer
Returns
  • xs:string*
declare function this:sample($colours as xs:string*, $num as xs:integer) as xs:string*
{
  if (empty($colours) or $num <= 1) then () else
  let $sep := count($colours) div $num
  for $i in 1 to $num
  return $colours[1 + ceiling($sep*($i - 1))]
}

Function: sample
declare function sample($colours as xs:string*, $above as xs:double, $below as xs:double) as xs:string*

Params
  • colours as xs:string*
  • above as xs:double
  • below as xs:double
Returns
  • xs:string*
declare function this:sample($colours as xs:string*, $above as xs:double, $below as xs:double) as xs:string*
{
  this:sample($colours, count($colours), $above, $below)
}

Function: sample
declare function sample($colours as xs:string*, $num as xs:integer, $above as xs:double, $below as xs:double) as xs:string*

Params
  • colours as xs:string*
  • num as xs:integer
  • above as xs:double
  • below as xs:double
Returns
  • xs:string*
declare function this:sample($colours as xs:string*, $num as xs:integer, $above as xs:double, $below as xs:double) as xs:string*
{
  if (empty($colours) or $num <= 1) then () else
  let $n := count($colours)
  let $min-n := round($n * $above) cast as xs:integer
  let $max-n := round($n * $below) cast as xs:integer
  (: Asymmetry here because we want to allow 0 for $above and 1 for $below :)
  let $colours := $colours[position() > $min-n and position() <= $max-n]
  let $sep := count($colours) div $num
  for $i in 1 to $num
  return $colours[1 + ceiling($sep*($i - 1))]
}

Function: sample-stops
declare function sample-stops($stops as element(svg:stop)*, $num as xs:integer) as element(svg:stop)*

Params
  • stops as element(svg:stop)*
  • num as xs:integer
Returns
  • element(svg:stop)*
declare function this:sample-stops($stops as element(svg:stop)*, $num as xs:integer) as element(svg:stop)*
{
  if (empty($stops) or $num <= 1) then () else
  let $sep := count($stops) div $num
  for $i in 1 to $num
  return $stops[1 + ceiling($sep*($i - 1))]
}

Function: circularize
declare function circularize($colours as xs:string*) as xs:string*

Params
  • colours as xs:string*
Returns
  • xs:string*
declare function this:circularize($colours as xs:string*) as xs:string*
{
  tail($colours), tail(reverse($colours))
}

Function: luminance-range
declare function luminance-range($colour as xs:string, $n as xs:integer, $delta as xs:double) as xs:string*

Params
  • colour as xs:string
  • n as xs:integer
  • delta as xs:double
Returns
  • xs:string*
declare function this:luminance-range(
  $colour as xs:string,
  $n as xs:integer,
  $delta as xs:double
) as xs:string*
{
  let $rgb := rgb:rgb($colour)
  let $lum := tone:luminance($rgb)
  return (
    for $l in util:linspace($n, $lum - $delta, $lum, true())
    return tone:luminate($rgb, util:clamp($l, 0.0, 1.0))=>rgb:to-string(),
    $colour,
    for $l in util:linspace($n, $lum, $lum + $delta)=>tail()
    return tone:luminate($rgb, util:clamp($l, 0.0, 1.0))=>rgb:to-string()
  )
}

Function: colour
declare function colour($name as xs:string) as xs:string

Params
  • name as xs:string
Returns
  • xs:string
declare function this:colour($name as xs:string) as xs:string
{
  let $known := $rgb:KNOWN-COLOURS($name)
  return (
    if (empty($known)) then $name
    else $known
  )
}

Function: ref
declare function ref($name as xs:string) as xs:string

Params
  • name as xs:string
Returns
  • xs:string
declare function this:ref($name as xs:string) as xs:string
{
  let $known := $rgb:KNOWN-COLOURS($name)
  return (
    if (exists($known)) then $known
    else if ($name="none") then $name
    else if (matches($name, "^(rgb|url|#)")) then $name
    else "url(#"||this:safe($name)||")"
  )
}

Function: safe
declare function safe($name as xs:string) as xs:string

Params
  • name as xs:string
Returns
  • xs:string
declare function this:safe($name as xs:string) as xs:string
{
  replace(replace($name, "[(),#]", "-"), " ", "")
}

Function: average-luminance
declare function average-luminance($colours as xs:string*) as xs:double


average-luminance()
Average luminance: a way of determining relative brightness of a set
of colours.

Params
  • colours as xs:string*
Returns
  • xs:double
declare function this:average-luminance($colours as xs:string*) as xs:double
{
  avg(
    for $colour in $colours return (
      tone:luminance(cs:rgb-to-xyz(rgb:rgb($colour)))
    )
  )
}

Function: linear-gradient-definition
declare function linear-gradient-definition($name as xs:string, $layout as xs:integer*, (: x1, x2, y1, y2 :) $opacities as xs:double*, (: min, max :) $colours as xs:string*) as element(svg:linearGradient)

Params
  • name as xs:string
  • layout as xs:integer*
  • opacities as xs:double*
  • colours as xs:string*
Returns
  • element(svg:linearGradient)
declare function this:linear-gradient-definition(
  $name as xs:string,
  $layout as xs:integer*, (: x1, x2, y1, y2 :)
  $opacities as xs:double*, (: min, max :)
  $colours as xs:string*
) as element(svg:linearGradient)
{
  let $n := count($colours)
  let $x1 := ($layout[1],0)[1]
  let $x2 := ($layout[2],100)[1]
  let $y1 := ($layout[3],0)[1]
  let $y2 := ($layout[4],0)[1]
  let $start-opacity := ($opacities[1],1)[1]
  let $end-opacity := ($opacities[2],1)[1]
  return
    if ($start-opacity=$end-opacity) then (
      <svg:linearGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="{$x1}%" x2="{$x2}%" y1="{$y1}%" y2="{$y2}%">
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 100) then (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          (: let $_ := xdmp:log($i||" "||$colour||" "||$offset) :)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>,
            if (exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$start-opacity}"/>
            else ()
          )
        ) else (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$start-opacity}"/>
      }
      </svg:linearGradient>
    ) else (
      <svg:linearGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="{$x1}%" x2="{$x2}%" y1="{$y1}%" y2="{$y2}%">
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 100) then (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>,
            if (exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$opacity}"/>
            else ()
          )
        ) else (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$end-opacity}"/>
      }
      </svg:linearGradient>
    )
}

Function: linear-standard-layout
declare function linear-standard-layout($kind as xs:string) as xs:integer*

Params
  • kind as xs:string
Returns
  • xs:integer*
declare function this:linear-standard-layout($kind as xs:string) as xs:integer*
{
  switch ($kind)
  case "reverse" return (100,0,0,0)
  case "flip" return (0,0,0,100)
  case "invert" return (0,0,100,0)
  case "full" return (0,100,0,100)
  case "anti" return (100,0,100,0)
  case "slant" return (0,50,0,90)
  default return (0,100,0,0)
}

Function: linear-gradient-definition
declare function linear-gradient-definition($name as xs:string, $kind as xs:string, $colours as xs:string*) as element(svg:linearGradient)

Params
  • name as xs:string
  • kind as xs:string
  • colours as xs:string*
Returns
  • element(svg:linearGradient)
declare function this:linear-gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $colours as xs:string*
) as element(svg:linearGradient)
{
  this:linear-gradient-definition($name, this:linear-standard-layout($kind), (1,1), $colours)
}

Function: radial-gradient-definition
declare function radial-gradient-definition($name as xs:string, $layout as xs:integer*, (: r, cx, cy :) $opacities as xs:double*, $colours as xs:string*) as element(svg:radialGradient)

Params
  • name as xs:string
  • layout as xs:integer*
  • opacities as xs:double*
  • colours as xs:string*
Returns
  • element(svg:radialGradient)
declare function this:radial-gradient-definition(
  $name as xs:string,
  $layout as xs:integer*, (: r, cx, cy :)
  $opacities as xs:double*,
  $colours as xs:string*
) as element(svg:radialGradient)
{
  let $n := count($colours)
  let $r := ($layout[1],50)[1]
  let $cx := ($layout[2],50)[1]
  let $cy := ($layout[3],$cx)[1]
  let $start-opacity := ($opacities[1],1)[1]
  let $end-opacity := ($opacities[2],1)[1]
  return
    if ($start-opacity=$end-opacity) then (
      <svg:radialGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" cx="{$cx}%" cy="{$cy}%" r="{$r}%">      
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 5) then (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>,
            if ($n > 100 and exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$start-opacity}"/>
            else ()
          )
        ) else (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$start-opacity}"/>
      }
      </svg:radialGradient>
    ) else (
      <svg:radialGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" cx="{$cx}%" cy="{$cy}%" r="{$r}%">      
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 5) then (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>,
            if ($n > 100 and exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$opacity}"/>
             else ()
          )
        ) else (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$end-opacity}"/>
      }
      </svg:radialGradient>
    )
}

Function: radial-standard-layout
declare function radial-standard-layout($kind as xs:string) as xs:integer*

Params
  • kind as xs:string
Returns
  • xs:integer*
declare function this:radial-standard-layout($kind as xs:string) as xs:integer*
{
  if ($kind="stargate") then (50,50,80) else (50,50,50)
}

Function: radial-colours
declare function radial-colours($kind as xs:string, $colours as xs:string*) as xs:string*

Params
  • kind as xs:string
  • colours as xs:string*
Returns
  • xs:string*
declare function this:radial-colours($kind as xs:string, $colours as xs:string*) as xs:string*
{
  if ($kind="inflow") then reverse($colours) else $colours
}

Function: radial-gradient-definition
declare function radial-gradient-definition($name as xs:string, $kind as xs:string, $colours as xs:string*) as element(svg:radialGradient)

Params
  • name as xs:string
  • kind as xs:string
  • colours as xs:string*
Returns
  • element(svg:radialGradient)
declare function this:radial-gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $colours as xs:string*
) as element(svg:radialGradient)
{
  this:radial-gradient-definition($name, this:radial-standard-layout($kind), (1,1), this:radial-colours($kind, $colours))
}

Function: gradient-definition
declare function gradient-definition($name as xs:string, $kind as xs:string, $opacities as xs:double*, $colours as xs:string*) as element()

Params
  • name as xs:string
  • kind as xs:string
  • opacities as xs:double*
  • colours as xs:string*
Returns
  • element()
declare function this:gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $opacities as xs:double*,
  $colours as xs:string*
) as element()
{
  if ($kind=("inflow","outflow","stargate")) then (
    this:radial-gradient-definition($name, this:radial-standard-layout($kind), $opacities, this:radial-colours($kind, $colours))
  ) else (
    this:linear-gradient-definition($name, this:linear-standard-layout($kind), $opacities, $colours)
  )
}

Function: gradient-definition
declare function gradient-definition($name as xs:string, $kind as xs:string, $colours as xs:string*) as element()

Params
  • name as xs:string
  • kind as xs:string
  • colours as xs:string*
Returns
  • element()
declare function this:gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $colours as xs:string*
) as element()
{
  this:gradient-definition($name, $kind, (1,1), $colours)
}

Function: is-radial
declare function is-radial($def as element()) as xs:boolean

Params
  • def as element()
Returns
  • xs:boolean
declare function this:is-radial($def as element()) as xs:boolean
{
  local-name($def)="radialGradient"
}

Function: gradient-name
declare function gradient-name($base-gradient as element(), $name as xs:string) as element()

Params
  • base-gradient as element()
  • name as xs:string
Returns
  • element()
declare function this:gradient-name(
  $base-gradient as element(),
  $name as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-name($base-gradient, $name)
  else this:linear-name($base-gradient, $name)
}

Function: gradient-reverse
declare function gradient-reverse($base-gradient as element()) as element()

Params
  • base-gradient as element()
Returns
  • element()
declare function this:gradient-reverse(
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-reverse($base-gradient)
  else this:linear-reverse($base-gradient)
}

Function: gradient-reverse
declare function gradient-reverse($name as xs:string, $base-gradient as element()) as element()

Params
  • name as xs:string
  • base-gradient as element()
Returns
  • element()
declare %art:deprecated function this:gradient-reverse(
  $name as xs:string,
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-reverse($base-gradient)=>this:radial-name($name)
  else this:linear-reverse($base-gradient)=>this:linear-name($name)
}

Function: gradient-random
declare function gradient-random($base-gradient as element()) as element()

Params
  • base-gradient as element()
Returns
  • element()
declare function this:gradient-random(
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-random($base-gradient)
  else this:linear-random($base-gradient)
}

Function: gradient-random
declare function gradient-random($name as xs:string, $base-gradient as element()) as element()

Params
  • name as xs:string
  • base-gradient as element()
Returns
  • element()
declare %art:deprecated function this:gradient-random(
  $name as xs:string,
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-random($base-gradient)=>this:radial-name($name)
  else this:linear-random($base-gradient)=>this:linear-name($name)
}

Function: gradient-sample
declare function gradient-sample($base-gradient as element(), $num as xs:integer) as element()

Params
  • base-gradient as element()
  • num as xs:integer
Returns
  • element()
declare function this:gradient-sample(
  $base-gradient as element(),
  $num as xs:integer
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-sample($base-gradient, $num)
  else this:linear-sample($base-gradient, $num)
}

Function: gradient-sample
declare function gradient-sample($name as xs:string, $base-gradient as element(), $num as xs:integer) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • num as xs:integer
Returns
  • element()
declare %art:deprecated function this:gradient-sample(
  $name as xs:string,
  $base-gradient as element(),
  $num as xs:integer
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-sample($base-gradient, $num)=>this:radial-name($name)
  else this:linear-sample($base-gradient, $num)=>this:linear-name($name)
}

Function: gradient-fade
declare function gradient-fade($base-gradient as element(), $start-opacity as xs:double, $end-opacity as xs:double) as element()

Params
  • base-gradient as element()
  • start-opacity as xs:double
  • end-opacity as xs:double
Returns
  • element()
declare function this:gradient-fade(
  $base-gradient as element(),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-fade($base-gradient, $start-opacity, $end-opacity)
  else this:linear-fade($base-gradient, $start-opacity, $end-opacity)
}

Function: gradient-fade
declare function gradient-fade($name as xs:string, $base-gradient as element(), $start-opacity as xs:double, $end-opacity as xs:double) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • start-opacity as xs:double
  • end-opacity as xs:double
Returns
  • element()
declare %art:deprecated function this:gradient-fade(
  $name as xs:string,
  $base-gradient as element(),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-fade($base-gradient, $start-opacity, $end-opacity)=>this:radial-name($name)
  else this:linear-fade($base-gradient, $start-opacity, $end-opacity)=>this:linear-name($name)
}

Function: gradient-transform
declare function gradient-transform($base-gradient as element(), $transform as xs:string) as element()

Params
  • base-gradient as element()
  • transform as xs:string
Returns
  • element()
declare function this:gradient-transform(
  $base-gradient as element(),
  $transform as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-transform($base-gradient, $transform)
  else this:linear-transform($base-gradient, $transform)
}

Function: gradient-transform
declare function gradient-transform($name as xs:string, $base-gradient as element(), $transform as xs:string) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • transform as xs:string
Returns
  • element()
declare %art:deprecated function this:gradient-transform(
  $name as xs:string,
  $base-gradient as element(),
  $transform as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-transform($base-gradient, $transform)=>this:radial-name($name)
  else this:linear-transform($base-gradient, $transform)=>this:linear-name($name)
}

Function: gradient-units
declare function gradient-units($base-gradient as element(), $objectBoundingBox as xs:boolean) as element()

Params
  • base-gradient as element()
  • objectBoundingBox as xs:boolean
Returns
  • element()
declare function this:gradient-units(
  $base-gradient as element(),
  $objectBoundingBox as xs:boolean
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-units($base-gradient, $objectBoundingBox)
  else this:linear-units($base-gradient, $objectBoundingBox)
}

Function: gradient-units
declare function gradient-units($name as xs:string, $base-gradient as element(), $objectBoundingBox as xs:boolean) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • objectBoundingBox as xs:boolean
Returns
  • element()
declare %art:deprecated function this:gradient-units(
  $name as xs:string,
  $base-gradient as element(),
  $objectBoundingBox as xs:boolean
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-units($base-gradient, $objectBoundingBox)=>this:radial-name($name)
  else this:linear-units($base-gradient, $objectBoundingBox)=>this:linear-name($name)
}

Function: gradient-spread
declare function gradient-spread($base-gradient as element(), $method as xs:string) as element()

Params
  • base-gradient as element()
  • method as xs:string
Returns
  • element()
declare function this:gradient-spread(
  $base-gradient as element(),
  $method as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-spread($base-gradient, $method)
  else this:linear-spread($base-gradient, $method)
}

Function: gradient-spread
declare function gradient-spread($name as xs:string, $base-gradient as element(), $method as xs:string) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • method as xs:string
Returns
  • element()
declare %art:deprecated function this:gradient-spread(
  $name as xs:string,
  $base-gradient as element(),
  $method as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-spread($base-gradient, $method)=>this:radial-name($name)
  else this:linear-spread($base-gradient, $method)=>this:linear-name($name)
}

Function: gradient-layout
declare function gradient-layout($base-gradient as element(), $layout as xs:integer*) as element()

Params
  • base-gradient as element()
  • layout as xs:integer*
Returns
  • element()
declare function this:gradient-layout(
  $base-gradient as element(),
  $layout as xs:integer*
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-layout($base-gradient, $layout)
  else this:linear-layout($base-gradient, $layout)
}

Function: gradient-layout
declare function gradient-layout($name as xs:string, $base-gradient as element(), $layout as xs:integer*) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • layout as xs:integer*
Returns
  • element()
declare %art:deprecated function this:gradient-layout(
  $name as xs:string,
  $base-gradient as element(),
  $layout as xs:integer*
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-layout($base-gradient, $layout)=>this:radial-name($name)
  else this:linear-layout($base-gradient, $layout)=>this:linear-name($name)
}

Function: gradient-interleave
declare function gradient-interleave($base-gradient as element(), $interleave as xs:string) as element()

Params
  • base-gradient as element()
  • interleave as xs:string
Returns
  • element()
declare function this:gradient-interleave(
  $base-gradient as element(),
  $interleave as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-interleave($base-gradient, $interleave)
  else this:linear-interleave($base-gradient, $interleave)
}

Function: gradient-interleave
declare function gradient-interleave($name as xs:string, $base-gradient as element(), $interleave as xs:string) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • interleave as xs:string
Returns
  • element()
declare %art:deprecated function this:gradient-interleave(
  $name as xs:string,
  $base-gradient as element(),
  $interleave as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-interleave($base-gradient, $interleave)=>this:radial-name($name)
  else this:linear-interleave($base-gradient, $interleave)=>this:linear-name($name)
}

Function: gradient-opacity
declare function gradient-opacity($base-gradient as element(), $opacity as function(xs:integer) as xs:double) as element()

Params
  • base-gradient as element()
  • opacity as function(xs:integer)asxs:double
Returns
  • element()
declare function this:gradient-opacity(
  $base-gradient as element(),
  $opacity as function(xs:integer) as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-opacity($base-gradient, $opacity)
  else this:linear-opacity($base-gradient, $opacity)
}

Function: gradient-opacity
declare function gradient-opacity($name as xs:string, $base-gradient as element(), $opacity as function(xs:integer) as xs:double) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • opacity as function(xs:integer)asxs:double
Returns
  • element()
declare %art:deprecated function this:gradient-opacity(
  $name as xs:string,
  $base-gradient as element(),
  $opacity as function(xs:integer) as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-opacity($base-gradient, $opacity)=>this:radial-name($name)
  else this:linear-opacity($base-gradient, $opacity)=>this:linear-name($name)
}

Function: gradient-colour
declare function gradient-colour($base-gradient as element(), $colour as function(xs:integer) as xs:string) as element()

Params
  • base-gradient as element()
  • colour as function(xs:integer)asxs:string
Returns
  • element()
declare function this:gradient-colour(
  $base-gradient as element(),
  $colour as function(xs:integer) as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-colour($base-gradient, $colour)
  else this:linear-colour($base-gradient, $colour)
}

Function: gradient-colour
declare function gradient-colour($name as xs:string, $base-gradient as element(), $colour as function(xs:integer) as xs:string) as element()

Params
  • name as xs:string
  • base-gradient as element()
  • colour as function(xs:integer)asxs:string
Returns
  • element()
declare %art:deprecated function this:gradient-colour(
  $name as xs:string,
  $base-gradient as element(),
  $colour as function(xs:integer) as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-colour($base-gradient, $colour)=>this:radial-name($name)
  else this:linear-colour($base-gradient, $colour)=>this:linear-name($name)
}

Function: gradient-ref
declare function gradient-ref($name as xs:string, $base-gradient-name as xs:string) as element()

Params
  • name as xs:string
  • base-gradient-name as xs:string
Returns
  • element()
declare function this:gradient-ref(
  $name as xs:string,
  $base-gradient-name as xs:string
) as element()
{
  let $def := this:gradient($base-gradient-name)
  return (
    if (exists($def) and this:is-radial($def))
    then this:radial-ref($name, $base-gradient-name)
    else this:linear-ref($name, $base-gradient-name)
  )
}

Function: linear-name
declare function linear-name($base-gradient as element(svg:linearGradient), $name as xs:string) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • name as xs:string
Returns
  • element(svg:linearGradient)
declare function this:linear-name(
  $base-gradient as element(svg:linearGradient),
  $name as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient id="{this:safe($name)}">{
    $base-gradient/(@* except @id),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-reverse
declare function linear-reverse($base-gradient as element(svg:linearGradient)) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:linearGradient)
declare function this:linear-reverse(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@x2}" x2="{$base-gradient/@x1}"
                      y1="{$base-gradient/@y2}" y2="{$base-gradient/@y1}">{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-flip
declare function linear-flip($base-gradient as element(svg:linearGradient)) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:linearGradient)
declare function this:linear-flip(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@y1}" x2="{$base-gradient/@y2}"
                      y1="{$base-gradient/@x1}" y2="{$base-gradient/@x2}"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-invert
declare function linear-invert($base-gradient as element(svg:linearGradient)) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:linearGradient)
declare function this:linear-invert(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@y1}" x2="{$base-gradient/@y2}"
                      y1="{$base-gradient/@x2}" y2="{$base-gradient/@x1}"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-full
declare function linear-full($base-gradient as element(svg:linearGradient)) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:linearGradient)
declare function this:linear-full(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient y1="{$base-gradient/@x1}" y2="{$base-gradient/@x2}"
  >{
    $base-gradient/(@* except (@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-anti
declare function linear-anti($base-gradient as element(svg:linearGradient)) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:linearGradient)
declare function this:linear-anti(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@x2}" x2="{$base-gradient/@x1}"
                      y1="{$base-gradient/@x2}" y2="{$base-gradient/@x1}"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-slant
declare function linear-slant($base-gradient as element(svg:linearGradient)) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:linearGradient)
declare function this:linear-slant(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="0%" x2="50%"
                      y1="0%" y2="90%"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-adjust
declare function linear-adjust($base-gradient as element(svg:linearGradient), $xys as xs:integer*) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • xys as xs:integer*
Returns
  • element(svg:linearGradient)
declare function this:linear-adjust(
  $base-gradient as element(svg:linearGradient),
  $xys as xs:integer* (: x1, y1, x2, y2 :)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$xys[1]}%" x2="{$xys[3]}%"
                      y1="{$xys[2]}%" y2="{$xys[4]}%"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-random
declare function linear-random($base-gradient as element(svg:linearGradient)) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:linearGradient)
declare function this:linear-random(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  let $stops := rand:shuffle($base-gradient/svg:stop)
  let $percentages := util:linspace(count($stops), 0, 100)!util:decimal(., 2)
  return
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $stops
      return (
        <svg:stop offset="{$percentages[$i]}%">{
          $stop/(@* except @offset)
        }</svg:stop>
      )
    }</svg:linearGradient>
}

Function: linear-interleave
declare function linear-interleave($base-gradient as element(svg:linearGradient), $interleave as xs:string) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • interleave as xs:string
Returns
  • element(svg:linearGradient)
declare function this:linear-interleave(
  $base-gradient as element(svg:linearGradient),
  $interleave as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    return (
      if ($i mod 2 = 0) then $stop
      else (
        <svg:stop stop-color="{$interleave}">{
          $stop/(@* except @stop-color)
        }</svg:stop>
      )
    )
  }</svg:linearGradient>
}

Function: linear-opacity
declare function linear-opacity($base-gradient as element(svg:linearGradient), $opacity as function(xs:integer) as xs:double) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • opacity as function(xs:integer)asxs:double
Returns
  • element(svg:linearGradient)
declare function this:linear-opacity(
  $base-gradient as element(svg:linearGradient),
  $opacity as function(xs:integer) as xs:double
) as element(svg:linearGradient)
{
  <svg:linearGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-opacity := util:decimal(util:clamp($opacity($i),0,1),3)
    return (
      <svg:stop stop-opacity="{$stop-opacity}">{
        $stop/(@* except @stop-opacity)
      }</svg:stop>
    )
  }</svg:linearGradient>
}

Function: linear-colour
declare function linear-colour($base-gradient as element(svg:linearGradient), $colour as function(xs:integer) as xs:string) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • colour as function(xs:integer)asxs:string
Returns
  • element(svg:linearGradient)
declare function this:linear-colour(
  $base-gradient as element(svg:linearGradient),
  $colour as function(xs:integer) as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-colour := $colour($i)
    return (
      <svg:stop stop-color="{$stop-colour}">{
        $stop/(@* except @stop-color)
      }</svg:stop>
    )
  }</svg:linearGradient>
}

Function: linear-sample
declare function linear-sample($base-gradient as element(svg:linearGradient), $num as xs:integer) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • num as xs:integer
Returns
  • element(svg:linearGradient)
declare function this:linear-sample(
  $base-gradient as element(svg:linearGradient),
  $num as xs:integer
) as element(svg:linearGradient)
{
  if ($num <= 1) then (
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/*
    }</svg:linearGradient>
  ) else (
    let $stops := $base-gradient/svg:stop
    let $percentages := util:linspace($num, 0, 100)!util:decimal(., 2)
    let $sep := count($stops) div $num
    return
      if (count($stops) <= $num) then (
        <svg:linearGradient>{
          $base-gradient/@*,
          $base-gradient/*
        }</svg:linearGradient>
      ) else (
        <svg:linearGradient>{
          $base-gradient/@*,
          $base-gradient/(* except svg:stop),
          for $i in 1 to $num
          let $stop := $stops[1 + ceiling($sep*($i - 1))]
          return (
            <svg:stop offset="{$percentages[$i]}%">{
              $stop/(@* except @offset)
            }</svg:stop>
          )
        }</svg:linearGradient>
      )
  )
}

Function: linear-fade
declare function linear-fade($base-gradient as element(svg:linearGradient), $start-opacity as xs:double, $end-opacity as xs:double) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • start-opacity as xs:double
  • end-opacity as xs:double
Returns
  • element(svg:linearGradient)
declare function this:linear-fade(
  $base-gradient as element(svg:linearGradient),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element(svg:linearGradient)
{
  let $n := count($base-gradient/svg:stop)
  let $delta := ($end-opacity - $start-opacity) div ($n - 1)
  return
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
      return (
        <svg:stop stop-opacity="{$opacity}">{
          $stop/(@* except @stop-opacity)
        }</svg:stop>
      )
    }</svg:linearGradient>
}

Function: linear-symmetric-fade
declare function linear-symmetric-fade($base-gradient as element(svg:linearGradient), $start-opacity as xs:double, $peak-opacity as xs:double, $end-opacity as xs:double) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • start-opacity as xs:double
  • peak-opacity as xs:double
  • end-opacity as xs:double
Returns
  • element(svg:linearGradient)
declare function this:linear-symmetric-fade(
  $base-gradient as element(svg:linearGradient),
  $start-opacity as xs:double,
  $peak-opacity as xs:double,
  $end-opacity as xs:double
) as element(svg:linearGradient)
{
  let $n := count($base-gradient/svg:stop)
  let $half := $n idiv 2
  let $delta1 := ($peak-opacity - $start-opacity) div ($n idiv 2)
  let $delta2 := ($end-opacity - $peak-opacity) div (($n + 1) idiv 2)
  return
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      let $opacity :=
        if ($i <= $half)
        then util:decimal($start-opacity + $delta1*($i - 1), 2)
        else util:decimal($peak-opacity + $delta2*($i - $half - 1), 2)
      return (
        <svg:stop stop-opacity="{$opacity}">{
          $stop/(@* except @stop-opacity)
        }</svg:stop>
      )
    }</svg:linearGradient>
}

Function: linear-transform
declare function linear-transform($base-gradient as element(svg:linearGradient), $transform as xs:string) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • transform as xs:string
Returns
  • element(svg:linearGradient)
declare function this:linear-transform(
  $base-gradient as element(svg:linearGradient),
  $transform as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient gradientTransform="{$transform}">{
    $base-gradient/(@* except @gradientTransform),
    $base-gradient/*
  }</svg:linearGradient>
}

Function: linear-units
declare function linear-units($base-gradient as element(svg:linearGradient), $objectBoundingBox as xs:boolean) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • objectBoundingBox as xs:boolean
Returns
  • element(svg:linearGradient)
declare function this:linear-units(
  $base-gradient as element(svg:linearGradient),
  $objectBoundingBox as xs:boolean
) as element(svg:linearGradient)
{
  let $val := if ($objectBoundingBox) then "objectBoundingBox" else "userSpaceOnUse"
  return
    <svg:linearGradient gradientUnits="{$val}">{
      $base-gradient/(@* except @gradientUnits),
      $base-gradient/*
    }</svg:linearGradient>
}

Function: linear-spread
declare function linear-spread($base-gradient as element(svg:linearGradient), $method as xs:string) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • method as xs:string
Returns
  • element(svg:linearGradient)
declare function this:linear-spread(
  $base-gradient as element(svg:linearGradient),
  $method as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient spreadMethod="{$method}">{
    $base-gradient/(@* except @spreadMethod),
    $base-gradient/*
  }</svg:linearGradient>  
}

Function: linear-layout
declare function linear-layout($base-gradient as element(svg:linearGradient), $layout as xs:integer*) as element(svg:linearGradient)

Params
  • base-gradient as element(svg:linearGradient)
  • layout as xs:integer*
Returns
  • element(svg:linearGradient)
declare function this:linear-layout(
  $base-gradient as element(svg:linearGradient),
  $layout as xs:integer*
) as element(svg:linearGradient)
{
  let $x1 := ($layout[1],0)[1]
  let $x2 := ($layout[2],100)[1]
  let $y1 := ($layout[3],0)[1]
  let $y2 := ($layout[4],0)[1]
  return
  <svg:linearGradient x1="{$x1}%" x2="{$x2}%" y1="{$y1}%" y2="{$y2}%">{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>  
}

Function: linear-ref
declare function linear-ref($name as xs:string, $base-gradient-name as xs:string) as element(svg:linearGradient)

Params
  • name as xs:string
  • base-gradient-name as xs:string
Returns
  • element(svg:linearGradient)
declare function this:linear-ref(
  $name as xs:string,
  $base-gradient-name as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient id="{this:safe($name)}" xlink:href="#{$base-gradient-name}"/>
}

Function: radial-name
declare function radial-name($base-gradient as element(svg:radialGradient), $name as xs:string) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • name as xs:string
Returns
  • element(svg:radialGradient)
declare function this:radial-name(
  $base-gradient as element(svg:radialGradient),
  $name as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient id="{this:safe($name)}">{
    $base-gradient/(@* except @id),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-outflow
declare function radial-outflow($base-gradient as element(svg:linearGradient)) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:radialGradient)
declare function this:radial-outflow(
  $base-gradient as element(svg:linearGradient)
) as element(svg:radialGradient)
{
  <svg:radialGradient cx="50%" cy="50%" r="50%">{
    $base-gradient/(@* except (@r|@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-inflow
declare function radial-inflow($base-gradient as element(svg:linearGradient)) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:linearGradient)
Returns
  • element(svg:radialGradient)
declare function this:radial-inflow(
  $base-gradient as element(svg:linearGradient)
) as element(svg:radialGradient)
{
  let $offsets :=
    reverse(
      for $offset in $base-gradient/svg:stop/@offset
      return
        if (contains($offset,"%"))
        then util:decimal(100.0 - number(substring-before($offset,"%")), 2)
        else util:decimal(100.0 - number($offset), 2)
    )
  let $colours := reverse($base-gradient/svg:stop/@stop-color)
  let $opacities := reverse($base-gradient/svg:stop/@stop-opacity)
  return (
    <svg:radialGradient cx="50%" cy="50%" r="50%">{
      $base-gradient/(@* except (@r|@cx|@cy)),
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      return (
        <svg:stop offset="{$offsets[$i]}%" stop-color="{$colours[$i]}" stop-opacity="{$opacities[$i]}"/>
      )
    }</svg:radialGradient>
  )
}

Function: radial-reverse
declare function radial-reverse($base-gradient as element(svg:radialGradient)) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
Returns
  • element(svg:radialGradient)
declare function this:radial-reverse(
  $base-gradient as element(svg:radialGradient)
) as element(svg:radialGradient)
{
  let $offsets :=
    reverse(
      for $offset in $base-gradient/svg:stop/@offset
      return
        if (contains($offset,"%"))
        then util:decimal(100.0 - number(substring-before($offset,"%")), 2)
        else util:decimal(100.0 - number($offset), 2)
    )
  let $colours := reverse($base-gradient/svg:stop/@stop-color)
  let $opacities := reverse($base-gradient/svg:stop/@stop-opacity)
  return (
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      return (
        <svg:stop offset="{$offsets[$i]}%" stop-color="{$colours[$i]}" stop-opacity="{$opacities[$i]}"/>
      )
    }</svg:radialGradient>
  )
}

Function: radial-random
declare function radial-random($base-gradient as element(svg:radialGradient)) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
Returns
  • element(svg:radialGradient)
declare function this:radial-random(
  $base-gradient as element(svg:radialGradient)
) as element(svg:radialGradient)
{
  let $stops := rand:shuffle($base-gradient/svg:stop)
  let $percentages := util:linspace(count($stops), 0, 100)!util:decimal(., 2)
  return (
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $stops
      return (
        <svg:stop offset="{$percentages[$i]}%">{
          $stop/(@* except @offset)
        }</svg:stop>
      )
    }</svg:radialGradient>
  )
}

Function: radial-interleave
declare function radial-interleave($base-gradient as element(svg:radialGradient), $interleave as xs:string) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • interleave as xs:string
Returns
  • element(svg:radialGradient)
declare function this:radial-interleave(
  $base-gradient as element(svg:radialGradient),
  $interleave as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    return (
      if ($i mod 2 = 0) then $stop
      else (
        <svg:stop stop-color="{$interleave}">{
          $stop/(@* except @stop-color)
        }</svg:stop>
      )
    )
  }</svg:radialGradient>
}

Function: radial-opacity
declare function radial-opacity($base-gradient as element(svg:radialGradient), $opacity as function(xs:integer) as xs:double) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • opacity as function(xs:integer)asxs:double
Returns
  • element(svg:radialGradient)
declare function this:radial-opacity(
  $base-gradient as element(svg:radialGradient),
  $opacity as function(xs:integer) as xs:double
) as element(svg:radialGradient)
{
  <svg:radialGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-opacity := util:decimal(util:clamp($opacity($i),0,1),3)
    return (
      <svg:stop stop-opacity="{$stop-opacity}">{
        $stop/(@* except @stop-opacity)
      }</svg:stop>
    )
  }</svg:radialGradient>
}

Function: radial-colour
declare function radial-colour($base-gradient as element(svg:radialGradient), $colour as function(xs:integer) as xs:string) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • colour as function(xs:integer)asxs:string
Returns
  • element(svg:radialGradient)
declare function this:radial-colour(
  $base-gradient as element(svg:radialGradient),
  $colour as function(xs:integer) as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-colour := $colour($i)
    return (
      <svg:stop stop-color="{$stop-colour}">{
        $stop/(@* except @stop-color)
      }</svg:stop>
    )
  }</svg:radialGradient>
}

Function: radial-sample
declare function radial-sample($base-gradient as element(svg:radialGradient), $num as xs:integer) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • num as xs:integer
Returns
  • element(svg:radialGradient)
declare function this:radial-sample(
  $base-gradient as element(svg:radialGradient),
  $num as xs:integer
) as element(svg:radialGradient)
{
  if ($num <= 1) then (
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/*
    }</svg:radialGradient>
  ) else (
    let $stops := $base-gradient/svg:stop
    let $percentages := util:linspace($num, 0, 100)!util:decimal(., 2)
    let $sep := count($stops) div $num
    return
      if (count($stops) <= $num) then (
        <svg:radialGradient>{
          $base-gradient/@*,
          $base-gradient/*
        }</svg:radialGradient>
      ) else (
        <svg:radialGradient>{
          $base-gradient/@*,
          $base-gradient/(* except svg:stop),
          for $i in 1 to $num
          let $stop := $stops[1 + ceiling($sep*($i - 1))]
          return (
            <svg:stop offset="{$percentages[$i]}%">{$stop/(@* except @offset)}</svg:stop>
          )
        }</svg:radialGradient>
      )
  )
}

Function: radial-fade
declare function radial-fade($base-gradient as element(svg:radialGradient), $start-opacity as xs:double, $end-opacity as xs:double) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • start-opacity as xs:double
  • end-opacity as xs:double
Returns
  • element(svg:radialGradient)
declare function this:radial-fade(
  $base-gradient as element(svg:radialGradient),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element(svg:radialGradient)
{
  let $n := count($base-gradient/svg:stop)
  let $delta := ($end-opacity - $start-opacity) div $n
  return
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
      return (
        <svg:stop stop-opacity="{$opacity}">{
          $stop/(@* except @stop-opacity)
        }</svg:stop>
      )
    }</svg:radialGradient>
}

Function: radial-units
declare function radial-units($base-gradient as element(svg:radialGradient), $objectBoundingBox as xs:boolean) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • objectBoundingBox as xs:boolean
Returns
  • element(svg:radialGradient)
declare function this:radial-units(
  $base-gradient as element(svg:radialGradient),
  $objectBoundingBox as xs:boolean
) as element(svg:radialGradient)
{
  let $val := if ($objectBoundingBox) then "objectBoundingBox" else "userSpaceOnUse"
  return
    <svg:radialGradient gradientUnits="{$val}">{
      $base-gradient/(@* except @gradientUnits),
      $base-gradient/*
    }</svg:radialGradient>
}

Function: radial-center
declare function radial-center($base-gradient as element(svg:radialGradient), $cx-percent as xs:integer, $cy-percent as xs:integer) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • cx-percent as xs:integer
  • cy-percent as xs:integer
Returns
  • element(svg:radialGradient)
declare function this:radial-center(
  $base-gradient as element(svg:radialGradient),
  $cx-percent as xs:integer,
  $cy-percent as xs:integer
) as element(svg:radialGradient)
{
  <svg:radialGradient cx="{$cx-percent}%" cy="{$cy-percent}%">{
    $base-gradient/(@* except (@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-focus
declare function radial-focus($base-gradient as element(svg:radialGradient), $fx-percent as xs:integer, $fy-percent as xs:integer) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • fx-percent as xs:integer
  • fy-percent as xs:integer
Returns
  • element(svg:radialGradient)
declare function this:radial-focus(
  $base-gradient as element(svg:radialGradient),
  $fx-percent as xs:integer,
  $fy-percent as xs:integer
) as element(svg:radialGradient)
{
  <svg:radialGradient fx="{$fx-percent}%" fy="{$fy-percent}%">{
    $base-gradient/(@* except (@fx|@fy)),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-radius
declare function radial-radius($base-gradient as element(svg:radialGradient), $r-percent as xs:integer) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • r-percent as xs:integer
Returns
  • element(svg:radialGradient)
declare function this:radial-radius(
  $base-gradient as element(svg:radialGradient),
  $r-percent as xs:integer
) as element(svg:radialGradient)
{
  <svg:radialGradient r="{$r-percent}%">{
    $base-gradient/(@* except (@r)),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-circle
declare function radial-circle($base-gradient as element(svg:radialGradient), $radial-circle as map(xs:string,item()*)) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • radial-circle as map(xs:string,item()*)
Returns
  • element(svg:radialGradient)
declare function this:radial-circle(
  $base-gradient as element(svg:radialGradient),
  $radial-circle as map(xs:string,item()*) (: all as percents :)
) as element(svg:radialGradient)
{
  let $r := ellipse:radius($radial-circle)
  let $center := ellipse:center($radial-circle)
  let $cx := point:x($center)
  let $cy := point:y($center)
  return
  <svg:radialGradient cx="{$cx}%" cy="{$cy}%" r="{$r}%">{
    $base-gradient/(@* except (@r|@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-transform
declare function radial-transform($base-gradient as element(svg:radialGradient), $transform as xs:string) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • transform as xs:string
Returns
  • element(svg:radialGradient)
declare function this:radial-transform(
  $base-gradient as element(svg:radialGradient),
  $transform as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient gradientTransform="{$transform}">{
    $base-gradient/(@* except @gradientTransform),
    $base-gradient/*
  }</svg:radialGradient>
}

Function: radial-spread
declare function radial-spread($base-gradient as element(svg:radialGradient), $method as xs:string) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • method as xs:string
Returns
  • element(svg:radialGradient)
declare function this:radial-spread(
  $base-gradient as element(svg:radialGradient),
  $method as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient spreadMethod="{$method}">{
    $base-gradient/(@* except @spreadMethod),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-layout
declare function radial-layout($base-gradient as element(svg:radialGradient), $layout as xs:integer*) as element(svg:radialGradient)

Params
  • base-gradient as element(svg:radialGradient)
  • layout as xs:integer*
Returns
  • element(svg:radialGradient)
declare function this:radial-layout(
  $base-gradient as element(svg:radialGradient),
  $layout as xs:integer* (: r, cx, cy :)
) as element(svg:radialGradient)
{
  let $r := ($layout[1],50)[1]
  let $cx := ($layout[2],50)[1]
  let $cy := ($layout[3],$cx)[1]
  return
  <svg:radialGradient cx="{$cx}%" cy="{$cy}%" r="{$r}%">{
    $base-gradient/(@* except (@r|@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
}

Function: radial-ref
declare function radial-ref($name as xs:string, $base-gradient-name as xs:string) as element(svg:radialGradient)

Params
  • name as xs:string
  • base-gradient-name as xs:string
Returns
  • element(svg:radialGradient)
declare function this:radial-ref(
  $name as xs:string,
  $base-gradient-name as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient id="{this:safe($name)}" xlink:href="#{$base-gradient-name}"/>
}

Function: expand-evenly
declare function expand-evenly($num-stops as xs:integer, $base-rgb-str as xs:string*, $do-spline as xs:boolean) as xs:string*

Params
  • num-stops as xs:integer
  • base-rgb-str as xs:string*
  • do-spline as xs:boolean
Returns
  • xs:string*
declare function this:expand-evenly(
  $num-stops as xs:integer,
  $base-rgb-str as xs:string*,
  $do-spline as xs:boolean
) as xs:string*
{
  (: To end up w/ 512 stops, we need to add n interpolations to each edge
   : we have ns stops already, so:
   : (ns - 1)*n + ns = 512
   : n = (512 - ns)/(ns - 1)
   : We can only interpolate an integer number of points so that
   : leaves us a residue which we use as a probability of adding another point
   :)
  let $ns := count($base-rgb-str)
  let $n := ($num-stops - $ns) idiv ($ns - 1)
  let $residue := 100 * ($ns - 1) * ((($num-stops - $ns) div ($ns - 1)) - $n)
  let $base-rgb := $base-rgb-str!rgb:rgb(.)
  let $base-hsl := $base-rgb!cs:rgb-to-hsluv(.)
  let $expanded-hsl := (
    $base-hsl[1],
    let $edges :=
      if ($do-spline) then (
        spline:spline($base-hsl)=>path:edges()
      ) else (
        edge:to-edges($base-hsl)
      )
    for $edge in $edges
    let $n-interp :=
      if (rand:flip($residue)) then $n + 1 else $n
    let $interp := tail(edge:interpolate($n-interp, $edge))
    return $interp
  )
  let $expanded-rgb := $expanded-hsl!cs:hsluv-to-rgb(.)
  let $expanded-rgb-str := $expanded-rgb!rgb:to-string(.)
  return $expanded-rgb-str
}

Function: expand-scaled
declare function expand-scaled($num-stops as xs:integer, $base-rgb-str as xs:string*, $base-percents as xs:double*, $do-spline as xs:boolean) as xs:string*

Params
  • num-stops as xs:integer
  • base-rgb-str as xs:string*
  • base-percents as xs:double*
  • do-spline as xs:boolean
Returns
  • xs:string*
declare function this:expand-scaled(
  $num-stops as xs:integer,
  $base-rgb-str as xs:string*,
  $base-percents as xs:double*,
  $do-spline as xs:boolean
) as xs:string*
{
  let $ns := count($base-rgb-str)
  let $edge-fractions :=
    for $percent at $i in tail($base-percents)
    return ($percent - $base-percents[$i]) div 100.0
  (: To end up w/ 512 stops, we need to add 512 - ns total
   : allocated in accordance with the percentage differences for each
   : edge
   : Use $num-stops - 1 because the percentages end up including the end
   : points so that works better
   : We can only interpolate an integer number of points so that
   : leaves us a residue which we use as a probability of adding another point
   :)
  let $edge-ns :=
    let $total := $num-stops - 1 (: $ns :)
    for $fraction in $edge-fractions
    return round($total * $fraction) cast as xs:integer
  let $residue := 100 * (($num-stops - (: $ns :) 1) - sum($edge-ns)) div ($ns - 1)
  let $base-rgb := $base-rgb-str!rgb:rgb(.)
  let $base-hsl := $base-rgb!cs:rgb-to-hsluv(.)
  let $expanded-hsl := (
    $base-hsl[1],
    let $edges :=
      if ($do-spline) then (
        spline:spline($base-hsl)=>path:edges()
      ) else (
        edge:to-edges($base-hsl)
      )
    for $edge at $i in $edges
    let $n-interp :=
      if (rand:flip($residue)) then $edge-ns[$i] + 1 else $edge-ns[$i]
    let $interp := tail(edge:interpolate($n-interp, $edge))
    return $interp
  )
  let $expanded-rgb := $expanded-hsl!cs:hsluv-to-rgb(.)
  let $expanded-rgb-str := $expanded-rgb!rgb:to-string(.)
  return $expanded-rgb-str
}

Function: expand-gradient
declare function expand-gradient($num-stops as xs:integer, $base-gradient as xs:string, $do-even as xs:boolean, $do-spline as xs:boolean) as xs:string*

Params
  • num-stops as xs:integer
  • base-gradient as xs:string
  • do-even as xs:boolean
  • do-spline as xs:boolean
Returns
  • xs:string*
declare function this:expand-gradient(
  $num-stops as xs:integer,
  $base-gradient as xs:string,
  $do-even as xs:boolean,
  $do-spline as xs:boolean
) as xs:string*
{
  let $base-rgb-str := this:gradient-colours($base-gradient)
  let $base-percents :=
    for $offset in this:gradient-stops($base-gradient)/@offset
    return xs:double(substring-before(string($offset),"%"))
  return
    if ($do-even)
    then this:expand-evenly($num-stops, $base-rgb-str, $do-spline)
    else this:expand-scaled($num-stops, $base-rgb-str, $base-percents, $do-spline)
}

Original Source Code

xquery version "3.1";
(:~
 : Module with functions providing some colouring support at the
 : algorithm level.
 :
 : Copyright© Mary Holstege 2020-2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since October 2021
 : @custom:Status Active
 :)
module namespace this="http://mathling.com/svg/gradients"; 

declare namespace art="http://mathling.com/art";
declare namespace svg="http://www.w3.org/2000/svg";
declare namespace xsl="http://www.w3.org/1999/XSL/Transform";
declare namespace xhtml="http://www.w3.org/1999/xhtml";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";

import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy";
import module namespace rand="http://mathling.com/core/random"
       at "../core/random.xqy";
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy";
import module namespace edge="http://mathling.com/geometric/edge"
       at "../geo/edge.xqy";
import module namespace path="http://mathling.com/geometric/path"
       at "../geo/path.xqy";
import module namespace ellipse="http://mathling.com/geometric/ellipse"
       at "../geo/ellipse.xqy";
import module namespace spline="http://mathling.com/geometric/spline"
       at "../geo/spline.xqy";
import module namespace cs="http://mathling.com/colour/space"
       at "../colourspace/colour-space.xqy";
import module namespace rgb="http://mathling.com/colour/rgb"
       at "../colourspace/rgb.xqy";
import module namespace xyz="http://mathling.com/colour/xyz"
       at "../colourspace/xyz.xqy";
import module namespace tone="http://mathling.com/colour/tonemap"
       at "../colourspace/tonemap.xqy";
import module namespace stop="http://mathling.com/colour/stop"
       at "../colourspace/stop.xqy";

declare variable $this:NAMED-GRADIENTS as xs:string* := (
  "acton", "bam", "bamako", "batlow", "berlin", "bilbao", "broc", "buda", "bukavu",
  "cork", "davos", "devon", "fes", "grayC", "hawaii", "imola", "lajolla", "lapaz",
  "lisbon", "nuuk", "oleron", "oslo", "roma", "tofino", "tokyo", "turku",
  "vanimo", "vik",
  "actonS", "bamakoS", "batlowS", (:"berlinS",:) "bilbaoS", (:"brocS",:) "budaS",
  (: "corkS", :) "davosS", "devonS", "grayCS", "hawaiiS", "imolaS", "lajollaS",
  "lapazS", (:"lisbonS",:) "nuukS", (:"oleronS",:) "osloS", (:"romaS",:) (:"tofinoS",:)
  "tokyoS", "turkuS", (:"vikS",:)
  "bamO", "brocO", "corkO", "romaO", "vikO",
  "batlowK", "batlowW",
  "inferno", "magma", "plasma", "viridis",
  "cividis",
  "twilight", "twilight-blue", "twilight-red",
  "sawtooth","azimuth",
  "cube1_0","cubeYF","Linear_L",
  "bathymetry","bathymetryO","CDOM","chlorophyll","density",
  "freesurface-red","freesurface-blue","oxygen","phase2","PAR","salinity",
  "temperature","turbidity","velocity-blue","velocity-green",
  "vorticity-pink","vorticity-turquoise",

  "autumn","blackbody","bluered","bone","cold","cool","copper",
  "cubehelix","earth","greens","greys","hot","hsv",
  "jet","picnic","portland","rainbow-soft","rainbow","rdbu",
  "spring","summer","warm","winter","yignbu","yiorrd",
  "expanded-bone","expanded-copper",

  "CET-R3","CET-R2","CET-R1","CET-L9","CET-L8","CET-L7","CET-L6","CET-L5",
  "CET-L4","CET-L3","CET-L2","CET-L1","CET-L19","CET-L18","CET-L17",
  "CET-L16","CET-L15","CET-L14","CET-L13","CET-L12","CET-L11","CET-L10",
  "CET-I3","CET-I2","CET-I1","CET-D9","CET-D8","CET-D7","CET-D6","CET-D4",
  "CET-D3","CET-D2","CET-D1","CET-D1A","CET-D13","CET-D12","CET-D11",
  "CET-D10","CET-CBTL2","CET-CBTL1","CET-CBTD1","CET-CBTC2","CET-CBTC1",
  "CET-CBL2","CET-CBL1","CET-CBD1","CET-CBC2","CET-CBC1","CET-C5s",
  "CET-C5","CET-C4s","CET-C4","CET-C3s","CET-C3","CET-C2s","CET-C2",
  "CET-C1s","CET-C1",

  "algae", "amp", "balance", "curl", "deep", "delta", "dense",
  "diff", "cm-gray", "haline", "ice", "matter", "oxy", "phase",
  "rain", "solar", "speed", "tarn", "tempo", "thermal", "topo",
  "turbid",
  
  "simpleBlack", "simpleBlue", "simpleGreen", "simpleIce", "simpleWhite"
);

declare variable $this:ALIASES as map(xs:string,xs:string) :=
  map {
    "bluegold": "CET-CBL2",
    "tampa": "CET-I2",
    "burn": "CET-L4",
    "jungle": "CET-L5",
    "sea": "CET-L6",
    "blossom": "CET-L7",
    "oasis": "CET-L11",
    "sky": "CET-L12",
    "blood": "CET-L13",
    "forest": "CET-L14",
    "deepsea": "CET-L15",
    "warmth": "CET-L18",
    "watermelon": "CET-D3"
  }
;

declare variable $this:NAMED-ALIASES as xs:string* := $this:ALIASES=>map:keys();

declare variable $this:WHITES as xs:string* := (
  "snow", "seashell", "white", "whitesmoke", "oldlace", "mintcream",
  "ivory", "ghostwhite", "floralwhite", "cornsilk", "gainsboro", "lemonchiffon"
);

(:~ PrettyCols from https://nrennie.github.io/PrettyCols/ CC0 :)
declare variable $this:PRETTY-PALETTES as map(xs:string,xs:string*) := map {
  (: sequential palettes :)
  "Blues": (
    "#436F85", "#4C7D96", "#548BA7", "#6497B1", "#75A2BA", "#86AEC2", "#97B9CB"
  ),
  "Purples": (
    "#432263", "#502876", "#5D2F89", "#6A359C", "#773BAF", "#8444C0", "#9057C6"
  ),
  "Tangerines": (
    "#DE7A00", "#F28500", "#FF9B21", "#FFB04F", "#FFC47D", "#FFD6A3", "#FFE1BD"
  ),
  "Greens": (
    "#416322", "#4E7628", "#5A892F", "#679C35", "#74AF3B", "#80C044", "#8DC657"
  ),
  "Pinks": (
    "#860A4D", "#9E0C5B", "#B50E68", "#CD1076", "#E51284", "#EE2290", "#F03A9C"
  ),
  "Teals": (
    "#004C4C", "#006666", "#008080", "#329999", "#66B2B2", "#99CCCC", "#CCE5E5"
  ),
  "Yellows": (
    "#E6B400", "#E6C700", "#E8D119", "#EBD632", "#F0E066", "#F2E57F", "#F7EFB2"
  ),
  "Reds": (
    "#B53737", "#BE5151", "#C76B6B", "#D08585", "#D99F9F", "#E3B9B9", "#ECD3D3"
  ),
  (: diverging palettes :)
  "PurpleGreens": (
    "#420F75", "#7640A9", "#AD72D6", "#E7A8FB", "#F5F5F5", "#99CE64", "#659A32", "#326812", "#033800"
  ),
  "PinkGreens": (
    "#7F0038", "#C31E6E", "#EF5FAF", "#FCAADE", "#F5F5F5", "#99CE64", "#659A32", "#326812", "#033800"
  ),
  "TangerineBlues": (
    "#552000", "#8A4D00", "#C17D17", "#F8B150", "#F5F5F5", "#93C6E1", "#5F93AC", "#2E627A", "#00344A"
  ),
  "PurpleTangerines": (
    "#420F75", "#7640A9", "#AD72D6", "#E7A8FB", "#F5F5F5", "#F8B150", "#C17D17", "#8A4D00", "#552000"),
  "PurplePinks": (
    "#420F75", "#7640A9", "#AD72D6", "#E7A8FB", "#F5F5F5", "#FCAADE", "#EF5FAF", "#C31E6E", "#7F0038"),
  "TealGreens": (
    "#00393A", "#0A6969", "#2D9C9C", "#6DCFCF", "#F5F5F5", "#99CE64", "#659A32", "#326812", "#033800"),
  "PurpleYellows": (
    "#420F75", "#7640A9", "#AD72D6", "#E7A8FB", "#F5F5F5", "#F2E8C4", "#EED682", "#EAC541", "#E6B400"
  ),
  "RedBlues": (
    "#B53737", "#C66969", "#D79C9C", "#E8CFCF", "#F5F5F5", "#93C6E1", "#5F93AC", "#2E627A", "#00344A"
  ),
  (: qualitative palettes :)
  "Bold": (
    "#6497B1", "#6A359C", "#FFB04F", "#679C35", "#CD1076"
  ),
  "Dark": (
    "#436F85", "#432263", "#DE7A00", "#416322", "#860A4D"
  ),
  "Light": (
    "#97B9CB", "#9057C6", "#FFE1BD", "#8DC657", "#F03A9C"
  ),
  "Neon": (
    "#FF9062", "#FD6598", "#CB64C0", "#3294DD", "#75FB8A", "#d0eb60"
  ),
  "Summer": (
    "#398DB2", "#D8B31E", "#2C350B", "#829625", "#867112", "#5D761E",
    "#6293A7", "#3E5A5E", "#AC5C05", "#FFA300", "#A47DB9", "#EC94CA"
  ),
  "Autumn": (
    "#774762", "#BA6E1D", "#D6BB3B", "#755028", "#F2DD78", "#205F4B",
    "#913914", "#585854", "#F0A430", "#768048", "#800000", "#1B3A54"
  ),
  "Winter": (
    "#446C84", "#C0CBDC", "#746E6F", "#C6DCF0", "#596D80", "#B9BFFF",
    "#A0C4E1", "#897340", "#E1E3E7", "#313C45", "#9BA7B2", "#CAE9F5"
  ),
  "Rainbow": (
    "#E51E32", "#FF782A", "#FDA805", "#E2CF04", "#B1CA05", "#98C217",
    "#779815", "#029E77", "#09989C", "#059CCD", "#3F64CE", "#7E2B8E"
  ),
  "Beach": (
    "#0E7C7B", "#17BEBB", "#D4F4DD", "#D62246", "#4B1D3F"
  ),
  "Fun": (
    "#134074", "#BFAB25", "#4EA699", "#EFB0A1", "#DF2935"
  ),
  "Sea": (
    "#86CB92", "#71B48D", "#404E7C", "#3A3559", "#260F26"
  ),
  "Bright": (
    "#462255", "#FF8811", "#9DD9D2", "#046E8F", "#D44D5C"
  ),
  "Relax": (
    "#4B3F72", "#CBB3BF", "#FFC857", "#119DA4", "#19647E"
  ),
  "Lucent": (
    "#E01A4F", "#F15946", "#F9C22E", "#53B3CB", "#7DCFB6"
  ),
  "Lively": (
    "#413C58", "#D1495B", "#EDAE49", "#00798C", "#003D5B"
  ),
  "Joyful": (
    "#80A1C1", "#C94277", "#EEE3AB", "#274C77", "#5E8C61"
  ),
  (: Pride flag :)
  "pride": (
    "#8e008e", "#400098", "#00c0c0", "#008e00", "#ff0", "#ff8e00", "red", "#ff69b4"
  )
};

(:~ Werner colours from Werner's Nomenclature of Colours :)
declare variable $this:WERNER-PALETTES as map(xs:string,xs:string*) :=
  map {
    "werner_whites": (
      "SnowWhite", "ReddishWhite", "PurplishWhite", "YellowishWhite",
      "OrangeColouredWhite", "GreenishWhite", "SkimmedMilkWhite",
      "GreyishWhite"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_greys": (
      "AshGrey", "SmokeGrey", "FrenchGrey", "PearlGrey", "YellowishGrey",
      "BluishGrey", "GreenishGrey", "BlackishGrey"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_blacks": (
      "GreyishBlack", "BluishBlack", "GreenishBlack",
      "PitchOrBrownishBlack", "ReddishBlack", "InkBlack", "VelvetBlack"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_blues": (
      "ScotchBlue", "PrussianBlue", "ChinaBlue", "IndigoBlue", 
      "AzureBlue", "UltramarineBlue", "FlaxFlowerBlue", 
      "VerditterBlue", "GreenishBlue", "BerlinBlue", "GreyishBlue"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_purples": (
      "BluishLilacPurple", "RedLilacPurple", "BluishPurple", "LavenderPurple",
      "CampanulaPurple", "ImperialPurple", "AuriculaPurple",
      "PaleBlackishPurple", "PlumPurple", "VioletPurple", "PansyPurple"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_greens": (
      "SiskinGreen", "AsparagusGreen", "CeladineGreen", "AppleGreen",
      "EmeraldGreen", "BluishGreen", "MountainGreen", "VerdigrisGreen",
      "LeekGreen", "PistachioGreen", "OilGreen", "GrassGreen", "SapGreen",
      "OliveGreen", "BlackishGreen", "DuckGreen"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_yellows": (
      "CreamYellow", "SiennaYellow", "StrawYellow", "OchreYellow",
      "PrimroseYellow", "KingsYellow", "GambogeYellow", "LemonYellow",
      "WineYellow", "SaffronYellow", "SulphurYellow", "WaxYellow",
      "HoneyYellow", "GallstoneYellow"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_oranges": (
      "DutchOrange", "BuffOrange", "OrpimentOrange", 
      "ReddishOrange", "DeepReddishOrange", "BrownishOrange"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_reds": (
      "PurplishRed", "VeinousBloodRed", "ChocolateRed", "BrownishRed",
      "ArterialBloodRed", "ScarletRed", "TileRed", "VermilionRed",
      "HyacinthRed", "AuroraRed", "FleshRed", "RoseRed", "PeachBlossomRed",
      "CrimsonRed", "LakeRed", "CarmineRed", "BrownishPurpleRed",
      "CochinealRed"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "werner_browns": (
      "DeepOrangeColouredBrown", "DeepReddishBrown", "UmberBrown",
      "ChestnutBrown", "YellowishBrown", "WoodBrown", "HairBrown",
      "BroccoliBrown", "CloveBrown", "LiverBrown", "BlackishBrown"
    )!(rgb:named-colour(.)=>rgb:to-string())
  }
;

declare variable $this:PALETTES as map(xs:string,xs:string*) :=
util:merge-into((
  map {
    "whites": $this:WHITES,
    "browns": (
      "#671905", "#833012", "#974B1A", "#AA6925", "#B6872D", "#BFA73B"
    ),
    "olives": (
      "#51351F", "#6D5332", "#8C7448", "#AC9561", "#CCB778", "#EEDA91"
    ),
    "sunsetland": (
      "#51351F", "#846841", "#B6A067", "#EEB46A", "#EA8C42", "#DF631D"
    ),
    "october": (
      "#695851", "#87837A", "#A4B0A4", "#FED32F", "#FEC52E", "#FCB72C"
    ),
    "farhills": (
      "#235240", "#426D5A", "#608977", "#80A594", "#A1C1B2", "#C4E0D2"
    ),
    "springhills": (
      "#636B32", "#778536", "#8E9F41", "#A7B84E", "#C4D263", "#E1EB7A"
    ),
    "mountains": (
      "#7E507E", "#9C7296", "#BB98AE", "#86963D", "#B0C258", "#E2EB78"
    ),
    "wotw": (
      "#3D3D1B", "#4F4E20", "#675C2E", "#FC6456", "#EC432E", "#D12615"
    ),
    "carnival": (
      "#2E294E", "#541388", "#F1E9DA", "#FFD400", "#D90368"
    ),
    "leaves": (
      "#24400C", "#2C510B", "#396C0D", "#4B8016", "#6BB121", "#8CD01F"
    ),
    "lake": (
      "#173A3F", "#0C5450", "#10615B", "#166862", "#30817A", "#41918F"
    ),
    "high-tea": (
      "MiddleBlueGreen", "TeaGreen", "Cream", "MediumChampagne", "DarkByzantium"
    )!(rgb:named-colour(.)=>rgb:to-string()),
    "plant-dyes": (
     "RedHotPoker", "HawthornLeaf", "BogMyrtle", "Silverweed", "GorseFlower",
     "ReedFlower", "NettleLeaf", "ColtsFoot", "Acorn", "IrisRoot"
    )!(rgb:named-colour(.)=>rgb:to-string())
  },
  $this:WERNER-PALETTES,
  $this:PRETTY-PALETTES
))
;

(: Alt browns: 
        <svg:stop offset="0.0%" stop-color="#7F440E" stop-opacity="1.0000"/>
        <svg:stop offset="20.0%" stop-color="#8D561F" stop-opacity="1.0000"/>
        <svg:stop offset="40.0%" stop-color="#A26519" stop-opacity="1.0000"/>
        <svg:stop offset="60.0%" stop-color="#AD8117" stop-opacity="1.0000"/>
        <svg:stop offset="80.0%" stop-color="#BE901F" stop-opacity="1.0000"/>
        <svg:stop offset="100.0%" stop-color="#C8A718" stop-opacity="1.0000"/>
:)

declare variable $this:NAMED-PALETTES as xs:string* := $this:PALETTES=>map:keys();

declare variable $this:SMALL-GRADIENTS as xs:string* := (
  $this:NAMED-PALETTES,
  "spring", "summer", "autumn", "winter",
  "bone", "copper", "earth", "electric", "portland", "rainbow-soft", "rainbow",
  "cold", "cool", "hot",
  "blackbody", "bluered", "greens", "greys", "hsv", "jet", "mars", "rdbu",
  "yignbu", "yiorrd",
  "simpleBlack", "simpleBlue", "simpleGreen", "simpleIce", "simpleWhite"
);

declare variable $this:SPLIT-GRADIENTS as xs:string* := (
  "bukavu", "fes", "oleron", "topo"
);

declare variable $this:CYCLIC-GRADIENTS as xs:string* := (
  "bamO", "bathymetryO", "brocO", "corkO", "romaO", "vikO",

  "CET-CBTC2", "CET-CBTC1", "CET-CBC2", "CET-CBC1"," CET-C5s",
  "CET-C5", "CET-C4s", "CET-C4", "CET-C3s", "CET-C3", "CET-C2s", "CET-C2",
  "CET-C1s", "CET-C1",

  "azimuth", "hsv", "phase", "phase2", "rainbow-soft", "sunlight", "twilight"
);

declare variable $this:DIVERGING-GRADIENTS as xs:string* := (
  "balance", "bam", "berlin", "broc", "cork", "curl", "delta", "diff",
  "lisbon", "picnic", "portland", "roma", "tarn", "tofino", "vanimo", "vik",

  "CET-CBD1", "CET-CBTD1", "CET-D1", "CET-D2", "CET-D3", "CET-D4",
  "CET-D6", "CET-D7", "CET-D8", "CET-D9", "CET-D10", "CET-D11",
  "CET-D12", "CET-D13", "CET-D1A",

  "rdbu", "october", "sunsetland",

  "PurpleGreens", "PinkGreens", "TangerineBlues", "PurpleTangerines",
  "PurplePinks", "TealGreens", "PurpleYellows", "RedBlues"
);

declare variable $this:LINEAR-GRADIENTS as xs:string* := (
  "acton", "bamako", "batlow", "bilbao", "buda",
  "davos", "devon", "grayC", "hawaii", "imola", "lajolla", "lapaz",
  "nuuk", "oslo", "tokyo", "turku",
  "batlowK", "batlowW",
  "inferno", "magma", "plasma", "viridis",
  "cividis",
  "twilight-blue", "twilight-red",
  "cube1_0", "cubeYF", "Linear_L",
  "bathymetry", "CDOM", "chlorophyll", "density",
  "freesurface-red", "freesurface-blue", "oxygen", "PAR", "salinity",
  "temperature", "turbidity", "velocity-blue", "velocity-green",
  "vorticity-pink", "vorticity-turquoise",

  "autumn", "blackbody", "bluered", "bone", "cold", "cool", "copper",
  "cubehelix", "earth", "greens", "greys", "hot",
  "jet", "rainbow", "rdbu",
  "spring", "summer", "warm", "winter", "yignbu", "yiorrd",
  "expanded-bone", "expanded-copper",

  "CET-R3", "CET-R2", "CET-R1",
  "CET-L9", "CET-L8", "CET-L7", "CET-L6", "CET-L5",
  "CET-L4", "CET-L3", "CET-L2", "CET-L1", "CET-L19", "CET-L18", "CET-L17",
  "CET-L16", "CET-L15", "CET-L14", "CET-L13", "CET-L12", "CET-L11", "CET-L10",
  "CET-I3", "CET-I2", "CET-I1", 
  "CET-CBTL2", "CET-CBTL1", 
  "CET-CBL2", "CET-CBL1",

  "algae", "amp", "deep", "dense",
  "cm-gray", "haline", "ice", "matter", 
  "rain", "solar", "speed", "tempo", "thermal", "topo",
  "turbid",
  
  "simpleBlack", "simpleBlue", "simpleGreen", "simpleIce", "simpleWhite",

  "Blues", "Purples", "Tangerines", "Greens", "Pinks", "Teals",
  "Yellows", "Reds"
);

(:~ 
 : starts-darker()
 : Does this gradient start with darker colours? i.e. dark-to-light
 : Return false for those that are dark-light-dark or light-dark-light or
 : scrambled
 :)
declare function this:starts-darker($gradient as xs:string) as xs:boolean
{
  ($gradient = (
  "acton", "bamako", "batlow", "buda", 
  "davos", "devon", "hawaii", "imola", "lapaz", 
  "nuuk", "oleron", "oslo", "tokyo", "turku", 
  "actonS", "bamakoS", "batlowS", (:"berlinS",:) "bilbaoS", (:"brocS",:) "budaS",
  "inferno", "magma", "plasma", "viridis",
  "cividis",
  "twilight-blue", "twilight-red",
  "sawtooth","azimuth",
  "cube1_0","cubeYF","Linear_L",
  "bathymetry","bathymetryO","CDOM","chlorophyll","density",
  "freesurface-red","freesurface-blue","oxygen","PAR","salinity",
  "temperature","turbidity","velocity-blue","velocity-green",
  "vorticity-pink","vorticity-turquoise",

  "autumn","blackbody","bluered","bone","cool","copper",
  "cubehelix","earth","electric","greens","greys","hot",
  "jet","picnic","portland","rainbow",
  "summer","warm","winter","yignbu","yiorrd",
  "expanded-bone","expanded-copper",

  "CET-L9","CET-L8","CET-L7","CET-L6","CET-L5",
  "CET-L4","CET-L3","CET-L2","CET-L1","CET-L18",
  "CET-L16","CET-L15","CET-L14","CET-L13","CET-L11","CET-L10",
  "CET-CBTL2","CET-CBTL1",
  "CET-CBL2","CET-CBL1",

  "bluegold", "burn", "jungle", "sea", "blossom", "oasis", "blood", "forest",
  "deepsea",

  "cm-gray", "haline", "ice", "oxy",
  "solar", "thermal", "topo",
  "turbid",
  
  "simpleBlack",

  "browns", "olives", "sunsetland", "october", "farhills", "springhills",
  "mountains", "wotw", "carnival", "greens",

  "werner_reds", "werner_blues", "werner_browns",

  "Blues", "Purples", "Tangerines", "Greens", "Pinks", "Teals", "Yellows", "Reds"
  ))
  or
  (contains($gradient,"-reverse") and
   not(this:starts-darker(replace($gradient,"-reverse","")))
  )
};

(:~
 : random-gradient()
 : Return then name of a random well-known base gradient or its reverse.
 :)
declare function this:random-gradient() as xs:string
{
  let $exclusion := "(^simple)"
  let $valid-names :=
    ($this:NAMED-GRADIENTS,$this:NAMED-ALIASES,$this:NAMED-PALETTES)[
      not(matches(.,$exclusion))
    ]
  let $gradient := rand:select-random($valid-names)
  return (
    if (empty($gradient) or $gradient="") then util:log("Empty gradient? "||string-join($valid-names,"+")) else (),
    util:assert(not(empty($gradient)), "__empty__"),
    util:assert($gradient ne "", "__empty string__"),
    if (rand:flip(50))
    then $gradient
    else $gradient||"-reverse"
  )
};

(:~
 : random-gradient()
 : Return then name of a random well-known base gradient or its reverse.
 :
 : @param $exclusion: regular expression of gradient names to exclude in addition
 :   to simpleXXX
 :)
declare function this:random-gradient($exclusion as xs:string) as xs:string
{
  let $exclusion := "("||$exclusion||")|(^simple)"
  let $valid-names :=
    ($this:NAMED-GRADIENTS,$this:NAMED-ALIASES,$this:NAMED-PALETTES)[
      not(matches(.,$exclusion))
    ]
  let $gradient := rand:select-random($valid-names)
  return (
    if (rand:flip(50))
    then $gradient
    else $gradient||"-reverse"
  )
};

(:~
 : fetch-gradient()
 : Get the gradient definition of a known named gradient or its alias.
 : Empty otherwise.
 :)
declare function this:fetch-gradient($gradient as xs:string) as element()?
{
  if ($gradient = $this:NAMED-GRADIENTS) then (
    (: SLEAZY HACK ALERT :)
    let $def :=
      try {
        doc("../COLOURS/"||$gradient||".xsl")//xsl:param/*
      } catch * { () }
    return
      if (exists($def)) then $def else (
        try {
          doc("../COLOURS/"||$gradient||".svg")//svg:defs/*
        } catch * { () }
      )
  ) else if ($this:ALIASES($gradient) = $this:NAMED-GRADIENTS) then (
    this:fetch-gradient($this:ALIASES($gradient))
  ) else (
  )
};

(:~ 
 : get-gradient-definition()
 : Get the gradient definition of the name, allowing for variants for
 : flips, inversions, samples, etc.
 :)
declare function this:get-gradient-definition($gradient as xs:string) as element()?
{
  let $gradient-pieces := tokenize($gradient, "[·+]") (: middle dot :)
  let $do-interleave := contains($gradient,"+")
  let $gradients :=
    for $gradient in $gradient-pieces
    return (
    if ($gradient="random") then (
      this:get-gradient-definition(this:random-gradient())
    ) else if ($gradient = $this:NAMED-GRADIENTS) then (
      this:fetch-gradient($gradient)
    ) else if ($this:ALIASES($gradient) = $this:NAMED-GRADIENTS) then (
      let $def := this:fetch-gradient($this:ALIASES($gradient))
      return (
        <svg:linearGradient id="{$gradient}">{
          $def/(@* except @id),
          $def/*
        }</svg:linearGradient>
      )
    ) else if ($gradient = $this:NAMED-PALETTES) then (
      this:linear-gradient-definition($gradient, "linear", $this:PALETTES($gradient))
    ) else if (matches($gradient,'(-reverse|-random|-flip|-invert|-full|-anti|-outflow|-inflow|-slant|-fade|-[0-9]+)$')) then (
      let $root := replace($gradient,'(-reverse|-random|-flip|-invert|-full|-anti|-outflow|-inflow|-slant|-fade|-[0-9]+)$','')
      let $definition := this:get-gradient-definition($root)
      let $operation := substring-after($gradient, $root||"-")
      return switch ($operation)
      case "random" return this:gradient-random($definition)=>this:gradient-name($gradient)
      case "reverse" return this:gradient-reverse($definition)=>this:gradient-name($gradient)
      case "flip" return this:linear-flip($definition)=>this:gradient-name($gradient)
      case "invert" return this:linear-invert($definition)=>this:gradient-name($gradient)
      case "full" return this:linear-full($definition)=>this:gradient-name($gradient)
      case "anti" return this:linear-anti($definition)=>this:gradient-name($gradient)
      case "outflow" return this:radial-outflow($definition)=>this:gradient-name($gradient)
      case "inflow" return this:radial-inflow($definition)=>this:gradient-name($gradient)
      case "slant" return this:linear-slant($definition)=>this:gradient-name($gradient)
      case "fade" return this:gradient-fade($definition, 0.0, 1.0)=>this:gradient-name($gradient)
      default (: one of the samples :) return (
        let $n := xs:integer($operation)
        return this:gradient-sample($definition, $n)=>this:gradient-name($gradient)
      )
    ) else if (starts-with($gradient,"black-to-")) then (
      let $colour := this:colour(substring-after($gradient,"black-to-"))
      return
        <svg:linearGradient id="{this:safe($gradient)}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="0%" x2="100%" y1="0%" y2="0%">      
          <svg:stop offset="0%" stop-color="#000000"/>
          <svg:stop offset="100%" stop-color="{$colour}"/>
        </svg:linearGradient>
    ) else if (starts-with($gradient,"white-to-")) then (
      let $colour := this:colour(substring-after($gradient,'white-to-'))
      return
        <svg:linearGradient id="{this:safe($gradient)}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="0%" x2="100%" y1="0%" y2="0%">      
          <svg:stop offset="0%" stop-color="#FFFFFF"/>
          <svg:stop offset="100%" stop-color="{$colour}"/>
        </svg:linearGradient>
    ) else (
      (: util:log("gradient="||$gradient||" no definition") :)
    )
  )
  return (
    if (count($gradients) le 1) then $gradients
    else if (count($gradients) = count($gradient-pieces)) then (
      this:merge($gradient, $gradients, $do-interleave)
    ) else (
    )
  )
};

declare function this:gradient($gradient as xs:string) as element()?
{
  this:get-gradient-definition($gradient)
};

declare function this:random-colour($gradients as xs:string*) as xs:string
{
  rand:select-random(this:colours($gradients))
};

declare function this:random-colour($gradients as xs:string*, $luminance as xs:double) as xs:string
{
  rand:select-random(this:colours($gradients))=>
    rgb:rgb()=>cs:rgb-to-xyz()=>
    tone:luminate($luminance)=>
    cs:xyz-to-rgb()=>rgb:to-string()
};

declare function this:random-colour($gradients as xs:string*, $low-luminance as xs:double, $high-luminance as xs:double) as xs:string
{
  let $colour := rand:select-random(this:colours($gradients))
  let $xyz := $colour=>rgb:rgb()=>cs:rgb-to-xyz()
  let $luminance := tone:luminance($xyz)
  return (
    if (util:twixt($luminance, $low-luminance, $high-luminance))
    then $colour
    else (
      $xyz=>tone:luminate(rand:uniform($low-luminance, $high-luminance))=>cs:xyz-to-rgb()=>rgb:to-string()
    )
  )
};

(:~
 : colours()
 : Return the colour strings from the named gradients in order.
 :)
declare function this:colours($gradients as xs:string*) as xs:string*
{
  for $gradient in $gradients return (
    let $definition := this:get-gradient-definition($gradient)
    let $colours :=
      if (empty($definition)) then $gradient
      else $definition/svg:stop/@stop-color
    return
      if (matches($gradient,'-reverse')) then (
        reverse($colours)
      ) else (
        $colours
      )
  )
};

(:~
 : colours()
 : Return the colour strings from the named gradients in order, but reluminated
 : to ensure their luminance is in the given range.
 :)
declare function this:colours(
  $gradients as xs:string*,
  $low as xs:double,
  $high as xs:double
) as xs:string*
{
  let $low := util:clamp-some($low, 0, ())
  let $high := util:clamp-some($high, (), 1)
  let $colours := this:colours($gradients)
  return (
    if ($low <= 0 and $high >= 1) then $colours else (
      for $c in $colours
      let $xyz := rgb:rgb($c)=>cs:rgb-to-xyz()
      let $luminance := tone:luminance($xyz)
      return (
        if ($luminance < $low)
        then tone:luminate($xyz, $low)=>cs:xyz-to-rgb()=>rgb:to-string()
        else if ($luminance > $high)
        then tone:luminate($xyz, $high)=>cs:xyz-to-rgb()=>rgb:to-string()
        else $c
      )
    )
  )
};

declare function this:gradient-colours($gradients as xs:string*) as xs:string*
{
  this:colours($gradients)
};

(:~
 : gradient-stops()
 : Return all the gradient stops, as svg:stop elements, from the given gradient.
 :)
declare function this:gradient-stops($gradient as xs:string) as element(svg:stop
)*
{
  this:get-gradient-definition($gradient)/svg:stop
};

declare function this:n-gradient-colours($gradient as xs:string) as xs:integer
{
  let $n := count(this:gradient-stops($gradient))
  return
    if ($n=0) then 1
    else $n
};

declare function this:n-colours($gradient as xs:string) as xs:integer
{
  let $n := count(this:gradient-stops($gradient))
  return
    if ($n=0) then 1
    else $n
};

(:~
 : gradient-points()
 : Return the points in the gradient as gradient points. This assumes
 : the stop values are in "rgb(r,g,b)" or hex format. Opacity gives the
 : alpha channel.
 : 
 : A gradient point has an offset and a colour point. The set of
 : gradient points can be used for interpolations and suchlike.
 : The colour points returned from this are RGBA points.
 : TODO: select output colour space
 :)
declare function this:gradient-points($gradient as xs:string) as map(xs:string,item()*)*
{
  for $stop in this:gradient-stops($gradient)
  let $offset :=
    if (ends-with($stop/@offset,"%"))
    then xs:double(substring-before($stop/@offset,"%")) div 100
    else xs:double($stop/@offset)
  let $alpha :=
    if (empty($stop/@stop-opacity)) then 1
    else if (ends-with($stop/@stop-opacity,"%"))
    then xs:double(substring-before($stop/@stop-opacity,"%")) div 100
    else xs:double($stop/@stop-opacity)
  let $rgba := rgb:rgba(rgb:rgb($stop/@stop-color), $alpha)
  return stop:stop("rgb", $offset, $rgba)
};

declare function this:id($gradient as element()) as xs:string
{
  $gradient/@id
};

declare function this:merge(
  $name as xs:string,
  $gradients as element()+,
  $do-interleave as xs:boolean
) as element()
{
  let $kind :=
    typeswitch(head($gradients))
    case element(svg:radialGradient) return "outflow"
    default return "linear"
  let $max-n := max(for $gradient in $gradients return count($gradient//svg:stop))
  let $stops :=
    if ($do-interleave) then (
      for $i in 1 to $max-n
      for $gradient in $gradients
      return (
        ($gradient//svg:stop)[$i]
      )
    ) else (
      for $gradient in $gradients return $gradient//svg:stop
    )
  return (
    this:gradient-definition($name, $kind,
      for $stop in $stops return $stop/@stop-opacity,
      for $stop in $stops return $stop/@stop-color
    )
  )
};

declare function this:sample($colours as xs:string*, $num as xs:integer) as xs:string*
{
  if (empty($colours) or $num <= 1) then () else
  let $sep := count($colours) div $num
  for $i in 1 to $num
  return $colours[1 + ceiling($sep*($i - 1))]
};

declare function this:sample($colours as xs:string*, $above as xs:double, $below as xs:double) as xs:string*
{
  this:sample($colours, count($colours), $above, $below)
};

declare function this:sample($colours as xs:string*, $num as xs:integer, $above as xs:double, $below as xs:double) as xs:string*
{
  if (empty($colours) or $num <= 1) then () else
  let $n := count($colours)
  let $min-n := round($n * $above) cast as xs:integer
  let $max-n := round($n * $below) cast as xs:integer
  (: Asymmetry here because we want to allow 0 for $above and 1 for $below :)
  let $colours := $colours[position() > $min-n and position() <= $max-n]
  let $sep := count($colours) div $num
  for $i in 1 to $num
  return $colours[1 + ceiling($sep*($i - 1))]
};

declare function this:sample-stops($stops as element(svg:stop)*, $num as xs:integer) as element(svg:stop)*
{
  if (empty($stops) or $num <= 1) then () else
  let $sep := count($stops) div $num
  for $i in 1 to $num
  return $stops[1 + ceiling($sep*($i - 1))]
};

declare function this:circularize($colours as xs:string*) as xs:string*
{
  tail($colours), tail(reverse($colours))
};

declare function this:luminance-range(
  $colour as xs:string,
  $n as xs:integer,
  $delta as xs:double
) as xs:string*
{
  let $rgb := rgb:rgb($colour)
  let $lum := tone:luminance($rgb)
  return (
    for $l in util:linspace($n, $lum - $delta, $lum, true())
    return tone:luminate($rgb, util:clamp($l, 0.0, 1.0))=>rgb:to-string(),
    $colour,
    for $l in util:linspace($n, $lum, $lum + $delta)=>tail()
    return tone:luminate($rgb, util:clamp($l, 0.0, 1.0))=>rgb:to-string()
  )
};

declare function this:colour($name as xs:string) as xs:string
{
  let $known := $rgb:KNOWN-COLOURS($name)
  return (
    if (empty($known)) then $name
    else $known
  )
};

declare function this:ref($name as xs:string) as xs:string
{
  let $known := $rgb:KNOWN-COLOURS($name)
  return (
    if (exists($known)) then $known
    else if ($name="none") then $name
    else if (matches($name, "^(rgb|url|#)")) then $name
    else "url(#"||this:safe($name)||")"
  )
};

declare function this:safe($name as xs:string) as xs:string
{
  replace(replace($name, "[(),#]", "-"), " ", "")
};

(:~
 : average-luminance()
 : Average luminance: a way of determining relative brightness of a set
 : of colours.
 :)
declare function this:average-luminance($colours as xs:string*) as xs:double
{
  avg(
    for $colour in $colours return (
      tone:luminance(cs:rgb-to-xyz(rgb:rgb($colour)))
    )
  )
};

declare function this:linear-gradient-definition(
  $name as xs:string,
  $layout as xs:integer*, (: x1, x2, y1, y2 :)
  $opacities as xs:double*, (: min, max :)
  $colours as xs:string*
) as element(svg:linearGradient)
{
  let $n := count($colours)
  let $x1 := ($layout[1],0)[1]
  let $x2 := ($layout[2],100)[1]
  let $y1 := ($layout[3],0)[1]
  let $y2 := ($layout[4],0)[1]
  let $start-opacity := ($opacities[1],1)[1]
  let $end-opacity := ($opacities[2],1)[1]
  return
    if ($start-opacity=$end-opacity) then (
      <svg:linearGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="{$x1}%" x2="{$x2}%" y1="{$y1}%" y2="{$y2}%">
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 100) then (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          (: let $_ := xdmp:log($i||" "||$colour||" "||$offset) :)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>,
            if (exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$start-opacity}"/>
            else ()
          )
        ) else (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$start-opacity}"/>
      }
      </svg:linearGradient>
    ) else (
      <svg:linearGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" x1="{$x1}%" x2="{$x2}%" y1="{$y1}%" y2="{$y2}%">
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 100) then (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>,
            if (exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$opacity}"/>
            else ()
          )
        ) else (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$end-opacity}"/>
      }
      </svg:linearGradient>
    )
};

declare function this:linear-standard-layout($kind as xs:string) as xs:integer*
{
  switch ($kind)
  case "reverse" return (100,0,0,0)
  case "flip" return (0,0,0,100)
  case "invert" return (0,0,100,0)
  case "full" return (0,100,0,100)
  case "anti" return (100,0,100,0)
  case "slant" return (0,50,0,90)
  default return (0,100,0,0)
};

declare function this:linear-gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $colours as xs:string*
) as element(svg:linearGradient)
{
  this:linear-gradient-definition($name, this:linear-standard-layout($kind), (1,1), $colours)
};

declare function this:radial-gradient-definition(
  $name as xs:string,
  $layout as xs:integer*, (: r, cx, cy :)
  $opacities as xs:double*,
  $colours as xs:string*
) as element(svg:radialGradient)
{
  let $n := count($colours)
  let $r := ($layout[1],50)[1]
  let $cx := ($layout[2],50)[1]
  let $cy := ($layout[3],$cx)[1]
  let $start-opacity := ($opacities[1],1)[1]
  let $end-opacity := ($opacities[2],1)[1]
  return
    if ($start-opacity=$end-opacity) then (
      <svg:radialGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" cx="{$cx}%" cy="{$cy}%" r="{$r}%">      
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 5) then (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>,
            if ($n > 100 and exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$start-opacity}"/>
            else ()
          )
        ) else (
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$start-opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$start-opacity}"/>
      }
      </svg:radialGradient>
    ) else (
      <svg:radialGradient id="{$name}" gradientUnits="objectBoundingBox" spreadMethod="pad" cx="{$cx}%" cy="{$cy}%" r="{$r}%">      
      {
        <svg:stop offset="0.00%" stop-color="{$colours[1]}" stop-opacity="{$start-opacity}"/>
        ,
        if ($n > 5) then (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>,
            if ($n > 100 and exists($colours[$i + 1])) then
            <svg:stop offset="{$offset}%" stop-color="{$colours[$i + 1]}" stop-opacity="{$opacity}"/>
             else ()
          )
        ) else (
          let $delta := ($end-opacity - $start-opacity) div $n
          for $colour at $i in $colours
          let $offset := round(100* 100*$i div $n) div 100 (: percent to 2 decimals :)
          let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
          where $i > 1 and $i < $n
          return (
            <svg:stop offset="{$offset}%" stop-color="{$colour}" stop-opacity="{$opacity}"/>
          )
        )
        ,
        <svg:stop offset="100.00%" stop-color="{$colours[last()]}" stop-opacity="{$end-opacity}"/>
      }
      </svg:radialGradient>
    )
};

declare function this:radial-standard-layout($kind as xs:string) as xs:integer*
{
  if ($kind="stargate") then (50,50,80) else (50,50,50)
};

declare function this:radial-colours($kind as xs:string, $colours as xs:string*) as xs:string*
{
  if ($kind="inflow") then reverse($colours) else $colours
};

declare function this:radial-gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $colours as xs:string*
) as element(svg:radialGradient)
{
  this:radial-gradient-definition($name, this:radial-standard-layout($kind), (1,1), this:radial-colours($kind, $colours))
};

declare function this:gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $opacities as xs:double*,
  $colours as xs:string*
) as element()
{
  if ($kind=("inflow","outflow","stargate")) then (
    this:radial-gradient-definition($name, this:radial-standard-layout($kind), $opacities, this:radial-colours($kind, $colours))
  ) else (
    this:linear-gradient-definition($name, this:linear-standard-layout($kind), $opacities, $colours)
  )
};

declare function this:gradient-definition(
  $name as xs:string,
  $kind as xs:string,
  $colours as xs:string*
) as element()
{
  this:gradient-definition($name, $kind, (1,1), $colours)
};

declare function this:is-radial($def as element()) as xs:boolean
{
  local-name($def)="radialGradient"
};

(:======================================================================:
 : Gradient manipulations
 :======================================================================:)

declare function this:gradient-name(
  $base-gradient as element(),
  $name as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-name($base-gradient, $name)
  else this:linear-name($base-gradient, $name)
};

declare function this:gradient-reverse(
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-reverse($base-gradient)
  else this:linear-reverse($base-gradient)
};

declare %art:deprecated function this:gradient-reverse(
  $name as xs:string,
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-reverse($base-gradient)=>this:radial-name($name)
  else this:linear-reverse($base-gradient)=>this:linear-name($name)
};

declare function this:gradient-random(
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-random($base-gradient)
  else this:linear-random($base-gradient)
};


declare %art:deprecated function this:gradient-random(
  $name as xs:string,
  $base-gradient as element()
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-random($base-gradient)=>this:radial-name($name)
  else this:linear-random($base-gradient)=>this:linear-name($name)
};

declare function this:gradient-sample(
  $base-gradient as element(),
  $num as xs:integer
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-sample($base-gradient, $num)
  else this:linear-sample($base-gradient, $num)
};

declare %art:deprecated function this:gradient-sample(
  $name as xs:string,
  $base-gradient as element(),
  $num as xs:integer
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-sample($base-gradient, $num)=>this:radial-name($name)
  else this:linear-sample($base-gradient, $num)=>this:linear-name($name)
};

declare function this:gradient-fade(
  $base-gradient as element(),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-fade($base-gradient, $start-opacity, $end-opacity)
  else this:linear-fade($base-gradient, $start-opacity, $end-opacity)
};

declare %art:deprecated function this:gradient-fade(
  $name as xs:string,
  $base-gradient as element(),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-fade($base-gradient, $start-opacity, $end-opacity)=>this:radial-name($name)
  else this:linear-fade($base-gradient, $start-opacity, $end-opacity)=>this:linear-name($name)
};

declare function this:gradient-transform(
  $base-gradient as element(),
  $transform as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-transform($base-gradient, $transform)
  else this:linear-transform($base-gradient, $transform)
};

declare %art:deprecated function this:gradient-transform(
  $name as xs:string,
  $base-gradient as element(),
  $transform as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-transform($base-gradient, $transform)=>this:radial-name($name)
  else this:linear-transform($base-gradient, $transform)=>this:linear-name($name)
};

declare function this:gradient-units(
  $base-gradient as element(),
  $objectBoundingBox as xs:boolean
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-units($base-gradient, $objectBoundingBox)
  else this:linear-units($base-gradient, $objectBoundingBox)
};

declare %art:deprecated function this:gradient-units(
  $name as xs:string,
  $base-gradient as element(),
  $objectBoundingBox as xs:boolean
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-units($base-gradient, $objectBoundingBox)=>this:radial-name($name)
  else this:linear-units($base-gradient, $objectBoundingBox)=>this:linear-name($name)
};

declare function this:gradient-spread(
  $base-gradient as element(),
  $method as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-spread($base-gradient, $method)
  else this:linear-spread($base-gradient, $method)
};

declare %art:deprecated function this:gradient-spread(
  $name as xs:string,
  $base-gradient as element(),
  $method as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-spread($base-gradient, $method)=>this:radial-name($name)
  else this:linear-spread($base-gradient, $method)=>this:linear-name($name)
};

declare function this:gradient-layout(
  $base-gradient as element(),
  $layout as xs:integer*
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-layout($base-gradient, $layout)
  else this:linear-layout($base-gradient, $layout)
};

declare %art:deprecated function this:gradient-layout(
  $name as xs:string,
  $base-gradient as element(),
  $layout as xs:integer*
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-layout($base-gradient, $layout)=>this:radial-name($name)
  else this:linear-layout($base-gradient, $layout)=>this:linear-name($name)
};

declare function this:gradient-interleave(
  $base-gradient as element(),
  $interleave as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-interleave($base-gradient, $interleave)
  else this:linear-interleave($base-gradient, $interleave)
};

declare %art:deprecated function this:gradient-interleave(
  $name as xs:string,
  $base-gradient as element(),
  $interleave as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-interleave($base-gradient, $interleave)=>this:radial-name($name)
  else this:linear-interleave($base-gradient, $interleave)=>this:linear-name($name)
};

declare function this:gradient-opacity(
  $base-gradient as element(),
  $opacity as function(xs:integer) as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-opacity($base-gradient, $opacity)
  else this:linear-opacity($base-gradient, $opacity)
};

declare %art:deprecated function this:gradient-opacity(
  $name as xs:string,
  $base-gradient as element(),
  $opacity as function(xs:integer) as xs:double
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-opacity($base-gradient, $opacity)=>this:radial-name($name)
  else this:linear-opacity($base-gradient, $opacity)=>this:linear-name($name)
};

declare function this:gradient-colour(
  $base-gradient as element(),
  $colour as function(xs:integer) as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-colour($base-gradient, $colour)
  else this:linear-colour($base-gradient, $colour)
};

declare %art:deprecated function this:gradient-colour(
  $name as xs:string,
  $base-gradient as element(),
  $colour as function(xs:integer) as xs:string
) as element()
{
  if (this:is-radial($base-gradient))
  then this:radial-colour($base-gradient, $colour)=>this:radial-name($name)
  else this:linear-colour($base-gradient, $colour)=>this:linear-name($name)
};

declare function this:gradient-ref(
  $name as xs:string,
  $base-gradient-name as xs:string
) as element()
{
  let $def := this:gradient($base-gradient-name)
  return (
    if (exists($def) and this:is-radial($def))
    then this:radial-ref($name, $base-gradient-name)
    else this:linear-ref($name, $base-gradient-name)
  )
};

declare function this:linear-name(
  $base-gradient as element(svg:linearGradient),
  $name as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient id="{this:safe($name)}">{
    $base-gradient/(@* except @id),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-reverse(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@x2}" x2="{$base-gradient/@x1}"
                      y1="{$base-gradient/@y2}" y2="{$base-gradient/@y1}">{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-flip(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@y1}" x2="{$base-gradient/@y2}"
                      y1="{$base-gradient/@x1}" y2="{$base-gradient/@x2}"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-invert(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@y1}" x2="{$base-gradient/@y2}"
                      y1="{$base-gradient/@x2}" y2="{$base-gradient/@x1}"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-full(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient y1="{$base-gradient/@x1}" y2="{$base-gradient/@x2}"
  >{
    $base-gradient/(@* except (@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-anti(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$base-gradient/@x2}" x2="{$base-gradient/@x1}"
                      y1="{$base-gradient/@x2}" y2="{$base-gradient/@x1}"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
};


declare function this:linear-slant(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="0%" x2="50%"
                      y1="0%" y2="90%"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-adjust(
  $base-gradient as element(svg:linearGradient),
  $xys as xs:integer* (: x1, y1, x2, y2 :)
) as element(svg:linearGradient)
{
  <svg:linearGradient x1="{$xys[1]}%" x2="{$xys[3]}%"
                      y1="{$xys[2]}%" y2="{$xys[4]}%"
  >{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-random(
  $base-gradient as element(svg:linearGradient)
) as element(svg:linearGradient)
{
  let $stops := rand:shuffle($base-gradient/svg:stop)
  let $percentages := util:linspace(count($stops), 0, 100)!util:decimal(., 2)
  return
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $stops
      return (
        <svg:stop offset="{$percentages[$i]}%">{
          $stop/(@* except @offset)
        }</svg:stop>
      )
    }</svg:linearGradient>
};

declare function this:linear-interleave(
  $base-gradient as element(svg:linearGradient),
  $interleave as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    return (
      if ($i mod 2 = 0) then $stop
      else (
        <svg:stop stop-color="{$interleave}">{
          $stop/(@* except @stop-color)
        }</svg:stop>
      )
    )
  }</svg:linearGradient>
};

declare function this:linear-opacity(
  $base-gradient as element(svg:linearGradient),
  $opacity as function(xs:integer) as xs:double
) as element(svg:linearGradient)
{
  <svg:linearGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-opacity := util:decimal(util:clamp($opacity($i),0,1),3)
    return (
      <svg:stop stop-opacity="{$stop-opacity}">{
        $stop/(@* except @stop-opacity)
      }</svg:stop>
    )
  }</svg:linearGradient>
};

declare function this:linear-colour(
  $base-gradient as element(svg:linearGradient),
  $colour as function(xs:integer) as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-colour := $colour($i)
    return (
      <svg:stop stop-color="{$stop-colour}">{
        $stop/(@* except @stop-color)
      }</svg:stop>
    )
  }</svg:linearGradient>
};

declare function this:linear-sample(
  $base-gradient as element(svg:linearGradient),
  $num as xs:integer
) as element(svg:linearGradient)
{
  if ($num <= 1) then (
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/*
    }</svg:linearGradient>
  ) else (
    let $stops := $base-gradient/svg:stop
    let $percentages := util:linspace($num, 0, 100)!util:decimal(., 2)
    let $sep := count($stops) div $num
    return
      if (count($stops) <= $num) then (
        <svg:linearGradient>{
          $base-gradient/@*,
          $base-gradient/*
        }</svg:linearGradient>
      ) else (
        <svg:linearGradient>{
          $base-gradient/@*,
          $base-gradient/(* except svg:stop),
          for $i in 1 to $num
          let $stop := $stops[1 + ceiling($sep*($i - 1))]
          return (
            <svg:stop offset="{$percentages[$i]}%">{
              $stop/(@* except @offset)
            }</svg:stop>
          )
        }</svg:linearGradient>
      )
  )
};

declare function this:linear-fade(
  $base-gradient as element(svg:linearGradient),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element(svg:linearGradient)
{
  let $n := count($base-gradient/svg:stop)
  let $delta := ($end-opacity - $start-opacity) div ($n - 1)
  return
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
      return (
        <svg:stop stop-opacity="{$opacity}">{
          $stop/(@* except @stop-opacity)
        }</svg:stop>
      )
    }</svg:linearGradient>
};

declare function this:linear-symmetric-fade(
  $base-gradient as element(svg:linearGradient),
  $start-opacity as xs:double,
  $peak-opacity as xs:double,
  $end-opacity as xs:double
) as element(svg:linearGradient)
{
  let $n := count($base-gradient/svg:stop)
  let $half := $n idiv 2
  let $delta1 := ($peak-opacity - $start-opacity) div ($n idiv 2)
  let $delta2 := ($end-opacity - $peak-opacity) div (($n + 1) idiv 2)
  return
    <svg:linearGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      let $opacity :=
        if ($i <= $half)
        then util:decimal($start-opacity + $delta1*($i - 1), 2)
        else util:decimal($peak-opacity + $delta2*($i - $half - 1), 2)
      return (
        <svg:stop stop-opacity="{$opacity}">{
          $stop/(@* except @stop-opacity)
        }</svg:stop>
      )
    }</svg:linearGradient>
};

declare function this:linear-transform(
  $base-gradient as element(svg:linearGradient),
  $transform as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient gradientTransform="{$transform}">{
    $base-gradient/(@* except @gradientTransform),
    $base-gradient/*
  }</svg:linearGradient>
};

declare function this:linear-units(
  $base-gradient as element(svg:linearGradient),
  $objectBoundingBox as xs:boolean
) as element(svg:linearGradient)
{
  let $val := if ($objectBoundingBox) then "objectBoundingBox" else "userSpaceOnUse"
  return
    <svg:linearGradient gradientUnits="{$val}">{
      $base-gradient/(@* except @gradientUnits),
      $base-gradient/*
    }</svg:linearGradient>
};

declare function this:linear-spread(
  $base-gradient as element(svg:linearGradient),
  $method as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient spreadMethod="{$method}">{
    $base-gradient/(@* except @spreadMethod),
    $base-gradient/*
  }</svg:linearGradient>  
};

declare function this:linear-layout(
  $base-gradient as element(svg:linearGradient),
  $layout as xs:integer*
) as element(svg:linearGradient)
{
  let $x1 := ($layout[1],0)[1]
  let $x2 := ($layout[2],100)[1]
  let $y1 := ($layout[3],0)[1]
  let $y2 := ($layout[4],0)[1]
  return
  <svg:linearGradient x1="{$x1}%" x2="{$x2}%" y1="{$y1}%" y2="{$y2}%">{
    $base-gradient/(@* except (@x1|@x2|@y1|@y2)),
    $base-gradient/*
  }</svg:linearGradient>  
};

declare function this:linear-ref(
  $name as xs:string,
  $base-gradient-name as xs:string
) as element(svg:linearGradient)
{
  <svg:linearGradient id="{this:safe($name)}" xlink:href="#{$base-gradient-name}"/>
};


declare function this:radial-name(
  $base-gradient as element(svg:radialGradient),
  $name as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient id="{this:safe($name)}">{
    $base-gradient/(@* except @id),
    $base-gradient/*
  }</svg:radialGradient>  
};


declare function this:radial-outflow(
  $base-gradient as element(svg:linearGradient)
) as element(svg:radialGradient)
{
  <svg:radialGradient cx="50%" cy="50%" r="50%">{
    $base-gradient/(@* except (@r|@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
};

declare function this:radial-inflow(
  $base-gradient as element(svg:linearGradient)
) as element(svg:radialGradient)
{
  let $offsets :=
    reverse(
      for $offset in $base-gradient/svg:stop/@offset
      return
        if (contains($offset,"%"))
        then util:decimal(100.0 - number(substring-before($offset,"%")), 2)
        else util:decimal(100.0 - number($offset), 2)
    )
  let $colours := reverse($base-gradient/svg:stop/@stop-color)
  let $opacities := reverse($base-gradient/svg:stop/@stop-opacity)
  return (
    <svg:radialGradient cx="50%" cy="50%" r="50%">{
      $base-gradient/(@* except (@r|@cx|@cy)),
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      return (
        <svg:stop offset="{$offsets[$i]}%" stop-color="{$colours[$i]}" stop-opacity="{$opacities[$i]}"/>
      )
    }</svg:radialGradient>
  )
};

declare function this:radial-reverse(
  $base-gradient as element(svg:radialGradient)
) as element(svg:radialGradient)
{
  let $offsets :=
    reverse(
      for $offset in $base-gradient/svg:stop/@offset
      return
        if (contains($offset,"%"))
        then util:decimal(100.0 - number(substring-before($offset,"%")), 2)
        else util:decimal(100.0 - number($offset), 2)
    )
  let $colours := reverse($base-gradient/svg:stop/@stop-color)
  let $opacities := reverse($base-gradient/svg:stop/@stop-opacity)
  return (
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      return (
        <svg:stop offset="{$offsets[$i]}%" stop-color="{$colours[$i]}" stop-opacity="{$opacities[$i]}"/>
      )
    }</svg:radialGradient>
  )
};

declare function this:radial-random(
  $base-gradient as element(svg:radialGradient)
) as element(svg:radialGradient)
{
  let $stops := rand:shuffle($base-gradient/svg:stop)
  let $percentages := util:linspace(count($stops), 0, 100)!util:decimal(., 2)
  return (
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $stops
      return (
        <svg:stop offset="{$percentages[$i]}%">{
          $stop/(@* except @offset)
        }</svg:stop>
      )
    }</svg:radialGradient>
  )
};

declare function this:radial-interleave(
  $base-gradient as element(svg:radialGradient),
  $interleave as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    return (
      if ($i mod 2 = 0) then $stop
      else (
        <svg:stop stop-color="{$interleave}">{
          $stop/(@* except @stop-color)
        }</svg:stop>
      )
    )
  }</svg:radialGradient>
};

declare function this:radial-opacity(
  $base-gradient as element(svg:radialGradient),
  $opacity as function(xs:integer) as xs:double
) as element(svg:radialGradient)
{
  <svg:radialGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-opacity := util:decimal(util:clamp($opacity($i),0,1),3)
    return (
      <svg:stop stop-opacity="{$stop-opacity}">{
        $stop/(@* except @stop-opacity)
      }</svg:stop>
    )
  }</svg:radialGradient>
};

declare function this:radial-colour(
  $base-gradient as element(svg:radialGradient),
  $colour as function(xs:integer) as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient>{
    $base-gradient/@*,
    $base-gradient/(* except svg:stop),
    for $stop at $i in $base-gradient/svg:stop
    let $stop-colour := $colour($i)
    return (
      <svg:stop stop-color="{$stop-colour}">{
        $stop/(@* except @stop-color)
      }</svg:stop>
    )
  }</svg:radialGradient>
};

declare function this:radial-sample(
  $base-gradient as element(svg:radialGradient),
  $num as xs:integer
) as element(svg:radialGradient)
{
  if ($num <= 1) then (
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/*
    }</svg:radialGradient>
  ) else (
    let $stops := $base-gradient/svg:stop
    let $percentages := util:linspace($num, 0, 100)!util:decimal(., 2)
    let $sep := count($stops) div $num
    return
      if (count($stops) <= $num) then (
        <svg:radialGradient>{
          $base-gradient/@*,
          $base-gradient/*
        }</svg:radialGradient>
      ) else (
        <svg:radialGradient>{
          $base-gradient/@*,
          $base-gradient/(* except svg:stop),
          for $i in 1 to $num
          let $stop := $stops[1 + ceiling($sep*($i - 1))]
          return (
            <svg:stop offset="{$percentages[$i]}%">{$stop/(@* except @offset)}</svg:stop>
          )
        }</svg:radialGradient>
      )
  )
};

declare function this:radial-fade(
  $base-gradient as element(svg:radialGradient),
  $start-opacity as xs:double,
  $end-opacity as xs:double
) as element(svg:radialGradient)
{
  let $n := count($base-gradient/svg:stop)
  let $delta := ($end-opacity - $start-opacity) div $n
  return
    <svg:radialGradient>{
      $base-gradient/@*,
      $base-gradient/(* except svg:stop),
      for $stop at $i in $base-gradient/svg:stop
      let $opacity := util:decimal($start-opacity + $delta*($i - 1), 2)
      return (
        <svg:stop stop-opacity="{$opacity}">{
          $stop/(@* except @stop-opacity)
        }</svg:stop>
      )
    }</svg:radialGradient>
};

declare function this:radial-units(
  $base-gradient as element(svg:radialGradient),
  $objectBoundingBox as xs:boolean
) as element(svg:radialGradient)
{
  let $val := if ($objectBoundingBox) then "objectBoundingBox" else "userSpaceOnUse"
  return
    <svg:radialGradient gradientUnits="{$val}">{
      $base-gradient/(@* except @gradientUnits),
      $base-gradient/*
    }</svg:radialGradient>
};

declare function this:radial-center(
  $base-gradient as element(svg:radialGradient),
  $cx-percent as xs:integer,
  $cy-percent as xs:integer
) as element(svg:radialGradient)
{
  <svg:radialGradient cx="{$cx-percent}%" cy="{$cy-percent}%">{
    $base-gradient/(@* except (@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
};

declare function this:radial-focus(
  $base-gradient as element(svg:radialGradient),
  $fx-percent as xs:integer,
  $fy-percent as xs:integer
) as element(svg:radialGradient)
{
  <svg:radialGradient fx="{$fx-percent}%" fy="{$fy-percent}%">{
    $base-gradient/(@* except (@fx|@fy)),
    $base-gradient/*
  }</svg:radialGradient>  
};

declare function this:radial-radius(
  $base-gradient as element(svg:radialGradient),
  $r-percent as xs:integer
) as element(svg:radialGradient)
{
  <svg:radialGradient r="{$r-percent}%">{
    $base-gradient/(@* except (@r)),
    $base-gradient/*
  }</svg:radialGradient>  
};

declare function this:radial-circle(
  $base-gradient as element(svg:radialGradient),
  $radial-circle as map(xs:string,item()*) (: all as percents :)
) as element(svg:radialGradient)
{
  let $r := ellipse:radius($radial-circle)
  let $center := ellipse:center($radial-circle)
  let $cx := point:x($center)
  let $cy := point:y($center)
  return
  <svg:radialGradient cx="{$cx}%" cy="{$cy}%" r="{$r}%">{
    $base-gradient/(@* except (@r|@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
};


declare function this:radial-transform(
  $base-gradient as element(svg:radialGradient),
  $transform as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient gradientTransform="{$transform}">{
    $base-gradient/(@* except @gradientTransform),
    $base-gradient/*
  }</svg:radialGradient>
};

declare function this:radial-spread(
  $base-gradient as element(svg:radialGradient),
  $method as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient spreadMethod="{$method}">{
    $base-gradient/(@* except @spreadMethod),
    $base-gradient/*
  }</svg:radialGradient>  
};


declare function this:radial-layout(
  $base-gradient as element(svg:radialGradient),
  $layout as xs:integer* (: r, cx, cy :)
) as element(svg:radialGradient)
{
  let $r := ($layout[1],50)[1]
  let $cx := ($layout[2],50)[1]
  let $cy := ($layout[3],$cx)[1]
  return
  <svg:radialGradient cx="{$cx}%" cy="{$cy}%" r="{$r}%">{
    $base-gradient/(@* except (@r|@cx|@cy)),
    $base-gradient/*
  }</svg:radialGradient>  
};

declare function this:radial-ref(
  $name as xs:string,
  $base-gradient-name as xs:string
) as element(svg:radialGradient)
{
  <svg:radialGradient id="{this:safe($name)}" xlink:href="#{$base-gradient-name}"/>
};

declare function this:expand-evenly(
  $num-stops as xs:integer,
  $base-rgb-str as xs:string*,
  $do-spline as xs:boolean
) as xs:string*
{
  (: To end up w/ 512 stops, we need to add n interpolations to each edge
   : we have ns stops already, so:
   : (ns - 1)*n + ns = 512
   : n = (512 - ns)/(ns - 1)
   : We can only interpolate an integer number of points so that
   : leaves us a residue which we use as a probability of adding another point
   :)
  let $ns := count($base-rgb-str)
  let $n := ($num-stops - $ns) idiv ($ns - 1)
  let $residue := 100 * ($ns - 1) * ((($num-stops - $ns) div ($ns - 1)) - $n)
  let $base-rgb := $base-rgb-str!rgb:rgb(.)
  let $base-hsl := $base-rgb!cs:rgb-to-hsluv(.)
  let $expanded-hsl := (
    $base-hsl[1],
    let $edges :=
      if ($do-spline) then (
        spline:spline($base-hsl)=>path:edges()
      ) else (
        edge:to-edges($base-hsl)
      )
    for $edge in $edges
    let $n-interp :=
      if (rand:flip($residue)) then $n + 1 else $n
    let $interp := tail(edge:interpolate($n-interp, $edge))
    return $interp
  )
  let $expanded-rgb := $expanded-hsl!cs:hsluv-to-rgb(.)
  let $expanded-rgb-str := $expanded-rgb!rgb:to-string(.)
  return $expanded-rgb-str
};

(: Interpolate in a way to preserve relative scaling :)
declare function this:expand-scaled(
  $num-stops as xs:integer,
  $base-rgb-str as xs:string*,
  $base-percents as xs:double*,
  $do-spline as xs:boolean
) as xs:string*
{
  let $ns := count($base-rgb-str)
  let $edge-fractions :=
    for $percent at $i in tail($base-percents)
    return ($percent - $base-percents[$i]) div 100.0
  (: To end up w/ 512 stops, we need to add 512 - ns total
   : allocated in accordance with the percentage differences for each
   : edge
   : Use $num-stops - 1 because the percentages end up including the end
   : points so that works better
   : We can only interpolate an integer number of points so that
   : leaves us a residue which we use as a probability of adding another point
   :)
  let $edge-ns :=
    let $total := $num-stops - 1 (: $ns :)
    for $fraction in $edge-fractions
    return round($total * $fraction) cast as xs:integer
  let $residue := 100 * (($num-stops - (: $ns :) 1) - sum($edge-ns)) div ($ns - 1)
  let $base-rgb := $base-rgb-str!rgb:rgb(.)
  let $base-hsl := $base-rgb!cs:rgb-to-hsluv(.)
  let $expanded-hsl := (
    $base-hsl[1],
    let $edges :=
      if ($do-spline) then (
        spline:spline($base-hsl)=>path:edges()
      ) else (
        edge:to-edges($base-hsl)
      )
    for $edge at $i in $edges
    let $n-interp :=
      if (rand:flip($residue)) then $edge-ns[$i] + 1 else $edge-ns[$i]
    let $interp := tail(edge:interpolate($n-interp, $edge))
    return $interp
  )
  let $expanded-rgb := $expanded-hsl!cs:hsluv-to-rgb(.)
  let $expanded-rgb-str := $expanded-rgb!rgb:to-string(.)
  return $expanded-rgb-str
};

declare function this:expand-gradient(
  $num-stops as xs:integer,
  $base-gradient as xs:string,
  $do-even as xs:boolean,
  $do-spline as xs:boolean
) as xs:string*
{
  let $base-rgb-str := this:gradient-colours($base-gradient)
  let $base-percents :=
    for $offset in this:gradient-stops($base-gradient)/@offset
    return xs:double(substring-before(string($offset),"%"))
  return
    if ($do-even)
    then this:expand-evenly($num-stops, $base-rgb-str, $do-spline)
    else this:expand-scaled($num-stops, $base-rgb-str, $base-percents, $do-spline)
};