asciidoctor-epub3 1.5.0.alpha.6 → 1.5.0.alpha.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +134 -0
- data/Gemfile +10 -0
- data/README.adoc +248 -172
- data/asciidoctor-epub3.gemspec +42 -0
- data/bin/adb-push-ebook +19 -10
- data/data/styles/epub3-css3-only.css +28 -11
- data/data/styles/epub3.css +40 -39
- data/lib/asciidoctor-epub3/converter.rb +188 -121
- data/lib/asciidoctor-epub3/core_ext/string.rb +1 -1
- data/lib/asciidoctor-epub3/font_icon_map.rb +1 -1
- data/lib/asciidoctor-epub3/packager.rb +240 -104
- data/lib/asciidoctor-epub3/spine_item_processor.rb +22 -11
- data/lib/asciidoctor-epub3/version.rb +1 -1
- metadata +24 -35
- data/data/samples/asciidoctor-epub3-readme.adoc +0 -849
- data/data/samples/asciidoctor-js-browser-extension.adoc +0 -46
- data/data/samples/asciidoctor-js-introduction.adoc +0 -91
- data/data/samples/i18n.adoc +0 -161
- data/data/samples/images/asciidoctor-js-chrome-extension.png +0 -0
- data/data/samples/images/avatars/graphitefriction.jpg +0 -0
- data/data/samples/images/avatars/mogztter.jpg +0 -0
- data/data/samples/images/avatars/mojavelinux.jpg +0 -0
- data/data/samples/images/correct-text-justification.png +0 -0
- data/data/samples/images/incorrect-text-justification.png +0 -0
- data/data/samples/images/screenshots/chapter-title-day.png +0 -0
- data/data/samples/images/screenshots/chapter-title.png +0 -0
- data/data/samples/images/screenshots/figure-admonition.png +0 -0
- data/data/samples/images/screenshots/section-title-paragraph.png +0 -0
- data/data/samples/images/screenshots/sidebar.png +0 -0
- data/data/samples/images/screenshots/table.png +0 -0
- data/data/samples/images/screenshots/text.png +0 -0
- data/data/samples/sample-book.adoc +0 -21
- data/data/samples/sample-content.adoc +0 -168
- data/scripts/generate-font-subsets.pe +0 -235
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('lib/asciidoctor-epub3/version', File.dirname(__FILE__))
|
3
|
+
require 'open3' unless defined? Open3
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'asciidoctor-epub3'
|
7
|
+
s.version = Asciidoctor::Epub3::VERSION
|
8
|
+
|
9
|
+
s.summary = 'Converts AsciiDoc documents to EPUB3 and KF8/MOBI (Kindle) e-book formats'
|
10
|
+
s.description = <<-EOS
|
11
|
+
An extension for Asciidoctor that converts AsciiDoc documents to EPUB3 and KF8/MOBI (Kindle) e-book archives.
|
12
|
+
EOS
|
13
|
+
|
14
|
+
s.authors = ['Dan Allen', 'Sarah White']
|
15
|
+
s.email = 'dan@opendevise.com'
|
16
|
+
s.homepage = 'https://github.com/asciidoctor/asciidoctor-epub3'
|
17
|
+
s.license = 'MIT'
|
18
|
+
|
19
|
+
s.required_ruby_version = '>= 1.9.3'
|
20
|
+
|
21
|
+
files = begin
|
22
|
+
(result = Open3.popen3('git ls-files -z') {|_, out| out.read }.split %(\0)).empty? ? Dir['**/*'] : result
|
23
|
+
rescue
|
24
|
+
Dir['**/*']
|
25
|
+
end
|
26
|
+
s.files = files.grep %r/^(?:(?:data\/(?:fonts|images|styles)|lib)\/.+|Gemfile|Rakefile|(?:CHANGELOG|LICENSE|NOTICE|README)\.adoc|#{s.name}\.gemspec)$/
|
27
|
+
s.executables = %w(asciidoctor-epub3 adb-push-ebook)
|
28
|
+
s.test_files = s.files.grep(/^(?:test|spec|feature)\/.*$/)
|
29
|
+
|
30
|
+
s.require_paths = ['lib']
|
31
|
+
|
32
|
+
s.has_rdoc = true
|
33
|
+
s.rdoc_options = ['--charset=UTF-8', '--title="Asciidoctor EPUB3"', '--main=README.adoc', '-ri']
|
34
|
+
s.extra_rdoc_files = ['CHANGELOG.adoc', 'LICENSE.adoc', 'NOTICE.adoc', 'README.adoc']
|
35
|
+
|
36
|
+
s.add_development_dependency 'rake'
|
37
|
+
#s.add_development_dependency 'rdoc', '~> 4.1.0'
|
38
|
+
|
39
|
+
s.add_runtime_dependency 'asciidoctor', '~> 1.5.0'
|
40
|
+
s.add_runtime_dependency 'gepub', '~> 0.6.9.2'
|
41
|
+
s.add_runtime_dependency 'thread_safe', '~> 0.3.6'
|
42
|
+
end
|
data/bin/adb-push-ebook
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
ADB = ENV['ADB'] || 'adb'
|
4
|
+
TARGETS = {
|
5
|
+
'.epub' => '/sdcard/',
|
6
|
+
'.mobi' => '/sdcard/Android/data/com.amazon.kindle/files/'
|
7
|
+
}
|
4
8
|
|
5
9
|
unless ::File.executable? ADB
|
6
10
|
warn %(adb-push-ebook: `adb` not found.\nPlease set the ADB environment variable or add `adb` to your PATH.)
|
@@ -10,16 +14,21 @@ end
|
|
10
14
|
require 'open3'
|
11
15
|
require 'shellwords'
|
12
16
|
|
13
|
-
|
17
|
+
payload_file = ARGV[0] || '_output/sample-book'
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
}
|
19
|
+
if (payload_file_ext = File.extname payload_file).empty?
|
20
|
+
transfers = TARGETS.map do |(ext, target_dir)|
|
21
|
+
{
|
22
|
+
src: %(#{payload_file}#{ext}),
|
23
|
+
dest: target_dir
|
24
|
+
}
|
25
|
+
end
|
26
|
+
else
|
27
|
+
transfers = [{ src: payload_file, dest: TARGETS[payload_file_ext] }]
|
28
|
+
end
|
19
29
|
|
20
|
-
|
21
|
-
|
22
|
-
Open3.popen2e(Shellwords.join [ADB, 'push', file, target]) {|input, output, wait_thr|
|
30
|
+
transfers.each do |transfer|
|
31
|
+
Open3.popen2e(Shellwords.join [ADB, 'push', transfer[:src], transfer[:dest]]) do |input, output, wait_thr|
|
23
32
|
output.each {|line| puts line }
|
24
|
-
|
25
|
-
|
33
|
+
end if File.file? transfer[:src]
|
34
|
+
end
|
@@ -1,17 +1,30 @@
|
|
1
|
-
/*
|
1
|
+
/* @page is for EPUB2 only */
|
2
|
+
@page {
|
3
|
+
margin: 0;
|
4
|
+
}
|
5
|
+
|
6
|
+
body.calibre-desktop {
|
7
|
+
padding: 20pt 0 !important;
|
8
|
+
}
|
9
|
+
|
10
|
+
body.calibre-desktop > section {
|
11
|
+
margin: 0 25pt;
|
12
|
+
}
|
13
|
+
|
14
|
+
/* Gitden & Namo default to 16px font-size; bump it to 19px (118.75%) */
|
2
15
|
body.gitden-reader,
|
3
16
|
body.namo-epub-library {
|
4
|
-
font-size:
|
17
|
+
font-size: 118.75%;
|
5
18
|
}
|
6
19
|
|
7
|
-
/* Gitden doesn't give us much margin, so let's match Kindle */
|
20
|
+
/* Gitden doesn't give us much margin, so let's roughly match Aldiko and Kindle (narrow setting) */
|
8
21
|
body.gitden-reader {
|
9
|
-
margin: 0
|
22
|
+
margin: 0 5pt !important;
|
10
23
|
}
|
11
24
|
|
12
25
|
/* Namo has the same margin problem, except setting side margins doesn't work */
|
13
26
|
/*body.namo-epub-library > section.chapter {
|
14
|
-
margin: 0
|
27
|
+
margin: 0 5pt;
|
15
28
|
}*/
|
16
29
|
|
17
30
|
/* Use tighter margins and smaller font (18px) on phones (Nexus 4 and smaller) */
|
@@ -22,15 +35,19 @@ only screen and (max-device-width: 1280px) and (max-device-height: 768px) {
|
|
22
35
|
font-size: 112.5%;
|
23
36
|
}
|
24
37
|
|
25
|
-
body.gitden-reader {
|
26
|
-
margin: 0 5pt;
|
27
|
-
}
|
38
|
+
/*body.gitden-reader {
|
39
|
+
margin: 0 5pt !important;
|
40
|
+
}*/
|
28
41
|
|
29
42
|
/*body.namo-epub-library > section.chapter {
|
30
43
|
margin: 0 5pt;
|
31
44
|
}*/
|
32
45
|
}
|
33
46
|
|
47
|
+
body.gitden-reader pre {
|
48
|
+
white-space: pre-wrap !important; /* Gitden must be applying white-space: pre !important */
|
49
|
+
}
|
50
|
+
|
34
51
|
body h1, body h2, body h3:not(.list-heading), body h4, body h5, body h6,
|
35
52
|
h1 :not(code), h2 :not(code), h3:not(.list-heading) :not(code), h4 :not(code), h5 :not(code), h6 :not(code) {
|
36
53
|
/* !important required to override custom font setting in Kindle / Gitden / Namo */
|
@@ -79,7 +96,7 @@ body code, body kbd, body pre, pre :not(code) {
|
|
79
96
|
h1.chapter-title .subtitle > b:last-child {
|
80
97
|
padding-right: 0;
|
81
98
|
}
|
82
|
-
|
99
|
+
|
83
100
|
h1.chapter-title .subtitle::after {
|
84
101
|
display: table;
|
85
102
|
content: ' ';
|
@@ -104,13 +121,13 @@ body code, body kbd, body pre, pre :not(code) {
|
|
104
121
|
.icon-1_5x {
|
105
122
|
padding: 0 0.25em;
|
106
123
|
-webkit-transform: scale(1.5, 1.5);
|
107
|
-
transform: scale(1.5, 1.5);
|
124
|
+
transform: scale(1.5, 1.5);
|
108
125
|
}
|
109
126
|
|
110
127
|
.icon-2x {
|
111
128
|
padding: 0 0.5em;
|
112
129
|
-webkit-transform: scale(2, 2);
|
113
|
-
transform: scale(2, 2);
|
130
|
+
transform: scale(2, 2);
|
114
131
|
}
|
115
132
|
|
116
133
|
.icon-small {
|
data/data/styles/epub3.css
CHANGED
@@ -25,7 +25,7 @@ html {
|
|
25
25
|
body {
|
26
26
|
padding: 0 !important;
|
27
27
|
/* add margin to ~ match Kindle's narrow setting */
|
28
|
-
/* don't use !important on margin as it
|
28
|
+
/* don't use !important on margin as it interferes with reader overrides (Calibre and Kindle) */
|
29
29
|
margin: 0;
|
30
30
|
font-size: 100%;
|
31
31
|
/* NOTE putting optimizeLegibility on the body slows down rendering considerably */
|
@@ -38,10 +38,10 @@ html body {
|
|
38
38
|
background-color: #FFFFFF;
|
39
39
|
}
|
40
40
|
|
41
|
-
/*
|
42
|
-
/* @page not supported by Kindle or GitDen */
|
41
|
+
/* @page only applies to EPUB2 readers; not supported by EPUB3 readers such as Kindle and Gitden */
|
43
42
|
@page {
|
44
|
-
/*
|
43
|
+
/* sets minimum margin permitted */
|
44
|
+
/* pushes the top & bottom margins down in Aldiko to emulate Kindle (Kindle uses ~ 10% of screen by default )*/
|
45
45
|
margin: 1cm;
|
46
46
|
}
|
47
47
|
|
@@ -895,7 +895,7 @@ blockquote footer {
|
|
895
895
|
|
896
896
|
blockquote footer .context {
|
897
897
|
font-size: 0.9em;
|
898
|
-
letter-spacing: -0.
|
898
|
+
letter-spacing: -0.05em;
|
899
899
|
color: #666665;
|
900
900
|
}
|
901
901
|
|
@@ -916,8 +916,7 @@ pre {
|
|
916
916
|
margin-top: 1em; /* 0.85rem */
|
917
917
|
/*margin-top: 1.176em;*/ /* 1rem */
|
918
918
|
white-space: pre-wrap;
|
919
|
-
|
920
|
-
word-wrap: break-word; /* break in middle of long word if no other break opportunities are available */
|
919
|
+
overflow-wrap: break-word; /* break in middle of long word if no other break opportunities are available */
|
921
920
|
font-size: 0.85em;
|
922
921
|
line-height: 1.4; /* matches what Kindle uses and can't go less */
|
923
922
|
background-color: #F2F2F2;
|
@@ -985,28 +984,28 @@ aside[class~="admonition"] {
|
|
985
984
|
}
|
986
985
|
|
987
986
|
aside.note {
|
988
|
-
border-left-color: #
|
989
|
-
background-color: #
|
987
|
+
border-left-color: #FFC14F;
|
988
|
+
background-color: #FFF0D4; /* 25% opacity of border */
|
990
989
|
}
|
991
990
|
|
992
991
|
aside.tip {
|
993
|
-
border-left-color: #
|
994
|
-
background-color: #
|
992
|
+
border-left-color: #40403E;
|
993
|
+
background-color: #D0D0CF; /* 25% opacity of border */
|
995
994
|
}
|
996
995
|
|
997
996
|
aside.caution {
|
998
|
-
border-left-color: #
|
999
|
-
background-color: #
|
997
|
+
border-left-color: #7F7F7D;
|
998
|
+
background-color: #DFDFDF; /* 25% opacity of border */
|
1000
999
|
}
|
1001
1000
|
|
1002
1001
|
aside.warning {
|
1003
1002
|
border-left-color: #C83737;
|
1004
|
-
background-color: #
|
1003
|
+
background-color: #F1CECE; /* 25% opacity of border */
|
1005
1004
|
}
|
1006
1005
|
|
1007
1006
|
aside.important {
|
1008
|
-
border-left-color: #
|
1009
|
-
background-color: #
|
1007
|
+
border-left-color: #F2642A;
|
1008
|
+
background-color: #FCD9CA; /* 25% opacity of border */
|
1010
1009
|
}
|
1011
1010
|
|
1012
1011
|
aside.admonition::before {
|
@@ -1018,7 +1017,7 @@ aside.admonition::before {
|
|
1018
1017
|
text-align: center;
|
1019
1018
|
margin-bottom: -0.25em;
|
1020
1019
|
margin-left: -0.5em;
|
1021
|
-
text-shadow: 0px 1px
|
1020
|
+
text-shadow: 0px 1px 1px rgba(102, 102, 101, 0.15);
|
1022
1021
|
}
|
1023
1022
|
|
1024
1023
|
aside.admonition > div.content {
|
@@ -1039,41 +1038,38 @@ aside[class~="admonition"] > div[class~="content"] {
|
|
1039
1038
|
}
|
1040
1039
|
|
1041
1040
|
aside.note::before {
|
1042
|
-
/*content: "\f0f4";*/ /* fa-coffee */
|
1043
1041
|
content: "\f040"; /* fa-pencil */
|
1044
|
-
color: #
|
1042
|
+
color: #FFC14F;
|
1045
1043
|
}
|
1046
1044
|
|
1047
1045
|
aside[class~="note"] > div[class~="content"] {
|
1048
|
-
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #
|
1049
|
-
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #
|
1046
|
+
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #FFC14F 45%, #FFC14F 55%, rgba(255,255,255,0) 57.5%);
|
1047
|
+
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #FFC14F 45%, #FFC14F 55%, rgba(255,255,255,0) 57.5%);
|
1050
1048
|
}
|
1051
1049
|
|
1052
1050
|
aside.tip::before {
|
1053
|
-
|
1054
|
-
|
1055
|
-
content: "\f15a"; /* fa-bitcoin */
|
1056
|
-
color: #57AD68; /* 87,173,104 */
|
1051
|
+
content: "\f0eb"; /* fa-lightbulb-o */
|
1052
|
+
color: #40403E;
|
1057
1053
|
}
|
1058
1054
|
|
1059
1055
|
aside[class~="tip"] > div[class~="content"] {
|
1060
|
-
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #
|
1061
|
-
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #
|
1056
|
+
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #40403E 45%, #40403E 55%, rgba(255,255,255,0) 57.5%);
|
1057
|
+
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #40403E 45%, #40403E 55%, rgba(255,255,255,0) 57.5%);
|
1062
1058
|
}
|
1063
1059
|
|
1064
1060
|
aside.caution::before {
|
1065
1061
|
content: "\f0c2"; /* fa-cloud */
|
1066
|
-
color: #
|
1062
|
+
color: #7F7F7D;
|
1067
1063
|
}
|
1068
1064
|
|
1069
1065
|
aside[class~="caution"] > div[class~="content"] {
|
1070
|
-
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #
|
1071
|
-
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #
|
1066
|
+
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #7F7F7D 45%, #7F7F7D 55%, rgba(255,255,255,0) 57.5%);
|
1067
|
+
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #7F7F7D 45%, #7F7F7D 55%, rgba(255,255,255,0) 57.5%);
|
1072
1068
|
}
|
1073
1069
|
|
1074
1070
|
aside.warning::before {
|
1075
1071
|
content: "\f0e7"; /* fa-bolt */
|
1076
|
-
color: #C83737;
|
1072
|
+
color: #C83737;
|
1077
1073
|
}
|
1078
1074
|
|
1079
1075
|
aside[class~="warning"] > div[class~="content"] {
|
@@ -1083,12 +1079,12 @@ aside[class~="warning"] > div[class~="content"] {
|
|
1083
1079
|
|
1084
1080
|
aside.important::before {
|
1085
1081
|
content: "\f12a"; /* fa-exclamation */
|
1086
|
-
color: #
|
1082
|
+
color: #F2642A;
|
1087
1083
|
}
|
1088
1084
|
|
1089
1085
|
aside[class~="important"] > div[class~="content"] {
|
1090
|
-
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #
|
1091
|
-
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #
|
1086
|
+
background-image: -webkit-linear-gradient(left, rgba(255,255,255,0) 42.5%, #F2642A 45%, #F2642A 55%, rgba(255,255,255,0) 57.5%);
|
1087
|
+
background-image: linear-gradient(to right, rgba(255,255,255,0) 42.5%, #F2642A 45%, #F2642A 55%, rgba(255,255,255,0) 57.5%);
|
1092
1088
|
}
|
1093
1089
|
|
1094
1090
|
aside.admonition > h2 {
|
@@ -1137,13 +1133,18 @@ table.table thead th {
|
|
1137
1133
|
border-bottom: 1px solid #80807F;
|
1138
1134
|
}
|
1139
1135
|
|
1140
|
-
table.table td > p
|
1136
|
+
table.table td > p,
|
1137
|
+
table.table div.embed > * {
|
1141
1138
|
margin-top: 0;
|
1139
|
+
}
|
1140
|
+
|
1141
|
+
table.table td > p {
|
1142
1142
|
text-align: left;
|
1143
1143
|
}
|
1144
1144
|
|
1145
1145
|
/* REVIEW */
|
1146
|
-
table.table td > p + p
|
1146
|
+
table.table td > p + p,
|
1147
|
+
table.table div.embed > * + * {
|
1147
1148
|
margin-top: 1em;
|
1148
1149
|
}
|
1149
1150
|
|
@@ -1167,7 +1168,7 @@ table.table-framed-sides {
|
|
1167
1168
|
|
1168
1169
|
table.table-grid th,
|
1169
1170
|
table.table-grid td {
|
1170
|
-
border-width: 0 1px 1px 0;
|
1171
|
+
border-width: 0 1px 1px 0;
|
1171
1172
|
border-style: solid;
|
1172
1173
|
border-color: #80807F;
|
1173
1174
|
}
|
@@ -1183,14 +1184,14 @@ table.table-grid tbody tr:last-child > td {
|
|
1183
1184
|
|
1184
1185
|
table.table-grid-rows tbody th,
|
1185
1186
|
table.table-grid-rows tbody td {
|
1186
|
-
border-width: 1px 0 0 0;
|
1187
|
+
border-width: 1px 0 0 0;
|
1187
1188
|
border-style: solid;
|
1188
1189
|
border-color: #80807F;
|
1189
1190
|
}
|
1190
1191
|
|
1191
1192
|
table.table-grid-cols th,
|
1192
1193
|
table.table-grid-cols td {
|
1193
|
-
border-width: 0 1px 0 0;
|
1194
|
+
border-width: 0 1px 0 0;
|
1194
1195
|
border-style: solid;
|
1195
1196
|
border-color: #80807F;
|
1196
1197
|
}
|
@@ -4,9 +4,6 @@ require_relative 'font_icon_map'
|
|
4
4
|
|
5
5
|
module Asciidoctor
|
6
6
|
module Epub3
|
7
|
-
# tried 8288, but it didn't work in older readers
|
8
|
-
WordJoiner = [65279].pack 'U*'
|
9
|
-
WordJoinerRx = RUBY_ENGINE_JRUBY ? /\uFEFF/ : WordJoiner
|
10
7
|
|
11
8
|
# Public: The main converter for the epub3 backend that handles packaging the
|
12
9
|
# EPUB3 or KF8 publication file.
|
@@ -25,15 +22,23 @@ class Converter
|
|
25
22
|
@extract = false
|
26
23
|
end
|
27
24
|
|
28
|
-
def convert
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
def convert node, name = nil
|
26
|
+
if (name ||= node.node_name) == 'document'
|
27
|
+
@validate = node.attr? 'ebook-validate'
|
28
|
+
@extract = node.attr? 'ebook-extract'
|
29
|
+
@compress = node.attr 'ebook-compress'
|
30
|
+
Packager.new node, (node.references[:spine_items] || [node]), node.attributes['ebook-format'].to_sym
|
31
|
+
# converting an element from the spine document, such as an inline node in the doctitle
|
32
|
+
elsif name.start_with? 'inline_'
|
33
|
+
(@content_converter ||= ::Asciidoctor::Converter::Factory.default.create('epub3-xhtml5')).convert node, name
|
34
|
+
else
|
35
|
+
raise ::ArgumentError, %(Encountered unexpected node in epub3 package converter: #{name})
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
39
|
# FIXME we have to package in write because we don't have access to target before this point
|
35
40
|
def write packager, target
|
36
|
-
packager.package validate: @validate, extract: @extract, target: target
|
41
|
+
packager.package validate: @validate, extract: @extract, compress: @compress, target: target
|
37
42
|
nil
|
38
43
|
end
|
39
44
|
end
|
@@ -45,14 +50,14 @@ class ContentConverter
|
|
45
50
|
|
46
51
|
register_for 'epub3-xhtml5'
|
47
52
|
|
48
|
-
|
49
|
-
EOL = "\n"
|
53
|
+
EOL = %(\n)
|
50
54
|
NoBreakSpace = ' '
|
51
55
|
ThinNoBreakSpace = ' '
|
52
56
|
RightAngleQuote = '›'
|
57
|
+
CalloutStartNum = %(\u2460)
|
53
58
|
|
54
59
|
XmlElementRx = /<\/?.+?>/
|
55
|
-
CharEntityRx = /&#(\d{2,
|
60
|
+
CharEntityRx = /&#(\d{2,6});/
|
56
61
|
NamedEntityRx = /&([A-Z]+);/
|
57
62
|
UppercaseTagRx = /<(\/)?([A-Z]+)>/
|
58
63
|
|
@@ -80,7 +85,7 @@ class ContentConverter
|
|
80
85
|
basebackend 'html'
|
81
86
|
outfilesuffix '.xhtml'
|
82
87
|
htmlsyntax 'xml'
|
83
|
-
@
|
88
|
+
@xrefs_seen = ::Set.new
|
84
89
|
@icon_names = []
|
85
90
|
end
|
86
91
|
|
@@ -88,36 +93,10 @@ class ContentConverter
|
|
88
93
|
if respond_to?(name ||= node.node_name)
|
89
94
|
send name, node
|
90
95
|
else
|
91
|
-
warn %(conversion missing in epub3 backend for #{name})
|
96
|
+
warn %(asciidoctor: WARNING: conversion missing in epub3 backend for #{name})
|
92
97
|
end
|
93
98
|
end
|
94
99
|
|
95
|
-
# TODO aggregate authors of spine document into authors attribute(s) on main document
|
96
|
-
def navigation_document node, spine
|
97
|
-
doctitle_sanitized = (node.doctitle sanitize: true, use_fallback: true).gsub WordJoinerRx, ''
|
98
|
-
lines = [%(<!DOCTYPE html>
|
99
|
-
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="#{lang = (node.attr 'lang', 'en')}" lang="#{lang}">
|
100
|
-
<head>
|
101
|
-
<meta charset="UTF-8"/>
|
102
|
-
<title>#{doctitle_sanitized}</title>
|
103
|
-
<link rel="stylesheet" type="text/css" href="styles/epub3.css"/>
|
104
|
-
<link rel="stylesheet" type="text/css" href="styles/epub3-css3-only.css" media="(min-device-width: 0px)"/>
|
105
|
-
</head>
|
106
|
-
<body>
|
107
|
-
<h1>#{doctitle_sanitized}</h1>
|
108
|
-
<nav epub:type="toc" id="toc">
|
109
|
-
<h2>#{node.attr 'toc-title'}</h2>
|
110
|
-
<ol>)]
|
111
|
-
spine.each do |item|
|
112
|
-
lines << %(<li><a href="#{item.id || (item.attr 'docname')}.xhtml">#{(item.doctitle sanitize: true, use_fallback: true).gsub WordJoinerRx, ''}</a></li>)
|
113
|
-
end
|
114
|
-
lines << %(</ol>
|
115
|
-
</nav>
|
116
|
-
</body>
|
117
|
-
</html>)
|
118
|
-
lines * EOL
|
119
|
-
end
|
120
|
-
|
121
100
|
def document node
|
122
101
|
docid = node.id
|
123
102
|
|
@@ -131,19 +110,22 @@ class ContentConverter
|
|
131
110
|
subtitle = doctitle.combined
|
132
111
|
end
|
133
112
|
|
134
|
-
doctitle_sanitized =
|
135
|
-
subtitle_formatted = subtitle.
|
136
|
-
# FIXME
|
113
|
+
doctitle_sanitized = doctitle.combined
|
114
|
+
subtitle_formatted = subtitle.split.map {|w| %(<b>#{w}</b>) } * ' '
|
115
|
+
# FIXME use uppercase pcdata helper to make less fragile (see logic in Asciidoctor PDF)
|
137
116
|
subtitle_formatted_upper = subtitle_formatted.upcase
|
138
117
|
.gsub(UppercaseTagRx) { %(<#{$1}#{$2.downcase}>) }
|
139
118
|
.gsub(NamedEntityRx) { %(&#{$1.downcase};) }
|
140
119
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
120
|
+
if (node.attr 'publication-type', 'book') == 'book'
|
121
|
+
byline = nil
|
122
|
+
else
|
123
|
+
author = node.attr 'author'
|
124
|
+
username = node.attr 'username', 'default'
|
125
|
+
imagesdir = (node.references[:spine].attr 'imagesdir', '.').chomp '/'
|
126
|
+
imagesdir = (imagesdir == '.' ? nil : %(#{imagesdir}/))
|
127
|
+
byline = %(<p class="byline"><img src="#{imagesdir}avatars/#{username}.jpg"/> <b class="author">#{author}</b></p>#{EOL})
|
128
|
+
end
|
147
129
|
|
148
130
|
mark_last_paragraph node
|
149
131
|
content = node.content
|
@@ -176,14 +158,12 @@ class ContentConverter
|
|
176
158
|
<link rel="stylesheet" type="text/css" href="styles/epub3.css"/>
|
177
159
|
<link rel="stylesheet" type="text/css" href="styles/epub3-css3-only.css" media="(min-device-width: 0px)"/>
|
178
160
|
#{icon_css_head}<script type="text/javascript">
|
179
|
-
document.addEventListener('DOMContentLoaded', function(event) {
|
180
|
-
|
181
|
-
|
182
|
-
if (window.parent == window || !(
|
183
|
-
return;
|
184
|
-
}
|
161
|
+
document.addEventListener('DOMContentLoaded', function(event, reader) {
|
162
|
+
if (!(reader = navigator.epubReadingSystem)) {
|
163
|
+
if (navigator.userAgent.indexOf(' calibre/') >= 0) reader = { name: 'calibre-desktop' };
|
164
|
+
else if (window.parent == window || !(reader = window.parent.navigator.epubReadingSystem)) return;
|
185
165
|
}
|
186
|
-
document.body.setAttribute('class',
|
166
|
+
document.body.setAttribute('class', reader.name.toLowerCase().replace(/ /g, '-'));
|
187
167
|
});
|
188
168
|
</script>
|
189
169
|
</head>
|
@@ -191,8 +171,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
191
171
|
<section class="chapter" title="#{doctitle_sanitized.gsub '"', '"'}" epub:type="chapter" id="#{docid}">
|
192
172
|
#{icon_css_scoped}<header>
|
193
173
|
<div class="chapter-header">
|
194
|
-
<
|
195
|
-
<h1 class="chapter-title">#{title_upper}#{subtitle ? %[ <small class="subtitle">#{subtitle_formatted_upper}</small>] : nil}</h1>
|
174
|
+
#{byline}<h1 class="chapter-title">#{title_upper}#{subtitle ? %[ <small class="subtitle">#{subtitle_formatted_upper}</small>] : nil}</h1>
|
196
175
|
</div>
|
197
176
|
</header>
|
198
177
|
#{content})]
|
@@ -219,6 +198,11 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
219
198
|
lines * EOL
|
220
199
|
end
|
221
200
|
|
201
|
+
# NOTE embedded is used for AsciiDoc table cell content
|
202
|
+
def embedded node
|
203
|
+
node.content
|
204
|
+
end
|
205
|
+
|
222
206
|
def section node
|
223
207
|
hlevel = node.level + 1
|
224
208
|
epub_type_attr = node.special ? %( epub:type="#{node.sectname}") : nil
|
@@ -248,9 +232,16 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
248
232
|
end
|
249
233
|
end
|
250
234
|
|
251
|
-
# QUESTION use convert_content?
|
252
235
|
def open node
|
253
|
-
node.
|
236
|
+
id_attr = node.id ? %( id="#{node.id}") : nil
|
237
|
+
class_attr = node.role ? %( class="#{node.role}") : nil
|
238
|
+
if id_attr || class_attr
|
239
|
+
%(<div#{id_attr}#{class_attr}>
|
240
|
+
#{convert_content node}
|
241
|
+
</div>)
|
242
|
+
else
|
243
|
+
convert_content node
|
244
|
+
end
|
254
245
|
end
|
255
246
|
|
256
247
|
def abstract node
|
@@ -263,11 +254,10 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
263
254
|
role = node.role
|
264
255
|
# stack-head is the alternative to the default, inline-head (where inline means "run-in")
|
265
256
|
head_stop = node.attr 'head-stop', (role && (node.has_role? 'stack-head') ? nil : '.')
|
257
|
+
# FIXME promote regexp to constant
|
266
258
|
head = node.title? ? %(<strong class="head">#{title = node.title}#{head_stop && title !~ /[[:punct:]]$/ ? head_stop : nil}</strong> ) : nil
|
267
259
|
if role
|
268
|
-
if node.has_role? 'signature'
|
269
|
-
node.set_option 'hardbreaks'
|
270
|
-
end
|
260
|
+
node.set_option 'hardbreaks' if node.has_role? 'signature'
|
271
261
|
%(<p class="#{role}">#{head}#{node.content}</p>)
|
272
262
|
else
|
273
263
|
%(<p>#{head}#{node.content}</p>)
|
@@ -330,15 +320,11 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
330
320
|
def listing node
|
331
321
|
figure_classes = ['listing']
|
332
322
|
figure_classes << 'coalesce' if node.option? 'unbreakable'
|
333
|
-
pre_classes =
|
334
|
-
['source', %(language-#{node.attr 'language'})]
|
335
|
-
else
|
336
|
-
['screen']
|
337
|
-
end
|
323
|
+
pre_classes = node.style == 'source' ? ['source', %(language-#{node.attr 'language'})] : ['screen']
|
338
324
|
title_div = node.title? ? %(<figcaption>#{node.captioned_title}</figcaption>
|
339
325
|
) : nil
|
340
326
|
# patches conums to fix extra or missing leading space
|
341
|
-
# TODO
|
327
|
+
# TODO remove patch once upgrading to Asciidoctor 1.5.6
|
342
328
|
%(<figure class="#{figure_classes * ' '}">
|
343
329
|
#{title_div}<pre class="#{pre_classes * ' '}"><code>#{(node.content || '').gsub(/(?<! )<i class="conum"| +<i class="conum"/, ' <i class="conum"')}</code></pre>
|
344
330
|
</figure>)
|
@@ -359,11 +345,11 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
359
345
|
|
360
346
|
def quote node
|
361
347
|
footer_content = []
|
362
|
-
if attribution =
|
363
|
-
footer_content << attribution
|
348
|
+
if (attribution = node.attr 'attribution')
|
349
|
+
footer_content << attribution
|
364
350
|
end
|
365
351
|
|
366
|
-
if citetitle =
|
352
|
+
if (citetitle = node.attr 'citetitle')
|
367
353
|
citetitle_sanitized = xml_sanitize citetitle
|
368
354
|
footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
|
369
355
|
end
|
@@ -386,11 +372,11 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
386
372
|
|
387
373
|
def verse node
|
388
374
|
footer_content = []
|
389
|
-
if attribution =
|
390
|
-
footer_content << attribution
|
375
|
+
if (attribution = node.attr 'attribution')
|
376
|
+
footer_content << attribution
|
391
377
|
end
|
392
378
|
|
393
|
-
if citetitle =
|
379
|
+
if (citetitle = node.attr 'citetitle')
|
394
380
|
citetitle_sanitized = xml_sanitize citetitle
|
395
381
|
footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
|
396
382
|
end
|
@@ -409,6 +395,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
409
395
|
title = node.title
|
410
396
|
title_sanitized = xml_sanitize title
|
411
397
|
title_attr = %( title="#{title_sanitized}")
|
398
|
+
# FIXME use uppercase pcdata helper to make less fragile (see logic in Asciidoctor PDF)
|
412
399
|
title_upper = title.upcase.gsub(NamedEntityRx) { %(&#{$1.downcase};) }
|
413
400
|
title_el = %(<h2>#{title_upper}</h2>
|
414
401
|
)
|
@@ -444,10 +431,10 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
444
431
|
end
|
445
432
|
table_class_attr = %( class="#{table_classes * ' '}")
|
446
433
|
table_styles = []
|
447
|
-
unless node.option? 'autowidth'
|
448
|
-
table_styles << %(width: #{node.attr 'tablepcwidth'}
|
434
|
+
unless (node.option? 'autowidth') && !(node.attr? 'width', nil, false)
|
435
|
+
table_styles << %(width: #{node.attr 'tablepcwidth'}%)
|
449
436
|
end
|
450
|
-
table_style_attr = table_styles.size > 0 ? %( style="#{table_styles * ' '}") : nil
|
437
|
+
table_style_attr = table_styles.size > 0 ? %( style="#{table_styles * '; '}") : nil
|
451
438
|
|
452
439
|
lines << %(<table#{table_id_attr}#{table_class_attr}#{table_style_attr}>)
|
453
440
|
lines << %(<caption>#{node.captioned_title}</caption>) if node.title?
|
@@ -460,7 +447,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
460
447
|
end
|
461
448
|
#else
|
462
449
|
# node.columns.each do |col|
|
463
|
-
# lines << %(<col style="width: #{col.attr 'colpcwidth'}
|
450
|
+
# lines << %(<col style="width: #{col.attr 'colpcwidth'}%"/>)
|
464
451
|
# end
|
465
452
|
#end
|
466
453
|
lines << '</colgroup>'
|
@@ -474,7 +461,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
474
461
|
else
|
475
462
|
case cell.style
|
476
463
|
when :asciidoc
|
477
|
-
cell_content = %(<div>#{cell.content}</div>)
|
464
|
+
cell_content = %(<div class="embed">#{cell.content}</div>)
|
478
465
|
when :verse
|
479
466
|
cell_content = %(<div class="verse">#{cell.text}</div>)
|
480
467
|
when :literal
|
@@ -498,7 +485,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
498
485
|
cell_class_attr = cell_classes.size > 0 ? %( class="#{cell_classes * ' '}") : nil
|
499
486
|
cell_colspan_attr = cell.colspan ? %( colspan="#{cell.colspan}") : nil
|
500
487
|
cell_rowspan_attr = cell.rowspan ? %( rowspan="#{cell.rowspan}") : nil
|
501
|
-
cell_style_attr = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'}
|
488
|
+
cell_style_attr = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'}") : nil
|
502
489
|
lines << %(<#{cell_tag_name}#{cell_class_attr}#{cell_colspan_attr}#{cell_rowspan_attr}#{cell_style_attr}>#{cell_content}</#{cell_tag_name}>)
|
503
490
|
end
|
504
491
|
lines << '</tr>'
|
@@ -515,7 +502,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
515
502
|
def colist node
|
516
503
|
lines = ['<div class="callout-list">
|
517
504
|
<ol>']
|
518
|
-
num =
|
505
|
+
num = CalloutStartNum
|
519
506
|
node.items.each_with_index do |item, i|
|
520
507
|
lines << %(<li><i class="conum" data-value="#{i + 1}">#{num}</i> #{item.text}</li>)
|
521
508
|
num = num.next
|
@@ -545,7 +532,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
545
532
|
lines << '<li>'
|
546
533
|
if dd
|
547
534
|
# NOTE: must wrap remaining text in a span to help webkit justify the text properly
|
548
|
-
lines << %(<span class="principal">#{subject_element}#{dd.text? ? %[ <span class="supporting">#{dd.text}</span>] : nil}</span>)
|
535
|
+
lines << %(<span class="principal">#{subject_element}#{dd.text? ? %[ <span class="supporting">#{dd.text}</span>] : nil}</span>)
|
549
536
|
lines << dd.content if dd.blocks?
|
550
537
|
else
|
551
538
|
lines << %(<span class="principal">#{subject_element}</span>)
|
@@ -611,7 +598,6 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
611
598
|
def ulist node
|
612
599
|
complex = false
|
613
600
|
div_classes = ['itemized-list', node.style, node.role].compact
|
614
|
-
# TODO could strip WordJoiner if brief since not using justify
|
615
601
|
ul_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
|
616
602
|
ul_class_attr = ul_classes.empty? ? nil : %( class="#{ul_classes * ' '}")
|
617
603
|
id_attribute = node.id ? %( id="#{node.id}") : nil
|
@@ -642,7 +628,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
642
628
|
img_attrs = [%(alt="#{node.attr 'alt'}")]
|
643
629
|
case type
|
644
630
|
when 'svg'
|
645
|
-
img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}
|
631
|
+
img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
|
646
632
|
# TODO make this a convenience method on document
|
647
633
|
epub_properties = (node.document.attr 'epub-properties') || []
|
648
634
|
unless epub_properties.include? 'svg'
|
@@ -651,7 +637,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
651
637
|
end
|
652
638
|
else
|
653
639
|
if node.attr? 'scaledwidth'
|
654
|
-
img_attrs << %(style="width: #{node.attr 'scaledwidth'}
|
640
|
+
img_attrs << %(style="width: #{node.attr 'scaledwidth'}")
|
655
641
|
end
|
656
642
|
end
|
657
643
|
=begin
|
@@ -664,7 +650,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
664
650
|
# img_attrs << %(width="#{node.attr 'scaledheight'}" height="#{node.attr 'scaledheight'}")
|
665
651
|
# ePub3
|
666
652
|
elsif node.attr? 'scaledheight'
|
667
|
-
img_attrs << %(height="#{node.attr 'scaledheight'}" style="max-height: #{node.attr 'scaledheight'} !important
|
653
|
+
img_attrs << %(height="#{node.attr 'scaledheight'}" style="max-height: #{node.attr 'scaledheight'} !important")
|
668
654
|
else
|
669
655
|
# Aldiko doesn't not scale width to 100% by default
|
670
656
|
img_attrs << %(width="100%")
|
@@ -682,22 +668,58 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
682
668
|
def inline_anchor node
|
683
669
|
target = node.target
|
684
670
|
case node.type
|
685
|
-
when :xref
|
686
|
-
refid = (node.attr 'refid') || target
|
687
|
-
|
688
|
-
|
689
|
-
|
671
|
+
when :xref # TODO would be helpful to know what type the target is (e.g., bibref)
|
672
|
+
doc, refid, text, path = node.document, ((node.attr 'refid') || target), node.text, (node.attr 'path')
|
673
|
+
# NOTE if path is non-nil, we have an inter-document xref
|
674
|
+
# QUESTION should we drop the id attribute for an inter-document xref?
|
675
|
+
if path
|
676
|
+
# ex. chapter-id#section-id
|
677
|
+
if node.attr 'fragment'
|
678
|
+
refdoc_id, refdoc_refid = refid.split '#', 2
|
679
|
+
if refdoc_id == refdoc_refid
|
680
|
+
target = target[0...(target.index '#')]
|
681
|
+
id_attr = %( id="xref--#{refdoc_id}")
|
682
|
+
else
|
683
|
+
id_attr = %( id="xref--#{refdoc_id}--#{refdoc_refid}")
|
684
|
+
end
|
685
|
+
# ex. chapter-id#
|
686
|
+
else
|
687
|
+
refdoc_id = refdoc_refid = refid
|
688
|
+
# inflate key to spine item root (e.g., transform chapter-id to chapter-id#chapter-id)
|
689
|
+
refid = %(#{refid}##{refid})
|
690
|
+
id_attr = %( id="xref--#{refdoc_id}")
|
691
|
+
end
|
692
|
+
id_attr = nil unless @xrefs_seen.add? refid
|
693
|
+
refdoc = doc.references[:spine_items].find {|it| refdoc_id == (it.id || (it.attr 'docname')) }
|
694
|
+
if refdoc
|
695
|
+
if (reftext = refdoc.references[:ids][refdoc_refid])
|
696
|
+
text ||= reftext
|
697
|
+
else
|
698
|
+
warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to unknown anchor in #{refdoc_id} chapter: #{refdoc_refid})
|
699
|
+
end
|
700
|
+
else
|
701
|
+
warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to anchor in unknown chapter: #{refdoc_id})
|
702
|
+
end
|
703
|
+
else
|
704
|
+
id_attr = (@xrefs_seen.add? refid) ? %( id="xref-#{refid}") : nil
|
705
|
+
if (reftext = doc.references[:ids][refid])
|
706
|
+
text ||= reftext
|
707
|
+
else
|
708
|
+
# FIXME we get false negatives for reference to bibref
|
709
|
+
warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to unknown local anchor (or valid bibref): #{refid})
|
710
|
+
end
|
690
711
|
end
|
691
|
-
#
|
692
|
-
# FIXME would be nice to know what type the target is (e.g., bibref)
|
693
|
-
text = node.text || (node.document.references[:ids][refid] || %([#{refid}]))
|
694
|
-
%(<a#{id_attr} href="#{target}" class="xref">#{text}</a>#{WordJoiner})
|
712
|
+
%(<a#{id_attr} href="#{target}" class="xref">#{text || "[#{refid}]"}</a>)
|
695
713
|
when :ref
|
696
714
|
%(<a id="#{target}"></a>)
|
697
715
|
when :link
|
698
|
-
%(<a href="#{target}" class="link">#{node.text}</a
|
716
|
+
%(<a href="#{target}" class="link">#{node.text}</a>)
|
699
717
|
when :bibref
|
700
|
-
|
718
|
+
if @xrefs_seen.include? target
|
719
|
+
%(<a id="#{target}" href="#xref-#{target}">[#{target}]</a>)
|
720
|
+
else
|
721
|
+
%(<a id="#{target}"></a>[#{target}])
|
722
|
+
end
|
701
723
|
end
|
702
724
|
end
|
703
725
|
|
@@ -706,11 +728,11 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
706
728
|
end
|
707
729
|
|
708
730
|
def inline_button node
|
709
|
-
%(<b class="button">[<span class="label">#{node.text}</span>]</b
|
731
|
+
%(<b class="button">[<span class="label">#{node.text}</span>]</b>)
|
710
732
|
end
|
711
733
|
|
712
734
|
def inline_callout node
|
713
|
-
num =
|
735
|
+
num = CalloutStartNum
|
714
736
|
int_num = node.text.to_i
|
715
737
|
(int_num - 1).times { num = num.next }
|
716
738
|
%(<i class="conum" data-value="#{int_num}">#{num}</i>)
|
@@ -725,7 +747,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
725
747
|
end
|
726
748
|
|
727
749
|
def inline_image node
|
728
|
-
if
|
750
|
+
if node.type == 'icon'
|
729
751
|
@icon_names << (icon_name = node.target)
|
730
752
|
i_classes = ['icon', %(i-#{icon_name})]
|
731
753
|
i_classes << %(icon-#{node.attr 'size'}) if node.attr? 'size'
|
@@ -748,7 +770,7 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
748
770
|
if (keys = node.attr 'keys').size == 1
|
749
771
|
%(<kbd>#{keys[0]}</kbd>)
|
750
772
|
else
|
751
|
-
key_combo = keys.map {|key| %(<kbd>#{key}</kbd
|
773
|
+
key_combo = keys.map {|key| %(<kbd>#{key}</kbd>) }.join '+'
|
752
774
|
%(<span class="keyseq">#{key_combo}</span>)
|
753
775
|
end
|
754
776
|
end
|
@@ -770,11 +792,11 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
770
792
|
def inline_quoted node
|
771
793
|
case node.type
|
772
794
|
when :strong
|
773
|
-
%(<strong>#{node.text}</strong
|
795
|
+
%(<strong>#{node.text}</strong>)
|
774
796
|
when :emphasis
|
775
|
-
%(<em>#{node.text}</em
|
797
|
+
%(<em>#{node.text}</em>)
|
776
798
|
when :monospaced
|
777
|
-
%(<code class="literal">#{node.text}</code
|
799
|
+
%(<code class="literal">#{node.text}</code>)
|
778
800
|
when :double
|
779
801
|
#%(“#{node.text}”)
|
780
802
|
%(“#{node.text}”)
|
@@ -782,28 +804,26 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
782
804
|
#%(‘#{node.text}’)
|
783
805
|
%(‘#{node.text}’)
|
784
806
|
when :superscript
|
785
|
-
%(<sup>#{node.text}</sup
|
807
|
+
%(<sup>#{node.text}</sup>)
|
786
808
|
when :subscript
|
787
|
-
%(<sub>#{node.text}</sub
|
809
|
+
%(<sub>#{node.text}</sub>)
|
788
810
|
else
|
789
811
|
node.text
|
790
812
|
end
|
791
813
|
end
|
792
814
|
|
793
815
|
def convert_content node
|
794
|
-
|
795
|
-
%(<p>#{node.content}</p>)
|
796
|
-
else
|
797
|
-
node.content
|
798
|
-
end
|
816
|
+
node.content_model == :simple ? %(<p>#{node.content}</p>) : node.content
|
799
817
|
end
|
800
818
|
|
819
|
+
# FIXME merge into with xml_sanitize helper
|
801
820
|
def xml_sanitize value, target = :attribute
|
802
|
-
sanitized = (value.include? '<') ? value.gsub(XmlElementRx, '').tr_s(' ', ' ')
|
821
|
+
sanitized = (value.include? '<') ? value.gsub(XmlElementRx, '').strip.tr_s(' ', ' ') : value
|
803
822
|
if target == :plain && (sanitized.include? ';')
|
804
|
-
sanitized = sanitized.gsub(CharEntityRx) { [$1.to_i].pack
|
823
|
+
sanitized = sanitized.gsub(CharEntityRx) { [$1.to_i].pack 'U*' } if sanitized.include? '&#'
|
824
|
+
sanitized = sanitized.gsub(FromHtmlSpecialCharsRx, FromHtmlSpecialCharsMap)
|
805
825
|
elsif target == :attribute
|
806
|
-
sanitized = sanitized.gsub
|
826
|
+
sanitized = sanitized.gsub '"', '"' if sanitized.include? '"'
|
807
827
|
end
|
808
828
|
sanitized
|
809
829
|
end
|
@@ -822,17 +842,58 @@ document.addEventListener('DOMContentLoaded', function(event) {
|
|
822
842
|
end
|
823
843
|
|
824
844
|
class DocumentIdGenerator
|
845
|
+
ReservedIds = %w(cover nav ncx)
|
846
|
+
CharRefRx = /&(?:([a-zA-Z]{2,})|#(\d{2,6})|#x([a-fA-F0-9]{2,5}));/
|
847
|
+
if defined? __dir__
|
848
|
+
InvalidIdCharsRx = /[^\p{Word}]+/
|
849
|
+
LeadingDigitRx = /^\p{Nd}/
|
850
|
+
else
|
851
|
+
InvalidIdCharsRx = /[^[:word:]]+/
|
852
|
+
LeadingDigitRx = /^[[:digit:]]/
|
853
|
+
end
|
825
854
|
class << self
|
826
|
-
def generate_id doc
|
855
|
+
def generate_id doc, pre = nil, sep = nil
|
856
|
+
synthetic = false
|
827
857
|
unless (id = doc.id)
|
828
|
-
|
829
|
-
|
858
|
+
# NOTE we assume pre is a valid ID prefix and that pre and sep only contain valid ID chars
|
859
|
+
pre ||= '_'
|
860
|
+
sep = sep ? sep.chr : '_'
|
861
|
+
if doc.header?
|
862
|
+
id = doc.doctitle sanitize: true
|
863
|
+
id = id.gsub CharRefRx do
|
864
|
+
$1 ? ($1 == 'amp' ? 'and' : sep) : ((d = $2 ? $2.to_i : $3.hex) == 8217 ? '' : ([d].pack 'U*'))
|
865
|
+
end if id.include? '&'
|
866
|
+
id = id.downcase.gsub InvalidIdCharsRx, sep
|
867
|
+
if id.empty?
|
868
|
+
id, synthetic = nil, true
|
869
|
+
else
|
870
|
+
unless sep.empty?
|
871
|
+
if (id = id.tr_s sep, sep).end_with? sep
|
872
|
+
if id == sep
|
873
|
+
id, synthetic = nil, true
|
874
|
+
else
|
875
|
+
id = (id.start_with? sep) ? id[1..-2] : id.chop
|
876
|
+
end
|
877
|
+
elsif id.start_with? sep
|
878
|
+
id = id[1..-1]
|
879
|
+
end
|
880
|
+
end
|
881
|
+
unless synthetic
|
882
|
+
if pre.empty?
|
883
|
+
id = %(_#{id}) if LeadingDigitRx =~ id
|
884
|
+
elsif !(id.start_with? pre)
|
885
|
+
id = %(#{pre}#{id})
|
886
|
+
end
|
887
|
+
end
|
888
|
+
end
|
830
889
|
elsif (first_section = doc.first_section)
|
831
|
-
first_section.id
|
890
|
+
id = first_section.id
|
832
891
|
else
|
833
|
-
|
892
|
+
synthetic = true
|
834
893
|
end
|
894
|
+
id = %(#{pre}document#{sep}#{doc.object_id}) if synthetic
|
835
895
|
end
|
896
|
+
warn %(asciidoctor: ERROR: chapter uses a reserved ID: #{id}) if !synthetic && (ReservedIds.include? id)
|
836
897
|
id
|
837
898
|
end
|
838
899
|
end
|
@@ -854,14 +915,20 @@ Extensions.register do
|
|
854
915
|
when 'epub3', 'kf8'
|
855
916
|
# all good
|
856
917
|
when 'mobi'
|
857
|
-
document.attributes['ebook-format'] = 'kf8'
|
918
|
+
ebook_format = document.attributes['ebook-format'] = 'kf8'
|
858
919
|
else
|
920
|
+
# QUESTION should we display a warning?
|
859
921
|
ebook_format = document.attributes['ebook-format'] = 'epub3'
|
860
922
|
end
|
861
923
|
document.attributes[%(ebook-format-#{ebook_format})] = ''
|
862
924
|
# Only fire SpineItemProcessor for top-level include directives
|
863
925
|
include_processor SpineItemProcessor.new(document)
|
864
|
-
treeprocessor
|
926
|
+
treeprocessor do
|
927
|
+
process do |doc|
|
928
|
+
doc.id = DocumentIdGenerator.generate_id doc, (doc.attr 'idprefix'), (doc.attr 'idseparator')
|
929
|
+
nil
|
930
|
+
end
|
931
|
+
end
|
865
932
|
end
|
866
933
|
end
|
867
934
|
end
|