diff --git a/lib/prawn/graphics.rb b/lib/prawn/graphics.rb index 6a354b5a1..5a4a07b0c 100644 --- a/lib/prawn/graphics.rb +++ b/lib/prawn/graphics.rb @@ -12,7 +12,7 @@ require "prawn/graphics/join_style" require "prawn/graphics/transparency" require "prawn/graphics/transformation" -require "prawn/graphics/gradient" +require "prawn/graphics/patterns" module Prawn @@ -30,7 +30,7 @@ module Graphics include JoinStyle include Transparency include Transformation - include Gradient + include Patterns ####################################################################### # Low level drawing operations must map the point to absolute coords! # diff --git a/lib/prawn/graphics/color.rb b/lib/prawn/graphics/color.rb index d6853fc69..f90104a8b 100644 --- a/lib/prawn/graphics/color.rb +++ b/lib/prawn/graphics/color.rb @@ -95,7 +95,14 @@ def color_type(color) when String :RGB when Array - :CMYK + case color.length + when 3 + :RGB + when 4 + :CMYK + else + raise ArgumentError, "Unknown type of color: #{color.inspect}" + end end end diff --git a/lib/prawn/graphics/gradient.rb b/lib/prawn/graphics/gradient.rb deleted file mode 100644 index 43047c70b..000000000 --- a/lib/prawn/graphics/gradient.rb +++ /dev/null @@ -1,84 +0,0 @@ -# encoding: utf-8 - -# gradient.rb : Implements axial gradient -# -# Contributed by Wojciech Piekutowski. November, 2009 -# -# This is free software. Please see the LICENSE and COPYING files for details. -# -module Prawn - module Graphics - module Gradient - # Sets the fill gradient from color1 to color2. - # - # It accepts CMYK and RGB colors, like #fill_color. Both colors must be - # of the same type. - # - # point, width and height define a bounding box in which the gradient - # will be rendered. For example, if you want to have page full of text - # with gradually changing color: - # - # pdf.fill_gradient [0, pdf.bounds.height], pdf.bounds.width, - # pdf.bounds.height, 'FF0000', '0000FF' - # pdf.text 'lots of text'*1000 - # - # :stroke_bounds - draw gradient bounds - def fill_gradient(point, width, height, color1, color2, options = {}) - set_gradient(:fill, point, width, height, color1, color2, options) - end - - # Sets the stroke gradient from color1 to color2. - # - # See #fill_gradient for details. - def stroke_gradient(point, width, height, color1, color2, options = {}) - set_gradient(:stroke, point, width, height, color1, color2, options) - end - - private - - def set_gradient(type, point, width, height, color1, color2, options) - if options[:stroke_bounds] - stroke_color 0, 0, 0, 100 - stroke_rectangle point, width, height - end - - if color_type(color1) != color_type(color2) - raise ArgumentError, 'both colors must be of the same type: RGB or CMYK' - end - - process_color color1 - process_color color2 - - shader = ref!({ - :FunctionType => 2, - :Domain => [0.0, 1.0], - :C0 => normalize_color(color1), - :C1 => normalize_color(color2), - :N => 1, - }) - - shading = ref!({ - :ShadingType => 2, # axial shading - :ColorSpace => color_type(color1) == :RGB ? :DeviceRGB : :DeviceCMYK, - :Coords => [0.0, 0.0, 1.0, 0.0], - :Function => shader, - :Extend => [true, true], - }) - - x, y = *point - shading_pattern = ref!({ - :PatternType => 2, # shading pattern - :Shading => shading, - :Matrix => [0,-height, -width, 0, x, y], - }) - - patterns = page.resources[:Pattern] ||= {} - id = patterns.empty? ? 'SP1' : patterns.keys.sort.last.succ - patterns[id] = shading_pattern - - set_color type, id, :pattern => true - end - end - end -end - diff --git a/lib/prawn/graphics/patterns.rb b/lib/prawn/graphics/patterns.rb new file mode 100644 index 000000000..b542154bd --- /dev/null +++ b/lib/prawn/graphics/patterns.rb @@ -0,0 +1,137 @@ +# encoding: utf-8 + +# patterns.rb : Implements axial & radial gradients +# +# Originally implemented by Wojciech Piekutowski. November, 2009 +# Copyright September 2012, Alexander Mankuta. All Rights Reserved. +# +# This is free software. Please see the LICENSE and COPYING files for details. +# +module Prawn + module Graphics + module Patterns + + # Sets the fill gradient from color1 to color2. + # old arguments: point, width, height, color1, color2, options = {} + # new arguments: from, to, color1, color1 + # or from, r1, to, r2, color1, color2 + def fill_gradient(*args) + if args[1].is_a?(Array) || args[2].is_a?(Array) + set_gradient(:fill, *args) + else + warn "[DEPRECATION] 'fill_gradient(point, width, height,...)' is deprecated in favor of 'fill_gradient(from, to,...)'. " + + "Old arguments will be removed in release 1.1" + set_gradient :fill, args[0], [args[0].first, args[0].last - args[2]], args[3], args[4] + end + end + + # Sets the stroke gradient from color1 to color2. + # old arguments: point, width, height, color1, color2, options = {} + # new arguments: from, to, color1, color2 + # or from, r1, to, r2, color1, color2 + def stroke_gradient(*args) + if args[1].is_a?(Array) || args[2].is_a?(Array) + set_gradient(:stroke, *args) + else + warn "[DEPRECATION] 'stroke_gradient(point, width, height,...)' is deprecated in favor of 'stroke_gradient(from, to,...)'. " + + "Old arguments will be removed in release 1.1" + set_gradient :stroke, args[0], [args[0].first, args[0].last - args[2]], args[3], args[4] + end + end + + private + + def set_gradient(type, *grad) + patterns = page.resources[:Pattern] ||= {} + + registry_key = gradient_registry_key grad + + if patterns["SP#{registry_key}"] + shading = patterns["SP#{registry_key}"] + else + unless shading = gradient_registry[registry_key] + shading = gradient(*grad) + gradient_registry[registry_key] = shading + end + + patterns["SP#{registry_key}"] = shading + end + + operator = case type + when :fill + 'scn' + when :stroke + 'SCN' + else + raise ArgumentError, "unknown type '#{type}'" + end + + set_color_space type, :Pattern + add_content "/SP#{registry_key} #{operator}" + end + + def gradient_registry_key(gradient) + if gradient[1].is_a?(Array) # axial + [ + map_to_absolute(gradient[0]), + map_to_absolute(gradient[1]), + gradient[2], gradient[3] + ] + else # radial + [ + map_to_absolute(gradient[0]), + gradient[1], + map_to_absolute(gradient[2]), + gradient[3], + gradient[4], gradient[5] + ] + end.hash + end + + def gradient_registry + @gradient_registry ||= {} + end + + def gradient(*args) + if args.length != 4 && args.length != 6 + raise ArgumentError, "Unknown type of gradient: #{args.inspect}" + end + + color1 = normalize_color(args[-2]).dup.freeze + color2 = normalize_color(args[-1]).dup.freeze + + if color_type(color1) != color_type(color2) + raise ArgumentError, "Both colors must be of the same color space: #{color1.inspect} and #{color2.inspect}" + end + + process_color color1 + process_color color2 + + shader = ref!({ + :FunctionType => 2, + :Domain => [0.0, 1.0], + :C0 => color1, + :C1 => color2, + :N => 1.0, + }) + + shading = ref!({ + :ShadingType => args.length == 4 ? 2 : 3, # axial : radial shading + :ColorSpace => color_space(color1), + :Coords => args.length == 4 ? + [0, 0, args[1].first - args[0].first, args[1].last - args[0].last] : + [0, 0, args[1], args[2].first - args[0].first, args[2].last - args[0].last, args[3]], + :Function => shader, + :Extend => [true, true], + }) + + shading_pattern = ref!({ + :PatternType => 2, # shading pattern + :Shading => shading, + :Matrix => [1, 0, + 0, 1] + map_to_absolute(args[0]), + }) + end + end + end +end diff --git a/spec/graphics_spec.rb b/spec/graphics_spec.rb index 6aeb83caf..655401308 100644 --- a/spec/graphics_spec.rb +++ b/spec/graphics_spec.rb @@ -280,37 +280,75 @@ end end -describe "Gradients" do +describe "Patterns" do before(:each) { create_pdf } - it "should create a /Pattern resource" do - @pdf.fill_gradient [0, @pdf.bounds.height], - @pdf.bounds.width, @pdf.bounds.height, 'FF0000', '0000FF' + describe 'linear gradients' do + it "should create a /Pattern resource" do + @pdf.fill_gradient [0, @pdf.bounds.height], + [@pdf.bounds.width, @pdf.bounds.height], 'FF0000', '0000FF' + + grad = PDF::Inspector::Graphics::Pattern.analyze(@pdf.render) + pattern = grad.patterns.values.first + + pattern.should.not.be.nil + pattern[:Shading][:ShadingType].should == 2 + pattern[:Shading][:Coords].should == [0, 0, @pdf.bounds.width, 0] + assert pattern[:Shading][:Function][:C0].zip([1, 0, 0]). + all?{ |x1, x2| (x1-x2).abs < 0.01 } + assert pattern[:Shading][:Function][:C1].zip([0, 0, 1]). + all?{ |x1, x2| (x1-x2).abs < 0.01 } + end - grad = PDF::Inspector::Graphics::Pattern.analyze(@pdf.render) - pattern = grad.patterns[:SP1] + it "fill_gradient should set fill color to the pattern" do + @pdf.fill_gradient [0, @pdf.bounds.height], + [@pdf.bounds.width, @pdf.bounds.height], 'FF0000', '0000FF' - pattern.should.not.be.nil - assert pattern[:Shading][:Function][:C0].zip([1, 0, 0]). - all?{ |x1, x2| (x1-x2).abs < 0.01 } - assert pattern[:Shading][:Function][:C1].zip([0, 0, 1]). - all?{ |x1, x2| (x1-x2).abs < 0.01 } - end + str = @pdf.render + str.should =~ %r{/Pattern\s+cs\s*/SP-?\d+\s+scn} + end - it "fill_gradient should set fill color to the pattern" do - @pdf.fill_gradient [0, @pdf.bounds.height], - @pdf.bounds.width, @pdf.bounds.height, 'FF0000', '0000FF' + it "stroke_gradient should set stroke color to the pattern" do + @pdf.stroke_gradient [0, @pdf.bounds.height], + [@pdf.bounds.width, @pdf.bounds.height], 'FF0000', '0000FF' - str = @pdf.render - str.should =~ %r{/Pattern\s+cs\s*/SP1\s+scn} + str = @pdf.render + str.should =~ %r{/Pattern\s+CS\s*/SP-?\d+\s+SCN} + end end - it "stroke_gradient should set stroke color to the pattern" do - @pdf.stroke_gradient [0, @pdf.bounds.height], - @pdf.bounds.width, @pdf.bounds.height, 'FF0000', '0000FF' + describe 'radial gradients' do + it "should create a /Pattern resource" do + @pdf.fill_gradient [0, @pdf.bounds.height], 10, + [@pdf.bounds.width, @pdf.bounds.height], 20, 'FF0000', '0000FF' + + grad = PDF::Inspector::Graphics::Pattern.analyze(@pdf.render) + pattern = grad.patterns.values.first + + pattern.should.not.be.nil + pattern[:Shading][:ShadingType].should == 3 + pattern[:Shading][:Coords].should == [0, 0, 10, @pdf.bounds.width, 0, 20] + assert pattern[:Shading][:Function][:C0].zip([1, 0, 0]). + all?{ |x1, x2| (x1-x2).abs < 0.01 } + assert pattern[:Shading][:Function][:C1].zip([0, 0, 1]). + all?{ |x1, x2| (x1-x2).abs < 0.01 } + end + + it "fill_gradient should set fill color to the pattern" do + @pdf.fill_gradient [0, @pdf.bounds.height], 10, + [@pdf.bounds.width, @pdf.bounds.height], 20, 'FF0000', '0000FF' + + str = @pdf.render + str.should =~ %r{/Pattern\s+cs\s*/SP-?\d+\s+scn} + end + + it "stroke_gradient should set stroke color to the pattern" do + @pdf.stroke_gradient [0, @pdf.bounds.height], 10, + [@pdf.bounds.width, @pdf.bounds.height], 20, 'FF0000', '0000FF' - str = @pdf.render - str.should =~ %r{/Pattern\s+CS\s*/SP1\s+SCN} + str = @pdf.render + str.should =~ %r{/Pattern\s+CS\s*/SP-?\d+\s+SCN} + end end end