#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; } };