datalog-theme 0.10.0 → 0.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +133 -0
- data/CITATION.cff +2 -2
- data/README.md +4 -2
- data/_data/i18n/en.yml +55 -3
- data/_data/i18n/es.yml +55 -3
- data/_data/i18n/pt.yml +55 -3
- data/_includes/analytics/dashboard.html +24 -9
- data/_includes/components/academic-dashboard.html +168 -133
- data/_includes/components/author-bio.html +7 -5
- data/_includes/components/author-list.html +6 -5
- data/_includes/components/citation-tools.html +2 -1
- data/_includes/components/content-provenance.html +19 -1
- data/_includes/components/correction-report.html +15 -8
- data/_includes/components/hero.html +22 -15
- data/_includes/components/package-index.html +121 -0
- data/_includes/components/package-install.html +27 -50
- data/_includes/components/post-list.html +14 -0
- data/_includes/components/responsive-image.html +15 -21
- data/_includes/footer.html +1 -1
- data/_includes/head.html +70 -11
- data/_includes/layouts/default/article.html +76 -46
- data/_includes/meta/dataset-json.html +171 -0
- data/_includes/meta/math-config.html +15 -1
- data/_includes/meta/package-json.html +69 -0
- data/_includes/meta/person-json.html +36 -13
- data/_includes/meta/publisher.html +15 -0
- data/_includes/meta/schema.html +108 -23
- data/_includes/meta/scholarly.html +10 -5
- data/_includes/post/related-posts.html +17 -43
- data/_includes/scripts.html +2 -2
- data/_includes/search/index-data.json +10 -1
- data/_includes/search/page.html +11 -6
- data/_layouts/archive.html +67 -0
- data/_layouts/dataset.html +2 -1
- data/_layouts/default.html +13 -11
- data/_layouts/docs.html +53 -0
- data/_layouts/home.html +30 -1
- data/_layouts/notebook.html +5 -1
- data/_layouts/package.html +38 -21
- data/_layouts/portfolio.html +1 -0
- data/_layouts/post.html +21 -62
- data/_layouts/project.html +2 -1
- data/_layouts/research.html +14 -2
- data/_plugins/analytics_dashboard.rb +4 -1
- data/_plugins/archive.rb +114 -0
- data/_plugins/authors.rb +50 -1
- data/_plugins/citation_exports.rb +12 -1
- data/_plugins/config_validator.rb +77 -2
- data/_plugins/correction_fallback.rb +110 -0
- data/_plugins/image_optimizer.rb +129 -22
- data/_plugins/math_preprocessor.rb +7 -48
- data/_plugins/notebook_converter.rb +44 -5
- data/_plugins/packages.rb +84 -0
- data/_plugins/page_dates.rb +37 -0
- data/_plugins/references.rb +16 -2
- data/_plugins/related_posts.rb +149 -0
- data/_plugins/search_sections.rb +110 -0
- data/_plugins/site_identity.rb +42 -0
- data/_plugins/social_cards.rb +17 -0
- data/_sass/_academic-dashboard.scss +18 -30
- data/_sass/_base.scss +2 -0
- data/_sass/_components.scss +21 -23
- data/_sass/_docs.scss +143 -0
- data/_sass/_features.scss +6 -0
- data/_sass/_layout.scss +65 -6
- data/_sass/_mathematical.scss +61 -19
- data/_sass/_notebooks.scss +1 -32
- data/_sass/_package-docs.scss +6 -9
- data/_sass/_post-components.scss +23 -23
- data/_sass/_print.scss +1 -1
- data/_sass/_search-page.scss +75 -0
- data/_sass/_search.scss +17 -26
- data/_sass/_syntax-highlighting.scss +23 -1
- data/_sass/_theme.scss +1 -0
- data/_sass/_utilities.scss +87 -40
- data/_sass/_variables.scss +31 -29
- data/assets/css/main.scss +2 -1
- data/assets/js/dist/academic.js +1 -1
- data/assets/js/dist/analytics-dashboard.js +1 -1
- data/assets/js/dist/chunks/chunk-TNVD6UAM.js +1 -0
- data/assets/js/dist/comments.js +1 -1
- data/assets/js/dist/contact.js +1 -1
- data/assets/js/dist/core.js +1 -1
- data/assets/js/dist/corrections.js +5 -1
- data/assets/js/dist/loader.js +1 -1
- data/assets/js/dist/math.js +2 -1
- data/assets/js/dist/moderation.js +1 -1
- data/assets/js/dist/reactions.js +1 -1
- data/assets/js/dist/search.js +1 -1
- data/assets/js/dist/sources.json +20 -16
- data/assets/js/dist/subscriptions.js +1 -1
- data/assets/js/loader.js +6 -2
- data/datalog-theme.gemspec +1 -1
- data/lib/datalog/audit/checks.rb +269 -0
- data/lib/datalog/audit/known_keys.rb +61 -0
- data/lib/datalog/audit/site_reader.rb +118 -0
- data/lib/datalog/audit/source_file.rb +85 -0
- data/lib/datalog/audit.rb +145 -0
- data/lib/datalog/citations/bibtex.rb +200 -0
- data/lib/datalog/citations/entry.rb +333 -0
- data/lib/datalog/citations/markup.rb +79 -0
- data/lib/datalog/cli.rb +164 -89
- data/lib/datalog/critical_css.rb +126 -21
- data/lib/datalog/latex_speech/words.json +88 -0
- data/lib/datalog/latex_speech.rb +355 -0
- data/lib/datalog/packages/command.rb +51 -0
- data/lib/datalog/packages/refresh.rb +187 -0
- data/lib/datalog/packages.rb +177 -0
- data/lib/datalog/plugin_system.rb +5 -0
- data/lib/datalog/plugins/citations.rb +325 -109
- data/lib/datalog/site_config.rb +26 -0
- data/lib/datalog/social_cards/font.rb +286 -0
- data/lib/datalog/social_cards/fonts/IBMPlexSans-Regular.ttf +0 -0
- data/lib/datalog/social_cards/fonts/IBMPlexSerif-SemiBold.ttf +0 -0
- data/lib/datalog/social_cards/fonts/OFL.txt +92 -0
- data/lib/datalog/social_cards/geometry.rb +51 -0
- data/lib/datalog/social_cards/outline.rb +54 -0
- data/lib/datalog/social_cards/plain_text.rb +258 -0
- data/lib/datalog/social_cards/template.rb +322 -0
- data/lib/datalog/social_cards/template.svg +17 -0
- data/lib/datalog/social_cards/typesetter.rb +178 -0
- data/lib/datalog/social_cards.rb +369 -0
- data/lib/datalog/theme/updater.rb +307 -0
- data/lib/datalog/theme/version.rb +1 -1
- metadata +46 -9
- data/_includes/components/enhanced-code-block.html +0 -212
- data/_includes/components/performance-monitor.html +0 -170
- data/_includes/components/viz-table-fallback.html +0 -19
- data/_plugins/datalog_bibliography.rb +0 -21
- data/assets/js/dist/chunks/chunk-WIRUK3OZ.js +0 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "outline"
|
|
4
|
+
|
|
5
|
+
module Datalog
|
|
6
|
+
module SocialCards
|
|
7
|
+
# A TrueType font, read with nothing but Ruby: the advance width of each
|
|
8
|
+
# glyph, to wrap a title, and its outline, to draw it. A card's text is
|
|
9
|
+
# drawn as paths, so neither ImageMagick nor the build machine needs the
|
|
10
|
+
# font installed, and no font lookup can pick a different one.
|
|
11
|
+
#
|
|
12
|
+
# Reads the tables a static TrueType font always has (head, hhea, maxp,
|
|
13
|
+
# hmtx, cmap, loca, glyf), simple and composite glyphs alike. Kerning and
|
|
14
|
+
# ligatures are left out: the text is set from advance widths alone.
|
|
15
|
+
class Font
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
# Glyph flags, from the glyf table.
|
|
19
|
+
ON_CURVE = 0x01
|
|
20
|
+
X_SHORT = 0x02
|
|
21
|
+
Y_SHORT = 0x04
|
|
22
|
+
REPEAT = 0x08
|
|
23
|
+
X_SAME = 0x10
|
|
24
|
+
Y_SAME = 0x20
|
|
25
|
+
|
|
26
|
+
# Component flags of a composite glyph.
|
|
27
|
+
WORDS = 0x0001
|
|
28
|
+
XY_VALUES = 0x0002
|
|
29
|
+
SCALE = 0x0008
|
|
30
|
+
MORE = 0x0020
|
|
31
|
+
XY_SCALE = 0x0040
|
|
32
|
+
TWO_BY_TWO = 0x0080
|
|
33
|
+
|
|
34
|
+
IDENTITY = [1.0, 0.0, 0.0, 1.0].freeze
|
|
35
|
+
|
|
36
|
+
attr_reader :file, :units_per_em, :ascender, :descender
|
|
37
|
+
|
|
38
|
+
def initialize(file)
|
|
39
|
+
@file = file
|
|
40
|
+
@data = File.binread(file)
|
|
41
|
+
@tables = directory
|
|
42
|
+
read_head
|
|
43
|
+
read_hhea
|
|
44
|
+
@glyph_count = u16(table("maxp") + 4)
|
|
45
|
+
@advances = Array.new(@metric_count) { |index| u16(table("hmtx") + (index * 4)) }
|
|
46
|
+
@cmap = read_cmap
|
|
47
|
+
@loca = read_loca
|
|
48
|
+
@outlines = {}
|
|
49
|
+
rescue ArgumentError, TypeError, NoMethodError => e
|
|
50
|
+
raise Error, "#{file} is not a TrueType font this can read (#{e.message})"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The glyph for a character, or 0 (.notdef) when the font has none.
|
|
54
|
+
def glyph_id(char)
|
|
55
|
+
@cmap.fetch(char.ord, 0)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def glyph?(char)
|
|
59
|
+
!glyph_id(char).zero?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def advance(glyph)
|
|
63
|
+
@advances.fetch(glyph) { @advances.last }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The width of the text at a font size, in pixels.
|
|
67
|
+
def width(text, size)
|
|
68
|
+
text.each_char.sum { |char| advance(glyph_id(char)) } * size / units_per_em.to_f
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The text's outline as path data, from `left` along a baseline at a
|
|
72
|
+
# size, in SVG's path syntax, which ImageMagick's MVG shares. The y
|
|
73
|
+
# axis points down, as it does on the card.
|
|
74
|
+
def path(text, left, baseline, size)
|
|
75
|
+
scale = size / units_per_em.to_f
|
|
76
|
+
commands = []
|
|
77
|
+
text.each_char do |char|
|
|
78
|
+
glyph = glyph_id(char)
|
|
79
|
+
outline(glyph).each { |contour| commands << Outline.path(contour, left, baseline, scale) }
|
|
80
|
+
left += advance(glyph) * scale
|
|
81
|
+
end
|
|
82
|
+
commands.join(" ")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Contours of [x, y, on_curve] points in font units, y up.
|
|
86
|
+
def outline(glyph, depth = 0)
|
|
87
|
+
@outlines[glyph] ||= begin
|
|
88
|
+
offset = @loca[glyph]
|
|
89
|
+
length = @loca[glyph + 1].to_i - offset.to_i
|
|
90
|
+
if offset.nil? || length <= 0 || depth > 8
|
|
91
|
+
[]
|
|
92
|
+
else
|
|
93
|
+
start = table("glyf") + offset
|
|
94
|
+
contours = s16(start)
|
|
95
|
+
contours.negative? ? composite(start + 10, depth) : simple(start + 10, contours)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def directory
|
|
103
|
+
(0...u16(4)).to_h do |index|
|
|
104
|
+
record = 12 + (index * 16)
|
|
105
|
+
[@data.byteslice(record, 4), u32(record + 8)]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def table(tag)
|
|
110
|
+
@tables.fetch(tag) { raise Error, "#{file} has no #{tag} table" }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def read_head
|
|
114
|
+
head = table("head")
|
|
115
|
+
@units_per_em = u16(head + 18)
|
|
116
|
+
@long_offsets = s16(head + 50) == 1
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def read_hhea
|
|
120
|
+
hhea = table("hhea")
|
|
121
|
+
@ascender = s16(hhea + 4)
|
|
122
|
+
@descender = s16(hhea + 6)
|
|
123
|
+
@metric_count = u16(hhea + 34)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Unicode to glyph, from the format 12 subtable when there is one (all of
|
|
127
|
+
# Unicode), else format 4 (the Basic Multilingual Plane).
|
|
128
|
+
def read_cmap
|
|
129
|
+
cmap = table("cmap")
|
|
130
|
+
offsets = Array.new(u16(cmap + 2)) { |index| cmap + 4 + (index * 8) }.filter_map do |record|
|
|
131
|
+
platform = u16(record)
|
|
132
|
+
encoding = u16(record + 2)
|
|
133
|
+
cmap + u32(record + 4) if platform.zero? || (platform == 3 && [1, 10].include?(encoding))
|
|
134
|
+
end
|
|
135
|
+
full = offsets.find { |offset| u16(offset) == 12 }
|
|
136
|
+
return format12(full) if full
|
|
137
|
+
|
|
138
|
+
bmp = offsets.find { |offset| u16(offset) == 4 }
|
|
139
|
+
raise Error, "#{file} has no Unicode character map" unless bmp
|
|
140
|
+
|
|
141
|
+
format4(bmp)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def format12(offset)
|
|
145
|
+
Array.new(u32(offset + 12)) { |index| offset + 16 + (index * 12) }.each_with_object({}) do |group, map|
|
|
146
|
+
first = u32(group)
|
|
147
|
+
glyph = u32(group + 8)
|
|
148
|
+
(first..u32(group + 4)).each { |code| map[code] = glyph + code - first }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def format4(offset)
|
|
153
|
+
segments = u16(offset + 6) / 2
|
|
154
|
+
ends = offset + 14
|
|
155
|
+
starts = ends + (segments * 2) + 2
|
|
156
|
+
(0...segments).each_with_object({}) do |index, map|
|
|
157
|
+
first = u16(starts + (index * 2))
|
|
158
|
+
next if first == 0xFFFF
|
|
159
|
+
|
|
160
|
+
(first..u16(ends + (index * 2))).each do |code|
|
|
161
|
+
glyph = segment_glyph(starts + (segments * 2), segments, index, code, first)
|
|
162
|
+
map[code] = glyph unless glyph.zero?
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# A code's glyph in a format 4 segment: its delta alone, or the delta
|
|
168
|
+
# added to an entry of the glyph array its range offset points into.
|
|
169
|
+
def segment_glyph(deltas, segments, index, code, first)
|
|
170
|
+
delta = s16(deltas + (index * 2))
|
|
171
|
+
range_at = deltas + (segments * 2) + (index * 2)
|
|
172
|
+
range = u16(range_at)
|
|
173
|
+
return (code + delta) & 0xFFFF if range.zero?
|
|
174
|
+
|
|
175
|
+
found = u16(range_at + range + ((code - first) * 2))
|
|
176
|
+
found.zero? ? 0 : (found + delta) & 0xFFFF
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def read_loca
|
|
180
|
+
loca = table("loca")
|
|
181
|
+
Array.new(@glyph_count + 1) do |index|
|
|
182
|
+
@long_offsets ? u32(loca + (index * 4)) : u16(loca + (index * 2)) * 2
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def simple(at, count)
|
|
187
|
+
ends = Array.new(count) { |index| u16(at + (index * 2)) }
|
|
188
|
+
at += (count * 2)
|
|
189
|
+
at += 2 + u16(at)
|
|
190
|
+
flags, at = read_flags(at, ends.empty? ? 0 : ends.last + 1)
|
|
191
|
+
xs, at = coordinates(flags, at, X_SHORT, X_SAME)
|
|
192
|
+
ys, = coordinates(flags, at, Y_SHORT, Y_SAME)
|
|
193
|
+
points = flags.each_with_index.map { |flag, index| [xs[index], ys[index], flag.anybits?(ON_CURVE)] }
|
|
194
|
+
starts = [0] + ends[0...-1].map(&:succ)
|
|
195
|
+
starts.zip(ends).map { |first, last| points[first..last] }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def read_flags(at, total)
|
|
199
|
+
flags = []
|
|
200
|
+
while flags.size < total
|
|
201
|
+
flag = @data.getbyte(at)
|
|
202
|
+
at += 1
|
|
203
|
+
flags << flag
|
|
204
|
+
next unless flag.anybits?(REPEAT)
|
|
205
|
+
|
|
206
|
+
@data.getbyte(at).times { flags << flag }
|
|
207
|
+
at += 1
|
|
208
|
+
end
|
|
209
|
+
[flags, at]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def coordinates(flags, at, short, same)
|
|
213
|
+
value = 0
|
|
214
|
+
values = flags.map do |flag|
|
|
215
|
+
if flag.anybits?(short)
|
|
216
|
+
step = @data.getbyte(at)
|
|
217
|
+
at += 1
|
|
218
|
+
value += flag.anybits?(same) ? step : -step
|
|
219
|
+
elsif flag.nobits?(same)
|
|
220
|
+
value += s16(at)
|
|
221
|
+
at += 2
|
|
222
|
+
end
|
|
223
|
+
value
|
|
224
|
+
end
|
|
225
|
+
[values, at]
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# A glyph made of others, each placed by an offset and an optional
|
|
229
|
+
# scale or 2x2 transform: most accented letters.
|
|
230
|
+
def composite(at, depth)
|
|
231
|
+
contours = []
|
|
232
|
+
loop do
|
|
233
|
+
flags = u16(at)
|
|
234
|
+
component = u16(at + 2)
|
|
235
|
+
dx, dy, at = component_offset(flags, at + 4)
|
|
236
|
+
(a, b, c, d), at = component_transform(flags, at)
|
|
237
|
+
outline(component, depth + 1).each do |contour|
|
|
238
|
+
contours << contour.map { |x, y, on| [(a * x) + (c * y) + dx, (b * x) + (d * y) + dy, on] }
|
|
239
|
+
end
|
|
240
|
+
break unless flags.anybits?(MORE)
|
|
241
|
+
end
|
|
242
|
+
contours
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def component_offset(flags, at)
|
|
246
|
+
if flags.anybits?(WORDS)
|
|
247
|
+
dx = s16(at)
|
|
248
|
+
dy = s16(at + 2)
|
|
249
|
+
at += 4
|
|
250
|
+
else
|
|
251
|
+
dx, dy = @data.byteslice(at, 2).unpack("cc")
|
|
252
|
+
at += 2
|
|
253
|
+
end
|
|
254
|
+
flags.anybits?(XY_VALUES) ? [dx, dy, at] : [0, 0, at]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def component_transform(flags, at)
|
|
258
|
+
if flags.anybits?(SCALE)
|
|
259
|
+
[[f2dot14(at), 0.0, 0.0, f2dot14(at)], at + 2]
|
|
260
|
+
elsif flags.anybits?(XY_SCALE)
|
|
261
|
+
[[f2dot14(at), 0.0, 0.0, f2dot14(at + 2)], at + 4]
|
|
262
|
+
elsif flags.anybits?(TWO_BY_TWO)
|
|
263
|
+
[[f2dot14(at), f2dot14(at + 2), f2dot14(at + 4), f2dot14(at + 6)], at + 8]
|
|
264
|
+
else
|
|
265
|
+
[IDENTITY, at]
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def f2dot14(at)
|
|
270
|
+
s16(at) / 16_384.0
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def u16(at)
|
|
274
|
+
@data.byteslice(at, 2).unpack1("n")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def s16(at)
|
|
278
|
+
@data.byteslice(at, 2).unpack1("s>")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def u32(at)
|
|
282
|
+
@data.byteslice(at, 4).unpack1("N")
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
-----------------------------------------------------------
|
|
8
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
PREAMBLE
|
|
12
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
13
|
+
development of collaborative font projects, to support the font creation
|
|
14
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
15
|
+
open framework in which fonts may be shared and improved in partnership
|
|
16
|
+
with others.
|
|
17
|
+
|
|
18
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
19
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
20
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
21
|
+
redistributed and/or sold with any software provided that any reserved
|
|
22
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
23
|
+
however, cannot be released under any other type of license. The
|
|
24
|
+
requirement for fonts to remain under this license does not apply
|
|
25
|
+
to any document created using the fonts or their derivatives.
|
|
26
|
+
|
|
27
|
+
DEFINITIONS
|
|
28
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
29
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
30
|
+
include source files, build scripts and documentation.
|
|
31
|
+
|
|
32
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
33
|
+
copyright statement(s).
|
|
34
|
+
|
|
35
|
+
"Original Version" refers to the collection of Font Software components as
|
|
36
|
+
distributed by the Copyright Holder(s).
|
|
37
|
+
|
|
38
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
39
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
40
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
41
|
+
new environment.
|
|
42
|
+
|
|
43
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
44
|
+
writer or other person who contributed to the Font Software.
|
|
45
|
+
|
|
46
|
+
PERMISSION & CONDITIONS
|
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
48
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
49
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
50
|
+
Software, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
1) Neither the Font Software nor any of its individual components,
|
|
53
|
+
in Original or Modified Versions, may be sold by itself.
|
|
54
|
+
|
|
55
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
56
|
+
redistributed and/or sold with any software, provided that each copy
|
|
57
|
+
contains the above copyright notice and this license. These can be
|
|
58
|
+
included either as stand-alone text files, human-readable headers or
|
|
59
|
+
in the appropriate machine-readable metadata fields within text or
|
|
60
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
61
|
+
|
|
62
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
63
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
64
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
65
|
+
presented to the users.
|
|
66
|
+
|
|
67
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
68
|
+
Software shall not be used to promote, endorse or advertise any
|
|
69
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
70
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
71
|
+
permission.
|
|
72
|
+
|
|
73
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
74
|
+
must be distributed entirely under this license, and must not be
|
|
75
|
+
distributed under any other license. The requirement for fonts to
|
|
76
|
+
remain under this license does not apply to any document created
|
|
77
|
+
using the Font Software.
|
|
78
|
+
|
|
79
|
+
TERMINATION
|
|
80
|
+
This license becomes null and void if any of the above conditions are
|
|
81
|
+
not met.
|
|
82
|
+
|
|
83
|
+
DISCLAIMER
|
|
84
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
85
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
86
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
87
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
88
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
89
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
90
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
91
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
92
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datalog
|
|
4
|
+
module SocialCards
|
|
5
|
+
# SVG's transforms as the six numbers of matrix(a b c d e f), which is
|
|
6
|
+
# also the order of MVG's `affine`.
|
|
7
|
+
module Geometry
|
|
8
|
+
IDENTITY_MATRIX = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0].freeze
|
|
9
|
+
NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/i
|
|
10
|
+
# The arguments hold no parenthesis, so a run of unclosed ones fails
|
|
11
|
+
# at once rather than scanning to the end from each.
|
|
12
|
+
TRANSFORM = /(matrix|translate|scale|rotate)\s*\(([^()]*)\)/
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# A `transform` attribute's list of operations, as one matrix.
|
|
17
|
+
def transform(value)
|
|
18
|
+
matrix = IDENTITY_MATRIX
|
|
19
|
+
value.to_s.scan(TRANSFORM) do |name, args|
|
|
20
|
+
matrix = multiply(matrix, operation(name, args.scan(NUMBER).map(&:to_f)))
|
|
21
|
+
end
|
|
22
|
+
matrix
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def operation(name, values)
|
|
26
|
+
case name
|
|
27
|
+
when "matrix" then values.size == 6 ? values : IDENTITY_MATRIX
|
|
28
|
+
when "translate" then [1.0, 0.0, 0.0, 1.0, values[0].to_f, values[1].to_f]
|
|
29
|
+
when "scale" then [values[0] || 1.0, 0.0, 0.0, values[1] || values[0] || 1.0, 0.0, 0.0]
|
|
30
|
+
when "rotate" then rotation(*values)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def rotation(degrees = 0.0, center_x = 0.0, center_y = 0.0)
|
|
35
|
+
radians = degrees * Math::PI / 180
|
|
36
|
+
cos = Math.cos(radians)
|
|
37
|
+
sin = Math.sin(radians)
|
|
38
|
+
turn = [cos, sin, -sin, cos, 0.0, 0.0]
|
|
39
|
+
multiply(multiply([1.0, 0.0, 0.0, 1.0, center_x, center_y], turn), [1.0, 0.0, 0.0, 1.0, -center_x, -center_y])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Two matrices composed: the result applies `inner` first.
|
|
43
|
+
def multiply(outer, inner)
|
|
44
|
+
a, b, c, d, e, f = outer
|
|
45
|
+
g, h, i, j, k, l = inner
|
|
46
|
+
[(a * g) + (c * h), (b * g) + (d * h), (a * i) + (c * j), (b * i) + (d * j),
|
|
47
|
+
(a * k) + (c * l) + e, (b * k) + (d * l) + f]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datalog
|
|
4
|
+
module SocialCards
|
|
5
|
+
# A TrueType contour as path commands. TrueType draws with quadratic
|
|
6
|
+
# curves, and two off-curve points in a row imply an on-curve point
|
|
7
|
+
# halfway between them.
|
|
8
|
+
module Outline
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# The contour's [x, y, on_curve] points, in font units with y up,
|
|
12
|
+
# placed at `left` on a baseline and scaled: one closed subpath.
|
|
13
|
+
def path(points, left, baseline, scale)
|
|
14
|
+
return "" if points.empty?
|
|
15
|
+
|
|
16
|
+
start, rest = start_and_rest(points)
|
|
17
|
+
place = ->(point) { "#{number(left + (point[0] * scale))} #{number(baseline - (point[1] * scale))}" }
|
|
18
|
+
commands = ["M #{place.call(start)}"]
|
|
19
|
+
control = nil
|
|
20
|
+
(rest + [start]).each do |point|
|
|
21
|
+
commands << segment(point, control, place)
|
|
22
|
+
control = point[2] ? nil : point
|
|
23
|
+
end
|
|
24
|
+
commands.compact.push("Z").join(" ")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Where the subpath starts: the first on-curve point, or, when every
|
|
28
|
+
# point is off the curve, halfway between the last and the first.
|
|
29
|
+
def start_and_rest(points)
|
|
30
|
+
on = points.index { |point| point[2] }
|
|
31
|
+
return [points[on], points.rotate(on).drop(1)] if on
|
|
32
|
+
|
|
33
|
+
first = points.first
|
|
34
|
+
last = points.last
|
|
35
|
+
[[(first[0] + last[0]) / 2.0, (first[1] + last[1]) / 2.0, true], points]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The command that reaches `point`, given the pending control point.
|
|
39
|
+
def segment(point, control, place)
|
|
40
|
+
if point[2]
|
|
41
|
+
control ? "Q #{place.call(control)} #{place.call(point)}" : "L #{place.call(point)}"
|
|
42
|
+
elsif control
|
|
43
|
+
middle = [(control[0] + point[0]) / 2.0, (control[1] + point[1]) / 2.0]
|
|
44
|
+
"Q #{place.call(control)} #{place.call(middle)}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def number(value)
|
|
49
|
+
rounded = value.round(2)
|
|
50
|
+
rounded == rounded.to_i ? rounded.to_i.to_s : rounded.to_s
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|