asciidoctor-pdf 2.0.8 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd47554913fab2e3f8002491a92c430e0dbfe48b9177705470ffa7c64155289b
4
- data.tar.gz: 580c0c9e6a46e5614927f42c4ad309e303dd69681bf10ddb674241cd930e6b31
3
+ metadata.gz: 2109e7ec140c759820252d4c7f0f155c2d7bf4adfcad9d34fddb8cf4bc81a260
4
+ data.tar.gz: c18e3fb15231f968d907fc05693f6c4d7f9fbedfd29e368b0dc355f44d2e4254
5
5
  SHA512:
6
- metadata.gz: 326e87435e664dde2e944b878d2c5c9a5b71cfd285da78115331a8cd14e20f2aaa0cb2b81779bc99ca8642df004ef03f60386a971e9176eb03d17755a9db951b
7
- data.tar.gz: 7d7cd662e4bbe5df6d10c057e2f00a1a1c800f0e5a3e4f7731c94b657b0bf6ec51599e88d42dbd806a1e4bf991e54282c4efb820b0d39912ed34a74d25f89ff4
6
+ metadata.gz: 4e51a465c863440441cae24e429bbf84d9fb782e2c711958d5b72dfa585ba4b1b7c9c94b92d8155b2cea0d72eec276fac6e4669ea8a50c175ff8ed82ab91eb8d
7
+ data.tar.gz: a62b653261f3194d51a738cc4b1cf46f4a78a6ba0c1687fbeb06bf5049ecc9d65a20edd7acc463a2f4b473d1248795ce39902b214b87b019cbc8485d394a32f3
data/CHANGELOG.adoc CHANGED
@@ -5,6 +5,53 @@
5
5
  This document provides a high-level view of the changes to the {project-name} by release.
6
6
  For a detailed view of what has changed, refer to the {url-repo}/commits/main[commit history] on GitHub.
7
7
 
8
+ == 2.1.2 (2022-06-17) - @mojavelinux
9
+
10
+ Bug Fixes::
11
+
12
+ * apply page layout from main document to new page in scratch document (#2248)
13
+ * use correct logic to insert page before TOC with automatic placement when doctype=book and media=prepress
14
+ * use `get_entries_for_toc` to determine if the TOC is non-empty rather than `Document#sections?`
15
+
16
+ === Details
17
+
18
+ {url-repo}/releases/tag/v2.1.2[git tag] | {url-repo}/compare/v2.1.1\...v2.1.2[full diff]
19
+
20
+ == 2.1.1 (2022-06-15) - @mojavelinux
21
+
22
+ Improvements::
23
+
24
+ * store zero-based column on Extent for extensions to use to position cursor at start of extent
25
+
26
+ Bug Fixes::
27
+
28
+ * place block image in SVG format in correct column when align is left and page columns are enabled (#2241)
29
+ * accurately trap LoadError from CodeRay if source language is not recognized on code block
30
+ * only draw rounded rectangle around phrase if `border-radius` is > 0
31
+ * use `base-border-color` value if border width is set on role for inline phrase but not border color
32
+
33
+ === Details
34
+
35
+ {url-repo}/releases/tag/v2.1.1[git tag] | {url-repo}/compare/v2.1.0\...v2.1.1[full diff]
36
+
37
+ == 2.1.0 (2022-06-11) - @mojavelinux
38
+
39
+ Enhancements::
40
+
41
+ * arrange body of article or manpage doctype into multiple columns if `page-columns` key is set in theme (#327)
42
+ * allow column gap to be specified using `page-column-gap` key (#327)
43
+ * introduce `convert_index_categories` method to handle rendering of categories for index inside column box (#327)
44
+ * rename `convert_index_list` method to `convert_index_term` to make its purpose more clear (#327)
45
+ * add `save_theme` helper to work with a copy of the theme within a scope (#2196)
46
+ * add support for `scale` attribute or `iw` unit on `pdfwidth` attribute on image macros (#1933)
47
+ * add backlink from bibref on bibliography entry to first reference to that entry in the document (#1737)
48
+ * preserve text formatting on index term in index section (#897)
49
+ * don't insert page break between part and first chapter if `heading-part-break-after` key in theme is `avoid` (#1795)
50
+
51
+ === Details
52
+
53
+ {url-repo}/releases/tag/v2.1.0[git tag] | {url-repo}/compare/v2.0.8\...v2.1.0[full diff]
54
+
8
55
  == 2.0.8 (2022-06-08) - @mojavelinux
9
56
 
10
57
  Improvements::
data/README.adoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = Asciidoctor PDF: A native PDF converter for AsciiDoc
2
2
  Dan Allen <https://github.com/mojavelinux[@mojavelinux]>; Sarah White <https://github.com/graphitefriction[@graphitefriction]>
3
- v2.0.8, 2022-06-08
3
+ v2.1.2, 2022-06-17
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -23,8 +23,7 @@ endif::[]
23
23
  :project-name: Asciidoctor PDF
24
24
  :project-handle: asciidoctor-pdf
25
25
  // Variables:
26
- :release-line: 2.0.x
27
- :release-version: 2.0.8
26
+ :release-line: 2.1.x
28
27
  // URLs:
29
28
  :url-gem: https://rubygems.org/gems/asciidoctor-pdf
30
29
  :url-project: https://github.com/asciidoctor/asciidoctor-pdf
@@ -47,7 +46,7 @@ The aim of this library is to take the pain out of creating PDF documents from A
47
46
 
48
47
  [NOTE]
49
48
  ====
50
- The documentation for Asciidoctor PDF {release-line} is now available at {url-project-docs}/.
49
+ The documentation for the latest, stable release of Asciidoctor PDF is available at {url-project-docs}/.
51
50
 
52
51
  If you're looking for the documentation for Asciidoctor PDF 1.6, refer to the {url-project-repo}/tree/v1.6.x#readme[README] in the v1.6.x branch.
53
52
  Asciidoctor PDF 1.6 is no longer being developed and will reach EOL later this year.
@@ -114,11 +113,11 @@ There are several optional features of this converter that require additional ge
114
113
  Those features are as follows.
115
114
 
116
115
  Source highlighting::
117
- You'll need to {url-project-repo}/syntax-highlighting/[install a syntax highlighter] to use source highlighting (build-time only).
116
+ You'll need to {url-project-docs}/syntax-highlighting/[install a syntax highlighter] to use source highlighting (build-time only).
118
117
 
119
118
  PDF optimization::
120
119
  If you want to optimize your PDF, you'll need rghost or hexapdf.
121
- See {url-project-repo}/optimize-pdf/[Optimize the PDF] for installation and usage instructions.
120
+ See {url-project-docs}/optimize-pdf/[Optimize the PDF] for installation and usage instructions.
122
121
 
123
122
  Automatic hyphenation::
124
123
  To turn on automatic hyphenation using the `hyphens` attribute, you'll need to install the `text-hyphen` gem:
@@ -128,7 +127,7 @@ To turn on automatic hyphenation using the `hyphens` attribute, you'll need to i
128
127
  Accelerated image decoding::
129
128
  Ruby is not particularly fast at decoding images, and the image formats it supports are limited.
130
129
  To help, you can install prawn-gmagick, which delegates the work of decoding images to GraphicsMagick.
131
- Refer to {url-project-repo}/image-paths-and-formats/#other-image-formats[Supporting additional image file formats] for instructions about how to enable this integration.
130
+ Refer to {url-project-docs}/image-paths-and-formats/#other-image-formats[Supporting additional image file formats] for instructions about how to enable this integration.
132
131
 
133
132
  Check the {url-project-docs}/install/#table-minimum-version[minimum supported version table] to make sure you're using a supported version of the dependency.
134
133
 
@@ -197,11 +196,6 @@ ifndef::env-site[]
197
196
  See the <<CONTRIBUTING.adoc#,contributing guide>>.
198
197
  To help develop {project-name}, or to simply use the development version, refer to the <<CONTRIBUTING-CODE.adoc#,developing and contributing code guide>>.
199
198
 
200
- [[resources,Links]]
201
- == Resources
202
-
203
- * https://groups.google.com/forum/#!msg/prawn-ruby/MbMsCx862iY/6ImCsvLGfVcJ[Discussion about image quality in PDFs]
204
-
205
199
  == Authors
206
200
 
207
201
  {project-name} was written by https://github.com/mojavelinux[Dan Allen] and https://github.com/graphitefriction[Sarah White] of OpenDevise Inc. on behalf of the Asciidoctor Project.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'formatted_string'
3
4
  require_relative 'formatted_text'
4
5
  require_relative 'index_catalog'
5
6
  require_relative 'pdfmark'
@@ -124,6 +125,7 @@ module Asciidoctor
124
125
  DropAnchorRx = %r(<(?:a\b[^>]*|/a)>)
125
126
  SourceHighlighters = %w(coderay pygments rouge).to_set
126
127
  ViewportWidth = ::Module.new
128
+ ImageWidth = ::Module.new
127
129
  (TitleStyles = {
128
130
  'toc' => [:numbered_title],
129
131
  'basic' => [:title],
@@ -169,7 +171,8 @@ module Asciidoctor
169
171
  # NOTE: a new page will already be started (page_number = 2) if the front cover image is a PDF
170
172
  ink_cover_page doc, :front
171
173
  has_front_cover = page_number > marked_page_number
172
- if (has_title_page = (title_page_on = doc.doctype == 'book' || (doc.attr? 'title-page')) && (start_title_page doc))
174
+ doctype = doc.doctype
175
+ if (has_title_page = (title_page_on = doctype == 'book' || (doc.attr? 'title-page')) && (start_title_page doc))
173
176
  # NOTE: the base font must be set before any content is written to the main or scratch document
174
177
  font @theme.base_font_family, size: @root_font_size, style: @theme.base_font_style
175
178
  if perform_on_single_page { ink_title_page doc }
@@ -195,8 +198,8 @@ module Asciidoctor
195
198
 
196
199
  indent_section do
197
200
  toc_num_levels = (doc.attr 'toclevels', 2).to_i
198
- if (insert_toc = (doc.attr? 'toc') && !((toc_placement = doc.attr 'toc-placement') == 'macro' || toc_placement == 'preamble') && doc.sections?)
199
- start_toc_page doc, toc_placement if title_page_on
201
+ if (insert_toc = (doc.attr? 'toc') && !((toc_placement = doc.attr 'toc-placement') == 'macro' || toc_placement == 'preamble') && !(get_entries_for_toc doc).empty?)
202
+ start_new_page if @ppbook && verso_page?
200
203
  add_dest_for_block doc, id: 'toc', y: (at_page_top? ? page_height : nil)
201
204
  @toc_extent = allocate_toc doc, toc_num_levels, cursor, title_page_on
202
205
  else
@@ -278,12 +281,19 @@ module Asciidoctor
278
281
  doc.set_attr 'pdf-anchor', (derive_anchor_from_id doc.id, 'top')
279
282
  doc.set_attr 'pdf-page-start', page_number
280
283
 
281
- convert_section generate_manname_section doc if doc.doctype == 'manpage' && (doc.attr? 'manpurpose')
282
-
283
- traverse doc
284
-
285
- # NOTE: for a book, these are leftover footnotes; for an article this is everything
286
- outdent_section { ink_footnotes doc }
284
+ if doctype == 'book' || (columns = @theme.page_columns || 1) < 2
285
+ convert_section generate_manname_section doc if doctype == 'manpage' && (doc.attr? 'manpurpose')
286
+ traverse doc
287
+ # NOTE: for a book, these are leftover footnotes; for an article this is everything
288
+ outdent_section { ink_footnotes doc }
289
+ else
290
+ column_box [bounds.left, cursor], columns: columns, width: bounds.width, reflow_margins: true, spacer: @theme.page_column_gap do
291
+ convert_section generate_manname_section doc if doctype == 'manpage' && (doc.attr? 'manpurpose')
292
+ traverse doc
293
+ # NOTE: for a book, these are leftover footnotes; for an article this is everything
294
+ outdent_section { ink_footnotes doc }
295
+ end
296
+ end
287
297
 
288
298
  if (toc_extent = @toc_extent)
289
299
  if title_page_on && !insert_toc
@@ -402,6 +412,7 @@ module Asciidoctor
402
412
  @list_bullets = []
403
413
  @bottom_gutters = [{}]
404
414
  @rendered_footnotes = []
415
+ @bibref_refs = ::Set.new
405
416
  @conum_glyphs = ConumSets[@theme.conum_glyphs || 'circled'] || (@theme.conum_glyphs.split ',').map do |r|
406
417
  from, to = r.lstrip.split '-', 2
407
418
  to ? ((get_char from)..(get_char to)).to_a : [(get_char from)]
@@ -575,6 +586,8 @@ module Asciidoctor
575
586
  theme.base_font_style = theme.base_font_style&.to_sym || :normal
576
587
  theme.page_numbering_start_at ||= 'body'
577
588
  theme.running_content_start_at ||= 'body'
589
+ theme.heading_chapter_break_before ||= 'always'
590
+ theme.heading_part_break_before ||= 'always'
578
591
  theme.heading_margin_page_top ||= 0
579
592
  theme.heading_margin_top ||= 0
580
593
  theme.heading_margin_bottom ||= 0
@@ -609,6 +622,13 @@ module Asciidoctor
609
622
  theme
610
623
  end
611
624
 
625
+ def save_theme
626
+ @theme = (original_theme = theme).dup
627
+ yield
628
+ ensure
629
+ @theme = original_theme
630
+ end
631
+
612
632
  def indent_section
613
633
  if (values = @section_indent)
614
634
  indent(values[0], values[1]) { yield }
@@ -647,13 +667,14 @@ module Asciidoctor
647
667
  hidden = sect.option? 'notitle'
648
668
  hopts = { align: text_align, level: hlevel, part: part, chapterlike: chapterlike, outdent: !(part || chapterlike) }
649
669
  if part
650
- unless @theme.heading_part_break_before == 'auto'
670
+ if @theme.heading_part_break_before == 'always'
651
671
  started_new = true
652
672
  start_new_part sect
653
673
  end
654
674
  elsif chapterlike
655
- if @theme.heading_chapter_break_before != 'auto' ||
656
- (@theme.heading_part_break_after == 'always' && sect == sect.parent.sections[0])
675
+ if (@theme.heading_chapter_break_before == 'always' &&
676
+ !(@theme.heading_part_break_after == 'avoid' && sect.first_section_of_part?)) ||
677
+ (@theme.heading_part_break_after == 'always' && sect.first_section_of_part?)
657
678
  started_new = true
658
679
  start_new_chapter sect
659
680
  end
@@ -700,53 +721,74 @@ module Asciidoctor
700
721
  end
701
722
 
702
723
  def convert_index_section node
703
- space_needed_for_category = @theme.description_list_term_spacing + (2 * (height_of_typeset_text 'A'))
704
- pagenum_sequence_style = node.document.attr 'index-pagenum-sequence-style'
705
- end_cursor = nil
706
- column_box [0, cursor], columns: @theme.index_columns, width: bounds.width, reflow_margins: true, spacer: @theme.index_column_gap do
707
- @index.categories.each do |category|
708
- bounds.move_past_bottom if space_needed_for_category > cursor
709
- ink_prose category.name,
710
- align: :left,
711
- inline_format: false,
712
- margin_bottom: @theme.description_list_term_spacing,
713
- style: @theme.description_list_term_font_style&.to_sym
714
- category.terms.each {|term| convert_index_list_item term, pagenum_sequence_style }
715
- @theme.prose_margin_bottom > cursor ? bounds.move_past_bottom : (move_down @theme.prose_margin_bottom)
716
- end
717
- end_cursor = cursor if bounds.current_column == 0
718
- end
719
- # Q: could we move this logic into column_box?
720
- move_cursor_to end_cursor if end_cursor
724
+ if ColumnBox === bounds || (columns = @theme.index_columns || 1) < 2
725
+ convert_index_categories @index.categories, (node.document.attr 'index-pagenum-sequence-style')
726
+ else
727
+ end_cursor = nil
728
+ column_box [bounds.left, cursor], columns: columns, width: bounds.width, reflow_margins: true, spacer: @theme.index_column_gap do
729
+ convert_index_categories @index.categories, (node.document.attr 'index-pagenum-sequence-style')
730
+ end_cursor = cursor if bounds.current_column == 0
731
+ end
732
+ # Q: could we move this logic into column_box?
733
+ move_cursor_to end_cursor if end_cursor
734
+ end
721
735
  nil
722
736
  end
723
737
 
724
- def convert_index_list_item term, pagenum_sequence_style = nil
725
- text = escape_xml term.name
738
+ def convert_index_categories categories, pagenum_sequence_style = nil
739
+ space_needed_for_category = @theme.description_list_term_spacing + (2 * (height_of_typeset_text 'A'))
740
+ categories.each do |category|
741
+ bounds.move_past_bottom if space_needed_for_category > cursor
742
+ ink_prose category.name,
743
+ align: :left,
744
+ inline_format: false,
745
+ margin_bottom: @theme.description_list_term_spacing,
746
+ style: @theme.description_list_term_font_style&.to_sym
747
+ category.terms.each {|term| convert_index_term term, pagenum_sequence_style }
748
+ @theme.prose_margin_bottom > cursor ? bounds.move_past_bottom : (move_down @theme.prose_margin_bottom)
749
+ end
750
+ end
751
+
752
+ def convert_index_term term, pagenum_sequence_style = nil
753
+ term_fragments = term.name.fragments
726
754
  unless term.container?
755
+ pagenum_fragment = (parse_text %(<a>#{DummyText}</a>), inline_format: true)[0]
727
756
  if @media == 'screen'
728
757
  case pagenum_sequence_style
729
758
  when 'page'
730
- pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| %(<a anchor="#{dest[:anchor]}">#{dest[:page]}</a>) }
759
+ pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| pagenum_fragment.merge anchor: dest[:anchor], text: dest[:page] }
731
760
  when 'range'
732
761
  first_anchor_per_page = {}.tap {|accum| term.dests.each {|dest| accum[dest[:page]] ||= dest[:anchor] } }
733
762
  pagenums = (consolidate_ranges first_anchor_per_page.keys).map do |range|
734
763
  anchor = first_anchor_per_page[(range.include? '-') ? (range.partition '-')[0] : range]
735
- %(<a anchor="#{anchor}">#{range}</a>)
764
+ pagenum_fragment.merge text: range, anchor: anchor
736
765
  end
737
766
  else # term
738
- pagenums = term.dests.map {|dest| %(<a anchor="#{dest[:anchor]}">#{dest[:page]}</a>) }
767
+ pagenums = term.dests.map {|dest| pagenum_fragment.merge text: dest[:page], anchor: dest[:anchor] }
739
768
  end
740
769
  else
741
770
  pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
742
771
  end
743
- text = %(#{text}, #{pagenums.join ', '})
772
+ pagenums.each do |pagenum|
773
+ # NOTE: addresses a very minor kerning issue for text adjacent to the comma
774
+ if (prev_fragment = term_fragments[-1]).size == 1
775
+ if ::String === pagenum
776
+ term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, #{pagenum})
777
+ next
778
+ else
779
+ term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, )
780
+ end
781
+ else
782
+ term_fragments << ({ text: ', ' })
783
+ end
784
+ term_fragments << (::String === pagenum ? { text: pagenum } : pagenum)
785
+ end
744
786
  end
745
787
  subterm_indent = @theme.description_list_description_indent
746
- ink_prose text, align: :left, margin: 0, hanging_indent: subterm_indent * 2
788
+ typeset_formatted_text term_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2
747
789
  indent subterm_indent do
748
790
  term.subterms.each do |subterm|
749
- convert_index_list_item subterm, pagenum_sequence_style
791
+ convert_index_term subterm, pagenum_sequence_style
750
792
  end
751
793
  end unless term.leaf?
752
794
  end
@@ -1066,7 +1108,10 @@ module Asciidoctor
1066
1108
  srclang = node.attr 'language', 'text'
1067
1109
  begin
1068
1110
  ::CodeRay::Scanners[(srclang = (srclang.start_with? 'html+') ? (srclang.slice 5, srclang.length).to_sym : srclang.to_sym)]
1069
- rescue ::ArgumentError
1111
+ rescue
1112
+ until ::LoadError === (cause ||= $!) || ::ArgumentError === cause
1113
+ raise $! unless (cause = cause.cause)
1114
+ end
1070
1115
  srclang = :text
1071
1116
  end
1072
1117
  fragments = (::CodeRay.scan source_string, srclang).to_prawn
@@ -1715,9 +1760,14 @@ module Asciidoctor
1715
1760
  return on_image_error :missing, node, target, (opts.merge align: alignment) unless image_path
1716
1761
 
1717
1762
  # TODO: support cover (aka canvas) image layout using "canvas" (or "cover") role
1718
- width = resolve_explicit_width node.attributes, bounds_width: (available_w = bounds.width), support_vw: true, use_fallback: true, constrain_to_bounds: true
1719
- # TODO: add `to_pt page_width` method to ViewportWidth type
1720
- width = (width.to_f / 100) * page_width if ViewportWidth === width
1763
+ case (width = resolve_explicit_width node.attributes, bounds_width: (available_w = bounds.width), support_vw: true, use_fallback: true, constrain_to_bounds: true)
1764
+ when ViewportWidth
1765
+ # TODO: add `to_pt page_width` method to ViewportWidth type
1766
+ width = page_width * (width.to_f / 100)
1767
+ when ImageWidth
1768
+ scale = width.to_f / 100
1769
+ width = nil
1770
+ end
1721
1771
 
1722
1772
  caption_end = @theme.image_caption_end&.to_sym || :bottom
1723
1773
  caption_max_width = @theme.image_caption_max_width
@@ -1740,14 +1790,15 @@ module Asciidoctor
1740
1790
  file_request_root = { base: (::File.dirname image_path), root: @jail_dir }
1741
1791
  end
1742
1792
  svg_obj = ::Prawn::SVG::Interface.new svg_data, self,
1743
- position: alignment,
1744
1793
  width: width,
1745
1794
  fallback_font_name: fallback_svg_font_name,
1746
1795
  enable_web_requests: allow_uri_read ? (method :load_open_uri).to_proc : false,
1747
1796
  enable_file_requests_with_root: file_request_root,
1748
1797
  cache_images: cache_uri
1749
1798
  rendered_w = (svg_size = svg_obj.document.sizing).output_width
1750
- if !width && (svg_obj.document.root.attributes.key? 'width') && rendered_w > available_w
1799
+ if scale
1800
+ svg_size = svg_obj.resize width: (rendered_w = [available_w, rendered_w * scale].min)
1801
+ elsif !width && (svg_obj.document.root.attributes.key? 'width') && rendered_w > available_w
1751
1802
  # NOTE: restrict width to available width (prawn-svg already coerces to pixels)
1752
1803
  svg_size = svg_obj.resize width: (rendered_w = available_w)
1753
1804
  end
@@ -1755,7 +1806,6 @@ module Asciidoctor
1755
1806
  if (rendered_h = svg_size.output_height) > (available_h = cursor - caption_h)
1756
1807
  unless pinned || at_page_top?
1757
1808
  advance_page
1758
- (svg_obj.options[:at] = svg_obj.position)[0] += bounds.left if ColumnBox === bounds
1759
1809
  available_h = cursor - caption_h
1760
1810
  end
1761
1811
  rendered_w = (svg_obj.resize height: (rendered_h = available_h)).output_width if rendered_h > available_h
@@ -1766,7 +1816,16 @@ module Asciidoctor
1766
1816
  update_colors if graphic_state.color_space.empty?
1767
1817
  ink_caption node, category: :image, end: :top, block_align: alignment, block_width: rendered_w, max_width: caption_max_width if caption_end == :top && node.title?
1768
1818
  image_y = y
1769
- image_cursor = cursor
1819
+ # NOTE: prawn-svg does not compute :at for alignment correctly in column box, so resort to our own logic
1820
+ case alignment
1821
+ when :center
1822
+ left = bounds.left + (available_w - rendered_w) * 0.5
1823
+ when :right
1824
+ left = bounds.left + available_w - rendered_w
1825
+ else
1826
+ left = bounds.left
1827
+ end
1828
+ svg_obj.options[:at] = [left, (image_cursor = cursor)]
1770
1829
  svg_obj.draw # NOTE: cursor advances automatically
1771
1830
  svg_obj.document.warnings.each do |img_warning|
1772
1831
  log :warn, %(problem encountered in image: #{image_path}; #{img_warning})
@@ -1781,8 +1840,10 @@ module Asciidoctor
1781
1840
  image_obj, image_info = ::Base64 === image_path ?
1782
1841
  ::StringIO.open((::Base64.decode64 image_path), 'rb') {|fd| build_image_object fd } :
1783
1842
  ::File.open(image_path, 'rb') {|fd| build_image_object fd }
1843
+ actual_w = to_pt image_info.width, :px
1844
+ width = actual_w * scale if scale
1784
1845
  # NOTE: if width is not specified, scale native width & height from px to pt and restrict width to available width
1785
- rendered_w, rendered_h = image_info.calc_image_dimensions width: (width || [available_w, (to_pt image_info.width, :px)].min)
1846
+ rendered_w, rendered_h = image_info.calc_image_dimensions width: (width || [available_w, actual_w].min)
1786
1847
  # NOTE: shrink image so it fits within available space; group image & caption
1787
1848
  if rendered_h > (available_h = cursor - caption_h)
1788
1849
  unless pinned || at_page_top?
@@ -2291,7 +2352,7 @@ module Asciidoctor
2291
2352
  # NOTE: only allow document to have a single managed toc
2292
2353
  return if @toc_extent
2293
2354
  is_macro = (placement = opts[:placement] || 'macro') == 'macro'
2294
- if ((doc = node.document).attr? 'toc-placement', placement) && (doc.attr? 'toc') && doc.sections?
2355
+ if ((doc = node.document).attr? 'toc-placement', placement) && (doc.attr? 'toc') && !(get_entries_for_toc doc).empty?
2295
2356
  start_toc_page node, placement if (is_book = doc.doctype == 'book')
2296
2357
  add_dest_for_block node, id: (node.id || 'toc') if is_macro
2297
2358
  toc_extent = @toc_extent = allocate_toc doc, (doc.attr 'toclevels', 2).to_i, cursor, (title_page_on = is_book || (doc.attr? 'title-page'))
@@ -2380,9 +2441,12 @@ module Asciidoctor
2380
2441
  if (text = ref.xreftext node.attr 'xrefstyle', nil, true)&.include? '<a'
2381
2442
  text = text.gsub DropAnchorRx, ''
2382
2443
  end
2444
+ if ref.inline? && ref.type == :bibref && !scratch? && (@bibref_refs.add? refid)
2445
+ anchor = %(<a id="_bibref_ref_#{refid}">#{DummyText}</a>)
2446
+ end
2383
2447
  @resolving_xref = nil
2384
2448
  end
2385
- %(<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', '&#93;'
2449
+ %(#{anchor || ''}<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', '&#93;'
2386
2450
  else
2387
2451
  %(<a anchor="#{doc.attr 'pdf-anchor'}">#{node.text || '[^top&#93;'}</a>)
2388
2452
  end
@@ -2390,10 +2454,12 @@ module Asciidoctor
2390
2454
  # NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
2391
2455
  %(<a id="#{node.id}">#{DummyText}</a>)
2392
2456
  when :bibref
2393
- # NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
2457
+ id = node.id
2394
2458
  # NOTE: technically node.text should be node.reftext, but subs have already been applied to text
2395
- reftext = (reftext = node.reftext) ? %([#{reftext}]) : %([#{node.id}])
2396
- %(<a id="#{node.id}">#{DummyText}</a>#{reftext})
2459
+ reftext = (reftext = node.reftext) ? %([#{reftext}]) : %([#{id}])
2460
+ reftext = %(<a anchor="_bibref_ref_#{id}">#{reftext}</a>) if @bibref_refs.include? id
2461
+ # NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
2462
+ %(<a id="#{id}">#{DummyText}</a>#{reftext})
2397
2463
  else
2398
2464
  log :warn, %(unknown anchor type: #{node.type.inspect})
2399
2465
  nil
@@ -2515,12 +2581,18 @@ module Asciidoctor
2515
2581
  class_attr = (role = node.role) ? %( class="#{role}") : ''
2516
2582
  fit_attr = (fit = node.attr 'fit') ? %( fit="#{fit}") : ''
2517
2583
  if (width = resolve_explicit_width node.attributes)
2518
- if node.parent.context == :table_cell && ::String === width && (width.end_with? '%')
2519
- width += (intrinsic_image_dimensions image_path, image_format)[:width].to_s
2584
+ if ImageWidth === width
2585
+ if state # check that converter is initialized
2586
+ width = (intrinsic_image_width image_path, image_format) * (width.to_f / 100)
2587
+ else
2588
+ width = %(auto*#{width})
2589
+ end
2590
+ elsif node.parent.context == :table_cell && ::String === width && (width.end_with? '%')
2591
+ width += (intrinsic_image_width image_path, image_format).to_s
2520
2592
  end
2521
2593
  width_attr = %( width="#{width}")
2522
2594
  elsif state # check that converter is initialized
2523
- width_attr = %( width="#{(intrinsic_image_dimensions image_path, image_format)[:width]}")
2595
+ width_attr = %( width="#{intrinsic_image_width image_path, image_format}")
2524
2596
  else
2525
2597
  width_attr = ' width="auto"' # defer operation until arranger runs
2526
2598
  end
@@ -2541,17 +2613,20 @@ module Asciidoctor
2541
2613
  if scratch?
2542
2614
  visible ? node.text : ''
2543
2615
  else
2544
- # NOTE: initialize index in case converter is called before PDF is initialized
2545
- @index ||= IndexCatalog.new
2616
+ unless defined? @index
2617
+ # NOTE: initialize index and text formatter in case converter is called before PDF is initialized
2618
+ @index = IndexCatalog.new
2619
+ @text_formatter = FormattedText::Formatter.new theme: (load_theme node.document)
2620
+ end
2546
2621
  # NOTE: page number (:page key) is added by InlineDestinationMarker
2547
2622
  dest = { anchor: (anchor_name = @index.next_anchor_name) }
2548
2623
  anchor = %(<a id="#{anchor_name}" type="indexterm"#{visible ? ' visible="true"' : ''}>#{DummyText}</a>)
2549
2624
  if visible
2550
2625
  visible_term = node.text
2551
- @index.store_primary_term (sanitize visible_term), dest
2626
+ @index.store_primary_term (FormattedString.new parse_text visible_term, inline_format: [normalize: true]), dest
2552
2627
  %(#{anchor}#{visible_term})
2553
2628
  else
2554
- @index.store_term (node.attr 'terms').map {|term| sanitize term }, dest
2629
+ @index.store_term (node.attr 'terms').map {|term| FormattedString.new parse_text term, inline_format: [normalize: true] }, dest
2555
2630
  anchor
2556
2631
  end
2557
2632
  end
@@ -2578,8 +2653,6 @@ module Asciidoctor
2578
2653
  end
2579
2654
 
2580
2655
  def convert_inline_quoted node
2581
- theme = load_theme node.document
2582
-
2583
2656
  case node.type
2584
2657
  when :emphasis
2585
2658
  open, close, is_tag = ['<em>', '</em>', true]
@@ -2592,25 +2665,26 @@ module Asciidoctor
2592
2665
  when :subscript
2593
2666
  open, close, is_tag = ['<sub>', '</sub>', true]
2594
2667
  when :double
2595
- open, close, is_tag = [theme.quotes[0], theme.quotes[1], false]
2668
+ open, close = (load_theme node.document).quotes.slice 0, 2
2596
2669
  quotes = true
2597
2670
  when :single
2598
- open, close, is_tag = [theme.quotes[2], theme.quotes[3], false]
2671
+ open, close = (load_theme node.document).quotes.slice 2, 2
2599
2672
  quotes = true
2600
2673
  when :mark
2601
2674
  open, close, is_tag = ['<mark>', '</mark>', true]
2602
2675
  else
2603
- open, close, is_tag = [nil, nil, false]
2676
+ open = close = ''
2604
2677
  end
2605
2678
 
2606
2679
  inner_text = node.text
2607
2680
 
2608
- if quotes && (len = inner_text.length) > 3 &&
2609
- (inner_text.end_with? '...') && !((inner_text_trunc = inner_text.slice 0, len - 3).end_with? ?\\)
2681
+ if quotes && (len = inner_text.length) > 3 && (inner_text.end_with? '...') &&
2682
+ !((inner_text_trunc = inner_text.slice 0, len - 3).end_with? ?\\)
2610
2683
  inner_text = inner_text_trunc + '&#8230;'
2611
2684
  end
2612
2685
 
2613
2686
  if (roles = node.role)
2687
+ theme = load_theme node.document
2614
2688
  roles.split.each do |role|
2615
2689
  if (text_transform = theme[%(role_#{role}_text_transform)])
2616
2690
  inner_text = transform_text inner_text, text_transform
@@ -2728,13 +2802,7 @@ module Asciidoctor
2728
2802
  value = (value.split LF).delete_if {|line| SimpleAttributeRefRx.match? line }.join LF if opts[:drop_lines_with_unresolved_attributes] && (value.include? '{')
2729
2803
  value = value.gsub '\{', '{' if escaped_attr_ref
2730
2804
  doc.set_attr 'attribute-missing', attribute_missing unless attribute_missing == 'skip'
2731
- if imagesdir
2732
- if imagesdir_to_restore
2733
- doc.set_attr 'imagesdir', imagesdir_to_restore
2734
- else
2735
- doc.remove_attr 'imagesdir'
2736
- end
2737
- end
2805
+ imagesdir_to_restore ? (doc.set_attr 'imagesdir', imagesdir_to_restore) : (doc.remove_attr 'imagesdir') if imagesdir
2738
2806
  value
2739
2807
  end
2740
2808
 
@@ -3871,6 +3939,10 @@ module Asciidoctor
3871
3939
  { width: 0, height: 0 }
3872
3940
  end
3873
3941
 
3942
+ def intrinsic_image_width path, format
3943
+ (intrinsic_image_dimensions path, format)[:width]
3944
+ end
3945
+
3874
3946
  # Sends the specified message to the log unless this method is called from the scratch document
3875
3947
  def log severity, message = nil, &block
3876
3948
  logger.send severity, message, &block unless scratch?
@@ -4042,11 +4114,15 @@ module Asciidoctor
4042
4114
  if attrs.key? 'pdfwidth'
4043
4115
  if (width = attrs['pdfwidth']).end_with? '%'
4044
4116
  bounds_width ? (width.to_f / 100) * bounds_width : width
4117
+ elsif width.end_with? 'iw'
4118
+ (width.chomp 'iw').extend ImageWidth
4045
4119
  elsif opts[:support_vw] && (width.end_with? 'vw')
4046
4120
  (width.chomp 'vw').extend ViewportWidth
4047
4121
  else
4048
4122
  str_to_pt width
4049
4123
  end
4124
+ elsif attrs.key? 'scale'
4125
+ attrs['scale'].dup.extend ImageWidth
4050
4126
  elsif attrs.key? 'scaledwidth'
4051
4127
  # NOTE: the parser automatically appends % if value is unitless
4052
4128
  if (width = attrs['scaledwidth']).end_with? '%'
@@ -4317,7 +4393,7 @@ module Asciidoctor
4317
4393
  b_shift, b_gap_color = (b_width ||= 0.5) * 0.5, @page_bg_color
4318
4394
  end
4319
4395
  ink_caption node_with_caption, category: category if node_with_caption
4320
- extent.from.page += 1 unless extent.from.page == page_number || ColumnBox === bounds # sanity check
4396
+ extent.from.page = page_number unless extent.from.page == page_number # sanity check
4321
4397
  float do
4322
4398
  extent.each_page do |first_page, last_page|
4323
4399
  advance_page unless first_page
@@ -30,4 +30,8 @@ class Asciidoctor::Section
30
30
  end
31
31
  opts[:formal] ? @cached_formal_numbered_title : @cached_numbered_title
32
32
  end unless method_defined? :numbered_title
33
+
34
+ def first_section_of_part?
35
+ (par = @parent).context == :section && par.sectname == 'part' && self == par.blocks.find {|it| it.context == :section }
36
+ end unless method_defined? :first_section_of_part?
33
37
  end
@@ -34,13 +34,14 @@ module Asciidoctor
34
34
  # - :final_gap determines whether a gap is added below the last line
35
35
  LineMetrics = ::Struct.new :height, :leading, :padding_top, :padding_bottom, :final_gap
36
36
 
37
- Position = ::Struct.new :page, :cursor
37
+ Position = ::Struct.new :page, :column, :cursor
38
38
 
39
39
  Extent = ::Struct.new :current, :from, :to do
40
- def initialize current_page, current_cursor, start_page, start_cursor, end_page, end_cursor
41
- self.from = self.current = Position.new current_page, current_cursor
42
- self.from = Position.new start_page, start_cursor unless start_page == current_page && start_cursor == current_cursor
43
- self.to = Position.new end_page, end_cursor
40
+ def initialize current_page, current_column, current_cursor, from_page, from_column, from_cursor, to_page, to_cursor
41
+ self.current = Position.new current_page, current_column, current_cursor
42
+ self.from = Position.new from_page, from_column, from_cursor
43
+ self.from = current if from == current
44
+ self.to = Position.new to_page, nil, to_cursor
44
45
  end
45
46
 
46
47
  def each_page
@@ -64,23 +65,23 @@ module Asciidoctor
64
65
 
65
66
  ScratchExtent = ::Struct.new :from, :to do
66
67
  def initialize start_page, start_cursor, end_page, end_cursor
67
- self.from = Position.new start_page, start_cursor
68
- self.to = Position.new end_page, end_cursor
68
+ self.from = Position.new start_page, 0, start_cursor
69
+ self.to = Position.new end_page, 0, end_cursor
69
70
  end
70
71
 
71
72
  def position_onto pdf, keep_together = nil
72
73
  current_page = pdf.page_number
74
+ current_column = ColumnBox === pdf.bounds ? (column_box = pdf.bounds).current_column : 0
73
75
  current_cursor = pdf.cursor
74
- from_page = current_page + (advance_by = from.page - 1)
75
- to_page = current_page + (to.page - 1)
76
- if advance_by > 0
76
+ if (advance_by = from.page - 1) > 0
77
77
  advance_by.times { pdf.advance_page }
78
78
  elsif keep_together && single_page? && !(try_to_fit_on_previous current_cursor)
79
79
  pdf.advance_page
80
- from_page += 1
81
- to_page += 1
82
80
  end
83
- Extent.new current_page, current_cursor, from_page, from.cursor, to_page, to.cursor
81
+ from_page = pdf.page_number
82
+ from_column = column_box&.current_column || 0
83
+ to_page = from_page + (to.page - from.page)
84
+ Extent.new current_page, current_column, current_cursor, from_page, from_column, from.cursor, to_page, to.cursor
84
85
  end
85
86
 
86
87
  def single_page?
@@ -972,11 +973,11 @@ module Asciidoctor
972
973
  state.on_page_create_callback = saved_callback
973
974
  end
974
975
 
975
- # This method is a smarter version of start_new_page. It calls start_new_page
976
- # if the current page is the last page of the document. Otherwise, it simply
977
- # advances to the next existing page.
976
+ # This method is a smarter version of start_new_page. It only calls start_new_page options are
977
+ # specified and the current page is the last page in the document. Otherwise, it advances the
978
+ # cursor to the next page (or column) using Bounds#move_past_bottom.
978
979
  def advance_page options = {}
979
- !options.empty? && last_page? ? (start_new_page options) : bounds.move_past_bottom
980
+ options.empty? || !last_page? ? bounds.move_past_bottom : (start_new_page options)
980
981
  end
981
982
 
982
983
  # Start a new page without triggering the on_page_create callback
@@ -1136,19 +1137,19 @@ module Asciidoctor
1136
1137
  # Note that if the block has content that itself requires a dry run, that nested dry run will
1137
1138
  # be performed in a separate scratch document.
1138
1139
  #
1139
- # opts - A Hash of options that configure the dry run computation:
1140
- # :keep_together - A Boolean indicating whether an attempt should be made to keep the
1141
- # content on the same page (optional, default: false).
1142
- # :single_page - A Boolean indicating whether the operation should stop if the content
1143
- # exceeds the height of a single page.
1144
- # :onto - The document onto which to position the scratch extent. If this option is
1145
- # set, the method returns an Extent instead of a ScratchExtent (optional, default: nil)
1146
- # :pages_advanced - The number of pages the content has been advanced during this
1147
- # operation (internal only) (optional, default: 0)
1140
+ # options - A Hash of options that configure the dry run computation:
1141
+ # :keep_together - A Boolean indicating whether an attempt should be made to keep
1142
+ # the content on the same page (optional, default: false).
1143
+ # :single_page - A Boolean indicating whether the operation should stop if the
1144
+ # content exceeds the height of a single page.
1145
+ # :onto - The document onto which to position the scratch extent. If this option is
1146
+ # set, the method returns an Extent instance (optional, default: false)
1147
+ # :pages_advanced - The number of pages the content has been advanced during this
1148
+ # operation (internal only) (optional, default: 0)
1148
1149
  #
1149
1150
  # Returns an Extent or ScratchExtent object that describes the bounds of the content block.
1150
1151
  def dry_run keep_together: nil, pages_advanced: 0, single_page: nil, onto: nil, &block
1151
- (scratch_pdf = scratch).start_new_page
1152
+ (scratch_pdf = scratch).start_new_page layout: page.layout
1152
1153
  saved_bounds = scratch_pdf.bounds
1153
1154
  scratch_pdf.bounds = bounds.dup.tap do |bounds_copy|
1154
1155
  bounds_copy.instance_variable_set :@document, scratch_pdf
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FormattedString < String
4
+ attr_reader :fragments
5
+
6
+ def initialize fragments
7
+ super [].tap {|accum| (@fragments = fragments).each {|it| accum << it[:text] } }.join
8
+ end
9
+
10
+ def eql? other
11
+ super && (FormattedString === other ? (@fragments ||= nil) == other.fragments : true)
12
+ end
13
+ end
@@ -51,6 +51,9 @@ module Asciidoctor::PDF::FormattedText
51
51
  image_format = fragment[:image_format]
52
52
  if (image_w = fragment[:image_width] || '100%') == 'auto'
53
53
  image_w = nil # use intrinsic width
54
+ elsif image_w.start_with? 'auto*'
55
+ image_scale = (image_w.slice 5, image_w.length).to_f
56
+ image_w = nil # use intrinsic width
54
57
  elsif (pctidx = image_w.index '%') && pctidx + 1 < image_w.length
55
58
  # NOTE: intrinsic width is stored behind % symbol
56
59
  pct = (image_w.slice 0, pctidx).to_f / 100
@@ -79,6 +82,7 @@ module Asciidoctor::PDF::FormattedText
79
82
  image_w = svg_size.output_width
80
83
  else
81
84
  fragment[:image_width] = (image_w = svg_size.output_width).to_s
85
+ image_w *= image_scale if image_scale
82
86
  image_w = available_w if image_w > available_w
83
87
  end
84
88
  fragment[:image_obj] = svg_obj
@@ -88,6 +92,7 @@ module Asciidoctor::PDF::FormattedText
88
92
  image_obj, image_info = ::File.open(image_path, 'rb') {|fd| doc.build_image_object fd }
89
93
  unless image_w
90
94
  fragment[:image_width] = (image_w = to_pt image_info.width, :px).to_s
95
+ image_w *= image_scale if image_scale
91
96
  image_w = available_w if image_w > available_w
92
97
  end
93
98
  if (image_h = image_w * (image_info.height.fdiv image_info.width)) > max_image_h
@@ -25,11 +25,11 @@ module Asciidoctor::PDF::FormattedText
25
25
  width = fragment.width
26
26
  height = fragment.height
27
27
  end
28
- border_radius = data[:border_radius]
28
+ border_radius = data[:border_radius] || 0
29
29
  if (background_color = data[:background_color])
30
30
  prev_fill_color = pdf.fill_color
31
31
  pdf.fill_color background_color
32
- if border_radius
32
+ if border_radius > 0
33
33
  pdf.fill_rounded_rectangle at, width, height, border_radius
34
34
  else
35
35
  pdf.fill_rectangle at, width, height
@@ -41,7 +41,7 @@ module Asciidoctor::PDF::FormattedText
41
41
  prev_line_width = pdf.line_width
42
42
  pdf.stroke_color border_color
43
43
  pdf.line_width border_width
44
- border_radius ? (pdf.stroke_rounded_rectangle at, width, height, border_radius) : (pdf.stroke_rectangle at, width, height)
44
+ border_radius > 0 ? (pdf.stroke_rounded_rectangle at, width, height, border_radius) : (pdf.stroke_rectangle at, width, height)
45
45
  pdf.stroke_color prev_stroke_color
46
46
  pdf.line_width prev_line_width
47
47
  end
@@ -96,21 +96,24 @@ module Asciidoctor
96
96
  }.compact,
97
97
  }
98
98
  @theme_settings.tap do |accum|
99
- revise_roles = [].to_set
99
+ roles_with_styles = [].to_set
100
100
  theme.each_pair do |key, val|
101
101
  next unless (key = key.to_s).start_with? 'role_'
102
102
  role, key = (key.slice 5, key.length).split '_', 2
103
103
  if (prop = ThemeKeyToFragmentProperty[key])
104
104
  (accum[role] ||= {})[prop] = val
105
+ if key == 'border_width' && val && !(theme[%(role_#{role}_border_color)])
106
+ accum[role][:border_color] = theme.base_border_color
107
+ end
105
108
  #elsif key == 'font_kerning'
106
109
  # unless (resolved_val = val == 'none' ? false : (val == 'normal' ? true : nil)).nil?
107
110
  # (accum[role] ||= {})[:kerning] = resolved_val
108
111
  # end
109
112
  elsif key == 'font_style' || key == 'text_decoration'
110
- revise_roles << role
113
+ roles_with_styles << role
111
114
  end
112
115
  end
113
- revise_roles.each do |role|
116
+ roles_with_styles.each do |role|
114
117
  (accum[role] ||= {})[:styles] = to_styles theme[%(role_#{role}_font_style)], theme[%(role_#{role}_text_decoration)]
115
118
  end
116
119
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '2.0.8'
5
+ VERSION = '2.1.2'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoctor-pdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.8
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Allen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-06-08 00:00:00.000000000 Z
12
+ date: 2022-06-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor
@@ -286,6 +286,7 @@ files:
286
286
  - lib/asciidoctor/pdf/ext/rouge.rb
287
287
  - lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb
288
288
  - lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb
289
+ - lib/asciidoctor/pdf/formatted_string.rb
289
290
  - lib/asciidoctor/pdf/formatted_text.rb
290
291
  - lib/asciidoctor/pdf/formatted_text/formatter.rb
291
292
  - lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb