squib 0.14.2 → 0.14.3.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +41 -41
- data/.travis.yml +19 -19
- data/CHANGELOG.md +366 -357
- data/CONTRIBUTING.md +40 -40
- data/Gemfile +2 -2
- data/LICENSE.txt +22 -22
- data/README.md +121 -121
- data/RELEASE TODO.md +21 -21
- data/Rakefile +48 -48
- data/appveyor.yml +24 -24
- data/bin/squib +4 -4
- data/lib/squib.rb +32 -32
- data/lib/squib/api/background.rb +15 -15
- data/lib/squib/api/data.rb +137 -137
- data/lib/squib/api/groups.rb +54 -54
- data/lib/squib/api/image.rb +49 -49
- data/lib/squib/api/save.rb +83 -83
- data/lib/squib/api/settings.rb +21 -21
- data/lib/squib/api/shapes.rb +124 -124
- data/lib/squib/api/text.rb +25 -25
- data/lib/squib/api/text_embed.rb +71 -71
- data/lib/squib/api/units.rb +27 -27
- data/lib/squib/args/arg_loader.rb +126 -126
- data/lib/squib/args/box.rb +55 -55
- data/lib/squib/args/card_range.rb +32 -32
- data/lib/squib/args/color_validator.rb +12 -12
- data/lib/squib/args/coords.rb +35 -35
- data/lib/squib/args/csv_opts.rb +25 -25
- data/lib/squib/args/dir_validator.rb +16 -16
- data/lib/squib/args/draw.rb +92 -92
- data/lib/squib/args/embed_adjust.rb +25 -25
- data/lib/squib/args/embed_key.rb +17 -17
- data/lib/squib/args/hand_special.rb +37 -37
- data/lib/squib/args/import.rb +40 -40
- data/lib/squib/args/input_file.rb +37 -37
- data/lib/squib/args/paint.rb +44 -44
- data/lib/squib/args/paragraph.rb +116 -116
- data/lib/squib/args/save_batch.rb +63 -63
- data/lib/squib/args/scale_box.rb +53 -53
- data/lib/squib/args/sheet.rb +172 -172
- data/lib/squib/args/showcase_special.rb +38 -38
- data/lib/squib/args/sprue_file.rb +44 -44
- data/lib/squib/args/svg_special.rb +37 -37
- data/lib/squib/args/transform.rb +61 -61
- data/lib/squib/args/typographer.rb +119 -119
- data/lib/squib/args/unit_conversion.rb +29 -29
- data/lib/squib/builtin/layouts/economy.yml +85 -85
- data/lib/squib/builtin/layouts/fantasy.yml +101 -101
- data/lib/squib/builtin/layouts/hand.yml +62 -62
- data/lib/squib/builtin/layouts/party.yml +94 -94
- data/lib/squib/builtin/layouts/playing-card.yml +35 -35
- data/lib/squib/builtin/layouts/tuck_box.yml +46 -46
- data/lib/squib/builtin/projects/advanced/.gitignore +4 -4
- data/lib/squib/builtin/projects/advanced/ABOUT.md +19 -19
- data/lib/squib/builtin/projects/advanced/Gemfile +11 -11
- data/lib/squib/builtin/projects/advanced/Guardfile +21 -21
- data/lib/squib/builtin/projects/advanced/IDEAS.md +22 -22
- data/lib/squib/builtin/projects/advanced/PLAYTESTING.md +26 -26
- data/lib/squib/builtin/projects/advanced/Rakefile +27 -27
- data/lib/squib/builtin/projects/advanced/config.yml +53 -49
- data/lib/squib/builtin/projects/advanced/docs/PNP NOTES.md +3 -3
- data/lib/squib/builtin/projects/advanced/docs/RULES.md +21 -21
- data/lib/squib/builtin/projects/advanced/img/example.svg +60 -60
- data/lib/squib/builtin/projects/advanced/layouts/deck.yml +27 -27
- data/lib/squib/builtin/projects/advanced/src/deck.rb +34 -34
- data/lib/squib/builtin/projects/advanced/src/version.rb +3 -3
- data/lib/squib/builtin/projects/basic/.gitignore +4 -4
- data/lib/squib/builtin/projects/basic/ABOUT.md +19 -19
- data/lib/squib/builtin/projects/basic/Gemfile +3 -3
- data/lib/squib/builtin/projects/basic/IDEAS.md +22 -22
- data/lib/squib/builtin/projects/basic/PLAYTESTING.md +26 -26
- data/lib/squib/builtin/projects/basic/PNP NOTES.md +3 -3
- data/lib/squib/builtin/projects/basic/RULES.md +21 -21
- data/lib/squib/builtin/projects/basic/Rakefile +7 -7
- data/lib/squib/builtin/projects/basic/config.yml +53 -49
- data/lib/squib/builtin/projects/basic/deck.rb +6 -6
- data/lib/squib/builtin/sprues/a4_euro_card.yml +42 -42
- data/lib/squib/builtin/sprues/a4_poker_card_8up.yml +40 -40
- data/lib/squib/builtin/sprues/a4_poker_card_9up.yml +42 -42
- data/lib/squib/builtin/sprues/a4_usa_card.yml +42 -42
- data/lib/squib/builtin/sprues/drivethrucards_1up.yml +10 -10
- data/lib/squib/builtin/sprues/letter_poker_card_9up.yml +25 -25
- data/lib/squib/builtin/sprues/letter_poker_foldable_8up.yml +52 -52
- data/lib/squib/builtin/sprues/printplaygames_18up.yml +68 -68
- data/lib/squib/card.rb +75 -75
- data/lib/squib/commands/cli.rb +39 -39
- data/lib/squib/commands/data/template_option.rb +109 -109
- data/lib/squib/commands/make_sprue.rb +275 -275
- data/lib/squib/commands/new.rb +77 -77
- data/lib/squib/conf.rb +144 -139
- data/lib/squib/constants.rb +17 -17
- data/lib/squib/deck.rb +116 -116
- data/lib/squib/graphics/background.rb +14 -14
- data/lib/squib/graphics/cairo_context_wrapper.rb +113 -113
- data/lib/squib/graphics/embedding_utils.rb +28 -28
- data/lib/squib/graphics/gradient_regex.rb +47 -47
- data/lib/squib/graphics/hand.rb +42 -42
- data/lib/squib/graphics/image.rb +129 -108
- data/lib/squib/graphics/save_doc.rb +61 -61
- data/lib/squib/graphics/save_images.rb +52 -52
- data/lib/squib/graphics/save_pdf.rb +90 -90
- data/lib/squib/graphics/save_sprue.rb +204 -204
- data/lib/squib/graphics/shapes.rb +143 -143
- data/lib/squib/graphics/showcase.rb +85 -85
- data/lib/squib/graphics/text.rb +174 -174
- data/lib/squib/import/data_frame.rb +108 -108
- data/lib/squib/layout_parser.rb +138 -138
- data/lib/squib/progress.rb +38 -38
- data/lib/squib/sample_helpers.rb +34 -34
- data/lib/squib/sprues/crop_line.rb +28 -28
- data/lib/squib/sprues/crop_line_dash.rb +35 -35
- data/lib/squib/sprues/invalid_sprue_definition.rb +9 -9
- data/lib/squib/sprues/sprue.rb +203 -203
- data/lib/squib/sprues/sprue_schema.rb +48 -48
- data/lib/squib/version.rb +10 -10
- data/samples/autoscale_font/_autoscale_font.rb +29 -29
- data/samples/backend/_backend.rb +26 -26
- data/samples/basic.rb +19 -19
- data/samples/bug256/_bug256.rb +13 -0
- data/samples/build_groups/build_groups.rb +36 -36
- data/samples/colors/_colors.rb +38 -38
- data/samples/colors/_gradients.rb +34 -34
- data/samples/config/config_text_markup.rb +20 -20
- data/samples/config/custom_config.rb +18 -18
- data/samples/data/_csv.rb +33 -33
- data/samples/data/_excel.rb +55 -55
- data/samples/data/_yaml.rb +12 -12
- data/samples/hello_world.rb +6 -6
- data/samples/images/_cairo_access.rb +39 -39
- data/samples/images/_images.rb +104 -104
- data/samples/images/_more_load_images.rb +102 -102
- data/samples/intro/01_hello.rb +8 -8
- data/samples/intro/02_options.rb +14 -14
- data/samples/intro/03_layout.rb +11 -11
- data/samples/intro/04_arrays.rb +15 -15
- data/samples/intro/05_excel.rb +14 -14
- data/samples/layouts/builtin_layouts.rb +97 -97
- data/samples/layouts/layouts.rb +71 -71
- data/samples/project/src/characters.rb +8 -8
- data/samples/project/src/skills.rb +7 -7
- data/samples/proofs/_tgc_proofs.rb +16 -16
- data/samples/ranges/_ranges.rb +64 -64
- data/samples/saves/_hand.rb +23 -23
- data/samples/saves/_portrait_landscape.rb +23 -23
- data/samples/saves/_save_pdf.rb +29 -29
- data/samples/saves/_saves.rb +51 -51
- data/samples/saves/_showcase.rb +25 -25
- data/samples/shapes/_draw_shapes.rb +60 -60
- data/samples/shapes/_proofs.rb +22 -22
- data/samples/sprues/_advanced_sprues.rb +24 -24
- data/samples/sprues/_builtin_sprues.rb +21 -21
- data/samples/sprues/_fold_sheet.rb +27 -27
- data/samples/sprues/_hex_tiles.rb +15 -15
- data/samples/sprues/_mints.rb +11 -11
- data/samples/sprues/_sprue_example.rb +11 -11
- data/samples/text/_embed_text.rb +128 -128
- data/samples/text/_text.rb +47 -47
- data/samples/text/_text_options.rb +102 -102
- data/samples/text/bug134.rb +14 -14
- data/samples/units/_units.rb +32 -32
- data/squib.gemspec +52 -52
- metadata +51 -48
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
module Squib
|
|
2
|
-
module Sprues
|
|
3
|
-
class CropLineDash
|
|
4
|
-
VALIDATION_REGEX = /%r{
|
|
5
|
-
^(\d*[.])?\d+(in|cm|mm)
|
|
6
|
-
\s+
|
|
7
|
-
(\d*[.])?\d+(in|cm|mm)$
|
|
8
|
-
}x/
|
|
9
|
-
|
|
10
|
-
attr_reader :pattern
|
|
11
|
-
|
|
12
|
-
def initialize(value, dpi)
|
|
13
|
-
if value == :solid
|
|
14
|
-
@pattern = nil
|
|
15
|
-
elsif value == :dotted
|
|
16
|
-
@pattern = [
|
|
17
|
-
Args::UnitConversion.parse('0.2mm', dpi),
|
|
18
|
-
Args::UnitConversion.parse('0.5mm', dpi)
|
|
19
|
-
]
|
|
20
|
-
elsif value == :dashed
|
|
21
|
-
@pattern = [
|
|
22
|
-
Args::UnitConversion.parse('2mm', dpi),
|
|
23
|
-
Args::UnitConversion.parse('2mm', dpi)
|
|
24
|
-
]
|
|
25
|
-
elsif value.is_a? String
|
|
26
|
-
@pattern = value.split(' ').map do |val|
|
|
27
|
-
Args::UnitConversion.parse val, dpi
|
|
28
|
-
end
|
|
29
|
-
else
|
|
30
|
-
raise ArgumentError, 'Unsupported dash style'
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
1
|
+
module Squib
|
|
2
|
+
module Sprues
|
|
3
|
+
class CropLineDash
|
|
4
|
+
VALIDATION_REGEX = /%r{
|
|
5
|
+
^(\d*[.])?\d+(in|cm|mm)
|
|
6
|
+
\s+
|
|
7
|
+
(\d*[.])?\d+(in|cm|mm)$
|
|
8
|
+
}x/
|
|
9
|
+
|
|
10
|
+
attr_reader :pattern
|
|
11
|
+
|
|
12
|
+
def initialize(value, dpi)
|
|
13
|
+
if value == :solid
|
|
14
|
+
@pattern = nil
|
|
15
|
+
elsif value == :dotted
|
|
16
|
+
@pattern = [
|
|
17
|
+
Args::UnitConversion.parse('0.2mm', dpi),
|
|
18
|
+
Args::UnitConversion.parse('0.5mm', dpi)
|
|
19
|
+
]
|
|
20
|
+
elsif value == :dashed
|
|
21
|
+
@pattern = [
|
|
22
|
+
Args::UnitConversion.parse('2mm', dpi),
|
|
23
|
+
Args::UnitConversion.parse('2mm', dpi)
|
|
24
|
+
]
|
|
25
|
+
elsif value.is_a? String
|
|
26
|
+
@pattern = value.split(' ').map do |val|
|
|
27
|
+
Args::UnitConversion.parse val, dpi
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
raise ArgumentError, 'Unsupported dash style'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module Squib
|
|
2
|
-
module Sprues
|
|
3
|
-
class InvalidSprueDefinition < StandardError
|
|
4
|
-
def initialize(file, error)
|
|
5
|
-
super("Invalid sprue definition in file: #{file}. #{error.message}")
|
|
6
|
-
end
|
|
7
|
-
end
|
|
8
|
-
end
|
|
9
|
-
end
|
|
1
|
+
module Squib
|
|
2
|
+
module Sprues
|
|
3
|
+
class InvalidSprueDefinition < StandardError
|
|
4
|
+
def initialize(file, error)
|
|
5
|
+
super("Invalid sprue definition in file: #{file}. #{error.message}")
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
data/lib/squib/sprues/sprue.rb
CHANGED
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
require 'yaml'
|
|
2
|
-
require 'classy_hash'
|
|
3
|
-
require_relative '../args/color_validator'
|
|
4
|
-
require_relative '../args/unit_conversion'
|
|
5
|
-
require_relative 'crop_line'
|
|
6
|
-
require_relative 'crop_line_dash'
|
|
7
|
-
require_relative 'invalid_sprue_definition'
|
|
8
|
-
require_relative 'sprue_schema'
|
|
9
|
-
|
|
10
|
-
module Squib
|
|
11
|
-
class Sprue
|
|
12
|
-
include Args::ColorValidator
|
|
13
|
-
|
|
14
|
-
# Defaults are set for poker sized deck on a A4 sheet, with no cards
|
|
15
|
-
DEFAULTS = {
|
|
16
|
-
'sheet_width' => nil,
|
|
17
|
-
'sheet_height' => nil,
|
|
18
|
-
'card_width' => nil,
|
|
19
|
-
'card_height' => nil,
|
|
20
|
-
'dpi' => 300,
|
|
21
|
-
'position_reference' => :topleft,
|
|
22
|
-
'rotate' => 0.0,
|
|
23
|
-
'crop_line' => {
|
|
24
|
-
'style' => :solid,
|
|
25
|
-
'width' => '0.02mm',
|
|
26
|
-
'color' => :black,
|
|
27
|
-
'overlay' => :on_margin,
|
|
28
|
-
'lines' => []
|
|
29
|
-
},
|
|
30
|
-
'cards' => []
|
|
31
|
-
}.freeze
|
|
32
|
-
|
|
33
|
-
attr_reader :dpi
|
|
34
|
-
|
|
35
|
-
def initialize(template_hash, dpi)
|
|
36
|
-
@template_hash = template_hash
|
|
37
|
-
@dpi = dpi
|
|
38
|
-
@crop_line_default = @template_hash['crop_line'].select do |k, _|
|
|
39
|
-
%w[style width color].include? k
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Load the template definition file
|
|
44
|
-
def self.load(file, dpi)
|
|
45
|
-
yaml = {}
|
|
46
|
-
thefile = file if File.exist?(file) # use custom first
|
|
47
|
-
thefile = builtin(file) if File.exist?(builtin(file)) # then builtin
|
|
48
|
-
unless File.exist?(thefile)
|
|
49
|
-
Squib::logger.error("Sprue not found: #{file}. Falling back to defaults.")
|
|
50
|
-
end
|
|
51
|
-
yaml = YAML.load_file(thefile) || {} if File.exist? thefile
|
|
52
|
-
# Bake the default values into our sprue
|
|
53
|
-
new_hash = DEFAULTS.merge(yaml)
|
|
54
|
-
new_hash['crop_line'] = DEFAULTS['crop_line'].
|
|
55
|
-
merge(new_hash['crop_line'])
|
|
56
|
-
warn_unrecognized(yaml)
|
|
57
|
-
|
|
58
|
-
# Validate
|
|
59
|
-
begin
|
|
60
|
-
require 'benchmark'
|
|
61
|
-
ClassyHash.validate(new_hash, Sprues::SCHEMA)
|
|
62
|
-
rescue ClassyHash::SchemaViolationError => e
|
|
63
|
-
raise Sprues::InvalidSprueDefinition.new(thefile, e)
|
|
64
|
-
end
|
|
65
|
-
Sprue.new new_hash, dpi
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def sheet_width
|
|
69
|
-
Args::UnitConversion.parse @template_hash['sheet_width'], @dpi
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def sheet_height
|
|
73
|
-
Args::UnitConversion.parse @template_hash['sheet_height'], @dpi
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def card_width
|
|
77
|
-
Args::UnitConversion.parse @template_hash['card_width'], @dpi
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def card_height
|
|
81
|
-
Args::UnitConversion.parse @template_hash['card_height'], @dpi
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def card_default_rotation
|
|
85
|
-
parse_rotate_param @template_hash['rotate']
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def crop_line_overlay
|
|
89
|
-
@template_hash['crop_line']['overlay']
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def crop_lines
|
|
93
|
-
lines = @template_hash['crop_line']['lines'].map(
|
|
94
|
-
&method(:parse_crop_line)
|
|
95
|
-
)
|
|
96
|
-
if block_given?
|
|
97
|
-
lines.each { |v| yield v }
|
|
98
|
-
else
|
|
99
|
-
lines
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def cards
|
|
104
|
-
parsed_cards = @template_hash['cards'].map(&method(:parse_card))
|
|
105
|
-
if block_given?
|
|
106
|
-
parsed_cards.each { |v| yield v }
|
|
107
|
-
else
|
|
108
|
-
parsed_cards
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def margin
|
|
113
|
-
# NOTE: There's a baseline of 0.25mm that we can 100% make sure that we
|
|
114
|
-
# can overlap really thin lines on the PDF
|
|
115
|
-
crop_line_width = [
|
|
116
|
-
Args::UnitConversion.parse(@template_hash['crop_line']['width'], @dpi),
|
|
117
|
-
Args::UnitConversion.parse('0.25mm', @dpi)
|
|
118
|
-
].max
|
|
119
|
-
|
|
120
|
-
parsed_cards = cards
|
|
121
|
-
left, right = parsed_cards.minmax { |a, b| a['x'] <=> b['x'] }
|
|
122
|
-
top, bottom = parsed_cards.minmax { |a, b| a['y'] <=> b['y'] }
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
left: left['x'] - crop_line_width,
|
|
126
|
-
right: right['x'] + card_width + crop_line_width,
|
|
127
|
-
top: top['y'] - crop_line_width,
|
|
128
|
-
bottom: bottom['y'] + card_height + crop_line_width
|
|
129
|
-
}
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Warn unrecognized options in the template sheet
|
|
133
|
-
def self.warn_unrecognized(yaml)
|
|
134
|
-
unrec = yaml.keys - DEFAULTS.keys
|
|
135
|
-
return unless unrec.any?
|
|
136
|
-
|
|
137
|
-
Squib.logger.warn(
|
|
138
|
-
"Unrecognized configuration option(s): #{unrec.join(',')}"
|
|
139
|
-
)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
private
|
|
143
|
-
|
|
144
|
-
# Return path for built-in sheet templates
|
|
145
|
-
def self.builtin(file)
|
|
146
|
-
"#{File.dirname(__FILE__)}/../builtin/sprues/#{file}"
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Parse crop line definitions from template.
|
|
150
|
-
def parse_crop_line(line)
|
|
151
|
-
new_line = @crop_line_default.merge line
|
|
152
|
-
new_line['width'] = Args::UnitConversion.parse(new_line['width'], @dpi)
|
|
153
|
-
new_line['color'] = colorify new_line['color']
|
|
154
|
-
new_line['style_desc'] = new_line['style']
|
|
155
|
-
new_line['style'] = Sprues::CropLineDash.new(new_line['style'], @dpi)
|
|
156
|
-
new_line['line'] = Sprues::CropLine.new(
|
|
157
|
-
new_line['type'], new_line['position'], sheet_width, sheet_height, @dpi
|
|
158
|
-
)
|
|
159
|
-
new_line
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Parse card definitions from template.
|
|
163
|
-
def parse_card(card)
|
|
164
|
-
new_card = card.clone
|
|
165
|
-
|
|
166
|
-
x = Args::UnitConversion.parse(card['x'], @dpi)
|
|
167
|
-
y = Args::UnitConversion.parse(card['y'], @dpi)
|
|
168
|
-
if @template_hash['position_reference'] == :center
|
|
169
|
-
# Normalize it to a top-left positional reference
|
|
170
|
-
x -= card_width / 2
|
|
171
|
-
y -= card_height / 2
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
new_card['x'] = x
|
|
175
|
-
new_card['y'] = y
|
|
176
|
-
new_card['rotate'] = parse_rotate_param(
|
|
177
|
-
card['rotate'] ? card['rotate'] : @template_hash['rotate'])
|
|
178
|
-
new_card
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def parse_rotate_param(val)
|
|
182
|
-
if val == :clockwise
|
|
183
|
-
0.5 * Math::PI
|
|
184
|
-
elsif val == :counterclockwise
|
|
185
|
-
1.5 * Math::PI
|
|
186
|
-
elsif val == :turnaround
|
|
187
|
-
Math::PI
|
|
188
|
-
elsif val.is_a? String
|
|
189
|
-
if val.end_with? 'deg'
|
|
190
|
-
val.gsub(/deg$/, '').to_f / 180 * Math::PI
|
|
191
|
-
elsif val.end_with? 'rad'
|
|
192
|
-
val.gsub(/rad$/, '').to_f
|
|
193
|
-
else
|
|
194
|
-
val.to_f
|
|
195
|
-
end
|
|
196
|
-
elsif val.nil?
|
|
197
|
-
0.0
|
|
198
|
-
else
|
|
199
|
-
val.to_f
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'classy_hash'
|
|
3
|
+
require_relative '../args/color_validator'
|
|
4
|
+
require_relative '../args/unit_conversion'
|
|
5
|
+
require_relative 'crop_line'
|
|
6
|
+
require_relative 'crop_line_dash'
|
|
7
|
+
require_relative 'invalid_sprue_definition'
|
|
8
|
+
require_relative 'sprue_schema'
|
|
9
|
+
|
|
10
|
+
module Squib
|
|
11
|
+
class Sprue
|
|
12
|
+
include Args::ColorValidator
|
|
13
|
+
|
|
14
|
+
# Defaults are set for poker sized deck on a A4 sheet, with no cards
|
|
15
|
+
DEFAULTS = {
|
|
16
|
+
'sheet_width' => nil,
|
|
17
|
+
'sheet_height' => nil,
|
|
18
|
+
'card_width' => nil,
|
|
19
|
+
'card_height' => nil,
|
|
20
|
+
'dpi' => 300,
|
|
21
|
+
'position_reference' => :topleft,
|
|
22
|
+
'rotate' => 0.0,
|
|
23
|
+
'crop_line' => {
|
|
24
|
+
'style' => :solid,
|
|
25
|
+
'width' => '0.02mm',
|
|
26
|
+
'color' => :black,
|
|
27
|
+
'overlay' => :on_margin,
|
|
28
|
+
'lines' => []
|
|
29
|
+
},
|
|
30
|
+
'cards' => []
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
attr_reader :dpi
|
|
34
|
+
|
|
35
|
+
def initialize(template_hash, dpi)
|
|
36
|
+
@template_hash = template_hash
|
|
37
|
+
@dpi = dpi
|
|
38
|
+
@crop_line_default = @template_hash['crop_line'].select do |k, _|
|
|
39
|
+
%w[style width color].include? k
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Load the template definition file
|
|
44
|
+
def self.load(file, dpi)
|
|
45
|
+
yaml = {}
|
|
46
|
+
thefile = file if File.exist?(file) # use custom first
|
|
47
|
+
thefile = builtin(file) if File.exist?(builtin(file)) # then builtin
|
|
48
|
+
unless File.exist?(thefile)
|
|
49
|
+
Squib::logger.error("Sprue not found: #{file}. Falling back to defaults.")
|
|
50
|
+
end
|
|
51
|
+
yaml = YAML.load_file(thefile) || {} if File.exist? thefile
|
|
52
|
+
# Bake the default values into our sprue
|
|
53
|
+
new_hash = DEFAULTS.merge(yaml)
|
|
54
|
+
new_hash['crop_line'] = DEFAULTS['crop_line'].
|
|
55
|
+
merge(new_hash['crop_line'])
|
|
56
|
+
warn_unrecognized(yaml)
|
|
57
|
+
|
|
58
|
+
# Validate
|
|
59
|
+
begin
|
|
60
|
+
require 'benchmark'
|
|
61
|
+
ClassyHash.validate(new_hash, Sprues::SCHEMA)
|
|
62
|
+
rescue ClassyHash::SchemaViolationError => e
|
|
63
|
+
raise Sprues::InvalidSprueDefinition.new(thefile, e)
|
|
64
|
+
end
|
|
65
|
+
Sprue.new new_hash, dpi
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def sheet_width
|
|
69
|
+
Args::UnitConversion.parse @template_hash['sheet_width'], @dpi
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def sheet_height
|
|
73
|
+
Args::UnitConversion.parse @template_hash['sheet_height'], @dpi
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def card_width
|
|
77
|
+
Args::UnitConversion.parse @template_hash['card_width'], @dpi
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def card_height
|
|
81
|
+
Args::UnitConversion.parse @template_hash['card_height'], @dpi
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def card_default_rotation
|
|
85
|
+
parse_rotate_param @template_hash['rotate']
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def crop_line_overlay
|
|
89
|
+
@template_hash['crop_line']['overlay']
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def crop_lines
|
|
93
|
+
lines = @template_hash['crop_line']['lines'].map(
|
|
94
|
+
&method(:parse_crop_line)
|
|
95
|
+
)
|
|
96
|
+
if block_given?
|
|
97
|
+
lines.each { |v| yield v }
|
|
98
|
+
else
|
|
99
|
+
lines
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def cards
|
|
104
|
+
parsed_cards = @template_hash['cards'].map(&method(:parse_card))
|
|
105
|
+
if block_given?
|
|
106
|
+
parsed_cards.each { |v| yield v }
|
|
107
|
+
else
|
|
108
|
+
parsed_cards
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def margin
|
|
113
|
+
# NOTE: There's a baseline of 0.25mm that we can 100% make sure that we
|
|
114
|
+
# can overlap really thin lines on the PDF
|
|
115
|
+
crop_line_width = [
|
|
116
|
+
Args::UnitConversion.parse(@template_hash['crop_line']['width'], @dpi),
|
|
117
|
+
Args::UnitConversion.parse('0.25mm', @dpi)
|
|
118
|
+
].max
|
|
119
|
+
|
|
120
|
+
parsed_cards = cards
|
|
121
|
+
left, right = parsed_cards.minmax { |a, b| a['x'] <=> b['x'] }
|
|
122
|
+
top, bottom = parsed_cards.minmax { |a, b| a['y'] <=> b['y'] }
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
left: left['x'] - crop_line_width,
|
|
126
|
+
right: right['x'] + card_width + crop_line_width,
|
|
127
|
+
top: top['y'] - crop_line_width,
|
|
128
|
+
bottom: bottom['y'] + card_height + crop_line_width
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Warn unrecognized options in the template sheet
|
|
133
|
+
def self.warn_unrecognized(yaml)
|
|
134
|
+
unrec = yaml.keys - DEFAULTS.keys
|
|
135
|
+
return unless unrec.any?
|
|
136
|
+
|
|
137
|
+
Squib.logger.warn(
|
|
138
|
+
"Unrecognized configuration option(s): #{unrec.join(',')}"
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
# Return path for built-in sheet templates
|
|
145
|
+
def self.builtin(file)
|
|
146
|
+
"#{File.dirname(__FILE__)}/../builtin/sprues/#{file}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Parse crop line definitions from template.
|
|
150
|
+
def parse_crop_line(line)
|
|
151
|
+
new_line = @crop_line_default.merge line
|
|
152
|
+
new_line['width'] = Args::UnitConversion.parse(new_line['width'], @dpi)
|
|
153
|
+
new_line['color'] = colorify new_line['color']
|
|
154
|
+
new_line['style_desc'] = new_line['style']
|
|
155
|
+
new_line['style'] = Sprues::CropLineDash.new(new_line['style'], @dpi)
|
|
156
|
+
new_line['line'] = Sprues::CropLine.new(
|
|
157
|
+
new_line['type'], new_line['position'], sheet_width, sheet_height, @dpi
|
|
158
|
+
)
|
|
159
|
+
new_line
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Parse card definitions from template.
|
|
163
|
+
def parse_card(card)
|
|
164
|
+
new_card = card.clone
|
|
165
|
+
|
|
166
|
+
x = Args::UnitConversion.parse(card['x'], @dpi)
|
|
167
|
+
y = Args::UnitConversion.parse(card['y'], @dpi)
|
|
168
|
+
if @template_hash['position_reference'] == :center
|
|
169
|
+
# Normalize it to a top-left positional reference
|
|
170
|
+
x -= card_width / 2
|
|
171
|
+
y -= card_height / 2
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
new_card['x'] = x
|
|
175
|
+
new_card['y'] = y
|
|
176
|
+
new_card['rotate'] = parse_rotate_param(
|
|
177
|
+
card['rotate'] ? card['rotate'] : @template_hash['rotate'])
|
|
178
|
+
new_card
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def parse_rotate_param(val)
|
|
182
|
+
if val == :clockwise
|
|
183
|
+
0.5 * Math::PI
|
|
184
|
+
elsif val == :counterclockwise
|
|
185
|
+
1.5 * Math::PI
|
|
186
|
+
elsif val == :turnaround
|
|
187
|
+
Math::PI
|
|
188
|
+
elsif val.is_a? String
|
|
189
|
+
if val.end_with? 'deg'
|
|
190
|
+
val.gsub(/deg$/, '').to_f / 180 * Math::PI
|
|
191
|
+
elsif val.end_with? 'rad'
|
|
192
|
+
val.gsub(/rad$/, '').to_f
|
|
193
|
+
else
|
|
194
|
+
val.to_f
|
|
195
|
+
end
|
|
196
|
+
elsif val.nil?
|
|
197
|
+
0.0
|
|
198
|
+
else
|
|
199
|
+
val.to_f
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|