diff options
Diffstat (limited to 'WebCore/platform/graphics/mac')
25 files changed, 4630 insertions, 0 deletions
diff --git a/WebCore/platform/graphics/mac/ColorMac.h b/WebCore/platform/graphics/mac/ColorMac.h new file mode 100644 index 0000000..3be9094 --- /dev/null +++ b/WebCore/platform/graphics/mac/ColorMac.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 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. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + +#ifndef ColorMac_h +#define ColorMac_h + +#include "Color.h" + +#ifdef __OBJC__ +@class NSColor; +#else +class NSColor; +#endif + +namespace WebCore { + + Color colorFromNSColor(NSColor *); + NSColor* nsColor(const Color&); + + bool usesTestModeFocusRingColor(); + void setUsesTestModeFocusRingColor(bool); + +} + +#endif diff --git a/WebCore/platform/graphics/mac/ColorMac.mm b/WebCore/platform/graphics/mac/ColorMac.mm new file mode 100644 index 0000000..96fdc39 --- /dev/null +++ b/WebCore/platform/graphics/mac/ColorMac.mm @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, 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. + */ + +#import "config.h" +#import "Color.h" +#import "ColorMac.h" + +#import <wtf/Assertions.h> +#import <wtf/RetainPtr.h> + +@interface WebCoreControlTintObserver : NSObject ++ (void)controlTintDidChange; +@end + +namespace WebCore { + +// NSColor calls don't throw, so no need to block Cocoa exceptions in this file + +static RGBA32 oldAquaFocusRingColor = 0xFF7DADD9; +static RGBA32 systemFocusRingColor; +static bool useOldAquaFocusRingColor; + + +static RGBA32 makeRGBAFromNSColor(NSColor *c) +{ + return makeRGBA((int)(255 * [c redComponent]), (int)(255 * [c greenComponent]), (int)(255 * [c blueComponent]), (int)(255 * [c alphaComponent])); +} + +Color colorFromNSColor(NSColor *c) +{ + return Color(makeRGBAFromNSColor(c)); +} + +NSColor* nsColor(const Color& color) +{ + unsigned c = color.rgb(); + switch (c) { + case 0: { + // Need this to avoid returning nil because cachedRGBAValues will default to 0. + static RetainPtr<NSColor> clearColor = [NSColor clearColor]; + return clearColor.get(); + } + case Color::black: { + static RetainPtr<NSColor> blackColor = [NSColor blackColor]; + return blackColor.get(); + } + case Color::white: { + static RetainPtr<NSColor> whiteColor = [NSColor whiteColor]; + return whiteColor.get(); + } + default: { + const int cacheSize = 32; + static unsigned cachedRGBAValues[cacheSize]; + static RetainPtr<NSColor> cachedColors[cacheSize]; + + for (int i = 0; i != cacheSize; ++i) + if (cachedRGBAValues[i] == c) + return cachedColors[i].get(); + +#ifdef COLORMATCH_EVERYTHING + NSColor* result = [NSColor colorWithCalibratedRed:color.red() / 255.0f + green:color.green() / 255.0f + blue:color.blue() / 255.0f + alpha:color.alpha() /255.0f]; +#else + NSColor* result = [NSColor colorWithDeviceRed:color.red() / 255.0f + green:color.green() / 255.0f + blue:color.blue() / 255.0f + alpha:color.alpha() /255.0f]; +#endif + + static int cursor; + cachedRGBAValues[cursor] = c; + cachedColors[cursor] = result; + if (++cursor == cacheSize) + cursor = 0; + return result; + } + } +} + +static CGColorRef CGColorFromNSColor(NSColor* color) +{ + // This needs to always use device colorspace so it can de-calibrate the color for + // CGColor to possibly recalibrate it. + NSColor* deviceColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + CGFloat red = [deviceColor redComponent]; + CGFloat green = [deviceColor greenComponent]; + CGFloat blue = [deviceColor blueComponent]; + CGFloat alpha = [deviceColor alphaComponent]; + const CGFloat components[4] = { red, green, blue, alpha }; + static CGColorSpaceRef deviceRGBColorSpace = CGColorSpaceCreateDeviceRGB(); + CGColorRef cgColor = CGColorCreate(deviceRGBColorSpace, components); + return cgColor; +} + +CGColorRef cgColor(const Color& c) +{ + // We could directly create a CGColor here, but that would + // skip any RGB caching the nsColor method does. A direct + // creation could be investigated for a possible performance win. + return CGColorFromNSColor(nsColor(c)); +} + +Color focusRingColor() +{ + 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; + + return systemFocusRingColor; +} + +bool usesTestModeFocusRingColor() +{ + return useOldAquaFocusRingColor; +} + +void setUsesTestModeFocusRingColor(bool newValue) +{ + useOldAquaFocusRingColor = newValue; +} + +} + +@implementation WebCoreControlTintObserver + ++ (void)controlTintDidChange +{ +#ifdef COLORMATCH_EVERYTHING +#error Not yet implemented. +#else + NSColor* color = [[NSColor keyboardFocusIndicatorColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + WebCore::systemFocusRingColor = WebCore::makeRGBAFromNSColor(color); +#endif +} + +@end 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/FloatPointMac.mm b/WebCore/platform/graphics/mac/FloatPointMac.mm new file mode 100644 index 0000000..2f73314 --- /dev/null +++ b/WebCore/platform/graphics/mac/FloatPointMac.mm @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005 Nokia. 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 "FloatPoint.h" + +namespace WebCore { + +#ifndef NSGEOMETRY_TYPES_SAME_AS_CGGEOMETRY_TYPES + +FloatPoint::FloatPoint(const NSPoint& p) : m_x(p.x), m_y(p.y) +{ +} + +FloatPoint::operator NSPoint() const +{ + return NSMakePoint(m_x, m_y); +} + +#endif + +} diff --git a/WebCore/platform/graphics/mac/FloatRectMac.mm b/WebCore/platform/graphics/mac/FloatRectMac.mm new file mode 100644 index 0000000..1d6b045 --- /dev/null +++ b/WebCore/platform/graphics/mac/FloatRectMac.mm @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005 Nokia. 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 "FloatRect.h" + +namespace WebCore { + +#ifndef NSGEOMETRY_TYPES_SAME_AS_CGGEOMETRY_TYPES + +FloatRect::FloatRect(const NSRect& r) : m_location(r.origin), m_size(r.size) +{ +} + +FloatRect::operator NSRect() const +{ + return NSMakeRect(x(), y(), width(), height()); +} + +#endif + +} diff --git a/WebCore/platform/graphics/mac/FloatSizeMac.mm b/WebCore/platform/graphics/mac/FloatSizeMac.mm new file mode 100644 index 0000000..01efbe9 --- /dev/null +++ b/WebCore/platform/graphics/mac/FloatSizeMac.mm @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005 Nokia. 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 "FloatSize.h" + +namespace WebCore { + +#ifndef NSGEOMETRY_TYPES_SAME_AS_CGGEOMETRY_TYPES + +FloatSize::FloatSize(const NSSize& s) : m_width(s.width), m_height(s.height) +{ +} + +FloatSize::operator NSSize() const +{ + return NSMakeSize(m_width, m_height); +} + +#endif + +} diff --git a/WebCore/platform/graphics/mac/FontCacheMac.mm b/WebCore/platform/graphics/mac/FontCacheMac.mm new file mode 100644 index 0000000..e7cda66 --- /dev/null +++ b/WebCore/platform/graphics/mac/FontCacheMac.mm @@ -0,0 +1,202 @@ +/* + * 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 + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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. + */ + +#import "config.h" +#import "FontCache.h" + +#import "Font.h" +#import "SimpleFontData.h" +#import "FontPlatformData.h" +#import "WebCoreSystemInterface.h" +#import "WebFontCache.h" + +#ifdef BUILDING_ON_TIGER +typedef int NSInteger; +#endif + +namespace WebCore { + +static void fontCacheATSNotificationCallback(ATSFontNotificationInfoRef, void*) +{ + FontCache::invalidate(); +} + +void FontCache::platformInit() +{ + wkSetUpFontCache(); + // FIXME: Passing kATSFontNotifyOptionReceiveWhileSuspended may be an overkill and does not seem to work anyway. + ATSFontNotificationSubscribe(fontCacheATSNotificationCallback, kATSFontNotifyOptionReceiveWhileSuspended, 0, 0); +} + +static int toAppKitFontWeight(FontWeight fontWeight) +{ + static int appKitFontWeights[] = { + 2, // FontWeight100 + 3, // FontWeight200 + 4, // FontWeight300 + 5, // FontWeight400 + 6, // FontWeight500 + 8, // FontWeight600 + 9, // FontWeight700 + 10, // FontWeight800 + 12, // FontWeight900 + }; + return appKitFontWeights[fontWeight]; +} + +static inline bool isAppKitFontWeightBold(NSInteger appKitFontWeight) +{ + return appKitFontWeight >= 7; +} + +const SimpleFontData* FontCache::getFontDataForCharacters(const Font& font, const UChar* characters, int length) +{ + 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]; + NSFont *substituteFont = wkGetFontInLanguageForRange(nsFont, string, NSMakeRange(0, length)); + [string release]; + + if (!substituteFont && length == 1) + 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 *fontManager = [NSFontManager sharedFontManager]; + + NSFontTraitMask traits; + NSInteger weight; + CGFloat size; + + if (nsFont) { + traits = [fontManager traitsOfFont:nsFont]; + if (platformData.m_syntheticBold) + traits |= NSBoldFontMask; + if (platformData.m_syntheticOblique) + traits |= NSFontItalicTrait; + weight = [fontManager weightOfFont:nsFont]; + size = [nsFont pointSize]; + } else { + // For custom fonts nsFont is nil. + traits = font.italic() ? NSFontItalicTrait : 0; + weight = toAppKitFontWeight(font.weight()); + size = font.pixelSize(); + } + + if (NSFont *bestVariation = [fontManager fontWithFamily:[substituteFont familyName] traits:traits weight:weight size:size]) + substituteFont = bestVariation; + + substituteFont = font.fontDescription().usePrinterFont() ? [substituteFont printerFont] : [substituteFont screenFont]; + + NSFontTraitMask substituteFontTraits = [fontManager traitsOfFont:substituteFont]; + NSInteger substituteFontWeight = [fontManager weightOfFont:substituteFont]; + + FontPlatformData alternateFont(substituteFont, + !font.isPlatformFont() && isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(substituteFontWeight), + !font.isPlatformFont() && (traits & NSFontItalicTrait) && !(substituteFontTraits & NSFontItalicTrait)); + return getCachedFontData(&alternateFont); +} + +FontPlatformData* FontCache::getSimilarFontPlatformData(const Font& font) +{ + // Attempt to find an appropriate font using a match based on + // the presence of keywords in the the requested names. For example, we'll + // match any name that contains "Arabic" to Geeza Pro. + FontPlatformData* platformData = 0; + const FontFamily* currFamily = &font.fontDescription().family(); + while (currFamily && !platformData) { + if (currFamily->family().length()) { + static String matchWords[3] = { String("Arabic"), String("Pashto"), String("Urdu") }; + static AtomicString geezaStr("Geeza Pro"); + for (int j = 0; j < 3 && !platformData; ++j) + if (currFamily->family().contains(matchWords[j], false)) + platformData = getCachedFontPlatformData(font.fontDescription(), geezaStr); + } + currFamily = currFamily->next(); + } + + return platformData; +} + +FontPlatformData* FontCache::getLastResortFallbackFont(const FontDescription& fontDescription) +{ + static AtomicString timesStr("Times"); + static AtomicString lucidaGrandeStr("Lucida Grande"); + + // FIXME: Would be even better to somehow get the user's default font here. For now we'll pick + // the default that the user would get without changing any prefs. + FontPlatformData* platformFont = getCachedFontPlatformData(fontDescription, timesStr); + if (!platformFont) + // The Times fallback will almost always work, but in the highly unusual case where + // the user doesn't have it, we fall back on Lucida Grande because that's + // guaranteed to be there, according to Nathan Taylor. This is good enough + // to avoid a crash at least. + platformFont = getCachedFontPlatformData(fontDescription, lucidaGrandeStr); + + return platformFont; +} + +void FontCache::getTraitsInFamily(const AtomicString& familyName, Vector<unsigned>& traitsMasks) +{ + [WebFontCache getTraits:traitsMasks inFamily:familyName]; +} + +FontPlatformData* FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomicString& family) +{ + NSFontTraitMask traits = fontDescription.italic() ? NSFontItalicTrait : 0; + NSInteger weight = toAppKitFontWeight(fontDescription.weight()); + float size = fontDescription.computedPixelSize(); + + NSFont *nsFont = [WebFontCache fontWithFamily:family traits:traits weight:weight size:size]; + if (!nsFont) + return 0; + + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + NSFontTraitMask actualTraits = 0; + 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 = isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(actualWeight); + result->m_syntheticOblique = (traits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait); + return result; +} + +} // namespace WebCore diff --git a/WebCore/platform/graphics/mac/FontCustomPlatformData.cpp b/WebCore/platform/graphics/mac/FontCustomPlatformData.cpp new file mode 100644 index 0000000..1fb144c --- /dev/null +++ b/WebCore/platform/graphics/mac/FontCustomPlatformData.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2007 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. + * + */ + +#include "config.h" +#include "FontCustomPlatformData.h" + +#include <ApplicationServices/ApplicationServices.h> +#include "SharedBuffer.h" +#include "FontPlatformData.h" + +namespace WebCore { + +FontCustomPlatformData::~FontCustomPlatformData() +{ + ATSFontDeactivate(m_atsContainer, NULL, kATSOptionFlagsDefault); + CGFontRelease(m_cgFont); +} + +FontPlatformData FontCustomPlatformData::fontPlatformData(int size, bool bold, bool italic, FontRenderingMode) +{ + return FontPlatformData(m_cgFont, (ATSUFontID)m_atsFont, size, bold, italic); +} + +FontCustomPlatformData* createFontCustomPlatformData(SharedBuffer* buffer) +{ + ASSERT_ARG(buffer, buffer); + + // Use ATS to activate the font. + ATSFontContainerRef containerRef = 0; + + // The value "3" means that the font is private and can't be seen by anyone else. + ATSFontActivateFromMemory((void*)buffer->data(), buffer->size(), 3, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &containerRef); + if (!containerRef) + return 0; + ItemCount fontCount; + ATSFontFindFromContainer(containerRef, kATSOptionFlagsDefault, 0, NULL, &fontCount); + + // We just support the first font in the list. + if (fontCount == 0) { + ATSFontDeactivate(containerRef, NULL, kATSOptionFlagsDefault); + return 0; + } + + ATSFontRef fontRef = 0; + ATSFontFindFromContainer(containerRef, kATSOptionFlagsDefault, 1, &fontRef, NULL); + if (!fontRef) { + ATSFontDeactivate(containerRef, NULL, kATSOptionFlagsDefault); + return 0; + } + + CGFontRef cgFontRef = CGFontCreateWithPlatformFont(&fontRef); +#ifndef BUILDING_ON_TIGER + // Workaround for <rdar://problem/5675504>. + if (!CGFontGetNumberOfGlyphs(cgFontRef)) { + CFRelease(cgFontRef); + cgFontRef = 0; + } +#endif + if (!cgFontRef) { + ATSFontDeactivate(containerRef, NULL, kATSOptionFlagsDefault); + return 0; + } + + return new FontCustomPlatformData(containerRef, fontRef, cgFontRef); +} + +} diff --git a/WebCore/platform/graphics/mac/FontCustomPlatformData.h b/WebCore/platform/graphics/mac/FontCustomPlatformData.h new file mode 100644 index 0000000..1e73ae0 --- /dev/null +++ b/WebCore/platform/graphics/mac/FontCustomPlatformData.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007 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. + * + */ + +#ifndef FontCustomPlatformData_h +#define FontCustomPlatformData_h + +#include "FontRenderingMode.h" +#include <wtf/Noncopyable.h> + +typedef struct CGFont* CGFontRef; +typedef UInt32 ATSFontContainerRef; +typedef UInt32 ATSFontRef; + +namespace WebCore { + +class FontPlatformData; +class SharedBuffer; + +struct FontCustomPlatformData : Noncopyable { + FontCustomPlatformData(ATSFontContainerRef container, ATSFontRef atsFont, CGFontRef cgFont) + : m_atsContainer(container), m_atsFont(atsFont), m_cgFont(cgFont) + {} + ~FontCustomPlatformData(); + + FontPlatformData fontPlatformData(int size, bool bold, bool italic, FontRenderingMode = NormalRenderingMode); + + ATSFontContainerRef m_atsContainer; + ATSFontRef m_atsFont; + CGFontRef m_cgFont; +}; + +FontCustomPlatformData* createFontCustomPlatformData(SharedBuffer* buffer); + +} + +#endif diff --git a/WebCore/platform/graphics/mac/FontMac.mm b/WebCore/platform/graphics/mac/FontMac.mm new file mode 100644 index 0000000..bef18d0 --- /dev/null +++ b/WebCore/platform/graphics/mac/FontMac.mm @@ -0,0 +1,122 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003, 2006 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#import "config.h" +#import "Font.h" + +#import "GlyphBuffer.h" +#import "GraphicsContext.h" +#import "Logging.h" +#import "SimpleFontData.h" +#import "WebCoreSystemInterface.h" +#import "WebCoreTextRenderer.h" + +#define SYNTHETIC_OBLIQUE_ANGLE 14 + +#ifdef __LP64__ +#define URefCon void* +#else +#define URefCon UInt32 +#endif + +using namespace std; + +namespace WebCore { + +void Font::drawGlyphs(GraphicsContext* context, const SimpleFontData* font, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const +{ + CGContextRef cgContext = context->platformContext(); + + bool originalShouldUseFontSmoothing = wkCGContextGetShouldSmoothFonts(cgContext); + bool newShouldUseFontSmoothing = WebCoreShouldUseFontSmoothing(); + + if (originalShouldUseFontSmoothing != newShouldUseFontSmoothing) + CGContextSetShouldSmoothFonts(cgContext, newShouldUseFontSmoothing); + + const FontPlatformData& platformData = font->platformData(); + NSFont* drawFont; + if (!isPrinterFont()) { + drawFont = [platformData.font() screenFont]; + if (drawFont != platformData.font()) + // We are getting this in too many places (3406411); use ERROR so it only prints on debug versions for now. (We should debug this also, eventually). + LOG_ERROR("Attempting to set non-screen font (%@) when drawing to screen. Using screen font anyway, may result in incorrect metrics.", + [[[platformData.font() fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]); + } else { + drawFont = [platformData.font() printerFont]; + if (drawFont != platformData.font()) + NSLog(@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.", + [[[platformData.font() fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]); + } + + CGContextSetFont(cgContext, platformData.cgFont()); + + CGAffineTransform matrix = CGAffineTransformIdentity; + if (drawFont) + memcpy(&matrix, [drawFont matrix], sizeof(matrix)); + matrix.b = -matrix.b; + matrix.d = -matrix.d; + if (platformData.m_syntheticOblique) + matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0)); + CGContextSetTextMatrix(cgContext, matrix); + + if (drawFont) { + wkSetCGFontRenderingMode(cgContext, drawFont); + 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) { + CGContextSetTextPosition(cgContext, point.x() + font->m_syntheticBoldOffset, point.y()); + 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 new file mode 100644 index 0000000..40a2dbd --- /dev/null +++ b/WebCore/platform/graphics/mac/FontPlatformData.h @@ -0,0 +1,126 @@ +/* + * This file is part of the internal font implementation. + * It should not be included by source files outside it. + * + * 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 + * 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. + * + */ + +#ifndef FontPlatformData_h +#define FontPlatformData_h + +#include "StringImpl.h" + +#ifdef __OBJC__ +@class NSFont; +#else +class NSFont; +#endif + +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 { + +#ifndef BUILDING_ON_TIGER +inline CTFontRef toCTFontRef(NSFont *nsFont) { return reinterpret_cast<CTFontRef>(nsFont); } +#endif + +struct FontPlatformData { + FontPlatformData(float size, bool syntheticBold, bool syntheticOblique) + : m_syntheticBold(syntheticBold) + , m_syntheticOblique(syntheticOblique) + , m_atsuFontID(0) + , m_size(size) + , m_font(0) +#ifdef BUILDING_ON_TIGER + , m_cgFont(0) +#endif + { + } + + 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_atsuFontID(fontID), m_size(s), m_font(0), m_cgFont(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; + + ATSUFontID m_atsuFontID; + float m_size; + + unsigned hash() const + { + ASSERT(m_font != 0 || m_cgFont == 0); + uintptr_t hashCodes[2] = { (uintptr_t)m_font, m_syntheticBold << 1 | m_syntheticOblique }; + 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 && + m_cgFont == other.m_cgFont && m_size == other.m_size && m_atsuFontID == other.m_atsuFontID; + } + + NSFont *font() const { return m_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 +}; + +} + +#endif diff --git a/WebCore/platform/graphics/mac/FontPlatformDataMac.mm b/WebCore/platform/graphics/mac/FontPlatformDataMac.mm new file mode 100644 index 0000000..15e573d --- /dev/null +++ b/WebCore/platform/graphics/mac/FontPlatformDataMac.mm @@ -0,0 +1,107 @@ +/* + * This file is part of the internal font implementation. + * + * Copyright (C) 2006-7 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 "FontPlatformData.h" + +#import "WebCoreSystemInterface.h" + +namespace WebCore { + +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 != 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; + m_cgFont = f.m_cgFont; + m_atsuFontID = f.m_atsuFontID; +} + +FontPlatformData:: ~FontPlatformData() +{ + if (m_font && m_font != reinterpret_cast<NSFont *>(-1)) + CFRelease(m_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 != 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 new file mode 100644 index 0000000..143e665 --- /dev/null +++ b/WebCore/platform/graphics/mac/GlyphPageTreeNodeMac.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2006, 2007 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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 "GlyphPageTreeNode.h" + +#include "SimpleFontData.h" +#include "WebCoreSystemInterface.h" +#include <ApplicationServices/ApplicationServices.h> + +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)]; + + OSStatus status = wkInitializeGlyphVector(GlyphPage::size, &glyphVector); + if (status != noErr) + // This should never happen, perhaps indicates a bad font! If it does the + // font substitution code will find an alternate font. + return false; + + wkConvertCharToGlyphs(fontData->m_styleGroup, buffer, bufferLength, &glyphVector); + + unsigned numGlyphs = wkGetGlyphVectorNumGlyphs(&glyphVector); + if (numGlyphs != length) { + // This should never happen, perhaps indicates a bad font? + // If it does happen, the font substitution code will find an alternate font. + wkClearGlyphVector(&glyphVector); + return false; + } + + ATSLayoutRecord* glyphRecord = (ATSLayoutRecord*)wkGetGlyphVectorFirstRecord(glyphVector); + for (unsigned i = 0; i < length; i++) { + Glyph glyph = glyphRecord->glyphID; + if (!glyph) + setGlyphDataForIndex(offset + i, 0, 0); + else { + setGlyphDataForIndex(offset + i, glyph, fontData); + haveGlyphs = true; + } + glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + wkGetGlyphVectorRecordSize(glyphVector)); + } + wkClearGlyphVector(&glyphVector); +#endif + + return haveGlyphs; +} + +} // namespace WebCore diff --git a/WebCore/platform/graphics/mac/GraphicsContextMac.mm b/WebCore/platform/graphics/mac/GraphicsContextMac.mm new file mode 100644 index 0000000..3f9176c --- /dev/null +++ b/WebCore/platform/graphics/mac/GraphicsContextMac.mm @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, 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. + */ + +#import "config.h" +#import "GraphicsContext.h" + +#import "../cg/GraphicsContextPlatformPrivateCG.h" + +#import "WebCoreSystemInterface.h" + +// FIXME: More of this should use CoreGraphics instead of AppKit. +// FIXME: More of this should move into GraphicsContextCG.cpp. + +namespace WebCore { + +// NSColor, NSBezierPath, and NSGraphicsContext +// calls in this file are all exception-safe, so we don't block +// exceptions for those. + +void GraphicsContext::drawFocusRing(const Color& color) +{ + if (paintingDisabled()) + return; + + int radius = (focusRingWidth() - 1) / 2; + int offset = radius + focusRingOffset(); + CGColorRef colorRef = color.isValid() ? cgColor(color) : 0; + + CGMutablePathRef focusRingPath = CGPathCreateMutable(); + const Vector<IntRect>& rects = focusRingRects(); + unsigned rectCount = rects.size(); + for (unsigned i = 0; i < rectCount; i++) + CGPathAddRect(focusRingPath, 0, CGRectInset(rects[i], -offset, -offset)); + + CGContextRef context = platformContext(); +#ifdef BUILDING_ON_TIGER + CGContextBeginTransparencyLayer(context, NULL); +#endif + CGContextBeginPath(context); + CGContextAddPath(context, focusRingPath); + wkDrawFocusRing(context, colorRef, radius); +#ifdef BUILDING_ON_TIGER + CGContextEndTransparencyLayer(context); +#endif + CGColorRelease(colorRef); + + CGPathRelease(focusRingPath); +} + +#ifdef BUILDING_ON_TIGER // Post-Tiger's setCompositeOperation() is defined in GraphicsContextCG.cpp. +void GraphicsContext::setCompositeOperation(CompositeOperator op) +{ + if (paintingDisabled()) + return; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [[NSGraphicsContext graphicsContextWithGraphicsPort:platformContext() flipped:YES] + setCompositingOperation:(NSCompositingOperation)op]; + [pool drain]; +} +#endif + +void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& point, int width, bool grammar) +{ + if (paintingDisabled()) + return; + + // Constants for spelling pattern color + static RetainPtr<NSColor> spellingPatternColor = nil; + static bool usingDotForSpelling = false; + + // Constants for grammar pattern color + static RetainPtr<NSColor> grammarPatternColor = nil; + static bool usingDotForGrammar = false; + + // These are the same for misspelling or bad grammar + int patternHeight = cMisspellingLineThickness; + int patternWidth = cMisspellingLinePatternWidth; + + // Initialize pattern color if needed + if (!grammar && !spellingPatternColor) { + NSImage *image = [NSImage imageNamed:@"SpellingDot"]; + ASSERT(image); // if image is not available, we want to know + NSColor *color = (image ? [NSColor colorWithPatternImage:image] : nil); + if (color) + usingDotForSpelling = true; + else + color = [NSColor redColor]; + spellingPatternColor = color; + } + + if (grammar && !grammarPatternColor) { + NSImage *image = [NSImage imageNamed:@"GrammarDot"]; + ASSERT(image); // if image is not available, we want to know + NSColor *color = (image ? [NSColor colorWithPatternImage:image] : nil); + if (color) + usingDotForGrammar = true; + else + color = [NSColor greenColor]; + grammarPatternColor = color; + } + + bool usingDot; + NSColor *patternColor; + if (grammar) { + usingDot = usingDotForGrammar; + patternColor = grammarPatternColor.get(); + } else { + usingDot = usingDotForSpelling; + patternColor = spellingPatternColor.get(); + } + + // Make sure to draw only complete dots. + // NOTE: Code here used to shift the underline to the left and increase the width + // to make sure everything gets underlined, but that results in drawing out of + // bounds (e.g. when at the edge of a view) and could make it appear that the + // space between adjacent misspelled words was underlined. + if (usingDot) { + // allow slightly more considering that the pattern ends with a transparent pixel + int widthMod = width % patternWidth; + if (patternWidth - widthMod > cMisspellingLinePatternGapWidth) + width -= widthMod; + } + + // FIXME: This code should not use NSGraphicsContext currentContext + // In order to remove this requirement we will need to use CGPattern instead of NSColor + // FIXME: This code should not be using wkSetPatternPhaseInUserSpace, as this approach is wrong + // for transforms. + + // Draw underline + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + CGContextRef context = (CGContextRef)[currentContext graphicsPort]; + CGContextSaveGState(context); + + [patternColor set]; + + wkSetPatternPhaseInUserSpace(context, point); + + NSRectFillUsingOperation(NSMakeRect(point.x(), point.y(), width, patternHeight), NSCompositeSourceOver); + + CGContextRestoreGState(context); +} + +} diff --git a/WebCore/platform/graphics/mac/IconMac.mm b/WebCore/platform/graphics/mac/IconMac.mm new file mode 100644 index 0000000..63abe59 --- /dev/null +++ b/WebCore/platform/graphics/mac/IconMac.mm @@ -0,0 +1,84 @@ +/* + * 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 + * 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 "Icon.h" + +#import "GraphicsContext.h" +#import "LocalCurrentGraphicsContext.h" +#import "PlatformString.h" +#import <wtf/PassRefPtr.h> + +namespace WebCore { + +Icon::Icon(NSImage *image) + : m_nsImage(image) +{ + // Need this because WebCore uses AppKit's flipped coordinate system exclusively. + [image setFlipped:YES]; +} + +Icon::~Icon() +{ +} + +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)[] + if (filename.isEmpty() || filename[0U] != '/') + return 0; + + NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile:filename]; + if (!image) + return 0; + + 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) +{ + if (context->paintingDisabled()) + return; + + LocalCurrentGraphicsContext localCurrentGC(context); + + [m_nsImage.get() drawInRect:rect + fromRect:NSMakeRect(0, 0, [m_nsImage.get() size].width, [m_nsImage.get() size].height) + operation:NSCompositeSourceOver fraction:1.0f]; +} + +} diff --git a/WebCore/platform/graphics/mac/ImageMac.mm b/WebCore/platform/graphics/mac/ImageMac.mm new file mode 100644 index 0000000..a0d257b --- /dev/null +++ b/WebCore/platform/graphics/mac/ImageMac.mm @@ -0,0 +1,125 @@ +/* + * 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 + * 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. + */ + +#import "config.h" +#import "BitmapImage.h" + +#import "FloatRect.h" +#import "FoundationExtras.h" +#import "GraphicsContext.h" +#import "PlatformString.h" + +@interface WebCoreBundleFinder : NSObject +@end + +@implementation WebCoreBundleFinder +@end + +namespace WebCore { + +void BitmapImage::initPlatformData() +{ +} + +void BitmapImage::invalidatePlatformData() +{ + if (m_frames.size() != 1) + return; + + m_nsImage = 0; + m_tiffRep = 0; +} + +PassRefPtr<Image> Image::loadPlatformResource(const char *name) +{ + NSBundle *bundle = [NSBundle bundleForClass:[WebCoreBundleFinder class]]; + NSString *imagePath = [bundle pathForResource:[NSString stringWithUTF8String:name] ofType:@"tiff"]; + NSData *namedImageData = [NSData dataWithContentsOfFile:imagePath]; + if (namedImageData) { + RefPtr<Image> image = BitmapImage::create(); + image->setData(SharedBuffer::wrapNSData(namedImageData), true); + 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 Image::nullImage(); +} + +CFDataRef BitmapImage::getTIFFRepresentation() +{ + if (m_tiffRep) + return m_tiffRep.get(); + + unsigned numFrames = frameCount(); + + // If numFrames is zero, we know for certain this image doesn't have valid data + // Even though the call to CGImageDestinationCreateWithData will fail and we'll handle it gracefully, + // in certain circumstances that call will spam the console with an error message + if (!numFrames) + return 0; + + Vector<CGImageRef> images; + for (unsigned i = 0; i < numFrames; ++i ) { + CGImageRef cgImage = frameAtIndex(i); + if (cgImage) + images.append(cgImage); + } + + unsigned numValidFrames = images.size(); + + RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(0, 0)); + // FIXME: Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API + CGImageDestinationRef destination = CGImageDestinationCreateWithData(data.get(), CFSTR("public.tiff"), numValidFrames, 0); + + if (!destination) + return 0; + + for (unsigned i = 0; i < numValidFrames; ++i) + CGImageDestinationAddImage(destination, images[i], 0); + + CGImageDestinationFinalize(destination); + CFRelease(destination); + + m_tiffRep = data; + return m_tiffRep.get(); +} + +NSImage* BitmapImage::getNSImage() +{ + if (m_nsImage) + return m_nsImage.get(); + + CFDataRef data = getTIFFRepresentation(); + if (!data) + return 0; + + m_nsImage.adoptNS([[NSImage alloc] initWithData:(NSData*)data]); + return m_nsImage.get(); +} + +} diff --git a/WebCore/platform/graphics/mac/IntPointMac.mm b/WebCore/platform/graphics/mac/IntPointMac.mm new file mode 100644 index 0000000..7a2e730 --- /dev/null +++ b/WebCore/platform/graphics/mac/IntPointMac.mm @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2004, 2005, 2006 Apple Computer, 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 "IntPoint.h" + +namespace WebCore { + +#ifndef NSGEOMETRY_TYPES_SAME_AS_CGGEOMETRY_TYPES + +IntPoint::IntPoint(const NSPoint& p) : m_x(static_cast<int>(p.x)), m_y(static_cast<int>(p.y)) +{ +} + +IntPoint::operator NSPoint() const +{ + return NSMakePoint(m_x, m_y); +} + +#endif + +} diff --git a/WebCore/platform/graphics/mac/IntRectMac.mm b/WebCore/platform/graphics/mac/IntRectMac.mm new file mode 100644 index 0000000..738618a --- /dev/null +++ b/WebCore/platform/graphics/mac/IntRectMac.mm @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, 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 "IntRect.h" + +namespace WebCore { + +#ifndef NSGEOMETRY_TYPES_SAME_AS_CGGEOMETRY_TYPES + +IntRect::operator NSRect() const +{ + return NSMakeRect(x(), y(), width(), height()); +} + +IntRect enclosingIntRect(const NSRect& rect) +{ + int l = static_cast<int>(floorf(rect.origin.x)); + int t = static_cast<int>(floorf(rect.origin.y)); + int r = static_cast<int>(ceilf(NSMaxX(rect))); + int b = static_cast<int>(ceilf(NSMaxY(rect))); + return IntRect(l, t, r - l, b - t); +} + +#endif + +} diff --git a/WebCore/platform/graphics/mac/IntSizeMac.mm b/WebCore/platform/graphics/mac/IntSizeMac.mm new file mode 100644 index 0000000..c7dcd88 --- /dev/null +++ b/WebCore/platform/graphics/mac/IntSizeMac.mm @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, 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 "IntSize.h" + +namespace WebCore { + +#ifndef NSGEOMETRY_TYPES_SAME_AS_CGGEOMETRY_TYPES + +IntSize::IntSize(const NSSize& s) : m_width(static_cast<int>(s.width)), m_height(static_cast<int>(s.height)) +{ +} + +IntSize::operator NSSize() const +{ + return NSMakeSize(m_width, m_height); +} + +#endif + +} diff --git a/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.h b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.h new file mode 100644 index 0000000..3f18ab4 --- /dev/null +++ b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef MediaPlayerPrivateQTKit_h +#define MediaPlayerPrivateQTKit_h + +#if ENABLE(VIDEO) + +#include "MediaPlayer.h" +#include "Timer.h" +#include <wtf/RetainPtr.h> + +#ifdef __OBJC__ +#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 { +public: + MediaPlayerPrivate(MediaPlayer*); + ~MediaPlayerPrivate(); + + IntSize naturalSize() const; + bool hasVideo() const; + + void load(const String& url); + void cancelLoad(); + + void play(); + void pause(); + + bool paused() const; + bool seeking() const; + + float duration() const; + float currentTime() const; + void seek(float time); + void setEndTime(float time); + + void setRate(float); + void setVolume(float); + + int dataRate() const; + + MediaPlayer::NetworkState networkState() const { return m_networkState; } + MediaPlayer::ReadyState readyState() const { return m_readyState; } + + float maxTimeBuffered() const; + float maxTimeSeekable() const; + unsigned bytesLoaded() const; + bool totalBytesKnown() const; + unsigned totalBytes() const; + + void setVisible(bool); + void setRect(const IntRect& r); + + void loadStateChanged(); + void rateChanged(); + void sizeChanged(); + void timeChanged(); + void didEnd(); + + void repaint(); + void paint(GraphicsContext*, const IntRect&); + + static void getSupportedTypes(HashSet<String>& types); + static bool isAvailable(); + +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(); + void doSeek(); + void cancelSeek(); + void seekTimerFired(Timer<MediaPlayerPrivate>*); + void endPointTimerFired(Timer<MediaPlayerPrivate>*); + float maxTimeLoaded() const; + void startEndPointTimerIfNeeded(); + void disableUnsupportedTracks(unsigned& enabledTrackCount); + + 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; + Timer<MediaPlayerPrivate> m_seekTimer; + Timer<MediaPlayerPrivate> m_endPointTimer; + MediaPlayer::NetworkState m_networkState; + 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 +}; + +} + +#endif +#endif diff --git a/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm new file mode 100644 index 0000000..0ec56d6 --- /dev/null +++ b/WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm @@ -0,0 +1,1024 @@ +/* + * 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 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. + */ + +#import "config.h" + +#if ENABLE(VIDEO) + +#import "MediaPlayerPrivateQTKit.h" + +#import "BlockExceptions.h" +#import "GraphicsContext.h" +#import "KURL.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) +{ + IMP result = m->method_imp; + m->method_imp = imp; + return result; +} +#endif + +SOFT_LINK_FRAMEWORK(QTKit) + +SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale)) + +SOFT_LINK_CLASS(QTKit, QTMovie) +SOFT_LINK_CLASS(QTKit, QTMovieView) + +SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *) +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 *) +SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *) +SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *) +SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *) +SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *) +SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *) +SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *) +SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *) +SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *) +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() + +#define QTMediaTypeAttribute getQTMediaTypeAttribute() +#define QTMediaTypeBase getQTMediaTypeBase() +#define QTMediaTypeSound getQTMediaTypeSound() +#define QTMediaTypeText getQTMediaTypeText() +#define QTMediaTypeVideo getQTMediaTypeVideo() +#define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute() +#define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute() +#define QTMovieDidEndNotification getQTMovieDidEndNotification() +#define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute() +#define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute() +#define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute() +#define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification() +#define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute() +#define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute() +#define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification() +#define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification() +#define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification() +#define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute() +#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 +enum { + QTMovieLoadStateError = -1L, + QTMovieLoadStateLoaded = 2000L, + QTMovieLoadStatePlayable = 10000L, + QTMovieLoadStatePlaythroughOK = 20000L, + QTMovieLoadStateComplete = 100000L +}; +#endif + +using namespace WebCore; +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; +-(void)rateChanged:(NSNotification *)notification; +-(void)sizeChanged:(NSNotification *)notification; +-(void)timeChanged:(NSNotification *)notification; +-(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]) + , m_seekTo(-1) + , m_endTime(numeric_limits<float>::infinity()) + , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired) + , m_endPointTimer(this, &MediaPlayerPrivate::endPointTimerFired) + , m_networkState(MediaPlayer::Empty) + , 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() +{ + tearDownVideoRendering(); + + [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; + [m_objcObserver.get() disconnect]; +} + +void MediaPlayerPrivate::createQTMovie(const String& url) +{ + [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; + + if (m_qtMovie) { + destroyQTVideoRenderer(); + m_qtMovie = 0; + } + + // Disable streaming support for now, <rdar://problem/5693967> + if (protocolIs(url, "rtsp")) + return; + + 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 = protocolIs(url, "rtsp"); + + if (!m_qtMovie) + return; + + [m_qtMovie.get() setVolume:m_player->volume()]; + + [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() + selector:@selector(loadStateChanged:) + name:QTMovieLoadStateDidChangeNotification + object:m_qtMovie.get()]; + [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() + selector:@selector(rateChanged:) + name:QTMovieRateDidChangeNotification + object:m_qtMovie.get()]; + [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() + selector:@selector(sizeChanged:) + name:QTMovieSizeDidChangeNotification + object:m_qtMovie.get()]; + [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() + selector:@selector(timeChanged:) + name:QTMovieTimeDidChangeNotification + object:m_qtMovie.get()]; + [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() + selector:@selector(didEnd:) + name:QTMovieDidEndNotification + object:m_qtMovie.get()]; +} + +static void mainThreadSetNeedsDisplay(id self, SEL _cmd) +{ + id movieView = [self superview]; + ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]); + if (!movieView || ![movieView isKindOfClass:[QTMovieView class]]) + return; + + WebCoreMovieObserver* delegate = [movieView delegate]; + ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]); + if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]]) + return; + + [delegate repaint]; +} + +static Class QTVideoRendererClass() +{ + static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly"); + return QTVideoRendererWebKitOnlyClass; +} + +void MediaPlayerPrivate::createQTMovieView() +{ + detachQTMovieView(); + + static bool addedCustomMethods = false; + if (!addedCustomMethods) { + Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView"); + ASSERT(QTMovieContentViewClass); + + Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay)); + ASSERT(mainThreadSetNeedsDisplayMethod); + + method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay)); + addedCustomMethods = true; + } + + m_qtMovieView.adoptNS([[QTMovieView alloc] init]); + setRect(m_player->rect()); + 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 + [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()]; +#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]]; + + // 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]; +#else + [m_qtMovieView.get() setDelegate:nil]; +#endif + [m_qtMovieView.get() removeFromSuperview]; + m_qtMovieView = nil; + } +} + +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) + return QTMakeTime(0, 600); + long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue]; + return QTMakeTime(time * timeScale, timeScale); +} + +void MediaPlayerPrivate::load(const String& url) +{ + if (m_networkState != MediaPlayer::Loading) { + m_networkState = MediaPlayer::Loading; + m_player->networkStateChanged(); + } + if (m_readyState != MediaPlayer::DataUnavailable) { + m_readyState = MediaPlayer::DataUnavailable; + m_player->readyStateChanged(); + } + cancelSeek(); + m_endPointTimer.stop(); + + [m_objcObserver.get() setDelayCallbacks:YES]; + + createQTMovie(url); + + [m_objcObserver.get() loadStateChanged:nil]; + [m_objcObserver.get() setDelayCallbacks:NO]; +} + +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]; + startEndPointTimerIfNeeded(); +} + +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]; + m_endPointTimer.stop(); +} + +float MediaPlayerPrivate::duration() const +{ + if (!m_qtMovie) + return 0; + QTTime time = [m_qtMovie.get() duration]; + if (time.flags == kQTTimeIsIndefinite) + return numeric_limits<float>::infinity(); + return static_cast<float>(time.timeValue) / time.timeScale; +} + +float MediaPlayerPrivate::currentTime() const +{ + if (!m_qtMovie) + return 0; + QTTime time = [m_qtMovie.get() currentTime]; + return min(static_cast<float>(time.timeValue) / time.timeScale, m_endTime); +} + +void MediaPlayerPrivate::seek(float time) +{ + cancelSeek(); + + if (!m_qtMovie) + return; + + if (time > duration()) + time = duration(); + + m_seekTo = time; + if (maxTimeLoaded() >= m_seekTo) + doSeek(); + else + m_seekTimer.start(0, 0.5f); +} + +void MediaPlayerPrivate::doSeek() +{ + QTTime qttime = createQTTime(m_seekTo); + // setCurrentTime generates several event callbacks, update afterwards + [m_objcObserver.get() setDelayCallbacks:YES]; + float oldRate = [m_qtMovie.get() rate]; + [m_qtMovie.get() setRate:0]; + [m_qtMovie.get() setCurrentTime:qttime]; + float timeAfterSeek = currentTime(); + // restore playback only if not at end, othewise QTMovie will loop + if (timeAfterSeek < duration() && timeAfterSeek < m_endTime) + [m_qtMovie.get() setRate:oldRate]; + cancelSeek(); + [m_objcObserver.get() setDelayCallbacks:NO]; +} + +void MediaPlayerPrivate::cancelSeek() +{ + m_seekTo = -1; + m_seekTimer.stop(); +} + +void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*) +{ + if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) { + cancelSeek(); + updateStates(); + m_player->timeChanged(); + return; + } + + if (maxTimeLoaded() >= m_seekTo) + doSeek(); + else { + MediaPlayer::NetworkState state = networkState(); + if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) { + cancelSeek(); + updateStates(); + m_player->timeChanged(); + } + } +} + +void MediaPlayerPrivate::setEndTime(float time) +{ + m_endTime = time; + startEndPointTimerIfNeeded(); +} + +void MediaPlayerPrivate::startEndPointTimerIfNeeded() +{ + if (m_endTime < duration() && m_startedPlaying && !m_endPointTimer.isActive()) + m_endPointTimer.startRepeating(endPointTimerInterval); +} + +void MediaPlayerPrivate::endPointTimerFired(Timer<MediaPlayerPrivate>*) +{ + float time = currentTime(); + + // just do end for now + if (time >= m_endTime) { + pause(); + didEnd(); + } +} + +bool MediaPlayerPrivate::paused() const +{ + if (!m_qtMovie) + return true; + return [m_qtMovie.get() rate] == 0; +} + +bool MediaPlayerPrivate::seeking() const +{ + if (!m_qtMovie) + return false; + return m_seekTo >= 0; +} + +IntSize MediaPlayerPrivate::naturalSize() const +{ + if (!m_qtMovie) + return IntSize(); + return IntSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]); +} + +bool MediaPlayerPrivate::hasVideo() const +{ + if (!m_qtMovie) + return false; + return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue]; +} + +void MediaPlayerPrivate::setVolume(float volume) +{ + if (!m_qtMovie) + return; + [m_qtMovie.get() setVolume:volume]; +} + +void MediaPlayerPrivate::setRate(float rate) +{ + if (!m_qtMovie) + return; + if (!paused()) + [m_qtMovie.get() setRate:rate]; +} + +int MediaPlayerPrivate::dataRate() const +{ + if (!m_qtMovie) + return 0; + return wkQTMovieDataRate(m_qtMovie.get()); +} + + +float MediaPlayerPrivate::maxTimeBuffered() const +{ + // rtsp streams are not buffered + return m_isStreaming ? 0 : maxTimeLoaded(); +} + +float MediaPlayerPrivate::maxTimeSeekable() const +{ + // infinite duration means live stream + return isinf(duration()) ? 0 : maxTimeLoaded(); +} + +float MediaPlayerPrivate::maxTimeLoaded() const +{ + if (!m_qtMovie) + return 0; + return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); +} + +unsigned MediaPlayerPrivate::bytesLoaded() const +{ + float dur = duration(); + if (!dur) + return 0; + return totalBytes() * maxTimeLoaded() / dur; +} + +bool MediaPlayerPrivate::totalBytesKnown() const +{ + return totalBytes() > 0; +} + +unsigned MediaPlayerPrivate::totalBytes() const +{ + if (!m_qtMovie) + return 0; + return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue]; +} + +void MediaPlayerPrivate::cancelLoad() +{ + // FIXME: Is there a better way to check for this? + if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) + return; + + tearDownVideoRendering(); + m_qtMovie = nil; + + updateStates(); +} + +void MediaPlayerPrivate::updateStates() +{ + MediaPlayer::NetworkState oldNetworkState = m_networkState; + MediaPlayer::ReadyState oldReadyState = m_readyState; + + long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError); + + 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) + m_networkState = MediaPlayer::Loaded; + m_readyState = MediaPlayer::CanPlayThrough; + } else if (loadState >= QTMovieLoadStatePlaythroughOK) { + if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking()) + m_networkState = MediaPlayer::LoadedFirstFrame; + m_readyState = MediaPlayer::CanPlayThrough; + } else if (loadState >= QTMovieLoadStatePlayable) { + if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking()) + m_networkState = MediaPlayer::LoadedFirstFrame; + // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967> + m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::CanPlay : MediaPlayer::DataUnavailable; + } else if (loadState >= QTMovieLoadStateLoaded) { + if (m_networkState < MediaPlayer::LoadedMetaData) + m_networkState = MediaPlayer::LoadedMetaData; + m_readyState = MediaPlayer::DataUnavailable; + } else if (loadState > QTMovieLoadStateError) { + if (m_networkState < MediaPlayer::Loading) + m_networkState = MediaPlayer::Loading; + m_readyState = MediaPlayer::DataUnavailable; + } else { + m_networkState = MediaPlayer::LoadFailed; + m_readyState = MediaPlayer::DataUnavailable; + } + + if (seeking()) + m_readyState = MediaPlayer::DataUnavailable; + + if (m_networkState != oldNetworkState) + m_player->networkStateChanged(); + if (m_readyState != oldReadyState) + m_player->readyStateChanged(); + + if (loadState >= QTMovieLoadStateLoaded && oldNetworkState < MediaPlayer::LoadedMetaData && m_player->visible()) + setUpVideoRendering(); +} + +void MediaPlayerPrivate::loadStateChanged() +{ + updateStates(); +} + +void MediaPlayerPrivate::rateChanged() +{ + updateStates(); +} + +void MediaPlayerPrivate::sizeChanged() +{ +} + +void MediaPlayerPrivate::timeChanged() +{ + updateStates(); + m_player->timeChanged(); +} + +void MediaPlayerPrivate::didEnd() +{ + m_endPointTimer.stop(); + m_startedPlaying = false; +#if DRAW_FRAME_RATE + m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate]; +#endif + updateStates(); + m_player->timeChanged(); +} + +void MediaPlayerPrivate::setRect(const IntRect& r) +{ + if (!m_qtMovieView) + return; + + 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 (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(); +} + +void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r) +{ + if (context->paintingDisabled()) + return; + NSView *view = m_qtMovieView.get(); + 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]; + + // 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]; +} + +void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types) +{ + NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes]; + int count = [fileTypes count]; + for (int n = 0; n < count; n++) { + CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]); + RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL)); + if (!uti) + continue; + RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType)); + if (!mime) + continue; + types.add(mime.get()); + } +} + +bool MediaPlayerPrivate::isAvailable() +{ +#ifdef BUILDING_ON_TIGER + SInt32 version; + OSErr result; + result = Gestalt(gestaltQuickTime, &version); + if (result != noErr) { + LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support."); + return false; + } + if (version < minimumQuickTimeVersion) { + LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion); + 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) +{ + if (!m_qtMovie) { + enabledTrackCount = 0; + return; + } + + static HashSet<String>* allowedTrackTypes = 0; + if (!allowedTrackTypes) { + allowedTrackTypes = new HashSet<String>; + allowedTrackTypes->add(QTMediaTypeVideo); + allowedTrackTypes->add(QTMediaTypeSound); + allowedTrackTypes->add(QTMediaTypeText); + allowedTrackTypes->add(QTMediaTypeBase); + allowedTrackTypes->add("clcp"); + allowedTrackTypes->add("sbtl"); + } + + NSArray *tracks = [m_qtMovie.get() tracks]; + + unsigned trackCount = [tracks count]; + enabledTrackCount = trackCount; + for (unsigned trackIndex = 0; trackIndex < trackCount; trackIndex++) { + // Grab the track at the current index. If there isn't one there, then + // we can move onto the next one. + QTTrack *track = [tracks objectAtIndex:trackIndex]; + if (!track) + continue; + + // Check to see if the track is disabled already, we should move along. + // We don't need to re-disable it. + if (![track isEnabled]) + continue; + + // Grab the track's media. We're going to check to see if we need to + // disable the tracks. They could be unsupported. + QTMedia *trackMedia = [track media]; + if (!trackMedia) + continue; + + // Grab the media type for this track. + NSString *mediaType = [trackMedia attributeForKey:QTMediaTypeAttribute]; + if (!mediaType) + continue; + + // Test whether the media type is in our white list. + if (!allowedTrackTypes->contains(mediaType)) { + // If this track type is not allowed, then we need to disable it. + [track setEnabled:NO]; + --enabledTrackCount; + } + + // Disable chapter tracks. These are most likely to lead to trouble, as + // they will be composited under the video tracks, forcing QT to do extra + // work. + QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)]; + if (!chapterTrack) + continue; + + // Try to grab the media for the track. + QTMedia *chapterMedia = [chapterTrack media]; + if (!chapterMedia) + continue; + + // Grab the media type for this track. + id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute]; + if (!chapterMediaType) + continue; + + // Check to see if the track is a video track. We don't care about + // other non-video tracks. + if (![chapterMediaType isEqual:QTMediaTypeVideo]) + continue; + + // Check to see if the track is already disabled. If it is, we + // should move along. + if (![chapterTrack isEnabled]) + continue; + + // Disable the evil, evil track. + [chapterTrack setEnabled:NO]; + --enabledTrackCount; + } +} + +} + +@implementation WebCoreMovieObserver + +- (id)initWithCallback:(MediaPlayerPrivate *)callback +{ + m_callback = callback; + return [super init]; +} + +- (void)disconnect +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + 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) + [self performSelector:_cmd withObject:nil afterDelay:0.]; + else if (m_callback) + m_callback->repaint(); +} + +- (void)loadStateChanged:(NSNotification *)notification +{ + if (m_delayCallbacks) + [self performSelector:_cmd withObject:nil afterDelay:0]; + else + m_callback->loadStateChanged(); +} + +- (void)rateChanged:(NSNotification *)notification +{ + if (m_delayCallbacks) + [self performSelector:_cmd withObject:nil afterDelay:0]; + else + m_callback->rateChanged(); +} + +- (void)sizeChanged:(NSNotification *)notification +{ + if (m_delayCallbacks) + [self performSelector:_cmd withObject:nil afterDelay:0]; + else + m_callback->sizeChanged(); +} + +- (void)timeChanged:(NSNotification *)notification +{ + if (m_delayCallbacks) + [self performSelector:_cmd withObject:nil afterDelay:0]; + else + m_callback->timeChanged(); +} + +- (void)didEnd:(NSNotification *)notification +{ + if (m_delayCallbacks) + [self performSelector:_cmd withObject:nil afterDelay:0]; + else + m_callback->didEnd(); +} + +- (void)newImageAvailable:(NSNotification *)notification +{ + [self repaint]; +} + +- (void)setDelayCallbacks:(BOOL)shouldDelay +{ + m_delayCallbacks = shouldDelay; +} + +@end + +#endif diff --git a/WebCore/platform/graphics/mac/SimpleFontDataMac.mm b/WebCore/platform/graphics/mac/SimpleFontDataMac.mm new file mode 100644 index 0000000..4ee5933 --- /dev/null +++ b/WebCore/platform/graphics/mac/SimpleFontDataMac.mm @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Alexey Proskuryakov + * + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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. + */ + +#import "config.h" +#import "SimpleFontData.h" + +#import "BlockExceptions.h" +#import "Color.h" +#import "FloatRect.h" +#import "Font.h" +#import "FontCache.h" +#import "FontDescription.h" +#import "SharedBuffer.h" +#import "WebCoreSystemInterface.h" +#import <ApplicationServices/ApplicationServices.h> +#import <float.h> +#import <unicode/uchar.h> +#import <wtf/Assertions.h> +#import <wtf/RetainPtr.h> + +@interface NSFont (WebAppKitSecretAPI) +- (BOOL)_isFakeFixedPitch; +@end + +namespace WebCore { + +const float smallCapsFontSizeMultiplier = 0.7f; +const float contextDPI = 72.0f; +static inline float scaleEmToUnits(float x, unsigned unitsPerEm) { return x * (contextDPI / (contextDPI * unitsPerEm)); } + +bool initFontData(SimpleFontData* fontData) +{ + 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); + return false; + } + + ATSUAttributeTag tag = kATSUFontTag; + ByteCount size = sizeof(ATSUFontID); + ATSUFontID *valueArray[1] = {&fontId}; + OSStatus status = ATSUSetAttributes(fontStyle, 1, &tag, &size, (void* const*)valueArray); + if (status != noErr) { + ATSUDisposeStyle(fontStyle); + return false; + } + + if (wkGetATSStyleGroup(fontStyle, &fontData->m_styleGroup) != noErr) { + ATSUDisposeStyle(fontStyle); + return false; + } + + ATSUDisposeStyle(fontStyle); +#endif + + return true; +} + +static NSString *webFallbackFontFamily(void) +{ + static RetainPtr<NSString> webFallbackFontFamily = nil; + if (!webFallbackFontFamily) + webFallbackFontFamily = [[NSFont systemFontOfSize:16.0f] familyName]; + 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. + // Try looking for an alternate 'base' font for this renderer. + + // Special case hack to use "Times New Roman" in place of "Times". + // "Times RO" is a common font whose family name is "Times". + // It overrides the normal "Times" family font. + // It also appears to have a corrupt regular variant. + NSString *fallbackFontFamily; + if ([[m_font.font() familyName] isEqual:@"Times"]) + fallbackFontFamily = @"Times New Roman"; + else + fallbackFontFamily = webFallbackFontFamily(); + + // Try setting up the alternate font. + // This is a last ditch effort to use a substitute font when something has gone wrong. +#if !ERROR_DISABLED + RetainPtr<NSFont> initialFont = m_font.font(); +#endif + 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 = pathFromFont(initialFont.get()); + if (!filePath) + filePath = @"not known"; +#endif + if (!initFontData(this)) { + if ([fallbackFontFamily isEqual:@"Times New Roman"]) { + // OK, couldn't setup Times New Roman as an alternate to Times, fallback + // on the system font. If this fails we have no alternative left. + m_font.setFont([[NSFontManager sharedFontManager] convertFont:m_font.font() toFamily:webFallbackFontFamily()]); + if (!initFontData(this)) { + // We tried, Times, Times New Roman, and the system font. No joy. We have to give up. + LOG_ERROR("unable to initialize with font %@ at %@", initialFont.get(), filePath); + failedSetup = true; + } + } else { + // We tried the requested font and the system font. No joy. We have to give up. + LOG_ERROR("unable to initialize with font %@ at %@", initialFont.get(), filePath); + failedSetup = true; + } + } + + // Report the problem. + LOG_ERROR("Corrupt font detected, using %@ in place of %@ located at \"%@\".", + [m_font.font() familyName], [initialFont.get() familyName], filePath); + } + + // If all else fails, try to set up using the system font. + // This is probably because Times and Times New Roman are both unavailable. + if (failedSetup) { + m_font.setFont([NSFont systemFontOfSize:[m_font.font() pointSize]]); + LOG_ERROR("failed to set up font, using system font %s", m_font.font()); + initFontData(this); + } + + int iAscent; + int iDescent; + int iLineGap; +#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; + float fLineGap = scaleEmToUnits(iLineGap, m_unitsPerEm) * pointSize; + + // We need to adjust Times, Helvetica, and Courier to closely match the + // vertical metrics of their Microsoft counterparts that are the de facto + // web standard. The AppKit adjustment of 20% is too big and is + // incorrectly added to line spacing, so we use a 15% adjustment instead + // and add it to the ascent. + 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); + m_lineGap = lroundf(fLineGap); + m_lineSpacing = m_ascent + m_descent + m_lineGap; + + // Hack Hiragino line metrics to allow room for marked text underlines. + // <rdar://problem/5386183> + if (m_descent < 3 && m_lineGap >= 3 && [familyName hasPrefix:@"Hiragino"]) { + m_lineGap -= 3 - m_descent; + m_descent = 3; + } + + // Measure the actual character "x", because AppKit synthesizes X height rather than getting it from the font. + // Unfortunately, NSFont will round this for us so we don't quite get the right value. + GlyphPage* glyphPageZero = GlyphPageTreeNode::getRootChild(this, 0)->page(); + NSGlyph xGlyph = glyphPageZero ? glyphPageZero->glyphDataForCharacter('x').glyph : 0; + if (xGlyph) { + NSRect xBox = [m_font.font() boundingRectForGlyph:xGlyph]; + // Use the maximum of either width or height because "x" is nearly square + // and web pages that foolishly use this metric for width will be laid out + // poorly if we return an accurate height. Classic case is Times 13 point, + // which has an "x" that is 7x6 pixels. + m_xHeight = MAX(NSMaxX(xBox), NSMaxY(xBox)); + } else + m_xHeight = [m_font.font() xHeight]; +} + +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 +{ + if (!m_smallCapsFontData) { + if (isCustomFont()) { + FontPlatformData smallCapsFontData(m_font); + smallCapsFontData.m_size = smallCapsFontData.m_size * smallCapsFontSizeMultiplier; + m_smallCapsFontData = new SimpleFontData(smallCapsFontData, true, false); + } else { + BEGIN_BLOCK_OBJC_EXCEPTIONS; + float size = [m_font.font() pointSize] * smallCapsFontSizeMultiplier; + FontPlatformData smallCapsFont([[NSFontManager sharedFontManager] convertFont:m_font.font() toSize:size]); + + // AppKit resets the type information (screen/printer) when you convert a font to a different size. + // We have to fix up the font that we're handed back. + smallCapsFont.setFont(fontDescription.usePrinterFont() ? [smallCapsFont.font() printerFont] : [smallCapsFont.font() screenFont]); + + if (smallCapsFont.font()) { + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + NSFontTraitMask fontTraits = [fontManager traitsOfFont:m_font.font()]; + + if (m_font.m_syntheticBold) + fontTraits |= NSBoldFontMask; + if (m_font.m_syntheticOblique) + fontTraits |= NSItalicFontMask; + + NSFontTraitMask smallCapsFontTraits = [fontManager traitsOfFont:smallCapsFont.font()]; + smallCapsFont.m_syntheticBold = (fontTraits & NSBoldFontMask) && !(smallCapsFontTraits & NSBoldFontMask); + smallCapsFont.m_syntheticOblique = (fontTraits & NSItalicFontMask) && !(smallCapsFontTraits & NSItalicFontMask); + + m_smallCapsFontData = FontCache::getCachedFontData(&smallCapsFont); + } + END_BLOCK_OBJC_EXCEPTIONS; + } + } + return m_smallCapsFontData; +} + +bool SimpleFontData::containsCharacters(const UChar* characters, int length) const +{ + NSString *string = [[NSString alloc] initWithCharactersNoCopy:(UniChar*)characters length:length freeWhenDone:NO]; + NSCharacterSet *set = [[m_font.font() coveredCharacterSet] invertedSet]; + bool result = set && [string rangeOfCharacterFromSet:set].location == NSNotFound; + [string release]; + return result; +} + +void SimpleFontData::determinePitch() +{ + NSFont* f = m_font.font(); + // Special case Osaka-Mono. + // According to <rdar://problem/3999467>, we should treat Osaka-Mono as fixed pitch. + // Note that the AppKit does not report Osaka-Mono as fixed pitch. + + // Special case MS-PGothic. + // According to <rdar://problem/4032938>, we should not treat MS-PGothic as fixed pitch. + // Note that AppKit does report MS-PGothic as fixed pitch. + + // Special case MonotypeCorsiva + // According to <rdar://problem/5454704>, we should not treat MonotypeCorsiva as fixed pitch. + // Note that AppKit does report MonotypeCorsiva as fixed pitch. + + NSString *name = [f fontName]; + m_treatAsFixedPitch = ([f isFixedPitch] || [f _isFakeFixedPitch] || + [name caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame) && + [name caseInsensitiveCompare:@"MS-PGothic"] != NSOrderedSame && + [name caseInsensitiveCompare:@"MonotypeCorsiva"] != NSOrderedSame; +} + +float SimpleFontData::platformWidthForGlyph(Glyph glyph) const +{ + NSFont* font = m_font.font(); + float pointSize = m_font.m_size; + CGAffineTransform m = CGAffineTransformMakeScale(pointSize, pointSize); + CGSize 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); + + m_checkedShapesArabic = true; + + ATSUFontID fontID = m_font.m_atsuFontID; + if (!fontID) { + LOG_ERROR("unable to get ATSUFontID for %@", m_font.font()); + return; + } + + // This function is called only on fonts that contain Arabic glyphs. Our + // heuristic is that if such a font has a glyph metamorphosis table, then + // it includes shaping information for Arabic. + FourCharCode tables[] = { 'morx', 'mort' }; + for (unsigned i = 0; i < sizeof(tables) / sizeof(tables[0]); ++i) { + ByteCount tableSize; + OSStatus status = ATSFontGetTable(fontID, tables[i], 0, 0, 0, &tableSize); + if (status == noErr) { + m_shapesArabic = true; + return; + } + + if (status != kATSInvalidFontTableAccess) + 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 |