Files
DocumentServer-v-9.2.0/core/PdfFile/SrcWriter/FontTTWriter.cpp
T
Yajbir Singh f1b860b25c
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

1225 lines
38 KiB
C++

/*
* (c) Copyright Ascensio System SIA 2010-2023
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
#include "FontTTWriter.h"
#include "../../DesktopEditor/common/File.h"
#define ttcfTag 0x74746366
#define cmapTag 0x636d6170
#define glyfTag 0x676c7966
#define headTag 0x68656164
#define hheaTag 0x68686561
#define hmtxTag 0x686d7478
#define locaTag 0x6c6f6361
#define nameTag 0x6e616d65
#define os2Tag 0x4f532f32
#define postTag 0x706f7374
#define cvtTag 0x63767420
#define fpgmTag 0x6670676d
#define maxpTag 0x6d617870
#define prepTag 0x70726570
namespace PdfWriter
{
struct TrueTypeLoca
{
int nIndex;
int nOrigOffset;
int nNewOffset;
int nLen;
};
static int CompareTrueTypeLocaOffset(const void *pL1, const void *pL2)
{
TrueTypeLoca *pLoca1 = (TrueTypeLoca *)pL1;
TrueTypeLoca *pLoca2 = (TrueTypeLoca *)pL2;
if (pLoca1->nOrigOffset == pLoca2->nOrigOffset)
return pLoca1->nIndex - pLoca2->nIndex;
return pLoca1->nOrigOffset - pLoca2->nOrigOffset;
}
static int CompareTrueTypeLocaIndex(const void *pL1, const void *pL2)
{
TrueTypeLoca *pLoca1 = (TrueTypeLoca *)pL1;
TrueTypeLoca *pLoca2 = (TrueTypeLoca *)pL2;
return pLoca1->nIndex - pLoca2->nIndex;
}
static int CompareTrueTypeTableTag(const void *pTab1, const void *pTab2)
{
TrueTypeTable *pTable1 = (TrueTypeTable *)pTab1;
TrueTypeTable *pTable2 = (TrueTypeTable *)pTab2;
return (int)pTable1->unTag - (int)pTable2->unTag;
}
//----------------------------------------------------------------------------------------
// CFontFileBase
//----------------------------------------------------------------------------------------
CFontFileBase::CFontFileBase(char *sFile, int nLen, bool bFreeFileData)
{
m_sFileData = m_sFile = (unsigned char *)sFile;
m_nLen = nLen;
m_bFreeFileData = bFreeFileData;
}
CFontFileBase::~CFontFileBase()
{
if (m_bFreeFileData)
free(m_sFileData);
}
char* CFontFileBase::ReadFile(const std::wstring & wsFileName, int *pnFileLen)
{
NSFile::CFileBinary oFile;
if (oFile.OpenFile(wsFileName) == false)
return NULL;
int nLen = oFile.GetFileSize();
DWORD nLenRead = 0;
char *sBuffer = (char *)malloc(nLen);
if (NULL != sBuffer)
{
if ((int)oFile.ReadFile((BYTE*)sBuffer, nLen, nLenRead) == false)
{
if (sBuffer)
free(sBuffer);
oFile.CloseFile();
return NULL;
}
oFile.CloseFile();
*pnFileLen = nLen;
}
else
{
oFile.CloseFile();
}
return sBuffer;
}
int CFontFileBase::GetS8(int nPos, bool *pbSuccess)
{
*pbSuccess = true;
if (nPos < 0 || nPos >= m_nLen)
{
*pbSuccess = false;
return 0;
}
int nRes = m_sFile[nPos];
if (nRes & 0x80)
nRes |= ~0xff;
return nRes;
}
int CFontFileBase::GetU8(int nPos, bool *pbSuccess)
{
*pbSuccess = true;
if (nPos < 0 || nPos >= m_nLen)
{
*pbSuccess = false;
return 0;
}
return m_sFile[nPos];
}
int CFontFileBase::GetS16BE(int nPos, bool *pbSuccess)
{
*pbSuccess = true;
if (nPos < 0 || nPos + 1 >= m_nLen)
{
*pbSuccess = false;
return 0;
}
int nRes = m_sFile[nPos];
nRes = (nRes << 8) + m_sFile[nPos + 1];
if (nRes & 0x8000)
nRes |= ~0xffff;
return nRes;
}
int CFontFileBase::GetU16BE(int nPos, bool *pbSuccess)
{
*pbSuccess = true;
if (nPos < 0 || nPos + 1 >= m_nLen)
{
*pbSuccess = false;
return 0;
}
int nRes = m_sFile[nPos];
nRes = (nRes << 8) + m_sFile[nPos + 1];
return nRes;
}
int CFontFileBase::GetS32BE(int nPos, bool *pbSuccess)
{
*pbSuccess = true;
if (nPos < 0 || nPos + 3 >= m_nLen)
{
*pbSuccess = false;
return 0;
}
int nRes = m_sFile[nPos];
nRes = (nRes << 8) + m_sFile[nPos + 1];
nRes = (nRes << 8) + m_sFile[nPos + 2];
nRes = (nRes << 8) + m_sFile[nPos + 3];
if (nRes & 0x80000000)
nRes |= ~0xffffffff;
return nRes;
}
unsigned int CFontFileBase::GetU32BE(int nPos, bool *pbSuccess)
{
*pbSuccess = true;
if (nPos < 0 || nPos + 3 >= m_nLen)
{
*pbSuccess = false;
return 0;
}
unsigned int nRes = m_sFile[nPos];
nRes = (nRes << 8) + m_sFile[nPos + 1];
nRes = (nRes << 8) + m_sFile[nPos + 2];
nRes = (nRes << 8) + m_sFile[nPos + 3];
return nRes;
}
unsigned int CFontFileBase::GetUVarBE(int nPos, int nSize, bool *pbSuccess)
{
*pbSuccess = true;
if (nPos < 0 || nPos + nSize > m_nLen)
{
*pbSuccess = false;
return 0;
}
unsigned int nRes = 0;
for (int nIndex = 0; nIndex < nSize; ++nIndex)
nRes = (nRes << 8) + m_sFile[nPos + nIndex];
return nRes;
}
bool CFontFileBase::CheckRegion(int nPos, int nSize)
{
return (nPos >= 0 && nPos + nSize >= nPos && nPos + nSize <= m_nLen);
}
//----------------------------------------------------------------------------------------
// CFontFileTrueType
//----------------------------------------------------------------------------------------
CFontFileTrueType::CFontFileTrueType(char *sBuffer, int nLen, bool bFreeFileData, unsigned int unFontIndex) :CFontFileBase(sBuffer, nLen, bFreeFileData)
{
m_pTables = NULL;
m_nTablesCount = 0;
m_pCMaps = NULL;
m_nCMapsCount = 0;
m_bSuccess = false;
m_unFontIndex = unFontIndex;
m_nAscent = 1000;
m_nDescent = -500;
m_nCapHeight = 800;
m_nWeight = 400;
Parse();
}
CFontFileTrueType* CFontFileTrueType::LoadFromBuffer(char *sBuffer, int nLen, unsigned int unIndex)
{
CFontFileTrueType *pTTF = new CFontFileTrueType(sBuffer, nLen, false, unIndex);
if (!pTTF->m_bSuccess)
{
delete pTTF;
return NULL;
}
return pTTF;
}
CFontFileTrueType* CFontFileTrueType::LoadFromFile(const std::wstring& wsFileName, unsigned int unIndex)
{
char *sBuffer;
int nLen = 0;
if (!(sBuffer = CFontFileBase::ReadFile(wsFileName, &nLen)))
return NULL;
CFontFileTrueType *pTTF = new CFontFileTrueType(sBuffer, nLen, true, unIndex);
if (!pTTF->m_bSuccess)
{
delete pTTF;
return NULL;
}
return pTTF;
}
CFontFileTrueType::~CFontFileTrueType()
{
if (m_pTables)
free(m_pTables);
if (m_pCMaps)
free(m_pCMaps);
}
void CFontFileTrueType::WriteTTF(CStream* pOutputStream, char* sName, unsigned short* pCodeToGID, unsigned int unCodesCount, unsigned char* pUseGlyfs, long lGlyfsCount)
{
static char arrCMapTab[36] =
{
0, 0, // table version number
0, 1, // number of encoding tables
0, 1, // platform ID
0, 0, // encoding ID
0, 0, 0, 12, // offset of subtable
0, 4, // subtable format
0, 24, // subtable length
0, 0, // subtable version
0, 2, // segment count * 2
0, 2, // 2 * 2 ^ floor(log2(segCount))
0, 0, // floor(log2(segCount))
0, 0, // 2*segCount - 2*2^floor(log2(segCount))
(char)0xff, (char)0xff, // endCount[0]
0, 0, // reserved
0, 0, // startCount[0]
0, 0, // idDelta[0]
0, 0 // pad to a mulitple of four bytes
};
static char arrNameTab[8] =
{
0, 0, // format
0, 0, // number of name records
0, 6, // offset to start of string storage
0, 0 // pad to multiple of four bytes
};
static char arrPostTab[32] =
{
0, 1, 0, 0, // format
0, 0, 0, 0, // italic angle
0, 0, // underline position
0, 0, // underline thickness
0, 0, 0, 0, // fixed pitch
0, 0, 0, 0, // min Type 42 memory
0, 0, 0, 0, // max Type 42 memory
0, 0, 0, 0, // min Type 1 memory
0, 0, 0, 0 // max Type 1 memory
};
static char arrOS2Tab[86] =
{
0, 1, // version
0, 1, // xAvgCharWidth
0, 0, // usWeightClass
0, 0, // usWidthClass
0, 0, // fsType
0, 0, // ySubscriptXSize
0, 0, // ySubscriptYSize
0, 0, // ySubscriptXOffset
0, 0, // ySubscriptYOffset
0, 0, // ySuperscriptXSize
0, 0, // ySuperscriptYSize
0, 0, // ySuperscriptXOffset
0, 0, // ySuperscriptYOffset
0, 0, // yStrikeoutSize
0, 0, // yStrikeoutPosition
0, 0, // sFamilyClass
0, 0, 0, 0, 0, // panose
0, 0, 0, 0, 0,
0, 0, 0, 0, // ulUnicodeRange1
0, 0, 0, 0, // ulUnicodeRange2
0, 0, 0, 0, // ulUnicodeRange3
0, 0, 0, 0, // ulUnicodeRange4
0, 0, 0, 0, // achVendID
0, 0, // fsSelection
0, 0, // usFirstCharIndex
0, 0, // usLastCharIndex
0, 0, // sTypoAscender
0, 0, // sTypoDescender
0, 0, // sTypoLineGap
0, 0, // usWinAscent
0, 0, // usWinDescent
0, 0, 0, 0, // ulCodePageRange1
0, 0, 0, 0 // ulCodePageRange2
};
bool badCmapLen, abbrevHMTX;
int nZeroLengthTables;
int nHMetrics, nAdvWidth, nLeftSideBearing;
TrueTypeTable *pNewTables;
char *arrNewNameTable, *arrNewCmapTable, *arrNewHHEATable, *arrNewHMTXTable;
int nNewTables, nCmapIndex, nCmapLen, nGlyphLen, nNewNameLen, nNewCmapLen, nNext;
int nNewHHEALen, nNewHMTXLen;
unsigned int nLocaChecksum, nGlyphChecksum, nFileChecksum;
char *arrTableDir;
char arrLocaBuf[4], arrChecksumBuf[4];
unsigned int t;
int nPos = 0, i, j, k, n;
// Записываем OpenType шрифт не меняя его
if (m_bOpenTypeCFF)
{
WriteCIDFontType0C(pOutputStream, pCodeToGID, unCodesCount);
return;
}
// Проверяем недостающие таблицы
bool bMissingCmap = (nCmapIndex = SeekTable("cmap")) < 0;
bool bMissingName = SeekTable("name") < 0;
bool bMissingPost = SeekTable("post") < 0;
bool bMissingOS2 = SeekTable("OS/2") < 0;
TrueTypeLoca *pLocaTable = (TrueTypeLoca *)malloc((m_nGlyphs + 1) * sizeof(TrueTypeLoca));
bool bUnsortedLoca = false;
i = SeekTable("loca");
nPos = m_pTables[i].nOffset;
bool bSuccess = true;
for (i = 0; i <= m_nGlyphs; ++i)
{
if (m_nLocaFormat)
{
pLocaTable[i].nOrigOffset = (int)GetU32BE(nPos + i * 4, &bSuccess);
}
else
{
pLocaTable[i].nOrigOffset = 2 * GetU16BE(nPos + i * 2, &bSuccess);
}
if (i > 0 && pLocaTable[i].nOrigOffset < pLocaTable[i - 1].nOrigOffset)
{
bUnsortedLoca = true;
}
// Описание глифа должны быть как минимум 12 байт (nContours,
// xMin, yMin, xMax, yMax, instructionLength - каждый по 2 байта);
if (i > 0 && pLocaTable[i].nOrigOffset - pLocaTable[i - 1].nOrigOffset > 0 && pLocaTable[i].nOrigOffset - pLocaTable[i - 1].nOrigOffset < 12)
{
pLocaTable[i - 1].nOrigOffset = pLocaTable[i].nOrigOffset;
bUnsortedLoca = true;
}
pLocaTable[i].nIndex = i;
}
// Проверяем наличие нулевых таблиц
nZeroLengthTables = 0;
for (i = 0; i < m_nTablesCount; ++i)
{
if (m_pTables[i].nLen == 0)
++nZeroLengthTables;
}
// Проверяем длину таблицы Cmap
badCmapLen = false;
nCmapLen = 0;
if (!bMissingCmap)
{
nCmapLen = m_pCMaps[0].nOffset + m_pCMaps[0].nLen;
for (i = 1; i < m_nCMapsCount; ++i)
{
if (m_pCMaps[i].nOffset + m_pCMaps[i].nLen > nCmapLen)
{
nCmapLen = m_pCMaps[i].nOffset + m_pCMaps[i].nLen;
}
}
nCmapLen -= m_pTables[nCmapIndex].nOffset;
if (nCmapLen > m_pTables[nCmapIndex].nLen)
{
badCmapLen = true;
}
}
// Проверяем, является ли таблица 'hmtx' сокращенной.
i = SeekTable("hhea");
nHMetrics = GetU16BE(m_pTables[i].nOffset + 34, &bSuccess);
abbrevHMTX = nHMetrics < m_nGlyphs;
// Если все впорядке, и нам не надо переписывать таблицы 'cmap' и 'name', тогда пишем файл TTF как он есть
if (!bMissingCmap && !bMissingName && !bMissingPost && !bMissingOS2 && !bUnsortedLoca && !badCmapLen && !abbrevHMTX && nZeroLengthTables == 0 && !sName && !pCodeToGID)
{
pOutputStream->Write((BYTE *)m_sFile, m_nLen);
free(pLocaTable);
return;
}
// Сортируем таблицу 'loca': некоторые шрифты содержат неупорядоченную
// таблицу 'loca'; а некоторые шрифты с нормальной таблицей 'loca'
// содержат пустые элементы в середине таблицы, cmpTrueTypeLocaOffset
// использует сдвиги как основной ключ для сортировки, а номера глифов
// как второй ключ (чтобы элементы в таблице, которые имели одинаковую позицию
// шли в том же порядке, как и в исходном шрифте)
nGlyphLen = 0;
if (bUnsortedLoca || pUseGlyfs)
{
qsort(pLocaTable, m_nGlyphs + 1, sizeof(TrueTypeLoca), &CompareTrueTypeLocaOffset);
for (i = 0; i < m_nGlyphs; ++i)
{
pLocaTable[i].nLen = pLocaTable[i + 1].nOrigOffset - pLocaTable[i].nOrigOffset;
}
pLocaTable[m_nGlyphs].nLen = 0;
qsort(pLocaTable, m_nGlyphs + 1, sizeof(TrueTypeLoca), &CompareTrueTypeLocaIndex);
nPos = 0;
for (i = 0; i <= m_nGlyphs; ++i)
{
// TO DO: Протестировать тут запись только тех глифов, которые нам нужны
if (pUseGlyfs && lGlyfsCount == m_nGlyphs)
{
pLocaTable[i].nNewOffset = nPos;
int nCurGlyfLen = pLocaTable[i].nLen;
pLocaTable[i].nLen = 0;
if (1 == pUseGlyfs[i])
{
pLocaTable[i].nLen = nCurGlyfLen;
nPos += pLocaTable[i].nLen;
if (nPos & 3)
{
nPos += 4 - (nPos & 3);
}
}
}
else
{
pLocaTable[i].nNewOffset = nPos;
nPos += pLocaTable[i].nLen;
if (nPos & 3)
{
nPos += 4 - (nPos & 3);
}
}
}
nGlyphLen = nPos;
}
// Вычисляем чексуммы таблиц 'loca' и 'glyf'
nLocaChecksum = nGlyphChecksum = 0;
if (bUnsortedLoca || pUseGlyfs)
{
if (m_nLocaFormat)
{
for (j = 0; j <= m_nGlyphs; ++j)
{
nLocaChecksum += pLocaTable[j].nNewOffset;
}
}
else
{
for (j = 0; j <= m_nGlyphs; j += 2)
{
nLocaChecksum += pLocaTable[j].nNewOffset << 16;
if (j + 1 <= m_nGlyphs)
{
nLocaChecksum += pLocaTable[j + 1].nNewOffset;
}
}
}
nPos = m_pTables[SeekTable("glyf")].nOffset;
for (j = 0; j < m_nGlyphs; ++j)
{
n = pLocaTable[j].nLen;
if (n > 0)
{
k = pLocaTable[j].nOrigOffset;
if (CheckRegion(nPos + k, n))
{
nGlyphChecksum += ComputeTableChecksum(m_sFile + nPos + k, n);
}
}
}
}
// Строим новую таблицу 'name'
if (sName)
{
n = strlen(sName);
nNewNameLen = (6 + 4 * 12 + 2 * (3 * n + 7) + 3) & ~3;
arrNewNameTable = (char *)malloc(nNewNameLen);
memset(arrNewNameTable, 0, nNewNameLen);
arrNewNameTable[0] = 0; // format selector
arrNewNameTable[1] = 0;
arrNewNameTable[2] = 0; // number of name records
arrNewNameTable[3] = 4;
arrNewNameTable[4] = 0; // offset to start of string storage
arrNewNameTable[5] = 6 + 4 * 12;
nNext = 0;
for (i = 0; i < 4; ++i)
{
arrNewNameTable[6 + i * 12 + 0] = 0; // platform ID = Microsoft
arrNewNameTable[6 + i * 12 + 1] = 3;
arrNewNameTable[6 + i * 12 + 2] = 0; // encoding ID = Unicode
arrNewNameTable[6 + i * 12 + 3] = 1;
arrNewNameTable[6 + i * 12 + 4] = 0x04; // language ID = American English
arrNewNameTable[6 + i * 12 + 5] = 0x09;
arrNewNameTable[6 + i * 12 + 6] = 0; // name ID
arrNewNameTable[6 + i * 12 + 7] = i + 1;
arrNewNameTable[6 + i * 12 + 8] = i + 1 == 2 ? 0 : ((2 * n) >> 8); // string length
arrNewNameTable[6 + i * 12 + 9] = i + 1 == 2 ? 14 : ((2 * n) & 0xff);
arrNewNameTable[6 + i * 12 + 10] = nNext >> 8; // string offset
arrNewNameTable[6 + i * 12 + 11] = nNext & 0xff;
if (i + 1 == 2)
{
memcpy(arrNewNameTable + 6 + 4 * 12 + nNext, "\0R\0e\0g\0u\0l\0a\0r", 14);
nNext += 14;
}
else
{
for (j = 0; j < n; ++j)
{
arrNewNameTable[6 + 4 * 12 + nNext + 2 * j] = 0;
arrNewNameTable[6 + 4 * 12 + nNext + 2 * j + 1] = sName[j];
}
nNext += 2 * n;
}
}
}
else
{
nNewNameLen = 0;
arrNewNameTable = NULL;
}
// Строим новую таблицу 'cmap'
if (pCodeToGID)
{
unsigned short ushSubTableLen = 10 + unCodesCount * 2;
nNewCmapLen = 12 + ushSubTableLen;
arrNewCmapTable = (char *)malloc(nNewCmapLen);
arrNewCmapTable[0] = 0; // table version number = 0
arrNewCmapTable[1] = 0; //
arrNewCmapTable[2] = 0; // number of encoding tables = 1
arrNewCmapTable[3] = 1; //
arrNewCmapTable[4] = 0; // platform ID = 1 (MacOS) // Эти два поля обязательно должны
arrNewCmapTable[5] = 1; // // иметь таки значения, иначе, Adobe
arrNewCmapTable[6] = 0; // encoding ID = 0 // Acrobat может открыть данный шрифт.
arrNewCmapTable[7] = 0; //
arrNewCmapTable[8] = 0; // offset of subtable
arrNewCmapTable[9] = 0; //
arrNewCmapTable[10] = 0; //
arrNewCmapTable[11] = 12; //
arrNewCmapTable[12] = 0; // subtable format = 6
arrNewCmapTable[13] = 6; //
arrNewCmapTable[14] = (ushSubTableLen >> 8) & 0xFF; // subtable length
arrNewCmapTable[15] = ushSubTableLen & 0xFF; //
arrNewCmapTable[16] = 0; // subtable version = 0
arrNewCmapTable[17] = 0; //
arrNewCmapTable[18] = 0; // firstCode
arrNewCmapTable[19] = 0; //
arrNewCmapTable[20] = 0x01; // entryCount
arrNewCmapTable[21] = 0x00; //
for (i = 0; i < unCodesCount; ++i)
{
arrNewCmapTable[22 + 2 * i] = pCodeToGID[i] >> 8;
arrNewCmapTable[22 + 2 * i + 1] = pCodeToGID[i] & 0xff;
}
}
else
{
nNewCmapLen = 0;
arrNewCmapTable = NULL;
}
// Генерируем новую таблицу 'hmtx' и обновляем таблицу 'hhea'
if (abbrevHMTX)
{
i = SeekTable("hhea");
nPos = m_pTables[i].nOffset;
nNewHHEALen = 36;
arrNewHHEATable = (char *)malloc(nNewHHEALen);
for (i = 0; i < nNewHHEALen; ++i)
{
arrNewHHEATable[i] = GetU8(nPos++, &bSuccess);
}
arrNewHHEATable[34] = m_nGlyphs >> 8;
arrNewHHEATable[35] = m_nGlyphs & 0xff;
i = SeekTable("hmtx");
nPos = m_pTables[i].nOffset;
nNewHMTXLen = 4 * m_nGlyphs;
arrNewHMTXTable = (char *)malloc(nNewHMTXLen);
nAdvWidth = 0;
for (i = 0; i < nHMetrics; ++i)
{
nAdvWidth = GetU16BE(nPos, &bSuccess);
nLeftSideBearing = GetU16BE(nPos + 2, &bSuccess);
nPos += 4;
arrNewHMTXTable[4 * i] = nAdvWidth >> 8;
arrNewHMTXTable[4 * i + 1] = nAdvWidth & 0xff;
arrNewHMTXTable[4 * i + 2] = nLeftSideBearing >> 8;
arrNewHMTXTable[4 * i + 3] = nLeftSideBearing & 0xff;
}
for (; i < m_nGlyphs; ++i)
{
nLeftSideBearing = GetU16BE(nPos, &bSuccess);
nPos += 2;
arrNewHMTXTable[4 * i] = nAdvWidth >> 8;
arrNewHMTXTable[4 * i + 1] = nAdvWidth & 0xff;
arrNewHMTXTable[4 * i + 2] = nLeftSideBearing >> 8;
arrNewHMTXTable[4 * i + 3] = nLeftSideBearing & 0xff;
}
}
else
{
arrNewHHEATable = arrNewHMTXTable = NULL;
nNewHHEALen = nNewHMTXLen = 0;
}
// Создаем список таблиц:
// - сохраняем исходные ненулевые таблицы
// - переписываем длину таблицы 'cmap', если необходимо
// - добавляем недостающие таблицы
// - сортируем таблицы по тэгам
// - вычисляем новые позиции таблиц, с учетом 4-байтового выравнивания
// - пересчитываем чексуммы таблиц
nNewTables = m_nTablesCount - nZeroLengthTables + (bMissingCmap ? 1 : 0) + (bMissingName ? 1 : 0) + (bMissingPost ? 1 : 0) + (bMissingOS2 ? 1 : 0);
pNewTables = (TrueTypeTable *)malloc(nNewTables * sizeof(TrueTypeTable));
j = 0;
for (i = 0; i < m_nTablesCount; ++i)
{
if (m_pTables[i].nLen > 0)
{
pNewTables[j] = m_pTables[i];
pNewTables[j].nOrigOffset = m_pTables[i].nOffset;
if (CheckRegion(m_pTables[i].nOffset, pNewTables[i].nLen))
{
pNewTables[j].unChecksum = ComputeTableChecksum(m_sFile + m_pTables[i].nOffset, m_pTables[i].nLen);
if (m_pTables[i].unTag == headTag)
{
// don't include the file checksum
pNewTables[j].unChecksum -= GetU32BE(m_pTables[i].nOffset + 8, &bSuccess);
}
}
if (pNewTables[j].unTag == cmapTag && pCodeToGID)
{
pNewTables[j].nLen = nNewCmapLen;
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrNewCmapTable, nNewCmapLen);
}
else if (pNewTables[j].unTag == cmapTag && badCmapLen)
{
pNewTables[j].nLen = nCmapLen;
}
else if (pNewTables[j].unTag == locaTag && (bUnsortedLoca || pCodeToGID))
{
pNewTables[j].nLen = (m_nGlyphs + 1) * (m_nLocaFormat ? 4 : 2);
pNewTables[j].unChecksum = nLocaChecksum;
}
else if (pNewTables[j].unTag == glyfTag && (bUnsortedLoca || pCodeToGID))
{
pNewTables[j].nLen = nGlyphLen;
pNewTables[j].unChecksum = nGlyphChecksum;
}
else if (pNewTables[j].unTag == nameTag && sName)
{
pNewTables[j].nLen = nNewNameLen;
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrNewNameTable, nNewNameLen);
}
else if (pNewTables[j].unTag == hheaTag && abbrevHMTX)
{
pNewTables[j].nLen = nNewHHEALen;
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrNewHHEATable, nNewHHEALen);
}
else if (pNewTables[j].unTag == hmtxTag && abbrevHMTX)
{
pNewTables[j].nLen = nNewHMTXLen;
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrNewHMTXTable, nNewHMTXLen);
}
++j;
}
}
if (bMissingCmap)
{
pNewTables[j].unTag = cmapTag;
if (pCodeToGID)
{
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrNewCmapTable, nNewCmapLen);
pNewTables[j].nLen = nNewCmapLen;
}
else
{
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrCMapTab, sizeof(arrCMapTab));
pNewTables[j].nLen = sizeof(arrCMapTab);
}
++j;
}
if (bMissingName)
{
pNewTables[j].unTag = nameTag;
if (sName)
{
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrNewNameTable, nNewNameLen);
pNewTables[j].nLen = nNewNameLen;
}
else
{
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrNameTab, sizeof(arrNameTab));
pNewTables[j].nLen = sizeof(arrNameTab);
}
++j;
}
if (bMissingPost)
{
pNewTables[j].unTag = postTag;
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrPostTab, sizeof(arrPostTab));
pNewTables[j].nLen = sizeof(arrPostTab);
++j;
}
if (bMissingOS2)
{
pNewTables[j].unTag = os2Tag;
pNewTables[j].unChecksum = ComputeTableChecksum((unsigned char *)arrOS2Tab, sizeof(arrOS2Tab));
pNewTables[j].nLen = sizeof(arrOS2Tab);
++j;
}
qsort(pNewTables, nNewTables, sizeof(TrueTypeTable), CompareTrueTypeTableTag);
unsigned char *pUseTable = new unsigned char[nNewTables];
if (!pUseTable)
{
free(arrNewHMTXTable);
free(arrNewHHEATable);
free(arrNewCmapTable);
free(arrNewNameTable);
free(pNewTables);
free(pLocaTable);
return;
}
::memset(pUseTable, 0, nNewTables * sizeof(unsigned char));
int nNewReqTables = 0;
for (int nIndex = 0; nIndex < nNewTables; nIndex++)
{
if (pNewTables[nIndex].unTag == cmapTag || pNewTables[nIndex].unTag == glyfTag ||
pNewTables[nIndex].unTag == headTag || pNewTables[nIndex].unTag == hheaTag ||
pNewTables[nIndex].unTag == hmtxTag || pNewTables[nIndex].unTag == locaTag ||
pNewTables[nIndex].unTag == nameTag || pNewTables[nIndex].unTag == os2Tag ||
pNewTables[nIndex].unTag == postTag || pNewTables[nIndex].unTag == cvtTag ||
pNewTables[nIndex].unTag == fpgmTag || pNewTables[nIndex].unTag == maxpTag ||
pNewTables[nIndex].unTag == prepTag)
{
pUseTable[nIndex] = 1;
nNewReqTables++;
}
}
nPos = 12 + nNewReqTables * 16;
for (i = 0; i < nNewTables; ++i)
{
if (1 == pUseTable[i])
{
pNewTables[i].nOffset = nPos;
nPos += pNewTables[i].nLen;
if (nPos & 3)
{
nPos += 4 - (nPos & 3);
}
}
}
// Записываем информацию о таблицах в файле
arrTableDir = (char *)malloc(12 + nNewReqTables * 16);
arrTableDir[0] = 0x00; // sfnt version
arrTableDir[1] = 0x01; //
arrTableDir[2] = 0x00; //
arrTableDir[3] = 0x00; //
arrTableDir[4] = (char)((nNewReqTables >> 8) & 0xff); // numTables
arrTableDir[5] = (char)(nNewReqTables & 0xff);
for (i = -1, t = (unsigned int)nNewReqTables; t; ++i, t >>= 1);
t = 1 << (4 + i);
arrTableDir[6] = (char)((t >> 8) & 0xff); // searchRange
arrTableDir[7] = (char)(t & 0xff);
arrTableDir[8] = (char)((i >> 8) & 0xff); // entrySelector
arrTableDir[9] = (char)(i & 0xff);
t = nNewReqTables * 16 - t;
arrTableDir[10] = (char)((t >> 8) & 0xff); // rangeShift
arrTableDir[11] = (char)(t & 0xff);
nPos = 12;
for (i = 0; i < nNewTables; ++i)
{
if (1 == pUseTable[i])
{
arrTableDir[nPos] = (char)(pNewTables[i].unTag >> 24);
arrTableDir[nPos + 1] = (char)(pNewTables[i].unTag >> 16);
arrTableDir[nPos + 2] = (char)(pNewTables[i].unTag >> 8);
arrTableDir[nPos + 3] = (char)pNewTables[i].unTag;
arrTableDir[nPos + 4] = (char)(pNewTables[i].unChecksum >> 24);
arrTableDir[nPos + 5] = (char)(pNewTables[i].unChecksum >> 16);
arrTableDir[nPos + 6] = (char)(pNewTables[i].unChecksum >> 8);
arrTableDir[nPos + 7] = (char)pNewTables[i].unChecksum;
arrTableDir[nPos + 8] = (char)(pNewTables[i].nOffset >> 24);
arrTableDir[nPos + 9] = (char)(pNewTables[i].nOffset >> 16);
arrTableDir[nPos + 10] = (char)(pNewTables[i].nOffset >> 8);
arrTableDir[nPos + 11] = (char)pNewTables[i].nOffset;
arrTableDir[nPos + 12] = (char)(pNewTables[i].nLen >> 24);
arrTableDir[nPos + 13] = (char)(pNewTables[i].nLen >> 16);
arrTableDir[nPos + 14] = (char)(pNewTables[i].nLen >> 8);
arrTableDir[nPos + 15] = (char)pNewTables[i].nLen;
nPos += 16;
}
}
pOutputStream->Write((BYTE*)arrTableDir, 12 + nNewReqTables * 16);
// Вычисляем чексумму файла
nFileChecksum = ComputeTableChecksum((unsigned char *)arrTableDir, 12 + nNewReqTables * 16);
for (i = 0; i < nNewTables; ++i)
{
if (1 == pUseTable[i])
{
nFileChecksum += pNewTables[i].unChecksum;
}
}
nFileChecksum = 0xb1b0afba - nFileChecksum;
// Записываем сами таблицы
for (i = 0; i < nNewTables; ++i)
{
if (1 == pUseTable[i])
{
if (pNewTables[i].unTag == headTag)
{
if (CheckRegion(pNewTables[i].nOrigOffset, pNewTables[i].nLen))
{
pOutputStream->Write((BYTE*)m_sFile + pNewTables[i].nOrigOffset, 8);
arrChecksumBuf[0] = nFileChecksum >> 24;
arrChecksumBuf[1] = nFileChecksum >> 16;
arrChecksumBuf[2] = nFileChecksum >> 8;
arrChecksumBuf[3] = nFileChecksum;
pOutputStream->Write((BYTE*)arrChecksumBuf, 4);
pOutputStream->Write((BYTE*)((char *)m_sFile + pNewTables[i].nOrigOffset + 12), pNewTables[i].nLen - 12);
}
else
{
for (j = 0; j < pNewTables[i].nLen; ++j)
{
pOutputStream->Write((BYTE*)"\0", 1);
}
}
}
else if (pNewTables[i].unTag == cmapTag && pCodeToGID)
pOutputStream->Write((BYTE*)arrNewCmapTable, pNewTables[i].nLen);
else if (pNewTables[i].unTag == cmapTag && bMissingCmap)
pOutputStream->Write((BYTE*)arrCMapTab, pNewTables[i].nLen);
else if (pNewTables[i].unTag == nameTag && sName)
pOutputStream->Write((BYTE*)arrNewNameTable, pNewTables[i].nLen);
else if (pNewTables[i].unTag == nameTag && bMissingName)
pOutputStream->Write((BYTE*)arrNameTab, pNewTables[i].nLen);
else if (pNewTables[i].unTag == postTag && bMissingPost)
pOutputStream->Write((BYTE*)arrPostTab, pNewTables[i].nLen);
else if (pNewTables[i].unTag == os2Tag && bMissingOS2)
pOutputStream->Write((BYTE*)arrOS2Tab, pNewTables[i].nLen);
else if (pNewTables[i].unTag == hheaTag && abbrevHMTX)
pOutputStream->Write((BYTE*)arrNewHHEATable, pNewTables[i].nLen);
else if (pNewTables[i].unTag == hmtxTag && abbrevHMTX)
pOutputStream->Write((BYTE*)arrNewHMTXTable, pNewTables[i].nLen);
else if (pNewTables[i].unTag == locaTag && (bUnsortedLoca || pCodeToGID))
{
for (j = 0; j <= m_nGlyphs; ++j)
{
if (m_nLocaFormat)
{
arrLocaBuf[0] = (char)(pLocaTable[j].nNewOffset >> 24);
arrLocaBuf[1] = (char)(pLocaTable[j].nNewOffset >> 16);
arrLocaBuf[2] = (char)(pLocaTable[j].nNewOffset >> 8);
arrLocaBuf[3] = (char)pLocaTable[j].nNewOffset;
pOutputStream->Write((BYTE*)arrLocaBuf, 4);
}
else
{
arrLocaBuf[0] = (char)(pLocaTable[j].nNewOffset >> 9);
arrLocaBuf[1] = (char)(pLocaTable[j].nNewOffset >> 1);
pOutputStream->Write((BYTE*)arrLocaBuf, 2);
}
}
}
else if (pNewTables[i].unTag == glyfTag && (bUnsortedLoca || pCodeToGID))
{
nPos = m_pTables[SeekTable("glyf")].nOffset;
for (j = 0; j < m_nGlyphs; ++j)
{
n = pLocaTable[j].nLen;
if (n > 0)
{
k = pLocaTable[j].nOrigOffset;
if (CheckRegion(nPos + k, n))
{
pOutputStream->Write((BYTE*)((char *)m_sFile + nPos + k), n);
}
else
{
for (k = 0; k < n; ++k)
{
pOutputStream->Write((BYTE*)"\0", 1);
}
}
if ((k = pLocaTable[j].nLen & 3))
{
pOutputStream->Write((BYTE*)"\0\0\0\0", 4 - k);
}
}
}
}
else
{
if (CheckRegion(pNewTables[i].nOrigOffset, pNewTables[i].nLen))
{
pOutputStream->Write((BYTE*)((char *)m_sFile + pNewTables[i].nOrigOffset), pNewTables[i].nLen);
}
else
{
for (j = 0; j < pNewTables[i].nLen; ++j)
{
pOutputStream->Write((BYTE*)"\0", 1);
}
}
}
if (pNewTables[i].nLen & 3)
{
pOutputStream->Write((BYTE*)"\0\0\0", 4 - (pNewTables[i].nLen & 3));
}
}
}
delete[]pUseTable;
free(arrNewHMTXTable);
free(arrNewHHEATable);
free(arrNewCmapTable);
free(arrNewNameTable);
free(arrTableDir);
free(pNewTables);
free(pLocaTable);
}
void CFontFileTrueType::WriteOTF(CStream* pOutputStream, char* sName, unsigned short* pCodeToGID)
{
if (!m_bOpenTypeCFF || SeekTable("CFF ") < 0)
return;
// Open Type Font записываем так как он есть, не изменяя его
pOutputStream->Write((BYTE*)m_sFile, m_nLen);
return;
}
int CFontFileTrueType::GetAscent()
{
return m_nAscent;
}
int CFontFileTrueType::GetDescent()
{
return m_nDescent;
}
int CFontFileTrueType::GetCapHeight()
{
return m_nCapHeight;
}
int* CFontFileTrueType::GetBBox()
{
return m_arrBBox;
}
int CFontFileTrueType::GetWeight()
{
return m_nWeight;
}
bool CFontFileTrueType::GetOpenTypeCFF() { return m_bOpenTypeCFF; }
void CFontFileTrueType::SetName(const std::string& sName) { m_sName = sName; }
unsigned int CFontFileTrueType::ComputeTableChecksum(unsigned char *sData, int nLength)
{
unsigned int nWord = 0;
unsigned int nChecksum = 0;
for (int nIndex = 0; nIndex + 3 < nLength; nIndex += 4)
{
nWord = ((sData[nIndex] & 0xff) << 24) + ((sData[nIndex + 1] & 0xff) << 16) + ((sData[nIndex + 2] & 0xff) << 8) + (sData[nIndex + 3] & 0xff);
nChecksum += nWord;
}
if (nLength & 3)
{
nWord = 0;
int nTemp = nLength & ~3;
switch (nLength & 3)
{
case 3:
nWord |= (sData[nTemp + 2] & 0xff) << 8;
case 2:
nWord |= (sData[nTemp + 1] & 0xff) << 16;
case 1:
nWord |= (sData[nTemp] & 0xff) << 24;
break;
}
nChecksum += nWord;
}
return nChecksum;
}
void CFontFileTrueType::Parse()
{
int nPos = 0, nIndex = 0, nJ;
m_bSuccess = true;
// Проверяем является ли данный файл (TTC)
unsigned int unTopTag = GetU32BE(0, &m_bSuccess);
if (!m_bSuccess)
return;
if (unTopTag == ttcfTag)
{
unsigned int unVersion = GetU32BE(4, &m_bSuccess);
unsigned int unNumFonts = GetU32BE(8, &m_bSuccess);
unsigned int unFontIndex = m_unFontIndex >= unNumFonts ? 0 : m_unFontIndex;
nPos = GetU32BE(12 + 4 * unFontIndex, &m_bSuccess);
if (!m_bSuccess)
return;
}
else
nPos = 0;
// Проверяем sfnt версию
int nSfntVersion = GetU32BE(nPos, &m_bSuccess);
if (!m_bSuccess)
return;
// Проверяем на формат данных. CCF или нет?
m_bOpenTypeCFF = (nSfntVersion == 0x4f54544f); // 'OTTO'
m_nTablesCount = GetU16BE(nPos + 4, &m_bSuccess);
if (!m_bSuccess)
return;
m_pTables = (TrueTypeTable *)malloc(m_nTablesCount * sizeof(TrueTypeTable));
nPos += 12;
for (nIndex = 0; nIndex < m_nTablesCount; ++nIndex)
{
m_pTables[nIndex].unTag = GetU32BE(nPos, &m_bSuccess);
m_pTables[nIndex].unChecksum = GetU32BE(nPos + 4, &m_bSuccess);
m_pTables[nIndex].nOffset = (int)GetU32BE(nPos + 8, &m_bSuccess);
m_pTables[nIndex].nLen = (int)GetU32BE(nPos + 12, &m_bSuccess);
if (m_pTables[nIndex].nOffset + m_pTables[nIndex].nLen < m_pTables[nIndex].nOffset || m_pTables[nIndex].nOffset + m_pTables[nIndex].nLen > m_nLen)
m_bSuccess = false;
nPos += 16;
}
if (!m_bSuccess)
return;
// ищем таблицы необходимые как и для TrueType так и для Type 42
if (SeekTable("head") < 0 || SeekTable("hhea") < 0 || SeekTable("maxp") < 0 || SeekTable("hmtx") < 0 || (!m_bOpenTypeCFF && SeekTable("loca") < 0) || (!m_bOpenTypeCFF && SeekTable("glyf") < 0) || (m_bOpenTypeCFF && SeekTable("CFF ") < 0))
{
m_bSuccess = false;
return;
}
// читаем таблицы CMaps
if ((nIndex = SeekTable("cmap")) >= 0)
{
nPos = m_pTables[nIndex].nOffset + 2;
m_nCMapsCount = GetU16BE(nPos, &m_bSuccess);
nPos += 2;
if (!m_bSuccess)
return;
m_pCMaps = (TrueTypeCmap *)malloc(m_nCMapsCount * sizeof(TrueTypeCmap));
for (nJ = 0; nJ < m_nCMapsCount; ++nJ)
{
m_pCMaps[nJ].nPlatform = GetU16BE(nPos, &m_bSuccess);
m_pCMaps[nJ].nEncoding = GetU16BE(nPos + 2, &m_bSuccess);
unsigned int nTemp = GetU32BE(nPos + 4, &m_bSuccess);
m_pCMaps[nJ].nOffset = m_pTables[nIndex].nOffset + GetU32BE(nPos + 4, &m_bSuccess);
nPos += 8;
m_pCMaps[nJ].nFormat = GetU16BE(m_pCMaps[nJ].nOffset, &m_bSuccess);
m_pCMaps[nJ].nLen = GetU16BE(m_pCMaps[nJ].nOffset + 2, &m_bSuccess);
}
if (!m_bSuccess)
return;
}
else
m_nCMapsCount = 0;
nIndex = SeekTable("maxp");
m_nGlyphs = GetU16BE(m_pTables[nIndex].nOffset + 4, &m_bSuccess);
if (!m_bSuccess)
return;
nIndex = SeekTable("head");
m_arrBBox[0] = GetS16BE(m_pTables[nIndex].nOffset + 36, &m_bSuccess);
m_arrBBox[1] = GetS16BE(m_pTables[nIndex].nOffset + 38, &m_bSuccess);
m_arrBBox[2] = GetS16BE(m_pTables[nIndex].nOffset + 40, &m_bSuccess);
m_arrBBox[3] = GetS16BE(m_pTables[nIndex].nOffset + 42, &m_bSuccess);
m_nLocaFormat = GetS16BE(m_pTables[nIndex].nOffset + 50, &m_bSuccess);
if (!m_bSuccess)
return;
// Проверяем корректность таблицы loca
if (!m_bOpenTypeCFF)
{
nIndex = SeekTable("loca");
if (m_pTables[nIndex].nLen < 0)
{
m_bSuccess = false;
return;
}
if (m_pTables[nIndex].nLen < (m_nGlyphs + 1) * (m_nLocaFormat ? 4 : 2))
{
m_nGlyphs = m_pTables[nIndex].nLen / (m_nLocaFormat ? 4 : 2) - 1;
}
for (nJ = 0; nJ <= m_nGlyphs; ++nJ)
{
if (m_nLocaFormat)
nPos = (int)GetU32BE(m_pTables[nIndex].nOffset + nJ * 4, &m_bSuccess);
else
nPos = GetU16BE(m_pTables[nIndex].nOffset + nJ * 2, &m_bSuccess);
if (nPos < 0 || nPos > m_nLen)
m_bSuccess = false;
}
if (!m_bSuccess)
return;
}
ReadOS2();
}
int CFontFileTrueType::SeekTable(const char *sTag)
{
unsigned int nTagIndex = ((sTag[0] & 0xff) << 24) | ((sTag[1] & 0xff) << 16) | ((sTag[2] & 0xff) << 8) | (sTag[3] & 0xff);
for (int nIndex = 0; nIndex < m_nTablesCount; ++nIndex)
{
if (m_pTables[nIndex].unTag == nTagIndex)
{
return nIndex;
}
}
return -1;
}
void CFontFileTrueType::ReadOS2()
{
int nIndex = SeekTable("OS/2");
if (-1 != nIndex && m_pTables[nIndex].nLen > 0)
{
unsigned int unOffset = m_pTables[nIndex].nOffset;
int nTableVersion = GetS16BE(unOffset, &m_bSuccess);
m_nWeight = GetS16BE(unOffset + 4, &m_bSuccess);
m_nAscent = GetS16BE(unOffset + 68, &m_bSuccess);
m_nDescent = GetS16BE(unOffset + 70, &m_bSuccess);
//https://learn.microsoft.com/en-us/typography/opentype/spec/os2#scapheight
if (nTableVersion >= 2)
m_nCapHeight = GetS16BE(unOffset + 88, &m_bSuccess);
}
}
}