# frozen_string_literal: true
require_relative 'spec_helper'
describe Asciidoctor::PDF::FormattedText::Formatter do
context 'HTML markup' do
it 'should format strong text' do
output = subject.format 'strong'
(expect output).to have_size 1
(expect output[0][:text]).to eql 'strong'
(expect output[0][:styles]).to eql [:bold].to_set
end
it 'should ignore unsupported style property' do
input = %(hot)
output = subject.format input
(expect output).to have_size 1
(expect output[0][:text]).to eql 'hot'
end
it 'should ignore font color if not a valid hex value' do
input = %(hot)
output = subject.format input
(expect output).to have_size 1
(expect output[0][:text]).to eql 'hot'
(expect output[0][:color]).to be_nil
end
it 'should allow font color to be set on phrase using hex value' do
['#F00', '#FF0000'].each do |color|
input = %(hot)
output = subject.format input
(expect output).to have_size 1
(expect output[0][:text]).to eql 'hot'
(expect output[0][:color]).to eql 'FF0000'
end
end
it 'should allow font color to be set on nested phrase' do
input = 'hot cold hot'
output = subject.format input
(expect output).to have_size 3
(expect output[1][:text]).to eql 'cold'
(expect output[1][:color]).to eql '0000FF'
end
it 'should ignore background color if not a valid hex value' do
input = %(highlight)
output = subject.format input
(expect output).to have_size 1
(expect output[0][:text]).to eql 'highlight'
(expect output[0][:background_color]).to be_nil
end
it 'should allow background color to be set on phrase using hex value' do
['#FF0', '#FFFF00'].each do |color|
input = %(highlight)
output = subject.format input
(expect output).to have_size 1
(expect output[0][:text]).to eql 'highlight'
(expect output[0][:background_color]).to eql 'FFFF00'
end
end
it 'should only register callback to apply background color once when background color specified on both style and role' do
pdf_theme = build_pdf_theme role_hl_background_color: 'FFFF00'
input = %(highlight)
output = (subject.class.new theme: pdf_theme).format input
(expect output).to have_size 1
(expect output[0][:text]).to eql 'highlight'
(expect output[0][:background_color]).to eql 'FFFF00'
(expect output[0][:callback]).to have_size 1
end
it 'should only register callback to apply background color once when background color specified on both element and role' do
pdf_theme = build_pdf_theme codespan_background_color: 'CCCCCC', role_hl_background_color: 'FFFF00'
input = %(code)
output = (subject.class.new theme: pdf_theme).format input
(expect output).to have_size 1
(expect output[0][:text]).to eql 'code'
(expect output[0][:background_color]).to eql 'FFFF00'
(expect output[0][:callback]).to have_size 1
end
it 'should allow font weight to be set on nested phrase' do
input = 'new release'
output = subject.format input
(expect output).to have_size 2
(expect output[0][:text]).to eql 'new'
(expect output[0][:styles].to_a).to eql [:bold]
end
it 'should ignore unknown font weight on phrase' do
input = 'new release'
output = subject.format input
(expect output).to have_size 2
(expect output[0][:text]).to eql 'new'
(expect output[0][:styles]).to be_nil
end
it 'should allow font style to be set on nested phrase' do
input = 'This is so easy'
output = subject.format input
(expect output).to have_size 3
(expect output[1][:text]).to eql 'so'
(expect output[1][:styles].to_a).to eql [:italic]
end
it 'should ignore unknown font style on phrase' do
input = 'This is so easy'
output = subject.format input
(expect output).to have_size 3
(expect output[1][:text]).to eql 'so'
(expect output[1][:styles]).to be_nil
end
it 'should warn if text contains unrecognized tag' do
input = 'before bar after'
(expect do
output = subject.format input
(expect output).to have_size 1
(expect output[0][:text]).to eql input
end).to log_message severity: :ERROR, message: /^failed to parse formatted text: #{Regexp.escape input} \(reason: Expected one of .* after < at byte 9\)/
end
it 'should warn if text contains unrecognized entity' do
input = 'a &daggar; in the back'
(expect do
output = subject.format input
(expect output).to have_size 1
(expect output[0][:text]).to eql input
end).to log_message severity: :ERROR, message: /^failed to parse formatted text: #{Regexp.escape input} \(reason: Expected one of .* after & at byte 4\)/
end
it 'should allow span tag to control width' do
output = subject.format 'hi'
(expect output).to have_size 1
(expect output[0][:text]).to eql 'hi'
(expect output[0][:width]).to eql '1in'
(expect output[0][:align]).to be_nil
end
it 'should allow span tag to align text to center within width' do
output = subject.format 'hi'
(expect output).to have_size 1
(expect output[0][:text]).to eql 'hi'
(expect output[0][:width]).to eql '1in'
(expect output[0][:align]).to eql :center
end
it 'should allow span tag to align text to right within width' do
output = subject.format 'hi'
(expect output).to have_size 1
(expect output[0][:text]).to eql 'hi'
(expect output[0][:width]).to eql '1in'
(expect output[0][:align]).to eql :right
end
it 'should allow span tag to align text to left within width' do
output = subject.format 'hi'
(expect output).to have_size 1
(expect output[0][:text]).to eql 'hi'
(expect output[0][:width]).to eql '1in'
(expect output[0][:align]).to eql :left
end
end
context 'character references' do
it 'should decode decimal character reference' do
{
''' => ?',
'©' => ?\u00a9,
'😃' => ([0x1f603].pack 'U1'),
}.each do |ref, chr|
output = subject.format ref
(expect output).to have_size 1
(expect output[0][:text]).to eql chr
end
end
it 'should decode hexadecimal character reference' do
{
''' => ?',
'©' => ?\u00a9,
'😃' => ([0x1f603].pack 'U1'),
'©' => ?\u00a9,
'😃' => ([0x1f603].pack 'U1'),
}.each do |ref, chr|
output = subject.format ref
(expect output).to have_size 1
(expect output[0][:text]).to eql chr
end
end
it 'should decode recognized named entities' do
output = subject.format '< > & ' "'
(expect output).to have_size 1
(expect output[0][:text]).to eql %(< > & ' \u00a0 ")
end
it 'should ignore unknown named entities' do
(expect do
output = subject.format '†'
(expect output).to have_size 1
(expect output[0][:text]).to eql '†'
end).to log_message severity: :ERROR, message: '~failed to parse formatted text'
end
it 'should decode decimal character references in link href' do
output = subject.format 'My Playlist'
(expect output).to have_size 1
(expect output[0][:link]).to eql 'https://cast.you?v=999999&list=abcde&index=1'
end
it 'should decode hexadecimal character references in link href' do
output = subject.format 'My Playlist'
(expect output).to have_size 1
(expect output[0][:link]).to eql 'https://cast.you?v=999999&list=abcde&index=1'
end
end
# QUESTION: should these go in a separate file?
context 'integration' do
it 'should format constrained strong phrase' do
pdf = to_pdf '*strong*', analyze: true
(expect pdf.text[0].values_at :string, :font_name).to eql %w(strong NotoSerif-Bold)
end
it 'should format unconstrained strong phrase' do
pdf = to_pdf '**super**nova', analyze: true
(expect pdf.text[0].values_at :string, :font_name).to eql %w(super NotoSerif-Bold)
(expect pdf.text[1].values_at :string, :font_name).to eql %w(nova NotoSerif)
end
it 'should format constrained emphasis phrase' do
pdf = to_pdf '_emphasis_', analyze: true
(expect pdf.text[0].values_at :string, :font_name).to eql %w(emphasis NotoSerif-Italic)
end
it 'should format unconstrained emphasis phrase' do
pdf = to_pdf '__un__cool', analyze: true
(expect pdf.text[0].values_at :string, :font_name).to eql %w(un NotoSerif-Italic)
(expect pdf.text[1].values_at :string, :font_name).to eql %w(cool NotoSerif)
end
it 'should format constrained monospace phrase' do
pdf = to_pdf '`monospace`', analyze: true
(expect pdf.text[0].values_at :string, :font_name).to eql %w(monospace mplus1mn-regular)
end
it 'should format unconstrained monospace phrase' do
pdf = to_pdf '``install``ed', analyze: true
(expect pdf.text[0].values_at :string, :font_name).to eql %w(install mplus1mn-regular)
(expect pdf.text[1].values_at :string, :font_name).to eql %w(ed NotoSerif)
end
it 'should ignore empty formatted phrase surrounded by text' do
pdf = to_pdf 'before *{empty}* after', analyze: true
text = pdf.text
(expect text).to have_size 1
(expect text[0][:string]).to eql 'before after'
end
it 'should ignore empty formatted phrase at extrema of line' do
pdf = to_pdf '*{empty}* between *{empty}*', analyze: true
text = pdf.text
(expect text).to have_size 1
(expect text[0][:string]).to eql 'between'
end
it 'should format stem equation as monospace' do
pdf = to_pdf 'Use stem:[x^2] to square the value.', analyze: true
equation_text = (pdf.find_text 'x^2')[0]
(expect equation_text[:font_name]).to eql 'mplus1mn-regular'
end
it 'should format superscript phrase' do
pdf = to_pdf 'x^2^', analyze: true
(expect pdf.strings).to eql %w(x 2)
text = pdf.text
(expect text[0][:font_size]).to be > text[1][:font_size]
(expect text[0][:y]).to be < text[1][:y]
end
it 'should compute font size for superscript phrase correctly when parent element uses em units' do
pdf = to_pdf '`x^2^` represents exponential growth', pdf_theme: { base_font_size: 14, codespan_font_size: '0.8em' }, analyze: true
expected_font_size = 14 * 0.8 * 0.583
superscript_text = pdf.find_unique_text '2'
(expect superscript_text[:font_size]).to eql expected_font_size
end
it 'should compute font size for superscript phrase correctly when parent element uses % units' do
pdf = to_pdf '`x^2^` represents exponential growth', pdf_theme: { base_font_size: 14, codespan_font_size: '90%' }, analyze: true
expected_font_size = 14 * 0.9 * 0.583
superscript_text = pdf.find_unique_text '2'
(expect superscript_text[:font_size]).to eql expected_font_size
end
it 'should compute font size for superscript phrase correctly when parent element uses no units' do
pdf = to_pdf '`x^2^` represents exponential growth', pdf_theme: { base_font_size: 14, codespan_font_size: '12' }, analyze: true
expected_font_size = (12 * 0.583).round 4
superscript_text = pdf.find_unique_text '2'
(expect superscript_text[:font_size]).to eql expected_font_size
end
it 'should format subscript phrase' do
pdf = to_pdf 'O~2~', analyze: true
(expect pdf.strings).to eql %w(O 2)
text = pdf.text
(expect text[0][:font_size]).to be > text[1][:font_size]
(expect text[0][:y]).to be > text[1][:y]
end
it 'should compute font size for subscript phrase correctly when parent element uses em units' do
pdf = to_pdf 'The formula `O~2~` is oxygen', pdf_theme: { base_font_size: 14, codespan_font_size: '0.8em' }, analyze: true
expected_font_size = 14 * 0.8 * 0.583
subscript_text = pdf.find_unique_text '2'
(expect subscript_text[:font_size]).to eql expected_font_size
end
it 'should compute font size for subscript phrase correctly when parent element uses % units' do
pdf = to_pdf 'The formula `O~2~` is oxygen', pdf_theme: { base_font_size: 14, codespan_font_size: '90%' }, analyze: true
expected_font_size = 14 * 0.9 * 0.583
subscript_text = pdf.find_unique_text '2'
(expect subscript_text[:font_size]).to eql expected_font_size
end
it 'should compute font size for subscript phrase correctly when parent element uses no units' do
pdf = to_pdf 'The formula `O~2~` is oxygen', pdf_theme: { base_font_size: 14, codespan_font_size: '12' }, analyze: true
expected_font_size = (12 * 0.583).round 4
subscript_text = pdf.find_unique_text '2'
(expect subscript_text[:font_size]).to eql expected_font_size
end
it 'should add background and border to code as defined in theme', visual: true do
theme_overrides = {
codespan_background_color: 'f5f5f5',
codespan_border_color: 'dddddd',
codespan_border_width: 0.25,
codespan_border_offset: 2.5,
}
to_file = to_pdf_file 'All your `code` belongs to us.', 'text-formatter-code.pdf', pdf_theme: theme_overrides
(expect to_file).to visually_match 'text-formatter-code.pdf'
end
it 'should use base border color if theme does not define border color for code', visual: true do
theme_overrides = {
base_border_color: 'dddddd',
codespan_background_color: 'f5f5f5',
codespan_border_width: 0.25,
codespan_border_offset: 2.5,
}
to_file = to_pdf_file 'All your `code` belongs to us.', 'text-formatter-code.pdf', pdf_theme: theme_overrides
(expect to_file).to visually_match 'text-formatter-code.pdf'
end
it 'should add border to phrase even when no background color is set', visual: true do
theme_overrides = {
codespan_font_color: '444444',
codespan_font_size: '0.75em',
codespan_border_color: 'E83E8C',
codespan_border_width: 0.25,
codespan_border_offset: 2.5,
codespan_border_radius: 3,
}
to_file = to_pdf_file 'Type `bundle install` to install dependencies', 'text-formatter-border-only.pdf', pdf_theme: theme_overrides
(expect to_file).to visually_match 'text-formatter-border-only.pdf'
end
it 'should add background and border to button as defined in theme', visual: true do
theme_overrides = {
button_content: '%s',
button_background_color: '007BFF',
button_border_offset: 2.5,
button_border_radius: 2,
button_border_width: 0.5,
button_border_color: '333333',
button_font_color: 'ffffff',
}
to_file = to_pdf_file 'Click btn:[Save] to save your work.', 'text-formatter-button.pdf', pdf_theme: theme_overrides, attribute_overrides: { 'experimental' => '' }
(expect to_file).to visually_match 'text-formatter-button.pdf'
end
it 'should use base border color if theme does not defined border color for button', visual: true do
theme_overrides = {
base_border_color: '333333',
button_content: '%s',
button_background_color: '007BFF',
button_border_offset: 2.5,
button_border_radius: 2,
button_border_width: 0.5,
button_font_color: 'ffffff',
}
to_file = to_pdf_file 'Click btn:[Save] to save your work.', 'text-formatter-button.pdf', pdf_theme: theme_overrides, attribute_overrides: { 'experimental' => '' }
(expect to_file).to visually_match 'text-formatter-button.pdf'
end
it 'should use label as default button content', visual: true do
theme_overrides = {
button_content: nil,
button_background_color: '007BFF',
button_border_offset: 2.5,
button_border_radius: 2,
button_border_width: 0.5,
button_border_color: '333333',
button_font_color: 'ffffff',
}
to_file = to_pdf_file 'Click btn:[Save] to save your work.', 'text-formatter-button-default.pdf', pdf_theme: theme_overrides, attribute_overrides: { 'experimental' => '' }
(expect to_file).to visually_match 'text-formatter-button.pdf'
end
it 'should replace %s with button label in button content defined in theme' do
theme_overrides = {
button_content: '[%s]',
button_font_color: '333333',
}
pdf = to_pdf 'Click btn:[Save] to save your work.', analyze: true, pdf_theme: theme_overrides, attribute_overrides: { 'experimental' => '' }
(expect pdf.lines).to eql ['Click [Save] to save your work.']
end
it 'should add background and border to kbd as defined in theme', visual: true do
to_file = to_pdf_file <<~'EOS', 'text-formatter-kbd.pdf', attribute_overrides: { 'experimental' => '' }
Press kbd:[q] to exit.
Press kbd:[Ctrl,c] to kill the process.
EOS
(expect to_file).to visually_match 'text-formatter-kbd.pdf'
end
it 'should use base border color if theme does not define border color for kbd', visual: true do
pdf_theme = {
base_border_color: 'CCCCCC',
kbd_border_color: nil,
}
to_file = to_pdf_file <<~'EOS', 'text-formatter-kbd.pdf', pdf_theme: pdf_theme, attribute_overrides: { 'experimental' => '' }
Press kbd:[q] to exit.
Press kbd:[Ctrl,c] to kill the process.
EOS
(expect to_file).to visually_match 'text-formatter-kbd.pdf'
end
it 'should use + as kbd separator if not specified in theme' do
pdf = to_pdf <<~'EOS', analyze: true, pdf_theme: { kbd_separator: nil }, attribute_overrides: { 'experimental' => '' }
Press kbd:[Ctrl,c] to kill the process.
EOS
(expect pdf.lines).to eql ['Press Ctrl + c to kill the process.']
end
it 'should convert menu macro' do
pdf = to_pdf <<~'EOS', analyze: true, attribute_overrides: { 'experimental' => '' }
Select menu:File[Quit] to exit.
EOS
menu_texts = pdf.find_text font_name: 'NotoSerif-Bold'
(expect menu_texts).to have_size 3
(expect menu_texts[0][:string]).to eql 'File '
(expect menu_texts[0][:font_color]).to eql '333333'
(expect menu_texts[1][:string]).to eql ?\u203a
(expect menu_texts[1][:font_color]).to eql 'B12146'
(expect menu_texts[2][:string]).to eql ' Quit'
(expect menu_texts[2][:font_color]).to eql '333333'
(expect pdf.lines).to eql [%(Select File \u203a Quit to exit.)]
end
it 'should support menu macro with only the root level' do
pdf = to_pdf <<~'EOS', analyze: true, attribute_overrides: { 'experimental' => '' }
The menu:File[] menu is where all the useful stuff is.
EOS
menu_texts = pdf.find_text font_name: 'NotoSerif-Bold'
(expect menu_texts).to have_size 1
(expect menu_texts[0][:string]).to eql 'File'
(expect menu_texts[0][:font_color]).to eql '333333'
(expect pdf.lines).to eql ['The File menu is where all the useful stuff is.']
end
it 'should support menu macro with multiple levels' do
pdf = to_pdf <<~'EOS', analyze: true, attribute_overrides: { 'experimental' => '' }
Select menu:File[New,Class] to create a new Java class.
EOS
menu_texts = pdf.find_text font_name: 'NotoSerif-Bold'
(expect menu_texts).to have_size 5
(expect menu_texts[0][:string]).to eql 'File '
(expect menu_texts[0][:font_color]).to eql '333333'
(expect menu_texts[1][:string]).to eql ?\u203a
(expect menu_texts[1][:font_color]).to eql 'B12146'
(expect menu_texts[2][:string]).to eql ' New '
(expect menu_texts[2][:font_color]).to eql '333333'
(expect menu_texts[3][:string]).to eql ?\u203a
(expect menu_texts[3][:font_color]).to eql 'B12146'
(expect menu_texts[4][:string]).to eql ' Class'
(expect menu_texts[4][:font_color]).to eql '333333'
(expect pdf.lines).to eql [%(Select File \u203a New \u203a Class to create a new Java class.)]
end
it 'should use default caret content for menu if not specified by theme' do
pdf = to_pdf <<~'EOS', analyze: true, pdf_theme: { menu_caret_content: nil }, attribute_overrides: { 'experimental' => '' }
Select menu:File[Quit] to exit.
EOS
menu_texts = pdf.find_text font_name: 'NotoSerif-Bold'
(expect menu_texts).to have_size 1
(expect menu_texts[0][:string]).to eql %(File \u203a Quit)
(expect menu_texts[0][:font_color]).to eql '333333'
(expect pdf.lines).to eql [%(Select File \u203a Quit to exit.)]
end
it 'should allow theme to control font properties for menu' do
pdf = to_pdf <<~'EOS', analyze: true, pdf_theme: { menu_font_color: 'AA0000', menu_font_size: 10, menu_font_style: 'bold_italic', menu_caret_content: ' > ' }, attribute_overrides: { 'experimental' => '' }
Select menu:File[Quit] to exit.
EOS
menu_texts = pdf.find_text font_name: 'NotoSerif-BoldItalic'
(expect menu_texts).to have_size 1
(expect menu_texts[0][:string]).to eql %(File > Quit)
(expect menu_texts[0][:font_color]).to eql 'AA0000'
(expect menu_texts[0][:font_size]).to eql 10
(expect pdf.lines).to eql [%(Select File > Quit to exit.)]
end
it 'should add background to mark as defined in theme', visual: true do
to_file = to_pdf_file 'normal #highlight# normal', 'text-formatter-mark.pdf'
(expect to_file).to visually_match 'text-formatter-mark.pdf'
end
it 'should be able to reference section title containing icon' do
pdf = to_pdf <<~'EOS', analyze: true
:icons: font
[#reference]
== icon:cogs[] Heading
See <>.
EOS
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[0]).to eql %(\uf085 Heading)
(expect lines[1]).to eql %(See \uf085 Heading.)
end
it 'should apply text transform to text without markup' do
[
['uppercase', 'here we go again', 'HERE WE GO AGAIN'],
['lowercase', 'Here We Go Again', 'here we go again'],
['capitalize', 'Here we go again', 'Here We Go Again'],
['smallcaps', 'Here We Go Again', 'Hᴇʀᴇ Wᴇ Go Aɢᴀɪɴ'],
].each do |(transform, before, after)|
pdf = to_pdf %(== #{before}), pdf_theme: { heading_text_transform: transform }, analyze: true
lines = pdf.lines
(expect lines).to have_size 1
(expect lines[0]).to eql after
formatted_word = (pdf.find_text %r/again|aɢᴀɪɴ/i)[0]
(expect formatted_word[:font_name]).to eql 'NotoSerif-Bold'
end
end
it 'should apply text transform to text with markup' do
[
['uppercase', 'here we go *again*', 'HERE WE GO AGAIN'],
['lowercase', 'Here We Go *Again*', 'here we go again'],
['capitalize', 'Here we go *again*', 'Here We Go Again'],
['smallcaps', 'Here we go *again*', 'Hᴇʀᴇ ᴡᴇ ɢo ᴀɢᴀɪɴ'],
].each do |(transform, before, after)|
pdf = to_pdf %(== #{before}), pdf_theme: { heading_text_transform: transform }, analyze: true
lines = pdf.lines
(expect lines).to have_size 1
(expect lines[0]).to eql after
formatted_word = (pdf.find_text %r/again|ᴀɢᴀɪɴ/i)[0]
(expect formatted_word[:font_name]).to eql 'NotoSerif-Bold'
end
end
it 'should apply capitalization to contiguous characters' do
pdf = to_pdf %(== foo-bar baz), pdf_theme: { heading_text_transform: 'capitalize' }, analyze: true
lines = pdf.lines
(expect lines).to have_size 1
(expect lines[0]).to eql 'Foo-bar Baz'
(expect pdf.text[0][:font_name]).to eql 'NotoSerif-Bold'
end
it 'should not lowercase tags when applying lowercase text transform' do
pdf = to_pdf <<~'EOS', pdf_theme: { sidebar_text_transform: 'lowercase' }
****
image:TuxTheLinuxPenguin.png[width=20] <= How this fella came to be the Linux mascot.
****
EOS
(expect get_images pdf).to have_size 1
end
it 'should apply width and alignment specified by span tag', visual: true do
%w(left center right).each do |align|
to_file = to_pdf_file <<~EOS, %(text-formatter-align-#{align}-within-width.pdf)
|+++hi+++|
EOS
(expect to_file).to visually_match %(text-formatter-align-#{align}-within-width.pdf)
end
end
it 'should not warn if text contains invalid markup in scratch document' do
# NOTE: this assertion will fail if the message is logged multiple times
(expect do
pdf = to_pdf <<~'EOS', analyze: true
[%unbreakable]
--
before +++bar+++ after
--
EOS
(expect pdf.lines).to eql ['before bar after']
end).to log_message severity: :ERROR, message: /^failed to parse formatted text:/
end
end
context 'Roles' do
it 'should support built-in underline role for text span' do
input = '[.underline]#2001: A Space Odyssey#'
pdf = to_pdf input, analyze: :line
lines = pdf.lines
(expect lines).to have_size 1
underline = lines[0]
pdf = to_pdf input, analyze: true
text = pdf.text
(expect text).to have_size 1
underlined_text = text[0]
(expect underline[:from][:x]).to eql underlined_text[:x]
(expect underline[:from][:y]).to be < underlined_text[:y]
(expect underlined_text[:y] - underline[:from][:y]).to eql 1.25
(expect underlined_text[:font_color]).to eql underline[:color]
(expect underline[:to][:x] - underline[:from][:x]).to be > 100
end
it 'should support built-in line-through role for text span' do
input = '[.line-through]#delete me#'
pdf = to_pdf input, analyze: :line
lines = pdf.lines
(expect lines).to have_size 1
underline = lines[0]
pdf = to_pdf input, analyze: true
text = pdf.text
(expect text).to have_size 1
underlined_text = text[0]
(expect underline[:from][:x]).to eql underlined_text[:x]
(expect underline[:from][:y]).to be > underlined_text[:y]
(expect underlined_text[:y] - underline[:from][:y]).to be < 0
(expect underlined_text[:font_color]).to eql underline[:color]
(expect underline[:to][:x] - underline[:from][:x]).to be > 45
end
it 'should allow theme to override formatting for text decoration roles' do
pdf_theme = {
'role_line-through_text_decoration': 'none',
'role_line-through_font_color': 'AA0000',
role_underline_text_decoration: 'none',
role_underline_font_color: '0000AA',
}
input = '[.underline]#underline# and [.line-through]#line-through#'
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: :line
(expect pdf.lines).to be_empty
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true
line_through_text = (pdf.find_text 'line-through')[0]
(expect line_through_text[:font_color]).to eql 'AA0000'
underline_text = (pdf.find_text 'underline')[0]
(expect underline_text[:font_color]).to eql '0000AA'
end
it 'should allow theme to set text decoration color and width' do
pdf_theme = {
'role_line-through_text_decoration_color': 'AA0000',
'role_line-through_text_decoration_width': 2,
role_underline_text_decoration_color: '0000AA',
role_underline_text_decoration_width: 0.5,
}
input = <<~'EOS'
[.underline]#underline#
[.line-through]#line-through#
EOS
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: :line
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[0][:color]).to eql '0000AA'
(expect lines[0][:width]).to eql 0.5
(expect lines[1][:color]).to eql 'AA0000'
(expect lines[1][:width]).to be 2
end
it 'should allow theme to set base text decoration width' do
pdf_theme = {
base_text_decoration_width: 0.5,
role_underline_text_decoration_color: '0000AA',
}
input = '[.underline]#underline#'
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: :line
lines = pdf.lines
(expect lines).to have_size 1
(expect lines[0][:color]).to eql '0000AA'
(expect lines[0][:width]).to eql 0.5
end
it 'should support size roles (big and small) in default theme' do
pdf_theme = build_pdf_theme
(expect pdf_theme.role_big_font_size).to be 13
(expect pdf_theme.role_small_font_size).to be 9
pdf = to_pdf '[.big]#big# and [.small]#small#', pdf_theme: (pdf_theme = build_pdf_theme), analyze: true
text = pdf.text
(expect text).to have_size 3
(expect text[0][:font_size].to_f.round 2).to eql pdf_theme.base_font_size_large.to_f
(expect text[1][:font_size]).to eql pdf_theme.base_font_size
(expect text[2][:font_size].to_f.round 2).to eql pdf_theme.base_font_size_small.to_f
end
it 'should allow theme to override formatting for font size roles' do
pdf_theme = {
role_big_font_size: 12,
role_big_font_style: 'bold',
role_small_font_size: 8,
role_small_font_style: 'italic',
}
pdf = to_pdf '[.big]#big# and [.small]#small#', pdf_theme: pdf_theme, analyze: true
text = pdf.text
(expect text).to have_size 3
(expect text[0][:font_size]).to be 12
(expect text[0][:font_name]).to eql 'NotoSerif-Bold'
(expect text[2][:font_size]).to be 8
(expect text[2][:font_name]).to eql 'NotoSerif-Italic'
end
it 'should support font size roles (big and small) using fallback values if not specified in theme' do
pdf_theme = build_pdf_theme({ base_font_size: 12 }, (fixture_file 'bare-theme.yml'))
pdf = to_pdf '[.big]#big# and [.small]#small#', pdf_theme: pdf_theme, analyze: true
text = pdf.text
(expect text).to have_size 3
(expect text[0][:font_size].to_f.round 2).to eql 14.0
(expect text[1][:font_size]).to be 12
(expect text[2][:font_size].to_f.round 2).to eql 10.0
end
it 'should base font size roles on large and small theme keys if not specified in theme' do
pdf_theme = build_pdf_theme({ base_font_size: 12, base_font_size_large: 18, base_font_size_small: 9 }, (fixture_file 'bare-theme.yml'))
pdf = to_pdf '[.big]#big# and [.small]#small#', pdf_theme: pdf_theme, analyze: true
text = pdf.text
(expect text).to have_size 3
(expect text[0][:font_size].to_f.round 2).to eql 18.0
(expect text[1][:font_size]).to be 12
(expect text[2][:font_size].to_f.round 2).to eql 9.0
end
it 'should support built-in pre-wrap role on phrase' do
pdf = to_pdf <<~'EOS', analyze: true
[.pre-wrap]`0 1 2 3 5`
EOS
shout_text = pdf.text[0]
(expect shout_text[:string]).to eql '0 1 2 3 5'
end
it 'should allow theme to control formatting applied to phrase by role' do
pdf_theme = {
role_red_font_color: 'ff0000',
role_red_font_style: 'bold',
role_blue_font_color: '0000ff',
role_blue_font_style: 'bold_italic',
}
pdf = to_pdf 'Roses are [.red]_red_, violets are [.blue]#blue#.', pdf_theme: pdf_theme, analyze: true
red_text = (pdf.find_text 'red')[0]
blue_text = (pdf.find_text 'blue')[0]
(expect red_text[:font_color]).to eql 'FF0000'
(expect red_text[:font_name]).to eql 'NotoSerif-BoldItalic'
(expect blue_text[:font_color]).to eql '0000FF'
(expect blue_text[:font_name]).to eql 'NotoSerif-BoldItalic'
end
it 'should allow custom role to specify underline text decoration' do
pdf_theme = { role_movie_text_decoration: 'underline' }
input = '[.movie]_2001: A Space Odyssey_'
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: :line
lines = pdf.lines
(expect lines).to have_size 1
underline = lines[0]
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true
text = pdf.text
(expect text).to have_size 1
underlined_text = text[0]
(expect underlined_text[:font_name]).to eql 'NotoSerif-Italic'
(expect underline[:from][:x]).to eql underlined_text[:x]
(expect underline[:from][:y]).to be < underlined_text[:y]
(expect underlined_text[:y] - underline[:from][:y]).to eql 1.25
(expect underlined_text[:font_color]).to eql underline[:color]
(expect underline[:to][:x] - underline[:from][:x]).to be > 100
end
it 'should allow custom role to specify line-through text decoration' do
pdf_theme = { role_delete_text_decoration: 'line-through' }
input = '[.delete]*delete me*'
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: :line
lines = pdf.lines
(expect lines).to have_size 1
underline = lines[0]
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true
text = pdf.text
(expect text).to have_size 1
underlined_text = text[0]
(expect underlined_text[:font_name]).to eql 'NotoSerif-Bold'
(expect underline[:from][:x]).to eql underlined_text[:x]
(expect underline[:from][:y]).to be > underlined_text[:y]
(expect underlined_text[:y] - underline[:from][:y]).to be < 0
(expect underlined_text[:font_color]).to eql underline[:color]
(expect underline[:to][:x] - underline[:from][:x]).to be > 45
end
it 'should allow theme to set text decoration color and width for custom role' do
pdf_theme = {
role_delete_text_decoration: 'line-through',
role_delete_text_decoration_color: 'AA0000',
role_delete_text_decoration_width: 2,
role_important_text_decoration: 'underline',
role_important_text_decoration_color: '0000AA',
role_important_text_decoration_width: 0.5,
}
input = <<~'EOS'
[.important]#important#
[.delete]#delete#
EOS
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: :line
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[0][:color]).to eql '0000AA'
(expect lines[0][:width]).to eql 0.5
(expect lines[1][:color]).to eql 'AA0000'
(expect lines[1][:width]).to be 2
end
it 'should allow custom role to specify font style and text decoration' do
pdf_theme = { role_heavy_text_decoration: 'underline', role_heavy_font_style: 'bold' }
input = '[.heavy]#kick#, bass, and trance'
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: :line
lines = pdf.lines
(expect lines).to have_size 1
underline = lines[0]
pdf = to_pdf input, pdf_theme: pdf_theme, analyze: true
text = pdf.text
(expect text).to have_size 2
underlined_text = text[0]
(expect underlined_text[:font_name]).to eql 'NotoSerif-Bold'
(expect underline[:from][:x]).to eql underlined_text[:x]
end
it 'should allow custom role to apply text transform' do
pdf_theme = {
role_lower_text_transform: 'lowercase',
role_upper_text_transform: 'uppercase',
role_capital_text_transform: 'capitalize',
}
pdf = to_pdf <<~'EOS', pdf_theme: pdf_theme, analyze: true
[.lower]#WHISPER# [.upper]#shout# [.capital]#here me roar#
EOS
lines = pdf.lines
(expect lines).to have_size 1
(expect lines[0]).to eql 'whisper SHOUT Here Me Roar'
end
it 'should allow custom role to apply text transform when it is not the only role on the phrase' do
pdf_theme = {
role_red_font_color: 'FF0000',
role_upper_text_transform: 'uppercase',
}
pdf = to_pdf <<~'EOS', pdf_theme: pdf_theme, analyze: true
[.upper.red]#shout#
EOS
shout_text = pdf.text[0]
(expect shout_text[:font_color]).to eql 'FF0000'
(expect shout_text[:string]).to eql 'SHOUT'
end
it 'should allow custom role to specify relative font size' do
pdf_theme = {
heading_h2_font_size: 24,
codespan_font_size: '0.75em',
role_mono_font_size: '0.875em',
}
pdf = to_pdf <<~'EOS', pdf_theme: pdf_theme, analyze: true
== `MIN` and [.mono]`MAX`
EOS
min_text = (pdf.find_text 'MIN')[0]
normal_text = (pdf.find_text ' and ')[0]
max_text = (pdf.find_text 'MAX')[0]
(expect min_text[:font_size].to_f).to eql 18.0
(expect normal_text[:font_size]).to be 24
(expect max_text[:font_size].to_f).to eql 21.0
end
it 'should add background to link as defined in theme', visual: true do
pdf_theme = {
link_background_color: 'EFEFEF',
link_border_offset: 1,
}
to_file = to_pdf_file 'Check out https://asciidoctor.org[Asciidoctor].', 'text-formatter-link-background.pdf', pdf_theme: pdf_theme
(expect to_file).to visually_match 'text-formatter-link-background.pdf'
end
it 'should allow custom role to override styles of link' do
pdf_theme = {
heading_font_color: '000000',
link_font_color: '0000AA',
role_hlink_font_color: '00AA00',
}
pdf = to_pdf <<~'EOS', pdf_theme: pdf_theme, analyze: true
== https://asciidoctor.org[Asciidoctor,role=hlink]
EOS
link_text = (pdf.find_text 'Asciidoctor')[0]
(expect link_text[:font_color]).to eql '00AA00'
end
it 'should allow custom role to contain hyphens' do
pdf_theme = {
'role_flaming-red_font_color' => 'ff0000',
'role_so-very-blue_font_color' => '0000ff',
}
pdf = to_pdf 'Roses are [.flaming-red]_red_, violets are [.so-very-blue]#blue#.', pdf_theme: pdf_theme, analyze: true
red_text = (pdf.find_text 'red')[0]
blue_text = (pdf.find_text 'blue')[0]
(expect red_text[:font_color]).to eql 'FF0000'
(expect blue_text[:font_color]).to eql '0000FF'
end
it 'should append font style configured for role to current style' do
pdf_theme = {
role_quick_font_style: 'italic',
}
pdf = to_pdf '*That was [.quick]#quick#.*', pdf_theme: pdf_theme, analyze: true
glorious_text = (pdf.find_text 'quick')[0]
(expect glorious_text[:font_name]).to eql 'NotoSerif-BoldItalic'
end
it 'should allow role to reset font style to normal' do
pdf_theme = {
role_normal_font_style: 'normal',
}
pdf = to_pdf '*Make it [.normal]#plain#.*', pdf_theme: pdf_theme, analyze: true
glorious_text = (pdf.find_text 'plain')[0]
(expect glorious_text[:font_name]).to eql 'NotoSerif'
end
it 'should allow role to set font style to italic inside bold text' do
pdf_theme = {
role_term_font_style: 'normal_italic',
}
pdf = to_pdf '*We call that [.term]#intersectional#.*', pdf_theme: pdf_theme, analyze: true
glorious_text = (pdf.find_text 'intersectional')[0]
(expect glorious_text[:font_name]).to eql 'NotoSerif-Italic'
end
it 'should support theming multiple roles on a single phrase' do
pdf_theme = {
role_bold_font_style: 'bold',
role_italic_font_style: 'italic',
role_blue_font_color: '0000ff',
role_mono_font_family: 'Courier',
role_tiny_font_size: 8,
}
pdf = to_pdf '[.bold.italic.blue.mono.tiny]#text#', pdf_theme: pdf_theme, analyze: true
formatted_text = (pdf.find_text 'text')[0]
(expect formatted_text[:font_name]).to eql 'Courier-BoldOblique'
(expect formatted_text[:font_color]).to eql '0000FF'
(expect formatted_text[:font_size]).to be 8
end
it 'should allow styles from role to override default styles for element' do
pdf_theme = {
role_blue_font_color: '0000ff',
}
pdf = to_pdf '[.blue]`text`', pdf_theme: pdf_theme, analyze: true
formatted_text = (pdf.find_text 'text')[0]
(expect formatted_text[:font_name]).to eql 'mplus1mn-regular'
(expect formatted_text[:font_color]).to eql '0000FF'
end
it 'should allow role to set font style back to normal' do
pdf_theme = {
role_normal_font_style: 'normal',
}
pdf = to_pdf '[.normal]_text_', pdf_theme: pdf_theme, analyze: true
formatted_text = (pdf.find_text 'text')[0]
(expect formatted_text[:font_name]).to eql 'NotoSerif'
end
it 'should allow theme to set background and border for custom role', visual: true do
pdf_theme = {
role_variable_font_family: 'Courier',
role_variable_font_size: '1.15em',
role_variable_font_color: 'FFFFFF',
role_variable_background_color: 'CF2974',
role_variable_border_color: '222222',
role_variable_border_offset: 2,
role_variable_border_radius: 2,
role_variable_border_width: 1,
}
to_file = to_pdf_file 'reads value from the [.variable]#counter# variable', 'text-formatter-inline-role-bg.pdf', pdf_theme: pdf_theme
(expect to_file).to visually_match 'text-formatter-inline-role-bg.pdf'
end
it 'should allow theme to set only border for custom role', visual: true do
pdf_theme = {
role_cmd_font_family: 'Courier',
role_cmd_font_size: '1.15em',
role_cmd_border_color: '222222',
role_cmd_border_width: 0.5,
}
to_file = to_pdf_file 'use the [.cmd]#man# command to get help', 'text-formatter-inline-role-border.pdf', pdf_theme: pdf_theme
(expect to_file).to visually_match 'text-formatter-inline-role-border.pdf'
end
it 'should support role that sets font color in section title and toc' do
pdf_theme = {
role_red_font_color: 'FF0000',
role_blue_font_color: '0000FF',
}
pdf = to_pdf <<~'EOS', analyze: true, pdf_theme: pdf_theme
= Document Title
:doctype: book
:notitle:
:toc:
== [.red]#Red Chapter#
== [.blue]#Blue Chapter#
== Default Chapter
EOS
red_section_text = pdf.find_text 'Red Chapter'
blue_section_text = pdf.find_text 'Blue Chapter'
default_section_text = pdf.find_text 'Default Chapter'
(expect red_section_text).to have_size 2
(expect red_section_text[0][:page_number]).to be 1
(expect red_section_text[0][:font_color]).to eql 'FF0000'
(expect red_section_text[1][:page_number]).to be 2
(expect red_section_text[1][:font_color]).to eql 'FF0000'
(expect blue_section_text).to have_size 2
(expect blue_section_text[0][:page_number]).to be 1
(expect blue_section_text[0][:font_color]).to eql '0000FF'
(expect blue_section_text[1][:page_number]).to be 3
(expect blue_section_text[1][:font_color]).to eql '0000FF'
(expect default_section_text).to have_size 2
(expect default_section_text[0][:page_number]).to be 1
(expect default_section_text[0][:font_color]).to eql '333333'
(expect default_section_text[1][:page_number]).to be 4
(expect default_section_text[1][:font_color]).to eql '333333'
end
end
describe 'typographic quotes' do
it 'should use double curved quotes by default' do
pdf = to_pdf '"`Double quoted`"', analyze: true
(expect pdf.text[0][:string]).to eql %(\u201cDouble quoted\u201d)
end
it 'should use single curved quotes by default' do
pdf = to_pdf '\'`Single quoted`\'', analyze: true
(expect pdf.text[0][:string]).to eql %(\u2018Single quoted\u2019)
end
it 'should use user-defined double quotation marks if specified' do
pdf_theme = { quotes: %w(« ») }
pdf = to_pdf '"`Double quoted`"', pdf_theme: pdf_theme, analyze: true
(expect pdf.text[0][:string]).to eql %(\u00abDouble quoted\u00bb)
end
it 'should use user-defined single quotation marks if specified' do
pdf_theme = { quotes: %w(« » ‹ ›) }
pdf = to_pdf '\'`Single quoted`\'', pdf_theme: pdf_theme, analyze: true
(expect pdf.text[0][:string]).to eql %(\u2039Single quoted\u203a)
end
it 'should use single curved quotes by default if theme only specifies double quotation marks' do
pdf_theme = { quotes: %w(« ») }
pdf = to_pdf '\'`Single quoted`\'', pdf_theme: pdf_theme, analyze: true
(expect pdf.text[0][:string]).to eql %(\u2018Single quoted\u2019)
end
it 'should not use the closing single quotation mark as apostrophe' do
pdf_theme = { quotes: %w(« » ‹ ›) }
pdf = to_pdf <<~'EOS', pdf_theme: pdf_theme, analyze: true
Apostrophes`' substitution shouldn`'t match '`single quoted`'
EOS
(expect pdf.text[0][:string]).to eql %(Apostrophes\u2019 substitution shouldn\u2019t match \u2039single quoted\u203a)
end
it 'should use user-defined quotation marks in the TOC' do
pdf_theme = { quotes: %w(« » ‹ ›) }
pdf = to_pdf <<~'EOS', pdf_theme: pdf_theme, analyze: true
= Document '`Title`'
:doctype: book
:toc:
== "`Double Quoted`"
== '`Single Quoted`'
EOS
(expect (pdf.find_text %(Document \u2039Title\u203a))).to have_size 1
(expect (pdf.find_text %(\u00abDouble Quoted\u00bb))).to have_size 2
(expect (pdf.find_text %(\u2039Single Quoted\u203a))).to have_size 2
end
it 'should keep closing double quote attached to trailing ellipsis' do
pdf = to_pdf <<~EOS, analyze: true
#{(['filler'] * 15).join ' '} ||||| "`and then...`"
EOS
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[1]).to start_with 'then'
end
it 'should keep closing single quote attached to trailing ellipsis' do
pdf = to_pdf <<~EOS, analyze: true
#{(['filler'] * 15).join ' '} .|||||. '`and then...`'
EOS
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[1]).to start_with 'then'
end
end
end