Files
DocumentServer-v-9.2.0/core/PdfFile/SrcWriter/FontTTWriter.cpp
Yajbir Singh f1b860b25c
Some checks failed
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++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* (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);
}
}
}