diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..7c869f02d --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Jeremy Friesen Jeremy Friesen diff --git a/.rubocop.yml b/.rubocop.yml index 754b4fb08..bd6df85b8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -109,8 +109,6 @@ NumericLiterals: Enabled: false AsciiComments: Enabled: false -EmptyLinesAroundBody: - Enabled: false SignalException: Enabled: false HandleExceptions: @@ -191,3 +189,41 @@ ParameterLists: Enabled: false BlockComments: Enabled: false +MultilineOperationIndentation: + Enabled: false +SpaceBeforeComment: + Enabled: false +AbcSize: + Enabled: false +EmptyLinesAroundClassBody: + Enabled: false +PerceivedComplexity: + Enabled: false +EmptyLinesAroundBlockBody: + Enabled: false +UnusedBlockArgument: + Enabled: false +UnusedMethodArgument: + Enabled: false +Next: + Enabled: false +SymbolProc: + Enabled: false +ClassCheck: + Enabled: false +SpaceBeforeComma: + Enabled: false +StringLiteralsInInterpolation: + Enabled: false +EmptyLinesAroundModuleBody: + Enabled: false +GuardClause: + Enabled: false +ElseAlignment: + Enabled: false +CommentIndentation: + Enabled: false +EachWithObject: + Enabled: false +BlockEndNewline: + Enabled: false diff --git a/.travis.yml b/.travis.yml index fc26480f0..4c9909a49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: ruby rvm: - - 1.9.3 - 2.0.0 - - 2.1.2 + - 2.1.4 + - 2.2.0 - rbx-2 - - jruby-19mode + - jruby +env: + JRUBY_OPTS=--2.0 matrix: allow_failures: - rvm: rbx-2 diff --git a/CHANGELOG.md b/CHANGELOG.md index bc94559bb..fc71996b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,178 @@ +## PrawnPDF version 2.0.0 -- February 26, 2015 + +### Changes to supported Ruby versions + +Now that Ruby 1.9.3 is no longer supported by the Ruby core team, Prawn will no +longer attempt to maintain 1.9.x compatibility. + +We will continue to support Ruby 2.0.0 and 2.1.x, and have added support for Ruby +2.2.x as well. + +If you're using JRuby, we recommend using JRuby 1.7.x (>= 1.7.18) in 2.0 mode +for now. Please file bug reports if you run into any problems! + +### Changes to PrawnPDF's versioning policies + +Starting with this release, we will set version numbers based on the following policy: + +* Whenever a documented feature is modified in a backwards-incompatible way, +we'll bump our major version number. + +* Whenever we add new functionality without breaking backwards compatibility, +we'll bump our minor version number. + +* Whenever we cut maintenance releases (which cover only bug fixes, +documentation, and internal improvements), we'll bump our tiny version number. + +This policy is similar in spirit to [Semantic Versioning](http://semver.org/), +and we may end up formally adopting SemVer in the future. + +The main caveat is that if a feature is not documented (either in our API +documentation or in Prawn's manual), you cannot assume anything about its +intended behavior. Prawn has a lot of cruft left in it due to piecewise +development over nearly a decade, so the APIs have not been designed as +much as they have been organically grown. + +To make sure that the amount of undefined behavior in Prawn shrinks over time, +we'll make sure to review and revise documentation whenever new functionality +is added, and also whenever we change existing features. + +### All decimals in PDF output are now rounded to a fixed precision of 4 decimal places + +This should improve compatibility across viewers that do not support +arbitrarily long decimal numbers, without effecting practical use +at all. (A PDF point is 1/72 inch, so 0.0001 PDF point is a very, very small number). + +This patch was added in response to certain PDFs on certain versions of Adobe +Reader raising errors when viewed. + +(Gregory Brown, [#782](https://github.com/prawnpdf/prawn/pull/782)) + +### Fixed text width calculation to prevent unnecessary soft hyphen + +Previously, the `width_of` method would include the width of all soft hyphens +in a string, regardless of whether they would be rendered or not. This caused +lines of text to appear longer than they actually were, causing unnecessary +wrapping and hyphenation at times. + +We've changed this calculation to only include the width of a soft hyphen when +it will actually be rendered (i.e. when a line needs to be wrapped), which +should prevent unnecessary hyphenation and text wrapping in strings containing +soft hyphens. + +(Mario Albert, [#775](https://github.com/prawnpdf/prawn/issues/775), [#786](https://github.com/prawnpdf/prawn/pull/786)) + +### Fixed styled text width calculations when using TTF files + +Previously, `width_of` calculations on styled text were relying on the +document font's name attribute in order to look up the appropriate +font style. This doesn't work for TTF fonts, since the name is a full +path to a single style of font, and the Prawn must know about the font +family in order to find another style. + +The `width_of` method has been updated to use the font family instead, +allowing calculations to work properly with TTFs. + +(Ernie Miller, [#827](https://github.com/prawnpdf/prawn/pull/827)) + +### Fixed broken vertical alignment for center and bottom + +In earlier versions of Prawn, center alignment and bottom alignment in text +boxes worked in a way that is inconsistent with common typographical +conventions: + +* Vertically centered text was padded so that the distance between the +top of the box and the ascender of the first line of text was made equal to the +distance between the descender of the bottom line to the descender of the last line of text. + +* Bottom aligned text included the line gap specified by a font, leaving a bit of +extra in the box space below the descender of the last line of text. + +Other commonly used software typically uses the baseline rather than the descender +when centering text, and does not include the line gap when bottom aligning text. +We've changed Prawn's behavior to be consistent with those conventions, which +should result in less surprising output. + +That said, this problem has existed in Prawn for a very, very long time. Check your code to +see if you've been working around this issue, because if so it may cause breakage. + +For a very detailed discussion (with pictures), see issue [#169](https://github.com/prawnpdf/prawn/issues/169). + +(Jesse Doyle, [#788](https://github.com/prawnpdf/prawn/pull/788)) + +### Calling dash(0) now raises an error instead of generating a corrupt PDF + +In earlier versions of Prawn, accidentally calling `dash(0)` instead of +`undash` in an attempt to clear dash settings would generate a corrupted +document instead of raising an error, making debugging difficult. + +Because `dash(0)` is not a valid API call, we now raise an error that says +"Zero length dashes are invalid. Call #undash to disable dashes.", making +the source of the problem much clearer. + +### Vastly improved handling of encodings for PDF built in (AFM) fonts + +Prawn has always had comprehensive UTF-8 support for TTF font files, but many +users still rely on the "built in" AFM fonts that are provided by PDF viewers. +These fonts only support the very limited set of internationalized characters +specified by the Windows-1252 character encoding, and that has been a long +standing source of confusion and awkward behaviors. + +Earlier versions of Prawn attempted to transcode UTF-8 to Windows-1252 +automatically, but some of our low level features either assumed that +text was already encoded properly, or returned text in a different +encoding than what was provided because of the internal transcoding +operations. We also handled Windows-1252 encoding manually, so strings +would come back tagged as ASCII-8BIT instead of Windows-1252, making +things even more confusing. + +In this release, we've made some major behavior changes to the way AFM +fonts work so that users need to think less about Prawn's internals: + +* Text handling for all public Prawn methods is now UTF-8-in, UTF-8-out, +making Windows-1252 transcoding purely an implementation detail of Prawn +that isn't visible from the outside. + +* When using AFM fonts + non-ASCII characters that are NOT supported in +Windows-1252, an exception will be raised rather than replacing w. + `_`. + +* When using AFM fonts + non-ASCII characters that are supported in + Windows-1252, users will see a warning about the limited +internationalization support, along with a recommendation to use a TTF + font instead. + +* The warning includes instructions on how to disable it (just set +`Prawn::Font::AFM.hide_m17_warning = true`) + +* When using AFM fonts + ASCII only text, no warning will be seen. + +* Internally, we're now using Ruby's M17n system to handle the encoding +into Windows-1252, so text.encoding will come back as Windows-1252 +when `AFM#normalize_encoding` is called, rather than `ASCII-8Bit` + +None of the above issues apply when using TTF fonts with Prawn, because +those have always been UTF-8 in, UTF-8 out, and no transcoding was +done internally. It is still our recommendation for those using internationalized +text to use TTF fonts because they do not have the same limitations +as AFM fonts, but those who need to use AFM for whatever reason +should benefit greatly from these changes. + +(Gregory Brown, [#793](https://github.com/prawnpdf/prawn/pull/793)) + +### Temporarily restored the Document#on_page_create method + +This method was moved into PDF::Core in the Prawn 1.3.0 release, removing +it from the `Prawn::Document` API. Although it is a low-level method not +meant for general use, it is necessary for certain tasks that we do not +have proper support for elsewhere. + +This method should still be considered part of Prawn's internals and is subject +to change at any time, but we have restored it temporarily until we have +a suitable replacement for it. See the discussion on [#797](https://github.com/prawnpdf/prawn/issues/797) +for more details. + +(Jesse Doyle, [#797](https://github.com/prawnpdf/prawn/issues/797), [#825](https://github.com/prawnpdf/prawn/pull/825)) ## PrawnPDF 1.3.0 -- September 28, 2014 @@ -291,4 +466,3 @@ Prawn::Outline and marked it as part of our stable API. For changes before our 1.0 release, see the following wiki page: https://github.com/prawnpdf/prawn/wiki/CHANGELOG - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb6d511d3..4b4c40d06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,33 +13,3 @@ to file a ticket or file one on your behalf. 3. Pull requests for bug fixes or enhancements are welcome. Feel free to open them in the early stages of your work so that we can give feedback and discuss ideas together. - -Please note that Prawn has had a rough couple of years, and so our forward -progress has slowed down quite a bit. To make it to a 1.0 release, we need -to work on just the most essential things, but our long-term goal is to -make this project as friendly to contributors as possible. - -Here are some notes on our issue tracker process: - -* We will close pull requests that need revisions, after tagging them -with a "needs-revision" tag. Once the revisions are made, you can -comment on the pull request and we'll review it again. - -* We may close any issue after 30 days of inactivity, labeling it -with a 'stale' tag. To re-open it, simply leave a comment on -the issue. If there is a proper example in place and the -issue still is relevant, we will open it back up. - -* We will close all incoming issues related to the currently -unmaintained templates feature, marking them with a 'templates' -tag. These will remained archived in our tracker until -someone else starts maintaining prawn-templates, but we -cannot provide any help for this unsupported feature at -this point in time. - -These are all temporary measures, and will be revised once we -hit 1.0. The underlying goal is to keep the issue tracker full -of actionable issues that we can respond to in a timely fashion, -not to sweep unresolved issues under the rug! - -Thanks for your patience, and happy Prawning. diff --git a/Gemfile b/Gemfile index 4c487543f..8ed201afa 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" gemspec -if ENV["CI"] +if ENV["CI"] platforms :rbx do gem "rubysl-singleton", "~> 2.0" gem "rubysl-digest", "~> 2.0" diff --git a/README.md b/README.md index fcca12689..2ae3804d9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Prawn: Fast, Nimble PDF Generation For Ruby [![Gem Version](https://badge.fury.io/rb/prawn.png)](http://badge.fury.io/rb/prawn) -[![Build Status](https://secure.travis-ci.org/prawnpdf/prawn.png)](http://travis-ci.org/prawnpdf/prawn) +[![Build Status](https://api.travis-ci.org/prawnpdf/prawn.svg?branch=master)](http://travis-ci.org/prawnpdf/prawn) Prawn is a pure Ruby PDF generation library that provides a lot of great functionality while trying to remain simple and reasonably performant. Here are some of the important features we provide: @@ -27,7 +27,7 @@ One thing Prawn is not, and will never be, is an HTML to PDF generator. For thos Because Prawn is pure Ruby and all of its runtime dependencies are maintained by us, it should work pretty much anywhere. We officially support -MRI {1.9.3, 2.0.0, 2.1.x} and jruby 1.7.x (>= 1.7.9) in 1.9 mode, however +MRI {2.0.0, 2.1.x, 2.2.x} and jruby 1.7.x (>= 1.7.18) in 2.0 mode, however we will accept patches to fix problems on other Ruby platforms if they aren't too invasive. @@ -67,8 +67,8 @@ Note that while we will try to keep the downloadable manual up to date, that it' Before upgrading Prawn on one of your projects, you should read our [API compatibility](https://github.com/prawnpdf/prawn/wiki/API-Compatibility-Notes) -guidelines. Generally speaking, you can expect tiny version updates to always be -safe upgrades, but minor and major updates can introduce incompatibilities. +guidelines. Generally speaking, you can expect tiny and minor version updates to always be +safe upgrades, but major updates can introduce incompatibilities. Be sure to read the release notes in CHANGELOG.md each time we cut a new release, and lock your gems accordingly. diff --git a/Rakefile b/Rakefile index d445241cc..5ffbec694 100644 --- a/Rakefile +++ b/Rakefile @@ -52,4 +52,4 @@ task :console do IRB.start end -Rubocop::RakeTask.new +RuboCop::RakeTask.new diff --git a/data/encodings/win_ansi.txt b/data/encodings/win_ansi.txt deleted file mode 100644 index 39ab7f4e3..000000000 --- a/data/encodings/win_ansi.txt +++ /dev/null @@ -1,29 +0,0 @@ -# A mapping of WinAnsi (win-1252) characters to unicode. Anything -# not specified is left unchanged -80;20AC -82;201A -83;0192 -84;201E -85;2026 -86;2020 -87;2021 -88;02C6 -89;2030 -8A;0160 -8B;2039 -8C;0152 -8E;017D -91;2018 -92;2019 -93;201C -94;201D -95;2022 -96;2013 -97;2014 -98;02DC -99;2122 -9A;0161 -9B;203A -9C;0153 -9E;017E -9F;0178 diff --git a/data/fonts/DejaVuSans-Bold.ttf b/data/fonts/DejaVuSans-Bold.ttf new file mode 100644 index 000000000..08695f23a Binary files /dev/null and b/data/fonts/DejaVuSans-Bold.ttf differ diff --git a/data/fonts/DejaVuSans.ttf b/data/fonts/DejaVuSans.ttf index 0f33b2bf8..9d40c3256 100644 Binary files a/data/fonts/DejaVuSans.ttf and b/data/fonts/DejaVuSans.ttf differ diff --git a/data/images/pal_bk.png b/data/images/pal_bk.png new file mode 100644 index 000000000..1923b0381 Binary files /dev/null and b/data/images/pal_bk.png differ diff --git a/lib/prawn.rb b/lib/prawn.rb index 6097d4cce..8d307c215 100644 --- a/lib/prawn.rb +++ b/lib/prawn.rb @@ -78,7 +78,6 @@ def configuration(*args) require_relative "prawn/security" require_relative "prawn/document" require_relative "prawn/font" -require_relative "prawn/encoding" require_relative "prawn/measurements" require_relative "prawn/repeater" require_relative "prawn/outline" diff --git a/lib/prawn/document.rb b/lib/prawn/document.rb index 2af991965..7f48d982c 100644 --- a/lib/prawn/document.rb +++ b/lib/prawn/document.rb @@ -66,7 +66,7 @@ class Document VALID_OPTIONS = [:page_size, :page_layout, :margin, :left_margin, :right_margin, :top_margin, :bottom_margin, :skip_page_creation, - :compress, :skip_encoding, :background, :info, + :compress, :background, :info, :text_formatter, :print_scaling] # Any module added to this array will be included into instances of @@ -243,7 +243,7 @@ def start_new_page(options = {}) if last_page = state.page last_page_size = last_page.size last_page_layout = last_page.layout - last_page_margins = last_page.margins + last_page_margins = last_page.margins.dup end page_options = {:size => options[:size] || last_page_size, diff --git a/lib/prawn/document/internals.rb b/lib/prawn/document/internals.rb index c6374afd5..3db7dc946 100644 --- a/lib/prawn/document/internals.rb +++ b/lib/prawn/document/internals.rb @@ -26,7 +26,8 @@ module Internals # Anyway, for now it's not clear what we should do w. them. delegate [ :graphic_state, :save_graphics_state, - :restore_graphics_state ] => :renderer + :restore_graphics_state, + :on_page_create ] => :renderer # FIXME: This is a circular reference, because in theory Prawn should # be passing instances of renderer to PDF::Core::Page, but it's diff --git a/lib/prawn/encoding.rb b/lib/prawn/encoding.rb index 8928a146c..8d85b8ed3 100644 --- a/lib/prawn/encoding.rb +++ b/lib/prawn/encoding.rb @@ -82,39 +82,6 @@ class WinAnsi #:nodoc: oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis ] - - def initialize - @mapping_file = "#{Prawn::DATADIR}/encodings/win_ansi.txt" - load_mapping if self.class.mapping.empty? - end - - # Converts a Unicode codepoint into a valid WinAnsi single byte character. - # - # If there is no WinAnsi equivlant for a character, a _ will be substituted. - # - def [](codepoint) - # unicode codepoints < 255 map directly to the single byte value in WinAnsi - return codepoint if codepoint <= 255 - - # There are a handful of codepoints > 255 that have equivilants in WinAnsi. - # Replace anything else with an underscore - self.class.mapping[codepoint] || 95 - end - - def self.mapping - @mapping ||= {} - end - - private - - def load_mapping - File.open(@mapping_file, "r:BINARY") do |f| - f.each do |l| - _, single_byte, unicode = *l.match(/([0-9A-Za-z]+);([0-9A-F]{4})/) - self.class.mapping["0x#{unicode}".hex] = "0x#{single_byte}".hex if single_byte - end - end - end end end end diff --git a/lib/prawn/font/afm.rb b/lib/prawn/font/afm.rb index 408941238..213c987fb 100644 --- a/lib/prawn/font/afm.rb +++ b/lib/prawn/font/afm.rb @@ -6,7 +6,7 @@ # # This is free software. Please see the LICENSE and COPYING files for details. -require_relative '../../prawn/encoding' +require_relative "../encoding" module Prawn class Font @@ -14,6 +14,12 @@ class Font # @private class AFM < Font + class << self + attr_accessor :hide_m17n_warning + end + + self.hide_m17n_warning = false + BUILT_INS = %w[ Courier Helvetica Times-Roman Symbol ZapfDingbats Courier-Bold Courier-Oblique Courier-BoldOblique Times-Bold Times-Italic Times-BoldItalic @@ -44,7 +50,6 @@ def initialize(document, name, options={}) #:nodoc: super - @@winansi ||= Prawn::Encoding::WinAnsi.new # parse data/encodings/win_ansi.txt once only @@font_data ||= SynchronizedCache.new # parse each ATM font file once only file_name = @name.dup @@ -94,11 +99,17 @@ def has_kerning_data? # is replaced with a string in WinAnsi encoding. # def normalize_encoding(text) - enc = @@winansi - text.unpack("U*").collect { |i| enc[i] }.pack("C*") - rescue ArgumentError + text.encode("windows-1252") + rescue ::Encoding::InvalidByteSequenceError, + ::Encoding::UndefinedConversionError + raise Prawn::Errors::IncompatibleStringEncoding, - "Arguments to text methods must be UTF-8 encoded" + "Your document includes text that's not compatible with the Windows-1252 character set.\n"+ + "If you need full UTF-8 support, use TTF fonts instead of PDF's built-in fonts\n." + end + + def to_utf8(text) + text.encode("UTF-8") end # Returns the number of characters in +str+ (a WinAnsi-encoded string). @@ -124,11 +135,9 @@ def encode_text(text, options={}) end def glyph_present?(char) - if char == "_" - true - else - normalize_encoding(char) != "_" - end + !!normalize_encoding(char) + rescue Prawn::Errors::IncompatibleStringEncoding + false end private diff --git a/lib/prawn/font/ttf.rb b/lib/prawn/font/ttf.rb index 96f643e7b..3971b4dc4 100644 --- a/lib/prawn/font/ttf.rb +++ b/lib/prawn/font/ttf.rb @@ -173,6 +173,10 @@ def normalize_encoding(text) end end + def to_utf8(text) + text.encode("UTF-8") + end + def glyph_present?(char) code = char.codepoints.first cmap[code] > 0 diff --git a/lib/prawn/font_metric_cache.rb b/lib/prawn/font_metric_cache.rb index 684999c1d..4c9085887 100644 --- a/lib/prawn/font_metric_cache.rb +++ b/lib/prawn/font_metric_cache.rb @@ -26,8 +26,7 @@ def initialize( document ) def width_of( string, options ) f = if options[:style] # override style with :style => :bold - @document.find_font(@document.font ? @document.font.name : 'Helvetica', - :style => options[:style]) + @document.find_font(@document.font.family, :style => options[:style]) else @document.font end diff --git a/lib/prawn/graphics.rb b/lib/prawn/graphics.rb index dbecca266..e22eeab19 100644 --- a/lib/prawn/graphics.rb +++ b/lib/prawn/graphics.rb @@ -46,8 +46,8 @@ module Graphics # pdf.move_to(100,50) # def move_to(*point) - x,y = map_to_absolute(point) - renderer.add_content("%.3f %.3f m" % [ x, y ]) + xy = PDF::Core.real_params(map_to_absolute(point)) + renderer.add_content("#{xy} m") end # Draws a line from the current drawing position to the specified point. @@ -57,8 +57,8 @@ def move_to(*point) # pdf.line_to(50,50) # def line_to(*point) - x,y = map_to_absolute(point) - renderer.add_content("%.3f %.3f l" % [ x, y ]) + xy = PDF::Core.real_params(map_to_absolute(point)) + renderer.add_content("#{xy} l") end # Draws a Bezier curve from the current drawing position to the @@ -71,9 +71,10 @@ def curve_to(dest,options={}) "Bounding points for bezier curve must be specified "+ "as :bounds => [[x1,y1],[x2,y2]]" - curve_points = (options[:bounds] << dest).map { |e| map_to_absolute(e) } - renderer.add_content("%.3f %.3f %.3f %.3f %.3f %.3f c" % - curve_points.flatten ) + curve_points = PDF::Core.real_params( + (options[:bounds] << dest).flat_map { |e| map_to_absolute(e) }) + + renderer.add_content("#{curve_points} c") end # Draws a rectangle given point, width and @@ -83,7 +84,9 @@ def curve_to(dest,options={}) # def rectangle(point,width,height) x,y = map_to_absolute(point) - renderer.add_content("%.3f %.3f %.3f %.3f re" % [ x, y - height, width, height ]) + box = PDF::Core.real_params([x, y - height, width, height]) + + renderer.add_content("#{box} re") end # Draws a rounded rectangle given point, width and diff --git a/lib/prawn/graphics/dash.rb b/lib/prawn/graphics/dash.rb index ba79a3e49..21b2ee3e2 100644 --- a/lib/prawn/graphics/dash.rb +++ b/lib/prawn/graphics/dash.rb @@ -55,6 +55,11 @@ module Dash def dash(length=nil, options={}) return current_dash_state if length.nil? + if length == 0 || length.kind_of?(Array) && length.any? { |e| e == 0 } + raise ArgumentError, + "Zero length dashes are invalid. Call #undash to disable dashes." + end + self.current_dash_state = { :dash => length, :space => length.kind_of?(Array) ? nil : options[:space] || length, :phase => options[:phase] || 0 } diff --git a/lib/prawn/images.rb b/lib/prawn/images.rb index 3c6a8924e..cad2a79da 100644 --- a/lib/prawn/images.rb +++ b/lib/prawn/images.rb @@ -121,9 +121,8 @@ def embed_image(pdf_obj, info, options) label = "I#{next_image_id}" state.page.xobjects.merge!(label => pdf_obj) - # add the image to the current page - instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ" - renderer.add_content instruct % [ w, h, x, y - h, label ] + cm_params = PDF::Core.real_params([ w, 0, 0, h, x, y - h]) + renderer.add_content("\nq\n#{cm_params} cm\n/#{label} Do\nQ") end private diff --git a/lib/prawn/images/png.rb b/lib/prawn/images/png.rb index 3e66e2ed4..ad112ed31 100644 --- a/lib/prawn/images/png.rb +++ b/lib/prawn/images/png.rb @@ -68,13 +68,9 @@ def initialize(data) @transparency = {} case @color_type when 3 - # Indexed colour, RGB. Each byte in this chunk is an alpha for - # the palette index in the PLTE ("palette") chunk up until the - # last non-opaque entry. Set up an array, stretching over all - # palette entries which will be 0 (opaque) or 1 (transparent). - @transparency[:indexed] = data.read(chunk_size).unpack("C*") - short = 255 - @transparency[:indexed].size - @transparency[:indexed] += ([255] * short) if short > 0 + raise Errors::UnsupportedImageType, + "Pallete-based transparency in PNG is not currently supported.\n" + + "See https://github.com/prawnpdf/prawn/issues/783" when 0 # Greyscale. Corresponding to entries in the PLTE chunk. # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1 @@ -208,11 +204,6 @@ def build_pdf_object(document) # components. rgb = transparency[:rgb] obj.data[:Mask] = rgb.collect { |x| [x,x] }.flatten - elsif transparency[:indexed] - # TODO: broken. I was attempting to us Color Key Masking, but I think - # we need to construct an SMask i think. Maybe do it inside - # the PNG class, and store it in alpha_channel - #obj.data[:Mask] = transparency[:indexed] end # For PNG color types 4 and 6, the transparency data is stored as a alpha diff --git a/lib/prawn/text.rb b/lib/prawn/text.rb index b33effb3f..940dbf91b 100644 --- a/lib/prawn/text.rb +++ b/lib/prawn/text.rb @@ -116,7 +116,7 @@ module Text # the current font familly. [current style] # :indent_paragraphs:: number. The amount to indent the # first line of each paragraph. Omit this - # option if you do not want indenting + # option if you do not want indenting. # :direction:: # :ltr, :rtl, Direction of the text (left-to-right # or right-to-left) [value of document.text_direction] @@ -199,17 +199,13 @@ def formatted_text(array, options={}) if @indent_paragraphs self.text_formatter.array_paragraphs(array).each do |paragraph| - options[:skip_encoding] = false remaining_text = draw_indented_formatted_line(paragraph, options) - options[:skip_encoding] = true if @no_text_printed # unless this paragraph was an empty line unless @all_text_printed @bounding_box.move_past_bottom - options[:skip_encoding] = false remaining_text = draw_indented_formatted_line(paragraph, options) - options[:skip_encoding] = true end end @@ -218,7 +214,6 @@ def formatted_text(array, options={}) end else remaining_text = fill_formatted_text_box(array, options) - options[:skip_encoding] = true draw_remaining_formatted_text_on_new_pages(remaining_text, options) end end @@ -292,6 +287,16 @@ def draw_text(text, options) # should already be set # def draw_text!(text, options) + unless font.unicode? || font.class.hide_m17n_warning || text.ascii_only? + warn "PDF's built-in fonts have very limited support for "+ + "internationalized text.\nIf you need full UTF-8 support, "+ + "consider using a TTF font instead.\n\nTo disable this "+ + "warning, add the following line to your code:\n"+ + "Prawn::Font::AFM.hide_m17n_warning = true\n" + + font.class.hide_m17n_warning = true + end + x,y = map_to_absolute(options[:at]) add_text_content(text,x,y,options) end @@ -354,7 +359,10 @@ def draw_remaining_formatted_text_on_new_pages(remaining_text, options) end def draw_indented_formatted_line(string, options) - indent(@indent_paragraphs) do + gap = options.fetch(:direction, text_direction) == :ltr ? + [@indent_paragraphs, 0] : [0, @indent_paragraphs] + + indent(*gap) do fill_formatted_text_box(string, options.dup.merge(:single_line => true)) end end diff --git a/lib/prawn/text/box.rb b/lib/prawn/text/box.rb index d4f83263b..7bba83672 100644 --- a/lib/prawn/text/box.rb +++ b/lib/prawn/text/box.rb @@ -72,7 +72,6 @@ module Text # :valign:: # :top, :center, or :bottom. Vertical # alignment within the bounding box [:top] - # # :rotate:: # number. The angle to rotate the text # :rotate_around:: @@ -84,8 +83,6 @@ module Text # document.default_leading] # :single_line:: # boolean. If true, then only the first line will be drawn [false] - # :skip_encoding:: - # boolean [false] # :overflow:: # :truncate, :shrink_to_fit, or :expand # This controls the behavior when the amount of text @@ -99,14 +96,9 @@ module Text # # Returns any text that did not print under the current settings. # - # NOTE: if an AFM font is used, then the returned text is encoded in - # WinAnsi. Subsequent calls to text_box that pass this returned text back - # into text box must include a :skip_encoding => true option. This is - # unnecessary when using TTF fonts because those operate on UTF-8 encoding. - # # == Exceptions # - # Raises Prawn::Errrors::CannotFit if not wide enough to print + # Raises Prawn::Errors::CannotFit if not wide enough to print # any text # def text_box(string, options={}) diff --git a/lib/prawn/text/formatted/box.rb b/lib/prawn/text/formatted/box.rb index 7b4238668..5c6cecd67 100644 --- a/lib/prawn/text/formatted/box.rb +++ b/lib/prawn/text/formatted/box.rb @@ -85,7 +85,7 @@ module Formatted # # Raises "Bad font family" if no font family is defined for the current font # - # Raises Prawn::Errrors::CannotFit if not wide enough to print + # Raises Prawn::Errors::CannotFit if not wide enough to print # any text # def formatted_text_box(array, options={}) @@ -168,7 +168,6 @@ def initialize(formatted_text, options={}) @rotate = options[:rotate] || 0 @rotate_around = options[:rotate_around] || :upper_left @single_line = options[:single_line] - @skip_encoding = options[:skip_encoding] || @document.skip_encoding @draw_text_callback = options[:draw_text_callback] # if the text rendering mode is :unknown, force it back to :fill @@ -229,7 +228,9 @@ def render(flags={}) end end - unprinted_text + unprinted_text.map do |e| + e.merge(:text => @document.font.to_utf8(e[:text])) + end end # The width available at this point in the box @@ -335,7 +336,6 @@ def valid_options :disable_wrap_by_char, :leading, :character_spacing, :mode, :single_line, - :skip_encoding, :document, :direction, :fallback_fonts, @@ -345,11 +345,7 @@ def valid_options private def normalized_text(flags) - if @skip_encoding - text = original_text - else - text = normalize_encoding - end + text = normalize_encoding text.each { |t| t.delete(:color) } if flags[:dry_run] @@ -484,14 +480,16 @@ def process_vertical_alignment(text) @vertical_alignment_processed = true return if @vertical_align == :top + wrap(text) case @vertical_align when :center - @at[1] = @at[1] - (@height - height) * 0.5 + @at[1] -= (@height - height + @descender) * 0.5 when :bottom - @at[1] = @at[1] - (@height - height) + @descender + @at[1] -= (@height - height) end + @height = height end diff --git a/lib/prawn/text/formatted/line_wrap.rb b/lib/prawn/text/formatted/line_wrap.rb index d4e986f1e..18ff423a9 100644 --- a/lib/prawn/text/formatted/line_wrap.rb +++ b/lib/prawn/text/formatted/line_wrap.rb @@ -102,6 +102,10 @@ def add_fragment_to_line(fragment) if @accumulated_width + segment_width <= @width @accumulated_width += segment_width + if segment[-1] == soft_hyphen + sh_width = @document.width_of("#{soft_hyphen}", :kerning => @kerning) + @accumulated_width -= sh_width + end @fragment_output += segment else end_of_the_line_reached(segment) @@ -124,6 +128,7 @@ def scan_pattern "[#{whitespace}]+|" + "#{hyphen}+[^#{break_chars}]*|" + "#{soft_hyphen}" + Regexp.new(pattern) end diff --git a/lib/prawn/version.rb b/lib/prawn/version.rb index 205864281..cd7e33ac1 100644 --- a/lib/prawn/version.rb +++ b/lib/prawn/version.rb @@ -1,5 +1,5 @@ # encoding: utf-8 module Prawn - VERSION = "1.3.1" + VERSION = "2.0.0" end diff --git a/manual/basic_concepts/measurement.rb b/manual/basic_concepts/measurement.rb index 9bd5e7a6b..88e4c666f 100644 --- a/manual/basic_concepts/measurement.rb +++ b/manual/basic_concepts/measurement.rb @@ -3,7 +3,7 @@ # The base unit in Prawn is the PDF Point. One PDF Point is equal to 1/72 of # an inch. # -# There is no need to waste time converting this measures. Prawn provides +# There is no need to waste time converting this measure. Prawn provides # helpers for converting from other measurements # to PDF Points. # diff --git a/manual/bounding_box/bounds.rb b/manual/bounding_box/bounds.rb index 61c274344..0261d0b4b 100644 --- a/manual/bounding_box/bounds.rb +++ b/manual/bounding_box/bounds.rb @@ -1,7 +1,7 @@ # encoding: utf-8 # # The bounds method returns the current bounding box. This is -# useful because the Prawn::BoundinBox exposes some nice boundary +# useful because the Prawn::BoundingBox exposes some nice boundary # helpers. # # top, bottom, left and diff --git a/manual/cover.rb b/manual/cover.rb index fc469cbc2..91274b505 100644 --- a/manual/cover.rb +++ b/manual/cover.rb @@ -24,11 +24,8 @@ ], :at => [170, cursor - 160]) if Dir.exist?("#{Prawn::BASEDIR}/.git") - #long git commit hash - #commit = `git show --pretty=%H` - #short git commit hash commit = `git show --pretty=%h` - git_commit = "git commit: #{commit}" + git_commit = "git commit: #{commit.lines.first}" else git_commit = "" end diff --git a/manual/example_helper.rb b/manual/example_helper.rb index 21601d079..530e013fe 100644 --- a/manual/example_helper.rb +++ b/manual/example_helper.rb @@ -4,3 +4,4 @@ require "prawn/manual_builder" Prawn::ManualBuilder.manual_dir = File.dirname(__FILE__) +Prawn::Font::AFM.hide_m17n_warning = true diff --git a/manual/repeatable_content/alternate_page_numbering.rb b/manual/repeatable_content/alternate_page_numbering.rb new file mode 100644 index 000000000..baa6a0edf --- /dev/null +++ b/manual/repeatable_content/alternate_page_numbering.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 +# +# Below is the code to generate page numbers that alternate being rendered +# on the right and left side of the page. The first page will have a "1" in +# the bottom right corner. The second page will have a "2" in the bottom +# left corner of the page. The third a "3" in the bottom right, etc. +require File.expand_path(File.join(File.dirname(__FILE__), + %w[.. example_helper])) + +filename = File.basename(__FILE__).gsub('.rb', '.pdf') +Prawn::ManualBuilder::Example.generate(filename) do + text "This is the first page!" + + 10.times do + start_new_page + text "Here comes yet another page." + end + + string = "" + odd_options = { :at => [bounds.right - 150, 0], + :width => 150, + :align => :right, + :page_filter => :odd, + :start_count_at => 1 } + even_options = { :at => [0, bounds.left], + :width => 150, + :align => :left, + :page_filter => :even, + :start_count_at => 2 } + number_pages string, odd_options + number_pages string, even_options +end diff --git a/manual/repeatable_content/repeatable_content.rb b/manual/repeatable_content/repeatable_content.rb index 0c0ec645b..70c9c8152 100644 --- a/manual/repeatable_content/repeatable_content.rb +++ b/manual/repeatable_content/repeatable_content.rb @@ -12,6 +12,7 @@ p.example "repeater", :eval_source => false p.example "stamp" p.example "page_numbering", :eval_source => false + p.example "alternate_page_numbering", :eval_source => false p.intro do prose("Prawn offers two ways to handle repeatable content blocks. Repeater is useful for content that gets repeated at well defined intervals while Stamp is more appropriate if you need better control of when to repeat it. diff --git a/manual/security/security.rb b/manual/security/security.rb index 460f0661b..ab82dabe4 100644 --- a/manual/security/security.rb +++ b/manual/security/security.rb @@ -18,7 +18,7 @@ The examples include:") list( "How to encrypt the document without the need for a password", - "How to configure the regular user permitions", + "How to configure the regular user permissions", "How to require a password for the regular user", "How to set a owner password that bypass the document permissions" ) diff --git a/manual/text/paragraph_indentation.rb b/manual/text/paragraph_indentation.rb index 31042116a..783a6bb61 100644 --- a/manual/text/paragraph_indentation.rb +++ b/manual/text/paragraph_indentation.rb @@ -23,4 +23,12 @@ text "This paragraph will be indented. " * 10 + "\n" + "This one will too. " * 10, :indent_paragraphs => 60 + + move_down 20 + + text "FROM RIGHT TO LEFT:" + text "This paragraph will be indented. " * 10 + + "\n" + "This one will too. " * 10, + :indent_paragraphs => 60, :direction => :rtl + end diff --git a/manual/text/right_to_left_text.rb b/manual/text/right_to_left_text.rb index 5056d498a..fe1181532 100644 --- a/manual/text/right_to_left_text.rb +++ b/manual/text/right_to_left_text.rb @@ -9,6 +9,10 @@ # that two fragments going against the main direction cannot be placed next to # each other without appearing in the wrong order. # +# Writing bidirectional text that combines both left-to-right and right-to-left +# languages is easy using the bidi Ruby Gem and its +# render_visual function. See https://github.com/elad/ruby-bidi for +# instructions and an example using Prawn. require File.expand_path(File.join(File.dirname(__FILE__), %w[.. example_helper])) diff --git a/manual/text/text.rb b/manual/text/text.rb index a1704e134..e31a7e3f1 100644 --- a/manual/text/text.rb +++ b/manual/text/text.rb @@ -42,7 +42,7 @@ s.example "registering_families" end - p.section "M17n" do |s| + p.section "Multilingualization" do |s| s.example "utf8" s.example "line_wrapping" s.example "right_to_left_text" diff --git a/manual/text/utf8.rb b/manual/text/utf8.rb index 88306918c..bde001613 100644 --- a/manual/text/utf8.rb +++ b/manual/text/utf8.rb @@ -13,12 +13,12 @@ text "€", :size => 32 move_down 20 - text "Seems ok. Now let's try something more complex:" - text "ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει." + text "This works, because € is one of the few "+ + "non-ASCII glyphs supported in PDF built-in fonts." + move_down 20 - text "Looks like the current font (#{font.inspect}) doesn't support those." - text "Let's try them with another font." + text "For full internationalized text support, we need to use TTF fonts:" move_down 20 font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf") do diff --git a/manual/text/win_ansi_charset.rb b/manual/text/win_ansi_charset.rb index 0f4f3def2..6c9728841 100644 --- a/manual/text/win_ansi_charset.rb +++ b/manual/text/win_ansi_charset.rb @@ -50,7 +50,8 @@ when :center then offset = (total_width - width)/2 end - text_box(field, :at => [dx + offset, y], :skip_encoding => true) + text_box(field.force_encoding("windows-1252").encode("UTF-8"), + :at => [dx + offset, y]) end dx += total_width diff --git a/prawn.gemspec b/prawn.gemspec index 5e0e17fde..728918c1f 100644 --- a/prawn.gemspec +++ b/prawn.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| "COPYING", "LICENSE", "GPLv2", "GPLv3", ".yardopts"] spec.require_path = "lib" - spec.required_ruby_version = '>= 1.9.3' + spec.required_ruby_version = '>= 2.0.0' spec.required_rubygems_version = ">= 1.3.6" spec.test_files = Dir[ "spec/*_spec.rb" ] @@ -25,9 +25,9 @@ Gem::Specification.new do |spec| spec.licenses = ['RUBY', 'GPL-2', 'GPL-3'] spec.add_dependency('ttfunk', '~> 1.4.0') - spec.add_dependency('pdf-core', "~> 0.4.0") + spec.add_dependency('pdf-core', "~> 0.5.0") - spec.add_development_dependency('pdf-inspector', '~> 1.1.0') + spec.add_development_dependency('pdf-inspector', '~> 1.2.0') spec.add_development_dependency('yard') spec.add_development_dependency('rspec', '2.14.1') spec.add_development_dependency('mocha') @@ -35,7 +35,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency('simplecov') spec.add_development_dependency('prawn-manual_builder', ">= 0.2.0") spec.add_development_dependency('pdf-reader', '~>1.2') - spec.add_development_dependency('rubocop', '0.20.1') + spec.add_development_dependency('rubocop', '0.28.0') spec.add_development_dependency('code_statistics', '0.2.13') spec.homepage = "http://prawn.majesticseacreature.com" diff --git a/spec/document_spec.rb b/spec/document_spec.rb index 95fca5d4f..74c758d27 100644 --- a/spec/document_spec.rb +++ b/spec/document_spec.rb @@ -180,6 +180,10 @@ def self.format(string) create_pdf end + it "should be delegated from Document to renderer" do + expect(@pdf.respond_to?(:on_page_create)).to be_true + end + it "should be invoked with document" do called_with = nil @@ -304,14 +308,33 @@ def self.format(string) pages[2][:strings].should == ["Old page 2"] end - it "should update the bounding box to the new page's margin box" do + it "should restore the layout of the page" do Prawn::Document.new do start_new_page :layout => :landscape lsize = [bounds.width, bounds.height] + + [bounds.width, bounds.height].should == lsize go_to_page 1 [bounds.width, bounds.height].should == lsize.reverse end end + + it "should restore the margin box of the page" do + Prawn::Document.new(:margin => [100, 100]) do + page1_bounds = bounds + + start_new_page(:margin => [200, 200]) + + [bounds.width, bounds.height].should == [page1_bounds.width - 200, + page1_bounds.height - 200] + + go_to_page(1) + + bounds.width.should == page1_bounds.width + bounds.height.should == page1_bounds.height + end + + end end describe "When setting page size" do diff --git a/spec/font_spec.rb b/spec/font_spec.rb index 6feb79c5f..26417fac0 100644 --- a/spec/font_spec.rb +++ b/spec/font_spec.rb @@ -80,6 +80,31 @@ styled_bold_hello.should == @bold_hello end + it "should calculate styled widths correctly using TTFs" do + create_pdf + + @pdf.font_families.update( + 'DejaVu Sans' => { + :normal => "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf", + :bold => "#{Prawn::DATADIR}/fonts/DejaVuSans-Bold.ttf", + } + ) + @pdf.font("DejaVu Sans") { + @styled_bold_hello = @pdf.width_of("hello", :style => :bold) + } + @pdf.font("DejaVu Sans", :style => :bold) { + @bold_hello = @pdf.width_of("hello") + } + + @pdf.font("DejaVu Sans") { + @plain_hello = @pdf.width_of("hello") + } + + @plain_hello.should_not == @bold_hello + + @styled_bold_hello.should == @bold_hello + end + it "should not treat minus as if it were a hyphen", :issue => 578 do create_pdf diff --git a/spec/formatted_text_box_spec.rb b/spec/formatted_text_box_spec.rb index 157d9405c..1bc7b01ac 100644 --- a/spec/formatted_text_box_spec.rb +++ b/spec/formatted_text_box_spec.rb @@ -18,9 +18,9 @@ text_box.text.should == "Hello\nWorld2" end - it "should not raise an Encoding::CompatibilityError when keeping a TTF and an " + - "AFM font together" do + it "should not raise an Encoding::CompatibilityError when keeping a TTF and an AFM font together" do file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf" + @pdf.font_families["Kai"] = { :normal => { :file => file, :font => "Kai" } } @@ -81,28 +81,6 @@ }.should_not raise_error text_box.text.should == "Noua Delineatio Geographica\ngeneralis | Apostolicarum\nperegrinationum | S FRANCISCI\nXAUERII | Indiarum & Iaponi\346\nApostoli" end - - describe "Unicode" do - before do - @reset_value = [Encoding.default_external, Encoding.default_internal] - Encoding.default_external = Encoding::UTF_8 - Encoding.default_internal = Encoding::UTF_8 - end - - after do - Encoding.default_external = @reset_value[0] - Encoding.default_internal = @reset_value[1] - end - - it "should properly handle empty slices using Unicode encoding" do - texts = [{ :text => "Noua Delineatio Geographica generalis | Apostolicarum peregrinationum | S FRANCISCI XAUERII | Indiarum & Iaponiæ Apostoli", :font => 'Courier', :size => 10 }] - text_box = Prawn::Text::Formatted::Box.new(texts, :document => @pdf, :width => @pdf.width_of("Noua Delineatio Geographica gen")) - lambda { - text_box.render - }.should_not raise_error - text_box.text.should == "Noua Delineatio Geographica\ngeneralis | Apostolicarum\nperegrinationum | S FRANCISCI\nXAUERII | Indiarum & Iaponi\346\nApostoli" - end - end end describe "Text::Formatted::Box with :fallback_fonts option that includes" + @@ -170,6 +148,12 @@ @pdf.font_families["Kai"] = { :normal => { :file => file, :font => "Kai" } } + + file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf" + @pdf.font_families["DejaVu Sans"] = { + :normal => { :file => file } + } + formatted_text = [{ :text => "hello你好" }, { :text => "再见goodbye", :font => "Times-Roman" }] @pdf.formatted_text_box(formatted_text, :fallback_fonts => ["Kai"]) @@ -197,6 +181,13 @@ @pdf.font_families["Kai"] = { :normal => { :file => file, :font => "Kai" } } + + file = "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf" + @pdf.font_families["DejaVu Sans"] = { + :normal => { :file => file } + } + + @formatted_text = [{ :text => "hello你好" }] @pdf.fallback_fonts(["Kai"]) @pdf.fallback_fonts = ["Kai"] @@ -215,26 +206,35 @@ fonts_used[1].to_s.should =~ /GBZenKai-Medium/ end it "should be able to override document-wide fallback_fonts" do - @pdf.formatted_text_box(@formatted_text, :fallback_fonts => ["Courier"]) + @pdf.fallback_fonts = ["DejaVu Sans"] + @pdf.formatted_text_box(@formatted_text, :fallback_fonts => ["Kai"]) text = PDF::Inspector::Text.analyze(@pdf.render) fonts_used = text.font_settings.map { |e| e[:name] } - fonts_used.length.should == 1 + fonts_used.length.should == 2 fonts_used[0].should == :"Helvetica" + fonts_used[1].should =~ /Kai/ end it "should omit the fallback fonts overhead when passing an empty array " + "as the :fallback_fonts" do + @pdf.font("Kai") + box = Prawn::Text::Formatted::Box.new(@formatted_text, :document => @pdf, :fallback_fonts => []) + box.expects(:process_fallback_fonts).never box.render end + it "should be able to clear document-wide fallback_fonts" do @pdf.fallback_fonts([]) box = Prawn::Text::Formatted::Box.new(@formatted_text, :document => @pdf) + + @pdf.font("Kai") + box.expects(:process_fallback_fonts).never box.render end @@ -242,16 +242,15 @@ describe "Text::Formatted::Box with :fallback_fonts option " + "with glyphs not in the primary or the fallback fonts" do - it "should use the primary font" do + + it "should raise an exception" do + file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf" create_pdf formatted_text = [{ :text => "hello world. 世界你好。" }] - @pdf.formatted_text_box(formatted_text, :fallback_fonts => ["Helvetica"]) - text = PDF::Inspector::Text.analyze(@pdf.render) - - fonts_used = text.font_settings.map { |e| e[:name] } - fonts_used.length.should == 1 - fonts_used[0].should == :"Helvetica" + lambda { + @pdf.formatted_text_box(formatted_text, :fallback_fonts => ["Courier"]) + }.should raise_error(Prawn::Errors::IncompatibleStringEncoding) end end @@ -616,6 +615,51 @@ end end +describe "Text::Formatted::Box#render with :valign => :center" do + it "should have a bottom gap equal to baseline and bottom of box" do + create_pdf + box_height = 100 + y = 450 + array = [{ :text => 'Vertical Align' }] + options = { + :document => @pdf, + :valign => :center, + :at => [0,y], + :width => 100, + :height => box_height, + :size => 16 + } + text_box = Prawn::Text::Formatted::Box.new(array, options) + text_box.render + line_padding = (box_height - text_box.height + text_box.descender) * 0.5 + baseline = y - line_padding + + text_box.at[1].should be_within(0.01).of(baseline) + end +end + +describe "Text::Formatted::Box#render with :valign => :bottom" do + it "should not render a gap between the text and bottom of box" do + create_pdf + box_height = 100 + y = 450 + array = [{ :text => 'Vertical Align' }] + options = { + :document => @pdf, + :valign => :bottom, + :at => [0,y], + :width => 100, + :height => box_height, + :size => 16 + } + text_box = Prawn::Text::Formatted::Box.new(array, options) + text_box.render + top_padding = y - (box_height - text_box.height) + + text_box.at[1].should be_within(0.01).of(top_padding) + end +end + class TestFragmentCallback def initialize(string, number, options) @document = options[:document] diff --git a/spec/graphics_spec.rb b/spec/graphics_spec.rb index e2603a9d7..7b862c3cc 100644 --- a/spec/graphics_spec.rb +++ b/spec/graphics_spec.rb @@ -143,10 +143,12 @@ end it "should draw a rectangle by connecting lines with rounded bezier curves" do - @all_coords.should == [[60.0, 550.0],[90.0, 550.0], [95.523, 550.0], [100.0, 545.523], [100.0, 540.0], - [100.0, 460.0], [100.0, 454.477], [95.523, 450.0], [90.0, 450.0], - [60.0, 450.0], [54.477, 450.0], [50.0, 454.477], [50.0, 460.0], - [50.0, 540.0], [50.0, 545.523], [54.477, 550.0], [60.0, 550.0]] + @all_coords.should == [[60.0, 550.0],[90.0, 550.0], [95.5228, 550.0], + [100.0, 545.5228], [100.0, 540.0], [100.0, 460.0], + [100.0, 454.4772], [95.5228, 450.0], [90.0, 450.0], + [60.0, 450.0], [54.4772, 450.0], [50.0, 454.4772], + [50.0, 460.0], [50.0, 540.0], [50.0, 545.5228], + [54.4772, 550.0], [60.0, 550.0]] end it "should start and end with the same point" do @@ -167,20 +169,20 @@ @curve.coords.should == [125.0, 100.0, - 125.0, 127.614, - 113.807, 150, + 125.0, 127.6142, + 113.8071, 150, 100.0, 150.0, - 86.193, 150.0, - 75.0, 127.614, + 86.1929, 150.0, + 75.0, 127.6142, 75.0, 100.0, - 75.0, 72.386, - 86.193, 50.0, + 75.0, 72.3858, + 86.1929, 50.0, 100.0, 50.0, - 113.807, 50.0, - 125.0, 72.386, + 113.8071, 50.0, + 125.0, 72.3858, 125.0, 100.0, 100.0, 100.0] @@ -427,6 +429,18 @@ @pdf.graphic_state.dash[:dash].should == 5 end + it "should round dash values to four decimal places" do + @pdf.dash 5.12345 + @pdf.graphic_state.dash_setting.should == "[5.1235 5.1235] 0.0 d" + end + + it "should raise an error when dash is called w. a zero length or space" do + expect { @pdf.dash(0) }.to raise_error(ArgumentError) + expect { @pdf.dash([0]) }.to raise_error(ArgumentError) + expect { @pdf.dash([0,0]) }.to raise_error(ArgumentError) + expect { @pdf.dash([0,0,0,1]) }.to raise_error(ArgumentError) + end + it "the current graphic state should keep track of previous unchanged settings" do @pdf.stroke_color '000000' @pdf.save_graphics_state diff --git a/spec/line_wrap_spec.rb b/spec/line_wrap_spec.rb index 484953f30..3e6669706 100644 --- a/spec/line_wrap_spec.rb +++ b/spec/line_wrap_spec.rb @@ -128,21 +128,36 @@ string.should == "hello#{Prawn::Text::SHY}" end + it "should ignore width of a soft-hyphen during adding fragments to line", :issue =>775 do + hyphen_string = "Hy#{Prawn::Text::SHY}phe#{Prawn::Text::SHY}nat#{Prawn::Text::SHY}ions " + string1 = @pdf.font.normalize_encoding(hyphen_string * 5) + string2 = @pdf.font.normalize_encoding("Hyphenations " * 3 + hyphen_string) + + array1 = [{text: string1}] + array2 = [{text: string2}] + + @arranger.format_array = array1 + + res1 = @line_wrap.wrap_line(:arranger => @arranger, + :width => 300, + :document => @pdf) + + @line_wrap = Prawn::Text::Formatted::LineWrap.new + + @arranger.format_array = array2 + + res2 = @line_wrap.wrap_line(:arranger => @arranger, + :width => 300, + :document => @pdf) + res1.should == res2 + end + it "should not display soft hyphens except at the end of a line " + "for more than one element in format_array", :issue => 347 do - string1 = @pdf.font.normalize_encoding("hello#{Prawn::Text::SHY}world ") - string2 = @pdf.font.normalize_encoding("hi#{Prawn::Text::SHY}earth") - array = [{ :text => string1 }, { :text => string2 }] - @arranger.format_array = array - string = @line_wrap.wrap_line(:arranger => @arranger, - :width => 300, - :document => @pdf) - string.should == "helloworld hiearth" - @pdf.font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf") @line_wrap = Prawn::Text::Formatted::LineWrap.new - string1 = "hello#{Prawn::Text::SHY}world " + string1 = @pdf.font.normalize_encoding("hello#{Prawn::Text::SHY}world ") string2 = @pdf.font.normalize_encoding("hi#{Prawn::Text::SHY}earth") array = [{ :text => string1 }, { :text => string2 }] @arranger.format_array = array @@ -349,3 +364,4 @@ @line_wrap.paragraph_finished?.should == true end end + diff --git a/spec/png_spec.rb b/spec/png_spec.rb index e30749b0b..935754554 100644 --- a/spec/png_spec.rb +++ b/spec/png_spec.rb @@ -98,7 +98,15 @@ end end -# TODO: describe "When reading an indexed color PNG file wiih transparency (color type 3)" +describe "When reading an indexed color PNG file "+ + "wiih transparency (color type 3)" do + + it "raises a not supported error" do + bin = File.binread("#{Prawn::DATADIR}/images/pal_bk.png") + expect { Prawn::Images::PNG.new(bin)}.to( + raise_error(Prawn::Errors::UnsupportedImageType)) + end +end describe "When reading an indexed color PNG file (color type 3)" do diff --git a/spec/soft_mask_spec.rb b/spec/soft_mask_spec.rb index 633d6a843..e7b9eb03f 100644 --- a/spec/soft_mask_spec.rb +++ b/spec/soft_mask_spec.rb @@ -57,7 +57,7 @@ def make_soft_mask end extgstate = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates.first - extgstate[:soft_mask][:G].data.should == "q\n/DeviceRGB cs\n0.000 0.000 0.000 scn\n/DeviceRGB CS\n0.000 0.000 0.000 SCN\n1 w\n0 J\n0 j\n[ ] 0 d\n/DeviceRGB cs\n0.502 0.502 0.502 scn\n100.000 -100.000 200.000 200.000 re\nf\nQ\n" + extgstate[:soft_mask][:G].data.should == "q\n/DeviceRGB cs\n0.000 0.000 0.000 scn\n/DeviceRGB CS\n0.000 0.000 0.000 SCN\n1 w\n0 J\n0 j\n[] 0 d\n/DeviceRGB cs\n0.502 0.502 0.502 scn\n100.0 -100.0 200.0 200.0 re\nf\nQ\n" end it "should not create duplicate extended graphics states" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c7a56e143..17dd66176 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,10 +12,10 @@ end end -$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') -require "prawn" +require_relative "../lib/prawn" Prawn.debug = true +Prawn::Font::AFM.hide_m17n_warning = true #require "test/spec" require "rspec" diff --git a/spec/text_at_spec.rb b/spec/text_at_spec.rb index 194fa6acd..834bf64f7 100644 --- a/spec/text_at_spec.rb +++ b/spec/text_at_spec.rb @@ -100,7 +100,6 @@ it "should raise_error an exception when a utf-8 incompatible string is rendered" do str = "Blah \xDD" - str.force_encoding(Encoding::ASCII_8BIT) lambda { @pdf.draw_text(str, :at => [0, 0]) }.should raise_error( Prawn::Errors::IncompatibleStringEncoding) end diff --git a/spec/text_box_spec.rb b/spec/text_box_spec.rb index 279938ea8..bcbe5d518 100644 --- a/spec/text_box_spec.rb +++ b/spec/text_box_spec.rb @@ -664,31 +664,12 @@ remaining_text = @text_box.render remaining_text.should == @text end - - it "subsequent calls to Text::Box need not include the" + - " :skip_encoding => true option" do - @pdf.font("Panic Sans") - remaining_text = @text_box.render - - # expect that calling text_box will not raise an encoding error - @pdf.text_box(remaining_text, :document => @pdf) - end end describe "when using an AFM font" do - it "unprinted text should be in WinAnsi encoding" do - remaining_text = @text_box.render - remaining_text.should == @pdf.font.normalize_encoding(@text) - end - it "subsequent calls to Text::Box must include the" + - " :skip_encoding => true option" do + it "unprinted text should be in UTF-8 encoding" do remaining_text = @text_box.render - lambda { - @pdf.text_box(remaining_text, :document => @pdf) - }.should raise_error(Prawn::Errors::IncompatibleStringEncoding) - - @pdf.text_box(remaining_text, :skip_encoding => true, - :document => @pdf) + remaining_text.should == @text end end end diff --git a/spec/text_spacing_spec.rb b/spec/text_spacing_spec.rb index db1cb3b02..2d034f5fe 100644 --- a/spec/text_spacing_spec.rb +++ b/spec/text_spacing_spec.rb @@ -9,7 +9,7 @@ @pdf.text("hello world") end contents = PDF::Inspector::Text.analyze(@pdf.render) - contents.character_spacing.first.should == 10.556 + contents.character_spacing.first.should == 10.5556 end it "should not draw the character spacing to the document" + " when the new character spacing matches the old" do @@ -63,7 +63,7 @@ @pdf.text("hello world") end contents = PDF::Inspector::Text.analyze(@pdf.render) - contents.word_spacing.first.should == 10.556 + contents.word_spacing.first.should == 10.5556 end it "should draw the word spacing to the document" + " when the new word spacing matches the old" do diff --git a/spec/text_spec.rb b/spec/text_spec.rb index f160946af..d5259f304 100644 --- a/spec/text_spec.rb +++ b/spec/text_spec.rb @@ -315,7 +315,6 @@ it "should raise_error an exception when a utf-8 incompatible string is rendered" do str = "Blah \xDD" - str.force_encoding(Encoding::ASCII_8BIT) lambda { @pdf.text str }.should raise_error( Prawn::Errors::IncompatibleStringEncoding) end @@ -351,6 +350,108 @@ text.strings[3].should == ("hello " * 19).strip text.strings[4].should == ("hello " * 21).strip end + + + it "should indent from right side when using :rtl direction" do + para1 = "The rain in spain falls mainly on the plains " * 3 + para2 = "The rain in spain falls mainly on the plains " * 3 + + @pdf.text(para1 + "\n" + para2, :indent_paragraphs => 60, :direction => :rtl) + + text = PDF::Inspector::Text.analyze(@pdf.render) + + lines = text.strings + x_positions = text.positions.map { |e| e[0] } + + # NOTE: The code below reflects Prawn's current kerning behavior for RTL + # text, which isn't necessarily correct. If we change that behavior, + # this test will need to be updated. + + x_positions[0].should( + be_within(0.001).of(@pdf.bounds.absolute_right - 60 - + @pdf.width_of(lines[0].reverse, :kerning => true))) + + x_positions[1].should( + be_within(0.001).of(@pdf.bounds.absolute_right - + @pdf.width_of(lines[1].reverse, :kerning => true))) + + x_positions[2].should( + be_within(0.001).of(@pdf.bounds.absolute_right - 60 - + @pdf.width_of(lines[2].reverse, :kerning => true))) + + x_positions[3].should( + be_within(0.001).of(@pdf.bounds.absolute_right - + @pdf.width_of(lines[3].reverse, :kerning => true))) + end + + it "should indent from right side when document has :rtl direction" do + para1 = "The rain in spain falls mainly on the plains " * 3 + para2 = "The rain in spain falls mainly on the plains " * 3 + + @pdf.text_direction = :rtl + @pdf.text(para1 + "\n" + para2, :indent_paragraphs => 60) + + text = PDF::Inspector::Text.analyze(@pdf.render) + + lines = text.strings + x_positions = text.positions.map { |e| e[0] } + + # NOTE: The code below reflects Prawn's current kerning behavior for RTL + # text, which isn't necessarily correct. If we change that behavior, + # this test will need to be updated. + + x_positions[0].should( + be_within(0.001).of(@pdf.bounds.absolute_right - 60 - + @pdf.width_of(lines[0].reverse, :kerning => true))) + + x_positions[1].should( + be_within(0.001).of(@pdf.bounds.absolute_right - + @pdf.width_of(lines[1].reverse, :kerning => true))) + + x_positions[2].should( + be_within(0.001).of(@pdf.bounds.absolute_right - 60 - + @pdf.width_of(lines[2].reverse, :kerning => true))) + + x_positions[3].should( + be_within(0.001).of(@pdf.bounds.absolute_right - + @pdf.width_of(lines[3].reverse, :kerning => true))) + end + + it "should indent from left side when using :ltr direction" do + para1 = "The rain in spain falls mainly on the plains " * 3 + para2 = "The rain in spain falls mainly on the plains " * 3 + + @pdf.text(para1 + "\n" + para2, :indent_paragraphs => 60, :direction => :ltr) + + text = PDF::Inspector::Text.analyze(@pdf.render) + + x_positions = text.positions.map { |e| e[0] } + + x_positions[0].should == 60 + x_positions[1].should == 0 + + x_positions[2].should == 60 + x_positions[3].should == 0 + end + + it "should indent from left side when document has :ltr direction" do + para1 = "The rain in spain falls mainly on the plains " * 3 + para2 = "The rain in spain falls mainly on the plains " * 3 + + @pdf.text_direction = :ltr + @pdf.text(para1 + "\n" + para2, :indent_paragraphs => 60) + + text = PDF::Inspector::Text.analyze(@pdf.render) + + x_positions = text.positions.map { |e| e[0] } + + x_positions[0].should == 60 + x_positions[1].should == 0 + + x_positions[2].should == 60 + x_positions[3].should == 0 + end + describe "when wrap to new page, and first line of new page" + " is not the start of a new paragraph, that line should" + " not be indented" do