ttfunk 1.5.1 → 1.6.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.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +60 -0
  5. data/README.md +2 -1
  6. data/lib/ttfunk.rb +45 -0
  7. data/lib/ttfunk/aggregate.rb +15 -0
  8. data/lib/ttfunk/bin_utils.rb +47 -0
  9. data/lib/ttfunk/bit_field.rb +31 -0
  10. data/lib/ttfunk/collection.rb +3 -1
  11. data/lib/ttfunk/directory.rb +6 -0
  12. data/lib/ttfunk/encoded_string.rb +97 -0
  13. data/lib/ttfunk/max.rb +25 -0
  14. data/lib/ttfunk/min.rb +25 -0
  15. data/lib/ttfunk/one_based_array.rb +36 -0
  16. data/lib/ttfunk/otf_encoder.rb +61 -0
  17. data/lib/ttfunk/placeholder.rb +13 -0
  18. data/lib/ttfunk/reader.rb +34 -32
  19. data/lib/ttfunk/resource_file.rb +7 -5
  20. data/lib/ttfunk/sci_form.rb +29 -0
  21. data/lib/ttfunk/sub_table.rb +38 -0
  22. data/lib/ttfunk/subset.rb +2 -0
  23. data/lib/ttfunk/subset/base.rb +61 -120
  24. data/lib/ttfunk/subset/code_page.rb +89 -0
  25. data/lib/ttfunk/subset/mac_roman.rb +5 -42
  26. data/lib/ttfunk/subset/unicode.rb +12 -6
  27. data/lib/ttfunk/subset/unicode_8bit.rb +14 -12
  28. data/lib/ttfunk/subset/windows_1252.rb +5 -47
  29. data/lib/ttfunk/subset_collection.rb +4 -0
  30. data/lib/ttfunk/sum.rb +20 -0
  31. data/lib/ttfunk/table.rb +4 -0
  32. data/lib/ttfunk/table/cff.rb +69 -0
  33. data/lib/ttfunk/table/cff/charset.rb +212 -0
  34. data/lib/ttfunk/table/cff/charsets.rb +14 -0
  35. data/lib/ttfunk/table/cff/charsets/expert.rb +189 -0
  36. data/lib/ttfunk/table/cff/charsets/expert_subset.rb +119 -0
  37. data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +241 -0
  38. data/lib/ttfunk/table/cff/charsets/standard_strings.rb +404 -0
  39. data/lib/ttfunk/table/cff/charstring.rb +487 -0
  40. data/lib/ttfunk/table/cff/charstrings_index.rb +39 -0
  41. data/lib/ttfunk/table/cff/dict.rb +266 -0
  42. data/lib/ttfunk/table/cff/encoding.rb +220 -0
  43. data/lib/ttfunk/table/cff/encodings.rb +12 -0
  44. data/lib/ttfunk/table/cff/encodings/expert.rb +206 -0
  45. data/lib/ttfunk/table/cff/encodings/standard.rb +181 -0
  46. data/lib/ttfunk/table/cff/fd_selector.rb +150 -0
  47. data/lib/ttfunk/table/cff/font_dict.rb +79 -0
  48. data/lib/ttfunk/table/cff/font_index.rb +29 -0
  49. data/lib/ttfunk/table/cff/header.rb +33 -0
  50. data/lib/ttfunk/table/cff/index.rb +125 -0
  51. data/lib/ttfunk/table/cff/one_based_index.rb +31 -0
  52. data/lib/ttfunk/table/cff/path.rb +66 -0
  53. data/lib/ttfunk/table/cff/private_dict.rb +84 -0
  54. data/lib/ttfunk/table/cff/subr_index.rb +19 -0
  55. data/lib/ttfunk/table/cff/top_dict.rb +230 -0
  56. data/lib/ttfunk/table/cff/top_index.rb +16 -0
  57. data/lib/ttfunk/table/cmap.rb +4 -4
  58. data/lib/ttfunk/table/cmap/format00.rb +1 -2
  59. data/lib/ttfunk/table/cmap/format04.rb +11 -3
  60. data/lib/ttfunk/table/cmap/format06.rb +2 -0
  61. data/lib/ttfunk/table/cmap/format10.rb +2 -0
  62. data/lib/ttfunk/table/cmap/format12.rb +2 -0
  63. data/lib/ttfunk/table/cmap/subtable.rb +12 -8
  64. data/lib/ttfunk/table/dsig.rb +50 -0
  65. data/lib/ttfunk/table/glyf.rb +11 -9
  66. data/lib/ttfunk/table/glyf/compound.rb +14 -7
  67. data/lib/ttfunk/table/glyf/path_based.rb +47 -0
  68. data/lib/ttfunk/table/glyf/simple.rb +21 -15
  69. data/lib/ttfunk/table/head.rb +43 -5
  70. data/lib/ttfunk/table/hhea.rb +47 -4
  71. data/lib/ttfunk/table/hmtx.rb +11 -4
  72. data/lib/ttfunk/table/kern.rb +3 -0
  73. data/lib/ttfunk/table/kern/format0.rb +3 -0
  74. data/lib/ttfunk/table/loca.rb +2 -0
  75. data/lib/ttfunk/table/maxp.rb +144 -10
  76. data/lib/ttfunk/table/name.rb +75 -37
  77. data/lib/ttfunk/table/os2.rb +327 -4
  78. data/lib/ttfunk/table/post.rb +8 -1
  79. data/lib/ttfunk/table/post/format10.rb +2 -0
  80. data/lib/ttfunk/table/post/format20.rb +5 -1
  81. data/lib/ttfunk/table/post/format30.rb +2 -0
  82. data/lib/ttfunk/table/post/format40.rb +2 -0
  83. data/lib/ttfunk/table/sbix.rb +2 -0
  84. data/lib/ttfunk/table/simple.rb +2 -0
  85. data/lib/ttfunk/table/vorg.rb +54 -0
  86. data/lib/ttfunk/ttf_encoder.rb +220 -0
  87. metadata +88 -20
  88. metadata.gz.sig +0 -0
  89. data/lib/ttfunk/encoding/mac_roman.rb +0 -100
  90. data/lib/ttfunk/encoding/windows_1252.rb +0 -76
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 901b4b6c09e1f0dfb7c327b51321af55a3f75237
4
- data.tar.gz: 504c1cc9444c543617c02963df1df04cc662d211
2
+ SHA256:
3
+ metadata.gz: 1036e45ae6026254c29d0d105dfab5e55864994d6b97bc88dbb0db9cd645f761
4
+ data.tar.gz: c8a8e0cd09c56f408949fb86ed2b6b19a062d3cf5697c2bc451fa363efb61238
5
5
  SHA512:
6
- metadata.gz: 767f9103f60beee020c5a36ff5efb448326f94ea23650eb4cd6c09e497e3194e0692340aeee60519b2dab3e92ac7d751cecb805b240fa87cf5247d9bebef2f12
7
- data.tar.gz: 92c600ec5bfcf601261f3c8b50df01fce414c8208451d491b359338057fccd372cf7a8ef7c655238914ae067af30e50d89df88dd6b69708754b0e2493bbfbb78
6
+ metadata.gz: 42012ccb4f26b39bcf82ff620a5d42417e1cfa1f678c5e398e1536c87abbe354e7caea988a0b5eb7141497e148ac8f338c3917b5a985ef5b64e153b3c2730cdc
7
+ data.tar.gz: bfb13cd6a01acfd9bbdff89a18b8261605d9acc235a229b2f75692653fe2a5761f524c53b699a587e49997685fd2bf3212536157645c41a51b77d2feedf25c52
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -7,6 +7,66 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ * OpenType fonts support
13
+
14
+ * Added support for CFF-flavored fonts (also known as CID-keyed or OpenType fonts)
15
+ * Added support for the VORG and DSIG tables
16
+ * Improved charset encoding support
17
+ * Improved font metrics calculations in the head, maxp, hhea, hmtx, and os/2 tables
18
+ * Subsetted fonts verified with Font-Validator, fontlint, and Mac OS's Font Book
19
+
20
+ Cameron Dutro
21
+
22
+ * Ruby 2.6 support
23
+
24
+ Alexander Mankuta
25
+
26
+ * JRuby 9.2 support
27
+
28
+ Alexander Mankuta
29
+
30
+ ### Removed
31
+
32
+ * Dropped Ruby 2.1 & 2.2 support
33
+
34
+ Alexander Mankuta
35
+
36
+ * Removed JRuby 9.1 support
37
+
38
+ Alexander Mankuta
39
+
40
+ ### Fixed
41
+
42
+ * Sort name table entries when generating subset font
43
+
44
+ Matjaz Gregoric
45
+
46
+ * Map the 0xFFFF char code to glyph 0 in cmap format 4
47
+
48
+ Matjaz Gregoric
49
+
50
+ * Order tables by tag when generating font subset
51
+
52
+ Matjaz Gregoric
53
+
54
+ * Fix typo in TTFunk::Subset::Unicode#includes?
55
+
56
+ Matjaz Gregoric
57
+
58
+ * Fixe calculation of search_range for font subsets
59
+
60
+ Matjaz Gregoric
61
+
62
+ * Fixed instance variable @offset and @length not initialized
63
+
64
+ Katsuya HIDAKA
65
+
66
+ * Code style fixes
67
+
68
+ Katsuya HIDAKA, Matjaz Gregoric, Alexander Mankuta
69
+
10
70
  ## [1.5.1]
11
71
 
12
72
  ### Fixed
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # TTFunk
2
2
 
3
- ![Maintained: yes](https://img.shields.io/badge/maintained-yes-brightgreen.png)
3
+ ![Maintained: yes](https://img.shields.io/badge/maintained-yes-brightgreen.svg)
4
+ ![CI status](https://github.com/prawnpdf/ttfunk/workflows/CI/badge.svg)
4
5
 
5
6
  TTFunk is a TrueType font parser written in pure ruby.
6
7
 
@@ -1,9 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
  require 'pathname'
3
5
 
6
+ require_relative 'ttfunk/aggregate'
4
7
  require_relative 'ttfunk/directory'
5
8
  require_relative 'ttfunk/resource_file'
6
9
  require_relative 'ttfunk/collection'
10
+ require_relative 'ttfunk/ttf_encoder'
11
+ require_relative 'ttfunk/encoded_string'
12
+ require_relative 'ttfunk/placeholder'
13
+ require_relative 'ttfunk/otf_encoder'
14
+ require_relative 'ttfunk/sci_form'
15
+ require_relative 'ttfunk/bit_field'
16
+ require_relative 'ttfunk/bin_utils'
17
+ require_relative 'ttfunk/sub_table'
18
+ require_relative 'ttfunk/min'
19
+ require_relative 'ttfunk/max'
20
+ require_relative 'ttfunk/sum'
21
+ require_relative 'ttfunk/one_based_array'
7
22
 
8
23
  module TTFunk
9
24
  class File
@@ -37,6 +52,7 @@ module TTFunk
37
52
  # String or Pathname
38
53
  io_or_path = Pathname.new(io_or_path)
39
54
  raise ArgumentError, "#{io_or_path} not found" unless io_or_path.file?
55
+
40
56
  io = io_or_path.open('rb')
41
57
  io
42
58
  end
@@ -116,10 +132,38 @@ module TTFunk
116
132
  def sbix
117
133
  @sbix ||= TTFunk::Table::Sbix.new(self)
118
134
  end
135
+
136
+ def cff
137
+ @cff ||= TTFunk::Table::Cff.new(self)
138
+ end
139
+
140
+ def vertical_origins
141
+ @vertical_origins ||=
142
+ if directory.tables.include?(TTFunk::Table::Vorg::TAG)
143
+ TTFunk::Table::Vorg.new(self)
144
+ end
145
+ end
146
+
147
+ def digital_signature
148
+ @digital_signature ||=
149
+ if directory.tables.include?(TTFunk::Table::Dsig::TAG)
150
+ TTFunk::Table::Dsig.new(self)
151
+ end
152
+ end
153
+
154
+ def find_glyph(glyph_id)
155
+ if cff.exists?
156
+ cff.top_index[0].charstrings_index[glyph_id].glyph
157
+ else
158
+ glyph_outlines.for(glyph_id)
159
+ end
160
+ end
119
161
  end
120
162
  end
121
163
 
164
+ require_relative 'ttfunk/table/cff'
122
165
  require_relative 'ttfunk/table/cmap'
166
+ require_relative 'ttfunk/table/dsig'
123
167
  require_relative 'ttfunk/table/glyf'
124
168
  require_relative 'ttfunk/table/head'
125
169
  require_relative 'ttfunk/table/hhea'
@@ -131,3 +175,4 @@ require_relative 'ttfunk/table/name'
131
175
  require_relative 'ttfunk/table/os2'
132
176
  require_relative 'ttfunk/table/post'
133
177
  require_relative 'ttfunk/table/sbix'
178
+ require_relative 'ttfunk/table/vorg'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Aggregate
5
+ private
6
+
7
+ def coerce(other)
8
+ if other.respond_to?(:value_or)
9
+ other.value_or(0)
10
+ else
11
+ other
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ module BinUtils
5
+ # assumes big-endian
6
+ def stitch_int(arr, bit_width:)
7
+ value = 0
8
+
9
+ arr.each_with_index do |element, index|
10
+ value |= element << bit_width * index
11
+ end
12
+
13
+ value
14
+ end
15
+
16
+ # assumes big-endian
17
+ def slice_int(value, bit_width:, slice_count:)
18
+ mask = 2**bit_width - 1
19
+
20
+ Array.new(slice_count) do |i|
21
+ (value >> bit_width * i) & mask
22
+ end
23
+ end
24
+
25
+ def twos_comp_to_int(num, bit_width:)
26
+ if num >> (bit_width - 1) == 1
27
+ # we want all ones
28
+ mask = (2**bit_width) - 1
29
+
30
+ # find 2's complement, i.e. flip bits (xor with mask) and add 1
31
+ -((num ^ mask) + 1)
32
+ else
33
+ num
34
+ end
35
+ end
36
+
37
+ # turns a (sorted) sequence of values into a series of two-element arrays
38
+ # where the first element is the start and the second is the length
39
+ def rangify(values)
40
+ values
41
+ .slice_when { |a, b| b - a > 1 }
42
+ .map { |span| [span.first, span.length - 1] }
43
+ end
44
+ end
45
+
46
+ BinUtils.extend(BinUtils)
47
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class BitField
5
+ attr_reader :value
6
+
7
+ def initialize(value = 0)
8
+ @value = value
9
+ end
10
+
11
+ def on(pos)
12
+ @value |= 2**pos
13
+ end
14
+
15
+ def on?(pos)
16
+ value & 2**pos > 0
17
+ end
18
+
19
+ def off(pos)
20
+ @value &= 2**Math.log2(value).ceil - 2**pos - 1
21
+ end
22
+
23
+ def off?(pos)
24
+ !on?(pos)
25
+ end
26
+
27
+ def dup
28
+ self.class.new(value)
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTFunk
2
4
  class Collection
3
5
  include Enumerable
@@ -13,7 +15,7 @@ module TTFunk
13
15
  raise ArgumentError, 'not a TTC file' unless tag == 'ttcf'
14
16
 
15
17
  _major, _minor = io.read(4).unpack('n*')
16
- count = io.read(4).unpack('N').first
18
+ count = io.read(4).unpack1('N')
17
19
  @offsets = io.read(count * 4).unpack('N*')
18
20
 
19
21
  io.rewind
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTFunk
2
4
  class Directory
3
5
  attr_reader :tables
@@ -5,6 +7,10 @@ module TTFunk
5
7
 
6
8
  def initialize(io, offset = 0)
7
9
  io.seek(offset)
10
+
11
+ # https://www.microsoft.com/typography/otspec/otff.htm#offsetTable
12
+ # We're ignoring searchRange, entrySelector, and rangeShift here, but
13
+ # skipping past them to get to the table information.
8
14
  @scaler_type, table_count = io.read(12).unpack('Nn')
9
15
 
10
16
  @tables = {}
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ module TTFunk
6
+ class UnresolvedPlaceholderError < StandardError
7
+ end
8
+
9
+ class DuplicatePlaceholderError < StandardError
10
+ end
11
+
12
+ class EncodedString
13
+ def initialize
14
+ yield self if block_given?
15
+ end
16
+
17
+ def <<(obj)
18
+ case obj
19
+ when String
20
+ io << obj
21
+ when Placeholder
22
+ add_placeholder(obj)
23
+ io << "\0" * obj.length
24
+ when self.class
25
+ # adjust placeholders to be relative to the entire encoded string
26
+ obj.placeholders.each_pair do |_, placeholder|
27
+ add_placeholder(placeholder.dup, placeholder.position + io.length)
28
+ end
29
+
30
+ self << obj.unresolved_string
31
+ end
32
+
33
+ self
34
+ end
35
+
36
+ def align!(width = 4)
37
+ if length % width > 0
38
+ self << "\0" * (width - length % width)
39
+ end
40
+
41
+ self
42
+ end
43
+
44
+ def length
45
+ io.length
46
+ end
47
+
48
+ def string
49
+ unless placeholders.empty?
50
+ raise UnresolvedPlaceholderError, 'string contains '\
51
+ "#{placeholders.size} unresolved placeholder(s)"
52
+ end
53
+
54
+ io.string
55
+ end
56
+
57
+ def bytes
58
+ string.bytes
59
+ end
60
+
61
+ def unresolved_string
62
+ io.string
63
+ end
64
+
65
+ def resolve_placeholder(name, value)
66
+ last_pos = io.pos
67
+
68
+ if (placeholder = placeholders[name])
69
+ io.seek(placeholder.position)
70
+ io.write(value[0..placeholder.length])
71
+ placeholders.delete(name)
72
+ end
73
+ ensure
74
+ io.seek(last_pos)
75
+ end
76
+
77
+ def placeholders
78
+ @placeholders ||= {}
79
+ end
80
+
81
+ private
82
+
83
+ def add_placeholder(new_placeholder, pos = io.pos)
84
+ if placeholders.include?(new_placeholder.name)
85
+ raise DuplicatePlaceholderError,
86
+ "placeholder #{new_placeholder.name} already exists"
87
+ end
88
+
89
+ new_placeholder.position = pos
90
+ placeholders[new_placeholder.name] = new_placeholder
91
+ end
92
+
93
+ def io
94
+ @io ||= StringIO.new(''.b).binmode
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Max < Aggregate
5
+ attr_reader :value
6
+
7
+ def initialize(init_value = nil)
8
+ @value = init_value
9
+ end
10
+
11
+ def <<(new_value)
12
+ new_value = coerce(new_value)
13
+
14
+ if value.nil? || new_value > value
15
+ @value = new_value
16
+ end
17
+ end
18
+
19
+ def value_or(default)
20
+ return default if value.nil?
21
+
22
+ value
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Min < Aggregate
5
+ attr_reader :value
6
+
7
+ def initialize(init_value = nil)
8
+ @value = init_value
9
+ end
10
+
11
+ def <<(new_value)
12
+ new_value = coerce(new_value)
13
+
14
+ if value.nil? || new_value < value
15
+ @value = new_value
16
+ end
17
+ end
18
+
19
+ def value_or(default)
20
+ return default if value.nil?
21
+
22
+ value
23
+ end
24
+ end
25
+ end