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.
@@ -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
@@ -2,46 +2,30 @@
2
2
 
3
3
  module Jekyll::T4J
4
4
  module Merger
5
- @@available = true
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
- url = File.dirname url unless url.end_with? "/"
32
-
33
- request.each {|k, v|
34
- File.write File.join(doc.site.dest, url, k), v
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
- @@cache.clear
44
- @@available = true
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
@@ -14,13 +14,13 @@ module Jekyll::T4J
14
14
  ).freeze
15
15
 
16
16
  TEXT_TEX_MASK = Regexp.union(
17
- /\\\[([\s\S]*?)\\\]/,
18
- /\$\$([\s\S]*?)\$\$/,
19
- /\\\(([\s\S]*?)\\\)/,
20
- /\$([\s\S]*?)\$/
17
+ /\\\[[\s\S]*?\\\]/,
18
+ /\$\$[\s\S]*?\$\$/,
19
+ /\\\([\s\S]*?\\\)/,
20
+ /\$[\s\S]*?\$/
21
21
  ).freeze
22
22
 
23
- def self.mask(str, reg, capture = false)
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 << (capture ? [s.matched, false, s.captures] : [s.matched, false])
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
- Jekyll::Hooks.register :documents, :post_render do |doc|
47
- result = String.new
45
+ def self.extract(html)
46
+ result = []
47
+ prev = nil
48
48
 
49
- gen = -> (s, is_display) {
50
- s.strip!
51
- return "" if s.empty?
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
- s.prepend "\\documentclass{article}#{Jekyll::T4J.cfg_pkgs}\\begin{document}\\pagenumbering{gobble}\\begin{math}"
54
- s << "\\end{math}\\end{document}"
55
- s = Jekyll::T4J::Engines.dvisvgm(s)
79
+ # T4J rendering phase
80
+ Jekyll::Hooks.register :site, :post_render do |site|
81
+ snippets = []
82
+ docs = []
56
83
 
57
- "<img src=\"#{Jekyll::T4J::Merger.ask_for_merge(doc.url, s, "svg")}\" style=\"#{
58
- is_display ? "display:block;margin:0 auto" : "display:inline;vertical-align:middle"
59
- };height:#{(s[/height='(\S+?)pt'/, 1].to_f * 0.1).to_s}em\">"
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
- for p0 in Jekyll::T4J.mask(doc.output, Jekyll::T4J::HTML_TEXT_MASK)
63
- if p0[1] then
64
- for p1 in Jekyll::T4J.mask(p0[0], Jekyll::T4J::TEXT_TEX_MASK, true)
65
- p1[2].each_index {|i|
66
- c = p1[2][i]
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
- result << p1[0]
109
+ newoutput << part[0]
71
110
  end
72
- else
73
- result << p0[0]
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module T4J
5
- VERSION = "0.2.2"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
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
- def self.parse_cfg_pkgs(cfg)
21
- ret = String.new
18
+ if cfg and (cfg = cfg["packages"]) then
19
+ pkgs = String.new
22
20
 
23
- for p in cfg
24
- raise "Illegal config: #{p}" unless p.match(/\s*([\w-]+)(\[[^\[\]]*\])?\s*/)
25
- ret << "\\usepackage#{$2}{#{$1}}"
26
- end
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
- ret
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/engines"
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.2.2
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-09 00:00:00.000000000 Z
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: '5'
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/engines.rb
43
- - lib/jekyll-t4j/engines/dvisvgm.rb
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
@@ -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"