foliokit 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ module Foliokit
2
+ class Issue
3
+ attr_reader :dir, :pages, :slides, :title, :description, :width, :height, :package
4
+
5
+ def initialize(file, dir, from: nil, to: nil)
6
+ @dir = dir
7
+
8
+ # Convert article to folio
9
+ @package = Foliokit::Package.new(file, dir, dir)
10
+ @title = package.manifest.title
11
+ @description = package.manifest.description
12
+ @width = package.manifest.device_width
13
+ @height = package.manifest.device_height
14
+ @from = from
15
+ @to = to
16
+ @pages = []
17
+
18
+ # Add Sub-Folio Content Stacks
19
+ if @package.manifest.article
20
+ convert_article!
21
+ else
22
+ if package.subfolios(package).any?
23
+ convert_folio!
24
+ else
25
+ convert_sub_folio!
26
+ end
27
+ end
28
+ end
29
+
30
+ def store_assets(assets_dir)
31
+ assets_dir.delete
32
+ dir.dir("assets").copy(assets_dir)
33
+ end
34
+
35
+ def export
36
+ document = Nokogiri::HTML("<html><head><title>#{title}</title><body></body></html>")
37
+ body = document.at("body")
38
+ pages.each do |page|
39
+ body << page.export(document)
40
+ end
41
+ document
42
+ end
43
+
44
+ def to_html
45
+ export.to_html
46
+ end
47
+
48
+ private
49
+
50
+ def convert_article!
51
+ manifest = package.subfolios(package).first.manifest
52
+ articles = manifest.content_stacks.first.articles[from..to]
53
+ if manifest.layout == "vertical"
54
+ @slides = [Slide.new(articles)]
55
+ else
56
+ @slides = articles.map do |article|
57
+ Slide.new([article])
58
+ end
59
+ end
60
+ end
61
+
62
+ def convert_folio!
63
+ subpackages = package.subfolios(package)[from..to]
64
+ @slides = subpackages.map do |subpackage|
65
+ slide = Slide.new
66
+ subpackage.manifest.content_stacks.each do |content_stack|
67
+ slide.articles.push(*content_stack.articles)
68
+ end
69
+ slide
70
+ end
71
+ end
72
+
73
+ def convert_sub_folio!
74
+ package.root_package = package
75
+ manifest = package.manifest
76
+ articles = manifest.content_stacks.first.articles[from..to]
77
+ @slides = [Slide.new(articles)]
78
+ end
79
+
80
+ def from
81
+ @from || 0
82
+ end
83
+
84
+ def to
85
+ if package.manifest.article
86
+ @to || (package.subfolios(package).first.manifest.content_stacks.first.articles.count - 1)
87
+ else
88
+ @to || (package.subfolios(package).count - 1)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,72 @@
1
+ require 'foliokit/modules/element'
2
+
3
+ module Foliokit
4
+ class Manifest
5
+ include Modules::Element
6
+
7
+ def self.element_base
8
+ element_reader :id, type: :s
9
+ element_reader :version, type: :s
10
+ element_reader :targetViewer, type: :s
11
+ element_reader :lastUpdated, type: :s
12
+ element_reader :date, type: :s
13
+ element_reader :bindingDirection, type: :s
14
+ element_reader :orientation, type: :s
15
+ element_reader :title, type: :s, selector: "metadata > magazineTitle", text: true
16
+ element_reader :description, type: :s, selector: "metadata > description", text: true
17
+ element_reader :article, type: :bool
18
+ end
19
+
20
+ def target_dimension
21
+ element.css("targetDimensions targetDimension").first
22
+ end
23
+
24
+ def wide_dimension
25
+ target_dimension["wideDimension"].to_i
26
+ end
27
+
28
+ def narrow_dimension
29
+ target_dimension["narrowDimension"].to_i
30
+ end
31
+
32
+ def device_height
33
+ (orientation == "landscape") ? narrow_dimension : wide_dimension
34
+ end
35
+
36
+ def device_width
37
+ (orientation == "landscape") ? wide_dimension : narrow_dimension
38
+ end
39
+
40
+ def referenced_ids
41
+ @_referenced_ids ||= begin
42
+ element.css("action[@target]").map { |action| hashify(action["target"]) } | element.css("action[@state]").map { |action| hashify(action["state"]) }
43
+ end
44
+ end
45
+
46
+ def valid_overlay_id?(id)
47
+ overlay_ids.include?(id)
48
+ end
49
+
50
+ def valid_referenced_id?(id)
51
+ referenced_ids.include?(id)
52
+ end
53
+
54
+ def overlay_ids
55
+ @_overlay_ids ||= begin
56
+ element.css("overlay[@id]").map { |overlay| hashify(overlay["id"]) }
57
+ end
58
+ end
59
+
60
+ def content_stack_ids
61
+ element.css("contentStacks contentStack").map { |s| s["id"] }
62
+ end
63
+
64
+ def content_stacks
65
+ element.css("contentStacks contentStack:not(:empty)").map { |element| ContentStack.new(element, package) }
66
+ end
67
+
68
+ def subfolios
69
+ element.css("contentStacks contentStack[@subfolio]:empty")
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,55 @@
1
+ module Foliokit
2
+ class ManifestArticle < Manifest
3
+ element_base
4
+ element_reader :layout, type: :s
5
+
6
+ def initialize(file, package)
7
+ article = Nokogiri::XML(file.read).css("article").first
8
+ # article["smoothScrolling"] = true
9
+ article.css("content page rendition").each do |rendition|
10
+ rendition.name = "assetRendition"
11
+ rendition["source"] = rendition.remove_attribute("href")
12
+ end
13
+ article.css("content page").each do |page|
14
+ page.name = "asset"
15
+ end
16
+
17
+ # build manifest
18
+ root_manifest = package.root_package.manifest
19
+ xml = Nokogiri::XML::Builder.new do |b|
20
+ b.folio(id: file.dir.name, version: root_manifest.version, targetViewer: root_manifest.target_viewer, lastUpdated: root_manifest.last_updated, date: root_manifest.date, bindingDirection: root_manifest.binding_direction, orientation: root_manifest.orientation, layout: article["layout"]) do
21
+ b.metadata do
22
+ b.description root_manifest.description
23
+ b.magazineTitle root_manifest.title
24
+ b.folioNumber root_manifest.title
25
+ end
26
+ b.targetDimensions do
27
+ b.targetDimension(wideDimension: root_manifest.wide_dimension, narrowDimension: root_manifest.narrow_dimension)
28
+ end
29
+ b.contentStacks do
30
+ b.contentStack(id: file.dir.name, version: article["version"], lastUpdated: article["lastUpdated"], smoothScrolling: article["smoothScrolling"], layout: article["layout"], alwaysDisplayOverlays: article["alwaysDisplayOverlays"], orientation: article["orientation"]) do
31
+ b.content do
32
+ b.regions do
33
+ b.region do
34
+ b.bounds do
35
+ b.rectangle(x: 0, y: 0, width: article[:width], height: article[:height])
36
+ end
37
+ end
38
+ end
39
+ b.assets do |assets_node|
40
+ article.css("content asset").each do |asset|
41
+ b.parent << asset
42
+ end
43
+ end
44
+ if file.dir.file("Overlays.xml").exists?
45
+ b.parent << Nokogiri::XML(file.dir.file("Overlays.xml").read).css("overlays").first
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ super(xml.doc.root, package)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ module Foliokit
2
+ class ManifestFolio < Manifest
3
+ element_base
4
+
5
+ def initialize(file, package)
6
+ super(Nokogiri::XML(file.read).root, package)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Foliokit
2
+ module Modules
3
+ module Core
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ module Foliokit
2
+ module Modules
3
+ module Element
4
+ extend ActiveSupport::Concern
5
+
6
+ included do |cls|
7
+ class << cls
8
+ attr_accessor :element_reader_attributes
9
+ end
10
+
11
+ def self.element_reader(name, options = {})
12
+ attr_accessor name.to_s.underscore
13
+ self.element_reader_attributes ||= {}
14
+ self.element_reader_attributes[name] = options
15
+ end
16
+
17
+ def hashify(value)
18
+ hashable = "#{package.id}/#{value}"
19
+ "id_#{Digest::MD5.hexdigest(hashable)}"
20
+ end
21
+
22
+ def self.element_collection(name, selector, klass = nil)
23
+ define_method :"#{name}" do
24
+ (element > selector).map do |el|
25
+ if klass and klass.class == Module
26
+ klass.factory(el, package)
27
+ elsif klass
28
+ klass.new(el, @package)
29
+ else
30
+ el
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.element_xpath(name, selector, klass)
37
+ define_method :"#{name}" do
38
+ el = element.xpath(selector).first
39
+ if klass.class == Module
40
+ klass.factory(el, package)
41
+ else
42
+ klass.new(el, @package)
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.element_xpath_collection(name, selector, klass)
48
+ define_method :"#{name}" do
49
+ element.xpath(selector).map do |el|
50
+ if klass.class == Module
51
+ klass.factory(el, package)
52
+ else
53
+ klass.new(el, @package)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ attr_reader :package
60
+
61
+ private
62
+
63
+ attr_reader :element
64
+ end
65
+
66
+ def initialize(element, package)
67
+ @element = element
68
+ @package = package
69
+ self.class.element_reader_attributes.each do |name, options|
70
+ key = name.to_s.underscore
71
+ value = nil
72
+ target = element
73
+ if options[:selector]
74
+ if options[:selector].respond_to?(:call)
75
+ target = (target > instance_exec(options, &(options[:selector]))).first
76
+ else
77
+ target = (target > options[:selector]).first
78
+ end
79
+ end
80
+ unless target.nil?
81
+ if options[:type].class == Class
82
+ value = options[:type].convert(target, name, options)
83
+ else
84
+ attribute_name = options[:attribute] || name
85
+ value = options[:text] ? target.text.strip : target[attribute_name]
86
+ unless value.nil?
87
+ value = value.send(:"to_#{options[:type]}") unless options[:type].nil?
88
+ value.gsub!(/\s/, "_") if options[:clean]
89
+ value = hashify(value) if options[:hash]
90
+ end
91
+ end
92
+ end
93
+ value = options[:default] if value.nil? and options[:default]
94
+ instance_variable_set("@#{key}", value)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,20 @@
1
+ module Foliokit
2
+ module Overlay
3
+ extend ActiveSupport::Autoload
4
+ autoload :OverlayBase
5
+ autoload :OverlayContainer
6
+ autoload :OverlayHyperlink
7
+ autoload :OverlayImage
8
+ autoload :OverlayButton
9
+ autoload :Overlay360
10
+ autoload :OverlayImagepan
11
+ autoload :OverlayAudio
12
+ autoload :OverlayVideo
13
+ autoload :OverlayWebview
14
+
15
+ def self.factory(el, package)
16
+ klass = "Foliokit::Overlay::Overlay#{el[:type].camelcase}".constantize
17
+ klass.new(el, package)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ module Foliokit
2
+ module Overlay
3
+ class Overlay360 < OverlayBase
4
+ element_base
5
+ element_reader :clockwise, type: "bool", selector: "data", attribute: "clockwise"
6
+ element_reader :loopCount, type: "i", selector: "data > loopCount", text: true
7
+ element_reader :autoStart, type: "bool", selector: "data > autoStart", text: true
8
+ element_reader :autoStartDelay, type: "f", selector: "data > autoStartDelay", text: true
9
+ element_reader :tapEnabled, type: "bool", selector: "data > tapEnabled", text: true
10
+ element_reader :swipeEnabled, type: "bool", selector: "data > swipeEnabled", text: true
11
+ element_reader :circularViewEnabled, type: "bool", selector: "data > circularViewEnabled", text: true
12
+ element_reader :autoPlayStopAtLast, type: "bool", selector: "data > autoPlayStopAtLast", text: true
13
+ element_reader :framesPerSecond, type: "f", selector: "data > framesPerSecond", text: true
14
+ element_collection :images, "data > overlayAsset"
15
+
16
+ def tagname
17
+ "div"
18
+ end
19
+
20
+ def export(document, options = {})
21
+ node = super(document, options)
22
+ node["clockwise"] = clockwise
23
+ node["auto-start"] = auto_start
24
+ node["auto-start-delay"] = auto_start_delay
25
+ node["tap-enabled"] = tap_enabled
26
+ node["swipe-enabled"] = swipe_enabled
27
+ node["circular-view-enabled"] = circular_view_enabled
28
+ node["auto-play-stop-at-last"] = auto_play_stop_at_last
29
+ node["frames-per-second"] = frames_per_second
30
+
31
+ # build container
32
+ container = Nokogiri::XML::Node.new("div", document)
33
+ container["class"] = "circlr-container"
34
+ if node["swipe-enabled"] == "true"
35
+ container["class"] += " prevent-swipe-horizontal prevent-swipe-vertical"
36
+ container["touchable"] = true
37
+ end
38
+ node << container
39
+
40
+ # build images
41
+ images.each do |image|
42
+ img = Nokogiri::XML::Node.new("img", document)
43
+ img["src"] = package.store_asset(image.text)
44
+ container << img
45
+ end
46
+ node
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ module Foliokit
2
+ module Overlay
3
+ class OverlayAudio < OverlayBase
4
+ element_base
5
+ element_reader :audio_url, selector: "data > audioUrl", text: true
6
+ element_reader :keep_alive, type: "bool", selector: "data > continuePlayingInBackground", text: true
7
+ element_reader :autoStart, type: "bool", selector: "data > autoStart", text: true
8
+ element_reader :autoStartDelay, type: "f", selector: "data > autoStartDelay", text: true
9
+
10
+ def tagname
11
+ "audio"
12
+ end
13
+
14
+ def export(document, options = {})
15
+ node = super(document, options)
16
+ if audio_url.present?
17
+ source = Nokogiri::XML::Node.new("source", document)
18
+ package.apply_asset(source, "src", audio_url)
19
+ source["type"] = "audio/mpeg"
20
+ node << source
21
+ end
22
+ node["auto-start"] = true if auto_start
23
+ node["keep-alive"] = true if keep_alive
24
+ node["data-ignore"] = true
25
+ node
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,108 @@
1
+ module Foliokit
2
+ module Overlay
3
+ class OverlayBase
4
+ include Modules::Element
5
+ attr_reader :state, :section_index
6
+
7
+ def self.element_base
8
+ element_reader :id, hash: true
9
+ element_reader :type
10
+ element_reader :lastUpdated, type: :datetime
11
+ element_reader :bounds, type: Types::Bounds, selector: ->(options) { "#{package.manifest.orientation}Bounds > rectangle" }
12
+ element_reader :stateTransitionDuration, type: :f, selector: "data > stateTransitionDuration", text: true, default: 0.5
13
+ element_reader :swipe_event, attribute: "type", selector: "data > eventHandling > event"
14
+ element_reader :swipe_handler, attribute: "handler", selector: "data > eventHandling > event"
15
+ element_collection :states, "data > states > state", State
16
+ element_collection :events, "data > bindings > onevent", Event
17
+ element_collection :overlays, "data > children > overlay", Overlay
18
+ end
19
+
20
+ def tagname
21
+ "div"
22
+ end
23
+
24
+ def initialize(element, package)
25
+ super(element, package)
26
+
27
+ # calculate bounds
28
+ bounds_target = element.css("#{package.manifest.orientation}Bounds > rectangle")
29
+ bounds_target = element if bounds_target.empty?
30
+ bounds_target = bounds_target.first if bounds_target.kind_of?(Nokogiri::XML::NodeSet)
31
+ @bounds = Types::Bounds.convert(bounds_target, "bounds")
32
+
33
+ # set initial state
34
+ @state = states.first
35
+ end
36
+
37
+ def stateful?
38
+ !!state
39
+ end
40
+
41
+ def export(document, parent: nil, section_index: nil)
42
+ @section_index = section_index
43
+ node = Nokogiri::XML::Node.new(tagname.to_s, document)
44
+ # node["id"] = id if package.manifest.valid_referenced_id?(id)
45
+ node["id"] = id
46
+ node["class"] = "overlay-#{type}" unless tagname.include? "overlay"
47
+ style = {
48
+ width: "#{bounds.width}px",
49
+ height: "#{bounds.height}px",
50
+ left: "#{bounds.x}px",
51
+ top: "#{bounds.y}px"
52
+ }
53
+ if parent&.stateful?
54
+ node["name"] = id
55
+ if state_transition_duration != 0
56
+ style[:transition] = "opacity #{state_transition_duration}s"
57
+ elsif parent.state_transition_duration != 0
58
+ style[:transition] = "opacity #{parent.state_transition_duration}s"
59
+ else
60
+ style[:transition] = "opacity 0s"
61
+ end
62
+ end
63
+ node["style"] = style.map { |k, v| "#{k}:#{v}" }.join(";")
64
+
65
+ # stateful behaviour
66
+ node["active-state"] = state.id if state
67
+
68
+ # event / action behaviour
69
+ bindings = {}
70
+ unless swipe_event.nil?
71
+ if swipe_event == "verticalSwipe"
72
+ node["class"] += " prevent-swipe-vertical"
73
+ elsif swipe_event == "horizontalSwipe"
74
+ node["class"] += " prevent-swipe-horizontal"
75
+ else
76
+ node["class"] += " prevent-swipe-horizontal prevent-swipe-vertical"
77
+ end
78
+ node["touchable"] = true
79
+ if swipe_handler == "changeState"
80
+ bindings[swipe_event] = { selector: swipe_handler.downcase }
81
+ elsif swipe_handler == "scroll"
82
+ node["scroll"] = swipe_event.gsub("Swipe", "")
83
+ end
84
+ end
85
+ if events.any?
86
+ events.each do |event|
87
+ actions = event.all_actions
88
+ if actions.any?
89
+ bindings[event.name] = event.all_actions.map(&:to_hash)
90
+ end
91
+ end
92
+ end
93
+
94
+ if bindings.any?
95
+ node["bindings"] = JSON.dump(bindings)
96
+ if bindings["click"] or bindings["horizontalSwipe"] or bindings["verticalSwipe"]
97
+ node["touchable"] = true
98
+ end
99
+ end
100
+ overlays.each do |overlay|
101
+ child_node = overlay.export(document, parent: self, section_index: section_index)
102
+ node << child_node unless child_node.nil?
103
+ end
104
+ node
105
+ end
106
+ end
107
+ end
108
+ end