1/*
2 * Copyright (C) 2012 Koji Ishii <kojiishi@gmail.com>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1.  Redistributions of source code must retain the above copyright
8 *     notice, this list of conditions and the following disclaimer.
9 * 2.  Redistributions in binary form must reproduce the above copyright
10 *     notice, this list of conditions and the following disclaimer in the
11 *     documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26#if ENABLE(OPENTYPE_VERTICAL)
27#include "platform/fonts/opentype/OpenTypeVerticalData.h"
28
29#include "platform/SharedBuffer.h"
30#include "platform/fonts/SimpleFontData.h"
31#include "platform/fonts/GlyphPage.h"
32#include "platform/fonts/opentype/OpenTypeTypes.h"
33#include "platform/geometry/FloatRect.h"
34#include "wtf/RefPtr.h"
35
36namespace blink {
37namespace OpenType {
38
39const uint32_t GSUBTag = OT_MAKE_TAG('G', 'S', 'U', 'B');
40const uint32_t HheaTag = OT_MAKE_TAG('h', 'h', 'e', 'a');
41const uint32_t HmtxTag = OT_MAKE_TAG('h', 'm', 't', 'x');
42const uint32_t VheaTag = OT_MAKE_TAG('v', 'h', 'e', 'a');
43const uint32_t VmtxTag = OT_MAKE_TAG('v', 'm', 't', 'x');
44const uint32_t VORGTag = OT_MAKE_TAG('V', 'O', 'R', 'G');
45
46const uint32_t DefaultScriptTag = OT_MAKE_TAG('D', 'F', 'L', 'T');
47
48const uint32_t VertFeatureTag = OT_MAKE_TAG('v', 'e', 'r', 't');
49
50#pragma pack(1)
51
52struct HheaTable {
53    OpenType::Fixed version;
54    OpenType::Int16 ascender;
55    OpenType::Int16 descender;
56    OpenType::Int16 lineGap;
57    OpenType::Int16 advanceWidthMax;
58    OpenType::Int16 minLeftSideBearing;
59    OpenType::Int16 minRightSideBearing;
60    OpenType::Int16 xMaxExtent;
61    OpenType::Int16 caretSlopeRise;
62    OpenType::Int16 caretSlopeRun;
63    OpenType::Int16 caretOffset;
64    OpenType::Int16 reserved[4];
65    OpenType::Int16 metricDataFormat;
66    OpenType::UInt16 numberOfHMetrics;
67};
68
69struct VheaTable {
70    OpenType::Fixed version;
71    OpenType::Int16 ascent;
72    OpenType::Int16 descent;
73    OpenType::Int16 lineGap;
74    OpenType::Int16 advanceHeightMax;
75    OpenType::Int16 minTopSideBearing;
76    OpenType::Int16 minBottomSideBearing;
77    OpenType::Int16 yMaxExtent;
78    OpenType::Int16 caretSlopeRise;
79    OpenType::Int16 caretSlopeRun;
80    OpenType::Int16 caretOffset;
81    OpenType::Int16 reserved[4];
82    OpenType::Int16 metricDataFormat;
83    OpenType::UInt16 numOfLongVerMetrics;
84};
85
86struct HmtxTable {
87    struct Entry {
88        OpenType::UInt16 advanceWidth;
89        OpenType::Int16 lsb;
90    } entries[1];
91};
92
93struct VmtxTable {
94    struct Entry {
95        OpenType::UInt16 advanceHeight;
96        OpenType::Int16 topSideBearing;
97    } entries[1];
98};
99
100struct VORGTable {
101    OpenType::UInt16 majorVersion;
102    OpenType::UInt16 minorVersion;
103    OpenType::Int16 defaultVertOriginY;
104    OpenType::UInt16 numVertOriginYMetrics;
105    struct VertOriginYMetrics {
106        OpenType::UInt16 glyphIndex;
107        OpenType::Int16 vertOriginY;
108    } vertOriginYMetrics[1];
109
110    size_t requiredSize() const { return sizeof(*this) + sizeof(VertOriginYMetrics) * (numVertOriginYMetrics - 1); }
111};
112
113struct CoverageTable : TableBase {
114    OpenType::UInt16 coverageFormat;
115};
116
117struct Coverage1Table : CoverageTable {
118    OpenType::UInt16 glyphCount;
119    OpenType::GlyphID glyphArray[1];
120};
121
122struct Coverage2Table : CoverageTable {
123    OpenType::UInt16 rangeCount;
124    struct RangeRecord {
125        OpenType::GlyphID start;
126        OpenType::GlyphID end;
127        OpenType::UInt16 startCoverageIndex;
128    } ranges[1];
129};
130
131struct SubstitutionSubTable : TableBase {
132    OpenType::UInt16 substFormat;
133    OpenType::Offset coverageOffset;
134
135    const CoverageTable* coverage(const SharedBuffer& buffer) const { return validateOffset<CoverageTable>(buffer, coverageOffset); }
136};
137
138struct SingleSubstitution2SubTable : SubstitutionSubTable {
139    OpenType::UInt16 glyphCount;
140    OpenType::GlyphID substitute[1];
141};
142
143struct LookupTable : TableBase {
144    OpenType::UInt16 lookupType;
145    OpenType::UInt16 lookupFlag;
146    OpenType::UInt16 subTableCount;
147    OpenType::Offset subTableOffsets[1];
148    // OpenType::UInt16 markFilteringSet; this field comes after variable length, so offset is determined dynamically.
149
150    bool getSubstitutions(HashMap<Glyph, Glyph>* map, const SharedBuffer& buffer) const
151    {
152        uint16_t countSubTable = subTableCount;
153        if (!isValidEnd(buffer, &subTableOffsets[countSubTable]))
154            return false;
155        if (lookupType != 1) // "Single Substitution Subtable" is all what we support
156            return false;
157        for (uint16_t i = 0; i < countSubTable; ++i) {
158            const SubstitutionSubTable* substitution = validateOffset<SubstitutionSubTable>(buffer, subTableOffsets[i]);
159            if (!substitution)
160                return false;
161            const CoverageTable* coverage = substitution->coverage(buffer);
162            if (!coverage)
163                return false;
164            if (substitution->substFormat != 2) // "Single Substitution Format 2" is all what we support
165                return false;
166            const SingleSubstitution2SubTable* singleSubstitution2 = validatePtr<SingleSubstitution2SubTable>(buffer, substitution);
167            if (!singleSubstitution2)
168                return false;
169            uint16_t countTo = singleSubstitution2->glyphCount;
170            if (!isValidEnd(buffer, &singleSubstitution2->substitute[countTo]))
171                return false;
172            switch (coverage->coverageFormat) {
173            case 1: { // Coverage Format 1 (e.g., MS Gothic)
174                const Coverage1Table* coverage1 = validatePtr<Coverage1Table>(buffer, coverage);
175                if (!coverage1)
176                    return false;
177                uint16_t countFrom = coverage1->glyphCount;
178                if (!isValidEnd(buffer, &coverage1->glyphArray[countFrom]) || countTo != countFrom)
179                    return false;
180                for (uint16_t i = 0; i < countTo; ++i)
181                    map->set(coverage1->glyphArray[i], singleSubstitution2->substitute[i]);
182                break;
183            }
184            case 2: { // Coverage Format 2 (e.g., Adobe Kozuka Gothic)
185                const Coverage2Table* coverage2 = validatePtr<Coverage2Table>(buffer, coverage);
186                if (!coverage2)
187                    return false;
188                uint16_t countRange = coverage2->rangeCount;
189                if (!isValidEnd(buffer, &coverage2->ranges[countRange]))
190                    return false;
191                for (uint16_t i = 0, indexTo = 0; i < countRange; ++i) {
192                    uint16_t from = coverage2->ranges[i].start;
193                    uint16_t fromEnd = coverage2->ranges[i].end + 1; // OpenType "end" is inclusive
194                    if (indexTo + (fromEnd - from) > countTo)
195                        return false;
196                    for (; from != fromEnd; ++from, ++indexTo)
197                        map->set(from, singleSubstitution2->substitute[indexTo]);
198                }
199                break;
200            }
201            default:
202                return false;
203            }
204        }
205        return true;
206    }
207};
208
209struct LookupList : TableBase {
210    OpenType::UInt16 lookupCount;
211    OpenType::Offset lookupOffsets[1];
212
213    const LookupTable* lookup(uint16_t index, const SharedBuffer& buffer) const
214    {
215        uint16_t count = lookupCount;
216        if (index >= count || !isValidEnd(buffer, &lookupOffsets[count]))
217            return 0;
218        return validateOffset<LookupTable>(buffer, lookupOffsets[index]);
219    }
220};
221
222struct FeatureTable : TableBase {
223    OpenType::Offset featureParams;
224    OpenType::UInt16 lookupCount;
225    OpenType::UInt16 lookupListIndex[1];
226
227    bool getGlyphSubstitutions(const LookupList* lookups, HashMap<Glyph, Glyph>* map, const SharedBuffer& buffer) const
228    {
229        uint16_t count = lookupCount;
230        if (!isValidEnd(buffer, &lookupListIndex[count]))
231            return false;
232        for (uint16_t i = 0; i < count; ++i) {
233            const LookupTable* lookup = lookups->lookup(lookupListIndex[i], buffer);
234            if (!lookup || !lookup->getSubstitutions(map, buffer))
235                return false;
236        }
237        return true;
238    }
239};
240
241struct FeatureList : TableBase {
242    OpenType::UInt16 featureCount;
243    struct FeatureRecord {
244        OpenType::Tag featureTag;
245        OpenType::Offset featureOffset;
246    } features[1];
247
248    const FeatureTable* feature(uint16_t index, OpenType::Tag tag, const SharedBuffer& buffer) const
249    {
250        uint16_t count = featureCount;
251        if (index >= count || !isValidEnd(buffer, &features[count]))
252            return 0;
253        if (features[index].featureTag == tag)
254            return validateOffset<FeatureTable>(buffer, features[index].featureOffset);
255        return 0;
256    }
257
258    const FeatureTable* findFeature(OpenType::Tag tag, const SharedBuffer& buffer) const
259    {
260        for (uint16_t i = 0; i < featureCount; ++i) {
261            if (isValidEnd(buffer, &features[i]) && features[i].featureTag == tag)
262                return validateOffset<FeatureTable>(buffer, features[i].featureOffset);
263        }
264        return 0;
265    }
266};
267
268struct LangSysTable : TableBase {
269    OpenType::Offset lookupOrder;
270    OpenType::UInt16 reqFeatureIndex;
271    OpenType::UInt16 featureCount;
272    OpenType::UInt16 featureIndex[1];
273
274    const FeatureTable* feature(OpenType::Tag featureTag, const FeatureList* features, const SharedBuffer& buffer) const
275    {
276        uint16_t count = featureCount;
277        if (!isValidEnd(buffer, &featureIndex[count]))
278            return 0;
279        for (uint16_t i = 0; i < count; ++i) {
280            const FeatureTable* featureTable = features->feature(featureIndex[i], featureTag, buffer);
281            if (featureTable)
282                return featureTable;
283        }
284        return 0;
285    }
286};
287
288struct ScriptTable : TableBase {
289    OpenType::Offset defaultLangSysOffset;
290    OpenType::UInt16 langSysCount;
291    struct LangSysRecord {
292        OpenType::Tag langSysTag;
293        OpenType::Offset langSysOffset;
294    } langSysRecords[1];
295
296    const LangSysTable* defaultLangSys(const SharedBuffer& buffer) const
297    {
298        uint16_t count = langSysCount;
299        if (!isValidEnd(buffer, &langSysRecords[count]))
300            return 0;
301        uint16_t offset = defaultLangSysOffset;
302        if (offset)
303            return validateOffset<LangSysTable>(buffer, offset);
304        if (count)
305            return validateOffset<LangSysTable>(buffer, langSysRecords[0].langSysOffset);
306        return 0;
307    }
308};
309
310struct ScriptList : TableBase {
311    OpenType::UInt16 scriptCount;
312    struct ScriptRecord {
313        OpenType::Tag scriptTag;
314        OpenType::Offset scriptOffset;
315    } scripts[1];
316
317    const ScriptTable* script(OpenType::Tag tag, const SharedBuffer& buffer) const
318    {
319        uint16_t count = scriptCount;
320        if (!isValidEnd(buffer, &scripts[count]))
321            return 0;
322        for (uint16_t i = 0; i < count; ++i) {
323            if (scripts[i].scriptTag == tag)
324                return validateOffset<ScriptTable>(buffer, scripts[i].scriptOffset);
325        }
326        return 0;
327    }
328
329    const ScriptTable* defaultScript(const SharedBuffer& buffer) const
330    {
331        uint16_t count = scriptCount;
332        if (!count || !isValidEnd(buffer, &scripts[count]))
333            return 0;
334        const ScriptTable* scriptOfDefaultTag = script(OpenType::DefaultScriptTag, buffer);
335        if (scriptOfDefaultTag)
336            return scriptOfDefaultTag;
337        return validateOffset<ScriptTable>(buffer, scripts[0].scriptOffset);
338    }
339
340    const LangSysTable* defaultLangSys(const SharedBuffer& buffer) const
341    {
342        const ScriptTable* scriptTable = defaultScript(buffer);
343        if (!scriptTable)
344            return 0;
345        return scriptTable->defaultLangSys(buffer);
346    }
347};
348
349struct GSUBTable : TableBase {
350    OpenType::Fixed version;
351    OpenType::Offset scriptListOffset;
352    OpenType::Offset featureListOffset;
353    OpenType::Offset lookupListOffset;
354
355    const ScriptList* scriptList(const SharedBuffer& buffer) const { return validateOffset<ScriptList>(buffer, scriptListOffset); }
356    const FeatureList* featureList(const SharedBuffer& buffer) const { return validateOffset<FeatureList>(buffer, featureListOffset); }
357    const LookupList* lookupList(const SharedBuffer& buffer) const { return validateOffset<LookupList>(buffer, lookupListOffset); }
358
359    const LangSysTable* defaultLangSys(const SharedBuffer& buffer) const
360    {
361        const ScriptList* scripts = scriptList(buffer);
362        if (!scripts)
363            return 0;
364        return scripts->defaultLangSys(buffer);
365    }
366
367    const FeatureTable* feature(OpenType::Tag featureTag, const SharedBuffer& buffer) const
368    {
369        const LangSysTable* langSys = defaultLangSys(buffer);
370        const FeatureList* features = featureList(buffer);
371        if (!features)
372            return 0;
373        const FeatureTable* feature = 0;
374        if (langSys)
375            feature = langSys->feature(featureTag, features, buffer);
376        if (!feature) {
377            // If the font has no langSys table, or has no default script and the first script doesn't
378            // have the requested feature, then use the first matching feature directly.
379            feature = features->findFeature(featureTag, buffer);
380        }
381        return feature;
382    }
383
384    bool getVerticalGlyphSubstitutions(HashMap<Glyph, Glyph>* map, const SharedBuffer& buffer) const
385    {
386        const FeatureTable* verticalFeatureTable = feature(OpenType::VertFeatureTag, buffer);
387        if (!verticalFeatureTable)
388            return false;
389        const LookupList* lookups = lookupList(buffer);
390        return lookups && verticalFeatureTable->getGlyphSubstitutions(lookups, map, buffer);
391    }
392};
393
394#pragma pack()
395
396} // namespace OpenType
397
398OpenTypeVerticalData::OpenTypeVerticalData(const FontPlatformData& platformData)
399    : m_defaultVertOriginY(0)
400{
401    loadMetrics(platformData);
402    loadVerticalGlyphSubstitutions(platformData);
403}
404
405void OpenTypeVerticalData::loadMetrics(const FontPlatformData& platformData)
406{
407    // Load hhea and hmtx to get x-component of vertical origins.
408    // If these tables are missing, it's not an OpenType font.
409    RefPtr<SharedBuffer> buffer = platformData.openTypeTable(OpenType::HheaTag);
410    const OpenType::HheaTable* hhea = OpenType::validateTable<OpenType::HheaTable>(buffer);
411    if (!hhea)
412        return;
413    uint16_t countHmtxEntries = hhea->numberOfHMetrics;
414    if (!countHmtxEntries) {
415        WTF_LOG_ERROR("Invalid numberOfHMetrics");
416        return;
417    }
418
419    buffer = platformData.openTypeTable(OpenType::HmtxTag);
420    const OpenType::HmtxTable* hmtx = OpenType::validateTable<OpenType::HmtxTable>(buffer, countHmtxEntries);
421    if (!hmtx) {
422        WTF_LOG_ERROR("hhea exists but hmtx does not (or broken)");
423        return;
424    }
425    m_advanceWidths.resize(countHmtxEntries);
426    for (uint16_t i = 0; i < countHmtxEntries; ++i)
427        m_advanceWidths[i] = hmtx->entries[i].advanceWidth;
428
429    // Load vhea first. This table is required for fonts that support vertical flow.
430    buffer = platformData.openTypeTable(OpenType::VheaTag);
431    const OpenType::VheaTable* vhea = OpenType::validateTable<OpenType::VheaTable>(buffer);
432    if (!vhea)
433        return;
434    uint16_t countVmtxEntries = vhea->numOfLongVerMetrics;
435    if (!countVmtxEntries) {
436        WTF_LOG_ERROR("Invalid numOfLongVerMetrics");
437        return;
438    }
439
440    // Load VORG. This table is optional.
441    buffer = platformData.openTypeTable(OpenType::VORGTag);
442    const OpenType::VORGTable* vorg = OpenType::validateTable<OpenType::VORGTable>(buffer);
443    if (vorg && buffer->size() >= vorg->requiredSize()) {
444        m_defaultVertOriginY = vorg->defaultVertOriginY;
445        uint16_t countVertOriginYMetrics = vorg->numVertOriginYMetrics;
446        if (!countVertOriginYMetrics) {
447            // Add one entry so that hasVORG() becomes true
448            m_vertOriginY.set(0, m_defaultVertOriginY);
449        } else {
450            for (uint16_t i = 0; i < countVertOriginYMetrics; ++i) {
451                const OpenType::VORGTable::VertOriginYMetrics& metrics = vorg->vertOriginYMetrics[i];
452                m_vertOriginY.set(metrics.glyphIndex, metrics.vertOriginY);
453            }
454        }
455    }
456
457    // Load vmtx then. This table is required for fonts that support vertical flow.
458    buffer = platformData.openTypeTable(OpenType::VmtxTag);
459    const OpenType::VmtxTable* vmtx = OpenType::validateTable<OpenType::VmtxTable>(buffer, countVmtxEntries);
460    if (!vmtx) {
461        WTF_LOG_ERROR("vhea exists but vmtx does not (or broken)");
462        return;
463    }
464    m_advanceHeights.resize(countVmtxEntries);
465    for (uint16_t i = 0; i < countVmtxEntries; ++i)
466        m_advanceHeights[i] = vmtx->entries[i].advanceHeight;
467
468    // VORG is preferred way to calculate vertical origin than vmtx,
469    // so load topSideBearing from vmtx only if VORG is missing.
470    if (hasVORG())
471        return;
472
473    size_t sizeExtra = buffer->size() - sizeof(OpenType::VmtxTable::Entry) * countVmtxEntries;
474    if (sizeExtra % sizeof(OpenType::Int16)) {
475        WTF_LOG_ERROR("vmtx has incorrect tsb count");
476        return;
477    }
478    size_t countTopSideBearings = countVmtxEntries + sizeExtra / sizeof(OpenType::Int16);
479    m_topSideBearings.resize(countTopSideBearings);
480    size_t i;
481    for (i = 0; i < countVmtxEntries; ++i)
482        m_topSideBearings[i] = vmtx->entries[i].topSideBearing;
483    if (i < countTopSideBearings) {
484        const OpenType::Int16* pTopSideBearingsExtra = reinterpret_cast<const OpenType::Int16*>(&vmtx->entries[countVmtxEntries]);
485        for (; i < countTopSideBearings; ++i, ++pTopSideBearingsExtra)
486            m_topSideBearings[i] = *pTopSideBearingsExtra;
487    }
488}
489
490void OpenTypeVerticalData::loadVerticalGlyphSubstitutions(const FontPlatformData& platformData)
491{
492    RefPtr<SharedBuffer> buffer = platformData.openTypeTable(OpenType::GSUBTag);
493    const OpenType::GSUBTable* gsub = OpenType::validateTable<OpenType::GSUBTable>(buffer);
494    if (gsub)
495        gsub->getVerticalGlyphSubstitutions(&m_verticalGlyphMap, *buffer.get());
496}
497
498float OpenTypeVerticalData::advanceHeight(const SimpleFontData* font, Glyph glyph) const
499{
500    size_t countHeights = m_advanceHeights.size();
501    if (countHeights) {
502        uint16_t advanceFUnit = m_advanceHeights[glyph < countHeights ? glyph : countHeights - 1];
503        float advance = advanceFUnit * font->sizePerUnit();
504        return advance;
505    }
506
507    // No vertical info in the font file; use height as advance.
508    return font->fontMetrics().height();
509}
510
511void OpenTypeVerticalData::getVerticalTranslationsForGlyphs(const SimpleFontData* font, const Glyph* glyphs, size_t count, float* outXYArray) const
512{
513    size_t countWidths = m_advanceWidths.size();
514    ASSERT(countWidths > 0);
515    const FontMetrics& metrics = font->fontMetrics();
516    float sizePerUnit = font->sizePerUnit();
517    float ascent = metrics.ascent();
518    bool useVORG = hasVORG();
519    size_t countTopSideBearings = m_topSideBearings.size();
520    float defaultVertOriginY = std::numeric_limits<float>::quiet_NaN();
521    for (float* end = &(outXYArray[count * 2]); outXYArray != end; ++glyphs, outXYArray += 2) {
522        Glyph glyph = *glyphs;
523        uint16_t widthFUnit = m_advanceWidths[glyph < countWidths ? glyph : countWidths - 1];
524        float width = widthFUnit * sizePerUnit;
525        outXYArray[0] = -width / 2;
526
527        // For Y, try VORG first.
528        if (useVORG) {
529            int16_t vertOriginYFUnit = m_vertOriginY.get(glyph);
530            if (vertOriginYFUnit) {
531                outXYArray[1] = -vertOriginYFUnit * sizePerUnit;
532                continue;
533            }
534            if (std::isnan(defaultVertOriginY))
535                defaultVertOriginY = -m_defaultVertOriginY * sizePerUnit;
536            outXYArray[1] = defaultVertOriginY;
537            continue;
538        }
539
540        // If no VORG, try vmtx next.
541        if (countTopSideBearings) {
542            int16_t topSideBearingFUnit = m_topSideBearings[glyph < countTopSideBearings ? glyph : countTopSideBearings - 1];
543            float topSideBearing = topSideBearingFUnit * sizePerUnit;
544            FloatRect bounds = font->boundsForGlyph(glyph);
545            outXYArray[1] = bounds.y() - topSideBearing;
546            continue;
547        }
548
549        // No vertical info in the font file; use ascent as vertical origin.
550        outXYArray[1] = -ascent;
551    }
552}
553
554void OpenTypeVerticalData::substituteWithVerticalGlyphs(const SimpleFontData* font, GlyphPage* glyphPage, unsigned offset, unsigned length) const
555{
556    const HashMap<Glyph, Glyph>& map = m_verticalGlyphMap;
557    if (map.isEmpty())
558        return;
559
560    for (unsigned index = offset, end = offset + length; index < end; ++index) {
561        GlyphData glyphData = glyphPage->glyphDataForIndex(index);
562        if (glyphData.glyph && glyphData.fontData == font) {
563            Glyph to = map.get(glyphData.glyph);
564            if (to)
565                glyphPage->setGlyphDataForIndex(index, to, font);
566        }
567    }
568}
569
570} // namespace blink
571#endif // ENABLE(OPENTYPE_VERTICAL)
572