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, 2007, 2008, 2009, 2010, 2011 Apple Inc.
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.
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.
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.
24 #import "FontCascade.h"
26 #import "ComplexTextController.h"
27 #import "CoreGraphicsSPI.h"
28 #import "CoreTextSPI.h"
31 #import "GlyphBuffer.h"
32 #import "GraphicsContext.h"
33 #import "LayoutRect.h"
35 #import "WebCoreSystemInterface.h"
37 #import <AppKit/AppKit.h>
39 #import <wtf/MathExtras.h>
41 #if ENABLE(LETTERPRESS)
43 #import "SoftLinking.h"
45 SOFT_LINK_PRIVATE_FRAMEWORK(CoreUI)
46 SOFT_LINK_CLASS(CoreUI, CUICatalog)
47 SOFT_LINK_CLASS(CoreUI, CUIStyleEffectConfiguration)
49 SOFT_LINK_FRAMEWORK(UIKit)
50 SOFT_LINK(UIKit, _UIKitGetTextEffectsCatalog, CUICatalog *, (void), ())
53 #define SYNTHETIC_OBLIQUE_ANGLE 14
58 #define URefCon UInt32
63 bool FontCascade::canReturnFallbackFontsForComplexText()
68 bool FontCascade::canExpandAroundIdeographsInComplexText()
73 static inline void fillVectorWithHorizontalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef context, const CGSize* advances, size_t count)
75 CGAffineTransform matrix = CGAffineTransformInvert(CGContextGetTextMatrix(context));
76 positions[0] = CGPointZero;
77 for (size_t i = 1; i < count; ++i) {
78 CGSize advance = CGSizeApplyAffineTransform(advances[i - 1], matrix);
79 positions[i].x = positions[i - 1].x + advance.width;
80 positions[i].y = positions[i - 1].y + advance.height;
84 static inline bool shouldUseLetterpressEffect(const GraphicsContext& context)
86 #if ENABLE(LETTERPRESS)
87 return context.textDrawingMode() & TextModeLetterpress;
89 UNUSED_PARAM(context);
94 static void showLetterpressedGlyphsWithAdvances(const FloatPoint& point, const Font* font, CGContextRef context, const CGGlyph* glyphs, const CGSize* advances, size_t count)
96 #if ENABLE(LETTERPRESS)
100 const FontPlatformData& platformData = font->platformData();
101 if (platformData.orientation() == Vertical) {
102 // FIXME: Implement support for vertical text. See <rdar://problem/13737298>.
106 CGContextSetTextPosition(context, point.x(), point.y());
107 Vector<CGPoint, 256> positions(count);
108 fillVectorWithHorizontalGlyphPositions(positions, context, advances, count);
110 CTFontRef ctFont = platformData.ctFont();
111 CGContextSetFontSize(context, CTFontGetSize(ctFont));
113 static CUICatalog *catalog = _UIKitGetTextEffectsCatalog();
117 static CUIStyleEffectConfiguration *styleConfiguration;
118 if (!styleConfiguration) {
119 styleConfiguration = [allocCUIStyleEffectConfigurationInstance() init];
120 styleConfiguration.useSimplifiedEffect = YES;
123 [catalog drawGlyphs:glyphs atPositions:positions.data() inContext:context withFont:ctFont count:count stylePresetName:@"_UIKitNewLetterpressStyle" styleConfiguration:styleConfiguration foregroundColor:CGContextGetFillColorAsColor(context)];
127 UNUSED_PARAM(context);
128 UNUSED_PARAM(glyphs);
129 UNUSED_PARAM(advances);
134 static void showGlyphsWithAdvances(const FloatPoint& point, const Font* font, CGContextRef context, const CGGlyph* glyphs, const CGSize* advances, size_t count)
139 CGContextSetTextPosition(context, point.x(), point.y());
141 const FontPlatformData& platformData = font->platformData();
142 Vector<CGPoint, 256> positions(count);
143 if (platformData.isColorBitmapFont())
144 fillVectorWithHorizontalGlyphPositions(positions, context, advances, count);
145 if (platformData.orientation() == Vertical) {
146 CGAffineTransform savedMatrix;
147 CGAffineTransform rotateLeftTransform = CGAffineTransformMake(0, -1, 1, 0, 0, 0);
148 savedMatrix = CGContextGetTextMatrix(context);
149 CGAffineTransform runMatrix = CGAffineTransformConcat(savedMatrix, rotateLeftTransform);
150 CGContextSetTextMatrix(context, runMatrix);
152 Vector<CGSize, 256> translations(count);
153 CTFontGetVerticalTranslationsForGlyphs(platformData.ctFont(), glyphs, translations.data(), count);
155 CGAffineTransform transform = CGAffineTransformInvert(CGContextGetTextMatrix(context));
157 CGPoint position = FloatPoint(point.x(), point.y() + font->fontMetrics().floatAscent(IdeographicBaseline) - font->fontMetrics().floatAscent());
158 for (size_t 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;
164 if (!platformData.isColorBitmapFont()) {
165 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 101000
166 CTFontSetRenderingParameters(platformData.ctFont(), context);
168 CGContextShowGlyphsAtPositions(context, glyphs, positions.data(), count);
170 CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
171 CGContextSetTextMatrix(context, savedMatrix);
173 if (!platformData.isColorBitmapFont()) {
174 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 101000
175 CTFontSetRenderingParameters(platformData.ctFont(), context);
177 #pragma clang diagnostic push
178 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
179 CGContextShowGlyphsWithAdvances(context, glyphs, advances, count);
180 #pragma clang diagnostic pop
182 CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
187 static void setCGFontRenderingMode(CGContextRef cgContext, NSFontRenderingMode renderingMode, BOOL shouldSubpixelQuantize)
189 if (renderingMode == NSFontIntegerAdvancementsRenderingMode) {
190 CGContextSetShouldAntialiasFonts(cgContext, false);
194 CGContextSetShouldAntialiasFonts(cgContext, true);
196 CGAffineTransform contextTransform = CGContextGetCTM(cgContext);
197 BOOL isTranslationOrIntegralScale = WTF::isIntegral(contextTransform.a) && WTF::isIntegral(contextTransform.d) && contextTransform.b == 0.f && contextTransform.c == 0.f;
198 BOOL isRotated = ((contextTransform.b || contextTransform.c) && (contextTransform.a || contextTransform.d));
199 BOOL doSubpixelQuantization = isTranslationOrIntegralScale || (!isRotated && shouldSubpixelQuantize);
201 CGContextSetShouldSubpixelPositionFonts(cgContext, renderingMode != NSFontAntialiasedIntegerAdvancementsRenderingMode || !isTranslationOrIntegralScale);
202 CGContextSetShouldSubpixelQuantizeFonts(cgContext, doSubpixelQuantization);
206 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
207 static CGSize dilationSizeForTextColor(const Color& color)
212 color.getHSL(hue, saturation, lightness);
214 // These values were derived empirically, and are only experimental.
215 if (lightness < 0.3333) // Dark
216 return CGSizeMake(0.007, 0.019);
218 if (lightness < 0.6667) // Medium
219 return CGSizeMake(0.032, 0.032);
222 return CGSizeMake(0.0475, 0.039);
226 static FloatPoint pointAdjustedForEmoji(const FontPlatformData& platformData, FloatPoint point)
229 if (!platformData.m_isEmoji)
232 // Mimic the positioining of non-bitmap glyphs, which are not subpixel-positioned.
233 point.setY(ceilf(point.y()));
235 // Emoji glyphs snap to the CSS pixel grid.
236 point.setX(floorf(point.x()));
238 // Emoji glyphs are offset one CSS pixel to the right.
241 // Emoji glyphs are offset vertically based on font size.
242 float fontSize = platformData.size();
244 if (fontSize <= 15) {
245 // Undo Core Text's y adjustment.
246 static float yAdjustmentFactor = iosExecutableWasLinkedOnOrAfterVersion(wkIOSSystemVersion_6_0) ? .19 : .1;
247 point.setY(floorf(y - yAdjustmentFactor * (fontSize + 2) + 2));
250 y -= .35f * fontSize - 10;
252 // Undo Core Text's y adjustment.
253 static float yAdjustment = iosExecutableWasLinkedOnOrAfterVersion(wkIOSSystemVersion_6_0) ? 3.8 : 2;
254 point.setY(floorf(y - yAdjustment));
257 UNUSED_PARAM(platformData);
262 void FontCascade::drawGlyphs(GraphicsContext* context, const Font* font, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& anchorPoint) const
264 const FontPlatformData& platformData = font->platformData();
265 if (!platformData.size())
268 CGContextRef cgContext = context->platformContext();
270 bool shouldSmoothFonts;
271 bool changeFontSmoothing;
272 bool matchAntialiasedAndSmoothedFonts = context->antialiasedFontDilationEnabled();
274 switch (fontDescription().fontSmoothing()) {
276 context->setShouldAntialias(true);
277 shouldSmoothFonts = false;
278 changeFontSmoothing = true;
279 matchAntialiasedAndSmoothedFonts = false; // CSS has opted into strictly antialiased fonts.
282 case SubpixelAntialiased: {
283 context->setShouldAntialias(true);
284 shouldSmoothFonts = true;
285 changeFontSmoothing = true;
286 matchAntialiasedAndSmoothedFonts = true;
290 context->setShouldAntialias(false);
291 shouldSmoothFonts = false;
292 changeFontSmoothing = true;
293 matchAntialiasedAndSmoothedFonts = false;
296 case AutoSmoothing: {
297 shouldSmoothFonts = true;
298 changeFontSmoothing = false;
299 matchAntialiasedAndSmoothedFonts = true;
304 if (!shouldUseSmoothing()) {
305 shouldSmoothFonts = false;
306 changeFontSmoothing = true;
307 matchAntialiasedAndSmoothedFonts = true;
311 bool originalShouldUseFontSmoothing = false;
312 if (changeFontSmoothing) {
313 originalShouldUseFontSmoothing = CGContextGetShouldSmoothFonts(cgContext);
314 CGContextSetShouldSmoothFonts(cgContext, shouldSmoothFonts);
317 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
318 CGFontAntialiasingStyle oldAntialiasingStyle;
319 bool resetAntialiasingStyle = false;
320 if (antialiasedFontDilationEnabled() && !CGContextGetShouldSmoothFonts(cgContext) && matchAntialiasedAndSmoothedFonts) {
321 resetAntialiasingStyle = true;
322 oldAntialiasingStyle = CGContextGetFontAntialiasingStyle(cgContext);
323 CGContextSetFontAntialiasingStyle(cgContext, kCGFontAntialiasingStyleUnfilteredCustomDilation);
324 CGContextSetFontDilation(cgContext, dilationSizeForTextColor(context->fillColor()));
330 NSFont* drawFont = [platformData.nsFont() printerFont];
333 CGContextSetFont(cgContext, platformData.cgFont());
335 bool useLetterpressEffect = shouldUseLetterpressEffect(*context);
336 FloatPoint point = pointAdjustedForEmoji(platformData, anchorPoint);
339 float fontSize = platformData.size();
340 CGAffineTransform matrix = useLetterpressEffect || platformData.isColorBitmapFont() ? CGAffineTransformIdentity : CGAffineTransformMakeScale(fontSize, fontSize);
342 CGAffineTransform matrix = CGAffineTransformIdentity;
343 if (drawFont && !platformData.isColorBitmapFont())
344 memcpy(&matrix, [drawFont matrix], sizeof(matrix));
346 matrix.b = -matrix.b;
347 matrix.d = -matrix.d;
348 if (platformData.m_syntheticOblique) {
349 static float obliqueSkew = tanf(SYNTHETIC_OBLIQUE_ANGLE * piFloat / 180);
350 if (platformData.orientation() == Vertical)
351 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, obliqueSkew, 0, 1, 0, 0));
353 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -obliqueSkew, 1, 0, 0));
355 CGContextSetTextMatrix(cgContext, matrix);
358 CGContextSetFontSize(cgContext, 1);
359 CGContextSetShouldSubpixelQuantizeFonts(cgContext, context->shouldSubpixelQuantizeFonts());
361 setCGFontRenderingMode(cgContext, [drawFont renderingMode], context->shouldSubpixelQuantizeFonts());
363 CGContextSetFontSize(cgContext, 1);
365 CGContextSetFontSize(cgContext, platformData.m_size);
369 FloatSize shadowOffset;
372 ColorSpace shadowColorSpace;
373 ColorSpace fillColorSpace = context->fillColorSpace();
374 context->getShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace);
376 AffineTransform contextCTM = context->getCTM();
377 float syntheticBoldOffset = font->syntheticBoldOffset();
378 if (syntheticBoldOffset && !contextCTM.isIdentityOrTranslationOrFlipped()) {
379 FloatSize horizontalUnitSizeInDevicePixels = contextCTM.mapSize(FloatSize(1, 0));
380 float horizontalUnitLengthInDevicePixels = sqrtf(horizontalUnitSizeInDevicePixels.width() * horizontalUnitSizeInDevicePixels.width() + horizontalUnitSizeInDevicePixels.height() * horizontalUnitSizeInDevicePixels.height());
381 if (horizontalUnitLengthInDevicePixels)
382 syntheticBoldOffset /= horizontalUnitLengthInDevicePixels;
385 bool hasSimpleShadow = context->textDrawingMode() == TextModeFill && shadowColor.isValid() && !shadowBlur && !platformData.isColorBitmapFont() && (!context->shadowsIgnoreTransforms() || contextCTM.isIdentityOrTranslationOrFlipped()) && !context->isInTransparencyLayer();
386 if (hasSimpleShadow) {
387 // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing.
388 context->clearShadow();
389 Color fillColor = context->fillColor();
390 Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255);
391 context->setFillColor(shadowFillColor, shadowColorSpace);
392 float shadowTextX = point.x() + shadowOffset.width();
393 // If shadows are ignoring transforms, then we haven't applied the Y coordinate flip yet, so down is negative.
394 float shadowTextY = point.y() + shadowOffset.height() * (context->shadowsIgnoreTransforms() ? -1 : 1);
395 showGlyphsWithAdvances(FloatPoint(shadowTextX, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
397 if (syntheticBoldOffset)
399 if (syntheticBoldOffset && !platformData.m_isEmoji)
401 showGlyphsWithAdvances(FloatPoint(shadowTextX + syntheticBoldOffset, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
402 context->setFillColor(fillColor, fillColorSpace);
405 if (useLetterpressEffect)
406 showLetterpressedGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
408 showGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
410 if (syntheticBoldOffset)
412 if (syntheticBoldOffset && !platformData.m_isEmoji)
414 showGlyphsWithAdvances(FloatPoint(point.x() + syntheticBoldOffset, point.y()), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
417 context->setShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace);
420 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
421 if (resetAntialiasingStyle)
422 CGContextSetFontAntialiasingStyle(cgContext, oldAntialiasingStyle);
425 if (changeFontSmoothing)
426 CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing);
430 #if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
431 struct GlyphIterationState {
432 GlyphIterationState(CGPoint startingPoint, CGPoint currentPoint, CGFloat y1, CGFloat y2, CGFloat minX, CGFloat maxX)
433 : startingPoint(startingPoint)
434 , currentPoint(currentPoint)
441 CGPoint startingPoint;
442 CGPoint currentPoint;
449 static bool findIntersectionPoint(float y, CGPoint p1, CGPoint p2, CGFloat& x)
451 x = p1.x + (y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
452 return (p1.y < y && p2.y > y) || (p1.y > y && p2.y < y);
455 static void updateX(GlyphIterationState& state, CGFloat x)
457 state.minX = std::min(state.minX, x);
458 state.maxX = std::max(state.maxX, x);
461 // This function is called by CGPathApply and is therefore invoked for each
462 // contour in a glyph. This function models each contours as a straight line
463 // and calculates the intersections between each pseudo-contour and
464 // two horizontal lines (the upper and lower bounds of an underline) found in
465 // GlyphIterationState::y1 and GlyphIterationState::y2. It keeps track of the
466 // leftmost and rightmost intersection in GlyphIterationState::minX and
467 // GlyphIterationState::maxX.
468 static void findPathIntersections(void* stateAsVoidPointer, const CGPathElement* e)
470 auto& state = *static_cast<GlyphIterationState*>(stateAsVoidPointer);
471 bool doIntersection = false;
472 CGPoint point = CGPointZero;
474 case kCGPathElementMoveToPoint:
475 state.startingPoint = e->points[0];
476 state.currentPoint = e->points[0];
478 case kCGPathElementAddLineToPoint:
479 doIntersection = true;
480 point = e->points[0];
482 case kCGPathElementAddQuadCurveToPoint:
483 doIntersection = true;
484 point = e->points[1];
486 case kCGPathElementAddCurveToPoint:
487 doIntersection = true;
488 point = e->points[2];
490 case kCGPathElementCloseSubpath:
491 doIntersection = true;
492 point = state.startingPoint;
498 if (findIntersectionPoint(state.y1, state.currentPoint, point, x))
500 if (findIntersectionPoint(state.y2, state.currentPoint, point, x))
502 if ((state.currentPoint.y >= state.y1 && state.currentPoint.y <= state.y2)
503 || (state.currentPoint.y <= state.y1 && state.currentPoint.y >= state.y2))
504 updateX(state, state.currentPoint.x);
505 state.currentPoint = point;
508 class MacGlyphToPathTranslator final : public GlyphToPathTranslator {
510 MacGlyphToPathTranslator(const TextRun& textRun, const GlyphBuffer& glyphBuffer, const FloatPoint& textOrigin)
513 , m_glyphBuffer(glyphBuffer)
514 , m_fontData(glyphBuffer.fontAt(m_index))
515 , m_translation(CGAffineTransformScale(CGAffineTransformMakeTranslation(textOrigin.x(), textOrigin.y()), 1, -1))
517 moveToNextValidGlyph();
520 virtual bool containsMorePaths() override
522 return m_index != m_glyphBuffer.size();
524 virtual Path path() override;
525 virtual std::pair<float, float> extents() override;
526 virtual GlyphUnderlineType underlineType() override;
527 virtual void advance() override;
528 void moveToNextValidGlyph();
531 const TextRun& m_textRun;
532 const GlyphBuffer& m_glyphBuffer;
533 const Font* m_fontData;
534 CGAffineTransform m_translation;
537 Path MacGlyphToPathTranslator::path()
539 RetainPtr<CGPathRef> result = adoptCF(CTFontCreatePathForGlyph(m_fontData->platformData().ctFont(), m_glyphBuffer.glyphAt(m_index), &m_translation));
540 return adoptCF(CGPathCreateMutableCopy(result.get()));
543 std::pair<float, float> MacGlyphToPathTranslator::extents()
545 CGPoint beginning = CGPointApplyAffineTransform(CGPointMake(0, 0), m_translation);
546 CGSize end = CGSizeApplyAffineTransform(m_glyphBuffer.advanceAt(m_index), m_translation);
547 return std::make_pair(static_cast<float>(beginning.x), static_cast<float>(beginning.x + end.width));
550 auto MacGlyphToPathTranslator::underlineType() -> GlyphUnderlineType
552 return computeUnderlineType(m_textRun, m_glyphBuffer, m_index);
555 void MacGlyphToPathTranslator::moveToNextValidGlyph()
557 if (!m_fontData->isSVGFont())
562 void MacGlyphToPathTranslator::advance()
565 GlyphBufferAdvance advance = m_glyphBuffer.advanceAt(m_index);
566 m_translation = CGAffineTransformTranslate(m_translation, advance.width(), advance.height());
568 if (m_index >= m_glyphBuffer.size())
570 m_fontData = m_glyphBuffer.fontAt(m_index);
571 } while (m_fontData->isSVGFont() && m_index < m_glyphBuffer.size());
574 DashArray FontCascade::dashesForIntersectionsWithRect(const TextRun& run, const FloatPoint& textOrigin, const FloatRect& lineExtents) const
576 if (isLoadingCustomFonts())
579 GlyphBuffer glyphBuffer;
580 glyphBuffer.saveOffsetsInString();
582 if (codePath(run) != FontCascade::Complex)
583 deltaX = getGlyphsAndAdvancesForSimpleText(run, 0, run.length(), glyphBuffer);
585 deltaX = getGlyphsAndAdvancesForComplexText(run, 0, run.length(), glyphBuffer);
587 if (!glyphBuffer.size())
590 // FIXME: Handle SVG + non-SVG interleaved runs. https://bugs.webkit.org/show_bug.cgi?id=133778
591 const Font* fontData = glyphBuffer.fontAt(0);
592 std::unique_ptr<GlyphToPathTranslator> translator;
594 FloatPoint origin = FloatPoint(textOrigin.x() + deltaX, textOrigin.y());
595 if (!fontData->isSVGFont())
596 translator = std::make_unique<MacGlyphToPathTranslator>(run, glyphBuffer, origin);
598 TextRun::RenderingContext* renderingContext = run.renderingContext();
599 if (!renderingContext)
601 translator = renderingContext->createGlyphToPathTranslator(*fontData, &run, glyphBuffer, 0, glyphBuffer.size(), origin);
605 for (int index = 0; translator->containsMorePaths(); ++index, translator->advance()) {
606 GlyphIterationState info = GlyphIterationState(CGPointMake(0, 0), CGPointMake(0, 0), lineExtents.y(), lineExtents.y() + lineExtents.height(), lineExtents.x() + lineExtents.width(), lineExtents.x());
607 const Font* localFont = glyphBuffer.fontAt(index);
608 if (!localFont || (!isSVG && localFont->isSVGFont()) || (isSVG && localFont != fontData)) {
609 // The advances will get all messed up if we do anything other than bail here.
613 switch (translator->underlineType()) {
614 case GlyphToPathTranslator::GlyphUnderlineType::SkipDescenders: {
615 Path path = translator->path();
616 CGPathApply(path.platformPath(), &info, &findPathIntersections);
617 if (info.minX < info.maxX) {
618 result.append(info.minX - lineExtents.x());
619 result.append(info.maxX - lineExtents.x());
623 case GlyphToPathTranslator::GlyphUnderlineType::SkipGlyph: {
624 std::pair<float, float> extents = translator->extents();
625 result.append(extents.first - lineExtents.x());
626 result.append(extents.second - lineExtents.x());
629 case GlyphToPathTranslator::GlyphUnderlineType::DrawOverGlyph:
638 bool FontCascade::primaryFontIsSystemFont() const
640 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
641 const auto& fontData = primaryFont();
642 return !fontData.isSVGFont() && CTFontDescriptorIsSystemUIFont(adoptCF(CTFontCopyFontDescriptor(fontData.platformData().ctFont())).get());
644 // System fonts are hidden by having a name that begins with a period, so simply search
645 // for that here rather than try to keep the list up to date.
646 return firstFamily().startsWith('.');
650 void FontCascade::adjustSelectionRectForComplexText(const TextRun& run, LayoutRect& selectionRect, int from, int to) const
652 ComplexTextController controller(*this, run);
653 controller.advance(from);
654 float beforeWidth = controller.runWidthSoFar();
655 controller.advance(to);
656 float afterWidth = controller.runWidthSoFar();
659 selectionRect.move(controller.totalWidth() - afterWidth + controller.leadingExpansion(), 0);
661 selectionRect.move(beforeWidth, 0);
662 selectionRect.setWidth(afterWidth - beforeWidth);
665 float FontCascade::getGlyphsAndAdvancesForComplexText(const TextRun& run, int from, int to, GlyphBuffer& glyphBuffer, ForTextEmphasisOrNot forTextEmphasis) const
667 float initialAdvance;
669 ComplexTextController controller(*this, run, false, 0, forTextEmphasis);
670 controller.advance(from);
671 float beforeWidth = controller.runWidthSoFar();
672 controller.advance(to, &glyphBuffer);
674 if (glyphBuffer.isEmpty())
677 float afterWidth = controller.runWidthSoFar();
680 initialAdvance = controller.totalWidth() + controller.finalRoundingWidth() - afterWidth + controller.leadingExpansion();
681 glyphBuffer.reverse(0, glyphBuffer.size());
683 initialAdvance = beforeWidth;
685 return initialAdvance;
688 float FontCascade::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
690 // This glyph buffer holds our glyphs + advances + font data for each glyph.
691 GlyphBuffer glyphBuffer;
693 float startX = point.x() + getGlyphsAndAdvancesForComplexText(run, from, to, glyphBuffer);
695 // We couldn't generate any glyphs for the run. Give up.
696 if (glyphBuffer.isEmpty())
699 // Draw the glyph buffer now at the starting point returned in startX.
700 FloatPoint startPoint(startX, point.y());
701 drawGlyphBuffer(context, run, glyphBuffer, startPoint);
703 return startPoint.x() - startX;
706 void FontCascade::drawEmphasisMarksForComplexText(GraphicsContext* context, const TextRun& run, const AtomicString& mark, const FloatPoint& point, int from, int to) const
708 GlyphBuffer glyphBuffer;
709 float initialAdvance = getGlyphsAndAdvancesForComplexText(run, from, to, glyphBuffer, ForTextEmphasis);
711 if (glyphBuffer.isEmpty())
714 drawEmphasisMarks(context, run, glyphBuffer, mark, FloatPoint(point.x() + initialAdvance, point.y()));
717 float FontCascade::floatWidthForComplexText(const TextRun& run, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
719 ComplexTextController controller(*this, run, true, fallbackFonts);
721 glyphOverflow->top = std::max<int>(glyphOverflow->top, ceilf(-controller.minGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().ascent()));
722 glyphOverflow->bottom = std::max<int>(glyphOverflow->bottom, ceilf(controller.maxGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().descent()));
723 glyphOverflow->left = std::max<int>(0, ceilf(-controller.minGlyphBoundingBoxX()));
724 glyphOverflow->right = std::max<int>(0, ceilf(controller.maxGlyphBoundingBoxX() - controller.totalWidth()));
726 return controller.totalWidth();
729 int FontCascade::offsetForPositionForComplexText(const TextRun& run, float x, bool includePartialGlyphs) const
731 ComplexTextController controller(*this, run);
732 return controller.offsetForPosition(x, includePartialGlyphs);
735 const Font* FontCascade::fontForCombiningCharacterSequence(const UChar* characters, size_t length, FontVariant variant) const
737 UChar32 baseCharacter;
738 size_t baseCharacterLength = 0;
739 U16_NEXT(characters, baseCharacterLength, length, baseCharacter);
741 GlyphData baseCharacterGlyphData = glyphDataForCharacter(baseCharacter, false, variant);
743 if (!baseCharacterGlyphData.glyph)
746 if (length == baseCharacterLength)
747 return baseCharacterGlyphData.font;
749 bool triedBaseCharacterFont = false;
751 for (unsigned i = 0; !fallbackRangesAt(i).isNull(); ++i) {
752 const Font* font = fallbackRangesAt(i).fontForCharacter(baseCharacter);
756 if (baseCharacter >= 0x0600 && baseCharacter <= 0x06ff && font->shouldNotBeUsedForArabic())
759 if (variant == NormalVariant) {
760 if (font->platformData().orientation() == Vertical) {
761 if (isCJKIdeographOrSymbol(baseCharacter) && !font->hasVerticalGlyphs()) {
762 variant = BrokenIdeographVariant;
763 font = font->brokenIdeographFont().get();
764 } else if (m_fontDescription.nonCJKGlyphOrientation() == NonCJKGlyphOrientationVerticalRight) {
765 Font* verticalRightFont = font->verticalRightOrientationFont().get();
766 Glyph verticalRightGlyph = verticalRightFont->glyphForCharacter(baseCharacter);
767 if (verticalRightGlyph == baseCharacterGlyphData.glyph)
768 font = verticalRightFont;
770 Font* uprightFont = font->uprightOrientationFont().get();
771 Glyph uprightGlyph = uprightFont->glyphForCharacter(baseCharacter);
772 if (uprightGlyph != baseCharacterGlyphData.glyph)
777 if (const Font* variantFont = font->variantFont(m_fontDescription, variant).get())
781 if (font == baseCharacterGlyphData.font)
782 triedBaseCharacterFont = true;
784 if (font->canRenderCombiningCharacterSequence(characters, length))
788 if (!triedBaseCharacterFont && baseCharacterGlyphData.font && baseCharacterGlyphData.font->canRenderCombiningCharacterSequence(characters, length))
789 return baseCharacterGlyphData.font;
791 return Font::systemFallback();