# frozen_string_literal: true

require_relative 'spec_helper'

describe 'Asciidoctor::PDF::Converter - Page' do
  context 'Size' do
    it 'should set page size specified by theme by default' do
      pdf = to_pdf <<~'END', analyze: :page
      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size]).to eql PDF::Core::PageGeometry::SIZES['A4']
    end

    it 'should set page size specified by page_size key in theme with predefined name' do
      ['LEGAL', 'legal', :LEGAL, :legal].each do |page_size|
        pdf = to_pdf <<~'END', pdf_theme: { page_size: page_size }, analyze: :page
        content
        END
        (expect pdf.pages).to have_size 1
        (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['LEGAL']
      end
    end

    it 'should set page size specified by pdf-page-size attribute using predefined name' do
      pdf = to_pdf <<~'END', analyze: :page
      :pdf-page-size: Letter

      content
      END
      (expect pdf.pages).to have_size 1
      # NOTE: pdf-core 0.8 coerces whole number floats to integers
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['LETTER']
    end

    it 'should ignore pdf-page-size attribute if value is unrecognized name' do
      pdf = to_pdf <<~'END', analyze: :page
      :pdf-page-size: Huge

      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size]).to eql PDF::Core::PageGeometry::SIZES['A4']
    end

    it 'should set page size specified by pdf-page-size attribute using dimension array in points' do
      pdf = to_pdf <<~'END', analyze: :page
      :pdf-page-size: [600, 800]

      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql [600.0, 800.0]
    end

    it 'should set page size specified by page_size theme key using dimension array in points' do
      pdf = to_pdf 'content', pdf_theme: { page_size: [600, 800] }, analyze: :page
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql [600.0, 800.0]
    end

    it 'should truncate page size array to two dimensions' do
      pdf = to_pdf 'content', pdf_theme: { page_size: [600, 800, 1000] }, analyze: :page
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql [600.0, 800.0]
    end

    it 'should expand page size array to two dimensions' do
      pdf = to_pdf 'content', pdf_theme: { page_size: [800] }, analyze: :page
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql [800.0, 800.0]
    end

    it 'should use default page size if page size array is empty' do
      pdf = to_pdf 'content', pdf_theme: { page_size: [] }, analyze: :page
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['A4']
    end

    it 'should use default page size if page size is an unrecognized type' do
      pdf = to_pdf 'content', pdf_theme: { page_size: true }, analyze: :page
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['A4']
    end

    it 'should use default page size if any dimension of page size is an unrecognized type' do
      pdf = to_pdf 'content', pdf_theme: { page_size: ['8.5in', true] }, analyze: :page
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['A4']
    end

    it 'should use default page size if any dimension of page size is an unrecognized measurement' do
      pdf = to_pdf 'content', pdf_theme: { page_size: %w(wide tall) }, analyze: :page
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['A4']
    end

    it 'should use default page size if one of dimensions in page size array is 0' do
      [[800, 0], ['8.5in', '0in']].each do |page_size|
        pdf = to_pdf <<~'END', pdf_theme: { page_size: page_size }, analyze: :page
        content
        END
        (expect pdf.pages).to have_size 1
        (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['A4']
      end
    end

    it 'should set page size specified by pdf-page-size attribute using dimension array in inches' do
      pdf = to_pdf <<~'END', analyze: :page
      :pdf-page-size: [8.5in, 11in]

      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['LETTER']
    end

    it 'should set page size specified by pdf-page-size attribute using dimension string in inches' do
      pdf = to_pdf <<~'END', analyze: :page
      :pdf-page-size: 8.5in x 11in

      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['LETTER']
    end

    it 'should set page size specified by page_size theme key using dimension array in inches' do
      pdf = to_pdf <<~'END', pdf_theme: { page_size: ['8.5in', '11in'] }, analyze: :page
      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0][:size].map(&:to_f)).to eql PDF::Core::PageGeometry::SIZES['LETTER']
    end
  end

  context 'Layout' do
    it 'should use layout specified in theme by default' do
      pdf = to_pdf <<~'END'
      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0].orientation).to eql 'portrait'
    end

    it 'should use layout specified by pdf-page-layout attribute' do
      pdf = to_pdf <<~'END'
      :pdf-page-layout: landscape

      content
      END
      (expect pdf.pages).to have_size 1
      (expect pdf.pages[0].orientation).to eql 'landscape'
    end
  end

  context 'Initial Zoom' do
    it 'should set initial zoom to FitH by default' do
      pdf = to_pdf 'content'
      open_action = pdf.catalog[:OpenAction]
      (expect open_action).not_to be_nil
      (expect open_action).to have_size 3
      (expect pdf.objects[open_action[0]]).to eql (pdf.page 1).page_object
      (expect open_action[1]).to be :FitH
      (expect open_action[2]).to eql (get_page_size pdf, 1)[1]
    end

    it 'should not set initial zoom if value specified in theme is unrecognized or nil' do
      [nil, 'Auto'].each do |value|
        pdf = to_pdf 'content', pdf_theme: { page_initial_zoom: value }
        open_action = pdf.catalog[:OpenAction]
        (expect open_action).to be_nil
      end
    end

    it 'should set initial zoom to Fit as specified by theme' do
      pdf = to_pdf 'content', pdf_theme: { page_initial_zoom: 'Fit' }
      open_action = pdf.catalog[:OpenAction]
      (expect open_action).not_to be_nil
      (expect open_action).to have_size 2
      (expect pdf.objects[open_action[0]]).to eql (pdf.page 1).page_object
      (expect open_action[1]).to be :Fit
    end

    it 'should set initial zoom to FitV as specified by theme' do
      pdf = to_pdf 'content', pdf_theme: { page_initial_zoom: 'FitV' }
      open_action = pdf.catalog[:OpenAction]
      (expect open_action).not_to be_nil
      (expect open_action).to have_size 3
      (expect pdf.objects[open_action[0]]).to eql (pdf.page 1).page_object
      (expect open_action[1]).to be :FitV
      (expect open_action[2]).to eql 0
    end
  end

  context 'Mode' do
    it 'should set page mode to /UseOutlines by default' do
      pdf = to_pdf 'content'
      (expect pdf.catalog[:PageMode]).to be :UseOutlines
    end

    it 'should set page mode to /UseOutlines if value of page_mode key in theme is outline' do
      pdf = to_pdf 'content', pdf_theme: { page_mode: 'outline' }
      (expect pdf.catalog[:PageMode]).to be :UseOutlines
    end

    it 'should set page mode to /UseOutlines if value of pdf-page-mode attribute is outline' do
      pdf = to_pdf 'content', attribute_overrides: { 'pdf-page-mode' => 'outline' }
      (expect pdf.catalog[:PageMode]).to be :UseOutlines
    end

    it 'should set page mode to /UseNone if value of page_mode key in theme is none' do
      pdf = to_pdf 'content', pdf_theme: { page_mode: 'none' }
      (expect pdf.catalog[:PageMode]).to be :UseNone
    end

    it 'should set page mode to /UseNone if value of pdf-page-mode attribute is none' do
      pdf = to_pdf 'content', attribute_overrides: { 'pdf-page-mode' => 'none' }
      (expect pdf.catalog[:PageMode]).to be :UseNone
    end

    it 'should set page mode to /UseThumbs if value of page_mode key in theme is thumbs' do
      pdf = to_pdf 'content', pdf_theme: { page_mode: 'thumbs' }
      (expect pdf.catalog[:PageMode]).to be :UseThumbs
    end

    it 'should set page mode to /UseThumbs if value of pdf-page-mode attribute is thumbs' do
      pdf = to_pdf 'content', attribute_overrides: { 'pdf-page-mode' => 'thumbs' }
      (expect pdf.catalog[:PageMode]).to be :UseThumbs
    end

    it 'should set page mode to /UseOutlines if value of page_mode key in theme is unrecognized' do
      pdf = to_pdf 'content', pdf_theme: { page_mode: 'invalid' }
      (expect pdf.catalog[:PageMode]).to be :UseOutlines
    end

    it 'should set page mode to /UseOutlines if value of pdf-page-mode attribute is unrecognized' do
      pdf = to_pdf 'content', attribute_overrides: { 'pdf-page-mode' => 'invalid' }
      (expect pdf.catalog[:PageMode]).to be :UseOutlines
    end

    it 'should set page mode to fullscreen if page_mode key in theme is fullscreen' do
      pdf = to_pdf 'content', pdf_theme: { page_mode: 'fullscreen' }
      (expect pdf.catalog[:PageMode]).to be :FullScreen
      (expect pdf.catalog[:NonFullScreenPageMode]).to be :UseOutlines
    end

    it 'should set page mode to fullscreen if pdf-page-mode attribute is fullscreen' do
      pdf = to_pdf 'content', attribute_overrides: { 'pdf-page-mode' => 'fullscreen' }
      (expect pdf.catalog[:PageMode]).to be :FullScreen
      (expect pdf.catalog[:NonFullScreenPageMode]).to be :UseOutlines
    end

    it 'should set secondary page mode to none if page_mode key in theme is fullscreen none' do
      pdf = to_pdf 'content', pdf_theme: { page_mode: 'fullscreen none' }
      (expect pdf.catalog[:PageMode]).to be :FullScreen
      (expect pdf.catalog[:NonFullScreenPageMode]).to be :UseNone
    end

    it 'should set secondary page mode to none if pdf-page-mode attribute is fullscreen none' do
      pdf = to_pdf 'content', attribute_overrides: { 'pdf-page-mode' => 'fullscreen none' }
      (expect pdf.catalog[:PageMode]).to be :FullScreen
      (expect pdf.catalog[:NonFullScreenPageMode]).to be :UseNone
    end

    it 'should allow pdf-page-mode attribute in document to disable fullscreen mode' do
      pdf = to_pdf 'content', pdf_theme: { page_mode: 'fullscreen' }, attribute_overrides: { 'pdf-page-mode' => '' }
      (expect pdf.catalog[:PageMode]).not_to be :FullScreen
      (expect pdf.catalog).not_to have_key :NonFullScreenPageMode
    end
  end

  context 'Margin' do
    it 'should use the margin specified in theme by default' do
      input = 'content'
      prawn = to_pdf input, analyze: :document
      pdf = to_pdf input, analyze: true

      (expect prawn.page_margin).to eql [36, 48.24, 48.24, 48.24]
      (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 48.24, 793.926]
    end

    it 'should use default margin if value of margin in theme is empty string' do
      pdf_theme = { page_margin: '' }
      input = 'content'
      prawn = to_pdf input, pdf_theme: pdf_theme, analyze: :document
      pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true
      (expect prawn.page_margin).to eql [36, 36, 36, 36]
      (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 36.0, 793.926]
    end

    it 'should use default margin if value of margin in theme is empty array' do
      pdf_theme = { page_margin: [] }
      input = 'content'
      prawn = to_pdf input, pdf_theme: pdf_theme, analyze: :document
      pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true
      (expect prawn.page_margin).to eql [36, 36, 36, 36]
      (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 36.0, 793.926]
    end

    it 'should coerce margin string values to numbers' do
      pdf_theme = { page_margin: ['0.5in', '0.67in', '0.67in', '0.75in'] }
      input = 'content'
      prawn = to_pdf input, pdf_theme: pdf_theme, analyze: :document
      pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true

      (expect prawn.page_margin).to eql [36.0, 48.24, 48.24, 54.0]
      (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 54.0, 793.926]
    end

    it 'should truncate margin array in theme to 4 values' do
      pdf_theme = { page_margin: [36, 24, 28.8, 24, 36, 36] }
      input = 'content'
      prawn = to_pdf input, pdf_theme: pdf_theme, analyze: :document
      pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true

      (expect prawn.page_margin).to eql [36, 24, 28.8, 24]
      content_text = pdf.text[0]
      (expect content_text.values_at :string, :page_number, :x, :y).to eql ['content', 1, 24.0, 793.926]
      content_top = (get_page_size pdf, 1)[1] - 36
      (expect content_text[:y] + content_text[:font_size]).to be_within(2).of content_top
    end

    it 'should use the margin specified by the pdf-page-margin attribute as array' do
      ['0.5in, 1in, 0.5in, 1in', '36pt, 72pt, 36pt, 72pt'].each do |val|
        pdf = to_pdf <<~END, analyze: true
        :pdf-page-margin: [#{val}]

        content
        END
        (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 72.0, 793.926]
      end
    end

    it 'should use the margin specified by the pdf-page-margin attribute as string' do
      %w(1in 72pt 25.4mm 2.54cm 96px).each do |val|
        pdf = to_pdf <<~END, analyze: true
        :pdf-page-margin: #{val}

        content
        END
        (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 72.0, 757.926]
      end
    end

    it 'should split margin specified by the pdf-page-margin attribute as a string and use first 4 values' do
      input = %(:pdf-page-margin: [32.5, 28, 32.5, 28, 36, 36]\n\ncontent)
      prawn = to_pdf input, analyze: :document
      pdf = to_pdf input, analyze: true
      (expect prawn.page_margin).to eql [32.5, 28.0, 32.5, 28.0]
      (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 28.0, 797.426]
    end

    it 'should use default margin if value of pdf-page-margin is empty array' do
      input = %(:pdf-page-margin: []\n\ncontent)
      prawn = to_pdf input, analyze: :document
      pdf = to_pdf input, analyze: true
      (expect prawn.page_margin).to eql [36, 36, 36, 36]
      (expect pdf.text[0].values_at :string, :page_number, :x, :y).to eql ['content', 1, 36.0, 793.926]
    end

    it 'should allow margins of rotated page to be configured independently using theme' do
      pdf_theme = {
        page_margin: [18, 36, 36, 36],
        page_margin_rotated: [18, 18, 36, 18],
      }
      pdf = to_pdf <<~'END', pdf_theme: pdf_theme, analyze: true
      first page

      [page-layout=landscape]
      <<<

      rotated page

      [page-layout=portrait]
      <<<

      last page
      END

      (expect pdf.pages).to have_size 3
      first_page_text = pdf.find_unique_text 'first page'
      (expect first_page_text[:x]).to eql 36.0
      rotated_page_text = pdf.find_unique_text 'rotated page'
      (expect rotated_page_text[:x]).to eql 18.0
      last_page_text = pdf.find_unique_text 'last page'
      (expect last_page_text[:x]).to eql 36.0
    end

    it 'should allow margins of rotated page to be configured independently using AsciiDoc attribute' do
      pdf = to_pdf <<~'END', enable_footer: true, analyze: true
      :pdf-page-layout: landscape
      :pdf-page-margin: [18, 18, 36, 18]
      // NOTE: verify margin is expanded
      :pdf-page-margin-rotated: [36, 36]

      first page

      [page-layout=portrait]
      <<<

      rotated page

      [page-layout=landscape]
      <<<

      last page
      END

      (expect pdf.pages).to have_size 3
      first_page_text = pdf.find_unique_text 'first page'
      (expect first_page_text[:x]).to eql 18.0
      rotated_page_text = pdf.find_unique_text 'rotated page'
      (expect rotated_page_text[:x]).to eql 36.0
      last_page_text = pdf.find_unique_text 'last page'
      (expect last_page_text[:x]).to eql 18.0
    end

    it 'should use same margin for all pages if rotated page margin is not specified' do
      pdf = to_pdf <<~'END', analyze: true
      :pdf-page-margin: [18, 36, 36, 36]

      first page

      [page-layout=landscape]
      <<<

      rotated page

      [page-layout=portrait]
      <<<

      last page
      END

      (expect pdf.pages).to have_size 3
      first_page_text = pdf.find_unique_text 'first page'
      (expect first_page_text[:x]).to eql 36.0
      rotated_page_text = pdf.find_unique_text 'rotated page'
      (expect rotated_page_text[:x]).to eql 36.0
      last_page_text = pdf.find_unique_text 'last page'
      (expect last_page_text[:x]).to eql 36.0
    end

    it 'should use recto/verso margins when media=prepress', visual: true do
      to_file = to_pdf_file <<~'END', 'page-prepress-margins.pdf', enable_footer: true
      = Book Title
      :media: prepress
      :pdf-theme: default
      :doctype: book
      // NOTE: setting front-cover-image to ~ informs converter cover page will be inserted by separate process
      :front-cover-image: ~

      == First Chapter

      <<<

      === A Section

      == Last Chapter

      <<<

      === B Section
      END

      (expect to_file).to visually_match 'page-prepress-margins.pdf'
    end

    it 'should allow recto/verso margins to be customized by theme when media=prepress', visual: true do
      pdf_theme = {
        page_margin_inner: 72,
        page_margin_outer: 54,
      }
      to_file = to_pdf_file <<~'END', 'page-prepress-custom-margins.pdf', pdf_theme: pdf_theme, enable_footer: true
      = Book Title
      :media: prepress
      :doctype: book
      :front-cover-image: ~

      == First Chapter

      <<<

      === A Section

      == Last Chapter

      <<<

      === B Section
      END

      (expect to_file).to visually_match 'page-prepress-custom-margins.pdf'
    end

    it 'should disable recto/verso margins when media=prepress if inner/outer margins in theme are nil', visual: true do
      pdf_theme = {
        page_margin_inner: nil,
        page_margin_outer: nil,
      }
      to_file = to_pdf_file <<~'END', 'page-prepress-normal-margins.pdf', pdf_theme: pdf_theme, enable_footer: true
      = Book Title
      :media: prepress
      :doctype: book
      :front-cover-image: ~

      == First Chapter

      <<<

      === A Section

      == Last Chapter

      <<<

      === B Section
      END

      (expect to_file).to visually_match 'page-prepress-normal-margins.pdf'
    end

    it 'should not apply recto margins to title page of prepress document if no cover is specifed', visual: true do
      pdf_theme = {
        page_margin_inner: 72,
        page_margin_outer: 54,
      }
      to_file = to_pdf_file <<~'END', 'page-prepress-margins-no-cover.pdf', pdf_theme: pdf_theme, enable_footer: true
      = Book Title
      :media: prepress
      :doctype: book

      == First Chapter

      <<<

      === A Section

      == Last Chapter

      <<<

      === B Section
      END

      (expect to_file).to visually_match 'page-prepress-margins-no-cover.pdf'
    end

    it 'should apply recto margins to first page of prepress document if not title page or cover', visual: true do
      pdf_theme = {
        page_margin_inner: 72,
        page_margin_outer: 54,
      }
      to_file = to_pdf_file <<~'END', 'page-prepress-margins-body-only.pdf', pdf_theme: pdf_theme, enable_footer: true
      :media: prepress
      :doctype: book

      == First Chapter

      <<<

      === A Section

      == Last Chapter

      <<<

      === B Section
      END

      (expect to_file).to visually_match 'page-prepress-margins-body-only.pdf'
    end

    it 'should invert recto/verso margins when pdf-folio-placement is inverted' do
      pdf_theme = {
        page_margin_inner: 72,
        page_margin_outer: 54,
        footer_recto_right_content: nil,
        footer_recto_left_content: 'page {page-number}',
        footer_verso_right_content: nil,
        footer_verso_left_content: 'p{page-number}',
        footer_padding: [6, 0, 0, 0],
      }
      pdf = to_pdf <<~'END', pdf_theme: pdf_theme, enable_footer: true, analyze: true
      = Book Title
      :media: prepress
      :doctype: book
      :pdf-folio-placement: physical-inverted

      == First Chapter

      content

      <<<

      more content

      == Last Chapter

      content
      END

      first_chapter_text = pdf.find_unique_text 'First Chapter'
      (expect first_chapter_text[:page_number]).to eql 3
      (expect first_chapter_text[:x]).to eql 54.0
      pgnum_1_text = pdf.find_unique_text 'p1'
      (expect pgnum_1_text[:x]).to eql 54.0
      (expect pgnum_1_text[:page_number]).to eql 3
      more_content_text = pdf.find_unique_text 'more content'
      (expect more_content_text[:x]).to eql 72.0
      (expect more_content_text[:page_number]).to eql 4
      pgnum_2_text = pdf.find_unique_text 'page 2'
      (expect pgnum_2_text[:x]).to eql 72.0
      (expect pgnum_2_text[:page_number]).to eql 4
      last_chapter_text = pdf.find_unique_text 'Last Chapter'
      (expect last_chapter_text[:x]).to eql 54.0
      (expect last_chapter_text[:page_number]).to eql 5
      pgnum_3_text = pdf.find_unique_text 'p3'
      (expect pgnum_3_text[:x]).to eql 54.0
      (expect pgnum_3_text[:page_number]).to eql 5
    end
  end

  context 'Columns' do
    it 'should ignore columns for book doctype' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2 }, analyze: true
      = Document Title
      :doctype: book
      :notitle:

      [.text-right]
      first page

      [.column]
      <<<

      second page
      END

      midpoint = (get_page_size pdf)[0] * 0.5
      (expect pdf.pages).to have_size 2
      (expect (pdf.find_unique_text 'first page')[:page_number]).to eql 1
      (expect (pdf.find_unique_text 'first page')[:x]).to be > midpoint
      (expect (pdf.find_unique_text 'second page')[:page_number]).to eql 2
    end

    it 'should ignore columns if less than 2' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 1 }, analyze: true
      = Document Title
      :notitle:

      first page

      [.column]
      <<<

      second page
      END

      (expect pdf.pages).to have_size 2
      (expect (pdf.find_unique_text 'first page')[:page_number]).to eql 1
      (expect (pdf.find_unique_text 'second page')[:page_number]).to eql 2
    end

    it 'should arrange article body into columns' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2 }, analyze: true
      first column

      [.column]
      <<<

      second column

      [.column]
      <<<

      [.text-right]
      first column again
      END

      midpoint = (get_page_size pdf)[0] * 0.5
      (expect pdf.pages).to have_size 2
      (expect (pdf.find_unique_text 'first column')[:page_number]).to eql 1
      (expect (pdf.find_unique_text 'second column')[:page_number]).to eql 1
      (expect (pdf.find_unique_text 'second column')[:x]).to be > midpoint
      (expect (pdf.find_unique_text 'first column again')[:page_number]).to eql 2
      (expect (pdf.find_unique_text 'first column again')[:x]).to be < midpoint
    end

    it 'should put footnotes at bottom of last column with content' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2 }, analyze: true
      first columnfootnote:[This page has two columns.]

      [.column]
      <<<

      second column
      END

      midpoint = (get_page_size pdf)[0] * 0.5
      (expect pdf.pages).to have_size 1
      (expect (pdf.find_unique_text 'second column')[:x]).to be > midpoint
      right_column_text = pdf.text.select {|it| it[:x] > midpoint }
      right_column_lines = pdf.lines right_column_text
      (expect right_column_lines).to have_size 2
      (expect right_column_lines[-1]).to eql '1. This page has two columns.'
    end

    it 'should place document title outside of column box' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2 }, analyze: true
      = Article Title Goes Here

      first column

      [.column]
      <<<

      second column
      END

      midpoint = (get_page_size pdf)[0] * 0.5
      (expect pdf.pages).to have_size 1
      title_text = pdf.find_unique_text 'Article Title Goes Here'
      (expect title_text[:x]).to be < midpoint
      (expect title_text[:x] + title_text[:width]).to be > midpoint
      (expect (pdf.find_unique_text 'second column')[:x]).to be > midpoint
    end

    it 'should place TOC outside of column box' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2 }, analyze: true
      = Article Title Goes Here
      :toc:

      == First Column

      [.column]
      <<<

      == Second Column
      END

      midpoint = (get_page_size pdf)[0] * 0.5
      (expect pdf.pages).to have_size 1
      first_column_text = (pdf.find_text 'First Column').sort_by {|it| -it[:y] }
      second_column_text = (pdf.find_text 'Second Column').sort_by {|it| -it[:y] }
      (expect first_column_text[0][:x]).to eql 48.24
      (expect first_column_text[1][:x]).to eql 48.24
      (expect second_column_text[0][:x]).to eql 48.24
      (expect second_column_text[1][:x]).to be > midpoint
      dots_text = pdf.text.select {|it| it[:string].include? '.' }
      dots_text.each do |it|
        (expect it[:x]).to be < midpoint
        (expect it[:x] + it[:width]).to be > midpoint
      end
    end

    it 'should allow theme to control number of columns' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 4 }, analyze: true
      one

      [.column]
      <<<

      two

      [.column]
      <<<

      three

      [.column]
      <<<<

      four
      END

      midpoint = (get_page_size pdf)[0] * 0.5
      (expect pdf.pages).to have_size 1
      one_text = pdf.find_unique_text 'one'
      two_text = pdf.find_unique_text 'two'
      three_text = pdf.find_unique_text 'three'
      four_text = pdf.find_unique_text 'four'
      (expect two_text[:x]).to be > one_text[:x]
      (expect two_text[:x]).to be < midpoint
      (expect four_text[:x]).to be > three_text[:x]
      (expect three_text[:x]).to be > midpoint
    end

    it 'should allow theme to control column gap' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2, page_column_gap: 12 }, analyze: :image
      image::square.png[pdfwidth=100%]

      [.column]
      <<<

      image::square.png[pdfwidth=100%]
      END

      images = pdf.images
      (expect images).to have_size 2
      column_gap = (images[1][:x] - (images[0][:x] + images[0][:width])).to_f
      (expect column_gap).to eql 12.0
    end

    it 'should restore columns following imported page' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2, page_column_gap: 12 }, analyze: true
      = Document Title

      left column

      image::blue-letter.pdf[]

      left column

      [.column]
      <<<

      right column
      END

      midpoint = pdf.pages[0][:size][0] * 0.5
      (expect pdf.pages).to have_size 3
      left_column_text = pdf.find_text 'left column'
      right_column_text = pdf.find_unique_text 'right column'
      (expect left_column_text).to have_size 2
      initial_left_column_y = left_column_text[0][:y]
      (expect left_column_text[0][:page_number]).to eql 1
      (expect left_column_text[0][:x]).to eql 48.24
      (expect left_column_text[1][:page_number]).to eql 3
      (expect left_column_text[1][:x]).to eql 48.24
      (expect left_column_text[1][:y]).to be > initial_left_column_y
      (expect right_column_text[:page_number]).to eql 3
      (expect right_column_text[:x]).to be > midpoint
      (expect left_column_text[1][:y]).to eql right_column_text[:y]
    end

    it 'should reset column index following imported page' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2, page_column_gap: 12 }, analyze: true
      = Document Title

      left column

      [.column]
      <<<

      right column

      image::blue-letter.pdf[]

      left column

      [.column]
      <<<

      right column
      END

      midpoint = pdf.pages[0][:size][0] * 0.5
      (expect pdf.pages).to have_size 3
      left_column_text = pdf.find_text 'left column'
      right_column_text = pdf.find_text 'right column'
      (expect left_column_text).to have_size 2
      (expect right_column_text).to have_size 2
      (expect left_column_text[0][:page_number]).to eql 1
      (expect left_column_text[0][:x]).to eql 48.24
      (expect right_column_text[0][:page_number]).to eql 1
      (expect right_column_text[0][:x]).to be > midpoint
      (expect left_column_text[1][:page_number]).to eql 3
      (expect left_column_text[1][:x]).to eql 48.24
      (expect right_column_text[1][:page_number]).to eql 3
      (expect right_column_text[1][:x]).to be > midpoint
    end

    it 'should restore column layout following missing imported page' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2, page_column_gap: 12 }, analyze: true
      = Document Title

      left column

      [.column]
      <<<

      right column

      image::blue-letter.pdf[page=10]

      left column

      [.column]
      <<<

      right column
      END

      midpoint = pdf.pages[0][:size][0] * 0.5
      (expect pdf.pages).to have_size 2
      left_column_text = pdf.find_text 'left column'
      right_column_text = pdf.find_text 'right column'
      (expect left_column_text).to have_size 2
      (expect right_column_text).to have_size 2
      (expect left_column_text[0][:page_number]).to eql 1
      (expect left_column_text[0][:x]).to eql 48.24
      (expect right_column_text[0][:page_number]).to eql 1
      (expect right_column_text[0][:x]).to be > midpoint
      (expect left_column_text[1][:page_number]).to eql 2
      (expect left_column_text[1][:x]).to eql 48.24
      (expect right_column_text[1][:page_number]).to eql 2
      (expect right_column_text[1][:x]).to be > midpoint
    end

    # NOTE: assert current behavior
    it 'should stop column layout at page layout change' do
      pdf = to_pdf <<~'END', pdf_theme: { page_columns: 2 }, analyze: true
      left column

      [.column]
      <<<

      right column

      [page-layout=landscape]
      <<<

      landscape

      <<<

      landscape

      [page-layout=portrait]
      <<<

      portrait

      <<<

      portrait
      END

      (expect pdf.pages).to have_size 5
      (expect (pdf.find_unique_text 'right column')[:x]).to be > 48.24
      p2_size = pdf.pages[1][:size]
      (expect p2_size[0]).to be > p2_size[1]
      (expect (pdf.find_text 'landscape').map {|it| it[:x] }.uniq).to eql [48.24]
      p4_size = pdf.pages[3][:size]
      (expect p4_size[0]).to be < p4_size[1]
      (expect (pdf.find_text 'portrait').map {|it| it[:x] }.uniq).to eql [48.24]
    end
  end

  context 'Background' do
    it 'should set page background to white if value is not defined or transparent', visual: true do
      [nil, 'transparent'].each do |bg_color|
        to_file = to_pdf_file <<~'END', %(page-background-color-#{bg_color || 'undefined'}.pdf), pdf_theme: { page_background_color: bg_color }
        = Document Title
        :doctype: book

        content
        END

        (expect to_file).to visually_match 'page-background-color-default.pdf'
      end
    end

    it 'should set page background color specified by page_background_color key in theme', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-color.pdf', pdf_theme: { page_background_color: 'ECFBF4' }
      = Document Title
      :doctype: book

      content
      END

      (expect to_file).to visually_match 'page-background-color.pdf'
    end

    it 'should not set page background if value of page-background-image is none or empty', visual: true do
      [' none', ''].each do |val|
        to_file = to_pdf_file <<~END, %(page-background-image-#{val.empty? ? 'empty' : 'none'}.pdf)
        = Document Title
        :doctype: book
        :page-background-image:#{val}

        content
        END

        (expect to_file).to visually_match 'page-background-color-default.pdf'
      end
    end

    it 'should set the background image using target of macro specified in page-background-image attribute', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-inline-macro.pdf'
      = Document Title
      :doctype: book
      :page-background-image: image:bg.png[]

      content
      END

      (expect to_file).to visually_match 'page-background-image.pdf'
    end

    it 'should use remote image specified by page-background-image attribute as page background', visual: true do
      with_local_webserver do |base_url|
        [%(#{base_url}/bg.png), %(image:#{base_url}/bg.png[])].each_with_index do |image_url, idx|
          to_file = output_file %(page-background-image-remote-#{idx}.pdf)
          doc = to_pdf <<~END, analyze: :document, to_file: to_file, attribute_overrides: { 'allow-uri-read' => '' }
          = Document Title
          :doctype: book
          :page-background-image: #{image_url}

          content
          END

          (expect to_file).to visually_match 'page-background-image.pdf'
          # NOTE: we could assert no log messages instead, but that assumes the remove_tmp_files method is even called
          (expect doc.converter.instance_variable_get :@tmp_files).to be_empty
        end
      end
    end

    it 'should use remote image specified in theme as page background', visual: true do
      with_local_webserver do |base_url|
        [%(#{base_url}/bg.png), %(image:#{base_url}/bg.png[])].each_with_index do |image_url, idx|
          to_file = to_pdf_file <<~END, %(page-background-image-remote-#{idx}.pdf), attribute_overrides: { 'allow-uri-read' => '' }, pdf_theme: { page_background_image: image_url }
          = Document Title
          :doctype: book

          content
          END

          (expect to_file).to visually_match 'page-background-image.pdf'
        end
      end
    end

    it 'should use data URI specified by page-background-image attribute as page background', visual: true do
      image_data = File.binread fixture_file 'square.png'
      encoded_image_data = [image_data].pack 'm0'
      to_file = to_pdf_file <<~END, %(page-background-image-attr-data-uri.pdf)
      = Document Title
      :page-background-image: image:data:image/png;base64,#{encoded_image_data}[fit=fill]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-fill.pdf'
    end

    it 'should use data URI specified in theme as page background', visual: true do
      image_data = File.binread fixture_file 'square.png'
      encoded_image_data = [image_data].pack 'm0'
      pdf_theme = { page_background_image: %(image:data:image/png;base64,#{encoded_image_data}[fit=fill]) }
      to_file = to_pdf_file <<~END, %(page-background-image-attr-data-uri.pdf), pdf_theme: pdf_theme
      = Document Title

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-fill.pdf'
    end

    it 'should resolve background image in theme relative to theme dir', visual: true do
      [true, false].each do |macro|
        pdf_theme = {
          __dir__: fixtures_dir,
          page_background_image: (macro ? 'image:bg.png[]' : 'bg.png'),
        }
        to_file = to_pdf_file <<~'END', %(page-background-image-#{macro ? 'macro' : 'bare'}.pdf), pdf_theme: pdf_theme
        = Document Title
        :doctype: book

        content
        END

        (expect to_file).to visually_match 'page-background-image.pdf'
      end
    end

    it 'should resolve background image in theme relative to themesdir in classloader', if: RUBY_ENGINE == 'jruby' do
      require fixture_file 'pdf-themes.jar'
      pdf = to_pdf <<~'END', attribute_overrides: { 'pdf-theme' => 'uri:classloader:/pdf-themes/page-background-image-theme.yml' }
      = Document Title
      :doctype: book

      content
      END

      images = get_images pdf, 1
      (expect images).to have_size 1
    end

    it 'should resolve background image with absolute path for theme loaded from classloader', if: RUBY_ENGINE == 'jruby' do
      require fixture_file 'pdf-themes.jar'
      pdf = to_pdf <<~'END', attribute_overrides: { 'pdf-theme' => 'uri:classloader:/pdf-themes/page-background-image-from-fixturesdir-theme.yml', 'fixturesdir' => fixtures_dir }
      = Document Title
      :doctype: book

      content
      END

      images = get_images pdf, 1
      (expect images).to have_size 1
    end

    it 'should resolve background image in theme relative to themesdir', visual: true do
      attribute_overrides = {
        'pdf-theme' => 'page-background-image',
        'pdf-themesdir' => fixtures_dir,
      }
      to_file = to_pdf_file <<~'END', 'page-background-image-bare-in-theme-file.pdf', attribute_overrides: attribute_overrides
      = Document Title
      :doctype: book

      content
      END

      (expect to_file).to visually_match 'page-background-image.pdf'
    end

    it 'should allow both background color and image to be set concurrently', visual: true do
      pdf_theme = {
        page_background_color: 'F9F9F9',
        page_background_image: %(image:#{fixture_file 'tux.png'}[fit=none,pdfwidth=50%]),
      }
      to_file = to_pdf_file '{blank}', 'page-background-color-and-image.pdf', pdf_theme: pdf_theme

      (expect to_file).to visually_match 'page-background-color-and-image.pdf'
    end

    it 'should resolve docdir attribute reference in image path in theme', visual: true do
      pdf_theme = {
        page_background_color: 'F9F9F9',
        page_background_image: 'image:{docdir}/tux.png[fit=none,pdfwidth=50%]',
      }
      to_file = to_pdf_file '{blank}', 'page-background-color-and-image-relative-to-docdir.pdf', pdf_theme: pdf_theme, attribute_overrides: { 'docdir' => fixtures_dir }

      (expect to_file).to visually_match 'page-background-color-and-image.pdf'
    end

    it 'should resolve docimagesdir attribute reference in image path in theme', visual: true do
      pdf_theme = {
        page_background_color: 'F9F9F9',
        page_background_image: 'image:{docimagesdir}/tux.png[fit=none,pdfwidth=50%]',
      }
      to_file = to_pdf_file '{blank}', 'page-background-color-and-image-relative-to-docdir.pdf', pdf_theme: pdf_theme, attribute_overrides: { 'docdir' => fixtures_dir, 'imagesdir' => 'images' }

      (expect to_file).to visually_match 'page-background-color-and-image.pdf'
    end

    it 'should only substitute attributes in image path in theme', visual: true do
      pdf_theme = {
        page_background_color: 'F9F9F9',
        page_background_image: 'image:{docdir}/tux--classic.png[fit=none,pdfwidth=50%]',
      }
      to_file = to_pdf_file '{blank}', 'page-background-color-and-image-relative-to-docdir-no-replacements.pdf', pdf_theme: pdf_theme, attribute_overrides: { 'docdir' => fixtures_dir }

      (expect to_file).to visually_match 'page-background-color-and-image.pdf'
    end

    it 'should recognize attribute value that use block macro syntax', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-block-macro.pdf'
      = Document Title
      :doctype: book
      :page-background-image: image::bg.png[]

      content
      END

      (expect to_file).to visually_match 'page-background-image.pdf'
    end

    it 'should not crash if background image is a URI and the allow-uri-read attribute is not set' do
      (expect do
        to_pdf <<~'END'
        = Document Title
        :page-background-image: image:https://example.org/bg.svg[]

        content
        END
      end).to not_raise_exception & (log_message severity: :WARN, message: '~allow-uri-read attribute not enabled')
    end

    it 'should set the background image using path specified in page-background-image attribute', visual: true do
      to_file = to_pdf_file <<~END, 'page-background-image-path.pdf'
      = Document Title
      :doctype: book
      :page-background-image: #{fixture_file 'bg.png', relative: true}

      content
      END

      (expect to_file).to visually_match 'page-background-image.pdf'
    end

    it 'should scale background image until it reaches shortest side', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-max-height.pdf'
      = Document Title
      :pdf-page-layout: landscape
      :page-background-image: image:square.png[]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-max-height.pdf'
    end

    it 'should ignore pdfwidth for background image if fit=none is not specified', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-max-height.pdf'
      = Document Title
      :pdf-page-layout: landscape
      :page-background-image: image:square.png[pdfwidth=25%]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-max-height.pdf'
    end

    it 'should set width of background image according to width attribute when fit=none', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-width.pdf'
      = Document Title
      :page-background-image: image:square.png[bg,200,fit=none]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-width.pdf'
    end

    it 'should scale down background PNG to fit boundaries of page if fit is scale-down and width slightly exceeds available width', visual: true do
      reference_file = to_pdf_file <<~'END', 'page-background-image-fit-scale-down-reference.pdf'
      = Document Title
      :page-background-image: image:wide.png[fit=contain]

      content
      END

      to_file = to_pdf_file <<~'END', 'page-background-image-fit-scale-down-slightly.pdf'
      = Document Title
      :page-background-image: image:wide.png[fit=scale-down]

      content
      END

      (expect to_file).to visually_match reference_file
    end

    it 'should scale up background SVG to fit boundaries of page if value is path', visual: true do
      to_file = to_pdf_file <<~END, 'page-background-image-svg-scale-up-from-path.pdf'
      = Document Title
      :page-background-image: #{fixture_file 'square.svg', relative: true}

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-up.pdf'
    end

    it 'should scale up background SVG to fit boundaries of page if value is macro', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-scale-up-from-macro.pdf'
      = Document Title
      :page-background-image: image:square.svg[]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-up.pdf'
    end

    it 'should scale up background SVG to fit boundaries of page if fit is contain', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-fit-contain.pdf'
      = Document Title
      :page-background-image: image:square.svg[fit=contain]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-up.pdf'
    end

    it 'should scale up background SVG to fit boundaries of page if fit is fill', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-fit-fill.pdf'
      = Document Title
      :page-background-image: image:square.svg[fit=fill]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-up.pdf'
    end

    it 'should scale up background SVG to fit boundaries of page if pdfwidth is 100% and fit=none', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-pdfwidth.pdf'
      = Document Title
      :pdf-page-layout: landscape
      :page-background-image: image:square.svg[fit=none,pdfwidth=100%]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-svg-contain.pdf'
    end

    it 'should scale down background SVG to fit boundaries of page if value is path', visual: true do
      to_file = to_pdf_file <<~END, 'page-background-image-svg-scale-down-from-path.pdf'
      = Document Title
      :page-background-image: #{fixture_file 'example-stamp.svg', relative: true}

      This page has a background image.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-down.pdf'
    end

    it 'should scale down background SVG to fit boundaries of page if value is macro', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-scale-down-from-macro.pdf'
      = Document Title
      :page-background-image: image:example-stamp.svg[]

      This page has a background image.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-down.pdf'
    end

    it 'should scale down background SVG to fit boundaries of page if fit is scale-down', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-fit-scale-down.pdf'
      = Document Title
      :page-background-image: image:example-stamp.svg[fit=scale-down]

      This page has a background image.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-down.pdf'
    end

    it 'should scale down background SVG to fit boundaries of page if fit is scale-down and width slightly exceeds available width', visual: true do
      reference_file = to_pdf_file <<~'END', 'page-background-image-svg-fit-scale-down-reference.pdf'
      = Document Title
      :page-background-image: image:wide.svg[fit=contain]

      content
      END

      to_file = to_pdf_file <<~'END', 'page-background-image-svg-fit-scale-down-slightly.pdf'
      = Document Title
      :page-background-image: image:wide.svg[fit=scale-down]

      content
      END

      (expect to_file).to visually_match reference_file
    end

    it 'should scale down background SVG to fit boundaries of page if computed height is greater than page height', visual: true do
      with_content_spacer 200, 600 do |spacer_path|
        to_file = to_pdf_file <<~END, 'page-background-image-svg-fit-scale-down-computed-height.pdf'
        :pdf-page-size: A6
        :page-background-image: image:#{spacer_path}[pdfwidth=200,fit=scale-down]
        END

        (expect to_file).to visually_match 'page-background-image-svg-fit-scale-down-height.pdf'
      end
    end

    it 'should scale down background SVG to fit boundaries of page if intrinsic height is greater than page height', visual: true do
      with_content_spacer 200, 600 do |spacer_path|
        to_file = to_pdf_file <<~END, 'page-background-image-svg-fit-scale-down-intrinsic-height.pdf'
        :pdf-page-size: A6
        :page-background-image: image:#{spacer_path}[fit=scale-down]
        END

        (expect to_file).to visually_match 'page-background-image-svg-fit-scale-down-height.pdf'
      end
    end

    it 'should not scale background SVG with explicit width to fit boundaries of page if fit is scale-down and image fits', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-prescaled.pdf'
      = Document Title
      :pdf-page-layout: landscape
      :page-background-image: image:green-bar.svg[pdfwidth=50%,fit=scale-down]

      This page has a background image.
      END

      (expect to_file).to visually_match 'page-background-image-svg-prescaled.pdf'
    end

    it 'should not scale background image without explicit width to fit boundaries of page if fit is scale-down and image fits' do
      pdf = to_pdf <<~'END', analyze: :image
      = Document Title
      :page-background-image: image:square.png[fit=scale-down]

      This page has a background image.
      END

      (expect pdf.images).to have_size 1
      bg_image = pdf.images[0]
      (expect bg_image[:width]).to eql 12.0
      (expect bg_image[:height]).to eql 12.0
    end

    it 'should not scale background SVG to fit boundaries of page if fit is none', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-fit-none.pdf'
      = Document Title
      :page-background-image: image:example-stamp.svg[fit=none]

      This page has a background image.
      END

      (expect to_file).to visually_match 'page-background-image-svg-unscaled.pdf'
    end

    it 'should scale up background SVG until it covers page if fit=cover', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-cover.pdf'
      = Document Title
      :page-background-image: image:square.svg[fit=cover]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-svg-cover.pdf'
    end

    it 'should scale background PNG to fill page if fit=fill', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-fill.pdf'
      = Document Title
      :page-background-image: image:square.png[fit=fill]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-fill.pdf'
    end

    it 'should allow remote image in SVG to be read if allow-uri-read attribute is set', network: true, visual: true do
      with_svg_with_remote_image do |image_path|
        to_file = to_pdf_file <<~END, 'page-background-image-svg-with-remote-image.pdf', attribute_overrides: { 'allow-uri-read' => '' }
        :page-background-image: image:#{image_path}[fit=none,position=top]

        Asciidoctor
        END

        (expect to_file).to visually_match 'page-background-image-svg-with-image.pdf'
      end
    end

    it 'should not allow remote image in SVG to be read if allow-uri-read attribute is not set', visual: true do
      with_svg_with_remote_image do |image_path|
        (expect do
          to_file = to_pdf_file <<~END, 'page-background-image-svg-with-remote-image-disabled.pdf'
          :page-background-image: image:#{image_path}[fit=none,position=top]

          Asciidoctor
          END

          (expect to_file).to visually_match 'page-background-image-svg-with-image-disabled.pdf'
        end).to log_message severity: :WARN, message: '~No handler available for this URL scheme'
      end
    end

    it 'should not warn if background SVG has warnings', visual: true do
      (expect do
        to_file = to_pdf_file <<~'END', 'page-background-image-svg-faulty.pdf'
        = Document Title
        :page-background-image: image:faulty.svg[]

        This page has a background image that is rather loud.
        END
        (expect to_file).to visually_match 'page-background-image-svg-scale-up.pdf'
      end).to log_message severity: :WARN, message: %(~problem encountered in image: #{fixture_file 'faulty.svg'}; Unknown tag 'foobar')
    end

    it 'should read local image relative to SVG', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-svg-with-local-image.pdf'
      :page-background-image: image:svg-with-local-image.svg[fit=none,pdfwidth=1cm,position=top]

      Asciidoctor
      END

      (expect to_file).to visually_match 'page-background-image-svg-with-image.pdf'
    end

    it 'should position background image according to value of position attribute on macro', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-position.pdf'
      = Document Title
      :page-background-image: image:example-stamp.svg[fit=none,pdfwidth=50%,position=bottom center]

      content
      END

      (expect to_file).to visually_match 'page-background-image-position.pdf'
    end

    it 'should position page background in center if position value is unrecognized' do
      pdf = to_pdf <<~'END', analyze: :image
      = Document Title
      :page-background-image: image:tux.png[fit=none,pdfwidth=4in,position=center]

      content
      END

      bg_image = pdf.images[0]
      center_coords = [bg_image[:x], bg_image[:y]]

      ['droit', 'haut droit'].each do |position|
        pdf = to_pdf <<~END, analyze: :image
        = Document Title
        :page-background-image: image:tux.png[fit=none,pdfwidth=4in,position=#{position}]

        content
        END

        bg_image = pdf.images[0]
        actual_coords = [bg_image[:x], bg_image[:y]]
        (expect actual_coords).to eql center_coords
      end
    end

    it 'should alternate page background if both verso and recto background images are specified', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-alt.pdf'
      = Document Title
      :doctype: book
      :page-background-image-recto: image:recto-bg.png[]
      :page-background-image-verso: image:verso-bg.png[]

      content

      <<<

      more content

      <<<

      the end
      END

      (expect to_file).to visually_match 'page-background-image-alt.pdf'
    end

    it 'should swap recto and verso background images when pdf-folio-placement is inverted', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-alt.pdf'
      = Document Title
      :doctype: book
      :page-background-image-recto: image:verso-bg.png[]
      :page-background-image-verso: image:recto-bg.png[]
      :pdf-folio-placement: physical-inverted

      content

      <<<

      more content

      <<<

      the end
      END

      (expect to_file).to visually_match 'page-background-image-alt.pdf'
    end

    it 'should alternate page background in landscape if both verso and recto background images are specified', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-alt-landscape.pdf'
      = Document Title
      :doctype: book
      :pdf-page-layout: landscape
      :page-background-image-recto: image:recto-bg-landscape.png[]
      :page-background-image-verso: image:verso-bg-landscape.png[]

      content

      <<<

      more content

      <<<

      the end
      END

      (expect to_file).to visually_match 'page-background-image-alt-landscape.pdf'
    end

    it 'should use background image as fallback if background image for side not specified', visual: true do
      to_file = to_pdf_file <<~'END', 'page-background-image-alt.pdf'
      = Document Title
      :doctype: book
      :page-background-image: image:recto-bg.png[]
      :page-background-image-verso: image:verso-bg.png[]

      content

      <<<

      more content

      <<<

      the end
      END

      (expect to_file).to visually_match 'page-background-image-alt.pdf'
    end

    it 'should allow recto background image to be turned off if side is set to none or empty', visual: true do
      [
        { 'page-background-image' => 'image:recto-bg.png[]', 'page-background-image-verso' => 'none' },
        { 'page-background-image' => 'image:recto-bg.png[]', 'page-background-image-verso' => '' },
        { 'page-background-image-recto' => 'image:recto-bg.png[]' },
      ].each do |attribute_overrides|
        to_file = to_pdf_file <<~'END', 'page-background-image-recto-only.pdf', attribute_overrides: attribute_overrides
        = Document Title
        :doctype: book

        content

        <<<

        more content

        <<<

        the end
        END

        (expect to_file).to visually_match 'page-background-image-recto-only.pdf'
      end
    end

    it 'should allow verso background image to be disabled if side is set to none', visual: true do
      [
        { 'page-background-image' => 'image:verso-bg.png[]', 'page-background-image-recto' => 'none' },
        { 'page-background-image-verso' => 'image:verso-bg.png[]' },
      ].each do |attribute_overrides|
        to_file = to_pdf_file <<~'END', 'page-background-image-verso-only.pdf', attribute_overrides: attribute_overrides
        = Document Title
        :doctype: book

        content

        <<<

        more content

        <<<

        the end
        END

        (expect to_file).to visually_match 'page-background-image-verso-only.pdf'
      end
    end

    it 'should allow page background image in theme to be specified per layout using page-layout attribute reference in path' do
      pdf_theme = { __dir__: fixtures_dir, page_background_image: 'image:square-{page-layout}.svg[fit=fill]' }
      rects = (to_pdf <<~'END', pdf_theme: pdf_theme, analyze: :rect).rects
      portrait page

      [page-layout=landscape]
      <<<

      landscape page
      END
      (expect rects).to have_size 2
      (expect rects[0][:fill_color]).to eql 'FF0000'
      (expect rects[0][:point][0]).to eql 0.0
      (expect rects[1][:point][0]).to be > 0
    end

    it 'should allow page background image in doc to be specified per layout using page-layout attribute reference in path' do
      rects = (to_pdf <<~'END', analyze: :rect).rects
      :page-background-image: image:square-\{page-layout}.svg[fit=fill]
      portrait page

      [page-layout=landscape]
      <<<

      landscape page
      END
      (expect rects).to have_size 2
      (expect rects[0][:fill_color]).to eql 'FF0000'
      (expect rects[0][:point][0]).to eql 0.0
      (expect rects[1][:point][0]).to be > 0
    end

    it 'should use the specified image format', visual: true do
      source_file = (dest_file = fixture_file 'square') + '.svg'
      FileUtils.cp source_file, dest_file
      to_file = to_pdf_file <<~'END', 'page-background-image-format.pdf'
      = Document Title
      :page-background-image: image:square[format=svg]

      This page has a background image that is rather loud.
      END

      (expect to_file).to visually_match 'page-background-image-svg-scale-up.pdf'
    ensure
      File.unlink dest_file
    end

    it 'should warn instead of crash if image is unreadable' do
      (expect do
        pdf = to_pdf <<~'END', analyze: :image
        = Document Title
        :page-background-image: image:does-not-exist.png[fit=cover]

        content
        END
        (expect pdf.images).to be_empty
      end).to log_message severity: :WARN, message: '~page background image not found or readable'
    end

    it 'should warn instead of crash if background image is invalid' do
      (expect do
        pdf = to_pdf <<~'END', analyze: :image
        = Document Title
        :page-background-image: image:corrupt.png[fit=cover]

        content
        END
        (expect pdf.images).to be_empty
      end).to log_message severity: :WARN, message: '~image file is an unrecognised format'
    end

    it 'should warn instead of crash if background image cannot be parsed' do
      (expect do
        pdf = to_pdf <<~'END', analyze: :image
        = Document Title
        :page-background-image: image:broken.svg[fit=cover]

        content
        END
        (expect pdf.images).to be_empty
      end).to log_message severity: :WARN, message: %(~The data supplied is not a valid SVG document.\nMissing end tag for 'rect')
    end

    it 'should only warn once if background image cannot be loaded' do
      (expect do
        pdf = to_pdf <<~'END', analyze: :image
        = Document Title
        :page-background-image: image:corrupt.png[fit=cover]

        content

        <<<

        more content

        <<<

        even more content
        END
        (expect pdf.images).to be_empty
      end).to log_message severity: :WARN, message: '~image file is an unrecognised format'
    end

    it 'should still render different facing background image when background image cannot be loaded' do
      (expect do
        pdf = to_pdf <<~'END', analyze: :image
        = Document Title
        :page-background-image: image:corrupt.png[fit=cover]
        :page-background-image-verso: image:bg.png[]

        content

        <<<

        more content

        <<<

        even more content
        END
        (expect pdf.images).to have_size 1
        (expect pdf.images[0][:page_number]).to be 2
      end).to log_message severity: :WARN, message: '~image file is an unrecognised format'
    end

    it 'should support PDF as background image', visual: true do
      # NOTE: the running content is automatically disabled since this becomes an imported page
      to_file = to_pdf_file <<~'END', 'page-background-image-pdf.pdf', enable_footer: true
      :page-background-image-recto: image:tux-bg.pdf[]

      Tux has left his mark on this page.

      <<<

      But not on this page.
      END

      (expect to_file).to visually_match 'page-background-image-pdf.pdf'
    end

    it 'should only warn once if PDF for background image cannot be found' do
      (expect do
        pdf = to_pdf <<~'END', analyze: :image
        = Document Title
        :page-background-image: image:no-such-file.pdf[]

        content

        <<<

        more content

        <<<

        even more content
        END
        (expect pdf.images).to be_empty
      end).to log_message severity: :WARN, message: '~page background image not found or readable'
    end
  end

  context 'Watermark' do
    it 'should stamp watermark image on the top of all pages if page-foreground-image is specified', visual: true do
      to_file = to_pdf_file <<~END, 'page-watermark.pdf'
      = Document Title
      :doctype: book
      :page-foreground-image: image:watermark.svg[]

      [.text-left]
      #{['lots of rambling'] * 150 * ?\n}

      <<<

      [.text-left]
      #{['lots of rambling'] * 150 * ?\n}

      [page-layout=landscape]
      <<<

      [.text-left]
      #{['lots of rambling'] * 150 * ?\n}
      END

      (expect to_file).to visually_match 'page-watermark.pdf'
    end

    it 'should no apply watermark image to front cover, back cover, or imported page', visual: true do
      to_file = to_pdf_file <<~END, 'page-watermark-content-only.pdf'
      = Document Title
      :doctype: book
      :front-cover-image: image:cover.jpg[]
      :back-cover-image: image:cover.jpg[]
      :page-foreground-image: image:watermark.svg[]
      :notitle:

      [.text-left]
      #{['lots of rambling'] * 150 * ?\n}

      image::red-green-blue.pdf[page=1]

      [.text-left]
      #{['lots of rambling'] * 150 * ?\n}
      END

      (expect to_file).to visually_match 'page-watermark-content-only.pdf'
    end

    it 'should support PNG transparency when applying watermark image to all pages', visual: true do
      to_file = to_pdf_file <<~END, 'page-watermark.pdf'
      = Document Title
      :doctype: book
      :notitle:
      :page-foreground-image: image:watermark.png[]

      [.text-left]
      #{['lots of rambling'] * 250 * ?\n}
      END

      (expect to_file).to visually_match 'page-watermark-png-transparency.pdf'
    end
  end
end
