File: C:/Ruby27-x64/lib/ruby/gems/2.7.0/gems/rqrcode-2.1.2/lib/rqrcode/export/svg.rb
# frozen_string_literal: true
# This class creates a SVG files.
# Initial code from: https://github.com/samvincent/rqrcode-rails3
module RQRCode
module Export
module SVG
class BaseOutputSVG
attr_reader :result
def initialize(qrcode)
@qrcode = qrcode
@result = []
end
end
class Path < BaseOutputSVG
def build(module_size, offset, color)
modules_array = @qrcode.modules
matrix_width = matrix_height = modules_array.length + 1
empty_row = [Array.new(matrix_width - 1, false)]
edge_matrix = Array.new(matrix_height) { Array.new(matrix_width) }
(empty_row + modules_array + empty_row).each_cons(2).with_index do |row_pair, row_index|
first_row, second_row = row_pair
# horizontal edges
first_row.zip(second_row).each_with_index do |cell_pair, column_index|
edge = case cell_pair
when [true, false] then Edge.new column_index + 1, row_index, :left
when [false, true] then Edge.new column_index, row_index, :right
end
(edge_matrix[edge.start_y][edge.start_x] ||= []) << edge if edge
end
# vertical edges
([false] + second_row + [false]).each_cons(2).each_with_index do |cell_pair, column_index|
edge = case cell_pair
when [true, false] then Edge.new column_index, row_index, :down
when [false, true] then Edge.new column_index, row_index + 1, :up
end
(edge_matrix[edge.start_y][edge.start_x] ||= []) << edge if edge
end
end
edge_count = edge_matrix.flatten.compact.count
path = []
while edge_count > 0
edge_loop = []
next_matrix_cell = edge_matrix.find(&:any?).find { |cell| cell&.any? }
edge = next_matrix_cell.first
while edge
edge_loop << edge
matrix_cell = edge_matrix[edge.start_y][edge.start_x]
matrix_cell.delete edge
edge_matrix[edge.start_y][edge.start_x] = nil if matrix_cell.empty?
edge_count -= 1
# try to find an edge continuing the current edge
edge = edge_matrix[edge.end_y][edge.end_x]&.first
end
first_edge = edge_loop.first
edge_loop_string = SVG_PATH_COMMANDS[:move]
edge_loop_string += "#{first_edge.start_x} #{first_edge.start_y}"
edge_loop.chunk(&:direction).to_a[0...-1].each do |direction, edges|
edge_loop_string << "#{SVG_PATH_COMMANDS[direction]}#{edges.length}"
end
edge_loop_string << SVG_PATH_COMMANDS[:close]
path << edge_loop_string
end
# Prefix hexadecimal colors unless using a named color (symbol)
color = "##{color}" unless color.is_a?(Symbol)
@result << %{<path d="#{path.join}" fill="#{color}" transform="translate(#{offset},#{offset}) scale(#{module_size})"/>}
end
end
class Rect < BaseOutputSVG
def build(module_size, offset, color)
# Prefix hexadecimal colors unless using a named color (symbol)
color = "##{color}" unless color.is_a?(Symbol)
@qrcode.modules.each_index do |c|
tmp = []
@qrcode.modules.each_index do |r|
y = c * module_size + offset
x = r * module_size + offset
next unless @qrcode.checked?(c, r)
tmp << %(<rect width="#{module_size}" height="#{module_size}" x="#{x}" y="#{y}" fill="#{color}"/>)
end
@result << tmp.join
end
end
end
class Edge < Struct.new(:start_x, :start_y, :direction)
def end_x
case direction
when :right then start_x + 1
when :left then start_x - 1
else start_x
end
end
def end_y
case direction
when :down then start_y + 1
when :up then start_y - 1
else start_y
end
end
end
DEFAULT_SVG_ATTRIBUTES = [
%(version="1.1"),
%(xmlns="http://www.w3.org/2000/svg"),
%(xmlns:xlink="http://www.w3.org/1999/xlink"),
%(xmlns:ev="http://www.w3.org/2001/xml-events")
]
SVG_PATH_COMMANDS = {
move: "M",
up: "v-",
down: "v",
left: "h-",
right: "h",
close: "z"
}
#
# Render the SVG from the Qrcode.
#
# Options:
# offset - Padding around the QR Code in pixels
# (default 0)
# fill - Background color e.g "ffffff"
# (default none)
# color - Foreground color e.g "000"
# (default "000")
# module_size - The Pixel size of each module
# (defaults 11)
# shape_rendering - SVG Attribute: auto | optimizeSpeed | crispEdges | geometricPrecision
# (defaults crispEdges)
# standalone - Whether to make this a full SVG file, or only an svg to embed in other svg
# (default true)
# use_path - Use <path> to render SVG rather than <rect> to significantly reduce size
# and quality. This will become the default in future versions.
# (default false)
# viewbox - replace `width` and `height` in <svg> with a viewBox, allows CSS scaling
# (default false)
# svg_attributes - A optional hash of custom <svg> attributes. Existing attributes will remain.
# (default {})
#
def as_svg(options = {})
fill = options[:fill]
use_path = options[:use_path]
offset = options[:offset].to_i || 0
color = options[:color] || "000"
shape_rendering = options[:shape_rendering] || "crispEdges"
module_size = options[:module_size] || 11
standalone = options[:standalone].nil? ? true : options[:standalone]
viewbox = options[:viewbox].nil? ? false : options[:viewbox]
svg_attributes = options[:svg_attributes] || {}
# height and width dependent on offset and QR complexity
dimension = (@qrcode.module_count * module_size) + (2 * offset)
# use dimensions differently if we are using a viewBox
dimensions_attr = viewbox ? %(viewBox="0 0 #{dimension} #{dimension}") : %(width="#{dimension}" height="#{dimension}")
svg_tag_attributes = (DEFAULT_SVG_ATTRIBUTES + [
dimensions_attr,
%(shape-rendering="#{shape_rendering}")
] + svg_attributes.map { |k, v| %(#{k}="#{v}") }).join(" ")
xml_tag = %(<?xml version="1.0" standalone="yes"?>)
open_tag = %(<svg #{svg_tag_attributes}>)
close_tag = "</svg>"
output_tag = (use_path ? Path : Rect).new(@qrcode)
output_tag.build(module_size, offset, color)
if fill
# Prefix hexadecimal colors unless using a named color (symbol)
fill = "##{fill}" unless fill.is_a?(Symbol)
output_tag.result.unshift %(<rect width="#{dimension}" height="#{dimension}" x="0" y="0" fill="#{fill}"/>)
end
if standalone
output_tag.result.unshift(xml_tag, open_tag)
output_tag.result << close_tag
end
output_tag.result.join
end
end
end
end
RQRCode::QRCode.send :include, RQRCode::Export::SVG