# frozen_string_literal: true require_relative 'shell_helper' require 'nokogiri' module Booru module ImageProcessing class SvgProcessor < Processor DATA_URI_REGEX = /\Adata:(image\/png|image\/gif|image\/jpeg|image\/svg+xml);base64,/ include ShellHelper def initialize(original_path, directory, metadata) super(original_path, directory, metadata) # We keep around a reference to the Tempfile object so that it doesn't get GC'd (and deleted) too early. @rasterized_tempfile = Tempfile.new(%w[rendered .png]).tap(&:close) @rasterized = @rasterized_tempfile.path end def pre_process write_log 'pre_process' # clean up SVG file to remove embedded scripts and remote references svg = Nokogiri::XML(IO.read(@original_path)) # The magic. svg.css('script').each(&:remove) svg.css('image').reject { |x| x.attributes['href'].to_s =~ DATA_URI_REGEX }.each(&:remove) File.open(@original_path, 'w') do |file| file.write svg.to_xml end generate_rasterized end def generate_rasterized # Is it OK to do this without scrubbing? - Yeah, that's the whole point of safe-rsvg-convert. exec_new 'RSVG-CONVERT', ['safe-rsvg-convert', @original_path, @rasterized] end def generate_version(dimensions, destination_path) write_log "generate_version #{dimensions} #{destination_path}" width, height = dimensions # resize rasterized version to desired size and optipng the resized version. # (converting the original file is slower than resizing a rasterized version) png = ::MiniMagick::Image.open(@rasterized) png.resize("#{width}x#{height}") png.write(destination_path) # is this needed? they didn't used to do that, but I do now. (because I want to) optipng(destination_path) end def generate_full_version(destination_path) write_log 'generate_full_version' # full.png -> rasterized # full.svg -> svg FileUtils.copy(@rasterized, destination_path) platform_link(@original_path, File.join(File.dirname(destination_path), 'full.svg')) end def post_process write_log 'post_process' optipng(@rasterized) end end end end