Class: Fontisan::Converters::WoffWriter

Inherits:
Object
  • Object
show all
Includes:
ConversionStrategy
Defined in:
lib/fontisan/converters/woff_writer.rb

Overview

WOFF font writer for creating WOFF files from TTF/OTF fonts

WoffWriter converts TrueType/OpenType fonts to WOFF 1.0 format. The WOFF spec mandates zlib compression; this writer exposes the spec-legal knobs only:

  • zlib_level (0–9) — zlib compression level
  • uncompressed (bool) — store tables uncompressed (legal per WOFF 1.0 §5.1; compLength == origLength). Metadata is still compressed per spec §7 ("it is never stored in uncompressed form").
  • compression_threshold (bytes) — skip compression for tables smaller than N bytes (rarely needed; keeps tiny tables uncompressed)
  • metadata_xml (string) — optional metadata block
  • private_data (string) — optional private data block

Cross-format options (e.g., brotli_quality) are rejected by ConversionStrategy#validate_options! — see ConversionStrategy.

Examples:

Convert TTF to WOFF with max zlib

writer = WoffWriter.new
woff = writer.convert(ttf_font, zlib_level: 9)
File.binwrite("out.woff", woff)

Uncompressed WOFF (legal per spec; useful for tooling pipelines)

writer = WoffWriter.new
woff = writer.convert(ttf_font, uncompressed: true)

Constant Summary collapse

WOFF_SIGNATURE =

WOFF signature constant

0x774F4646
WOFF_VERSION_MAJOR =

WOFF version 1.0

1
WOFF_VERSION_MINOR =
0

Instance Method Summary collapse

Methods included from ConversionStrategy

included, #supports?

Constructor Details

#initializeWoffWriter

Initialize writer. The writer is stateless per call; all knobs come through the per-convert options hash.



63
# File 'lib/fontisan/converters/woff_writer.rb', line 63

def initialize; end

Instance Method Details

#convert(font, options = {}) ⇒ String

Convert font to WOFF format.

Parameters:

  • font (TrueTypeFont, OpenTypeFont)

    Source font

  • options (Hash{Symbol => Object}) (defaults to: {})

    Per-call options; see declared options above. Unknown keys (framework metadata like target_format) are tolerated silently. Cross-format misuse (brotli_quality on a WOFF target) is caught upstream by FormatConverter.validate_options_for_target!.

Returns:

  • (String)

    WOFF file data as binary string

Raises:

  • (ArgumentError)

    if any declared option fails validation

  • (ArgumentError)

    if font does not respond to required methods



76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/fontisan/converters/woff_writer.rb', line 76

def convert(font, options = {})
  self.class.validate_options!(strategy_options(options))
  validate(font, :woff)

  opts = self.class.default_options.merge(strategy_options(options))
  write_font(
    font,
    zlib_level: opts[:zlib_level],
    uncompressed: opts[:uncompressed],
    compression_threshold: opts[:compression_threshold],
    metadata: opts[:metadata_xml],
    private_data: opts[:private_data],
  )[:woff_binary]
end

#supported_conversionsArray<Array<Symbol>>

Get supported conversions.

Returns:

  • (Array<Array<Symbol>>)

    Pairs this strategy handles



94
95
96
97
98
99
# File 'lib/fontisan/converters/woff_writer.rb', line 94

def supported_conversions
  [
    %i[ttf woff],
    %i[otf woff],
  ]
end

#validate(font, target_format) ⇒ Boolean

Validate that the given font can be converted to WOFF.

Parameters:

  • font (Object)

    Font to validate

  • target_format (Symbol)

    Must be :woff

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    if font is nil or missing required methods

  • (Fontisan::Error)

    if target_format is not :woff



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/fontisan/converters/woff_writer.rb', line 108

def validate(font, target_format)
  unless target_format == :woff
    raise Fontisan::Error,
          "WoffWriter only supports conversion to woff, got: #{target_format}"
  end

  raise ArgumentError, "Font cannot be nil" if font.nil?

  unless font.respond_to?(:tables) && font.respond_to?(:table_data)
    raise ArgumentError, "Font must respond to :tables and :table_data"
  end
end

#write_font(font, zlib_level:, uncompressed:, compression_threshold:, metadata: nil, private_data: nil) ⇒ Hash{Symbol => String}

Write font to WOFF binary.

Layout per WOFF 1.0 spec:

[header (44)] [table directory (20 × N)] [font tables, 4-byte aligned]
[optional metadata, 4-byte aligned] [optional private data, 4-byte aligned]

Each font table is padded with 0-3 null bytes to a 4-byte boundary (spec §5/§6: "Font data tables in the WOFF file have the same requirement: they MUST begin on 4-byte boundaries and be zero-padded to the next 4-byte boundary"). Padding aligns the next block.

Parameters:

  • font (TrueTypeFont, OpenTypeFont)

    Source font

  • zlib_level (Integer)

    0–9

  • uncompressed (Boolean)

    skip zlib; store tables as-is

  • compression_threshold (Integer)

    skip compression below N bytes

  • metadata (String, nil) (defaults to: nil)

    optional metadata XML (always compressed per spec §7 — "it is never stored in uncompressed form")

  • private_data (String, nil) (defaults to: nil)

    optional private data

Returns:

  • (Hash{Symbol => String})

    { woff_binary: <bytes> }



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/fontisan/converters/woff_writer.rb', line 140

def write_font(font, zlib_level:, uncompressed:, compression_threshold:,
               metadata: nil, private_data: nil)
  tables = collect_tables_data(font)
  compressed_tables = compress_tables(
    tables,
    zlib_level: uncompressed ? 0 : zlib_level,
    skip_compression: uncompressed,
    compression_threshold: compression_threshold,
  )
  # Per spec §7, metadata MUST always be zlib-compressed regardless of
  # the per-table `uncompressed` flag.
   = (, zlib_level:)
  binary = build_woff_file(compressed_tables, font, ,
                           private_data)
  { woff_binary: binary }
end