asciidoctor-pdf 2.0.8 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd47554913fab2e3f8002491a92c430e0dbfe48b9177705470ffa7c64155289b
4
- data.tar.gz: 580c0c9e6a46e5614927f42c4ad309e303dd69681bf10ddb674241cd930e6b31
3
+ metadata.gz: 3fc9b0baae841d33e6205109c5aeacbb88bc196e6396b5dac8df187e0d209f9c
4
+ data.tar.gz: 21fd8ea4cebcb721dc273fa1a8f81104ec8f14f6a4c1a6f396bf6194f5e2309d
5
5
  SHA512:
6
- metadata.gz: 326e87435e664dde2e944b878d2c5c9a5b71cfd285da78115331a8cd14e20f2aaa0cb2b81779bc99ca8642df004ef03f60386a971e9176eb03d17755a9db951b
7
- data.tar.gz: 7d7cd662e4bbe5df6d10c057e2f00a1a1c800f0e5a3e4f7731c94b657b0bf6ec51599e88d42dbd806a1e4bf991e54282c4efb820b0d39912ed34a74d25f89ff4
6
+ metadata.gz: 956e66763693837a9483919f0b8b0428adbda5858efbfa75aa7b3a2fc04aff92a8e666643f7aae74ee01b656c1b9ad16984173dec4d0b0552bf668d0ad73d024
7
+ data.tar.gz: e6e2ca9d393d1eafbea531bdf9b828d50075bf398723ecca99549175331aa9f1422a9640128ccbf511e8ec97db3e7ce0c6d763a3bc28aeadf92fc6fd2a130f77
data/CHANGELOG.adoc CHANGED
@@ -5,6 +5,24 @@
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.0 (2022-06-11) - @mojavelinux
9
+
10
+ Enhancements::
11
+
12
+ * arrange body of article or manpage doctype into multiple columns if `page-columns` key is set in theme (#327)
13
+ * allow column gap to be specified using `page-column-gap` key (#327)
14
+ * introduce `convert_index_categories` method to handle rendering of categories for index inside column box (#327)
15
+ * rename `convert_index_list` method to `convert_index_term` to make its purpose more clear (#327)
16
+ * add `save_theme` helper to work with a copy of the theme within a scope (#2196)
17
+ * add support for `scale` attribute or `iw` unit on `pdfwidth` attribute on image macros (#1933)
18
+ * add backlink from bibref on bibliography entry to first reference to that entry in the document (#1737)
19
+ * preserve text formatting on index term in index section (#897)
20
+ * don't insert page break between part and first chapter if `heading-part-break-after` key in theme is `avoid` (#1795)
21
+
22
+ === Details
23
+
24
+ {url-repo}/releases/tag/v2.1.0[git tag] | {url-repo}/compare/v2.0.8\...v2.1.0[full diff]
25
+
8
26
  == 2.0.8 (2022-06-08) - @mojavelinux
9
27
 
10
28
  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.0, 2022-06-11
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 2.0.x 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 }
@@ -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
@@ -1715,9 +1757,14 @@ module Asciidoctor
1715
1757
  return on_image_error :missing, node, target, (opts.merge align: alignment) unless image_path
1716
1758
 
1717
1759
  # 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
1760
+ case (width = resolve_explicit_width node.attributes, bounds_width: (available_w = bounds.width), support_vw: true, use_fallback: true, constrain_to_bounds: true)
1761
+ when ViewportWidth
1762
+ # TODO: add `to_pt page_width` method to ViewportWidth type
1763
+ width = page_width * (width.to_f / 100)
1764
+ when ImageWidth
1765
+ scale = width.to_f / 100
1766
+ width = nil
1767
+ end
1721
1768
 
1722
1769
  caption_end = @theme.image_caption_end&.to_sym || :bottom
1723
1770
  caption_max_width = @theme.image_caption_max_width
@@ -1747,7 +1794,9 @@ module Asciidoctor
1747
1794
  enable_file_requests_with_root: file_request_root,
1748
1795
  cache_images: cache_uri
1749
1796
  rendered_w = (svg_size = svg_obj.document.sizing).output_width
1750
- if !width && (svg_obj.document.root.attributes.key? 'width') && rendered_w > available_w
1797
+ if scale
1798
+ svg_size = svg_obj.resize width: (rendered_w = [available_w, rendered_w * scale].min)
1799
+ elsif !width && (svg_obj.document.root.attributes.key? 'width') && rendered_w > available_w
1751
1800
  # NOTE: restrict width to available width (prawn-svg already coerces to pixels)
1752
1801
  svg_size = svg_obj.resize width: (rendered_w = available_w)
1753
1802
  end
@@ -1781,8 +1830,10 @@ module Asciidoctor
1781
1830
  image_obj, image_info = ::Base64 === image_path ?
1782
1831
  ::StringIO.open((::Base64.decode64 image_path), 'rb') {|fd| build_image_object fd } :
1783
1832
  ::File.open(image_path, 'rb') {|fd| build_image_object fd }
1833
+ actual_w = to_pt image_info.width, :px
1834
+ width = actual_w * scale if scale
1784
1835
  # 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)
1836
+ rendered_w, rendered_h = image_info.calc_image_dimensions width: (width || [available_w, actual_w].min)
1786
1837
  # NOTE: shrink image so it fits within available space; group image & caption
1787
1838
  if rendered_h > (available_h = cursor - caption_h)
1788
1839
  unless pinned || at_page_top?
@@ -2380,9 +2431,12 @@ module Asciidoctor
2380
2431
  if (text = ref.xreftext node.attr 'xrefstyle', nil, true)&.include? '<a'
2381
2432
  text = text.gsub DropAnchorRx, ''
2382
2433
  end
2434
+ if ref.inline? && ref.type == :bibref && !scratch? && (@bibref_refs.add? refid)
2435
+ anchor = %(<a id="_bibref_ref_#{refid}">#{DummyText}</a>)
2436
+ end
2383
2437
  @resolving_xref = nil
2384
2438
  end
2385
- %(<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', '&#93;'
2439
+ %(#{anchor || ''}<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', '&#93;'
2386
2440
  else
2387
2441
  %(<a anchor="#{doc.attr 'pdf-anchor'}">#{node.text || '[^top&#93;'}</a>)
2388
2442
  end
@@ -2390,10 +2444,12 @@ module Asciidoctor
2390
2444
  # NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
2391
2445
  %(<a id="#{node.id}">#{DummyText}</a>)
2392
2446
  when :bibref
2393
- # NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
2447
+ id = node.id
2394
2448
  # 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})
2449
+ reftext = (reftext = node.reftext) ? %([#{reftext}]) : %([#{id}])
2450
+ reftext = %(<a anchor="_bibref_ref_#{id}">#{reftext}</a>) if @bibref_refs.include? id
2451
+ # NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
2452
+ %(<a id="#{id}">#{DummyText}</a>#{reftext})
2397
2453
  else
2398
2454
  log :warn, %(unknown anchor type: #{node.type.inspect})
2399
2455
  nil
@@ -2515,12 +2571,18 @@ module Asciidoctor
2515
2571
  class_attr = (role = node.role) ? %( class="#{role}") : ''
2516
2572
  fit_attr = (fit = node.attr 'fit') ? %( fit="#{fit}") : ''
2517
2573
  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
2574
+ if ImageWidth === width
2575
+ if state # check that converter is initialized
2576
+ width = (intrinsic_image_width image_path, image_format) * (width.to_f / 100)
2577
+ else
2578
+ width = %(auto*#{width})
2579
+ end
2580
+ elsif node.parent.context == :table_cell && ::String === width && (width.end_with? '%')
2581
+ width += (intrinsic_image_width image_path, image_format).to_s
2520
2582
  end
2521
2583
  width_attr = %( width="#{width}")
2522
2584
  elsif state # check that converter is initialized
2523
- width_attr = %( width="#{(intrinsic_image_dimensions image_path, image_format)[:width]}")
2585
+ width_attr = %( width="#{intrinsic_image_width image_path, image_format}")
2524
2586
  else
2525
2587
  width_attr = ' width="auto"' # defer operation until arranger runs
2526
2588
  end
@@ -2541,17 +2603,20 @@ module Asciidoctor
2541
2603
  if scratch?
2542
2604
  visible ? node.text : ''
2543
2605
  else
2544
- # NOTE: initialize index in case converter is called before PDF is initialized
2545
- @index ||= IndexCatalog.new
2606
+ unless defined? @index
2607
+ # NOTE: initialize index and text formatter in case converter is called before PDF is initialized
2608
+ @index = IndexCatalog.new
2609
+ @text_formatter = FormattedText::Formatter.new theme: (load_theme node.document)
2610
+ end
2546
2611
  # NOTE: page number (:page key) is added by InlineDestinationMarker
2547
2612
  dest = { anchor: (anchor_name = @index.next_anchor_name) }
2548
2613
  anchor = %(<a id="#{anchor_name}" type="indexterm"#{visible ? ' visible="true"' : ''}>#{DummyText}</a>)
2549
2614
  if visible
2550
2615
  visible_term = node.text
2551
- @index.store_primary_term (sanitize visible_term), dest
2616
+ @index.store_primary_term (FormattedString.new parse_text visible_term, inline_format: [normalize: true]), dest
2552
2617
  %(#{anchor}#{visible_term})
2553
2618
  else
2554
- @index.store_term (node.attr 'terms').map {|term| sanitize term }, dest
2619
+ @index.store_term (node.attr 'terms').map {|term| FormattedString.new parse_text term, inline_format: [normalize: true] }, dest
2555
2620
  anchor
2556
2621
  end
2557
2622
  end
@@ -2578,8 +2643,6 @@ module Asciidoctor
2578
2643
  end
2579
2644
 
2580
2645
  def convert_inline_quoted node
2581
- theme = load_theme node.document
2582
-
2583
2646
  case node.type
2584
2647
  when :emphasis
2585
2648
  open, close, is_tag = ['<em>', '</em>', true]
@@ -2592,25 +2655,26 @@ module Asciidoctor
2592
2655
  when :subscript
2593
2656
  open, close, is_tag = ['<sub>', '</sub>', true]
2594
2657
  when :double
2595
- open, close, is_tag = [theme.quotes[0], theme.quotes[1], false]
2658
+ open, close = (load_theme node.document).quotes.slice 0, 2
2596
2659
  quotes = true
2597
2660
  when :single
2598
- open, close, is_tag = [theme.quotes[2], theme.quotes[3], false]
2661
+ open, close = (load_theme node.document).quotes.slice 2, 2
2599
2662
  quotes = true
2600
2663
  when :mark
2601
2664
  open, close, is_tag = ['<mark>', '</mark>', true]
2602
2665
  else
2603
- open, close, is_tag = [nil, nil, false]
2666
+ open = close = ''
2604
2667
  end
2605
2668
 
2606
2669
  inner_text = node.text
2607
2670
 
2608
- if quotes && (len = inner_text.length) > 3 &&
2609
- (inner_text.end_with? '...') && !((inner_text_trunc = inner_text.slice 0, len - 3).end_with? ?\\)
2671
+ if quotes && (len = inner_text.length) > 3 && (inner_text.end_with? '...') &&
2672
+ !((inner_text_trunc = inner_text.slice 0, len - 3).end_with? ?\\)
2610
2673
  inner_text = inner_text_trunc + '&#8230;'
2611
2674
  end
2612
2675
 
2613
2676
  if (roles = node.role)
2677
+ theme = load_theme node.document
2614
2678
  roles.split.each do |role|
2615
2679
  if (text_transform = theme[%(role_#{role}_text_transform)])
2616
2680
  inner_text = transform_text inner_text, text_transform
@@ -3871,6 +3935,10 @@ module Asciidoctor
3871
3935
  { width: 0, height: 0 }
3872
3936
  end
3873
3937
 
3938
+ def intrinsic_image_width path, format
3939
+ (intrinsic_image_dimensions path, format)[:width]
3940
+ end
3941
+
3874
3942
  # Sends the specified message to the log unless this method is called from the scratch document
3875
3943
  def log severity, message = nil, &block
3876
3944
  logger.send severity, message, &block unless scratch?
@@ -4042,11 +4110,15 @@ module Asciidoctor
4042
4110
  if attrs.key? 'pdfwidth'
4043
4111
  if (width = attrs['pdfwidth']).end_with? '%'
4044
4112
  bounds_width ? (width.to_f / 100) * bounds_width : width
4113
+ elsif width.end_with? 'iw'
4114
+ (width.chomp 'iw').extend ImageWidth
4045
4115
  elsif opts[:support_vw] && (width.end_with? 'vw')
4046
4116
  (width.chomp 'vw').extend ViewportWidth
4047
4117
  else
4048
4118
  str_to_pt width
4049
4119
  end
4120
+ elsif attrs.key? 'scale'
4121
+ attrs['scale'].dup.extend ImageWidth
4050
4122
  elsif attrs.key? 'scaledwidth'
4051
4123
  # NOTE: the parser automatically appends % if value is unitless
4052
4124
  if (width = attrs['scaledwidth']).end_with? '%'
@@ -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
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '2.0.8'
5
+ VERSION = '2.1.0'
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.0
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-11 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