diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:15 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:15 -0800 |
commit | 1cbdecfa9fc428ac2d8aca0fa91c9580b3d57353 (patch) | |
tree | 4457a7306ea5acb43fe05bfe0973b1f7faf97ba2 /WebCore/platform/graphics/mac | |
parent | 9364f22aed35e1a1e9d07c121510f80be3ab0502 (diff) | |
download | external_webkit-1cbdecfa9fc428ac2d8aca0fa91c9580b3d57353.zip external_webkit-1cbdecfa9fc428ac2d8aca0fa91c9580b3d57353.tar.gz external_webkit-1cbdecfa9fc428ac2d8aca0fa91c9580b3d57353.tar.bz2 |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'WebCore/platform/graphics/mac')
17 files changed, 1940 insertions, 783 deletions
diff --git a/WebCore/platform/graphics/mac/ColorMac.mm b/WebCore/platform/graphics/mac/ColorMac.mm index 5c89715..96fdc39 100644 --- a/WebCore/platform/graphics/mac/ColorMac.mm +++ b/WebCore/platform/graphics/mac/ColorMac.mm @@ -39,8 +39,6 @@ namespace WebCore { // NSColor calls don't throw, so no need to block Cocoa exceptions in this file static RGBA32 oldAquaFocusRingColor = 0xFF7DADD9; -static bool tintIsKnown; -static void (*tintChangeFunction)(); static RGBA32 systemFocusRingColor; static bool useOldAquaFocusRingColor; @@ -126,29 +124,17 @@ CGColorRef cgColor(const Color& c) return CGColorFromNSColor(nsColor(c)); } -static void observeTint() -{ - ASSERT(!tintIsKnown); - [[NSNotificationCenter defaultCenter] addObserver:[WebCoreControlTintObserver class] - selector:@selector(controlTintDidChange) - name:NSControlTintDidChangeNotification - object:NSApp]; - [WebCoreControlTintObserver controlTintDidChange]; - tintIsKnown = true; -} - -void setFocusRingColorChangeFunction(void (*function)()) -{ - ASSERT(!tintChangeFunction); - tintChangeFunction = function; - if (!tintIsKnown) - observeTint(); -} - Color focusRingColor() { - if (!tintIsKnown) - observeTint(); + static bool tintIsKnown = false; + if (!tintIsKnown) { + [[NSNotificationCenter defaultCenter] addObserver:[WebCoreControlTintObserver class] + selector:@selector(controlTintDidChange) + name:NSControlTintDidChangeNotification + object:NSApp]; + [WebCoreControlTintObserver controlTintDidChange]; + tintIsKnown = true; + } if (usesTestModeFocusRingColor()) return oldAquaFocusRingColor; diff --git a/WebCore/platform/graphics/mac/CoreTextController.cpp b/WebCore/platform/graphics/mac/CoreTextController.cpp new file mode 100644 index 0000000..171a7ec --- /dev/null +++ b/WebCore/platform/graphics/mac/CoreTextController.cpp @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CoreTextController.h" + +#if USE(CORE_TEXT) + +#include "CharacterNames.h" +#include "Font.h" +#include "FontCache.h" +#include "SimpleFontData.h" +#include "TextBreakIterator.h" +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +static inline CGFloat roundCGFloat(CGFloat f) +{ + if (sizeof(CGFloat) == sizeof(float)) + return roundf(static_cast<float>(f)); + return static_cast<CGFloat>(round(f)); +} + +static inline CGFloat ceilCGFloat(CGFloat f) +{ + if (sizeof(CGFloat) == sizeof(float)) + return ceilf(static_cast<float>(f)); + return static_cast<CGFloat>(ceil(f)); +} + +CoreTextController::CoreTextRun::CoreTextRun(CTRunRef ctRun, const SimpleFontData* fontData, const UChar* characters, unsigned stringLocation, size_t stringLength) + : m_CTRun(ctRun) + , m_fontData(fontData) + , m_characters(characters) + , m_stringLocation(stringLocation) + , m_stringLength(stringLength) +{ + m_glyphCount = CTRunGetGlyphCount(ctRun); + m_indices = CTRunGetStringIndicesPtr(ctRun); + if (!m_indices) { + m_indicesData.adoptCF(CFDataCreateMutable(kCFAllocatorDefault, m_glyphCount * sizeof(CFIndex))); + CFDataIncreaseLength(m_indicesData.get(), m_glyphCount * sizeof(CFIndex)); + m_indices = reinterpret_cast<const CFIndex*>(CFDataGetMutableBytePtr(m_indicesData.get())); + CTRunGetStringIndices(ctRun, CFRangeMake(0, 0), const_cast<CFIndex*>(m_indices)); + } +} + +// Missing glyphs run constructor. Core Text will not generate a run of missing glyphs, instead falling back on +// glyphs from LastResort. We want to use the primary font's missing glyph in order to match the fast text code path. +CoreTextController::CoreTextRun::CoreTextRun(const SimpleFontData* fontData, const UChar* characters, unsigned stringLocation, size_t stringLength, bool ltr) + : m_fontData(fontData) + , m_characters(characters) + , m_stringLocation(stringLocation) + , m_stringLength(stringLength) +{ + Vector<CFIndex, 16> indices; + unsigned r = 0; + while (r < stringLength) { + indices.append(r); + if (U_IS_SURROGATE(characters[r])) { + ASSERT(r + 1 < stringLength); + ASSERT(U_IS_SURROGATE_LEAD(characters[r])); + ASSERT(U_IS_TRAIL(characters[r + 1])); + r += 2; + } else + r++; + } + m_glyphCount = indices.size(); + if (!ltr) { + for (unsigned r = 0, end = m_glyphCount - 1; r < m_glyphCount / 2; ++r, --end) + std::swap(indices[r], indices[end]); + } + m_indicesData.adoptCF(CFDataCreateMutable(kCFAllocatorDefault, m_glyphCount * sizeof(CFIndex))); + CFDataAppendBytes(m_indicesData.get(), reinterpret_cast<const UInt8*>(indices.data()), m_glyphCount * sizeof(CFIndex)); + m_indices = reinterpret_cast<const CFIndex*>(CFDataGetBytePtr(m_indicesData.get())); +} + +CoreTextController::CoreTextController(const Font* font, const TextRun& run, bool mayUseNaturalWritingDirection) + : m_font(*font) + , m_run(run) + , m_mayUseNaturalWritingDirection(mayUseNaturalWritingDirection) + , m_currentCharacter(0) + , m_end(run.length()) + , m_totalWidth(0) + , m_runWidthSoFar(0) + , m_numGlyphsSoFar(0) + , m_currentRun(0) + , m_glyphInCurrentRun(0) + , m_finalRoundingWidth(0) + , m_lastRoundingGlyph(0) +{ + m_padding = m_run.padding(); + if (!m_padding) + m_padPerSpace = 0; + else { + float numSpaces = 0; + for (int s = 0; s < m_run.length(); s++) + if (Font::treatAsSpace(m_run[s])) + numSpaces++; + + if (numSpaces == 0) + m_padPerSpace = 0; + else + m_padPerSpace = ceilf(m_run.padding() / numSpaces); + } + + collectCoreTextRuns(); + adjustGlyphsAndAdvances(); +} + +int CoreTextController::offsetForPosition(int h, bool includePartialGlyphs) +{ + // FIXME: For positions occurring within a ligature, we should return the closest "ligature caret" or + // approximate it by dividing the width of the ligature by the number of characters it encompasses. + // However, Core Text does not expose a low-level API for directly finding + // out how many characters a ligature encompasses (the "attachment count"). + if (h >= m_totalWidth) + return m_run.ltr() ? m_end : 0; + if (h < 0) + return m_run.ltr() ? 0 : m_end; + + CGFloat x = h; + + size_t runCount = m_coreTextRuns.size(); + size_t offsetIntoAdjustedGlyphs = 0; + + for (size_t r = 0; r < runCount; ++r) { + const CoreTextRun& coreTextRun = m_coreTextRuns[r]; + for (unsigned j = 0; j < coreTextRun.glyphCount(); ++j) { + CGFloat adjustedAdvance = m_adjustedAdvances[offsetIntoAdjustedGlyphs + j].width; + if (x <= adjustedAdvance) { + CFIndex hitIndex = coreTextRun.indexAt(j); + int stringLength = coreTextRun.stringLength(); + TextBreakIterator* characterIterator = characterBreakIterator(coreTextRun.characters(), stringLength); + int clusterStart; + if (isTextBreak(characterIterator, hitIndex)) + clusterStart = hitIndex; + else { + clusterStart = textBreakPreceding(characterIterator, hitIndex); + if (clusterStart == TextBreakDone) + clusterStart = 0; + } + + if (!includePartialGlyphs) + return coreTextRun.stringLocation() + clusterStart; + + int clusterEnd = textBreakFollowing(characterIterator, hitIndex); + if (clusterEnd == TextBreakDone) + clusterEnd = stringLength; + + CGFloat clusterWidth = adjustedAdvance; + // FIXME: The search stops at the boundaries of coreTextRun. In theory, it should go on into neighboring CoreTextRuns + // derived from the same CTLine. In practice, we do not expect there to be more than one CTRun in a CTLine, as no + // reordering and on font fallback should occur within a CTLine. + if (clusterEnd - clusterStart > 1) { + int firstGlyphBeforeCluster = j - 1; + while (firstGlyphBeforeCluster && coreTextRun.indexAt(firstGlyphBeforeCluster) >= clusterStart && coreTextRun.indexAt(firstGlyphBeforeCluster) < clusterEnd) { + CGFloat width = m_adjustedAdvances[offsetIntoAdjustedGlyphs + firstGlyphBeforeCluster].width; + clusterWidth += width; + x += width; + firstGlyphBeforeCluster--; + } + unsigned firstGlyphAfterCluster = j + 1; + while (firstGlyphAfterCluster < coreTextRun.glyphCount() && coreTextRun.indexAt(firstGlyphAfterCluster) >= clusterStart && coreTextRun.indexAt(firstGlyphAfterCluster) < clusterEnd) { + clusterWidth += m_adjustedAdvances[offsetIntoAdjustedGlyphs + firstGlyphAfterCluster].width; + firstGlyphAfterCluster++; + } + } + if (x <= clusterWidth / 2) + return coreTextRun.stringLocation() + (m_run.ltr() ? clusterStart : clusterEnd); + else + return coreTextRun.stringLocation() + (m_run.ltr() ? clusterEnd : clusterStart); + } + x -= adjustedAdvance; + } + offsetIntoAdjustedGlyphs += coreTextRun.glyphCount(); + } + + ASSERT_NOT_REACHED(); + return 0; +} + +void CoreTextController::collectCoreTextRuns() +{ + if (!m_end) + return; + + // We break up glyph run generation for the string by FontData and (if needed) the use of small caps. + const UChar* cp = m_run.characters(); + bool hasTrailingSoftHyphen = m_run[m_end - 1] == softHyphen; + + if (m_font.isSmallCaps() || hasTrailingSoftHyphen) + m_smallCapsBuffer.resize(m_end); + + unsigned indexOfFontTransition = m_run.rtl() ? m_end - 1 : 0; + const UChar* curr = m_run.rtl() ? cp + m_end - 1 : cp; + const UChar* end = m_run.rtl() ? cp - 1 : cp + m_end; + + // FIXME: Using HYPHEN-MINUS rather than HYPHEN because Times has a HYPHEN-MINUS glyph that looks like its + // SOFT-HYPHEN glyph, and has no HYPHEN glyph. + static const UChar hyphen = '-'; + + if (hasTrailingSoftHyphen && m_run.rtl()) { + collectCoreTextRunsForCharacters(&hyphen, 1, m_end - 1, m_font.glyphDataForCharacter(hyphen, false).fontData); + indexOfFontTransition--; + curr--; + } + + GlyphData glyphData; + GlyphData nextGlyphData; + + bool isSurrogate = U16_IS_SURROGATE(*curr); + if (isSurrogate) { + if (m_run.ltr()) { + if (!U16_IS_SURROGATE_LEAD(curr[0]) || curr + 1 == end || !U16_IS_TRAIL(curr[1])) + return; + nextGlyphData = m_font.glyphDataForCharacter(U16_GET_SUPPLEMENTARY(curr[0], curr[1]), false); + } else { + if (!U16_IS_TRAIL(curr[0]) || curr -1 == end || !U16_IS_SURROGATE_LEAD(curr[-1])) + return; + nextGlyphData = m_font.glyphDataForCharacter(U16_GET_SUPPLEMENTARY(curr[-1], curr[0]), false); + } + } else + nextGlyphData = m_font.glyphDataForCharacter(*curr, false); + + UChar newC = 0; + + bool isSmallCaps; + bool nextIsSmallCaps = !isSurrogate && m_font.isSmallCaps() && !(U_GET_GC_MASK(*curr) & U_GC_M_MASK) && (newC = u_toupper(*curr)) != *curr; + + if (nextIsSmallCaps) + m_smallCapsBuffer[curr - cp] = newC; + + while (true) { + curr = m_run.rtl() ? curr - (isSurrogate ? 2 : 1) : curr + (isSurrogate ? 2 : 1); + if (curr == end) + break; + + glyphData = nextGlyphData; + isSmallCaps = nextIsSmallCaps; + int index = curr - cp; + isSurrogate = U16_IS_SURROGATE(*curr); + UChar c = *curr; + bool forceSmallCaps = !isSurrogate && isSmallCaps && (U_GET_GC_MASK(c) & U_GC_M_MASK); + if (isSurrogate) { + if (m_run.ltr()) { + if (!U16_IS_SURROGATE_LEAD(curr[0]) || curr + 1 == end || !U16_IS_TRAIL(curr[1])) + return; + nextGlyphData = m_font.glyphDataForCharacter(U16_GET_SUPPLEMENTARY(curr[0], curr[1]), false); + } else { + if (!U16_IS_TRAIL(curr[0]) || curr -1 == end || !U16_IS_SURROGATE_LEAD(curr[-1])) + return; + nextGlyphData = m_font.glyphDataForCharacter(U16_GET_SUPPLEMENTARY(curr[-1], curr[0]), false); + } + } else + nextGlyphData = m_font.glyphDataForCharacter(*curr, false, forceSmallCaps); + + if (!isSurrogate && m_font.isSmallCaps()) { + nextIsSmallCaps = forceSmallCaps || (newC = u_toupper(c)) != c; + if (nextIsSmallCaps) + m_smallCapsBuffer[index] = forceSmallCaps ? c : newC; + } + + if (nextGlyphData.fontData != glyphData.fontData || nextIsSmallCaps != isSmallCaps || !nextGlyphData.glyph != !glyphData.glyph) { + int itemStart = m_run.rtl() ? index + 1 : indexOfFontTransition; + int itemLength = m_run.rtl() ? indexOfFontTransition - index : index - indexOfFontTransition; + collectCoreTextRunsForCharacters((isSmallCaps ? m_smallCapsBuffer.data() : cp) + itemStart, itemLength, itemStart, glyphData.glyph ? glyphData.fontData : 0); + indexOfFontTransition = index; + } + } + + int itemLength = m_run.rtl() ? indexOfFontTransition + 1 : m_end - indexOfFontTransition - (hasTrailingSoftHyphen ? 1 : 0); + if (itemLength) { + int itemStart = m_run.rtl() ? 0 : indexOfFontTransition; + collectCoreTextRunsForCharacters((nextIsSmallCaps ? m_smallCapsBuffer.data() : cp) + itemStart, itemLength, itemStart, nextGlyphData.glyph ? nextGlyphData.fontData : 0); + } + + if (hasTrailingSoftHyphen && m_run.ltr()) + collectCoreTextRunsForCharacters(&hyphen, 1, m_end - 1, m_font.glyphDataForCharacter(hyphen, false).fontData); +} + +void CoreTextController::advance(unsigned offset, GlyphBuffer* glyphBuffer) +{ + // FIXME: For offsets falling inside a ligature, we should advance only as far as the appropriate "ligature caret" + // or divide the width of the ligature by the number of offsets it encompasses and make an advance proportional + // to the offsets into the ligature. However, Core Text does not expose a low-level API for + // directly finding out how many characters a ligature encompasses (the "attachment count"). + if (static_cast<int>(offset) > m_end) + offset = m_end; + + if (offset <= m_currentCharacter) + return; + + m_currentCharacter = offset; + + size_t runCount = m_coreTextRuns.size(); + + bool ltr = m_run.ltr(); + + unsigned k = ltr ? m_numGlyphsSoFar : m_adjustedGlyphs.size() - 1 - m_numGlyphsSoFar; + while (m_currentRun < runCount) { + const CoreTextRun& coreTextRun = m_coreTextRuns[ltr ? m_currentRun : runCount - 1 - m_currentRun]; + size_t glyphCount = coreTextRun.glyphCount(); + unsigned g = ltr ? m_glyphInCurrentRun : glyphCount - 1 - m_glyphInCurrentRun; + while (m_glyphInCurrentRun < glyphCount) { + if (coreTextRun.indexAt(g) + coreTextRun.stringLocation() >= m_currentCharacter) + return; + CGSize adjustedAdvance = m_adjustedAdvances[k]; + if (glyphBuffer) + glyphBuffer->add(m_adjustedGlyphs[k], coreTextRun.fontData(), adjustedAdvance); + m_runWidthSoFar += adjustedAdvance.width; + m_numGlyphsSoFar++; + m_glyphInCurrentRun++; + if (ltr) { + g++; + k++; + } else { + g--; + k--; + } + } + m_currentRun++; + m_glyphInCurrentRun = 0; + } + if (!ltr && m_numGlyphsSoFar == m_adjustedAdvances.size()) + m_runWidthSoFar += m_finalRoundingWidth; +} + +void CoreTextController::collectCoreTextRunsForCharacters(const UChar* cp, unsigned length, unsigned stringLocation, const SimpleFontData* fontData) +{ + if (!fontData) { + // Create a run of missing glyphs from the primary font. + m_coreTextRuns.append(CoreTextRun(m_font.primaryFont(), cp, stringLocation, length, m_run.ltr())); + return; + } + + RetainPtr<CFStringRef> string(AdoptCF, CFStringCreateWithCharactersNoCopy(NULL, cp, length, kCFAllocatorNull)); + + RetainPtr<CFAttributedStringRef> attributedString(AdoptCF, CFAttributedStringCreate(NULL, string.get(), fontData->getCFStringAttributes())); + + RetainPtr<CTTypesetterRef> typesetter; + + if (!m_mayUseNaturalWritingDirection || m_run.directionalOverride()) { + static const void* optionKeys[] = { kCTTypesetterOptionForcedEmbeddingLevel }; + static const void* ltrOptionValues[] = { kCFBooleanFalse }; + static const void* rtlOptionValues[] = { kCFBooleanTrue }; + static RetainPtr<CFDictionaryRef> ltrTypesetterOptions(AdoptCF, CFDictionaryCreate(kCFAllocatorDefault, optionKeys, ltrOptionValues, sizeof(optionKeys) / sizeof(*optionKeys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + static RetainPtr<CFDictionaryRef> rtlTypesetterOptions(AdoptCF, CFDictionaryCreate(kCFAllocatorDefault, optionKeys, rtlOptionValues, sizeof(optionKeys) / sizeof(*optionKeys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + typesetter.adoptCF(CTTypesetterCreateWithAttributedStringAndOptions(attributedString.get(), m_run.ltr() ? ltrTypesetterOptions.get() : rtlTypesetterOptions.get())); + } else + typesetter.adoptCF(CTTypesetterCreateWithAttributedString(attributedString.get())); + + RetainPtr<CTLineRef> line(AdoptCF, CTTypesetterCreateLine(typesetter.get(), CFRangeMake(0, 0))); + + CFArrayRef runArray = CTLineGetGlyphRuns(line.get()); + + CFIndex runCount = CFArrayGetCount(runArray); + + for (CFIndex r = 0; r < runCount; r++) { + CTRunRef ctRun = static_cast<CTRunRef>(CFArrayGetValueAtIndex(runArray, r)); + ASSERT(CFGetTypeID(ctRun) == CTRunGetTypeID()); + m_coreTextRuns.append(CoreTextRun(ctRun, fontData, cp, stringLocation, length)); + } +} + +void CoreTextController::adjustGlyphsAndAdvances() +{ + size_t runCount = m_coreTextRuns.size(); + for (size_t r = 0; r < runCount; ++r) { + const CoreTextRun& coreTextRun = m_coreTextRuns[r]; + unsigned glyphCount = coreTextRun.glyphCount(); + const SimpleFontData* fontData = coreTextRun.fontData(); + + Vector<CGGlyph, 256> glyphsVector; + const CGGlyph* glyphs; + + Vector<CGSize, 256> advancesVector; + const CGSize* advances; + + if (coreTextRun.ctRun()) { + glyphs = CTRunGetGlyphsPtr(coreTextRun.ctRun()); + if (!glyphs) { + glyphsVector.grow(glyphCount); + CTRunGetGlyphs(coreTextRun.ctRun(), CFRangeMake(0, 0), glyphsVector.data()); + glyphs = glyphsVector.data(); + } + + advances = CTRunGetAdvancesPtr(coreTextRun.ctRun()); + if (!advances) { + advancesVector.grow(glyphCount); + CTRunGetAdvances(coreTextRun.ctRun(), CFRangeMake(0, 0), advancesVector.data()); + advances = advancesVector.data(); + } + } else { + // Synthesize a run of missing glyphs. + glyphsVector.fill(0, glyphCount); + glyphs = glyphsVector.data(); + advancesVector.fill(CGSizeMake(fontData->widthForGlyph(0), 0), glyphCount); + advances = advancesVector.data(); + } + + bool lastRun = r + 1 == runCount; + const UChar* cp = coreTextRun.characters(); + CGFloat roundedSpaceWidth = roundCGFloat(fontData->m_spaceWidth); + bool roundsAdvances = !m_font.isPrinterFont() && fontData->platformData().roundsGlyphAdvances(); + bool hasExtraSpacing = (m_font.letterSpacing() || m_font.wordSpacing() || m_padding) && !m_run.spacingDisabled(); + + + for (unsigned i = 0; i < glyphCount; i++) { + CFIndex characterIndex = coreTextRun.indexAt(i); + UChar ch = *(cp + characterIndex); + bool lastGlyph = lastRun && i + 1 == glyphCount; + UChar nextCh; + if (lastGlyph) + nextCh = ' '; + else if (i + 1 < glyphCount) + nextCh = *(cp + coreTextRun.indexAt(i + 1)); + else + nextCh = *(m_coreTextRuns[r + 1].characters() + m_coreTextRuns[r + 1].indexAt(0)); + + bool treatAsSpace = Font::treatAsSpace(ch); + CGGlyph glyph = treatAsSpace ? fontData->m_spaceGlyph : glyphs[i]; + CGSize advance = treatAsSpace ? CGSizeMake(fontData->m_spaceWidth, advances[i].height) : advances[i]; + + if (ch == '\t' && m_run.allowTabs()) { + float tabWidth = m_font.tabWidth(); + advance.width = tabWidth - fmodf(m_run.xPos() + m_totalWidth, tabWidth); + } else if (ch == zeroWidthSpace || Font::treatAsZeroWidthSpace(ch) && !treatAsSpace) { + advance.width = 0; + glyph = fontData->m_spaceGlyph; + } + + float roundedAdvanceWidth = roundf(advance.width); + if (roundsAdvances) + advance.width = roundedAdvanceWidth; + + advance.width += fontData->m_syntheticBoldOffset; + + // We special case spaces in two ways when applying word rounding. + // First, we round spaces to an adjusted width in all fonts. + // Second, in fixed-pitch fonts we ensure that all glyphs that + // match the width of the space glyph have the same width as the space glyph. + if (roundedAdvanceWidth == roundedSpaceWidth && (fontData->m_treatAsFixedPitch || glyph == fontData->m_spaceGlyph) && m_run.applyWordRounding()) + advance.width = fontData->m_adjustedSpaceWidth; + + if (hasExtraSpacing) { + // If we're a glyph with an advance, go ahead and add in letter-spacing. + // That way we weed out zero width lurkers. This behavior matches the fast text code path. + if (advance.width && m_font.letterSpacing()) + advance.width += m_font.letterSpacing(); + + // Handle justification and word-spacing. + if (glyph == fontData->m_spaceGlyph) { + // Account for padding. WebCore uses space padding to justify text. + // We distribute the specified padding over the available spaces in the run. + if (m_padding) { + // Use leftover padding if not evenly divisible by number of spaces. + if (m_padding < m_padPerSpace) { + advance.width += m_padding; + m_padding = 0; + } else { + advance.width += m_padPerSpace; + m_padding -= m_padPerSpace; + } + } + + // Account for word-spacing. + if (treatAsSpace && characterIndex > 0 && !Font::treatAsSpace(*m_run.data(characterIndex - 1)) && m_font.wordSpacing()) + advance.width += m_font.wordSpacing(); + } + } + + // Deal with the float/integer impedance mismatch between CG and WebCore. "Words" (characters + // followed by a character defined by isRoundingHackCharacter()) are always an integer width. + // We adjust the width of the last character of a "word" to ensure an integer width. + // Force characters that are used to determine word boundaries for the rounding hack + // to be integer width, so the following words will start on an integer boundary. + if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(ch)) + advance.width = ceilCGFloat(advance.width); + + // Check to see if the next character is a "rounding hack character", if so, adjust the + // width so that the total run width will be on an integer boundary. + if (m_run.applyWordRounding() && !lastGlyph && Font::isRoundingHackCharacter(nextCh) || m_run.applyRunRounding() && lastGlyph) { + CGFloat totalWidth = m_totalWidth + advance.width; + CGFloat extraWidth = ceilCGFloat(totalWidth) - totalWidth; + if (m_run.ltr()) + advance.width += extraWidth; + else { + m_totalWidth += extraWidth; + if (m_lastRoundingGlyph) + m_adjustedAdvances[m_lastRoundingGlyph - 1].width += extraWidth; + else + m_finalRoundingWidth = extraWidth; + m_lastRoundingGlyph = m_adjustedAdvances.size() + 1; + } + } + + m_totalWidth += advance.width; + advance.height *= -1; + m_adjustedAdvances.append(advance); + m_adjustedGlyphs.append(glyph); + } + } +} + +} // namespace WebCore + +#endif // USE(CORE_TEXT) diff --git a/WebCore/platform/graphics/mac/CoreTextController.h b/WebCore/platform/graphics/mac/CoreTextController.h new file mode 100644 index 0000000..8dbb7fb --- /dev/null +++ b/WebCore/platform/graphics/mac/CoreTextController.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CoreTextController_h +#define CoreTextController_h + +#if USE(CORE_TEXT) + +#include "Font.h" +#include "GlyphBuffer.h" +#include <wtf/RetainPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CoreTextController { +public: + CoreTextController(const Font*, const TextRun&, bool mayUseNaturalWritingDirection = false); + + // Advance and emit glyphs up to the specified character. + void advance(unsigned to, GlyphBuffer* = 0); + + // Compute the character offset for a given x coordinate. + int offsetForPosition(int x, bool includePartialGlyphs); + + // Returns the width of everything we've consumed so far. + float runWidthSoFar() const { return m_runWidthSoFar; } + + float totalWidth() const { return m_totalWidth; } + + // Extra width to the left of the leftmost glyph. + float finalRoundingWidth() const { return m_finalRoundingWidth; } + +private: + class CoreTextRun { + public: + CoreTextRun(CTRunRef, const SimpleFontData*, const UChar* characters, unsigned stringLocation, size_t stringLength); + CoreTextRun(const SimpleFontData*, const UChar* characters, unsigned stringLocation, size_t stringLength, bool ltr); + + CTRunRef ctRun() const { return m_CTRun.get(); } + unsigned glyphCount() const { return m_glyphCount; } + const SimpleFontData* fontData() const { return m_fontData; } + const UChar* characters() const { return m_characters; } + unsigned stringLocation() const { return m_stringLocation; } + size_t stringLength() const { return m_stringLength; } + CFIndex indexAt(size_t i) const { return m_indices[i]; } + + private: + RetainPtr<CTRunRef> m_CTRun; + unsigned m_glyphCount; + const SimpleFontData* m_fontData; + const UChar* m_characters; + unsigned m_stringLocation; + size_t m_stringLength; + const CFIndex* m_indices; + // Used only if CTRunGet*Ptr fails or if this is a missing glyphs run. + RetainPtr<CFMutableDataRef> m_indicesData; + }; + + void collectCoreTextRuns(); + void collectCoreTextRunsForCharacters(const UChar*, unsigned length, unsigned stringLocation, const SimpleFontData*); + void adjustGlyphsAndAdvances(); + + const Font& m_font; + const TextRun& m_run; + bool m_mayUseNaturalWritingDirection; + + Vector<UChar, 256> m_smallCapsBuffer; + + Vector<CoreTextRun, 16> m_coreTextRuns; + Vector<CGSize, 256> m_adjustedAdvances; + Vector<CGGlyph, 256> m_adjustedGlyphs; + + unsigned m_currentCharacter; + int m_end; + + CGFloat m_totalWidth; + + float m_runWidthSoFar; + unsigned m_numGlyphsSoFar; + size_t m_currentRun; + unsigned m_glyphInCurrentRun; + float m_finalRoundingWidth; + float m_padding; + float m_padPerSpace; + + unsigned m_lastRoundingGlyph; +}; + +} // namespace WebCore +#endif // USE(CORE_TEXT) +#endif // CoreTextController_h diff --git a/WebCore/platform/graphics/mac/FontCacheMac.mm b/WebCore/platform/graphics/mac/FontCacheMac.mm index ffb3068..e7cda66 100644 --- a/WebCore/platform/graphics/mac/FontCacheMac.mm +++ b/WebCore/platform/graphics/mac/FontCacheMac.mm @@ -1,5 +1,6 @@ /* * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,84 +42,37 @@ typedef int NSInteger; namespace WebCore { -static bool getAppDefaultValue(CFStringRef key, int *v) +static void fontCacheATSNotificationCallback(ATSFontNotificationInfoRef, void*) { - CFPropertyListRef value; - - value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication, - kCFPreferencesAnyUser, - kCFPreferencesAnyHost); - if (value == 0) { - value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication, - kCFPreferencesCurrentUser, - kCFPreferencesAnyHost); - if (value == 0) - return false; - } - - if (CFGetTypeID(value) == CFNumberGetTypeID()) { - if (v != 0) - CFNumberGetValue((const CFNumberRef)value, kCFNumberIntType, v); - } else if (CFGetTypeID(value) == CFStringGetTypeID()) { - if (v != 0) - *v = CFStringGetIntValue((const CFStringRef)value); - } else { - CFRelease(value); - return false; - } - - CFRelease(value); - return true; + FontCache::invalidate(); } -static bool getUserDefaultValue(CFStringRef key, int *v) +void FontCache::platformInit() { - CFPropertyListRef value; - - value = CFPreferencesCopyValue(key, kCFPreferencesAnyApplication, - kCFPreferencesCurrentUser, - kCFPreferencesCurrentHost); - if (value == 0) - return false; - - if (CFGetTypeID(value) == CFNumberGetTypeID()) { - if (v != 0) - CFNumberGetValue((const CFNumberRef)value, kCFNumberIntType, v); - } else if (CFGetTypeID(value) == CFStringGetTypeID()) { - if (v != 0) - *v = CFStringGetIntValue((const CFStringRef)value); - } else { - CFRelease(value); - return false; - } - - CFRelease(value); - return true; + wkSetUpFontCache(); + // FIXME: Passing kATSFontNotifyOptionReceiveWhileSuspended may be an overkill and does not seem to work anyway. + ATSFontNotificationSubscribe(fontCacheATSNotificationCallback, kATSFontNotifyOptionReceiveWhileSuspended, 0, 0); } -static int getLCDScaleParameters(void) +static int toAppKitFontWeight(FontWeight fontWeight) { - int mode; - CFStringRef key; - - key = CFSTR("AppleFontSmoothing"); - if (!getAppDefaultValue(key, &mode)) { - if (!getUserDefaultValue(key, &mode)) - return 1; - } - - if (wkFontSmoothingModeIsLCD(mode)) - return 4; - return 1; + static int appKitFontWeights[] = { + 2, // FontWeight100 + 3, // FontWeight200 + 4, // FontWeight300 + 5, // FontWeight400 + 6, // FontWeight500 + 8, // FontWeight600 + 9, // FontWeight700 + 10, // FontWeight800 + 12, // FontWeight900 + }; + return appKitFontWeights[fontWeight]; } -#define MINIMUM_GLYPH_CACHE_SIZE 1536 * 1024 - -void FontCache::platformInit() +static inline bool isAppKitFontWeightBold(NSInteger appKitFontWeight) { - size_t s = MINIMUM_GLYPH_CACHE_SIZE*getLCDScaleParameters(); - - wkSetUpFontCache(s); + return appKitFontWeight >= 7; } const SimpleFontData* FontCache::getFontDataForCharacters(const Font& font, const UChar* characters, int length) @@ -126,8 +80,7 @@ const SimpleFontData* FontCache::getFontDataForCharacters(const Font& font, cons const FontPlatformData& platformData = font.fontDataAt(0)->fontDataForCharacter(characters[0])->platformData(); NSFont *nsFont = platformData.font(); - NSString *string = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(characters) - length:length freeWhenDone:NO]; + NSString *string = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(characters) length:length freeWhenDone:NO]; NSFont *substituteFont = wkGetFontInLanguageForRange(nsFont, string, NSMakeRange(0, length)); [string release]; @@ -135,49 +88,45 @@ const SimpleFontData* FontCache::getFontDataForCharacters(const Font& font, cons substituteFont = wkGetFontInLanguageForCharacter(nsFont, characters[0]); if (!substituteFont) return 0; - + // Use the family name from the AppKit-supplied substitute font, requesting the // traits, weight, and size we want. One way this does better than the original // AppKit request is that it takes synthetic bold and oblique into account. // But it does create the possibility that we could end up with a font that // doesn't actually cover the characters we need. - NSFontManager *manager = [NSFontManager sharedFontManager]; + NSFontManager *fontManager = [NSFontManager sharedFontManager]; NSFontTraitMask traits; NSInteger weight; CGFloat size; if (nsFont) { - traits = [manager traitsOfFont:nsFont]; + traits = [fontManager traitsOfFont:nsFont]; if (platformData.m_syntheticBold) traits |= NSBoldFontMask; if (platformData.m_syntheticOblique) - traits |= NSItalicFontMask; - weight = [manager weightOfFont:nsFont]; + traits |= NSFontItalicTrait; + weight = [fontManager weightOfFont:nsFont]; size = [nsFont pointSize]; } else { // For custom fonts nsFont is nil. - traits = (font.bold() ? NSBoldFontMask : 0) | (font.italic() ? NSItalicFontMask : 0); - weight = 5; + traits = font.italic() ? NSFontItalicTrait : 0; + weight = toAppKitFontWeight(font.weight()); size = font.pixelSize(); } - NSFont *bestVariation = [manager fontWithFamily:[substituteFont familyName] - traits:traits - weight:weight - size:size]; - if (bestVariation) + if (NSFont *bestVariation = [fontManager fontWithFamily:[substituteFont familyName] traits:traits weight:weight size:size]) substituteFont = bestVariation; - substituteFont = font.fontDescription().usePrinterFont() - ? [substituteFont printerFont] : [substituteFont screenFont]; + substituteFont = font.fontDescription().usePrinterFont() ? [substituteFont printerFont] : [substituteFont screenFont]; - NSFontTraitMask substituteFontTraits = [manager traitsOfFont:substituteFont]; + NSFontTraitMask substituteFontTraits = [fontManager traitsOfFont:substituteFont]; + NSInteger substituteFontWeight = [fontManager weightOfFont:substituteFont]; FontPlatformData alternateFont(substituteFont, - !font.isPlatformFont() && (traits & NSBoldFontMask) && !(substituteFontTraits & NSBoldFontMask), - !font.isPlatformFont() && (traits & NSItalicFontMask) && !(substituteFontTraits & NSItalicFontMask)); + !font.isPlatformFont() && isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(substituteFontWeight), + !font.isPlatformFont() && (traits & NSFontItalicTrait) && !(substituteFontTraits & NSFontItalicTrait)); return getCachedFontData(&alternateFont); } @@ -220,42 +169,33 @@ FontPlatformData* FontCache::getLastResortFallbackFont(const FontDescription& fo return platformFont; } -bool FontCache::fontExists(const FontDescription& fontDescription, const AtomicString& family) +void FontCache::getTraitsInFamily(const AtomicString& familyName, Vector<unsigned>& traitsMasks) { - NSFontTraitMask traits = 0; - if (fontDescription.italic()) - traits |= NSItalicFontMask; - if (fontDescription.bold()) - traits |= NSBoldFontMask; - float size = fontDescription.computedPixelSize(); - - NSFont* nsFont = [WebFontCache fontWithFamily:family traits:traits size:size]; - return nsFont != 0; + [WebFontCache getTraits:traitsMasks inFamily:familyName]; } FontPlatformData* FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomicString& family) { - NSFontTraitMask traits = 0; - if (fontDescription.italic()) - traits |= NSItalicFontMask; - if (fontDescription.bold()) - traits |= NSBoldFontMask; + NSFontTraitMask traits = fontDescription.italic() ? NSFontItalicTrait : 0; + NSInteger weight = toAppKitFontWeight(fontDescription.weight()); float size = fontDescription.computedPixelSize(); - - NSFont* nsFont = [WebFontCache fontWithFamily:family traits:traits size:size]; + + NSFont *nsFont = [WebFontCache fontWithFamily:family traits:traits weight:weight size:size]; if (!nsFont) return 0; + NSFontManager *fontManager = [NSFontManager sharedFontManager]; NSFontTraitMask actualTraits = 0; - if (fontDescription.bold() || fontDescription.italic()) - actualTraits = [[NSFontManager sharedFontManager] traitsOfFont:nsFont]; - + if (fontDescription.italic()) + actualTraits = [fontManager traitsOfFont:nsFont]; + NSInteger actualWeight = [fontManager weightOfFont:nsFont]; + FontPlatformData* result = new FontPlatformData; - + // Use the correct font for print vs. screen. result->setFont(fontDescription.usePrinterFont() ? [nsFont printerFont] : [nsFont screenFont]); - result->m_syntheticBold = (traits & NSBoldFontMask) && !(actualTraits & NSBoldFontMask); - result->m_syntheticOblique = (traits & NSItalicFontMask) && !(actualTraits & NSItalicFontMask); + result->m_syntheticBold = isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(actualWeight); + result->m_syntheticOblique = (traits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait); return result; } diff --git a/WebCore/platform/graphics/mac/FontCustomPlatformData.cpp b/WebCore/platform/graphics/mac/FontCustomPlatformData.cpp index f143458..1fb144c 100644 --- a/WebCore/platform/graphics/mac/FontCustomPlatformData.cpp +++ b/WebCore/platform/graphics/mac/FontCustomPlatformData.cpp @@ -33,7 +33,7 @@ FontCustomPlatformData::~FontCustomPlatformData() CGFontRelease(m_cgFont); } -FontPlatformData FontCustomPlatformData::fontPlatformData(int size, bool bold, bool italic) +FontPlatformData FontCustomPlatformData::fontPlatformData(int size, bool bold, bool italic, FontRenderingMode) { return FontPlatformData(m_cgFont, (ATSUFontID)m_atsFont, size, bold, italic); } diff --git a/WebCore/platform/graphics/mac/FontCustomPlatformData.h b/WebCore/platform/graphics/mac/FontCustomPlatformData.h index d2e83ca..1e73ae0 100644 --- a/WebCore/platform/graphics/mac/FontCustomPlatformData.h +++ b/WebCore/platform/graphics/mac/FontCustomPlatformData.h @@ -21,6 +21,7 @@ #ifndef FontCustomPlatformData_h #define FontCustomPlatformData_h +#include "FontRenderingMode.h" #include <wtf/Noncopyable.h> typedef struct CGFont* CGFontRef; @@ -38,7 +39,7 @@ struct FontCustomPlatformData : Noncopyable { {} ~FontCustomPlatformData(); - FontPlatformData fontPlatformData(int size, bool bold, bool italic); + FontPlatformData fontPlatformData(int size, bool bold, bool italic, FontRenderingMode = NormalRenderingMode); ATSFontContainerRef m_atsContainer; ATSFontRef m_atsFont; diff --git a/WebCore/platform/graphics/mac/FontMac.mm b/WebCore/platform/graphics/mac/FontMac.mm index 06d8d9e..bef18d0 100644 --- a/WebCore/platform/graphics/mac/FontMac.mm +++ b/WebCore/platform/graphics/mac/FontMac.mm @@ -1,6 +1,4 @@ -/** - * 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) @@ -20,20 +18,14 @@ * 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 "FontFallbackList.h" #import "GlyphBuffer.h" #import "GraphicsContext.h" -#import "IntRect.h" #import "Logging.h" -#import "ShapeArabic.h" #import "SimpleFontData.h" #import "WebCoreSystemInterface.h" #import "WebCoreTextRenderer.h" @@ -50,559 +42,6 @@ using namespace std; namespace WebCore { -// ================================================================= -// Font Class (Platform-Specific Portion) -// ================================================================= - -struct ATSULayoutParameters -{ - ATSULayoutParameters(const TextRun& run) - : m_run(run) - , 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 Font* m_font; - - ATSUTextLayout m_layout; - const SimpleFontData **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; - - TextRun result = run; - result.setText(charactersWithOverride, run.length() + 2); - return result; -} - -static void initializeATSUStyle(const SimpleFontData* 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 = fontData->platformData().m_atsuFontID; - 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.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); - - // 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_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 : params->m_run.characters(); - const SimpleFontData **renderers = params->m_fonts; - const SimpleFontData *renderer; - const SimpleFontData *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_run.padding(); - 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->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_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(); - } - } - - 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; - } -} - -void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* graphicsContext) -{ - m_font = font; - - const SimpleFontData* fontData = font->primaryFont(); - m_fonts = new const SimpleFontData*[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_run.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_run.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 SimpleFontData* substituteFontData = 0; - - while (substituteOffset < runLength) { - // FIXME: Using ATSUMatchFontsToText() here results in several problems: the CSS font family list is not necessarily followed for the 2nd - // and onwards unmatched characters; segmented fonts do not work correctly; behavior does not match the simple text and Uniscribe code - // paths. Change this function to use Font::glyphDataForCharacter() for each character instead. - lastOffset = substituteOffset; - status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength); - if (status == kATSUFontsMatched || status == kATSUFontsNotMatched) { - const FontData* fallbackFontData = m_font->fontDataForCharacters(m_run.characters() + substituteOffset, substituteLength); - substituteFontData = fallbackFontData ? fallbackFontData->fontDataForCharacter(m_run[0]) : 0; - 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 SimpleFontData *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 && WTF::Unicode::isArabicChar(m_run[i]) && !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_run.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 SimpleFontData* 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_run.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_run.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 IntPoint& point, int h, int from, int to) const -{ - TextRun adjustedRun = run.directionalOverride() ? addDirectionalOverride(run, run.rtl()) : run; - if (run.directionalOverride()) { - from++; - to++; - } - - ATSULayoutParameters params(adjustedRun); - 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 (run.directionalOverride()) - delete []adjustedRun.characters(); - - return rect; -} - -void Font::drawComplexText(GraphicsContext* graphicsContext, const TextRun& run, const FloatPoint& point, int from, int to) const -{ - OSStatus status; - - int drawPortionLength = to - from; - TextRun adjustedRun = run.directionalOverride() ? addDirectionalOverride(run, run.rtl()) : run; - if (run.directionalOverride()) - from++; - - ATSULayoutParameters params(adjustedRun); - 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 (run.directionalOverride()) - delete []adjustedRun.characters(); -} - -float Font::floatWidthForComplexText(const TextRun& run) const -{ - if (run.length() == 0) - return 0; - - ATSULayoutParameters params(run); - 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, int x, bool includePartialGlyphs) const -{ - TextRun adjustedRun = run.directionalOverride() ? addDirectionalOverride(run, run.rtl()) : run; - - ATSULayoutParameters params(adjustedRun); - 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 (run.directionalOverride() && offset > 0) - offset--; - } else - // Failed to find offset! Return 0 offset. - offset = 0; - - disposeATSULayoutParameters(¶ms); - - if (run.directionalOverride()) - delete []adjustedRun.characters(); - - return offset; -} - void Font::drawGlyphs(GraphicsContext* context, const SimpleFontData* font, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const { CGContextRef cgContext = context->platformContext(); @@ -628,7 +67,7 @@ void Font::drawGlyphs(GraphicsContext* context, const SimpleFontData* font, cons [[[platformData.font() fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]); } - CGContextSetFont(cgContext, platformData.m_cgFont); + CGContextSetFont(cgContext, platformData.cgFont()); CGAffineTransform matrix = CGAffineTransformIdentity; if (drawFont) @@ -644,7 +83,28 @@ void Font::drawGlyphs(GraphicsContext* context, const SimpleFontData* font, cons CGContextSetFontSize(cgContext, 1.0f); } else CGContextSetFontSize(cgContext, platformData.m_size); - + + IntSize shadowSize; + int shadowBlur; + Color shadowColor; + context->getShadow(shadowSize, shadowBlur, shadowColor); + + bool hasSimpleShadow = context->textDrawingMode() == cTextFill && shadowColor.isValid() && !shadowBlur; + if (hasSimpleShadow) { + // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing. + context->clearShadow(); + Color fillColor = context->fillColor(); + Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255); + context->setFillColor(shadowFillColor); + CGContextSetTextPosition(cgContext, point.x() + shadowSize.width(), point.y() + shadowSize.height()); + CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs); + if (font->m_syntheticBoldOffset) { + CGContextSetTextPosition(cgContext, point.x() + shadowSize.width() + font->m_syntheticBoldOffset, point.y() + shadowSize.height()); + CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs); + } + context->setFillColor(fillColor); + } + CGContextSetTextPosition(cgContext, point.x(), point.y()); CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs); if (font->m_syntheticBoldOffset) { @@ -652,6 +112,9 @@ void Font::drawGlyphs(GraphicsContext* context, const SimpleFontData* font, cons CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs); } + if (hasSimpleShadow) + context->setShadow(shadowSize, shadowBlur, shadowColor); + if (originalShouldUseFontSmoothing != newShouldUseFontSmoothing) CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing); } diff --git a/WebCore/platform/graphics/mac/FontMacATSUI.mm b/WebCore/platform/graphics/mac/FontMacATSUI.mm new file mode 100644 index 0000000..9a45c5a --- /dev/null +++ b/WebCore/platform/graphics/mac/FontMacATSUI.mm @@ -0,0 +1,623 @@ +/* + * 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 +#endif + +using 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 iCurrentOperation, ATSULineRef iLineRef, URefCon iRefCon, + void *iOperationCallbackParameterPtr, 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; + } +} + +void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* graphicsContext) +{ + m_font = font; + + const SimpleFontData* fontData = font->primaryFont(); + m_fonts.set(new const SimpleFontData*[m_run.length()]); + if (font->isSmallCaps()) + m_charBuffer.set(new UChar[m_run.length()]); + + 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.get(), m_run.characters(), runLength * sizeof(UChar)); + + status = ATSUCreateTextLayoutWithTextPtr( + (m_charBuffer ? m_charBuffer.get() : 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_run.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_run.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 SimpleFontData* substituteFontData = 0; + + while (substituteOffset < runLength) { + // FIXME: Using ATSUMatchFontsToText() here results in several problems: the CSS font family list is not necessarily followed for the 2nd + // and onwards unmatched characters; segmented fonts do not work correctly; behavior does not match the simple text and Uniscribe code + // paths. Change this function to use Font::glyphDataForCharacter() for each character instead. + lastOffset = substituteOffset; + status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength); + if (status == kATSUFontsMatched || status == kATSUFontsNotMatched) { + const FontData* fallbackFontData = m_font->fontDataForCharacters(m_run.characters() + substituteOffset, substituteLength); + substituteFontData = fallbackFontData ? fallbackFontData->fontDataForCharacter(m_run[0]) : 0; + 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 SimpleFontData *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 && WTF::Unicode::isArabicChar(m_run[i]) && !r->shapesArabic()) { + shapedArabic = true; + if (!m_charBuffer) { + m_charBuffer.set(new UChar[runLength]); + memcpy(m_charBuffer.get(), m_run.characters(), i * sizeof(UChar)); + ATSUTextMoved(layout, m_charBuffer.get()); + } + shapeArabic(m_run.characters(), m_charBuffer.get(), runLength, i); + } + if (m_run.rtl() && !r->m_ATSUMirrors) { + UChar mirroredChar = u_charMirror(m_run[i]); + if (mirroredChar != m_run[i]) { + if (!m_charBuffer) { + m_charBuffer.set(new UChar[runLength]); + memcpy(m_charBuffer.get(), m_run.characters(), runLength * sizeof(UChar)); + ATSUTextMoved(layout, m_charBuffer.get()); + } + m_charBuffer[i] = mirroredChar; + } + } + if (m_font->isSmallCaps()) { + const SimpleFontData* 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_run.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_run.padding() / numSpaces); + } else + m_padPerSpace = 0; +} + +FloatRect Font::selectionRectForComplexText(const TextRun& run, const IntPoint& point, int h, int from, int to) const +{ + OwnArrayPtr<UChar> charactersWithOverride; + TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride); + if (run.directionalOverride()) { + from++; + to++; + } + + ATSULayoutParameters params(adjustedRun); + 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; + } + + 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); + + return rect; +} + +void Font::drawComplexText(GraphicsContext* graphicsContext, const TextRun& run, const FloatPoint& point, int from, int to) const +{ + OSStatus status; + + int drawPortionLength = to - from; + OwnArrayPtr<UChar> charactersWithOverride; + TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride); + if (run.directionalOverride()) + from++; + + ATSULayoutParameters params(adjustedRun); + 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()); + + IntSize shadowSize; + int shadowBlur; + Color shadowColor; + graphicsContext->getShadow(shadowSize, shadowBlur, shadowColor); + + bool hasSimpleShadow = graphicsContext->textDrawingMode() == cTextFill && shadowColor.isValid() && !shadowBlur; + if (hasSimpleShadow) { + // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing. + graphicsContext->clearShadow(); + Color fillColor = graphicsContext->fillColor(); + Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255); + graphicsContext->setFillColor(shadowFillColor); + CGContextTranslateCTM(context, shadowSize.width(), shadowSize.height()); + 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); + // Force relayout for the next pass + ATSUClearLayoutCache(params.m_layout, 0); + params.m_syntheticBoldPass = false; + } + CGContextTranslateCTM(context, -shadowSize.width(), -shadowSize.height()); + graphicsContext->setFillColor(fillColor); + } + + 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); + + if (hasSimpleShadow) + graphicsContext->setShadow(shadowSize, shadowBlur, shadowColor); +} + +float Font::floatWidthForComplexText(const TextRun& run) const +{ + if (run.length() == 0) + return 0; + + ATSULayoutParameters params(run); + 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); + + 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, int x, bool includePartialGlyphs) const +{ + OwnArrayPtr<UChar> charactersWithOverride; + TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride); + + ATSULayoutParameters params(adjustedRun); + 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 (run.directionalOverride() && offset > 0) + offset--; + } else + // Failed to find offset! Return 0 offset. + offset = 0; + + return offset; +} + +} +#endif // USE(ATSUI) diff --git a/WebCore/platform/graphics/mac/FontMacCoreText.cpp b/WebCore/platform/graphics/mac/FontMacCoreText.cpp new file mode 100644 index 0000000..5fb9d5d --- /dev/null +++ b/WebCore/platform/graphics/mac/FontMacCoreText.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Font.h" + +#if USE(CORE_TEXT) + +#include "CoreTextController.h" +#include "FontFallbackList.h" +#include "GlyphBuffer.h" +#include "GraphicsContext.h" +#include "IntRect.h" +#include "SimpleFontData.h" +#include <wtf/MathExtras.h> + +namespace WebCore { + +FloatRect Font::selectionRectForComplexText(const TextRun& run, const IntPoint& point, int h, + int from, int to) const +{ + CoreTextController controller(this, run); + controller.advance(from); + float beforeWidth = controller.runWidthSoFar(); + controller.advance(to); + float afterWidth = controller.runWidthSoFar(); + + // Using roundf() rather than ceilf() for the right edge as a compromise to ensure correct caret positioning + if (run.rtl()) { + float totalWidth = controller.totalWidth(); + return FloatRect(point.x() + floorf(totalWidth - afterWidth), point.y(), roundf(totalWidth - beforeWidth) - floorf(totalWidth - afterWidth), h); + } + + return FloatRect(point.x() + floorf(beforeWidth), point.y(), roundf(afterWidth) - floorf(beforeWidth), h); +} + +void Font::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, + int from, int to) const +{ + // This glyph buffer holds our glyphs + advances + font data for each glyph. + GlyphBuffer glyphBuffer; + + float startX = point.x(); + CoreTextController controller(this, run); + controller.advance(from); + float beforeWidth = controller.runWidthSoFar(); + controller.advance(to, &glyphBuffer); + + // We couldn't generate any glyphs for the run. Give up. + if (glyphBuffer.isEmpty()) + return; + + float afterWidth = controller.runWidthSoFar(); + + if (run.rtl()) { + startX += controller.totalWidth() + controller.finalRoundingWidth() - afterWidth; + for (int i = 0, end = glyphBuffer.size() - 1; i < glyphBuffer.size() / 2; ++i, --end) + glyphBuffer.swap(i, end); + } else + startX += beforeWidth; + + // Draw the glyph buffer now at the starting point returned in startX. + FloatPoint startPoint(startX, point.y()); + drawGlyphBuffer(context, glyphBuffer, run, startPoint); +} + +float Font::floatWidthForComplexText(const TextRun& run) const +{ + CoreTextController controller(this, run, true); + return controller.totalWidth(); +} + +int Font::offsetForPositionForComplexText(const TextRun& run, int x, bool includePartialGlyphs) const +{ + CoreTextController controller(this, run); + return controller.offsetForPosition(x, includePartialGlyphs); +} + +} +#endif // USE(CORE_TEXT) diff --git a/WebCore/platform/graphics/mac/FontPlatformData.h b/WebCore/platform/graphics/mac/FontPlatformData.h index 8f118e0..40a2dbd 100644 --- a/WebCore/platform/graphics/mac/FontPlatformData.h +++ b/WebCore/platform/graphics/mac/FontPlatformData.h @@ -1,8 +1,8 @@ /* - * This file is part of the internal font implementation. It should not be included by anyone other than - * FontMac.cpp, FontWin.cpp and Font.cpp. + * This file is part of the internal font implementation. + * It should not be included by source files outside it. * - * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -34,46 +34,52 @@ class NSFont; typedef struct CGFont* CGFontRef; typedef UInt32 ATSUFontID; +#ifndef BUILDING_ON_TIGER +typedef const struct __CTFont* CTFontRef; +#endif #include <CoreFoundation/CFBase.h> #include <objc/objc-auto.h> +#include <wtf/RetainPtr.h> namespace WebCore { -struct FontPlatformData { - class Deleted {}; - - FontPlatformData(Deleted) - : m_syntheticBold(false), m_syntheticOblique(false), m_cgFont(0), m_atsuFontID(0), m_size(0), m_font((NSFont*)-1) - {} +#ifndef BUILDING_ON_TIGER +inline CTFontRef toCTFontRef(NSFont *nsFont) { return reinterpret_cast<CTFontRef>(nsFont); } +#endif - FontPlatformData(float s, bool b, bool o) - : m_syntheticBold(b) - , m_syntheticOblique(o) - , m_cgFont(0) +struct FontPlatformData { + FontPlatformData(float size, bool syntheticBold, bool syntheticOblique) + : m_syntheticBold(syntheticBold) + , m_syntheticOblique(syntheticOblique) , m_atsuFontID(0) - , m_size(s) + , m_size(size) , m_font(0) +#ifdef BUILDING_ON_TIGER + , m_cgFont(0) +#endif { } - FontPlatformData(NSFont* f = 0, bool b = false, bool o = false); + FontPlatformData(NSFont * = 0, bool syntheticBold = false, bool syntheticOblique = false); FontPlatformData(CGFontRef f, ATSUFontID fontID, float s, bool b , bool o) - : m_syntheticBold(b), m_syntheticOblique(o), m_cgFont(f), m_atsuFontID(fontID), m_size(s), m_font(0) + : m_syntheticBold(b), m_syntheticOblique(o), m_atsuFontID(fontID), m_size(s), m_font(0), m_cgFont(f) { } - FontPlatformData(const FontPlatformData& f); + FontPlatformData(const FontPlatformData&); ~FontPlatformData(); + FontPlatformData(WTF::HashTableDeletedValueType) : m_font(hashTableDeletedFontValue()) { } + bool isHashTableDeletedValue() const { return m_font == hashTableDeletedFontValue(); } + float size() const { return m_size; } bool m_syntheticBold; bool m_syntheticOblique; - - CGFontRef m_cgFont; // It is not necessary to refcount this, since either an NSFont owns it or some CachedFont has it referenced. + ATSUFontID m_atsuFontID; float m_size; @@ -84,6 +90,8 @@ struct FontPlatformData { return StringImpl::computeHash(reinterpret_cast<UChar*>(hashCodes), sizeof(hashCodes) / sizeof(UChar)); } + const FontPlatformData& operator=(const FontPlatformData& f); + bool operator==(const FontPlatformData& other) const { return m_font == other.m_font && m_syntheticBold == other.m_syntheticBold && m_syntheticOblique == other.m_syntheticOblique && @@ -91,10 +99,26 @@ struct FontPlatformData { } NSFont *font() const { return m_font; } - void setFont(NSFont* font); + void setFont(NSFont *font); + + bool roundsGlyphAdvances() const; + bool allowsLigatures() const; + +#ifndef BUILDING_ON_TIGER + CGFontRef cgFont() const { return m_cgFont.get(); } +#else + CGFontRef cgFont() const { return m_cgFont; } +#endif private: + static NSFont *hashTableDeletedFontValue() { return reinterpret_cast<NSFont *>(-1); } + NSFont *m_font; +#ifndef BUILDING_ON_TIGER + RetainPtr<CGFontRef> m_cgFont; +#else + CGFontRef m_cgFont; // It is not necessary to refcount this, since either an NSFont owns it or some CachedFont has it referenced. +#endif }; } diff --git a/WebCore/platform/graphics/mac/FontPlatformDataMac.mm b/WebCore/platform/graphics/mac/FontPlatformDataMac.mm index d1e00d1..15e573d 100644 --- a/WebCore/platform/graphics/mac/FontPlatformDataMac.mm +++ b/WebCore/platform/graphics/mac/FontPlatformDataMac.mm @@ -27,19 +27,24 @@ namespace WebCore { -FontPlatformData::FontPlatformData(NSFont* f, bool b , bool o) +FontPlatformData::FontPlatformData(NSFont *f, bool b , bool o) : m_syntheticBold(b), m_syntheticOblique(o), m_font(f) { if (f) CFRetain(f); m_size = f ? [f pointSize] : 0.0f; +#ifndef BUILDING_ON_TIGER + m_cgFont = CTFontCopyGraphicsFont(toCTFontRef(f), 0); + m_atsuFontID = CTFontGetPlatformFont(toCTFontRef(f), 0); +#else m_cgFont = wkGetCGFontFromNSFont(f); m_atsuFontID = wkGetNSFontATSUFontId(f); +#endif } FontPlatformData::FontPlatformData(const FontPlatformData& f) { - m_font = (f.m_font && f.m_font != (NSFont*)-1) ? (NSFont*)CFRetain(f.m_font) : f.m_font; + m_font = f.m_font && f.m_font != reinterpret_cast<NSFont *>(-1) ? static_cast<const NSFont *>(CFRetain(f.m_font)) : f.m_font; m_syntheticBold = f.m_syntheticBold; m_syntheticOblique = f.m_syntheticOblique; m_size = f.m_size; @@ -49,22 +54,54 @@ FontPlatformData::FontPlatformData(const FontPlatformData& f) FontPlatformData:: ~FontPlatformData() { - if (m_font && m_font != (NSFont*)-1) + if (m_font && m_font != reinterpret_cast<NSFont *>(-1)) CFRelease(m_font); } -void FontPlatformData::setFont(NSFont* font) { +const FontPlatformData& FontPlatformData::operator=(const FontPlatformData& f) +{ + m_syntheticBold = f.m_syntheticBold; + m_syntheticOblique = f.m_syntheticOblique; + m_size = f.m_size; + m_cgFont = f.m_cgFont; + m_atsuFontID = f.m_atsuFontID; + if (m_font == f.m_font) + return *this; + if (f.m_font && f.m_font != reinterpret_cast<NSFont *>(-1)) + CFRetain(f.m_font); + if (m_font && m_font != reinterpret_cast<NSFont *>(-1)) + CFRelease(m_font); + m_font = f.m_font; + return *this; +} + +void FontPlatformData::setFont(NSFont *font) +{ if (m_font == font) return; if (font) CFRetain(font); - if (m_font && m_font != (NSFont*)-1) + if (m_font && m_font != reinterpret_cast<NSFont *>(-1)) CFRelease(m_font); m_font = font; m_size = font ? [font pointSize] : 0.0f; +#ifndef BUILDING_ON_TIGER + m_cgFont = CTFontCopyGraphicsFont(toCTFontRef(font), 0); + m_atsuFontID = CTFontGetPlatformFont(toCTFontRef(font), 0); +#else m_cgFont = wkGetCGFontFromNSFont(font); m_atsuFontID = wkGetNSFontATSUFontId(font); +#endif } +bool FontPlatformData::roundsGlyphAdvances() const +{ + return [m_font renderingMode] == NSFontAntialiasedIntegerAdvancementsRenderingMode; +} + +bool FontPlatformData::allowsLigatures() const +{ + return ![[m_font coveredCharacterSet] characterIsMember:'a']; } +} // namespace WebCore diff --git a/WebCore/platform/graphics/mac/GlyphPageTreeNodeMac.cpp b/WebCore/platform/graphics/mac/GlyphPageTreeNodeMac.cpp index b9f2da3..143e665 100644 --- a/WebCore/platform/graphics/mac/GlyphPageTreeNodeMac.cpp +++ b/WebCore/platform/graphics/mac/GlyphPageTreeNodeMac.cpp @@ -37,6 +37,21 @@ namespace WebCore { bool GlyphPage::fill(unsigned offset, unsigned length, UChar* buffer, unsigned bufferLength, const SimpleFontData* fontData) { + bool haveGlyphs = false; + +#ifndef BUILDING_ON_TIGER + Vector<CGGlyph, 512> glyphs(bufferLength); + wkGetGlyphsForCharacters(fontData->platformData().cgFont(), buffer, glyphs.data(), bufferLength); + + for (unsigned i = 0; i < length; ++i) { + if (!glyphs[i]) + setGlyphDataForIndex(offset + i, 0, 0); + else { + setGlyphDataForIndex(offset + i, glyphs[i], fontData); + haveGlyphs = true; + } + } +#else // Use an array of long so we get good enough alignment. long glyphVector[(GLYPH_VECTOR_SIZE + sizeof(long) - 1) / sizeof(long)]; @@ -56,7 +71,6 @@ bool GlyphPage::fill(unsigned offset, unsigned length, UChar* buffer, unsigned b return false; } - bool haveGlyphs = false; ATSLayoutRecord* glyphRecord = (ATSLayoutRecord*)wkGetGlyphVectorFirstRecord(glyphVector); for (unsigned i = 0; i < length; i++) { Glyph glyph = glyphRecord->glyphID; @@ -69,6 +83,7 @@ bool GlyphPage::fill(unsigned offset, unsigned length, UChar* buffer, unsigned b glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + wkGetGlyphVectorRecordSize(glyphVector)); } wkClearGlyphVector(&glyphVector); +#endif return haveGlyphs; } diff --git a/WebCore/platform/graphics/mac/IconMac.mm b/WebCore/platform/graphics/mac/IconMac.mm index cda73a0..63abe59 100644 --- a/WebCore/platform/graphics/mac/IconMac.mm +++ b/WebCore/platform/graphics/mac/IconMac.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -28,10 +28,6 @@ namespace WebCore { -Icon::Icon() -{ -} - Icon::Icon(NSImage *image) : m_nsImage(image) { @@ -43,7 +39,7 @@ Icon::~Icon() { } -PassRefPtr<Icon> Icon::newIconForFile(const String& filename) +PassRefPtr<Icon> Icon::createIconForFile(const String& filename) { // Don't pass relative filenames -- we don't want a result that depends on the current directory. // Need 0U here to disambiguate String::operator[] from operator(NSString*, int)[] @@ -54,7 +50,23 @@ PassRefPtr<Icon> Icon::newIconForFile(const String& filename) if (!image) return 0; - return new Icon(image); + return adoptRef(new Icon(image)); +} + +PassRefPtr<Icon> Icon::createIconForFiles(const Vector<String>& filenames) +{ + if (filenames.isEmpty()) + return 0; +#ifdef BUILDING_ON_TIGER + // FIXME: find a better image to use on Tiger. + return createIconForFile(filenames[0]); +#else + NSImage* image = [NSImage imageNamed:NSImageNameMultipleDocuments]; + if (!image) + return 0; + + return adoptRef(new Icon(image)); +#endif } void Icon::paint(GraphicsContext* context, const IntRect& rect) diff --git a/WebCore/platform/graphics/mac/ImageMac.mm b/WebCore/platform/graphics/mac/ImageMac.mm index 0b14d71..a0d257b 100644 --- a/WebCore/platform/graphics/mac/ImageMac.mm +++ b/WebCore/platform/graphics/mac/ImageMac.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -30,8 +30,12 @@ #import "FoundationExtras.h" #import "GraphicsContext.h" #import "PlatformString.h" -#import "WebCoreFrameBridge.h" -#import "WebCoreSystemInterface.h" + +@interface WebCoreBundleFinder : NSObject +@end + +@implementation WebCoreBundleFinder +@end namespace WebCore { @@ -48,24 +52,22 @@ void BitmapImage::invalidatePlatformData() m_tiffRep = 0; } -Image* Image::loadPlatformResource(const char *name) +PassRefPtr<Image> Image::loadPlatformResource(const char *name) { - static BitmapImage nullImage; - - NSBundle *bundle = [NSBundle bundleForClass:[WebCoreFrameBridge class]]; + NSBundle *bundle = [NSBundle bundleForClass:[WebCoreBundleFinder class]]; NSString *imagePath = [bundle pathForResource:[NSString stringWithUTF8String:name] ofType:@"tiff"]; NSData *namedImageData = [NSData dataWithContentsOfFile:imagePath]; if (namedImageData) { - Image* image = new BitmapImage; + RefPtr<Image> image = BitmapImage::create(); image->setData(SharedBuffer::wrapNSData(namedImageData), true); - return image; + return image.release(); } - + // We have reports indicating resource loads are failing, but we don't yet know the root cause(s). // Two theories are bad installs (image files are missing), and too-many-open-files. // See rdar://5607381 ASSERT_NOT_REACHED(); - return &nullImage; + return Image::nullImage(); } CFDataRef BitmapImage::getTIFFRepresentation() diff --git a/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.h b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.h index 8975d9b..3f18ab4 100644 --- a/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.h +++ b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.h @@ -36,14 +36,20 @@ #import <QTKit/QTTime.h> @class QTMovie; @class QTMovieView; +@class QTVideoRendererWebKitOnly; @class WebCoreMovieObserver; #else class QTMovie; class QTMovieView; class QTTime; +class QTVideoRendererWebKitOnly; class WebCoreMovieObserver; #endif +#ifndef DRAW_FRAME_RATE +#define DRAW_FRAME_RATE 0 +#endif + namespace WebCore { class MediaPlayerPrivate : Noncopyable { @@ -99,8 +105,12 @@ public: private: void createQTMovie(const String& url); + void setUpVideoRendering(); + void tearDownVideoRendering(); void createQTMovieView(); void detachQTMovieView(); + void createQTVideoRenderer(); + void destroyQTVideoRenderer(); QTTime createQTTime(float time) const; void updateStates(); @@ -115,6 +125,7 @@ private: MediaPlayer* m_player; RetainPtr<QTMovie> m_qtMovie; RetainPtr<QTMovieView> m_qtMovieView; + RetainPtr<QTVideoRendererWebKitOnly> m_qtVideoRenderer; RetainPtr<WebCoreMovieObserver> m_objcObserver; float m_seekTo; float m_endTime; @@ -124,6 +135,12 @@ private: MediaPlayer::ReadyState m_readyState; bool m_startedPlaying; bool m_isStreaming; + bool m_visible; +#if DRAW_FRAME_RATE + int m_frameCountWhilePlaying; + double m_timeStartedPlaying; + double m_timeStoppedPlaying; +#endif }; } diff --git a/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm index 85c7a9e..0ec56d6 100644 --- a/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm +++ b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 Apple Inc. All rights reserved. + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -32,12 +32,20 @@ #import "BlockExceptions.h" #import "GraphicsContext.h" #import "KURL.h" -#import "ScrollView.h" +#import "FrameView.h" #import "SoftLinking.h" #import "WebCoreSystemInterface.h" #import <QTKit/QTKit.h> #import <objc/objc-runtime.h> +#if DRAW_FRAME_RATE +#import "Font.h" +#import "Frame.h" +#import "Document.h" +#import "RenderObject.h" +#import "RenderStyle.h" +#endif + #ifdef BUILDING_ON_TIGER static IMP method_setImplementation(Method m, IMP imp) { @@ -59,6 +67,7 @@ SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *) +SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *) @@ -74,6 +83,7 @@ SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *) +SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *) #define QTMovie getQTMovieClass() #define QTMovieView getQTMovieViewClass() @@ -83,6 +93,7 @@ SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *) #define QTMediaTypeSound getQTMediaTypeSound() #define QTMediaTypeText getQTMediaTypeText() #define QTMediaTypeVideo getQTMediaTypeVideo() +#define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute() #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute() #define QTMovieDidEndNotification getQTMovieDidEndNotification() #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute() @@ -98,6 +109,7 @@ SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *) #define QTMovieURLAttribute getQTMovieURLAttribute() #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification() #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute() +#define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification() // Older versions of the QTKit header don't have these constants. #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0 @@ -116,10 +128,12 @@ using namespace std; @interface WebCoreMovieObserver : NSObject { MediaPlayerPrivate* m_callback; + NSView* m_view; BOOL m_delayCallbacks; } -(id)initWithCallback:(MediaPlayerPrivate*)callback; -(void)disconnect; +-(void)setView:(NSView*)view; -(void)repaint; -(void)setDelayCallbacks:(BOOL)shouldDelay; -(void)loadStateChanged:(NSNotification *)notification; @@ -129,11 +143,19 @@ using namespace std; -(void)didEnd:(NSNotification *)notification; @end +@protocol WebKitVideoRenderingDetails +-(void)setMovie:(id)movie; +-(void)drawInRect:(NSRect)rect; +@end + namespace WebCore { static const float endPointTimerInterval = 0.020f; + +#ifdef BUILDING_ON_TIGER static const long minimumQuickTimeVersion = 0x07300000; // 7.3 - +#endif + MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) : m_player(player) , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this]) @@ -145,12 +167,18 @@ MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) , m_readyState(MediaPlayer::DataUnavailable) , m_startedPlaying(false) , m_isStreaming(false) + , m_visible(false) +#if DRAW_FRAME_RATE + , m_frameCountWhilePlaying(0) + , m_timeStartedPlaying(0) + , m_timeStoppedPlaying(0) +#endif { } MediaPlayerPrivate::~MediaPlayerPrivate() { - detachQTMovieView(); + tearDownVideoRendering(); [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; [m_objcObserver.get() disconnect]; @@ -160,23 +188,29 @@ void MediaPlayerPrivate::createQTMovie(const String& url) { [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; - m_qtMovie = 0; + if (m_qtMovie) { + destroyQTVideoRenderer(); + m_qtMovie = 0; + } // Disable streaming support for now, <rdar://problem/5693967> - if (url.startsWith("rtsp:")) + if (protocolIs(url, "rtsp")) return; - - NSDictionary* movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys: - KURL(url.deprecatedString()).getNSURL(), QTMovieURLAttribute, + + NSURL *cocoaURL = KURL(url); + NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys: + cocoaURL, QTMovieURLAttribute, [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute, [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute, + [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute, + [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute", // FIXME: Use defined attribute when required version of QT supports this attribute nil]; NSError* error = nil; m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]); // FIXME: Find a proper way to detect streaming content. - m_isStreaming = url.startsWith("rtsp:"); + m_isStreaming = protocolIs(url, "rtsp"); if (!m_qtMovie) return; @@ -220,13 +254,16 @@ static void mainThreadSetNeedsDisplay(id self, SEL _cmd) [delegate repaint]; } +static Class QTVideoRendererClass() +{ + static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly"); + return QTVideoRendererWebKitOnlyClass; +} + void MediaPlayerPrivate::createQTMovieView() { detachQTMovieView(); - if (!m_player->m_parentWidget || !m_qtMovie) - return; - static bool addedCustomMethods = false; if (!addedCustomMethods) { Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView"); @@ -241,7 +278,7 @@ void MediaPlayerPrivate::createQTMovieView() m_qtMovieView.adoptNS([[QTMovieView alloc] init]); setRect(m_player->rect()); - NSView* parentView = static_cast<ScrollView*>(m_player->m_parentWidget)->getDocumentView(); + NSView* parentView = m_player->m_frameView->documentView(); [parentView addSubview:m_qtMovieView.get()]; #ifdef BUILDING_ON_TIGER // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy @@ -249,17 +286,24 @@ void MediaPlayerPrivate::createQTMovieView() #else [m_qtMovieView.get() setDelegate:m_objcObserver.get()]; #endif + [m_objcObserver.get() setView:m_qtMovieView.get()]; [m_qtMovieView.get() setMovie:m_qtMovie.get()]; [m_qtMovieView.get() setControllerVisible:NO]; [m_qtMovieView.get() setPreservesAspectRatio:NO]; // the area not covered by video should be transparent [m_qtMovieView.get() setFillColor:[NSColor clearColor]]; - wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES); + + // If we're in a media document, allow QTMovieView to render in its default mode; + // otherwise tell it to draw synchronously. + // Note that we expect mainThreadSetNeedsDisplay to be invoked only when synchronous drawing is requested. + if (!m_player->inMediaDocument()) + wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES); } void MediaPlayerPrivate::detachQTMovieView() { if (m_qtMovieView) { + [m_objcObserver.get() setView:nil]; #ifdef BUILDING_ON_TIGER // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil]; @@ -271,6 +315,59 @@ void MediaPlayerPrivate::detachQTMovieView() } } +void MediaPlayerPrivate::createQTVideoRenderer() +{ + destroyQTVideoRenderer(); + + m_qtVideoRenderer.adoptNS([[QTVideoRendererClass() alloc] init]); + if (!m_qtVideoRenderer) + return; + + // associate our movie with our instance of QTVideoRendererWebKitOnly + [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()]; + + // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification + [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() + selector:@selector(newImageAvailable:) + name:QTVideoRendererWebKitOnlyNewImageAvailableNotification + object:m_qtVideoRenderer.get()]; +} + +void MediaPlayerPrivate::destroyQTVideoRenderer() +{ + if (!m_qtVideoRenderer) + return; + + // stop observing the renderer's notifications before we toss it + [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get() + name:QTVideoRendererWebKitOnlyNewImageAvailableNotification + object:m_qtVideoRenderer.get()]; + + // disassociate our movie from our instance of QTVideoRendererWebKitOnly + [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil]; + + m_qtVideoRenderer = nil; +} + +void MediaPlayerPrivate::setUpVideoRendering() +{ + if (!m_player->m_frameView || !m_qtMovie) + return; + + if (m_player->inMediaDocument() || !QTVideoRendererClass() ) + createQTMovieView(); + else + createQTVideoRenderer(); +} + +void MediaPlayerPrivate::tearDownVideoRendering() +{ + if (m_qtMovieView) + detachQTMovieView(); + else + destroyQTVideoRenderer(); +} + QTTime MediaPlayerPrivate::createQTTime(float time) const { if (!m_qtMovie) @@ -295,8 +392,6 @@ void MediaPlayerPrivate::load(const String& url) [m_objcObserver.get() setDelayCallbacks:YES]; createQTMovie(url); - if (m_player->visible()) - createQTMovieView(); [m_objcObserver.get() loadStateChanged:nil]; [m_objcObserver.get() setDelayCallbacks:NO]; @@ -307,6 +402,9 @@ void MediaPlayerPrivate::play() if (!m_qtMovie) return; m_startedPlaying = true; +#if DRAW_FRAME_RATE + m_frameCountWhilePlaying = 0; +#endif [m_objcObserver.get() setDelayCallbacks:YES]; [m_qtMovie.get() setRate:m_player->rate()]; [m_objcObserver.get() setDelayCallbacks:NO]; @@ -318,6 +416,9 @@ void MediaPlayerPrivate::pause() if (!m_qtMovie) return; m_startedPlaying = false; +#if DRAW_FRAME_RATE + m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate]; +#endif [m_objcObserver.get() setDelayCallbacks:YES]; [m_qtMovie.get() stop]; [m_objcObserver.get() setDelayCallbacks:NO]; @@ -521,7 +622,7 @@ void MediaPlayerPrivate::cancelLoad() if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) return; - detachQTMovieView(); + tearDownVideoRendering(); m_qtMovie = nil; updateStates(); @@ -534,14 +635,14 @@ void MediaPlayerPrivate::updateStates() long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError); - if (loadState >= QTMovieLoadStateLoaded && m_networkState < MediaPlayer::LoadedMetaData) { + if (loadState >= QTMovieLoadStateLoaded && m_networkState < MediaPlayer::LoadedMetaData && !m_player->inMediaDocument()) { unsigned enabledTrackCount; disableUnsupportedTracks(enabledTrackCount); // FIXME: We should differentiate between load errors and decode errors <rdar://problem/5605692> if (!enabledTrackCount) loadState = QTMovieLoadStateError; } - + // "Loaded" is reserved for fully buffered movies, never the case when streaming if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) { if (m_networkState < MediaPlayer::Loaded) @@ -576,6 +677,9 @@ void MediaPlayerPrivate::updateStates() m_player->networkStateChanged(); if (m_readyState != oldReadyState) m_player->readyStateChanged(); + + if (loadState >= QTMovieLoadStateLoaded && oldNetworkState < MediaPlayer::LoadedMetaData && m_player->visible()) + setUpVideoRendering(); } void MediaPlayerPrivate::loadStateChanged() @@ -602,6 +706,9 @@ void MediaPlayerPrivate::didEnd() { m_endPointTimer.stop(); m_startedPlaying = false; +#if DRAW_FRAME_RATE + m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate]; +#endif updateStates(); m_player->timeChanged(); } @@ -610,23 +717,42 @@ void MediaPlayerPrivate::setRect(const IntRect& r) { if (!m_qtMovieView) return; - // We don't really need the QTMovieView in any specific location so let's just get it out of the way - // where it won't intercept events or try to bring up the context menu. - IntRect farAwayButCorrectSize(r); - farAwayButCorrectSize.move(-1000000, -1000000); - [m_qtMovieView.get() setFrame:farAwayButCorrectSize]; + + if (m_player->inMediaDocument()) + // We need the QTMovieView to be placed in the proper location for document mode. + [m_qtMovieView.get() setFrame:r]; + else { + // We don't really need the QTMovieView in any specific location so let's just get it out of the way + // where it won't intercept events or try to bring up the context menu. + IntRect farAwayButCorrectSize(r); + farAwayButCorrectSize.move(-1000000, -1000000); + [m_qtMovieView.get() setFrame:farAwayButCorrectSize]; + } } void MediaPlayerPrivate::setVisible(bool b) { - if (b) - createQTMovieView(); - else - detachQTMovieView(); + if (m_visible != b) { + m_visible = b; + if (b) { + if (m_networkState >= MediaPlayer::LoadedMetaData) + setUpVideoRendering(); + } else + tearDownVideoRendering(); + } } void MediaPlayerPrivate::repaint() { +#if DRAW_FRAME_RATE + if (m_startedPlaying) { + m_frameCountWhilePlaying++; + // to eliminate preroll costs from our calculation, + // our frame rate calculation excludes the first frame drawn after playback starts + if (1==m_frameCountWhilePlaying) + m_timeStartedPlaying = [NSDate timeIntervalSinceReferenceDate]; + } +#endif m_player->repaint(); } @@ -635,16 +761,52 @@ void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r) if (context->paintingDisabled()) return; NSView *view = m_qtMovieView.get(); - if (view == nil) + id qtVideoRenderer = m_qtVideoRenderer.get(); + if (!view && !qtVideoRenderer) return; + [m_objcObserver.get() setDelayCallbacks:YES]; BEGIN_BLOCK_OBJC_EXCEPTIONS; context->save(); context->translate(r.x(), r.y() + r.height()); context->scale(FloatSize(1.0f, -1.0f)); + context->setImageInterpolationQuality(InterpolationLow); IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height())); NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO]; - [view displayRectIgnoringOpacity:paintRect inContext:newContext]; + + // draw the current video frame + if (qtVideoRenderer) { + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:newContext]; + [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect]; + [NSGraphicsContext restoreGraphicsState]; + } else + [view displayRectIgnoringOpacity:paintRect inContext:newContext]; + +#if DRAW_FRAME_RATE + // Draw the frame rate only after having played more than 10 frames. + if (m_frameCountWhilePlaying > 10) { + Frame* frame = m_player->m_frameView ? m_player->m_frameView->frame() : NULL; + Document* document = frame ? frame->document() : NULL; + RenderObject* renderer = document ? document->renderer() : NULL; + RenderStyle* styleToUse = renderer ? renderer->style() : NULL; + if (styleToUse) { + double frameRate = (m_frameCountWhilePlaying - 1) / ( m_startedPlaying ? ([NSDate timeIntervalSinceReferenceDate] - m_timeStartedPlaying) : + (m_timeStoppedPlaying - m_timeStartedPlaying) ); + String text = String::format("%1.2f", frameRate); + TextRun textRun(text.characters(), text.length()); + const Color color(255, 0, 0); + context->scale(FloatSize(1.0f, -1.0f)); + context->setFont(styleToUse->font()); + context->setStrokeColor(color); + context->setStrokeStyle(SolidStroke); + context->setStrokeThickness(1.0f); + context->setFillColor(color); + context->drawText(textRun, IntPoint(2, -3)); + } + } +#endif + context->restore(); END_BLOCK_OBJC_EXCEPTIONS; [m_objcObserver.get() setDelayCallbacks:NO]; @@ -668,9 +830,9 @@ void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types) bool MediaPlayerPrivate::isAvailable() { +#ifdef BUILDING_ON_TIGER SInt32 version; OSErr result; - // This Carbon API is available in 64 bit too result = Gestalt(gestaltQuickTime, &version); if (result != noErr) { LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support."); @@ -681,6 +843,10 @@ bool MediaPlayerPrivate::isAvailable() return false; } return true; +#else + // On 10.5 and higher, QuickTime will always be new enough for <video> and <audio> support, so we just check that the framework can be loaded. + return QTKitLibrary(); +#endif } void MediaPlayerPrivate::disableUnsupportedTracks(unsigned& enabledTrackCount) @@ -784,6 +950,17 @@ void MediaPlayerPrivate::disableUnsupportedTracks(unsigned& enabledTrackCount) m_callback = 0; } +-(NSMenu*)menuForEventDelegate:(NSEvent*)theEvent +{ + // Get the contextual menu from the QTMovieView's superview, the frame view + return [[m_view superview] menuForEvent:theEvent]; +} + +-(void)setView:(NSView*)view +{ + m_view = view; +} + -(void)repaint { if (m_delayCallbacks) @@ -832,6 +1009,11 @@ void MediaPlayerPrivate::disableUnsupportedTracks(unsigned& enabledTrackCount) m_callback->didEnd(); } +- (void)newImageAvailable:(NSNotification *)notification +{ + [self repaint]; +} + - (void)setDelayCallbacks:(BOOL)shouldDelay { m_delayCallbacks = shouldDelay; diff --git a/WebCore/platform/graphics/mac/SimpleFontDataMac.mm b/WebCore/platform/graphics/mac/SimpleFontDataMac.mm index 1f45c94..4ee5933 100644 --- a/WebCore/platform/graphics/mac/SimpleFontDataMac.mm +++ b/WebCore/platform/graphics/mac/SimpleFontDataMac.mm @@ -56,13 +56,14 @@ static inline float scaleEmToUnits(float x, unsigned unitsPerEm) { return x * (c bool initFontData(SimpleFontData* fontData) { - if (!fontData->m_font.m_cgFont) + if (!fontData->m_font.cgFont()) return false; +#ifdef BUILDING_ON_TIGER ATSUStyle fontStyle; if (ATSUCreateStyle(&fontStyle) != noErr) return false; - + ATSUFontID fontId = fontData->m_font.m_atsuFontID; if (!fontId) { ATSUDisposeStyle(fontStyle); @@ -84,6 +85,7 @@ bool initFontData(SimpleFontData* fontData) } ATSUDisposeStyle(fontStyle); +#endif return true; } @@ -96,16 +98,63 @@ static NSString *webFallbackFontFamily(void) return webFallbackFontFamily.get(); } +#if !ERROR_DISABLED +#ifdef __LP64__ +static NSString* pathFromFont(NSFont*) +{ + // FMGetATSFontRefFromFont is not available in 64-bit. As pathFromFont is only used for debugging + // purposes, returning nil is acceptable. + return nil; +} +#else +static NSString* pathFromFont(NSFont *font) +{ +#ifndef BUILDING_ON_TIGER + ATSFontRef atsFont = FMGetATSFontRefFromFont(CTFontGetPlatformFont(toCTFontRef(font), 0)); +#else + ATSFontRef atsFont = FMGetATSFontRefFromFont(wkGetNSFontATSUFontId(font)); +#endif + FSRef fileRef; + +#ifndef BUILDING_ON_TIGER + OSStatus status = ATSFontGetFileReference(atsFont, &fileRef); + if (status != noErr) + return nil; +#else + FSSpec oFile; + OSStatus status = ATSFontGetFileSpecification(atsFont, &oFile); + if (status != noErr) + return nil; + + status = FSpMakeFSRef(&oFile, &fileRef); + if (status != noErr) + return nil; +#endif + + UInt8 filePathBuffer[PATH_MAX]; + status = FSRefMakePath(&fileRef, filePathBuffer, PATH_MAX); + if (status == noErr) + return [NSString stringWithUTF8String:(const char*)filePathBuffer]; + + return nil; +} +#endif // __LP64__ +#endif // !ERROR_DISABLED + void SimpleFontData::platformInit() { +#ifdef BUILDING_ON_TIGER m_styleGroup = 0; +#endif +#if USE(ATSUI) m_ATSUStyleInitialized = false; m_ATSUMirrors = false; m_checkedShapesArabic = false; m_shapesArabic = false; +#endif m_syntheticBoldOffset = m_font.m_syntheticBold ? 1.0f : 0.f; - + bool failedSetup = false; if (!initFontData(this)) { // Ack! Something very bad happened, like a corrupt font. @@ -126,9 +175,12 @@ void SimpleFontData::platformInit() #if !ERROR_DISABLED RetainPtr<NSFont> initialFont = m_font.font(); #endif - m_font.setFont([[NSFontManager sharedFontManager] convertFont:m_font.font() toFamily:fallbackFontFamily]); + if (m_font.font()) + m_font.setFont([[NSFontManager sharedFontManager] convertFont:m_font.font() toFamily:fallbackFontFamily]); + else + m_font.setFont([NSFont fontWithName:fallbackFontFamily size:m_font.size()]); #if !ERROR_DISABLED - NSString *filePath = wkPathFromFont(initialFont.get()); + NSString *filePath = pathFromFont(initialFont.get()); if (!filePath) filePath = @"not known"; #endif @@ -165,7 +217,15 @@ void SimpleFontData::platformInit() int iAscent; int iDescent; int iLineGap; - wkGetFontMetrics(m_font.m_cgFont, &iAscent, &iDescent, &iLineGap, &m_unitsPerEm); +#ifdef BUILDING_ON_TIGER + wkGetFontMetrics(m_font.cgFont(), &iAscent, &iDescent, &iLineGap, &m_unitsPerEm); +#else + iAscent = CGFontGetAscent(m_font.cgFont()); + iDescent = CGFontGetDescent(m_font.cgFont()); + iLineGap = CGFontGetLeading(m_font.cgFont()); + m_unitsPerEm = CGFontGetUnitsPerEm(m_font.cgFont()); +#endif + float pointSize = m_font.m_size; float fAscent = scaleEmToUnits(iAscent, m_unitsPerEm) * pointSize; float fDescent = -scaleEmToUnits(iDescent, m_unitsPerEm) * pointSize; @@ -179,6 +239,13 @@ void SimpleFontData::platformInit() NSString *familyName = [m_font.font() familyName]; if ([familyName isEqualToString:@"Times"] || [familyName isEqualToString:@"Helvetica"] || [familyName isEqualToString:@"Courier"]) fAscent += floorf(((fAscent + fDescent) * 0.15f) + 0.5f); + else if ([familyName isEqualToString:@"Geeza Pro"]) { + // Geeza Pro has glyphs that draw slightly above the ascent or far below the descent. Adjust + // those vertical metrics to better match reality, so that diacritics at the bottom of one line + // do not overlap diacritics at the top of the next line. + fAscent *= 1.08f; + fDescent *= 2.f; + } m_ascent = lroundf(fAscent); m_descent = lroundf(fDescent); @@ -209,11 +276,14 @@ void SimpleFontData::platformInit() void SimpleFontData::platformDestroy() { +#ifdef BUILDING_ON_TIGER if (m_styleGroup) wkReleaseStyleGroup(m_styleGroup); - +#endif +#if USE(ATSUI) if (m_ATSUStyleInitialized) ATSUDisposeStyle(m_ATSUStyle); +#endif } SimpleFontData* SimpleFontData::smallCapsFontData(const FontDescription& fontDescription) const @@ -290,13 +360,14 @@ float SimpleFontData::platformWidthForGlyph(Glyph glyph) const float pointSize = m_font.m_size; CGAffineTransform m = CGAffineTransformMakeScale(pointSize, pointSize); CGSize advance; - if (!wkGetGlyphTransformedAdvances(m_font.m_cgFont, font, &m, &glyph, &advance)) { + if (!wkGetGlyphTransformedAdvances(m_font.cgFont(), font, &m, &glyph, &advance)) { LOG_ERROR("Unable to cache glyph widths for %@ %f", [font displayName], pointSize); advance.width = 0; } return advance.width + m_syntheticBoldOffset; } +#if USE(ATSUI) void SimpleFontData::checkShapesArabic() const { ASSERT(!m_checkedShapesArabic); @@ -325,5 +396,40 @@ void SimpleFontData::checkShapesArabic() const LOG_ERROR("ATSFontGetTable failed (%d)", status); } } +#endif +#if USE(CORE_TEXT) +CTFontRef SimpleFontData::getCTFont() const +{ + if (getNSFont()) + return toCTFontRef(getNSFont()); + if (!m_CTFont) + m_CTFont.adoptCF(CTFontCreateWithGraphicsFont(m_font.cgFont(), m_font.size(), NULL, NULL)); + return m_CTFont.get(); } + +CFDictionaryRef SimpleFontData::getCFStringAttributes() const +{ + if (m_CFStringAttributes) + return m_CFStringAttributes.get(); + + static const float kerningAdjustmentValue = 0; + static CFNumberRef kerningAdjustment = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &kerningAdjustmentValue); + + static const int ligaturesNotAllowedValue = 0; + static CFNumberRef ligaturesNotAllowed = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &ligaturesNotAllowedValue); + + static const int ligaturesAllowedValue = 1; + static CFNumberRef ligaturesAllowed = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &ligaturesAllowedValue); + + static const void* attributeKeys[] = { kCTFontAttributeName, kCTKernAttributeName, kCTLigatureAttributeName }; + const void* attributeValues[] = { getCTFont(), kerningAdjustment, platformData().allowsLigatures() ? ligaturesAllowed : ligaturesNotAllowed }; + + m_CFStringAttributes.adoptCF(CFDictionaryCreate(NULL, attributeKeys, attributeValues, sizeof(attributeKeys) / sizeof(*attributeKeys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + + return m_CFStringAttributes.get(); +} + +#endif + +} // namespace WebCore |