diff --git a/lib/prawn/document.rb b/lib/prawn/document.rb index 439667d5a..f12983450 100644 --- a/lib/prawn/document.rb +++ b/lib/prawn/document.rb @@ -218,6 +218,7 @@ def initialize(options = {}, &block) @background = options[:background] @background_scale = options[:background_scale] || 1 + @font = nil @font_size = 12 @bounding_box = nil diff --git a/lib/prawn/font.rb b/lib/prawn/font.rb index 6eb4dac94..0959ea355 100644 --- a/lib/prawn/font.rb +++ b/lib/prawn/font.rb @@ -47,14 +47,15 @@ class Document # font files. # See font_families for more information. # - def font(name = nil, options = DEFAULT_OPTS) - return((defined?(@font) && @font) || font('Helvetica')) if name.nil? + def font(name = nil, options = nil) + return @font || font('Helvetica') if name.nil? && options.nil? if state.pages.empty? && !state.page.in_stamp_stream? raise Prawn::Errors::NotOnPage end - new_font = find_font(name.to_s, options) + options ||= DEFAULT_OPTS + new_font = find_font(name, options) if block_given? save_font do @@ -68,6 +69,54 @@ def font(name = nil, options = DEFAULT_OPTS) @font end + # Returns the family name of the current font, if the font was selected using + # a font family name, or nil, if the font was specified as a specific built-in + # font or a TTF file. + def font_family + @font ? @font.family : 'Helvetica' + end + + # @method font_style(points=nil) + # + # When called with no argument, returns the current font style. + # + # When called with a single argument but no block, sets the current font + # style. When a block is used, the font style is applied transactionally and + # is rolled back when the block exits. You may still change the font style + # within a transactional block for individual text segments, or nested calls + # to font_style. + # + # Prawn::Document.generate("font_style.pdf") do + # font_style :bold + # text "Bold text" + # + # font_style(:italic) do + # text "Italic text" + # text "Bold text", :style => :bold + # text "Italic text" + # end + # + # text "Bold text" + # end + # + # Font styles are not additive, i.e. if the current style is :bold, setting + # the style to :italic results in italic, not bold-italic text. Use + # :bold_italic instead. + # + # Font style can only be applied, if the current font is specified using a + # font family name, not a specific built-in font or TTF file. + # + def font_style(style = nil, &block) + return @font ? @font.style : :normal if style.nil? + + font(nil, style: style, &block) + end + + # Sets the font style + def font_style=(style = nil) + font_style(style) + end + # @method font_size(points=nil) # # When called with no argument, returns the current font size. @@ -91,9 +140,6 @@ def font(name = nil, options = DEFAULT_OPTS) # text "At size 16" # end # - # When called without an argument, this method returns the current font - # size. - # def font_size(points = nil) return @font_size unless points @@ -237,13 +283,22 @@ def save_font # # @private def find_font(name, options = {}) #:nodoc: + name ||= font_family || font.name + style = options[:style] || :normal if font_families.key?(name) family = name - name = font_families[name][options[:style] || :normal] + if !font_families[family].key?(style) + raise Prawn::Errors::UnknownFont, + "Font family `#{family}` has no `:#{style}` style." + end + name = font_families[family][style] if name.is_a?(::Hash) options = options.merge(name) name = options[:file] end + elsif style != :normal + options[:style] = :normal + warn "Style not supported for `#{name}`." end key = "#{name}:#{options[:font] || 0}" @@ -299,6 +354,9 @@ class Font # The current font family attr_reader :family + # The current font style + attr_reader :style + # The options hash used to initialize the font attr_reader :options @@ -334,6 +392,7 @@ def initialize(document, name, options = {}) #:nodoc: @options = options @family = options[:family] + @style = options[:style] || :normal @identifier = generate_unique_id diff --git a/lib/prawn/font_metric_cache.rb b/lib/prawn/font_metric_cache.rb index 1f49f8a53..2db8b16c2 100644 --- a/lib/prawn/font_metric_cache.rb +++ b/lib/prawn/font_metric_cache.rb @@ -25,7 +25,7 @@ def width_of(string, options) f = if options[:style] # override style with :style => :bold - @document.find_font(@document.font.family, style: options[:style]) + @document.find_font(nil, style: options[:style]) else @document.font end diff --git a/lib/prawn/fonts/afm.rb b/lib/prawn/fonts/afm.rb index dabd772b9..8d7795858 100644 --- a/lib/prawn/fonts/afm.rb +++ b/lib/prawn/fonts/afm.rb @@ -55,7 +55,7 @@ def initialize(document, name, options = {}) #:nodoc: name ||= options[:family] unless BUILT_INS.include?(name) raise Prawn::Errors::UnknownFont, - "#{name} (#{options[:style] || 'normal'}) is not a known font." + "`#{name}` is not a known font." end super diff --git a/manual/text/font.rb b/manual/text/font.rb index e4b353f5b..97bc4f530 100644 --- a/manual/text/font.rb +++ b/manual/text/font.rb @@ -5,11 +5,12 @@ # If we don't pass it any arguments it will return the current font being used # to render text. # -# If we just pass it a font name it will use that font for rendering text -# through the rest of the document. +# If we just pass it a font name and an options hash, it will use that font for +# rendering text through the rest of the document. Both the font name and +# the options can be omitted. Valid option keys are :style and :size. # -# It can also be used by passing a font name and a block. In this case the -# specified font will only be used to render text inside the block. +# It can also be used by passing a font name, an options hash and a block. In this +# case the specified font will only be used to render text inside the block. # # The default font is Helvetica. @@ -18,6 +19,7 @@ filename = File.basename(__FILE__).gsub('.rb', '.pdf') Prawn::ManualBuilder::Example.generate(filename) do text "Let's see which font we are using: #{font.inspect}" + text "Family is #{font_family}, style is #{font_style}, and size is #{font_size}" move_down 20 font 'Times-Roman' @@ -31,6 +33,11 @@ move_down 20 text 'Written in Times again as we left the previous block.' + move_down 20 + font nil, size: 16, style: :bold do + text 'Written in 16pt bold, still Times.' + end + move_down 20 text "Let's see which font we are using again: #{font.inspect}" diff --git a/manual/text/font_style.rb b/manual/text/font_style.rb index c6fd844e2..19f8cc5aa 100644 --- a/manual/text/font_style.rb +++ b/manual/text/font_style.rb @@ -1,25 +1,51 @@ # frozen_string_literal: true +# The font_style method works just like the font +# method. +# +# In fact we can even use font with the :style option +# to declare which size we want. +# +# Another way to change the font size is by supplying the :style +# option to the text methods. +# # Most font families come with some styles other than normal. Most common are # bold, italic and bold_italic. -# -# The style can be set the using the :style option, with either the -# font method which will set the font and style for rest of the -# document, or with the inline text methods. require_relative '../example_helper' filename = File.basename(__FILE__).gsub('.rb', '.pdf') Prawn::ManualBuilder::Example.generate(filename) do - fonts = %w[Courier Helvetica Times-Roman] - styles = %i[bold bold_italic italic normal] + text "The default style is normal" + + move_down 10 + font_style :bold + text 'This is bold' - fonts.each do |example_font| - move_down 20 + move_down 10 + font_style :italic + text 'This is italic (not bold, i.e. existing style is overwritten)' - styles.each do |style| - font example_font, style: style - text "I'm writing in #{example_font} (#{style})" - end + move_down 10 + font_style :bold_italic + text 'This is bold italic' + + move_down 10 + font_style :normal + text 'Back to normal' + + move_down 10 + text 'A single line of italic', style: :italic + + move_down 10 + font_style :bold do + text 'A single line of bold' end + + move_down 10 + font 'Courier', style: :bold_italic + text 'This is Courier bold italic' + + font 'Courier' + text 'This is Courier normal (style is reset when changing font)' end diff --git a/spec/prawn/font_spec.rb b/spec/prawn/font_spec.rb index 60ed4ebf0..53e920aa6 100644 --- a/spec/prawn/font_spec.rb +++ b/spec/prawn/font_spec.rb @@ -71,12 +71,19 @@ end end - it 'reports missing font with style' do + it 'raises on missing style in family' do expect do - pdf.font('Nada', style: :bold) do - pdf.width_of('hello') + pdf.font('Helvetica') do + pdf.width_of('hello', style: :heavy) end - end.to raise_error(Prawn::Errors::UnknownFont, /Nada \(bold\)/) + end.to raise_error(Prawn::Errors::UnknownFont, 'Font family `Helvetica` has no `:heavy` style.') + end + + it 'warns on style with single font' do + expect(pdf).to receive(:warn).with('Style not supported for `Courier-Bold`.').once + pdf.font('Courier-Bold') do + pdf.width_of('hello', style: :bold) + end end it 'calculates styled widths correctly using TTFs' do @@ -111,11 +118,153 @@ end end + describe '#font' do + it 'allows setting of size directly when font is created' do + pdf.font 'Courier', size: 16 #, style: :bold + expect(pdf.font_size).to eq(16) + end + + it 'allows temporary setting of a new font using a transaction' do + pdf.font 'Helvetica', size: 12 + + pdf.font 'Courier', size: 16, style: :bold do + expect(pdf.font_family).to eq('Courier') + expect(pdf.font_size).to eq(16) + expect(pdf.font_style).to eq(:bold) + end + + expect(pdf.font.name).to eq('Helvetica') + expect(pdf.font_size).to eq(12) + expect(pdf.font_style).to eq(:normal) + end + + it 'masks font size when using a transacation' do + pdf.font 'Courier', size: 16 do + expect(pdf.font_size).to eq(16) + end + + pdf.font 'Times-Roman' + pdf.font 'Courier' + + expect(pdf.font_size).to eq(12) + end + + it 'raises on unknown font' do + expect do + pdf.font 'Arial Black' + end.to raise_error(Prawn::Errors::UnknownFont, '`Arial Black` is not a known font.') + end + + it 'raises on missing style in family' do + expect do + pdf.font 'Helvetica', style: :heavy + end.to raise_error(Prawn::Errors::UnknownFont, 'Font family `Helvetica` has no `:heavy` style.') + end + + it 'warns on style with single font' do + expect(pdf).to receive(:warn).with('Style not supported for `Courier-Bold`.').once + pdf.font 'Courier-Bold', style: :bold + expect(pdf.font.name).to eq('Courier-Bold') + expect(pdf.font_style).to eq(:normal) + end + end + describe '#font_size' do + it 'returns default font size' do + expect(pdf.font_size).to eq(12) + end + it 'allows setting font size in DSL style' do pdf.font_size 20 expect(pdf.font_size).to eq(20) end + + it 'allows setting font size in DSL style using a transaction' do + pdf.font_size 20 do + expect(pdf.font_size).to eq(20) + end + + expect(pdf.font_size).to eq(12) + end + + it 'allows setting font size as assignment' do + pdf.font_size = 20 + expect(pdf.font_size).to eq(20) + end + end + + describe '#font_style' do + it 'returns default font style' do + expect(pdf.font_style).to eq(:normal) + end + + it 'allows setting font style in DSL style' do + pdf.font_style :bold + expect(pdf.font_style).to eq(:bold) + expect(pdf.font.name).to eq('Helvetica-Bold') + end + + it 'allows setting font style in DSL style using a transaction' do + pdf.font_style :bold do + expect(pdf.font_style).to eq(:bold) + expect(pdf.font.name).to eq('Helvetica-Bold') + end + + expect(pdf.font_style).to eq(:normal) + expect(pdf.font.name).to eq('Helvetica') + end + + it 'allows setting font style for a TTF font' do + pdf.font_families.update( + 'deja' => { + normal: "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf", + bold: "#{Prawn::DATADIR}/fonts/DejaVuSans-Bold.ttf" + } + ) + pdf.font 'deja' + expect(pdf.font_style).to eq(:normal) + pdf.font_style :bold + expect(pdf.font_style).to eq(:bold) + expect(pdf.font_family).to eq('deja') + expect(pdf.font.name).to eq("#{Prawn::DATADIR}/fonts/DejaVuSans-Bold.ttf") + end + + it 'allows setting font style as assignment' do + pdf.font_style = :bold + expect(pdf.font_style).to eq(:bold) + end + + it 'raises on missing style in family' do + expect do + pdf.font('Helvetica') do + pdf.font_style :heavy + end + end.to raise_error(Prawn::Errors::UnknownFont, 'Font family `Helvetica` has no `:heavy` style.') + end + + it 'warns on style with single font' do + expect(pdf).to receive(:warn).with('Style not supported for `Courier-Bold`.').once + pdf.font('Courier-Bold') do + pdf.font_style :bold + expect(pdf.font_style).to eq(:normal) + end + end + end + + describe '#font_family' do + it 'returns default font family' do + expect(pdf.font_family).to eq('Helvetica') + end + + it 'returns font family' do + pdf.font 'Courier', style: :bold + expect(pdf.font_family).to eq('Courier') + end + + it 'returns nil font family for specific font' do + pdf.font 'ZapfDingbats' + expect(pdf.font_family).to be_nil + end end describe 'font style support' do @@ -142,11 +291,14 @@ pdf.font 'Helvetica' pdf.text 'In Normal Helvetica' + pdf.font nil, style: :bold + pdf.text 'In Bold Helvetica' + text = PDF::Inspector::Text.analyze(pdf.render) expect(text.font_settings.map { |e| e[:name] }).to eq( %i[ Courier-Bold Courier-BoldOblique Courier-Oblique - Courier Helvetica + Courier Helvetica Helvetica-Bold ] ) end @@ -233,36 +385,6 @@ end end - describe 'Transactional font handling' do - it 'allows setting of size directly when font is created' do - pdf.font 'Courier', size: 16 - expect(pdf.font_size).to eq(16) - end - - it 'allows temporary setting of a new font using a transaction' do - pdf.font 'Helvetica', size: 12 - - pdf.font 'Courier', size: 16 do - expect(pdf.font.name).to eq('Courier') - expect(pdf.font_size).to eq(16) - end - - expect(pdf.font.name).to eq('Helvetica') - expect(pdf.font_size).to eq(12) - end - - it 'masks font size when using a transacation' do - pdf.font 'Courier', size: 16 do - expect(pdf.font_size).to eq(16) - end - - pdf.font 'Times-Roman' - pdf.font 'Courier' - - expect(pdf.font_size).to eq(12) - end - end - describe 'Document#page_fonts' do it 'registers fonts properly by page' do pdf.font 'Courier'