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.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.yardopts +12 -0
  3. data/CHANGELOG.adoc +66 -0
  4. data/LICENSE.adoc +1 -1
  5. data/README.adoc +221 -68
  6. data/asciidoctor-pdf.gemspec +41 -42
  7. data/bin/asciidoctor-pdf +3 -3
  8. data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
  9. data/data/fonts/notoserif-bold-subset.ttf +0 -0
  10. data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
  11. data/data/fonts/notoserif-italic-subset.ttf +0 -0
  12. data/data/fonts/notoserif-regular-subset.ttf +0 -0
  13. data/data/themes/default-theme.yml +6 -3
  14. data/docs/theming-guide.adoc +162 -23
  15. data/lib/asciidoctor-pdf.rb +2 -1
  16. data/lib/asciidoctor-pdf/asciidoctor_ext.rb +1 -0
  17. data/lib/asciidoctor-pdf/asciidoctor_ext/logging_shim.rb +19 -0
  18. data/lib/asciidoctor-pdf/converter.rb +408 -186
  19. data/lib/asciidoctor-pdf/core_ext/array.rb +0 -6
  20. data/lib/asciidoctor-pdf/core_ext/numeric.rb +21 -12
  21. data/lib/asciidoctor-pdf/core_ext/ostruct.rb +3 -12
  22. data/lib/asciidoctor-pdf/core_ext/string.rb +1 -1
  23. data/lib/asciidoctor-pdf/formatted_text.rb +1 -0
  24. data/lib/asciidoctor-pdf/formatted_text/formatter.rb +8 -2
  25. data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +1 -1
  26. data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +18 -32
  27. data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +3 -3
  28. data/lib/asciidoctor-pdf/formatted_text/inline_text_aligner.rb +20 -0
  29. data/lib/asciidoctor-pdf/formatted_text/parser.rb +124 -38
  30. data/lib/asciidoctor-pdf/formatted_text/parser.treetop +17 -10
  31. data/lib/asciidoctor-pdf/formatted_text/transform.rb +30 -20
  32. data/lib/asciidoctor-pdf/implicit_header_processor.rb +2 -2
  33. data/lib/asciidoctor-pdf/index_catalog.rb +25 -23
  34. data/lib/asciidoctor-pdf/measurements.rb +1 -1
  35. data/lib/asciidoctor-pdf/pdf-core_ext/pdf_object.rb +1 -1
  36. data/lib/asciidoctor-pdf/pdfmark.rb +13 -13
  37. data/lib/asciidoctor-pdf/prawn-svg_ext.rb +2 -2
  38. data/lib/asciidoctor-pdf/prawn-svg_ext/interface.rb +2 -2
  39. data/lib/asciidoctor-pdf/prawn-table_ext.rb +1 -0
  40. data/lib/asciidoctor-pdf/prawn-table_ext/cell.rb +60 -0
  41. data/lib/asciidoctor-pdf/prawn-table_ext/cell/text.rb +3 -3
  42. data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +3 -3
  43. data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +39 -14
  44. data/lib/asciidoctor-pdf/prawn_ext/formatted_text/fragment.rb +9 -10
  45. data/lib/asciidoctor-pdf/prawn_ext/images.rb +2 -2
  46. data/lib/asciidoctor-pdf/roman_numeral.rb +7 -7
  47. data/lib/asciidoctor-pdf/rouge_ext.rb +2 -2
  48. data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +20 -9
  49. data/lib/asciidoctor-pdf/rouge_ext/themes/{pastie.rb → asciidoctor_pdf_default.rb} +5 -5
  50. data/lib/asciidoctor-pdf/rouge_ext/themes/bw.rb +38 -0
  51. data/lib/asciidoctor-pdf/sanitizer.rb +36 -23
  52. data/lib/asciidoctor-pdf/temporary_path.rb +1 -1
  53. data/lib/asciidoctor-pdf/theme_loader.rb +17 -14
  54. data/lib/asciidoctor-pdf/version.rb +3 -2
  55. data/lib/asciidoctor/pdf.rb +1 -0
  56. data/lib/asciidoctor/pdf/version.rb +1 -0
  57. metadata +113 -84
  58. data/Gemfile +0 -22
  59. data/Rakefile +0 -81
  60. data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +0 -15
@@ -1,3 +1,4 @@
1
- require 'asciidoctor' unless defined? Asciidoctor::VERSION
1
+ require 'asciidoctor' unless defined? Asciidoctor.load
2
+ require_relative 'asciidoctor-pdf/asciidoctor_ext'
2
3
  require_relative 'asciidoctor-pdf/version'
3
4
  require_relative 'asciidoctor-pdf/converter'
@@ -2,4 +2,5 @@
2
2
  require_relative 'asciidoctor_ext/section'
3
3
  require_relative 'asciidoctor_ext/list'
4
4
  require_relative 'asciidoctor_ext/list_item'
5
+ require_relative 'asciidoctor_ext/logging_shim' unless defined? Asciidoctor::Logging
5
6
  require_relative 'asciidoctor_ext/image'
@@ -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: UTF-8
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 Pdf
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: 'fa-fire', stroke_color: 'BF3400', size: 24 },
44
- important: { name: 'fa-exclamation-circle', stroke_color: 'BF0000', size: 24 },
45
- note: { name: 'fa-info-circle', stroke_color: '19407C', size: 24 },
46
- tip: { name: 'fa-lightbulb-o', stroke_color: '111111', size: 24 },
47
- warning: { name: 'fa-exclamation-triangle', stroke_color: 'BF6900', size: 24 }
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 = ['left', 'center', 'right', 'justify']
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
- #htmlsyntax 'xml'
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 %(asciidoctor: WARNING: conversion missing in backend #{@backend} for #{name})
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
- layout_title_page doc
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 a new page will already be started if the cover image is a PDF
188
- start_new_page unless page_is_empty?
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
- dry_run { toc_page_nums = layout_toc doc, num_toc_levels, toc_page_nums }
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
- toc_page_nums.each { start_new_page }
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
- num_front_matter_pages = (@index.start_page_number = page_number) - 1
204
- font @theme.base_font_family, size: @theme.base_font_size, style: @theme.base_font_style.to_sym
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
- if page_count > num_front_matter_pages
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 :convert_embedded :convert_document
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
- @theme = theme = ThemeLoader.load_theme((doc.attr 'pdf-style'), (doc.attr 'pdf-stylesdir'))
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]).truncate_to_precision 4
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 PdfMarks should use the PDF info result
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::Pdf::VERSION}, based on Prawn #{::Prawn::VERSION}).as_pdf
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.each do |role|
488
- case role
489
- when 'text-left'
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 is_lead
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') ? (node.document.attr 'icons') : false
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 %(asciidoctor: WARNING: admonition icon image not found or not readable: #{icon_path}) unless scratch?
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
- begin
597
- image_obj, image_info = build_image_object icon_path
598
- icon_aspect_ratio = image_info.width.fdiv image_info.height
599
- # NOTE don't scale image up if smaller than label_width
600
- icon_width = [(to_pt image_info.width, :px), label_width].min
601
- if (icon_height = icon_width * (1 / icon_aspect_ratio)) > box_height
602
- icon_width *= box_height / icon_height
603
- icon_height = box_height
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 :convert_quote :convert_quote_or_verse
745
- alias :convert_verse :convert_quote_or_verse
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 term.text, style: @theme.description_list_term_font_style.to_sym, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
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 %(asciidoctor: WARNING: unknown unordered list style: #{candidate})
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 << Bullets[bullet_type]
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
- marker = @list_bullets[-1]
992
- if marker == :checkbox
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
- marker = BallotBox[(node.attr? 'checked', nil, false) ? :checked : :unchecked]
995
- else
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 %(asciidoctor: WARNING: unknown list type #{list_type.inspect})
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
- marker_width = rendered_width_of_string marker
1011
- start_position = -marker_width + -(rendered_width_of_char 'x')
1012
- float do
1013
- flow_bounding_box start_position, width: marker_width do
1014
- layout_prose marker,
1015
- align: :right,
1016
- color: (@theme.outline_list_marker_font_color || @font_color),
1017
- normalize: false,
1018
- inline_format: false,
1019
- margin: 0,
1020
- character_spacing: -0.5,
1021
- single_line: true
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 %(asciidoctor: WARNING: GIF image format not supported. Install the prawn-gmagick gem or convert #{target} to PNG.) unless scratch?
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 %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path || target}) unless scratch?
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 (width_relative_to_page = ViewportWidth === width)
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 width_relative_to_page do
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 = ::IO.read image_path
1210
+ svg_data = ::File.read image_path
1089
1211
  file_request_root = ::File.dirname image_path
1090
1212
  end
1091
- svg_obj = ::Prawn::Svg::Interface.new svg_data, self,
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 => e
1164
- on_image_error :exception, node, target, (opts.merge message: %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.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
- result = lexer.highlight source_string, options: lexer_opts
1324
- fragments = guard_indentation text_formatter.format result
1325
- conum_mapping ? (restore_conums fragments, conum_mapping, num_trailing_spaces) : fragments
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
- # NOTE trailing endline is added to address https://github.com/jneen/rouge/issues/279
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
- if source_string =~ BuiltInEntityCharOrTagRx
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 :convert_listing :convert_listing_or_literal
1407
- alias :convert_literal :convert_listing_or_literal
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
- # TODO need to also add top and bottom padding from line metrics
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
- # TODO need to also add top and bottom padding from line metrics
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', false)
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', false)
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', 'even', false
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 :convert_horizontal_rule :convert_thematic_break
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
- advance_page unless at_page_top?
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
- %(<a name="#{node.target}">#{DummyText}</a>)
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
- %(<a name="#{target = node.target}">#{DummyText}</a>[#{target}])
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 %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
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
- #text = node.document.footnotes.find {|fn| fn.index == index }.text
1934
- %( <color rgb="#999999">[#{index}: #{node.text}]</color>)
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
- size = (size = (node.attr 'size')) == 'lg' ? '1.3333em' : (size.sub 'x', 'em')
1951
- size_attr = %( size="#{size}")
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 = nil
2115
+ size_attr = ''
1954
2116
  end
1955
2117
  begin
1956
- # TODO support rotate and flip attributes; support fw (full-width) size
1957
- %(<font name="#{icon_set}"#{size_attr}>#{::Prawn::Icon::FontData.load(self, icon_set).unicode icon_name}</font>)
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 %(asciidoctor: WARNING: #{icon_name} is not a valid icon name in the #{icon_set} icon set)
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 %(asciidoctor: WARNING: GIF image format not supported. Install the prawn-gmagick gem or convert #{target} to PNG.) unless scratch?
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 %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path || target}) unless scratch?
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 = @theme.menu_caret_content || %( \u203a )
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 %(asciidoctor: WARNING: #{face} cover image not found or readable: #{cover_image})
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 :start_new_part :start_new_chapter
2217
- alias :layout_part_title :layout_chapter_title
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
- string = subject.title? ? subject.captioned_title : nil
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
- typeset_formatted_text sect_title_fragments, line_metrics
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 = ::IO.read img_path
2717
- svg_obj = ::Prawn::Svg::Interface.new svg_data, self,
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 => e
2739
- warn %(asciidoctor: WARNING: could not embed image in running content: #{img_path}; #{e.message})
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).truncate_to_precision 4
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.truncate_to_precision 4
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.new ['image-', image_format && %(.#{image_format})]
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 %(asciidoctor: WARNING: allow-uri-read is not enabled; cannot embed remote image: #{image_path}) unless scratch?
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.new ['image-', image_format && %(.#{image_format})]
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 %(asciidoctor: WARNING: #{key.tr '-', ' '} not found or readable: #{bg_image})
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 => e
3307
- warn %(asciidoctor: WARNING: could not delete temporary image: #{path}; #{e.message})
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 &amp;)
3311
3513
  def breakable_uri uri
3312
3514
  scheme, address = uri.split UriSchemeBoundaryRx, 2
3313
3515
  address, scheme = scheme, address unless address
3314
- address = address.gsub UriBreakCharsRx, UriBreakCharRepl
3315
- # NOTE require at least two characters after a break
3316
- address.slice!(-2) if address[-2] == ZeroWidthSpace
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