diff --git a/lib/prawn/font_metric_cache.rb b/lib/prawn/font_metric_cache.rb index a72d614ac..1b2e2faf7 100644 --- a/lib/prawn/font_metric_cache.rb +++ b/lib/prawn/font_metric_cache.rb @@ -37,9 +37,10 @@ def width_of(string, options) length = @cache[key] - length + + (length + (@document.character_spacing * - @document.font.character_count(encoded_string)) + @document.font.character_count(encoded_string))) * + (@document.horizontal_text_scaling / 100.0) end end end diff --git a/lib/prawn/text/box.rb b/lib/prawn/text/box.rb index ecbeaeeeb..c811f6672 100644 --- a/lib/prawn/text/box.rb +++ b/lib/prawn/text/box.rb @@ -40,6 +40,9 @@ module Text # :character_spacing:: number. The amount of space to add # to or remove from the default character # spacing. [0] + # :horizontal_text_scaling:: number. The amount as a + # percentage by which to scale the text + # horizontally. [100] # :disable_wrap_by_char:: boolean Whether # or not to prevent mid-word breaks when text does not fit in box. [false] # :mode:: symbol. The text rendering mode. See diff --git a/lib/prawn/text/formatted/arranger.rb b/lib/prawn/text/formatted/arranger.rb index 7c7b2c630..9c5fd0926 100644 --- a/lib/prawn/text/formatted/arranger.rb +++ b/lib/prawn/text/formatted/arranger.rb @@ -156,25 +156,31 @@ def apply_font_settings(fragment = nil, &block) size = current_format_state[:size] character_spacing = current_format_state[:character_spacing] || @document.character_spacing + horizontal_text_scaling = + current_format_state[:horizontal_text_scaling] || + @document.horizontal_text_scaling styles = current_format_state[:styles] else font = fragment.font size = fragment.size character_spacing = fragment.character_spacing + horizontal_text_scaling = fragment.horizontal_text_scaling styles = fragment.styles end font_style = font_style(styles) @document.character_spacing(character_spacing) do - if font || font_style != :normal - raise 'Bad font family' unless @document.font.family - @document.font( - font || @document.font.family, style: font_style - ) do + @document.horizontal_text_scaling(horizontal_text_scaling) do + if font || font_style != :normal + raise 'Bad font family' unless @document.font.family + @document.font( + font || @document.font.family, style: font_style + ) do + apply_font_size(size, styles, &block) + end + else apply_font_size(size, styles, &block) end - else - apply_font_size(size, styles, &block) end end end diff --git a/lib/prawn/text/formatted/box.rb b/lib/prawn/text/formatted/box.rb index 620cdf95e..4bf651561 100644 --- a/lib/prawn/text/formatted/box.rb +++ b/lib/prawn/text/formatted/box.rb @@ -33,6 +33,9 @@ module Formatted # :character_spacing:: # a number denoting how much to increase or decrease the default # spacing between characters + # :horizontal_text_scaling:: + # The amount as a percentage by which to scale the text + # horizontally. [100] # :font:: # the name of a font. The name must be an AFM font with the desired # faces or must be a font that is already registered using @@ -166,6 +169,8 @@ def initialize(formatted_text, options = {}) @leading = options[:leading] || @document.default_leading @character_spacing = options[:character_spacing] || @document.character_spacing + @horizontal_text_scaling = options[:horizontal_text_scaling] || + @document.horizontal_text_scaling @mode = options[:mode] || @document.text_rendering_mode @rotate = options[:rotate] || 0 @rotate_around = options[:rotate_around] || :upper_left @@ -213,21 +218,23 @@ def render(flags = {}) @document.save_font do @document.character_spacing(@character_spacing) do - @document.text_rendering_mode(@mode) do - process_options - - text = normalized_text(flags) - - @document.font_size(@font_size) do - shrink_to_fit(text) if @overflow == :shrink_to_fit - process_vertical_alignment(text) - @inked = true unless flags[:dry_run] - unprinted_text = if @rotate != 0 && @inked - render_rotated(text) - else - wrap(text) - end - @inked = false + @document.horizontal_text_scaling(@horizontal_text_scaling) do + @document.text_rendering_mode(@mode) do + process_options + + text = normalized_text(flags) + + @document.font_size(@font_size) do + shrink_to_fit(text) if @overflow == :shrink_to_fit + process_vertical_alignment(text) + @inked = true unless flags[:dry_run] + unprinted_text = if @rotate != 0 && @inked + render_rotated(text) + else + wrap(text) + end + @inked = false + end end end end @@ -350,7 +357,7 @@ def valid_options :rotate, :rotate_around, :overflow, :min_font_size, :disable_wrap_by_char, - :leading, :character_spacing, + :leading, :character_spacing, :horizontal_text_scaling, :mode, :single_line, :document, :direction, diff --git a/lib/prawn/text/formatted/fragment.rb b/lib/prawn/text/formatted/fragment.rb index c10ecc17f..3e46c06c8 100644 --- a/lib/prawn/text/formatted/fragment.rb +++ b/lib/prawn/text/formatted/fragment.rb @@ -111,6 +111,11 @@ def character_spacing @document.character_spacing end + def horizontal_text_scaling + @format_state[:horizontal_text_scaling] || + @document.horizontal_text_scaling + end + def direction @format_state[:direction] end diff --git a/lib/prawn/text/formatted/parser.rb b/lib/prawn/text/formatted/parser.rb index e5823b969..5bea4aa53 100644 --- a/lib/prawn/text/formatted/parser.rb +++ b/lib/prawn/text/formatted/parser.rb @@ -70,8 +70,15 @@ def self.to_string(array) else character_spacing = nil end - if font || size || character_spacing - prefix += "" + if hash[:horizontal_text_scaling] + horizontal_text_scaling = + " horizontal_text_scaling='#{hash[:horizontal_text_scaling]}'" + else + horizontal_text_scaling = nil + end + if font || size || character_spacing || horizontal_text_scaling + prefix += "" suffix = '' end @@ -133,6 +140,7 @@ def self.array_from_tokens(tokens) fonts = [] sizes = [] character_spacings = [] + horizontal_text_scalings = [] tokens.each do |token| case token @@ -170,6 +178,7 @@ def self.array_from_tokens(tokens) fonts.pop sizes.pop character_spacings.pop + horizontal_text_scalings.pop when /^]*>$/, /^]*>$/ matches = /href="([^"]*)"/.match(token) || /href='([^']*)'/.match(token) @@ -220,6 +229,10 @@ def self.array_from_tokens(tokens) matches = /character_spacing="([^"]*)"/.match(token) || /character_spacing='([^']*)'/.match(token) character_spacings << matches[1].to_f unless matches.nil? + + matches = /horizontal_text_scaling="([^"]*)"/.match(token) || + /horizontal_text_scaling='([^']*)'/.match(token) + horizontal_text_scalings << matches[1].to_f unless matches.nil? else string = token.gsub('<', '<').gsub('>', '>') .gsub('&', '&') @@ -232,7 +245,8 @@ def self.array_from_tokens(tokens) anchor: anchor, font: fonts.last, size: sizes.last, - character_spacing: character_spacings.last + character_spacing: character_spacings.last, + horizontal_text_scaling: horizontal_text_scalings.last } end end diff --git a/prawn.gemspec b/prawn.gemspec index df3aad3f4..9bef38796 100644 --- a/prawn.gemspec +++ b/prawn.gemspec @@ -39,7 +39,7 @@ Gem::Specification.new do |spec| spec.add_dependency('ttfunk', '~> 1.5') spec.add_dependency('pdf-core', '~> 0.8.1') - spec.add_development_dependency('pdf-inspector', '>= 1.2.1', '< 2.0.a') + spec.add_development_dependency('pdf-inspector', '>= 1.3.0', '< 2.0.a') spec.add_development_dependency('yard') spec.add_development_dependency('rspec', '~> 3.0') spec.add_development_dependency('rake', '~> 12.0') diff --git a/spec/prawn/document_spec.rb b/spec/prawn/document_spec.rb index ed900767d..d23ead01e 100644 --- a/spec/prawn/document_spec.rb +++ b/spec/prawn/document_spec.rb @@ -46,7 +46,8 @@ def self.format(string) local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil } ] end @@ -798,4 +799,36 @@ def self.format(string) .to eq([1, 3, 6, 9]) end end + + describe '#horizontal_text_scaling' do + it 'draws the horizontal text scaling to the document' do + pdf.horizontal_text_scaling(110) do + pdf.text('hello world') + end + contents = PDF::Inspector::Text.analyze(pdf.render) + expect(contents.horizontal_text_scaling.first).to eq(110) + end + it 'should not draw the horizontal text scaling to the document' \ + ' when the new horizontal text scaling matches the old' do + pdf.horizontal_text_scaling(100) do + pdf.text('hello world') + end + contents = PDF::Inspector::Text.analyze(pdf.render) + expect(contents.horizontal_text_scaling).to be_empty + end + it 'restores horizontal text scaling to 100' do + pdf.horizontal_text_scaling(110) do + pdf.text('hello world') + end + contents = PDF::Inspector::Text.analyze(pdf.render) + expect(contents.horizontal_text_scaling.last).to eq(100) + end + it 'functions as an accessor when no parameter given' do + pdf.horizontal_text_scaling(110) do + pdf.text('hello world') + expect(pdf.horizontal_text_scaling).to eq(110) + end + expect(pdf.horizontal_text_scaling).to eq(100) + end + end end diff --git a/spec/prawn/font_spec.rb b/spec/prawn/font_spec.rb index fe43167b1..cec75706b 100644 --- a/spec/prawn/font_spec.rb +++ b/spec/prawn/font_spec.rb @@ -45,6 +45,13 @@ end end + it 'takes horizontal text scaling into account' do + original_width = pdf.width_of('hello world') + pdf.horizontal_text_scaling(110) do + expect(pdf.width_of('hello world')).to eq(original_width * 1.1) + end + end + it 'excludes newlines' do # Use a TTF font that has a non-zero width for \n pdf.font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf") diff --git a/spec/prawn/text/box_spec.rb b/spec/prawn/text/box_spec.rb index 694058e0f..e9bf8ec1c 100644 --- a/spec/prawn/text/box_spec.rb +++ b/spec/prawn/text/box_spec.rb @@ -904,6 +904,29 @@ end end + describe '#render with :horizontal_text_scaling option' do + it 'draws the horizontal text scaling to the document' do + string = 'hello world' + options = { document: pdf, horizontal_text_scaling: 110 } + text_box = described_class.new(string, options) + text_box.render + contents = PDF::Inspector::Text.analyze(pdf.render) + expect(contents.horizontal_text_scaling[0]).to eq(110) + end + it 'takes horizontal text scaling into account when wrapping' do + pdf.font 'Courier' + text_box = described_class.new( + 'hello world', + width: 100, + overflow: :expand, + horizontal_text_scaling: 150, + document: pdf + ) + text_box.render + expect(text_box.text).to eq("hello\nworld") + end + end + describe 'wrapping' do it 'wraps text' do text = 'Please wrap this text about HERE. ' \ diff --git a/spec/prawn/text/formatted/arranger_spec.rb b/spec/prawn/text/formatted/arranger_spec.rb index be514da7b..1ce9551b9 100644 --- a/spec/prawn/text/formatted/arranger_spec.rb +++ b/spec/prawn/text/formatted/arranger_spec.rb @@ -348,6 +348,63 @@ end end + describe '#line_width with horizontal_text_scaling > 100' do + it 'returns a width greater than a line without a '\ + 'horizontal_text_scaling' do + array = [ + { text: 'hello ' }, + { text: 'world', styles: [:bold] } + ] + arranger.format_array = array + while arranger.next_string + end + arranger.finalize_line + + base_line_width = arranger.line_width + + array = [ + { text: 'hello ' }, + { + text: 'world', styles: [:bold], + horizontal_text_scaling: 110 + } + ] + arranger.format_array = array + while arranger.next_string + end + arranger.finalize_line + expect(arranger.line_width).to be > base_line_width + end + end + + describe '#line_width with horizontal_text_scaling < 100' do + it 'returns a width less than a line without a horizontal_text_scaling' do + array = [ + { text: 'hello ' }, + { text: 'world', styles: [:bold] } + ] + arranger.format_array = array + while arranger.next_string + end + arranger.finalize_line + + base_line_width = arranger.line_width + + array = [ + { text: 'hello ' }, + { + text: 'world', styles: [:bold], + horizontal_text_scaling: 90 + } + ] + arranger.format_array = array + while arranger.next_string + end + arranger.finalize_line + expect(arranger.line_width).to be < base_line_width + end + end + describe '#line' do before do array = [ diff --git a/spec/prawn/text/formatted/box_spec.rb b/spec/prawn/text/formatted/box_spec.rb index cd3512ee1..bf16815cf 100644 --- a/spec/prawn/text/formatted/box_spec.rb +++ b/spec/prawn/text/formatted/box_spec.rb @@ -729,6 +729,37 @@ end end + describe 'Text::Formatted::Box#render with fragment level '\ + ':horizontal_text_scaling option' do + it 'draws the horizontal text scaling to the document' do + array = [{ + text: 'hello world', + horizontal_text_scaling: 110 + }] + options = { document: pdf } + text_box = described_class.new(array, options) + text_box.render + contents = PDF::Inspector::Text.analyze(pdf.render) + expect(contents.horizontal_text_scaling[0]).to eq(110) + end + + it 'lays out text properly' do + array = [{ + text: 'hello world', + font: 'Courier', + horizontal_text_scaling: 150 + }] + options = { + document: pdf, + width: 100, + overflow: :expand + } + text_box = described_class.new(array, options) + text_box.render + expect(text_box.text).to eq("hello\nworld") + end + end + describe 'Text::Formatted::Box#render with :align => :justify' do it 'does not justify the last line of a paragraph' do array = [ diff --git a/spec/prawn/text/formatted/parser_spec.rb b/spec/prawn/text/formatted/parser_spec.rb index 8312d9aeb..e8e33d230 100644 --- a/spec/prawn/text/formatted/parser_spec.rb +++ b/spec/prawn/text/formatted/parser_spec.rb @@ -16,7 +16,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles sub' do @@ -31,7 +32,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles rgb' do @@ -46,7 +48,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it '# should be optional in rgb' do @@ -61,7 +64,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles cmyk' do @@ -76,7 +80,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles fonts' do @@ -91,7 +96,8 @@ local: nil, font: 'Courier', size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles size' do @@ -106,7 +112,8 @@ local: nil, font: nil, size: 14, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles character_spacing' do @@ -121,7 +128,25 @@ local: nil, font: nil, size: nil, - character_spacing: 2.5 + character_spacing: 2.5, + horizontal_text_scaling: nil + ) + end + it 'handles horizontal_text_scaling' do + string = "extra horizontal text "\ + 'scaling' + array = described_class.format(string) + expect(array[0]).to eq( + text: 'extra horizontal text scaling', + styles: [], + color: nil, + link: nil, + anchor: nil, + local: nil, + font: nil, + size: nil, + character_spacing: nil, + horizontal_text_scaling: 125 ) end it 'handles links' do @@ -136,7 +161,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles local links' do @@ -151,7 +177,8 @@ local: '/home/example/foo.bar', font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles anchors' do @@ -166,7 +193,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles higher order characters properly' do @@ -181,7 +209,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[1]).to eq( text: "\n", @@ -192,7 +221,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[2]).to eq( text: '©', @@ -203,7 +233,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'converts < >, and & to <, >, and &, respectively' do @@ -218,7 +249,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles double qoutes around tag attributes' do @@ -233,7 +265,8 @@ local: nil, font: nil, size: 14, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'handles single qoutes around tag attributes' do @@ -248,7 +281,8 @@ local: nil, font: nil, size: 14, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'constructs a formatted text array from a string' do @@ -264,7 +298,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[1]).to eq( text: 'world', @@ -275,7 +310,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[2]).to eq( text: "\n", @@ -286,7 +322,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[3]).to eq( text: 'how ', @@ -297,7 +334,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[4]).to eq( text: 'are', @@ -308,7 +346,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[5]).to eq( text: ' you?', @@ -319,7 +358,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'accepts as an alternative to ' do @@ -335,7 +375,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[1]).to eq( text: ' not bold', @@ -346,7 +387,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'accepts as an alternative to ' do @@ -362,7 +404,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[1]).to eq( text: ' not italic', @@ -373,7 +416,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end it 'accepts as an alternative to ' do @@ -389,7 +433,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) expect(array[1]).to eq( text: ' not a link', @@ -400,7 +445,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil ) end @@ -422,7 +468,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] expect(described_class.to_string(array)).to eq(string) end @@ -437,7 +484,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] expect(described_class.to_string(array)).to eq(string) end @@ -452,7 +500,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] expect(described_class.to_string(array)).to eq(string) end @@ -467,7 +516,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] expect(described_class.to_string(array)).to eq(string) end @@ -482,7 +532,8 @@ local: nil, font: 'Courier', size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] expect(described_class.to_string(array)).to eq(string) end @@ -497,7 +548,8 @@ local: nil, font: nil, size: 14, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] expect(described_class.to_string(array)).to eq(string) end @@ -513,7 +565,25 @@ local: nil, font: nil, size: nil, - character_spacing: 2.5 + character_spacing: 2.5, + horizontal_text_scaling: nil + }] + expect(described_class.to_string(array)).to eq(string) + end + it 'handles horizontal text scaling' do + string = "125 extra horizontal text "\ + 'scaling' + array = [{ + text: '125 extra horizontal text scaling', + styles: [], + color: nil, + link: nil, + anchor: nil, + local: nil, + font: nil, + size: nil, + character_spacing: nil, + horizontal_text_scaling: 125 }] expect(described_class.to_string(array)).to eq(string) end @@ -527,7 +597,8 @@ local: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] string = "external link" expect(described_class.to_string(array)).to eq(string) @@ -541,7 +612,8 @@ anchor: 'ToC', font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }] string = "internal link" expect(described_class.to_string(array)).to eq(string) @@ -555,7 +627,8 @@ link: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }, { text: '<, >, and &', @@ -564,7 +637,8 @@ link: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil } ] string = 'hello <, >, and &' @@ -579,7 +653,8 @@ link: nil, font: nil, size: 14, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }, { text: 'world', @@ -588,7 +663,8 @@ link: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }, { text: "\n", @@ -597,7 +673,8 @@ link: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }, { text: 'how ', @@ -606,7 +683,8 @@ link: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }, { text: 'are', @@ -615,7 +693,8 @@ link: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil }, { text: ' you?', @@ -624,7 +703,8 @@ link: nil, font: nil, size: nil, - character_spacing: nil + character_spacing: nil, + horizontal_text_scaling: nil } ] string = "hello world\n"\