/** * This file is part of the html renderer for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2000 Dirk Mueller (mueller@kde.org) * Copyright (C) 2003, 2006 Apple Computer, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #import "config.h" #import "Font.h" #import "BlockExceptions.h" #import "CharacterNames.h" #import "FontData.h" #import "FontFallbackList.h" #import "GlyphBuffer.h" #import "GraphicsContext.h" #import "IntRect.h" #import "Logging.h" #import "TextStyle.h" #import "WebCoreSystemInterface.h" #import "WebCoreTextRenderer.h" #import "ShapeArabic.h" #define SYNTHETIC_OBLIQUE_ANGLE 14 #ifdef __LP64__ #define URefCon void* #else #define URefCon UInt32 #endif using namespace std; namespace WebCore { // ================================================================= // Font Class (Platform-Specific Portion) // ================================================================= struct ATSULayoutParameters { ATSULayoutParameters(const TextRun& run, const TextStyle& style) : m_run(run) , m_style(style) , m_font(0) , m_fonts(0) , m_charBuffer(0) , m_hasSyntheticBold(false) , m_syntheticBoldPass(false) , m_padPerSpace(0) {} void initialize(const Font*, const GraphicsContext* = 0); const TextRun& m_run; const TextStyle& m_style; const Font* m_font; ATSUTextLayout m_layout; const FontData **m_fonts; UChar *m_charBuffer; bool m_hasSyntheticBold; bool m_syntheticBoldPass; float m_padPerSpace; }; // Be sure to free the array allocated by this function. static TextRun addDirectionalOverride(const TextRun& run, bool rtl) { UChar* charactersWithOverride = new UChar[run.length() + 2]; charactersWithOverride[0] = rtl ? rightToLeftOverride : leftToRightOverride; memcpy(&charactersWithOverride[1], run.data(0), sizeof(UChar) * run.length()); charactersWithOverride[run.length() + 1] = popDirectionalFormatting; return TextRun(charactersWithOverride, run.length() + 2); } static void initializeATSUStyle(const FontData* fontData) { // The two NSFont calls in this method (pointSize and _atsFontID) do not raise exceptions. if (!fontData->m_ATSUStyleInitialized) { OSStatus status; ByteCount propTableSize; status = ATSUCreateStyle(&fontData->m_ATSUStyle); if (status != noErr) LOG_ERROR("ATSUCreateStyle failed (%d)", status); ATSUFontID fontID = wkGetNSFontATSUFontId(fontData->m_font.font()); if (fontID == 0) { ATSUDisposeStyle(fontData->m_ATSUStyle); LOG_ERROR("unable to get ATSUFontID for %@", fontData->m_font.font()); return; } CGAffineTransform transform = CGAffineTransformMakeScale(1, -1); if (fontData->m_font.syntheticOblique) transform = CGAffineTransformConcat(transform, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0)); Fixed fontSize = FloatToFixed([fontData->m_font.font() pointSize]); // Turn off automatic kerning until it is supported in the CG code path (6136 in bugzilla) Fract kerningInhibitFactor = FloatToFract(1.0); ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag }; ByteCount styleSizes[4] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(Fract) }; ATSUAttributeValuePtr styleValues[4] = { &fontSize, &fontID, &transform, &kerningInhibitFactor }; status = ATSUSetAttributes(fontData->m_ATSUStyle, 4, styleTags, styleSizes, styleValues); if (status != noErr) LOG_ERROR("ATSUSetAttributes failed (%d)", status); status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize); if (status == noErr) // naively assume that if a 'prop' table exists then it contains mirroring info fontData->m_ATSUMirrors = true; else if (status == kATSInvalidFontTableAccess) fontData->m_ATSUMirrors = false; else LOG_ERROR("ATSFontGetTable failed (%d)", status); // Turn off ligatures such as 'fi' to match the CG code path's behavior, until bugzilla 6135 is fixed. // Don't be too aggressive: if the font doesn't contain 'a', then assume that any ligatures it contains are // in characters that always go through ATSUI, and therefore allow them. Geeza Pro is an example. // See bugzilla 5166. if ([[fontData->m_font.font() coveredCharacterSet] characterIsMember:'a']) { ATSUFontFeatureType featureTypes[] = { kLigaturesType }; ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector }; status = ATSUSetFontFeatures(fontData->m_ATSUStyle, 1, featureTypes, featureSelectors); } fontData->m_ATSUStyleInitialized = true; } } static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector iCurrentOperation, ATSULineRef iLineRef, URefCon iRefCon, void *iOperationCallbackParameterPtr, ATSULayoutOperationCallbackStatus *oCallbackStatus) { ATSULayoutParameters *params = (ATSULayoutParameters *)iRefCon; OSStatus status; ItemCount count; ATSLayoutRecord *layoutRecords; if (params->m_style.applyWordRounding()) { status = ATSUDirectGetLayoutDataArrayPtrFromLineRef(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, true, (void **)&layoutRecords, &count); if (status != noErr) { *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue; return status; } Fixed lastNativePos = 0; float lastAdjustedPos = 0; const UChar* characters = params->m_charBuffer ? params->m_charBuffer : params->m_run.characters(); const FontData **renderers = params->m_fonts; const FontData *renderer; const FontData *lastRenderer = 0; UChar ch, nextCh; ByteCount offset = layoutRecords[0].originalOffset; nextCh = *(UChar *)(((char *)characters)+offset); bool shouldRound = false; bool syntheticBoldPass = params->m_syntheticBoldPass; Fixed syntheticBoldOffset = 0; ATSGlyphRef spaceGlyph = 0; bool hasExtraSpacing = params->m_font->letterSpacing() || params->m_font->wordSpacing() | params->m_style.padding(); float padding = params->m_style.padding(); // In the CoreGraphics code path, the rounding hack is applied in logical order. // Here it is applied in visual left-to-right order, which may be better. ItemCount lastRoundingChar = 0; ItemCount i; for (i = 1; i < count; i++) { bool isLastChar = i == count - 1; renderer = renderers[offset / 2]; if (renderer != lastRenderer) { lastRenderer = renderer; spaceGlyph = renderer->m_spaceGlyph; // The CoreGraphics interpretation of NSFontAntialiasedIntegerAdvancementsRenderingMode seems // to be "round each glyph's width to the nearest integer". This is not the same as ATSUI // does in any of its device-metrics modes. shouldRound = [renderer->m_font.font() renderingMode] == NSFontAntialiasedIntegerAdvancementsRenderingMode; if (syntheticBoldPass) syntheticBoldOffset = FloatToFixed(renderer->m_syntheticBoldOffset); } float width; if (nextCh == zeroWidthSpace || Font::treatAsZeroWidthSpace(nextCh) && !Font::treatAsSpace(nextCh)) { width = 0; layoutRecords[i-1].glyphID = spaceGlyph; } else { width = FixedToFloat(layoutRecords[i].realPos - lastNativePos); if (shouldRound) width = roundf(width); width += renderer->m_syntheticBoldOffset; if (renderer->m_treatAsFixedPitch ? width == renderer->m_spaceWidth : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace)) width = renderer->m_adjustedSpaceWidth; } lastNativePos = layoutRecords[i].realPos; if (hasExtraSpacing) { if (width && params->m_font->letterSpacing()) width +=params->m_font->letterSpacing(); if (Font::treatAsSpace(nextCh)) { if (params->m_style.padding()) { if (padding < params->m_padPerSpace) { width += padding; padding = 0; } else { width += params->m_padPerSpace; padding -= params->m_padPerSpace; } } if (offset != 0 && !Font::treatAsSpace(*((UChar *)(((char *)characters)+offset) - 1)) && params->m_font->wordSpacing()) width += params->m_font->wordSpacing(); } } ch = nextCh; offset = layoutRecords[i].originalOffset; // Use space for nextCh at the end of the loop so that we get inside the rounding hack code. // We won't actually round unless the other conditions are satisfied. nextCh = isLastChar ? ' ' : *(UChar *)(((char *)characters)+offset); if (Font::isRoundingHackCharacter(ch)) width = ceilf(width); lastAdjustedPos = lastAdjustedPos + width; if (Font::isRoundingHackCharacter(nextCh) && (!isLastChar || params->m_style.applyRunRounding())){ if (params->m_style.ltr()) lastAdjustedPos = ceilf(lastAdjustedPos); else { float roundingWidth = ceilf(lastAdjustedPos) - lastAdjustedPos; Fixed rw = FloatToFixed(roundingWidth); ItemCount j; for (j = lastRoundingChar; j < i; j++) layoutRecords[j].realPos += rw; lastRoundingChar = i; lastAdjustedPos += roundingWidth; } } if (syntheticBoldPass) { if (syntheticBoldOffset) layoutRecords[i-1].realPos += syntheticBoldOffset; else layoutRecords[i-1].glyphID = spaceGlyph; } layoutRecords[i].realPos = FloatToFixed(lastAdjustedPos); } status = ATSUDirectReleaseLayoutDataArrayPtr(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **)&layoutRecords); } *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled; return noErr; } static inline bool isArabicLamWithAlefLigature(UChar c) { return c >= 0xfef5 && c <= 0xfefc; } static void shapeArabic(const UChar* source, UChar* dest, unsigned totalLength, unsigned shapingStart) { while (shapingStart < totalLength) { unsigned shapingEnd; // We do not want to pass a Lam with Alef ligature followed by a space to the shaper, // since we want to be able to identify this sequence as the result of shaping a Lam // followed by an Alef and padding with a space. bool foundLigatureSpace = false; for (shapingEnd = shapingStart; !foundLigatureSpace && shapingEnd < totalLength - 1; ++shapingEnd) foundLigatureSpace = isArabicLamWithAlefLigature(source[shapingEnd]) && source[shapingEnd + 1] == ' '; shapingEnd++; UErrorCode shapingError = U_ZERO_ERROR; unsigned charsWritten = shapeArabic(source + shapingStart, shapingEnd - shapingStart, dest + shapingStart, shapingEnd - shapingStart, U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR, &shapingError); if (U_SUCCESS(shapingError) && charsWritten == shapingEnd - shapingStart) { for (unsigned j = shapingStart; j < shapingEnd - 1; ++j) { if (isArabicLamWithAlefLigature(dest[j]) && dest[j + 1] == ' ') dest[++j] = zeroWidthSpace; } if (foundLigatureSpace) { dest[shapingEnd] = ' '; shapingEnd++; } else if (isArabicLamWithAlefLigature(dest[shapingEnd - 1])) { // u_shapeArabic quirk: if the last two characters in the source string are a Lam and an Alef, // the space is put at the beginning of the string, despite U_SHAPE_LENGTH_FIXED_SPACES_NEAR. ASSERT(dest[shapingStart] == ' '); dest[shapingStart] = zeroWidthSpace; } } else { // Something went wrong. Abandon shaping and just copy the rest of the buffer. LOG_ERROR("u_shapeArabic failed(%d)", shapingError); shapingEnd = totalLength; memcpy(dest + shapingStart, source + shapingStart, (shapingEnd - shapingStart) * sizeof(UChar)); } shapingStart = shapingEnd; } } void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* graphicsContext) { m_font = font; const FontData* fontData = font->primaryFont(); m_fonts = new const FontData*[m_run.length()]; m_charBuffer = font->isSmallCaps() ? new UChar[m_run.length()] : 0; ATSUTextLayout layout; OSStatus status; ATSULayoutOperationOverrideSpecifier overrideSpecifier; initializeATSUStyle(fontData); // FIXME: This is currently missing the following required features that the CoreGraphics code path has: // - \n, \t, and nonbreaking space render as a space. UniCharCount runLength = m_run.length(); if (m_charBuffer) memcpy(m_charBuffer, m_run.characters(), runLength * sizeof(UChar)); status = ATSUCreateTextLayoutWithTextPtr( (m_charBuffer ? m_charBuffer : m_run.characters()), 0, // offset runLength, // length runLength, // total length 1, // styleRunCount &runLength, // length of style run &fontData->m_ATSUStyle, &layout); if (status != noErr) LOG_ERROR("ATSUCreateTextLayoutWithTextPtr failed(%d)", status); m_layout = layout; ATSUSetTextLayoutRefCon(m_layout, (URefCon)this); // FIXME: There are certain times when this method is called, when we don't have access to a GraphicsContext // measuring text runs with floatWidthForComplexText is one example. // ATSUI requires that we pass a valid CGContextRef to it when specifying kATSUCGContextTag (crashes when passed 0) // ATSUI disables sub-pixel rendering if kATSUCGContextTag is not specified! So we're in a bind. // Sometimes [[NSGraphicsContext currentContext] graphicsPort] may return the wrong (or no!) context. Nothing we can do about it (yet). CGContextRef cgContext = graphicsContext ? graphicsContext->platformContext() : (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers; Boolean rtl = m_style.rtl(); overrideSpecifier.operationSelector = kATSULayoutOperationPostLayoutAdjustment; overrideSpecifier.overrideUPP = overrideLayoutOperation; ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag, kATSULayoutOperationOverrideTag }; ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean), sizeof(ATSULayoutOperationOverrideSpecifier) }; ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl, &overrideSpecifier }; status = ATSUSetLayoutControls(layout, (m_style.applyWordRounding() ? 4 : 3), tags, sizes, values); if (status != noErr) LOG_ERROR("ATSUSetLayoutControls failed(%d)", status); status = ATSUSetTransientFontMatching(layout, YES); if (status != noErr) LOG_ERROR("ATSUSetTransientFontMatching failed(%d)", status); m_hasSyntheticBold = false; ATSUFontID ATSUSubstituteFont; UniCharArrayOffset substituteOffset = 0; UniCharCount substituteLength; UniCharArrayOffset lastOffset; const FontData* substituteFontData = 0; while (substituteOffset < runLength) { lastOffset = substituteOffset; status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength); if (status == kATSUFontsMatched || status == kATSUFontsNotMatched) { substituteFontData = m_font->fontDataForCharacters(m_run.characters() + substituteOffset, substituteLength); if (substituteFontData) { initializeATSUStyle(substituteFontData); if (substituteFontData->m_ATSUStyle) ATSUSetRunStyle(layout, substituteFontData->m_ATSUStyle, substituteOffset, substituteLength); } else substituteFontData = fontData; } else { substituteOffset = runLength; substituteLength = 0; } bool shapedArabic = false; bool isSmallCap = false; UniCharArrayOffset firstSmallCap = 0; const FontData *r = fontData; UniCharArrayOffset i; for (i = lastOffset; ; i++) { if (i == substituteOffset || i == substituteOffset + substituteLength) { if (isSmallCap) { isSmallCap = false; initializeATSUStyle(r->smallCapsFontData(m_font->fontDescription())); ATSUSetRunStyle(layout, r->smallCapsFontData(m_font->fontDescription())->m_ATSUStyle, firstSmallCap, i - firstSmallCap); } if (i == substituteOffset && substituteLength > 0) r = substituteFontData; else break; } if (!shapedArabic && ublock_getCode(m_run[i]) == UBLOCK_ARABIC && !r->shapesArabic()) { shapedArabic = true; if (!m_charBuffer) { m_charBuffer = new UChar[runLength]; memcpy(m_charBuffer, m_run.characters(), i * sizeof(UChar)); ATSUTextMoved(layout, m_charBuffer); } shapeArabic(m_run.characters(), m_charBuffer, runLength, i); } if (m_style.rtl() && !r->m_ATSUMirrors) { UChar mirroredChar = u_charMirror(m_run[i]); if (mirroredChar != m_run[i]) { if (!m_charBuffer) { m_charBuffer = new UChar[runLength]; memcpy(m_charBuffer, m_run.characters(), runLength * sizeof(UChar)); ATSUTextMoved(layout, m_charBuffer); } m_charBuffer[i] = mirroredChar; } } if (m_font->isSmallCaps()) { const FontData* smallCapsData = r->smallCapsFontData(m_font->fontDescription()); UChar c = m_charBuffer[i]; UChar newC; if (U_GET_GC_MASK(c) & U_GC_M_MASK) m_fonts[i] = isSmallCap ? smallCapsData : r; else if (!u_isUUppercase(c) && (newC = u_toupper(c)) != c) { m_charBuffer[i] = newC; if (!isSmallCap) { isSmallCap = true; firstSmallCap = i; } m_fonts[i] = smallCapsData; } else { if (isSmallCap) { isSmallCap = false; initializeATSUStyle(smallCapsData); ATSUSetRunStyle(layout, smallCapsData->m_ATSUStyle, firstSmallCap, i - firstSmallCap); } m_fonts[i] = r; } } else m_fonts[i] = r; if (m_fonts[i]->m_syntheticBoldOffset) m_hasSyntheticBold = true; } substituteOffset += substituteLength; } if (m_style.padding()) { float numSpaces = 0; unsigned k; for (k = 0; k < runLength; k++) if (Font::treatAsSpace(m_run[k])) numSpaces++; if (numSpaces == 0) m_padPerSpace = 0; else m_padPerSpace = ceilf(m_style.padding() / numSpaces); } else m_padPerSpace = 0; } static void disposeATSULayoutParameters(ATSULayoutParameters *params) { ATSUDisposeTextLayout(params->m_layout); delete []params->m_charBuffer; delete []params->m_fonts; } FloatRect Font::selectionRectForComplexText(const TextRun& run, const TextStyle& style, const IntPoint& point, int h, int from, int to) const { TextRun adjustedRun = style.directionalOverride() ? addDirectionalOverride(run, style.rtl()) : run; if (style.directionalOverride()) { from++; to++; } ATSULayoutParameters params(adjustedRun, style); params.initialize(this); ATSTrapezoid firstGlyphBounds; ItemCount actualNumBounds; OSStatus status = ATSUGetGlyphBounds(params.m_layout, 0, 0, from, to - from, kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds); if (status != noErr || actualNumBounds != 1) { static ATSTrapezoid zeroTrapezoid = { {0, 0}, {0, 0}, {0, 0}, {0, 0} }; firstGlyphBounds = zeroTrapezoid; } disposeATSULayoutParameters(¶ms); float beforeWidth = MIN(FixedToFloat(firstGlyphBounds.lowerLeft.x), FixedToFloat(firstGlyphBounds.upperLeft.x)); float afterWidth = MAX(FixedToFloat(firstGlyphBounds.lowerRight.x), FixedToFloat(firstGlyphBounds.upperRight.x)); FloatRect rect(point.x() + floorf(beforeWidth), point.y(), roundf(afterWidth) - floorf(beforeWidth), h); if (style.directionalOverride()) delete []adjustedRun.characters(); return rect; } void Font::drawComplexText(GraphicsContext* graphicsContext, const TextRun& run, const TextStyle& style, const FloatPoint& point, int from, int to) const { OSStatus status; int drawPortionLength = to - from; TextRun adjustedRun = style.directionalOverride() ? addDirectionalOverride(run, style.rtl()) : run; if (style.directionalOverride()) from++; ATSULayoutParameters params(TextRun(adjustedRun.characters(), adjustedRun.length()), style); params.initialize(this, graphicsContext); // ATSUI can't draw beyond -32768 to +32767 so we translate the CTM and tell ATSUI to draw at (0, 0). CGContextRef context = graphicsContext->platformContext(); CGContextTranslateCTM(context, point.x(), point.y()); status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0); if (status == noErr && params.m_hasSyntheticBold) { // Force relayout for the bold pass ATSUClearLayoutCache(params.m_layout, 0); params.m_syntheticBoldPass = true; status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0); } CGContextTranslateCTM(context, -point.x(), -point.y()); if (status != noErr) // Nothing to do but report the error (dev build only). LOG_ERROR("ATSUDrawText() failed(%d)", status); disposeATSULayoutParameters(¶ms); if (style.directionalOverride()) delete []adjustedRun.characters(); } float Font::floatWidthForComplexText(const TextRun& run, const TextStyle& style) const { if (run.length() == 0) return 0; ATSULayoutParameters params(run, style); params.initialize(this); OSStatus status; ATSTrapezoid firstGlyphBounds; ItemCount actualNumBounds; status = ATSUGetGlyphBounds(params.m_layout, 0, 0, 0, run.length(), kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds); if (status != noErr) LOG_ERROR("ATSUGetGlyphBounds() failed(%d)", status); if (actualNumBounds != 1) LOG_ERROR("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds); disposeATSULayoutParameters(¶ms); return MAX(FixedToFloat(firstGlyphBounds.upperRight.x), FixedToFloat(firstGlyphBounds.lowerRight.x)) - MIN(FixedToFloat(firstGlyphBounds.upperLeft.x), FixedToFloat(firstGlyphBounds.lowerLeft.x)); } int Font::offsetForPositionForComplexText(const TextRun& run, const TextStyle& style, int x, bool includePartialGlyphs) const { TextRun adjustedRun = style.directionalOverride() ? addDirectionalOverride(run, style.rtl()) : run; ATSULayoutParameters params(adjustedRun, style); params.initialize(this); UniCharArrayOffset primaryOffset = 0; // FIXME: No idea how to avoid including partial glyphs. // Not even sure if that's the behavior this yields now. Boolean isLeading; UniCharArrayOffset secondaryOffset = 0; OSStatus status = ATSUPositionToOffset(params.m_layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset); unsigned offset; if (status == noErr) { offset = (unsigned)primaryOffset; if (style.directionalOverride() && offset > 0) offset--; } else // Failed to find offset! Return 0 offset. offset = 0; disposeATSULayoutParameters(¶ms); if (style.directionalOverride()) delete []adjustedRun.characters(); return offset; } void Font::drawGlyphs(GraphicsContext* context, const FontData* font, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const { CGContextRef cgContext = context->platformContext(); bool originalShouldUseFontSmoothing = wkCGContextGetShouldSmoothFonts(cgContext); bool newShouldUseFontSmoothing = WebCoreShouldUseFontSmoothing(); if (originalShouldUseFontSmoothing != newShouldUseFontSmoothing) CGContextSetShouldSmoothFonts(cgContext, newShouldUseFontSmoothing); const FontPlatformData& platformData = font->platformData(); NSFont* drawFont; if (!isPrinterFont()) { drawFont = [platformData.font() screenFont]; if (drawFont != platformData.font()) // We are getting this in too many places (3406411); use ERROR so it only prints on debug versions for now. (We should debug this also, eventually). LOG_ERROR("Attempting to set non-screen font (%@) when drawing to screen. Using screen font anyway, may result in incorrect metrics.", [[[platformData.font() fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]); } else { drawFont = [platformData.font() printerFont]; if (drawFont != platformData.font()) NSLog(@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.", [[[platformData.font() fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]); } CGContextSetFont(cgContext, wkGetCGFontFromNSFont(drawFont)); CGAffineTransform matrix; memcpy(&matrix, [drawFont matrix], sizeof(matrix)); matrix.b = -matrix.b; matrix.d = -matrix.d; if (platformData.syntheticOblique) matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0)); CGContextSetTextMatrix(cgContext, matrix); wkSetCGFontRenderingMode(cgContext, drawFont); CGContextSetFontSize(cgContext, 1.0f); CGContextSetTextPosition(cgContext, point.x(), point.y()); CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs); if (font->m_syntheticBoldOffset) { CGContextSetTextPosition(cgContext, point.x() + font->m_syntheticBoldOffset, point.y()); CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs); } if (originalShouldUseFontSmoothing != newShouldUseFontSmoothing) CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing); } }