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