diff options
author | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-17 17:30:19 +0000 |
---|---|---|
committer | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-17 17:30:19 +0000 |
commit | 9b9877d2952882560e37205edf33ad460a7efa91 (patch) | |
tree | 452db327f3b534b65d8d8260213094c5d9e6d00d /webkit | |
parent | b6f2b91367ba6ed9ebb305233d4e786c1c748e45 (diff) | |
download | chromium_src-9b9877d2952882560e37205edf33ad460a7efa91.zip chromium_src-9b9877d2952882560e37205edf33ad460a7efa91.tar.gz chromium_src-9b9877d2952882560e37205edf33ad460a7efa91.tar.bz2 |
Debase our Uniscribe code. This moves FontUtils and all our Uniscribe code from
base/gfx to webkit/port/platform/graphics. I fixed the indenting and naming of
the moved code.
Review URL: http://codereview.chromium.org/10785
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@5561 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r-- | webkit/SConscript.port | 3 | ||||
-rw-r--r-- | webkit/build/port/port.vcproj | 40 | ||||
-rw-r--r-- | webkit/port/platform/UniscribeStateTextRun.cpp | 128 | ||||
-rw-r--r-- | webkit/port/platform/graphics/FontUtilsWin.cpp | 339 | ||||
-rw-r--r-- | webkit/port/platform/graphics/FontUtilsWin.h | 87 | ||||
-rw-r--r-- | webkit/port/platform/graphics/FontWin.cpp | 13 | ||||
-rw-r--r-- | webkit/port/platform/graphics/GlyphPageTreeNodeWin.cpp | 12 | ||||
-rw-r--r-- | webkit/port/platform/graphics/UniscribeHelper.cpp | 868 | ||||
-rw-r--r-- | webkit/port/platform/graphics/UniscribeHelper.h | 380 | ||||
-rw-r--r-- | webkit/port/platform/graphics/UniscribeHelperTextRun.cpp | 135 | ||||
-rw-r--r-- | webkit/port/platform/graphics/UniscribeHelperTextRun.h (renamed from webkit/port/platform/UniscribeStateTextRun.h) | 73 | ||||
-rw-r--r-- | webkit/port/platform/graphics/UniscribeHelper_unittest.cpp | 142 | ||||
-rw-r--r-- | webkit/port/rendering/RenderThemeWin.cpp | 32 | ||||
-rw-r--r-- | webkit/tools/test_shell/SConscript | 1 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell_tests.vcproj | 12 |
15 files changed, 2056 insertions, 209 deletions
diff --git a/webkit/SConscript.port b/webkit/SConscript.port index 681461c..cbb08d4 100644 --- a/webkit/SConscript.port +++ b/webkit/SConscript.port @@ -159,11 +159,14 @@ if env['PLATFORM'] == 'win32': '$PORT_DIR/platform/win/SoundWin.cpp', '$PORT_DIR/platform/graphics/FontCacheWin.cpp', '$PORT_DIR/platform/graphics/FontPlatformDataWin.cpp', + '$PORT_DIR/platform/graphics/FontUtilsWin.cpp', '$PORT_DIR/platform/graphics/FontWin.cpp', '$PORT_DIR/platform/graphics/GlyphPageTreeNodeWin.cpp', '$PORT_DIR/platform/graphics/IconWin.cpp', '$PORT_DIR/platform/graphics/SimpleFontDataWin.cpp', '$PORT_DIR/platform/graphics/ThemeHelperWin.cpp', + '$PORT_DIR/platform/graphics/UniscribeHelper.cpp', + '$PORT_DIR/platform/graphics/UniscribeHelperTextRun.cpp', '$PORT_DIR/rendering/RenderThemeWin.cpp', ]) diff --git a/webkit/build/port/port.vcproj b/webkit/build/port/port.vcproj index e272395..d521dda 100644 --- a/webkit/build/port/port.vcproj +++ b/webkit/build/port/port.vcproj @@ -671,14 +671,6 @@ RelativePath="..\..\port\platform\StateTrackingString.h" > </File> - <File - RelativePath="..\..\port\platform\UniscribeStateTextRun.cpp" - > - </File> - <File - RelativePath="..\..\port\platform\UniscribeStateTextRun.h" - > - </File> <Filter Name="win" > @@ -723,19 +715,19 @@ > </File> <File - RelativePath="..\..\port\platform\chromium\ContextMenuChromium.cpp" + RelativePath="..\..\port\platform\chromium\ClipboardUtilitiesChromium.cpp" > </File> <File - RelativePath="..\..\port\platform\chromium\ContextMenuItemChromium.cpp" + RelativePath="..\..\port\platform\chromium\ClipboardUtilitiesChromium.h" > </File> <File - RelativePath="..\..\port\platform\chromium\ClipboardUtilitiesChromium.cpp" + RelativePath="..\..\port\platform\chromium\ContextMenuChromium.cpp" > </File> <File - RelativePath="..\..\port\platform\chromium\ClipboardUtilitiesChromium.h" + RelativePath="..\..\port\platform\chromium\ContextMenuItemChromium.cpp" > </File> <File @@ -895,6 +887,14 @@ > </File> <File + RelativePath="..\..\port\platform\graphics\FontUtilsWin.cpp" + > + </File> + <File + RelativePath="..\..\port\platform\graphics\FontUtilsWin.h" + > + </File> + <File RelativePath="..\..\port\platform\graphics\FontWin.cpp" > </File> @@ -986,6 +986,22 @@ RelativePath="..\..\port\platform\graphics\ThemeHelperWin.h" > </File> + <File + RelativePath="..\..\port\platform\graphics\UniscribeHelper.cpp" + > + </File> + <File + RelativePath="..\..\port\platform\graphics\UniscribeHelper.h" + > + </File> + <File + RelativePath="..\..\port\platform\graphics\UniscribeHelperTextRun.cpp" + > + </File> + <File + RelativePath="..\..\port\platform\graphics\UniscribeHelperTextRun.h" + > + </File> <Filter Name="svg" > diff --git a/webkit/port/platform/UniscribeStateTextRun.cpp b/webkit/port/platform/UniscribeStateTextRun.cpp deleted file mode 100644 index 25d79c1..0000000 --- a/webkit/port/platform/UniscribeStateTextRun.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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. - * - * 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 "UniscribeStateTextRun.h" - -#include "ChromiumBridge.h" -#include "Font.h" -#include "SimpleFontData.h" - -UniscribeStateTextRun::UniscribeStateTextRun(const WebCore::TextRun& run, - const WebCore::Font& font) - : UniscribeState(run.characters(), run.length(), run.rtl(), - font.primaryFont()->platformData().hfont(), - font.primaryFont()->platformData().scriptCache(), - font.primaryFont()->platformData().scriptFontProperties()), - font_(&font), - font_index_(0) { - set_directional_override(run.directionalOverride()); - set_letter_spacing(font.letterSpacing()); - set_space_width(font.spaceWidth()); - set_word_spacing(font.wordSpacing()); - set_ascent(font.primaryFont()->ascent()); - - Init(); - - // Padding is the amount to add to make justification happen. This - // should be done after Init() so all the runs are already measured. - if (run.padding() > 0) - Justify(run.padding()); -} - -UniscribeStateTextRun::UniscribeStateTextRun( - const wchar_t* input, - int input_length, - bool is_rtl, - HFONT hfont, - SCRIPT_CACHE* script_cache, - SCRIPT_FONTPROPERTIES* font_properties) - : UniscribeState(input, input_length, is_rtl, hfont, - script_cache, font_properties), - font_(NULL), - font_index_(-1) { -} - -void UniscribeStateTextRun::TryToPreloadFont(HFONT font) { - // Ask the browser to get the font metrics for this font. - // That will preload the font and it should now be accessible - // from the renderer. - WebCore::ChromiumBridge::ensureFontLoaded(font); -} - -bool UniscribeStateTextRun::NextWinFontData( - HFONT* hfont, - SCRIPT_CACHE** script_cache, - SCRIPT_FONTPROPERTIES** font_properties, - int* ascent) { - // This check is necessary because NextWinFontData can be - // called again after we already ran out of fonts. fontDataAt - // behaves in a strange manner when the difference between - // param passed and # of fonts stored in WebKit::Font is - // larger than one. We can avoid this check by setting - // font_index_ to # of elements in hfonts_ when we run out - // of font. In that case, we'd have to go through a couple of - // more checks before returning false. - if (font_index_ == -1 || !font_) - return false; - - // If the font data for a fallback font requested is not - // yet retrieved, add them to our vectors. Note that '>' rather - // than '>=' is used to test that condition. primaryFont is not - // stored in hfonts_, and friends so that indices for fontDataAt - // and our vectors for font data are 1 off from each other. - // That is, when fully populated, hfonts_ and friends have - // one font fewer than what's contained in font_. - if (static_cast<size_t>(++font_index_) > hfonts_->size()) { - const WebCore::FontData *font_data; - font_data = font_->fontDataAt(font_index_); - if (!font_data) { - // run out of fonts - font_index_ = -1; - return false; - } - - // TODO(ericroman): this won't work for SegmentedFontData - // http://b/issue?id=1007335 - const WebCore::SimpleFontData* simple_font_data = - font_data->fontDataForCharacter(' '); - - hfonts_->push_back(simple_font_data->platformData().hfont()); - script_caches_->push_back(simple_font_data->platformData().scriptCache()); - font_properties_->push_back(simple_font_data->platformData().scriptFontProperties()); - ascents_->push_back(simple_font_data->ascent()); - } - - *hfont = hfonts_[font_index_ - 1]; - *script_cache = script_caches_[font_index_ - 1]; - *font_properties = font_properties_[font_index_ - 1]; - *ascent = ascents_[font_index_ - 1]; - return true; -} - -void UniscribeStateTextRun::ResetFontIndex() { - font_index_ = 0; -} diff --git a/webkit/port/platform/graphics/FontUtilsWin.cpp b/webkit/port/platform/graphics/FontUtilsWin.cpp new file mode 100644 index 0000000..80d8ce0b --- /dev/null +++ b/webkit/port/platform/graphics/FontUtilsWin.cpp @@ -0,0 +1,339 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "FontUtilsWin.h" + +#include "UniscribeHelper.h" + +#include <limits> +#include <map> + + +#include "base/logging.h" +#include "base/singleton.h" +#include "base/string_util.h" +#include "unicode/locid.h" +#include "unicode/uchar.h" + +namespace WebCore { + +namespace { + +// hash_map has extra cost with no sizable gain for a small number of integer +// key items. When the map size becomes much bigger (which will be later as +// more scripts are added) and this turns out to be prominent in the profile, we +// may consider switching to hash_map (or just an array if we support all the +// scripts) +typedef std::map<UScriptCode, const UChar*> ScriptToFontMap; + +struct ScriptToFontMapSingletonTraits + : public DefaultSingletonTraits<ScriptToFontMap> { + static ScriptToFontMap* New() { + struct FontMap { + UScriptCode script; + const UChar* family; + }; + + const static FontMap font_map[] = { + {USCRIPT_LATIN, L"times new roman"}, + {USCRIPT_GREEK, L"times new roman"}, + {USCRIPT_CYRILLIC, L"times new roman"}, + {USCRIPT_SIMPLIFIED_HAN, L"simsun"}, + //{USCRIPT_TRADITIONAL_HAN, L"pmingliu"}, + {USCRIPT_HIRAGANA, L"ms pgothic"}, + {USCRIPT_KATAKANA, L"ms pgothic"}, + {USCRIPT_KATAKANA_OR_HIRAGANA, L"ms pgothic"}, + {USCRIPT_HANGUL, L"gulim"}, + {USCRIPT_THAI, L"tahoma"}, + {USCRIPT_HEBREW, L"david"}, + {USCRIPT_ARABIC, L"tahoma"}, + {USCRIPT_DEVANAGARI, L"mangal"}, + {USCRIPT_BENGALI, L"vrinda"}, + {USCRIPT_GURMUKHI, L"raavi"}, + {USCRIPT_GUJARATI, L"shruti"}, + {USCRIPT_ORIYA, L"kalinga"}, + {USCRIPT_TAMIL, L"latha"}, + {USCRIPT_TELUGU, L"gautami"}, + {USCRIPT_KANNADA, L"tunga"}, + {USCRIPT_MALAYALAM, L"kartika"}, + {USCRIPT_LAO, L"dokchampa"}, + {USCRIPT_TIBETAN, L"microsoft himalaya"}, + {USCRIPT_GEORGIAN, L"sylfaen"}, + {USCRIPT_ARMENIAN, L"sylfaen"}, + {USCRIPT_ETHIOPIC, L"nyala"}, + {USCRIPT_CANADIAN_ABORIGINAL, L"euphemia"}, + {USCRIPT_CHEROKEE, L"plantagenet cherokee"}, + {USCRIPT_YI, L"microsoft yi balti"}, + {USCRIPT_SINHALA, L"iskoola pota"}, + {USCRIPT_SYRIAC, L"estrangelo edessa"}, + {USCRIPT_KHMER, L"daunpenh"}, + {USCRIPT_THAANA, L"mv boli"}, + {USCRIPT_MONGOLIAN, L"mongolian balti"}, + {USCRIPT_MYANMAR, L"padauk"}, + // For USCRIPT_COMMON, we map blocks to scripts when + // that makes sense. + }; + + ScriptToFontMap* new_instance = new ScriptToFontMap; + // Cannot recover from OOM so that there's no need to check. + for (int i = 0; i < arraysize(font_map); ++i) + (*new_instance)[font_map[i].script] = font_map[i].family; + + // Initialize the locale-dependent mapping. + // Since Chrome synchronizes the ICU default locale with its UI locale, + // this ICU locale tells the current UI locale of Chrome. + Locale locale = Locale::getDefault(); + ScriptToFontMap::const_iterator iter; + if (locale == Locale::getJapanese()) { + iter = new_instance->find(USCRIPT_HIRAGANA); + } else if (locale == Locale::getKorean()) { + iter = new_instance->find(USCRIPT_HANGUL); + } else { + // Use Simplified Chinese font for all other locales including + // Traditional Chinese because Simsun (SC font) has a wider + // coverage (covering both SC and TC) than PMingLiu (TC font). + // This also speeds up the TC version of Chrome when rendering SC + // pages. + iter = new_instance->find(USCRIPT_SIMPLIFIED_HAN); + } + if (iter != new_instance->end()) + (*new_instance)[USCRIPT_HAN] = iter->second; + + return new_instance; + } +}; + +Singleton<ScriptToFontMap, ScriptToFontMapSingletonTraits> script_font_map; + +const int kUndefinedAscent = std::numeric_limits<int>::min(); + +// Given an HFONT, return the ascent. If GetTextMetrics fails, +// kUndefinedAscent is returned, instead. +int GetAscent(HFONT hfont) { + HDC dc = GetDC(NULL); + HGDIOBJ oldFont = SelectObject(dc, hfont); + TEXTMETRIC tm; + BOOL got_metrics = GetTextMetrics(dc, &tm); + SelectObject(dc, oldFont); + ReleaseDC(NULL, dc); + return got_metrics ? tm.tmAscent : kUndefinedAscent; +} + +struct FontData { + FontData() : hfont(NULL), ascent(kUndefinedAscent), script_cache(NULL) {} + HFONT hfont; + int ascent; + mutable SCRIPT_CACHE script_cache; +}; + +// Again, using hash_map does not earn us much here. +// page_cycler_test intl2 gave us a 'better' result with map than with hash_map +// even though they're well-within 1-sigma of each other so that the difference +// is not significant. On the other hand, some pages in intl2 seem to +// take longer to load with map in the 1st pass. Need to experiment further. +typedef std::map<std::wstring, FontData*> FontDataCache; +struct FontDataCacheSingletonTraits + : public DefaultSingletonTraits<FontDataCache> { + static void Delete(FontDataCache* cache) { + FontDataCache::iterator iter = cache->begin(); + while (iter != cache->end()) { + SCRIPT_CACHE script_cache = iter->second->script_cache; + if (script_cache) + ScriptFreeCache(&script_cache); + delete iter->second; + ++iter; + } + delete cache; + } +}; + +} // namespace + +// TODO(jungshik) : this is font fallback code version 0.1 +// - Cover all the scripts +// - Get the default font for each script/generic family from the +// preference instead of hardcoding in the source. +// (at least, read values from the registry for IE font settings). +// - Support generic families (from FontDescription) +// - If the default font for a script is not available, +// try some more fonts known to support it. Finally, we can +// use EnumFontFamilies or similar APIs to come up with a list of +// fonts supporting the script and cache the result. +// - Consider using UnicodeSet (or UnicodeMap) converted from +// GLYPHSET (BMP) or directly read from truetype cmap tables to +// keep track of which character is supported by which font +// - Update script_font_cache in response to WM_FONTCHANGE + +const UChar* GetFontFamilyForScript(UScriptCode script, + GenericFamilyType generic) { + ScriptToFontMap::const_iterator iter = script_font_map->find(script); + const UChar* family = NULL; + if (iter != script_font_map->end()) + family = iter->second; + return family; +} + +// TODO(jungshik) +// - Handle 'Inherited', 'Common' and 'Unknown' +// (see http://www.unicode.org/reports/tr24/#Usage_Model ) +// For 'Inherited' and 'Common', perhaps we need to +// accept another parameter indicating the previous family +// and just return it. +// - All the characters (or characters up to the point a single +// font can cover) need to be taken into account +const UChar* GetFallbackFamily(const UChar *characters, + int length, + GenericFamilyType generic, + UChar32 *char_checked, + UScriptCode *script_checked) { + DCHECK(characters && characters[0] && length > 0); + UScriptCode script = USCRIPT_COMMON; + + // Sometimes characters common to script (e.g. space) is at + // the beginning of a string so that we need to skip them + // to get a font required to render the string. + int i = 0; + UChar32 ucs4 = 0; + while (i < length && script == USCRIPT_COMMON || + script == USCRIPT_INVALID_CODE) { + U16_NEXT(characters, i, length, ucs4); + UErrorCode err = U_ZERO_ERROR; + script = uscript_getScript(ucs4, &err); + // silently ignore the error + } + + // hack for full width ASCII. For the full-width ASCII, use the font + // for Han (which is locale-dependent). + if (0xFF00 < ucs4 && ucs4 < 0xFF5F) + script = USCRIPT_HAN; + + // There are a lot of characters in USCRIPT_COMMON that can be covered + // by fonts for scripts closely related to them. See + // http://unicode.org/cldr/utility/list-unicodeset.jsp?a=[:Script=Common:] + // TODO(jungshik): make this more efficient with a wider coverage + if (script == USCRIPT_COMMON || script == USCRIPT_INHERITED) { + UBlockCode block = ublock_getCode(ucs4); + switch (block) { + case UBLOCK_BASIC_LATIN: + script = USCRIPT_LATIN; + break; + case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION: + script = USCRIPT_HAN; + break; + case UBLOCK_HIRAGANA: + case UBLOCK_KATAKANA: + script = USCRIPT_HIRAGANA; + break; + case UBLOCK_ARABIC: + script = USCRIPT_ARABIC; + break; + case UBLOCK_GREEK: + script = USCRIPT_GREEK; + break; + case UBLOCK_DEVANAGARI: + // For Danda and Double Danda (U+0964, U+0965), use a Devanagari + // font for now although they're used by other scripts as well. + // Without a context, we can't do any better. + script = USCRIPT_DEVANAGARI; + break; + case UBLOCK_ARMENIAN: + script = USCRIPT_ARMENIAN; + break; + case UBLOCK_GEORGIAN: + script = USCRIPT_GEORGIAN; + break; + case UBLOCK_KANNADA: + script = USCRIPT_KANNADA; + break; + } + } + + // Another lame work-around to cover non-BMP characters. + const UChar* family = GetFontFamilyForScript(script, generic); + if (!family) { + int plane = ucs4 >> 16; + switch (plane) { + case 1: + family = L"code2001"; + break; + case 2: + family = L"simsun-extb"; + break; + default: + family = L"lucida sans unicode"; + } + } + + if (char_checked) *char_checked = ucs4; + if (script_checked) *script_checked = script; + return family; +} + + + +// Be aware that this is not thread-safe. +bool GetDerivedFontData(const UChar *family, + int style, + LOGFONT *logfont, + int *ascent, + HFONT *hfont, + SCRIPT_CACHE **script_cache) { + DCHECK(logfont && family && *family); + // Using |Singleton| here is not free, but the intl2 page cycler test + // does not show any noticeable difference with and without it. Leaking + // the contents of FontDataCache (especially SCRIPT_CACHE) at the end + // of a renderer process may not be a good idea. We may use + // atexit(). However, with no noticeable performance difference, |Singleton| + // is cleaner, I believe. + FontDataCache* font_data_cache = + Singleton<FontDataCache, FontDataCacheSingletonTraits>::get(); + // TODO(jungshik) : This comes up pretty high in the profile so that + // we need to measure whether using SHA256 (after coercing all the + // fields to char*) is faster than StringPrintf. + std::wstring font_key = StringPrintf(L"%1d:%d:%ls", style, + logfont->lfHeight, family); + FontDataCache::const_iterator iter = font_data_cache->find(font_key); + FontData *derived; + if (iter == font_data_cache->end()) { + DCHECK(wcslen(family) < LF_FACESIZE); + wcscpy_s(logfont->lfFaceName, LF_FACESIZE, family); + // TODO(jungshik): CreateFontIndirect always comes up with + // a font even if there's no font matching the name. Need to + // check it against what we actually want (as is done in + // FontCacheWin.cpp) + derived = new FontData; + derived->hfont = CreateFontIndirect(logfont); + // GetAscent may return kUndefinedAscent, but we still want to + // cache it so that we won't have to call CreateFontIndirect once + // more for HFONT next time. + derived->ascent = GetAscent(derived->hfont); + (*font_data_cache)[font_key] = derived; + } else { + derived = iter->second; + // Last time, GetAscent failed so that only HFONT was + // cached. Try once more assuming that TryPreloadFont + // was called by a caller between calls. + if (kUndefinedAscent == derived->ascent) + derived->ascent = GetAscent(derived->hfont); + } + *hfont = derived->hfont; + *ascent = derived->ascent; + *script_cache = &(derived->script_cache); + return *ascent != kUndefinedAscent; +} + +int GetStyleFromLogfont(const LOGFONT* logfont) { + // TODO(jungshik) : consider defining UNDEFINED or INVALID for style and + // returning it when logfont is NULL + if (!logfont) { + NOTREACHED(); + return FONT_STYLE_NORMAL; + } + return (logfont->lfItalic ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL) | + (logfont->lfUnderline ? FONT_STYLE_UNDERLINED : FONT_STYLE_NORMAL) | + (logfont->lfWeight >= 700 ? FONT_STYLE_BOLD : FONT_STYLE_NORMAL); +} + +} // namespace WebCore diff --git a/webkit/port/platform/graphics/FontUtilsWin.h b/webkit/port/platform/graphics/FontUtilsWin.h new file mode 100644 index 0000000..1ec49eb --- /dev/null +++ b/webkit/port/platform/graphics/FontUtilsWin.h @@ -0,0 +1,87 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A collection of utilities for font handling. + +#ifndef FontUtilsWin_h +#define FontUtilsWin_h + +#include <usp10.h> +#include <wchar.h> +#include <windows.h> + +#include <unicode/uscript.h> + +namespace WebCore { + +// The order of family types needs to be exactly the same as +// WebCore::FontDescription::GenericFamilyType. We may lift that restriction +// when we make webkit_glue::WebkitGenericToChromeGenericFamily more +// intelligent. +enum GenericFamilyType { + GENERIC_FAMILY_NONE = 0, + GENERIC_FAMILY_STANDARD, + GENERIC_FAMILY_SERIF, + GENERIC_FAMILY_SANSSERIF, + GENERIC_FAMILY_MONOSPACE, + GENERIC_FAMILY_CURSIVE, + GENERIC_FAMILY_FANTASY +}; + +// Return a font family that supports a script and belongs to |generic| font family. +// It can return NULL and a caller has to implement its own fallback. +const UChar* GetFontFamilyForScript(UScriptCode script, + GenericFamilyType generic); + +// Return a font family that can render |characters| based on +// what script characters belong to. When char_checked is non-NULL, +// it's filled with the character used to determine the script. +// When script_checked is non-NULL, the script used to determine +// the family is returned. +// TODO(jungshik) : This function needs a total overhaul. +const UChar* GetFallbackFamily(const UChar* characters, + int length, + GenericFamilyType generic, + UChar32 *char_checked, + UScriptCode *script_checked); + +// Derive a new HFONT by replacing lfFaceName of LOGFONT with |family|, +// calculate the ascent for the derived HFONT, and initialize SCRIPT_CACHE +// in FontData. +// |style| is only used for cache key generation. |style| is +// bit-wise OR of BOLD(1), UNDERLINED(2) and ITALIC(4) and +// should match what's contained in LOGFONT. It should be calculated +// by calling GetStyleFromLogFont. +// Returns false if the font is not accessible, in which case |ascent| field +// of |fontdata| is set to kUndefinedAscent. +// Be aware that this is not thread-safe. +// TODO(jungshik): Instead of having three out params, we'd better have one +// (|*FontData|), but somehow it mysteriously messes up the layout for +// certain complex script pages (e.g. hi.wikipedia.org) and also crashes +// at the start-up if recently visited page list includes pages with complex +// scripts in their title. Moreover, somehow the very first-pass of +// intl2 page-cycler test is noticeably slower with one out param than +// the current version although the subsequent 9 passes take about the +// same time. +bool GetDerivedFontData(const UChar *family, + int style, + LOGFONT *logfont, + int *ascent, + HFONT *hfont, + SCRIPT_CACHE **script_cache); + +enum { + FONT_STYLE_NORMAL = 0, + FONT_STYLE_BOLD = 1, + FONT_STYLE_ITALIC = 2, + FONT_STYLE_UNDERLINED = 4 +}; + +// Derive style (bit-wise OR of FONT_STYLE_BOLD, FONT_STYLE_UNDERLINED, and +// FONT_STYLE_ITALIC) from LOGFONT. Returns 0 if |*logfont| is NULL. +int GetStyleFromLogfont(const LOGFONT *logfont); + +} // namespace WebCore + +#endif // FontUtilsWin_h diff --git a/webkit/port/platform/graphics/FontWin.cpp b/webkit/port/platform/graphics/FontWin.cpp index e881247..17c3668 100644 --- a/webkit/port/platform/graphics/FontWin.cpp +++ b/webkit/port/platform/graphics/FontWin.cpp @@ -32,7 +32,7 @@ #include "GlyphBuffer.h" #include "PlatformContextSkia.h" #include "SimpleFontData.h" -#include "UniscribeStateTextRun.h" +#include "UniscribeHelperTextRun.h" #include "base/gfx/platform_canvas_win.h" #include "base/gfx/skia_utils.h" @@ -134,7 +134,7 @@ FloatRect Font::selectionRectForComplexText(const TextRun& run, int from, int to) const { - UniscribeStateTextRun state(run, *this); + UniscribeHelperTextRun state(run, *this); float left = static_cast<float>(point.x() + state.CharacterToX(from)); float right = static_cast<float>(point.x() + state.CharacterToX(to)); @@ -154,7 +154,7 @@ void Font::drawComplexText(GraphicsContext* graphicsContext, int to) const { PlatformGraphicsContext* context = graphicsContext->platformContext(); - UniscribeStateTextRun state(run, *this); + UniscribeHelperTextRun state(run, *this); SkColor color = context->fillColor(); uint8 alpha = SkColorGetA(color); @@ -184,15 +184,16 @@ void Font::drawComplexText(GraphicsContext* graphicsContext, float Font::floatWidthForComplexText(const TextRun& run) const { - UniscribeStateTextRun state(run, *this); + UniscribeHelperTextRun state(run, *this); return static_cast<float>(state.Width()); } -int Font::offsetForPositionForComplexText(const TextRun& run, int x, bool includePartialGlyphs) const +int Font::offsetForPositionForComplexText(const TextRun& run, int x, + bool includePartialGlyphs) const { // Mac code ignores includePartialGlyphs, and they don't know what it's // supposed to do, so we just ignore it as well. - UniscribeStateTextRun state(run, *this); + UniscribeHelperTextRun state(run, *this); int char_index = state.XToCharacter(x); // XToCharacter will return -1 if the position is before the first diff --git a/webkit/port/platform/graphics/GlyphPageTreeNodeWin.cpp b/webkit/port/platform/graphics/GlyphPageTreeNodeWin.cpp index 6e0ea3e..ef7c0d3 100644 --- a/webkit/port/platform/graphics/GlyphPageTreeNodeWin.cpp +++ b/webkit/port/platform/graphics/GlyphPageTreeNodeWin.cpp @@ -35,7 +35,7 @@ #include "Font.h" #include "GlyphPageTreeNode.h" #include "SimpleFontData.h" -#include "UniscribeStateTextRun.h" +#include "UniscribeHelperTextRun.h" #include "base/win_util.h" @@ -196,11 +196,11 @@ static bool FillNonBMPGlyphs(UChar* buffer, { bool have_glyphs = false; - UniscribeStateTextRun state(buffer, GlyphPage::size * 2, false, - fontData->m_font.hfont(), - fontData->m_font.scriptCache(), - fontData->m_font.scriptFontProperties()); - state.set_inhibit_ligate(true); + UniscribeHelperTextRun state(buffer, GlyphPage::size * 2, false, + fontData->m_font.hfont(), + fontData->m_font.scriptCache(), + fontData->m_font.scriptFontProperties()); + state.setInhibitLigate(true); state.Init(); for (unsigned i = 0; i < GlyphPage::size; i++) { diff --git a/webkit/port/platform/graphics/UniscribeHelper.cpp b/webkit/port/platform/graphics/UniscribeHelper.cpp new file mode 100644 index 0000000..e0ff01a --- /dev/null +++ b/webkit/port/platform/graphics/UniscribeHelper.cpp @@ -0,0 +1,868 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "UniscribeHelper.h" + +#include <windows.h> + +#include "FontUtilsWin.h" +#include "wtf/Assertions.h" + +namespace WebCore { + +// This function is used to see where word spacing should be applied inside +// runs. Note that this must match Font::treatAsSpace so we all agree where +// and how much space this is, so we don't want to do more general Unicode +// "is this a word break" thing. +static bool TreatAsSpace(UChar c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == 0x00A0; +} + +// SCRIPT_FONTPROPERTIES contains glyph indices for default, invalid +// and blank glyphs. Just because ScriptShape succeeds does not mean +// that a text run is rendered correctly. Some characters may be rendered +// with default/invalid/blank glyphs. Therefore, we need to check if the glyph +// array returned by ScriptShape contains any of those glyphs to make +// sure that the text run is rendered successfully. +static bool ContainsMissingGlyphs(WORD *glyphs, + int length, + SCRIPT_FONTPROPERTIES* properties) +{ + for (int i = 0; i < length; ++i) { + if (glyphs[i] == properties->wgDefault || + (glyphs[i] == properties->wgInvalid && + glyphs[i] != properties->wgBlank)) + return true; + } + + return false; +} + +// HFONT is the 'incarnation' of 'everything' about font, but it's an opaque +// handle and we can't directly query it to make a new HFONT sharing +// its characteristics (height, style, etc) except for family name. +// This function uses GetObject to convert HFONT back to LOGFONT, +// resets the fields of LOGFONT and calculates style to use later +// for the creation of a font identical to HFONT other than family name. +static void SetLogFontAndStyle(HFONT hfont, LOGFONT *logfont, int *style) +{ + ASSERT(hfont && logfont); + if (!hfont || !logfont) + return; + + GetObject(hfont, sizeof(LOGFONT), logfont); + // We reset these fields to values appropriate for CreateFontIndirect. + // while keeping lfHeight, which is the most important value in creating + // a new font similar to hfont. + logfont->lfWidth = 0; + logfont->lfEscapement = 0; + logfont->lfOrientation = 0; + logfont->lfCharSet = DEFAULT_CHARSET; + logfont->lfOutPrecision = OUT_TT_ONLY_PRECIS; + logfont->lfQuality = DEFAULT_QUALITY; // Honor user's desktop settings. + logfont->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + if (style) + *style = GetStyleFromLogfont(logfont); +} + +UniscribeHelper::UniscribeHelper(const UChar* input, + int inputLength, + bool isRtl, + HFONT hfont, + SCRIPT_CACHE* scriptCache, + SCRIPT_FONTPROPERTIES* fontProperties) + : m_input(input) + , m_inputLength(inputLength) + , m_isRtl(isRtl) + , m_hfont(hfont) + , m_scriptCache(scriptCache) + , m_fontProperties(fontProperties) + , m_directionalOverride(false) + , m_inhibitLigate(false) + , m_letterSpacing(0) + , m_spaceWidth(0) + , m_wordSpacing(0) + , m_ascent(0) +{ + m_logfont.lfFaceName[0] = 0; +} + +UniscribeHelper::~UniscribeHelper() +{ +} + +void UniscribeHelper::InitWithOptionalLengthProtection(bool lengthProtection) +{ + // We cap the input length and just don't do anything. We'll allocate a lot + // of things of the size of the number of characters, so the allocated + // memory will be several times the input length. Plus shaping such a large + // buffer may be a form of denial of service. No legitimate text should be + // this long. It also appears that Uniscribe flatly rejects very long + // strings, so we don't lose anything by doing this. + // + // The input length protection may be disabled by the unit tests to cause + // an error condition. + static const int kMaxInputLength = 65535; + if (m_inputLength == 0 || + (lengthProtection && m_inputLength > kMaxInputLength)) + return; + + FillRuns(); + FillShapes(); + FillScreenOrder(); +} + +int UniscribeHelper::Width() const +{ + int width = 0; + for (int item_index = 0; item_index < static_cast<int>(m_runs.size()); + item_index++) { + width += AdvanceForItem(item_index); + } + return width; +} + +void UniscribeHelper::Justify(int additionalSpace) +{ + // Count the total number of glyphs we have so we know how big to make the + // buffers below. + int totalGlyphs = 0; + for (size_t run = 0; run < m_runs.size(); run++) { + int run_idx = m_screenOrder[run]; + totalGlyphs += static_cast<int>(m_shapes[run_idx].glyphLength()); + } + if (totalGlyphs == 0) + return; // Nothing to do. + + // We make one big buffer in screen order of all the glyphs we are drawing + // across runs so that the justification function will adjust evenly across + // all glyphs. + Vector<SCRIPT_VISATTR, 64> visattr; + visattr.resize(totalGlyphs); + Vector<int, 64> advances; + advances.resize(totalGlyphs); + Vector<int, 64> justify; + justify.resize(totalGlyphs); + + // Build the packed input. + int dest_index = 0; + for (size_t run = 0; run < m_runs.size(); run++) { + int run_idx = m_screenOrder[run]; + const Shaping& shaping = m_shapes[run_idx]; + + for (int i = 0; i < shaping.glyphLength(); i++, dest_index++) { + memcpy(&visattr[dest_index], &shaping.m_visattr[i], + sizeof(SCRIPT_VISATTR)); + advances[dest_index] = shaping.m_advance[i]; + } + } + + // The documentation for ScriptJustify is wrong, the parameter is the space + // to add and not the width of the column you want. + const int minKashida = 1; // How do we decide what this should be? + ScriptJustify(&visattr[0], &advances[0], totalGlyphs, additionalSpace, + minKashida, &justify[0]); + + // Now we have to unpack the justification amounts back into the runs so + // the glyph indices match. + int globalGlyphIndex = 0; + for (size_t run = 0; run < m_runs.size(); run++) { + int run_idx = m_screenOrder[run]; + Shaping& shaping = m_shapes[run_idx]; + + shaping.m_justify.resize(shaping.glyphLength()); + for (int i = 0; i < shaping.glyphLength(); i++, globalGlyphIndex++) + shaping.m_justify[i] = justify[globalGlyphIndex]; + } +} + +int UniscribeHelper::CharacterToX(int offset) const +{ + HRESULT hr; + ASSERT(offset <= m_inputLength); + + // Our algorithm is to traverse the items in screen order from left to + // right, adding in each item's screen width until we find the item with + // the requested character in it. + int width = 0; + for (size_t screen_idx = 0; screen_idx < m_runs.size(); screen_idx++) { + // Compute the length of this run. + int itemIdx = m_screenOrder[screen_idx]; + const SCRIPT_ITEM& item = m_runs[itemIdx]; + const Shaping& shaping = m_shapes[itemIdx]; + int itemLength = shaping.charLength(); + + if (offset >= item.iCharPos && offset <= item.iCharPos + itemLength) { + // Character offset is in this run. + int char_len = offset - item.iCharPos; + + int curX = 0; + hr = ScriptCPtoX(char_len, FALSE, itemLength, + shaping.glyphLength(), + &shaping.m_logs[0], &shaping.m_visattr[0], + shaping.effectiveAdvances(), &item.a, &curX); + if (FAILED(hr)) + return 0; + + width += curX + shaping.m_prePadding; + ASSERT(width >= 0); + return width; + } + + // Move to the next item. + width += AdvanceForItem(itemIdx); + } + ASSERT(width >= 0); + return width; +} + +int UniscribeHelper::XToCharacter(int x) const +{ + // We iterate in screen order until we find the item with the given pixel + // position in it. When we find that guy, we ask Uniscribe for the + // character index. + HRESULT hr; + for (size_t screen_idx = 0; screen_idx < m_runs.size(); screen_idx++) { + int itemIdx = m_screenOrder[screen_idx]; + int advance_for_item = AdvanceForItem(itemIdx); + + // Note that the run may be empty if shaping failed, so we want to skip + // over it. + const Shaping& shaping = m_shapes[itemIdx]; + int itemLength = shaping.charLength(); + if (x <= advance_for_item && itemLength > 0) { + // The requested offset is within this item. + const SCRIPT_ITEM& item = m_runs[itemIdx]; + + // Account for the leading space we've added to this run that + // Uniscribe doesn't know about. + x -= shaping.m_prePadding; + + int char_x = 0; + int trailing; + hr = ScriptXtoCP(x, itemLength, shaping.glyphLength(), + &shaping.m_logs[0], &shaping.m_visattr[0], + shaping.effectiveAdvances(), &item.a, &char_x, + &trailing); + + // The character offset is within the item. We need to add the + // item's offset to transform it into the space of the TextRun + return char_x + item.iCharPos; + } + + // The offset is beyond this item, account for its length and move on. + x -= advance_for_item; + } + + // Error condition, we don't know what to do if we don't have that X + // position in any of our items. + return 0; +} + +void UniscribeHelper::Draw(HDC dc, int x, int y, int from, int to) +{ + HGDIOBJ oldFont = 0; + int curX = x; + bool firstRun = true; + + for (size_t screen_idx = 0; screen_idx < m_runs.size(); screen_idx++) { + int itemIdx = m_screenOrder[screen_idx]; + const SCRIPT_ITEM& item = m_runs[itemIdx]; + const Shaping& shaping = m_shapes[itemIdx]; + + // Character offsets within this run. THESE MAY NOT BE IN RANGE and may + // be negative, etc. The code below handles this. + int fromChar = from - item.iCharPos; + int to_char = to - item.iCharPos; + + // See if we need to draw any characters in this item. + if (shaping.charLength() == 0 || + fromChar >= shaping.charLength() || to_char <= 0) { + // No chars in this item to display. + curX += AdvanceForItem(itemIdx); + continue; + } + + // Compute the starting glyph within this span. |from| and |to| are + // global offsets that may intersect arbitrarily with our local run. + int fromGlyph, afterGlyph; + if (item.a.fRTL) { + // To compute the first glyph when going RTL, we use |to|. + if (to_char >= shaping.charLength()) { + // The end of the text is after (to the left) of us. + fromGlyph = 0; + } else { + // Since |to| is exclusive, the first character we draw on the + // left is actually the one right before (to the right) of + // |to|. + fromGlyph = shaping.m_logs[to_char - 1]; + } + + // The last glyph is actually the first character in the range. + if (fromChar <= 0) { + // The first character to draw is before (to the right) of this + // span, so draw all the way to the end. + afterGlyph = shaping.glyphLength(); + } else { + // We want to draw everything up until the character to the + // right of |from|. To the right is - 1, so we look that up + // (remember our character could be more than one glyph, so we + // can't look up our glyph and add one). + afterGlyph = shaping.m_logs[fromChar - 1]; + } + } else { + // Easy case, everybody agrees about directions. We only need to + // handle boundary conditions to get a range inclusive at the + // beginning, and exclusive at the ending. We have to do some + // computation to see the glyph one past the end. + fromGlyph = shaping.m_logs[fromChar < 0 ? 0 : fromChar]; + if (to_char >= shaping.charLength()) + afterGlyph = shaping.glyphLength(); + else + afterGlyph = shaping.m_logs[to_char]; + } + + // Account for the characters that were skipped in this run. When + // WebKit asks us to draw a subset of the run, it actually tells us + // to draw at the X offset of the beginning of the run, since it + // doesn't know the internal position of any of our characters. + const int* effectiveAdvances = shaping.effectiveAdvances(); + int innerOffset = 0; + for (int i = 0; i < fromGlyph; i++) + innerOffset += effectiveAdvances[i]; + + // Actually draw the glyphs we found. + int glyphCount = afterGlyph - fromGlyph; + if (fromGlyph >= 0 && glyphCount > 0) { + // Account for the preceeding space we need to add to this run. We + // don't need to count for the following space because that will be + // counted in AdvanceForItem below when we move to the next run. + innerOffset += shaping.m_prePadding; + + // Pass NULL in when there is no justification. + const int* justify = shaping.m_justify.size() == 0 ? + NULL : &shaping.m_justify[fromGlyph]; + + if (firstRun) { + oldFont = SelectObject(dc, shaping.m_hfont); + firstRun = false; + } else { + SelectObject(dc, shaping.m_hfont); + } + + // TODO(brettw) bug 698452: if a half a character is selected, + // we should set up a clip rect so we draw the half of the glyph + // correctly. + // Fonts with different ascents can be used to render different + // runs. 'Across-runs' y-coordinate correction needs to be + // adjusted for each font. + HRESULT hr = S_FALSE; + for (int executions = 0; executions < 2; ++executions) { + hr = ScriptTextOut(dc, shaping.m_scriptCache, + curX + innerOffset, + y - shaping.m_ascentOffset, + 0, NULL, &item.a, NULL, 0, + &shaping.m_glyphs[fromGlyph], + glyphCount, + &shaping.m_advance[fromGlyph], + justify, + &shaping.m_offsets[fromGlyph]); + if (S_OK != hr && 0 == executions) { + // If this ScriptTextOut is called from the renderer it + // might fail because the sandbox is preventing it from + // opening the font files. If we are running in the + // renderer, TryToPreloadFont is overridden to ask the + // browser to preload the font for us so we can access it. + TryToPreloadFont(shaping.m_hfont); + continue; + } + break; + } + + ASSERT(S_OK == hr); + } + + curX += AdvanceForItem(itemIdx); + } + + if (oldFont) + SelectObject(dc, oldFont); +} + +WORD UniscribeHelper::FirstGlyphForCharacter(int charOffset) const +{ + // Find the run for the given character. + for (int i = 0; i < static_cast<int>(m_runs.size()); i++) { + int firstChar = m_runs[i].iCharPos; + const Shaping& shaping = m_shapes[i]; + int localOffset = charOffset - firstChar; + if (localOffset >= 0 && localOffset < shaping.charLength()) { + // The character is in this run, return the first glyph for it + // (should generally be the only glyph). It seems Uniscribe gives + // glyph 0 for empty, which is what we want to return in the + // "missing" case. + size_t glyphIndex = shaping.m_logs[localOffset]; + if (glyphIndex >= shaping.m_glyphs.size()) { + // The glyph should be in this run, but the run has too few + // actual characters. This can happen when shaping the run + // fails, in which case, we should have no data in the logs at + // all. + ASSERT(shaping.m_glyphs.size() == 0); + return 0; + } + return shaping.m_glyphs[glyphIndex]; + } + } + return 0; +} + +void UniscribeHelper::FillRuns() +{ + HRESULT hr; + m_runs.resize(UNISCRIBE_HELPER_STACK_RUNS); + + SCRIPT_STATE inputState; + inputState.uBidiLevel = m_isRtl; + inputState.fOverrideDirection = m_directionalOverride; + inputState.fInhibitSymSwap = false; + inputState.fCharShape = false; // Not implemented in Uniscribe + inputState.fDigitSubstitute = false; // Do we want this for Arabic? + inputState.fInhibitLigate = m_inhibitLigate; + inputState.fDisplayZWG = false; // Don't draw control characters. + inputState.fArabicNumContext = m_isRtl; // Do we want this for Arabic? + inputState.fGcpClusters = false; + inputState.fReserved = 0; + inputState.fEngineReserved = 0; + // The psControl argument to ScriptItemize should be non-NULL for RTL text, + // per http://msdn.microsoft.com/en-us/library/ms776532.aspx . So use a + // SCRIPT_CONTROL that is set to all zeros. Zero as a locale ID means the + // neutral locale per http://msdn.microsoft.com/en-us/library/ms776294.aspx + static SCRIPT_CONTROL inputControl = {0, // uDefaultLanguage :16; + 0, // fContextDigits :1; + 0, // fInvertPreBoundDir :1; + 0, // fInvertPostBoundDir :1; + 0, // fLinkStringBefore :1; + 0, // fLinkStringAfter :1; + 0, // fNeutralOverride :1; + 0, // fNumericOverride :1; + 0, // fLegacyBidiClass :1; + 0, // fMergeNeutralItems :1; + 0};// fReserved :7; + // Calling ScriptApplyDigitSubstitution( NULL, &inputControl, &inputState) + // here would be appropriate if we wanted to set the language ID, and get + // local digit substitution behavior. For now, don't do it. + + while (true) { + int num_items = 0; + + // Ideally, we would have a way to know the runs before and after this + // one, and put them into the control parameter of ScriptItemize. This + // would allow us to shape characters properly that cross style + // boundaries (WebKit bug 6148). + // + // We tell ScriptItemize that the output list of items is one smaller + // than it actually is. According to Mozilla bug 366643, if there is + // not enough room in the array on pre-SP2 systems, ScriptItemize will + // write one past the end of the buffer. + // + // ScriptItemize is very strange. It will often require a much larger + // ITEM buffer internally than it will give us as output. For example, + // it will say a 16-item buffer is not big enough, and will write + // interesting numbers into all those items. But when we give it a 32 + // item buffer and it succeeds, it only has one item output. + // + // It seems to be doing at least two passes, the first where it puts a + // lot of intermediate data into our items, and the second where it + // collates them. + hr = ScriptItemize(m_input, m_inputLength, + static_cast<int>(m_runs.size()) - 1, &inputControl, + &inputState, + &m_runs[0], &num_items); + if (SUCCEEDED(hr)) { + m_runs.resize(num_items); + break; + } + if (hr != E_OUTOFMEMORY) { + // Some kind of unexpected error. + m_runs.resize(0); + break; + } + // There was not enough items for it to write into, expand. + m_runs.resize(m_runs.size() * 2); + } +} + +bool UniscribeHelper::Shape(const UChar* input, + int itemLength, + int numGlyphs, + SCRIPT_ITEM& run, + Shaping& shaping) +{ + HFONT hfont = m_hfont; + SCRIPT_CACHE* scriptCache = m_scriptCache; + SCRIPT_FONTPROPERTIES* fontProperties = m_fontProperties; + int ascent = m_ascent; + HDC tempDC = NULL; + HGDIOBJ oldFont = 0; + HRESULT hr; + bool lastFallbackTried = false; + bool result; + + int generatedGlyphs = 0; + + // In case HFONT passed in ctor cannot render this run, we have to scan + // other fonts from the beginning of the font list. + ResetFontIndex(); + + // Compute shapes. + while (true) { + shaping.m_logs.resize(itemLength); + shaping.m_glyphs.resize(numGlyphs); + shaping.m_visattr.resize(numGlyphs); + + // Firefox sets SCRIPT_ANALYSIS.SCRIPT_STATE.fDisplayZWG to true + // here. Is that what we want? It will display control characters. + hr = ScriptShape(tempDC, scriptCache, input, itemLength, + numGlyphs, &run.a, + &shaping.m_glyphs[0], &shaping.m_logs[0], + &shaping.m_visattr[0], &generatedGlyphs); + if (hr == E_PENDING) { + // Allocate the DC. + tempDC = GetDC(NULL); + oldFont = SelectObject(tempDC, hfont); + continue; + } else if (hr == E_OUTOFMEMORY) { + numGlyphs *= 2; + continue; + } else if (SUCCEEDED(hr) && + (lastFallbackTried || + !ContainsMissingGlyphs(&shaping.m_glyphs[0], + generatedGlyphs, fontProperties))) { + break; + } + + // The current font can't render this run. clear DC and try + // next font. + if (tempDC) { + SelectObject(tempDC, oldFont); + ReleaseDC(NULL, tempDC); + tempDC = NULL; + } + + if (NextWinFontData(&hfont, &scriptCache, &fontProperties, &ascent)) { + // The primary font does not support this run. Try next font. + // In case of web page rendering, they come from fonts specified in + // CSS stylesheets. + continue; + } else if (!lastFallbackTried) { + lastFallbackTried = true; + + // Generate a last fallback font based on the script of + // a character to draw while inheriting size and styles + // from the primary font + if (!m_logfont.lfFaceName[0]) + SetLogFontAndStyle(m_hfont, &m_logfont, &m_style); + + // TODO(jungshik): generic type should come from webkit for + // UniscribeHelperTextRun (a derived class used in webkit). + const UChar *family = GetFallbackFamily(input, itemLength, + GENERIC_FAMILY_STANDARD, NULL, NULL); + bool font_ok = GetDerivedFontData(family, m_style, &m_logfont, + &ascent, &hfont, &scriptCache); + + if (!font_ok) { + // If this GetDerivedFontData is called from the renderer it + // might fail because the sandbox is preventing it from opening + // the font files. If we are running in the renderer, + // TryToPreloadFont is overridden to ask the browser to preload + // the font for us so we can access it. + TryToPreloadFont(hfont); + + // Try again. + font_ok = GetDerivedFontData(family, m_style, &m_logfont, + &ascent, &hfont, &scriptCache); + ASSERT(font_ok); + } + + // TODO(jungshik) : Currently GetDerivedHFont always returns a + // a valid HFONT, but in the future, I may change it to return 0. + ASSERT(hfont); + + // We don't need a font_properties for the last resort fallback font + // because we don't have anything more to try and are forced to + // accept empty glyph boxes. If we tried a series of fonts as + // 'last-resort fallback', we'd need it, but currently, we don't. + continue; + } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { + run.a.eScript = SCRIPT_UNDEFINED; + continue; + } else if (FAILED(hr)) { + // Error shaping. + generatedGlyphs = 0; + result = false; + goto cleanup; + } + } + + // Sets Windows font data for this run to those corresponding to + // a font supporting this run. we don't need to store font_properties + // because it's not used elsewhere. + shaping.m_hfont = hfont; + shaping.m_scriptCache = scriptCache; + + // The ascent of a font for this run can be different from + // that of the primary font so that we need to keep track of + // the difference per run and take that into account when calling + // ScriptTextOut in |Draw|. Otherwise, different runs rendered by + // different fonts would not be aligned vertically. + shaping.m_ascentOffset = m_ascent ? ascent - m_ascent : 0; + result = true; + + cleanup: + shaping.m_glyphs.resize(generatedGlyphs); + shaping.m_visattr.resize(generatedGlyphs); + shaping.m_advance.resize(generatedGlyphs); + shaping.m_offsets.resize(generatedGlyphs); + if (tempDC) { + SelectObject(tempDC, oldFont); + ReleaseDC(NULL, tempDC); + } + // On failure, our logs don't mean anything, so zero those out. + if (!result) + shaping.m_logs.clear(); + + return result; +} + +void UniscribeHelper::FillShapes() +{ + m_shapes.resize(m_runs.size()); + for (size_t i = 0; i < m_runs.size(); i++) { + int startItem = m_runs[i].iCharPos; + int itemLength = m_inputLength - startItem; + if (i < m_runs.size() - 1) + itemLength = m_runs[i + 1].iCharPos - startItem; + + int numGlyphs; + if (itemLength < UNISCRIBE_HELPER_STACK_CHARS) { + // We'll start our buffer sizes with the current stack space + // available in our buffers if the current input fits. As long as + // it doesn't expand past that we'll save a lot of time mallocing. + numGlyphs = UNISCRIBE_HELPER_STACK_CHARS; + } else { + // When the input doesn't fit, give up with the stack since it will + // almost surely not be enough room (unless the input actually + // shrinks, which is unlikely) and just start with the length + // recommended by the Uniscribe documentation as a "usually fits" + // size. + numGlyphs = itemLength * 3 / 2 + 16; + } + + // Convert a string to a glyph string trying the primary font, fonts in + // the fallback list and then script-specific last resort font. + Shaping& shaping = m_shapes[i]; + if (!Shape(&m_input[startItem], itemLength, numGlyphs, m_runs[i], + shaping)) + continue; + + // Compute placements. Note that offsets is documented incorrectly + // and is actually an array. + + // DC that we lazily create if Uniscribe commands us to. + // (this does not happen often because scriptCache is already + // updated when calling ScriptShape). + HDC tempDC = NULL; + HGDIOBJ oldFont = NULL; + HRESULT hr; + while (true) { + shaping.m_prePadding = 0; + hr = ScriptPlace(tempDC, shaping.m_scriptCache, + &shaping.m_glyphs[0], + static_cast<int>(shaping.m_glyphs.size()), + &shaping.m_visattr[0], &m_runs[i].a, + &shaping.m_advance[0], &shaping.m_offsets[0], + &shaping.m_abc); + if (hr != E_PENDING) + break; + + // Allocate the DC and run the loop again. + tempDC = GetDC(NULL); + oldFont = SelectObject(tempDC, shaping.m_hfont); + } + + if (FAILED(hr)) { + // Some error we don't know how to handle. Nuke all of our data + // since we can't deal with partially valid data later. + m_runs.clear(); + m_shapes.clear(); + m_screenOrder.clear(); + } + + if (tempDC) { + SelectObject(tempDC, oldFont); + ReleaseDC(NULL, tempDC); + } + } + + AdjustSpaceAdvances(); + + if (m_letterSpacing != 0 || m_wordSpacing != 0) + ApplySpacing(); +} + +void UniscribeHelper::FillScreenOrder() +{ + m_screenOrder.resize(m_runs.size()); + + // We assume that the input has only one text direction in it. + // TODO(brettw) are we sure we want to keep this restriction? + if (m_isRtl) { + for (int i = 0; i < static_cast<int>(m_screenOrder.size()); i++) + m_screenOrder[static_cast<int>(m_screenOrder.size()) - i - 1] = i; + } else { + for (int i = 0; i < static_cast<int>(m_screenOrder.size()); i++) + m_screenOrder[i] = i; + } +} + +void UniscribeHelper::AdjustSpaceAdvances() +{ + if (m_spaceWidth == 0) + return; + + int spaceWidthWithoutLetterSpacing = m_spaceWidth - m_letterSpacing; + + // This mostly matches what WebKit's UniscribeController::shapeAndPlaceItem. + for (size_t run = 0; run < m_runs.size(); run++) { + Shaping& shaping = m_shapes[run]; + + for (int i = 0; i < shaping.charLength(); i++) { + if (!TreatAsSpace(m_input[m_runs[run].iCharPos + i])) + continue; + + int glyphIndex = shaping.m_logs[i]; + int currentAdvance = shaping.m_advance[glyphIndex]; + // Don't give zero-width spaces a width. + if (!currentAdvance) + continue; + + // currentAdvance does not include additional letter-spacing, but + // space_width does. Here we find out how off we are from the + // correct width for the space not including letter-spacing, then + // just subtract that diff. + int diff = currentAdvance - spaceWidthWithoutLetterSpacing; + // The shaping can consist of a run of text, so only subtract the + // difference in the width of the glyph. + shaping.m_advance[glyphIndex] -= diff; + shaping.m_abc.abcB -= diff; + } + } +} + +void UniscribeHelper::ApplySpacing() +{ + for (size_t run = 0; run < m_runs.size(); run++) { + Shaping& shaping = m_shapes[run]; + bool isRtl = m_runs[run].a.fRTL; + + if (m_letterSpacing != 0) { + // RTL text gets padded to the left of each character. We increment + // the run's advance to make this happen. This will be balanced out + // by NOT adding additional advance to the last glyph in the run. + if (isRtl) + shaping.m_prePadding += m_letterSpacing; + + // Go through all the glyphs in this run and increase the "advance" + // to account for letter spacing. We adjust letter spacing only on + // cluster boundaries. + // + // This works for most scripts, but may have problems with some + // indic scripts. This behavior is better than Firefox or IE for + // Hebrew. + for (int i = 0; i < shaping.glyphLength(); i++) { + if (shaping.m_visattr[i].fClusterStart) { + // Ick, we need to assign the extra space so that the glyph + // comes first, then is followed by the space. This is + // opposite for RTL. + if (isRtl) { + if (i != shaping.glyphLength() - 1) { + // All but the last character just get the spacing + // applied to their advance. The last character + // doesn't get anything, + shaping.m_advance[i] += m_letterSpacing; + shaping.m_abc.abcB += m_letterSpacing; + } + } else { + // LTR case is easier, we just add to the advance. + shaping.m_advance[i] += m_letterSpacing; + shaping.m_abc.abcB += m_letterSpacing; + } + } + } + } + + // Go through all the characters to find whitespace and insert the + // extra wordspacing amount for the glyphs they correspond to. + if (m_wordSpacing != 0) { + for (int i = 0; i < shaping.charLength(); i++) { + if (!TreatAsSpace(m_input[m_runs[run].iCharPos + i])) + continue; + + // The char in question is a word separator... + int glyphIndex = shaping.m_logs[i]; + + // Spaces will not have a glyph in Uniscribe, it will just add + // additional advance to the character to the left of the + // space. The space's corresponding glyph will be the character + // following it in reading order. + if (isRtl) { + // In RTL, the glyph to the left of the space is the same + // as the first glyph of the following character, so we can + // just increment it. + shaping.m_advance[glyphIndex] += m_wordSpacing; + shaping.m_abc.abcB += m_wordSpacing; + } else { + // LTR is actually more complex here, we apply it to the + // previous character if there is one, otherwise we have to + // apply it to the leading space of the run. + if (glyphIndex == 0) { + shaping.m_prePadding += m_wordSpacing; + } else { + shaping.m_advance[glyphIndex - 1] += m_wordSpacing; + shaping.m_abc.abcB += m_wordSpacing; + } + } + } + } // m_wordSpacing != 0 + + // Loop for next run... + } +} + +// The advance is the ABC width of the run +int UniscribeHelper::AdvanceForItem(int item_index) const +{ + int accum = 0; + const Shaping& shaping = m_shapes[item_index]; + + if (shaping.m_justify.size() == 0) { + // Easy case with no justification, the width is just the ABC width of + // the run. (The ABC width is the sum of the advances). + return shaping.m_abc.abcA + shaping.m_abc.abcB + + shaping.m_abc.abcC + shaping.m_prePadding; + } + + // With justification, we use the justified amounts instead. The + // justification array contains both the advance and the extra space + // added for justification, so is the width we want. + int justification = 0; + for (size_t i = 0; i < shaping.m_justify.size(); i++) + justification += shaping.m_justify[i]; + + return shaping.m_prePadding + justification; +} + +} // namespace WebCore diff --git a/webkit/port/platform/graphics/UniscribeHelper.h b/webkit/port/platform/graphics/UniscribeHelper.h new file mode 100644 index 0000000..27d7961 --- /dev/null +++ b/webkit/port/platform/graphics/UniscribeHelper.h @@ -0,0 +1,380 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A wrapper around Uniscribe that provides a reasonable API. + +#ifndef UniscribeHelper_h +#define UniscribeHelper_h + +#include <windows.h> +#include <usp10.h> +#include <map> + +#include "wtf/Vector.h" + +#include "testing/gtest/include/gtest/gtest_prod.h" +#include "unicode/uchar.h" + +namespace WebCore { + +#define UNISCRIBE_HELPER_STACK_RUNS 8 +#define UNISCRIBE_HELPER_STACK_CHARS 32 + +// This object should be safe to create & destroy frequently, as long as the +// caller preserves the script_cache when possible (this data may be slow to +// compute). +// +// This object is "kind of large" (~1K) because it reserves a lot of space for +// working with to avoid expensive heap operations. Therefore, not only should +// you not worry about creating and destroying it, you should try to not keep +// them around. +class UniscribeHelper { +public: + // Initializes this Uniscribe run with the text pointed to by |run| with + // |length|. The input is NOT null terminated. + // + // The is_rtl flag should be set if the input script is RTL. It is assumed + // that the caller has already divided up the input text (using ICU, for + // example) into runs of the same direction of script. This avoids + // disagreements between the caller and Uniscribe later (see FillItems). + // + // A script cache should be provided by the caller that is initialized to + // NULL. When the caller is done with the cache (it may be stored between + // runs as long as it is used consistently with the same HFONT), it should + // call ScriptFreeCache(). + UniscribeHelper(const UChar* input, + int inputLength, + bool isRtl, + HFONT hfont, + SCRIPT_CACHE* scriptCache, + SCRIPT_FONTPROPERTIES* fontProperties); + + virtual ~UniscribeHelper(); + + // Sets Uniscribe's directional override flag. False by default. + bool directionalOverride() const + { + return m_directionalOverride; + } + void setDirectionalOverride(bool override) + { + m_directionalOverride = override; + } + + // Set's Uniscribe's no-ligate override flag. False by default. + bool inhibitLigate() const + { + return m_inhibitLigate; + } + void setInhibitLigate(bool inhibit) + { + m_inhibitLigate = inhibit; + } + + // Set letter spacing. We will try to insert this much space between + // graphemes (one or more glyphs perceived as a single unit by ordinary + // users of a script). Positive values increase letter spacing, negative + // values decrease it. 0 by default. + int letterSpacing() const + { + return m_letterSpacing; + } + void setLetterSpacing(int letterSpacing) + { + m_letterSpacing = letterSpacing; + } + + // Set the width of a standard space character. We use this to normalize + // space widths. Windows will make spaces after Hindi characters larger than + // other spaces. A space_width of 0 means to use the default space width. + // + // Must be set before Init() is called. + int spaceWidth() const + { + return m_spaceWidth; + } + void setSpaceWidth(int spaceWidth) + { + m_spaceWidth = spaceWidth; + } + + // Set word spacing. We will try to insert this much extra space between + // each word in the input (beyond whatever whitespace character separates + // words). Positive values lead to increased letter spacing, negative values + // decrease it. 0 by default. + // + // Must be set before Init() is called. + int wordSpacing() const + { + return m_wordSpacing; + } + void setWordSpacing(int wordSpacing) + { + m_wordSpacing = wordSpacing; + } + + void setAscent(int ascent) + { + m_ascent = ascent; + } + + // You must call this after setting any options but before doing any + // other calls like asking for widths or drawing. + void Init() + { + InitWithOptionalLengthProtection(true); + } + + // Returns the total width in pixels of the text run. + int Width() const; + + // Call to justify the text, with the amount of space that should be ADDED + // to get the desired width that the column should be justified to. + // Normally, spaces are inserted, but for Arabic there will be kashidas + // (extra strokes) inserted instead. + // + // This function MUST be called AFTER Init(). + void Justify(int additionalSpace); + + // Computes the given character offset into a pixel offset of the beginning + // of that character. + int CharacterToX(int offset) const; + + // Converts the given pixel X position into a logical character offset into + // the run. For positions appearing before the first character, this will + // return -1. + int XToCharacter(int x) const; + + // Draws the given characters to (x, y) in the given DC. The font will be + // handled by this function, but the font color and other attributes should + // be pre-set. + // + // The y position is the upper left corner, NOT the baseline. + void Draw(HDC dc, int x, int y, int from, int to); + + // Returns the first glyph assigned to the character at the given offset. + // This function is used to retrieve glyph information when Uniscribe is + // being used to generate glyphs for non-complex, non-BMP (above U+FFFF) + // characters. These characters are not otherwise special and have no + // complex shaping rules, so we don't otherwise need Uniscribe, except + // Uniscribe is the only way to get glyphs for non-BMP characters. + // + // Returns 0 if there is no glyph for the given character. + WORD FirstGlyphForCharacter(int charOffset) const; + +protected: + // Backend for init. The flag allows the unit test to specify whether we + // should fail early for very long strings like normal, or try to pass the + // long string to Uniscribe. The latter provides a way to force failure of + // shaping. + void InitWithOptionalLengthProtection(bool lengthProtection); + + // Tries to preload the font when the it is not accessible. + // This is the default implementation and it does not do anything. + virtual void TryToPreloadFont(HFONT font) {} + +private: + FRIEND_TEST(UniscribeTest, TooBig); + + // An array corresponding to each item in runs_ containing information + // on each of the glyphs that were generated. Like runs_, this is in + // reading order. However, for rtl text, the characters within each + // item will be reversed. + struct Shaping { + Shaping() + : m_prePadding(0) + , m_hfont(NULL) + , m_scriptCache(NULL) + , m_ascentOffset(0) { + m_abc.abcA = 0; + m_abc.abcB = 0; + m_abc.abcC = 0; + } + + // Returns the number of glyphs (which will be drawn to the screen) + // in this run. + int glyphLength() const + { + return static_cast<int>(m_glyphs.size()); + } + + // Returns the number of characters (that we started with) in this run. + int charLength() const + { + return static_cast<int>(m_logs.size()); + } + + // Returns the advance array that should be used when measuring glyphs. + // The returned pointer will indicate an array with glyph_length() + // elements and the advance that should be used for each one. This is + // either the real advance, or the justified advances if there is one, + // and is the array we want to use for measurement. + const int* effectiveAdvances() const + { + if (m_advance.size() == 0) + return 0; + if (m_justify.size() == 0) + return &m_advance[0]; + return &m_justify[0]; + } + + // This is the advance amount of space that we have added to the + // beginning of the run. It is like the ABC's |A| advance but one that + // we create and must handle internally whenever computing with pixel + // offsets. + int m_prePadding; + + // Glyph indices in the font used to display this item. These indices + // are in screen order. + Vector<WORD, UNISCRIBE_HELPER_STACK_CHARS> m_glyphs; + + // For each input character, this tells us the first glyph index it + // generated. This is the only array with size of the input chars. + // + // All offsets are from the beginning of this run. Multiple characters + // can generate one glyph, in which case there will be adjacent + // duplicates in this list. One character can also generate multiple + // glyphs, in which case there will be skipped indices in this list. + Vector<WORD, UNISCRIBE_HELPER_STACK_CHARS> m_logs; + + // Flags and such for each glyph. + Vector<SCRIPT_VISATTR, UNISCRIBE_HELPER_STACK_CHARS> m_visattr; + + // Horizontal advances for each glyph listed above, this is basically + // how wide each glyph is. + Vector<int, UNISCRIBE_HELPER_STACK_CHARS> m_advance; + + // This contains glyph offsets, from the nominal position of a glyph. + // It is used to adjust the positions of multiple combining characters + // around/above/below base characters in a context-sensitive manner so + // that they don't bump against each other and the base character. + Vector<GOFFSET, UNISCRIBE_HELPER_STACK_CHARS> m_offsets; + + // Filled by a call to Justify, this is empty for nonjustified text. + // If nonempty, this contains the array of justify characters for each + // character as returned by ScriptJustify. + // + // This is the same as the advance array, but with extra space added + // for some characters. The difference between a glyph's |justify| + // width and it's |advance| width is the extra space added. + Vector<int, UNISCRIBE_HELPER_STACK_CHARS> m_justify; + + // Sizing information for this run. This treats the entire run as a + // character with a preceeding advance, width, and ending advance. The + // B width is the sum of the |advance| array, and the A and C widths + // are any extra spacing applied to each end. + // + // It is unclear from the documentation what this actually means. From + // experimentation, it seems that the sum of the character advances is + // always the sum of the ABC values, and I'm not sure what you're + // supposed to do with the ABC values. + ABC m_abc; + + // Pointers to windows font data used to render this run. + HFONT m_hfont; + SCRIPT_CACHE* m_scriptCache; + + // Ascent offset between the ascent of the primary font + // and that of the fallback font. The offset needs to be applied, + // when drawing a string, to align multiple runs rendered with + // different fonts. + int m_ascentOffset; + }; + + // Computes the runs_ array from the text run. + void FillRuns(); + + // Computes the shapes_ array given an runs_ array already filled in. + void FillShapes(); + + // Fills in the screen_order_ array (see below). + void FillScreenOrder(); + + // Called to update the glyph positions based on the current spacing + // options that are set. + void ApplySpacing(); + + // Normalizes all advances for spaces to the same width. This keeps windows + // from making spaces after Hindi characters larger, which is then + // inconsistent with our meaure of the width since WebKit doesn't include + // spaces in text-runs sent to uniscribe unless white-space:pre. + void AdjustSpaceAdvances(); + + // Returns the total width of a single item. + int AdvanceForItem(int item_index) const; + + // Shapes a run (pointed to by |input|) using |hfont| first. + // Tries a series of fonts specified retrieved with NextWinFontData + // and finally a font covering characters in |*input|. A string pointed + // by |input| comes from ScriptItemize and is supposed to contain + // characters belonging to a single script aside from characters common to + // all scripts (e.g. space). + bool Shape(const UChar* input, + int item_length, + int num_glyphs, + SCRIPT_ITEM& run, + Shaping& shaping); + + // Gets Windows font data for the next best font to try in the list + // of fonts. When there's no more font available, returns false + // without touching any of out params. Need to call ResetFontIndex + // to start scanning of the font list from the beginning. + virtual bool NextWinFontData(HFONT* hfont, + SCRIPT_CACHE** script_cache, + SCRIPT_FONTPROPERTIES** font_properties, + int* ascent) { + return false; + } + + // Resets the font index to the first in the list of fonts to try after the + // primaryFont turns out not to work. With fontIndex reset, + // NextWinFontData scans fallback fonts from the beginning. + virtual void ResetFontIndex() {} + + // The input data for this run of Uniscribe. See the constructor. + const UChar* m_input; + const int m_inputLength; + const bool m_isRtl; + + // Windows font data for the primary font. In a sense, m_logfont and m_style + // are redundant because m_hfont contains all the information. However, + // invoking GetObject, everytime we need the height and the style, is rather + // expensive so that we cache them. Would it be better to add getter and + // (virtual) setter for the height and the style of the primary font, + // instead of m_logfont? Then, a derived class ctor can set m_ascent, + // m_height and m_style if they're known. Getters for them would have to + // 'infer' their values from m_hfont ONLY when they're not set. + HFONT m_hfont; + SCRIPT_CACHE* m_scriptCache; + SCRIPT_FONTPROPERTIES* m_fontProperties; + int m_ascent; + LOGFONT m_logfont; + int m_style; + + // Options, see the getters/setters above. + bool m_directionalOverride; + bool m_inhibitLigate; + int m_letterSpacing; + int m_spaceWidth; + int m_wordSpacing; + + // Uniscribe breaks the text into Runs. These are one length of text that is + // in one script and one direction. This array is in reading order. + Vector<SCRIPT_ITEM, UNISCRIBE_HELPER_STACK_RUNS> m_runs; + + Vector<Shaping, UNISCRIBE_HELPER_STACK_RUNS> m_shapes; + + // This is a mapping between reading order and screen order for the items. + // Uniscribe's items array are in reading order. For right-to-left text, + // or mixed (although WebKit's |TextRun| should really be only one + // direction), this makes it very difficult to compute character offsets + // and positions. This list is in screen order from left to right, and + // gives the index into the |m_runs| and |m_shapes| arrays of each + // subsequent item. + Vector<int, UNISCRIBE_HELPER_STACK_RUNS> m_screenOrder; +}; + +} // namespace WebCore + +#endif // UniscribeHelper_h diff --git a/webkit/port/platform/graphics/UniscribeHelperTextRun.cpp b/webkit/port/platform/graphics/UniscribeHelperTextRun.cpp new file mode 100644 index 0000000..93d23e9 --- /dev/null +++ b/webkit/port/platform/graphics/UniscribeHelperTextRun.cpp @@ -0,0 +1,135 @@ +/* + * 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. + * + * 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 "UniscribeHelperTextRun.h" + +#include "ChromiumBridge.h" +#include "Font.h" +#include "SimpleFontData.h" + +namespace WebCore { + +UniscribeHelperTextRun::UniscribeHelperTextRun(const WebCore::TextRun& run, + const WebCore::Font& font) + : UniscribeHelper(run.characters(), run.length(), run.rtl(), + font.primaryFont()->platformData().hfont(), + font.primaryFont()->platformData().scriptCache(), + font.primaryFont()->platformData().scriptFontProperties()) + , m_font(&font) + , m_fontIndex(0) +{ + setDirectionalOverride(run.directionalOverride()); + setLetterSpacing(font.letterSpacing()); + setSpaceWidth(font.spaceWidth()); + setWordSpacing(font.wordSpacing()); + setAscent(font.primaryFont()->ascent()); + + Init(); + + // Padding is the amount to add to make justification happen. This + // should be done after Init() so all the runs are already measured. + if (run.padding() > 0) + Justify(run.padding()); +} + +UniscribeHelperTextRun::UniscribeHelperTextRun( + const wchar_t* input, + int inputLength, + bool isRtl, + HFONT hfont, + SCRIPT_CACHE* scriptCache, + SCRIPT_FONTPROPERTIES* fontProperties) + : UniscribeHelper(input, inputLength, isRtl, hfont, + scriptCache, fontProperties) + , m_font(NULL) + , m_fontIndex(-1) +{ +} + +void UniscribeHelperTextRun::TryToPreloadFont(HFONT font) +{ + // Ask the browser to get the font metrics for this font. + // That will preload the font and it should now be accessible + // from the renderer. + WebCore::ChromiumBridge::ensureFontLoaded(font); +} + +bool UniscribeHelperTextRun::NextWinFontData( + HFONT* hfont, + SCRIPT_CACHE** scriptCache, + SCRIPT_FONTPROPERTIES** fontProperties, + int* ascent) +{ + // This check is necessary because NextWinFontData can be called again + // after we already ran out of fonts. fontDataAt behaves in a strange + // manner when the difference between param passed and # of fonts stored in + // WebKit::Font is larger than one. We can avoid this check by setting + // font_index_ to # of elements in hfonts_ when we run out of font. In that + // case, we'd have to go through a couple of more checks before returning + // false. + if (m_fontIndex == -1 || !m_font) + return false; + + // If the font data for a fallback font requested is not yet retrieved, add + // them to our vectors. Note that '>' rather than '>=' is used to test that + // condition. primaryFont is not stored in hfonts_, and friends so that + // indices for fontDataAt and our vectors for font data are 1 off from each + // other. That is, when fully populated, hfonts_ and friends have one font + // fewer than what's contained in font_. + if (static_cast<size_t>(++m_fontIndex) > m_hfonts.size()) { + const WebCore::FontData *fontData = m_font->fontDataAt(m_fontIndex); + if (!fontData) { + // Ran out of fonts. + m_fontIndex = -1; + return false; + } + + // TODO(ericroman): this won't work for SegmentedFontData + // http://b/issue?id=1007335 + const WebCore::SimpleFontData* simpleFontData = + fontData->fontDataForCharacter(' '); + + m_hfonts.append(simpleFontData->platformData().hfont()); + m_scriptCaches.append( + simpleFontData->platformData().scriptCache()); + m_fontProperties.append( + simpleFontData->platformData().scriptFontProperties()); + m_ascents.append(simpleFontData->ascent()); + } + + *hfont = m_hfonts[m_fontIndex - 1]; + *scriptCache = m_scriptCaches[m_fontIndex - 1]; + *fontProperties = m_fontProperties[m_fontIndex - 1]; + *ascent = m_ascents[m_fontIndex - 1]; + return true; +} + +void UniscribeHelperTextRun::ResetFontIndex() +{ + m_fontIndex = 0; +} + +} // namespace WebCore diff --git a/webkit/port/platform/UniscribeStateTextRun.h b/webkit/port/platform/graphics/UniscribeHelperTextRun.h index 7f6e43a..281e6d9 100644 --- a/webkit/port/platform/UniscribeStateTextRun.h +++ b/webkit/port/platform/graphics/UniscribeHelperTextRun.h @@ -27,76 +27,73 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef UniscribeStateTextRun_H -#define UniscribeStateTextRun_H +#ifndef UniscribeHelperTextRun_h +#define UniscribeHelperTextRun_h -#include "base/gfx/uniscribe.h" +#include "UniscribeHelper.h" namespace WebCore { class Font; class TextRun; -} - -// Wrapper around the Uniscribe state that automatically sets it up with the +// Wrapper around the Uniscribe helper that automatically sets it up with the // WebKit types we supply. -class UniscribeStateTextRun : public gfx::UniscribeState { +class UniscribeHelperTextRun : public UniscribeHelper { public: // Regular constructor used for WebCore text run processing. - UniscribeStateTextRun(const WebCore::TextRun& run, - const WebCore::Font& font); + UniscribeHelperTextRun(const WebCore::TextRun& run, + const WebCore::Font& font); // Constructor with the same interface as the gfx::UniscribeState. Using // this constructor will not give you font fallback, but it will provide // the ability to load fonts that may not be in the OS cache // ("TryToPreloadFont") if the caller does not have a TextRun/Font. - UniscribeStateTextRun(const wchar_t* input, - int input_length, - bool is_rtl, - HFONT hfont, - SCRIPT_CACHE* script_cache, - SCRIPT_FONTPROPERTIES* font_properties); + UniscribeHelperTextRun(const wchar_t* input, + int inputLength, + bool isRtl, + HFONT hfont, + SCRIPT_CACHE* scriptCache, + SCRIPT_FONTPROPERTIES* fontProperties); protected: virtual void TryToPreloadFont(HFONT font); private: - // This function retrieves the Windows font data (HFONT, etc) - // for the next WebKit font in the list. If the font data - // corresponding to font_index_ has been obtained before, - // returns the values stored in our internal vectors (hfonts_, etc). - // Otherwise, it gets next SimpleFontData from WebKit and adds them to - // in hfonts_ and friends so that font data can be returned - // quickly next time they're requested. + // This function retrieves the Windows font data (HFONT, etc) for the next + // WebKit font in the list. If the font data corresponding to font_index_ + // has been obtained before, returns the values stored in our internal + // vectors (hfonts_, etc). Otherwise, it gets next SimpleFontData from + // WebKit and adds them to in hfonts_ and friends so that font data can be + // returned quickly next time they're requested. virtual bool NextWinFontData(HFONT* hfont, - SCRIPT_CACHE** script_cache, - SCRIPT_FONTPROPERTIES** font_properties, + SCRIPT_CACHE** scriptCache, + SCRIPT_FONTPROPERTIES** fontProperties, int* ascent); virtual void ResetFontIndex(); - // Reference to WebKit::Font that contains all the information - // about fonts we can use to render this input run of text. - // It is used in NextWinFontData to retrieve Windows font data - // for a series of non-primary fonts. + // Reference to WebKit::Font that contains all the information about fonts + // we can use to render this input run of text. It is used in + // NextWinFontData to retrieve Windows font data for a series of + // non-primary fonts. // // This pointer can be NULL for no font fallback handling. - const WebCore::Font* font_; + const Font* m_font; // It's rare that many fonts are listed in stylesheets. // Four would be large enough in most cases. const static size_t kNumberOfFonts = 4; - // These vectors are used to store Windows font data for - // non-primary fonts. - StackVector<HFONT, kNumberOfFonts> hfonts_; - StackVector<SCRIPT_CACHE*, kNumberOfFonts> script_caches_; - StackVector<SCRIPT_FONTPROPERTIES*, kNumberOfFonts> font_properties_; - StackVector<int, kNumberOfFonts> ascents_; + // These vectors are used to store Windows font data for non-primary fonts. + Vector<HFONT, kNumberOfFonts> m_hfonts; + Vector<SCRIPT_CACHE*, kNumberOfFonts> m_scriptCaches; + Vector<SCRIPT_FONTPROPERTIES*, kNumberOfFonts> m_fontProperties; + Vector<int, kNumberOfFonts> m_ascents; - // - int font_index_; + // Index of the fallback font we're currently using for NextWinFontData. + int m_fontIndex; }; -#endif // UniscribeStateTextRun_H +} // namespace WebCore +#endif // UniscribeHelperTextRun_h diff --git a/webkit/port/platform/graphics/UniscribeHelper_unittest.cpp b/webkit/port/platform/graphics/UniscribeHelper_unittest.cpp new file mode 100644 index 0000000..d3e8206 --- /dev/null +++ b/webkit/port/platform/graphics/UniscribeHelper_unittest.cpp @@ -0,0 +1,142 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "UniscribeHelper.h" + +#include "PlatformString.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This must be in the gfx namespace for the friend statements in uniscribe.h +// to work. +namespace WebCore { + +namespace { + +class UniscribeTest : public testing::Test { +public: + UniscribeTest() + { + } + + // Returns an HFONT with the given name. The caller does not have to free + // this, it will be automatically freed at the end of the test. Returns NULL + // on failure. On success, the + HFONT MakeFont(const wchar_t* fontName, SCRIPT_CACHE** cache) + { + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + lf.lfHeight = 20; + wcscpy_s(lf.lfFaceName, fontName); + + HFONT hfont = CreateFontIndirect(&lf); + if (!hfont) + return NULL; + + *cache = new SCRIPT_CACHE; + **cache = NULL; + m_createdFonts.append(std::make_pair(hfont, *cache)); + return hfont; + } + +protected: + // Default font properties structure for tests to use. + SCRIPT_FONTPROPERTIES m_properties; + +private: + virtual void SetUp() + { + memset(&m_properties, 0, sizeof(SCRIPT_FONTPROPERTIES)); + m_properties.cBytes = sizeof(SCRIPT_FONTPROPERTIES); + m_properties.wgBlank = ' '; + m_properties.wgDefault = '?'; // Used when the char is not in the font. + m_properties.wgInvalid = '#'; // Used for invalid characters. + } + + virtual void TearDown() + { + // Free any allocated fonts. + for (size_t i = 0; i < m_createdFonts.size(); i++) { + DeleteObject(m_createdFonts[i].first); + ScriptFreeCache(m_createdFonts[i].second); + delete m_createdFonts[i].second; + } + m_createdFonts.clear(); + } + + // Tracks allocated fonts so we can delete them at the end of the test. + // The script cache pointer is heap allocated and must be freed. + Vector< std::pair<HFONT, SCRIPT_CACHE*> > m_createdFonts; +}; + +} // namespace + +// This test tests giving Uniscribe a very large buffer, which will cause a +// failure. +TEST_F(UniscribeTest, TooBig) +{ + // Make a large string with an e with a zillion combining accents. + String input(L"e"); + for (int i = 0; i < 100000; i++) + input.append(static_cast<UChar>(0x301)); // Combining acute accent. + + SCRIPT_CACHE* scriptCache; + HFONT hfont = MakeFont(L"Times New Roman", &scriptCache); + ASSERT_TRUE(hfont); + + // Test a long string without the normal length protection we have. This + // will cause shaping to fail. + { + UniscribeHelper uniscribe( + input.characters(), static_cast<int>(input.length()), + false, hfont, scriptCache, &m_properties); + uniscribe.InitWithOptionalLengthProtection(false); + + // There should be one shaping entry, with nothing in it. + ASSERT_EQ(1, uniscribe.m_shapes.size()); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_glyphs.size()); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_logs.size()); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_visattr.size()); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_advance.size()); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_offsets.size()); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_justify.size()); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_abc.abcA); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_abc.abcB); + EXPECT_EQ(0, uniscribe.m_shapes[0].m_abc.abcC); + + // The sizes of the other stuff should match the shaping entry. + EXPECT_EQ(1, uniscribe.m_runs.size()); + EXPECT_EQ(1, uniscribe.m_screenOrder.size()); + + // Check that the various querying functions handle the empty case + // properly. + EXPECT_EQ(0, uniscribe.Width()); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(0)); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(1000)); + EXPECT_EQ(0, uniscribe.XToCharacter(0)); + EXPECT_EQ(0, uniscribe.XToCharacter(1000)); + } + + // Now test the very large string and make sure it is handled properly by + // the length protection. + { + UniscribeHelper uniscribe( + input.characters(), static_cast<int>(input.length()), + false, hfont, scriptCache, &m_properties); + uniscribe.InitWithOptionalLengthProtection(true); + + // There should be 0 runs and shapes. + EXPECT_EQ(0, uniscribe.m_runs.size()); + EXPECT_EQ(0, uniscribe.m_shapes.size()); + EXPECT_EQ(0, uniscribe.m_screenOrder.size()); + + EXPECT_EQ(0, uniscribe.Width()); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(0)); + EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(1000)); + EXPECT_EQ(0, uniscribe.XToCharacter(0)); + EXPECT_EQ(0, uniscribe.XToCharacter(1000)); + } +} + +} // namespace WebCore diff --git a/webkit/port/rendering/RenderThemeWin.cpp b/webkit/port/rendering/RenderThemeWin.cpp index 74a477b..591a965 100644 --- a/webkit/port/rendering/RenderThemeWin.cpp +++ b/webkit/port/rendering/RenderThemeWin.cpp @@ -31,13 +31,13 @@ #include "CSSValueKeywords.h" #include "Document.h" #include "FontSelector.h" +#include "FontUtilsWin.h" #include "GraphicsContext.h" #include "ScrollbarTheme.h" #include "SkiaUtils.h" #include "ThemeHelperWin.h" #include "base/gfx/native_theme.h" -#include "base/gfx/font_utils.h" #include "base/gfx/skia_utils.h" #include "base/win_util.h" @@ -243,20 +243,22 @@ static float systemFontSize(const LOGFONT& font) // (e.g. 15px). So, for now we just use Arial. static wchar_t* defaultGUIFont(Document* document) { - UScriptCode dominantScript = document->dominantScript(); - const wchar_t* family = NULL; - - // TODO(jungshik) : Special-casing of Latin/Greeek/Cyrillic should go away - // once GetFontFamilyForScript is enhanced to support GenericFamilyType for real. - // For now, we make sure that we use Arial to match IE for those scripts. - if (dominantScript != USCRIPT_LATIN && dominantScript != USCRIPT_CYRILLIC && - dominantScript != USCRIPT_GREEK && dominantScript != USCRIPT_INVALID_CODE) { - family = gfx::GetFontFamilyForScript(dominantScript, - gfx::GENERIC_FAMILY_NONE); - if (family) - return const_cast<wchar_t*>(family); - } - return L"Arial"; + UScriptCode dominantScript = document->dominantScript(); + const wchar_t* family = NULL; + + // TODO(jungshik) : Special-casing of Latin/Greeek/Cyrillic should go away + // once GetFontFamilyForScript is enhanced to support GenericFamilyType for + // real. For now, we make sure that we use Arial to match IE for those + // scripts. + if (dominantScript != USCRIPT_LATIN && + dominantScript != USCRIPT_CYRILLIC && + dominantScript != USCRIPT_GREEK && + dominantScript != USCRIPT_INVALID_CODE) { + family = GetFontFamilyForScript(dominantScript, GENERIC_FAMILY_NONE); + if (family) + return const_cast<wchar_t*>(family); + } + return L"Arial"; } // Converts |points| to pixels. One point is 1/72 of an inch. diff --git a/webkit/tools/test_shell/SConscript b/webkit/tools/test_shell/SConscript index c98ae98..fe4ab1c 100644 --- a/webkit/tools/test_shell/SConscript +++ b/webkit/tools/test_shell/SConscript @@ -176,6 +176,7 @@ test_files = [ '$WEBKIT_DIR/glue/resource_fetcher_unittest.cc', '$WEBKIT_DIR/glue/webframe_unittest.cc', '$WEBKIT_DIR/port/platform/GKURL_unittest.cpp', + '$WEBKIT_DIR/port/platform/graphics/UniscribeHelper_unittest.cpp', '$WEBKIT_DIR/port/platform/image-decoders/bmp/BMPImageDecoder_unittest.cpp', '$WEBKIT_DIR/port/platform/image-decoders/ico/ICOImageDecoder_unittest.cpp', '$WEBKIT_DIR/port/platform/image-decoders/xbm/XBMImageDecoder_unittest.cpp', diff --git a/webkit/tools/test_shell/test_shell_tests.vcproj b/webkit/tools/test_shell/test_shell_tests.vcproj index a8f8d00..c21ea63 100644 --- a/webkit/tools/test_shell/test_shell_tests.vcproj +++ b/webkit/tools/test_shell/test_shell_tests.vcproj @@ -219,10 +219,6 @@ > </File> <File - RelativePath=".\test_shell_win.cc" - > - </File> - <File RelativePath=".\resources\test_shell.ico" > </File> @@ -255,6 +251,10 @@ > </File> <File + RelativePath=".\test_shell_win.cc" + > + </File> + <File RelativePath=".\test_webview_delegate.cc" > </File> @@ -383,6 +383,10 @@ > </File> <File + RelativePath="..\..\port\platform\graphics\UniscribeHelper_unittest.cpp" + > + </File> + <File RelativePath="..\..\glue\webframe_unittest.cc" > </File> |