asciidoctor-pdf 1.6.1 → 2.0.0.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.adoc +273 -31
- data/NOTICE.adoc +16 -4
- data/README.adoc +208 -68
- data/asciidoctor-pdf.gemspec +3 -7
- data/data/fonts/ABOUT-mplus1mn-subset +1 -1
- data/data/fonts/ABOUT-mplus1p-subset +2 -2
- data/data/fonts/ABOUT-notosans-subset +26 -0
- data/data/fonts/ABOUT-notoserif-subset +1 -1
- data/data/fonts/{LICENSE-notoserif → LICENSE-noto} +0 -0
- data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
- data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
- data/data/fonts/notoemoji-subset.ttf +0 -0
- data/data/fonts/notosans-bold-subset.ttf +0 -0
- data/data/fonts/notosans-bold_italic-subset.ttf +0 -0
- data/data/fonts/notosans-italic-subset.ttf +0 -0
- data/data/fonts/notosans-regular-subset.ttf +0 -0
- data/data/fonts/notoserif-bold-subset.ttf +0 -0
- data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
- data/data/fonts/notoserif-italic-subset.ttf +0 -0
- data/data/fonts/notoserif-regular-subset.ttf +0 -0
- data/data/themes/base-theme.yml +21 -24
- data/data/themes/default-for-print-theme.yml +24 -0
- data/data/themes/default-for-print-with-fallback-font-theme.yml +3 -0
- data/data/themes/default-theme.yml +55 -59
- data/data/themes/default-with-fallback-font-theme.yml +2 -2
- data/data/themes/sans-with-fallback-font-theme.yml +10 -0
- data/docs/theming-guide.adoc +977 -352
- data/lib/asciidoctor/pdf/converter.rb +1853 -1566
- data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +22 -1
- data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +9 -15
- data/lib/asciidoctor/pdf/ext/asciidoctor/list.rb +6 -13
- data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +3 -16
- data/lib/asciidoctor/pdf/ext/asciidoctor.rb +1 -5
- data/lib/asciidoctor/pdf/ext/core/file.rb +1 -1
- data/lib/asciidoctor/pdf/ext/core/quantifiable_stdout.rb +1 -4
- data/lib/asciidoctor/pdf/ext/core/string.rb +2 -2
- data/lib/asciidoctor/pdf/ext/core.rb +1 -4
- data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +8 -33
- data/lib/asciidoctor/pdf/ext/pdf-core.rb +0 -16
- data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +5 -7
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +489 -331
- data/lib/asciidoctor/pdf/ext/prawn/font/afm.rb +0 -4
- data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +1 -1
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/arranger.rb +33 -3
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +25 -14
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +9 -3
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/protect_bottom_gutter.rb +13 -0
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +20 -18
- data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/data.rb +6 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/web.rb +22 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/url_loader.rb +13 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg.rb +5 -2
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -20
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/text.rb +39 -1
- data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +21 -15
- data/lib/asciidoctor/pdf/ext/prawn-table.rb +1 -1
- data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
- data/lib/asciidoctor/pdf/ext/pygments.rb +2 -2
- data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +17 -20
- data/lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb +1 -0
- data/lib/asciidoctor/pdf/ext/rouge.rb +0 -1
- data/lib/asciidoctor/pdf/formatted_text/formatter.rb +2 -2
- data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +8 -10
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +69 -78
- data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +7 -10
- data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +2 -4
- data/lib/asciidoctor/pdf/formatted_text/parser.rb +53 -47
- data/lib/asciidoctor/pdf/formatted_text/parser.treetop +5 -7
- data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +14 -14
- data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +4 -7
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +122 -110
- data/lib/asciidoctor/pdf/formatted_text.rb +0 -1
- data/lib/asciidoctor/pdf/index_catalog.rb +7 -11
- data/lib/asciidoctor/pdf/nogmagick.rb +6 -0
- data/lib/asciidoctor/pdf/optimizer.rb +3 -5
- data/lib/asciidoctor/pdf/pdfmark.rb +16 -8
- data/lib/asciidoctor/pdf/roman_numeral.rb +4 -22
- data/lib/asciidoctor/pdf/sanitizer.rb +18 -13
- data/lib/asciidoctor/pdf/section_info_by_page.rb +24 -0
- data/lib/asciidoctor/pdf/theme_loader.rb +100 -80
- data/lib/asciidoctor/pdf/version.rb +1 -2
- data/lib/asciidoctor/pdf.rb +5 -2
- metadata +36 -64
- data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +0 -7
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +0 -7
- data/lib/asciidoctor/pdf/ext/asciidoctor/list_item.rb +0 -18
- data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +0 -33
- data/lib/asciidoctor/pdf/ext/core/array.rb +0 -11
- data/lib/asciidoctor/pdf/ext/core/hash.rb +0 -7
- data/lib/asciidoctor/pdf/ext/core/regexp.rb +0 -5
- data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +0 -8
- data/lib/asciidoctor-pdf/converter.rb +0 -3
- data/lib/asciidoctor-pdf/version.rb +0 -3
@@ -6,9 +6,9 @@ module Asciidoctor
|
|
6
6
|
class Transform
|
7
7
|
LF = ?\n
|
8
8
|
ZeroWidthSpace = ?\u200b
|
9
|
-
CharEntityTable = { amp:
|
10
|
-
CharRefRx = /&(?:(#{CharEntityTable.keys.join
|
11
|
-
HexColorRx =
|
9
|
+
CharEntityTable = { amp: '&', apos: ?', gt: '>', lt: '<', nbsp: ?\u00a0, quot: '"' }
|
10
|
+
CharRefRx = /&(?:(#{CharEntityTable.keys.join '|'})|#(?:(\d\d\d{0,4})|x(\h\h\h{0,3})));/
|
11
|
+
HexColorRx = /^#\h\h\h\h{0,3}$/
|
12
12
|
TextDecorationTable = { 'underline' => :underline, 'line-through' => :strikethrough }
|
13
13
|
ThemeKeyToFragmentProperty = {
|
14
14
|
'background_color' => :background_color,
|
@@ -43,30 +43,30 @@ module Asciidoctor
|
|
43
43
|
callback: button_bg_or_border && [TextBackgroundAndBorderRenderer],
|
44
44
|
}.compact,
|
45
45
|
code: {
|
46
|
-
color: theme.
|
47
|
-
font: theme.
|
48
|
-
size: theme.
|
49
|
-
styles: (to_styles theme.
|
50
|
-
background_color: (mono_bg_color = theme.
|
51
|
-
border_width: (mono_border_width = theme.
|
52
|
-
border_color: mono_border_width && (theme.
|
53
|
-
border_offset: (mono_border_offset = (mono_bg_or_border = mono_bg_color || mono_border_width) && theme.
|
54
|
-
border_radius: mono_bg_or_border && theme.
|
46
|
+
color: theme.codespan_font_color,
|
47
|
+
font: theme.codespan_font_family,
|
48
|
+
size: theme.codespan_font_size,
|
49
|
+
styles: (to_styles theme.codespan_font_style),
|
50
|
+
background_color: (mono_bg_color = theme.codespan_background_color),
|
51
|
+
border_width: (mono_border_width = theme.codespan_border_width),
|
52
|
+
border_color: mono_border_width && (theme.codespan_border_color || theme.base_border_color),
|
53
|
+
border_offset: (mono_border_offset = (mono_bg_or_border = mono_bg_color || mono_border_width) && theme.codespan_border_offset),
|
54
|
+
border_radius: mono_bg_or_border && theme.codespan_border_radius,
|
55
55
|
align: mono_border_offset && :center,
|
56
56
|
callback: mono_bg_or_border && [TextBackgroundAndBorderRenderer],
|
57
57
|
}.compact,
|
58
|
-
|
59
|
-
color: theme.
|
60
|
-
font: theme.
|
61
|
-
size: theme.
|
62
|
-
styles: (to_styles theme.
|
63
|
-
background_color: (
|
64
|
-
border_width: (
|
65
|
-
border_color:
|
66
|
-
border_offset: (
|
67
|
-
border_radius:
|
68
|
-
align:
|
69
|
-
callback:
|
58
|
+
kbd: {
|
59
|
+
color: theme.kbd_font_color,
|
60
|
+
font: theme.kbd_font_family || theme.codespan_font_family,
|
61
|
+
size: theme.kbd_font_size,
|
62
|
+
styles: (to_styles theme.kbd_font_style),
|
63
|
+
background_color: (kbd_bg_color = theme.kbd_background_color),
|
64
|
+
border_width: (kbd_border_width = theme.kbd_border_width),
|
65
|
+
border_color: kbd_border_width && (theme.kbd_border_color || theme.base_border_color),
|
66
|
+
border_offset: (kbd_border_offset = (kbd_bg_or_border = kbd_bg_color || kbd_border_width) && theme.kbd_border_offset),
|
67
|
+
border_radius: kbd_bg_or_border && theme.kbd_border_radius,
|
68
|
+
align: kbd_border_offset && :center,
|
69
|
+
callback: kbd_bg_or_border && [TextBackgroundAndBorderRenderer],
|
70
70
|
}.compact,
|
71
71
|
link: {
|
72
72
|
color: theme.link_font_color,
|
@@ -75,6 +75,10 @@ module Asciidoctor
|
|
75
75
|
styles: (to_styles theme.link_font_style, theme.link_text_decoration),
|
76
76
|
text_decoration_color: theme.link_text_decoration_color,
|
77
77
|
text_decoration_width: theme.link_text_decoration_width,
|
78
|
+
background_color: (link_bg_color = theme.link_background_color),
|
79
|
+
border_offset: (link_border_offset = link_bg_color && theme.link_border_offset),
|
80
|
+
align: link_border_offset && :center,
|
81
|
+
callback: link_bg_color && [TextBackgroundAndBorderRenderer],
|
78
82
|
}.compact,
|
79
83
|
mark: {
|
80
84
|
color: theme.mark_font_color,
|
@@ -84,6 +88,12 @@ module Asciidoctor
|
|
84
88
|
align: mark_border_offset && :center,
|
85
89
|
callback: mark_bg_color && [TextBackgroundAndBorderRenderer],
|
86
90
|
}.compact,
|
91
|
+
menu: {
|
92
|
+
color: theme.menu_font_color,
|
93
|
+
font: theme.menu_font_family,
|
94
|
+
size: theme.menu_font_size,
|
95
|
+
styles: (to_styles theme.menu_font_style),
|
96
|
+
}.compact,
|
87
97
|
}
|
88
98
|
revise_roles = [].to_set
|
89
99
|
theme.each_pair.each_with_object @theme_settings do |(key, val), accum|
|
@@ -106,14 +116,14 @@ module Asciidoctor
|
|
106
116
|
@theme_settings['underline'] = { styles: [:underline].to_set } unless @theme_settings.key? 'underline'
|
107
117
|
unless @theme_settings.key? 'big'
|
108
118
|
if (base_font_size_large = theme.base_font_size_large)
|
109
|
-
@theme_settings['big'] = { size: %(#{(base_font_size_large / theme.base_font_size.to_f).round
|
119
|
+
@theme_settings['big'] = { size: %(#{(base_font_size_large / theme.base_font_size.to_f).round 5}em) }
|
110
120
|
else
|
111
121
|
@theme_settings['big'] = { size: '1.1667em' }
|
112
122
|
end
|
113
123
|
end
|
114
124
|
unless @theme_settings.key? 'small'
|
115
125
|
if (base_font_size_small = theme.base_font_size_small)
|
116
|
-
@theme_settings['small'] = { size: %(#{(base_font_size_small / theme.base_font_size.to_f).round
|
126
|
+
@theme_settings['small'] = { size: %(#{(base_font_size_small / theme.base_font_size.to_f).round 5}em) }
|
117
127
|
else
|
118
128
|
@theme_settings['small'] = { size: '0.8333em' }
|
119
129
|
end
|
@@ -122,9 +132,10 @@ module Asciidoctor
|
|
122
132
|
@theme_settings = {
|
123
133
|
button: { font: 'Courier', styles: [:bold].to_set },
|
124
134
|
code: { font: 'Courier' },
|
125
|
-
|
135
|
+
kbd: { font: 'Courier', styles: [:italic].to_set },
|
126
136
|
link: { color: '0000FF' },
|
127
137
|
mark: { background_color: 'FFFF00', callback: [TextBackgroundAndBorderRenderer] },
|
138
|
+
menu: { styles: [:bold].to_set },
|
128
139
|
'line-through' => { styles: [:strikethrough].to_set },
|
129
140
|
'underline' => { styles: [:underline].to_set },
|
130
141
|
'big' => { size: '1.667em' },
|
@@ -135,48 +146,58 @@ module Asciidoctor
|
|
135
146
|
|
136
147
|
def apply parsed, fragments = [], inherited = nil
|
137
148
|
previous_fragment_is_text = false
|
138
|
-
# NOTE we use each since using inject is slower than a manual loop
|
149
|
+
# NOTE: we use each since using inject is slower than a manual loop
|
139
150
|
parsed.each do |node|
|
140
151
|
case node[:type]
|
141
152
|
when :element
|
142
153
|
# case 1: non-void element
|
143
154
|
if node.key? :pcdata
|
144
|
-
# NOTE skip element if it has no children
|
155
|
+
# NOTE: skip element if it has no children
|
145
156
|
if (pcdata = node[:pcdata]).empty?
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
# previous_fragment_is_text = false
|
151
|
-
#end
|
157
|
+
# QUESTION: should this be handled by the formatter after the transform is complete?
|
158
|
+
if previous_fragment_is_text && ((previous_fragment_text = fragments[-1][:text]).end_with? ' ')
|
159
|
+
fragments[-1][:text] = previous_fragment_text.chop
|
160
|
+
end
|
152
161
|
else
|
153
162
|
tag_name = node[:name]
|
154
163
|
attributes = node[:attributes]
|
155
164
|
parent = clone_fragment inherited
|
156
|
-
|
157
|
-
|
165
|
+
fragment = build_fragment parent, tag_name, attributes
|
166
|
+
if tag_name == :a && fragment[:type] == :indexterm && !attributes[:visible] &&
|
167
|
+
previous_fragment_is_text && ((previous_fragment_text = fragments[-1][:text]).end_with? ' ')
|
168
|
+
fragments[-1][:text] = previous_fragment_text.chop
|
169
|
+
end
|
170
|
+
# NOTE: decorate child fragments with inherited properties from this element
|
171
|
+
apply pcdata, fragments, fragment
|
158
172
|
previous_fragment_is_text = false
|
159
173
|
end
|
160
174
|
# case 2: void element
|
161
175
|
else
|
162
176
|
case node[:name]
|
163
|
-
when :br
|
164
|
-
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
165
|
-
fragments << (clone_fragment inherited, text: %(#{fragments.pop[:text]}#{LF}))
|
166
|
-
else
|
167
|
-
fragments << { text: LF }
|
168
|
-
end
|
169
|
-
previous_fragment_is_text = true
|
170
177
|
when :img
|
171
178
|
attributes = node[:attributes]
|
172
179
|
fragment = {
|
173
180
|
image_path: attributes[:src],
|
174
181
|
image_format: attributes[:format],
|
182
|
+
# a zero-width space in the text will cause the image to be duplicated
|
175
183
|
# NOTE: add enclosing square brackets here to avoid errors in parsing
|
176
184
|
text: %([#{attributes[:alt].delete ZeroWidthSpace}]),
|
177
|
-
callback: [InlineImageRenderer],
|
178
185
|
object_id: node.object_id, # used to deduplicate if fragment gets split up
|
179
186
|
}
|
187
|
+
if inherited && (callback = inherited[:callback]) && (callback.include? TextBackgroundAndBorderRenderer)
|
188
|
+
# NOTE: if we keep InlineTextAligner, it needs to skip draw_text! for image fragment
|
189
|
+
fragment[:callback] = [TextBackgroundAndBorderRenderer, InlineImageRenderer]
|
190
|
+
fragment.update inherited.slice :border_color, :border_offset, :border_radius, :border_width, :background_color
|
191
|
+
else
|
192
|
+
fragment[:callback] = [InlineImageRenderer]
|
193
|
+
end
|
194
|
+
attributes[:class].split.each do |class_name|
|
195
|
+
next unless @theme_settings.key? class_name
|
196
|
+
update_fragment fragment, @theme_settings[class_name]
|
197
|
+
if fragment[:background_color] || (fragment[:border_color] && fragment[:border_width])
|
198
|
+
fragment[:callback] = [TextBackgroundAndBorderRenderer] | fragment[:callback]
|
199
|
+
end
|
200
|
+
end if attributes.key? :class
|
180
201
|
if inherited && (link = inherited[:link])
|
181
202
|
fragment[:link] = link
|
182
203
|
end
|
@@ -188,15 +209,15 @@ module Asciidoctor
|
|
188
209
|
end
|
189
210
|
fragments << fragment
|
190
211
|
previous_fragment_is_text = false
|
212
|
+
else # :br
|
213
|
+
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
214
|
+
fragments << (clone_fragment inherited, text: %(#{fragments.pop[:text]}#{LF}))
|
215
|
+
else
|
216
|
+
fragments << { text: LF }
|
217
|
+
end
|
218
|
+
previous_fragment_is_text = true
|
191
219
|
end
|
192
220
|
end
|
193
|
-
when :text
|
194
|
-
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
195
|
-
fragments << (clone_fragment inherited, text: %(#{fragments.pop[:text]}#{node[:value]}))
|
196
|
-
else
|
197
|
-
fragments << (clone_fragment inherited, text: node[:value])
|
198
|
-
end
|
199
|
-
previous_fragment_is_text = true
|
200
221
|
when :charref
|
201
222
|
if (ref_type = node[:reference_type]) == :name
|
202
223
|
text = CharEntityTable[node[:value]]
|
@@ -213,61 +234,54 @@ module Asciidoctor
|
|
213
234
|
fragments << (clone_fragment inherited, text: text)
|
214
235
|
end
|
215
236
|
previous_fragment_is_text = true
|
237
|
+
else # :text
|
238
|
+
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
239
|
+
fragments << (clone_fragment inherited, text: %(#{fragments.pop[:text]}#{node[:value]}))
|
240
|
+
else
|
241
|
+
fragments << (clone_fragment inherited, text: node[:value])
|
242
|
+
end
|
243
|
+
previous_fragment_is_text = true
|
216
244
|
end
|
217
245
|
end
|
218
246
|
fragments
|
219
247
|
end
|
220
248
|
|
221
|
-
def build_fragment fragment, tag_name, attrs
|
249
|
+
def build_fragment fragment, tag_name, attrs
|
222
250
|
styles = (fragment[:styles] ||= ::Set.new)
|
223
251
|
case tag_name
|
224
252
|
when :strong
|
225
253
|
styles << :bold
|
226
254
|
when :em
|
227
255
|
styles << :italic
|
228
|
-
when :button, :code, :
|
256
|
+
when :button, :code, :kbd, :mark, :menu
|
229
257
|
update_fragment fragment, @theme_settings[tag_name]
|
230
|
-
when :color
|
231
|
-
if (rgb = attrs[:rgb])
|
232
|
-
case rgb.chr
|
233
|
-
when '#'
|
234
|
-
fragment[:color] = rgb.slice 1, rgb.length
|
235
|
-
when '['
|
236
|
-
# treat value as CMYK array (e.g., "[50, 100, 0, 0]")
|
237
|
-
fragment[:color] = rgb.slice(1, rgb.length).chomp(']').split(', ').map(&:to_i)
|
238
|
-
# ...or we could honor an rgb array too
|
239
|
-
#case (vals = rgb.slice(1, rgb.length).chomp(']').split(', ')).size
|
240
|
-
#when 4
|
241
|
-
# fragment[:color] = vals.map(&:to_i)
|
242
|
-
#when 3
|
243
|
-
# fragment[:color] = vals.map {|e| '%02X' % e.to_i }.join
|
244
|
-
#end
|
245
|
-
else
|
246
|
-
fragment[:color] = rgb
|
247
|
-
end
|
248
|
-
# QUESTION should we even support r,g,b and c,m,y,k as individual values?
|
249
|
-
elsif (r_val = attrs[:r]) && (g_val = attrs[:g]) && (b_val = attrs[:b])
|
250
|
-
fragment[:color] = [r_val, g_val, b_val].map {|e| '%02X' % e.to_i }.join
|
251
|
-
elsif (c_val = attrs[:c]) && (m_val = attrs[:m]) && (y_val = attrs[:y]) && (k_val = attrs[:k])
|
252
|
-
fragment[:color] = [c_val.to_i, m_val.to_i, y_val.to_i, k_val.to_i]
|
253
|
-
end
|
254
258
|
when :font
|
255
259
|
if (value = attrs[:name])
|
256
260
|
fragment[:font] = value
|
257
261
|
end
|
258
262
|
if (value = attrs[:size])
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
fragment[:size] = value
|
263
|
+
if value.end_with? 'em'
|
264
|
+
fragment[:size] = value unless value == '1em'
|
265
|
+
else
|
266
|
+
fragment[:size] = value.to_f
|
264
267
|
end
|
265
268
|
end
|
266
|
-
# NOTE width is used for font-based icons
|
269
|
+
# NOTE: width is used for font-based icons
|
267
270
|
if (value = attrs[:width])
|
268
271
|
fragment[:width] = value
|
269
272
|
fragment[:align] = :center
|
270
|
-
|
273
|
+
end
|
274
|
+
if (value = attrs[:color])
|
275
|
+
case value.chr
|
276
|
+
when '#' # hex string (e.g., #FF0000)
|
277
|
+
fragment[:color] = value.length == 7 ? (value.slice 1, 6) : (value.slice 1, 3).each_char.map {|c| c * 2 }.join if HexColorRx.match? value
|
278
|
+
when '[' # CMYK array (e.g., [50, 100, 0, 0])
|
279
|
+
fragment[:color] = ((((value.slice 1, value.length).chomp ']').split ', ', 4).each_with_object ::Array.new 4, 0).with_index do |(it, accum), idx|
|
280
|
+
accum[idx] = (ival = it.to_i) == (fval = it.to_f) ? ival : fval
|
281
|
+
end
|
282
|
+
else # assume a 6-character hex color (internal only)
|
283
|
+
fragment[:color] = value
|
284
|
+
end
|
271
285
|
end
|
272
286
|
#if (value = attrs[:character_spacing])
|
273
287
|
# fragment[:character_spacing] = value.to_f
|
@@ -276,15 +290,15 @@ module Asciidoctor
|
|
276
290
|
visible = true
|
277
291
|
# a element can have no attributes, so short-circuit if that's the case
|
278
292
|
unless attrs.empty?
|
279
|
-
# NOTE href, anchor, and name are mutually exclusive; nesting is not supported
|
293
|
+
# NOTE: href, anchor, and name are mutually exclusive; nesting is not supported
|
280
294
|
if (value = attrs[:anchor])
|
281
295
|
fragment[:anchor] = value
|
282
296
|
elsif (value = attrs[:href])
|
283
297
|
fragment[:link] = (value.include? ';') ? (value.gsub CharRefRx do
|
284
298
|
$1 ? CharEntityTable[$1.to_sym] : [$2 ? $2.to_i : ($3.to_i 16)].pack('U1')
|
285
299
|
end) : value
|
286
|
-
elsif (value = attrs[:id]
|
287
|
-
# NOTE text is null character, which is used as placeholder text so Prawn doesn't drop fragment
|
300
|
+
elsif (value = attrs[:id])
|
301
|
+
# NOTE: text is null character, which is used as placeholder text so Prawn doesn't drop fragment
|
288
302
|
fragment = { name: value, callback: [InlineDestinationMarker] }
|
289
303
|
if (type = attrs[:type])
|
290
304
|
fragment[:type] = type.to_sym
|
@@ -299,39 +313,29 @@ module Asciidoctor
|
|
299
313
|
styles << :superscript
|
300
314
|
when :del
|
301
315
|
styles << :strikethrough
|
302
|
-
|
303
|
-
# NOTE spaces in style value are superfluous for our purpose; split drops record after trailing ;
|
316
|
+
else # :span
|
317
|
+
# NOTE: spaces in style value are superfluous for our purpose; split drops record after trailing ;
|
304
318
|
attrs[:style].tr(' ', '').split(';').each do |style|
|
305
319
|
pname, pvalue = style.split ':', 2
|
306
320
|
# TODO: text-transform
|
307
321
|
case pname
|
308
|
-
when 'color'
|
309
|
-
|
310
|
-
case pvalue.length
|
311
|
-
when 6
|
312
|
-
fragment[:color] = pvalue
|
313
|
-
when 7
|
314
|
-
fragment[:color] = pvalue.slice 1, 6 if pvalue.start_with? '#'
|
315
|
-
end
|
316
|
-
# QUESTION should we support the 3 character form?
|
317
|
-
#when 3
|
318
|
-
# fragment[:color] = pvalue.each_char.map {|c| c * 2 }.join
|
319
|
-
#when 4
|
320
|
-
# fragment[:color] = pvalue.slice(1, 3).each_char.map {|c| c * 2 }.join if pvalue.start_with?('#')
|
322
|
+
when 'color' # color needed to support syntax highlighters
|
323
|
+
fragment[:color] = pvalue.length == 7 ? (pvalue.slice 1, 6) : (pvalue.slice 1, 3).each_char.map {|c| c * 2 }.join if (pvalue.start_with? '#') && (HexColorRx.match? pvalue)
|
321
324
|
when 'font-weight'
|
322
325
|
styles << :bold if pvalue == 'bold'
|
323
326
|
when 'font-style'
|
324
327
|
styles << :italic if pvalue == 'italic'
|
325
328
|
when 'align', 'text-align'
|
326
329
|
fragment[:align] = pvalue.to_sym
|
327
|
-
fragment[:callback] = (fragment[:callback] || []) | [InlineTextAligner]
|
328
330
|
when 'width'
|
329
|
-
# NOTE implicitly activates inline-block behavior
|
331
|
+
# NOTE: implicitly activates inline-block behavior
|
330
332
|
fragment[:width] = pvalue
|
331
333
|
when 'background-color' # background-color needed to support syntax highlighters
|
332
334
|
if (pvalue.start_with? '#') && (HexColorRx.match? pvalue)
|
333
|
-
fragment[:background_color] = pvalue.slice 1, pvalue.
|
334
|
-
|
335
|
+
fragment[:background_color] = pvalue.length == 7 ? (pvalue.slice 1, 6) : (pvalue.slice 1, 3).each_char.map {|c| c * 2 }.join
|
336
|
+
# Q: is it possible that callback would already be set?
|
337
|
+
#fragment[:callback] = [TextBackgroundAndBorderRenderer] | (fragment[:callback] || [])
|
338
|
+
fragment[:callback] = [TextBackgroundAndBorderRenderer]
|
335
339
|
end
|
336
340
|
end
|
337
341
|
end if attrs.key? :style
|
@@ -340,6 +344,7 @@ module Asciidoctor
|
|
340
344
|
attrs[:class].split.each do |class_name|
|
341
345
|
next unless @theme_settings.key? class_name
|
342
346
|
update_fragment fragment, @theme_settings[class_name]
|
347
|
+
# NOTE: defer assignment of callback since we must look at combined styles of element and role
|
343
348
|
if fragment[:background_color] || (fragment[:border_color] && fragment[:border_width])
|
344
349
|
fragment[:callback] = [TextBackgroundAndBorderRenderer] | (fragment[:callback] || [])
|
345
350
|
fragment[:align] = :center if fragment[:border_offset]
|
@@ -370,6 +375,8 @@ module Asciidoctor
|
|
370
375
|
styles = [:italic].to_set
|
371
376
|
when 'bold_italic'
|
372
377
|
styles = [:bold, :italic].to_set
|
378
|
+
when 'normal_italic'
|
379
|
+
styles = [:normal, :italic].to_set
|
373
380
|
end
|
374
381
|
if (style = TextDecorationTable[text_decoration])
|
375
382
|
styles ? (styles << style) : [style].to_set
|
@@ -382,9 +389,14 @@ module Asciidoctor
|
|
382
389
|
fragment.update props do |k, oval, nval|
|
383
390
|
case k
|
384
391
|
when :styles
|
385
|
-
|
386
|
-
|
387
|
-
|
392
|
+
if nval
|
393
|
+
oval.subtract [:bold, :italic] if nval.delete? :normal
|
394
|
+
oval.merge nval
|
395
|
+
else
|
396
|
+
oval.clear
|
397
|
+
end
|
398
|
+
#when :callback
|
399
|
+
# oval | nval
|
388
400
|
else
|
389
401
|
nval
|
390
402
|
end
|
@@ -20,7 +20,7 @@ module Asciidoctor
|
|
20
20
|
%(__indexterm-#{@sequence += 1})
|
21
21
|
end
|
22
22
|
|
23
|
-
def store_term names, dest
|
23
|
+
def store_term names, dest
|
24
24
|
if (num_terms = names.size) > 2
|
25
25
|
store_tertiary_term names[0], names[1], names[2], dest
|
26
26
|
elsif num_terms == 2
|
@@ -40,8 +40,8 @@ module Asciidoctor
|
|
40
40
|
(store_primary_term primary_name).store_term secondary_name, dest
|
41
41
|
end
|
42
42
|
|
43
|
-
def store_tertiary_term primary_name, secondary_name, tertiary_name, dest
|
44
|
-
store_dest dest
|
43
|
+
def store_tertiary_term primary_name, secondary_name, tertiary_name, dest
|
44
|
+
store_dest dest
|
45
45
|
(store_secondary_term primary_name, secondary_name).store_term tertiary_name, dest
|
46
46
|
end
|
47
47
|
|
@@ -60,7 +60,7 @@ module Asciidoctor
|
|
60
60
|
|
61
61
|
def link_dest_to_page anchor, physical_page_number
|
62
62
|
if (dest = @dests[anchor])
|
63
|
-
virtual_page_number = physical_page_number - (@start_page_number - 1)
|
63
|
+
virtual_page_number = (dest[:page_sortable] = physical_page_number) - (@start_page_number - 1)
|
64
64
|
dest[:page] = (virtual_page_number < 1 ? (RomanNumeral.new physical_page_number, :lower) : virtual_page_number).to_s
|
65
65
|
end
|
66
66
|
end
|
@@ -70,7 +70,7 @@ module Asciidoctor
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def categories
|
73
|
-
@categories.
|
73
|
+
@categories.values.sort
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -83,16 +83,12 @@ module Asciidoctor
|
|
83
83
|
@terms = {}
|
84
84
|
end
|
85
85
|
|
86
|
-
def store_term name, dest
|
86
|
+
def store_term name, dest
|
87
87
|
term = (@terms[name] ||= (IndexTerm.new name))
|
88
88
|
term.add_dest dest if dest
|
89
89
|
term
|
90
90
|
end
|
91
91
|
|
92
|
-
def find_term name
|
93
|
-
@terms[name]
|
94
|
-
end
|
95
|
-
|
96
92
|
def terms
|
97
93
|
@terms.empty? ? [] : @terms.values.sort
|
98
94
|
end
|
@@ -118,7 +114,7 @@ module Asciidoctor
|
|
118
114
|
end
|
119
115
|
|
120
116
|
def dests
|
121
|
-
@dests.select {|d| d.key? :page }.
|
117
|
+
@dests.select {|d| d.key? :page }.sort_by {|d| d[:page_sortable] }
|
122
118
|
end
|
123
119
|
|
124
120
|
def container?
|
@@ -6,8 +6,6 @@ require 'rghost/gs_alone'
|
|
6
6
|
require 'tmpdir'
|
7
7
|
|
8
8
|
RGhost::GSAlone.prepend (Module.new do
|
9
|
-
WindowsRx = /win|ming/
|
10
|
-
|
11
9
|
def initialize params, debug
|
12
10
|
(@params = params.dup).push(*(@params.pop.split File::PATH_SEPARATOR))
|
13
11
|
@debug = debug
|
@@ -60,9 +58,9 @@ module Asciidoctor
|
|
60
58
|
inputs = target
|
61
59
|
end
|
62
60
|
(::RGhost::Convert.new inputs).to :pdf,
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
filename: filename_tmp.to_s,
|
62
|
+
quality: @quality,
|
63
|
+
d: { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
|
66
64
|
filename_o.binwrite filename_tmp.binread
|
67
65
|
end
|
68
66
|
nil
|
@@ -17,23 +17,31 @@ module Asciidoctor
|
|
17
17
|
mod_date = (::Time.parse doc.attr 'docdatetime') rescue (now ||= ::Time.now)
|
18
18
|
creation_date = (::Time.parse doc.attr 'localdatetime') rescue (now || ::Time.now)
|
19
19
|
end
|
20
|
-
|
20
|
+
if (doc.attribute_locked? 'author') && !(doc.attribute_locked? 'authors')
|
21
|
+
author = sanitize doc.attr 'author'
|
22
|
+
elsif doc.attr? 'authors'
|
23
|
+
author = sanitize doc.attr 'authors'
|
24
|
+
elsif doc.attr? 'author' # rubocop:disable Lint/DuplicateBranch
|
25
|
+
author = sanitize doc.attr 'author'
|
26
|
+
end
|
27
|
+
# see https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
|
21
28
|
<<~EOS
|
22
|
-
[ /Title #{(sanitize doc.doctitle
|
23
|
-
/Author #{
|
24
|
-
/Subject #{(doc.attr 'subject').to_pdf_object}
|
25
|
-
/Keywords #{(doc.attr 'keywords').to_pdf_object}
|
29
|
+
[ /Title #{(sanitize doc.header? ? doc.doctitle : (doc.attr 'untitled-label')).to_pdf_object}
|
30
|
+
/Author #{author.to_pdf_object}
|
31
|
+
/Subject #{((doc.attr? 'subject') ? (sanitize doc.attr 'subject') : nil).to_pdf_object}
|
32
|
+
/Keywords #{((doc.attr? 'keywords') ? (sanitize doc.attr 'keywords') : nil).to_pdf_object}
|
26
33
|
/ModDate #{mod_date.to_pdf_object}
|
27
34
|
/CreationDate #{creation_date.to_pdf_object}
|
28
35
|
/Creator (Asciidoctor PDF #{::Asciidoctor::PDF::VERSION}, based on Prawn #{::Prawn::VERSION})
|
29
|
-
/Producer #{(doc.attr 'publisher').to_pdf_object}
|
36
|
+
/Producer #{((doc.attr? 'publisher') ? (sanitize doc.attr 'publisher') : nil).to_pdf_object}
|
30
37
|
/DOCINFO pdfmark
|
31
38
|
EOS
|
32
39
|
end
|
33
40
|
|
34
41
|
def generate_file pdf_file
|
35
|
-
# QUESTION should we use the extension pdfmeta to be more clear?
|
36
|
-
::File.write %(#{pdf_file}mark), generate
|
42
|
+
# QUESTION: should we use the extension pdfmeta to be more clear?
|
43
|
+
::File.write (pdfmark_file = %(#{pdf_file}mark)), generate
|
44
|
+
pdfmark_file
|
37
45
|
end
|
38
46
|
end
|
39
47
|
end
|
@@ -48,15 +48,9 @@ module Asciidoctor
|
|
48
48
|
1000 => 'M',
|
49
49
|
}
|
50
50
|
|
51
|
-
def initialize initial_value, letter_case
|
52
|
-
initial_value
|
53
|
-
|
54
|
-
@integer_value = initial_value
|
55
|
-
else
|
56
|
-
@integer_value = RomanNumeral.roman_to_int initial_value
|
57
|
-
letter_case = :lower if letter_case.nil? && initial_value.upcase != initial_value
|
58
|
-
end
|
59
|
-
@letter_case = letter_case.nil? ? :upper : letter_case
|
51
|
+
def initialize initial_value, letter_case
|
52
|
+
@integer_value = ::Integer === initial_value ? initial_value : (RomanNumeral.roman_to_int initial_value)
|
53
|
+
@letter_case = letter_case
|
60
54
|
end
|
61
55
|
|
62
56
|
def to_s
|
@@ -71,18 +65,6 @@ module Asciidoctor
|
|
71
65
|
@letter_case == :lower ? roman.downcase : roman
|
72
66
|
end
|
73
67
|
|
74
|
-
def to_i
|
75
|
-
@integer_value
|
76
|
-
end
|
77
|
-
|
78
|
-
def odd?
|
79
|
-
to_i.odd?
|
80
|
-
end
|
81
|
-
|
82
|
-
def even?
|
83
|
-
to_i.even?
|
84
|
-
end
|
85
|
-
|
86
68
|
def next
|
87
69
|
RomanNumeral.new @integer_value + 1, @letter_case
|
88
70
|
end
|
@@ -96,7 +78,7 @@ module Asciidoctor
|
|
96
78
|
RomanNumeral.new @integer_value - 1, @letter_case
|
97
79
|
end
|
98
80
|
|
99
|
-
def
|
81
|
+
def nil_or_empty?
|
100
82
|
false
|
101
83
|
end
|
102
84
|
|
@@ -4,29 +4,30 @@ module Asciidoctor
|
|
4
4
|
module PDF
|
5
5
|
module Sanitizer
|
6
6
|
XMLSpecialChars = {
|
7
|
-
'<' =>
|
8
|
-
'>' =>
|
9
|
-
'&' =>
|
7
|
+
'<' => '<',
|
8
|
+
'>' => '>',
|
9
|
+
'&' => '&',
|
10
10
|
}
|
11
|
-
XMLSpecialCharsRx = /(?:#{XMLSpecialChars.keys.join
|
11
|
+
XMLSpecialCharsRx = /(?:#{XMLSpecialChars.keys.join '|'})/
|
12
12
|
InverseXMLSpecialChars = XMLSpecialChars.invert
|
13
13
|
InverseXMLSpecialCharsRx = /[#{InverseXMLSpecialChars.keys.join}]/
|
14
14
|
(BuiltInNamedEntities = {
|
15
|
-
'amp' =>
|
15
|
+
'amp' => '&',
|
16
16
|
'apos' => ?',
|
17
|
-
'gt' =>
|
18
|
-
'lt' =>
|
17
|
+
'gt' => '>',
|
18
|
+
'lt' => '<',
|
19
19
|
'nbsp' => ' ',
|
20
|
-
'quot' =>
|
21
|
-
}).default =
|
20
|
+
'quot' => '"',
|
21
|
+
}).default = '?'
|
22
22
|
SanitizeXMLRx = /<[^>]+>/
|
23
|
-
CharRefRx = /&(?:([a-z][a-z]+\d{0,2})|#(?:(\d\d\d{0,4})|x(
|
23
|
+
CharRefRx = /&(?:amp;)?(?:([a-z][a-z]+\d{0,2})|#(?:(\d\d\d{0,4})|x(\h\h\h{0,3})));/
|
24
|
+
UnescapedAmpersandRx = /&(?!(?:[a-z][a-z]+\d{0,2}|#(?:\d\d\d{0,4}|x\h\h\h{0,3}));)/
|
24
25
|
|
25
26
|
# Strip leading, trailing and repeating whitespace, remove XML tags and
|
26
27
|
# resolve all entities in the specified string.
|
27
28
|
#
|
28
|
-
# FIXME move to a module so we can mix it in elsewhere
|
29
|
-
# FIXME add option to control escaping entities, or a filter mechanism in general
|
29
|
+
# FIXME: move to a module so we can mix it in elsewhere
|
30
|
+
# FIXME: add option to control escaping entities, or a filter mechanism in general
|
30
31
|
def sanitize string
|
31
32
|
string = string.gsub SanitizeXMLRx, '' if string.include? '<'
|
32
33
|
string = string.gsub(CharRefRx) { $1 ? BuiltInNamedEntities[$1] : ([$2 ? $2.to_i : ($3.to_i 16)].pack 'U1') } if string.include? '&'
|
@@ -37,8 +38,12 @@ module Asciidoctor
|
|
37
38
|
string.gsub InverseXMLSpecialCharsRx, InverseXMLSpecialChars
|
38
39
|
end
|
39
40
|
|
41
|
+
def escape_amp string
|
42
|
+
string.gsub UnescapedAmpersandRx, '&'
|
43
|
+
end
|
44
|
+
|
40
45
|
def encode_quotes string
|
41
|
-
(string.include?
|
46
|
+
(string.include? '"') ? (string.gsub '"', '"') : string
|
42
47
|
end
|
43
48
|
end
|
44
49
|
end
|