srdperu-prawn-format 0.1.1.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.
@@ -0,0 +1,189 @@
1
+ # encoding: utf-8
2
+
3
+ module Prawn
4
+ module Format
5
+ class State
6
+ attr_reader :document
7
+ attr_reader :original_style, :style
8
+
9
+ def initialize(document, options={})
10
+ @document = document
11
+ @previous = options[:previous]
12
+
13
+ @original_style = (@previous && @previous.inheritable_style || {}).
14
+ merge(options[:style] || {})
15
+
16
+ compute_styles!
17
+
18
+ @style[:kerning] = font.has_kerning_data? unless @style.key?(:kerning)
19
+ end
20
+
21
+ def inheritable_style
22
+ @inheritable_style ||= begin
23
+ subset = original_style.dup
24
+ subset.delete(:meta)
25
+ subset.delete(:display)
26
+ subset.delete(:width)
27
+
28
+ # explicitly set font-size so that relative font-sizes don't get
29
+ # recomputed upon each nesting.
30
+ subset[:font_size] = font_size
31
+
32
+ subset
33
+ end
34
+ end
35
+
36
+ def kerning?
37
+ @style[:kerning]
38
+ end
39
+
40
+ def display
41
+ @style[:display] || :inline
42
+ end
43
+
44
+ def font_size
45
+ @style[:font_size] || 12
46
+ end
47
+
48
+ def font_family
49
+ @style[:font_family] || "Helvetica"
50
+ end
51
+
52
+ def font_style
53
+ @style[:font_style] || :normal
54
+ end
55
+
56
+ def font_weight
57
+ @style[:font_weight] || :normal
58
+ end
59
+
60
+ def color
61
+ @style[:color] || "000000"
62
+ end
63
+
64
+ def vertical_align
65
+ @style[:vertical_align] || 0
66
+ end
67
+
68
+ def text_decoration
69
+ @style[:text_decoration] || :none
70
+ end
71
+
72
+ def white_space
73
+ @style[:white_space] || :normal
74
+ end
75
+
76
+ def width
77
+ @style[:width] || 0
78
+ end
79
+
80
+ def font
81
+ @font ||= document.find_font(font_family, :style => pdf_font_style)
82
+ end
83
+
84
+ def pdf_font_style
85
+ if bold? && italic?
86
+ :bold_italic
87
+ elsif bold?
88
+ :bold
89
+ elsif italic?
90
+ :italic
91
+ else
92
+ :normal
93
+ end
94
+ end
95
+
96
+ def with_style(style)
97
+ self.class.new(document, :previous => self, :style => style)
98
+ end
99
+
100
+ def apply!(text_object, cookies)
101
+ if cookies[:color] != color
102
+ cookies[:color] = color
103
+ text_object.fill_color(color)
104
+ end
105
+
106
+ if cookies[:vertical_align] != vertical_align
107
+ cookies[:vertical_align] = vertical_align
108
+ text_object.rise(vertical_align)
109
+ end
110
+ end
111
+
112
+ def apply_font!(text_object, cookies, subset)
113
+ if cookies[:font] != [font_family, pdf_font_style, font_size, subset]
114
+ cookies[:font] = [font_family, pdf_font_style, font_size, subset]
115
+ font = document.font(font_family, :style => pdf_font_style)
116
+ font.add_to_current_page(subset)
117
+ text_object.font(font.identifier_for(subset), font_size)
118
+ end
119
+ end
120
+
121
+ def italic?
122
+ font_style == :italic
123
+ end
124
+
125
+ def bold?
126
+ font_weight == :bold
127
+ end
128
+
129
+ def previous(attr=nil, default=nil)
130
+ return @previous unless attr
131
+ return default unless @previous
132
+ return @previous.send(attr) || default
133
+ end
134
+
135
+ private
136
+
137
+ def compute_styles!
138
+ @style = @original_style.dup
139
+
140
+ evaluate_style(:font_size, 12, :current)
141
+ evaluate_style(:vertical_align, 0, font_size, :super => "+40%", :sub => "-30%")
142
+ evaluate_style(:width, 0, document.bounds.width)
143
+
144
+ @style[:color] = evaluate_color(@style[:color])
145
+ end
146
+
147
+ def evaluate_style(which, default, relative_to, mappings={})
148
+ current = previous(which, default)
149
+ relative_to = current if relative_to == :current
150
+ @style[which] = document.evaluate_measure(@style[which],
151
+ :em => @previous && @previous.font_size || 12,
152
+ :current => current, :relative => relative_to, :mappings => mappings) || default
153
+ end
154
+
155
+ HTML_COLORS = {
156
+ "aqua" => "00FFFF",
157
+ "black" => "000000",
158
+ "blue" => "0000FF",
159
+ "fuchsia" => "FF00FF",
160
+ "gray" => "808080",
161
+ "green" => "008000",
162
+ "lime" => "00FF00",
163
+ "maroon" => "800000",
164
+ "navy" => "000080",
165
+ "olive" => "808000",
166
+ "purple" => "800080",
167
+ "red" => "FF0000",
168
+ "silver" => "C0C0C0",
169
+ "teal" => "008080",
170
+ "white" => "FFFFFF",
171
+ "yellow" => "FFFF00"
172
+ }
173
+
174
+ def evaluate_color(color)
175
+ case color
176
+ when nil then nil
177
+ when /^\s*#?([a-f0-9]{3})\s*$/i then
178
+ return $1.gsub(/./, '\&0')
179
+ when /^\s*#?([a-f0-9]+)$\s*/i then
180
+ return $1
181
+ when /^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/
182
+ return "%02x%02x%02x" % [$1.to_i, $2.to_i, $3.to_i]
183
+ else
184
+ return HTML_COLORS[color.strip.downcase]
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,107 @@
1
+ # encoding: utf-8
2
+
3
+ require 'prawn/graphics/color'
4
+
5
+ module Prawn
6
+ module Format
7
+ class TextObject
8
+ include Prawn::Graphics::Color
9
+
10
+ RENDER_MODES = {
11
+ :fill => 0,
12
+ :stroke => 1,
13
+ :fill_stroke => 2,
14
+ :invisible => 3,
15
+ :fill_clip => 4,
16
+ :stroke_clip => 5,
17
+ :fill_stroke_clip => 6,
18
+ :clip => 7
19
+ }
20
+
21
+ def initialize
22
+ @content = nil
23
+ @last_x = @last_y = 0
24
+ end
25
+
26
+ def open
27
+ @content = "BT\n"
28
+ self
29
+ end
30
+
31
+ def close
32
+ @content << "ET"
33
+ self
34
+ end
35
+
36
+ def move_to(x, y)
37
+ move_by(x - @last_x, y - @last_y)
38
+ end
39
+
40
+ def move_by(dx,dy)
41
+ @last_x += dx
42
+ @last_y += dy
43
+ @content << "#{dx} #{dy} Td\n"
44
+ self
45
+ end
46
+
47
+ def next_line(dy)
48
+ end
49
+
50
+ def show(argument)
51
+ instruction = argument.is_a?(Array) ? "TJ" : "Tj"
52
+ @content << "#{Prawn::PdfObject(argument, true)} #{instruction}\n"
53
+ self
54
+ end
55
+
56
+ def character_space(dc)
57
+ @content << "#{dc} Tc\n"
58
+ self
59
+ end
60
+
61
+ def word_space(dw)
62
+ @content << "#{dw} Tw\n"
63
+ self
64
+ end
65
+
66
+ def leading(dl)
67
+ @content << "#{dl} TL\n"
68
+ self
69
+ end
70
+
71
+ def font(identifier, size)
72
+ @content << "/#{identifier} #{size} Tf\n"
73
+ self
74
+ end
75
+
76
+ def render(mode)
77
+ mode_value = RENDER_MODES[mode] || raise(ArgumentError, "unsupported render mode #{mode.inspect}, should be one of #{RENDER_MODES.keys.inspect}")
78
+ @content << "#{mode_value} Tr\n"
79
+ self
80
+ end
81
+
82
+ def rise(value)
83
+ @content << "#{value} Ts\n"
84
+ self
85
+ end
86
+
87
+ def rotate(x, y, theta)
88
+ radians = theta * Math::PI / 180
89
+ cos, sin = Math.cos(radians), Math.sin(radians)
90
+ arr = [cos, sin, -sin, cos, x, y]
91
+ add_content "%.3f %.3f %.3f %.3f %.3f %.3f Tm" % arr
92
+ end
93
+
94
+ def to_s
95
+ @content
96
+ end
97
+
98
+ def to_str
99
+ @content
100
+ end
101
+
102
+ def add_content(string)
103
+ @content << string << "\n"
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,5 @@
1
+ module Prawn
2
+ module Format
3
+ VERSION = "0.2.0.1"
4
+ end
5
+ end
@@ -0,0 +1,229 @@
1
+ # encoding: utf-8
2
+
3
+ require 'prawn/format/layout_builder'
4
+ require 'prawn/format/text_object'
5
+
6
+ module Prawn
7
+ module Format
8
+ def self.included(mod)
9
+ mod.send :alias_method, :text_without_formatting, :text
10
+ mod.send :alias_method, :text, :text_with_formatting
11
+
12
+ mod.send :alias_method, :width_of_without_formatting, :width_of
13
+ mod.send :alias_method, :width_of, :width_of_with_formatting
14
+
15
+ mod.send :alias_method, :height_of_without_formatting, :height_of
16
+ mod.send :alias_method, :height_of, :height_of_with_formatting
17
+ end
18
+
19
+ # Overloaded version of #text. Call via #text, rather than #text_with_formatting
20
+ # (see above, where it aliased to #text).
21
+ def text_with_formatting(text, options={}) #:nodoc:
22
+ if unformatted?(text, options)
23
+ text_without_formatting(text, options)
24
+ else
25
+ format(text, options)
26
+ end
27
+ end
28
+
29
+ # Overloaded version of #height_of. Call via #height_of, rather than
30
+ # #height_of_with_formatting (see above, where it aliased to #height_of).
31
+ def height_of_with_formatting(string, line_width, size=font_size, options={}) #:nodoc:
32
+ if unformatted?(string, options)
33
+ height_of_without_formatting(string, line_width, size)
34
+ else
35
+ formatted_height(string, line_width, size, options)
36
+ end
37
+ end
38
+
39
+ # Overloaded version of #width_of. Call via #width_of, rather than
40
+ # #width_of_with_formatting (see above, where it aliased to #width_of).
41
+ def width_of_with_formatting(string, options={}) #:nodoc:
42
+ if unformatted?(string, options)
43
+ width_of_without_formatting(string, options)
44
+ else
45
+ formatted_width(string, options)
46
+ end
47
+ end
48
+
49
+ DEFAULT_TAGS = {
50
+ :a => { :meta => { :name => :anchor, :href => :target }, :color => "0000ff", :text_decoration => :underline },
51
+ :b => { :font_weight => :bold },
52
+ :br => { :display => :break },
53
+ :code => { :font_family => "Courier", :font_size => "90%" },
54
+ :em => { :font_style => :italic },
55
+ :font => { :meta => { :face => :font_family, :color => :color, :size => :font_size } },
56
+ :i => { :font_style => :italic },
57
+ :pre => { :white_space => :pre, :font_family => "Courier", :font_size => "90%" },
58
+ :span => {},
59
+ :strong => { :font_weight => :bold },
60
+ :sub => { :vertical_align => :sub, :font_size => "70%" },
61
+ :sup => { :vertical_align => :super, :font_size => "70%" },
62
+ :tt => { :font_family => "Courier" },
63
+ :u => { :text_decoration => :underline },
64
+ }.freeze
65
+
66
+ def tags(update={})
67
+ @tags ||= DEFAULT_TAGS.dup
68
+ @tags.update(update)
69
+ end
70
+
71
+ def styles(update={})
72
+ @styles ||= {}
73
+ @styles.update(update)
74
+ end
75
+
76
+ def default_style
77
+ { :font_family => font.family || font.name,
78
+ :font_size => font_size,
79
+ :color => fill_color }
80
+ end
81
+
82
+ def evaluate_measure(measure, options={})
83
+ case measure
84
+ when nil then nil
85
+ when Numeric then return measure
86
+ when Symbol then
87
+ mappings = options[:mappings] || {}
88
+ raise ArgumentError, "unrecognized value #{measure.inspect}" unless mappings.key?(measure)
89
+ return evaluate_measure(mappings[measure], options)
90
+ when String then
91
+ operator, value, unit = measure.match(/^([-+]?)(\d+(?:\.\d+)?)(.*)$/)[1,3]
92
+
93
+ value = case unit
94
+ when "%" then
95
+ relative = options[:relative] || 0
96
+ relative * value.to_f / 100
97
+ when "em" then
98
+ # not a true em, but good enough for approximating. patches welcome.
99
+ value.to_f * (options[:em] || font_size)
100
+ when "", "pt" then return value.to_f
101
+ when "pc" then return value.to_f * 12
102
+ when "in" then return value.to_f * 72
103
+ else raise ArgumentError, "unsupport units in style value: #{measure.inspect}"
104
+ end
105
+
106
+ current = options[:current] || 0
107
+ case operator
108
+ when "+" then return current + value
109
+ when "-" then return current - value
110
+ else return value
111
+ end
112
+ else return measure.to_f
113
+ end
114
+ end
115
+
116
+ def draw_lines(x, y, width, lines, options={})
117
+ real_x, real_y = translate(x, y)
118
+
119
+ state = options[:state] || {}
120
+ options[:align] ||= :left
121
+
122
+ state = state.merge(:width => width,
123
+ :x => x, :y => y,
124
+ :real_x => real_x, :real_y => real_y,
125
+ :dx => 0, :dy => 0)
126
+
127
+ state[:cookies] ||= {}
128
+ state[:pending_effects] ||= []
129
+
130
+ return state if lines.empty?
131
+
132
+ text_object do |text|
133
+ text.rotate(real_x, real_y, options[:rotate] || 0)
134
+ state[:text] = text
135
+ lines.each { |line| line.draw_on(self, state, options) }
136
+ end
137
+
138
+ state.delete(:text)
139
+
140
+ #rectangle [x, y+state[:dy]], width, state[:dy]
141
+ #stroke
142
+
143
+ return state
144
+ end
145
+
146
+ def layout(text, options={})
147
+ helper = Format::LayoutBuilder.new(self, text, options)
148
+ yield helper if block_given?
149
+ return helper
150
+ end
151
+
152
+ def format(text, options={})
153
+ if options[:at]
154
+ x, y = options[:at]
155
+ format_positioned_text(text, x, y, options)
156
+ else
157
+ format_wrapped_text(text, options)
158
+ end
159
+ end
160
+
161
+ def text_object
162
+ object = TextObject.new
163
+
164
+ if block_given?
165
+ yield object.open
166
+ add_content(object.close)
167
+ end
168
+
169
+ return object
170
+ end
171
+
172
+ private
173
+
174
+ def unformatted?(text, options={})
175
+ # If they have a preference, use it
176
+ if options.key?(:plain)
177
+ return options[:plain]
178
+
179
+ # Otherwise, if they're asking for full-justification, we must assume
180
+ # the text is formatted (since Prawn's text() method has no full justification)
181
+ elsif options[:align] == :justify
182
+ return false
183
+
184
+ # Otherwise, look for tags or XML entities in the text
185
+ else
186
+ return text !~ /<|&(?:#x?)?\w+;/
187
+ end
188
+ end
189
+
190
+ def format_positioned_text(text, x, y, options={})
191
+ helper = layout(text, options)
192
+ line = helper.next
193
+ draw_lines(x, y+line.ascent, line.width, [line], options)
194
+ end
195
+
196
+ def format_wrapped_text(text, options={})
197
+ helper = layout(text, options)
198
+
199
+ start_new_page if self.y < bounds.absolute_bottom
200
+
201
+ until helper.done?
202
+ y = self.y - bounds.absolute_bottom
203
+ height = bounds.stretchy? ? bounds.absolute_top : y
204
+
205
+ y = helper.fill(bounds.left, y, bounds.width, options.merge(:height => height))
206
+
207
+ if helper.done?
208
+ self.y = y + bounds.absolute_bottom
209
+ else
210
+ start_new_page
211
+ end
212
+ end
213
+ end
214
+
215
+ def formatted_height(string, line_width, size=font_size, options={})
216
+ helper = layout(string, options.merge(:size => size))
217
+ lines = helper.word_wrap(line_width)
218
+ return lines.inject(0) { |s, line| s + line.height }
219
+ end
220
+
221
+ def formatted_width(string, options={})
222
+ helper = layout(string, options)
223
+ helper.next.width
224
+ end
225
+ end
226
+ end
227
+
228
+ require 'prawn/document'
229
+ Prawn::Document.send(:include, Prawn::Format)