vivlio-starter-pdf 1.1.2 → 1.2.0
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/CHANGELOG.md +50 -0
- data/README.md +32 -28
- data/RELEASE_NOTE.md +39 -2
- data/exe/vivlio-starter-pdf +89 -31
- data/lib/vivlio_starter/cli/pdf/enhanced_provider.rb +10 -85
- data/lib/vivlio_starter/cli/pdf/outline_writer.rb +36 -23
- data/lib/vivlio_starter/cli/pdf/reader.rb +856 -232
- data/lib/vivlio_starter/cli/pdf/version.rb +1 -1
- data/vivlio-starter-pdf.gemspec +10 -17
- metadata +51 -17
- data/.ruby-version +0 -1
- data/Rakefile +0 -74
|
@@ -17,7 +17,14 @@ module VivlioStarter
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Writes the given +items+ into the document outline.
|
|
20
|
-
#
|
|
20
|
+
#
|
|
21
|
+
# An entry with +:top+ (the y coordinate of the heading, in PDF units) jumps to the
|
|
22
|
+
# heading itself instead of the top of its page, so readers do not have to look for
|
|
23
|
+
# a section that sits near the bottom of the page. Left and zoom stay +nil+ so the
|
|
24
|
+
# reader keeps their own horizontal position and magnification. Entries without
|
|
25
|
+
# +:top+ (chapters, whose opener shows art above the heading) still show the whole page.
|
|
26
|
+
#
|
|
27
|
+
# @param items [Array<Hash>] entries with :level, :text, :page, and optionally :top
|
|
21
28
|
# @return [Integer] number of inserted outline nodes
|
|
22
29
|
def write(items)
|
|
23
30
|
return 0 if @page_count.zero?
|
|
@@ -31,7 +38,7 @@ module VivlioStarter
|
|
|
31
38
|
|
|
32
39
|
normalized.each do |entry|
|
|
33
40
|
parent = parent_for(entry[:level], stack)
|
|
34
|
-
destination = resolve_destination(entry[:page])
|
|
41
|
+
destination = resolve_destination(entry[:page], entry[:top])
|
|
35
42
|
unless destination
|
|
36
43
|
warn_skip("page #{entry[:page]} is outside 1-#{@page_count}")
|
|
37
44
|
next
|
|
@@ -70,23 +77,22 @@ module VivlioStarter
|
|
|
70
77
|
level = 1 if level < 1
|
|
71
78
|
level = @max_level if level > @max_level
|
|
72
79
|
|
|
73
|
-
{ text:, page:, level: }
|
|
80
|
+
{ text:, page:, level:, top: entry_top(raw) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The heading position, or +nil+ to show the whole page. Anything that is not a
|
|
84
|
+
# finite number falls back to the page rather than dropping the entry: a bookmark
|
|
85
|
+
# that lands on the right page is still useful.
|
|
86
|
+
def entry_top(raw)
|
|
87
|
+
top = fetch(raw, :top, nil)
|
|
88
|
+
top.is_a?(Numeric) && top.to_f.finite? ? top.to_f : nil
|
|
74
89
|
end
|
|
75
90
|
|
|
76
91
|
# Returns the trimmed title, or +nil+ when the entry must be skipped.
|
|
77
92
|
#
|
|
78
|
-
#
|
|
79
|
-
# +
|
|
80
|
-
#
|
|
81
|
-
# mojibake heading would leave the book with no bookmarks at all. Every other
|
|
82
|
-
# kind of bad entry is skipped one by one through +on_skip+ — this one must
|
|
83
|
-
# behave the same way, and the message has to name the entry so the author can
|
|
84
|
-
# fix the manuscript heading that produced it.
|
|
85
|
-
#
|
|
86
|
-
# Both +EncodingError+ and +ArgumentError+ are caught on purpose: which one
|
|
87
|
-
# Ruby raises for broken bytes depends on the method (+strip+ raises
|
|
88
|
-
# +Encoding::CompatibilityError+, +match?+ raises +ArgumentError+), and that
|
|
89
|
-
# detail must not decide whether the book keeps its bookmarks.
|
|
93
|
+
# Invalid UTF-8 makes +strip+ raise +Encoding::CompatibilityError+ and +match?+ raise
|
|
94
|
+
# +ArgumentError+. Both are caught so that the entry is skipped through +on_skip+ and
|
|
95
|
+
# the rest of the outline is still written.
|
|
90
96
|
def entry_title(raw)
|
|
91
97
|
fetch(raw, :text, fetch(raw, :title, nil)).to_s.strip
|
|
92
98
|
rescue EncodingError, ArgumentError
|
|
@@ -97,11 +103,8 @@ module VivlioStarter
|
|
|
97
103
|
|
|
98
104
|
# Fetches a value from the entry by symbol or string key.
|
|
99
105
|
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
# a Symbol (+Integer#[]+ is bit access), which would abort the whole outline —
|
|
103
|
-
# exactly what the per-entry skip exists to prevent. Duck typing is kept so a
|
|
104
|
-
# Hash-like object still works; only the indexing failure falls back.
|
|
106
|
+
# A String or Integer in the array answers +respond_to?(:[])+ but raises +TypeError+
|
|
107
|
+
# when indexed with a Symbol, so that failure returns +fallback+.
|
|
105
108
|
def fetch(raw, key, fallback)
|
|
106
109
|
value = (raw[key] || raw[key.to_s] if raw.respond_to?(:[]))
|
|
107
110
|
value.nil? ? fallback : value
|
|
@@ -118,16 +121,26 @@ module VivlioStarter
|
|
|
118
121
|
stack.last[:node]
|
|
119
122
|
end
|
|
120
123
|
|
|
121
|
-
# Resolves the destination
|
|
122
|
-
|
|
124
|
+
# Resolves the destination for an entry: the heading position when +top+ lies on
|
|
125
|
+
# the page, otherwise the whole page.
|
|
126
|
+
def resolve_destination(page_number, top = nil)
|
|
123
127
|
index = page_number.to_i - 1
|
|
124
128
|
return nil unless index.between?(0, @page_count - 1)
|
|
125
129
|
|
|
126
|
-
@doc.pages[index]
|
|
130
|
+
page = @doc.pages[index]
|
|
131
|
+
return page unless top && on_page?(page, top)
|
|
132
|
+
|
|
133
|
+
{ type: :xyz, page:, top: }
|
|
127
134
|
rescue StandardError
|
|
128
135
|
nil
|
|
129
136
|
end
|
|
130
137
|
|
|
138
|
+
# A +top+ outside the media box would scroll past the page, so it is not used.
|
|
139
|
+
def on_page?(page, top)
|
|
140
|
+
box = page.box(:media)
|
|
141
|
+
top.between?(box.bottom, box.top)
|
|
142
|
+
end
|
|
143
|
+
|
|
131
144
|
# Recreates an empty outline root before inserting new nodes.
|
|
132
145
|
def rebuild_outline_root
|
|
133
146
|
@doc.catalog.delete(:Outlines)
|