zotica 1.0.0

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