asciidoctor-pdf 2.0.0.beta.2 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ Asciidoctor::AbstractBlock.prepend (Module.new do
4
+ def empty?
5
+ blocks.empty?
6
+ end
7
+
8
+ def first_child
9
+ blocks[0]
10
+ end
11
+
12
+ def last_child
13
+ blocks[-1]
14
+ end
15
+
16
+ def last_child?
17
+ self == parent.blocks[-1]
18
+ end
19
+
20
+ def next_sibling
21
+ (siblings = parent.blocks)[(siblings.index self) + 1]
22
+ end
23
+
24
+ def previous_sibling
25
+ (self_idx = (siblings = parent.blocks).index self) > 0 ? siblings[self_idx - 1] : nil
26
+ end
27
+
28
+ def remove
29
+ parent.blocks.delete self
30
+ end
31
+ end)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # NOTE: these are either candidates for inclusion in Asciidoctor core or backports
4
+ require_relative 'asciidoctor/abstract_block'
4
5
  require_relative 'asciidoctor/document'
5
6
  require_relative 'asciidoctor/section'
6
7
  require_relative 'asciidoctor/list'
@@ -5,12 +5,16 @@ Prawn::Document::ColumnBox.prepend (Module.new do
5
5
  stretchy? ? @parent.absolute_bottom : super
6
6
  end
7
7
 
8
- def move_past_bottom *_args
9
- initial_page = @document.page
10
- super
11
- if (page = @document.page) != initial_page && page.margins != initial_page.margins
12
- @document.bounds = self.class.new @document, @parent, (margin_box = @document.margin_box).absolute_top_left,
8
+ def move_past_bottom
9
+ (doc = @document).y = @y
10
+ return if (@current_column = (@current_column + 1) % @columns) > 0
11
+ @y = (par = @parent).absolute_top if @reflow_margins
12
+ initial_margins = doc.page.margins
13
+ par.move_past_bottom
14
+ if doc.page.margins != initial_margins
15
+ doc.bounds = self.class.new doc, par, (margin_box = doc.margin_box).absolute_top_left,
13
16
  columns: @columns, reflow_margins: true, spacer: @spacer, width: margin_box.width
14
17
  end
18
+ nil
15
19
  end
16
20
  end)
@@ -4,7 +4,7 @@ Prawn::Font::AFM.instance_variable_set :@hide_m17n_warning, true
4
4
 
5
5
  require 'prawn/icon'
6
6
 
7
- Prawn::Icon::Compatibility.send :prepend, (::Module.new { def warning *_args; end })
7
+ Prawn::Icon::Compatibility.prepend (::Module.new { def warning *_args; end })
8
8
 
9
9
  module Asciidoctor
10
10
  module Prawn
@@ -13,6 +13,8 @@ module Asciidoctor
13
13
  include ::Asciidoctor::PDF::Sanitizer
14
14
  include ::Asciidoctor::PDF::TextTransformer
15
15
 
16
+ ColumnBox = ::Prawn::Document::ColumnBox
17
+
16
18
  FontAwesomeIconSets = %w(fab far fas)
17
19
  IconSets = %w(fab far fas fi pf).to_set
18
20
  IconSetPrefixes = IconSets.map {|it| it + '-' }
@@ -429,20 +431,27 @@ module Asciidoctor
429
431
 
430
432
  # NOTE: override built-in fill_formatted_text_box to insert leading before second line when :first_line is true
431
433
  def fill_formatted_text_box text, options
434
+ if (initial_gap = options[:initial_gap]) && !text.empty? && text[0][:from_page] != page_number
435
+ self.y -= initial_gap
436
+ end
432
437
  merge_text_box_positioning_options options
433
438
  box = ::Prawn::Text::Formatted::Box.new text, options
434
- remaining_text = box.render
439
+ remaining_fragments = box.render
435
440
  @no_text_printed = box.nothing_printed?
436
441
  @all_text_printed = box.everything_printed?
442
+ unless remaining_fragments.empty? || (remaining_fragments[0][:from_page] ||= page_number) == page_number
443
+ log :error, %(cannot fit formatted text on page: #{remaining_fragments.map {|it| it[:image_path] || it[:text] }.join})
444
+ page.tare_content_stream
445
+ remaining_fragments = {}
446
+ end
437
447
 
438
- if ((defined? @final_gap) && @final_gap) ||
439
- (options[:first_line] && (options[:final_gap] || !(@no_text_printed || @all_text_printed)))
448
+ if @final_gap || (options[:first_line] && !(@no_text_printed || @all_text_printed))
440
449
  self.y -= box.height + box.line_gap + box.leading
441
450
  else
442
451
  self.y -= box.height
443
452
  end
444
453
 
445
- remaining_text
454
+ remaining_fragments
446
455
  end
447
456
 
448
457
  # NOTE: override built-in draw_indented_formatted_line to set first_line flag
@@ -455,7 +464,7 @@ module Asciidoctor
455
464
  # remaining lines (which is the default behavior in Prawn).
456
465
  def text_with_formatted_first_line string, first_line_options, options
457
466
  if (first_line_font_color = first_line_options.delete :color)
458
- other_lines_font_color, options[:color] = options[:color], first_line_font_color
467
+ remaining_lines_font_color, options[:color] = options[:color], first_line_font_color
459
468
  end
460
469
  fragments = parse_text string, options
461
470
  # NOTE: the low-level APIs we're using don't recognize the :styles option, so we must resolve
@@ -468,19 +477,25 @@ module Asciidoctor
468
477
  end
469
478
  first_line_text_transform = first_line_options.delete :text_transform
470
479
  options = options.merge document: self
480
+ @final_gap = final_gap = options.delete :final_gap
471
481
  text_indent = options.delete :indent_paragraphs
472
482
  # QUESTION: should we merge more carefully here? (hand-select keys?)
473
483
  first_line_options = (options.merge first_line_options).merge single_line: true, first_line: true
474
484
  box = ::Prawn::Text::Formatted::Box.new fragments, first_line_options
475
485
  if text_indent
476
- remaining_fragments = indent text_indent do
477
- box.render dry_run: true
478
- end
486
+ remaining_fragments = indent(text_indent) { box.render dry_run: true }
479
487
  else
480
488
  remaining_fragments = box.render dry_run: true
481
489
  end
490
+ if remaining_fragments.empty?
491
+ remaining_fragments = nil
492
+ elsif (remaining_fragments[0][:from_page] ||= page_number) != page_number
493
+ log :error, %(cannot fit formatted text on page: #{remaining_fragments.map {|it| it[:image_path] || it[:text] }.join})
494
+ page.tare_content_stream
495
+ remaining_fragments = nil
496
+ end
482
497
  if first_line_text_transform
483
- # NOTE: applying text transform here could alter the wrapping, so we need to isolate first line and shrink to fit
498
+ # NOTE: applying text transform here could alter the wrapping, so isolate first line and shrink it to fit
484
499
  first_line_text = (box.instance_variable_get :@printed_lines)[0]
485
500
  unless first_line_text == fragments[0][:text]
486
501
  original_fragments, fragments = fragments, []
@@ -496,17 +511,16 @@ module Asciidoctor
496
511
  end
497
512
  fragments.each {|fragment| fragment[:text] = transform_text fragment[:text], first_line_text_transform }
498
513
  first_line_options[:overflow] = :shrink_to_fit
499
- first_line_options[:final_gap] = first_line_options[:force_justify] = true unless remaining_fragments.empty?
514
+ @final_gap = first_line_options[:force_justify] = true if remaining_fragments
500
515
  end
501
516
  if text_indent
502
- indent text_indent do
503
- fill_formatted_text_box fragments, first_line_options
504
- end
517
+ indent(text_indent) { fill_formatted_text_box fragments, first_line_options }
505
518
  else
506
519
  fill_formatted_text_box fragments, first_line_options
507
520
  end
508
- unless remaining_fragments.empty?
509
- options[:color] = other_lines_font_color if first_line_font_color
521
+ if remaining_fragments
522
+ options[:color] = remaining_lines_font_color if first_line_font_color
523
+ @final_gap = final_gap if first_line_text_transform
510
524
  remaining_fragments = fill_formatted_text_box remaining_fragments, options
511
525
  draw_remaining_formatted_text_on_new_pages remaining_fragments, options
512
526
  end
@@ -541,6 +555,18 @@ module Asciidoctor
541
555
 
542
556
  # Cursor
543
557
 
558
+ # Override the built-in float method to add support for restoring the current column of a ColumnBox
559
+ #
560
+ def float
561
+ original_page_number = page_number
562
+ original_y = y
563
+ original_column = bounds.instance_variable_get :@current_column if ColumnBox === bounds
564
+ yield
565
+ go_to_page original_page_number unless page_number == original_page_number
566
+ self.y = original_y
567
+ bounds.instance_variable_set :@current_column, original_column if original_column
568
+ end
569
+
544
570
  # Short-circuits the call to the built-in move_up operation
545
571
  # when n is 0.
546
572
  #
@@ -589,7 +615,7 @@ module Asciidoctor
589
615
  p_top, p_right, p_bottom, p_left = expand_padding_value padding
590
616
  # logic is intentionally inlined
591
617
  begin
592
- if node && ((last_block = node).content_model != :compound || (last_block = node.blocks[-1])&.context == :paragraph)
618
+ if node && ((last_block = node).content_model != :compound || (last_block = node.last_child)&.context == :paragraph)
593
619
  @bottom_gutters << { last_block => p_bottom }
594
620
  else
595
621
  @bottom_gutters << {}
@@ -599,7 +625,7 @@ module Asciidoctor
599
625
  bounds.add_right_padding p_right
600
626
  yield
601
627
  ensure
602
- cursor > p_bottom ? (move_down p_bottom) : reference_bounds.move_past_bottom unless at_page_top?
628
+ cursor > p_bottom ? (move_down p_bottom) : bounds.move_past_bottom unless at_page_top?
603
629
  @bottom_gutters.pop
604
630
  bounds.subtract_left_padding p_left
605
631
  bounds.subtract_right_padding p_right
@@ -943,7 +969,7 @@ module Asciidoctor
943
969
  # if the current page is the last page of the document. Otherwise, it simply
944
970
  # advances to the next existing page.
945
971
  def advance_page options = {}
946
- last_page? ? (start_new_page options) : (go_to_page page_number + 1)
972
+ !options.empty? && last_page? ? (start_new_page options) : bounds.move_past_bottom
947
973
  end
948
974
 
949
975
  # Start a new page without triggering the on_page_create callback
@@ -952,14 +978,14 @@ module Asciidoctor
952
978
  perform_discretely { start_new_page options }
953
979
  end
954
980
 
955
- # Grouping
981
+ # Scratch
956
982
 
957
- def allocate_prototype
958
- @prototype = create_prototype { ::Marshal.load ::Marshal.dump self }
983
+ def allocate_scratch_prototype
984
+ @scratch_prototype = create_scratch_prototype { ::Marshal.load ::Marshal.dump self }
959
985
  end
960
986
 
961
987
  def scratch
962
- @scratch ||= ((Marshal.load Marshal.dump @prototype).send :init_scratch, self)
988
+ @scratch ||= ((Marshal.load Marshal.dump @scratch_prototype).send :init_scratch, self)
963
989
  end
964
990
 
965
991
  def scratch?
@@ -1116,10 +1142,14 @@ module Asciidoctor
1116
1142
  # Returns an Extent or ScratchExtent object that describes the bounds of the content block.
1117
1143
  def dry_run keep_together: nil, pages_advanced: 0, single_page: nil, onto: nil, &block
1118
1144
  (scratch_pdf = scratch).start_new_page
1119
- scratch_bounds = scratch_pdf.bounds
1120
- restore_bounds = [:@total_left_padding, :@total_right_padding, :@width, :@x].each_with_object({}) do |name, accum|
1121
- accum[name] = scratch_bounds.instance_variable_get name
1122
- scratch_bounds.instance_variable_set name, (bounds.instance_variable_get name)
1145
+ saved_bounds = scratch_pdf.bounds
1146
+ scratch_pdf.bounds = bounds.dup.tap do |bounds_copy|
1147
+ bounds_copy.instance_variable_set :@document, scratch_pdf
1148
+ bounds_copy.instance_variable_set :@parent, saved_bounds
1149
+ if ColumnBox === bounds_copy
1150
+ bounds_copy.instance_variable_set :@width, bounds_copy.bare_column_width
1151
+ bounds_copy.instance_variable_set :@current_column, (bounds_copy.instance_variable_set :@columns, 1) - 1
1152
+ end
1123
1153
  end
1124
1154
  scratch_pdf.move_cursor_to cursor unless (scratch_start_at_top = keep_together || pages_advanced > 0 || at_page_top?)
1125
1155
  scratch_start_cursor = scratch_pdf.cursor
@@ -1161,7 +1191,7 @@ module Asciidoctor
1161
1191
  extent = ScratchExtent.new scratch_start_page, scratch_start_cursor, scratch_end_page, scratch_end_cursor
1162
1192
  onto ? extent.position_onto(*onto) : extent
1163
1193
  ensure
1164
- restore_bounds.each {|name, val| scratch_bounds.instance_variable_set name, val }
1194
+ scratch_pdf.bounds = saved_bounds
1165
1195
  end
1166
1196
  end
1167
1197
  end
@@ -2,12 +2,15 @@
2
2
 
3
3
  module Prawn::Text::Formatted::ProtectBottomGutter
4
4
  def enough_height_for_this_line?
5
- return super unless @arranger.finished?
6
- begin
7
- @height -= @bottom_gutter
5
+ if @arranger.finished? && @arranger.fragments.none? {|it| it.format_state[:full_height] }
6
+ begin
7
+ @height -= @bottom_gutter
8
+ super
9
+ ensure
10
+ @height += @bottom_gutter
11
+ end
12
+ else
8
13
  super
9
- ensure
10
- @height += @bottom_gutter
11
14
  end
12
15
  end
13
16
  end
@@ -38,7 +38,7 @@ module Asciidoctor::PDF::FormattedText
38
38
  return if (raw_image_fragments = fragments.select {|f| (f.key? :image_path) && !(f.key? :image_obj) }).empty?
39
39
  scratch = doc.scratch?
40
40
  available_w = available_width
41
- available_h = doc.page.empty? ? doc.cursor : doc.bounds.height
41
+ available_h = doc.bounds.height
42
42
  last_fragment = {}
43
43
  raw_image_fragments.each do |fragment|
44
44
  if fragment[:object_id] == last_fragment[:object_id]
@@ -96,6 +96,10 @@ module Asciidoctor::PDF::FormattedText
96
96
  if (f_height = image_h) > (line_font = doc.font).height * 1.5
97
97
  # align with descender (equivalent to vertical-align: bottom in CSS)
98
98
  fragment[:ascender] = f_height - (fragment[:descender] = line_font.descender)
99
+ if f_height == available_h
100
+ fragment[:ascender] -= (doc.calc_line_metrics (doc.instance_variable_get :@base_line_height), line_font, doc.font_size).padding_top
101
+ fragment[:full_height] = true
102
+ end
99
103
  doc.font_size (fragment[:size] = f_height * (doc.font_size / line_font.height))
100
104
  # align with baseline (roughly equivalent to vertical-align: baseline in CSS)
101
105
  #fragment[:ascender] = f_height
@@ -11,15 +11,15 @@ module Asciidoctor
11
11
  def wrap array
12
12
  return super unless array[0][:linenum] # sanity check
13
13
  initialize_wrap array
14
+ @line_wrap.extend SourceLineWrap
14
15
  highlight_line = stop = nil
15
16
  unconsumed = @arranger.unconsumed
16
17
  until stop
17
18
  if (first_fragment = unconsumed[0])[:linenum]
18
19
  linenum_text = first_fragment[:text]
19
- linenum_spacer ||= { text: (NoBreakSpace.encode linenum_text.encoding) + (' ' * (linenum_text.length - 1)) }
20
+ linenum_spacer ||= { text: (NoBreakSpace.encode linenum_text.encoding) + (' ' * (linenum_text.length - 1)), linenum: :spacer }
20
21
  highlight_line = (second_fragment = unconsumed[1])[:highlight] ? second_fragment.dup : nil
21
- else
22
- # NOTE: a wrapped line
22
+ else # wrapped line
23
23
  first_fragment[:text] = first_fragment[:text].lstrip
24
24
  @arranger.unconsumed.unshift highlight_line if highlight_line
25
25
  @arranger.unconsumed.unshift linenum_spacer.dup
@@ -43,6 +43,12 @@ module Asciidoctor
43
43
  @arranger.unconsumed
44
44
  end
45
45
  end
46
+
47
+ module SourceLineWrap
48
+ def update_line_status_based_on_last_output
49
+ @arranger.current_format_state[:linenum] ? nil : super
50
+ end
51
+ end
46
52
  end
47
53
  end
48
54
  end
@@ -95,22 +95,24 @@ module Asciidoctor
95
95
  styles: (to_styles theme.menu_font_style),
96
96
  }.compact,
97
97
  }
98
- revise_roles = [].to_set
99
- theme.each_pair.each_with_object @theme_settings do |(key, val), accum|
100
- next unless (key = key.to_s).start_with? 'role_'
101
- role, key = (key.slice 5, key.length).split '_', 2
102
- if (prop = ThemeKeyToFragmentProperty[key])
103
- (accum[role] ||= {})[prop] = val
104
- #elsif key == 'font_kerning'
105
- # unless (resolved_val = val == 'none' ? false : (val == 'normal' ? true : nil)).nil?
106
- # (accum[role] ||= {})[:kerning] = resolved_val
107
- # end
108
- elsif key == 'font_style' || key == 'text_decoration'
109
- revise_roles << role
98
+ @theme_settings.tap do |accum|
99
+ revise_roles = [].to_set
100
+ theme.each_pair do |key, val|
101
+ next unless (key = key.to_s).start_with? 'role_'
102
+ role, key = (key.slice 5, key.length).split '_', 2
103
+ if (prop = ThemeKeyToFragmentProperty[key])
104
+ (accum[role] ||= {})[prop] = val
105
+ #elsif key == 'font_kerning'
106
+ # unless (resolved_val = val == 'none' ? false : (val == 'normal' ? true : nil)).nil?
107
+ # (accum[role] ||= {})[:kerning] = resolved_val
108
+ # end
109
+ elsif key == 'font_style' || key == 'text_decoration'
110
+ revise_roles << role
111
+ end
112
+ end
113
+ revise_roles.each do |role|
114
+ (accum[role] ||= {})[:styles] = to_styles theme[%(role_#{role}_font_style)], theme[%(role_#{role}_text_decoration)]
110
115
  end
111
- end
112
- revise_roles.each_with_object @theme_settings do |role, accum|
113
- (accum[role] ||= {})[:styles] = to_styles theme[%(role_#{role}_font_style)], theme[%(role_#{role}_text_decoration)]
114
116
  end
115
117
  @theme_settings['line-through'] = { styles: [:strikethrough].to_set } unless @theme_settings.key? 'line-through'
116
118
  @theme_settings['underline'] = { styles: [:underline].to_set } unless @theme_settings.key? 'underline'
@@ -276,8 +278,10 @@ module Asciidoctor
276
278
  when '#' # hex string (e.g., #FF0000)
277
279
  fragment[:color] = value.length == 7 ? (value.slice 1, 6) : (value.slice 1, 3).each_char.map {|c| c * 2 }.join if HexColorRx.match? value
278
280
  when '[' # CMYK array (e.g., [50, 100, 0, 0])
279
- fragment[:color] = ((((value.slice 1, value.length).chomp ']').split ', ', 4).each_with_object ::Array.new 4, 0).with_index do |(it, accum), idx|
280
- accum[idx] = (ival = it.to_i) == (fval = it.to_f) ? ival : fval
281
+ fragment[:color] = [0, 0, 0, 0].tap do |accum|
282
+ (((value.slice 1, value.length).chomp ']').split ', ', 4).each_with_index do |it, idx|
283
+ accum[idx] = (ival = it.to_i) == (fval = it.to_f) ? ival : fval
284
+ end
281
285
  end
282
286
  else # assume a 6-character hex color (internal only)
283
287
  fragment[:color] = value
@@ -15,9 +15,7 @@ module Asciidoctor
15
15
  BaseThemePath = ::File.join ThemesDir, 'base-theme.yml'
16
16
  BundledThemeNames = (::Dir.children ThemesDir).map {|it| it.slice 0, it.length - 10 }
17
17
  DeprecatedCategoryKeys = { 'blockquote' => 'quote', 'key' => 'kbd', 'literal' => 'codespan', 'outline_list' => 'list' }
18
- DeprecatedKeys = %w(base heading heading_h1 heading_h2 heading_h3 heading_h4 heading_h5 heading_h6 title_page abstract abstract_title admonition_label sidebar_title toc_title).each_with_object({ 'table_caption_side' => 'table_caption_end' }) do |prefix, accum|
19
- accum[%(#{prefix}_align)] = %(#{prefix}_text_align)
20
- end
18
+ DeprecatedKeys = { 'table_caption_side' => 'table_caption_end' }.tap {|accum| %w(base heading heading_h1 heading_h2 heading_h3 heading_h4 heading_h5 heading_h6 title_page abstract abstract_title admonition_label sidebar_title toc_title).each {|prefix| accum[%(#{prefix}_align)] = %(#{prefix}_text_align) } }
21
19
  PaddingBottomHackKeys = %w(example_padding quote_padding sidebar_padding verse_padding)
22
20
 
23
21
  VariableRx = /\$([a-z0-9_-]+)/
@@ -159,10 +157,12 @@ module Asciidoctor
159
157
  elsif key == 'font_fallbacks'
160
158
  data[key] = ::Array === val ? val.map {|name| expand_vars name.to_s, data } : []
161
159
  elsif key.start_with? 'admonition_icon_'
162
- data[key] = val.map do |(key2, val2)|
163
- key2 = key2.tr '-', '_' if key2.include? '-'
164
- [key2.to_sym, (key2.end_with? '_color') ? (to_color evaluate val2, data) : (evaluate val2, data)]
165
- end.to_h if val
160
+ data[key] = {}.tap do |accum|
161
+ val.each do |key2, val2|
162
+ key2 = key2.tr '-', '_' if key2.include? '-'
163
+ accum[key2.to_sym] = (key2.end_with? '_color') ? (to_color evaluate val2, data) : (evaluate val2, data)
164
+ end
165
+ end if val
166
166
  elsif ::Hash === val
167
167
  if (rekey = DeprecatedCategoryKeys[key])
168
168
  logger.warn %(the #{key.tr '_', '-'} theme category is deprecated; use the #{rekey.tr '_', '-'} category instead)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '2.0.0.beta.2'
5
+ VERSION = '2.0.1'
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.0.beta.2
4
+ version: 2.0.1
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-05-14 00:00:00.000000000 Z
12
+ date: 2022-05-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor
@@ -246,6 +246,7 @@ files:
246
246
  - lib/asciidoctor/pdf/converter.rb
247
247
  - lib/asciidoctor/pdf/ext.rb
248
248
  - lib/asciidoctor/pdf/ext/asciidoctor.rb
249
+ - lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb
249
250
  - lib/asciidoctor/pdf/ext/asciidoctor/document.rb
250
251
  - lib/asciidoctor/pdf/ext/asciidoctor/image.rb
251
252
  - lib/asciidoctor/pdf/ext/asciidoctor/list.rb
@@ -324,9 +325,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
324
325
  version: '0'
325
326
  required_rubygems_version: !ruby/object:Gem::Requirement
326
327
  requirements:
327
- - - ">"
328
+ - - ">="
328
329
  - !ruby/object:Gem::Version
329
- version: 1.3.1
330
+ version: '0'
330
331
  requirements: []
331
332
  rubygems_version: 3.3.7
332
333
  signing_key: