jekyll-t4j 0.2.0 → 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,8 +14,10 @@ module Jekyll::T4J
14
14
  ).freeze
15
15
 
16
16
  TEXT_TEX_MASK = Regexp.union(
17
+ /\\\[[\s\S]*?\\\]/,
18
+ /\$\$[\s\S]*?\$\$/,
17
19
  /\\\([\s\S]*?\\\)/,
18
- /\\\[[\s\S]*?\\\]/
20
+ /\$[\s\S]*?\$/
19
21
  ).freeze
20
22
 
21
23
  def self.mask(str, reg)
@@ -29,7 +31,7 @@ module Jekyll::T4J
29
31
  if m then
30
32
  p1 = s.charpos - s.matched.size - 1
31
33
 
32
- parts << [str[p0..p1], true] unless p1 <= p0
34
+ parts << [str[p0..p1], true] unless p1 < p0
33
35
  parts << [s.matched, false]
34
36
  else
35
37
  parts << [str[p0..-1], true]
@@ -39,34 +41,75 @@ module Jekyll::T4J
39
41
 
40
42
  parts
41
43
  end
42
- end
43
44
 
44
- Jekyll::Hooks.register :documents, :post_render do |doc|
45
- result = String.new
45
+ def self.extract(html)
46
+ result = []
47
+ prev = nil
46
48
 
47
- gen = -> (s) {
48
- is_inline = s.start_with?("\\(")
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
49
78
 
50
- s.prepend "\\documentclass{article}#{Jekyll::T4J.cfg_pkgs}\\begin{document}\\pagenumbering{gobble}"
51
- s << "\\end{document}"
52
- s = Jekyll::T4J::Engines.dvisvgm(s)
79
+ # T4J rendering phase
80
+ Jekyll::Hooks.register :site, :post_render do |site|
81
+ snippets = []
82
+ docs = []
53
83
 
54
- "<img src=\"#{Jekyll::T4J::Merger.ask_for_merge(doc.url, s, "svg")}\" style=\"#{
55
- is_inline ? "display:inline;height:1.1em;" : "display:block;margin:0 auto;height:calc(0.1em * #{s[/height='(\S+?)pt'/, 1]});"
56
- }\">"
57
- }
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
58
102
 
59
- for p0 in Jekyll::T4J.mask(doc.output, Jekyll::T4J::HTML_TEXT_MASK)
60
- if p0[1] then
61
- for p1 in Jekyll::T4J.mask(p0[0], Jekyll::T4J::TEXT_TEX_MASK)
62
- p1[0] = gen.(CGI::unescapeHTML p1[0]) 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
63
108
 
64
- result << p1[0]
109
+ newoutput << part[0]
65
110
  end
66
- else
67
- result << p0[0]
111
+
112
+ doc[0].output = newoutput.insert(newoutput.index("</head>"), Jekyll::T4J::Engine.header)
68
113
  end
69
114
  end
70
-
71
- doc.output = result
72
115
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module T4J
5
- VERSION = "0.2.0"
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.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-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 Errors::FatalException.new("Missing TeX distribution")
20
- end
21
- end
22
- end
23
- end
24
-
25
- require "jekyll-t4j/engines/dvisvgm"