| /* |
| * 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 "subtly/font_assembler.h" |
| |
| #include <stdio.h> |
| |
| #include <set> |
| #include <map> |
| |
| #include "sfntly/tag.h" |
| #include "sfntly/font.h" |
| #include "sfntly/font_factory.h" |
| #include "sfntly/table/core/cmap_table.h" |
| #include "sfntly/table/truetype/loca_table.h" |
| #include "sfntly/table/truetype/glyph_table.h" |
| #include "sfntly/table/core/maximum_profile_table.h" |
| #include "sfntly/port/type.h" |
| #include "sfntly/port/refcount.h" |
| #include "subtly/font_info.h" |
| |
| namespace subtly { |
| using namespace sfntly; |
| |
| FontAssembler::FontAssembler(FontInfo* font_info, |
| IntegerSet* table_blacklist) |
| : table_blacklist_(table_blacklist) { |
| font_info_ = font_info; |
| Initialize(); |
| } |
| |
| FontAssembler::FontAssembler(FontInfo* font_info) |
| : table_blacklist_(NULL) { |
| font_info_ = font_info; |
| Initialize(); |
| } |
| |
| void FontAssembler::Initialize() { |
| font_factory_.Attach(sfntly::FontFactory::GetInstance()); |
| font_builder_.Attach(font_factory_->NewFontBuilder()); |
| } |
| |
| CALLER_ATTACH Font* FontAssembler::Assemble() { |
| // Assemble tables we can subset. |
| if (!AssembleCMapTable() || !AssembleGlyphAndLocaTables()) { |
| return NULL; |
| } |
| // For all other tables, either include them unmodified or don't at all. |
| const TableMap* common_table_map = |
| font_info_->GetTableMap(font_info_->fonts()->begin()->first); |
| for (TableMap::const_iterator it = common_table_map->begin(), |
| e = common_table_map->end(); it != e; ++it) { |
| if (table_blacklist_ |
| && table_blacklist_->find(it->first) != table_blacklist_->end()) { |
| continue; |
| } |
| font_builder_->NewTableBuilder(it->first, it->second->ReadFontData()); |
| } |
| return font_builder_->Build(); |
| } |
| |
| bool FontAssembler::AssembleCMapTable() { |
| // Creating the new CMapTable and the new format 4 CMap |
| Ptr<CMapTable::Builder> cmap_table_builder = |
| down_cast<CMapTable::Builder*> |
| (font_builder_->NewTableBuilder(Tag::cmap)); |
| if (!cmap_table_builder) |
| return false; |
| Ptr<CMapTable::CMapFormat4::Builder> cmap_builder = |
| down_cast<CMapTable::CMapFormat4::Builder*> |
| (cmap_table_builder->NewCMapBuilder(CMapFormat::kFormat4, |
| CMapTable::WINDOWS_BMP)); |
| if (!cmap_builder) |
| return false; |
| // Creating the segments and the glyph id array |
| CharacterMap* chars_to_glyph_ids = font_info_->chars_to_glyph_ids(); |
| SegmentList* segment_list = new SegmentList; |
| IntegerList* glyph_id_array = new IntegerList; |
| int32_t last_chararacter = -2; |
| int32_t last_offset = 0; |
| Ptr<CMapTable::CMapFormat4::Builder::Segment> current_segment; |
| |
| // For simplicity, we will have one segment per contiguous range. |
| // To test the algorithm, we've replaced the original CMap with the CMap |
| // generated by this code without removing any character. |
| // Tuffy.ttf: CMap went from 3146 to 3972 bytes (1.7% to 2.17% of file) |
| // AnonymousPro.ttf: CMap went from 1524 to 1900 bytes (0.96% to 1.2%) |
| for (CharacterMap::iterator it = chars_to_glyph_ids->begin(), |
| e = chars_to_glyph_ids->end(); it != e; ++it) { |
| int32_t character = it->first; |
| int32_t glyph_id = it->second.glyph_id(); |
| if (character != last_chararacter + 1) { // new segment |
| if (current_segment != NULL) { |
| current_segment->set_end_count(last_chararacter); |
| segment_list->push_back(current_segment); |
| } |
| // start_code = character |
| // end_code = -1 (unknown for now) |
| // id_delta = 0 (we don't use id_delta for this representation) |
| // id_range_offset = last_offset (offset into the glyph_id_array) |
| current_segment = |
| new CMapTable::CMapFormat4::Builder:: |
| Segment(character, -1, 0, last_offset); |
| } |
| glyph_id_array->push_back(glyph_id); |
| last_offset += DataSize::kSHORT; |
| last_chararacter = character; |
| } |
| // The last segment is still open. |
| current_segment->set_end_count(last_chararacter); |
| segment_list->push_back(current_segment); |
| // Updating the id_range_offset for every segment. |
| for (int32_t i = 0, num_segs = segment_list->size(); i < num_segs; ++i) { |
| Ptr<CMapTable::CMapFormat4::Builder::Segment> segment = segment_list->at(i); |
| segment->set_id_range_offset(segment->id_range_offset() |
| + (num_segs - i + 1) * DataSize::kSHORT); |
| } |
| // Adding the final, required segment. |
| current_segment = |
| new CMapTable::CMapFormat4::Builder::Segment(0xffff, 0xffff, 1, 0); |
| segment_list->push_back(current_segment); |
| // Writing the segments and glyph id array to the CMap |
| cmap_builder->set_segments(segment_list); |
| cmap_builder->set_glyph_id_array(glyph_id_array); |
| delete segment_list; |
| delete glyph_id_array; |
| return true; |
| } |
| |
| bool FontAssembler::AssembleGlyphAndLocaTables() { |
| Ptr<LocaTable::Builder> loca_table_builder = |
| down_cast<LocaTable::Builder*> |
| (font_builder_->NewTableBuilder(Tag::loca)); |
| Ptr<GlyphTable::Builder> glyph_table_builder = |
| down_cast<GlyphTable::Builder*> |
| (font_builder_->NewTableBuilder(Tag::glyf)); |
| |
| GlyphIdSet* resolved_glyph_ids = font_info_->resolved_glyph_ids(); |
| IntegerList loca_list; |
| // Basic sanity check: all LOCA tables are of the same size |
| // This is necessary but not suficient! |
| int32_t previous_size = -1; |
| for (FontIdMap::iterator it = font_info_->fonts()->begin(); |
| it != font_info_->fonts()->end(); ++it) { |
| Ptr<LocaTable> loca_table = |
| down_cast<LocaTable*>(font_info_->GetTable(it->first, Tag::loca)); |
| int32_t current_size = loca_table->header_length(); |
| if (previous_size != -1 && current_size != previous_size) { |
| return false; |
| } |
| previous_size = current_size; |
| } |
| |
| // Assuming all fonts referenced by the FontInfo are the subsets of the same |
| // font, their loca tables should all have the same sizes. |
| // We'll just get the size of the first font's LOCA table for simplicty. |
| Ptr<LocaTable> first_loca_table = |
| down_cast<LocaTable*> |
| (font_info_->GetTable(font_info_->fonts()->begin()->first, Tag::loca)); |
| int32_t num_loca_glyphs = first_loca_table->num_glyphs(); |
| loca_list.resize(num_loca_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 (GlyphIdSet::iterator it = resolved_glyph_ids->begin(), |
| e = resolved_glyph_ids->end(); it != e; ++it) { |
| // Get the glyph for this resolved_glyph_id. |
| int32_t resolved_glyph_id = it->glyph_id(); |
| int32_t font_id = it->font_id(); |
| // Get the LOCA table for the current glyph id. |
| Ptr<LocaTable> loca_table = |
| down_cast<LocaTable*> |
| (font_info_->GetTable(font_id, Tag::loca)); |
| int32_t length = loca_table->GlyphLength(resolved_glyph_id); |
| int32_t offset = loca_table->GlyphOffset(resolved_glyph_id); |
| |
| // Get the GLYF table for the current glyph id. |
| Ptr<GlyphTable> glyph_table = |
| down_cast<GlyphTable*> |
| (font_info_->GetTable(font_id, Tag::glyf)); |
| GlyphPtr glyph; |
| glyph.Attach(glyph_table->GetGlyph(offset, length)); |
| |
| // The data reference by the glyph is copied into a new glyph and |
| // added to the glyph_builders belonging to the glyph_table_builder. |
| // When Build gets called, all the glyphs will be built. |
| Ptr<ReadableFontData> data = glyph->ReadFontData(); |
| Ptr<WritableFontData> 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); |
| |
| // If there are missing glyphs between the last glyph_id and the |
| // current resolved_glyph_id, since the LOCA table needs to have the same |
| // size, the offset is kept the same. |
| for (int32_t i = last_glyph_id + 1; i <= resolved_glyph_id; ++i) |
| loca_list[i] = last_offset; |
| last_offset += length; |
| loca_list[resolved_glyph_id + 1] = last_offset; |
| last_glyph_id = resolved_glyph_id + 1; |
| } |
| // If there are missing glyph ids, their loca entries must all point |
| // to the same offset as the last valid glyph id making them all zero length. |
| for (int32_t i = last_glyph_id + 1; i <= num_loca_glyphs; ++i) |
| loca_list[i] = last_offset; |
| loca_table_builder->SetLocaList(&loca_list); |
| return true; |
| } |
| } |