| /* |
| * 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 <stdio.h> |
| |
| #include <functional> |
| #include <algorithm> |
| #include <map> |
| #include <string> |
| #include <typeinfo> |
| #include <iterator> |
| |
| #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 { |
| |
| const int32_t SFNTVERSION_MAJOR = 1; |
| const int32_t SFNTVERSION_MINOR = 0; |
| |
| /****************************************************************************** |
| * Font class |
| ******************************************************************************/ |
| Font::~Font() {} |
| |
| bool Font::HasTable(int32_t tag) { |
| TableMap::const_iterator result = tables_.find(tag); |
| TableMap::const_iterator end = tables_.end(); |
| return (result != 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<byte_t>(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<int32_t, bool> Int2Bool; |
| typedef std::pair<int32_t, bool> 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) { |
| TableBuilderMap::iterator target = table_builders_.find(tag); |
| if (target != table_builders_.end()) { |
| table_builders_.erase(target); |
| } |
| } |
| |
| Font::Builder::Builder(FontFactory* factory) |
| : factory_(factory), |
| sfnt_version_(Fixed1616::Fixed(SFNTVERSION_MAJOR, SFNTVERSION_MINOR)) { |
| } |
| |
| 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<Table*>(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) { |
| TableBuilderMap::iterator target = builder_map->find(tag); |
| if (target != builder_map->end()) { |
| return target->second.p_; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void Font::Builder::InterRelateBuilders(TableBuilderMap* builder_map) { |
| Table::Builder* raw_head_builder = GetBuilder(builder_map, Tag::head); |
| FontHeaderTableBuilderPtr header_table_builder; |
| if (raw_head_builder != NULL) { |
| header_table_builder = |
| down_cast<FontHeaderTable::Builder*>(raw_head_builder); |
| } |
| |
| Table::Builder* raw_hhea_builder = GetBuilder(builder_map, Tag::hhea); |
| HorizontalHeaderTableBuilderPtr horizontal_header_builder; |
| if (raw_head_builder != NULL) { |
| horizontal_header_builder = |
| down_cast<HorizontalHeaderTable::Builder*>(raw_hhea_builder); |
| } |
| |
| Table::Builder* raw_maxp_builder = GetBuilder(builder_map, Tag::maxp); |
| MaximumProfileTableBuilderPtr max_profile_builder; |
| if (raw_maxp_builder != NULL) { |
| max_profile_builder = |
| down_cast<MaximumProfileTable::Builder*>(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<LocaTable::Builder*>(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<HorizontalMetricsTable::Builder*>(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<HorizontalDeviceMetricsTable::Builder*>(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 table_header = headers->begin(), |
| table_end = headers->end(); |
| table_header != table_end; |
| ++table_header) { |
| is->Skip((*table_header)->offset() - is->position()); |
| FontInputStream table_is(is, (*table_header)->length()); |
| WritableFontDataPtr data; |
| data.Attach( |
| WritableFontData::CreateWritableFontData((*table_header)->length())); |
| data->CopyFrom(&table_is, (*table_header)->length()); |
| table_data->insert(DataBlockEntry(*table_header, data)); |
| } |
| } |
| |
| void Font::Builder::LoadTableData(HeaderOffsetSortedSet* headers, |
| WritableFontData* fd, |
| DataBlockMap* table_data) { |
| for (HeaderOffsetSortedSet::iterator table_header = headers->begin(), |
| table_end = headers->end(); |
| table_header != table_end; |
| ++table_header) { |
| FontDataPtr sliced_data; |
| sliced_data.Attach( |
| fd->Slice((*table_header)->offset(), (*table_header)->length())); |
| WritableFontDataPtr data = down_cast<WritableFontData*>(sliced_data.p_); |
| table_data->insert(DataBlockEntry(*table_header, data)); |
| } |
| } |
| |
| } // namespace sfntly |