deep-cover 0.8.0 → 0.8.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/.pryrc +11 -0
- data/.rubocop.yml +27 -10
- data/.travis.yml +1 -0
- data/README.md +4 -1
- data/deep_cover.gemspec +1 -1
- data/profile/fixtures/converter.rb +4537 -0
- data/profile/profile_output.rb +18 -0
- metadata +10 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 621b28ff48f341de43e6968478e56ad4a19f3fd5857938589a70a811aaa5fbc0
|
|
4
|
+
data.tar.gz: 96188d3620f46b1b84013ea31fb74238baa5096b91d6674312addff99d36a665
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3cd1be3fb4a64e36728781248c10cc3f199a00151ae08b0aec33a9f35914981deaa9fbc22f53f5084cdb19d67f8ab35530f469fe8ca858983fdb8d67379d1f2b
|
|
7
|
+
data.tar.gz: c26c90179da7bad93c2fe605b4c231221c8cb2d1b136aa17253c90cb1319b9dcd5b91aa53744e98f5fa9cc12dd8e5263fa7f657885aef830ee66c982cc092996
|
data/.pryrc
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH << './lib' << './core_gem/lib'
|
|
4
|
+
require 'deep_cover'
|
|
5
|
+
|
|
6
|
+
Pry.config.hooks.add_hook(:when_started, :set_context) do |binding, options, pry|
|
|
7
|
+
if binding.eval('self').class == Object # true when starting `pry`
|
|
8
|
+
# false when called from binding.pry
|
|
9
|
+
pry.input = StringIO.new('cd DeepCover')
|
|
10
|
+
end
|
|
11
|
+
end
|
data/.rubocop.yml
CHANGED
|
@@ -6,6 +6,7 @@ AllCops:
|
|
|
6
6
|
Exclude:
|
|
7
7
|
- '**/spec/char_cover/**/*'
|
|
8
8
|
- '**/spec/code_fixtures/**/*'
|
|
9
|
+
- '**/profile/fixtures/**/*'
|
|
9
10
|
- '**/spec/samples/**/*'
|
|
10
11
|
- '**/spec/specs_tools.rb'
|
|
11
12
|
- 'bin/**/*'
|
|
@@ -16,7 +17,7 @@ Gemspec/RequiredRubyVersion:
|
|
|
16
17
|
# Yes, we still support ruby 2.1...
|
|
17
18
|
Enabled: false
|
|
18
19
|
|
|
19
|
-
Layout/
|
|
20
|
+
Layout/HashAlignment:
|
|
20
21
|
Enabled: false
|
|
21
22
|
|
|
22
23
|
Layout/ClosingParenthesisIndentation:
|
|
@@ -34,16 +35,16 @@ Layout/EmptyLines:
|
|
|
34
35
|
Layout/EmptyLineBetweenDefs:
|
|
35
36
|
NumberOfEmptyLines: [1, 2]
|
|
36
37
|
|
|
37
|
-
Layout/
|
|
38
|
+
Layout/FirstArgumentIndentation:
|
|
38
39
|
IndentationWidth: 4
|
|
39
40
|
|
|
40
|
-
Layout/
|
|
41
|
+
Layout/FirstArrayElementIndentation:
|
|
41
42
|
EnforcedStyle: align_brackets
|
|
42
43
|
|
|
43
|
-
Layout/
|
|
44
|
+
Layout/FirstHashElementIndentation:
|
|
44
45
|
EnforcedStyle: align_braces
|
|
45
46
|
|
|
46
|
-
Layout/
|
|
47
|
+
Layout/HeredocIndentation:
|
|
47
48
|
Enabled: false
|
|
48
49
|
|
|
49
50
|
Layout/MultilineArrayBraceLayout:
|
|
@@ -79,7 +80,7 @@ Metrics/CyclomaticComplexity:
|
|
|
79
80
|
Enabled: false
|
|
80
81
|
|
|
81
82
|
# Really, you aim for less than that, but we won't bug you unless you reach 150
|
|
82
|
-
|
|
83
|
+
Layout/LineLength:
|
|
83
84
|
IgnoreCopDirectives: true
|
|
84
85
|
Max: 150
|
|
85
86
|
|
|
@@ -175,7 +176,8 @@ Style/GlobalVars:
|
|
|
175
176
|
Enabled: false
|
|
176
177
|
|
|
177
178
|
Style/FormatStringToken:
|
|
178
|
-
|
|
179
|
+
Enabled: false
|
|
180
|
+
# EnforcedStyle: template
|
|
179
181
|
|
|
180
182
|
Style/MutableConstant:
|
|
181
183
|
Enabled: false # TODO: - Remove me one asap
|
|
@@ -187,9 +189,9 @@ Naming/PredicateName:
|
|
|
187
189
|
NamePrefix:
|
|
188
190
|
- is_
|
|
189
191
|
- have_
|
|
190
|
-
|
|
192
|
+
ForbiddenPrefixes:
|
|
191
193
|
- have_
|
|
192
|
-
|
|
194
|
+
AllowedMethods:
|
|
193
195
|
- is_statement
|
|
194
196
|
- is_child_statement
|
|
195
197
|
|
|
@@ -229,6 +231,21 @@ Style/NilComparison:
|
|
|
229
231
|
Style/NonNilCheck:
|
|
230
232
|
Enabled: false
|
|
231
233
|
|
|
234
|
+
Style/HashTransformKeys:
|
|
235
|
+
Enabled: true
|
|
236
|
+
|
|
237
|
+
Style/HashTransformValues:
|
|
238
|
+
Enabled: true
|
|
239
|
+
|
|
240
|
+
Style/HashEachMethods:
|
|
241
|
+
Enabled: true
|
|
242
|
+
|
|
243
|
+
Lint/StructNewOverride:
|
|
244
|
+
Enabled: true
|
|
245
|
+
|
|
246
|
+
Lint/RaiseException:
|
|
247
|
+
Enabled: true
|
|
248
|
+
|
|
232
249
|
Lint/EmptyWhen:
|
|
233
250
|
Enabled: false
|
|
234
251
|
|
|
@@ -273,7 +290,7 @@ Gemspec/RubyVersionGlobalsUsage:
|
|
|
273
290
|
Lint/BooleanSymbol:
|
|
274
291
|
Enabled: false
|
|
275
292
|
|
|
276
|
-
Naming/
|
|
293
|
+
Naming/MethodParameterName:
|
|
277
294
|
Exclude:
|
|
278
295
|
- core_gem/spec/**/*
|
|
279
296
|
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
|
@@ -80,7 +80,7 @@ Do the appropriate of the installation of the gem, then follow the steps that co
|
|
|
80
80
|
spec.add_development_dependency 'deep-cover', '~> 0.7'
|
|
81
81
|
|
|
82
82
|
# otherwise if using a Gemfile, add this to it and then run `bundle install`
|
|
83
|
-
gem 'deep-cover', '~> 0.7', group: :test
|
|
83
|
+
gem 'deep-cover', '~> 0.7', group: :test
|
|
84
84
|
|
|
85
85
|
# otherwise just run:
|
|
86
86
|
gem install deep-cover
|
|
@@ -106,6 +106,9 @@ Note, this is a bit slower and may cause issues in your tests if your use relati
|
|
|
106
106
|
|
|
107
107
|
Typically, you want to insert that line **at the very top** of `test/test_helper.rb` or `spec/spec_helper.rb` . If `deep-cover` is required after your code, then it won't be able to detect the coverage.
|
|
108
108
|
|
|
109
|
+
Note that if some of your tests run by launching another process, that process will have to `require 'deep-cover'` also. For example you could insert `require 'deep-cover' if ENV['DEEP_COVER']` at the beginning of `lib/my_awesome_gem.rb`, before all the `require_relative 'my_awesome_gem/fabulous_core_part_1'`, ...
|
|
110
|
+
Note that the environment variable `DEEP_COVER` is set by `deep-cover exec` or `DeepCover.start`.
|
|
111
|
+
|
|
109
112
|
2. Create a config file (optional)
|
|
110
113
|
|
|
111
114
|
You may want to create a config file `.deep_cover.rb` at the root of your project, where you can set the config as you wish.
|
data/deep_cover.gemspec
CHANGED
|
@@ -40,7 +40,7 @@ Gem::Specification.new do |spec|
|
|
|
40
40
|
# About every single release breaks something
|
|
41
41
|
# Ruby 2.1 is no longer supported
|
|
42
42
|
if RUBY_VERSION >= '2.3.0'
|
|
43
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 0.81.0'
|
|
44
44
|
spec.add_development_dependency 'rubocop-performance'
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -0,0 +1,4537 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Adapted from: https://github.com/asciidoctor/asciidoctor-pdf/blob/master/lib/asciidoctor/pdf/converter.rb
|
|
4
|
+
if true
|
|
5
|
+
module Asciidoctor
|
|
6
|
+
module Converter; end
|
|
7
|
+
module Logging; end
|
|
8
|
+
module Writer; end
|
|
9
|
+
module Prawn
|
|
10
|
+
module Extensions
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
module Prawn
|
|
15
|
+
class Document
|
|
16
|
+
def self.register_for(*)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
require_relative 'formatted_text'
|
|
22
|
+
require_relative 'index_catalog'
|
|
23
|
+
require_relative 'pdfmark'
|
|
24
|
+
require_relative 'roman_numeral'
|
|
25
|
+
require_relative 'section_info_by_page'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
autoload :StringIO, 'stringio'
|
|
29
|
+
autoload :Tempfile, 'tempfile'
|
|
30
|
+
|
|
31
|
+
module Asciidoctor
|
|
32
|
+
module PDF
|
|
33
|
+
class Converter < ::Prawn::Document
|
|
34
|
+
include ::Asciidoctor::Converter
|
|
35
|
+
include ::Asciidoctor::Logging
|
|
36
|
+
include ::Asciidoctor::Writer
|
|
37
|
+
include ::Asciidoctor::Prawn::Extensions
|
|
38
|
+
|
|
39
|
+
register_for 'pdf'
|
|
40
|
+
|
|
41
|
+
attr_reader :allow_uri_read
|
|
42
|
+
|
|
43
|
+
attr_reader :cache_uri
|
|
44
|
+
|
|
45
|
+
attr_reader :theme
|
|
46
|
+
|
|
47
|
+
attr_reader :text_decoration_width
|
|
48
|
+
|
|
49
|
+
# NOTE require_library doesn't support require_relative and we don't modify the load path for this gem
|
|
50
|
+
CodeRayRequirePath = ::File.join __dir__, 'ext/prawn/coderay_encoder'
|
|
51
|
+
RougeRequirePath = ::File.join __dir__, 'ext/rouge'
|
|
52
|
+
PygmentsRequirePath = ::File.join __dir__, 'ext/pygments'
|
|
53
|
+
OptimizerRequirePath = ::File.join __dir__, 'optimizer'
|
|
54
|
+
|
|
55
|
+
AdmonitionIcons = {
|
|
56
|
+
caution: { name: 'fas-fire', stroke_color: 'BF3400', size: 24 },
|
|
57
|
+
important: { name: 'fas-exclamation-circle', stroke_color: 'BF0000', size: 24 },
|
|
58
|
+
note: { name: 'fas-info-circle', stroke_color: '19407C', size: 24 },
|
|
59
|
+
tip: { name: 'far-lightbulb', stroke_color: '111111', size: 24 },
|
|
60
|
+
warning: { name: 'fas-exclamation-triangle', stroke_color: 'BF6900', size: 24 },
|
|
61
|
+
}
|
|
62
|
+
TextAlignmentNames = %w(justify left center right)
|
|
63
|
+
TextAlignmentRoles = %w(text-justify text-left text-center text-right)
|
|
64
|
+
TextDecorationStyleTable = { 'underline' => :underline, 'line-through' => :strikethrough }
|
|
65
|
+
FontKerningTable = { 'normal' => true, 'none' => false }
|
|
66
|
+
BlockAlignmentNames = %w(left center right)
|
|
67
|
+
AlignmentTable = { '<' => :left, '=' => :center, '>' => :right }
|
|
68
|
+
ColumnPositions = [:left, :center, :right]
|
|
69
|
+
PageLayouts = [:portrait, :landscape]
|
|
70
|
+
(PageModes = {
|
|
71
|
+
'fullscreen' => [:FullScreen, :UseOutlines],
|
|
72
|
+
'fullscreen none' => [:FullScreen, :UseNone],
|
|
73
|
+
'fullscreen outline' => [:FullScreen, :UseOutlines],
|
|
74
|
+
'fullscreen thumbs' => [:FullScreen, :UseThumbs],
|
|
75
|
+
'none' => :UseNone,
|
|
76
|
+
'outline' => :UseOutlines,
|
|
77
|
+
'thumbs' => :UseThumbs,
|
|
78
|
+
}).default = :UseOutlines
|
|
79
|
+
PageSides = [:recto, :verso]
|
|
80
|
+
(PDFVersions = { '1.3' => 1.3, '1.4' => 1.4, '1.5' => 1.5, '1.6' => 1.6, '1.7' => 1.7 }).default = 1.4
|
|
81
|
+
AuthorAttributeNames = %w(author authorinitials firstname middlename lastname email)
|
|
82
|
+
LF = ?\n
|
|
83
|
+
DoubleLF = LF * 2
|
|
84
|
+
TAB = ?\t
|
|
85
|
+
InnerIndent = LF + ' '
|
|
86
|
+
# a no-break space is used to replace a leading space to prevent Prawn from trimming indentation
|
|
87
|
+
# a leading zero-width space can't be used as it gets dropped when calculating the line width
|
|
88
|
+
GuardedIndent = ?\u00a0
|
|
89
|
+
GuardedInnerIndent = LF + GuardedIndent
|
|
90
|
+
TabRx = /\t/
|
|
91
|
+
TabIndentRx = /^\t+/
|
|
92
|
+
NoBreakSpace = ?\u00a0
|
|
93
|
+
ZeroWidthSpace = ?\u200b
|
|
94
|
+
DummyText = ?\u0000
|
|
95
|
+
DotLeaderTextDefault = '. '
|
|
96
|
+
EmDash = ?\u2014
|
|
97
|
+
RightPointer = ?\u25ba
|
|
98
|
+
LowercaseGreekA = ?\u03b1
|
|
99
|
+
Bullets = {
|
|
100
|
+
disc: ?\u2022,
|
|
101
|
+
circle: ?\u25e6,
|
|
102
|
+
square: ?\u25aa,
|
|
103
|
+
none: '',
|
|
104
|
+
}
|
|
105
|
+
# NOTE Default theme font uses ballot boxes from FontAwesome
|
|
106
|
+
BallotBox = {
|
|
107
|
+
checked: ?\u2611,
|
|
108
|
+
unchecked: ?\u2610,
|
|
109
|
+
}
|
|
110
|
+
ConumSets = {
|
|
111
|
+
'circled' => (?\u2460..?\u2473).to_a,
|
|
112
|
+
'filled' => (?\u2776..?\u277f).to_a + (?\u24eb..?\u24f4).to_a,
|
|
113
|
+
}
|
|
114
|
+
SimpleAttributeRefRx = /(?<!\\)\{\w+(?:[\-]\w+)*\}/
|
|
115
|
+
MeasurementRxt = '\\d+(?:\\.\\d+)?(?:in|cm|mm|p[txc])?'
|
|
116
|
+
MeasurementPartsRx = /^(\d+(?:\.\d+)?)(in|mm|cm|p[txc])?$/
|
|
117
|
+
PageSizeRx = /^(?:\[(#{MeasurementRxt}), ?(#{MeasurementRxt})\]|(#{MeasurementRxt})(?: x |x)(#{MeasurementRxt})|\S+)$/
|
|
118
|
+
CalloutExtractRx = /(?:(?:\/\/|#|--|;;) ?)?(\\)?<!?(|--)(\d+|\.)\2> ?(?=(?:\\?<!?\2(?:\d+|\.)\2>)*$)/
|
|
119
|
+
ImageAttributeValueRx = /^image:{1,2}(.*?)\[(.*?)\]$/
|
|
120
|
+
StopPunctRx = /[.!?;:]$/
|
|
121
|
+
UriBreakCharsRx = /(?:\/|\?|&|#)(?!$)/
|
|
122
|
+
UriBreakCharRepl = %(\\&#{ZeroWidthSpace})
|
|
123
|
+
UriSchemeBoundaryRx = /(?<=:\/\/)/
|
|
124
|
+
LineScanRx = /\n|.+/
|
|
125
|
+
BlankLineRx = /\n{2,}/
|
|
126
|
+
CjkLineBreakRx = /(?=[\u3000\u30a0-\u30ff\u3040-\u309f\p{Han}\uff00-\uffef])/
|
|
127
|
+
WhitespaceChars = ' ' + TAB + LF
|
|
128
|
+
ValueSeparatorRx = /;|,/
|
|
129
|
+
HexColorRx = /^#[a-fA-F0-9]{6}$/
|
|
130
|
+
VimeoThumbnailRx = /<thumbnail_large>(.*?)<\/thumbnail_large>/
|
|
131
|
+
SourceHighlighters = %w(coderay pygments rouge).to_set
|
|
132
|
+
ViewportWidth = ::Module.new
|
|
133
|
+
(TitleStyles = {
|
|
134
|
+
'toc' => [:numbered_title],
|
|
135
|
+
'basic' => [:title],
|
|
136
|
+
}).default = [:numbered_title, formal: true]
|
|
137
|
+
|
|
138
|
+
def initialize backend, opts
|
|
139
|
+
super
|
|
140
|
+
basebackend 'html'
|
|
141
|
+
filetype 'pdf'
|
|
142
|
+
htmlsyntax 'html'
|
|
143
|
+
outfilesuffix '.pdf'
|
|
144
|
+
if (doc = opts[:document])
|
|
145
|
+
# NOTE enabling data-uri forces Asciidoctor Diagram to produce absolute image paths
|
|
146
|
+
doc.attributes['data-uri'] = (doc.instance_variable_get :@attribute_overrides)['data-uri'] = ''
|
|
147
|
+
end
|
|
148
|
+
@initial_instance_variables = [:@initial_instance_variables] + instance_variables
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def convert node, name = nil, _opts = {}
|
|
152
|
+
method_name = %(convert_#{name ||= node.node_name})
|
|
153
|
+
if respond_to? method_name
|
|
154
|
+
result = send method_name, node
|
|
155
|
+
else
|
|
156
|
+
# TODO: delegate to convert_method_missing
|
|
157
|
+
logger.warn %(missing convert handler for #{name} node in #{@backend} backend) unless scratch?
|
|
158
|
+
end
|
|
159
|
+
# NOTE: inline nodes generate pseudo-HTML strings; the remainder write directly to PDF object
|
|
160
|
+
::Asciidoctor::Inline === node ? result : self
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def traverse node, opts = {}
|
|
164
|
+
# NOTE converter instance in scratch document gets duplicated; must be rewired to this one
|
|
165
|
+
if self == (prev_converter = node.document.converter)
|
|
166
|
+
prev_converter = nil
|
|
167
|
+
else
|
|
168
|
+
node.document.instance_variable_set :@converter, self
|
|
169
|
+
end
|
|
170
|
+
if node.blocks?
|
|
171
|
+
node.content
|
|
172
|
+
elsif node.content_model != :compound && (string = node.content)
|
|
173
|
+
# TODO: this content could be cached on repeat invocations!
|
|
174
|
+
layout_prose string, (opts.merge hyphenate: true)
|
|
175
|
+
end
|
|
176
|
+
ensure
|
|
177
|
+
node.document.instance_variable_set :@converter, prev_converter if prev_converter
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def convert_document doc
|
|
181
|
+
init_pdf doc
|
|
182
|
+
# set default value for outline and pagenums if not otherwise set
|
|
183
|
+
doc.attributes['outline'] = '' unless (doc.attribute_locked? 'outline') || ((doc.instance_variable_get :@attributes_modified).include? 'outline')
|
|
184
|
+
doc.attributes['pagenums'] = '' unless (doc.attribute_locked? 'pagenums') || ((doc.instance_variable_get :@attributes_modified).include? 'pagenums')
|
|
185
|
+
#assign_missing_section_ids doc
|
|
186
|
+
|
|
187
|
+
# promote anonymous preface (defined using preamble block) to preface section
|
|
188
|
+
# FIXME: this should be done in core
|
|
189
|
+
if doc.doctype == 'book' && (blk0 = doc.blocks[0]) && blk0.context == :preamble && blk0.title? &&
|
|
190
|
+
!blk0.title.nil_or_empty? && blk0.blocks[0].style != 'abstract' && (blk1 = doc.blocks[1]) && blk1.context == :section
|
|
191
|
+
preface = Section.new doc, blk1.level, false, attributes: { 1 => 'preface', 'style' => 'preface' }
|
|
192
|
+
preface.special = true
|
|
193
|
+
preface.sectname = 'preface'
|
|
194
|
+
preface.title = blk0.instance_variable_get :@title
|
|
195
|
+
# QUESTION should ID be generated from raw or converted title? core is not clear about this
|
|
196
|
+
preface.id = preface.generate_id
|
|
197
|
+
preface.blocks.replace blk0.blocks.map {|b| b.parent = preface; b } # rubocop:disable Style/Semicolon
|
|
198
|
+
doc.blocks[0] = preface
|
|
199
|
+
blk0 = blk1 = preface = nil # rubocop:disable Lint/UselessAssignment
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
on_page_create(&(method :init_page))
|
|
203
|
+
|
|
204
|
+
marked_page_number = page_number
|
|
205
|
+
# NOTE: a new page will already be started (page_number = 2) if the front cover image is a PDF
|
|
206
|
+
layout_cover_page doc, :front
|
|
207
|
+
has_front_cover = page_number > marked_page_number
|
|
208
|
+
|
|
209
|
+
if (use_title_page = doc.doctype == 'book' || (doc.attr? 'title-page'))
|
|
210
|
+
layout_title_page doc
|
|
211
|
+
has_title_page = page_number == (has_front_cover ? 2 : 1)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
@page_margin_by_side[:cover] = @page_margin_by_side[:recto] if @media == 'prepress' && page_number == 0
|
|
215
|
+
|
|
216
|
+
# NOTE: font must be set before content is written to the main or scratch document
|
|
217
|
+
start_new_page unless page.empty?
|
|
218
|
+
font @theme.base_font_family, size: @root_font_size, style: (@theme.base_font_style || :normal).to_sym
|
|
219
|
+
|
|
220
|
+
unless use_title_page
|
|
221
|
+
body_start_page_number = page_number
|
|
222
|
+
theme_font :heading, level: 1 do
|
|
223
|
+
layout_heading doc.doctitle, align: (@theme.heading_h1_align || :center).to_sym, level: 1
|
|
224
|
+
end if doc.header? && !doc.notitle
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
num_front_matter_pages = toc_page_nums = toc_num_levels = nil
|
|
228
|
+
|
|
229
|
+
indent_section do
|
|
230
|
+
toc_num_levels = (doc.attr 'toclevels', 2).to_i
|
|
231
|
+
if (insert_toc = (doc.attr? 'toc') && !(doc.attr? 'toc-placement', 'macro') && doc.sections?)
|
|
232
|
+
start_new_page if @ppbook && verso_page?
|
|
233
|
+
add_dest_for_block doc, 'toc'
|
|
234
|
+
allocate_toc doc, toc_num_levels, @y, use_title_page
|
|
235
|
+
else
|
|
236
|
+
@toc_extent = nil
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
start_new_page if @ppbook && verso_page?
|
|
240
|
+
|
|
241
|
+
if use_title_page
|
|
242
|
+
zero_page_offset = has_front_cover ? 1 : 0
|
|
243
|
+
first_page_offset = has_title_page ? zero_page_offset.next : zero_page_offset
|
|
244
|
+
body_offset = (body_start_page_number = page_number) - 1
|
|
245
|
+
if ::Integer === (running_content_start_at = @theme.running_content_start_at || 'body')
|
|
246
|
+
running_content_body_offset = body_offset + [running_content_start_at.pred, 1].max
|
|
247
|
+
running_content_start_at = 'body'
|
|
248
|
+
else
|
|
249
|
+
running_content_body_offset = body_offset
|
|
250
|
+
running_content_start_at = 'toc' if running_content_start_at == 'title' && !has_title_page
|
|
251
|
+
running_content_start_at = 'body' if running_content_start_at == 'toc' && !insert_toc
|
|
252
|
+
end
|
|
253
|
+
if ::Integer === (page_numbering_start_at = @theme.page_numbering_start_at || 'body')
|
|
254
|
+
page_numbering_body_offset = body_offset + [page_numbering_start_at.pred, 1].max
|
|
255
|
+
page_numbering_start_at = 'body'
|
|
256
|
+
else
|
|
257
|
+
page_numbering_body_offset = body_offset
|
|
258
|
+
page_numbering_start_at = 'toc' if page_numbering_start_at == 'title' && !has_title_page
|
|
259
|
+
page_numbering_start_at = 'body' if page_numbering_start_at == 'toc' && !insert_toc
|
|
260
|
+
end
|
|
261
|
+
front_matter_sig = [running_content_start_at, page_numbering_start_at]
|
|
262
|
+
# table values are number of pages to skip before starting running content and page numbering, respectively
|
|
263
|
+
num_front_matter_pages = {
|
|
264
|
+
%w(title title) => [zero_page_offset, zero_page_offset],
|
|
265
|
+
%w(title toc) => [zero_page_offset, first_page_offset],
|
|
266
|
+
%w(title body) => [zero_page_offset, page_numbering_body_offset],
|
|
267
|
+
%w(toc title) => [first_page_offset, zero_page_offset],
|
|
268
|
+
%w(toc toc) => [first_page_offset, first_page_offset],
|
|
269
|
+
%w(toc body) => [first_page_offset, page_numbering_body_offset],
|
|
270
|
+
%w(body title) => [running_content_body_offset, zero_page_offset],
|
|
271
|
+
%w(body toc) => [running_content_body_offset, first_page_offset],
|
|
272
|
+
}[front_matter_sig] || [running_content_body_offset, page_numbering_body_offset]
|
|
273
|
+
else
|
|
274
|
+
body_offset = body_start_page_number - 1
|
|
275
|
+
if ::Integer === (running_content_start_at = @theme.running_content_start_at || 'body')
|
|
276
|
+
running_content_body_offset = body_offset + [running_content_start_at.pred, 1].max
|
|
277
|
+
else
|
|
278
|
+
running_content_body_offset = body_offset
|
|
279
|
+
end
|
|
280
|
+
if ::Integer === (page_numbering_start_at = @theme.page_numbering_start_at || 'body')
|
|
281
|
+
page_numbering_body_offset = body_offset + [page_numbering_start_at.pred, 1].max
|
|
282
|
+
else
|
|
283
|
+
page_numbering_body_offset = body_offset
|
|
284
|
+
end
|
|
285
|
+
num_front_matter_pages = [running_content_body_offset, page_numbering_body_offset]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
@index.start_page_number = num_front_matter_pages[1] + 1
|
|
289
|
+
doc.set_attr 'pdf-anchor', (doc_anchor = derive_anchor_from_id doc.id, 'top')
|
|
290
|
+
add_dest_for_block doc, doc_anchor
|
|
291
|
+
|
|
292
|
+
convert_section generate_manname_section doc if doc.doctype == 'manpage' && (doc.attr? 'manpurpose')
|
|
293
|
+
|
|
294
|
+
traverse doc
|
|
295
|
+
|
|
296
|
+
# NOTE: for a book, these are leftover footnotes; for an article this is everything
|
|
297
|
+
outdent_section { layout_footnotes doc }
|
|
298
|
+
|
|
299
|
+
toc_page_nums = @toc_extent ? (layout_toc doc, toc_num_levels, @toc_extent[:page_nums].first, @toc_extent[:start_y], num_front_matter_pages[1]) : []
|
|
300
|
+
|
|
301
|
+
# NOTE: delete orphaned page (a page was created but there was no additional content)
|
|
302
|
+
# QUESTION should we delete page if document is empty? (leaving no pages?)
|
|
303
|
+
delete_page if page_count > 1 && page.empty?
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
unless page_count < body_start_page_number
|
|
307
|
+
layout_running_content :header, doc, skip: num_front_matter_pages, body_start_page_number: body_start_page_number unless doc.noheader || @theme.header_height.to_f == 0
|
|
308
|
+
layout_running_content :footer, doc, skip: num_front_matter_pages, body_start_page_number: body_start_page_number unless doc.nofooter || @theme.footer_height.to_f == 0
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
add_outline doc, (doc.attr 'outlinelevels', toc_num_levels), toc_page_nums, num_front_matter_pages[1], has_front_cover
|
|
312
|
+
if !state.pages.empty? && (initial_zoom = @theme.page_initial_zoom)
|
|
313
|
+
case initial_zoom.to_sym
|
|
314
|
+
when :Fit
|
|
315
|
+
catalog.data[:OpenAction] = dest_fit state.pages[0]
|
|
316
|
+
when :FitV
|
|
317
|
+
catalog.data[:OpenAction] = dest_fit_vertically 0, state.pages[0]
|
|
318
|
+
when :FitH
|
|
319
|
+
catalog.data[:OpenAction] = dest_fit_horizontally page_height, state.pages[0]
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
catalog.data[:ViewerPreferences] = { DisplayDocTitle: true }
|
|
323
|
+
|
|
324
|
+
stamp_foreground_image doc, has_front_cover
|
|
325
|
+
layout_cover_page doc, :back
|
|
326
|
+
nil
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# NOTE: embedded only makes sense if perhaps we are building
|
|
330
|
+
# on an existing Prawn::Document instance; for now, just treat
|
|
331
|
+
# it the same as a full document.
|
|
332
|
+
alias convert_embedded convert_document
|
|
333
|
+
|
|
334
|
+
def init_pdf doc
|
|
335
|
+
(instance_variables - @initial_instance_variables).each {|ivar| remove_instance_variable ivar } if state
|
|
336
|
+
pdf_opts = build_pdf_options doc, (theme = load_theme doc)
|
|
337
|
+
# QUESTION should page options be preserved? (otherwise, not readily available)
|
|
338
|
+
#@page_opts = { size: pdf_opts[:page_size], layout: pdf_opts[:page_layout] }
|
|
339
|
+
((::Prawn::Document.instance_method :initialize).bind self).call pdf_opts
|
|
340
|
+
renderer.min_version (@pdf_version = PDFVersions[doc.attr 'pdf-version'])
|
|
341
|
+
@page_margin_by_side = { recto: page_margin, verso: page_margin, cover: page_margin }
|
|
342
|
+
if (@media = doc.attr 'media', 'screen') == 'prepress'
|
|
343
|
+
@ppbook = doc.doctype == 'book'
|
|
344
|
+
page_margin_recto = @page_margin_by_side[:recto]
|
|
345
|
+
if (page_margin_outer = theme.page_margin_outer)
|
|
346
|
+
page_margin_recto[1] = @page_margin_by_side[:verso][3] = page_margin_outer
|
|
347
|
+
end
|
|
348
|
+
if (page_margin_inner = theme.page_margin_inner)
|
|
349
|
+
page_margin_recto[3] = @page_margin_by_side[:verso][1] = page_margin_inner
|
|
350
|
+
end
|
|
351
|
+
# NOTE: prepare scratch document to use page margin from recto side (which has same width as verso side)
|
|
352
|
+
set_page_margin page_margin_recto unless page_margin_recto == page_margin
|
|
353
|
+
else
|
|
354
|
+
@ppbook = nil
|
|
355
|
+
end
|
|
356
|
+
# QUESTION should ThemeLoader handle registering fonts instead?
|
|
357
|
+
register_fonts theme.font_catalog, (doc.attr 'pdf-fontsdir', 'GEM_FONTS_DIR')
|
|
358
|
+
default_kerning theme.base_font_kerning != 'none'
|
|
359
|
+
@fallback_fonts = [*theme.font_fallbacks]
|
|
360
|
+
@allow_uri_read = doc.attr? 'allow-uri-read'
|
|
361
|
+
@cache_uri = doc.attr? 'cache-uri'
|
|
362
|
+
@tmp_files = {}
|
|
363
|
+
if (bg_image = resolve_background_image doc, theme, 'page-background-image') && bg_image[0]
|
|
364
|
+
@page_bg_image = { verso: bg_image, recto: bg_image }
|
|
365
|
+
else
|
|
366
|
+
@page_bg_image = { verso: nil, recto: nil }
|
|
367
|
+
end
|
|
368
|
+
if (bg_image = resolve_background_image doc, theme, 'page-background-image-verso')
|
|
369
|
+
@page_bg_image[:verso] = bg_image[0] && bg_image
|
|
370
|
+
end
|
|
371
|
+
if (bg_image = resolve_background_image doc, theme, 'page-background-image-recto')
|
|
372
|
+
@page_bg_image[:recto] = bg_image[0] && bg_image
|
|
373
|
+
end
|
|
374
|
+
@page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF'
|
|
375
|
+
@root_font_size = theme.base_font_size || 12
|
|
376
|
+
@font_color = theme.base_font_color || '000000'
|
|
377
|
+
@text_decoration_width = theme.base_text_decoration_width
|
|
378
|
+
@base_align = (align = doc.attr 'text-align') && (TextAlignmentNames.include? align) ? align : theme.base_align
|
|
379
|
+
@cjk_line_breaks = doc.attr? 'scripts', 'cjk'
|
|
380
|
+
if (hyphen_lang = doc.attr 'hyphens') &&
|
|
381
|
+
((defined? ::Text::Hyphen::VERSION) || !(Helpers.require_library 'text/hyphen', 'text-hyphen', :warn).nil?)
|
|
382
|
+
hyphen_lang = doc.attr 'lang' if hyphen_lang.empty?
|
|
383
|
+
hyphen_lang = 'en_us' if hyphen_lang.nil_or_empty? || hyphen_lang == 'en'
|
|
384
|
+
hyphen_lang = (hyphen_lang.tr '-', '_').downcase
|
|
385
|
+
@hyphenator = ::Text::Hyphen.new language: hyphen_lang
|
|
386
|
+
end
|
|
387
|
+
@text_transform = nil
|
|
388
|
+
@list_numerals = []
|
|
389
|
+
@list_bullets = []
|
|
390
|
+
@rendered_footnotes = []
|
|
391
|
+
@conum_glyphs = ConumSets[@theme.conum_glyphs || 'circled'] || (@theme.conum_glyphs.split ',').map {|r|
|
|
392
|
+
from, to = r.rstrip.split '-', 2
|
|
393
|
+
to ? ((get_char from)..(get_char to)).to_a : [(get_char from)]
|
|
394
|
+
}.flatten
|
|
395
|
+
@section_indent = (val = @theme.section_indent) && (expand_indent_value val)
|
|
396
|
+
@toc_max_pagenum_digits = (doc.attr 'toc-max-pagenum-digits', 3).to_i
|
|
397
|
+
@disable_running_content = {}
|
|
398
|
+
@index = IndexCatalog.new
|
|
399
|
+
# NOTE: we have to init Pdfmark class here while we have reference to the doc
|
|
400
|
+
@pdfmark = (doc.attr? 'pdfmark') ? (Pdfmark.new doc) : nil
|
|
401
|
+
@optimize = doc.attr 'optimize'
|
|
402
|
+
init_scratch_prototype
|
|
403
|
+
self
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def load_theme doc
|
|
407
|
+
@theme ||= begin # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
408
|
+
if (theme = doc.options[:pdf_theme])
|
|
409
|
+
@themesdir = ::File.expand_path theme.__dir__ || (doc.attr 'pdf-themesdir') || (doc.attr 'pdf-stylesdir') || ::Dir.pwd
|
|
410
|
+
elsif (theme_name = (doc.attr 'pdf-theme') || (doc.attr 'pdf-style'))
|
|
411
|
+
theme = ThemeLoader.load_theme theme_name, (user_themesdir = (doc.attr 'pdf-themesdir') || (doc.attr 'pdf-stylesdir'))
|
|
412
|
+
@themesdir = theme.__dir__
|
|
413
|
+
else
|
|
414
|
+
@themesdir = (theme = ThemeLoader.load_theme).__dir__
|
|
415
|
+
end
|
|
416
|
+
theme
|
|
417
|
+
rescue
|
|
418
|
+
if user_themesdir
|
|
419
|
+
message = %(could not locate or load the pdf theme `#{theme_name}' in #{user_themesdir})
|
|
420
|
+
else
|
|
421
|
+
message = %(could not locate or load the built-in pdf theme `#{theme_name}')
|
|
422
|
+
end
|
|
423
|
+
message += %( because of #{$!.class} #{$!.message}) unless ::SystemCallError === $!
|
|
424
|
+
logger.error %(#{message}; reverting to default theme)
|
|
425
|
+
@themesdir = (theme = ThemeLoader.load_theme).__dir__
|
|
426
|
+
theme
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def build_pdf_options doc, theme
|
|
431
|
+
case (page_margin = (doc.attr 'pdf-page-margin') || theme.page_margin)
|
|
432
|
+
when ::Array
|
|
433
|
+
if page_margin.empty?
|
|
434
|
+
page_margin = nil
|
|
435
|
+
else
|
|
436
|
+
page_margin = page_margin.slice 0, 4 if page_margin.length > 4
|
|
437
|
+
page_margin = page_margin.map {|v| ::Numeric === v ? v : (str_to_pt v.to_s) }
|
|
438
|
+
end
|
|
439
|
+
when ::Numeric
|
|
440
|
+
page_margin = [page_margin]
|
|
441
|
+
when ::String
|
|
442
|
+
if page_margin.empty?
|
|
443
|
+
page_margin = nil
|
|
444
|
+
elsif (page_margin.start_with? '[') && (page_margin.end_with? ']')
|
|
445
|
+
if (page_margin = (page_margin.slice 1, page_margin.length - 2).rstrip).empty?
|
|
446
|
+
page_margin = nil
|
|
447
|
+
else
|
|
448
|
+
if (page_margin = page_margin.split ',', -1).length > 4
|
|
449
|
+
page_margin = page_margin.slice 0, 4
|
|
450
|
+
end
|
|
451
|
+
page_margin = page_margin.map {|v| str_to_pt v.rstrip }
|
|
452
|
+
end
|
|
453
|
+
else
|
|
454
|
+
page_margin = [(str_to_pt page_margin)]
|
|
455
|
+
end
|
|
456
|
+
else
|
|
457
|
+
page_margin = nil
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
if (doc.attr? 'pdf-page-size') && PageSizeRx =~ (doc.attr 'pdf-page-size')
|
|
461
|
+
# e.g, [8.5in, 11in]
|
|
462
|
+
if $1
|
|
463
|
+
page_size = [$1, $2]
|
|
464
|
+
# e.g, 8.5in x 11in
|
|
465
|
+
elsif $3
|
|
466
|
+
page_size = [$3, $4]
|
|
467
|
+
# e.g, A4
|
|
468
|
+
else
|
|
469
|
+
page_size = $&
|
|
470
|
+
end
|
|
471
|
+
else
|
|
472
|
+
page_size = theme.page_size
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
case page_size
|
|
476
|
+
when ::String
|
|
477
|
+
# TODO: extract helper method to check for named page size
|
|
478
|
+
page_size = page_size.upcase
|
|
479
|
+
page_size = nil unless ::PDF::Core::PageGeometry::SIZES.key? page_size
|
|
480
|
+
when ::Array
|
|
481
|
+
page_size = (page_size.slice 0, 2).fill(0..1) {|i| page_size[i] || 0 } unless page_size.size == 2
|
|
482
|
+
page_size = page_size.map do |dim|
|
|
483
|
+
if ::Numeric === dim
|
|
484
|
+
# dimension cannot be less than 0
|
|
485
|
+
dim > 0 ? dim : break
|
|
486
|
+
elsif ::String === dim && MeasurementPartsRx =~ dim
|
|
487
|
+
# NOTE: truncate to max precision retained by PDF::Core
|
|
488
|
+
(to_pt $1.to_f, $2).truncate 4
|
|
489
|
+
else
|
|
490
|
+
break
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
else
|
|
494
|
+
page_size = nil
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if (page_layout = (doc.attr 'pdf-page-layout') || theme.page_layout).nil_or_empty? ||
|
|
498
|
+
!PageLayouts.include?(page_layout = page_layout.to_sym)
|
|
499
|
+
page_layout = nil
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
margin: (page_margin || 36),
|
|
504
|
+
page_size: (page_size || 'A4'),
|
|
505
|
+
page_layout: (page_layout || :portrait),
|
|
506
|
+
info: (build_pdf_info doc),
|
|
507
|
+
compress: (doc.attr? 'compress'),
|
|
508
|
+
skip_page_creation: true,
|
|
509
|
+
text_formatter: (FormattedText::Formatter.new theme: theme),
|
|
510
|
+
}
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# FIXME: Pdfmark should use the PDF info result
|
|
514
|
+
def build_pdf_info doc
|
|
515
|
+
info = {}
|
|
516
|
+
if (doctitle = doc.header? ? doc.doctitle : (doc.attr 'untitled-label'))
|
|
517
|
+
info[:Title] = (sanitize doctitle).as_pdf
|
|
518
|
+
end
|
|
519
|
+
info[:Author] = (sanitize doc.attr 'authors').as_pdf if doc.attr? 'authors'
|
|
520
|
+
info[:Subject] = (sanitize doc.attr 'subject').as_pdf if doc.attr? 'subject'
|
|
521
|
+
info[:Keywords] = (sanitize doc.attr 'keywords').as_pdf if doc.attr? 'keywords'
|
|
522
|
+
info[:Producer] = (sanitize doc.attr 'publisher').as_pdf if doc.attr? 'publisher'
|
|
523
|
+
if doc.attr? 'reproducible'
|
|
524
|
+
info[:Creator] = 'Asciidoctor PDF, based on Prawn'.as_pdf
|
|
525
|
+
info[:Producer] ||= (info[:Author] || info[:Creator])
|
|
526
|
+
else
|
|
527
|
+
info[:Creator] = %(Asciidoctor PDF #{::Asciidoctor::PDF::VERSION}, based on Prawn #{::Prawn::VERSION}).as_pdf
|
|
528
|
+
info[:Producer] ||= (info[:Author] || info[:Creator])
|
|
529
|
+
# NOTE: since we don't track the creation date of the input file, we map the ModDate header to the last modified
|
|
530
|
+
# date of the input document and the CreationDate header to the date the PDF was produced by the converter.
|
|
531
|
+
info[:ModDate] = (::Time.parse doc.attr 'docdatetime') rescue (now ||= ::Time.now)
|
|
532
|
+
info[:CreationDate] = (::Time.parse doc.attr 'localdatetime') rescue (now || ::Time.now)
|
|
533
|
+
end
|
|
534
|
+
info
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# # NOTE: init_page is called within a float context
|
|
538
|
+
# # NOTE: init_page is not called for imported pages, front and back cover pages, and other image pages
|
|
539
|
+
# def init_page *_args
|
|
540
|
+
# # NOTE: we assume in prepress that physical page number reflects page side
|
|
541
|
+
# if @media == 'prepress' &&
|
|
542
|
+
# (next_page_margin = @page_margin_by_side[page_number == 1 ? :cover : page_side]) != page_margin
|
|
543
|
+
# set_page_margin next_page_margin
|
|
544
|
+
# end
|
|
545
|
+
# unless @page_bg_color == 'FFFFFF'
|
|
546
|
+
# tare = true
|
|
547
|
+
# fill_absolute_bounds @page_bg_color
|
|
548
|
+
# end
|
|
549
|
+
# if (bg_image = @page_bg_image[page_side])
|
|
550
|
+
# tare = true
|
|
551
|
+
# # NOTE: float is necessary since prawn-svg may mess with cursor position
|
|
552
|
+
# float { canvas { image bg_image[0], ({ position: :center, vposition: :center }.merge bg_image[1]) } }
|
|
553
|
+
# end
|
|
554
|
+
# page.tare_content_stream if tare
|
|
555
|
+
# end
|
|
556
|
+
|
|
557
|
+
# def convert_section sect, _opts = {}
|
|
558
|
+
# if sect.sectname == 'abstract'
|
|
559
|
+
# # HACK: cheat a bit to hide this section from TOC; TOC should filter these sections
|
|
560
|
+
# sect.context = :open
|
|
561
|
+
# return convert_abstract sect
|
|
562
|
+
# elsif (index_section = sect.sectname == 'index')
|
|
563
|
+
# return if @index.empty?
|
|
564
|
+
# end
|
|
565
|
+
|
|
566
|
+
# type = nil
|
|
567
|
+
# title = sect.numbered_title formal: true
|
|
568
|
+
# sep = (sect.attr 'separator', nil, false) || (sect.document.attr 'title-separator') || ''
|
|
569
|
+
# if !sep.empty? && title.include?(sep = %(#{sep} ))
|
|
570
|
+
# title, _, subtitle = title.rpartition sep
|
|
571
|
+
# title = %(#{title}\n<em class="subtitle">#{subtitle}</em>)
|
|
572
|
+
# end
|
|
573
|
+
# theme_font :heading, level: (hlevel = sect.level + 1) do
|
|
574
|
+
# align = (@theme[%(heading_h#{hlevel}_align)] || @theme.heading_align || @base_align).to_sym
|
|
575
|
+
# if sect.part_or_chapter?
|
|
576
|
+
# if sect.chapter?
|
|
577
|
+
# type = :chapter
|
|
578
|
+
# if @theme.heading_chapter_break_before == 'auto'
|
|
579
|
+
# start_new_chapter sect if @theme.heading_part_break_after == 'always' && sect == sect.parent.sections[0]
|
|
580
|
+
# else
|
|
581
|
+
# start_new_chapter sect
|
|
582
|
+
# end
|
|
583
|
+
# else
|
|
584
|
+
# type = :part
|
|
585
|
+
# start_new_part sect unless @theme.heading_part_break_before == 'auto'
|
|
586
|
+
# end
|
|
587
|
+
# end
|
|
588
|
+
# unless at_page_top?
|
|
589
|
+
# # FIXME: this height doesn't account for impact of text transform or inline formatting
|
|
590
|
+
# heading_height =
|
|
591
|
+
# (height_of_typeset_text title, line_height: (@theme[%(heading_h#{hlevel}_line_height)] || @theme.heading_line_height)) +
|
|
592
|
+
# (@theme[%(heading_h#{hlevel}_margin_top)] || @theme.heading_margin_top || 0) +
|
|
593
|
+
# (@theme[%(heading_h#{hlevel}_margin_bottom)] || @theme.heading_margin_bottom || 0)
|
|
594
|
+
# heading_height += (@theme.heading_min_height_after || 0) if sect.blocks?
|
|
595
|
+
# start_new_page unless cursor > heading_height
|
|
596
|
+
# end
|
|
597
|
+
# # QUESTION should we store pdf-page-start, pdf-anchor & pdf-destination in internal map?
|
|
598
|
+
# sect.set_attr 'pdf-page-start', (start_pgnum = page_number)
|
|
599
|
+
# # QUESTION should we just assign the section this generated id?
|
|
600
|
+
# # NOTE: section must have pdf-anchor in order to be listed in the TOC
|
|
601
|
+
# sect.set_attr 'pdf-anchor', (sect_anchor = derive_anchor_from_id sect.id, %(#{start_pgnum}-#{y.ceil}))
|
|
602
|
+
# add_dest_for_block sect, sect_anchor
|
|
603
|
+
# if type == :part
|
|
604
|
+
# layout_part_title sect, title, align: align, level: hlevel
|
|
605
|
+
# elsif type == :chapter
|
|
606
|
+
# layout_chapter_title sect, title, align: align, level: hlevel
|
|
607
|
+
# else
|
|
608
|
+
# layout_heading title, align: align, level: hlevel, outdent: true
|
|
609
|
+
# end
|
|
610
|
+
# end
|
|
611
|
+
|
|
612
|
+
# if index_section
|
|
613
|
+
# outdent_section { convert_index_section sect }
|
|
614
|
+
# else
|
|
615
|
+
# traverse sect
|
|
616
|
+
# end
|
|
617
|
+
# outdent_section { layout_footnotes sect } if type == :chapter
|
|
618
|
+
# sect.set_attr 'pdf-page-end', page_number
|
|
619
|
+
# end
|
|
620
|
+
|
|
621
|
+
# def indent_section
|
|
622
|
+
# if (values = @section_indent)
|
|
623
|
+
# indent(values[0], values[1]) { yield }
|
|
624
|
+
# else
|
|
625
|
+
# yield
|
|
626
|
+
# end
|
|
627
|
+
# end
|
|
628
|
+
|
|
629
|
+
# def outdent_section enabled = true
|
|
630
|
+
# if enabled && (values = @section_indent)
|
|
631
|
+
# indent(-values[0], -values[1]) { yield }
|
|
632
|
+
# else
|
|
633
|
+
# yield
|
|
634
|
+
# end
|
|
635
|
+
# end
|
|
636
|
+
|
|
637
|
+
# # QUESTION if a footnote ref appears in a separate chapter, should the footnote def be duplicated?
|
|
638
|
+
# def layout_footnotes node
|
|
639
|
+
# return if (fns = (doc = node.document).footnotes - @rendered_footnotes).empty?
|
|
640
|
+
# theme_margin :footnotes, :top
|
|
641
|
+
# theme_font :footnotes do
|
|
642
|
+
# (title = doc.attr 'footnotes-title') && (layout_caption title, category: :footnotes)
|
|
643
|
+
# item_spacing = @theme.footnotes_item_spacing || 0
|
|
644
|
+
# index_offset = @rendered_footnotes.length
|
|
645
|
+
# sect_xreftext = node.context == :section && (node.xreftext node.document.attr 'xrefstyle')
|
|
646
|
+
# fns.each do |fn|
|
|
647
|
+
# label = (index = fn.index) - index_offset
|
|
648
|
+
# if sect_xreftext
|
|
649
|
+
# fn.singleton_class.send :attr_accessor, :label unless fn.respond_to? :label=
|
|
650
|
+
# fn.label = %(#{label} - #{sect_xreftext})
|
|
651
|
+
# end
|
|
652
|
+
# layout_prose %(<a id="_footnotedef_#{index}">#{DummyText}</a>[<a anchor="_footnoteref_#{index}">#{label}</a>] #{fn.text}), margin_bottom: item_spacing, hyphenate: true
|
|
653
|
+
# end
|
|
654
|
+
# @rendered_footnotes += fns
|
|
655
|
+
# end
|
|
656
|
+
# nil
|
|
657
|
+
# end
|
|
658
|
+
|
|
659
|
+
# def convert_floating_title node
|
|
660
|
+
# add_dest_for_block node if node.id
|
|
661
|
+
# hlevel = node.level.next
|
|
662
|
+
# unless (align = resolve_alignment_from_role node.roles)
|
|
663
|
+
# align = (@theme[%(heading_h#{hlevel}_align)] || @theme.heading_align || @base_align).to_sym
|
|
664
|
+
# end
|
|
665
|
+
# # QUESTION should we decouple styles from section titles?
|
|
666
|
+
# theme_font :heading, level: hlevel do
|
|
667
|
+
# layout_heading node.title, align: align, level: hlevel, outdent: (node.parent.context == :section)
|
|
668
|
+
# end
|
|
669
|
+
# end
|
|
670
|
+
|
|
671
|
+
# def convert_abstract node
|
|
672
|
+
# add_dest_for_block node if node.id
|
|
673
|
+
# outdent_section do
|
|
674
|
+
# pad_box @theme.abstract_padding do
|
|
675
|
+
# theme_font :abstract_title do
|
|
676
|
+
# layout_prose node.title, align: (@theme.abstract_title_align || @base_align).to_sym, margin_top: (@theme.heading_margin_top || 0), margin_bottom: (@theme.heading_margin_bottom || 0), line_height: @theme.heading_line_height
|
|
677
|
+
# end if node.title?
|
|
678
|
+
# theme_font :abstract do
|
|
679
|
+
# prose_opts = { line_height: @theme.abstract_line_height, align: (@theme.abstract_align || @base_align).to_sym, hyphenate: true }
|
|
680
|
+
# if (text_indent = @theme.prose_text_indent || 0) > 0
|
|
681
|
+
# prose_opts[:indent_paragraphs] = text_indent
|
|
682
|
+
# end
|
|
683
|
+
# # FIXME: allow theme to control more first line options
|
|
684
|
+
# if (line1_font_style = @theme.abstract_first_line_font_style) && line1_font_style.to_sym != font_style
|
|
685
|
+
# first_line_options = { styles: [font_style, line1_font_style.to_sym] }
|
|
686
|
+
# end
|
|
687
|
+
# if (line1_font_color = @theme.abstract_first_line_font_color)
|
|
688
|
+
# (first_line_options ||= {})[:color] = line1_font_color
|
|
689
|
+
# end
|
|
690
|
+
# prose_opts[:first_line_options] = first_line_options if first_line_options
|
|
691
|
+
# # FIXME: make this cleaner!!
|
|
692
|
+
# if node.blocks?
|
|
693
|
+
# node.blocks.each do |child|
|
|
694
|
+
# if child.context == :paragraph
|
|
695
|
+
# child.document.playback_attributes child.attributes
|
|
696
|
+
# layout_prose child.content, ((align = resolve_alignment_from_role child.roles) ? (prose_opts.merge align: align) : prose_opts.dup)
|
|
697
|
+
# prose_opts.delete :first_line_options
|
|
698
|
+
# else
|
|
699
|
+
# # FIXME: this could do strange things if the wrong kind of content shows up
|
|
700
|
+
# child.convert
|
|
701
|
+
# end
|
|
702
|
+
# end
|
|
703
|
+
# elsif node.content_model != :compound && (string = node.content)
|
|
704
|
+
# if (align = resolve_alignment_from_role node.roles)
|
|
705
|
+
# prose_opts[:align] = align
|
|
706
|
+
# end
|
|
707
|
+
# layout_prose string, prose_opts
|
|
708
|
+
# end
|
|
709
|
+
# end
|
|
710
|
+
# end
|
|
711
|
+
# # QUESTION should we be adding margin below the abstract??
|
|
712
|
+
# #theme_margin :block, :bottom
|
|
713
|
+
# end
|
|
714
|
+
# end
|
|
715
|
+
|
|
716
|
+
# def convert_preamble node
|
|
717
|
+
# # FIXME: core should not be promoting paragraph to preamble if there are no sections
|
|
718
|
+
# if node.blocks? && (first_block = node.blocks[0]).context == :paragraph && node.document.sections?
|
|
719
|
+
# first_block.add_role 'lead' unless first_block.role?
|
|
720
|
+
# end
|
|
721
|
+
# traverse node
|
|
722
|
+
# end
|
|
723
|
+
|
|
724
|
+
# def convert_paragraph node
|
|
725
|
+
# add_dest_for_block node if node.id
|
|
726
|
+
# prose_opts = { margin_bottom: 0, hyphenate: true }
|
|
727
|
+
# lead = (roles = node.roles).include? 'lead'
|
|
728
|
+
# if (align = resolve_alignment_from_role roles)
|
|
729
|
+
# prose_opts[:align] = align
|
|
730
|
+
# end
|
|
731
|
+
|
|
732
|
+
# if (text_indent = @theme.prose_text_indent || 0) > 0
|
|
733
|
+
# prose_opts[:indent_paragraphs] = text_indent
|
|
734
|
+
# end
|
|
735
|
+
|
|
736
|
+
# # TODO: check if we're within one line of the bottom of the page
|
|
737
|
+
# # and advance to the next page if so (similar to logic for section titles)
|
|
738
|
+
# layout_caption node.title if node.title?
|
|
739
|
+
|
|
740
|
+
# if lead
|
|
741
|
+
# theme_font :lead do
|
|
742
|
+
# layout_prose node.content, prose_opts
|
|
743
|
+
# end
|
|
744
|
+
# else
|
|
745
|
+
# layout_prose node.content, prose_opts
|
|
746
|
+
# end
|
|
747
|
+
|
|
748
|
+
# if (margin_inner_val = @theme.prose_margin_inner) &&
|
|
749
|
+
# (next_block = (siblings = node.parent.blocks)[(siblings.index node) + 1]) && next_block.context == :paragraph
|
|
750
|
+
# margin_bottom margin_inner_val
|
|
751
|
+
# else
|
|
752
|
+
# margin_bottom @theme.prose_margin_bottom
|
|
753
|
+
# end
|
|
754
|
+
# end
|
|
755
|
+
|
|
756
|
+
# def convert_admonition node
|
|
757
|
+
# add_dest_for_block node if node.id
|
|
758
|
+
# theme_margin :block, :top
|
|
759
|
+
# type = node.attr 'name'
|
|
760
|
+
# label_align = (@theme.admonition_label_align || :center).to_sym
|
|
761
|
+
# # TODO: allow vertical_align to be a number
|
|
762
|
+
# if (label_valign = (@theme.admonition_label_vertical_align || :middle).to_sym) == :middle
|
|
763
|
+
# label_valign = :center
|
|
764
|
+
# end
|
|
765
|
+
# if (label_min_width = @theme.admonition_label_min_width)
|
|
766
|
+
# label_min_width = label_min_width.to_f
|
|
767
|
+
# end
|
|
768
|
+
# icons = ((doc = node.document).attr? 'icons') ? (doc.attr 'icons') : nil
|
|
769
|
+
# if (data_uri_enabled = doc.attr? 'data-uri')
|
|
770
|
+
# doc.remove_attr 'data-uri'
|
|
771
|
+
# end
|
|
772
|
+
# if icons == 'font' && !(node.attr? 'icon', nil, false)
|
|
773
|
+
# label_text = type.to_sym
|
|
774
|
+
# icon_data = admonition_icon_data label_text
|
|
775
|
+
# label_width = label_min_width || ((icon_size = icon_data[:size] || 24) * 1.5)
|
|
776
|
+
# # NOTE: icon_uri will consider icon attribute on node first, then type
|
|
777
|
+
# # QUESTION should we use resolve_image_path here?
|
|
778
|
+
# elsif icons && (icon_path = node.icon_uri type) &&
|
|
779
|
+
# (icon_path = node.normalize_system_path icon_path, nil, nil, target_name: 'admonition icon') &&
|
|
780
|
+
# (::File.readable? icon_path)
|
|
781
|
+
# icons = true
|
|
782
|
+
# # TODO: introduce @theme.admonition_image_width? or use size key from admonition_icon_<name>?
|
|
783
|
+
# label_width = label_min_width || 36.0
|
|
784
|
+
# else
|
|
785
|
+
# if icons
|
|
786
|
+
# icons = nil
|
|
787
|
+
# logger.warn %(admonition icon not found or not readable: #{icon_path}) unless scratch?
|
|
788
|
+
# end
|
|
789
|
+
# label_text = node.caption
|
|
790
|
+
# theme_font :admonition_label do
|
|
791
|
+
# theme_font %(admonition_label_#{type}) do
|
|
792
|
+
# label_text = transform_text label_text, @text_transform if @text_transform
|
|
793
|
+
# label_width = rendered_width_of_string label_text
|
|
794
|
+
# label_width = label_min_width if label_min_width && label_min_width > label_width
|
|
795
|
+
# end
|
|
796
|
+
# end
|
|
797
|
+
# end
|
|
798
|
+
# doc.set_attr 'data-uri', '' if data_uri_enabled
|
|
799
|
+
# unless ::Array === (cpad = @theme.admonition_padding)
|
|
800
|
+
# cpad = ::Array.new 4, cpad
|
|
801
|
+
# end
|
|
802
|
+
# unless ::Array === (lpad = @theme.admonition_label_padding || cpad)
|
|
803
|
+
# lpad = ::Array.new 4, lpad
|
|
804
|
+
# end
|
|
805
|
+
# # FIXME: this shift stuff is a real hack until we have proper margin collapsing
|
|
806
|
+
# shift_base = @theme.prose_margin_bottom
|
|
807
|
+
# shift_top = shift_base / 3.0
|
|
808
|
+
# shift_bottom = (shift_base * 2) / 3.0
|
|
809
|
+
# keep_together do |box_height = nil|
|
|
810
|
+
# push_scratch doc if scratch?
|
|
811
|
+
# theme_fill_and_stroke_block :admonition, box_height if box_height
|
|
812
|
+
# pad_box [0, cpad[1], 0, lpad[3]] do
|
|
813
|
+
# if box_height
|
|
814
|
+
# label_height = [box_height, cursor].min
|
|
815
|
+
# if (rule_color = @theme.admonition_column_rule_color) &&
|
|
816
|
+
# (rule_width = @theme.admonition_column_rule_width || @theme.base_border_width) && rule_width > 0
|
|
817
|
+
# float do
|
|
818
|
+
# rule_height = box_height
|
|
819
|
+
# while rule_height > 0
|
|
820
|
+
# rule_segment_height = [rule_height, cursor].min
|
|
821
|
+
# bounding_box [0, cursor], width: label_width + lpad[1], height: rule_segment_height do
|
|
822
|
+
# stroke_vertical_rule rule_color,
|
|
823
|
+
# at: bounds.right,
|
|
824
|
+
# line_style: (@theme.admonition_column_rule_style || :solid).to_sym,
|
|
825
|
+
# line_width: rule_width
|
|
826
|
+
# end
|
|
827
|
+
# advance_page if (rule_height -= rule_segment_height) > 0
|
|
828
|
+
# end
|
|
829
|
+
# end
|
|
830
|
+
# end
|
|
831
|
+
# float do
|
|
832
|
+
# bounding_box [0, cursor], width: label_width, height: label_height do
|
|
833
|
+
# if icons == 'font'
|
|
834
|
+
# # FIXME: we assume icon is square
|
|
835
|
+
# icon_size = fit_icon_to_bounds icon_size
|
|
836
|
+
# # NOTE: Prawn's vertical center is not reliable, so calculate it manually
|
|
837
|
+
# if label_valign == :center
|
|
838
|
+
# label_valign = :top
|
|
839
|
+
# if (vcenter_pos = (label_height - icon_size) * 0.5) > 0
|
|
840
|
+
# move_down vcenter_pos
|
|
841
|
+
# end
|
|
842
|
+
# end
|
|
843
|
+
# icon icon_data[:name],
|
|
844
|
+
# valign: label_valign,
|
|
845
|
+
# align: label_align,
|
|
846
|
+
# color: icon_data[:stroke_color],
|
|
847
|
+
# size: icon_size
|
|
848
|
+
# elsif icons
|
|
849
|
+
# if (::Asciidoctor::Image.format icon_path) == 'svg'
|
|
850
|
+
# begin
|
|
851
|
+
# svg_obj = ::Prawn::SVG::Interface.new ::File.read(icon_path, mode: 'r:UTF-8'), self,
|
|
852
|
+
# position: label_align,
|
|
853
|
+
# vposition: label_valign,
|
|
854
|
+
# width: label_width,
|
|
855
|
+
# height: label_height,
|
|
856
|
+
# fallback_font_name: fallback_svg_font_name,
|
|
857
|
+
# enable_web_requests: allow_uri_read ? (method :load_open_uri).to_proc : false,
|
|
858
|
+
# enable_file_requests_with_root: (::File.dirname icon_path),
|
|
859
|
+
# cache_images: cache_uri
|
|
860
|
+
# if (icon_height = (svg_size = svg_obj.document.sizing).output_height) > label_height
|
|
861
|
+
# icon_width = (svg_obj.resize height: (icon_height = label_height)).output_width
|
|
862
|
+
# else
|
|
863
|
+
# icon_width = svg_size.output_width
|
|
864
|
+
# end
|
|
865
|
+
# svg_obj.draw
|
|
866
|
+
# svg_obj.document.warnings.each do |icon_warning|
|
|
867
|
+
# logger.warn %(problem encountered in image: #{icon_path}; #{icon_warning})
|
|
868
|
+
# end unless scratch?
|
|
869
|
+
# rescue
|
|
870
|
+
# logger.warn %(could not embed admonition icon: #{icon_path}; #{$!.message}) unless scratch?
|
|
871
|
+
# end
|
|
872
|
+
# else
|
|
873
|
+
# begin
|
|
874
|
+
# image_obj, image_info = ::File.open(icon_path, 'rb') {|fd| build_image_object fd }
|
|
875
|
+
# icon_aspect_ratio = image_info.width.fdiv image_info.height
|
|
876
|
+
# # NOTE: don't scale image up if smaller than label_width
|
|
877
|
+
# icon_width = [(to_pt image_info.width, :px), label_width].min
|
|
878
|
+
# if (icon_height = icon_width * (1 / icon_aspect_ratio)) > label_height
|
|
879
|
+
# icon_width *= label_height / icon_height
|
|
880
|
+
# icon_height = label_height # rubocop:disable Lint/UselessAssignment
|
|
881
|
+
# end
|
|
882
|
+
# embed_image image_obj, image_info, width: icon_width, position: label_align, vposition: label_valign
|
|
883
|
+
# rescue
|
|
884
|
+
# # QUESTION should we show the label in this case?
|
|
885
|
+
# logger.warn %(could not embed admonition icon: #{icon_path}; #{$!.message}) unless scratch?
|
|
886
|
+
# end
|
|
887
|
+
# end
|
|
888
|
+
# else
|
|
889
|
+
# # IMPORTANT the label must fit in the alotted space or it shows up on another page!
|
|
890
|
+
# # QUESTION anyway to prevent text overflow in the case it doesn't fit?
|
|
891
|
+
# theme_font :admonition_label do
|
|
892
|
+
# theme_font %(admonition_label_#{type}) do
|
|
893
|
+
# # NOTE: Prawn's vertical center is not reliable, so calculate it manually
|
|
894
|
+
# if label_valign == :center
|
|
895
|
+
# label_valign = :top
|
|
896
|
+
# if (vcenter_pos = (label_height - (height_of_typeset_text label_text, line_height: 1)) * 0.5) > 0
|
|
897
|
+
# move_down vcenter_pos
|
|
898
|
+
# end
|
|
899
|
+
# end
|
|
900
|
+
# @text_transform = nil # already applied to label
|
|
901
|
+
# layout_prose label_text,
|
|
902
|
+
# align: label_align,
|
|
903
|
+
# valign: label_valign,
|
|
904
|
+
# line_height: 1,
|
|
905
|
+
# margin: 0,
|
|
906
|
+
# inline_format: false
|
|
907
|
+
# end
|
|
908
|
+
# end
|
|
909
|
+
# end
|
|
910
|
+
# end
|
|
911
|
+
# end
|
|
912
|
+
# end
|
|
913
|
+
# pad_box [cpad[0], 0, cpad[2], label_width + lpad[1] + cpad[3]] do
|
|
914
|
+
# move_down shift_top
|
|
915
|
+
# layout_caption node.title, category: :admonition if node.title?
|
|
916
|
+
# theme_font :admonition do
|
|
917
|
+
# traverse node
|
|
918
|
+
# end
|
|
919
|
+
# # FIXME: HACK compensate for margin bottom of admonition content
|
|
920
|
+
# move_up shift_bottom unless at_page_top?
|
|
921
|
+
# end
|
|
922
|
+
# end
|
|
923
|
+
# pop_scratch doc if scratch?
|
|
924
|
+
# end
|
|
925
|
+
# theme_margin :block, :bottom
|
|
926
|
+
# end
|
|
927
|
+
|
|
928
|
+
# def convert_example node
|
|
929
|
+
# add_dest_for_block node if node.id
|
|
930
|
+
# theme_margin :block, :top
|
|
931
|
+
# caption_height = node.title? ? (layout_caption node, category: :example, dry_run: true) : 0
|
|
932
|
+
# keep_together do |box_height = nil|
|
|
933
|
+
# push_scratch node.document if scratch?
|
|
934
|
+
# if box_height
|
|
935
|
+
# theme_fill_and_stroke_block :example, box_height, caption_node: node
|
|
936
|
+
# else
|
|
937
|
+
# move_down caption_height
|
|
938
|
+
# end
|
|
939
|
+
# pad_box @theme.example_padding do
|
|
940
|
+
# theme_font :example do
|
|
941
|
+
# traverse node
|
|
942
|
+
# end
|
|
943
|
+
# end
|
|
944
|
+
# pop_scratch node.document if scratch?
|
|
945
|
+
# end
|
|
946
|
+
# theme_margin :block, :bottom
|
|
947
|
+
# end
|
|
948
|
+
|
|
949
|
+
# def convert_open node
|
|
950
|
+
# if node.style == 'abstract'
|
|
951
|
+
# convert_abstract node
|
|
952
|
+
# else
|
|
953
|
+
# doc = node.document
|
|
954
|
+
# keep_together_if node.option? 'unbreakable' do
|
|
955
|
+
# push_scratch doc if scratch?
|
|
956
|
+
# add_dest_for_block node if node.id
|
|
957
|
+
# layout_caption node.title if node.title?
|
|
958
|
+
# traverse node
|
|
959
|
+
# pop_scratch doc if scratch?
|
|
960
|
+
# end
|
|
961
|
+
# end
|
|
962
|
+
# end
|
|
963
|
+
|
|
964
|
+
# def convert_quote_or_verse node
|
|
965
|
+
# add_dest_for_block node if node.id
|
|
966
|
+
# theme_margin :block, :top
|
|
967
|
+
# category = node.context == :quote ? :blockquote : :verse
|
|
968
|
+
# # NOTE: b_width and b_left_width are mutually exclusive
|
|
969
|
+
# unless (b_left_width = @theme[%(#{category}_border_left_width)]) && b_left_width > 0
|
|
970
|
+
# b_left_width = nil
|
|
971
|
+
# if (b_width = @theme[%(#{category}_border_width)])
|
|
972
|
+
# b_width = nil unless b_width > 0
|
|
973
|
+
# end
|
|
974
|
+
# end
|
|
975
|
+
# keep_together do |box_height = nil|
|
|
976
|
+
# push_scratch node.document if scratch?
|
|
977
|
+
# theme_fill_and_stroke_block category, box_height, border_width: b_width if box_height && (b_width || @theme[%(#{category}_background_color)])
|
|
978
|
+
# start_page_number = page_number
|
|
979
|
+
# start_cursor = cursor
|
|
980
|
+
# caption_height = node.title? ? (layout_caption node, category: category) : 0
|
|
981
|
+
# pad_box @theme[%(#{category}_padding)] do
|
|
982
|
+
# theme_font category do
|
|
983
|
+
# if category == :blockquote
|
|
984
|
+
# traverse node
|
|
985
|
+
# else # verse
|
|
986
|
+
# content = guard_indentation node.content
|
|
987
|
+
# layout_prose content, normalize: false, align: :left, hyphenate: true
|
|
988
|
+
# end
|
|
989
|
+
# end
|
|
990
|
+
# if node.attr? 'attribution', nil, false
|
|
991
|
+
# theme_font %(#{category}_cite) do
|
|
992
|
+
# layout_prose %(#{EmDash} #{[(node.attr 'attribution'), (node.attr 'citetitle', nil, false)].compact.join ', '}), align: :left, normalize: false
|
|
993
|
+
# end
|
|
994
|
+
# end
|
|
995
|
+
# end
|
|
996
|
+
# # FIXME: we want to draw graphics before content, but box_height is not reliable when spanning pages
|
|
997
|
+
# # FIXME: border extends to bottom of content area if block terminates at bottom of page
|
|
998
|
+
# if box_height && b_left_width
|
|
999
|
+
# b_color = @theme[%(#{category}_border_color)]
|
|
1000
|
+
# page_spread = page_number - start_page_number + 1
|
|
1001
|
+
# end_cursor = cursor
|
|
1002
|
+
# go_to_page start_page_number
|
|
1003
|
+
# move_cursor_to start_cursor
|
|
1004
|
+
# page_spread.times do |i|
|
|
1005
|
+
# if i == 0
|
|
1006
|
+
# y_draw = cursor
|
|
1007
|
+
# b_height = page_spread > 1 ? y_draw : (y_draw - end_cursor)
|
|
1008
|
+
# else
|
|
1009
|
+
# bounds.move_past_bottom
|
|
1010
|
+
# y_draw = cursor
|
|
1011
|
+
# b_height = page_spread - 1 == i ? (y_draw - end_cursor) : y_draw
|
|
1012
|
+
# end
|
|
1013
|
+
# # NOTE: skip past caption if present
|
|
1014
|
+
# if caption_height > 0
|
|
1015
|
+
# if caption_height > cursor
|
|
1016
|
+
# caption_height -= cursor
|
|
1017
|
+
# next # keep skipping, caption is on next page
|
|
1018
|
+
# end
|
|
1019
|
+
# y_draw -= caption_height
|
|
1020
|
+
# b_height -= caption_height
|
|
1021
|
+
# caption_height = 0
|
|
1022
|
+
# end
|
|
1023
|
+
# # NOTE: b_height is 0 when block terminates at bottom of page
|
|
1024
|
+
# next if b_height == 0
|
|
1025
|
+
# bounding_box [0, y_draw], width: bounds.width, height: b_height do
|
|
1026
|
+
# stroke_vertical_rule b_color, line_width: b_left_width, at: b_left_width * 0.5
|
|
1027
|
+
# end
|
|
1028
|
+
# end
|
|
1029
|
+
# end
|
|
1030
|
+
# pop_scratch node.document if scratch?
|
|
1031
|
+
# end
|
|
1032
|
+
# theme_margin :block, :bottom
|
|
1033
|
+
# end
|
|
1034
|
+
|
|
1035
|
+
# alias convert_quote convert_quote_or_verse
|
|
1036
|
+
# alias convert_verse convert_quote_or_verse
|
|
1037
|
+
|
|
1038
|
+
# def convert_sidebar node
|
|
1039
|
+
# add_dest_for_block node if node.id
|
|
1040
|
+
# theme_margin :block, :top
|
|
1041
|
+
# keep_together do |box_height = nil|
|
|
1042
|
+
# push_scratch node.document if scratch?
|
|
1043
|
+
# theme_fill_and_stroke_block :sidebar, box_height if box_height
|
|
1044
|
+
# pad_box @theme.sidebar_padding do
|
|
1045
|
+
# theme_font :sidebar_title do
|
|
1046
|
+
# # QUESTION should we allow margins of sidebar title to be customized?
|
|
1047
|
+
# layout_prose node.title, align: (@theme.sidebar_title_align || @base_align).to_sym, margin_top: 0, margin_bottom: (@theme.heading_margin_bottom || 0), line_height: @theme.heading_line_height
|
|
1048
|
+
# end if node.title?
|
|
1049
|
+
# theme_font :sidebar do
|
|
1050
|
+
# traverse node
|
|
1051
|
+
# end
|
|
1052
|
+
# end
|
|
1053
|
+
# pop_scratch node.document if scratch?
|
|
1054
|
+
# end
|
|
1055
|
+
# theme_margin :block, :bottom
|
|
1056
|
+
# end
|
|
1057
|
+
|
|
1058
|
+
# def convert_colist node
|
|
1059
|
+
# # HACK: undo the margin below previous listing or literal block
|
|
1060
|
+
# # TODO: allow this to be set using colist_margin_top
|
|
1061
|
+
# if (self_idx = node.parent.blocks.index node) && self_idx > 0 &&
|
|
1062
|
+
# [:listing, :literal].include?(node.parent.blocks[self_idx - 1].context)
|
|
1063
|
+
# move_up @theme.block_margin_bottom - @theme.outline_list_item_spacing
|
|
1064
|
+
# end unless at_page_top?
|
|
1065
|
+
# add_dest_for_block node if node.id
|
|
1066
|
+
# @list_numerals << 1
|
|
1067
|
+
# #stroke_horizontal_rule @theme.caption_border_bottom_color
|
|
1068
|
+
# line_metrics = theme_font(:conum) { calc_line_metrics @theme.base_line_height }
|
|
1069
|
+
# node.items.each do |item|
|
|
1070
|
+
# allocate_space_for_list_item line_metrics
|
|
1071
|
+
# convert_colist_item item
|
|
1072
|
+
# end
|
|
1073
|
+
# @list_numerals.pop
|
|
1074
|
+
# # correct bottom margin of last item
|
|
1075
|
+
# list_margin_bottom = @theme.prose_margin_bottom
|
|
1076
|
+
# margin_bottom list_margin_bottom - @theme.outline_list_item_spacing
|
|
1077
|
+
# end
|
|
1078
|
+
|
|
1079
|
+
# def convert_colist_item node
|
|
1080
|
+
# marker_width = nil
|
|
1081
|
+
# @list_numerals << (index = @list_numerals.pop).next
|
|
1082
|
+
# theme_font :conum do
|
|
1083
|
+
# marker_width = rendered_width_of_string %(#{marker = conum_glyph index}x)
|
|
1084
|
+
# float do
|
|
1085
|
+
# bounding_box [0, cursor], width: marker_width do
|
|
1086
|
+
# theme_font :conum do
|
|
1087
|
+
# layout_prose marker, align: :center, line_height: @theme.conum_line_height, inline_format: false, margin: 0
|
|
1088
|
+
# end
|
|
1089
|
+
# end
|
|
1090
|
+
# end
|
|
1091
|
+
# end
|
|
1092
|
+
|
|
1093
|
+
# indent marker_width do
|
|
1094
|
+
# traverse_list_item node, :colist, margin_bottom: @theme.outline_list_item_spacing, normalize_line_height: true
|
|
1095
|
+
# end
|
|
1096
|
+
# end
|
|
1097
|
+
|
|
1098
|
+
# def convert_dlist node
|
|
1099
|
+
# add_dest_for_block node if node.id
|
|
1100
|
+
|
|
1101
|
+
# case (style = node.style)
|
|
1102
|
+
# when 'unordered', 'ordered'
|
|
1103
|
+
# if style == 'unordered'
|
|
1104
|
+
# list_style = :ulist
|
|
1105
|
+
# (markers = @list_bullets) << :disc
|
|
1106
|
+
# else
|
|
1107
|
+
# list_style = :olist
|
|
1108
|
+
# (markers = @list_numerals) << 1
|
|
1109
|
+
# end
|
|
1110
|
+
# list = List.new node.parent, list_style
|
|
1111
|
+
# stack_subject = node.has_role? 'stack'
|
|
1112
|
+
# subject_stop = node.attr 'subject-stop', (stack_subject ? nil : ':'), false
|
|
1113
|
+
# node.items.each do |subjects, dd|
|
|
1114
|
+
# subject = [*subjects].first.text
|
|
1115
|
+
# if dd
|
|
1116
|
+
# list_item_text = %(+++<strong>#{subject}#{(StopPunctRx.match? sanitize subject) ? '' : subject_stop}</strong>#{dd.text? ? "#{stack_subject ? '<br>' : ' '}#{dd.text}" : ''}+++)
|
|
1117
|
+
# list_item = ListItem.new list, list_item_text
|
|
1118
|
+
# dd.blocks.each {|it| list_item << it } if dd.block?
|
|
1119
|
+
# else
|
|
1120
|
+
# list_item = ListItem.new list, %(+++<strong>#{subject}</strong>+++)
|
|
1121
|
+
# end
|
|
1122
|
+
# list << list_item
|
|
1123
|
+
# end
|
|
1124
|
+
# convert_outline_list list
|
|
1125
|
+
# markers.pop
|
|
1126
|
+
# when 'horizontal'
|
|
1127
|
+
# table_data = []
|
|
1128
|
+
# term_padding = desc_padding = term_line_metrics = term_inline_format = term_kerning = nil
|
|
1129
|
+
# max_term_width = 0
|
|
1130
|
+
# theme_font :description_list_term do
|
|
1131
|
+
# term_inline_format = (term_font_styles = font_styles).empty? ? true : [inherited: { styles: term_font_styles }]
|
|
1132
|
+
# term_line_metrics = calc_line_metrics @theme.description_list_term_line_height || @theme.base_line_height
|
|
1133
|
+
# term_padding = [term_line_metrics.padding_top, 10, (@theme.prose_margin_bottom || 0) * 0.5 + term_line_metrics.padding_bottom, 10]
|
|
1134
|
+
# desc_padding = [0, 10, (@theme.prose_margin_bottom || 0) * 0.5, 10]
|
|
1135
|
+
# term_kerning = default_kerning?
|
|
1136
|
+
# end
|
|
1137
|
+
# node.items.each do |terms, desc|
|
|
1138
|
+
# term_text = terms.map(&:text).join ?\n
|
|
1139
|
+
# if (term_width = width_of term_text, inline_format: term_inline_format, kerning: term_kerning) > max_term_width
|
|
1140
|
+
# max_term_width = term_width
|
|
1141
|
+
# end
|
|
1142
|
+
# row_data = [{
|
|
1143
|
+
# text_color: @font_color,
|
|
1144
|
+
# kerning: term_kerning,
|
|
1145
|
+
# content: term_text,
|
|
1146
|
+
# inline_format: term_inline_format,
|
|
1147
|
+
# padding: term_padding,
|
|
1148
|
+
# leading: term_line_metrics.leading,
|
|
1149
|
+
# # FIXME: prawn-table doesn't have support for final_gap option
|
|
1150
|
+
# #final_gap: term_line_metrics.final_gap,
|
|
1151
|
+
# valign: :top,
|
|
1152
|
+
# }]
|
|
1153
|
+
# if desc
|
|
1154
|
+
# desc_container = Block.new desc, :open
|
|
1155
|
+
# desc_container << (Block.new desc_container, :paragraph, source: (desc.instance_variable_get :@text), subs: :default) if desc.text?
|
|
1156
|
+
# desc.blocks.each {|b| desc_container << b } if desc.block?
|
|
1157
|
+
# row_data << {
|
|
1158
|
+
# content: (::Prawn::Table::Cell::AsciiDoc.new self, content: desc_container, text_color: @font_color, padding: desc_padding, valign: :top),
|
|
1159
|
+
# }
|
|
1160
|
+
# else
|
|
1161
|
+
# row_data << {}
|
|
1162
|
+
# end
|
|
1163
|
+
# table_data << row_data
|
|
1164
|
+
# end
|
|
1165
|
+
# max_term_width += (term_padding[1] + term_padding[3])
|
|
1166
|
+
# term_column_width = [max_term_width, bounds.width * 0.5].min
|
|
1167
|
+
# table table_data, position: :left, cell_style: { border_width: 0 }, column_widths: [term_column_width] do
|
|
1168
|
+
# @pdf.layout_table_caption node if node.title?
|
|
1169
|
+
# end
|
|
1170
|
+
# margin_bottom (@theme.prose_margin_bottom || 0) * 0.5
|
|
1171
|
+
# when 'qanda'
|
|
1172
|
+
# @list_numerals << '1'
|
|
1173
|
+
# convert_outline_list node
|
|
1174
|
+
# @list_numerals.pop
|
|
1175
|
+
# else
|
|
1176
|
+
# # TODO: check if we're within one line of the bottom of the page
|
|
1177
|
+
# # and advance to the next page if so (similar to logic for section titles)
|
|
1178
|
+
# layout_caption node.title, category: :description_list if node.title?
|
|
1179
|
+
|
|
1180
|
+
# term_line_height = @theme.description_list_term_line_height || @theme.base_line_height
|
|
1181
|
+
# line_metrics = theme_font(:description_list_term) { calc_line_metrics term_line_height }
|
|
1182
|
+
# node.items.each do |terms, desc|
|
|
1183
|
+
# # NOTE: don't orphan the terms (keep together terms and at least one line of content)
|
|
1184
|
+
# allocate_space_for_list_item line_metrics, (terms.size + 1), ((@theme.description_list_term_spacing || 0) + 0.05)
|
|
1185
|
+
# theme_font :description_list_term do
|
|
1186
|
+
# if (term_font_styles = font_styles).empty?
|
|
1187
|
+
# term_font_styles = nil
|
|
1188
|
+
# end
|
|
1189
|
+
# terms.each do |term|
|
|
1190
|
+
# # QUESTION should we pass down styles in other calls to layout_prose
|
|
1191
|
+
# layout_prose term.text, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left, line_height: term_line_height, normalize_line_height: true, styles: term_font_styles
|
|
1192
|
+
# end
|
|
1193
|
+
# end
|
|
1194
|
+
# indent(@theme.description_list_description_indent || 0) do
|
|
1195
|
+
# traverse_list_item desc, :dlist_desc, normalize_line_height: true
|
|
1196
|
+
# end if desc
|
|
1197
|
+
# end
|
|
1198
|
+
# end
|
|
1199
|
+
# end
|
|
1200
|
+
|
|
1201
|
+
# def convert_olist node
|
|
1202
|
+
# add_dest_for_block node if node.id
|
|
1203
|
+
# # TODO: move list_numeral resolve to a method
|
|
1204
|
+
# case node.style
|
|
1205
|
+
# when 'arabic'
|
|
1206
|
+
# list_numeral = 1
|
|
1207
|
+
# when 'decimal'
|
|
1208
|
+
# list_numeral = '01'
|
|
1209
|
+
# when 'loweralpha'
|
|
1210
|
+
# list_numeral = 'a'
|
|
1211
|
+
# when 'upperalpha'
|
|
1212
|
+
# list_numeral = 'A'
|
|
1213
|
+
# when 'lowerroman'
|
|
1214
|
+
# list_numeral = RomanNumeral.new 'i'
|
|
1215
|
+
# when 'upperroman'
|
|
1216
|
+
# list_numeral = RomanNumeral.new 'I'
|
|
1217
|
+
# when 'lowergreek'
|
|
1218
|
+
# list_numeral = LowercaseGreekA
|
|
1219
|
+
# when 'unstyled', 'unnumbered', 'no-bullet'
|
|
1220
|
+
# list_numeral = nil
|
|
1221
|
+
# when 'none'
|
|
1222
|
+
# list_numeral = ''
|
|
1223
|
+
# else
|
|
1224
|
+
# list_numeral = 1
|
|
1225
|
+
# end
|
|
1226
|
+
# if list_numeral && list_numeral != '' &&
|
|
1227
|
+
# (start = (node.attr 'start', nil, false) || ((node.option? 'reversed') ? node.items.size : nil))
|
|
1228
|
+
# if (start = start.to_i) > 1
|
|
1229
|
+
# (start - 1).times { list_numeral = list_numeral.next }
|
|
1230
|
+
# elsif start < 1 && !(::String === list_numeral)
|
|
1231
|
+
# (start - 1).abs.times { list_numeral = list_numeral.pred }
|
|
1232
|
+
# end
|
|
1233
|
+
# end
|
|
1234
|
+
# @list_numerals << list_numeral
|
|
1235
|
+
# convert_outline_list node
|
|
1236
|
+
# @list_numerals.pop
|
|
1237
|
+
# end
|
|
1238
|
+
|
|
1239
|
+
# def convert_ulist node
|
|
1240
|
+
# add_dest_for_block node if node.id
|
|
1241
|
+
# # TODO: move bullet_type to method on List (or helper method)
|
|
1242
|
+
# if node.option? 'checklist'
|
|
1243
|
+
# @list_bullets << :checkbox
|
|
1244
|
+
# else
|
|
1245
|
+
# if (style = node.style)
|
|
1246
|
+
# case style
|
|
1247
|
+
# when 'bibliography'
|
|
1248
|
+
# bullet_type = :square
|
|
1249
|
+
# when 'unstyled', 'no-bullet'
|
|
1250
|
+
# bullet_type = nil
|
|
1251
|
+
# else
|
|
1252
|
+
# candidate = style.to_sym
|
|
1253
|
+
# if Bullets.key? candidate
|
|
1254
|
+
# bullet_type = candidate
|
|
1255
|
+
# else
|
|
1256
|
+
# logger.warn %(unknown unordered list style: #{candidate}) unless scratch?
|
|
1257
|
+
# bullet_type = :disc
|
|
1258
|
+
# end
|
|
1259
|
+
# end
|
|
1260
|
+
# else
|
|
1261
|
+
# case node.outline_level
|
|
1262
|
+
# when 1
|
|
1263
|
+
# bullet_type = :disc
|
|
1264
|
+
# when 2
|
|
1265
|
+
# bullet_type = :circle
|
|
1266
|
+
# else
|
|
1267
|
+
# bullet_type = :square
|
|
1268
|
+
# end
|
|
1269
|
+
# end
|
|
1270
|
+
# @list_bullets << bullet_type
|
|
1271
|
+
# end
|
|
1272
|
+
# convert_outline_list node
|
|
1273
|
+
# @list_bullets.pop
|
|
1274
|
+
# end
|
|
1275
|
+
|
|
1276
|
+
# def convert_outline_list node
|
|
1277
|
+
# # TODO: check if we're within one line of the bottom of the page
|
|
1278
|
+
# # and advance to the next page if so (similar to logic for section titles)
|
|
1279
|
+
# layout_caption node.title, category: :outline_list if node.title?
|
|
1280
|
+
|
|
1281
|
+
# opts = {}
|
|
1282
|
+
# if (align = resolve_alignment_from_role node.roles)
|
|
1283
|
+
# opts[:align] = align
|
|
1284
|
+
# elsif node.style == 'bibliography'
|
|
1285
|
+
# opts[:align] = :left
|
|
1286
|
+
# elsif (align = @theme.outline_list_text_align)
|
|
1287
|
+
# # NOTE: theme setting only affects alignment of list text (not nested blocks)
|
|
1288
|
+
# opts[:align] = align.to_sym
|
|
1289
|
+
# end
|
|
1290
|
+
|
|
1291
|
+
# line_metrics = calc_line_metrics @theme.base_line_height
|
|
1292
|
+
# complex = false
|
|
1293
|
+
# # ...or if we want to give all items in the list the same treatment
|
|
1294
|
+
# #complex = node.items.find(&:compound?) ? true : false
|
|
1295
|
+
# if (node.context == :ulist && !@list_bullets[-1]) || (node.context == :olist && !@list_numerals[-1])
|
|
1296
|
+
# if node.style == 'unstyled'
|
|
1297
|
+
# # unstyled takes away all indentation
|
|
1298
|
+
# list_indent = 0
|
|
1299
|
+
# elsif (list_indent = @theme.outline_list_indent || 0) > 0
|
|
1300
|
+
# # no-bullet aligns text with left-hand side of bullet position (as though there's no bullet)
|
|
1301
|
+
# list_indent = [list_indent - (rendered_width_of_string %(#{node.context == :ulist ? ?\u2022 : '1.'}x)), 0].max
|
|
1302
|
+
# end
|
|
1303
|
+
# else
|
|
1304
|
+
# list_indent = @theme.outline_list_indent || 0
|
|
1305
|
+
# end
|
|
1306
|
+
# indent list_indent do
|
|
1307
|
+
# node.items.each do |item|
|
|
1308
|
+
# allocate_space_for_list_item line_metrics
|
|
1309
|
+
# convert_outline_list_item item, node, opts
|
|
1310
|
+
# end
|
|
1311
|
+
# end
|
|
1312
|
+
# # NOTE: Children will provide the necessary bottom margin if last item is complex.
|
|
1313
|
+
# # However, don't leave gap at the bottom if list is nested in an outline list
|
|
1314
|
+
# unless complex || (node.nested? && node.parent.parent.outline?)
|
|
1315
|
+
# # correct bottom margin of last item
|
|
1316
|
+
# margin_bottom((@theme.prose_margin_bottom || 0) - (@theme.outline_list_item_spacing || 0))
|
|
1317
|
+
# end
|
|
1318
|
+
# end
|
|
1319
|
+
|
|
1320
|
+
# def convert_outline_list_item node, list, opts = {}
|
|
1321
|
+
# # TODO: move this to a draw_bullet (or draw_marker) method
|
|
1322
|
+
# marker_style = {}
|
|
1323
|
+
# marker_style[:font_color] = @theme.outline_list_marker_font_color || @font_color
|
|
1324
|
+
# marker_style[:font_family] = font_family
|
|
1325
|
+
# marker_style[:font_size] = font_size
|
|
1326
|
+
# marker_style[:line_height] = @theme.base_line_height
|
|
1327
|
+
# case (list_type = list.context)
|
|
1328
|
+
# when :dlist
|
|
1329
|
+
# # NOTE: list.style is 'qanda'
|
|
1330
|
+
# complex = node[1]&.compound?
|
|
1331
|
+
# @list_numerals << (index = @list_numerals.pop).next
|
|
1332
|
+
# marker = %(#{index}.)
|
|
1333
|
+
# when :olist
|
|
1334
|
+
# complex = node.compound?
|
|
1335
|
+
# if (index = @list_numerals.pop)
|
|
1336
|
+
# if index == ''
|
|
1337
|
+
# marker = ''
|
|
1338
|
+
# else
|
|
1339
|
+
# marker = %(#{index}.)
|
|
1340
|
+
# dir = (node.parent.option? 'reversed') ? :pred : :next
|
|
1341
|
+
# @list_numerals << (index.public_send dir)
|
|
1342
|
+
# end
|
|
1343
|
+
# end
|
|
1344
|
+
# else # :ulist
|
|
1345
|
+
# complex = node.compound?
|
|
1346
|
+
# if (marker_type = @list_bullets[-1])
|
|
1347
|
+
# if marker_type == :checkbox
|
|
1348
|
+
# # QUESTION should we remove marker indent if not a checkbox?
|
|
1349
|
+
# if node.attr? 'checkbox', nil, false
|
|
1350
|
+
# marker_type = (node.attr? 'checked', nil, false) ? :checked : :unchecked
|
|
1351
|
+
# marker = @theme[%(ulist_marker_#{marker_type}_content)] || BallotBox[marker_type]
|
|
1352
|
+
# end
|
|
1353
|
+
# else
|
|
1354
|
+
# marker = @theme[%(ulist_marker_#{marker_type}_content)] || Bullets[marker_type]
|
|
1355
|
+
# end
|
|
1356
|
+
# [:font_color, :font_family, :font_size, :line_height].each do |prop|
|
|
1357
|
+
# marker_style[prop] = @theme[%(ulist_marker_#{marker_type}_#{prop})] || @theme[%(ulist_marker_#{prop})] || marker_style[prop]
|
|
1358
|
+
# end if marker
|
|
1359
|
+
# end
|
|
1360
|
+
# end
|
|
1361
|
+
|
|
1362
|
+
# if marker
|
|
1363
|
+
# if marker_style[:font_family] == 'fa'
|
|
1364
|
+
# logger.info 'deprecated fa icon set found in theme; use fas, far, or fab instead' unless scratch?
|
|
1365
|
+
# marker_style[:font_family] = FontAwesomeIconSets.find {|candidate| (icon_font_data candidate).yaml[candidate].value? marker } || 'fas'
|
|
1366
|
+
# end
|
|
1367
|
+
# marker_gap = rendered_width_of_char 'x'
|
|
1368
|
+
# font marker_style[:font_family], size: marker_style[:font_size] do
|
|
1369
|
+
# marker_width = rendered_width_of_string marker
|
|
1370
|
+
# marker_height = height_of_typeset_text marker, line_height: marker_style[:line_height], single_line: true
|
|
1371
|
+
# start_position = -marker_width + -marker_gap
|
|
1372
|
+
# float do
|
|
1373
|
+
# start_new_page if @media == 'prepress' && cursor < marker_height
|
|
1374
|
+
# flow_bounding_box start_position, width: marker_width do
|
|
1375
|
+
# layout_prose marker,
|
|
1376
|
+
# align: :right,
|
|
1377
|
+
# character_spacing: -0.5,
|
|
1378
|
+
# color: marker_style[:font_color],
|
|
1379
|
+
# inline_format: false,
|
|
1380
|
+
# line_height: marker_style[:line_height],
|
|
1381
|
+
# margin: 0,
|
|
1382
|
+
# normalize: false,
|
|
1383
|
+
# single_line: true
|
|
1384
|
+
# end
|
|
1385
|
+
# end
|
|
1386
|
+
# end
|
|
1387
|
+
# end
|
|
1388
|
+
|
|
1389
|
+
# if complex
|
|
1390
|
+
# traverse_list_item node, list_type, (opts.merge normalize_line_height: true)
|
|
1391
|
+
# else
|
|
1392
|
+
# traverse_list_item node, list_type, (opts.merge margin_bottom: @theme.outline_list_item_spacing, normalize_line_height: true)
|
|
1393
|
+
# end
|
|
1394
|
+
# end
|
|
1395
|
+
|
|
1396
|
+
# def traverse_list_item node, list_type, opts = {}
|
|
1397
|
+
# if list_type == :dlist # qanda
|
|
1398
|
+
# terms, desc = node
|
|
1399
|
+
# terms.each {|term| layout_prose %(<em>#{term.text}</em>), (opts.merge margin_top: 0, margin_bottom: @theme.description_list_term_spacing) }
|
|
1400
|
+
# if desc
|
|
1401
|
+
# layout_prose desc.text, (opts.merge hyphenate: true) if desc.text?
|
|
1402
|
+
# traverse desc
|
|
1403
|
+
# end
|
|
1404
|
+
# else
|
|
1405
|
+
# if (primary_text = node.text).nil_or_empty?
|
|
1406
|
+
# layout_prose DummyText, opts unless node.blocks?
|
|
1407
|
+
# else
|
|
1408
|
+
# layout_prose primary_text, (opts.merge hyphenate: true)
|
|
1409
|
+
# end
|
|
1410
|
+
# traverse node
|
|
1411
|
+
# end
|
|
1412
|
+
# end
|
|
1413
|
+
|
|
1414
|
+
# def allocate_space_for_list_item line_metrics, number = 1, additional_gap = 0
|
|
1415
|
+
# advance_page if !at_page_top? && cursor < (line_metrics.height + line_metrics.leading + line_metrics.padding_top + additional_gap) * number
|
|
1416
|
+
# end
|
|
1417
|
+
|
|
1418
|
+
# def convert_image node, opts = {}
|
|
1419
|
+
# node.extend ::Asciidoctor::Image unless ::Asciidoctor::Image === node
|
|
1420
|
+
# target, image_format = node.target_and_format
|
|
1421
|
+
|
|
1422
|
+
# if image_format == 'gif' && !(defined? ::GMagick::Image)
|
|
1423
|
+
# logger.warn %(GIF image format not supported. Install the prawn-gmagick gem or convert #{target} to PNG.) unless scratch?
|
|
1424
|
+
# image_path = nil
|
|
1425
|
+
# elsif ::Base64 === target
|
|
1426
|
+
# image_path = target
|
|
1427
|
+
# elsif (image_path = resolve_image_path node, target, image_format, (opts.fetch :relative_to_imagesdir, true))
|
|
1428
|
+
# if image_format == 'pdf'
|
|
1429
|
+
# if ::File.readable? image_path
|
|
1430
|
+
# if (id = node.id)
|
|
1431
|
+
# add_dest_block = proc do
|
|
1432
|
+
# node.set_attr 'pdf-destination', (node_dest = dest_top)
|
|
1433
|
+
# add_dest id, node_dest
|
|
1434
|
+
# end
|
|
1435
|
+
# end
|
|
1436
|
+
# # NOTE: import_page automatically advances to next page afterwards
|
|
1437
|
+
# # QUESTION should we add destination to top of imported page?
|
|
1438
|
+
# if (pgnums = node.attr 'pages', nil, false)
|
|
1439
|
+
# (resolve_pagenums pgnums).each_with_index do |pgnum, idx|
|
|
1440
|
+
# if idx == 0
|
|
1441
|
+
# import_page image_path, page: pgnum, replace: page.empty?, &add_dest_block
|
|
1442
|
+
# else
|
|
1443
|
+
# import_page image_path, page: pgnum, replace: true
|
|
1444
|
+
# end
|
|
1445
|
+
# end
|
|
1446
|
+
# else
|
|
1447
|
+
# import_page image_path, page: [(node.attr 'page', nil, 1).to_i, 1].max, replace: page.empty?, &add_dest_block
|
|
1448
|
+
# end
|
|
1449
|
+
# return
|
|
1450
|
+
# else
|
|
1451
|
+
# logger.warn %(pdf to insert not found or not readable: #{image_path}) unless scratch?
|
|
1452
|
+
# image_path = nil
|
|
1453
|
+
# end
|
|
1454
|
+
# elsif !(::File.readable? image_path)
|
|
1455
|
+
# logger.warn %(image to embed not found or not readable: #{image_path}) unless scratch?
|
|
1456
|
+
# image_path = nil
|
|
1457
|
+
# end
|
|
1458
|
+
# end
|
|
1459
|
+
|
|
1460
|
+
# theme_margin :block, :top unless (pinned = opts[:pinned])
|
|
1461
|
+
|
|
1462
|
+
# return on_image_error :missing, node, target, opts unless image_path
|
|
1463
|
+
|
|
1464
|
+
# alignment = ((node.attr 'align', nil, false) || (resolve_alignment_from_role node.roles) || @theme.image_align || :left).to_sym
|
|
1465
|
+
# # TODO: support cover (aka canvas) image layout using "canvas" (or "cover") role
|
|
1466
|
+
# width = resolve_explicit_width node.attributes, bounds_width: (available_w = bounds.width), support_vw: true, use_fallback: true, constrain_to_bounds: true
|
|
1467
|
+
# # TODO: add `to_pt page_width` method to ViewportWidth type
|
|
1468
|
+
# width = (width.to_f / 100) * page_width if ViewportWidth === width
|
|
1469
|
+
|
|
1470
|
+
# # NOTE: if width is not set explicitly and max-width is fit-content, caption height may not be accurate
|
|
1471
|
+
# caption_h = node.title? ? (layout_caption node, category: :image, side: :bottom, block_align: alignment, block_width: width, max_width: @theme.image_caption_max_width, dry_run: true) : 0
|
|
1472
|
+
|
|
1473
|
+
# align_to_page = node.option? 'align-to-page'
|
|
1474
|
+
|
|
1475
|
+
# begin
|
|
1476
|
+
# rendered_w = nil
|
|
1477
|
+
# span_page_width_if align_to_page do
|
|
1478
|
+
# if image_format == 'svg'
|
|
1479
|
+
# if ::Base64 === image_path
|
|
1480
|
+
# svg_data = ::Base64.decode64 image_path
|
|
1481
|
+
# file_request_root = false
|
|
1482
|
+
# else
|
|
1483
|
+
# svg_data = ::File.read image_path, mode: 'r:UTF-8'
|
|
1484
|
+
# file_request_root = ::File.dirname image_path
|
|
1485
|
+
# end
|
|
1486
|
+
# svg_obj = ::Prawn::SVG::Interface.new svg_data, self,
|
|
1487
|
+
# position: alignment,
|
|
1488
|
+
# width: width,
|
|
1489
|
+
# fallback_font_name: fallback_svg_font_name,
|
|
1490
|
+
# enable_web_requests: allow_uri_read ? (method :load_open_uri).to_proc : false,
|
|
1491
|
+
# enable_file_requests_with_root: file_request_root,
|
|
1492
|
+
# cache_images: cache_uri
|
|
1493
|
+
# rendered_w = (svg_size = svg_obj.document.sizing).output_width
|
|
1494
|
+
# if !width && (svg_obj.document.root.attributes.key? 'width')
|
|
1495
|
+
# # NOTE: scale native width & height from px to pt and restrict width to available width
|
|
1496
|
+
# if (adjusted_w = [available_w, (to_pt rendered_w, :px)].min) != rendered_w
|
|
1497
|
+
# svg_size = svg_obj.resize width: (rendered_w = adjusted_w)
|
|
1498
|
+
# end
|
|
1499
|
+
# end
|
|
1500
|
+
# # NOTE: shrink image so it fits within available space; group image & caption
|
|
1501
|
+
# if (rendered_h = svg_size.output_height) > (available_h = cursor - caption_h)
|
|
1502
|
+
# unless pinned || at_page_top?
|
|
1503
|
+
# advance_page
|
|
1504
|
+
# available_h = cursor - caption_h
|
|
1505
|
+
# end
|
|
1506
|
+
# rendered_w = (svg_obj.resize height: (rendered_h = available_h)).output_width if rendered_h > available_h
|
|
1507
|
+
# end
|
|
1508
|
+
# image_y = y
|
|
1509
|
+
# image_cursor = cursor
|
|
1510
|
+
# add_dest_for_block node if node.id
|
|
1511
|
+
# # NOTE: workaround to fix Prawn not adding fill and stroke commands on page that only has an image;
|
|
1512
|
+
# # breakage occurs when running content (stamps) are added to page
|
|
1513
|
+
# # seems to be resolved as of Prawn 2.2.2
|
|
1514
|
+
# #update_colors if graphic_state.color_space.empty?
|
|
1515
|
+
# # 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)
|
|
1516
|
+
# # NOTE: cursor advances automatically
|
|
1517
|
+
# svg_obj.draw
|
|
1518
|
+
# svg_obj.document.warnings.each do |img_warning|
|
|
1519
|
+
# logger.warn %(problem encountered in image: #{image_path}; #{img_warning})
|
|
1520
|
+
# end unless scratch?
|
|
1521
|
+
# draw_image_border image_cursor, rendered_w, rendered_h, alignment unless node.role? && (node.has_role? 'noborder')
|
|
1522
|
+
# if (link = node.attr 'link', nil, false)
|
|
1523
|
+
# add_link_to_image link, { width: rendered_w, height: rendered_h }, position: alignment, y: image_y
|
|
1524
|
+
# end
|
|
1525
|
+
# else
|
|
1526
|
+
# # FIXME: this code really needs to be better organized!
|
|
1527
|
+
# # NOTE: use low-level API to access intrinsic dimensions; build_image_object caches image data previously loaded
|
|
1528
|
+
# image_obj, image_info = ::Base64 === image_path ?
|
|
1529
|
+
# ::StringIO.open((::Base64.decode64 image_path), 'rb') {|fd| build_image_object fd } :
|
|
1530
|
+
# ::File.open(image_path, 'rb') {|fd| build_image_object fd }
|
|
1531
|
+
# # NOTE: if width is not specified, scale native width & height from px to pt and restrict width to available width
|
|
1532
|
+
# rendered_w, rendered_h = image_info.calc_image_dimensions width: (width || [available_w, (to_pt image_info.width, :px)].min)
|
|
1533
|
+
# # NOTE: shrink image so it fits within available space; group image & caption
|
|
1534
|
+
# if rendered_h > (available_h = cursor - caption_h)
|
|
1535
|
+
# unless pinned || at_page_top?
|
|
1536
|
+
# advance_page
|
|
1537
|
+
# available_h = cursor - caption_h
|
|
1538
|
+
# end
|
|
1539
|
+
# rendered_w, rendered_h = image_info.calc_image_dimensions height: available_h if rendered_h > available_h
|
|
1540
|
+
# end
|
|
1541
|
+
# image_y = y
|
|
1542
|
+
# image_cursor = cursor
|
|
1543
|
+
# add_dest_for_block node if node.id
|
|
1544
|
+
# # NOTE: workaround to fix Prawn not adding fill and stroke commands on page that only has an image;
|
|
1545
|
+
# # breakage occurs when running content (stamps) are added to page
|
|
1546
|
+
# # seems to be resolved as of Prawn 2.2.2
|
|
1547
|
+
# #update_colors if graphic_state.color_space.empty?
|
|
1548
|
+
# # NOTE: specify both width and height to avoid recalculation
|
|
1549
|
+
# embed_image image_obj, image_info, width: rendered_w, height: rendered_h, position: alignment
|
|
1550
|
+
# draw_image_border image_cursor, rendered_w, rendered_h, alignment unless node.role? && (node.has_role? 'noborder')
|
|
1551
|
+
# if (link = node.attr 'link', nil, false)
|
|
1552
|
+
# add_link_to_image link, { width: rendered_w, height: rendered_h }, position: alignment, y: image_y
|
|
1553
|
+
# end
|
|
1554
|
+
# # NOTE: Asciidoctor disables automatic advancement of cursor for raster images, so move cursor manually
|
|
1555
|
+
# move_down rendered_h if y == image_y
|
|
1556
|
+
# end
|
|
1557
|
+
# end
|
|
1558
|
+
# layout_caption node, category: :image, side: :bottom, block_align: alignment, block_width: rendered_w, max_width: @theme.image_caption_max_width if node.title?
|
|
1559
|
+
# theme_margin :block, :bottom unless pinned
|
|
1560
|
+
# rescue
|
|
1561
|
+
# on_image_error :exception, node, target, (opts.merge message: %(could not embed image: #{image_path}; #{$!.message}#{::Prawn::Errors::UnsupportedImageType === $! && !(defined? ::GMagick::Image) ? '; install prawn-gmagick gem to add support' : ''}))
|
|
1562
|
+
# end
|
|
1563
|
+
# end
|
|
1564
|
+
|
|
1565
|
+
# def draw_image_border top, w, h, alignment
|
|
1566
|
+
# if (@theme.image_border_width || 0) > 0 && @theme.image_border_color
|
|
1567
|
+
# if (@theme.image_border_fit || 'content') == 'auto'
|
|
1568
|
+
# bb_width = bounds.width
|
|
1569
|
+
# elsif alignment == :center
|
|
1570
|
+
# bb_x = (bounds.width - w) * 0.5
|
|
1571
|
+
# elsif alignment == :right
|
|
1572
|
+
# bb_x = bounds.width - w
|
|
1573
|
+
# end
|
|
1574
|
+
# bounding_box [(bb_x || 0), top], width: (bb_width || w), height: h, position: alignment do
|
|
1575
|
+
# theme_fill_and_stroke_bounds :image, background_color: nil
|
|
1576
|
+
# end
|
|
1577
|
+
# true
|
|
1578
|
+
# end
|
|
1579
|
+
# end
|
|
1580
|
+
|
|
1581
|
+
# def on_image_error _reason, node, target, opts = {}
|
|
1582
|
+
# logger.warn opts[:message] if (opts.key? :message) && !scratch?
|
|
1583
|
+
# alt_text_vars = { alt: (node.attr 'alt'), target: target }
|
|
1584
|
+
# alt_text_template = @theme.image_alt_content || '%{link}[%{alt}]%{/link} | <em>%{target}</em>'
|
|
1585
|
+
# return if alt_text_template.empty?
|
|
1586
|
+
# if (link = node.attr 'link', nil, false)
|
|
1587
|
+
# alt_text_vars[:link] = %(<a href="#{link}">)
|
|
1588
|
+
# alt_text_vars[:'/link'] = '</a>'
|
|
1589
|
+
# else
|
|
1590
|
+
# alt_text_vars[:link] = ''
|
|
1591
|
+
# alt_text_vars[:'/link'] = ''
|
|
1592
|
+
# end
|
|
1593
|
+
# theme_font :image_alt do
|
|
1594
|
+
# layout_prose alt_text_template % alt_text_vars,
|
|
1595
|
+
# align: ((node.attr 'align', nil, false) || @theme.image_align).to_sym,
|
|
1596
|
+
# margin: 0,
|
|
1597
|
+
# normalize: false,
|
|
1598
|
+
# single_line: true
|
|
1599
|
+
# end
|
|
1600
|
+
# layout_caption node, category: :image, side: :bottom if node.title?
|
|
1601
|
+
# theme_margin :block, :bottom unless opts[:pinned]
|
|
1602
|
+
# nil
|
|
1603
|
+
# end
|
|
1604
|
+
|
|
1605
|
+
# def convert_audio node
|
|
1606
|
+
# add_dest_for_block node if node.id
|
|
1607
|
+
# theme_margin :block, :top
|
|
1608
|
+
# audio_path = node.media_uri node.attr 'target'
|
|
1609
|
+
# play_symbol = (node.document.attr? 'icons', 'font') ? %(<font name="fas">#{(icon_font_data 'fas').unicode 'play'}</font>) : RightPointer
|
|
1610
|
+
# layout_prose %(#{play_symbol}#{NoBreakSpace}<a href="#{audio_path}">#{audio_path}</a> <em>(audio)</em>), normalize: false, margin: 0, single_line: true
|
|
1611
|
+
# layout_caption node, side: :bottom if node.title?
|
|
1612
|
+
# theme_margin :block, :bottom
|
|
1613
|
+
# end
|
|
1614
|
+
|
|
1615
|
+
# def convert_video node
|
|
1616
|
+
# case (poster = node.attr 'poster', nil, false)
|
|
1617
|
+
# when 'youtube'
|
|
1618
|
+
# video_path = %(https://www.youtube.com/watch?v=#{video_id = node.attr 'target'})
|
|
1619
|
+
# # see http://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
|
|
1620
|
+
# poster = allow_uri_read ? %(https://img.youtube.com/vi/#{video_id}/maxresdefault.jpg) : nil
|
|
1621
|
+
# type = 'YouTube video'
|
|
1622
|
+
# when 'vimeo'
|
|
1623
|
+
# video_path = %(https://vimeo.com/#{video_id = node.attr 'target'})
|
|
1624
|
+
# if allow_uri_read
|
|
1625
|
+
# poster = load_open_uri.open_uri %(http://vimeo.com/api/v2/video/#{video_id}.xml), 'r' do |f|
|
|
1626
|
+
# VimeoThumbnailRx =~ f.read && $1
|
|
1627
|
+
# end
|
|
1628
|
+
# end
|
|
1629
|
+
# type = 'Vimeo video'
|
|
1630
|
+
# else
|
|
1631
|
+
# video_path = node.media_uri node.attr 'target'
|
|
1632
|
+
# type = 'video'
|
|
1633
|
+
# end
|
|
1634
|
+
|
|
1635
|
+
# if poster.nil_or_empty?
|
|
1636
|
+
# add_dest_for_block node if node.id
|
|
1637
|
+
# theme_margin :block, :top
|
|
1638
|
+
# play_symbol = (node.document.attr? 'icons', 'font') ? %(<font name="fas">#{(icon_font_data 'fas').unicode 'play'}</font>) : RightPointer
|
|
1639
|
+
# layout_prose %(#{play_symbol}#{NoBreakSpace}<a href="#{video_path}">#{video_path}</a> <em>(#{type})</em>), normalize: false, margin: 0, single_line: true
|
|
1640
|
+
# layout_caption node, side: :bottom if node.title?
|
|
1641
|
+
# theme_margin :block, :bottom
|
|
1642
|
+
# else
|
|
1643
|
+
# original_attributes = node.attributes.dup
|
|
1644
|
+
# begin
|
|
1645
|
+
# node.update_attributes 'target' => poster, 'link' => video_path
|
|
1646
|
+
# #node.set_attr 'pdfwidth', '100%' unless (node.attr? 'width') || (node.attr? 'pdfwidth')
|
|
1647
|
+
# convert_image node
|
|
1648
|
+
# ensure
|
|
1649
|
+
# node.attributes.replace original_attributes
|
|
1650
|
+
# end
|
|
1651
|
+
# end
|
|
1652
|
+
# end
|
|
1653
|
+
|
|
1654
|
+
# # QUESTION can we avoid arranging fragments multiple times (conums & autofit) by eagerly preparing arranger?
|
|
1655
|
+
# def convert_listing_or_literal node
|
|
1656
|
+
# add_dest_for_block node if node.id
|
|
1657
|
+
# wrap_ext = source_chunks = bg_color_override = font_color_override = adjusted_font_size = nil
|
|
1658
|
+
# theme_font :code do
|
|
1659
|
+
# # HACK: disable built-in syntax highlighter; must be done before calling node.content!
|
|
1660
|
+
# if node.style == 'source' && (highlighter = (syntax_hl = node.document.syntax_highlighter) && syntax_hl.highlight? && syntax_hl.name)
|
|
1661
|
+
# case highlighter
|
|
1662
|
+
# when 'coderay'
|
|
1663
|
+
# unless defined? ::Asciidoctor::Prawn::CodeRayEncoder
|
|
1664
|
+
# highlighter = nil if (Helpers.require_library CodeRayRequirePath, 'coderay', :warn).nil?
|
|
1665
|
+
# end
|
|
1666
|
+
# when 'pygments'
|
|
1667
|
+
# unless defined? ::Pygments::Ext::BlockStyles
|
|
1668
|
+
# highlighter = nil if (Helpers.require_library PygmentsRequirePath, 'pygments.rb', :warn).nil?
|
|
1669
|
+
# end
|
|
1670
|
+
# when 'rouge'
|
|
1671
|
+
# unless defined? ::Rouge::Formatters::Prawn
|
|
1672
|
+
# highlighter = nil if (Helpers.require_library RougeRequirePath, 'rouge', :warn).nil?
|
|
1673
|
+
# end
|
|
1674
|
+
# end
|
|
1675
|
+
# prev_subs = (subs = node.subs).dup
|
|
1676
|
+
# # NOTE: the highlight sub is only set for coderay, rouge, and pygments atm
|
|
1677
|
+
# highlight_idx = subs.index :highlight
|
|
1678
|
+
# # NOTE: scratch? here only applies if listing block is nested inside another block
|
|
1679
|
+
# if !highlighter || scratch?
|
|
1680
|
+
# highlighter = nil
|
|
1681
|
+
# if highlight_idx
|
|
1682
|
+
# # switch the :highlight sub back to :specialcharacters
|
|
1683
|
+
# subs[highlight_idx] = :specialcharacters
|
|
1684
|
+
# else
|
|
1685
|
+
# prev_subs = nil
|
|
1686
|
+
# end
|
|
1687
|
+
# source_string = guard_indentation node.content
|
|
1688
|
+
# else
|
|
1689
|
+
# # NOTE: the source highlighter logic below handles the callouts and highlight subs
|
|
1690
|
+
# if highlight_idx
|
|
1691
|
+
# subs.delete_all :highlight, :callouts
|
|
1692
|
+
# else
|
|
1693
|
+
# subs.delete_all :specialcharacters, :callouts
|
|
1694
|
+
# end
|
|
1695
|
+
# # NOTE: indentation guards will be added by the source highlighter logic
|
|
1696
|
+
# source_string = expand_tabs node.content
|
|
1697
|
+
# end
|
|
1698
|
+
# else
|
|
1699
|
+
# highlighter = nil
|
|
1700
|
+
# source_string = guard_indentation node.content
|
|
1701
|
+
# end
|
|
1702
|
+
|
|
1703
|
+
# case highlighter
|
|
1704
|
+
# when 'coderay'
|
|
1705
|
+
# source_string, conum_mapping = extract_conums source_string
|
|
1706
|
+
# srclang = node.attr 'language', 'text', false
|
|
1707
|
+
# begin
|
|
1708
|
+
# ::CodeRay::Scanners[(srclang = (srclang.start_with? 'html+') ? (srclang.slice 5, srclang.length).to_sym : srclang.to_sym)]
|
|
1709
|
+
# rescue ::ArgumentError
|
|
1710
|
+
# srclang = :text
|
|
1711
|
+
# end
|
|
1712
|
+
# fragments = (::CodeRay.scan source_string, srclang).to_prawn
|
|
1713
|
+
# source_chunks = conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
|
|
1714
|
+
# when 'pygments'
|
|
1715
|
+
# style = (node.document.attr 'pygments-style') || 'pastie'
|
|
1716
|
+
# # QUESTION allow border color to be set by theme for highlighted block?
|
|
1717
|
+
# pg_block_styles = ::Pygments::Ext::BlockStyles.for style
|
|
1718
|
+
# bg_color_override = pg_block_styles[:background_color]
|
|
1719
|
+
# font_color_override = pg_block_styles[:font_color]
|
|
1720
|
+
# if source_string.empty?
|
|
1721
|
+
# source_chunks = []
|
|
1722
|
+
# else
|
|
1723
|
+
# lexer = (::Pygments::Lexer.find_by_alias node.attr 'language', 'text', false) || (::Pygments::Lexer.find_by_mimetype 'text/plain')
|
|
1724
|
+
# lexer_opts = { nowrap: true, noclasses: true, stripnl: false, style: style }
|
|
1725
|
+
# lexer_opts[:startinline] = !(node.option? 'mixed') if lexer.name == 'PHP'
|
|
1726
|
+
# source_string, conum_mapping = extract_conums source_string
|
|
1727
|
+
# # NOTE: highlight can return nil if something goes wrong; fallback to encoded source string if this happens
|
|
1728
|
+
# result = (lexer.highlight source_string, options: lexer_opts) || (node.apply_subs source_string, [:specialcharacters])
|
|
1729
|
+
# if node.attr? 'highlight', nil, false
|
|
1730
|
+
# if (highlight_lines = (node.method :resolve_lines_to_highlight).arity > 1 ?
|
|
1731
|
+
# (node.resolve_lines_to_highlight source_string, (node.attr 'highlight')) :
|
|
1732
|
+
# (node.resolve_lines_to_highlight node.attr 'highlight')).empty?
|
|
1733
|
+
# highlight_lines = nil
|
|
1734
|
+
# else
|
|
1735
|
+
# pg_highlight_bg_color = pg_block_styles[:highlight_background_color]
|
|
1736
|
+
# highlight_lines = highlight_lines.map {|linenum| [linenum, pg_highlight_bg_color] }.to_h
|
|
1737
|
+
# end
|
|
1738
|
+
# end
|
|
1739
|
+
# if node.attr? 'linenums'
|
|
1740
|
+
# linenums = (node.attr 'start', 1, false).to_i
|
|
1741
|
+
# @theme.code_linenum_font_color ||= '999999'
|
|
1742
|
+
# postprocess = true
|
|
1743
|
+
# wrap_ext = FormattedText::SourceWrap
|
|
1744
|
+
# elsif conum_mapping || highlight_lines
|
|
1745
|
+
# postprocess = true
|
|
1746
|
+
# end
|
|
1747
|
+
# fragments = text_formatter.format result
|
|
1748
|
+
# fragments = restore_conums fragments, conum_mapping, linenums, highlight_lines if postprocess
|
|
1749
|
+
# source_chunks = guard_indentation_in_fragments fragments
|
|
1750
|
+
# end
|
|
1751
|
+
# when 'rouge'
|
|
1752
|
+
# formatter = (@rouge_formatter ||= ::Rouge::Formatters::Prawn.new theme: (node.document.attr 'rouge-style'), line_gap: @theme.code_line_gap, highlight_background_color: @theme.code_highlight_background_color)
|
|
1753
|
+
# # QUESTION allow border color to be set by theme for highlighted block?
|
|
1754
|
+
# bg_color_override = formatter.background_color
|
|
1755
|
+
# if source_string.empty?
|
|
1756
|
+
# source_chunks = []
|
|
1757
|
+
# else
|
|
1758
|
+
# if node.attr? 'linenums'
|
|
1759
|
+
# formatter_opts = { line_numbers: true, start_line: (node.attr 'start', 1, false).to_i }
|
|
1760
|
+
# wrap_ext = FormattedText::SourceWrap
|
|
1761
|
+
# else
|
|
1762
|
+
# formatter_opts = {}
|
|
1763
|
+
# end
|
|
1764
|
+
# if (srclang = node.attr 'language', nil, false)
|
|
1765
|
+
# if srclang.include? '?'
|
|
1766
|
+
# if (lexer = ::Rouge::Lexer.find_fancy srclang)
|
|
1767
|
+
# unless lexer.tag != 'php' || (node.option? 'mixed') || ((lexer_opts = lexer.options).key? 'start_inline')
|
|
1768
|
+
# lexer = lexer.class.new lexer_opts.merge 'start_inline' => true
|
|
1769
|
+
# end
|
|
1770
|
+
# end
|
|
1771
|
+
# elsif (lexer = ::Rouge::Lexer.find srclang)
|
|
1772
|
+
# lexer = lexer.new start_inline: true if lexer.tag == 'php' && !(node.option? 'mixed')
|
|
1773
|
+
# end
|
|
1774
|
+
# end
|
|
1775
|
+
# lexer ||= ::Rouge::Lexers::PlainText
|
|
1776
|
+
# source_string, conum_mapping = extract_conums source_string
|
|
1777
|
+
# if node.attr? 'highlight', nil, false
|
|
1778
|
+
# unless (hl_lines = (node.method :resolve_lines_to_highlight).arity > 1 ?
|
|
1779
|
+
# (node.resolve_lines_to_highlight source_string, (node.attr 'highlight')) :
|
|
1780
|
+
# (node.resolve_lines_to_highlight node.attr 'highlight')).empty?
|
|
1781
|
+
# formatter_opts[:highlight_lines] = hl_lines.map {|linenum| [linenum, true] }.to_h
|
|
1782
|
+
# end
|
|
1783
|
+
# end
|
|
1784
|
+
# fragments = formatter.format (lexer.lex source_string), formatter_opts
|
|
1785
|
+
# source_chunks = conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
|
|
1786
|
+
# end
|
|
1787
|
+
# else
|
|
1788
|
+
# # NOTE: only format if we detect a need (callouts or inline formatting)
|
|
1789
|
+
# source_chunks = (XMLMarkupRx.match? source_string) ? (text_formatter.format source_string) : [text: source_string]
|
|
1790
|
+
# end
|
|
1791
|
+
# node.subs.replace prev_subs if prev_subs
|
|
1792
|
+
# adjusted_font_size = ((node.option? 'autofit') || (node.document.attr? 'autofit-option')) ? (compute_autofit_font_size source_chunks, :code) : nil
|
|
1793
|
+
# end
|
|
1794
|
+
|
|
1795
|
+
# theme_margin :block, :top
|
|
1796
|
+
|
|
1797
|
+
# keep_together do |box_height = nil|
|
|
1798
|
+
# caption_height = node.title? ? (layout_caption node, category: :code) : 0
|
|
1799
|
+
# theme_font :code do
|
|
1800
|
+
# theme_fill_and_stroke_block :code, (box_height - caption_height), background_color: bg_color_override, split_from_top: false if box_height
|
|
1801
|
+
# pad_box @theme.code_padding do
|
|
1802
|
+
# ::Prawn::Text::Formatted::Box.extensions << wrap_ext if wrap_ext
|
|
1803
|
+
# typeset_formatted_text source_chunks, (calc_line_metrics @theme.code_line_height || @theme.base_line_height),
|
|
1804
|
+
# color: (font_color_override || @theme.code_font_color || @font_color),
|
|
1805
|
+
# size: adjusted_font_size
|
|
1806
|
+
# ::Prawn::Text::Formatted::Box.extensions.pop if wrap_ext
|
|
1807
|
+
# end
|
|
1808
|
+
# end
|
|
1809
|
+
# end
|
|
1810
|
+
|
|
1811
|
+
# stroke_horizontal_rule @theme.caption_border_bottom_color if node.title? && @theme.caption_border_bottom_color
|
|
1812
|
+
|
|
1813
|
+
# theme_margin :block, :bottom
|
|
1814
|
+
# end
|
|
1815
|
+
|
|
1816
|
+
# alias convert_listing convert_listing_or_literal
|
|
1817
|
+
# alias convert_literal convert_listing_or_literal
|
|
1818
|
+
|
|
1819
|
+
# def convert_pass node
|
|
1820
|
+
# node = node.dup
|
|
1821
|
+
# (subs = node.subs.dup).unshift :specialcharacters
|
|
1822
|
+
# node.instance_variable_set :@subs, subs.uniq
|
|
1823
|
+
# convert_listing_or_literal node
|
|
1824
|
+
# end
|
|
1825
|
+
|
|
1826
|
+
# alias convert_stem convert_listing_or_literal
|
|
1827
|
+
|
|
1828
|
+
# # Extract callout marks from string, indexed by 0-based line number
|
|
1829
|
+
# # Return an Array with the processed string as the first argument
|
|
1830
|
+
# # and the mapping of lines to conums as the second.
|
|
1831
|
+
# def extract_conums string
|
|
1832
|
+
# conum_mapping = {}
|
|
1833
|
+
# auto_num = 0
|
|
1834
|
+
# string = string.split(LF).map.with_index {|line, line_num|
|
|
1835
|
+
# # FIXME: we get extra spaces before numbers if more than one on a line
|
|
1836
|
+
# if line.include? '<'
|
|
1837
|
+
# line = line.gsub CalloutExtractRx do
|
|
1838
|
+
# # honor the escape
|
|
1839
|
+
# if $1 == ?\\
|
|
1840
|
+
# $&.sub $1, ''
|
|
1841
|
+
# else
|
|
1842
|
+
# (conum_mapping[line_num] ||= []) << ($3 == '.' ? (auto_num += 1) : $3.to_i)
|
|
1843
|
+
# ''
|
|
1844
|
+
# end
|
|
1845
|
+
# end
|
|
1846
|
+
# # NOTE use first position to store space that precedes conums
|
|
1847
|
+
# if (conum_mapping.key? line_num) && (line.end_with? ' ')
|
|
1848
|
+
# trimmed_line = line.rstrip
|
|
1849
|
+
# conum_mapping[line_num].unshift line.slice trimmed_line.length, line.length
|
|
1850
|
+
# line = trimmed_line
|
|
1851
|
+
# end
|
|
1852
|
+
# end
|
|
1853
|
+
# line
|
|
1854
|
+
# }.join LF
|
|
1855
|
+
# conum_mapping = nil if conum_mapping.empty?
|
|
1856
|
+
# [string, conum_mapping]
|
|
1857
|
+
# end
|
|
1858
|
+
|
|
1859
|
+
# # Restore the conums into the Array of formatted text fragments
|
|
1860
|
+
# #--
|
|
1861
|
+
# # QUESTION can this be done more efficiently?
|
|
1862
|
+
# # QUESTION can we reuse arrange_fragments_by_line?
|
|
1863
|
+
# def restore_conums fragments, conum_mapping, linenums = nil, highlight_lines = nil
|
|
1864
|
+
# lines = []
|
|
1865
|
+
# line_num = 0
|
|
1866
|
+
# # reorganize the fragments into an array of lines
|
|
1867
|
+
# fragments.each do |fragment|
|
|
1868
|
+
# line = (lines[line_num] ||= [])
|
|
1869
|
+
# if (text = fragment[:text]) == LF
|
|
1870
|
+
# lines[line_num += 1] ||= []
|
|
1871
|
+
# elsif text.include? LF
|
|
1872
|
+
# text.split(LF, -1).each_with_index do |line_in_fragment, idx|
|
|
1873
|
+
# line = (lines[line_num += 1] ||= []) unless idx == 0
|
|
1874
|
+
# line << (fragment.merge text: line_in_fragment) unless line_in_fragment.empty?
|
|
1875
|
+
# end
|
|
1876
|
+
# else
|
|
1877
|
+
# line << fragment
|
|
1878
|
+
# end
|
|
1879
|
+
# end
|
|
1880
|
+
# conum_font_color = @theme.conum_font_color
|
|
1881
|
+
# if (conum_font_name = @theme.conum_font_family) == font_name
|
|
1882
|
+
# conum_font_name = nil
|
|
1883
|
+
# end
|
|
1884
|
+
# last_line_num = lines.size - 1
|
|
1885
|
+
# if linenums
|
|
1886
|
+
# pad_size = (last_line_num + 1).to_s.length
|
|
1887
|
+
# linenum_color = @theme.code_linenum_font_color
|
|
1888
|
+
# end
|
|
1889
|
+
# # append conums to appropriate lines, then flatten to an array of fragments
|
|
1890
|
+
# lines.flat_map.with_index do |line, cur_line_num|
|
|
1891
|
+
# last_line = cur_line_num == last_line_num
|
|
1892
|
+
# visible_line_num = cur_line_num + (linenums || 1)
|
|
1893
|
+
# if highlight_lines && (highlight_bg_color = highlight_lines[visible_line_num])
|
|
1894
|
+
# line.unshift text: DummyText, background_color: highlight_bg_color, highlight: true, inline_block: true, extend: true, width: 0, callback: [FormattedText::TextBackgroundAndBorderRenderer]
|
|
1895
|
+
# end
|
|
1896
|
+
# line.unshift text: %(#{visible_line_num.to_s.rjust pad_size} ), linenum: visible_line_num, color: linenum_color if linenums
|
|
1897
|
+
# if conum_mapping && (conums = conum_mapping.delete cur_line_num)
|
|
1898
|
+
# line << { text: conums.shift } if ::String === conums[0]
|
|
1899
|
+
# conum_text = conums.map {|num| conum_glyph num }.join ' '
|
|
1900
|
+
# conum_fragment = { text: conum_text }
|
|
1901
|
+
# conum_fragment[:color] = conum_font_color if conum_font_color
|
|
1902
|
+
# conum_fragment[:font] = conum_font_name if conum_font_name
|
|
1903
|
+
# line << conum_fragment
|
|
1904
|
+
# end
|
|
1905
|
+
# line << { text: LF } unless last_line
|
|
1906
|
+
# line
|
|
1907
|
+
# end
|
|
1908
|
+
# end
|
|
1909
|
+
|
|
1910
|
+
# def conum_glyph number
|
|
1911
|
+
# @conum_glyphs[number - 1]
|
|
1912
|
+
# end
|
|
1913
|
+
|
|
1914
|
+
# def convert_table node
|
|
1915
|
+
# add_dest_for_block node if node.id
|
|
1916
|
+
# # TODO: we could skip a lot of the logic below when num_rows == 0
|
|
1917
|
+
# num_rows = node.attr 'rowcount'
|
|
1918
|
+
# num_cols = node.columns.size
|
|
1919
|
+
# table_header_size = false
|
|
1920
|
+
# theme = @theme
|
|
1921
|
+
|
|
1922
|
+
# tbl_bg_color = resolve_theme_color :table_background_color
|
|
1923
|
+
# # QUESTION should we fallback to page background color? (which is never transparent)
|
|
1924
|
+
# #tbl_bg_color = resolve_theme_color :table_background_color, @page_bg_color
|
|
1925
|
+
# # ...and if so, should we try to be helpful and use @page_bg_color for tables nested in blocks?
|
|
1926
|
+
# #unless tbl_bg_color
|
|
1927
|
+
# # tbl_bg_color = @page_bg_color unless [:section, :document].include? node.parent.context
|
|
1928
|
+
# #end
|
|
1929
|
+
|
|
1930
|
+
# # NOTE: emulate table bg color by using it as a fallback value for each element
|
|
1931
|
+
# head_bg_color = resolve_theme_color :table_head_background_color, tbl_bg_color
|
|
1932
|
+
# foot_bg_color = resolve_theme_color :table_foot_background_color, tbl_bg_color
|
|
1933
|
+
# body_bg_color = resolve_theme_color :table_body_background_color, tbl_bg_color
|
|
1934
|
+
# body_stripe_bg_color = resolve_theme_color :table_body_stripe_background_color, tbl_bg_color
|
|
1935
|
+
|
|
1936
|
+
# base_header_cell_data = nil
|
|
1937
|
+
# header_cell_line_metrics = nil
|
|
1938
|
+
|
|
1939
|
+
# table_data = []
|
|
1940
|
+
# theme_font :table do
|
|
1941
|
+
# head_rows = node.rows[:head]
|
|
1942
|
+
# body_rows = node.rows[:body]
|
|
1943
|
+
# #if (hrows = node.attr 'hrows', false, nil) && (shift_rows = hrows.to_i - head_rows.size) > 0
|
|
1944
|
+
# # head_rows = head_rows.dup
|
|
1945
|
+
# # body_rows = body_rows.dup
|
|
1946
|
+
# # shift_rows.times { head_rows << body_rows.shift unless body_rows.empty? }
|
|
1947
|
+
# #end
|
|
1948
|
+
# theme_font :table_head do
|
|
1949
|
+
# table_header_size = head_rows.size
|
|
1950
|
+
# head_font_info = font_info
|
|
1951
|
+
# head_line_metrics = calc_line_metrics theme.base_line_height
|
|
1952
|
+
# head_cell_padding = theme.table_head_cell_padding || theme.table_cell_padding
|
|
1953
|
+
# head_cell_padding = ::Array === head_cell_padding && head_cell_padding.size == 4 ? head_cell_padding.dup : (expand_padding_value head_cell_padding)
|
|
1954
|
+
# head_cell_padding[0] += head_line_metrics.padding_top
|
|
1955
|
+
# head_cell_padding[2] += head_line_metrics.padding_bottom
|
|
1956
|
+
# # QUESTION why doesn't text transform inherit from table?
|
|
1957
|
+
# head_transform = resolve_text_transform :table_head_text_transform, nil
|
|
1958
|
+
# base_cell_data = {
|
|
1959
|
+
# inline_format: [normalize: true],
|
|
1960
|
+
# background_color: head_bg_color,
|
|
1961
|
+
# text_color: @font_color,
|
|
1962
|
+
# size: head_font_info[:size],
|
|
1963
|
+
# font: head_font_info[:family],
|
|
1964
|
+
# font_style: head_font_info[:style],
|
|
1965
|
+
# kerning: default_kerning?,
|
|
1966
|
+
# padding: head_cell_padding,
|
|
1967
|
+
# leading: head_line_metrics.leading,
|
|
1968
|
+
# # TODO: patch prawn-table to pass through final_gap option
|
|
1969
|
+
# #final_gap: head_line_metrics.final_gap,
|
|
1970
|
+
# }
|
|
1971
|
+
# head_rows.each do |row|
|
|
1972
|
+
# table_data << (row.map do |cell|
|
|
1973
|
+
# cell_text = head_transform ? (transform_text cell.text.strip, head_transform) : cell.text.strip
|
|
1974
|
+
# cell_text = hyphenate_text cell_text, @hyphenator if defined? @hyphenator
|
|
1975
|
+
# base_cell_data.merge \
|
|
1976
|
+
# content: cell_text,
|
|
1977
|
+
# colspan: cell.colspan || 1,
|
|
1978
|
+
# align: (cell.attr 'halign', nil, false).to_sym,
|
|
1979
|
+
# valign: (val = cell.attr 'valign', nil, false) == 'middle' ? :center : val.to_sym
|
|
1980
|
+
# end)
|
|
1981
|
+
# end
|
|
1982
|
+
# end unless head_rows.empty?
|
|
1983
|
+
|
|
1984
|
+
# base_cell_data = {
|
|
1985
|
+
# font: (body_font_info = font_info)[:family],
|
|
1986
|
+
# font_style: body_font_info[:style],
|
|
1987
|
+
# size: body_font_info[:size],
|
|
1988
|
+
# kerning: default_kerning?,
|
|
1989
|
+
# text_color: @font_color,
|
|
1990
|
+
# }
|
|
1991
|
+
# body_cell_line_metrics = calc_line_metrics theme.base_line_height
|
|
1992
|
+
# (body_rows + node.rows[:foot]).each do |row|
|
|
1993
|
+
# table_data << (row.map do |cell|
|
|
1994
|
+
# cell_data = base_cell_data.merge \
|
|
1995
|
+
# colspan: cell.colspan || 1,
|
|
1996
|
+
# rowspan: cell.rowspan || 1,
|
|
1997
|
+
# align: (cell.attr 'halign', nil, false).to_sym,
|
|
1998
|
+
# valign: (val = cell.attr 'valign', nil, false) == 'middle' ? :center : val.to_sym
|
|
1999
|
+
# cell_line_metrics = body_cell_line_metrics
|
|
2000
|
+
# case cell.style
|
|
2001
|
+
# when :emphasis
|
|
2002
|
+
# cell_data[:font_style] = :italic
|
|
2003
|
+
# when :strong
|
|
2004
|
+
# cell_data[:font_style] = :bold
|
|
2005
|
+
# when :header
|
|
2006
|
+
# unless base_header_cell_data
|
|
2007
|
+
# theme_font :table_head do
|
|
2008
|
+
# theme_font :table_header_cell do
|
|
2009
|
+
# header_cell_font_info = font_info
|
|
2010
|
+
# base_header_cell_data = {
|
|
2011
|
+
# text_color: @font_color,
|
|
2012
|
+
# font: header_cell_font_info[:family],
|
|
2013
|
+
# size: header_cell_font_info[:size],
|
|
2014
|
+
# font_style: header_cell_font_info[:style],
|
|
2015
|
+
# text_transform: @text_transform,
|
|
2016
|
+
# }
|
|
2017
|
+
# header_cell_line_metrics = calc_line_metrics theme.base_line_height
|
|
2018
|
+
# end
|
|
2019
|
+
# end
|
|
2020
|
+
# if (val = resolve_theme_color :table_header_cell_background_color, head_bg_color)
|
|
2021
|
+
# base_header_cell_data[:background_color] = val
|
|
2022
|
+
# end
|
|
2023
|
+
# end
|
|
2024
|
+
# cell_data.update base_header_cell_data
|
|
2025
|
+
# cell_transform = cell_data.delete :text_transform
|
|
2026
|
+
# cell_line_metrics = header_cell_line_metrics
|
|
2027
|
+
# when :monospaced
|
|
2028
|
+
# cell_data.delete :font_style
|
|
2029
|
+
# theme_font :literal do
|
|
2030
|
+
# mono_cell_font_info = font_info
|
|
2031
|
+
# cell_data[:font] = mono_cell_font_info[:family]
|
|
2032
|
+
# cell_data[:size] = mono_cell_font_info[:size]
|
|
2033
|
+
# cell_data[:text_color] = @font_color
|
|
2034
|
+
# cell_line_metrics = calc_line_metrics theme.base_line_height
|
|
2035
|
+
# end
|
|
2036
|
+
# when :literal
|
|
2037
|
+
# # NOTE: we want the raw AsciiDoc in this case
|
|
2038
|
+
# cell_data[:content] = guard_indentation cell.instance_variable_get :@text
|
|
2039
|
+
# # NOTE: the absence of the inline_format option implies it's disabled
|
|
2040
|
+
# cell_data.delete :font_style
|
|
2041
|
+
# # QUESTION should we use literal_font_*, code_font_*, or introduce another category?
|
|
2042
|
+
# theme_font :code do
|
|
2043
|
+
# literal_cell_font_info = font_info
|
|
2044
|
+
# cell_data[:font] = literal_cell_font_info[:family]
|
|
2045
|
+
# cell_data[:size] = literal_cell_font_info[:size]
|
|
2046
|
+
# cell_data[:text_color] = @font_color
|
|
2047
|
+
# cell_line_metrics = calc_line_metrics theme.base_line_height
|
|
2048
|
+
# end
|
|
2049
|
+
# when :asciidoc
|
|
2050
|
+
# cell_data.delete :kerning
|
|
2051
|
+
# cell_data.delete :font_style
|
|
2052
|
+
# cell_line_metrics = nil
|
|
2053
|
+
# asciidoc_cell = ::Prawn::Table::Cell::AsciiDoc.new self,
|
|
2054
|
+
# (cell_data.merge content: cell.inner_document, font_style: (val = theme.table_font_style) ? val.to_sym : nil, padding: theme.table_cell_padding)
|
|
2055
|
+
# cell_data = { content: asciidoc_cell }
|
|
2056
|
+
# end
|
|
2057
|
+
# if cell_line_metrics
|
|
2058
|
+
# cell_padding = ::Array === (cell_padding = theme.table_cell_padding) && cell_padding.size == 4 ? cell_padding.dup : (expand_padding_value cell_padding)
|
|
2059
|
+
# cell_padding[0] += cell_line_metrics.padding_top
|
|
2060
|
+
# cell_padding[2] += cell_line_metrics.padding_bottom
|
|
2061
|
+
# cell_data[:leading] = cell_line_metrics.leading
|
|
2062
|
+
# # TODO: patch prawn-table to pass through final_gap option
|
|
2063
|
+
# #cell_data[:final_gap] = cell_line_metrics.final_gap
|
|
2064
|
+
# cell_data[:padding] = cell_padding
|
|
2065
|
+
# end
|
|
2066
|
+
# unless cell_data.key? :content
|
|
2067
|
+
# cell_text = cell.text.strip
|
|
2068
|
+
# cell_text = transform_text cell_text, cell_transform if cell_transform
|
|
2069
|
+
# cell_text = hyphenate_text cell_text, @hyphenator if defined? @hyphenator
|
|
2070
|
+
# cell_text = cell_text.gsub CjkLineBreakRx, ZeroWidthSpace if @cjk_line_breaks
|
|
2071
|
+
# if cell_text.include? DoubleLF
|
|
2072
|
+
# # FIXME: hard breaks not quite the same result as separate paragraphs; need custom cell impl here
|
|
2073
|
+
# cell_data[:content] = (cell_text.split BlankLineRx).map {|l| l.tr_s WhitespaceChars, ' ' }.join DoubleLF
|
|
2074
|
+
# cell_data[:inline_format] = true
|
|
2075
|
+
# else
|
|
2076
|
+
# cell_data[:content] = cell_text
|
|
2077
|
+
# cell_data[:inline_format] = [normalize: true]
|
|
2078
|
+
# end
|
|
2079
|
+
# end
|
|
2080
|
+
# if node.document.attr? 'cellbgcolor'
|
|
2081
|
+
# if (cell_bg_color = node.document.attr 'cellbgcolor') == 'transparent'
|
|
2082
|
+
# cell_data[:background_color] = body_bg_color
|
|
2083
|
+
# elsif (cell_bg_color.start_with? '#') && (HexColorRx.match? cell_bg_color)
|
|
2084
|
+
# cell_data[:background_color] = cell_bg_color.slice 1, cell_bg_color.length
|
|
2085
|
+
# end
|
|
2086
|
+
# end
|
|
2087
|
+
# cell_data
|
|
2088
|
+
# end)
|
|
2089
|
+
# end
|
|
2090
|
+
# end
|
|
2091
|
+
|
|
2092
|
+
# # NOTE: Prawn aborts if table data is empty, so ensure there's at least one row
|
|
2093
|
+
# if table_data.empty?
|
|
2094
|
+
# logger.warn message_with_context 'no rows found in table', source_location: node.source_location
|
|
2095
|
+
# table_data << ::Array.new([node.columns.size, 1].max) { { content: '' } }
|
|
2096
|
+
# end
|
|
2097
|
+
|
|
2098
|
+
# border_width = {}
|
|
2099
|
+
# table_border_color = theme.table_border_color || theme.table_grid_color || theme.base_border_color
|
|
2100
|
+
# table_border_style = (theme.table_border_style || :solid).to_sym
|
|
2101
|
+
# table_border_width = theme.table_border_width
|
|
2102
|
+
# if table_header_size
|
|
2103
|
+
# head_border_bottom_color = theme.table_head_border_bottom_color || table_border_color
|
|
2104
|
+
# head_border_bottom_style = (theme.table_head_border_bottom_style || table_border_style).to_sym
|
|
2105
|
+
# head_border_bottom_width = theme.table_head_border_bottom_width || table_border_width
|
|
2106
|
+
# end
|
|
2107
|
+
# [:top, :bottom, :left, :right].each {|edge| border_width[edge] = table_border_width }
|
|
2108
|
+
# table_grid_color = theme.table_grid_color || table_border_color
|
|
2109
|
+
# table_grid_style = (theme.table_grid_style || table_border_style).to_sym
|
|
2110
|
+
# table_grid_width = theme.table_grid_width || theme.table_border_width
|
|
2111
|
+
# [:cols, :rows].each {|edge| border_width[edge] = table_grid_width }
|
|
2112
|
+
|
|
2113
|
+
# case (grid = node.attr 'grid', 'all', 'table-grid')
|
|
2114
|
+
# when 'all'
|
|
2115
|
+
# # keep inner borders
|
|
2116
|
+
# when 'cols'
|
|
2117
|
+
# border_width[:rows] = 0
|
|
2118
|
+
# when 'rows'
|
|
2119
|
+
# border_width[:cols] = 0
|
|
2120
|
+
# else # none
|
|
2121
|
+
# border_width[:rows] = border_width[:cols] = 0
|
|
2122
|
+
# end
|
|
2123
|
+
|
|
2124
|
+
# case (frame = node.attr 'frame', 'all', 'table-frame')
|
|
2125
|
+
# when 'all'
|
|
2126
|
+
# # keep outer borders
|
|
2127
|
+
# when 'topbot', 'ends'
|
|
2128
|
+
# border_width[:left] = border_width[:right] = 0
|
|
2129
|
+
# when 'sides'
|
|
2130
|
+
# border_width[:top] = border_width[:bottom] = 0
|
|
2131
|
+
# else # none
|
|
2132
|
+
# border_width[:top] = border_width[:right] = border_width[:bottom] = border_width[:left] = 0
|
|
2133
|
+
# end
|
|
2134
|
+
|
|
2135
|
+
# if node.option? 'autowidth'
|
|
2136
|
+
# table_width = (node.attr? 'width', nil, false) ? bounds.width * ((node.attr 'tablepcwidth') / 100.0) :
|
|
2137
|
+
# (((node.has_role? 'stretch') || (node.has_role? 'spread')) ? bounds.width : nil)
|
|
2138
|
+
# column_widths = []
|
|
2139
|
+
# else
|
|
2140
|
+
# table_width = bounds.width * ((node.attr 'tablepcwidth') / 100.0)
|
|
2141
|
+
# column_widths = node.columns.map {|col| ((col.attr 'colpcwidth') * table_width) / 100.0 }
|
|
2142
|
+
# end
|
|
2143
|
+
|
|
2144
|
+
# if ((alignment = node.attr 'align', nil, false) && (BlockAlignmentNames.include? alignment)) ||
|
|
2145
|
+
# (alignment = (node.roles & BlockAlignmentNames)[-1])
|
|
2146
|
+
# alignment = alignment.to_sym
|
|
2147
|
+
# else
|
|
2148
|
+
# alignment = (theme.table_align || :left).to_sym
|
|
2149
|
+
# end
|
|
2150
|
+
|
|
2151
|
+
# caption_side = (theme.table_caption_side || :top).to_sym
|
|
2152
|
+
# caption_max_width = theme.table_caption_max_width || 'fit-content'
|
|
2153
|
+
|
|
2154
|
+
# table_settings = {
|
|
2155
|
+
# header: table_header_size,
|
|
2156
|
+
# # NOTE: position is handled by this method
|
|
2157
|
+
# position: :left,
|
|
2158
|
+
# cell_style: {
|
|
2159
|
+
# # NOTE: the border color and style of the outer frame is set later
|
|
2160
|
+
# border_color: table_grid_color,
|
|
2161
|
+
# border_lines: [table_grid_style],
|
|
2162
|
+
# # NOTE: the border width is set later
|
|
2163
|
+
# border_width: 0,
|
|
2164
|
+
# },
|
|
2165
|
+
# width: table_width,
|
|
2166
|
+
# column_widths: column_widths,
|
|
2167
|
+
# }
|
|
2168
|
+
|
|
2169
|
+
# # QUESTION should we support nth; should we support sequence of roles?
|
|
2170
|
+
# case node.attr 'stripes', nil, 'table-stripes'
|
|
2171
|
+
# when 'all'
|
|
2172
|
+
# table_settings[:row_colors] = [body_stripe_bg_color]
|
|
2173
|
+
# when 'even'
|
|
2174
|
+
# table_settings[:row_colors] = [body_bg_color, body_stripe_bg_color]
|
|
2175
|
+
# when 'odd'
|
|
2176
|
+
# table_settings[:row_colors] = [body_stripe_bg_color, body_bg_color]
|
|
2177
|
+
# else # none
|
|
2178
|
+
# table_settings[:row_colors] = [body_bg_color]
|
|
2179
|
+
# end
|
|
2180
|
+
|
|
2181
|
+
# theme_margin :block, :top
|
|
2182
|
+
|
|
2183
|
+
# left_padding = right_padding = nil
|
|
2184
|
+
# table table_data, table_settings do
|
|
2185
|
+
# # NOTE: call width to capture resolved table width
|
|
2186
|
+
# table_width = width
|
|
2187
|
+
# @pdf.layout_table_caption node, alignment, table_width, caption_max_width if node.title? && caption_side == :top
|
|
2188
|
+
# # NOTE align using padding instead of bounding_box as prawn-table does
|
|
2189
|
+
# # using a bounding_box across pages mangles the margin box of subsequent pages
|
|
2190
|
+
# if alignment != :left && table_width != (this_bounds = @pdf.bounds).width
|
|
2191
|
+
# if alignment == :center
|
|
2192
|
+
# left_padding = right_padding = (this_bounds.width - width) * 0.5
|
|
2193
|
+
# this_bounds.add_left_padding left_padding
|
|
2194
|
+
# this_bounds.add_right_padding right_padding
|
|
2195
|
+
# else # :right
|
|
2196
|
+
# left_padding = this_bounds.width - width
|
|
2197
|
+
# this_bounds.add_left_padding left_padding
|
|
2198
|
+
# end
|
|
2199
|
+
# end
|
|
2200
|
+
# if grid == 'none' && frame == 'none'
|
|
2201
|
+
# rows(table_header_size).tap do |r|
|
|
2202
|
+
# r.border_bottom_color = head_border_bottom_color
|
|
2203
|
+
# r.border_bottom_line = head_border_bottom_style
|
|
2204
|
+
# r.border_bottom_width = head_border_bottom_width
|
|
2205
|
+
# end if table_header_size
|
|
2206
|
+
# else
|
|
2207
|
+
# # apply the grid setting first across all cells
|
|
2208
|
+
# cells.border_width = [border_width[:rows], border_width[:cols], border_width[:rows], border_width[:cols]]
|
|
2209
|
+
|
|
2210
|
+
# if table_header_size
|
|
2211
|
+
# rows(table_header_size - 1).tap do |r|
|
|
2212
|
+
# r.border_bottom_color = head_border_bottom_color
|
|
2213
|
+
# r.border_bottom_line = head_border_bottom_style
|
|
2214
|
+
# r.border_bottom_width = head_border_bottom_width
|
|
2215
|
+
# end
|
|
2216
|
+
# rows(table_header_size).tap do |r|
|
|
2217
|
+
# r.border_top_color = head_border_bottom_color
|
|
2218
|
+
# r.border_top_line = head_border_bottom_style
|
|
2219
|
+
# r.border_top_width = head_border_bottom_width
|
|
2220
|
+
# end if num_rows > table_header_size
|
|
2221
|
+
# end
|
|
2222
|
+
|
|
2223
|
+
# # top edge of table
|
|
2224
|
+
# rows(0).tap do |r|
|
|
2225
|
+
# r.border_top_color, r.border_top_line, r.border_top_width = table_border_color, table_border_style, border_width[:top]
|
|
2226
|
+
# end
|
|
2227
|
+
# # right edge of table
|
|
2228
|
+
# columns(num_cols - 1).tap do |r|
|
|
2229
|
+
# r.border_right_color, r.border_right_line, r.border_right_width = table_border_color, table_border_style, border_width[:right]
|
|
2230
|
+
# end
|
|
2231
|
+
# # bottom edge of table
|
|
2232
|
+
# rows(num_rows - 1).tap do |r|
|
|
2233
|
+
# r.border_bottom_color, r.border_bottom_line, r.border_bottom_width = table_border_color, table_border_style, border_width[:bottom]
|
|
2234
|
+
# end
|
|
2235
|
+
# # left edge of table
|
|
2236
|
+
# columns(0).tap do |r|
|
|
2237
|
+
# r.border_left_color, r.border_left_line, r.border_left_width = table_border_color, table_border_style, border_width[:left]
|
|
2238
|
+
# end
|
|
2239
|
+
# end
|
|
2240
|
+
|
|
2241
|
+
# # QUESTION should cell padding be configurable for foot row cells?
|
|
2242
|
+
# unless node.rows[:foot].empty?
|
|
2243
|
+
# foot_row = row num_rows.pred
|
|
2244
|
+
# foot_row.background_color = foot_bg_color
|
|
2245
|
+
# # FIXME: find a way to do this when defining the cells
|
|
2246
|
+
# foot_row.text_color = theme.table_foot_font_color if theme.table_foot_font_color
|
|
2247
|
+
# foot_row.size = theme.table_foot_font_size if theme.table_foot_font_size
|
|
2248
|
+
# foot_row.font = theme.table_foot_font_family if theme.table_foot_font_family
|
|
2249
|
+
# foot_row.font_style = theme.table_foot_font_style.to_sym if theme.table_foot_font_style
|
|
2250
|
+
# # HACK: we should do this transformation when creating the cell
|
|
2251
|
+
# #if (foot_transform = resolve_text_transform :table_foot_text_transform, nil)
|
|
2252
|
+
# # foot_row.each {|c| c.content = (transform_text c.content, foot_transform) if c.content }
|
|
2253
|
+
# #end
|
|
2254
|
+
# end
|
|
2255
|
+
# end
|
|
2256
|
+
# if left_padding
|
|
2257
|
+
# bounds.subtract_left_padding left_padding
|
|
2258
|
+
# bounds.subtract_right_padding right_padding if right_padding
|
|
2259
|
+
# end
|
|
2260
|
+
# layout_table_caption node, alignment, table_width, caption_max_width, caption_side if node.title? && caption_side == :bottom
|
|
2261
|
+
# theme_margin :block, :bottom
|
|
2262
|
+
# end
|
|
2263
|
+
|
|
2264
|
+
# def convert_thematic_break _node
|
|
2265
|
+
# theme_margin :thematic_break, :top
|
|
2266
|
+
# stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width, line_style: (@theme.thematic_break_border_style || :solid).to_sym
|
|
2267
|
+
# theme_margin :thematic_break, :bottom
|
|
2268
|
+
# end
|
|
2269
|
+
|
|
2270
|
+
# def convert_toc node
|
|
2271
|
+
# if ((doc = node.document).attr? 'toc-placement', 'macro') && doc.sections?
|
|
2272
|
+
# if (is_book = doc.doctype == 'book')
|
|
2273
|
+
# start_new_page unless at_page_top?
|
|
2274
|
+
# start_new_page if @ppbook && verso_page? && !(node.option? 'nonfacing')
|
|
2275
|
+
# end
|
|
2276
|
+
# add_dest_for_block node, (node.id || 'toc')
|
|
2277
|
+
# allocate_toc doc, (doc.attr 'toclevels', 2).to_i, @y, (is_book || (doc.attr? 'title-page'))
|
|
2278
|
+
# @disable_running_content[:header] = (@disable_running_content[:header] || ::Set.new) + @toc_extent[:page_nums] if node.option? 'noheader'
|
|
2279
|
+
# @disable_running_content[:footer] = (@disable_running_content[:footer] || ::Set.new) + @toc_extent[:page_nums] if node.option? 'nofooter'
|
|
2280
|
+
# end
|
|
2281
|
+
# nil
|
|
2282
|
+
# end
|
|
2283
|
+
|
|
2284
|
+
# # NOTE to insert sequential page breaks, you must put {nbsp} between page breaks
|
|
2285
|
+
# def convert_page_break node
|
|
2286
|
+
# if (page_layout = node.attr 'page-layout').nil_or_empty?
|
|
2287
|
+
# unless node.role? && (page_layout = (node.roles.map(&:to_sym) & PageLayouts)[-1])
|
|
2288
|
+
# page_layout = nil
|
|
2289
|
+
# end
|
|
2290
|
+
# elsif !PageLayouts.include?(page_layout = page_layout.to_sym)
|
|
2291
|
+
# page_layout = nil
|
|
2292
|
+
# end
|
|
2293
|
+
|
|
2294
|
+
# if at_page_top?
|
|
2295
|
+
# if page_layout && page_layout != page.layout && page.empty?
|
|
2296
|
+
# delete_page
|
|
2297
|
+
# advance_page layout: page_layout
|
|
2298
|
+
# end
|
|
2299
|
+
# elsif page_layout
|
|
2300
|
+
# advance_page layout: page_layout
|
|
2301
|
+
# else
|
|
2302
|
+
# advance_page
|
|
2303
|
+
# end
|
|
2304
|
+
# end
|
|
2305
|
+
|
|
2306
|
+
# def convert_index_section _node
|
|
2307
|
+
# space_needed_for_category = @theme.description_list_term_spacing + (2 * (height_of_typeset_text 'A'))
|
|
2308
|
+
# column_box [0, cursor], columns: 2, width: bounds.width, reflow_margins: true do
|
|
2309
|
+
# @index.categories.each do |category|
|
|
2310
|
+
# # NOTE cursor method always returns 0 inside column_box; breaks reference_bounds.move_past_bottom
|
|
2311
|
+
# bounds.move_past_bottom if space_needed_for_category > y - reference_bounds.absolute_bottom
|
|
2312
|
+
# layout_prose category.name,
|
|
2313
|
+
# align: :left,
|
|
2314
|
+
# inline_format: false,
|
|
2315
|
+
# margin_top: 0,
|
|
2316
|
+
# margin_bottom: @theme.description_list_term_spacing,
|
|
2317
|
+
# style: @theme.description_list_term_font_style.to_sym
|
|
2318
|
+
# category.terms.each do |term|
|
|
2319
|
+
# convert_index_list_item term
|
|
2320
|
+
# end
|
|
2321
|
+
# if @theme.prose_margin_bottom > y - reference_bounds.absolute_bottom
|
|
2322
|
+
# bounds.move_past_bottom
|
|
2323
|
+
# else
|
|
2324
|
+
# move_down @theme.prose_margin_bottom
|
|
2325
|
+
# end
|
|
2326
|
+
# end
|
|
2327
|
+
# end
|
|
2328
|
+
# nil
|
|
2329
|
+
# end
|
|
2330
|
+
|
|
2331
|
+
# def convert_index_list_item term
|
|
2332
|
+
# text = escape_xml term.name
|
|
2333
|
+
# unless term.container?
|
|
2334
|
+
# if @media == 'screen'
|
|
2335
|
+
# pagenums = term.dests.map {|dest| %(<a anchor="#{dest[:anchor]}">#{dest[:page]}</a>) }
|
|
2336
|
+
# else
|
|
2337
|
+
# pagenums = consolidate_ranges term.dests.uniq {|dest| dest[:page] }.map {|dest| dest[:page].to_s }
|
|
2338
|
+
# end
|
|
2339
|
+
# text = %(#{text}, #{pagenums.join ', '})
|
|
2340
|
+
# end
|
|
2341
|
+
# subterm_indent = @theme.description_list_description_indent
|
|
2342
|
+
# layout_prose text, align: :left, margin: 0, normalize_line_height: true, hanging_indent: subterm_indent * 2
|
|
2343
|
+
# indent subterm_indent do
|
|
2344
|
+
# term.subterms.each do |subterm|
|
|
2345
|
+
# convert_index_list_item subterm
|
|
2346
|
+
# end
|
|
2347
|
+
# end unless term.leaf?
|
|
2348
|
+
# end
|
|
2349
|
+
|
|
2350
|
+
# def convert_inline_anchor node
|
|
2351
|
+
# doc = node.document
|
|
2352
|
+
# target = node.target
|
|
2353
|
+
# case node.type
|
|
2354
|
+
# when :link
|
|
2355
|
+
# attrs = []
|
|
2356
|
+
# #attrs << %( id="#{node.id}") if node.id
|
|
2357
|
+
# if (role = node.role)
|
|
2358
|
+
# attrs << %( class="#{role}")
|
|
2359
|
+
# end
|
|
2360
|
+
# #attrs << %( title="#{node.attr 'title'}") if node.attr? 'title'
|
|
2361
|
+
# attrs << %( target="#{node.attr 'window'}") if node.attr? 'window', nil, false
|
|
2362
|
+
# if (@media ||= doc.attr 'media', 'screen') != 'screen' && (target.start_with? 'mailto:') && (doc.attr? 'hide-uri-scheme')
|
|
2363
|
+
# bare_target = target.slice 7, target.length
|
|
2364
|
+
# node.add_role 'bare' if (text = node.text) == bare_target
|
|
2365
|
+
# else
|
|
2366
|
+
# bare_target = target
|
|
2367
|
+
# text = node.text
|
|
2368
|
+
# end
|
|
2369
|
+
# if (role = node.attr 'role', nil, false) && (role == 'bare' || ((role.split ' ').include? 'bare'))
|
|
2370
|
+
# # QUESTION should we insert breakable chars into URI when building fragment instead?
|
|
2371
|
+
# %(<a href="#{target}"#{attrs.join}>#{breakable_uri text}</a>)
|
|
2372
|
+
# # NOTE @media may not be initialized if method is called before convert phase
|
|
2373
|
+
# elsif @media != 'screen' || (doc.attr? 'show-link-uri')
|
|
2374
|
+
# # QUESTION should we insert breakable chars into URI when building fragment instead?
|
|
2375
|
+
# # TODO: allow style of printed link to be controlled by theme
|
|
2376
|
+
# %(<a href="#{target}"#{attrs.join}>#{text}</a> [<font size="0.85em">#{breakable_uri bare_target}</font>])
|
|
2377
|
+
# else
|
|
2378
|
+
# %(<a href="#{target}"#{attrs.join}>#{text}</a>)
|
|
2379
|
+
# end
|
|
2380
|
+
# when :xref
|
|
2381
|
+
# # NOTE non-nil path indicates this is an inter-document xref that's not included in current document
|
|
2382
|
+
# if (path = node.attributes['path'])
|
|
2383
|
+
# # NOTE we don't use local as that doesn't work on the web
|
|
2384
|
+
# # NOTE for the fragment to work in most viewers, it must be #page=<N> <= document this!
|
|
2385
|
+
# %(<a href="#{target}">#{node.text || path}</a>)
|
|
2386
|
+
# elsif (refid = node.attributes['refid'])
|
|
2387
|
+
# unless (text = node.text)
|
|
2388
|
+
# refs = doc.catalog[:refs]
|
|
2389
|
+
# if ::Asciidoctor::AbstractNode === (ref = refs[refid])
|
|
2390
|
+
# text = ref.xreftext node.attr 'xrefstyle', nil, true
|
|
2391
|
+
# end
|
|
2392
|
+
# end
|
|
2393
|
+
# %(<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', ']'
|
|
2394
|
+
# else
|
|
2395
|
+
# %(<a anchor="#{doc.attr 'pdf-anchor'}">#{node.text || '[^top]'}</a>)
|
|
2396
|
+
# end
|
|
2397
|
+
# when :ref
|
|
2398
|
+
# # NOTE destination is created inside callback registered by FormattedTextTransform#build_fragment
|
|
2399
|
+
# %(<a id="#{node.id}">#{DummyText}</a>)
|
|
2400
|
+
# when :bibref
|
|
2401
|
+
# # NOTE destination is created inside callback registered by FormattedTextTransform#build_fragment
|
|
2402
|
+
# # NOTE technically node.text should be node.reftext, but subs have already been applied to text
|
|
2403
|
+
# reftext = (reftext = node.reftext) ? %([#{reftext}]) : %([#{node.id}])
|
|
2404
|
+
# %(<a id="#{node.id}">#{DummyText}</a>#{reftext})
|
|
2405
|
+
# else
|
|
2406
|
+
# logger.warn %(unknown anchor type: #{node.type.inspect}) unless scratch?
|
|
2407
|
+
# end
|
|
2408
|
+
# end
|
|
2409
|
+
|
|
2410
|
+
# def convert_inline_break node
|
|
2411
|
+
# %(#{node.text}<br>)
|
|
2412
|
+
# end
|
|
2413
|
+
|
|
2414
|
+
# def convert_inline_button node
|
|
2415
|
+
# %(<button>#{((load_theme node.document).button_content || '%s').sub '%s', node.text}</button>)
|
|
2416
|
+
# end
|
|
2417
|
+
|
|
2418
|
+
# def convert_inline_callout node
|
|
2419
|
+
# if (conum_font_family = @theme.conum_font_family) != font_name
|
|
2420
|
+
# result = %(<font name="#{conum_font_family}">#{conum_glyph node.text.to_i}</font>)
|
|
2421
|
+
# else
|
|
2422
|
+
# result = conum_glyph node.text.to_i
|
|
2423
|
+
# end
|
|
2424
|
+
# if (conum_font_color = @theme.conum_font_color)
|
|
2425
|
+
# # NOTE CMYK value gets flattened here, but is restored by formatted text parser
|
|
2426
|
+
# result = %(<color rgb="#{conum_font_color}">#{result}</font>)
|
|
2427
|
+
# end
|
|
2428
|
+
# result
|
|
2429
|
+
# end
|
|
2430
|
+
|
|
2431
|
+
# def convert_inline_footnote node
|
|
2432
|
+
# if (index = node.attr 'index') && (fn = node.document.footnotes.find {|candidate| candidate.index == index })
|
|
2433
|
+
# anchor = node.type == :xref ? '' : %(<a id="_footnoteref_#{index}">#{DummyText}</a>)
|
|
2434
|
+
# label = (@rendered_footnotes.include? fn) ? fn.label : (index - @rendered_footnotes.length)
|
|
2435
|
+
# %(#{anchor}<sup>[<a anchor="_footnotedef_#{index}">#{label}</a>]</sup>)
|
|
2436
|
+
# elsif node.type == :xref
|
|
2437
|
+
# # NOTE footnote reference not found
|
|
2438
|
+
# %( <color rgb="FF0000">[#{node.text}]</color>)
|
|
2439
|
+
# end
|
|
2440
|
+
# end
|
|
2441
|
+
|
|
2442
|
+
# def convert_inline_icon node
|
|
2443
|
+
# if node.document.attr? 'icons', 'font'
|
|
2444
|
+
# if (icon_name = node.target).include? '@'
|
|
2445
|
+
# icon_name, icon_set = icon_name.split '@', 2
|
|
2446
|
+
# explicit_icon_set = true
|
|
2447
|
+
# elsif (icon_set = node.attr 'set', nil, false)
|
|
2448
|
+
# explicit_icon_set = true
|
|
2449
|
+
# else
|
|
2450
|
+
# icon_set = node.document.attr 'icon-set', 'fa'
|
|
2451
|
+
# end
|
|
2452
|
+
# if icon_set == 'fa' || !(IconSets.include? icon_set)
|
|
2453
|
+
# icon_set = 'fa'
|
|
2454
|
+
# # legacy name from Font Awesome < 5
|
|
2455
|
+
# if (remapped_icon_name = resolve_legacy_icon_name icon_name)
|
|
2456
|
+
# requested_icon_name = icon_name
|
|
2457
|
+
# icon_set, icon_name = remapped_icon_name.split '-', 2
|
|
2458
|
+
# glyph = (icon_font_data icon_set).unicode icon_name
|
|
2459
|
+
# logger.info { %(#{requested_icon_name} icon found in deprecated fa icon set; using #{icon_name} from #{icon_set} icon set instead) } unless scratch?
|
|
2460
|
+
# # new name in Font Awesome >= 5 (but document is configured to use fa icon set)
|
|
2461
|
+
# else
|
|
2462
|
+
# font_data = nil
|
|
2463
|
+
# if (resolved_icon_set = FontAwesomeIconSets.find {|candidate| (font_data = icon_font_data candidate).unicode icon_name rescue nil })
|
|
2464
|
+
# icon_set = resolved_icon_set
|
|
2465
|
+
# glyph = font_data.unicode icon_name
|
|
2466
|
+
# logger.info { %(#{icon_name} icon not found in deprecated fa icon set; using match found in #{resolved_icon_set} icon set instead) } unless scratch?
|
|
2467
|
+
# end
|
|
2468
|
+
# end
|
|
2469
|
+
# else
|
|
2470
|
+
# glyph = (icon_font_data icon_set).unicode icon_name rescue nil
|
|
2471
|
+
# end
|
|
2472
|
+
# unless glyph || explicit_icon_set || !icon_name.start_with?(*IconSetPrefixes)
|
|
2473
|
+
# icon_set, icon_name = icon_name.split '-', 2
|
|
2474
|
+
# glyph = (icon_font_data icon_set).unicode icon_name rescue nil
|
|
2475
|
+
# end
|
|
2476
|
+
# if glyph
|
|
2477
|
+
# if node.attr? 'size', nil, false
|
|
2478
|
+
# case (size = node.attr 'size')
|
|
2479
|
+
# when 'lg'
|
|
2480
|
+
# size_attr = ' size="1.333em"'
|
|
2481
|
+
# when 'fw'
|
|
2482
|
+
# size_attr = ' width="1em"'
|
|
2483
|
+
# else
|
|
2484
|
+
# size_attr = %( size="#{size.sub 'x', 'em'}")
|
|
2485
|
+
# end
|
|
2486
|
+
# else
|
|
2487
|
+
# size_attr = ''
|
|
2488
|
+
# end
|
|
2489
|
+
# class_attr = node.role? ? %( class="#{node.role}") : ''
|
|
2490
|
+
# # TODO: support rotate and flip attributes
|
|
2491
|
+
# %(<font name="#{icon_set}"#{size_attr}#{class_attr}>#{glyph}</font>)
|
|
2492
|
+
# else
|
|
2493
|
+
# logger.warn %(#{icon_name} is not a valid icon name in the #{icon_set} icon set) unless scratch?
|
|
2494
|
+
# %([#{node.attr 'alt'}])
|
|
2495
|
+
# end
|
|
2496
|
+
# else
|
|
2497
|
+
# %([#{node.attr 'alt'}])
|
|
2498
|
+
# end
|
|
2499
|
+
# end
|
|
2500
|
+
|
|
2501
|
+
# def convert_inline_image node
|
|
2502
|
+
# if node.type == 'icon'
|
|
2503
|
+
# convert_inline_icon node
|
|
2504
|
+
# else
|
|
2505
|
+
# node.extend ::Asciidoctor::Image unless ::Asciidoctor::Image === node
|
|
2506
|
+
# target, image_format = node.target_and_format
|
|
2507
|
+
# if image_format == 'gif' && !(defined? ::GMagick::Image)
|
|
2508
|
+
# logger.warn %(GIF image format not supported. Install the prawn-gmagick gem or convert #{target} to PNG.) unless scratch?
|
|
2509
|
+
# img = %([#{node.attr 'alt'}])
|
|
2510
|
+
# # NOTE an image with a data URI is handled using a temporary file
|
|
2511
|
+
# elsif (image_path = resolve_image_path node, target, image_format, true)
|
|
2512
|
+
# if ::File.readable? image_path
|
|
2513
|
+
# width_attr = (width = resolve_explicit_width node.attributes) ? %( width="#{width}") : ''
|
|
2514
|
+
# fit_attr = (fit = node.attr 'fit', nil, false) ? %( fit="#{fit}") : ''
|
|
2515
|
+
# img = %(<img src="#{image_path}" format="#{image_format}" alt="[#{encode_quotes node.attr 'alt'}]"#{width_attr}#{fit_attr}>)
|
|
2516
|
+
# else
|
|
2517
|
+
# logger.warn %(image to embed not found or not readable: #{image_path}) unless scratch?
|
|
2518
|
+
# img = %([#{node.attr 'alt'}])
|
|
2519
|
+
# end
|
|
2520
|
+
# else
|
|
2521
|
+
# img = %([#{node.attr 'alt'}])
|
|
2522
|
+
# end
|
|
2523
|
+
# (node.attr? 'link', nil, false) ? %(<a href="#{node.attr 'link'}">#{img}</a>) : img
|
|
2524
|
+
# end
|
|
2525
|
+
# end
|
|
2526
|
+
|
|
2527
|
+
# def convert_inline_indexterm node
|
|
2528
|
+
# # NOTE indexterms not supported if text gets substituted before PDF is initialized
|
|
2529
|
+
# if !(defined? @index)
|
|
2530
|
+
# ''
|
|
2531
|
+
# elsif scratch?
|
|
2532
|
+
# node.type == :visible ? node.text : ''
|
|
2533
|
+
# else
|
|
2534
|
+
# # NOTE page number (:page key) is added by InlineDestinationMarker
|
|
2535
|
+
# dest = { anchor: (anchor_name = @index.next_anchor_name) }
|
|
2536
|
+
# anchor = %(<a id="#{anchor_name}" type="indexterm">#{DummyText}</a>)
|
|
2537
|
+
# if node.type == :visible
|
|
2538
|
+
# visible_term = node.text
|
|
2539
|
+
# @index.store_primary_term (sanitize visible_term), dest
|
|
2540
|
+
# %(#{anchor}#{visible_term})
|
|
2541
|
+
# else
|
|
2542
|
+
# @index.store_term((node.attr 'terms').map {|term| sanitize term }, dest)
|
|
2543
|
+
# anchor
|
|
2544
|
+
# end
|
|
2545
|
+
# end
|
|
2546
|
+
# end
|
|
2547
|
+
|
|
2548
|
+
# def convert_inline_kbd node
|
|
2549
|
+
# if (keys = node.attr 'keys').size == 1
|
|
2550
|
+
# %(<key>#{keys[0]}</key>)
|
|
2551
|
+
# else
|
|
2552
|
+
# keys.map {|key| %(<key>#{key}</key>) }.join (load_theme node.document).key_separator || '+'
|
|
2553
|
+
# end
|
|
2554
|
+
# end
|
|
2555
|
+
|
|
2556
|
+
# def convert_inline_menu node
|
|
2557
|
+
# menu = node.attr 'menu'
|
|
2558
|
+
# caret = (load_theme node.document).menu_caret_content || %( \u203a )
|
|
2559
|
+
# if !(submenus = node.attr 'submenus').empty?
|
|
2560
|
+
# %(<strong>#{[menu, *submenus, (node.attr 'menuitem')].join caret}</strong>)
|
|
2561
|
+
# elsif (menuitem = node.attr 'menuitem')
|
|
2562
|
+
# %(<strong>#{menu}#{caret}#{menuitem}</strong>)
|
|
2563
|
+
# else
|
|
2564
|
+
# %(<strong>#{menu}</strong>)
|
|
2565
|
+
# end
|
|
2566
|
+
# end
|
|
2567
|
+
|
|
2568
|
+
# def convert_inline_quoted node
|
|
2569
|
+
# case node.type
|
|
2570
|
+
# when :emphasis
|
|
2571
|
+
# open, close, is_tag = ['<em>', '</em>', true]
|
|
2572
|
+
# when :strong
|
|
2573
|
+
# open, close, is_tag = ['<strong>', '</strong>', true]
|
|
2574
|
+
# when :monospaced, :asciimath, :latexmath
|
|
2575
|
+
# open, close, is_tag = ['<code>', '</code>', true]
|
|
2576
|
+
# when :superscript
|
|
2577
|
+
# open, close, is_tag = ['<sup>', '</sup>', true]
|
|
2578
|
+
# when :subscript
|
|
2579
|
+
# open, close, is_tag = ['<sub>', '</sub>', true]
|
|
2580
|
+
# when :double
|
|
2581
|
+
# open, close, is_tag = ['“', '”', false]
|
|
2582
|
+
# when :single
|
|
2583
|
+
# open, close, is_tag = ['‘', '’', false]
|
|
2584
|
+
# when :mark
|
|
2585
|
+
# open, close, is_tag = ['<mark>', '</mark>', true]
|
|
2586
|
+
# else
|
|
2587
|
+
# open, close, is_tag = [nil, nil, false]
|
|
2588
|
+
# end
|
|
2589
|
+
|
|
2590
|
+
# inner_text = node.text
|
|
2591
|
+
|
|
2592
|
+
# if (role = node.role)
|
|
2593
|
+
# if (text_transform = (load_theme node.document)[%(role_#{role}_text_transform)])
|
|
2594
|
+
# inner_text = transform_text inner_text, text_transform
|
|
2595
|
+
# end
|
|
2596
|
+
# quoted_text = is_tag ? %(#{open.chop} class="#{role}">#{inner_text}#{close}) : %(<span class="#{role}">#{open}#{inner_text}#{close}</span>)
|
|
2597
|
+
# else
|
|
2598
|
+
# quoted_text = %(#{open}#{inner_text}#{close})
|
|
2599
|
+
# end
|
|
2600
|
+
|
|
2601
|
+
# # NOTE destination is created inside callback registered by FormattedTextTransform#build_fragment
|
|
2602
|
+
# node.id ? %(<a id="#{node.id}">#{DummyText}</a>#{quoted_text}) : quoted_text
|
|
2603
|
+
# end
|
|
2604
|
+
|
|
2605
|
+
# def layout_title_page doc
|
|
2606
|
+
# return unless doc.header? && !doc.notitle
|
|
2607
|
+
|
|
2608
|
+
# # NOTE a new page may have already been started at this point, so decide what to do with it
|
|
2609
|
+
# if page.empty?
|
|
2610
|
+
# page.reset_content if (recycle = @ppbook ? recto_page? : true)
|
|
2611
|
+
# elsif @ppbook && page_number > 0 && recto_page?
|
|
2612
|
+
# start_new_page
|
|
2613
|
+
# end
|
|
2614
|
+
|
|
2615
|
+
# side = recycle ? page_side : (page_side page_number + 1)
|
|
2616
|
+
# prev_bg_image = @page_bg_image[side]
|
|
2617
|
+
# prev_bg_color = @page_bg_color
|
|
2618
|
+
# if (bg_image = resolve_background_image doc, @theme, 'title-page-background-image')
|
|
2619
|
+
# @page_bg_image[side] = bg_image[0] && bg_image
|
|
2620
|
+
# end
|
|
2621
|
+
# if (bg_color = resolve_theme_color :title_page_background_color)
|
|
2622
|
+
# @page_bg_color = bg_color
|
|
2623
|
+
# end
|
|
2624
|
+
# recycle ? (init_page self) : start_new_page
|
|
2625
|
+
# @page_bg_image[side] = prev_bg_image if bg_image
|
|
2626
|
+
# @page_bg_color = prev_bg_color if bg_color
|
|
2627
|
+
|
|
2628
|
+
# # IMPORTANT this is the first page created, so we need to set the base font
|
|
2629
|
+
# font @theme.base_font_family, size: @root_font_size
|
|
2630
|
+
|
|
2631
|
+
# # QUESTION allow alignment per element on title page?
|
|
2632
|
+
# title_align = (@theme.title_page_align || @base_align).to_sym
|
|
2633
|
+
|
|
2634
|
+
# # FIXME: disallow .pdf as image type
|
|
2635
|
+
# if @theme.title_page_logo_display != 'none' && (logo_image_path = (doc.attr 'title-logo-image') || (logo_image_from_theme = @theme.title_page_logo_image))
|
|
2636
|
+
# if (logo_image_path.include? ':') && logo_image_path =~ ImageAttributeValueRx
|
|
2637
|
+
# logo_image_attrs = (AttributeList.new $2).parse %w(alt width height)
|
|
2638
|
+
# if logo_image_from_theme
|
|
2639
|
+
# relative_to_imagesdir = false
|
|
2640
|
+
# logo_image_path = sub_attributes_discretely doc, $1
|
|
2641
|
+
# logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, @themesdir unless doc.is_uri? logo_image_path
|
|
2642
|
+
# else
|
|
2643
|
+
# relative_to_imagesdir = true
|
|
2644
|
+
# logo_image_path = $1
|
|
2645
|
+
# end
|
|
2646
|
+
# else
|
|
2647
|
+
# logo_image_attrs = {}
|
|
2648
|
+
# relative_to_imagesdir = false
|
|
2649
|
+
# if logo_image_from_theme
|
|
2650
|
+
# logo_image_path = sub_attributes_discretely doc, logo_image_path
|
|
2651
|
+
# logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, @themesdir unless doc.is_uri? logo_image_path
|
|
2652
|
+
# end
|
|
2653
|
+
# end
|
|
2654
|
+
# logo_image_attrs['target'] = logo_image_path
|
|
2655
|
+
# if (logo_align = [(logo_image_attrs.delete 'align'), @theme.title_page_logo_align, title_align.to_s].find {|val| (BlockAlignmentNames.include? val) })
|
|
2656
|
+
# logo_image_attrs['align'] = logo_align
|
|
2657
|
+
# end
|
|
2658
|
+
# if (logo_image_top = logo_image_attrs['top'] || @theme.title_page_logo_top)
|
|
2659
|
+
# initial_y, @y = @y, (resolve_top logo_image_top)
|
|
2660
|
+
# end
|
|
2661
|
+
# # FIXME: add API to Asciidoctor for creating blocks like this (extract from extensions module?)
|
|
2662
|
+
# image_block = ::Asciidoctor::Block.new doc, :image, content_model: :empty, attributes: logo_image_attrs
|
|
2663
|
+
# # NOTE pinned option keeps image on same page
|
|
2664
|
+
# indent (@theme.title_page_logo_margin_left || 0), (@theme.title_page_logo_margin_right || 0) do
|
|
2665
|
+
# convert_image image_block, relative_to_imagesdir: relative_to_imagesdir, pinned: true
|
|
2666
|
+
# end
|
|
2667
|
+
# @y = initial_y if initial_y
|
|
2668
|
+
# end
|
|
2669
|
+
|
|
2670
|
+
# # TODO: prevent content from spilling to next page
|
|
2671
|
+
# theme_font :title_page do
|
|
2672
|
+
# if (title_top = @theme.title_page_title_top)
|
|
2673
|
+
# @y = resolve_top title_top
|
|
2674
|
+
# end
|
|
2675
|
+
# unless @theme.title_page_title_display == 'none'
|
|
2676
|
+
# doctitle = doc.doctitle partition: true
|
|
2677
|
+
# move_down(@theme.title_page_title_margin_top || 0)
|
|
2678
|
+
# indent (@theme.title_page_title_margin_left || 0), (@theme.title_page_title_margin_right || 0) do
|
|
2679
|
+
# theme_font :title_page_title do
|
|
2680
|
+
# layout_prose doctitle.main,
|
|
2681
|
+
# align: title_align,
|
|
2682
|
+
# margin: 0,
|
|
2683
|
+
# line_height: @theme.title_page_title_line_height
|
|
2684
|
+
# end
|
|
2685
|
+
# end
|
|
2686
|
+
# move_down(@theme.title_page_title_margin_bottom || 0)
|
|
2687
|
+
# end
|
|
2688
|
+
# if @theme.title_page_subtitle_display != 'none' && (subtitle = (doctitle || (doc.doctitle partition: true)).subtitle)
|
|
2689
|
+
# move_down(@theme.title_page_subtitle_margin_top || 0)
|
|
2690
|
+
# indent (@theme.title_page_subtitle_margin_left || 0), (@theme.title_page_subtitle_margin_right || 0) do
|
|
2691
|
+
# theme_font :title_page_subtitle do
|
|
2692
|
+
# layout_prose subtitle,
|
|
2693
|
+
# align: title_align,
|
|
2694
|
+
# margin: 0,
|
|
2695
|
+
# line_height: @theme.title_page_subtitle_line_height
|
|
2696
|
+
# end
|
|
2697
|
+
# end
|
|
2698
|
+
# move_down(@theme.title_page_subtitle_margin_bottom || 0)
|
|
2699
|
+
# end
|
|
2700
|
+
# if @theme.title_page_authors_display != 'none' && (doc.attr? 'authors')
|
|
2701
|
+
# move_down(@theme.title_page_authors_margin_top || 0)
|
|
2702
|
+
# indent (@theme.title_page_authors_margin_left || 0), (@theme.title_page_authors_margin_right || 0) do
|
|
2703
|
+
# generic_authors_content = @theme.title_page_authors_content
|
|
2704
|
+
# authors_content = {
|
|
2705
|
+
# name_only: @theme.title_page_authors_content_name_only || generic_authors_content,
|
|
2706
|
+
# with_email: @theme.title_page_authors_content_with_email || generic_authors_content,
|
|
2707
|
+
# with_url: @theme.title_page_authors_content_with_url || generic_authors_content,
|
|
2708
|
+
# }
|
|
2709
|
+
# # TODO: provide an API in core to get authors as an array
|
|
2710
|
+
# authors = (1..(doc.attr 'authorcount', 1).to_i).map {|idx|
|
|
2711
|
+
# promote_author doc, idx do
|
|
2712
|
+
# author_content_key = (url = doc.attr 'url') ? ((url.start_with? 'mailto:') ? :with_email : :with_url) : :name_only
|
|
2713
|
+
# if (author_content = authors_content[author_content_key])
|
|
2714
|
+
# apply_subs_discretely doc, author_content, drop_lines_with_unresolved_attributes: true
|
|
2715
|
+
# else
|
|
2716
|
+
# doc.attr 'author'
|
|
2717
|
+
# end
|
|
2718
|
+
# end
|
|
2719
|
+
# }.join (@theme.title_page_authors_delimiter || ', ')
|
|
2720
|
+
# theme_font :title_page_authors do
|
|
2721
|
+
# layout_prose authors,
|
|
2722
|
+
# align: title_align,
|
|
2723
|
+
# margin: 0,
|
|
2724
|
+
# normalize: true
|
|
2725
|
+
# end
|
|
2726
|
+
# end
|
|
2727
|
+
# move_down(@theme.title_page_authors_margin_bottom || 0)
|
|
2728
|
+
# end
|
|
2729
|
+
# unless @theme.title_page_revision_display == 'none' || (revision_info = [(doc.attr? 'revnumber') ? %(#{doc.attr 'version-label'} #{doc.attr 'revnumber'}) : nil, (doc.attr 'revdate')].compact).empty?
|
|
2730
|
+
# move_down(@theme.title_page_revision_margin_top || 0)
|
|
2731
|
+
# revision_text = revision_info.join (@theme.title_page_revision_delimiter || ', ')
|
|
2732
|
+
# if (revremark = doc.attr 'revremark')
|
|
2733
|
+
# revision_text = %(#{revision_text}: #{revremark})
|
|
2734
|
+
# end
|
|
2735
|
+
# indent (@theme.title_page_revision_margin_left || 0), (@theme.title_page_revision_margin_right || 0) do
|
|
2736
|
+
# theme_font :title_page_revision do
|
|
2737
|
+
# layout_prose revision_text,
|
|
2738
|
+
# align: title_align,
|
|
2739
|
+
# margin: 0,
|
|
2740
|
+
# normalize: false
|
|
2741
|
+
# end
|
|
2742
|
+
# end
|
|
2743
|
+
# move_down(@theme.title_page_revision_margin_bottom || 0)
|
|
2744
|
+
# end
|
|
2745
|
+
# end
|
|
2746
|
+
|
|
2747
|
+
# layout_prose DummyText, margin: 0, line_height: 1, normalize: false if page.empty?
|
|
2748
|
+
# end
|
|
2749
|
+
|
|
2750
|
+
# def layout_cover_page doc, face
|
|
2751
|
+
# bg_image = resolve_background_image doc, @theme, %(#{face}-cover-image), theme_key: %(cover_#{face}_image).to_sym, symbolic_paths: ['', '~']
|
|
2752
|
+
# if bg_image && bg_image[0]
|
|
2753
|
+
# image_path, image_opts = bg_image
|
|
2754
|
+
# if image_path.empty?
|
|
2755
|
+
# go_to_page page_count if face == :back
|
|
2756
|
+
# start_new_page_discretely
|
|
2757
|
+
# # NOTE open graphics state to prevent page from being reused
|
|
2758
|
+
# open_graphics_state if face == :front
|
|
2759
|
+
# return
|
|
2760
|
+
# elsif image_path == '~'
|
|
2761
|
+
# @page_margin_by_side[:cover] = @page_margin_by_side[:recto] if @media == 'prepress'
|
|
2762
|
+
# return
|
|
2763
|
+
# end
|
|
2764
|
+
|
|
2765
|
+
# go_to_page page_count if face == :back
|
|
2766
|
+
# if image_opts[:format] == 'pdf'
|
|
2767
|
+
# import_page image_path, (image_opts.merge advance: face != :back)
|
|
2768
|
+
# else
|
|
2769
|
+
# image_page image_path, (image_opts.merge canvas: true)
|
|
2770
|
+
# end
|
|
2771
|
+
# end
|
|
2772
|
+
# end
|
|
2773
|
+
|
|
2774
|
+
# def stamp_foreground_image doc, has_front_cover
|
|
2775
|
+
# pages = state.pages
|
|
2776
|
+
# if (first_page = (has_front_cover ? (pages.slice 1, pages.size) : pages).find {|it| !it.imported_page? }) &&
|
|
2777
|
+
# (first_page_num = (pages.index first_page) + 1) &&
|
|
2778
|
+
# (fg_image = resolve_background_image doc, @theme, 'page-foreground-image') && fg_image[0]
|
|
2779
|
+
# go_to_page first_page_num
|
|
2780
|
+
# create_stamp 'foreground-image' do
|
|
2781
|
+
# canvas { image fg_image[0], ({ position: :center, vposition: :center }.merge fg_image[1]) }
|
|
2782
|
+
# end
|
|
2783
|
+
# stamp 'foreground-image'
|
|
2784
|
+
# (first_page_num.next..page_count).each do |num|
|
|
2785
|
+
# go_to_page num
|
|
2786
|
+
# stamp 'foreground-image' unless page.imported_page?
|
|
2787
|
+
# end
|
|
2788
|
+
# end
|
|
2789
|
+
# end
|
|
2790
|
+
|
|
2791
|
+
# def start_new_chapter chapter
|
|
2792
|
+
# start_new_page unless at_page_top?
|
|
2793
|
+
# # TODO: must call update_colors before advancing to next page if start_new_page is called in layout_chapter_title
|
|
2794
|
+
# start_new_page if @ppbook && verso_page? && !(chapter.option? 'nonfacing')
|
|
2795
|
+
# end
|
|
2796
|
+
|
|
2797
|
+
# alias start_new_part start_new_chapter
|
|
2798
|
+
|
|
2799
|
+
# def layout_chapter_title _node, title, opts = {}
|
|
2800
|
+
# layout_heading title, (opts.merge outdent: true)
|
|
2801
|
+
# end
|
|
2802
|
+
|
|
2803
|
+
# alias layout_part_title layout_chapter_title
|
|
2804
|
+
|
|
2805
|
+
# # NOTE layout_heading doesn't set the theme font because it's used for various types of headings
|
|
2806
|
+
# # QUESTION why doesn't layout_heading accept a node?
|
|
2807
|
+
# def layout_heading string, opts = {}
|
|
2808
|
+
# hlevel = opts[:level]
|
|
2809
|
+
# unless (top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top))
|
|
2810
|
+
# if at_page_top?
|
|
2811
|
+
# if hlevel && (top_margin = @theme[%(heading_h#{hlevel}_margin_page_top)] || @theme.heading_margin_page_top || 0) > 0
|
|
2812
|
+
# move_down top_margin
|
|
2813
|
+
# end
|
|
2814
|
+
# top_margin = 0
|
|
2815
|
+
# else
|
|
2816
|
+
# top_margin = (hlevel ? @theme[%(heading_h#{hlevel}_margin_top)] : nil) || @theme.heading_margin_top
|
|
2817
|
+
# end
|
|
2818
|
+
# end
|
|
2819
|
+
# bot_margin = margin || (opts.delete :margin_bottom) || (hlevel ? @theme[%(heading_h#{hlevel}_margin_bottom)] : nil) || @theme.heading_margin_bottom
|
|
2820
|
+
# if (transform = resolve_text_transform opts)
|
|
2821
|
+
# string = transform_text string, transform
|
|
2822
|
+
# end
|
|
2823
|
+
# outdent_section opts.delete :outdent do
|
|
2824
|
+
# margin_top top_margin
|
|
2825
|
+
# # QUESTION should we move inherited styles to typeset_text?
|
|
2826
|
+
# if (inherited = apply_text_decoration font_styles, :heading, hlevel).empty?
|
|
2827
|
+
# inline_format_opts = true
|
|
2828
|
+
# else
|
|
2829
|
+
# inline_format_opts = [{ inherited: inherited }]
|
|
2830
|
+
# end
|
|
2831
|
+
# typeset_text string, calc_line_metrics((opts.delete :line_height) || (hlevel ? @theme[%(heading_h#{hlevel}_line_height)] : nil) || @theme.heading_line_height || @theme.base_line_height), {
|
|
2832
|
+
# color: @font_color,
|
|
2833
|
+
# inline_format: inline_format_opts,
|
|
2834
|
+
# align: @base_align.to_sym,
|
|
2835
|
+
# }.merge(opts)
|
|
2836
|
+
# margin_bottom bot_margin
|
|
2837
|
+
# end
|
|
2838
|
+
# end
|
|
2839
|
+
|
|
2840
|
+
# # NOTE inline_format is true by default
|
|
2841
|
+
# def layout_prose string, opts = {}
|
|
2842
|
+
# top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.prose_margin_top
|
|
2843
|
+
# bot_margin = margin || (opts.delete :margin_bottom) || @theme.prose_margin_bottom
|
|
2844
|
+
# if (transform = resolve_text_transform opts)
|
|
2845
|
+
# string = transform_text string, transform
|
|
2846
|
+
# end
|
|
2847
|
+
# string = hyphenate_text string, @hyphenator if (opts.delete :hyphenate) && (defined? @hyphenator)
|
|
2848
|
+
# # NOTE used by extensions; ensures linked text gets formatted using the link styles
|
|
2849
|
+
# if (anchor = opts.delete :anchor)
|
|
2850
|
+
# string = %(<a anchor="#{anchor}">#{string}</a>)
|
|
2851
|
+
# end
|
|
2852
|
+
# margin_top top_margin
|
|
2853
|
+
# string = ZeroWidthSpace + string if opts.delete :normalize_line_height
|
|
2854
|
+
# # NOTE normalize makes endlines soft (replaces "\n" with ' ')
|
|
2855
|
+
# inline_format_opts = { normalize: (opts.delete :normalize) != false }
|
|
2856
|
+
# if (styles = opts.delete :styles)
|
|
2857
|
+
# inline_format_opts[:inherited] = { styles: styles }
|
|
2858
|
+
# end
|
|
2859
|
+
# typeset_text string, calc_line_metrics((opts.delete :line_height) || @theme.base_line_height), {
|
|
2860
|
+
# color: @font_color,
|
|
2861
|
+
# inline_format: [inline_format_opts],
|
|
2862
|
+
# align: @base_align.to_sym,
|
|
2863
|
+
# }.merge(opts)
|
|
2864
|
+
# margin_bottom bot_margin
|
|
2865
|
+
# end
|
|
2866
|
+
|
|
2867
|
+
# def generate_manname_section node
|
|
2868
|
+
# title = node.attr 'manname-title', 'Name'
|
|
2869
|
+
# if (next_section = node.sections[0]) && (next_section_title = next_section.title) == next_section_title.upcase
|
|
2870
|
+
# title = title.upcase
|
|
2871
|
+
# end
|
|
2872
|
+
# sect = Section.new node, 1
|
|
2873
|
+
# sect.sectname = 'section'
|
|
2874
|
+
# sect.id = node.attr 'manname-id'
|
|
2875
|
+
# sect.title = title
|
|
2876
|
+
# sect << (Block.new sect, :paragraph, source: %(#{node.attr 'manname'} - #{node.attr 'manpurpose'}), subs: :normal)
|
|
2877
|
+
# sect
|
|
2878
|
+
# end
|
|
2879
|
+
|
|
2880
|
+
# # Render the caption and return the height of the rendered content
|
|
2881
|
+
# #
|
|
2882
|
+
# # The subject argument can either be a String or an AbstractNode. If
|
|
2883
|
+
# # subject is an AbstractNode, only call this method if the node has a
|
|
2884
|
+
# # title (i.e., subject.title? return true).
|
|
2885
|
+
# #--
|
|
2886
|
+
# # TODO: allow margin to be zeroed
|
|
2887
|
+
# def layout_caption subject, opts = {}
|
|
2888
|
+
# if opts.delete :dry_run
|
|
2889
|
+
# height = nil
|
|
2890
|
+
# dry_run do
|
|
2891
|
+
# move_down 0.001 # HACK: force top margin to be applied
|
|
2892
|
+
# height = layout_caption subject, opts
|
|
2893
|
+
# end
|
|
2894
|
+
# return height
|
|
2895
|
+
# end
|
|
2896
|
+
# mark = { cursor: cursor, page_number: page_number }
|
|
2897
|
+
# case subject
|
|
2898
|
+
# when ::String
|
|
2899
|
+
# string = subject
|
|
2900
|
+
# when ::Asciidoctor::AbstractBlock
|
|
2901
|
+
# string = subject.captioned_title
|
|
2902
|
+
# else
|
|
2903
|
+
# raise ArgumentError, 'invalid subject'
|
|
2904
|
+
# end
|
|
2905
|
+
# category_caption = (category = opts[:category]) ? %(#{category}_caption) : 'caption'
|
|
2906
|
+
# container_width = bounds.width
|
|
2907
|
+
# block_align = opts.delete :block_align
|
|
2908
|
+
# if (align = @theme[%(#{category_caption}_align)] || @theme.caption_align)
|
|
2909
|
+
# align = align == 'inherit' ? (block_align || @base_align) : align.to_sym
|
|
2910
|
+
# else
|
|
2911
|
+
# align = @base_align.to_sym
|
|
2912
|
+
# end
|
|
2913
|
+
# if (text_align = @theme[%(#{category_caption}_text_align)] || @theme.caption_text_align)
|
|
2914
|
+
# text_align = text_align == 'inherit' ? align : text_align.to_sym
|
|
2915
|
+
# else
|
|
2916
|
+
# text_align = align
|
|
2917
|
+
# end
|
|
2918
|
+
# indent_by = [0, 0]
|
|
2919
|
+
# block_width = opts.delete :block_width
|
|
2920
|
+
# if (max_width = opts.delete :max_width) && max_width != 'none'
|
|
2921
|
+
# if max_width.start_with? 'fit-content'
|
|
2922
|
+
# if max_width.end_with? 't', '()'
|
|
2923
|
+
# max_width = block_width || container_width
|
|
2924
|
+
# else
|
|
2925
|
+
# max_width = (block_width || container_width) * (max_width.slice 12, max_width.length - 1).to_f / 100.0
|
|
2926
|
+
# end
|
|
2927
|
+
# else
|
|
2928
|
+
# max_width = [max_width.to_f / 100 * bounds.width, bounds.width].min if ::String === max_width && (max_width.end_with? '%')
|
|
2929
|
+
# block_align = align
|
|
2930
|
+
# end
|
|
2931
|
+
# if (remainder = container_width - max_width) > 0
|
|
2932
|
+
# case block_align
|
|
2933
|
+
# when :right
|
|
2934
|
+
# indent_by = [remainder, 0]
|
|
2935
|
+
# when :center
|
|
2936
|
+
# indent_by = [(side_margin = remainder * 0.5), side_margin]
|
|
2937
|
+
# else # :left, nil
|
|
2938
|
+
# indent_by = [0, remainder]
|
|
2939
|
+
# end
|
|
2940
|
+
# end
|
|
2941
|
+
# end
|
|
2942
|
+
# theme_font :caption do
|
|
2943
|
+
# theme_font category_caption do
|
|
2944
|
+
# caption_margin_outside = @theme[%(#{category_caption}_margin_outside)] || @theme.caption_margin_outside
|
|
2945
|
+
# caption_margin_inside = @theme[%(#{category_caption}_margin_inside)] || @theme.caption_margin_inside
|
|
2946
|
+
# if (side = (opts.delete :side) || :top) == :top
|
|
2947
|
+
# margin = { top: caption_margin_outside, bottom: caption_margin_inside }
|
|
2948
|
+
# else
|
|
2949
|
+
# margin = { top: caption_margin_inside, bottom: caption_margin_outside }
|
|
2950
|
+
# end
|
|
2951
|
+
# indent(*indent_by) do
|
|
2952
|
+
# layout_prose string, {
|
|
2953
|
+
# margin_top: margin[:top],
|
|
2954
|
+
# margin_bottom: margin[:bottom],
|
|
2955
|
+
# align: text_align,
|
|
2956
|
+
# normalize: false,
|
|
2957
|
+
# normalize_line_height: true,
|
|
2958
|
+
# hyphenate: true,
|
|
2959
|
+
# }.merge(opts)
|
|
2960
|
+
# end
|
|
2961
|
+
# if side == :top && (bb_color = @theme[%(#{category_caption}_border_bottom_color)] || @theme.caption_border_bottom_color)
|
|
2962
|
+
# stroke_horizontal_rule bb_color
|
|
2963
|
+
# # FIXME: HACK move down slightly so line isn't covered by filled area (half width of line)
|
|
2964
|
+
# move_down 0.25
|
|
2965
|
+
# end
|
|
2966
|
+
# end
|
|
2967
|
+
# end
|
|
2968
|
+
# # NOTE we assume we don't clear more than one page
|
|
2969
|
+
# if page_number > mark[:page_number]
|
|
2970
|
+
# mark[:cursor] + (bounds.top - cursor)
|
|
2971
|
+
# else
|
|
2972
|
+
# mark[:cursor] - cursor
|
|
2973
|
+
# end
|
|
2974
|
+
# end
|
|
2975
|
+
|
|
2976
|
+
# # Render the caption for a table and return the height of the rendered content
|
|
2977
|
+
# def layout_table_caption node, table_alignment = :left, table_width = nil, max_width = nil, side = :top
|
|
2978
|
+
# layout_caption node, category: :table, side: side, block_align: table_alignment, block_width: table_width, max_width: max_width
|
|
2979
|
+
# end
|
|
2980
|
+
|
|
2981
|
+
# def allocate_toc doc, toc_num_levels, toc_start_y, use_title_page
|
|
2982
|
+
# toc_page_nums = page_number
|
|
2983
|
+
# toc_end = nil
|
|
2984
|
+
# dry_run do
|
|
2985
|
+
# toc_page_nums = layout_toc doc, toc_num_levels, toc_page_nums, toc_start_y
|
|
2986
|
+
# move_down @theme.block_margin_bottom unless use_title_page
|
|
2987
|
+
# toc_end = @y
|
|
2988
|
+
# end
|
|
2989
|
+
# # NOTE reserve pages for the toc; leaves cursor on page after last page in toc
|
|
2990
|
+
# if use_title_page
|
|
2991
|
+
# toc_page_nums.each { start_new_page }
|
|
2992
|
+
# else
|
|
2993
|
+
# (toc_page_nums.size - 1).times { start_new_page }
|
|
2994
|
+
# @y = toc_end
|
|
2995
|
+
# end
|
|
2996
|
+
# @toc_extent = { page_nums: toc_page_nums, start_y: toc_start_y }
|
|
2997
|
+
# end
|
|
2998
|
+
|
|
2999
|
+
# # NOTE num_front_matter_pages is not used during a dry run
|
|
3000
|
+
# def layout_toc doc, num_levels = 2, toc_page_number = 2, start_y = nil, num_front_matter_pages = 0
|
|
3001
|
+
# go_to_page toc_page_number unless (page_number == toc_page_number) || scratch?
|
|
3002
|
+
# start_page_number = page_number
|
|
3003
|
+
# @y = start_y if start_y
|
|
3004
|
+
# unless (toc_title = doc.attr 'toc-title').nil_or_empty?
|
|
3005
|
+
# theme_font :heading, level: 2 do
|
|
3006
|
+
# theme_font :toc_title do
|
|
3007
|
+
# toc_title_align = (@theme.toc_title_align || @theme.heading_h2_align || @theme.heading_align || @base_align).to_sym
|
|
3008
|
+
# layout_heading toc_title, align: toc_title_align, level: 2, outdent: true
|
|
3009
|
+
# end
|
|
3010
|
+
# end
|
|
3011
|
+
# end
|
|
3012
|
+
# # QUESTION should we skip this whole method if num_levels < 0?
|
|
3013
|
+
# unless num_levels < 0
|
|
3014
|
+
# dot_leader = theme_font :toc do
|
|
3015
|
+
# # TODO: we could simplify by using nested theme_font :toc_dot_leader
|
|
3016
|
+
# if (dot_leader_font_style = (@theme.toc_dot_leader_font_style || :normal).to_sym) != font_style
|
|
3017
|
+
# font_style dot_leader_font_style
|
|
3018
|
+
# end
|
|
3019
|
+
# {
|
|
3020
|
+
# font_color: @theme.toc_dot_leader_font_color || @font_color,
|
|
3021
|
+
# font_style: dot_leader_font_style,
|
|
3022
|
+
# levels: ((dot_leader_l = @theme.toc_dot_leader_levels) == 'none' ? ::Set.new :
|
|
3023
|
+
# (dot_leader_l && dot_leader_l != 'all' ? dot_leader_l.to_s.split.map(&:to_i).to_set : (0..num_levels).to_set)),
|
|
3024
|
+
# text: (dot_leader_text = @theme.toc_dot_leader_content || DotLeaderTextDefault),
|
|
3025
|
+
# width: dot_leader_text.empty? ? 0 : (rendered_width_of_string dot_leader_text),
|
|
3026
|
+
# # TODO: spacer gives a little bit of room between dots and page number
|
|
3027
|
+
# spacer: { text: NoBreakSpace, size: (spacer_font_size = @font_size * 0.25) },
|
|
3028
|
+
# spacer_width: (rendered_width_of_char NoBreakSpace, size: spacer_font_size),
|
|
3029
|
+
# }
|
|
3030
|
+
# end
|
|
3031
|
+
# line_metrics = calc_line_metrics @theme.toc_line_height
|
|
3032
|
+
# theme_margin :toc, :top
|
|
3033
|
+
# layout_toc_level doc.sections, num_levels, line_metrics, dot_leader, num_front_matter_pages
|
|
3034
|
+
# end
|
|
3035
|
+
# # NOTE range must be calculated relative to toc_page_number; absolute page number in scratch document is arbitrary
|
|
3036
|
+
# toc_page_numbers = (toc_page_number..(toc_page_number + (page_number - start_page_number)))
|
|
3037
|
+
# go_to_page page_count unless scratch?
|
|
3038
|
+
# toc_page_numbers
|
|
3039
|
+
# end
|
|
3040
|
+
|
|
3041
|
+
# def layout_toc_level sections, num_levels, line_metrics, dot_leader, num_front_matter_pages = 0
|
|
3042
|
+
# # NOTE font options aren't always reliable, so store size separately
|
|
3043
|
+
# toc_font_info = theme_font :toc do
|
|
3044
|
+
# { font: font, size: @font_size }
|
|
3045
|
+
# end
|
|
3046
|
+
# hanging_indent = @theme.toc_hanging_indent || 0
|
|
3047
|
+
# sections.each do |sect|
|
|
3048
|
+
# next if (num_levels_for_sect = (sect.attr 'toclevels', num_levels, false).to_i) < sect.level
|
|
3049
|
+
# theme_font :toc, level: (sect.level + 1) do
|
|
3050
|
+
# sect_title = ZeroWidthSpace + (@text_transform ? (transform_text sect.numbered_title, @text_transform) : sect.numbered_title)
|
|
3051
|
+
# pgnum_label_placeholder_width = rendered_width_of_string '0' * @toc_max_pagenum_digits
|
|
3052
|
+
# # NOTE only write section title (excluding dots and page number) if this is a dry run
|
|
3053
|
+
# if scratch?
|
|
3054
|
+
# indent 0, pgnum_label_placeholder_width do
|
|
3055
|
+
# # FIXME: use layout_prose
|
|
3056
|
+
# # NOTE must wrap title in empty anchor element in case links are styled with different font family / size
|
|
3057
|
+
# typeset_text %(<a>#{sect_title}</a>), line_metrics, inline_format: true, hanging_indent: hanging_indent
|
|
3058
|
+
# end
|
|
3059
|
+
# else
|
|
3060
|
+
# physical_pgnum = sect.attr 'pdf-page-start'
|
|
3061
|
+
# virtual_pgnum = physical_pgnum - num_front_matter_pages
|
|
3062
|
+
# pgnum_label = (virtual_pgnum < 1 ? (RomanNumeral.new physical_pgnum, :lower) : virtual_pgnum).to_s
|
|
3063
|
+
# start_page_number = page_number
|
|
3064
|
+
# start_cursor = cursor
|
|
3065
|
+
# start_dots = nil
|
|
3066
|
+
# sect_title_inherited = (apply_text_decoration ::Set.new, :toc, sect.level.next).merge anchor: (sect_anchor = sect.attr 'pdf-anchor'), color: @font_color
|
|
3067
|
+
# # NOTE use text formatter to add anchor overlay to avoid using inline format with synthetic anchor tag
|
|
3068
|
+
# sect_title_fragments = text_formatter.format sect_title, inherited: sect_title_inherited
|
|
3069
|
+
# indent 0, pgnum_label_placeholder_width do
|
|
3070
|
+
# sect_title_fragments[-1][:callback] = (last_fragment_pos = ::Asciidoctor::PDF::FormattedText::FragmentPositionRenderer.new)
|
|
3071
|
+
# typeset_formatted_text sect_title_fragments, line_metrics, hanging_indent: hanging_indent
|
|
3072
|
+
# start_dots = last_fragment_pos.right + hanging_indent
|
|
3073
|
+
# last_fragment_cursor = last_fragment_pos.top + line_metrics.padding_top
|
|
3074
|
+
# # NOTE this will be incorrect if wrapped line is all monospace
|
|
3075
|
+
# if (last_fragment_page_number = last_fragment_pos.page_number) > start_page_number ||
|
|
3076
|
+
# (start_cursor - last_fragment_cursor) > line_metrics.height
|
|
3077
|
+
# start_page_number = last_fragment_page_number
|
|
3078
|
+
# start_cursor = last_fragment_cursor
|
|
3079
|
+
# end
|
|
3080
|
+
# end
|
|
3081
|
+
# end_page_number = page_number
|
|
3082
|
+
# end_cursor = cursor
|
|
3083
|
+
# # TODO: it would be convenient to have a cursor mark / placement utility that took page number into account
|
|
3084
|
+
# go_to_page start_page_number if start_page_number != end_page_number
|
|
3085
|
+
# move_cursor_to start_cursor
|
|
3086
|
+
# if dot_leader[:width] > 0 && (dot_leader[:levels].include? sect.level)
|
|
3087
|
+
# pgnum_label_width = rendered_width_of_string pgnum_label
|
|
3088
|
+
# pgnum_label_font_settings = { color: @font_color, font: font_family, size: @font_size, styles: font_styles }
|
|
3089
|
+
# save_font do
|
|
3090
|
+
# # NOTE the same font is used for dot leaders throughout toc
|
|
3091
|
+
# set_font toc_font_info[:font], toc_font_info[:size]
|
|
3092
|
+
# font_style dot_leader[:font_style]
|
|
3093
|
+
# num_dots = ((bounds.width - start_dots - dot_leader[:spacer_width] - pgnum_label_width) / dot_leader[:width]).floor
|
|
3094
|
+
# # FIXME: dots don't line up in columns if width of page numbers differ
|
|
3095
|
+
# typeset_formatted_text [
|
|
3096
|
+
# { text: (dot_leader[:text] * (num_dots < 0 ? 0 : num_dots)), color: dot_leader[:font_color] },
|
|
3097
|
+
# dot_leader[:spacer],
|
|
3098
|
+
# { text: pgnum_label, anchor: sect_anchor }.merge(pgnum_label_font_settings),
|
|
3099
|
+
# ], line_metrics, align: :right
|
|
3100
|
+
# end
|
|
3101
|
+
# else
|
|
3102
|
+
# typeset_formatted_text [{ text: pgnum_label, color: @font_color, anchor: sect_anchor }], line_metrics, align: :right
|
|
3103
|
+
# end
|
|
3104
|
+
# go_to_page end_page_number if page_number != end_page_number
|
|
3105
|
+
# move_cursor_to end_cursor
|
|
3106
|
+
# end
|
|
3107
|
+
# end
|
|
3108
|
+
# indent @theme.toc_indent do
|
|
3109
|
+
# layout_toc_level sect.sections, num_levels_for_sect, line_metrics, dot_leader, num_front_matter_pages
|
|
3110
|
+
# end if num_levels_for_sect > sect.level
|
|
3111
|
+
# end
|
|
3112
|
+
# end
|
|
3113
|
+
|
|
3114
|
+
# # Reduce icon height to fit inside bounds.height. Icons will not render
|
|
3115
|
+
# # properly if they are larger than the current bounds.height.
|
|
3116
|
+
# def fit_icon_to_bounds preferred_size = 24
|
|
3117
|
+
# (max_height = bounds.height) < preferred_size ? max_height : preferred_size
|
|
3118
|
+
# end
|
|
3119
|
+
|
|
3120
|
+
# def admonition_icon_data key
|
|
3121
|
+
# if (icon_data = @theme[%(admonition_icon_#{key})])
|
|
3122
|
+
# icon_data = (AdmonitionIcons[key] || {}).merge icon_data
|
|
3123
|
+
# if (icon_name = icon_data[:name])
|
|
3124
|
+
# unless icon_name.start_with?(*IconSetPrefixes)
|
|
3125
|
+
# logger.info { %(#{key} admonition in theme uses icon from deprecated fa icon set; use fas, far, or fab instead) } unless scratch?
|
|
3126
|
+
# icon_data[:name] = %(fa-#{icon_name}) unless icon_name.start_with? 'fa-'
|
|
3127
|
+
# end
|
|
3128
|
+
# end
|
|
3129
|
+
# icon_data
|
|
3130
|
+
# else
|
|
3131
|
+
# AdmonitionIcons[key]
|
|
3132
|
+
# end
|
|
3133
|
+
# end
|
|
3134
|
+
|
|
3135
|
+
# # TODO: delegate to layout_page_header and layout_page_footer per page
|
|
3136
|
+
# def layout_running_content periphery, doc, opts = {}
|
|
3137
|
+
# skip, skip_pagenums = opts[:skip] || [1, 1]
|
|
3138
|
+
# body_start_page_number = opts[:body_start_page_number] || 1
|
|
3139
|
+
# # NOTE find and advance to first non-imported content page to use as model page
|
|
3140
|
+
# return unless (content_start_page = state.pages[skip..-1].index {|it| !it.imported_page? })
|
|
3141
|
+
# content_start_page += (skip + 1)
|
|
3142
|
+
# num_pages = page_count
|
|
3143
|
+
# prev_page_number = page_number
|
|
3144
|
+
# go_to_page content_start_page
|
|
3145
|
+
|
|
3146
|
+
# # FIXME: probably need to treat doctypes differently
|
|
3147
|
+
# is_book = doc.doctype == 'book'
|
|
3148
|
+
# header = doc.header? ? doc.header : nil
|
|
3149
|
+
# sectlevels = (@theme[%(#{periphery}_sectlevels)] || 2).to_i
|
|
3150
|
+
# sections = doc.find_by(context: :section) {|sect| sect.level <= sectlevels && sect != header } || []
|
|
3151
|
+
# if (toc_page_nums = @toc_extent && @toc_extent[:page_nums])
|
|
3152
|
+
# toc_title = (doc.attr 'toc-title') || ''
|
|
3153
|
+
# end
|
|
3154
|
+
# disable_on_pages = @disable_running_content[periphery]
|
|
3155
|
+
|
|
3156
|
+
# title_method = TitleStyles[@theme[%(#{periphery}_title_style)]]
|
|
3157
|
+
# # FIXME: we need a proper model for all this page counting
|
|
3158
|
+
# # FIXME: we make a big assumption that part & chapter start on new pages
|
|
3159
|
+
# # index parts, chapters and sections by the physical page number on which they start
|
|
3160
|
+
# part_start_pages = {}
|
|
3161
|
+
# chapter_start_pages = {}
|
|
3162
|
+
# section_start_pages = {}
|
|
3163
|
+
# trailing_section_start_pages = {}
|
|
3164
|
+
# sections.each do |sect|
|
|
3165
|
+
# pgnum = (sect.attr 'pdf-page-start').to_i
|
|
3166
|
+
# if is_book && ((sect_is_part = sect.part?) || sect.chapter?)
|
|
3167
|
+
# if sect_is_part
|
|
3168
|
+
# part_start_pages[pgnum] ||= sect
|
|
3169
|
+
# else
|
|
3170
|
+
# chapter_start_pages[pgnum] ||= sect
|
|
3171
|
+
# # FIXME: need a better way to indicate that part has ended
|
|
3172
|
+
# part_start_pages[pgnum] = '' if sect.sectname == 'appendix' && !part_start_pages.empty?
|
|
3173
|
+
# end
|
|
3174
|
+
# else
|
|
3175
|
+
# trailing_section_start_pages[pgnum] = sect
|
|
3176
|
+
# section_start_pages[pgnum] ||= sect
|
|
3177
|
+
# end
|
|
3178
|
+
# end
|
|
3179
|
+
|
|
3180
|
+
# # index parts, chapters, and sections by the physical page number on which they appear
|
|
3181
|
+
# parts_by_page = SectionInfoByPage.new title_method
|
|
3182
|
+
# chapters_by_page = SectionInfoByPage.new title_method
|
|
3183
|
+
# sections_by_page = SectionInfoByPage.new title_method
|
|
3184
|
+
# # QUESTION should the default part be the doctitle?
|
|
3185
|
+
# last_part = nil
|
|
3186
|
+
# # QUESTION should we enforce that the preamble is a preface?
|
|
3187
|
+
# last_chap = is_book ? :pre : nil
|
|
3188
|
+
# last_sect = nil
|
|
3189
|
+
# sect_search_threshold = 1
|
|
3190
|
+
# (1..num_pages).each do |pgnum|
|
|
3191
|
+
# if (part = part_start_pages[pgnum])
|
|
3192
|
+
# last_part = part
|
|
3193
|
+
# last_chap = nil
|
|
3194
|
+
# last_sect = nil
|
|
3195
|
+
# end
|
|
3196
|
+
# if (chap = chapter_start_pages[pgnum])
|
|
3197
|
+
# last_chap = chap
|
|
3198
|
+
# last_sect = nil
|
|
3199
|
+
# end
|
|
3200
|
+
# if (sect = section_start_pages[pgnum])
|
|
3201
|
+
# last_sect = sect
|
|
3202
|
+
# elsif part || chap
|
|
3203
|
+
# sect_search_threshold = pgnum
|
|
3204
|
+
# # NOTE we didn't find a section on this page; look back to find last section started
|
|
3205
|
+
# elsif last_sect
|
|
3206
|
+
# (sect_search_threshold..(pgnum - 1)).reverse_each do |prev|
|
|
3207
|
+
# if (sect = trailing_section_start_pages[prev])
|
|
3208
|
+
# last_sect = sect
|
|
3209
|
+
# break
|
|
3210
|
+
# end
|
|
3211
|
+
# end
|
|
3212
|
+
# end
|
|
3213
|
+
# parts_by_page[pgnum] = last_part
|
|
3214
|
+
# if toc_page_nums && (toc_page_nums.cover? pgnum)
|
|
3215
|
+
# if is_book
|
|
3216
|
+
# chapters_by_page[pgnum] = toc_title
|
|
3217
|
+
# sections_by_page[pgnum] = nil
|
|
3218
|
+
# else
|
|
3219
|
+
# chapters_by_page[pgnum] = nil
|
|
3220
|
+
# sections_by_page[pgnum] = section_start_pages[pgnum] || toc_title
|
|
3221
|
+
# end
|
|
3222
|
+
# toc_page_nums = nil if toc_page_nums.end == pgnum
|
|
3223
|
+
# elsif last_chap == :pre
|
|
3224
|
+
# chapters_by_page[pgnum] = pgnum < body_start_page_number ? doc.doctitle : (is_book ? (doc.attr 'preface-title', 'Preface') : nil)
|
|
3225
|
+
# sections_by_page[pgnum] = last_sect
|
|
3226
|
+
# else
|
|
3227
|
+
# chapters_by_page[pgnum] = last_chap
|
|
3228
|
+
# sections_by_page[pgnum] = last_sect
|
|
3229
|
+
# end
|
|
3230
|
+
# end
|
|
3231
|
+
|
|
3232
|
+
# doctitle = doc.doctitle partition: true, use_fallback: true
|
|
3233
|
+
# # NOTE set doctitle again so it's properly escaped
|
|
3234
|
+
# doc.set_attr 'doctitle', doctitle.combined
|
|
3235
|
+
# doc.set_attr 'document-title', doctitle.main
|
|
3236
|
+
# doc.set_attr 'document-subtitle', doctitle.subtitle
|
|
3237
|
+
# doc.set_attr 'page-count', (num_pages - skip_pagenums)
|
|
3238
|
+
|
|
3239
|
+
# pagenums_enabled = doc.attr? 'pagenums'
|
|
3240
|
+
# case @media == 'prepress' ? 'physical' : (doc.attr 'pdf-folio-placement')
|
|
3241
|
+
# when 'physical'
|
|
3242
|
+
# folio_basis, invert_folio = :physical, false
|
|
3243
|
+
# when 'physical-inverted'
|
|
3244
|
+
# folio_basis, invert_folio = :physical, true
|
|
3245
|
+
# when 'virtual-inverted'
|
|
3246
|
+
# folio_basis, invert_folio = :virtual, true
|
|
3247
|
+
# else
|
|
3248
|
+
# folio_basis, invert_folio = :virtual, false
|
|
3249
|
+
# end
|
|
3250
|
+
# periphery_layout_cache = {}
|
|
3251
|
+
# # NOTE: this block is invoked during PDF generation, after convert_document has returned
|
|
3252
|
+
# repeat (content_start_page..num_pages), dynamic: true do
|
|
3253
|
+
# pgnum = page_number
|
|
3254
|
+
# # NOTE: don't write on pages which are imported / inserts (otherwise we can get a corrupt PDF)
|
|
3255
|
+
# next if page.imported_page? || (disable_on_pages && (disable_on_pages.include? pgnum))
|
|
3256
|
+
# virtual_pgnum = pgnum - skip_pagenums
|
|
3257
|
+
# pgnum_label = (virtual_pgnum < 1 ? (RomanNumeral.new pgnum, :lower) : virtual_pgnum).to_s
|
|
3258
|
+
# side = page_side((folio_basis == :physical ? pgnum : virtual_pgnum), invert_folio)
|
|
3259
|
+
# doc.set_attr 'page-layout', page.layout.to_s
|
|
3260
|
+
|
|
3261
|
+
# # NOTE: running content is cached per page layout
|
|
3262
|
+
# # QUESTION: should allocation be per side?
|
|
3263
|
+
# trim_styles, colspec_dict, content_dict, stamp_names = allocate_running_content_layout doc, page, periphery, periphery_layout_cache
|
|
3264
|
+
# # FIXME: we need to have a content setting for chapter pages
|
|
3265
|
+
# content_by_position, colspec_by_position = content_dict[side], colspec_dict[side]
|
|
3266
|
+
|
|
3267
|
+
# doc.set_attr 'page-number', pgnum_label if pagenums_enabled
|
|
3268
|
+
# # QUESTION should the fallback value be nil instead of empty string? or should we remove attribute if no value?
|
|
3269
|
+
# doc.set_attr 'part-title', ((part_info = parts_by_page[pgnum])[:title] || '')
|
|
3270
|
+
# if (part_numeral = part_info[:numeral])
|
|
3271
|
+
# doc.set_attr 'part-numeral', part_numeral
|
|
3272
|
+
# else
|
|
3273
|
+
# doc.remove_attr 'part-numeral'
|
|
3274
|
+
# end
|
|
3275
|
+
# doc.set_attr 'chapter-title', ((chap_info = chapters_by_page[pgnum])[:title] || '')
|
|
3276
|
+
# if (chap_numeral = chap_info[:numeral])
|
|
3277
|
+
# doc.set_attr 'chapter-numeral', chap_numeral
|
|
3278
|
+
# else
|
|
3279
|
+
# doc.remove_attr 'chapter-numeral'
|
|
3280
|
+
# end
|
|
3281
|
+
# doc.set_attr 'section-title', ((sect_info = sections_by_page[pgnum])[:title] || '')
|
|
3282
|
+
# doc.set_attr 'section-or-chapter-title', (sect_info[:title] || chap_info[:title] || '')
|
|
3283
|
+
|
|
3284
|
+
# stamp stamp_names[side] if stamp_names
|
|
3285
|
+
|
|
3286
|
+
# theme_font periphery do
|
|
3287
|
+
# canvas do
|
|
3288
|
+
# bounding_box [trim_styles[:content_left][side], trim_styles[:top][side]], width: trim_styles[:content_width][side], height: trim_styles[:height] do
|
|
3289
|
+
# if (trim_column_rule_width = trim_styles[:column_rule_width]) > 0
|
|
3290
|
+
# trim_column_rule_spacing = trim_styles[:column_rule_spacing]
|
|
3291
|
+
# else
|
|
3292
|
+
# trim_column_rule_width = nil
|
|
3293
|
+
# end
|
|
3294
|
+
# prev_position = nil
|
|
3295
|
+
# ColumnPositions.each do |position|
|
|
3296
|
+
# next unless (content = content_by_position[position])
|
|
3297
|
+
# next unless (colspec = colspec_by_position[position])[:width] > 0
|
|
3298
|
+
# left, colwidth = colspec[:x], colspec[:width]
|
|
3299
|
+
# if trim_column_rule_width && colwidth < bounds.width
|
|
3300
|
+
# if (trim_column_rule = prev_position)
|
|
3301
|
+
# left += (trim_column_rule_spacing * 0.5)
|
|
3302
|
+
# colwidth -= trim_column_rule_spacing
|
|
3303
|
+
# else
|
|
3304
|
+
# colwidth -= (trim_column_rule_spacing * 0.5)
|
|
3305
|
+
# end
|
|
3306
|
+
# end
|
|
3307
|
+
# # FIXME: we need to have a content setting for chapter pages
|
|
3308
|
+
# case content
|
|
3309
|
+
# when ::Array
|
|
3310
|
+
# # NOTE float ensures cursor position is restored and returns us to current page if we overrun
|
|
3311
|
+
# float do
|
|
3312
|
+
# # NOTE bounding_box is redundant if both vertical padding and border width are 0
|
|
3313
|
+
# bounding_box [left, bounds.top - trim_styles[:padding][side][0] - trim_styles[:content_offset]], width: colwidth, height: trim_styles[:content_height][side] do
|
|
3314
|
+
# # NOTE image vposition respects padding; use negative image_vertical_align value to revert
|
|
3315
|
+
# image_opts = content[1].merge position: colspec[:align], vposition: trim_styles[:img_valign]
|
|
3316
|
+
# begin
|
|
3317
|
+
# image_info = image content[0], image_opts
|
|
3318
|
+
# if (image_link = content[2])
|
|
3319
|
+
# image_info = { width: image_info.scaled_width, height: image_info.scaled_height } unless image_opts[:format] == 'svg'
|
|
3320
|
+
# add_link_to_image image_link, image_info, image_opts
|
|
3321
|
+
# end
|
|
3322
|
+
# rescue
|
|
3323
|
+
# logger.warn %(could not embed image in running content: #{content[0]}; #{$!.message})
|
|
3324
|
+
# end
|
|
3325
|
+
# end
|
|
3326
|
+
# end
|
|
3327
|
+
# when ::String
|
|
3328
|
+
# theme_font %(#{periphery}_#{side}_#{position}) do
|
|
3329
|
+
# # NOTE minor optimization
|
|
3330
|
+
# if content == '{page-number}'
|
|
3331
|
+
# content = pagenums_enabled ? pgnum_label : nil
|
|
3332
|
+
# else
|
|
3333
|
+
# content = apply_subs_discretely doc, content, drop_lines_with_unresolved_attributes: true
|
|
3334
|
+
# content = transform_text content, @text_transform if @text_transform
|
|
3335
|
+
# end
|
|
3336
|
+
# formatted_text_box parse_text(content, color: @font_color, inline_format: [normalize: true]),
|
|
3337
|
+
# at: [left, bounds.top - trim_styles[:padding][side][0] - trim_styles[:content_offset] + ((Array trim_styles[:valign])[0] == :center ? font.descender * 0.5 : 0)],
|
|
3338
|
+
# width: colwidth,
|
|
3339
|
+
# height: trim_styles[:prose_content_height][side],
|
|
3340
|
+
# align: colspec[:align],
|
|
3341
|
+
# valign: trim_styles[:valign],
|
|
3342
|
+
# leading: trim_styles[:line_metrics].leading,
|
|
3343
|
+
# final_gap: false,
|
|
3344
|
+
# overflow: :truncate
|
|
3345
|
+
# end
|
|
3346
|
+
# end
|
|
3347
|
+
# bounding_box [colspec[:x], bounds.top - trim_styles[:padding][side][0] - trim_styles[:content_offset]], width: colspec[:width], height: trim_styles[:content_height][side] do
|
|
3348
|
+
# stroke_vertical_rule trim_styles[:column_rule_color], at: bounds.left, line_style: trim_styles[:column_rule_style], line_width: trim_column_rule_width
|
|
3349
|
+
# end if trim_column_rule
|
|
3350
|
+
# prev_position = position
|
|
3351
|
+
# end
|
|
3352
|
+
# end
|
|
3353
|
+
# end
|
|
3354
|
+
# end
|
|
3355
|
+
# end
|
|
3356
|
+
|
|
3357
|
+
# go_to_page prev_page_number
|
|
3358
|
+
# nil
|
|
3359
|
+
# end
|
|
3360
|
+
|
|
3361
|
+
# def allocate_running_content_layout doc, page, periphery, cache
|
|
3362
|
+
# cache[layout = page.layout] ||= begin
|
|
3363
|
+
# page_margin_recto = @page_margin_by_side[:recto]
|
|
3364
|
+
# trim_margin_recto = @theme[%(#{periphery}_recto_margin)] || @theme[%(#{periphery}_margin)] || [0, 'inherit', 0, 'inherit']
|
|
3365
|
+
# trim_margin_recto = (expand_margin_value trim_margin_recto).map.with_index {|v, i| i.odd? && v == 'inherit' ? page_margin_recto[i] : v.to_f }
|
|
3366
|
+
# trim_content_margin_recto = @theme[%(#{periphery}_recto_content_margin)] || @theme[%(#{periphery}_content_margin)] || [0, 'inherit', 0, 'inherit']
|
|
3367
|
+
# trim_content_margin_recto = (expand_margin_value trim_content_margin_recto).map.with_index {|v, i| i.odd? && v == 'inherit' ? page_margin_recto[i] - trim_margin_recto[i] : v.to_f }
|
|
3368
|
+
# if (trim_padding_recto = @theme[%(#{periphery}_recto_padding)] || @theme[%(#{periphery}_padding)])
|
|
3369
|
+
# trim_padding_recto = (expand_margin_value trim_padding_recto).map.with_index {|v, i| v + trim_content_margin_recto[i] }
|
|
3370
|
+
# else
|
|
3371
|
+
# trim_padding_recto = trim_content_margin_recto
|
|
3372
|
+
# end
|
|
3373
|
+
# page_margin_verso = @page_margin_by_side[:verso]
|
|
3374
|
+
# trim_margin_verso = @theme[%(#{periphery}_verso_margin)] || @theme[%(#{periphery}_margin)] || [0, 'inherit', 0, 'inherit']
|
|
3375
|
+
# trim_margin_verso = (expand_margin_value trim_margin_verso).map.with_index {|v, i| i.odd? && v == 'inherit' ? page_margin_verso[i] : v.to_f }
|
|
3376
|
+
# trim_content_margin_verso = @theme[%(#{periphery}_verso_content_margin)] || @theme[%(#{periphery}_content_margin)] || [0, 'inherit', 0, 'inherit']
|
|
3377
|
+
# trim_content_margin_verso = (expand_margin_value trim_content_margin_verso).map.with_index {|v, i| i.odd? && v == 'inherit' ? page_margin_verso[i] - trim_margin_verso[i] : v.to_f }
|
|
3378
|
+
# if (trim_padding_verso = @theme[%(#{periphery}_verso_padding)] || @theme[%(#{periphery}_padding)])
|
|
3379
|
+
# trim_padding_verso = (expand_margin_value trim_padding_verso).map.with_index {|v, i| v + trim_content_margin_verso[i] }
|
|
3380
|
+
# else
|
|
3381
|
+
# trim_padding_verso = trim_content_margin_verso
|
|
3382
|
+
# end
|
|
3383
|
+
# valign, valign_offset = @theme[%(#{periphery}_vertical_align)]
|
|
3384
|
+
# if (valign = (valign || :middle).to_sym) == :middle
|
|
3385
|
+
# valign = :center
|
|
3386
|
+
# end
|
|
3387
|
+
# trim_styles = {
|
|
3388
|
+
# line_metrics: (trim_line_metrics = calc_line_metrics @theme[%(#{periphery}_line_height)] || @theme.base_line_height),
|
|
3389
|
+
# # NOTE we've already verified this property is set
|
|
3390
|
+
# height: (trim_height = @theme[%(#{periphery}_height)]),
|
|
3391
|
+
# bg_color: (resolve_theme_color %(#{periphery}_background_color).to_sym),
|
|
3392
|
+
# border_color: (trim_border_color = resolve_theme_color %(#{periphery}_border_color).to_sym),
|
|
3393
|
+
# border_style: (@theme[%(#{periphery}_border_style)] || :solid).to_sym,
|
|
3394
|
+
# border_width: (trim_border_width = trim_border_color ? @theme[%(#{periphery}_border_width)] || @theme.base_border_width || 0 : 0),
|
|
3395
|
+
# column_rule_color: (trim_column_rule_color = resolve_theme_color %(#{periphery}_column_rule_color).to_sym),
|
|
3396
|
+
# column_rule_style: (@theme[%(#{periphery}_column_rule_style)] || :solid).to_sym,
|
|
3397
|
+
# column_rule_width: (trim_column_rule_color ? @theme[%(#{periphery}_column_rule_width)] || 0 : 0),
|
|
3398
|
+
# column_rule_spacing: (@theme[%(#{periphery}_column_rule_spacing)] || 0),
|
|
3399
|
+
# valign: valign_offset ? [valign, valign_offset] : valign,
|
|
3400
|
+
# img_valign: @theme[%(#{periphery}_image_vertical_align)],
|
|
3401
|
+
# top: {
|
|
3402
|
+
# recto: periphery == :header ? page_height - trim_margin_recto[0] : trim_height + trim_margin_recto[2],
|
|
3403
|
+
# verso: periphery == :header ? page_height - trim_margin_verso[0] : trim_height + trim_margin_verso[2],
|
|
3404
|
+
# },
|
|
3405
|
+
# left: {
|
|
3406
|
+
# recto: (trim_left_recto = trim_margin_recto[3]),
|
|
3407
|
+
# verso: (trim_left_verso = trim_margin_verso[3]),
|
|
3408
|
+
# },
|
|
3409
|
+
# width: {
|
|
3410
|
+
# recto: (trim_width_recto = page_width - trim_left_recto - trim_margin_recto[1]),
|
|
3411
|
+
# verso: (trim_width_verso = page_width - trim_left_verso - trim_margin_verso[1]),
|
|
3412
|
+
# },
|
|
3413
|
+
# padding: {
|
|
3414
|
+
# recto: trim_padding_recto,
|
|
3415
|
+
# verso: trim_padding_verso,
|
|
3416
|
+
# },
|
|
3417
|
+
# content_left: {
|
|
3418
|
+
# recto: trim_left_recto + trim_padding_recto[3],
|
|
3419
|
+
# verso: trim_left_verso + trim_padding_verso[3],
|
|
3420
|
+
# },
|
|
3421
|
+
# content_width: (trim_content_width = {
|
|
3422
|
+
# recto: trim_width_recto - trim_padding_recto[1] - trim_padding_recto[3],
|
|
3423
|
+
# verso: trim_width_verso - trim_padding_verso[1] - trim_padding_verso[3],
|
|
3424
|
+
# }),
|
|
3425
|
+
# content_height: (trim_content_height = {
|
|
3426
|
+
# recto: trim_height - trim_padding_recto[0] - trim_padding_recto[2] - (trim_border_width * 0.5),
|
|
3427
|
+
# verso: trim_height - trim_padding_verso[0] - trim_padding_verso[2] - (trim_border_width * 0.5),
|
|
3428
|
+
# }),
|
|
3429
|
+
# prose_content_height: {
|
|
3430
|
+
# recto: trim_content_height[:recto] - trim_line_metrics.padding_top - trim_line_metrics.padding_bottom,
|
|
3431
|
+
# verso: trim_content_height[:verso] - trim_line_metrics.padding_top - trim_line_metrics.padding_bottom,
|
|
3432
|
+
# },
|
|
3433
|
+
# # NOTE content offset adjusts y position to account for border
|
|
3434
|
+
# content_offset: (periphery == :footer ? trim_border_width * 0.5 : 0),
|
|
3435
|
+
# }
|
|
3436
|
+
# case trim_styles[:img_valign]
|
|
3437
|
+
# when nil
|
|
3438
|
+
# trim_styles[:img_valign] = valign
|
|
3439
|
+
# when 'middle'
|
|
3440
|
+
# trim_styles[:img_valign] = :center
|
|
3441
|
+
# when 'top', 'center', 'bottom'
|
|
3442
|
+
# trim_styles[:img_valign] = trim_styles[:img_valign].to_sym
|
|
3443
|
+
# end
|
|
3444
|
+
|
|
3445
|
+
# if (trim_bg_image_recto = resolve_background_image doc, @theme, %(#{periphery}_background_image).to_sym, container_size: [trim_width_recto, trim_height]) && trim_bg_image_recto[0]
|
|
3446
|
+
# trim_bg_image = { recto: trim_bg_image_recto }
|
|
3447
|
+
# if trim_width_recto == trim_width_verso
|
|
3448
|
+
# trim_bg_image[:verso] = trim_bg_image_recto
|
|
3449
|
+
# else
|
|
3450
|
+
# trim_bg_image[:verso] = resolve_background_image doc, @theme, %(#{periphery}_background_image).to_sym, container_size: [trim_width_verso, trim_height]
|
|
3451
|
+
# end
|
|
3452
|
+
# end
|
|
3453
|
+
|
|
3454
|
+
# colspec_dict = PageSides.each_with_object({}) do |side, acc|
|
|
3455
|
+
# side_trim_content_width = trim_content_width[side]
|
|
3456
|
+
# if (custom_colspecs = @theme[%(#{periphery}_#{side}_columns)] || @theme[%(#{periphery}_columns)])
|
|
3457
|
+
# case (colspecs = (custom_colspecs.to_s.tr ',', ' ').split).size
|
|
3458
|
+
# when 0, 1
|
|
3459
|
+
# colspecs = { left: '0', center: colspecs[0] || '100', right: '0' }
|
|
3460
|
+
# when 2
|
|
3461
|
+
# colspecs = { left: colspecs[0], center: '0', right: colspecs[1] }
|
|
3462
|
+
# else # 3
|
|
3463
|
+
# colspecs = { left: colspecs[0], center: colspecs[1], right: colspecs[2] }
|
|
3464
|
+
# end
|
|
3465
|
+
# tot_width = 0
|
|
3466
|
+
# side_colspecs = colspecs.map {|col, spec|
|
|
3467
|
+
# if (alignment_char = spec.chr).to_i.to_s != alignment_char
|
|
3468
|
+
# alignment = AlignmentTable[alignment_char] || :left
|
|
3469
|
+
# rel_width = (spec.slice 1, spec.length).to_f
|
|
3470
|
+
# else
|
|
3471
|
+
# alignment = :left
|
|
3472
|
+
# rel_width = spec.to_f
|
|
3473
|
+
# end
|
|
3474
|
+
# tot_width += rel_width
|
|
3475
|
+
# [col, { align: alignment, width: rel_width, x: 0 }]
|
|
3476
|
+
# }.to_h
|
|
3477
|
+
# # QUESTION should we allow the columns to overlap (capping width at 100%)?
|
|
3478
|
+
# side_colspecs.each {|_, colspec| colspec[:width] = (colspec[:width] / tot_width) * side_trim_content_width }
|
|
3479
|
+
# side_colspecs[:right][:x] = (side_colspecs[:center][:x] = side_colspecs[:left][:width]) + side_colspecs[:center][:width]
|
|
3480
|
+
# acc[side] = side_colspecs
|
|
3481
|
+
# else
|
|
3482
|
+
# acc[side] = {
|
|
3483
|
+
# left: { align: :left, width: side_trim_content_width, x: 0 },
|
|
3484
|
+
# center: { align: :center, width: side_trim_content_width, x: 0 },
|
|
3485
|
+
# right: { align: :right, width: side_trim_content_width, x: 0 },
|
|
3486
|
+
# }
|
|
3487
|
+
# end
|
|
3488
|
+
# end
|
|
3489
|
+
|
|
3490
|
+
# content_dict = PageSides.each_with_object({}) do |side, acc|
|
|
3491
|
+
# side_content = {}
|
|
3492
|
+
# ColumnPositions.each do |position|
|
|
3493
|
+
# unless (val = @theme[%(#{periphery}_#{side}_#{position}_content)]).nil_or_empty?
|
|
3494
|
+
# if (val.include? ':') && val =~ ImageAttributeValueRx
|
|
3495
|
+
# attrlist = $2
|
|
3496
|
+
# image_attrs = (AttributeList.new attrlist).parse %w(alt width)
|
|
3497
|
+
# image_path, image_format = ::Asciidoctor::Image.target_and_format $1, image_attrs
|
|
3498
|
+
# if (image_path = resolve_image_path doc, image_path, image_format, @themesdir) && (::File.readable? image_path)
|
|
3499
|
+
# image_opts = resolve_image_options image_path, image_format, image_attrs, container_size: [colspec_dict[side][position][:width], trim_content_height[side]]
|
|
3500
|
+
# side_content[position] = [image_path, image_opts, image_attrs['link']]
|
|
3501
|
+
# else
|
|
3502
|
+
# # NOTE allows inline image handler to report invalid reference and replace with alt text
|
|
3503
|
+
# side_content[position] = %(image:#{image_path}[#{attrlist}])
|
|
3504
|
+
# end
|
|
3505
|
+
# else
|
|
3506
|
+
# side_content[position] = val
|
|
3507
|
+
# end
|
|
3508
|
+
# end
|
|
3509
|
+
# end
|
|
3510
|
+
|
|
3511
|
+
# acc[side] = side_content
|
|
3512
|
+
# end
|
|
3513
|
+
|
|
3514
|
+
# if (trim_bg_color = trim_styles[:bg_color]) || trim_bg_image || trim_border_width > 0
|
|
3515
|
+
# stamp_names = { recto: %(#{layout}_#{periphery}_recto), verso: %(#{layout}_#{periphery}_verso) }
|
|
3516
|
+
# PageSides.each do |side|
|
|
3517
|
+
# create_stamp stamp_names[side] do
|
|
3518
|
+
# canvas do
|
|
3519
|
+
# bounding_box [trim_styles[:left][side], trim_styles[:top][side]], width: trim_styles[:width][side], height: trim_height do
|
|
3520
|
+
# fill_bounds trim_bg_color if trim_bg_color
|
|
3521
|
+
# # NOTE: must draw line before image or SVG will cause border to disappear
|
|
3522
|
+
# stroke_horizontal_rule trim_styles[:border_color], line_width: trim_border_width, line_style: trim_styles[:border_style], at: (periphery == :header ? bounds.height : 0) if trim_border_width > 0
|
|
3523
|
+
# image trim_bg_image[side][0], ({ position: :center, vposition: :center }.merge trim_bg_image[side][1]) if trim_bg_image
|
|
3524
|
+
# end
|
|
3525
|
+
# end
|
|
3526
|
+
# end
|
|
3527
|
+
# end
|
|
3528
|
+
# end
|
|
3529
|
+
|
|
3530
|
+
# [trim_styles, colspec_dict, content_dict, stamp_names]
|
|
3531
|
+
# end
|
|
3532
|
+
# end
|
|
3533
|
+
|
|
3534
|
+
# def add_outline doc, num_levels = 2, toc_page_nums = [], num_front_matter_pages = 0, has_front_cover = false
|
|
3535
|
+
# if ::String === num_levels
|
|
3536
|
+
# if num_levels.include? ':'
|
|
3537
|
+
# num_levels, expand_levels = num_levels.split ':', 2
|
|
3538
|
+
# num_levels = num_levels.empty? ? (doc.attr 'toclevels', 2).to_i : num_levels.to_i
|
|
3539
|
+
# expand_levels = expand_levels.to_i
|
|
3540
|
+
# else
|
|
3541
|
+
# num_levels = expand_levels = num_levels.to_i
|
|
3542
|
+
# end
|
|
3543
|
+
# else
|
|
3544
|
+
# expand_levels = num_levels
|
|
3545
|
+
# end
|
|
3546
|
+
# front_matter_counter = RomanNumeral.new 0, :lower
|
|
3547
|
+
# pagenum_labels = {}
|
|
3548
|
+
|
|
3549
|
+
# num_front_matter_pages.times do |n|
|
|
3550
|
+
# pagenum_labels[n] = { P: (::PDF::Core::LiteralString.new front_matter_counter.next!.to_s) }
|
|
3551
|
+
# end
|
|
3552
|
+
|
|
3553
|
+
# # add labels for each content page, which is required for reader's page navigator to work correctly
|
|
3554
|
+
# (num_front_matter_pages..(page_count - 1)).each_with_index do |n, i|
|
|
3555
|
+
# pagenum_labels[n] = { P: (::PDF::Core::LiteralString.new (i + 1).to_s) }
|
|
3556
|
+
# end
|
|
3557
|
+
|
|
3558
|
+
# unless toc_page_nums.none? || (toc_title = doc.attr 'toc-title').nil_or_empty?
|
|
3559
|
+
# toc_section = insert_toc_section doc, toc_title, toc_page_nums
|
|
3560
|
+
# end
|
|
3561
|
+
|
|
3562
|
+
# outline.define do
|
|
3563
|
+
# initial_pagenum = has_front_cover ? 2 : 1
|
|
3564
|
+
# # FIXME: use sanitize: :plain_text once available
|
|
3565
|
+
# if document.page_count >= initial_pagenum && (doctitle = doc.header? ? doc.doctitle : (doc.attr 'untitled-label'))
|
|
3566
|
+
# page title: (document.sanitize doctitle), destination: (document.dest_top has_front_cover ? 2 : 1)
|
|
3567
|
+
# end
|
|
3568
|
+
# # QUESTION is there any way to get add_outline_level to invoke in the context of the outline?
|
|
3569
|
+
# document.add_outline_level self, doc.sections, num_levels, expand_levels
|
|
3570
|
+
# end if doc.attr? 'outline'
|
|
3571
|
+
|
|
3572
|
+
# toc_section.parent.blocks.delete toc_section if toc_section
|
|
3573
|
+
|
|
3574
|
+
# catalog.data[:PageLabels] = state.store.ref Nums: pagenum_labels.flatten
|
|
3575
|
+
# primary_page_mode, secondary_page_mode = PageModes[(doc.attr 'pdf-page-mode') || @theme.page_mode]
|
|
3576
|
+
# catalog.data[:PageMode] = primary_page_mode
|
|
3577
|
+
# catalog.data[:NonFullScreenPageMode] = secondary_page_mode if secondary_page_mode
|
|
3578
|
+
# nil
|
|
3579
|
+
# end
|
|
3580
|
+
|
|
3581
|
+
# def add_outline_level outline, sections, num_levels, expand_levels
|
|
3582
|
+
# sections.each do |sect|
|
|
3583
|
+
# sect_title = sanitize sect.numbered_title formal: true
|
|
3584
|
+
# sect_destination = sect.attr 'pdf-destination'
|
|
3585
|
+
# if (level = sect.level) == num_levels || !sect.sections?
|
|
3586
|
+
# outline.page title: sect_title, destination: sect_destination
|
|
3587
|
+
# elsif level <= num_levels
|
|
3588
|
+
# outline.section sect_title, destination: sect_destination, closed: expand_levels < 1 do
|
|
3589
|
+
# add_outline_level outline, sect.sections, num_levels, (expand_levels - 1)
|
|
3590
|
+
# end
|
|
3591
|
+
# end
|
|
3592
|
+
# end
|
|
3593
|
+
# end
|
|
3594
|
+
|
|
3595
|
+
# def insert_toc_section doc, toc_title, toc_page_nums
|
|
3596
|
+
# if (doc.attr? 'toc-placement', 'macro') && (toc_node = (doc.find_by context: :toc)[0])
|
|
3597
|
+
# if (parent_section = toc_node.parent).context == :section
|
|
3598
|
+
# grandparent_section = parent_section.parent
|
|
3599
|
+
# toc_level = parent_section.level
|
|
3600
|
+
# insert_idx = (grandparent_section.blocks.index parent_section) + 1
|
|
3601
|
+
# else
|
|
3602
|
+
# grandparent_section = doc
|
|
3603
|
+
# toc_level = doc.sections[0].level
|
|
3604
|
+
# insert_idx = 0
|
|
3605
|
+
# end
|
|
3606
|
+
# toc_dest = toc_node.attr 'pdf-destination'
|
|
3607
|
+
# else
|
|
3608
|
+
# grandparent_section = doc
|
|
3609
|
+
# toc_level = doc.sections[0].level
|
|
3610
|
+
# insert_idx = 0
|
|
3611
|
+
# toc_dest = dest_top toc_page_nums.first
|
|
3612
|
+
# end
|
|
3613
|
+
# toc_section = Section.new grandparent_section, toc_level, false, attributes: { 'pdf-destination' => toc_dest }
|
|
3614
|
+
# toc_section.title = toc_title
|
|
3615
|
+
# grandparent_section.blocks.insert insert_idx, toc_section
|
|
3616
|
+
# toc_section
|
|
3617
|
+
# end
|
|
3618
|
+
|
|
3619
|
+
# def write pdf_doc, target
|
|
3620
|
+
# if target.respond_to? :write
|
|
3621
|
+
# target = ::QuantifiableStdout.new STDOUT if target == STDOUT
|
|
3622
|
+
# pdf_doc.render target
|
|
3623
|
+
# else
|
|
3624
|
+
# pdf_doc.render_file target
|
|
3625
|
+
# # QUESTION restore attributes first?
|
|
3626
|
+
# @pdfmark&.generate_file target
|
|
3627
|
+
# (Optimizer.new @optimize, pdf_doc.min_version).generate_file target if @optimize && ((defined? ::Asciidoctor::PDF::Optimizer) || !(Helpers.require_library OptimizerRequirePath, 'rghost', :warn).nil?)
|
|
3628
|
+
# to_file = true
|
|
3629
|
+
# end
|
|
3630
|
+
# if !ENV['KEEP_ARTIFACTS']
|
|
3631
|
+
# remove_tmp_files
|
|
3632
|
+
# elsif to_file
|
|
3633
|
+
# scratch_target = (target.slice 0, target.length - (target_ext = ::File.extname target).length) + '-scratch' + target_ext
|
|
3634
|
+
# get_scratch_document.render_file scratch_target
|
|
3635
|
+
# end
|
|
3636
|
+
# clear_scratch
|
|
3637
|
+
# nil
|
|
3638
|
+
# end
|
|
3639
|
+
|
|
3640
|
+
# def register_fonts font_catalog, fonts_dir
|
|
3641
|
+
# return unless font_catalog
|
|
3642
|
+
# dirs = (fonts_dir.split ValueSeparatorRx, -1).map do |dir|
|
|
3643
|
+
# dir == 'GEM_FONTS_DIR' || dir.empty? ? ThemeLoader::FontsDir : dir
|
|
3644
|
+
# end
|
|
3645
|
+
# font_catalog.each do |key, styles|
|
|
3646
|
+
# styles = styles.each_with_object({}) do |(style, path), accum|
|
|
3647
|
+
# found = dirs.find do |dir|
|
|
3648
|
+
# resolved_font_path = font_path path, dir
|
|
3649
|
+
# if ::File.readable? resolved_font_path
|
|
3650
|
+
# accum[style.to_sym] = resolved_font_path
|
|
3651
|
+
# true
|
|
3652
|
+
# end
|
|
3653
|
+
# end
|
|
3654
|
+
# raise ::Errno::ENOENT, ((File.absolute_path? path) ? %(#{path} not found) : %(#{path} not found in #{fonts_dir.gsub ValueSeparatorRx, ' or '})) unless found
|
|
3655
|
+
# end
|
|
3656
|
+
# register_font key => styles
|
|
3657
|
+
# end
|
|
3658
|
+
# end
|
|
3659
|
+
|
|
3660
|
+
# def font_path font_file, fonts_dir
|
|
3661
|
+
# # resolve relative to built-in font dir unless path is absolute
|
|
3662
|
+
# ::File.absolute_path font_file, fonts_dir
|
|
3663
|
+
# end
|
|
3664
|
+
|
|
3665
|
+
# def fallback_svg_font_name
|
|
3666
|
+
# @theme.svg_fallback_font_family || @theme.svg_font_family || @theme.base_font_family
|
|
3667
|
+
# end
|
|
3668
|
+
|
|
3669
|
+
# def apply_text_decoration styles, category, level = nil
|
|
3670
|
+
# if (text_decoration_style = TextDecorationStyleTable[(level && @theme[%(#{category}_h#{level}_text_decoration)]) || @theme[%(#{category}_text_decoration)]])
|
|
3671
|
+
# {
|
|
3672
|
+
# styles: (styles << text_decoration_style),
|
|
3673
|
+
# text_decoration_color: (level && @theme[%(#{category}_h#{level}_text_decoration_color)]) || @theme[%(#{category}_text_decoration_color)],
|
|
3674
|
+
# text_decoration_width: (level && @theme[%(#{category}_h#{level}_text_decoration_width)]) || @theme[%(#{category}_text_decoration_width)],
|
|
3675
|
+
# }.compact
|
|
3676
|
+
# else
|
|
3677
|
+
# styles.empty? ? {} : { styles: styles }
|
|
3678
|
+
# end
|
|
3679
|
+
# end
|
|
3680
|
+
|
|
3681
|
+
# def resolve_text_transform key, use_fallback = true
|
|
3682
|
+
# if (transform = ::Hash === key ? (key.delete :text_transform) : @theme[key.to_s])
|
|
3683
|
+
# transform == 'none' ? nil : transform
|
|
3684
|
+
# elsif use_fallback
|
|
3685
|
+
# @text_transform
|
|
3686
|
+
# end
|
|
3687
|
+
# end
|
|
3688
|
+
|
|
3689
|
+
# # QUESTION should we pass a category as an argument?
|
|
3690
|
+
# # QUESTION should we make this a method on the theme ostruct? (e.g., @theme.resolve_color key, fallback)
|
|
3691
|
+
# def resolve_theme_color key, fallback_color = nil
|
|
3692
|
+
# if (color = @theme[key.to_s]) && color != 'transparent'
|
|
3693
|
+
# color
|
|
3694
|
+
# else
|
|
3695
|
+
# fallback_color
|
|
3696
|
+
# end
|
|
3697
|
+
# end
|
|
3698
|
+
|
|
3699
|
+
# def resolve_font_kerning keyword, fallback = default_kerning?
|
|
3700
|
+
# keyword && (FontKerningTable.key? keyword) ? FontKerningTable[keyword] : fallback
|
|
3701
|
+
# end
|
|
3702
|
+
|
|
3703
|
+
# def theme_fill_and_stroke_bounds category, opts = {}
|
|
3704
|
+
# bg_color = (opts.key? :background_color) ? opts[:background_color] : @theme[%(#{category}_background_color)]
|
|
3705
|
+
# fill_and_stroke_bounds bg_color, @theme[%(#{category}_border_color)],
|
|
3706
|
+
# line_width: (@theme[%(#{category}_border_width)] || 0),
|
|
3707
|
+
# line_style: (@theme[%(#{category}_border_style)] || :solid).to_sym,
|
|
3708
|
+
# radius: @theme[%(#{category}_border_radius)]
|
|
3709
|
+
# end
|
|
3710
|
+
|
|
3711
|
+
# def theme_fill_and_stroke_block category, block_height, opts = {}
|
|
3712
|
+
# if (b_width = (opts.key? :border_width) ? opts[:border_width] : @theme[%(#{category}_border_width)])
|
|
3713
|
+
# b_width = nil unless b_width > 0
|
|
3714
|
+
# end
|
|
3715
|
+
# if (bg_color = opts[:background_color] || @theme[%(#{category}_background_color)]) == 'transparent'
|
|
3716
|
+
# bg_color = nil
|
|
3717
|
+
# end
|
|
3718
|
+
# unless b_width || bg_color
|
|
3719
|
+
# (node = opts[:caption_node]) && node.title? && (layout_caption node, category: category)
|
|
3720
|
+
# return
|
|
3721
|
+
# end
|
|
3722
|
+
# if (b_color = @theme[%(#{category}_border_color)]) == 'transparent'
|
|
3723
|
+
# b_color = @page_bg_color
|
|
3724
|
+
# end
|
|
3725
|
+
# b_radius = (@theme[%(#{category}_border_radius)] || 0) + (b_width || 0)
|
|
3726
|
+
# if b_width && b_color
|
|
3727
|
+
# if b_color == @page_bg_color # let page background cut into block background
|
|
3728
|
+
# b_gap_color, b_shift = @page_bg_color, (b_width * 0.5)
|
|
3729
|
+
# elsif (b_gap_color = bg_color) && b_gap_color != b_color
|
|
3730
|
+
# b_shift = 0
|
|
3731
|
+
# else # let page background cut into border
|
|
3732
|
+
# b_gap_color, b_shift = @page_bg_color, 0
|
|
3733
|
+
# end
|
|
3734
|
+
# else # let page background cut into block background
|
|
3735
|
+
# b_shift, b_gap_color = (b_width ||= 0.5) * 0.5, @page_bg_color
|
|
3736
|
+
# end
|
|
3737
|
+
# # FIXME: due to the calculation error logged in #789, we must advance page even when content is split across pages
|
|
3738
|
+
# advance_page if (opts.fetch :split_from_top, true) && block_height > cursor && !at_page_top?
|
|
3739
|
+
# caption_height = (node = opts[:caption_node]) && node.title? ? (layout_caption node, category: category) : 0
|
|
3740
|
+
# float do
|
|
3741
|
+
# remaining_height = block_height - caption_height
|
|
3742
|
+
# initial_page = true
|
|
3743
|
+
# while remaining_height > 0
|
|
3744
|
+
# advance_page unless initial_page
|
|
3745
|
+
# chunk_height = [(available_height = cursor), remaining_height].min
|
|
3746
|
+
# bounding_box [0, available_height], width: bounds.width, height: chunk_height do
|
|
3747
|
+
# theme_fill_and_stroke_bounds category, background_color: bg_color
|
|
3748
|
+
# # NOTE b_width is always set; if no border is set, split indicator is cut into background
|
|
3749
|
+
# indent b_radius, b_radius do
|
|
3750
|
+
# # dashed line indicates continuation from previous page; swell line slightly to cover background
|
|
3751
|
+
# stroke_horizontal_rule b_gap_color, line_width: b_width * 1.2, line_style: :dashed, at: b_shift
|
|
3752
|
+
# end unless initial_page
|
|
3753
|
+
# if remaining_height > chunk_height
|
|
3754
|
+
# move_down chunk_height - b_shift
|
|
3755
|
+
# indent b_radius, b_radius do
|
|
3756
|
+
# # dashed line indicates continuation from previous page; swell line slightly to cover background
|
|
3757
|
+
# stroke_horizontal_rule b_gap_color, line_width: b_width * 1.2, line_style: :dashed
|
|
3758
|
+
# end
|
|
3759
|
+
# end
|
|
3760
|
+
# end
|
|
3761
|
+
# initial_page = false
|
|
3762
|
+
# remaining_height -= chunk_height
|
|
3763
|
+
# end
|
|
3764
|
+
# end
|
|
3765
|
+
# end
|
|
3766
|
+
|
|
3767
|
+
# # Insert a top margin equal to amount if cursor is not at the top of the
|
|
3768
|
+
# # page. Start a new page instead if amount is greater than the remaining
|
|
3769
|
+
# # space on the page.
|
|
3770
|
+
# def margin_top amount
|
|
3771
|
+
# margin amount, :top
|
|
3772
|
+
# end
|
|
3773
|
+
|
|
3774
|
+
# # Insert a bottom margin equal to amount unless cursor is at the top of the
|
|
3775
|
+
# # page (not likely). Start a new page instead if amount is greater than the
|
|
3776
|
+
# # remaining space on the page.
|
|
3777
|
+
# def margin_bottom amount
|
|
3778
|
+
# margin amount, :bottom
|
|
3779
|
+
# end
|
|
3780
|
+
|
|
3781
|
+
# # Insert a margin at the specified side if the cursor is not at the top of
|
|
3782
|
+
# # the page. Start a new page if amount is greater than the remaining space on
|
|
3783
|
+
# # the page.
|
|
3784
|
+
# def margin amount, _side
|
|
3785
|
+
# unless (amount || 0) == 0 || at_page_top?
|
|
3786
|
+
# # NOTE use low-level cursor calculation to workaround cursor bug in column_box context
|
|
3787
|
+
# if y - reference_bounds.absolute_bottom > amount
|
|
3788
|
+
# move_down amount
|
|
3789
|
+
# else
|
|
3790
|
+
# # set cursor at top of next page
|
|
3791
|
+
# reference_bounds.move_past_bottom
|
|
3792
|
+
# end
|
|
3793
|
+
# end
|
|
3794
|
+
# end
|
|
3795
|
+
|
|
3796
|
+
# # Lookup margin for theme element and side, then delegate to margin method.
|
|
3797
|
+
# # If margin value is not found, assume:
|
|
3798
|
+
# # - 0 when side == :top
|
|
3799
|
+
# # - @theme.vertical_spacing when side == :bottom
|
|
3800
|
+
# def theme_margin category, side
|
|
3801
|
+
# margin((@theme[%(#{category}_margin_#{side})] || (side == :bottom ? @theme.vertical_spacing : 0)), side)
|
|
3802
|
+
# end
|
|
3803
|
+
|
|
3804
|
+
# def theme_font category, opts = {}
|
|
3805
|
+
# result = nil
|
|
3806
|
+
# # TODO: inheriting from generic category should be an option
|
|
3807
|
+
# if opts.key? :level
|
|
3808
|
+
# hlevel_category = %(#{category}_h#{opts[:level]})
|
|
3809
|
+
# family = @theme[%(#{hlevel_category}_font_family)] || @theme[%(#{category}_font_family)] || @theme.base_font_family || font_family
|
|
3810
|
+
# size = @theme[%(#{hlevel_category}_font_size)] || @theme[%(#{category}_font_size)] || @root_font_size
|
|
3811
|
+
# style = @theme[%(#{hlevel_category}_font_style)] || @theme[%(#{category}_font_style)]
|
|
3812
|
+
# color = @theme[%(#{hlevel_category}_font_color)] || @theme[%(#{category}_font_color)]
|
|
3813
|
+
# kerning = resolve_font_kerning @theme[%(#{hlevel_category}_font_kerning)] || @theme[%(#{category}_font_kerning)], nil
|
|
3814
|
+
# # NOTE global text_transform is not currently supported
|
|
3815
|
+
# transform = @theme[%(#{hlevel_category}_text_transform)] || @theme[%(#{category}_text_transform)]
|
|
3816
|
+
# else
|
|
3817
|
+
# inherited_font = font_info
|
|
3818
|
+
# family = @theme[%(#{category}_font_family)] || inherited_font[:family]
|
|
3819
|
+
# size = @theme[%(#{category}_font_size)] || inherited_font[:size]
|
|
3820
|
+
# style = @theme[%(#{category}_font_style)] || inherited_font[:style]
|
|
3821
|
+
# color = @theme[%(#{category}_font_color)]
|
|
3822
|
+
# kerning = resolve_font_kerning @theme[%(#{category}_font_kerning)], nil
|
|
3823
|
+
# # NOTE global text_transform is not currently supported
|
|
3824
|
+
# transform = @theme[%(#{category}_text_transform)]
|
|
3825
|
+
# end
|
|
3826
|
+
|
|
3827
|
+
# prev_color, @font_color = @font_color, color if color
|
|
3828
|
+
# prev_kerning, self.default_kerning = default_kerning?, kerning unless kerning.nil?
|
|
3829
|
+
# prev_transform, @text_transform = @text_transform, (transform == 'none' ? nil : transform) if transform
|
|
3830
|
+
|
|
3831
|
+
# font family, size: size, style: (style && style.to_sym) do
|
|
3832
|
+
# result = yield
|
|
3833
|
+
# end
|
|
3834
|
+
|
|
3835
|
+
# @font_color = prev_color if color
|
|
3836
|
+
# default_kerning prev_kerning unless kerning.nil?
|
|
3837
|
+
# @text_transform = prev_transform if transform
|
|
3838
|
+
# result
|
|
3839
|
+
# end
|
|
3840
|
+
|
|
3841
|
+
# # Calculate the font size (down to the minimum font size) that would allow
|
|
3842
|
+
# # all the specified fragments to fit in the available width without wrapping lines.
|
|
3843
|
+
# #
|
|
3844
|
+
# # Return the calculated font size if an adjustment is necessary or nil if no
|
|
3845
|
+
# # font size adjustment is necessary.
|
|
3846
|
+
# def compute_autofit_font_size fragments, category
|
|
3847
|
+
# arranger = arrange_fragments_by_line fragments
|
|
3848
|
+
# # NOTE finalizing the line here generates fragments & calculates their widths using the current font settings
|
|
3849
|
+
# # CAUTION it also removes zero-width spaces
|
|
3850
|
+
# arranger.finalize_line
|
|
3851
|
+
# actual_width = width_of_fragments arranger.fragments
|
|
3852
|
+
# unless ::Array === (padding = @theme[%(#{category}_padding)])
|
|
3853
|
+
# padding = ::Array.new 4, padding
|
|
3854
|
+
# end
|
|
3855
|
+
# available_width = bounds.width - (padding[3] || 0) - (padding[1] || 0)
|
|
3856
|
+
# if actual_width > available_width
|
|
3857
|
+
# adjusted_font_size = ((available_width * font_size).to_f / actual_width).truncate 4
|
|
3858
|
+
# if (min = @theme[%(#{category}_font_size_min)] || @theme.base_font_size_min) && adjusted_font_size < min
|
|
3859
|
+
# min
|
|
3860
|
+
# else
|
|
3861
|
+
# adjusted_font_size
|
|
3862
|
+
# end
|
|
3863
|
+
# end
|
|
3864
|
+
# end
|
|
3865
|
+
|
|
3866
|
+
# # Arrange fragments by line in an arranger and return an unfinalized arranger.
|
|
3867
|
+
# #
|
|
3868
|
+
# # Finalizing the arranger is deferred since it must be done in the context of
|
|
3869
|
+
# # the global font settings you want applied to each fragment.
|
|
3870
|
+
# def arrange_fragments_by_line fragments, _opts = {}
|
|
3871
|
+
# arranger = ::Prawn::Text::Formatted::Arranger.new self
|
|
3872
|
+
# by_line = arranger.consumed = []
|
|
3873
|
+
# fragments.each do |fragment|
|
|
3874
|
+
# if (text = fragment[:text]) == LF
|
|
3875
|
+
# by_line << fragment
|
|
3876
|
+
# elsif text.include? LF
|
|
3877
|
+
# text.scan LineScanRx do |line|
|
|
3878
|
+
# by_line << (line == LF ? { text: LF } : (fragment.merge text: line))
|
|
3879
|
+
# end
|
|
3880
|
+
# else
|
|
3881
|
+
# by_line << fragment
|
|
3882
|
+
# end
|
|
3883
|
+
# end
|
|
3884
|
+
# arranger
|
|
3885
|
+
# end
|
|
3886
|
+
|
|
3887
|
+
# # Calculate the width that is needed to print all the
|
|
3888
|
+
# # fragments without wrapping any lines.
|
|
3889
|
+
# #
|
|
3890
|
+
# # This method assumes endlines are represented as discrete entries in the
|
|
3891
|
+
# # fragments array.
|
|
3892
|
+
# def width_of_fragments fragments
|
|
3893
|
+
# line_widths = [0]
|
|
3894
|
+
# fragments.each do |fragment|
|
|
3895
|
+
# if fragment.text == LF
|
|
3896
|
+
# line_widths << 0
|
|
3897
|
+
# else
|
|
3898
|
+
# line_widths[-1] += fragment.width
|
|
3899
|
+
# end
|
|
3900
|
+
# end
|
|
3901
|
+
# line_widths.max
|
|
3902
|
+
# end
|
|
3903
|
+
|
|
3904
|
+
# # Compute the rendered width of a string, taking fallback fonts into account
|
|
3905
|
+
# def rendered_width_of_string str, opts = {}
|
|
3906
|
+
# opts = opts.merge kerning: default_kerning?
|
|
3907
|
+
# if str.length == 1
|
|
3908
|
+
# rendered_width_of_char str, opts
|
|
3909
|
+
# elsif (chars = str.each_char).all? {|char| font.glyph_present? char }
|
|
3910
|
+
# width_of_string str, opts
|
|
3911
|
+
# else
|
|
3912
|
+
# char_widths = chars.map {|char| rendered_width_of_char char, opts }
|
|
3913
|
+
# char_widths.sum + (char_widths.length * character_spacing)
|
|
3914
|
+
# end
|
|
3915
|
+
# end
|
|
3916
|
+
|
|
3917
|
+
# # Compute the rendered width of a char, taking fallback fonts into account
|
|
3918
|
+
# def rendered_width_of_char char, opts = {}
|
|
3919
|
+
# unless @fallback_fonts.empty? || (font.glyph_present? char)
|
|
3920
|
+
# @fallback_fonts.each do |fallback_font|
|
|
3921
|
+
# font fallback_font do
|
|
3922
|
+
# return width_of_string char, opts if font.glyph_present? char
|
|
3923
|
+
# end
|
|
3924
|
+
# end
|
|
3925
|
+
# end
|
|
3926
|
+
# width_of_string char, opts
|
|
3927
|
+
# end
|
|
3928
|
+
|
|
3929
|
+
# # TODO: document me, esp the first line formatting functionality
|
|
3930
|
+
# def typeset_text string, line_metrics, opts = {}
|
|
3931
|
+
# move_down line_metrics.padding_top
|
|
3932
|
+
# opts = { leading: line_metrics.leading, final_gap: line_metrics.final_gap }.merge opts
|
|
3933
|
+
# string = string.gsub CjkLineBreakRx, ZeroWidthSpace if @cjk_line_breaks
|
|
3934
|
+
# if (hanging_indent = (opts.delete :hanging_indent) || 0) > 0
|
|
3935
|
+
# indent hanging_indent do
|
|
3936
|
+
# text string, (opts.merge indent_paragraphs: -hanging_indent)
|
|
3937
|
+
# end
|
|
3938
|
+
# elsif (first_line_opts = opts.delete :first_line_options)
|
|
3939
|
+
# # TODO: good candidate for Prawn enhancement!
|
|
3940
|
+
# text_with_formatted_first_line string, first_line_opts, opts
|
|
3941
|
+
# else
|
|
3942
|
+
# text string, opts
|
|
3943
|
+
# end
|
|
3944
|
+
# move_down line_metrics.padding_bottom
|
|
3945
|
+
# end
|
|
3946
|
+
|
|
3947
|
+
# # QUESTION combine with typeset_text?
|
|
3948
|
+
# def typeset_formatted_text fragments, line_metrics, opts = {}
|
|
3949
|
+
# move_down line_metrics.padding_top
|
|
3950
|
+
# opts = { leading: line_metrics.leading, final_gap: line_metrics.final_gap }.merge opts
|
|
3951
|
+
# if (hanging_indent = (opts.delete :hanging_indent) || 0) > 0
|
|
3952
|
+
# indent hanging_indent do
|
|
3953
|
+
# formatted_text fragments, (opts.merge indent_paragraphs: -hanging_indent)
|
|
3954
|
+
# end
|
|
3955
|
+
# else
|
|
3956
|
+
# formatted_text fragments, opts
|
|
3957
|
+
# end
|
|
3958
|
+
# move_down line_metrics.padding_bottom
|
|
3959
|
+
# end
|
|
3960
|
+
|
|
3961
|
+
# def height_of_typeset_text string, opts = {}
|
|
3962
|
+
# line_metrics = (calc_line_metrics opts[:line_height] || @theme.base_line_height)
|
|
3963
|
+
# (height_of string, leading: line_metrics.leading, final_gap: line_metrics.final_gap) + line_metrics.padding_top + (opts[:single_line] ? 0 : line_metrics.padding_bottom)
|
|
3964
|
+
# end
|
|
3965
|
+
|
|
3966
|
+
# # NOTE only used when tabsize attribute is not specified
|
|
3967
|
+
# # tabs must always be replaced with spaces in order for the indentation guards to work
|
|
3968
|
+
# def expand_tabs string
|
|
3969
|
+
# if string.nil_or_empty?
|
|
3970
|
+
# ''
|
|
3971
|
+
# elsif string.include? TAB
|
|
3972
|
+
# full_tab_space = ' ' * (tab_size = 4)
|
|
3973
|
+
# (string.split LF, -1).map {|line|
|
|
3974
|
+
# if line.empty?
|
|
3975
|
+
# line
|
|
3976
|
+
# elsif (tab_idx = line.index TAB)
|
|
3977
|
+
# if tab_idx == 0
|
|
3978
|
+
# leading_tabs = 0
|
|
3979
|
+
# line.each_byte do |b|
|
|
3980
|
+
# break unless b == 9
|
|
3981
|
+
# leading_tabs += 1
|
|
3982
|
+
# end
|
|
3983
|
+
# line = %(#{full_tab_space * leading_tabs}#{rest = line.slice leading_tabs, line.length})
|
|
3984
|
+
# next line unless rest.include? TAB
|
|
3985
|
+
# end
|
|
3986
|
+
# # keeps track of how many spaces were added to adjust offset in match data
|
|
3987
|
+
# spaces_added = 0
|
|
3988
|
+
# idx = 0
|
|
3989
|
+
# result = ''
|
|
3990
|
+
# line.each_char do |c|
|
|
3991
|
+
# if c == TAB
|
|
3992
|
+
# # calculate how many spaces this tab represents, then replace tab with spaces
|
|
3993
|
+
# if (offset = idx + spaces_added) % tab_size == 0
|
|
3994
|
+
# spaces_added += (tab_size - 1)
|
|
3995
|
+
# result += full_tab_space
|
|
3996
|
+
# else
|
|
3997
|
+
# unless (spaces = tab_size - offset % tab_size) == 1
|
|
3998
|
+
# spaces_added += (spaces - 1)
|
|
3999
|
+
# end
|
|
4000
|
+
# result += (' ' * spaces)
|
|
4001
|
+
# end
|
|
4002
|
+
# else
|
|
4003
|
+
# result += c
|
|
4004
|
+
# end
|
|
4005
|
+
# idx += 1
|
|
4006
|
+
# end
|
|
4007
|
+
# result
|
|
4008
|
+
# else
|
|
4009
|
+
# line
|
|
4010
|
+
# end
|
|
4011
|
+
# }.join LF
|
|
4012
|
+
# else
|
|
4013
|
+
# string
|
|
4014
|
+
# end
|
|
4015
|
+
# end
|
|
4016
|
+
|
|
4017
|
+
# # Add an indentation guard at the start of indented lines.
|
|
4018
|
+
# # Expand tabs to spaces if tabs are present
|
|
4019
|
+
# def guard_indentation string
|
|
4020
|
+
# unless (string = expand_tabs string).empty?
|
|
4021
|
+
# string[0] = GuardedIndent if string.start_with? ' '
|
|
4022
|
+
# string.gsub! InnerIndent, GuardedInnerIndent if string.include? InnerIndent
|
|
4023
|
+
# end
|
|
4024
|
+
# string
|
|
4025
|
+
# end
|
|
4026
|
+
|
|
4027
|
+
# def guard_indentation_in_fragments fragments
|
|
4028
|
+
# start_of_line = true
|
|
4029
|
+
# fragments.each do |fragment|
|
|
4030
|
+
# next if (text = fragment[:text]).empty?
|
|
4031
|
+
# if start_of_line && (text.start_with? ' ')
|
|
4032
|
+
# fragment[:text] = GuardedIndent + (((text = text.slice 1, text.length).include? InnerIndent) ? (text.gsub InnerIndent, GuardedInnerIndent) : text)
|
|
4033
|
+
# elsif text.include? InnerIndent
|
|
4034
|
+
# fragment[:text] = text.gsub InnerIndent, GuardedInnerIndent
|
|
4035
|
+
# end
|
|
4036
|
+
# start_of_line = text.end_with? LF
|
|
4037
|
+
# end
|
|
4038
|
+
# fragments
|
|
4039
|
+
# end
|
|
4040
|
+
|
|
4041
|
+
# # Derive a PDF-safe, ASCII-only anchor name from the given value.
|
|
4042
|
+
# # Encodes value into hex if it contains characters outside the ASCII range.
|
|
4043
|
+
# # If value is nil, derive an anchor name from the default_value, if given.
|
|
4044
|
+
# def derive_anchor_from_id value, default_value = nil
|
|
4045
|
+
# if value
|
|
4046
|
+
# value.ascii_only? ? value : %(0x#{::PDF::Core.string_to_hex value})
|
|
4047
|
+
# else
|
|
4048
|
+
# %(__anchor-#{default_value})
|
|
4049
|
+
# end
|
|
4050
|
+
# end
|
|
4051
|
+
|
|
4052
|
+
# # If an id is provided or the node passed as the first argument has an id,
|
|
4053
|
+
# # add a named destination to the document equivalent to the node id at the
|
|
4054
|
+
# # current y position. If the node does not have an id and an id is not
|
|
4055
|
+
# # specified, do nothing.
|
|
4056
|
+
# #
|
|
4057
|
+
# # If the node is a section, and the current y position is the top of the
|
|
4058
|
+
# # page, set the y position equal to the page height to improve the navigation
|
|
4059
|
+
# # experience. If the current x position is at or inside the left margin, set
|
|
4060
|
+
# # the x position equal to 0 (left edge of page) to improve the navigation
|
|
4061
|
+
# # experience.
|
|
4062
|
+
# def add_dest_for_block node, id = nil
|
|
4063
|
+
# if !scratch? && (id ||= node.id)
|
|
4064
|
+
# dest_x = bounds.absolute_left.truncate 4
|
|
4065
|
+
# # QUESTION when content is aligned to left margin, should we keep precise x value or just use 0?
|
|
4066
|
+
# dest_x = 0 if dest_x <= page_margin_left
|
|
4067
|
+
# dest_y = at_page_top? && (node.context == :section || node.context == :document) ? page_height : y
|
|
4068
|
+
# # TODO: find a way to store only the ref of the destination; look it up when we need it
|
|
4069
|
+
# node.set_attr 'pdf-destination', (node_dest = (dest_xyz dest_x, dest_y))
|
|
4070
|
+
# add_dest id, node_dest
|
|
4071
|
+
# end
|
|
4072
|
+
# nil
|
|
4073
|
+
# end
|
|
4074
|
+
|
|
4075
|
+
# def resolve_alignment_from_role roles
|
|
4076
|
+
# if (align_role = roles.reverse.find {|r| TextAlignmentRoles.include? r })
|
|
4077
|
+
# (align_role.slice 5, align_role.length).to_sym
|
|
4078
|
+
# end
|
|
4079
|
+
# end
|
|
4080
|
+
|
|
4081
|
+
# # QUESTION is this method still necessary?
|
|
4082
|
+
# def resolve_imagesdir doc
|
|
4083
|
+
# if (imagesdir = doc.attr 'imagesdir').nil_or_empty? || (imagesdir = imagesdir.chomp '/') == '.'
|
|
4084
|
+
# nil
|
|
4085
|
+
# else
|
|
4086
|
+
# imagesdir
|
|
4087
|
+
# end
|
|
4088
|
+
# end
|
|
4089
|
+
|
|
4090
|
+
# # Resolve the system path of the specified image path.
|
|
4091
|
+
# #
|
|
4092
|
+
# # Resolve and normalize the absolute system path of the specified image,
|
|
4093
|
+
# # taking into account the imagesdir attribute. If an image path is not
|
|
4094
|
+
# # specified, the path is read from the target attribute of the specified
|
|
4095
|
+
# # document node.
|
|
4096
|
+
# #
|
|
4097
|
+
# # If the target is a URI and the allow-uri-read attribute is set on the
|
|
4098
|
+
# # document, read the file contents to a temporary file and return the path to
|
|
4099
|
+
# # the temporary file. If the target is a URI and the allow-uri-read attribute
|
|
4100
|
+
# # is not set, or the URI cannot be read, this method returns a nil value.
|
|
4101
|
+
# #
|
|
4102
|
+
# # When a temporary file is used, the file is stored in @tmp_files to be cleaned up after conversion.
|
|
4103
|
+
# def resolve_image_path node, image_path, image_format, relative_to = true
|
|
4104
|
+
# doc = node.document
|
|
4105
|
+
# imagesdir = relative_to == true ? (resolve_imagesdir doc) : relative_to
|
|
4106
|
+
# #image_format ||= ::Asciidoctor::Image.format image_path, (::Asciidoctor::Image === node ? node.attributes : nil)
|
|
4107
|
+
# # NOTE base64 logic currently used for inline images
|
|
4108
|
+
# if ::Base64 === image_path
|
|
4109
|
+
# return @tmp_files[image_path] if @tmp_files.key? image_path
|
|
4110
|
+
# tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
|
|
4111
|
+
# tmp_image.binmode unless image_format == 'svg'
|
|
4112
|
+
# begin
|
|
4113
|
+
# tmp_image.write ::Base64.decode64 image_path
|
|
4114
|
+
# tmp_image.close
|
|
4115
|
+
# @tmp_files[image_path] = tmp_image.path
|
|
4116
|
+
# rescue
|
|
4117
|
+
# @tmp_files[image_path] = nil
|
|
4118
|
+
# tmp_image.close
|
|
4119
|
+
# unlink_tmp_file tmp_image.path
|
|
4120
|
+
# nil
|
|
4121
|
+
# end
|
|
4122
|
+
# # handle case when image is a URI
|
|
4123
|
+
# elsif (node.is_uri? image_path) ||
|
|
4124
|
+
# (imagesdir && (node.is_uri? imagesdir) && (image_path = node.normalize_web_path image_path, imagesdir, false))
|
|
4125
|
+
# unless allow_uri_read
|
|
4126
|
+
# logger.warn %(allow-uri-read is not enabled; cannot embed remote image: #{image_path}) unless scratch?
|
|
4127
|
+
# return
|
|
4128
|
+
# end
|
|
4129
|
+
# return @tmp_files[image_path] if @tmp_files.key? image_path
|
|
4130
|
+
# tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
|
|
4131
|
+
# tmp_image.binmode if (binary = image_format != 'svg')
|
|
4132
|
+
# begin
|
|
4133
|
+
# load_open_uri.open_uri(image_path, (binary ? 'rb' : 'r')) {|fd| tmp_image.write fd.read }
|
|
4134
|
+
# tmp_image.close
|
|
4135
|
+
# @tmp_files[image_path] = tmp_image.path
|
|
4136
|
+
# rescue
|
|
4137
|
+
# @tmp_files[image_path] = nil
|
|
4138
|
+
# logger.warn %(could not retrieve remote image: #{image_path}; #{$!.message}) unless scratch?
|
|
4139
|
+
# tmp_image.close
|
|
4140
|
+
# unlink_tmp_file tmp_image.path
|
|
4141
|
+
# nil
|
|
4142
|
+
# end
|
|
4143
|
+
# # handle case when image is a local file
|
|
4144
|
+
# else
|
|
4145
|
+
# node.normalize_system_path image_path, imagesdir, nil, target_name: 'image'
|
|
4146
|
+
# end
|
|
4147
|
+
# end
|
|
4148
|
+
|
|
4149
|
+
# # Resolve the path and sizing of the background image either from a document attribute or theme key.
|
|
4150
|
+
# #
|
|
4151
|
+
# # Returns the argument list for the image method if the document attribute or theme key is found. Otherwise,
|
|
4152
|
+
# # nothing. The first argument in the argument list is the image path. If that value is nil, the background
|
|
4153
|
+
# # image is disabled. The second argument is the options hash to specify the dimensions, such as width and fit.
|
|
4154
|
+
# def resolve_background_image doc, theme, key, opts = {}
|
|
4155
|
+
# if ::String === key
|
|
4156
|
+
# theme_key = opts.delete :theme_key
|
|
4157
|
+
# image_path = (doc.attr key) || (from_theme = theme[theme_key || (key.tr '-', '_').to_sym])
|
|
4158
|
+
# else
|
|
4159
|
+
# image_path = from_theme = theme[key]
|
|
4160
|
+
# end
|
|
4161
|
+
# symbolic_paths = opts.delete :symbolic_paths
|
|
4162
|
+
# if image_path
|
|
4163
|
+
# if symbolic_paths && (symbolic_paths.include? image_path)
|
|
4164
|
+
# return [image_path, {}]
|
|
4165
|
+
# elsif image_path == 'none'
|
|
4166
|
+
# return []
|
|
4167
|
+
# elsif (image_path.include? ':') && image_path =~ ImageAttributeValueRx
|
|
4168
|
+
# image_attrs = (AttributeList.new $2).parse %w(alt width)
|
|
4169
|
+
# if from_theme
|
|
4170
|
+
# image_path = sub_attributes_discretely doc, $1
|
|
4171
|
+
# image_relative_to = @themesdir
|
|
4172
|
+
# else
|
|
4173
|
+
# image_path = $1
|
|
4174
|
+
# image_relative_to = true
|
|
4175
|
+
# end
|
|
4176
|
+
# elsif from_theme
|
|
4177
|
+
# image_path = sub_attributes_discretely doc, image_path
|
|
4178
|
+
# image_relative_to = @themesdir
|
|
4179
|
+
# end
|
|
4180
|
+
|
|
4181
|
+
# image_path, image_format = ::Asciidoctor::Image.target_and_format image_path, image_attrs
|
|
4182
|
+
# image_path = resolve_image_path doc, image_path, image_format, image_relative_to
|
|
4183
|
+
|
|
4184
|
+
# return unless image_path
|
|
4185
|
+
|
|
4186
|
+
# unless ::File.readable? image_path
|
|
4187
|
+
# logger.warn %(#{key.to_s.tr '-_', ' '} not found or readable: #{image_path})
|
|
4188
|
+
# return
|
|
4189
|
+
# end
|
|
4190
|
+
|
|
4191
|
+
# if image_format == 'pdf'
|
|
4192
|
+
# [image_path, page: [((image_attrs || {})['page']).to_i, 1].max, format: image_format]
|
|
4193
|
+
# else
|
|
4194
|
+
# [image_path, (resolve_image_options image_path, image_format, image_attrs, (opts.merge background: true))]
|
|
4195
|
+
# end
|
|
4196
|
+
# end
|
|
4197
|
+
# end
|
|
4198
|
+
|
|
4199
|
+
# def resolve_image_options image_path, image_format, image_attrs, opts = {}
|
|
4200
|
+
# if image_format == 'svg'
|
|
4201
|
+
# image_opts = {
|
|
4202
|
+
# enable_file_requests_with_root: (::File.dirname image_path),
|
|
4203
|
+
# enable_web_requests: allow_uri_read ? (method :load_open_uri).to_proc : false,
|
|
4204
|
+
# cache_images: cache_uri,
|
|
4205
|
+
# fallback_font_name: fallback_svg_font_name,
|
|
4206
|
+
# format: 'svg',
|
|
4207
|
+
# }
|
|
4208
|
+
# else
|
|
4209
|
+
# image_opts = {}
|
|
4210
|
+
# end
|
|
4211
|
+
# background = opts[:background]
|
|
4212
|
+
# container_size = opts[:container_size] || (background ? [page_width, page_height] : [bounds.width, bounds.height])
|
|
4213
|
+
# if image_attrs
|
|
4214
|
+
# if background && (image_pos = image_attrs['position']) && (image_pos = resolve_background_position image_pos, nil)
|
|
4215
|
+
# image_opts.update image_pos
|
|
4216
|
+
# end
|
|
4217
|
+
# if (image_fit = image_attrs['fit'] || (background ? 'contain' : nil))
|
|
4218
|
+
# image_fit = 'contain' if image_format == 'svg' && image_fit == 'fill'
|
|
4219
|
+
# container_width, container_height = container_size
|
|
4220
|
+
# case image_fit
|
|
4221
|
+
# when 'none'
|
|
4222
|
+
# if (image_width = resolve_explicit_width image_attrs, bounds_width: container_width)
|
|
4223
|
+
# image_opts[:width] = image_width
|
|
4224
|
+
# end
|
|
4225
|
+
# when 'scale-down'
|
|
4226
|
+
# # NOTE if width and height aren't set in SVG, real width and height are computed after stretching viewbox to fit page
|
|
4227
|
+
# if (image_width = resolve_explicit_width image_attrs, bounds_width: container_width) && image_width > container_width
|
|
4228
|
+
# image_opts[:fit] = container_size
|
|
4229
|
+
# elsif (image_size = intrinsic_image_dimensions image_path, image_format) &&
|
|
4230
|
+
# (image_width ? image_width * (image_size[:height].to_f / image_size[:width]) > container_height : (to_pt image_size[:width], :px) > container_width || (to_pt image_size[:height], :px) > container_height)
|
|
4231
|
+
# image_opts[:fit] = container_size
|
|
4232
|
+
# elsif image_width
|
|
4233
|
+
# image_opts[:width] = image_width
|
|
4234
|
+
# end
|
|
4235
|
+
# when 'cover'
|
|
4236
|
+
# # QUESTION should we take explicit width into account?
|
|
4237
|
+
# if (image_size = intrinsic_image_dimensions image_path, image_format)
|
|
4238
|
+
# if container_width * (image_size[:height].to_f / image_size[:width]) < container_height
|
|
4239
|
+
# image_opts[:height] = container_height
|
|
4240
|
+
# else
|
|
4241
|
+
# image_opts[:width] = container_width
|
|
4242
|
+
# end
|
|
4243
|
+
# end
|
|
4244
|
+
# when 'fill'
|
|
4245
|
+
# image_opts[:width] = container_width
|
|
4246
|
+
# image_opts[:height] = container_height
|
|
4247
|
+
# else # when 'contain'
|
|
4248
|
+
# image_opts[:fit] = container_size
|
|
4249
|
+
# end
|
|
4250
|
+
# elsif (image_width = resolve_explicit_width image_attrs, bounds_width: container_size[0])
|
|
4251
|
+
# image_opts[:width] = image_width
|
|
4252
|
+
# else # default to fit=contain if sizing is not specified
|
|
4253
|
+
# image_opts[:fit] = container_size
|
|
4254
|
+
# end
|
|
4255
|
+
# else
|
|
4256
|
+
# image_opts[:fit] = container_size
|
|
4257
|
+
# end
|
|
4258
|
+
# image_opts
|
|
4259
|
+
# end
|
|
4260
|
+
|
|
4261
|
+
# # Resolves the explicit width, if specified, as a PDF pt value.
|
|
4262
|
+
# #
|
|
4263
|
+
# # Resolves the explicit width, first considering the pdfwidth attribute, then the scaledwidth
|
|
4264
|
+
# # attribute, then the theme default (if enabled by the :use_fallback option), and finally the
|
|
4265
|
+
# # width attribute. If the specified value is in pixels, the value is scaled by 75% to perform
|
|
4266
|
+
# # approximate CSS px to PDF pt conversion. If the value is a percentage, and the
|
|
4267
|
+
# # bounds_width option is given, the percentage of the bounds_width value is returned.
|
|
4268
|
+
# # Otherwise, the percentage width is returned.
|
|
4269
|
+
# #--
|
|
4270
|
+
# # QUESTION should we enforce positive result?
|
|
4271
|
+
# def resolve_explicit_width attrs, opts = {}
|
|
4272
|
+
# bounds_width = opts[:bounds_width]
|
|
4273
|
+
# # QUESTION should we restrict width to bounds_width for pdfwidth?
|
|
4274
|
+
# if attrs.key? 'pdfwidth'
|
|
4275
|
+
# if (width = attrs['pdfwidth']).end_with? '%'
|
|
4276
|
+
# bounds_width ? (width.to_f / 100) * bounds_width : width
|
|
4277
|
+
# elsif opts[:support_vw] && (width.end_with? 'vw')
|
|
4278
|
+
# (width.chomp 'vw').extend ViewportWidth
|
|
4279
|
+
# else
|
|
4280
|
+
# str_to_pt width
|
|
4281
|
+
# end
|
|
4282
|
+
# elsif attrs.key? 'scaledwidth'
|
|
4283
|
+
# # NOTE the parser automatically appends % if value is unitless
|
|
4284
|
+
# if (width = attrs['scaledwidth']).end_with? '%'
|
|
4285
|
+
# bounds_width ? (width.to_f / 100) * bounds_width : width
|
|
4286
|
+
# else
|
|
4287
|
+
# str_to_pt width
|
|
4288
|
+
# end
|
|
4289
|
+
# elsif opts[:use_fallback] && (width = @theme.image_width)
|
|
4290
|
+
# if ::Numeric === width
|
|
4291
|
+
# width
|
|
4292
|
+
# elsif (width = width.to_s).end_with? '%'
|
|
4293
|
+
# bounds_width ? (width.to_f / 100) * bounds_width : bounds_width
|
|
4294
|
+
# elsif opts[:support_vw] && (width.end_with? 'vw')
|
|
4295
|
+
# (width.chomp 'vw').extend ViewportWidth
|
|
4296
|
+
# else
|
|
4297
|
+
# str_to_pt width
|
|
4298
|
+
# end
|
|
4299
|
+
# elsif attrs.key? 'width'
|
|
4300
|
+
# if (width = attrs['width']).end_with? '%'
|
|
4301
|
+
# width = (width.to_f / 100) * bounds_width if bounds_width
|
|
4302
|
+
# else
|
|
4303
|
+
# width = to_pt width.to_f, :px
|
|
4304
|
+
# end
|
|
4305
|
+
# bounds_width && opts[:constrain_to_bounds] ? [bounds_width, width].min : width
|
|
4306
|
+
# end
|
|
4307
|
+
# end
|
|
4308
|
+
|
|
4309
|
+
# def resolve_background_position value, default_value = {}
|
|
4310
|
+
# if value.include? ' '
|
|
4311
|
+
# result = {}
|
|
4312
|
+
# center = nil
|
|
4313
|
+
# (value.split ' ', 2).each do |keyword|
|
|
4314
|
+
# if keyword == 'left' || keyword == 'right'
|
|
4315
|
+
# result[:position] = keyword.to_sym
|
|
4316
|
+
# elsif keyword == 'top' || keyword == 'bottom'
|
|
4317
|
+
# result[:vposition] = keyword.to_sym
|
|
4318
|
+
# elsif keyword == 'center'
|
|
4319
|
+
# center = true
|
|
4320
|
+
# end
|
|
4321
|
+
# end
|
|
4322
|
+
# if center
|
|
4323
|
+
# result[:position] ||= :center
|
|
4324
|
+
# result[:vposition] ||= :center
|
|
4325
|
+
# result
|
|
4326
|
+
# elsif (result.key? :position) && (result.key? :vposition)
|
|
4327
|
+
# result
|
|
4328
|
+
# else
|
|
4329
|
+
# default_value
|
|
4330
|
+
# end
|
|
4331
|
+
# elsif value == 'left' || value == 'right' || value == 'center'
|
|
4332
|
+
# { position: value.to_sym, vposition: :center }
|
|
4333
|
+
# elsif value == 'top' || value == 'bottom'
|
|
4334
|
+
# { position: :center, vposition: value.to_sym }
|
|
4335
|
+
# else
|
|
4336
|
+
# default_value
|
|
4337
|
+
# end
|
|
4338
|
+
# end
|
|
4339
|
+
|
|
4340
|
+
# def resolve_top val
|
|
4341
|
+
# if val.end_with? 'vh'
|
|
4342
|
+
# page_height * (1 - (val.to_f / 100))
|
|
4343
|
+
# elsif val.end_with? '%'
|
|
4344
|
+
# @y - effective_page_height * (val.to_f / 100)
|
|
4345
|
+
# else
|
|
4346
|
+
# @y - (str_to_pt val)
|
|
4347
|
+
# end
|
|
4348
|
+
# end
|
|
4349
|
+
|
|
4350
|
+
# def add_link_to_image uri, image_info, image_opts
|
|
4351
|
+
# image_width = image_info[:width]
|
|
4352
|
+
# image_height = image_info[:height]
|
|
4353
|
+
|
|
4354
|
+
# case image_opts[:position]
|
|
4355
|
+
# when :center
|
|
4356
|
+
# image_x = bounds.left_side + (bounds.width - image_width) * 0.5
|
|
4357
|
+
# when :right
|
|
4358
|
+
# image_x = bounds.right_side - image_width
|
|
4359
|
+
# else # :left or not set
|
|
4360
|
+
# image_x = bounds.left_side
|
|
4361
|
+
# end
|
|
4362
|
+
|
|
4363
|
+
# case image_opts[:vposition]
|
|
4364
|
+
# when :top
|
|
4365
|
+
# image_y = bounds.absolute_top
|
|
4366
|
+
# when :center
|
|
4367
|
+
# image_y = bounds.absolute_top - (bounds.height - image_height) * 0.5
|
|
4368
|
+
# when :bottom
|
|
4369
|
+
# image_y = bounds.absolute_bottom + image_height
|
|
4370
|
+
# else
|
|
4371
|
+
# image_y = y
|
|
4372
|
+
# end unless (image_y = image_opts[:y])
|
|
4373
|
+
|
|
4374
|
+
# link_annotation [image_x, (image_y - image_height), (image_x + image_width), image_y], Border: [0, 0, 0], A: { Type: :Action, S: :URI, URI: uri.as_pdf }
|
|
4375
|
+
# end
|
|
4376
|
+
|
|
4377
|
+
# def load_open_uri
|
|
4378
|
+
# if @cache_uri && !(defined? ::OpenURI::Cache)
|
|
4379
|
+
# if (Helpers.require_library 'open-uri/cached', 'open-uri-cached', :warn).nil?
|
|
4380
|
+
# @cache_uri = false # disable since it failed to load
|
|
4381
|
+
# end
|
|
4382
|
+
# end
|
|
4383
|
+
# ::OpenURI
|
|
4384
|
+
# end
|
|
4385
|
+
|
|
4386
|
+
# def remove_tmp_files
|
|
4387
|
+
# @tmp_files.reject! {|_, path| path ? (unlink_tmp_file path) : true }
|
|
4388
|
+
# end
|
|
4389
|
+
|
|
4390
|
+
# def unlink_tmp_file path
|
|
4391
|
+
# ::File.unlink path if ::File.exist? path
|
|
4392
|
+
# true
|
|
4393
|
+
# rescue
|
|
4394
|
+
# logger.warn %(could not delete temporary file: #{path}; #{$!.message}) unless scratch?
|
|
4395
|
+
# false
|
|
4396
|
+
# end
|
|
4397
|
+
|
|
4398
|
+
# def apply_subs_discretely doc, value, opts = {}
|
|
4399
|
+
# imagesdir = doc.attr 'imagesdir'
|
|
4400
|
+
# doc.set_attr 'imagesdir', @themesdir
|
|
4401
|
+
# # FIXME: get sub_attributes to handle drop-line w/o a warning
|
|
4402
|
+
# doc.set_attr 'attribute-missing', 'skip' unless (attribute_missing = doc.attr 'attribute-missing') == 'skip'
|
|
4403
|
+
# value = value.gsub '\{', '\\\\\\{' if (escaped_attr_ref = value.include? '\{')
|
|
4404
|
+
# value = doc.apply_subs value
|
|
4405
|
+
# value = (value.split LF).delete_if {|line| SimpleAttributeRefRx.match? line }.join LF if opts[:drop_lines_with_unresolved_attributes] && (value.include? '{')
|
|
4406
|
+
# value = value.gsub '\{', '{' if escaped_attr_ref
|
|
4407
|
+
# doc.set_attr 'attribute-missing', attribute_missing unless attribute_missing == 'skip'
|
|
4408
|
+
# if imagesdir
|
|
4409
|
+
# doc.set_attr 'imagesdir', imagesdir
|
|
4410
|
+
# else
|
|
4411
|
+
# doc.remove_attr 'imagesdir'
|
|
4412
|
+
# end
|
|
4413
|
+
# value
|
|
4414
|
+
# end
|
|
4415
|
+
|
|
4416
|
+
# def sub_attributes_discretely doc, value
|
|
4417
|
+
# doc.set_attr 'attribute-missing', 'skip' unless (attribute_missing = doc.attr 'attribute-missing') == 'skip'
|
|
4418
|
+
# value = doc.apply_subs value, [:attributes]
|
|
4419
|
+
# doc.set_attr 'attribute-missing', attribute_missing unless attribute_missing == 'skip'
|
|
4420
|
+
# value
|
|
4421
|
+
# end
|
|
4422
|
+
|
|
4423
|
+
# def promote_author doc, idx = 1
|
|
4424
|
+
# doc.remove_attr 'url' if (original_url = doc.attr 'url')
|
|
4425
|
+
# email = nil
|
|
4426
|
+
# if idx > 1
|
|
4427
|
+
# original_attrs = AuthorAttributeNames.each_with_object({}) do |name, accum|
|
|
4428
|
+
# accum[name] = doc.attr name
|
|
4429
|
+
# if (val = doc.attr %(#{name}_#{idx}))
|
|
4430
|
+
# doc.set_attr name, val
|
|
4431
|
+
# # NOTE email holds url as well
|
|
4432
|
+
# email = val if name == 'email'
|
|
4433
|
+
# else
|
|
4434
|
+
# doc.remove_attr name
|
|
4435
|
+
# end
|
|
4436
|
+
# end
|
|
4437
|
+
# doc.set_attr 'url', ((email.include? '@') ? %(mailto:#{email}) : email) if email
|
|
4438
|
+
# result = yield
|
|
4439
|
+
# original_attrs.each {|name, val| val ? (doc.set_attr name, val) : (doc.remove_attr name) }
|
|
4440
|
+
# else
|
|
4441
|
+
# if (email = doc.attr 'email')
|
|
4442
|
+
# doc.set_attr 'url', ((email.include? '@') ? %(mailto:#{email}) : email)
|
|
4443
|
+
# end
|
|
4444
|
+
# result = yield
|
|
4445
|
+
# end
|
|
4446
|
+
# if original_url
|
|
4447
|
+
# doc.set_attr 'url', original_url
|
|
4448
|
+
# elsif email
|
|
4449
|
+
# doc.remove_attr 'url'
|
|
4450
|
+
# end
|
|
4451
|
+
# result
|
|
4452
|
+
# end
|
|
4453
|
+
|
|
4454
|
+
# # NOTE assume URL is escaped (i.e., contains character references such as &)
|
|
4455
|
+
# def breakable_uri uri
|
|
4456
|
+
# scheme, address = uri.split UriSchemeBoundaryRx, 2
|
|
4457
|
+
# address, scheme = scheme, address unless address
|
|
4458
|
+
# unless address.nil_or_empty?
|
|
4459
|
+
# address = address.gsub UriBreakCharsRx, UriBreakCharRepl
|
|
4460
|
+
# # NOTE require at least two characters after a break
|
|
4461
|
+
# address.slice!(-2) if address[-2] == ZeroWidthSpace
|
|
4462
|
+
# end
|
|
4463
|
+
# %(#{scheme}#{address})
|
|
4464
|
+
# end
|
|
4465
|
+
|
|
4466
|
+
# def consolidate_ranges nums
|
|
4467
|
+
# if nums.size > 1
|
|
4468
|
+
# prev = nil
|
|
4469
|
+
# nums.each_with_object([]) {|num, accum|
|
|
4470
|
+
# if prev && (prev.to_i + 1) == num.to_i
|
|
4471
|
+
# accum[-1][1] = num
|
|
4472
|
+
# else
|
|
4473
|
+
# accum << [num]
|
|
4474
|
+
# end
|
|
4475
|
+
# prev = num
|
|
4476
|
+
# }.map {|range| range.join '-' }
|
|
4477
|
+
# else
|
|
4478
|
+
# nums
|
|
4479
|
+
# end
|
|
4480
|
+
# end
|
|
4481
|
+
|
|
4482
|
+
# def resolve_pagenums val
|
|
4483
|
+
# pgnums = []
|
|
4484
|
+
# ((val.include? ',') ? (val.split ',') : (val.split ';')).each do |entry|
|
|
4485
|
+
# if entry.include? '..'
|
|
4486
|
+
# from, _, to = entry.partition '..'
|
|
4487
|
+
# pgnums += ([from.to_i, 1].max..[to.to_i, 1].max).to_a
|
|
4488
|
+
# else
|
|
4489
|
+
# pgnums << entry.to_i
|
|
4490
|
+
# end
|
|
4491
|
+
# end
|
|
4492
|
+
|
|
4493
|
+
# pgnums
|
|
4494
|
+
# end
|
|
4495
|
+
|
|
4496
|
+
# def get_char code
|
|
4497
|
+
# (code.start_with? '\u') ? ([((code.slice 2, code.length).to_i 16)].pack 'U1') : code
|
|
4498
|
+
# end
|
|
4499
|
+
|
|
4500
|
+
# # QUESTION move to prawn/extensions.rb?
|
|
4501
|
+
# def init_scratch_prototype
|
|
4502
|
+
# @save_state = nil
|
|
4503
|
+
# @scratch_depth = 0
|
|
4504
|
+
# # NOTE don't need background image in scratch document; can cause marshal error anyway
|
|
4505
|
+
# saved_page_bg_image, @page_bg_image = @page_bg_image, { verso: nil, recto: nil }
|
|
4506
|
+
# # IMPORTANT don't set font before using Marshal, it causes serialization to fail
|
|
4507
|
+
# @prototype = ::Marshal.load ::Marshal.dump self
|
|
4508
|
+
# @page_bg_image = saved_page_bg_image
|
|
4509
|
+
# @prototype.state.store.info.data[:Scratch] = @prototype.text_formatter.scratch = true
|
|
4510
|
+
# # NOTE we're now starting a new page each time, so no need to do it here
|
|
4511
|
+
# #@prototype.start_new_page if @prototype.page_number == 0
|
|
4512
|
+
# end
|
|
4513
|
+
|
|
4514
|
+
# def push_scratch doc
|
|
4515
|
+
# if (@scratch_depth += 1) == 1
|
|
4516
|
+
# @save_state = {
|
|
4517
|
+
# catalog: {}.tap {|accum| doc.catalog.each {|k, v| accum[k] = v.dup } },
|
|
4518
|
+
# attributes: doc.attributes.dup,
|
|
4519
|
+
# }
|
|
4520
|
+
# end
|
|
4521
|
+
# end
|
|
4522
|
+
|
|
4523
|
+
# def pop_scratch doc
|
|
4524
|
+
# if (@scratch_depth -= 1) == 0
|
|
4525
|
+
# doc.catalog.replace @save_state[:catalog]
|
|
4526
|
+
# doc.attributes.replace @save_state[:attributes]
|
|
4527
|
+
# @save_state = nil
|
|
4528
|
+
# end
|
|
4529
|
+
# end
|
|
4530
|
+
|
|
4531
|
+
# def clear_scratch
|
|
4532
|
+
# @scratch_depth = 0
|
|
4533
|
+
# @save_state = @prototype = @scratch = nil
|
|
4534
|
+
# end
|
|
4535
|
+
end
|
|
4536
|
+
end
|
|
4537
|
+
end
|