├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .mailmap ├── .rspec ├── .rubocop.yml ├── .yardopts ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYING ├── GPLv2 ├── GPLv3 ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bench ├── afm_text_bench.rb ├── png_type_6.rb ├── png_type_6_objects.rb ├── table_bench.rb └── ttf_text_bench.rb ├── certs └── pointlessone.pem ├── checksums ├── prawn-2.2.0.gem.sha512 ├── prawn-2.2.1.gem.sha512 ├── prawn-2.2.2.sha512 ├── prawn-2.3.0.gem.sha512 ├── prawn-2.4.0.gem.sha512 └── prawn-2.5.0.gem.sha512 ├── data ├── fonts │ ├── Bodoni-Book.otf │ ├── Courier-Bold.afm │ ├── Courier-BoldOblique.afm │ ├── Courier-Oblique.afm │ ├── Courier.afm │ ├── DejaVuSans-Bold.ttf │ ├── DejaVuSans.ttc │ ├── DejaVuSans.ttf │ ├── Dustismo_Roman.ttf │ ├── Helvetica-Bold.afm │ ├── Helvetica-BoldOblique.afm │ ├── Helvetica-Oblique.afm │ ├── Helvetica.afm │ ├── MustRead.html │ ├── Panic+Sans.dfont │ ├── Symbol.afm │ ├── Times-Bold.afm │ ├── Times-BoldItalic.afm │ ├── Times-Italic.afm │ ├── Times-Roman.afm │ ├── ZapfDingbats.afm │ └── gkai00mp.ttf ├── images │ ├── 16bit.alpha │ ├── 16bit.color │ ├── 16bit.png │ ├── arrow.png │ ├── arrow2.png │ ├── blend_modes_bottom_layer.jpg │ ├── blend_modes_top_layer.jpg │ ├── dice.alpha │ ├── dice.color │ ├── dice.png │ ├── dice_interlaced.png │ ├── fractal.jpg │ ├── indexed_color.dat │ ├── indexed_color.png │ ├── indexed_transparency.png │ ├── indexed_transparency_alpha.dat │ ├── indexed_transparency_color.dat │ ├── letterhead.jpg │ ├── license.md │ ├── page_white_text.alpha │ ├── page_white_text.color │ ├── page_white_text.png │ ├── pigs.jpg │ ├── prawn.png │ ├── ruport.png │ ├── ruport_data.dat │ ├── ruport_transparent.png │ ├── ruport_type0.png │ ├── stef.jpg │ ├── tru256.bmp │ ├── web-links.dat │ └── web-links.png └── shift_jis_text.txt ├── lib ├── prawn.rb └── prawn │ ├── document.rb │ ├── document │ ├── bounding_box.rb │ ├── column_box.rb │ ├── internals.rb │ └── span.rb │ ├── encoding.rb │ ├── errors.rb │ ├── font.rb │ ├── font_metric_cache.rb │ ├── fonts.rb │ ├── fonts │ ├── afm.rb │ ├── dfont.rb │ ├── otf.rb │ ├── to_unicode_cmap.rb │ ├── ttc.rb │ └── ttf.rb │ ├── graphics.rb │ ├── graphics │ ├── blend_mode.rb │ ├── cap_style.rb │ ├── color.rb │ ├── dash.rb │ ├── join_style.rb │ ├── patterns.rb │ ├── transformation.rb │ └── transparency.rb │ ├── grid.rb │ ├── image_handler.rb │ ├── images.rb │ ├── images │ ├── image.rb │ ├── jpg.rb │ └── png.rb │ ├── measurement_extensions.rb │ ├── measurements.rb │ ├── outline.rb │ ├── repeater.rb │ ├── security.rb │ ├── security │ └── arcfour.rb │ ├── soft_mask.rb │ ├── stamp.rb │ ├── text.rb │ ├── text │ ├── box.rb │ ├── formatted.rb │ └── formatted │ │ ├── arranger.rb │ │ ├── box.rb │ │ ├── fragment.rb │ │ ├── line_wrap.rb │ │ ├── parser.rb │ │ └── wrap.rb │ ├── transformation_stack.rb │ ├── utilities.rb │ ├── version.rb │ └── view.rb ├── manual ├── absolute_position.pdf ├── basic_concepts.rb ├── basic_concepts │ ├── adding_pages.rb │ ├── creation.rb │ ├── cursor.rb │ ├── measurement.rb │ ├── origin.rb │ ├── other_cursor_helpers.rb │ └── view.rb ├── bounding_box.rb ├── bounding_box │ ├── bounds.rb │ ├── canvas.rb │ ├── creation.rb │ ├── indentation.rb │ ├── nesting.rb │ ├── recursive_boxes.rb │ └── stretchy.rb ├── contents.rb ├── cover.rb ├── document_and_page_options.rb ├── document_and_page_options │ ├── background.rb │ ├── metadata.rb │ ├── page_margins.rb │ ├── page_size.rb │ └── print_scaling.rb ├── graphics.rb ├── graphics │ ├── blend_mode.rb │ ├── circle_and_ellipse.rb │ ├── color.rb │ ├── common_lines.rb │ ├── fill_and_stroke.rb │ ├── fill_rules.rb │ ├── gradients.rb │ ├── helper.rb │ ├── line_width.rb │ ├── lines_and_curves.rb │ ├── polygon.rb │ ├── rectangle.rb │ ├── rotate.rb │ ├── scale.rb │ ├── soft_masks.rb │ ├── stroke_cap.rb │ ├── stroke_dash.rb │ ├── stroke_join.rb │ ├── translate.rb │ └── transparency.rb ├── how_to_read_this_manual.rb ├── images.rb ├── images │ ├── absolute_position.rb │ ├── fit.rb │ ├── horizontal.rb │ ├── plain_image.rb │ ├── scale.rb │ ├── vertical.rb │ └── width_and_height.rb ├── layout.rb ├── layout │ ├── boxes.rb │ ├── content.rb │ └── simple_grid.rb ├── manual.rb ├── outline.rb ├── outline │ ├── add_subsection_to.rb │ ├── insert_section_after.rb │ └── sections_and_pages.rb ├── repeatable_content.rb ├── repeatable_content │ ├── alternate_page_numbering.rb │ ├── page_numbering.rb │ ├── repeater.rb │ └── stamp.rb ├── security.rb ├── security │ ├── encryption.rb │ └── permissions.rb ├── table.rb ├── text.rb └── text │ ├── alignment.rb │ ├── color.rb │ ├── column_box.rb │ ├── fallback_fonts.rb │ ├── font.rb │ ├── font_size.rb │ ├── font_style.rb │ ├── formatted_callbacks.rb │ ├── formatted_text.rb │ ├── free_flowing_text.rb │ ├── inline.rb │ ├── kerning_and_character_spacing.rb │ ├── leading.rb │ ├── line_wrapping.rb │ ├── paragraph_indentation.rb │ ├── positioned_text.rb │ ├── registering_families.rb │ ├── rendering_and_color.rb │ ├── right_to_left_text.rb │ ├── rotation.rb │ ├── single_usage.rb │ ├── text_box_excess.rb │ ├── text_box_extensions.rb │ ├── text_box_overflow.rb │ ├── utf8.rb │ └── win_ansi_charset.rb ├── prawn.gemspec └── spec ├── data └── curves.pdf ├── extensions └── encoding_helpers.rb ├── prawn ├── document │ ├── bounding_box_spec.rb │ ├── column_box_spec.rb │ └── security_spec.rb ├── document_annotations_spec.rb ├── document_destinations_spec.rb ├── document_grid_spec.rb ├── document_reference_spec.rb ├── document_span_spec.rb ├── document_spec.rb ├── font_metric_cache_spec.rb ├── font_spec.rb ├── fonts │ └── to_unicode_cmap_spec.rb ├── graphics │ ├── blend_mode_spec.rb │ └── transparency_spec.rb ├── graphics_spec.rb ├── graphics_stroke_styles_spec.rb ├── image_handler_spec.rb ├── images │ ├── jpg_spec.rb │ └── png_spec.rb ├── images_spec.rb ├── measurements_extensions_spec.rb ├── outline_spec.rb ├── repeater_spec.rb ├── soft_mask_spec.rb ├── stamp_spec.rb ├── text │ ├── box_spec.rb │ └── formatted │ │ ├── arranger_spec.rb │ │ ├── box_spec.rb │ │ ├── fragment_spec.rb │ │ ├── line_wrap_spec.rb │ │ └── parser_spec.rb ├── text_draw_text_spec.rb ├── text_rendering_mode_spec.rb ├── text_spacing_spec.rb ├── text_spec.rb ├── text_with_inline_formatting_spec.rb ├── transformation_stack_spec.rb └── view_spec.rb ├── prawn_manual_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | nbproject 3 | pkg 4 | .rvmrc 5 | .bundle 6 | Gemfile.lock 7 | Gemfile.local 8 | drop_to_console.rb 9 | /.idea 10 | /doc 11 | /bin 12 | .DS_Store 13 | *.pdf 14 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Jeremy Friesen Jeremy Friesen 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | prawn-dev: rubocop.yml 3 | 4 | Lint/ConstantDefinitionInBlock: 5 | Exclude: 6 | - manual/text/formatted_callbacks.rb 7 | - manual/text/text_box_extensions.rb 8 | 9 | Layout/LineLength: 10 | Exclude: 11 | # Hashes are long 12 | - spec/prawn_manual_spec.rb 13 | 14 | Layout/MultilineOperationIndentation: 15 | EnforcedStyle: indented 16 | Exclude: 17 | - prawn.gemspec 18 | 19 | # This file shows examples on how to instantiate a document in multiple ways, 20 | # it does not actually do the instantiation and isn't actually shadowing any 21 | # variables. 22 | Lint/ShadowingOuterLocalVariable: 23 | Exclude: 24 | - manual/basic_concepts/creation.rb 25 | 26 | # In this case we suppress Prawn::Errors::CannotFit while trying to scale 27 | # text down to fit. 28 | Lint/SuppressedException: 29 | Exclude: 30 | - lib/prawn/text/formatted/box.rb 31 | 32 | # Disable this cop here, rather than in the file, so the Rubocop directives 33 | # don't print out in the manual. 34 | Lint/UselessAssignment: 35 | Exclude: 36 | - manual/graphics/line_width.rb 37 | 38 | Metrics/BlockLength: 39 | Exclude: 40 | - lib/prawn/graphics.rb 41 | - lib/prawn/images/png.rb 42 | - lib/prawn/text/formatted/parser.rb 43 | - manual/**/*.rb 44 | - prawn.gemspec 45 | - spec/**/*.rb 46 | 47 | Naming/AccessorMethodName: 48 | Exclude: 49 | - lib/prawn/graphics/color.rb 50 | 51 | Naming/PredicateName: 52 | Exclude: 53 | - lib/prawn/font/ttf.rb 54 | - lib/prawn/font/afm.rb 55 | 56 | Style/Encoding: 57 | # To be safe we are leaving these files encoded ASCII-8BIT as 58 | # discussed at https://github.com/prawnpdf/prawn/pull/705 59 | Exclude: 60 | - lib/prawn/images/jpg.rb 61 | - lib/prawn/images/png.rb 62 | - lib/prawn/images.rb 63 | - spec/png_spec.rb 64 | 65 | RSpec/FilePath: 66 | Exclude: 67 | - spec/prawn/fonts/to_unicode_cmap_spec.rb 68 | 69 | RSpec/SpecFilePathFormat: 70 | Exclude: 71 | - spec/prawn/fonts/to_unicode_cmap_spec.rb 72 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --charset UTF-8 2 | --main README.md 3 | --title 'Prawn Documentation' 4 | --no-private 5 | --embed-mixins 6 | - 7 | CONTRIBUTING.md 8 | COPYING 9 | LICENSE 10 | README.md 11 | CHANGELOG.md 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | All contributors must agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md) 2 | 3 | 1. Bug reports are welcome, but please make sure to include a minimal code sample that 4 | shows your problem. In most cases, we'll need you to give us an example that we 5 | can actually run, so include any necessary sample data. A screenshot 6 | or stack trace can also be helpful! 7 | 8 | 2. For all support requests and feature requests, please use the [Prawn mailing 9 | list](https://groups.google.com/forum/#!forum/prawn-ruby) instead of the Github tracker, 10 | or see if anyone is around in #prawn on Freenode to discuss your issue. If we 11 | find that you discovered a bug, or that your feature request is one that 12 | we want to continue to research and discuss on Github, we'll either ask you 13 | to file a ticket or file one on your behalf. 14 | 15 | 3. Pull requests for bug fixes or enhancements are welcome. Feel free to open 16 | them in the early stages of your work so that we can give feedback 17 | and discuss ideas together. 18 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Prawn may be used under Matz's original licensing terms for Ruby, or GPLv2 or GPLv3. 2 | See LICENSE for Matz's terms, or GPLv2 and GPLv3 files. 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | # Evaluate Gemfile.local if it exists 8 | if File.exist?("#{__FILE__}.local") 9 | instance_eval(File.read("#{__FILE__}.local"), "#{__FILE__}.local") 10 | end 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Prawn is copyrighted free software produced by Gregory Brown along with 2 | community contributions. See git log for authorship information. 3 | 4 | Licensing terms follow: 5 | 6 | You can redistribute Prawn and/or modify it under either the terms of the GPLv2 7 | or GPLv3 (see GPLv2 and GPLv3 files), or the conditions below: 8 | 9 | 1. You may make and give away verbatim copies of the source form of the 10 | software without restriction, provided that you duplicate all of the 11 | original copyright notices and associated disclaimers. 12 | 13 | 2. You may modify your copy of the software in any way, provided that 14 | you do at least ONE of the following: 15 | 16 | a) place your modifications in the Public Domain or otherwise 17 | make them Freely Available, such as by posting said 18 | modifications to Usenet or an equivalent medium, or by allowing 19 | the author to include your modifications in the software. 20 | 21 | b) use the modified software only within your corporation or 22 | organization. 23 | 24 | c) rename any non-standard executables so the names do not conflict 25 | with standard executables, which must also be provided. 26 | 27 | d) make other distribution arrangements with the author. 28 | 29 | 3. You may distribute the software in object code or executable 30 | form, provided that you do at least ONE of the following: 31 | 32 | a) distribute the executables and library files of the software, 33 | together with instructions (in the manual page or equivalent) 34 | on where to get the original distribution. 35 | 36 | b) accompany the distribution with the machine-readable source of 37 | the software. 38 | 39 | c) give non-standard executables non-standard names, with 40 | instructions on where to get the original software distribution. 41 | 42 | d) make other distribution arrangements with the author. 43 | 44 | 4. You may modify and include the part of the software into any other 45 | software (possibly commercial). 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | GEMSPEC = File.expand_path('prawn.gemspec', __dir__) 4 | require 'prawn/dev/tasks' 5 | 6 | task default: %i[spec rubocop] 7 | 8 | desc "Generate the 'Prawn by Example' manual" 9 | task :manual do 10 | puts 'Building manual...' 11 | require_relative 'manual/manual' 12 | manual_path = File.expand_path('manual/manual.rb', __dir__) 13 | manual = eval(File.read(manual_path), TOPLEVEL_BINDING, manual_path) # rubocop: disable Security/Eval 14 | manual.generate('manual.pdf') 15 | puts 'The Prawn manual is available at manual.pdf. Happy Prawning!' 16 | end 17 | 18 | desc 'Run a console with Prawn loaded' 19 | task :console do 20 | require 'irb' 21 | require 'irb/completion' 22 | require_relative 'lib/prawn' 23 | Prawn.debug = true 24 | 25 | ARGV.clear 26 | IRB.start 27 | end 28 | -------------------------------------------------------------------------------- /bench/afm_text_bench.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 4 | require 'prawn' 5 | require 'benchmark' 6 | 7 | N = 2000 8 | 9 | Benchmark.bmbm do |x| 10 | x.report('AFM text') do 11 | Prawn::Document.new do 12 | N.times do 13 | (1..5).each do |i| 14 | draw_text('Hello Prawn', at: [200, i * 100]) 15 | end 16 | start_new_page 17 | end 18 | end.render 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /bench/png_type_6.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 4 | require 'prawn' 5 | require 'benchmark' 6 | 7 | N = 100 8 | 9 | Benchmark.bmbm do |x| 10 | x.report('PNG Type 6') do 11 | N.times do 12 | Prawn::Document.new do 13 | image("#{Prawn::DATADIR}/images/dice.png") 14 | end.render_file('dice.pdf') 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bench/png_type_6_objects.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 4 | require 'prawn' 5 | 6 | GC.disable 7 | 8 | before = GC.stat 9 | 10 | Prawn::Document.new do 11 | image("#{Prawn::DATADIR}/images/dice.png") 12 | end.render 13 | 14 | after = GC.stat 15 | total = after[:total_allocated_object] - before[:total_allocated_object] 16 | 17 | puts "allocated objects: #{total}" 18 | -------------------------------------------------------------------------------- /bench/table_bench.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 4 | require 'prawn' 5 | require 'benchmark' 6 | 7 | # Helpers for benchmark 8 | 9 | class String 10 | CHARS = ('a'..'z').to_a 11 | def self.random(length) 12 | Array.new(length) { CHARS.sample }.join 13 | end 14 | end 15 | 16 | def data_for_table(columns, rows, string_size) 17 | Array.new(rows) { Array.new(columns) { String.random(string_size) } } 18 | end 19 | 20 | def benchmark_table_generation(columns, rows, string_size, options = {}) 21 | data = data_for_table(columns, rows, string_size) 22 | Benchmark.bm do |x| 23 | x.report( 24 | "#{columns}x#{rows} table (#{columns * rows} cells, with #{string_size} " \ 25 | 'char string contents' \ 26 | "#{", options = #{options.inspect}" unless options.empty?})", 27 | ) do 28 | Prawn::Document.new { table(data, options) }.render 29 | end 30 | end 31 | end 32 | 33 | # Slowest case: styled table, which is very squeezed horizontally, 34 | # so text has to be wrapped 35 | benchmark_table_generation( 36 | 26, 37 | 50, 38 | 10, 39 | row_colors: %w[FFFFFF F0F0FF], 40 | header: true, 41 | cell_style: { inline_format: true }, 42 | ) 43 | 44 | # Try building and rendering tables of different sizes 45 | benchmark_table_generation(10, 400, 5) 46 | benchmark_table_generation(10, 200, 5) 47 | benchmark_table_generation(10, 100, 5) 48 | 49 | # Try different optional arguments to Prawn::Document#table 50 | benchmark_table_generation(10, 450, 5, cell_style: { inline_format: true }) 51 | benchmark_table_generation( 52 | 10, 53 | 450, 54 | 5, 55 | row_colors: %w[FFFFFF F0F0FF], 56 | header: true, 57 | cell_style: { inline_format: true }, 58 | ) 59 | -------------------------------------------------------------------------------- /bench/ttf_text_bench.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 4 | require 'prawn' 5 | require 'benchmark' 6 | 7 | N = 2000 8 | 9 | Benchmark.bmbm do |x| 10 | x.report('TTF text') do 11 | Prawn::Document.new do 12 | font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf") 13 | N.times do 14 | (1..5).each do |i| 15 | draw_text('Hello Prawn', at: [200, i * 100]) 16 | end 17 | start_new_page 18 | end 19 | end.render 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /certs/pointlessone.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+jCCAeKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhhbGV4 3 | L0RDPXBvaW50bGVzcy9EQz1vbmUwHhcNMjQwMzA1MDkyODIyWhcNMjUwMzA1MDky 4 | ODIyWjAjMSEwHwYDVQQDDBhhbGV4L0RDPXBvaW50bGVzcy9EQz1vbmUwggEiMA0G 5 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPOVLPGEK+eaP6zJfifrpWvPTg4qo3 6 | XNJJPom80SwqX2hVCVsRDK4RYgKUQqKRQzHhlx14wZHwWLETBVbNDGX3uqyCnTWU 7 | JUKh3ydiZShXpNHoV/NW7hhEYvNsDcBAjYTmbvXOhuYCo0Tz/0N2Oiun/0wIICtP 8 | vytY9TY0/lklWjAbsqJjNOu3o8IYkJBAN/rU96E/6WhFwjnxLcTnV9RfFRXdjG5j 9 | CughoB2xSwKX8gwbQ8fsnaZRmdyDGYNpz6sGF0zycfiLkTttbLA2nYATCALy98CH 10 | nsyZNsTjb4WINCuY2yEDjwesw9f/ROkNC68EgQ5M+aMjp+D0WcYGfzojAgMBAAGj 11 | OTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRPgIwSVbeonua/ 12 | Ny/8576oxdUbrjANBgkqhkiG9w0BAQsFAAOCAQEAKA0kHIMnHGRwc1oPq8fDqBmH 13 | oCSExtvNKH3tu8pfzkoJNX+fCkQ64OOuOqQsB4jjvvonw4OK8ZIQ7M3F1eX5ORy5 14 | 02Qpl0WgDiLIFRRQiXHBT0yEEXfoPjiEekYtUj3H4s2vJYPH0uUNE1xnCP9e3Z2V 15 | 5QEaIgAi9v+7J/wAo2qh8sc32QVBI2KBfYuHNTMzJIZrTSkokyIiViISUG4exNE3 16 | PvfW/Czo3M1bWzI4AFcHqc/w/DIjz8MywiwYFDpOrHF9/9475yNN7J9SkCZvM5SC 17 | +4mEk/fC1a9497DDkhfkkItNG2kMkzAj8/VSFbbl02U59NFdtm+hhlZSLPYJeA== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /checksums/prawn-2.2.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 8413463a0b4c2d6d16db9c74e9fd9e8035993d3f23267df77b8cfae320c1ce84f08998ce434ccbfc3f3d3469b997fc686ea34e41267195c5d4496f0b74fc6117 2 | -------------------------------------------------------------------------------- /checksums/prawn-2.2.1.gem.sha512: -------------------------------------------------------------------------------- 1 | e8c41122cc738f287e0dcee6f34867a5f68ef5898549b23a3c4acb3111c6ce3e76c5f407a0a1ec3913a9c981e9885f3ecfb00748e505fe4e9b1939de8ddb8baa 2 | -------------------------------------------------------------------------------- /checksums/prawn-2.2.2.sha512: -------------------------------------------------------------------------------- 1 | 59c1fab27099decbbe7eb2b954069131ccdea2c92cdbdac8227fe1036fb9f0af6d2d377777b9c9ab9fff75491f9967b7f169fde66b2a0b3db1a9b4b1ef1104f1 2 | -------------------------------------------------------------------------------- /checksums/prawn-2.3.0.gem.sha512: -------------------------------------------------------------------------------- 1 | de830e1aaddc4ab382495516d503f433063a883cef6066ddbe9c6f670a51132979e33fa7860d7d3b32e1f1cd4f2ec118af7dfa5d13f5b79cfdd12f7e10e27b15 -------------------------------------------------------------------------------- /checksums/prawn-2.4.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 74025d1bdcb51da3808736f60f834a1964430f30d351c0fd16d6af7d8a9c302fa9bee50087a474528d319932c3d22132c5c900b0ce39503f0dce3ac650772df3 -------------------------------------------------------------------------------- /checksums/prawn-2.5.0.gem.sha512: -------------------------------------------------------------------------------- 1 | a8ce611ab63e995f2380dafec013cfe3624acfb6eea9200dc12e4a44dc812ba3d1d36c67de1c389c782ac6ea2f1f3949fa293f7940752e9f279d1022ab67c7f7 -------------------------------------------------------------------------------- /data/fonts/Bodoni-Book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/fonts/Bodoni-Book.otf -------------------------------------------------------------------------------- /data/fonts/DejaVuSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/fonts/DejaVuSans-Bold.ttf -------------------------------------------------------------------------------- /data/fonts/DejaVuSans.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/fonts/DejaVuSans.ttc -------------------------------------------------------------------------------- /data/fonts/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/fonts/DejaVuSans.ttf -------------------------------------------------------------------------------- /data/fonts/Dustismo_Roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/fonts/Dustismo_Roman.ttf -------------------------------------------------------------------------------- /data/fonts/MustRead.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Core 14 AFM Files - ReadMe 7 | 8 | 9 | 10 | or 11 | 12 | 13 | 14 | 15 | 16 |
This file and the 14 PostScript(R) AFM files it accompanies may be used, copied, and distributed for any purpose and without charge, with or without modification, provided that all copyright notices are retained; that the AFM files are not distributed without this file; that all modifications to this file or any of the AFM files are prominently noted in the modified file(s); and that this paragraph is not modified. Adobe Systems has no responsibility or obligation to support the use of the AFM files. Col
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /data/fonts/Panic+Sans.dfont: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/fonts/Panic+Sans.dfont -------------------------------------------------------------------------------- /data/fonts/gkai00mp.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/fonts/gkai00mp.ttf -------------------------------------------------------------------------------- /data/images/16bit.alpha: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/16bit.alpha -------------------------------------------------------------------------------- /data/images/16bit.color: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/16bit.color -------------------------------------------------------------------------------- /data/images/16bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/16bit.png -------------------------------------------------------------------------------- /data/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/arrow.png -------------------------------------------------------------------------------- /data/images/arrow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/arrow2.png -------------------------------------------------------------------------------- /data/images/blend_modes_bottom_layer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/blend_modes_bottom_layer.jpg -------------------------------------------------------------------------------- /data/images/blend_modes_top_layer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/blend_modes_top_layer.jpg -------------------------------------------------------------------------------- /data/images/dice.alpha: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/dice.alpha -------------------------------------------------------------------------------- /data/images/dice.color: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/dice.color -------------------------------------------------------------------------------- /data/images/dice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/dice.png -------------------------------------------------------------------------------- /data/images/dice_interlaced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/dice_interlaced.png -------------------------------------------------------------------------------- /data/images/fractal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/fractal.jpg -------------------------------------------------------------------------------- /data/images/indexed_color.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/indexed_color.dat -------------------------------------------------------------------------------- /data/images/indexed_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/indexed_color.png -------------------------------------------------------------------------------- /data/images/indexed_transparency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/indexed_transparency.png -------------------------------------------------------------------------------- /data/images/indexed_transparency_alpha.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/indexed_transparency_alpha.dat -------------------------------------------------------------------------------- /data/images/indexed_transparency_color.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/indexed_transparency_color.dat -------------------------------------------------------------------------------- /data/images/letterhead.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/letterhead.jpg -------------------------------------------------------------------------------- /data/images/license.md: -------------------------------------------------------------------------------- 1 | All images distributed with Prawn need to be freely distributable under 2 | a license that is compatible with our own (see LICENSE and COPYING files). 3 | 4 | It is our understanding that all files in this folder are compatible 5 | with our licensing, but if you notice any problems, please 6 | file an issue in our tracker on Github and we will promptly address it: 7 | 8 | https://github.com/prawnpdf/prawn/issues 9 | -------------------------------------------------------------------------------- /data/images/page_white_text.alpha: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/page_white_text.alpha -------------------------------------------------------------------------------- /data/images/page_white_text.color: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/page_white_text.color -------------------------------------------------------------------------------- /data/images/page_white_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/page_white_text.png -------------------------------------------------------------------------------- /data/images/pigs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/pigs.jpg -------------------------------------------------------------------------------- /data/images/prawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/prawn.png -------------------------------------------------------------------------------- /data/images/ruport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/ruport.png -------------------------------------------------------------------------------- /data/images/ruport_data.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/ruport_data.dat -------------------------------------------------------------------------------- /data/images/ruport_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/ruport_transparent.png -------------------------------------------------------------------------------- /data/images/ruport_type0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/ruport_type0.png -------------------------------------------------------------------------------- /data/images/stef.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/stef.jpg -------------------------------------------------------------------------------- /data/images/tru256.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/tru256.bmp -------------------------------------------------------------------------------- /data/images/web-links.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/web-links.dat -------------------------------------------------------------------------------- /data/images/web-links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/images/web-links.png -------------------------------------------------------------------------------- /data/shift_jis_text.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/data/shift_jis_text.txt -------------------------------------------------------------------------------- /lib/prawn/document/span.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # span.rb : Implements text columns 4 | # 5 | # Copyright September 2008, Gregory Brown. All Rights Reserved. 6 | # 7 | # This is free software. Please see the LICENSE and COPYING files for details. 8 | 9 | module Prawn 10 | class Document # rubocop: disable Style/Documentation 11 | # @group Stable API 12 | 13 | # A span is a special purpose bounding box that allows a column of elements 14 | # to be positioned relative to the margin_box. 15 | # 16 | # This method is typically used for flowing a column of text from one page 17 | # to the next. 18 | # 19 | # @example 20 | # span(350, position: :center) do 21 | # text "Here's some centered text in a 350 point column. " * 100 22 | # end 23 | # 24 | # @param width [Number] The width of the column in PDF points 25 | # @param options [Hash{Symbol => any }] 26 | # @option options :position [:left, :center, :right, Number] 27 | # position of the span relative to the page margins. 28 | # @yield 29 | # @return [void] 30 | # @raise [ArgumentError] For unsupported `:position` value. 31 | def span(width, options = {}) 32 | Prawn.verify_options([:position], options) 33 | original_position = y 34 | 35 | # FIXME: Any way to move this upstream? 36 | left_boundary = 37 | case options.fetch(:position, :left) 38 | when :left 39 | margin_box.absolute_left 40 | when :center 41 | margin_box.absolute_left + (margin_box.width / 2.0) - (width / 2.0) 42 | when :right 43 | margin_box.absolute_right - width 44 | when Numeric 45 | margin_box.absolute_left + options[:position] 46 | else 47 | raise ArgumentError, 'Invalid option for :position' 48 | end 49 | 50 | # we need to bust out of whatever nested bounding boxes we're in. 51 | canvas do 52 | bounding_box( 53 | [ 54 | left_boundary, 55 | margin_box.absolute_top, 56 | ], 57 | width: width, 58 | ) do 59 | self.y = original_position 60 | yield 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/prawn/font_metric_cache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | # Cache used internally by {Prawn::Document} instances to calculate the width 5 | # of various strings for layout purposes. 6 | # 7 | # @private 8 | class FontMetricCache 9 | CacheEntry = Struct.new(:font, :font_size, :options, :string) 10 | 11 | def initialize(document) 12 | @document = document 13 | 14 | @cache = {} 15 | end 16 | 17 | # Get width of string. 18 | # 19 | # @param string [String] 20 | # @param options [Hash{Symbol => any}] 21 | # @option options :style [Symbol] 22 | # @option options :size [Number] 23 | # @option options :kerning [Boolean] (false) 24 | # @return [Number] 25 | def width_of(string, options) 26 | f = 27 | if options[:style] 28 | # override style with :style => :bold 29 | @document.find_font(@document.font.family, style: options[:style]) 30 | else 31 | @document.font 32 | end 33 | 34 | encoded_string = f.normalize_encoding(string) 35 | 36 | key = CacheEntry.new(f, @document.font_size, options, encoded_string) 37 | 38 | @cache[key] ||= f.compute_width_of(encoded_string, options) 39 | 40 | length = @cache[key] 41 | 42 | character_count = @document.font.character_count(encoded_string) 43 | if character_count.positive? 44 | length += @document.character_spacing * (character_count - 1) 45 | end 46 | 47 | length 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/prawn/fonts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | # Namespace for different fonts. 5 | module Fonts 6 | end 7 | end 8 | 9 | require_relative 'font' 10 | require_relative 'fonts/afm' 11 | require_relative 'fonts/ttf' 12 | require_relative 'fonts/dfont' 13 | require_relative 'fonts/otf' 14 | require_relative 'fonts/ttc' 15 | -------------------------------------------------------------------------------- /lib/prawn/fonts/dfont.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'ttf' 4 | 5 | module Prawn 6 | module Fonts 7 | # DFONT font. DFONT is a bunch of TrueType fonts in a single file. 8 | # 9 | # @note You shouldn't use this class directly. 10 | class DFont < TTF 11 | # Returns a list of the names of all named fonts in the given dfont file. 12 | # Note that fonts are not required to be named in a dfont file, so the 13 | # list may be empty even if the file does contain fonts. Also, note that 14 | # the list is returned in no particular order, so the first font in the 15 | # list is not necessarily the font at index 0 in the file. 16 | # 17 | # @param file [String] 18 | # @return [Array] 19 | def self.named_fonts(file) 20 | TTFunk::ResourceFile.open(file) do |f| 21 | return f.resources_for('sfnt') 22 | end 23 | end 24 | 25 | # Returns the number of fonts contained in the dfont file. 26 | # 27 | # @param file [String] 28 | # @return [Integer] 29 | def self.font_count(file) 30 | TTFunk::ResourceFile.open(file) do |f| 31 | return f.map['sfnt'][:list].length 32 | end 33 | end 34 | 35 | private 36 | 37 | def read_ttf_file 38 | TTFunk::File.from_dfont(@name, @options[:font] || 0) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/prawn/fonts/otf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'ttf' 4 | 5 | module Prawn 6 | module Fonts 7 | # OpenType font. This class is used mostly to distinguish OTF from TTF. 8 | # All functionality is in the {Fonts::TTF} class. 9 | # 10 | # @note You shouldn't use this class directly. 11 | class OTF < TTF 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/prawn/fonts/ttc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'ttf' 4 | 5 | module Prawn 6 | module Fonts 7 | # TrueType Collection font. It's an SFNT-based format that contains a bunch 8 | # of TrueType fonts in a single file. 9 | # 10 | # @note You shouldn't use this class directly. 11 | class TTC < TTF 12 | # Returns a list of the names of all named fonts in the given ttc file. 13 | # They are returned in order of their appearance in the file. 14 | # 15 | # @param file [String] 16 | # @return [Array] 17 | def self.font_names(file) 18 | TTFunk::Collection.open(file) do |ttc| 19 | ttc.map { |font| font.name.font_name.first } 20 | end 21 | end 22 | 23 | private 24 | 25 | def read_ttf_file 26 | TTFunk::File.from_ttc( 27 | @name, 28 | font_option_to_index(@name, @options[:font]), 29 | ) 30 | end 31 | 32 | def font_option_to_index(file, option) 33 | if option.is_a?(Numeric) 34 | option 35 | else 36 | self.class.font_names(file).index { |n| n == option } || 0 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/prawn/graphics/blend_mode.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | module Graphics 5 | # The {Prawn::BlendMode} module is used to change the way two graphic 6 | # objects are blended together. 7 | module BlendMode 8 | # @group Stable API 9 | 10 | # Set blend mode. If a block is passed blend mode is restored afterwards. 11 | # 12 | # Passing an array of blend modes is allowed. PDF viewers should blend 13 | # layers based on the first recognized blend mode. 14 | # 15 | # Valid blend modes since PDF 1.4 include `:Normal`, `:Multiply`, `:Screen`, 16 | # `:Overlay`, `:Darken`, `:Lighten`, `:ColorDodge`, `:ColorBurn`, 17 | # `:HardLight`, `:SoftLight`, `:Difference`, `:Exclusion`, `:Hue`, 18 | # `:Saturation`, `:Color`, and `:Luminosity`. 19 | # 20 | # @example 21 | # pdf.fill_color('0000ff') 22 | # pdf.fill_rectangle([x, y + 25], 50, 50) 23 | # pdf.blend_mode(:Multiply) do 24 | # pdf.fill_color('ff0000') 25 | # pdf.fill_circle([x, y], 25) 26 | # end 27 | # 28 | # @param blend_mode [Symbol, Array] 29 | # @yield 30 | # @return [void] 31 | def blend_mode(blend_mode = :Normal) 32 | renderer.min_version(1.4) 33 | 34 | save_graphics_state if block_given? 35 | renderer.add_content("/#{blend_mode_dictionary_name(blend_mode)} gs") 36 | if block_given? 37 | yield 38 | restore_graphics_state 39 | end 40 | end 41 | 42 | private 43 | 44 | def blend_mode_dictionary_registry 45 | @blend_mode_dictionary_registry ||= {} 46 | end 47 | 48 | def blend_mode_dictionary_name(blend_mode) 49 | key = Array(blend_mode).join('') 50 | dictionary_name = "BM#{key}" 51 | 52 | dictionary = blend_mode_dictionary_registry[dictionary_name] ||= ref!( 53 | Type: :ExtGState, 54 | BM: blend_mode, 55 | ) 56 | 57 | page.ext_gstates[dictionary_name] = dictionary 58 | dictionary_name 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/prawn/graphics/cap_style.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | module Graphics 5 | # Implements stroke cap styling 6 | module CapStyle 7 | # @group Stable API 8 | 9 | # @private 10 | CAP_STYLES = { butt: 0, round: 1, projecting_square: 2 }.freeze 11 | 12 | # Sets the cap style for stroked lines and curves. 13 | # 14 | # @overload cap_style(style) 15 | # @param style [:butt, :round, :projecting_square] (:butt) 16 | # @return [void] 17 | # @overload cap_style() 18 | # @return [Symbol] 19 | def cap_style(style = nil) 20 | return current_cap_style || :butt if style.nil? 21 | 22 | self.current_cap_style = style 23 | 24 | write_stroke_cap_style 25 | end 26 | 27 | alias cap_style= cap_style 28 | 29 | private 30 | 31 | def current_cap_style 32 | graphic_state.cap_style 33 | end 34 | 35 | def current_cap_style=(style) 36 | graphic_state.cap_style = style 37 | end 38 | 39 | def write_stroke_cap_style 40 | renderer.add_content("#{CAP_STYLES[current_cap_style]} J") 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/prawn/graphics/join_style.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | module Graphics 5 | # Implements stroke join styling. 6 | module JoinStyle 7 | # @private 8 | JOIN_STYLES = { miter: 0, round: 1, bevel: 2 }.freeze 9 | 10 | # @group Stable API 11 | 12 | # Get or set the join style for stroked lines and curves. 13 | # 14 | # @overload join_style 15 | # Get current join style. 16 | # 17 | # @return [:miter, :round, :bevel] 18 | # 19 | # @overload join_style(style) 20 | # Set join style. 21 | # 22 | # @note If this method is never called, `:miter` will be used for join 23 | # style throughout the document. 24 | # 25 | # @param style [:miter, :round, :bevel] 26 | # @return [void] 27 | # 28 | # 29 | def join_style(style = nil) 30 | return current_join_style || :miter if style.nil? 31 | 32 | self.current_join_style = style 33 | 34 | unless JOIN_STYLES.key?(current_join_style) 35 | raise Prawn::Errors::InvalidJoinStyle, 36 | "#{style} is not a recognized join style. Valid styles are " + 37 | JOIN_STYLES.keys.join(', ') 38 | end 39 | 40 | write_stroke_join_style 41 | end 42 | 43 | alias join_style= join_style 44 | 45 | private 46 | 47 | def current_join_style 48 | graphic_state.join_style 49 | end 50 | 51 | def current_join_style=(style) 52 | graphic_state.join_style = style 53 | end 54 | 55 | def write_stroke_join_style 56 | renderer.add_content("#{JOIN_STYLES[current_join_style]} j") 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/prawn/graphics/transparency.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | module Graphics 5 | # This module is used to place transparent content on the page. It has the 6 | # capacity for separate transparency values for stroked content and all 7 | # other content. 8 | module Transparency 9 | # @group Stable API 10 | 11 | # Set opacity. 12 | # 13 | # @example Both the fill and stroke will be at 50% opacity. 14 | # pdf.transparent(0.5) do 15 | # pdf.text("hello world") 16 | # pdf.fill_and_stroke_circle([x, y], 25) 17 | # end 18 | # 19 | # @example The fill will be at 50% opacity, but the stroke will be at 75% opacity. 20 | # pdf.transparent(0.5, 0.75) do 21 | # pdf.text("hello world") 22 | # pdf.fill_and_stroke_circle([x, y], 25) 23 | # end 24 | # 25 | # @param opacity [Number] Fill opacity. Clipped to the 0.0 to 1.0 range. 26 | # @param stroke_opacity [Number] Stroke opacity. Clipped to the 27 | # 0.0 to 1.0 range. 28 | # @yield 29 | # @return [void] 30 | def transparent(opacity, stroke_opacity = opacity) 31 | renderer.min_version(1.4) 32 | 33 | opacity = opacity.clamp(0.0, 1.0) 34 | stroke_opacity = stroke_opacity.clamp(0.0, 1.0) 35 | 36 | save_graphics_state 37 | renderer.add_content( 38 | "/#{opacity_dictionary_name(opacity, stroke_opacity)} gs", 39 | ) 40 | yield 41 | restore_graphics_state 42 | end 43 | 44 | private 45 | 46 | def opacity_dictionary_registry 47 | @opacity_dictionary_registry ||= {} 48 | end 49 | 50 | def next_opacity_dictionary_id 51 | opacity_dictionary_registry.length + 1 52 | end 53 | 54 | def opacity_dictionary_name(opacity, stroke_opacity) 55 | key = "#{opacity}_#{stroke_opacity}" 56 | 57 | if opacity_dictionary_registry[key] 58 | dictionary = opacity_dictionary_registry[key][:obj] 59 | dictionary_name = opacity_dictionary_registry[key][:name] 60 | else 61 | dictionary = ref!( 62 | Type: :ExtGState, 63 | CA: stroke_opacity, 64 | ca: opacity, 65 | ) 66 | 67 | dictionary_name = "Tr#{next_opacity_dictionary_id}" 68 | opacity_dictionary_registry[key] = { 69 | name: dictionary_name, 70 | obj: dictionary, 71 | } 72 | end 73 | 74 | page.ext_gstates[dictionary_name] = dictionary 75 | dictionary_name 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/prawn/image_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn # rubocop: disable Style/Documentation 4 | # @group Extension API 5 | 6 | # Image handler. 7 | # 8 | # @return [ImageHandler] 9 | def self.image_handler 10 | @image_handler ||= ImageHandler.new 11 | end 12 | 13 | # ImageHandler provides a way to register image processors with Prawn. 14 | class ImageHandler 15 | # @private 16 | def initialize 17 | @handlers = [] 18 | end 19 | 20 | # Register an image handler. 21 | # 22 | # @param handler [Object] 23 | # @return [void] 24 | def register(handler) 25 | @handlers.delete(handler) 26 | @handlers.push(handler) 27 | end 28 | 29 | # Register an image handler with the highest priority. 30 | # 31 | # @param handler [Object] 32 | # @return [void] 33 | def register!(handler) 34 | @handlers.delete(handler) 35 | @handlers.unshift(handler) 36 | end 37 | 38 | # Unregister an image handler. 39 | # 40 | # @param handler [Object] 41 | # @return [void] 42 | def unregister(handler) 43 | @handlers.reject! { |h| h == handler } 44 | end 45 | 46 | # Find an image handler for an image. 47 | # 48 | # @param image_blob [String] 49 | # @return [Object] 50 | # @raise [Prawn::Errors::UnsupportedImageType] If no image handler were 51 | # found for the image. 52 | def find(image_blob) 53 | handler = @handlers.find { |h| h.can_render?(image_blob) } 54 | 55 | return handler if handler 56 | 57 | raise Prawn::Errors::UnsupportedImageType, 58 | 'image file is an unrecognised format' 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/prawn/images/image.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'digest/sha1' 4 | 5 | module Prawn 6 | module Images 7 | # Base class for image info objects 8 | # @abstract 9 | class Image 10 | # @group Extension API 11 | 12 | # Calculate the final image dimensions from provided options. 13 | # @private 14 | def calc_image_dimensions(options) 15 | w = options[:width] || width 16 | h = options[:height] || height 17 | 18 | if options[:width] && !options[:height] 19 | wp = w / Float(width) 20 | w = width * wp 21 | h = height * wp 22 | elsif options[:height] && !options[:width] 23 | hp = h / Float(height) 24 | w = width * hp 25 | h = height * hp 26 | elsif options[:scale] 27 | w = width * options[:scale] 28 | h = height * options[:scale] 29 | elsif options[:fit] 30 | bw, bh = options[:fit] 31 | bp = bw / Float(bh) 32 | ip = width / Float(height) 33 | if ip > bp 34 | w = bw 35 | h = bw / ip 36 | else 37 | h = bh 38 | w = bh * ip 39 | end 40 | end 41 | self.scaled_width = w 42 | self.scaled_height = h 43 | 44 | [w, h] 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/prawn/measurement_extensions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'measurements' 4 | 5 | # @group Stable API 6 | 7 | # Core extensions for {Prawn::Measurements}. 8 | # 9 | # This mainly enables measurements DSL. 10 | # 11 | # You have to explicitly require "prawn/measurement_extensions" to enable these. 12 | # 13 | # ```ruby 14 | # require 'prawn/measurement_extensions' 15 | # 16 | # 12.mm 17 | # 2.cm 18 | # 0.5.in 19 | # 4.yd + 2.ft 20 | # ``` 21 | class Numeric 22 | include Prawn::Measurements 23 | # Prawn's basic unit is PostScript-Point: 72 points per inch. 24 | 25 | # @group Experimental API 26 | 27 | # Convert from millimeters to points. 28 | # 29 | # @return [Number] 30 | def mm 31 | mm2pt(self) 32 | end 33 | 34 | # Convert from centimeters to points. 35 | # 36 | # @return [Number] 37 | def cm 38 | cm2pt(self) 39 | end 40 | 41 | # Convert from decimeters to points. 42 | # 43 | # @return [Number] 44 | def dm 45 | dm2pt(self) 46 | end 47 | 48 | # Convert from meters to points. 49 | # 50 | # @return [Number] 51 | def m 52 | m2pt(self) 53 | end 54 | 55 | # Convert from inches to points. 56 | # 57 | # @return [Number] 58 | def in 59 | in2pt(self) 60 | end 61 | 62 | # Convert from yards to points. 63 | # 64 | # @return [Number] 65 | def yd 66 | yd2pt(self) 67 | end 68 | 69 | # Convert from feet to points. 70 | # 71 | # @return [Number] 72 | def ft 73 | ft2pt(self) 74 | end 75 | 76 | # Convert from points to points. 77 | # 78 | # @return [Number] 79 | def pt 80 | pt2pt(self) 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/prawn/security/arcfour.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Implementation of the "ARCFOUR" algorithm ("alleged RC4 (tm)"). Implemented 4 | # as described at: 5 | # http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt 6 | # 7 | # "RC4" is a trademark of RSA Data Security, Inc. 8 | # 9 | # @private 10 | class Arcfour 11 | def initialize(key) 12 | # Convert string key to Array of integers 13 | key = key.unpack('c*') if key.is_a?(String) 14 | 15 | # 1. Allocate an 256 element array of 8 bit bytes to be used as an S-box 16 | # 2. Initialize the S-box. Fill each entry first with it's index 17 | @sbox = (0..255).to_a 18 | 19 | # 3. Fill another array of the same size (256) with the key, repeating 20 | # bytes as necessary. 21 | s2 = [] 22 | while s2.length < 256 23 | s2 += key 24 | end 25 | s2 = s2[0, 256] 26 | 27 | # 4. Set j to zero and initialize the S-box 28 | j = 0 29 | (0..255).each do |i| 30 | j = (j + @sbox[i] + s2[i]) % 256 31 | @sbox[i], @sbox[j] = @sbox[j], @sbox[i] 32 | end 33 | 34 | @i = @j = 0 35 | end 36 | 37 | # Encrypt string. 38 | # 39 | # @param string [String] 40 | # @return [String] 41 | def encrypt(string) 42 | string.unpack('c*').map { |byte| byte ^ key_byte }.pack('c*') 43 | end 44 | 45 | private 46 | 47 | # Produces the next byte of key material in the stream (3.2 Stream Generation) 48 | def key_byte 49 | @i = (@i + 1) % 256 50 | @j = (@j + @sbox[@i]) % 256 51 | @sbox[@i], @sbox[@j] = @sbox[@j], @sbox[@i] 52 | @sbox[(@sbox[@i] + @sbox[@j]) % 256] 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/prawn/transformation_stack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'matrix' 4 | 5 | module Prawn 6 | # Stores the transformations that have been applied to the document. 7 | # @private 8 | module TransformationStack 9 | # rubocop: disable Metrics/ParameterLists, Naming/MethodParameterName 10 | 11 | # Add transformation to the stack. 12 | # 13 | # @param a [Number] 14 | # @param b [Number] 15 | # @param c [Number] 16 | # @param d [Number] 17 | # @param e [Number] 18 | # @param f [Number] 19 | # @return [void] 20 | def add_to_transformation_stack(a, b, c, d, e, f) 21 | @transformation_stack ||= [[]] 22 | @transformation_stack.last.push([a, b, c, d, e, f].map { |i| Float(i) }) 23 | end 24 | 25 | # Save transformation stack. 26 | # 27 | # @return [void] 28 | def save_transformation_stack 29 | @transformation_stack ||= [[]] 30 | @transformation_stack.push(@transformation_stack.last.dup) 31 | end 32 | 33 | # Restore previous transformation. 34 | # 35 | # Effectively pops the last transformation off of the transformation stack. 36 | # 37 | # @return [void] 38 | def restore_transformation_stack 39 | @transformation_stack&.pop 40 | end 41 | 42 | # Get current transformation matrix. It's a result of multiplication of the 43 | # whole transformation stack with additional translation. 44 | # 45 | # @param x [Number] 46 | # @param y [Number] 47 | # @return [Array(Number, Number, Number, Number, Number, Number)] 48 | def current_transformation_matrix_with_translation(x = 0, y = 0) 49 | transformations = (@transformation_stack || [[]]).last 50 | 51 | matrix = Matrix.identity(3) 52 | 53 | transformations.each do |a, b, c, d, e, f| 54 | matrix *= Matrix[[a, c, e], [b, d, f], [0, 0, 1]] 55 | end 56 | 57 | matrix *= Matrix[[1, 0, x], [0, 1, y], [0, 0, 1]] 58 | 59 | matrix.to_a[0..1].transpose.flatten 60 | end 61 | # rubocop: enable Metrics/ParameterLists, Naming/MethodParameterName 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/prawn/utilities.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | # Throughout the Prawn codebase, repeated calculations which can benefit from 5 | # caching are made. n some cases, caching and reusing results can not only 6 | # save CPU cycles but also greatly reduce memory requirements But at the same 7 | # time, we don't want to throw away thread safety. 8 | # @private 9 | class SynchronizedCache 10 | # As an optimization, this could access the hash directly on VMs with 11 | # a global interpreter lock (like MRI). 12 | def initialize 13 | @cache = {} 14 | @mutex = Mutex.new 15 | end 16 | 17 | # Get cache entry. 18 | # 19 | # @param key [any] 20 | # @return [any] 21 | def [](key) 22 | @mutex.synchronize { @cache[key] } 23 | end 24 | 25 | # Set cache entry. 26 | # 27 | # @param key [any] 28 | # @param value [any] 29 | # @return [void] 30 | def []=(key, value) 31 | @mutex.synchronize { @cache[key] = value } 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/prawn/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Prawn 4 | # Prawn versions 5 | VERSION = '2.5.0' 6 | end 7 | -------------------------------------------------------------------------------- /manual/absolute_position.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/manual/absolute_position.pdf -------------------------------------------------------------------------------- /manual/basic_concepts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Basic Concepts') 8 | 9 | prose <<~TEXT 10 | This chapter covers the minimum amount of functionality you'll need to 11 | start using Prawn. 12 | 13 | If you are new to Prawn this is the first chapter to read. Once you are 14 | comfortable with the concepts shown here you might want to check the 15 | Basics section of the Graphics, Bounding Box and Text sections. 16 | 17 | The examples show: 18 | TEXT 19 | 20 | list( 21 | 'How to create new pdf documents in every possible way', 22 | 'Where the origin for the document coordinates is. What are Bounding ' \ 23 | 'Boxes and how they interact with the origin', 24 | 'How the cursor behaves', 25 | 'How to start new pages', 26 | 'What the base unit for measurement and coordinates is and how to use ' \ 27 | 'other convenient measures', 28 | "How to build custom view objects that use Prawn's DSL", 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /manual/basic_concepts/adding_pages.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Adding Pages' 7 | 8 | text do 9 | prose <<~TEXT 10 | A PDF document is a collection of pages. When we create a new document be 11 | it with Document.new or on a Document.generate 12 | block one initial page is created for us. 13 | 14 | Some methods might create new pages automatically like text 15 | which will create a new page whenever the text string cannot fit on the 16 | current page. 17 | 18 | But what if you want to go to the next page by yourself? That is easy. 19 | 20 | Just use the start_new_page method and a shiny new page will 21 | be created for you just like in the following snippet. 22 | TEXT 23 | end 24 | 25 | example do 26 | text "We are still on the initial page for this example. Now I'll ask " \ 27 | 'Prawn to gently start a new page. Please follow me to the next page.' 28 | 29 | start_new_page 30 | 31 | text "See. We've left the previous page behind." 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /manual/basic_concepts/creation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Creating a PDF Document' 7 | 8 | text do 9 | prose <<~TEXT 10 | There are three ways to create a PDF Document in Prawn: creating a new 11 | Prawn::Document instance, or using the 12 | Prawn::Document.generate method with and without block 13 | arguments. 14 | 15 | The following snippet showcase each way by creating a simple document with 16 | some text drawn. 17 | 18 | When we instantiate the Prawn::Document object the actual pdf 19 | document will only be created after we call render_file. 20 | 21 | The generate method will render the actual pdf object after exiting the 22 | block. When we use it without a block argument the provided block is 23 | evaluated in the context of a newly created Prawn::Document 24 | instance. When we use it with a block argument a 25 | Prawn::Document instance is created and passed to the block. 26 | 27 | The generate method without block arguments requires less typing and 28 | defines and renders the pdf document in one shot. Almost all of the 29 | examples are coded this way. 30 | TEXT 31 | end 32 | 33 | example eval: false, standalone: true do 34 | # Assignment 35 | pdf = Prawn::Document.new 36 | pdf.text('Hello World') 37 | pdf.render_file('assignment.pdf') 38 | 39 | # Implicit Block 40 | Prawn::Document.generate('implicit.pdf') do 41 | text 'Hello World' 42 | end 43 | 44 | # Explicit Block 45 | Prawn::Document.generate('explicit.pdf') do |pdf| 46 | pdf.text('Hello World') 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /manual/basic_concepts/cursor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Cursor' 7 | 8 | text do 9 | prose <<~TEXT 10 | We normally write our documents from top to bottom and it is no different 11 | with Prawn. Even if the origin is on the bottom left corner we still fill 12 | the page from the top to the bottom. In other words the cursor for 13 | inserting content starts on the top of the page. 14 | 15 | Most of the functions that insert content on the page will start at the 16 | current cursor position and proceed to the bottom of the page. 17 | 18 | The following snippet shows how the cursor behaves when we add some text 19 | to the page and demonstrates some of the helpers to manage the cursor 20 | position. The cursor method returns the current cursor 21 | position. 22 | TEXT 23 | end 24 | 25 | example axes: true do 26 | text "the cursor is here: #{cursor}" 27 | text "now it is here: #{cursor}" 28 | 29 | move_down 100 30 | text "on the first move the cursor went down to: #{cursor}" 31 | 32 | move_up 50 33 | text "on the second move the cursor went up to: #{cursor}" 34 | 35 | move_cursor_to 50 36 | text "on the last move the cursor went directly to: #{cursor}" 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /manual/basic_concepts/measurement.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Measurement Extensions' 7 | 8 | text do 9 | prose <<~TEXT 10 | The base unit in Prawn is the PDF Point. One PDF Point is equal to 1/72 11 | of an inch. 12 | 13 | There is no need to waste time converting this measure. Prawn provides 14 | helpers for converting from other measurements to PDF Points. 15 | 16 | Just require "prawn/measurement_extensions" and it will mix 17 | some helpers onto Numeric for converting common measurement 18 | units to PDF Points. 19 | TEXT 20 | end 21 | 22 | example do 23 | require 'prawn/measurement_extensions' 24 | 25 | %i[mm cm dm m in yd ft].each do |measurement| 26 | text "1 #{measurement} in PDF Points: #{1.public_send(measurement)} pt" 27 | move_down 5.mm 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /manual/basic_concepts/origin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Origin' 7 | 8 | text do 9 | prose <<~TEXT 10 | This is the most important concept you need to learn about Prawn: 11 | 12 | PDF documents have the origin [0,0] at the bottom-left corner 13 | of the page. 14 | 15 | A bounding box is a structure which provides boundaries for inserting 16 | content. A bounding box also has the property of relocating the origin to 17 | its relative bottom-left corner. However, be aware that the location 18 | specified when creating a bounding box is its top-left corner, not 19 | bottom-left (hence the [100, 300] coordinates below). 20 | 21 | Even if you never create a bounding box explicitly, each document already 22 | comes with one called the margin box. This initial bounding box is the 23 | one responsible for the document margins. 24 | 25 | So practically speaking the origin of a page on a default generated 26 | document isn't the absolute bottom left corner but the bottom left corner 27 | of the margin box. 28 | 29 | The following snippet strokes a circle on the margin box origin. Then 30 | strokes the boundaries of a bounding box and a circle on its origin. 31 | TEXT 32 | end 33 | 34 | example axes: true do 35 | stroke_circle [0, 0], 10 36 | 37 | bounding_box([100, 200], width: 300, height: 100) do 38 | stroke_bounds 39 | stroke_circle [0, 0], 10 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /manual/basic_concepts/other_cursor_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Other Cursor Helpers' 7 | 8 | text do 9 | prose <<~TEXT 10 | Another group of helpers for changing the cursor position are the pad 11 | methods. They accept a numeric value and a block. pad will 12 | use the numeric value to move the cursor down both before and after the 13 | block content. pad_top will only move the cursor before the 14 | block while pad_bottom will only move after. 15 | 16 | float is a method for not changing the cursor. Pass it a 17 | block and the cursor will remain on the same place when the block 18 | returns. 19 | TEXT 20 | end 21 | 22 | example new_page: true do 23 | stroke_horizontal_rule 24 | pad(20) { text 'Text padded both before and after.' } 25 | 26 | stroke_horizontal_rule 27 | pad_top(20) { text 'Text padded on the top.' } 28 | 29 | stroke_horizontal_rule 30 | pad_bottom(20) { text 'Text padded on the bottom.' } 31 | 32 | stroke_horizontal_rule 33 | move_down 30 34 | 35 | text 'Text written before the float block.' 36 | 37 | float do 38 | move_down 30 39 | bounding_box([0, cursor], width: 200) do 40 | text 'Text written inside the float block.' 41 | stroke_bounds 42 | end 43 | end 44 | 45 | text 'Text written after the float block.' 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /manual/basic_concepts/view.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'View' 7 | 8 | text do 9 | prose <<~TEXT 10 | The recommended way to extend Prawn's functionality is to include the 11 | Prawn::View mixin in your own class, which will make all 12 | Prawn::Document methods available to your custom objects. 13 | 14 | This approach is preferred over inheriting from 15 | Prawn::Document, as your state will be kept completely 16 | separate from Prawn::Document's, thus avoiding accidental 17 | method collisions. 18 | 19 | Note that Prawn::View lazily instantiates a 20 | Prawn::Document with default initialization settings, such 21 | as page size, layout, margins, etc. 22 | 23 | By defining your own document method you will be able to 24 | override those settings and initialize a Prawn::Document to 25 | your heart's content. This method will be called repeatedly by 26 | Prawn::View, so be sure to memoize the object by assigning 27 | it to an instance variable via the ||= operator. 28 | TEXT 29 | end 30 | 31 | # rubocop: disable Lint/ConstantDefinitionInBlock 32 | example eval: false, standalone: true do 33 | class Greeter 34 | include Prawn::View 35 | 36 | def initialize(name) 37 | @name = name 38 | end 39 | 40 | def document 41 | @document ||= Prawn::Document.new(page_size: 'A4', margin: 30) 42 | end 43 | 44 | def say_hello 45 | font('Courier') do 46 | text("Hello, #{@name}!") 47 | end 48 | end 49 | end 50 | 51 | greeter = Greeter.new('Gregory') 52 | 53 | greeter.say_hello 54 | 55 | greeter.save_as('greetings.pdf') 56 | end 57 | # rubocop: enable Lint/ConstantDefinitionInBlock 58 | end 59 | -------------------------------------------------------------------------------- /manual/bounding_box.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Bounding Box') 8 | 9 | prose <<-TEXT 10 | Bounding boxes are the basic containers for structuring the content 11 | flow. Even being low level building blocks sometimes their simplicity is 12 | very welcome. 13 | 14 | The examples show: 15 | TEXT 16 | 17 | list( 18 | 'How to create bounding boxes with specific dimensions', 19 | 'How to inspect the current bounding box for its coordinates', 20 | 'Stretchy bounding boxes', 21 | 'Nested bounding boxes', 22 | 'Indent blocks', 23 | ) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /manual/bounding_box/bounds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Bounding Box Creation' 7 | 8 | text do 9 | prose <<~TEXT 10 | The bounds method returns the current bounding box. This is 11 | useful because the Prawn::BoundingBox exposes some nice 12 | boundary helpers. 13 | 14 | top, bottom, left and 15 | right methods return the bounding box boundaries relative to 16 | its translated origin. top_left, top_right, 17 | bottom_left and bottom_right return those 18 | boundaries pairs inside arrays. 19 | 20 | All these methods have an "absolute" version like 21 | absolute_right. The absolute version returns the same 22 | boundary relative to the page absolute coordinates. 23 | 24 | The following snippet shows the boundaries for the margin box side by 25 | side with the boundaries for a custom bounding box. 26 | TEXT 27 | end 28 | 29 | example new_page: true do 30 | def print_coordinates 31 | text("top: #{bounds.top}") 32 | text("bottom: #{bounds.bottom}") 33 | text("left: #{bounds.left}") 34 | text("right: #{bounds.right}") 35 | 36 | move_down(10) 37 | 38 | text("absolute top: #{Float(bounds.absolute_top).round(2)}") 39 | text("absolute bottom: #{Float(bounds.absolute_bottom).round(2)}") 40 | text("absolute left: #{Float(bounds.absolute_left).round(2)}") 41 | text("absolute right: #{Float(bounds.absolute_right).round(2)}") 42 | end 43 | 44 | move_down 20 45 | 46 | text 'Margin box bounds:' 47 | move_down 5 48 | print_coordinates 49 | 50 | bounding_box([250, cursor + 140], width: 200, height: 150) do 51 | text 'This bounding box bounds:' 52 | move_down 5 53 | print_coordinates 54 | transparent(0.5) { stroke_bounds } 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /manual/bounding_box/canvas.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Canvas' 7 | 8 | text do 9 | prose <<~TEXT 10 | The origin example already mentions that a new document already comes 11 | with a margin box whose bottom left corner is used as the origin for 12 | calculating coordinates. 13 | 14 | What has not been told is that there is one helper for "bypassing" the 15 | margin box: canvas. This method is a shortcut for creating a 16 | bounding box mapped to the absolute coordinates and evaluating the code 17 | inside it. 18 | 19 | The following snippet draws a circle on each of the four absolute 20 | corners. 21 | TEXT 22 | end 23 | 24 | example do 25 | canvas do 26 | fill_circle [bounds.left, bounds.top], 30 27 | fill_circle [bounds.right, bounds.top], 30 28 | fill_circle [bounds.right, bounds.bottom], 30 29 | fill_circle [0, 0], 30 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /manual/bounding_box/creation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Bounding Box Creation' 7 | 8 | text do 9 | prose <<~TEXT 10 | If you've read the basic concepts examples you probably know that the 11 | origin of a page is on the bottom left corner and that the content flows 12 | from top to bottom. 13 | 14 | You also know that a Bounding Box is a structure for helping the content 15 | flow. 16 | 17 | A bounding box can be created with the bounding_box method. 18 | Just provide the top left corner, a required :width option 19 | and an optional :height. 20 | TEXT 21 | end 22 | 23 | example do 24 | bounding_box([200, cursor - 100], width: 200, height: 100) do 25 | text 'Just your regular bounding box' 26 | 27 | transparent(0.5) { stroke_bounds } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /manual/bounding_box/indentation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Bounding Box Indentation' 7 | 8 | text do 9 | prose <<~TEXT 10 | Sometimes you just need to indent a portion of the contents of a bounding 11 | box, and using a nested bounding box is pure overkill. The 12 | indent method is what you might need. 13 | 14 | Just provide a number for it to indent all content generated inside the 15 | block. 16 | TEXT 17 | end 18 | 19 | example new_page: true do 20 | text 'No indentation on the margin box.' 21 | indent(20) do 22 | text 'Some indentation inside an indent block.' 23 | end 24 | move_down 20 25 | 26 | bounding_box([50, cursor], width: 400, height: cursor) do 27 | transparent(0.5) { stroke_bounds } 28 | 29 | move_down 10 30 | text 'No indentation inside this bounding box.' 31 | indent(40) do 32 | text 'Inside an indent block. And so is this horizontal line:' 33 | 34 | stroke_horizontal_rule 35 | end 36 | move_down 10 37 | text 'No indentation' 38 | 39 | move_down 20 40 | indent(60) do 41 | text 'Another indent block.' 42 | 43 | bounding_box([0, cursor], width: 200) do 44 | text 'Note that this bounding box coordinates are relative to the indent block' 45 | 46 | transparent(0.5) { stroke_bounds } 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /manual/bounding_box/nesting.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Nesting Bounding Boxes' 7 | 8 | text do 9 | prose <<~TEXT 10 | Normally when we provide the top left corner of a bounding box we express 11 | the coordinates relative to the margin box. This is not the case when we 12 | have nested bounding boxes. Once nested the inner bounding box 13 | coordinates are relative to the outer bounding box. 14 | 15 | This example shows some nested bounding boxes with fixed and stretchy 16 | heights. Note how the cursor method returns coordinates 17 | relative to the current bounding box. 18 | TEXT 19 | end 20 | 21 | example new_page: true do 22 | def box_content(string) 23 | text(string) 24 | transparent(0.5) { stroke_bounds } 25 | end 26 | 27 | gap = 20 28 | bounding_box([50, cursor], width: 400, height: 200) do 29 | box_content('Fixed height') 30 | 31 | bounding_box([gap, cursor - gap], width: 300) do 32 | text 'Stretchy height' 33 | 34 | bounding_box([gap, bounds.top - gap], width: 100) do 35 | text 'Stretchy height' 36 | transparent(0.5) do 37 | dash(1) 38 | stroke_bounds 39 | undash 40 | end 41 | end 42 | 43 | bounding_box([gap * 7, bounds.top - gap], width: 100, height: 50) do 44 | box_content('Fixed height') 45 | end 46 | 47 | transparent(0.5) do 48 | dash(1) 49 | stroke_bounds 50 | undash 51 | end 52 | end 53 | 54 | bounding_box([gap, cursor - gap], width: 300, height: 50) do 55 | box_content('Fixed height') 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /manual/bounding_box/recursive_boxes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Recursive Boxes' 7 | 8 | text do 9 | prose <<~TEXT 10 | This example is mostly just for fun, and shows how nested bounding boxes 11 | can simplify calculations. See the "Bounding Box" section of the manual 12 | for more basic uses. 13 | TEXT 14 | end 15 | 16 | example do 17 | def combine(horizontal_span, vertical_span) 18 | vertical_span.flat_map do |y| 19 | horizontal_span.zip([y] * horizontal_span.size) 20 | end 21 | end 22 | 23 | def recurse_bounding_box(max_depth = 4, depth = 1) 24 | width = (bounds.width - 15) / 2 25 | height = (bounds.height - 15) / 2 26 | left_top_corners = combine([5, bounds.right - width - 5], [bounds.top - 5, height + 5]) 27 | left_top_corners.each do |lt| 28 | bounding_box(lt, width: width, height: height) do 29 | stroke_bounds 30 | recurse_bounding_box(max_depth, depth + 1) if depth < max_depth 31 | end 32 | end 33 | end 34 | 35 | recurse_bounding_box 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /manual/bounding_box/stretchy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Stretchy Bounding Box' 7 | 8 | text do 9 | prose <<~TEXT 10 | Bounding Boxes accept an optional :height parameter. Unless 11 | it is provided the bounding box will be stretchy. It will expand the 12 | height to fit all content generated inside it. 13 | TEXT 14 | end 15 | 16 | example do 17 | y_position = cursor 18 | bounding_box([0, y_position], width: 200, height: 100) do 19 | text 'This bounding box has a height of 100. If this text gets too large ' \ 20 | 'it will flow to the next page.' 21 | 22 | transparent(0.5) { stroke_bounds } 23 | end 24 | 25 | bounding_box([300, y_position], width: 200) do 26 | text 'This bounding box has variable height. No matter how much text is ' \ 27 | 'written here, the height will expand to fit.' 28 | 29 | text ' _' * 100 30 | 31 | text ' *' * 100 32 | 33 | transparent(0.5) { stroke_bounds } 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /manual/contents.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Generates the Prawn by example manual. 4 | 5 | require_relative 'example_helper' 6 | 7 | def prawn_manual_document 8 | old_default_external_encoding = Encoding.default_external 9 | Encoding.default_external = Encoding::UTF_8 10 | 11 | Prawn::ManualBuilder::Example.new( 12 | skip_page_creation: true, 13 | page_size: 'FOLIO', 14 | ) do 15 | load_page('', 'cover') 16 | load_page('', 'how_to_read_this_manual') 17 | 18 | # Core chapters 19 | load_package('basic_concepts') 20 | load_package('graphics') 21 | load_package('text') 22 | load_package('bounding_box') 23 | 24 | # Remaining chapters 25 | load_package('layout') 26 | load_page('', 'table') 27 | load_package('images') 28 | load_package('document_and_page_options') 29 | load_package('outline') 30 | load_package('repeatable_content') 31 | load_package('security') 32 | end 33 | ensure 34 | Encoding.default_external = old_default_external_encoding 35 | end 36 | -------------------------------------------------------------------------------- /manual/cover.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | doc.move_down(200) 8 | 9 | doc.image( 10 | "#{Prawn::DATADIR}/images/prawn.png", 11 | scale: 0.9, 12 | at: [0, doc.cursor - 60], 13 | ) 14 | 15 | doc.formatted_text_box( 16 | [{ text: "Prawn\n", font: 'DejaVu', styles: [:bold], size: 85 }], 17 | at: [160, doc.cursor - 50], 18 | ) 19 | 20 | doc.formatted_text_box( 21 | [{ text: 'by example', font: 'Iosevka', size: 58 }], 22 | at: [165, doc.cursor - 130], 23 | ) 24 | 25 | unless ENV['CI'] 26 | git_commit = 27 | if Dir.exist?(File.expand_path('../.git', __dir__)) 28 | commit = `git show --pretty=%h` 29 | "git commit: #{commit.lines.first}" 30 | else 31 | '' 32 | end 33 | 34 | doc.canvas do 35 | v_text = [ 36 | { 37 | text: "Last Update: #{Time.now.strftime('%Y-%m-%d')}\n" \ 38 | "Prawn Version: #{Prawn::VERSION}\n#{git_commit}", 39 | font: 'DejaVu', 40 | size: 12, 41 | }, 42 | ] 43 | h = doc.height_of_formatted(v_text) 44 | 45 | doc.formatted_text_box(v_text, at: [370, h + 50]) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /manual/document_and_page_options.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Document and Page Options') 8 | 9 | prose <<-TEXT 10 | So far we've already seen how to create new documents and start new 11 | pages. This chapter expands on the previous examples by showing other 12 | options avialable. Some of the options are only available when creating 13 | new documents. 14 | 15 | The examples show: 16 | TEXT 17 | 18 | list( 19 | 'How to configure page size', 20 | 'How to configure page margins', 21 | 'How to use a background image', 22 | 'How to add metadata to the generated PDF', 23 | ) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /manual/document_and_page_options/background.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Background' 7 | 8 | text do 9 | prose <<~TEXT 10 | Pass an image path to the :background option and it will be 11 | used as the background for all pages. 12 | 13 | This option can only be used on document creation. 14 | TEXT 15 | end 16 | 17 | example eval: false, standalone: true do 18 | img = "#{Prawn::DATADIR}/images/letterhead.jpg" 19 | 20 | Prawn::Document.generate('example.pdf', background: img, margin: 100) do 21 | text 'My report caption', size: 18, align: :right 22 | 23 | move_down font.height * 2 24 | 25 | text 'Here is my text explaining this report. ' * 20, 26 | size: 12, 27 | align: :left, 28 | leading: 2 29 | 30 | move_down font.height 31 | 32 | text "I'm using a soft background. " * 40, 33 | size: 12, 34 | align: :left, 35 | leading: 2 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /manual/document_and_page_options/metadata.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Document Metadata' 7 | 8 | text do 9 | prose <<~TEXT 10 | To set the document metadata just pass a hash to the :info 11 | option when creating new documents. 12 | 13 | The keys in the example below are arbitrary, so you may add whatever keys 14 | you want. 15 | TEXT 16 | end 17 | 18 | example eval: false, standalone: true do 19 | info = { 20 | Title: 'My title', 21 | Author: 'John Doe', 22 | Subject: 'My Subject', 23 | Keywords: 'test metadata ruby pdf dry', 24 | Creator: 'ACME Soft App', 25 | Producer: 'Prawn', 26 | CreationDate: Time.now, 27 | } 28 | 29 | Prawn::Document.generate('example.pdf', info: info) do 30 | text 'This is a test of setting metadata properties via the info option.' 31 | text 'While the keys are arbitrary, the above example sets common attributes.' 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /manual/document_and_page_options/page_margins.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Page Margins' 7 | 8 | text do 9 | prose <<~TEXT 10 | The default margin for pages is 0.5 inch but you can change that with the 11 | :margin option or if you'd like to have different margins 12 | you can use the :left_margin, :right_margin, 13 | :top_margin, :bottom_margin options. 14 | 15 | These options are available both for starting new pages and creating new 16 | documents. 17 | TEXT 18 | end 19 | 20 | example eval: false, standalone: true do 21 | Prawn::Document.generate('example.pdf', margin: 100) do 22 | text '100 pts margins.' 23 | stroke_bounds 24 | 25 | start_new_page(left_margin: 300) 26 | text '300 pts margin on the left.' 27 | stroke_bounds 28 | 29 | start_new_page(top_margin: 300) 30 | text '300 pts margin both on the top and on the left. Notice that whenever ' \ 31 | 'you set an option for a new page it will remain the default for the ' \ 32 | 'following pages.' 33 | stroke_bounds 34 | 35 | start_new_page(margin: 50) 36 | text '50 pts margins. Using the margin option will reset previous specific ' \ 37 | 'calls to left, right, top and bottom margins.' 38 | stroke_bounds 39 | 40 | start_new_page(margin: [50, 100, 150, 200]) 41 | text 'There is also the shorthand CSS like syntax used here.' 42 | stroke_bounds 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /manual/document_and_page_options/page_size.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Page Size' 7 | 8 | text do 9 | prose <<~TEXT 10 | Prawn comes with support for most of the common page sizes so you'll only 11 | need to provide specific values if your intended format is not supported. 12 | To see a list with all supported sizes take a look at 13 | PDF::Core::PageGeometry. 14 | 15 | To define the size use :page_size when creating new 16 | documents and :size when starting new pages. The default 17 | page size for new documents is LETTER (612.00 x 792.00). 18 | 19 | You may also define the orientation of the page to be either portrait 20 | (default) or landscape. Use :page_layout when creating new 21 | documents and :layout when starting new pages. 22 | TEXT 23 | end 24 | 25 | example eval: false, standalone: true do 26 | Prawn::Document.generate( 27 | 'example.pdf', 28 | page_size: 'EXECUTIVE', 29 | page_layout: :landscape, 30 | ) do 31 | text 'EXECUTIVE landscape page.' 32 | 33 | custom_size = [275, 326] 34 | 35 | ['A4', 'TABLOID', 'B7', custom_size].each do |size| 36 | start_new_page(size: size, layout: :portrait) 37 | text "#{size} portrait page." 38 | 39 | start_new_page(size: size, layout: :landscape) 40 | text "#{size} landscape page." 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /manual/document_and_page_options/print_scaling.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Print Scaling' 7 | 8 | text do 9 | prose <<~TEXT 10 | (Optional; PDF 1.6) The page scaling option to be selected when a print 11 | dialog is displayed for this document. Valid values are 12 | None, which indicates that the print dialog should reflect 13 | no page scaling, and AppDefault, which indicates that 14 | applications should use the current print scaling. If this entry has an 15 | unrecognized value, applications should use the current print scaling. 16 | Default value: AppDefault. 17 | 18 | Note: If the print dialog is suppressed and its parameters are provided 19 | directly by the application, the value of this entry should still be 20 | used. 21 | TEXT 22 | end 23 | 24 | example eval: false, standalone: true do 25 | Prawn::Document.generate( 26 | 'example.pdf', 27 | page_layout: :landscape, 28 | print_scaling: :none, 29 | ) do 30 | text 'When you print this document, the scale to fit in print preview ' \ 31 | 'should be disabled by default.' 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /manual/graphics.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Graphics') 8 | 9 | prose <<-TEXT 10 | Here we show all the drawing methods provided by Prawn. Use them to draw 11 | the most beautiful imaginable things. 12 | 13 | Most of the content that you'll add to your pdf document will use the 14 | graphics package. Even text is rendered on a page just like a rectangle 15 | so even if you never use any of the shapes described here you should at 16 | least read the basic examples. 17 | 18 | The examples show: 19 | TEXT 20 | 21 | list( 22 | 'All the possible ways that you can fill or stroke shapes on a page', 23 | 'How to draw all the shapes that Prawn has to offer from a measly ' \ 24 | 'line to a mighty polygon or ellipse', 25 | 'The configuration options for stroking lines and filling shapes', 26 | 'How to apply transformations to your drawing space', 27 | ) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /manual/graphics/blend_mode.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Blend Modes' 7 | 8 | text do 9 | prose <<~TEXT 10 | Blend modes can be used to change the way two layers (images, graphics, 11 | text, etc.) are blended together. The blend_mode method 12 | accepts a single blend mode or an array of blend modes. PDF viewers 13 | should blend the layers based on the first recognized blend mode. 14 | 15 | Valid blend modes in v1.4 of the PDF spec include :Normal, 16 | :Multiply, :Screen, :Overlay, 17 | :Darken, :Lighten, :ColorDodge, 18 | :ColorBurn, :HardLight, 19 | :SoftLight, :Difference, 20 | :Exclusion, :Hue, :Saturation, 21 | :Color, and :Luminosity. 22 | TEXT 23 | end 24 | 25 | example new_page: true do 26 | # https://commons.wikimedia.org/wiki/File:Blend_modes_2.-bottom-layer.jpg#/media/File:Blend_modes_2.-bottom-layer.jpg 27 | bottom_layer = "#{Prawn::DATADIR}/images/blend_modes_bottom_layer.jpg" 28 | 29 | # https://commons.wikimedia.org/wiki/File:Blend_modes_1.-top-layer.jpg#/media/File:Blend_modes_1.-top-layer.jpg 30 | top_layer = "#{Prawn::DATADIR}/images/blend_modes_top_layer.jpg" 31 | 32 | blend_modes = %i[ 33 | Normal Multiply Screen Overlay Darken Lighten ColorDodge 34 | ColorBurn HardLight SoftLight Difference Exclusion Hue 35 | Saturation Color Luminosity 36 | ] 37 | blend_modes.each_with_index do |blend_mode, index| 38 | x = 5 + (index % 4 * 130) 39 | y = cursor - (index / 4 * 195) - 5 40 | 41 | image bottom_layer, at: [x, y], fit: [120, 120] 42 | blend_mode(blend_mode) do 43 | image top_layer, at: [x, y], fit: [120, 120] 44 | end 45 | 46 | y -= 130 47 | 48 | fill_color '009ddc' 49 | fill_rectangle [x, y], 75, 25 50 | blend_mode(blend_mode) do 51 | fill_color 'fdb827' 52 | fill_rectangle [x + 50, y], 70, 25 53 | end 54 | 55 | y -= 30 56 | 57 | fill_color '000000' 58 | text_box blend_mode.to_s, at: [x, y] 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /manual/graphics/circle_and_ellipse.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Circles and Ellipses' 7 | 8 | text do 9 | prose <<~TEXT 10 | To define a circle all you need is the center point and the 11 | radius. 12 | 13 | To define an ellipse you provide the center point and two 14 | radii (or axes) values. If the second radius value is omitted, both radii 15 | will be equal and you will end up drawing a circle. 16 | TEXT 17 | end 18 | 19 | example axes: true do 20 | stroke_circle [100, 300], 100 21 | 22 | fill_ellipse [200, 100], 100, 50 23 | 24 | fill_ellipse [400, 100], 50 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /manual/graphics/color.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Color' 7 | 8 | text do 9 | prose <<~TEXT 10 | We can change the stroke and fill colors providing an HTML rgb 6 digit 11 | color code string ("AB1234") or 4 values for CMYK. 12 | TEXT 13 | end 14 | 15 | example do 16 | # Fill with Orange using RGB (Unlike css, there is no leading #) 17 | fill_color 'FF8844' 18 | fill_polygon [50, 150], [150, 200], [250, 150], [250, 50], [150, 0], [50, 50] 19 | 20 | # Stroke with Purple using CMYK 21 | stroke_color 50, 100, 0, 0 22 | stroke_rectangle [300, 300], 200, 100 23 | 24 | # Both together 25 | fill_and_stroke_circle [400, 100], 50 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /manual/graphics/common_lines.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Common Lines' 7 | 8 | text do 9 | prose <<~TEXT 10 | Prawn provides helpers for drawing some commonly used lines: 11 | 12 | vertical_line and horizontal_line do just what 13 | their names imply. Specify the start and end point at a fixed coordinate 14 | to define the line. 15 | 16 | horizontal_rule draws a horizontal line on the current 17 | bounding box from border to border, using the current y position. 18 | TEXT 19 | end 20 | 21 | example axes: true do 22 | stroke_color 'ff0000' 23 | 24 | stroke do 25 | # just lower the current y position 26 | move_down 50 27 | horizontal_rule 28 | 29 | vertical_line 100, 300, at: 50 30 | 31 | horizontal_line 200, 500, at: 150 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /manual/graphics/fill_and_stroke.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Fill and Stroke' 7 | 8 | text do 9 | prose <<~TEXT 10 | There are two drawing primitives in Prawn: fill and 11 | stroke. 12 | 13 | These are the methods that actually draw stuff on the document. All the 14 | other drawing shapes like rectangle, circle or 15 | line_to define drawing paths. These paths need to be either 16 | stroked or filled to gain form on the document. 17 | 18 | Calling these methods without a block will act on the drawing path that 19 | has been defined prior to the call. 20 | 21 | Calling with a block will act on the drawing path set within the block. 22 | 23 | Most of the methods which define drawing paths have methods of the same 24 | name starting with stroke_ and fill_ which create the drawing path and 25 | then stroke or fill it. 26 | TEXT 27 | end 28 | 29 | example axes: true do 30 | # No block 31 | line [0, 200], [100, 150] 32 | stroke 33 | 34 | rectangle [0, 100], 100, 100 35 | fill 36 | 37 | # With block 38 | stroke { line [200, 200], [300, 150] } 39 | fill { rectangle [200, 100], 100, 100 } 40 | 41 | # Method hook 42 | stroke_line [400, 200], [500, 150] 43 | fill_rectangle [400, 100], 100, 100 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /manual/graphics/fill_rules.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Fill Rules' 7 | 8 | text do 9 | prose <<~TEXT 10 | Prawn's fill operators (fill and 11 | fill_and_stroke both accept a :fill_rule 12 | option. These rules determine which parts of the page are counted as 13 | "inside" vs. "outside" the path. There are two fill rules: 14 | TEXT 15 | 16 | list( 17 | ':nonzero_winding_number (default): a point is inside the ' \ 18 | 'path if a ray from that point to infinity crosses a nonzero "net ' \ 19 | 'number" of path segments, where path segments intersecting in one ' \ 20 | 'direction are counted as positive and those in the other direction ' \ 21 | 'negative.', 22 | ':even_odd: A point is inside the path if a ray from that ' \ 23 | 'point to infinity crosses an odd number of path segments, regardless ' \ 24 | 'of direction.', 25 | ) 26 | 27 | prose <<~TEXT 28 | The differences between the fill rules only come into play with complex 29 | paths; they are identical for simple shapes. 30 | TEXT 31 | end 32 | 33 | example axes: true do 34 | pentagram = [[181, 95], [0, 36], [111, 190], [111, 0], [0, 154]] 35 | 36 | stroke_color 'ff0000' 37 | line_width 2 38 | 39 | text_box 'Nonzero Winding Number', at: [10, 200] 40 | polygon(*pentagram.map { |x, y| [x + 50, y] }) 41 | fill_and_stroke 42 | 43 | text_box 'Even-Odd', at: [330, 200] 44 | polygon(*pentagram.map { |x, y| [x + 330, y] }) 45 | fill_and_stroke(fill_rule: :even_odd) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /manual/graphics/gradients.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Gradients' 7 | 8 | text do 9 | prose <<~TEXT 10 | Note that because of the way PDF renders radial gradients in order to get 11 | solid fill your start circle must be fully inside your end circle. 12 | Otherwise you will get triangle fill like illustrated in the example 13 | below. 14 | TEXT 15 | end 16 | 17 | example new_page: true do 18 | self.line_width = 10 19 | 20 | # Linear Gradients 21 | fill_gradient [0, 250], [100, 150], 'ff0000', '0000ff' 22 | fill_rectangle [0, 250], 100, 100 23 | 24 | stroke_gradient [150, 150], [250, 250], '00ffff', 'ffff00' 25 | stroke_rectangle [150, 250], 100, 100 26 | 27 | fill_gradient [300, 250], [400, 150], 'ff0000', '0000ff' 28 | stroke_gradient [300, 150], [400, 250], '00ffff', 'ffff00' 29 | fill_and_stroke_rectangle [300, 250], 100, 100 30 | 31 | rotate 45, origin: [500, 200] do 32 | stops = { 0 => 'ff0000', 0.6 => '999900', 0.8 => '00cc00', 1 => '4444ff' } 33 | fill_gradient from: [460, 240], to: [540, 160], stops: stops 34 | fill_rectangle [460, 240], 80, 80 35 | end 36 | 37 | # Radial gradients 38 | fill_gradient [50, 50], 0, [50, 50], 70.71, 'ff0000', '0000ff' 39 | fill_rectangle [0, 100], 100, 100 40 | 41 | stroke_gradient [200, 50], 45, [200, 50], 70.71, '00ffff', 'ffff00' 42 | stroke_rectangle [150, 100], 100, 100 43 | 44 | stroke_gradient [350, 50], 45, [350, 50], 70.71, '00ffff', 'ffff00' 45 | fill_gradient [350, 50], 0, [350, 50], 70.71, 'ff0000', '0000ff' 46 | fill_and_stroke_rectangle [300, 100], 100, 100 47 | 48 | fill_gradient [500, 100], 50, [500, 0], 0, 'ff0000', '0000ff' 49 | fill_rectangle [450, 100], 100, 100 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /manual/graphics/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Stroke Axis' 7 | 8 | text do 9 | prose <<~TEXT 10 | To produce this manual we use the stroke_axis helper method 11 | within the examples. 12 | 13 | stroke_axis prints the x and y axis for the current bounding 14 | box with markers in 100 increments. The defaults can be changed with 15 | various options. 16 | 17 | Note that the examples define a custom :height option so 18 | that only the example canvas is used (as seen with the output of the 19 | first line of the example code). 20 | TEXT 21 | end 22 | 23 | example do 24 | stroke_axis 25 | stroke_axis( 26 | at: [70, 70], 27 | height: 200, 28 | step_length: 50, 29 | negative_axes_length: 5, 30 | color: '0000FF', 31 | ) 32 | stroke_axis( 33 | at: [140, 140], 34 | width: 200, 35 | height: Integer(cursor) - 140, 36 | step_length: 20, 37 | negative_axes_length: 40, 38 | color: 'FF0000', 39 | ) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /manual/graphics/line_width.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Line Width' 7 | 8 | text do 9 | prose <<~TEXT 10 | The line_width= method sets the stroke width for subsequent 11 | stroke calls. 12 | 13 | Since Ruby assumes that an unknown variable on the left hand side of an 14 | assignment is a local temporary, rather than a setter method, if you are 15 | using the block call to Prawn::Document.generate without 16 | passing params you will need to call line_width on self. 17 | TEXT 18 | end 19 | 20 | example axes: true do 21 | y = 225 22 | 23 | 3.times do |i| 24 | case i 25 | when 0 then line_width = 10 # This call will have no effect 26 | when 1 then self.line_width = 10 27 | when 2 then self.line_width = 25 28 | end 29 | 30 | stroke do 31 | horizontal_line 25, 75, at: y 32 | rectangle [225, y + 25], 50, 50 33 | circle [450, y], 25 34 | end 35 | 36 | y -= 90 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /manual/graphics/lines_and_curves.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Lines and Curves' 7 | 8 | text do 9 | prose <<~TEXT 10 | Prawn supports drawing both lines and curves starting either at the 11 | current position, or from a specified starting position. 12 | 13 | line_to and curve_to set the drawing path from 14 | the current drawing position to the specified point. The initial drawing 15 | position can be set with move_to. They are useful when you 16 | want to chain successive calls because the drawing position will be set 17 | to the specified point afterwards. 18 | 19 | line and curve set the drawing path between the 20 | two specified points. 21 | 22 | Both curve methods define a Bezier curve bounded by two aditional points 23 | provided as the :bounds param. 24 | TEXT 25 | end 26 | 27 | example axes: true do 28 | # line_to and curve_to 29 | stroke do 30 | move_to 0, 0 31 | line_to 100, 100 32 | line_to 0, 100 33 | curve_to [150, 200], bounds: [[20, 150], [120, 150]] 34 | curve_to [200, 0], bounds: [[150, 200], [450, 10]] 35 | end 36 | 37 | # line and curve 38 | stroke do 39 | line [300, 200], [400, 50] 40 | curve [500, 0], [400, 100], bounds: [[600, 200], [300, 290]] 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /manual/graphics/polygon.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Polygons' 7 | 8 | text do 9 | prose <<~TEXT 10 | Drawing polygons in Prawn is easy, just pass a sequence of points to one 11 | of the polygon family of methods. 12 | 13 | Just like rounded_rectangle we also have 14 | rounded_polygon. The only difference is the radius param 15 | comes before the polygon points. 16 | TEXT 17 | end 18 | 19 | example axes: true do 20 | # Triangle 21 | stroke_polygon [50, 200], [50, 300], [150, 300] 22 | 23 | # Hexagon 24 | fill_polygon [50, 150], [150, 200], [250, 150], [250, 50], [150, 0], [50, 50] 25 | 26 | # Pentagram 27 | pentagon_points = [500, 100], [430, 5], [319, 41], [319, 159], [430, 195] 28 | pentagram_points = [0, 2, 4, 1, 3].map { |i| pentagon_points[i] } 29 | 30 | stroke_rounded_polygon(20, *pentagram_points) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /manual/graphics/rectangle.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Rectangles' 7 | 8 | text do 9 | prose <<~TEXT 10 | To draw a rectangle, just provide the upper-left corner, width and height 11 | to the rectangle method. 12 | 13 | There's also rounded_rectangle. Just provide an additional 14 | radius value for the rounded corners. 15 | TEXT 16 | end 17 | 18 | example axes: true do 19 | stroke do 20 | rectangle [100, 300], 100, 200 21 | 22 | rounded_rectangle [300, 300], 100, 200, 20 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /manual/graphics/rotate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Rotation' 7 | 8 | text do 9 | prose <<~TEXT 10 | This transformation is used to rotate the user space. Give it an angle 11 | and an :origin point about which to rotate and a block. 12 | Everything inside the block will be drawn with the rotated coordinates. 13 | 14 | The angle is in degrees. 15 | 16 | If you omit the :origin option the page origin will be used. 17 | TEXT 18 | end 19 | 20 | example do 21 | fill_circle [270, 180], 2 22 | 23 | 12.times do |i| 24 | rotate(i * 30, origin: [270, 180]) do 25 | stroke_rectangle [350, 200], 80, 40 26 | text_box "Rotated #{i * 30}°", size: 10, at: [360, 185] 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /manual/graphics/scale.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Scaling' 7 | 8 | text do 9 | prose <<~TEXT 10 | This transformation is used to scale the user space. Give it an scale 11 | factor and an :origin point and everything inside the block 12 | will be scaled using the origin point as reference. 13 | 14 | If you omit the :origin option the page origin will be used. 15 | TEXT 16 | end 17 | 18 | example do 19 | width = 100 20 | height = 50 21 | y = 190 22 | 23 | x = 50 24 | stroke_rectangle [x, y], width, height 25 | text_box 'reference rectangle', at: [x + 10, y - 10], width: width - 20 26 | 27 | scale(2, origin: [x, y]) do 28 | stroke_rectangle [x, y], width, height 29 | text_box 'rectangle scaled from upper-left corner', at: [x, y - height - 5], width: width 30 | end 31 | 32 | x = 350 33 | stroke_rectangle [x, y], width, height 34 | text_box 'reference rectangle', at: [x + 10, y - 10], width: width - 20 35 | 36 | scale(2, origin: [x + (width / 2), y - (height / 2)]) do 37 | stroke_rectangle [x, y], width, height 38 | text_box 'rectangle scaled from center', at: [x, y - height - 5], width: width 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /manual/graphics/soft_masks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Soft Masks' 7 | 8 | text do 9 | prose <<~TEXT 10 | Soft masks are used for more complex alpha channel manipulations. You can 11 | use arbitrary drawing functions for creation of soft masks. The resulting 12 | alpha channel is made of greyscale version of the drawing (luminosity 13 | channel to be precise). So while you can use any combination of colors 14 | for soft masks it's easier to use greyscales. Black will result in full 15 | transparency and white will make region fully opaque. 16 | 17 | Soft mask is a part of page graphic state. So if you want to apply soft 18 | mask only to a part of page you need to enclose drawing instructions in 19 | save_graphics_state block. 20 | TEXT 21 | end 22 | 23 | example do 24 | save_graphics_state do 25 | soft_mask do 26 | 0.upto(15) do |i| 27 | fill_color 0, 0, 0, 100.0 / 16.0 * (15 - i) 28 | fill_circle [75 + (i * 25), 100], 60 29 | end 30 | end 31 | 32 | %w[009ddc 963d97 e03a3e f5821f fdb827 61bb46].each_with_index do |color, i| 33 | fill_color color 34 | fill_rectangle [0, 60 + (i * 20)], 600, 20 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /manual/graphics/stroke_cap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Stroke Cap' 7 | 8 | text do 9 | prose <<~TEXT 10 | The cap style defines how the edge of a line or curve will be drawn. 11 | There are three types: :butt (the default), 12 | :round and :projecting_square. 13 | 14 | The difference is better seen with thicker lines. With :butt 15 | lines are drawn starting and ending at the exact points provided. With 16 | both :round and :projecting_square the line is 17 | projected beyond the start and end points. 18 | 19 | Just like line_width= the cap_style= method 20 | needs an explicit receiver to work. 21 | TEXT 22 | end 23 | 24 | example axes: true do 25 | self.line_width = 25 26 | 27 | %i[butt round projecting_square].each_with_index do |cap, i| 28 | self.cap_style = cap 29 | 30 | y = 250 - (i * 100) 31 | stroke_horizontal_line 100, 300, at: y 32 | stroke_circle [400, y], 15 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /manual/graphics/stroke_dash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Stroke Dash Pattern' 7 | 8 | text do 9 | prose <<~TEXT 10 | This sets the dashed pattern for lines and curves. The (dash) length 11 | defines how long each dash will be. 12 | 13 | The :space option defines the length of the space between 14 | the dashes. 15 | 16 | The :phase option defines the start point of the sequence of 17 | dashes and spaces. 18 | 19 | Complex dash patterns can be specified by using an array with alternating 20 | dash/gap lengths for the first parameter (note that the 21 | :space option is ignored in this case). 22 | TEXT 23 | end 24 | 25 | example new_page: true do 26 | move_down 20 27 | dash([1, 2, 3, 2, 1, 5], phase: 6) 28 | stroke_horizontal_line 50, 500 29 | move_down 10 30 | dash([1, 2, 3, 4, 5, 6, 7, 8]) 31 | stroke_horizontal_line 50, 500 32 | 33 | base_y = cursor - 10 34 | 35 | 24.times do |i| 36 | length = (i / 4) + 1 37 | space = length # space between dashes same length as dash 38 | phase = 0 # start with dash 39 | 40 | case i % 4 41 | when 0 then base_y -= 10 42 | when 1 then phase = length # start with space between dashes 43 | when 2 then space = length * 0.5 # space between dashes half as long as dash 44 | when 3 45 | space = length * 0.5 # space between dashes half as long as dash 46 | phase = length # start with space between dashes 47 | end 48 | base_y -= 10 49 | 50 | dash(length, space: space, phase: phase) 51 | stroke_horizontal_line 50, 500, at: base_y - (2 * i) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /manual/graphics/stroke_join.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Stroke Join' 7 | 8 | text do 9 | prose <<~TEXT 10 | The join style defines how the intersection between two lines is drawn. 11 | There are three types: :miter (the default), 12 | :round and :bevel. 13 | 14 | Just like cap_style, the difference between styles is better 15 | seen with thicker lines. 16 | TEXT 17 | end 18 | 19 | example do 20 | self.line_width = 25 21 | 22 | %i[miter round bevel].each_with_index do |style, i| 23 | self.join_style = style 24 | 25 | y = 200 - (i * 100) 26 | stroke do 27 | move_to(100, y) 28 | line_to(200, y + 100) 29 | line_to(300, y) 30 | end 31 | stroke_rectangle [400, y + 75], 50, 50 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /manual/graphics/translate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Translation' 7 | 8 | text do 9 | prose <<~TEXT 10 | This transformation is used to translate the user space. Just provide the 11 | x and y coordinates for the new origin. 12 | TEXT 13 | end 14 | 15 | example axes: true do 16 | 1.upto(3) do |i| 17 | x = i * 50 18 | y = i * 100 19 | translate(x, y) do 20 | # Draw a point on the new origin 21 | fill_circle [0, 0], 2 22 | draw_text "New origin after translation to [#{x}, #{y}]", at: [5, -3] 23 | 24 | stroke_rectangle [100, 50], 200, 30 25 | text_box 'Top left corner at [100, 50]', at: [110, 40], width: 180 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /manual/graphics/transparency.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Transparency' 7 | 8 | text do 9 | prose <<~TEXT 10 | Although the name of the method is transparency, what we are 11 | actually setting is the opacity for fill and stroke. So 0 12 | means completely transparent and 1.0 means completely 13 | opaque. 14 | 15 | You may call it providing one or two values. The first value sets fill 16 | opacity and the second value sets stroke opacity. If the second value is 17 | omitted fill and stroke will have the same opacity. 18 | TEXT 19 | end 20 | 21 | example do 22 | self.line_width = 5 23 | fill_color 'ff0000' 24 | fill_rectangle [0, 100], 500, 100 25 | 26 | fill_color '000000' 27 | stroke_color 'ffffff' 28 | 29 | base_x = 100 30 | [[0.5, 1], 0.5, [1, 0.5]].each do |args| 31 | transparent(*args) do 32 | fill_circle [base_x, 100], 50 33 | stroke_rectangle [base_x - 20, 100], 40, 80 34 | end 35 | 36 | base_x += 150 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /manual/how_to_read_this_manual.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | doc.move_down(Prawn::ManualBuilder::INNER_MARGIN) 8 | 9 | header('How to read this manual') 10 | 11 | prose <<~END_TEXT 12 | This manual is a collection of examples categorized by theme and 13 | organized from the least to the most complex. While it covers most of the 14 | common use cases it is not a comprehensive guide. 15 | 16 | The best way to read it depends on your previous knowledge of Prawn and 17 | what you need to accomplish. 18 | 19 | If you are beginning with Prawn the first chapter will teach you the most 20 | basic concepts and how to create pdf documents. For an overview of the 21 | other features each chapter beyond the first either has a Basics section 22 | (which offer enough insight on the feature without showing all the 23 | advanced stuff you might never use) or is simple enough with only a few 24 | examples. 25 | 26 | Once you understand the basics you might want to come back to this manual 27 | looking for examples that accomplish tasks you need. 28 | 29 | Advanced users are encouraged to go beyond this manual and read the 30 | source code directly if any doubt is not directly covered on this manual. 31 | END_TEXT 32 | 33 | doc.move_down(Prawn::ManualBuilder::RHYTHM + Prawn::ManualBuilder::INNER_MARGIN) 34 | header('Reading the examples') 35 | 36 | prose <<~END_TEXT 37 | The title of each example is the relative path from the Prawn source 38 | manual/ folder. 39 | 40 | The first body of text is the introductory text for the example. 41 | Generally it is a short description of the features illustrated by the 42 | example. 43 | 44 | Next comes the example source code block in fixed width font. 45 | 46 | Most of the example snippets illustrate features that alter the page in 47 | place. The effect of these snippets is shown right below a dashed line. 48 | If it doesn't make sense to evaluate the snippet inline, a box with the 49 | link for the example file is shown instead. 50 | 51 | Note that the stroke_axis method used throughout the manual 52 | is part of standard Prawn. It is defined in this file: 53 | 54 | https://github.com/prawnpdf/prawn/blob/master/lib/prawn/graphics.rb 55 | END_TEXT 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /manual/images.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Images') 8 | 9 | prose <<-TEXT 10 | Embedding images on PDF documents is fairly easy. Prawn supports both JPG 11 | and PNG images. 12 | 13 | The examples show: 14 | TEXT 15 | 16 | list( 17 | 'How to add an image to a page', 18 | 'How place the image on a specific position', 19 | 'How to configure the image dimensions by setting the width and ' \ 20 | 'height or by scaling it', 21 | ) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /manual/images/absolute_position.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Absolute Positioning' 7 | 8 | text do 9 | prose <<~TEXT 10 | One of the options that the image method accepts is 11 | :at. If you've read some of the graphics examples you are 12 | probably already familiar with it. Just provide it the upper-left corner 13 | where you want the image placed. 14 | 15 | While sometimes useful this option won't be practical. Notice that the 16 | cursor won't be moved after the image is rendered and there is nothing 17 | forbidding the text to overlap with the image. 18 | TEXT 19 | end 20 | 21 | example do 22 | y_position = cursor 23 | text "The image won't go below this line of text." 24 | 25 | image "#{Prawn::DATADIR}/images/fractal.jpg", at: [200, y_position] 26 | 27 | text 'And this line of text will go just below the previous one.' 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /manual/images/fit.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Fiting' 7 | 8 | text do 9 | prose <<~TEXT 10 | :fit option is useful when you want the image to have the 11 | maximum size within a container preserving the aspect ratio without 12 | overlapping. 13 | 14 | Just provide the container width and height pair. 15 | TEXT 16 | end 17 | 18 | example do 19 | size = 300 20 | 21 | text 'Using the fit option' 22 | bounding_box([0, cursor], width: size, height: size) do 23 | image "#{Prawn::DATADIR}/images/pigs.jpg", fit: [size, size] 24 | stroke_bounds 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /manual/images/horizontal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Horizontal Positioning' 7 | 8 | text do 9 | prose <<~TEXT 10 | The image may be positioned relatively to the current bounding box. The 11 | horizontal position may be set with the :position option. 12 | 13 | It may be :left, :center, :right 14 | or a number representing an x-offset from the left boundary. 15 | TEXT 16 | end 17 | 18 | example new_page: true do 19 | bounding_box([50, cursor], width: 400, height: 450) do 20 | stroke_bounds 21 | 22 | %i[left center right].each do |position| 23 | text "Image aligned to the #{position}." 24 | image "#{Prawn::DATADIR}/images/stef.jpg", position: position 25 | end 26 | 27 | text 'The next image has a 50 point offset from the left boundary' 28 | image "#{Prawn::DATADIR}/images/stef.jpg", position: 50 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /manual/images/plain_image.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Plain Image' 7 | 8 | text do 9 | prose <<~TEXT 10 | To embed images onto your PDF file use the image method. 11 | It accepts the file path of the image to be loaded and some optional 12 | arguments. 13 | 14 | If only the image path is provided the image will be rendered starting on 15 | the cursor position. No manipulation is done with the image even if it 16 | doesn't fit entirely on the page like the following snippet. 17 | TEXT 18 | end 19 | 20 | example new_page: true do 21 | text 'The image will go right below this line of text.' 22 | image "#{Prawn::DATADIR}/images/pigs.jpg" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /manual/images/scale.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Scaling Images' 7 | 8 | text do 9 | prose <<~TEXT 10 | To scale an image use the :scale option. 11 | 12 | It scales the image proportionally given the provided value. 13 | TEXT 14 | end 15 | 16 | example do 17 | text 'Normal size' 18 | image "#{Prawn::DATADIR}/images/stef.jpg" 19 | move_down 10 20 | 21 | text 'Scaled to 50%' 22 | image "#{Prawn::DATADIR}/images/stef.jpg", scale: 0.5 23 | move_down 10 24 | 25 | text 'Scaled to 200%' 26 | image "#{Prawn::DATADIR}/images/stef.jpg", scale: 2 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /manual/images/vertical.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Vertical Positioning' 7 | 8 | text do 9 | prose <<~TEXT 10 | To set the vertical position of an image use the :vposition 11 | option. 12 | 13 | It may be :top, :center, :bottom 14 | or a number representing the y-offset from the top boundary. 15 | TEXT 16 | end 17 | 18 | example new_page: true do 19 | bounding_box([0, cursor], width: 500, height: 450) do 20 | stroke_bounds 21 | 22 | %i[top center bottom].each do |vposition| 23 | text "Image vertically aligned to the #{vposition}.", valign: vposition 24 | image "#{Prawn::DATADIR}/images/stef.jpg", 25 | position: 220, 26 | vposition: vposition 27 | end 28 | 29 | text_box 'The next image has a 100 point offset from the top boundary', 30 | at: [bounds.width - 110, bounds.top - 10], 31 | width: 100 32 | image "#{Prawn::DATADIR}/images/stef.jpg", 33 | position: :right, 34 | vposition: 100 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /manual/images/width_and_height.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Width and Height' 7 | 8 | text do 9 | prose <<~TEXT 10 | The image size can be set with the :width and 11 | :height options. 12 | 13 | If only one of those is provided, the image will be scaled 14 | proportionally. When both are provided, the image will be stretched to 15 | fit the dimensions without maintaining the aspect ratio. 16 | TEXT 17 | end 18 | 19 | example do 20 | text 'Scale by setting only the width' 21 | image "#{Prawn::DATADIR}/images/pigs.jpg", width: 150 22 | move_down 10 23 | 24 | text 'Scale by setting only the height' 25 | image "#{Prawn::DATADIR}/images/pigs.jpg", height: 80 26 | move_down 10 27 | 28 | text 'Stretch to fit the width and height provided' 29 | image "#{Prawn::DATADIR}/images/pigs.jpg", width: 500, height: 100 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /manual/layout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Layout') 8 | 9 | prose <<-TEXT 10 | Prawn has support for two-dimensional grid based layouts out of the box. 11 | 12 | The examples show: 13 | TEXT 14 | 15 | list( 16 | 'How to define the document grid', 17 | 'How to configure the grid rows and columns gutters', 18 | 'How to create boxes according to the grid', 19 | ) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /manual/layout/boxes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Boxes' 7 | 8 | text do 9 | prose <<~TEXT 10 | After defined the grid is there but nothing happens. To start taking 11 | effect we need to use the grid boxes. 12 | 13 | grid has three different return values based on the 14 | arguments received. With no arguments it will return the grid itself. 15 | With integers it will return the grid box at those indices. With two 16 | arrays it will return a multi-box spanning the region of the two grid 17 | boxes at the arrays indices. 18 | TEXT 19 | end 20 | 21 | example do 22 | # The grid only need to be defined once, but since all the examples should be 23 | # able to run alone we are repeating it on every example 24 | define_grid(columns: 5, rows: 4, gutter: 10) 25 | 26 | grid(0, 0).show 27 | grid(1, 1).show 28 | 29 | grid([2, 2], [3, 3]).show 30 | 31 | grid([0, 4], [3, 4]).show 32 | grid([3, 0], [3, 1]).show 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /manual/layout/content.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Content' 7 | 8 | text do 9 | prose <<~TEXT 10 | Now that we know how to access the boxes we might as well add some 11 | content to them. 12 | 13 | This can be done by taping into the bounding box for a given grid box or 14 | multi-box with the bounding_box method. 15 | TEXT 16 | end 17 | 18 | example do 19 | # The grid only need to be defined once, but since all the examples should be 20 | # able to run alone we are repeating it on every example 21 | define_grid(rows: 4, columns: 5, gutter: 10) 22 | 23 | grid([1, 0], [3, 1]).bounding_box do 24 | text "Adding some content to this multi_box.\n#{' _ ' * 200}" 25 | end 26 | 27 | grid(2, 3).bounding_box do 28 | text "Just a little snippet here.\n#{' _ ' * 10}" 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /manual/layout/simple_grid.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Simple Grid' 7 | 8 | text do 9 | prose <<~TEXT 10 | The document grid on Prawn is just a table-like structure with a defined 11 | number of rows and columns. There are some helpers to create boxes of 12 | content based on the grid coordinates. 13 | 14 | define_grid accepts the following options which are pretty 15 | much self-explanatory: :rows, :columns, 16 | :gutter, :row_gutter, 17 | :column_gutter. 18 | TEXT 19 | end 20 | 21 | example do 22 | # The grid only need to be defined once, but since all the examples should be 23 | # able to run alone we are repeating it on every example 24 | define_grid(columns: 5, rows: 8, gutter: 10) 25 | text 'We defined the grid, roll over to the next page to see its outline' 26 | 27 | start_new_page 28 | grid.show_all 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /manual/outline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Outline') 8 | 9 | prose <<-TEXT 10 | The outline of a PDF document is the table of contents tab you see to 11 | the right or left of your PDF viewer. 12 | 13 | The examples include: 14 | TEXT 15 | 16 | list( 17 | 'How to define sections and pages', 18 | 'How to insert sections and/or pages to a previously defined outline structure', 19 | ) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /manual/outline/add_subsection_to.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Adding a Subsection to the Outline Tree' 7 | 8 | text do 9 | prose <<~TEXT 10 | We have already seen how to define an outline tree sequentially. 11 | 12 | If you'd like to add nodes to the middle of an outline tree the 13 | add_subsection_to may help you. 14 | 15 | It allows you to insert sections to the outline tree at any point. Just 16 | provide the title of the parent section, the 17 | position you want the new subsection to be inserted 18 | :first or :last (defaults to 19 | :last) and a block to declare the subsection. 20 | 21 | The add_subsection_to block doesn't necessarily create new 22 | sections, it may also create new pages. 23 | 24 | If the parent title provided is the title of a page. The page will be 25 | converted into a section to receive the subsection created. 26 | TEXT 27 | end 28 | 29 | example eval: false do 30 | # First we create 10 pages and some default outline 31 | (1..10).each do |index| 32 | text "Page #{index}" 33 | start_new_page 34 | end 35 | 36 | outline.define do 37 | section('Section 1', destination: 1) do 38 | page title: 'Page 2', destination: 2 39 | page title: 'Page 3', destination: 3 40 | end 41 | end 42 | 43 | # Now we will start adding nodes to the previous outline 44 | outline.add_subsection_to('Section 1', :first) do 45 | outline.section('Added later - first position') do 46 | outline.page(title: 'Page 4', destination: 4) 47 | outline.page(title: 'Page 5', destination: 5) 48 | end 49 | end 50 | 51 | outline.add_subsection_to('Section 1') do 52 | outline.page(title: 'Added later - last position', destination: 6) 53 | end 54 | 55 | outline.add_subsection_to('Added later - first position') do 56 | outline.page(title: 'Another page added later', destination: 7) 57 | end 58 | 59 | # The title provided is for a page which will be converted into a section 60 | outline.add_subsection_to('Page 3') do 61 | outline.page(title: 'Last page added', destination: 8) 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /manual/outline/insert_section_after.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Insert Section After' 7 | 8 | text do 9 | prose <<~TEXT 10 | Another way to insert nodes into an existing outline is the 11 | insert_section_after method. 12 | 13 | It accepts the title of the node that the new section will go after and a 14 | block declaring the new section. 15 | 16 | As is the case with add_subsection_to the section added 17 | doesn't need to be a section, it may be just a page. 18 | TEXT 19 | end 20 | 21 | example eval: false do 22 | # First we create 10 pages and some default outline 23 | (1..10).each do |index| 24 | text "Page #{index}" 25 | start_new_page 26 | end 27 | 28 | outline.define do 29 | section('Section 1', destination: 1) do 30 | page title: 'Page 2', destination: 2 31 | page title: 'Page 3', destination: 3 32 | end 33 | end 34 | 35 | # Now we will start adding nodes to the previous outline 36 | outline.insert_section_after('Page 2') do 37 | outline.section('Section after Page 2') do 38 | outline.page(title: 'Page 4', destination: 4) 39 | end 40 | end 41 | 42 | outline.insert_section_after('Section 1') do 43 | outline.section('Section after Section 1') do 44 | outline.page(title: 'Page 5', destination: 5) 45 | end 46 | end 47 | 48 | # Adding just a page 49 | outline.insert_section_after('Page 3') do 50 | outline.page(title: 'Page after Page 3', destination: 6) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /manual/repeatable_content.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Repeatable Content') 8 | 9 | prose <<-TEXT 10 | Prawn offers two ways to handle repeatable content blocks. Repeater is 11 | useful for content that gets repeated at well defined intervals while 12 | Stamp is more appropriate if you need better control of when to repeat 13 | it. 14 | 15 | There is also one very specific helper for numbering pages. 16 | 17 | The examples show: 18 | TEXT 19 | 20 | list( 21 | 'How to repeat content on several pages with a single invocation', 22 | 'How to create a new Stamp', 23 | 'How to "stamp" the content block on the page', 24 | 'How to number the document pages with one simple call', 25 | ) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /manual/repeatable_content/alternate_page_numbering.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Alternating Page Numbering' 7 | 8 | text do 9 | prose <<~TEXT 10 | Below is the code to generate page numbers that alternate being rendered 11 | on the right and left side of the page. The first page will have a "1" in 12 | the bottom right corner. The second page will have a "2" in the bottom 13 | left corner of the page. The third a "3" in the bottom right, etc. 14 | TEXT 15 | end 16 | 17 | example eval: false do 18 | text 'This is the first page!' 19 | 20 | 10.times do 21 | start_new_page 22 | text 'Here comes yet another page.' 23 | end 24 | 25 | string = '' 26 | odd_options = { 27 | at: [bounds.right - 150, 0], 28 | width: 150, 29 | align: :right, 30 | page_filter: :odd, 31 | start_count_at: 1, 32 | } 33 | even_options = { 34 | at: [0, bounds.left], 35 | width: 150, 36 | align: :left, 37 | page_filter: :even, 38 | start_count_at: 2, 39 | } 40 | number_pages string, odd_options 41 | number_pages string, even_options 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /manual/repeatable_content/page_numbering.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Page Numbering' 7 | 8 | text do 9 | prose <<~TEXT 10 | The number_pages method is a simple way to number the pages 11 | of your document. It should be called towards the end of the document 12 | since pages created after the call won't be numbered. 13 | 14 | It accepts a string and a hash of options: 15 | TEXT 16 | 17 | list( 18 | 'start_count_at is the value from which to start numbering pages', 19 | 'total_pages If provided, will replace total ' \ 20 | 'with the value given. Useful for overriding the total number of pages ' \ 21 | 'when using the start_count_at option.', 22 | 'page_filter, which is one of: :all, ' \ 23 | ':odd, :even, an array, a range, or a Proc that ' \ 24 | 'receives the page number as an argument and should return true if the page ' \ 25 | 'number should be printed on that page.', 26 | 'color which accepts the same values as fill_color', 27 | 'As well as any option accepted by text_box', 28 | ) 29 | end 30 | 31 | example eval: false do 32 | text 'This is the first page!' 33 | 34 | 10.times do 35 | start_new_page 36 | text 'Here comes yet another page.' 37 | end 38 | 39 | string = 'page of ' 40 | # Green page numbers 1 to 7 41 | options = { 42 | at: [bounds.right - 150, 0], 43 | width: 150, 44 | align: :right, 45 | page_filter: (1..7), 46 | start_count_at: 1, 47 | color: '007700', 48 | } 49 | number_pages string, options 50 | 51 | # Gray page numbers from 8 on up 52 | options[:page_filter] = ->(pg) { pg > 7 } 53 | options[:start_count_at] = 8 54 | options[:color] = '333333' 55 | number_pages string, options 56 | 57 | start_new_page 58 | text "See. This page isn't numbered and doesn't count towards the total." 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /manual/repeatable_content/repeater.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Repeater' 7 | 8 | text do 9 | prose <<~TEXT 10 | The repeat method is quite versatile when it comes to define 11 | the intervals at which the content block should repeat. 12 | 13 | The interval may be a symbol (:all, :odd, 14 | :even), an array listing the pages, a range or a 15 | Proc that receives the page number as an argument and should 16 | return true if the content is to be repeated on the given page. 17 | 18 | You may also pass an option :dynamic to reevaluate the code 19 | block on every call which is useful when the content changes based on the 20 | page number. 21 | 22 | It is also important to say that no matter where you define the repeater 23 | it will be applied to all matching pages. 24 | TEXT 25 | end 26 | 27 | example eval: false do 28 | repeat(:all) do 29 | draw_text 'All pages', at: bounds.top_left 30 | end 31 | 32 | repeat(:odd) do 33 | draw_text 'Only odd pages', at: [0, 0] 34 | end 35 | 36 | repeat(:even) do 37 | draw_text 'Only even pages', at: [0, 0] 38 | end 39 | 40 | repeat([1, 3, 7]) do 41 | draw_text 'Only on pages 1, 3 and 7', at: [100, 0] 42 | end 43 | 44 | repeat(2..4) do 45 | draw_text 'From the 2nd to the 4th page', at: [300, 0] 46 | end 47 | 48 | repeat(->(pg) { (pg % 3).zero? }) do 49 | draw_text 'Every third page', at: [250, 20] 50 | end 51 | 52 | repeat(:all, dynamic: true) do 53 | draw_text page_number, at: [500, 0] 54 | end 55 | 56 | 10.times do 57 | start_new_page 58 | draw_text 'A wonderful page', at: [400, 400] 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /manual/repeatable_content/stamp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Stamp' 7 | 8 | text do 9 | prose <<~TEXT 10 | Stamps should be used when you have content that will be included 11 | multiple times in a document. Its advantages over creating the content 12 | anew each time are: 13 | TEXT 14 | 15 | ordered_list( 16 | 'Faster document creation', 17 | 'Smaller final document', 18 | 'Faster display on subsequent displays of the repeated element because ' \ 19 | 'the viewer application can cache the rendered results', 20 | ) 21 | 22 | prose <<~TEXT 23 | The create_stamp method does just what it says. Pass it a 24 | block with the content that should be generated and the stamp will be 25 | created. 26 | 27 | There are two methods to render the stamp on a page stamp 28 | and stamp_at. The first will render the stamp as is while 29 | the second accepts a point to serve as an offset to the stamp content. 30 | TEXT 31 | end 32 | 33 | example do 34 | create_stamp('approved') do 35 | rotate(30, origin: [-5, -5]) do 36 | stroke_color 'FF3333' 37 | stroke_ellipse [0, 0], 29, 15 38 | stroke_color '000000' 39 | 40 | fill_color '993333' 41 | font('Times-Roman') do 42 | draw_text 'Approved', at: [-23, -3] 43 | end 44 | fill_color '000000' 45 | end 46 | end 47 | 48 | stamp 'approved' 49 | 50 | stamp_at 'approved', [200, 100] 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /manual/security.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Security') 8 | 9 | prose <<-TEXT 10 | Security lets you control who can read the document by defining 11 | a password. 12 | 13 | The examples include: 14 | TEXT 15 | 16 | list( 17 | 'How to encrypt the document without the need for a password', 18 | 'How to configure the regular user permissions', 19 | 'How to require a password for the regular user', 20 | 'How to set a owner password that bypass the document permissions', 21 | ) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /manual/security/encryption.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Encryption' 7 | 8 | text do 9 | prose <<~TEXT 10 | The encrypt_document method, as you might have already 11 | guessed, is used to encrypt the PDF document. 12 | 13 | Once encrypted whoever is using the document will need the user password 14 | to read the document. This password can be set with the 15 | :user_password option. If this is not set the document will 16 | be encrypted but a password will not be needed to read the document. 17 | 18 | There are some caveats when encrypting your PDFs. Be sure to read the 19 | source documentation (you can find it here: 20 | https://github.com/prawnpdf/prawn/blob/master/lib/prawn/security.rb\u200B) 21 | before using this for anything super serious. 22 | TEXT 23 | end 24 | 25 | example eval: false, standalone: true do 26 | # Bare encryption. No password needed. 27 | Prawn::ManualBuilder::Example.generate('bare_encryption.pdf') do 28 | text 'See, no password was asked but the document is still encrypted.' 29 | encrypt_document 30 | end 31 | 32 | # Simple password. All permissions granted. 33 | Prawn::ManualBuilder::Example.generate('simple_password.pdf') do 34 | text 'You was asked for a password.' 35 | encrypt_document(user_password: 'foo', owner_password: 'bar') 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /manual/security/permissions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Permissions' 7 | 8 | text do 9 | prose <<~TEXT 10 | Some permissions may be set for the regular user with the following 11 | options: :print_document, :modify_contents, 12 | :copy_contents, :modify_annotations. All this 13 | options default to true, so if you'd like to revoke just set 14 | them to false. 15 | 16 | A user may bypass all permissions if he provides the owner password which 17 | may be set with the :owner_password option. This option may 18 | be set to :random so that users will never be able to bypass 19 | permissions. 20 | 21 | There are some caveats when encrypting your PDFs. Be sure to read the source 22 | documentation (you can find it here: 23 | https://github.com/prawnpdf/prawn/blob/master/lib/prawn/security.rb\u200B) 24 | before using this for anything super serious. 25 | TEXT 26 | end 27 | 28 | example eval: false, standalone: true do 29 | # User cannot print the document. 30 | Prawn::Document.generate('cannot_print.pdf') do 31 | text "If you used the user password you won't be able to print the doc." 32 | encrypt_document( 33 | user_password: 'foo', 34 | owner_password: 'bar', 35 | permissions: { print_document: false }, 36 | ) 37 | end 38 | 39 | # All permissions revoked and owner password set to random 40 | Prawn::Document.generate('no_permissions.pdf') do 41 | text "You may only view this and won't be able to use the owner password." 42 | encrypt_document( 43 | user_password: 'foo', 44 | owner_password: :random, 45 | permissions: { 46 | print_document: false, 47 | modify_contents: false, 48 | copy_contents: false, 49 | modify_annotations: false, 50 | }, 51 | ) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /manual/table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Prawn::Table' 7 | 8 | text do 9 | prose <<~TEXT 10 | As of Prawn 1.2.0, Prawn::Table has been extracted into its own 11 | semi-officially supported gem. 12 | 13 | Please see https://github.com/prawnpdf/prawn-table for more details. 14 | TEXT 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /manual/text.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Peritext.new do 6 | text do 7 | header_with_bg('Text') 8 | 9 | prose <<-TEXT 10 | This is probably the feature people will use the most. There is no 11 | shortage of options when it comes to text. You'll be hard pressed to 12 | find a use case that is not covered by one of the text methods and 13 | configurable options. 14 | 15 | The examples show: 16 | TEXT 17 | 18 | list( 19 | 'Text that flows from page to page automatically starting new pages when necessary', 20 | 'How to use text boxes and place them on specific positions', 21 | 'What to do when a text box is too small to fit its content', 22 | 'Flowing text in columns', 23 | 'How to change the text style configuring font, size, alignment and many other settings', 24 | 'How to style specific portions of a text with inline styling and formatted text', 25 | 'How to define formatted callbacks to reuse common styling definitions', 26 | 'How to use the different rendering modes available for the text methods', 27 | 'How to create your custom text box extensions', 28 | 'How to use external fonts on your pdfs', 29 | 'What happens when rendering text in different languages', 30 | ) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /manual/text/alignment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Alignment' 7 | 8 | text do 9 | prose <<~TEXT 10 | Horizontal text alignment can be achieved by supplying the 11 | :align option to the text methods. Available options are 12 | :left (default), :right, :center, 13 | and :justify. 14 | 15 | Vertical text alignment can be achieved using the :valign 16 | option with the text methods. Available options are :top 17 | (default), :center, and :bottom. 18 | 19 | Both forms of alignment will be evaluated in the context of the current 20 | bounding_box. 21 | TEXT 22 | end 23 | 24 | example new_page: true do 25 | text 'This text should be left aligned' 26 | text 'This text should be centered', align: :center 27 | text 'This text should be right aligned', align: :right 28 | 29 | y = cursor - 20 30 | bounding_box([0, y], width: 250, height: 220) do 31 | text 'This text is flowing from the left. ' * 4 32 | 33 | move_down 15 34 | text 'This text is flowing from the center. ' * 3, align: :center 35 | 36 | move_down 15 37 | text 'This text is flowing from the right. ' * 4, align: :right 38 | 39 | move_down 15 40 | text 'This text is justified. ' * 6, align: :justify 41 | transparent(0.5) { stroke_bounds } 42 | end 43 | 44 | bounding_box([270, y], width: 250, height: 220) do 45 | text 'This text should be vertically top aligned' 46 | text 'This text should be vertically centered', valign: :center 47 | text 'This text should be vertically bottom aligned', valign: :bottom 48 | transparent(0.5) { stroke_bounds } 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /manual/text/color.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Color' 7 | 8 | text do 9 | prose <<~TEXT 10 | The :color attribute can give a block of text a default 11 | color, in RGB hex format or 4-value CMYK. 12 | TEXT 13 | end 14 | 15 | example do 16 | text 'Default color is black' 17 | move_down 25 18 | 19 | text 'Changed to red', color: 'FF0000' 20 | move_down 25 21 | 22 | text 'CMYK color', color: [22, 55, 79, 30] 23 | move_down 25 24 | 25 | text( 26 | "Also works with inline formatting", 27 | color: '0000FF', 28 | inline_format: true, 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /manual/text/column_box.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Column Box' 7 | 8 | text do 9 | prose <<~TEXT 10 | The column_box method allows you to define columns that flow 11 | their contents from one section to the next. You can have a number of 12 | columns on the page, and only when the last column overflows will a new 13 | page be created. 14 | TEXT 15 | end 16 | 17 | example new_page: true do 18 | move_down 30 19 | text 'The Prince', align: :center, size: 18 20 | text 'Niccolò Machiavelli', align: :center, size: 14 21 | move_down 30 22 | 23 | column_box([0, cursor], columns: 2, width: bounds.width) do 24 | text("#{<<~TEXT.gsub(/\s+/, ' ')}\n\n" * 5) 25 | All the States and Governments by which men are or ever have been ruled, 26 | have been and are either Republics or Princedoms. Princedoms are either 27 | hereditary, in which the sovereignty is derived through an ancient line 28 | of ancestors, or they are new. New Princedoms are either wholly new, as 29 | that of Milan to Francesco Sforza; or they are like limbs joined on to 30 | the hereditary possessions of the Prince who acquires them, as the 31 | Kingdom of Naples to the dominions of the King of Spain. The States thus 32 | acquired have either been used to live under a Prince or have been free; 33 | and he who acquires them does so either by his own arms or by the arms of 34 | others, and either by good fortune or by merit. 35 | TEXT 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /manual/text/fallback_fonts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Fallback Fonts' 7 | 8 | text do 9 | prose <<~TEXT 10 | Prawn enables the declaration of fallback fonts for those glyphs that may 11 | not be present in the desired font. Use the :fallback_fonts 12 | option with any of the text or text box methods, or set fallback_fonts 13 | document-wide. 14 | TEXT 15 | end 16 | 17 | example do 18 | jigmo_file = "#{Prawn::ManualBuilder::DATADIR}/fonts/Jigmo.ttf" 19 | font_families['Jigmo'] = { normal: { file: jigmo_file, font: 'Jigmo' } } 20 | panic_sans_file = "#{Prawn::ManualBuilder::DATADIR}/fonts/Panic+Sans.dfont" 21 | font_families['Panic Sans'] = { normal: { file: panic_sans_file, font: 'PanicSans' } } 22 | 23 | font('Panic Sans') do 24 | text( 25 | 'When fallback fonts are included, each glyph will be rendered ' \ 26 | 'using the first font that includes the glyph, starting with the ' \ 27 | 'current font and then moving through the fallback fonts from left ' \ 28 | 'to right.' \ 29 | "\n\n" \ 30 | "hello ƒ 你好\n再见 ƒ goodbye", 31 | fallback_fonts: %w[Times-Roman Jigmo], 32 | ) 33 | end 34 | move_down 20 35 | 36 | formatted_text( 37 | [ 38 | { text: 'Fallback fonts can even override' }, 39 | { text: 'fragment fonts (你好)', font: 'Times-Roman' }, 40 | ], 41 | fallback_fonts: %w[Times-Roman Jigmo], 42 | ) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /manual/text/font.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Fonts' 7 | 8 | text do 9 | prose <<~TEXT 10 | The font method can be used in three different ways. 11 | 12 | If we don't pass it any arguments it will return the current font being 13 | used to render text. 14 | 15 | If we just pass it a font name it will use that font for rendering text 16 | through the rest of the document. 17 | 18 | It can also be used by passing a font name and a block. In this case the 19 | specified font will only be used to render text inside the block. 20 | 21 | The default font is Helvetica. 22 | TEXT 23 | end 24 | 25 | example do 26 | text "Let's see which font we are using: #{font.inspect}" 27 | 28 | font 'Times-Roman' 29 | text 'Written in Times.' 30 | 31 | font('Courier') do 32 | text 'Written in Courier because we are inside the block.' 33 | end 34 | 35 | text 'Written in Times again as we left the previous block.' 36 | 37 | text "Let's see which font we are using again: #{font.inspect}" 38 | 39 | font 'Helvetica' 40 | text 'Back to normal.' 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /manual/text/font_size.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Font Size' 7 | 8 | text do 9 | prose <<~TEXT 10 | The font_size method works just like the font 11 | method. 12 | 13 | In fact we can even use font with the :size 14 | option to declare which size we want. 15 | 16 | Another way to change the font size is by supplying the 17 | :size option to the text methods. 18 | 19 | The default font size is 12. 20 | TEXT 21 | end 22 | 23 | example do 24 | text "Let's see which is the current font_size: #{font_size.inspect}" 25 | 26 | font_size 16 27 | text 'Yeah, something bigger!' 28 | 29 | font_size(25) { text 'Even bigger!' } 30 | 31 | text 'Back to 16 again.' 32 | 33 | text 'Single line on 20 using the :size option.', size: 20 34 | 35 | text 'Back to 16 once more.' 36 | 37 | font('Courier', size: 10) do 38 | text 'Yeah, using Courier 10 courtesy of the font method.' 39 | end 40 | 41 | font('Helvetica', size: 12) 42 | text 'Back to normal' 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /manual/text/font_style.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Font Style' 7 | 8 | text do 9 | prose <<~TEXT 10 | Most font families come with some styles other than normal. Most common 11 | are bold, italic and bold_italic. 12 | 13 | The style can be set the using the :style option, with 14 | either the font method which will set the font and style for 15 | rest of the document, or with the inline text methods. 16 | TEXT 17 | end 18 | 19 | example do 20 | fonts = %w[Courier Helvetica Times-Roman] 21 | styles = %i[bold bold_italic italic normal] 22 | 23 | fonts.each do |example_font| 24 | move_down 20 25 | 26 | styles.each do |style| 27 | font example_font, style: style 28 | text "I'm writing in #{example_font} (#{style})" 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /manual/text/formatted_callbacks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Formatted Text Callbacks' 7 | 8 | text do 9 | prose <<~TEXT 10 | The :callback option is also available for the formatted 11 | text methods. 12 | 13 | This option accepts an object (or array of objects) on which two methods 14 | will be called if defined: render_behind and 15 | render_in_front. They are called before and after rendering 16 | the text fragment and are passed the fragment as an argument. 17 | 18 | This example defines two new callback classes and provide callback 19 | objects for the formatted_text. 20 | TEXT 21 | end 22 | 23 | example new_page: true do 24 | class HighlightCallback 25 | def initialize(options) 26 | @color, @document = options.values_at(:color, :document) 27 | end 28 | 29 | def render_behind(fragment) 30 | original_color = @document.fill_color 31 | @document.fill_color = @color 32 | @document.fill_rectangle(fragment.top_left, fragment.width, fragment.height) 33 | @document.fill_color = original_color 34 | end 35 | end 36 | 37 | class ConnectedBorderCallback 38 | def initialize(options) 39 | @radius, @document = options.values_at(:radius, :document) 40 | end 41 | 42 | def render_in_front(fragment) 43 | points = [fragment.top_left, fragment.top_right, fragment.bottom_right, fragment.bottom_left] 44 | @document.stroke_polygon(*points) 45 | points.each { |point| @document.fill_circle(point, @radius) } 46 | end 47 | end 48 | 49 | highlight = HighlightCallback.new(color: 'ffff00', document: self) 50 | border = ConnectedBorderCallback.new(radius: 2.5, document: self) 51 | 52 | formatted_text( 53 | [ 54 | { text: 'hello', callback: highlight }, 55 | { text: ' ' }, 56 | { text: 'world', callback: border }, 57 | { text: ' ' }, 58 | { text: 'hello world', callback: [highlight, border] }, 59 | ], 60 | size: 20, 61 | ) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /manual/text/formatted_text.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Formatted Text' 7 | 8 | text do 9 | prose <<~TEXT 10 | There are two other text methods available: formatted_text 11 | and formatted_text_box. 12 | 13 | These are useful when the provided text has numerous portions that need 14 | to be formatted differently. As you might imply from their names the 15 | first should be used for free flowing text just like the 16 | text method and the last should be used for positioned text 17 | just like text_box. 18 | 19 | The main difference between these methods and the text and 20 | text_box methods is how the text is provided. The 21 | formatted_text and formatted_text_box methods 22 | accept an array of hashes. Each hash must provide a :text 23 | option which is the text string and may provide the following options: 24 | :styles (an array of symbols), :size (the font 25 | size), :character_spacing (additional space between the 26 | characters), :font (the name of a registered font), 27 | :color (the same input accepted by fill_color 28 | and stroke_color), :link (an URL to create a 29 | link), and :local (a link to a local file). 30 | TEXT 31 | end 32 | 33 | example new_page: true do 34 | formatted_text [ 35 | { text: 'Some bold. ', styles: [:bold] }, 36 | { text: 'Some italic. ', styles: [:italic] }, 37 | { text: 'Bold italic. ', styles: %i[bold italic] }, 38 | { text: 'Bigger Text. ', size: 20 }, 39 | { text: 'More spacing. ', character_spacing: 3 }, 40 | { text: 'Different Font. ', font: 'Courier' }, 41 | { text: 'Some coloring. ', color: 'FF00FF' }, 42 | { text: 'Link to the home page. ', color: '0000FF', link: 'https://prawnpdf.org/' }, 43 | { text: 'Link to a local file. ', color: '0000FF', local: 'README.md' }, 44 | ] 45 | 46 | formatted_text_box( 47 | [ 48 | { text: 'Just your regular' }, 49 | { text: ' text_box ', font: 'Courier' }, 50 | { 51 | text: 'with some additional formatting options added to the mix.', 52 | color: [50, 100, 0, 0], 53 | styles: [:italic], 54 | }, 55 | ], 56 | at: [100, 100], 57 | width: 200, 58 | height: 100, 59 | ) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /manual/text/free_flowing_text.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Free Flowing Text' 7 | 8 | text do 9 | prose <<~TEXT 10 | Text rendering can be as simple or as complex as you want. 11 | 12 | This example covers the most basic method: text. It is meant 13 | for free flowing text. The provided string will flow according to the 14 | current bounding box width and height. It will also flow onto the next 15 | page if the bottom of the bounding box is reached. 16 | 17 | The text will start being rendered on the current cursor position. When 18 | it finishes rendering, the cursor is left directly below the text. 19 | 20 | This example also shows text flowing across pages following the margin 21 | box and other bounding boxes. 22 | TEXT 23 | end 24 | 25 | example do 26 | text 'This text will flow to the next page. ' * 20 27 | 28 | y_position = cursor 29 | bounding_box([0, y_position], width: 200, height: 150) do 30 | transparent(0.5) { stroke_bounds } 31 | text 'This text will flow along this bounding box we created for it. ' * 5 32 | end 33 | 34 | bounding_box([300, y_position], width: 200, height: 150) do 35 | transparent(0.5) { stroke_bounds } # This will stroke on one page 36 | 37 | text 'Now look what happens when the free flowing text reaches the end of a bounding box ' \ 38 | "that is narrower than the margin box.#{' . ' * 200}It continues on the next page as if " \ 39 | 'the previous bounding box was cloned. If we want it to have the same border as the one on ' \ 40 | 'the previous page we will need to stroke the boundaries again.' 41 | 42 | transparent(0.5) { stroke_bounds } # And this will stroke on the next 43 | end 44 | 45 | move_cursor_to 200 46 | span(350, position: :center) do 47 | text 'Span is a different kind of bounding box as it lets the text flow gracefully onto the ' \ 48 | "next page. It doesn't matter if the text started on the middle of the previous page, when " \ 49 | "it flows to the next page it will start at the beginning.#{' _ ' * 500}" \ 50 | 'I told you it would start on the beginning of this page.' 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /manual/text/inline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Inline Formatting' 7 | 8 | text do 9 | prose <<~TEXT 10 | Inline formatting gives you the option to format specific portions of a 11 | text. It uses HTML-esque syntax inside the text string. Supported tags 12 | are: b (bold), i (italic), u 13 | (underline), strikethrough, sub (subscript), 14 | sup (superscript). 15 | 16 | The following tags accept specific attributes: font accepts 17 | size, name, and character_spacing; 18 | color accepts rgb and set of c, 19 | m, y, and k; 20 | link accepts href for external links. 21 | TEXT 22 | end 23 | 24 | example do 25 | %w[b i u strikethrough sub sup].each do |tag| 26 | text "Just your regular text <#{tag}>except this portion " \ 27 | "is using the #{tag} tag", 28 | inline_format: true 29 | # move_down 10 30 | end 31 | 32 | text "This line uses " \ 33 | "all the font tag attributes in " \ 34 | "a single line. ", 35 | inline_format: true 36 | # move_down 10 37 | 38 | text "Coloring in both RGB " \ 39 | "and CMYK", 40 | inline_format: true 41 | # move_down 10 42 | 43 | text 'This an external link to the ' \ 44 | "Prawn home page" \ 45 | '', 46 | inline_format: true 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /manual/text/kerning_and_character_spacing.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Kerning and Character Spacing' 7 | 8 | text do 9 | prose <<~TEXT 10 | Kerning is the process of adjusting the spacing between characters in a 11 | proportional font. It is usually done with specific letter pairs. We can 12 | switch it on and off if it is available with the current font. Just pass 13 | a boolean value to the :kerning option of the text methods. 14 | 15 | Character Spacing is the space between characters. It can be increased or 16 | decreased and will have effect on the whole text. Just pass a number to 17 | the :character_spacing option from the text methods. 18 | TEXT 19 | end 20 | 21 | example new_page: true do 22 | font_size(30) do 23 | text_box 'With kerning:', kerning: true, at: [0, y - 40] 24 | text_box 'Without kerning:', kerning: false, at: [0, y - 80] 25 | 26 | text_box 'Tomato', kerning: true, at: [230, y - 40] 27 | text_box 'Tomato', kerning: false, at: [230, y - 80] 28 | 29 | text_box 'WAR', kerning: true, at: [360, y - 40] 30 | text_box 'WAR', kerning: false, at: [360, y - 80] 31 | 32 | text_box 'F.', kerning: true, at: [470, y - 40] 33 | text_box 'F.', kerning: false, at: [470, y - 80] 34 | end 35 | 36 | move_down 80 37 | 38 | string = 'What have you done to the space between the characters?' 39 | [-2, -1, 0, 0.5, 1, 2].each do |spacing| 40 | move_down 20 41 | text "#{string} (character_spacing: #{spacing})", 42 | character_spacing: spacing 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /manual/text/leading.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Leading' 7 | 8 | text do 9 | prose <<~TEXT 10 | Leading is the additional space between lines of text. 11 | 12 | The leading can be set using the default_leading method 13 | which applies to the rest of the document or until it is changed, or 14 | inline in the text methods with the :leading option. 15 | 16 | The default leading is 0. 17 | TEXT 18 | end 19 | 20 | example do 21 | string = 'Hey, what did you do with the space between my lines? ' * 8 22 | text string, leading: 0 23 | 24 | move_down 20 25 | default_leading 5 26 | text string 27 | 28 | move_down 20 29 | text string, leading: 10 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /manual/text/paragraph_indentation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Paragraph Indentation' 7 | 8 | text do 9 | prose <<~TEXT 10 | Prawn strips all whitespace from the beginning and the end of strings so 11 | there are two ways to indent paragraphs: 12 | 13 | One is to use non-breaking spaces which Prawn won't strip. One shortcut 14 | to using them is the Prawn::Text::NBSP. 15 | 16 | The other is to use the :indent_paragraphs option with the 17 | text methods. Just pass a number with the space to indent the first line 18 | in each paragraph. 19 | TEXT 20 | end 21 | 22 | example new_page: true do 23 | # Using non-breaking spaces 24 | text (' ' * 10) + ("This paragraph won't be indented. " * 10) + 25 | "\n#{Prawn::Text::NBSP * 10}" + ('This one will with NBSP. ' * 10) 26 | 27 | move_down 20 28 | text "#{'This paragraph will be indented. ' * 10}\n#{'This one will too. ' * 10}", 29 | indent_paragraphs: 60 30 | 31 | move_down 20 32 | text 'FROM RIGHT TO LEFT:' 33 | text "#{'This paragraph will be indented. ' * 10}\n#{'This one will too. ' * 10}", 34 | indent_paragraphs: 60, 35 | direction: :rtl 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /manual/text/positioned_text.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Positioned Text' 7 | 8 | text do 9 | prose <<~TEXT 10 | Sometimes we want the text on a specific position on the page. The 11 | text method just won't help us. 12 | 13 | There are two other methods for this task: draw_text and 14 | text_box. 15 | 16 | draw_text is very simple. It will render text starting at 17 | the position provided to the :at option. It won't flow to a 18 | new line even if it hits the document boundaries so it is best suited for 19 | short text. 20 | 21 | text_box gives us much more control over the output. Just 22 | provide :width and :height options and the text 23 | will flow accordingly. Even if you don't provide a :width 24 | option the text will flow to a new line if it reaches the right border. 25 | 26 | Given that, text_box is the better option available. 27 | TEXT 28 | end 29 | 30 | example do 31 | draw_text "This draw_text line is absolute positioned. However don't " \ 32 | 'expect it to flow even if it hits the document border', 33 | at: [200, 170] 34 | 35 | text_box 'This is a text box, you can control where it will flow by ' \ 36 | 'specifying the :height and :width options', 37 | at: [50, 150], 38 | height: 100, 39 | width: 100 40 | 41 | text_box 'Another text box with no :width option passed, so it will ' \ 42 | 'flow to a new line whenever it reaches the right margin. ', 43 | at: [200, 100] 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /manual/text/registering_families.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Registering Font Families' 7 | 8 | text do 9 | prose <<~TEXT 10 | Registering font families will help you when you want to use a font over 11 | and over or if you would like to take advantage of the :style 12 | option of the text methods and the b and i tags 13 | when using inline formatting. 14 | 15 | To register a font family update the font_families hash with 16 | the font path for each style you want to use. 17 | TEXT 18 | end 19 | 20 | example new_page: true do 21 | # Registering a single external font 22 | font_families.update( 23 | 'DejaVu Sans' => { 24 | normal: "#{Prawn::ManualBuilder::DATADIR}/fonts/DejaVuSans.ttf", 25 | }, 26 | ) 27 | 28 | font('DejaVu Sans') do 29 | text 'Using the DejaVu Sans font providing only its name to the font method' 30 | end 31 | move_down 20 32 | 33 | # Registering a DFONT package 34 | font_path = "#{Prawn::ManualBuilder::DATADIR}/fonts/Panic+Sans.dfont" 35 | font_families.update( 36 | 'Panic Sans' => { 37 | normal: { file: font_path, font: 'PanicSans' }, 38 | italic: { file: font_path, font: 'PanicSans-Italic' }, 39 | bold: { file: font_path, font: 'PanicSans-Bold' }, 40 | bold_italic: { file: font_path, font: 'PanicSans-BoldItalic' }, 41 | }, 42 | ) 43 | 44 | font 'Panic Sans' 45 | text 'Also using Panic Sans by providing only its name' 46 | move_down 20 47 | 48 | text 'Taking advantage of the inline formatting', 49 | inline_format: true 50 | move_down 20 51 | 52 | %i[bold bold_italic italic normal].each do |style| 53 | text "Using the #{style} style option.", style: style 54 | move_down 10 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /manual/text/rendering_and_color.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Text Rendering Modes' 7 | 8 | text do 9 | prose <<~TEXT 10 | You have already seen how to set the text color using both inline 11 | formatting and the format text methods. There is another way by using the 12 | graphics methods fill_color and stroke_color. 13 | 14 | When reading the graphics reference you learned about fill and stroke. If 15 | you haven't read it before, read it now before continuing. 16 | 17 | Text can be rendered by being filled (the default mode) or just stroked, 18 | or both filled and stroked. This can be set using the 19 | text_rendering_mode method or the :mode option 20 | on the text methods. 21 | TEXT 22 | end 23 | 24 | example do 25 | fill_color '00ff00' 26 | stroke_color '0000ff' 27 | 28 | font_size(40) do 29 | # normal rendering mode: fill 30 | text 'This text is filled with green.' 31 | 32 | # inline rendering mode: stroke 33 | text 'This text is stroked with blue', mode: :stroke 34 | 35 | # block rendering mode: fill and stroke 36 | text_rendering_mode(:fill_stroke) do 37 | text 'This text is filled with green and stroked with blue' 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /manual/text/right_to_left_text.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Right-to-Left Text' 7 | 8 | text do 9 | prose <<~TEXT 10 | Prawn can be used with right-to-left text. The direction can be set 11 | document-wide, on particular text, or on a text-box. Setting the 12 | direction to :rtl automatically changes the default 13 | alignment to :right. 14 | 15 | You can even override direction on an individual fragment. The one caveat 16 | is that two fragments going against the main direction cannot be placed 17 | next to each other without appearing in the wrong order. 18 | 19 | Writing bidirectional text that combines both left-to-right and 20 | right-to-left languages is easy using the bidi Ruby Gem and 21 | its render_visual function. See 22 | https://github.com/elad/ruby-bidi for instructions and an example using Prawn. 23 | TEXT 24 | end 25 | 26 | example new_page: true do 27 | # set the direction document-wide 28 | self.text_direction = :rtl 29 | 30 | font("#{Prawn::ManualBuilder::DATADIR}/fonts/Jigmo.ttf", size: 16) do 31 | long_text = '写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事写个小爬虫把你的页面上的' \ 32 | '关键信息顺次爬下来也不是什么难事写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事写' \ 33 | '个小' 34 | text long_text 35 | move_down 20 36 | 37 | text 'You can override the document direction.', direction: :ltr 38 | move_down 20 39 | 40 | formatted_text [ 41 | { text: '更可怕的是,同质化竞争对手可以按照' }, 42 | { text: 'URL', direction: :ltr }, 43 | { text: '中后面这个' }, 44 | { text: 'ID', direction: :ltr }, 45 | { text: '来遍历您的' }, 46 | { text: 'DB', direction: :ltr }, 47 | { 48 | text: '中的内容、写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事、这样的话、' \ 49 | '你就非常被动了。', 50 | }, 51 | ] 52 | move_down 20 53 | 54 | formatted_text [ 55 | { text: '更可怕的是、同质化竞争对手可以按照' }, 56 | { text: 'this', direction: :ltr }, 57 | { text: "won't", direction: :ltr, size: 24 }, 58 | { text: 'work', direction: :ltr }, 59 | { text: '中的内容、写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事' }, 60 | ] 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /manual/text/rotation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Rotation' 7 | 8 | text do 9 | prose <<~TEXT 10 | Rotating text is best avoided on free flowing text, so this example 11 | will only use the text_box method as we can have much more 12 | control over its output. 13 | 14 | To rotate text all we need to do is use the :rotate option 15 | passing an angle in degrees and an optional :rotate_around 16 | to indicate the origin of the rotation (the default is 17 | :upper_left). 18 | TEXT 19 | end 20 | 21 | example new_page: true do 22 | width = 100 23 | height = 60 24 | angle = 30 25 | x = 200 26 | y = cursor - 30 27 | 28 | stroke_rectangle [0, y], width, height 29 | text_box( 30 | 'This text was not rotated', 31 | at: [0, y], 32 | width: width, 33 | height: height, 34 | ) 35 | 36 | stroke_rectangle [0, y - 100], width, height 37 | text_box( 38 | 'This text was rotated around the center', 39 | at: [0, y - 100], 40 | width: width, 41 | height: height, 42 | rotate: angle, 43 | rotate_around: :center, 44 | ) 45 | 46 | %i[lower_left upper_left lower_right upper_right].each_with_index do |corner, index| 47 | y -= 100 if index == 2 48 | stroke_rectangle [x + ((index % 2) * 200), y], width, height 49 | text_box( 50 | "This text was rotated around the #{corner} corner.", 51 | at: [x + ((index % 2) * 200), y], 52 | width: width, 53 | height: height, 54 | rotate: angle, 55 | rotate_around: corner, 56 | ) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /manual/text/single_usage.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Single Usage Fonts' 7 | 8 | text do 9 | prose <<~TEXT 10 | The PDF format has some built-in font support. If you want to use other 11 | fonts in Prawn you need to embed the font file. 12 | 13 | Doing this for a single font is extremely simple. Remember the Styling 14 | font example? Another use of the font method is to provide a 15 | font file path and the font will be embedded in the document and set as 16 | the current font. 17 | 18 | This is reasonable if a font is used only once, but, if a font used 19 | several times, providing the path each time it is used becomes 20 | cumbersome. The example on the next page shows a better way to deal with 21 | fonts which are used several times in a document. 22 | TEXT 23 | end 24 | 25 | example do 26 | # Using a TTF font file 27 | font("#{Prawn::ManualBuilder::DATADIR}/fonts/DejaVuSans.ttf") do 28 | text 'Written with the DejaVu Sans TTF font.' 29 | end 30 | move_down 20 31 | 32 | text 'Written with the default font.' 33 | move_down 20 34 | 35 | # Using an DFONT font file 36 | font("#{Prawn::ManualBuilder::DATADIR}/fonts/Panic+Sans.dfont") do 37 | text 'Written with the Panic Sans DFONT font' 38 | end 39 | move_down 20 40 | 41 | text 'Written with the default font once more.' 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /manual/text/text_box_excess.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Text Box Excess' 7 | 8 | text do 9 | prose <<~TEXT 10 | Whenever the text_box method truncates text, this truncated 11 | bit is not lost, it is the method return value and we can take advantage 12 | of that. 13 | 14 | We just need to take some precautions. 15 | 16 | This example renders as much of the text as will fit in a larger font 17 | inside one text_box and then proceeds to render the remaining text in the 18 | default size in a second text_box. 19 | TEXT 20 | end 21 | 22 | example do 23 | string = 'This is the beginning of the text. It will be cut somewhere and ' \ 24 | 'the rest of the text will proceed to be rendered this time by ' \ 25 | 'calling another method.' + (' . ' * 50) 26 | 27 | y_position = cursor - 20 28 | excess_text = text_box( 29 | string, 30 | width: 300, 31 | height: 50, 32 | overflow: :truncate, 33 | at: [100, y_position], 34 | size: 18, 35 | ) 36 | 37 | text_box( 38 | excess_text, 39 | width: 300, 40 | at: [100, y_position - 100], 41 | ) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /manual/text/text_box_extensions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Text Box Extensions' 7 | 8 | text do 9 | prose <<~TEXT 10 | We've already seen one way of using text boxes with the 11 | text_box method. Turns out this method is just a convenience 12 | for using the Prawn::Text::Box class as it creates a new 13 | object and call render on it. 14 | 15 | Knowing that any extensions we add to Prawn::Text::Box will 16 | take effect when we use the text_box method. To add an 17 | extension all we need to do is append the 18 | Prawn::Text::Box.extensions array with a module. 19 | TEXT 20 | end 21 | 22 | example new_page: true do 23 | module TriangleBox 24 | def available_width 25 | height + 25 26 | end 27 | end 28 | 29 | y_position = cursor 30 | width = 100 31 | height = 100 32 | 33 | Prawn::Text::Box.extensions << TriangleBox 34 | stroke_rectangle([0, y_position], width, height) 35 | text_box( 36 | 'A' * 100, 37 | at: [0, y_position], 38 | width: width, 39 | height: height, 40 | ) 41 | 42 | Prawn::Text::Formatted::Box.extensions << TriangleBox 43 | stroke_rectangle([200, y_position], width, height) 44 | formatted_text_box( 45 | [{ text: 'A' * 100, color: '009900' }], 46 | at: [200, y_position], 47 | width: width, 48 | height: height, 49 | ) 50 | 51 | # Here we clear the extensions array 52 | Prawn::Text::Box.extensions.clear 53 | Prawn::Text::Formatted::Box.extensions.clear 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /manual/text/text_box_overflow.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Text Box Overflow' 7 | 8 | text do 9 | prose <<~TEXT 10 | The text_box method accepts both :width and 11 | :height options. So what happens if the text doesn't fit the 12 | box? 13 | 14 | The default behavior is to truncate the text but this can be changed with 15 | the :overflow option. Available modes are 16 | :expand (the box will increase to fit the text) and 17 | :shrink_to_fit (the text font size will be shrunk to fit). 18 | 19 | If :shrink_to_fit mode is used with the 20 | :min_font_size option set, the font size will not be reduced 21 | to less than the value provided even if it means truncating some text. 22 | 23 | If the :disable_wrap_by_char is set to true 24 | then any text wrapping done while using the :shrink_to_fit 25 | mode will not break up the middle of words. 26 | TEXT 27 | end 28 | 29 | example new_page: true do 30 | string = 'This is the sample text used for the text boxes. See how it ' \ 31 | 'behave with the various overflow options used.' 32 | 33 | text string 34 | 35 | y_position = cursor - 20 36 | %i[truncate expand shrink_to_fit].each_with_index do |mode, i| 37 | text_box string, 38 | at: [i * 150, y_position], 39 | width: 100, 40 | height: 50, 41 | overflow: mode 42 | end 43 | 44 | string = 'If the box is too small for the text, :shrink_to_fit ' \ 45 | 'can render the text in a really small font size.' 46 | 47 | move_down 120 48 | text string 49 | y_position = cursor - 20 50 | [nil, 8, 10, 12].each_with_index do |value, index| 51 | text_box string, 52 | at: [index * 150, y_position], 53 | width: 50, 54 | height: 50, 55 | overflow: :shrink_to_fit, 56 | min_font_size: value 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /manual/text/utf8.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'UTF-8' 7 | 8 | text do 9 | prose <<~TEXT 10 | Multilingualization isn't much of a problem on Prawn as its default 11 | encoding is UTF-8. The only thing you need to worry about is if the font 12 | support the glyphs of your language. 13 | TEXT 14 | end 15 | 16 | example do 17 | text 'Take this example, a simple Euro sign:' 18 | text '€', size: 32 19 | move_down 20 20 | 21 | text 'This works, because € is one of the few ' \ 22 | 'non-ASCII glyphs supported in PDF built-in fonts.' 23 | 24 | move_down 20 25 | 26 | text 'For full internationalized text support, we need to use external fonts:' 27 | move_down 20 28 | 29 | font("#{Prawn::ManualBuilder::DATADIR}/fonts/DejaVuSans.ttf") do 30 | text 'ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει.' 31 | text 'There you go.' 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /manual/text/win_ansi_charset.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prawn/manual_builder' 4 | 5 | Prawn::ManualBuilder::Chapter.new do 6 | title 'Windown-1252 Charset' 7 | 8 | text do 9 | prose <<~TEXT 10 | Here's a list of all of the glyphs that can be rendered by Adobe's built 11 | in fonts, along with their character widths and WinAnsi codes. Be sure 12 | to pass these glyphs as UTF-8, and Prawn will transcode them for you. 13 | TEXT 14 | end 15 | 16 | example new_page: true do 17 | font 'Helvetica', size: 10 18 | 19 | x = 0 20 | y = bounds.top 21 | 22 | fields = [[20, :right], [8, :left], [12, :center], [30, :right], [8, :left], [0, :left]] 23 | 24 | Prawn::Encoding::WinAnsi::CHARACTERS.each_with_index do |name, index| 25 | next if name == '.notdef' 26 | 27 | y -= font_size 28 | if y < font_size 29 | y = bounds.top - font_size 30 | x += 170 31 | end 32 | 33 | code = format('%d.', index: index) 34 | char = index.chr.force_encoding(::Encoding::Windows_1252) 35 | 36 | width = 1000 * width_of(char, size: font_size) / font_size 37 | size = format('%d', width: width) 38 | 39 | data = [code, nil, char, size, nil, name] 40 | dx = x 41 | fields.zip(data).each do |(total_width, align), field| 42 | if field 43 | width = width_of(field, size: font_size) 44 | 45 | case align 46 | when :left then offset = 0 47 | when :right then offset = total_width - width 48 | when :center then offset = (total_width - width) / 2 49 | end 50 | 51 | text_box(field.dup.force_encoding('windows-1252').encode('UTF-8'), at: [dx + offset, y]) 52 | end 53 | 54 | dx += total_width 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /prawn.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | basedir = __dir__ 4 | 5 | require "#{basedir}/lib/prawn/version" 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'prawn' 9 | spec.version = Prawn::VERSION 10 | spec.platform = Gem::Platform::RUBY 11 | spec.summary = 'A fast and nimble PDF generator for Ruby' 12 | spec.description = 'Prawn is a fast, tiny, and nimble PDF generator for Ruby' 13 | 14 | spec.files = Dir.glob('{lib}/**/**/*') + 15 | Dir.glob('data/fonts/{MustRead.html,*.afm}') + 16 | %w[COPYING LICENSE GPLv2 GPLv3] 17 | 18 | if File.basename($PROGRAM_NAME) == 'gem' && ARGV.include?('build') 19 | signing_key = File.expand_path('~/.gem/gem-private_key.pem') 20 | if File.exist?(signing_key) 21 | spec.cert_chain = ['certs/pointlessone.pem'] 22 | spec.signing_key = signing_key 23 | else 24 | warn 'WARNING: Signing key is missing. The gem is not signed and its authenticity can not be verified.' 25 | end 26 | end 27 | 28 | spec.required_ruby_version = '>= 2.7' 29 | spec.required_rubygems_version = '>= 1.3.6' 30 | 31 | spec.authors = [ 32 | 'Alexander Mankuta', 'Gregory Brown', 'Brad Ediger', 'Daniel Nelson', 33 | 'Jonathan Greenberg', 'James Healy', 34 | ] 35 | spec.email = [ 36 | 'alex@pointless.one', 'gregory.t.brown@gmail.com', 'brad@bradediger.com', 37 | 'dnelson@bluejade.com', 'greenberg@entryway.net', 'jimmy@deefa.com', 38 | ] 39 | spec.licenses = %w[Nonstandard GPL-2.0-only GPL-3.0-only] 40 | spec.homepage = 'http://prawnpdf.org/' 41 | spec.metadata = { 42 | 'rubygems_mfa_required' => 'true', 43 | 'homepage_uri' => spec.homepage, 44 | 'changelog_uri' => "https://github.com/prawnpdf/prawn/blob/#{spec.version}/CHANGELOG.md", 45 | 'source_code_uri' => 'https://github.com/prawnpdf/prawn', 46 | 'documentation_uri' => "https://prawnpdf.org/docs/prawn/#{spec.version}/", 47 | 'bug_tracker_uri' => 'https://github.com/prawnpdf/prawn/issues', 48 | } 49 | 50 | spec.add_dependency('matrix', '~> 0.4') 51 | spec.add_dependency('pdf-core', '~> 0.10.0') 52 | spec.add_dependency('ttfunk', '~> 1.8') 53 | 54 | spec.add_development_dependency('pdf-inspector', '>= 1.2.1', '< 2.0.a') 55 | spec.add_development_dependency('pdf-reader', '~> 1.4', '>= 1.4.1') 56 | spec.add_development_dependency('prawn-dev', '~> 0.5.0') 57 | spec.add_development_dependency('prawn-manual_builder', '~> 0.4.0') 58 | end 59 | -------------------------------------------------------------------------------- /spec/data/curves.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prawnpdf/prawn/1442068706058adb80ea623192b7ae45b344f1fc/spec/data/curves.pdf -------------------------------------------------------------------------------- /spec/extensions/encoding_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # String encoding helpers. 4 | module EncodingHelpers 5 | # Make sure the string is Windows-1252 -encoded. 6 | def win1252_string(str) 7 | str.dup.force_encoding(Encoding::Windows_1252) 8 | end 9 | 10 | # Make sure the string is binary-encoded 11 | def bin_string(str) 12 | str.dup.force_encoding(Encoding::ASCII_8BIT) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/prawn/document/column_box_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Document::ColumnBox do 6 | let(:pdf) { create_pdf } 7 | 8 | it 'has sensible left and right values' do 9 | pdf.column_box( 10 | [0, pdf.cursor], 11 | width: pdf.bounds.width, 12 | height: 200, 13 | columns: 3, 14 | spacer: 25, 15 | ) do 16 | left = pdf.bounds.left 17 | right = pdf.bounds.right 18 | 19 | pdf.bounds.move_past_bottom # next column 20 | 21 | expect(pdf.bounds.left).to be > left 22 | expect(pdf.bounds.left).to be > right 23 | expect(pdf.bounds.right).to be > pdf.bounds.left 24 | end 25 | end 26 | 27 | it 'includes spacers between columns but not at the end' do 28 | pdf.column_box( 29 | [0, pdf.cursor], 30 | width: 500, 31 | height: 200, 32 | columns: 3, 33 | spacer: 25, 34 | ) do 35 | expect(pdf.bounds.width).to eq(150) # (500 - (25 * 2)) / 3 36 | 37 | pdf.bounds.move_past_bottom 38 | pdf.bounds.move_past_bottom 39 | 40 | expect(pdf.bounds.right).to eq(500) 41 | end 42 | end 43 | 44 | it 'does not reset the top margin on a new page by default' do 45 | page_top = pdf.cursor 46 | pdf.move_down(50) 47 | init_column_top = pdf.cursor 48 | pdf.column_box([0, pdf.cursor], width: 500, height: 200, columns: 2) do 49 | pdf.bounds.move_past_bottom 50 | pdf.bounds.move_past_bottom 51 | 52 | expect(pdf.bounds.absolute_top).to eq(init_column_top) 53 | expect(pdf.bounds.absolute_top).to_not eq(page_top) 54 | end 55 | end 56 | 57 | it 'does reset the top margin when reflow_margins is set' do 58 | page_top = pdf.cursor 59 | pdf.move_down(50) 60 | init_column_top = pdf.cursor 61 | pdf.column_box( 62 | [0, pdf.cursor], 63 | width: 500, 64 | reflow_margins: true, 65 | height: 200, 66 | columns: 2, 67 | ) do 68 | pdf.bounds.move_past_bottom 69 | pdf.bounds.move_past_bottom 70 | 71 | expect(pdf.bounds.absolute_top).to eq(page_top) 72 | expect(pdf.bounds.absolute_top).to_not eq(init_column_top) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/prawn/document_annotations_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Document do 6 | let(:pdf) { create_pdf } 7 | 8 | describe 'When creating annotations' do 9 | it 'appends annotation to current page' do 10 | pdf.start_new_page 11 | pdf.annotate( 12 | Rect: [0, 0, 10, 10], 13 | Subtype: :Text, 14 | Contents: 'Hello world!', 15 | ) 16 | PDF::Reader.open(StringIO.new(pdf.render)) do |pdf| 17 | expect(pdf.page(1).attributes[:Annots]).to be_nil 18 | expect(pdf.page(2).attributes[:Annots].size).to eq(1) 19 | end 20 | end 21 | 22 | it 'forces :Type to be :Annot' do 23 | opts = pdf.annotate( 24 | Rect: [0, 0, 10, 10], 25 | Subtype: :Text, 26 | Contents: 'Hello world!', 27 | ) 28 | expect(opts[:Type]).to eq(:Annot) 29 | opts = pdf.annotate( 30 | Type: :Bogus, 31 | Rect: [0, 0, 10, 10], 32 | Subtype: :Text, 33 | Contents: 'Hello world!', 34 | ) 35 | expect(opts[:Type]).to eq(:Annot) 36 | end 37 | end 38 | 39 | describe 'When creating text annotations' do 40 | let(:rect) { [0, 0, 10, 10] } 41 | let(:content) { 'Hello, world!' } 42 | 43 | it 'builds appropriate annotation' do 44 | opts = pdf.text_annotation(rect, content) 45 | expect(opts[:Type]).to eq(:Annot) 46 | expect(opts[:Subtype]).to eq(:Text) 47 | expect(opts[:Rect]).to eq(rect) 48 | expect(opts[:Contents]).to eq(content) 49 | end 50 | 51 | it 'merges extra options' do 52 | opts = pdf.text_annotation(rect, content, Open: true, Subtype: :Bogus) 53 | expect(opts[:Subtype]).to eq(:Text) 54 | expect(opts[:Open]).to be(true) 55 | end 56 | end 57 | 58 | describe 'When creating link annotations' do 59 | let(:rect) { [0, 0, 10, 10] } 60 | let(:dest) { 'home' } 61 | 62 | it 'builds appropriate annotation' do 63 | opts = pdf.link_annotation(rect, Dest: dest) 64 | expect(opts[:Type]).to eq(:Annot) 65 | expect(opts[:Subtype]).to eq(:Link) 66 | expect(opts[:Rect]).to eq(rect) 67 | expect(opts[:Dest]).to eq(dest) 68 | end 69 | 70 | it 'merges extra options' do 71 | opts = pdf.link_annotation(rect, Dest: dest, Subtype: :Bogus) 72 | expect(opts[:Subtype]).to eq(:Link) 73 | expect(opts[:Dest]).to eq(dest) 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/prawn/document_destinations_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Document do 6 | describe 'When creating destinations' do 7 | let(:pdf) { create_pdf } 8 | 9 | it 'adds entry to Dests name tree' do 10 | expect(pdf.dests.data.empty?).to be(true) 11 | pdf.add_dest('candy', 'chocolate') 12 | expect(pdf.dests.data.size).to eq(1) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/prawn/document_reference_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Document do 6 | describe 'A Reference object' do 7 | describe 'generated via Prawn::Document' do 8 | it 'returns a proper reference on ref!' do 9 | pdf = described_class.new 10 | expect(pdf.ref!({}).is_a?(PDF::Core::Reference)).to be(true) 11 | end 12 | 13 | it 'returns an identifier on ref' do 14 | pdf = described_class.new 15 | r = pdf.ref({}) 16 | expect(r.is_a?(Integer)).to be(true) 17 | end 18 | 19 | it 'has :Length of stream if it has one when compression disabled' do 20 | pdf = described_class.new(compress: false) 21 | ref = pdf.ref!({}) 22 | ref << 'Hello' 23 | expect(ref.stream.data[:Length]).to eq(5) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/prawn/document_span_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Document do 6 | let(:pdf) { create_pdf } 7 | 8 | it 'onlies accept :position as option in debug mode' do 9 | Prawn.debug = true 10 | expect { 11 | pdf.span(350, x: 3) do 12 | # draw 13 | end 14 | }.to raise_error(Prawn::Errors::UnknownOption) 15 | end 16 | 17 | it 'has raise an error if :position is invalid' do 18 | expect { 19 | pdf.span(350, position: :x) do 20 | # draw 21 | end 22 | }.to raise_error(ArgumentError) 23 | end 24 | 25 | it 'restores the margin box when bounding box exits' do 26 | margin_box = pdf.bounds 27 | 28 | pdf.span(350, position: :center) do 29 | pdf.text("Here's some centered text in a 350 point column. " * 100) 30 | end 31 | 32 | expect(pdf.bounds).to eq(margin_box) 33 | end 34 | 35 | it 'does create a margin box' do 36 | margin_box = 37 | pdf.span(350, position: :center) { 38 | pdf.text("Here's some centered text in a 350 point column. " * 100) 39 | } 40 | 41 | expect(margin_box.top).to eq(792.0) 42 | expect(margin_box.bottom).to eq(0) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/prawn/font_metric_cache_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'pathname' 5 | 6 | describe Prawn::FontMetricCache do 7 | let(:document) { Prawn::Document.new } 8 | let(:font_metric_cache) { described_class.new(document) } 9 | 10 | it 'starts with an empty cache' do 11 | expect(font_metric_cache.instance_variable_get(:@cache)).to be_empty 12 | end 13 | 14 | it 'caches the width of the provided string' do 15 | font_metric_cache.width_of('M', {}) 16 | 17 | expect(font_metric_cache.instance_variable_get(:@cache).size).to eq(1) 18 | end 19 | 20 | it 'onlies cache a single copy of the same string' do 21 | font_metric_cache.width_of('M', {}) 22 | font_metric_cache.width_of('M', {}) 23 | 24 | expect(font_metric_cache.instance_variable_get(:@cache).size).to eq(1) 25 | end 26 | 27 | it 'caches different copies for different strings' do 28 | font_metric_cache.width_of('M', {}) 29 | font_metric_cache.width_of('W', {}) 30 | 31 | expect(font_metric_cache.instance_variable_get(:@cache).entries.size) 32 | .to eq 2 33 | end 34 | 35 | it 'caches different copies of the same string with different font sizes' do 36 | font_metric_cache.width_of('M', {}) 37 | 38 | document.font_size(24) 39 | font_metric_cache.width_of('M', {}) 40 | 41 | expect(font_metric_cache.instance_variable_get(:@cache).entries.size) 42 | .to eq 2 43 | end 44 | 45 | it 'caches different copies of the same string with different fonts' do 46 | font_metric_cache.width_of('M', {}) 47 | 48 | document.font('Courier') 49 | font_metric_cache.width_of('M', {}) 50 | 51 | expect(font_metric_cache.instance_variable_get(:@cache).entries.size) 52 | .to eq 2 53 | end 54 | 55 | it 'does not use the cached width of a different font size' do 56 | pdf = 57 | Prawn::Document.new do 58 | font('Helvetica', size: 42, style: :bold) do 59 | text('First part M') 60 | end 61 | font('Helvetica', size: 12) do 62 | text('First part M second part', inline_format: true) 63 | text('First part W second part.', inline_format: true) 64 | end 65 | end 66 | 67 | x_positions = PDF::Inspector::Text.analyze(pdf.render).positions.map(&:first) 68 | 69 | expect(x_positions[2]).to be_within(3.0).of(x_positions[4]) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/prawn/graphics/blend_mode_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Graphics::BlendMode do 6 | def make_blend_mode(blend_mode) 7 | pdf.blend_mode(blend_mode) do 8 | yield if block_given? 9 | end 10 | end 11 | 12 | let(:pdf) { create_pdf } 13 | 14 | it 'the PDF version should be at least 1.4' do 15 | make_blend_mode(:Multiply) 16 | str = pdf.render 17 | expect(str[0, 8]).to eq('%PDF-1.4') 18 | end 19 | 20 | it 'a new extended graphics state should be created for each unique blend mode setting' do 21 | make_blend_mode(:Multiply) do 22 | make_blend_mode(:Screen) 23 | end 24 | extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates 25 | expect(extgstates.length).to eq(2) 26 | end 27 | 28 | it 'a new extended graphics state should not be created for each duplicate blend mode setting' do 29 | make_blend_mode(:Multiply) do 30 | make_blend_mode(:Multiply) 31 | end 32 | extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates 33 | expect(extgstates.length).to eq(1) 34 | end 35 | 36 | it 'setting the blend mode with only one parameter sets a single blend mode value' do 37 | make_blend_mode(:Multiply) 38 | extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates.first 39 | expect(extgstate[:blend_mode]).to eq(:Multiply) 40 | end 41 | 42 | it 'setting the blend mode with multiple parameters sets an array of blend modes' do 43 | make_blend_mode(%i[Multiply Screen Overlay]) 44 | extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates.first 45 | expect(extgstate[:blend_mode]).to eq(%i[Multiply Screen Overlay]) 46 | end 47 | 48 | describe 'with more than one page' do 49 | it 'the extended graphic state resource should be added to both pages' do 50 | make_blend_mode(:Multiply) 51 | pdf.start_new_page 52 | make_blend_mode(:Multiply) 53 | extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates 54 | extgstate = extgstates[0] 55 | expect(extgstates.length).to eq(2) 56 | expect(extgstate[:blend_mode]).to eq(:Multiply) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/prawn/image_handler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::ImageHandler do 6 | let(:image_handler) { described_class.new } 7 | 8 | let(:dummy_handler) do 9 | Class.new do 10 | def self.can_render?(_blob); end 11 | end 12 | end 13 | let(:handler_a) { object_double(dummy_handler, 'Handler A') } 14 | let(:handler_b) { object_double(dummy_handler, 'Handler B') } 15 | 16 | it 'finds the image handler for an image' do 17 | allow(handler_a).to receive(:can_render?).and_return(true) 18 | 19 | image_handler.register(handler_a) 20 | image_handler.register(handler_b) 21 | 22 | handler = image_handler.find('arbitrary blob') 23 | expect(handler).to eq(handler_a) 24 | end 25 | 26 | it 'can prepend handlers' do 27 | allow(handler_b).to receive(:can_render?).and_return(true) 28 | 29 | image_handler.register(handler_a) 30 | image_handler.register!(handler_b) 31 | 32 | handler = image_handler.find('arbitrary blob') 33 | expect(handler).to eq(handler_b) 34 | end 35 | 36 | it 'can unregister a handler' do 37 | allow(handler_b).to receive(:can_render?).and_return(true) 38 | 39 | image_handler.register(handler_a) 40 | image_handler.register(handler_b) 41 | 42 | image_handler.unregister(handler_a) 43 | 44 | handler = image_handler.find('arbitrary blob') 45 | expect(handler).to eq(handler_b) 46 | end 47 | 48 | it 'raises an error when no matching handler is found' do 49 | allow(handler_a).to receive(:can_render?).and_return(false) 50 | allow(handler_b).to receive(:can_render?).and_return(false) 51 | 52 | image_handler.register(handler_a) 53 | image_handler.register(handler_b) 54 | 55 | expect { image_handler.find('arbitrary blob') } 56 | .to(raise_error(Prawn::Errors::UnsupportedImageType)) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/prawn/images/jpg_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Spec'ing the PNG class. Not complete yet - still needs to check the 4 | # contents of palette and transparency to ensure they're correct. 5 | # Need to find files that have these sections first. 6 | 7 | require 'spec_helper' 8 | 9 | describe Prawn::Images::JPG do 10 | let(:img_data) { File.binread("#{Prawn::DATADIR}/images/pigs.jpg") } 11 | 12 | it 'reads the basic attributes correctly' do 13 | jpg = described_class.new(img_data) 14 | 15 | expect(jpg.width).to eq(604) 16 | expect(jpg.height).to eq(453) 17 | expect(jpg.bits).to eq(8) 18 | expect(jpg.channels).to eq(3) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/prawn/measurements_extensions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'prawn/measurement_extensions' 5 | 6 | describe Prawn::Measurements do 7 | describe 'Numeric extensions' do 8 | it 'converts units to PostScriptPoints' do 9 | expect(1.mm).to be_within(0.000000001).of(2.834645669) 10 | expect(1.mm).to eq(72 / 25.4) 11 | expect(2.mm).to eq(2 * 72 / 25.4) 12 | expect(3.mm).to eq(3 * 72 / 25.4) 13 | expect(-3.mm).to eq(-3 * 72 / 25.4) 14 | expect(1.cm).to eq(10 * 72 / 25.4) 15 | expect(1.dm).to eq(100 * 72 / 25.4) 16 | expect(1.m).to eq(1000 * 72 / 25.4) 17 | 18 | expect(1.in).to eq(72) 19 | expect(1.ft).to eq(72 * 12) 20 | expect(1.yd).to eq(72 * 12 * 3) 21 | expect(1.pt).to eq(1) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/prawn/soft_mask_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::SoftMask do 6 | let(:pdf) { create_pdf } 7 | 8 | def make_soft_mask 9 | pdf.save_graphics_state do 10 | pdf.soft_mask do 11 | if block_given? 12 | yield 13 | else 14 | pdf.fill_color('808080') 15 | pdf.fill_rectangle([100, 100], 200, 200) 16 | end 17 | end 18 | 19 | pdf.fill_color('000000') 20 | pdf.fill_rectangle([0, 0], 200, 200) 21 | end 22 | end 23 | 24 | it 'has PDF version at least 1.4' do 25 | make_soft_mask 26 | str = pdf.render 27 | expect(str[0, 8]).to eq('%PDF-1.4') 28 | end 29 | 30 | it 'creates a new extended graphics state for each unique soft mask' do 31 | make_soft_mask do 32 | pdf.fill_color('808080') 33 | pdf.fill_rectangle([100, 100], 200, 200) 34 | end 35 | 36 | make_soft_mask do 37 | pdf.fill_color('808080') 38 | pdf.fill_rectangle([10, 10], 200, 200) 39 | end 40 | 41 | extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates 42 | expect(extgstates.length).to eq(2) 43 | end 44 | 45 | it 'a new extended graphics state contains soft mask with drawing instructions' do 46 | make_soft_mask do 47 | pdf.fill_color('808080') 48 | pdf.fill_rectangle([100, 100], 200, 200) 49 | end 50 | 51 | extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates.first 52 | expect(extgstate[:soft_mask][:G].data).to eq( 53 | "q\n/DeviceRGB cs\n0.0 0.0 0.0 scn\n/DeviceRGB CS\n0.0 0.0 0.0 SCN\n" \ 54 | "1 w\n0 J\n0 j\n[] 0 d\n/DeviceRGB cs\n0.50196 0.50196 0.50196 scn\n" \ 55 | "100.0 -100.0 200.0 200.0 re\nf\nQ\n", 56 | ) 57 | end 58 | 59 | it 'does not create duplicate extended graphics states' do 60 | make_soft_mask do 61 | pdf.fill_color('808080') 62 | pdf.fill_rectangle([100, 100], 200, 200) 63 | end 64 | 65 | make_soft_mask do 66 | pdf.fill_color('808080') 67 | pdf.fill_rectangle([100, 100], 200, 200) 68 | end 69 | 70 | extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates 71 | expect(extgstates.length).to eq(1) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/prawn/text_rendering_mode_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Text do 6 | let(:pdf) { create_pdf } 7 | 8 | describe '#text_rendering_mode' do 9 | it 'draws the text rendering mode to the document' do 10 | pdf.text_rendering_mode(:stroke) do 11 | pdf.text('hello world') 12 | end 13 | contents = PDF::Inspector::Text.analyze(pdf.render) 14 | expect(contents.text_rendering_mode.first).to eq(1) 15 | end 16 | 17 | it 'does not draw the text rendering mode to the document when the new mode matches the old' do 18 | pdf.text_rendering_mode(:fill) do 19 | pdf.text('hello world') 20 | end 21 | contents = PDF::Inspector::Text.analyze(pdf.render) 22 | expect(contents.text_rendering_mode).to be_empty 23 | end 24 | 25 | it 'restores character spacing to 0' do 26 | pdf.text_rendering_mode(:stroke) do 27 | pdf.text('hello world') 28 | end 29 | contents = PDF::Inspector::Text.analyze(pdf.render) 30 | expect(contents.text_rendering_mode).to eq([1, 0]) 31 | end 32 | 33 | it 'functions as an accessor when no parameter given' do 34 | pdf.text_rendering_mode(:fill_stroke) do 35 | pdf.text('hello world') 36 | expect(pdf.text_rendering_mode).to eq(:fill_stroke) 37 | end 38 | expect(pdf.text_rendering_mode).to eq(:fill) 39 | end 40 | 41 | it 'raise_errors an exception when passed an invalid mode' do 42 | expect { pdf.text_rendering_mode(-1) }.to raise_error(ArgumentError) 43 | expect { pdf.text_rendering_mode(8) }.to raise_error(ArgumentError) 44 | expect { pdf.text_rendering_mode(:flil) }.to raise_error(ArgumentError) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/prawn/text_with_inline_formatting_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::Text do 6 | let(:pdf) { create_pdf } 7 | 8 | describe '#formatted_text' do 9 | it 'draws text' do 10 | string = 'hello world' 11 | format_array = [{ text: string }] 12 | pdf.formatted_text(format_array) 13 | # grab the text from the rendered PDF and ensure it matches 14 | text = PDF::Inspector::Text.analyze(pdf.render) 15 | expect(text.strings.first).to eq(string) 16 | end 17 | end 18 | 19 | describe '#text with inline styling' do 20 | it "automatically moves to a new page if the tallest fragment on the next line won't fit in the available space" do 21 | pdf.move_cursor_to(pdf.font.height) 22 | formatted = "this contains sized text" 23 | pdf.text(formatted, inline_format: true) 24 | pages = PDF::Inspector::Page.analyze(pdf.render).pages 25 | expect(pages.size).to eq(2) 26 | end 27 | 28 | it 'embeds links as literal strings' do 29 | pdf.text( 30 | "wiki", 31 | inline_format: true, 32 | ) 33 | expect(pdf.render).to match(%r{/URI\s+\(http://wiki\.github\.com}) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/prawn/transformation_stack_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::TransformationStack do 6 | let(:pdf) do 7 | create_pdf do |document| 8 | document.add_to_transformation_stack(2, 0, 0, 2, 100, 100) 9 | end 10 | end 11 | let(:stack) { pdf.instance_variable_get(:@transformation_stack) } 12 | 13 | describe '#add_to_transformation_stack' do 14 | it 'creates and adds to the stack' do 15 | pdf.add_to_transformation_stack(1, 0, 0, 1, 20, 20) 16 | 17 | expect(stack).to eq [[[2, 0, 0, 2, 100, 100], [1, 0, 0, 1, 20, 20]]] 18 | end 19 | 20 | it 'adds to the last stack' do 21 | pdf.save_transformation_stack 22 | pdf.add_to_transformation_stack(1, 0, 0, 1, 20, 20) 23 | 24 | expect(stack).to eq [ 25 | [[2, 0, 0, 2, 100, 100]], 26 | [[2, 0, 0, 2, 100, 100], [1, 0, 0, 1, 20, 20]], 27 | ] 28 | end 29 | end 30 | 31 | describe '#save_transformation_stack' do 32 | it 'clones the last stack' do 33 | pdf.save_transformation_stack 34 | 35 | expect(stack.length).to eq 2 36 | expect(stack.first).to eq stack.last 37 | expect(stack.first).to_not be stack.last 38 | end 39 | end 40 | 41 | describe '#restore_transformation_stack' do 42 | it 'pops off the last stack' do 43 | pdf.save_transformation_stack 44 | pdf.add_to_transformation_stack(1, 0, 0, 1, 20, 20) 45 | pdf.restore_transformation_stack 46 | 47 | expect(stack).to eq [[[2, 0, 0, 2, 100, 100]]] 48 | end 49 | end 50 | 51 | describe 'current_transformation_matrix_with_translation' do 52 | before do 53 | pdf.add_to_transformation_stack(1, 0, 0, 1, 20, 20) 54 | end 55 | 56 | it 'calculates the last transformation' do 57 | expect(pdf.current_transformation_matrix_with_translation) 58 | .to eq [2, 0, 0, 2, 140, 140] 59 | end 60 | 61 | it 'adds the supplied x and y coordinates to the transformation stack' do 62 | expect(pdf.current_transformation_matrix_with_translation(15, 15)) 63 | .to eq [2, 0, 0, 2, 170, 170] 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/prawn/view_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Prawn::View do 6 | let(:view_object) { Object.new.tap { |o| o.extend(described_class) } } 7 | 8 | it 'provides a Prawn::Document object by default' do 9 | expect(view_object.document).to be_a(Prawn::Document) 10 | end 11 | 12 | it 'delegates unhandled methods to object returned by document method' do 13 | doc = instance_double(Prawn::Document) 14 | allow(view_object).to receive(:document).and_return(doc) 15 | allow(doc).to receive(:fill_gradient) 16 | block = proc {} 17 | 18 | view_object.fill_gradient([1, 2], [3, 4], 'ff0000', [0, 0, 0, 1], apply_margin_options: true, &block) 19 | 20 | expect(doc).to have_received(:fill_gradient) 21 | .with([1, 2], [3, 4], 'ff0000', [0, 0, 0, 1], apply_margin_options: true, &block) 22 | end 23 | 24 | it 'allows a block-like DSL via the update method' do 25 | doc = instance_double(Prawn::Document) 26 | allow(view_object).to receive(:document).and_return(doc) 27 | 28 | allow(doc).to receive(:font) 29 | allow(doc).to receive(:cap_style) 30 | 31 | view_object.update do 32 | font 33 | cap_style 34 | end 35 | expect(doc).to have_received(:font) 36 | expect(doc).to have_received(:cap_style) 37 | end 38 | 39 | it 'aliases save_as() to document.render_file()' do 40 | doc = instance_double(Prawn::Document) 41 | allow(doc).to receive(:render_file) 42 | 43 | allow(view_object).to receive(:document).and_return(doc) 44 | 45 | view_object.save_as('foo.pdf') 46 | expect(doc).to have_received(:render_file) 47 | end 48 | 49 | describe '#respond_to?', issue: 1064 do 50 | subject { view_object.respond_to?(method) } 51 | 52 | context 'when called with an existing method from Prawn::Document' do 53 | let(:method) { :text } 54 | 55 | it { is_expected.to be_truthy } 56 | end 57 | 58 | context 'when called with a non-existing method' do 59 | let(:method) { :non_existing_method } 60 | 61 | it { is_expected.to be_falsey } 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/prawn_manual_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'digest/sha2' 5 | 6 | MANUAL_HASH = 7 | case RUBY_ENGINE 8 | when 'ruby' 9 | '06ce69758c64b0e5f14d09474d94ba580aaa4edca7014c6ab5bc9536b5bb0d0c163425aceff74a0ad3867859f8372e07b96e63822cc0e789549bb3a35d3cf185' 10 | when 'jruby' 11 | '31b7c93ddf81f0c734a036644f07541071af36cee1f2e9a6c99847bd98ae6a66a9755afb69f4351fac711382bfc04d1cb50bc00122d7c4d187428f1582680794' 12 | end 13 | 14 | RSpec.describe Prawn do 15 | describe 'manual' do 16 | # JRuby's zlib is a bit quirky. It sometimes produces different output to 17 | # libzlib (used by MRI). It's still a proper deflate stream and can be 18 | # decompressed just fine but for whatever reason compressin produses 19 | # different output. 20 | # 21 | # See: https://github.com/jruby/jruby/issues/4244 22 | it 'contains no unexpected changes' do 23 | ENV['CI'] ||= 'true' 24 | 25 | manual_path = File.expand_path('../manual/manual.rb', __dir__) 26 | manual = eval(File.read(manual_path), TOPLEVEL_BINDING, manual_path) # rubocop: disable Security/Eval 27 | s = manual.generate 28 | 29 | hash = Digest::SHA512.hexdigest(s) 30 | 31 | expect(hash).to eq MANUAL_HASH 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | puts "Prawn specs: Running on Ruby Version: #{RUBY_VERSION}" 4 | 5 | if ENV['COVERAGE'] 6 | require 'simplecov' 7 | SimpleCov.start do 8 | add_filter '/spec/' 9 | end 10 | end 11 | 12 | require_relative '../lib/prawn' 13 | 14 | Prawn.debug = true 15 | Prawn::Fonts::AFM.hide_m17n_warning = true 16 | 17 | require 'rspec' 18 | require 'pdf/reader' 19 | require 'pdf/inspector' 20 | 21 | # Requires supporting ruby files with custom matchers and macros, etc, 22 | # in spec/extensions/ and its subdirectories. 23 | Dir[File.join(__dir__, 'extensions', '**', '*.rb')].sort.each { |f| require f } 24 | 25 | RSpec.configure do |config| 26 | config.include(EncodingHelpers) 27 | end 28 | 29 | # Create a document. 30 | def create_pdf(klass = Prawn::Document, &block) 31 | klass.new(margin: 0, &block) 32 | end 33 | 34 | RSpec::Matchers.define(:have_parseable_xobjects) do 35 | match do |actual| 36 | expect { PDF::Inspector::XObject.analyze(actual.render) }.to_not raise_error 37 | true 38 | end 39 | failure_message do |actual| 40 | "expected that #{actual}'s XObjects could be successfully parsed" 41 | end 42 | end 43 | 44 | # Make some methods public to assist in testing 45 | # module Prawn 46 | # module Graphics 47 | # public :map_to_absolute 48 | # end 49 | # end 50 | --------------------------------------------------------------------------------