antex 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +10 -0
- data/lib/antex.rb +13 -0
- data/lib/antex/command.rb +93 -0
- data/lib/antex/defaults.yml +73 -0
- data/lib/antex/error.rb +5 -0
- data/lib/antex/job.rb +117 -0
- data/lib/antex/liquid_helpers.rb +49 -0
- data/lib/antex/measurable.rb +65 -0
- data/lib/antex/set_box.rb +81 -0
- data/lib/antex/svg_box.rb +22 -0
- data/lib/antex/tex_box.rb +30 -0
- data/lib/antex/version.rb +5 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9e8f4f3fd672813d3ad879df973fd7f322a74690
|
4
|
+
data.tar.gz: 84e7aa764ab967c2afc9423ce92b03a04e4d5e2e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 586cb677c67b145dd111eaf9056d3bca2b4739c64be9cf7bf42336bcb75ed71f6bab884fd47932bbc79c10a70fa4a823c74f74b0622f12be73041ccd724439bc
|
7
|
+
data.tar.gz: 42335c58867725e0d1959e96b93a2b1a21a867f6943daeea39a39527ab4145c912fd1728d3e4038955224e191d3e7f20948222ec0158c381b94ed4a73c414d9c
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Paolo Brasolin
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# antex
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/paolobrasolin/antex.svg?branch=master)](https://travis-ci.org/paolobrasolin/antex)
|
4
|
+
[![Latest Release](https://img.shields.io/github/release/paolobrasolin/antex.svg)](https://github.com/paolobrasolin/antex)
|
5
|
+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
|
6
|
+
|
7
|
+
[![Code Climate](https://codeclimate.com/github/paolobrasolin/antex/badges/gpa.svg)](https://codeclimate.com/github/paolobrasolin/antex)
|
8
|
+
[![Test Coverage](https://codeclimate.com/github/paolobrasolin/antex/badges/coverage.svg)](https://codeclimate.com/github/paolobrasolin/antex/coverage)
|
9
|
+
[![Inline docs](http://inch-ci.org/github/paolobrasolin/antex.svg?branch=master)](http://inch-ci.org/github/paolobrasolin/antex)
|
10
|
+
[![Issue Count](https://codeclimate.com/github/paolobrasolin/antex/badges/issue_count.svg)](https://codeclimate.com/github/paolobrasolin/antex)
|
data/lib/antex.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'antex/version'
|
4
|
+
require 'antex/error'
|
5
|
+
|
6
|
+
require 'antex/measurable'
|
7
|
+
require 'antex/tex_box'
|
8
|
+
require 'antex/svg_box'
|
9
|
+
require 'antex/set_box'
|
10
|
+
|
11
|
+
require 'antex/liquid_helpers'
|
12
|
+
require 'antex/job'
|
13
|
+
require 'antex/command'
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module Antex
|
6
|
+
# This class encapsulates the execution of a command line that needs
|
7
|
+
# some source files to produce some target files.
|
8
|
+
#
|
9
|
+
# Existence checks are performed on files:
|
10
|
+
# * if all targets exist before execution then it is skipped
|
11
|
+
# * all sources must exist before execution
|
12
|
+
# * all targets must exist after execution
|
13
|
+
#
|
14
|
+
# Details of the execution itself are given by
|
15
|
+
# the attributes {#stdout}, {#stderr} and {#status}.
|
16
|
+
class Command
|
17
|
+
class MissingSourceFiles < Error; end
|
18
|
+
class MissingTargetFiles < Error; end
|
19
|
+
class ExecutionFailed < Error; end
|
20
|
+
|
21
|
+
# @return [String, nil] the +stdout+ returned by the command line
|
22
|
+
attr_reader :stdout
|
23
|
+
|
24
|
+
# @return [String, nil] the +stderr+ returned by the command line
|
25
|
+
attr_reader :stderr
|
26
|
+
|
27
|
+
# @return [Process::Status, nil] the +status+ returned by the command line
|
28
|
+
attr_reader :status
|
29
|
+
|
30
|
+
# Initializes the command.
|
31
|
+
#
|
32
|
+
# @note A command with no targets will always skip
|
33
|
+
# since it needs to produce nothing!
|
34
|
+
#
|
35
|
+
# @param name [String] name of the command (just for error reporting)
|
36
|
+
# @param sources [Array] list of source files
|
37
|
+
# @param targets [Array] list of target files
|
38
|
+
# @param command_line [String] command line that will be executed
|
39
|
+
def initialize(name:, sources:, targets:, command_line:)
|
40
|
+
@name = name
|
41
|
+
@sources = sources
|
42
|
+
@targets = targets
|
43
|
+
@command_line = command_line
|
44
|
+
end
|
45
|
+
|
46
|
+
# Executes the command.
|
47
|
+
#
|
48
|
+
# @raise [MissingSourceFiles] when source files are missing
|
49
|
+
# @raise [ExecutionFailed] when command execution fails
|
50
|
+
# @raise [MissingTargetFiles] when command does not create target files
|
51
|
+
def run!
|
52
|
+
return if all_exist? @targets
|
53
|
+
check_source_files!
|
54
|
+
@stdout, @stderr, @status = Open3.capture3 @command_line
|
55
|
+
check_status!
|
56
|
+
check_target_files!
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def all_exist?(files)
|
62
|
+
files.all?(&File.method(:exist?))
|
63
|
+
end
|
64
|
+
|
65
|
+
def missing(files)
|
66
|
+
files.reject(&File.method(:exist?))
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_source_files!
|
70
|
+
raise MissingSourceFiles, <<~MISSING_SOURCE unless all_exist? @sources
|
71
|
+
Required source files #{missing @sources} for command #{@name} are missing.
|
72
|
+
MISSING_SOURCE
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_status!
|
76
|
+
raise ExecutionFailed, <<~EXECUTION_FAILED unless @status.success?
|
77
|
+
Command #{@name} failed.
|
78
|
+
Command line: #{@command_line}
|
79
|
+
Status: #{@status}
|
80
|
+
Stdout:
|
81
|
+
#{@stdout}
|
82
|
+
Stderr:
|
83
|
+
#{@stderr}
|
84
|
+
EXECUTION_FAILED
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_target_files!
|
88
|
+
raise MissingTargetFiles, <<~MISSING_TARGET unless all_exist? @targets
|
89
|
+
Expected target files #{missing @targets} were not produced by command #{@name}.
|
90
|
+
MISSING_TARGET
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
template: |
|
2
|
+
\documentclass{article}
|
3
|
+
\pagestyle{empty}
|
4
|
+
{{ preamble }}
|
5
|
+
\newsavebox\snippet
|
6
|
+
\begin{lrbox}{\snippet}{{ prepend }}{{ snippet }}{{ append }}\end{lrbox}
|
7
|
+
\newwrite\file
|
8
|
+
\immediate\openout\file=\jobname.yml
|
9
|
+
\immediate\write\file{em: \the\dimexpr1em}
|
10
|
+
\immediate\write\file{ex: \the\dimexpr1ex}
|
11
|
+
\immediate\write\file{ht: \the\ht\snippet}
|
12
|
+
\immediate\write\file{dp: \the\dp\snippet}
|
13
|
+
\immediate\write\file{wd: \the\wd\snippet}
|
14
|
+
\closeout\file
|
15
|
+
\begin{document}\usebox{\snippet}\end{document}
|
16
|
+
preamble:
|
17
|
+
prepend:
|
18
|
+
append:
|
19
|
+
dirs:
|
20
|
+
work: .antex-cache
|
21
|
+
files:
|
22
|
+
tex: "{{ dir.work }}/{{ hash }}.tex"
|
23
|
+
dvi: "{{ dir.work }}/{{ hash }}.dvi"
|
24
|
+
yml: "{{ dir.work }}/{{ hash }}.yml"
|
25
|
+
tfm: "{{ dir.work }}/{{ hash }}.tfm.svg"
|
26
|
+
fit: "{{ dir.work }}/{{ hash }}.fit.svg"
|
27
|
+
svg: "{{ dir.work }}/{{ hash }}.svg"
|
28
|
+
pipeline:
|
29
|
+
- latexmk
|
30
|
+
- dvisvgm_tfm
|
31
|
+
- dvisvgm_fit
|
32
|
+
- rename
|
33
|
+
commands:
|
34
|
+
latexmk:
|
35
|
+
command:
|
36
|
+
- "latexmk"
|
37
|
+
- "-output-directory={{ dir.work }}"
|
38
|
+
- "{{ file.tex }}"
|
39
|
+
sources:
|
40
|
+
- "{{ file.tex }}"
|
41
|
+
targets:
|
42
|
+
- "{{ file.dvi }}"
|
43
|
+
- "{{ file.yml }}"
|
44
|
+
dvisvgm_tfm:
|
45
|
+
command:
|
46
|
+
- "dvisvgm"
|
47
|
+
- "--no-fonts"
|
48
|
+
- "{{ file.dvi }}"
|
49
|
+
- "--output={{ file.tfm }}"
|
50
|
+
sources:
|
51
|
+
- "{{ file.dvi }}"
|
52
|
+
targets:
|
53
|
+
- "{{ file.tfm }}"
|
54
|
+
dvisvgm_fit:
|
55
|
+
command:
|
56
|
+
- "dvisvgm"
|
57
|
+
- "--no-fonts"
|
58
|
+
- "--exact"
|
59
|
+
- "{{ file.dvi }}"
|
60
|
+
- "--output={{ file.fit }}"
|
61
|
+
sources:
|
62
|
+
- "{{ file.dvi }}"
|
63
|
+
targets:
|
64
|
+
- "{{ file.fit }}"
|
65
|
+
rename:
|
66
|
+
command:
|
67
|
+
- "cp"
|
68
|
+
- "{{ file.fit }}"
|
69
|
+
- "{{ file.svg }}"
|
70
|
+
sources:
|
71
|
+
- "{{ file.fit }}"
|
72
|
+
targets:
|
73
|
+
- "{{ file.svg }}"
|
data/lib/antex/error.rb
ADDED
data/lib/antex/job.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Antex
|
4
|
+
# This class is the beating heart of {Antex}.
|
5
|
+
#
|
6
|
+
# The simplest usage of this gem involves three steps:
|
7
|
+
# # 1. Get a LaTeX snippet and initialize a job.
|
8
|
+
# job = Antex::Job.new snippet: "Hello, \TeX world!"
|
9
|
+
# # 2. Run the job!
|
10
|
+
# job.run!
|
11
|
+
# # 3. Do something neat with your new SVG file and its typesetting metrics.
|
12
|
+
# job.files(:svg) # => "./.antex-cache/eb64793dafe3bbd963dc663385a22096.svg"
|
13
|
+
# job.set_box.measures # => {:ex=>1, :wd=>17.03..., :mt=>-0.05..., ...}
|
14
|
+
#
|
15
|
+
# The {Antex::Job::DEFAULTS} are tuned to work with a basic
|
16
|
+
# +latexmk+/+dvisvgm+ pipeline.
|
17
|
+
# To get started with customization. you'll want to read the (not yet well
|
18
|
+
# documented) +YAML+ source loaded into the constant at the
|
19
|
+
# {https://github.com/paolobrasolin/antex/blob/master/lib/antex/defaults.yml
|
20
|
+
# github repo}.
|
21
|
+
class Job
|
22
|
+
include LiquidHelpers
|
23
|
+
|
24
|
+
# The configuration defaults for a +latexmk+/+dvisvgm+ pipeline.
|
25
|
+
DEFAULTS =
|
26
|
+
YAML.load_file(File.join(File.dirname(__FILE__), 'defaults.yml')).freeze
|
27
|
+
|
28
|
+
# @return [Hash] the initialization options
|
29
|
+
attr_reader :options
|
30
|
+
|
31
|
+
# @return [Hash] the dirs paths rendered from the options
|
32
|
+
attr_reader :dirs
|
33
|
+
|
34
|
+
# @return [Hash] the files paths rendered from the options
|
35
|
+
attr_reader :files
|
36
|
+
|
37
|
+
# @return [String] the unique hash identifying the job
|
38
|
+
attr_reader :hash
|
39
|
+
|
40
|
+
# @return [SetBox, nil] the {SetBox} calculated by the {#run!}
|
41
|
+
attr_reader :set_box
|
42
|
+
|
43
|
+
# @param snippet [String] +TeX+ code snippet to render
|
44
|
+
# @param options [Hash] job options
|
45
|
+
def initialize(snippet: '', options: Antex::Job::DEFAULTS)
|
46
|
+
@options = options
|
47
|
+
@snippet = snippet
|
48
|
+
|
49
|
+
prepare_code
|
50
|
+
prepare_hash
|
51
|
+
prepare_dirs
|
52
|
+
prepare_files
|
53
|
+
end
|
54
|
+
|
55
|
+
# Run the job, compile the +TeX+ snippet and calculate the {#set_box}.
|
56
|
+
def run!
|
57
|
+
create_dirs
|
58
|
+
write_code
|
59
|
+
run_pipeline!
|
60
|
+
load_set_box
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def prepare_code
|
66
|
+
@code = liquid_render @options['template'],
|
67
|
+
'preamble' => @options['preamble'],
|
68
|
+
'append' => @options['append'],
|
69
|
+
'prepend' => @options['prepend'],
|
70
|
+
'snippet' => @snippet
|
71
|
+
end
|
72
|
+
|
73
|
+
def prepare_hash
|
74
|
+
@hash = Digest::MD5.hexdigest @code
|
75
|
+
end
|
76
|
+
|
77
|
+
def prepare_dirs
|
78
|
+
@dirs = liquid_render @options['dirs'],
|
79
|
+
'hash' => @hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def prepare_files
|
83
|
+
@files = liquid_render @options['files'],
|
84
|
+
'hash' => @hash,
|
85
|
+
'dir' => @dirs
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_dirs
|
89
|
+
@dirs.each_value { |path| FileUtils.mkdir_p path }
|
90
|
+
end
|
91
|
+
|
92
|
+
def write_code
|
93
|
+
return if File.exist? @files['tex']
|
94
|
+
File.open(@files['tex'], 'w') { |io| io.write @code }
|
95
|
+
end
|
96
|
+
|
97
|
+
def run_pipeline!
|
98
|
+
context = { 'hash' => @hash, 'dir' => @dirs, 'file' => @files }
|
99
|
+
pipeline = liquid_render @options['pipeline'], context
|
100
|
+
commands = liquid_render @options['commands'], context
|
101
|
+
|
102
|
+
pipeline.each do |command_name|
|
103
|
+
options = { name: command_name,
|
104
|
+
sources: commands[command_name]['sources'],
|
105
|
+
targets: commands[command_name]['targets'],
|
106
|
+
command_line: commands[command_name]['command'].join(' ') }
|
107
|
+
Command.new(options).run!
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def load_set_box
|
112
|
+
@set_box = SetBox.new.load yml: @files['yml'],
|
113
|
+
tfm: @files['tfm'],
|
114
|
+
fit: @files['fit']
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'liquid'
|
4
|
+
|
5
|
+
module Antex
|
6
|
+
# Exposes helper methods to simplify +Liquid+ templates rendering.
|
7
|
+
module LiquidHelpers
|
8
|
+
class UnknownClass < Error; end
|
9
|
+
|
10
|
+
# Recursively renders +Liquid+ template strings, possibly organized in
|
11
|
+
# nested arrays and hashes, using the given hash of contextual variables.
|
12
|
+
#
|
13
|
+
# @param object [String, Array, Hash] the object to render
|
14
|
+
# @param context_hash [Hash]
|
15
|
+
# the context hash accessible from the object strings
|
16
|
+
# @return [String] the rendered object
|
17
|
+
# @raise [UnknownClass] when given anything that's not renderable
|
18
|
+
def liquid_render(object, context_hash = {})
|
19
|
+
case object
|
20
|
+
when String
|
21
|
+
liquid_render_string object, context_hash
|
22
|
+
when Array
|
23
|
+
liquid_render_array object, context_hash
|
24
|
+
when Hash
|
25
|
+
liquid_render_hash object, context_hash
|
26
|
+
else
|
27
|
+
raise UnknownClass, "I don't know how to render a #{object.class}."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def liquid_render_string(string, context_hash = {})
|
34
|
+
Liquid::Template.parse(string).render(context_hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
def liquid_render_array(array, context_hash = {})
|
38
|
+
array.map do |element|
|
39
|
+
liquid_render element, context_hash
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def liquid_render_hash(hash, context_hash = {})
|
44
|
+
hash.map do |key, value|
|
45
|
+
[key, liquid_render(value, context_hash)]
|
46
|
+
end.to_h
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Antex
|
4
|
+
# Implements an easy to use container for storing measures
|
5
|
+
# and retrieving them performing unit conversions.
|
6
|
+
#
|
7
|
+
# Measures should be a hash with symbolic keys
|
8
|
+
# and numeric values with uniform units.
|
9
|
+
# mea = Measurable.new
|
10
|
+
# mea.measures = { km: 1, mi: 0.621 }
|
11
|
+
class Measurable
|
12
|
+
class InvalidUnit < Antex::Error; end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@default_unit = nil
|
16
|
+
@measures = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Symbol] the default unit
|
20
|
+
attr_reader :default_unit
|
21
|
+
|
22
|
+
def default_unit=(unit)
|
23
|
+
raise InvalidUnit, "Unit #{unit} is undefined." unless @measures.key? unit
|
24
|
+
raise InvalidUnit, "Unit #{unit} is zero." if @measures[unit].zero?
|
25
|
+
@default_unit = unit
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Hash] the stored measures
|
29
|
+
# @note When setting, the default unit will be set to +nil+.
|
30
|
+
attr_reader :measures
|
31
|
+
|
32
|
+
def measures=(**measures)
|
33
|
+
@default_unit = nil
|
34
|
+
@measures = measures
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def calculate(metric, unit = @default_unit)
|
40
|
+
raise InvalidUnit, 'Default unit is not set.' if unit.nil?
|
41
|
+
raise InvalidUnit, "Unit #{unit} is undefined." unless @measures.key? unit
|
42
|
+
raise InvalidUnit, "Unit #{unit} is zero." if @measures[unit].zero?
|
43
|
+
@measures[metric].fdiv @measures[unit]
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to_missing?(method_name, include_private = false)
|
47
|
+
(@measures.include? method_name) || super
|
48
|
+
end
|
49
|
+
|
50
|
+
# Once measures are set they can be accessed and converted.
|
51
|
+
# mea.km(:km) # => 1.0
|
52
|
+
# mea.km(:mi) # => 1.610...
|
53
|
+
# mea.mi(:mi) # => 1.0
|
54
|
+
# mea.mi(:km) # => 0.621
|
55
|
+
#
|
56
|
+
# You can set a default unit for quicker access.
|
57
|
+
# mea.default_unit = :mi
|
58
|
+
# mea.km # => 1.610...
|
59
|
+
# mea.mi # => 1.0
|
60
|
+
def method_missing(method_name, *arguments, &block)
|
61
|
+
return super unless @measures.include? method_name
|
62
|
+
send :calculate, method_name, *arguments, &block
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Antex
|
4
|
+
# Encapsulates calculations and results for perfectly typesetting
|
5
|
+
# the +SVG+ vectorial conversion of a +TeX+ box.
|
6
|
+
#
|
7
|
+
# Three files are required by {#load} for initialization:
|
8
|
+
# [+YAML+ file containing +TeX+ measures]
|
9
|
+
# We obtain this by using +TeX+ directly
|
10
|
+
# to measure the box and write to file.
|
11
|
+
# Required metrics are +ht+, +dp+ and +wd+ (naturally expressed in +pt+s).
|
12
|
+
# [+SVG+ picture fitting +tfm+ metrics]
|
13
|
+
# We obtain this by converting the +DVI+ rendition of the box
|
14
|
+
# to +SVG+ using {http://dvisvgm.bplaced.net/ +dvisvgm+}.
|
15
|
+
# [+SVG+ picture fitting the ink]
|
16
|
+
# We obtain this by converting the +DVI+ rendition of the box
|
17
|
+
# to +SVG+ using {http://dvisvgm.bplaced.net/ +dvisvgm+}
|
18
|
+
# with the +--exact+ flag.
|
19
|
+
#
|
20
|
+
# After the initialization the calculations are performed immediatly.
|
21
|
+
# Six metrics become available:
|
22
|
+
# * +mt+, +mr+, +ml+ and +ml+ (the margins, positive or negative)
|
23
|
+
# * +wd+ and +ht+ (width and height)
|
24
|
+
# * +ex+ (the default unit)
|
25
|
+
class SetBox < Measurable
|
26
|
+
class InvalidMeasure < Antex::Error; end
|
27
|
+
|
28
|
+
# @param yml [String] path of the +YAML+ file containing +TeX+ measures
|
29
|
+
# @param tfm [String] path of the +SVG+ picture fitting +tfm+ metrics
|
30
|
+
# @param fit [String] path of the +SVG+ picture fitting the ink
|
31
|
+
# @return [TexBox] returns +self+ after loading
|
32
|
+
def load(yml:, tfm:, fit:)
|
33
|
+
@tex = TexBox.new.load yml
|
34
|
+
@tfm = SVGBox.new.load tfm
|
35
|
+
@fit = SVGBox.new.load fit
|
36
|
+
@tex.default_unit = :ex
|
37
|
+
check_measures!
|
38
|
+
self.measures = compute_measures
|
39
|
+
self.default_unit = :ex
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Memorandum:
|
46
|
+
# .--> x ml fit.dx mr
|
47
|
+
# | |--|-------------|--|
|
48
|
+
# v y
|
49
|
+
# - ,-------------------. -
|
50
|
+
# mt | | TFM | |
|
51
|
+
# - | ,-------------. | | tfm.dy
|
52
|
+
# | | | FIT | | |
|
53
|
+
# | | | | | |
|
54
|
+
# fit.dy | | | | | | r ,-------. - tex.ht
|
55
|
+
# | | | | | | <---> | TEX | |
|
56
|
+
# | |- |- - - - - - -| -| | |- - - -| -
|
57
|
+
# - | `-------------' | | `-------' - tex.dp
|
58
|
+
# mb | | | | |-------|
|
59
|
+
# - `-------------------' - tex.wd
|
60
|
+
# |-------------------|
|
61
|
+
# tfm.dx
|
62
|
+
def compute_measures # rubocop:disable Metrics/AbcSize
|
63
|
+
ex_px = (@tex.ht + @tex.dp) / @tfm.dy # [ex/px]
|
64
|
+
{
|
65
|
+
ex: 1,
|
66
|
+
th: ex_px * @fit.dy,
|
67
|
+
wd: ex_px * @fit.dx,
|
68
|
+
ml: ex_px * (- @tfm.ox + @fit.ox),
|
69
|
+
mt: ex_px * (- @tfm.oy + @fit.oy),
|
70
|
+
mr: ex_px * (+ @tfm.ox - @fit.ox + @tfm.dx - @fit.dx),
|
71
|
+
mb: ex_px * (+ @tfm.oy - @fit.oy + @tfm.dy - @fit.dy) - @tex.dp
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_measures!
|
76
|
+
raise InvalidMeasure, <<~INVALID_MEASURE if @tfm.dy.zero?
|
77
|
+
The given tfm SVG file has zero height.
|
78
|
+
INVALID_MEASURE
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module Antex
|
6
|
+
# Loads and manages measures for +SVG+ pictures.
|
7
|
+
class SVGBox < Measurable
|
8
|
+
# Loads an +SVG+ file and extracts the measures of its +viewBox+.
|
9
|
+
#
|
10
|
+
# @param filepath [String] the path of the SVG file to load
|
11
|
+
# @return [SVGBox] returns +self+ after loading
|
12
|
+
def load(filepath)
|
13
|
+
svg_ast = Nokogiri::XML.parse File.read(filepath)
|
14
|
+
view_box = svg_ast.css('svg').attribute('viewBox')
|
15
|
+
magnitudes = view_box.to_s.split(' ').map(&:to_f)
|
16
|
+
@measures = %i[ox oy dx dy].zip(magnitudes).to_h
|
17
|
+
@measures[:px] ||= 1.0
|
18
|
+
@default_unit = :px
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Antex
|
6
|
+
# Loads and manages measures for +TeX+ boxes.
|
7
|
+
class TexBox < Measurable
|
8
|
+
# Loads a +YAML+ file containing +TeX+ measures.
|
9
|
+
#
|
10
|
+
# The expected input is a hash of named +pt+ lenghts.
|
11
|
+
# E.g.:
|
12
|
+
#
|
13
|
+
# pt: 1.0pt
|
14
|
+
# wd: 5.0pt
|
15
|
+
# ht: 8.0pt
|
16
|
+
# dp: 2.0pt
|
17
|
+
#
|
18
|
+
# @param filepath [String] the path of the YAML file to load
|
19
|
+
# @return [TexBox] returns +self+ after loading
|
20
|
+
def load(filepath)
|
21
|
+
yaml_hash = YAML.safe_load File.read(filepath)
|
22
|
+
units = yaml_hash.keys.map(&:to_sym)
|
23
|
+
magnitudes = yaml_hash.values.map { |value| value.chomp('pt').to_f }
|
24
|
+
@measures = units.zip(magnitudes).to_h
|
25
|
+
@measures[:pt] ||= 1.0
|
26
|
+
@default_unit = :pt
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: antex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paolo Brasolin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: liquid
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: guard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.15'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.15'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.50'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.50'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: yard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.9'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.9'
|
125
|
+
description: 'anTeX implements a universal TeX integration pipeline to easily embed
|
126
|
+
and render arbitrary code using any engine and dialect. '
|
127
|
+
email: paolo.brasolin@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- LICENSE.txt
|
133
|
+
- README.md
|
134
|
+
- lib/antex.rb
|
135
|
+
- lib/antex/command.rb
|
136
|
+
- lib/antex/defaults.yml
|
137
|
+
- lib/antex/error.rb
|
138
|
+
- lib/antex/job.rb
|
139
|
+
- lib/antex/liquid_helpers.rb
|
140
|
+
- lib/antex/measurable.rb
|
141
|
+
- lib/antex/set_box.rb
|
142
|
+
- lib/antex/svg_box.rb
|
143
|
+
- lib/antex/tex_box.rb
|
144
|
+
- lib/antex/version.rb
|
145
|
+
homepage: https://github.com/paolobrasolin/antex
|
146
|
+
licenses:
|
147
|
+
- MIT
|
148
|
+
metadata: {}
|
149
|
+
post_install_message:
|
150
|
+
rdoc_options: []
|
151
|
+
require_paths:
|
152
|
+
- lib
|
153
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '2.3'
|
158
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
requirements: []
|
164
|
+
rubyforge_project:
|
165
|
+
rubygems_version: 2.6.13
|
166
|
+
signing_key:
|
167
|
+
specification_version: 4
|
168
|
+
summary: Universal TeX integrator
|
169
|
+
test_files: []
|