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