squib 0.18.0 → 0.19.0a
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/.github/ISSUE_TEMPLATE/bug_report.md +39 -39
- data/.github/ISSUE_TEMPLATE/feature_request.md +22 -22
- data/.github/ISSUE_TEMPLATE/help-wanted.md +13 -13
- data/.github/ISSUE_TEMPLATE/release-todo.md +45 -33
- data/.github/PULL_REQUEST_TEMPLATE.md +13 -13
- data/.github/workflows/tests.yml +22 -22
- data/.gitignore +41 -41
- data/.vscode/settings.json +4 -3
- data/CHANGELOG.md +460 -460
- data/CONTRIBUTING.md +40 -40
- data/Dockerfile +27 -27
- data/Gemfile +2 -2
- data/Guardfile +8 -8
- data/LICENSE.txt +22 -22
- data/README.md +139 -139
- data/Rakefile +51 -51
- data/bin/squib +5 -5
- data/lib/squib/api/settings.rb +21 -21
- data/lib/squib/args/arg_loader.rb +138 -138
- data/lib/squib/args/box.rb +59 -59
- data/lib/squib/args/card_range.rb +34 -34
- data/lib/squib/args/color_validator.rb +7 -7
- data/lib/squib/args/coords.rb +49 -49
- data/lib/squib/args/csv_opts.rb +22 -22
- data/lib/squib/args/dir_validator.rb +11 -11
- data/lib/squib/args/draw.rb +93 -93
- data/lib/squib/args/drop_shadow.rb +39 -39
- data/lib/squib/args/embed_adjust.rb +22 -22
- data/lib/squib/args/embed_key.rb +12 -12
- data/lib/squib/args/hand_special.rb +37 -37
- data/lib/squib/args/import.rb +67 -67
- data/lib/squib/args/input_file.rb +55 -55
- data/lib/squib/args/paint.rb +43 -43
- data/lib/squib/args/paragraph.rb +118 -118
- data/lib/squib/args/save_batch.rb +65 -65
- data/lib/squib/args/scale_box.rb +57 -57
- data/lib/squib/args/sheet.rb +165 -165
- data/lib/squib/args/showcase_special.rb +41 -41
- data/lib/squib/args/sprue_file.rb +44 -44
- data/lib/squib/args/svg_special.rb +37 -37
- data/lib/squib/args/transform.rb +55 -55
- data/lib/squib/args/typographer.rb +115 -115
- data/lib/squib/args/unit_conversion.rb +27 -27
- data/lib/squib/args/xywh_shorthands.rb +50 -50
- 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 +49 -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 +50 -50
- 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 +277 -277
- data/lib/squib/commands/new.rb +77 -77
- data/lib/squib/conf.rb +149 -149
- data/lib/squib/constants.rb +17 -17
- data/lib/squib/deck.rb +138 -138
- data/lib/squib/dsl/background.rb +35 -35
- data/lib/squib/dsl/circle.rb +39 -39
- data/lib/squib/dsl/csv.rb +42 -42
- data/lib/squib/dsl/curve.rb +35 -35
- data/lib/squib/dsl/cut_zone.rb +47 -47
- data/lib/squib/dsl/ellipse.rb +37 -37
- data/lib/squib/dsl/grid.rb +35 -35
- data/lib/squib/dsl/groups.rb +54 -54
- data/lib/squib/dsl/hand.rb +42 -42
- data/lib/squib/dsl/line.rb +35 -35
- data/lib/squib/dsl/png.rb +57 -57
- data/lib/squib/dsl/polygon.rb +36 -36
- data/lib/squib/dsl/rect.rb +37 -37
- data/lib/squib/dsl/safe_zone.rb +48 -48
- data/lib/squib/dsl/save.rb +21 -21
- data/lib/squib/dsl/save_pdf.rb +50 -50
- data/lib/squib/dsl/save_png.rb +48 -48
- data/lib/squib/dsl/save_sheet.rb +53 -53
- data/lib/squib/dsl/showcase.rb +43 -43
- data/lib/squib/dsl/star.rb +37 -37
- data/lib/squib/dsl/svg.rb +63 -63
- data/lib/squib/dsl/text.rb +54 -54
- data/lib/squib/dsl/text_embed.rb +79 -79
- data/lib/squib/dsl/triangle.rb +35 -35
- data/lib/squib/dsl/units.rb +37 -37
- data/lib/squib/dsl/xlsx.rb +40 -40
- data/lib/squib/dsl/yaml.rb +40 -40
- data/lib/squib/errors_warnings/warn_unexpected_params.rb +14 -14
- data/lib/squib/graphics/background.rb +14 -14
- data/lib/squib/graphics/cairo_context_wrapper.rb +115 -115
- 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 +123 -123
- data/lib/squib/graphics/save_doc.rb +77 -77
- data/lib/squib/graphics/save_images.rb +91 -91
- data/lib/squib/graphics/save_pdf.rb +90 -90
- data/lib/squib/graphics/save_sprue.rb +231 -231
- data/lib/squib/graphics/shapes.rb +143 -143
- data/lib/squib/graphics/showcase.rb +85 -85
- data/lib/squib/graphics/text.rb +202 -202
- data/lib/squib/import/csv_importer.rb +45 -45
- data/lib/squib/import/data_frame.rb +108 -108
- data/lib/squib/import/quantity_exploder.rb +17 -17
- data/lib/squib/import/xlsx_importer.rb +28 -28
- data/lib/squib/import/yaml_importer.rb +30 -30
- data/lib/squib/layout_parser.rb +155 -155
- 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 +208 -208
- data/lib/squib/sprues/sprue_schema.rb +51 -51
- data/lib/squib/system_fonts.rb +16 -16
- data/lib/squib/version.rb +11 -11
- data/lib/squib.rb +35 -35
- data/samples/autoscale_font/_autoscale_font.rb +98 -98
- data/samples/backend/_backend.rb +26 -26
- data/samples/basic.rb +19 -19
- data/samples/build_groups/build_groups.rb +36 -36
- data/samples/colors/_colors.rb +44 -44
- data/samples/colors/_gradients.rb +34 -34
- data/samples/colors/_switch_color.rb +33 -33
- 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/images/_placeholders.rb +48 -48
- 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_filenames.rb +28 -28
- data/samples/saves/_save_pdf.rb +29 -29
- data/samples/saves/_saves.rb +75 -75
- data/samples/saves/_showcase.rb +25 -25
- data/samples/shadows/_shadow.rb +71 -71
- data/samples/shapes/_draw_shapes.rb +60 -60
- data/samples/shapes/_proofs.rb +22 -22
- data/samples/sprues/_advanced_sprues.rb +25 -25
- data/samples/sprues/_builtin_sprues.rb +22 -22
- data/samples/sprues/_fold_sheet.rb +30 -30
- data/samples/sprues/_hex_tiles.rb +15 -15
- data/samples/sprues/_mints.rb +11 -11
- data/samples/sprues/_negative_coords.rb +6 -6
- data/samples/sprues/_sprue_example.rb +11 -11
- data/samples/system_font_debug/_list_fonts.rb +14 -14
- data/samples/text/_embed_text.rb +128 -128
- data/samples/text/_text.rb +52 -52
- data/samples/text/_text_options.rb +103 -103
- data/samples/text/bug134.rb +14 -14
- data/samples/units/_cells.rb +50 -50
- data/samples/units/_shorthands.rb +48 -48
- data/samples/units/_units.rb +39 -39
- data/squib.gemspec +58 -58
- metadata +21 -21
data/lib/squib/graphics/text.rb
CHANGED
|
@@ -1,202 +1,202 @@
|
|
|
1
|
-
require 'pango'
|
|
2
|
-
require_relative '../args/typographer'
|
|
3
|
-
require_relative 'embedding_utils'
|
|
4
|
-
|
|
5
|
-
module Squib
|
|
6
|
-
class Card
|
|
7
|
-
|
|
8
|
-
# :nodoc:
|
|
9
|
-
# @api private
|
|
10
|
-
def draw_text_hint(cc, x, y, layout, color)
|
|
11
|
-
color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
|
|
12
|
-
return if color.to_s.eql? 'off' or color.nil?
|
|
13
|
-
# when w,h < 0, it was never set. extents[1] are ink extents
|
|
14
|
-
w = layout.width / Pango::SCALE
|
|
15
|
-
w = layout.extents[1].width / Pango::SCALE if w < 0
|
|
16
|
-
h = layout.height / Pango::SCALE
|
|
17
|
-
h = layout.extents[1].height / Pango::SCALE if h < 0
|
|
18
|
-
cc.rounded_rectangle(0, 0, w, h, 0, 0)
|
|
19
|
-
cc.set_source_color(color)
|
|
20
|
-
cc.set_line_width(2.0)
|
|
21
|
-
cc.stroke
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# :nodoc:
|
|
25
|
-
# @api private
|
|
26
|
-
def compute_valign(layout, valign, embed_h)
|
|
27
|
-
return 0 unless layout.height > 0
|
|
28
|
-
ink_extents = layout.extents[1]
|
|
29
|
-
ink_extents.height = embed_h * Pango::SCALE if ink_extents.height == 0 # JUST embed, bug #134
|
|
30
|
-
case valign.to_s.downcase
|
|
31
|
-
when 'middle'
|
|
32
|
-
Pango.pixels((layout.height - ink_extents.height) / 2)
|
|
33
|
-
when 'bottom'
|
|
34
|
-
Pango.pixels(layout.height - ink_extents.height)
|
|
35
|
-
else
|
|
36
|
-
0
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def set_font_rendering_opts!(layout)
|
|
41
|
-
font_options = Cairo::FontOptions.new
|
|
42
|
-
font_options.antialias = Conf::ANTIALIAS_OPTS[(@deck.antialias || 'gray').downcase]
|
|
43
|
-
font_options.hint_metrics = 'on' # TODO make this configurable
|
|
44
|
-
font_options.hint_style = 'full' # TODO make this configurable
|
|
45
|
-
layout.context.font_options = font_options
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# :nodoc:
|
|
49
|
-
# @api private
|
|
50
|
-
def set_wh!(layout, width, height)
|
|
51
|
-
layout.width = width * Pango::SCALE unless width.nil? || width == :auto
|
|
52
|
-
layout.height = height * Pango::SCALE unless height.nil? || height == :auto
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Compute the width of the carve that we need
|
|
56
|
-
def compute_carve(rule, range)
|
|
57
|
-
w = rule[:box].width[@index]
|
|
58
|
-
if w == :native
|
|
59
|
-
file = rule[:file][@index].file
|
|
60
|
-
case rule[:type]
|
|
61
|
-
when :png
|
|
62
|
-
Squib.cache_load_image(file).width.to_f / (range.size - 1)
|
|
63
|
-
when :svg
|
|
64
|
-
svg_data = rule[:svg_args].data[@index]
|
|
65
|
-
unless file.to_s.empty? || svg_data.to_s.empty?
|
|
66
|
-
Squib.logger.warn 'Both an SVG file and SVG data were specified'
|
|
67
|
-
end
|
|
68
|
-
return 0 if (file.nil? or file.eql? '') and svg_data.nil?
|
|
69
|
-
svg_data = File.read(file) if svg_data.to_s.empty?
|
|
70
|
-
RSVG::Handle.new_from_data(svg_data).width
|
|
71
|
-
end
|
|
72
|
-
else
|
|
73
|
-
rule[:box].width[@index] * Pango::SCALE / (range.size - 1)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# # :nodoc:
|
|
78
|
-
# # @api private
|
|
79
|
-
def embed_images!(embed, str, layout, valign, scale)
|
|
80
|
-
return [] unless embed.rules.any?
|
|
81
|
-
layout.markup = str
|
|
82
|
-
clean_str = layout.text
|
|
83
|
-
attrs = layout.attributes || Pango::AttrList.new
|
|
84
|
-
EmbeddingUtils.indices(clean_str, embed.rules.keys).each do |key, ranges|
|
|
85
|
-
rule = embed.rules[key]
|
|
86
|
-
ranges.each do |range|
|
|
87
|
-
carve = Pango::Rectangle.new(0, 0, compute_carve(rule, range) * scale, 0)
|
|
88
|
-
att = Pango::AttrShape.new(carve, carve, rule)
|
|
89
|
-
att.start_index = range.first
|
|
90
|
-
att.end_index = range.last
|
|
91
|
-
attrs.insert(att)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
layout.attributes = attrs
|
|
95
|
-
layout.context.set_shape_renderer do |cxt, att, do_path|
|
|
96
|
-
unless do_path # when stroking the text
|
|
97
|
-
rule = att.data
|
|
98
|
-
x = Pango.pixels(layout.index_to_pos(att.start_index).x) +
|
|
99
|
-
rule[:adjust].dx[@index]
|
|
100
|
-
y = Pango.pixels(layout.index_to_pos(att.start_index).y) +
|
|
101
|
-
rule[:adjust].dy[@index] +
|
|
102
|
-
compute_valign(layout, valign, rule[:box].height[@index])
|
|
103
|
-
rule[:draw].call(self, x, y, scale)
|
|
104
|
-
cxt.reset_clip
|
|
105
|
-
[cxt, att, do_path]
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def stroke_outline!(cc, layout, draw)
|
|
111
|
-
if draw.stroke_width > 0
|
|
112
|
-
cc.pango_layout_path(layout)
|
|
113
|
-
cc.fancy_stroke draw
|
|
114
|
-
cc.set_source_squibcolor(draw.color)
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def warn_if_ellipsized(layout)
|
|
119
|
-
if @deck.conf.warn_ellipsize? && layout.ellipsized?
|
|
120
|
-
Squib.logger.warn { "Ellipsized (too much text). Card \##{@index}. Text: \"#{layout.text}\". \n (To disable this warning, set warn_ellipsize: false in config.yml)" }
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# @api private
|
|
125
|
-
def text(embed, para, box, trans, draw, dpi)
|
|
126
|
-
font_desc = Pango::FontDescription.new(para.font)
|
|
127
|
-
font_desc.size = para.font_size * Pango::SCALE if para.font_size.is_a? Numeric
|
|
128
|
-
orig_font_size = font_desc.size
|
|
129
|
-
|
|
130
|
-
# If text autoscaling is enabled, find the largest text size (smaller or equal to the set text size) that fits
|
|
131
|
-
if para.ellipsize == :autoscale
|
|
132
|
-
para.ellipsize = Pango::EllipsizeMode::END
|
|
133
|
-
sizes = sizes = (1 .. font_desc.size).to_a.reverse
|
|
134
|
-
|
|
135
|
-
# Dummy render to an area outside the card with decreasing font sizes until text no longer ellipsizes
|
|
136
|
-
max_fitting_size = sizes.bsearch{ |sz|
|
|
137
|
-
font_desc.size = sz
|
|
138
|
-
extents = render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, true)
|
|
139
|
-
!extents[:ellipsized]
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if max_fitting_size.nil?
|
|
143
|
-
max_fitting_size = sizes.last
|
|
144
|
-
Squib.logger.warn{"Could not autosize for Card \##{@index} as minimum specified size #{max_fitting_size} still ellipsizes."}
|
|
145
|
-
end
|
|
146
|
-
font_desc.size = max_fitting_size
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, false)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# :nodoc:
|
|
153
|
-
# @api private
|
|
154
|
-
def render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, dummy_draw)
|
|
155
|
-
Squib.logger.debug {"Rendering text with: \n#{para} \nat:\n #{box} \ndraw:\n #{draw} \ntransform: #{trans}"}
|
|
156
|
-
extents = nil
|
|
157
|
-
use_cairo do |cc|
|
|
158
|
-
cc.set_source_squibcolor(draw.color)
|
|
159
|
-
cc.translate(box.x, box.y)
|
|
160
|
-
cc.translate(-10000, -10000) if dummy_draw
|
|
161
|
-
cc.rotate(trans.angle)
|
|
162
|
-
cc.move_to(0, 0)
|
|
163
|
-
|
|
164
|
-
layout = cc.create_pango_layout
|
|
165
|
-
layout.font_description = font_desc
|
|
166
|
-
layout.text = para.str.to_s
|
|
167
|
-
layout.context.resolution = dpi
|
|
168
|
-
if para.markup
|
|
169
|
-
para.str = @deck.typographer.process(layout.text)
|
|
170
|
-
layout.markup = para.str.to_s
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
set_font_rendering_opts!(layout)
|
|
174
|
-
set_wh!(layout, box.width, box.height)
|
|
175
|
-
layout.wrap = para.wrap
|
|
176
|
-
layout.ellipsize = para.ellipsize
|
|
177
|
-
layout.alignment = para.align
|
|
178
|
-
|
|
179
|
-
layout.justify = para.justify unless para.justify.nil?
|
|
180
|
-
layout.spacing = para.spacing unless para.spacing.nil?
|
|
181
|
-
|
|
182
|
-
embed_images!(embed, para.str, layout, para.valign, font_desc.size / orig_font_size.to_f)
|
|
183
|
-
|
|
184
|
-
vertical_start = compute_valign(layout, para.valign, 0)
|
|
185
|
-
cc.move_to(0, vertical_start)
|
|
186
|
-
|
|
187
|
-
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :stroke_first
|
|
188
|
-
cc.move_to(0, vertical_start)
|
|
189
|
-
|
|
190
|
-
cc.show_pango_layout(layout)
|
|
191
|
-
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :fill_first
|
|
192
|
-
draw_text_hint(cc, box.x, box.y, layout, para.hint)
|
|
193
|
-
extents = { width: layout.extents[1].width / Pango::SCALE,
|
|
194
|
-
height: layout.extents[1].height / Pango::SCALE,
|
|
195
|
-
ellipsized: layout.ellipsized?}
|
|
196
|
-
warn_if_ellipsized layout unless dummy_draw
|
|
197
|
-
end
|
|
198
|
-
return extents
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
end
|
|
202
|
-
end
|
|
1
|
+
require 'pango'
|
|
2
|
+
require_relative '../args/typographer'
|
|
3
|
+
require_relative 'embedding_utils'
|
|
4
|
+
|
|
5
|
+
module Squib
|
|
6
|
+
class Card
|
|
7
|
+
|
|
8
|
+
# :nodoc:
|
|
9
|
+
# @api private
|
|
10
|
+
def draw_text_hint(cc, x, y, layout, color)
|
|
11
|
+
color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
|
|
12
|
+
return if color.to_s.eql? 'off' or color.nil?
|
|
13
|
+
# when w,h < 0, it was never set. extents[1] are ink extents
|
|
14
|
+
w = layout.width / Pango::SCALE
|
|
15
|
+
w = layout.extents[1].width / Pango::SCALE if w < 0
|
|
16
|
+
h = layout.height / Pango::SCALE
|
|
17
|
+
h = layout.extents[1].height / Pango::SCALE if h < 0
|
|
18
|
+
cc.rounded_rectangle(0, 0, w, h, 0, 0)
|
|
19
|
+
cc.set_source_color(color)
|
|
20
|
+
cc.set_line_width(2.0)
|
|
21
|
+
cc.stroke
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# :nodoc:
|
|
25
|
+
# @api private
|
|
26
|
+
def compute_valign(layout, valign, embed_h)
|
|
27
|
+
return 0 unless layout.height > 0
|
|
28
|
+
ink_extents = layout.extents[1]
|
|
29
|
+
ink_extents.height = embed_h * Pango::SCALE if ink_extents.height == 0 # JUST embed, bug #134
|
|
30
|
+
case valign.to_s.downcase
|
|
31
|
+
when 'middle'
|
|
32
|
+
Pango.pixels((layout.height - ink_extents.height) / 2)
|
|
33
|
+
when 'bottom'
|
|
34
|
+
Pango.pixels(layout.height - ink_extents.height)
|
|
35
|
+
else
|
|
36
|
+
0
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_font_rendering_opts!(layout)
|
|
41
|
+
font_options = Cairo::FontOptions.new
|
|
42
|
+
font_options.antialias = Conf::ANTIALIAS_OPTS[(@deck.antialias || 'gray').downcase]
|
|
43
|
+
font_options.hint_metrics = 'on' # TODO make this configurable
|
|
44
|
+
font_options.hint_style = 'full' # TODO make this configurable
|
|
45
|
+
layout.context.font_options = font_options
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# :nodoc:
|
|
49
|
+
# @api private
|
|
50
|
+
def set_wh!(layout, width, height)
|
|
51
|
+
layout.width = width * Pango::SCALE unless width.nil? || width == :auto
|
|
52
|
+
layout.height = height * Pango::SCALE unless height.nil? || height == :auto
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Compute the width of the carve that we need
|
|
56
|
+
def compute_carve(rule, range)
|
|
57
|
+
w = rule[:box].width[@index]
|
|
58
|
+
if w == :native
|
|
59
|
+
file = rule[:file][@index].file
|
|
60
|
+
case rule[:type]
|
|
61
|
+
when :png
|
|
62
|
+
Squib.cache_load_image(file).width.to_f / (range.size - 1)
|
|
63
|
+
when :svg
|
|
64
|
+
svg_data = rule[:svg_args].data[@index]
|
|
65
|
+
unless file.to_s.empty? || svg_data.to_s.empty?
|
|
66
|
+
Squib.logger.warn 'Both an SVG file and SVG data were specified'
|
|
67
|
+
end
|
|
68
|
+
return 0 if (file.nil? or file.eql? '') and svg_data.nil?
|
|
69
|
+
svg_data = File.read(file) if svg_data.to_s.empty?
|
|
70
|
+
RSVG::Handle.new_from_data(svg_data).width
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
rule[:box].width[@index] * Pango::SCALE / (range.size - 1)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# # :nodoc:
|
|
78
|
+
# # @api private
|
|
79
|
+
def embed_images!(embed, str, layout, valign, scale)
|
|
80
|
+
return [] unless embed.rules.any?
|
|
81
|
+
layout.markup = str
|
|
82
|
+
clean_str = layout.text
|
|
83
|
+
attrs = layout.attributes || Pango::AttrList.new
|
|
84
|
+
EmbeddingUtils.indices(clean_str, embed.rules.keys).each do |key, ranges|
|
|
85
|
+
rule = embed.rules[key]
|
|
86
|
+
ranges.each do |range|
|
|
87
|
+
carve = Pango::Rectangle.new(0, 0, compute_carve(rule, range) * scale, 0)
|
|
88
|
+
att = Pango::AttrShape.new(carve, carve, rule)
|
|
89
|
+
att.start_index = range.first
|
|
90
|
+
att.end_index = range.last
|
|
91
|
+
attrs.insert(att)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
layout.attributes = attrs
|
|
95
|
+
layout.context.set_shape_renderer do |cxt, att, do_path|
|
|
96
|
+
unless do_path # when stroking the text
|
|
97
|
+
rule = att.data
|
|
98
|
+
x = Pango.pixels(layout.index_to_pos(att.start_index).x) +
|
|
99
|
+
rule[:adjust].dx[@index]
|
|
100
|
+
y = Pango.pixels(layout.index_to_pos(att.start_index).y) +
|
|
101
|
+
rule[:adjust].dy[@index] +
|
|
102
|
+
compute_valign(layout, valign, rule[:box].height[@index])
|
|
103
|
+
rule[:draw].call(self, x, y, scale)
|
|
104
|
+
cxt.reset_clip
|
|
105
|
+
[cxt, att, do_path]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def stroke_outline!(cc, layout, draw)
|
|
111
|
+
if draw.stroke_width > 0
|
|
112
|
+
cc.pango_layout_path(layout)
|
|
113
|
+
cc.fancy_stroke draw
|
|
114
|
+
cc.set_source_squibcolor(draw.color)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def warn_if_ellipsized(layout)
|
|
119
|
+
if @deck.conf.warn_ellipsize? && layout.ellipsized?
|
|
120
|
+
Squib.logger.warn { "Ellipsized (too much text). Card \##{@index}. Text: \"#{layout.text}\". \n (To disable this warning, set warn_ellipsize: false in config.yml)" }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @api private
|
|
125
|
+
def text(embed, para, box, trans, draw, dpi)
|
|
126
|
+
font_desc = Pango::FontDescription.new(para.font)
|
|
127
|
+
font_desc.size = para.font_size * Pango::SCALE if para.font_size.is_a? Numeric
|
|
128
|
+
orig_font_size = font_desc.size
|
|
129
|
+
|
|
130
|
+
# If text autoscaling is enabled, find the largest text size (smaller or equal to the set text size) that fits
|
|
131
|
+
if para.ellipsize == :autoscale
|
|
132
|
+
para.ellipsize = Pango::EllipsizeMode::END
|
|
133
|
+
sizes = sizes = (1 .. font_desc.size).to_a.reverse
|
|
134
|
+
|
|
135
|
+
# Dummy render to an area outside the card with decreasing font sizes until text no longer ellipsizes
|
|
136
|
+
max_fitting_size = sizes.bsearch{ |sz|
|
|
137
|
+
font_desc.size = sz
|
|
138
|
+
extents = render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, true)
|
|
139
|
+
!extents[:ellipsized]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if max_fitting_size.nil?
|
|
143
|
+
max_fitting_size = sizes.last
|
|
144
|
+
Squib.logger.warn{"Could not autosize for Card \##{@index} as minimum specified size #{max_fitting_size} still ellipsizes."}
|
|
145
|
+
end
|
|
146
|
+
font_desc.size = max_fitting_size
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, false)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# :nodoc:
|
|
153
|
+
# @api private
|
|
154
|
+
def render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, dummy_draw)
|
|
155
|
+
Squib.logger.debug {"Rendering text with: \n#{para} \nat:\n #{box} \ndraw:\n #{draw} \ntransform: #{trans}"}
|
|
156
|
+
extents = nil
|
|
157
|
+
use_cairo do |cc|
|
|
158
|
+
cc.set_source_squibcolor(draw.color)
|
|
159
|
+
cc.translate(box.x, box.y)
|
|
160
|
+
cc.translate(-10000, -10000) if dummy_draw
|
|
161
|
+
cc.rotate(trans.angle)
|
|
162
|
+
cc.move_to(0, 0)
|
|
163
|
+
|
|
164
|
+
layout = cc.create_pango_layout
|
|
165
|
+
layout.font_description = font_desc
|
|
166
|
+
layout.text = para.str.to_s
|
|
167
|
+
layout.context.resolution = dpi
|
|
168
|
+
if para.markup
|
|
169
|
+
para.str = @deck.typographer.process(layout.text)
|
|
170
|
+
layout.markup = para.str.to_s
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
set_font_rendering_opts!(layout)
|
|
174
|
+
set_wh!(layout, box.width, box.height)
|
|
175
|
+
layout.wrap = para.wrap
|
|
176
|
+
layout.ellipsize = para.ellipsize
|
|
177
|
+
layout.alignment = para.align
|
|
178
|
+
|
|
179
|
+
layout.justify = para.justify unless para.justify.nil?
|
|
180
|
+
layout.spacing = para.spacing unless para.spacing.nil?
|
|
181
|
+
|
|
182
|
+
embed_images!(embed, para.str, layout, para.valign, font_desc.size / orig_font_size.to_f)
|
|
183
|
+
|
|
184
|
+
vertical_start = compute_valign(layout, para.valign, 0)
|
|
185
|
+
cc.move_to(0, vertical_start)
|
|
186
|
+
|
|
187
|
+
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :stroke_first
|
|
188
|
+
cc.move_to(0, vertical_start)
|
|
189
|
+
|
|
190
|
+
cc.show_pango_layout(layout)
|
|
191
|
+
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :fill_first
|
|
192
|
+
draw_text_hint(cc, box.x, box.y, layout, para.hint)
|
|
193
|
+
extents = { width: layout.extents[1].width / Pango::SCALE,
|
|
194
|
+
height: layout.extents[1].height / Pango::SCALE,
|
|
195
|
+
ellipsized: layout.ellipsized?}
|
|
196
|
+
warn_if_ellipsized layout unless dummy_draw
|
|
197
|
+
end
|
|
198
|
+
return extents
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
require 'csv'
|
|
2
|
-
require_relative 'quantity_exploder'
|
|
3
|
-
|
|
4
|
-
module Squib::Import
|
|
5
|
-
class CsvImporter
|
|
6
|
-
include Squib::Import::QuantityExploder
|
|
7
|
-
def import_to_dataframe(import, csv_opts, &block)
|
|
8
|
-
data = import.data.nil? ? File.read(import.file) : import.data
|
|
9
|
-
table = CSV.parse(data, **csv_opts.to_hash)
|
|
10
|
-
check_duplicate_csv_headers(table)
|
|
11
|
-
hash = Squib::DataFrame.new
|
|
12
|
-
table.headers.each do |header|
|
|
13
|
-
new_header = header.to_s
|
|
14
|
-
new_header = new_header.strip if import.strip?
|
|
15
|
-
hash[new_header] ||= table[header]
|
|
16
|
-
end
|
|
17
|
-
if import.strip?
|
|
18
|
-
new_hash = Squib::DataFrame.new
|
|
19
|
-
hash.each do |header, col|
|
|
20
|
-
new_hash[header] = col.map do |str|
|
|
21
|
-
str = str.strip if str.respond_to?(:strip)
|
|
22
|
-
str
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
hash = new_hash
|
|
26
|
-
end
|
|
27
|
-
unless block.nil?
|
|
28
|
-
hash.each do |header, col|
|
|
29
|
-
col.map! do |val|
|
|
30
|
-
yield(header, val)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
return explode_quantities(hash, import.explode)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def check_duplicate_csv_headers(table)
|
|
38
|
-
if table.headers.size != table.headers.uniq.size
|
|
39
|
-
dups = table.headers.select{|e| table.headers.count(e) > 1 }
|
|
40
|
-
Squib.logger.warn "CSV duplicated the following column keys: #{dups.join(',')}"
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
1
|
+
require 'csv'
|
|
2
|
+
require_relative 'quantity_exploder'
|
|
3
|
+
|
|
4
|
+
module Squib::Import
|
|
5
|
+
class CsvImporter
|
|
6
|
+
include Squib::Import::QuantityExploder
|
|
7
|
+
def import_to_dataframe(import, csv_opts, &block)
|
|
8
|
+
data = import.data.nil? ? File.read(import.file) : import.data
|
|
9
|
+
table = CSV.parse(data, **csv_opts.to_hash)
|
|
10
|
+
check_duplicate_csv_headers(table)
|
|
11
|
+
hash = Squib::DataFrame.new
|
|
12
|
+
table.headers.each do |header|
|
|
13
|
+
new_header = header.to_s
|
|
14
|
+
new_header = new_header.strip if import.strip?
|
|
15
|
+
hash[new_header] ||= table[header]
|
|
16
|
+
end
|
|
17
|
+
if import.strip?
|
|
18
|
+
new_hash = Squib::DataFrame.new
|
|
19
|
+
hash.each do |header, col|
|
|
20
|
+
new_hash[header] = col.map do |str|
|
|
21
|
+
str = str.strip if str.respond_to?(:strip)
|
|
22
|
+
str
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
hash = new_hash
|
|
26
|
+
end
|
|
27
|
+
unless block.nil?
|
|
28
|
+
hash.each do |header, col|
|
|
29
|
+
col.map! do |val|
|
|
30
|
+
yield(header, val)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
return explode_quantities(hash, import.explode)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def check_duplicate_csv_headers(table)
|
|
38
|
+
if table.headers.size != table.headers.uniq.size
|
|
39
|
+
dups = table.headers.select{|e| table.headers.count(e) > 1 }
|
|
40
|
+
Squib.logger.warn "CSV duplicated the following column keys: #{dups.join(',')}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|