#pragma once /* Woff2OT: WOFF to OpenType (PostScript/TrueType) 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 "Woff2OT 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 }; }; class Woff2OT { 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* woff_file) { m_input_file = fopen(woff_file, "rb"); if (!m_input_file) { throw runtime_error("Unable to load the WOFF font file"); } } void create_output_file(const char* ot_file) { m_output_file = fopen(ot_file, "wb"); if (!m_output_file) { throw runtime_error("Unable to create the OpenType file"); } } uint32_t compute_table_sizes(table_directory_entry* tbl_directory_entry_data, uint32_t num_tables) { uint32_t total_size = 0; for (uint16_t i = 0; i < num_tables; ++i) { uint32_t size = bswap32(tbl_directory_entry_data[i].orig_length); total_size += (size + 3) & 0xFFFFFFFC; } return total_size; } void write_temp_table_record(uint16_t num_tables) { table_record record; fwrite(&record, 1, num_tables * sizeof(record), m_output_file); } uint32_t get_max_buffer_size(table_directory_entry* tbl_directory_entry_data, uint16_t num_tables) { uint32_t size = 0; for (uint16_t i = 0; i < num_tables; ++i) { uint32_t length = bswap32(tbl_directory_entry_data[i].orig_length); uint32_t offset = bswap32(tbl_directory_entry_data[i].offset); uint32_t comp_length = bswap32(tbl_directory_entry_data[i].comp_length); tbl_directory_entry_data[i].orig_length = length; tbl_directory_entry_data[i].offset = offset; tbl_directory_entry_data[i].comp_length = comp_length; size = length > size ? 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; } } static int compare_offset(const void* a, const void* b) { table_directory_entry* i = (table_directory_entry*)a; table_directory_entry* j = (table_directory_entry*)b; return i->offset - j->offset; } /* void dump_tables(table_directory_entry* tbl_directory_entry_data, uint32_t num_tables) { for (uint16_t i = 0; i < num_tables; ++i) { printf("%.*s %u\n", 4, (char*)&tbl_directory_entry_data[i].tag, tbl_directory_entry_data[i].offset); } } */ void write_tables(table_directory_entry* tbl_directory_entry_data, uint32_t num_tables) { uint32_t buffer_size = get_max_buffer_size(tbl_directory_entry_data, num_tables) + 4; vector buf1(buffer_size); vector buf2(buffer_size); byte_t* original_data = buf1.data(); byte_t* compressed_data = buf2.data(); qsort(tbl_directory_entry_data, num_tables, sizeof(*tbl_directory_entry_data), compare_offset); for (uint16_t i = 0; i < num_tables; ++i) { table_directory_entry& entry = tbl_directory_entry_data[i]; uint32_t offset = entry.offset; uLongf orig_length = entry.orig_length; uLongf compressed_length = entry.comp_length; int ret; fseek(m_input_file, offset, SEEK_SET); entry.offset = bswap32(ftell(m_output_file)); if (compressed_length < orig_length) { uLongf dest_len = buffer_size; uLongf new_dest_len; fread(compressed_data, 1, compressed_length, m_input_file); ret = uncompress(original_data, &dest_len, compressed_data, compressed_length); if (ret != Z_OK) { throw runtime_error("Error decompressing the table"); } else if (dest_len != orig_length) { throw runtime_error("Decompressed length does not match the original length"); } new_dest_len = (dest_len + 3) & 0xFFFFFFFC; pad_table(original_data, dest_len, new_dest_len); fwrite(original_data, 1, new_dest_len, m_output_file); entry.orig_length = bswap32(dest_len); } else { uLongf new_length = (orig_length + 3) & 0xFFFFFFFC; fread(original_data, 1, orig_length, m_input_file); pad_table(original_data, orig_length, new_length); fwrite(original_data, 1, new_length, m_output_file); entry.orig_length = bswap32(orig_length); } } } static int compare_tag(const void* a, const void* b) { table_directory_entry* i = (table_directory_entry*)a; table_directory_entry* j = (table_directory_entry*)b; return strncmp(((char*)&i->tag), ((char*)&j->tag), 4); } void rewrite_table_record(long offset, table_directory_entry* tbl_directory_entry_data, uint32_t num_tables) { table_record record; qsort(tbl_directory_entry_data, num_tables, sizeof(*tbl_directory_entry_data), compare_tag); fseek(m_output_file, offset, SEEK_SET); for (uint16_t i = 0; i < num_tables; ++i) { table_directory_entry& entry = tbl_directory_entry_data[i]; record.checksum = entry.orig_checksum; record.length = entry.orig_length; record.offset = entry.offset; record.table_tag = entry.tag; fwrite(&record, 1, sizeof(record), m_output_file); } } uint16_t max_power_of_2(uint16_t num_tables) { for (int i = num_tables; i >= 1; --i) { if ((i & (i - 1)) == 0) return i; } return 0; } void write_ot_header(uint32_t flavor, uint16_t num_tables) { uint16_t mp2 = max_power_of_2(num_tables); offset_table header; header.sfntVersion = flavor; header.numTables = bswap16(num_tables); header.searchRange = bswap16(mp2 * 16); header.entrySelector = bswap16((uint16_t)log2(mp2)); header.rangeShift = bswap16(num_tables * 16 - (mp2 * 16)); fwrite(&header, 1, sizeof(header), m_output_file); } void parse_input_file() { WOFF_header hdr; if (fread(&hdr, 1, sizeof(hdr), m_input_file) != sizeof(hdr)) { throw runtime_error("Error reading the input file"); } else { uint16_t num_tables = bswap16(hdr.m_num_tables); long offset; if (num_tables > 0) { vector tbl_directory_entry(num_tables); table_directory_entry* tbl_directory_entry_data = tbl_directory_entry.data(); uint32_t total_sfnt_size = bswap32(hdr.m_total_sfnt_size); uint32_t size_to_read = num_tables * sizeof(*tbl_directory_entry_data); uint32_t total_table_size; uint32_t computed_sfnt_size; // write the OT header write_ot_header(hdr.m_flavor, num_tables); offset = ftell(m_output_file); write_temp_table_record(num_tables); if (fread(tbl_directory_entry_data, 1, size_to_read, m_input_file) != size_to_read) { throw runtime_error("Error reading the table directory entries"); } total_table_size = compute_table_sizes(tbl_directory_entry_data, num_tables); computed_sfnt_size = sizeof(offset_table) + (num_tables * sizeof(table_record)) + total_table_size; if (total_sfnt_size != computed_sfnt_size) { throw runtime_error("Invalid 'totalSfntSize' size"); } write_tables(tbl_directory_entry_data, num_tables); rewrite_table_record(offset, tbl_directory_entry_data, num_tables); } else { throw runtime_error("No tables found"); } } } public: Woff2OT() : m_error() {} ~Woff2OT() {} string error() const { return m_error; } uint32_t get_font_type(const char* woff_file) { uint32_t result = 0; try { WOFF_header hdr; load_input_file(woff_file); if (fread(&hdr, 1, sizeof(hdr), m_input_file) != sizeof(hdr)) { throw runtime_error("Error reading the input file"); } result = bswap32(hdr.m_flavor); } catch (const exception& ex) { m_error = ex.what(); } clear(); return result; } uint32_t write_font_type(const char* woff_file) { uint32_t type; type = get_font_type(woff_file); switch (type) { case OPENTYPE_TRUETYPE: puts("Font type is OpenType TrueType (.TTF)"); break; case OPENTYPE_TRUETYPE_MAC: puts("Font type is OpenType TrueType for Mac (.TTF)"); break; case OPENTYPE_CFF: puts("Font type is OpenType PostScript (.OTF)"); break; default: puts("Font type is unknown or font is not a WOFF font"); break; } return type; } bool convert(const char* woff_file, const char* ot_file) { bool result = true; try { load_input_file(woff_file); create_output_file(ot_file); parse_input_file(); } catch (const exception& ex) { m_error = ex.what(); result = false; } clear(); return result; } };