v2 / vlib / x / ttf / ttf.v
1189 lines · 1056 sloc · 29.99 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1module ttf
2
3/**********************************************************************
4*
5* TrueTypeFont reader V implementation
6*
7* Copyright (c) 2021 Dario Deledda. All rights reserved.
8* Use of this source code is governed by an MIT license
9* that can be found in the LICENSE file.
10*
11* Note:
12* - inspired by: http://stevehanov.ca/blog/?id=143
13*
14* TODO:
15* - check for unicode > 0xFFFF if supported
16* - evaluate use a buffer for the points in the glyph
17**********************************************************************/
18import strings
19
20/******************************************************************************
21*
22* CMAP structs
23*
24******************************************************************************/
25struct Segment {
26mut:
27 id_range_offset u32
28 start_code u16
29 end_code u16
30 id_delta u16
31}
32
33struct TrueTypeCmap {
34mut:
35 format int
36 cache []int = []int{len: 65536, init: -1} // for now we allocate 2^16 charcode
37 segments []Segment
38 arr []int
39}
40
41// TTF_File represents the data contents of a complete `*.ttf` file.
42// The struct is usually initialized by reading raw TTF data into the `buf` member field
43// for example by doing: `ttf_font.buf = os.read_bytes("arial.ttf") or { panic(err) }`,
44// and then run the `init/0` method, for example: `ttf_font.init()`
45pub struct TTF_File {
46pub mut:
47 buf []u8
48 pos u32
49 length u16
50 scalar_type u32
51 search_range u16
52 entry_selector u16
53 range_shift u16
54 tables map[string]Offset_Table
55 version f32
56 font_revision f32
57 checksum_adjustment u32
58 magic_number u32
59 flags u16
60 units_per_em u16
61 created u64
62 modified u64
63 x_min f32
64 y_min f32
65 x_max f32
66 y_max f32
67 mac_style u16
68 lowest_rec_ppem u16
69 font_direction_hint i16
70 index_to_loc_format i16
71 glyph_data_format i16
72 font_family string
73 font_sub_family string
74 full_name string
75 postscript_name string
76 cmaps []TrueTypeCmap
77 ascent i16
78 descent i16
79 line_gap i16
80 advance_width_max u16
81 min_left_side_bearing i16
82 min_right_side_bearing i16
83 x_max_extent i16
84 caret_slope_rise i16
85 caret_slope_run i16
86 caret_offset i16
87 metric_data_format i16
88 num_of_long_hor_metrics u16
89 kern []Kern0Table
90 // panose
91 panose_array []u8 = []u8{len: 12, init: 0}
92 // cache
93 glyph_cache map[int]Glyph
94 // font widths array scale for PDF export
95 width_scale f32 = 1.0
96}
97
98// init initializes essential `TTF_File` fields from the contents of `buf`.
99pub fn (mut tf TTF_File) init() {
100 tf.read_offset_tables()
101 tf.read_head_table()
102 // dprintln(tf.get_info_string())
103 tf.read_name_table()
104 tf.read_cmap_table()
105 tf.read_hhea_table()
106 tf.read_kern_table()
107 tf.read_panose_table()
108 tf.length = tf.glyph_count()
109 dprintln('Number of symbols: ${tf.length}')
110 dprintln('*****************************')
111 dprintln('Unit per em: ${tf.units_per_em}')
112 dprintln('advance_width_max: ${tf.advance_width_max}')
113}
114
115// Point represents a 2D point.
116pub struct Point {
117pub mut:
118 x int
119 y int
120 on_curve bool
121}
122
123struct Gylph_Component {
124mut:
125 points []Point
126}
127
128// type of glyph
129const g_type_simple = u16(1) // simple type
130
131const g_type_complex = u16(2)
132
133// Glyph represents a single renderable unit ("a character") of the TTF.
134pub struct Glyph {
135pub mut:
136 g_type u16 = g_type_simple
137 contour_ends []u16
138 number_of_contours i16
139 points []Point
140 x_min i16
141 x_max i16
142 y_min i16
143 y_max i16
144 valid_glyph bool
145 components []Component
146}
147
148// TTF_File metrics and glyph
149
150// get_horizontal_metrics returns the horizontal metrics `advance_width` and `left_side_bearing` for the glyph at index `glyph_index`.
151pub fn (mut tf TTF_File) get_horizontal_metrics(glyph_index u16) (int, int) {
152 assert 'hmtx' in tf.tables
153 old_pos := tf.pos
154 mut offset := tf.tables['hmtx'].offset
155
156 mut advance_width := 0
157 mut left_side_bearing := 0
158 if glyph_index < tf.num_of_long_hor_metrics {
159 offset += u32(glyph_index) * 4
160 tf.pos = offset
161 advance_width = tf.get_u16()
162 left_side_bearing = tf.get_i16()
163 // dprintln("${glyph_index} aw:${advance_width} lsb:${left_side_bearing}")
164 } else {
165 // read the last entry of the hMetrics array
166 tf.pos = offset + u32(tf.num_of_long_hor_metrics - 1) * 4
167 advance_width = tf.get_u16()
168 tf.pos = offset + u32(tf.num_of_long_hor_metrics) * 4 + 2 * u32(glyph_index -
169 tf.num_of_long_hor_metrics)
170 left_side_bearing = tf.get_fword()
171 }
172 tf.pos = old_pos
173 return advance_width, left_side_bearing
174}
175
176fn (mut tf TTF_File) get_glyph_offset(index u32) u32 {
177 // check if needed tables exists
178 assert 'loca' in tf.tables
179 assert 'glyf' in tf.tables
180 mut old_pos := tf.pos
181
182 table := tf.tables['loca']
183 mut offset := u32(0)
184 mut next := u32(0)
185 if tf.index_to_loc_format == 1 {
186 tf.pos = table.offset + (index << 2)
187 offset = tf.get_u32()
188 next = tf.get_u32()
189 } else {
190 tf.pos = table.offset + (index << 1)
191 offset = tf.get_u16() << 1
192 next = tf.get_u16() << 1
193 }
194
195 if offset == next {
196 // indicates glyph has no outline( eg space)
197 return 0
198 }
199 // dprintln("Offset for glyph index ${index} is ${offset}")
200 tf.pos = old_pos
201 return offset + tf.tables['glyf'].offset
202}
203
204// glyph_count returns the number of glyphs available in the TTF.
205pub fn (mut tf TTF_File) glyph_count() u16 {
206 assert 'maxp' in tf.tables
207 old_pos := tf.pos
208 tf.pos = tf.tables['maxp'].offset + 4
209 count := tf.get_u16()
210 tf.pos = old_pos
211 return count
212}
213
214// read_glyph_dim returns glyph dimension data in the form `x_min`, `x_max`, `y_min` and `y_max`.
215pub fn (mut tf TTF_File) read_glyph_dim(index u16) (int, int, int, int) {
216 offset := tf.get_glyph_offset(index)
217 // dprintln("offset: ${offset}")
218 if offset == 0 || offset >= tf.tables['glyf'].offset + tf.tables['glyf'].length {
219 dprintln('No glyph found!')
220 return 0, 0, 0, 0
221 }
222
223 assert offset >= tf.tables['glyf'].offset
224 assert offset < tf.tables['glyf'].offset + tf.tables['glyf'].length
225
226 tf.pos = offset
227 // dprintln("file seek read_glyph: ${tf.pos}")
228
229 // number_of_contours
230 _ := tf.get_i16()
231 x_min := tf.get_fword()
232 y_min := tf.get_fword()
233 x_max := tf.get_fword()
234 y_max := tf.get_fword()
235
236 return x_min, x_max, y_min, y_max
237}
238
239// get_ttf_widths returns all possible widths of the TTF.
240pub fn (mut tf TTF_File) get_ttf_widths() ([]int, int, int) {
241 mut space_cw, _ := tf.get_horizontal_metrics(u16(` `))
242 // div_space_cw := int((f32(space_cw) * 0.3))
243
244 // count := int(tf.glyph_count())
245 mut min_code := 0xFFFF + 1
246 mut max_code := 0
247 for i in 0 .. 300 {
248 glyph_index := tf.map_code(i)
249 if glyph_index == 0 {
250 continue
251 }
252 // dprintln("${i} = glyph_index: ${glyph_index} ${i:c}")
253 if i > max_code {
254 max_code = i
255 }
256 if i < min_code {
257 min_code = i
258 }
259 }
260 // dprintln("min_code: ${min_code} max_code: ${max_code}")
261 mut widths := []int{len: max_code - min_code + 1, init: 0}
262
263 for i in min_code .. max_code {
264 pos := i - min_code
265 glyph_index := tf.map_code(i)
266
267 if glyph_index == 0 || i == 32 {
268 widths[pos] = space_cw
269 continue
270 }
271
272 x_min, x_max, _, _ := tf.read_glyph_dim(glyph_index)
273 aw, lsb := tf.get_horizontal_metrics(u16(glyph_index))
274 w := x_max - x_min
275 rsb := aw - (lsb + w)
276
277 // pp1 := x_min - lsb
278 // pp2 := pp1 + aw
279
280 w1 := w + lsb + rsb
281
282 widths[pos] = int(w1 / tf.width_scale)
283 // if i >= int(`A`) && i <= int(`Z`) {
284 // dprintln("${i:c}|${glyph_index} [${pos}] => width:${x_max-x_min} aw:${aw}|w1:${w1} lsb:${lsb} rsb:${rsb} pp1:${pp1} pp2:${pp2}")
285 //}
286 }
287
288 // dprintln("Widths: ${widths.len}")
289 return widths, min_code, max_code
290}
291
292// read_glyph returns `Glyph` data for the glyph at `index`.
293pub fn (mut tf TTF_File) read_glyph(index u16) Glyph {
294 index_int := int(index) // index.str()
295 if index_int in tf.glyph_cache {
296 // dprintln("Found glyp: ${index}")
297 return tf.glyph_cache[index_int]
298 }
299 // dprintln("Create glyp: ${index}")
300
301 offset := tf.get_glyph_offset(index)
302 // dprintln("offset: ${offset}")
303 if offset == 0 || offset >= tf.tables['glyf'].offset + tf.tables['glyf'].length {
304 dprintln('No glyph found!')
305 return Glyph{}
306 }
307
308 assert offset >= tf.tables['glyf'].offset
309 assert offset < tf.tables['glyf'].offset + tf.tables['glyf'].length
310
311 tf.pos = offset
312 // dprintln("file seek read_glyph: ${tf.pos}")
313
314 /*
315 ---- BUG TO SOLVE -----
316 --- Order of the data if printed in the main is shuffled!! Very Strange
317 mut tmp_glyph := Glyph{
318 number_of_contours : tf.get_i16()
319 x_min : tf.get_fword()
320 y_min : tf.get_fword()
321 x_max : tf.get_fword()
322 y_max : tf.get_fword()
323 }
324 */
325
326 mut tmp_glyph := Glyph{}
327 tmp_glyph.number_of_contours = tf.get_i16()
328 tmp_glyph.x_min = tf.get_fword()
329 tmp_glyph.y_min = tf.get_fword()
330 tmp_glyph.x_max = tf.get_fword()
331 tmp_glyph.y_max = tf.get_fword()
332
333 // dprintln("file seek after read_glyph: ${tf.pos}")
334
335 assert tmp_glyph.number_of_contours >= -1
336
337 if tmp_glyph.number_of_contours == -1 {
338 // dprintln("read_compound_glyph")
339 tf.read_compound_glyph(mut tmp_glyph)
340 } else {
341 // dprintln("read_simple_glyph")
342 tf.read_simple_glyph(mut tmp_glyph)
343 }
344
345 tf.glyph_cache[index_int] = tmp_glyph
346 return tmp_glyph
347}
348
349const tfk_on_curve = 1
350const tfk_x_is_byte = 2
351const tfk_y_is_byte = 4
352const tfk_repeat = 8
353const tfk_x_delta = 16
354const tfk_y_delta = 32
355
356fn (mut tf TTF_File) read_simple_glyph(mut in_glyph Glyph) {
357 if in_glyph.number_of_contours == 0 {
358 return
359 }
360
361 for _ in 0 .. in_glyph.number_of_contours {
362 in_glyph.contour_ends << tf.get_u16()
363 }
364
365 // skip over instructions
366 tf.pos = tf.get_u16() + tf.pos
367
368 mut num_points := 0
369 for ce in in_glyph.contour_ends {
370 if ce > num_points {
371 num_points = ce
372 }
373 }
374 num_points++
375
376 mut i := 0
377 mut flags := []u8{}
378 for i < num_points {
379 flag := tf.get_u8()
380 flags << flag
381 in_glyph.points << Point{
382 x: 0
383 y: 0
384 on_curve: (flag & tfk_on_curve) > 0
385 }
386 if (flag & tfk_repeat) > 0 {
387 mut repeat_count := tf.get_u8()
388 assert repeat_count > 0
389 i += repeat_count
390 for repeat_count > 0 {
391 flags << flag
392 in_glyph.points << Point{
393 x: 0
394 y: 0
395 on_curve: (flag & tfk_on_curve) > 0
396 }
397 repeat_count--
398 }
399 }
400 i++
401 }
402
403 // read coords x
404 mut value := 0
405 for i_x in 0 .. num_points {
406 flag_x := flags[i_x]
407 if (flag_x & tfk_x_is_byte) > 0 {
408 if (flag_x & tfk_x_delta) > 0 {
409 value += tf.get_u8()
410 } else {
411 value -= tf.get_u8()
412 }
413 } else if (~flag_x & tfk_x_delta) > 0 {
414 value += tf.get_i16()
415 } else {
416 // value is unchanged
417 }
418 // dprintln("${i_x} x: ${value}")
419 in_glyph.points[i_x].x = value
420 }
421
422 // read coords y
423 value = 0
424 for i_y in 0 .. num_points {
425 flag_y := flags[i_y]
426 if (flag_y & tfk_y_is_byte) > 0 {
427 if (flag_y & tfk_y_delta) > 0 {
428 value += tf.get_u8()
429 } else {
430 value -= tf.get_u8()
431 }
432 } else if (~flag_y & tfk_y_delta) > 0 {
433 value += tf.get_i16()
434 } else {
435 // value is unchanged
436 }
437 // dprintln("${i_y} y: ${value}")
438 in_glyph.points[i_y].y = value
439 }
440
441 // ok we have a valid glyph
442 in_glyph.valid_glyph = true
443}
444
445const tfkc_arg_1_and_2_are_words = 1
446const tfkc_args_are_xy_values = 2
447const tfkc_round_xy_to_grid = 4
448const tfkc_we_have_a_scale = 8
449// reserved = 16
450const tfkc_more_components = 32
451const tfkc_we_have_an_x_and_y_scale = 64
452const tfkc_we_have_a_two_by_two = 128
453const tfkc_we_have_instructions = 256
454const tfkc_use_my_metrics = 512
455const tfkc_overlap_component = 1024
456
457struct Component {
458mut:
459 glyph_index u16
460 dest_point_index i16
461 src_point_index i16
462 matrix []f32 = [f32(1.0), 0, 0, 1.0, 0, 0]
463}
464
465fn (mut tf TTF_File) read_compound_glyph(mut in_glyph Glyph) {
466 in_glyph.g_type = g_type_complex
467 mut component := Component{}
468 mut flags := tfkc_more_components
469 for (flags & tfkc_more_components) > 0 {
470 mut arg1 := i16(0)
471 mut arg2 := i16(0)
472
473 flags = tf.get_u16()
474
475 component.glyph_index = tf.get_u16()
476
477 if (flags & tfkc_arg_1_and_2_are_words) > 0 {
478 arg1 = tf.get_i16()
479 arg2 = tf.get_i16()
480 } else {
481 arg1 = tf.get_u8()
482 arg2 = tf.get_u8()
483 }
484
485 if (flags & tfkc_args_are_xy_values) > 0 {
486 component.matrix[4] = arg1
487 component.matrix[5] = arg2
488 } else {
489 component.dest_point_index = arg1
490 component.src_point_index = arg2
491 }
492
493 if (flags & tfkc_we_have_a_scale) > 0 {
494 component.matrix[0] = tf.get_2dot14()
495 component.matrix[3] = component.matrix[0]
496 } else if (flags & tfkc_we_have_an_x_and_y_scale) > 0 {
497 component.matrix[0] = tf.get_2dot14()
498 component.matrix[3] = tf.get_2dot14()
499 } else if (flags & tfkc_we_have_a_two_by_two) > 0 {
500 component.matrix[0] = tf.get_2dot14()
501 component.matrix[1] = tf.get_2dot14()
502 component.matrix[2] = tf.get_2dot14()
503 component.matrix[3] = tf.get_2dot14()
504 }
505 // dprintln("Read component glyph index ${component.glyph_index}")
506 // dprintln("Transform: ${component.matrix}")
507
508 old_pos := tf.pos
509
510 simple_glyph := tf.read_glyph(component.glyph_index)
511 if simple_glyph.valid_glyph {
512 point_offset := in_glyph.points.len
513 for i in 0 .. simple_glyph.contour_ends.len {
514 in_glyph.contour_ends << u16(simple_glyph.contour_ends[i] + point_offset)
515 }
516
517 for p in simple_glyph.points {
518 mut x := f32(p.x)
519 mut y := f32(p.y)
520 x = component.matrix[0] * x + component.matrix[1] * y + component.matrix[4]
521 y = component.matrix[2] * x + component.matrix[3] * y + component.matrix[5]
522 in_glyph.points << Point{
523 x: int(x)
524 y: int(y)
525 on_curve: p.on_curve
526 }
527 }
528 }
529 tf.pos = old_pos
530 }
531
532 in_glyph.number_of_contours = i16(in_glyph.contour_ends.len)
533
534 if (flags & tfkc_we_have_instructions) > 0 {
535 tf.pos = tf.get_u16() + tf.pos
536 }
537 // ok we have a valid glyph
538 in_glyph.valid_glyph = true
539}
540
541/******************************************************************************
542*
543* TTF_File get functions
544*
545******************************************************************************/
546fn (mut tf TTF_File) get_u8() u8 {
547 x := tf.buf[tf.pos]
548 tf.pos++
549 return u8(x)
550}
551
552fn (mut tf TTF_File) get_i8() i8 {
553 return i8(tf.get_u8())
554}
555
556fn (mut tf TTF_File) get_u16() u16 {
557 x := u16(tf.buf[tf.pos]) << 8 | u16(tf.buf[tf.pos + 1])
558 tf.pos += 2
559 return x
560}
561
562fn (mut tf TTF_File) get_ufword() u16 {
563 return tf.get_u16()
564}
565
566fn (mut tf TTF_File) get_i16() i16 {
567 // return i16(tf.get_u16())
568 mut res := u32(tf.get_u16())
569 if (res & 0x8000) > 0 {
570 res -= (u32(1) << 16)
571 }
572 return i16(res)
573}
574
575fn (mut tf TTF_File) get_fword() i16 {
576 return tf.get_i16()
577}
578
579fn (mut tf TTF_File) get_u32() u32 {
580 x := (u32(tf.buf[tf.pos]) << u32(24)) | (u32(tf.buf[tf.pos + 1]) << u32(16)) | (u32(tf.buf[
581 tf.pos + 2]) << u32(8)) | u32(tf.buf[tf.pos + 3])
582 tf.pos += 4
583 return x
584}
585
586fn (mut tf TTF_File) get_i32() int {
587 mut res := u64(tf.get_u32())
588 if (res & 0x8000_0000) > 0 {
589 res -= (u64(1) << 32)
590 }
591 return int(res)
592}
593
594fn (mut tf TTF_File) get_2dot14() f32 {
595 return f32(tf.get_i16()) / f32(i16(1 << 14))
596}
597
598fn (mut tf TTF_File) get_fixed() f32 {
599 return f32(tf.get_i32() / f32(1 << 16))
600}
601
602fn (mut tf TTF_File) get_string(length int) string {
603 tmp_pos := u64(tf.pos)
604 tf.pos += u32(length)
605 return unsafe { tos(&u8(u64(tf.buf.data) + tmp_pos), length) }
606}
607
608fn (mut tf TTF_File) get_unicode_string(length int) string {
609 mut tmp_txt := strings.new_builder(length)
610 mut real_len := 0
611
612 for _ in 0 .. (length >> 1) {
613 c := tf.get_u16()
614 c_len := int(((u32(0xe5000000) >> ((c >> 3) & 0x1e)) & 3) + 1)
615 real_len += c_len
616 if c_len == 1 {
617 tmp_txt.write_u8(u8(c & 0xff))
618 } else {
619 tmp_txt.write_u8(u8((c >> 8) & 0xff))
620 tmp_txt.write_u8(u8(c & 0xff))
621 }
622 // dprintln("c: ${c:c}|${ u8(c &0xff) :c} c_len: ${c_len} str_len: ${real_len} in_len: ${length}")
623 }
624 tf.pos += u32(real_len)
625 res_txt := tmp_txt.str()
626 // dprintln("get_unicode_string: ${res_txt}")
627 return res_txt
628}
629
630fn (mut tf TTF_File) get_date() u64 {
631 // get mac time and covert it to unix timestamp
632 mac_time := (u64(tf.get_u32()) << 32) + u64(tf.get_u32())
633 utc_time := mac_time - u64(2082844800)
634 return utc_time
635}
636
637fn (mut tf TTF_File) calc_checksum(offset u32, length u32) u32 {
638 old_index := tf.pos
639 mut sum := u64(0)
640 mut nlongs := int((length + 3) >> 2)
641 tf.pos = offset
642 // dprintln("offs: ${offset} nlongs: ${nlongs}")
643 for nlongs > 0 {
644 sum = sum + u64(tf.get_u32())
645 nlongs--
646 }
647 tf.pos = old_index
648 return u32(sum & u64(0xffff_ffff))
649}
650
651/******************************************************************************
652*
653* Offset_Table
654*
655******************************************************************************/
656struct Offset_Table {
657mut:
658 checksum u32
659 offset u32
660 length u32
661}
662
663fn (mut tf TTF_File) read_offset_tables() {
664 dprintln('*** READ TABLES OFFSET ***')
665 tf.pos = 0
666 tf.scalar_type = tf.get_u32()
667 num_tables := tf.get_u16()
668 tf.search_range = tf.get_u16()
669 tf.entry_selector = tf.get_u16()
670 tf.range_shift = tf.get_u16()
671
672 dprintln('scalar_type : [0x${tf.scalar_type.hex()}]')
673 dprintln('num tables : [${num_tables}]')
674 dprintln('search_range : [0x${tf.search_range.hex()}]')
675 dprintln('entry_selector: [0x${tf.entry_selector.hex()}]')
676 dprintln('range_shift : [0x${tf.range_shift.hex()}]')
677
678 mut i := 0
679 for i < num_tables {
680 tag := tf.get_string(4)
681 tf.tables[tag] = Offset_Table{
682 checksum: tf.get_u32()
683 offset: tf.get_u32()
684 length: tf.get_u32()
685 }
686 dprintln('Table: [${tag}]')
687 // dprintln("${tf.tables[tag]}")
688
689 if tag != 'head' {
690 assert tf.calc_checksum(tf.tables[tag].offset, tf.tables[tag].length) == tf.tables[tag].checksum
691 }
692 i++
693 }
694 dprintln('*** END READ TABLES OFFSET ***')
695}
696
697/******************************************************************************
698*
699* Head_Table
700*
701******************************************************************************/
702fn (mut tf TTF_File) read_head_table() {
703 dprintln('*** READ HEAD TABLE ***')
704 tf.pos = tf.tables['head'].offset
705 dprintln('Offset: ${tf.pos}')
706
707 tf.version = tf.get_fixed()
708 tf.font_revision = tf.get_fixed()
709 tf.checksum_adjustment = tf.get_u32()
710 tf.magic_number = tf.get_u32()
711 assert tf.magic_number == 0x5f0f3cf5
712 tf.flags = tf.get_u16()
713 tf.units_per_em = tf.get_u16()
714 tf.created = tf.get_date()
715 tf.modified = tf.get_date()
716 tf.x_min = tf.get_i16()
717 tf.y_min = tf.get_i16()
718 tf.x_max = tf.get_i16()
719 tf.y_max = tf.get_i16()
720 tf.mac_style = tf.get_u16()
721 tf.lowest_rec_ppem = tf.get_u16()
722 tf.font_direction_hint = tf.get_i16()
723 tf.index_to_loc_format = tf.get_i16()
724 tf.glyph_data_format = tf.get_i16()
725}
726
727/******************************************************************************
728*
729* Name_Table
730*
731******************************************************************************/
732fn (mut tf TTF_File) read_name_table() {
733 dprintln('*** READ NAME TABLE ***')
734 assert 'name' in tf.tables
735 table_offset := tf.tables['name'].offset
736 tf.pos = tf.tables['name'].offset
737
738 format := tf.get_u16() // must be 0
739 assert format == 0
740 count := tf.get_u16()
741 string_offset := tf.get_u16()
742
743 for _ in 0 .. count {
744 platform_id := tf.get_u16()
745 // platform_specific_id :=
746 tf.get_u16()
747 // language_id :=
748 tf.get_u16()
749 name_id := tf.get_u16()
750 length := tf.get_u16()
751 offset := tf.get_u16()
752
753 old_pos := tf.pos
754 tf.pos = u32(table_offset) + u32(string_offset) + u32(offset)
755
756 mut name := ''
757 if platform_id == 0 || platform_id == 3 {
758 name = tf.get_unicode_string(length)
759 } else {
760 name = tf.get_string(length)
761 }
762 // dprintln("Name [${platform_id} / ${platform_specific_id}] id:[${name_id}] language:[${language_id}] [${name}]")
763 tf.pos = old_pos
764
765 match name_id {
766 1 { tf.font_family = name }
767 2 { tf.font_sub_family = name }
768 4 { tf.full_name = name }
769 6 { tf.postscript_name = name }
770 else {}
771 }
772 }
773}
774
775/******************************************************************************
776*
777* Cmap_Table
778*
779******************************************************************************/
780fn (mut tf TTF_File) read_cmap_table() {
781 dprintln('*** READ CMAP TABLE ***')
782 assert 'cmap' in tf.tables
783 table_offset := tf.tables['cmap'].offset
784 tf.pos = table_offset
785
786 version := tf.get_u16() // must be 0
787 assert version == 0
788 number_sub_tables := tf.get_u16()
789
790 // tables must be sorted by platform id and then platform specific
791 // encoding.
792 for _ in 0 .. number_sub_tables {
793 // platforms are:
794 // 0 - Unicode -- use specific id 6 for full coverage. 0/4 common.
795 // 1 - Macintosh (Discouraged)
796 // 2 - reserved
797 // 3 - Microsoft
798 platform_id := tf.get_u16()
799 platform_specific_id := tf.get_u16()
800 offset := tf.get_u32()
801 dprintln('CMap platform_id=${platform_id} specific_id=${platform_specific_id} offset=${offset}')
802 if platform_id == 3 && platform_specific_id <= 1 {
803 tf.read_cmap(table_offset + offset)
804 }
805 }
806}
807
808fn (mut tf TTF_File) read_cmap(offset u32) {
809 old_pos := tf.pos
810 tf.pos = offset
811 format := tf.get_u16()
812 length := tf.get_u16()
813 language := tf.get_u16()
814
815 dprintln(' Cmap format: ${format} length: ${length} language: ${language}')
816 if format == 0 {
817 dprintln(' Cmap 0 Init...')
818 mut cmap := TrueTypeCmap{}
819 cmap.init_0(mut tf)
820 tf.cmaps << cmap
821 } else if format == 4 {
822 dprintln(' Cmap 4 Init...')
823 mut cmap := TrueTypeCmap{}
824 cmap.init_4(mut tf)
825 tf.cmaps << cmap
826 }
827
828 tf.pos = old_pos
829}
830
831/******************************************************************************
832*
833* CMAPS 0/4
834*
835******************************************************************************/
836// map_code returns the glyph index for the `char_code` character code.
837// map_code returns `0` if the character code could not be found.
838pub fn (mut tf TTF_File) map_code(char_code int) u16 {
839 mut index := 0
840 for i in 0 .. tf.cmaps.len {
841 mut cmap := tf.cmaps[i]
842 if cmap.format == 0 {
843 // dprintln("format 0")
844 index = cmap.map_0(char_code)
845 } else if cmap.format == 4 {
846 // dprintln("format 4")
847 index = cmap.map_4(char_code, mut tf)
848 }
849 }
850 return u16(index)
851}
852
853fn (mut tm TrueTypeCmap) init_0(mut tf TTF_File) {
854 tm.format = 0
855 for i in 0 .. 256 {
856 glyph_index := tf.get_u8()
857 dprintln(' Glyph[${i}] = %glyph_index')
858 tm.arr << glyph_index
859 }
860}
861
862fn (mut tm TrueTypeCmap) map_0(char_code int) int {
863 if char_code >= 0 && char_code <= 255 {
864 // dprintln("charCode ${char_code} maps to ${tm.arr[char_code]}")
865 return tm.arr[char_code]
866 }
867 return 0
868}
869
870fn (mut tm TrueTypeCmap) init_4(mut tf TTF_File) {
871 tm.format = 4
872
873 // 2x segcount
874 seg_count := tf.get_u16() >> 1
875 // search_range :=
876 tf.get_u16()
877 // entry_selector :=
878 tf.get_u16()
879 // range_shift :=
880 tf.get_u16()
881
882 // Ending character code for each segment, last is 0xffff
883 for _ in 0 .. seg_count {
884 tm.segments << Segment{0, 0, tf.get_u16(), 0}
885 }
886
887 // reservePAD
888 tf.get_u16()
889
890 // starting character code for each segment
891 for i in 0 .. seg_count {
892 tm.segments[i].start_code = tf.get_u16()
893 }
894
895 // Delta for all character codes in segment
896 for i in 0 .. seg_count {
897 tm.segments[i].id_delta = tf.get_u16()
898 }
899
900 // offset in bytes to glyph indexArray, or 0
901 for i in 0 .. seg_count {
902 ro := u32(tf.get_u16())
903 if ro != 0 {
904 tm.segments[i].id_range_offset = tf.pos - 2 + ro
905 } else {
906 tm.segments[i].id_range_offset = 0
907 }
908 }
909 /*
910 // DEBUG LOG
911 for i in 0..seg_count {
912 seg := tm.segments[i]
913 dprintln(" segments[${i}] = ${seg.start_code} ${seg.end_code} ${seg.id_delta} ${seg.id_range_offset}")
914 }
915 */
916}
917
918fn (mut tm TrueTypeCmap) map_4(char_code int, mut tf TTF_File) int {
919 // dprintln("HERE map_4 for char [${char_code}]")
920 old_pos := tf.pos
921 if tm.cache[char_code] == -1 {
922 // dprintln("Not found, search for it!")
923 mut found := false
924 for segment in tm.segments {
925 if segment.start_code <= char_code && segment.end_code >= char_code {
926 mut index := (segment.id_delta + char_code) & 0xffff
927 if segment.id_range_offset > 0 {
928 glyph_index_address := u32(segment.id_range_offset) + 2 * u32(char_code -
929 segment.start_code)
930 tf.pos = glyph_index_address
931 index = tf.get_u16()
932 }
933
934 tm.cache[char_code] = index
935 found = true
936 break
937 }
938 }
939 if !found {
940 tm.cache[char_code] = 0
941 }
942 }
943 tf.pos = old_pos
944 return tm.cache[char_code]
945}
946
947/******************************************************************************
948*
949* Hhea table
950*
951******************************************************************************/
952fn (mut tf TTF_File) read_hhea_table() {
953 dprintln('*** READ HHEA TABLE ***')
954 assert 'hhea' in tf.tables
955 table_offset := tf.tables['hhea'].offset
956 tf.pos = table_offset
957
958 // version :=
959 tf.get_fixed() // 0x00010000
960
961 tf.ascent = tf.get_fword()
962 tf.descent = tf.get_fword()
963 tf.line_gap = tf.get_fword()
964 tf.advance_width_max = tf.get_ufword()
965 tf.min_left_side_bearing = tf.get_fword()
966 tf.min_right_side_bearing = tf.get_fword()
967 tf.x_max_extent = tf.get_fword()
968 tf.caret_slope_rise = tf.get_i16()
969 tf.caret_slope_run = tf.get_i16()
970 tf.caret_offset = tf.get_fword()
971 tf.get_i16() // reserved
972 tf.get_i16() // reserved
973 tf.get_i16() // reserved
974 tf.get_i16() // reserved
975 tf.metric_data_format = tf.get_i16()
976 tf.num_of_long_hor_metrics = tf.get_u16()
977}
978
979/******************************************************************************
980*
981* Kern table
982*
983******************************************************************************/
984struct Kern0Table {
985mut:
986 swap bool
987 offset u32
988 n_pairs int
989 kmap map[u32]i16
990 old_index int = -1
991}
992
993fn (mut kt Kern0Table) reset() {
994 kt.old_index = -1
995}
996
997fn (mut kt Kern0Table) get(glyph_index int) (int, int) {
998 mut x := 0
999
1000 if kt.old_index >= 0 {
1001 ch := ((u32(kt.old_index & 0xFFFF) << 16) | u32(glyph_index & 0xFFFF))
1002 // dprintln("kern_get: ${ch}")
1003 if ch in kt.kmap {
1004 x = int(kt.kmap[ch])
1005 }
1006 }
1007 kt.old_index = glyph_index
1008 if kt.swap {
1009 return 0, x
1010 }
1011 return x, 0
1012}
1013
1014fn (mut tf TTF_File) create_kern_table0(vertical bool, cross bool) Kern0Table {
1015 offset := tf.pos
1016 n_pairs := tf.get_u16()
1017 search_range := tf.get_u16()
1018 entry_selector := tf.get_u16()
1019 range_shift := tf.get_u16()
1020 dprintln('n_pairs: ${n_pairs} search_range: ${search_range} entry_selector: ${entry_selector} range_shift: ${range_shift}')
1021
1022 mut kt0 := Kern0Table{
1023 swap: (vertical && !cross) || (!vertical && cross)
1024 offset: offset
1025 n_pairs: n_pairs
1026 }
1027
1028 for _ in 0 .. n_pairs {
1029 left := tf.get_u16()
1030 right := tf.get_u16()
1031 value := tf.get_fword()
1032 tmp_index := (u32(left) << 16) | u32(right)
1033 kt0.kmap[tmp_index] = value
1034 // dprintln("index: ${tmp_index.hex()} val: ${value.hex()}")
1035 }
1036 kt0.old_index = -1
1037 return kt0
1038}
1039
1040fn (mut tf TTF_File) read_kern_table() {
1041 dprintln('*** READ KERN TABLE ***')
1042 if 'kern' !in tf.tables {
1043 return
1044 }
1045 table_offset := tf.tables['kern'].offset
1046 tf.pos = table_offset
1047
1048 version := tf.get_u16() // must be 0
1049 assert version == 0 // must be 0
1050 n_tables := tf.get_u16()
1051
1052 dprintln('Kern Table version: ${version} Kern nTables: ${n_tables}')
1053
1054 for _ in 0 .. n_tables {
1055 st_version := tf.get_u16() // sub table version
1056 length := tf.get_u16()
1057 coverage := tf.get_u16()
1058 format := coverage >> 8
1059 cross := coverage & 4
1060 vertical := (coverage & 0x1) == 0
1061 dprintln('Kerning subtable version [${st_version}] format [${format}] length [${length}] coverage: [${coverage.hex()}]')
1062 if format == 0 {
1063 dprintln('kern format: 0')
1064 kern := tf.create_kern_table0(vertical, cross != 0)
1065 tf.kern << kern
1066 } else {
1067 dprintln('Unknown format -- skip')
1068 tf.pos = tf.pos + length
1069 }
1070 }
1071}
1072
1073// reset_kern resets the internal kerning table data.
1074pub fn (mut tf TTF_File) reset_kern() {
1075 for i in 0 .. tf.kern.len {
1076 tf.kern[i].reset()
1077 }
1078}
1079
1080// next_kern returns the next `x`, `y` kerning for the glyph at index `glyph_index`.
1081pub fn (mut tf TTF_File) next_kern(glyph_index int) (int, int) {
1082 mut x := 0
1083 mut y := 0
1084 for i in 0 .. tf.kern.len {
1085 tmp_x, tmp_y := tf.kern[i].get(glyph_index)
1086 x = x + tmp_x
1087 y = y + tmp_y
1088 }
1089 return x, y
1090}
1091
1092/******************************************************************************
1093*
1094* Panose table
1095*
1096******************************************************************************/
1097fn (mut tf TTF_File) read_panose_table() {
1098 dprintln('*** READ PANOSE TABLE ***')
1099 if 'OS/2' !in tf.tables {
1100 return
1101 }
1102 table_offset := tf.tables['OS/2'].offset
1103 tf.pos = table_offset
1104 // dprintln('READING! PANOSE offset:${tf.tables['OS/2']}')
1105 version := tf.get_u16()
1106 dprintln('Panose version: ${version:04x}')
1107 tf.pos += 2 * 14 // move to Panose class + 10 byte array
1108 mut count := 0
1109
1110 // get family
1111 family_class := tf.get_i16()
1112 tf.panose_array[count] = u8(family_class >> 8)
1113 count++
1114 tf.panose_array[count] = u8(family_class & 0xFF)
1115 count++
1116 dprintln('family_class: ${family_class:04x}')
1117
1118 // get panose data
1119 for _ in 0 .. 10 {
1120 tf.panose_array[count] = tf.get_u8()
1121 count++
1122 }
1123
1124 // family_class1 := (i16(tf.panose_array[0]) << 8) + i16(tf.panose_array[1])
1125 // dprintln("family_class: ${family_class1:04x}")
1126 // dprintln("Panose array: ${tf.panose_array}")
1127}
1128
1129/******************************************************************************
1130*
1131* TTF_File Utility
1132*
1133******************************************************************************/
1134// get_info_string returns a string with various information about the TTF.
1135pub fn (tf TTF_File) get_info_string() string {
1136 txt := '----- Font Info -----
1137font_family : ${tf.font_family}
1138font_sub_family : ${tf.font_sub_family}
1139full_name : ${tf.full_name}
1140postscript_name : ${tf.postscript_name}
1141version : ${tf.version}
1142font_revision : ${tf.font_revision}
1143magic_number : ${tf.magic_number.hex()}
1144flags : ${tf.flags.hex()}
1145created unixTS : ${tf.created}
1146modified unixTS : ${tf.modified}
1147box : [x_min:${tf.x_min}, y_min:${tf.y_min}, x_Max:${tf.x_max}, y_Max:${tf.y_max}]
1148mac_style : ${tf.mac_style}
1149-----------------------
1150'
1151 return txt
1152}
1153
1154/******************************************************************************
1155*
1156* TTF_File test
1157*
1158******************************************************************************/
1159fn tst() {
1160 mut tf := TTF_File{}
1161
1162 tf.buf = [
1163 u8(0xFF), // 8 bit
1164 0xF1,
1165 0xF2, // 16 bit
1166 0x81,
1167 0x23,
1168 0x45,
1169 0x67, // 32 bit
1170 0x12,
1171 0x34,
1172 0x12,
1173 0x34, // get_2dot14 16 bit
1174 0x12,
1175 0x34,
1176 0x12,
1177 0x34, // get_fixed 32 bit int
1178 ]
1179 assert tf.get_u8().hex() == 'ff'
1180 assert tf.get_u16().hex() == 'f1f2'
1181 assert tf.get_u32().hex() == '81234567'
1182
1183 dprintln('buf len: ${tf.buf.len}')
1184 // dprintln( tf.get_u8().hex() )
1185 // dprintln( tf.get_u16().hex() )
1186 // dprintln( tf.get_u32().hex() )
1187 // dprintln( tf.get_2dot14() )
1188 // dprintln( tf.get_fixed() )
1189}
1190