squib 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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