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 class RenderingStyleSaver {
136 #if !PLATFORM(MAC) || __MAC_OS_X_VERSION_MIN_REQUIRED <= 101000
138 RenderingStyleSaver(CTFontRef, CGContextRef) { }
140 #elif !defined(CORETEXT_HAS_CTFontSetRenderingStyle) || CORETEXT_HAS_CTFontSetRenderingStyle != 1
142 // This is very slow, but it's just a holdover until everyone migrates to CTFontSetRenderingStyle()
143 // FIXME: Delete this implementation when everyone has migrated off
144 RenderingStyleSaver(CTFontRef font, CGContextRef context)
147 CGContextSaveGState(context);
148 CTFontSetRenderingParameters(font, context);
151 ~RenderingStyleSaver()
153 CGContextRestoreGState(m_context);
157 CGContextRef m_context;
161 RenderingStyleSaver(CTFontRef font, CGContextRef context)
164 m_changed = CTFontSetRenderingStyle(font, context, &m_originalStyle, &m_originalDilation);
167 ~RenderingStyleSaver()
171 CGContextSetFontRenderingStyle(m_context, m_originalStyle);
172 CGContextSetFontDilation(m_context, m_originalDilation);
177 CGContextRef m_context;
178 CGFontRenderingStyle m_originalStyle;
179 CGSize m_originalDilation;
184 static void showGlyphsWithAdvances(const FloatPoint& point, const Font* font, CGContextRef context, const CGGlyph* glyphs, const CGSize* advances, size_t count)
189 CGContextSetTextPosition(context, point.x(), point.y());
191 const FontPlatformData& platformData = font->platformData();
192 Vector<CGPoint, 256> positions(count);
193 if (platformData.isColorBitmapFont())
194 fillVectorWithHorizontalGlyphPositions(positions, context, advances, count);
195 if (platformData.orientation() == Vertical) {
196 CGAffineTransform savedMatrix;
197 CGAffineTransform rotateLeftTransform = CGAffineTransformMake(0, -1, 1, 0, 0, 0);
198 savedMatrix = CGContextGetTextMatrix(context);
199 CGAffineTransform runMatrix = CGAffineTransformConcat(savedMatrix, rotateLeftTransform);
200 CGContextSetTextMatrix(context, runMatrix);
202 Vector<CGSize, 256> translations(count);
203 CTFontGetVerticalTranslationsForGlyphs(platformData.ctFont(), glyphs, translations.data(), count);
205 CGAffineTransform transform = CGAffineTransformInvert(CGContextGetTextMatrix(context));
207 CGPoint position = FloatPoint(point.x(), point.y() + font->fontMetrics().floatAscent(IdeographicBaseline) - font->fontMetrics().floatAscent());
208 for (size_t i = 0; i < count; ++i) {
209 CGSize translation = CGSizeApplyAffineTransform(translations[i], rotateLeftTransform);
210 positions[i] = CGPointApplyAffineTransform(CGPointMake(position.x - translation.width, position.y + translation.height), transform);
211 position.x += advances[i].width;
212 position.y += advances[i].height;
214 if (!platformData.isColorBitmapFont()) {
215 RenderingStyleSaver saver(platformData.ctFont(), context);
216 CGContextShowGlyphsAtPositions(context, glyphs, positions.data(), count);
218 CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
219 CGContextSetTextMatrix(context, savedMatrix);
221 if (!platformData.isColorBitmapFont()) {
222 RenderingStyleSaver saver(platformData.ctFont(), context);
223 #pragma clang diagnostic push
224 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
225 CGContextShowGlyphsWithAdvances(context, glyphs, advances, count);
226 #pragma clang diagnostic pop
228 CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
233 static void setCGFontRenderingMode(CGContextRef cgContext, NSFontRenderingMode renderingMode, BOOL shouldSubpixelQuantize)
235 if (renderingMode == NSFontIntegerAdvancementsRenderingMode) {
236 CGContextSetShouldAntialiasFonts(cgContext, false);
240 CGContextSetShouldAntialiasFonts(cgContext, true);
242 CGAffineTransform contextTransform = CGContextGetCTM(cgContext);
243 BOOL isTranslationOrIntegralScale = WTF::isIntegral(contextTransform.a) && WTF::isIntegral(contextTransform.d) && contextTransform.b == 0.f && contextTransform.c == 0.f;
244 BOOL isRotated = ((contextTransform.b || contextTransform.c) && (contextTransform.a || contextTransform.d));
245 BOOL doSubpixelQuantization = isTranslationOrIntegralScale || (!isRotated && shouldSubpixelQuantize);
247 CGContextSetShouldSubpixelPositionFonts(cgContext, renderingMode != NSFontAntialiasedIntegerAdvancementsRenderingMode || !isTranslationOrIntegralScale);
248 CGContextSetShouldSubpixelQuantizeFonts(cgContext, doSubpixelQuantization);
252 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
253 static CGSize dilationSizeForTextColor(const Color& color)
258 color.getHSL(hue, saturation, lightness);
260 // These values were derived empirically, and are only experimental.
261 if (lightness < 0.3333) // Dark
262 return CGSizeMake(0.007, 0.019);
264 if (lightness < 0.6667) // Medium
265 return CGSizeMake(0.032, 0.032);
268 return CGSizeMake(0.0475, 0.039);
272 static FloatPoint pointAdjustedForEmoji(const FontPlatformData& platformData, FloatPoint point)
275 if (!platformData.m_isEmoji)
278 // Mimic the positioining of non-bitmap glyphs, which are not subpixel-positioned.
279 point.setY(ceilf(point.y()));
281 // Emoji glyphs snap to the CSS pixel grid.
282 point.setX(floorf(point.x()));
284 // Emoji glyphs are offset one CSS pixel to the right.
287 // Emoji glyphs are offset vertically based on font size.
288 float fontSize = platformData.size();
290 if (fontSize <= 15) {
291 // Undo Core Text's y adjustment.
292 static float yAdjustmentFactor = iosExecutableWasLinkedOnOrAfterVersion(wkIOSSystemVersion_6_0) ? .19 : .1;
293 point.setY(floorf(y - yAdjustmentFactor * (fontSize + 2) + 2));
296 y -= .35f * fontSize - 10;
298 // Undo Core Text's y adjustment.
299 static float yAdjustment = iosExecutableWasLinkedOnOrAfterVersion(wkIOSSystemVersion_6_0) ? 3.8 : 2;
300 point.setY(floorf(y - yAdjustment));
303 UNUSED_PARAM(platformData);
308 void FontCascade::drawGlyphs(GraphicsContext* context, const Font* font, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& anchorPoint) const
310 const FontPlatformData& platformData = font->platformData();
311 if (!platformData.size())
314 CGContextRef cgContext = context->platformContext();
316 bool shouldSmoothFonts;
317 bool changeFontSmoothing;
318 bool matchAntialiasedAndSmoothedFonts = context->antialiasedFontDilationEnabled();
320 switch (fontDescription().fontSmoothing()) {
322 context->setShouldAntialias(true);
323 shouldSmoothFonts = false;
324 changeFontSmoothing = true;
325 matchAntialiasedAndSmoothedFonts = false; // CSS has opted into strictly antialiased fonts.
328 case SubpixelAntialiased: {
329 context->setShouldAntialias(true);
330 shouldSmoothFonts = true;
331 changeFontSmoothing = true;
332 matchAntialiasedAndSmoothedFonts = true;
336 context->setShouldAntialias(false);
337 shouldSmoothFonts = false;
338 changeFontSmoothing = true;
339 matchAntialiasedAndSmoothedFonts = false;
342 case AutoSmoothing: {
343 shouldSmoothFonts = true;
344 changeFontSmoothing = false;
345 matchAntialiasedAndSmoothedFonts = true;
350 if (!shouldUseSmoothing()) {
351 shouldSmoothFonts = false;
352 changeFontSmoothing = true;
353 matchAntialiasedAndSmoothedFonts = true;
357 bool originalShouldUseFontSmoothing = false;
358 if (changeFontSmoothing) {
359 originalShouldUseFontSmoothing = CGContextGetShouldSmoothFonts(cgContext);
360 CGContextSetShouldSmoothFonts(cgContext, shouldSmoothFonts);
363 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
364 CGFontAntialiasingStyle oldAntialiasingStyle;
365 bool resetAntialiasingStyle = false;
366 if (antialiasedFontDilationEnabled() && !CGContextGetShouldSmoothFonts(cgContext) && matchAntialiasedAndSmoothedFonts) {
367 resetAntialiasingStyle = true;
368 oldAntialiasingStyle = CGContextGetFontAntialiasingStyle(cgContext);
369 CGContextSetFontAntialiasingStyle(cgContext, kCGFontAntialiasingStyleUnfilteredCustomDilation);
370 CGContextSetFontDilation(cgContext, dilationSizeForTextColor(context->fillColor()));
376 NSFont* drawFont = [platformData.nsFont() printerFont];
379 CGContextSetFont(cgContext, platformData.cgFont());
381 bool useLetterpressEffect = shouldUseLetterpressEffect(*context);
382 FloatPoint point = pointAdjustedForEmoji(platformData, anchorPoint);
385 float fontSize = platformData.size();
386 CGAffineTransform matrix = useLetterpressEffect || platformData.isColorBitmapFont() ? CGAffineTransformIdentity : CGAffineTransformMakeScale(fontSize, fontSize);
388 CGAffineTransform matrix = CGAffineTransformIdentity;
389 if (drawFont && !platformData.isColorBitmapFont())
390 memcpy(&matrix, [drawFont matrix], sizeof(matrix));
392 matrix.b = -matrix.b;
393 matrix.d = -matrix.d;
394 if (platformData.m_syntheticOblique) {
395 static float obliqueSkew = tanf(SYNTHETIC_OBLIQUE_ANGLE * piFloat / 180);
396 if (platformData.orientation() == Vertical)
397 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, obliqueSkew, 0, 1, 0, 0));
399 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -obliqueSkew, 1, 0, 0));
401 CGContextSetTextMatrix(cgContext, matrix);
404 CGContextSetFontSize(cgContext, 1);
405 CGContextSetShouldSubpixelQuantizeFonts(cgContext, context->shouldSubpixelQuantizeFonts());
407 setCGFontRenderingMode(cgContext, [drawFont renderingMode], context->shouldSubpixelQuantizeFonts());
409 CGContextSetFontSize(cgContext, 1);
411 CGContextSetFontSize(cgContext, platformData.m_size);
415 FloatSize shadowOffset;
418 ColorSpace shadowColorSpace;
419 ColorSpace fillColorSpace = context->fillColorSpace();
420 context->getShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace);
422 AffineTransform contextCTM = context->getCTM();
423 float syntheticBoldOffset = font->syntheticBoldOffset();
424 if (syntheticBoldOffset && !contextCTM.isIdentityOrTranslationOrFlipped()) {
425 FloatSize horizontalUnitSizeInDevicePixels = contextCTM.mapSize(FloatSize(1, 0));
426 float horizontalUnitLengthInDevicePixels = sqrtf(horizontalUnitSizeInDevicePixels.width() * horizontalUnitSizeInDevicePixels.width() + horizontalUnitSizeInDevicePixels.height() * horizontalUnitSizeInDevicePixels.height());
427 if (horizontalUnitLengthInDevicePixels)
428 syntheticBoldOffset /= horizontalUnitLengthInDevicePixels;
431 bool hasSimpleShadow = context->textDrawingMode() == TextModeFill && shadowColor.isValid() && !shadowBlur && !platformData.isColorBitmapFont() && (!context->shadowsIgnoreTransforms() || contextCTM.isIdentityOrTranslationOrFlipped()) && !context->isInTransparencyLayer();
432 if (hasSimpleShadow) {
433 // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing.
434 context->clearShadow();
435 Color fillColor = context->fillColor();
436 Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255);
437 context->setFillColor(shadowFillColor, shadowColorSpace);
438 float shadowTextX = point.x() + shadowOffset.width();
439 // If shadows are ignoring transforms, then we haven't applied the Y coordinate flip yet, so down is negative.
440 float shadowTextY = point.y() + shadowOffset.height() * (context->shadowsIgnoreTransforms() ? -1 : 1);
441 showGlyphsWithAdvances(FloatPoint(shadowTextX, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
443 if (syntheticBoldOffset)
445 if (syntheticBoldOffset && !platformData.m_isEmoji)
447 showGlyphsWithAdvances(FloatPoint(shadowTextX + syntheticBoldOffset, shadowTextY), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
448 context->setFillColor(fillColor, fillColorSpace);
451 if (useLetterpressEffect)
452 showLetterpressedGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
454 showGlyphsWithAdvances(point, font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
456 if (syntheticBoldOffset)
458 if (syntheticBoldOffset && !platformData.m_isEmoji)
460 showGlyphsWithAdvances(FloatPoint(point.x() + syntheticBoldOffset, point.y()), font, cgContext, glyphBuffer.glyphs(from), static_cast<const CGSize*>(glyphBuffer.advances(from)), numGlyphs);
463 context->setShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace);
466 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
467 if (resetAntialiasingStyle)
468 CGContextSetFontAntialiasingStyle(cgContext, oldAntialiasingStyle);
471 if (changeFontSmoothing)
472 CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing);
476 #if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
477 struct GlyphIterationState {
478 GlyphIterationState(CGPoint startingPoint, CGPoint currentPoint, CGFloat y1, CGFloat y2, CGFloat minX, CGFloat maxX)
479 : startingPoint(startingPoint)
480 , currentPoint(currentPoint)
487 CGPoint startingPoint;
488 CGPoint currentPoint;
495 static bool findIntersectionPoint(float y, CGPoint p1, CGPoint p2, CGFloat& x)
497 x = p1.x + (y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
498 return (p1.y < y && p2.y > y) || (p1.y > y && p2.y < y);
501 static void updateX(GlyphIterationState& state, CGFloat x)
503 state.minX = std::min(state.minX, x);
504 state.maxX = std::max(state.maxX, x);
507 // This function is called by CGPathApply and is therefore invoked for each
508 // contour in a glyph. This function models each contours as a straight line
509 // and calculates the intersections between each pseudo-contour and
510 // two horizontal lines (the upper and lower bounds of an underline) found in
511 // GlyphIterationState::y1 and GlyphIterationState::y2. It keeps track of the
512 // leftmost and rightmost intersection in GlyphIterationState::minX and
513 // GlyphIterationState::maxX.
514 static void findPathIntersections(void* stateAsVoidPointer, const CGPathElement* e)
516 auto& state = *static_cast<GlyphIterationState*>(stateAsVoidPointer);
517 bool doIntersection = false;
518 CGPoint point = CGPointZero;
520 case kCGPathElementMoveToPoint:
521 state.startingPoint = e->points[0];
522 state.currentPoint = e->points[0];
524 case kCGPathElementAddLineToPoint:
525 doIntersection = true;
526 point = e->points[0];
528 case kCGPathElementAddQuadCurveToPoint:
529 doIntersection = true;
530 point = e->points[1];
532 case kCGPathElementAddCurveToPoint:
533 doIntersection = true;
534 point = e->points[2];
536 case kCGPathElementCloseSubpath:
537 doIntersection = true;
538 point = state.startingPoint;
544 if (findIntersectionPoint(state.y1, state.currentPoint, point, x))
546 if (findIntersectionPoint(state.y2, state.currentPoint, point, x))
548 if ((state.currentPoint.y >= state.y1 && state.currentPoint.y <= state.y2)
549 || (state.currentPoint.y <= state.y1 && state.currentPoint.y >= state.y2))
550 updateX(state, state.currentPoint.x);
551 state.currentPoint = point;
554 class MacGlyphToPathTranslator final : public GlyphToPathTranslator {
556 MacGlyphToPathTranslator(const TextRun& textRun, const GlyphBuffer& glyphBuffer, const FloatPoint& textOrigin)
559 , m_glyphBuffer(glyphBuffer)
560 , m_fontData(glyphBuffer.fontAt(m_index))
561 , m_translation(CGAffineTransformScale(CGAffineTransformMakeTranslation(textOrigin.x(), textOrigin.y()), 1, -1))
563 moveToNextValidGlyph();
566 virtual bool containsMorePaths() override
568 return m_index != m_glyphBuffer.size();
570 virtual Path path() override;
571 virtual std::pair<float, float> extents() override;
572 virtual GlyphUnderlineType underlineType() override;
573 virtual void advance() override;
574 void moveToNextValidGlyph();
577 const TextRun& m_textRun;
578 const GlyphBuffer& m_glyphBuffer;
579 const Font* m_fontData;
580 CGAffineTransform m_translation;
583 Path MacGlyphToPathTranslator::path()
585 RetainPtr<CGPathRef> result = adoptCF(CTFontCreatePathForGlyph(m_fontData->platformData().ctFont(), m_glyphBuffer.glyphAt(m_index), &m_translation));
586 return adoptCF(CGPathCreateMutableCopy(result.get()));
589 std::pair<float, float> MacGlyphToPathTranslator::extents()
591 CGPoint beginning = CGPointApplyAffineTransform(CGPointMake(0, 0), m_translation);
592 CGSize end = CGSizeApplyAffineTransform(m_glyphBuffer.advanceAt(m_index), m_translation);
593 return std::make_pair(static_cast<float>(beginning.x), static_cast<float>(beginning.x + end.width));
596 auto MacGlyphToPathTranslator::underlineType() -> GlyphUnderlineType
598 return computeUnderlineType(m_textRun, m_glyphBuffer, m_index);
601 void MacGlyphToPathTranslator::moveToNextValidGlyph()
603 if (!m_fontData->isSVGFont())
608 void MacGlyphToPathTranslator::advance()
611 GlyphBufferAdvance advance = m_glyphBuffer.advanceAt(m_index);
612 m_translation = CGAffineTransformTranslate(m_translation, advance.width(), advance.height());
614 if (m_index >= m_glyphBuffer.size())
616 m_fontData = m_glyphBuffer.fontAt(m_index);
617 } while (m_fontData->isSVGFont() && m_index < m_glyphBuffer.size());
620 DashArray FontCascade::dashesForIntersectionsWithRect(const TextRun& run, const FloatPoint& textOrigin, const FloatRect& lineExtents) const
622 if (isLoadingCustomFonts())
625 GlyphBuffer glyphBuffer;
626 glyphBuffer.saveOffsetsInString();
628 if (codePath(run) != FontCascade::Complex)
629 deltaX = getGlyphsAndAdvancesForSimpleText(run, 0, run.length(), glyphBuffer);
631 deltaX = getGlyphsAndAdvancesForComplexText(run, 0, run.length(), glyphBuffer);
633 if (!glyphBuffer.size())
636 // FIXME: Handle SVG + non-SVG interleaved runs. https://bugs.webkit.org/show_bug.cgi?id=133778
637 const Font* fontData = glyphBuffer.fontAt(0);
638 std::unique_ptr<GlyphToPathTranslator> translator;
640 FloatPoint origin = FloatPoint(textOrigin.x() + deltaX, textOrigin.y());
641 if (!fontData->isSVGFont())
642 translator = std::make_unique<MacGlyphToPathTranslator>(run, glyphBuffer, origin);
644 TextRun::RenderingContext* renderingContext = run.renderingContext();
645 if (!renderingContext)
647 translator = renderingContext->createGlyphToPathTranslator(*fontData, &run, glyphBuffer, 0, glyphBuffer.size(), origin);
651 for (int index = 0; translator->containsMorePaths(); ++index, translator->advance()) {
652 GlyphIterationState info = GlyphIterationState(CGPointMake(0, 0), CGPointMake(0, 0), lineExtents.y(), lineExtents.y() + lineExtents.height(), lineExtents.x() + lineExtents.width(), lineExtents.x());
653 const Font* localFont = glyphBuffer.fontAt(index);
654 if (!localFont || (!isSVG && localFont->isSVGFont()) || (isSVG && localFont != fontData)) {
655 // The advances will get all messed up if we do anything other than bail here.
659 switch (translator->underlineType()) {
660 case GlyphToPathTranslator::GlyphUnderlineType::SkipDescenders: {
661 Path path = translator->path();
662 CGPathApply(path.platformPath(), &info, &findPathIntersections);
663 if (info.minX < info.maxX) {
664 result.append(info.minX - lineExtents.x());
665 result.append(info.maxX - lineExtents.x());
669 case GlyphToPathTranslator::GlyphUnderlineType::SkipGlyph: {
670 std::pair<float, float> extents = translator->extents();
671 result.append(extents.first - lineExtents.x());
672 result.append(extents.second - lineExtents.x());
675 case GlyphToPathTranslator::GlyphUnderlineType::DrawOverGlyph:
684 bool FontCascade::primaryFontIsSystemFont() const
686 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
687 const auto& fontData = primaryFont();
688 return !fontData.isSVGFont() && CTFontDescriptorIsSystemUIFont(adoptCF(CTFontCopyFontDescriptor(fontData.platformData().ctFont())).get());
690 // System fonts are hidden by having a name that begins with a period, so simply search
691 // for that here rather than try to keep the list up to date.
692 return firstFamily().startsWith('.');
696 void FontCascade::adjustSelectionRectForComplexText(const TextRun& run, LayoutRect& selectionRect, int from, int to) const
698 ComplexTextController controller(*this, run);
699 controller.advance(from);
700 float beforeWidth = controller.runWidthSoFar();
701 controller.advance(to);
702 float afterWidth = controller.runWidthSoFar();
705 selectionRect.move(controller.totalWidth() - afterWidth + controller.leadingExpansion(), 0);
707 selectionRect.move(beforeWidth, 0);
708 selectionRect.setWidth(afterWidth - beforeWidth);
711 float FontCascade::getGlyphsAndAdvancesForComplexText(const TextRun& run, int from, int to, GlyphBuffer& glyphBuffer, ForTextEmphasisOrNot forTextEmphasis) const
713 float initialAdvance;
715 ComplexTextController controller(*this, run, false, 0, forTextEmphasis);
716 controller.advance(from);
717 float beforeWidth = controller.runWidthSoFar();
718 controller.advance(to, &glyphBuffer);
720 if (glyphBuffer.isEmpty())
723 float afterWidth = controller.runWidthSoFar();
726 initialAdvance = controller.totalWidth() + controller.finalRoundingWidth() - afterWidth + controller.leadingExpansion();
727 glyphBuffer.reverse(0, glyphBuffer.size());
729 initialAdvance = beforeWidth;
731 return initialAdvance;
734 float FontCascade::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
736 // This glyph buffer holds our glyphs + advances + font data for each glyph.
737 GlyphBuffer glyphBuffer;
739 float startX = point.x() + getGlyphsAndAdvancesForComplexText(run, from, to, glyphBuffer);
741 // We couldn't generate any glyphs for the run. Give up.
742 if (glyphBuffer.isEmpty())
745 // Draw the glyph buffer now at the starting point returned in startX.
746 FloatPoint startPoint(startX, point.y());
747 drawGlyphBuffer(context, run, glyphBuffer, startPoint);
749 return startPoint.x() - startX;
752 void FontCascade::drawEmphasisMarksForComplexText(GraphicsContext* context, const TextRun& run, const AtomicString& mark, const FloatPoint& point, int from, int to) const
754 GlyphBuffer glyphBuffer;
755 float initialAdvance = getGlyphsAndAdvancesForComplexText(run, from, to, glyphBuffer, ForTextEmphasis);
757 if (glyphBuffer.isEmpty())
760 drawEmphasisMarks(context, run, glyphBuffer, mark, FloatPoint(point.x() + initialAdvance, point.y()));
763 float FontCascade::floatWidthForComplexText(const TextRun& run, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
765 ComplexTextController controller(*this, run, true, fallbackFonts);
767 glyphOverflow->top = std::max<int>(glyphOverflow->top, ceilf(-controller.minGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().ascent()));
768 glyphOverflow->bottom = std::max<int>(glyphOverflow->bottom, ceilf(controller.maxGlyphBoundingBoxY()) - (glyphOverflow->computeBounds ? 0 : fontMetrics().descent()));
769 glyphOverflow->left = std::max<int>(0, ceilf(-controller.minGlyphBoundingBoxX()));
770 glyphOverflow->right = std::max<int>(0, ceilf(controller.maxGlyphBoundingBoxX() - controller.totalWidth()));
772 return controller.totalWidth();
775 int FontCascade::offsetForPositionForComplexText(const TextRun& run, float x, bool includePartialGlyphs) const
777 ComplexTextController controller(*this, run);
778 return controller.offsetForPosition(x, includePartialGlyphs);
781 const Font* FontCascade::fontForCombiningCharacterSequence(const UChar* characters, size_t length, FontVariant variant) const
783 UChar32 baseCharacter;
784 size_t baseCharacterLength = 0;
785 U16_NEXT(characters, baseCharacterLength, length, baseCharacter);
787 GlyphData baseCharacterGlyphData = glyphDataForCharacter(baseCharacter, false, variant);
789 if (!baseCharacterGlyphData.glyph)
792 if (length == baseCharacterLength)
793 return baseCharacterGlyphData.font;
795 bool triedBaseCharacterFont = false;
797 for (unsigned i = 0; !fallbackRangesAt(i).isNull(); ++i) {
798 const Font* font = fallbackRangesAt(i).fontForCharacter(baseCharacter);
802 if (baseCharacter >= 0x0600 && baseCharacter <= 0x06ff && font->shouldNotBeUsedForArabic())
805 if (variant == NormalVariant) {
806 if (font->platformData().orientation() == Vertical) {
807 if (isCJKIdeographOrSymbol(baseCharacter) && !font->hasVerticalGlyphs()) {
808 variant = BrokenIdeographVariant;
809 font = font->brokenIdeographFont().get();
810 } else if (m_fontDescription.nonCJKGlyphOrientation() == NonCJKGlyphOrientationVerticalRight) {
811 Font* verticalRightFont = font->verticalRightOrientationFont().get();
812 Glyph verticalRightGlyph = verticalRightFont->glyphForCharacter(baseCharacter);
813 if (verticalRightGlyph == baseCharacterGlyphData.glyph)
814 font = verticalRightFont;
816 Font* uprightFont = font->uprightOrientationFont().get();
817 Glyph uprightGlyph = uprightFont->glyphForCharacter(baseCharacter);
818 if (uprightGlyph != baseCharacterGlyphData.glyph)
823 if (const Font* variantFont = font->variantFont(m_fontDescription, variant).get())
827 if (font == baseCharacterGlyphData.font)
828 triedBaseCharacterFont = true;
830 if (font->canRenderCombiningCharacterSequence(characters, length))
834 if (!triedBaseCharacterFont && baseCharacterGlyphData.font && baseCharacterGlyphData.font->canRenderCombiningCharacterSequence(characters, length))
835 return baseCharacterGlyphData.font;
837 return Font::systemFallback();