# frozen_string_literal: true# This class creates a SVG files.# Initial code from: https://github.com/samvincent/rqrcode-rails3moduleRQRCodemoduleExportmoduleSVGclassBaseOutputSVGattr_reader:resultdefinitialize(qrcode)@qrcode=qrcode@result=[]endendclassPath<BaseOutputSVGdefbuild(module_size,offset,color)modules_array=@qrcode.modulesmatrix_width=matrix_height=modules_array.length+1empty_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_indexdo|row_pair,row_index|first_row,second_row=row_pair# horizontal edgesfirst_row.zip(second_row).each_with_indexdo|cell_pair,column_index|edge=casecell_pairwhen[true,false]thenEdge.newcolumn_index+1,row_index,:leftwhen[false,true]thenEdge.newcolumn_index,row_index,:rightend(edge_matrix[edge.start_y][edge.start_x]||=[])<<edgeifedgeend# vertical edges([false]+second_row+[false]).each_cons(2).each_with_indexdo|cell_pair,column_index|edge=casecell_pairwhen[true,false]thenEdge.newcolumn_index,row_index,:downwhen[false,true]thenEdge.newcolumn_index,row_index+1,:upend(edge_matrix[edge.start_y][edge.start_x]||=[])<<edgeifedgeendendedge_count=edge_matrix.flatten.compact.countpath=[]whileedge_count>0edge_loop=[]next_matrix_cell=edge_matrix.find(&:any?).find{|cell|cell&.any?}edge=next_matrix_cell.firstwhileedgeedge_loop<<edgematrix_cell=edge_matrix[edge.start_y][edge.start_x]matrix_cell.deleteedgeedge_matrix[edge.start_y][edge.start_x]=nilifmatrix_cell.empty?edge_count-=1# try to find an edge continuing the current edgeedge=edge_matrix[edge.end_y][edge.end_x]&.firstendfirst_edge=edge_loop.firstedge_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].eachdo|direction,edges|edge_loop_string<<"#{SVG_PATH_COMMANDS[direction]}#{edges.length}"endedge_loop_string<<SVG_PATH_COMMANDS[:close]path<<edge_loop_stringend# Prefix hexadecimal colors unless using a named color (symbol)color="##{color}"unlesscolor.is_a?(Symbol)@result<<%{<path d="#{path.join}" fill="#{color}" transform="translate(#{offset},#{offset}) scale(#{module_size})"/>}endendclassRect<BaseOutputSVGdefbuild(module_size,offset,color)# Prefix hexadecimal colors unless using a named color (symbol)color="##{color}"unlesscolor.is_a?(Symbol)@qrcode.modules.each_indexdo|c|tmp=[]@qrcode.modules.each_indexdo|r|y=c*module_size+offsetx=r*module_size+offsetnextunless@qrcode.checked?(c,r)tmp<<%(<rect width="#{module_size}" height="#{module_size}" x="#{x}" y="#{y}" fill="#{color}"/>)end@result<<tmp.joinendendendclassEdge<Struct.new(:start_x,:start_y,:direction)defend_xcasedirectionwhen:rightthenstart_x+1when:leftthenstart_x-1elsestart_xendenddefend_ycasedirectionwhen:downthenstart_y+1when:upthenstart_y-1elsestart_yendendendDEFAULT_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 {})#defas_svg(options={})fill=options[:fill]use_path=options[:use_path]offset=options[:offset].to_i||0color=options[:color]||"000"shape_rendering=options[:shape_rendering]||"crispEdges"module_size=options[:module_size]||11standalone=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 complexitydimension=(@qrcode.module_count*module_size)+(2*offset)# use dimensions differently if we are using a viewBoxdimensions_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)iffill# Prefix hexadecimal colors unless using a named color (symbol)fill="##{fill}"unlessfill.is_a?(Symbol)output_tag.result.unshift%(<rect width="#{dimension}" height="#{dimension}" x="0" y="0" fill="#{fill}"/>)endifstandaloneoutput_tag.result.unshift(xml_tag,open_tag)output_tag.result<<close_tagendoutput_tag.result.joinendendendendRQRCode::QRCode.send:include,RQRCode::Export::SVG