asciidoctor-pdf 1.5.0.alpha.16 → 1.5.0.alpha.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.yardopts +12 -0
- data/CHANGELOG.adoc +66 -0
- data/LICENSE.adoc +1 -1
- data/README.adoc +221 -68
- data/asciidoctor-pdf.gemspec +41 -42
- data/bin/asciidoctor-pdf +3 -3
- data/data/fonts/mplus1p-regular-fallback.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/default-theme.yml +6 -3
- data/docs/theming-guide.adoc +162 -23
- data/lib/asciidoctor-pdf.rb +2 -1
- data/lib/asciidoctor-pdf/asciidoctor_ext.rb +1 -0
- data/lib/asciidoctor-pdf/asciidoctor_ext/logging_shim.rb +19 -0
- data/lib/asciidoctor-pdf/converter.rb +408 -186
- data/lib/asciidoctor-pdf/core_ext/array.rb +0 -6
- data/lib/asciidoctor-pdf/core_ext/numeric.rb +21 -12
- data/lib/asciidoctor-pdf/core_ext/ostruct.rb +3 -12
- data/lib/asciidoctor-pdf/core_ext/string.rb +1 -1
- data/lib/asciidoctor-pdf/formatted_text.rb +1 -0
- data/lib/asciidoctor-pdf/formatted_text/formatter.rb +8 -2
- data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +1 -1
- data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +18 -32
- data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +3 -3
- data/lib/asciidoctor-pdf/formatted_text/inline_text_aligner.rb +20 -0
- data/lib/asciidoctor-pdf/formatted_text/parser.rb +124 -38
- data/lib/asciidoctor-pdf/formatted_text/parser.treetop +17 -10
- data/lib/asciidoctor-pdf/formatted_text/transform.rb +30 -20
- data/lib/asciidoctor-pdf/implicit_header_processor.rb +2 -2
- data/lib/asciidoctor-pdf/index_catalog.rb +25 -23
- data/lib/asciidoctor-pdf/measurements.rb +1 -1
- data/lib/asciidoctor-pdf/pdf-core_ext/pdf_object.rb +1 -1
- data/lib/asciidoctor-pdf/pdfmark.rb +13 -13
- data/lib/asciidoctor-pdf/prawn-svg_ext.rb +2 -2
- data/lib/asciidoctor-pdf/prawn-svg_ext/interface.rb +2 -2
- data/lib/asciidoctor-pdf/prawn-table_ext.rb +1 -0
- data/lib/asciidoctor-pdf/prawn-table_ext/cell.rb +60 -0
- data/lib/asciidoctor-pdf/prawn-table_ext/cell/text.rb +3 -3
- data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +3 -3
- data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +39 -14
- data/lib/asciidoctor-pdf/prawn_ext/formatted_text/fragment.rb +9 -10
- data/lib/asciidoctor-pdf/prawn_ext/images.rb +2 -2
- data/lib/asciidoctor-pdf/roman_numeral.rb +7 -7
- data/lib/asciidoctor-pdf/rouge_ext.rb +2 -2
- data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +20 -9
- data/lib/asciidoctor-pdf/rouge_ext/themes/{pastie.rb → asciidoctor_pdf_default.rb} +5 -5
- data/lib/asciidoctor-pdf/rouge_ext/themes/bw.rb +38 -0
- data/lib/asciidoctor-pdf/sanitizer.rb +36 -23
- data/lib/asciidoctor-pdf/temporary_path.rb +1 -1
- data/lib/asciidoctor-pdf/theme_loader.rb +17 -14
- data/lib/asciidoctor-pdf/version.rb +3 -2
- data/lib/asciidoctor/pdf.rb +1 -0
- data/lib/asciidoctor/pdf/version.rb +1 -0
- metadata +113 -84
- data/Gemfile +0 -22
- data/Rakefile +0 -81
- data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +0 -15
data/lib/asciidoctor-pdf.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
class StubLogger
|
3
|
+
class << self
|
4
|
+
def warn message
|
5
|
+
::Kernel.warn %(asciidoctor: WARNING: #{message})
|
6
|
+
end
|
7
|
+
|
8
|
+
def error message
|
9
|
+
::Kernel.warn %(asciidoctor: ERROR: #{message})
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module LoggingShim
|
15
|
+
def logger
|
16
|
+
StubLogger
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# encoding:
|
1
|
+
# encoding: utf-8
|
2
2
|
# TODO cleanup imports...decide what belongs in asciidoctor-pdf.rb
|
3
3
|
require 'prawn'
|
4
4
|
require_relative 'ttfunk_ext'
|
@@ -17,7 +17,6 @@ require_relative 'sanitizer'
|
|
17
17
|
require_relative 'prawn_ext'
|
18
18
|
require_relative 'formatted_text'
|
19
19
|
require_relative 'pdfmark'
|
20
|
-
require_relative 'asciidoctor_ext'
|
21
20
|
require_relative 'theme_loader'
|
22
21
|
require_relative 'roman_numeral'
|
23
22
|
require_relative 'index_catalog'
|
@@ -26,9 +25,14 @@ autoload :StringIO, 'stringio'
|
|
26
25
|
autoload :Tempfile, 'tempfile'
|
27
26
|
|
28
27
|
module Asciidoctor
|
29
|
-
module
|
28
|
+
module PDF
|
30
29
|
class Converter < ::Prawn::Document
|
31
30
|
include ::Asciidoctor::Converter
|
31
|
+
if defined? ::Asciidoctor::Logging
|
32
|
+
include ::Asciidoctor::Logging
|
33
|
+
else
|
34
|
+
include ::Asciidoctor::LoggingShim
|
35
|
+
end
|
32
36
|
include ::Asciidoctor::Writer
|
33
37
|
include ::Asciidoctor::Prawn::Extensions
|
34
38
|
|
@@ -40,13 +44,14 @@ class Converter < ::Prawn::Document
|
|
40
44
|
|
41
45
|
AsciidoctorVersion = ::Gem::Version.create ::Asciidoctor::VERSION
|
42
46
|
AdmonitionIcons = {
|
43
|
-
caution: { name: '
|
44
|
-
important: { name: '
|
45
|
-
note: { name: '
|
46
|
-
tip: { name: '
|
47
|
-
warning: { name: '
|
47
|
+
caution: { name: 'fas-fire', stroke_color: 'BF3400', size: 24 },
|
48
|
+
important: { name: 'fas-exclamation-circle', stroke_color: 'BF0000', size: 24 },
|
49
|
+
note: { name: 'fas-info-circle', stroke_color: '19407C', size: 24 },
|
50
|
+
tip: { name: 'far-lightbulb', stroke_color: '111111', size: 24 },
|
51
|
+
warning: { name: 'fas-exclamation-triangle', stroke_color: 'BF6900', size: 24 }
|
48
52
|
}
|
49
|
-
TextAlignmentNames = ['
|
53
|
+
TextAlignmentNames = ['justify', 'left', 'center', 'right']
|
54
|
+
TextAlignmentRoles = ['text-justify', 'text-left', 'text-center', 'text-right']
|
50
55
|
BlockAlignmentNames = ['left', 'center', 'right']
|
51
56
|
AlignmentTable = { '<' => :left, '=' => :center, '>' => :right }
|
52
57
|
ColumnPositions = [:left, :center, :right]
|
@@ -101,8 +106,13 @@ class Converter < ::Prawn::Document
|
|
101
106
|
def initialize backend, opts
|
102
107
|
super
|
103
108
|
basebackend 'html'
|
109
|
+
filetype 'pdf'
|
110
|
+
htmlsyntax 'html'
|
104
111
|
outfilesuffix '.pdf'
|
105
|
-
|
112
|
+
if (doc = opts[:document])
|
113
|
+
# NOTE enabling data-uri forces Asciidoctor Diagram to produce absolute image paths
|
114
|
+
doc.attributes['data-uri'] = ((doc.instance_variable_get :@attribute_overrides) || {})['data-uri'] = ''
|
115
|
+
end
|
106
116
|
@list_numbers = []
|
107
117
|
@list_bullets = []
|
108
118
|
@capabilities = {
|
@@ -118,7 +128,7 @@ class Converter < ::Prawn::Document
|
|
118
128
|
result = send method_name, node
|
119
129
|
else
|
120
130
|
# TODO delegate to convert_method_missing
|
121
|
-
warn %(
|
131
|
+
logger.warn %(conversion missing in backend #{@backend} for #{name})
|
122
132
|
end
|
123
133
|
# NOTE inline nodes generate pseudo-HTML strings; the remainder write directly to PDF object
|
124
134
|
::Asciidoctor::Inline === node ? result : self
|
@@ -141,8 +151,6 @@ class Converter < ::Prawn::Document
|
|
141
151
|
|
142
152
|
def convert_document doc
|
143
153
|
init_pdf doc
|
144
|
-
# data-uri doesn't apply to PDF, so explicitly disable (is there a better place?)
|
145
|
-
doc.attributes.delete 'data-uri'
|
146
154
|
# set default value for pagenums if not otherwise set
|
147
155
|
unless (doc.attribute_locked? 'pagenums') || ((doc.instance_variable_get :@attributes_modified).include? 'pagenums')
|
148
156
|
doc.attributes['pagenums'] = ''
|
@@ -182,37 +190,80 @@ class Converter < ::Prawn::Document
|
|
182
190
|
end if respond_to? :on_page_create
|
183
191
|
|
184
192
|
layout_cover_page :front, doc
|
185
|
-
|
193
|
+
if (insert_title_page = doc.doctype == 'book' || (doc.attr? 'title-page'))
|
194
|
+
layout_title_page doc
|
195
|
+
# NOTE a new page will already be started if the cover image is a PDF
|
196
|
+
start_new_page unless page_is_empty?
|
197
|
+
else
|
198
|
+
# NOTE a new page will already be started if the cover image is a PDF
|
199
|
+
start_new_page unless page_is_empty?
|
200
|
+
body_start_page_number = page_number
|
201
|
+
if doc.header? && !doc.notitle
|
202
|
+
theme_font :heading, level: 1 do
|
203
|
+
layout_heading doc.doctitle, align: (@theme.heading_h1_align || :center).to_sym, level: 1
|
204
|
+
end
|
205
|
+
toc_start = @y
|
206
|
+
end
|
207
|
+
end
|
186
208
|
|
187
|
-
# NOTE
|
188
|
-
|
209
|
+
# NOTE font must be set before toc dry run to ensure dry run size is accurate
|
210
|
+
font @theme.base_font_family, size: @theme.base_font_size, style: @theme.base_font_style.to_sym
|
189
211
|
|
190
212
|
num_toc_levels = (doc.attr 'toclevels', 2).to_i
|
191
213
|
if (insert_toc = (doc.attr? 'toc') && doc.sections?)
|
192
214
|
start_new_page if @ppbook && verso_page?
|
193
215
|
toc_page_nums = page_number
|
194
|
-
|
216
|
+
toc_end = nil
|
217
|
+
dry_run do
|
218
|
+
toc_page_nums = layout_toc doc, num_toc_levels, toc_page_nums, 0, toc_start
|
219
|
+
move_down @theme.block_margin_bottom unless insert_title_page
|
220
|
+
toc_end = @y
|
221
|
+
end
|
195
222
|
# NOTE reserve pages for the toc; leaves cursor on page after last page in toc
|
196
|
-
|
223
|
+
if insert_title_page
|
224
|
+
toc_page_nums.each { start_new_page }
|
225
|
+
else
|
226
|
+
(toc_page_nums.first...toc_page_nums.last).each { start_new_page }
|
227
|
+
@y = toc_end
|
228
|
+
end
|
197
229
|
end
|
198
230
|
|
199
231
|
# FIXME only apply to book doctype once title and toc are moved to start page when using article doctype
|
200
232
|
#start_new_page if @ppbook && verso_page?
|
201
233
|
start_new_page if @media == 'prepress' && verso_page?
|
202
234
|
|
203
|
-
|
204
|
-
|
235
|
+
if insert_title_page
|
236
|
+
body_start_page_number = page_number
|
237
|
+
# NOTE start running content from title or toc, if specified (default: body)
|
238
|
+
if @theme.running_content_start_at == 'title'
|
239
|
+
num_front_matter_pages = 0
|
240
|
+
elsif insert_toc && @theme.running_content_start_at == 'toc'
|
241
|
+
num_front_matter_pages = 1
|
242
|
+
else # body
|
243
|
+
num_front_matter_pages = body_start_page_number - 1
|
244
|
+
end
|
245
|
+
else
|
246
|
+
num_front_matter_pages = body_start_page_number - 1
|
247
|
+
end
|
248
|
+
|
249
|
+
@index.start_page_number = num_front_matter_pages + 1
|
205
250
|
doc.set_attr 'pdf-anchor', (doc_anchor = derive_anchor_from_id doc.id, 'top')
|
206
251
|
add_dest_for_block doc, doc_anchor
|
252
|
+
|
253
|
+
convert_section generate_manname_section doc if doc.doctype == 'manpage' && (doc.attr? 'manpurpose')
|
254
|
+
|
207
255
|
convert_content_for_block doc
|
208
256
|
|
257
|
+
# NOTE for a book, these are leftover footnotes; for an article this is everything
|
258
|
+
layout_footnotes doc
|
259
|
+
|
209
260
|
# NOTE delete orphaned page (a page was created but there was no additional content)
|
210
261
|
# QUESTION should we delete page if document is empty? (leaving no pages?)
|
211
262
|
delete_page if page_is_empty? && page_count > 1
|
212
263
|
|
213
|
-
toc_page_nums = insert_toc ? (layout_toc doc, num_toc_levels, toc_page_nums.first, num_front_matter_pages) : []
|
264
|
+
toc_page_nums = insert_toc ? (layout_toc doc, num_toc_levels, toc_page_nums.first, num_front_matter_pages, toc_start) : []
|
214
265
|
|
215
|
-
|
266
|
+
unless page_count < body_start_page_number
|
216
267
|
unless doc.noheader || @theme.header_height.to_f.zero?
|
217
268
|
layout_running_content :header, doc, skip: num_front_matter_pages
|
218
269
|
end
|
@@ -228,16 +279,17 @@ class Converter < ::Prawn::Document
|
|
228
279
|
catalog.data[:ViewerPreferences] = { DisplayDocTitle: true }
|
229
280
|
|
230
281
|
layout_cover_page :back, doc
|
282
|
+
nil
|
231
283
|
end
|
232
284
|
|
233
285
|
# NOTE embedded only makes sense if perhaps we are building
|
234
286
|
# on an existing Prawn::Document instance; for now, just treat
|
235
287
|
# it the same as a full document.
|
236
|
-
alias
|
288
|
+
alias convert_embedded convert_document
|
237
289
|
|
238
290
|
# TODO only allow method to be called once (or we need a reset)
|
239
291
|
def init_pdf doc
|
240
|
-
|
292
|
+
theme = load_theme doc
|
241
293
|
pdf_opts = build_pdf_options doc, theme
|
242
294
|
# QUESTION should page options be preserved (otherwise, not readily available)
|
243
295
|
#@page_opts = { size: pdf_opts[:page_size], layout: pdf_opts[:page_layout] }
|
@@ -252,7 +304,7 @@ class Converter < ::Prawn::Document
|
|
252
304
|
if (page_margin_inner = theme.page_margin_inner)
|
253
305
|
page_margin_recto[3] = @page_margin_by_side[:verso][1] = page_margin_inner
|
254
306
|
end
|
255
|
-
# NOTE prepare scratch document to use page margin from recto side
|
307
|
+
# NOTE prepare scratch document to use page margin from recto side (which has same width as verso side)
|
256
308
|
set_page_margin page_margin_recto unless page_margin_recto == page_margin
|
257
309
|
else
|
258
310
|
@ppbook = false
|
@@ -269,6 +321,7 @@ class Converter < ::Prawn::Document
|
|
269
321
|
@font_color = theme.base_font_color
|
270
322
|
@base_align = (align = doc.attr 'text-alignment') && (TextAlignmentNames.include? align) ? align : theme.base_align
|
271
323
|
@text_transform = nil
|
324
|
+
@footnotes = []
|
272
325
|
@index = IndexCatalog.new
|
273
326
|
# NOTE we have to init Pdfmark class here while we have reference to the doc
|
274
327
|
@pdfmark = (doc.attr? 'pdfmark') ? (Pdfmark.new doc) : nil
|
@@ -276,6 +329,10 @@ class Converter < ::Prawn::Document
|
|
276
329
|
self
|
277
330
|
end
|
278
331
|
|
332
|
+
def load_theme doc
|
333
|
+
@theme ||= doc.options[:pdf_theme] || ThemeLoader.load_theme((doc.attr 'pdf-style'), (doc.attr 'pdf-stylesdir'))
|
334
|
+
end
|
335
|
+
|
279
336
|
def build_pdf_options doc, theme
|
280
337
|
case (page_margin = (doc.attr 'pdf-page-margin') || theme.page_margin)
|
281
338
|
when ::Array
|
@@ -333,7 +390,7 @@ class Converter < ::Prawn::Document
|
|
333
390
|
dim > 0 ? dim : break
|
334
391
|
elsif ::String === dim && (m = (MeasurementPartsRx.match dim))
|
335
392
|
# NOTE truncate to max precision retained by PDF::Core
|
336
|
-
(to_pt m[1].to_f, m[2]).
|
393
|
+
(to_pt m[1].to_f, m[2]).truncate 4
|
337
394
|
else
|
338
395
|
break
|
339
396
|
end
|
@@ -357,7 +414,7 @@ class Converter < ::Prawn::Document
|
|
357
414
|
}
|
358
415
|
end
|
359
416
|
|
360
|
-
# FIXME
|
417
|
+
# FIXME Pdfmark should use the PDF info result
|
361
418
|
def build_pdf_info doc
|
362
419
|
info = {}
|
363
420
|
# FIXME use sanitize: :plain_text once available
|
@@ -374,7 +431,7 @@ class Converter < ::Prawn::Document
|
|
374
431
|
if (doc.attr? 'publisher')
|
375
432
|
info[:Producer] = (doc.attr 'publisher').as_pdf
|
376
433
|
end
|
377
|
-
info[:Creator] = %(Asciidoctor PDF #{::Asciidoctor::
|
434
|
+
info[:Creator] = %(Asciidoctor PDF #{::Asciidoctor::PDF::VERSION}, based on Prawn #{::Prawn::VERSION}).as_pdf
|
378
435
|
info[:Producer] ||= (info[:Author] || info[:Creator])
|
379
436
|
unless doc.attr? 'reproducible'
|
380
437
|
# NOTE since we don't track the creation date of the input file, we map the ModDate header to the last modified
|
@@ -392,10 +449,10 @@ class Converter < ::Prawn::Document
|
|
392
449
|
return convert_abstract sect
|
393
450
|
end
|
394
451
|
|
452
|
+
type = nil
|
395
453
|
theme_font :heading, level: (hlevel = sect.level + 1) do
|
396
454
|
title = sect.numbered_title formal: true
|
397
455
|
align = (@theme[%(heading_h#{hlevel}_align)] || @theme.heading_align || @base_align).to_sym
|
398
|
-
type = nil
|
399
456
|
if sect.part_or_chapter?
|
400
457
|
if sect.chapter?
|
401
458
|
type = :chapter
|
@@ -415,23 +472,40 @@ class Converter < ::Prawn::Document
|
|
415
472
|
sect.set_attr 'pdf-anchor', (sect_anchor = derive_anchor_from_id sect.id, %(#{start_pgnum}-#{y.ceil}))
|
416
473
|
add_dest_for_block sect, sect_anchor
|
417
474
|
if type == :part
|
418
|
-
layout_part_title sect, title, align: align
|
475
|
+
layout_part_title sect, title, align: align, level: hlevel
|
419
476
|
elsif type == :chapter
|
420
|
-
layout_chapter_title sect, title, align: align
|
477
|
+
layout_chapter_title sect, title, align: align, level: hlevel
|
421
478
|
else
|
422
|
-
layout_heading title, align: align
|
479
|
+
layout_heading title, align: align, level: hlevel
|
423
480
|
end
|
424
481
|
end
|
425
482
|
|
426
483
|
sect.sectname == 'index' ? (convert_index_section sect) : (convert_content_for_block sect)
|
484
|
+
layout_footnotes sect if type == :chapter
|
427
485
|
sect.set_attr 'pdf-page-end', page_number
|
428
486
|
end
|
429
487
|
|
488
|
+
# QUESTION if a footnote ref appears in a separate chapter, should the footnote def be duplicated?
|
489
|
+
def layout_footnotes node
|
490
|
+
return if (fns = (doc = node.document).footnotes - @footnotes).empty?
|
491
|
+
theme_margin :footnotes, :top
|
492
|
+
theme_font :footnotes do
|
493
|
+
# FIXME layout_caption resets the theme font for footnotes
|
494
|
+
(title = doc.attr 'footnotes-title') && (layout_caption title)
|
495
|
+
item_spacing = @theme.footnotes_item_spacing || 0
|
496
|
+
fns.each do |fn|
|
497
|
+
layout_prose %(<a name="_footnotedef_#{index = fn.index}">#{DummyText}</a>[<a anchor="_footnoteref_#{index}">#{index}</a>] #{fn.text}), margin_bottom: item_spacing
|
498
|
+
end
|
499
|
+
@footnotes += fns
|
500
|
+
end
|
501
|
+
nil
|
502
|
+
end
|
503
|
+
|
430
504
|
def convert_floating_title node
|
431
505
|
add_dest_for_block node if node.id
|
432
506
|
# QUESTION should we decouple styles from section titles?
|
433
507
|
theme_font :heading, level: (hlevel = node.level + 1) do
|
434
|
-
layout_heading node.title, align: (@theme[%(heading_h#{hlevel}_align)] || @theme.heading_align || @base_align).to_sym
|
508
|
+
layout_heading node.title, align: (@theme[%(heading_h#{hlevel}_align)] || @theme.heading_align || @base_align).to_sym, level: hlevel
|
435
509
|
end
|
436
510
|
end
|
437
511
|
|
@@ -444,7 +518,7 @@ class Converter < ::Prawn::Document
|
|
444
518
|
end
|
445
519
|
end
|
446
520
|
theme_font :abstract do
|
447
|
-
prose_opts = { line_height: @theme.abstract_line_height }
|
521
|
+
prose_opts = { line_height: @theme.abstract_line_height, align: (@theme.abstract_align || @base_align).to_sym }
|
448
522
|
# FIXME control more first_line_options using theme
|
449
523
|
if (line1_font_style = @theme.abstract_first_line_font_style) && line1_font_style.to_sym != font_style
|
450
524
|
prose_opts[:first_line_options] = { styles: [font_style, line1_font_style.to_sym] }
|
@@ -482,30 +556,17 @@ class Converter < ::Prawn::Document
|
|
482
556
|
# TODO add prose around image logic (use role to add special logic for headshot)
|
483
557
|
def convert_paragraph node
|
484
558
|
add_dest_for_block node if node.id
|
485
|
-
is_lead = false
|
486
559
|
prose_opts = {}
|
487
|
-
node.roles.
|
488
|
-
|
489
|
-
|
490
|
-
prose_opts[:align] = :left
|
491
|
-
when 'text-right'
|
492
|
-
prose_opts[:align] = :right
|
493
|
-
when 'text-justify'
|
494
|
-
prose_opts[:align] = :justify
|
495
|
-
when 'text-center'
|
496
|
-
prose_opts[:align] = :center
|
497
|
-
when 'lead'
|
498
|
-
is_lead = true
|
499
|
-
#when 'signature'
|
500
|
-
# prose_opts[:size] = @theme.base_font_size_small
|
501
|
-
end
|
560
|
+
lead = (roles = node.roles).include? 'lead'
|
561
|
+
if (align = resolve_alignment_from_role roles)
|
562
|
+
prose_opts[:align] = align
|
502
563
|
end
|
503
564
|
|
504
565
|
# TODO check if we're within one line of the bottom of the page
|
505
566
|
# and advance to the next page if so (similar to logic for section titles)
|
506
567
|
layout_caption node.title if node.title?
|
507
568
|
|
508
|
-
if
|
569
|
+
if lead
|
509
570
|
theme_font :lead do
|
510
571
|
layout_prose node.content, prose_opts
|
511
572
|
end
|
@@ -526,7 +587,10 @@ class Converter < ::Prawn::Document
|
|
526
587
|
if (label_min_width = @theme.admonition_label_min_width)
|
527
588
|
label_min_width = label_min_width.to_f
|
528
589
|
end
|
529
|
-
icons = (node.document.attr? 'icons') ? (
|
590
|
+
icons = ((doc = node.document).attr? 'icons') ? (doc.attr 'icons') : false
|
591
|
+
if (data_uri_enabled = doc.attr? 'data-uri')
|
592
|
+
doc.remove_attr 'data-uri'
|
593
|
+
end
|
530
594
|
if icons == 'font' && !(node.attr? 'icon', nil, false)
|
531
595
|
icon_data = admonition_icon_data(label_text = type.to_sym)
|
532
596
|
label_width = label_min_width ? label_min_width : (icon_data[:size] * 1.5)
|
@@ -538,7 +602,7 @@ class Converter < ::Prawn::Document
|
|
538
602
|
else
|
539
603
|
if icons
|
540
604
|
icons = false
|
541
|
-
warn %(
|
605
|
+
logger.warn %(admonition icon image not found or not readable: #{icon_path}) unless scratch?
|
542
606
|
end
|
543
607
|
label_text = node.caption
|
544
608
|
theme_font :admonition_label do
|
@@ -551,6 +615,7 @@ class Converter < ::Prawn::Document
|
|
551
615
|
end
|
552
616
|
end
|
553
617
|
end
|
618
|
+
doc.set_attr 'data-uri', '' if data_uri_enabled
|
554
619
|
unless ::Array === (cpad = @theme.admonition_padding)
|
555
620
|
cpad = ::Array.new 4, cpad
|
556
621
|
end
|
@@ -593,19 +658,40 @@ class Converter < ::Prawn::Document
|
|
593
658
|
color: icon_data[:stroke_color],
|
594
659
|
size: icon_size
|
595
660
|
elsif icons
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
661
|
+
if icon_path.end_with? '.svg'
|
662
|
+
begin
|
663
|
+
svg_obj = ::Prawn::SVG::Interface.new ::File.read(icon_path), self,
|
664
|
+
position: label_align,
|
665
|
+
vposition: label_valign,
|
666
|
+
width: label_width,
|
667
|
+
height: box_height,
|
668
|
+
fallback_font_name: default_svg_font,
|
669
|
+
enable_web_requests: (doc.attr? 'allow-uri-read'),
|
670
|
+
enable_file_requests_with_root: (::File.dirname icon_path)
|
671
|
+
if (icon_height = (svg_size = svg_obj.document.sizing).output_height) > box_height
|
672
|
+
icon_width = (svg_obj.resize height: (icon_height = box_height)).output_width
|
673
|
+
else
|
674
|
+
icon_width = svg_size.output_width
|
675
|
+
end
|
676
|
+
svg_obj.draw
|
677
|
+
rescue
|
678
|
+
logger.warn %(could not embed admonition icon image: #{icon_path}; #{$!.message})
|
679
|
+
end
|
680
|
+
else
|
681
|
+
begin
|
682
|
+
image_obj, image_info = build_image_object icon_path
|
683
|
+
icon_aspect_ratio = image_info.width.fdiv image_info.height
|
684
|
+
# NOTE don't scale image up if smaller than label_width
|
685
|
+
icon_width = [(to_pt image_info.width, :px), label_width].min
|
686
|
+
if (icon_height = icon_width * (1 / icon_aspect_ratio)) > box_height
|
687
|
+
icon_width *= box_height / icon_height
|
688
|
+
icon_height = box_height
|
689
|
+
end
|
690
|
+
embed_image image_obj, image_info, width: icon_width, position: label_align, vposition: label_valign
|
691
|
+
rescue
|
692
|
+
# QUESTION should we show the label in this case?
|
693
|
+
logger.warn %(could not embed admonition icon image: #{icon_path}; #{$!.message})
|
604
694
|
end
|
605
|
-
embed_image image_obj, image_info, width: icon_width, position: label_align, vposition: label_valign
|
606
|
-
rescue => e
|
607
|
-
# QUESTION should we show the label in this case?
|
608
|
-
warn %(asciidoctor: WARNING: could not embed admonition icon image: #{icon_path}; #{e.message})
|
609
695
|
end
|
610
696
|
else
|
611
697
|
# IMPORTANT the label must fit in the alotted space or it shows up on another page!
|
@@ -741,8 +827,8 @@ class Converter < ::Prawn::Document
|
|
741
827
|
theme_margin :block, :bottom
|
742
828
|
end
|
743
829
|
|
744
|
-
alias
|
745
|
-
alias
|
830
|
+
alias convert_quote convert_quote_or_verse
|
831
|
+
alias convert_verse convert_quote_or_verse
|
746
832
|
|
747
833
|
def convert_sidebar node
|
748
834
|
add_dest_for_block node if node.id
|
@@ -830,7 +916,7 @@ class Converter < ::Prawn::Document
|
|
830
916
|
line_metrics = calc_line_metrics @theme.base_line_height
|
831
917
|
node.items.each_with_index do |item, idx|
|
832
918
|
# FIXME extract to an ensure_space (or similar) method; simplify
|
833
|
-
advance_page if cursor < (line_metrics.height + line_metrics.leading + line_metrics.padding_top)
|
919
|
+
advance_page if cursor < (line_metrics.height + line_metrics.leading + line_metrics.padding_top) + 1
|
834
920
|
convert_colist_item item
|
835
921
|
end
|
836
922
|
@list_numbers.pop
|
@@ -871,7 +957,18 @@ class Converter < ::Prawn::Document
|
|
871
957
|
# FIXME extract ensure_space (or similar) method
|
872
958
|
advance_page if cursor < @theme.base_line_height_length * (terms.size + 1)
|
873
959
|
terms.each do |term|
|
874
|
-
layout_prose
|
960
|
+
# FIXME layout_prose should pass style downward when parsing formatted text
|
961
|
+
#layout_prose term.text, style: @theme.description_list_term_font_style.to_sym, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
|
962
|
+
term_text = term.text
|
963
|
+
case @theme.description_list_term_font_style.to_sym
|
964
|
+
when :bold
|
965
|
+
term_text = %(<strong>#{term_text}</strong>)
|
966
|
+
when :italic
|
967
|
+
term_text = %(<em>#{term_text}</em>)
|
968
|
+
when :bold_italic
|
969
|
+
term_text = %(<strong><em>#{term_text}</em></strong>)
|
970
|
+
end
|
971
|
+
layout_prose term_text, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
|
875
972
|
end
|
876
973
|
if desc
|
877
974
|
indent @theme.description_list_description_indent do
|
@@ -926,9 +1023,9 @@ class Converter < ::Prawn::Document
|
|
926
1023
|
nil
|
927
1024
|
else
|
928
1025
|
if Bullets.key?(candidate = style.to_sym)
|
929
|
-
candidate
|
1026
|
+
candidate
|
930
1027
|
else
|
931
|
-
warn %(
|
1028
|
+
logger.warn %(unknown unordered list style: #{candidate})
|
932
1029
|
:disc
|
933
1030
|
end
|
934
1031
|
end
|
@@ -942,7 +1039,7 @@ class Converter < ::Prawn::Document
|
|
942
1039
|
:square
|
943
1040
|
end
|
944
1041
|
end
|
945
|
-
@list_bullets <<
|
1042
|
+
@list_bullets << bullet_type
|
946
1043
|
end
|
947
1044
|
convert_outline_list node
|
948
1045
|
@list_bullets.pop
|
@@ -953,6 +1050,16 @@ class Converter < ::Prawn::Document
|
|
953
1050
|
# and advance to the next page if so (similar to logic for section titles)
|
954
1051
|
layout_caption node.title if node.title?
|
955
1052
|
|
1053
|
+
opts = {}
|
1054
|
+
if (align = resolve_alignment_from_role node.roles)
|
1055
|
+
opts[:align] = align
|
1056
|
+
elsif node.style == 'bibliography'
|
1057
|
+
opts[:align] = :left
|
1058
|
+
elsif (align = @theme.outline_list_text_align)
|
1059
|
+
# NOTE theme setting only affects alignment of list text (not nested blocks)
|
1060
|
+
opts[:align] = align.to_sym
|
1061
|
+
end
|
1062
|
+
|
956
1063
|
line_metrics = calc_line_metrics @theme.base_line_height
|
957
1064
|
complex = false
|
958
1065
|
# ...or if we want to give all items in the list the same treatment
|
@@ -972,7 +1079,7 @@ class Converter < ::Prawn::Document
|
|
972
1079
|
node.items.each do |item|
|
973
1080
|
# FIXME extract to an ensure_space (or similar) method; simplify
|
974
1081
|
advance_page if cursor < (line_metrics.height + line_metrics.leading + line_metrics.padding_top)
|
975
|
-
convert_outline_list_item item, item.complex
|
1082
|
+
convert_outline_list_item item, item.complex?, opts
|
976
1083
|
end
|
977
1084
|
end
|
978
1085
|
# NOTE Children will provide the necessary bottom margin if last item is complex.
|
@@ -984,57 +1091,71 @@ class Converter < ::Prawn::Document
|
|
984
1091
|
end
|
985
1092
|
end
|
986
1093
|
|
987
|
-
def convert_outline_list_item node, complex = false
|
1094
|
+
def convert_outline_list_item node, complex = false, opts = {}
|
988
1095
|
# TODO move this to a draw_bullet (or draw_marker) method
|
1096
|
+
marker_style = {}
|
1097
|
+
marker_style[:font_color] = @theme.outline_list_marker_font_color || @font_color
|
1098
|
+
marker_style[:font_family] = font_family
|
1099
|
+
marker_style[:font_size] = font_size
|
1100
|
+
marker_style[:line_height] = @theme.base_line_height
|
989
1101
|
case (list_type = node.parent.context)
|
990
1102
|
when :ulist
|
991
|
-
|
992
|
-
if
|
1103
|
+
marker_type = @list_bullets[-1]
|
1104
|
+
if marker_type == :checkbox
|
1105
|
+
# QUESTION should we remove marker indent if not a checkbox?
|
993
1106
|
if node.attr? 'checkbox', nil, false
|
994
|
-
|
995
|
-
|
996
|
-
# QUESTION should we remove marker indent in this case?
|
997
|
-
marker = nil
|
1107
|
+
marker_type = (node.attr? 'checked', nil, false) ? :checked : :unchecked
|
1108
|
+
marker = @theme[%(ulist_marker_#{marker_type}_content)] || BallotBox[marker_type]
|
998
1109
|
end
|
1110
|
+
else
|
1111
|
+
marker = @theme[%(ulist_marker_#{marker_type}_content)] || Bullets[marker_type]
|
999
1112
|
end
|
1113
|
+
[:font_color, :font_family, :font_size, :line_height].each do |prop|
|
1114
|
+
marker_style[prop] = @theme[%(ulist_marker_#{marker_type}_#{prop})] || @theme[%(ulist_marker_#{prop})] || marker_style[prop]
|
1115
|
+
end if marker
|
1000
1116
|
when :olist
|
1001
1117
|
dir = (node.parent.option? 'reversed') ? :pred : :next
|
1002
1118
|
@list_numbers << ((index = @list_numbers.pop).public_send dir)
|
1003
1119
|
marker = %(#{index}.)
|
1004
1120
|
else
|
1005
|
-
warn %(
|
1006
|
-
marker = Bullets[:disc]
|
1121
|
+
logger.warn %(unknown list type #{list_type.inspect})
|
1122
|
+
marker = @theme.ulist_marker_disc_content || Bullets[:disc]
|
1007
1123
|
end
|
1008
1124
|
|
1009
1125
|
if marker
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1126
|
+
if marker_style[:font_family] == 'fa'
|
1127
|
+
logger.info { 'deprecated fa icon set found in theme; use fas, far, or fab instead' }
|
1128
|
+
marker_style[:font_family] = FontAwesomeIconSets.find {|candidate| (icon_font_data candidate).yaml[candidate].value? marker } || 'fas'
|
1129
|
+
end
|
1130
|
+
marker_gap = rendered_width_of_char 'x'
|
1131
|
+
font marker_style[:font_family], size: marker_style[:font_size] do
|
1132
|
+
marker_width = rendered_width_of_string marker
|
1133
|
+
start_position = -marker_width + -marker_gap
|
1134
|
+
float do
|
1135
|
+
flow_bounding_box start_position, width: marker_width do
|
1136
|
+
layout_prose marker,
|
1137
|
+
align: :right,
|
1138
|
+
character_spacing: -0.5,
|
1139
|
+
color: marker_style[:font_color],
|
1140
|
+
inline_format: false,
|
1141
|
+
line_height: marker_style[:line_height],
|
1142
|
+
margin: 0,
|
1143
|
+
normalize: false,
|
1144
|
+
single_line: true
|
1145
|
+
end
|
1022
1146
|
end
|
1023
1147
|
end
|
1024
1148
|
end
|
1025
1149
|
|
1026
1150
|
if complex
|
1027
|
-
convert_content_for_list_item node
|
1151
|
+
convert_content_for_list_item node, opts
|
1028
1152
|
else
|
1029
|
-
convert_content_for_list_item node, margin_bottom: @theme.outline_list_item_spacing
|
1153
|
+
convert_content_for_list_item node, (opts.merge margin_bottom: @theme.outline_list_item_spacing)
|
1030
1154
|
end
|
1031
1155
|
end
|
1032
1156
|
|
1033
1157
|
def convert_content_for_list_item node, opts = {}
|
1034
|
-
if node.text?
|
1035
|
-
opts[:align] = :left if node.parent.style == 'bibliography'
|
1036
|
-
layout_prose node.text, opts
|
1037
|
-
end
|
1158
|
+
layout_prose node.text, opts if node.text?
|
1038
1159
|
convert_content_for_block node
|
1039
1160
|
end
|
1040
1161
|
|
@@ -1043,7 +1164,7 @@ class Converter < ::Prawn::Document
|
|
1043
1164
|
target, image_format = node.target_and_format
|
1044
1165
|
|
1045
1166
|
if image_format == 'gif' && !(defined? ::GMagick::Image)
|
1046
|
-
warn %(
|
1167
|
+
logger.warn %(GIF image format not supported. Install the prawn-gmagick gem or convert #{target} to PNG.) unless scratch?
|
1047
1168
|
image_path = false
|
1048
1169
|
elsif ::Base64 === target
|
1049
1170
|
image_path = target
|
@@ -1053,7 +1174,7 @@ class Converter < ::Prawn::Document
|
|
1053
1174
|
# QUESTION should we add destination to top of imported page?
|
1054
1175
|
return import_page image_path, replace: page_is_empty? if image_format == 'pdf'
|
1055
1176
|
else
|
1056
|
-
warn %(
|
1177
|
+
logger.warn %(image to embed not found or not readable: #{image_path || target}) unless scratch?
|
1057
1178
|
# QUESTION should we use alt text in this case?
|
1058
1179
|
return if image_format == 'pdf'
|
1059
1180
|
image_path = false
|
@@ -1074,21 +1195,22 @@ class Converter < ::Prawn::Document
|
|
1074
1195
|
# TODO support cover (aka canvas) image layout using "canvas" (or "cover") role
|
1075
1196
|
width = resolve_explicit_width node.attributes, (available_w = bounds.width), support_vw: true, use_fallback: true
|
1076
1197
|
# TODO add `to_pt page_width` method to ViewportWidth type
|
1077
|
-
width = (width.to_f / 100) * page_width if
|
1198
|
+
width = (width.to_f / 100) * page_width if ViewportWidth === width
|
1078
1199
|
|
1079
1200
|
alignment = ((node.attr 'align', nil, false) || @theme.image_align).to_sym
|
1201
|
+
align_to_page = node.option? 'align-to-page'
|
1080
1202
|
|
1081
1203
|
begin
|
1082
|
-
span_page_width_if
|
1204
|
+
span_page_width_if align_to_page do
|
1083
1205
|
if image_format == 'svg'
|
1084
1206
|
if ::Base64 === image_path
|
1085
1207
|
svg_data = ::Base64.decode64 image_path
|
1086
1208
|
file_request_root = false
|
1087
1209
|
else
|
1088
|
-
svg_data = ::
|
1210
|
+
svg_data = ::File.read image_path
|
1089
1211
|
file_request_root = ::File.dirname image_path
|
1090
1212
|
end
|
1091
|
-
svg_obj = ::Prawn::
|
1213
|
+
svg_obj = ::Prawn::SVG::Interface.new svg_data, self,
|
1092
1214
|
position: alignment,
|
1093
1215
|
width: width,
|
1094
1216
|
fallback_font_name: default_svg_font,
|
@@ -1160,15 +1282,15 @@ class Converter < ::Prawn::Document
|
|
1160
1282
|
end
|
1161
1283
|
layout_caption node, side: :bottom if node.title?
|
1162
1284
|
theme_margin :block, :bottom unless pinned
|
1163
|
-
rescue
|
1164
|
-
on_image_error :exception, node, target, (opts.merge message: %(
|
1285
|
+
rescue
|
1286
|
+
on_image_error :exception, node, target, (opts.merge message: %(could not embed image: #{image_path}; #{$!.message}))
|
1165
1287
|
end
|
1166
1288
|
ensure
|
1167
1289
|
unlink_tmp_file image_path if image_path
|
1168
1290
|
end
|
1169
1291
|
|
1170
1292
|
def on_image_error reason, node, target, opts = {}
|
1171
|
-
warn opts[:message] if opts.key? :message
|
1293
|
+
logger.warn opts[:message] if opts.key? :message
|
1172
1294
|
alt_text = (link = node.attr 'link', nil, false) ?
|
1173
1295
|
%(<a href="#{link}">[#{node.attr 'alt'}]</a> | <em>#{target}</em>) :
|
1174
1296
|
%([#{node.attr 'alt'}] | <em>#{target}</em>)
|
@@ -1186,8 +1308,7 @@ class Converter < ::Prawn::Document
|
|
1186
1308
|
add_dest_for_block node if node.id
|
1187
1309
|
theme_margin :block, :top
|
1188
1310
|
audio_path = node.media_uri(node.attr 'target')
|
1189
|
-
play_symbol = (node.document.attr? 'icons', 'font') ?
|
1190
|
-
%(<font name="fa">#{::Prawn::Icon::FontData.load(self, 'fa').unicode 'play'}</font>) : RightPointer
|
1311
|
+
play_symbol = (node.document.attr? 'icons', 'font') ? %(<font name="fas">#{(icon_font_data 'fas').unicode 'play'}</font>) : RightPointer
|
1191
1312
|
layout_prose %(#{play_symbol}#{NoBreakSpace}<a href="#{audio_path}">#{audio_path}</a> <em>(audio)</em>), normalize: false, margin: 0, single_line: true
|
1192
1313
|
layout_caption node, side: :bottom if node.title?
|
1193
1314
|
theme_margin :block, :bottom
|
@@ -1223,8 +1344,7 @@ class Converter < ::Prawn::Document
|
|
1223
1344
|
if poster.nil_or_empty?
|
1224
1345
|
add_dest_for_block node if node.id
|
1225
1346
|
theme_margin :block, :top
|
1226
|
-
play_symbol = (node.document.attr? 'icons', 'font') ?
|
1227
|
-
%(<font name="fa">#{::Prawn::Icon::FontData.load(self, 'fa').unicode 'play'}</font>) : RightPointer
|
1347
|
+
play_symbol = (node.document.attr? 'icons', 'font') ? %(<font name="fas">#{(icon_font_data 'fas').unicode 'play'}</font>) : RightPointer
|
1228
1348
|
layout_prose %(#{play_symbol}#{NoBreakSpace}<a href="#{video_path}">#{video_path}</a> <em>(#{type})</em>), normalize: false, margin: 0, single_line: true
|
1229
1349
|
layout_caption node, side: :bottom if node.title?
|
1230
1350
|
theme_margin :block, :bottom
|
@@ -1320,30 +1440,32 @@ class Converter < ::Prawn::Document
|
|
1320
1440
|
source_string, conum_mapping = extract_conums source_string
|
1321
1441
|
# NOTE pygments.rb strips trailing whitespace; preserve it in case there are conums on last line
|
1322
1442
|
num_trailing_spaces = source_string.length - (source_string = source_string.rstrip).length if conum_mapping
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1443
|
+
# NOTE highlight can return nil if something goes wrong; fallback to encoded source string if this happens
|
1444
|
+
result = (lexer.highlight source_string, options: lexer_opts) || (node.apply_subs source_string, [:specialcharacters])
|
1445
|
+
if (linenums = node.attr? 'linenums')
|
1446
|
+
linenums = (node.attr 'start', 1, false).to_i
|
1447
|
+
@theme.code_linenum_font_color ||= '999999'
|
1448
|
+
conum_mapping ||= {}
|
1449
|
+
end
|
1450
|
+
fragments = text_formatter.format result
|
1451
|
+
fragments = restore_conums fragments, conum_mapping, num_trailing_spaces, linenums if conum_mapping
|
1452
|
+
fragments = guard_indentation fragments
|
1326
1453
|
when 'rouge'
|
1327
1454
|
Helpers.require_library RougeRequirePath, 'rouge' unless defined? ::Rouge::Formatters::Prawn
|
1328
1455
|
lexer = ::Rouge::Lexer.find(node.attr 'language', 'text', false) || ::Rouge::Lexers::PlainText
|
1329
1456
|
lexer_opts = lexer.tag == 'php' ? { start_inline: !(node.option? 'mixed') } : {}
|
1330
1457
|
formatter = (@rouge_formatter ||= ::Rouge::Formatters::Prawn.new theme: (node.document.attr 'rouge-style'), line_gap: @theme.code_line_gap)
|
1331
|
-
formatter_opts = (node.attr? 'linenums') ? { line_numbers: true, start_line: (node.attr 'start').to_i } : {}
|
1458
|
+
formatter_opts = (node.attr? 'linenums') ? { line_numbers: true, start_line: (node.attr 'start', 1, false).to_i } : {}
|
1332
1459
|
# QUESTION allow border color to be set by theme for highlighted block?
|
1333
1460
|
bg_color_override = formatter.background_color
|
1334
1461
|
source_string, conum_mapping = extract_conums source_string
|
1335
|
-
|
1336
|
-
fragments = formatter.format((lexer.lex %(#{source_string}#{LF}), lexer_opts), formatter_opts)
|
1462
|
+
fragments = formatter.format((lexer.lex source_string, lexer_opts), formatter_opts)
|
1337
1463
|
# NOTE cleanup trailing endline (handled in rouge_ext/formatters/prawn instead)
|
1338
1464
|
#fragments[-1][:text] == LF ? fragments.pop : fragments[-1][:text].chop!
|
1339
1465
|
conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
|
1340
1466
|
else
|
1341
1467
|
# NOTE only format if we detect a need (callouts or inline formatting)
|
1342
|
-
|
1343
|
-
text_formatter.format source_string
|
1344
|
-
else
|
1345
|
-
[{ text: source_string }]
|
1346
|
-
end
|
1468
|
+
(XMLMarkupRx.match? source_string) ? (text_formatter.format source_string) : [{ text: source_string }]
|
1347
1469
|
end
|
1348
1470
|
|
1349
1471
|
node.subs.replace prev_subs if prev_subs
|
@@ -1403,8 +1525,8 @@ class Converter < ::Prawn::Document
|
|
1403
1525
|
theme_margin :block, :bottom
|
1404
1526
|
end
|
1405
1527
|
|
1406
|
-
alias
|
1407
|
-
alias
|
1528
|
+
alias convert_listing convert_listing_or_literal
|
1529
|
+
alias convert_literal convert_listing_or_literal
|
1408
1530
|
|
1409
1531
|
# Extract callout marks from string, indexed by 0-based line number
|
1410
1532
|
# Return an Array with the processed string as the first argument
|
@@ -1435,7 +1557,7 @@ class Converter < ::Prawn::Document
|
|
1435
1557
|
#--
|
1436
1558
|
# QUESTION can this be done more efficiently?
|
1437
1559
|
# QUESTION can we reuse arrange_fragments_by_line?
|
1438
|
-
def restore_conums fragments, conum_mapping, num_trailing_spaces = 0
|
1560
|
+
def restore_conums fragments, conum_mapping, num_trailing_spaces = 0, linenums = nil
|
1439
1561
|
lines = []
|
1440
1562
|
line_num = 0
|
1441
1563
|
# reorganize the fragments into an array of lines
|
@@ -1454,9 +1576,14 @@ class Converter < ::Prawn::Document
|
|
1454
1576
|
end
|
1455
1577
|
conum_color = @theme.conum_font_color
|
1456
1578
|
last_line_num = lines.size - 1
|
1579
|
+
if linenums
|
1580
|
+
pad_size = (last_line_num + 1).to_s.length
|
1581
|
+
linenum_color = @theme.code_linenum_font_color
|
1582
|
+
end
|
1457
1583
|
# append conums to appropriate lines, then flatten to an array of fragments
|
1458
1584
|
lines.flat_map.with_index do |line, cur_line_num|
|
1459
1585
|
last_line = cur_line_num == last_line_num
|
1586
|
+
line.unshift text: %(#{(cur_line_num + linenums).to_s.rjust pad_size} ), color: linenum_color if linenums
|
1460
1587
|
if (conums = conum_mapping.delete cur_line_num)
|
1461
1588
|
line << { text: ' ' * num_trailing_spaces } if last_line && num_trailing_spaces > 0
|
1462
1589
|
conum_text = conums.map {|num| conum_glyph num } * ' '
|
@@ -1550,14 +1677,17 @@ class Converter < ::Prawn::Document
|
|
1550
1677
|
colspan: cell.colspan || 1,
|
1551
1678
|
rowspan: cell.rowspan || 1,
|
1552
1679
|
align: (cell.attr 'halign', nil, false).to_sym,
|
1553
|
-
valign: (val = cell.attr 'valign', nil, false) == 'middle' ? :center : val.to_sym
|
1680
|
+
valign: (val = cell.attr 'valign', nil, false) == 'middle' ? :center : val.to_sym,
|
1681
|
+
padding: theme.table_cell_padding
|
1554
1682
|
}
|
1555
1683
|
cell_transform = nil
|
1556
1684
|
case cell.style
|
1557
1685
|
when :emphasis
|
1558
1686
|
cell_data[:font_style] = :italic
|
1687
|
+
cell_line_metrics = calc_line_metrics theme.base_line_height
|
1559
1688
|
when :strong
|
1560
1689
|
cell_data[:font_style] = :bold
|
1690
|
+
cell_line_metrics = calc_line_metrics theme.base_line_height
|
1561
1691
|
when :header
|
1562
1692
|
unless header_cell_data_cache
|
1563
1693
|
header_cell_data_cache = {}
|
@@ -1582,6 +1712,7 @@ class Converter < ::Prawn::Document
|
|
1582
1712
|
cell_transform = nil
|
1583
1713
|
end
|
1584
1714
|
cell_data.update header_cell_data unless header_cell_data.empty?
|
1715
|
+
cell_line_metrics = calc_line_metrics theme.base_line_height
|
1585
1716
|
when :monospaced
|
1586
1717
|
cell_data[:font] = theme.literal_font_family
|
1587
1718
|
if (val = theme.literal_font_size)
|
@@ -1590,8 +1721,7 @@ class Converter < ::Prawn::Document
|
|
1590
1721
|
if (val = theme.literal_font_color)
|
1591
1722
|
cell_data[:text_color] = val
|
1592
1723
|
end
|
1593
|
-
|
1594
|
-
#cell_data[:leading] = (calc_line_metrics theme.base_line_height).leading
|
1724
|
+
cell_line_metrics = calc_line_metrics theme.base_line_height
|
1595
1725
|
when :literal
|
1596
1726
|
# FIXME core should not substitute in this case
|
1597
1727
|
cell_data[:content] = preserve_indentation((cell.instance_variable_get :@text), (node.document.attr 'tabsize'))
|
@@ -1604,17 +1734,28 @@ class Converter < ::Prawn::Document
|
|
1604
1734
|
if (val = theme.code_font_color)
|
1605
1735
|
cell_data[:text_color] = val
|
1606
1736
|
end
|
1607
|
-
|
1608
|
-
#cell_data[:leading] = (calc_line_metrics theme.code_line_height).leading
|
1737
|
+
cell_line_metrics = calc_line_metrics theme.code_line_height
|
1609
1738
|
when :verse
|
1610
1739
|
cell_data[:content] = preserve_indentation cell.text, (node.document.attr 'tabsize')
|
1611
1740
|
cell_data[:inline_format] = true
|
1741
|
+
cell_line_metrics = calc_line_metrics theme.base_line_height
|
1612
1742
|
when :asciidoc
|
1613
1743
|
asciidoc_cell = ::Prawn::Table::Cell::AsciiDoc.new self,
|
1614
1744
|
(cell_data.merge content: cell.inner_document, font_style: (val = theme.table_font_style) ? val.to_sym : nil)
|
1615
1745
|
cell_data = { content: asciidoc_cell }
|
1616
1746
|
else
|
1617
1747
|
cell_data[:font_style] = (val = theme.table_font_style) ? val.to_sym : nil
|
1748
|
+
cell_line_metrics = calc_line_metrics theme.base_line_height
|
1749
|
+
end
|
1750
|
+
if cell_line_metrics
|
1751
|
+
unless ::Array === (cell_padding = cell_data[:padding]) && cell_padding.size == 4
|
1752
|
+
cell_padding = cell_data[:padding] = inflate_padding cell_padding
|
1753
|
+
end
|
1754
|
+
cell_padding[0] += cell_line_metrics.padding_top
|
1755
|
+
cell_padding[2] += cell_line_metrics.padding_bottom
|
1756
|
+
cell_data[:leading] = cell_line_metrics.leading
|
1757
|
+
# TODO patch prawn-table to pass through final_gap option
|
1758
|
+
#cell_data[:final_gap] = cell_line_metrics.final_gap
|
1618
1759
|
end
|
1619
1760
|
unless cell_data.key? :content
|
1620
1761
|
if (cell_text = cell_transform ? (transform_text cell.text, cell_transform) : cell.text).include? LF
|
@@ -1651,7 +1792,7 @@ class Converter < ::Prawn::Document
|
|
1651
1792
|
table_grid_width = theme.table_grid_width || theme.table_border_width
|
1652
1793
|
[:cols, :rows].each {|edge| border_width[edge] = table_grid_width }
|
1653
1794
|
|
1654
|
-
case (grid = node.attr 'grid', 'all',
|
1795
|
+
case (grid = node.attr 'grid', 'all', 'table-grid')
|
1655
1796
|
when 'all'
|
1656
1797
|
# keep inner borders
|
1657
1798
|
when 'cols'
|
@@ -1662,10 +1803,10 @@ class Converter < ::Prawn::Document
|
|
1662
1803
|
border_width[:rows] = border_width[:cols] = 0
|
1663
1804
|
end
|
1664
1805
|
|
1665
|
-
case (frame = node.attr 'frame', 'all',
|
1806
|
+
case (frame = node.attr 'frame', 'all', 'table-frame')
|
1666
1807
|
when 'all'
|
1667
1808
|
# keep outer borders
|
1668
|
-
when 'topbot'
|
1809
|
+
when 'topbot', 'ends'
|
1669
1810
|
border_width[:left] = border_width[:right] = 0
|
1670
1811
|
when 'sides'
|
1671
1812
|
border_width[:top] = border_width[:bottom] = 0
|
@@ -1703,15 +1844,14 @@ class Converter < ::Prawn::Document
|
|
1703
1844
|
border_color: table_grid_color,
|
1704
1845
|
border_lines: [table_grid_style],
|
1705
1846
|
# NOTE the border width is set later
|
1706
|
-
border_width: 0
|
1707
|
-
padding: theme.table_cell_padding
|
1847
|
+
border_width: 0
|
1708
1848
|
},
|
1709
1849
|
width: table_width,
|
1710
1850
|
column_widths: column_widths
|
1711
1851
|
}
|
1712
1852
|
|
1713
1853
|
# QUESTION should we support nth; should we support sequence of roles?
|
1714
|
-
case node.attr 'stripes', '
|
1854
|
+
case node.attr 'stripes', nil, 'table-stripes'
|
1715
1855
|
when 'all'
|
1716
1856
|
table_settings[:row_colors] = [body_stripe_bg_color]
|
1717
1857
|
when 'even'
|
@@ -1799,7 +1939,7 @@ class Converter < ::Prawn::Document
|
|
1799
1939
|
end
|
1800
1940
|
|
1801
1941
|
# deprecated
|
1802
|
-
alias
|
1942
|
+
alias convert_horizontal_rule convert_thematic_break
|
1803
1943
|
|
1804
1944
|
# NOTE manual placement not yet possible, so return nil
|
1805
1945
|
def convert_toc node
|
@@ -1808,13 +1948,25 @@ class Converter < ::Prawn::Document
|
|
1808
1948
|
|
1809
1949
|
# NOTE to insert sequential page breaks, you must put {nbsp} between page breaks
|
1810
1950
|
def convert_page_break node
|
1811
|
-
|
1951
|
+
unless at_page_top?
|
1952
|
+
if (page_layout = node.attr 'page-layout').nil_or_empty?
|
1953
|
+
if node.role? && (page_layout = (node.roles.map(&:to_sym) & PageLayouts)[-1])
|
1954
|
+
advance_page layout: page_layout
|
1955
|
+
else
|
1956
|
+
advance_page
|
1957
|
+
end
|
1958
|
+
elsif PageLayouts.include?(page_layout = page_layout.to_sym)
|
1959
|
+
advance_page layout: page_layout
|
1960
|
+
else
|
1961
|
+
advance_page
|
1962
|
+
end
|
1963
|
+
end
|
1812
1964
|
end
|
1813
1965
|
|
1814
1966
|
def convert_index_section node
|
1815
1967
|
unless @index.empty?
|
1816
1968
|
space_needed_for_category = @theme.description_list_term_spacing + (2 * (height_of_typeset_text 'A'))
|
1817
|
-
column_box [0, cursor], columns: 2, width: bounds.width do
|
1969
|
+
column_box [0, cursor], columns: 2, width: bounds.width, reflow_margins: true do
|
1818
1970
|
@index.categories.each do |category|
|
1819
1971
|
# NOTE cursor method always returns 0 inside column_box; breaks reference_bounds.move_past_bottom
|
1820
1972
|
bounds.move_past_bottom if space_needed_for_category > y - reference_bounds.absolute_bottom
|
@@ -1844,7 +1996,7 @@ class Converter < ::Prawn::Document
|
|
1844
1996
|
if @media == 'screen'
|
1845
1997
|
pagenums = term.dests.map {|dest| %(<a anchor="#{dest[:anchor]}">#{dest[:page]}</a>) }
|
1846
1998
|
else
|
1847
|
-
pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| dest[:page].to_s }
|
1999
|
+
pagenums = consolidate_ranges term.dests.uniq {|dest| dest[:page] }.map {|dest| dest[:page].to_s }
|
1848
2000
|
end
|
1849
2001
|
text = %(#{text}, #{pagenums * ', '})
|
1850
2002
|
end
|
@@ -1901,12 +2053,16 @@ class Converter < ::Prawn::Document
|
|
1901
2053
|
end
|
1902
2054
|
when :ref
|
1903
2055
|
# NOTE destination is created inside callback registered by FormattedTextTransform#build_fragment
|
1904
|
-
|
2056
|
+
# NOTE id is used instead of target starting in Asciidoctor 2.0.0
|
2057
|
+
%(<a name="#{node.target || node.id}">#{DummyText}</a>)
|
1905
2058
|
when :bibref
|
1906
2059
|
# NOTE destination is created inside callback registered by FormattedTextTransform#build_fragment
|
1907
|
-
|
2060
|
+
# NOTE technically node.text should be node.reftext, but subs have already been applied to text
|
2061
|
+
# NOTE check reftext? for compatibility with Asciidoctor <= 1.5.5
|
2062
|
+
# NOTE id is used instead of target starting in Asciidoctor 2.0.0
|
2063
|
+
%(<a name="#{node.target || node.id}">#{DummyText}</a>#{(reftext = node.reftext) ? reftext : "[#{node.target || node.id}]"})
|
1908
2064
|
else
|
1909
|
-
warn %(
|
2065
|
+
logger.warn %(unknown anchor type: #{node.type.inspect})
|
1910
2066
|
end
|
1911
2067
|
end
|
1912
2068
|
|
@@ -1929,9 +2085,9 @@ class Converter < ::Prawn::Document
|
|
1929
2085
|
end
|
1930
2086
|
|
1931
2087
|
def convert_inline_footnote node
|
1932
|
-
if (index = node.attr 'index')
|
1933
|
-
|
1934
|
-
%(
|
2088
|
+
if (index = node.attr 'index') && (node.document.footnotes.find {|fn| fn.index == index })
|
2089
|
+
anchor = node.type == :xref ? '' : %(<a name="_footnoteref_#{index}">#{DummyText}</a>)
|
2090
|
+
%(#{anchor}<sup>[<a anchor="_footnotedef_#{index}">#{index}</a>]</sup>)
|
1935
2091
|
elsif node.type == :xref
|
1936
2092
|
# NOTE footnote reference not found
|
1937
2093
|
%( <color rgb="FF0000">[#{node.text}]</color>)
|
@@ -1947,16 +2103,34 @@ class Converter < ::Prawn::Document
|
|
1947
2103
|
end
|
1948
2104
|
icon_set = 'fa' unless IconSets.include? icon_set
|
1949
2105
|
if node.attr? 'size', nil, false
|
1950
|
-
|
1951
|
-
|
2106
|
+
case (size = node.attr 'size')
|
2107
|
+
when 'lg'
|
2108
|
+
size_attr = %( size="1.333em")
|
2109
|
+
when 'fw'
|
2110
|
+
size_attr = %( width="1em" align="center")
|
2111
|
+
else
|
2112
|
+
size_attr = %( size="#{size.sub 'x', 'em'}")
|
2113
|
+
end
|
1952
2114
|
else
|
1953
|
-
size_attr =
|
2115
|
+
size_attr = ''
|
1954
2116
|
end
|
1955
2117
|
begin
|
1956
|
-
|
1957
|
-
|
2118
|
+
if icon_set == 'fa'
|
2119
|
+
font_data = nil
|
2120
|
+
resolved_icon_set = FontAwesomeIconSets.find {|candidate| (font_data = icon_font_data candidate).unicode icon_name rescue nil }
|
2121
|
+
if resolved_icon_set
|
2122
|
+
icon_set = resolved_icon_set
|
2123
|
+
logger.info { %(#{icon_name} icon found in deprecated fa icon set; use #{icon_set} icon set instead) }
|
2124
|
+
else
|
2125
|
+
raise
|
2126
|
+
end
|
2127
|
+
else
|
2128
|
+
font_data = icon_font_data icon_set
|
2129
|
+
end
|
2130
|
+
# TODO support rotate and flip attributes
|
2131
|
+
%(<font name="#{icon_set}"#{size_attr}>#{font_data.unicode icon_name}</font>)
|
1958
2132
|
rescue
|
1959
|
-
warn %(
|
2133
|
+
logger.warn %(#{icon_name} is not a valid icon name in the #{icon_set} icon set)
|
1960
2134
|
%([#{node.attr 'alt'}])
|
1961
2135
|
end
|
1962
2136
|
else
|
@@ -1971,14 +2145,14 @@ class Converter < ::Prawn::Document
|
|
1971
2145
|
node.extend ::Asciidoctor::Image unless ::Asciidoctor::Image === node
|
1972
2146
|
target, image_format = node.target_and_format
|
1973
2147
|
if image_format == 'gif' && !(defined? ::GMagick::Image)
|
1974
|
-
warn %(
|
2148
|
+
logger.warn %(GIF image format not supported. Install the prawn-gmagick gem or convert #{target} to PNG.) unless scratch?
|
1975
2149
|
img = %([#{node.attr 'alt'}])
|
1976
2150
|
# NOTE an image with a data URI is handled using a temporary file
|
1977
2151
|
elsif (image_path = resolve_image_path node, target, true, image_format) && (::File.readable? image_path)
|
1978
2152
|
width_attr = (width = preresolve_explicit_width node.attributes) ? %( width="#{width}") : nil
|
1979
|
-
img = %(<img src="#{image_path}" format="#{image_format}" alt="[#{node.attr 'alt'}]"#{width_attr} tmp="#{TemporaryPath === image_path}">)
|
2153
|
+
img = %(<img src="#{image_path}" format="#{image_format}" alt="[#{encode_quotes node.attr 'alt'}]"#{width_attr} tmp="#{TemporaryPath === image_path}">)
|
1980
2154
|
else
|
1981
|
-
warn %(
|
2155
|
+
logger.warn %(image to embed not found or not readable: #{image_path || target}) unless scratch?
|
1982
2156
|
img = %([#{node.attr 'alt'}])
|
1983
2157
|
end
|
1984
2158
|
(node.attr? 'link', nil, false) ? %(<a href="#{node.attr 'link'}">#{img}</a>) : img
|
@@ -2016,7 +2190,7 @@ class Converter < ::Prawn::Document
|
|
2016
2190
|
|
2017
2191
|
def convert_inline_menu node
|
2018
2192
|
menu = node.attr 'menu'
|
2019
|
-
caret =
|
2193
|
+
caret = (load_theme node.document).menu_caret_content || %( \u203a )
|
2020
2194
|
if !(submenus = node.attr 'submenus').empty?
|
2021
2195
|
%(<strong>#{[menu, *submenus, (node.attr 'menuitem')] * caret}</strong>)
|
2022
2196
|
elsif (menuitem = node.attr 'menuitem')
|
@@ -2061,7 +2235,6 @@ class Converter < ::Prawn::Document
|
|
2061
2235
|
node.id ? %(<a name="#{node.id}">#{DummyText}</a>#{quoted_text}) : quoted_text
|
2062
2236
|
end
|
2063
2237
|
|
2064
|
-
# FIXME only create title page if doctype=book!
|
2065
2238
|
def layout_title_page doc
|
2066
2239
|
return unless doc.header? && !doc.notitle
|
2067
2240
|
|
@@ -2196,7 +2369,7 @@ class Converter < ::Prawn::Document
|
|
2196
2369
|
image_page cover_image, canvas: true
|
2197
2370
|
end
|
2198
2371
|
else
|
2199
|
-
warn %(
|
2372
|
+
logger.warn %(#{face} cover image not found or readable: #{cover_image})
|
2200
2373
|
end
|
2201
2374
|
end
|
2202
2375
|
ensure
|
@@ -2213,19 +2386,19 @@ class Converter < ::Prawn::Document
|
|
2213
2386
|
layout_heading title, opts
|
2214
2387
|
end
|
2215
2388
|
|
2216
|
-
alias
|
2217
|
-
alias
|
2389
|
+
alias start_new_part start_new_chapter
|
2390
|
+
alias layout_part_title layout_chapter_title
|
2218
2391
|
|
2219
2392
|
# QUESTION why doesn't layout_heading set the font??
|
2220
2393
|
# QUESTION why doesn't layout_heading accept a node?
|
2221
2394
|
def layout_heading string, opts = {}
|
2222
|
-
top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.heading_margin_top
|
2223
|
-
bot_margin = margin || (opts.delete :margin_bottom) || @theme.heading_margin_bottom
|
2395
|
+
top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme[%(heading_h#{opts[:level]}_margin_top)] || @theme.heading_margin_top
|
2396
|
+
bot_margin = margin || (opts.delete :margin_bottom) || @theme[%(heading_h#{opts[:level]}_margin_bottom)] || @theme.heading_margin_bottom
|
2224
2397
|
if (transform = (opts.delete :text_transform) || @text_transform) && transform != 'none'
|
2225
2398
|
string = transform_text string, transform
|
2226
2399
|
end
|
2227
2400
|
margin_top top_margin
|
2228
|
-
typeset_text string, calc_line_metrics((opts.delete :line_height) || @theme.heading_line_height), {
|
2401
|
+
typeset_text string, calc_line_metrics((opts.delete :line_height) || @theme[%(heading_h#{opts[:level]}_line_height)] || @theme.heading_line_height), {
|
2229
2402
|
color: @font_color,
|
2230
2403
|
inline_format: true,
|
2231
2404
|
align: @base_align.to_sym
|
@@ -2254,8 +2427,20 @@ class Converter < ::Prawn::Document
|
|
2254
2427
|
margin_bottom bot_margin
|
2255
2428
|
end
|
2256
2429
|
|
2430
|
+
def generate_manname_section node
|
2431
|
+
title = node.attr 'manname-title', 'Name'
|
2432
|
+
if (next_section = node.sections[0]) && (next_section_title = next_section.title) == next_section_title.upcase
|
2433
|
+
title = title.upcase
|
2434
|
+
end
|
2435
|
+
sect = Section.new node, 1
|
2436
|
+
sect.sectname = 'section'
|
2437
|
+
sect.id = node.attr 'manname-id'
|
2438
|
+
sect.title = title
|
2439
|
+
sect << (Block.new sect, :paragraph, source: %(#{node.attr 'manname'} - #{node.attr 'manpurpose'}))
|
2440
|
+
sect
|
2441
|
+
end
|
2442
|
+
|
2257
2443
|
# Render the caption and return the height of the rendered content
|
2258
|
-
# QUESTION should layout_caption check for title? and return 0 if false?
|
2259
2444
|
# TODO allow margin to be zeroed
|
2260
2445
|
def layout_caption subject, opts = {}
|
2261
2446
|
mark = { cursor: cursor, page_number: page_number }
|
@@ -2263,7 +2448,11 @@ class Converter < ::Prawn::Document
|
|
2263
2448
|
when ::String
|
2264
2449
|
string = subject
|
2265
2450
|
when ::Asciidoctor::AbstractBlock
|
2266
|
-
|
2451
|
+
if subject.title?
|
2452
|
+
string = subject.captioned_title
|
2453
|
+
else
|
2454
|
+
return 0
|
2455
|
+
end
|
2267
2456
|
else
|
2268
2457
|
return 0
|
2269
2458
|
end
|
@@ -2306,9 +2495,10 @@ class Converter < ::Prawn::Document
|
|
2306
2495
|
end
|
2307
2496
|
|
2308
2497
|
# NOTE num_front_matter_pages is not used during a dry run
|
2309
|
-
def layout_toc doc, num_levels = 2, toc_page_number = 2, num_front_matter_pages = 0
|
2498
|
+
def layout_toc doc, num_levels = 2, toc_page_number = 2, num_front_matter_pages = 0, start_at = nil
|
2310
2499
|
go_to_page toc_page_number unless (page_number == toc_page_number) || scratch?
|
2311
2500
|
start_page_number = page_number
|
2501
|
+
@y = start_at if start_at
|
2312
2502
|
theme_font :heading, level: 2 do
|
2313
2503
|
theme_font :toc_title do
|
2314
2504
|
toc_title_align = (@theme.toc_title_align || @theme.heading_h2_align || @theme.heading_align || @base_align).to_sym
|
@@ -2374,7 +2564,10 @@ class Converter < ::Prawn::Document
|
|
2374
2564
|
key == :styles ? (old_val.merge new_val) : new_val
|
2375
2565
|
end
|
2376
2566
|
end
|
2377
|
-
|
2567
|
+
pgnum_label_width = rendered_width_of_string pgnum_label
|
2568
|
+
indent 0, pgnum_label_width do
|
2569
|
+
typeset_formatted_text sect_title_fragments, line_metrics
|
2570
|
+
end
|
2378
2571
|
end_page_number = page_number
|
2379
2572
|
end_cursor = cursor
|
2380
2573
|
# TODO it would be convenient to have a cursor mark / placement utility that took page number into account
|
@@ -2382,7 +2575,6 @@ class Converter < ::Prawn::Document
|
|
2382
2575
|
move_cursor_to start_cursor
|
2383
2576
|
if dot_leader[:width] > 0 && (dot_leader[:levels].include? sect.level)
|
2384
2577
|
pgnum_label_font_settings = { color: @font_color, font: font_family, size: @font_size, styles: font_styles }
|
2385
|
-
pgnum_label_width = rendered_width_of_string pgnum_label
|
2386
2578
|
# WARNING width_of is not accurate if string must use characters from fallback font
|
2387
2579
|
sect_title_width = width_of sect_title, inline_format: true
|
2388
2580
|
save_font do
|
@@ -2478,15 +2670,17 @@ class Converter < ::Prawn::Document
|
|
2478
2670
|
(1..num_pages).each do |num|
|
2479
2671
|
if (part = part_start_pages[num])
|
2480
2672
|
last_part = part
|
2673
|
+
last_chap = nil
|
2674
|
+
last_sect = nil
|
2481
2675
|
end
|
2482
2676
|
if (chap = chapter_start_pages[num])
|
2483
2677
|
last_chap = chap
|
2678
|
+
last_sect = nil
|
2484
2679
|
end
|
2485
2680
|
if (sect = section_start_pages[num])
|
2486
2681
|
last_sect = sect
|
2487
2682
|
elsif part || chap
|
2488
2683
|
sect_search_threshold = num
|
2489
|
-
last_sect = nil
|
2490
2684
|
# NOTE we didn't find a section on this page; look back to find last section started
|
2491
2685
|
elsif last_sect
|
2492
2686
|
((sect_search_threshold)..(num - 1)).reverse_each do |prev|
|
@@ -2713,8 +2907,8 @@ class Converter < ::Prawn::Document
|
|
2713
2907
|
bounding_box [colspec[:x], cursor - trim_padding[0]], width: colspec[:width], height: (bounds.height - trim_v_padding) do
|
2714
2908
|
begin
|
2715
2909
|
if (img_path = content[:path]).downcase.end_with? '.svg'
|
2716
|
-
svg_data = ::
|
2717
|
-
svg_obj = ::Prawn::
|
2910
|
+
svg_data = ::File.read img_path
|
2911
|
+
svg_obj = ::Prawn::SVG::Interface.new svg_data, self,
|
2718
2912
|
position: colspec[:align],
|
2719
2913
|
vposition: trim_img_valign,
|
2720
2914
|
width: content[:width],
|
@@ -2735,8 +2929,8 @@ class Converter < ::Prawn::Document
|
|
2735
2929
|
end
|
2736
2930
|
image img_path, img_opts
|
2737
2931
|
end
|
2738
|
-
rescue
|
2739
|
-
warn %(
|
2932
|
+
rescue
|
2933
|
+
logger.warn %(could not embed image in running content: #{img_path}; #{$!.message})
|
2740
2934
|
end
|
2741
2935
|
end
|
2742
2936
|
end
|
@@ -2953,7 +3147,7 @@ class Converter < ::Prawn::Document
|
|
2953
3147
|
end
|
2954
3148
|
available_width = bounds.width - (padding[3] || 0) - (padding[1] || 0)
|
2955
3149
|
if actual_width > available_width
|
2956
|
-
adjusted_font_size = ((available_width * font_size).to_f / actual_width).
|
3150
|
+
adjusted_font_size = ((available_width * font_size).to_f / actual_width).truncate 4
|
2957
3151
|
if (min = @theme[%(#{category}_font_size_min)] || @theme.base_font_size_min) && adjusted_font_size < min
|
2958
3152
|
min
|
2959
3153
|
else
|
@@ -3133,7 +3327,7 @@ class Converter < ::Prawn::Document
|
|
3133
3327
|
# experience.
|
3134
3328
|
def add_dest_for_block node, id = nil
|
3135
3329
|
if !scratch? && (id ||= node.id)
|
3136
|
-
dest_x = bounds.absolute_left.
|
3330
|
+
dest_x = bounds.absolute_left.truncate 4
|
3137
3331
|
# QUESTION when content is aligned to left margin, should we keep precise x value or just use 0?
|
3138
3332
|
dest_x = 0 if dest_x <= page_margin_left
|
3139
3333
|
dest_y = at_page_top? && (node.context == :section || node.context == :document) ? page_height : y
|
@@ -3144,6 +3338,14 @@ class Converter < ::Prawn::Document
|
|
3144
3338
|
nil
|
3145
3339
|
end
|
3146
3340
|
|
3341
|
+
def resolve_alignment_from_role roles
|
3342
|
+
if (align_role = roles.reverse.find {|r| TextAlignmentRoles.include? r })
|
3343
|
+
align_role[5..-1].to_sym
|
3344
|
+
else
|
3345
|
+
nil
|
3346
|
+
end
|
3347
|
+
end
|
3348
|
+
|
3147
3349
|
# QUESTION is this method still necessary?
|
3148
3350
|
def resolve_imagesdir doc
|
3149
3351
|
if (imagesdir = doc.attr 'imagesdir').nil_or_empty? || (imagesdir = imagesdir.chomp '/') == '.'
|
@@ -3173,7 +3375,7 @@ class Converter < ::Prawn::Document
|
|
3173
3375
|
image_format ||= ::Asciidoctor::Image.format image_path, (::Asciidoctor::Image === node ? node : nil)
|
3174
3376
|
# NOTE currently used for inline images
|
3175
3377
|
if ::Base64 === image_path
|
3176
|
-
tmp_image = ::Tempfile.
|
3378
|
+
tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
|
3177
3379
|
tmp_image.binmode unless image_format == 'svg'
|
3178
3380
|
begin
|
3179
3381
|
tmp_image.write(::Base64.decode64 image_path)
|
@@ -3187,7 +3389,7 @@ class Converter < ::Prawn::Document
|
|
3187
3389
|
elsif (node.is_uri? image_path) || (imagesdir && (node.is_uri? imagesdir) &&
|
3188
3390
|
(image_path = (node.normalize_web_path image_path, imagesdir, false)))
|
3189
3391
|
unless doc.attr? 'allow-uri-read'
|
3190
|
-
warn %(
|
3392
|
+
logger.warn %(allow-uri-read is not enabled; cannot embed remote image: #{image_path}) unless scratch?
|
3191
3393
|
return
|
3192
3394
|
end
|
3193
3395
|
if doc.attr? 'cache-uri'
|
@@ -3195,7 +3397,7 @@ class Converter < ::Prawn::Document
|
|
3195
3397
|
else
|
3196
3398
|
::OpenURI
|
3197
3399
|
end
|
3198
|
-
tmp_image = ::Tempfile.
|
3400
|
+
tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
|
3199
3401
|
tmp_image.binmode if (binary = image_format != 'svg')
|
3200
3402
|
begin
|
3201
3403
|
open(image_path, (binary ? 'rb' : 'r')) {|fd| tmp_image.write fd.read }
|
@@ -3234,7 +3436,7 @@ class Converter < ::Prawn::Document
|
|
3234
3436
|
if ::File.readable? bg_image
|
3235
3437
|
bg_image
|
3236
3438
|
else
|
3237
|
-
warn %(
|
3439
|
+
logger.warn %(#{key.tr '-', ' '} not found or readable: #{bg_image})
|
3238
3440
|
nil
|
3239
3441
|
end
|
3240
3442
|
end
|
@@ -3303,20 +3505,39 @@ class Converter < ::Prawn::Document
|
|
3303
3505
|
# NOTE Ruby 1.9 will sometimes delete a tmp file before the process exits
|
3304
3506
|
def unlink_tmp_file path
|
3305
3507
|
path.unlink if TemporaryPath === path && path.exist?
|
3306
|
-
rescue
|
3307
|
-
warn %(
|
3508
|
+
rescue
|
3509
|
+
logger.warn %(could not delete temporary image: #{path}; #{$!.message})
|
3308
3510
|
end
|
3309
3511
|
|
3310
3512
|
# NOTE assume URL is escaped (i.e., contains character references such as &)
|
3311
3513
|
def breakable_uri uri
|
3312
3514
|
scheme, address = uri.split UriSchemeBoundaryRx, 2
|
3313
3515
|
address, scheme = scheme, address unless address
|
3314
|
-
|
3315
|
-
|
3316
|
-
|
3516
|
+
unless address.nil_or_empty?
|
3517
|
+
address = address.gsub UriBreakCharsRx, UriBreakCharRepl
|
3518
|
+
# NOTE require at least two characters after a break
|
3519
|
+
address.slice!(-2) if address[-2] == ZeroWidthSpace
|
3520
|
+
end
|
3317
3521
|
%(#{scheme}#{address})
|
3318
3522
|
end
|
3319
3523
|
|
3524
|
+
def consolidate_ranges nums
|
3525
|
+
if nums.size > 1
|
3526
|
+
prev = nil
|
3527
|
+
nums.inject([]) {|accum, num|
|
3528
|
+
if prev && (prev.to_i + 1) == num.to_i
|
3529
|
+
accum[-1][1] = num
|
3530
|
+
else
|
3531
|
+
accum << [num]
|
3532
|
+
end
|
3533
|
+
prev = num
|
3534
|
+
accum
|
3535
|
+
}.map {|range| range.join '-' }
|
3536
|
+
else
|
3537
|
+
nums
|
3538
|
+
end
|
3539
|
+
end
|
3540
|
+
|
3320
3541
|
# QUESTION move to prawn/extensions.rb?
|
3321
3542
|
def init_scratch_prototype
|
3322
3543
|
# IMPORTANT don't set font before using Marshal, it causes serialization to fail
|
@@ -3341,4 +3562,5 @@ class Converter < ::Prawn::Document
|
|
3341
3562
|
=end
|
3342
3563
|
end
|
3343
3564
|
end
|
3565
|
+
Pdf = PDF unless const_defined? :Pdf, false
|
3344
3566
|
end
|