asciidoctor-pdf 1.5.0.alpha.16 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +5 -5
  2. data/.yardopts +12 -0
  3. data/CHANGELOG.adoc +415 -1
  4. data/LICENSE.adoc +1 -1
  5. data/NOTICE.adoc +14 -11
  6. data/README.adoc +647 -222
  7. data/asciidoctor-pdf.gemspec +47 -44
  8. data/bin/asciidoctor-pdf +5 -9
  9. data/bin/asciidoctor-pdf-optimize +20 -0
  10. data/data/fonts/ABOUT-mplus1mn-subset +26 -0
  11. data/data/fonts/ABOUT-mplus1p-subset +26 -0
  12. data/data/fonts/ABOUT-notoemoji-subset +3 -0
  13. data/data/fonts/ABOUT-notoserif-subset +26 -0
  14. data/data/fonts/{LICENSE-mplus-testflight-58 → LICENSE-mplus} +2 -2
  15. data/data/fonts/{LICENSE-noto-2015-06-05 → LICENSE-notoserif} +0 -0
  16. data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
  17. data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
  18. data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
  19. data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
  20. data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
  21. data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
  22. data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
  23. data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
  24. data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
  25. data/data/fonts/notoemoji-subset.ttf +0 -0
  26. data/data/fonts/notoserif-bold-subset.ttf +0 -0
  27. data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
  28. data/data/fonts/notoserif-italic-subset.ttf +0 -0
  29. data/data/fonts/notoserif-regular-subset.ttf +0 -0
  30. data/data/themes/base-theme.yml +26 -4
  31. data/data/themes/default-theme.yml +76 -60
  32. data/data/themes/default-with-fallback-font-theme.yml +9 -0
  33. data/docs/theming-guide.adoc +2731 -922
  34. data/lib/asciidoctor/pdf/converter.rb +4489 -0
  35. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +7 -0
  36. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +7 -0
  37. data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +5 -0
  38. data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +35 -0
  39. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list.rb +4 -2
  40. data/lib/{asciidoctor-pdf/asciidoctor_ext → asciidoctor/pdf/ext/asciidoctor}/list_item.rb +3 -1
  41. data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +33 -0
  42. data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +45 -0
  43. data/lib/asciidoctor/pdf/ext/asciidoctor.rb +11 -0
  44. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/array.rb +6 -6
  45. data/lib/asciidoctor/pdf/ext/core/file.rb +9 -0
  46. data/lib/asciidoctor/pdf/ext/core/hash.rb +7 -0
  47. data/lib/asciidoctor/pdf/ext/core/numeric.rb +26 -0
  48. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/object.rb +3 -1
  49. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/quantifiable_stdout.rb +9 -1
  50. data/lib/asciidoctor/pdf/ext/core/regexp.rb +5 -0
  51. data/lib/{asciidoctor-pdf/core_ext → asciidoctor/pdf/ext/core}/string.rb +9 -13
  52. data/lib/asciidoctor/pdf/ext/core.rb +10 -0
  53. data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +54 -0
  54. data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +8 -0
  55. data/lib/asciidoctor/pdf/ext/pdf-core.rb +4 -0
  56. data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +117 -0
  57. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +922 -0
  58. data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/font/afm.rb +14 -10
  59. data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +9 -0
  60. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +66 -0
  61. data/lib/{asciidoctor-pdf/prawn_ext → asciidoctor/pdf/ext/prawn}/formatted_text/fragment.rb +21 -18
  62. data/lib/asciidoctor/pdf/ext/prawn/images.rb +54 -0
  63. data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +14 -0
  64. data/lib/asciidoctor/pdf/ext/prawn-svg.rb +6 -0
  65. data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -0
  66. data/lib/{asciidoctor-pdf/prawn-table_ext → asciidoctor/pdf/ext/prawn-table}/cell/text.rb +6 -3
  67. data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +60 -0
  68. data/lib/asciidoctor/pdf/ext/prawn-table.rb +6 -0
  69. data/lib/{asciidoctor-pdf/prawn-templates_ext.rb → asciidoctor/pdf/ext/prawn-templates.rb} +2 -0
  70. data/lib/asciidoctor/pdf/ext/prawn.rb +9 -0
  71. data/lib/asciidoctor/pdf/ext/pygments.rb +34 -0
  72. data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +208 -0
  73. data/lib/{asciidoctor-pdf/rouge_ext/themes/pastie.rb → asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb} +7 -5
  74. data/lib/asciidoctor/pdf/ext/rouge.rb +5 -0
  75. data/lib/asciidoctor/pdf/ext.rb +9 -0
  76. data/lib/asciidoctor/pdf/formatted_text/formatter.rb +43 -0
  77. data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +14 -0
  78. data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +21 -0
  79. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +134 -0
  80. data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +51 -0
  81. data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +22 -0
  82. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.rb +175 -53
  83. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text/parser.treetop +20 -14
  84. data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +43 -0
  85. data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +55 -0
  86. data/lib/asciidoctor/pdf/formatted_text/transform.rb +394 -0
  87. data/lib/{asciidoctor-pdf → asciidoctor/pdf}/formatted_text.rb +6 -0
  88. data/lib/asciidoctor/pdf/index_catalog.rb +133 -0
  89. data/lib/asciidoctor/pdf/measurements.rb +62 -0
  90. data/lib/asciidoctor/pdf/optimizer.rb +44 -0
  91. data/lib/asciidoctor/pdf/pdfmark.rb +41 -0
  92. data/lib/asciidoctor/pdf/roman_numeral.rb +128 -0
  93. data/lib/asciidoctor/pdf/sanitizer.rb +45 -0
  94. data/lib/asciidoctor/pdf/text_transformer.rb +116 -0
  95. data/lib/asciidoctor/pdf/theme_loader.rb +305 -0
  96. data/lib/asciidoctor/pdf/version.rb +8 -0
  97. data/lib/asciidoctor/pdf.rb +15 -0
  98. data/lib/asciidoctor-pdf/converter.rb +2 -3343
  99. data/lib/asciidoctor-pdf/version.rb +3 -5
  100. data/lib/asciidoctor-pdf.rb +3 -3
  101. metadata +242 -128
  102. data/Gemfile +0 -22
  103. data/Rakefile +0 -81
  104. data/lib/asciidoctor-pdf/asciidoctor_ext/image.rb +0 -24
  105. data/lib/asciidoctor-pdf/asciidoctor_ext/section.rb +0 -34
  106. data/lib/asciidoctor-pdf/asciidoctor_ext.rb +0 -5
  107. data/lib/asciidoctor-pdf/core_ext/numeric.rb +0 -15
  108. data/lib/asciidoctor-pdf/core_ext/ostruct.rb +0 -17
  109. data/lib/asciidoctor-pdf/core_ext.rb +0 -4
  110. data/lib/asciidoctor-pdf/formatted_text/formatter.rb +0 -27
  111. data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +0 -21
  112. data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +0 -172
  113. data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +0 -46
  114. data/lib/asciidoctor-pdf/formatted_text/transform.rb +0 -261
  115. data/lib/asciidoctor-pdf/implicit_header_processor.rb +0 -63
  116. data/lib/asciidoctor-pdf/index_catalog.rb +0 -119
  117. data/lib/asciidoctor-pdf/measurements.rb +0 -58
  118. data/lib/asciidoctor-pdf/pdf-core_ext/page.rb +0 -25
  119. data/lib/asciidoctor-pdf/pdf-core_ext/pdf_object.rb +0 -6
  120. data/lib/asciidoctor-pdf/pdf-core_ext.rb +0 -2
  121. data/lib/asciidoctor-pdf/pdfmark.rb +0 -33
  122. data/lib/asciidoctor-pdf/prawn-svg_ext/interface.rb +0 -10
  123. data/lib/asciidoctor-pdf/prawn-svg_ext.rb +0 -4
  124. data/lib/asciidoctor-pdf/prawn-table_ext/cell/asciidoc.rb +0 -69
  125. data/lib/asciidoctor-pdf/prawn-table_ext.rb +0 -3
  126. data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +0 -115
  127. data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +0 -863
  128. data/lib/asciidoctor-pdf/prawn_ext/images.rb +0 -40
  129. data/lib/asciidoctor-pdf/prawn_ext.rb +0 -5
  130. data/lib/asciidoctor-pdf/roman_numeral.rb +0 -114
  131. data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +0 -15
  132. data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +0 -164
  133. data/lib/asciidoctor-pdf/rouge_ext.rb +0 -4
  134. data/lib/asciidoctor-pdf/sanitizer.rb +0 -88
  135. data/lib/asciidoctor-pdf/temporary_path.rb +0 -13
  136. data/lib/asciidoctor-pdf/theme_loader.rb +0 -247
  137. data/lib/asciidoctor-pdf/ttfunk_ext.rb +0 -8
@@ -1,863 +0,0 @@
1
- Prawn::Font::AFM.instance_variable_set :@hide_m17n_warning, true
2
-
3
- require 'prawn/icon'
4
-
5
- module Asciidoctor
6
- module Prawn
7
- module Extensions
8
- include ::Asciidoctor::Pdf::Measurements
9
- include ::Asciidoctor::Pdf::Sanitizer
10
-
11
- IconSets = ['fa', 'fi', 'octicon', 'pf'].to_set
12
- InitialPageContent = %(q\n)
13
-
14
- # - :height is the height of a line
15
- # - :leading is spacing between adjacent lines
16
- # - :padding_top is half line spacing, plus any line_gap in the font
17
- # - :padding_bottom is half line spacing
18
- # - :final_gap determines whether a gap is added below the last line
19
- LineMetrics = ::Struct.new :height, :leading, :padding_top, :padding_bottom, :final_gap
20
-
21
- # Core
22
-
23
- # Retrieves the catalog reference data for the PDF.
24
- #
25
- def catalog
26
- state.store.root
27
- end
28
-
29
- # Measurements
30
-
31
- # Returns the width of the current page from edge-to-edge
32
- #
33
- def page_width
34
- page.dimensions[2]
35
- end
36
-
37
- # Returns the effective (writable) width of the page
38
- #
39
- # If inside a fixed-height bounding box, returns height of box.
40
- #
41
- def effective_page_width
42
- reference_bounds.width
43
- end
44
-
45
- # Returns the height of the current page from edge-to-edge
46
- #
47
- def page_height
48
- page.dimensions[3]
49
- end
50
-
51
- # Returns the effective (writable) height of the page
52
- #
53
- # If inside a fixed-height bounding box, returns width of box.
54
- #
55
- def effective_page_height
56
- reference_bounds.height
57
- end
58
-
59
- # Set the margins for the current page.
60
- #
61
- def set_page_margin margin
62
- # FIXME is there a cleaner way to set margins? does it make sense to override create_new_page?
63
- apply_margin_options margin: margin
64
- generate_margin_box
65
- end
66
-
67
- # Returns the margins for the current page as a 4 element array (top, right, bottom, left)
68
- #
69
- def page_margin
70
- [page.margins[:top], page.margins[:right], page.margins[:bottom], page.margins[:left]]
71
- end
72
-
73
- # Returns the width of the left margin for the current page
74
- #
75
- def page_margin_left
76
- page.margins[:left]
77
- end
78
- # deprecated
79
- alias :left_margin :page_margin_left
80
-
81
- # Returns the width of the right margin for the current page
82
- #
83
- def page_margin_right
84
- page.margins[:right]
85
- end
86
- # deprecated
87
- alias :right_margin :page_margin_right
88
-
89
- # Returns the width of the top margin for the current page
90
- #
91
- def page_margin_top
92
- page.margins[:top]
93
- end
94
-
95
- # Returns the width of the bottom margin for the current page
96
- #
97
- def page_margin_bottom
98
- page.margins[:bottom]
99
- end
100
-
101
- # Returns the total left margin (to the page edge) for the current bounds.
102
- #
103
- def bounds_margin_left
104
- bounds.absolute_left
105
- end
106
-
107
- # Returns the total right margin (to the page edge) for the current bounds.
108
- #
109
- def bounds_margin_right
110
- page.dimensions[2] - bounds.absolute_right
111
- end
112
-
113
- # Returns the side the current page is facing, :recto or :verso.
114
- #
115
- def page_side pgnum = nil, invert = nil
116
- if invert
117
- (recto_page? pgnum) ? :verso : :recto
118
- else
119
- (recto_page? pgnum) ? :recto : :verso
120
- end
121
- end
122
-
123
- # Returns whether the page is a recto page.
124
- #
125
- def recto_page? pgnum = nil
126
- (pgnum || page_number).odd?
127
- end
128
-
129
- # Returns whether the page is a verso page.
130
- #
131
- def verso_page? pgnum = nil
132
- (pgnum || page_number).even?
133
- end
134
-
135
- # Returns whether the cursor is at the top of the page (i.e., margin box).
136
- #
137
- def at_page_top?
138
- @y == @margin_box.absolute_top
139
- end
140
-
141
- # Returns whether the current page is empty (i.e., no content has been written).
142
- # Returns false if a page has not yet been created.
143
- #
144
- def empty_page?
145
- # if we are at the page top, assume we didn't write anything to the page
146
- #at_page_top?
147
- # ...or use more robust, low-level check (initial value of content is "q\n")
148
- page_number > 0 && page.content.stream.filtered_stream == InitialPageContent
149
- end
150
- alias :page_is_empty? :empty_page?
151
-
152
- # Returns whether the current page is the last page in the document.
153
- #
154
- def last_page?
155
- page_number == page_count
156
- end
157
-
158
- # Destinations
159
-
160
- # Generates a destination object that resolves to the top of the page
161
- # specified by the page_num parameter or the current page if no page number
162
- # is provided. The destination preserves the user's zoom level unlike
163
- # the destinations generated by the outline builder.
164
- #
165
- def dest_top page_num = nil
166
- dest_xyz 0, page_height, nil, (page_num ? state.pages[page_num - 1] : page)
167
- end
168
-
169
- # Fonts
170
-
171
- # Registers a new custom font described in the data parameter
172
- # after converting the font name to a String.
173
- #
174
- # Example:
175
- #
176
- # register_font Roboto: {
177
- # normal: 'fonts/roboto-normal.ttf',
178
- # italic: 'fonts/roboto-italic.ttf',
179
- # bold: 'fonts/roboto-bold.ttf',
180
- # bold_italic: 'fonts/roboto-bold_italic.ttf'
181
- # }
182
- #
183
- def register_font data
184
- font_families.update data.inject({}) {|accum, (key, val)| accum[key.to_s] = val; accum }
185
- end
186
-
187
- # Enhances the built-in font method to allow the font
188
- # size to be specified as the second option and to
189
- # lazily load font-based icons.
190
- #
191
- def font name = nil, options = {}
192
- if name
193
- ::Prawn::Icon::FontData.load self, name if IconSets.include? name
194
- options = { size: options } if ::Numeric === options
195
- end
196
- super name, options
197
- end
198
-
199
- # Retrieves the current font name (i.e., family).
200
- #
201
- def font_family
202
- font.options[:family]
203
- end
204
-
205
- alias :font_name :font_family
206
-
207
- # Retrieves the current font info (family, style, size) as a Hash
208
- #
209
- def font_info
210
- { family: font.options[:family], style: (font.options[:style] || :normal), size: @font_size }
211
- end
212
-
213
- # Sets the font style for the scope of the block to which this method
214
- # yields. If the style is nil and no block is given, return the current
215
- # font style.
216
- #
217
- def font_style style = nil
218
- if block_given?
219
- font font.options[:family], style: style do
220
- yield
221
- end
222
- elsif style
223
- font font.options[:family], style: style
224
- else
225
- font.options[:style] || :normal
226
- end
227
- end
228
-
229
- # Applies points as a scale factor of the current font if the value provided
230
- # is less than or equal to 1 or it's a string (e.g., 1.1em), then delegates to the super
231
- # implementation to carry out the built-in functionality.
232
- #
233
- #--
234
- # QUESTION should we round the result?
235
- def font_size points = nil
236
- return @font_size unless points
237
- if points == 1
238
- super @font_size
239
- elsif String === points
240
- if points.end_with? 'rem'
241
- super(@theme.base_font_size * points.to_f)
242
- elsif points.end_with? 'em'
243
- super(@font_size * points.to_f)
244
- elsif points.end_with? '%'
245
- super(@font_size * (points.to_f / 100.0))
246
- else
247
- super points.to_f
248
- end
249
- # FIXME HACK assume em value
250
- elsif points < 1
251
- super(@font_size * points)
252
- else
253
- super points
254
- end
255
- end
256
-
257
- def resolve_font_style styles
258
- if styles.include? :bold
259
- (styles.include? :italic) ? :bold_italic : :bold
260
- elsif styles.include? :italic
261
- :italic
262
- else
263
- :normal
264
- end
265
- end
266
-
267
- # Retreives the collection of font styles from the given font style key,
268
- # which defaults to the current font style.
269
- #
270
- def font_styles style = font_style
271
- if style
272
- style == :bold_italic ? [:bold, :italic].to_set : [style].to_set
273
- else
274
- ::Set.new
275
- end
276
- end
277
-
278
- # Apply the font settings (family, size, styles and character spacing) from
279
- # the fragment to the document, then yield to the block.
280
- #
281
- # The original font settings are restored before this method returns.
282
- #
283
- def fragment_font fragment
284
- f_info = font_info
285
- f_family = fragment[:font] || f_info[:family]
286
- f_size = fragment[:size] || f_info[:size]
287
- if (f_styles = fragment[:styles])
288
- f_style = resolve_font_style f_styles
289
- else
290
- f_style = :normal
291
- end
292
-
293
- if (c_spacing = fragment[:character_spacing])
294
- character_spacing c_spacing do
295
- font f_family, size: f_size, style: f_style do
296
- yield
297
- end
298
- end
299
- else
300
- font f_family, size: f_size, style: f_style do
301
- yield
302
- end
303
- end
304
- end
305
-
306
- def calc_line_metrics line_height = 1, font = self.font, font_size = self.font_size
307
- line_height_length = line_height * font_size
308
- leading = line_height_length - font_size
309
- half_leading = leading / 2
310
- padding_top = half_leading + font.line_gap
311
- padding_bottom = half_leading
312
- LineMetrics.new line_height_length, leading, padding_top, padding_bottom, false
313
- end
314
-
315
- =begin
316
- # these line metrics attempted to figure out a correction based on the reported height and the font_size
317
- # however, it only works for some fonts, and breaks down for fonts like Noto Serif
318
- def calc_line_metrics line_height = 1, font = self.font, font_size = self.font_size
319
- line_height_length = font_size * line_height
320
- line_gap = line_height_length - font_size
321
- correction = font.height - font_size
322
- leading = line_gap - correction
323
- shift = (font.line_gap + correction + line_gap) / 2
324
- final_gap = font.line_gap != 0
325
- LineMetrics.new line_height_length, leading, shift, shift, final_gap
326
- end
327
- =end
328
-
329
- # Parse the text into an array of fragments using the text formatter.
330
- def parse_text string, options = {}
331
- return [] if string.nil?
332
-
333
- options = options.dup
334
- if (format_option = options.delete :inline_format)
335
- format_option = [] unless ::Array === format_option
336
- fragments = self.text_formatter.format string, *format_option
337
- else
338
- fragments = [{text: string}]
339
- end
340
-
341
- if (color = options.delete :color)
342
- fragments.map do |fragment|
343
- fragment[:color] ? fragment : fragment.merge(color: color)
344
- end
345
- else
346
- fragments
347
- end
348
- end
349
-
350
- # Performs the same work as text except that the first_line_opts
351
- # are applied to the first line of text renderered. It's necessary
352
- # to use low-level APIs in this method so that we only style the
353
- # first line and not the remaining lines (which is the default
354
- # behavior in Prawn).
355
- def text_with_formatted_first_line string, first_line_opts, opts
356
- color = opts.delete :color
357
- fragments = parse_text string, opts
358
- # NOTE the low-level APIs we're using don't recognize the :styles option, so we must resolve
359
- if (styles = opts.delete :styles)
360
- opts[:style] = resolve_font_style styles
361
- end
362
- if (first_line_styles = first_line_opts.delete :styles)
363
- first_line_opts[:style] = resolve_font_style first_line_styles
364
- end
365
- first_line_color = (first_line_opts.delete :color) || color
366
- opts = opts.merge document: self
367
- # QUESTION should we merge more carefully here? (hand-select keys?)
368
- first_line_opts = opts.merge(first_line_opts).merge single_line: true
369
- box = ::Prawn::Text::Formatted::Box.new fragments, first_line_opts
370
- # NOTE get remaining_fragments before we add color to fragments on first line
371
- remaining_fragments = box.render dry_run: true
372
- # NOTE color must be applied per-fragment
373
- if first_line_color
374
- fragments.each {|fragment| fragment[:color] ||= first_line_color}
375
- end
376
- fill_formatted_text_box fragments, first_line_opts
377
- unless remaining_fragments.empty?
378
- # NOTE color must be applied per-fragment
379
- if color
380
- remaining_fragments.each {|fragment| fragment[:color] ||= color }
381
- end
382
- # as of Prawn 1.2.1, we have to handle the line gap after the first line manually
383
- move_down opts[:leading]
384
- remaining_fragments = fill_formatted_text_box remaining_fragments, opts
385
- draw_remaining_formatted_text_on_new_pages remaining_fragments, opts
386
- end
387
- end
388
-
389
- # Apply the text transform to the specified text.
390
- #
391
- # Supported transform values are "uppercase", "lowercase", or "none" (passed
392
- # as either a String or a Symbol). When the uppercase transform is applied to
393
- # the text, it correctly uppercases visible text while leaving markup and
394
- # named character entities unchanged. The none transform returns the text
395
- # unmodified.
396
- #
397
- def transform_text text, transform
398
- case transform
399
- when :uppercase, 'uppercase'
400
- uppercase_pcdata text
401
- when :lowercase, 'lowercase'
402
- lowercase_mb text
403
- else
404
- text
405
- end
406
- end
407
-
408
- # Cursor
409
-
410
- # Short-circuits the call to the built-in move_up operation
411
- # when n is 0.
412
- #
413
- def move_up n
414
- super unless n == 0
415
- end
416
-
417
- # Override built-in move_text_position method to prevent Prawn from advancing
418
- # to next page if image doesn't fit before rendering image.
419
- #--
420
- # NOTE could use :at option when calling image/embed_image instead
421
- def move_text_position h
422
- end
423
-
424
- # Short-circuits the call to the built-in move_down operation
425
- # when n is 0.
426
- #
427
- def move_down n
428
- super unless n == 0
429
- end
430
-
431
- # Bounds
432
-
433
- # Overrides the built-in pad operation to allow for asymmetric paddings.
434
- #
435
- # Example:
436
- #
437
- # pad 20, 10 do
438
- # text 'A paragraph with twice as much top padding as bottom padding.'
439
- # end
440
- #
441
- def pad top, bottom = nil
442
- move_down top
443
- yield
444
- move_down(bottom || top)
445
- end
446
-
447
- # Combines the built-in pad and indent operations into a single method.
448
- #
449
- # Padding may be specified as an array of four values, or as a single value.
450
- # The single value is used as the padding around all four sides of the box.
451
- #
452
- # If padding is nil, this method simply yields to the block and returns.
453
- #
454
- # Example:
455
- #
456
- # pad_box 20 do
457
- # text 'A paragraph inside a blox with even padding on all sides.'
458
- # end
459
- #
460
- # pad_box [10, 10, 10, 20] do
461
- # text 'An indented paragraph inside a box with equal padding on all sides.'
462
- # end
463
- #
464
- def pad_box padding
465
- if padding
466
- # TODO implement shorthand combinations like in CSS
467
- p_top, p_right, p_bottom, p_left = ::Array === padding ? padding : (::Array.new 4, padding)
468
- begin
469
- # logic is intentionally inlined
470
- move_down p_top
471
- bounds.add_left_padding p_left
472
- bounds.add_right_padding p_right
473
- yield
474
- # NOTE support negative bottom padding for use with quote block
475
- if p_bottom < 0
476
- # QUESTION should we return to previous page if top of page is reached?
477
- p_bottom < cursor - reference_bounds.top ? (move_cursor_to reference_bounds.top) : (move_down p_bottom)
478
- else
479
- p_bottom < cursor ? (move_down p_bottom) : reference_bounds.move_past_bottom
480
- end
481
- ensure
482
- bounds.subtract_left_padding p_left
483
- bounds.subtract_right_padding p_right
484
- end
485
- else
486
- yield
487
- end
488
-
489
- # alternate, delegated logic
490
- #pad padding[0], padding[2] do
491
- # indent padding[1], padding[3] do
492
- # yield
493
- # end
494
- #end
495
- end
496
-
497
- # Stretch the current bounds to the left and right edges of the current page
498
- # while yielding the specified block if the verdict argument is true.
499
- # Otherwise, simply yield the specified block.
500
- #
501
- def span_page_width_if verdict
502
- if verdict
503
- indent(-bounds_margin_left, -bounds_margin_right) do
504
- yield
505
- end
506
- else
507
- yield
508
- end
509
- end
510
-
511
- # A flowing version of the bounding_box. If the content runs to another page, the cursor starts
512
- # at the top of the page instead of the original cursor position. Similar to span, except
513
- # you can specify an absolute left position and pass additional options through to bounding_box.
514
- #
515
- def flow_bounding_box left = 0, opts = {}
516
- original_y = self.y
517
- # QUESTION should preserving original_x be an option?
518
- original_x = bounds.absolute_left - margin_box.absolute_left
519
- canvas do
520
- bounding_box [margin_box.absolute_left + original_x + left, margin_box.absolute_top], opts do
521
- self.y = original_y
522
- yield
523
- end
524
- end
525
- end
526
-
527
- # Graphics
528
-
529
- # Fills the current bounding box with the specified fill color. Before
530
- # returning from this method, the original fill color on the document is
531
- # restored.
532
- def fill_bounds f_color = fill_color
533
- if f_color && f_color != 'transparent'
534
- prev_fill_color = fill_color
535
- fill_color f_color
536
- fill_rectangle bounds.top_left, bounds.width, bounds.height
537
- fill_color prev_fill_color
538
- end
539
- end
540
-
541
- # Fills the absolute bounding box with the specified fill color. Before
542
- # returning from this method, the original fill color on the document is
543
- # restored.
544
- def fill_absolute_bounds f_color = fill_color
545
- canvas { fill_bounds f_color }
546
- end
547
-
548
- # Fills the current bounds using the specified fill color and strokes the
549
- # bounds using the specified stroke color. Sets the line with if specified
550
- # in the options. Before returning from this method, the original fill
551
- # color, stroke color and line width on the document are restored.
552
- #
553
- def fill_and_stroke_bounds f_color = fill_color, s_color = stroke_color, options = {}
554
- no_fill = !f_color || f_color == 'transparent'
555
- no_stroke = !s_color || s_color == 'transparent' || options[:line_width] == 0
556
- return if no_fill && no_stroke
557
- save_graphics_state do
558
- radius = options[:radius] || 0
559
-
560
- # fill
561
- unless no_fill
562
- fill_color f_color
563
- fill_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
564
- end
565
-
566
- # stroke
567
- unless no_stroke
568
- stroke_color s_color
569
- line_width(options[:line_width] || 0.5)
570
- # FIXME think about best way to indicate dashed borders
571
- #if options.has_key? :dash_width
572
- # dash options[:dash_width], space: options[:dash_space] || 1
573
- #end
574
- stroke_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
575
- #undash if options.has_key? :dash_width
576
- end
577
- end
578
- end
579
-
580
- # Fills and, optionally, strokes the current bounds using the fill and
581
- # stroke color specified, then yields to the block. The only_if option can
582
- # be used to conditionally disable this behavior.
583
- #
584
- def shade_box color, line_color = nil, options = {}
585
- if (!options.has_key? :only_if) || options[:only_if]
586
- # FIXME could use save_graphics_state here
587
- previous_fill_color = current_fill_color
588
- fill_color color
589
- fill_rectangle [bounds.left, bounds.top], bounds.right, bounds.top - bounds.bottom
590
- fill_color previous_fill_color
591
- if line_color
592
- line_width 0.5
593
- previous_stroke_color = current_stroke_color
594
- stroke_color line_color
595
- stroke_bounds
596
- stroke_color previous_stroke_color
597
- end
598
- end
599
- yield
600
- end
601
-
602
- # A compliment to the stroke_horizontal_rule method, strokes a
603
- # vertical line using the current bounds. The width of the line
604
- # can be specified using the line_width option. The horizontal (x)
605
- # position can be specified using the at option.
606
- #
607
- def stroke_vertical_rule rule_color = stroke_color, options = {}
608
- rule_x = options[:at] || 0
609
- rule_y_from = bounds.top
610
- rule_y_to = bounds.bottom
611
- rule_style = options[:line_style]
612
- rule_width = options[:line_width] || 0.5
613
- save_graphics_state do
614
- line_width rule_width
615
- stroke_color rule_color
616
- case rule_style
617
- when :dashed
618
- dash rule_width * 4
619
- when :dotted
620
- dash rule_width
621
- when :double
622
- stroke_vertical_line rule_y_from, rule_y_to, at: (rule_x - rule_width)
623
- rule_x += rule_width
624
- end if rule_style
625
- stroke_vertical_line rule_y_from, rule_y_to, at: rule_x
626
- end
627
- end
628
-
629
- # Strokes a horizontal line using the current bounds. The width of the line
630
- # can be specified using the line_width option.
631
- #
632
- def stroke_horizontal_rule rule_color = stroke_color, options = {}
633
- rule_style = options[:line_style]
634
- rule_width = options[:line_width] || 0.5
635
- rule_x_start = bounds.left
636
- rule_x_end = bounds.right
637
- rule_inked = false
638
- save_graphics_state do
639
- line_width rule_width
640
- stroke_color rule_color
641
- case rule_style
642
- when :dashed
643
- dash rule_width * 4
644
- when :dotted
645
- dash rule_width
646
- when :double
647
- move_up rule_width
648
- stroke_horizontal_line rule_x_start, rule_x_end
649
- move_down rule_width * 2
650
- stroke_horizontal_line rule_x_start, rule_x_end
651
- move_up rule_width
652
- rule_inked = true
653
- end if rule_style
654
- stroke_horizontal_line rule_x_start, rule_x_end unless rule_inked
655
- end
656
- end
657
-
658
- # Pages
659
-
660
- # Deletes the current page and move the cursor
661
- # to the previous page.
662
- def delete_page
663
- pg = page_number
664
- pdf_store = state.store
665
- pdf_objs = pdf_store.instance_variable_get :@objects
666
- pdf_ids = pdf_store.instance_variable_get :@identifiers
667
- page_id = pdf_store.object_id_for_page pg
668
- content_id = page.content.identifier
669
- [page_id, content_id].each do |key|
670
- pdf_objs.delete key
671
- pdf_ids.delete key
672
- end
673
- pdf_store.pages.data[:Kids].pop
674
- pdf_store.pages.data[:Count] -= 1
675
- state.pages.pop
676
- if pg > 1
677
- go_to_page pg - 1
678
- else
679
- @page_number = 0
680
- state.page = nil
681
- end
682
- end
683
-
684
- # Import the specified page into the current document.
685
- #
686
- # By default, advance to the subsequent page, creating one if necessary.
687
- # This behavior can be disabled by passing the option `advance: false`.
688
- #
689
- def import_page file, opts = {}
690
- prev_page_layout = page.layout
691
- prev_page_size = page.size
692
- state.compress = false if state.compress # can't use compression if using template
693
- prev_text_rendering_mode = (defined? @text_rendering_mode) ? @text_rendering_mode : nil
694
- delete_page if opts[:replace]
695
- # NOTE use functionality provided by prawn-templates
696
- start_new_page_discretely template: file
697
- # prawn-templates sets text_rendering_mode to :unknown, which breaks running content; revert
698
- @text_rendering_mode = prev_text_rendering_mode
699
- if opts.fetch :advance, true
700
- # NOTE set page size & layout explicitly in case imported page differs
701
- # I'm not sure it's right to start a new page here, but unfortunately there's no other
702
- # way atm to prevent the size & layout of the imported page from affecting subsequent pages
703
- advance_page size: prev_page_size, layout: prev_page_layout
704
- end
705
- nil
706
- end
707
-
708
- # Create a new page for the specified image. If the
709
- # canvas option is true, the image is stretched to the
710
- # edges of the page (full coverage).
711
- def image_page file, options = {}
712
- start_new_page_discretely
713
- if options[:canvas]
714
- canvas do
715
- image file, width: bounds.width, height: bounds.height
716
- end
717
- else
718
- image file, fit: [bounds.width, bounds.height]
719
- end
720
- # FIXME shouldn't this be `go_to_page prev_page_number + 1`?
721
- go_to_page page_count
722
- nil
723
- end
724
-
725
- # Perform an operation (such as creating a new page) without triggering the on_page_create callback
726
- #
727
- def perform_discretely
728
- if (saved_callback = state.on_page_create_callback)
729
- # equivalent to calling `on_page_create`
730
- state.on_page_create_callback = nil
731
- yield
732
- # equivalent to calling `on_page_create &saved_callback`
733
- state.on_page_create_callback = saved_callback
734
- else
735
- yield
736
- end
737
- end
738
-
739
- # This method is a smarter version of start_new_page. It calls start_new_page
740
- # if the current page is the last page of the document. Otherwise, it simply
741
- # advances to the next existing page.
742
- def advance_page opts = {}
743
- last_page? ? (start_new_page opts) : (go_to_page page_number + 1)
744
- end
745
-
746
- # Start a new page without triggering the on_page_create callback
747
- #
748
- def start_new_page_discretely options = {}
749
- perform_discretely do
750
- start_new_page options
751
- end
752
- end
753
-
754
- # Grouping
755
-
756
- # Conditional group operation
757
- #
758
- def group_if verdict
759
- if verdict
760
- state.optimize_objects = false # optimize objects breaks group
761
- group { yield }
762
- else
763
- yield
764
- end
765
- end
766
-
767
- def get_scratch_document
768
- # marshal if not using transaction feature
769
- #Marshal.load Marshal.dump @prototype
770
-
771
- # use cached instance, tests show it's faster
772
- #@prototype ||= ::Prawn::Document.new
773
- @scratch ||= if defined? @prototype
774
- scratch = Marshal.load Marshal.dump @prototype
775
- scratch.instance_variable_set(:@prototype, @prototype)
776
- # TODO set scratch number on scratch document
777
- scratch
778
- else
779
- warn 'asciidoctor: WARNING: no scratch prototype available; instantiating fresh scratch document'
780
- ::Prawn::Document.new
781
- end
782
- end
783
-
784
- def scratch?
785
- (@_label ||= (state.store.info.data[:Scratch] ? :scratch : :primary)) == :scratch
786
- rescue
787
- false # NOTE this method may get called before the state is initialized
788
- end
789
- alias :is_scratch? :scratch?
790
-
791
- # TODO document me
792
- def dry_run &block
793
- scratch = get_scratch_document
794
- # QUESTION should we use scratch.advance_page instead?
795
- scratch.start_new_page
796
- start_page_number = scratch.page_number
797
- start_y = scratch.y
798
- if (left_padding = bounds.total_left_padding) > 0
799
- scratch.bounds.add_left_padding left_padding
800
- end
801
- if (right_padding = bounds.total_right_padding) > 0
802
- scratch.bounds.add_right_padding right_padding
803
- end
804
- scratch.font font_family, style: font_style, size: font_size do
805
- scratch.instance_exec(&block)
806
- end
807
- # NOTE don't count excess if cursor exceeds writable area (due to padding)
808
- full_page_height = scratch.effective_page_height
809
- partial_page_height = [full_page_height, start_y - scratch.y].min
810
- scratch.bounds.subtract_left_padding left_padding if left_padding > 0
811
- scratch.bounds.subtract_right_padding right_padding if right_padding > 0
812
- whole_pages = scratch.page_number - start_page_number
813
- [(whole_pages * full_page_height + partial_page_height), whole_pages, partial_page_height]
814
- end
815
-
816
- # Attempt to keep the objects generated in the block on the same page
817
- #
818
- # TODO short-circuit nested usage
819
- def keep_together &block
820
- available_space = cursor
821
- total_height, _, _ = dry_run(&block)
822
- # NOTE technically, if we're at the page top, we don't even need to do the
823
- # dry run, except several uses of this method rely on the calculated height
824
- if total_height > available_space && !at_page_top? && total_height <= effective_page_height
825
- advance_page
826
- started_new_page = true
827
- else
828
- started_new_page = false
829
- end
830
-
831
- # HACK yield doesn't work here on JRuby (at least not when called from AsciidoctorJ)
832
- #yield remainder, started_new_page
833
- instance_exec(total_height, started_new_page, &block)
834
- end
835
-
836
- # Attempt to keep the objects generated in the block on the same page
837
- # if the verdict parameter is true.
838
- #
839
- def keep_together_if verdict, &block
840
- if verdict
841
- keep_together(&block)
842
- else
843
- yield
844
- end
845
- end
846
-
847
- =begin
848
- def run_with_trial &block
849
- available_space = cursor
850
- total_height, whole_pages, remainder = dry_run(&block)
851
- if whole_pages > 0 || remainder > available_space
852
- started_new_page = true
853
- else
854
- started_new_page = false
855
- end
856
- # HACK yield doesn't work here on JRuby (at least not when called from AsciidoctorJ)
857
- #yield remainder, started_new_page
858
- instance_exec(remainder, started_new_page, &block)
859
- end
860
- =end
861
- end
862
- end
863
- end