glyphic 0.1.1 → 0.1.2
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/.codespellignore +0 -0
- data/.yamllint +16 -0
- data/CHANGELOG.md +5 -2
- data/lib/glyphic/version.rb +1 -1
- data/lib/glyphic.rb +49 -24
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0e68eb3afceb9ab4a9fd3f4d3ca5d4e358481b0ec4e9929366bbddb015aa74c
|
|
4
|
+
data.tar.gz: cd24a61fb5ff6a44b92a24c02bc68ff3512b4850efee64c4ab402d138e036062
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 772f4464e4ecf3dd68e2a510dbec1349f8cc8c9a55c2f0e3123240540baff074182d34e61fd421987b671f8617619b108dcbe5d53aab59043dd8755dcfe12211
|
|
7
|
+
data.tar.gz: 66345c6bbf892723eefde799a1f327c75daf575c88a8ef51f10de9975040df110a6149233f1be20c60e5965c0701e1df9d438a9090e9420dce351b3faaeb4ce0
|
data/.codespellignore
ADDED
|
File without changes
|
data/.yamllint
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.2] - 2026-09-26
|
|
6
|
+
|
|
7
|
+
- Reject truncated or malformed BDF and TrueType fonts with clear errors.
|
|
8
|
+
|
|
5
9
|
## [0.1.1] - 2026-09-25
|
|
6
10
|
|
|
7
|
-
-
|
|
8
|
-
- Bound the Tessel dependency to compatible major versions.
|
|
11
|
+
- Prevent installation with unsupported major versions of Tessel.
|
|
9
12
|
|
|
10
13
|
## [0.1.0] - 2026-09-23
|
|
11
14
|
|
data/lib/glyphic/version.rb
CHANGED
data/lib/glyphic.rb
CHANGED
|
@@ -123,12 +123,17 @@ module Glyphic
|
|
|
123
123
|
block << lines[index]
|
|
124
124
|
index += 1
|
|
125
125
|
end
|
|
126
|
+
raise UnsupportedError, "invalid BDF glyph terminator" unless lines[index] == "ENDCHAR"
|
|
127
|
+
|
|
126
128
|
encoded = block.find { |line| line.start_with?("ENCODING") }&.split&.last
|
|
127
129
|
code = encoded&.to_i
|
|
128
130
|
if code && code >= 0
|
|
129
131
|
code = jis_to_unicode(code) if registry.start_with?("JISX0208")
|
|
130
132
|
if code
|
|
131
|
-
|
|
133
|
+
advance = block.find { |line| line.start_with?("DWIDTH ") }&.split&.[](1)
|
|
134
|
+
raise UnsupportedError, "invalid BDF glyph advance" unless advance&.match?(/\A-?\d+\z/)
|
|
135
|
+
|
|
136
|
+
glyph = parse_glyph(block, advance: advance.to_i)
|
|
132
137
|
glyphs[code] = glyph
|
|
133
138
|
else
|
|
134
139
|
skipped += 1
|
|
@@ -142,10 +147,16 @@ module Glyphic
|
|
|
142
147
|
end
|
|
143
148
|
|
|
144
149
|
def parse_glyph(block, advance:)
|
|
145
|
-
bbx = block.find { |line| line.start_with?("BBX") }
|
|
150
|
+
bbx = block.find { |line| line.start_with?("BBX ") }&.split&.drop(1)&.map { |value| Integer(value, exception: false) }
|
|
151
|
+
raise UnsupportedError, "invalid BDF glyph bounds" unless bbx&.length == 4 && bbx.all? && bbx[0] >= 0 && bbx[1] >= 0
|
|
152
|
+
|
|
146
153
|
width, height, offset_x, offset_y = bbx
|
|
147
154
|
bitmap_start = block.index("BITMAP")
|
|
155
|
+
raise UnsupportedError, "invalid BDF glyph bitmap" unless bitmap_start
|
|
156
|
+
|
|
148
157
|
rows = block[(bitmap_start + 1), height].to_a
|
|
158
|
+
raise UnsupportedError, "invalid BDF glyph bitmap" unless rows.length == height && rows.all? { |row| row.match?(/\A[0-9A-Fa-f]*\z/) && row.length * 4 >= width }
|
|
159
|
+
|
|
149
160
|
alpha = rows.map do |line|
|
|
150
161
|
value = line.to_i(16)
|
|
151
162
|
(0...width).map { |x| (value & (1 << ([line.length * 4, width].max - x - 1))).positive? ? 255 : 0 }
|
|
@@ -172,16 +183,29 @@ module Glyphic
|
|
|
172
183
|
def initialize(bytes)
|
|
173
184
|
@bytes = bytes.b
|
|
174
185
|
@tables = {}
|
|
186
|
+
raise UnsupportedError, "invalid TrueType table directory" unless @bytes.bytesize >= 12
|
|
187
|
+
|
|
175
188
|
count = @bytes.byteslice(4, 2).unpack1("n")
|
|
189
|
+
raise UnsupportedError, "invalid TrueType table directory" if 12 + count * 16 > @bytes.bytesize
|
|
190
|
+
|
|
176
191
|
count.times do |index|
|
|
177
192
|
tag, _checksum, offset, length = @bytes.byteslice(12 + index * 16, 16).unpack("a4N3")
|
|
193
|
+
raise UnsupportedError, "invalid TrueType table data" if offset > @bytes.bytesize || length > @bytes.bytesize - offset
|
|
194
|
+
|
|
178
195
|
@tables[tag] = @bytes.byteslice(offset, length)
|
|
179
196
|
end
|
|
180
197
|
end
|
|
181
198
|
|
|
182
|
-
def
|
|
183
|
-
|
|
184
|
-
|
|
199
|
+
def slice(data, offset, length)
|
|
200
|
+
part = data&.byteslice(offset, length) if offset >= 0 && length >= 0
|
|
201
|
+
raise UnsupportedError, "invalid TrueType table data" unless part && part.bytesize == length
|
|
202
|
+
|
|
203
|
+
part
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def u16(data, offset) = slice(data, offset, 2).unpack1("n")
|
|
207
|
+
def i16(data, offset) = slice(data, offset, 2).unpack1("s>")
|
|
208
|
+
def u32(data, offset) = slice(data, offset, 4).unpack1("N")
|
|
185
209
|
end
|
|
186
210
|
|
|
187
211
|
class Font < Glyphic::Font
|
|
@@ -238,13 +262,14 @@ module Glyphic
|
|
|
238
262
|
|
|
239
263
|
def build_cmap(data)
|
|
240
264
|
records = @reader.u16(data, 2).times.map do |index|
|
|
241
|
-
platform, encoding, offset =
|
|
265
|
+
platform, encoding, offset = @reader.slice(data, 4 + index * 8, 8).unpack("n2N")
|
|
242
266
|
[platform, encoding, offset]
|
|
243
267
|
end
|
|
244
268
|
selected = records.sort_by { |platform, encoding, _| platform == 3 && encoding == 10 ? 0 : platform == 3 && encoding == 1 ? 1 : platform.zero? ? 2 : 3 }.find { |_, _, offset| [4, 12].include?(@reader.u16(data, offset)) }
|
|
245
269
|
return {} unless selected
|
|
246
270
|
format = @reader.u16(data, selected[2])
|
|
247
|
-
|
|
271
|
+
subtable = @reader.slice(data, selected[2], data.bytesize - selected[2])
|
|
272
|
+
format == 12 ? cmap12(subtable) : cmap4(subtable)
|
|
248
273
|
end
|
|
249
274
|
|
|
250
275
|
def build_kern(data)
|
|
@@ -258,7 +283,7 @@ module Glyphic
|
|
|
258
283
|
if coverage & 0xff == 0
|
|
259
284
|
base = offset + 6
|
|
260
285
|
@reader.u16(data, base).times do |index|
|
|
261
|
-
left, right, value =
|
|
286
|
+
left, right, value = @reader.slice(data, base + 8 + index * 6, 6).unpack("n2s>")
|
|
262
287
|
pairs[[left, right]] = value
|
|
263
288
|
end
|
|
264
289
|
end
|
|
@@ -270,7 +295,7 @@ module Glyphic
|
|
|
270
295
|
def cmap12(data)
|
|
271
296
|
result = {}
|
|
272
297
|
@reader.u32(data, 12).times do |index|
|
|
273
|
-
start_code, end_code, start_glyph =
|
|
298
|
+
start_code, end_code, start_glyph = @reader.slice(data, 16 + index * 12, 12).unpack("N3")
|
|
274
299
|
(start_code..end_code).each { |code| result[code] = start_glyph + code - start_code }
|
|
275
300
|
end
|
|
276
301
|
result
|
|
@@ -278,13 +303,13 @@ module Glyphic
|
|
|
278
303
|
|
|
279
304
|
def cmap4(data)
|
|
280
305
|
segments = @reader.u16(data, 6) / 2
|
|
281
|
-
end_codes =
|
|
306
|
+
end_codes = @reader.slice(data, 14, segments * 2).unpack("n*")
|
|
282
307
|
start_offset = 16 + segments * 2
|
|
283
|
-
start_codes =
|
|
308
|
+
start_codes = @reader.slice(data, start_offset, segments * 2).unpack("n*")
|
|
284
309
|
delta_offset = start_offset + segments * 2
|
|
285
|
-
deltas =
|
|
310
|
+
deltas = @reader.slice(data, delta_offset, segments * 2).unpack("s>*")
|
|
286
311
|
range_offset = delta_offset + segments * 2
|
|
287
|
-
offsets =
|
|
312
|
+
offsets = @reader.slice(data, range_offset, segments * 2).unpack("n*")
|
|
288
313
|
result = {}
|
|
289
314
|
end_codes.each_index do |segment|
|
|
290
315
|
(start_codes[segment]..end_codes[segment]).each do |code|
|
|
@@ -324,26 +349,26 @@ module Glyphic
|
|
|
324
349
|
finish = glyph_offset(index + 1)
|
|
325
350
|
return [[], [], 0, 0, 0, 0, []] if finish <= offset
|
|
326
351
|
|
|
327
|
-
glyph = @
|
|
352
|
+
glyph = @reader.slice(@glyf, offset, finish - offset)
|
|
328
353
|
contours = @reader.i16(glyph, 0)
|
|
329
354
|
return composite_outline(index, glyph, stack) if contours.negative?
|
|
330
355
|
|
|
331
|
-
end_points =
|
|
356
|
+
end_points = @reader.slice(glyph, 10, contours * 2).unpack("n*")
|
|
332
357
|
point_count = end_points.last.to_i + 1
|
|
333
358
|
cursor = 10 + contours * 2
|
|
334
359
|
instruction_length = @reader.u16(glyph, cursor)
|
|
335
360
|
cursor += 2 + instruction_length
|
|
336
361
|
flags = []
|
|
337
362
|
while flags.length < point_count
|
|
338
|
-
flag = glyph.getbyte(
|
|
363
|
+
flag = @reader.slice(glyph, cursor, 1).getbyte(0)
|
|
339
364
|
cursor += 1
|
|
340
|
-
repeat = flag.anybits?(8) ? glyph.getbyte(
|
|
365
|
+
repeat = flag.anybits?(8) ? @reader.slice(glyph, cursor, 1).getbyte(0).tap { cursor += 1 } : 0
|
|
341
366
|
flags.concat([flag] * (repeat + 1))
|
|
342
367
|
end
|
|
343
368
|
xs = read_coordinates(glyph, flags, cursor, point_count, horizontal: true)
|
|
344
369
|
ys = read_coordinates(glyph, flags, xs[1], point_count, horizontal: false)
|
|
345
370
|
raw_points = xs[0].zip(ys[0]).map { |x, y| [x, y] }
|
|
346
|
-
x_min, y_min, x_max, y_max =
|
|
371
|
+
x_min, y_min, x_max, y_max = @reader.slice(glyph, 2, 8).unpack("s>4")
|
|
347
372
|
points, flattened_end_points = flatten_contours(raw_points, flags, end_points)
|
|
348
373
|
[points, flattened_end_points, x_min, y_min, x_max, y_max, raw_points]
|
|
349
374
|
end
|
|
@@ -368,11 +393,11 @@ module Glyphic
|
|
|
368
393
|
cursor += 4
|
|
369
394
|
word_args = flags.anybits?(1)
|
|
370
395
|
arg1, arg2 = if word_args
|
|
371
|
-
values =
|
|
396
|
+
values = @reader.slice(glyph, cursor, 4).unpack("s>2")
|
|
372
397
|
cursor += 4
|
|
373
398
|
values
|
|
374
399
|
else
|
|
375
|
-
values =
|
|
400
|
+
values = @reader.slice(glyph, cursor, 2).unpack("c2")
|
|
376
401
|
cursor += 2
|
|
377
402
|
values
|
|
378
403
|
end
|
|
@@ -476,9 +501,9 @@ module Glyphic
|
|
|
476
501
|
|
|
477
502
|
def component_transform(glyph, flags, cursor)
|
|
478
503
|
if flags.anybits?(128)
|
|
479
|
-
|
|
504
|
+
@reader.slice(glyph, cursor, 8).unpack("s>4").map { |value| value / 16_384.0 }
|
|
480
505
|
elsif flags.anybits?(64)
|
|
481
|
-
x, y =
|
|
506
|
+
x, y = @reader.slice(glyph, cursor, 4).unpack("s>2").map { |value| value / 16_384.0 }
|
|
482
507
|
[x, 0, 0, y]
|
|
483
508
|
elsif flags.anybits?(8)
|
|
484
509
|
scale = @reader.i16(glyph, cursor) / 16_384.0
|
|
@@ -502,13 +527,13 @@ module Glyphic
|
|
|
502
527
|
short = horizontal ? flag.anybits?(2) : flag.anybits?(4)
|
|
503
528
|
same = horizontal ? flag.anybits?(16) : flag.anybits?(32)
|
|
504
529
|
if short
|
|
505
|
-
delta = glyph.getbyte(
|
|
530
|
+
delta = @reader.slice(glyph, cursor, 1).getbyte(0)
|
|
506
531
|
cursor += 1
|
|
507
532
|
delta = -delta if !same
|
|
508
533
|
elsif same
|
|
509
534
|
delta = 0
|
|
510
535
|
else
|
|
511
|
-
delta =
|
|
536
|
+
delta = @reader.slice(glyph, cursor, 2).unpack1("s>")
|
|
512
537
|
cursor += 2
|
|
513
538
|
end
|
|
514
539
|
previous += delta
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glyphic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yudai Takada
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tessel
|
|
@@ -37,6 +37,8 @@ executables: []
|
|
|
37
37
|
extensions: []
|
|
38
38
|
extra_rdoc_files: []
|
|
39
39
|
files:
|
|
40
|
+
- ".codespellignore"
|
|
41
|
+
- ".yamllint"
|
|
40
42
|
- CHANGELOG.md
|
|
41
43
|
- LICENSE.txt
|
|
42
44
|
- README.md
|