asciidoctor-pdf 1.5.2 → 1.6.1
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 +68 -1
- data/{LICENSE.adoc → LICENSE} +3 -4
- data/NOTICE.adoc +4 -6
- data/README.adoc +139 -46
- data/asciidoctor-pdf.gemspec +11 -14
- data/bin/asciidoctor-pdf-optimize +1 -1
- data/docs/theming-guide.adoc +26 -22
- data/lib/asciidoctor/pdf/converter.rb +88 -78
- data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +3 -2
- data/lib/asciidoctor/pdf/ext/core/file.rb +4 -5
- data/lib/asciidoctor/pdf/ext/core.rb +0 -1
- data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +1 -1
- data/lib/asciidoctor/pdf/ext/pdf-core.rb +15 -0
- data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +4 -13
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +28 -2
- data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +1 -1
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/arranger.rb +15 -0
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +28 -7
- data/lib/asciidoctor/pdf/ext/prawn-svg.rb +0 -1
- data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
- data/lib/asciidoctor/pdf/ext/pygments.rb +11 -9
- data/lib/asciidoctor/pdf/ext.rb +0 -1
- data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +5 -1
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +9 -2
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +6 -4
- data/lib/asciidoctor/pdf/index_catalog.rb +1 -1
- data/lib/asciidoctor/pdf/optimizer.rb +40 -12
- data/lib/asciidoctor/pdf/pdfmark.rb +1 -2
- data/lib/asciidoctor/pdf/text_transformer.rb +10 -71
- data/lib/asciidoctor/pdf/theme_loader.rb +1 -1
- data/lib/asciidoctor/pdf/version.rb +1 -1
- data/lib/asciidoctor/pdf.rb +3 -0
- metadata +21 -63
- data/lib/asciidoctor/pdf/ext/core/numeric.rb +0 -26
- data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +0 -14
- data/lib/asciidoctor/pdf/ext/prawn-templates.rb +0 -7
@@ -8,10 +8,11 @@ class Asciidoctor::Section
|
|
8
8
|
if @numbered && !@caption && slevel <= (@document.attr 'sectnumlevels', 3).to_i
|
9
9
|
@is_numbered = true
|
10
10
|
if @document.doctype == 'book'
|
11
|
-
|
11
|
+
case slevel
|
12
|
+
when 0
|
12
13
|
@cached_numbered_title = %(#{sectnum nil, ':'} #{title})
|
13
14
|
@cached_formal_numbered_title = %(#{@document.attr 'part-signifier', 'Part'} #{@cached_numbered_title}).lstrip
|
14
|
-
|
15
|
+
when 1
|
15
16
|
@cached_numbered_title = %(#{sectnum} #{title})
|
16
17
|
@cached_formal_numbered_title = %(#{@document.attr 'chapter-signifier', (@document.attr 'chapter-label', 'Chapter')} #{@cached_numbered_title}).lstrip
|
17
18
|
else
|
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class File
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
4
|
+
# NOTE: remove once minimum required Ruby version is at least 2.7
|
5
|
+
def self.absolute_path? path
|
6
|
+
(::Pathname.new path).absolute?
|
7
|
+
end unless respond_to? :absolute_path?
|
9
8
|
end
|
@@ -4,7 +4,6 @@ require_relative 'core/object'
|
|
4
4
|
require_relative 'core/array'
|
5
5
|
require_relative 'core/file'
|
6
6
|
require_relative 'core/hash'
|
7
|
-
require_relative 'core/numeric'
|
8
7
|
require_relative 'core/string'
|
9
8
|
require_relative 'core/regexp'
|
10
9
|
require_relative 'core/quantifiable_stdout'
|
@@ -1,4 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module PDF::Core
|
4
|
+
class << self
|
5
|
+
alias _initial_real real
|
6
|
+
def real num
|
7
|
+
num.to_f.round 4
|
8
|
+
end
|
9
|
+
|
10
|
+
alias _initial_real_params real_params
|
11
|
+
def real_params array
|
12
|
+
return array.map {|it| it.to_f.round 5 }.join ' ' if (caller_locations 1, 1)[0].base_label == 'transformation_matrix'
|
13
|
+
_initial_real_params array
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
3
18
|
require_relative 'pdf-core/pdf_object'
|
4
19
|
require_relative 'pdf-core/page'
|
@@ -5,21 +5,12 @@
|
|
5
5
|
# This file was copied from Prawn (manual/syntax_highlight.rb) and
|
6
6
|
# modified for use with Asciidoctor PDF.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# the
|
11
|
-
# (at your option) any later version.
|
12
|
-
#
|
13
|
-
# Prawn is distributed in the hope that it will be useful,
|
14
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
-
# GNU General Public License for more details.
|
17
|
-
#
|
18
|
-
# You should have received a copy of the GNU General Public License
|
19
|
-
# along with Prawn. If not, see <http://www.gnu.org/licenses/>.
|
8
|
+
# Since the file originates from the Prawn project, it shares the Prawn
|
9
|
+
# license. Thus, the file may be used under Matz's original licensing terms for
|
10
|
+
# Ruby, the GPLv2 license, or the GPLv3 license.
|
20
11
|
#
|
21
12
|
# Copyright (C) Felipe Doria
|
22
|
-
# Copyright (C) 2014 OpenDevise Inc. and the Asciidoctor Project
|
13
|
+
# Copyright (C) 2014-present OpenDevise Inc. and the Asciidoctor Project
|
23
14
|
#
|
24
15
|
######################################################################
|
25
16
|
|
@@ -76,6 +76,27 @@ module Asciidoctor
|
|
76
76
|
reference_bounds.height
|
77
77
|
end
|
78
78
|
|
79
|
+
# workaround for https://github.com/prawnpdf/prawn/issues/1121
|
80
|
+
def generate_margin_box
|
81
|
+
page_w, page_h = (page = state.page).dimensions.slice 2, 2
|
82
|
+
page_m = page.margins
|
83
|
+
prev_margin_box, @margin_box = @margin_box, (::Prawn::Document::BoundingBox.new self, nil, [page_m[:left], page_h - page_m[:top]], width: page_w - page_m[:left] - page_m[:right], height: page_h - page_m[:top] - page_m[:bottom])
|
84
|
+
|
85
|
+
# update bounding box if not flowing from the previous page
|
86
|
+
unless @bounding_box&.parent
|
87
|
+
prev_margin_box = @bounding_box
|
88
|
+
@bounding_box = @margin_box
|
89
|
+
end
|
90
|
+
|
91
|
+
# maintains indentation settings across page breaks
|
92
|
+
if prev_margin_box
|
93
|
+
@margin_box.add_left_padding prev_margin_box.total_left_padding
|
94
|
+
@margin_box.add_right_padding prev_margin_box.total_right_padding
|
95
|
+
end
|
96
|
+
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
79
100
|
# Set the margins for the current page.
|
80
101
|
#
|
81
102
|
def set_page_margin margin
|
@@ -190,7 +211,7 @@ module Asciidoctor
|
|
190
211
|
# }
|
191
212
|
#
|
192
213
|
def register_font data
|
193
|
-
font_families.update data.
|
214
|
+
font_families.update data.transform_keys(&:to_s)
|
194
215
|
end
|
195
216
|
|
196
217
|
# Enhances the built-in font method to allow the font
|
@@ -553,7 +574,7 @@ module Asciidoctor
|
|
553
574
|
|
554
575
|
# TODO: memoize the result
|
555
576
|
def inflate_padding padding
|
556
|
-
padding =
|
577
|
+
padding = (Array padding || 0).slice 0, 4
|
557
578
|
case padding.size
|
558
579
|
when 1
|
559
580
|
[padding[0], padding[0], padding[0], padding[0]]
|
@@ -882,6 +903,11 @@ module Asciidoctor
|
|
882
903
|
[(whole_pages * full_page_height + partial_page_height), whole_pages, partial_page_height]
|
883
904
|
end
|
884
905
|
|
906
|
+
def with_dry_run &block
|
907
|
+
total_height, = dry_run(&block)
|
908
|
+
instance_exec total_height, &block
|
909
|
+
end
|
910
|
+
|
885
911
|
# Attempt to keep the objects generated in the block on the same page
|
886
912
|
#
|
887
913
|
# TODO: short-circuit nested usage
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Prawn::Text::Formatted::Arranger.prepend (Module.new do
|
4
|
+
def initialize *_args
|
5
|
+
super
|
6
|
+
@dummy_text = ?\u0000
|
7
|
+
end
|
8
|
+
|
9
|
+
def next_string
|
10
|
+
if (string = super) == @dummy_text
|
11
|
+
def string.lstrip!; end
|
12
|
+
end
|
13
|
+
string
|
14
|
+
end
|
15
|
+
end)
|
@@ -17,9 +17,32 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
(
|
22
|
-
if
|
20
|
+
def analyze_glyphs_for_fallback_font_support fragment_hash
|
21
|
+
fragment_font = fragment_hash[:font] || (original_font = @document.font.family)
|
22
|
+
if (fragment_font_styles = fragment_hash[:styles])
|
23
|
+
if fragment_font_styles.include? :bold
|
24
|
+
fragment_font_opts = { style: (fragment_font_styles.include? :italic) ? :bold_italic : :bold }
|
25
|
+
elsif fragment_font_styles.include? :italic
|
26
|
+
fragment_font_opts = { style: :italic }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
fallback_fonts = @fallback_fonts.dup
|
30
|
+
font_glyph_pairs = []
|
31
|
+
@document.save_font do
|
32
|
+
fragment_hash[:text].each_char do |char|
|
33
|
+
font_glyph_pairs << [(find_font_for_this_glyph char, fragment_font, fragment_font_opts || {}, fallback_fonts.dup), char]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
# NOTE: don't add a :font to fragment if it wasn't there originally
|
37
|
+
font_glyph_pairs.each {|pair| pair[0] = nil if pair[0] == original_font } if original_font
|
38
|
+
form_fragments_from_like_font_glyph_pairs font_glyph_pairs, fragment_hash
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_font_for_this_glyph char, current_font, current_font_opts = {}, fallback_fonts_to_check = [], original_font = current_font
|
42
|
+
(doc = @document).font current_font, current_font_opts
|
43
|
+
if doc.font.glyph_present? char
|
44
|
+
current_font
|
45
|
+
elsif fallback_fonts_to_check.empty?
|
23
46
|
if logger.info? && !doc.scratch?
|
24
47
|
fonts_checked = @fallback_fonts.dup.unshift original_font
|
25
48
|
missing_chars = (doc.instance_variable_defined? :@missing_chars) ?
|
@@ -30,11 +53,9 @@ Prawn::Text::Formatted::Box.prepend (Module.new do
|
|
30
53
|
previous_fonts_checked << fonts_checked
|
31
54
|
end
|
32
55
|
end
|
33
|
-
|
34
|
-
elsif doc.font.glyph_present? char
|
35
|
-
current_font
|
56
|
+
original_font
|
36
57
|
else
|
37
|
-
find_font_for_this_glyph char, fallback_fonts_to_check.shift, fallback_fonts_to_check, original_font
|
58
|
+
find_font_for_this_glyph char, fallback_fonts_to_check.shift, current_font_opts, fallback_fonts_to_check, original_font
|
38
59
|
end
|
39
60
|
end
|
40
61
|
|
@@ -4,6 +4,7 @@
|
|
4
4
|
require_relative 'prawn/font_metric_cache'
|
5
5
|
require_relative 'prawn/font/afm'
|
6
6
|
require_relative 'prawn/images'
|
7
|
+
require_relative 'prawn/formatted_text/arranger'
|
7
8
|
require_relative 'prawn/formatted_text/box'
|
8
9
|
require_relative 'prawn/formatted_text/fragment'
|
9
10
|
require_relative 'prawn/extensions'
|
@@ -1,23 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'pygments.rb'
|
4
|
-
|
5
3
|
module Pygments
|
6
4
|
module Ext
|
7
5
|
module BlockStyles
|
8
6
|
BlockSelectorRx = /^\.highlight *\{([^}]+?)\}/
|
9
7
|
HighlightBackgroundColorRx = /^\.highlight +\.hll +{ *background(?:-color)?: *#([a-fA-F0-9]{6})/
|
10
|
-
|
8
|
+
ColorPropertiesRx = /(?:^|;) *(background(?:-color)?|color): *#?([a-fA-F0-9]{6}) *(?=$|;)/
|
11
9
|
|
12
10
|
@cache = ::Hash.new do |cache, key|
|
13
11
|
styles = {}
|
14
12
|
if BlockSelectorRx =~ (css = ::Pygments.css '.highlight', style: key)
|
15
|
-
|
16
|
-
pname
|
17
|
-
|
18
|
-
styles[:background_color] = pval
|
19
|
-
|
20
|
-
styles[:font_color] = pval
|
13
|
+
$1.scan ColorPropertiesRx do |pname, pval|
|
14
|
+
case pname
|
15
|
+
when 'background', 'background-color'
|
16
|
+
styles[:background_color] = pval
|
17
|
+
when 'color'
|
18
|
+
styles[:font_color] = pval
|
21
19
|
end
|
22
20
|
end
|
23
21
|
end
|
@@ -26,6 +24,10 @@ module Pygments
|
|
26
24
|
styles
|
27
25
|
end
|
28
26
|
|
27
|
+
def self.available? style
|
28
|
+
(@available ||= ::Pygments.styles.to_set).include? style
|
29
|
+
end
|
30
|
+
|
29
31
|
def self.for style
|
30
32
|
@cache[style]
|
31
33
|
end
|
data/lib/asciidoctor/pdf/ext.rb
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
module Asciidoctor::PDF::FormattedText
|
4
4
|
class FragmentPositionRenderer
|
5
|
-
attr_reader :top
|
5
|
+
attr_reader :top
|
6
|
+
attr_reader :right
|
7
|
+
attr_reader :bottom
|
8
|
+
attr_reader :left
|
9
|
+
attr_reader :page_number
|
6
10
|
|
7
11
|
def render_behind fragment
|
8
12
|
@top = fragment.top
|
@@ -39,8 +39,14 @@ module Asciidoctor::PDF::FormattedText
|
|
39
39
|
scratch = doc.scratch?
|
40
40
|
available_w = doc.bounds.width
|
41
41
|
available_h = doc.page.empty? ? doc.cursor : doc.bounds.height
|
42
|
+
last_fragment = {}
|
42
43
|
raw_image_fragments.each do |fragment|
|
43
|
-
|
44
|
+
if fragment[:object_id] == last_fragment[:object_id]
|
45
|
+
fragments.delete fragment
|
46
|
+
next
|
47
|
+
else
|
48
|
+
drop = scratch
|
49
|
+
end
|
44
50
|
begin
|
45
51
|
image_path = fragment[:image_path]
|
46
52
|
|
@@ -115,7 +121,7 @@ module Asciidoctor::PDF::FormattedText
|
|
115
121
|
fragment[:image_width] = fragment[:width] = image_w
|
116
122
|
fragment[:image_height] = image_h
|
117
123
|
rescue
|
118
|
-
logger.warn %(could not embed image: #{image_path}; #{$!.message}#{::Prawn::Errors::UnsupportedImageType === $! ? '; install prawn-gmagick gem to add support' : ''}) unless scratch
|
124
|
+
logger.warn %(could not embed image: #{image_path}; #{$!.message}#{::Prawn::Errors::UnsupportedImageType === $! && !(defined? ::GMagick::Image) ? '; install prawn-gmagick gem to add support' : ''}) unless scratch
|
119
125
|
drop = true # delegate to cleanup logic in ensure block
|
120
126
|
ensure
|
121
127
|
# NOTE skip rendering image in scratch document or if image can't be loaded
|
@@ -125,6 +131,7 @@ module Asciidoctor::PDF::FormattedText
|
|
125
131
|
# NOTE retain key to indicate we've visited fragment already
|
126
132
|
fragment[:image_obj] = nil
|
127
133
|
end
|
134
|
+
last_fragment = fragment
|
128
135
|
end
|
129
136
|
end
|
130
137
|
end
|
@@ -172,9 +172,10 @@ module Asciidoctor
|
|
172
172
|
fragment = {
|
173
173
|
image_path: attributes[:src],
|
174
174
|
image_format: attributes[:format],
|
175
|
-
#
|
176
|
-
text: (attributes[:alt].delete ZeroWidthSpace),
|
175
|
+
# NOTE: add enclosing square brackets here to avoid errors in parsing
|
176
|
+
text: %([#{attributes[:alt].delete ZeroWidthSpace}]),
|
177
177
|
callback: [InlineImageRenderer],
|
178
|
+
object_id: node.object_id, # used to deduplicate if fragment gets split up
|
178
179
|
}
|
179
180
|
if inherited && (link = inherited[:link])
|
180
181
|
fragment[:link] = link
|
@@ -379,9 +380,10 @@ module Asciidoctor
|
|
379
380
|
|
380
381
|
def update_fragment fragment, props
|
381
382
|
fragment.update props do |k, oval, nval|
|
382
|
-
|
383
|
+
case k
|
384
|
+
when :styles
|
383
385
|
nval ? (oval.merge nval) : oval.clear
|
384
|
-
|
386
|
+
when :callback
|
385
387
|
oval | nval
|
386
388
|
else
|
387
389
|
nval
|
@@ -32,7 +32,7 @@ module Asciidoctor
|
|
32
32
|
|
33
33
|
def store_primary_term name, dest = nil
|
34
34
|
store_dest dest if dest
|
35
|
-
(init_category
|
35
|
+
(init_category name.chr.upcase).store_term name, dest
|
36
36
|
end
|
37
37
|
|
38
38
|
def store_secondary_term primary_name, secondary_name, dest = nil
|
@@ -2,11 +2,35 @@
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'rghost'
|
5
|
+
require 'rghost/gs_alone'
|
5
6
|
require 'tmpdir'
|
6
7
|
|
8
|
+
RGhost::GSAlone.prepend (Module.new do
|
9
|
+
WindowsRx = /win|ming/
|
10
|
+
|
11
|
+
def initialize params, debug
|
12
|
+
(@params = params.dup).push(*(@params.pop.split File::PATH_SEPARATOR))
|
13
|
+
@debug = debug
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
RGhost::Config.config_platform unless File.exist? RGhost::Config::GS[:path].to_s
|
18
|
+
(cmd = @params.slice 1, @params.length).unshift RGhost::Config::GS[:path].to_s
|
19
|
+
#puts cmd if @debug
|
20
|
+
system(*cmd)
|
21
|
+
end
|
22
|
+
end)
|
23
|
+
|
24
|
+
RGhost::Engine.prepend (Module.new do
|
25
|
+
def shellescape str
|
26
|
+
str
|
27
|
+
end
|
28
|
+
end)
|
29
|
+
|
7
30
|
module Asciidoctor
|
8
31
|
module PDF
|
9
32
|
class Optimizer
|
33
|
+
# see https://www.ghostscript.com/doc/current/VectorDevices.htm#PSPDF_IN for details
|
10
34
|
(QUALITY_NAMES = {
|
11
35
|
'default' => :default,
|
12
36
|
'screen' => :screen,
|
@@ -15,27 +39,31 @@ module Asciidoctor
|
|
15
39
|
'prepress' => :prepress,
|
16
40
|
}).default = :default
|
17
41
|
|
42
|
+
attr_reader :quality
|
43
|
+
attr_reader :compatibility_level
|
44
|
+
|
18
45
|
def initialize quality = 'default', compatibility_level = '1.4'
|
19
46
|
@quality = QUALITY_NAMES[quality]
|
20
47
|
@compatibility_level = compatibility_level
|
48
|
+
if (gs_path = ::ENV['GS'])
|
49
|
+
::RGhost::Config::GS[:path] = gs_path
|
50
|
+
end
|
21
51
|
end
|
22
52
|
|
23
|
-
def
|
53
|
+
def optimize_file target
|
24
54
|
::Dir::Tmpname.create ['asciidoctor-pdf-', '.pdf'] do |tmpfile|
|
25
|
-
|
26
|
-
|
27
|
-
pdfmark =
|
28
|
-
|
55
|
+
filename_o = ::Pathname.new target
|
56
|
+
filename_tmp = ::Pathname.new tmpfile
|
57
|
+
if (pdfmark = filename_o.sub_ext '.pdfmark').file?
|
58
|
+
inputs = [target, pdfmark.to_s].join ::File::PATH_SEPARATOR
|
59
|
+
else
|
60
|
+
inputs = target
|
61
|
+
end
|
29
62
|
(::RGhost::Convert.new inputs).to :pdf,
|
30
|
-
filename:
|
63
|
+
filename: filename_tmp.to_s,
|
31
64
|
quality: @quality,
|
32
65
|
d: { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
|
33
|
-
|
34
|
-
filename_o.rename target
|
35
|
-
rescue ::Errno::EXDEV
|
36
|
-
filename.binwrite filename_o.binread
|
37
|
-
filename_o.unlink
|
38
|
-
end
|
66
|
+
filename_o.binwrite filename_tmp.binread
|
39
67
|
end
|
40
68
|
nil
|
41
69
|
end
|