jekyll-t4j 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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"