/*
|
* 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 {
|
|
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<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) {
|
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<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)
|
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<FontHeaderTable::Builder*>(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<HorizontalHeaderTable::Builder*>(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<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 it = headers->begin(),
|
table_end = headers->end();
|
it != table_end;
|
++it) {
|
const Ptr<Header> 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> header = *it;
|
if (header->length() > kMaxTableSize)
|
continue;
|
|
FontDataPtr sliced_data;
|
sliced_data.Attach(fd->Slice(header->offset(), header->length()));
|
WritableFontDataPtr data = down_cast<WritableFontData*>(sliced_data.p_);
|
table_data->insert(DataBlockEntry(header, data));
|
}
|
}
|
|
} // namespace sfntly
|