jektex 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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