1 // 2 // sfnt.cpp 3 // Scribus 4 // 5 // Created by Andreas Vox on 18.04.15. 6 // 7 // 8 9 #include "fonts/sfnt.h" 10 #include "fonts/sfnt_format.h" 11 12 #include FT_TRUETYPE_TABLES_H 13 #include FT_TRUETYPE_TAGS_H 14 #include FT_TRUETYPE_IDS_H 15 16 #include <QDebug> 17 #include <QScopedPointer> 18 19 #include "scconfig.h" 20 21 #include <harfbuzz/hb.h> 22 #ifdef HAVE_HARFBUZZ_SUBSET 23 #include <harfbuzz/hb-subset.h> 24 #endif 25 26 struct HbBlobDeleter 27 { cleanupHbBlobDeleter28 static void cleanup(void *pointer) 29 { 30 if (pointer) 31 hb_blob_destroy((hb_blob_t*) pointer); 32 } 33 }; 34 35 struct HbFaceDeleter 36 { cleanupHbFaceDeleter37 static void cleanup(void *pointer) 38 { 39 if (pointer) 40 hb_face_destroy((hb_face_t*) pointer); 41 } 42 }; 43 44 #ifdef HAVE_HARFBUZZ_SUBSET 45 struct HbSubsetInputDeleter 46 { cleanupHbSubsetInputDeleter47 static void cleanup(void *pointer) 48 { 49 if (pointer) 50 hb_subset_input_destroy((hb_subset_input_t*) pointer); 51 } 52 }; 53 #endif 54 55 namespace sfnt { 56 byte(const QByteArray & bb,uint pos)57 uchar byte(const QByteArray & bb, uint pos) 58 { 59 const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos; 60 return pp[0]; 61 } 62 word(const QByteArray & bb,uint pos)63 quint32 word(const QByteArray & bb, uint pos) 64 { 65 const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos; 66 return pp[0] << 24 | pp[1] << 16 | pp[2] << 8 | pp[3]; 67 } 68 putWord(QByteArray & bb,uint pos,quint32 val)69 void putWord(QByteArray & bb, uint pos, quint32 val) 70 { 71 unsigned char * pp = reinterpret_cast<unsigned char*>(bb.data()) + pos; 72 *pp++ = (val >> 24) & 0xFF; 73 *pp++ = (val >> 16) & 0xFF; 74 *pp++ = (val >> 8) & 0xFF; 75 *pp++ = (val) & 0xFF; 76 } 77 appendWord(QByteArray & bb,quint32 val)78 void appendWord(QByteArray & bb, quint32 val) 79 { 80 uint pos = bb.size(); 81 bb.resize(pos + 4); 82 putWord(bb, pos, val); 83 } 84 word16(const QByteArray & bb,uint pos)85 quint16 word16(const QByteArray & bb, uint pos) 86 { 87 const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos; 88 return pp[0] << 8 | pp[1]; 89 } 90 putWord16(QByteArray & bb,uint pos,quint16 val)91 void putWord16(QByteArray & bb, uint pos, quint16 val) 92 { 93 unsigned char * pp = reinterpret_cast<unsigned char*>(bb.data()) + pos; 94 *pp++ = (val >> 8) & 0xFF; 95 *pp++ = (val) & 0xFF; 96 } 97 appendWord16(QByteArray & bb,quint16 val)98 void appendWord16(QByteArray & bb, quint16 val) 99 { 100 uint pos = bb.size(); 101 bb.resize(pos + 2); 102 putWord16(bb, pos, val); 103 } 104 tag(const QByteArray & bb,uint pos)105 QByteArray tag(const QByteArray& bb, uint pos) 106 { 107 return QByteArray::fromRawData(bb.constData() + pos, 4); 108 } 109 tag(uint word)110 QByteArray tag(uint word) 111 { 112 QByteArray result; 113 result.resize(4); 114 result[0] = (word >> 24) & 0xFF; 115 result[1] = (word >> 16) & 0xFF; 116 result[2] = (word >> 8) & 0xFF; 117 result[3] = (word) & 0xFF; 118 return result; 119 } 120 copy(QByteArray & dst,uint to,const QByteArray & src,uint from,uint len)121 bool copy(QByteArray & dst, uint to, const QByteArray & src, uint from, uint len) 122 { 123 if (!dst.data()) 124 return false; 125 if (!src.data()) 126 return false; 127 if (to + len > static_cast<uint>(dst.size())) 128 return false; 129 if (from + len > static_cast<uint>(src.size())) 130 return false; 131 132 memcpy(dst.data() + to, src.data() + from, len); 133 return true; 134 } 135 136 const uint post_format10_names_count = 258; 137 138 static const char* post_format10_names[] = { 139 ".notdef", 140 ".null", 141 "nonmarkingreturn", 142 "space", 143 "exclam", 144 "quotedbl", 145 "numbersign", 146 "dollar", 147 "percent", 148 "ampersand", 149 "quotesingle", 150 "parenleft", 151 "parenright", 152 "asterisk", 153 "plus", 154 "comma", 155 "hyphen", 156 "period", 157 "slash", 158 "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", 159 "colon", 160 "semicolon", 161 "less", 162 "equal", 163 "greater", 164 "question", 165 "at", 166 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 167 "bracketleft", 168 "backslash", 169 "bracketright", 170 "asciicircum", 171 "underscore", 172 "grave", 173 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 174 "braceleft", 175 "bar", 176 "braceright", 177 "asciitilde", 178 "Adieresis", 179 "Aring", 180 "Ccedilla", 181 "Eacute", 182 "Ntilde", 183 "Odieresis", 184 "Udieresis", 185 "aacute", 186 "agrave", 187 "acircumflex", 188 "adieresis", 189 "atilde", 190 "aring", 191 "ccedilla", 192 "eacute", 193 "egrave", 194 "ecircumflex", 195 "edieresis", 196 "iacute", 197 "igrave", 198 "icircumflex", 199 "idieresis", 200 "ntilde", 201 "oacute", 202 "ograve", 203 "ocircumflex", 204 "odieresis", 205 "otilde", 206 "uacute", 207 "ugrave", 208 "ucircumflex", 209 "udieresis", 210 "dagger", 211 "degree", 212 "cent", 213 "sterling", 214 "section", 215 "bullet", 216 "paragraph", 217 "germandbls", 218 "registered", 219 "copyright", 220 "trademark", 221 "acute", 222 "dieresis", 223 "notequal", 224 "AE", 225 "Oslash", 226 "infinity", 227 "plusminus", 228 "lessequal", 229 "greaterequal", 230 "yen", 231 "mu", 232 "partialdiff", 233 "summation", 234 "product", 235 "pi", 236 "integral", 237 "ordfeminine", 238 "ordmasculine", 239 "Omega", 240 "ae", 241 "oslash", 242 "questiondown", 243 "exclamdown", 244 "logicalnot", 245 "radical", 246 "florin", 247 "approxequal", 248 "Delta", 249 "guillemotleft", 250 "guillemotright", 251 "ellipsis", 252 "nonbreakingspace", 253 "Agrave", 254 "Atilde", 255 "Otilde", 256 "OE", 257 "oe", 258 "endash", 259 "emdash", 260 "quotedblleft", 261 "quotedblright", 262 "quoteleft", 263 "quoteright", 264 "divide", 265 "lozenge", 266 "ydieresis", 267 "Ydieresis", 268 "fraction", 269 "currency", 270 "guilsinglleft", 271 "guilsinglright", 272 "fi", 273 "fl", 274 "daggerdbl", 275 "periodcentered", 276 "quotesinglbase", 277 "quotedblbase", 278 "perthousand", 279 "Acircumflex", 280 "Ecircumflex", 281 "Aacute", 282 "Edieresis", 283 "Egrave", 284 "Iacute", 285 "Icircumflex", 286 "Idieresis", 287 "Igrave", 288 "Oacute", 289 "Ocircumflex", 290 "apple", 291 "Ograve", 292 "Uacute", 293 "Ucircumflex", 294 "Ugrave", 295 "dotlessi", 296 "circumflex", 297 "tilde", 298 "macron", 299 "breve", 300 "dotaccent", 301 "ring", 302 "cedilla", 303 "hungarumlaut", 304 "ogonek", 305 "caron", 306 "Lslash", 307 "lslash", 308 "Scaron", 309 "scaron", 310 "Zcaron", 311 "zcaron", 312 "brokenbar", 313 "Eth", 314 "eth", 315 "Yacute", 316 "yacute", 317 "Thorn", 318 "thorn", 319 "minus", 320 "multiply", 321 "onesuperior", 322 "twosuperior", 323 "threesuperior", 324 "onehalf", 325 "onequarter", 326 "threequarters", 327 "franc", 328 "Gbreve", 329 "gbreve", 330 "Idotaccent", 331 "Scedilla", 332 "scedilla", 333 "Cacute", 334 "cacute", 335 "Ccaron", 336 "ccaron", 337 "dcroat" 338 }; 339 usable() const340 bool PostTable::usable() const 341 { 342 return m_usable; 343 } 344 setUsable(bool usable)345 void PostTable::setUsable(bool usable) 346 { 347 m_usable = usable; 348 } 349 errorMsg() const350 QString PostTable::errorMsg() const 351 { 352 return m_errorMsg; 353 } 354 setErrorMsg(const QString & errorMsg)355 void PostTable::setErrorMsg(const QString& errorMsg) 356 { 357 m_errorMsg = errorMsg; 358 } 359 numberOfGlyphs() const360 uint PostTable::numberOfGlyphs() const 361 { 362 if (m_names.length() > 0) 363 return m_names.length(); 364 return post_format10_names_count; 365 } 366 nameFor(uint glyph) const367 QString PostTable::nameFor(uint glyph) const 368 { 369 if (glyph < (uint) m_names.length()) 370 return m_names[glyph]; 371 if (glyph < sfnt::post_format10_names_count) 372 return post_format10_names[glyph]; 373 return ".notdef"; 374 } 375 readFrom(FT_Face face)376 void PostTable::readFrom(FT_Face face) 377 { 378 QByteArray postData; 379 FT_ULong size = 0; 380 int error = FT_Load_Sfnt_Table ( face, TTAG_post , 0, nullptr, &size ); 381 //qDebug() << "load post" << error << size; 382 if (error || size == 0) 383 { 384 m_errorMsg = "no post table"; 385 m_usable = false; 386 return; 387 } 388 postData.resize(size); 389 error = FT_Load_Sfnt_Table ( face, TTAG_post , 0, reinterpret_cast<FT_Byte*>(postData.data()), &size ); 390 if (error) 391 { 392 m_errorMsg = "can't load post table"; 393 m_usable = false; 394 return; 395 } 396 397 switch (sfnt::word(postData, ttf_post_format)) 398 { 399 case sfnt::post_format10: 400 m_usable = true; 401 m_names.clear(); 402 return; 403 case sfnt::post_format20: 404 break; 405 case sfnt::post_format30: 406 m_errorMsg = QString("post table has no glyph names"); 407 m_usable = false; 408 return; 409 case sfnt::post_format25: 410 case sfnt::post_format40: 411 default: 412 m_errorMsg = QString("unsupported post format %1").arg(sfnt::word(postData,0)); 413 m_usable = false; 414 return; 415 416 } 417 QMap<QString,uint> usedNames; 418 QList<QByteArray> pascalStrings; 419 420 uint nrOfGlyphs = sfnt::word16(postData, ttf_post_header_length); 421 uint stringPos = ttf_post_header_length + 2 + 2 * nrOfGlyphs; 422 while (stringPos < (uint) postData.length()) 423 { 424 int strLen = byte(postData, stringPos); 425 ++stringPos; 426 pascalStrings.append(postData.mid(stringPos, strLen)); 427 stringPos += strLen; 428 } 429 uint pos = ttf_post_header_length + 2; 430 for (uint gid = 0; gid < nrOfGlyphs; ++gid) 431 { 432 uint nameIndex = sfnt::word16(postData, pos); 433 pos += 2; 434 QString name; 435 if (nameIndex < sfnt::post_format10_names_count) 436 name = sfnt::post_format10_names[nameIndex]; 437 else if (nameIndex < pascalStrings.length() + sfnt::post_format10_names_count) 438 name = pascalStrings[nameIndex - sfnt::post_format10_names_count]; 439 else { 440 m_usable = false; 441 m_errorMsg = QString("missing name %1 for glyph %2").arg(nameIndex).arg(gid); 442 return; 443 } 444 if (name != ".notdef" && name[0] != QChar(0) && usedNames.contains(name)) 445 { 446 m_usable = false; 447 m_errorMsg = QString("duplicate name %1 used for glyphs %2 and %3").arg(name).arg(gid).arg(usedNames[name]); 448 return; 449 } 450 usedNames[name] = gid; 451 m_names.append(name); 452 } 453 m_errorMsg = ""; 454 m_usable = true; 455 } 456 copyTable(QByteArray & ttf,uint destDirEntry,uint pos,const QByteArray & source,uint dirEntry)457 int copyTable(QByteArray& ttf, uint destDirEntry, uint pos, const QByteArray& source, uint dirEntry) 458 { 459 FT_ULong tag = word(source, dirEntry + ttf_TableRecord_tag); 460 uint checksum = word(source, dirEntry + ttf_TableRecord_checkSum); 461 uint tableStart = word(source, dirEntry + ttf_TableRecord_offset); 462 uint tableSize = word(source, dirEntry + ttf_TableRecord_length); 463 464 if (!copy(ttf, pos, source, tableStart, tableSize)) 465 return -1; 466 467 putWord(ttf, destDirEntry + ttf_TableRecord_tag, tag); 468 putWord(ttf, destDirEntry + ttf_TableRecord_checkSum, checksum); 469 putWord(ttf, destDirEntry + ttf_TableRecord_offset, pos); 470 putWord(ttf, destDirEntry + ttf_TableRecord_length, tableSize); 471 472 return tableSize; 473 } 474 extractFace(const QByteArray & coll,int faceIndex)475 QByteArray extractFace(const QByteArray& coll, int faceIndex) 476 { 477 QByteArray result; 478 479 const int numFonts = word(coll, ttc_numFonts); 480 if (faceIndex >= static_cast<int>(numFonts)) 481 { 482 return result; 483 } 484 485 uint faceOffset = sfnt::word(coll, ttc_OffsetTables + 4 * faceIndex); 486 uint nTables = sfnt::word16(coll, faceOffset + ttf_numtables); 487 488 //qDebug() << QObject::tr("extracting face %1 from font %2 (offset=%3, nTables=%4)").arg(faceIndex).arg("collection").arg(faceOffset).arg(nTables); 489 490 uint headerLength = ttf_TableRecords + ttf_TableRecord_Size * nTables; 491 492 uint tableLengths = 0; 493 // sum table lengths incl padding 494 for (uint i=0; i < nTables; ++i) 495 { 496 tableLengths += sfnt::word(coll, faceOffset + ttf_TableRecords + ttf_TableRecord_Size * i + ttf_TableRecord_length); 497 tableLengths = (tableLengths+3) & ~3; 498 } 499 result.resize(headerLength + tableLengths); 500 if (!result.data()) 501 { 502 result.resize(0); 503 return result; 504 } 505 506 // write header 507 // sDebug(QObject::tr("memcpy header: %1 %2 %3").arg(0).arg(faceOffset).arg(headerLength)); 508 if (!copy(result, 0, coll, faceOffset, headerLength)) 509 { 510 result.resize(0); 511 return result; 512 } 513 uint pos = headerLength; 514 for (uint i=0; i < nTables; ++i) 515 { 516 uint sourceDirEntry = faceOffset + ttf_TableRecords + ttf_TableRecord_Size * i; 517 uint destDirEntry = ttf_TableRecords + ttf_TableRecord_Size * i; 518 519 int tableSize = copyTable(result, destDirEntry, pos, coll, sourceDirEntry); 520 if (tableSize < 0) 521 { 522 result.resize(0); 523 return result; 524 } 525 pos += tableSize; 526 527 // pad 528 while ((pos & 3) != 0) 529 result.data()[pos++] = '\0'; 530 } 531 return result; 532 } 533 getTableDirEntry(const QByteArray & ttf,const QByteArray & ttfTag)534 uint getTableDirEntry(const QByteArray& ttf, const QByteArray& ttfTag) 535 { 536 uint nTables = word16(ttf, ttf_numtables); 537 uint pos = ttf_TableRecords; 538 for (uint i=0; i < nTables; ++i) 539 { 540 if (ttfTag == tag(word(ttf, pos + ttf_TableRecord_tag))) 541 { 542 return pos; 543 } 544 pos += ttf_TableRecord_Size; 545 } 546 return 0; 547 } 548 getTable(const QByteArray & ttf,const QByteArray & ttfTag)549 QByteArray getTable(const QByteArray& ttf, const QByteArray& ttfTag) 550 { 551 uint pos = getTableDirEntry(ttf, ttfTag); 552 if (pos <= 0) 553 return QByteArray(); 554 555 uint offset = word(ttf, pos + ttf_TableRecord_offset); 556 uint length = word(ttf, pos + ttf_TableRecord_length); 557 return QByteArray::fromRawData(ttf.constData() + offset, length); 558 } 559 createTableDir(const QList<QByteArray> & tags)560 QByteArray createTableDir(const QList<QByteArray>& tags) 561 { 562 QByteArray result; 563 uint numTables = tags.length(); 564 uint tableRecordsSize = numTables * ttf_TableRecord_Size; 565 result.resize(ttf_TableRecords + tableRecordsSize); 566 uint entrySelector = 0; 567 uint searchRange = ttf_TableRecord_Size; 568 while (2*searchRange < tableRecordsSize) 569 { 570 searchRange *= 2; 571 ++entrySelector; 572 } 573 uint rangeShift = tableRecordsSize - searchRange; 574 575 putWord(result, ttf_sfnt_version, 0x00010000); 576 putWord16(result, ttf_numtables, numTables); 577 putWord16(result, ttf_searchRange, searchRange); 578 putWord16(result, ttf_entrySelector, entrySelector); 579 putWord16(result, ttf_rangeShift, rangeShift); 580 for (uint i = 0; i < numTables; ++i) 581 { 582 copy(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_tag, tags[i], 0, 4); 583 putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_checkSum, 0); 584 putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_offset, 0); 585 putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size +ttf_TableRecord_length, 0); 586 } 587 return result; 588 } 589 calcTableChecksum(QByteArray & table)590 quint32 calcTableChecksum(QByteArray& table) 591 { 592 quint32 Sum = 0L; 593 for (int pos = 0; pos < table.length(); pos += 4) 594 Sum += word(table, pos); 595 return Sum; 596 } 597 writeTable(QByteArray & ttf,const QByteArray & tag,QByteArray & table)598 void writeTable(QByteArray& ttf, const QByteArray& tag, QByteArray& table) 599 { 600 //qDebug() << "writing table" << tag << table.size() << "@" << ttf.size(); 601 uint length = table.size(); 602 while (table.size() & 0x3) 603 table.append('\0'); 604 uint offset = ttf.size(); 605 uint checksum = calcTableChecksum(table); 606 uint pos = getTableDirEntry(ttf, tag); 607 putWord(ttf, pos + ttf_TableRecord_checkSum, checksum); 608 putWord(ttf, pos + ttf_TableRecord_offset, offset); 609 putWord(ttf, pos + ttf_TableRecord_length, length); 610 ttf.append(table); 611 } 612 hasLongLocaFormat(const QByteArray & ttf)613 bool hasLongLocaFormat(const QByteArray& ttf) 614 { 615 const QByteArray head = getTable(ttf, "head"); 616 uint idxToLocFormat = word16(head, ttf_head_indexToLocFormat); 617 // qDebug() << "loca format:" << (void*)idxToLocFormat; 618 return idxToLocFormat == 1; 619 } 620 readLoca(const QByteArray & ttf)621 QList<quint32> readLoca(const QByteArray& ttf) 622 { 623 QList<quint32> result; 624 const QByteArray loca = getTable(ttf, "loca"); 625 result.reserve(loca.length()); 626 if (hasLongLocaFormat(ttf)) 627 { 628 for (int i = 0; i < loca.length(); i+=4) 629 result.append(word(loca, i)); 630 } 631 else 632 { 633 for (int i = 0; i < loca.length(); i+=2) 634 result.append(word16(loca, i) * 2); 635 } 636 return result; 637 } 638 writeLoca(const QList<uint> & loca,bool longFormat)639 QByteArray writeLoca(const QList<uint>& loca, bool longFormat) 640 { 641 QByteArray result; 642 if (longFormat) 643 { 644 for (int i=0; i < loca.length(); ++i) 645 appendWord(result, loca[i]); 646 } 647 else 648 { 649 for (int i=0; i < loca.length(); ++i) 650 appendWord16(result, loca[i] / 2); 651 } 652 return result; 653 } 654 readHmtx(const QByteArray & ttf)655 QList<std::pair<qint16,quint16> > readHmtx(const QByteArray& ttf) 656 { 657 QList<std::pair<qint16,quint16> > result; 658 const QByteArray hhea = getTable(ttf, "hhea"); 659 const QByteArray hmtx = getTable(ttf, "hmtx"); 660 uint endOfLongHorMetrics = 4 * word16(hhea, ttf_hhea_numOfLongHorMetrics); 661 qint16 advance; 662 quint16 leftSideBearing; 663 uint pos = 0; 664 while (pos < endOfLongHorMetrics) 665 { 666 advance = word16(hmtx, pos); 667 leftSideBearing = word16(hmtx, pos+2); 668 //qDebug() << pos << "hmtx" << advance << leftSideBearing; 669 result.append(std::pair<qint16,quint16>(advance, leftSideBearing)); 670 pos += 4; 671 } 672 while (pos < (uint) hmtx.length()) 673 { 674 leftSideBearing = word16(hmtx, pos); 675 //qDebug() << pos << "hmtx =" << advance << leftSideBearing; 676 result.append(std::pair<qint16,quint16>(advance, leftSideBearing)); 677 pos += 2; 678 } 679 return result; 680 } 681 writeHmtx(const QList<std::pair<qint16,quint16>> & longHorMetrics)682 QByteArray writeHmtx(const QList<std::pair<qint16,quint16> >& longHorMetrics) 683 { 684 QByteArray result; 685 QList<std::pair<qint16,quint16> >::const_iterator it; 686 //int i = 0; 687 for (it = longHorMetrics.cbegin(); it < longHorMetrics.cend(); ++it) 688 { 689 //qDebug() << "hmtx" << i++ << it->first << it->second; 690 appendWord16(result, it->first); 691 appendWord16(result, it->second); 692 } 693 return result; 694 } 695 readCMap(const QByteArray & ttf)696 QMap<uint, uint> readCMap(const QByteArray& ttf) 697 { 698 QMap<uint,uint> result; 699 const QByteArray cmaps = getTable(ttf, "cmap"); 700 701 uint numSubtables = word16(cmaps, ttf_cmap_numberSubtables); 702 uint startOfUnicodeTable = 0; 703 uint startOfSymbolTable = 0; 704 uint unicodeFormat = 0, symbolFormat = 0; 705 uint pos = ttf_cmap_encodings; 706 for (uint i = 0; i < numSubtables; ++i) 707 { 708 uint platform = word16(cmaps, pos + ttf_cmap_encoding_platformID); 709 uint encoding = word16(cmaps, pos + ttf_cmap_encoding_platformSpecificID); 710 uint offset = word(cmaps, pos + ttf_cmap_encoding_offset); 711 uint format = word16(cmaps, offset + ttf_cmapx_format); 712 pos += ttf_cmap_encoding_Size; 713 714 if (format < 4 || format > 12) 715 continue; 716 if (platform == 0 || (platform == 3 && encoding == 1)) 717 { 718 startOfUnicodeTable = offset; 719 unicodeFormat = format; 720 break; 721 } 722 if (platform == 3 && encoding == 0) // MS Symbol cmap 723 { 724 startOfSymbolTable = offset; 725 symbolFormat = format; 726 continue; 727 } 728 format = 1; // no such format 729 } 730 // If no unicode cmap was found, fallback on ms symbol cmap 731 uint format = unicodeFormat; 732 if ((unicodeFormat <= 0) && (symbolFormat > 0)) 733 { 734 startOfUnicodeTable = startOfSymbolTable; 735 format = symbolFormat; 736 } 737 //qDebug() << "reading cmap format" << format; 738 switch (format) 739 { 740 case 4: 741 { 742 uint segCount2 = word16(cmaps, startOfUnicodeTable + ttf_cmap4_segCountX2); 743 uint endCodes = startOfUnicodeTable + ttf_cmap4_EndCodes; 744 uint startCodes = endCodes + segCount2 + ttf_cmap4_StartCodes_EndCodes; 745 uint idDeltas = startCodes + segCount2 + ttf_cmap4_IdDeltas_StartCodes; 746 uint idRangeOffsets = idDeltas + segCount2 + ttf_cmap4_IdRangeOffsets_IdDeltas; 747 //uint glyphIndexArray = idRangeOffsets + segCount2 + ttf_cmap4_GlyphIds_IdRangeOffsets; 748 749 for (uint seg = 0; seg < segCount2; seg+=2) 750 { 751 uint start = word16(cmaps, startCodes + seg); 752 uint end = word16(cmaps, endCodes + seg); 753 uint idDelta = word16(cmaps, idDeltas + seg); 754 uint idRangeOffset = word16(cmaps, idRangeOffsets + seg); 755 for (uint c = start; c <= end; ++c) 756 { 757 quint16 glyph; 758 if (idRangeOffset > 0) 759 { 760 uint glyphIndexAdress = idRangeOffset + 2*(c-start) + (idRangeOffsets + seg); 761 glyph = word16(cmaps, glyphIndexAdress); 762 if (glyph != 0) 763 glyph += idDelta; 764 } 765 else 766 { 767 glyph = c + idDelta; 768 } 769 if (!result.contains(c)) 770 { 771 // search would always find the one in the segment with the lower endcode, i.e. earlier segment 772 if (c < 256 || glyph == 0) 773 //qDebug() << "(" << QChar(c) << "," << glyph << ")"; 774 result[c] = glyph; 775 } 776 else 777 { 778 // nothing to do. No idea if fonts with overlapping cmap4 segments exist, though. 779 } 780 } 781 } 782 break; 783 } 784 case 6: 785 { 786 uint firstCode = word16(cmaps, startOfUnicodeTable + ttf_cmap6_firstCode); 787 uint count = word16(cmaps, startOfUnicodeTable + ttf_cmap6_entryCount); 788 pos = word16(cmaps, startOfUnicodeTable + ttf_cmap6_glyphIndexArray); 789 for (uint i = 0; i < count; ++i) 790 { 791 result[firstCode + i] = word16(cmaps, pos); 792 pos += 2; 793 } 794 break; 795 } 796 case 12: 797 { 798 uint nGroups = word(cmaps, startOfUnicodeTable + ttf_cmap12_nGroups); 799 pos = startOfUnicodeTable + ttf_cmap12_Groups; 800 for (uint grp = 0; grp < nGroups; ++grp) 801 { 802 uint start = word(cmaps, pos + ttf_cmap12_Group_startCharCode); 803 uint end = word(cmaps, pos + ttf_cmap12_Group_endCharCode); 804 uint gid = word(cmaps, pos + ttf_cmap12_Group_startGlyphCode); 805 for (uint c = start; c <= end; ++c) 806 { 807 result[c] = gid; 808 ++gid; 809 } 810 pos += ttf_cmap12_Group_Size; 811 } 812 break; 813 } 814 default: 815 { 816 qDebug() << "unsupported cmap format" << format; 817 break; 818 } 819 } 820 return result; 821 } 822 writeCMap(const QMap<uint,uint> & cmap)823 QByteArray writeCMap(const QMap<uint, uint>& cmap) 824 { 825 // we always write only one table: platform=3(MS), encoding=1(Unicode 16bit) 826 QByteArray result; 827 appendWord16(result, 0); // version 828 appendWord16(result, 1); // number of subtables 829 appendWord16(result, 3); // platformID Microsoft 830 appendWord16(result, 1); // encodingID UnicodeBMP 831 appendWord(result, result.size() + 4); // offset 832 833 // find the segments 834 835 QList<uint> chars; 836 QMap<uint, uint>::ConstIterator cit; 837 //qDebug() << "writing cmap"; 838 bool cmapHasData = false; 839 for (cit = cmap.cbegin(); cit != cmap.cend(); ++cit) 840 { 841 uint ch = cit.key(); 842 if (!QChar::requiresSurrogates(ch) && cit.value() != 0) 843 { 844 //qDebug() << "(" << QChar(cit.key()) << "," << cit.value() << ")"; 845 chars.append(ch); 846 cmapHasData = true; 847 } 848 // qDebug() << QChar(ch) << QChar::requiresSurrogates(ch) << cit.value(); 849 } 850 std::sort(chars.begin(), chars.end()); 851 852 QList<quint16> startCodes; 853 QList<quint16> endCodes; 854 QList<quint16> idDeltas; 855 QList<quint16> rangeOffsets; 856 if (cmapHasData) 857 { 858 uint pos = 0; 859 do { 860 quint16 start = chars[pos]; 861 quint16 delta = cmap[start] - start; 862 quint16 rangeOffset = 0; 863 quint16 end = start; 864 quint16 next; 865 ++pos; 866 while (pos < (uint) chars.length() && (next = chars[pos]) == end+1) 867 { 868 end = next; 869 if (delta != (quint16)(cmap[chars[pos]] - next)) 870 { 871 rangeOffset = 1; // will be changed later 872 } 873 ++pos; 874 } 875 startCodes.append(start); 876 endCodes.append(end); 877 idDeltas.append(delta); 878 rangeOffsets.append(rangeOffset); 879 } 880 while (pos < (uint) chars.length()); 881 } 882 883 startCodes.append(0xFFFF); 884 endCodes.append(0xFFFF); 885 idDeltas.append(1); // makes gid 0 886 rangeOffsets.append(0); 887 888 // write the tables 889 890 uint startOfTable = result.size(); 891 result.resize(startOfTable + ttf_cmap4_EndCodes); 892 893 uint segCount = endCodes.length(); 894 uint segCountX2 = 2 * segCount; 895 uint entrySelector = 0; 896 uint searchRange = 2; 897 while (searchRange <= segCount) 898 { 899 ++entrySelector; 900 searchRange *= 2; 901 } 902 putWord16(result, startOfTable + ttf_cmapx_format, 4); 903 /* ttf_cmap4_length is set later */ 904 putWord16(result, startOfTable + ttf_cmap4_language, 0); 905 putWord16(result, startOfTable + ttf_cmap4_segCountX2, segCountX2); 906 putWord16(result, startOfTable + ttf_cmap4_searchRange, searchRange); 907 putWord16(result, startOfTable + ttf_cmap4_entrySelector, entrySelector); 908 putWord16(result, startOfTable + ttf_cmap4_rangeShift, segCountX2 - searchRange); 909 910 for (uint i = 0; i < segCount; ++i) 911 { 912 appendWord16(result, endCodes[i]); 913 }; 914 appendWord16(result, 0); // reservedPad 915 916 for (uint i = 0; i < segCount; ++i) 917 { 918 appendWord16(result, startCodes[i]); 919 }; 920 for (uint i = 0; i < segCount; ++i) 921 { 922 appendWord16(result, idDeltas[i]); 923 }; 924 925 926 uint startOfIdRangeOffsetTable = result.size(); 927 uint startOfGlyphIndexArray = startOfIdRangeOffsetTable + segCountX2; 928 result.resize(startOfGlyphIndexArray); 929 930 for (uint i = 0; i < segCount; ++i) 931 { 932 uint idRangeOffsetAddress = startOfIdRangeOffsetTable + 2*i; 933 if (rangeOffsets[i] == 0) 934 { 935 //quint16 dbg = startCodes[i] + idDeltas[i]; 936 //qDebug() << QChar(startCodes[i]) << "-" << QChar(endCodes[i]) << "/" << (endCodes[i]-startCodes[i]+1) << "+" << idDeltas[i] << "-->" << dbg; 937 putWord16(result, idRangeOffsetAddress, 0); 938 } 939 else 940 { 941 quint16 idRangeOffset = result.size() - idRangeOffsetAddress; 942 putWord16(result, idRangeOffsetAddress, idRangeOffset); 943 944 //qDebug() << QChar(startCodes[i]) << "-" << QChar(endCodes[i]) << "/" << (endCodes[i]-startCodes[i]+1) << "@" << idRangeOffset << "+" << idDeltas[i]; 945 946 uint startCode = startCodes[i]; 947 uint segLength = (endCodes[i]-startCode+1); 948 for (uint offset = 0; offset < segLength; ++offset) 949 { 950 quint16 glyph = cmap[startCode + offset]; 951 if (glyph != 0) 952 { 953 glyph -= idDeltas[i]; 954 } 955 appendWord16(result, glyph); 956 } 957 } 958 }; 959 960 putWord16(result, startOfTable + ttf_cmap4_length, result.size() - startOfTable); 961 return result; 962 } 963 copyGlyphComponents(QByteArray & destGlyf,const QByteArray & srcGlyf,uint srcOffset,QMap<uint,uint> & newForOldGid,uint & nextFreeGid)964 QList<uint> copyGlyphComponents(QByteArray& destGlyf, const QByteArray& srcGlyf, uint srcOffset, 965 QMap<uint,uint>& newForOldGid, uint& nextFreeGid) 966 { 967 QList<uint> result; 968 969 uint destStart = destGlyf.size(); 970 destGlyf.resize(destStart + ttf_glyf_headerSize); 971 copy(destGlyf, destStart, srcGlyf, srcOffset, ttf_glyf_headerSize); 972 973 uint pos = srcOffset + ttf_glyf_headerSize; 974 bool haveInstructions = false; 975 uint flags = 0; 976 do { 977 /* flags */ 978 flags = word16(srcGlyf, pos); 979 pos += 2; 980 haveInstructions |= ((flags & ttf_glyf_ComponentFlag_WE_HAVE_INSTRUCTIONS) != 0); 981 appendWord16(destGlyf, flags); 982 983 /* glyphindex */ 984 uint glyphIndex = word16(srcGlyf, pos); 985 pos += 2; 986 if (!newForOldGid.contains(glyphIndex)) 987 { 988 result.append(glyphIndex); 989 newForOldGid[glyphIndex] = nextFreeGid++; 990 } 991 glyphIndex = newForOldGid[glyphIndex]; 992 appendWord16(destGlyf, glyphIndex); 993 994 /* args */ 995 if ( flags & ttf_glyf_ComponentFlag_ARG_1_AND_2_ARE_WORDS) { 996 appendWord16(destGlyf, word16(srcGlyf, pos)); // arg1 997 pos += 2; 998 appendWord16(destGlyf, word16(srcGlyf, pos)); // arg2 999 pos += 2; 1000 } 1001 else { 1002 appendWord16(destGlyf, word16(srcGlyf, pos)); // arg1and2 1003 pos += 2; 1004 } 1005 if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_A_SCALE ) { 1006 appendWord16(destGlyf, word16(srcGlyf, pos)); // scale 1007 pos += 2; 1008 } else if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_AN_X_AND_Y_SCALE ) { 1009 appendWord16(destGlyf, word16(srcGlyf, pos)); // xscale 1010 pos += 2; 1011 appendWord16(destGlyf, word16(srcGlyf, pos)); // yscale 1012 pos += 2; 1013 } else if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_A_TWO_BY_TWO ) { 1014 appendWord16(destGlyf, word16(srcGlyf, pos)); // xscale 1015 pos += 2; 1016 appendWord16(destGlyf, word16(srcGlyf, pos)); // scale01 1017 pos += 2; 1018 appendWord16(destGlyf, word16(srcGlyf, pos)); // scale10 1019 pos += 2; 1020 appendWord16(destGlyf, word16(srcGlyf, pos)); // yscale 1021 pos += 2; 1022 } 1023 } while ( flags & ttf_glyf_ComponentFlag_MORE_COMPONENTS ); 1024 1025 if (haveInstructions) 1026 { 1027 uint numInstr = word16(srcGlyf, pos); 1028 appendWord16(destGlyf, numInstr); 1029 pos += 2; 1030 uint destPos = destGlyf.size(); 1031 destGlyf.resize(destPos + numInstr); 1032 copy(destGlyf, destPos, srcGlyf, pos, numInstr); 1033 } 1034 1035 return result; 1036 } 1037 copyGlyph(QList<uint> & destLoca,QByteArray & destGlyf,uint destGid,const QList<uint> & srcLoca,const QByteArray & srcGlyf,uint srcGid,QMap<uint,uint> & newForOldGid,uint & nextFreeGid)1038 QList<uint> copyGlyph(QList<uint>& destLoca, QByteArray& destGlyf, uint destGid, 1039 const QList<uint>& srcLoca, const QByteArray& srcGlyf, uint srcGid, 1040 QMap<uint,uint>& newForOldGid, uint& nextFreeGid) 1041 { 1042 QList<uint> compositeElements; 1043 uint glyphStart = srcLoca[srcGid]; 1044 uint glyphLength = srcLoca[srcGid+1] - glyphStart; 1045 destLoca.append(destGlyf.size()); 1046 //int i = 0; 1047 if (glyphLength > 0) 1048 { 1049 quint16 nrOfContours = word16(srcGlyf, glyphStart); 1050 if (nrOfContours <= ttf_glyf_Max_numberOfContours) 1051 { 1052 // simple glyph 1053 uint destStart = destGlyf.size(); 1054 //qDebug() << i++ << ":" << nrOfContours << "contours" << glyphStart << "-->" << destStart << "/" << glyphLength; 1055 destGlyf.resize(destStart + glyphLength); 1056 copy(destGlyf, destStart, srcGlyf, glyphStart, glyphLength); 1057 } 1058 else 1059 { 1060 compositeElements.append(copyGlyphComponents(destGlyf, srcGlyf, glyphStart, newForOldGid, nextFreeGid)); 1061 //qDebug() << i++ << ":" << srcGid << "composite glyph brought" << compositeElements.size() << "more glyphs"; 1062 } 1063 // Data must be aligned at least on 2 byte boundary, however 1064 // a 4-byte alignment is recommended by TTF specs for reasons 1065 // related to CPU and efficiency 1066 int targetSize = (destGlyf.size() + 3) & ~3; 1067 if (destGlyf.size() < targetSize) 1068 { 1069 destGlyf.reserve(targetSize); 1070 while (destGlyf.size() < targetSize) 1071 destGlyf.append('\0'); 1072 } 1073 } 1074 1075 return compositeElements; 1076 } 1077 subsetFace(const QByteArray & ttf,QList<uint> & glyphs,QMap<uint,uint> & glyphMap)1078 QByteArray subsetFace(const QByteArray& ttf, QList<uint>& glyphs, QMap<uint, uint>& glyphMap) 1079 { 1080 QMap<QByteArray,QByteArray> tables; 1081 1082 // qDebug() << "loca table:" << (void*) oldLoca[0] << (void*) oldLoca[1] << (void*) oldLoca[2] << (void*) oldLoca[3] << (void*) oldLoca[4] << (void*) oldLoca[5] << (void*) oldLoca[6] << (void*) oldLoca[7]; 1083 1084 QMap<uint, uint> newForOldGid; 1085 if (glyphs.length() == 0) 1086 { 1087 tables["loca"] = getTable(ttf, "loca"); 1088 tables["glyf"] = getTable(ttf, "glyf"); 1089 } 1090 else 1091 { 1092 QList<uint> oldLoca = readLoca(ttf); 1093 const QByteArray oldGlyf = getTable(ttf, "glyf"); 1094 1095 QList<quint32> newLoca; 1096 QByteArray newGlyf; 1097 glyphs.removeAll(0); 1098 glyphs.prepend(0); 1099 1100 for (int i = 0; i < glyphs.length(); ++i) 1101 { 1102 uint oldGid = glyphs[i]; 1103 newForOldGid[oldGid] = i; 1104 } 1105 1106 uint nextFreeGid = glyphs.length(); 1107 for (int i = 0; i < glyphs.length(); ++i) 1108 { 1109 uint oldGid = glyphs[i]; 1110 glyphs.append(copyGlyph(newLoca, newGlyf, i, 1111 oldLoca, oldGlyf, oldGid, 1112 newForOldGid, nextFreeGid)); 1113 } 1114 newLoca.append(newGlyf.length()); 1115 1116 tables["loca"] = writeLoca(newLoca, hasLongLocaFormat(ttf)); 1117 tables["glyf"] = newGlyf; 1118 } 1119 1120 QMap<uint,uint> cmap = readCMap(ttf); 1121 QMap<uint,uint>::iterator it; 1122 uint firstChar = 0xFFFFFFFF; 1123 uint lastChar = 0; 1124 for (it = cmap.begin(); it != cmap.end(); ++it) 1125 { 1126 if (glyphs.length() > 0 && !glyphs.contains(it.value())) 1127 { 1128 it.value() = 0; 1129 } 1130 else if (it.value() != 0) 1131 { 1132 if (glyphs.length() > 0) 1133 { 1134 //qDebug() << "MAP" << QChar(it.key()) << it.value() << "-->" << newForOldGid[it.value()]; 1135 it.value() = newForOldGid[it.value()]; 1136 } 1137 if (it.key() < firstChar) 1138 firstChar = it.key(); 1139 else if (it.key() > lastChar) 1140 lastChar = it.key(); 1141 } 1142 } 1143 tables["cmap"] = writeCMap(cmap); 1144 1145 QByteArray os2 = getTable(ttf, "OS/2"); 1146 if (os2.length() > ttf_os2_usLastCharIndex) 1147 { 1148 // TODO: adapt unicode ranges 1149 putWord16(os2, ttf_os2_usFirstCharIndex, firstChar < 0xFFFF ? firstChar : 0xFFFF); 1150 putWord16(os2, ttf_os2_usLastCharIndex, lastChar < 0xFFFF ? lastChar : 0xFFFF); 1151 tables["OS/2"] = os2; 1152 } 1153 1154 if (glyphs.length() > 0) 1155 { 1156 QList<std::pair<qint16, quint16> > oldHmtx = readHmtx(ttf); 1157 QList<std::pair<qint16, quint16> > newHmtx; 1158 newHmtx.reserve(glyphs.length() + 1); 1159 newHmtx.append(std::pair<qint16, quint16>(1234, 123)); 1160 for (int i = 1; i < glyphs.length(); ++i) 1161 newHmtx.append(newHmtx[0]); 1162 QMap<uint,uint>::const_iterator iter; 1163 for (iter = newForOldGid.cbegin(); iter != newForOldGid.cend(); ++iter) 1164 { 1165 //qDebug() << "hmtx" << iter.key() << " -> " << iter.value() << "=" << oldHmtx[iter.key()].first; 1166 newHmtx[iter.value()] = oldHmtx[iter.key()]; 1167 } 1168 tables["hmtx"] = writeHmtx(newHmtx); 1169 } 1170 else 1171 { 1172 tables["hmtx"] = getTable(ttf, "hmtx"); 1173 } 1174 1175 QByteArray maxp = getTable(ttf, "maxp"); 1176 if (glyphs.length() > 0) 1177 { 1178 putWord16(maxp, ttf_maxp_numGlyphs, glyphs.length()); 1179 } 1180 tables["maxp"] = maxp; 1181 1182 QByteArray hhea = getTable(ttf, "hhea"); 1183 if (glyphs.length() > 0) 1184 { 1185 putWord16(hhea, ttf_hhea_numOfLongHorMetrics, glyphs.length()); 1186 } 1187 tables["hhea"] = hhea; 1188 1189 QByteArray post = getTable(ttf, "post"); 1190 if (word(post, ttf_post_format) != post_format30) 1191 { 1192 putWord(post, ttf_post_format, post_format30); 1193 post.truncate(ttf_post_header_length); 1194 } 1195 tables["post"] = post; 1196 1197 // TODO: kern table 1198 1199 QByteArray name = getTable(ttf, "name"); 1200 if (name.length() > 0) 1201 tables["name"] = name; 1202 1203 QByteArray prep = getTable(ttf, "prep"); 1204 if (prep.length() > 0) 1205 tables["prep"] = prep; 1206 1207 QByteArray cvt = getTable(ttf, "cvt "); 1208 if (cvt.length() > 0) 1209 tables["cvt "] = cvt; 1210 1211 QByteArray fpgm = getTable(ttf, "fpgm"); 1212 if (fpgm.length() > 0) 1213 tables["fpgm"] = fpgm; 1214 1215 QByteArray head = getTable(ttf, "head"); 1216 putWord(head, ttf_head_checkSumAdjustment, 0); 1217 tables["head"] = head; 1218 1219 QByteArray font = createTableDir(tables.keys()); 1220 QMap<QByteArray,QByteArray>::iterator tableP; 1221 for (tableP = tables.begin(); tableP != tables.end(); ++tableP) 1222 { 1223 writeTable(font, tableP.key(), tableP.value()); 1224 } 1225 1226 uint checkSumAdjustment = 0xB1B0AFBA - calcTableChecksum(font); 1227 uint headTable = getTableDirEntry(font, "head"); 1228 headTable = word(font, headTable + ttf_TableRecord_offset); 1229 putWord(font, headTable + ttf_head_checkSumAdjustment, checkSumAdjustment); 1230 1231 glyphMap.clear(); 1232 for (int i = 0; i < glyphs.length(); ++i) 1233 glyphMap[glyphs[i]] = i; 1234 // done! 1235 1236 return font; 1237 } 1238 subsetFaceWithHB(const QByteArray & fontData,QList<uint> cids,int faceIndex,QMap<uint,uint> & glyphMap)1239 QByteArray subsetFaceWithHB(const QByteArray& fontData, QList<uint> cids, int faceIndex, QMap<uint, uint>& glyphMap) 1240 { 1241 glyphMap.clear(); 1242 1243 #ifdef HAVE_HARFBUZZ_SUBSET 1244 QScopedPointer<hb_blob_t, HbBlobDeleter> hbBlob(hb_blob_create(fontData.data(), fontData.length(), HB_MEMORY_MODE_READONLY, nullptr, nullptr)); 1245 if (hbBlob.isNull()) 1246 return QByteArray(); 1247 1248 QScopedPointer<hb_face_t, HbFaceDeleter> hbFullFace(hb_face_create(hbBlob.get(), faceIndex)); 1249 if (hbFullFace.isNull()) 1250 return QByteArray(); 1251 1252 QScopedPointer<hb_subset_input_t, HbSubsetInputDeleter> hbSubsetInput(hb_subset_input_create_or_fail()); 1253 hb_set_t* glyphSet = hb_subset_input_glyph_set(hbSubsetInput.get()); 1254 if (glyphSet == nullptr) 1255 return QByteArray(); 1256 1257 for (int i = 0; i < cids.count(); ++i) 1258 hb_set_add(glyphSet, cids.at(i)); 1259 1260 #if HB_VERSION_ATLEAST(2, 9, 0) 1261 uint32_t subsetFlags = (uint32_t) hb_subset_input_get_flags(hbSubsetInput.get()); 1262 subsetFlags |= HB_SUBSET_FLAGS_RETAIN_GIDS; 1263 subsetFlags &= ~HB_SUBSET_FLAGS_NO_HINTING; 1264 subsetFlags |= HB_SUBSET_FLAGS_NAME_LEGACY; 1265 hb_subset_input_set_flags(hbSubsetInput.get(), subsetFlags); 1266 #else 1267 hb_subset_input_set_retain_gids(hbSubsetInput.get(), true); 1268 hb_subset_input_set_drop_hints(hbSubsetInput.get(), false); 1269 #if HB_VERSION_ATLEAST(2, 6, 5) 1270 hb_subset_input_set_name_legacy(hbSubsetInput.get(), true); 1271 #endif 1272 #endif 1273 1274 QScopedPointer<hb_face_t, HbFaceDeleter> hbSubsetFace(hb_subset_or_fail(hbFullFace.get(), hbSubsetInput.get())); 1275 if (hbSubsetFace.isNull()) 1276 return QByteArray(); 1277 1278 QScopedPointer<hb_blob_t, HbBlobDeleter> hbSubsetBlob(hb_face_reference_blob(hbSubsetFace.get())); 1279 if (hbSubsetBlob.isNull()) 1280 return QByteArray(); 1281 1282 unsigned int length; 1283 const char* subsetData = hb_blob_get_data(hbSubsetBlob.get(), &length); 1284 1285 QByteArray subset(subsetData, length); 1286 if (!subset.isEmpty()) 1287 { 1288 for (int i = 0; i < cids.length(); ++i) 1289 glyphMap[cids[i]] = cids[i]; 1290 } 1291 return subset; 1292 #else 1293 return QByteArray(); 1294 #endif 1295 } 1296 canSubsetOpenTypeFonts()1297 bool canSubsetOpenTypeFonts() 1298 { 1299 #ifdef HAVE_HARFBUZZ_SUBSET 1300 return true; 1301 #else 1302 return false; 1303 #endif 1304 } 1305 1306 } // namespace sfnt 1307