asciidoctor-pdf 1.5.2 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|