Class: Fontisan::Tables::Head

Inherits:
Binary::BaseRecord show all
Defined in:
lib/fontisan/tables/head.rb

Overview

BinData structure for the 'head' (Font Header) table

The head table contains global information about the font, including metadata about the font file, bounding box, and indexing information.

Reference: OpenType specification, head table

Examples:

Reading a head table

data = File.binread("font.ttf", 54, head_offset)
head = Fontisan::Tables::Head.read(data)
puts head.units_per_em  # => 2048
puts head.version_number  # => 1.0

Constant Summary collapse

MAGIC_NUMBER =

Magic number that must be present in the head table

0x5F0F3CF5
FLAG_LOSSLESS_MODIFYING =

Bit flags for the flags field (uint16), per OpenType spec. Only bits we currently reference are named here; see the OpenType head table specification for the full list.

Bit 11 indicates the font data has been losslessly modified (e.g. by subsetting or WOFF2 encoding). Per WOFF2 spec section 5, encoders MUST set this bit. Chrome's OpenType Sanitizer (OTS) rejects WOFF2 files where it is not set.

0x0800
MAC_EPOCH_OFFSET =

Difference between 1904-01-01 00:00:00 UTC (Mac epoch, used by LONGDATETIME fields) and 1970-01-01 00:00:00 UTC (Unix epoch). Single source of truth for converting between the two — head's created/modified fields are LONGDATETIME.

2_082_844_800

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Binary::BaseRecord

#raw_data, read

Class Method Details

.longdatetime_to_time(seconds) ⇒ Time

Convert LONGDATETIME to Ruby Time

Parameters:

  • seconds (Integer)

    Seconds since 1904-01-01 00:00:00

Returns:

  • (Time)

    Ruby Time object



200
201
202
# File 'lib/fontisan/tables/head.rb', line 200

def self.longdatetime_to_time(seconds)
  Time.at(seconds - MAC_EPOCH_OFFSET)
end

.now_longdatetimeInteger

Current wall clock time as a LONGDATETIME integer (seconds since Mac epoch). Used wherever a created/modified timestamp needs to be set to a meaningful "now" value (WOFF2 encoder, Type 1 converter, UFO compile fallback).

Returns:

  • (Integer)

    Seconds since 1904-01-01 00:00:00 UTC



165
166
167
# File 'lib/fontisan/tables/head.rb', line 165

def self.now_longdatetime
  to_longdatetime(Time.now)
end

.to_longdatetime(time = Time.now) ⇒ Integer

Convert a Unix-epoch Time (or Integer seconds) to a LONGDATETIME integer (Mac-epoch seconds). Symmetric with #longdatetime_to_time — pair them whenever you convert across the epoch boundary.

Parameters:

  • time (Time, Integer) (defaults to: Time.now)

    Unix-epoch time, default Time.now

Returns:

  • (Integer)

    Seconds since 1904-01-01 00:00:00 UTC



175
176
177
# File 'lib/fontisan/tables/head.rb', line 175

def self.to_longdatetime(time = Time.now)
  time.to_i + MAC_EPOCH_OFFSET
end

Instance Method Details

#createdTime

Convert created timestamp to Time object

Returns:

  • (Time)

    Creation time



81
82
83
# File 'lib/fontisan/tables/head.rb', line 81

def created
  self.class.longdatetime_to_time(created_raw)
end

#font_revisionFloat

Convert font revision from fixed-point to float

Returns:

  • (Float)

    Font revision number



74
75
76
# File 'lib/fontisan/tables/head.rb', line 74

def font_revision
  fixed_to_float(font_revision_raw)
end

#modifiedTime

Convert modified timestamp to Time object

Returns:

  • (Time)

    Modification time



88
89
90
# File 'lib/fontisan/tables/head.rb', line 88

def modified
  self.class.longdatetime_to_time(modified_raw)
end

#valid?Boolean

Validate that the magic number is correct

Returns:

  • (Boolean)

    True if magic number is valid



95
96
97
# File 'lib/fontisan/tables/head.rb', line 95

def valid?
  magic_number == MAGIC_NUMBER
end

#valid_bounding_box?Boolean

Validation helper: Check if bounding box is valid

The bounding box should have xMin < xMax and yMin < yMax

Returns:

  • (Boolean)

    True if bounding box coordinates are valid



137
138
139
# File 'lib/fontisan/tables/head.rb', line 137

def valid_bounding_box?
  x_min < x_max && y_min < y_max
end

#valid_glyph_data_format?Boolean

Validation helper: Check if glyph_data_format is valid

Must be 0 for current format

Returns:

  • (Boolean)

    True if format is 0



155
156
157
# File 'lib/fontisan/tables/head.rb', line 155

def valid_glyph_data_format?
  glyph_data_format.zero?
end

#valid_index_to_loc_format?Boolean

Validation helper: Check if index_to_loc_format is valid

Must be 0 (short) or 1 (long)

Returns:

  • (Boolean)

    True if format is 0 or 1



146
147
148
# File 'lib/fontisan/tables/head.rb', line 146

def valid_index_to_loc_format?
  [0, 1].include?(index_to_loc_format)
end

#valid_magic?Boolean

Validation helper: Check if magic number is valid

Returns:

  • (Boolean)

    True if magic number equals 0x5F0F3CF5



102
103
104
# File 'lib/fontisan/tables/head.rb', line 102

def valid_magic?
  magic_number == MAGIC_NUMBER
end

#valid_units_per_em?Boolean

Validation helper: Check if units per em is valid

Units per em should be a power of 2 between 16 and 16384

Returns:

  • (Boolean)

    True if units_per_em is valid



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/fontisan/tables/head.rb', line 120

def valid_units_per_em?
  return false if units_per_em.nil? || units_per_em.zero?

  # Must be between 16 and 16384
  return false unless units_per_em.between?(16, 16384)

  # Should be a power of 2 (recommended but not required)
  # Common values: 1000, 1024, 2048
  # We'll allow any value in range for flexibility
  true
end

#valid_version?Boolean

Validation helper: Check if version is valid

OpenType spec requires version to be 1.0

Returns:

  • (Boolean)

    True if version is 1.0



111
112
113
# File 'lib/fontisan/tables/head.rb', line 111

def valid_version?
  version_raw == 0x00010000 # Version 1.0
end

#validate_magic_number!Object Also known as: validate!

Validate magic number and raise error if invalid

Raises:



182
183
184
185
186
187
188
189
190
191
# File 'lib/fontisan/tables/head.rb', line 182

def validate_magic_number!
  return if valid?

  message = "Invalid magic number in head table: " \
            "expected 0x#{MAGIC_NUMBER.to_s(16).upcase}, " \
            "got 0x#{magic_number.to_s(16).upcase}"
  error = Fontisan::CorruptedTableError.new(message)
  error.set_backtrace(caller)
  Kernel.raise(error)
end

#versionFloat

Convert version from fixed-point to float

Returns:

  • (Float)

    Version number (e.g., 1.0)



67
68
69
# File 'lib/fontisan/tables/head.rb', line 67

def version
  fixed_to_float(version_raw)
end