asciidoctor-pdf 1.5.0.alpha.16 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.yardopts +12 -0
- data/CHANGELOG.adoc +415 -1
- data/LICENSE.adoc +1 -1
- data/NOTICE.adoc +14 -11
- data/README.adoc +647 -222
- data/asciidoctor-pdf.gemspec +47 -44
- data/bin/asciidoctor-pdf +5 -9
- data/bin/asciidoctor-pdf-optimize +20 -0
- data/data/fonts/ABOUT-mplus1mn-subset +26 -0
- data/data/fonts/ABOUT-mplus1p-subset +26 -0
- data/data/fonts/ABOUT-notoemoji-subset +3 -0
- data/data/fonts/ABOUT-notoserif-subset +26 -0
- data/data/fonts/{LICENSE-mplus-testflight-58 → LICENSE-mplus} +2 -2
- data/data/fonts/{LICENSE-noto-2015-06-05 → LICENSE-notoserif} +0 -0
- data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
- data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
- data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
- data/data/fonts/notoemoji-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 +26 -4
- data/data/themes/default-theme.yml +76 -60
- data/data/themes/default-with-fallback-font-theme.yml +9 -0
- data/docs/theming-guide.adoc +2731 -922
- data/lib/asciidoctor/pdf/converter.rb +4489 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +7 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +7 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +5 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +35 -0
- data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list.rb +4 -2
- data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list_item.rb +3 -1
- data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +33 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +45 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor.rb +11 -0
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/array.rb +6 -6
- data/lib/asciidoctor/pdf/ext/core/file.rb +9 -0
- data/lib/asciidoctor/pdf/ext/core/hash.rb +7 -0
- data/lib/asciidoctor/pdf/ext/core/numeric.rb +26 -0
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/object.rb +3 -1
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/quantifiable_stdout.rb +9 -1
- data/lib/asciidoctor/pdf/ext/core/regexp.rb +5 -0
- data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/string.rb +9 -13
- data/lib/asciidoctor/pdf/ext/core.rb +10 -0
- data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +54 -0
- data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +8 -0
- data/lib/asciidoctor/pdf/ext/pdf-core.rb +4 -0
- data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +117 -0
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +922 -0
- data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/font/afm.rb +14 -10
- data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +9 -0
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +66 -0
- data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/formatted_text/fragment.rb +21 -18
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +54 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +14 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg.rb +6 -0
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -0
- data/lib/{asciidoctor-pdf/prawn-table_ext → asciidoctor/pdf/ext/prawn-table}/cell/text.rb +6 -3
- data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +60 -0
- data/lib/asciidoctor/pdf/ext/prawn-table.rb +6 -0
- data/lib/{asciidoctor-pdf/prawn-templates_ext.rb → asciidoctor/pdf/ext/prawn-templates.rb} +2 -0
- data/lib/asciidoctor/pdf/ext/prawn.rb +9 -0
- data/lib/asciidoctor/pdf/ext/pygments.rb +34 -0
- data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +208 -0
- data/lib/{asciidoctor-pdf/rouge_ext/themes/pastie.rb → asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb} +7 -5
- data/lib/asciidoctor/pdf/ext/rouge.rb +5 -0
- data/lib/asciidoctor/pdf/ext.rb +9 -0
- data/lib/asciidoctor/pdf/formatted_text/formatter.rb +43 -0
- data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +14 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +21 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +134 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +51 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +22 -0
- data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.rb +175 -53
- data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.treetop +20 -14
- data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +43 -0
- data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +55 -0
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +394 -0
- data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text.rb +6 -0
- data/lib/asciidoctor/pdf/index_catalog.rb +133 -0
- data/lib/asciidoctor/pdf/measurements.rb +62 -0
- data/lib/asciidoctor/pdf/optimizer.rb +44 -0
- data/lib/asciidoctor/pdf/pdfmark.rb +41 -0
- data/lib/asciidoctor/pdf/roman_numeral.rb +128 -0
- data/lib/asciidoctor/pdf/sanitizer.rb +45 -0
- data/lib/asciidoctor/pdf/text_transformer.rb +116 -0
- data/lib/asciidoctor/pdf/theme_loader.rb +305 -0
- data/lib/asciidoctor/pdf/version.rb +8 -0
- data/lib/asciidoctor/pdf.rb +15 -0
- data/lib/asciidoctor-pdf/converter.rb +2 -3343
- data/lib/asciidoctor-pdf/version.rb +3 -5
- data/lib/asciidoctor-pdf.rb +3 -3
- metadata +242 -128
- data/Gemfile +0 -22
- data/Rakefile +0 -81
- data/lib/asciidoctor-pdf/asciidoctor_ext/image.rb +0 -24
- data/lib/asciidoctor-pdf/asciidoctor_ext/section.rb +0 -34
- data/lib/asciidoctor-pdf/asciidoctor_ext.rb +0 -5
- data/lib/asciidoctor-pdf/core_ext/numeric.rb +0 -15
- data/lib/asciidoctor-pdf/core_ext/ostruct.rb +0 -17
- data/lib/asciidoctor-pdf/core_ext.rb +0 -4
- data/lib/asciidoctor-pdf/formatted_text/formatter.rb +0 -27
- data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +0 -21
- data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +0 -172
- data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +0 -46
- data/lib/asciidoctor-pdf/formatted_text/transform.rb +0 -261
- data/lib/asciidoctor-pdf/implicit_header_processor.rb +0 -63
- data/lib/asciidoctor-pdf/index_catalog.rb +0 -119
- data/lib/asciidoctor-pdf/measurements.rb +0 -58
- data/lib/asciidoctor-pdf/pdf-core_ext/page.rb +0 -25
- data/lib/asciidoctor-pdf/pdf-core_ext/pdf_object.rb +0 -6
- data/lib/asciidoctor-pdf/pdf-core_ext.rb +0 -2
- data/lib/asciidoctor-pdf/pdfmark.rb +0 -33
- data/lib/asciidoctor-pdf/prawn-svg_ext/interface.rb +0 -10
- data/lib/asciidoctor-pdf/prawn-svg_ext.rb +0 -4
- data/lib/asciidoctor-pdf/prawn-table_ext/cell/asciidoc.rb +0 -69
- data/lib/asciidoctor-pdf/prawn-table_ext.rb +0 -3
- data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +0 -115
- data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +0 -863
- data/lib/asciidoctor-pdf/prawn_ext/images.rb +0 -40
- data/lib/asciidoctor-pdf/prawn_ext.rb +0 -5
- data/lib/asciidoctor-pdf/roman_numeral.rb +0 -114
- data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +0 -15
- data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +0 -164
- data/lib/asciidoctor-pdf/rouge_ext.rb +0 -4
- data/lib/asciidoctor-pdf/sanitizer.rb +0 -88
- data/lib/asciidoctor-pdf/temporary_path.rb +0 -13
- data/lib/asciidoctor-pdf/theme_loader.rb +0 -247
- data/lib/asciidoctor-pdf/ttfunk_ext.rb +0 -8
@@ -1,34 +0,0 @@
|
|
1
|
-
class Asciidoctor::Section
|
2
|
-
def numbered_title opts = {}
|
3
|
-
unless (@cached_numbered_title ||= nil)
|
4
|
-
if (slevel = (@level == 0 && @special ? 1 : @level)) == 0
|
5
|
-
@is_numbered = false
|
6
|
-
@cached_numbered_title = @cached_formal_numbered_title = title
|
7
|
-
elsif @numbered && !@caption && slevel <= (@document.attr 'sectnumlevels', 3).to_i
|
8
|
-
@is_numbered = true
|
9
|
-
@cached_numbered_title = %(#{sectnum} #{title})
|
10
|
-
@cached_formal_numbered_title = if slevel == 1 && @document.doctype == 'book'
|
11
|
-
%(#{@document.attr 'chapter-label', 'Chapter'} #{@cached_numbered_title}).lstrip
|
12
|
-
else
|
13
|
-
@cached_numbered_title
|
14
|
-
end
|
15
|
-
else
|
16
|
-
@is_numbered = false
|
17
|
-
@cached_numbered_title = @cached_formal_numbered_title = captioned_title
|
18
|
-
end
|
19
|
-
end
|
20
|
-
opts[:formal] ? @cached_formal_numbered_title : @cached_numbered_title
|
21
|
-
end unless method_defined? :numbered_title
|
22
|
-
|
23
|
-
def part?
|
24
|
-
@document.doctype == 'book' && @level == 0 && !@special
|
25
|
-
end unless method_defined? :part?
|
26
|
-
|
27
|
-
def chapter?
|
28
|
-
@document.doctype == 'book' && (@level == 1 || (@special && @level == 0))
|
29
|
-
end unless method_defined? :chapter?
|
30
|
-
|
31
|
-
def part_or_chapter?
|
32
|
-
@document.doctype == 'book' && @level < 2
|
33
|
-
end unless method_defined? :part_or_chapter?
|
34
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
class Numeric
|
2
|
-
if (instance_method :truncate).arity == 0
|
3
|
-
def truncate_to_precision precision
|
4
|
-
if (precision = precision.to_i) > 0
|
5
|
-
factor = 10 ** precision
|
6
|
-
(self * factor).truncate.fdiv factor
|
7
|
-
else
|
8
|
-
truncate
|
9
|
-
end
|
10
|
-
end
|
11
|
-
else
|
12
|
-
# use native method in Ruby >= 2.4
|
13
|
-
alias :truncate_to_precision :truncate
|
14
|
-
end unless method_defined? :truncate_to_precision
|
15
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
class OpenStruct
|
2
|
-
def [] key
|
3
|
-
send key
|
4
|
-
end unless method_defined? :[]
|
5
|
-
|
6
|
-
def []= key, val
|
7
|
-
send %(#{key}=), val
|
8
|
-
end unless method_defined? :[]=
|
9
|
-
end if RUBY_ENGINE == 'rbx' || RUBY_VERSION < '2.0.0'
|
10
|
-
|
11
|
-
class OpenStruct
|
12
|
-
def delete key
|
13
|
-
begin
|
14
|
-
delete_field key
|
15
|
-
rescue ::NameError; end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Asciidoctor
|
2
|
-
module Pdf
|
3
|
-
module FormattedText
|
4
|
-
class Formatter
|
5
|
-
FormattingSnifferPattern = /[<&]/
|
6
|
-
WHITESPACE = " \t\n"
|
7
|
-
|
8
|
-
def initialize options = {}
|
9
|
-
@parser = MarkupParser.new
|
10
|
-
@transform = Transform.new merge_adjacent_text_nodes: true, theme: options[:theme]
|
11
|
-
end
|
12
|
-
|
13
|
-
def format string, *args
|
14
|
-
options = args[0] || {}
|
15
|
-
string = string.tr_s(WHITESPACE, ' ') if options[:normalize]
|
16
|
-
return [text: string] unless string.match(FormattingSnifferPattern)
|
17
|
-
if (parsed = @parser.parse(string))
|
18
|
-
@transform.apply(parsed.content)
|
19
|
-
else
|
20
|
-
warn %(Failed to parse formatted text: #{string})
|
21
|
-
[text: string]
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Asciidoctor::Pdf::FormattedText
|
2
|
-
module InlineDestinationMarker
|
3
|
-
module_function
|
4
|
-
|
5
|
-
# render_behind is called before the text is printed
|
6
|
-
def render_behind fragment
|
7
|
-
unless (pdf = fragment.document).scratch?
|
8
|
-
if (name = fragment.format_state[:name])
|
9
|
-
if fragment.format_state[:type] == :indexterm
|
10
|
-
(pdf.instance_variable_get :@index).link_dest_to_page name, pdf.page_number
|
11
|
-
end
|
12
|
-
# get precise position of the reference (x, y)
|
13
|
-
dest_rect = fragment.absolute_bounding_box
|
14
|
-
pdf.add_dest name, (pdf.dest_xyz dest_rect[0], dest_rect[-1])
|
15
|
-
# prevent any text from being written
|
16
|
-
fragment.conceal
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,172 +0,0 @@
|
|
1
|
-
module Asciidoctor::Pdf::FormattedText
|
2
|
-
module InlineImageArranger
|
3
|
-
include ::Asciidoctor::Pdf::Measurements
|
4
|
-
|
5
|
-
ImagePlaceholderChar = '.'
|
6
|
-
begin
|
7
|
-
require 'thread_safe' unless defined? ::ThreadSafe
|
8
|
-
PlaceholderWidthCache = ::ThreadSafe::Cache.new
|
9
|
-
rescue
|
10
|
-
PlaceholderWidthCache = {}
|
11
|
-
end
|
12
|
-
TemporaryPath = ::Asciidoctor::Pdf::TemporaryPath
|
13
|
-
|
14
|
-
if respond_to? :prepend
|
15
|
-
def wrap fragments
|
16
|
-
arrange_images fragments
|
17
|
-
super
|
18
|
-
end
|
19
|
-
else
|
20
|
-
class << self
|
21
|
-
def extended base
|
22
|
-
base.class.__send__ :alias_method, :_initial_wrap, :wrap
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def wrap fragments
|
27
|
-
arrange_images fragments
|
28
|
-
_initial_wrap fragments
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Iterates over the fragments that represent inline images and prepares the
|
33
|
-
# image data to be embedded into the document.
|
34
|
-
#
|
35
|
-
# This method populates the image_width, image_height, image_obj and
|
36
|
-
# image_info (PNG only) keys on the fragment. The text is replaced with
|
37
|
-
# placeholder text that will be used to reserve enough room in the line to
|
38
|
-
# fit the image.
|
39
|
-
#
|
40
|
-
# The image height is scaled down to 75% of the specified width (px to pt
|
41
|
-
# conversion). If the image height is more than 1.5x the height of the
|
42
|
-
# surrounding line of text, the font size and line metrics of the fragment
|
43
|
-
# are modified to fit the image in the line.
|
44
|
-
#
|
45
|
-
# If this is the scratch document, the image renderer callback is removed so
|
46
|
-
# that the image is not embedded.
|
47
|
-
#
|
48
|
-
# When this method is called, the cursor is positioned at start of where this
|
49
|
-
# group of fragments will be written (offset by the leading padding).
|
50
|
-
#
|
51
|
-
# This method is called each time the set of fragments overflow to another
|
52
|
-
# page, so it's necessary to short-circuit if that case is detected.
|
53
|
-
def arrange_images fragments
|
54
|
-
doc = @document
|
55
|
-
return if (raw_image_fragments = fragments.select {|f| (f.key? :image_path) && !(f.key? :image_obj) }).empty?
|
56
|
-
scratch = doc.scratch?
|
57
|
-
available_w = doc.bounds.width
|
58
|
-
#available_h = doc.bounds.height
|
59
|
-
# NOTE try to fit image within bounds if cursor is within 1% of top of page
|
60
|
-
# QUESTION are we considering the right boundaries here?
|
61
|
-
available_h ||= (doc.cursor / (available_h = doc.bounds.height) >= 0.99 ? doc.cursor : available_h)
|
62
|
-
raw_image_fragments.each do |fragment|
|
63
|
-
drop = scratch
|
64
|
-
begin
|
65
|
-
image_path = fragment[:image_path]
|
66
|
-
|
67
|
-
# NOTE only attempt to convert an unresolved (i.e., String) value
|
68
|
-
if ::String === (image_w = fragment[:image_width])
|
69
|
-
image_w = [available_w, (image_w.end_with? '%') ? (image_w.to_f / 100 * available_w) : image_w.to_f].min
|
70
|
-
end
|
71
|
-
|
72
|
-
# TODO make helper method to calculate width and height of image
|
73
|
-
if fragment[:image_format] == 'svg'
|
74
|
-
svg_obj = ::Prawn::Svg::Interface.new ::IO.read(image_path), doc,
|
75
|
-
at: doc.bounds.top_left,
|
76
|
-
width: image_w,
|
77
|
-
fallback_font_name: doc.default_svg_font
|
78
|
-
svg_size = image_w ? svg_obj.document.sizing :
|
79
|
-
# NOTE convert intrinsic dimensions to points; constrain to content width
|
80
|
-
(svg_obj.resize width: [(to_pt svg_obj.document.sizing.output_width, :px), available_w].min)
|
81
|
-
# NOTE the best we can do is make the image fit within full height of bounds
|
82
|
-
if (image_h = svg_size.output_height) > available_h
|
83
|
-
image_w = (svg_size = svg_obj.resize height: (image_h = available_h)).output_width
|
84
|
-
else
|
85
|
-
image_w = svg_size.output_width
|
86
|
-
end
|
87
|
-
fragment[:image_obj] = svg_obj
|
88
|
-
else
|
89
|
-
# TODO cache image info based on path (Prawn caches based on SHA1 of content)
|
90
|
-
# NOTE image_obj is constrained to image_width by renderer
|
91
|
-
image_obj, image_info = ::File.open(image_path, 'rb') {|fd| doc.build_image_object fd }
|
92
|
-
if image_w
|
93
|
-
if image_w == image_info.width
|
94
|
-
image_h = image_info.height.to_f
|
95
|
-
else
|
96
|
-
image_h = image_w * (image_info.height.fdiv image_info.width)
|
97
|
-
end
|
98
|
-
else
|
99
|
-
# NOTE convert intrinsic dimensions to points; constrain to content width
|
100
|
-
if (image_w = to_pt image_info.width, :px) > available_w
|
101
|
-
image_h = (image_w = available_w) * (image_info.height.fdiv image_info.width)
|
102
|
-
else
|
103
|
-
image_h = to_pt image_info.height, :px
|
104
|
-
end
|
105
|
-
end
|
106
|
-
# NOTE the best we can do is make the image fit within full height of bounds
|
107
|
-
image_w = (image_h = available_h) * (image_info.width.fdiv image_info.height) if image_h > available_h
|
108
|
-
fragment[:image_obj] = image_obj
|
109
|
-
fragment[:image_info] = image_info
|
110
|
-
end
|
111
|
-
|
112
|
-
spacer_w = nil
|
113
|
-
doc.fragment_font fragment do
|
114
|
-
# NOTE if image height exceeds line height by more than 1.5x, increase the line height
|
115
|
-
# FIXME we could really use a nicer API from Prawn here; this is an ugly hack
|
116
|
-
if (f_height = image_h) > (line_font = doc.font).height * 1.5
|
117
|
-
# align with descender (equivalent to vertical-align: bottom in CSS)
|
118
|
-
fragment[:ascender] = f_height - (fragment[:descender] = line_font.descender)
|
119
|
-
doc.font_size(fragment[:size] = f_height * (doc.font_size / line_font.height))
|
120
|
-
# align with baseline (roughly equivalent to vertical-align: baseline in CSS)
|
121
|
-
#fragment[:ascender] = f_height
|
122
|
-
#fragment[:descender] = 0
|
123
|
-
#doc.font_size(fragment[:size] = (f_height + line_font.descender) * (doc.font_size / line_font.height))
|
124
|
-
fragment[:line_height_increased] = true
|
125
|
-
end
|
126
|
-
|
127
|
-
unless (spacer_w = PlaceholderWidthCache[f_info = doc.font_info])
|
128
|
-
spacer_w = PlaceholderWidthCache[f_info] = doc.width_of ImagePlaceholderChar
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# NOTE make room for image by repeating image placeholder character, using character spacing to fine-tune
|
133
|
-
# NOTE image_width is constrained to available_w, so we don't have to check for overflow
|
134
|
-
spacer_cnt, remainder = image_w.divmod spacer_w
|
135
|
-
if spacer_cnt > 0
|
136
|
-
fragment[:text] = ImagePlaceholderChar * spacer_cnt
|
137
|
-
fragment[:character_spacing] = remainder.fdiv spacer_cnt if remainder > 0
|
138
|
-
else
|
139
|
-
fragment[:text] = ImagePlaceholderChar
|
140
|
-
fragment[:character_spacing] = -(spacer_w - remainder)
|
141
|
-
end
|
142
|
-
|
143
|
-
# FIXME we could use a nicer API from Prawn here that lets us reserve a fragment width without text
|
144
|
-
#fragment[:image_width] = fragment[:width] = image_w
|
145
|
-
fragment[:image_width] = image_w
|
146
|
-
fragment[:image_height] = image_h
|
147
|
-
rescue => e
|
148
|
-
warn %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.message})
|
149
|
-
drop = true # delegate to cleanup logic in ensure block
|
150
|
-
ensure
|
151
|
-
# NOTE skip rendering image in scratch document or if image can't be loaded
|
152
|
-
if drop
|
153
|
-
fragment.delete :callback
|
154
|
-
fragment.delete :image_info
|
155
|
-
# NOTE retain key to indicate we've visited fragment already
|
156
|
-
fragment[:image_obj] = nil
|
157
|
-
# NOTE in main document, temporary image path is unlinked by renderer
|
158
|
-
::File.unlink image_path if TemporaryPath === image_path && image_path.exist?
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
if respond_to? :prepend
|
166
|
-
class ::Prawn::Text::Formatted::Box
|
167
|
-
prepend InlineImageArranger
|
168
|
-
end
|
169
|
-
else
|
170
|
-
::Prawn::Text::Formatted::Box.extensions << InlineImageArranger
|
171
|
-
end
|
172
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Asciidoctor::Pdf::FormattedText
|
2
|
-
module InlineImageRenderer
|
3
|
-
TemporaryPath = ::Asciidoctor::Pdf::TemporaryPath
|
4
|
-
module_function
|
5
|
-
|
6
|
-
# Embeds the image object in this fragment into the document in place of the
|
7
|
-
# text that was previously used to reserve space for the image in the line.
|
8
|
-
#
|
9
|
-
# If the image height is less than 1.5x the height of the surrounding text,
|
10
|
-
# it is centered vertically in the line. If the image height is greater, then
|
11
|
-
# the image is aligned to the bottom of the text.
|
12
|
-
#
|
13
|
-
# Note that render_behind is called before the text is printed.
|
14
|
-
#
|
15
|
-
# This handler is only used on the main document (not the scratch document).
|
16
|
-
#
|
17
|
-
def render_behind fragment
|
18
|
-
pdf = fragment.document
|
19
|
-
data = fragment.format_state
|
20
|
-
image_top = if data.key? :line_height_increased
|
21
|
-
# align image to bottom of line (differs from fragment.top by descender value)
|
22
|
-
fragment.bottom + data[:image_height]
|
23
|
-
else
|
24
|
-
# center image in line
|
25
|
-
fragment.top - ((fragment.height - data[:image_height]) / 2.0)
|
26
|
-
end
|
27
|
-
image_left = fragment.left + ((fragment.width - data[:image_width]) / 2.0)
|
28
|
-
case data[:image_format]
|
29
|
-
when 'svg'
|
30
|
-
(image_obj = data[:image_obj]).options[:at] = [image_left, image_top]
|
31
|
-
# NOTE prawn-svg messes with the cursor; use float to workaround
|
32
|
-
# NOTE prawn-svg 0.24.0, 0.25.0, & 0.25.1 didn't restore font after call to draw (see mogest/prawn-svg#80)
|
33
|
-
pdf.float { image_obj.draw }
|
34
|
-
else
|
35
|
-
pdf.embed_image data[:image_obj], data[:image_info], at: [image_left, image_top], width: data[:image_width], height: data[:image_height]
|
36
|
-
end
|
37
|
-
# ...or use the public interface, loading the image again
|
38
|
-
#pdf.image data[:image_path], at: [image_left, image_top], width: data[:image_width]
|
39
|
-
|
40
|
-
# prevent any text from being written
|
41
|
-
fragment.conceal
|
42
|
-
ensure
|
43
|
-
::File.unlink data[:image_path] if TemporaryPath === data[:image_path] && data[:image_path].exist?
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,261 +0,0 @@
|
|
1
|
-
module Asciidoctor
|
2
|
-
module Pdf
|
3
|
-
module FormattedText
|
4
|
-
class Transform
|
5
|
-
LF = %(\n)
|
6
|
-
CharEntityTable = {
|
7
|
-
lt: '<',
|
8
|
-
gt: '>',
|
9
|
-
amp: '&',
|
10
|
-
quot: '"',
|
11
|
-
apos: '\''
|
12
|
-
}
|
13
|
-
CharRefRx = /&(?:#(\d{2,6})|(#{CharEntityTable.keys * '|'}));/
|
14
|
-
TextDecorationTable = { 'underline' => :underline, 'line-through' => :strikethrough }
|
15
|
-
#DummyText = %(\u0000)
|
16
|
-
|
17
|
-
def initialize(options = {})
|
18
|
-
@merge_adjacent_text_nodes = options[:merge_adjacent_text_nodes]
|
19
|
-
# TODO add support for character spacing
|
20
|
-
if (theme = options[:theme])
|
21
|
-
@link_font_settings = {
|
22
|
-
color: theme.link_font_color,
|
23
|
-
font: theme.link_font_family,
|
24
|
-
size: theme.link_font_size,
|
25
|
-
styles: to_styles(theme.link_font_style, theme.link_text_decoration)
|
26
|
-
}.select! {|_, val| val }
|
27
|
-
@monospaced_font_settings = {
|
28
|
-
color: theme.literal_font_color,
|
29
|
-
font: theme.literal_font_family,
|
30
|
-
size: theme.literal_font_size,
|
31
|
-
styles: to_styles(theme.literal_font_style)
|
32
|
-
}.select! {|_, val| val }
|
33
|
-
else
|
34
|
-
@link_font_settings = { color: '0000FF' }
|
35
|
-
@monospaced_font_settings = { font: 'Courier', size: 0.9 }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# FIXME pass styles downwards to child elements rather than decorating on way out of hierarchy
|
40
|
-
def apply(parsed)
|
41
|
-
fragments = []
|
42
|
-
previous_fragment_is_text = false
|
43
|
-
# NOTE we use each since using inject is slower than a manual loop
|
44
|
-
parsed.each do |node|
|
45
|
-
case node[:type]
|
46
|
-
when :element
|
47
|
-
# case 1: non-void element
|
48
|
-
if node.key?(:pcdata)
|
49
|
-
unless (pcdata = node[:pcdata]).empty?
|
50
|
-
tag_name = node[:name]
|
51
|
-
attributes = node[:attributes]
|
52
|
-
# NOTE decorate child fragments with styles from this element
|
53
|
-
fragments << apply(pcdata).map {|fragment| build_fragment(fragment, tag_name, attributes) }
|
54
|
-
previous_fragment_is_text = false
|
55
|
-
# NOTE skip element if it has no children
|
56
|
-
#else
|
57
|
-
# # NOTE handle an empty anchor element (i.e., <a ...></a>)
|
58
|
-
# if (tag_name = node[:name]) == :a
|
59
|
-
# fragments << build_fragment({ text: DummyText }, tag_name, node[:attributes])
|
60
|
-
# previous_fragment_is_text = false
|
61
|
-
# end
|
62
|
-
end
|
63
|
-
# case 2: void element
|
64
|
-
else
|
65
|
-
case node[:name]
|
66
|
-
when :br
|
67
|
-
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
68
|
-
fragments << { text: %(#{fragments.pop[:text]}#{LF}) }
|
69
|
-
else
|
70
|
-
fragments << { text: LF }
|
71
|
-
end
|
72
|
-
previous_fragment_is_text = true
|
73
|
-
when :img
|
74
|
-
attributes = node[:attributes]
|
75
|
-
fragment = {
|
76
|
-
image_path: attributes[:tmp] == 'true' ? attributes[:src].extend(TemporaryPath) : attributes[:src],
|
77
|
-
image_format: attributes[:format],
|
78
|
-
text: attributes[:alt],
|
79
|
-
callback: InlineImageRenderer
|
80
|
-
}
|
81
|
-
if (img_w = attributes[:width])
|
82
|
-
fragment[:image_width] = img_w
|
83
|
-
end
|
84
|
-
fragments << fragment
|
85
|
-
previous_fragment_is_text = false
|
86
|
-
end
|
87
|
-
end
|
88
|
-
when :text
|
89
|
-
text = node[:value]
|
90
|
-
# NOTE the remaining logic is shared with :entity
|
91
|
-
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
92
|
-
fragments << { text: %(#{fragments.pop[:text]}#{text}) }
|
93
|
-
else
|
94
|
-
fragments << { text: text }
|
95
|
-
end
|
96
|
-
previous_fragment_is_text = true
|
97
|
-
when :entity
|
98
|
-
if (name = node[:name])
|
99
|
-
text = CharEntityTable[name]
|
100
|
-
else
|
101
|
-
# FIXME AFM fonts do not include a thin space glyph; set fallback_fonts to allow glyph to be resolved
|
102
|
-
text = [node[:number]].pack('U*')
|
103
|
-
end
|
104
|
-
# NOTE the remaining logic is shared with :text
|
105
|
-
if @merge_adjacent_text_nodes && previous_fragment_is_text
|
106
|
-
fragments << { text: %(#{fragments.pop[:text]}#{text}) }
|
107
|
-
else
|
108
|
-
fragments << { text: text }
|
109
|
-
end
|
110
|
-
previous_fragment_is_text = true
|
111
|
-
end
|
112
|
-
end
|
113
|
-
fragments.flatten
|
114
|
-
end
|
115
|
-
|
116
|
-
def build_fragment(fragment, tag_name, attrs = {})
|
117
|
-
styles = (fragment[:styles] ||= ::Set.new)
|
118
|
-
case tag_name
|
119
|
-
when :strong
|
120
|
-
styles << :bold
|
121
|
-
when :em
|
122
|
-
styles << :italic
|
123
|
-
when :code
|
124
|
-
# NOTE prefer old value, except for styles, which should be combined
|
125
|
-
fragment.update(@monospaced_font_settings) {|k, old_v, new_v| k == :styles ? old_v.merge(new_v) : old_v }
|
126
|
-
when :color
|
127
|
-
if !fragment[:color]
|
128
|
-
if (rgb = attrs[:rgb])
|
129
|
-
case rgb.chr
|
130
|
-
when '#'
|
131
|
-
fragment[:color] = rgb[1..-1]
|
132
|
-
when '['
|
133
|
-
# treat value as CMYK array (e.g., "[50, 100, 0, 0]")
|
134
|
-
fragment[:color] = rgb[1..-1].chomp(']').split(', ').map(&:to_i)
|
135
|
-
# ...or we could honor an rgb array too
|
136
|
-
#case (vals = rgb[1..-1].chomp(']').split(', ')).size
|
137
|
-
#when 4
|
138
|
-
# fragment[:color] = vals.map(&:to_i)
|
139
|
-
#when 3
|
140
|
-
# fragment[:color] = vals.map {|e| '%02X' % e.to_i }.join
|
141
|
-
#end
|
142
|
-
else
|
143
|
-
fragment[:color] = rgb
|
144
|
-
end
|
145
|
-
# QUESTION should we even support r,g,b and c,m,y,k as individual values?
|
146
|
-
elsif (r_val = attrs[:r]) && (g_val = attrs[:g]) && (b_val = attrs[:b])
|
147
|
-
fragment[:color] = [r_val, g_val, b_val].map {|e| '%02X' % e.to_i }.join
|
148
|
-
elsif (c_val = attrs[:c]) && (m_val = attrs[:m]) && (y_val = attrs[:y]) && (k_val = attrs[:k])
|
149
|
-
fragment[:color] = [c_val.to_i, m_val.to_i, y_val.to_i, k_val.to_i]
|
150
|
-
end
|
151
|
-
end
|
152
|
-
when :font
|
153
|
-
if !fragment[:font] && (value = attrs[:name])
|
154
|
-
fragment[:font] = value
|
155
|
-
end
|
156
|
-
if !fragment[:size] && (value = attrs[:size])
|
157
|
-
# FIXME can we make this comparison more robust / accurate?
|
158
|
-
if %(#{f_value = value.to_f}) == value || %(#{value.to_i}) == value
|
159
|
-
fragment[:size] = f_value
|
160
|
-
elsif value != '1em'
|
161
|
-
fragment[:size] = value
|
162
|
-
end
|
163
|
-
end
|
164
|
-
#if !fragment[:character_spacing] && (value = attrs[:character_spacing])
|
165
|
-
# fragment[:character_spacing] = value.to_f
|
166
|
-
#end
|
167
|
-
when :a
|
168
|
-
visible = true
|
169
|
-
# a element can have no attributes, so short-circuit if that's the case
|
170
|
-
unless attrs.empty?
|
171
|
-
# NOTE href, anchor, and name are mutually exclusive; nesting is not supported
|
172
|
-
if (value = attrs[:anchor])
|
173
|
-
fragment[:anchor] = value
|
174
|
-
elsif (value = attrs[:href])
|
175
|
-
fragment[:link] = value.include?(';') ? value.gsub(CharRefRx) {
|
176
|
-
$2 ? CharEntityTable[$2.to_sym] : [$1.to_i].pack('U*')
|
177
|
-
} : value
|
178
|
-
elsif (value = attrs[:name])
|
179
|
-
# NOTE text is null character, which is used as placeholder text so Prawn doesn't drop fragment
|
180
|
-
fragment[:name] = value
|
181
|
-
if (type = attrs[:type])
|
182
|
-
fragment[:type] = type.to_sym
|
183
|
-
end
|
184
|
-
fragment[:callback] = InlineDestinationMarker
|
185
|
-
visible = false
|
186
|
-
end
|
187
|
-
end
|
188
|
-
# NOTE prefer old value, except for styles, which should be combined
|
189
|
-
fragment.update(@link_font_settings) {|k, old_v, new_v| k == :styles ? old_v.merge(new_v) : old_v } if visible
|
190
|
-
when :sub
|
191
|
-
styles << :subscript
|
192
|
-
when :sup
|
193
|
-
styles << :superscript
|
194
|
-
when :del
|
195
|
-
styles << :strikethrough
|
196
|
-
when :span
|
197
|
-
# NOTE spaces in style attribute value are superfluous, for our purpose; split drops record after trailing ;
|
198
|
-
attrs[:style].tr(' ', '').split(';').each do |style|
|
199
|
-
pname, pvalue = style.split(':', 2)
|
200
|
-
case pname
|
201
|
-
when 'color'
|
202
|
-
# QUESTION should we check whether the value is a valid hex color?
|
203
|
-
unless fragment[:color]
|
204
|
-
case pvalue.length
|
205
|
-
when 6
|
206
|
-
fragment[:color] = pvalue
|
207
|
-
when 7
|
208
|
-
fragment[:color] = pvalue.slice(1, 6) if pvalue.start_with?('#')
|
209
|
-
# QUESTION should we support the 3 character form?
|
210
|
-
#when 3
|
211
|
-
# fragment[:color] = pvalue.each_char.map {|c| c * 2 }.join
|
212
|
-
#when 4
|
213
|
-
# fragment[:color] = pvalue.slice(1, 3).each_char.map {|c| c * 2 }.join if pvalue.start_with?('#')
|
214
|
-
end
|
215
|
-
end
|
216
|
-
when 'font-weight'
|
217
|
-
if pvalue == 'bold'
|
218
|
-
styles << :bold
|
219
|
-
end
|
220
|
-
when 'font-style'
|
221
|
-
if pvalue == 'italic'
|
222
|
-
styles << :italic
|
223
|
-
end
|
224
|
-
# TODO text-transform
|
225
|
-
end
|
226
|
-
end if attrs.key?(:style)
|
227
|
-
end
|
228
|
-
# TODO we could limit to select tags, but doesn't seem to really affect performance
|
229
|
-
attrs[:class].split.each do |class_name|
|
230
|
-
case class_name
|
231
|
-
when 'underline'
|
232
|
-
styles << :underline
|
233
|
-
when 'line-through'
|
234
|
-
styles << :strikethrough
|
235
|
-
end
|
236
|
-
end if attrs.key?(:class)
|
237
|
-
fragment.delete(:styles) if styles.empty?
|
238
|
-
fragment
|
239
|
-
end
|
240
|
-
|
241
|
-
def to_styles(font_style, text_decoration = nil)
|
242
|
-
case font_style
|
243
|
-
when 'bold'
|
244
|
-
styles = [:bold].to_set
|
245
|
-
when 'italic'
|
246
|
-
styles = [:italic].to_set
|
247
|
-
when 'bold_italic'
|
248
|
-
styles = [:bold, :italic].to_set
|
249
|
-
else
|
250
|
-
styles = nil
|
251
|
-
end
|
252
|
-
if (style = TextDecorationTable[text_decoration])
|
253
|
-
styles ? (styles << style) : [style].to_set
|
254
|
-
else
|
255
|
-
styles
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
require 'asciidoctor/extensions'
|
2
|
-
|
3
|
-
module Asciidoctor
|
4
|
-
module Pdf
|
5
|
-
# An include processor that skips the implicit author line below
|
6
|
-
# the document title within include documents.
|
7
|
-
class ImplicitHeaderProcessor < ::Asciidoctor::Extensions::IncludeProcessor
|
8
|
-
def process doc, reader, target, attributes
|
9
|
-
return reader unless File.exist? target
|
10
|
-
::File.open target, 'r' do |fd|
|
11
|
-
# FIXME handle case where doc id is specified above title
|
12
|
-
if (first_line = fd.readline) && (first_line.start_with? '= ')
|
13
|
-
# HACK reset counters for each article for Editions
|
14
|
-
if doc.attr? 'env', 'editions'
|
15
|
-
doc.counters.each do |counter_key, counter_val|
|
16
|
-
doc.attributes.delete counter_key
|
17
|
-
end
|
18
|
-
doc.counters.clear
|
19
|
-
end
|
20
|
-
if (second_line = fd.readline)
|
21
|
-
if AuthorInfoLineRx =~ second_line
|
22
|
-
# FIXME temporary hack to set author and e-mail attributes; this should handle all attributes in header!
|
23
|
-
author = [$1, $2, $3].compact * ' '
|
24
|
-
email = $4
|
25
|
-
reader.push_include fd.readlines, target, target, 3, attributes unless fd.eof?
|
26
|
-
reader.push_include first_line, target, target, 1, attributes
|
27
|
-
lines = [%(:author: #{author})]
|
28
|
-
lines << %(:email: #{email}) if email
|
29
|
-
reader.push_include lines, target, target, 2, attributes
|
30
|
-
else
|
31
|
-
lines = [second_line]
|
32
|
-
lines += fd.readlines unless fd.eof?
|
33
|
-
reader.push_include lines, target, target, 2, attributes
|
34
|
-
reader.push_include first_line, target, target, 1, attributes
|
35
|
-
end
|
36
|
-
else
|
37
|
-
reader.push_include first_line, target, target, 1, attributes
|
38
|
-
end
|
39
|
-
else
|
40
|
-
lines = [first_line]
|
41
|
-
lines += fd.readlines unless fd.eof?
|
42
|
-
reader.push_include lines, target, target, 1, attributes
|
43
|
-
end
|
44
|
-
end
|
45
|
-
reader
|
46
|
-
end
|
47
|
-
|
48
|
-
def handles? target
|
49
|
-
# FIXME should not require this hack to skip processing bio
|
50
|
-
!(target.end_with? 'bio.adoc') && ((target.end_with? '.adoc') || (target.end_with? '.asciidoc'))
|
51
|
-
end
|
52
|
-
|
53
|
-
# FIXME this method shouldn't be required
|
54
|
-
def update_config config
|
55
|
-
(@config ||= {}).update config
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
Asciidoctor::Extensions.register :pdf do
|
62
|
-
include_processor Asciidoctor::Pdf::ImplicitHeaderProcessor if @document.backend == 'pdf'
|
63
|
-
end
|