Class: Fontisan::Tables::Head

Inherits:
Binary::BaseRecord show all
Extended by:
Registered
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

Instance Attribute Summary

Attributes inherited from Binary::BaseRecord

#raw_data

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Registered

register_tag

Methods inherited from Binary::BaseRecord

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



203
204
205
# File 'lib/fontisan/tables/head.rb', line 203

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



168
169
170
# File 'lib/fontisan/tables/head.rb', line 168

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



178
179
180
# File 'lib/fontisan/tables/head.rb', line 178

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



84
85
86
# File 'lib/fontisan/tables/head.rb', line 84

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



77
78
79
# File 'lib/fontisan/tables/head.rb', line 77

def font_revision
  fixed_to_float(font_revision_raw)
end

#modifiedTime

Convert modified timestamp to Time object

Returns:

  • (Time)

    Modification time



91
92
93
# File 'lib/fontisan/tables/head.rb', line 91

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



98
99
100
# File 'lib/fontisan/tables/head.rb', line 98

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



140
141
142
# File 'lib/fontisan/tables/head.rb', line 140

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



158
159
160
# File 'lib/fontisan/tables/head.rb', line 158

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



149
150
151
# File 'lib/fontisan/tables/head.rb', line 149

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



105
106
107
# File 'lib/fontisan/tables/head.rb', line 105

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



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/fontisan/tables/head.rb', line 123

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



114
115
116
# File 'lib/fontisan/tables/head.rb', line 114

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:



185
186
187
188
189
190
191
192
193
194
# File 'lib/fontisan/tables/head.rb', line 185

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)



70
71
72
# File 'lib/fontisan/tables/head.rb', line 70

def version
  fixed_to_float(version_raw)
end