Module: Sevgi::Sundries::Export
- Extended by:
- Export
- Included in:
- Export
- Defined in:
- lib/sevgi/sundries/export/native.rb,
lib/sevgi/sundries/export/system.rb
Overview
Exports SVG content and post-processes PDF output.
Defined Under Namespace
Modules: Renderer
Constant Summary collapse
- ExportError =
Raised when SVG export or PDF post-processing cannot be completed.
Class.new(Error)
- DEFAULT_DPI =
Default SVG CSS pixel density.
96.0- AVAILABLE =
Supported export format names mapped to file extensions.
(EXTENSIONS = { ".pdf" => :pdf, ".png" => :png }.freeze) .invert .freeze
Class Method Summary collapse
-
.call(svg, output, format: nil, width: nil, height: nil, dpi: DEFAULT_DPI, css: nil) {|svg| ... } ⇒ Object
Exports SVG source to a PDF or PNG file using librsvg and Cairo.
Instance Method Summary collapse
-
#a5_on_a4(infile, outfile) ⇒ Sevgi::Function::Shell::Result
Places a single A5 PDF page twice on an A4 landscape sheet with pdfcpu.
-
#a5_on_a4!(infile) ⇒ void
Replaces a PDF file with an A5-on-A4 layout generated by pdfcpu.
-
#format_for!(format, output) ⇒ Symbol
Resolves the export format from an explicit value or output extension.
-
#inject(svg, css) ⇒ String
Inserts CSS before the closing svg tag.
-
#inkscape(infile, outfile = nil, format: nil, background: "#ffffff", opacity: 1, width: nil, height: nil, id: nil, page: nil, css: nil) ⇒ Sevgi::Function::Shell::Result
Exports an SVG file through Inkscape.
-
#rsvg(infile, outfile = nil, format: nil, background: "#ffffff", width: nil, height: nil, id: nil, css: nil) ⇒ Sevgi::Function::Shell::Result
Exports an SVG file through rsvg-convert.
-
#stamp(infile, outfile, stamp:, placeholder:) ⇒ Boolean
Replaces a placeholder text object in a PDF stream.
-
#stamp!(infile, stamp:, placeholder:) ⇒ Boolean
Replaces a placeholder text object inside a PDF file in place.
-
#unite(sources, outfile) ⇒ Sevgi::Function::Shell::Result
Merges PDF files with pdfunite.
Class Method Details
.call(svg, output, format: nil, width: nil, height: nil, dpi: DEFAULT_DPI, css: nil) {|svg| ... } ⇒ Object
Exports SVG source to a PDF or PNG file using librsvg and Cairo.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/sevgi/sundries/export/native.rb', line 33 def self.call(svg, output, format: nil, width: nil, height: nil, dpi: DEFAULT_DPI, css: nil, &block) ArgumentError.("SVG content must be a String") unless svg.is_a?(String) ArgumentError.("Export output must be provided") if output.nil? || output.to_s.strip.empty? svg = inject(svg, css) if css && !css.strip.empty? svg = block.call(svg) if block ArgumentError.("SVG content must be a String") unless svg.is_a?(String) format = format_for!(format, output) renderer = Renderer.method(format) begin handle = Rsvg::Handle.new_from_data(svg) iw, ih = intrinsic_size(handle) ExportError.("Invalid SVG dimensions") if iw <= 0 || ih <= 0 scale = dpi / DEFAULT_DPI iw *= scale ih *= scale tw, th = target_size(iw, ih, width, height) ExportError.("Invalid export dimensions") unless target_size?(format, tw, th) renderer.call( handle: handle, output: output.to_s, iw: iw, ih: ih, tw: tw, th: th, dpi: dpi ) rescue Rsvg::Error, Cairo::Error => e ExportError.("Render error: #{e.}") end output end |
Instance Method Details
#a5_on_a4(infile, outfile) ⇒ Sevgi::Function::Shell::Result
Places a single A5 PDF page twice on an A4 landscape sheet with pdfcpu.
16 |
# File 'lib/sevgi/sundries/export/system.rb', line 16 def a5_on_a4(infile, outfile) = F.sh!("pdfcpu", "nup", "--", "form:A4L, border:off", outfile, "2", infile) |
#a5_on_a4!(infile) ⇒ void
This method returns an undefined value.
Replaces a PDF file with an A5-on-A4 layout generated by pdfcpu.
23 24 25 26 27 28 29 |
# File 'lib/sevgi/sundries/export/system.rb', line 23 def a5_on_a4!(infile) temp = Tempfile.new(%w[output .pdf], File.dirname(infile)) a5_on_a4(infile, temp.path) FileUtils.mv(temp.path, infile) ensure temp&.close! end |
#format_for!(format, output) ⇒ Symbol
Resolves the export format from an explicit value or output extension.
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/sevgi/sundries/export/native.rb', line 87 def format_for!(format, output) if format format = format.to_sym ExportError.("Unsupported export format: #{format}") unless AVAILABLE.key?(format) format else ext = File.extname(output.to_s).downcase ExportError.("Unrecognized file extension: #{ext}") unless EXTENSIONS.key?(ext) EXTENSIONS[ext] end end |
#inject(svg, css) ⇒ String
Inserts CSS before the closing svg tag.
105 |
# File 'lib/sevgi/sundries/export/native.rb', line 105 def inject(svg, css) = svg.sub("</svg>", "<style>#{css}</style></svg>") |
#inkscape(infile, outfile = nil, format: nil, background: "#ffffff", opacity: 1, width: nil, height: nil, id: nil, page: nil, css: nil) ⇒ Sevgi::Function::Shell::Result
Exports an SVG file through Inkscape.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/sevgi/sundries/export/system.rb', line 47 def inkscape( infile, outfile = nil, format: nil, background: "#ffffff", opacity: 1, width: nil, height: nil, id: nil, page: nil, css: nil ) infile = File.(infile) outfile ||= F.subext(".pdf", infile) outfile = File.(outfile) format = format_for!(format, outfile) if css temp = Tempfile.new(%w[input .svg], File.dirname(infile)) ::File.write(temp.path, inject(::File.read(infile), css)) infile = temp.path end F.sh!( *[ "inkscape", "--batch-process", "--actions=select-by-class:text,object-to-path", "--export-type=#{format}", ("--export-background=#{background}" if background), ("--export-background-opacity=#{opacity}" if opacity), ("--export-width=#{width}" if width), ("--export-height=#{height}" if height), ("--export-id=#{id}" if id), ("--export-id-only" if id), ("--export-page=#{page}" if page), "--export-filename=#{outfile}", infile ].compact ) ensure temp&.close! end |
#rsvg(infile, outfile = nil, format: nil, background: "#ffffff", width: nil, height: nil, id: nil, css: nil) ⇒ Sevgi::Function::Shell::Result
Exports an SVG file through rsvg-convert.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/sevgi/sundries/export/system.rb', line 105 def rsvg( infile, outfile = nil, format: nil, background: "#ffffff", width: nil, height: nil, id: nil, css: nil ) infile = File.(infile) outfile ||= F.subext(".pdf", infile) outfile = File.(outfile) format = format_for!(format, outfile) if css temp = Tempfile.new(%w[input .svg], File.dirname(infile)) ::File.write(temp.path, inject(::File.read(infile), css)) infile = temp.path end F.sh!( *[ "rsvg-convert", "--format=#{format}", ("--background-color=#{background}" if background), ("--width=#{width}" if width), ("--height=#{height}" if height), ("--export-id=#{id}" if id), "--output=#{outfile}", infile ].compact ) ensure temp&.close! end |
#stamp(infile, outfile, stamp:, placeholder:) ⇒ Boolean
Replaces a placeholder text object in a PDF stream.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/sevgi/sundries/export/native.rb', line 114 def stamp(infile, outfile, stamp:, placeholder:) doc = HexaPDF::Document.open(infile) stamped = false doc.pages.each do |page| Array(page[:Contents]).each do |ref| obj = doc.object(ref) next unless obj.respond_to?(:stream) data = obj.stream next unless data.include?("(#{placeholder})") data = data.gsub( %r{1 1 1 rg (BT\s+.*?/\S+ \d+ Tf\s+)\(#{Regexp.escape(placeholder)}\)Tj}m, "0.101961 0.101961 0.101961 rg \\1(#{stamp})Tj" ) obj.stream = data obj.set_filter(:FlateDecode) stamped = true end end doc.write(outfile, optimize: true) if stamped stamped rescue HexaPDF::Error, ::SystemCallError => e ExportError.("PDF stamp error: #{e.}") end |
#stamp!(infile, stamp:, placeholder:) ⇒ Boolean
Replaces a placeholder text object inside a PDF file in place.
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/sevgi/sundries/export/native.rb', line 149 def stamp!(infile, stamp:, placeholder:) temp = Tempfile.new(%w[stamp .pdf], File.dirname(infile)) stamped = stamp(infile, temp.path, stamp:, placeholder:) if stamped if File.exist?(temp.path) && File.empty?(temp.path) warn("Skipping 0 byte file which was produced during stamping") else FileUtils.mv(temp.path, infile) end end stamped rescue ::SystemCallError => e ExportError.("PDF stamp error: #{e.}") ensure temp&.close! end |
#unite(sources, outfile) ⇒ Sevgi::Function::Shell::Result
Merges PDF files with pdfunite.
149 |
# File 'lib/sevgi/sundries/export/system.rb', line 149 def unite(sources, outfile) = F.sh!("pdfunite", *sources, outfile) |