├── .github └── workflows │ └── build.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib └── prawn │ ├── markup.rb │ └── markup │ ├── builders │ ├── list_builder.rb │ ├── nestable_builder.rb │ └── table_builder.rb │ ├── elements │ ├── cell.rb │ ├── item.rb │ └── list.rb │ ├── interface.rb │ ├── processor.rb │ ├── processor │ ├── blocks.rb │ ├── headings.rb │ ├── images.rb │ ├── inputs.rb │ ├── lists.rb │ ├── tables.rb │ └── text.rb │ ├── support │ ├── hash_merger.rb │ ├── normalizer.rb │ └── size_converter.rb │ └── version.rb ├── prawn-markup.gemspec └── spec ├── fixtures ├── DejaVuSans.ttf ├── logo.png └── showcase.html ├── pdf_helpers.rb ├── prawn ├── markup │ ├── interface_spec.rb │ ├── normalizer_spec.rb │ ├── processor │ │ ├── blocks_spec.rb │ │ ├── headings_spec.rb │ │ ├── images_spec.rb │ │ ├── inputs_spec.rb │ │ ├── lists_spec.rb │ │ ├── tables_spec.rb │ │ └── text_spec.rb │ ├── processor_spec.rb │ └── showcase_spec.rb └── markup_spec.rb └── spec_helper.rb /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # create pull request to run actions on other branches 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | ruby-version: ["3.1", "3.2", "3.3"] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby-version }} 23 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 24 | - name: Run tests 25 | run: bundle exec rake 26 | 27 | lint: 28 | name: Lint 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: 3.2 36 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 37 | - name: Run rubocop 38 | run: bundle exec rubocop 39 | 40 | coverage: 41 | name: Coverage 42 | runs-on: ubuntu-latest 43 | if: ${{ !github.event.pull_request.head.repo.fork }} 44 | steps: 45 | - uses: actions/checkout@v4 46 | - name: Set up Ruby 47 | uses: ruby/setup-ruby@v1 48 | with: 49 | ruby-version: 3.2 50 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 51 | - uses: paambaati/codeclimate-action@v9.0.0 52 | env: 53 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 54 | with: 55 | coverageCommand: bundle exec rake 56 | coverageLocations: | 57 | ${{github.workspace}}/spec/coverage/coverage.json:simplecov 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/coverage/ 9 | /tmp/ 10 | 11 | # rspec failure tracking 12 | .rspec_status 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayCopNames: true 3 | NewCops: enable 4 | SuggestExtensions: false 5 | TargetRubyVersion: 3.1 6 | Exclude: 7 | - "*.gemspec" 8 | - spec/**/* 9 | - vendor/**/* 10 | 11 | Metrics/AbcSize: 12 | Severity: error 13 | 14 | Metrics/ClassLength: 15 | Max: 250 16 | Severity: error 17 | 18 | Metrics/CyclomaticComplexity: 19 | Severity: error 20 | 21 | Metrics/MethodLength: 22 | Max: 10 23 | Severity: error 24 | 25 | Metrics/ModuleLength: 26 | Max: 150 27 | Severity: error 28 | 29 | Metrics/ParameterLists: 30 | Max: 4 31 | Severity: warning 32 | 33 | Layout/LineLength: 34 | Max: 100 35 | Severity: error 36 | 37 | # We thinks that's fine for specs 38 | Layout/EmptyLinesAroundBlockBody: 39 | Enabled: false 40 | 41 | # We thinks that's fine 42 | Layout/EmptyLinesAroundClassBody: 43 | Enabled: false 44 | 45 | # We thinks that's fine 46 | Layout/EmptyLinesAroundModuleBody: 47 | Enabled: false 48 | 49 | # Keep for now, easier with superclass definitions 50 | Style/ClassAndModuleChildren: 51 | Enabled: false 52 | 53 | # The ones we use must exist for the entire class hierarchy. 54 | Style/ClassVars: 55 | Enabled: false 56 | 57 | # Well, well, well 58 | Style/Documentation: 59 | Enabled: false 60 | 61 | # Keep single line bodys for if and unless 62 | Style/IfUnlessModifier: 63 | Enabled: false 64 | 65 | # We think that's the developers choice 66 | Style/GuardClause: 67 | Enabled: false 68 | 69 | # Backward compatibility 70 | Style/OptionalBooleanParameter: 71 | Enabled: false 72 | 73 | # Backwards compatibility 74 | Style/SafeNavigation: 75 | Enabled: false 76 | 77 | # Backwards compatibility 78 | Style/SlicingWithRange: 79 | Enabled: false 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at info@puzzle.ch. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in prawn-markup.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | prawn-markup (1.1.0) 5 | nokogiri 6 | prawn 7 | prawn-table 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | Ascii85 (2.0.1) 13 | afm (0.2.2) 14 | ast (2.4.2) 15 | bigdecimal (3.1.9) 16 | byebug (11.1.3) 17 | diff-lcs (1.6.0) 18 | docile (1.4.1) 19 | hashery (2.1.2) 20 | json (2.10.2) 21 | language_server-protocol (3.17.0.4) 22 | lint_roller (1.1.0) 23 | matrix (0.4.2) 24 | nokogiri (1.18.8-x86_64-linux-gnu) 25 | racc (~> 1.4) 26 | parallel (1.26.3) 27 | parser (3.3.7.1) 28 | ast (~> 2.4.1) 29 | racc 30 | pdf-core (0.10.0) 31 | pdf-inspector (1.3.0) 32 | pdf-reader (>= 1.0, < 3.0.a) 33 | pdf-reader (2.14.1) 34 | Ascii85 (>= 1.0, < 3.0, != 2.0.0) 35 | afm (~> 0.2.1) 36 | hashery (~> 2.0) 37 | ruby-rc4 38 | ttfunk 39 | prawn (2.5.0) 40 | matrix (~> 0.4) 41 | pdf-core (~> 0.10.0) 42 | ttfunk (~> 1.8) 43 | prawn-table (0.2.2) 44 | prawn (>= 1.3.0, < 3.0.0) 45 | racc (1.8.1) 46 | rainbow (3.1.1) 47 | rake (13.2.1) 48 | regexp_parser (2.10.0) 49 | rspec (3.13.0) 50 | rspec-core (~> 3.13.0) 51 | rspec-expectations (~> 3.13.0) 52 | rspec-mocks (~> 3.13.0) 53 | rspec-core (3.13.3) 54 | rspec-support (~> 3.13.0) 55 | rspec-expectations (3.13.3) 56 | diff-lcs (>= 1.2.0, < 2.0) 57 | rspec-support (~> 3.13.0) 58 | rspec-mocks (3.13.2) 59 | diff-lcs (>= 1.2.0, < 2.0) 60 | rspec-support (~> 3.13.0) 61 | rspec-support (3.13.2) 62 | rubocop (1.72.2) 63 | json (~> 2.3) 64 | language_server-protocol (~> 3.17.0.2) 65 | lint_roller (~> 1.1.0) 66 | parallel (~> 1.10) 67 | parser (>= 3.3.0.2) 68 | rainbow (>= 2.2.2, < 4.0) 69 | regexp_parser (>= 2.9.3, < 3.0) 70 | rubocop-ast (>= 1.38.0, < 2.0) 71 | ruby-progressbar (~> 1.7) 72 | unicode-display_width (>= 2.4.0, < 4.0) 73 | rubocop-ast (1.38.0) 74 | parser (>= 3.3.1.0) 75 | ruby-progressbar (1.13.0) 76 | ruby-rc4 (0.1.5) 77 | simplecov (0.22.0) 78 | docile (~> 1.1) 79 | simplecov-html (~> 0.11) 80 | simplecov_json_formatter (~> 0.1) 81 | simplecov-html (0.13.1) 82 | simplecov_json_formatter (0.1.4) 83 | ttfunk (1.8.0) 84 | bigdecimal (~> 3.1) 85 | unicode-display_width (3.1.4) 86 | unicode-emoji (~> 4.0, >= 4.0.4) 87 | unicode-emoji (4.0.4) 88 | 89 | PLATFORMS 90 | x86_64-linux 91 | 92 | DEPENDENCIES 93 | bundler 94 | byebug 95 | matrix 96 | pdf-inspector 97 | prawn-markup! 98 | rake 99 | rspec 100 | rubocop 101 | simplecov 102 | 103 | BUNDLED WITH 104 | 2.2.31 105 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Puzzle ITC GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prawn::Markup 2 | 3 | [![Build Status](https://github.com/puzzle/prawn-markup/actions/workflows/build.yml/badge.svg)](https://github.com/puzzle/prawn-markup/actions/workflows/build.yml) 4 | [![Maintainability](https://api.codeclimate.com/v1/badges/52a462f9d65e33352d4e/maintainability)](https://codeclimate.com/github/puzzle/prawn-markup/maintainability) 5 | [![Test Coverage](https://api.codeclimate.com/v1/badges/52a462f9d65e33352d4e/test_coverage)](https://codeclimate.com/github/puzzle/prawn-markup/test_coverage) 6 | 7 | Adds simple HTML snippets into [Prawn](http://prawnpdf.org)-generated PDFs. All elements are layouted vertically using Prawn's formatting options. A major use case for this gem is to include WYSIWYG-generated HTML parts into server-generated PDF documents. 8 | 9 | This gem does not and will never convert entire HTML + CSS pages to PDF. Use [wkhtmltopdf](https://wkhtmltopdf.org/) for that. Have a look at the details of the [Supported HTML](#supported-html). 10 | 11 | ## Installation 12 | 13 | Add this line to your application's Gemfile: 14 | 15 | ```ruby 16 | gem 'prawn-markup' 17 | ``` 18 | 19 | And then execute: 20 | 21 | $ bundle 22 | 23 | Or install it yourself as: 24 | 25 | $ gem install prawn-markup 26 | 27 | ## Usage 28 | 29 | In your prawn Document, add HTML like this: 30 | 31 | ```ruby 32 | doc = Prawn::Document.new 33 | doc.markup('

Hello World


KTHXBYE

') 34 | ``` 35 | 36 | ## Supported HTML 37 | 38 | This gem parses the given HTML and layouts the following elements in a vertical order: 39 | 40 | - Text blocks: `p`, `div`, `ol`, `ul`, `li`, `hr`, `br` 41 | - Text semantics: `a`, `b`, `strong`, `i`, `em`, `u`, `s`, `del`, `sub`, `sup`, `color` 42 | - Headings: `h1`, `h2`, `h3`, `h4`, `h5`, `h6` 43 | - Tables: `table`, `tr`, `td`, `th` 44 | - Media: `img`, `iframe` 45 | - Inputs: `type=checkbox`, `type=radio` 46 | 47 | All other elements are ignored, their content is added to the parent element. With a few exceptions, no CSS is processed. One exception is the `width` property of `img`, `td` and `th`, which may contain values in `cm`, `mm`, `px`, `pt`, `%` or `auto`. Another exception is the `rgb` or `cmyk` properties of the Prawn-specific `color` tag. 48 | 49 | If no explicit loader is given (see below), images are loaded from `http(s)` addresses or may be contained in the `src` attribute as base64 encoded data URIs. Prawn only supports `PNG` and `JPG`. 50 | 51 | ## Example 52 | 53 | Have a look at [showcase.html](spec/fixtures/showcase.html), which is rendered by the corresponding [spec](spec/prawn/markup/showcase_spec.rb). Uncomment the `lookatit` call there to directly open the generated PDF when running the spec with `spec spec/prawn/markup/showcase_spec.rb`. 54 | 55 | ## Formatting Options 56 | 57 | To customize element formatting, do: 58 | 59 | ```ruby 60 | doc = Prawn::Document.new 61 | # set options for the entire document 62 | doc.markup_options = { 63 | text: { font: 'Times' }, 64 | table: { header: { style: :bold, background_color: 'FFFFDD' } } 65 | } 66 | # set additional options for each single call 67 | doc.markup('

Hello World


KTHXBYE

', text: { align: :center }) 68 | ``` 69 | 70 | Options may be set for `text`, `heading[1-6]`, `table` (subkeys `cell` and `header`) and `list` (subkeys `content` and `bullet`). 71 | 72 | Text and heading options include all keys from Prawns [#text](https://prawnpdf.org/docs/prawn/2.5.0/Prawn/Text.html#text-instance_method) method: `font`, `size`, `color`, `style`, `align`, `valign`, `leading`,`direction`, `character_spacing`, `indent_paragraphs`, `kerning`, `mode`. 73 | 74 | Tables and lists are rendered with [prawn-table](https://github.com/prawnpdf/prawn-table) and have the following additional options: `padding`, `borders`, `border_width`, `border_color`, `background_color`, `border_lines`, `rotate`, `overflow`, `min_font_size`. Options from `text` may be overridden. 75 | 76 | Beside these options handled by Prawn / prawn-table, the following values may be customized: 77 | 78 | - `:text` 79 | - `:preprocessor`: A proc/callable that is called each time before a chunk of text is rendered. 80 | - `:margin_bottom`: Margin after each `

`, `

    `, `