diff options
Diffstat (limited to 'fonts/utilities/ot2woff/ot2woff.h')
-rw-r--r-- | fonts/utilities/ot2woff/ot2woff.h | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/fonts/utilities/ot2woff/ot2woff.h b/fonts/utilities/ot2woff/ot2woff.h new file mode 100644 index 0000000000..8ec2d67d33 --- /dev/null +++ b/fonts/utilities/ot2woff/ot2woff.h @@ -0,0 +1,406 @@ +#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 <https://www.gnu.org/licenses/>. + + + The author may be contacted via the e-mail address pfranejr@hotmail.com +*/ + +#include <stdexcept> +#include <string> +#include <stdio.h> +#include <vector> +#include <stdint.h> +#include <stdlib.h> +#include <zlib.h> + +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<Bytef> 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_table_record> sorted_tbl_record; + vector<table_directory_entry> 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; + } +}; |