asciidoctor-pdf 1.5.0.alpha.7 → 1.5.0.alpha.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NOTICE.adoc +2 -2
- data/README.adoc +127 -128
- data/Rakefile +5 -4
- data/bin/asciidoctor-pdf +15 -2
- data/data/fonts/notoserif-regular-latin.ttf +0 -0
- data/data/themes/default-theme.yml +15 -13
- data/docs/theme-schema.json +114 -0
- data/docs/theming-guide.adoc +386 -132
- data/lib/asciidoctor-pdf/asciidoctor_ext.rb +2 -0
- data/lib/asciidoctor-pdf/asciidoctor_ext/image.rb +18 -0
- data/lib/asciidoctor-pdf/converter.rb +377 -221
- data/lib/asciidoctor-pdf/core_ext.rb +2 -0
- data/lib/asciidoctor-pdf/core_ext/array.rb +10 -4
- data/lib/asciidoctor-pdf/core_ext/numeric.rb +11 -0
- data/lib/asciidoctor-pdf/core_ext/ostruct.rb +1 -1
- data/lib/asciidoctor-pdf/formatted_text.rb +8 -0
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/formatter.rb +6 -9
- data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +16 -0
- data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +125 -0
- data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +45 -0
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/parser.rb +252 -218
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/parser.treetop +18 -9
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/transform.rb +80 -69
- data/lib/asciidoctor-pdf/prawn_ext.rb +2 -2
- data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +164 -35
- data/lib/asciidoctor-pdf/prawn_ext/formatted_text/fragment.rb +37 -0
- data/lib/asciidoctor-pdf/prawn_ext/images.rb +11 -9
- data/lib/asciidoctor-pdf/temporary_path.rb +9 -0
- data/lib/asciidoctor-pdf/theme_loader.rb +40 -33
- data/lib/asciidoctor-pdf/version.rb +1 -1
- metadata +30 -14
@@ -1,6 +1,8 @@
|
|
1
|
+
# regenerate parser.rb using `tt parser.treetop`
|
1
2
|
module Asciidoctor
|
2
|
-
module
|
3
|
-
|
3
|
+
module Pdf
|
4
|
+
module FormattedText
|
5
|
+
grammar Markup
|
4
6
|
rule text
|
5
7
|
complex
|
6
8
|
end
|
@@ -15,20 +17,20 @@ grammar FormattedText
|
|
15
17
|
|
16
18
|
rule element
|
17
19
|
# strict tag matching (costs a minor toll)
|
18
|
-
#
|
20
|
+
# void_element / start_tag complex end_tag &{|seq| seq[0].name == seq[2].name } {
|
19
21
|
|
20
|
-
|
21
|
-
# NOTE content only applies to non-
|
22
|
+
void_element / start_tag complex end_tag {
|
23
|
+
# NOTE content only applies to non-void elements (second part of rule)
|
22
24
|
def content
|
23
25
|
{ type: :element, name: (tag_element = elements[0]).name.to_sym, attributes: tag_element.attributes, pcdata: elements[1].content }
|
24
26
|
end
|
25
27
|
}
|
26
28
|
end
|
27
29
|
|
28
|
-
rule
|
29
|
-
'<
|
30
|
+
rule void_element
|
31
|
+
'<' void_tag_name attributes (spaces? '/')? '>' {
|
30
32
|
def content
|
31
|
-
{ type: :element, name:
|
33
|
+
{ type: :element, name: elements[1].text_value.to_sym, attributes: elements[2].content }
|
32
34
|
end
|
33
35
|
}
|
34
36
|
end
|
@@ -47,7 +49,13 @@ grammar FormattedText
|
|
47
49
|
|
48
50
|
rule tag_name
|
49
51
|
# QUESTION faster to do regex?
|
50
|
-
|
52
|
+
# QUESTION can we cut stuff we aren't using? what about supporting hr?
|
53
|
+
#'a' / 'b' / 'code' / 'color' / 'del' / 'em' / 'font' / 'i' / 'img' / 'link' / 'span' / 'strikethrough' / 'strong' / 'sub' / 'sup' / 'u'
|
54
|
+
'a' / 'code' / 'color' / 'del' / 'em' / 'font' / 'span' / 'strong' / 'sub' / 'sup'
|
55
|
+
end
|
56
|
+
|
57
|
+
rule void_tag_name
|
58
|
+
'br' / 'img'
|
51
59
|
end
|
52
60
|
|
53
61
|
rule attributes
|
@@ -113,3 +121,4 @@ grammar FormattedText
|
|
113
121
|
end
|
114
122
|
end
|
115
123
|
end
|
124
|
+
end
|
@@ -1,10 +1,19 @@
|
|
1
1
|
module Asciidoctor
|
2
|
-
module
|
3
|
-
|
2
|
+
module Pdf
|
3
|
+
module FormattedText
|
4
|
+
class Transform
|
5
|
+
EOL = "\n"
|
6
|
+
NamedEntityTable = {
|
7
|
+
:lt => '<',
|
8
|
+
:gt => '>',
|
9
|
+
:amp => '&',
|
10
|
+
:quot => '"',
|
11
|
+
:apos => '\''
|
12
|
+
}
|
4
13
|
#ZeroWidthSpace = [0x200b].pack 'U*'
|
5
14
|
|
6
15
|
def initialize(options = {})
|
7
|
-
@merge_adjacent_text_nodes = options
|
16
|
+
@merge_adjacent_text_nodes = options[:merge_adjacent_text_nodes]
|
8
17
|
if (theme = options[:theme])
|
9
18
|
@link_font_color = theme.link_font_color
|
10
19
|
@monospaced_font_color = theme.literal_font_color
|
@@ -20,69 +29,79 @@ class FormattedTextTransform
|
|
20
29
|
end
|
21
30
|
end
|
22
31
|
|
23
|
-
# FIXME
|
32
|
+
# FIXME pass styles downwards to child elements rather than decorating on way out of hierarchy
|
24
33
|
def apply(parsed)
|
25
34
|
fragments = []
|
26
35
|
previous_fragment_is_text = false
|
27
|
-
# NOTE using inject is slower than a manual loop
|
36
|
+
# NOTE we use each since using inject is slower than a manual loop
|
28
37
|
parsed.each {|node|
|
29
|
-
case
|
38
|
+
case node[:type]
|
30
39
|
when :element
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
fragments << { text: "\n" }
|
36
|
-
end
|
37
|
-
previous_fragment_is_text = true
|
38
|
-
else
|
39
|
-
if (pcdata = node[:pcdata]) && pcdata.size > 0
|
40
|
+
# case 1: non-void element
|
41
|
+
if (pcdata = node[:pcdata])
|
42
|
+
if pcdata.size > 0
|
43
|
+
tag_name = node[:name]
|
40
44
|
attributes = node[:attributes]
|
41
45
|
fragments << apply(pcdata).map {|fragment|
|
42
46
|
# decorate child fragments with styles from this element
|
43
47
|
build_fragment(fragment, tag_name, attributes)
|
44
48
|
}
|
49
|
+
previous_fragment_is_text = false
|
50
|
+
# NOTE skip element if it has no children
|
45
51
|
#else
|
46
|
-
# # NOTE
|
47
|
-
# if tag_name == :a
|
52
|
+
# # NOTE handle an empty anchor element (i.e., <a ...></a>)
|
53
|
+
# if (tag_name = node[:name]) == :a
|
48
54
|
# fragments << build_fragment({ text: ZeroWidthSpace }, tag_name, node[:attributes])
|
55
|
+
# previous_fragment_is_text = false
|
49
56
|
# end
|
50
57
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
case entity_name
|
60
|
-
when :lt
|
61
|
-
'<'
|
62
|
-
when :gt
|
63
|
-
'>'
|
64
|
-
when :amp
|
65
|
-
'&'
|
66
|
-
when :quot
|
67
|
-
'"'
|
68
|
-
when :apos
|
69
|
-
'\''
|
58
|
+
# case 2: void element
|
59
|
+
else
|
60
|
+
case node[:name]
|
61
|
+
when :br
|
62
|
+
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
63
|
+
fragments << { text: %(#{fragments.pop[:text]}#{EOL}) }
|
64
|
+
else
|
65
|
+
fragments << { text: EOL }
|
70
66
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
67
|
+
previous_fragment_is_text = true
|
68
|
+
when :img
|
69
|
+
attributes = node[:attributes]
|
70
|
+
fragment = {
|
71
|
+
image_path: attributes[:src],
|
72
|
+
image_type: attributes[:type],
|
73
|
+
image_tmp: (attributes[:tmp] == 'true'),
|
74
|
+
text: attributes[:alt],
|
75
|
+
callback: InlineImageRenderer
|
76
|
+
}
|
77
|
+
if (img_w = attributes[:width])
|
78
|
+
fragment[:image_width] = img_w.to_f
|
79
|
+
end
|
80
|
+
fragments << fragment
|
81
|
+
previous_fragment_is_text = false
|
80
82
|
end
|
81
83
|
end
|
84
|
+
when :text
|
85
|
+
text = node[:value]
|
86
|
+
# NOTE the remaining logic is shared with :entity
|
87
|
+
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
88
|
+
fragments << { text: %(#{fragments.pop[:text]}#{text}) }
|
89
|
+
else
|
90
|
+
fragments << { text: text }
|
91
|
+
end
|
92
|
+
previous_fragment_is_text = true
|
93
|
+
when :entity
|
94
|
+
if (name = node[:name])
|
95
|
+
text = NamedEntityTable[name]
|
96
|
+
else
|
97
|
+
# NOTE AFM fonts do not include a thin space glyph; set fallback_fonts to allow glyph to be resolved
|
98
|
+
text = [node[:number]].pack('U*')
|
99
|
+
end
|
100
|
+
# NOTE the remaining logic is shared with :text
|
82
101
|
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
83
|
-
fragments << { text: %(#{fragments.pop[:text]}#{
|
102
|
+
fragments << { text: %(#{fragments.pop[:text]}#{text}) }
|
84
103
|
else
|
85
|
-
fragments << { text:
|
104
|
+
fragments << { text: text }
|
86
105
|
end
|
87
106
|
previous_fragment_is_text = true
|
88
107
|
end
|
@@ -91,12 +110,13 @@ class FormattedTextTransform
|
|
91
110
|
end
|
92
111
|
|
93
112
|
def build_fragment(fragment, tag_name = nil, attrs = {})
|
94
|
-
#
|
113
|
+
# QUESTION should we short-circuit if tag_name is nil?
|
114
|
+
#return { text: fragment } unless tag_name
|
95
115
|
styles = (fragment[:styles] ||= ::Set.new)
|
96
116
|
case tag_name
|
97
|
-
when :
|
117
|
+
when :strong
|
98
118
|
styles << :bold
|
99
|
-
when :
|
119
|
+
when :em
|
100
120
|
styles << :italic
|
101
121
|
when :code
|
102
122
|
fragment[:font] ||= @monospaced_font_family
|
@@ -140,12 +160,17 @@ class FormattedTextTransform
|
|
140
160
|
fragment[:font] = value
|
141
161
|
end
|
142
162
|
if !fragment[:size] && (value = attrs[:size])
|
143
|
-
|
163
|
+
# FIXME can we make this comparison more robust / accurate?
|
164
|
+
if %(#{f_value = value.to_f}) == value || %(#{value.to_i}) == value
|
165
|
+
fragment[:size] = f_value
|
166
|
+
elsif value != '1em'
|
167
|
+
fragment[:size] = value
|
168
|
+
end
|
144
169
|
end
|
145
170
|
#if !fragment[:character_spacing] && (value = attrs[:character_spacing])
|
146
171
|
# fragment[:character_spacing] = value.to_f
|
147
172
|
#end
|
148
|
-
when :a
|
173
|
+
when :a
|
149
174
|
if !fragment[:anchor] && (value = attrs[:anchor])
|
150
175
|
fragment[:anchor] = value
|
151
176
|
end
|
@@ -164,9 +189,9 @@ class FormattedTextTransform
|
|
164
189
|
styles << :subscript
|
165
190
|
when :sup
|
166
191
|
styles << :superscript
|
167
|
-
when :u
|
168
|
-
|
169
|
-
when :del
|
192
|
+
#when :u
|
193
|
+
# styles << :underline
|
194
|
+
when :del
|
170
195
|
styles << :strikethrough
|
171
196
|
when :span
|
172
197
|
# span logic with normal style parsing
|
@@ -201,20 +226,6 @@ class FormattedTextTransform
|
|
201
226
|
fragment
|
202
227
|
end
|
203
228
|
end
|
204
|
-
|
205
|
-
module InlineDestinationMarker
|
206
|
-
module_function
|
207
|
-
|
208
|
-
def render_behind fragment
|
209
|
-
unless (pdf_doc = fragment.instance_variable_get :@document).scratch?
|
210
|
-
if (name = (fragment.instance_variable_get :@format_state)[:name])
|
211
|
-
# get precise position of the reference
|
212
|
-
dest_rect = fragment.absolute_bounding_box
|
213
|
-
# QUESTION should we set precise x value of destination or just 0?
|
214
|
-
pdf_doc.add_dest name, (pdf_doc.dest_xyz dest_rect.first, dest_rect.last)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
218
229
|
end
|
219
230
|
end
|
220
231
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# the following
|
1
|
+
# the following are organized under the Asciidoctor::Prawn namespace
|
2
2
|
require_relative 'prawn_ext/images'
|
3
|
+
require_relative 'prawn_ext/formatted_text/fragment'
|
3
4
|
require_relative 'prawn_ext/extensions'
|
4
|
-
require_relative 'prawn_ext/formatted_text/formatter'
|
@@ -5,6 +5,8 @@ module Extensions
|
|
5
5
|
include ::Asciidoctor::Pdf::Sanitizer
|
6
6
|
include ::Asciidoctor::PdfCore::PdfObject
|
7
7
|
|
8
|
+
MeasurementValueRx = /(\d+|\d*\.\d+)(in|mm|cm|px|pt)?$/
|
9
|
+
|
8
10
|
# - :height is the height of a line
|
9
11
|
# - :leading is spacing between adjacent lines
|
10
12
|
# - :padding_top is half line spacing, plus any line_gap in the font
|
@@ -28,12 +30,24 @@ module Extensions
|
|
28
30
|
page.dimensions[2]
|
29
31
|
end
|
30
32
|
|
33
|
+
# Returns the effective (writable) width of the page
|
34
|
+
#
|
35
|
+
def effective_page_width
|
36
|
+
reference_bounds.width
|
37
|
+
end
|
38
|
+
|
31
39
|
# Returns the height of the current page from edge-to-edge
|
32
40
|
#
|
33
41
|
def page_height
|
34
42
|
page.dimensions[3]
|
35
43
|
end
|
36
44
|
|
45
|
+
# Returns the effective (writable) height of the page
|
46
|
+
#
|
47
|
+
def effective_page_height
|
48
|
+
reference_bounds.height
|
49
|
+
end
|
50
|
+
|
37
51
|
# Returns the width of the left margin for the current page
|
38
52
|
#
|
39
53
|
def page_margin_left
|
@@ -68,6 +82,37 @@ module Extensions
|
|
68
82
|
@y == @margin_box.absolute_top
|
69
83
|
end
|
70
84
|
|
85
|
+
# Converts the specified float value to a pt value from the
|
86
|
+
# specified unit of measurement (e.g., in, cm, mm, etc).
|
87
|
+
def to_pt num, units
|
88
|
+
case units
|
89
|
+
when nil, 'pt'
|
90
|
+
num
|
91
|
+
when 'in'
|
92
|
+
num * 72
|
93
|
+
when 'mm'
|
94
|
+
num * (72 / 25.4)
|
95
|
+
when 'cm'
|
96
|
+
num * (720 / 25.4)
|
97
|
+
when 'px'
|
98
|
+
num * 0.75
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Convert the specified string value to a pt value from the
|
103
|
+
# specified unit of measurement (e.g., in, cm, mm, etc).
|
104
|
+
#
|
105
|
+
# Examples:
|
106
|
+
#
|
107
|
+
# 0.5in => 36.0
|
108
|
+
# 100px => 75.0
|
109
|
+
#
|
110
|
+
def str_to_pt val
|
111
|
+
if MeasurementValueRx =~ val
|
112
|
+
to_pt $1.to_f, $2
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
71
116
|
# Destinations
|
72
117
|
|
73
118
|
# Generates a destination object that resolves to the top of the page
|
@@ -95,10 +140,11 @@ module Extensions
|
|
95
140
|
#
|
96
141
|
# Example:
|
97
142
|
#
|
98
|
-
# register_font
|
99
|
-
# normal: '
|
100
|
-
#
|
101
|
-
#
|
143
|
+
# register_font Roboto: {
|
144
|
+
# normal: 'fonts/roboto-normal.ttf',
|
145
|
+
# italic: 'fonts/roboto-italic.ttf',
|
146
|
+
# bold: 'fonts/roboto-bold.ttf',
|
147
|
+
# bold_italic: 'fonts/roboto-bold_italic.ttf'
|
102
148
|
# }
|
103
149
|
#
|
104
150
|
def register_font data
|
@@ -126,7 +172,7 @@ module Extensions
|
|
126
172
|
# Retrieves the current font info (family, style, size) as a Hash
|
127
173
|
#
|
128
174
|
def font_info
|
129
|
-
{ family: font.options[:family], style: font.options[:style] || :normal, size: @font_size }
|
175
|
+
{ family: font.options[:family], style: (font.options[:style] || :normal), size: @font_size }
|
130
176
|
end
|
131
177
|
|
132
178
|
# Sets the font style for the scope of the block to which this method
|
@@ -153,17 +199,62 @@ module Extensions
|
|
153
199
|
# QUESTION should we round the result?
|
154
200
|
def font_size points = nil
|
155
201
|
return @font_size unless points
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
202
|
+
if points == 1
|
203
|
+
super @font_size
|
204
|
+
elsif String === points
|
205
|
+
if points.end_with? 'rem'
|
206
|
+
super (@theme.base_font_size * points.to_f)
|
207
|
+
elsif points.end_with? 'em'
|
208
|
+
super (@font_size * points.to_f)
|
209
|
+
elsif points.end_with? '%'
|
210
|
+
super (@font_size * (points.to_f / 100.0))
|
211
|
+
else
|
212
|
+
super points.to_f
|
213
|
+
end
|
214
|
+
# FIXME HACK assume em value
|
215
|
+
elsif points < 1
|
216
|
+
super (@font_size * points)
|
217
|
+
else
|
218
|
+
super points
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def resolve_font_style styles
|
223
|
+
if styles.include? :bold
|
224
|
+
(styles.include? :italic) ? :bold_italic : :bold
|
225
|
+
elsif styles.include? :italic
|
226
|
+
:italic
|
227
|
+
else
|
228
|
+
:normal
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Apply the font settings (family, size, styles and character spacing) from
|
233
|
+
# the fragment to the document, then yield to the block.
|
234
|
+
#
|
235
|
+
# The original font settings are restored before this method returns.
|
236
|
+
#
|
237
|
+
def fragment_font fragment
|
238
|
+
f_info = font_info
|
239
|
+
f_family = fragment[:font] || f_info[:family]
|
240
|
+
f_size = fragment[:size] || f_info[:size]
|
241
|
+
if (f_styles = fragment[:styles])
|
242
|
+
f_style = resolve_font_style f_styles
|
243
|
+
else
|
244
|
+
f_style = :normal
|
245
|
+
end
|
246
|
+
|
247
|
+
if (c_spacing = fragment[:character_spacing])
|
248
|
+
character_spacing c_spacing do
|
249
|
+
font f_family, size: f_size, style: f_style do
|
250
|
+
yield
|
251
|
+
end
|
252
|
+
end
|
253
|
+
else
|
254
|
+
font f_family, size: f_size, style: f_style do
|
255
|
+
yield
|
256
|
+
end
|
165
257
|
end
|
166
|
-
super points
|
167
258
|
end
|
168
259
|
|
169
260
|
def calc_line_metrics line_height = 1, font = self.font, font_size = self.font_size
|
@@ -177,7 +268,7 @@ module Extensions
|
|
177
268
|
|
178
269
|
=begin
|
179
270
|
# these line metrics attempted to figure out a correction based on the reported height and the font_size
|
180
|
-
# however, it only works for some fonts, and breaks down for fonts like
|
271
|
+
# however, it only works for some fonts, and breaks down for fonts like Noto Serif
|
181
272
|
def calc_line_metrics line_height = 1, font = self.font, font_size = self.font_size
|
182
273
|
line_height_length = font_size * line_height
|
183
274
|
line_gap = line_height_length - font_size
|
@@ -210,21 +301,38 @@ module Extensions
|
|
210
301
|
end
|
211
302
|
end
|
212
303
|
|
213
|
-
# Performs the same work as text except that the
|
214
|
-
# are applied to the first line of text renderered.
|
215
|
-
|
304
|
+
# Performs the same work as text except that the first_line_opts
|
305
|
+
# are applied to the first line of text renderered. It's necessary
|
306
|
+
# to use low-level APIs in this method so that we only style the
|
307
|
+
# first line and not the remaining lines (which is the default
|
308
|
+
# behavior in Prawn).
|
309
|
+
def text_with_formatted_first_line string, first_line_opts, opts
|
310
|
+
color = opts.delete :color
|
216
311
|
fragments = parse_text string, opts
|
312
|
+
# NOTE the low-level APIs we're using don't recognize the :styles option, so we must resolve
|
313
|
+
if (styles = opts.delete :styles)
|
314
|
+
opts[:style] = resolve_font_style styles
|
315
|
+
end
|
316
|
+
if (first_line_styles = first_line_opts.delete :styles)
|
317
|
+
first_line_opts[:style] = resolve_font_style first_line_styles
|
318
|
+
end
|
319
|
+
first_line_color = (first_line_opts.delete :color) || color
|
217
320
|
opts = opts.merge document: self
|
218
|
-
|
321
|
+
# QUESTION should we merge more carefully here? (hand-select keys?)
|
322
|
+
first_line_opts = opts.merge(first_line_opts).merge single_line: true
|
323
|
+
box = ::Prawn::Text::Formatted::Box.new fragments, first_line_opts
|
324
|
+
# NOTE get remaining_fragments before we add color to fragments on first line
|
219
325
|
remaining_fragments = box.render dry_run: true
|
220
|
-
#
|
221
|
-
if
|
222
|
-
|
326
|
+
# NOTE color must be applied per-fragment
|
327
|
+
if first_line_color
|
328
|
+
fragments.each {|fragment| fragment[:color] ||= first_line_color}
|
223
329
|
end
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
330
|
+
fill_formatted_text_box fragments, first_line_opts
|
331
|
+
unless remaining_fragments.empty?
|
332
|
+
# NOTE color must be applied per-fragment
|
333
|
+
if color
|
334
|
+
remaining_fragments.each {|fragment| fragment[:color] ||= color }
|
335
|
+
end
|
228
336
|
# as of Prawn 1.2.1, we have to handle the line gap after the first line manually
|
229
337
|
move_down opts[:leading]
|
230
338
|
remaining_fragments = fill_formatted_text_box remaining_fragments, opts
|
@@ -364,7 +472,7 @@ module Extensions
|
|
364
472
|
#
|
365
473
|
def fill_and_stroke_bounds f_color = fill_color, s_color = stroke_color, options = {}
|
366
474
|
no_fill = !f_color || f_color == 'transparent'
|
367
|
-
no_stroke = !s_color || s_color == 'transparent'
|
475
|
+
no_stroke = !s_color || s_color == 'transparent' || options[:line_width] == 0
|
368
476
|
return if no_fill && no_stroke
|
369
477
|
save_graphics_state do
|
370
478
|
radius = options[:radius] || 0
|
@@ -378,7 +486,7 @@ module Extensions
|
|
378
486
|
# stroke
|
379
487
|
unless no_stroke
|
380
488
|
stroke_color s_color
|
381
|
-
line_width
|
489
|
+
line_width(options[:line_width] || 0.5)
|
382
490
|
# FIXME think about best way to indicate dashed borders
|
383
491
|
#if options.has_key? :dash_width
|
384
492
|
# dash options[:dash_width], space: options[:dash_space] || 1
|
@@ -429,9 +537,26 @@ module Extensions
|
|
429
537
|
#
|
430
538
|
def stroke_horizontal_rule s_color = stroke_color, options = {}
|
431
539
|
save_graphics_state do
|
432
|
-
line_width options[:line_width] || 0.5
|
540
|
+
line_width(l_width = options[:line_width] || 0.5)
|
433
541
|
stroke_color s_color
|
434
|
-
|
542
|
+
case (options[:line_style] || :solid)
|
543
|
+
when :solid
|
544
|
+
stroke_horizontal_line bounds.left, bounds.right
|
545
|
+
when :double
|
546
|
+
move_up l_width * 1.5
|
547
|
+
stroke_horizontal_line bounds.left, bounds.right
|
548
|
+
move_down l_width * 3
|
549
|
+
stroke_horizontal_line bounds.left, bounds.right
|
550
|
+
move_up l_width * 1.5
|
551
|
+
when :dashed
|
552
|
+
dash l_width * 4
|
553
|
+
stroke_horizontal_line bounds.left, bounds.right
|
554
|
+
undash
|
555
|
+
when :dotted
|
556
|
+
dash l_width
|
557
|
+
stroke_horizontal_line bounds.left, bounds.right
|
558
|
+
undash
|
559
|
+
end
|
435
560
|
end
|
436
561
|
end
|
437
562
|
|
@@ -511,20 +636,24 @@ module Extensions
|
|
511
636
|
end
|
512
637
|
end
|
513
638
|
|
514
|
-
def
|
515
|
-
|
639
|
+
def scratch?
|
640
|
+
(@_label ||= (state.store.info.data[:Scratch] ? :scratch : :primary)) == :scratch
|
516
641
|
end
|
517
|
-
alias :
|
642
|
+
alias :is_scratch? :scratch?
|
518
643
|
|
519
644
|
# TODO document me
|
520
645
|
def dry_run &block
|
521
646
|
scratch = get_scratch_document
|
522
647
|
scratch.start_new_page
|
523
648
|
start_page_number = scratch.page_number
|
649
|
+
# QUESTION is it enough to just set the padding or do we need to clone the bounds?
|
650
|
+
default_bounds = scratch.bounds
|
651
|
+
scratch.bounds = bounds.deep_copy.tap {|b| b.instance_variable_set :@document, scratch }
|
524
652
|
start_y = scratch.y
|
525
653
|
scratch.font font_family, style: font_style, size: font_size do
|
526
654
|
scratch.instance_exec(&block)
|
527
655
|
end
|
656
|
+
scratch.bounds = default_bounds
|
528
657
|
whole_pages = scratch.page_number - start_page_number
|
529
658
|
[(whole_pages * bounds.height + (start_y - scratch.y)), whole_pages, (start_y - scratch.y)]
|
530
659
|
end
|
@@ -537,7 +666,7 @@ module Extensions
|
|
537
666
|
total_height, _whole_pages, _remainder = dry_run(&block)
|
538
667
|
# NOTE technically, if we're at the page top, we don't even need to do the
|
539
668
|
# dry run, except several uses of this method rely on the calculated height
|
540
|
-
if total_height > available_space && !at_page_top?
|
669
|
+
if total_height > available_space && !at_page_top? && total_height <= effective_page_height
|
541
670
|
start_new_page
|
542
671
|
started_new_page = true
|
543
672
|
else
|