Skip to content

Commit

Permalink
Follow href on gradient to reference parent stop definitions
Browse files Browse the repository at this point in the history
Implemented as part of #117.
  • Loading branch information
Roger Nesbitt committed Mar 28, 2020
1 parent f046e03 commit 69f2899
Showing 1 changed file with 31 additions and 20 deletions.
51 changes: 31 additions & 20 deletions lib/prawn/svg/elements/gradient.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
attr_reader :parent_gradient
attr_reader :x1, :y1, :x2, :y2, :cx, :cy, :fx, :fy, :radius, :units, :stops, :transform_matrix

TAG_NAME_TO_TYPE = {
"linearGradient" => :linear,
"radialGradient" => :radial
Expand All @@ -8,6 +11,8 @@ def parse
# A gradient tag without an ID is inaccessible and can never be used
raise SkipElementQuietly if attributes['id'].nil?

@parent_gradient = document.gradients[href_attribute[1..-1]] if href_attribute && href_attribute[0] == '#'

assert_compatible_prawn_version
load_gradient_configuration
load_coordinates
Expand All @@ -23,7 +28,7 @@ def gradient_arguments(element)
# by a monkey patch installed by prawn-svg. Prawn only sees this as a truthy variable.
#
# See Prawn::SVG::Extensions::AdditionalGradientTransforms for details.
base_arguments = {stops: @stops, apply_transformations: @transform_matrix || true}
base_arguments = {stops: stops, apply_transformations: transform_matrix || true}

arguments = specific_gradient_arguments(element)
arguments.merge(base_arguments) if arguments
Expand All @@ -32,38 +37,38 @@ def gradient_arguments(element)
private

def specific_gradient_arguments(element)
if @units == :bounding_box
x1, y1, x2, y2 = element.bounding_box
return if y2.nil?
if units == :bounding_box
bounding_x1, bounding_y1, bounding_x2, bounding_y2 = element.bounding_box
return if bounding_y2.nil?

width = x2 - x1
height = y1 - y2
width = bounding_x2 - bounding_x1
height = bounding_y1 - bounding_y2
end

case [type, @units]
case [type, units]
when [:linear, :bounding_box]
from = [x1 + width * @x1, y1 - height * @y1]
to = [x1 + width * @x2, y1 - height * @y2]
from = [bounding_x1 + width * x1, bounding_y1 - height * y1]
to = [bounding_x1 + width * x2, bounding_y1 - height * y2]

{from: from, to: to}

when [:linear, :user_space]
{from: [@x1, @y1], to: [@x2, @y2]}
{from: [x1, y1], to: [x2, y2]}

when [:radial, :bounding_box]
center = [x1 + width * @cx, y1 - height * @cy]
focus = [x1 + width * @fx, y1 - height * @fy]
center = [bounding_x1 + width * cx, bounding_y1 - height * cy]
focus = [bounding_x1 + width * fx, bounding_y1 - height * fy]

# Note: Chrome, at least, implements radial bounding box radiuses as
# having separate X and Y components, so in bounding box mode their
# gradients come out as ovals instead of circles. PDF radial shading
# doesn't have the option to do this, and it's confusing why the
# Chrome user space gradients don't apply the same logic anyway.
hypot = Math.sqrt(width * width + height * height)
{from: focus, r1: 0, to: center, r2: @radius * hypot}
{from: focus, r1: 0, to: center, r2: radius * hypot}

when [:radial, :user_space]
{from: [@fx, @fy], r1: 0, to: [@cx, @cy], r2: @radius}
{from: [fx, fy], r1: 0, to: [cx, cy], r2: radius}

else
raise "unexpected type/unit system"
Expand Down Expand Up @@ -93,7 +98,7 @@ def load_gradient_configuration
end

def load_coordinates
case [type, @units]
case [type, units]
when [:linear, :bounding_box]
@x1 = parse_zero_to_one(attributes["x1"], 0)
@y1 = parse_zero_to_one(attributes["y1"], 0)
Expand All @@ -109,8 +114,8 @@ def load_coordinates
when [:radial, :bounding_box]
@cx = parse_zero_to_one(attributes["cx"], 0.5)
@cy = parse_zero_to_one(attributes["cy"], 0.5)
@fx = parse_zero_to_one(attributes["fx"], @cx)
@fy = parse_zero_to_one(attributes["fy"], @cy)
@fx = parse_zero_to_one(attributes["fx"], cx)
@fy = parse_zero_to_one(attributes["fy"], cy)
@radius = parse_zero_to_one(attributes["r"], 0.5)

when [:radial, :user_space]
Expand Down Expand Up @@ -147,10 +152,16 @@ def load_stops
end
end

raise SkipElementError, "gradient does not have any valid stops" if @stops.empty?
if stops.empty?
if parent_gradient.nil? || parent_gradient.stops.empty?
raise SkipElementError, "gradient does not have any valid stops"
end

@stops.unshift([0, @stops.first.last]) if @stops.first.first > 0
@stops.push([1, @stops.last.last]) if @stops.last.first < 1
@stops = parent_gradient.stops
else
stops.unshift([0, stops.first.last]) if stops.first.first > 0
stops.push([1, stops.last.last]) if stops.last.first < 1
end
end

def parse_zero_to_one(string, default = 0)
Expand Down

0 comments on commit 69f2899

Please sign in to comment.