asciidoctor-pdf 2.0.0.alpha.2 → 2.0.0.beta.2
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 +4 -4
- data/CHANGELOG.adoc +185 -83
- data/README.adoc +31 -126
- data/data/fonts/ABOUT-mplus1p-subset +1 -0
- data/data/fonts/ABOUT-notosans-subset +1 -0
- data/data/fonts/ABOUT-notoserif-subset +1 -0
- data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
- data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
- data/data/fonts/notosans-bold-subset.ttf +0 -0
- data/data/fonts/notosans-bold_italic-subset.ttf +0 -0
- data/data/fonts/notosans-italic-subset.ttf +0 -0
- data/data/fonts/notosans-regular-subset.ttf +0 -0
- data/data/fonts/notoserif-bold-subset.ttf +0 -0
- data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
- data/data/fonts/notoserif-italic-subset.ttf +0 -0
- data/data/fonts/notoserif-regular-subset.ttf +0 -0
- data/data/themes/base-theme.yml +10 -20
- data/data/themes/default-for-print-theme.yml +4 -4
- data/data/themes/default-for-print-with-fallback-font-theme.yml +2 -3
- data/data/themes/default-for-print-with-font-fallbacks-theme.yml +3 -0
- data/data/themes/{sans-with-fallback-font-theme.yml → default-sans-theme.yml} +1 -1
- data/data/themes/default-sans-with-font-fallbacks-theme.yml +3 -0
- data/data/themes/default-theme.yml +5 -4
- data/data/themes/default-with-fallback-font-theme.yml +2 -9
- data/data/themes/default-with-font-fallbacks-theme.yml +9 -0
- data/docs/theming-guide.adoc +5 -6050
- data/lib/asciidoctor/pdf/converter.rb +505 -294
- data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +8 -0
- data/lib/asciidoctor/pdf/ext/prawn/document/column_box.rb +16 -0
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +110 -58
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +14 -0
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/indented_paragraph_wrap.rb +39 -0
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +3 -10
- data/lib/asciidoctor/pdf/ext/prawn.rb +2 -0
- data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +7 -2
- data/lib/asciidoctor/pdf/nopngmagick.rb +3 -0
- data/lib/asciidoctor/pdf/optimizer.rb +12 -5
- data/lib/asciidoctor/pdf/text_transformer.rb +14 -0
- data/lib/asciidoctor/pdf/theme_loader.rb +9 -3
- data/lib/asciidoctor/pdf/version.rb +1 -1
- 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.
|
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.
|
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
|
-
|
400
|
-
if (format_option = options.delete :inline_format)
|
422
|
+
if (format_option = options[:inline_format])
|
401
423
|
format_option = [] unless ::Array === format_option
|
402
|
-
|
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
|
-
|
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) ||
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
466
|
-
|
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
|
-
|
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
|
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 [
|
545
|
-
# text '
|
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
|
-
|
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
|
-
|
580
|
-
if ::Array ===
|
581
|
-
case
|
617
|
+
(@edge_shorthand_cache ||= ::Hash.new do |store, key|
|
618
|
+
if ::Array === key
|
619
|
+
case key.size
|
582
620
|
when 1
|
583
|
-
|
621
|
+
value = [(value0 = key[0] || 0), value0, value0, value0]
|
584
622
|
when 2
|
585
|
-
|
623
|
+
value = [(value0 = key[0] || 0), (value1 = key[1] || 0), value0, value1]
|
586
624
|
when 3
|
587
|
-
|
625
|
+
value = [key[0] || 0, (value1 = key[1] || 0), key[2] || 0, value1]
|
588
626
|
when 4
|
589
|
-
|
627
|
+
value = key.map {|it| it || 0 }
|
590
628
|
else
|
591
|
-
|
629
|
+
value = (key.slice 0, 4).map {|it| it || 0 }
|
592
630
|
end
|
593
631
|
else
|
594
|
-
|
632
|
+
value = [(value0 = key || 0), value0, value0, value0]
|
595
633
|
end
|
596
|
-
|
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
|
-
|
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
|
-
|
722
|
-
|
723
|
-
|
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
|
726
|
-
|
727
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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.
|
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
|
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?
|
@@ -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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
d:
|
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
|
-
|
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 =
|
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 =
|
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})
|