mtg_card_maker 0.1.0 → 0.2.1
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/CHANGELOG.md +48 -0
- data/LICENSE.txt +59 -14
- data/README.md +257 -73
- data/lib/mtg_card_maker/base_card.rb +24 -3
- data/lib/mtg_card_maker/cli.rb +69 -66
- data/lib/mtg_card_maker/color_scheme.rb +18 -23
- data/lib/mtg_card_maker/css_service.rb +72 -0
- data/lib/mtg_card_maker/fonts/GoudyBookletter1911.ttf +0 -0
- data/lib/mtg_card_maker/fonts/OFL.txt +92 -0
- data/lib/mtg_card_maker/fonts/goudy_bookletter_base64.txt +1 -0
- data/lib/mtg_card_maker/icon_filename_service.rb +118 -0
- data/lib/mtg_card_maker/icon_service.rb +87 -25
- data/lib/mtg_card_maker/icons/black.svg +6 -1
- data/lib/mtg_card_maker/icons/blue.svg +6 -1
- data/lib/mtg_card_maker/icons/colorless.svg +6 -1
- data/lib/mtg_card_maker/icons/double-digit.svg +6 -0
- data/lib/mtg_card_maker/icons/energy.svg +26 -0
- data/lib/mtg_card_maker/icons/green.svg +6 -1
- data/lib/mtg_card_maker/icons/hybrid/2-black.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/2-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/2-green.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/2-red.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/2-white.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/black-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/black-green.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/black-red.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/black-white.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/blue-black.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/blue-green.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/blue-red.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/blue-white.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/colorless-black.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/colorless-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/colorless-green.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/colorless-red.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/colorless-white.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/green-black.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/green-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/green-red.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/green-white.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/red-black.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/red-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/red-green.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/red-white.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/white-black.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/white-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/white-green.svg +19 -0
- data/lib/mtg_card_maker/icons/hybrid/white-red.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/black-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/black-green.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/black-red.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/black-white.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/black.svg +6 -0
- data/lib/mtg_card_maker/icons/phyrexian/blue-black.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/blue-green.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/blue-red.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/blue-white.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/blue.svg +6 -0
- data/lib/mtg_card_maker/icons/phyrexian/colorless.svg +6 -0
- data/lib/mtg_card_maker/icons/phyrexian/green-black.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/green-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/green-red.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/green-white.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/green.svg +6 -0
- data/lib/mtg_card_maker/icons/phyrexian/red-black.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/red-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/red-green.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/red-white.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/red.svg +6 -0
- data/lib/mtg_card_maker/icons/phyrexian/white-black.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/white-blue.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/white-green.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/white-red.svg +19 -0
- data/lib/mtg_card_maker/icons/phyrexian/white.svg +6 -0
- data/lib/mtg_card_maker/icons/red.svg +6 -1
- data/lib/mtg_card_maker/icons/single-digit.svg +6 -0
- data/lib/mtg_card_maker/icons/snow.svg +24 -0
- data/lib/mtg_card_maker/icons/tap.svg +1 -0
- data/lib/mtg_card_maker/icons/tap_legacy.svg +45 -0
- data/lib/mtg_card_maker/icons/untap.svg +1 -0
- data/lib/mtg_card_maker/icons/white.svg +6 -1
- data/lib/mtg_card_maker/icons/x.svg +6 -0
- data/lib/mtg_card_maker/layer_config.rb +21 -46
- data/lib/mtg_card_maker/layers/border_layer.rb +4 -5
- data/lib/mtg_card_maker/layers/frame_layer.rb +1 -1
- data/lib/mtg_card_maker/layers/name_layer.rb +16 -8
- data/lib/mtg_card_maker/layers/power_layer.rb +1 -3
- data/lib/mtg_card_maker/layers/text_box_layer.rb +205 -51
- data/lib/mtg_card_maker/layers/type_line_layer.rb +2 -4
- data/lib/mtg_card_maker/logger.rb +62 -0
- data/lib/mtg_card_maker/mana_cost.rb +59 -128
- data/lib/mtg_card_maker/sprite_sheet_assets.rb +10 -55
- data/lib/mtg_card_maker/sprite_sheet_builder.rb +20 -8
- data/lib/mtg_card_maker/sprite_sheet_service.rb +22 -9
- data/lib/mtg_card_maker/svg_gradient_service.rb +11 -11
- data/lib/mtg_card_maker/symbol_replacement_service.rb +102 -0
- data/lib/mtg_card_maker/text_rendering_service.rb +7 -3
- data/lib/mtg_card_maker/version.rb +2 -2
- data/lib/mtg_card_maker.rb +9 -78
- metadata +79 -12
- data/lib/mtg_card_maker/fonts/Goudy Mediaeval DemiBold.ttf +0 -0
- data/lib/mtg_card_maker/fonts/goudy_base64.txt +0 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
2
|
+
width="24px"
|
|
3
|
+
height="24px"
|
|
4
|
+
viewBox="0 0 600 600">
|
|
5
|
+
<g
|
|
6
|
+
transform="translate(-70.576066,118.21478)"
|
|
7
|
+
id="layer1">
|
|
8
|
+
<circle
|
|
9
|
+
cx="50.000999"
|
|
10
|
+
cy="50"
|
|
11
|
+
r="50"
|
|
12
|
+
transform="matrix(5.99994,0,0,6,70.576066,-118.21478)"
|
|
13
|
+
id="circle485"
|
|
14
|
+
style="fill:#c9c4be" />
|
|
15
|
+
<path d="M25.001,93.303 L33.505,89.209 L37.843,89.157 L40.198,85.077 L50.001,98.881 L59.803,85.077 L62.158,89.157 L66.496,89.208 L75.001,93.303 L75.708,83.891 L77.833,80.105 L75.479,76.027 L92.334,74.439 L85.279,59.05 L89.99,59.05 L92.203,55.318 L100,50 L92.203,44.681 L89.99,40.95 L85.279,40.95 L92.333,25.559 L75.479,23.973 L77.833,19.893 L75.707,16.111 L75,6.699 L66.496,10.792 L62.158,10.843 L59.803,14.922 L50,1.118 L40.198,14.922 L37.843,10.843 L33.505,10.792 L25,6.699 L24.293,16.111 L22.168,19.893 L24.523,23.973 L7.667,25.56 L14.722,40.95 L10.011,40.95 L7.798,44.682 L0,50 L7.797,55.318 L10.011,59.05 L14.722,59.05 L7.668,74.441 L24.523,76.027 L22.168,80.105 L24.293,83.889" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#0d0f0f" />
|
|
16
|
+
<path d="M68.609,86.282 L72.116,84.258 L72.116,80.212 L68.609,78.188 L55.088,52.938 L83.717,52.023 L87.223,54.048 L90.727,52.025 L90.727,47.976 L87.223,45.953 L83.717,47.976 L55.088,47.063 L68.611,21.813 L72.117,19.789 L72.117,15.743 L68.611,13.719 L65.107,15.742 L65.105,19.789 L50.001,44.125 L34.896,19.789 L34.895,15.741 L31.392,13.718 L27.885,15.743 L27.885,19.789 L31.391,21.813 L44.913,47.063 L16.285,47.976 L12.778,45.953 L9.275,47.975 L9.275,52.023 L12.778,54.047 L16.285,52.023 L44.913,52.938 L31.39,78.188 L27.885,80.211 L27.884,84.258 L31.39,86.281 L34.895,84.258 L34.895,80.211 L50,55.875 L65.105,80.211 L65.105,84.259" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#ffffff" />
|
|
17
|
+
<path d="M57.051,22.393 L50.001,34.681 L42.951,22.393 L50.001,10.104" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#ffffff" />
|
|
18
|
+
<path d="M29.617,30.09 L36.734,42.34 L22.567,42.301 L15.45,30.052" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#ffffff" />
|
|
19
|
+
<path d="M22.566,57.697 L36.733,57.659 L29.616,69.908 L15.449,69.947" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#ffffff" />
|
|
20
|
+
<path d="M42.949,77.607 L50,65.318 L57.05,77.607 L50,89.896" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#ffffff" />
|
|
21
|
+
<path d="M70.384,69.91 L63.268,57.66 L77.434,57.699 L84.551,69.947" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#ffffff" />
|
|
22
|
+
<path d="M77.436,42.303 L63.268,42.341 L70.385,30.092 L84.551,30.053" transform="matrix(5.99994,0,0,6,70.576066,-118.21478)" style="fill:#ffffff" />
|
|
23
|
+
</g>
|
|
24
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 600 600" version="1.1"><circle cx="300" cy="300" r="300" fill="#DDD"/><path d="m512 353.5-216 0 79.1-56.3c-29.4-23.3-63.4-35-101.9-35-19.3 0-32.5 3.7-39.5 11-7.1 7.4-10.7 20.7-10.7 39.9 0 53.2 27.4 110 82.2 170.4l-62.3 63.1c-72.5-88.2-108.8-167.1-108.8-236.5 0-41.6 12.5-74.7 37.6-99.3 25.1-24.6 58.5-36.9 100-36.9 50.7 0 106 19.3 165.9 57.8l46.4-79.1 28.2 200.8" fill="#111"/></svg>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
6
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
|
7
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
8
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
11
|
+
version="1.1"
|
|
12
|
+
width="600"
|
|
13
|
+
height="600"
|
|
14
|
+
id="svg5391">
|
|
15
|
+
<defs
|
|
16
|
+
id="defs5393" />
|
|
17
|
+
<metadata
|
|
18
|
+
id="metadata5396">
|
|
19
|
+
<rdf:RDF>
|
|
20
|
+
<cc:Work
|
|
21
|
+
rdf:about="">
|
|
22
|
+
<dc:format>image/svg+xml</dc:format>
|
|
23
|
+
<dc:type
|
|
24
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
25
|
+
<dc:title />
|
|
26
|
+
</cc:Work>
|
|
27
|
+
</rdf:RDF>
|
|
28
|
+
</metadata>
|
|
29
|
+
<g
|
|
30
|
+
transform="translate(-284.8627,49.643341)"
|
|
31
|
+
id="layer1">
|
|
32
|
+
<circle
|
|
33
|
+
cx="-475.125"
|
|
34
|
+
cy="365.06799"
|
|
35
|
+
r="50"
|
|
36
|
+
transform="matrix(6,0,0,6,3435.6127,-1940.0513)"
|
|
37
|
+
id="circle447"
|
|
38
|
+
style="fill:#c9c4be" />
|
|
39
|
+
<path
|
|
40
|
+
d="m 853.51868,285.44471 -63.42,76.37397 -15.53999,-15.52797 c 6.042,-15.24601 9.86998,-29.274 11.44199,-42.08402 1.57201,-12.79798 0.92399,-24.65399 -1.944,-35.60398 -0.28784,-1.42802 -0.63601,-2.802 -1.07999,-4.092 -0.426,-1.31403 -0.92999,-2.532 -1.51203,-3.67801 -2.29799,-6.88799 -5.75398,-13.37401 -10.36199,-19.404 -4.602,-6.06001 -11.20798,-13.38 -19.836,-22.00802 L 705.95469,173.2507 495.39067,383.8267 c -18.41398,18.396 -28.34397,34.374 -29.77799,47.87401 -1.44601,13.53597 5.17802,27.62999 19.85401,42.30597 l 6.042,5.16602 -16.39799,16.39199 -138.072,-138.07201 16.39198,-16.39198 6.9,6.88799 c 14.382,14.964 28.33199,21.73799 41.85599,20.28601 13.51803,-1.42802 29.34603,-11.22599 47.46602,-29.34599 L 660.64268,127.93269 613.6147,80.904705 c -13.23001,-12.654021 -24.522,-21.504022 -33.87003,-26.532022 -9.354,-5.039976 -20.21999,-7.127995 -32.57399,-6.257984 -1.73398,0 -3.53999,0.07213 -5.39998,0.210013 -1.872,0.149961 -3.816,0.365979 -5.82602,0.641982 -3.744,0.293884 -7.04999,0.714011 -9.91798,1.302014 -2.88003,0.575992 -5.616,0.995985 -8.20201,1.290003 -0.87001,0.293884 -1.65602,0.503997 -2.36999,0.641982 -0.72002,0.137884 -1.65602,0.22209 -2.80801,0.210013 -3.168,0.869977 -6.76802,1.878006 -10.78799,3.023986 -4.03802,1.134003 -9.06602,2.447993 -15.10802,3.876014 l -14.238,-15.108019 74.64002,-65.142004 306.36598,306.384027 z"
|
|
41
|
+
inkscape:connector-curvature="0"
|
|
42
|
+
id="path451"
|
|
43
|
+
style="fill:#0d0f0f" />
|
|
44
|
+
</g>
|
|
45
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 600 600" version="1.1"><circle cx="300" cy="300" r="300" fill="#111"/><path d="m473.7 145.7-75.9 44.5c37 57.4 55.4 110.4 55.4 159.1 0 39.9-11.8 71.9-35.4 95.9-23.6 24.1-55.3 36.1-95.2 36.1-66.7 0-142.3-34.8-226.9-104.3l60.6-59.9c57.9 52.6 112.4 78.8 163.5 78.8 18.5 0 31.2-3.4 38.3-10.2 7.1-6.8 10.6-19.5 10.6-37.9 0-37-11.2-69.6-33.6-97.8l-54 75.9 0-207.2 192.6 27" fill="#FFF"/></svg>
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
|
|
2
|
+
<circle cx='50%' cy='-50%' r='480' fill='#FEFDDF' />
|
|
3
|
+
<g transform='translate(70, -70) scale(0.85)'>
|
|
4
|
+
<path d="M-6-420v-120h282v120H-6Zm299-162L185-691l84-84 109 108-85 85Zm127-102v-282h120v282H420Zm247 102-85-85 109-108 84 84-108 109Zm17 162v-120h282v120H684Zm-203.71 85Q420-335 377.5-377.21q-42.5-42.2-42.5-102.5 0-60.29 42.21-102.79 42.2-42.5 102.5-42.5 60.29 0 102.79 42.21 42.5 42.2 42.5 102.5 0 60.29-42.21 102.79-42.2 42.5-102.5 42.5ZM691-185 582-293l85-85 108 109-84 84Zm-422 0-84-84 108-109 85 85-109 108ZM420 6v-282h120V6H420Z"/>
|
|
5
|
+
</g>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="-15 -15 30 30" width="24px">
|
|
2
|
+
<circle cx='0' cy='0' r='15' fill='#DDD' />
|
|
3
|
+
<text x='0' y='10' fill='#000' text-anchor='middle' font-weight='normal' font-size='30' font-family='serif'>
|
|
4
|
+
X
|
|
5
|
+
</text>
|
|
6
|
+
</svg>
|
|
@@ -27,10 +27,9 @@ module MtgCardMaker
|
|
|
27
27
|
DEFAULT_CONFIG = {
|
|
28
28
|
# Text rendering settings
|
|
29
29
|
font_sizes: {
|
|
30
|
-
name:
|
|
30
|
+
name: 36,
|
|
31
31
|
type: 22,
|
|
32
|
-
|
|
33
|
-
flavor_text: 18,
|
|
32
|
+
text_box: 24,
|
|
34
33
|
power_area: 28,
|
|
35
34
|
copyright: 14
|
|
36
35
|
},
|
|
@@ -43,8 +42,8 @@ module MtgCardMaker
|
|
|
43
42
|
char_width_multiplier: 0.42,
|
|
44
43
|
css_classes: {
|
|
45
44
|
card_name: 'card-name',
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
type_line: 'card-type',
|
|
46
|
+
rules_text: 'card-rules-text',
|
|
48
47
|
flavor_text: 'card-flavor-text',
|
|
49
48
|
power_area: 'card-power-toughness',
|
|
50
49
|
copyright: 'card-copyright'
|
|
@@ -67,7 +66,7 @@ module MtgCardMaker
|
|
|
67
66
|
y_offset: 8,
|
|
68
67
|
width_ratio: 0.75
|
|
69
68
|
},
|
|
70
|
-
|
|
69
|
+
rules_text: {
|
|
71
70
|
y_offset: 30,
|
|
72
71
|
width_ratio: 1.0
|
|
73
72
|
},
|
|
@@ -94,11 +93,16 @@ module MtgCardMaker
|
|
|
94
93
|
}
|
|
95
94
|
},
|
|
96
95
|
|
|
96
|
+
text_box: {
|
|
97
|
+
icon_size: 24,
|
|
98
|
+
hybrid_icon_size: 32
|
|
99
|
+
},
|
|
100
|
+
|
|
97
101
|
# Mana cost settings
|
|
98
102
|
mana_cost: {
|
|
99
|
-
circle_radius: 15,
|
|
100
103
|
circle_spacing: 35,
|
|
101
|
-
icon_size:
|
|
104
|
+
icon_size: 30,
|
|
105
|
+
hybrid_icon_size: 38,
|
|
102
106
|
max_circles: 10,
|
|
103
107
|
margin: 10
|
|
104
108
|
},
|
|
@@ -125,21 +129,9 @@ module MtgCardMaker
|
|
|
125
129
|
|
|
126
130
|
# Drop shadow settings
|
|
127
131
|
drop_shadow: {
|
|
128
|
-
dx: -
|
|
129
|
-
dy: 2,
|
|
130
|
-
std_deviation:
|
|
131
|
-
flood_opacity: 1.0
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
# Text positioning adjustments
|
|
135
|
-
text_positioning: {
|
|
136
|
-
mana_cost_text_y_offset: 7,
|
|
137
|
-
mana_cost_font_size: 24
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
# Icon opacity settings
|
|
141
|
-
icon_opacity: {
|
|
142
|
-
mana_cost: 0.7
|
|
132
|
+
dx: -1,
|
|
133
|
+
dy: 2.5,
|
|
134
|
+
std_deviation: 0
|
|
143
135
|
}
|
|
144
136
|
}.freeze
|
|
145
137
|
|
|
@@ -155,7 +147,7 @@ module MtgCardMaker
|
|
|
155
147
|
@config = default_config_with_merge.deep_merge(custom_config)
|
|
156
148
|
end
|
|
157
149
|
|
|
158
|
-
#
|
|
150
|
+
# Size getters
|
|
159
151
|
def font_size(layer_type)
|
|
160
152
|
config[:font_sizes][layer_type]
|
|
161
153
|
end
|
|
@@ -183,6 +175,11 @@ module MtgCardMaker
|
|
|
183
175
|
config[:frames][:corner_radius][layer_type] || { x: 5, y: 5 }
|
|
184
176
|
end
|
|
185
177
|
|
|
178
|
+
# Text box getters
|
|
179
|
+
def text_box_config
|
|
180
|
+
config[:text_box]
|
|
181
|
+
end
|
|
182
|
+
|
|
186
183
|
# Mana cost getters
|
|
187
184
|
def mana_cost_config
|
|
188
185
|
config[:mana_cost]
|
|
@@ -237,28 +234,6 @@ module MtgCardMaker
|
|
|
237
234
|
config[:drop_shadow]
|
|
238
235
|
end
|
|
239
236
|
|
|
240
|
-
# Text positioning getters
|
|
241
|
-
def text_positioning_config
|
|
242
|
-
config[:text_positioning]
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def mana_cost_text_y_offset
|
|
246
|
-
text_positioning_config[:mana_cost_text_y_offset]
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def mana_cost_font_size
|
|
250
|
-
text_positioning_config[:mana_cost_font_size]
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Icon opacity getters
|
|
254
|
-
def icon_opacity_config
|
|
255
|
-
config[:icon_opacity]
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
def mana_cost_icon_opacity
|
|
259
|
-
icon_opacity_config[:mana_cost]
|
|
260
|
-
end
|
|
261
|
-
|
|
262
237
|
# Convenience methods for common calculations
|
|
263
238
|
def text_width(base_width, layer_type = nil)
|
|
264
239
|
if layer_type && positioning(layer_type)[:width_ratio]
|
|
@@ -32,7 +32,7 @@ module MtgCardMaker
|
|
|
32
32
|
SUPPORTED_COLORS = {
|
|
33
33
|
white: '#EEE',
|
|
34
34
|
black: '#000',
|
|
35
|
-
silver: :
|
|
35
|
+
silver: :silver,
|
|
36
36
|
gold: :gold
|
|
37
37
|
}.freeze
|
|
38
38
|
|
|
@@ -77,7 +77,7 @@ module MtgCardMaker
|
|
|
77
77
|
render_white_frame(corners)
|
|
78
78
|
when :black
|
|
79
79
|
render_black_frame(corners)
|
|
80
|
-
when :
|
|
80
|
+
when :silver, :gold
|
|
81
81
|
render_metallic_frame(corners)
|
|
82
82
|
else
|
|
83
83
|
raise ArgumentError, "Unsupported border color: #{color.inspect}. Supported: white, black, silver, gold."
|
|
@@ -102,7 +102,6 @@ module MtgCardMaker
|
|
|
102
102
|
rx: corners[:x], ry: corners[:y],
|
|
103
103
|
fill: '#EEE',
|
|
104
104
|
mask: "url(##{@mask_id})"
|
|
105
|
-
SvgGradientService.define_all_gradients(svg, color_scheme)
|
|
106
105
|
render_metallic_elements(
|
|
107
106
|
mask: @mask_id,
|
|
108
107
|
corners: corners,
|
|
@@ -119,7 +118,7 @@ module MtgCardMaker
|
|
|
119
118
|
end
|
|
120
119
|
|
|
121
120
|
def color_key_for(color)
|
|
122
|
-
return :
|
|
121
|
+
return :silver if color == :silver
|
|
123
122
|
return :gold if color == :gold
|
|
124
123
|
|
|
125
124
|
color
|
|
@@ -143,7 +142,7 @@ module MtgCardMaker
|
|
|
143
142
|
|
|
144
143
|
def render_copyright_texts(layer_config)
|
|
145
144
|
copyright_texts = [
|
|
146
|
-
'
|
|
145
|
+
'MTG Card Maker by Joe Sharp',
|
|
147
146
|
'Portions of the materials used are property of Wizards of the Coast.',
|
|
148
147
|
'© Wizards of the Coast LLC'
|
|
149
148
|
]
|
|
@@ -9,6 +9,7 @@ module MtgCardMaker
|
|
|
9
9
|
class FrameLayer < BaseLayer
|
|
10
10
|
include MetallicRenderer
|
|
11
11
|
include LayerInitializer
|
|
12
|
+
|
|
12
13
|
attr_reader :color_scheme, :mask_id, :layer_config
|
|
13
14
|
|
|
14
15
|
def initialize(dimensions:, color: nil, color_scheme: DEFAULT_COLOR_SCHEME, mask_id: 'artWindowMask')
|
|
@@ -21,7 +22,6 @@ module MtgCardMaker
|
|
|
21
22
|
|
|
22
23
|
# Render the frame with a gradient background
|
|
23
24
|
def render
|
|
24
|
-
SvgGradientService.define_all_gradients(svg, color_scheme)
|
|
25
25
|
if metallic_properties?
|
|
26
26
|
render_metallic_frame
|
|
27
27
|
else
|
|
@@ -7,6 +7,7 @@ module MtgCardMaker
|
|
|
7
7
|
# This is the area that surrounds the name and cost of the card
|
|
8
8
|
class NameLayer < BaseLayer
|
|
9
9
|
include LayerInitializer
|
|
10
|
+
|
|
10
11
|
attr_reader :name, :cost, :color_scheme
|
|
11
12
|
|
|
12
13
|
def initialize(dimensions:, name:, cost:, color: nil, color_scheme: DEFAULT_COLOR_SCHEME)
|
|
@@ -18,9 +19,6 @@ module MtgCardMaker
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def render
|
|
21
|
-
# Ensure gradients are defined for this color scheme
|
|
22
|
-
SvgGradientService.define_all_gradients(svg, color_scheme)
|
|
23
|
-
|
|
24
22
|
svg.g do
|
|
25
23
|
render_name_area
|
|
26
24
|
render_card_name
|
|
@@ -42,13 +40,16 @@ module MtgCardMaker
|
|
|
42
40
|
rx: corners[:x], ry: corners[:y], mask: "url(##{@mask_id})"
|
|
43
41
|
end
|
|
44
42
|
|
|
43
|
+
def layer_config
|
|
44
|
+
@layer_config ||= LayerConfig.default
|
|
45
|
+
end
|
|
46
|
+
|
|
45
47
|
def render_card_name
|
|
46
|
-
layer_config = LayerConfig.default
|
|
47
48
|
text_service = TextRenderingService.new(
|
|
48
49
|
text: name,
|
|
49
50
|
layer_config: layer_config,
|
|
50
|
-
x:
|
|
51
|
-
y:
|
|
51
|
+
x: text_x_position,
|
|
52
|
+
y: text_y_position,
|
|
52
53
|
font_size: layer_config.font_size(:name),
|
|
53
54
|
available_width: layer_config.text_width(width, :name_area),
|
|
54
55
|
css_class: layer_config.css_class(:card_name)
|
|
@@ -59,6 +60,14 @@ module MtgCardMaker
|
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
|
|
63
|
+
def text_x_position
|
|
64
|
+
layer_config.text_x_position(x)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def text_y_position
|
|
68
|
+
layer_config.text_y_position(y, :name_area, height)
|
|
69
|
+
end
|
|
70
|
+
|
|
62
71
|
def render_mana_cost
|
|
63
72
|
mana_cost = ManaCost.new(cost)
|
|
64
73
|
cost_x = cost_position_x(mana_cost)
|
|
@@ -69,9 +78,8 @@ module MtgCardMaker
|
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
def cost_position_x(mana_cost)
|
|
72
|
-
layer_config = LayerConfig.default
|
|
73
81
|
config = layer_config.mana_cost_config
|
|
74
|
-
base_x = x + width - config[:margin] - config[:
|
|
82
|
+
base_x = x + width - config[:margin] - (config[:icon_size] / 2)
|
|
75
83
|
base_x - (config[:circle_spacing] * (mana_cost.elements.length - 1))
|
|
76
84
|
end
|
|
77
85
|
|
|
@@ -5,6 +5,7 @@ module MtgCardMaker
|
|
|
5
5
|
# This is the area that surrounds the power and toughness of the card
|
|
6
6
|
class PowerLayer < BaseLayer
|
|
7
7
|
include LayerInitializer
|
|
8
|
+
|
|
8
9
|
attr_reader :power, :toughness, :color_scheme
|
|
9
10
|
|
|
10
11
|
def initialize(dimensions:, power:, toughness:, color: nil, color_scheme: DEFAULT_COLOR_SCHEME)
|
|
@@ -19,9 +20,6 @@ module MtgCardMaker
|
|
|
19
20
|
# Don't render if power or toughness are nil, empty, or invalid
|
|
20
21
|
return if power.nil? || toughness.nil? || power.to_s.strip.empty? || toughness.to_s.strip.empty?
|
|
21
22
|
|
|
22
|
-
# Ensure gradients are defined for this color scheme
|
|
23
|
-
SvgGradientService.define_all_gradients(svg, color_scheme)
|
|
24
|
-
|
|
25
23
|
layer_config = LayerConfig.default
|
|
26
24
|
stroke_width = layer_config.stroke_width
|
|
27
25
|
corners = layer_config.corner_radius(:power)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MtgCardMaker
|
|
4
|
+
# Text limits for different scenarios
|
|
5
|
+
MAX_RULES_LINES = 9
|
|
6
|
+
MAX_FLAVOR_LINES = 7 # Two less because one line of rules is required, plus the separator
|
|
7
|
+
|
|
4
8
|
# TextBoxLayer is a specialized layer for the rules and flavor text
|
|
9
|
+
# with bidirectional text flow from a dynamic separator
|
|
5
10
|
class TextBoxLayer < BaseLayer
|
|
6
11
|
include LayerInitializer
|
|
7
|
-
|
|
12
|
+
|
|
13
|
+
attr_reader :rules_text, :flavor_text, :color_scheme, :font_size, :text_width
|
|
8
14
|
|
|
9
15
|
def initialize(dimensions:, rules_text:, flavor_text: nil, color: nil, color_scheme: DEFAULT_COLOR_SCHEME)
|
|
10
16
|
frame_color = initialize_layer_color(color, color_scheme, :background_color)
|
|
@@ -12,96 +18,244 @@ module MtgCardMaker
|
|
|
12
18
|
@rules_text = rules_text
|
|
13
19
|
@flavor_text = flavor_text
|
|
14
20
|
@color_scheme = color_scheme
|
|
21
|
+
@font_size = layer_config.font_size(:text_box)
|
|
22
|
+
@text_width = layer_config.text_width(width, :rules_text)
|
|
15
23
|
end
|
|
16
24
|
|
|
17
|
-
# Render the rules text and flavor text in a text box with
|
|
25
|
+
# Render the rules text and flavor text in a text box with bidirectional flow
|
|
18
26
|
def render
|
|
19
|
-
# Ensure gradients are defined for this color scheme
|
|
20
|
-
SvgGradientService.define_all_gradients(svg, color_scheme)
|
|
21
|
-
|
|
22
27
|
render_text_box
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
private
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
layer_config = LayerConfig.default
|
|
29
|
-
stroke_width = layer_config.stroke_width
|
|
32
|
+
attr_reader :text_box_lines
|
|
30
33
|
|
|
34
|
+
def render_text_box
|
|
31
35
|
svg.g do
|
|
32
|
-
|
|
33
|
-
svg.rect x: x, y: y, width: width, height: height,
|
|
34
|
-
fill: "url(##{SvgGradientService.description_gradient_id(color_scheme)})",
|
|
35
|
-
stroke: color_scheme.primary_color,
|
|
36
|
-
stroke_width: stroke_width
|
|
37
|
-
|
|
36
|
+
render_background
|
|
38
37
|
render_text_content
|
|
39
38
|
end
|
|
40
39
|
end
|
|
41
40
|
|
|
41
|
+
def render_background
|
|
42
|
+
svg.rect x: x, y: y, width: width, height: height,
|
|
43
|
+
fill: "url(##{SvgGradientService.text_box_gradient_id(color_scheme)})",
|
|
44
|
+
stroke: color_scheme.primary_color,
|
|
45
|
+
stroke_width: layer_config.stroke_width
|
|
46
|
+
end
|
|
47
|
+
|
|
42
48
|
def render_text_content
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
text_box_lines = create_text_box_lines
|
|
50
|
+
|
|
51
|
+
# Render separator
|
|
52
|
+
separator = text_box_lines.separator_line
|
|
53
|
+
render_separator(separator[:y_pos]) if separator
|
|
54
|
+
|
|
55
|
+
# Render text lines
|
|
56
|
+
text_box_lines.text_lines.each do |line|
|
|
57
|
+
render_text_line(line[:text], line[:y_pos], line[:type])
|
|
58
|
+
end
|
|
45
59
|
end
|
|
46
60
|
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
def create_text_box_lines
|
|
62
|
+
@text_box_lines = TextBoxLines.new(x: x, y: y, width: width, height: height)
|
|
63
|
+
|
|
64
|
+
if flavor_text_present?
|
|
65
|
+
setup_bidirectional_text
|
|
66
|
+
else
|
|
67
|
+
setup_rules_only_text
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@text_box_lines
|
|
51
71
|
end
|
|
52
72
|
|
|
53
|
-
def
|
|
54
|
-
|
|
73
|
+
def setup_bidirectional_text
|
|
74
|
+
rules_lines = calculate_rules_lines
|
|
75
|
+
flavor_lines = calculate_flavor_lines(rules_lines.length)
|
|
55
76
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
# Calculate separator line: first line after rules text, but never lower than line 4 (0-based)
|
|
78
|
+
separator_line = [rules_lines.length, 4].max
|
|
79
|
+
|
|
80
|
+
# Place rules text upward from separator
|
|
81
|
+
place_text_lines(rules_lines.reverse, separator_line, direction: :upward, type: :rules_text)
|
|
82
|
+
|
|
83
|
+
# Place separator
|
|
84
|
+
text_box_lines.set_line(separator_line, type: :separator)
|
|
85
|
+
|
|
86
|
+
# Place flavor text downward from separator
|
|
87
|
+
place_text_lines(flavor_lines, separator_line, direction: :downward, type: :flavor_text)
|
|
60
88
|
end
|
|
61
89
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
color: color_scheme.text_color,
|
|
70
|
-
available_width: layer_config.text_width(width, text_type),
|
|
71
|
-
css_class: layer_config.css_class(css_class)
|
|
72
|
-
)
|
|
90
|
+
def setup_rules_only_text
|
|
91
|
+
rules_lines = calculate_rules_lines
|
|
92
|
+
|
|
93
|
+
# Center the rules text in the available lines
|
|
94
|
+
start_line = (MAX_RULES_LINES - rules_lines.length) / 2
|
|
95
|
+
|
|
96
|
+
place_text_lines(rules_lines, start_line, direction: :forward, type: :rules_text)
|
|
73
97
|
end
|
|
74
98
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
99
|
+
def place_text_lines(lines, start_line, direction:, type:)
|
|
100
|
+
lines.each_with_index do |line, index|
|
|
101
|
+
line_number = calculate_line_number(start_line, index, direction)
|
|
102
|
+
break if line_number.negative? || line_number >= MAX_RULES_LINES
|
|
103
|
+
|
|
104
|
+
text_box_lines.set_line(line_number, text: line, type: type)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def calculate_line_number(start_line, index, direction)
|
|
109
|
+
case direction
|
|
110
|
+
when :upward
|
|
111
|
+
start_line - index - 1
|
|
112
|
+
when :downward
|
|
113
|
+
start_line + index + 1
|
|
78
114
|
else
|
|
79
|
-
|
|
115
|
+
start_line + index
|
|
80
116
|
end
|
|
81
117
|
end
|
|
82
118
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
def render_text_line(line, y_pos, text_type)
|
|
120
|
+
if symbol_service.contains_symbols?(line)
|
|
121
|
+
render_line_with_symbols(line, y_pos)
|
|
122
|
+
else
|
|
123
|
+
svg.text line, {
|
|
124
|
+
x: layer_config.text_x_position(x),
|
|
125
|
+
y: y_pos,
|
|
126
|
+
fill: color_scheme.text_color,
|
|
127
|
+
font_size: font_size,
|
|
128
|
+
class: layer_config.css_class(text_type)
|
|
129
|
+
}
|
|
86
130
|
end
|
|
87
131
|
end
|
|
88
132
|
|
|
89
|
-
def
|
|
90
|
-
|
|
133
|
+
def render_line_with_symbols(line, y_pos)
|
|
134
|
+
html_content = symbol_service.render_line_with_symbols(line, text_width)
|
|
135
|
+
|
|
136
|
+
# Use foreignObject with HTML for proper text/symbol alignment
|
|
137
|
+
svg.foreignObject x: layer_config.text_x_position(x),
|
|
138
|
+
y: y_pos - font_size, # Ensure symbols don't throw off the line
|
|
139
|
+
width: text_width,
|
|
140
|
+
height: font_size * 2 do
|
|
141
|
+
svg << html_content
|
|
142
|
+
end
|
|
91
143
|
end
|
|
92
144
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
145
|
+
def symbol_service
|
|
146
|
+
@symbol_service ||= SymbolReplacementService.new
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def render_separator(separator_y)
|
|
150
|
+
svg.line(
|
|
98
151
|
x1: layer_config.text_x_position(x),
|
|
99
152
|
y1: separator_y,
|
|
100
153
|
x2: x + width - layer_config.horizontal_padding,
|
|
101
154
|
y2: separator_y,
|
|
102
155
|
stroke: color_scheme.primary_color,
|
|
103
156
|
stroke_width: 1
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def calculate_rules_lines
|
|
161
|
+
return [] if rules_text.nil? || rules_text.strip.empty?
|
|
162
|
+
|
|
163
|
+
text_service = create_text_service(rules_text, :rules_text)
|
|
164
|
+
text_service.wrapped_text_lines.first(MAX_RULES_LINES).map(&:first)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def calculate_flavor_lines(rules_line_count)
|
|
168
|
+
return [] unless flavor_text_present?
|
|
169
|
+
|
|
170
|
+
text_service = create_text_service(flavor_text, :flavor_text)
|
|
171
|
+
max_flavor_lines = [(MAX_FLAVOR_LINES - rules_line_count + 1), 1].max
|
|
172
|
+
text_service.wrapped_text_lines.first(max_flavor_lines).map(&:first)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def create_text_service(text, text_type)
|
|
176
|
+
TextRenderingService.new(
|
|
177
|
+
text: text,
|
|
178
|
+
layer_config: layer_config,
|
|
179
|
+
x: layer_config.text_x_position(x),
|
|
180
|
+
y: 0, # Will be calculated per line
|
|
181
|
+
font_size: font_size,
|
|
182
|
+
color: color_scheme.text_color,
|
|
183
|
+
available_width: text_width,
|
|
184
|
+
css_class: layer_config.css_class(text_type)
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def layer_config
|
|
189
|
+
@layer_config ||= LayerConfig.default
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def flavor_text_present?
|
|
193
|
+
flavor_text && !flavor_text.strip.empty?
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# TextBoxLines manages the positioning and content of the MAX_RULES_LINES lines in a text box
|
|
198
|
+
# Each line can contain rules text, flavor text, or be a separator
|
|
199
|
+
class TextBoxLines
|
|
200
|
+
attr_reader :x, :y, :width, :height
|
|
201
|
+
|
|
202
|
+
def initialize(x:, y:, width:, height:)
|
|
203
|
+
@width = width
|
|
204
|
+
@height = height
|
|
205
|
+
@lines = Array.new(MAX_RULES_LINES) { { text: nil, type: :rules_text } }
|
|
206
|
+
@line_spacing = height / 10.0
|
|
207
|
+
@x = x
|
|
208
|
+
@y = y + (@line_spacing / 3)
|
|
209
|
+
calculate_line_positions
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Set the content and type for a specific line (0-8)
|
|
213
|
+
def set_line(line_number, text: nil, type: :rules_text)
|
|
214
|
+
@lines[line_number] = { text: text, type: type }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Get the content, type, and y position for a specific line (0-8)
|
|
218
|
+
def get_line(line_number)
|
|
219
|
+
line_data = @lines[line_number]
|
|
220
|
+
{
|
|
221
|
+
text: line_data[:text],
|
|
222
|
+
type: line_data[:type],
|
|
223
|
+
y_pos: @line_positions[line_number]
|
|
104
224
|
}
|
|
105
225
|
end
|
|
226
|
+
|
|
227
|
+
# Get all lines as an array of hashes with text, type, and y_pos
|
|
228
|
+
def all_lines
|
|
229
|
+
@lines.map.with_index do |line_data, index|
|
|
230
|
+
{
|
|
231
|
+
text: line_data[:text],
|
|
232
|
+
type: line_data[:type],
|
|
233
|
+
y_pos: @line_positions[index]
|
|
234
|
+
}
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Get all lines except separators and empty lines
|
|
239
|
+
def text_lines
|
|
240
|
+
all_lines.reject { |line| line[:type] == :separator || line[:text].nil? || line[:text].strip.empty? }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def separator_line
|
|
244
|
+
separator = all_lines.find { |line| line[:type] == :separator }
|
|
245
|
+
# Position the separator in the middle of its line
|
|
246
|
+
separator[:y_pos] -= (@line_spacing / 3) if separator
|
|
247
|
+
separator
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
private
|
|
251
|
+
|
|
252
|
+
def calculate_line_positions
|
|
253
|
+
# Calculate MAX_RULES_LINES equally distributed vertical positions within the text box
|
|
254
|
+
# Starting from the top of the text box (y) and ending at the bottom (y + height)
|
|
255
|
+
last_line = MAX_RULES_LINES - 1
|
|
256
|
+
@line_positions = (0..last_line).map do |line_num|
|
|
257
|
+
@y + (@line_spacing * (line_num + 1))
|
|
258
|
+
end
|
|
259
|
+
end
|
|
106
260
|
end
|
|
107
261
|
end
|