Remove WebCoreSystemInterface
[WebKit-https.git] / Source / WebCore / platform / graphics / cocoa / FontCascadeCocoa.mm
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2000 Dirk Mueller (mueller@kde.org)
5  * Copyright (C) 2003, 2006-2011, 2016 Apple Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #import "config.h"
24 #import "FontCascade.h"
25
26 #import "ComplexTextController.h"
27 #import "DashArray.h"
28 #import "Font.h"
29 #import "GlyphBuffer.h"
30 #import "GraphicsContext.h"
31 #import "LayoutRect.h"
32 #import "Logging.h"
33 #import <pal/spi/cg/CoreGraphicsSPI.h>
34 #import <pal/spi/cocoa/CoreTextSPI.h>
35 #if USE(APPKIT)
36 #import <AppKit/AppKit.h>
37 #endif
38 #import <wtf/MathExtras.h>
39
40 #if ENABLE(LETTERPRESS)
41 #import <pal/spi/ios/CoreUISPI.h>
42 #import <wtf/SoftLinking.h>
43
44 SOFT_LINK_PRIVATE_FRAMEWORK(CoreUI)
45 SOFT_LINK_CLASS(CoreUI, CUICatalog)
46 SOFT_LINK_CLASS(CoreUI, CUIStyleEffectConfiguration)
47
48 SOFT_LINK_FRAMEWORK(UIKit)
49 SOFT_LINK(UIKit, _UIKitGetTextEffectsCatalog, CUICatalog *, (void), ())
50 #endif
51
52 #ifdef __LP64__
53 #define URefCon void*
54 #else
55 #define URefCon UInt32
56 #endif
57
58 namespace WebCore {
59
60 bool FontCascade::canReturnFallbackFontsForComplexText()
61 {
62     return true;
63 }
64
65 bool FontCascade::canExpandAroundIdeographsInComplexText()
66 {
67     return true;
68 }
69
70 static inline void fillVectorWithHorizontalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef context, const CGSize* advances, unsigned count)
71 {
72     CGAffineTransform matrix = CGAffineTransformInvert(CGContextGetTextMatrix(context));
73     positions[0] = CGPointZero;
74     for (unsigned i = 1; i < count; ++i) {
75         CGSize advance = CGSizeApplyAffineTransform(advances[i - 1], matrix);
76         positions[i].x = positions[i - 1].x + advance.width;
77         positions[i].y = positions[i - 1].y + advance.height;
78     }
79 }
80
81 static inline bool shouldUseLetterpressEffect(const GraphicsContext& context)
82 {
83 #if ENABLE(LETTERPRESS)
84     return context.textDrawingMode() & TextModeLetterpress;
85 #else
86     UNUSED_PARAM(context);
87     return false;
88 #endif
89 }
90
91 static void showLetterpressedGlyphsWithAdvances(const FloatPoint& point, const Font& font, CGContextRef context, const CGGlyph* glyphs, const CGSize* advances, unsigned count)
92 {
93 #if ENABLE(LETTERPRESS)
94     if (!count)
95         return;
96
97     const FontPlatformData& platformData = font.platformData();
98     if (platformData.orientation() == Vertical) {
99         // FIXME: Implement support for vertical text. See <rdar://problem/13737298>.
100         return;
101     }
102
103     CGContextSetTextPosition(context, point.x(), point.y());
104     Vector<CGPoint, 256> positions(count);
105     fillVectorWithHorizontalGlyphPositions(positions, context, advances, count);
106
107     CTFontRef ctFont = platformData.ctFont();
108     CGContextSetFontSize(context, CTFontGetSize(ctFont));
109
110     static CUICatalog *catalog = _UIKitGetTextEffectsCatalog();
111     if (!catalog)
112         return;
113
114     static CUIStyleEffectConfiguration *styleConfiguration;
115     if (!styleConfiguration) {
116         styleConfiguration = [allocCUIStyleEffectConfigurationInstance() init];
117         styleConfiguration.useSimplifiedEffect = YES;
118     }
119
120     CGContextSetFont(context, adoptCF(CTFontCopyGraphicsFont(ctFont, nullptr)).get());
121     CGContextSetFontSize(context, platformData.size());
122
123     [catalog drawGlyphs:glyphs atPositions:positions.data() inContext:context withFont:ctFont count:count stylePresetName:@"_UIKitNewLetterpressStyle" styleConfiguration:styleConfiguration foregroundColor:CGContextGetFillColorAsColor(context)];
124
125     CGContextSetFont(context, nullptr);
126     CGContextSetFontSize(context, 0);
127 #else
128     UNUSED_PARAM(point);
129     UNUSED_PARAM(font);
130     UNUSED_PARAM(context);
131     UNUSED_PARAM(glyphs);
132     UNUSED_PARAM(advances);
133     UNUSED_PARAM(count);
134 #endif
135 }
136
137 static void showGlyphsWithAdvances(const FloatPoint& point, const Font& font, CGContextRef context, const CGGlyph* glyphs, const CGSize* advances, unsigned count)
138 {
139     if (!count)
140         return;
141
142     CGContextSetTextPosition(context, point.x(), point.y());
143
144     const FontPlatformData& platformData = font.platformData();
145     Vector<CGPoint, 256> positions(count);
146     if (platformData.orientation() == Vertical) {
147         CGAffineTransform rotateLeftTransform = CGAffineTransformMake(0, -1, 1, 0, 0, 0);
148         CGAffineTransform textMatrix = CGContextGetTextMatrix(context);
149         CGAffineTransform runMatrix = CGAffineTransformConcat(textMatrix, rotateLeftTransform);
150         ScopedTextMatrix savedMatrix(runMatrix, context);
151
152         Vector<CGSize, 256> translations(count);
153         CTFontGetVerticalTranslationsForGlyphs(platformData.ctFont(), glyphs, translations.data(), count);
154
155         CGAffineTransform transform = CGAffineTransformInvert(CGContextGetTextMatrix(context));
156
157         CGPoint position = FloatPoint(point.x(), point.y() + font.fontMetrics().floatAscent(IdeographicBaseline) - font.fontMetrics().floatAscent());
158         for (unsigned i = 0; i < count; ++i) {
159             CGSize translation = CGSizeApplyAffineTransform(translations[i], rotateLeftTransform);
160             positions[i] = CGPointApplyAffineTransform(CGPointMake(position.x - translation.width, position.y + translation.height), transform);
161             position.x += advances[i].width;
162             position.y += advances[i].height;
163         }
164         CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
165     } else {
166         fillVectorWithHorizontalGlyphPositions(positions, context, advances, count);
167         CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
168     }
169 }
170
171 static void setCGFontRenderingMode(GraphicsContext& context)
172 {
173     CGContextRef cgContext = context.platformContext();
174     CGContextSetShouldAntialiasFonts(cgContext, true);
175
176     CGAffineTransform contextTransform = CGContextGetCTM(cgContext);
177     bool isTranslationOrIntegralScale = WTF::isIntegral(contextTransform.a) && WTF::isIntegral(contextTransform.d) && contextTransform.b == 0.f && contextTransform.c == 0.f;
178     bool isRotated = ((contextTransform.b || contextTransform.c) && (contextTransform.a || contextTransform.d));
179     bool doSubpixelQuantization = isTranslationOrIntegralScale || (!isRotated && context.shouldSubpixelQuantizeFonts());
180
181     CGContextSetShouldSubpixelPositionFonts(cgContext, true);
182     CGContextSetShouldSubpixelQuantizeFonts(cgContext, doSubpixelQuantization);
183 }
184
185 void FontCascade::drawGlyphs(GraphicsContext& context, const Font& font, const GlyphBuffer& glyphBuffer, unsigned from, unsigned numGlyphs, const FloatPoint& anchorPoint, FontSmoothingMode smoothingMode)
186 {
187     const FontPlatformData& platformData = font.platformData();
188     if (!platformData.size())
189         return;
190
191     CGContextRef cgContext = context.platformContext();
192
193     bool shouldSmoothFonts;
194     bool changeFontSmoothing;
195     
196     switch (smoothingMode) {
197     case Antialiased: {
198         context.setShouldAntialias(true);
199         shouldSmoothFonts = false;
200         changeFontSmoothing = true;
201         break;
202     }
203     case SubpixelAntialiased: {
204         context.setShouldAntialias(true);
205         shouldSmoothFonts = true;
206         changeFontSmoothing = true;
207         break;
208     }
209     case NoSmoothing: {
210         context.setShouldAntialias(false);
211         shouldSmoothFonts = false;
212         changeFontSmoothing = true;
213         break;
214     }
215     case AutoSmoothing: {
216         shouldSmoothFonts = true;
217         changeFontSmoothing = false;
218         break;
219     }
220     }
221     
222     if (!shouldUseSmoothing()) {
223         shouldSmoothFonts = false;
224         changeFontSmoothing = true;
225     }
226
227 #if !PLATFORM(IOS)
228     bool originalShouldUseFontSmoothing = false;
229     if (changeFontSmoothing) {
230         originalShouldUseFontSmoothing = CGContextGetShouldSmoothFonts(cgContext);
231         CGContextSetShouldSmoothFonts(cgContext, shouldSmoothFonts);
232     }
233 #endif
234
235     bool useLetterpressEffect = shouldUseLetterpressEffect(context);
236     FloatPoint point = anchorPoint;
237
238     CGAffineTransform matrix = CGAffineTransformIdentity;
239     if (!platformData.isColorBitmapFont())
240         matrix = CTFontGetMatrix(platformData.font());
241     matrix.b = -matrix.b;
242     matrix.d = -matrix.d;
243     if (platformData.syntheticOblique()) {
244         static float obliqueSkew = tanf(syntheticObliqueAngle() * piFloat / 180);
245         if (platformData.orientation() == Vertical) {
246             if (font.isTextOrientationFallback())
247                 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, obliqueSkew, 0, 1, 0, 0));
248             else
249                 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, -obliqueSkew, 0, 1, 0, 0));
250         } else
251             matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -obliqueSkew, 1, 0, 0));
252     }
253     ScopedTextMatrix restorer(matrix, cgContext);
254
255     setCGFontRenderingMode(context);
256     CGContextSetFontSize(cgContext, platformData.size());
257
258
259     FloatSize shadowOffset;
260     float shadowBlur;
261     Color shadowColor;
262     context.getShadow(shadowOffset, shadowBlur, shadowColor);
263
264     AffineTransform contextCTM = context.getCTM();
265     float syntheticBoldOffset = font.syntheticBoldOffset();
266     if (syntheticBoldOffset && !contextCTM.isIdentityOrTranslationOrFlipped()) {
267         FloatSize horizontalUnitSizeInDevicePixels = contextCTM.mapSize(FloatSize(1, 0));
268         float horizontalUnitLengthInDevicePixels = sqrtf(horizontalUnitSizeInDevicePixels.width() * horizontalUnitSizeInDevicePixels.width() + horizontalUnitSizeInDevicePixels.height() * horizontalUnitSizeInDevicePixels.height());
269         if (horizontalUnitLengthInDevicePixels)
270             syntheticBoldOffset /= horizontalUnitLengthInDevicePixels;
271     };
272
273     bool hasSimpleShadow = context.textDrawingMode() == TextModeFill && shadowColor.isValid() && !shadowBlur && !platformData.isColorBitmapFont() && (!context.shadowsIgnoreTransforms() || contextCTM.isIdentityOrTranslationOrFlipped()) && !context.isInTransparencyLayer();
274     if (hasSimpleShadow) {
275         // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing.
276         context.clearShadow();
277         Color fillColor = context.fillColor();
278         Color shadowFillColor = shadowColor.colorWithAlphaMultipliedBy(fillColor.alphaAsFloat());
279         context.setFillColor(shadowFillColor);
280         float shadowTextX = point.x() + shadowOffset.width();
281         // If shadows are ignoring transforms, then we haven't applied the Y coordinate flip yet, so down is negative.
282         float shadowTextY = point.y() + shadowOffset.height() * (context.shadowsIgnoreTransforms() ? -1 : 1);
283         showGlyphsWithAdvances(FloatPoint(shadowTextX, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
284         if (syntheticBoldOffset)
285             showGlyphsWithAdvances(FloatPoint(shadowTextX + syntheticBoldOffset, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
286         context.setFillColor(fillColor);
287     }
288
289     if (useLetterpressEffect)
290         showLetterpressedGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
291     else
292         showGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
293     if (syntheticBoldOffset)
294         showGlyphsWithAdvances(FloatPoint(point.x() + syntheticBoldOffset, point.y()), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
295
296     if (hasSimpleShadow)
297         context.setShadow(shadowOffset, shadowBlur, shadowColor);
298
299 #if !PLATFORM(IOS)
300     if (changeFontSmoothing)
301         CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing);
302 #endif
303 }
304
305 #if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
306 struct GlyphIterationState {
307     GlyphIterationState(CGPoint startingPoint, CGPoint currentPoint, CGFloat y1, CGFloat y2, CGFloat minX, CGFloat maxX)
308         : startingPoint(startingPoint)
309         , currentPoint(currentPoint)
310         , y1(y1)
311         , y2(y2)
312         , minX(minX)
313         , maxX(maxX)
314     {
315     }
316     CGPoint startingPoint;
317     CGPoint currentPoint;
318     CGFloat y1;
319     CGFloat y2;
320     CGFloat minX;
321     CGFloat maxX;
322 };
323
324 static bool findIntersectionPoint(float y, CGPoint p1, CGPoint p2, CGFloat& x)
325 {
326     x = p1.x + (y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
327     return (p1.y < y && p2.y > y) || (p1.y > y && p2.y < y);
328 }
329
330 static void updateX(GlyphIterationState& state, CGFloat x)
331 {
332     state.minX = std::min(state.minX, x);
333     state.maxX = std::max(state.maxX, x);
334 }
335
336 // This function is called by CGPathApply and is therefore invoked for each
337 // contour in a glyph. This function models each contours as a straight line
338 // and calculates the intersections between each pseudo-contour and
339 // two horizontal lines (the upper and lower bounds of an underline) found in
340 // GlyphIterationState::y1 and GlyphIterationState::y2. It keeps track of the
341 // leftmost and rightmost intersection in GlyphIterationState::minX and
342 // GlyphIterationState::maxX.
343 static void findPathIntersections(void* stateAsVoidPointer, const CGPathElement* e)
344 {
345     auto& state = *static_cast<GlyphIterationState*>(stateAsVoidPointer);
346     bool doIntersection = false;
347     CGPoint point = CGPointZero;
348     switch (e->type) {
349     case kCGPathElementMoveToPoint:
350         state.startingPoint = e->points[0];
351         state.currentPoint = e->points[0];
352         break;
353     case kCGPathElementAddLineToPoint:
354         doIntersection = true;
355         point = e->points[0];
356         break;
357     case kCGPathElementAddQuadCurveToPoint:
358         doIntersection = true;
359         point = e->points[1];
360         break;
361     case kCGPathElementAddCurveToPoint:
362         doIntersection = true;
363         point = e->points[2];
364         break;
365     case kCGPathElementCloseSubpath:
366         doIntersection = true;
367         point = state.startingPoint;
368         break;
369     }
370     if (!doIntersection)
371         return;
372     CGFloat x;
373     if (findIntersectionPoint(state.y1, state.currentPoint, point, x))
374         updateX(state, x);
375     if (findIntersectionPoint(state.y2, state.currentPoint, point, x))
376         updateX(state, x);
377     if ((state.currentPoint.y >= state.y1 && state.currentPoint.y <= state.y2)
378         || (state.currentPoint.y <= state.y1 && state.currentPoint.y >= state.y2))
379         updateX(state, state.currentPoint.x);
380     state.currentPoint = point;
381 }
382
383 class MacGlyphToPathTranslator final : public GlyphToPathTranslator {
384 public:
385     MacGlyphToPathTranslator(const TextRun& textRun, const GlyphBuffer& glyphBuffer, const FloatPoint& textOrigin)
386         : m_index(0)
387         , m_textRun(textRun)
388         , m_glyphBuffer(glyphBuffer)
389         , m_fontData(glyphBuffer.fontAt(m_index))
390         , m_translation(CGAffineTransformScale(CGAffineTransformMakeTranslation(textOrigin.x(), textOrigin.y()), 1, -1))
391     {
392     }
393     bool containsMorePaths() final { return m_index != m_glyphBuffer.size(); }
394     Path path() final;
395     std::pair<float, float> extents() final;
396     GlyphUnderlineType underlineType() final;
397     void advance() final;
398
399 private:
400     unsigned m_index;
401     const TextRun& m_textRun;
402     const GlyphBuffer& m_glyphBuffer;
403     const Font* m_fontData;
404     CGAffineTransform m_translation;
405 };
406
407 Path MacGlyphToPathTranslator::path()
408 {
409     RetainPtr<CGPathRef> result = adoptCF(CTFontCreatePathForGlyph(m_fontData->platformData().ctFont(), m_glyphBuffer.glyphAt(m_index), &m_translation));
410     return adoptCF(CGPathCreateMutableCopy(result.get()));
411 }
412
413 std::pair<float, float> MacGlyphToPathTranslator::extents()
414 {
415     CGPoint beginning = CGPointApplyAffineTransform(CGPointMake(0, 0), m_translation);
416     CGSize end = CGSizeApplyAffineTransform(m_glyphBuffer.advanceAt(m_index), m_translation);
417     return std::make_pair(static_cast<float>(beginning.x), static_cast<float>(beginning.x + end.width));
418 }
419
420 auto MacGlyphToPathTranslator::underlineType() -> GlyphUnderlineType
421 {
422     return computeUnderlineType(m_textRun, m_glyphBuffer, m_index);
423 }
424
425 void MacGlyphToPathTranslator::advance()
426 {
427     GlyphBufferAdvance advance = m_glyphBuffer.advanceAt(m_index);
428     m_translation = CGAffineTransformTranslate(m_translation, advance.width(), advance.height());
429     ++m_index;
430     if (m_index < m_glyphBuffer.size())
431         m_fontData = m_glyphBuffer.fontAt(m_index);
432 }
433
434 DashArray FontCascade::dashesForIntersectionsWithRect(const TextRun& run, const FloatPoint& textOrigin, const FloatRect& lineExtents) const
435 {
436     if (isLoadingCustomFonts())
437         return DashArray();
438
439     GlyphBuffer glyphBuffer;
440     glyphBuffer.saveOffsetsInString();
441     float deltaX;
442     if (codePath(run) != FontCascade::Complex)
443         deltaX = getGlyphsAndAdvancesForSimpleText(run, 0, run.length(), glyphBuffer);
444     else
445         deltaX = getGlyphsAndAdvancesForComplexText(run, 0, run.length(), glyphBuffer);
446
447     if (!glyphBuffer.size())
448         return DashArray();
449     
450     // FIXME: Handle SVG + non-SVG interleaved runs. https://bugs.webkit.org/show_bug.cgi?id=133778
451     FloatPoint origin = FloatPoint(textOrigin.x() + deltaX, textOrigin.y());
452     MacGlyphToPathTranslator translator(run, glyphBuffer, origin);
453     DashArray result;
454     for (unsigned index = 0; translator.containsMorePaths(); ++index, translator.advance()) {
455         GlyphIterationState info = GlyphIterationState(CGPointMake(0, 0), CGPointMake(0, 0), lineExtents.y(), lineExtents.y() + lineExtents.height(), lineExtents.x() + lineExtents.width(), lineExtents.x());
456         const Font* localFont = glyphBuffer.fontAt(index);
457         if (!localFont) {
458             // The advances will get all messed up if we do anything other than bail here.
459             result.clear();
460             break;
461         }
462         switch (translator.underlineType()) {
463         case GlyphToPathTranslator::GlyphUnderlineType::SkipDescenders: {
464             Path path = translator.path();
465             CGPathApply(path.platformPath(), &info, &findPathIntersections);
466             if (info.minX < info.maxX) {
467                 result.append(info.minX - lineExtents.x());
468                 result.append(info.maxX - lineExtents.x());
469             }
470             break;
471         }
472         case GlyphToPathTranslator::GlyphUnderlineType::SkipGlyph: {
473             std::pair<float, float> extents = translator.extents();
474             result.append(extents.first - lineExtents.x());
475             result.append(extents.second - lineExtents.x());
476             break;
477         }
478         case GlyphToPathTranslator::GlyphUnderlineType::DrawOverGlyph:
479             // Nothing to do
480             break;
481         }
482     }
483     return result;
484 }
485 #endif
486
487 bool FontCascade::primaryFontIsSystemFont() const
488 {
489     const auto& fontData = primaryFont();
490     return CTFontDescriptorIsSystemUIFont(adoptCF(CTFontCopyFontDescriptor(fontData.platformData().ctFont())).get());
491 }
492
493 // FIXME: Use this on all ports.
494 const Font* FontCascade::fontForCombiningCharacterSequence(const UChar* characters, size_t length) const
495 {
496     UChar32 baseCharacter;
497     size_t baseCharacterLength = 0;
498     U16_NEXT(characters, baseCharacterLength, length, baseCharacter);
499
500     GlyphData baseCharacterGlyphData = glyphDataForCharacter(baseCharacter, false, NormalVariant);
501
502     if (!baseCharacterGlyphData.glyph)
503         return nullptr;
504
505     if (length == baseCharacterLength)
506         return baseCharacterGlyphData.font;
507
508     bool triedBaseCharacterFont = false;
509
510     for (unsigned i = 0; !fallbackRangesAt(i).isNull(); ++i) {
511         const Font* font = fallbackRangesAt(i).fontForCharacter(baseCharacter);
512         if (!font)
513             continue;
514 #if PLATFORM(IOS)
515         if (baseCharacter >= 0x0600 && baseCharacter <= 0x06ff && font->shouldNotBeUsedForArabic())
516             continue;
517 #endif
518         if (font->platformData().orientation() == Vertical) {
519             if (isCJKIdeographOrSymbol(baseCharacter) && !font->hasVerticalGlyphs())
520                 font = &font->brokenIdeographFont();
521             else if (m_fontDescription.nonCJKGlyphOrientation() == NonCJKGlyphOrientation::Mixed) {
522                 const Font& verticalRightFont = font->verticalRightOrientationFont();
523                 Glyph verticalRightGlyph = verticalRightFont.glyphForCharacter(baseCharacter);
524                 if (verticalRightGlyph == baseCharacterGlyphData.glyph)
525                     font = &verticalRightFont;
526             } else {
527                 const Font& uprightFont = font->uprightOrientationFont();
528                 Glyph uprightGlyph = uprightFont.glyphForCharacter(baseCharacter);
529                 if (uprightGlyph != baseCharacterGlyphData.glyph)
530                     font = &uprightFont;
531             }
532         }
533
534         if (font == baseCharacterGlyphData.font)
535             triedBaseCharacterFont = true;
536
537         if (font->canRenderCombiningCharacterSequence(characters, length))
538             return font;
539     }
540
541     if (!triedBaseCharacterFont && baseCharacterGlyphData.font && baseCharacterGlyphData.font->canRenderCombiningCharacterSequence(characters, length))
542         return baseCharacterGlyphData.font;
543
544     return Font::systemFallback();
545 }
546
547 }