squib 0.8.0 → 0.9.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.
- checksums.yaml +4 -4
- data/.gitmodules +14 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +201 -175
- data/Gemfile +2 -4
- data/README.md +650 -645
- data/RELEASE TODO.md +18 -18
- data/Rakefile +106 -99
- data/appveyor.yml +29 -0
- data/lib/squib.rb +32 -32
- data/lib/squib/api/background.rb +20 -20
- data/lib/squib/api/data.rb +131 -131
- data/lib/squib/api/image.rb +108 -90
- data/lib/squib/api/save.rb +151 -149
- data/lib/squib/api/settings.rb +35 -35
- data/lib/squib/api/shapes.rb +255 -230
- data/lib/squib/api/text.rb +65 -65
- data/lib/squib/api/text_embed.rb +96 -96
- data/lib/squib/args/arg_loader.rb +138 -138
- data/lib/squib/args/box.rb +54 -54
- data/lib/squib/args/card_range.rb +32 -32
- data/lib/squib/args/color_validator.rb +11 -11
- data/lib/squib/args/coords.rb +32 -32
- 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 +39 -39
- data/lib/squib/args/input_file.rb +37 -37
- data/lib/squib/args/paint.rb +43 -43
- data/lib/squib/args/paragraph.rb +116 -115
- data/lib/squib/args/save_batch.rb +63 -60
- data/lib/squib/args/scale_box.rb +53 -53
- data/lib/squib/args/sheet.rb +72 -72
- data/lib/squib/args/showcase_special.rb +38 -38
- data/lib/squib/args/svg_special.rb +37 -37
- data/lib/squib/args/transform.rb +60 -24
- data/lib/squib/args/typographer.rb +117 -117
- data/lib/squib/card.rb +66 -67
- data/lib/squib/conf.rb +131 -117
- data/lib/squib/constants.rb +12 -178
- data/lib/squib/deck.rb +113 -113
- data/lib/squib/graphics/cairo_context_wrapper.rb +113 -99
- data/lib/squib/graphics/gradient_regex.rb +46 -46
- data/lib/squib/graphics/hand.rb +42 -42
- data/lib/squib/graphics/image.rb +103 -76
- data/lib/squib/graphics/save_doc.rb +103 -103
- data/lib/squib/graphics/save_images.rb +39 -33
- data/lib/squib/graphics/shapes.rb +135 -119
- data/lib/squib/graphics/showcase.rb +85 -85
- data/lib/squib/graphics/text.rb +176 -176
- data/lib/squib/layout_parser.rb +91 -91
- data/lib/squib/layouts/economy.yml +85 -85
- data/lib/squib/layouts/fantasy.yml +101 -101
- data/lib/squib/layouts/hand.yml +62 -62
- data/lib/squib/layouts/playing-card.yml +35 -35
- data/lib/squib/layouts/tuck_box.yml +45 -45
- data/lib/squib/project_template/IDEAS.md +22 -0
- data/lib/squib/project_template/PLAYTESTING.md +26 -0
- data/lib/squib/project_template/RULES.md +21 -0
- data/lib/squib/project_template/config.yml +49 -45
- data/lib/squib/sample_helpers.rb +34 -0
- data/lib/squib/version.rb +10 -10
- data/samples/autoscale_font/_autoscale_font.rb +29 -0
- data/samples/color_shortcuts.rb +6 -6
- data/samples/csv_import.rb +26 -26
- data/samples/custom-config.yml +5 -5
- data/samples/custom_config.rb +18 -18
- data/samples/draw_shapes.rb +48 -45
- data/samples/embed_text.rb +88 -88
- data/samples/excel.rb +55 -55
- data/samples/hand.rb +24 -24
- data/samples/images/_images.rb +104 -0
- data/samples/intro/01_hello.rb +9 -0
- data/samples/intro/02_options.rb +15 -0
- data/samples/intro/03_layout.rb +12 -0
- data/samples/intro/04_arrays.rb +16 -0
- data/samples/intro/05_excel.rb +15 -0
- data/samples/layouts.rb +62 -62
- data/samples/layouts_builtin.rb +51 -51
- data/samples/load_images.rb +99 -78
- data/samples/load_images_config.yml +1 -0
- data/samples/quantity_explosion.csv +2 -2
- data/samples/ranges.rb +64 -64
- data/samples/sample.csv +2 -2
- data/samples/saves.rb +9 -1
- data/samples/sprites.png +0 -0
- data/samples/text/_text.rb +46 -0
- data/samples/text_options.rb +102 -102
- data/spec/api/api_data_spec.rb +117 -117
- data/spec/api/api_settings_spec.rb +37 -37
- data/spec/args/box_spec.rb +127 -127
- data/spec/args/draw_spec.rb +101 -95
- data/spec/args/embed_key_spec.rb +13 -13
- data/spec/args/input_file_spec.rb +21 -21
- data/spec/args/paint_spec.rb +21 -21
- data/spec/args/paragraph_spec.rb +152 -152
- data/spec/args/range_spec.rb +40 -40
- data/spec/args/save_batch_spec.rb +51 -51
- data/spec/args/scale_box_spec.rb +71 -71
- data/spec/args/sheet_spec.rb +58 -58
- data/spec/args/showcase_special_spec.rb +15 -15
- data/spec/args/transform_spec.rb +25 -0
- data/spec/card_spec.rb +11 -0
- data/spec/conf_spec.rb +13 -3
- data/spec/data/conf/unrecognized.yml +4 -0
- data/spec/data/csv/qty.csv +2 -2
- data/spec/data/csv/qty_named.csv +2 -2
- data/spec/data/csv/with_spaces.csv +2 -2
- data/spec/data/samples/autoscale_font.rb.txt +84 -84
- data/spec/data/samples/basic.rb.txt +227 -209
- data/spec/data/samples/config_text_markup.rb.txt +72 -72
- data/spec/data/samples/csv_import.rb.txt +213 -213
- data/spec/data/samples/custom_config.rb.txt +57 -48
- data/spec/data/samples/draw_shapes.rb.txt +555 -3
- data/spec/data/samples/embed_text.rb.txt +283 -283
- data/spec/data/samples/excel.rb.txt +661 -661
- data/spec/data/samples/gradients.rb.txt +77 -79
- data/spec/data/samples/hand.rb.txt +538 -538
- data/spec/data/samples/hello_world.rb.txt +36 -36
- data/spec/data/samples/load_images.rb.txt +170 -0
- data/spec/data/samples/portrait-landscape.rb.txt +51 -49
- data/spec/data/samples/ranges.rb.txt +472 -460
- data/spec/data/samples/saves.rb.txt +810 -801
- data/spec/data/samples/showcase.rb.txt +5926 -5910
- data/spec/data/samples/text_options.rb.txt +1125 -1125
- data/spec/data/samples/tgc_proofs.rb.txt +95 -81
- data/spec/graphics/cairo_context_wrapper_spec.rb +104 -84
- data/spec/graphics/graphics_save_doc_spec.rb +67 -67
- data/spec/samples/diff-with-css.example.html +39 -0
- data/spec/samples/expected/load_images_00.png +0 -0
- data/spec/samples/expected/shape_00.png +0 -0
- data/spec/samples/run_samples_spec.rb +17 -0
- data/spec/samples/samples_regression_spec.rb +72 -82
- data/spec/spec_helper.rb +9 -1
- data/squib.gemspec +49 -48
- data/squib.sublime-project +42 -42
- metadata +94 -48
- data/spec/graphics/graphics_images_spec.rb +0 -94
@@ -1,85 +1,85 @@
|
|
1
|
-
require 'squib/graphics/cairo_context_wrapper'
|
2
|
-
|
3
|
-
module Squib
|
4
|
-
class Deck
|
5
|
-
|
6
|
-
# So the Cairo people have said over and over again that they won't support the 3x3 matrices that would handle perspective transforms.
|
7
|
-
# Since our perspective transform needs are a bit simpler, we can use a "striping method" that does the job for us.
|
8
|
-
# It's a little bit involved, but it works well enough for limited ranges of our parameters.
|
9
|
-
# These were also helpful:
|
10
|
-
# http://kapo-cpp.blogspot.com/2008/01/perspective-effect-using-cairo.html
|
11
|
-
# http://zetcode.com/gui/pygtk/drawingII/
|
12
|
-
# :nodoc:
|
13
|
-
# @api private
|
14
|
-
def render_showcase(range, sheet, showcase)
|
15
|
-
out_width = range.size * ((@width - 2*sheet.trim) * showcase.scale * showcase.offset) + 2*sheet.margin
|
16
|
-
out_height = showcase.reflect_offset + (1.0 + showcase.reflect_percent) * (@height - 2*sheet.trim) + 2*sheet.margin
|
17
|
-
out_cc = Cairo::Context.new(Cairo::ImageSurface.new(out_width, out_height))
|
18
|
-
wrapper = Squib::Graphics::CairoContextWrapper.new(out_cc)
|
19
|
-
wrapper.set_source_squibcolor(sheet.fill_color)
|
20
|
-
wrapper.paint
|
21
|
-
|
22
|
-
cards = range.collect { |i| @cards[i] }
|
23
|
-
cards.each_with_index do |card, i|
|
24
|
-
trimmed = trim_rounded(card.cairo_surface, sheet.trim, sheet.trim_radius)
|
25
|
-
reflected = reflect(trimmed, showcase.reflect_offset, showcase.reflect_percent, showcase.reflect_strength)
|
26
|
-
perspectived = perspective(reflected, showcase.scale, showcase.face_right?)
|
27
|
-
out_cc.set_source(perspectived, sheet.margin + i * perspectived.width * showcase.offset, sheet.margin)
|
28
|
-
out_cc.paint
|
29
|
-
end
|
30
|
-
out_cc.target.write_to_png("#{sheet.dir}/#{sheet.file}")
|
31
|
-
end
|
32
|
-
|
33
|
-
# :nodoc:
|
34
|
-
# @api private
|
35
|
-
def trim_rounded(src, trim, radius)
|
36
|
-
trim_cc = Cairo::Context.new(Cairo::ImageSurface.new(src.width-2.0*trim, src.height-2.0*trim))
|
37
|
-
trim_cc.rounded_rectangle(0, 0, trim_cc.target.width, trim_cc.target.height, radius, radius)
|
38
|
-
trim_cc.set_source(src, -1 * trim, -1 * trim)
|
39
|
-
trim_cc.clip
|
40
|
-
trim_cc.paint
|
41
|
-
return trim_cc.target
|
42
|
-
end
|
43
|
-
|
44
|
-
# :nodoc:
|
45
|
-
# @api private
|
46
|
-
def reflect(src, roffset, rpercent, rstrength)
|
47
|
-
tmp_cc = Cairo::Context.new(Cairo::ImageSurface.new(src.width, src.height * (1.0 + rpercent) + roffset))
|
48
|
-
tmp_cc.set_source(src, 0, 0)
|
49
|
-
tmp_cc.paint
|
50
|
-
# Flip affine magic from: http://cairographics.org/matrix_transform/
|
51
|
-
matrix = Cairo::Matrix.new(1, 0, 0, -1, 0, 2 * src.height + roffset)
|
52
|
-
tmp_cc.transform(matrix) # flips the coordinate system
|
53
|
-
top_y = src.height # top of the reflection
|
54
|
-
bottom_y = src.height * (1.0 - rpercent) + roffset # bottom of the reflection
|
55
|
-
gradient = Cairo::LinearPattern.new(0,top_y, 0,bottom_y)
|
56
|
-
gradient.add_color_stop_rgba(0.0, 0,0,0, rstrength) # start a little reflected
|
57
|
-
gradient.add_color_stop_rgba(1.0, 0,0,0, 0.0) # fade to nothing
|
58
|
-
tmp_cc.set_source(src, 0, 0)
|
59
|
-
tmp_cc.mask(gradient)
|
60
|
-
return tmp_cc.target
|
61
|
-
end
|
62
|
-
|
63
|
-
# :nodoc:
|
64
|
-
# @api private
|
65
|
-
def perspective(src, scale, face_right)
|
66
|
-
dest_cxt = Cairo::Context.new(Cairo::ImageSurface.new(src.width * scale, src.height))
|
67
|
-
in_thickness = 1 # Take strip 1 pixel-width at a time
|
68
|
-
out_thickness = 3 # Scale it to 3 pixels wider to cover any gaps
|
69
|
-
(0..src.width).step(in_thickness) do |i|
|
70
|
-
percentage = i / src.width.to_f
|
71
|
-
i = src.width - i if face_right
|
72
|
-
factor = scale + (percentage * (1.0 - scale)) #linear interpolation
|
73
|
-
dest_cxt.save
|
74
|
-
dest_cxt.translate 0, src.height / 2.0 * (1.0 - factor)
|
75
|
-
dest_cxt.scale factor * scale, factor
|
76
|
-
dest_cxt.set_source src, 0, 0
|
77
|
-
dest_cxt.rounded_rectangle i, 0, out_thickness, src.height, 0,0
|
78
|
-
dest_cxt.fill
|
79
|
-
dest_cxt.restore
|
80
|
-
end
|
81
|
-
return dest_cxt.target
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
end
|
1
|
+
require 'squib/graphics/cairo_context_wrapper'
|
2
|
+
|
3
|
+
module Squib
|
4
|
+
class Deck
|
5
|
+
|
6
|
+
# So the Cairo people have said over and over again that they won't support the 3x3 matrices that would handle perspective transforms.
|
7
|
+
# Since our perspective transform needs are a bit simpler, we can use a "striping method" that does the job for us.
|
8
|
+
# It's a little bit involved, but it works well enough for limited ranges of our parameters.
|
9
|
+
# These were also helpful:
|
10
|
+
# http://kapo-cpp.blogspot.com/2008/01/perspective-effect-using-cairo.html
|
11
|
+
# http://zetcode.com/gui/pygtk/drawingII/
|
12
|
+
# :nodoc:
|
13
|
+
# @api private
|
14
|
+
def render_showcase(range, sheet, showcase)
|
15
|
+
out_width = range.size * ((@width - 2*sheet.trim) * showcase.scale * showcase.offset) + 2*sheet.margin
|
16
|
+
out_height = showcase.reflect_offset + (1.0 + showcase.reflect_percent) * (@height - 2*sheet.trim) + 2*sheet.margin
|
17
|
+
out_cc = Cairo::Context.new(Cairo::ImageSurface.new(out_width, out_height))
|
18
|
+
wrapper = Squib::Graphics::CairoContextWrapper.new(out_cc)
|
19
|
+
wrapper.set_source_squibcolor(sheet.fill_color)
|
20
|
+
wrapper.paint
|
21
|
+
|
22
|
+
cards = range.collect { |i| @cards[i] }
|
23
|
+
cards.each_with_index do |card, i|
|
24
|
+
trimmed = trim_rounded(card.cairo_surface, sheet.trim, sheet.trim_radius)
|
25
|
+
reflected = reflect(trimmed, showcase.reflect_offset, showcase.reflect_percent, showcase.reflect_strength)
|
26
|
+
perspectived = perspective(reflected, showcase.scale, showcase.face_right?)
|
27
|
+
out_cc.set_source(perspectived, sheet.margin + i * perspectived.width * showcase.offset, sheet.margin)
|
28
|
+
out_cc.paint
|
29
|
+
end
|
30
|
+
out_cc.target.write_to_png("#{sheet.dir}/#{sheet.file}")
|
31
|
+
end
|
32
|
+
|
33
|
+
# :nodoc:
|
34
|
+
# @api private
|
35
|
+
def trim_rounded(src, trim, radius)
|
36
|
+
trim_cc = Cairo::Context.new(Cairo::ImageSurface.new(src.width-2.0*trim, src.height-2.0*trim))
|
37
|
+
trim_cc.rounded_rectangle(0, 0, trim_cc.target.width, trim_cc.target.height, radius, radius)
|
38
|
+
trim_cc.set_source(src, -1 * trim, -1 * trim)
|
39
|
+
trim_cc.clip
|
40
|
+
trim_cc.paint
|
41
|
+
return trim_cc.target
|
42
|
+
end
|
43
|
+
|
44
|
+
# :nodoc:
|
45
|
+
# @api private
|
46
|
+
def reflect(src, roffset, rpercent, rstrength)
|
47
|
+
tmp_cc = Cairo::Context.new(Cairo::ImageSurface.new(src.width, src.height * (1.0 + rpercent) + roffset))
|
48
|
+
tmp_cc.set_source(src, 0, 0)
|
49
|
+
tmp_cc.paint
|
50
|
+
# Flip affine magic from: http://cairographics.org/matrix_transform/
|
51
|
+
matrix = Cairo::Matrix.new(1, 0, 0, -1, 0, 2 * src.height + roffset)
|
52
|
+
tmp_cc.transform(matrix) # flips the coordinate system
|
53
|
+
top_y = src.height # top of the reflection
|
54
|
+
bottom_y = src.height * (1.0 - rpercent) + roffset # bottom of the reflection
|
55
|
+
gradient = Cairo::LinearPattern.new(0,top_y, 0,bottom_y)
|
56
|
+
gradient.add_color_stop_rgba(0.0, 0,0,0, rstrength) # start a little reflected
|
57
|
+
gradient.add_color_stop_rgba(1.0, 0,0,0, 0.0) # fade to nothing
|
58
|
+
tmp_cc.set_source(src, 0, 0)
|
59
|
+
tmp_cc.mask(gradient)
|
60
|
+
return tmp_cc.target
|
61
|
+
end
|
62
|
+
|
63
|
+
# :nodoc:
|
64
|
+
# @api private
|
65
|
+
def perspective(src, scale, face_right)
|
66
|
+
dest_cxt = Cairo::Context.new(Cairo::ImageSurface.new(src.width * scale, src.height))
|
67
|
+
in_thickness = 1 # Take strip 1 pixel-width at a time
|
68
|
+
out_thickness = 3 # Scale it to 3 pixels wider to cover any gaps
|
69
|
+
(0..src.width).step(in_thickness) do |i|
|
70
|
+
percentage = i / src.width.to_f
|
71
|
+
i = src.width - i if face_right
|
72
|
+
factor = scale + (percentage * (1.0 - scale)) #linear interpolation
|
73
|
+
dest_cxt.save
|
74
|
+
dest_cxt.translate 0, src.height / 2.0 * (1.0 - factor)
|
75
|
+
dest_cxt.scale factor * scale, factor
|
76
|
+
dest_cxt.set_source src, 0, 0
|
77
|
+
dest_cxt.rounded_rectangle i, 0, out_thickness, src.height, 0,0
|
78
|
+
dest_cxt.fill
|
79
|
+
dest_cxt.restore
|
80
|
+
end
|
81
|
+
return dest_cxt.target
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
data/lib/squib/graphics/text.rb
CHANGED
@@ -1,176 +1,176 @@
|
|
1
|
-
require 'pango'
|
2
|
-
require 'squib/args/typographer'
|
3
|
-
|
4
|
-
module Squib
|
5
|
-
class Card
|
6
|
-
|
7
|
-
# :nodoc:
|
8
|
-
# @api private
|
9
|
-
def draw_text_hint(cc, x, y, layout, color)
|
10
|
-
color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
|
11
|
-
return if color.to_s.eql? 'off' or color.nil?
|
12
|
-
# when w,h < 0, it was never set. extents[1] are ink extents
|
13
|
-
w = layout.width / Pango::SCALE
|
14
|
-
w = layout.extents[1].width / Pango::SCALE if w < 0
|
15
|
-
h = layout.height / Pango::SCALE
|
16
|
-
h = layout.extents[1].height / Pango::SCALE if h < 0
|
17
|
-
cc.rounded_rectangle(0, 0, w, h, 0, 0)
|
18
|
-
cc.set_source_color(color)
|
19
|
-
cc.set_line_width(2.0)
|
20
|
-
cc.stroke
|
21
|
-
end
|
22
|
-
|
23
|
-
# :nodoc:
|
24
|
-
# @api private
|
25
|
-
def compute_valign(layout, valign)
|
26
|
-
return 0 unless layout.height > 0
|
27
|
-
ink_extents = layout.extents[1]
|
28
|
-
case valign.to_s.downcase
|
29
|
-
when 'middle'
|
30
|
-
Pango.pixels( (layout.height - ink_extents.height) / 2)
|
31
|
-
when 'bottom'
|
32
|
-
Pango.pixels(layout.height - ink_extents.height)
|
33
|
-
else
|
34
|
-
0
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def set_font_rendering_opts!(layout)
|
39
|
-
font_options = Cairo::FontOptions.new
|
40
|
-
font_options.antialias = Conf::ANTIALIAS_OPTS[(@deck.antialias || 'gray').downcase]
|
41
|
-
font_options.hint_metrics = 'on' # TODO make this configurable
|
42
|
-
font_options.hint_style = 'full' # TODO make this configurable
|
43
|
-
layout.context.font_options = font_options
|
44
|
-
end
|
45
|
-
|
46
|
-
# :nodoc:
|
47
|
-
# @api private
|
48
|
-
def set_wh!(layout, width, height)
|
49
|
-
layout.width = width * Pango::SCALE unless width.nil? || width == :auto
|
50
|
-
layout.height = height * Pango::SCALE unless height.nil? || height == :auto
|
51
|
-
end
|
52
|
-
|
53
|
-
# :nodoc:
|
54
|
-
# @api private
|
55
|
-
def next_embed(keys, str)
|
56
|
-
ret = nil
|
57
|
-
ret_key = nil
|
58
|
-
keys.each do |key|
|
59
|
-
i = str.index(key)
|
60
|
-
ret ||= i
|
61
|
-
unless i.nil? || i > ret
|
62
|
-
ret = i
|
63
|
-
ret_key = key
|
64
|
-
end
|
65
|
-
end
|
66
|
-
ret_key
|
67
|
-
end
|
68
|
-
|
69
|
-
# When we do embedded icons, we create a 3-character string that has a font
|
70
|
-
# size of zero and a letter-spacing that fits the icon we need. That gives
|
71
|
-
# us the wrapping behavior we need but no clutter beneath. On most
|
72
|
-
# platforms, this works fine. On Linux, this creates
|
73
|
-
# a Cairo transformation matrix that
|
74
|
-
ZERO_WIDTH_CHAR_SIZE = 0 # this works on most platforms
|
75
|
-
# some platforms make this Pango pixels (1/1024), others a 1 pt font
|
76
|
-
ZERO_WIDTH_CHAR_SIZE = 1 if RbConfig::CONFIG['host_os'] === 'linux-gnu'
|
77
|
-
|
78
|
-
# :nodoc:
|
79
|
-
# @api private
|
80
|
-
def process_embeds(embed, str, layout)
|
81
|
-
return [] unless embed.rules.any?
|
82
|
-
layout.markup = str
|
83
|
-
clean_str = layout.text
|
84
|
-
draw_calls = []
|
85
|
-
searches = []
|
86
|
-
while (key = next_embed(embed.rules.keys, clean_str)) != nil
|
87
|
-
rule = embed.rules[key]
|
88
|
-
spacing = rule[:box].width[@index] * Pango::SCALE
|
89
|
-
kindex = clean_str.index(key)
|
90
|
-
kindex = clean_str[0..kindex].bytesize #convert to byte index (bug #57)
|
91
|
-
str = str.sub(key, "<span size=\"#{ZERO_WIDTH_CHAR_SIZE}\">a<span letter_spacing=\"#{spacing.to_i}\">a</span>a</span>")
|
92
|
-
layout.markup = str
|
93
|
-
clean_str = layout.text
|
94
|
-
searches << { index: kindex, rule: rule }
|
95
|
-
end
|
96
|
-
searches.each do |search|
|
97
|
-
rect = layout.index_to_pos(search[:index])
|
98
|
-
x = Pango.pixels(rect.x) + search[:rule][:adjust].dx[@index]
|
99
|
-
y = Pango.pixels(rect.y) + search[:rule][:adjust].dy[@index]
|
100
|
-
draw_calls << {x: x, y: y, draw: search[:rule][:draw]} # defer drawing until we've valigned
|
101
|
-
end
|
102
|
-
return draw_calls
|
103
|
-
end
|
104
|
-
|
105
|
-
def stroke_outline!(cc, layout, draw)
|
106
|
-
if draw.stroke_width > 0
|
107
|
-
cc.pango_layout_path(layout)
|
108
|
-
cc.fancy_stroke draw
|
109
|
-
cc.set_source_squibcolor(draw.color)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def warn_if_ellipsized(layout)
|
114
|
-
if @deck.conf.warn_ellipsize? && layout.ellipsized?
|
115
|
-
Squib.logger.warn { "Ellipsized (too much text). Card \##{@index}. Text: \"#{layout.text}\". \n (To disable this warning, set warn_ellipsize: false in config.yml)" }
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# :nodoc:
|
120
|
-
# @api private
|
121
|
-
def text(embed, para, box, trans, draw)
|
122
|
-
Squib.logger.debug {"Rendering text with: \n#{para} \nat:\n #{box} \ndraw:\n #{draw} \ntransform: #{trans}"}
|
123
|
-
extents = nil
|
124
|
-
use_cairo do |cc|
|
125
|
-
cc.set_source_squibcolor(draw.color)
|
126
|
-
cc.translate(box.x, box.y)
|
127
|
-
cc.rotate(trans.angle)
|
128
|
-
cc.move_to(0, 0)
|
129
|
-
|
130
|
-
font_desc = Pango::FontDescription.new(para.font)
|
131
|
-
font_desc.size = para.font_size * Pango::SCALE unless para.font_size.nil?
|
132
|
-
layout = cc.create_pango_layout
|
133
|
-
layout.font_description = font_desc
|
134
|
-
layout.text = para.str
|
135
|
-
if para.markup
|
136
|
-
para.str = @deck.typographer.process(layout.text)
|
137
|
-
layout.markup = para.str
|
138
|
-
end
|
139
|
-
|
140
|
-
set_font_rendering_opts!(layout)
|
141
|
-
set_wh!(layout, box.width, box.height)
|
142
|
-
layout.wrap = para.wrap
|
143
|
-
layout.ellipsize = para.ellipsize
|
144
|
-
layout.alignment = para.align
|
145
|
-
|
146
|
-
layout.justify = para.justify unless para.justify.nil?
|
147
|
-
layout.spacing = para.spacing unless para.spacing.nil?
|
148
|
-
|
149
|
-
embed_draws = process_embeds(embed, para.str, layout)
|
150
|
-
|
151
|
-
vertical_start = compute_valign(layout, para.valign)
|
152
|
-
cc.move_to(0, vertical_start) #TODO clean this up a bit
|
153
|
-
|
154
|
-
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :stroke_first
|
155
|
-
cc.move_to(0, vertical_start)
|
156
|
-
cc.show_pango_layout(layout)
|
157
|
-
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :fill_first
|
158
|
-
begin
|
159
|
-
embed_draws.each { |ed| ed[:draw].call(self, ed[:x], ed[:y] + vertical_start) }
|
160
|
-
rescue Exception => e
|
161
|
-
puts "====EXCEPTION!===="
|
162
|
-
puts e
|
163
|
-
puts "If this was a non-invertible matrix error, this is a known issue with a potential workaround. Please report it at: https://github.com/andymeneely/squib/issues/55"
|
164
|
-
puts "=================="
|
165
|
-
raise e
|
166
|
-
end
|
167
|
-
draw_text_hint(cc, box.x, box.y, layout, para.hint)
|
168
|
-
extents = { width: layout.extents[1].width / Pango::SCALE,
|
169
|
-
height: layout.extents[1].height / Pango::SCALE }
|
170
|
-
warn_if_ellipsized layout
|
171
|
-
end
|
172
|
-
return extents
|
173
|
-
end
|
174
|
-
|
175
|
-
end
|
176
|
-
end
|
1
|
+
require 'pango'
|
2
|
+
require 'squib/args/typographer'
|
3
|
+
|
4
|
+
module Squib
|
5
|
+
class Card
|
6
|
+
|
7
|
+
# :nodoc:
|
8
|
+
# @api private
|
9
|
+
def draw_text_hint(cc, x, y, layout, color)
|
10
|
+
color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
|
11
|
+
return if color.to_s.eql? 'off' or color.nil?
|
12
|
+
# when w,h < 0, it was never set. extents[1] are ink extents
|
13
|
+
w = layout.width / Pango::SCALE
|
14
|
+
w = layout.extents[1].width / Pango::SCALE if w < 0
|
15
|
+
h = layout.height / Pango::SCALE
|
16
|
+
h = layout.extents[1].height / Pango::SCALE if h < 0
|
17
|
+
cc.rounded_rectangle(0, 0, w, h, 0, 0)
|
18
|
+
cc.set_source_color(color)
|
19
|
+
cc.set_line_width(2.0)
|
20
|
+
cc.stroke
|
21
|
+
end
|
22
|
+
|
23
|
+
# :nodoc:
|
24
|
+
# @api private
|
25
|
+
def compute_valign(layout, valign)
|
26
|
+
return 0 unless layout.height > 0
|
27
|
+
ink_extents = layout.extents[1]
|
28
|
+
case valign.to_s.downcase
|
29
|
+
when 'middle'
|
30
|
+
Pango.pixels( (layout.height - ink_extents.height) / 2)
|
31
|
+
when 'bottom'
|
32
|
+
Pango.pixels(layout.height - ink_extents.height)
|
33
|
+
else
|
34
|
+
0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_font_rendering_opts!(layout)
|
39
|
+
font_options = Cairo::FontOptions.new
|
40
|
+
font_options.antialias = Conf::ANTIALIAS_OPTS[(@deck.antialias || 'gray').downcase]
|
41
|
+
font_options.hint_metrics = 'on' # TODO make this configurable
|
42
|
+
font_options.hint_style = 'full' # TODO make this configurable
|
43
|
+
layout.context.font_options = font_options
|
44
|
+
end
|
45
|
+
|
46
|
+
# :nodoc:
|
47
|
+
# @api private
|
48
|
+
def set_wh!(layout, width, height)
|
49
|
+
layout.width = width * Pango::SCALE unless width.nil? || width == :auto
|
50
|
+
layout.height = height * Pango::SCALE unless height.nil? || height == :auto
|
51
|
+
end
|
52
|
+
|
53
|
+
# :nodoc:
|
54
|
+
# @api private
|
55
|
+
def next_embed(keys, str)
|
56
|
+
ret = nil
|
57
|
+
ret_key = nil
|
58
|
+
keys.each do |key|
|
59
|
+
i = str.index(key)
|
60
|
+
ret ||= i
|
61
|
+
unless i.nil? || i > ret
|
62
|
+
ret = i
|
63
|
+
ret_key = key
|
64
|
+
end
|
65
|
+
end
|
66
|
+
ret_key
|
67
|
+
end
|
68
|
+
|
69
|
+
# When we do embedded icons, we create a 3-character string that has a font
|
70
|
+
# size of zero and a letter-spacing that fits the icon we need. That gives
|
71
|
+
# us the wrapping behavior we need but no clutter beneath. On most
|
72
|
+
# platforms, this works fine. On Linux, this creates
|
73
|
+
# a Cairo transformation matrix that
|
74
|
+
ZERO_WIDTH_CHAR_SIZE = 0 # this works on most platforms
|
75
|
+
# some platforms make this Pango pixels (1/1024), others a 1 pt font
|
76
|
+
ZERO_WIDTH_CHAR_SIZE = 1 if RbConfig::CONFIG['host_os'] === 'linux-gnu'
|
77
|
+
|
78
|
+
# :nodoc:
|
79
|
+
# @api private
|
80
|
+
def process_embeds(embed, str, layout)
|
81
|
+
return [] unless embed.rules.any?
|
82
|
+
layout.markup = str
|
83
|
+
clean_str = layout.text
|
84
|
+
draw_calls = []
|
85
|
+
searches = []
|
86
|
+
while (key = next_embed(embed.rules.keys, clean_str)) != nil
|
87
|
+
rule = embed.rules[key]
|
88
|
+
spacing = rule[:box].width[@index] * Pango::SCALE
|
89
|
+
kindex = clean_str.index(key)
|
90
|
+
kindex = clean_str[0..kindex].bytesize #convert to byte index (bug #57)
|
91
|
+
str = str.sub(key, "<span size=\"#{ZERO_WIDTH_CHAR_SIZE}\">a<span letter_spacing=\"#{spacing.to_i}\">a</span>a</span>")
|
92
|
+
layout.markup = str
|
93
|
+
clean_str = layout.text
|
94
|
+
searches << { index: kindex, rule: rule }
|
95
|
+
end
|
96
|
+
searches.each do |search|
|
97
|
+
rect = layout.index_to_pos(search[:index])
|
98
|
+
x = Pango.pixels(rect.x) + search[:rule][:adjust].dx[@index]
|
99
|
+
y = Pango.pixels(rect.y) + search[:rule][:adjust].dy[@index]
|
100
|
+
draw_calls << {x: x, y: y, draw: search[:rule][:draw]} # defer drawing until we've valigned
|
101
|
+
end
|
102
|
+
return draw_calls
|
103
|
+
end
|
104
|
+
|
105
|
+
def stroke_outline!(cc, layout, draw)
|
106
|
+
if draw.stroke_width > 0
|
107
|
+
cc.pango_layout_path(layout)
|
108
|
+
cc.fancy_stroke draw
|
109
|
+
cc.set_source_squibcolor(draw.color)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def warn_if_ellipsized(layout)
|
114
|
+
if @deck.conf.warn_ellipsize? && layout.ellipsized?
|
115
|
+
Squib.logger.warn { "Ellipsized (too much text). Card \##{@index}. Text: \"#{layout.text}\". \n (To disable this warning, set warn_ellipsize: false in config.yml)" }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# :nodoc:
|
120
|
+
# @api private
|
121
|
+
def text(embed, para, box, trans, draw)
|
122
|
+
Squib.logger.debug {"Rendering text with: \n#{para} \nat:\n #{box} \ndraw:\n #{draw} \ntransform: #{trans}"}
|
123
|
+
extents = nil
|
124
|
+
use_cairo do |cc|
|
125
|
+
cc.set_source_squibcolor(draw.color)
|
126
|
+
cc.translate(box.x, box.y)
|
127
|
+
cc.rotate(trans.angle)
|
128
|
+
cc.move_to(0, 0)
|
129
|
+
|
130
|
+
font_desc = Pango::FontDescription.new(para.font)
|
131
|
+
font_desc.size = para.font_size * Pango::SCALE unless para.font_size.nil?
|
132
|
+
layout = cc.create_pango_layout
|
133
|
+
layout.font_description = font_desc
|
134
|
+
layout.text = para.str
|
135
|
+
if para.markup
|
136
|
+
para.str = @deck.typographer.process(layout.text)
|
137
|
+
layout.markup = para.str
|
138
|
+
end
|
139
|
+
|
140
|
+
set_font_rendering_opts!(layout)
|
141
|
+
set_wh!(layout, box.width, box.height)
|
142
|
+
layout.wrap = para.wrap
|
143
|
+
layout.ellipsize = para.ellipsize
|
144
|
+
layout.alignment = para.align
|
145
|
+
|
146
|
+
layout.justify = para.justify unless para.justify.nil?
|
147
|
+
layout.spacing = para.spacing unless para.spacing.nil?
|
148
|
+
|
149
|
+
embed_draws = process_embeds(embed, para.str, layout)
|
150
|
+
|
151
|
+
vertical_start = compute_valign(layout, para.valign)
|
152
|
+
cc.move_to(0, vertical_start) #TODO clean this up a bit
|
153
|
+
|
154
|
+
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :stroke_first
|
155
|
+
cc.move_to(0, vertical_start)
|
156
|
+
cc.show_pango_layout(layout)
|
157
|
+
stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :fill_first
|
158
|
+
begin
|
159
|
+
embed_draws.each { |ed| ed[:draw].call(self, ed[:x], ed[:y] + vertical_start) }
|
160
|
+
rescue Exception => e
|
161
|
+
puts "====EXCEPTION!===="
|
162
|
+
puts e
|
163
|
+
puts "If this was a non-invertible matrix error, this is a known issue with a potential workaround. Please report it at: https://github.com/andymeneely/squib/issues/55"
|
164
|
+
puts "=================="
|
165
|
+
raise e
|
166
|
+
end
|
167
|
+
draw_text_hint(cc, box.x, box.y, layout, para.hint)
|
168
|
+
extents = { width: layout.extents[1].width / Pango::SCALE,
|
169
|
+
height: layout.extents[1].height / Pango::SCALE }
|
170
|
+
warn_if_ellipsized layout
|
171
|
+
end
|
172
|
+
return extents
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|