# frozen_string_literal: true require_relative 'spec_helper' describe 'Asciidoctor::PDF::Converter - PDF Info' do context 'compliance' do it 'should generate a PDF 1.4-compatible document by default' do (expect (to_pdf 'hello').pdf_version).to eql 1.4 end it 'should set PDF version specified by pdf-version attribute if valid' do (expect (to_pdf 'hello', attributes: { 'pdf-version' => '1.6' }).pdf_version).to eql 1.6 end it 'should generate a PDF 1.4-compatible document if value of pdf-version attribute is not recognized' do (expect (to_pdf 'hello', attributes: { 'pdf-version' => '3.0' }).pdf_version).to eql 1.4 end end context 'attribution' do it 'should include Asciidoctor PDF and Prawn versions in Creator field' do creator = (to_pdf 'hello').info[:Creator] (expect creator).not_to be_nil (expect creator).to include %(Asciidoctor PDF #{Asciidoctor::PDF::VERSION}) (expect creator).to include %(Prawn #{Prawn::VERSION}) end it 'should set Producer field to value of Creator field by default' do pdf = to_pdf 'hello' (expect pdf.info[:Producer]).not_to be_nil (expect pdf.info[:Producer]).to eql pdf.info[:Creator] end it 'should set Author and Producer field to value of author attribute if set' do ['Author Name', ':author: Author Name'].each do |author_line| pdf = to_pdf <<~END = Document Title #{author_line} content END (expect pdf.info[:Producer]).to eql pdf.info[:Author] (expect pdf.info[:Author]).to eql 'Author Name' end end it 'should set Author and Producer field to value of author attribute if set to multiple authors' do ['Author Name; Assistant Name', ':authors: Author Name; Assistant Name'].each do |author_line| pdf = to_pdf <<~END = Document Title #{author_line} [%hardbreaks] First Author: {author_1} Second Author: {author_2} END lines = ((pdf.page 1).text.split ?\n).map(&:strip) (expect pdf.info[:Producer]).to eql pdf.info[:Author] (expect pdf.info[:Author]).to eql 'Author Name, Assistant Name' (expect lines).to include 'First Author: Author Name' (expect lines).to include 'Second Author: Assistant Name' end end it 'should set Author and Producer field using authors attribute with non-Latin characters' do ['Doc Writer; Antonín Dvořák', ':authors: Doc Writer; Antonín Dvořák'].each do |author_line| pdf = to_pdf <<~END = Document Title #{author_line} [%hardbreaks] First Author: {author_1} Second Author: {author_2} END lines = ((pdf.page 1).text.split ?\n).map(&:strip) (expect pdf.info[:Producer]).to eql pdf.info[:Author] (expect pdf.info[:Author]).to eql 'Doc Writer, Antonín Dvořák' (expect lines).to include 'First Author: Doc Writer' (expect lines).to include 'Second Author: Antonín Dvořák' end end it 'should set Author field to value of author attribute if locked by the API' do pdf = to_pdf <<~'END', attribute_overrides: { 'author' => 'Doc Writer' } = Document Title Author Name content END (expect pdf.info[:Author]).to eql 'Doc Writer' end it 'should set Author field to value of authors attribute if locked by the API' do pdf = to_pdf <<~'END', attribute_overrides: { 'authors' => 'Doc Writer' } = Document Title Author Name content END (expect pdf.info[:Author]).to eql 'Doc Writer' end it 'should set Author field to value of authors attribute if both author and authors attributes are locked by the API' do pdf = to_pdf <<~'END', attribute_overrides: { 'authors' => 'Doc Writer', 'author' => 'Anonymous' } = Document Title Author Name content END (expect pdf.info[:Author]).to eql 'Doc Writer' end it 'should set Author field to value of author attribute if document has no doctitle' do pdf = to_pdf <<~'END' :author: Author Name == Section Title content END (expect pdf.info[:Author]).to eql 'Author Name' end it 'should set Author field to value of authors attribute if document has no doctitle' do pdf = to_pdf <<~'END' :authors: Author Name == Section Title content END (expect pdf.info[:Author]).to eql 'Author Name' end it 'should set Producer field to value of publisher attribute if set' do pdf = to_pdf <<~'END' = Document Title Author Name :publisher: Big Cheese content END (expect pdf.info[:Author]).to eql 'Author Name' (expect pdf.info[:Producer]).to eql 'Big Cheese' end it 'should set Subject field to value of subject attribute if set' do pdf = to_pdf <<~'END' = Document Title :subject: Cooking content END (expect pdf.info[:Subject]).to eql 'Cooking' end it 'should set Keywords field to value of subject attribute if set' do pdf = to_pdf <<~'END' = Document Title :keywords: cooking, diet, plants content END (expect pdf.info[:Keywords]).to eql 'cooking, diet, plants' end it 'should sanitize values of Author, Subject, Keywords, and Producer fields' do pdf = to_pdf <<~'END' = Document Title D_J Allen :subject: Science & Math :keywords: mass–energy equivalence :publisher: Schrödinger’s Cat content END pdf_info = pdf.info (expect pdf_info[:Author]).to eql 'D_J Allen' (expect pdf_info[:Subject]).to eql 'Science & Math' (expect pdf_info[:Keywords]).to eql 'mass–energy equivalence' (expect pdf_info[:Producer]).to eql 'Schrödinger’s Cat' end it 'should parse date attributes as local date objects' do pdf = to_pdf 'content', attribute_overrides: { 'docdatetime' => '2019-01-15', 'localdatetime' => '2019-01-15' } (expect pdf.info[:ModDate]).not_to be_nil (expect pdf.info[:ModDate]).to start_with 'D:20190115000000' (expect pdf.info[:CreationDate]).not_to be_nil (expect pdf.info[:CreationDate]).to start_with 'D:20190115000000' end it 'should set date attributes to same current date when date attributes cannot be parsed' do pdf = to_pdf 'content', attribute_overrides: { 'docdatetime' => 'garbage', 'localdatetime' => 'garbage' } (expect pdf.info[:ModDate]).not_to be_nil (expect pdf.info[:ModDate]).to start_with 'D:' (expect pdf.info[:CreationDate]).not_to be_nil (expect pdf.info[:CreationDate]).to start_with 'D:' (expect pdf.info[:ModDate]).to eql pdf.info[:CreationDate] end it 'should use current date as fallback when docdatetime cannot be parsed' do expected = (Time.now.strftime '%s').to_i pdf = to_pdf 'content', attribute_overrides: { 'docdatetime' => 'garbage' } mod_date = pdf.info[:ModDate] (expect mod_date).not_to be_nil (expect mod_date).to start_with 'D:' actual = ((DateTime.parse (mod_date.slice 2, mod_date.length).gsub ?', '').strftime '%s').to_i (expect actual).to be_within(1).of(expected) end it 'should use current date as fallback when localdatetime cannot be parsed' do expected = (Time.now.strftime '%s').to_i pdf = to_pdf 'content', attribute_overrides: { 'localdatetime' => 'garbage' } creation_date = pdf.info[:CreationDate] (expect creation_date).not_to be_nil (expect creation_date).to start_with 'D:' actual = ((DateTime.parse (creation_date.slice 2, creation_date.length).gsub ?', '').strftime '%s').to_i (expect actual).to be_within(1).of(expected) end it 'should not add dates to document if reproducible attribute is set' do pdf = to_pdf <<~'END', attribute_overrides: { 'reproducible' => '' } = Document Title Author Name content END (expect pdf.info[:ModDate]).to be_nil (expect pdf.info[:CreationDate]).to be_nil end it 'should not add software versions to document if reproducible attribute is set' do pdf = to_pdf <<~'END', attribute_overrides: { 'reproducible' => '' } = Document Title Author Name content END (expect pdf.info[:Creator]).to eql 'Asciidoctor PDF, based on Prawn' end it 'should set mod and creation dates to match SOURCE_DATE_EPOCH environment variable' do old_source_date_epoch = ENV.delete 'SOURCE_DATE_EPOCH' ENV['SOURCE_DATE_EPOCH'] = '1234123412' pdf = to_pdf 'content' (expect pdf.info[:ModDate]).to eql 'D:20090208200332+00\'00\'' (expect pdf.info[:CreationDate]).to eql 'D:20090208200332+00\'00\'' ensure if old_source_date_epoch ENV['SOURCE_DATE_EPOCH'] = old_source_date_epoch else ENV.delete 'SOURCE_DATE_EPOCH' end end end context 'document title' do it 'should set Title field to value of untitled-label attribute if doctitle is not set' do pdf = to_pdf 'body' (expect pdf.info[:Title]).to eql 'Untitled' end it 'should not set Title field if untitled-label attribute is unset and doctitle is not set' do pdf = to_pdf 'body', attribute_overrides: { 'untitled-label' => nil } (expect pdf.info).not_to have_key :Title end it 'should set Title field to value of document title if set' do pdf = to_pdf '= Document Title' (expect pdf.info[:Title]).to eql 'Document Title' end it 'should remove text formatting from document title before assigning to Title field' do pdf = to_pdf '= *Document* _Title_' (expect pdf.info[:Title]).to eql 'Document Title' end it 'should decode character references in document title before assigning to Title field' do pdf = to_pdf '= ACME(TM) Catalog <№ 1>' (expect pdf.info[:Title]).to eql %(ACME\u2122 Catalog <\u2116 1>) end it 'should hex encode non-ASCII characters in Title field' do doctitle = 'Guide de démarrage rapide' pdf = to_pdf %(= #{doctitle}) (expect pdf.info[:Title]).to eql doctitle encoded_doctitle = pdf.objects[pdf.objects.trailer[:Info]][:Title].unpack 'H*' (expect encoded_doctitle).to eql (doctitle.encode Encoding::UTF_16).unpack 'H*' end end context 'compress' do it 'should not compress streams by default' do pdf = to_pdf 'foobar' objects = pdf.objects pages = pdf.objects.values.find {|it| Hash === it && it[:Type] == :Pages } stream = objects[objects[pages[:Kids][0]][:Contents]] (expect stream.hash[:Filter]).to be_nil (expect stream.data).to include '/DeviceRGB' end it 'should compress streams if compress attribute is set on document' do pdf = to_pdf 'foobar', attribute_overrides: { 'compress' => '' } objects = pdf.objects pages = pdf.objects.values.find {|it| Hash === it && it[:Type] == :Pages } stream = objects[objects[pages[:Kids][0]][:Contents]] (expect stream.hash[:Filter]).to eql [:FlateDecode] (expect stream.data).not_to include '/DeviceRGB' end it 'should not compress streams when compress attribute is set on document and page is imported' do pdf = to_pdf <<~'END', attribute_overrides: { 'compress' => '' } before image::red-green-blue.pdf[page=1] after END objects = pdf.objects pages = pdf.objects.values.find {|it| Hash === it && it[:Type] == :Pages } objects[pages[:Kids][1]][:Contents].map {|it| objects[it] }.each do |stream| (expect stream.hash[:Filter]).to be_nil (expect stream.data).to include '/DeviceRGB' end stream = objects[objects[pages[:Kids][0]][:Contents]] (expect stream.hash[:Filter]).to be_nil (expect stream.data).to include '/DeviceRGB' stream = objects[objects[pages[:Kids][2]][:Contents]] (expect stream.hash[:Filter]).to be_nil (expect stream.data).to include '/DeviceRGB' end end end