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.
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