zotica 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 768244b2c952c5326c6a245d54e51b6103f8a5e6d072f2155cc76cc49f652176
4
+ data.tar.gz: ef29e149f6fe8ddf376e9eff1f0da778fd17fa572860bb53af7e4c4388992ece
5
+ SHA512:
6
+ metadata.gz: 4b08b255e685e21aa9f401538a2997990672f80d8757f712efe65a0507923019e3af89b6b015aa69f3e5caffb2b5df2655c51ef63429a54da18cf4be5dd010e3
7
+ data.tar.gz: b807e3c24c149a0984fd5ad8eab7cbba49897b79c54eedb845a6afd57f90271403ba98d49d7ce2a8ae8254ec0ef66ad8f53681d185a5a8b8617ca2c71b432fa7
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+
5
+ require 'optparse'
6
+ require 'zotica'
7
+ include Zenithal
8
+
9
+
10
+ class Executor
11
+
12
+ def initialize(args)
13
+ @mode, @options, @paths = nil, {}, []
14
+ parser = OptionParser.new
15
+ parser.on("-o PATH") do |value|
16
+ @options[:output_path] = value
17
+ end
18
+ parser.on("-m NAME") do |value|
19
+ @options[:math_macro_name] = value
20
+ end
21
+ parser.on("-r NAME") do |value|
22
+ @options[:raw_macro_name] = value
23
+ end
24
+ parser.on("-c NAME") do |value|
25
+ @options[:resource_macro_name] = value
26
+ end
27
+ parser.on("-f PATH") do |value|
28
+ @options[:font_path] = value
29
+ end
30
+ parser.on("-z") do
31
+ @mode = nil
32
+ end
33
+ parser.on("-l") do
34
+ @mode = :light
35
+ end
36
+ parser.on("-j") do
37
+ @mode = :script
38
+ end
39
+ parser.on("-s") do
40
+ @mode = :style
41
+ end
42
+ @paths = parser.parse(args)
43
+ end
44
+
45
+ def execute
46
+ case @mode
47
+ when :light
48
+ convert(true)
49
+ when :script, :style
50
+ output_resource
51
+ else
52
+ convert(false)
53
+ end
54
+ end
55
+
56
+ def convert(only_math = false)
57
+ unless @paths.empty?
58
+ source = File.read(@paths.first)
59
+ else
60
+ source = STDIN.read
61
+ end
62
+ if only_math
63
+ parser = ZoticaSingleParser.new(source)
64
+ else
65
+ parser = ZoticaParser.new(source)
66
+ parser.register_simple_math_macro(@options[:math_macro_name] || "m")
67
+ parser.register_raw_macro(@options[:raw_macro_name] || "raw")
68
+ parser.register_resource_macro(@options[:resource_macro_name] || "math-resource")
69
+ end
70
+ parser.load_font(@options[:font_path])
71
+ document = parser.run
72
+ converter = ZenithalConverter.simple_html(document)
73
+ output = converter.convert
74
+ if @options[:output_path]
75
+ File.write(@options[:output_path], output)
76
+ else
77
+ STDOUT.print(output)
78
+ end
79
+ end
80
+
81
+ def output_resource
82
+ case @mode
83
+ when :script
84
+ output = ZoticaBuilder.create_script_string
85
+ when :style
86
+ font_url = @options[:font_path] || "font.otf"
87
+ output = ZoticaBuilder.create_style_string(font_url)
88
+ end
89
+ if @options[:output_path]
90
+ File.write(@options[:output_path], output)
91
+ else
92
+ STDOUT.print(output)
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+ executor = Executor.new(ARGV)
99
+ executor.execute
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+
5
+ require 'optparse'
6
+ require 'zotica'
7
+ include Zenithal
8
+
9
+
10
+ class Executor
11
+
12
+ def initialize(args)
13
+ @mode, @options, @paths = nil, {}, []
14
+ parser = OptionParser.new
15
+ parser.on("-o PATH") do |value|
16
+ @options[:output_path] = value
17
+ end
18
+ parser.on("-a ASCENT", Numeric) do |value|
19
+ @options[:ascent] = value.to_f
20
+ end
21
+ parser.on("-d DESCENT", Numeric) do |value|
22
+ @options[:descent] = value.to_f
23
+ end
24
+ @paths = parser.parse(args)
25
+ end
26
+
27
+ def execute
28
+ case @mode
29
+ when :other
30
+ else
31
+ create_font
32
+ end
33
+ end
34
+
35
+ def create_font
36
+ metrics = {}
37
+ metrics[:ascent] = @options[:ascent] || 1638
38
+ metrics[:descent] = @options[:descent] || 410
39
+ metrics[:em] = metrics[:ascent] + metrics[:descent]
40
+ output = ZoticaBuilder.create_font_string(:main, @paths.first, metrics)
41
+ if @options[:output_path]
42
+ File.write(@options[:output_path], output)
43
+ else
44
+ STDOUT.print(output)
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+
51
+ executor = Executor.new(ARGV)
52
+ executor.execute
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+
3
+
4
+ require 'zenml'
5
+
6
+
7
+ module Zenithal
8
+
9
+ ZOTICA_VERSION = "1.0.0"
10
+
11
+ require_relative 'zotica/builder'
12
+ require_relative 'zotica/parser'
13
+
14
+ end
@@ -0,0 +1,979 @@
1
+ # coding: utf-8
2
+
3
+
4
+ require 'json'
5
+ require 'pp'
6
+ require 'rexml/document'
7
+ require 'sassc'
8
+ require 'ttfunk'
9
+ include REXML
10
+
11
+
12
+ module ZoticaBuilder
13
+
14
+ DATA_PATH = "resource/math.json"
15
+ COMMON_STYLE_PATH = "resource/style/math.scss"
16
+ SPECIALIZED_STYLE_PATH = "resource/style/times.scss"
17
+ DEFAULT_TTF_PATHS = {:main => "resource/font/main.ttf", :math => "resource/font/math.ttf"}
18
+ DEFAULT_FONT_PATHS = {:main => "resource/font/main.json", :math => "resource/font/math.json"}
19
+ SCRIPT_DIR = "resource/script"
20
+
21
+ CODEPOINTS = {
22
+ :main => [
23
+ 0x0..0x2AF, 0x370..0x52F,
24
+ 0x2100..0x214F, 0x2200..0x22FF
25
+ ],
26
+ :math => [
27
+ 0x0..0x2AF,
28
+ 0x2100..0x214F, 0x2190..0x21FF, 0x2200..0x22FF, 0x2300..0x23FF, 0x27C0..0x27EF, 0x27F0..0x27FF, 0x2900..0x297F, 0x2980..0x29FF, 0x2A00..0x2AFF, 0x2B00..0x2BFF,
29
+ 0x1D400..0x1D7FF, 0xF0000..0xF05FF, 0xF1000..0xF10FF
30
+ ]
31
+ }
32
+ ROLES = ["bin", "rel", "sbin", "srel", "del", "fun", "not", "ord", "lpar", "rpar", "cpar"]
33
+ ALIGNS = {"c" => "center", "l" => "left", "r" => "right"}
34
+
35
+ module_function
36
+
37
+ def apply_options(nodes, options)
38
+ nodes.each do |node|
39
+ if node.is_a?(Element) && options[:role]
40
+ classes = node["class"].split(" ") - ROLES
41
+ classes << options[:role]
42
+ node["class"] = classes.join(" ")
43
+ end
44
+ if options[:class]
45
+ node["class"] = (node["class"].split(" ") + options[:class].split(" ")).join(" ")
46
+ end
47
+ end
48
+ end
49
+
50
+ def inherit_role(target_element, source_element)
51
+ inner_elements = source_element.elements.to_a
52
+ if inner_elements.size == 1
53
+ inner_element = inner_elements.first
54
+ inner_role = (inner_element["class"].split(" ") & ROLES).first
55
+ if inner_role
56
+ target_classes = target_element["class"].split(" ")
57
+ if (target_classes & ROLES).empty?
58
+ target_element["class"] = [*target_classes, inner_role].join(" ")
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def build_number(text, options = {})
65
+ this = Nodes[]
66
+ element = nil
67
+ this << Element.build("math-n") do |this|
68
+ this << ~text
69
+ element = this
70
+ end
71
+ apply_options(this, options)
72
+ modify_vertical_margins(element, "main", options)
73
+ return this
74
+ end
75
+
76
+ def fetch_identifier_char(kind)
77
+ char = DATA["identifier"].dig(kind) || ""
78
+ return char
79
+ end
80
+
81
+ def fetch_alternative_identifier_text(kind, raw_text)
82
+ text = raw_text.chars.map{|s| DATA.dig("alternative", kind, s) || ""}.join
83
+ return text
84
+ end
85
+
86
+ def build_identifier(text, types, options = {})
87
+ this = Nodes[]
88
+ element = nil
89
+ font_type = (types.include?("alt")) ? "math" : "main"
90
+ this << Element.build("math-i") do |this|
91
+ this["class"] = types.join(" ")
92
+ this["data-cont"] = text
93
+ this << ~text
94
+ element = this
95
+ end
96
+ apply_options(this, options)
97
+ modify_vertical_margins(element, font_type, options)
98
+ return this
99
+ end
100
+
101
+ def fetch_operator_symbol(kind)
102
+ symbol, types = DATA.dig("operator", kind) || ["", ["bin"]]
103
+ return symbol, types
104
+ end
105
+
106
+ def build_operator(symbol, types, options = {}, &block)
107
+ this = Nodes[]
108
+ element = nil
109
+ font_type = (types.include?("txt")) ? "main" : "math"
110
+ this << Element.build("math-o") do |this|
111
+ this["class"] = types.join(" ")
112
+ this << ~symbol
113
+ element = this
114
+ end
115
+ apply_options(this, options)
116
+ modify_vertical_margins(element, font_type, options)
117
+ return this
118
+ end
119
+
120
+ def modify_vertical_margins(element, font_type, options)
121
+ text = element.inner_text
122
+ max_top_margin, max_bottom_margin = -2, -2
123
+ text.each_char do |char|
124
+ codepoint = char.unpack1("U*")
125
+ bottom_margin, top_margin = options[:fonts]&.dig(font_type.intern, codepoint.to_s) || DEFAULT_FONTS.dig(font_type.intern, codepoint.to_s) || [0, 0]
126
+ if top_margin > max_top_margin
127
+ max_top_margin = top_margin
128
+ end
129
+ if bottom_margin > max_bottom_margin
130
+ max_bottom_margin = bottom_margin
131
+ end
132
+ end
133
+ element["style"] += "line-height: 1; "
134
+ element["style"] += "margin-top: #{max_top_margin}em; "
135
+ element["style"] += "margin-bottom: #{max_bottom_margin}em; "
136
+ end
137
+
138
+ def build_strut(type, options = {})
139
+ this = Nodes[]
140
+ this << Element.build("math-strut") do |this|
141
+ if type == "upper" || type == "dupper"
142
+ this["style"] += "margin-bottom: -0.5em;"
143
+ elsif type == "dlower" || type == "dfull"
144
+ this["style"] += "margin-bottom: -0em;"
145
+ else
146
+ bottom_margin = options[:fonts]&.dig(:main, "72", 0) || DEFAULT_FONTS.dig(:main, "72", 0)
147
+ this["style"] += "margin-bottom: #{bottom_margin}em;"
148
+ end
149
+ if type == "lower" || type == "dlower"
150
+ this["style"] += "margin-top: -0.5em;"
151
+ elsif type == "dupper" || type == "dfull"
152
+ this["style"] += "margin-top: -0em;"
153
+ else
154
+ top_margin = options[:fonts]&.dig(:main, "72", 1) || DEFAULT_FONTS.dig(:main, "72", 1)
155
+ this["style"] += "margin-top: #{top_margin}em;"
156
+ end
157
+ this << Element.build("math-text") do |this|
158
+ this["style"] += "line-height: 1;"
159
+ this << ~" "
160
+ end
161
+ end
162
+ apply_options(this, options)
163
+ return this
164
+ end
165
+
166
+ def build_subsuper(options = {}, &block)
167
+ this = Nodes[]
168
+ base_element, sub_element, super_element, left_sub_element, left_super_element = nil
169
+ main_element = nil
170
+ this << Element.build("math-subsup") do |this|
171
+ main_element = this
172
+ this << Element.build("math-lsub") do |this|
173
+ left_sub_element = this
174
+ end
175
+ this << Element.build("math-lsup") do |this|
176
+ left_super_element = this
177
+ end
178
+ this << Element.build("math-base") do |this|
179
+ base_element = this
180
+ end
181
+ this << Element.build("math-sub") do |this|
182
+ sub_element = this
183
+ end
184
+ this << Element.build("math-sup") do |this|
185
+ super_element = this
186
+ end
187
+ end
188
+ apply_options(this, options)
189
+ block&.call(base_element, sub_element, super_element, left_sub_element, left_super_element)
190
+ inherit_role(main_element, base_element)
191
+ modify_subsuper(base_element, sub_element, super_element, left_sub_element, left_super_element)
192
+ return this
193
+ end
194
+
195
+ def modify_subsuper(base_element, sub_element, super_element, left_sub_element, left_super_element)
196
+ if sub_element.children.size <= 0
197
+ sub_element.remove
198
+ end
199
+ if super_element.children.size <= 0
200
+ super_element.remove
201
+ end
202
+ if left_sub_element && left_sub_element.children.size <= 0
203
+ left_sub_element.remove
204
+ end
205
+ if left_super_element && left_super_element.children.size <= 0
206
+ left_super_element.remove
207
+ end
208
+ end
209
+
210
+ def build_underover(options = {}, &block)
211
+ this = Nodes[]
212
+ base_element, under_element, over_element = nil
213
+ main_element = nil
214
+ this << Element.build("math-underover") do |this|
215
+ main_element = this
216
+ this << Element.build("math-over") do |this|
217
+ over_element = this
218
+ end
219
+ this << Element.build("math-basewrap") do |this|
220
+ this << Element.build("math-base") do |this|
221
+ base_element = this
222
+ end
223
+ this << Element.build("math-under") do |this|
224
+ under_element = this
225
+ end
226
+ end
227
+ end
228
+ apply_options(this, options)
229
+ block&.call(base_element, under_element, over_element)
230
+ inherit_role(main_element, base_element)
231
+ modify_underover(under_element, over_element)
232
+ return this
233
+ end
234
+
235
+ def modify_underover(under_element, over_element)
236
+ if under_element.children.size <= 0
237
+ under_element.remove
238
+ end
239
+ if over_element.children.size <= 0
240
+ over_element.remove
241
+ end
242
+ end
243
+
244
+ def build_fraction(options = {}, &block)
245
+ this = Nodes[]
246
+ numerator_element, denominator_element = nil
247
+ this << Element.build("math-frac") do |this|
248
+ this << Element.build("math-num") do |this|
249
+ numerator_element = this
250
+ end
251
+ this << Element.build("math-denwrap") do |this|
252
+ this << Element.new("math-line")
253
+ this << Element.build("math-den") do |this|
254
+ denominator_element = this
255
+ end
256
+ end
257
+ end
258
+ apply_options(this, options)
259
+ block&.call(numerator_element, denominator_element)
260
+ modify_fraction(numerator_element, denominator_element)
261
+ return this
262
+ end
263
+
264
+ def modify_fraction(numerator_element, denominator_element)
265
+ numerator_element[0, 0] = build_strut("dlower").first
266
+ denominator_element[0, 0] = build_strut("upper").first
267
+ end
268
+
269
+ def fetch_radical_symbol(stretch_level)
270
+ stretch_level ||= "0"
271
+ symbol = DATA.dig("radical", stretch_level) || ""
272
+ return symbol
273
+ end
274
+
275
+ def build_radical(symbol, modify, options = {}, &block)
276
+ this = Nodes[]
277
+ content_element, index_element = nil
278
+ this << Element.build("math-rad") do |this|
279
+ if modify
280
+ this["class"] = "mod"
281
+ end
282
+ this << Element.build("math-index") do |this|
283
+ index_element = this
284
+ end
285
+ this << Element.build("math-sqrt") do |this|
286
+ this << Element.build("math-surd") do |this|
287
+ this << Element.build("math-o") do |this|
288
+ this << Text.new(symbol, true, nil, false)
289
+ end
290
+ end
291
+ this << Element.build("math-cont") do |this|
292
+ content_element = this
293
+ end
294
+ end
295
+ end
296
+ apply_options(this, options)
297
+ block&.call(content_element, index_element)
298
+ modify_radical(content_element, index_element)
299
+ return this
300
+ end
301
+
302
+ def modify_radical(content_element, index_element)
303
+ content_element[0, 0] = build_strut("upper").first
304
+ if index_element.children.size <= 0
305
+ index_element.remove
306
+ end
307
+ end
308
+
309
+ def fetch_fence_symbol(kind, position, stretch_level)
310
+ stretch_level ||= "0"
311
+ symbol = DATA.dig("fence", kind, position, stretch_level) || ""
312
+ return symbol
313
+ end
314
+
315
+ def build_fence(left_kind, right_kind, left_symbol, right_symbol, modify, options = {}, &block)
316
+ this = Nodes[]
317
+ content_element = nil
318
+ this << Element.build("math-fence") do |this|
319
+ this["class"] = "par"
320
+ if modify
321
+ this["class"] = [*this["class"].split(" "), "mod"].join(" ")
322
+ this["data-left"] = left_kind
323
+ this["data-right"] = right_kind
324
+ end
325
+ this << Element.build("math-left") do |this|
326
+ this << Element.build("math-o") do |this|
327
+ this << Text.new(left_symbol, true, nil, false)
328
+ end
329
+ end
330
+ this << Element.build("math-cont") do |this|
331
+ content_element = this
332
+ end
333
+ this << Element.build("math-right") do |this|
334
+ this << Element.build("math-o") do |this|
335
+ this << Text.new(right_symbol, true, nil, false)
336
+ end
337
+ end
338
+ end
339
+ apply_options(this, options)
340
+ block&.call(content_element)
341
+ return this
342
+ end
343
+
344
+ def build_set(left_kind, right_kind, center_kind, left_symbol, right_symbol, center_symbol, modify, options = {}, &block)
345
+ this = Nodes[]
346
+ left_element, right_element = nil
347
+ this << Element.build("math-fence") do |this|
348
+ this["class"] = "par"
349
+ if modify
350
+ this["class"] = [*this["class"].split(" "), "mod"].join(" ")
351
+ this["data-left"] = left_kind
352
+ this["data-right"] = right_kind
353
+ this["data-center"] = center_kind
354
+ end
355
+ this << Element.build("math-left") do |this|
356
+ this << Element.build("math-o") do |this|
357
+ this << Text.new(left_symbol, true, nil, false)
358
+ end
359
+ end
360
+ this << Element.build("math-cont") do |this|
361
+ left_element = this
362
+ end
363
+ this << Element.build("math-center") do |this|
364
+ this["class"] = "cpar"
365
+ this << Element.build("math-o") do |this|
366
+ this << Text.new(right_symbol, true, nil, false)
367
+ end
368
+ end
369
+ this << Element.build("math-cont") do |this|
370
+ right_element = this
371
+ end
372
+ this << Element.build("math-right") do |this|
373
+ this << Element.build("math-o") do |this|
374
+ this << Text.new(right_symbol, true, nil, false)
375
+ end
376
+ end
377
+ end
378
+ apply_options(this, options)
379
+ block&.call(left_element, right_element)
380
+ return this
381
+ end
382
+
383
+ def fetch_integral_symbol(kind, size)
384
+ size_index = (size == "lrg") ? 1 : 0
385
+ symbol = DATA.dig("integral", kind, size_index) || ""
386
+ return symbol
387
+ end
388
+
389
+ def build_integral(symbol, size, options = {}, &block)
390
+ this = Nodes[]
391
+ base_element, sub_element, super_element = nil
392
+ this << Element.build("math-subsup") do |this|
393
+ this["class"] = "int"
394
+ unless size == "lrg"
395
+ this["class"] = [*this["class"].split(" "), size].join(" ")
396
+ end
397
+ this << Element.build("math-base") do |this|
398
+ base_element = this
399
+ this << Element.build("math-o") do |this|
400
+ this["class"] = "int"
401
+ unless size == "lrg"
402
+ this["class"] = [*this["class"].split(" "), size].join(" ")
403
+ end
404
+ this << Text.new(symbol, true, nil, false)
405
+ end
406
+ end
407
+ this << Element.build("math-sub") do |this|
408
+ sub_element = this
409
+ end
410
+ this << Element.build("math-sup") do |this|
411
+ super_element = this
412
+ end
413
+ end
414
+ apply_options(this, options)
415
+ block&.call(sub_element, super_element)
416
+ modify_subsuper(base_element, sub_element, super_element, nil, nil)
417
+ return this
418
+ end
419
+
420
+ def fetch_sum_symbol(kind, size)
421
+ size_index = (size == "lrg") ? 1 : 0
422
+ symbol = DATA.dig("sum", kind, size_index) || ""
423
+ return symbol
424
+ end
425
+
426
+ def build_sum(symbol, size, options = {}, &block)
427
+ this = Nodes[]
428
+ if size == "lrg"
429
+ under_element, over_element = nil
430
+ this << Element.build("math-underover") do |this|
431
+ this["class"] = "sum"
432
+ this << Element.build("math-over") do |this|
433
+ over_element = this
434
+ end
435
+ this << Element.build("math-basewrap") do |this|
436
+ this << Element.build("math-base") do |this|
437
+ this << Element.build("math-o") do |this|
438
+ this["class"] = "sum"
439
+ this << Text.new(symbol, true, nil, false)
440
+ end
441
+ end
442
+ this << Element.build("math-under") do |this|
443
+ under_element = this
444
+ end
445
+ end
446
+ end
447
+ apply_options(this, options)
448
+ block&.call(under_element, over_element)
449
+ modify_underover(under_element, over_element)
450
+ else
451
+ base_element, sub_element, super_element = nil
452
+ this << Element.build("math-subsup") do |this|
453
+ this["class"] = "sum inl"
454
+ this << Element.build("math-base") do |this|
455
+ base_element = this
456
+ this << Element.build("math-o") do |this|
457
+ this["class"] = "sum inl"
458
+ this << Text.new(symbol, true, nil, false)
459
+ end
460
+ end
461
+ this << Element.build("math-sub") do |this|
462
+ sub_element = this
463
+ end
464
+ this << Element.build("math-sup") do |this|
465
+ super_element = this
466
+ end
467
+ end
468
+ apply_options(this, options)
469
+ block&.call(sub_element, super_element)
470
+ modify_subsuper(base_element, sub_element, super_element, nil, nil)
471
+ end
472
+ return this
473
+ end
474
+
475
+ def build_accent(under_symbol, over_symbol, options = {}, &block)
476
+ this = Nodes[]
477
+ base_element, under_element, over_element = nil
478
+ main_element = nil
479
+ this << Element.build("math-underover") do |this|
480
+ main_element = this
481
+ this["class"] = "acc"
482
+ this << Element.build("math-over") do |this|
483
+ over_element = this
484
+ if over_symbol
485
+ this << Element.build("math-o") do |this|
486
+ this["class"] = "acc"
487
+ this << Text.new(over_symbol, true, nil, false)
488
+ end
489
+ end
490
+ end
491
+ this << Element.build("math-basewrap") do |this|
492
+ this << Element.build("math-base") do |this|
493
+ base_element = this
494
+ end
495
+ this << Element.build("math-under") do |this|
496
+ under_element = this
497
+ if under_symbol
498
+ this << Element.build("math-o") do |this|
499
+ this["class"] = "acc"
500
+ this << Text.new(under_symbol, true, nil, false)
501
+ end
502
+ end
503
+ end
504
+ end
505
+ end
506
+ apply_options(this, options)
507
+ block&.call(base_element)
508
+ inherit_role(main_element, base_element)
509
+ modify_underover(under_element, over_element)
510
+ modify_accent(base_element, under_element, over_element)
511
+ return this
512
+ end
513
+
514
+ def modify_accent(base_element, under_element, over_element)
515
+ children = base_element.children
516
+ if children.size == 1
517
+ child = children.first
518
+ if child.name == "math-i" && (child["class"].split(" ") & ["rm", "alt"]).empty?
519
+ under_symbol_element = under_element.children.first
520
+ over_symbol_element = over_element.children.first
521
+ if under_symbol_element
522
+ under_symbol_element["class"] = [*under_symbol_element["class"].split(" "), "it"].join(" ")
523
+ end
524
+ if over_symbol_element
525
+ over_symbol_element["class"] = [*over_symbol_element["class"].split(" "), "it"].join(" ")
526
+ end
527
+ end
528
+ end
529
+ end
530
+
531
+ def fetch_accent_symbol(kind, position)
532
+ symbol = DATA.dig("accent", kind, position) || nil
533
+ return symbol
534
+ end
535
+
536
+ def fetch_wide_symbol(kind, position, stretch_level)
537
+ stretch_level ||= "0"
538
+ symbol = DATA.dig("wide", kind, position, stretch_level) || nil
539
+ return symbol
540
+ end
541
+
542
+ def build_wide(kind, under_symbol, over_symbol, modify, options = {}, &block)
543
+ this = Nodes[]
544
+ base_element, under_element, over_element = nil
545
+ main_element = nil
546
+ this << Element.build("math-underover") do |this|
547
+ this["class"] = "wid"
548
+ if modify
549
+ this["class"] = [*this["class"].split(" "), "mod"].join(" ")
550
+ this["data-kind"] = kind
551
+ end
552
+ main_element = this
553
+ this << Element.build("math-over") do |this|
554
+ if over_symbol
555
+ this << Element.build("math-o") do |this|
556
+ this["class"] = "wid"
557
+ this << Text.new(over_symbol, true, nil, false)
558
+ end
559
+ end
560
+ over_element = this
561
+ end
562
+ this << Element.build("math-basewrap") do |this|
563
+ this << Element.build("math-base") do |this|
564
+ base_element = this
565
+ end
566
+ this << Element.build("math-under") do |this|
567
+ if under_symbol
568
+ this << Element.build("math-o") do |this|
569
+ this["class"] = "wid"
570
+ this << Text.new(under_symbol, true, nil, false)
571
+ end
572
+ end
573
+ under_element = this
574
+ end
575
+ end
576
+ end
577
+ apply_options(this, options)
578
+ block&.call(base_element)
579
+ inherit_role(main_element, base_element)
580
+ modify_underover(under_element, over_element)
581
+ return this
582
+ end
583
+
584
+ def build_table(type, align_config, raw, options = {}, &block)
585
+ this = Nodes[]
586
+ table_element = nil
587
+ this << Element.build("math-table") do |this|
588
+ this["class"] = type
589
+ table_element = this
590
+ end
591
+ apply_options(this, options)
592
+ block&.call(table_element)
593
+ modify_table(table_element, type, align_config, raw)
594
+ return this
595
+ end
596
+
597
+ def build_diagram(vertical_gaps_string, horizontal_gaps_string, options = {}, &block)
598
+ this = Nodes[]
599
+ table_element = nil
600
+ this << Element.build("math-diagram") do |this|
601
+ if vertical_gaps_string
602
+ this["class"] = [*this["class"].split(" "), "vnon"].join(" ")
603
+ end
604
+ if horizontal_gaps_string
605
+ this["class"] = [*this["class"].split(" "), "hnon"].join(" ")
606
+ end
607
+ table_element = this
608
+ end
609
+ apply_options(this, options)
610
+ block&.call(table_element)
611
+ modify_diagram(table_element, vertical_gaps_string, horizontal_gaps_string)
612
+ modify_table(table_element, "diag", nil, false)
613
+ return this
614
+ end
615
+
616
+ def modify_diagram(element, vertical_gaps_string, horizontal_gaps_string)
617
+ cell_elements = element.elements.to_a
618
+ vertical_gaps = vertical_gaps_string&.split(/\s*,\s*/)
619
+ horizontal_gaps = horizontal_gaps_string&.split(/\s*,\s*/)
620
+ column, row = 0, 0
621
+ cell_elements.each_with_index do |child, i|
622
+ if child.name == "math-cellwrap"
623
+ vertical_gap = vertical_gaps&.fetch(row - 1, vertical_gaps.last)
624
+ horizontal_gap = horizontal_gaps&.fetch(column - 1, horizontal_gaps.last)
625
+ if vertical_gap && row > 0
626
+ if vertical_gap =~ /^\d+$/
627
+ child["style"] += "margin-top: #{vertical_gap.to_f / 18}em; "
628
+ else
629
+ child["class"] = [*child["class"].split(" "), "v#{vertical_gap}"].join(" ")
630
+ end
631
+ end
632
+ if horizontal_gap && column > 0
633
+ if horizontal_gap =~ /^\d+$/
634
+ child["style"] += "margin-left: #{horizontal_gap.to_f / 18}em; "
635
+ else
636
+ child["class"] = [*child["class"].split(" "), "h#{horizontal_gap}"].join(" ")
637
+ end
638
+ end
639
+ column += 1
640
+ elsif child.name == "math-sys-br"
641
+ row += 1
642
+ column = 0
643
+ end
644
+ end
645
+ end
646
+
647
+ def modify_table(element, type, align_config, raw)
648
+ align_array = align_config&.chars
649
+ cell_elements = element.elements.to_a
650
+ column, row = 0, 0
651
+ cell_elements.each_with_index do |child, i|
652
+ if child.name == "math-cell" || child.name == "math-cellwrap"
653
+ if raw
654
+ extra_class = []
655
+ extra_class << "lpres" unless column == 0
656
+ extra_class << "rpres" unless cell_elements[i + 1]&.name == "math-sys-br"
657
+ child["class"] = (child["class"].split(" ") + extra_class).join(" ")
658
+ end
659
+ child["style"] += "grid-row: #{row + 1}; grid-column: #{column + 1};"
660
+ if align_array
661
+ align = ALIGNS[align_array[column]]
662
+ child["style"] += "text-align: #{align};"
663
+ end
664
+ unless type == "stk" || type == "diag"
665
+ child[0, 0] = build_strut("dfull").first
666
+ end
667
+ column += 1
668
+ elsif child.name == "math-sys-br"
669
+ element.delete_element(child)
670
+ row += 1
671
+ column = 0
672
+ end
673
+ end
674
+ end
675
+
676
+ def build_table_cell(options = {}, &block)
677
+ this = Nodes[]
678
+ cell_element = nil
679
+ this << Element.build("math-cell") do |this|
680
+ cell_element = this
681
+ end
682
+ apply_options(this, options)
683
+ block&.call(cell_element)
684
+ return this
685
+ end
686
+
687
+ def build_diagram_vertex(name, options = {}, &block)
688
+ this = Nodes[]
689
+ vertex_element = nil
690
+ this << Element.build("math-cellwrap") do |this|
691
+ if name
692
+ this["data-name"] = name
693
+ end
694
+ this << Element.build("math-cell") do |this|
695
+ vertex_element = this
696
+ end
697
+ end
698
+ apply_options(this, options)
699
+ block&.call(vertex_element)
700
+ return this
701
+ end
702
+
703
+ def build_arrow(name, configs, options = {}, &block)
704
+ this = Nodes[]
705
+ label_element = nil
706
+ this << Element.build("math-arrow") do |this|
707
+ this["data-start"] = configs[:start_config]
708
+ this["data-end"] = configs[:end_config]
709
+ if configs[:tip_kinds]
710
+ this["data-tip"] = configs[:tip_kinds]
711
+ end
712
+ if configs[:bend_angle]
713
+ this["data-bend"] = configs[:bend_angle]
714
+ end
715
+ if configs[:shift]
716
+ this["data-shift"] = configs[:shift]
717
+ end
718
+ if configs[:line_count]
719
+ this["data-line"] = configs[:line_count]
720
+ end
721
+ if configs[:dashed]
722
+ this["data-dash"] = "data-dash"
723
+ end
724
+ if configs[:label_position]
725
+ this["data-pos"] = configs[:label_position]
726
+ end
727
+ if configs[:inverted]
728
+ this["data-inv"] = "data-inv"
729
+ end
730
+ if configs[:mark]
731
+ this["data-mark"] = "data-mark"
732
+ end
733
+ if name
734
+ this["data-name"] = name
735
+ end
736
+ label_element = this
737
+ end
738
+ apply_options(this, options)
739
+ block&.call(label_element)
740
+ return this
741
+ end
742
+
743
+ def build_tree(options = {}, &block)
744
+ this = Nodes[]
745
+ content_element = nil
746
+ this << Element.build("math-tree") do |this|
747
+ content_element = this
748
+ end
749
+ apply_options(this, options)
750
+ block&.call(content_element)
751
+ modify_tree(content_element)
752
+ return this
753
+ end
754
+
755
+ def modify_tree(element)
756
+ stack = []
757
+ element.elements.each do |child|
758
+ case child.name
759
+ when "math-axiom"
760
+ child[0, 0] = build_strut("dlower").first
761
+ stack.push(child)
762
+ when "math-sys-infer"
763
+ number = child.attribute("data-num").to_s.to_i
764
+ left_label_element = child.get_elements("math-sys-llabel").first
765
+ right_label_element = child.get_elements("math-sys-rlabel").first
766
+ antecedent_elements = stack.pop(number)
767
+ inference_element = Element.build("math-infer") do |this|
768
+ this << Element.build("math-label") do |this|
769
+ if left_label_element.to_a.empty?
770
+ this["class"] = "non"
771
+ end
772
+ left_label_element.each_element do |each_element|
773
+ this << each_element
774
+ end
775
+ end
776
+ this << Element.build("math-step") do |this|
777
+ this << Element.build("math-ant") do |this|
778
+ antecedent_elements.each do |antecedent_element|
779
+ this << antecedent_element
780
+ end
781
+ end
782
+ this << Element.build("math-conwrap") do |this|
783
+ this << Element.new("math-line")
784
+ this << Element.build("math-con") do |this|
785
+ this << ZoticaBuilder.build_strut("upper").first
786
+ this << ZoticaBuilder.build_strut("dlower").first
787
+ this << child.get_elements("math-cont").first
788
+ end
789
+ end
790
+ end
791
+ this << Element.build("math-label") do |this|
792
+ if right_label_element.to_a.empty?
793
+ this["class"] = "non"
794
+ end
795
+ right_label_element.each_element do |each_element|
796
+ this << each_element
797
+ end
798
+ end
799
+ end
800
+ stack.push(inference_element)
801
+ end
802
+ end
803
+ element.each_element do |child|
804
+ child.remove
805
+ end
806
+ element << stack.first
807
+ end
808
+
809
+ def build_tree_axiom(options = {}, &block)
810
+ this = Nodes[]
811
+ content_element = nil
812
+ this << Element.build("math-axiom") do |this|
813
+ content_element = this
814
+ end
815
+ apply_options(this, options)
816
+ block&.call(content_element)
817
+ return this
818
+ end
819
+
820
+ def build_tree_inference(number, options = {}, &block)
821
+ this = Nodes[]
822
+ content_element, right_label_element, left_label_element = nil
823
+ this << Element.build("math-sys-infer") do |this|
824
+ this["data-num"] = number.to_s
825
+ this << Element.build("math-cont") do |this|
826
+ content_element = this
827
+ end
828
+ this << Element.build("math-sys-rlabel") do |this|
829
+ right_label_element = this
830
+ end
831
+ this << Element.build("math-sys-llabel") do |this|
832
+ left_label_element = this
833
+ end
834
+ end
835
+ apply_options(this, options)
836
+ block&.call(content_element, right_label_element, left_label_element)
837
+ return this
838
+ end
839
+
840
+ def build_group(transform_configs, options = {}, &block)
841
+ this = Nodes[]
842
+ content_element = nil
843
+ this << Element.build("math-group") do |this|
844
+ transforms = []
845
+ if transform_configs[:rotate]
846
+ transforms << "rotate(#{transform_configs[:rotate]}deg)"
847
+ end
848
+ unless transforms.empty?
849
+ this["style"] += "transform: " + transforms.join(" ") + ";"
850
+ end
851
+ content_element = this
852
+ end
853
+ apply_options(this, options)
854
+ block&.call(content_element)
855
+ return this
856
+ end
857
+
858
+ def build_space(type, options = {})
859
+ this = Nodes[]
860
+ this << Element.build("math-space") do |this|
861
+ this["class"] = type
862
+ end
863
+ apply_options(this, options)
864
+ return this
865
+ end
866
+
867
+ def build_phantom(type, options = {}, &block)
868
+ this = Nodes[]
869
+ content_element = nil
870
+ this << Element.build("math-phantom") do |this|
871
+ this["class"] = ["lpres", "rpres"].join(" ")
872
+ unless type == "bth"
873
+ this["class"] = [*this["class"].split(" "), type].join(" ")
874
+ end
875
+ content_element = this
876
+ end
877
+ apply_options(this, options)
878
+ block&.call(content_element)
879
+ return this
880
+ end
881
+
882
+ def build_text(text, options = {}, &block)
883
+ this = Nodes[]
884
+ this << Element.build("math-text") do |this|
885
+ this << Text.new(text, true, nil, false)
886
+ end
887
+ apply_options(this, options)
888
+ return this
889
+ end
890
+
891
+ def create_style_string(font_url = nil, style = :compressed)
892
+ common_path = File.expand_path("../" + COMMON_STYLE_PATH, __FILE__)
893
+ common_string = SassC::Engine.new(File.read(common_path), {:style => style}).render
894
+ common_string.gsub!("__mathfonturl__", font_url || "font.otf")
895
+ specialized_path = File.expand_path("../" + SPECIALIZED_STYLE_PATH, __FILE__)
896
+ specialized_string = SassC::Engine.new(File.read(specialized_path), {:style => style}).render
897
+ string = common_string + specialized_string
898
+ return string
899
+ end
900
+
901
+ def create_script_string
902
+ dir = File.expand_path("../" + SCRIPT_DIR, __FILE__)
903
+ string = "const DATA = "
904
+ string << JSON.generate(ZoticaBuilder::DATA.slice("radical", "fence", "wide", "shift", "arrow"))
905
+ string << ";\n"
906
+ string << File.read(dir + "/main.js")
907
+ string << "\n"
908
+ Dir.each_child(dir) do |entry|
909
+ unless entry == "main.js"
910
+ string << File.read(dir + "/" + entry)
911
+ string << "\n"
912
+ end
913
+ end
914
+ string << "window.onload = () => Modifier.execute();"
915
+ return string
916
+ end
917
+
918
+ def create_font_string(type, path = nil, metrics = nil)
919
+ unless path
920
+ if type == :main
921
+ path = File.expand_path("../" + DEFAULT_TTF_PATHS[:main], __FILE__)
922
+ metrics = {:em => 2048, :ascent => 1638 + 78, :descent => 410 - 78}
923
+ else
924
+ path = File.expand_path("../" + DEFAULT_TTF_PATHS[:math], __FILE__)
925
+ metrics = {:em => 1000, :ascent => 762, :descent => 238}
926
+ end
927
+ end
928
+ file = TTFunk::File.open(path)
929
+ first = true
930
+ string = "{\n"
931
+ CODEPOINTS[type].each do |part_codepoints|
932
+ part_codepoints.each do |codepoint|
933
+ glyph_id = file.cmap.unicode.first[codepoint]
934
+ glyph = file.glyph_outlines.for(glyph_id)
935
+ if glyph
936
+ top_margin = (glyph.y_max - metrics[:ascent]) / metrics[:em].to_f
937
+ bottom_margin = (-glyph.y_min - metrics[:descent]) / metrics[:em].to_f
938
+ string << ",\n" unless first
939
+ string << " \"#{codepoint}\": [#{bottom_margin}, #{top_margin}]"
940
+ first = false
941
+ end
942
+ end
943
+ end
944
+ string << "\n}"
945
+ return string
946
+ end
947
+
948
+ def save_font_strings
949
+ main_font_path = File.expand_path("../" + DEFAULT_FONT_PATHS[:main], __FILE__)
950
+ math_font_path = File.expand_path("../" + DEFAULT_FONT_PATHS[:math], __FILE__)
951
+ File.open(main_font_path, "w") do |file|
952
+ file.write(ZoticaBuilder.create_font_string(:main))
953
+ end
954
+ File.open(math_font_path, "w") do |file|
955
+ file.write(ZoticaBuilder.create_font_string(:math))
956
+ end
957
+ end
958
+
959
+ private
960
+
961
+ def self.create_data
962
+ path = File.expand_path("../" + DATA_PATH, __FILE__)
963
+ data = JSON.parse(File.read(path))
964
+ return data
965
+ end
966
+
967
+ def self.create_default_fonts
968
+ fonts = {}
969
+ main_path = File.expand_path("../" + DEFAULT_FONT_PATHS[:main], __FILE__)
970
+ math_path = File.expand_path("../" + DEFAULT_FONT_PATHS[:math], __FILE__)
971
+ fonts[:main] = JSON.parse(File.read(main_path))
972
+ fonts[:math] = JSON.parse(File.read(math_path))
973
+ return fonts
974
+ end
975
+
976
+ DATA = self.create_data
977
+ DEFAULT_FONTS = self.create_default_fonts
978
+
979
+ end