diff options
Diffstat (limited to 'fonts/utilities/woff2ot/woff2ot.h')
-rw-r--r-- | fonts/utilities/woff2ot/woff2ot.h | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/fonts/utilities/woff2ot/woff2ot.h b/fonts/utilities/woff2ot/woff2ot.h new file mode 100644 index 0000000000..edbfe66809 --- /dev/null +++ b/fonts/utilities/woff2ot/woff2ot.h @@ -0,0 +1,440 @@ +#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 <https://www.gnu.org/licenses/>. + + + The author may be contacted via the e-mail address pfranejr@hotmail.com +*/ + +#include <stdexcept> +#include <string> +#include <cstdio> +#include <vector> +#include <stdint.h> +#include <stdlib.h> +#include <zlib.h> + + +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<Bytef> buf1(buffer_size); + vector<Bytef> 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<table_directory_entry> 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; + } +};
\ No newline at end of file |