jekyll-t4j 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -17
- data/lib/jekyll-t4j/engine/dvisvgm.rb +54 -0
- data/lib/jekyll-t4j/engine/dvisvgm.tex +8 -0
- data/lib/jekyll-t4j/engine/katex.js +18798 -0
- data/lib/jekyll-t4j/engine/katex.rb +19 -0
- data/lib/jekyll-t4j/engine.rb +118 -0
- data/lib/jekyll-t4j/merger.rb +18 -34
- data/lib/jekyll-t4j/renderer.rb +68 -31
- data/lib/jekyll-t4j/version.rb +1 -1
- data/lib/jekyll-t4j.rb +11 -10
- metadata +26 -13
- data/lib/jekyll-t4j/engines/dvisvgm.rb +0 -32
- data/lib/jekyll-t4j/engines.rb +0 -25
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "execjs"
|
4
|
+
|
5
|
+
module Jekyll::T4J
|
6
|
+
module Engine
|
7
|
+
KATEX_CSS_VERSION = "0.16.4"
|
8
|
+
|
9
|
+
@@_katex_js_ = ExecJS.runtime.compile File.read(File.join(__dir__, "katex.js"))
|
10
|
+
|
11
|
+
def self.katex_raw(snippet, options = nil)
|
12
|
+
snippet = split_snippet(snippet)
|
13
|
+
options = {} unless options
|
14
|
+
options[:displayMode] = snippet[1]
|
15
|
+
|
16
|
+
@@_katex_js_.call("katex.renderToString", snippet[0], options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless system("latex -version", [:out, :err]=>File::NULL)
|
4
|
+
STDERR.puts "You are missing a TeX distribution. Please install:"
|
5
|
+
STDERR.puts " MiKTeX or TeX Live"
|
6
|
+
raise "Missing TeX distribution"
|
7
|
+
end
|
8
|
+
|
9
|
+
require "jekyll/cache"
|
10
|
+
|
11
|
+
require "jekyll-t4j/engine/dvisvgm"
|
12
|
+
require "jekyll-t4j/engine/katex"
|
13
|
+
|
14
|
+
module Jekyll::T4J
|
15
|
+
module Engine
|
16
|
+
@@cache_katex = Jekyll::Cache.new "Jekyll::T4J::Katex"
|
17
|
+
@@cache_dvisvgm = Jekyll::Cache.new "Jekyll::T4J::Dvisvgm"
|
18
|
+
|
19
|
+
@@header = <<~HEREDOC
|
20
|
+
<link rel=\"stylesheet\" href=\"https://unpkg.com/katex@#{KATEX_CSS_VERSION}/dist/katex.min.css\">
|
21
|
+
<style>
|
22
|
+
.katex-ext-d {
|
23
|
+
border-radius: 0px;
|
24
|
+
display: block;
|
25
|
+
margin: 5px auto;
|
26
|
+
}
|
27
|
+
.katex-ext-i {
|
28
|
+
border-radius: 0px;
|
29
|
+
display: inline;
|
30
|
+
vertical-align: middle;
|
31
|
+
}
|
32
|
+
</style>
|
33
|
+
HEREDOC
|
34
|
+
|
35
|
+
def self.header
|
36
|
+
@@header
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.render(snippets, merger)
|
40
|
+
gen = ->(svg_data, displayMode) {
|
41
|
+
"<img src=\"#{merger.(svg_data, ".svg")}\" class=\"#{
|
42
|
+
displayMode ? "katex-ext-d" : "katex-ext-i"
|
43
|
+
}\" style=\"height:#{
|
44
|
+
(svg_data[/height='(\S+?)pt'/, 1].to_f * 0.1).to_s[/\d+\.\d{1,4}/]
|
45
|
+
}em\">"
|
46
|
+
}
|
47
|
+
|
48
|
+
# normalize 'snippets'
|
49
|
+
snippets.map! {|s|
|
50
|
+
if not s.start_with?("\\") then
|
51
|
+
s = split_snippet(s)
|
52
|
+
s = s[1] ? "\\[#{s[0]}\\]" : "\\(#{s[0]}\\)"
|
53
|
+
end
|
54
|
+
|
55
|
+
s
|
56
|
+
}
|
57
|
+
|
58
|
+
# fill 'snippets' with katex and cached dvisvgm
|
59
|
+
unset = []
|
60
|
+
uncached = {}
|
61
|
+
snippets.each_index {|i|
|
62
|
+
s = snippets[i]
|
63
|
+
r = @@cache_katex.getset(s) {
|
64
|
+
begin
|
65
|
+
katex_raw(s, {strict: true})
|
66
|
+
rescue
|
67
|
+
"NIL"
|
68
|
+
end
|
69
|
+
}
|
70
|
+
|
71
|
+
if r == "NIL" then
|
72
|
+
begin
|
73
|
+
r = gen.(@@cache_dvisvgm[Jekyll::T4J.cfg_pkgs + s], is_display_mode?(s))
|
74
|
+
rescue
|
75
|
+
uncached[s] = r = nil
|
76
|
+
unset << [i, s]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
snippets[i] = r
|
81
|
+
}
|
82
|
+
|
83
|
+
# render 'uncached'
|
84
|
+
if not uncached.empty? then
|
85
|
+
# bulk render 'uncached'
|
86
|
+
begin
|
87
|
+
uncached_snippets = uncached.keys
|
88
|
+
rendered = dvisvgm_raw_bulk(uncached_snippets)
|
89
|
+
rendered.each_index {|i| uncached[uncached_snippets[i]] = rendered[i]}
|
90
|
+
end
|
91
|
+
|
92
|
+
# flush 'uncached' to 'snippets' and cache them
|
93
|
+
unset.each {|b|
|
94
|
+
s = b[1]
|
95
|
+
snippets[b[0]] = gen.(uncached[s], is_display_mode?(s))
|
96
|
+
@@cache_dvisvgm[Jekyll::T4J.cfg_pkgs + s] = uncached[s]
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.is_display_mode?(snippet)
|
102
|
+
snippet.start_with?("\\[") or snippet.start_with?("$$")
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.split_snippet(snippet)
|
106
|
+
displayMode = false
|
107
|
+
range = 2..-3
|
108
|
+
|
109
|
+
if snippet.start_with?("\\[") or snippet.start_with?("$$") then
|
110
|
+
displayMode = true
|
111
|
+
elsif snippet.start_with?("$") then
|
112
|
+
range = 1..-2
|
113
|
+
end
|
114
|
+
|
115
|
+
[snippet[range], displayMode]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/jekyll-t4j/merger.rb
CHANGED
@@ -2,46 +2,30 @@
|
|
2
2
|
|
3
3
|
module Jekyll::T4J
|
4
4
|
module Merger
|
5
|
-
@@
|
6
|
-
@@cache = {}
|
5
|
+
@@table = {}
|
7
6
|
@@rnd_range = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$@".chars
|
8
|
-
|
9
|
-
def self.ask_for_merge(url, filedata, extname)
|
10
|
-
raise "Merge request during post processing!" unless @@available
|
11
|
-
|
12
|
-
request = @@cache[url]
|
13
|
-
request = @@cache[url] = {} unless request
|
14
|
-
|
15
|
-
entry = request.rassoc(filedata.freeze)
|
16
|
-
return entry[0] if entry and entry[0].split(".")[1] == extname
|
17
|
-
|
18
|
-
filename = @@rnd_range.sample(22).join.prepend("_") << "." << extname
|
19
|
-
request[filename] = filedata
|
20
|
-
filename.freeze
|
21
|
-
end
|
22
|
-
|
23
|
-
# write external files
|
24
|
-
Jekyll::Hooks.register :documents, :post_write do |doc|
|
25
|
-
@@available = false
|
26
|
-
|
27
|
-
url = doc.url
|
28
|
-
request = @@cache[url]
|
29
7
|
|
8
|
+
def self.ask_for_merge(filedata, extname)
|
9
|
+
request = @@table[filedata]
|
10
|
+
|
30
11
|
if request then
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
}
|
36
|
-
|
37
|
-
request.clear
|
12
|
+
request[1] << extname
|
13
|
+
else
|
14
|
+
basename = @@rnd_range.sample(22).join.prepend("_").freeze
|
15
|
+
request = @@table[filedata] = [basename, [extname]]
|
38
16
|
end
|
17
|
+
|
18
|
+
"/" + request[0] + extname
|
39
19
|
end
|
40
20
|
|
41
|
-
# clean up
|
42
|
-
Jekyll::Hooks.register :site, :post_write do
|
43
|
-
@@
|
44
|
-
|
21
|
+
# write external files and clean up
|
22
|
+
Jekyll::Hooks.register :site, :post_write, priority: Jekyll::Hooks::PRIORITY_MAP[:low] do |site|
|
23
|
+
@@table.each {|filedata, request|
|
24
|
+
basename = request[0]
|
25
|
+
request[1].each {|extname| File.write(File.join(site.dest, basename + extname), filedata)}
|
26
|
+
}
|
27
|
+
|
28
|
+
@@table.clear
|
45
29
|
end
|
46
30
|
end
|
47
31
|
end
|
data/lib/jekyll-t4j/renderer.rb
CHANGED
@@ -14,13 +14,13 @@ module Jekyll::T4J
|
|
14
14
|
).freeze
|
15
15
|
|
16
16
|
TEXT_TEX_MASK = Regexp.union(
|
17
|
-
/\\\[
|
18
|
-
/\$\$
|
19
|
-
/\\\(
|
20
|
-
/\$
|
17
|
+
/\\\[[\s\S]*?\\\]/,
|
18
|
+
/\$\$[\s\S]*?\$\$/,
|
19
|
+
/\\\([\s\S]*?\\\)/,
|
20
|
+
/\$[\s\S]*?\$/
|
21
21
|
).freeze
|
22
22
|
|
23
|
-
def self.mask(str, reg
|
23
|
+
def self.mask(str, reg)
|
24
24
|
s = StringScanner.new str
|
25
25
|
parts = []
|
26
26
|
|
@@ -32,7 +32,7 @@ module Jekyll::T4J
|
|
32
32
|
p1 = s.charpos - s.matched.size - 1
|
33
33
|
|
34
34
|
parts << [str[p0..p1], true] unless p1 < p0
|
35
|
-
parts <<
|
35
|
+
parts << [s.matched, false]
|
36
36
|
else
|
37
37
|
parts << [str[p0..-1], true]
|
38
38
|
break
|
@@ -41,38 +41,75 @@ module Jekyll::T4J
|
|
41
41
|
|
42
42
|
parts
|
43
43
|
end
|
44
|
-
end
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
def self.extract(html)
|
46
|
+
result = []
|
47
|
+
prev = nil
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
for p0 in mask(html, HTML_TEXT_MASK)
|
50
|
+
if p0[1] then #p0[0] is a text node
|
51
|
+
for p1 in mask(p0[0], TEXT_TEX_MASK)
|
52
|
+
if not p1[1] then #p1[0] is a tex snippet
|
53
|
+
prev = nil
|
54
|
+
p1[1] = true
|
55
|
+
result << p1
|
56
|
+
else
|
57
|
+
if prev then
|
58
|
+
prev << p1[0]
|
59
|
+
else
|
60
|
+
prev = p1[0]
|
61
|
+
p1[1] = false
|
62
|
+
result << p1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
else
|
67
|
+
if prev then
|
68
|
+
prev << p0[0]
|
69
|
+
else
|
70
|
+
prev = p0[0]
|
71
|
+
result << p0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
result
|
77
|
+
end
|
52
78
|
|
53
|
-
|
54
|
-
|
55
|
-
|
79
|
+
# T4J rendering phase
|
80
|
+
Jekyll::Hooks.register :site, :post_render do |site|
|
81
|
+
snippets = []
|
82
|
+
docs = []
|
56
83
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
84
|
+
# collect tex snippets
|
85
|
+
site.each_site_file {|f|
|
86
|
+
if !f.is_a?(Jekyll::StaticFile) then
|
87
|
+
parts = extract(f.output)
|
88
|
+
if parts.size > 1 then #'f' has tex snippet(s)
|
89
|
+
parts.each {|part| snippets << CGI::unescapeHTML(part[0]) if part[1]} # TODO: a subtle bug about unescaping HTML
|
90
|
+
docs << [f, parts]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
# render 'snippets'
|
96
|
+
Jekyll::T4J::Engine.render(snippets, ->(data, extname) {Jekyll::T4J::Merger.ask_for_merge(data, extname)})
|
97
|
+
|
98
|
+
# 'snippets' -> 'docs'
|
99
|
+
i = 0
|
100
|
+
for doc in docs
|
101
|
+
newoutput = String.new
|
61
102
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
p1[0] = gen.(CGI::unescapeHTML(c), i < 2) if not c.empty?
|
68
|
-
} if not p1[1]
|
103
|
+
for part in doc[1]
|
104
|
+
if part[1] then
|
105
|
+
part[0] = snippets[i]
|
106
|
+
i += 1
|
107
|
+
end
|
69
108
|
|
70
|
-
|
109
|
+
newoutput << part[0]
|
71
110
|
end
|
72
|
-
|
73
|
-
|
111
|
+
|
112
|
+
doc[0].output = newoutput.insert(newoutput.index("</head>"), Jekyll::T4J::Engine.header)
|
74
113
|
end
|
75
114
|
end
|
76
|
-
|
77
|
-
doc.output = result
|
78
115
|
end
|
data/lib/jekyll-t4j/version.rb
CHANGED
data/lib/jekyll-t4j.rb
CHANGED
@@ -14,21 +14,22 @@ module Jekyll::T4J
|
|
14
14
|
# initialize plugin
|
15
15
|
Jekyll::Hooks.register :site, :after_init do |site|
|
16
16
|
cfg = site.config["t4j"]
|
17
|
-
@@cfg_pkgs = (cfg and (cfg = cfg["packages"])) ? parse_cfg_pkgs(cfg) : ""
|
18
|
-
end
|
19
17
|
|
20
|
-
|
21
|
-
|
18
|
+
if cfg and (cfg = cfg["packages"]) then
|
19
|
+
pkgs = String.new
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
for p in cfg
|
22
|
+
raise "Illegal config: #{p}" unless p.match(/\s*([\w-]+)(\[[^\[\]]*\])?\s*/)
|
23
|
+
pkgs << "\\usepackage#{$2}{#{$1}}"
|
24
|
+
end
|
27
25
|
|
28
|
-
|
26
|
+
@@cfg_pkgs = pkgs
|
27
|
+
else
|
28
|
+
@@cfg_pkgs = ""
|
29
|
+
end
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
33
|
require "jekyll-t4j/merger"
|
33
|
-
require "jekyll-t4j/
|
34
|
+
require "jekyll-t4j/engine"
|
34
35
|
require "jekyll-t4j/renderer"
|
metadata
CHANGED
@@ -1,36 +1,46 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-t4j
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- crow02531
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '4.1'
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '5'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - "
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '4.1'
|
30
|
-
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: execjs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
31
39
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
33
|
-
description:
|
40
|
+
version: '2.8'
|
41
|
+
description: |2
|
42
|
+
A Jekyll plugin providing (nearly) full support of LaTeX. Capable of
|
43
|
+
rendering almost all packages, including tikz, chemfig, etc.
|
34
44
|
email: crow02531@outlook.com
|
35
45
|
executables: []
|
36
46
|
extensions: []
|
@@ -39,8 +49,11 @@ files:
|
|
39
49
|
- LICENSE
|
40
50
|
- README.md
|
41
51
|
- lib/jekyll-t4j.rb
|
42
|
-
- lib/jekyll-t4j/
|
43
|
-
- lib/jekyll-t4j/
|
52
|
+
- lib/jekyll-t4j/engine.rb
|
53
|
+
- lib/jekyll-t4j/engine/dvisvgm.rb
|
54
|
+
- lib/jekyll-t4j/engine/dvisvgm.tex
|
55
|
+
- lib/jekyll-t4j/engine/katex.js
|
56
|
+
- lib/jekyll-t4j/engine/katex.rb
|
44
57
|
- lib/jekyll-t4j/merger.rb
|
45
58
|
- lib/jekyll-t4j/renderer.rb
|
46
59
|
- lib/jekyll-t4j/version.rb
|
@@ -63,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
76
|
- !ruby/object:Gem::Version
|
64
77
|
version: '0'
|
65
78
|
requirements:
|
66
|
-
- TeX distribution
|
79
|
+
- A TeX distribution
|
67
80
|
rubygems_version: 3.3.7
|
68
81
|
signing_key:
|
69
82
|
specification_version: 4
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "tmpdir"
|
4
|
-
require "jekyll/cache"
|
5
|
-
|
6
|
-
module Jekyll::T4J
|
7
|
-
module Engines
|
8
|
-
@@cache_dvisvgm = Jekyll::Cache.new "Jekyll::T4J::Dvisvgm"
|
9
|
-
|
10
|
-
def self.dvisvgm_raw(src)
|
11
|
-
# setup: write 'src' to 'content.tex'
|
12
|
-
check_tex
|
13
|
-
pwd = Dir.mktmpdir
|
14
|
-
File.write "#{pwd}/content.tex", src
|
15
|
-
|
16
|
-
# call 'latex' to compile: tex->dvi
|
17
|
-
system "latex -halt-on-error content", :chdir => pwd, [:out, :err] => File::NULL, exception: true
|
18
|
-
system "latex -halt-on-error content", :chdir => pwd, [:out, :err] => File::NULL, exception: true
|
19
|
-
# call 'dvisvgm' to convert dvi to svg
|
20
|
-
system "dvisvgm -n -e content", :chdir => pwd, [:out, :err] => File::NULL, exception: true
|
21
|
-
|
22
|
-
# fetch result
|
23
|
-
File.read "#{pwd}/content.svg"
|
24
|
-
ensure
|
25
|
-
FileUtils.remove_entry pwd if pwd
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.dvisvgm(src)
|
29
|
-
@@cache_dvisvgm.getset(src) {dvisvgm_raw(src)}
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/lib/jekyll-t4j/engines.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Jekyll::T4J
|
4
|
-
module Engines
|
5
|
-
@@_passed = nil
|
6
|
-
|
7
|
-
def self.has_tex?
|
8
|
-
if @@_passed == nil then
|
9
|
-
@@_passed = system("latex -version", [:out, :err]=>File::NULL) ? true : false
|
10
|
-
end
|
11
|
-
|
12
|
-
@@_passed
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.check_tex
|
16
|
-
unless has_tex?
|
17
|
-
STDERR.puts "You are missing a TeX distribution. Please install:"
|
18
|
-
STDERR.puts " MiKTeX or TeX Live"
|
19
|
-
raise "Missing TeX distribution"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
require "jekyll-t4j/engines/dvisvgm"
|