#pragma once
/*
OT2Woff: An OpenType (PostScript/TrueType) to WOFF converter
Copyright (c) 2020 by Peter Frane Jr.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
The author may be contacted via the e-mail address pfranejr@hotmail.com
*/
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#pragma comment(lib, "zdll.lib")
#define COPYRIGHT_NOTICE "OT2Woff v. 1.0\nCopyright (c) 2000 P.D. Frane Jr.\n"
#ifdef _MSC_VER
#define bswap16(x) _byteswap_ushort(x)
#define bswap32(x) _byteswap_ulong(x)
#else
#define bswap16(x) __builtin_bswap16(x)
#define bswap32(x) __builtin_bswap32(x)
#endif
const uint32_t OPENTYPE_TRUETYPE = 0x00010000;
const uint32_t OPENTYPE_TRUETYPE_MAC = 0x74727565;
const uint32_t OPENTYPE_CFF = 0x4F54544F;
typedef uint32_t offset32;
typedef unsigned char byte_t;
struct WOFF_header
{
uint32_t m_signature{ 0 }; //0x774F4646 'wOFF'
uint32_t m_flavor{ 0 }; // The "sfnt version" of the input font.
uint32_t m_length{ 0 }; // Total size of the WOFF file.
uint16_t m_num_tables{ 0 }; // Number of entries in directory of font tables.
uint16_t m_reserved{ 0 }; // Reserved; set to zero.
uint32_t m_total_sfnt_size{ 0 }; // Total size needed for the uncompressed font data, including the sfnt header, directory, and font tables(including padding).
uint16_t m_major_version{ 1 }; // Major version of the WOFF file.
uint16_t m_minor_version{ 0 }; // Minor version of the WOFF file.
uint32_t m_meta_offset{ 0 }; // Offset to metadata block, from beginning of WOFF file.
uint32_t m_meta_length{ 0 }; // Length of compressed metadata block.
uint32_t m_meta_orig_length{ 0 }; // Uncompressed size of metadata block.
uint32_t m_priv_offset{ 0 }; // Offset to private data block, from beginning of WOFF file.
uint32_t m_priv_length{ 0 }; // Length of private data block.
};
struct table_directory_entry
{
uint32_t tag{ 0 };
uint32_t offset{ 0 };
uint32_t comp_length{ 0 };// Length of the compressed data, excluding padding.
uint32_t orig_length{ 0 };// Length of the uncompressed table, excluding padding.
uint32_t orig_checksum{ 0 };
};
struct offset_table
{
uint32_t sfntVersion;
uint16_t numTables;
uint16_t searchRange;
uint16_t entrySelector;
uint16_t rangeShift;
};
struct table_record
{
uint32_t table_tag{ 0 };
uint32_t checksum{ 0 };
offset32 offset{ 0 };
uint32_t length{ 0 };
};
struct table_record_index
{
uint16_t index{ 0 };
offset32 offset{ 0 };
};
struct sorted_table_record
{
uint16_t index{ 0 };
offset32 offset{ 0 };
uint32_t length{ 0 };
};
class OT2Woff
{
FILE* m_input_file{ nullptr };
FILE* m_output_file{ nullptr };
string m_error;
void clear()
{
if (m_input_file)
{
fclose(m_input_file);
}
if (m_output_file)
{
fclose(m_output_file);
}
m_input_file = m_output_file = nullptr;
}
void load_input_file(const char* otf_file)
{
m_input_file = fopen(otf_file, "rb");
if (!m_input_file)
{
throw runtime_error("Unable to load the font");
}
}
void create_output_file(const char* woff_file)
{
m_output_file = fopen(woff_file, "wb");
if (!m_output_file)
{
throw runtime_error("Unable to create the WOFF file");
}
}
void read_header(WOFF_header& hdr, uint16_t& num_tables)
{
offset_table offset_tbl = { 0 };
if (fread(&offset_tbl, 1, sizeof(offset_tbl), m_input_file) != sizeof(offset_tbl))
{
throw runtime_error("Error reading the header of the input file");
}
else
{
uint32_t sfntVersion = bswap32(offset_tbl.sfntVersion);
if (sfntVersion != OPENTYPE_CFF && sfntVersion != OPENTYPE_TRUETYPE &&
sfntVersion != OPENTYPE_TRUETYPE_MAC)
{
throw runtime_error("Unknown file type");
}
//calculate_checksum_adjustment();
hdr.m_signature = bswap32(0x774F4646); // wOFF
hdr.m_flavor = offset_tbl.sfntVersion;
hdr.m_num_tables = offset_tbl.numTables;
num_tables = bswap16(offset_tbl.numTables);
}
}
static int compare(const void* a, const void* b)
{
sorted_table_record* i = (sorted_table_record*)a;
sorted_table_record* j = (sorted_table_record*)b;
return i->offset - j->offset;
}
uint32_t read_table_record(uint16_t num_tables, table_directory_entry* tbl_directory_entry_data, sorted_table_record* sorted_table_data)
{
uint32_t total_sfnt_size;
table_record tbl_record;
total_sfnt_size = 12 + sizeof(tbl_record) * num_tables;
for (uint16_t i = 0; i < num_tables; ++i)
{
if (fread(&tbl_record, 1, sizeof(tbl_record), m_input_file) != sizeof(tbl_record))
{
throw runtime_error("Error reading the font table records");
}
tbl_directory_entry_data[i].tag = tbl_record.table_tag;
tbl_directory_entry_data[i].orig_checksum = tbl_record.checksum;
tbl_directory_entry_data[i].orig_length = tbl_record.length;
sorted_table_data[i].index = i;
sorted_table_data[i].length = bswap32(tbl_record.length);
sorted_table_data[i].offset = bswap32(tbl_record.offset);
total_sfnt_size += (sorted_table_data[i].length + 3) & 0xFFFFFFFC;
}
qsort(sorted_table_data, num_tables, sizeof(*sorted_table_data), compare);
return bswap32(total_sfnt_size);
}
void write_temp_header(uint16_t num_tables)
{
WOFF_header hdr;
table_directory_entry entry;
fwrite(&hdr, 1, sizeof(hdr), m_output_file);
fwrite(&entry, num_tables, sizeof(entry), m_output_file);
}
uint32_t get_max_buffer_size(uint16_t num_tables, sorted_table_record* sorted_data)
{
uint32_t size = 0;
for (uint16_t i = 0; i < num_tables; ++i)
{
size = sorted_data[i].length > size ? sorted_data[i].length : size;
}
return size;
}
void pad_table(byte_t* buffer, uint32_t length, uint32_t padded_length)
{
for (uint32_t i = length; i < padded_length; ++i)
{
buffer[i] = 0;
}
}
int get_compression_level(const char* compression_level)
{
if (compression_level)
{
int num;
if (sscanf(compression_level, "%d", &num) == 1)
{
if (num >= 0 && num <= 9)
{
return num;
}
else
{
printf("Error: Invalid compression level: %d. Value of 9 is used instead.", num);
}
}
else
{
puts("Error: Value of the 4th parameter must be a number from 0 to 9 (highest compression). Value of 9 is used instead.");
}
}
return 9;
}
uint32_t compress_and_write_table_data(uint16_t num_tables, table_directory_entry* tbl_directory_entry_data,
sorted_table_record* sorted_table_data, const char* compression_level)
{
uint32_t buf_size = get_max_buffer_size(num_tables, sorted_table_data);
uint32_t buf_size_padded = (buf_size + 3) & 0xFFFFFFFC;
vector buf1(buf_size_padded), buf2(buf_size_padded);
uint32_t table_length = 0;
Bytef* buffer, * compressed_data;
int level = 9;
buffer = buf1.data();
compressed_data = buf2.data();
level = get_compression_level(compression_level);
for (uint16_t i = 0; i < num_tables; ++i)
{
uint16_t index = sorted_table_data[i].index;
uint32_t offset = sorted_table_data[i].offset;
uLongf length = sorted_table_data[i].length;
uint32_t padded_length = (length + 3) & 0xFFFFFFFC;
table_directory_entry& entry = tbl_directory_entry_data[index];
uLongf dest_len = buf_size;
int ret;
fseek(m_input_file, offset, SEEK_SET);
fread(buffer, 1, (uint32_t)length, m_input_file);
ret = compress2(compressed_data, &dest_len, buffer, (uLong)length, level);
if (ret != Z_OK)
{
throw runtime_error("Error compressing the data. Zlib function'compress2()' failed");
}
else
{
entry.offset = bswap32(ftell(m_output_file));
// if length of compressed data is smaller
if (dest_len < length)
{
uLongf new_dest_len = (dest_len + 3) & 0xFFFFFFFC; // pad to align on 4-byte boundary
entry.comp_length = bswap32((uint32_t)dest_len);
pad_table(compressed_data, (uint32_t)dest_len, (uint32_t)new_dest_len);
fwrite(compressed_data, 1, new_dest_len, m_output_file);
table_length += new_dest_len;
}
else
{
uLongf new_length = (length + 3) & 0xFFFFFFFC;
entry.comp_length = entry.orig_length;
pad_table(buffer, (uint32_t)length, (uint32_t)new_length);
fwrite(buffer, 1, (size_t)new_length, m_output_file);
table_length += (uint32_t)new_length;
}
}
}
return table_length;
}
void rewrite_header(WOFF_header& hdr)
{
fseek(m_output_file, 0, SEEK_SET);
fwrite(&hdr, 1, sizeof(hdr), m_output_file);
}
void rewrite_table_directory_entries(uint16_t num_tables, table_directory_entry* tbl_directory_entry_data)
{
fwrite(tbl_directory_entry_data, 1, num_tables * sizeof(*tbl_directory_entry_data), m_output_file);
}
void parse_input_file(const char* compression_level)
{
WOFF_header hdr;
uint16_t num_tables;
vector sorted_tbl_record;
vector tbl_directory_entry;
uint32_t table_length;
table_directory_entry* tbl_directory_entry_data;
sorted_table_record* sorted_tbl_record_data;
read_header(hdr, num_tables);
sorted_tbl_record.resize(num_tables);
tbl_directory_entry.resize(num_tables);
tbl_directory_entry_data = tbl_directory_entry.data();
sorted_tbl_record_data = sorted_tbl_record.data();
hdr.m_total_sfnt_size = read_table_record(num_tables, tbl_directory_entry_data, sorted_tbl_record_data);
write_temp_header(num_tables);
table_length = compress_and_write_table_data(num_tables, tbl_directory_entry_data, sorted_tbl_record_data, compression_level);
// total size of the woff file: header + no. of table_directory_entry + table_length
table_length += sizeof(hdr) + (num_tables * sizeof(*tbl_directory_entry_data));
hdr.m_length = bswap32(table_length);
rewrite_header(hdr);
rewrite_table_directory_entries(num_tables, tbl_directory_entry_data);
}
public:
OT2Woff() : m_error()
{}
~OT2Woff()
{}
string error() const
{
return m_error;
}
bool convert(const char* otf_file, const char* woff_file, const char *compression_level)
{
bool result = true;
try
{
puts(COPYRIGHT_NOTICE);
load_input_file(otf_file);
create_output_file(woff_file);
parse_input_file(compression_level);
}
catch (const exception& ex)
{
m_error = ex.what();
result = false;
}
clear();
return result;
}
};