bread-basket 0.0.0 → 0.0.1
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/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +4 -1
- data/Gemfile +1 -1
- data/Guardfile +7 -2
- data/README.md +28 -17
- data/Rakefile +13 -6
- data/bin/bread-basket +14 -0
- data/bread-basket.gemspec +29 -24
- data/lib/bread/basket.rb +12 -6
- data/lib/bread/basket/cli.rb +10 -0
- data/lib/bread/basket/poster.rb +38 -0
- data/lib/bread/basket/poster/block_code_handler.rb +50 -0
- data/lib/bread/basket/poster/block_renderer.rb +14 -0
- data/lib/bread/basket/poster/box.rb +119 -0
- data/lib/bread/basket/poster/box_checker.rb +60 -0
- data/lib/bread/basket/poster/columns.rb +90 -0
- data/lib/bread/basket/poster/css_reader.rb +130 -0
- data/lib/bread/basket/poster/dimensions_helper.rb +123 -0
- data/lib/bread/basket/poster/header_callback.rb +29 -0
- data/lib/bread/basket/poster/header_maker.rb +74 -0
- data/lib/bread/basket/poster/image_box.rb +93 -0
- data/lib/bread/basket/poster/layout.rb +108 -0
- data/lib/bread/basket/poster/pdf_builder.rb +113 -0
- data/lib/bread/basket/poster/poster_maker.rb +55 -0
- data/lib/bread/basket/poster/prawn_patches/column_box.rb +17 -0
- data/lib/bread/basket/poster/text_renderer.rb +89 -0
- data/lib/bread/basket/poster/units_helper.rb +39 -0
- data/lib/bread/basket/version.rb +1 -1
- data/samples/block_sample.css +86 -0
- data/samples/flow_sample.css +68 -0
- data/samples/ipsum.jpg +0 -0
- data/samples/lorem.jpg +0 -0
- data/samples/lorem_block.md +86 -0
- data/samples/lorem_flow.md +58 -0
- data/samples/lorem_flow.pdf +3834 -6
- data/samples/sample.md +59 -0
- data/samples/simple.css +19 -0
- data/samples/simple.md +3 -0
- data/samples/ucair_logo.png +0 -0
- data/spec/cli_spec.rb +13 -0
- data/spec/poster/block_code_handler_spec.rb +47 -0
- data/spec/poster/box_spec.rb +114 -0
- data/spec/poster/columns_spec.rb +64 -0
- data/spec/poster/css_reader_spec.rb +115 -0
- data/spec/poster/header_maker_spec.rb +49 -0
- data/spec/poster/image_box_spec.rb +69 -0
- data/spec/poster/layout_spec.rb +75 -0
- data/spec/poster/pdf_builder_spec.rb +60 -0
- data/spec/poster/poster_maker_spec.rb +15 -0
- data/spec/poster/test_files/bad_file.md +1 -0
- data/spec/poster/test_files/basic_block.css +14 -0
- data/spec/poster/test_files/basic_flow.css +31 -0
- data/spec/poster/test_files/block_code_test.css +13 -0
- data/spec/poster/test_files/builder.css +39 -0
- data/spec/poster/test_files/circular.css +39 -0
- data/spec/poster/test_files/dragon.png +0 -0
- data/spec/poster/test_files/fitted_image.css +39 -0
- data/spec/poster/test_files/good_file.md +5 -0
- data/spec/poster/test_files/good_file.pdf +0 -0
- data/spec/poster/test_files/header_test.css +22 -0
- data/spec/poster/test_files/nearly_empty.css +10 -0
- data/spec/poster/test_files/self_referential.css +108 -0
- data/spec/poster/test_files/ucair_logo.png +0 -0
- data/spec/poster/text_renderer_spec.rb +54 -0
- data/spec/poster/units_helper_spec.rb +36 -0
- data/spec/spec_helper.rb +6 -0
- metadata +212 -34
- data/bread-basket-0.0.1.gem +0 -0
- data/spec/basket_spec.rb +0 -7
@@ -0,0 +1,60 @@
|
|
1
|
+
module Bread
|
2
|
+
module Basket
|
3
|
+
module Poster
|
4
|
+
class BoxChecker
|
5
|
+
# Determines which dimensions were provided and if provided dimensions
|
6
|
+
# are sufficient to determine left, right, top, and width of box
|
7
|
+
# bottom and height are optional but if top is missing are required to
|
8
|
+
# determine location of top.
|
9
|
+
attr_reader :box, :dimensions
|
10
|
+
|
11
|
+
def initialize(box, dimensions)
|
12
|
+
@box = box
|
13
|
+
@dimensions = dimensions
|
14
|
+
end
|
15
|
+
|
16
|
+
def horizontal_dimensions
|
17
|
+
arr = []
|
18
|
+
dimensions.each_key do |key|
|
19
|
+
arr << key if %w(left right width).include? key
|
20
|
+
end
|
21
|
+
warn_right if arr.length > 2
|
22
|
+
arr
|
23
|
+
end
|
24
|
+
|
25
|
+
def vertical_dimensions
|
26
|
+
arr = []
|
27
|
+
dimensions.each_key do |key|
|
28
|
+
arr << key if %w(top bottom height).include? key
|
29
|
+
end
|
30
|
+
warn_bottom if arr.length > 2
|
31
|
+
arr
|
32
|
+
end
|
33
|
+
|
34
|
+
def horizontal_ok?
|
35
|
+
horiz = %w(left right width) - horizontal_dimensions
|
36
|
+
horiz.length < 2
|
37
|
+
end
|
38
|
+
|
39
|
+
def vertical_ok?
|
40
|
+
bottom_and_height = %w(bottom height).all? { |s| dimensions.key? s }
|
41
|
+
dimensions.key?('top') || bottom_and_height
|
42
|
+
end
|
43
|
+
|
44
|
+
def box_ok?
|
45
|
+
horizontal_ok? && vertical_ok?
|
46
|
+
end
|
47
|
+
|
48
|
+
def warn_right
|
49
|
+
puts "Warning: For selector #{box.selector_name}, left, width AND right were " \
|
50
|
+
'provided. Right will be ignored.'
|
51
|
+
end
|
52
|
+
|
53
|
+
def warn_bottom
|
54
|
+
puts "Warning: For selector #{box.selector_name}, top, height AND bottom were " \
|
55
|
+
'provided. Bottom will be ignored.'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Bread
|
2
|
+
module Basket
|
3
|
+
module Poster
|
4
|
+
class Columns
|
5
|
+
# Columns are a special type of bounding box in prawn so they get their
|
6
|
+
# own class here to handle their dimensions.
|
7
|
+
attr_reader :specs, :count, :layout, :tops, :width, :lefts, :boxes, :spacing
|
8
|
+
|
9
|
+
def initialize(specs, layout)
|
10
|
+
@specs = specs
|
11
|
+
@layout = layout
|
12
|
+
check_specs
|
13
|
+
@count = specs['count'].to_i
|
14
|
+
@spacing = specs['font-size'] || layout.font_size
|
15
|
+
init_tops
|
16
|
+
init_widths
|
17
|
+
init_lefts
|
18
|
+
create_boxes
|
19
|
+
end
|
20
|
+
|
21
|
+
def init_tops
|
22
|
+
if specs['top'].is_a? Array
|
23
|
+
@tops = tops_from_arr
|
24
|
+
else
|
25
|
+
@tops = Array.new(count, specs['top'])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def tops_from_arr
|
30
|
+
case specs['top'].length
|
31
|
+
when (count / 2.0).ceil
|
32
|
+
symmetric_tops!
|
33
|
+
when count
|
34
|
+
specs['top']
|
35
|
+
else
|
36
|
+
message = 'Columns top specification has wrong length'
|
37
|
+
layout.give_up(message)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def symmetric_tops!
|
42
|
+
arr = specs['top'].clone
|
43
|
+
specs['top'].pop if count.odd?
|
44
|
+
specs['top'].reverse!
|
45
|
+
arr + specs['top']
|
46
|
+
end
|
47
|
+
|
48
|
+
def init_widths
|
49
|
+
col_spacing = spacing * (count - 1) # from Prawn
|
50
|
+
@width = (columns_width - col_spacing) / count.to_f
|
51
|
+
end
|
52
|
+
|
53
|
+
def columns_width
|
54
|
+
total_margin_space = 2 * layout.margin
|
55
|
+
layout.width - total_margin_space
|
56
|
+
end
|
57
|
+
|
58
|
+
def init_lefts
|
59
|
+
column_width = width + spacing # again from Prawn
|
60
|
+
@lefts = count.times.inject([layout.margin]) do |a|
|
61
|
+
a << a[-1] + column_width
|
62
|
+
end
|
63
|
+
@lefts.pop
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_boxes
|
67
|
+
@boxes = []
|
68
|
+
count.times do |index|
|
69
|
+
box = Box.new "columns[#{index}]",
|
70
|
+
layout,
|
71
|
+
'top' => tops[index],
|
72
|
+
'left' => lefts[index],
|
73
|
+
'width' => width,
|
74
|
+
'bottom' => layout.margin
|
75
|
+
@boxes << box
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def check_specs
|
80
|
+
message = '.columns selector must include top and count to be valid'
|
81
|
+
layout.give_up(message) unless top_and_count?
|
82
|
+
end
|
83
|
+
|
84
|
+
def top_and_count?
|
85
|
+
%w(top count).all? { |attr| specs.key? attr }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Bread
|
2
|
+
module Basket
|
3
|
+
module Poster
|
4
|
+
class CSSReader
|
5
|
+
# The CSSReader reads in the css and then creates:
|
6
|
+
# - layout
|
7
|
+
# - columns (if flow)
|
8
|
+
# - boxes (Box or ImageBox)
|
9
|
+
# and then attaches #-styles to the layout help
|
10
|
+
include CssParser
|
11
|
+
include UnitsHelper
|
12
|
+
attr_reader :parser, :layout
|
13
|
+
|
14
|
+
COLUMNS_FAIL = 'To use a flow layout your stylesheet must include ' \
|
15
|
+
'.columns selector.'
|
16
|
+
LAYOUT_FAIL = 'Your stylesheet must include the .layout selector.'
|
17
|
+
|
18
|
+
def initialize(stylesheet_path, layout)
|
19
|
+
@layout = layout
|
20
|
+
@parser = CssParser::Parser.new
|
21
|
+
@parser.load_file!(stylesheet_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_your_thing!
|
25
|
+
init_layout
|
26
|
+
create_columns if layout.flow?
|
27
|
+
create_bounding_boxes
|
28
|
+
create_styles
|
29
|
+
end
|
30
|
+
|
31
|
+
def init_layout
|
32
|
+
layout_rules = parser.find_by_selector '.layout'
|
33
|
+
layout.give_up LAYOUT_FAIL if layout_rules.empty?
|
34
|
+
specs = rules_to_specs(layout_rules[0])
|
35
|
+
create_layout_attributes(specs)
|
36
|
+
layout.handle_defaults
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_layout_attributes(specs)
|
40
|
+
attributes_from_specs(specs)
|
41
|
+
finish_layout_box
|
42
|
+
end
|
43
|
+
|
44
|
+
def attributes_from_specs(specs)
|
45
|
+
layout.width = specs.delete 'width'
|
46
|
+
layout.height = specs.delete 'height'
|
47
|
+
layout.margin = specs.delete 'margin'
|
48
|
+
specs.each do |k, v|
|
49
|
+
layout.create_attribute(k, v)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def finish_layout_box
|
54
|
+
layout.left = 0
|
55
|
+
layout.right = layout.width
|
56
|
+
layout.bottom = 0
|
57
|
+
layout.top = layout.height
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_columns
|
61
|
+
columns = parser.find_by_selector '.columns'
|
62
|
+
layout.give_up COLUMNS_FAIL if columns.empty?
|
63
|
+
specs = rules_to_specs(columns[0])
|
64
|
+
columns = Columns.new specs, layout
|
65
|
+
layout.create_attribute('columns', columns.boxes)
|
66
|
+
layout.create_attribute('column_styles', specs)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_bounding_boxes
|
70
|
+
parser.each_selector do |selector, declarations|
|
71
|
+
next if skip_selector? selector
|
72
|
+
method_name = to_method_name selector
|
73
|
+
specs = rules_to_specs declarations
|
74
|
+
box = create_box selector, layout, specs
|
75
|
+
layout.create_attribute(method_name, box)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def skip_selector?(selector)
|
80
|
+
old_selector = %w(.layout .columns).include? selector
|
81
|
+
hash_selector = selector =~ HASH_SELECTOR_REGEX
|
82
|
+
old_selector || hash_selector
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_box(selector, layout, specs)
|
86
|
+
if specs.key?('src')
|
87
|
+
ImageBox.new selector, layout, specs
|
88
|
+
else
|
89
|
+
Box.new selector, layout, specs
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_styles
|
94
|
+
parser.each_selector do |selector, declarations|
|
95
|
+
next unless selector =~ HASH_SELECTOR_REGEX
|
96
|
+
method_name = to_method_name selector
|
97
|
+
specs = rules_to_specs declarations
|
98
|
+
layout.create_attribute(method_name, specs)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def rules_to_specs(css_rules)
|
103
|
+
# rule => "top: title.bottom, authors.bottom"
|
104
|
+
# spec => { top: ['title.bottom', 'authors.bottom'] }
|
105
|
+
rules_arr = css_rules.split(';')
|
106
|
+
hash = {}
|
107
|
+
|
108
|
+
rules_arr.each { |rule| rule_to_hash(rule, hash) }
|
109
|
+
|
110
|
+
hash.each_with_object({}) do |(k, v), h|
|
111
|
+
arr = v.map { |value| convert_units(value) }
|
112
|
+
h[k] = arr.length == 1 ? arr[0] : arr
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def rule_to_hash(rule, hash)
|
117
|
+
rule_arr = rule.split(':')
|
118
|
+
key = rule_arr[0].strip
|
119
|
+
value_arr = rule_arr[1].strip.split(',')
|
120
|
+
value_arr.map!(&:strip)
|
121
|
+
hash[key] = value_arr
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_method_name(hash_selector)
|
125
|
+
hash_selector.sub('#', '').sub('.', '').gsub('-', '_')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Bread
|
2
|
+
module Basket
|
3
|
+
module Poster
|
4
|
+
class DimensionsHelper
|
5
|
+
# Dimensions Helper checks the dimensions and determines how to handle
|
6
|
+
# them (pending vs numeric)
|
7
|
+
include UnitsHelper
|
8
|
+
attr_reader :box, :layout, :dimensions, :checker
|
9
|
+
|
10
|
+
def initialize(box, layout, dimensions)
|
11
|
+
@layout = layout
|
12
|
+
@box = box
|
13
|
+
@dimensions = dimensions
|
14
|
+
@checker = BoxChecker.new box, dimensions
|
15
|
+
fail_dimensions unless checker.box_ok?
|
16
|
+
determine_dimensions
|
17
|
+
box.try_to_resolve
|
18
|
+
frame_box
|
19
|
+
end
|
20
|
+
|
21
|
+
def fail_dimensions
|
22
|
+
message = "For the selector #{box.selector_name}, you failed to " \
|
23
|
+
'provide enough dimensions to draw a box. You need two of these three: ' \
|
24
|
+
'left, right, width; and either `top` or `bottom and height`. Instead you ' \
|
25
|
+
"provided #{dimensions}."
|
26
|
+
layout.give_up message
|
27
|
+
end
|
28
|
+
|
29
|
+
def determine_dimensions
|
30
|
+
dimensions.each do |key, value|
|
31
|
+
next unless DIMENSIONS.include? key
|
32
|
+
case value
|
33
|
+
when String
|
34
|
+
create_pending_dimension key, value
|
35
|
+
when Numeric
|
36
|
+
add_numeric_dimension key, value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_numeric_dimension(dimension_name, value)
|
42
|
+
box.instance_variable_set "@#{dimension_name}", value
|
43
|
+
box.add_to_determined(dimension_name, value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_pending_dimension(dimension_name, command_string)
|
47
|
+
box.pending << dimension_name
|
48
|
+
commands = command_array command_string
|
49
|
+
pending = create_pending_hash commands
|
50
|
+
return_hash = { pending: pending, command: commands }
|
51
|
+
box.instance_variable_set "@#{dimension_name}", return_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def command_array(command_string)
|
55
|
+
commands = command_string.split(' ')
|
56
|
+
commands.map { |command| convert_command(command) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def convert_command(command)
|
60
|
+
case command
|
61
|
+
when '*', '+', '-', '/'
|
62
|
+
command.to_sym
|
63
|
+
when layout.css_reader.class::NUMERIC_REGEX
|
64
|
+
Float command
|
65
|
+
else
|
66
|
+
command.gsub('-', '_')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_pending_hash(command_array)
|
71
|
+
h = {}
|
72
|
+
command_array.each_with_index do |command, index|
|
73
|
+
if (command.is_a? String) && (command != '(') && (command != ')')
|
74
|
+
h[command] = index
|
75
|
+
end
|
76
|
+
end
|
77
|
+
h
|
78
|
+
end
|
79
|
+
|
80
|
+
def frame_box
|
81
|
+
# from two of left, right, width, determine other dimension
|
82
|
+
# bottom, height are optional unless top is missing
|
83
|
+
left_right_width
|
84
|
+
top_bottom_height
|
85
|
+
end
|
86
|
+
|
87
|
+
def left_right_width
|
88
|
+
given = checker.horizontal_dimensions
|
89
|
+
difference = %w(left right width) - given
|
90
|
+
missing = difference.empty? ? 'right' : difference[0]
|
91
|
+
missing_dimension given, missing
|
92
|
+
end
|
93
|
+
|
94
|
+
def top_bottom_height
|
95
|
+
given = checker.vertical_dimensions
|
96
|
+
difference = %w(top bottom height) - given
|
97
|
+
case difference.length
|
98
|
+
when 0
|
99
|
+
missing_dimension given, 'bottom'
|
100
|
+
when 1
|
101
|
+
missing_dimension given, difference[0]
|
102
|
+
when 2
|
103
|
+
make_stretchy
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def missing_dimension(given, missing)
|
108
|
+
if given.any? { |dim| box.pending.include? dim }
|
109
|
+
box.unfinished << missing
|
110
|
+
else
|
111
|
+
box.determine_missing missing
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def make_stretchy
|
116
|
+
box.add_to_determined 'bottom', :stretchy
|
117
|
+
box.add_to_determined 'height', :stretchy
|
118
|
+
box.stretchy = true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Bread
|
2
|
+
module Basket
|
3
|
+
module Poster
|
4
|
+
class HeaderCallback
|
5
|
+
def initialize(document, options)
|
6
|
+
@color = options['background-color']
|
7
|
+
@document = document
|
8
|
+
@radius = options['radius']
|
9
|
+
@height = options['height']
|
10
|
+
@width = options['width']
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_behind(fragment)
|
14
|
+
original_color = @document.fill_color
|
15
|
+
vert_dist = (@height - fragment.height) / 2
|
16
|
+
|
17
|
+
# TODO: If you want to get real picky, the vertical centering should
|
18
|
+
# be aware of whether the text has ascenders or descenders, there's
|
19
|
+
# a prawn method for calculating that sort of thing precisely.
|
20
|
+
left_top = [@document.bounds.left, fragment.top + vert_dist]
|
21
|
+
|
22
|
+
@document.fill_color = @color.sub('#', '')
|
23
|
+
@document.fill_rounded_rectangle(left_top, @width, @height, @radius)
|
24
|
+
@document.fill_color = original_color
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Bread
|
2
|
+
module Basket
|
3
|
+
module Poster
|
4
|
+
class HeaderMaker
|
5
|
+
attr_reader :pdf, :layout, :styles
|
6
|
+
|
7
|
+
HEADER_DEFAULTS =
|
8
|
+
{ 'font-size' => 36,
|
9
|
+
'color' => '000000',
|
10
|
+
'margin-top' => 0,
|
11
|
+
'margin-bottom' => 0,
|
12
|
+
'radius' => 0,
|
13
|
+
'background-color' => 'ffffff',
|
14
|
+
'font-family' => 'Helvetica'
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(pdf, layout)
|
18
|
+
@pdf = pdf
|
19
|
+
@layout = layout
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_header(text, styles)
|
23
|
+
@text = text
|
24
|
+
@styles = init_styles(styles)
|
25
|
+
@header_callback = Poster::HeaderCallback.new(pdf, @styles)
|
26
|
+
|
27
|
+
pdf.move_down top_margin
|
28
|
+
pdf.formatted_text_box text_box_arr, text_box_opts
|
29
|
+
pdf.move_down bottom_margin
|
30
|
+
|
31
|
+
text # don't return a Numeric, Redcarpet hates that!
|
32
|
+
end
|
33
|
+
|
34
|
+
def init_styles(styles)
|
35
|
+
styles = HEADER_DEFAULTS.merge layout_styles
|
36
|
+
styles['width'] = layout.columns[0].width unless styles.key? 'width'
|
37
|
+
styles['height'] = styles['font-size'] * 2 unless styles.key? 'height'
|
38
|
+
styles
|
39
|
+
end
|
40
|
+
|
41
|
+
def layout_styles
|
42
|
+
if layout.respond_to? :header
|
43
|
+
layout.header
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def text_box_arr
|
50
|
+
[{ text: @text,
|
51
|
+
callback: @header_callback,
|
52
|
+
color: styles['color'].sub('#', ''),
|
53
|
+
font: styles['font-family'],
|
54
|
+
size: styles['font-size']
|
55
|
+
}]
|
56
|
+
end
|
57
|
+
|
58
|
+
def text_box_opts
|
59
|
+
{ at: [pdf.bounds.left, pdf.cursor],
|
60
|
+
width: layout.columns[0].width,
|
61
|
+
align: :center }
|
62
|
+
end
|
63
|
+
|
64
|
+
def top_margin
|
65
|
+
styles['margin-top'] + (styles['height'] - styles['font-size']) / 2
|
66
|
+
end
|
67
|
+
|
68
|
+
def bottom_margin
|
69
|
+
styles['margin-bottom'] + styles['height']
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|