mtg_card_maker 0.1.0

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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/CODE_OF_CONDUCT.md +131 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.md +305 -0
  6. data/bin/mtg_card_maker +7 -0
  7. data/lib/mtg_card_maker/base_card.rb +210 -0
  8. data/lib/mtg_card_maker/cli.rb +221 -0
  9. data/lib/mtg_card_maker/color_palette.rb +135 -0
  10. data/lib/mtg_card_maker/color_scheme.rb +305 -0
  11. data/lib/mtg_card_maker/core_ext/deep_merge.rb +21 -0
  12. data/lib/mtg_card_maker/fonts/Goudy Mediaeval DemiBold.ttf +0 -0
  13. data/lib/mtg_card_maker/fonts/goudy_base64.txt +1 -0
  14. data/lib/mtg_card_maker/icon_service.rb +95 -0
  15. data/lib/mtg_card_maker/icons/black.svg +1 -0
  16. data/lib/mtg_card_maker/icons/blue.svg +1 -0
  17. data/lib/mtg_card_maker/icons/colorless.svg +1 -0
  18. data/lib/mtg_card_maker/icons/green.svg +1 -0
  19. data/lib/mtg_card_maker/icons/jsharp.svg +1 -0
  20. data/lib/mtg_card_maker/icons/qrcode.svg +1 -0
  21. data/lib/mtg_card_maker/icons/red.svg +1 -0
  22. data/lib/mtg_card_maker/icons/white.svg +1 -0
  23. data/lib/mtg_card_maker/layer_config.rb +289 -0
  24. data/lib/mtg_card_maker/layer_factory.rb +122 -0
  25. data/lib/mtg_card_maker/layer_initializer.rb +12 -0
  26. data/lib/mtg_card_maker/layers/art_layer.rb +63 -0
  27. data/lib/mtg_card_maker/layers/border_layer.rb +166 -0
  28. data/lib/mtg_card_maker/layers/frame_layer.rb +62 -0
  29. data/lib/mtg_card_maker/layers/name_layer.rb +82 -0
  30. data/lib/mtg_card_maker/layers/power_layer.rb +69 -0
  31. data/lib/mtg_card_maker/layers/text_box_layer.rb +107 -0
  32. data/lib/mtg_card_maker/layers/type_line_layer.rb +86 -0
  33. data/lib/mtg_card_maker/mana_cost.rb +220 -0
  34. data/lib/mtg_card_maker/metallic_renderer.rb +174 -0
  35. data/lib/mtg_card_maker/sprite_sheet_assets.rb +158 -0
  36. data/lib/mtg_card_maker/sprite_sheet_builder.rb +90 -0
  37. data/lib/mtg_card_maker/sprite_sheet_service.rb +126 -0
  38. data/lib/mtg_card_maker/svg_gradient_service.rb +159 -0
  39. data/lib/mtg_card_maker/text_rendering_service.rb +160 -0
  40. data/lib/mtg_card_maker/version.rb +8 -0
  41. data/lib/mtg_card_maker.rb +268 -0
  42. data/sig/mtg_card_maker.rbs +4 -0
  43. metadata +149 -0
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MtgCardMaker
4
+ # Simplified gradient definitions for SVG cards
5
+ # Removes complex abstraction in favor of direct gradient creation
6
+ class SvgGradientService # rubocop:disable Metrics/ClassLength
7
+ class << self
8
+ # Defines all gradients needed for a color scheme
9
+ def define_all_gradients(svg, color_scheme = DEFAULT_COLOR_SCHEME)
10
+ svg.defs do
11
+ define_standard_gradients(svg, color_scheme)
12
+ define_metallic_gradients(svg, color_scheme) if metallic_properties?(color_scheme)
13
+ end
14
+ end
15
+
16
+ # Get gradient ID for card gradient
17
+ def card_gradient_id(color_scheme)
18
+ "#{color_scheme.scheme_name}_card_gradient"
19
+ end
20
+
21
+ # Get gradient ID for frame gradient
22
+ def frame_gradient_id(color_scheme)
23
+ "#{color_scheme.scheme_name}_frame_gradient"
24
+ end
25
+
26
+ # Get gradient ID for name gradient
27
+ def name_gradient_id(color_scheme)
28
+ "#{color_scheme.scheme_name}_name_gradient"
29
+ end
30
+
31
+ # Get gradient ID for description gradient
32
+ def description_gradient_id(color_scheme)
33
+ "#{color_scheme.scheme_name}_description_gradient"
34
+ end
35
+
36
+ # Get gradient ID for metallic highlight gradient
37
+ def metallic_highlight_gradient_id(color_scheme)
38
+ "#{color_scheme.scheme_name}_metallic_highlight_gradient"
39
+ end
40
+
41
+ # Get gradient ID for metallic shadow gradient
42
+ def metallic_shadow_gradient_id(color_scheme)
43
+ "#{color_scheme.scheme_name}_metallic_shadow_gradient"
44
+ end
45
+
46
+ # Get pattern ID for metallic texture
47
+ def metallic_pattern_id(color_scheme)
48
+ "#{color_scheme.scheme_name}_metallic_pattern"
49
+ end
50
+
51
+ # Check if color scheme has metallic properties
52
+ def metallic_properties?(color_scheme)
53
+ [:gold, :colorless].include?(color_scheme.scheme_name)
54
+ end
55
+
56
+ def define_standard_gradients(svg, color_scheme)
57
+ scheme_name = color_scheme.scheme_name
58
+
59
+ define_card_gradient(svg, color_scheme, scheme_name)
60
+ define_frame_gradient(svg, color_scheme, scheme_name)
61
+ define_name_gradient(svg, color_scheme, scheme_name)
62
+ define_description_gradient(svg, color_scheme, scheme_name)
63
+ end
64
+
65
+ def define_card_gradient(svg, color_scheme, scheme_name)
66
+ svg.linearGradient id: "#{scheme_name}_card_gradient", x1: '0%', y1: '0%', x2: '100%', y2: '100%' do
67
+ svg.stop offset: '0%', 'stop-color': color_scheme.card_gradient_start
68
+ svg.stop offset: '50%', 'stop-color': color_scheme.card_gradient_middle
69
+ svg.stop offset: '100%', 'stop-color': color_scheme.card_gradient_end
70
+ end
71
+ end
72
+
73
+ def define_frame_gradient(svg, color_scheme, scheme_name)
74
+ svg.linearGradient id: "#{scheme_name}_frame_gradient", x1: '0%', y1: '0%', x2: '100%', y2: '100%' do
75
+ svg.stop offset: '0%', 'stop-color': color_scheme.frame_gradient_start
76
+ svg.stop offset: '50%', 'stop-color': color_scheme.frame_gradient_middle
77
+ svg.stop offset: '100%', 'stop-color': color_scheme.frame_gradient_end
78
+ end
79
+ end
80
+
81
+ def define_name_gradient(svg, color_scheme, scheme_name)
82
+ svg.linearGradient id: "#{scheme_name}_name_gradient", x1: '0%', y1: '0%', x2: '100%', y2: '100%' do
83
+ svg.stop offset: '0%', 'stop-color': color_scheme.name_gradient_start
84
+ svg.stop offset: '50%', 'stop-color': color_scheme.name_gradient_middle
85
+ svg.stop offset: '100%', 'stop-color': color_scheme.name_gradient_end
86
+ end
87
+ end
88
+
89
+ def define_description_gradient(svg, color_scheme, scheme_name)
90
+ svg.linearGradient id: "#{scheme_name}_description_gradient", x1: '0%', y1: '0%', x2: '100%', y2: '100%' do
91
+ svg.stop offset: '0%', 'stop-color': color_scheme.description_gradient_start
92
+ svg.stop offset: '50%', 'stop-color': color_scheme.description_gradient_middle
93
+ svg.stop offset: '100%', 'stop-color': color_scheme.description_gradient_end
94
+ end
95
+ end
96
+
97
+ def define_metallic_gradients(svg, color_scheme)
98
+ scheme_name = color_scheme.scheme_name
99
+
100
+ define_metallic_highlight_gradient(svg, color_scheme, scheme_name)
101
+ define_metallic_shadow_gradient(svg, color_scheme, scheme_name)
102
+ define_metallic_pattern(svg, color_scheme, scheme_name)
103
+ end
104
+
105
+ def define_metallic_highlight_gradient(svg, color_scheme, scheme_name)
106
+ svg.linearGradient id: "#{scheme_name}_metallic_highlight_gradient", x1: '0%', y1: '0%', x2: '100%',
107
+ y2: '100%' do
108
+ svg.stop offset: '0%', 'stop-color': color_scheme.metallic_highlight_end, 'stop-opacity': '0.6'
109
+ svg.stop offset: '20%', 'stop-color': color_scheme.metallic_highlight_middle, 'stop-opacity': '0.9'
110
+ svg.stop offset: '38%', 'stop-color': color_scheme.metallic_highlight_start, 'stop-opacity': '1.0'
111
+ svg.stop offset: '42%', 'stop-color': color_scheme.metallic_highlight_middle, 'stop-opacity': '0.7'
112
+ svg.stop offset: '62%', 'stop-color': color_scheme.metallic_highlight_end, 'stop-opacity': '0.5'
113
+ svg.stop offset: '73%', 'stop-color': color_scheme.metallic_highlight_start, 'stop-opacity': '0.8'
114
+ svg.stop offset: '80%', 'stop-color': color_scheme.metallic_highlight_middle, 'stop-opacity': '0.6'
115
+ svg.stop offset: '100%', 'stop-color': color_scheme.metallic_highlight_end, 'stop-opacity': '0.4'
116
+ end
117
+ end
118
+
119
+ def define_metallic_shadow_gradient(svg, color_scheme, scheme_name)
120
+ svg.radialGradient id: "#{scheme_name}_metallic_shadow_gradient", cx: '50%', cy: '50%', r: '70%' do
121
+ svg.stop offset: '0%', 'stop-color': color_scheme.metallic_shadow_start, 'stop-opacity': '0.3'
122
+ svg.stop offset: '50%', 'stop-color': color_scheme.metallic_shadow_middle, 'stop-opacity': '0.5'
123
+ svg.stop offset: '100%', 'stop-color': color_scheme.metallic_shadow_end, 'stop-opacity': '0.7'
124
+ end
125
+ end
126
+
127
+ def define_metallic_pattern(svg, color_scheme, scheme_name)
128
+ svg.pattern id: "#{scheme_name}_metallic_pattern", x: '0', y: '0', width: '20', height: '20',
129
+ patternUnits: 'userSpaceOnUse' do
130
+ add_metallic_pattern_lines(svg, color_scheme)
131
+ add_metallic_pattern_circles(svg, color_scheme)
132
+ end
133
+ end
134
+
135
+ def add_metallic_pattern_lines(svg, color_scheme)
136
+ svg.line x1: '0', y1: '0', x2: '20', y2: '20',
137
+ stroke: color_scheme.metallic_pattern_light,
138
+ 'stroke-width': '0.5',
139
+ opacity: '0.3'
140
+ svg.line x1: '20', y1: '0', x2: '0', y2: '20',
141
+ stroke: color_scheme.metallic_pattern_dark,
142
+ 'stroke-width': '0.5',
143
+ opacity: '0.2'
144
+ end
145
+
146
+ def add_metallic_pattern_circles(svg, color_scheme)
147
+ svg.circle cx: '5', cy: '5', r: '0.5',
148
+ fill: color_scheme.metallic_pattern_light,
149
+ opacity: '0.6'
150
+ svg.circle cx: '15', cy: '15', r: '0.5',
151
+ fill: color_scheme.metallic_pattern_light,
152
+ opacity: '0.6'
153
+ svg.circle cx: '10', cy: '10', r: '0.3',
154
+ fill: color_scheme.metallic_pattern_dark,
155
+ opacity: '0.4'
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MtgCardMaker
4
+ # Minimal text rendering service with basic word wrapping for SVG.
5
+ # This service handles text layout, wrapping, and attribute generation
6
+ # for SVG text elements with configurable font sizes, colors, and spacing.
7
+ #
8
+ # @example
9
+ # service = MtgCardMaker::TextRenderingService.new(
10
+ # text: "Lightning Bolt deals 3 damage to any target.",
11
+ # x: 40, y: 545, font_size: 24, available_width: 550
12
+ # )
13
+ # lines = service.wrapped_text_lines
14
+ #
15
+ # @since 0.1.0
16
+ class TextRenderingService
17
+ # @return [String] the text to render
18
+ attr_accessor :text
19
+
20
+ # @return [LayerConfig] the layer configuration
21
+ attr_accessor :layer_config
22
+
23
+ # @return [Integer] the x-coordinate for text positioning
24
+ attr_accessor :x
25
+
26
+ # @return [Integer] the y-coordinate for text positioning
27
+ attr_accessor :y
28
+
29
+ # @return [Integer] the font size
30
+ attr_accessor :font_size
31
+
32
+ # @return [String] the text color
33
+ attr_accessor :color
34
+
35
+ # @return [Integer] the available width for text wrapping
36
+ attr_accessor :available_width
37
+
38
+ # @return [Integer] the line height
39
+ attr_accessor :line_height
40
+
41
+ # @return [String, nil] the CSS class for styling
42
+ attr_accessor :css_class
43
+
44
+ # Initialize a new text rendering service
45
+ #
46
+ # @param kwargs [Hash] the initialization parameters
47
+ # @option kwargs [String] :text the text to render (default: '')
48
+ # @option kwargs [LayerConfig] :layer_config the layer configuration (default: LayerConfig.default)
49
+ # @option kwargs [Integer] :x the x-coordinate (default: 0)
50
+ # @option kwargs [Integer] :y the y-coordinate (default: 0)
51
+ # @option kwargs [Integer] :font_size the font size (default: from layer_config)
52
+ # @option kwargs [String] :color the text color (default: from layer_config)
53
+ # @option kwargs [Integer] :available_width the available width (default: CARD_WIDTH)
54
+ # @option kwargs [Integer] :line_height the line height (default: calculated)
55
+ # @option kwargs [String] :css_class the CSS class (default: nil)
56
+ def initialize(**kwargs)
57
+ # Set defaults
58
+ defaults = {
59
+ text: '',
60
+ layer_config: LayerConfig.default,
61
+ x: 0,
62
+ y: 0,
63
+ font_size: nil,
64
+ color: nil,
65
+ available_width: nil,
66
+ line_height: nil,
67
+ css_class: nil
68
+ }
69
+
70
+ # Merge defaults with provided kwargs
71
+ final_params = defaults.merge(kwargs)
72
+
73
+ # Set instance variables
74
+ final_params.each do |key, value|
75
+ send("#{key}=", value) if respond_to?("#{key}=")
76
+ end
77
+
78
+ # Post-process dependent values
79
+ @font_size ||= @layer_config.default_font_size
80
+ @color ||= @layer_config.default_text_color
81
+ @available_width ||= CARD_WIDTH
82
+ @line_height ||= calculate_default_line_height
83
+ end
84
+
85
+ # Returns an array of [line, attrs] for SVG text elements
86
+ #
87
+ # @param text [String, nil] optional text to override current text
88
+ # @return [Array<Array>] array of [line_text, attributes_hash] pairs
89
+ def wrapped_text_lines(text = nil)
90
+ @text = text if text
91
+ render_text_lines
92
+ end
93
+
94
+ private
95
+
96
+ def calculate_default_line_height
97
+ @font_size * @layer_config.default_line_height_multiplier
98
+ end
99
+
100
+ def render_text_lines
101
+ lines = text_to_lines
102
+ build_text_attributes(lines)
103
+ end
104
+
105
+ def text_to_lines
106
+ @text.to_s.split(/\r?\n/).flat_map do |line|
107
+ wrap_line(line)
108
+ end.reject(&:empty?)
109
+ end
110
+
111
+ def build_text_attributes(lines)
112
+ lines.each_with_index.map do |line, idx|
113
+ attrs = {
114
+ x: @x,
115
+ y: @y + (idx * @line_height).to_i,
116
+ fill: @color,
117
+ font_size: @font_size
118
+ }
119
+ attrs[:class] = @css_class if @css_class
120
+ [line, attrs]
121
+ end
122
+ end
123
+
124
+ def wrap_line(line)
125
+ return [''] if line.strip.empty?
126
+
127
+ char_width = @font_size * @layer_config.char_width_multiplier
128
+ words = line.split(/\s+/)
129
+ process_words(words, char_width)
130
+ end
131
+
132
+ def process_words(words, char_width)
133
+ lines = []
134
+ current = ''
135
+
136
+ words.each do |word|
137
+ current, completed_line = process_word(word, current, char_width)
138
+ lines << completed_line if completed_line
139
+ end
140
+ lines << current unless current.empty?
141
+ lines
142
+ end
143
+
144
+ def process_word(word, current, char_width)
145
+ test = current.empty? ? word : "#{current} #{word}"
146
+ if test.length * char_width <= @available_width
147
+ [test, nil]
148
+ else
149
+ [word, current]
150
+ end
151
+ end
152
+
153
+ class << self
154
+ # Convenience method for backward compatibility
155
+ def wrapped_text_lines(*, **)
156
+ new(*, **).wrapped_text_lines
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MtgCardMaker
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
8
+ end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mtg_card_maker/version'
4
+ require 'victor'
5
+
6
+ # MTG Card Maker is a Ruby gem for creating Magic: The Gathering card templates.
7
+ # It uses the Victor SVG library to generate layered card designs with customizable
8
+ # colors, text, and artwork. The library provides a modular layer system where
9
+ # each component (frame, art, text areas) inherits from BaseLayer and implements
10
+ # its own rendering logic.
11
+ #
12
+ # @example Basic Usage
13
+ # card = MtgCardMaker::BaseCard.new(
14
+ # name: "Lightning Bolt",
15
+ # mana_cost: "R",
16
+ # type: "Instant",
17
+ # description: "Lightning Bolt deals 3 damage to any target.",
18
+ # color_scheme: :red
19
+ # )
20
+ # card.generate("lightning_bolt.svg")
21
+ #
22
+ # @example Custom Template
23
+ # template = MtgCardMaker::Template.new
24
+ # template.add_layer(MtgCardMaker::BorderLayer.new(dimensions: {...}, color: :red))
25
+ # template.add_layer(MtgCardMaker::NameLayer.new(dimensions: {...}, text: "Lightning Bolt"))
26
+ # template.save("custom_card.svg")
27
+ #
28
+ # @since 0.1.0
29
+ # @author Joe Sharp
30
+ module MtgCardMaker
31
+ # Standard MTG card width in pixels
32
+ # @return [Integer] the width of a standard MTG card
33
+ CARD_WIDTH = 630
34
+
35
+ # Standard MTG card height in pixels
36
+ # @return [Integer] the height of a standard MTG card
37
+ CARD_HEIGHT = 880
38
+
39
+ # Load unified color scheme system
40
+ require_relative 'mtg_card_maker/color_scheme'
41
+ require_relative 'mtg_card_maker/color_palette'
42
+ require_relative 'mtg_card_maker/layer_config'
43
+
44
+ # Default color scheme using colorless colors
45
+ # @return [ColorScheme] the default colorless color scheme
46
+ DEFAULT_COLOR_SCHEME = ColorScheme.new(:colorless)
47
+
48
+ # Default text color from the default color scheme
49
+ # @return [String] the default text color hex value
50
+ DEFAULT_TEXT_COLOR = DEFAULT_COLOR_SCHEME.text_color
51
+
52
+ # White color constant for border compatibility
53
+ # @return [String] the white color hex value
54
+ WHITE = '#EEE'
55
+
56
+ # Custom error class for MTG Card Maker specific errors
57
+ class Error < StandardError; end
58
+
59
+ # Base class for all card layers that provides common attributes and rendering interface.
60
+ # All layer classes (frames, text areas, art) inherit from this base class and
61
+ # implement their own specific rendering logic.
62
+ #
63
+ # @abstract Subclasses must implement the {#render} method
64
+ # @since 0.1.0
65
+ class BaseLayer
66
+ # @return [Template, nil] the template instance this layer belongs to
67
+ attr_accessor :template
68
+
69
+ # @return [Integer] the x-coordinate of the layer
70
+ attr_reader :x
71
+
72
+ # @return [Integer] the y-coordinate of the layer
73
+ attr_reader :y
74
+
75
+ # @return [Integer] the width of the layer
76
+ attr_reader :width
77
+
78
+ # @return [Integer] the height of the layer
79
+ attr_reader :height
80
+
81
+ # @return [String] the color of the layer
82
+ attr_reader :color
83
+
84
+ # Initialize a new layer with the given dimensions and color
85
+ #
86
+ # @param dimensions [Hash] the layer dimensions
87
+ # @option dimensions [Integer] :x the x-coordinate
88
+ # @option dimensions [Integer] :y the y-coordinate
89
+ # @option dimensions [Integer] :width the width
90
+ # @option dimensions [Integer] :height the height
91
+ # @param color [String] the color of the layer (default: 'white')
92
+ def initialize(dimensions:, color: 'white')
93
+ @x = dimensions[:x]
94
+ @y = dimensions[:y]
95
+ @width = dimensions[:width]
96
+ @height = dimensions[:height]
97
+ @color = color
98
+ @template = nil
99
+ end
100
+
101
+ # Render the layer to the SVG canvas
102
+ #
103
+ # @abstract Subclasses must implement this method
104
+ # @raise [NotImplementedError] if not implemented by subclass
105
+ def render
106
+ raise NotImplementedError, "Subclasses must implement the 'render' method."
107
+ end
108
+
109
+ # Access the SVG canvas through the template
110
+ #
111
+ # @return [Victor::SVG, nil] the SVG canvas instance
112
+ def svg
113
+ @template&.instance_variable_get(:@svg)
114
+ end
115
+ end
116
+
117
+ require_relative 'mtg_card_maker/layer_initializer'
118
+ require_relative 'mtg_card_maker/base_card'
119
+ require_relative 'mtg_card_maker/layer_factory'
120
+ require_relative 'mtg_card_maker/svg_gradient_service'
121
+ require_relative 'mtg_card_maker/text_rendering_service'
122
+ require_relative 'mtg_card_maker/sprite_sheet_assets'
123
+ require_relative 'mtg_card_maker/sprite_sheet_builder'
124
+ require_relative 'mtg_card_maker/sprite_sheet_service'
125
+ require_relative 'mtg_card_maker/layers/border_layer'
126
+ require_relative 'mtg_card_maker/layers/frame_layer'
127
+ require_relative 'mtg_card_maker/layers/name_layer'
128
+ require_relative 'mtg_card_maker/layers/art_layer'
129
+ require_relative 'mtg_card_maker/layers/type_line_layer'
130
+ require_relative 'mtg_card_maker/layers/text_box_layer'
131
+ require_relative 'mtg_card_maker/layers/power_layer'
132
+ require_relative 'mtg_card_maker/cli'
133
+
134
+ # Main template class that manages the SVG canvas and layer composition.
135
+ # This class handles creating the SVG canvas, adding layers, and saving
136
+ # the final SVG file. It also manages font embedding and CSS styling.
137
+ #
138
+ # @example
139
+ # template = MtgCardMaker::Template.new
140
+ # template.add_layer(MtgCardMaker::BorderLayer.new(dimensions: {...}))
141
+ # template.add_layer(MtgCardMaker::NameLayer.new(dimensions: {...}, text: "Lightning Bolt"))
142
+ # template.save("card.svg")
143
+ #
144
+ # @since 0.1.0
145
+ class Template
146
+ # @return [Integer] the width of the SVG canvas
147
+ attr_reader :width
148
+
149
+ # @return [Integer] the height of the SVG canvas
150
+ attr_reader :height
151
+
152
+ # Initialize a new template with the given dimensions
153
+ #
154
+ # @param width [Integer] the width of the canvas (default: CARD_WIDTH)
155
+ # @param height [Integer] the height of the canvas (default: CARD_HEIGHT)
156
+ # @param embed_font [Boolean] whether to embed the font as base64 (default: false)
157
+ def initialize(width: CARD_WIDTH, height: CARD_HEIGHT, embed_font: false)
158
+ @width = width
159
+ @height = height
160
+ @svg = Victor::SVG.new width: width, height: height
161
+ embed_font(embed: embed_font)
162
+ define_css_classes
163
+ end
164
+
165
+ # Add a layer to the template and render it
166
+ #
167
+ # @param layer [BaseLayer] the layer to add and render
168
+ # @return [void]
169
+ def add_layer(layer)
170
+ layer.template = self
171
+ layer.render
172
+ end
173
+
174
+ # Save the SVG to a file
175
+ #
176
+ # @param filename [String] the filename to save to
177
+ # @return [void]
178
+ def save(filename)
179
+ @svg.save(filename)
180
+ end
181
+
182
+ # Get the SVG as a string
183
+ #
184
+ # @return [String] the SVG content as a string
185
+ def to_svg
186
+ @svg.to_s
187
+ end
188
+
189
+ private
190
+
191
+ def embed_font(embed: false)
192
+ if embed
193
+ font_path = File.join(__dir__, 'mtg_card_maker', 'fonts', 'goudy_base64.txt')
194
+ base64_font_data = File.read(font_path).strip
195
+ @svg.style <<~CSS
196
+ @font-face {
197
+ font-family: 'Goudy Mediaeval DemiBold';
198
+ src: url(data:font/truetype;charset=utf-8;base64,#{base64_font_data}) format('truetype');
199
+ font-weight: normal;
200
+ font-style: normal;
201
+ }
202
+ CSS
203
+ else
204
+ @svg.style <<~CSS
205
+ @font-face {
206
+ font-family: 'Goudy Mediaeval DemiBold';
207
+ src: url('fonts/Goudy Mediaeval DemiBold.ttf') format('truetype');
208
+ font-weight: normal;
209
+ font-style: normal;
210
+ }
211
+ CSS
212
+ end
213
+ end
214
+
215
+ def define_css_classes
216
+ @svg.style <<~CSS
217
+ /* Font Classes */
218
+ .card-name {
219
+ font-family: 'Goudy Mediaeval DemiBold', serif;
220
+ font-weight: normal;
221
+ font-style: normal;
222
+ }
223
+
224
+ .card-type {
225
+ font-family: 'Goudy Mediaeval DemiBold', serif;
226
+ font-weight: normal;
227
+ font-style: normal;
228
+ }
229
+
230
+ .card-description {
231
+ font-family: serif;
232
+ font-weight: normal;
233
+ font-style: normal;
234
+ }
235
+
236
+ .card-flavor-text {
237
+ font-family: serif;
238
+ font-weight: normal;
239
+ font-style: italic;
240
+ }
241
+
242
+ .card-power-toughness {
243
+ font-family: serif;
244
+ font-weight: bold;
245
+ font-style: normal;
246
+ }
247
+
248
+ .card-copyright {
249
+ font-family: sans-serif;
250
+ font-weight: normal;
251
+ font-style: normal;
252
+ }
253
+
254
+ .mana-cost-text {
255
+ font-family: serif;
256
+ font-weight: normal;
257
+ font-style: normal;
258
+ }
259
+
260
+ .mana-cost-text-large {
261
+ font-family: serif;
262
+ font-weight: semibold;
263
+ font-style: normal;
264
+ }
265
+ CSS
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,4 @@
1
+ module MtgCardMaker
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end