Class: Fontisan::Tables::CompoundGlyphResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/tables/glyf/compound_glyph_resolver.rb

Overview

Resolves compound glyphs into simple outlines

CompoundGlyphResolver handles the recursive resolution of compound (composite) glyphs in TrueType fonts. Compound glyphs are composed of references to other glyphs with transformation matrices applied. This resolver:

  • Recursively resolves component glyphs (which may themselves be compound)
  • Applies transformation matrices to each component
  • Merges all components into a single simple outline
  • Handles nested compound glyphs (compound glyphs referencing other compounds)
  • Detects and prevents circular references

Transformation Process:

Each component has a transformation matrix [a, b, c, d, e, f] representing:

x' = a*x + c*y + e
y' = b*x + d*y + f

Where:

  • a, d: Scale factors for x and y
  • b, c: Rotation/skew components
  • e, f: Translation offsets (x, y)

Resolution Strategy:

  1. Start with compound glyph
  2. For each component:
    1. Get component glyph (may be simple or compound)
    2. If compound, recursively resolve it first
    3. Apply component's transformation matrix
    4. Merge into result outline
  3. Return merged simple outline

Examples:

Resolving a compound glyph

resolver = CompoundGlyphResolver.new(glyf_table, loca_table, head_table)
outline = resolver.resolve(compound_glyph)

With circular reference detection

visited = Set.new
outline = resolver.resolve(compound_glyph, visited)

Constant Summary collapse

MAX_DEPTH =

Maximum recursion depth to prevent infinite loops

32

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(glyf, loca, head) ⇒ CompoundGlyphResolver

Initialize resolver with required tables

Parameters:

  • glyf (Glyf)

    Glyf table

  • loca (Loca)

    Loca table

  • head (Head)

    Head table



64
65
66
67
68
# File 'lib/fontisan/tables/glyf/compound_glyph_resolver.rb', line 64

def initialize(glyf, loca, head)
  @glyf = glyf
  @loca = loca
  @head = head
end

Instance Attribute Details

#glyfGlyf (readonly)

Returns The glyf table.

Returns:

  • (Glyf)

    The glyf table



51
52
53
# File 'lib/fontisan/tables/glyf/compound_glyph_resolver.rb', line 51

def glyf
  @glyf
end

#headHead (readonly)

Returns The head table.

Returns:

  • (Head)

    The head table



57
58
59
# File 'lib/fontisan/tables/glyf/compound_glyph_resolver.rb', line 57

def head
  @head
end

#locaLoca (readonly)

Returns The loca table.

Returns:

  • (Loca)

    The loca table



54
55
56
# File 'lib/fontisan/tables/glyf/compound_glyph_resolver.rb', line 54

def loca
  @loca
end

Instance Method Details

#resolve(compound_glyph, visited = Set.new, depth = 0) ⇒ Outline

Resolve a compound glyph into a simple outline

Parameters:

  • compound_glyph (CompoundGlyph)

    Compound glyph to resolve

  • visited (Set<Integer>) (defaults to: Set.new)

    Set of visited glyph IDs (for circular ref detection)

  • depth (Integer) (defaults to: 0)

    Current recursion depth

Returns:

  • (Outline)

    Resolved simple outline

Raises:

  • (Error)

    If circular reference detected or max depth exceeded



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/fontisan/tables/glyf/compound_glyph_resolver.rb', line 77

def resolve(compound_glyph, visited = Set.new, depth = 0)
  # Check recursion depth
  if depth > MAX_DEPTH
    raise Fontisan::Error,
          "Maximum recursion depth (#{MAX_DEPTH}) exceeded resolving compound glyph #{compound_glyph.glyph_id}"
  end

  # Check for circular reference
  if visited.include?(compound_glyph.glyph_id)
    raise Fontisan::Error,
          "Circular reference detected in compound glyph #{compound_glyph.glyph_id}"
  end

  # Mark as visited
  visited = visited.dup.add(compound_glyph.glyph_id)

  # Start with empty merged outline
  merged_outline = Models::Outline.new(
    glyph_id: compound_glyph.glyph_id,
    commands: [],
    bbox: {
      x_min: compound_glyph.x_min,
      y_min: compound_glyph.y_min,
      x_max: compound_glyph.x_max,
      y_max: compound_glyph.y_max,
    },
  )

  # Resolve each component
  compound_glyph.components.each do |component|
    # Get component glyph
    component_glyph = glyf.glyph_for(component.glyph_index, loca, head)

    # Skip empty components
    next if component_glyph.nil? || component_glyph.empty?

    # Get component outline (recursively if compound)
    component_outline = if component_glyph.compound?
                          # Recursively resolve compound component
                          resolve(component_glyph, visited, depth + 1)
                        else
                          # Convert simple glyph to outline
                          Models::Outline.from_truetype(component_glyph,
                                                        component.glyph_index)
                        end

    # Apply transformation matrix
    matrix = component.transformation_matrix
    transformed_outline = component_outline.transform(matrix)

    # Merge into result
    merged_outline.merge!(transformed_outline)
  end

  merged_outline
end