Class: Uniword::Resource::FontSubstitutor

Inherits:
Object
  • Object
show all
Defined in:
lib/uniword/resource/font_substitutor.rb

Overview

SERVICE for font substitution Pure functions - no state, no side effects

Constant Summary collapse

SUBSTITUTIONS =

Font substitution map (MS fonts -> open-source alternatives)

{
  "Calibri" => "Carlito",
  "Calibri Light" => "Carlito",
  "Cambria" => "Caladea",
  "Cambria Math" => "Caladea",
  "Arial" => "Liberation Sans",
  "Arial Black" => "Liberation Sans",
  "Times New Roman" => "Liberation Serif",
  "Courier New" => "Liberation Mono",
  "Segoe UI" => "Source Sans Pro",
  "Tahoma" => "Liberation Sans",
}.freeze

Class Method Summary collapse

Class Method Details

.check_availability(font_name) ⇒ Boolean

Check font availability via Fontist

Parameters:

  • font_name (String)

    Font name to check

Returns:

  • (Boolean)

    true if font is available or Fontist is not installed



97
98
99
100
101
102
103
104
105
106
# File 'lib/uniword/resource/font_substitutor.rb', line 97

def self.check_availability(font_name)
  return true unless defined?(Fontist)

  Fontist.available?(font_name)
rescue StandardError => e
  Uniword.logger&.debug do
    "Font availability check failed for #{font_name}: #{e.message}"
  end
  false
end

.registryHash

Load the OFL font registry

Returns:

  • (Hash)

    The font registry data



111
112
113
114
115
116
117
118
119
# File 'lib/uniword/resource/font_substitutor.rb', line 111

def self.registry
  @registry ||= begin
    path = File.join(__dir__, "../../../data/resources/font_registry.yml")
    return {} unless File.exist?(path)

    require "yaml"
    YAML.load_file(path)
  end
end

.reset_registry!Object

Reset cached registry (useful for testing)



122
123
124
# File 'lib/uniword/resource/font_substitutor.rb', line 122

def self.reset_registry!
  @registry = nil
end

.substitute(font_name) ⇒ String

Get substitute font name

Parameters:

  • font_name (String)

    Original font name

Returns:

  • (String)

    Substituted font name or original if no substitution



26
27
28
# File 'lib/uniword/resource/font_substitutor.rb', line 26

def self.substitute(font_name)
  SUBSTITUTIONS[font_name] || registry_substitution(font_name) || font_name
end

.substitute_script(script_code, original_typeface) ⇒ String

Get substitute for a specific script code

Parameters:

  • script_code (String)

    OOXML script code (e.g., “Jpan”, “Hans”)

  • original_typeface (String)

    Original font name

Returns:

  • (String)

    OFL typeface name



35
36
37
# File 'lib/uniword/resource/font_substitutor.rb', line 35

def self.substitute_script(script_code, original_typeface)
  registry.dig("per_script", script_code) || substitute(original_typeface)
end

.transform_font_scheme(font_scheme) ⇒ Drawingml::FontScheme

Transform OOXML font scheme with full substitution Handles Latin, EA, CS, and per-script entries

Parameters:

Returns:



44
45
46
47
48
49
50
51
52
# File 'lib/uniword/resource/font_substitutor.rb', line 44

def self.transform_font_scheme(font_scheme)
  font_scheme.dup.tap do |scheme|
    # Transform major font Latin
    scheme.major_font_obj.latin.typeface = substitute(scheme.major_font_obj.latin.typeface) if scheme.major_font_obj&.latin

    # Transform minor font Latin
    scheme.minor_font_obj.latin.typeface = substitute(scheme.minor_font_obj.latin.typeface) if scheme.minor_font_obj&.latin
  end
end

.transform_friendly_font_scheme(friendly) ⇒ Themes::FontScheme

Transform friendly FontScheme with full substitution Populates empty EA/CS fields from registry

Parameters:

Returns:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/uniword/resource/font_substitutor.rb', line 59

def self.transform_friendly_font_scheme(friendly)
  reg = registry
  friendly.tap do |fs|
    fs.major_font = substitute(fs.major_font) if fs.major_font && !fs.major_font.empty?
    fs.minor_font = substitute(fs.minor_font) if fs.minor_font && !fs.minor_font.empty?

    # Populate EA fields if empty
    fs.major_east_asian = if fs.major_east_asian.to_s.empty?
                            reg.dig("east_asian", "japanese", "heading")
                          else
                            substitute(fs.major_east_asian)
                          end
    fs.minor_east_asian = if fs.minor_east_asian.to_s.empty?
                            reg.dig("east_asian", "japanese", "body")
                          else
                            substitute(fs.minor_east_asian)
                          end

    # Populate CS fields if empty
    fs.major_complex_script = if fs.major_complex_script.to_s.empty?
                                reg.dig("complex_script", "arabic",
                                        "heading")
                              else
                                substitute(fs.major_complex_script)
                              end
    fs.minor_complex_script = if fs.minor_complex_script.to_s.empty?
                                reg.dig("complex_script", "arabic",
                                        "body")
                              else
                                substitute(fs.minor_complex_script)
                              end
  end
end