jektex 0.0.2
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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +83 -0
- data/lib/jektex/jektex.rb +125 -0
- data/lib/jektex/katex.min.js +1 -0
- data/lib/jektex/version.rb +3 -0
- data/lib/jektex.rb +5 -0
- metadata +134 -0
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Jektex
|
2
|
+
Jekyll plugin for blazing fast server side cached LaTeX rendering with support of macros.
|
3
|
+
Enjoy comfort of latex and markdown without cluttering your site with bloated javascript.
|
4
|
+
|
5
|
+
## About
|
6
|
+
- Renders LaTeX formulas during Jekyll rendering
|
7
|
+
- Works without any javascript o clients side
|
8
|
+
- Is faster than any other server side Jekyll latex renderer
|
9
|
+
- Supports user defined global macros
|
10
|
+
- Has I/O efficient caching system
|
11
|
+
- Has dynamic and informative log during rendering
|
12
|
+
- Is easy to setup
|
13
|
+
- Does not interfere with Jekyll workflow and project structure
|
14
|
+
- Marks invalid syntax in document
|
15
|
+
- Prints location of invalid expression during rendering
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
### Notation
|
20
|
+
**Inline formula**
|
21
|
+
Put formula between two pairs of `$` inside of paragraph.
|
22
|
+
|
23
|
+
```latex
|
24
|
+
Lorem ipsum dolor sit amet, consectetur $$e^{i\theta}=\cos(\theta)+i\sin(\theta)$$
|
25
|
+
adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
26
|
+
```
|
27
|
+
|
28
|
+
**Display formula**
|
29
|
+
Put formula between two pairs of `$` and surround it between two empty lines.
|
30
|
+
```latex
|
31
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
32
|
+
incididunt ut labore et dolore magna aliqua.
|
33
|
+
|
34
|
+
$$ i\hbar\frac{\partial}{\partial t} \Psi(\mathbf{r},t) = \left [ \frac{-\hbar^2}{2\mu}\nabla^2 + V(\mathbf{r},t)\right ] \Psi(\mathbf{r},t) $$
|
35
|
+
|
36
|
+
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
37
|
+
ea commodo consequat.
|
38
|
+
```
|
39
|
+
|
40
|
+
_Why Jektex does not use conventional single `$` for inline formulas and double `$$` for
|
41
|
+
display mode?
|
42
|
+
Unfortunately this is how [kramdown](https://kramdown.gettalong.org/)
|
43
|
+
(Jekyll's markdown parser) works and it would probably do be easier to write custom
|
44
|
+
markdown parser than hacking kramdown to behave differently._
|
45
|
+
|
46
|
+
### Macros
|
47
|
+
You can define global macros in your `_config.yml` file:
|
48
|
+
|
49
|
+
```yaml
|
50
|
+
# Jektex macros
|
51
|
+
jektex-macros:
|
52
|
+
- ["\\Q", "\\mathbb{Q}"]
|
53
|
+
- ["\\C", "\\mathbb{C}"]
|
54
|
+
```
|
55
|
+
|
56
|
+
## Installation
|
57
|
+
|
58
|
+
### Using bundler
|
59
|
+
Add `jektex` to your `Gemfile` like this:
|
60
|
+
|
61
|
+
```yaml
|
62
|
+
group :jekyll_plugins do
|
63
|
+
gem "jektex"
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
and run `bundle install`
|
68
|
+
|
69
|
+
### Without bundler
|
70
|
+
Just run `gem install jektex` and add jektex to your plugin list in your `_config.yml`
|
71
|
+
file:
|
72
|
+
```
|
73
|
+
plugins:
|
74
|
+
- jektex
|
75
|
+
|
76
|
+
```
|
77
|
+
|
78
|
+
### Style sheets
|
79
|
+
Do not forget to add `katex.min.css` to you html head:
|
80
|
+
```html
|
81
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css" integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ" crossorigin="anonymous">
|
82
|
+
```
|
83
|
+
It is much better practice to download css file and loaded as an asset from your server directly.
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'execjs'
|
2
|
+
require 'digest'
|
3
|
+
require 'htmlentities'
|
4
|
+
|
5
|
+
|
6
|
+
PATH_TO_JS = __dir__ + "/katex.min.js"
|
7
|
+
CACHE_DIR = "./.jektex-cache/"
|
8
|
+
CACHE_FILE = "jektex-cache.marshal"
|
9
|
+
PATH_TO_CACHE = CACHE_DIR + CACHE_FILE
|
10
|
+
KATEX = ExecJS.compile(open(PATH_TO_JS).read)
|
11
|
+
PARSE_ERROR_PLACEHOLDER = "<b style='color: red;'>PARSE ERROR</b>"
|
12
|
+
$global_macros = Hash.new
|
13
|
+
$count_newly_generated_expressions = 0
|
14
|
+
$cache = nil
|
15
|
+
$disable_disk_cache = false
|
16
|
+
|
17
|
+
def convert(doc)
|
18
|
+
# convert HTML enetities back to characters
|
19
|
+
post = HTMLEntities.new.decode(doc.to_s)
|
20
|
+
post = post.gsub(/(\\\()((.|\n)*?)(?<!\\)\\\)/) { |m| escape_method($1, $2, doc.path) }
|
21
|
+
post = post.gsub(/(\\\[)((.|\n)*?)(?<!\\)\\\]/) { |m| escape_method($1, $2, doc.path) }
|
22
|
+
return post
|
23
|
+
end
|
24
|
+
|
25
|
+
def escape_method( type, string, doc_path )
|
26
|
+
@display = false
|
27
|
+
|
28
|
+
# detect if expression is display view
|
29
|
+
case type.downcase
|
30
|
+
when /\(/
|
31
|
+
@display = false
|
32
|
+
else /\[/
|
33
|
+
@display = true
|
34
|
+
end
|
35
|
+
|
36
|
+
# generate a hash from the math expression
|
37
|
+
@expression_hash = Digest::SHA2.hexdigest(string) + @display.to_s
|
38
|
+
|
39
|
+
# use it if it exists
|
40
|
+
if($cache.has_key?(@expression_hash))
|
41
|
+
$count_newly_generated_expressions += 1
|
42
|
+
print_stats
|
43
|
+
return $cache[@expression_hash]
|
44
|
+
|
45
|
+
# else generate one and store it
|
46
|
+
else
|
47
|
+
# create the cache directory, if it doesn't exist
|
48
|
+
begin
|
49
|
+
# render using ExecJS
|
50
|
+
@result = KATEX.call("katex.renderToString", string,
|
51
|
+
{displayMode: @display, macros: $global_macros})
|
52
|
+
rescue SystemExit, Interrupt
|
53
|
+
# save cache to disk
|
54
|
+
File.open(PATH_TO_CACHE, "w"){|to_file| Marshal.dump($cache, to_file)}
|
55
|
+
# this stops jekyll being immune to interupts and kill command
|
56
|
+
raise
|
57
|
+
rescue Exception => e
|
58
|
+
# catch parse error
|
59
|
+
puts "\e[31m " + e.message.gsub("ParseError: ", "") + "\n\t" + doc_path + "\e[0m"
|
60
|
+
return PARSE_ERROR_PLACEHOLDER
|
61
|
+
end
|
62
|
+
# save to cache
|
63
|
+
$cache[@expression_hash] = @result
|
64
|
+
# update count of newly generated expressions
|
65
|
+
$count_newly_generated_expressions += 1
|
66
|
+
print_stats
|
67
|
+
return @result
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def print_stats
|
72
|
+
print " LaTeX: " +
|
73
|
+
($count_newly_generated_expressions).to_s +
|
74
|
+
" expressions rendered (" + $cache.size.to_s +
|
75
|
+
" already cached) \r"
|
76
|
+
$stdout.flush
|
77
|
+
end
|
78
|
+
|
79
|
+
Jekyll::Hooks.register :documents, :post_render do |doc|
|
80
|
+
doc.output = convert(doc)
|
81
|
+
end
|
82
|
+
|
83
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
84
|
+
# load macros from config file
|
85
|
+
if site.config["jektex-macros"] != nil
|
86
|
+
for macro_definition in site.config["jektex-macros"]
|
87
|
+
$global_macros[macro_definition[0]] = macro_definition[1]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
# print macro information
|
91
|
+
if $global_macros.size == 0
|
92
|
+
puts " LaTeX: no macros loaded"
|
93
|
+
else
|
94
|
+
puts " LaTeX: " + $global_macros.size.to_s + " macro" +
|
95
|
+
($global_macros.size == 1 ? "" : "s") + " loaded"
|
96
|
+
end
|
97
|
+
|
98
|
+
if site.config["disable_disk_cache"] != nil
|
99
|
+
$disable_disk_cache = site.config["disable_disk_cache"]
|
100
|
+
end
|
101
|
+
|
102
|
+
# load content of cache file if it exists
|
103
|
+
if(File.exist?(PATH_TO_CACHE))
|
104
|
+
$cache = File.open(PATH_TO_CACHE, "r"){|from_file| Marshal.load(from_file)}
|
105
|
+
else
|
106
|
+
$cache = Hash.new
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
Jekyll::Hooks.register :site, :after_reset do
|
111
|
+
# reset count after reset
|
112
|
+
$count_newly_generated_expressions = 0
|
113
|
+
end
|
114
|
+
|
115
|
+
Jekyll::Hooks.register :site, :post_write do
|
116
|
+
# print new line to prevent overwriting previous output
|
117
|
+
print "\n"
|
118
|
+
puts
|
119
|
+
# check if caching is enabled
|
120
|
+
if $disable_disk_cache == false
|
121
|
+
# save cache to disk
|
122
|
+
Dir.mkdir(CACHE_DIR) unless File.exists?(CACHE_DIR)
|
123
|
+
File.open(PATH_TO_CACHE, "w"){|to_file| Marshal.dump($cache, to_file)}
|
124
|
+
end
|
125
|
+
end
|