/* * Copyright 2011 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "sfntly/font.h" #include #include #include #include #include #include #include #include "sfntly/data/font_input_stream.h" #include "sfntly/font_factory.h" #include "sfntly/math/fixed1616.h" #include "sfntly/math/font_math.h" #include "sfntly/port/exception_type.h" #include "sfntly/table/core/font_header_table.h" #include "sfntly/table/core/horizontal_device_metrics_table.h" #include "sfntly/table/core/horizontal_header_table.h" #include "sfntly/table/core/horizontal_metrics_table.h" #include "sfntly/table/core/maximum_profile_table.h" #include "sfntly/table/truetype/loca_table.h" #include "sfntly/tag.h" namespace sfntly { namespace { const int32_t kSFNTVersionMajor = 1; const int32_t kSFNTVersionMinor = 0; const int32_t kMaxTableSize = 200 * 1024 * 1024; } // namespace /****************************************************************************** * Font class ******************************************************************************/ Font::~Font() {} bool Font::HasTable(int32_t tag) const { return tables_.find(tag) != tables_.end(); } Table* Font::GetTable(int32_t tag) { if (!HasTable(tag)) return NULL; return tables_[tag]; } const TableMap* Font::GetTableMap() { return &tables_; } void Font::Serialize(OutputStream* os, IntegerList* table_ordering) { assert(table_ordering); IntegerList final_table_ordering; GenerateTableOrdering(table_ordering, &final_table_ordering); TableHeaderList table_records; BuildTableHeadersForSerialization(&final_table_ordering, &table_records); FontOutputStream fos(os); SerializeHeader(&fos, &table_records); SerializeTables(&fos, &table_records); } Font::Font(int32_t sfnt_version, ByteVector* digest) : sfnt_version_(sfnt_version) { // non-trivial assignments that makes debugging hard if placed in // initialization list digest_ = *digest; } void Font::BuildTableHeadersForSerialization(IntegerList* table_ordering, TableHeaderList* table_headers) { assert(table_headers); assert(table_ordering); IntegerList final_table_ordering; GenerateTableOrdering(table_ordering, &final_table_ordering); int32_t table_offset = Offset::kTableRecordBegin + num_tables() * Offset::kTableRecordSize; for (IntegerList::iterator tag = final_table_ordering.begin(), tag_end = final_table_ordering.end(); tag != tag_end; ++tag) { if (tables_.find(*tag) == tables_.end()) { continue; } TablePtr table = tables_[*tag]; if (table != NULL) { HeaderPtr header = new Header(*tag, table->CalculatedChecksum(), table_offset, table->header()->length()); table_headers->push_back(header); table_offset += (table->DataLength() + 3) & ~3; } } } void Font::SerializeHeader(FontOutputStream* fos, TableHeaderList* table_headers) { fos->WriteFixed(sfnt_version_); fos->WriteUShort(table_headers->size()); int32_t log2_of_max_power_of_2 = FontMath::Log2(table_headers->size()); int32_t search_range = 2 << (log2_of_max_power_of_2 - 1 + 4); fos->WriteUShort(search_range); fos->WriteUShort(log2_of_max_power_of_2); fos->WriteUShort((table_headers->size() * 16) - search_range); HeaderTagSortedSet sorted_headers; std::copy(table_headers->begin(), table_headers->end(), std::inserter(sorted_headers, sorted_headers.end())); for (HeaderTagSortedSet::iterator record = sorted_headers.begin(), record_end = sorted_headers.end(); record != record_end; ++record) { fos->WriteULong((*record)->tag()); fos->WriteULong((int32_t)((*record)->checksum())); fos->WriteULong((*record)->offset()); fos->WriteULong((*record)->length()); } } void Font::SerializeTables(FontOutputStream* fos, TableHeaderList* table_headers) { assert(fos); assert(table_headers); for (TableHeaderList::iterator record = table_headers->begin(), end_of_headers = table_headers->end(); record != end_of_headers; ++record) { TablePtr target_table = GetTable((*record)->tag()); if (target_table == NULL) { #if !defined (SFNTLY_NO_EXCEPTION) throw IOException("Table out of sync with font header."); #endif return; } int32_t table_size = target_table->Serialize(fos); if (table_size != (*record)->length()) { assert(false); } int32_t filler_size = ((table_size + 3) & ~3) - table_size; for (int32_t i = 0; i < filler_size; ++i) { fos->Write(static_cast(0)); } } } void Font::GenerateTableOrdering(IntegerList* default_table_ordering, IntegerList* table_ordering) { assert(default_table_ordering); assert(table_ordering); table_ordering->clear(); if (default_table_ordering->empty()) { DefaultTableOrdering(default_table_ordering); } typedef std::map Int2Bool; typedef std::pair Int2BoolEntry; Int2Bool tables_in_font; for (TableMap::iterator table = tables_.begin(), table_end = tables_.end(); table != table_end; ++table) { tables_in_font.insert(Int2BoolEntry(table->first, false)); } for (IntegerList::iterator tag = default_table_ordering->begin(), tag_end = default_table_ordering->end(); tag != tag_end; ++tag) { if (HasTable(*tag)) { table_ordering->push_back(*tag); tables_in_font[*tag] = true; } } for (Int2Bool::iterator table = tables_in_font.begin(), table_end = tables_in_font.end(); table != table_end; ++table) { if (table->second == false) table_ordering->push_back(table->first); } } void Font::DefaultTableOrdering(IntegerList* default_table_ordering) { assert(default_table_ordering); default_table_ordering->clear(); if (HasTable(Tag::CFF)) { default_table_ordering->resize(CFF_TABLE_ORDERING_SIZE); std::copy(CFF_TABLE_ORDERING, CFF_TABLE_ORDERING + CFF_TABLE_ORDERING_SIZE, default_table_ordering->begin()); return; } default_table_ordering->resize(TRUE_TYPE_TABLE_ORDERING_SIZE); std::copy(TRUE_TYPE_TABLE_ORDERING, TRUE_TYPE_TABLE_ORDERING + TRUE_TYPE_TABLE_ORDERING_SIZE, default_table_ordering->begin()); } /****************************************************************************** * Font::Builder class ******************************************************************************/ Font::Builder::~Builder() {} CALLER_ATTACH Font::Builder* Font::Builder::GetOTFBuilder(FontFactory* factory, InputStream* is) { FontBuilderPtr builder = new Builder(factory); builder->LoadFont(is); return builder.Detach(); } CALLER_ATTACH Font::Builder* Font::Builder::GetOTFBuilder( FontFactory* factory, WritableFontData* wfd, int32_t offset_to_offset_table) { FontBuilderPtr builder = new Builder(factory); builder->LoadFont(wfd, offset_to_offset_table); return builder.Detach(); } CALLER_ATTACH Font::Builder* Font::Builder::GetOTFBuilder( FontFactory* factory) { FontBuilderPtr builder = new Builder(factory); return builder.Detach(); } bool Font::Builder::ReadyToBuild() { // just read in data with no manipulation if (table_builders_.empty() && !data_blocks_.empty()) { return true; } // TODO(stuartg): font level checks - required tables etc? for (TableBuilderMap::iterator table_builder = table_builders_.begin(), table_builder_end = table_builders_.end(); table_builder != table_builder_end; ++table_builder) { if (!table_builder->second->ReadyToBuild()) return false; } return true; } CALLER_ATTACH Font* Font::Builder::Build() { FontPtr font = new Font(sfnt_version_, &digest_); if (!table_builders_.empty()) { // Note: Different from Java. Directly use font->tables_ here to avoid // STL container copying. BuildTablesFromBuilders(font, &table_builders_, &font->tables_); } table_builders_.clear(); data_blocks_.clear(); return font.Detach(); } void Font::Builder::SetDigest(ByteVector* digest) { digest_.clear(); digest_ = *digest; } void Font::Builder::ClearTableBuilders() { table_builders_.clear(); } bool Font::Builder::HasTableBuilder(int32_t tag) { return (table_builders_.find(tag) != table_builders_.end()); } Table::Builder* Font::Builder::GetTableBuilder(int32_t tag) { if (HasTableBuilder(tag)) return table_builders_[tag]; return NULL; } Table::Builder* Font::Builder::NewTableBuilder(int32_t tag) { HeaderPtr header = new Header(tag); TableBuilderPtr builder; builder.Attach(Table::Builder::GetBuilder(header, NULL)); table_builders_.insert(TableBuilderEntry(header->tag(), builder)); return builder; } Table::Builder* Font::Builder::NewTableBuilder(int32_t tag, ReadableFontData* src_data) { assert(src_data); WritableFontDataPtr data; data.Attach(WritableFontData::CreateWritableFontData(src_data->Length())); // TODO(stuarg): take over original data instead? src_data->CopyTo(data); HeaderPtr header = new Header(tag, data->Length()); TableBuilderPtr builder; builder.Attach(Table::Builder::GetBuilder(header, data)); table_builders_.insert(TableBuilderEntry(tag, builder)); return builder; } void Font::Builder::RemoveTableBuilder(int32_t tag) { table_builders_.erase(tag); } Font::Builder::Builder(FontFactory* factory) : factory_(factory), sfnt_version_(Fixed1616::Fixed(kSFNTVersionMajor, kSFNTVersionMinor)) { } void Font::Builder::LoadFont(InputStream* is) { // Note: we do not throw exception here for is. This is more of an assertion. assert(is); FontInputStream font_is(is); HeaderOffsetSortedSet records; ReadHeader(&font_is, &records); LoadTableData(&records, &font_is, &data_blocks_); BuildAllTableBuilders(&data_blocks_, &table_builders_); font_is.Close(); } void Font::Builder::LoadFont(WritableFontData* wfd, int32_t offset_to_offset_table) { // Note: we do not throw exception here for is. This is more of an assertion. assert(wfd); HeaderOffsetSortedSet records; ReadHeader(wfd, offset_to_offset_table, &records); LoadTableData(&records, wfd, &data_blocks_); BuildAllTableBuilders(&data_blocks_, &table_builders_); } int32_t Font::Builder::SfntWrapperSize() { return Offset::kSfntHeaderSize + (Offset::kTableRecordSize * table_builders_.size()); } void Font::Builder::BuildAllTableBuilders(DataBlockMap* table_data, TableBuilderMap* builder_map) { for (DataBlockMap::iterator record = table_data->begin(), record_end = table_data->end(); record != record_end; ++record) { TableBuilderPtr builder; builder.Attach(GetTableBuilder(record->first.p_, record->second.p_)); builder_map->insert(TableBuilderEntry(record->first->tag(), builder)); } InterRelateBuilders(&table_builders_); } CALLER_ATTACH Table::Builder* Font::Builder::GetTableBuilder(Header* header, WritableFontData* data) { return Table::Builder::GetBuilder(header, data); } void Font::Builder::BuildTablesFromBuilders(Font* font, TableBuilderMap* builder_map, TableMap* table_map) { UNREFERENCED_PARAMETER(font); InterRelateBuilders(builder_map); // Now build all the tables. for (TableBuilderMap::iterator builder = builder_map->begin(), builder_end = builder_map->end(); builder != builder_end; ++builder) { TablePtr table; if (builder->second && builder->second->ReadyToBuild()) { table.Attach(down_cast(builder->second->Build())); } if (table == NULL) { table_map->clear(); #if !defined (SFNTLY_NO_EXCEPTION) std::string builder_string = "Unable to build table - "; char* table_name = TagToString(builder->first); builder_string += table_name; delete[] table_name; throw RuntimeException(builder_string.c_str()); #endif return; } table_map->insert(TableMapEntry(table->header()->tag(), table)); } } static Table::Builder* GetBuilder(TableBuilderMap* builder_map, int32_t tag) { if (!builder_map) return NULL; TableBuilderMap::iterator target = builder_map->find(tag); if (target == builder_map->end()) return NULL; return target->second.p_; } // Like GetBuilder(), but the returned Builder must be able to support reads. static Table::Builder* GetReadBuilder(TableBuilderMap* builder_map, int32_t tag) { Table::Builder* builder = GetBuilder(builder_map, tag); if (!builder || !builder->InternalReadData()) return NULL; return builder; } void Font::Builder::InterRelateBuilders(TableBuilderMap* builder_map) { Table::Builder* raw_head_builder = GetReadBuilder(builder_map, Tag::head); FontHeaderTableBuilderPtr header_table_builder; if (raw_head_builder != NULL) { header_table_builder = down_cast(raw_head_builder); } Table::Builder* raw_hhea_builder = GetReadBuilder(builder_map, Tag::hhea); HorizontalHeaderTableBuilderPtr horizontal_header_builder; if (raw_head_builder != NULL) { horizontal_header_builder = down_cast(raw_hhea_builder); } Table::Builder* raw_maxp_builder = GetReadBuilder(builder_map, Tag::maxp); MaximumProfileTableBuilderPtr max_profile_builder; if (raw_maxp_builder != NULL) { max_profile_builder = down_cast(raw_maxp_builder); } Table::Builder* raw_loca_builder = GetBuilder(builder_map, Tag::loca); LocaTableBuilderPtr loca_table_builder; if (raw_loca_builder != NULL) { loca_table_builder = down_cast(raw_loca_builder); } Table::Builder* raw_hmtx_builder = GetBuilder(builder_map, Tag::hmtx); HorizontalMetricsTableBuilderPtr horizontal_metrics_builder; if (raw_hmtx_builder != NULL) { horizontal_metrics_builder = down_cast(raw_hmtx_builder); } #if defined (SFNTLY_EXPERIMENTAL) Table::Builder* raw_hdmx_builder = GetBuilder(builder_map, Tag::hdmx); HorizontalDeviceMetricsTableBuilderPtr hdmx_table_builder; if (raw_hdmx_builder != NULL) { hdmx_table_builder = down_cast(raw_hdmx_builder); } #endif // set the inter table data required to build certain tables if (horizontal_metrics_builder != NULL) { if (max_profile_builder != NULL) { horizontal_metrics_builder->SetNumGlyphs( max_profile_builder->NumGlyphs()); } if (horizontal_header_builder != NULL) { horizontal_metrics_builder->SetNumberOfHMetrics( horizontal_header_builder->NumberOfHMetrics()); } } if (loca_table_builder != NULL) { if (max_profile_builder != NULL) { loca_table_builder->SetNumGlyphs(max_profile_builder->NumGlyphs()); } if (header_table_builder != NULL) { loca_table_builder->set_format_version( header_table_builder->IndexToLocFormat()); } } #if defined (SFNTLY_EXPERIMENTAL) // Note: In C++, hdmx_table_builder can be NULL in a subsetter. if (max_profile_builder != NULL && hdmx_table_builder != NULL) { hdmx_table_builder->SetNumGlyphs(max_profile_builder->NumGlyphs()); } #endif } void Font::Builder::ReadHeader(FontInputStream* is, HeaderOffsetSortedSet* records) { assert(records); sfnt_version_ = is->ReadFixed(); num_tables_ = is->ReadUShort(); search_range_ = is->ReadUShort(); entry_selector_ = is->ReadUShort(); range_shift_ = is->ReadUShort(); for (int32_t table_number = 0; table_number < num_tables_; ++table_number) { // Need to use temporary vars here. C++ evaluates function parameters from // right to left and thus breaks the order of input stream. int32_t tag = is->ReadULongAsInt(); int64_t checksum = is->ReadULong(); int32_t offset = is->ReadULongAsInt(); int32_t length = is->ReadULongAsInt(); HeaderPtr table = new Header(tag, checksum, offset, length); records->insert(table); } } void Font::Builder::ReadHeader(ReadableFontData* fd, int32_t offset, HeaderOffsetSortedSet* records) { assert(records); sfnt_version_ = fd->ReadFixed(offset + Offset::kSfntVersion); num_tables_ = fd->ReadUShort(offset + Offset::kNumTables); search_range_ = fd->ReadUShort(offset + Offset::kSearchRange); entry_selector_ = fd->ReadUShort(offset + Offset::kEntrySelector); range_shift_ = fd->ReadUShort(offset + Offset::kRangeShift); int32_t table_offset = offset + Offset::kTableRecordBegin; for (int32_t table_number = 0; table_number < num_tables_; table_number++, table_offset += Offset::kTableRecordSize) { int32_t tag = fd->ReadULongAsInt(table_offset + Offset::kTableTag); int64_t checksum = fd->ReadULong(table_offset + Offset::kTableCheckSum); int32_t offset = fd->ReadULongAsInt(table_offset + Offset::kTableOffset); int32_t length = fd->ReadULongAsInt(table_offset + Offset::kTableLength); HeaderPtr table = new Header(tag, checksum, offset, length); records->insert(table); } } void Font::Builder::LoadTableData(HeaderOffsetSortedSet* headers, FontInputStream* is, DataBlockMap* table_data) { assert(table_data); for (HeaderOffsetSortedSet::iterator it = headers->begin(), table_end = headers->end(); it != table_end; ++it) { const Ptr
header = *it; is->Skip(header->offset() - is->position()); if (header->length() > kMaxTableSize) continue; FontInputStream table_is(is, header->length()); WritableFontDataPtr data; data.Attach(WritableFontData::CreateWritableFontData(header->length())); data->CopyFrom(&table_is, header->length()); table_data->insert(DataBlockEntry(header, data)); } } void Font::Builder::LoadTableData(HeaderOffsetSortedSet* headers, WritableFontData* fd, DataBlockMap* table_data) { for (HeaderOffsetSortedSet::iterator it = headers->begin(), table_end = headers->end(); it != table_end; ++it) { const Ptr
header = *it; if (header->length() > kMaxTableSize) continue; FontDataPtr sliced_data; sliced_data.Attach(fd->Slice(header->offset(), header->length())); WritableFontDataPtr data = down_cast(sliced_data.p_); table_data->insert(DataBlockEntry(header, data)); } } } // namespace sfntly