commit 9a071262b2c5b6a1fd8ed374754827138615938d Author: John Bintz Date: Sat Nov 9 17:34:27 2013 -0500 initial commit, works for crma, now to get working for lyfo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d87d4be --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..803042a --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +gemspec + +gem 'nokogiri' +gem 'virtus' +gem 'google_drive' +gem 'activesupport' diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8ad8cf3 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2013 John Bintz + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..81ec8ca --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Process Inkscape files and create sets of cards for board games + +Not much in the way of docs yet. You'll need `inkscape`, `convert`, `montage`, and `gs` in your `PATH`. + +Create a `Cardfile` in your working directory. It should look +something like this: + +``` ruby +@session.configure do |c| + c.svg_source = "template/template.svg" + c.svg_merged_target = "template/output.svg" + + c.png_export_width = 825 + c.pdf_card_size = "750x1050" + c.pdf_dpi = 300 + + c.individual_files_path = "template/output/card_%02d.svg" + c.png_files_path = "template/png/card_%02d.png" + + c.pdf_target = "merged.pdf" +end + +@session.process do + require 'google_drive' + require 'virtus' + require 'active_support/inflector' + + require './card_definitions.rb' + + CardDefinitions.processed.each do |card| + @session.with_new_target do |target| + datum = card.to_svggvs + + # #active_layers indicates what sublayers within the "Source" layer of + # the Inkscape document should be toggled as visible. All others are hidden. + target.active_layers = datum[:active] + + # Any text with {% liquid_like_tags %} will have those tags replaced with the + # values within the hash passed in. + target.replacements = datum[:replacements] + end + end +end +``` + +You can also have a `.cardrc` file which is run before loading the `Cardfile`. + +Process your cards with `svggvs`: + +* `svggvs merged_file`: Create a big SVG file with al cards as layers +* `svggvs svgs`: Write out individual SVG files +* `svggvs pngs`: Write out PNG files after writing out the SVG files +* `svggvs pdf`: Write out the merged PnP PDF file + +You can also pass in `--cardfile ` to load a different cardfile, say for +card backs. + diff --git a/bin/svggvs b/bin/svggvs new file mode 100755 index 0000000..2215eba --- /dev/null +++ b/bin/svggvs @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +require 'thor' +require 'digest/md5' + +require_relative '../lib/svggvs' + +module SVGGVS + class Cli < Thor + class_option :cardfile, default: 'Cardfile' + + desc "merged_file", "Write out a merged file" + def merged_file + context.write_merged_file + end + + desc "svgs", "Write out individual SVG files" + def svgs + write_svgs + end + + desc "pngs", "Write out individual PNG files" + def pngs + write_svgs + ensure_tmp + + @exported_pngs = [] + + context.individual_files.each_with_index do |svg_file, index| + target = Pathname(context.session.png_files_path % index) + target.parent.mkpath + + @exported_pngs << target + + system %{inkscape --export-area-page --export-png "#{target.expand_path}" --export-width #{context.session.png_export_width} --export-background="#ffffffff" "#{svg_file.expand_path}"} + end + end + + desc "pdf", "Create PDF of card images" + def pdf + pngs + + trimmed_pngs = @exported_pngs.collect do |png| + tmp_target = tmp_path.join(Digest::MD5.hexdigest(png.to_s) + '.png') + + system %{convert #{png} -gravity Center -crop #{context.session.pdf_card_size}+0+0 +repage #{tmp_target}} + + tmp_target + end + + png_slices = trimmed_pngs.each_slice(9) + + page_count = trimmed_pngs.length / 9 + + pages = png_slices.each_with_index.collect do |files, page_index| + tmp_pdf_target = tmp_path.join("page%05d.pdf" % page_index) + + system %{montage -density #{context.session.pdf_dpi} -geometry +0+0 #{files.join(' ')} #{tmp_pdf_target}} + + tmp_pdf_target + end + + system "gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=#{context.session.pdf_target} -dBATCH #{pages.join(" ")}" + end + + no_tasks do + def tmp_path + @tmp_path ||= Pathname(".tmp") + end + + def ensure_tmp + tmp_path.rmtree if tmp_path.directory? + tmp_path.mkpath + end + + def context + @context ||= SVGGVS::Context.load(options[:cardfile]) + end + + def write_svgs + context.write_individual_files + end + end + end +end + +SVGGVS::Cli.start diff --git a/lib/svggvs.rb b/lib/svggvs.rb new file mode 100644 index 0000000..135744d --- /dev/null +++ b/lib/svggvs.rb @@ -0,0 +1,8 @@ +require 'svggvs/file' +require 'svggvs/target' +require 'svggvs/context' +require 'svggvs/session' + +module SVGGVS +end + diff --git a/lib/svggvs/context.rb b/lib/svggvs/context.rb new file mode 100644 index 0000000..55ab2ef --- /dev/null +++ b/lib/svggvs/context.rb @@ -0,0 +1,63 @@ +require 'pathname' + +module SVGGVS + class Context + attr_reader :individual_files + + def initialize(cardfile = "Cardfile") + @cardfile = cardfile + + @individual_files = [] + end + + def self.load(cardfile = "Cardfile") + context = new(cardfile) + context.load + context + end + + def session + @session ||= SVGGVS::Session.new + end + + def cardrc? + ::File.file?('.cardrc') + end + + def load + session + + if cardrc? + self.instance_eval(::File.read('.cardrc')) + end + + self.instance_eval(cardfile_rb) + end + + def cardfile_rb + @cardfile_rb ||= ::File.read(@cardfile) + end + + def write_merged_file + session.on_card_finished = nil + session.run + + session.file.save @session.svg_merged_target + end + + def write_individual_files + session.on_card_finished do |index| + target = Pathname(session.individual_files_path % index) + + target.parent.mkpath + + session.file.dup_with_only_last_target.save target.to_s + + @individual_files << target + end + + session.run + end + end +end + diff --git a/lib/svggvs/file.rb b/lib/svggvs/file.rb new file mode 100644 index 0000000..c9c4c04 --- /dev/null +++ b/lib/svggvs/file.rb @@ -0,0 +1,83 @@ +require 'nokogiri' + +module SVGGVS + class File + def initialize(path_or_doc) + @instance = 0 + + case path_or_doc + when String + @path = path_or_doc + else + @doc = path_or_doc + end + end + + def source + return @source if @source + + @source = doc.at_css('g[inkscape|label="Source"]') + @source['style'] = 'display:none' + @source + end + + def root + source.parent + end + + def target + @target ||= doc.at_css('g[inkscape|label="Target"]') + end + + def doc + return @doc if @doc + + @doc = Nokogiri::XML(::File.read(@path)) + clear_targets! + + @doc + end + + def clear_targets! + target.children.each(&:remove) + end + + def with_new_target + new_target = source.dup + new_target[:id] = new_target[:id] + "_#{@instance}" + new_target['inkscape:label'] = new_target['inkscape:label'] + "_#{@instance}" + + target_obj = Target.new(new_target) + + yield target_obj + + target_obj.replaced + + target << target_obj.target + + @instance += 1 + end + + def dup_with_only_last_target + dupe = self.class.new(doc.dup) + + target = dupe.target.children.last.dup + target[:style] = '' + + dupe.target.remove + + dupe.root.children.each do |child| + child[:style] = 'display:none' + end + + dupe.root << target + + dupe + end + + def save(file) + ::File.open(file, 'w') { |fh| fh.print doc.to_xml } + end + end +end + diff --git a/lib/svggvs/session.rb b/lib/svggvs/session.rb new file mode 100644 index 0000000..825e4b9 --- /dev/null +++ b/lib/svggvs/session.rb @@ -0,0 +1,46 @@ +module SVGGVS + class Session + attr_accessor :svg_source, :svg_merged_target, :individual_files_path, :on_card_finished + attr_accessor :png_files_path, :png_export_width, :pdf_card_size, :pdf_dpi + attr_accessor :pdf_target + + def initialize + @index = 0 + end + + def configure + yield self + end + + def process(&block) + @process = block + end + + def card_finished! + @on_card_finished.call(@index) if @on_card_finished + + @index += 1 + end + + def on_card_finished(&block) + @on_card_finished = block + end + + def file + @file ||= SVGGVS::File.new(@svg_source) + end + + def run + @process.call + end + + def with_new_target + file.with_new_target do |target| + yield target + end + + card_finished! + end + end +end + diff --git a/lib/svggvs/target.rb b/lib/svggvs/target.rb new file mode 100644 index 0000000..36b96f3 --- /dev/null +++ b/lib/svggvs/target.rb @@ -0,0 +1,43 @@ +require 'delegate' + +module SVGGVS + class Target < SimpleDelegator + attr_reader :target + + def initialize(target) + @target = target + end + + def __getobj__ + @target + end + + def active_layers=(layers) + css("g[inkscape|groupmode='layer']").each do |layer| + if layers.include?(layer['inkscape:label']) + layer['style'] = '' + else + layer['style'] = 'display:none' + end + end + end + + def replacements=(replacements) + @replacements = replacements + end + + def replaced(node = @target) + if !!@replacements + node.children.each do |child| + if child.text? + if match = child.content[%r{\{% ([^ ]+) %\}}, 1] + child.content = @replacements[match] || '' + end + else + replaced(child) + end + end + end + end + end +end diff --git a/lib/svggvs/version.rb b/lib/svggvs/version.rb new file mode 100644 index 0000000..bf07a32 --- /dev/null +++ b/lib/svggvs/version.rb @@ -0,0 +1,3 @@ +module Svggvs + VERSION = "0.0.1" +end diff --git a/svggvs.gemspec b/svggvs.gemspec new file mode 100644 index 0000000..b76bf48 --- /dev/null +++ b/svggvs.gemspec @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'svggvs/version' + +Gem::Specification.new do |gem| + gem.name = "svggvs" + gem.version = Svggvs::VERSION + gem.authors = ["John Bintz"] + gem.email = ["john@coswellproductions.com"] + gem.description = %q{TODO: Write a gem description} + gem.summary = %q{TODO: Write a gem summary} + gem.homepage = "" + + gem.files = `git ls-files`.split($/) + gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.require_paths = ["lib"] + + gem.add_dependency 'nokogiri' + gem.add_dependency 'thor' +end