- name: 2d.composite.globalAlpha.range
  code: |
    ctx.globalAlpha = 0.5;
    // This may not set it to exactly 0.5 if it is rounded/quantised, so
    // remember for future comparisons.
    var a = ctx.globalAlpha;
    @assert ctx.globalAlpha === a;
    ctx.globalAlpha = 1.1;
    @assert ctx.globalAlpha === a;
    ctx.globalAlpha = -0.1;
    @assert ctx.globalAlpha === a;
    ctx.globalAlpha = 0;
    @assert ctx.globalAlpha === 0;
    ctx.globalAlpha = 1;
    @assert ctx.globalAlpha === 1;

- name: 2d.composite.globalAlpha.invalid
  code: |
    ctx.globalAlpha = 0.5;
    // This may not set it to exactly 0.5 if it is rounded/quantised, so
    // remember for future comparisons.
    var a = ctx.globalAlpha;
    ctx.globalAlpha = Infinity;
    @assert ctx.globalAlpha === a;
    ctx.globalAlpha = -Infinity;
    @assert ctx.globalAlpha === a;
    ctx.globalAlpha = NaN;
    @assert ctx.globalAlpha === a;

- name: 2d.composite.globalAlpha.default
  code: |
    @assert ctx.globalAlpha === 1.0;

- name: 2d.composite.globalAlpha.fill
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    // Avoiding any potential alpha = 0 optimisations.
    ctx.globalAlpha = 0.01;
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 ==~ 2,253,0,255;
  expected: green

- name: 2d.composite.globalAlpha.canvas
  canvas_types: ['HtmlCanvas']
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    ctx2.fillStyle = '#f00';
    ctx2.fillRect(0, 0, 100, 50);

    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    // Avoiding any potential alpha = 0 optimisations.
    ctx.globalAlpha = 0.01;
    ctx.drawImage(canvas2, 0, 0);
    @assert pixel 50,25 ==~ 2,253,0,255;
  expected: green

- name: 2d.composite.globalAlpha.canvaspattern
  canvas_types: ['HtmlCanvas']
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    ctx2.fillStyle = '#f00';
    ctx2.fillRect(0, 0, 100, 50);

    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = ctx.createPattern(canvas2, 'no-repeat');
    // Avoiding any potential alpha = 0 optimisations.
    ctx.globalAlpha = 0.01;
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 ==~ 2,253,0,255;
  expected: green

- name: 2d.composite.globalAlpha.canvascopy
  canvas_types: ['HtmlCanvas']
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    ctx2.fillStyle = '#0f0';
    ctx2.fillRect(0, 0, 100, 50);

    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);

    ctx.globalCompositeOperation = 'copy'
    ctx.globalAlpha = 0.51;
    ctx.drawImage(canvas2, 0, 0);
    @assert pixel 50,25 ==~ 0,255,0,130;
  expected: green


- name: 2d.composite.operation.get
  code: |
    var modes = ['source-atop', 'source-in', 'source-out', 'source-over',
        'destination-atop', 'destination-in', 'destination-out', 'destination-over',
        'lighter', 'copy', 'xor'];
    for (var i = 0; i < modes.length; ++i)
    {
        ctx.globalCompositeOperation = modes[i];
        @assert ctx.globalCompositeOperation === modes[i];
    }

- name: 2d.composite.operation.unrecognised
  code: |
    ctx.globalCompositeOperation = 'xor';
    ctx.globalCompositeOperation = 'nonexistent';
    @assert ctx.globalCompositeOperation === 'xor';

- name: 2d.composite.operation.darker
  code: |
    ctx.globalCompositeOperation = 'xor';
    ctx.globalCompositeOperation = 'darker';
    @assert ctx.globalCompositeOperation === 'xor';

- name: 2d.composite.operation.over
  code: |
    ctx.globalCompositeOperation = 'xor';
    ctx.globalCompositeOperation = 'over';
    @assert ctx.globalCompositeOperation === 'xor';

- name: 2d.composite.operation.clear
  code: |
    ctx.globalCompositeOperation = 'xor';
    ctx.globalCompositeOperation = 'clear';
    @assert ctx.globalCompositeOperation === 'clear';

- name: 2d.composite.operation.highlight
  code: |
    ctx.globalCompositeOperation = 'xor';
    ctx.globalCompositeOperation = 'highlight';
    @assert ctx.globalCompositeOperation === 'xor';

- name: 2d.composite.operation.nullsuffix
  code: |
    ctx.globalCompositeOperation = 'xor';
    ctx.globalCompositeOperation = 'source-over\0';
    @assert ctx.globalCompositeOperation === 'xor';

- name: 2d.composite.operation.casesensitive
  code: |
    ctx.globalCompositeOperation = 'xor';
    ctx.globalCompositeOperation = 'Source-over';
    @assert ctx.globalCompositeOperation === 'xor';

- name: 2d.composite.operation.default
  code: |
    @assert ctx.globalCompositeOperation === 'source-over';


- name: 2d.composite.globalAlpha.image
  test_type: promise
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    // Avoiding any potential alpha = 0 optimisations.
    ctx.globalAlpha = 0.01;
    const response = await fetch('/images/red.png');
    const blob = await response.blob();
    const bitmap = await createImageBitmap(blob);

    ctx.drawImage(bitmap, 0, 0);
    @assert pixel 50,25 ==~ 2,253,0,255;
  expected: green

- name: 2d.composite.globalAlpha.canvas
  canvas_types: ['OffscreenCanvas', 'Worker']
  code: |
    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
    var ctx2 = offscreenCanvas2.getContext('2d');
    ctx2.fillStyle = '#f00';
    ctx2.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    // Avoiding any potential alpha = 0 optimisations.
    ctx.globalAlpha = 0.01;
    ctx.drawImage(offscreenCanvas2, 0, 0);
    @assert pixel 50,25 ==~ 2,253,0,255;

- name: 2d.composite.globalAlpha.imagepattern
  test_type: promise
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    const response = await fetch('/images/red.png');
    const blob = await response.blob();
    const bitmap = await createImageBitmap(blob);

    ctx.fillStyle = ctx.createPattern(bitmap, 'no-repeat');
    // Avoiding any potential alpha = 0 optimisations.
    ctx.globalAlpha = 0.01;
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 ==~ 2,253,0,255;
  expected: green

- name: 2d.composite.globalAlpha.canvaspattern
  canvas_types: ['OffscreenCanvas', 'Worker']
  code: |
    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
    var ctx2 = offscreenCanvas2.getContext('2d');
    ctx2.fillStyle = '#f00';
    ctx2.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = ctx.createPattern(offscreenCanvas2, 'no-repeat');
    // Avoiding any potential alpha = 0 optimisations.
    ctx.globalAlpha = 0.01;
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 ==~ 2,253,0,255;

- name: 2d.composite.globalAlpha.canvascopy
  canvas_types: ['OffscreenCanvas', 'Worker']
  code: |
    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
    var ctx2 = offscreenCanvas2.getContext('2d');
    ctx2.fillStyle = '#0f0';
    ctx2.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.globalCompositeOperation = 'copy'
    ctx.globalAlpha = 0.51;
    ctx.drawImage(offscreenCanvas2, 0, 0);
    @assert pixel 50,25 ==~ 0,255,0,130;

- name: 2d.composite.grid
  size: [80, 60]
  code: |
    ctx.fillStyle = 'rgba(0, 102, 240, 0.8)';
    ctx.fillRect(15, 15, 50, 30);

    ctx.translate(25, 20);
    ctx.rotate(Math.PI / 2);
    ctx.scale(0.6, 1.2);
    ctx.translate(-25, -20);

    ctx.globalAlpha = 0.5;

    {{ js_filter_code }}
    {{ js_shadow_code }}

    ctx.globalCompositeOperation = '{{ variant_names[0] }}';

    {{ js_draw_code }}
  cairo_reference: |
    # Background.
    cr.push_group()
    cr.set_source_rgba(0, 102/255, 240/255, 0.8)
    cr.rectangle(15, 15, 50, 30)
    cr.fill()
    background = cr.pop_group()

    # Foreground.
    cr.push_group()
    cr.translate(25, 20)
    cr.rotate(math.pi / 2)
    cr.scale(0.6, 1.2)
    cr.translate(-25, -20)
    cr.set_source_rgba(52/255, 1, 52/255, 0.5)
    cr.rectangle(5, 5, 50, 30)
    cr.fill()
    foreground = cr.pop_group()

    # Filtered foreground.
    cr.push_group()
    {{ cairo_filter_code }}
    cr.set_source(foreground)
    cr.paint()
    filtered_foreground = cr.pop_group()

    {% if cairo_operator != 'SOURCE' %}
    cr.set_source(background)
    cr.paint()
    {% endif %}

    cr.set_operator(cairo.OPERATOR_{{ cairo_operator }})

    {% if cairo_operator != 'SOURCE' %}
    {{ cairo_shadow_code }}
    {% endif %}

    cr.set_source(filtered_foreground)
    cr.paint()
  fuzzy: maxDifference=0-4; totalPixels=0-33000
  variants_layout:
  - single_file
  - multi_files
  - multi_files
  - multi_files
  grid_width: 6
  variants:
  - source-over:
      cairo_operator: OVER
    source-in:
      cairo_operator: IN
    source-out:
      cairo_operator: OUT
    source-atop:
      cairo_operator: ATOP
    destination-over:
      cairo_operator: DEST_OVER
    destination-in:
      cairo_operator: DEST_IN
    destination-out:
      cairo_operator: DEST_OUT
    destination-atop:
      cairo_operator: DEST_ATOP
    lighter:
      cairo_operator: ADD
    copy:
      cairo_operator: SOURCE
    xor:
      cairo_operator: XOR
    multiply:
      cairo_operator: MULTIPLY
    screen:
      cairo_operator: SCREEN
    overlay:
      cairo_operator: OVERLAY
    darken:
      cairo_operator: DARKEN
    lighten:
      cairo_operator: LIGHTEN
    color-dodge:
      cairo_operator: COLOR_DODGE
    color-burn:
      cairo_operator: COLOR_BURN
    hard-light:
      cairo_operator: HARD_LIGHT
    soft-light:
      cairo_operator: SOFT_LIGHT
    difference:
      cairo_operator: DIFFERENCE
    exclusion:
      cairo_operator: EXCLUSION
    hue:
      cairo_operator: HSL_HUE
    saturation:
      cairo_operator: HSL_SATURATION
    color:
      cairo_operator: HSL_COLOR
    luminosity:
      cairo_operator: HSL_LUMINOSITY

  - no_filter:
      js_filter_code: // No filter.
      cairo_filter_code: "# No filter."
    filter:
      js_filter_code: |-
        ctx.filter = 'drop-shadow(5px -5px 0px rgb(255, 154, 100))';
      cairo_filter_code: |-
        cr.push_group()
        cr.set_operator(cairo.OPERATOR_OVER)
        cr.translate(5, -5)  # Filter offset.
        cr.set_source(foreground)
        cr.paint()
        cr.set_operator(cairo.OPERATOR_IN)
        cr.set_source_rgb(1, 154/255, 100/255)
        cr.paint()
        cr.pop_group_to_source()
        cr.paint()

  - no_shadow:
      js_shadow_code: // No shadow.
      cairo_shadow_code: "# No shadow."
    shadow:
      js_shadow_code: |-
        ctx.shadowOffsetX = 20;
        ctx.shadowOffsetY = 20;
        ctx.shadowColor = 'rgba(154, 0, 154, 0.8)';
      cairo_shadow_code: |-
        cr.push_group()
        cr.set_operator(cairo.OPERATOR_OVER)
        cr.translate(20, 20)  # Shadow offset.
        cr.set_source(filtered_foreground)
        cr.paint()
        cr.set_operator(cairo.OPERATOR_IN)
        cr.set_source_rgba(154/255, 0, 154/255, 0.8)
        cr.paint()
        cr.pop_group_to_source()
        cr.paint()

  - fillRect:
      js_draw_code: |-
        ctx.fillStyle = 'rgb(52, 255, 52)';
        ctx.fillRect(5, 5, 50, 30);
    drawImage:
      js_draw_code: |-
        const img_canvas = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
        const img_ctx = img_canvas.getContext('2d');
        img_ctx.fillStyle = 'rgb(52, 255, 52)';
        img_ctx.fillRect(0, 0, {{ size[0] }}, {{ size[1] }});
        ctx.drawImage(img_canvas, 5, 5, 50, 30);
    pattern:
      js_draw_code: |-
        const img_canvas = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
        const img_ctx = img_canvas.getContext('2d');
        img_ctx.fillStyle = 'rgb(52, 255, 52)';
        img_ctx.fillRect(0, 0, {{ size[0] }}, {{ size[1] }});
        ctx.fillStyle = ctx.createPattern(img_canvas, 'repeat');
        ctx.fillRect(5, 5, 50, 30);

# Composite operation tests
# <http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2007-March/010608.html>
- name: 2d.composite
  macros: |
    {% macro calc_output(A, B, FA, FB) %}
      {% set RA, GA, BA, aA = A -%}
      {% set RB, GB, BB, aB = B -%}
      {% set rA, gA, bA = RA * aA, GA * aA, BA * aA -%}
      {% set rB, gB, bB = RB * aB, GB * aB, BB * aB -%}

      {% set FA = FA[0] + FA[1] * aA + FA[2] * aB -%}
      {% set FB = FB[0] + FB[1] * aA + FB[2] * aB -%}

      {% set rO = rA * FA + rB * FB -%}
      {% set gO = gA * FA + gB * FB -%}
      {% set bO = bA * FA + bB * FB -%}
      {% set aO = aA * FA + aB * FB -%}

      {% set rO = (255, rO) | min -%}
      {% set gO = (255, gO) | min -%}
      {% set bO = (255, bO) | min -%}
      {% set aO = (1, aO) | min -%}

      {% set RO = rO / aO if aO else 0 -%}
      {% set GO = gO / aO if aO else 0 -%}
      {% set BO = bO / aO if aO else 0 -%}

      {{- '%f,%f,%f,%f' | format(RO, GO, BO, aO) -}}
    {% endmacro %}

    {% macro rgba_format(color) %}
      {% set r, g, b, a = color -%}
      rgba{{ (r, g, b, a) -}}
    {% endmacro %}

    {% macro js_format(color) %}
      {% set r, g, b, a = color.split(',') | map('float') %}
      {{- '%d,%d,%d,%d' |
          format(r | round, g | round, b | round, (a * 255) | round) -}}
    {% endmacro %}

    {% macro cairo_format(color) %}
      {% set r, g, b, a = color.split(',') | map('float') %}
      {{- '%f,%f,%f,%f' | format(r / 255.0, g / 255.0, b / 255.0, a) -}}
    {% endmacro %}
  code: |
    {% import 'macros' as m -%}
    ctx.fillStyle = '{{ m.rgba_format(dest_color) }}';
    ctx.fillRect(0, 0, 100, 50);
    ctx.globalCompositeOperation = '{{ variant_names[1] }}';
    {{ draw_code }}
    {{ assertion }}
  assertion: |-
    {% import 'macros' as m -%}
    @assert pixel 50,25 ==~ {{ m.js_format(expected_color) }} +/- 5;
  expected: |
    {% import 'macros' as m %}
    size 100 50
    cr.set_source_rgba({{ m.cairo_format(expected_color) }})
    cr.rectangle(0, 0, 100, 50)
    cr.fill()
  new_auxiliary_canvas: |-
    {%- if canvas_type == 'HtmlCanvas' -%}
    document.createElement('canvas');
    canvas2.width = canvas.width;
    canvas2.height = canvas.height;
    {%- else -%}
    new OffscreenCanvas(canvas.width, canvas.height);
    {%- endif -%}
  variants:
  - solid:
      src_color: [255, 255, 0, 1.0]
      dest_color: [0, 255, 255, 1.0]
      draw_code: |-
        {% import 'macros' as m %}
        ctx.fillStyle = '{{ m.rgba_format(src_color) }}';
        ctx.fillRect(0, 0, 100, 50);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output(src_color, dest_color, fa, fb) }}
    transparent:
      src_color: [0, 0, 255, 0.75]
      dest_color: [0, 255, 0, 0.5]
      draw_code: |-
        {% import 'macros' as m %}
        ctx.fillStyle = '{{ m.rgba_format(src_color) }}';
        ctx.fillRect(0, 0, 100, 50);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output(src_color, dest_color, fa, fb) }}
    image:
      src_color: [255, 255, 0, 0.75]
      dest_color: [0, 255, 255, 0.5]
      test_type: 'promise'
      draw_code: |-
        const response = await fetch('/images/yellow75.png')
        const blob = await response.blob();
        const bitmap = await createImageBitmap(blob);
        ctx.drawImage(bitmap, 0, 0);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output(src_color, dest_color, fa, fb) }}
    canvas:
      src_color: [255, 255, 0, 0.75]
      dest_color: [0, 255, 255, 0.5]
      test_type: 'promise'
      draw_code: |-
        const canvas2 = {{ new_auxiliary_canvas }}
        const ctx2 = canvas2.getContext('2d');
        const response = await fetch('/images/yellow75.png')
        const blob = await response.blob();
        const bitmap = await createImageBitmap(blob);
        ctx2.drawImage(bitmap, 0, 0);
        ctx.drawImage(canvas2, 0, 0);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output(src_color, dest_color, fa, fb) }}
    uncovered.fill:
      desc: >-
          fill() draws pixels not covered by the source object as (0,0,0,0), and
          does not leave the pixels unchanged.
      src_color: [0, 0, 255, 0.75]
      dest_color: [0, 255, 0, 0.5]
      draw_code: |-
        {% import 'macros' as m %}
        ctx.fillStyle = '{{ m.rgba_format(src_color) }}';
        ctx.translate(0, 25);
        ctx.fillRect(0, 50, 100, 50);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }}
      enabled: |-
        {{ variant_names[1] in ['source-in',
                                'destination-in',
                                'source-out',
                                'destination-atop',
                                'copy'] }}
      timeout: |-
        {%- if variant_names[1] == 'destination-in' and
               canvas_type != 'HtmlCanvas' -%}
          long
        {%- endif -%}
    uncovered.image:
      desc: >-
        drawImage() draws pixels not covered by the source object as (0,0,0,0),
        and does not leave the pixels unchanged.
      src_color: [255, 255, 0, 1.0]
      dest_color: [0, 255, 255, 0.5]
      test_type: 'promise'
      draw_code: |-
        const response = await fetch('/images/yellow.png')
        const blob = await response.blob();
        const bitmap = await createImageBitmap(blob);
        ctx.drawImage(bitmap, 40, 40, 10, 10, 40, 50, 10, 10);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }}
      enabled: |-
        {{ variant_names[1] in ['source-in',
                                'destination-in',
                                'source-out',
                                'destination-atop',
                                'copy'] }}
    uncovered.nocontext:
      desc: >-
        drawImage() of a canvas with no context draws pixels as (0,0,0,0), and
        does not leave the pixels unchanged.
      src_color: [255, 255, 0, 1.0]
      dest_color: [0, 255, 255, 0.5]
      draw_code: |-
        const canvas2 = {{ new_auxiliary_canvas }}
        ctx.drawImage(canvas2, 0, 0);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }}
      enabled: |-
        {{ variant_names[1] in ['source-in',
                                'destination-in',
                                'source-out',
                                'destination-atop',
                                'copy'] }}
    uncovered.pattern:
      desc: >-
        Pattern fill() draws pixels not covered by the source object as
        (0,0,0,0), and does not leave the pixels unchanged.
      src_color: [255, 255, 0, 1.0]
      dest_color: [0, 255, 255, 0.5]
      test_type: 'promise'
      draw_code: |-
        const response = await fetch('/images/yellow.png')
        const blob = await response.blob();
        const bitmap = await createImageBitmap(blob);
        ctx.fillStyle = ctx.createPattern(bitmap, 'no-repeat');
        ctx.fillRect(0, 50, 100, 50);
      expected_color: |
        {% import 'macros' as m %}
        {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }}
      enabled: |-
        {{ variant_names[1] in ['source-in',
                                'destination-in',
                                'source-out',
                                'destination-atop',
                                'copy'] }}
    clip:
      desc: fill() does not affect pixels outside the clip region.
      src_color: [255, 0, 0, 1]
      dest_color: [0, 255, 0, 1]
      draw_code: |-
        {% import 'macros' as m %}
        ctx.rect(-20, -20, 10, 10);
        ctx.clip();
        ctx.fillStyle = '{{ m.rgba_format(src_color) }}';
        ctx.fillRect(0, 0, 50, 50);
      assertion: |-
        @assert pixel 50,25 == 0,255,0,255;
      expected: green
  - source-over:
      fa: [1, 0, 0]   # 1
      fb: [1, -1, 0]  # 1-aA
    destination-over:
      fa: [1, 0, -1]  # 1-aB
      fb: [1, 0, 0]   # 1
    source-in:
      fa: [0, 0, 1]   # aB
      fb: [0, 0, 0]   # 0
    destination-in:
      fa: [0, 0, 0]   # 0
      fb: [0, 1, 0]   # aA
    source-out:
      fa: [1, 0, -1]  # 1-aB
      fb: [0, 0, 0]   # 0
    destination-out:
      fa: [0, 0, 0]   # 0
      fb: [1, -1, 0]  # 1-aA
    source-atop:
      fa: [0, 0, 1]   # aB
      fb: [1, -1, 0]  # 1-aA
    destination-atop:
      fa: [1, 0, -1]  # 1-aB
      fb: [0, 1, 0]   # aA
    xor:
      fa: [1, 0, -1]  # 1-aB
      fb: [1, -1, 0]  # 1-aA
    copy:
      fa: [1, 0, 0]  # 1
      fb: [0, 0, 0]  # 0
    lighter:
      fa: [1, 0, 0]  # 1
      fb: [1, 0, 0]  # 1
    clear:
      fa: [0, 0, 0]  # 0
      fb: [0, 0, 0]  # 0
