asciidoctor-pdf 2.0.6 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +55 -0
- data/README.adoc +8 -14
- data/lib/asciidoctor/pdf/converter.rb +175 -69
- data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +4 -0
- data/lib/asciidoctor/pdf/ext/prawn/document/column_box.rb +21 -6
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +13 -9
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +2 -19
- data/lib/asciidoctor/pdf/ext/prawn-svg/elements/image.rb +15 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/file.rb +22 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg.rb +2 -0
- data/lib/asciidoctor/pdf/formatted_string.rb +13 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +21 -8
- data/lib/asciidoctor/pdf/theme_loader.rb +1 -1
- data/lib/asciidoctor/pdf/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fc9b0baae841d33e6205109c5aeacbb88bc196e6396b5dac8df187e0d209f9c
|
4
|
+
data.tar.gz: 21fd8ea4cebcb721dc273fa1a8f81104ec8f14f6a4c1a6f396bf6194f5e2309d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 956e66763693837a9483919f0b8b0428adbda5858efbfa75aa7b3a2fc04aff92a8e666643f7aae74ee01b656c1b9ad16984173dec4d0b0552bf668d0ad73d024
|
7
|
+
data.tar.gz: e6e2ca9d393d1eafbea531bdf9b828d50075bf398723ecca99549175331aa9f1422a9640128ccbf511e8ec97db3e7ce0c6d763a3bc28aeadf92fc6fd2a130f77
|
data/CHANGELOG.adoc
CHANGED
@@ -5,6 +5,61 @@
|
|
5
5
|
This document provides a high-level view of the changes to the {project-name} by release.
|
6
6
|
For a detailed view of what has changed, refer to the {url-repo}/commits/main[commit history] on GitHub.
|
7
7
|
|
8
|
+
== 2.1.0 (2022-06-11) - @mojavelinux
|
9
|
+
|
10
|
+
Enhancements::
|
11
|
+
|
12
|
+
* arrange body of article or manpage doctype into multiple columns if `page-columns` key is set in theme (#327)
|
13
|
+
* allow column gap to be specified using `page-column-gap` key (#327)
|
14
|
+
* introduce `convert_index_categories` method to handle rendering of categories for index inside column box (#327)
|
15
|
+
* rename `convert_index_list` method to `convert_index_term` to make its purpose more clear (#327)
|
16
|
+
* add `save_theme` helper to work with a copy of the theme within a scope (#2196)
|
17
|
+
* add support for `scale` attribute or `iw` unit on `pdfwidth` attribute on image macros (#1933)
|
18
|
+
* add backlink from bibref on bibliography entry to first reference to that entry in the document (#1737)
|
19
|
+
* preserve text formatting on index term in index section (#897)
|
20
|
+
* don't insert page break between part and first chapter if `heading-part-break-after` key in theme is `avoid` (#1795)
|
21
|
+
|
22
|
+
=== Details
|
23
|
+
|
24
|
+
{url-repo}/releases/tag/v2.1.0[git tag] | {url-repo}/compare/v2.0.8\...v2.1.0[full diff]
|
25
|
+
|
26
|
+
== 2.0.8 (2022-06-08) - @mojavelinux
|
27
|
+
|
28
|
+
Improvements::
|
29
|
+
|
30
|
+
* encapsulate logic to adjust column box inside float and dry run
|
31
|
+
* automatically set height on column box if not specified; update value automatically when reflowing margins
|
32
|
+
|
33
|
+
Bug Fixes::
|
34
|
+
|
35
|
+
* correctly compute value of to cursor on extent when column box starts below top of page (#2230)
|
36
|
+
* fix crash in `ColumnBox#move_past_bottom` when `:reflow_margins` option is not set
|
37
|
+
* fix x position of SVG when advanced to next column of column box
|
38
|
+
* `at_page_top?` should consider top of column box to be top of page
|
39
|
+
* prevent SVG image taller than column from being advanced to next column
|
40
|
+
* don't push section that follows index section in article to new page if last page of index does not extend to bottom of page
|
41
|
+
|
42
|
+
=== Details
|
43
|
+
|
44
|
+
{url-repo}/releases/tag/v2.0.8[git tag] | {url-repo}/compare/v2.0.7\...v2.0.8[full diff]
|
45
|
+
|
46
|
+
== 2.0.7 (2022-06-03) - @mojavelinux
|
47
|
+
|
48
|
+
Improvements::
|
49
|
+
|
50
|
+
* don't recommend prawn-gmagick if PNG or JPG is corrupt or incomplete
|
51
|
+
* add helper method to determine when to recommend the prawn-gmagick gem
|
52
|
+
|
53
|
+
Bug Fixes::
|
54
|
+
|
55
|
+
* fix crash when doctitle or section title with automatic ID contains inline image without explicit width (#2228)
|
56
|
+
* use prawn-gmagick, if available, to read raster image referenced by SVG (#2223)
|
57
|
+
* allow image path in SVG to refer to any location within Asciidoctor jail (no restriction if safe mode is unsafe) (#1941)
|
58
|
+
|
59
|
+
=== Details
|
60
|
+
|
61
|
+
{url-repo}/releases/tag/v2.0.7[git tag] | {url-repo}/compare/v2.0.6\...v2.0.7[full diff]
|
62
|
+
|
8
63
|
== 2.0.6 (2022-05-30) - @mojavelinux
|
9
64
|
|
10
65
|
Bug Fixes::
|
data/README.adoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= Asciidoctor PDF: A native PDF converter for AsciiDoc
|
2
2
|
Dan Allen <https://github.com/mojavelinux[@mojavelinux]>; Sarah White <https://github.com/graphitefriction[@graphitefriction]>
|
3
|
-
v2.0
|
3
|
+
v2.1.0, 2022-06-11
|
4
4
|
// Settings:
|
5
5
|
:experimental:
|
6
6
|
:idprefix:
|
@@ -23,8 +23,7 @@ endif::[]
|
|
23
23
|
:project-name: Asciidoctor PDF
|
24
24
|
:project-handle: asciidoctor-pdf
|
25
25
|
// Variables:
|
26
|
-
:release-line: 2.
|
27
|
-
:release-version: 2.0.6
|
26
|
+
:release-line: 2.1.x
|
28
27
|
// URLs:
|
29
28
|
:url-gem: https://rubygems.org/gems/asciidoctor-pdf
|
30
29
|
:url-project: https://github.com/asciidoctor/asciidoctor-pdf
|
@@ -47,7 +46,7 @@ The aim of this library is to take the pain out of creating PDF documents from A
|
|
47
46
|
|
48
47
|
[NOTE]
|
49
48
|
====
|
50
|
-
The documentation for Asciidoctor PDF
|
49
|
+
The documentation for the latest, stable release of Asciidoctor PDF 2.0.x is available at {url-project-docs}/.
|
51
50
|
|
52
51
|
If you're looking for the documentation for Asciidoctor PDF 1.6, refer to the {url-project-repo}/tree/v1.6.x#readme[README] in the v1.6.x branch.
|
53
52
|
Asciidoctor PDF 1.6 is no longer being developed and will reach EOL later this year.
|
@@ -62,7 +61,7 @@ Asciidoctor PDF converts an AsciiDoc document directly to a PDF document.
|
|
62
61
|
The style and layout of the PDF are controlled by a dedicated theme file.
|
63
62
|
To the degree possible, Asciidoctor PDF supports all the features of AsciiDoc that are supported by Asciidoctor.
|
64
63
|
It also provides {url-project-docs}/features/[PDF-specific features].
|
65
|
-
However, there are {url-project-docs}/features/#limitations[certain
|
64
|
+
However, there are {url-project-docs}/features/#limitations[certain limitations] imposed by the PDF format and the PDF library this extension uses.
|
66
65
|
|
67
66
|
Asciidoctor PDF uses the Prawn gem and Prawn's extensions, such as prawn-svg and prawn-table, to generate a PDF document.
|
68
67
|
{url-prawn}[Prawn] is a general purpose PDF generator for Ruby that features high-level APIs for common needs like setting up the page and inserting images and low-level APIs for positioning and rendering text and graphics.
|
@@ -114,11 +113,11 @@ There are several optional features of this converter that require additional ge
|
|
114
113
|
Those features are as follows.
|
115
114
|
|
116
115
|
Source highlighting::
|
117
|
-
You'll need to {url-project-
|
116
|
+
You'll need to {url-project-docs}/syntax-highlighting/[install a syntax highlighter] to use source highlighting (build-time only).
|
118
117
|
|
119
118
|
PDF optimization::
|
120
119
|
If you want to optimize your PDF, you'll need rghost or hexapdf.
|
121
|
-
See {url-project-
|
120
|
+
See {url-project-docs}/optimize-pdf/[Optimize the PDF] for installation and usage instructions.
|
122
121
|
|
123
122
|
Automatic hyphenation::
|
124
123
|
To turn on automatic hyphenation using the `hyphens` attribute, you'll need to install the `text-hyphen` gem:
|
@@ -128,7 +127,7 @@ To turn on automatic hyphenation using the `hyphens` attribute, you'll need to i
|
|
128
127
|
Accelerated image decoding::
|
129
128
|
Ruby is not particularly fast at decoding images, and the image formats it supports are limited.
|
130
129
|
To help, you can install prawn-gmagick, which delegates the work of decoding images to GraphicsMagick.
|
131
|
-
Refer to {url-project-
|
130
|
+
Refer to {url-project-docs}/image-paths-and-formats/#other-image-formats[Supporting additional image file formats] for instructions about how to enable this integration.
|
132
131
|
|
133
132
|
Check the {url-project-docs}/install/#table-minimum-version[minimum supported version table] to make sure you're using a supported version of the dependency.
|
134
133
|
|
@@ -182,7 +181,7 @@ Asciidoctor creates the output file in the same directory as the input file by d
|
|
182
181
|
Open the [.path]_basic-example.pdf_ file with a PDF viewer to see the result.
|
183
182
|
|
184
183
|
.Example PDF document rendered in a PDF viewer
|
185
|
-
image::docs/modules/ROOT/images/basic-example-pdf-screenshot.png[Screenshot of PDF document,960,
|
184
|
+
image::docs/modules/ROOT/images/basic-example-pdf-screenshot.png[Screenshot of PDF document,960,pdfwidth=100%]
|
186
185
|
|
187
186
|
For more information about how to use Asciidoctor PDF and PDF-specific AsciiDoc syntax, see the {url-project-docs}/[Asciidoctor PDF documentation].
|
188
187
|
|
@@ -197,11 +196,6 @@ ifndef::env-site[]
|
|
197
196
|
See the <<CONTRIBUTING.adoc#,contributing guide>>.
|
198
197
|
To help develop {project-name}, or to simply use the development version, refer to the <<CONTRIBUTING-CODE.adoc#,developing and contributing code guide>>.
|
199
198
|
|
200
|
-
[[resources,Links]]
|
201
|
-
== Resources
|
202
|
-
|
203
|
-
* https://groups.google.com/forum/#!msg/prawn-ruby/MbMsCx862iY/6ImCsvLGfVcJ[Discussion about image quality in PDFs]
|
204
|
-
|
205
199
|
== Authors
|
206
200
|
|
207
201
|
{project-name} was written by https://github.com/mojavelinux[Dan Allen] and https://github.com/graphitefriction[Sarah White] of OpenDevise Inc. on behalf of the Asciidoctor Project.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'formatted_string'
|
3
4
|
require_relative 'formatted_text'
|
4
5
|
require_relative 'index_catalog'
|
5
6
|
require_relative 'pdfmark'
|
@@ -20,6 +21,8 @@ module Asciidoctor
|
|
20
21
|
|
21
22
|
attr_reader :cache_uri
|
22
23
|
|
24
|
+
attr_reader :jail_dir
|
25
|
+
|
23
26
|
attr_accessor :font_color
|
24
27
|
|
25
28
|
attr_accessor :font_scale
|
@@ -122,6 +125,7 @@ module Asciidoctor
|
|
122
125
|
DropAnchorRx = %r(<(?:a\b[^>]*|/a)>)
|
123
126
|
SourceHighlighters = %w(coderay pygments rouge).to_set
|
124
127
|
ViewportWidth = ::Module.new
|
128
|
+
ImageWidth = ::Module.new
|
125
129
|
(TitleStyles = {
|
126
130
|
'toc' => [:numbered_title],
|
127
131
|
'basic' => [:title],
|
@@ -150,7 +154,7 @@ module Asciidoctor
|
|
150
154
|
log :warn, %(missing convert handler for #{name} node in #{@backend} backend)
|
151
155
|
end
|
152
156
|
# NOTE: inline node handlers generate HTML-like strings; all other handlers write directly to the PDF object
|
153
|
-
|
157
|
+
node.inline? ? result : self
|
154
158
|
end
|
155
159
|
|
156
160
|
def convert_document doc
|
@@ -160,7 +164,6 @@ module Asciidoctor
|
|
160
164
|
doc.attributes['outline'] = '' unless (doc.attribute_locked? 'outline') || ((doc.instance_variable_get :@attributes_modified).include? 'outline')
|
161
165
|
doc.attributes['outline-title'] = '' unless (doc.attribute_locked? 'outline-title') || ((doc.instance_variable_get :@attributes_modified).include? 'outline-title')
|
162
166
|
doc.attributes['pagenums'] = '' unless (doc.attribute_locked? 'pagenums') || ((doc.instance_variable_get :@attributes_modified).include? 'pagenums')
|
163
|
-
#assign_missing_section_ids doc
|
164
167
|
|
165
168
|
on_page_create(&(method :init_page))
|
166
169
|
|
@@ -168,7 +171,8 @@ module Asciidoctor
|
|
168
171
|
# NOTE: a new page will already be started (page_number = 2) if the front cover image is a PDF
|
169
172
|
ink_cover_page doc, :front
|
170
173
|
has_front_cover = page_number > marked_page_number
|
171
|
-
|
174
|
+
doctype = doc.doctype
|
175
|
+
if (has_title_page = (title_page_on = doctype == 'book' || (doc.attr? 'title-page')) && (start_title_page doc))
|
172
176
|
# NOTE: the base font must be set before any content is written to the main or scratch document
|
173
177
|
font @theme.base_font_family, size: @root_font_size, style: @theme.base_font_style
|
174
178
|
if perform_on_single_page { ink_title_page doc }
|
@@ -277,12 +281,19 @@ module Asciidoctor
|
|
277
281
|
doc.set_attr 'pdf-anchor', (derive_anchor_from_id doc.id, 'top')
|
278
282
|
doc.set_attr 'pdf-page-start', page_number
|
279
283
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
284
|
+
if doctype == 'book' || (columns = @theme.page_columns || 1) < 2
|
285
|
+
convert_section generate_manname_section doc if doctype == 'manpage' && (doc.attr? 'manpurpose')
|
286
|
+
traverse doc
|
287
|
+
# NOTE: for a book, these are leftover footnotes; for an article this is everything
|
288
|
+
outdent_section { ink_footnotes doc }
|
289
|
+
else
|
290
|
+
column_box [bounds.left, cursor], columns: columns, width: bounds.width, reflow_margins: true, spacer: @theme.page_column_gap do
|
291
|
+
convert_section generate_manname_section doc if doctype == 'manpage' && (doc.attr? 'manpurpose')
|
292
|
+
traverse doc
|
293
|
+
# NOTE: for a book, these are leftover footnotes; for an article this is everything
|
294
|
+
outdent_section { ink_footnotes doc }
|
295
|
+
end
|
296
|
+
end
|
286
297
|
|
287
298
|
if (toc_extent = @toc_extent)
|
288
299
|
if title_page_on && !insert_toc
|
@@ -336,7 +347,11 @@ module Asciidoctor
|
|
336
347
|
#@page_opts = { size: pdf_opts[:page_size], layout: pdf_opts[:page_layout] }
|
337
348
|
((::Prawn::Document.instance_method :initialize).bind self).call pdf_opts
|
338
349
|
renderer.min_version (@pdf_version = PDFVersions[doc.attr 'pdf-version'])
|
339
|
-
@
|
350
|
+
@tmp_files ||= {}
|
351
|
+
@allow_uri_read = doc.attr? 'allow-uri-read'
|
352
|
+
@cache_uri = doc.attr? 'cache-uri'
|
353
|
+
@jail_dir = doc.safe < ::Asciidoctor::SafeMode::SAFE ? nil : doc.base_dir
|
354
|
+
@media ||= doc.attr 'media', 'screen'
|
340
355
|
@page_margin_by_side = { recto: (page_margin_recto = page_margin), verso: (page_margin_verso = page_margin), cover: page_margin }
|
341
356
|
case doc.attr 'pdf-folio-placement', (@media == 'prepress' ? 'physical' : 'virtual')
|
342
357
|
when 'physical'
|
@@ -361,13 +376,6 @@ module Asciidoctor
|
|
361
376
|
else
|
362
377
|
@ppbook = nil
|
363
378
|
end
|
364
|
-
# QUESTION: should ThemeLoader handle registering fonts instead?
|
365
|
-
register_fonts theme.font_catalog, ((doc.attr 'pdf-fontsdir')&.sub '{docdir}', (doc.attr 'docdir')) || 'GEM_FONTS_DIR'
|
366
|
-
default_kerning theme.base_font_kerning != 'none'
|
367
|
-
@fallback_fonts = Array theme.font_fallbacks
|
368
|
-
@allow_uri_read = doc.attr? 'allow-uri-read'
|
369
|
-
@cache_uri = doc.attr? 'cache-uri'
|
370
|
-
@tmp_files = {}
|
371
379
|
if (bg_image = resolve_background_image doc, theme, 'page-background-image')&.first
|
372
380
|
@page_bg_image = { verso: bg_image, recto: bg_image }
|
373
381
|
else
|
@@ -380,6 +388,10 @@ module Asciidoctor
|
|
380
388
|
@page_bg_image[:recto] = bg_image[0] && bg_image
|
381
389
|
end
|
382
390
|
@page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF'
|
391
|
+
# QUESTION: should ThemeLoader handle registering fonts instead?
|
392
|
+
register_fonts theme.font_catalog, ((doc.attr 'pdf-fontsdir')&.sub '{docdir}', (doc.attr 'docdir')) || 'GEM_FONTS_DIR'
|
393
|
+
default_kerning theme.base_font_kerning != 'none'
|
394
|
+
@fallback_fonts = Array theme.font_fallbacks
|
383
395
|
@root_font_size = theme.base_font_size
|
384
396
|
@font_scale = 1
|
385
397
|
@font_color = theme.base_font_color
|
@@ -400,6 +412,7 @@ module Asciidoctor
|
|
400
412
|
@list_bullets = []
|
401
413
|
@bottom_gutters = [{}]
|
402
414
|
@rendered_footnotes = []
|
415
|
+
@bibref_refs = ::Set.new
|
403
416
|
@conum_glyphs = ConumSets[@theme.conum_glyphs || 'circled'] || (@theme.conum_glyphs.split ',').map do |r|
|
404
417
|
from, to = r.lstrip.split '-', 2
|
405
418
|
to ? ((get_char from)..(get_char to)).to_a : [(get_char from)]
|
@@ -573,6 +586,8 @@ module Asciidoctor
|
|
573
586
|
theme.base_font_style = theme.base_font_style&.to_sym || :normal
|
574
587
|
theme.page_numbering_start_at ||= 'body'
|
575
588
|
theme.running_content_start_at ||= 'body'
|
589
|
+
theme.heading_chapter_break_before ||= 'always'
|
590
|
+
theme.heading_part_break_before ||= 'always'
|
576
591
|
theme.heading_margin_page_top ||= 0
|
577
592
|
theme.heading_margin_top ||= 0
|
578
593
|
theme.heading_margin_bottom ||= 0
|
@@ -607,6 +622,13 @@ module Asciidoctor
|
|
607
622
|
theme
|
608
623
|
end
|
609
624
|
|
625
|
+
def save_theme
|
626
|
+
@theme = (original_theme = theme).dup
|
627
|
+
yield
|
628
|
+
ensure
|
629
|
+
@theme = original_theme
|
630
|
+
end
|
631
|
+
|
610
632
|
def indent_section
|
611
633
|
if (values = @section_indent)
|
612
634
|
indent(values[0], values[1]) { yield }
|
@@ -645,13 +667,14 @@ module Asciidoctor
|
|
645
667
|
hidden = sect.option? 'notitle'
|
646
668
|
hopts = { align: text_align, level: hlevel, part: part, chapterlike: chapterlike, outdent: !(part || chapterlike) }
|
647
669
|
if part
|
648
|
-
|
670
|
+
if @theme.heading_part_break_before == 'always'
|
649
671
|
started_new = true
|
650
672
|
start_new_part sect
|
651
673
|
end
|
652
674
|
elsif chapterlike
|
653
|
-
if @theme.heading_chapter_break_before
|
654
|
-
(@theme.heading_part_break_after == '
|
675
|
+
if (@theme.heading_chapter_break_before == 'always' &&
|
676
|
+
!(@theme.heading_part_break_after == 'avoid' && sect.first_section_of_part?)) ||
|
677
|
+
(@theme.heading_part_break_after == 'always' && sect.first_section_of_part?)
|
655
678
|
started_new = true
|
656
679
|
start_new_chapter sect
|
657
680
|
end
|
@@ -698,49 +721,74 @@ module Asciidoctor
|
|
698
721
|
end
|
699
722
|
|
700
723
|
def convert_index_section node
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
inline_format: false,
|
709
|
-
margin_bottom: @theme.description_list_term_spacing,
|
710
|
-
style: @theme.description_list_term_font_style&.to_sym
|
711
|
-
category.terms.each {|term| convert_index_list_item term, pagenum_sequence_style }
|
712
|
-
@theme.prose_margin_bottom > cursor ? bounds.move_past_bottom : (move_down @theme.prose_margin_bottom)
|
724
|
+
if ColumnBox === bounds || (columns = @theme.index_columns || 1) < 2
|
725
|
+
convert_index_categories @index.categories, (node.document.attr 'index-pagenum-sequence-style')
|
726
|
+
else
|
727
|
+
end_cursor = nil
|
728
|
+
column_box [bounds.left, cursor], columns: columns, width: bounds.width, reflow_margins: true, spacer: @theme.index_column_gap do
|
729
|
+
convert_index_categories @index.categories, (node.document.attr 'index-pagenum-sequence-style')
|
730
|
+
end_cursor = cursor if bounds.current_column == 0
|
713
731
|
end
|
732
|
+
# Q: could we move this logic into column_box?
|
733
|
+
move_cursor_to end_cursor if end_cursor
|
714
734
|
end
|
715
735
|
nil
|
716
736
|
end
|
717
737
|
|
718
|
-
def
|
719
|
-
|
738
|
+
def convert_index_categories categories, pagenum_sequence_style = nil
|
739
|
+
space_needed_for_category = @theme.description_list_term_spacing + (2 * (height_of_typeset_text 'A'))
|
740
|
+
categories.each do |category|
|
741
|
+
bounds.move_past_bottom if space_needed_for_category > cursor
|
742
|
+
ink_prose category.name,
|
743
|
+
align: :left,
|
744
|
+
inline_format: false,
|
745
|
+
margin_bottom: @theme.description_list_term_spacing,
|
746
|
+
style: @theme.description_list_term_font_style&.to_sym
|
747
|
+
category.terms.each {|term| convert_index_term term, pagenum_sequence_style }
|
748
|
+
@theme.prose_margin_bottom > cursor ? bounds.move_past_bottom : (move_down @theme.prose_margin_bottom)
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
def convert_index_term term, pagenum_sequence_style = nil
|
753
|
+
term_fragments = term.name.fragments
|
720
754
|
unless term.container?
|
755
|
+
pagenum_fragment = (parse_text %(<a>#{DummyText}</a>), inline_format: true)[0]
|
721
756
|
if @media == 'screen'
|
722
757
|
case pagenum_sequence_style
|
723
758
|
when 'page'
|
724
|
-
pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest|
|
759
|
+
pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| pagenum_fragment.merge anchor: dest[:anchor], text: dest[:page] }
|
725
760
|
when 'range'
|
726
761
|
first_anchor_per_page = {}.tap {|accum| term.dests.each {|dest| accum[dest[:page]] ||= dest[:anchor] } }
|
727
762
|
pagenums = (consolidate_ranges first_anchor_per_page.keys).map do |range|
|
728
763
|
anchor = first_anchor_per_page[(range.include? '-') ? (range.partition '-')[0] : range]
|
729
|
-
|
764
|
+
pagenum_fragment.merge text: range, anchor: anchor
|
730
765
|
end
|
731
766
|
else # term
|
732
|
-
pagenums = term.dests.map {|dest|
|
767
|
+
pagenums = term.dests.map {|dest| pagenum_fragment.merge text: dest[:page], anchor: dest[:anchor] }
|
733
768
|
end
|
734
769
|
else
|
735
770
|
pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
|
736
771
|
end
|
737
|
-
|
772
|
+
pagenums.each do |pagenum|
|
773
|
+
# NOTE: addresses a very minor kerning issue for text adjacent to the comma
|
774
|
+
if (prev_fragment = term_fragments[-1]).size == 1
|
775
|
+
if ::String === pagenum
|
776
|
+
term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, #{pagenum})
|
777
|
+
next
|
778
|
+
else
|
779
|
+
term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, )
|
780
|
+
end
|
781
|
+
else
|
782
|
+
term_fragments << ({ text: ', ' })
|
783
|
+
end
|
784
|
+
term_fragments << (::String === pagenum ? { text: pagenum } : pagenum)
|
785
|
+
end
|
738
786
|
end
|
739
787
|
subterm_indent = @theme.description_list_description_indent
|
740
|
-
|
788
|
+
typeset_formatted_text term_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2
|
741
789
|
indent subterm_indent do
|
742
790
|
term.subterms.each do |subterm|
|
743
|
-
|
791
|
+
convert_index_term subterm, pagenum_sequence_style
|
744
792
|
end
|
745
793
|
end unless term.leaf?
|
746
794
|
end
|
@@ -936,7 +984,7 @@ module Asciidoctor
|
|
936
984
|
height: label_height,
|
937
985
|
fallback_font_name: fallback_svg_font_name,
|
938
986
|
enable_web_requests: allow_uri_read ? (method :load_open_uri).to_proc : false,
|
939
|
-
enable_file_requests_with_root: (::File.dirname icon_path),
|
987
|
+
enable_file_requests_with_root: { base: (::File.dirname icon_path), root: @jail_dir },
|
940
988
|
cache_images: cache_uri
|
941
989
|
svg_obj.resize height: label_height if svg_obj.document.sizing.output_height > label_height
|
942
990
|
svg_obj.draw
|
@@ -1709,9 +1757,14 @@ module Asciidoctor
|
|
1709
1757
|
return on_image_error :missing, node, target, (opts.merge align: alignment) unless image_path
|
1710
1758
|
|
1711
1759
|
# TODO: support cover (aka canvas) image layout using "canvas" (or "cover") role
|
1712
|
-
width = resolve_explicit_width node.attributes, bounds_width: (available_w = bounds.width), support_vw: true, use_fallback: true, constrain_to_bounds: true
|
1713
|
-
|
1714
|
-
|
1760
|
+
case (width = resolve_explicit_width node.attributes, bounds_width: (available_w = bounds.width), support_vw: true, use_fallback: true, constrain_to_bounds: true)
|
1761
|
+
when ViewportWidth
|
1762
|
+
# TODO: add `to_pt page_width` method to ViewportWidth type
|
1763
|
+
width = page_width * (width.to_f / 100)
|
1764
|
+
when ImageWidth
|
1765
|
+
scale = width.to_f / 100
|
1766
|
+
width = nil
|
1767
|
+
end
|
1715
1768
|
|
1716
1769
|
caption_end = @theme.image_caption_end&.to_sym || :bottom
|
1717
1770
|
caption_max_width = @theme.image_caption_max_width
|
@@ -1731,7 +1784,7 @@ module Asciidoctor
|
|
1731
1784
|
file_request_root = false
|
1732
1785
|
else
|
1733
1786
|
svg_data = ::File.read image_path, mode: 'r:UTF-8'
|
1734
|
-
file_request_root = ::File.dirname image_path
|
1787
|
+
file_request_root = { base: (::File.dirname image_path), root: @jail_dir }
|
1735
1788
|
end
|
1736
1789
|
svg_obj = ::Prawn::SVG::Interface.new svg_data, self,
|
1737
1790
|
position: alignment,
|
@@ -1741,7 +1794,9 @@ module Asciidoctor
|
|
1741
1794
|
enable_file_requests_with_root: file_request_root,
|
1742
1795
|
cache_images: cache_uri
|
1743
1796
|
rendered_w = (svg_size = svg_obj.document.sizing).output_width
|
1744
|
-
if
|
1797
|
+
if scale
|
1798
|
+
svg_size = svg_obj.resize width: (rendered_w = [available_w, rendered_w * scale].min)
|
1799
|
+
elsif !width && (svg_obj.document.root.attributes.key? 'width') && rendered_w > available_w
|
1745
1800
|
# NOTE: restrict width to available width (prawn-svg already coerces to pixels)
|
1746
1801
|
svg_size = svg_obj.resize width: (rendered_w = available_w)
|
1747
1802
|
end
|
@@ -1749,6 +1804,7 @@ module Asciidoctor
|
|
1749
1804
|
if (rendered_h = svg_size.output_height) > (available_h = cursor - caption_h)
|
1750
1805
|
unless pinned || at_page_top?
|
1751
1806
|
advance_page
|
1807
|
+
(svg_obj.options[:at] = svg_obj.position)[0] += bounds.left if ColumnBox === bounds
|
1752
1808
|
available_h = cursor - caption_h
|
1753
1809
|
end
|
1754
1810
|
rendered_w = (svg_obj.resize height: (rendered_h = available_h)).output_width if rendered_h > available_h
|
@@ -1774,8 +1830,10 @@ module Asciidoctor
|
|
1774
1830
|
image_obj, image_info = ::Base64 === image_path ?
|
1775
1831
|
::StringIO.open((::Base64.decode64 image_path), 'rb') {|fd| build_image_object fd } :
|
1776
1832
|
::File.open(image_path, 'rb') {|fd| build_image_object fd }
|
1833
|
+
actual_w = to_pt image_info.width, :px
|
1834
|
+
width = actual_w * scale if scale
|
1777
1835
|
# NOTE: if width is not specified, scale native width & height from px to pt and restrict width to available width
|
1778
|
-
rendered_w, rendered_h = image_info.calc_image_dimensions width: (width || [available_w,
|
1836
|
+
rendered_w, rendered_h = image_info.calc_image_dimensions width: (width || [available_w, actual_w].min)
|
1779
1837
|
# NOTE: shrink image so it fits within available space; group image & caption
|
1780
1838
|
if rendered_h > (available_h = cursor - caption_h)
|
1781
1839
|
unless pinned || at_page_top?
|
@@ -1811,7 +1869,7 @@ module Asciidoctor
|
|
1811
1869
|
end
|
1812
1870
|
rescue => e
|
1813
1871
|
raise if ::StopIteration === e
|
1814
|
-
on_image_error :exception, node, target, (opts.merge align: alignment, message: %(could not embed image: #{image_path}; #{e.message}#{
|
1872
|
+
on_image_error :exception, node, target, (opts.merge align: alignment, message: %(could not embed image: #{image_path}; #{e.message}#{(recommend_prawn_gmagick? e, image_format) ? %(; install prawn-gmagick gem to add support for #{image_format&.upcase || 'unknown'} image format) : ''}))
|
1815
1873
|
end
|
1816
1874
|
end
|
1817
1875
|
|
@@ -2373,9 +2431,12 @@ module Asciidoctor
|
|
2373
2431
|
if (text = ref.xreftext node.attr 'xrefstyle', nil, true)&.include? '<a'
|
2374
2432
|
text = text.gsub DropAnchorRx, ''
|
2375
2433
|
end
|
2434
|
+
if ref.inline? && ref.type == :bibref && !scratch? && (@bibref_refs.add? refid)
|
2435
|
+
anchor = %(<a id="_bibref_ref_#{refid}">#{DummyText}</a>)
|
2436
|
+
end
|
2376
2437
|
@resolving_xref = nil
|
2377
2438
|
end
|
2378
|
-
%(<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', ']'
|
2439
|
+
%(#{anchor || ''}<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', ']'
|
2379
2440
|
else
|
2380
2441
|
%(<a anchor="#{doc.attr 'pdf-anchor'}">#{node.text || '[^top]'}</a>)
|
2381
2442
|
end
|
@@ -2383,10 +2444,12 @@ module Asciidoctor
|
|
2383
2444
|
# NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
|
2384
2445
|
%(<a id="#{node.id}">#{DummyText}</a>)
|
2385
2446
|
when :bibref
|
2386
|
-
|
2447
|
+
id = node.id
|
2387
2448
|
# NOTE: technically node.text should be node.reftext, but subs have already been applied to text
|
2388
|
-
reftext = (reftext = node.reftext) ? %([#{reftext}]) : %([#{
|
2389
|
-
%(<a
|
2449
|
+
reftext = (reftext = node.reftext) ? %([#{reftext}]) : %([#{id}])
|
2450
|
+
reftext = %(<a anchor="_bibref_ref_#{id}">#{reftext}</a>) if @bibref_refs.include? id
|
2451
|
+
# NOTE: destination is created inside callback registered by FormattedTextTransform#build_fragment
|
2452
|
+
%(<a id="#{id}">#{DummyText}</a>#{reftext})
|
2390
2453
|
else
|
2391
2454
|
log :warn, %(unknown anchor type: #{node.type.inspect})
|
2392
2455
|
nil
|
@@ -2505,14 +2568,25 @@ module Asciidoctor
|
|
2505
2568
|
# NOTE: an image with a data URI is handled using a temporary file
|
2506
2569
|
elsif (image_path = resolve_image_path node, target, image_format)
|
2507
2570
|
if ::File.readable? image_path
|
2571
|
+
class_attr = (role = node.role) ? %( class="#{role}") : ''
|
2572
|
+
fit_attr = (fit = node.attr 'fit') ? %( fit="#{fit}") : ''
|
2508
2573
|
if (width = resolve_explicit_width node.attributes)
|
2509
|
-
|
2574
|
+
if ImageWidth === width
|
2575
|
+
if state # check that converter is initialized
|
2576
|
+
width = (intrinsic_image_width image_path, image_format) * (width.to_f / 100)
|
2577
|
+
else
|
2578
|
+
width = %(auto*#{width})
|
2579
|
+
end
|
2580
|
+
elsif node.parent.context == :table_cell && ::String === width && (width.end_with? '%')
|
2581
|
+
width += (intrinsic_image_width image_path, image_format).to_s
|
2582
|
+
end
|
2583
|
+
width_attr = %( width="#{width}")
|
2584
|
+
elsif state # check that converter is initialized
|
2585
|
+
width_attr = %( width="#{intrinsic_image_width image_path, image_format}")
|
2510
2586
|
else
|
2511
|
-
|
2587
|
+
width_attr = ' width="auto"' # defer operation until arranger runs
|
2512
2588
|
end
|
2513
|
-
|
2514
|
-
fit_attr = (fit = node.attr 'fit') ? %( fit="#{fit}") : ''
|
2515
|
-
img = %(<img src="#{image_path}" format="#{image_format}" alt="#{encode_quotes node.attr 'alt'}" width="#{width}"#{class_attr}#{fit_attr}>)
|
2589
|
+
img = %(<img src="#{image_path}" format="#{image_format}" alt="#{encode_quotes node.attr 'alt'}"#{width_attr}#{class_attr}#{fit_attr}>)
|
2516
2590
|
else
|
2517
2591
|
log :warn, %(image to embed not found or not readable: #{image_path})
|
2518
2592
|
img = %([#{node.attr 'alt'}])
|
@@ -2529,17 +2603,20 @@ module Asciidoctor
|
|
2529
2603
|
if scratch?
|
2530
2604
|
visible ? node.text : ''
|
2531
2605
|
else
|
2532
|
-
|
2533
|
-
|
2606
|
+
unless defined? @index
|
2607
|
+
# NOTE: initialize index and text formatter in case converter is called before PDF is initialized
|
2608
|
+
@index = IndexCatalog.new
|
2609
|
+
@text_formatter = FormattedText::Formatter.new theme: (load_theme node.document)
|
2610
|
+
end
|
2534
2611
|
# NOTE: page number (:page key) is added by InlineDestinationMarker
|
2535
2612
|
dest = { anchor: (anchor_name = @index.next_anchor_name) }
|
2536
2613
|
anchor = %(<a id="#{anchor_name}" type="indexterm"#{visible ? ' visible="true"' : ''}>#{DummyText}</a>)
|
2537
2614
|
if visible
|
2538
2615
|
visible_term = node.text
|
2539
|
-
@index.store_primary_term (
|
2616
|
+
@index.store_primary_term (FormattedString.new parse_text visible_term, inline_format: [normalize: true]), dest
|
2540
2617
|
%(#{anchor}#{visible_term})
|
2541
2618
|
else
|
2542
|
-
@index.store_term (node.attr 'terms').map {|term|
|
2619
|
+
@index.store_term (node.attr 'terms').map {|term| FormattedString.new parse_text term, inline_format: [normalize: true] }, dest
|
2543
2620
|
anchor
|
2544
2621
|
end
|
2545
2622
|
end
|
@@ -2566,8 +2643,6 @@ module Asciidoctor
|
|
2566
2643
|
end
|
2567
2644
|
|
2568
2645
|
def convert_inline_quoted node
|
2569
|
-
theme = load_theme node.document
|
2570
|
-
|
2571
2646
|
case node.type
|
2572
2647
|
when :emphasis
|
2573
2648
|
open, close, is_tag = ['<em>', '</em>', true]
|
@@ -2580,25 +2655,26 @@ module Asciidoctor
|
|
2580
2655
|
when :subscript
|
2581
2656
|
open, close, is_tag = ['<sub>', '</sub>', true]
|
2582
2657
|
when :double
|
2583
|
-
open, close
|
2658
|
+
open, close = (load_theme node.document).quotes.slice 0, 2
|
2584
2659
|
quotes = true
|
2585
2660
|
when :single
|
2586
|
-
open, close
|
2661
|
+
open, close = (load_theme node.document).quotes.slice 2, 2
|
2587
2662
|
quotes = true
|
2588
2663
|
when :mark
|
2589
2664
|
open, close, is_tag = ['<mark>', '</mark>', true]
|
2590
2665
|
else
|
2591
|
-
open
|
2666
|
+
open = close = ''
|
2592
2667
|
end
|
2593
2668
|
|
2594
2669
|
inner_text = node.text
|
2595
2670
|
|
2596
|
-
if quotes && (len = inner_text.length) > 3 &&
|
2597
|
-
|
2671
|
+
if quotes && (len = inner_text.length) > 3 && (inner_text.end_with? '...') &&
|
2672
|
+
!((inner_text_trunc = inner_text.slice 0, len - 3).end_with? ?\\)
|
2598
2673
|
inner_text = inner_text_trunc + '…'
|
2599
2674
|
end
|
2600
2675
|
|
2601
2676
|
if (roles = node.role)
|
2677
|
+
theme = load_theme node.document
|
2602
2678
|
roles.split.each do |role|
|
2603
2679
|
if (text_transform = theme[%(role_#{role}_text_transform)])
|
2604
2680
|
inner_text = transform_text inner_text, text_transform
|
@@ -3838,6 +3914,31 @@ module Asciidoctor
|
|
3838
3914
|
end
|
3839
3915
|
end
|
3840
3916
|
|
3917
|
+
# Retrieve the intrinsic image dimensions for the specified path in pt.
|
3918
|
+
#
|
3919
|
+
# Returns a Hash containing :width and :height keys that map to the image's
|
3920
|
+
# intrinsic width and height values (in pt).
|
3921
|
+
def intrinsic_image_dimensions path, format
|
3922
|
+
if format == 'svg'
|
3923
|
+
# NOTE: prawn-svg automatically converts intrinsic width and height to pt
|
3924
|
+
img_obj = ::Prawn::SVG::Interface.new (::File.read path, mode: 'r:UTF-8'), self, {}
|
3925
|
+
img_size = img_obj.document.sizing
|
3926
|
+
{ width: img_size.output_width, height: img_size.output_height }
|
3927
|
+
else
|
3928
|
+
# NOTE: build_image_object caches image data previously loaded
|
3929
|
+
# NOTE: build_image_object computes intrinsic width and height in px
|
3930
|
+
_, img_size = ::File.open(path, 'rb') {|fd| build_image_object fd }
|
3931
|
+
{ width: (to_pt img_size.width, :px), height: (to_pt img_size.height, :px) }
|
3932
|
+
end
|
3933
|
+
rescue
|
3934
|
+
# NOTE: image can't be read, so it won't be used anyway
|
3935
|
+
{ width: 0, height: 0 }
|
3936
|
+
end
|
3937
|
+
|
3938
|
+
def intrinsic_image_width path, format
|
3939
|
+
(intrinsic_image_dimensions path, format)[:width]
|
3940
|
+
end
|
3941
|
+
|
3841
3942
|
# Sends the specified message to the log unless this method is called from the scratch document
|
3842
3943
|
def log severity, message = nil, &block
|
3843
3944
|
logger.send severity, message, &block unless scratch?
|
@@ -4009,11 +4110,15 @@ module Asciidoctor
|
|
4009
4110
|
if attrs.key? 'pdfwidth'
|
4010
4111
|
if (width = attrs['pdfwidth']).end_with? '%'
|
4011
4112
|
bounds_width ? (width.to_f / 100) * bounds_width : width
|
4113
|
+
elsif width.end_with? 'iw'
|
4114
|
+
(width.chomp 'iw').extend ImageWidth
|
4012
4115
|
elsif opts[:support_vw] && (width.end_with? 'vw')
|
4013
4116
|
(width.chomp 'vw').extend ViewportWidth
|
4014
4117
|
else
|
4015
4118
|
str_to_pt width
|
4016
4119
|
end
|
4120
|
+
elsif attrs.key? 'scale'
|
4121
|
+
attrs['scale'].dup.extend ImageWidth
|
4017
4122
|
elsif attrs.key? 'scaledwidth'
|
4018
4123
|
# NOTE: the parser automatically appends % if value is unitless
|
4019
4124
|
if (width = attrs['scaledwidth']).end_with? '%'
|
@@ -4046,7 +4151,7 @@ module Asciidoctor
|
|
4046
4151
|
def resolve_image_options image_path, image_format, image_attrs, opts = {}
|
4047
4152
|
if image_format == 'svg'
|
4048
4153
|
image_opts = {
|
4049
|
-
enable_file_requests_with_root: (::File.dirname image_path),
|
4154
|
+
enable_file_requests_with_root: { base: (::File.dirname image_path), root: @jail_dir },
|
4050
4155
|
enable_web_requests: allow_uri_read ? (method :load_open_uri).to_proc : false,
|
4051
4156
|
cache_images: cache_uri,
|
4052
4157
|
fallback_font_name: fallback_svg_font_name,
|
@@ -4133,6 +4238,7 @@ module Asciidoctor
|
|
4133
4238
|
else
|
4134
4239
|
imagesdir = relative_to
|
4135
4240
|
end
|
4241
|
+
@tmp_files ||= {}
|
4136
4242
|
# NOTE: base64 logic currently used for inline images
|
4137
4243
|
if ::Base64 === image_path
|
4138
4244
|
return @tmp_files[image_path] if @tmp_files.key? image_path
|
@@ -30,4 +30,8 @@ class Asciidoctor::Section
|
|
30
30
|
end
|
31
31
|
opts[:formal] ? @cached_formal_numbered_title : @cached_numbered_title
|
32
32
|
end unless method_defined? :numbered_title
|
33
|
+
|
34
|
+
def first_section_of_part?
|
35
|
+
(par = @parent).context == :section && par.sectname == 'part' && self == par.blocks.find {|it| it.context == :section }
|
36
|
+
end unless method_defined? :first_section_of_part?
|
33
37
|
end
|
@@ -1,20 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Prawn::Document::ColumnBox.prepend (Module.new do
|
4
|
-
|
5
|
-
stretchy? ? @parent.absolute_bottom : super
|
6
|
-
end
|
4
|
+
attr_accessor :current_column
|
7
5
|
|
8
6
|
def move_past_bottom
|
9
7
|
(doc = @document).y = @y
|
10
8
|
return if (@current_column = (@current_column + 1) % @columns) > 0
|
11
|
-
|
9
|
+
par = @parent
|
10
|
+
if (reset_y = @reflow_margins) && (reset_y == true || reset_y > doc.page_number)
|
11
|
+
@y = par.absolute_top
|
12
|
+
@height = par.height unless stretchy?
|
13
|
+
end
|
12
14
|
initial_margins = doc.page.margins
|
13
15
|
par.move_past_bottom
|
14
16
|
if doc.page.margins != initial_margins
|
15
|
-
doc.bounds = self.class.new doc, par, (margin_box = doc.margin_box).
|
16
|
-
columns: @columns, reflow_margins:
|
17
|
+
doc.bounds = self.class.new doc, par, [(margin_box = doc.margin_box).absolute_left, @y],
|
18
|
+
columns: @columns, reflow_margins: @reflow_margins, spacer: @spacer, width: margin_box.width, height: @height
|
19
|
+
end
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# Rearranges the column box into a single column, where the original columns are in a single file. Used
|
24
|
+
# for the purpose of computing the extent of content in a scratch document.
|
25
|
+
def single_file
|
26
|
+
if @reflow_margins && @parent.absolute_top > @y && @columns > @current_column + 1
|
27
|
+
# defer reflow margins until all columns on current page have been exhausted
|
28
|
+
@reflow_margins = @document.page_number + (@columns - @current_column)
|
17
29
|
end
|
30
|
+
@width = bare_column_width
|
31
|
+
@columns = 1
|
32
|
+
@current_column = 0
|
18
33
|
nil
|
19
34
|
end
|
20
35
|
end)
|
@@ -251,16 +251,17 @@ module Asciidoctor
|
|
251
251
|
# Returns whether the cursor is at the top of the page (i.e., margin box).
|
252
252
|
#
|
253
253
|
def at_page_top?
|
254
|
-
@y == @margin_box.absolute_top
|
254
|
+
@y == (ColumnBox === bounds ? bounds : @margin_box).absolute_top
|
255
255
|
end
|
256
256
|
|
257
257
|
# Prevents at_page_top? from returning true while yielding to the specified block.
|
258
258
|
#
|
259
259
|
def conceal_page_top
|
260
|
-
|
260
|
+
old_top = (outer_bounds = ColumnBox === bounds ? bounds : @margin_box).absolute_top
|
261
|
+
outer_bounds.instance_variable_set :@y, old_top + 0.0001
|
261
262
|
yield
|
262
263
|
ensure
|
263
|
-
|
264
|
+
outer_bounds.instance_variable_set :@y, old_top
|
264
265
|
end
|
265
266
|
|
266
267
|
# Returns whether the current page is the last page in the document.
|
@@ -560,11 +561,11 @@ module Asciidoctor
|
|
560
561
|
def float
|
561
562
|
original_page_number = page_number
|
562
563
|
original_y = y
|
563
|
-
original_column = bounds.
|
564
|
+
original_column = bounds.current_column if ColumnBox === bounds
|
564
565
|
yield
|
565
566
|
go_to_page original_page_number unless page_number == original_page_number
|
566
567
|
self.y = original_y
|
567
|
-
bounds.
|
568
|
+
bounds.current_column = original_column if original_column
|
568
569
|
end
|
569
570
|
|
570
571
|
# Short-circuits the call to the built-in move_up operation
|
@@ -721,6 +722,12 @@ module Asciidoctor
|
|
721
722
|
end
|
722
723
|
end
|
723
724
|
|
725
|
+
# Wraps the column_box method and automatically sets the height unless the :height option is specified.
|
726
|
+
def column_box point, options, &block
|
727
|
+
options[:height] = cursor unless options.key? :height
|
728
|
+
super
|
729
|
+
end
|
730
|
+
|
724
731
|
# A flowing version of bounding_box. If the content runs to another page, the cursor starts at
|
725
732
|
# the top of the page instead of from the original cursor position. Similar to span, except
|
726
733
|
# the :position option is limited to a numeric value and additional options are passed through
|
@@ -1146,10 +1153,7 @@ module Asciidoctor
|
|
1146
1153
|
scratch_pdf.bounds = bounds.dup.tap do |bounds_copy|
|
1147
1154
|
bounds_copy.instance_variable_set :@document, scratch_pdf
|
1148
1155
|
bounds_copy.instance_variable_set :@parent, saved_bounds
|
1149
|
-
if ColumnBox === bounds_copy
|
1150
|
-
bounds_copy.instance_variable_set :@width, bounds_copy.bare_column_width
|
1151
|
-
bounds_copy.instance_variable_set :@current_column, (bounds_copy.instance_variable_set :@columns, 1) - 1
|
1152
|
-
end
|
1156
|
+
bounds_copy.single_file if ColumnBox === bounds_copy
|
1153
1157
|
end
|
1154
1158
|
scratch_pdf.move_cursor_to cursor unless (scratch_start_at_top = keep_together || pages_advanced > 0 || at_page_top?)
|
1155
1159
|
scratch_start_cursor = scratch_pdf.cursor
|
@@ -33,25 +33,8 @@ module Asciidoctor
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
# Returns a Hash containing :width and :height keys that map to the image's
|
39
|
-
# intrinsic width and height values (in pt).
|
40
|
-
def intrinsic_image_dimensions path, format
|
41
|
-
if format == 'svg'
|
42
|
-
# NOTE: prawn-svg computes intrinsic width and height in pt
|
43
|
-
img_obj = ::Prawn::SVG::Interface.new (::File.read path, mode: 'r:UTF-8'), self, {}
|
44
|
-
img_size = img_obj.document.sizing
|
45
|
-
{ width: img_size.output_width, height: img_size.output_height }
|
46
|
-
else
|
47
|
-
# NOTE: build_image_object caches image data previously loaded
|
48
|
-
# NOTE: build_image_object computes intrinsic width and height in px
|
49
|
-
_, img_size = ::File.open(path, 'rb') {|fd| build_image_object fd }
|
50
|
-
{ width: (to_pt img_size.width, :px), height: (to_pt img_size.height, :px) }
|
51
|
-
end
|
52
|
-
rescue
|
53
|
-
# NOTE: image cannot be read, so it won't be used anyway
|
54
|
-
{ width: 0, height: 0 }
|
36
|
+
def recommend_prawn_gmagick? err, image_format
|
37
|
+
::Prawn::Errors::UnsupportedImageType === err && !(defined? ::GMagick::Image) && ((err.message.include? 'PNG') || (%w(jpg png).none? image_format))
|
55
38
|
end
|
56
39
|
end
|
57
40
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Prawn::SVG::Elements::Image.prepend (Module.new do
|
4
|
+
def image_dimensions data
|
5
|
+
unless (handler = find_image_handler data)
|
6
|
+
raise ::Prawn::SVG::Elements::Base::SkipElementError, 'Unsupported image type supplied to image tag'
|
7
|
+
end
|
8
|
+
image = handler.new data
|
9
|
+
[image.width.to_f, image.height.to_f]
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_image_handler data
|
13
|
+
Prawn.image_handler.find data rescue nil
|
14
|
+
end
|
15
|
+
end)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Prawn::SVG::Loaders::File.prepend (Module.new do
|
4
|
+
attr_reader :jail_path
|
5
|
+
|
6
|
+
def initialize root_path
|
7
|
+
if Hash === root_path
|
8
|
+
@jail_path = root_path[:root]
|
9
|
+
root_path = root_path[:base]
|
10
|
+
super
|
11
|
+
else
|
12
|
+
super
|
13
|
+
@jail_path = self.root_path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert_valid_path! path
|
18
|
+
if jail_path && !(path.start_with? %(#{jail_path}#{File::SEPARATOR}))
|
19
|
+
raise Prawn::SVG::UrlLoader::Error, %(file path points to location outside of jail #{jail_path})
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end)
|
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'prawn-svg'
|
4
4
|
require_relative 'prawn-svg/calculators/document_sizing'
|
5
|
+
require_relative 'prawn-svg/elements/image'
|
5
6
|
require_relative 'prawn-svg/loaders/data'
|
7
|
+
require_relative 'prawn-svg/loaders/file'
|
6
8
|
require_relative 'prawn-svg/loaders/web'
|
7
9
|
require_relative 'prawn-svg/url_loader'
|
8
10
|
# NOTE: disable system fonts since they're non-portable
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FormattedString < String
|
4
|
+
attr_reader :fragments
|
5
|
+
|
6
|
+
def initialize fragments
|
7
|
+
super [].tap {|accum| (@fragments = fragments).each {|it| accum << it[:text] } }.join
|
8
|
+
end
|
9
|
+
|
10
|
+
def eql? other
|
11
|
+
super && (FormattedString === other ? (@fragments ||= nil) == other.fragments : true)
|
12
|
+
end
|
13
|
+
end
|
@@ -48,10 +48,14 @@ module Asciidoctor::PDF::FormattedText
|
|
48
48
|
drop = scratch
|
49
49
|
end
|
50
50
|
image_path = fragment[:image_path]
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
image_format = fragment[:image_format]
|
52
|
+
if (image_w = fragment[:image_width] || '100%') == 'auto'
|
53
|
+
image_w = nil # use intrinsic width
|
54
|
+
elsif image_w.start_with? 'auto*'
|
55
|
+
image_scale = (image_w.slice 5, image_w.length).to_f
|
56
|
+
image_w = nil # use intrinsic width
|
57
|
+
elsif (pctidx = image_w.index '%') && pctidx + 1 < image_w.length
|
58
|
+
# NOTE: intrinsic width is stored behind % symbol
|
55
59
|
pct = (image_w.slice 0, pctidx).to_f / 100
|
56
60
|
intrinsic_w = (image_w.slice pctidx + 1, image_w.length).to_f
|
57
61
|
image_w = [available_w, pct * intrinsic_w].min
|
@@ -62,26 +66,35 @@ module Asciidoctor::PDF::FormattedText
|
|
62
66
|
max_image_h = fragment[:image_fit] == 'line' ? [available_h, doc.font.height].min : available_h
|
63
67
|
|
64
68
|
# TODO: make helper method to calculate width and height of image
|
65
|
-
if
|
69
|
+
if image_format == 'svg'
|
66
70
|
svg_obj = ::Prawn::SVG::Interface.new ::File.read(image_path, mode: 'r:UTF-8'), doc,
|
67
71
|
at: doc.bounds.top_left,
|
68
72
|
width: image_w,
|
69
73
|
fallback_font_name: doc.fallback_svg_font_name,
|
70
74
|
enable_web_requests: doc.allow_uri_read ? (doc.method :load_open_uri).to_proc : false,
|
71
|
-
enable_file_requests_with_root: (::File.dirname image_path),
|
75
|
+
enable_file_requests_with_root: { base: (::File.dirname image_path), root: doc.jail_dir },
|
72
76
|
cache_images: doc.cache_uri
|
73
77
|
svg_size = svg_obj.document.sizing
|
74
78
|
# NOTE: the best we can do is make the image fit within full height of bounds
|
75
79
|
if (image_h = svg_size.output_height) > max_image_h
|
76
80
|
image_w = (svg_obj.resize height: (image_h = max_image_h)).output_width
|
77
|
-
|
81
|
+
elsif image_w
|
78
82
|
image_w = svg_size.output_width
|
83
|
+
else
|
84
|
+
fragment[:image_width] = (image_w = svg_size.output_width).to_s
|
85
|
+
image_w *= image_scale if image_scale
|
86
|
+
image_w = available_w if image_w > available_w
|
79
87
|
end
|
80
88
|
fragment[:image_obj] = svg_obj
|
81
89
|
else
|
82
90
|
# TODO: cache image info based on path (Prawn caches based on SHA1 of content)
|
83
91
|
# NOTE: image_obj is constrained to image_width by renderer
|
84
92
|
image_obj, image_info = ::File.open(image_path, 'rb') {|fd| doc.build_image_object fd }
|
93
|
+
unless image_w
|
94
|
+
fragment[:image_width] = (image_w = to_pt image_info.width, :px).to_s
|
95
|
+
image_w *= image_scale if image_scale
|
96
|
+
image_w = available_w if image_w > available_w
|
97
|
+
end
|
85
98
|
if (image_h = image_w * (image_info.height.fdiv image_info.width)) > max_image_h
|
86
99
|
# NOTE: the best we can do is make the image fit within full height of bounds
|
87
100
|
image_w = (image_h = max_image_h) * (image_info.width.fdiv image_info.height)
|
@@ -117,7 +130,7 @@ module Asciidoctor::PDF::FormattedText
|
|
117
130
|
fragment[:image_width] = fragment[:width] = image_w
|
118
131
|
fragment[:image_height] = image_h
|
119
132
|
rescue
|
120
|
-
logger.warn %(could not embed image: #{image_path}; #{$!.message}#{
|
133
|
+
logger.warn %(could not embed image: #{image_path}; #{$!.message}#{(doc.recommend_prawn_gmagick? $!, image_format) ? %(; install prawn-gmagick gem to add support for #{image_format&.upcase || 'unknown'} image format) : ''}) unless scratch
|
121
134
|
drop = true # delegate to cleanup logic in ensure block
|
122
135
|
ensure
|
123
136
|
# NOTE: skip rendering image in scratch document or if image can't be loaded
|
@@ -69,7 +69,7 @@ module Asciidoctor
|
|
69
69
|
# NOTE: base theme is loaded "as is" (no post-processing)
|
70
70
|
def self.load_base_theme
|
71
71
|
::File.open BaseThemePath, mode: 'r:UTF-8' do |io|
|
72
|
-
(::OpenStruct.new ::YAML.safe_load io,
|
72
|
+
(::OpenStruct.new ::YAML.safe_load io, filename: BaseThemePath).tap {|theme| theme.__dir__ = ThemesDir }
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asciidoctor-pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Allen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-06-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: asciidoctor
|
@@ -261,7 +261,9 @@ files:
|
|
261
261
|
- lib/asciidoctor/pdf/ext/prawn-gmagick.rb
|
262
262
|
- lib/asciidoctor/pdf/ext/prawn-svg.rb
|
263
263
|
- lib/asciidoctor/pdf/ext/prawn-svg/calculators/document_sizing.rb
|
264
|
+
- lib/asciidoctor/pdf/ext/prawn-svg/elements/image.rb
|
264
265
|
- lib/asciidoctor/pdf/ext/prawn-svg/loaders/data.rb
|
266
|
+
- lib/asciidoctor/pdf/ext/prawn-svg/loaders/file.rb
|
265
267
|
- lib/asciidoctor/pdf/ext/prawn-svg/loaders/web.rb
|
266
268
|
- lib/asciidoctor/pdf/ext/prawn-svg/url_loader.rb
|
267
269
|
- lib/asciidoctor/pdf/ext/prawn-table.rb
|
@@ -284,6 +286,7 @@ files:
|
|
284
286
|
- lib/asciidoctor/pdf/ext/rouge.rb
|
285
287
|
- lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb
|
286
288
|
- lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb
|
289
|
+
- lib/asciidoctor/pdf/formatted_string.rb
|
287
290
|
- lib/asciidoctor/pdf/formatted_text.rb
|
288
291
|
- lib/asciidoctor/pdf/formatted_text/formatter.rb
|
289
292
|
- lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb
|