📄 fontmacatsui.mm
字号:
/* * 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"#if USE(ATSUI)#import "CharacterNames.h"#import "GraphicsContext.h"#import "Logging.h"#import "ShapeArabic.h"#import "SimpleFontData.h"#import <wtf/OwnArrayPtr.h>#define SYNTHETIC_OBLIQUE_ANGLE 14#ifdef __LP64__#define URefCon void*#else#define URefCon UInt32#endifusing namespace std;namespace WebCore {struct ATSULayoutParameters : Noncopyable{ ATSULayoutParameters(const TextRun& run) : m_run(run) , m_font(0) , m_hasSyntheticBold(false) , m_syntheticBoldPass(false) , m_padPerSpace(0) {} ~ATSULayoutParameters() { ATSUDisposeTextLayout(m_layout); } void initialize(const Font*, const GraphicsContext* = 0); const TextRun& m_run; const Font* m_font; ATSUTextLayout m_layout; OwnArrayPtr<const SimpleFontData*> m_fonts; OwnArrayPtr<UChar> m_charBuffer; bool m_hasSyntheticBold; bool m_syntheticBoldPass; float m_padPerSpace;};static TextRun copyRunForDirectionalOverrideIfNecessary(const TextRun& run, OwnArrayPtr<UChar>& charactersWithOverride){ if (!run.directionalOverride()) return run; charactersWithOverride.set(new UChar[run.length() + 2]); charactersWithOverride[0] = run.rtl() ? rightToLeftOverride : leftToRightOverride; memcpy(&charactersWithOverride[1], run.data(0), sizeof(UChar) * run.length()); charactersWithOverride[run.length() + 1] = popDirectionalFormatting; TextRun result = run; result.setText(charactersWithOverride.get(), run.length() + 2); return result;}static bool fontHasMirroringInfo(ATSUFontID fontID){ ByteCount propTableSize; OSStatus status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize); if (status == noErr) // naively assume that if a 'prop' table exists then it contains mirroring info return true; else if (status != kATSInvalidFontTableAccess) // anything other than a missing table is logged as an error LOG_ERROR("ATSFontGetTable failed (%d)", status); return false;}static void disableLigatures(const SimpleFontData* fontData){ // 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->platformData().allowsLigatures()) return; ATSUFontFeatureType featureTypes[] = { kLigaturesType }; ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector }; OSStatus status = ATSUSetFontFeatures(fontData->m_ATSUStyle, 1, featureTypes, featureSelectors); if (status != noErr) LOG_ERROR("ATSUSetFontFeatures failed (%d) -- ligatures remain enabled", status);}static void initializeATSUStyle(const SimpleFontData* fontData){ if (fontData->m_ATSUStyleInitialized) return; ATSUFontID fontID = fontData->platformData().m_atsuFontID; if (!fontID) { LOG_ERROR("unable to get ATSUFontID for %@", fontData->m_font.font()); return; } OSStatus status = ATSUCreateStyle(&fontData->m_ATSUStyle); if (status != noErr) // Who knows how many ATSU functions will crash when passed a NULL style... LOG_ERROR("ATSUCreateStyle failed (%d)", status); CGAffineTransform transform = CGAffineTransformMakeScale(1, -1); if (fontData->m_font.m_syntheticOblique) transform = CGAffineTransformConcat(transform, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0)); Fixed fontSize = FloatToFixed(fontData->platformData().m_size); ByteCount styleSizes[4] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(Fract) }; // Turn off automatic kerning until it is supported in the CG code path (bug 6136) Fract kerningInhibitFactor = FloatToFract(1.0); ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag }; 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); fontData->m_ATSUMirrors = fontHasMirroringInfo(fontID); // Turn off ligatures such as 'fi' to match the CG code path's behavior, until bug 6135 is fixed. disableLigatures(fontData); fontData->m_ATSUStyleInitialized = true;}static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector, ATSULineRef iLineRef, URefCon iRefCon, void*, ATSULayoutOperationCallbackStatus* oCallbackStatus){ ATSULayoutParameters* params = reinterpret_cast<ATSULayoutParameters*>(iRefCon); OSStatus status; ItemCount count; ATSLayoutRecord *layoutRecords; if (params->m_run.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.get() : params->m_run.characters(); const SimpleFontData** renderers = params->m_fonts.get(); const SimpleFontData* renderer; const SimpleFontData* lastRenderer = 0; ByteCount offset = layoutRecords[0].originalOffset; UChar 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_run.padding()) && !params->m_run.spacingDisabled(); float padding = params->m_run.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->platformData().roundsGlyphAdvances(); 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_run.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(); } } UChar 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_run.applyRunRounding())){ if (params->m_run.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; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -