initial commit
This commit is contained in:
commit
4b60ece582
327 changed files with 28286 additions and 0 deletions
17
lib/fontParser/CMakeLists.txt
Normal file
17
lib/fontParser/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
project(fontparser)
|
||||
|
||||
set(SOURCES
|
||||
font.cpp
|
||||
)
|
||||
|
||||
add_subdirectory(tables)
|
||||
|
||||
add_library(font STATIC ${SOURCES})
|
||||
target_link_libraries(font tables)
|
||||
|
||||
add_executable(fontparser main.cpp)
|
||||
target_link_libraries(fontparser font)
|
||||
|
||||
|
||||
install(TARGETS fontparser RUNTIME DESTINATION bin)
|
223
lib/fontParser/font.cpp
Normal file
223
lib/fontParser/font.cpp
Normal file
|
@ -0,0 +1,223 @@
|
|||
#include "font.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
Font::Font(const std::string& p_path):
|
||||
path(p_path),
|
||||
tables(),
|
||||
cmap(0),
|
||||
hhea(0),
|
||||
hmtx(0),
|
||||
head(0),
|
||||
name(0)
|
||||
{
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
|
||||
char * buffer;
|
||||
|
||||
buffer = new char[4];
|
||||
file.read(buffer, 4);
|
||||
uint32_t sfntVersion = ntohl(*((uint32_t*) buffer));
|
||||
if (sfntVersion == 0x00010000) {
|
||||
version = TrueTypeOutlines;
|
||||
} else if (sfntVersion == 0x4f54544f) {
|
||||
version = WithCFFData;
|
||||
} else {
|
||||
std::cout << "unsupported sfntVersion" << std::endl;
|
||||
throw 1;
|
||||
}
|
||||
delete[] buffer;
|
||||
|
||||
buffer = new char[2];
|
||||
file.read(buffer, 2);
|
||||
numberTables = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
searchRange = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
entrySelector = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
rangeShift = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
for (int i = 0; i < numberTables; ++i) {
|
||||
Table* t = Table::fromIfStream(file);
|
||||
tables.insert(std::make_pair(t->tag, t));
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
Font::~Font()
|
||||
{
|
||||
std::map<std::string, Table*>::const_iterator beg = tables.begin();
|
||||
std::map<std::string, Table*>::const_iterator end = tables.end();
|
||||
|
||||
for (; beg != end; ++beg) {
|
||||
delete beg->second;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Font::hasTable(const std::string& tag) const
|
||||
{
|
||||
std::map<std::string, Table*>::const_iterator itr = tables.find(tag);
|
||||
return itr != tables.end();
|
||||
}
|
||||
|
||||
std::list<std::string> Font::availableTables() const
|
||||
{
|
||||
std::list<std::string> res;
|
||||
std::map<std::string, Table*>::const_iterator beg = tables.begin();
|
||||
std::map<std::string, Table*>::const_iterator end = tables.end();
|
||||
|
||||
for (; beg != end; ++beg) {
|
||||
res.push_back(beg->first);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::map<uint32_t, uint32_t> Font::getCharCodeToCIDTable(uint32_t start, uint32_t end)
|
||||
{
|
||||
if (cmap == NULL) {
|
||||
cmap = static_cast<Cmap*>(tables.at("cmap"));
|
||||
cmap->read(path);
|
||||
}
|
||||
std::map<uint32_t, uint32_t> res;
|
||||
for (uint32_t i = start; i <= end; ++i) {
|
||||
res.insert(std::make_pair(i, cmap->getCID(i)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::map<uint32_t, Hmtx::HMetric> Font::getCharCodeMetrics(uint32_t start, uint32_t end)
|
||||
{
|
||||
std::map<uint32_t, uint32_t> CCtoCID = getCharCodeToCIDTable(start, end);
|
||||
std::map<uint32_t, Hmtx::HMetric> res;
|
||||
|
||||
if (hmtx == NULL) {
|
||||
hmtx = static_cast<Hmtx*>(tables.at("hmtx"));
|
||||
if (hhea == NULL) {
|
||||
hhea = static_cast<Hhea*>(tables.at("hhea"));
|
||||
hhea->read(path);
|
||||
}
|
||||
hmtx->numOfLongHorMetrics = hhea->numOfLongHorMetrics;
|
||||
hmtx->read(path);
|
||||
}
|
||||
|
||||
std::map<uint32_t, uint32_t>::const_iterator itr = CCtoCID.begin();
|
||||
std::map<uint32_t, uint32_t>::const_iterator mend = CCtoCID.end();
|
||||
|
||||
for (; itr != mend; ++itr) {
|
||||
res.insert(std::make_pair(itr->first, hmtx->getMetric(itr->second)));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
Table * Font::getTable(const std::string& tag)
|
||||
{
|
||||
std::map<std::string, Table*>::iterator itr = tables.find(tag);
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
uint16_t Font::getUnitsPerEm()
|
||||
{
|
||||
if (head == NULL) {
|
||||
head = static_cast<Head*>(tables.at("head"));
|
||||
head->read(path);
|
||||
}
|
||||
return head->unitsPerEm;
|
||||
}
|
||||
|
||||
int16_t Font::getAscent()
|
||||
{
|
||||
if (hhea == NULL) {
|
||||
hhea = static_cast<Hhea*>(tables.at("hhea"));
|
||||
hhea->read(path);
|
||||
}
|
||||
return hhea->ascent;
|
||||
}
|
||||
|
||||
int16_t Font::getDescent()
|
||||
{
|
||||
if (hhea == NULL) {
|
||||
hhea = static_cast<Hhea*>(tables.at("hhea"));
|
||||
hhea->read(path);
|
||||
}
|
||||
return hhea->descent;
|
||||
}
|
||||
|
||||
int16_t Font::getLineGap()
|
||||
{
|
||||
if (hhea == NULL) {
|
||||
hhea = static_cast<Hhea*>(tables.at("hhea"));
|
||||
hhea->read(path);
|
||||
}
|
||||
return hhea->lineGap;
|
||||
}
|
||||
|
||||
std::string Font::getNameField(std::string key)
|
||||
{
|
||||
if (name == NULL) {
|
||||
name = static_cast<Name*>(tables.at("name"));
|
||||
name->read(path);
|
||||
}
|
||||
return name->getRecord(key);
|
||||
}
|
||||
|
||||
int16_t Font::getCaretSlopeRise()
|
||||
{
|
||||
if (hhea == NULL) {
|
||||
hhea = static_cast<Hhea*>(tables.at("hhea"));
|
||||
hhea->read(path);
|
||||
}
|
||||
return hhea->caretSlopeRise;
|
||||
}
|
||||
|
||||
int16_t Font::getCaretSlopeRun()
|
||||
{
|
||||
if (hhea == NULL) {
|
||||
hhea = static_cast<Hhea*>(tables.at("hhea"));
|
||||
hhea->read(path);
|
||||
}
|
||||
return hhea->caretSlopeRun;
|
||||
}
|
||||
|
||||
int16_t Font::getXMax()
|
||||
{
|
||||
if (head == NULL) {
|
||||
head = static_cast<Head*>(tables.at("head"));
|
||||
head->read(path);
|
||||
}
|
||||
return head->xMax;
|
||||
}
|
||||
|
||||
int16_t Font::getXMin()
|
||||
{
|
||||
if (head == NULL) {
|
||||
head = static_cast<Head*>(tables.at("head"));
|
||||
head->read(path);
|
||||
}
|
||||
return head->xMin;
|
||||
}
|
||||
|
||||
int16_t Font::getYMax()
|
||||
{
|
||||
if (head == NULL) {
|
||||
head = static_cast<Head*>(tables.at("head"));
|
||||
head->read(path);
|
||||
}
|
||||
return head->yMax;
|
||||
}
|
||||
|
||||
int16_t Font::getYMin()
|
||||
{
|
||||
if (head == NULL) {
|
||||
head = static_cast<Head*>(tables.at("head"));
|
||||
head->read(path);
|
||||
}
|
||||
return head->yMin;
|
||||
}
|
62
lib/fontParser/font.h
Normal file
62
lib/fontParser/font.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef FILE_H
|
||||
#define FILE_H
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
#include "tables/table.h"
|
||||
#include "tables/cmap.h"
|
||||
#include "tables/hhea.h"
|
||||
#include "tables/hmtx.h"
|
||||
#include "tables/head.h"
|
||||
#include "tables/name.h"
|
||||
|
||||
class Font
|
||||
{
|
||||
public:
|
||||
enum SfntVersion {
|
||||
TrueTypeOutlines,
|
||||
WithCFFData
|
||||
};
|
||||
Font(const std::string& p_path);
|
||||
~Font();
|
||||
|
||||
|
||||
bool hasTable(const std::string& tag) const;
|
||||
Table* getTable(const std::string& tag);
|
||||
std::list<std::string> availableTables() const;
|
||||
std::map<uint32_t, uint32_t> getCharCodeToCIDTable(uint32_t start = 0, uint32_t end = 0xffff);
|
||||
std::map<uint32_t, Hmtx::HMetric> getCharCodeMetrics(uint32_t start = 0, uint32_t end = 0xffff);
|
||||
uint16_t getUnitsPerEm();
|
||||
int16_t getAscent();
|
||||
int16_t getDescent();
|
||||
int16_t getLineGap();
|
||||
int16_t getCaretSlopeRise();
|
||||
int16_t getCaretSlopeRun();
|
||||
int16_t getXMin();
|
||||
int16_t getXMax();
|
||||
int16_t getYMin();
|
||||
int16_t getYMax();
|
||||
std::string getNameField(std::string key);
|
||||
|
||||
SfntVersion version;
|
||||
uint16_t numberTables;
|
||||
uint16_t searchRange;
|
||||
uint16_t entrySelector;
|
||||
uint16_t rangeShift;
|
||||
|
||||
private:
|
||||
const std::string path;
|
||||
std::map<std::string, Table*> tables;
|
||||
Cmap* cmap;
|
||||
Hhea* hhea;
|
||||
Hmtx* hmtx;
|
||||
Head* head;
|
||||
Name* name;
|
||||
};
|
||||
|
||||
#endif // FILE_H
|
50
lib/fontParser/main.cpp
Normal file
50
lib/fontParser/main.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include "font.h"
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include "tables/hmtx.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Font file(argv[1]);
|
||||
|
||||
std::map<uint32_t, Hmtx::HMetric> cidMap = file.getCharCodeMetrics(0, 0x4ff);
|
||||
std::map<uint32_t, Hmtx::HMetric>::const_iterator itr = cidMap.begin();
|
||||
std::map<uint32_t, Hmtx::HMetric>::const_iterator end = cidMap.end();
|
||||
|
||||
std::cout << "{\n";
|
||||
std::cout << " \"ascent\": " << file.getAscent() << ",\n";
|
||||
std::cout << " \"descent\": " << file.getDescent() << ",\n";
|
||||
std::cout << " \"lineGap\": " << file.getLineGap() << ",\n";
|
||||
std::cout << " \"caretSlopeRise\": " << file.getCaretSlopeRise() << ",\n";
|
||||
std::cout << " \"caretSlopeRun\": " << file.getCaretSlopeRun() << ",\n";
|
||||
std::cout << " \"unitsPerEm\": " << file.getUnitsPerEm() << ",\n";
|
||||
std::cout << " \"fontFamily\": \"" << file.getNameField("fontFamily") << "\",\n";
|
||||
std::cout << " \"postScriptName\": \"" << file.getNameField("postScriptName") << "\",\n";
|
||||
|
||||
std::cout << " \"boundingBox\": {\n";
|
||||
std::cout << " \"xMin\": " << file.getXMin() << ",\n";
|
||||
std::cout << " \"xMax\": " << file.getXMax() << ",\n";
|
||||
std::cout << " \"yMin\": " << file.getYMin() << ",\n";
|
||||
std::cout << " \"yMax\": " << file.getYMax() << "\n";
|
||||
std::cout << " },\n";
|
||||
|
||||
std::cout << " \"advanceWidthArray\": [\n ";
|
||||
int i = 0;
|
||||
for (; itr != end; ++itr) {
|
||||
if (i != 0) {
|
||||
if (i == 16) {
|
||||
std::cout << ",\n ";
|
||||
i = 0;
|
||||
} else {
|
||||
std::cout << ", ";
|
||||
}
|
||||
}
|
||||
std::cout << itr->second.advanceWidth;
|
||||
++i;
|
||||
}
|
||||
|
||||
std::cout << "\n ]\n";
|
||||
std::cout << "}" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
13
lib/fontParser/tables/CMakeLists.txt
Normal file
13
lib/fontParser/tables/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
project(tables)
|
||||
|
||||
set(SOURCES
|
||||
table.cpp
|
||||
cmap.cpp
|
||||
hhea.cpp
|
||||
hmtx.cpp
|
||||
head.cpp
|
||||
name.cpp
|
||||
)
|
||||
|
||||
add_library(tables STATIC ${SOURCES})
|
218
lib/fontParser/tables/cmap.cpp
Normal file
218
lib/fontParser/tables/cmap.cpp
Normal file
|
@ -0,0 +1,218 @@
|
|||
#include "cmap.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
Cmap::Cmap(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length):
|
||||
Table(p_tag, p_checkSum, p_offset, p_length),
|
||||
initialized(false),
|
||||
mt(0)
|
||||
{
|
||||
}
|
||||
|
||||
Cmap::~Cmap()
|
||||
{
|
||||
if (initialized) {
|
||||
delete mt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Cmap::read(const std::string& path)
|
||||
{
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
file.seekg(offset);
|
||||
|
||||
char * buffer;
|
||||
buffer = new char[2];
|
||||
|
||||
file.read(buffer, 2);
|
||||
version = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
numberOfTables = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
delete[] buffer;
|
||||
buffer = new char[8];
|
||||
|
||||
std::list<Enc> encodings;
|
||||
for (int i = 0; i < numberOfTables; ++i) {
|
||||
file.read(buffer, 8);
|
||||
|
||||
char pb[2] = {buffer[0], buffer[1]};
|
||||
char eb[2] = {buffer[2], buffer[3]};
|
||||
char ob[4] = {buffer[4], buffer[5], buffer[6], buffer[7]};
|
||||
|
||||
uint16_t pid = ntohs(*((uint16_t*) pb));
|
||||
uint16_t eid = ntohs(*((uint16_t*) eb));
|
||||
uint16_t offset = ntohl(*((uint32_t*) ob));
|
||||
|
||||
//std::cout << "Found encoding platformId " << pid << ", encodingId " << eid << std::endl;
|
||||
|
||||
if (pid == 0 || (pid == 3 && eid == 1)) {
|
||||
encodings.emplace_back(pid, eid, offset);
|
||||
}
|
||||
}
|
||||
delete[] buffer;
|
||||
std::list<Enc>::const_iterator itr = encodings.begin();
|
||||
std::list<Enc>::const_iterator end = encodings.end();
|
||||
for (; itr != end; ++itr) {
|
||||
//std::cout << "Trying platformId " << itr->platformId << ", encodingId " << itr->encodingId << std::endl;
|
||||
file.seekg(offset + itr->offset);
|
||||
bool success = true;
|
||||
MappingTable* table;
|
||||
try {
|
||||
table = MappingTable::fromIfStream(file);
|
||||
} catch (int e) {
|
||||
success = false;
|
||||
}
|
||||
if (success) {
|
||||
initialized = true;
|
||||
mt = table;
|
||||
break;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
if (!initialized) {
|
||||
//std::cout << "Error reading cmap: no supported encoding format" << std::endl;
|
||||
throw 3;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Cmap::getCID(uint32_t charCode) const
|
||||
{
|
||||
return this->mt->getCID(charCode);
|
||||
}
|
||||
|
||||
|
||||
MappingTable * MappingTable::fromIfStream(std::ifstream& file)
|
||||
{
|
||||
uint64_t position = file.tellg();
|
||||
char * buffer;
|
||||
buffer = new char[2];
|
||||
file.read(buffer, 2);
|
||||
uint16_t format = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
MappingTable* table = NULL;
|
||||
|
||||
if (format >= 8) {
|
||||
if (format != 14) {
|
||||
file.read(buffer, 2); //padded .0 in stupid formats
|
||||
}
|
||||
delete[] buffer;
|
||||
buffer = new char[4];
|
||||
file.read(buffer, 4);
|
||||
uint32_t length = ntohl(*((uint32_t*) buffer));
|
||||
file.seekg(position);
|
||||
buffer = new char[length];
|
||||
file.read(buffer, length);
|
||||
} else {
|
||||
file.read(buffer, 2);
|
||||
uint16_t length = ntohs(*((uint16_t*) buffer));
|
||||
file.seekg(position);
|
||||
buffer = new char[length];
|
||||
file.read(buffer, length);
|
||||
|
||||
if (format == 4) {
|
||||
table = new Format4(buffer, length);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] buffer;
|
||||
|
||||
if (table == NULL) {
|
||||
std::cout << "Unrecognized format " << format << std::endl;
|
||||
throw 3;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
MappingTable::MappingTable(uint16_t p_f):
|
||||
format(p_f)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MappingTable::~MappingTable()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Format4::Format4(char * data, uint16_t length):
|
||||
MappingTable(4),
|
||||
charCodesEndCode(),
|
||||
segments(0),
|
||||
glyphIndexArray(0)
|
||||
{
|
||||
char sc[2] = {data[6], data[7]};
|
||||
uint16_t segCount = ntohs(*((uint16_t*) sc)) / 2;
|
||||
segments = new std::vector<SegParams>(segCount);
|
||||
|
||||
int endCodeShift = 14;
|
||||
int startCodeShift = endCodeShift + segCount * 2 + 2;
|
||||
int deltaShift = startCodeShift + segCount * 2;
|
||||
int rangeShift = deltaShift + segCount * 2;
|
||||
int giaShift = rangeShift + segCount * 2;
|
||||
int giaLength = (length - giaShift) / 2;
|
||||
glyphIndexArray = new std::vector<uint16_t>(giaLength);
|
||||
// std::cout << "Segments: " << segCount << ", ";
|
||||
// std::cout << "Glyphs: " << giaLength << "\n";
|
||||
// std::cout << "******************************************" << "\n";
|
||||
|
||||
for (int i = 0; i < segCount; ++i) {
|
||||
char cc[2] = {data[2 * i + endCodeShift], data[2 * i + endCodeShift + 1]};
|
||||
char sc[2] = {data[2 * i + startCodeShift], data[2 * i + startCodeShift + 1]};
|
||||
char dc[2] = {data[2 * i + deltaShift], data[2 * i + deltaShift + 1]};
|
||||
char rc[2] = {data[2 * i + rangeShift], data[2 * i + rangeShift + 1]};
|
||||
|
||||
uint16_t endCharCode = ntohs(*((uint16_t*) cc));
|
||||
uint16_t startCharCode = ntohs(*((uint16_t*) sc));
|
||||
int16_t delta = ntohs(*((int16_t*) dc));
|
||||
uint16_t range = ntohs(*((uint16_t*) rc));
|
||||
|
||||
SegParams& sp = segments->at(i);
|
||||
sp.endCode = endCharCode;
|
||||
sp.startCode = startCharCode;
|
||||
sp.idDelta = delta;
|
||||
sp.idRangeOffset = range;
|
||||
|
||||
charCodesEndCode.insert(std::make_pair(endCharCode, i));
|
||||
// std::cout << "Segment " << i << ",\t";
|
||||
// std::cout << "Start " << startCharCode << ",\t";
|
||||
// std::cout << "End " << endCharCode << ",\t";
|
||||
// std::cout << "Delta " << delta << ",\t";
|
||||
// std::cout << "Range " << range << "\n";
|
||||
}
|
||||
// std::cout << "******************************************" << std::endl;;
|
||||
|
||||
for (int i = 0; i < giaLength; ++i) {
|
||||
char cc[2] = {data[2 * i + giaShift], data[2 * i + giaShift + 1]};
|
||||
uint16_t glyphIndex = ntohs(*((uint16_t*) cc));
|
||||
glyphIndexArray->at(i) = glyphIndex;
|
||||
}
|
||||
}
|
||||
|
||||
Format4::~Format4()
|
||||
{
|
||||
delete segments;
|
||||
delete glyphIndexArray;
|
||||
}
|
||||
|
||||
|
||||
uint32_t Format4::getCID(uint32_t charCode) const
|
||||
{
|
||||
uint16_t cid;
|
||||
uint16_t c = charCode & 0xffff;
|
||||
std::map<uint16_t, uint16_t>::const_iterator itr = charCodesEndCode.lower_bound(c);
|
||||
uint16_t i = itr->second;
|
||||
SegParams& seg = segments->at(i);
|
||||
if (seg.startCode > c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (seg.idRangeOffset == 0) {
|
||||
cid = c + seg.idDelta;
|
||||
} else {
|
||||
cid = i + seg.idRangeOffset - segments->size() + c - seg.startCode;
|
||||
}
|
||||
|
||||
return cid;
|
||||
}
|
66
lib/fontParser/tables/cmap.h
Normal file
66
lib/fontParser/tables/cmap.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef CMAP_H
|
||||
#define CMAP_H
|
||||
|
||||
#include "table.h"
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
struct Enc {
|
||||
Enc(uint16_t pid, uint16_t eid, uint32_t off): platformId(pid), encodingId(eid), offset(off) {}
|
||||
|
||||
uint16_t platformId;
|
||||
uint16_t encodingId;
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
class MappingTable {
|
||||
protected:
|
||||
MappingTable(uint16_t p_f);
|
||||
|
||||
uint16_t format;
|
||||
|
||||
public:
|
||||
static MappingTable* fromIfStream(std::ifstream& file);
|
||||
virtual ~MappingTable();
|
||||
virtual uint32_t getCID(uint32_t charCode) const = 0;
|
||||
};
|
||||
|
||||
class Format4 : public MappingTable {
|
||||
public:
|
||||
Format4(char* data, uint16_t length);
|
||||
~Format4();
|
||||
|
||||
uint32_t getCID(uint32_t charCode) const override;
|
||||
|
||||
private:
|
||||
struct SegParams {
|
||||
uint16_t endCode;
|
||||
uint16_t startCode;
|
||||
int16_t idDelta;
|
||||
uint16_t idRangeOffset;
|
||||
};
|
||||
|
||||
std::map<uint16_t, uint16_t> charCodesEndCode;
|
||||
std::vector<SegParams>* segments;
|
||||
std::vector<uint16_t>* glyphIndexArray;
|
||||
};
|
||||
|
||||
class Cmap : public Table
|
||||
{
|
||||
public:
|
||||
Cmap(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length);
|
||||
~Cmap();
|
||||
|
||||
void read(const std::string & path) override;
|
||||
uint32_t getCID(uint32_t charCode) const;
|
||||
|
||||
uint16_t version;
|
||||
uint16_t numberOfTables;
|
||||
|
||||
private:
|
||||
bool initialized;
|
||||
MappingTable* mt;
|
||||
};
|
||||
|
||||
#endif // CMAP_H
|
93
lib/fontParser/tables/head.cpp
Normal file
93
lib/fontParser/tables/head.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "head.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
Head::Head(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length):
|
||||
Table(p_tag, p_checkSum, p_offset, p_length),
|
||||
fontRevisionMajor(0),
|
||||
fontRevisionMinor(0),
|
||||
flags(0),
|
||||
unitsPerEm(0),
|
||||
xMin(0),
|
||||
yMin(0),
|
||||
xMax(0),
|
||||
yMax(0),
|
||||
macStyle(0),
|
||||
lowestRecPPEM(0),
|
||||
fontDirectionHint(0),
|
||||
indexToLocFormat(0)
|
||||
{
|
||||
}
|
||||
|
||||
Head::~Head()
|
||||
{
|
||||
}
|
||||
|
||||
void Head::read(const std::string& path)
|
||||
{
|
||||
char * buffer;
|
||||
buffer = new char[2];
|
||||
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
file.seekg(offset);
|
||||
|
||||
file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0";
|
||||
file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0";
|
||||
|
||||
file.read(buffer, 2);
|
||||
fontRevisionMajor = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
fontRevisionMinor = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
delete[] buffer;
|
||||
buffer = new char[4];
|
||||
file.read(buffer, 4); //checkSumAdjustment - it's something fishy, no idea what to use it for;
|
||||
file.read(buffer, 4); //magicNumber, always set to 0x5f0f3cf5;
|
||||
delete[] buffer;
|
||||
buffer = new char[2];
|
||||
|
||||
file.read(buffer, 2);
|
||||
flags = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
unitsPerEm = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2); //creation date is a signed int64
|
||||
file.read(buffer, 2);
|
||||
file.read(buffer, 2);
|
||||
file.read(buffer, 2);
|
||||
|
||||
file.read(buffer, 2); //last modification date is a signed int64
|
||||
file.read(buffer, 2);
|
||||
file.read(buffer, 2);
|
||||
file.read(buffer, 2);
|
||||
|
||||
file.read(buffer, 2);
|
||||
xMin = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
yMin = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
xMax = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
yMax = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
macStyle = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
lowestRecPPEM = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
fontDirectionHint = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
indexToLocFormat = ntohs(*((int16_t*) buffer));
|
||||
|
||||
//and there is stil uint16 glyph data format, but its always 0;
|
||||
|
||||
file.close();
|
||||
delete[] buffer;
|
||||
}
|
28
lib/fontParser/tables/head.h
Normal file
28
lib/fontParser/tables/head.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef HEAD_H
|
||||
#define HEAD_H
|
||||
|
||||
#include "table.h"
|
||||
|
||||
class Head : public Table
|
||||
{
|
||||
public:
|
||||
Head(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length);
|
||||
~Head();
|
||||
|
||||
void read(const std::string & path) override;
|
||||
|
||||
uint16_t fontRevisionMajor;
|
||||
uint16_t fontRevisionMinor;
|
||||
uint16_t flags;
|
||||
uint16_t unitsPerEm;
|
||||
int16_t xMin;
|
||||
int16_t yMin;
|
||||
int16_t xMax;
|
||||
int16_t yMax;
|
||||
uint16_t macStyle;
|
||||
uint16_t lowestRecPPEM;
|
||||
int16_t fontDirectionHint;
|
||||
int16_t indexToLocFormat;
|
||||
};
|
||||
|
||||
#endif // HEAD_H
|
76
lib/fontParser/tables/hhea.cpp
Normal file
76
lib/fontParser/tables/hhea.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include "hhea.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
Hhea::Hhea(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length):
|
||||
Table(p_tag, p_checkSum, p_offset, p_length),
|
||||
ascent(0),
|
||||
descent(0),
|
||||
lineGap(0),
|
||||
advanceWidthMax(0),
|
||||
minLeftSideBearing(0),
|
||||
minRightSideBearing(0),
|
||||
xMaxExtent(0),
|
||||
caretSlopeRise(0),
|
||||
caretSlopeRun(0),
|
||||
caretOffset(0),
|
||||
numOfLongHorMetrics(0)
|
||||
{
|
||||
}
|
||||
|
||||
Hhea::~Hhea()
|
||||
{
|
||||
}
|
||||
|
||||
void Hhea::read(const std::string& path)
|
||||
{
|
||||
char * buffer;
|
||||
buffer = new char[2];
|
||||
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
file.seekg(offset);
|
||||
|
||||
file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0";
|
||||
file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0";
|
||||
|
||||
file.read(buffer, 2);
|
||||
ascent = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
descent = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
lineGap = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
advanceWidthMax = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
minLeftSideBearing = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
minRightSideBearing = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
xMaxExtent = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
caretSlopeRise = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
caretSlopeRun = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
caretOffset = ntohs(*((int16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2); //reserved empty field, supposed to be 0;
|
||||
file.read(buffer, 2); //reserved empty field, supposed to be 0;
|
||||
file.read(buffer, 2); //reserved empty field, supposed to be 0;
|
||||
file.read(buffer, 2); //reserved empty field, supposed to be 0;
|
||||
file.read(buffer, 2); //metricDataFormat, it's supposed to be 0;
|
||||
|
||||
file.read(buffer, 2);
|
||||
numOfLongHorMetrics = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
delete[] buffer;
|
||||
file.close();
|
||||
}
|
27
lib/fontParser/tables/hhea.h
Normal file
27
lib/fontParser/tables/hhea.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef HHEA_H
|
||||
#define HHEA_H
|
||||
|
||||
#include "table.h"
|
||||
|
||||
class Hhea : public Table
|
||||
{
|
||||
public:
|
||||
Hhea(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length);
|
||||
~Hhea();
|
||||
|
||||
void read(const std::string & path) override;
|
||||
|
||||
int16_t ascent;
|
||||
int16_t descent;
|
||||
int16_t lineGap;
|
||||
uint16_t advanceWidthMax;
|
||||
int16_t minLeftSideBearing;
|
||||
int16_t minRightSideBearing;
|
||||
int16_t xMaxExtent;
|
||||
int16_t caretSlopeRise;
|
||||
int16_t caretSlopeRun;
|
||||
int16_t caretOffset;
|
||||
uint16_t numOfLongHorMetrics;
|
||||
};
|
||||
|
||||
#endif // HHEA_H
|
59
lib/fontParser/tables/hmtx.cpp
Normal file
59
lib/fontParser/tables/hmtx.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#include "hmtx.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
Hmtx::Hmtx(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length):
|
||||
Table(p_tag, p_checkSum, p_offset, p_length),
|
||||
numOfLongHorMetrics(0),
|
||||
longHorMetric(0)
|
||||
{
|
||||
}
|
||||
|
||||
Hmtx::~Hmtx()
|
||||
{
|
||||
delete longHorMetric;
|
||||
}
|
||||
|
||||
void Hmtx::read(const std::string& path)
|
||||
{
|
||||
if (numOfLongHorMetrics == 0) {
|
||||
throw 1;
|
||||
}
|
||||
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
file.seekg(offset);
|
||||
|
||||
char * buffer;
|
||||
buffer = new char[2];
|
||||
|
||||
longHorMetric = new std::vector<HMetric>(numOfLongHorMetrics);
|
||||
|
||||
for (int i = 0; i < numOfLongHorMetrics; ++i) {
|
||||
HMetric& met = longHorMetric->at(i);
|
||||
|
||||
file.read(buffer, 2);
|
||||
uint16_t aw = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
int16_t lsb = ntohs(*((int16_t*) buffer));
|
||||
|
||||
met.advanceWidth = aw;
|
||||
met.leftSideBearing = lsb;
|
||||
}
|
||||
file.close();
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
Hmtx::HMetric::HMetric():
|
||||
advanceWidth(0),
|
||||
leftSideBearing(0)
|
||||
{
|
||||
}
|
||||
|
||||
Hmtx::HMetric Hmtx::getMetric(uint16_t cid) const
|
||||
{
|
||||
if (cid >= longHorMetric->size()) {
|
||||
cid = longHorMetric->size() - 1;
|
||||
}
|
||||
|
||||
return longHorMetric->at(cid);
|
||||
}
|
29
lib/fontParser/tables/hmtx.h
Normal file
29
lib/fontParser/tables/hmtx.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#ifndef HMTX_H
|
||||
#define HMTX_H
|
||||
|
||||
#include "table.h"
|
||||
#include <vector>
|
||||
|
||||
class Hmtx : public Table
|
||||
{
|
||||
public:
|
||||
Hmtx(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length);
|
||||
~Hmtx();
|
||||
|
||||
uint16_t numOfLongHorMetrics;
|
||||
|
||||
struct HMetric {
|
||||
HMetric();
|
||||
|
||||
uint16_t advanceWidth;
|
||||
int16_t leftSideBearing;
|
||||
};
|
||||
|
||||
void read(const std::string & path) override;
|
||||
HMetric getMetric(uint16_t cid) const;
|
||||
|
||||
private:
|
||||
std::vector<HMetric>* longHorMetric;
|
||||
};
|
||||
|
||||
#endif // HMTX_H
|
136
lib/fontParser/tables/name.cpp
Normal file
136
lib/fontParser/tables/name.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "name.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
|
||||
const std::map<std::string, uint16_t> Name::nameIds({
|
||||
{ "copyright", 0 },
|
||||
{ "fontFamily", 1 },
|
||||
{ "fontSubfamily", 2 },
|
||||
{ "uniqueSubfamilyId", 3 },
|
||||
{ "fullFontName", 4 },
|
||||
{ "nameTableVersion", 5 },
|
||||
{ "postScriptName", 6 },
|
||||
{ "trademarkNotice", 7 },
|
||||
{ "manufacturerName", 8 },
|
||||
{ "designerName", 9 },
|
||||
{ "description", 10 },
|
||||
{ "vendorURL", 11 },
|
||||
{ "designerURL", 12 },
|
||||
{ "licenseDescription", 13 },
|
||||
{ "licenseURL", 14 },
|
||||
|
||||
{ "preferredFamily", 16 },
|
||||
{ "preferredSubfamily", 17 },
|
||||
{ "compatibleFull", 18 },
|
||||
{ "sampleText", 19 },
|
||||
{ "postScriptCID", 20 }
|
||||
});
|
||||
|
||||
Name::Name(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length):
|
||||
Table(p_tag, p_checkSum, p_offset, p_length),
|
||||
names()
|
||||
{
|
||||
}
|
||||
|
||||
Name::~Name()
|
||||
{
|
||||
}
|
||||
|
||||
void Name::read(const std::string& path)
|
||||
{
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
file.seekg(offset);
|
||||
|
||||
char * buffer;
|
||||
buffer = new char[2];
|
||||
|
||||
file.read(buffer, 2); //format. it is always 0 or 1 for stupid microsoft langTags, but I don't cate, gonna use offset;
|
||||
file.read(buffer, 2);
|
||||
uint16_t count = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
file.read(buffer, 2);
|
||||
uint32_t storageOffset = offset + ntohs(*((uint16_t*) buffer));
|
||||
|
||||
std::list<NameRecord> list;
|
||||
std::set<uint16_t> ids;
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
file.read(buffer, 2);
|
||||
uint16_t pid = ntohs(*((uint16_t*) buffer));
|
||||
file.read(buffer, 2);
|
||||
uint16_t eid = ntohs(*((uint16_t*) buffer));
|
||||
file.read(buffer, 2);
|
||||
uint16_t lid = ntohs(*((uint16_t*) buffer));
|
||||
file.read(buffer, 2);
|
||||
uint16_t nid = ntohs(*((uint16_t*) buffer));
|
||||
file.read(buffer, 2);
|
||||
uint16_t length = ntohs(*((uint16_t*) buffer));
|
||||
file.read(buffer, 2);
|
||||
uint16_t nameOffset = ntohs(*((uint16_t*) buffer));
|
||||
|
||||
//std::cout << "Found pid " << pid << ", eid " << eid << ", nid " << nid << std::endl;
|
||||
|
||||
if (ids.find(nid) == ids.end()) {
|
||||
if ((pid == 0 && (eid == 3 || eid == 4)) || (pid == 3 && eid == 1)) { //screw microsoft, screw apple;
|
||||
list.emplace_back(pid, eid, lid, nid, length, nameOffset);
|
||||
ids.insert(nid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
std::list<NameRecord>::const_iterator itr;
|
||||
for (itr = list.begin(); itr != list.end(); ++itr) {
|
||||
const NameRecord& nr = *itr;
|
||||
file.seekg(storageOffset + nr.offset);
|
||||
|
||||
if ((nr.platformId == 0 && (nr.encodingId == 3 || nr.encodingId == 4)) || (nr.platformId == 3 && nr.encodingId == 1)) {
|
||||
char16_t buf[nr.length / 2];
|
||||
for (int i = 0; i < nr.length / 2; ++i) {
|
||||
file.read(buffer, 2);
|
||||
buf[i] = ntohs(*((char16_t*) buffer));
|
||||
}
|
||||
std::u16string string(buf, nr.length / 2);
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
|
||||
names.insert(std::make_pair(nr.nameId, convert.to_bytes(string)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delete[] buffer;
|
||||
file.close();
|
||||
}
|
||||
|
||||
std::string Name::getRecord(uint16_t id) const
|
||||
{
|
||||
std::string res("");
|
||||
std::map<uint16_t, std::string>::const_iterator itr = names.find(id);
|
||||
if (itr != names.end()) {
|
||||
res = itr->second;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string Name::getRecord(const std::string& name) const
|
||||
{
|
||||
std::map<std::string, uint16_t>::const_iterator itr = nameIds.find(name);
|
||||
if (itr == nameIds.end()) {
|
||||
return "";
|
||||
} else {
|
||||
return getRecord(itr->second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NameRecord::NameRecord(uint16_t pid, uint16_t eid, uint16_t lid, uint16_t nid, uint16_t p_l, uint16_t p_o):
|
||||
platformId(pid),
|
||||
encodingId(eid),
|
||||
languageId(lid),
|
||||
nameId(nid),
|
||||
length(p_l),
|
||||
offset(p_o)
|
||||
{
|
||||
}
|
35
lib/fontParser/tables/name.h
Normal file
35
lib/fontParser/tables/name.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef NAME_H
|
||||
#define NAME_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "table.h"
|
||||
|
||||
class Name : public Table
|
||||
{
|
||||
public:
|
||||
Name(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length);
|
||||
~Name();
|
||||
|
||||
void read(const std::string & path) override;
|
||||
std::string getRecord(uint16_t id) const;
|
||||
std::string getRecord(const std::string& name) const;
|
||||
|
||||
private:
|
||||
std::map<uint16_t, std::string> names;
|
||||
|
||||
static const std::map<std::string, uint16_t> nameIds;
|
||||
};
|
||||
|
||||
struct NameRecord {
|
||||
NameRecord(uint16_t pid, uint16_t eid, uint16_t lid, uint16_t nid, uint16_t p_l, uint16_t p_o);
|
||||
uint16_t platformId;
|
||||
uint16_t encodingId;
|
||||
uint16_t languageId;
|
||||
uint16_t nameId;
|
||||
uint16_t length;
|
||||
uint16_t offset;
|
||||
};
|
||||
|
||||
#endif // NAME_H
|
56
lib/fontParser/tables/table.cpp
Normal file
56
lib/fontParser/tables/table.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "table.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "cmap.h"
|
||||
#include "hhea.h"
|
||||
#include "hmtx.h"
|
||||
#include "head.h"
|
||||
#include "name.h"
|
||||
|
||||
Table::Table(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length):
|
||||
tag(p_tag),
|
||||
checkSum(p_checkSum),
|
||||
offset(p_offset),
|
||||
length(p_length)
|
||||
{
|
||||
}
|
||||
|
||||
Table::~Table()
|
||||
{
|
||||
}
|
||||
|
||||
Table* Table::fromIfStream(std::ifstream& stream)
|
||||
{
|
||||
char * buffer;
|
||||
buffer = new char[4];
|
||||
stream.read(buffer, 4);
|
||||
std::string tag(buffer, 4);
|
||||
|
||||
stream.read(buffer, 4);
|
||||
uint32_t cs = ntohl(*((uint32_t*) buffer));
|
||||
|
||||
stream.read(buffer, 4);
|
||||
uint32_t offset = ntohl(*((uint32_t*) buffer));
|
||||
|
||||
stream.read(buffer, 4);
|
||||
uint32_t l = ntohl(*((uint32_t*) buffer));
|
||||
|
||||
if (tag == "cmap") {
|
||||
return new Cmap(tag, cs, offset, l);
|
||||
} else if (tag == "hhea") {
|
||||
return new Hhea(tag, cs, offset, l);
|
||||
} else if (tag == "hmtx") {
|
||||
return new Hmtx(tag, cs, offset, l);
|
||||
} else if (tag == "head") {
|
||||
return new Head(tag, cs, offset, l);
|
||||
} else if (tag == "name") {
|
||||
return new Name(tag, cs, offset, l);
|
||||
} else {
|
||||
return new Table(tag, cs, offset, l);
|
||||
}
|
||||
}
|
||||
|
||||
void Table::read(const std::string& path)
|
||||
{
|
||||
std::cout << "table with type " << tag << " is not supported yet" << std::endl;
|
||||
}
|
26
lib/fontParser/tables/table.h
Normal file
26
lib/fontParser/tables/table.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef TABLE_H
|
||||
#define TABLE_H
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
class Table
|
||||
{
|
||||
public:
|
||||
Table(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length);
|
||||
virtual ~Table();
|
||||
|
||||
const std::string tag;
|
||||
const uint32_t checkSum;
|
||||
const uint32_t offset;
|
||||
const uint32_t length;
|
||||
|
||||
static Table* fromIfStream(std::ifstream& stream);
|
||||
|
||||
virtual void read(const std::string& path);
|
||||
};
|
||||
|
||||
|
||||
#endif // TABLE_H
|
Loading…
Add table
Add a link
Reference in a new issue