foliokit 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|