diff --git a/lib/asciidoctor/pdf/converter.rb b/lib/asciidoctor/pdf/converter.rb
index 5f8bb1a05..175a5efea 100644
--- a/lib/asciidoctor/pdf/converter.rb
+++ b/lib/asciidoctor/pdf/converter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require_relative 'formatted_string'
require_relative 'formatted_text'
require_relative 'index_catalog'
require_relative 'pdfmark'
@@ -731,28 +732,42 @@ def convert_index_section node
end
def convert_index_list_item term, pagenum_sequence_style = nil
- text = escape_xml term.name
+ term_fragments = term.name.fragments
unless term.container?
+ pagenum_fragment = (parse_text %(#{DummyText}), inline_format: true)[0]
if @media == 'screen'
case pagenum_sequence_style
when 'page'
- pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| %(#{dest[:page]}) }
+ pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| pagenum_fragment.merge anchor: dest[:anchor], text: dest[:page] }
when 'range'
first_anchor_per_page = {}.tap {|accum| term.dests.each {|dest| accum[dest[:page]] ||= dest[:anchor] } }
pagenums = (consolidate_ranges first_anchor_per_page.keys).map do |range|
anchor = first_anchor_per_page[(range.include? '-') ? (range.partition '-')[0] : range]
- %(#{range})
+ pagenum_fragment.merge text: range, anchor: anchor
end
else # term
- pagenums = term.dests.map {|dest| %(#{dest[:page]}) }
+ pagenums = term.dests.map {|dest| pagenum_fragment.merge text: dest[:page], anchor: dest[:anchor] }
end
else
- pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
+ pagenums = (consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq).map {|range| { text: range } }
+ end
+ pagenums.each do |pagenum|
+ if (prev_fragment = term_fragments[-1]).size == 1
+ # NOTE: addresses a very minor kerning issue for text adjacent to the comma
+ if pagenum.size == 1
+ term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, #{pagenum[:text]})
+ next
+ else
+ term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, )
+ end
+ else
+ term_fragments << ({ text: ', ' })
+ end
+ term_fragments << pagenum
end
- text = %(#{text}, #{pagenums.join ', '})
end
subterm_indent = @theme.description_list_description_indent
- ink_prose text, align: :left, margin: 0, hanging_indent: subterm_indent * 2
+ typeset_formatted_text term_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2
indent subterm_indent do
term.subterms.each do |subterm|
convert_index_list_item subterm, pagenum_sequence_style
@@ -2570,17 +2585,20 @@ def convert_inline_indexterm node
if scratch?
visible ? node.text : ''
else
- # NOTE: initialize index in case converter is called before PDF is initialized
- @index ||= IndexCatalog.new
+ unless defined? @index
+ # NOTE: initialize index and text formatter in case converter is called before PDF is initialized
+ @index = IndexCatalog.new
+ @text_formatter = FormattedText::Formatter.new theme: (load_theme node.document)
+ end
# NOTE: page number (:page key) is added by InlineDestinationMarker
dest = { anchor: (anchor_name = @index.next_anchor_name) }
anchor = %(#{DummyText})
if visible
visible_term = node.text
- @index.store_primary_term (sanitize visible_term), dest
+ @index.store_primary_term (FormattedString.new parse_text visible_term, inline_format: [normalize: true]), dest
%(#{anchor}#{visible_term})
else
- @index.store_term (node.attr 'terms').map {|term| sanitize term }, dest
+ @index.store_term (node.attr 'terms').map {|term| FormattedString.new parse_text term, inline_format: [normalize: true] }, dest
anchor
end
end
diff --git a/lib/asciidoctor/pdf/formatted_string.rb b/lib/asciidoctor/pdf/formatted_string.rb
new file mode 100644
index 000000000..f44ff5eed
--- /dev/null
+++ b/lib/asciidoctor/pdf/formatted_string.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class FormattedString < String
+ attr_reader :fragments
+
+ def initialize fragments
+ super [].tap {|accum| (@fragments = fragments).each {|it| accum << it[:text] } }.join
+ end
+
+ def eql? other
+ self == other && @fragments == other.fragments
+ end
+end
diff --git a/spec/index_spec.rb b/spec/index_spec.rb
index bc97ab69a..dfbb99955 100644
--- a/spec/index_spec.rb
+++ b/spec/index_spec.rb
@@ -31,7 +31,8 @@
((foo
{empty}
bar))(((yin
-
+ {empty}
+ {empty}
yang)))
<<<
@@ -147,6 +148,51 @@
(expect index_lines).to include 'custom behavior, 1'
end
+ it 'should preserve text formatting in display of index term' do
+ pdf = to_pdf <<~'EOS', doctype: :book, analyze: true
+ = Document Title
+
+ == Content
+
+ Use the ((`return`)) keyword(((_keyword_))) to force a method to return early.
+
+ There are cats, and then there are ((*big* cats)).
+
+ A ((mouse _gesture_)) is a movement the software recognizes and interprets as a command.
+
+ [index]
+ = Index
+ EOS
+
+ (expect pdf.pages).to have_size 3
+ return_entry_text = pdf.find_unique_text 'return', page_number: 3
+ (expect return_entry_text[:font_name]).to eql 'mplus1mn-regular'
+ keyword_entry_text = pdf.find_unique_text 'keyword', page_number: 3
+ (expect keyword_entry_text[:font_name]).to eql 'NotoSerif-Italic'
+ big_text = pdf.find_unique_text 'big', page_number: 3
+ (expect big_text[:font_name]).to eql 'NotoSerif-Bold'
+ gesture_text = pdf.find_unique_text 'gesture', page_number: 3
+ (expect gesture_text[:font_name]).to eql 'NotoSerif-Italic'
+ end
+
+ it 'should not group term with and without formatting' do
+ pdf = to_pdf <<~'EOS', doctype: :book, analyze: true
+ The ((`proc`)) keyword in Ruby defines a ((proc)), which is a block of code.
+
+ [index]
+ = Index
+ EOS
+
+ (expect pdf.pages).to have_size 2
+ proc_text = pdf.find_text %r/^proc/, page_number: 2
+ (expect proc_text).to have_size 2
+ (expect proc_text[0][:string]).to eql 'proc'
+ (expect proc_text[0][:font_name]).to eql 'mplus1mn-regular'
+ (expect proc_text[1][:font_name]).not_to eql 'mplus1mn-regular'
+ index_lines = pdf.lines pdf.find_text page_number: 2
+ (expect index_lines).to eql ['Index', 'P', 'proc, 1', 'proc, 1']
+ end
+
it 'should not add index entries inside delimited block to index twice' do
pdf = to_pdf <<~'EOS', doctype: :book, analyze: true
= Document Title