├── .github ├── issue_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── gemfiles ├── 5.0.gemfile ├── 5.1.gemfile ├── 5.2.gemfile ├── 6.0.gemfile ├── 6.1.gemfile └── 7.0.gemfile ├── generators └── wicked_pdf │ ├── templates │ └── wicked_pdf.rb │ └── wicked_pdf_generator.rb ├── init.rb ├── lib ├── generators │ └── wicked_pdf_generator.rb ├── wicked_pdf.rb └── wicked_pdf │ ├── binary.rb │ ├── middleware.rb │ ├── option_parser.rb │ ├── pdf_helper.rb │ ├── progress.rb │ ├── railtie.rb │ ├── tempfile.rb │ ├── version.rb │ ├── wicked_pdf_helper.rb │ └── wicked_pdf_helper │ └── assets.rb ├── test ├── fixtures │ ├── database.yml │ ├── document_with_long_line.html │ ├── manifest.js │ ├── subdirectory │ │ └── nested.js │ ├── wicked.css │ └── wicked.js ├── functional │ ├── pdf_helper_test.rb │ ├── wicked_pdf_helper_assets_test.rb │ └── wicked_pdf_helper_test.rb ├── test_helper.rb └── unit │ ├── wicked_pdf_binary_test.rb │ ├── wicked_pdf_option_parser_test.rb │ ├── wicked_pdf_test.rb │ └── wkhtmltopdf_location_test.rb └── wicked_pdf.gemspec /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Issue description 2 | 3 | ## Expected or desired behavior 4 | 5 | ## System specifications 6 | 7 | wicked_pdf gem version (output of `cat Gemfile.lock | grep wicked_pdf`): 8 | 9 | wkhtmltopdf version (output of `wkhtmltopdf --version`): 10 | 11 | whtmltopdf [provider gem](https://rubygems.org/search?utf8=%E2%9C%93&query=wkhtmltopdf) and version if one is used: 12 | 13 | platform/distribution and version (e.g. Windows 10 / Ubuntu 16.04 / Heroku cedar): 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | tests: 7 | name: Tests with Ruby ${{ matrix.ruby-version }} and Rails ${{ matrix.gemfile }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | gemfile: ["5.0"] 12 | ruby-version: [2.6] 13 | include: 14 | - gemfile: "5.0" 15 | ruby-version: 2.7 16 | - gemfile: "5.1" 17 | ruby-version: 2.6 18 | - gemfile: "5.1" 19 | ruby-version: 2.7 20 | - gemfile: "5.2" 21 | ruby-version: 2.6 22 | - gemfile: "5.2" 23 | ruby-version: 2.7 24 | - gemfile: "6.0" 25 | ruby-version: 2.6 26 | - gemfile: "6.0" 27 | ruby-version: 2.7 28 | - gemfile: "6.1" 29 | ruby-version: 2.7 30 | - gemfile: "6.1" 31 | ruby-version: '3.0' 32 | - gemfile: "7.0" 33 | ruby-version: 3.1 34 | - gemfile: "7.0" 35 | ruby-version: 3.2 36 | 37 | env: 38 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 39 | WKHTMLTOPDF_BIN: /usr/bin/wkhtmltopdf 40 | 41 | steps: 42 | - uses: actions/checkout@v3 43 | 44 | - name: Install OS dependencies 45 | run: | 46 | sudo apt-get update -y -qq 47 | sudo apt-get install -y wkhtmltopdf 48 | 49 | - name: Install Ruby ${{ matrix.ruby-version }} 50 | uses: ruby/setup-ruby@v1 51 | with: 52 | ruby-version: ${{ matrix.ruby-version }} 53 | bundler-cache: true 54 | 55 | - name: Run tests with Ruby ${{ matrix.ruby-version }} and Rails ${{ matrix.gemfile }} 56 | run: bundle exec rake 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.swp 19 | test/dummy/**/* 20 | gemfiles/*.lock 21 | .DS_Store -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | NewCops: disable 5 | SuggestExtensions: false 6 | TargetRubyVersion: 2.6 7 | Exclude: 8 | - 'gemfiles/bin/*' 9 | - 'test/dummy/**/*' 10 | - 'vendor/**/*' 11 | 12 | Metrics/PerceivedComplexity: 13 | Enabled: false 14 | 15 | Gemspec/RequiredRubyVersion: 16 | Enabled: false 17 | 18 | Bundler/OrderedGems: 19 | Enabled: false 20 | 21 | Style/FrozenStringLiteralComment: 22 | Enabled: false 23 | 24 | Style/RedundantBegin: 25 | Enabled: false 26 | 27 | Style/NumericPredicate: 28 | Enabled: false 29 | 30 | Style/RedundantRegexpEscape: 31 | Enabled: false 32 | 33 | Style/SafeNavigation: 34 | Enabled: false 35 | 36 | Lint/SendWithMixinArgument: 37 | Enabled: false 38 | 39 | Lint/RedundantCopDisableDirective: 40 | Enabled: false 41 | 42 | Metrics/AbcSize: 43 | Enabled: false 44 | 45 | Style/StringConcatenation: 46 | Enabled: false 47 | 48 | Style/RedundantFetchBlock: 49 | Enabled: false 50 | 51 | Style/CaseLikeIf: 52 | Enabled: false 53 | 54 | Style/SoleNestedConditional: 55 | Enabled: false 56 | 57 | Style/RedundantReturn: 58 | Enabled: false 59 | 60 | Metrics/BlockLength: 61 | Exclude: 62 | - 'wicked_pdf.gemspec' 63 | 64 | Metrics/ModuleLength: 65 | Exclude: 66 | # Excluding to keep the logic in one module for the time being. 67 | - 'lib/wicked_pdf/wicked_pdf_helper/assets.rb' 68 | 69 | # I'd like wicked_pdf to keep Ruby 1.8 compatibility for now 70 | Style/HashSyntax: 71 | EnforcedStyle: hash_rockets 72 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-01-24 11:24:49 UTC using RuboCop version 1.44.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 2 10 | # Configuration parameters: CountComments, CountAsOne. 11 | Metrics/ClassLength: 12 | Max: 203 13 | 14 | # Offense count: 7 15 | # Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. 16 | Metrics/CyclomaticComplexity: 17 | Max: 13 18 | 19 | # Offense count: 17 20 | # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods. 21 | Metrics/MethodLength: 22 | Max: 34 23 | 24 | # Offense count: 1 25 | # Configuration parameters: CountComments, CountAsOne. 26 | Metrics/ModuleLength: 27 | Max: 104 28 | 29 | # Offense count: 1 30 | Naming/AccessorMethodName: 31 | Exclude: 32 | - 'lib/wicked_pdf/middleware.rb' 33 | 34 | # Offense count: 1 35 | # This cop supports safe autocorrection (--autocorrect). 36 | # Configuration parameters: AllowOnConstant, AllowOnSelfClass. 37 | Style/CaseEquality: 38 | Exclude: 39 | - 'lib/wicked_pdf/wicked_pdf_helper.rb' 40 | 41 | # Offense count: 1 42 | Style/ClassVars: 43 | Exclude: 44 | - 'lib/wicked_pdf.rb' 45 | 46 | # Offense count: 13 47 | # Configuration parameters: AllowedConstants. 48 | Style/Documentation: 49 | Exclude: 50 | - 'spec/**/*' 51 | - 'test/**/*' 52 | - 'generators/wicked_pdf/wicked_pdf_generator.rb' 53 | - 'lib/wicked_pdf.rb' 54 | - 'lib/wicked_pdf/binary.rb' 55 | - 'lib/wicked_pdf/middleware.rb' 56 | - 'lib/wicked_pdf/option_parser.rb' 57 | - 'lib/wicked_pdf/pdf_helper.rb' 58 | - 'lib/wicked_pdf/progress.rb' 59 | - 'lib/wicked_pdf/railtie.rb' 60 | - 'lib/wicked_pdf/tempfile.rb' 61 | - 'lib/wicked_pdf/wicked_pdf_helper.rb' 62 | - 'lib/wicked_pdf/wicked_pdf_helper/assets.rb' 63 | 64 | # Offense count: 2 65 | # This cop supports safe autocorrection (--autocorrect). 66 | Style/ExpandPathArguments: 67 | Exclude: 68 | - 'test/test_helper.rb' 69 | - 'wicked_pdf.gemspec' 70 | 71 | # Offense count: 4 72 | # This cop supports safe autocorrection (--autocorrect). 73 | # Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. 74 | # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys 75 | # SupportedShorthandSyntax: always, never, either, consistent 76 | Style/HashSyntax: 77 | Exclude: 78 | - 'gemfiles/5.0.gemfile' 79 | - 'gemfiles/5.1.gemfile' 80 | - 'gemfiles/5.2.gemfile' 81 | - 'gemfiles/6.0.gemfile' 82 | 83 | # Offense count: 2 84 | # This cop supports unsafe autocorrection (--autocorrect-all). 85 | # Configuration parameters: EnforcedStyle. 86 | # SupportedStyles: literals, strict 87 | Style/MutableConstant: 88 | Exclude: 89 | - 'lib/wicked_pdf/wicked_pdf_helper/assets.rb' 90 | 91 | # Offense count: 5 92 | # This cop supports safe autocorrection (--autocorrect). 93 | # Configuration parameters: EnforcedStyle, AllowInnerSlashes. 94 | # SupportedStyles: slashes, percent_r, mixed 95 | Style/RegexpLiteral: 96 | Exclude: 97 | - 'lib/wicked_pdf/middleware.rb' 98 | - 'lib/wicked_pdf/wicked_pdf_helper/assets.rb' 99 | 100 | # Offense count: 18 101 | # This cop supports safe autocorrection (--autocorrect). 102 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. 103 | # URISchemes: http, https 104 | Layout/LineLength: 105 | Max: 563 106 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project should be documented in this file. 3 | This project attempts to adhere to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [[master branch] - Unreleased changes](https://github.com/mileszs/wicked_pdf/compare/2.8.2...HEAD) 6 | ### Breaking Changes 7 | ### New Features 8 | ### Fixes 9 | 10 | ## [2.8.2] 11 | ### Fixes 12 | - [Fix for frozen_string_literal in Ruby 3.4](https://github.com/mileszs/wicked_pdf/pull/1118) 13 | - [Add OpenStruct dependency explicitly for Ruby 3.5](https://github.com/mileszs/wicked_pdf/pull/1131) 14 | 15 | ## [2.8.1] 16 | ### Fixes 17 | - [Explicitly require OpenStruct, which isn't loaded by default anymore in new versions of Rake](https://github.com/mileszs/wicked_pdf/pull/1110) 18 | - [Ensure assets without extensions are handled correctly](https://github.com/mileszs/wicked_pdf/pull/1115) 19 | 20 | ## [2.8.0] 21 | ### New Features 22 | - [Add New config option `raise_on_missing_assets`](https://github.com/mileszs/wicked_pdf/pull/1094) 23 | - [Add support for truffleruby (22.1.0) on Linux](https://github.com/mileszs/wicked_pdf/pull/1028) 24 | 25 | ### Fixes 26 | - [Fix Propshaft encoding issue](https://github.com/mileszs/wicked_pdf/pull/1096) 27 | - [Fix Webpacker & Shakapacker compatibility issue](https://github.com/mileszs/wicked_pdf/pull/1099) 28 | 29 | ## [2.7.0] 30 | ### New Features 31 | - [Support Shakapacker 7](https://github.com/mileszs/wicked_pdf/pull/1067) 32 | - [Add option `delete_temporary_files` to keep the temporary files generated by `pdf_from_string` method](https://github.com/mileszs/wicked_pdf/pull/1068) 33 | - [Add support for --allow flag](https://github.com/mileszs/wicked_pdf/pull/1030) 34 | 35 | ## Fixes 36 | - [Add require for `stringio`, which is no longer loaded by default in Ruby 3.1+](https://github.com/mileszs/wicked_pdf/pull/1062) 37 | - [Fix CI build.](https://github.com/mileszs/wicked_pdf/pull/1055) 38 | - [Fix Header/footer temporary file is removed before `wkhtmltopdf` is called](https://github.com/mileszs/wicked_pdf/pull/1039) 39 | - [Bump rubocop to 1.46](https://github.com/mileszs/wicked_pdf/pull/1051) 40 | - [Add Ruby 3.2 to the test matrix](https://github.com/mileszs/wicked_pdf/pull/1046) 41 | 42 | ## [2.6.3] 43 | ### Fixes 44 | - [Fix typo of #possible_binary_locations](https://github.com/mileszs/wicked_pdf/pull/1025) 45 | - [Drop unused executables gemspec directive](https://github.com/mileszs/wicked_pdf/pull/1024) 46 | 47 | ## [2.6.2] 48 | ### Fixes 49 | - [Fix undefined local variable or method 'block' for render_to_string](https://github.com/mileszs/wicked_pdf/pull/962) 50 | - [Add require for `delegate`, which is no longer loaded by default in Ruby 2.7+](https://github.com/mileszs/wicked_pdf/pull/1019) 51 | ## [2.6.0] 52 | ### New Features 53 | - [Support Propshaft in find_asset helper](https://github.com/mileszs/wicked_pdf/pull/1010) 54 | ### Fixes 55 | - [Update Changelog with changes from 2.1.0](https://github.com/mileszs/wicked_pdf/pull/1013) 56 | - [Fix CI build for Rails 7.](https://github.com/mileszs/wicked_pdf/pull/1014) 57 | 58 | ## [2.5.4] December 20th 2021 769f9df487f3c1e31dc91431666baa78d2aa24fb 59 | ### New Features 60 | - [Test with Rails 7](https://github.com/mileszs/wicked_pdf/pull/998) 61 | ### Fixes 62 | - [Include view helper on view load.](https://github.com/mileszs/wicked_pdf/pull/992) 63 | 64 | ## [2.5.3] December 15th 2021 7991877de634067b4245fb47fdad65da43761887 65 | - [Fix check for webpacker version](https://github.com/mileszs/wicked_pdf/pull/964) 66 | - [Complete transition to Github actions](https://github.com/mileszs/wicked_pdf/pull/987) 67 | 68 | ## [2.5.2] November 2021 - fix webpacker_source_url bdd0ca3eca759e277ce5461141b1506f56fefcd1 69 | - [fix: `webpacker_source_url`](https://github.com/mileszs/wicked_pdf/pull/993) 70 | - [update README](https://github.com/mileszs/wicked_pdf/pull/968) 71 | 72 | ## [2.5.1] September 2021 - fix webpacker helper, github actions and Readme updates ae725e8055dc8f51a392c27767b4dcdcfffe155d 73 | - [Add comment about enable_local_file_access to README](https://github.com/mileszs/wicked_pdf/commit/2dc96dde2e0fd7362395064f2480cac1edcc1f48) 74 | - [README updates](https://github.com/mileszs/wicked_pdf/pull/974) && 75 | - [Github actions](https://github.com/mileszs/wicked_pdf/pull/986) 76 | - [Screencast links](https://github.com/mileszs/wicked_pdf/pull/976) 77 | - [fix url generating in webpacker helper](https://github.com/mileszs/wicked_pdf/pull/973) 78 | 79 | ## [2.5.0] November 2020 Release - 2b1d47a84fce3600e7cbe2f50843af1a7b84d4a6 80 | - [Remove code for unsupported rails and ruby versions](https://github.com/mileszs/wicked_pdf/pull/925) 81 | 82 | ## [2.4.1] b56c46a05895def395ebc75ed8e822551c2c478f 83 | - [Extract reading in chunk](https://github.com/mileszs/wicked_pdf/pull/951) 84 | - [add ruby 2.7 to the test matrix](https://github.com/mileszs/wicked_pdf/pull/952) 85 | 86 | ## [2.4.0] 8c007a77057e1a6680469d1ef53aa19a108fe209 87 | ### New Features 88 | - [Do not unlink HTML temp files immediately (to enable HTML tempfile inspection)](https://github.com/mileszs/wicked_pdf/pull/950) 89 | - [Read HTML string and generated PDF file in chunks (to reduce memory overhead of generating large PDFs)](https://github.com/mileszs/wicked_pdf/pull/949) 90 | - [Add `wicked_pdf_url_base64` helper](https://github.com/mileszs/wicked_pdf/pull/947) 91 | 92 | ## [2.3.1] - Allow bundler 2.x ee6a5e1f807c872af37c1382f629dd4cac3040a8 93 | - [Adjust gemspec development dependencies](https://github.com/mileszs/wicked_pdf/pull/814) 94 | 95 | ## [2.3.0] - Remove support for Ruby 1.x and Rails 2.x 66149c67e54cd3a63dd27528f5b78255fdd5ac43 96 | - [Remove support for Ruby 1.x and Rails 2.x](https://github.com/mileszs/wicked_pdf/pull/859) 97 | 98 | ## [2.2.0] - October 2020 release f8abe706f5eb6dba2fcded473c81f2176e9d717e 99 | ### Fixes 100 | - [Make CI green again](https://github.com/mileszs/wicked_pdf/pull/939) 101 | - [rubocop fixes](https://github.com/mileszs/wicked_pdf/pull/945) 102 | ### New Features 103 | - [Add support for --keep-relative-links flag](https://github.com/mileszs/wicked_pdf/pull/930) 104 | - [Encapsulate binary path and version handling](https://github.com/mileszs/wicked_pdf/pull/816) && [#815](https://github.com/mileszs/wicked_pdf/pull/815) 105 | 106 | 107 | ## [2.1.0] - 2020-06-14 108 | ### Fixes 109 | - [Document no_stop_slow_scripts in README](https://github.com/mileszs/wicked_pdf/pull/905) 110 | - [Document how to use locals in README](https://github.com/mileszs/wicked_pdf/pull/915) 111 | 112 | ### New Features 113 | - [Improved support for Webpacker assets with `wicked_pdf_asset_pack_path`](https://github.com/mileszs/wicked_pdf/pull/896) 114 | - [Support enabling/disabling local file access compatible with wkhtmltopdf 0.12.6](https://github.com/mileszs/wicked_pdf/pull/920) 115 | - [Add option `use_xvfb` to emulate an X server](https://github.com/mileszs/wicked_pdf/pull/909) 116 | 117 | ## [2.0.2] - 2020-03-17 118 | ### Fixes 119 | - [Force UTF-8 encoding in assets helper](https://github.com/mileszs/wicked_pdf/pull/894) 120 | 121 | ## [2.0.1] - 2020-02-22 122 | ### Fixes 123 | - [Replace open-uri with more secure Net:HTTP.get](https://github.com/mileszs/wicked_pdf/pull/864) 124 | 125 | ## [2.0.0] - 2020-02-22 126 | ### Breaking changes 127 | - [Remove support for older Ruby and Rails versions](https://github.com/mileszs/wicked_pdf/pull/854) - This project no longer supports Ruby < 2.2 and Rails < 4. It may work for you, but we are no longer worrying about breaking backwards compatibility for versions older than these. If you are on an affected version, you can continue to use the 1.x releases. Patches to fix broken behavior on old versions may not be accepted unless they are highly decoupled from the rest of the code base. 128 | 129 | ### New Features 130 | - [Add Rubygems metadata hash to gemspec](https://github.com/mileszs/wicked_pdf/pull/856) 131 | - [Add support for Rails 6](https://github.com/mileszs/wicked_pdf/pull/869) 132 | 133 | ### Fixes 134 | - [Fix Webpacker helpers in production environment](https://github.com/mileszs/wicked_pdf/pull/837) 135 | - [Fix unit tests](https://github.com/mileszs/wicked_pdf/pull/852) 136 | 137 | ## [1.4.0] - 2019-05-23 138 | ### New Features 139 | - [Add support for `log_level` and `quiet` options](https://github.com/mileszs/wicked_pdf/pull/834) 140 | 141 | ## [1.3.0] - 2019-05-20 142 | ### New Features 143 | - [Add support for Webpacker provided bundles](https://github.com/mileszs/wicked_pdf/pull/739) 144 | 145 | ## [1.2.2] - 2019-04-13 146 | ### Fixes 147 | - [Fix issue loading Pty on Windows](https://github.com/mileszs/wicked_pdf/pull/820) 148 | - [Fix conflict with remotipart causing SystemStackError](https://github.com/mileszs/wicked_pdf/pull/821) 149 | 150 | ## [1.2.1] - 2019-03-16 151 | ### Fixes 152 | - [Fix `SystemStackError` in some setups](https://github.com/mileszs/wicked_pdf/pull/813) 153 | 154 | ## [1.2.0] - 2019-03-16 155 | ### New Features 156 | - [Add `raise_on_all_errors: true` option to raise on any error that prints to STDOUT during PDF generation](https://github.com/mileszs/wicked_pdf/pull/751) 157 | - [Add ability to use the `assigns` option to `render` to assign instance variables to a PDF template](https://github.com/mileszs/wicked_pdf/pull/801) 158 | - [Add ability to track console progress](https://github.com/mileszs/wicked_pdf/pull/804) with `progress: -> (output) { puts output }`. This is useful to add reporting hooks to show your frontend what page number is being generated. 159 | 160 | ### Fixes 161 | - [Fix conflict with other gems that hook into `render`](https://github.com/mileszs/wicked_pdf/pull/574) and avoid using `alias_method_chain` where possible 162 | - [Fix issue using the shell to locate `wkhtmltopdf` in a Bundler environment](https://github.com/mileszs/wicked_pdf/pull/728) 163 | - [Fix `wkhtmltopdf` path detection when HOME environment variable is unset](https://github.com/mileszs/wicked_pdf/pull/568) 164 | - [Fix error when the `Rails` constant is defined but not actually using Rails](https://github.com/mileszs/wicked_pdf/pull/613) 165 | - [Fix compatibility issue with Sprockets 4](https://github.com/mileszs/wicked_pdf/pull/615) 166 | - [Fix compatibility issue with `Mime::JS` in Rails 5.1+](https://github.com/mileszs/wicked_pdf/pull/627) 167 | - [Fix deprecation warning by using `after_action` instead of `after_filter` when available](https://github.com/mileszs/wicked_pdf/pull/663) 168 | - [Provide Rails `base_path` to `find_asset` calls for Sprockets file lookup](https://github.com/mileszs/wicked_pdf/pull/688) 169 | - Logger changes: 170 | - [Use `Rails.logger.debug` instead of `p`](https://github.com/mileszs/wicked_pdf/pull/575) 171 | - [Change logger message to prepend `[wicked_pdf]` instead of nonstandard `****************WICKED****************`](https://github.com/mileszs/wicked_pdf/pull/589) 172 | - Documentation changes: 173 | - [Update link to wkhtmltopdf homepage](https://github.com/mileszs/wicked_pdf/pull/582) 174 | - [Update link to `wkhtmltopdf_binary_gem`](https://github.com/mileszs/wicked_pdf/commit/59e6c5fca3985f2fa2f345089596250df5da2682) 175 | - [Update documentation for usage with the Asset Pipeline](https://github.com/mileszs/wicked_pdf/commit/690d00157706699a71b7dcd71834759f4d84702f) 176 | - [Document `default_protocol` option](https://github.com/mileszs/wicked_pdf/pull/585) 177 | - [Document `image` and `no_image` options](https://github.com/mileszs/wicked_pdf/pull/689) 178 | - [Document issue with DPI/scaling on various platforms](https://github.com/mileszs/wicked_pdf/pull/715) 179 | - [Document creating and attaching a PDF in a mailer](https://github.com/mileszs/wicked_pdf/pull/746) 180 | - [Document dependency on `wkhtmltopdf` with RubyGems](https://github.com/mileszs/wicked_pdf/pull/656) 181 | - [Add example using WickedPDF with Rails in an API-only configuration](https://github.com/mileszs/wicked_pdf/pull/796) 182 | - [Add example for rending a template as a header/footer](https://github.com/mileszs/wicked_pdf/pull/603) 183 | - [Add GitHub issue template](https://github.com/mileszs/wicked_pdf/pull/805) 184 | - [Add CodeClimate Badge](https://github.com/mileszs/wicked_pdf/pull/646) 185 | - RuboCop cleanup 186 | - Updates to Travis CI pipeline to support newer versions of Ruby & Rails 187 | 188 | ## [1.1.0] - 2016-08-30 189 | ### New Features 190 | - Support Rails 5.x and Sprockets 3.x 191 | - Support `window_status: 'somestring'` option, to instruct wkhtmltopdf to wait until the browser `window.status` is equal to the supplied string. This can be useful to force rendering to wait [as explained quite well here](https://spin.atomicobject.com/2015/08/29/ember-app-done-loading/) 192 | - Support `no_stop_slow_scripts: true` to let slow running scripts delay rendering 193 | - [Changes to asset finding to support Rails 5](https://github.com/mileszs/wicked_pdf/pull/561) 194 | 195 | ### Fixes 196 | - [Improved error handling](https://github.com/mileszs/wicked_pdf/pull/543) 197 | - [Namespace helper classes under WickedPdf namespace](https://github.com/mileszs/wicked_pdf/pull/538) 198 | 199 | ## [1.0.6] - 2016-04-04 200 | ### Fixes 201 | - Revert shell escaping of options. The fix was causing more issues than it solved (like "[page] of [topage]" being escaped, and thus not parsed by `wkhtmltopdf`). See #514 for details. 202 | 203 | ## [1.0.5] - 2016-03-28 204 | ### Fixes 205 | - Numerous RuboCop style violation fixes, spelling errors, and test-setup issues from [indyrb.org](http://indyrb.org/) hack night. Thank you all for your contributions! 206 | - Shellescape options. A stray quote in `header` or `footer` would cause PDF to fail to generate, and this should close down many potential attack vectors if you allow user-supplied values to be passed into `wicked_pdf` render options. 207 | 208 | ## [1.0.4] - 2016-01-26 209 | ### Fixes 210 | - Check that logger responds to info before calling it. It was possible to have a `logger` method defined as a controller helper that would override `Rails.logger`. 211 | - [Issue with Sprockets 3.0](https://github.com/mileszs/wicked_pdf/issues/476) where an asset referenced in a stylesheet not existing would raise an exception `read_asset` on nil. 212 | 213 | ## [1.0.3] - 2015-12-02 214 | ### Fixes 215 | - Revert default DPI. Some installs of `wkhtmltopdf` would experience major slowdowns or crashes with it set to 72. It is suggested that a DPI of 75 may be better, but I'm holding off on making it a default without more information. 216 | 217 | ## [1.0.2] - 2015-11-30 218 | ### Fixes 219 | - The default dpi is now 72. Previously the default would be whatever your `wkhtmltopdf` version specified as the default. This change [speeds up generation of documents that contain `border-radius` dramatically](https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1510) 220 | 221 | ## [1.0.1] - 2015-11-19 222 | ### Fixes 223 | - Made minor RuboCop style tweaks. 224 | - Added default [RuboCop](https://github.com/bbatsov/rubocop) config and run after test suite. 225 | - Issue with `nil.basename` from asset helpers. 226 | 227 | ## [1.0.0] - 2015-11-03 228 | ### Breaking Changes 229 | - Accepted that `WickedPDF` cannot guarantee backwards compatibility with older versions of `wkthmltopdf`, and decided to publish a new version with the MAJOR number incremented, signaling that this may have breaking changes for some people, but providing a path forward for progress. This release number also signals that this is a mature (and relatively stable) project, and should be deemed ready for production (since it has been used in production since ~2009, and downloaded over a *million* times on [RubyGems.org](https://rubygems.org/gems/wicked_pdf)). 230 | - Stopped attempting to track with version number of `wkhtmltopdf` binary releases (`wkhtmltopdf` v9.x == `WickedPDF` v9.x) 231 | - Adopted [Semantic Versioning](http://semver.org/) for release numbering 232 | - Added a CHANGELOG (based on [keepachangelog.com](http://keepachangelog.com/)) 233 | - Misc code tweaks as suggested by [RuboCop](https://github.com/bbatsov/rubocop) 234 | 235 | ### New Features 236 | - Check version of `wkhtmltopdf` before deciding to pass arguments with or without dashes 237 | - New arguments and options for the table of contents supported in newer versions of wkhtmltopdf: `text_size_shrink`, `level_indentation`, `disable_dotted_lines`, `disable_toc_links`, `xsl_style_sheet` 238 | - Merge in global options to `pdf_from_html_file` and `pdf_from_string` 239 | - Add ability to generate pdf from a web resource: `pdf_from_url(url)` 240 | - Removed explicit dependency on [Rails](https://github.com/rails/rails), since parts of this library may be used without it. 241 | 242 | ### Fixes 243 | - Comment out the `:exe_path` option in the generated initializer by default (since many systems won't have `wkthmltopdf` installed in that specific location) 244 | - Issues with `file://` paths on Windows-based systems 245 | - Issues with parsed options/argument ordering on versions of `wkthmltopdf` > 0.9 246 | - Issues with middleware headers when running Rails app mounted in a subdirectory 247 | - Issues with options that have a `key: 'value'` syntax when passed to `wkthmltopdf` 248 | - Issue with `:temp_path` option being deleted from original options hash 249 | - Issue with header/footer `:content` being deleted after the first page 250 | - Issues with options being modified during processing (including global config options) 251 | - Issues with asset helpers recognizing assets specified without a protocol 252 | - Issues with `url()` references and embedded `data:base64` assets in stylesheets rendered with `wicked_pdf_stylesheet_link_tag` 253 | - Asset helpers no longer add a file extension if it already is specified with one 254 | 255 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Miles Z. Sterrett 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wicked PDF [![Gem Version](https://badge.fury.io/rb/wicked_pdf.svg)](http://badge.fury.io/rb/wicked_pdf) [![Build Status](https://github.com/mileszs/wicked_pdf/actions/workflows/ci.yml/badge.svg)](https://github.com/mileszs/wicked_pdf/actions/workflows/ci.yml) [![Code Climate](https://codeclimate.com/github/mileszs/wicked_pdf/badges/gpa.svg)](https://codeclimate.com/github/mileszs/wicked_pdf) [![Open Source Helpers](https://www.codetriage.com/mileszs/wicked_pdf/badges/users.svg)](https://www.codetriage.com/mileszs/wicked_pdf) 2 | 3 | ## A PDF generation plugin for Ruby on Rails 4 | 5 | Wicked PDF uses the shell utility [wkhtmltopdf](http://wkhtmltopdf.org) to serve a PDF file to a user from HTML. In other words, rather than dealing with a PDF generation DSL of some sort, you simply write an HTML view as you would normally, then let Wicked PDF take care of the hard stuff. 6 | 7 | _Wicked PDF has been verified to work on Ruby versions 2.2 through 3.2; Rails 4 through 7.0_ 8 | 9 | ### Installation 10 | 11 | Add this to your Gemfile and run `bundle install`: 12 | 13 | ```ruby 14 | gem 'wicked_pdf' 15 | ``` 16 | 17 | Then create the initializer with 18 | 19 | rails generate wicked_pdf 20 | 21 | You may also need to add 22 | ```ruby 23 | Mime::Type.register "application/pdf", :pdf 24 | ``` 25 | to `config/initializers/mime_types.rb` in older versions of Rails. 26 | 27 | Because `wicked_pdf` is a wrapper for [wkhtmltopdf](http://wkhtmltopdf.org/), you'll need to install that, too. 28 | 29 | The simplest way to install all of the binaries on most Linux or OSX systems is through the gem [wkhtmltopdf-binary](https://github.com/zakird/wkhtmltopdf_binary_gem). Builds for other systems are available [here](https://wkhtmltopdf.org/downloads.html) 30 | To install that gem, add this: 31 | 32 | ```ruby 33 | gem 'wkhtmltopdf-binary' 34 | ``` 35 | 36 | To your Gemfile and run `bundle install`. 37 | 38 | This gem currently installs version 0.12.x of `wkhtmltopdf`. Some of the options listed below are specific 0.9 or below, and others are for 0.12 and up. 39 | 40 | You can see what flags are supported for the current version in [wkhtmltopdf's auto-generated manual](https://wkhtmltopdf.org/usage/wkhtmltopdf.txt) 41 | 42 | If your wkhtmltopdf executable is not on your webserver's path, you can configure it in an initializer: 43 | 44 | ```ruby 45 | WickedPdf.configure do |c| 46 | c.exe_path = '/usr/local/bin/wkhtmltopdf' 47 | c.enable_local_file_access = true 48 | end 49 | ``` 50 | 51 | For more information about `wkhtmltopdf`, see the project's [homepage](http://wkhtmltopdf.org/). 52 | 53 | ### Basic Usage 54 | ```ruby 55 | class ThingsController < ApplicationController 56 | def show 57 | respond_to do |format| 58 | format.html 59 | format.pdf do 60 | render pdf: "file_name" # Excluding ".pdf" extension. 61 | end 62 | end 63 | end 64 | end 65 | ``` 66 | ### Usage Conditions - Important! 67 | 68 | The wkhtmltopdf binary is run outside of your Rails application; therefore, your normal layouts will not work. If you plan to use any CSS, JavaScript, or image files, you must modify your layout so that you provide an absolute reference to these files. The best option for Rails without the asset pipeline is to use the `wicked_pdf_stylesheet_link_tag`, `wicked_pdf_image_tag`, and `wicked_pdf_javascript_include_tag` helpers or to go straight to a CDN (Content Delivery Network) for popular libraries such as jQuery. 69 | 70 | #### wicked_pdf helpers 71 | ```html 72 | 73 | 74 | 75 | 76 | <%= wicked_pdf_stylesheet_link_tag "pdf" -%> 77 | <%= wicked_pdf_javascript_include_tag "number_pages" %> 78 | 79 | 80 | 83 |
84 | <%= yield %> 85 |
86 | 87 | 88 | ``` 89 | Using wicked_pdf_helpers with asset pipeline raises `Asset names passed to helpers should not include the "/assets/" prefix.` error. To work around this, you can use `wicked_pdf_asset_base64` with the normal Rails helpers, but be aware that this will base64 encode your content and inline it in the page. This is very quick for small assets, but large ones can take a long time. 90 | 91 | ```html 92 | 93 | 94 | 95 | 96 | <%= stylesheet_link_tag wicked_pdf_asset_base64("pdf") %> 97 | <%= javascript_include_tag wicked_pdf_asset_base64("number_pages") %> 98 | 99 | 100 | 101 | 104 |
105 | <%= yield %> 106 |
107 | 108 | 109 | ``` 110 | 111 | #### Webpacker usage 112 | 113 | wicked_pdf supports webpack assets. 114 | 115 | - Use `wicked_pdf_stylesheet_pack_tag` for stylesheets 116 | - Use `wicked_pdf_javascript_pack_tag` for javascripts 117 | - Use `wicked_pdf_asset_pack_path` to access an asset directly, for example: `image_tag wicked_pdf_asset_pack_path("media/images/foobar.png")` 118 | 119 | #### Asset pipeline usage 120 | 121 | It is best to precompile assets used in PDF views. This will help avoid issues when it comes to deploying, as Rails serves asset files differently between development and production (`config.assets.compile = false`), which can make it look like your PDFs work in development, but fail to load assets in production. 122 | 123 | config.assets.precompile += ['blueprint/screen.css', 'pdf.css', 'jquery.ui.datepicker.js', 'pdf.js', ...etc...] 124 | 125 | #### CDN reference 126 | 127 | In this case, you can use that standard Rails helpers and point to the current CDN for whichever framework you are using. For jQuery, it would look somethng like this, given the current versions at the time of this writing. 128 | ```html 129 | 130 | 131 | 132 | <%= javascript_include_tag "http://code.jquery.com/jquery-1.10.0.min.js" %> 133 | <%= javascript_include_tag "http://code.jquery.com/ui/1.10.3/jquery-ui.min.js" %> 134 | ``` 135 | 136 | ### Advanced Usage with all available options 137 | 138 | _NOTE: Certain options are only supported in specific versions of wkhtmltopdf._ 139 | 140 | ```ruby 141 | class ThingsController < ApplicationController 142 | def show 143 | respond_to do |format| 144 | format.html 145 | format.pdf do 146 | render pdf: 'file_name', 147 | disposition: 'attachment', # default 'inline' 148 | template: 'things/show', 149 | locals: {foo: @bar}, 150 | file: "#{Rails.root}/files/foo.erb", 151 | inline: 'INLINE HTML', 152 | layout: 'pdf', # for a pdf.pdf.erb file 153 | wkhtmltopdf: '/usr/local/bin/wkhtmltopdf', # path to binary 154 | show_as_html: params.key?('debug'), # allow debugging based on url param 155 | orientation: 'Landscape', # default Portrait 156 | page_size: 'A4, Letter, ...', # default A4 157 | page_height: NUMBER, 158 | page_width: NUMBER, 159 | save_to_file: Rails.root.join('pdfs', "#{filename}.pdf"), 160 | save_only: false, # depends on :save_to_file being set first 161 | default_protocol: 'http', 162 | proxy: 'TEXT', 163 | basic_auth: false # when true username & password are automatically sent from session 164 | username: 'TEXT', 165 | password: 'TEXT', 166 | title: 'Alternate Title', # otherwise first page title is used 167 | cover: 'URL, Pathname, or raw HTML string', 168 | dpi: 'dpi', 169 | encoding: 'TEXT', 170 | user_style_sheet: 'URL', 171 | cookie: ['_session_id SESSION_ID'], # could be an array or a single string in a 'name value' format 172 | post: ['query QUERY_PARAM'], # could be an array or a single string in a 'name value' format 173 | redirect_delay: NUMBER, 174 | javascript_delay: NUMBER, 175 | window_status: 'TEXT', # wait to render until some JS sets window.status to the given string 176 | image_quality: NUMBER, 177 | no_pdf_compression: true, 178 | zoom: FLOAT, 179 | page_offset: NUMBER, 180 | book: true, 181 | default_header: true, 182 | disable_javascript: false, 183 | grayscale: true, 184 | lowquality: true, 185 | enable_plugins: true, 186 | disable_internal_links: true, 187 | disable_external_links: true, 188 | keep_relative_links: true, 189 | print_media_type: true, 190 | 191 | # define as true the key 'disable_local_file_access' or 'enable_local_file_access', not both 192 | disable_local_file_access: true, 193 | enable_local_file_access: false, # must be true when using wkhtmltopdf > 0.12.6 194 | allow: ["#{Rails.root}/public"], # could be an array or a single string 195 | 196 | disable_smart_shrinking: true, 197 | use_xserver: true, 198 | background: false, # background needs to be true to enable background colors to render 199 | no_background: true, 200 | no_stop_slow_scripts: false, 201 | viewport_size: 'TEXT', # available only with use_xserver or patched QT 202 | extra: '', # directly inserted into the command to wkhtmltopdf 203 | raise_on_all_errors: nil, # raise error for any stderr output. Such as missing media, image assets 204 | raise_on_missing_assets: nil, # raise when trying to access a missing asset 205 | log_level: 'info', # Available values: none, error, warn, or info - only available with wkhtmltopdf 0.12.5+ 206 | quiet: false, # `false` is same as `log_level: 'info'`, `true` is same as `log_level: 'none'` 207 | outline: { outline: true, 208 | outline_depth: LEVEL }, 209 | margin: { top: SIZE, # default 10 (mm) 210 | bottom: SIZE, 211 | left: SIZE, 212 | right: SIZE }, 213 | header: { html: { template: 'users/header', # use :template OR :url 214 | layout: 'pdf_plain', # optional, use 'pdf_plain' for a pdf_plain.html.pdf.erb file, defaults to main layout 215 | url: 'www.example.com', 216 | locals: { foo: @bar }}, 217 | center: 'TEXT', 218 | font_name: 'NAME', 219 | font_size: SIZE, 220 | left: 'TEXT', 221 | right: 'TEXT', 222 | spacing: REAL, 223 | line: true, 224 | content: 'HTML CONTENT ALREADY RENDERED'}, # optionally you can pass plain html already rendered (useful if using pdf_from_string) 225 | footer: { html: { template:'shared/footer', # use :template OR :url 226 | layout: 'pdf_plain.html', # optional, use 'pdf_plain' for a pdf_plain.html.pdf.erb file, defaults to main layout 227 | url: 'www.example.com', 228 | locals: { foo: @bar }}, 229 | center: 'TEXT', 230 | font_name: 'NAME', 231 | font_size: SIZE, 232 | left: 'TEXT', 233 | right: 'TEXT', 234 | spacing: REAL, 235 | line: true, 236 | content: 'HTML CONTENT ALREADY RENDERED'}, # optionally you can pass plain html already rendered (useful if using pdf_from_string) 237 | toc: { font_name: "NAME", 238 | depth: LEVEL, 239 | header_text: "TEXT", 240 | header_fs: SIZE, 241 | text_size_shrink: 0.8, 242 | l1_font_size: SIZE, 243 | l2_font_size: SIZE, 244 | l3_font_size: SIZE, 245 | l4_font_size: SIZE, 246 | l5_font_size: SIZE, 247 | l6_font_size: SIZE, 248 | l7_font_size: SIZE, 249 | level_indentation: NUM, 250 | l1_indentation: NUM, 251 | l2_indentation: NUM, 252 | l3_indentation: NUM, 253 | l4_indentation: NUM, 254 | l5_indentation: NUM, 255 | l6_indentation: NUM, 256 | l7_indentation: NUM, 257 | no_dots: true, 258 | disable_dotted_lines: true, 259 | disable_links: true, 260 | disable_toc_links: true, 261 | disable_back_links:true, 262 | xsl_style_sheet: 'file.xsl'}, # optional XSLT stylesheet to use for styling table of contents 263 | progress: proc { |output| puts output }, # proc called when console output changes 264 | delete_temporary_files: true # explicitly delete temporary files, default false 265 | end 266 | end 267 | end 268 | end 269 | ``` 270 | By default, it will render without a layout (layout: false) and the template for the current controller and action. 271 | 272 | #### wkhtmltopdf Binary Options 273 | 274 | Some of the options above are being passed to `wkhtmltopdf` binary. They can be used to control the options used in Webkit rendering before generating the PDF. 275 | 276 | Examples of those options are: 277 | 278 | ```ruby 279 | print_media_type: true # Passes `--print-media-type` 280 | no_background: true # Passes `--no-background` 281 | ``` 282 | 283 | You can see the complete list of options under "Global Options" in wkhtmltopdf usage [docs](http://wkhtmltopdf.org/usage/wkhtmltopdf.txt). 284 | 285 | ### Super Advanced Usage ### 286 | 287 | If you need to just create a pdf and not display it: 288 | ```ruby 289 | # create a pdf from a string 290 | pdf = WickedPdf.new.pdf_from_string('

Hello There!

') 291 | 292 | # create a pdf file from a html file without converting it to string 293 | # Path must be absolute path 294 | pdf = WickedPdf.new.pdf_from_html_file('/your/absolute/path/here') 295 | 296 | # create a pdf from a URL 297 | pdf = WickedPdf.new.pdf_from_url('https://github.com/mileszs/wicked_pdf') 298 | 299 | # create a pdf from string using templates, layouts, and content option for header or footer 300 | pdf = WickedPdf.new.pdf_from_string( 301 | render_to_string('templates/pdf', layout: 'pdfs/layout_pdf.html'), 302 | footer: { 303 | content: render_to_string( 304 | 'templates/footer', 305 | layout: 'pdfs/layout_pdf.html' 306 | ) 307 | } 308 | ) 309 | 310 | # It is possible to use footer/header templates without a layout, in that case you need to provide a valid HTML document 311 | pdf = WickedPdf.new.pdf_from_string( 312 | render_to_string('templates/full_pdf_template'), 313 | header: { 314 | content: render_to_string('templates/full_header_template') 315 | } 316 | ) 317 | 318 | # or from your controller, using views & templates and all wicked_pdf options as normal 319 | pdf = render_to_string pdf: "some_file_name", template: "templates/pdf", encoding: "UTF-8" 320 | 321 | # then save to a file 322 | save_path = Rails.root.join('pdfs','filename.pdf') 323 | File.open(save_path, 'wb') do |file| 324 | file << pdf 325 | end 326 | 327 | # you can also track progress on your PDF generation, such as when using it from within a Resque job 328 | class PdfJob 329 | def perform 330 | blk = proc { |output| 331 | match = output.match(/\[.+\] Page (?\d+) of (?\d+)/) 332 | if match 333 | current_page = match[:current_page].to_i 334 | total_pages = match[:total_pages].to_i 335 | message = "Generated #{current_page} of #{total_pages} pages" 336 | at current_page, total_pages, message 337 | end 338 | } 339 | WickedPdf.new.pdf_from_string(html, progress: blk) 340 | end 341 | end 342 | ``` 343 | If you need to display utf encoded characters, add this to your pdf views or layouts: 344 | ```html 345 | 346 | ``` 347 | If you need to return a PDF in a controller with Rails in API mode: 348 | ```ruby 349 | pdf_html = ActionController::Base.new.render_to_string(template: 'controller_name/action_name', layout: 'pdf') 350 | pdf = WickedPdf.new.pdf_from_string(pdf_html) 351 | send_data pdf, filename: 'file.pdf' 352 | ``` 353 | ### Page Breaks 354 | 355 | You can control page breaks with CSS. 356 | 357 | Add a few styles like this to your stylesheet or page: 358 | ```css 359 | div.alwaysbreak { page-break-before: always; } 360 | div.nobreak:before { clear:both; } 361 | div.nobreak { page-break-inside: avoid; } 362 | ``` 363 | 364 | ### Page Numbering 365 | 366 | A bit of javascript can help you number your pages. Create a template or header/footer file with this: 367 | ```html 368 | 369 | 370 | 382 | 383 | 384 | Page of 385 | 386 | 387 | ``` 388 | Anything with a class listed in "var x" above will be auto-filled at render time. 389 | 390 | If you do not have explicit page breaks (and therefore do not have any "page" class), you can also use wkhtmltopdf's built in page number generation by setting one of the headers to "[page]": 391 | ```ruby 392 | render pdf: 'filename', header: { right: '[page] of [topage]' } 393 | ``` 394 | ### Configuration 395 | 396 | You can put your default configuration, applied to all pdf's at "wicked_pdf.rb" initializer. 397 | 398 | ### Rack Middleware 399 | 400 | If you would like to have WickedPdf automatically generate PDF views for all (or nearly all) pages by appending .pdf to the URL, add the following to your Rails app: 401 | ```ruby 402 | # in application.rb (Rails3) or environment.rb (Rails2) 403 | require 'wicked_pdf' 404 | config.middleware.use WickedPdf::Middleware 405 | ``` 406 | If you want to turn on or off the middleware for certain URLs, use the `:only` or `:except` conditions like so: 407 | ```ruby 408 | # conditions can be plain strings or regular expressions, and you can supply only one or an array 409 | config.middleware.use WickedPdf::Middleware, {}, only: '/invoice' 410 | config.middleware.use WickedPdf::Middleware, {}, except: [ %r[^/admin], '/secret', %r[^/people/\d] ] 411 | ``` 412 | If you use the standard `render pdf: 'some_pdf'` in your app, you will want to exclude those actions from the middleware. 413 | 414 | 415 | ### Include in an email as an attachment 416 | 417 | To include a rendered pdf file in an email you can do the following: 418 | 419 | ```ruby 420 | attachments['attachment.pdf'] = WickedPdf.new.pdf_from_string( 421 | render_to_string('link_to_view.pdf.erb', layout: 'pdf') 422 | ) 423 | ``` 424 | 425 | This will render the pdf to a string and include it in the email. This is very slow so make sure you schedule your email delivery in a job. 426 | 427 | ### Further Reading 428 | 429 | Mike Ackerman's post [How To Create PDFs in Rails](https://www.viget.com/articles/how-to-create-pdfs-in-rails) 430 | 431 | Andreas Happe's post [Generating PDFs from Ruby on Rails](http://www.snikt.net/blog/2012/04/26/wicked-pdf/) 432 | 433 | JESii's post [WickedPDF, wkhtmltopdf, and Heroku...a tricky combination](http://www.nubyrubyrailstales.com/2013/06/wickedpdf-wkhtmltopdf-and-herokua.html) 434 | 435 | Berislav Babic's post [Send PDF attachments from Rails with WickedPdf and ActionMailer](http://berislavbabic.com/send-pdf-attachments-from-rails-with-wickedpdf-and-actionmailer/) 436 | 437 | Corsego's 2021 post [Complete guide to generating PDFs with gem wicked_pdf](https://blog.corsego.com/gem-wicked-pdf) 438 | 439 | PDFTron's post [How to Generate PDFs With Ruby on Rails](https://www.pdftron.com/blog/rails/how-to-generate-pdf-with-ruby-on-rails/) 440 | 441 | StackOverflow [questions with the tag "wicked-pdf"](http://stackoverflow.com/questions/tagged/wicked-pdf) 442 | 443 | ### Screencasts 444 | 445 | * SupeRails Screencast [EN] 446 | 447 | [![Ruby on Rails #17 generate, save, send PDFs with gem wicked_pdf](https://i3.ytimg.com/vi/tFvtwEmW-GE/hqdefault.jpg)](https://youtu.be/tFvtwEmW-GE) 448 | 449 | * codigofacilito Screencast [ES] 450 | 451 | [![Generar PDF con Ruby on Rails - Tutorial](https://i3.ytimg.com/vi/jeWM_gusmJc/hqdefault.jpg)](https://youtu.be/jeWM_gusmJc) 452 | 453 | ### Debugging 454 | 455 | Now you can use a debug param on the URL that shows you the content of the pdf in plain html to design it faster. 456 | 457 | First of all you must configure the render parameter `show_as_html: params.key?('debug')` and then just use it like you normally would but add "debug" as a GET param in the URL: 458 | 459 | http://localhost:3001/CONTROLLER/X.pdf?debug 460 | 461 | However, the wicked_pdf_* helpers will use file:/// paths for assets when using :show_as_html, and your browser's cross-domain safety feature will kick in, and not render them. To get around this, you can load your assets like so in your templates: 462 | ```html 463 | <%= params.key?('debug') ? image_tag('foo') : wicked_pdf_image_tag('foo') %> 464 | ``` 465 | 466 | #### Gotchas 467 | 468 | If one image from your HTML cannot be found (relative or wrong path for example), others images with right paths **may not** be displayed in the output PDF as well (it seems to be an issue with wkhtmltopdf). 469 | 470 | wkhtmltopdf may render at different resolutions on different platforms. For example, Linux prints at 75 dpi (native for WebKit) while on Windows it's at the desktop's DPI (which is normally 96 dpi). [Use `:zoom => 0.78125`](https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2184) (75/96) to match Linux rendering to Windows. 471 | 472 | ### Security considerations 473 | 474 | WickedPdf renders page content on the server by saving HTML and assets to temporary files on disk, then executing `wkhtmltopdf` to convert that HTML to a PDF file. 475 | 476 | It is highly recommended if you allow user-generated HTML/CSS/JS to be converted to PDF, you sanitize it first, or at least disallow requesting content from internal IP addresses and hostnames. 477 | 478 | For example, these could potentially leak internal AWS metadata: 479 | ```html 480 | 481 | 482 | ``` 483 | 484 | Thank you to Adam Gold from [Snyk](https://snyk.io) for reporting this. 485 | We are considering adding host allow & block lists and/or potentially HTML element sanitizing. 486 | Please open an issue or PR to help us out with this. 487 | 488 | ### Inspiration 489 | 490 | You may have noticed: this plugin is heavily inspired by the PrinceXML plugin [princely](http://github.com/mbleigh/princely/tree/master). PrinceXML's cost was prohibitive for me. So, with a little help from some friends (thanks [jqr](http://github.com/jqr)), I tracked down wkhtmltopdf, and here we are. 491 | 492 | ### Contributing 493 | 494 | 1. Fork it 495 | 2. Create your feature branch (`git checkout -b my-new-feature`) 496 | 3. Run the test suite and check the output (`rake`) 497 | 4. Add tests for your feature or fix (please) 498 | 5. Commit your changes (`git commit -am 'Add some feature'`) 499 | 6. Push to the branch (`git push origin my-new-feature`) 500 | 7. Create new Pull Request 501 | 502 | ### Awesome Peoples 503 | 504 | Also, thanks to [unixmonkey](https://github.com/Unixmonkey), [galdomedia](http://github.com/galdomedia), [jcrisp](http://github.com/jcrisp), [lleirborras](http://github.com/lleirborras), [tiennou](http://github.com/tiennou), and everyone else for all their hard work and patience with my delays in merging in their enhancements. 505 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | require 'rdoc/task' 4 | require 'rails/version' 5 | require 'bundler/gem_tasks' 6 | 7 | desc 'Default: run unit tests.' 8 | task :default => %i[setup_and_run_tests rubocop] 9 | 10 | desc 'Test the wicked_pdf plugin.' 11 | Rake::TestTask.new(:test) do |t| 12 | t.libs << 'lib' 13 | t.libs << 'test' 14 | t.pattern = 'test/**/*_test.rb' 15 | t.verbose = true 16 | end 17 | 18 | desc 'Run RuboCop' 19 | task :rubocop do 20 | require 'rubocop/rake_task' 21 | RuboCop::RakeTask.new 22 | end 23 | 24 | desc 'Setup and run all tests' 25 | task :setup_and_run_tests do 26 | Rake::Task[:dummy_generate].invoke unless File.exist?('test/dummy/config/environment.rb') 27 | Rake::Task[:test].invoke 28 | end 29 | 30 | desc 'Generate dummy application for test cases' 31 | task :dummy_generate do 32 | Rake::Task[:dummy_remove].invoke 33 | puts 'Creating dummy application to run tests' 34 | system('rails new test/dummy --database=sqlite3') 35 | system('touch test/dummy/db/schema.rb') 36 | FileUtils.cp 'test/fixtures/database.yml', 'test/dummy/config/' 37 | FileUtils.rm_r Dir.glob('test/dummy/test/*') 38 | 39 | # rails 6 needs this to be present before start: 40 | FileUtils.mkdir_p('test/dummy/app/assets/config') 41 | FileUtils.mkdir_p('test/dummy/app/assets/javascripts') 42 | FileUtils.cp 'test/fixtures/manifest.js', 'test/dummy/app/assets/config/' 43 | FileUtils.cp 'test/fixtures/wicked.js', 'test/dummy/app/assets/javascripts/' 44 | end 45 | 46 | desc 'Remove dummy application' 47 | task :dummy_remove do 48 | FileUtils.rm_r Dir.glob('test/dummy/') 49 | end 50 | 51 | desc 'Generate documentation for the wicked_pdf gem.' 52 | RDoc::Task.new(:rdoc) do |rdoc| 53 | rdoc.rdoc_dir = 'rdoc' 54 | rdoc.title = 'WickedPdf' 55 | rdoc.options << '--line-numbers' << '--inline-source' 56 | rdoc.rdoc_files.include('README.md') 57 | rdoc.rdoc_files.include('lib/**/*.rb') 58 | end 59 | -------------------------------------------------------------------------------- /gemfiles/5.0.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '~> 5.0.0' 4 | gem 'rdoc' 5 | gem 'sprockets', '~>3.0' # v4 strips newlines from assets causing tests to fail 6 | gem 'sqlite3', '~> 1.3.6' 7 | 8 | gemspec path: '../' 9 | -------------------------------------------------------------------------------- /gemfiles/5.1.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '~> 5.1.0' 4 | gem 'rdoc' 5 | gem 'sprockets', '~>3.0' # v4 strips newlines from assets causing tests to fail 6 | gem 'sqlite3', '~> 1.3.6' 7 | 8 | gemspec path: '../' 9 | -------------------------------------------------------------------------------- /gemfiles/5.2.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'bootsnap' # required to run `rake test` in Rails 5.2 4 | gem 'rails', '~> 5.2' 5 | gem 'rdoc' 6 | gem 'sprockets', '~>3.0' # v4 strips newlines from assets causing tests to fail 7 | gem 'sqlite3', '~> 1.3.6' 8 | 9 | gemspec path: '../' 10 | -------------------------------------------------------------------------------- /gemfiles/6.0.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'bootsnap' # required to run `rake test` in Rails 6.0 4 | gem 'bundler', '~>2' 5 | gem 'rails', '~>6.0.1' 6 | gem 'rdoc' 7 | gem 'sprockets', '~>3.0' 8 | gem 'sqlite3', '~> 1.4' 9 | 10 | gemspec path: '../' 11 | -------------------------------------------------------------------------------- /gemfiles/6.1.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'bootsnap' # required to run `rake test` in Rails 6.1 4 | gem 'bundler', '~>2' 5 | gem 'rails', '~>6.1.0' 6 | gem 'webpacker' 7 | gem 'rdoc' 8 | gem 'sprockets', '~>3.0' 9 | gem 'sqlite3', '~> 1.4' 10 | 11 | gemspec :path => '../' 12 | -------------------------------------------------------------------------------- /gemfiles/7.0.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'bootsnap' # required to run `rake test` in Rails 7.0 4 | gem 'bundler', '~>2' 5 | gem 'rails', '~>7.0.0' 6 | gem 'sprockets-rails' 7 | gem 'rdoc' 8 | gem 'sprockets', '~>3.0' 9 | gem 'sqlite3', '~> 1.4' 10 | 11 | gemspec :path => '../' 12 | -------------------------------------------------------------------------------- /generators/wicked_pdf/templates/wicked_pdf.rb: -------------------------------------------------------------------------------- 1 | # WickedPDF Global Configuration 2 | # 3 | # Use this to set up shared configuration options for your entire application. 4 | # Any of the configuration options shown here can also be applied to single 5 | # models by passing arguments to the `render :pdf` call. 6 | # 7 | # To learn more, check out the README: 8 | # 9 | # https://github.com/mileszs/wicked_pdf/blob/master/README.md 10 | 11 | WickedPdf.configure do |config| 12 | # Path to the wkhtmltopdf executable: This usually isn't needed if using 13 | # one of the wkhtmltopdf-binary family of gems. 14 | # config.exe_path = '/usr/local/bin/wkhtmltopdf' 15 | # or 16 | # config.exe_path = Gem.bin_path('wkhtmltopdf-binary', 'wkhtmltopdf') 17 | 18 | # Needed for wkhtmltopdf 0.12.6+ to use many wicked_pdf asset helpers 19 | # config.enable_local_file_access = true 20 | 21 | # Layout file to be used for all PDFs 22 | # (but can be overridden in `render :pdf` calls) 23 | # config.layout = 'pdf.html' 24 | 25 | # Using wkhtmltopdf without an X server can be achieved by enabling the 26 | # 'use_xvfb' flag. This will wrap all wkhtmltopdf commands around the 27 | # 'xvfb-run' command, in order to simulate an X server. 28 | # 29 | # config.use_xvfb = true 30 | end 31 | -------------------------------------------------------------------------------- /generators/wicked_pdf/wicked_pdf_generator.rb: -------------------------------------------------------------------------------- 1 | class WickedPdfGenerator < Rails::Generator::Base 2 | def manifest 3 | record do |m| 4 | m.file 'wicked_pdf.rb', 'config/initializers/wicked_pdf.rb' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'wicked_pdf' 2 | require 'wicked_pdf_tempfile' 3 | -------------------------------------------------------------------------------- /lib/generators/wicked_pdf_generator.rb: -------------------------------------------------------------------------------- 1 | # Rails generator invoked with 'rails generate wicked_pdf' 2 | class WickedPdfGenerator < Rails::Generators::Base 3 | source_root(File.expand_path(File.dirname(__FILE__) + '/../../generators/wicked_pdf/templates')) 4 | def copy_initializer 5 | copy_file 'wicked_pdf.rb', 'config/initializers/wicked_pdf.rb' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/wicked_pdf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # wkhtml2pdf Ruby interface 4 | # http://wkhtmltopdf.org/ 5 | 6 | require 'logger' 7 | require 'digest/md5' 8 | require 'rbconfig' 9 | require 'open3' 10 | require 'ostruct' 11 | 12 | require 'active_support/core_ext/module/attribute_accessors' 13 | require 'active_support/core_ext/object/blank' 14 | 15 | require 'wicked_pdf/version' 16 | require 'wicked_pdf/railtie' 17 | require 'wicked_pdf/option_parser' 18 | require 'wicked_pdf/tempfile' 19 | require 'wicked_pdf/binary' 20 | require 'wicked_pdf/middleware' 21 | require 'wicked_pdf/progress' 22 | 23 | class WickedPdf 24 | DEFAULT_BINARY_VERSION = Gem::Version.new('0.9.9') 25 | @@config = {} 26 | cattr_accessor :config, :silence_deprecations 27 | 28 | include Progress 29 | 30 | def self.config=(config) 31 | ::Kernel.warn 'WickedPdf.config= is deprecated and will be removed in future versions. Use WickedPdf.configure instead.' unless @@silence_deprecations 32 | 33 | @@config = config 34 | end 35 | 36 | def self.configure 37 | config = OpenStruct.new(@@config) 38 | yield config 39 | 40 | @@config.merge! config.to_h 41 | end 42 | 43 | def self.clear_config 44 | @@config = {} 45 | end 46 | 47 | def initialize(wkhtmltopdf_binary_path = nil) 48 | @binary = Binary.new(wkhtmltopdf_binary_path, DEFAULT_BINARY_VERSION) 49 | end 50 | 51 | def binary_version 52 | @binary.version 53 | end 54 | 55 | def pdf_from_html_file(filepath, options = {}) 56 | pdf_from_url("file:///#{filepath}", options) 57 | end 58 | 59 | def pdf_from_string(string, options = {}) 60 | options = options.dup 61 | options.merge!(WickedPdf.config) { |_key, option, _config| option } 62 | string_file = WickedPdf::Tempfile.new('wicked_pdf.html', options[:temp_path]) 63 | string_file.write_in_chunks(string) 64 | pdf_from_html_file(string_file.path, options) 65 | ensure 66 | if options[:delete_temporary_files] && string_file 67 | string_file.close! 68 | elsif string_file 69 | string_file.close 70 | end 71 | end 72 | 73 | def pdf_from_url(url, options = {}) # rubocop:disable Metrics/CyclomaticComplexity 74 | # merge in global config options 75 | options.merge!(WickedPdf.config) { |_key, option, _config| option } 76 | generated_pdf_file = WickedPdf::Tempfile.new('wicked_pdf_generated_file.pdf', options[:temp_path]) 77 | command = [@binary.path] 78 | command.unshift(@binary.xvfb_run_path) if options[:use_xvfb] 79 | command += option_parser.parse(options) 80 | command << url 81 | command << generated_pdf_file.path.to_s 82 | 83 | print_command(command.inspect) if in_development_mode? 84 | 85 | if track_progress?(options) 86 | invoke_with_progress(command, options) 87 | else 88 | _out, err, status = Open3.capture3(*command) 89 | err = [status.to_s, err].join("\n") if !err.empty? || !status.success? 90 | end 91 | if options[:return_file] 92 | return_file = options.delete(:return_file) 93 | return generated_pdf_file 94 | end 95 | 96 | pdf = generated_pdf_file.read_in_chunks 97 | 98 | raise "Error generating PDF\n Command Error: #{err}" if options[:raise_on_all_errors] && !err.empty? 99 | raise "PDF could not be generated!\n Command Error: #{err}" if pdf && pdf.rstrip.empty? 100 | 101 | pdf 102 | rescue StandardError => e 103 | raise "Failed to execute:\n#{command}\nError: #{e}" 104 | ensure 105 | clean_temp_files 106 | generated_pdf_file.close! if generated_pdf_file && !return_file 107 | end 108 | 109 | private 110 | 111 | def in_development_mode? 112 | return Rails.env == 'development' if defined?(Rails.env) 113 | 114 | RAILS_ENV == 'development' if defined?(RAILS_ENV) 115 | end 116 | 117 | def on_windows? 118 | RbConfig::CONFIG['target_os'] =~ /mswin|mingw/ 119 | end 120 | 121 | def print_command(cmd) 122 | Rails.logger.debug '[wicked_pdf]: ' + cmd 123 | end 124 | 125 | def option_parser 126 | @option_parser ||= OptionParser.new(binary_version) 127 | end 128 | 129 | def clean_temp_files 130 | return unless option_parser.hf_tempfiles.present? 131 | 132 | option_parser.hf_tempfiles.each { |file| File.delete(file) } 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/wicked_pdf/binary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WickedPdf 4 | class Binary 5 | EXE_NAME = 'wkhtmltopdf' 6 | 7 | attr_reader :path, :default_version 8 | 9 | def initialize(binary_path, default_version = WickedPdf::DEFAULT_BINARY_VERSION) 10 | @path = binary_path || find_binary_path 11 | @default_version = default_version 12 | 13 | raise "Location of #{EXE_NAME} unknown" if @path.empty? 14 | raise "Bad #{EXE_NAME}'s path: #{@path}" unless File.exist?(@path) 15 | raise "#{EXE_NAME} is not executable" unless File.executable?(@path) 16 | end 17 | 18 | def version 19 | @version ||= retrieve_binary_version 20 | end 21 | 22 | def parse_version_string(version_info) 23 | match_data = /wkhtmltopdf\s*(\d*\.\d*\.\d*\w*)/.match(version_info) 24 | if match_data && (match_data.length == 2) 25 | Gem::Version.new(match_data[1]) 26 | else 27 | default_version 28 | end 29 | end 30 | 31 | def xvfb_run_path 32 | path = possible_binary_locations.map { |l| File.expand_path("#{l}/xvfb-run") }.find { |location| File.exist?(location) } 33 | raise StandardError, 'Could not find binary xvfb-run on the system.' unless path 34 | 35 | path 36 | end 37 | 38 | private 39 | 40 | def retrieve_binary_version 41 | stdin, stdout, stderr = Open3.popen3(@path + ' -V') 42 | stdin.close 43 | stderr.close 44 | parse_version_string(stdout.gets(nil)) 45 | rescue StandardError 46 | default_version 47 | end 48 | 49 | def find_binary_path 50 | exe_path ||= WickedPdf.config[:exe_path] unless WickedPdf.config.empty? 51 | exe_path ||= possible_which_path 52 | exe_path ||= possible_binary_locations.map { |l| File.expand_path("#{l}/#{EXE_NAME}") }.find { |location| File.exist?(location) } 53 | exe_path || '' 54 | end 55 | 56 | def possible_which_path 57 | detected_path = (defined?(Bundler) ? Bundler.which('wkhtmltopdf') : `which wkhtmltopdf`).chomp 58 | detected_path.present? && detected_path 59 | rescue StandardError 60 | nil 61 | end 62 | 63 | def possible_binary_locations 64 | possible_locations = (ENV['PATH'].split(':') + %w[/usr/bin /usr/local/bin]).uniq 65 | possible_locations += %w[~/bin] if ENV.key?('HOME') 66 | possible_locations 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/wicked_pdf/middleware.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WickedPdf 4 | class Middleware 5 | def initialize(app, options = {}, conditions = {}) 6 | @app = app 7 | @options = (WickedPdf.config || {}).merge(options) 8 | @conditions = conditions 9 | end 10 | 11 | def call(env) 12 | @request = Rack::Request.new(env) 13 | @render_pdf = false 14 | 15 | set_request_to_render_as_pdf(env) if render_as_pdf? 16 | status, headers, response = @app.call(env) 17 | 18 | if rendering_pdf? && headers['Content-Type'] =~ /text\/html|application\/xhtml\+xml/ 19 | body = response.respond_to?(:body) ? response.body : response.join 20 | body = body.join if body.is_a?(Array) 21 | 22 | body = WickedPdf.new(@options[:wkhtmltopdf]).pdf_from_string(translate_paths(body, env), @options) 23 | 24 | response = [body] 25 | 26 | # Do not cache PDFs 27 | headers.delete('ETag') 28 | headers.delete('Cache-Control') 29 | 30 | headers['Content-Length'] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s 31 | headers['Content-Type'] = 'application/pdf' 32 | if @options.fetch(:disposition, '') == 'attachment' 33 | headers['Content-Disposition'] = 'attachment' 34 | headers['Content-Transfer-Encoding'] = 'binary' 35 | end 36 | end 37 | 38 | [status, headers, response] 39 | end 40 | 41 | private 42 | 43 | # Change relative paths to absolute 44 | def translate_paths(body, env) 45 | # Host with protocol 46 | root = WickedPdf.config[:root_url] || "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}/" 47 | 48 | body.gsub(/(href|src)=(['"])\/([^\"']*|[^"']*)['"]/, '\1=\2' + root + '\3\2') 49 | end 50 | 51 | def rendering_pdf? 52 | @render_pdf 53 | end 54 | 55 | def render_as_pdf? 56 | request_path_is_pdf = @request.path.match(%r{\.pdf$}) 57 | 58 | if request_path_is_pdf && @conditions[:only] 59 | rules = [@conditions[:only]].flatten 60 | rules.any? do |pattern| 61 | if pattern.is_a?(Regexp) 62 | @request.fullpath =~ pattern 63 | else 64 | @request.path[0, pattern.length] == pattern 65 | end 66 | end 67 | elsif request_path_is_pdf && @conditions[:except] 68 | rules = [@conditions[:except]].flatten 69 | rules.map do |pattern| 70 | if pattern.is_a?(Regexp) 71 | return false if @request.fullpath =~ pattern 72 | elsif @request.path[0, pattern.length] == pattern 73 | return false 74 | end 75 | end 76 | 77 | return true 78 | else 79 | request_path_is_pdf 80 | end 81 | end 82 | 83 | def set_request_to_render_as_pdf(env) 84 | @render_pdf = true 85 | %w[PATH_INFO REQUEST_URI].each { |e| env[e] = env[e].sub(%r{\.pdf\b}, '') } 86 | env['HTTP_ACCEPT'] = concat(env['HTTP_ACCEPT'], Rack::Mime.mime_type('.html')) 87 | env['Rack-Middleware-WickedPdf'] = 'true' 88 | end 89 | 90 | def concat(accepts, type) 91 | (accepts || '').split(',').unshift(type).compact.join(',') 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/wicked_pdf/option_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WickedPdf 4 | class OptionParser 5 | BINARY_VERSION_WITHOUT_DASHES = Gem::Version.new('0.12.0') 6 | 7 | attr_reader :binary_version, :hf_tempfiles 8 | 9 | def initialize(binary_version = WickedPdf::DEFAULT_BINARY_VERSION) 10 | @binary_version = binary_version 11 | end 12 | 13 | def parse(options) 14 | [ 15 | parse_extra(options), 16 | parse_others(options), 17 | parse_global(options), 18 | parse_outline(options.delete(:outline)), 19 | parse_header_footer(:header => options.delete(:header), 20 | :footer => options.delete(:footer), 21 | :layout => options[:layout]), 22 | parse_cover(options.delete(:cover)), 23 | parse_toc(options.delete(:toc)), 24 | parse_basic_auth(options) 25 | ].flatten 26 | end 27 | 28 | def valid_option(name) 29 | if binary_version < BINARY_VERSION_WITHOUT_DASHES 30 | "--#{name}" 31 | else 32 | name 33 | end 34 | end 35 | 36 | private 37 | 38 | def parse_extra(options) 39 | return [] if options[:extra].nil? 40 | return options[:extra].split if options[:extra].respond_to?(:split) 41 | 42 | options[:extra] 43 | end 44 | 45 | def parse_basic_auth(options) 46 | if options[:basic_auth] 47 | user, passwd = Base64.decode64(options[:basic_auth]).split(':') 48 | ['--username', user, '--password', passwd] 49 | else 50 | [] 51 | end 52 | end 53 | 54 | def parse_header_footer(options) 55 | r = [] 56 | unless options.blank? 57 | %i[header footer].collect do |hf| 58 | next if options[hf].blank? 59 | 60 | opt_hf = options[hf] 61 | r += make_options(opt_hf, %i[center font_name left right], hf.to_s) 62 | r += make_options(opt_hf, %i[font_size spacing], hf.to_s, :numeric) 63 | r += make_options(opt_hf, [:line], hf.to_s, :boolean) 64 | if options[hf] && options[hf][:content] 65 | @hf_tempfiles = [] unless defined?(@hf_tempfiles) 66 | @hf_tempfiles.push(tf = File.new(Dir::Tmpname.create(["wicked_#{hf}_pdf", '.html']) {}, 'w')) 67 | tf.write options[hf][:content] 68 | tf.flush 69 | options[hf][:html] = {} 70 | options[hf][:html][:url] = "file:///#{tf.path}" 71 | end 72 | unless opt_hf[:html].blank? 73 | r += make_option("#{hf}-html", opt_hf[:html][:url]) unless opt_hf[:html][:url].blank? 74 | end 75 | end 76 | end 77 | r 78 | end 79 | 80 | def parse_cover(argument) 81 | arg = argument.to_s 82 | return [] if arg.blank? 83 | 84 | # Filesystem path or URL - hand off to wkhtmltopdf 85 | if argument.is_a?(Pathname) || (arg[0, 4] == 'http') 86 | [valid_option('cover'), arg] 87 | else # HTML content 88 | @hf_tempfiles ||= [] 89 | @hf_tempfiles << tf = WickedPdf::Tempfile.new('wicked_cover_pdf.html') 90 | tf.write arg 91 | tf.flush 92 | [valid_option('cover'), tf.path] 93 | end 94 | end 95 | 96 | def parse_toc(options) 97 | return [] if options.nil? 98 | 99 | r = [valid_option('toc')] 100 | unless options.blank? 101 | r += make_options(options, %i[font_name header_text], 'toc') 102 | r += make_options(options, [:xsl_style_sheet]) 103 | r += make_options(options, %i[depth 104 | header_fs 105 | text_size_shrink 106 | l1_font_size 107 | l2_font_size 108 | l3_font_size 109 | l4_font_size 110 | l5_font_size 111 | l6_font_size 112 | l7_font_size 113 | level_indentation 114 | l1_indentation 115 | l2_indentation 116 | l3_indentation 117 | l4_indentation 118 | l5_indentation 119 | l6_indentation 120 | l7_indentation], 'toc', :numeric) 121 | r += make_options(options, %i[no_dots 122 | disable_links 123 | disable_back_links], 'toc', :boolean) 124 | r += make_options(options, %i[disable_dotted_lines 125 | disable_toc_links], nil, :boolean) 126 | end 127 | r 128 | end 129 | 130 | def parse_outline(options) 131 | r = [] 132 | unless options.blank? 133 | r = make_options(options, [:outline], '', :boolean) 134 | r += make_options(options, [:outline_depth], '', :numeric) 135 | end 136 | r 137 | end 138 | 139 | def parse_margins(options) 140 | make_options(options, %i[top bottom left right], 'margin', :numeric) 141 | end 142 | 143 | def parse_global(options) 144 | r = [] 145 | unless options.blank? 146 | r += make_options(options, %i[orientation 147 | dpi 148 | page_size 149 | page_width 150 | title 151 | log_level]) 152 | r += make_options(options, %i[lowquality 153 | grayscale 154 | no_pdf_compression 155 | quiet], '', :boolean) 156 | r += make_options(options, %i[image_dpi 157 | image_quality 158 | page_height], '', :numeric) 159 | r += parse_margins(options.delete(:margin)) 160 | end 161 | r 162 | end 163 | 164 | def parse_others(options) 165 | r = [] 166 | unless options.blank? 167 | r += make_options(options, %i[proxy 168 | username 169 | password 170 | encoding 171 | user_style_sheet 172 | viewport_size 173 | window_status 174 | allow]) 175 | r += make_options(options, %i[cookie 176 | post], '', :name_value) 177 | r += make_options(options, %i[redirect_delay 178 | zoom 179 | page_offset 180 | javascript_delay], '', :numeric) 181 | r += make_options(options, %i[book 182 | default_header 183 | disable_javascript 184 | enable_plugins 185 | disable_internal_links 186 | disable_external_links 187 | keep_relative_links 188 | print_media_type 189 | disable_local_file_access 190 | enable_local_file_access 191 | disable_smart_shrinking 192 | use_xserver 193 | no_background 194 | images 195 | no_images 196 | no_stop_slow_scripts], '', :boolean) 197 | end 198 | r 199 | end 200 | 201 | def make_options(options, names, prefix = '', type = :string) 202 | return [] if options.nil? 203 | 204 | names.collect do |o| 205 | if options[o].blank? 206 | [] 207 | else 208 | make_option("#{prefix.blank? ? '' : prefix + '-'}#{o}", 209 | options[o], 210 | type) 211 | end 212 | end 213 | end 214 | 215 | def make_option(name, value, type = :string) 216 | return value.collect { |v| make_option(name, v, type) } if value.is_a?(Array) 217 | 218 | if type == :name_value 219 | parts = value.to_s.split(' ') 220 | ["--#{name.tr('_', '-')}", *parts] 221 | elsif type == :boolean 222 | if value 223 | ["--#{name.tr('_', '-')}"] 224 | else 225 | [] 226 | end 227 | else 228 | ["--#{name.tr('_', '-')}", value.to_s] 229 | end 230 | end 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /lib/wicked_pdf/pdf_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WickedPdf 4 | module PdfHelper 5 | def self.prepended(base) 6 | # Protect from trying to augment modules that appear 7 | # as the result of adding other gems. 8 | return if base != ActionController::Base 9 | 10 | base.class_eval do 11 | after_action :clean_temp_files 12 | end 13 | end 14 | 15 | def render(*args) 16 | options = args.first 17 | if options.is_a?(Hash) && options.key?(:pdf) 18 | render_with_wicked_pdf(options) 19 | else 20 | super 21 | end 22 | end 23 | 24 | def render_to_string(*args) 25 | options = args.first 26 | if options.is_a?(Hash) && options.key?(:pdf) 27 | render_to_string_with_wicked_pdf(options) 28 | else 29 | super 30 | end 31 | end 32 | 33 | def render_with_wicked_pdf(options) 34 | raise ArgumentError, 'missing keyword: pdf' unless options.is_a?(Hash) && options.key?(:pdf) 35 | 36 | options[:basic_auth] = set_basic_auth(options) 37 | make_and_send_pdf(options.delete(:pdf), (WickedPdf.config || {}).merge(options)) 38 | end 39 | 40 | def render_to_string_with_wicked_pdf(options) 41 | raise ArgumentError, 'missing keyword: pdf' unless options.is_a?(Hash) && options.key?(:pdf) 42 | 43 | options[:basic_auth] = set_basic_auth(options) 44 | options.delete :pdf 45 | make_pdf((WickedPdf.config || {}).merge(options)) 46 | end 47 | 48 | private 49 | 50 | def set_basic_auth(options = {}) 51 | options[:basic_auth] ||= WickedPdf.config.fetch(:basic_auth) { false } 52 | return unless options[:basic_auth] && request.env['HTTP_AUTHORIZATION'] 53 | 54 | request.env['HTTP_AUTHORIZATION'].split(' ').last 55 | end 56 | 57 | def clean_temp_files 58 | return unless defined?(@hf_tempfiles) 59 | 60 | @hf_tempfiles.each(&:close) 61 | end 62 | 63 | def make_pdf(options = {}) 64 | render_opts = { 65 | :template => options[:template], 66 | :layout => options[:layout], 67 | :formats => options[:formats], 68 | :handlers => options[:handlers], 69 | :assigns => options[:assigns] 70 | } 71 | render_opts[:inline] = options[:inline] if options[:inline] 72 | render_opts[:locals] = options[:locals] if options[:locals] 73 | render_opts[:file] = options[:file] if options[:file] 74 | html_string = render_to_string(render_opts) 75 | options = prerender_header_and_footer(options) 76 | w = WickedPdf.new(options[:wkhtmltopdf]) 77 | w.pdf_from_string(html_string, options) 78 | end 79 | 80 | def make_and_send_pdf(pdf_name, options = {}) 81 | options[:wkhtmltopdf] ||= nil 82 | options[:layout] ||= false 83 | options[:template] ||= File.join(controller_path, action_name) 84 | options[:disposition] ||= 'inline' 85 | if options[:show_as_html] 86 | render_opts = { 87 | :template => options[:template], 88 | :layout => options[:layout], 89 | :formats => options[:formats], 90 | :handlers => options[:handlers], 91 | :assigns => options[:assigns], 92 | :content_type => 'text/html' 93 | } 94 | render_opts[:inline] = options[:inline] if options[:inline] 95 | render_opts[:locals] = options[:locals] if options[:locals] 96 | render_opts[:file] = options[:file] if options[:file] 97 | render(render_opts) 98 | else 99 | pdf_content = make_pdf(options) 100 | File.open(options[:save_to_file], 'wb') { |file| file << pdf_content } if options[:save_to_file] 101 | send_data(pdf_content, :filename => pdf_name + '.pdf', :type => 'application/pdf', :disposition => options[:disposition]) unless options[:save_only] 102 | end 103 | end 104 | 105 | # Given an options hash, prerenders content for the header and footer sections 106 | # to temp files and return a new options hash including the URLs to these files. 107 | def prerender_header_and_footer(options) 108 | %i[header footer].each do |hf| 109 | next unless options[hf] && options[hf][:html] && options[hf][:html][:template] 110 | 111 | @hf_tempfiles = [] unless defined?(@hf_tempfiles) 112 | @hf_tempfiles.push(tf = WickedPdf::Tempfile.new("wicked_#{hf}_pdf.html")) 113 | options[hf][:html][:layout] ||= options[:layout] 114 | render_opts = { 115 | :template => options[hf][:html][:template], 116 | :layout => options[hf][:html][:layout], 117 | :formats => options[hf][:html][:formats], 118 | :handlers => options[hf][:html][:handlers], 119 | :assigns => options[hf][:html][:assigns] 120 | } 121 | render_opts[:locals] = options[hf][:html][:locals] if options[hf][:html][:locals] 122 | render_opts[:file] = options[hf][:html][:file] if options[:file] 123 | tf.write render_to_string(render_opts) 124 | tf.flush 125 | options[hf][:html][:url] = "file:///#{tf.path}" 126 | end 127 | options 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/wicked_pdf/progress.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WickedPdf 4 | module Progress 5 | require 'pty' if RbConfig::CONFIG['target_os'] !~ /mswin|mingw/ && RUBY_ENGINE != 'truffleruby' # no support for windows and truffleruby 6 | require 'English' 7 | 8 | def track_progress?(options) 9 | options[:progress] && !(on_windows? || RUBY_ENGINE == 'truffleruby') 10 | end 11 | 12 | def invoke_with_progress(command, options) 13 | output = [] 14 | begin 15 | PTY.spawn(command.join(' ')) do |stdout, _stdin, pid| 16 | begin 17 | stdout.sync 18 | stdout.each_line("\r") do |line| 19 | output << line.chomp 20 | options[:progress].call(line) if options[:progress] 21 | end 22 | rescue Errno::EIO # rubocop:disable Lint/HandleExceptions 23 | # child process is terminated, this is expected behaviour 24 | ensure 25 | ::Process.wait pid 26 | end 27 | end 28 | rescue PTY::ChildExited 29 | puts 'The child process exited!' 30 | end 31 | err = output.join('\n') 32 | raise "#{command} failed (exitstatus 0). Output was: #{err}" unless $CHILD_STATUS && $CHILD_STATUS.exitstatus.zero? 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/wicked_pdf/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'wicked_pdf/pdf_helper' 4 | require 'wicked_pdf/wicked_pdf_helper' 5 | require 'wicked_pdf/wicked_pdf_helper/assets' 6 | 7 | class WickedPdf 8 | if defined?(Rails.env) 9 | class WickedRailtie < Rails::Railtie 10 | initializer 'wicked_pdf.register', :after => 'remotipart.controller_helper' do |_app| 11 | ActiveSupport.on_load(:action_controller) { ActionController::Base.send :prepend, PdfHelper } 12 | ActiveSupport.on_load(:action_view) { include WickedPdfHelper::Assets } 13 | end 14 | end 15 | 16 | Mime::Type.register('application/pdf', :pdf) if Mime::Type.lookup_by_extension(:pdf).nil? 17 | 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/wicked_pdf/tempfile.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'tempfile' 4 | require 'stringio' 5 | 6 | class WickedPdf 7 | class Tempfile < ::Tempfile 8 | def initialize(filename, temp_dir = nil) 9 | temp_dir ||= Dir.tmpdir 10 | extension = File.extname(filename) 11 | basename = File.basename(filename, extension) 12 | super([basename, extension], temp_dir) 13 | end 14 | 15 | def write_in_chunks(input_string) 16 | binmode 17 | string_io = StringIO.new(input_string) 18 | write(string_io.read(chunk_size)) until string_io.eof? 19 | close 20 | self 21 | rescue Errno::EINVAL => e 22 | raise e, file_too_large_message 23 | end 24 | 25 | def read_in_chunks 26 | rewind 27 | binmode 28 | chunks = [] 29 | chunks << read(chunk_size) until eof? 30 | chunks.join 31 | rescue Errno::EINVAL => e 32 | raise e, file_too_large_message 33 | end 34 | 35 | private 36 | 37 | def chunk_size 38 | 1024 * 1024 39 | end 40 | 41 | def file_too_large_message 42 | 'The HTML file is too large! Try reducing the size or using the return_file option instead.' 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/wicked_pdf/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WickedPdf 4 | VERSION = '2.8.1' 5 | end 6 | -------------------------------------------------------------------------------- /lib/wicked_pdf/wicked_pdf_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WickedPdf 4 | module WickedPdfHelper 5 | def self.root_path 6 | String === Rails.root ? Pathname.new(Rails.root) : Rails.root 7 | end 8 | 9 | def self.add_extension(filename, extension) 10 | filename.to_s.split('.').include?(extension) ? filename : "#{filename}.#{extension}" 11 | end 12 | 13 | def wicked_pdf_stylesheet_link_tag(*sources) 14 | css_dir = WickedPdfHelper.root_path.join('public', 'stylesheets') 15 | css_text = sources.collect do |source| 16 | source = WickedPdfHelper.add_extension(source, 'css') 17 | "" 18 | end.join("\n") 19 | css_text.respond_to?(:html_safe) ? css_text.html_safe : css_text 20 | end 21 | 22 | def wicked_pdf_image_tag(img, options = {}) 23 | image_tag "file:///#{WickedPdfHelper.root_path.join('public', 'images', img)}", options 24 | end 25 | 26 | def wicked_pdf_javascript_src_tag(jsfile, options = {}) 27 | jsfile = WickedPdfHelper.add_extension(jsfile, 'js') 28 | type = ::Mime.respond_to?(:[]) ? ::Mime[:js] : ::Mime::JS # ::Mime[:js] cannot be used in Rails 2.3. 29 | src = "file:///#{WickedPdfHelper.root_path.join('public', 'javascripts', jsfile)}" 30 | content_tag('script', '', { 'type' => type, 'src' => path_to_javascript(src) }.merge(options)) 31 | end 32 | 33 | def wicked_pdf_javascript_include_tag(*sources) 34 | js_text = sources.collect { |source| wicked_pdf_javascript_src_tag(source, {}) }.join("\n") 35 | js_text.respond_to?(:html_safe) ? js_text.html_safe : js_text 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/wicked_pdf/wicked_pdf_helper/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'net/http' 4 | require 'delegate' 5 | require 'stringio' 6 | 7 | class WickedPdf 8 | module WickedPdfHelper 9 | module Assets 10 | ASSET_URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/ 11 | 12 | class MissingAsset < StandardError; end 13 | 14 | class MissingLocalAsset < MissingAsset 15 | attr_reader :path 16 | 17 | def initialize(path) 18 | @path = path 19 | super("Could not find asset '#{path}'") 20 | end 21 | end 22 | 23 | class MissingRemoteAsset < MissingAsset 24 | attr_reader :url, :response 25 | 26 | def initialize(url, response) 27 | @url = url 28 | @response = response 29 | super("Could not fetch asset '#{url}': server responded with #{response.code} #{response.message}") 30 | end 31 | end 32 | 33 | class SprocketsEnvironment 34 | def self.instance 35 | @instance ||= Sprockets::Railtie.build_environment(Rails.application) 36 | end 37 | 38 | def self.find_asset(*args) 39 | instance.find_asset(*args) 40 | end 41 | end 42 | 43 | class LocalAsset 44 | attr_reader :path 45 | 46 | def initialize(path) 47 | @path = path 48 | end 49 | 50 | def content_type 51 | Mime::Type.lookup_by_extension(File.extname(path).delete('.')) 52 | end 53 | 54 | def to_s 55 | IO.read(path) 56 | end 57 | 58 | def filename 59 | path.to_s 60 | end 61 | end 62 | 63 | class PropshaftAsset < LocalAsset 64 | def to_s 65 | Rails.application.assets.resolver.read(path) 66 | end 67 | end 68 | 69 | def wicked_pdf_asset_base64(path) 70 | asset = find_asset(path) 71 | raise MissingLocalAsset, path if asset.nil? 72 | 73 | base64 = Base64.encode64(asset.to_s).gsub(/\s+/, '') 74 | "data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}" 75 | end 76 | 77 | # Using `image_tag` with URLs when generating PDFs (specifically large PDFs with lots of pages) can cause buffer/stack overflows. 78 | # 79 | def wicked_pdf_url_base64(url) 80 | response = Net::HTTP.get_response(URI(url)) 81 | 82 | if response.is_a?(Net::HTTPSuccess) 83 | base64 = Base64.encode64(response.body).gsub(/\s+/, '') 84 | "data:#{response.content_type};base64,#{Rack::Utils.escape(base64)}" 85 | else 86 | Rails.logger.warn("[wicked_pdf] #{response.code} #{response.message}: #{url}") 87 | nil 88 | end 89 | end 90 | 91 | def wicked_pdf_stylesheet_link_tag(*sources) 92 | stylesheet_contents = sources.collect do |source| 93 | source = WickedPdfHelper.add_extension(source, 'css') 94 | "" 95 | end.join("\n") 96 | 97 | stylesheet_contents.gsub(ASSET_URL_REGEX) do 98 | if Regexp.last_match[1].starts_with?('data:') 99 | "url(#{Regexp.last_match[1]})" 100 | else 101 | "url(#{wicked_pdf_asset_path(Regexp.last_match[1])})" 102 | end 103 | end.html_safe 104 | end 105 | 106 | def wicked_pdf_stylesheet_pack_tag(*sources) 107 | return unless webpacker_class 108 | 109 | if running_in_development? 110 | stylesheet_pack_tag(*sources) 111 | else 112 | css_text = sources.collect do |source| 113 | source = WickedPdfHelper.add_extension(source, 'css') 114 | wicked_pdf_stylesheet_link_tag(webpacker_source_url(source)) 115 | end.join("\n") 116 | css_text.respond_to?(:html_safe) ? css_text.html_safe : css_text 117 | end 118 | end 119 | 120 | def wicked_pdf_javascript_pack_tag(*sources) 121 | return unless webpacker_class 122 | 123 | if running_in_development? 124 | javascript_pack_tag(*sources) 125 | else 126 | sources.collect do |source| 127 | source = WickedPdfHelper.add_extension(source, 'js') 128 | "" 129 | end.join("\n").html_safe 130 | end 131 | end 132 | 133 | def wicked_pdf_image_tag(img, options = {}) 134 | image_tag wicked_pdf_asset_path(img), options 135 | end 136 | 137 | def wicked_pdf_javascript_src_tag(jsfile, options = {}) 138 | jsfile = WickedPdfHelper.add_extension(jsfile, 'js') 139 | javascript_include_tag wicked_pdf_asset_path(jsfile), options 140 | end 141 | 142 | def wicked_pdf_javascript_include_tag(*sources) 143 | sources.collect do |source| 144 | source = WickedPdfHelper.add_extension(source, 'js') 145 | "" 146 | end.join("\n").html_safe 147 | end 148 | 149 | def wicked_pdf_asset_path(asset) 150 | if (pathname = asset_pathname(asset).to_s) =~ URI_REGEXP 151 | pathname 152 | else 153 | "file:///#{pathname}" 154 | end 155 | end 156 | 157 | def wicked_pdf_asset_pack_path(asset) 158 | return unless webpacker_class 159 | 160 | if running_in_development? 161 | asset_pack_path(asset) 162 | else 163 | wicked_pdf_asset_path webpacker_source_url(asset) 164 | end 165 | end 166 | 167 | private 168 | 169 | # borrowed from actionpack/lib/action_view/helpers/asset_url_helper.rb 170 | URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//} 171 | 172 | def asset_pathname(source) 173 | if precompiled_or_absolute_asset?(source) 174 | asset = asset_path(source) 175 | pathname = prepend_protocol(asset) 176 | if pathname =~ URI_REGEXP 177 | # asset_path returns an absolute URL using asset_host if asset_host is set 178 | pathname 179 | else 180 | File.join(Rails.public_path, asset.sub(/\A#{Rails.application.config.action_controller.relative_url_root}/, '')) 181 | end 182 | else 183 | asset = find_asset(source) 184 | if asset 185 | # older versions need pathname, Sprockets 4 supports only filename 186 | asset.respond_to?(:filename) ? asset.filename : asset.pathname 187 | else 188 | File.join(Rails.public_path, source) 189 | end 190 | end 191 | end 192 | 193 | def find_asset(path) 194 | if Rails.application.assets.respond_to?(:find_asset) 195 | Rails.application.assets.find_asset(path, :base_path => Rails.application.root.to_s) 196 | elsif defined?(Propshaft::Assembly) && Rails.application.assets.is_a?(Propshaft::Assembly) 197 | PropshaftAsset.new(path) 198 | elsif Rails.application.respond_to?(:assets_manifest) 199 | relative_asset_path = get_asset_path_from_manifest(path) 200 | return unless relative_asset_path 201 | 202 | asset_path = File.join(Rails.application.assets_manifest.dir, relative_asset_path) 203 | LocalAsset.new(asset_path) if File.file?(asset_path) 204 | else 205 | SprocketsEnvironment.find_asset(path, :base_path => Rails.application.root.to_s) 206 | end 207 | end 208 | 209 | def get_asset_path_from_manifest(path) 210 | assets = Rails.application.assets_manifest.assets 211 | 212 | if File.extname(path).empty? 213 | assets.find do |asset, _v| 214 | directory = File.dirname(asset) 215 | asset_path = File.basename(asset, File.extname(asset)) 216 | asset_path = File.join(directory, asset_path) if directory != '.' 217 | 218 | asset_path == path 219 | end&.last 220 | else 221 | assets[path] 222 | end 223 | end 224 | 225 | # will prepend a http or default_protocol to a protocol relative URL 226 | # or when no protcol is set. 227 | def prepend_protocol(source) 228 | protocol = WickedPdf.config[:default_protocol] || 'http' 229 | if source[0, 2] == '//' 230 | source = [protocol, ':', source].join 231 | elsif source[0] != '/' && !source[0, 8].include?('://') 232 | source = [protocol, '://', source].join 233 | end 234 | source 235 | end 236 | 237 | def precompiled_or_absolute_asset?(source) 238 | !Rails.configuration.respond_to?(:assets) || 239 | Rails.configuration.assets.compile == false || 240 | source.to_s[0] == '/' || 241 | source.to_s.match(/\Ahttps?\:\/\//) 242 | end 243 | 244 | def read_asset(source) 245 | asset = find_asset(source) 246 | return asset.to_s.force_encoding('UTF-8') if asset 247 | 248 | unless precompiled_or_absolute_asset?(source) 249 | raise MissingLocalAsset, source if WickedPdf.config[:raise_on_missing_assets] 250 | 251 | return 252 | end 253 | 254 | pathname = asset_pathname(source) 255 | if pathname =~ URI_REGEXP 256 | read_from_uri(pathname) 257 | elsif File.file?(pathname) 258 | IO.read(pathname) 259 | elsif WickedPdf.config[:raise_on_missing_assets] 260 | raise MissingLocalAsset, pathname if WickedPdf.config[:raise_on_missing_assets] 261 | end 262 | end 263 | 264 | def read_from_uri(uri) 265 | response = Net::HTTP.get_response(URI(uri)) 266 | 267 | unless response.is_a?(Net::HTTPSuccess) 268 | raise MissingRemoteAsset.new(uri, response) if WickedPdf.config[:raise_on_missing_assets] 269 | 270 | return 271 | end 272 | 273 | asset = response.body 274 | asset.force_encoding('UTF-8') if asset 275 | asset = gzip(asset) if WickedPdf.config[:expect_gzipped_remote_assets] 276 | asset 277 | end 278 | 279 | def gzip(asset) 280 | stringified_asset = StringIO.new(asset) 281 | gzipper = Zlib::GzipReader.new(stringified_asset) 282 | gzipper.read 283 | rescue Zlib::GzipFile::Error 284 | nil 285 | end 286 | 287 | def webpacker_source_url(source) 288 | return unless webpacker_version 289 | 290 | # In Webpacker 3.2.0 asset_pack_url is introduced 291 | if webpacker_version >= '3.2.0' 292 | if (host = Rails.application.config.asset_host) 293 | asset_pack_path(source, :host => host) 294 | else 295 | asset_pack_url(source) 296 | end 297 | else 298 | source_path = asset_pack_path(source) 299 | # Remove last slash from root path 300 | root_url[0...-1] + source_path 301 | end 302 | end 303 | 304 | def running_in_development? 305 | return unless webpacker_class 306 | 307 | # :dev_server method was added in webpacker 3.0.0 308 | if webpacker_class.respond_to?(:dev_server) 309 | webpacker_class.dev_server.running? 310 | else 311 | Rails.env.development? || Rails.env.test? 312 | end 313 | end 314 | 315 | def webpacker_version 316 | return unless webpacker_class 317 | 318 | require "#{webpacker_class.to_s.downcase}/version" 319 | webpacker_class.const_get('VERSION') 320 | end 321 | 322 | def webpacker_class 323 | if defined?(Shakapacker) 324 | Shakapacker 325 | elsif defined?(Webpacker) 326 | Webpacker 327 | end 328 | end 329 | end 330 | end 331 | end 332 | -------------------------------------------------------------------------------- /test/fixtures/database.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: sqlite3 3 | database: ":memory:" 4 | timeout: 500 -------------------------------------------------------------------------------- /test/fixtures/document_with_long_line.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/fixtures/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link wicked.js 3 | //= link wicked.css 4 | -------------------------------------------------------------------------------- /test/fixtures/subdirectory/nested.js: -------------------------------------------------------------------------------- 1 | // Nested js 2 | -------------------------------------------------------------------------------- /test/fixtures/wicked.css: -------------------------------------------------------------------------------- 1 | /* Wicked styles */ 2 | -------------------------------------------------------------------------------- /test/fixtures/wicked.js: -------------------------------------------------------------------------------- 1 | // Wicked js 2 | -------------------------------------------------------------------------------- /test/functional/pdf_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module ActionController 4 | class Base 5 | def render_to_string(opts = {}) 6 | opts.to_s 7 | end 8 | 9 | def self.alias_method_chain(_target, _feature); end 10 | end 11 | end 12 | 13 | module ActionControllerMock 14 | class Base 15 | def render(_) 16 | [:base] 17 | end 18 | 19 | def render_to_string; end 20 | 21 | def self.after_action(_); end 22 | end 23 | end 24 | 25 | class PdfHelperTest < ActionController::TestCase 26 | module SomePatch 27 | def render(_) 28 | super.tap do |s| 29 | s << :patched 30 | end 31 | end 32 | end 33 | 34 | def setup 35 | @ac = ActionController::Base.new 36 | end 37 | 38 | def teardown 39 | @ac = nil 40 | end 41 | 42 | test 'should prerender header and footer :template options' do 43 | options = @ac.send(:prerender_header_and_footer, 44 | :header => { :html => { :template => 'hf.html.erb' } }) 45 | assert_match %r{^file:\/\/\/.*wicked_header_pdf.*\.html}, options[:header][:html][:url] 46 | end 47 | 48 | test 'should not interfere with already prepended patches' do 49 | # Emulate railtie 50 | if Rails::VERSION::MAJOR >= 5 51 | # this spec tests the following: 52 | # if another gem prepends a render method to ActionController::Base 53 | # before wicked_pdf does, does calling render trigger an infinite loop? 54 | # this spec fails with 6392bea1fe3a41682dfd7c20fd9c179b5a758f59 because PdfHelper 55 | # aliases the render method prepended by the other gem to render_without_pdf, then 56 | # base_evals its own definition of render, which calls render_with_pdf -> render_without_pdf. 57 | # If the other gem uses the prepend inhertinance pattern (calling super instead of aliasing), 58 | # when it calls super it calls the base_eval'd version of render instead of going up the 59 | # inheritance chain, causing an infinite loop. 60 | 61 | # This fiddling with consts is required to get around the fact that PdfHelper checks 62 | # that it is being prepended to ActionController::Base 63 | OriginalBase = ActionController::Base 64 | ActionController.send(:remove_const, :Base) 65 | ActionController.const_set(:Base, ActionControllerMock::Base) 66 | 67 | # Emulate another gem being loaded before wicked 68 | ActionController::Base.prepend(SomePatch) 69 | ActionController::Base.prepend(::WickedPdf::PdfHelper) 70 | 71 | begin 72 | # test that wicked's render method is actually called 73 | ac = ActionController::Base.new 74 | ac.expects(:render_with_wicked_pdf) 75 | ac.render(:cats) 76 | 77 | # test that calling render does not trigger infinite loop 78 | ac = ActionController::Base.new 79 | assert_equal %i[base patched], ac.render(:cats) 80 | rescue SystemStackError 81 | assert_equal true, false # force spec failure 82 | ensure 83 | ActionController.send(:remove_const, :Base) 84 | ActionController.const_set(:Base, OriginalBase) 85 | end 86 | end 87 | end 88 | 89 | test 'should call after_action instead of after_filter when able' do 90 | ActionController::Base.expects(:after_filter).with(:clean_temp_files).never 91 | ActionController::Base.expects(:after_action).with(:clean_temp_files).once 92 | ActionController::Base.class_eval do 93 | include ::WickedPdf::PdfHelper 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /test/functional/wicked_pdf_helper_assets_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'action_view/test_case' 3 | 4 | class WickedPdfHelperAssetsTest < ActionView::TestCase 5 | include WickedPdf::WickedPdfHelper::Assets 6 | 7 | setup do 8 | @saved_config = WickedPdf.config 9 | WickedPdf.config = {} 10 | end 11 | 12 | teardown do 13 | WickedPdf.config = @saved_config 14 | 15 | # @see freerange/mocha#331 16 | Rails.application.unstub(:assets) 17 | Rails.application.unstub(:assets_manifest) 18 | end 19 | 20 | if Rails::VERSION::MAJOR > 3 || (Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR > 0) 21 | test 'wicked_pdf_asset_base64 returns a base64 encoded asset' do 22 | assert_match %r{data:text\/css;base64,.+}, wicked_pdf_asset_base64('wicked.css') 23 | end 24 | 25 | test 'wicked_pdf_asset_base64 works without file extension when using sprockets' do 26 | assert_match %r{data:application\/javascript;base64,.+}, wicked_pdf_asset_base64('wicked') 27 | end 28 | 29 | test 'wicked_pdf_asset_base64 works with nested files and without file extension when using sprockets' do 30 | assert_match %r{data:application\/javascript;base64,.+}, wicked_pdf_asset_base64('subdirectory/nested') 31 | end 32 | 33 | test 'wicked_pdf_asset_base64 works without file extension when using asset manifest' do 34 | stub_manifest = OpenStruct.new( 35 | :dir => Rails.root.join('app/assets'), 36 | :assets => { 'wicked.css' => 'stylesheets/wicked.css', 'wicked.js' => 'javascripts/wicked.js' } 37 | ) 38 | Rails.application.stubs(:assets).returns(nil) 39 | Rails.application.stubs(:assets_manifest).returns(stub_manifest) 40 | 41 | assert_match %r{data:text\/css;base64,.+}, wicked_pdf_asset_base64('wicked') 42 | end 43 | 44 | test 'wicked_pdf_asset_base64 works with nested files and without file extension when using asset manifest' do 45 | stub_manifest = OpenStruct.new( 46 | :dir => Rails.root.join('app/assets'), 47 | :assets => { 'subdirectory/nested.js' => 'javascripts/subdirectory/nested.js' } 48 | ) 49 | Rails.application.stubs(:assets).returns(nil) 50 | Rails.application.stubs(:assets_manifest).returns(stub_manifest) 51 | 52 | assert_match %r{data:text\/javascript;base64,.+}, wicked_pdf_asset_base64('subdirectory/nested') 53 | end 54 | 55 | test 'wicked_pdf_stylesheet_link_tag should inline the stylesheets passed in' do 56 | Rails.configuration.assets.expects(:compile => true) 57 | assert_equal "", 58 | wicked_pdf_stylesheet_link_tag('wicked') 59 | end 60 | 61 | test 'wicked_pdf_stylesheet_link_tag should work without file extension when using sprockets' do 62 | Rails.configuration.assets.expects(:compile => true) 63 | assert_equal "", 64 | wicked_pdf_stylesheet_link_tag('wicked') 65 | end 66 | 67 | test 'wicked_pdf_stylesheet_link_tag should work without file extension when using asset manifest' do 68 | stub_manifest = OpenStruct.new( 69 | :dir => Rails.root.join('app/assets'), 70 | :assets => { 'wicked.css' => 'stylesheets/wicked.css', 'wicked.js' => 'javascripts/wicked.js' } 71 | ) 72 | 73 | Rails.application.stubs(:assets).returns(nil) 74 | Rails.application.stubs(:assets_manifest).returns(stub_manifest) 75 | 76 | assert_equal "", 77 | wicked_pdf_stylesheet_link_tag('wicked') 78 | end 79 | 80 | test 'wicked_pdf_stylesheet_link_tag should raise if the stylesheet is not available and config is set' do 81 | Rails.configuration.assets.expects(:compile => true) 82 | WickedPdf.config[:raise_on_missing_assets] = true 83 | assert_raise WickedPdf::WickedPdfHelper::Assets::MissingLocalAsset do 84 | wicked_pdf_stylesheet_link_tag('non_existent') 85 | end 86 | end 87 | 88 | test 'wicked_pdf_stylesheet_link_tag should return empty if the stylesheet is not available' do 89 | Rails.configuration.assets.expects(:compile => true) 90 | assert_equal "", 91 | wicked_pdf_stylesheet_link_tag('non_existent') 92 | end 93 | 94 | test 'wicked_pdf_stylesheet_link_tag should raise if the absolute path stylesheet is not available and config is set' do 95 | Rails.configuration.assets.expects(:compile => true) 96 | WickedPdf.config[:raise_on_missing_assets] = true 97 | expects(:precompiled_or_absolute_asset? => true).twice 98 | assert_raise WickedPdf::WickedPdfHelper::Assets::MissingLocalAsset do 99 | wicked_pdf_stylesheet_link_tag('/non_existent') 100 | end 101 | end 102 | 103 | test 'wicked_pdf_stylesheet_link_tag should return empty if the absolute path stylesheet is not available' do 104 | Rails.configuration.assets.expects(:compile => true).twice 105 | assert_equal "", 106 | wicked_pdf_stylesheet_link_tag('/non_existent') 107 | end 108 | 109 | test 'wicked_pdf_stylesheet_link_tag should inline the stylesheets passed in when assets are remote' do 110 | stub_request(:get, 'https://www.example.com/wicked.css').to_return(:status => 200, :body => '/* Wicked styles */') 111 | expects(:precompiled_or_absolute_asset? => true).twice 112 | assert_equal "", 113 | wicked_pdf_stylesheet_link_tag('https://www.example.com/wicked.css') 114 | end 115 | 116 | test 'wicked_pdf_stylesheet_link_tag should inline the stylesheets passed in when assets are remote and using asset manifest' do 117 | stub_manifest = OpenStruct.new( 118 | :dir => Rails.root.join('app/assets'), 119 | :assets => { 'wicked.css' => 'stylesheets/wicked.css', 'wicked.js' => 'javascripts/wicked.js' } 120 | ) 121 | 122 | Rails.application.stubs(:assets).returns(nil) 123 | Rails.application.stubs(:assets_manifest).returns(stub_manifest) 124 | 125 | stub_request(:get, 'https://www.example.com/wicked.css').to_return(:status => 200, :body => '/* Wicked styles */') 126 | expects(:precompiled_or_absolute_asset? => true).twice 127 | assert_equal "", 128 | wicked_pdf_stylesheet_link_tag('https://www.example.com/wicked.css') 129 | end 130 | 131 | test 'wicked_pdf_stylesheet_link_tag should raise if remote assets are not available and config is set' do 132 | WickedPdf.config[:raise_on_missing_assets] = true 133 | stub_request(:get, 'https://www.example.com/wicked.css').to_return(:status => 404, :body => 'File not found') 134 | expects(:precompiled_or_absolute_asset? => true).twice 135 | assert_raise WickedPdf::WickedPdfHelper::Assets::MissingRemoteAsset do 136 | wicked_pdf_stylesheet_link_tag('https://www.example.com/wicked.css') 137 | end 138 | end 139 | 140 | test 'wicked_pdf_stylesheet_link_tag should return empty if remote assets are not available' do 141 | stub_request(:get, 'https://www.example.com/wicked.css').to_return(:status => 404, :body => 'File not found') 142 | expects(:precompiled_or_absolute_asset? => true).twice 143 | assert_equal "", 144 | wicked_pdf_stylesheet_link_tag('https://www.example.com/wicked.css') 145 | end 146 | 147 | test 'wicked_pdf_image_tag should return the same as image_tag when passed a full path' do 148 | Rails.configuration.assets.expects(:compile => true) 149 | assert_equal image_tag("file:///#{Rails.root.join('public', 'pdf')}"), 150 | wicked_pdf_image_tag('pdf') 151 | end 152 | 153 | test 'wicked_pdf_javascript_include_tag should inline the javascripts passed in' do 154 | Rails.configuration.assets.expects(:compile => true) 155 | assert_equal "", 156 | wicked_pdf_javascript_include_tag('wicked') 157 | end 158 | 159 | test 'wicked_pdf_asset_path should return a url when assets are served by an asset server' do 160 | expects(:asset_pathname => 'http://assets.domain.com/dummy.png') 161 | assert_equal 'http://assets.domain.com/dummy.png', wicked_pdf_asset_path('dummy.png') 162 | end 163 | 164 | test 'wicked_pdf_asset_path should return a url when assets are served by an asset server using HTTPS' do 165 | Rails.configuration.assets.expects(:compile => false) 166 | expects(:asset_path => 'https://assets.domain.com/dummy.png') 167 | assert_equal 'https://assets.domain.com/dummy.png', wicked_pdf_asset_path('dummy.png') 168 | end 169 | 170 | test 'wicked_pdf_asset_path should return a url with a protocol when assets are served by an asset server with relative urls' do 171 | Rails.configuration.assets.expects(:compile => false) 172 | expects(:asset_path => '//assets.domain.com/dummy.png') 173 | assert_equal 'http://assets.domain.com/dummy.png', wicked_pdf_asset_path('dummy.png') 174 | end 175 | 176 | test 'wicked_pdf_asset_path should return a url with a protocol when assets are served by an asset server with no protocol set' do 177 | Rails.configuration.assets.expects(:compile => false) 178 | expects(:asset_path => 'assets.domain.com/dummy.png') 179 | assert_equal 'http://assets.domain.com/dummy.png', wicked_pdf_asset_path('dummy.png') 180 | end 181 | 182 | test 'wicked_pdf_asset_path should return a path' do 183 | Rails.configuration.assets.expects(:compile => true) 184 | path = wicked_pdf_asset_path('application.css') 185 | 186 | assert path.include?('/app/assets/stylesheets/application.css') 187 | assert path.include?('file:///') 188 | 189 | Rails.configuration.assets.expects(:compile => false) 190 | expects(:asset_path => '/assets/application-6fba03f13d6ff1553477dba03475c4b9b02542e9fb8913bd63c258f4de5b48d9.css') 191 | path = wicked_pdf_asset_path('application.css') 192 | 193 | assert path.include?('/public/assets/application-6fba03f13d6ff1553477dba03475c4b9b02542e9fb8913bd63c258f4de5b48d9.css') 194 | assert path.include?('file:///') 195 | end 196 | 197 | # This assets does not exists so probably it doesn't matter what is 198 | # returned, but lets ensure that returned value is the same when assets 199 | # are precompiled and when they are not 200 | test 'wicked_pdf_asset_path should return a path when asset does not exist' do 201 | Rails.configuration.assets.expects(:compile => true) 202 | path = wicked_pdf_asset_path('missing.png') 203 | 204 | assert path.include?('/public/missing.png') 205 | assert path.include?('file:///') 206 | 207 | Rails.configuration.assets.expects(:compile => false) 208 | expects(:asset_path => '/missing.png') 209 | path = wicked_pdf_asset_path('missing.png') 210 | 211 | assert path.include?('/public/missing.png') 212 | assert path.include?('file:///') 213 | end 214 | 215 | test 'wicked_pdf_asset_path should return a url when asset is url' do 216 | Rails.configuration.assets.expects(:compile => true) 217 | expects(:asset_path => 'http://example.com/rails.png') 218 | assert_equal 'http://example.com/rails.png', wicked_pdf_asset_path('http://example.com/rails.png') 219 | 220 | Rails.configuration.assets.expects(:compile => false) 221 | expects(:asset_path => 'http://example.com/rails.png') 222 | assert_equal 'http://example.com/rails.png', wicked_pdf_asset_path('http://example.com/rails.png') 223 | end 224 | 225 | test 'wicked_pdf_asset_path should return a url when asset is url without protocol' do 226 | Rails.configuration.assets.expects(:compile => true) 227 | expects(:asset_path => '//example.com/rails.png') 228 | assert_equal 'http://example.com/rails.png', wicked_pdf_asset_path('//example.com/rails.png') 229 | 230 | Rails.configuration.assets.expects(:compile => false) 231 | expects(:asset_path => '//example.com/rails.png') 232 | assert_equal 'http://example.com/rails.png', wicked_pdf_asset_path('//example.com/rails.png') 233 | end 234 | 235 | test 'WickedPdfHelper::Assets::ASSET_URL_REGEX should match various URL data type formats' do 236 | assert_match WickedPdf::WickedPdfHelper::Assets::ASSET_URL_REGEX, 'url(\'/asset/stylesheets/application.css\');' 237 | assert_match WickedPdf::WickedPdfHelper::Assets::ASSET_URL_REGEX, 'url("/asset/stylesheets/application.css");' 238 | assert_match WickedPdf::WickedPdfHelper::Assets::ASSET_URL_REGEX, 'url(/asset/stylesheets/application.css);' 239 | assert_match WickedPdf::WickedPdfHelper::Assets::ASSET_URL_REGEX, 'url(\'http://assets.domain.com/dummy.png\');' 240 | assert_match WickedPdf::WickedPdfHelper::Assets::ASSET_URL_REGEX, 'url("http://assets.domain.com/dummy.png");' 241 | assert_match WickedPdf::WickedPdfHelper::Assets::ASSET_URL_REGEX, 'url(http://assets.domain.com/dummy.png);' 242 | assert_no_match WickedPdf::WickedPdfHelper::Assets::ASSET_URL_REGEX, '.url { \'http://assets.domain.com/dummy.png\' }' 243 | end 244 | 245 | test 'prepend_protocol should properly set the protocol when the asset is precompiled' do 246 | assert_equal 'http://assets.domain.com/dummy.png', prepend_protocol('//assets.domain.com/dummy.png') 247 | assert_equal '/assets.domain.com/dummy.png', prepend_protocol('/assets.domain.com/dummy.png') 248 | assert_equal 'http://assets.domain.com/dummy.png', prepend_protocol('http://assets.domain.com/dummy.png') 249 | assert_equal 'https://assets.domain.com/dummy.png', prepend_protocol('https://assets.domain.com/dummy.png') 250 | end 251 | end 252 | end 253 | -------------------------------------------------------------------------------- /test/functional/wicked_pdf_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'action_view/test_case' 3 | 4 | class WickedPdfHelperTest < ActionView::TestCase 5 | include WickedPdf::WickedPdfHelper 6 | 7 | test 'wicked_pdf_stylesheet_link_tag should inline the stylesheets passed in' do 8 | assert_equal "", 9 | wicked_pdf_stylesheet_link_tag('../../../fixtures/wicked') 10 | end 11 | 12 | test 'wicked_pdf_image_tag should return the same as image_tag when passed a full path' do 13 | assert_equal image_tag("file:///#{Rails.root.join('public', 'images', 'pdf')}"), 14 | wicked_pdf_image_tag('pdf') 15 | end 16 | 17 | if Rails::VERSION::MAJOR == 2 18 | test 'wicked_pdf_javascript_src_tag should return the same as javascript_src_tag when passed a full path' do 19 | assert_equal javascript_src_tag("file:///#{Rails.root.join('public', 'javascripts', 'pdf')}", {}), 20 | wicked_pdf_javascript_src_tag('pdf') 21 | end 22 | end 23 | 24 | test 'wicked_pdf_include_tag should return many wicked_pdf_javascript_src_tags' do 25 | assert_equal [wicked_pdf_javascript_src_tag('foo'), wicked_pdf_javascript_src_tag('bar')].join("\n"), 26 | wicked_pdf_javascript_include_tag('foo', 'bar') 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV['RAILS_ENV'] = 'test' 3 | 4 | require File.expand_path('../dummy/config/environment.rb', __FILE__) 5 | 6 | require 'test/unit' 7 | require 'mocha' 8 | require 'rails/test_help' 9 | require 'mocha/test_unit' 10 | require 'webmock/minitest' 11 | 12 | require 'wicked_pdf' 13 | 14 | Rails.backtrace_cleaner.remove_silencers! 15 | WickedPdf.silence_deprecations = true 16 | 17 | if (assets_dir = Rails.root.join('app/assets')) && File.directory?(assets_dir) 18 | # Copy CSS file 19 | destination = assets_dir.join('stylesheets/wicked.css') 20 | source = File.read('test/fixtures/wicked.css') 21 | File.open(destination, 'w') { |f| f.write(source) } 22 | 23 | # Copy JS file 24 | js_dir = assets_dir.join('javascripts') 25 | Dir.mkdir(js_dir) unless File.directory?(js_dir) 26 | destination = js_dir.join('wicked.js') 27 | source = File.read('test/fixtures/wicked.js') 28 | File.open(destination, 'w') { |f| f.write(source) } 29 | 30 | Dir.mkdir(js_dir.join('subdirectory')) unless File.directory?(js_dir.join('subdirectory')) 31 | destination = js_dir.join('subdirectory/nested.js') 32 | source = File.read('test/fixtures/subdirectory/nested.js') 33 | File.open(destination, 'w') { |f| f.write(source) } 34 | 35 | config_dir = assets_dir.join('config') 36 | Dir.mkdir(config_dir) unless File.directory?(config_dir) 37 | source = File.read('test/fixtures/manifest.js') 38 | destination = config_dir.join('manifest.js') 39 | File.open(destination, 'w') { |f| f.write(source) } 40 | end 41 | -------------------------------------------------------------------------------- /test/unit/wicked_pdf_binary_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class WickedPdfBinaryTest < ActiveSupport::TestCase 4 | test 'should extract old wkhtmltopdf version' do 5 | version_info_sample = "Name:\n wkhtmltopdf 0.9.9\n\nLicense:\n Copyright (C) 2008,2009 Wkhtmltopdf Authors.\n\n\n\n License GPLv3+: GNU GPL version 3 or later .\n This is free software: you are free to change and redistribute it. There is NO\n WARRANTY, to the extent permitted by law.\n\nAuthors:\n Written by Jakob Truelsen. Patches by Mrio Silva, Benoit Garret and Emmanuel\n Bouthenot.\n" 6 | assert_equal WickedPdf::DEFAULT_BINARY_VERSION, binary.parse_version_string(version_info_sample) 7 | end 8 | 9 | test 'should extract new wkhtmltopdf version' do 10 | version_info_sample = "Name:\n wkhtmltopdf 0.11.0 rc2\n\nLicense:\n Copyright (C) 2010 wkhtmltopdf/wkhtmltoimage Authors.\n\n\n\n License LGPLv3+: GNU Lesser General Public License version 3 or later\n . This is free software: you are free to\n change and redistribute it. There is NO WARRANTY, to the extent permitted by\n law.\n\nAuthors:\n Written by Jan Habermann, Christian Sciberras and Jakob Truelsen. Patches by\n Mehdi Abbad, Lyes Amazouz, Pascal Bach, Emmanuel Bouthenot, Benoit Garret and\n Mario Silva." 11 | assert_equal Gem::Version.new('0.11.0'), binary.parse_version_string(version_info_sample) 12 | end 13 | 14 | test 'should extract wkhtmltopdf version with nondigit symbols' do 15 | version_info_sample = "Name:\n wkhtmltopdf 0.10.4b\n\nLicense:\n Copyright (C) 2008,2009 Wkhtmltopdf Authors.\n\n\n\n License GPLv3+: GNU GPL version 3 or later .\n This is free software: you are free to change and redistribute it. There is NO\n WARRANTY, to the extent permitted by law.\n\nAuthors:\n Written by Jakob Truelsen. Patches by Mrio Silva, Benoit Garret and Emmanuel\n Bouthenot.\n" 16 | assert_equal Gem::Version.new('0.10.4b'), binary.parse_version_string(version_info_sample) 17 | end 18 | 19 | test 'should fallback to default version on parse error' do 20 | assert_equal WickedPdf::DEFAULT_BINARY_VERSION, binary.parse_version_string('') 21 | end 22 | 23 | def binary(path = nil) 24 | WickedPdf::Binary.new(path) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/unit/wicked_pdf_option_parser_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class WickedPdfOptionParserTest < ActiveSupport::TestCase 4 | test 'should parse header and footer options' do 5 | %i[header footer].each do |hf| 6 | %i[center font_name left right].each do |o| 7 | assert_equal "--#{hf}-#{o.to_s.tr('_', '-')} header_footer", 8 | parse_options(hf => { o => 'header_footer' }).strip 9 | end 10 | 11 | %i[font_size spacing].each do |o| 12 | assert_equal "--#{hf}-#{o.to_s.tr('_', '-')} 12", 13 | parse_options(hf => { o => '12' }).strip 14 | end 15 | 16 | assert_equal "--#{hf}-line", 17 | parse_options(hf => { :line => true }).strip 18 | assert_equal "--#{hf}-html http://www.abc.com", 19 | parse_options(hf => { :html => { :url => 'http://www.abc.com' } }).strip 20 | end 21 | end 22 | 23 | test 'should parse toc options' do 24 | toc_option = option_parser.valid_option('toc') 25 | 26 | %i[font_name header_text].each do |o| 27 | assert_equal "#{toc_option} --toc-#{o.to_s.tr('_', '-')} toc", 28 | parse_options(:toc => { o => 'toc' }).strip 29 | end 30 | 31 | %i[ 32 | depth header_fs l1_font_size l2_font_size l3_font_size l4_font_size 33 | l5_font_size l6_font_size l7_font_size l1_indentation l2_indentation 34 | l3_indentation l4_indentation l5_indentation l6_indentation l7_indentation 35 | ].each do |o| 36 | assert_equal "#{toc_option} --toc-#{o.to_s.tr('_', '-')} 5", 37 | parse_options(:toc => { o => 5 }).strip 38 | end 39 | 40 | %i[no_dots disable_links disable_back_links].each do |o| 41 | assert_equal "#{toc_option} --toc-#{o.to_s.tr('_', '-')}", 42 | parse_options(:toc => { o => true }).strip 43 | end 44 | end 45 | 46 | test 'should parse outline options' do 47 | assert_equal '--outline', parse_options(:outline => { :outline => true }).strip 48 | assert_equal '--outline-depth 5', parse_options(:outline => { :outline_depth => 5 }).strip 49 | end 50 | 51 | test 'should parse no_images option' do 52 | assert_equal '--no-images', parse_options(:no_images => true).strip 53 | assert_equal '--images', parse_options(:images => true).strip 54 | end 55 | 56 | test 'should parse margins options' do 57 | %i[top bottom left right].each do |o| 58 | assert_equal "--margin-#{o} 12", parse_options(:margin => { o => '12' }).strip 59 | end 60 | end 61 | 62 | test 'should parse cover' do 63 | cover_option = option_parser.valid_option('cover') 64 | 65 | pathname = Rails.root.join('app', 'views', 'pdf', 'file.html') 66 | assert_equal "#{cover_option} http://example.org", parse_options(:cover => 'http://example.org').strip, 'URL' 67 | assert_equal "#{cover_option} #{pathname}", parse_options(:cover => pathname).strip, 'Pathname' 68 | assert_match(/#{cover_option} .+wicked_cover_pdf.+\.html/, parse_options(:cover => 'HELLO').strip, 'HTML') 69 | end 70 | 71 | test 'should parse other options' do 72 | %i[ 73 | orientation page_size proxy username password dpi 74 | encoding user_style_sheet 75 | ].each do |o| 76 | assert_equal "--#{o.to_s.tr('_', '-')} opts", parse_options(o => 'opts').strip 77 | end 78 | 79 | %i[allow].each do |o| 80 | assert_equal "--#{o.to_s.tr('_', '-')} opts", parse_options(o => 'opts').strip 81 | assert_equal "--#{o.to_s.tr('_', '-')} opts1 --#{o.to_s.tr('_', '-')} opts2", parse_options(o => %w[opts1 opts2]).strip 82 | end 83 | 84 | %i[cookie post].each do |o| 85 | assert_equal "--#{o.to_s.tr('_', '-')} name value", parse_options(o => 'name value').strip 86 | 87 | nv_formatter = proc { |number| "--#{o.to_s.tr('_', '-')} par#{number} val#{number}" } 88 | assert_equal "#{nv_formatter.call(1)} #{nv_formatter.call(2)}", parse_options(o => ['par1 val1', 'par2 val2']).strip 89 | end 90 | 91 | %i[redirect_delay zoom page_offset].each do |o| 92 | assert_equal "--#{o.to_s.tr('_', '-')} 5", parse_options(o => 5).strip 93 | end 94 | 95 | %i[ 96 | book default_header disable_javascript grayscale lowquality 97 | enable_plugins disable_internal_links disable_external_links 98 | print_media_type disable_smart_shrinking use_xserver no_background disable_local_file_access 99 | ].each do |o| 100 | assert_equal "--#{o.to_s.tr('_', '-')}", parse_options(o => true).strip 101 | end 102 | end 103 | 104 | test 'should not use double dash options for version without dashes' do 105 | op = option_parser(WickedPdf::OptionParser::BINARY_VERSION_WITHOUT_DASHES) 106 | 107 | %w[toc cover].each do |name| 108 | assert_equal op.valid_option(name), name 109 | end 110 | end 111 | 112 | test 'should use double dash options for version with dashes' do 113 | op = option_parser(Gem::Version.new('0.11.0')) 114 | 115 | %w[toc cover].each do |name| 116 | assert_equal op.valid_option(name), "--#{name}" 117 | end 118 | end 119 | 120 | test '-- options should not be given after object' do 121 | options = { :header => { :center => 3 }, :cover => 'http://example.org', :disable_javascript => true } 122 | cover_option = option_parser.valid_option('cover') 123 | assert_equal parse_options(options), "--disable-javascript --header-center 3 #{cover_option} http://example.org" 124 | end 125 | 126 | def parse_options(options, version = WickedPdf::DEFAULT_BINARY_VERSION) 127 | option_parser(version).parse(options).join(' ') 128 | end 129 | 130 | def option_parser(version = WickedPdf::DEFAULT_BINARY_VERSION) 131 | WickedPdf::OptionParser.new(version) 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /test/unit/wicked_pdf_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | WickedPdf.config = { :exe_path => ENV['WKHTMLTOPDF_BIN'] || '/usr/local/bin/wkhtmltopdf' } 3 | HTML_DOCUMENT = 'Hello World'.freeze 4 | 5 | class WickedPdfTest < ActiveSupport::TestCase 6 | def setup 7 | @wp = WickedPdf.new 8 | end 9 | 10 | test 'should update config through .configure class method' do 11 | WickedPdf.configure do |c| 12 | c.test = 'foobar' 13 | end 14 | 15 | assert WickedPdf.config == { :exe_path => ENV['WKHTMLTOPDF_BIN'] || '/usr/local/bin/wkhtmltopdf', :test => 'foobar' } 16 | end 17 | 18 | test 'should clear config through .clear_config class method' do 19 | backup_config = WickedPdf.config 20 | 21 | WickedPdf.clear_config 22 | assert WickedPdf.config == {} 23 | 24 | WickedPdf.config = backup_config 25 | end 26 | 27 | test 'should generate PDF from html document' do 28 | pdf = @wp.pdf_from_string HTML_DOCUMENT 29 | assert pdf.start_with?('%PDF-1.4') 30 | assert pdf.rstrip.end_with?('%%EOF') 31 | assert pdf.length > 100 32 | end 33 | 34 | test 'should generate PDF from html document with long lines' do 35 | document_with_long_line_file = File.new('test/fixtures/document_with_long_line.html', 'r') 36 | pdf = @wp.pdf_from_string(document_with_long_line_file.read) 37 | assert pdf.start_with?('%PDF-1.4') 38 | assert pdf.rstrip.end_with?('%%EOF') 39 | assert pdf.length > 100 40 | end 41 | 42 | test 'should generate PDF from html existing HTML file without converting it to string' do 43 | filepath = File.join(Dir.pwd, 'test/fixtures/document_with_long_line.html') 44 | pdf = @wp.pdf_from_html_file(filepath) 45 | assert pdf.start_with?('%PDF-1.4') 46 | assert pdf.rstrip.end_with?('%%EOF') 47 | assert pdf.length > 100 48 | end 49 | 50 | test 'should raise exception when no path to wkhtmltopdf' do 51 | assert_raise RuntimeError do 52 | WickedPdf.new ' ' 53 | end 54 | end 55 | 56 | test 'should raise exception when wkhtmltopdf path is wrong' do 57 | assert_raise RuntimeError do 58 | WickedPdf.new '/i/do/not/exist/notwkhtmltopdf' 59 | end 60 | end 61 | 62 | test 'should raise exception when wkhtmltopdf is not executable' do 63 | begin 64 | tmp = Tempfile.new('wkhtmltopdf') 65 | fp = tmp.path 66 | File.chmod 0o000, fp 67 | assert_raise RuntimeError do 68 | WickedPdf.new fp 69 | end 70 | ensure 71 | tmp.delete 72 | end 73 | end 74 | 75 | test 'should raise exception when pdf generation fails' do 76 | begin 77 | tmp = Tempfile.new('wkhtmltopdf') 78 | fp = tmp.path 79 | File.chmod 0o777, fp 80 | wp = WickedPdf.new fp 81 | assert_raise RuntimeError do 82 | wp.pdf_from_string HTML_DOCUMENT 83 | end 84 | ensure 85 | tmp.delete 86 | end 87 | end 88 | 89 | test 'should output progress when creating pdfs on compatible hosts' do 90 | wp = WickedPdf.new 91 | output = [] 92 | options = { :progress => proc { |o| output << o } } 93 | wp.pdf_from_string HTML_DOCUMENT, options 94 | if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/ 95 | assert_empty output 96 | else 97 | assert(output.collect { |l| !l.match(/Loading/).nil? }.include?(true)) # should output something like "Loading pages (1/5)" 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /test/unit/wkhtmltopdf_location_test.rb: -------------------------------------------------------------------------------- 1 | class WkhtmltopdfLocationTest < ActiveSupport::TestCase 2 | setup do 3 | @saved_config = WickedPdf.config 4 | WickedPdf.config = {} 5 | end 6 | 7 | teardown do 8 | WickedPdf.config = @saved_config 9 | end 10 | 11 | test 'should correctly locate wkhtmltopdf without bundler' do 12 | bundler_module = Bundler 13 | Object.send(:remove_const, :Bundler) 14 | 15 | assert_nothing_raised do 16 | WickedPdf.new 17 | end 18 | 19 | Object.const_set(:Bundler, bundler_module) 20 | end 21 | 22 | test 'should correctly locate wkhtmltopdf with bundler' do 23 | assert_nothing_raised do 24 | WickedPdf.new 25 | end 26 | end 27 | 28 | class LocationNonWritableTest < ActiveSupport::TestCase 29 | setup do 30 | @saved_config = WickedPdf.config 31 | WickedPdf.config = {} 32 | 33 | @old_home = ENV['HOME'] 34 | ENV['HOME'] = '/not/a/writable/directory' 35 | end 36 | 37 | teardown do 38 | WickedPdf.config = @saved_config 39 | ENV['HOME'] = @old_home 40 | end 41 | 42 | test 'should correctly locate wkhtmltopdf with bundler while HOME is set to a non-writable directory' do 43 | assert_nothing_raised do 44 | WickedPdf.new 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /wicked_pdf.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'wicked_pdf/version' 4 | require 'English' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'wicked_pdf' 8 | spec.version = WickedPdf::VERSION 9 | spec.authors = ['Miles Z. Sterrett', 'David Jones'] 10 | spec.email = ['miles.sterrett@gmail.com', 'unixmonkey1@gmail.com'] 11 | spec.summary = 'PDF generator (from HTML) gem for Ruby on Rails' 12 | spec.homepage = 'https://github.com/mileszs/wicked_pdf' 13 | spec.license = 'MIT' 14 | spec.date = Time.now.strftime('%Y-%m-%d') 15 | spec.description = < 'https://github.com/mileszs/wicked_pdf/blob/master/CHANGELOG.md' 22 | } 23 | 24 | spec.required_ruby_version = Gem::Requirement.new('>= 2.2') 25 | spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) 26 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 27 | spec.require_paths = ['lib'] 28 | 29 | spec.requirements << 'wkhtmltopdf' 30 | 31 | spec.add_dependency 'activesupport' 32 | spec.add_dependency 'ostruct' 33 | 34 | spec.add_development_dependency 'bundler' 35 | spec.add_development_dependency 'mocha', '= 1.3' 36 | spec.add_development_dependency 'rails' 37 | spec.add_development_dependency 'rake' 38 | spec.add_development_dependency 'rubocop', '~> 1.46' 39 | spec.add_development_dependency 'sqlite3', '~> 1.3' 40 | spec.add_development_dependency 'test-unit' 41 | spec.add_development_dependency 'webmock', '~> 3.19' 42 | end 43 | --------------------------------------------------------------------------------