Selim Gurun | 30d4e1f | 2013-08-15 12:46:15 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2011 Google Inc. All Rights Reserved. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "subtly/font_assembler.h" |
| 18 | |
| 19 | #include <stdio.h> |
| 20 | |
| 21 | #include <set> |
| 22 | #include <map> |
| 23 | |
| 24 | #include "sfntly/tag.h" |
| 25 | #include "sfntly/font.h" |
| 26 | #include "sfntly/font_factory.h" |
| 27 | #include "sfntly/table/core/cmap_table.h" |
| 28 | #include "sfntly/table/truetype/loca_table.h" |
| 29 | #include "sfntly/table/truetype/glyph_table.h" |
| 30 | #include "sfntly/table/core/maximum_profile_table.h" |
| 31 | #include "sfntly/port/type.h" |
| 32 | #include "sfntly/port/refcount.h" |
| 33 | #include "subtly/font_info.h" |
| 34 | |
| 35 | namespace subtly { |
| 36 | using namespace sfntly; |
| 37 | |
| 38 | FontAssembler::FontAssembler(FontInfo* font_info, |
| 39 | IntegerSet* table_blacklist) |
| 40 | : table_blacklist_(table_blacklist) { |
| 41 | font_info_ = font_info; |
| 42 | Initialize(); |
| 43 | } |
| 44 | |
| 45 | FontAssembler::FontAssembler(FontInfo* font_info) |
| 46 | : table_blacklist_(NULL) { |
| 47 | font_info_ = font_info; |
| 48 | Initialize(); |
| 49 | } |
| 50 | |
| 51 | void FontAssembler::Initialize() { |
| 52 | font_factory_.Attach(sfntly::FontFactory::GetInstance()); |
| 53 | font_builder_.Attach(font_factory_->NewFontBuilder()); |
| 54 | } |
| 55 | |
| 56 | CALLER_ATTACH Font* FontAssembler::Assemble() { |
| 57 | // Assemble tables we can subset. |
| 58 | if (!AssembleCMapTable() || !AssembleGlyphAndLocaTables()) { |
| 59 | return NULL; |
| 60 | } |
| 61 | // For all other tables, either include them unmodified or don't at all. |
| 62 | const TableMap* common_table_map = |
| 63 | font_info_->GetTableMap(font_info_->fonts()->begin()->first); |
| 64 | for (TableMap::const_iterator it = common_table_map->begin(), |
| 65 | e = common_table_map->end(); it != e; ++it) { |
| 66 | if (table_blacklist_ |
| 67 | && table_blacklist_->find(it->first) != table_blacklist_->end()) { |
| 68 | continue; |
| 69 | } |
| 70 | font_builder_->NewTableBuilder(it->first, it->second->ReadFontData()); |
| 71 | } |
| 72 | return font_builder_->Build(); |
| 73 | } |
| 74 | |
| 75 | bool FontAssembler::AssembleCMapTable() { |
| 76 | // Creating the new CMapTable and the new format 4 CMap |
| 77 | Ptr<CMapTable::Builder> cmap_table_builder = |
| 78 | down_cast<CMapTable::Builder*> |
| 79 | (font_builder_->NewTableBuilder(Tag::cmap)); |
| 80 | if (!cmap_table_builder) |
| 81 | return false; |
| 82 | Ptr<CMapTable::CMapFormat4::Builder> cmap_builder = |
| 83 | down_cast<CMapTable::CMapFormat4::Builder*> |
| 84 | (cmap_table_builder->NewCMapBuilder(CMapFormat::kFormat4, |
| 85 | CMapTable::WINDOWS_BMP)); |
| 86 | if (!cmap_builder) |
| 87 | return false; |
| 88 | // Creating the segments and the glyph id array |
| 89 | CharacterMap* chars_to_glyph_ids = font_info_->chars_to_glyph_ids(); |
| 90 | SegmentList* segment_list = new SegmentList; |
| 91 | IntegerList* glyph_id_array = new IntegerList; |
| 92 | int32_t last_chararacter = -2; |
| 93 | int32_t last_offset = 0; |
| 94 | Ptr<CMapTable::CMapFormat4::Builder::Segment> current_segment; |
| 95 | |
| 96 | // For simplicity, we will have one segment per contiguous range. |
| 97 | // To test the algorithm, we've replaced the original CMap with the CMap |
| 98 | // generated by this code without removing any character. |
| 99 | // Tuffy.ttf: CMap went from 3146 to 3972 bytes (1.7% to 2.17% of file) |
| 100 | // AnonymousPro.ttf: CMap went from 1524 to 1900 bytes (0.96% to 1.2%) |
| 101 | for (CharacterMap::iterator it = chars_to_glyph_ids->begin(), |
| 102 | e = chars_to_glyph_ids->end(); it != e; ++it) { |
| 103 | int32_t character = it->first; |
| 104 | int32_t glyph_id = it->second.glyph_id(); |
| 105 | if (character != last_chararacter + 1) { // new segment |
| 106 | if (current_segment != NULL) { |
| 107 | current_segment->set_end_count(last_chararacter); |
| 108 | segment_list->push_back(current_segment); |
| 109 | } |
| 110 | // start_code = character |
| 111 | // end_code = -1 (unknown for now) |
| 112 | // id_delta = 0 (we don't use id_delta for this representation) |
| 113 | // id_range_offset = last_offset (offset into the glyph_id_array) |
| 114 | current_segment = |
| 115 | new CMapTable::CMapFormat4::Builder:: |
| 116 | Segment(character, -1, 0, last_offset); |
| 117 | } |
| 118 | glyph_id_array->push_back(glyph_id); |
| 119 | last_offset += DataSize::kSHORT; |
| 120 | last_chararacter = character; |
| 121 | } |
| 122 | // The last segment is still open. |
| 123 | current_segment->set_end_count(last_chararacter); |
| 124 | segment_list->push_back(current_segment); |
| 125 | // Updating the id_range_offset for every segment. |
| 126 | for (int32_t i = 0, num_segs = segment_list->size(); i < num_segs; ++i) { |
| 127 | Ptr<CMapTable::CMapFormat4::Builder::Segment> segment = segment_list->at(i); |
| 128 | segment->set_id_range_offset(segment->id_range_offset() |
| 129 | + (num_segs - i + 1) * DataSize::kSHORT); |
| 130 | } |
| 131 | // Adding the final, required segment. |
| 132 | current_segment = |
| 133 | new CMapTable::CMapFormat4::Builder::Segment(0xffff, 0xffff, 1, 0); |
| 134 | segment_list->push_back(current_segment); |
| 135 | // Writing the segments and glyph id array to the CMap |
| 136 | cmap_builder->set_segments(segment_list); |
| 137 | cmap_builder->set_glyph_id_array(glyph_id_array); |
| 138 | delete segment_list; |
| 139 | delete glyph_id_array; |
| 140 | return true; |
| 141 | } |
| 142 | |
| 143 | bool FontAssembler::AssembleGlyphAndLocaTables() { |
| 144 | Ptr<LocaTable::Builder> loca_table_builder = |
| 145 | down_cast<LocaTable::Builder*> |
| 146 | (font_builder_->NewTableBuilder(Tag::loca)); |
| 147 | Ptr<GlyphTable::Builder> glyph_table_builder = |
| 148 | down_cast<GlyphTable::Builder*> |
| 149 | (font_builder_->NewTableBuilder(Tag::glyf)); |
| 150 | |
| 151 | GlyphIdSet* resolved_glyph_ids = font_info_->resolved_glyph_ids(); |
| 152 | IntegerList loca_list; |
| 153 | // Basic sanity check: all LOCA tables are of the same size |
| 154 | // This is necessary but not suficient! |
| 155 | int32_t previous_size = -1; |
| 156 | for (FontIdMap::iterator it = font_info_->fonts()->begin(); |
| 157 | it != font_info_->fonts()->end(); ++it) { |
| 158 | Ptr<LocaTable> loca_table = |
| 159 | down_cast<LocaTable*>(font_info_->GetTable(it->first, Tag::loca)); |
| 160 | int32_t current_size = loca_table->header_length(); |
| 161 | if (previous_size != -1 && current_size != previous_size) { |
| 162 | return false; |
| 163 | } |
| 164 | previous_size = current_size; |
| 165 | } |
| 166 | |
| 167 | // Assuming all fonts referenced by the FontInfo are the subsets of the same |
| 168 | // font, their loca tables should all have the same sizes. |
| 169 | // We'll just get the size of the first font's LOCA table for simplicty. |
| 170 | Ptr<LocaTable> first_loca_table = |
| 171 | down_cast<LocaTable*> |
| 172 | (font_info_->GetTable(font_info_->fonts()->begin()->first, Tag::loca)); |
| 173 | int32_t num_loca_glyphs = first_loca_table->num_glyphs(); |
| 174 | loca_list.resize(num_loca_glyphs); |
| 175 | loca_list.push_back(0); |
| 176 | int32_t last_glyph_id = 0; |
| 177 | int32_t last_offset = 0; |
| 178 | GlyphTable::GlyphBuilderList* glyph_builders = |
| 179 | glyph_table_builder->GlyphBuilders(); |
| 180 | |
| 181 | for (GlyphIdSet::iterator it = resolved_glyph_ids->begin(), |
| 182 | e = resolved_glyph_ids->end(); it != e; ++it) { |
| 183 | // Get the glyph for this resolved_glyph_id. |
| 184 | int32_t resolved_glyph_id = it->glyph_id(); |
| 185 | int32_t font_id = it->font_id(); |
| 186 | // Get the LOCA table for the current glyph id. |
| 187 | Ptr<LocaTable> loca_table = |
| 188 | down_cast<LocaTable*> |
| 189 | (font_info_->GetTable(font_id, Tag::loca)); |
| 190 | int32_t length = loca_table->GlyphLength(resolved_glyph_id); |
| 191 | int32_t offset = loca_table->GlyphOffset(resolved_glyph_id); |
| 192 | |
| 193 | // Get the GLYF table for the current glyph id. |
| 194 | Ptr<GlyphTable> glyph_table = |
| 195 | down_cast<GlyphTable*> |
| 196 | (font_info_->GetTable(font_id, Tag::glyf)); |
| 197 | GlyphPtr glyph; |
| 198 | glyph.Attach(glyph_table->GetGlyph(offset, length)); |
| 199 | |
| 200 | // The data reference by the glyph is copied into a new glyph and |
| 201 | // added to the glyph_builders belonging to the glyph_table_builder. |
| 202 | // When Build gets called, all the glyphs will be built. |
| 203 | Ptr<ReadableFontData> data = glyph->ReadFontData(); |
| 204 | Ptr<WritableFontData> copy_data; |
| 205 | copy_data.Attach(WritableFontData::CreateWritableFontData(data->Length())); |
| 206 | data->CopyTo(copy_data); |
| 207 | GlyphBuilderPtr glyph_builder; |
| 208 | glyph_builder.Attach(glyph_table_builder->GlyphBuilder(copy_data)); |
| 209 | glyph_builders->push_back(glyph_builder); |
| 210 | |
| 211 | // If there are missing glyphs between the last glyph_id and the |
| 212 | // current resolved_glyph_id, since the LOCA table needs to have the same |
| 213 | // size, the offset is kept the same. |
Colin Cross | 13a8243 | 2017-04-07 10:50:33 -0700 | [diff] [blame] | 214 | loca_list.resize(std::max(loca_list.size(), |
| 215 | static_cast<size_t>(resolved_glyph_id + 2))); |
Selim Gurun | 30d4e1f | 2013-08-15 12:46:15 -0700 | [diff] [blame] | 216 | for (int32_t i = last_glyph_id + 1; i <= resolved_glyph_id; ++i) |
| 217 | loca_list[i] = last_offset; |
| 218 | last_offset += length; |
| 219 | loca_list[resolved_glyph_id + 1] = last_offset; |
| 220 | last_glyph_id = resolved_glyph_id + 1; |
| 221 | } |
| 222 | // If there are missing glyph ids, their loca entries must all point |
| 223 | // to the same offset as the last valid glyph id making them all zero length. |
| 224 | for (int32_t i = last_glyph_id + 1; i <= num_loca_glyphs; ++i) |
| 225 | loca_list[i] = last_offset; |
| 226 | loca_table_builder->SetLocaList(&loca_list); |
| 227 | return true; |
| 228 | } |
| 229 | } |