jekyll-tex-eqn 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/jekyll-tex-eqn.rb +253 -0
  3. metadata +105 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 023fc19029bcf5f081be6279007e70ded05d0f2fb0c8d74f54b02fc326aa8cbb
4
+ data.tar.gz: bd333677c9ece485e5dc4b2eb99cca678a43b3a66af6d61eddc41e767e88e842
5
+ SHA512:
6
+ metadata.gz: fdc10bae69788c276bd9e96d0fdc0d84f10385cbebf87f3331c5e703c7b637920bb7e2444f7681dbeecd02fe71d70c53b0fff52aa2c4a0619d298c76762515ac
7
+ data.tar.gz: f68f67316fa9040ff3cfb312716266a40fe011bc91a5ecd821fc2ff8cbebd360b535d7c5c7d5c3ccdd8ecdfa382d5d82dcaa693932a93d5dce97afed765ffc48
@@ -0,0 +1,253 @@
1
+ # Copyright 2022 krab5
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+ ####
21
+
22
+
23
+ # This sole module contains everything needed to perform the tasks provided by
24
+ # the plug-in. In particular, it contains:
25
+ # - A few utility classes
26
+ # - Liquid tags and blocks
27
+ # It also makes the necessary registration for working with Jekyll
28
+
29
+ require "jekyll"
30
+ require "fileutils"
31
+ require "digest" # Needed for generating unique file names
32
+
33
+ module Jekyll
34
+ # Main module for the plug-in
35
+ module JekyllTexEqn
36
+ # Utility class, with a few constants and class methods, mostly to retrieve
37
+ # options and to report errors
38
+ class Util
39
+ ROOT="texeqn" # YAML tag for this plugin's options
40
+ BACKEND_KEY="backend" # Key for backend configuration
41
+ OPTIONS_KEY="options" # Key for extra options for the backend
42
+ PACKAGES_KEY="packages" # Key for the package list
43
+ EXTRAPACKAGES_KEY="extra_packages" # Key for the extra package list
44
+ TMPDIR_KEY="tmpdir" # Key for the temporary directory
45
+ OUTPUTDIR_KEY="outputdir" # Key for the output directory
46
+ INLINE_CLASS_KEY="inlineclass" # Key for the inline equation class configuration
47
+ BLOCK_CLASS_KEY="blockclass" # Key for the block equation class configuration
48
+ EXTRAHEAD_KEY="extra_head" # Key for the extra header
49
+ INLINE_SCALE_KEY="inline_scale" # Key for setting up image scaling for inline equations
50
+ BLOCK_SCALE_KEY="block_scale" # Key for setting up image scaling for block equations
51
+
52
+ # Default values
53
+ DEFAULT_BACKEND="pdflatex"
54
+ DEFAULT_PACKAGES= [
55
+ {"name": "inputenc", "option": "utf8"},
56
+ {"name": "fontenc", "option": "T1"},
57
+ {"name": "amsmath"},
58
+ {"name": "amssymb"}
59
+ ]
60
+ DEFAULT_TMPDIR="_tmp"
61
+ DEFAULT_OUTPUTDIR="assets/texeqn"
62
+ DEFAULT_INLINE_SCALE="2.4"
63
+ DEFAULT_BLOCK_SCALE="2.4"
64
+
65
+ # Retrieve Jekyll configuration for this plugin
66
+ @@config = Jekyll.configuration({})[ROOT]
67
+
68
+ # Get the value of an option, or the provided default value if that option
69
+ # has not been set
70
+ def self.get_option(key, default)
71
+ v = @@config[key]
72
+ if v.nil? && !default.nil? then
73
+ v = default
74
+ end
75
+ v
76
+ end
77
+
78
+ # Make an error message to be reported
79
+ def self.report(context, msg, cause)
80
+ err = ""
81
+ if !context.nil? then
82
+ thispage = context.registers[:page]['path']
83
+ err = "On #{thispage}: "
84
+ end
85
+ err << msg
86
+ if !cause.nil? && !cause.message.empty? then
87
+ err << ": #{cause.message}"
88
+ end
89
+ err
90
+ end
91
+ end
92
+
93
+ # Tool-class for doing all the SVG generation
94
+ class Generate
95
+ @@basedir = Util.get_option(Util::TMPDIR_KEY, Util::DEFAULT_TMPDIR)
96
+ @@outdir = Util.get_option(Util::OUTPUTDIR_KEY, Util::DEFAULT_OUTPUTDIR)
97
+ @@backend = Util.get_option(Util::BACKEND_KEY, Util::DEFAULT_BACKEND)
98
+ @@options = Util.get_option(Util::OPTIONS_KEY, []).join(" ")
99
+
100
+ # This is the PDF command. The provided options are mandatory for smooth
101
+ # running:
102
+ # * "-halt-on-error" means LaTeX will exit upon error, instead of
103
+ # asking the user to provide a solution
104
+ # * "-interaction nonstopmode" remove any form of interaction with
105
+ # LaTeX
106
+ # * "-file-line-error" put the file name and the line number for errors
107
+ # (better for debugging your code)
108
+ # * "--jobname=output" means the result of LaTeX will be named
109
+ # "output.pdf"; this makes things easier during the process
110
+ @@pdf = "#{@@backend} -halt-on-error -interaction nonstopmode -file-line-error --jobname=output #{@@options}"
111
+
112
+ # Run the set of commands that perform the conversion from TeX to SVG
113
+ # This takes as argument "basedir", the directory where TeX files are
114
+ # located, and "base" the basename of the .tex file to process (more
115
+ # convenient when browsing directory). Also takes "outdir", the directory
116
+ # where to export the SVG, and "pdf", the pdfxx command that performs the
117
+ # TeX => PDF conversion (easier because then this value is calculated only
118
+ # once).
119
+ def self.run_cmd(base)
120
+ text = ""
121
+
122
+ # Cleanup if needed (in case of past error for instanec)
123
+ if !File.exists?("#{@@basedir}/#{base}") then
124
+ Dir.mkdir("#{@@basedir}/#{base}")
125
+ end
126
+
127
+ # Run pdfxx, compile TeX into PDF
128
+ text = %x|#{@@pdf} -output-directory=#{@@basedir}/#{base}/ #{@@basedir}/#{base}.tex 2>&1|
129
+ if $?.exitstatus != 0 then
130
+ raise "[#{base}] Error #{$?.exitstatus} while executing backend:\n#{text}"
131
+ end
132
+
133
+ # Use pdfcrop to crop the PDF
134
+ text = %x|pdfcrop #{@@basedir}/#{base}/output.pdf #{@@basedir}/#{base}/output-crop.pdf 2>&1|
135
+ if $?.exitstatus != 0 then
136
+ raise "[#{base}] Error #{$?.exitstatus} while croping PDF:\n#{text}"
137
+ end
138
+
139
+ # Use pdf2svg to transform the cropped PDF into an SVG
140
+ text = %x|pdf2svg #{@@basedir}/#{base}/output-crop.pdf #{@@outdir}/#{base}.svg 2>&1|
141
+ if $?.exitstatus != 0 then
142
+ raise "[#{base}] Error #{$?.exitstatus} while generating SVG:\n#{text}"
143
+ end
144
+
145
+ end
146
+
147
+ # Build a file name where the code will be stored, based on the "host"
148
+ # file path (i.e. file where the equation is located) and the equation's
149
+ # content.
150
+ # The filename is generated by making sure there are no spaces, and using
151
+ # a hash function on the content to obtain (virtually) unique names.
152
+ def self.filename(filepath, content)
153
+ id = Digest::MD5.hexdigest content
154
+ pagepath = filepath
155
+ pagepath.gsub("/", "_").gsub(" ", "-")
156
+ "#{pagepath}-#{id}"
157
+ end
158
+
159
+ # Generate a tex file from the given content, using the provided equation
160
+ # environment opener and closer (e.g. \[-\], \begin{equation}-\end{equation}, etc.)
161
+ def self.generate_file(context, content, begineqn, endeqn, file)
162
+ text = "\\documentclass{minimal}\n"
163
+ pkgs = Util.get_option(Util::PACKAGES_KEY, Util::DEFAULT_PACKAGES)
164
+ pkgs.concat Util.get_option(Util::EXTRAPACKAGES_KEY, [])
165
+ for p in pkgs do
166
+ text << "\\usepackage"
167
+ if p.include?(:option) then
168
+ text << '[' << (p[:option].nil? ? p['option'] : p[:option]) << ']'
169
+ end
170
+ text << '{' << (p[:name].nil? ? p['name'] : p[:name]) << '}' << "\n"
171
+ end
172
+
173
+ text << Util.get_option(Util::EXTRAHEAD_KEY, "")
174
+ text << "\n\\begin{document}\n"
175
+ text << begineqn
176
+ text << content
177
+ text << endeqn << "\n"
178
+ text << "\\end{document}\n"
179
+
180
+ begin
181
+ File.open(file, 'w') { |f|
182
+ f.write text
183
+ }
184
+ rescue => e
185
+ raise Util.report(context, "error while creating tex file '#{file}'", e)
186
+ end
187
+ end
188
+
189
+ # Perform a full rendering step, i.e. create the Tex file, compile, crop and transform
190
+ # into an SVG
191
+ def self.do_render(context, content, scale, begineqn, endeqn)
192
+ content.strip!
193
+ path = context.registers[:page]['path']
194
+ file = Generate.filename(path, content)
195
+ texfile = "#{@@basedir}/#{file}.tex"
196
+ svgfile = "#{@@outdir}/#{file}.svg"
197
+
198
+ # If the SVG file already exists, no need to re-render it (usually)
199
+ # If the TeX file already exists, this usually mean something went wrong and it is
200
+ # erroneous!
201
+ if !File.exists?(texfile) && !File.exists?(svgfile) then
202
+ Jekyll.logger.info("Generating image file #{file}")
203
+ Generate.generate_file(context, content, begineqn, endeqn, texfile)
204
+ Generate.run_cmd(file)
205
+ File.delete(texfile)
206
+ if File.exists?("#{@@basedir}/#{file}") then
207
+ FileUtils.remove_dir("#{@@basedir}/#{file}")
208
+ end
209
+ end
210
+
211
+ # Retrieve SVG dimensions
212
+ svghead = File.readlines(svgfile)[1]
213
+ width = svghead[/width="(\d+)[a-z]+"/,1]
214
+ height = svghead[/height="(\d+)[a-z]+"/,1]
215
+
216
+ { file: svgfile, width: width.to_f * scale, height: height.to_f * scale } # Dimensions are scaled
217
+ end
218
+ end
219
+
220
+ # Liquid tag for inline equations
221
+ class RenderTexTag < Liquid::Tag
222
+ TAGNAME="ieqn"
223
+
224
+ def initialize(tagname, text, tokens)
225
+ super
226
+ @content = text
227
+ @content.chomp
228
+ end
229
+
230
+ def render(context)
231
+ svg = Generate.do_render(context, @content, Util.get_option(Util::INLINE_SCALE_KEY, Util::DEFAULT_INLINE_SCALE).to_f, "$", "$")
232
+ "<span class='#{Util.get_option(Util::INLINE_CLASS_KEY, "")}'><img width='#{svg[:width]}px' height='#{svg[:height]}px' src='/#{svg[:file]}'/></span>"
233
+ end
234
+ end
235
+
236
+ # Liquid block for block equations
237
+ class RenderTexBlock < Liquid::Block
238
+ TAGNAME="eqn"
239
+
240
+ def render(context)
241
+ content = super
242
+ svg = Generate.do_render(context, content, Util.get_option(Util::BLOCK_SCALE_KEY, Util::DEFAULT_BLOCK_SCALE).to_f, "\\begin{displaymath}\n", "\n\\end{displaymath}")
243
+ "<div class='#{Util.get_option(Util::BLOCK_CLASS_KEY, "")}'><img width='#{svg[:width]}px' height='#{svg[:height]}px' src='/#{svg[:file]}'/></div>"
244
+ end
245
+ end
246
+ end
247
+
248
+ Liquid::Template.register_tag(JekyllTexEqn::RenderTexBlock::TAGNAME, JekyllTexEqn::RenderTexBlock)
249
+ Liquid::Template.register_tag(JekyllTexEqn::RenderTexTag::TAGNAME, JekyllTexEqn::RenderTexTag)
250
+ end
251
+
252
+
253
+
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-tex-eqn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - krab5
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-04-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: digest
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: fileutils
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.4'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.4'
61
+ - !ruby/object:Gem::Dependency
62
+ name: bundler
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.10'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.10'
75
+ description:
76
+ email: crab.delicieux@gmail.com
77
+ executables: []
78
+ extensions: []
79
+ extra_rdoc_files: []
80
+ files:
81
+ - lib/jekyll-tex-eqn.rb
82
+ homepage: https://github.com/krab5/jekyll-tex-eqn
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 2.4.0
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.2.5
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Standalone, static, no-JS, TeX-rendered mathematical equations for Jekyll
105
+ test_files: []