foliokit 1.4.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 +7 -0
- data/README.md +17 -0
- data/assets/foliokit.css +2 -0
- data/assets/foliokit.js +18 -0
- data/bin/foliokit +4 -0
- data/lib/foliokit.rb +39 -0
- data/lib/foliokit/article.rb +47 -0
- data/lib/foliokit/asset_rendition.rb +54 -0
- data/lib/foliokit/base.rb +7 -0
- data/lib/foliokit/command.rb +81 -0
- data/lib/foliokit/content_stack.rb +57 -0
- data/lib/foliokit/event.rb +88 -0
- data/lib/foliokit/exporter.rb +106 -0
- data/lib/foliokit/issue.rb +92 -0
- data/lib/foliokit/manifest.rb +72 -0
- data/lib/foliokit/manifest_article.rb +55 -0
- data/lib/foliokit/manifest_folio.rb +9 -0
- data/lib/foliokit/modules/core.rb +9 -0
- data/lib/foliokit/modules/element.rb +99 -0
- data/lib/foliokit/overlay.rb +20 -0
- data/lib/foliokit/overlay/overlay360.rb +50 -0
- data/lib/foliokit/overlay/overlay_audio.rb +29 -0
- data/lib/foliokit/overlay/overlay_base.rb +108 -0
- data/lib/foliokit/overlay/overlay_button.rb +24 -0
- data/lib/foliokit/overlay/overlay_container.rb +10 -0
- data/lib/foliokit/overlay/overlay_hyperlink.rb +87 -0
- data/lib/foliokit/overlay/overlay_image.rb +18 -0
- data/lib/foliokit/overlay/overlay_imagepan.rb +36 -0
- data/lib/foliokit/overlay/overlay_video.rb +42 -0
- data/lib/foliokit/overlay/overlay_webview.rb +57 -0
- data/lib/foliokit/package.rb +123 -0
- data/lib/foliokit/slide.rb +9 -0
- data/lib/foliokit/state.rb +6 -0
- data/lib/foliokit/types/bounds.rb +47 -0
- data/lib/foliokit/types/source.rb +9 -0
- data/lib/foliokit/version.rb +3 -0
- metadata +269 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Foliokit
|
2
|
+
module Overlay
|
3
|
+
class OverlayButton < OverlayBase
|
4
|
+
element_base
|
5
|
+
element_reader :source, selector: "data > overlayAsset", text: true
|
6
|
+
|
7
|
+
def tagname
|
8
|
+
"a"
|
9
|
+
end
|
10
|
+
|
11
|
+
def export(document, options = {})
|
12
|
+
node = super(document, options)
|
13
|
+
if source.present?
|
14
|
+
src = package.apply_asset(node, "source", source)
|
15
|
+
node["style"] ||= ""
|
16
|
+
node["style"] = [node["style"], "background-image: url(#{src})"].join(";")
|
17
|
+
end
|
18
|
+
node["swipable"] = true
|
19
|
+
node["touchable"] = true
|
20
|
+
node
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Foliokit
|
2
|
+
module Overlay
|
3
|
+
class OverlayHyperlink < OverlayBase
|
4
|
+
element_base
|
5
|
+
element_reader :url, selector: "data > url", text: true
|
6
|
+
element_reader :source, selector: "data > upStateURL", text: true
|
7
|
+
element_reader :active_source, selector: "data > downStateURL", text: true
|
8
|
+
element_reader :req_nav_confirm, type: "bool", selector: "data > reqNavConfirm", text: true
|
9
|
+
element_reader :open_in_app, type: "bool", selector: "data > openInApp", text: true
|
10
|
+
|
11
|
+
def tagname
|
12
|
+
"a"
|
13
|
+
end
|
14
|
+
|
15
|
+
def export(document, options = {})
|
16
|
+
node = super(document, options)
|
17
|
+
href = parse_url(node)
|
18
|
+
return nil unless href
|
19
|
+
|
20
|
+
node["href"] = href
|
21
|
+
node["confirm"] = req_nav_confirm
|
22
|
+
node["external"] = !open_in_app
|
23
|
+
node["swipable"] = true
|
24
|
+
node["touchable"] = true
|
25
|
+
|
26
|
+
if source.present?
|
27
|
+
img = Nokogiri::XML::Node.new("img", document)
|
28
|
+
package.apply_asset(img, "src", source)
|
29
|
+
img["class"] = "source"
|
30
|
+
node << img
|
31
|
+
end
|
32
|
+
|
33
|
+
if active_source.present?
|
34
|
+
img = Nokogiri::XML::Node.new("img", document)
|
35
|
+
package.apply_asset(img, "src", active_source)
|
36
|
+
img["class"] = "active-source"
|
37
|
+
node << img
|
38
|
+
end
|
39
|
+
|
40
|
+
node
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_url(node)
|
44
|
+
# history navitation
|
45
|
+
return url if url =~ %r{^goto://}
|
46
|
+
|
47
|
+
# url navigation
|
48
|
+
if url =~ %r{^http://.+} || url =~ %r{^https://.+}
|
49
|
+
uri = Addressable::URI.parse(url)
|
50
|
+
if open_in_app
|
51
|
+
params = URI.decode_www_form(uri.normalized_query || "").to_h
|
52
|
+
params["referrer"] = "Baker"
|
53
|
+
uri.query = URI.encode_www_form(params)
|
54
|
+
return uri.to_s
|
55
|
+
else
|
56
|
+
node["target"] = "_blank"
|
57
|
+
return url
|
58
|
+
end
|
59
|
+
elsif url =~ /^mailto:/
|
60
|
+
return url
|
61
|
+
end
|
62
|
+
|
63
|
+
# page / section navitation
|
64
|
+
if (matches = url.match(%r{^navto://relative/current#([0-9.]+)?}))
|
65
|
+
page_index = matches[1].to_i
|
66
|
+
return "#/#{page_index}/0"
|
67
|
+
elsif (matches = url.match(%r{^navto://tmp#?([0-9.]+)?}))
|
68
|
+
page_index = matches[1].to_i
|
69
|
+
return "#/#{page_index}/0"
|
70
|
+
elsif (matches = url.match(%r{^navto://([^#]+)#?([0-9.]+)?}))
|
71
|
+
page_id = matches[1]
|
72
|
+
page_index = @package.root_package.manifest.content_stack_ids.index(page_id)
|
73
|
+
return false if page_index.nil?
|
74
|
+
|
75
|
+
section_index = matches[2].to_i || 0
|
76
|
+
current_page_index = @package.root_package.manifest.content_stack_ids.index(@package.id)
|
77
|
+
current_section_index = @section_index
|
78
|
+
return false if page_index == current_page_index and section_index == current_section_index
|
79
|
+
|
80
|
+
return "#/#{page_index}/#{section_index}"
|
81
|
+
end
|
82
|
+
|
83
|
+
false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Foliokit
|
2
|
+
module Overlay
|
3
|
+
class OverlayImage < OverlayBase
|
4
|
+
element_base
|
5
|
+
element_reader :source, selector: "data > overlayAsset", text: true
|
6
|
+
|
7
|
+
def tagname
|
8
|
+
"img"
|
9
|
+
end
|
10
|
+
|
11
|
+
def export(document, options = {})
|
12
|
+
node = super(document, options)
|
13
|
+
package.apply_asset(node, "src", source, "div", self.bounds.width)
|
14
|
+
node
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Foliokit
|
2
|
+
module Overlay
|
3
|
+
class OverlayImagepan < OverlayBase
|
4
|
+
element_base
|
5
|
+
element_reader :source, selector: "data > overlayAsset", text: true
|
6
|
+
element_reader :source_width, type: :i, selector: "data > overlayAsset", attribute: "width"
|
7
|
+
element_reader :source_height, type: :i, selector: "data > overlayAsset", attribute: "height"
|
8
|
+
element_reader :initial_viewport, type: Types::Bounds, selector: ->(options) { "data > initialViewport > #{package.manifest.orientation}Bounds > rectangle" }
|
9
|
+
|
10
|
+
def tagname
|
11
|
+
"div"
|
12
|
+
end
|
13
|
+
|
14
|
+
def export(document, options = {})
|
15
|
+
node = super(document, options)
|
16
|
+
if source.present?
|
17
|
+
src = package.apply_asset(node, "source", source)
|
18
|
+
node["style"] ||= ""
|
19
|
+
node["style"] = [node["style"], "background-image: url(#{src})"].join(";")
|
20
|
+
end
|
21
|
+
|
22
|
+
if initial_viewport.present?
|
23
|
+
zoom = source_width.to_f / initial_viewport.width.to_f
|
24
|
+
width = bounds.width * zoom
|
25
|
+
node["style"] ||= ""
|
26
|
+
node["style"] = [node["style"], "background-size: #{width}px"].join(";")
|
27
|
+
node["source-width"] = source_width
|
28
|
+
node["source-height"] = source_height
|
29
|
+
end
|
30
|
+
|
31
|
+
node["touchable"] = true
|
32
|
+
node
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Foliokit
|
2
|
+
module Overlay
|
3
|
+
class OverlayVideo < OverlayBase
|
4
|
+
element_base
|
5
|
+
element_reader :videoUrl, selector: "data > videoUrl", text: true
|
6
|
+
element_reader :playInContext, type: "bool", selector: "data > playInContext", text: true
|
7
|
+
element_reader :showControlsByDefault, type: "bool", selector: "data > showControlsByDefault", text: true
|
8
|
+
element_reader :allowPause, type: "bool", selector: "data > allowPause", text: true
|
9
|
+
element_reader :hideWhenFinishedPlaying, type: "bool", selector: "data > hideWhenFinishedPlaying", text: true
|
10
|
+
element_reader :autoStart, type: "bool", selector: "data > autoStart", text: true
|
11
|
+
element_reader :autoStartDelay, type: "f", selector: "data > autoStartDelay", text: true
|
12
|
+
|
13
|
+
def tagname
|
14
|
+
"div"
|
15
|
+
end
|
16
|
+
|
17
|
+
def export(document, options = {})
|
18
|
+
node = super(document, options)
|
19
|
+
|
20
|
+
# process youtube videos
|
21
|
+
uri = Addressable::URI.parse(video_url)
|
22
|
+
if uri.absolute? and uri.host =~ /(youtube)/
|
23
|
+
node["touchable"] = true
|
24
|
+
node["data-type"] = "youtube"
|
25
|
+
node["data-src"] = uri.to_s
|
26
|
+
node["class"] = "overlay-webview"
|
27
|
+
return node
|
28
|
+
end
|
29
|
+
|
30
|
+
node["src"] = package.store_asset(video_url)
|
31
|
+
node["auto-start"] = auto_start
|
32
|
+
node["loop"] = hide_when_finished_playing
|
33
|
+
node["fullscreen"] = play_in_context ? false : true
|
34
|
+
node["playsinline"] = "playsinline" if play_in_context
|
35
|
+
node["controls"] = true if show_controls_by_default
|
36
|
+
node["swipable"] = true
|
37
|
+
node["touchable"] = true
|
38
|
+
node
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Foliokit
|
2
|
+
module Overlay
|
3
|
+
class OverlayWebview < OverlayBase
|
4
|
+
element_base
|
5
|
+
element_reader :web_view_url, selector: "data > webViewUrl", text: true
|
6
|
+
|
7
|
+
def tagname
|
8
|
+
"div"
|
9
|
+
end
|
10
|
+
|
11
|
+
def export(document, options = {})
|
12
|
+
node = super(document, options)
|
13
|
+
node["touchable"] = true
|
14
|
+
node["data-type"] = "iframe"
|
15
|
+
uri = Addressable::URI.parse(web_view_url)
|
16
|
+
|
17
|
+
# Handle simple iframe embeds
|
18
|
+
if uri.relative?
|
19
|
+
file = package.dir.file(uri.to_s)
|
20
|
+
return nil unless file.exists?
|
21
|
+
|
22
|
+
elements = Nokogiri::HTML(file.read).css("body > *")
|
23
|
+
if elements.count == 1 and elements.first.name == "iframe"
|
24
|
+
uri = Addressable::URI.parse(elements.first["src"])
|
25
|
+
uri.query = "referrer=WebView"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Process embed type
|
30
|
+
if uri.absolute?
|
31
|
+
if uri.host =~ /(youtube)/
|
32
|
+
node["data-type"] = "youtube"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
file = package.dir.file(uri.to_s)
|
36
|
+
return nil unless file.exists?
|
37
|
+
|
38
|
+
html_dir_path = file.dir.relative_path(package.dir)
|
39
|
+
html_dir_hash = Digest::MD5.hexdigest(html_dir_path)
|
40
|
+
html_dir = package.root.dir("html").dir(html_dir_hash)
|
41
|
+
unless html_dir.exists?
|
42
|
+
puts "-- creating html dir"
|
43
|
+
file.dir.copy(html_dir)
|
44
|
+
end
|
45
|
+
html_file = html_dir.file(file.name)
|
46
|
+
uri = Addressable::URI.parse(html_file.relative_path(package.root))
|
47
|
+
uri.query = "referrer=WebView"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Set source
|
51
|
+
node["data-src"] = uri.to_s
|
52
|
+
|
53
|
+
node
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Foliokit
|
2
|
+
class Package
|
3
|
+
attr_reader :file, :dir, :manifest, :root
|
4
|
+
attr_accessor :assets, :root_package
|
5
|
+
|
6
|
+
def initialize(file, dir, root, root_package = nil)
|
7
|
+
@file = file
|
8
|
+
@dir = dir
|
9
|
+
@root = root
|
10
|
+
@root_package = root_package
|
11
|
+
@dir.delete if @dir.exists? and !@dir.file("Folio.xml").exists?
|
12
|
+
file.extract(@dir) unless @dir.exists?
|
13
|
+
unwrap_folder! if @dir.files.none?
|
14
|
+
wrap_article! if root_package.nil? and @dir.file("Article.xml").exists?
|
15
|
+
if @dir.file("Folio.xml").exists?
|
16
|
+
@manifest = Foliokit::ManifestFolio.new(@dir.file("Folio.xml"), self)
|
17
|
+
elsif @dir.file("Article.xml").exists?
|
18
|
+
@manifest = Foliokit::ManifestArticle.new(@dir.file("Article.xml"), self)
|
19
|
+
else
|
20
|
+
raise "invalid folio format"
|
21
|
+
end
|
22
|
+
@assets = {}
|
23
|
+
@checksums = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def id
|
27
|
+
manifest.id
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_stacks
|
31
|
+
manifest.content_stacks
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid_overlay_id?(id)
|
35
|
+
manifest.valid_overlay_id?(id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def subfolios(root_package)
|
39
|
+
manifest.subfolios.map do |element|
|
40
|
+
Foliokit::Package.new(dir.file(element[:subfolio]), dir.dir(element[:id]), root, root_package)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def apply_asset(node, attribute, path, fallback_tag = nil, target_width = nil)
|
45
|
+
src = store_asset(path, target_width)
|
46
|
+
if src.nil?
|
47
|
+
node.name = fallback_tag if fallback_tag.present?
|
48
|
+
else
|
49
|
+
node[attribute] = src
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def store_asset(path, target_width = nil, bounds = nil)
|
54
|
+
parts = path.split("#")
|
55
|
+
source = parts[0]
|
56
|
+
index = (parts[1]&.to_i or 1) - 1
|
57
|
+
source_file = dir.file(source)
|
58
|
+
source_file.align_encoding! unless source_file.exists?
|
59
|
+
return "#" unless source_file.exists?
|
60
|
+
|
61
|
+
hash = Digest::MD5.hexdigest("#{dir.name}/#{source}/#{index}/#{bounds}")
|
62
|
+
extension = source_file.extension
|
63
|
+
asset_file = root.file("assets/#{hash}.#{extension}")
|
64
|
+
asset_file.dir.create unless asset_file.dir.exists?
|
65
|
+
checksum = "#{Digest::SHA256.file(source_file.to_s)}/#{index}/#{bounds}"
|
66
|
+
return @checksums[checksum] if @checksums.key?(checksum)
|
67
|
+
|
68
|
+
# generate image from pdf or copy file
|
69
|
+
if source_file.mimetype == "application/pdf"
|
70
|
+
# Convert PDF images
|
71
|
+
doc = Poppler::Document.new(source_file.to_s)
|
72
|
+
page = doc[index]
|
73
|
+
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, *page.size)
|
74
|
+
cr = Cairo::Context.new(surface)
|
75
|
+
cr.render_poppler_page(page)
|
76
|
+
asset_file = root.file("assets/#{hash}.png")
|
77
|
+
cr.target.write_to_png(asset_file.to_s)
|
78
|
+
cr.target.finish
|
79
|
+
else
|
80
|
+
source_file.copy(asset_file)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Crop to bounds
|
84
|
+
asset_file.crop(bounds.to_h) unless bounds.nil?
|
85
|
+
|
86
|
+
# Optimize image
|
87
|
+
asset_file.optimize!
|
88
|
+
|
89
|
+
# Cache and return result
|
90
|
+
@checksums[checksum] = asset_file.relative_path(root)
|
91
|
+
@checksums[checksum]
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def wrap_article!
|
97
|
+
article = Nokogiri::XML(dir.file("Article.xml").read).css("article").first
|
98
|
+
dir.clean
|
99
|
+
file.copy(dir.file(file.name))
|
100
|
+
xml = Nokogiri::XML::Builder.new do |b|
|
101
|
+
b.folio(version: article["version"], lastUpdated: article["lastUpdated"], date: article["lastUpdated"], orientation: article["orientation"], bindingDirection: "left", id: file.basename, targetViewer: article["targetViewer"], article: "true") do
|
102
|
+
b.metadata do
|
103
|
+
b.description file.basename.titleize
|
104
|
+
b.magazineTitle file.basename.titleize
|
105
|
+
b.folioNumber file.basename.titleize
|
106
|
+
end
|
107
|
+
b.targetDimensions do
|
108
|
+
b.targetDimension(wideDimension: [article["width"].to_i, article["height"].to_i].max, narrowDimension: [article["width"].to_i, article["height"].to_i].min)
|
109
|
+
end
|
110
|
+
b.contentStacks do
|
111
|
+
b.contentStack(id: file.basename, subfolio: file.name)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
dir.file("Folio.xml").write(xml.doc.to_s)
|
116
|
+
end
|
117
|
+
|
118
|
+
def unwrap_folder!
|
119
|
+
dirs = dir.directories.reject { |d| (/^_/ =~ d.name) }
|
120
|
+
@dir = dirs.first if dirs.count == 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Foliokit
|
2
|
+
module Types
|
3
|
+
class Bounds
|
4
|
+
attr_reader :x, :y, :width, :height
|
5
|
+
|
6
|
+
def self.convert(target, name, options = {})
|
7
|
+
self.new(target[:x].to_i, target[:y].to_i, target[:width].to_i, target[:height].to_i)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(x, y, width, height)
|
11
|
+
@x = x
|
12
|
+
@y = y
|
13
|
+
@width = width
|
14
|
+
@height = height
|
15
|
+
end
|
16
|
+
|
17
|
+
def translate(x = 0, y = 0)
|
18
|
+
@x += x
|
19
|
+
@y += y
|
20
|
+
end
|
21
|
+
|
22
|
+
def top
|
23
|
+
@y
|
24
|
+
end
|
25
|
+
|
26
|
+
def left
|
27
|
+
@x
|
28
|
+
end
|
29
|
+
|
30
|
+
def bottom
|
31
|
+
@y + @height
|
32
|
+
end
|
33
|
+
|
34
|
+
def right
|
35
|
+
@x + @width
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"#{x}-#{y}-#{width}-#{height}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_h
|
43
|
+
{ x: x, y: y, width: width, height: height }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|