bookbinder 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/lib/bookbinder.rb +6 -5
  2. data/lib/bookbinder/file.rb +1 -2
  3. data/lib/bookbinder/file_system/zip_file.rb +4 -4
  4. data/lib/bookbinder/operations.rb +7 -1
  5. data/lib/bookbinder/package.rb +25 -27
  6. data/lib/bookbinder/package/openbook.rb +2 -3
  7. data/lib/bookbinder/scratch.rb +36 -0
  8. data/lib/bookbinder/transform/{openbook/json.rb → book_json.rb} +1 -1
  9. data/lib/bookbinder/version.rb +1 -1
  10. metadata +12 -63
  11. data/lib/bookbinder/package/epub.rb +0 -64
  12. data/lib/bookbinder/package/media_ripper.rb +0 -47
  13. data/lib/bookbinder/package/mp3_audiobook.rb +0 -43
  14. data/lib/bookbinder/transform/epub/audio_overlay.rb +0 -227
  15. data/lib/bookbinder/transform/epub/audio_soundtrack.rb +0 -73
  16. data/lib/bookbinder/transform/epub/contributor.rb +0 -11
  17. data/lib/bookbinder/transform/epub/cover_image.rb +0 -80
  18. data/lib/bookbinder/transform/epub/cover_page.rb +0 -148
  19. data/lib/bookbinder/transform/epub/creator.rb +0 -67
  20. data/lib/bookbinder/transform/epub/description.rb +0 -43
  21. data/lib/bookbinder/transform/epub/language.rb +0 -29
  22. data/lib/bookbinder/transform/epub/metadata.rb +0 -140
  23. data/lib/bookbinder/transform/epub/nav.rb +0 -60
  24. data/lib/bookbinder/transform/epub/nav_toc.rb +0 -177
  25. data/lib/bookbinder/transform/epub/ncx.rb +0 -63
  26. data/lib/bookbinder/transform/epub/ocf.rb +0 -33
  27. data/lib/bookbinder/transform/epub/opf.rb +0 -22
  28. data/lib/bookbinder/transform/epub/package_identifier.rb +0 -87
  29. data/lib/bookbinder/transform/epub/rendition.rb +0 -273
  30. data/lib/bookbinder/transform/epub/resources.rb +0 -38
  31. data/lib/bookbinder/transform/epub/spine.rb +0 -79
  32. data/lib/bookbinder/transform/epub/title.rb +0 -92
  33. data/lib/bookbinder/transform/epub/version.rb +0 -39
  34. data/lib/bookbinder/transform/media_ripper/cover_image.rb +0 -12
  35. data/lib/bookbinder/transform/media_ripper/eisbn.rb +0 -15
  36. data/lib/bookbinder/transform/media_ripper/metadata.rb +0 -27
  37. data/lib/bookbinder/transform/media_ripper/nav_toc.rb +0 -57
  38. data/lib/bookbinder/transform/media_ripper/publisher.rb +0 -15
  39. data/lib/bookbinder/transform/media_ripper/rendition.rb +0 -7
  40. data/lib/bookbinder/transform/media_ripper/spine.rb +0 -34
  41. data/lib/bookbinder/transform/media_ripper/title.rb +0 -16
  42. data/lib/bookbinder/transform/mp3_audiobook/cover_image.rb +0 -25
  43. data/lib/bookbinder/transform/mp3_audiobook/creator.rb +0 -27
  44. data/lib/bookbinder/transform/mp3_audiobook/description.rb +0 -18
  45. data/lib/bookbinder/transform/mp3_audiobook/metadata.rb +0 -20
  46. data/lib/bookbinder/transform/mp3_audiobook/nav_toc.rb +0 -71
  47. data/lib/bookbinder/transform/mp3_audiobook/publisher.rb +0 -18
  48. data/lib/bookbinder/transform/mp3_audiobook/rendition.rb +0 -7
  49. data/lib/bookbinder/transform/mp3_audiobook/spine.rb +0 -33
  50. data/lib/bookbinder/transform/mp3_audiobook/subject.rb +0 -18
  51. data/lib/bookbinder/transform/mp3_audiobook/title.rb +0 -18
@@ -1,177 +0,0 @@
1
- class Bookbinder::Transform::EPUB_NavToc < Bookbinder::Transform
2
-
3
- def dependencies
4
- [Bookbinder::Transform::EPUB_Nav, Bookbinder::Transform::EPUB_NCX]
5
- end
6
-
7
-
8
- def to_map(package)
9
- toc = []
10
- package.if_file(:nav) { |nav_file|
11
- nav_doc = nav_file.document('r')
12
- base_path = content_rooted_path(package.content_root, nav_file.path)
13
- nav_doc.each('nav[epub|type="toc"] > ol > li') { |li|
14
- toc.push(extract_nav_chapter(nav_doc, li, base_path))
15
- }
16
- }
17
- unless toc.any?
18
- package.if_file(:ncx) { |ncx_file|
19
- ncx_doc = ncx_file.document
20
- base_path = content_rooted_path(package.content_root, ncx_file.path)
21
- ncx_doc.each('ncx|navMap > ncx|navPoint') { |point|
22
- toc.push(extract_ncx_chapter(ncx_doc, point, base_path))
23
- }
24
- }
25
- end
26
- if toc.any?
27
- package.map['nav'] ||= {}
28
- package.map['nav']['toc'] = toc
29
- end
30
- end
31
-
32
-
33
- def from_map(package)
34
- package.if_file(:ncx) { |ncx_file|
35
- @max_depth = 0
36
- @chapter_count = 0
37
- toc = (package.map['nav'] || {})['toc'] || []
38
- build_ncx_map(ncx_file.document, toc)
39
- build_ncx_depth_meta(ncx_file.document, @max_depth)
40
- }
41
- package.if_file(:nav) { |nav_file|
42
- toc = (package.map['nav'] || {})['toc'] || []
43
- build_nav_tag(nav_file.document, toc)
44
- }
45
- end
46
-
47
-
48
- protected
49
-
50
- # NAV to_map
51
-
52
- def extract_nav_chapter(doc, li, base_path)
53
- {}.tap { |chp|
54
- content_node = doc.find_within(li, '> a, span')
55
- chp['title'] = content_node['title'] || content_node.content.strip
56
- if content_node.node_name.downcase == 'a'
57
- chp['path'] = resolved_path(base_path, content_node['href'])
58
- end
59
- children = doc.search_within(li, '> li')
60
- if children.any?
61
- chp['contents'] = children.collect { |child|
62
- extract_nav_chapter(doc, child, base_path)
63
- }
64
- end
65
- }
66
- end
67
-
68
-
69
- # NCX to_map
70
-
71
- def extract_ncx_chapter(doc, point, base_path)
72
- {
73
- 'title' => doc.find_within(point, 'text').text,
74
- 'path' => resolved_path(
75
- base_path,
76
- doc.find_within(point, 'content')['src']
77
- )
78
- }.tap { |out|
79
- children = doc.search_within(point, '> navPoint')
80
- if children.any?
81
- out['contents'] = children.collect { |pt|
82
- extract_ncx_chapter(doc, pt, base_path)
83
- }
84
- end
85
- }
86
- end
87
-
88
-
89
- # NAV from_map
90
-
91
- def build_nav_tag(nav_doc, toc)
92
- nav_tag_frag = Nokogiri::XML::DocumentFragment.parse('')
93
- Nokogiri::XML::Builder.with(nav_tag_frag) { |x|
94
- x.nav('epub:type' => 'toc') { build_nav_ol_for_chapters(x, toc) }
95
- }
96
- nav_doc.find('body').add_child(nav_tag_frag)
97
- end
98
-
99
-
100
- def build_nav_ol_for_chapters(x, chapters)
101
- x.ol {
102
- chapters.each { |chp|
103
- x.li {
104
- atts = { 'href' => chp['path'] }
105
- x.a(chp['title'], atts)
106
- build_nav_ol_for_chapters(x, chp['contents']) if chp['contents']
107
- }
108
- }
109
- }
110
- end
111
-
112
-
113
- # NCX from_map
114
-
115
- def build_ncx_map(ncx_doc, toc)
116
- nav_map_frag = Nokogiri::XML::DocumentFragment.parse('')
117
- Nokogiri::XML::Builder.with(nav_map_frag) { |x|
118
- x.navMap {
119
- toc.each { |chp| build_ncx_point_for_chapter(x, chp, 0) }
120
- }
121
- }
122
- ncx_doc.root.add_child(nav_map_frag)
123
- end
124
-
125
-
126
- def build_ncx_depth_meta(ncx_doc, max_depth)
127
- ncx_doc.new_node('meta', :append => 'ncx|head') { |depth_meta_tag|
128
- depth_meta_tag['name'] = 'dtb:depth'
129
- depth_meta_tag['content'] = max_depth
130
- }
131
- end
132
-
133
-
134
- def build_ncx_point_for_chapter(x, chp, depth)
135
- @chapter_count += 1
136
- depth += 1
137
- @max_depth = depth if depth > @max_depth
138
- x.navPoint(
139
- 'id' => "toc-#{@chapter_count}",
140
- 'class' => 'chapter',
141
- 'playOrder' => @chapter_count
142
- ) {
143
- x.navLabel { x.text_(chp['title']) }
144
- x.content('src' => chp['path'])
145
- if chp['children']
146
- chp['children'].each { |chd|
147
- build_ncx_point_for_chapter(x, chd, depth)
148
- }
149
- end
150
- }
151
- end
152
-
153
-
154
- # Utilities
155
- def content_rooted_path(content_root, path)
156
- File.dirname(path).sub(/^#{content_root}\/?/, '').sub(/^\.$/, '')
157
- end
158
-
159
-
160
- def resolved_path(base_path, path)
161
- relative_path = File.join(base_path, path).sub(/^\//, '')
162
- relative_parts = relative_path.split('/')
163
- resolved_parts = []
164
- while relative_parts.any?
165
- part = relative_parts.shift
166
- if part == '.'
167
- # do nothing
168
- elsif part == '..'
169
- resolved_parts.pop
170
- else
171
- resolved_parts.push(part)
172
- end
173
- end
174
- File.join(resolved_parts)
175
- end
176
-
177
- end
@@ -1,63 +0,0 @@
1
- class Bookbinder::Transform::EPUB_NCX < Bookbinder::Transform
2
-
3
- DEFAULT_FILE_NAME = 'book.ncx'
4
-
5
-
6
- def dependencies
7
- [Bookbinder::Transform::EPUB_Resources]
8
- end
9
-
10
-
11
- def to_map(package)
12
- opf_doc = package.file(:opf).document('r')
13
- spine = opf_doc.find('opf|spine')
14
- if spine && ncx_id = spine['toc']
15
- ncx_item = opf_doc.find('opf|manifest > opf|item[id="'+ncx_id+'"]')
16
- ncx_path = package.make_path(ncx_item['href'])
17
- package.file_aliases[:ncx] = ncx_path
18
- package.map['resources'].delete_if { |rsrc|
19
- package.file_path(rsrc['path']) == ncx_path
20
- }
21
- end
22
- end
23
-
24
-
25
- def from_map(package)
26
- if package.options['ncx_file'] != false
27
- stub_ncx(package)
28
- add_to_opf_manifest(package)
29
- end
30
- end
31
-
32
-
33
- protected
34
-
35
- def stub_ncx(package)
36
- package.file_aliases[:ncx] = DEFAULT_FILE_NAME
37
- package.file(:ncx).new_xml_document { |doc, x|
38
- x.ncx('version' => '2005-1', 'xml:lang' => 'en') {
39
- doc.add_node_namespace(x.parent, 'ncx', true)
40
- x.head {
41
- x.meta(
42
- 'name' => 'dtb:generator',
43
- 'content' => "Bookbinder #{Bookbinder::VERSION}"
44
- )
45
- }
46
- }
47
- }
48
- end
49
-
50
-
51
- def add_to_opf_manifest(package)
52
- opf_file = package.file(:opf)
53
- ncx_file = package.file(:ncx)
54
- ncx_id = package.make_id(DEFAULT_FILE_NAME)
55
- opf_file.document.new_node('item', :append => 'opf|manifest') { |item_tag|
56
- item_tag['href'] = package.make_href(DEFAULT_FILE_NAME)
57
- item_tag['media-type'] = ncx_file.media_type
58
- item_tag['id'] = ncx_id
59
- }
60
- opf_file.document.find('opf|spine')['toc'] = ncx_id
61
- end
62
-
63
- end
@@ -1,33 +0,0 @@
1
- class Bookbinder::Transform::EPUB_OCF < Bookbinder::Transform
2
-
3
- OCF_PATH = 'META-INF/container.xml'
4
-
5
- # No dependencies! Something has to have no dependencies, after all.
6
-
7
- def to_map(package)
8
- package.file_aliases[:ocf] = OCF_PATH
9
- ocf = package.file(:ocf, false)
10
- opf_path = ocf.document('r').find('ocf|rootfile')['full-path']
11
- package.file_aliases[:opf] = File.basename(opf_path)
12
- package.content_root = File.dirname(opf_path).sub(/^\./, '')
13
- end
14
-
15
-
16
- def from_map(package)
17
- package.file_aliases[:ocf] = OCF_PATH
18
- package.file_aliases[:opf] = 'book.opf'
19
- opf_file = package.file(:opf)
20
- package.file(:ocf, false).new_xml_document { |doc, x|
21
- x.container('version' => '1.0') {
22
- doc.add_node_namespace(x.parent, 'ocf', true)
23
- x.rootfiles {
24
- x.rootfile(
25
- 'full-path' => opf_file.path,
26
- 'media-type' => opf_file.media_type
27
- )
28
- }
29
- }
30
- }
31
- end
32
-
33
- end
@@ -1,22 +0,0 @@
1
- class Bookbinder::Transform::EPUB_OPF < Bookbinder::Transform
2
-
3
- def dependencies
4
- [Bookbinder::Transform::EPUB_OCF]
5
- end
6
-
7
-
8
- def from_map(package)
9
- package.file(:opf).new_xml_document { |opf_doc, x|
10
- x.package('version' => '3.0') {
11
- opf_doc.add_node_namespace(x.parent, 'opf', true)
12
- x.metadata {
13
- opf_doc.add_node_namespace(x.parent, 'dc')
14
- opf_doc.add_node_namespace(x.parent, 'dcterms')
15
- }
16
- x.manifest
17
- x.spine
18
- }
19
- }
20
- end
21
-
22
- end
@@ -1,87 +0,0 @@
1
- # See: http://www.idpf.org/epub/30/spec/epub30-publications.html#sec-opf-metadata-identifiers-pid
2
- #
3
- # "To redress this problem of identifying minor modifications and releases
4
- # without changing the Unique Identifier, this specification defines the
5
- # semantics for a Package Identifier, or means of distinguishing and
6
- # sequentially ordering Publications with the same Unique Identifier. The
7
- # Package Identifier is not an actual property in the package metadata section,
8
- # but is a value that can be obtained from two required pieces of metadata: the
9
- # Unique Identifier and the last modification date of the Publication.
10
- #
11
- # "When the taken together, the combined value represents a unique identity that
12
- # can be used to distinguish any particular version of an EPUB Manifestation
13
- # from another. To ensure that a Package Identifier can be constructed, the
14
- # Publication must include exactly one `modified` property containing
15
- # the last modification date (see meta)."
16
- #
17
- class Bookbinder::Transform::EPUB_PackageIdentifier < Bookbinder::Transform
18
-
19
- def dependencies
20
- [Bookbinder::Transform::EPUB_Metadata, Bookbinder::Transform::EPUB_NCX]
21
- end
22
-
23
-
24
- def to_map(package)
25
- opf_doc = package.file(:opf).document('r')
26
-
27
- # Unique identifier
28
- unless id_hashes = package.map['metadata']['identifier']
29
- package.warn('No identifiers found in package metadata')
30
- end
31
- if id_hashes && uid_id = opf_doc.root['unique-identifier']
32
- uid_hash = id_hashes.detect { |id_hash|
33
- id_hash['id'] && id_hash['id']['@'] == uid_id
34
- }
35
- else
36
- package.warn('OPF root "unique-identifier" attribute not found')
37
- end
38
- if uid_hash
39
- id_hashes.delete(uid_hash)
40
- package.map['metadata'].delete('identifier') if id_hashes.empty?
41
- package.map['unique-identifier'] = uid_hash['@']
42
- else
43
- package.warn('OPF metadata for required "unique-identifier" not found')
44
- end
45
-
46
- # Modified
47
- begin
48
- mod_hash = package.map['metadata'].delete('dcterms:modified').first
49
- timestamp = Time.parse(mod_hash['@'])
50
- rescue
51
- package.warn('OPF metadata for "modified" not found - using now()')
52
- timestamp = Time.now
53
- end
54
- package.map['modified'] = timestamp.utc.iso8601
55
- end
56
-
57
-
58
- def from_map(package)
59
- map_uid = package.map['unique-identifier']
60
- map_mod = package.map['modified']
61
- opf_doc = package.file(:opf).document
62
- metadata_tag = opf_doc.find('opf|metadata')
63
-
64
- # Unique identifier
65
- opf_doc.root['unique-identifier'] = 'unique-identifier'
66
- opf_doc.new_node('dc:identifier', :append => metadata_tag) { |uid_tag|
67
- uid_tag['id'] = 'unique-identifier'
68
- uid_tag.content = map_uid
69
- }
70
-
71
- # Add the unique identifier to the NCX as well, because too
72
- # much data redundancy is never enough.
73
- package.if_file(:ncx) { |ncx_file|
74
- ncx_file.document.new_node('meta', :append => 'ncx|head') { |uid_meta_tag|
75
- uid_meta_tag['name'] = 'dtb:uid'
76
- uid_meta_tag['content'] = map_uid
77
- }
78
- }
79
-
80
- # Modified
81
- opf_doc.new_node('meta', :append => metadata_tag) { |mod_tag|
82
- mod_tag['property'] = 'dcterms:modified'
83
- mod_tag.content = map_mod
84
- }
85
- end
86
-
87
- end
@@ -1,273 +0,0 @@
1
- # For the original Apple extension to EPUB 2, see any version of the iBooks
2
- # Asset Guide prior to version 5. Here's a link to version 4.7:
3
- #
4
- # http://www.mobileread.com/forums/attachment.php?attachmentid=74234
5
- #
6
- # In particular, see the section titled "Configuring Display Options"
7
- #
8
- # For the EPUB 3 Fixed Layout specification (actually a Mixed Layout
9
- # specification), see:
10
- #
11
- # http://www.idpf.org/epub/301/spec/epub-publications.html#sec-package-metadata-fxl
12
- #
13
- # NOTE: a potential gotcha. The EPUB3 properties are 'rendition:...',
14
- # with a colon, whereas the Openbook form follows the dash convention
15
- # for property names, ie 'rendition-...'.
16
- #
17
- class Bookbinder::Transform::EPUB_Rendition < Bookbinder::Transform
18
-
19
- RENDITION_DEFAULT_PROPERTIES = {
20
- 'rendition:flow' => 'paginated',
21
- 'rendition:layout' => 'reflowable',
22
- 'rendition:spread' => 'auto',
23
- 'rendition:orientation' => 'auto',
24
- 'rendition:viewport' => nil
25
- }
26
- APPLE_FXL_PATH = 'META-INF/com.apple.ibooks.display-options.xml'
27
-
28
-
29
- def dependencies
30
- [Bookbinder::Transform::EPUB_Spine, Bookbinder::Transform::EPUB_Metadata]
31
- end
32
-
33
-
34
- def to_map(package)
35
- package.map['rendition-format'] = 'ebook'
36
- book_properties = book_properties_from_apple_display_options(package)
37
- book_properties.update(book_properties_from_opf(package))
38
- component_rendition_properties_from_opf(package, book_properties)
39
- end
40
-
41
-
42
- def from_map(package)
43
- first_cmpt = package.map['spine'][0]
44
- book_properties = {
45
- 'rendition:flow' => first_cmpt['rendition-flow'],
46
- 'rendition:layout' => first_cmpt['rendition-layout'],
47
- 'rendition:spread' => first_cmpt['rendition-spread'],
48
- 'rendition:orientation' => first_cmpt['rendition-orientation'],
49
- 'rendition:viewport' => first_cmpt['rendition-viewport']
50
- }
51
- opf_doc = package.file(:opf).document
52
- opf_doc.add_prefix('rendition')
53
- book_properties.each_pair { |prop, content|
54
- if content && RENDITION_DEFAULT_PROPERTIES[prop] != content
55
- book_meta_tag(opf_doc, prop, content)
56
- end
57
- }
58
- opf_doc.each('opf|spine > opf|itemref') { |itemref|
59
- # Update the itemref properties attribute.
60
- cmpt = component_for_itemref(package, itemref)
61
- props = (itemref['properties'] || '').split
62
- book_properties.each_pair { |prop, book_content|
63
- key = prop.gsub(':', '-')
64
- if cmpt[key] != book_content
65
- cmpt_content = cmpt[key] || RENDITION_DEFAULT_PROPERTIES[prop]
66
- props.push("#{prop}-#{cmpt_content}")
67
- end
68
- }
69
- if ['left', 'right'].include?(cmpt['rendition-position'])
70
- props.push('page-spread-'+cmpt['rendition-position'])
71
- elsif cmpt['rendition-position'] == 'center'
72
- props.push('rendition:page-spread-center')
73
- end
74
- if cmpt['rendition-align-x-center']
75
- props.push('rendition:align-x-center')
76
- end
77
- itemref['properties'] = props.join(' ') if props.any?
78
-
79
- # Update the viewport/viewBox attribute in the component file.
80
- add_icb_to_component(
81
- package.file(cmpt['path']),
82
- cmpt['rendition-viewport']
83
- ) if cmpt['rendition-viewport']
84
- }
85
- end
86
-
87
-
88
- protected
89
-
90
- # Inspects Apple's display-options file in META-INF for some
91
- # option values that we can convert to EPUB3 equivalents.
92
- # Returns a hash with zero or more rendition:* properties.
93
- #
94
- def book_properties_from_apple_display_options(package)
95
- {}.tap { |book_properties|
96
- package.if_file(APPLE_FXL_PATH, false) { |apple_file|
97
- apple_options = apple_file.document('r').search(
98
- 'display_options > platform[name="*"] > option'
99
- )
100
- apple_hash = apple_options.inject({}) { |acc, apple_option|
101
- acc.update(apple_option['name'] => apple_option.content.strip)
102
- }
103
-
104
- if apple_hash['fixed-layout'] == 'true'
105
- book_properties['rendition-layout'] = 'pre-paginated'
106
- end
107
-
108
- if apple_hash['orientation-lock'] == 'landscape-only'
109
- book_properties['rendition-orientation'] = 'landscape'
110
- elsif apple_hash['orientation-lock'] == 'portrait-only'
111
- book_properties['rendition-orientation'] = 'portrait'
112
- end
113
-
114
- # FIXME: not really a 1-to-1 mapping - should we ignore this option?
115
- # UPDATE: yes, disabled for now.
116
- # if apple_hash['open-to-spread'] == 'true'
117
- # book_properties['rendition-spread'] = 'both'
118
- # end
119
- }
120
- }
121
- end
122
-
123
-
124
- # Looks for book rendition properties in the OPF <metadata>
125
- # tag, returning a hash with zero or more such properties.
126
- #
127
- # Book-level rendition properties are:
128
- #
129
- # <meta property="rendition:layout">reflowable</meta>
130
- # <meta property="rendition:spread">auto</meta>
131
- # <meta property="rendition:orientation">landscape</meta>
132
- #
133
- def book_properties_from_opf(package)
134
- {}.tap { |book_properties|
135
- spent = []
136
- package.map['metadata'].each_pair { |key, value|
137
- if RENDITION_DEFAULT_PROPERTIES.keys.include?(key)
138
- book_properties.update(key.sub(':', '-') => value.first['@'])
139
- spent.push(key)
140
- end
141
- }
142
- spent.each { |key| package.map['metadata'].delete(key) }
143
- if vpt_str = book_properties['rendition-viewport']
144
- if vpt = parse_viewport_string(vpt_str)
145
- book_properties['rendition-viewport'] = vpt
146
- else
147
- package.warn("Viewport string unparseable: #{vpt_str}")
148
- end
149
- end
150
- }
151
- end
152
-
153
-
154
- # Iterates over each spine component looking for component-specific
155
- # rendition properties, which are merged over the top of the default
156
- # book-level properties, then applied directly to the map's component.
157
- #
158
- def component_rendition_properties_from_opf(package, book_properties)
159
- opf_doc = package.file(:opf).document('r')
160
- opf_doc.each('opf|spine opf|itemref') { |itemref|
161
- cmpt = component_for_itemref(package, itemref)
162
- cmpt_properties = book_properties.clone
163
- (itemref['properties'] || '').split.each { |itemref_prop|
164
- if match = itemref_prop.match(/^rendition:flow-(.+)/)
165
- cmpt_properties['rendition-flow'] = match[1]
166
- end
167
- if match = itemref_prop.match(/^rendition:layout-(.+)/)
168
- cmpt_properties['rendition-layout'] = match[1]
169
- end
170
- if match = itemref_prop.match(/^rendition:spread-(.+)/)
171
- cmpt_properties['rendition-spread'] = match[1]
172
- end
173
- if match = itemref_prop.match(/^rendition:orientation-(.+)/)
174
- cmpt_properties['rendition-spread'] = match[1]
175
- end
176
- if match = itemref_prop.match(/^(rendition:)?page-spread-(\w+)/)
177
- cmpt_properties['rendition-position'] = match[2]
178
- end
179
- if match = itemref_prop.match(/^rendition:align-x-center$/)
180
- cmpt_properties['rendition-align-x-center'] = true
181
- end
182
- }
183
-
184
- # The rendition:viewport should ONLY be set on the component if
185
- # it is pre-paginated, and we should prefer the values in the
186
- # HTML file itself over anything we find in the OPF.
187
- book_vpt = cmpt_properties.delete('rendition-viewport')
188
- if cmpt_properties['rendition-layout'] == 'pre-paginated'
189
- cmpt_vpt = retrieve_icb_from_component(package.file(cmpt['path']))
190
- if cmpt_vpt || book_vpt
191
- cmpt_properties['rendition-viewport'] = cmpt_vpt || book_vpt
192
- else
193
- package.warn("Pre-paginated: viewport not found: #{cmpt['path']}")
194
- puts("Expected viewport, found nothing")
195
- cmpt_properties.delete('rendition-layout')
196
- end
197
- end
198
- cmpt.update(cmpt_properties)
199
- }
200
- end
201
-
202
-
203
- # Given an itemref tag, finds the map's component hash that
204
- # corresponds to it. (Look-up is performed using href, via
205
- # the manifest item tag.)
206
- #
207
- def component_for_itemref(package, itemref)
208
- package.map['spine'].detect { |cmpt|
209
- cmpt['id'] == itemref['idref']
210
- }
211
- end
212
-
213
-
214
- # Finds the "initial containing block" (in EPUB3 parlance) for
215
- # the given component document. This is either the SVG viewBox
216
- # attribute, or the HTML <meta name="viewport"> content attribute.
217
- #
218
- def retrieve_icb_from_component(cmpt_file)
219
- cmpt_doc = cmpt_file.document('r')
220
- if cmpt_file.media_type.match(/svg/)
221
- icb = cmpt_doc.root['viewBox'].split
222
- { 'width' => icb[0], 'height' => icb[1] }
223
- elsif viewport_meta = cmpt_doc.find('meta[name="viewport"]')
224
- parse_viewport_string(viewport_meta['content'])
225
- end
226
- end
227
-
228
-
229
- def parse_viewport_string(icb)
230
- {
231
- 'width' => icb.match(/width\s*=\s*(\d+)/)[1].to_i,
232
- 'height' => icb.match(/height\s*=\s*(\d+)/)[1].to_i
233
- }
234
- rescue
235
- # package.warn("Viewport string unparseable: #{icb}")
236
- nil
237
- end
238
-
239
-
240
- # Creates a book-level meta tag for the given rendition:* property,
241
- # inserting it into the OPF document.
242
- #
243
- def book_meta_tag(opf_doc, property, content)
244
- opf_doc.new_node('meta', :append => 'opf|metadata') { |meta_tag|
245
- meta_tag['property'] = property
246
- meta_tag.content = content
247
- }
248
- end
249
-
250
-
251
- # Updates the viewBox or meta-viewport declaration in the
252
- # component document (SVG or HTML respectively), creating
253
- # tags/attributes as necessary.
254
- #
255
- def add_icb_to_component(cmpt_file, icb)
256
- cmpt_doc = cmpt_file.document
257
- if cmpt_file.media_type.match(/svg/)
258
- cmpt_doc.root['viewBox'] = icb.join(' ')
259
- elsif viewport_meta = cmpt_doc.find('meta[name="viewport"]')
260
- vpc = viewport_meta['content']
261
- vpc.gsub!(/width\s*=\s*\d+/, "width=#{icb['width']}")
262
- vpc.gsub!(/height\s*=\s*\d+/, "height=#{icb['height']}")
263
- viewport_meta['content'] = vpc
264
- else
265
- cmpt_doc.new_node('meta', :append => 'head') { |viewport_meta_tag|
266
- viewport_meta_tag['name'] = 'viewport'
267
- viewport_meta_tag['content'] =
268
- "width=#{icb['width']},height=#{icb['height']}"
269
- }
270
- end
271
- end
272
-
273
- end