1 /* 2 * Copyright 2011 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // type.h needs to be included first because of building issues on Windows 18 // Type aliases we delcare are defined in other headers and make the build 19 // fail otherwise. 20 #include "sfntly/port/type.h" 21 #include "sfntly/table/core/cmap_table.h" 22 23 #include <stdio.h> 24 #include <stdlib.h> 25 26 #include <utility> 27 28 #include "sfntly/font.h" 29 #include "sfntly/math/font_math.h" 30 #include "sfntly/port/endian.h" 31 #include "sfntly/port/exception_type.h" 32 #include "sfntly/table/core/name_table.h" 33 34 namespace sfntly { 35 36 const int32_t CMapTable::NOTDEF = 0; 37 38 CMapTable::CMapId CMapTable::WINDOWS_BMP = { 39 PlatformId::kWindows, 40 WindowsEncodingId::kUnicodeUCS2 41 }; 42 CMapTable::CMapId CMapTable::WINDOWS_UCS4 = { 43 PlatformId::kWindows, 44 WindowsEncodingId::kUnicodeUCS4 45 }; 46 CMapTable::CMapId CMapTable::MAC_ROMAN = { 47 PlatformId::kWindows, 48 MacintoshEncodingId::kRoman 49 }; 50 51 /****************************************************************************** 52 * CMapTable class 53 ******************************************************************************/ 54 CMapTable::CMapTable(Header* header, ReadableFontData* data) 55 : SubTableContainerTable(header, data) { 56 } 57 58 CMapTable::~CMapTable() {} 59 60 CALLER_ATTACH CMapTable::CMap* CMapTable::GetCMap(const int32_t index) { 61 if (index < 0 || index > NumCMaps()) { 62 #ifndef SFNTLY_NO_EXCEPTION 63 throw IndexOutOfBoundException("Requested CMap index is out of bounds."); 64 #else 65 return NULL; 66 #endif 67 } 68 int32_t platform_id = PlatformId(index); 69 int32_t encoding_id = EncodingId(index); 70 CMapId cmap_id = NewCMapId(platform_id, encoding_id); 71 int32_t offset_ = Offset(index); 72 Ptr<FontDataTable::Builder> cmap_builder = 73 (CMap::Builder::GetBuilder(data_, offset_, cmap_id)); 74 if (!cmap_builder) { 75 #ifndef SFNTLY_NO_EXCEPTION 76 throw NoSuchElementException("Cannot find builder for requested CMap."); 77 #else 78 return NULL; 79 #endif 80 } 81 return down_cast<CMapTable::CMap*>(cmap_builder->Build()); 82 } 83 84 CALLER_ATTACH CMapTable::CMap* CMapTable::GetCMap(const int32_t platform_id, 85 const int32_t encoding_id) { 86 return GetCMap(NewCMapId(platform_id, encoding_id)); 87 } 88 89 CALLER_ATTACH CMapTable::CMap* 90 CMapTable::GetCMap(const CMapTable::CMapId cmap_id) { 91 CMapIdFilter id_filter(cmap_id); 92 CMapIterator cmap_iterator(this, &id_filter); 93 // There can only be one cmap with a particular CMapId 94 if (cmap_iterator.HasNext()) { 95 Ptr<CMapTable::CMap> cmap; 96 cmap.Attach(cmap_iterator.Next()); 97 return cmap.Detach(); 98 } 99 #ifndef SFNTLY_NO_EXCEPTION 100 throw NoSuchElementException(); 101 #else 102 return NULL; 103 #endif 104 } 105 106 int32_t CMapTable::Version() { 107 return data_->ReadUShort(Offset::kVersion); 108 } 109 110 int32_t CMapTable::NumCMaps() { 111 return data_->ReadUShort(Offset::kNumTables); 112 } 113 114 CMapTable::CMapId CMapTable::GetCMapId(int32_t index) { 115 return NewCMapId(PlatformId(index), EncodingId(index)); 116 } 117 118 int32_t CMapTable::PlatformId(int32_t index) { 119 return data_->ReadUShort(Offset::kEncodingRecordPlatformId + 120 OffsetForEncodingRecord(index)); 121 } 122 123 int32_t CMapTable::EncodingId(int32_t index) { 124 return data_->ReadUShort(Offset::kEncodingRecordEncodingId + 125 OffsetForEncodingRecord(index)); 126 } 127 128 int32_t CMapTable::Offset(int32_t index) { 129 return data_->ReadULongAsInt(Offset::kEncodingRecordOffset + 130 OffsetForEncodingRecord(index)); 131 } 132 133 int32_t CMapTable::OffsetForEncodingRecord(int32_t index) { 134 return Offset::kEncodingRecordStart + index * Offset::kEncodingRecordSize; 135 } 136 137 CMapTable::CMapId CMapTable::NewCMapId(int32_t platform_id, 138 int32_t encoding_id) { 139 CMapId result; 140 result.platform_id = platform_id; 141 result.encoding_id = encoding_id; 142 return result; 143 } 144 145 CMapTable::CMapId CMapTable::NewCMapId(const CMapId& obj) { 146 CMapId result; 147 result.platform_id = obj.platform_id; 148 result.encoding_id = obj.encoding_id; 149 return result; 150 } 151 152 /****************************************************************************** 153 * CMapTable::CMapIterator class 154 ******************************************************************************/ 155 CMapTable::CMapIterator::CMapIterator(CMapTable* table, 156 const CMapFilter* filter) 157 : table_index_(0), filter_(filter), table_(table) { 158 } 159 160 bool CMapTable::CMapIterator::HasNext() { 161 if (!filter_) { 162 if (table_index_ < table_->NumCMaps()) { 163 return true; 164 } 165 return false; 166 } 167 168 for (; table_index_ < table_->NumCMaps(); ++table_index_) { 169 if (filter_->accept(table_->GetCMapId(table_index_))) { 170 return true; 171 } 172 } 173 return false; 174 } 175 176 CALLER_ATTACH CMapTable::CMap* CMapTable::CMapIterator::Next() { 177 if (!HasNext()) { 178 #ifndef SFNTLY_NO_EXCEPTION 179 throw NoSuchElementException(); 180 #else 181 return NULL; 182 #endif 183 } 184 CMapPtr next_cmap; 185 next_cmap.Attach(table_->GetCMap(table_index_++)); 186 if (next_cmap == NULL) { 187 #ifndef SFNTLY_NO_EXCEPTION 188 throw NoSuchElementException("Error during the creation of the CMap"); 189 #else 190 return NULL; 191 #endif 192 } 193 return next_cmap.Detach(); 194 } 195 196 /****************************************************************************** 197 * CMapTable::CMapId class 198 ******************************************************************************/ 199 200 /****************************************************************************** 201 * CMapTable::CMapIdComparator class 202 ******************************************************************************/ 203 204 bool CMapTable::CMapIdComparator::operator()(const CMapId& lhs, 205 const CMapId& rhs) const { 206 return ((lhs.platform_id << 8 | lhs.encoding_id) > 207 (rhs.platform_id << 8 | rhs.encoding_id)); 208 } 209 210 /****************************************************************************** 211 * CMapTable::CMapIdFilter class 212 ******************************************************************************/ 213 CMapTable::CMapIdFilter::CMapIdFilter(const CMapId wanted_id) 214 : wanted_id_(wanted_id), 215 comparator_(NULL) { 216 } 217 218 CMapTable::CMapIdFilter::CMapIdFilter(const CMapId wanted_id, 219 const CMapIdComparator* comparator) 220 : wanted_id_(wanted_id), 221 comparator_(comparator) { 222 } 223 224 bool CMapTable::CMapIdFilter::accept(const CMapId& cmap_id) const { 225 if (!comparator_) 226 return wanted_id_ == cmap_id; 227 return (*comparator_)(wanted_id_, cmap_id); 228 } 229 230 /****************************************************************************** 231 * CMapTable::CMap class 232 ******************************************************************************/ 233 CMapTable::CMap::CMap(ReadableFontData* data, int32_t format, 234 const CMapId& cmap_id) 235 : SubTable(data), format_(format), cmap_id_(cmap_id) { 236 } 237 238 CMapTable::CMap::~CMap() { 239 } 240 241 /****************************************************************************** 242 * CMapTable::CMap::Builder class 243 ******************************************************************************/ 244 CMapTable::CMap::Builder::~Builder() { 245 } 246 247 CALLER_ATTACH CMapTable::CMap::Builder* 248 CMapTable::CMap::Builder::GetBuilder(ReadableFontData* data, int32_t offset, 249 const CMapId& cmap_id) { 250 // NOT IMPLEMENTED: Java enum value validation 251 int32_t format = data->ReadUShort(offset); 252 CMapBuilderPtr builder; 253 switch (format) { 254 case CMapFormat::kFormat0: 255 builder.Attach(CMapFormat0::Builder::NewInstance(data, offset, cmap_id)); 256 break; 257 case CMapFormat::kFormat2: 258 #if defined (SFNTLY_DEBUG_CMAP) 259 fprintf(stderr, "Requesting Format2 builder, but it's unsupported; " 260 "returning NULL\n"); 261 #endif 262 break; 263 case CMapFormat::kFormat4: 264 builder.Attach(CMapFormat4::Builder::NewInstance(data, offset, cmap_id)); 265 break; 266 default: 267 #ifdef SFNTLY_DEBUG_CMAP 268 fprintf(stderr, "Unknown builder format requested\n"); 269 #endif 270 break; 271 } 272 return builder.Detach(); 273 } 274 275 CALLER_ATTACH CMapTable::CMap::Builder* 276 CMapTable::CMap::Builder::GetBuilder(int32_t format, const CMapId& cmap_id) { 277 Ptr<CMapTable::CMap::Builder> builder; 278 switch (format) { 279 case CMapFormat::kFormat0: 280 builder.Attach(CMapFormat0::Builder::NewInstance(cmap_id)); 281 break; 282 case CMapFormat::kFormat2: 283 #if defined (SFNTLY_DEBUG_CMAP) 284 fprintf(stderr, "Requesting Format2 builder, but it's unsupported; " 285 "returning NULL\n"); 286 #endif 287 break; 288 case CMapFormat::kFormat4: 289 builder.Attach(CMapFormat4::Builder::NewInstance(cmap_id)); 290 break; 291 default: 292 #ifdef SFNTLY_DEBUG_CMAP 293 fprintf(stderr, "Unknown builder format requested\n"); 294 #endif 295 break; 296 } 297 return builder.Detach(); 298 } 299 300 CMapTable::CMap::Builder::Builder(ReadableFontData* data, 301 int32_t format, 302 const CMapId& cmap_id) 303 : SubTable::Builder(data), 304 format_(format), 305 cmap_id_(cmap_id), 306 language_(0) { 307 } 308 309 CMapTable::CMap::Builder::Builder(WritableFontData* data, 310 int32_t format, 311 const CMapId& cmap_id) 312 : SubTable::Builder(data), 313 format_(format), 314 cmap_id_(cmap_id), 315 language_(0) { 316 } 317 318 int32_t CMapTable::CMap::Builder::SubSerialize(WritableFontData* new_data) { 319 return InternalReadData()->CopyTo(new_data); 320 } 321 322 bool CMapTable::CMap::Builder::SubReadyToSerialize() { 323 return true; 324 } 325 326 int32_t CMapTable::CMap::Builder::SubDataSizeToSerialize() { 327 ReadableFontDataPtr read_data = InternalReadData(); 328 if (!read_data) 329 return 0; 330 return read_data->Length(); 331 } 332 333 void CMapTable::CMap::Builder::SubDataSet() { 334 // NOP 335 } 336 337 /****************************************************************************** 338 * CMapTable::CMapFormat0 339 ******************************************************************************/ 340 CMapTable::CMapFormat0::~CMapFormat0() { 341 } 342 343 int32_t CMapTable::CMapFormat0::Language() { 344 return 0; 345 } 346 347 int32_t CMapTable::CMapFormat0::GlyphId(int32_t character) { 348 if (character < 0 || character > 255) { 349 return CMapTable::NOTDEF; 350 } 351 return data_->ReadUByte(character + Offset::kFormat0GlyphIdArray); 352 } 353 354 CMapTable::CMapFormat0::CMapFormat0(ReadableFontData* data, 355 const CMapId& cmap_id) 356 : CMap(data, CMapFormat::kFormat0, cmap_id) { 357 } 358 359 CMapTable::CMap::CharacterIterator* CMapTable::CMapFormat0::Iterator() { 360 return new CMapTable::CMapFormat0::CharacterIterator(0, 0xff); 361 } 362 363 364 /****************************************************************************** 365 * CMapTable::CMapFormat0::CharacterIterator 366 ******************************************************************************/ 367 CMapTable::CMapFormat0::CharacterIterator::CharacterIterator(int32_t start, 368 int32_t end) 369 : character_(start), 370 max_character_(end) { 371 } 372 373 CMapTable::CMapFormat0::CharacterIterator::~CharacterIterator() {} 374 375 bool CMapTable::CMapFormat0::CharacterIterator::HasNext() { 376 return character_ < max_character_; 377 } 378 379 int32_t CMapTable::CMapFormat0::CharacterIterator::Next() { 380 if (HasNext()) 381 return character_++; 382 #ifndef SFNTLY_NO_EXCEPTION 383 throw NoSuchElementException("No more characters to iterate."); 384 #endif 385 return -1; 386 } 387 388 /****************************************************************************** 389 * CMapTable::CMapFormat0::Builder 390 ******************************************************************************/ 391 // static 392 CALLER_ATTACH CMapTable::CMapFormat0::Builder* 393 CMapTable::CMapFormat0::Builder::NewInstance(WritableFontData* data, 394 int32_t offset, 395 const CMapId& cmap_id) { 396 WritableFontDataPtr wdata; 397 if (data) { 398 wdata.Attach(down_cast<WritableFontData*>( 399 data->Slice(offset, 400 data->ReadUShort(offset + Offset::kFormat0Length)))); 401 } 402 return new Builder(wdata, CMapFormat::kFormat0, cmap_id); 403 } 404 405 // static 406 CALLER_ATTACH CMapTable::CMapFormat0::Builder* 407 CMapTable::CMapFormat0::Builder::NewInstance(ReadableFontData* data, 408 int32_t offset, 409 const CMapId& cmap_id) { 410 ReadableFontDataPtr rdata; 411 if (data) { 412 rdata.Attach(down_cast<ReadableFontData*>( 413 data->Slice(offset, 414 data->ReadUShort(offset + Offset::kFormat0Length)))); 415 } 416 return new Builder(rdata, CMapFormat::kFormat0, cmap_id); 417 } 418 419 // static 420 CALLER_ATTACH CMapTable::CMapFormat0::Builder* 421 CMapTable::CMapFormat0::Builder::NewInstance(const CMapId& cmap_id) { 422 return new Builder(cmap_id); 423 } 424 425 // Always call NewInstance instead of the constructor for creating a new builder 426 // object! This refactoring avoids memory leaks when slicing the font data. 427 CMapTable::CMapFormat0::Builder::Builder(WritableFontData* data, int32_t offset, 428 const CMapId& cmap_id) 429 : CMapTable::CMap::Builder(data, CMapFormat::kFormat0, cmap_id) { 430 UNREFERENCED_PARAMETER(offset); 431 } 432 433 CMapTable::CMapFormat0::Builder::Builder( 434 ReadableFontData* data, 435 int32_t offset, 436 const CMapId& cmap_id) 437 : CMapTable::CMap::Builder(data, CMapFormat::kFormat0, cmap_id) { 438 UNREFERENCED_PARAMETER(offset); 439 } 440 441 CMapTable::CMapFormat0::Builder::Builder(const CMapId& cmap_id) 442 : CMap::Builder(reinterpret_cast<ReadableFontData*>(NULL), 443 CMapFormat::kFormat0, 444 cmap_id) { 445 } 446 447 CMapTable::CMapFormat0::Builder::~Builder() { 448 } 449 450 CALLER_ATTACH FontDataTable* 451 CMapTable::CMapFormat0::Builder::SubBuildTable(ReadableFontData* data) { 452 FontDataTablePtr table = new CMapFormat0(data, cmap_id()); 453 return table.Detach(); 454 } 455 456 /****************************************************************************** 457 * CMapTable::CMapFormat2 458 ******************************************************************************/ 459 CMapTable::CMapFormat2::~CMapFormat2() { 460 } 461 462 int32_t CMapTable::CMapFormat2::Language() { 463 return 0; 464 } 465 466 int32_t CMapTable::CMapFormat2::GlyphId(int32_t character) { 467 if (character > 0xffff) { 468 return CMapTable::NOTDEF; 469 } 470 471 uint32_t c = ToBE32(character); 472 byte_t high_byte = (c >> 8) & 0xff; 473 byte_t low_byte = c & 0xff; 474 int32_t offset = SubHeaderOffset(high_byte); 475 476 if (offset == 0) { 477 low_byte = high_byte; 478 high_byte = 0; 479 } 480 481 int32_t first_code = FirstCode(high_byte); 482 int32_t entry_count = EntryCount(high_byte); 483 484 if (low_byte < first_code || low_byte >= first_code + entry_count) { 485 return CMapTable::NOTDEF; 486 } 487 488 int32_t id_range_offset = IdRangeOffset(high_byte); 489 490 // position of idRangeOffset + value of idRangeOffset + index for low byte 491 // = firstcode 492 int32_t p_location = (offset + Offset::kFormat2SubHeader_idRangeOffset) + 493 id_range_offset + 494 (low_byte - first_code) * DataSize::kUSHORT; 495 int p = data_->ReadUShort(p_location); 496 if (p == 0) { 497 return CMapTable::NOTDEF; 498 } 499 500 if (offset == 0) { 501 return p; 502 } 503 int id_delta = IdDelta(high_byte); 504 return (p + id_delta) % 65536; 505 } 506 507 int32_t CMapTable::CMapFormat2::BytesConsumed(int32_t character) { 508 uint32_t c = ToBE32(character); 509 int32_t high_byte = (c >> 8) & 0xff; 510 int32_t offset = SubHeaderOffset(high_byte); 511 return (offset == 0) ? 1 : 2; 512 } 513 514 CMapTable::CMapFormat2::CMapFormat2(ReadableFontData* data, 515 const CMapId& cmap_id) 516 : CMap(data, CMapFormat::kFormat2, cmap_id) { 517 } 518 519 int32_t CMapTable::CMapFormat2::SubHeaderOffset(int32_t sub_header_index) { 520 return data_->ReadUShort(Offset::kFormat2SubHeaderKeys + 521 sub_header_index * DataSize::kUSHORT); 522 } 523 524 int32_t CMapTable::CMapFormat2::FirstCode(int32_t sub_header_index) { 525 int32_t sub_header_offset = SubHeaderOffset(sub_header_index); 526 return data_->ReadUShort(sub_header_offset + 527 Offset::kFormat2SubHeaderKeys + 528 Offset::kFormat2SubHeader_firstCode); 529 } 530 531 int32_t CMapTable::CMapFormat2::EntryCount(int32_t sub_header_index) { 532 int32_t sub_header_offset = SubHeaderOffset(sub_header_index); 533 return data_->ReadUShort(sub_header_offset + 534 Offset::kFormat2SubHeaderKeys + 535 Offset::kFormat2SubHeader_entryCount); 536 } 537 538 int32_t CMapTable::CMapFormat2::IdRangeOffset(int32_t sub_header_index) { 539 int32_t sub_header_offset = SubHeaderOffset(sub_header_index); 540 return data_->ReadUShort(sub_header_offset + 541 Offset::kFormat2SubHeaderKeys + 542 Offset::kFormat2SubHeader_idRangeOffset); 543 } 544 545 int32_t CMapTable::CMapFormat2::IdDelta(int32_t sub_header_index) { 546 int32_t sub_header_offset = SubHeaderOffset(sub_header_index); 547 return data_->ReadUShort(sub_header_offset + 548 Offset::kFormat2SubHeaderKeys + 549 Offset::kFormat2SubHeader_idDelta); 550 } 551 552 CMapTable::CMap::CharacterIterator* CMapTable::CMapFormat2::Iterator() { 553 // UNIMPLEMENTED 554 return NULL; 555 } 556 557 /****************************************************************************** 558 * CMapTable::CMapFormat2::Builder 559 ******************************************************************************/ 560 CMapTable::CMapFormat2::Builder::Builder(WritableFontData* data, 561 int32_t offset, 562 const CMapId& cmap_id) 563 : CMapTable::CMap::Builder(data ? down_cast<WritableFontData*>( 564 data->Slice(offset, data->ReadUShort( 565 offset + Offset::kFormat0Length))) 566 : reinterpret_cast<WritableFontData*>(NULL), 567 CMapFormat::kFormat2, cmap_id) { 568 // TODO(arthurhsu): FIXIT: heavy lifting and leak, need fix. 569 } 570 571 CMapTable::CMapFormat2::Builder::Builder(ReadableFontData* data, 572 int32_t offset, 573 const CMapId& cmap_id) 574 : CMapTable::CMap::Builder(data ? down_cast<ReadableFontData*>( 575 data->Slice(offset, data->ReadUShort( 576 offset + Offset::kFormat0Length))) 577 : reinterpret_cast<ReadableFontData*>(NULL), 578 CMapFormat::kFormat2, cmap_id) { 579 // TODO(arthurhsu): FIXIT: heavy lifting and leak, need fix. 580 } 581 582 CMapTable::CMapFormat2::Builder::~Builder() { 583 } 584 585 CALLER_ATTACH FontDataTable* 586 CMapTable::CMapFormat2::Builder::SubBuildTable(ReadableFontData* data) { 587 FontDataTablePtr table = new CMapFormat2(data, cmap_id()); 588 return table.Detach(); 589 } 590 591 /****************************************************************************** 592 * CMapTable::CMapFormat4 593 ******************************************************************************/ 594 CMapTable::CMapFormat4::CMapFormat4(ReadableFontData* data, 595 const CMapId& cmap_id) 596 : CMap(data, CMapFormat::kFormat4, cmap_id), 597 seg_count_(SegCount(data)), 598 start_code_offset_(StartCodeOffset(seg_count_)), 599 id_delta_offset_(IdDeltaOffset(seg_count_)), 600 glyph_id_array_offset_(GlyphIdArrayOffset(seg_count_)) { 601 } 602 603 CMapTable::CMapFormat4::~CMapFormat4() { 604 } 605 606 int32_t CMapTable::CMapFormat4::GlyphId(int32_t character) { 607 int32_t segment = data_->SearchUShort(StartCodeOffset(seg_count_), 608 DataSize::kUSHORT, 609 Offset::kFormat4EndCount, 610 DataSize::kUSHORT, 611 seg_count_, 612 character); 613 if (segment == -1) { 614 return CMapTable::NOTDEF; 615 } 616 int32_t start_code = StartCode(segment); 617 return RetrieveGlyphId(segment, start_code, character); 618 } 619 620 int32_t CMapTable::CMapFormat4::RetrieveGlyphId(int32_t segment, 621 int32_t start_code, 622 int32_t character) { 623 if (character < start_code) { 624 return CMapTable::NOTDEF; 625 } 626 int32_t id_range_offset = IdRangeOffset(segment); 627 if (id_range_offset == 0) { 628 return (character + IdDelta(segment)) % 65536; 629 } 630 return data_->ReadUShort(id_range_offset + 631 IdRangeOffsetLocation(segment) + 632 2 * (character - start_code)); 633 } 634 635 int32_t CMapTable::CMapFormat4::seg_count() { 636 return seg_count_; 637 } 638 639 int32_t CMapTable::CMapFormat4::Length() { 640 return Length(data_); 641 } 642 643 int32_t CMapTable::CMapFormat4::StartCode(int32_t segment) { 644 if (!IsValidIndex(segment)) { 645 return -1; 646 } 647 return StartCode(data_.p_, seg_count_, segment); 648 } 649 650 // static 651 int32_t CMapTable::CMapFormat4::Language(ReadableFontData* data) { 652 int32_t language = data->ReadUShort(Offset::kFormat4Language); 653 return language; 654 } 655 656 // static 657 int32_t CMapTable::CMapFormat4::Length(ReadableFontData* data) { 658 int32_t length = data->ReadUShort(Offset::kFormat4Length); 659 return length; 660 } 661 662 // static 663 int32_t CMapTable::CMapFormat4::SegCount(ReadableFontData* data) { 664 int32_t seg_count = data->ReadUShort(Offset::kFormat4SegCountX2) / 2; 665 return seg_count; 666 } 667 668 // static 669 int32_t CMapTable::CMapFormat4::StartCode(ReadableFontData* data, 670 int32_t seg_count, 671 int32_t index) { 672 int32_t start_code = data->ReadUShort(StartCodeOffset(seg_count) + 673 index * DataSize::kUSHORT); 674 return start_code; 675 } 676 677 // static 678 int32_t CMapTable::CMapFormat4::StartCodeOffset(int32_t seg_count) { 679 int32_t start_code_offset = Offset::kFormat4EndCount + 680 (seg_count + 1) * DataSize::kUSHORT; 681 return start_code_offset; 682 } 683 684 // static 685 int32_t CMapTable::CMapFormat4::EndCode(ReadableFontData* data, 686 int32_t seg_count, 687 int32_t index) { 688 UNREFERENCED_PARAMETER(seg_count); 689 int32_t end_code = data->ReadUShort(Offset::kFormat4EndCount + 690 index * DataSize::kUSHORT); 691 return end_code; 692 } 693 694 // static 695 int32_t CMapTable::CMapFormat4::IdDelta(ReadableFontData* data, 696 int32_t seg_count, 697 int32_t index) { 698 int32_t id_delta = data->ReadUShort(IdDeltaOffset(seg_count) + 699 index * DataSize::kUSHORT); 700 return id_delta; 701 } 702 703 // static 704 int32_t CMapTable::CMapFormat4::IdDeltaOffset(int32_t seg_count) { 705 int32_t id_delta_offset = 706 Offset::kFormat4EndCount + (2 * seg_count + 1) * DataSize::kUSHORT; 707 return id_delta_offset; 708 } 709 710 // static 711 int32_t CMapTable::CMapFormat4::IdRangeOffset(ReadableFontData* data, 712 int32_t seg_count, 713 int32_t index) { 714 int32_t id_range_offset = 715 data->ReadUShort(IdRangeOffsetOffset(seg_count) 716 + index * DataSize::kUSHORT); 717 return id_range_offset; 718 } 719 720 // static 721 int32_t CMapTable::CMapFormat4::IdRangeOffsetOffset(int32_t seg_count) { 722 int32_t id_range_offset_offset = 723 Offset::kFormat4EndCount + (2 * seg_count + 1) * DataSize::kUSHORT + 724 seg_count * DataSize::kSHORT; 725 return id_range_offset_offset; 726 } 727 728 // static 729 int32_t CMapTable::CMapFormat4::GlyphIdArrayOffset(int32_t seg_count) { 730 int32_t glyph_id_array_offset = 731 Offset::kFormat4EndCount + (3 * seg_count + 1) * DataSize::kUSHORT + 732 seg_count * DataSize::kSHORT; 733 return glyph_id_array_offset; 734 } 735 736 int32_t CMapTable::CMapFormat4::EndCode(int32_t segment) { 737 if (IsValidIndex(segment)) { 738 return EndCode(data_, seg_count_, segment); 739 } 740 #if defined (SFNTLY_NO_EXCEPTION) 741 return -1; 742 #else 743 throw IllegalArgumentException(); 744 #endif 745 } 746 747 bool CMapTable::CMapFormat4::IsValidIndex(int32_t segment) { 748 if (segment < 0 || segment >= seg_count_) { 749 #if defined (SFNTLY_NO_EXCEPTION) 750 return false; 751 #else 752 throw IllegalArgumentException(); 753 #endif 754 } 755 return true; 756 } 757 758 int32_t CMapTable::CMapFormat4::IdDelta(int32_t segment) { 759 if (IsValidIndex(segment)) 760 return IdDelta(data_, seg_count_, segment); 761 return -1; 762 } 763 764 int32_t CMapTable::CMapFormat4::IdRangeOffset(int32_t segment) { 765 if (IsValidIndex(segment)) 766 return data_->ReadUShort(IdRangeOffsetLocation(segment)); 767 return -1; 768 } 769 770 int32_t CMapTable::CMapFormat4::IdRangeOffsetLocation(int32_t segment) { 771 if (IsValidIndex(segment)) 772 return IdRangeOffsetOffset(seg_count_) + segment * DataSize::kUSHORT; 773 return -1; 774 } 775 776 int32_t CMapTable::CMapFormat4::GlyphIdArray(int32_t index) { 777 return data_->ReadUShort(glyph_id_array_offset_ + index * DataSize::kUSHORT); 778 } 779 780 int32_t CMapTable::CMapFormat4::Language() { 781 return Language(data_); 782 } 783 784 785 CMapTable::CMap::CharacterIterator* CMapTable::CMapFormat4::Iterator() { 786 return new CharacterIterator(this); 787 } 788 789 /****************************************************************************** 790 * CMapTable::CMapFormat4::CharacterIterator class 791 ******************************************************************************/ 792 CMapTable::CMapFormat4::CharacterIterator::CharacterIterator( 793 CMapFormat4* parent) 794 : parent_(parent), 795 segment_index_(0), 796 first_char_in_segment_(-1), 797 last_char_in_segment_(-1), 798 next_char_(-1), 799 next_char_set_(false) { 800 } 801 802 bool CMapTable::CMapFormat4::CharacterIterator::HasNext() { 803 if (next_char_set_) 804 return true; 805 while (segment_index_ < parent_->seg_count_) { 806 if (first_char_in_segment_ < 0) { 807 first_char_in_segment_ = parent_->StartCode(segment_index_); 808 last_char_in_segment_ = parent_->EndCode(segment_index_); 809 next_char_ = first_char_in_segment_; 810 next_char_set_ = true; 811 return true; 812 } 813 if (next_char_ < last_char_in_segment_) { 814 next_char_++; 815 next_char_set_ = true; 816 return true; 817 } 818 segment_index_++; 819 first_char_in_segment_ = -1; 820 } 821 return false; 822 } 823 824 int32_t CMapTable::CMapFormat4::CharacterIterator::Next() { 825 if (!next_char_set_) { 826 if (!HasNext()) { 827 #if defined (SFNTLY_NO_EXCEPTION) 828 return -1; 829 #else 830 throw NoSuchElementException("No more characters to iterate."); 831 #endif 832 } 833 } 834 next_char_set_ = false; 835 return next_char_; 836 } 837 838 /****************************************************************************** 839 * CMapTable::CMapFormat4::Builder::Segment class 840 ******************************************************************************/ 841 CMapTable::CMapFormat4::Builder::Segment::Segment() {} 842 843 CMapTable::CMapFormat4::Builder::Segment::Segment(Segment* other) 844 : start_count_(other->start_count_), 845 end_count_(other->end_count_), 846 id_delta_(other->id_delta_), 847 id_range_offset_(other->id_range_offset_) { 848 } 849 850 CMapTable::CMapFormat4::Builder::Segment::Segment(int32_t start_count, 851 int32_t end_count, 852 int32_t id_delta, 853 int32_t id_range_offset) 854 : start_count_(start_count), 855 end_count_(end_count), 856 id_delta_(id_delta), 857 id_range_offset_(id_range_offset) { 858 } 859 860 CMapTable::CMapFormat4::Builder::Segment::~Segment() {} 861 862 int32_t CMapTable::CMapFormat4::Builder::Segment::start_count() { 863 return start_count_; 864 } 865 866 void 867 CMapTable::CMapFormat4::Builder::Segment::set_start_count(int32_t start_count) { 868 start_count_ = start_count; 869 } 870 871 int32_t CMapTable::CMapFormat4::Builder::Segment::end_count() { 872 return end_count_; 873 } 874 875 void 876 CMapTable::CMapFormat4::Builder::Segment::set_end_count(int32_t end_count) { 877 end_count_ = end_count; 878 } 879 880 int32_t CMapTable::CMapFormat4::Builder::Segment::id_delta() { 881 return id_delta_; 882 } 883 884 void 885 CMapTable::CMapFormat4::Builder::Segment::set_id_delta(int32_t id_delta) { 886 id_delta_ = id_delta; 887 } 888 889 int32_t CMapTable::CMapFormat4::Builder::Segment::id_range_offset() { 890 return id_range_offset_; 891 } 892 893 void 894 CMapTable::CMapFormat4::Builder::Segment:: 895 set_id_range_offset(int32_t id_range_offset) { 896 id_range_offset_ = id_range_offset; 897 } 898 899 // static 900 CALLER_ATTACH SegmentList* 901 CMapTable::CMapFormat4::Builder::Segment::DeepCopy(SegmentList* original) { 902 SegmentList* list = new SegmentList; 903 for (SegmentList::iterator it = original->begin(), 904 e = original->end(); it != e; ++it) { 905 list->push_back(*it); 906 } 907 return list; 908 } 909 910 /****************************************************************************** 911 * CMapTable::CMapFormat4::Builder class 912 ******************************************************************************/ 913 CALLER_ATTACH CMapTable::CMapFormat4::Builder* 914 CMapTable::CMapFormat4::Builder::NewInstance(ReadableFontData* data, 915 int32_t offset, 916 const CMapId& cmap_id) { 917 ReadableFontDataPtr rdata; 918 if (data) { 919 rdata.Attach 920 (down_cast<ReadableFontData*> 921 (data->Slice(offset, 922 data->ReadUShort(offset + Offset::kFormat4Length)))); 923 } 924 return new Builder(rdata, CMapFormat::kFormat4, cmap_id); 925 } 926 927 CALLER_ATTACH CMapTable::CMapFormat4::Builder* 928 CMapTable::CMapFormat4::Builder::NewInstance(WritableFontData* data, 929 int32_t offset, 930 const CMapId& cmap_id) { 931 WritableFontDataPtr wdata; 932 if (data) { 933 wdata.Attach 934 (down_cast<WritableFontData*> 935 (data->Slice(offset, 936 data->ReadUShort(offset + Offset::kFormat4Length)))); 937 } 938 return new Builder(wdata, CMapFormat::kFormat4, cmap_id); 939 } 940 941 CALLER_ATTACH CMapTable::CMapFormat4::Builder* 942 CMapTable::CMapFormat4::Builder::NewInstance(const CMapId& cmap_id) { 943 return new Builder(cmap_id); 944 } 945 946 CMapTable::CMapFormat4::Builder::Builder(ReadableFontData* data, int32_t offset, 947 const CMapId& cmap_id) 948 : CMap::Builder(data, CMapFormat::kFormat4, cmap_id) { 949 UNREFERENCED_PARAMETER(offset); 950 } 951 952 CMapTable::CMapFormat4::Builder::Builder(WritableFontData* data, int32_t offset, 953 const CMapId& cmap_id) 954 : CMap::Builder(data, CMapFormat::kFormat4, cmap_id) { 955 UNREFERENCED_PARAMETER(offset); 956 } 957 958 CMapTable::CMapFormat4::Builder::Builder(SegmentList* segments, 959 IntegerList* glyph_id_array, 960 const CMapId& cmap_id) 961 : CMap::Builder(reinterpret_cast<ReadableFontData*>(NULL), 962 CMapFormat::kFormat4, cmap_id), 963 segments_(segments->begin(), segments->end()), 964 glyph_id_array_(glyph_id_array->begin(), glyph_id_array->end()) { 965 set_model_changed(); 966 } 967 968 CMapTable::CMapFormat4::Builder::Builder(const CMapId& cmap_id) 969 : CMap::Builder(reinterpret_cast<ReadableFontData*>(NULL), 970 CMapFormat::kFormat4, cmap_id) { 971 } 972 973 CMapTable::CMapFormat4::Builder::~Builder() {} 974 975 void CMapTable::CMapFormat4::Builder::Initialize(ReadableFontData* data) { 976 if (data == NULL || data->Length() == 0) 977 return; 978 979 // build segments 980 int32_t seg_count = CMapFormat4::SegCount(data); 981 for (int32_t index = 0; index < seg_count; ++index) { 982 Ptr<Segment> segment = new Segment; 983 segment->set_start_count(CMapFormat4::StartCode(data, seg_count, index)); 984 #if defined SFNTLY_DEBUG_CMAP 985 fprintf(stderr, "Segment %d; start %d\n", index, segment->start_count()); 986 #endif 987 segment->set_end_count(CMapFormat4::EndCode(data, seg_count, index)); 988 segment->set_id_delta(CMapFormat4::IdDelta(data, seg_count, index)); 989 segment->set_id_range_offset(CMapFormat4::IdRangeOffset(data, 990 seg_count, 991 index)); 992 segments_.push_back(segment); 993 } 994 995 // build glyph id array 996 int32_t glyph_id_array_offset = CMapFormat4::GlyphIdArrayOffset(seg_count); 997 int32_t glyph_id_array_length = 998 (CMapFormat4::Length(data) - glyph_id_array_offset) 999 / DataSize::kUSHORT; 1000 fprintf(stderr, "id array size %d\n", glyph_id_array_length); 1001 for (int32_t i = 0; i < glyph_id_array_length; i += DataSize::kUSHORT) { 1002 glyph_id_array_.push_back(data->ReadUShort(glyph_id_array_offset + i)); 1003 } 1004 } 1005 1006 SegmentList* CMapTable::CMapFormat4::Builder::segments() { 1007 if (segments_.empty()) { 1008 Initialize(InternalReadData()); 1009 set_model_changed(); 1010 } 1011 return &segments_; 1012 } 1013 1014 void CMapTable::CMapFormat4::Builder::set_segments(SegmentList* segments) { 1015 segments_.assign(segments->begin(), segments->end()); 1016 set_model_changed(); 1017 } 1018 1019 IntegerList* CMapTable::CMapFormat4::Builder::glyph_id_array() { 1020 if (glyph_id_array_.empty()) { 1021 Initialize(InternalReadData()); 1022 set_model_changed(); 1023 } 1024 return &glyph_id_array_; 1025 } 1026 1027 void CMapTable::CMapFormat4::Builder:: 1028 set_glyph_id_array(IntegerList* glyph_id_array) { 1029 glyph_id_array_.assign(glyph_id_array->begin(), glyph_id_array->end()); 1030 set_model_changed(); 1031 } 1032 1033 CALLER_ATTACH FontDataTable* 1034 CMapTable::CMapFormat4::Builder::SubBuildTable(ReadableFontData* data) { 1035 FontDataTablePtr table = new CMapFormat4(data, cmap_id()); 1036 return table.Detach(); 1037 } 1038 1039 void CMapTable::CMapFormat4::Builder::SubDataSet() { 1040 segments_.clear(); 1041 glyph_id_array_.clear(); 1042 set_model_changed(); 1043 } 1044 1045 int32_t CMapTable::CMapFormat4::Builder::SubDataSizeToSerialize() { 1046 if (!model_changed()) { 1047 return CMap::Builder::SubDataSizeToSerialize(); 1048 } 1049 int32_t size = Offset::kFormat4FixedSize + segments_.size() 1050 * (3 * DataSize::kUSHORT + DataSize::kSHORT) 1051 + glyph_id_array_.size() * DataSize::kSHORT; 1052 return size; 1053 } 1054 1055 bool CMapTable::CMapFormat4::Builder::SubReadyToSerialize() { 1056 if (!model_changed()) { 1057 return CMap::Builder::SubReadyToSerialize(); 1058 } 1059 if (!segments()->empty()) { 1060 return true; 1061 } 1062 return false; 1063 } 1064 1065 int32_t 1066 CMapTable::CMapFormat4::Builder::SubSerialize(WritableFontData* new_data) { 1067 if (!model_changed()) { 1068 return CMap::Builder::SubSerialize(new_data); 1069 } 1070 int32_t index = 0; 1071 index += new_data->WriteUShort(index, CMapFormat::kFormat4); 1072 index += DataSize::kUSHORT; // length - write this at the end 1073 index += new_data->WriteUShort(index, language()); 1074 1075 int32_t seg_count = segments_.size(); 1076 index += new_data->WriteUShort(index, seg_count * 2); 1077 int32_t log2_seg_count = FontMath::Log2(seg_count); 1078 int32_t search_range = 1 << (log2_seg_count + 1); 1079 index += new_data->WriteUShort(index, search_range); 1080 int32_t entry_selector = log2_seg_count; 1081 index += new_data->WriteUShort(index, entry_selector); 1082 int32_t range_shift = 2 * seg_count - search_range; 1083 index += new_data->WriteUShort(index, range_shift); 1084 1085 for (int32_t i = 0; i < seg_count; ++i) { 1086 index += new_data->WriteUShort(index, segments_[i]->end_count()); 1087 } 1088 index += new_data->WriteUShort(index, 0); // reserved ushort 1089 for (int32_t i = 0; i < seg_count; ++i) { 1090 #if defined SFNTLY_DEBUG_CMAP 1091 fprintf(stderr, "Segment %d; start %d\n", i, segments_[i]->start_count()); 1092 #endif 1093 index += new_data->WriteUShort(index, segments_[i]->start_count()); 1094 } 1095 for (int32_t i = 0; i < seg_count; ++i) { 1096 index += new_data->WriteShort(index, segments_[i]->id_delta()); 1097 } 1098 for (int32_t i = 0; i < seg_count; ++i) { 1099 index += new_data->WriteUShort(index, segments_[i]->id_range_offset()); 1100 } 1101 1102 #if defined SFNTLY_DEBUG_CMAP 1103 fprintf(stderr, "Glyph id array size %lu\n", glyph_id_array_.size()); 1104 #endif 1105 for (size_t i = 0; i < glyph_id_array_.size(); ++i) { 1106 index += new_data->WriteUShort(index, glyph_id_array_[i]); 1107 } 1108 1109 new_data->WriteUShort(Offset::kFormat4Length, index); 1110 return index; 1111 } 1112 1113 /****************************************************************************** 1114 * CMapTable::Builder class 1115 ******************************************************************************/ 1116 CMapTable::Builder::Builder(Header* header, WritableFontData* data) 1117 : SubTableContainerTable::Builder(header, data), version_(0) { 1118 } 1119 1120 CMapTable::Builder::Builder(Header* header, ReadableFontData* data) 1121 : SubTableContainerTable::Builder(header, data), version_(0) { 1122 } 1123 1124 CMapTable::Builder::~Builder() { 1125 } 1126 1127 int32_t CMapTable::Builder::SubSerialize(WritableFontData* new_data) { 1128 int32_t size = new_data->WriteUShort(CMapTable::Offset::kVersion, 1129 version_); 1130 size += new_data->WriteUShort(CMapTable::Offset::kNumTables, 1131 GetCMapBuilders()->size()); 1132 1133 int32_t index_offset = size; 1134 size += GetCMapBuilders()->size() * CMapTable::Offset::kEncodingRecordSize; 1135 for (CMapBuilderMap::iterator it = GetCMapBuilders()->begin(), 1136 e = GetCMapBuilders()->end(); it != e; ++it) { 1137 CMapBuilderPtr b = it->second; 1138 // header entry 1139 index_offset += new_data->WriteUShort(index_offset, b->platform_id()); 1140 index_offset += new_data->WriteUShort(index_offset, b->encoding_id()); 1141 index_offset += new_data->WriteULong(index_offset, size); 1142 1143 // cmap 1144 FontDataPtr slice; 1145 slice.Attach(new_data->Slice(size)); 1146 size += b->SubSerialize(down_cast<WritableFontData*>(slice.p_)); 1147 } 1148 return size; 1149 } 1150 1151 bool CMapTable::Builder::SubReadyToSerialize() { 1152 if (GetCMapBuilders()->empty()) 1153 return false; 1154 1155 // check each table 1156 for (CMapBuilderMap::iterator it = GetCMapBuilders()->begin(), 1157 e = GetCMapBuilders()->end(); it != e; ++it) { 1158 if (!it->second->SubReadyToSerialize()) 1159 return false; 1160 } 1161 return true; 1162 } 1163 1164 int32_t CMapTable::Builder::SubDataSizeToSerialize() { 1165 if (GetCMapBuilders()->empty()) 1166 return 0; 1167 1168 bool variable = false; 1169 int32_t size = CMapTable::Offset::kEncodingRecordStart + 1170 GetCMapBuilders()->size() * CMapTable::Offset::kEncodingRecordSize; 1171 1172 // calculate size of each table 1173 for (CMapBuilderMap::iterator it = GetCMapBuilders()->begin(), 1174 e = GetCMapBuilders()->end(); it != e; ++it) { 1175 int32_t cmap_size = it->second->SubDataSizeToSerialize(); 1176 size += abs(cmap_size); 1177 variable |= cmap_size <= 0; 1178 } 1179 return variable ? -size : size; 1180 } 1181 1182 void CMapTable::Builder::SubDataSet() { 1183 GetCMapBuilders()->clear(); 1184 Table::Builder::set_model_changed(); 1185 } 1186 1187 CALLER_ATTACH FontDataTable* 1188 CMapTable::Builder::SubBuildTable(ReadableFontData* data) { 1189 FontDataTablePtr table = new CMapTable(header(), data); 1190 return table.Detach(); 1191 } 1192 1193 CALLER_ATTACH CMapTable::Builder* 1194 CMapTable::Builder::CreateBuilder(Header* header, 1195 WritableFontData* data) { 1196 Ptr<CMapTable::Builder> builder; 1197 builder = new CMapTable::Builder(header, data); 1198 return builder.Detach(); 1199 } 1200 1201 // static 1202 CALLER_ATTACH CMapTable::CMap::Builder* 1203 CMapTable::Builder::CMapBuilder(ReadableFontData* data, int32_t index) { 1204 if (index < 0 || index > NumCMaps(data)) { 1205 #if !defined (SFNTLY_NO_EXCEPTION) 1206 throw IndexOutOfBoundException( 1207 "CMap table is outside of the bounds of the known tables."); 1208 #endif 1209 return NULL; 1210 } 1211 1212 int32_t platform_id = data->ReadUShort(Offset::kEncodingRecordPlatformId + 1213 OffsetForEncodingRecord(index)); 1214 int32_t encoding_id = data->ReadUShort(Offset::kEncodingRecordEncodingId + 1215 OffsetForEncodingRecord(index)); 1216 int32_t offset = data->ReadULongAsInt(Offset::kEncodingRecordOffset + 1217 OffsetForEncodingRecord(index)); 1218 return CMap::Builder::GetBuilder(data, offset, 1219 NewCMapId(platform_id, encoding_id)); 1220 } 1221 1222 // static 1223 int32_t CMapTable::Builder::NumCMaps(ReadableFontData* data) { 1224 if (data == NULL) { 1225 return 0; 1226 } 1227 return data->ReadUShort(Offset::kNumTables); 1228 } 1229 1230 int32_t CMapTable::Builder::NumCMaps() { 1231 return GetCMapBuilders()->size(); 1232 } 1233 1234 void CMapTable::Builder::Initialize(ReadableFontData* data) { 1235 int32_t num_cmaps = NumCMaps(data); 1236 for (int32_t i = 0; i < num_cmaps; ++i) { 1237 CMapTable::CMap::Builder* cmap_builder = CMapBuilder(data, i); 1238 if (!cmap_builder) 1239 continue; 1240 cmap_builders_[cmap_builder->cmap_id()] = cmap_builder; 1241 } 1242 } 1243 1244 CMapTable::CMap::Builder* CMapTable::Builder::NewCMapBuilder( 1245 const CMapId& cmap_id, 1246 ReadableFontData* data) { 1247 Ptr<WritableFontData> wfd; 1248 wfd.Attach(WritableFontData::CreateWritableFontData(data->Size())); 1249 data->CopyTo(wfd.p_); 1250 CMapTable::CMapBuilderPtr builder; 1251 builder.Attach(CMap::Builder::GetBuilder(wfd.p_, 0, cmap_id)); 1252 CMapBuilderMap* cmap_builders = CMapTable::Builder::GetCMapBuilders(); 1253 cmap_builders->insert(std::make_pair(cmap_id, builder.p_)); 1254 return builder.Detach(); 1255 } 1256 1257 CMapTable::CMap::Builder* 1258 CMapTable::Builder::NewCMapBuilder(int32_t format, const CMapId& cmap_id) { 1259 Ptr<CMapTable::CMap::Builder> cmap_builder; 1260 cmap_builder.Attach(CMap::Builder::GetBuilder(format, cmap_id)); 1261 CMapBuilderMap* cmap_builders = CMapTable::Builder::GetCMapBuilders(); 1262 cmap_builders->insert(std::make_pair(cmap_id, cmap_builder.p_)); 1263 return cmap_builder.Detach(); 1264 } 1265 1266 CMapTable::CMap::Builder* 1267 CMapTable::Builder::CMapBuilder(const CMapId& cmap_id) { 1268 CMapBuilderMap* cmap_builders = this->GetCMapBuilders(); 1269 CMapBuilderMap::iterator builder = cmap_builders->find(cmap_id); 1270 if (builder != cmap_builders->end()) 1271 return builder->second; 1272 #ifndef SFNTLY_NO_EXCEPTION 1273 throw NoSuchElementException("No builder found for cmap_id"); 1274 #else 1275 return NULL; 1276 #endif 1277 } 1278 1279 CMapTable::CMapBuilderMap* CMapTable::Builder::GetCMapBuilders() { 1280 if (cmap_builders_.empty()) { 1281 Initialize(InternalReadData()); 1282 set_model_changed(); 1283 } 1284 return &cmap_builders_; 1285 } 1286 1287 } // namespace sfntly 1288