- name: 2d.shadow.attributes.shadowBlur.initial
  code: |
    @assert ctx.shadowBlur === 0;

- name: 2d.shadow.attributes.shadowBlur.valid
  code: |
    ctx.shadowBlur = 1;
    @assert ctx.shadowBlur === 1;

    ctx.shadowBlur = 0.5;
    @assert ctx.shadowBlur === 0.5;

    ctx.shadowBlur = 1e6;
    @assert ctx.shadowBlur === 1e6;

    ctx.shadowBlur = 0;
    @assert ctx.shadowBlur === 0;

- name: 2d.shadow.attributes.shadowBlur.invalid
  code: |
    ctx.shadowBlur = 1;
    ctx.shadowBlur = -2;
    @assert ctx.shadowBlur === 1;

    ctx.shadowBlur = 1;
    ctx.shadowBlur = Infinity;
    @assert ctx.shadowBlur === 1;

    ctx.shadowBlur = 1;
    ctx.shadowBlur = -Infinity;
    @assert ctx.shadowBlur === 1;

    ctx.shadowBlur = 1;
    ctx.shadowBlur = NaN;
    @assert ctx.shadowBlur === 1;

    ctx.shadowBlur = 1;
    ctx.shadowBlur = 'string';
    @assert ctx.shadowBlur === 1;

    ctx.shadowBlur = 1;
    ctx.shadowBlur = true;
    @assert ctx.shadowBlur === 1;

    ctx.shadowBlur = 1;
    ctx.shadowBlur = false;
    @assert ctx.shadowBlur === 0;

- name: 2d.shadow.attributes.shadowOffset.initial
  code: |
    @assert ctx.shadowOffsetX === 0;
    @assert ctx.shadowOffsetY === 0;

- name: 2d.shadow.attributes.shadowOffset.valid
  code: |
    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 2;
    @assert ctx.shadowOffsetX === 1;
    @assert ctx.shadowOffsetY === 2;

    ctx.shadowOffsetX = 0.5;
    ctx.shadowOffsetY = 0.25;
    @assert ctx.shadowOffsetX === 0.5;
    @assert ctx.shadowOffsetY === 0.25;

    ctx.shadowOffsetX = -0.5;
    ctx.shadowOffsetY = -0.25;
    @assert ctx.shadowOffsetX === -0.5;
    @assert ctx.shadowOffsetY === -0.25;

    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
    @assert ctx.shadowOffsetX === 0;
    @assert ctx.shadowOffsetY === 0;

    ctx.shadowOffsetX = 1e6;
    ctx.shadowOffsetY = 1e6;
    @assert ctx.shadowOffsetX === 1e6;
    @assert ctx.shadowOffsetY === 1e6;

- name: 2d.shadow.attributes.shadowOffset.invalid
  code: |
    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 2;
    ctx.shadowOffsetX = Infinity;
    ctx.shadowOffsetY = Infinity;
    @assert ctx.shadowOffsetX === 1;
    @assert ctx.shadowOffsetY === 2;

    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 2;
    ctx.shadowOffsetX = -Infinity;
    ctx.shadowOffsetY = -Infinity;
    @assert ctx.shadowOffsetX === 1;
    @assert ctx.shadowOffsetY === 2;

    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 2;
    ctx.shadowOffsetX = NaN;
    ctx.shadowOffsetY = NaN;
    @assert ctx.shadowOffsetX === 1;
    @assert ctx.shadowOffsetY === 2;

    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 2;
    ctx.shadowOffsetX = 'string';
    ctx.shadowOffsetY = 'string';
    @assert ctx.shadowOffsetX === 1;
    @assert ctx.shadowOffsetY === 2;

    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 2;
    ctx.shadowOffsetX = true;
    ctx.shadowOffsetY = true;
    @assert ctx.shadowOffsetX === 1;
    @assert ctx.shadowOffsetY === 1;

    ctx.shadowOffsetX = 1;
    ctx.shadowOffsetY = 2;
    ctx.shadowOffsetX = false;
    ctx.shadowOffsetY = false;
    @assert ctx.shadowOffsetX === 0;
    @assert ctx.shadowOffsetY === 0;

- name: 2d.shadow.attributes.shadowColor.initial
  code: |
    @assert ctx.shadowColor === 'rgba(0, 0, 0, 0)';

- name: 2d.shadow.attributes.shadowColor.valid
  code: |
    ctx.shadowColor = 'lime';
    @assert ctx.shadowColor === '#00ff00';

    ctx.shadowColor = 'RGBA(0,255, 0,0)';
    @assert ctx.shadowColor === 'rgba(0, 255, 0, 0)';

- name: 2d.shadow.attributes.shadowColor.current.basic
  desc: currentColor is computed from the canvas element
  canvas_types: ['HtmlCanvas']
  code: |
    canvas.style.color = '#0f0';
    ctx.shadowColor = 'currentColor';
    @assert ctx.shadowColor === '#00ff00';

- name: 2d.shadow.attributes.shadowColor.current.changed
  desc: currentColor is computed when the attribute is set, not when it is painted
  canvas_types: ['HtmlCanvas']
  code: |
    canvas.style.color = '#0f0';
    ctx.shadowColor = 'currentColor';
    canvas.style.color = '#f00';
    @assert ctx.shadowColor === '#00ff00';

- name: 2d.shadow.attributes.shadowColor.current.removed
  desc: currentColor is solid black when the canvas element is not in a document
  canvas_types: ['HtmlCanvas']
  code: |
    // Try not to let it undetectably incorrectly pick up opaque-black
    // from other parts of the document:
    document.documentElement.style.color = '#f00';
    document.body.style.color = '#f00';
    canvas.style.color = '#f00';

    canvas.remove();
    ctx.shadowColor = 'currentColor';
    @assert ctx.shadowColor === '#000000';

- name: 2d.shadow.attributes.shadowColor.invalid
  code: |
    ctx.shadowColor = '#00ff00';
    ctx.shadowColor = 'bogus';
    @assert ctx.shadowColor === '#00ff00';

    ctx.shadowColor = '#00ff00';
    ctx.shadowColor = 'red bogus';
    @assert ctx.shadowColor === '#00ff00';

    ctx.shadowColor = '#00ff00';
    ctx.shadowColor = ctx;
    @assert ctx.shadowColor === '#00ff00';

    ctx.shadowColor = '#00ff00';
    ctx.shadowColor = undefined;
    @assert ctx.shadowColor === '#00ff00';

- name: 2d.shadow.enable.off.1
  desc: Shadows are not drawn when only shadowColor is set
  code: |
    ctx.shadowColor = '#f00';
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.enable.off.2
  desc: Shadows are not drawn when only shadowColor is set
  code: |
    ctx.globalCompositeOperation = 'destination-atop';
    ctx.shadowColor = '#f00';
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.enable.blur
  desc: Shadows are drawn if shadowBlur is set
  code: |
    ctx.globalCompositeOperation = 'destination-atop';
    ctx.shadowColor = '#0f0';
    ctx.shadowBlur = 0.1;
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.enable.x
  desc: Shadows are drawn if shadowOffsetX is set
  code: |
    ctx.globalCompositeOperation = 'destination-atop';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetX = 0.1;
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.enable.y
  desc: Shadows are drawn if shadowOffsetY is set
  code: |
    ctx.globalCompositeOperation = 'destination-atop';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 0.1;
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.offset.positiveX
  desc: Shadows can be offset with positive x
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0f0';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetX = 50;
    ctx.fillRect(0, 0, 50, 50);
    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.offset.negativeX
  desc: Shadows can be offset with negative x
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0f0';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetX = -50;
    ctx.fillRect(50, 0, 50, 50);
    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.offset.positiveY
  desc: Shadows can be offset with positive y
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0f0';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 25;
    ctx.fillRect(0, 0, 100, 25);
    @assert pixel 50,12 == 0,255,0,255;
    @assert pixel 50,37 == 0,255,0,255;
  expected: green

- name: 2d.shadow.offset.negativeY
  desc: Shadows can be offset with negative y
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0f0';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = -25;
    ctx.fillRect(0, 25, 100, 25);
    @assert pixel 50,12 == 0,255,0,255;
    @assert pixel 50,37 == 0,255,0,255;
  expected: green

- name: 2d.shadow.outside
  desc: Shadows of shapes outside the visible area can be offset onto the visible
    area
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetX = 100;
    ctx.fillRect(-100, 0, 25, 50);
    ctx.shadowOffsetX = -100;
    ctx.fillRect(175, 0, 25, 50);
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 100;
    ctx.fillRect(25, -100, 50, 25);
    ctx.shadowOffsetY = -100;
    ctx.fillRect(25, 125, 50, 25);
    @assert pixel 12,25 == 0,255,0,255;
    @assert pixel 87,25 == 0,255,0,255;
    @assert pixel 50,12 == 0,255,0,255;
    @assert pixel 50,37 == 0,255,0,255;
  expected: green

- name: 2d.shadow.clip.1
  desc: Shadows of clipped shapes are still drawn within the clipping region
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#f00';
    ctx.fillRect(50, 0, 50, 50);

    ctx.save();
    ctx.beginPath();
    ctx.rect(50, 0, 50, 50);
    ctx.clip();
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetX = 50;
    ctx.fillRect(0, 0, 50, 50);
    ctx.restore();

    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.clip.2
  desc: Shadows are not drawn outside the clipping region
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(50, 0, 50, 50);

    ctx.save();
    ctx.beginPath();
    ctx.rect(0, 0, 50, 50);
    ctx.clip();
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetX = 50;
    ctx.fillRect(0, 0, 50, 50);
    ctx.restore();

    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.clip.3
  desc: Shadows of clipped shapes are still drawn within the clipping region
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(50, 0, 50, 50);

    ctx.save();
    ctx.beginPath();
    ctx.rect(0, 0, 50, 50);
    ctx.clip();
    ctx.fillStyle = '#f00';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetX = 50;
    ctx.fillRect(-50, 0, 50, 50);
    ctx.restore();

    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.stroke.basic
  desc: Shadows are drawn for strokes
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.strokeStyle = '#f00';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 50;
    ctx.beginPath();
    ctx.lineWidth = 50;
    ctx.moveTo(0, -25);
    ctx.lineTo(100, -25);
    ctx.stroke();

    @assert pixel 1,25 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 98,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.stroke.cap.1
  desc: Shadows are not drawn for areas outside stroke caps
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.strokeStyle = '#f00';
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetY = 50;
    ctx.beginPath();
    ctx.lineWidth = 50;
    ctx.lineCap = 'butt';
    ctx.moveTo(-50, -25);
    ctx.lineTo(0, -25);
    ctx.moveTo(100, -25);
    ctx.lineTo(150, -25);
    ctx.stroke();

    @assert pixel 1,25 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 98,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.stroke.cap.2
  desc: Shadows are drawn for stroke caps
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.strokeStyle = '#f00';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 50;
    ctx.beginPath();
    ctx.lineWidth = 50;
    ctx.lineCap = 'square';
    ctx.moveTo(25, -25);
    ctx.lineTo(75, -25);
    ctx.stroke();

    @assert pixel 1,25 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 98,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.stroke.join.1
  desc: Shadows are not drawn for areas outside stroke joins
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.strokeStyle = '#f00';
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetX = 100;
    ctx.lineWidth = 200;
    ctx.lineJoin = 'bevel';
    ctx.beginPath();
    ctx.moveTo(-200, -50);
    ctx.lineTo(-150, -50);
    ctx.lineTo(-151, -100);
    ctx.stroke();

    @assert pixel 1,1 == 0,255,0,255;
    @assert pixel 48,48 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 98,48 == 0,255,0,255;
  expected: green

- name: 2d.shadow.stroke.join.2
  desc: Shadows are drawn for stroke joins
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(50, 0, 50, 50);
    ctx.strokeStyle = '#f00';
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetX = 100;
    ctx.lineWidth = 200;
    ctx.lineJoin = 'miter';
    ctx.beginPath();
    ctx.moveTo(-200, -50);
    ctx.lineTo(-150, -50);
    ctx.lineTo(-151, -100);
    ctx.stroke();

    @assert pixel 1,1 == 0,255,0,255;
    @assert pixel 48,48 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 98,48 == 0,255,0,255;
  expected: green

- name: 2d.shadow.stroke.join.3
  desc: Shadows are drawn for stroke joins respecting miter limit
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.strokeStyle = '#f00';
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetX = 100;
    ctx.lineWidth = 200;
    ctx.lineJoin = 'miter';
    ctx.miterLimit = 0.1;
    ctx.beginPath();
    ctx.moveTo(-200, -50);
    ctx.lineTo(-150, -50);
    ctx.lineTo(-151, -100); // (not an exact right angle, to avoid some other bug in Firefox 3)
    ctx.stroke();

    @assert pixel 1,1 == 0,255,0,255;
    @assert pixel 48,48 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 98,48 == 0,255,0,255;
  expected: green

- name: 2d.shadow.image.basic
  desc: Shadows are drawn for images
  images:
  - red.png
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 50;
    {{ load_image }}
    ctx.drawImage(img, 0, -50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green
  variants: &load-image-variant-definition
  - HtmlCanvas:
      append_variants_to_name: false
      canvas_types: ['HtmlCanvas']
      load_image: var img = document.getElementById('{{ images[0] }}');
    OffscreenCanvas:
      append_variants_to_name: false
      canvas_types: ['OffscreenCanvas', 'Worker']
      test_type: promise
      load_image: |-
        var response = await fetch('/images/{{ images[0] }}')
        var blob = await response.blob();
        var img = await createImageBitmap(blob);

- name: 2d.shadow.image.transparent.1
  desc: Shadows are not drawn for transparent images
  images:
  - transparent.png
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetY = 50;
    {{ load_image }}
    ctx.drawImage(img, 0, -50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green
  variants: *load-image-variant-definition

- name: 2d.shadow.image.transparent.2
  desc: Shadows are not drawn for transparent parts of images
  images:
  - redtransparent.png
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#f00';
    ctx.fillRect(50, 0, 50, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#0f0';
    {{ load_image }}
    ctx.drawImage(img, 50, -50);
    ctx.shadowColor = '#f00';
    ctx.drawImage(img, -50, -50);

    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green
  variants: *load-image-variant-definition

- name: 2d.shadow.image.alpha
  desc: Shadows are drawn correctly for partially-transparent images
  images:
  - transparent50.png
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#00f';
    {{ load_image }}
    ctx.drawImage(img, 0, -50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()
  variants: *load-image-variant-definition

- name: 2d.shadow.image.section
  desc: Shadows are not drawn for areas outside image source rectangles
  images:
  - redtransparent.png
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#f00';
    {{ load_image }}
    ctx.drawImage(img, 50, 0, 50, 50, 0, -50, 50, 50);

    @assert pixel 25,25 ==~ 0,255,0,255;
    @assert pixel 50,25 ==~ 0,255,0,255;
    @assert pixel 75,25 ==~ 0,255,0,255;
  expected: green
  variants: *load-image-variant-definition

- name: 2d.shadow.image.scale
  desc: Shadows are drawn correctly for scaled images
  images:
  - redtransparent.png
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#0f0';
    {{ load_image }}
    ctx.drawImage(img, 0, 0, 100, 50, -10, -50, 240, 50);

    @assert pixel 25,25 ==~ 0,255,0,255;
    @assert pixel 50,25 ==~ 0,255,0,255;
    @assert pixel 75,25 ==~ 0,255,0,255;
  expected: green
  variants: *load-image-variant-definition

- name: 2d.shadow.canvas.basic
  desc: Shadows are drawn for canvases
  code: |
    {{ create_canvas2 }}
    var ctx2 = canvas2.getContext('2d');
    ctx2.fillStyle = '#f00';
    ctx2.fillRect(0, 0, 100, 50);

    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 50;
    ctx.drawImage(canvas2, 0, -50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green
  variants: &create-canvas2-variant-definition
  - HtmlCanvas:
      append_variants_to_name: false
      canvas_types: ['HtmlCanvas']
      create_canvas2: |-
        var canvas2 = document.createElement('canvas');
        canvas2.width = 100;
        canvas2.height = 50;
    OffscreenCanvas:
      append_variants_to_name: false
      canvas_types: ['OffscreenCanvas', 'Worker']
      create_canvas2: |-
        var canvas2 = new OffscreenCanvas(100, 50);

- name: 2d.shadow.canvas.transparent.1
  desc: Shadows are not drawn for transparent canvases
  code: |
    {{ create_canvas2 }}
    var ctx2 = canvas2.getContext('2d');

    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetY = 50;
    ctx.drawImage(canvas2, 0, -50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green
  variants: *create-canvas2-variant-definition

- name: 2d.shadow.canvas.transparent.2
  desc: Shadows are not drawn for transparent parts of canvases
  code: |
    {{ create_canvas2 }}
    var ctx2 = canvas2.getContext('2d');
    ctx2.fillStyle = '#f00';
    ctx2.fillRect(0, 0, 50, 50);

    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#f00';
    ctx.fillRect(50, 0, 50, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#0f0';
    ctx.drawImage(canvas2, 50, -50);
    ctx.shadowColor = '#f00';
    ctx.drawImage(canvas2, -50, -50);

    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green
  variants: *create-canvas2-variant-definition

- name: 2d.shadow.canvas.alpha
  desc: Shadows are drawn correctly for partially-transparent canvases
  code: |
    {{ create_canvas2 }}
    var ctx2 = canvas2.getContext('2d');
    ctx2.fillStyle = 'rgba(255, 0, 0, 0.5)';
    ctx2.fillRect(0, 0, 100, 50);

    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#00f';
    ctx.drawImage(canvas2, 0, -50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()
  variants: *create-canvas2-variant-definition

- name: 2d.shadow.pattern.basic
  desc: Shadows are drawn for fill patterns
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  images:
  - red.png
  code: |
    {{ load_image }}
    var pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 50;
    ctx.fillStyle = pattern;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green
  variants: *load-image-variant-definition

- name: 2d.shadow.pattern.transparent.1
  desc: Shadows are not drawn for transparent fill patterns
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  images:
  - transparent.png
  code: |
    {{ load_image }}
    var pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetY = 50;
    ctx.fillStyle = pattern;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green
  variants: *load-image-variant-definition

- name: 2d.shadow.pattern.transparent.2
  desc: Shadows are not drawn for transparent parts of fill patterns
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  images:
  - redtransparent.png
  code: |
    {{ load_image }}
    var pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(50, 0, 50, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#0f0';
    ctx.fillStyle = pattern;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green
  variants: *load-image-variant-definition

- name: 2d.shadow.pattern.alpha
  desc: Shadows are drawn correctly for partially-transparent fill patterns
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  images:
  - transparent50.png
  code: |
    {{ load_image }}
    var pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#00f';
    ctx.fillStyle = pattern;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()
  variants: *load-image-variant-definition

- name: 2d.shadow.gradient.basic
  desc: Shadows are drawn for gradient fills
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  code: |
    var gradient = ctx.createLinearGradient(0, 0, 100, 0);
    gradient.addColorStop(0, '#f00');
    gradient.addColorStop(1, '#f00');
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#0f0';
    ctx.shadowOffsetY = 50;
    ctx.fillStyle = gradient;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.gradient.transparent.1
  desc: Shadows are not drawn for transparent gradient fills
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  code: |
    var gradient = ctx.createLinearGradient(0, 0, 100, 0);
    gradient.addColorStop(0, 'rgba(0,0,0,0)');
    gradient.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetY = 50;
    ctx.fillStyle = gradient;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.gradient.transparent.2
  desc: Shadows are not drawn for transparent parts of gradient fills
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  code: |
    var gradient = ctx.createLinearGradient(0, 0, 100, 0);
    gradient.addColorStop(0, '#f00');
    gradient.addColorStop(0.499, '#f00');
    gradient.addColorStop(0.5, 'rgba(0,0,0,0)');
    gradient.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 50, 50);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(50, 0, 50, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#0f0';
    ctx.fillStyle = gradient;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 25,25 == 0,255,0,255;
    @assert pixel 50,25 == 0,255,0,255;
    @assert pixel 75,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.gradient.alpha
  desc: Shadows are drawn correctly for partially-transparent gradient fills
  # http://bugs.webkit.org/show_bug.cgi?id=15266
  code: |
    var gradient = ctx.createLinearGradient(0, 0, 100, 0);
    gradient.addColorStop(0, 'rgba(255,0,0,0.5)');
    gradient.addColorStop(1, 'rgba(255,0,0,0.5)');
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#00f';
    ctx.fillStyle = gradient;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()

- name: 2d.shadow.transform.1
  desc: Shadows take account of transformations
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#0f0';
    ctx.translate(100, 100);
    ctx.fillRect(-100, -150, 100, 50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.transform.2
  desc: Shadow offsets are not affected by transformations
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowOffsetY = 50;
    ctx.shadowColor = '#0f0';
    ctx.rotate(Math.PI)
    ctx.fillRect(-100, 0, 100, 50);

    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: 2d.shadow.blur.low
  desc: Shadows look correct for small blurs
  manual:
  code: |
    ctx.fillStyle = '#ff0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#00f';
    ctx.shadowOffsetY = 25;
    for (var x = 0; x < 100; ++x) {
        ctx.save();
        ctx.beginPath();
        ctx.rect(x, 0, 1, 50);
        ctx.clip();
        ctx.shadowBlur = x;
        ctx.fillRect(-200, -200, 500, 200);
        ctx.restore();
    }
  expected: |
    size 100 50
    import math
    cr.set_source_rgb(0, 0, 1)
    cr.rectangle(0, 0, 1, 25)
    cr.fill()
    cr.set_source_rgb(1, 1, 0)
    cr.rectangle(0, 25, 1, 25)
    cr.fill()
    for x in range(1, 100):
        sigma = x/2.0
        filter = []
        for i in range(-24, 26):
            filter.append(math.exp(-i*i / (2*sigma*sigma)) / (math.sqrt(2*math.pi)*sigma))
        accum = [0]
        for f in filter:
            accum.append(accum[-1] + f)
        for y in range(0, 50):
            cr.set_source_rgb(accum[y], accum[y], 1-accum[y])
            cr.rectangle(x, y, 1, 1)
            cr.fill()

- name: 2d.shadow.blur.high
  desc: Shadows look correct for large blurs
  manual:
  code: |
    ctx.fillStyle = '#ff0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = '#00f';
    ctx.shadowOffsetY = 0;
    ctx.shadowBlur = 100;
    ctx.fillRect(-200, -200, 200, 400);
  expected: |
    size 100 50
    import math
    sigma = 100.0/2
    filter = []
    for i in range(-200, 100):
        filter.append(math.exp(-i*i / (2*sigma*sigma)) / (math.sqrt(2*math.pi)*sigma))
    accum = [0]
    for f in filter:
        accum.append(accum[-1] + f)
    for x in range(0, 100):
        cr.set_source_rgb(accum[x+200], accum[x+200], 1-accum[x+200])
        cr.rectangle(x, 0, 1, 50)
        cr.fill()

- name: 2d.shadow.alpha.1
  desc: Shadow color alpha components are used
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = 'rgba(255, 0, 0, 0.01)';
    ctx.shadowOffsetY = 50;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 ==~ 0,255,0,255 +/- 4;
  expected: green

- name: 2d.shadow.alpha.2
  desc: Shadow color alpha components are used
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.shadowColor = 'rgba(0, 0, 255, 0.5)';
    ctx.shadowOffsetY = 50;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()

- name: 2d.shadow.alpha.3
  desc: Shadows are affected by globalAlpha
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#f00'; // (work around broken Firefox globalAlpha caching)
    ctx.shadowColor = '#00f';
    ctx.shadowOffsetY = 50;
    ctx.globalAlpha = 0.5;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()

- name: 2d.shadow.alpha.4
  desc: Shadows with alpha components are correctly affected by globalAlpha
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#f00'; // (work around broken Firefox globalAlpha caching)
    ctx.shadowColor = 'rgba(0, 0, 255, 0.707)';
    ctx.shadowOffsetY = 50;
    ctx.globalAlpha = 0.707;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()

- name: 2d.shadow.alpha.5
  desc: Shadows of shapes with alpha components are drawn correctly
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = 'rgba(64, 0, 0, 0.5)';
    ctx.shadowColor = '#00f';
    ctx.shadowOffsetY = 50;
    ctx.fillRect(0, -50, 100, 50);

    @assert pixel 50,25 ==~ 127,0,127,255;
  expected: |
    size 100 50
    cr.set_source_rgb(0.5, 0, 0.5)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()

- name: 2d.shadow.composite.1
  desc: Shadows are drawn using globalCompositeOperation
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.globalCompositeOperation = 'xor';
    ctx.shadowColor = '#f00';
    ctx.shadowOffsetX = 100;
    ctx.fillStyle = '#0f0';
    ctx.fillRect(-100, 0, 200, 50);

    @assert pixel 50,25 ==~ 0,255,0,255;
  expected: green

- name: 2d.shadow.composite.2
  desc: Shadows are drawn using globalCompositeOperation
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.globalCompositeOperation = 'xor';
    ctx.shadowColor = '#f00';
    ctx.shadowBlur = 1;
    ctx.fillStyle = '#0f0';
    ctx.fillRect(-10, -10, 120, 70);

    @assert pixel 50,25 ==~ 0,255,0,255;
  expected: green

- name: 2d.shadow.composite.3
  desc: Areas outside shadows are drawn correctly with destination-out
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.globalCompositeOperation = 'destination-out';
    ctx.shadowColor = '#f00';
    ctx.shadowBlur = 10;
    ctx.fillStyle = '#f00';
    ctx.fillRect(200, 0, 100, 50);

    @assert pixel 5,5 ==~ 0,255,0,255;
    @assert pixel 50,25 ==~ 0,255,0,255;
  expected: green
