asciidoctor-pdf 2.0.0.alpha.2 → 2.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +185 -83
  3. data/README.adoc +31 -126
  4. data/data/fonts/ABOUT-mplus1p-subset +1 -0
  5. data/data/fonts/ABOUT-notosans-subset +1 -0
  6. data/data/fonts/ABOUT-notoserif-subset +1 -0
  7. data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
  8. data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
  9. data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
  10. data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
  11. data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
  12. data/data/fonts/notosans-bold-subset.ttf +0 -0
  13. data/data/fonts/notosans-bold_italic-subset.ttf +0 -0
  14. data/data/fonts/notosans-italic-subset.ttf +0 -0
  15. data/data/fonts/notosans-regular-subset.ttf +0 -0
  16. data/data/fonts/notoserif-bold-subset.ttf +0 -0
  17. data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
  18. data/data/fonts/notoserif-italic-subset.ttf +0 -0
  19. data/data/fonts/notoserif-regular-subset.ttf +0 -0
  20. data/data/themes/base-theme.yml +10 -20
  21. data/data/themes/default-for-print-theme.yml +4 -4
  22. data/data/themes/default-for-print-with-fallback-font-theme.yml +2 -3
  23. data/data/themes/default-for-print-with-font-fallbacks-theme.yml +3 -0
  24. data/data/themes/{sans-with-fallback-font-theme.yml → default-sans-theme.yml} +1 -1
  25. data/data/themes/default-sans-with-font-fallbacks-theme.yml +3 -0
  26. data/data/themes/default-theme.yml +5 -4
  27. data/data/themes/default-with-fallback-font-theme.yml +2 -9
  28. data/data/themes/default-with-font-fallbacks-theme.yml +9 -0
  29. data/docs/theming-guide.adoc +5 -6050
  30. data/lib/asciidoctor/pdf/converter.rb +505 -294
  31. data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +8 -0
  32. data/lib/asciidoctor/pdf/ext/prawn/document/column_box.rb +16 -0
  33. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +110 -58
  34. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +14 -0
  35. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/indented_paragraph_wrap.rb +39 -0
  36. data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +3 -10
  37. data/lib/asciidoctor/pdf/ext/prawn.rb +2 -0
  38. data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +7 -2
  39. data/lib/asciidoctor/pdf/nopngmagick.rb +3 -0
  40. data/lib/asciidoctor/pdf/optimizer.rb +12 -5
  41. data/lib/asciidoctor/pdf/text_transformer.rb +14 -0
  42. data/lib/asciidoctor/pdf/theme_loader.rb +9 -3
  43. data/lib/asciidoctor/pdf/version.rb +1 -1
  44. metadata +9 -3
@@ -14,6 +14,14 @@ class PDF::Core::Page
14
14
  content.stream.filtered_stream == (@tare_content_stream ||= InitialPageContent) && document.page_number > 0
15
15
  end
16
16
 
17
+ # Flags this page as imported.
18
+ #
19
+ def imported
20
+ @imported_page = true
21
+ end
22
+
23
+ alias imported_page imported
24
+
17
25
  # Reset the content of the page.
18
26
  # Note that this method may leave behind an orphaned background image.
19
27
  def reset_content
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn::Document::ColumnBox.prepend (Module.new do
4
+ def absolute_bottom
5
+ stretchy? ? @parent.absolute_bottom : super
6
+ end
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,
13
+ columns: @columns, reflow_margins: true, spacer: @spacer, width: margin_box.width
14
+ end
15
+ end
16
+ end)
@@ -103,7 +103,7 @@ module Asciidoctor
103
103
  NewPageRequiredError = ::Class.new ::StopIteration
104
104
 
105
105
  InhibitNewPageProc = proc do |pdf|
106
- pdf.delete_page
106
+ pdf.delete_current_page
107
107
  raise NewPageRequiredError
108
108
  end
109
109
 
@@ -111,7 +111,7 @@ module Asciidoctor
111
111
 
112
112
  DetectEmptyFirstPageProc = proc do |delegate, pdf|
113
113
  if pdf.state.pages[pdf.page_number - 2].empty?
114
- pdf.delete_page
114
+ pdf.delete_current_page
115
115
  raise NewPageRequiredError
116
116
  end
117
117
  delegate.call pdf if (pdf.state.on_page_create_callback = delegate)
@@ -252,6 +252,15 @@ module Asciidoctor
252
252
  @y == @margin_box.absolute_top
253
253
  end
254
254
 
255
+ # Prevents at_page_top? from returning true while yielding to the specified block.
256
+ #
257
+ def conceal_page_top
258
+ margin_box.instance_variable_set :@y, (old_top = margin_box.absolute_top) + 0.0001
259
+ yield
260
+ ensure
261
+ margin_box.instance_variable_set :@y, old_top
262
+ end
263
+
255
264
  # Returns whether the current page is the last page in the document.
256
265
  #
257
266
  def last_page?
@@ -269,6 +278,20 @@ module Asciidoctor
269
278
  dest_xyz 0, page_height, nil, (page_num ? state.pages[page_num - 1] : page)
270
279
  end
271
280
 
281
+ # Gets the destination registered for the specified name. The return value
282
+ # matches that which was passed to the add_dest method.
283
+ #
284
+ def get_dest name, node = dests.data
285
+ node.children.each do |child|
286
+ if ::PDF::Core::NameTree::Value === child
287
+ return child.value.data if child.name == name
288
+ elsif (found = get_dest name, child)
289
+ return found
290
+ end
291
+ end
292
+ nil
293
+ end
294
+
272
295
  # Fonts
273
296
 
274
297
  # Registers a new custom font described in the data parameter
@@ -396,20 +419,11 @@ module Asciidoctor
396
419
  def parse_text string, options = {}
397
420
  return [] if string.nil?
398
421
 
399
- options = options.dup
400
- if (format_option = options.delete :inline_format)
422
+ if (format_option = options[:inline_format])
401
423
  format_option = [] unless ::Array === format_option
402
- fragments = text_formatter.format string, *format_option
403
- else
404
- fragments = [text: string]
405
- end
406
-
407
- if (color = options.delete :color)
408
- fragments.map do |fragment|
409
- fragment[:color] ? fragment : (fragment.merge color: color)
410
- end
424
+ text_formatter.format string, *format_option
411
425
  else
412
- fragments
426
+ [text: string]
413
427
  end
414
428
  end
415
429
 
@@ -421,7 +435,8 @@ module Asciidoctor
421
435
  @no_text_printed = box.nothing_printed?
422
436
  @all_text_printed = box.everything_printed?
423
437
 
424
- if ((defined? @final_gap) && @final_gap) || (options[:first_line] && !(@no_text_printed || @all_text_printed))
438
+ if ((defined? @final_gap) && @final_gap) ||
439
+ (options[:first_line] && (options[:final_gap] || !(@no_text_printed || @all_text_printed)))
425
440
  self.y -= box.height + box.line_gap + box.leading
426
441
  else
427
442
  self.y -= box.height
@@ -439,31 +454,50 @@ module Asciidoctor
439
454
  # renderered. It's necessary to use low-level APIs in this method so we only style the first line and not the
440
455
  # remaining lines (which is the default behavior in Prawn).
441
456
  def text_with_formatted_first_line string, first_line_options, options
442
- color = options.delete :color
457
+ if (first_line_font_color = first_line_options.delete :color)
458
+ other_lines_font_color, options[:color] = options[:color], first_line_font_color
459
+ end
443
460
  fragments = parse_text string, options
444
461
  # NOTE: the low-level APIs we're using don't recognize the :styles option, so we must resolve
445
- # NOTE: disabled until we have a need for it
462
+ # NOTE: disabled until we have a need for it; currently handled in convert_abstract
446
463
  #if (styles = options.delete :styles)
447
464
  # options[:style] = resolve_font_style styles
448
465
  #end
449
466
  if (first_line_styles = first_line_options.delete :styles)
450
467
  first_line_options[:style] = resolve_font_style first_line_styles
451
468
  end
452
- first_line_color = (first_line_options.delete :color) || color
469
+ first_line_text_transform = first_line_options.delete :text_transform
453
470
  options = options.merge document: self
471
+ text_indent = options.delete :indent_paragraphs
454
472
  # QUESTION: should we merge more carefully here? (hand-select keys?)
455
473
  first_line_options = (options.merge first_line_options).merge single_line: true, first_line: true
456
474
  box = ::Prawn::Text::Formatted::Box.new fragments, first_line_options
457
- # NOTE: get remaining_fragments before we add color to fragments on first line
458
- if (text_indent = options.delete :indent_paragraphs)
475
+ if text_indent
459
476
  remaining_fragments = indent text_indent do
460
477
  box.render dry_run: true
461
478
  end
462
479
  else
463
480
  remaining_fragments = box.render dry_run: true
464
481
  end
465
- # NOTE: color must be applied per-fragment
466
- fragments.each {|fragment| fragment[:color] ||= first_line_color }
482
+ 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
484
+ first_line_text = (box.instance_variable_get :@printed_lines)[0]
485
+ unless first_line_text == fragments[0][:text]
486
+ original_fragments, fragments = fragments, []
487
+ original_fragments.reduce '' do |traced, fragment|
488
+ fragments << fragment
489
+ # NOTE: we could just do a length comparison here
490
+ if (traced += fragment[:text]).start_with? first_line_text
491
+ fragment[:text] = fragment[:text][0...-(traced.length - first_line_text.length)]
492
+ break
493
+ end
494
+ traced
495
+ end
496
+ end
497
+ fragments.each {|fragment| fragment[:text] = transform_text fragment[:text], first_line_text_transform }
498
+ first_line_options[:overflow] = :shrink_to_fit
499
+ first_line_options[:final_gap] = first_line_options[:force_justify] = true unless remaining_fragments.empty?
500
+ end
467
501
  if text_indent
468
502
  indent text_indent do
469
503
  fill_formatted_text_box fragments, first_line_options
@@ -472,8 +506,7 @@ module Asciidoctor
472
506
  fill_formatted_text_box fragments, first_line_options
473
507
  end
474
508
  unless remaining_fragments.empty?
475
- # NOTE: color must be applied per-fragment
476
- remaining_fragments.each {|fragment| fragment[:color] ||= color }
509
+ options[:color] = other_lines_font_color if first_line_font_color
477
510
  remaining_fragments = fill_formatted_text_box remaining_fragments, options
478
511
  draw_remaining_formatted_text_on_new_pages remaining_fragments, options
479
512
  end
@@ -495,6 +528,8 @@ module Asciidoctor
495
528
  lowercase_pcdata text
496
529
  when :capitalize, 'capitalize'
497
530
  capitalize_words_pcdata text
531
+ when :smallcaps, 'smallcaps'
532
+ smallcaps_pcdata text
498
533
  else
499
534
  text
500
535
  end
@@ -538,17 +573,20 @@ module Asciidoctor
538
573
  # Example:
539
574
  #
540
575
  # pad_box 20 do
541
- # text 'A paragraph inside a blox with even padding on all sides.'
576
+ # text 'A paragraph inside a blox with even padding from all edges.'
577
+ # end
578
+ #
579
+ # pad_box [10, 5] do
580
+ # text 'A paragraph inside a box with different padding from ends and sides.'
542
581
  # end
543
582
  #
544
- # pad_box [10, 10, 10, 20] do
545
- # text 'An indented paragraph inside a box with equal padding on all sides.'
583
+ # pad_box [5, 10, 15, 20] do
584
+ # text 'A paragraph inside a box with different padding from each edge.'
546
585
  # end
547
586
  #
548
587
  def pad_box padding, node = nil
549
588
  if padding
550
- # TODO: implement shorthand combinations like in CSS
551
- p_top, p_right, p_bottom, p_left = ::Array === padding ? padding : (::Array.new 4, padding)
589
+ p_top, p_right, p_bottom, p_left = expand_padding_value padding
552
590
  # logic is intentionally inlined
553
591
  begin
554
592
  if node && ((last_block = node).content_model != :compound || (last_block = node.blocks[-1])&.context == :paragraph)
@@ -560,8 +598,8 @@ module Asciidoctor
560
598
  bounds.add_left_padding p_left
561
599
  bounds.add_right_padding p_right
562
600
  yield
563
- cursor > p_bottom ? (move_down p_bottom) : reference_bounds.move_past_bottom unless at_page_top?
564
601
  ensure
602
+ cursor > p_bottom ? (move_down p_bottom) : reference_bounds.move_past_bottom unless at_page_top?
565
603
  @bottom_gutters.pop
566
604
  bounds.subtract_left_padding p_left
567
605
  bounds.subtract_right_padding p_right
@@ -576,26 +614,25 @@ module Asciidoctor
576
614
  end
577
615
 
578
616
  def expand_padding_value shorthand
579
- unless (padding = (@side_area_shorthand_cache ||= {})[shorthand])
580
- if ::Array === shorthand
581
- case shorthand.size
617
+ (@edge_shorthand_cache ||= ::Hash.new do |store, key|
618
+ if ::Array === key
619
+ case key.size
582
620
  when 1
583
- padding = [shorthand[0], shorthand[0], shorthand[0], shorthand[0]]
621
+ value = [(value0 = key[0] || 0), value0, value0, value0]
584
622
  when 2
585
- padding = [shorthand[0], shorthand[1], shorthand[0], shorthand[1]]
623
+ value = [(value0 = key[0] || 0), (value1 = key[1] || 0), value0, value1]
586
624
  when 3
587
- padding = [shorthand[0], shorthand[1], shorthand[2], shorthand[1]]
625
+ value = [key[0] || 0, (value1 = key[1] || 0), key[2] || 0, value1]
588
626
  when 4
589
- padding = shorthand
627
+ value = key.map {|it| it || 0 }
590
628
  else
591
- padding = shorthand.slice 0, 4
629
+ value = (key.slice 0, 4).map {|it| it || 0 }
592
630
  end
593
631
  else
594
- padding = ::Array.new 4, (shorthand || 0)
632
+ value = [(value0 = key || 0), value0, value0, value0]
595
633
  end
596
- @side_area_shorthand_cache[shorthand] = padding
597
- end
598
- padding.dup
634
+ store[key] = value
635
+ end)[shorthand]
599
636
  end
600
637
 
601
638
  alias expand_margin_value expand_padding_value
@@ -700,7 +737,8 @@ module Asciidoctor
700
737
  def fill_and_stroke_bounds f_color = fill_color, s_color = stroke_color, options = {}
701
738
  no_fill = !f_color || f_color == 'transparent'
702
739
  if ::Array === (s_width = options[:line_width] || 0)
703
- s_width_max = s_width.map(&:to_i).max
740
+ s_width = [s_width[0], s_width[1], s_width[0], s_width[1]] if s_width.size == 2
741
+ s_width_max = (s_width = s_width.map {|it| it || 0 }).max
704
742
  radius = 0
705
743
  else
706
744
  radius = options[:radius] || 0
@@ -718,13 +756,19 @@ module Asciidoctor
718
756
 
719
757
  # stroke
720
758
  if s_width_max
721
- if (s_width_end = s_width[0] || 0) > 0
722
- stroke_horizontal_rule s_color, line_width: s_width_end, line_style: options[:line_style]
723
- stroke_horizontal_rule s_color, line_width: s_width_end, line_style: options[:line_style], at: bounds.height
759
+ s_width_top, s_width_right, s_width_bottom, s_width_left = s_width
760
+ projection_top, projection_right, projection_bottom, projection_left = s_width.map {|it| it * 0.5 }
761
+ if s_width_top > 0
762
+ stroke_horizontal_rule s_color, line_width: s_width_top, line_style: options[:line_style], left_projection: projection_left, right_projection: projection_right
763
+ end
764
+ if s_width_right > 0
765
+ stroke_vertical_rule s_color, line_width: s_width_right, line_style: options[:line_style], at: bounds.width, top_projection: projection_top, bottom_projection: projection_bottom
724
766
  end
725
- if (s_width_side = s_width[1] || 0) > 0
726
- stroke_vertical_rule s_color, line_width: s_width_side, line_style: options[:line_style]
727
- stroke_vertical_rule s_color, line_width: s_width_side, line_style: options[:line_style], at: bounds.width
767
+ if s_width_bottom > 0
768
+ stroke_horizontal_rule s_color, line_width: s_width_bottom, line_style: options[:line_style], at: bounds.height, left_projection: projection_left, right_projection: projection_right
769
+ end
770
+ if s_width_left > 0
771
+ stroke_vertical_rule s_color, line_width: s_width_left, line_style: options[:line_style], top_projection: projection_top, bottom_projection: projection_bottom
728
772
  end
729
773
  else
730
774
  stroke_color s_color
@@ -759,8 +803,8 @@ module Asciidoctor
759
803
  rule_y = cursor - (options[:at] || 0)
760
804
  rule_style = options[:line_style]
761
805
  rule_width = options[:line_width] || 0.5
762
- rule_x_start = bounds.left
763
- rule_x_end = bounds.right
806
+ rule_x_start = bounds.left - (options[:left_projection] || 0)
807
+ rule_x_end = bounds.right - (options[:right_projection] || 0)
764
808
  save_graphics_state do
765
809
  stroke_color rule_color
766
810
  case rule_style
@@ -790,8 +834,8 @@ module Asciidoctor
790
834
  #
791
835
  def stroke_vertical_rule rule_color = stroke_color, options = {}
792
836
  rule_x = options[:at] || 0
793
- rule_y_from = bounds.top
794
- rule_y_to = bounds.bottom
837
+ rule_y_from = bounds.top + (options[:top_projection] || 0)
838
+ rule_y_to = bounds.bottom - (options[:bottom_projection] || 0)
795
839
  rule_style = options[:line_style]
796
840
  rule_width = options[:line_width] || 0.5
797
841
  save_graphics_state do
@@ -814,7 +858,7 @@ module Asciidoctor
814
858
 
815
859
  # Deletes the current page and move the cursor
816
860
  # to the previous page.
817
- def delete_page
861
+ def delete_current_page
818
862
  pg = page_number
819
863
  pdf_store = state.store
820
864
  content_id = page.content.identifier
@@ -848,7 +892,7 @@ module Asciidoctor
848
892
  prev_page_size = page.size
849
893
  state.compress = false if state.compress # can't use compression if using template
850
894
  prev_text_rendering_mode = (defined? @text_rendering_mode) ? @text_rendering_mode : nil
851
- delete_page if options[:replace]
895
+ delete_current_page if options[:replace]
852
896
  # NOTE: use functionality provided by prawn-templates
853
897
  start_new_page_discretely template: file, template_page: options[:page]
854
898
  # prawn-templates sets text_rendering_mode to :unknown, which breaks running content; revert
@@ -860,11 +904,11 @@ module Asciidoctor
860
904
  # way atm to prevent the size & layout of the imported page from affecting subsequent pages
861
905
  advance_page size: prev_page_size, layout: prev_page_layout if options.fetch :advance, true
862
906
  elsif options.fetch :advance_if_missing, true
863
- delete_page
907
+ delete_current_page
864
908
  # NOTE: see previous comment
865
909
  advance_page size: prev_page_size, layout: prev_page_layout
866
910
  else
867
- delete_page
911
+ delete_current_page
868
912
  end
869
913
  nil
870
914
  end
@@ -993,7 +1037,15 @@ module Asciidoctor
993
1037
  state.on_page_create_callback = delegate
994
1038
  end
995
1039
 
996
- # NOTE: only used in dry_run since that's when DetectEmptyFirstPage is active
1040
+ # This method delegates to the provided block, then tares (i.e., resets) the content stream of
1041
+ # the initial page.
1042
+ #
1043
+ # The purpose of this method is to ink content while making it appear as though the page is
1044
+ # empty. This technique allows the caller to detect whether any subsequent content was written
1045
+ # to the page following the content inked by the block. It's often used to keep the title of a
1046
+ # content block with the block's first child.
1047
+ #
1048
+ # NOTE: this method should only used inside dry_run since that's when DetectEmptyFirstPage is active
997
1049
  def tare_first_page_content_stream
998
1050
  return yield unless DetectEmptyFirstPage === (delegate = state.on_page_create_callback)
999
1051
  on_page_create_called = nil
@@ -4,13 +4,18 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
4
4
  include Asciidoctor::Logging
5
5
 
6
6
  def initialize formatted_text, options = {}
7
+ if (color = options[:color]) && !formatted_text.empty?
8
+ formatted_text = formatted_text.map {|fragment| fragment[:color] ? fragment : (fragment.merge color: color) }
9
+ end
7
10
  super
8
11
  formatted_text[0][:normalize_line_height] = true if options[:normalize_line_height] && !formatted_text.empty?
9
12
  options[:extensions]&.each {|extension| extend extension }
13
+ extend Prawn::Text::Formatted::IndentedParagraphWrap if (@indent_paragraphs = options[:indent_paragraphs])
10
14
  if (bottom_gutter = options[:bottom_gutter]) && bottom_gutter > 0
11
15
  @bottom_gutter = bottom_gutter
12
16
  extend Prawn::Text::Formatted::ProtectBottomGutter
13
17
  end
18
+ @force_justify = options[:force_justify]
14
19
  end
15
20
 
16
21
  def draw_fragment_overlay_styles fragment
@@ -71,6 +76,15 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
71
76
  end
72
77
  end
73
78
 
79
+ # Override method to force text justification when :force_justify option is set (typically for rendering a single line)
80
+ def word_spacing_for_this_line
81
+ if @align == :justify && (@force_justify || (@line_wrap.space_count > 0 && !@line_wrap.paragraph_finished?))
82
+ (available_width - @line_wrap.width) / @line_wrap.space_count
83
+ else
84
+ 0
85
+ end
86
+ end
87
+
74
88
  # Override method in super class to provide support for a tuple consisting of alignment and offset
75
89
  def process_vertical_alignment text
76
90
  return super if Symbol === (valign = @vertical_align)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn::Text::Formatted
4
+ module IndentedParagraphWrap
5
+ # Override Prawn::Text::Formatted::Box#wrap method to add support for :indent_paragraphs to (formatted_)text_box.
6
+ def wrap array
7
+ initialize_wrap array
8
+ stop = nil
9
+ until stop
10
+ if (first_line_indent = @indent_paragraphs) && @printed_lines.empty?
11
+ @width -= first_line_indent
12
+ stop = @document.indent(first_line_indent) { wrap_and_print_line }
13
+ @width += first_line_indent
14
+ else
15
+ stop = wrap_and_print_line
16
+ end
17
+ end
18
+ @text = @printed_lines.join ?\n
19
+ @everything_printed = @arranger.finished?
20
+ @arranger.unconsumed
21
+ end
22
+
23
+ def wrap_and_print_line
24
+ @line_wrap.wrap_line \
25
+ document: @document,
26
+ kerning: @kerning,
27
+ width: @width,
28
+ arranger: @arranger,
29
+ disable_wrap_by_char: @disable_wrap_by_char
30
+ if enough_height_for_this_line?
31
+ move_baseline_down
32
+ print_line
33
+ @single_line || @arranger.finished?
34
+ else
35
+ true
36
+ end
37
+ end
38
+ end
39
+ end
@@ -41,17 +41,10 @@ module Prawn
41
41
  extent = @pdf.dry_run keep_together: true, single_page: true do
42
42
  push_scratch parent_doc
43
43
  doc.catalog[:footnotes] = parent_doc.catalog[:footnotes]
44
- if padding_y > 0
45
- move_down padding_y
46
- #elsif at_page_top?
47
- else
48
- # TODO: encapsulate this logic to force top margin to be applied
49
- margin_box.instance_variable_set :@y, margin_box.absolute_top + 0.0001
50
- end
51
44
  # NOTE: we should be able to use cell.max_width, but returns 0 in some conditions (like when colspan > 1)
52
45
  indent cell.padding_left, bounds.width - cell.width + cell.padding_right do
53
- # TODO: truncate margin bottom of last block
54
- traverse cell.content
46
+ move_down padding_y if padding_y > 0
47
+ conceal_page_top { traverse cell.content }
55
48
  end
56
49
  pop_scratch parent_doc
57
50
  doc.catalog[:footnotes] = parent_doc.catalog[:footnotes]
@@ -99,7 +92,7 @@ module Prawn
99
92
  apply_font_properties { pdf.traverse content }
100
93
  if (extra_pages = pdf.page_number - start_page) > 0
101
94
  logger.error %(the table cell on page #{start_page} has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page) unless extra_pages == 1 && pdf.page.empty?
102
- extra_pages.times { pdf.delete_page }
95
+ extra_pages.times { pdf.delete_current_page }
103
96
  end
104
97
  nil
105
98
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # the following are organized under the Asciidoctor::Prawn namespace
4
+ require_relative 'prawn/document/column_box'
4
5
  require_relative 'prawn/font_metric_cache'
5
6
  require_relative 'prawn/font/afm'
6
7
  require_relative 'prawn/images'
7
8
  require_relative 'prawn/formatted_text/arranger'
8
9
  require_relative 'prawn/formatted_text/box'
9
10
  require_relative 'prawn/formatted_text/fragment'
11
+ require_relative 'prawn/formatted_text/indented_paragraph_wrap'
10
12
  require_relative 'prawn/formatted_text/protect_bottom_gutter'
11
13
  require_relative 'prawn/extensions'
@@ -24,14 +24,19 @@ module Asciidoctor
24
24
  @arranger.unconsumed.unshift highlight_line if highlight_line
25
25
  @arranger.unconsumed.unshift linenum_spacer.dup
26
26
  end
27
- @line_wrap.wrap_line document: @document, kerning: @kerning, width: available_width, arranger: @arranger, disable_wrap_by_char: @disable_wrap_by_char
27
+ @line_wrap.wrap_line \
28
+ document: @document,
29
+ kerning: @kerning,
30
+ width: @width,
31
+ arranger: @arranger,
32
+ disable_wrap_by_char: @disable_wrap_by_char
28
33
  if enough_height_for_this_line?
29
34
  move_baseline_down
30
35
  print_line
36
+ stop = @arranger.finished?
31
37
  else
32
38
  stop = true
33
39
  end
34
- stop ||= @arranger.finished?
35
40
  end
36
41
  @text = @printed_lines.join ?\n
37
42
  @everything_printed = @arranger.finished?
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn.image_handler.register! Prawn::Images::PNG if defined? GMagick::Image
@@ -40,9 +40,10 @@ module Asciidoctor
40
40
  attr_reader :quality
41
41
  attr_reader :compatibility_level
42
42
 
43
- def initialize quality = 'default', compatibility_level = '1.4'
43
+ def initialize quality = 'default', compatibility_level = '1.4', compliance = 'PDF'
44
44
  @quality = QUALITY_NAMES[quality]
45
45
  @compatibility_level = compatibility_level
46
+ @compliance = compliance
46
47
  if (gs_path = ::ENV['GS'])
47
48
  ::RGhost::Config::GS[:path] = gs_path
48
49
  end
@@ -57,10 +58,16 @@ module Asciidoctor
57
58
  else
58
59
  inputs = target
59
60
  end
60
- (::RGhost::Convert.new inputs).to :pdf,
61
- filename: filename_tmp.to_s,
62
- quality: @quality,
63
- d: { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
61
+ d = { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
62
+ case @compliance
63
+ when 'PDF/A', 'PDF/A-1', 'PDF/A-2', 'PDF/A-3'
64
+ d[:PDFA] = ((@compliance.split '-', 2)[1] || 1).to_i
65
+ d[:ShowAnnots] = false
66
+ when 'PDF/X', 'PDF/X-1', 'PDF/X-3'
67
+ d[:PDFX] = true
68
+ d[:ShowAnnots] = false
69
+ end
70
+ (::RGhost::Convert.new inputs).to :pdf, filename: filename_tmp.to_s, quality: @quality, d: d
64
71
  filename_o.binwrite filename_tmp.binread
65
72
  end
66
73
  nil
@@ -10,6 +10,12 @@ module Asciidoctor
10
10
  WordRx = /\p{Word}+/
11
11
  Hyphen = '-'
12
12
  SoftHyphen = ?\u00ad
13
+ LowerAlphaChars = 'a-z'
14
+ # NOTE: use more widely-supported ғ instead of ꜰ as replacement for F
15
+ # NOTE: use more widely-supported ǫ instead of ꞯ as replacement for Q
16
+ # NOTE: use more widely-supported s (lowercase latin "s") instead of ꜱ as replacement for S
17
+ # NOTE: in small caps, x (lowercase latin "x") remains unchanged
18
+ SmallCapsChars = 'ᴀʙᴄᴅᴇғɢʜɪᴊᴋʟᴍɴoᴘǫʀsᴛᴜᴠᴡxʏᴢ'
13
19
 
14
20
  def capitalize_words_pcdata string
15
21
  if XMLMarkupRx.match? string
@@ -50,6 +56,14 @@ module Asciidoctor
50
56
  string.upcase
51
57
  end
52
58
  end
59
+
60
+ def smallcaps_pcdata string
61
+ if XMLMarkupRx.match? string
62
+ string.gsub(PCDATAFilterRx) { $2 ? ($2.tr LowerAlphaChars, SmallCapsChars) : $1 }
63
+ else
64
+ string.tr LowerAlphaChars, SmallCapsChars
65
+ end
66
+ end
53
67
  end
54
68
  end
55
69
  end
@@ -15,9 +15,10 @@ 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
- AmbiguousAlignKeys = %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({}) do |prefix, accum|
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
19
  accum[%(#{prefix}_align)] = %(#{prefix}_text_align)
20
20
  end
21
+ PaddingBottomHackKeys = %w(example_padding quote_padding sidebar_padding verse_padding)
21
22
 
22
23
  VariableRx = /\$([a-z0-9_-]+)/
23
24
  LoneVariableRx = /^\$([a-z0-9_-]+)$/
@@ -170,9 +171,14 @@ module Asciidoctor
170
171
  val.each do |subkey, subval|
171
172
  process_entry %(#{key}_#{key == 'role' || !(subkey.include? '-') ? subkey : (subkey.tr '-', '_')}), subval, data
172
173
  end
173
- elsif (rekey = AmbiguousAlignKeys[key]) ||
174
+ elsif (rekey = DeprecatedKeys[key]) ||
174
175
  ((key.start_with? 'role_') && (key.end_with? '_align') && (rekey = key.sub RoleAlignKeyRx, '_text_align'))
175
176
  data[rekey] = evaluate val, data
177
+ elsif PaddingBottomHackKeys.include? key
178
+ val = evaluate val, data
179
+ # normalize padding hacks for themes designed before the converter had smart margins
180
+ val[2] = val[0] if ::Array === val && val[0].to_f >= 0 && val[2].to_f <= 0
181
+ data[key] = val
176
182
  # QUESTION: do we really need to evaluate_math in this case?
177
183
  elsif key.end_with? '_color'
178
184
  if key == 'table_border_color'
@@ -216,7 +222,7 @@ module Asciidoctor
216
222
  var = var.tr '-', '_' if var.include? '-'
217
223
  if (vars.respond_to? var) ||
218
224
  DeprecatedCategoryKeys.any? {|old, new| (var.start_with? old + '_') && (vars.respond_to? (replace = new + (var.slice old.length, var.length))) && (var = replace) } ||
219
- ((replace = AmbiguousAlignKeys[var]) && (vars.respond_to? replace) && (var = replace))
225
+ ((replace = DeprecatedKeys[var]) && (vars.respond_to? replace) && (var = replace))
220
226
  vars[var]
221
227
  else
222
228
  logger.warn %(unknown variable reference in PDF theme: #{ref})
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '2.0.0.alpha.2'
5
+ VERSION = '2.0.0.beta.2'
6
6
  end
7
7
  end