def render(knockout:, **, &block)
def render(knockout:, **, &block)
defs = REXML::Element.new("defs").tap(&block)
defs.add_attributes "id" => "#{@name}.defs"
drawing_features.group_by do |feature, categories|
categories || Array(feature["category"]).map(&:to_s).map(&method(:categorise)).to_set
end.flat_map do |categories, features|
dupes = params_for(categories)["dupe"]
Array(dupes).map(&:to_s).map do |dupe|
[categories | Set[dupe], [name, *categories, "content"].join(?.)]
end.push [categories, features]
end.tap do |ordered|
params.fetch("order", []).reverse.map(&:split).map(&:to_set).each do |filter|
ordered.sort_by!.with_index do |(categories, features), index|
[filter <= categories ? 0 : 1, index]
end
end
end.each do |categories, features|
ids = [name, *categories]
use = REXML::Element.new("use")
use.add_attributes "id" => ids.join(?.)
case features
when String
use.add_attributes "href" => "##{features}"
when Array
content = defs.add_element "g", "id" => [*ids, "content"].join(?.)
use.add_attributes "href" => "#" + [*ids, "content"].join(?.)
end
use.tap(&block)
category_params = params_for(categories)
font_size, stroke_width, bezier = category_params.values_at "font-size", "stroke-width", "bezier"
subdivide = category_params.slice("subdivide", "section").values.first
category_params.slice(*SVG_ATTRIBUTES).tap do |svg_attributes|
svg_attributes.slice(*FONT_SCALED_ATTRIBUTES).each do |key, value|
svg_attributes[key] = svg_attributes[key].to_i * font_size * 0.01 if /^\d+%$/ === value
end if font_size
use.add_attributes svg_attributes
end
features.each do |feature, _|
case feature
when GeoJSON::Point
symbol_id = [*ids, "symbol"].join(?.)
transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*feature.coordinates, feature.fetch("rotation", @map.rotation) - @map.rotation]
content.add_element "use", "transform" => transform, "href" => "#%s" % symbol_id
when GeoJSON::LineString
(subdivide ? feature.subdivide(subdivide) : feature).explode.each do |feature|
content.add_element "path", "fill" => "none", "d" => feature.svg_path_data(bezier: bezier)
end
when GeoJSON::Polygon
content.add_element "path", "fill-rule" => "nonzero", "d" => feature.svg_path_data
when REXML::Element
case feature.name
when "text", "textPath" then content << feature
when "path" then defs << feature
end
end
end if content
category_params.each do |command, args|
next unless args
args = args.map(&:to_a).inject([], &:+) if Array === args && args.all?(Hash)
case command
when "blur"
filter_id = [*ids, "blur"].join(?.)
use.add_attribute "filter", "url(#%s)" % filter_id
defs.add_element("filter", "id" => filter_id).add_element "feGaussianBlur", "stdDeviation" => args, "in" => "SourceGraphic"
when "symbol"
next unless content
symbol = defs.add_element "g", "id" => [*ids, "symbol"].join(?.)
args.each do |element, attributes|
if attributes
symbol.add_element element, attributes
else
symbol.add_element REXML::Document.new(element).root
end
end
when "pattern"
dimensions, args = args.partition do |key, value|
%w[width height].include? key
end
width, height = Hash[dimensions].values_at "width", "height"
pattern_id = [*ids, "pattern"].join(?.)
pattern = defs.add_element "pattern", "id" => pattern_id, "patternUnits" => "userSpaceOnUse", "width" => width, "height" => height
args.each do |element, attributes|
pattern.add_element element, attributes
end
use.add_attribute "fill", "url(#%s)" % pattern_id
when "symbolise"
next unless content
args, symbols = args.partition do |element, attributes|
%w[interval offset].include? element
end
interval, offset = Hash[args].values_at "interval", "offset"
symbol_ids = symbols.map.with_index do |(element, attributes), index|
symbol_id = [*ids, "symbol", index].join(?.).tap do |symbol_id|
defs.add_element("g", "id" => symbol_id).add_element(element, attributes)
end
end
rings = features.grep(GeoJSON::Polygon).map(&:rings).flat_map(&:explode)
lines = features.grep(GeoJSON::LineString)
(rings + lines).each do |feature|
feature.sample_at(interval, offset: offset) do |point, along, angle|
"translate(%s) rotate(%s)" % [POINT, ANGLE] % [*point, 180.0 * angle / Math::PI]
end.each do |transform|
content.add_element "use", "transform" => transform, "href" => "#%s" % symbol_ids.sample
end
end
when "inpoint", "outpoint", "endpoint"
next unless content
symbol_id = [*ids, command].join(?.)
symbol = defs.add_element "g", "id" => symbol_id
args.each do |element, attributes|
symbol.add_element element, attributes
end
features.grep(GeoJSON::LineString).map(&:coordinates).each do |line|
case command
when "inpoint" then [line.first(2)]
when "outpoint" then [line.last(2).rotate]
when "endpoint" then [line.first(2), line.last(2).rotate]
end.each do |p0, p1|
transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*p0, 180.0 * (p1 - p0).angle / Math::PI]
use.add_element "use", "transform" => transform, "href" => "#%s" % symbol_id
end
end
when "knockout"
use.add_attributes "mask" => "url(##{knockout})"
Knockout.new(use, *args).tap(&block)
when "preserve"
use.add_attributes "mask" => "none"
when "cutout", "mask" # mask deprecated
Cutout.new(use).tap(&block)
when "barrier", "fence" # fence deprecated
next unless content && args
buffer = 0.5 * (Numeric === args ? args : Numeric === stroke_width ? stroke_width : 0)
features.grep_v(REXML::Element).each do |feature|
Labels::ConvexHulls.new(feature, buffer).tap(&block)
end
when "shield"
next unless content
content.elements.each("text") do |element|
case
when text_length = element.elements["./ancestor-or-self::[@textLength]/@textLength"]&.value&.to_f
shield = REXML::Element.new("g")
width, height = text_length + SHIELD_X * font_size, (1 + SHIELD_Y) * font_size
shield.add_element "rect", "x" => -0.5 * width, "y" => -0.5 * height, "width" => width, "height" => height, "rx" => font_size * 0.3, "ry" => font_size * 0.3, "stroke" => "none", "fill" => args
text_transform = element.attributes.get_attribute "transform"
text_transform.remove
shield.attributes << text_transform
element.parent.elements << shield
shield << element
when href = element.elements["./textPath[@href]/@href"]&.value
shield = REXML::Element.new("g")
shield.add_element "use", "href" => href, "stroke-width" => (1 + SHIELD_Y) * font_size, "stroke" => args, "stroke-linecap" => "round"
element.parent.elements << shield
shield << element
end
end
end
end
end
end