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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +14 -0
  3. data/.travis.yml +4 -0
  4. data/CHANGELOG.md +201 -175
  5. data/Gemfile +2 -4
  6. data/README.md +650 -645
  7. data/RELEASE TODO.md +18 -18
  8. data/Rakefile +106 -99
  9. data/appveyor.yml +29 -0
  10. data/lib/squib.rb +32 -32
  11. data/lib/squib/api/background.rb +20 -20
  12. data/lib/squib/api/data.rb +131 -131
  13. data/lib/squib/api/image.rb +108 -90
  14. data/lib/squib/api/save.rb +151 -149
  15. data/lib/squib/api/settings.rb +35 -35
  16. data/lib/squib/api/shapes.rb +255 -230
  17. data/lib/squib/api/text.rb +65 -65
  18. data/lib/squib/api/text_embed.rb +96 -96
  19. data/lib/squib/args/arg_loader.rb +138 -138
  20. data/lib/squib/args/box.rb +54 -54
  21. data/lib/squib/args/card_range.rb +32 -32
  22. data/lib/squib/args/color_validator.rb +11 -11
  23. data/lib/squib/args/coords.rb +32 -32
  24. data/lib/squib/args/dir_validator.rb +16 -16
  25. data/lib/squib/args/draw.rb +92 -92
  26. data/lib/squib/args/embed_adjust.rb +25 -25
  27. data/lib/squib/args/embed_key.rb +17 -17
  28. data/lib/squib/args/hand_special.rb +37 -37
  29. data/lib/squib/args/import.rb +39 -39
  30. data/lib/squib/args/input_file.rb +37 -37
  31. data/lib/squib/args/paint.rb +43 -43
  32. data/lib/squib/args/paragraph.rb +116 -115
  33. data/lib/squib/args/save_batch.rb +63 -60
  34. data/lib/squib/args/scale_box.rb +53 -53
  35. data/lib/squib/args/sheet.rb +72 -72
  36. data/lib/squib/args/showcase_special.rb +38 -38
  37. data/lib/squib/args/svg_special.rb +37 -37
  38. data/lib/squib/args/transform.rb +60 -24
  39. data/lib/squib/args/typographer.rb +117 -117
  40. data/lib/squib/card.rb +66 -67
  41. data/lib/squib/conf.rb +131 -117
  42. data/lib/squib/constants.rb +12 -178
  43. data/lib/squib/deck.rb +113 -113
  44. data/lib/squib/graphics/cairo_context_wrapper.rb +113 -99
  45. data/lib/squib/graphics/gradient_regex.rb +46 -46
  46. data/lib/squib/graphics/hand.rb +42 -42
  47. data/lib/squib/graphics/image.rb +103 -76
  48. data/lib/squib/graphics/save_doc.rb +103 -103
  49. data/lib/squib/graphics/save_images.rb +39 -33
  50. data/lib/squib/graphics/shapes.rb +135 -119
  51. data/lib/squib/graphics/showcase.rb +85 -85
  52. data/lib/squib/graphics/text.rb +176 -176
  53. data/lib/squib/layout_parser.rb +91 -91
  54. data/lib/squib/layouts/economy.yml +85 -85
  55. data/lib/squib/layouts/fantasy.yml +101 -101
  56. data/lib/squib/layouts/hand.yml +62 -62
  57. data/lib/squib/layouts/playing-card.yml +35 -35
  58. data/lib/squib/layouts/tuck_box.yml +45 -45
  59. data/lib/squib/project_template/IDEAS.md +22 -0
  60. data/lib/squib/project_template/PLAYTESTING.md +26 -0
  61. data/lib/squib/project_template/RULES.md +21 -0
  62. data/lib/squib/project_template/config.yml +49 -45
  63. data/lib/squib/sample_helpers.rb +34 -0
  64. data/lib/squib/version.rb +10 -10
  65. data/samples/autoscale_font/_autoscale_font.rb +29 -0
  66. data/samples/color_shortcuts.rb +6 -6
  67. data/samples/csv_import.rb +26 -26
  68. data/samples/custom-config.yml +5 -5
  69. data/samples/custom_config.rb +18 -18
  70. data/samples/draw_shapes.rb +48 -45
  71. data/samples/embed_text.rb +88 -88
  72. data/samples/excel.rb +55 -55
  73. data/samples/hand.rb +24 -24
  74. data/samples/images/_images.rb +104 -0
  75. data/samples/intro/01_hello.rb +9 -0
  76. data/samples/intro/02_options.rb +15 -0
  77. data/samples/intro/03_layout.rb +12 -0
  78. data/samples/intro/04_arrays.rb +16 -0
  79. data/samples/intro/05_excel.rb +15 -0
  80. data/samples/layouts.rb +62 -62
  81. data/samples/layouts_builtin.rb +51 -51
  82. data/samples/load_images.rb +99 -78
  83. data/samples/load_images_config.yml +1 -0
  84. data/samples/quantity_explosion.csv +2 -2
  85. data/samples/ranges.rb +64 -64
  86. data/samples/sample.csv +2 -2
  87. data/samples/saves.rb +9 -1
  88. data/samples/sprites.png +0 -0
  89. data/samples/text/_text.rb +46 -0
  90. data/samples/text_options.rb +102 -102
  91. data/spec/api/api_data_spec.rb +117 -117
  92. data/spec/api/api_settings_spec.rb +37 -37
  93. data/spec/args/box_spec.rb +127 -127
  94. data/spec/args/draw_spec.rb +101 -95
  95. data/spec/args/embed_key_spec.rb +13 -13
  96. data/spec/args/input_file_spec.rb +21 -21
  97. data/spec/args/paint_spec.rb +21 -21
  98. data/spec/args/paragraph_spec.rb +152 -152
  99. data/spec/args/range_spec.rb +40 -40
  100. data/spec/args/save_batch_spec.rb +51 -51
  101. data/spec/args/scale_box_spec.rb +71 -71
  102. data/spec/args/sheet_spec.rb +58 -58
  103. data/spec/args/showcase_special_spec.rb +15 -15
  104. data/spec/args/transform_spec.rb +25 -0
  105. data/spec/card_spec.rb +11 -0
  106. data/spec/conf_spec.rb +13 -3
  107. data/spec/data/conf/unrecognized.yml +4 -0
  108. data/spec/data/csv/qty.csv +2 -2
  109. data/spec/data/csv/qty_named.csv +2 -2
  110. data/spec/data/csv/with_spaces.csv +2 -2
  111. data/spec/data/samples/autoscale_font.rb.txt +84 -84
  112. data/spec/data/samples/basic.rb.txt +227 -209
  113. data/spec/data/samples/config_text_markup.rb.txt +72 -72
  114. data/spec/data/samples/csv_import.rb.txt +213 -213
  115. data/spec/data/samples/custom_config.rb.txt +57 -48
  116. data/spec/data/samples/draw_shapes.rb.txt +555 -3
  117. data/spec/data/samples/embed_text.rb.txt +283 -283
  118. data/spec/data/samples/excel.rb.txt +661 -661
  119. data/spec/data/samples/gradients.rb.txt +77 -79
  120. data/spec/data/samples/hand.rb.txt +538 -538
  121. data/spec/data/samples/hello_world.rb.txt +36 -36
  122. data/spec/data/samples/load_images.rb.txt +170 -0
  123. data/spec/data/samples/portrait-landscape.rb.txt +51 -49
  124. data/spec/data/samples/ranges.rb.txt +472 -460
  125. data/spec/data/samples/saves.rb.txt +810 -801
  126. data/spec/data/samples/showcase.rb.txt +5926 -5910
  127. data/spec/data/samples/text_options.rb.txt +1125 -1125
  128. data/spec/data/samples/tgc_proofs.rb.txt +95 -81
  129. data/spec/graphics/cairo_context_wrapper_spec.rb +104 -84
  130. data/spec/graphics/graphics_save_doc_spec.rb +67 -67
  131. data/spec/samples/diff-with-css.example.html +39 -0
  132. data/spec/samples/expected/load_images_00.png +0 -0
  133. data/spec/samples/expected/shape_00.png +0 -0
  134. data/spec/samples/run_samples_spec.rb +17 -0
  135. data/spec/samples/samples_regression_spec.rb +72 -82
  136. data/spec/spec_helper.rb +9 -1
  137. data/squib.gemspec +49 -48
  138. data/squib.sublime-project +42 -42
  139. metadata +94 -48
  140. 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
@@ -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