foliokit 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 @@
1
+ module Foliokit
2
+ module Overlay
3
+ class OverlayContainer < OverlayBase
4
+ element_base
5
+ def tagname
6
+ "div"
7
+ end
8
+ end
9
+ end
10
+ 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,9 @@
1
+ module Foliokit
2
+ class Slide
3
+ attr_reader :articles
4
+
5
+ def initialize(articles = [])
6
+ @articles = articles
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Foliokit
2
+ class State
3
+ include Modules::Element
4
+ element_reader :id, hash: true
5
+ end
6
+ 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