squib 0.4.0 → 0.5.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/.gitignore +1 -0
- data/.travis.yml +5 -4
- data/CHANGELOG.md +12 -2
- data/Gemfile +2 -0
- data/README.md +177 -31
- data/Rakefile +1 -0
- data/lib/squib/api/shapes.rb +25 -0
- data/lib/squib/api/text.rb +9 -3
- data/lib/squib/api/text_embed.rb +64 -0
- data/lib/squib/args/typographer.rb +112 -0
- data/lib/squib/args/unit_conversion.rb +4 -0
- data/lib/squib/card.rb +2 -1
- data/lib/squib/constants.rb +32 -1
- data/lib/squib/deck.rb +6 -1
- data/lib/squib/graphics/cairo_context_wrapper.rb +9 -1
- data/lib/squib/graphics/save_doc.rb +1 -1
- data/lib/squib/graphics/shapes.rb +16 -0
- data/lib/squib/graphics/showcase.rb +2 -0
- data/lib/squib/graphics/text.rb +105 -23
- data/lib/squib/project_template/config.yml +13 -0
- data/lib/squib/version.rb +1 -1
- data/samples/config_disable_quotes.yml +3 -0
- data/samples/config_text_markup.rb +20 -0
- data/samples/config_text_markup.yml +9 -0
- data/samples/draw_shapes.rb +5 -0
- data/samples/embed_text.rb +90 -0
- data/samples/text_options.rb +21 -7
- data/spec/api/api_text_spec.rb +3 -3
- data/spec/args/typographer_spec.rb +71 -0
- data/spec/data/samples/autoscale_font.rb.txt +9 -9
- data/spec/data/samples/basic.rb.txt +12 -12
- data/spec/data/samples/config_text_markup.rb.txt +75 -0
- data/spec/data/samples/csv_import.rb.txt +12 -12
- data/spec/data/samples/custom_config.rb.txt +2 -6
- data/spec/data/samples/draw_shapes.rb.txt +11 -0
- data/spec/data/samples/embed_text.rb.txt +295 -0
- data/spec/data/samples/excel.rb.txt +18 -18
- data/spec/data/samples/gradients.rb.txt +2 -2
- data/spec/data/samples/hello_world.rb.txt +2 -2
- data/spec/data/samples/portrait-landscape.rb.txt +2 -2
- data/spec/data/samples/ranges.rb.txt +48 -48
- data/spec/data/samples/saves.rb.txt +32 -32
- data/spec/data/samples/showcase.rb.txt +8 -8
- data/spec/data/samples/text_options.rb.txt +162 -120
- data/spec/data/samples/tgc_proofs.rb.txt +4 -4
- data/spec/graphics/graphics_text_spec.rb +13 -11
- data/spec/samples/samples_regression_spec.rb +2 -0
- data/spec/spec_helper.rb +22 -5
- data/squib.gemspec +1 -1
- data/squib.sublime-project +49 -0
- metadata +17 -16
@@ -0,0 +1,64 @@
|
|
1
|
+
module Squib
|
2
|
+
class TextEmbed
|
3
|
+
# :nodoc:
|
4
|
+
# @api private
|
5
|
+
attr_reader :rules
|
6
|
+
|
7
|
+
# :nodoc:
|
8
|
+
# @api private
|
9
|
+
def initialize
|
10
|
+
@rules = {} # store an array of options for later usage
|
11
|
+
end
|
12
|
+
|
13
|
+
# Context object for embedding an svg icon within text
|
14
|
+
#
|
15
|
+
# @option opts key [String] ('*') the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'
|
16
|
+
# @option opts file [String] ('') file(s) to read in. If it's a single file, then it's use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or '', nothing is done. See {file:README.md#Specifying_Files Specifying Files}. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
17
|
+
# @option opts id [String] (nil) if set, then only render the SVG element with the given id. Prefix '#' is optional. Note: the x-y coordinates are still relative to the SVG document's page. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
18
|
+
# @option opts force_id [Boolean] (false) if set, then this svg will not be rendered at all if the id is empty or nil. If not set, the entire SVG is rendered. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
19
|
+
# @option opts layout [String, Symbol] (nil) entry in the layout to use as defaults for this command. See {file:README.md#Custom_Layouts Custom Layouts}. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
20
|
+
# @option opts width [Integer, :native] (:native) the width of the image rendered.
|
21
|
+
# @option opts height [Integer, :native] the height the height of the image rendered.
|
22
|
+
# @option opts dx [Integer] (0) "delta x", or adjust the icon horizontally by x pixels
|
23
|
+
# @option opts dy [Integer] (0) "delta y", or adjust the icon vertically by y pixels
|
24
|
+
# @option opts alpha [Decimal] (1.0) the alpha-transparency percentage used to blend this image. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
25
|
+
# @option opts blend [:none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity] (:none) the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
26
|
+
# @option opts angle [FixNum] (0) Rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
27
|
+
# @api public
|
28
|
+
def svg(opts)
|
29
|
+
opts = Squib::SYSTEM_DEFAULTS.merge(opts)
|
30
|
+
# TODO: add input validation here. We need the key for example.
|
31
|
+
rule = {type: :svg}.merge(opts)
|
32
|
+
rule[:draw] = Proc.new do |card, x,y|
|
33
|
+
card.svg(rule[:file], rule[:id], x, y, rule[:width], rule[:height],
|
34
|
+
rule[:alpha], rule[:blend], rule[:angle], rule[:mask])
|
35
|
+
end
|
36
|
+
@rules[opts[:key]] = rule
|
37
|
+
end
|
38
|
+
|
39
|
+
# Context object for embedding a png within text
|
40
|
+
#
|
41
|
+
# @option opts key [String] ('*') the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'
|
42
|
+
# @option opts file [String] ('') file(s) to read in. If it's a single file, then it's use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or '', nothing is done. See {file:README.md#Specifying_Files Specifying Files}. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
43
|
+
# @option opts layout [String, Symbol] (nil) entry in the layout to use as defaults for this command. See {file:README.md#Custom_Layouts Custom Layouts}. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
44
|
+
# @option opts width [Integer, :native] (:native) the width of the image rendered
|
45
|
+
# @option opts height [Integer, :native] the height the height of the image rendered
|
46
|
+
# @option opts dx [Integer] (0) "delta x", or adjust the icon horizontally by x pixels
|
47
|
+
# @option opts dy [Integer] (0) "delta y", or adjust the icon vertically by y pixels
|
48
|
+
# @option opts alpha [Decimal] (1.0) the alpha-transparency percentage used to blend this image. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
49
|
+
# @option opts blend [:none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity] (:none) the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
50
|
+
# @option opts angle [FixNum] (0) Rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
|
51
|
+
# @api public
|
52
|
+
def png(opts)
|
53
|
+
opts = Squib::SYSTEM_DEFAULTS.merge(opts)
|
54
|
+
# TODO: add input validation here. We need the key for example.
|
55
|
+
rule = {type: :png}.merge(opts)
|
56
|
+
rule[:draw] = Proc.new do |card, x,y|
|
57
|
+
card.png(rule[:file], x, y, rule[:width], rule[:height],
|
58
|
+
rule[:alpha], rule[:blend], rule[:angle], rule[:mask])
|
59
|
+
end
|
60
|
+
@rules[opts[:key]] = rule
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'squib/constants'
|
2
|
+
module Squib
|
3
|
+
module Args
|
4
|
+
class Typographer
|
5
|
+
|
6
|
+
def initialize(config = CONFIG_DEFAULTS)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def process(str)
|
11
|
+
str = explicit_replacements(str)
|
12
|
+
str = smart_quotes(str) if @config['smart_quotes']
|
13
|
+
str
|
14
|
+
end
|
15
|
+
|
16
|
+
def explicit_replacements(str)
|
17
|
+
[ :left_curly, :right_curly, :apostraphize,
|
18
|
+
:ellipsificate, :em_dash, :en_dash ].each do |sym|
|
19
|
+
str = each_non_tag(str) do |token|
|
20
|
+
self.method(sym).call(token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
str
|
24
|
+
end
|
25
|
+
|
26
|
+
def smart_quotes(str)
|
27
|
+
[ :single_inside_double_quote,
|
28
|
+
:right_double_quote,
|
29
|
+
:left_double_quote,
|
30
|
+
:right_single_quote,
|
31
|
+
:left_single_quote].each do |sym|
|
32
|
+
str = each_non_tag(str) do |token|
|
33
|
+
self.method(sym).call(token)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
str
|
37
|
+
end
|
38
|
+
|
39
|
+
# Iterate over each non-tag for processing
|
40
|
+
# Allows us to ignore anything inside < and >
|
41
|
+
def each_non_tag(str)
|
42
|
+
full_str = ''
|
43
|
+
tag_delimit = /(<(?:(?!<).)*>)/ # use non-capturing group w/ negative lookahead
|
44
|
+
str.split(tag_delimit).each do |token|
|
45
|
+
if token.start_with? '<'
|
46
|
+
full_str << token # don't process tags
|
47
|
+
else
|
48
|
+
full_str << yield(token)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
return full_str
|
52
|
+
end
|
53
|
+
|
54
|
+
# Straightforward replace
|
55
|
+
def left_curly(str)
|
56
|
+
str.gsub('``', @config['ldquote'])
|
57
|
+
end
|
58
|
+
|
59
|
+
# Straightforward replace
|
60
|
+
def right_curly(str)
|
61
|
+
str.gsub(%{''}, @config['rdquote'])
|
62
|
+
end
|
63
|
+
|
64
|
+
# A quote between two letters is an apostraphe
|
65
|
+
def apostraphize(str)
|
66
|
+
str.gsub(/(\w)(\')(\w)/, '\1' + @config['rsquote'] + '\3')
|
67
|
+
end
|
68
|
+
|
69
|
+
# Straightforward replace
|
70
|
+
def ellipsificate(str)
|
71
|
+
str.gsub('...', @config['ellipsis'])
|
72
|
+
end
|
73
|
+
|
74
|
+
# Straightforward replace
|
75
|
+
def en_dash(str)
|
76
|
+
str.gsub('--', @config['en_dash'])
|
77
|
+
end
|
78
|
+
|
79
|
+
# Straightforward replace
|
80
|
+
def em_dash(str)
|
81
|
+
str.gsub('---', @config['em_dash'])
|
82
|
+
end
|
83
|
+
|
84
|
+
# Quote next to non-whitespace curls
|
85
|
+
def right_double_quote(str)
|
86
|
+
str.gsub(/(\S)(\")/, '\1' + @config['rdquote'])
|
87
|
+
end
|
88
|
+
|
89
|
+
# Quote next to non-whitespace curls
|
90
|
+
def left_double_quote(str)
|
91
|
+
str.gsub(/(\")(\S)/, @config['ldquote'] + '\2')
|
92
|
+
end
|
93
|
+
|
94
|
+
# Handle the cases where a double quote is next to a single quote
|
95
|
+
def single_inside_double_quote(str)
|
96
|
+
str.gsub(/(\")(\')(\S)/, @config['ldquote'] + @config['lsquote'] + '\3')
|
97
|
+
.gsub(/(\")(\')(\S)/, '\1' + @config['rsquote'] + @config['rdquote'])
|
98
|
+
end
|
99
|
+
|
100
|
+
# Quote next to non-whitespace curls
|
101
|
+
def right_single_quote(str)
|
102
|
+
str.gsub(/(\S)(\')/, '\1' + @config['rsquote'])
|
103
|
+
end
|
104
|
+
|
105
|
+
# Quote next to non-whitespace curls
|
106
|
+
def left_single_quote(str)
|
107
|
+
str.gsub(/(\')(\S)/, @config['lsquote'] + '\2')
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/squib/card.rb
CHANGED
@@ -5,7 +5,6 @@ require 'squib/graphics/cairo_context_wrapper'
|
|
5
5
|
module Squib
|
6
6
|
# Back end graphics. Private.
|
7
7
|
class Card
|
8
|
-
include Squib::InputHelpers
|
9
8
|
|
10
9
|
# :nodoc:
|
11
10
|
# @api private
|
@@ -28,6 +27,8 @@ module Squib
|
|
28
27
|
@cairo_context.antialias = ANTIALIAS_OPTS[(@deck.antialias.downcase)] || 'subpixel'
|
29
28
|
end
|
30
29
|
|
30
|
+
# :nodoc:
|
31
|
+
# @api private
|
31
32
|
def make_surface(svgfile, backend)
|
32
33
|
case backend
|
33
34
|
when :memory
|
data/lib/squib/constants.rb
CHANGED
@@ -8,10 +8,16 @@ module Squib
|
|
8
8
|
:angle => 0,
|
9
9
|
:blend => :none,
|
10
10
|
:color => :black,
|
11
|
-
:columns =>
|
11
|
+
:columns => 5,
|
12
12
|
:count_format => '%02d',
|
13
|
+
:cx1 => 0,
|
14
|
+
:cx2 => 0,
|
15
|
+
:cy1 => 0,
|
16
|
+
:cy2 => 0,
|
13
17
|
:default_font => 'Arial 36',
|
14
18
|
:dir => '_output',
|
19
|
+
:dx => 0, # delta
|
20
|
+
:dy => 0, # delta
|
15
21
|
:ellipsize => :end,
|
16
22
|
:face => :left,
|
17
23
|
:fill_color => '#0000',
|
@@ -24,12 +30,14 @@ module Squib
|
|
24
30
|
:hint => :off,
|
25
31
|
:img_dir => '.',
|
26
32
|
:justify => false,
|
33
|
+
:key => '*',
|
27
34
|
:margin => 75,
|
28
35
|
:markup => false,
|
29
36
|
:mask => nil,
|
30
37
|
:offset => 1.1,
|
31
38
|
:prefix => 'card_',
|
32
39
|
:progress_bar => false,
|
40
|
+
:quotes => :dumb,
|
33
41
|
:reflect_offset => 15,
|
34
42
|
:reflect_percent => 0.25,
|
35
43
|
:reflect_strength => 0.2,
|
@@ -72,6 +80,14 @@ module Squib
|
|
72
80
|
'hint' => :none,
|
73
81
|
'img_dir' => '.',
|
74
82
|
'progress_bar' => false,
|
83
|
+
'ldquote' => "\u201C", # UTF8 chars
|
84
|
+
'rdquote' => "\u201D",
|
85
|
+
'lsquote' => "\u2018",
|
86
|
+
'rsquote' => "\u2019",
|
87
|
+
'em_dash' => "\u2014",
|
88
|
+
'en_dash' => "\u2013",
|
89
|
+
'ellipsis' => "\u2026",
|
90
|
+
'smart_quotes' => true,
|
75
91
|
}
|
76
92
|
|
77
93
|
#Translate the hints to the methods.
|
@@ -79,6 +95,8 @@ module Squib
|
|
79
95
|
'best' => 'subpixel',
|
80
96
|
'good' => 'gray',
|
81
97
|
'fast' => 'gray',
|
98
|
+
'gray' => 'gray',
|
99
|
+
'subpixel' => 'subpixel'
|
82
100
|
}
|
83
101
|
# These are parameters that are intended to be "expanded" across
|
84
102
|
# range if they are singletons.
|
@@ -96,6 +114,12 @@ module Squib
|
|
96
114
|
:blend => :blend,
|
97
115
|
:circle_radius => :radius,
|
98
116
|
:color => :color,
|
117
|
+
:cx1 => :cx1,
|
118
|
+
:cx2 => :cx2,
|
119
|
+
:cy1 => :cy1,
|
120
|
+
:cy2 => :cy2,
|
121
|
+
:dx => :dx,
|
122
|
+
:dy => :dy,
|
99
123
|
:ellipsize => :ellipsize,
|
100
124
|
:files => :file,
|
101
125
|
:fill_color => :fill_color,
|
@@ -108,6 +132,7 @@ module Squib
|
|
108
132
|
:layout => :layout,
|
109
133
|
:markup => :markup,
|
110
134
|
:mask => :mask,
|
135
|
+
:quotes => :quotes,
|
111
136
|
:rect_radius => :radius,
|
112
137
|
:spacing => :spacing,
|
113
138
|
:str => :str,
|
@@ -138,6 +163,12 @@ module Squib
|
|
138
163
|
# value: the user-facing API key (e.g. radius: '1in')
|
139
164
|
UNIT_CONVERSION_PARAMS = {
|
140
165
|
:circle_radius => :radius,
|
166
|
+
:cx1 => :cx1,
|
167
|
+
:cx2 => :cx2,
|
168
|
+
:cy1 => :cy1,
|
169
|
+
:cy2 => :cy2,
|
170
|
+
:dx => :dx, # delta
|
171
|
+
:dy => :dx, # delta
|
141
172
|
:gap => :gap,
|
142
173
|
:height => :height,
|
143
174
|
:margin => :margin,
|
data/lib/squib/deck.rb
CHANGED
@@ -34,7 +34,7 @@ module Squib
|
|
34
34
|
|
35
35
|
# :nodoc:
|
36
36
|
# @api private
|
37
|
-
attr_reader :layout, :config
|
37
|
+
attr_reader :layout, :config, :quote_chars
|
38
38
|
|
39
39
|
attr_reader :dir, :prefix, :count_format
|
40
40
|
|
@@ -70,6 +70,7 @@ module Squib
|
|
70
70
|
@dir = SYSTEM_DEFAULTS[:dir]
|
71
71
|
@prefix = SYSTEM_DEFAULTS[:prefix]
|
72
72
|
@count_format = SYSTEM_DEFAULTS[:count_format]
|
73
|
+
@quote_chars = CONFIG_DEFAULTS.select {|k,v| %w(lsquote rsquote ldquote rdquote em_dash en_dash ellipsis smart_quotes).include?(k) }
|
73
74
|
show_info(config, layout)
|
74
75
|
load_config(config)
|
75
76
|
@width = Args::UnitConversion.parse width, dpi
|
@@ -111,6 +112,10 @@ module Squib
|
|
111
112
|
@prefix = config['prefix']
|
112
113
|
@count_format = config['count_format']
|
113
114
|
@antialias = config['antialias']
|
115
|
+
@quote_chars ||= {}
|
116
|
+
%w(lsquote rsquote ldquote rdquote smart_quotes em_dash en_dash ellipsis).each do |key|
|
117
|
+
@quote_chars[key] = config[key]
|
118
|
+
end
|
114
119
|
end
|
115
120
|
end
|
116
121
|
|
@@ -3,10 +3,16 @@ require 'squib/graphics/gradient_regex'
|
|
3
3
|
|
4
4
|
module Squib
|
5
5
|
module Graphics
|
6
|
+
# Wrapper class for the Cairo context. Private.
|
6
7
|
class CairoContextWrapper
|
7
8
|
extend Forwardable
|
9
|
+
|
10
|
+
# :nodoc:
|
11
|
+
# @api private
|
8
12
|
attr_accessor :cairo_cxt
|
9
13
|
|
14
|
+
# :nodoc:
|
15
|
+
# @api private
|
10
16
|
def initialize(cairo_cxt)
|
11
17
|
@cairo_cxt = cairo_cxt
|
12
18
|
end
|
@@ -16,8 +22,10 @@ module Squib
|
|
16
22
|
:show_pango_layout, :rounded_rectangle, :set_line_width, :stroke, :fill,
|
17
23
|
:set_source, :scale, :render_rsvg_handle, :circle, :triangle, :line_to,
|
18
24
|
:operator=, :show_page, :clip, :transform, :mask, :create_pango_layout,
|
19
|
-
:antialias
|
25
|
+
:antialias=, :curve_to, :matrix, :matrix=, :identity_matrix
|
20
26
|
|
27
|
+
# :nodoc:
|
28
|
+
# @api private
|
21
29
|
def set_source_squibcolor(arg)
|
22
30
|
if match = arg.match(LINEAR_GRADIENT)
|
23
31
|
x1, y1, x2, y2 = match.captures
|
@@ -67,7 +67,7 @@ module Squib
|
|
67
67
|
# save_sheet prefix: 'sheet_', margin: 75, gap: 5, trim: 37
|
68
68
|
#
|
69
69
|
# @option opts [Enumerable] range (:all) the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges}
|
70
|
-
# @option opts colulmns [Integer] (
|
70
|
+
# @option opts colulmns [Integer] (5) the number of columns in the grid
|
71
71
|
# @option opts rows [Integer] (:infinite) the number of rows in the grid. When set to :infinite, the sheet scales to the rows needed. If there are more cards than rows*columns, new sheets are started.
|
72
72
|
# @option opts [String] prefix (card_) the prefix of the file name(s)
|
73
73
|
# @option opts [String] count_format (%02d) the format string used for formatting the card count (e.g. padding zeros). Uses a Ruby format string (see the Ruby doc for Kernel::sprintf for specifics)
|
@@ -58,5 +58,21 @@ module Squib
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
+
# :nodoc:
|
62
|
+
# @api private
|
63
|
+
def curve(x1, y1, cx1, cy1, x2, y2, cx2, cy2, fill_color, stroke_color, stroke_width)
|
64
|
+
use_cairo do |cc|
|
65
|
+
cc.move_to(x1, y1)
|
66
|
+
cc.curve_to(cx1, cy1, cx2, cy2, x2, y2)
|
67
|
+
cc.set_line_width(stroke_width)
|
68
|
+
cc.set_source_squibcolor(stroke_color)
|
69
|
+
cc.stroke
|
70
|
+
cc.move_to(x1, y1)
|
71
|
+
cc.curve_to(cx1, cy1, cx2, cy2, x2, y2)
|
72
|
+
cc.set_source_squibcolor(fill_color)
|
73
|
+
cc.fill
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
61
77
|
end
|
62
78
|
end
|
@@ -60,6 +60,8 @@ module Squib
|
|
60
60
|
return tmp_cc.target
|
61
61
|
end
|
62
62
|
|
63
|
+
# :nodoc:
|
64
|
+
# @api private
|
63
65
|
def perspective(src, scale, face_right)
|
64
66
|
dest_cxt = Cairo::Context.new(Cairo::ImageSurface.new(src.width * scale, src.height))
|
65
67
|
in_thickness = 1 # Take strip 1 pixel-width at a time
|
data/lib/squib/graphics/text.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'pango'
|
2
|
+
require 'squib/args/typographer'
|
2
3
|
|
3
4
|
module Squib
|
4
5
|
class Card
|
@@ -6,14 +7,14 @@ module Squib
|
|
6
7
|
# :nodoc:
|
7
8
|
# @api private
|
8
9
|
def draw_text_hint(cc,x,y,layout, color,angle)
|
9
|
-
color = @deck.text_hint if color.
|
10
|
+
color = @deck.text_hint if color.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
|
10
11
|
return if color.to_s.eql? 'off' or color.nil?
|
11
12
|
# when w,h < 0, it was never set. extents[1] are ink extents
|
12
13
|
w = layout.width / Pango::SCALE
|
13
14
|
w = layout.extents[1].width / Pango::SCALE if w < 0
|
14
15
|
h = layout.height / Pango::SCALE
|
15
16
|
h = layout.extents[1].height / Pango::SCALE if h < 0
|
16
|
-
cc.rounded_rectangle(
|
17
|
+
cc.rounded_rectangle(0, 0, w, h, 0, 0)
|
17
18
|
cc.set_source_color(color)
|
18
19
|
cc.set_line_width(2.0)
|
19
20
|
cc.stroke
|
@@ -62,57 +63,138 @@ module Squib
|
|
62
63
|
|
63
64
|
# :nodoc:
|
64
65
|
# @api private
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
66
|
+
def compute_valign(layout, valign)
|
67
|
+
return 0 unless layout.height > 0
|
68
|
+
ink_extents = layout.extents[1]
|
69
|
+
case valign.to_s.downcase
|
70
|
+
when 'middle'
|
71
|
+
Pango.pixels( (layout.height - ink_extents.height) / 2)
|
72
|
+
when 'bottom'
|
73
|
+
Pango.pixels(layout.height - ink_extents.height)
|
74
|
+
else
|
75
|
+
0
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
79
|
+
def set_font_rendering_opts!(layout)
|
80
|
+
font_options = Cairo::FontOptions.new
|
81
|
+
font_options.antialias = ANTIALIAS_OPTS[(@deck.antialias.downcase)] || 'gray'
|
82
|
+
font_options.hint_metrics = 'on' # TODO make this configurable
|
83
|
+
font_options.hint_style = 'full' # TODO make this configurable
|
84
|
+
layout.context.font_options = font_options
|
85
|
+
end
|
86
|
+
|
77
87
|
# :nodoc:
|
78
88
|
# @api private
|
79
89
|
def set_wh!(layout, width, height)
|
80
|
-
layout.width
|
90
|
+
layout.width = width * Pango::SCALE unless width.nil? || width == :native
|
81
91
|
layout.height = height * Pango::SCALE unless height.nil? || height == :native
|
82
|
-
layout
|
83
92
|
end
|
84
93
|
|
85
94
|
# :nodoc:
|
86
95
|
# @api private
|
87
|
-
def
|
96
|
+
def next_embed(keys, str)
|
97
|
+
ret = nil
|
98
|
+
ret_key = nil
|
99
|
+
keys.each do |key|
|
100
|
+
i = str.index(key)
|
101
|
+
ret ||= i
|
102
|
+
unless i.nil? || i > ret
|
103
|
+
ret = i
|
104
|
+
ret_key = key
|
105
|
+
end
|
106
|
+
end
|
107
|
+
ret_key
|
108
|
+
end
|
109
|
+
|
110
|
+
# When we do embedded icons, we create a 3-character string that has a font
|
111
|
+
# size of zero and a letter-spacing that fits the icon we need. That gives
|
112
|
+
# us the wrapping behavior we need but no clutter beneath. On most
|
113
|
+
# platforms, this works fine. On Linux, this creates
|
114
|
+
# a Cairo transformation matrix that
|
115
|
+
ZERO_WIDTH_CHAR_SIZE = 0 # this works on most platforms
|
116
|
+
# some platforms make this Pango pixels (1/1024), others a 1 pt font
|
117
|
+
ZERO_WIDTH_CHAR_SIZE = 1 if RbConfig::CONFIG['host_os'] === 'linux-gnu'
|
118
|
+
|
119
|
+
# :nodoc:
|
120
|
+
# @api private
|
121
|
+
def process_embeds(embed, str, layout)
|
122
|
+
return [] unless embed.rules.any?
|
123
|
+
layout.markup = str
|
124
|
+
clean_str = layout.text
|
125
|
+
draw_calls = []
|
126
|
+
searches = []
|
127
|
+
while (key = next_embed(embed.rules.keys, clean_str)) != nil
|
128
|
+
rule = embed.rules[key]
|
129
|
+
spacing = rule[:width] * Pango::SCALE
|
130
|
+
index = clean_str.index(key)
|
131
|
+
index = clean_str[0..index].bytesize #convert to byte index (bug #57)
|
132
|
+
str = str.sub(key, "<span size=\"#{ZERO_WIDTH_CHAR_SIZE}\">a<span letter_spacing=\"#{spacing.to_i}\">a</span>a</span>")
|
133
|
+
layout.markup = str
|
134
|
+
clean_str = layout.text
|
135
|
+
searches << { index: index, rule: rule }
|
136
|
+
end
|
137
|
+
searches.each do |search|
|
138
|
+
rect = layout.index_to_pos(search[:index])
|
139
|
+
x = Pango.pixels(rect.x) + search[:rule][:dx]
|
140
|
+
y = Pango.pixels(rect.y) + search[:rule][:dy]
|
141
|
+
draw_calls << {x: x, y: y, draw: search[:rule][:draw]} # defer drawing until we've valigned
|
142
|
+
end
|
143
|
+
return draw_calls
|
144
|
+
end
|
145
|
+
|
146
|
+
# :nodoc:
|
147
|
+
# @api private
|
148
|
+
def text(embed,str, font, font_size, color,
|
88
149
|
x, y, width, height,
|
89
150
|
markup, justify, wrap, ellipsize,
|
90
151
|
spacing, align, valign, hint, angle)
|
91
152
|
Squib.logger.debug {"Placing '#{str}'' with font '#{font}' @ #{x}, #{y}, color: #{color}, angle: #{angle} etc."}
|
92
153
|
extents = nil
|
154
|
+
str = str.to_s
|
93
155
|
use_cairo do |cc|
|
94
156
|
cc.set_source_squibcolor(color)
|
95
157
|
cc.translate(x,y)
|
96
158
|
cc.rotate(angle)
|
97
|
-
cc.
|
98
|
-
cc.move_to(x,y)
|
159
|
+
cc.move_to(0, 0)
|
99
160
|
|
100
|
-
|
101
|
-
font_desc = Pango::FontDescription.new(font)
|
161
|
+
font_desc = Pango::FontDescription.new(font)
|
102
162
|
font_desc.size = font_size * Pango::SCALE unless font_size.nil?
|
163
|
+
layout = cc.create_pango_layout
|
103
164
|
layout.font_description = font_desc
|
104
|
-
layout.text
|
105
|
-
|
165
|
+
layout.text = str
|
166
|
+
if markup
|
167
|
+
str = Args::Typographer.new(@deck.quote_chars).process(layout.text)
|
168
|
+
layout.markup = str
|
169
|
+
end
|
170
|
+
|
171
|
+
set_font_rendering_opts!(layout)
|
106
172
|
set_wh!(layout, width, height)
|
107
173
|
set_wrap!(layout, wrap)
|
108
174
|
set_ellipsize!(layout, ellipsize)
|
109
175
|
set_align!(layout, align)
|
176
|
+
|
110
177
|
layout.justify = justify unless justify.nil?
|
111
178
|
layout.spacing = spacing * Pango::SCALE unless spacing.nil?
|
112
179
|
cc.update_pango_layout(layout)
|
113
|
-
|
114
|
-
|
115
|
-
|
180
|
+
|
181
|
+
embed_draws = process_embeds(embed, str, layout)
|
182
|
+
|
183
|
+
vertical_start = compute_valign(layout, valign)
|
184
|
+
cc.move_to(0, vertical_start)
|
185
|
+
|
186
|
+
cc.update_pango_layout(layout)
|
187
|
+
cc.show_pango_layout(layout)
|
188
|
+
begin
|
189
|
+
embed_draws.each { |ed| ed[:draw].call(self, ed[:x], ed[:y] + vertical_start) }
|
190
|
+
rescue Exception => e
|
191
|
+
puts "====EXCEPTION!===="
|
192
|
+
puts e
|
193
|
+
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"
|
194
|
+
puts "=================="
|
195
|
+
raise e
|
196
|
+
end
|
197
|
+
draw_text_hint(cc, x, y, layout, hint, angle)
|
116
198
|
extents = { width: layout.extents[1].width / Pango::SCALE,
|
117
199
|
height: layout.extents[1].height / Pango::SCALE }
|
118
200
|
end
|