squib 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 54d41c6aefc288c69ad7d481ffa6e5b7b26e2658
4
- data.tar.gz: fd8a41e64858c0b9b9a6e924dd64ee5ab52b46fc
3
+ metadata.gz: 7c3a21f564c6f39f4cdd9ac84826e2dd76df1c9e
4
+ data.tar.gz: dd923de21db270d9e71b5a06752a0a55390feffd
5
5
  SHA512:
6
- metadata.gz: 30dcd2caaa2ec623d456184a81729aea87765f81d40c726524e2117942bf316165a0df920479faa425462501296a1b984d64ab9b03b10a04bebad4815e0dbc43
7
- data.tar.gz: 77d4d1950264389af9396aa895863fc1ba0a4ba1617d4889e0aa1ce982e05d16cd7951ab6488dcf86e05ea7997edd9ab6cc6b2b80ca27be728c848a1a94f6791
6
+ metadata.gz: 0eab4d96556c539261b0ca346d56af96331d968a3a828826d1172315821ffe3364a5070cf868a4b837775b70ca902e51fd2f9bc194069f10bb99d1675a005549
7
+ data.tar.gz: c654ed7a07aa5958861b4033066fd64b1cf2e3a114a1e372fca0b58c8b8b40ec6a454fc461e5ba7d5eb60a871e677564bed71466f31f59245e5fec5062aaeca9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Squib CHANGELOG
2
2
 
3
+ ## v0.2.0
4
+ * Added `showcase` feature to create a fancy-looking 3D reflection to showcase your cards. Documented, tested, and added a sample for it.
5
+ * Added a basic Rakefile, documented in README.
6
+ * Some internal refactoring, better testing, and more documentation with layouts
7
+
3
8
  ## v0.1.0
4
9
  * Added `save_sheet` command that saves a range into PNG sheets, complete with trim, gap, margin, columns, and sometimes automagically computed rows. See samples/saves.rb.
5
10
  * Unit conversion! Now you can write "2in" and it will convert based on the current dpi. `save_pdf` not supported (yet).
data/README.md CHANGED
@@ -76,6 +76,7 @@ About the other files:
76
76
  * `_output` is the directory where your built files will go. Can easily be changed, of course.
77
77
  * `.gitignore` and `gitkeep.txt` are for if you are using Git. See {file:README.md#Source_control Source control}. (Feel free to remove these if you are not using Git.)
78
78
  * `ABOUT.md` and `PHP NOTES.md` are Markdown files for posting. Not used by Squib, but there by convention.
79
+ * `Rakefile` is a basic build file. Not required but handy - see {file:README.md#Rakefile Rakefile}
79
80
 
80
81
  # Learning Squib
81
82
 
@@ -208,6 +209,20 @@ yang:
208
209
  x: += 50
209
210
  ```
210
211
 
212
+ Furthermore, if you want to extend multiple parents, it looks like this:
213
+
214
+ ```yaml
215
+ socrates:
216
+ x: 100
217
+ aristotle:
218
+ y: 200
219
+ aristotle:
220
+ extends:
221
+ - socrates
222
+ - plato
223
+ x: += 50
224
+ ```
225
+
211
226
  ### Multiple layout files
212
227
 
213
228
  Squib also supports the combination of multiple layout files. As shown in the above example, if you provide an `Array` of files then Squib will merge them sequentially. Colliding keys will be completely re-defined by the later file. Extends is processed after _each file_. YAML merge keys are NOT supported across multiple files - use extends instead. Here's a demonstrative example:
@@ -309,6 +324,14 @@ If you want to make a deck that has some portrait and some landscape cards, I re
309
324
 
310
325
  {include:file:samples/portrait-landscape.rb}
311
326
 
327
+ ## Rakefile
328
+
329
+ When you run `squib new`, you are given a basic Rakefile. At this stage of Squib, it's basically just a shortcut for `ruby deck.rb`. But, even in this simple form this Rakefile has some advantages:
330
+
331
+ * If you're in a subdirectory at the time, `rake` will simply traverse up and `cd` to the proper directory so you don't get rogue `_output` directories
332
+ * If you find yourself building multiple decks, you can make your own tasks for each one individually, or all (e.g. `rake marketing`)
333
+ * Don't need the `require squib` at the top of your code (although that breaks `ruby deck.rb`, so it's probably a bad idea)
334
+
312
335
  # Development
313
336
 
314
337
  Squib is currently in pre-release alpha, so the API is still maturing. I do change my mind about the names and meaning of things at this stage. If you are using Squib, however, I'd love to hear about it! Feel free to [file a bug or feature request](https://github.com/andymeneely/squib/issues).
data/RELEASE TODO.md CHANGED
@@ -3,11 +3,14 @@ Be sure to remember to do the following for releases. (Copy into a GitHub issue)
3
3
  - [ ] CHANGELOG is written for all new changes
4
4
  - [ ] README is updated
5
5
  - [ ] Samples are updated
6
+ - [ ] `rake doc`
6
7
  - [ ] Check that sample regression tests are all enabled
7
8
  - [ ] Bump version.rb
8
9
  - [ ] Do a full rake locally
9
10
  - [ ] Travis is passing on dev branch
11
+ - [ ] Merge master branch
12
+ - [ ] Create GitHub release tag
10
13
  - [ ] `gem push pkg/squib-x.y.z.gem`
11
- - [ ] `rake doc`
14
+ - [ ] Github milestone closed
12
15
  - [ ] Push `rake doc` to website
13
16
  - [ ] Bump version.rb to the next alpha
data/Rakefile CHANGED
@@ -2,9 +2,10 @@ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
  require 'yard'
4
4
 
5
- RSpec::Core::RakeTask.new(:spec)
6
5
  task default: [:install, :spec]
7
6
 
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
8
9
  YARD::Rake::YardocTask.new(:doc) do |t|
9
10
  t.files = ['lib/**/*.rb', 'samples/**/*.rb'] # optional
10
11
  #t.options = ['--any', '--extra', '--opts'] # optional
data/lib/squib.rb CHANGED
@@ -19,7 +19,7 @@ module Squib
19
19
  # @api public
20
20
  def logger
21
21
  if @logger.nil?
22
- @logger = Logger.new(STDOUT);
22
+ @logger = Logger.new($stdout);
23
23
  @logger.level = Logger::WARN;
24
24
  @logger.formatter = proc do |severity, datetime, m_progname, msg|
25
25
  "#{datetime} #{severity}: #{msg}\n"
@@ -38,5 +38,36 @@ module Squib
38
38
  end
39
39
  end
40
40
 
41
+ # Renders a range of files in a showcase as if they are sitting on a reflective surface
42
+ # See {file:samples/showcase.rb} for full example
43
+ #
44
+ # @example
45
+ # showcase file: 'showcase_output.png', trim: 78, trim_radius: 32
46
+ #
47
+ # @option opts [Enumerable, :all] range (:all) the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges}
48
+ # @option opts [Fixnum] trim (0) the margin around the card to trim before putting into the showcase
49
+ # @option opts [Fixnum] trim_radius (38) the rounded rectangle radius around the card to trim before putting into the showcase
50
+ # @option opts [Fixnum] margin (75) the margin around the entire showcase
51
+ # @option opts [Fixnum] scale (0.8) percentage of original width of each (trimmed) card to scale to. Must be between 0.0 and 1.0, but starts looking bad around 0.6.
52
+ # @option opts [Fixnum] offset (1.1) percentage of the scaled width of each card to shift each offset. e.g. 1.1 is a 10% shift, and 0.95 is overlapping by 5%
53
+ # @option opts [String, Color] fill_color (:white) backdrop color. Usually black or white.
54
+ # @option opts [Fixnum] reflect_offset (15) the number of pixels between the bottom of the card and the reflection
55
+ # @option opts [Fixnum] reflect_strength (0.2) the starting alpha transparency of the reflection (at the top of the card). Percentage between 0 and 1. Looks more realistic at low values since even shiny surfaces lose a lot of light.
56
+ # @option opts [Fixnum] reflect_percent (0.25) the length of the reflection in percentage of the card. Larger values tend to make the reflection draw just as much attention as the card, which is not good.
57
+ # @option opts [:left, :right] face (:left) which direction the cards face. Anything but `:right` will face left
58
+ # @option opts [String] dir (_output) the directory for the output to be sent to. Will be created if it doesn't exist.
59
+ # @option opts [String] file ('showcase.png') the file to save in dir. Will be overwritten.
60
+ # @return [nil] Returns nothing.
61
+ # @api public
62
+ def showcase(opts = {})
63
+ opts = {file: 'showcase.png', fill_color: :white}.merge(opts)
64
+ opts = needs(opts,[:range, :trim, :trim_radius, :creatable_dir, :file_to_save, :face])
65
+ render_showcase(opts[:range], opts[:trim], opts[:trim_radius],
66
+ opts[:scale], opts[:offset], opts[:fill_color],
67
+ opts[:reflect_offset], opts[:reflect_percent], opts[:reflect_strength],
68
+ opts[:margin], opts[:face],
69
+ opts[:dir], opts[:file])
70
+ end
71
+
41
72
  end
42
73
  end
data/lib/squib/card.rb CHANGED
@@ -4,42 +4,43 @@ require 'squib/input_helpers'
4
4
  module Squib
5
5
  # Back end graphics. Private.
6
6
  class Card
7
- include Squib::InputHelpers
7
+ include Squib::InputHelpers
8
8
 
9
- # :nodoc:
10
- # @api private
11
- attr_reader :width, :height
9
+ # :nodoc:
10
+ # @api private
11
+ attr_reader :width, :height
12
12
 
13
- # :nodoc:
14
- # @api private
15
- attr_accessor :cairo_surface, :cairo_context
13
+ # :nodoc:
14
+ # @api private
15
+ attr_accessor :cairo_surface, :cairo_context
16
16
 
17
- # :nodoc:
18
- # @api private
19
- def initialize(deck, width, height)
20
- @deck=deck; @width=width; @height=height
21
- @cairo_surface = Cairo::ImageSurface.new(width,height)
22
- @cairo_context = Cairo::Context.new(@cairo_surface)
23
- end
17
+ # :nodoc:
18
+ # @api private
19
+ def initialize(deck, width, height)
20
+ @deck=deck; @width=width; @height=height
21
+ @cairo_surface = Cairo::ImageSurface.new(width,height)
22
+ @cairo_context = Cairo::Context.new(@cairo_surface)
23
+ end
24
24
 
25
25
  # A save/restore wrapper for using Cairo
26
26
  # :nodoc:
27
27
  # @api private
28
- def use_cairo(&block)
29
- @cairo_context.save
30
- block.yield(@cairo_context)
31
- @cairo_context.restore
32
- end
28
+ def use_cairo(&block)
29
+ @cairo_context.save
30
+ block.yield(@cairo_context)
31
+ @cairo_context.restore
32
+ end
33
33
 
34
- ########################
35
- ### BACKEND GRAPHICS ###
36
- ########################
37
- require 'squib/graphics/background'
38
- require 'squib/graphics/image'
39
- require 'squib/graphics/save_doc'
40
- require 'squib/graphics/save_images'
41
- require 'squib/graphics/shapes'
42
- require 'squib/graphics/text'
34
+ ########################
35
+ ### BACKEND GRAPHICS ###
36
+ ########################
37
+ require 'squib/graphics/background'
38
+ require 'squib/graphics/image'
39
+ require 'squib/graphics/save_doc'
40
+ require 'squib/graphics/save_images'
41
+ require 'squib/graphics/shapes'
42
+ require 'squib/graphics/showcase'
43
+ require 'squib/graphics/text'
43
44
 
44
45
  end
45
46
  end
@@ -12,6 +12,7 @@ module Squib
12
12
  :default_font => 'Arial 36',
13
13
  :dir => '_output',
14
14
  :ellipsize => :end,
15
+ :face => :left,
15
16
  :fill_color => '#0000',
16
17
  :force_id => false,
17
18
  :font => :use_set,
@@ -24,17 +25,23 @@ module Squib
24
25
  :justify => false,
25
26
  :margin => 75,
26
27
  :markup => false,
28
+ :offset => 1.1,
27
29
  :prefix => 'card_',
28
30
  :progress_bar => false,
31
+ :reflect_offset => 15,
32
+ :reflect_percent => 0.25,
33
+ :reflect_strength => 0.2,
29
34
  :range => :all,
30
35
  :rotate => false,
31
36
  :rows => :infinite,
37
+ :scale => 0.85,
32
38
  :sheet => 0,
33
39
  :spacing => 0,
34
40
  :str => '',
35
41
  :stroke_color => :black,
36
42
  :stroke_width => 2.0,
37
43
  :trim => 0,
44
+ :trim_radius => 38,
38
45
  :valign => :top,
39
46
  :width => :native,
40
47
  :wrap => true,
data/lib/squib/deck.rb CHANGED
@@ -5,6 +5,7 @@ require 'squib/card'
5
5
  require 'squib/progress'
6
6
  require 'squib/input_helpers'
7
7
  require 'squib/constants'
8
+ require 'squib/layout_parser'
8
9
 
9
10
  # The project module
10
11
  #
@@ -65,7 +66,7 @@ module Squib
65
66
  cards.times{ @cards << Squib::Card.new(self, width, height) }
66
67
  show_info(config, layout)
67
68
  load_config(config)
68
- load_layout(layout)
69
+ @layout = LayoutParser.load_layout(layout)
69
70
  if block_given?
70
71
  instance_eval(&block)
71
72
  end
@@ -85,13 +86,6 @@ module Squib
85
86
  @cards.each { |card| block.call(card) }
86
87
  end
87
88
 
88
- # Shows a descriptive place of the location
89
- #
90
- # @api private
91
- def location(opts)
92
- opts[:layout] || (" @ #{opts[:x]},#{opts[:y]}")
93
- end
94
-
95
89
  # Load the configuration file, if exists, overriding hardcoded defaults
96
90
  # @api private
97
91
  def load_config(file)
@@ -106,69 +100,6 @@ module Squib
106
100
  end
107
101
  end
108
102
 
109
- # Load the layout configuration file, if exists
110
- # @api private
111
- def load_layout(files)
112
- @layout = {}
113
- Squib::logger.info { " using layout(s): #{files}" }
114
- Array(files).each do |file|
115
- thefile = file
116
- thefile = "#{File.dirname(__FILE__)}/layouts/#{file}" unless File.exists?(file)
117
- if File.exists? thefile
118
- yml = @layout.merge(YAML.load_file(thefile) || {}) #load_file returns false on empty file
119
- yml.each do |key, value|
120
- @layout[key] = recurse_extends(yml, key, {})
121
- end
122
- else
123
- puts "the file: #{thefile}"
124
- Squib::logger.error { "Layout file not found: #{file}" }
125
- end
126
- end
127
- end
128
-
129
- # Process the extends recursively
130
- # :nodoc:
131
- # @api private
132
- def recurse_extends(yml, key, visited )
133
- assert_not_visited(key, visited)
134
- return yml[key] unless has_extends?(yml, key)
135
- visited[key] = key
136
- parent_keys = [yml[key]['extends']].flatten
137
- h = {}
138
- parent_keys.each do |parent_key|
139
- from_extends = yml[key].merge(recurse_extends(yml, parent_key, visited)) do |key, child_val, parent_val|
140
- if child_val.to_s.strip.start_with?('+=')
141
- parent_val + child_val.sub('+=','').strip.to_f
142
- elsif child_val.to_s.strip.start_with?('-=')
143
- parent_val - child_val.sub('-=','').strip.to_f
144
- else
145
- child_val #child overrides parent when merging, no +=
146
- end
147
- end
148
- h = h.merge(from_extends) do |key, older_sibling, younger_sibling|
149
- younger_sibling #when two siblings have the same entry, the "younger" (lower one) overrides
150
- end
151
- end
152
- return h
153
- end
154
-
155
- # Does this layout entry have an extends field?
156
- # i.e. is it a base-case or will it need recursion?
157
- # :nodoc:
158
- # @api private
159
- def has_extends?(yml, key)
160
- !!yml[key] && yml[key].key?('extends')
161
- end
162
-
163
- # Safeguard against malformed circular extends
164
- # :nodoc:
165
- # @api private
166
- def assert_not_visited(key, visited)
167
- if visited.key? key
168
- raise "Invalid layout: circular extends with '#{key}'"
169
- end
170
- end
171
-
172
103
  # Use Logger to show more detail on the run
173
104
  # :nodoc:
174
105
  # @api private
@@ -0,0 +1,83 @@
1
+ module Squib
2
+ class Deck
3
+
4
+ # So the Cairo people have said over and over again that they won't support the 3x3 matrices that would handle perspective transforms.
5
+ # Since our perspective transform needs are a bit simpler, we can use a "striping method" that does the job for us.
6
+ # It's a little bit involved, but it works well enough for limited ranges of our parameters.
7
+ # These were also helpful:
8
+ # http://kapo-cpp.blogspot.com/2008/01/perspective-effect-using-cairo.html
9
+ # http://zetcode.com/gui/pygtk/drawingII/
10
+ # :nodoc:
11
+ # @api private
12
+ def render_showcase(range,
13
+ trim, trim_radius, scale, offset, fill_color,
14
+ reflect_offset, reflect_percent, reflect_strength, margin, face_right,
15
+ dir, file_to_save)
16
+ out_width = range.size * ((@width - 2*trim) * scale * offset) + 2*margin
17
+ out_height = reflect_offset + (1.0 + reflect_percent) * (@height - 2*trim) + 2*margin
18
+ out_cc = Cairo::Context.new(Cairo::ImageSurface.new(out_width, out_height))
19
+ out_cc.set_source_color(fill_color)
20
+ out_cc.paint
21
+
22
+ cards = range.collect { |i| @cards[i] }
23
+ cards.each_with_index do |card, i|
24
+ trimmed = trim_rounded(card.cairo_surface, trim, trim_radius)
25
+ reflected = reflect(trimmed, reflect_offset, reflect_percent, reflect_strength)
26
+ perspectived = perspective(reflected, scale, face_right)
27
+ out_cc.set_source(perspectived, margin + i * perspectived.width * offset, margin)
28
+ out_cc.paint
29
+ end
30
+ out_cc.target.write_to_png("#{dir}/#{file_to_save}")
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
+ def perspective(src, scale, face_right)
64
+ dest_cxt = Cairo::Context.new(Cairo::ImageSurface.new(src.width * scale, src.height))
65
+ in_thickness = 1 # Take strip 1 pixel-width at a time
66
+ out_thickness = 3 # Scale it to 3 pixels wider to cover any gaps
67
+ (0..src.width).step(in_thickness) do |i|
68
+ percentage = i / src.width.to_f
69
+ i = src.width - i if face_right
70
+ factor = scale + (percentage * (1.0 - scale)) #linear interpolation
71
+ dest_cxt.save
72
+ dest_cxt.translate 0, src.height / 2.0 * (1.0 - factor)
73
+ dest_cxt.scale factor * scale, factor
74
+ dest_cxt.set_source src, 0, 0
75
+ dest_cxt.rounded_rectangle i, 0, out_thickness, src.height, 0,0
76
+ dest_cxt.fill
77
+ dest_cxt.restore
78
+ end
79
+ return dest_cxt.target
80
+ end
81
+
82
+ end
83
+ end
@@ -28,6 +28,7 @@ module Squib
28
28
  opts = formatify(opts) if params.include? :formats
29
29
  opts = rotateify(opts) if params.include? :rotate
30
30
  opts = rowify(opts) if params.include? :rows
31
+ opts = faceify(opts) if params.include? :face
31
32
  opts = convert_units(opts, params)
32
33
  opts
33
34
  end
@@ -225,5 +226,13 @@ module Squib
225
226
  opts
226
227
  end
227
228
 
229
+ # Used for showcase - face right if it's :right
230
+ # :nodoc:
231
+ # @api private
232
+ def faceify(opts)
233
+ opts[:face] = (opts[:face].to_s.downcase == 'right')
234
+ opts
235
+ end
236
+
228
237
  end
229
238
  end