blob: 8b64429641f13c035d87306956e0b2d194ec9a4e [file] [log] [blame]
/*
* 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.
*/
// File is originally from Chromium third_party/sfntly/src/subsetter.
// Use as test case in sfntly so that problems can be caught in upstream early.
#include "test/subsetter_impl.h"
#include <string.h>
#include <map>
#include <set>
#include "sfntly/table/bitmap/eblc_table.h"
#include "sfntly/table/bitmap/ebdt_table.h"
#include "sfntly/table/bitmap/index_sub_table.h"
#include "sfntly/table/bitmap/index_sub_table_format1.h"
#include "sfntly/table/bitmap/index_sub_table_format2.h"
#include "sfntly/table/bitmap/index_sub_table_format3.h"
#include "sfntly/table/bitmap/index_sub_table_format4.h"
#include "sfntly/table/bitmap/index_sub_table_format5.h"
#include "sfntly/table/core/name_table.h"
#include "sfntly/table/truetype/glyph_table.h"
#include "sfntly/table/truetype/loca_table.h"
#include "sfntly/tag.h"
#include "sfntly/data/memory_byte_array.h"
#include "sfntly/port/memory_input_stream.h"
#include "sfntly/port/memory_output_stream.h"
namespace {
// The bitmap tables must be greater than 16KB to trigger bitmap subsetter.
static const int BITMAP_SIZE_THRESHOLD = 16384;
}
namespace sfntly {
void ConstructName(UChar* name_part, UnicodeString* name, int32_t name_id) {
switch (name_id) {
case NameId::kFullFontName:
*name = name_part;
break;
case NameId::kFontFamilyName:
case NameId::kPreferredFamily:
case NameId::kWWSFamilyName: {
UnicodeString original = *name;
*name = name_part;
*name += original;
break;
}
case NameId::kFontSubfamilyName:
case NameId::kPreferredSubfamily:
case NameId::kWWSSubfamilyName:
*name += name_part;
break;
default:
// This name part is not used to construct font name (e.g. copyright).
// Simply ignore it.
break;
}
}
int32_t HashCode(int32_t platform_id, int32_t encoding_id, int32_t language_id,
int32_t name_id) {
int32_t result = platform_id << 24 | encoding_id << 16 | language_id << 8;
if (name_id == NameId::kFullFontName) {
result |= 0xff;
} else if (name_id == NameId::kPreferredFamily ||
name_id == NameId::kPreferredSubfamily) {
result |= 0xf;
} else if (name_id == NameId::kWWSFamilyName ||
name_id == NameId::kWWSSubfamilyName) {
result |= 1;
}
return result;
}
bool HasName(const char* font_name, Font* font) {
UnicodeString font_string = UnicodeString::fromUTF8(font_name);
if (font_string.isEmpty())
return false;
UnicodeString regular_suffix = UnicodeString::fromUTF8(" Regular");
UnicodeString alt_font_string = font_string;
alt_font_string += regular_suffix;
typedef std::map<int32_t, UnicodeString> NameMap;
NameMap names;
NameTablePtr name_table = down_cast<NameTable*>(font->GetTable(Tag::name));
if (name_table == NULL) {
return false;
}
for (int32_t i = 0; i < name_table->NameCount(); ++i) {
switch (name_table->NameId(i)) {
case NameId::kFontFamilyName:
case NameId::kFontSubfamilyName:
case NameId::kFullFontName:
case NameId::kPreferredFamily:
case NameId::kPreferredSubfamily:
case NameId::kWWSFamilyName:
case NameId::kWWSSubfamilyName: {
int32_t hash_code = HashCode(name_table->PlatformId(i),
name_table->EncodingId(i),
name_table->LanguageId(i),
name_table->NameId(i));
UChar* name_part = name_table->Name(i);
ConstructName(name_part, &(names[hash_code]), name_table->NameId(i));
delete[] name_part;
break;
}
default:
break;
}
}
if (!names.empty()) {
for (NameMap::iterator b = names.begin(), e = names.end(); b != e; ++b) {
if (b->second.caseCompare(font_string, 0) == 0 ||
b->second.caseCompare(alt_font_string, 0) == 0) {
return true;
}
}
}
return false;
}
Font* FindFont(const char* font_name, const FontArray& font_array) {
if (font_array.empty() || font_array[0] == NULL) {
return NULL;
}
if (font_name && strlen(font_name)) {
for (FontArray::const_iterator b = font_array.begin(), e = font_array.end();
b != e; ++b) {
if (HasName(font_name, (*b).p_)) {
return (*b).p_;
}
}
}
return font_array[0].p_;
}
bool ResolveCompositeGlyphs(GlyphTable* glyf,
LocaTable* loca,
const unsigned int* glyph_ids,
size_t glyph_count,
IntegerSet* glyph_id_processed) {
if (glyf == NULL || loca == NULL || glyph_ids == NULL || glyph_count == 0 ||
glyph_id_processed == NULL) {
return false;
}
// Find glyf and loca table.
GlyphTablePtr glyph_table = glyf;
LocaTablePtr loca_table = loca;
// Sort and uniquify glyph ids.
IntegerSet glyph_id_remaining;
glyph_id_remaining.insert(0); // Always include glyph id 0.
for (size_t i = 0; i < glyph_count; ++i) {
glyph_id_remaining.insert(glyph_ids[i]);
}
// Identify if any given glyph id maps to a composite glyph. If so, include
// the glyphs referenced by that composite glyph.
while (!glyph_id_remaining.empty()) {
IntegerSet comp_glyph_id;
for (IntegerSet::iterator i = glyph_id_remaining.begin(),
e = glyph_id_remaining.end(); i != e; ++i) {
if (*i < 0 || *i >= loca_table->num_glyphs()) {
// Invalid glyph id, ignore.
continue;
}
int32_t length = loca_table->GlyphLength(*i);
if (length == 0) {
// Empty glyph, ignore.
continue;
}
int32_t offset = loca_table->GlyphOffset(*i);
GlyphPtr glyph;
glyph.Attach(glyph_table->GetGlyph(offset, length));
if (glyph == NULL) {
// Error finding glyph, ignore.
continue;
}
if (glyph->GlyphType() == GlyphType::kComposite) {
Ptr<GlyphTable::CompositeGlyph> comp_glyph =
down_cast<GlyphTable::CompositeGlyph*>(glyph.p_);
for (int32_t j = 0; j < comp_glyph->NumGlyphs(); ++j) {
int32_t glyph_id = comp_glyph->GlyphIndex(j);
if (glyph_id_processed->find(glyph_id) == glyph_id_processed->end() &&
glyph_id_remaining.find(glyph_id) == glyph_id_remaining.end()) {
comp_glyph_id.insert(comp_glyph->GlyphIndex(j));
}
}
}
glyph_id_processed->insert(*i);
}
glyph_id_remaining.clear();
glyph_id_remaining = comp_glyph_id;
}
return true;
}
bool SetupGlyfBuilders(Font::Builder* builder,
GlyphTable* glyf,
LocaTable* loca,
const IntegerSet& glyph_ids) {
if (!builder || !glyf || !loca) {
return false;
}
// The tables are already checked in ResolveCompositeGlyphs().
GlyphTablePtr glyph_table = glyf;
LocaTablePtr loca_table = loca;
FontBuilderPtr font_builder = builder;
GlyphTableBuilderPtr glyph_table_builder =
down_cast<GlyphTable::Builder*>(font_builder->NewTableBuilder(Tag::glyf));
LocaTableBuilderPtr loca_table_builder =
down_cast<LocaTable::Builder*>(font_builder->NewTableBuilder(Tag::loca));
if (glyph_table_builder == NULL || loca_table_builder == NULL) {
// Out of memory.
return false;
}
// Extract glyphs and setup loca list.
IntegerList loca_list;
loca_list.resize(loca_table->num_glyphs());
loca_list.push_back(0);
int32_t last_glyph_id = 0;
int32_t last_offset = 0;
GlyphTable::GlyphBuilderList* glyph_builders =
glyph_table_builder->GlyphBuilders();
for (IntegerSet::const_iterator i = glyph_ids.begin(), e = glyph_ids.end();
i != e; ++i) {
int32_t length = loca_table->GlyphLength(*i);
int32_t offset = loca_table->GlyphOffset(*i);
GlyphPtr glyph;
glyph.Attach(glyph_table->GetGlyph(offset, length));
// Add glyph to new glyf table.
ReadableFontDataPtr data = glyph->ReadFontData();
WritableFontDataPtr copy_data;
copy_data.Attach(WritableFontData::CreateWritableFontData(data->Length()));
data->CopyTo(copy_data);
GlyphBuilderPtr glyph_builder;
glyph_builder.Attach(glyph_table_builder->GlyphBuilder(copy_data));
glyph_builders->push_back(glyph_builder);
// Configure loca list.
for (int32_t j = last_glyph_id + 1; j <= *i; ++j) {
loca_list[j] = last_offset;
}
last_offset += length;
loca_list[*i + 1] = last_offset;
last_glyph_id = *i;
}
for (int32_t j = last_glyph_id + 1; j <= loca_table->num_glyphs(); ++j) {
loca_list[j] = last_offset;
}
loca_table_builder->SetLocaList(&loca_list);
return true;
}
bool HasOverlap(int32_t range_begin, int32_t range_end,
const IntegerSet& glyph_ids) {
if (range_begin == range_end) {
return glyph_ids.find(range_begin) != glyph_ids.end();
} else if (range_end > range_begin) {
IntegerSet::const_iterator left = glyph_ids.lower_bound(range_begin);
IntegerSet::const_iterator right = glyph_ids.lower_bound(range_end);
return right != left;
}
return false;
}
// Initialize builder, returns false if glyph_id subset is not covered.
bool ShallSubset(EbdtTable::Builder* ebdt, EblcTable::Builder* eblc,
const IntegerSet& glyph_ids) {
EblcTableBuilderPtr eblc_builder = eblc;
EbdtTableBuilderPtr ebdt_builder = ebdt;
BitmapLocaList loca_list;
BitmapSizeTableBuilderList* strikes = eblc_builder->BitmapSizeBuilders();
// Note: Do not call eblc_builder->GenerateLocaList(&loca_list) and then
// ebdt_builder->SetLoca(loca_list). For fonts like SimSun, there are
// >28K glyphs inside, where a typical usage will be <1K glyphs. Doing
// the calls improperly will result in creation of >100K objects that
// will be destroyed immediately, inducing significant slowness.
IntegerList removed_strikes;
for (size_t i = 0; i < strikes->size(); i++) {
if (!HasOverlap((*strikes)[i]->StartGlyphIndex(),
(*strikes)[i]->EndGlyphIndex(), glyph_ids)) {
removed_strikes.push_back(i);
continue;
}
IndexSubTableBuilderList* index_builders =
(*strikes)[i]->IndexSubTableBuilders();
IntegerList removed_indexes;
BitmapGlyphInfoMap info_map;
for (size_t j = 0; j < index_builders->size(); ++j) {
int32_t first_glyph_id = (*index_builders)[j]->first_glyph_index();
int32_t last_glyph_id = (*index_builders)[j]->last_glyph_index();
if (!HasOverlap(first_glyph_id, last_glyph_id, glyph_ids)) {
removed_indexes.push_back(j);
continue;
}
for (IntegerSet::const_iterator gid = glyph_ids.begin(),
gid_end = glyph_ids.end();
gid != gid_end; gid++) {
if (*gid < first_glyph_id) {
continue;
}
if (*gid > last_glyph_id) {
break;
}
BitmapGlyphInfoPtr info;
info.Attach((*index_builders)[j]->GlyphInfo(*gid));
if (info && info->length()) { // Do not include gid without bitmap
info_map[*gid] = info;
}
}
}
if (!info_map.empty()) {
loca_list.push_back(info_map);
} else {
removed_strikes.push_back(i); // Detected null entries.
}
// Remove unused index sub tables
for (IntegerList::reverse_iterator j = removed_indexes.rbegin(),
e = removed_indexes.rend();
j != e; j++) {
index_builders->erase(index_builders->begin() + *j);
}
}
if (removed_strikes.size() == strikes->size() || loca_list.empty()) {
return false; // All strikes shall be gone.
}
// Remove unused strikes
for (IntegerList::reverse_iterator j = removed_strikes.rbegin(),
e = removed_strikes.rend(); j != e; j++) {
strikes->erase(strikes->begin() + *j);
}
if (strikes->empty()) { // no glyph covered, can safely drop the builders.
return false;
}
ebdt_builder->SetLoca(&loca_list);
ebdt_builder->GlyphBuilders(); // Initialize the builder.
return true;
}
void CopyBigGlyphMetrics(BigGlyphMetrics::Builder* source,
BigGlyphMetrics::Builder* target) {
target->SetHeight(static_cast<byte_t>(source->Height()));
target->SetWidth(static_cast<byte_t>(source->Width()));
target->SetHoriBearingX(static_cast<byte_t>(source->HoriBearingX()));
target->SetHoriBearingY(static_cast<byte_t>(source->HoriBearingY()));
target->SetHoriAdvance(static_cast<byte_t>(source->HoriAdvance()));
target->SetVertBearingX(static_cast<byte_t>(source->VertBearingX()));
target->SetVertBearingY(static_cast<byte_t>(source->VertBearingY()));
target->SetVertAdvance(static_cast<byte_t>(source->VertAdvance()));
}
CALLER_ATTACH IndexSubTable::Builder*
ConstructIndexFormat4(IndexSubTable::Builder* b, const BitmapGlyphInfoMap& loca,
int32_t* image_data_offset) {
IndexSubTableFormat4BuilderPtr builder4;
builder4.Attach(IndexSubTableFormat4::Builder::CreateBuilder());
CodeOffsetPairBuilderList offset_pairs;
size_t offset = 0;
int32_t lower_bound = b->first_glyph_index();
int32_t upper_bound = b->last_glyph_index();
bool lower_bound_reached = false;
bool upper_bound_reached = false;
int32_t last_gid = -1;
BitmapGlyphInfoMap::const_iterator last_element = loca.end();
--last_element;
for (BitmapGlyphInfoMap::const_iterator i = loca.begin(), e = loca.end();
i != e; i++) {
int32_t gid = i->first;
if (gid < lower_bound) {
continue;
}
if (!lower_bound_reached) {
builder4->set_first_glyph_index(gid);
builder4->set_image_format(b->image_format());
builder4->set_image_data_offset(*image_data_offset);
last_gid = gid;
lower_bound_reached = true;
}
if (gid > upper_bound) {
upper_bound_reached = true;
}
if (!upper_bound_reached) {
offset_pairs.push_back(
IndexSubTableFormat4::CodeOffsetPairBuilder(gid, offset));
offset += i->second->length();
last_gid = gid;
if (i == last_element) {
upper_bound_reached = true;
}
}
if (upper_bound_reached) {
offset_pairs.push_back(
IndexSubTableFormat4::CodeOffsetPairBuilder(-1, offset));
builder4->set_last_glyph_index(last_gid);
*image_data_offset += offset;
break;
}
}
builder4->SetOffsetArray(offset_pairs);
return builder4.Detach();
}
CALLER_ATTACH IndexSubTable::Builder*
ConstructIndexFormat5(IndexSubTable::Builder* b, const BitmapGlyphInfoMap& loca,
int32_t* image_data_offset) {
IndexSubTableFormat5BuilderPtr new_builder;
new_builder.Attach(IndexSubTableFormat5::Builder::CreateBuilder());
// Copy BigMetrics
int32_t image_size = 0;
if (b->index_format() == IndexSubTable::Format::FORMAT_2) {
IndexSubTableFormat2BuilderPtr builder2 =
down_cast<IndexSubTableFormat2::Builder*>(b);
CopyBigGlyphMetrics(builder2->BigMetrics(), new_builder->BigMetrics());
image_size = builder2->ImageSize();
} else {
IndexSubTableFormat5BuilderPtr builder5 =
down_cast<IndexSubTableFormat5::Builder*>(b);
BigGlyphMetricsBuilderPtr metrics_builder;
CopyBigGlyphMetrics(builder5->BigMetrics(), new_builder->BigMetrics());
image_size = builder5->ImageSize();
}
IntegerList* glyph_array = new_builder->GlyphArray();
size_t offset = 0;
int32_t lower_bound = b->first_glyph_index();
int32_t upper_bound = b->last_glyph_index();
bool lower_bound_reached = false;
bool upper_bound_reached = false;
int32_t last_gid = -1;
BitmapGlyphInfoMap::const_iterator last_element = loca.end();
--last_element;
for (BitmapGlyphInfoMap::const_iterator i = loca.begin(), e = loca.end();
i != e; i++) {
int32_t gid = i->first;
if (gid < lower_bound) {
continue;
}
if (!lower_bound_reached) {
new_builder->set_first_glyph_index(gid);
new_builder->set_image_format(b->image_format());
new_builder->set_image_data_offset(*image_data_offset);
new_builder->SetImageSize(image_size);
last_gid = gid;
lower_bound_reached = true;
}
if (gid > upper_bound || i == last_element) {
upper_bound_reached = true;
}
if (!upper_bound_reached || i == last_element) {
glyph_array->push_back(gid);
offset += i->second->length();
last_gid = gid;
}
if (upper_bound_reached) {
new_builder->set_last_glyph_index(last_gid);
*image_data_offset += offset;
break;
}
}
return new_builder.Detach();
}
CALLER_ATTACH IndexSubTable::Builder*
SubsetIndexSubTable(IndexSubTable::Builder* builder,
const BitmapGlyphInfoMap& loca,
int32_t* image_data_offset) {
switch (builder->index_format()) {
case IndexSubTable::Format::FORMAT_1:
case IndexSubTable::Format::FORMAT_3:
case IndexSubTable::Format::FORMAT_4:
return ConstructIndexFormat4(builder, loca, image_data_offset);
case IndexSubTable::Format::FORMAT_2:
case IndexSubTable::Format::FORMAT_5:
return ConstructIndexFormat5(builder, loca, image_data_offset);
default:
assert(false); // Shall not be here.
break;
}
return NULL;
}
void SubsetEBLC(EblcTable::Builder* eblc, const BitmapLocaList& new_loca) {
EblcTableBuilderPtr eblc_builder = eblc;
BitmapSizeTableBuilderList* size_builders = eblc->BitmapSizeBuilders();
if (size_builders == NULL) {
return; // No valid EBLC.
}
int32_t image_data_offset = EbdtTable::Offset::kHeaderLength;
for (size_t strike = 0; strike < size_builders->size(); ++strike) {
IndexSubTableBuilderList* index_builders =
(*size_builders)[strike]->IndexSubTableBuilders();
for (size_t index = 0; index < index_builders->size(); ++index) {
IndexSubTable::Builder* new_builder_raw =
SubsetIndexSubTable((*index_builders)[index], new_loca[strike],
&image_data_offset);
if (NULL != new_builder_raw) {
(*index_builders)[index].Attach(new_builder_raw);
}
}
}
}
/******************************************************************************
Long background comments
EBLC structure:
header
bitmapSizeTable[]
one per strike
holds strike metrics - sbitLineMetrics
holds info about indexSubTableArray
indexSubTableArray[][]
one per strike and then one per indexSubTable for that strike
holds info about the indexSubTable
the indexSubTable entries pointed to can be of different formats
indexSubTable
one per indexSubTableArray entry
tells how to get the glyphs
may hold the glyph metrics if they are uniform for all the glyphs in range
There is nothing that says that the indexSubTableArray entries and/or the
indexSubTable items need to be unique. They may be shared between strikes.
EBDT structure:
header
glyphs
amorphous blob of data
different glyphs that are only able to be figured out from the EBLC table
may hold metrics - depends on the EBLC entry that pointed to them
Subsetting EBLC table:
Most pages use only a fraction (hundreds or less) glyphs out of a given font
(which can have >20K glyphs for CJK). It's safe to assume that the subset
font will have sparse bitmap glyphs. As a result, the EBLC table shall be
reconstructed to either format 4 or 5.
*******************************************************************************/
bool SetupBitmapBuilders(Font* font, Font::Builder* builder,
const IntegerSet& glyph_ids, bool use_ebdt) {
if (!font || !builder) {
return false;
}
EbdtTablePtr ebdt_table =
down_cast<EbdtTable*>(font->GetTable(use_ebdt ? Tag::EBDT : Tag::bdat));
EblcTablePtr eblc_table =
down_cast<EblcTable*>(font->GetTable(use_ebdt ? Tag::EBLC : Tag::bloc));
// If the bitmap table's size is too small, skip subsetting.
if (ebdt_table->DataLength() + eblc_table->DataLength() <
BITMAP_SIZE_THRESHOLD) {
return true;
}
// Get the builders.
FontBuilderPtr font_builder = builder;
EbdtTableBuilderPtr ebdt_table_builder = down_cast<EbdtTable::Builder*>(
font_builder->NewTableBuilder(use_ebdt ? Tag::EBDT : Tag::bdat,
ebdt_table->ReadFontData()));
EblcTableBuilderPtr eblc_table_builder = down_cast<EblcTable::Builder*>(
font_builder->NewTableBuilder(use_ebdt ? Tag::EBLC : Tag::bloc,
eblc_table->ReadFontData()));
if (ebdt_table_builder == NULL || eblc_table_builder == NULL) {
// Out of memory.
return false;
}
if (!ShallSubset(ebdt_table_builder, eblc_table_builder, glyph_ids)) {
// Bitmap tables do not cover the glyphs in our subset.
font_builder->RemoveTableBuilder(use_ebdt ? Tag::EBLC : Tag::bloc);
font_builder->RemoveTableBuilder(use_ebdt ? Tag::EBDT : Tag::bdat);
return false;
}
BitmapLocaList new_loca;
ebdt_table_builder->GenerateLocaList(&new_loca);
SubsetEBLC(eblc_table_builder, new_loca);
return true;
}
enum BitmapDetection {
kNotFound,
kEBDTFound,
kOnlyBDATFound
};
// Some fonts have both EBDT/EBLC and bdat/bloc, we need only one set of them.
int DetectBitmapBuilders(Font* font) {
// Check if bitmap table exists.
EbdtTablePtr ebdt_table = down_cast<EbdtTable*>(font->GetTable(Tag::EBDT));
EblcTablePtr eblc_table = down_cast<EblcTable*>(font->GetTable(Tag::EBLC));
if (ebdt_table == NULL && eblc_table == NULL) {
// Check BDAT variants.
ebdt_table = down_cast<EbdtTable*>(font->GetTable(Tag::bdat));
eblc_table = down_cast<EblcTable*>(font->GetTable(Tag::bloc));
if (ebdt_table == NULL || eblc_table == NULL) {
// There's no bitmap tables.
return kNotFound;
}
return kOnlyBDATFound;
}
return kEBDTFound;
}
SubsetterImpl::SubsetterImpl() {
}
SubsetterImpl::~SubsetterImpl() {
}
bool SubsetterImpl::LoadFont(const char* font_name,
const unsigned char* original_font,
size_t font_size) {
MemoryInputStream mis;
mis.Attach(original_font, font_size);
if (factory_ == NULL) {
factory_.Attach(FontFactory::GetInstance());
}
FontArray font_array;
factory_->LoadFonts(&mis, &font_array);
font_ = FindFont(font_name, font_array);
if (font_ == NULL) {
return false;
}
return true;
}
int SubsetterImpl::SubsetFont(const unsigned int* glyph_ids,
size_t glyph_count,
unsigned char** output_buffer) {
if (factory_ == NULL || font_ == NULL) {
return -1;
}
// Find glyf and loca table.
GlyphTablePtr glyph_table =
down_cast<GlyphTable*>(font_->GetTable(Tag::glyf));
LocaTablePtr loca_table = down_cast<LocaTable*>(font_->GetTable(Tag::loca));
if (glyph_table == NULL || loca_table == NULL) {
// We are not able to subset the font.
return 0;
}
IntegerSet glyph_id_processed;
if (!ResolveCompositeGlyphs(glyph_table, loca_table,
glyph_ids, glyph_count, &glyph_id_processed) ||
glyph_id_processed.empty()) {
return 0;
}
FontPtr new_font;
new_font.Attach(Subset(glyph_id_processed));
if (new_font == NULL) {
return 0;
}
MemoryOutputStream output_stream;
factory_->SerializeFont(new_font, &output_stream);
int length = static_cast<int>(output_stream.Size());
if (length > 0) {
*output_buffer = new unsigned char[length];
memcpy(*output_buffer, output_stream.Get(), length);
}
return length;
}
/*******************************************************************************
Long comments regarding TTF tables and PDF
According to PDF spec (section 9.9), the following tables must present:
head, hhea, loca, maxp, cvt, prep, glyf, hmtx, fpgm
cmap if font is used as a TTF and not a CIDFont dict
Other tables we need to keep for PDF rendering to support zoom in/out:
bdat, bloc, ebdt, eblc, ebsc, gasp
Special table:
CFF - if you have this table then you shouldn't have a glyf table and this is
the table with all the glyphs. Shall skip subsetting completely since
sfntly is not capable of subsetting it for now.
post - extra info here for printing on PostScript printers but maybe not
enough to outweigh the space taken by the names
Tables to break apart:
name - could throw away all but one language and one platform strings / might
throw away some of the name entries
cmap - could strip out non-needed cmap subtables
- format 4 subtable can be subsetted as well using sfntly
Graphite tables:
silf, glat, gloc, feat - shall be okay to strip out
Tables that can be discarded:
OS/2 - everything here is for layout and description of the font that is
elsewhere (some in the PDF objects)
BASE, GDEF, GSUB, GPOS, JSTF - all used for layout
kern - old style layout
DSIG - this will be invalid after subsetting
hdmx - layout
PCLT - metadata that's not needed
vmtx - layout
vhea - layout
VDMX
VORG - not used by TT/OT - used by CFF
hsty - would be surprised if you saw one of these - used on the Newton
AAT tables - mort, morx, feat, acnt, bsin, just, lcar, fdsc, fmtx, prop,
Zapf, opbd, trak, fvar, gvar, avar, cvar
- these are all layout tables and once layout happens are not
needed anymore
LTSH - layout
*******************************************************************************/
CALLER_ATTACH Font* SubsetterImpl::Subset(const IntegerSet& glyph_ids) {
// The const is initialized here to workaround VC bug of rendering all Tag::*
// as 0. These tags represents the TTF tables that we will embed in subset
// font.
const int32_t VALID_TABLE_TAG[] = {
Tag::head, Tag::hhea, Tag::loca, Tag::maxp, Tag::cvt,
Tag::prep, Tag::glyf, Tag::hmtx, Tag::fpgm, Tag::EBDT,
Tag::EBLC, Tag::EBSC, Tag::bdat, Tag::bloc, Tag::bhed,
Tag::cmap, // Keep here for future tagged PDF development.
Tag::name, // Keep here due to legal concerns: copyright info inside.
};
// Setup font builders we need.
FontBuilderPtr font_builder;
font_builder.Attach(factory_->NewFontBuilder());
IntegerSet remove_tags;
GlyphTablePtr glyph_table =
down_cast<GlyphTable*>(font_->GetTable(Tag::glyf));
LocaTablePtr loca_table = down_cast<LocaTable*>(font_->GetTable(Tag::loca));
if (SetupGlyfBuilders(font_builder, glyph_table, loca_table, glyph_ids)) {
remove_tags.insert(Tag::glyf);
remove_tags.insert(Tag::loca);
}
int flag = DetectBitmapBuilders(font_);
if (flag != kNotFound) {
bool use_ebdt = (flag == kEBDTFound);
bool subset_success =
SetupBitmapBuilders(font_, font_builder, glyph_ids, use_ebdt);
if (use_ebdt || !subset_success) {
remove_tags.insert(Tag::bdat);
remove_tags.insert(Tag::bloc);
remove_tags.insert(Tag::bhed);
}
if (use_ebdt && !subset_success) {
remove_tags.insert(Tag::EBDT);
remove_tags.insert(Tag::EBLC);
remove_tags.insert(Tag::EBSC);
}
}
IntegerSet allowed_tags;
for (size_t i = 0; i < sizeof(VALID_TABLE_TAG) / sizeof(int32_t); ++i) {
allowed_tags.insert(VALID_TABLE_TAG[i]);
}
for (IntegerSet::iterator i = remove_tags.begin(), e = remove_tags.end();
i != e; i++) {
IntegerSet::iterator it = allowed_tags.find(*i);
if (it != allowed_tags.end()) {
allowed_tags.erase(it);
}
}
// Setup remaining builders.
for (IntegerSet::iterator i = allowed_tags.begin(), e = allowed_tags.end();
i != e; ++i) {
Table* table = font_->GetTable(*i);
if (table) {
font_builder->NewTableBuilder(*i, table->ReadFontData());
}
}
return font_builder->Build();
}
} // namespace sfntly