require "#{File.dirname(__FILE__)}/../../../spec_helper"

describe Prawn::SVG::Elements::Text do
  let(:document) do
    Prawn::SVG::Document.new(svg, [800, 600], {},
      font_registry: Prawn::SVG::FontRegistry.new('Helvetica' => { normal: nil }, 'Courier' => { normal: nil }, 'Times-Roman' => { normal: nil }))
  end
  let(:element) { Prawn::SVG::Elements::Text.new(document, document.root, [], fake_state) }

  let(:default_style) do
    { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }
  end

  describe 'xml:space preserve' do
    let(:svg) { %(<text#{attributes}>some\n\t  text</text>) }

    context 'when xml:space is preserve' do
      let(:attributes) { ' xml:space="preserve"' }

      it 'converts newlines and tabs to spaces, and preserves spaces' do
        element.process

        expect(flatten_calls(element.calls)).to include ['draw_text', ['some    text'],
                                                         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }]
      end
    end

    context 'when xml:space is unspecified' do
      let(:attributes) { '' }

      it 'strips space' do
        element.process

        expect(flatten_calls(element.calls)).to include ['draw_text', ['some text'],
                                                         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }]
      end
    end
  end

  describe 'conventional whitespace handling' do
    let(:svg) do
      <<~SVG
        <text>
          <tspan>
          </tspan>
          Some text here
          <tspan>More text</tspan>
        Even more
        <tspan></tspan>
        <tspan>
          leading goodness
          </tspan>
          ok
              <tspan>
              </tspan>
        </text>
      SVG
    end

    it 'correctly apportions white space between the tags' do
      element.process
      calls = element.calls.flatten
      expect(calls).to include 'Some text here '
      expect(calls).to include 'More text'
      expect(calls).to include 'Even more'
      expect(calls).to include ' leading goodness '
      expect(calls).to include 'ok'
    end
  end

  describe 'when text-anchor is specified' do
    let(:svg) { '<g text-anchor="middle" font-size="12"><text x="50" y="14">Text</text></g>' }
    let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], fake_state) }

    it 'should inherit text-anchor from parent element' do
      element.process
      expect(element.calls.flatten).to include(size: 12.0, style: :normal, text_anchor: 'middle',
        at: [50.0, 586.0], offset: [0, 0])
    end
  end

  describe 'letter-spacing' do
    let(:svg) { '<text letter-spacing="5">spaced</text>' }

    it 'calls character_spacing with the requested size' do
      element.process

      expect(element.base_calls).to eq [
        ['text_group', [], {}, [
          ['font', ['Helvetica'], { style: :normal }, []],
          ['character_spacing', [5.0], {}, [
            ['draw_text', ['spaced'], default_style, []]
          ]]
        ]]
      ]
    end
  end

  describe 'underline' do
    let(:svg) { '<text text-decoration="underline">underlined</text>' }

    it 'marks the element to be underlined' do
      element.process

      expect(element.base_calls).to eq [
        ['text_group', [], {}, [
          ['font', ['Helvetica'], { style: :normal }, []],
          ['draw_text', ['underlined'], default_style.merge(decoration: 'underline'), []]
        ]]
      ]
    end
  end

  describe 'fill/stroke modes' do
    context 'with a stroke and no fill' do
      let(:svg) { '<text stroke="red" fill="none">stroked</text>' }

      it 'calls text_rendering_mode with the requested options' do
        element.process

        expect(element.base_calls).to eq [
          ['text_group', [], {}, [
            ['stroke_color', ['ff0000'], {}, []],
            ['font', ['Helvetica'], { style: :normal }, []],
            ['text_rendering_mode', [:stroke], {}, [
              ['draw_text', ['stroked'], default_style, []]
            ]]
          ]]
        ]
      end
    end

    context 'with a mixture of everything' do
      let(:svg) do
        '<text stroke="red" fill="none">stroked <tspan fill="black">both</tspan><tspan stroke="none">neither</tspan></text>'
      end

      it 'calls text_rendering_mode with the requested options' do
        element.process

        expect(element.base_calls).to eq [
          ['text_group', [], {}, [
            ['stroke_color', ['ff0000'], {}, []],
            ['font', ['Helvetica'], { style: :normal }, []],
            ['text_rendering_mode', [:stroke], {}, [
              ['draw_text', ['stroked '], default_style, []],
              ['save', [], {}, []],
              ['fill_color', ['000000'], {}, []],
              ['font', ['Helvetica'], { style: :normal }, []],
              ['text_rendering_mode', [:fill_stroke], {}, [
                ['draw_text', ['both'], default_style, []]
              ]],
              ['restore', [], {}, []],
              ['save', [], {}, []],
              ['font', ['Helvetica'], { style: :normal }, []],
              ['text_rendering_mode', [:invisible], {}, [
                ['draw_text', ['neither'], default_style, []]
              ]],
              ['restore', [], {}, []]
            ]]
          ]]
        ]
      end
    end
  end

  describe 'font finding' do
    context 'with a font that exists' do
      let(:svg) { '<text font-family="monospace">hello</text>' }

      it 'finds the font and uses it' do
        element.process
        expect(flatten_calls(element.base_calls)).to include ['font', ['Courier'], { style: :normal }]
      end
    end

    context "with a font that doesn't exist" do
      let(:svg) { '<text font-family="does not exist">hello</text>' }

      it 'uses the fallback font' do
        element.process
        expect(flatten_calls(element.base_calls)).to include ['font', ['Times-Roman'], { style: :normal }]
      end

      context 'when there is no fallback font' do
        before { document.font_registry.installed_fonts.delete('Times-Roman') }

        it "doesn't call the font method and logs a warning" do
          element.process
          expect(element.base_calls.flatten).to_not include 'font'
          expect(document.warnings.first).to include 'is not a known font'
        end
      end
    end
  end

  describe '<tref>' do
    let(:svg) { '<svg xmlns:xlink="http://www.w3.org/1999/xlink"><defs><text id="ref" fill="green">my reference text</text></defs><text x="10"><tref xlink:href="#ref" fill="red" /></text></svg>' }
    let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, [], fake_state) }

    it 'references the text' do
      element.process
      expect(flatten_calls(element.base_calls)[9..11]).to eq [
        ['fill_color', ['ff0000'], {}],
        ['font', ['Helvetica'], { style: :normal }],
        ['draw_text', ['my reference text'],
         { size: 16, style: :normal, text_anchor: 'start', at: [10.0, :relative], offset: [0, 0] }]
      ]
    end
  end

  describe 'dx and dy attributes' do
    let(:svg) { '<text x="10 20" dx="30 50 80" dy="2">Hi there, this is a good test</text>' }

    it 'correctly calculates the positions of the text' do
      element.process

      expect(flatten_calls(element.base_calls)).to eq [
        ['text_group', [], {}],
        ['font', ['Helvetica'], { style: :normal }],
        ['draw_text', ['H'],
         { size: 16, style: :normal, text_anchor: 'start', at: [10.0, :relative], offset: [30.0, 2.0] }],
        ['draw_text', ['i'],
         { size: 16, style: :normal, text_anchor: 'start', at: [20.0, :relative], offset: [50.0, 0] }],
        ['draw_text', [' there, this is a good test'],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [80.0, 0] }]
      ]
    end
  end

  describe 'rotate attribute' do
    let(:svg) { '<text rotate="10 20 30 40 50 60 70 80 90 100">Hi <tspan rotate="0">this</tspan> ok!</text>' }

    it 'correctly calculates the positions of the text' do
      element.process

      expect(flatten_calls(element.base_calls)).to eq [
        ['text_group', [], {}],
        ['font', ['Helvetica'], { style: :normal }],
        ['draw_text', ['H'],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -10.0 }],
        ['draw_text', ['i'],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -20.0 }],
        ['draw_text', [' '],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -30.0 }],
        ['save', [], {}],
        ['font', ['Helvetica'], { style: :normal }],
        ['draw_text', ['this'],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }],
        ['restore', [], {}],
        ['draw_text', [' '],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -80.0 }],
        ['draw_text', ['o'],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -90.0 }],
        ['draw_text', ['k'],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -100.0 }],
        ['draw_text', ['!'],
         { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -100.0 }]
      ]
    end
  end

  describe "when there's a comment inside the text element" do
    let(:svg) { '<text>Hi <!-- comment --> there</text>' }

    it 'ignores the comment' do
      element.process

      expect(flatten_calls(element.calls)).to eq [
        ['text_group', [], {}],
        ['font', ['Helvetica'], { style: :normal }],
        ['draw_text',
         ['Hi '],
         { size:        16,
           style:       :normal,
           text_anchor: 'start',
           at:          [:relative, :relative],
           offset:      [0, 0] }],
        ['draw_text',
         ['there'],
         { size:        16,
           style:       :normal,
           text_anchor: 'start',
           at:          [:relative, :relative],
           offset:      [0, 0] }]
      ]
    end
  end
end