asciidoctor-pdf 1.5.0.beta.6 → 1.5.0.beta.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -185,7 +185,7 @@ class Converter < ::Prawn::Document
185
185
  blk_0 = blk_1 = preface = nil
186
186
  end
187
187
 
188
- on_page_create &(method :init_page)
188
+ on_page_create(&(method :init_page))
189
189
 
190
190
  marked_page_number = page_number
191
191
  # NOTE a new page will already be started (page_number = 2) if the front cover image is a PDF
@@ -207,58 +207,62 @@ class Converter < ::Prawn::Document
207
207
  end if doc.header? && !doc.notitle
208
208
  end
209
209
 
210
- toc_num_levels = (doc.attr 'toclevels', 2).to_i
211
- if (insert_toc = (doc.attr? 'toc') && !(doc.attr? 'toc-placement', 'macro') && doc.sections?)
212
- start_new_page if @ppbook && verso_page?
213
- allocate_toc doc, toc_num_levels, @y, use_title_page
214
- else
215
- @toc_extent = nil
216
- end
210
+ num_front_matter_pages = toc_page_nums = toc_num_levels = nil
217
211
 
218
- start_new_page if @ppbook && verso_page?
212
+ indent_section do
213
+ toc_num_levels = (doc.attr 'toclevels', 2).to_i
214
+ if (insert_toc = (doc.attr? 'toc') && !(doc.attr? 'toc-placement', 'macro') && doc.sections?)
215
+ start_new_page if @ppbook && verso_page?
216
+ allocate_toc doc, toc_num_levels, @y, use_title_page
217
+ else
218
+ @toc_extent = nil
219
+ end
219
220
 
220
- if use_title_page
221
- zero_page_offset = has_front_cover ? 1 : 0
222
- first_page_offset = has_title_page ? zero_page_offset.next : zero_page_offset
223
- body_offset = (body_start_page_number = page_number) - 1
224
- running_content_start_at = @theme.running_content_start_at || 'body'
225
- running_content_start_at = 'toc' if running_content_start_at == 'title' && !has_title_page
226
- running_content_start_at = 'body' if running_content_start_at == 'toc' && !insert_toc
227
- page_numbering_start_at = @theme.page_numbering_start_at || 'body'
228
- page_numbering_start_at = 'toc' if page_numbering_start_at == 'title' && !has_title_page
229
- page_numbering_start_at = 'body' if page_numbering_start_at == 'toc' && !insert_toc
230
- front_matter_sig = [running_content_start_at, page_numbering_start_at]
231
- # table values are number of pages to skip before starting running content and page numbering, respectively
232
- num_front_matter_pages = {
233
- ['title', 'title'] => [zero_page_offset, zero_page_offset],
234
- ['title', 'toc'] => [zero_page_offset, first_page_offset],
235
- ['title', 'body'] => [zero_page_offset, body_offset],
236
- ['toc', 'title'] => [first_page_offset, zero_page_offset],
237
- ['toc', 'toc'] => [first_page_offset, first_page_offset],
238
- ['toc', 'body'] => [first_page_offset, body_offset],
239
- ['body', 'title'] => [body_offset, zero_page_offset],
240
- ['body', 'toc'] => [body_offset, first_page_offset],
241
- }[front_matter_sig] || [body_offset, body_offset]
242
- else
243
- num_front_matter_pages = [body_start_page_number - 1] * 2
244
- end
221
+ start_new_page if @ppbook && verso_page?
245
222
 
246
- @index.start_page_number = num_front_matter_pages[1] + 1
247
- doc.set_attr 'pdf-anchor', (doc_anchor = derive_anchor_from_id doc.id, 'top')
248
- add_dest_for_block doc, doc_anchor
223
+ if use_title_page
224
+ zero_page_offset = has_front_cover ? 1 : 0
225
+ first_page_offset = has_title_page ? zero_page_offset.next : zero_page_offset
226
+ body_offset = (body_start_page_number = page_number) - 1
227
+ running_content_start_at = @theme.running_content_start_at || 'body'
228
+ running_content_start_at = 'toc' if running_content_start_at == 'title' && !has_title_page
229
+ running_content_start_at = 'body' if running_content_start_at == 'toc' && !insert_toc
230
+ page_numbering_start_at = @theme.page_numbering_start_at || 'body'
231
+ page_numbering_start_at = 'toc' if page_numbering_start_at == 'title' && !has_title_page
232
+ page_numbering_start_at = 'body' if page_numbering_start_at == 'toc' && !insert_toc
233
+ front_matter_sig = [running_content_start_at, page_numbering_start_at]
234
+ # table values are number of pages to skip before starting running content and page numbering, respectively
235
+ num_front_matter_pages = {
236
+ ['title', 'title'] => [zero_page_offset, zero_page_offset],
237
+ ['title', 'toc'] => [zero_page_offset, first_page_offset],
238
+ ['title', 'body'] => [zero_page_offset, body_offset],
239
+ ['toc', 'title'] => [first_page_offset, zero_page_offset],
240
+ ['toc', 'toc'] => [first_page_offset, first_page_offset],
241
+ ['toc', 'body'] => [first_page_offset, body_offset],
242
+ ['body', 'title'] => [body_offset, zero_page_offset],
243
+ ['body', 'toc'] => [body_offset, first_page_offset],
244
+ }[front_matter_sig] || [body_offset, body_offset]
245
+ else
246
+ num_front_matter_pages = [body_start_page_number - 1] * 2
247
+ end
249
248
 
250
- convert_section generate_manname_section doc if doc.doctype == 'manpage' && (doc.attr? 'manpurpose')
249
+ @index.start_page_number = num_front_matter_pages[1] + 1
250
+ doc.set_attr 'pdf-anchor', (doc_anchor = derive_anchor_from_id doc.id, 'top')
251
+ add_dest_for_block doc, doc_anchor
251
252
 
252
- convert_content_for_block doc
253
+ convert_section generate_manname_section doc if doc.doctype == 'manpage' && (doc.attr? 'manpurpose')
253
254
 
254
- # NOTE for a book, these are leftover footnotes; for an article this is everything
255
- layout_footnotes doc
255
+ convert_content_for_block doc
256
256
 
257
- # NOTE delete orphaned page (a page was created but there was no additional content)
258
- # QUESTION should we delete page if document is empty? (leaving no pages?)
259
- delete_page if page.empty? && page_count > 1
257
+ # NOTE for a book, these are leftover footnotes; for an article this is everything
258
+ outdent_section { layout_footnotes doc }
260
259
 
261
- toc_page_nums = @toc_extent ? (layout_toc doc, toc_num_levels, @toc_extent[:page_nums].first, @toc_extent[:start_y], num_front_matter_pages[1]) : []
260
+ # NOTE delete orphaned page (a page was created but there was no additional content)
261
+ # QUESTION should we delete page if document is empty? (leaving no pages?)
262
+ delete_page if page.empty? && page_count > 1
263
+
264
+ toc_page_nums = @toc_extent ? (layout_toc doc, toc_num_levels, @toc_extent[:page_nums].first, @toc_extent[:start_y], num_front_matter_pages[1]) : []
265
+ end
262
266
 
263
267
  unless page_count < body_start_page_number
264
268
  unless doc.noheader || @theme.header_height.to_f.zero?
@@ -343,6 +347,7 @@ class Converter < ::Prawn::Document
343
347
  from, to = r.rstrip.split '-', 2
344
348
  to ? ((get_char from)..(get_char to)).to_a : [(get_char from)]
345
349
  }.flatten
350
+ @section_indent = (val = @theme.section_indent) && (inflate_indent val)
346
351
  @index = IndexCatalog.new
347
352
  # NOTE we have to init Pdfmark class here while we have reference to the doc
348
353
  @pdfmark = (doc.attr? 'pdfmark') ? (Pdfmark.new doc) : nil
@@ -458,7 +463,9 @@ class Converter < ::Prawn::Document
458
463
  def build_pdf_info doc
459
464
  info = {}
460
465
  # FIXME use sanitize: :plain_text once available
461
- info[:Title] = sanitize(doc.doctitle use_fallback: true).as_pdf
466
+ if (doctitle = doc.header? ? doc.doctitle : (doc.attr 'untitled-label'))
467
+ info[:Title] = (sanitize doctitle).as_pdf
468
+ end
462
469
  info[:Author] = (doc.attr 'authors').as_pdf if doc.attr? 'authors'
463
470
  info[:Subject] = (doc.attr 'subject').as_pdf if doc.attr? 'subject'
464
471
  info[:Keywords] = (doc.attr 'keywords').as_pdf if doc.attr? 'keywords'
@@ -536,22 +543,35 @@ class Converter < ::Prawn::Document
536
543
  elsif type == :chapter
537
544
  layout_chapter_title sect, title, align: align, level: hlevel
538
545
  else
539
- layout_heading title, align: align, level: hlevel
546
+ layout_heading title, align: align, level: hlevel, outdent: true
540
547
  end
541
548
  end
542
549
 
543
- if (section_indent = @theme.section_indent)
544
- indent_l, indent_r = inflate_indent section_indent
545
- indent indent_l, indent_r do
546
- sect.sectname == 'index' ? (convert_index_section sect) : (convert_content_for_block sect)
547
- end
550
+ if sect.sectname == 'index'
551
+ outdent_section { convert_index_section sect }
548
552
  else
549
- sect.sectname == 'index' ? (convert_index_section sect) : (convert_content_for_block sect)
553
+ convert_content_for_block sect
550
554
  end
551
- layout_footnotes sect if type == :chapter
555
+ outdent_section { layout_footnotes sect } if type == :chapter
552
556
  sect.set_attr 'pdf-page-end', page_number
553
557
  end
554
558
 
559
+ def indent_section
560
+ if (values = @section_indent)
561
+ indent(values[0], values[1]) { yield }
562
+ else
563
+ yield
564
+ end
565
+ end
566
+
567
+ def outdent_section enabled = true
568
+ if enabled && (values = @section_indent)
569
+ indent(-values[0], -values[1]) { yield }
570
+ else
571
+ yield
572
+ end
573
+ end
574
+
555
575
  # QUESTION if a footnote ref appears in a separate chapter, should the footnote def be duplicated?
556
576
  def layout_footnotes node
557
577
  return if (fns = (doc = node.document).footnotes - @footnotes).empty?
@@ -571,59 +591,60 @@ class Converter < ::Prawn::Document
571
591
  add_dest_for_block node if node.id
572
592
  # QUESTION should we decouple styles from section titles?
573
593
  theme_font :heading, level: (hlevel = node.level + 1) do
574
- layout_heading node.title, align: (@theme[%(heading_h#{hlevel}_align)] || @theme.heading_align || @base_align).to_sym, level: hlevel
594
+ layout_heading node.title, align: (@theme[%(heading_h#{hlevel}_align)] || @theme.heading_align || @base_align).to_sym, level: hlevel, outdent: (node.parent.context == :section)
575
595
  end
576
596
  end
577
597
 
578
598
  def convert_abstract node
579
599
  add_dest_for_block node if node.id
580
- pad_box @theme.abstract_padding do
581
- theme_font :abstract_title do
582
- layout_heading node.title, align: (@theme.abstract_title_align || @base_align).to_sym, margin_top: (@theme.heading_margin_top || 0), margin_bottom: (@theme.heading_margin_bottom || 0)
583
- end if node.title?
584
- theme_font :abstract do
585
- prose_opts = { line_height: @theme.abstract_line_height, align: (initial_alignment = (@theme.abstract_align || @base_align).to_sym) }
586
- if (text_indent = @theme.prose_text_indent)
587
- prose_opts[:indent_paragraphs] = text_indent
588
- end
589
- # FIXME control more first_line_options using theme
590
- if (line1_font_style = @theme.abstract_first_line_font_style) && line1_font_style.to_sym != font_style
591
- prose_opts[:first_line_options] = { styles: [font_style, line1_font_style.to_sym] }
592
- end
593
- # FIXME make this cleaner!!
594
- if node.blocks?
595
- node.blocks.each do |child|
596
- # FIXME is playback necessary here?
597
- child.document.playback_attributes child.attributes
598
- if child.context == :paragraph
599
- if (alignment = resolve_alignment_from_role child.roles)
600
- prose_opts[:align] = alignment
600
+ outdent_section do
601
+ pad_box @theme.abstract_padding do
602
+ theme_font :abstract_title do
603
+ layout_prose node.title, align: (@theme.abstract_title_align || @base_align).to_sym, margin_top: (@theme.heading_margin_top || 0), margin_bottom: (@theme.heading_margin_bottom || 0), line_height: @theme.heading_line_height
604
+ end if node.title?
605
+ theme_font :abstract do
606
+ prose_opts = { line_height: @theme.abstract_line_height, align: (initial_alignment = (@theme.abstract_align || @base_align).to_sym) }
607
+ if (text_indent = @theme.prose_text_indent)
608
+ prose_opts[:indent_paragraphs] = text_indent
609
+ end
610
+ # FIXME control more first_line_options using theme
611
+ if (line1_font_style = @theme.abstract_first_line_font_style) && line1_font_style.to_sym != font_style
612
+ prose_opts[:first_line_options] = { styles: [font_style, line1_font_style.to_sym] }
613
+ end
614
+ # FIXME make this cleaner!!
615
+ if node.blocks?
616
+ node.blocks.each do |child|
617
+ # FIXME is playback necessary here?
618
+ child.document.playback_attributes child.attributes
619
+ if child.context == :paragraph
620
+ if (alignment = resolve_alignment_from_role child.roles)
621
+ prose_opts[:align] = alignment
622
+ end
623
+ layout_prose child.content, prose_opts
624
+ prose_opts.delete :first_line_options
625
+ prose_opts[:align] = initial_alignment
626
+ else
627
+ # FIXME this could do strange things if the wrong kind of content shows up
628
+ convert_content_for_block child
601
629
  end
602
- layout_prose child.content, prose_opts
603
- prose_opts.delete :first_line_options
604
- prose_opts[:align] = initial_alignment
605
- else
606
- # FIXME this could do strange things if the wrong kind of content shows up
607
- convert_content_for_block child
608
630
  end
631
+ elsif node.content_model != :compound && (string = node.content)
632
+ if (alignment = resolve_alignment_from_role node.roles)
633
+ prose_opts[:align] = alignment
634
+ end
635
+ layout_prose string, prose_opts
609
636
  end
610
- elsif node.content_model != :compound && (string = node.content)
611
- if (alignment = resolve_alignment_from_role node.roles)
612
- prose_opts[:align] = alignment
613
- end
614
- layout_prose string, prose_opts
615
637
  end
616
638
  end
639
+ # QUESTION should we be adding margin below the abstract??
640
+ #theme_margin :block, :bottom
617
641
  end
618
- # QUESTION should we be adding margin below the abstract??
619
- #theme_margin :block, :bottom
620
642
  end
621
643
 
622
644
  def convert_preamble node
623
- # TODO find_by needs to support a depth argument
624
645
  # FIXME core should not be promoting paragraph to preamble if there are no sections
625
- if (first_p = (node.find_by context: :paragraph)[0]) && first_p.parent == node && node.document.sections?
626
- first_p.add_role 'lead'
646
+ if node.blocks? && (first_block = node.blocks[0]).context == :paragraph && node.document.sections?
647
+ first_block.add_role 'lead' unless first_block.role?
627
648
  end
628
649
  convert_content_for_block node
629
650
  end
@@ -1024,7 +1045,7 @@ class Converter < ::Prawn::Document
1024
1045
  pad_box @theme.sidebar_padding do
1025
1046
  theme_font :sidebar_title do
1026
1047
  # QUESTION should we allow margins of sidebar title to be customized?
1027
- layout_heading node.title, align: (@theme.sidebar_title_align || @base_align).to_sym, margin_top: 0, margin_bottom: (@theme.heading_margin_bottom || 0)
1048
+ layout_prose node.title, align: (@theme.sidebar_title_align || @base_align).to_sym, margin_top: 0, margin_bottom: (@theme.heading_margin_bottom || 0), line_height: @theme.heading_line_height
1028
1049
  end if node.title?
1029
1050
  theme_font :sidebar do
1030
1051
  convert_content_for_block node
@@ -1418,7 +1439,13 @@ class Converter < ::Prawn::Document
1418
1439
  if ::File.readable? image_path
1419
1440
  # NOTE import_page automatically advances to next page afterwards
1420
1441
  # QUESTION should we add destination to top of imported page?
1421
- import_page image_path, page: [(node.attr 'page').to_i, 1].max, replace: page.empty?
1442
+ if (pgnums = node.attr 'pages', nil, false)
1443
+ (resolve_pagenums pgnums).each_with_index do |pgnum, idx|
1444
+ import_page image_path, page: pgnum, replace: (idx == 0 ? page.empty? : true)
1445
+ end
1446
+ else
1447
+ import_page image_path, page: [(node.attr 'page', nil, 1).to_i, 1].max, replace: page.empty?
1448
+ end
1422
1449
  else
1423
1450
  # QUESTION should we use alt text in this case?
1424
1451
  logger.warn %(pdf to insert not found or not readable: #{image_path})
@@ -1545,7 +1572,7 @@ class Converter < ::Prawn::Document
1545
1572
  end
1546
1573
 
1547
1574
  def draw_image_border top, w, h, alignment
1548
- if (b_width = @theme.image_border_width || 0) > 0 && @theme.image_border_color
1575
+ if (@theme.image_border_width || 0) > 0 && @theme.image_border_color
1549
1576
  if (@theme.image_border_fit || 'content') == 'auto'
1550
1577
  bb_width = bounds.width
1551
1578
  elsif alignment == :center
@@ -1953,7 +1980,7 @@ class Converter < ::Prawn::Document
1953
1980
  rowspan: cell.rowspan || 1,
1954
1981
  align: (cell.attr 'halign', nil, false).to_sym,
1955
1982
  valign: (val = cell.attr 'valign', nil, false) == 'middle' ? :center : val.to_sym,
1956
- padding: theme.table_cell_padding
1983
+ padding: theme.table_cell_padding,
1957
1984
  }
1958
1985
  cell_transform = nil
1959
1986
  case cell.style
@@ -2490,7 +2517,7 @@ class Converter < ::Prawn::Document
2490
2517
 
2491
2518
  def convert_inline_indexterm node
2492
2519
  # NOTE indexterms not supported if text gets substituted before PDF is initialized
2493
- return '' unless instance_variable_defined? :@index
2520
+ return '' unless defined? @index
2494
2521
  if scratch?
2495
2522
  node.type == :visible ? node.text : ''
2496
2523
  else
@@ -2596,7 +2623,7 @@ class Converter < ::Prawn::Document
2596
2623
  title_align = (@theme.title_page_align || @base_align).to_sym
2597
2624
 
2598
2625
  # TODO disallow .pdf as image type
2599
- if (logo_image_path = (doc.attr 'title-logo-image') || (logo_image_from_theme = @theme.title_page_logo_image))
2626
+ if @theme.title_page_logo_display != 'none' && (logo_image_path = (doc.attr 'title-logo-image') || (logo_image_from_theme = @theme.title_page_logo_image))
2600
2627
  if (logo_image_path.include? ':') && logo_image_path =~ ImageAttributeValueRx
2601
2628
  logo_image_attrs = (AttributeList.new $2).parse ['alt', 'width', 'height']
2602
2629
  relative_to_imagesdir = true
@@ -2607,7 +2634,9 @@ class Converter < ::Prawn::Document
2607
2634
  logo_image_path = ThemeLoader.resolve_theme_asset (sub_attributes_discretely doc, logo_image_path), @themesdir if logo_image_from_theme
2608
2635
  end
2609
2636
  logo_image_attrs['target'] = logo_image_path
2610
- logo_image_attrs['align'] ||= (@theme.title_page_logo_align || title_align.to_s)
2637
+ if (logo_align = [(logo_image_attrs.delete 'align'), @theme.title_page_logo_align, title_align.to_s].find {|val| (BlockAlignmentNames.include? val) })
2638
+ logo_image_attrs['align'] = logo_align
2639
+ end
2611
2640
  # QUESTION should we allow theme to turn logo image off?
2612
2641
  logo_image_top = logo_image_attrs['top'] || @theme.title_page_logo_top || '10%'
2613
2642
  # FIXME delegate to method to convert page % to y value
@@ -2628,7 +2657,6 @@ class Converter < ::Prawn::Document
2628
2657
 
2629
2658
  # TODO prevent content from spilling to next page
2630
2659
  theme_font :title_page do
2631
- doctitle = doc.doctitle partition: true
2632
2660
  if (title_top = @theme.title_page_title_top)
2633
2661
  if title_top.end_with? 'vh'
2634
2662
  title_top = page_height - page_height * title_top.to_f / 100.0
@@ -2638,21 +2666,24 @@ class Converter < ::Prawn::Document
2638
2666
  # FIXME delegate to method to convert page % to y value
2639
2667
  @y = title_top
2640
2668
  end
2641
- move_down(@theme.title_page_title_margin_top || 0)
2642
- indent (@theme.title_page_title_margin_left || 0), (@theme.title_page_title_margin_right || 0) do
2643
- theme_font :title_page_title do
2644
- layout_heading doctitle.main,
2645
- align: title_align,
2646
- margin: 0,
2647
- line_height: @theme.title_page_title_line_height
2669
+ unless @theme.title_page_title_display == 'none'
2670
+ doctitle = doc.doctitle partition: true
2671
+ move_down(@theme.title_page_title_margin_top || 0)
2672
+ indent (@theme.title_page_title_margin_left || 0), (@theme.title_page_title_margin_right || 0) do
2673
+ theme_font :title_page_title do
2674
+ layout_prose doctitle.main,
2675
+ align: title_align,
2676
+ margin: 0,
2677
+ line_height: @theme.title_page_title_line_height
2678
+ end
2648
2679
  end
2680
+ move_down(@theme.title_page_title_margin_bottom || 0)
2649
2681
  end
2650
- move_down(@theme.title_page_title_margin_bottom || 0)
2651
- if doctitle.subtitle
2682
+ if @theme.title_page_subtitle_display != 'none' && (subtitle = (doctitle || (doc.doctitle partition: true)).subtitle)
2652
2683
  move_down(@theme.title_page_subtitle_margin_top || 0)
2653
2684
  indent (@theme.title_page_subtitle_margin_left || 0), (@theme.title_page_subtitle_margin_right || 0) do
2654
2685
  theme_font :title_page_subtitle do
2655
- layout_heading doctitle.subtitle,
2686
+ layout_prose subtitle,
2656
2687
  align: title_align,
2657
2688
  margin: 0,
2658
2689
  line_height: @theme.title_page_subtitle_line_height
@@ -2660,7 +2691,7 @@ class Converter < ::Prawn::Document
2660
2691
  end
2661
2692
  move_down(@theme.title_page_subtitle_margin_bottom || 0)
2662
2693
  end
2663
- if doc.attr? 'authors'
2694
+ if @theme.title_page_authors_display != 'none' && (doc.attr? 'authors')
2664
2695
  move_down(@theme.title_page_authors_margin_top || 0)
2665
2696
  indent (@theme.title_page_authors_margin_left || 0), (@theme.title_page_authors_margin_right || 0) do
2666
2697
  # TODO provide an API in core to get authors as an array
@@ -2676,8 +2707,7 @@ class Converter < ::Prawn::Document
2676
2707
  end
2677
2708
  move_down(@theme.title_page_authors_margin_bottom || 0)
2678
2709
  end
2679
- revision_info = [(doc.attr? 'revnumber') ? %(#{doc.attr 'version-label'} #{doc.attr 'revnumber'}) : nil, (doc.attr 'revdate')].compact
2680
- unless revision_info.empty?
2710
+ unless @theme.title_page_revision_display == 'none' || (revision_info = [(doc.attr? 'revnumber') ? %(#{doc.attr 'version-label'} #{doc.attr 'revnumber'}) : nil, (doc.attr 'revdate')].compact).empty?
2681
2711
  move_down(@theme.title_page_revision_margin_top || 0)
2682
2712
  revision_text = revision_info.join (@theme.title_page_revision_delimiter || ', ')
2683
2713
  if (revremark = doc.attr 'revremark')
@@ -2694,6 +2724,8 @@ class Converter < ::Prawn::Document
2694
2724
  move_down(@theme.title_page_revision_margin_bottom || 0)
2695
2725
  end
2696
2726
  end
2727
+
2728
+ layout_prose DummyText, margin: 0, line_height: 1, normalize: false if page.empty?
2697
2729
  end
2698
2730
 
2699
2731
  def layout_cover_page doc, face
@@ -2748,7 +2780,7 @@ class Converter < ::Prawn::Document
2748
2780
  end
2749
2781
 
2750
2782
  def layout_chapter_title node, title, opts = {}
2751
- layout_heading title, opts
2783
+ layout_heading title, (opts.merge outdent: true)
2752
2784
  end
2753
2785
 
2754
2786
  alias start_new_part start_new_chapter
@@ -2772,13 +2804,15 @@ class Converter < ::Prawn::Document
2772
2804
  if (transform = resolve_text_transform opts)
2773
2805
  string = transform_text string, transform
2774
2806
  end
2775
- margin_top top_margin
2776
- typeset_text string, calc_line_metrics((opts.delete :line_height) || (hlevel ? @theme[%(heading_h#{hlevel}_line_height)] : nil) || @theme.heading_line_height || @theme.base_line_height), {
2777
- color: @font_color,
2778
- inline_format: true,
2779
- align: @base_align.to_sym
2780
- }.merge(opts)
2781
- margin_bottom bot_margin
2807
+ outdent_section(opts.delete :outdent) do
2808
+ margin_top top_margin
2809
+ typeset_text string, calc_line_metrics((opts.delete :line_height) || (hlevel ? @theme[%(heading_h#{hlevel}_line_height)] : nil) || @theme.heading_line_height || @theme.base_line_height), {
2810
+ color: @font_color,
2811
+ inline_format: true,
2812
+ align: @base_align.to_sym
2813
+ }.merge(opts)
2814
+ margin_bottom bot_margin
2815
+ end
2782
2816
  end
2783
2817
 
2784
2818
  # NOTE inline_format is true by default
@@ -2915,7 +2949,7 @@ class Converter < ::Prawn::Document
2915
2949
  theme_font :heading, level: 2 do
2916
2950
  theme_font :toc_title do
2917
2951
  toc_title_align = (@theme.toc_title_align || @theme.heading_h2_align || @theme.heading_align || @base_align).to_sym
2918
- layout_heading toc_title, align: toc_title_align, level: 2
2952
+ layout_heading toc_title, align: toc_title_align, level: 2, outdent: true
2919
2953
  end
2920
2954
  end
2921
2955
  end
@@ -3040,12 +3074,12 @@ class Converter < ::Prawn::Document
3040
3074
 
3041
3075
  # TODO delegate to layout_page_header and layout_page_footer per page
3042
3076
  def layout_running_content periphery, doc, opts = {}
3043
- skip, skip_pagenums, body_start_page_number = opts[:skip] || [1, 1]
3077
+ skip, skip_pagenums = opts[:skip] || [1, 1]
3044
3078
  body_start_page_number = opts[:body_start_page_number] || 1
3045
3079
  # NOTE find and advance to first non-imported content page to use as model page
3046
3080
  return unless (content_start_page = state.pages[skip..-1].index {|it| !it.imported_page? })
3047
3081
  content_start_page += (skip + 1)
3048
- num_pages = page_count - skip
3082
+ num_pages = page_count
3049
3083
  prev_page_number = page_number
3050
3084
  go_to_page content_start_page
3051
3085
 
@@ -3054,34 +3088,37 @@ class Converter < ::Prawn::Document
3054
3088
  header = doc.header? ? doc.header : nil
3055
3089
  sectlevels = (@theme[%(#{periphery}_sectlevels)] || 2).to_i
3056
3090
  sections = doc.find_by(context: :section) {|sect| sect.level <= sectlevels && sect != header } || []
3091
+ if (toc_page_nums = @toc_extent && @toc_extent[:page_nums])
3092
+ toc_title = (doc.attr 'toc-title') || ''
3093
+ end
3057
3094
 
3058
3095
  title_method = TitleStyles[@theme[%(#{periphery}_title_style)]]
3059
3096
  # FIXME we need a proper model for all this page counting
3060
3097
  # FIXME we make a big assumption that part & chapter start on new pages
3061
- # index parts, chapters and sections by the visual page number on which they start
3098
+ # index parts, chapters and sections by the physical page number on which they start
3062
3099
  part_start_pages = {}
3063
3100
  chapter_start_pages = {}
3064
3101
  section_start_pages = {}
3065
3102
  trailing_section_start_pages = {}
3066
3103
  sections.each do |sect|
3067
- page_num = (sect.attr 'pdf-page-start').to_i - skip_pagenums
3104
+ pgnum = (sect.attr 'pdf-page-start').to_i
3068
3105
  if is_book && ((sect_is_part = sect.part?) || sect.chapter?)
3069
3106
  if sect_is_part
3070
- part_start_pages[page_num] ||= sect.send(*title_method)
3107
+ part_start_pages[pgnum] ||= sect.send(*title_method)
3071
3108
  else
3072
- chapter_start_pages[page_num] ||= sect.send(*title_method)
3109
+ chapter_start_pages[pgnum] ||= sect.send(*title_method)
3073
3110
  if sect.sectname == 'appendix' && !part_start_pages.empty?
3074
3111
  # FIXME need a better way to indicate that part has ended
3075
- part_start_pages[page_num] = ''
3112
+ part_start_pages[pgnum] = ''
3076
3113
  end
3077
3114
  end
3078
3115
  else
3079
- sect_title = trailing_section_start_pages[page_num] = sect.send(*title_method)
3080
- section_start_pages[page_num] ||= sect_title
3116
+ sect_title = trailing_section_start_pages[pgnum] = sect.send(*title_method)
3117
+ section_start_pages[pgnum] ||= sect_title
3081
3118
  end
3082
3119
  end
3083
3120
 
3084
- # index parts, chapters, and sections by the visual page number on which they appear
3121
+ # index parts, chapters, and sections by the physical page number on which they appear
3085
3122
  parts_by_page = {}
3086
3123
  chapters_by_page = {}
3087
3124
  sections_by_page = {}
@@ -3091,42 +3128,42 @@ class Converter < ::Prawn::Document
3091
3128
  last_chap = is_book ? :pre : nil
3092
3129
  last_sect = nil
3093
3130
  sect_search_threshold = 1
3094
- (1..num_pages).each do |num|
3095
- if (part = part_start_pages[num])
3131
+ (1..num_pages).each do |pgnum|
3132
+ if (part = part_start_pages[pgnum])
3096
3133
  last_part = part
3097
3134
  last_chap = nil
3098
3135
  last_sect = nil
3099
3136
  end
3100
- if (chap = chapter_start_pages[num])
3137
+ if (chap = chapter_start_pages[pgnum])
3101
3138
  last_chap = chap
3102
3139
  last_sect = nil
3103
3140
  end
3104
- if (sect = section_start_pages[num])
3141
+ if (sect = section_start_pages[pgnum])
3105
3142
  last_sect = sect
3106
3143
  elsif part || chap
3107
- sect_search_threshold = num
3144
+ sect_search_threshold = pgnum
3108
3145
  # NOTE we didn't find a section on this page; look back to find last section started
3109
3146
  elsif last_sect
3110
- ((sect_search_threshold)..(num - 1)).reverse_each do |prev|
3147
+ ((sect_search_threshold)..(pgnum - 1)).reverse_each do |prev|
3111
3148
  if (sect = trailing_section_start_pages[prev])
3112
3149
  last_sect = sect
3113
3150
  break
3114
3151
  end
3115
3152
  end
3116
3153
  end
3117
- parts_by_page[num] = last_part
3154
+ parts_by_page[pgnum] = last_part
3118
3155
  if last_chap == :pre
3119
- if num == 1
3120
- chapters_by_page[num] = doc.doctitle
3121
- elsif num >= body_start_page_number
3122
- chapters_by_page[num] = is_book ? (doc.attr 'preface-title', 'Preface') : nil
3156
+ if pgnum >= body_start_page_number
3157
+ chapters_by_page[pgnum] = is_book ? (doc.attr 'preface-title', 'Preface') : nil
3158
+ elsif toc_page_nums && (toc_page_nums.cover? pgnum)
3159
+ chapters_by_page[pgnum] = toc_title
3123
3160
  else
3124
- chapters_by_page[num] = doc.attr 'toc-title'
3161
+ chapters_by_page[pgnum] = doc.doctitle
3125
3162
  end
3126
3163
  else
3127
- chapters_by_page[num] = last_chap
3164
+ chapters_by_page[pgnum] = last_chap
3128
3165
  end
3129
- sections_by_page[num] = last_sect
3166
+ sections_by_page[pgnum] = last_sect
3130
3167
  end
3131
3168
 
3132
3169
  doctitle = doc.doctitle partition: true, use_fallback: true
@@ -3134,7 +3171,7 @@ class Converter < ::Prawn::Document
3134
3171
  doc.set_attr 'doctitle', doctitle.combined
3135
3172
  doc.set_attr 'document-title', doctitle.main
3136
3173
  doc.set_attr 'document-subtitle', doctitle.subtitle
3137
- doc.set_attr 'page-count', num_pages
3174
+ doc.set_attr 'page-count', (num_pages - skip_pagenums)
3138
3175
 
3139
3176
  pagenums_enabled = doc.attr? 'pagenums'
3140
3177
  case @media == 'prepress' ? 'physical' : (doc.attr 'pdf-folio-placement')
@@ -3148,24 +3185,36 @@ class Converter < ::Prawn::Document
3148
3185
  folio_basis, invert_folio = :virtual, false
3149
3186
  end
3150
3187
  periphery_layout_cache = {}
3151
- repeat((content_start_page..page_count), dynamic: true) do
3188
+ repeat((content_start_page..num_pages), dynamic: true) do
3152
3189
  # NOTE don't write on pages which are imported / inserts (otherwise we can get a corrupt PDF)
3153
3190
  next if page.imported_page?
3154
- pgnum_label = page_number - skip_pagenums
3155
- pgnum_label = (RomanNumeral.new page_number, :lower) if pgnum_label < 1
3156
- side = page_side((folio_basis == :physical ? page_number : pgnum_label), invert_folio)
3191
+ virtual_pgnum = (pgnum = page_number) - skip_pagenums
3192
+ pgnum_label = (virtual_pgnum < 1 ? (RomanNumeral.new pgnum, :lower) : virtual_pgnum).to_s
3193
+ side = page_side((folio_basis == :physical ? pgnum : virtual_pgnum), invert_folio)
3157
3194
  # QUESTION should allocation be per side?
3158
3195
  trim_styles, colspec_dict, content_dict, stamp_names = allocate_running_content_layout page, periphery, periphery_layout_cache
3159
3196
  # FIXME we need to have a content setting for chapter pages
3160
3197
  content_by_position, colspec_by_position = content_dict[side], colspec_dict[side]
3161
3198
  # TODO populate chapter-number
3162
3199
  # TODO populate numbered and unnumbered chapter and section titles
3163
- doc.set_attr 'page-number', pgnum_label.to_s if pagenums_enabled
3200
+ doc.set_attr 'page-number', pgnum_label if pagenums_enabled
3164
3201
  # QUESTION should the fallback value be nil instead of empty string? or should we remove attribute if no value?
3165
- doc.set_attr 'part-title', (parts_by_page[pgnum_label] || '')
3166
- doc.set_attr 'chapter-title', (chapters_by_page[pgnum_label] || '')
3167
- doc.set_attr 'section-title', (sections_by_page[pgnum_label] || '')
3168
- doc.set_attr 'section-or-chapter-title', (sections_by_page[pgnum_label] || chapters_by_page[pgnum_label] || '')
3202
+ doc.set_attr 'part-title', (parts_by_page[pgnum] || '')
3203
+ if toc_page_nums && (toc_page_nums.cover? pgnum)
3204
+ if is_book
3205
+ doc.set_attr 'chapter-title', (sect_or_chap_title = toc_title)
3206
+ doc.set_attr 'section-title', ''
3207
+ else
3208
+ doc.set_attr 'chapter-title', ''
3209
+ doc.set_attr 'section-title', (sect_or_chap_title = section_start_pages[pgnum] ? sections_by_page[pgnum] : toc_title)
3210
+ end
3211
+ doc.set_attr 'section-or-chapter-title', sect_or_chap_title
3212
+ toc_page_nums = nil if toc_page_nums.end == pgnum
3213
+ else
3214
+ doc.set_attr 'chapter-title', (chapters_by_page[pgnum] || '')
3215
+ doc.set_attr 'section-title', (sections_by_page[pgnum] || '')
3216
+ doc.set_attr 'section-or-chapter-title', (sections_by_page[pgnum] || chapters_by_page[pgnum] || '')
3217
+ end
3169
3218
 
3170
3219
  stamp stamp_names[side] if stamp_names
3171
3220
 
@@ -3214,7 +3263,7 @@ class Converter < ::Prawn::Document
3214
3263
  theme_font %(#{periphery}_#{side}_#{position}) do
3215
3264
  # NOTE minor optimization
3216
3265
  if content == '{page-number}'
3217
- content = pagenums_enabled ? pgnum_label.to_s : nil
3266
+ content = pagenums_enabled ? pgnum_label : nil
3218
3267
  else
3219
3268
  content = apply_subs_discretely doc, content, drop_lines_with_unresolved_attributes: true
3220
3269
  content = transform_text content, @text_transform if @text_transform
@@ -3245,8 +3294,7 @@ class Converter < ::Prawn::Document
3245
3294
  end
3246
3295
 
3247
3296
  def allocate_running_content_layout page, periphery, cache
3248
- layout = page.layout
3249
- cache[layout] ||= begin
3297
+ cache[layout = page.layout] ||= begin
3250
3298
  trim_styles = {
3251
3299
  line_metrics: (trim_line_metrics = calc_line_metrics @theme[%(#{periphery}_line_height)] || @theme.base_line_height),
3252
3300
  # NOTE we've already verified this property is set
@@ -3260,7 +3308,7 @@ class Converter < ::Prawn::Document
3260
3308
  column_rule_color: (trim_column_rule_color = resolve_theme_color %(#{periphery}_column_rule_color).to_sym),
3261
3309
  column_rule_style: (@theme[%(#{periphery}_column_rule_style)] || :solid).to_sym,
3262
3310
  column_rule_width: (trim_column_rule_color ? @theme[%(#{periphery}_column_rule_width)] || 0 : 0),
3263
- column_rule_spacing: (trim_column_rule_spacing = @theme[%(#{periphery}_column_rule_spacing)] || 0),
3311
+ column_rule_spacing: (@theme[%(#{periphery}_column_rule_spacing)] || 0),
3264
3312
  valign: (val = (@theme[%(#{periphery}_vertical_align)] || :middle).to_sym) == :middle ? :center : val,
3265
3313
  img_valign: @theme[%(#{periphery}_image_vertical_align)],
3266
3314
  left: {
@@ -3349,10 +3397,6 @@ class Converter < ::Prawn::Document
3349
3397
  end
3350
3398
  end
3351
3399
  end
3352
- # NOTE set fallbacks if not explicitly disabled
3353
- if side_content.empty? && periphery == :footer && @theme[%(footer_#{side}_content)] != 'none'
3354
- side_content = { side == :recto ? :right : :left => '{page-number}' }
3355
- end
3356
3400
 
3357
3401
  acc[side] = side_content
3358
3402
  acc
@@ -3413,14 +3457,15 @@ class Converter < ::Prawn::Document
3413
3457
  end
3414
3458
 
3415
3459
  outline.define do
3460
+ initial_pagenum = has_front_cover ? 2 : 1
3416
3461
  # FIXME use sanitize: :plain_text once available
3417
- if (doctitle = document.sanitize(doc.doctitle use_fallback: true)) && document.page_count > (has_front_cover ? 2 : 1)
3418
- page title: doctitle, destination: (document.dest_top has_front_cover ? 2 : 1)
3462
+ if document.page_count >= initial_pagenum && (doctitle = doc.header? ? doc.doctitle : (doc.attr 'untitled-label'))
3463
+ page title: (document.sanitize doctitle), destination: (document.dest_top has_front_cover ? 2 : 1)
3419
3464
  end
3420
3465
  unless toc_page_nums.none? || (toc_title = doc.attr 'toc-title').nil_or_empty?
3421
3466
  page title: toc_title, destination: (document.dest_top toc_page_nums.first)
3422
3467
  end
3423
- # QUESTION any way to get add_outline_level to invoke in the context of the outline?
3468
+ # QUESTION is there any way to get add_outline_level to invoke in the context of the outline?
3424
3469
  document.add_outline_level self, doc.sections, num_levels, expand_levels
3425
3470
  end
3426
3471
 
@@ -4107,7 +4152,6 @@ class Converter < ::Prawn::Document
4107
4152
  # QUESTION is there a better way to do this?
4108
4153
  # I suppose we could have @tmp_files as an instance variable on converter instead
4109
4154
  # It might be sufficient to delete temporary files once per conversion
4110
- # NOTE Ruby 1.9 will sometimes delete a tmp file before the process exits
4111
4155
  def unlink_tmp_file path
4112
4156
  path.unlink if TemporaryPath === path && path.exist?
4113
4157
  rescue
@@ -4171,6 +4215,20 @@ class Converter < ::Prawn::Document
4171
4215
  end
4172
4216
  end
4173
4217
 
4218
+ def resolve_pagenums val
4219
+ pgnums = []
4220
+ ((val.include? ',') ? (val.split ',') : (val.split ';')).each do |entry|
4221
+ if entry.include? '..'
4222
+ from, _, to = entry.partition '..'
4223
+ pgnums += ([from.to_i, 1].max..[to.to_i, 1].max).to_a
4224
+ else
4225
+ pgnums << entry.to_i
4226
+ end
4227
+ end
4228
+
4229
+ pgnums
4230
+ end
4231
+
4174
4232
  def get_char code
4175
4233
  (code.start_with? '\u') ? ([((code.slice 2, code.length).to_i 16)].pack 'U1') : code
4176
4234
  end