зеркало из https://github.com/mozilla/pjs.git
bug 502906 - part 3 - factor out Uniscribe and GDI shapers from Windows GDI font code. r=jdaggett
--HG-- rename : gfx/thebes/src/gfxWindowsFonts.cpp => gfx/thebes/src/gfxUniscribeShaper.cpp rename : gfx/thebes/public/gfxWindowsFonts.h => gfx/thebes/src/gfxUniscribeShaper.h
This commit is contained in:
Родитель
5975e1fa49
Коммит
544fbd0636
|
@ -51,7 +51,6 @@ EXPORTS += gfxFT2Fonts.h \
|
|||
gfxDDrawSurface.h \
|
||||
$(NULL)
|
||||
else
|
||||
EXPORTS += gfxWindowsFonts.h
|
||||
EXPORTS += gfxDWriteFonts.h
|
||||
EXPORTS += gfxD2DSurface.h
|
||||
endif
|
||||
|
|
|
@ -660,11 +660,11 @@ public:
|
|||
|
||||
virtual ~gfxFontShaper() { }
|
||||
|
||||
virtual void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength) = 0;
|
||||
virtual PRBool InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength) = 0;
|
||||
|
||||
protected:
|
||||
// the font this shaper is working with
|
||||
|
@ -697,6 +697,14 @@ public:
|
|||
|
||||
PRInt32 GetRefCount() { return mRefCnt; }
|
||||
|
||||
// options to specify the kind of AA to be used when creating a font
|
||||
typedef enum {
|
||||
kAntialiasDefault,
|
||||
kAntialiasNone,
|
||||
kAntialiasGrayscale,
|
||||
kAntialiasSubpixel
|
||||
} AntialiasOption;
|
||||
|
||||
protected:
|
||||
nsAutoRefCnt mRefCnt;
|
||||
|
||||
|
@ -712,7 +720,8 @@ protected:
|
|||
}
|
||||
}
|
||||
|
||||
gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle);
|
||||
gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
AntialiasOption anAAOption = kAntialiasDefault);
|
||||
|
||||
public:
|
||||
virtual ~gfxFont();
|
||||
|
@ -753,6 +762,11 @@ public:
|
|||
|
||||
virtual nsString GetUniqueName() { return GetName(); }
|
||||
|
||||
virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) {
|
||||
// platforms where this actually matters should override
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
// Font metrics
|
||||
struct Metrics {
|
||||
gfxFloat xHeight;
|
||||
|
@ -910,17 +924,14 @@ public:
|
|||
return mFontEntry->HasCharacter(ch);
|
||||
}
|
||||
|
||||
void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength) {
|
||||
NS_ASSERTION(mShaper != nsnull, "no shaper?!");
|
||||
if (!mShaper) {
|
||||
return;
|
||||
}
|
||||
mShaper->InitTextRun(aContext, aTextRun, aString, aRunStart, aRunLength);
|
||||
}
|
||||
// Default implementation simply calls mShaper->InitTextRun().
|
||||
// Override if the font class wants to give special handling
|
||||
// to shaper failure.
|
||||
virtual void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
|
||||
protected:
|
||||
nsRefPtr<gfxFontEntry> mFontEntry;
|
||||
|
@ -933,6 +944,9 @@ protected:
|
|||
// synthetic bolding for environments where this is not supported by the platform
|
||||
PRUint32 mSyntheticBoldOffset; // number of devunit pixels to offset double-strike, 0 ==> no bolding
|
||||
|
||||
// the AA setting requested for this font - may affect glyph bounds
|
||||
AntialiasOption mAntialiasOption;
|
||||
|
||||
nsAutoPtr<gfxFontShaper> mShaper;
|
||||
|
||||
// some fonts have bad metrics, this method sanitize them.
|
||||
|
|
|
@ -1,205 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Foundation code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2005
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Stuart Parmenter <stuart@mozilla.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef GFX_WINDOWSFONTS_H
|
||||
#define GFX_WINDOWSFONTS_H
|
||||
|
||||
#include "prtypes.h"
|
||||
#include "gfxTypes.h"
|
||||
#include "gfxColor.h"
|
||||
#include "gfxFont.h"
|
||||
#include "gfxMatrix.h"
|
||||
#include "gfxFontUtils.h"
|
||||
#include "gfxUserFontSet.h"
|
||||
|
||||
#include "nsDataHashtable.h"
|
||||
|
||||
#include <usp10.h>
|
||||
#include <cairo-win32.h>
|
||||
|
||||
class GDIFontEntry;
|
||||
|
||||
/**********************************************************************
|
||||
*
|
||||
* class gfxWindowsFont
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
class gfxWindowsFont : public gfxFont {
|
||||
public:
|
||||
gfxWindowsFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
cairo_antialias_t anAntialiasOption = CAIRO_ANTIALIAS_DEFAULT);
|
||||
virtual ~gfxWindowsFont();
|
||||
|
||||
virtual const gfxFont::Metrics& GetMetrics();
|
||||
|
||||
HFONT GetHFONT() { return mFont; }
|
||||
cairo_font_face_t *CairoFontFace();
|
||||
cairo_scaled_font_t *CairoScaledFont();
|
||||
SCRIPT_CACHE *ScriptCache() { return &mScriptCache; }
|
||||
gfxFloat GetAdjustedSize() { MakeHFONT(); return mAdjustedSize; }
|
||||
|
||||
virtual nsString GetUniqueName();
|
||||
|
||||
virtual void Draw(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd,
|
||||
gfxContext *aContext, PRBool aDrawToPath, gfxPoint *aBaselineOrigin,
|
||||
Spacing *aSpacing);
|
||||
|
||||
virtual RunMetrics Measure(gfxTextRun *aTextRun,
|
||||
PRUint32 aStart, PRUint32 aEnd,
|
||||
BoundingBoxType aBoundingBoxType,
|
||||
gfxContext *aContextForTightBoundingBox,
|
||||
Spacing *aSpacing);
|
||||
|
||||
virtual PRUint32 GetSpaceGlyph() {
|
||||
GetMetrics(); // ensure that the metrics are computed but don't recompute them
|
||||
return mSpaceGlyph;
|
||||
};
|
||||
|
||||
PRBool IsValid() { GetMetrics(); return mIsValid; }
|
||||
GDIFontEntry *GetFontEntry();
|
||||
|
||||
virtual void InitTextRun(gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
|
||||
static already_AddRefed<gfxWindowsFont>
|
||||
GetOrMakeFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle,
|
||||
PRBool aNeedsBold = PR_FALSE);
|
||||
|
||||
protected:
|
||||
HFONT MakeHFONT();
|
||||
void FillLogFont(gfxFloat aSize);
|
||||
|
||||
HFONT mFont;
|
||||
gfxFloat mAdjustedSize;
|
||||
PRUint32 mSpaceGlyph;
|
||||
|
||||
private:
|
||||
void ComputeMetrics();
|
||||
|
||||
SCRIPT_CACHE mScriptCache;
|
||||
|
||||
cairo_font_face_t *mFontFace;
|
||||
cairo_scaled_font_t *mScaledFont;
|
||||
|
||||
gfxFont::Metrics *mMetrics;
|
||||
|
||||
LOGFONTW mLogFont;
|
||||
|
||||
cairo_antialias_t mAntialiasOption;
|
||||
|
||||
virtual PRBool SetupCairoFont(gfxContext *aContext);
|
||||
};
|
||||
|
||||
/**********************************************************************
|
||||
*
|
||||
* class gfxWindowsFontGroup
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
class THEBES_API gfxWindowsFontGroup : public gfxFontGroup {
|
||||
|
||||
public:
|
||||
gfxWindowsFontGroup(const nsAString& aFamilies, const gfxFontStyle* aStyle, gfxUserFontSet *aUserFontSet);
|
||||
virtual ~gfxWindowsFontGroup();
|
||||
|
||||
virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle);
|
||||
|
||||
virtual gfxTextRun *MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
|
||||
const Parameters* aParams, PRUint32 aFlags);
|
||||
virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength,
|
||||
const Parameters* aParams, PRUint32 aFlags);
|
||||
|
||||
const nsACString& GetGenericFamily() const {
|
||||
return mGenericFamily;
|
||||
}
|
||||
|
||||
void GroupFamilyListToArrayList(nsTArray<nsRefPtr<gfxFontEntry> > *list,
|
||||
nsTArray<PRPackedBool> *aNeedsBold);
|
||||
void FamilyListToArrayList(const nsString& aFamilies,
|
||||
nsIAtom *aLangGroup,
|
||||
nsTArray<nsRefPtr<gfxFontEntry> > *list);
|
||||
|
||||
virtual void UpdateFontList();
|
||||
virtual gfxFloat GetUnderlineOffset();
|
||||
|
||||
gfxWindowsFont* GetFontAt(PRInt32 aFontIndex) {
|
||||
// If it turns out to be hard for all clients that cache font
|
||||
// groups to call UpdateFontList at appropriate times, we could
|
||||
// instead consider just calling UpdateFontList from someplace
|
||||
// more central (such as here).
|
||||
NS_ASSERTION(!mUserFontSet || mCurrGeneration == GetGeneration(),
|
||||
"Whoever was caching this font group should have "
|
||||
"called UpdateFontList on it");
|
||||
|
||||
return static_cast<gfxWindowsFont*>(static_cast<gfxFont*>(mFonts[aFontIndex]));
|
||||
}
|
||||
|
||||
protected:
|
||||
void InitFontList();
|
||||
void InitTextRunGDI(gfxContext *aContext, gfxTextRun *aRun, const char *aString, PRUint32 aLength);
|
||||
void InitTextRunGDI(gfxContext *aContext, gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength);
|
||||
|
||||
void InitTextRunUniscribe(gfxContext *aContext, gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength);
|
||||
|
||||
already_AddRefed<gfxFont> WhichPrefFontSupportsChar(PRUint32 aCh);
|
||||
already_AddRefed<gfxFont> WhichSystemFontSupportsChar(PRUint32 aCh);
|
||||
|
||||
already_AddRefed<gfxWindowsFont> WhichFontSupportsChar(const nsTArray<nsRefPtr<gfxFontEntry> >& fonts, PRUint32 ch);
|
||||
void GetPrefFonts(nsIAtom *aLangGroup, nsTArray<nsRefPtr<gfxFontEntry> >& array);
|
||||
void GetCJKPrefFonts(nsTArray<nsRefPtr<gfxFontEntry> >& array);
|
||||
|
||||
static PRBool FindWindowsFont(const nsAString& aName,
|
||||
const nsACString& aGenericName,
|
||||
void *closure);
|
||||
|
||||
PRBool HasFont(gfxFontEntry *aFontEntry);
|
||||
|
||||
private:
|
||||
|
||||
nsCString mGenericFamily;
|
||||
nsTArray<PRPackedBool> mFontNeedsBold;
|
||||
|
||||
const char *mItemLangGroup; // used by pref-lang handling code
|
||||
|
||||
};
|
||||
|
||||
#endif /* GFX_WINDOWSFONTS_H */
|
|
@ -55,12 +55,12 @@
|
|||
#ifdef MOZ_FT2_FONTS
|
||||
#include "gfxFT2Fonts.h"
|
||||
#else
|
||||
#include "gfxWindowsFonts.h"
|
||||
#ifdef CAIRO_HAS_DWRITE_FONT
|
||||
#include "gfxDWriteFonts.h"
|
||||
#endif
|
||||
#endif
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxContext.h"
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "nsDataHashtable.h"
|
||||
|
@ -71,6 +71,40 @@ typedef struct FT_LibraryRec_ *FT_Library;
|
|||
|
||||
#include <windows.h>
|
||||
|
||||
// Utility to get a Windows HDC from a thebes context,
|
||||
// used by both GDI and Uniscribe font shapers
|
||||
struct DCFromContext {
|
||||
DCFromContext(gfxContext *aContext) {
|
||||
dc = NULL;
|
||||
nsRefPtr<gfxASurface> aSurface = aContext->CurrentSurface();
|
||||
NS_ASSERTION(aSurface, "DCFromContext: null surface");
|
||||
if (aSurface &&
|
||||
(aSurface->GetType() == gfxASurface::SurfaceTypeWin32 ||
|
||||
aSurface->GetType() == gfxASurface::SurfaceTypeWin32Printing))
|
||||
{
|
||||
dc = static_cast<gfxWindowsSurface*>(aSurface.get())->GetDC();
|
||||
needsRelease = PR_FALSE;
|
||||
}
|
||||
if (!dc) {
|
||||
dc = GetDC(NULL);
|
||||
SetGraphicsMode(dc, GM_ADVANCED);
|
||||
needsRelease = PR_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
~DCFromContext() {
|
||||
if (needsRelease)
|
||||
ReleaseDC(NULL, dc);
|
||||
}
|
||||
|
||||
operator HDC () {
|
||||
return dc;
|
||||
}
|
||||
|
||||
HDC dc;
|
||||
PRBool needsRelease;
|
||||
};
|
||||
|
||||
class THEBES_API gfxWindowsPlatform : public gfxPlatform {
|
||||
public:
|
||||
gfxWindowsPlatform();
|
||||
|
@ -147,10 +181,6 @@ public:
|
|||
*/
|
||||
virtual PRBool IsFontFormatSupported(nsIURI *aFontURI, PRUint32 aFormatFlags);
|
||||
|
||||
#ifndef MOZ_FT2_FONTS
|
||||
virtual void SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString);
|
||||
#endif
|
||||
|
||||
/* Find a FontFamily/FontEntry object that represents a font on your system given a name */
|
||||
gfxFontFamily *FindFontFamily(const nsAString& aName);
|
||||
gfxFontEntry *FindFontEntry(const nsAString& aName, const gfxFontStyle& aFontStyle);
|
||||
|
|
|
@ -77,8 +77,10 @@ CPPSRCS += gfxDWriteFonts.cpp \
|
|||
gfxDWriteFontList.cpp \
|
||||
$(NULL)
|
||||
endif
|
||||
CPPSRCS += gfxWindowsFonts.cpp \
|
||||
CPPSRCS += gfxGDIFont.cpp \
|
||||
gfxGDIFontList.cpp \
|
||||
gfxGDIShaper.cpp \
|
||||
gfxUniscribeShaper.cpp \
|
||||
$(NULL)
|
||||
_OS_LIBS = usp10 msimg32
|
||||
endif
|
||||
|
|
|
@ -102,7 +102,7 @@ gfxCoreTextShaper::~gfxCoreTextShaper()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
PRBool
|
||||
gfxCoreTextShaper::InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
|
@ -203,13 +203,19 @@ gfxCoreTextShaper::InitTextRun(gfxContext *aContext,
|
|||
// Iterate through the glyph runs.
|
||||
// Note that this includes the bidi wrapper, so we have to be careful
|
||||
// not to include the extra glyphs from there
|
||||
PRBool success = PR_TRUE;
|
||||
for (PRUint32 runIndex = 0; runIndex < numRuns; runIndex++) {
|
||||
CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
|
||||
if (SetGlyphsFromRun(aTextRun, aCTRun, startOffset, aRunStart, aRunLength) != NS_OK)
|
||||
if (SetGlyphsFromRun(aTextRun, aCTRun, startOffset,
|
||||
aRunStart, aRunLength) != NS_OK) {
|
||||
success = PR_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
::CFRelease(line);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
#define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data;
|
||||
|
|
|
@ -57,11 +57,11 @@ public:
|
|||
|
||||
virtual ~gfxCoreTextShaper();
|
||||
|
||||
virtual void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
virtual PRBool InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
|
||||
// clean up static objects that may have been cached
|
||||
static void Shutdown();
|
||||
|
|
|
@ -807,9 +807,11 @@ gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, PRBool aOtherIsOnLeft
|
|||
mAdvanceWidth += aOther.mAdvanceWidth;
|
||||
}
|
||||
|
||||
gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle) :
|
||||
gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
AntialiasOption anAAOption) :
|
||||
mFontEntry(aFontEntry), mIsValid(PR_TRUE),
|
||||
mStyle(*aFontStyle), mSyntheticBoldOffset(0),
|
||||
mAntialiasOption(anAAOption),
|
||||
mShaper(nsnull)
|
||||
{
|
||||
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
||||
|
@ -1062,9 +1064,26 @@ NeedsGlyphExtents(gfxTextRun *aTextRun)
|
|||
gfxFont::RunMetrics
|
||||
gfxFont::Measure(gfxTextRun *aTextRun,
|
||||
PRUint32 aStart, PRUint32 aEnd,
|
||||
BoundingBoxType aBoundingBoxType, gfxContext *aRefContext,
|
||||
BoundingBoxType aBoundingBoxType,
|
||||
gfxContext *aRefContext,
|
||||
Spacing *aSpacing)
|
||||
{
|
||||
// If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
|
||||
// and the underlying cairo font may be antialiased,
|
||||
// we need to create a copy in order to avoid getting cached extents.
|
||||
// This is inefficient, but only used by MathML layout at present.
|
||||
if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS &&
|
||||
mAntialiasOption != kAntialiasNone) {
|
||||
nsAutoPtr<gfxFont> tempFont(CopyWithAntialiasOption(kAntialiasNone));
|
||||
// if font subclass doesn't implement CopyWithAntialiasOption(),
|
||||
// it will return null and we'll proceed to use the existing font
|
||||
if (tempFont) {
|
||||
return tempFont->Measure(aTextRun, aStart, aEnd,
|
||||
TIGHT_HINTED_OUTLINE_EXTENTS,
|
||||
aRefContext, aSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
|
||||
// Current position in appunits
|
||||
const gfxFont::Metrics& fontMetrics = GetMetrics();
|
||||
|
@ -1171,6 +1190,23 @@ gfxFont::Measure(gfxTextRun *aTextRun,
|
|||
return metrics;
|
||||
}
|
||||
|
||||
void
|
||||
gfxFont::InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength)
|
||||
{
|
||||
NS_ASSERTION(mShaper != nsnull, "no shaper?!");
|
||||
if (!mShaper) {
|
||||
return;
|
||||
}
|
||||
|
||||
PRBool ok = mShaper->InitTextRun(aContext, aTextRun, aString,
|
||||
aRunStart, aRunLength);
|
||||
NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
|
||||
}
|
||||
|
||||
gfxGlyphExtents *
|
||||
gfxFont::GetOrCreateGlyphExtents(PRUint32 aAppUnitsPerDevUnit) {
|
||||
PRUint32 i;
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Foundation code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2005-2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Stuart Parmenter <stuart@mozilla.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* Mats Palmgren <mats.palmgren@bredband.net>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
* Jonathan Kew <jfkthame@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "gfxGDIFont.h"
|
||||
#include "gfxGDIShaper.h"
|
||||
#include "gfxUniscribeShaper.h"
|
||||
#include "gfxWindowsPlatform.h"
|
||||
#include "gfxContext.h"
|
||||
|
||||
#include "cairo-win32.h"
|
||||
|
||||
#define ROUND(x) floor((x) + 0.5)
|
||||
|
||||
static inline cairo_antialias_t
|
||||
GetCairoAntialiasOption(gfxFont::AntialiasOption anAntialiasOption)
|
||||
{
|
||||
switch (anAntialiasOption) {
|
||||
default:
|
||||
case gfxFont::kAntialiasDefault:
|
||||
return CAIRO_ANTIALIAS_DEFAULT;
|
||||
case gfxFont::kAntialiasNone:
|
||||
return CAIRO_ANTIALIAS_NONE;
|
||||
case gfxFont::kAntialiasGrayscale:
|
||||
return CAIRO_ANTIALIAS_GRAY;
|
||||
case gfxFont::kAntialiasSubpixel:
|
||||
return CAIRO_ANTIALIAS_SUBPIXEL;
|
||||
}
|
||||
}
|
||||
|
||||
gfxGDIFont::gfxGDIFont(GDIFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
PRBool aNeedsBold,
|
||||
AntialiasOption anAAOption)
|
||||
: gfxFont(aFontEntry, aFontStyle, anAAOption),
|
||||
mNeedsBold(aNeedsBold),
|
||||
mFont(NULL),
|
||||
mFontFace(nsnull),
|
||||
mScaledFont(nsnull),
|
||||
mAdjustedSize(0.0)
|
||||
{
|
||||
// InitMetrics will handle the sizeAdjust factor and set mAdjustedSize,
|
||||
// fill in our mLogFont structure and create mFont
|
||||
InitMetrics();
|
||||
if (!mIsValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFontFace = cairo_win32_font_face_create_for_logfontw_hfont(&mLogFont,
|
||||
mFont);
|
||||
|
||||
cairo_matrix_t sizeMatrix, ctm;
|
||||
cairo_matrix_init_identity(&ctm);
|
||||
cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
|
||||
|
||||
cairo_font_options_t *fontOptions = cairo_font_options_create();
|
||||
if (anAAOption != kAntialiasDefault) {
|
||||
cairo_font_options_set_antialias(fontOptions,
|
||||
GetCairoAntialiasOption(anAAOption));
|
||||
}
|
||||
mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix,
|
||||
&ctm, fontOptions);
|
||||
cairo_font_options_destroy(fontOptions);
|
||||
|
||||
cairo_status_t cairoerr = cairo_scaled_font_status(mScaledFont);
|
||||
if (cairoerr != CAIRO_STATUS_SUCCESS) {
|
||||
mIsValid = PR_FALSE;
|
||||
#ifdef DEBUG
|
||||
char warnBuf[1024];
|
||||
sprintf(warnBuf, "Failed to create scaled font: %s status: %d",
|
||||
NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(), cairoerr);
|
||||
NS_WARNING(warnBuf);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (aFontEntry->mForceGDI) {
|
||||
mShaper = new gfxGDIShaper(this);
|
||||
} else {
|
||||
mShaper = new gfxUniscribeShaper(this);
|
||||
}
|
||||
}
|
||||
|
||||
gfxGDIFont::~gfxGDIFont()
|
||||
{
|
||||
if (mScaledFont) {
|
||||
cairo_scaled_font_destroy(mScaledFont);
|
||||
}
|
||||
if (mFontFace) {
|
||||
cairo_font_face_destroy(mFontFace);
|
||||
}
|
||||
if (mFont) {
|
||||
::DeleteObject(mFont);
|
||||
}
|
||||
}
|
||||
|
||||
gfxFont*
|
||||
gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
|
||||
{
|
||||
return new gfxGDIFont(static_cast<GDIFontEntry*>(mFontEntry.get()),
|
||||
&mStyle, mNeedsBold, anAAOption);
|
||||
}
|
||||
|
||||
void
|
||||
gfxGDIFont::InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength)
|
||||
{
|
||||
PRBool ok = mShaper->InitTextRun(aContext, aTextRun, aString,
|
||||
aRunStart, aRunLength);
|
||||
if (!ok) {
|
||||
// shaping failed; if we were using uniscribe, fall back to GDI
|
||||
GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
|
||||
if (!fe->mForceGDI) {
|
||||
NS_WARNING("uniscribe failed, switching to GDI shaper");
|
||||
fe->mForceGDI = PR_TRUE;
|
||||
mShaper = new gfxGDIShaper(this);
|
||||
ok = mShaper->InitTextRun(aContext, aTextRun, aString,
|
||||
aRunStart, aRunLength);
|
||||
}
|
||||
}
|
||||
NS_WARN_IF_FALSE(ok, "shaper failed, expect broken or missing text");
|
||||
}
|
||||
|
||||
const gfxFont::Metrics&
|
||||
gfxGDIFont::GetMetrics()
|
||||
{
|
||||
return mMetrics;
|
||||
}
|
||||
|
||||
PRUint32
|
||||
gfxGDIFont::GetSpaceGlyph()
|
||||
{
|
||||
return mSpaceGlyph;
|
||||
}
|
||||
|
||||
PRBool
|
||||
gfxGDIFont::SetupCairoFont(gfxContext *aContext)
|
||||
{
|
||||
if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
|
||||
// Don't cairo_set_scaled_font as that would propagate the error to
|
||||
// the cairo_t, precluding any further drawing.
|
||||
return PR_FALSE;
|
||||
}
|
||||
cairo_set_scaled_font(aContext->GetCairo(), mScaledFont);
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
gfxGDIFont::InitMetrics()
|
||||
{
|
||||
if (mAdjustedSize == 0.0) {
|
||||
mAdjustedSize = mStyle.size;
|
||||
if (mStyle.sizeAdjust != 0.0 && mAdjustedSize > 0.0) {
|
||||
// to implement font-size-adjust, we first create the "unadjusted" font
|
||||
FillLogFont(mAdjustedSize);
|
||||
mFont = ::CreateFontIndirectW(&mLogFont);
|
||||
|
||||
// initialize its metrics, then delete the font
|
||||
InitMetrics();
|
||||
::DeleteObject(mFont);
|
||||
mFont = nsnull;
|
||||
|
||||
// calculate the properly adjusted size, and then proceed
|
||||
gfxFloat aspect = mMetrics.xHeight / mMetrics.emHeight;
|
||||
mAdjustedSize = mStyle.GetAdjustedSize(aspect);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mFont) {
|
||||
FillLogFont(mAdjustedSize);
|
||||
mFont = ::CreateFontIndirectW(&mLogFont);
|
||||
}
|
||||
|
||||
AutoDC dc;
|
||||
SetGraphicsMode(dc.GetDC(), GM_ADVANCED);
|
||||
AutoSelectFont selectFont(dc.GetDC(), mFont);
|
||||
|
||||
// Get font metrics
|
||||
OUTLINETEXTMETRIC oMetrics;
|
||||
TEXTMETRIC& metrics = oMetrics.otmTextMetrics;
|
||||
|
||||
if (0 < GetOutlineTextMetrics(dc.GetDC(), sizeof(oMetrics), &oMetrics)) {
|
||||
mMetrics.superscriptOffset = (double)oMetrics.otmptSuperscriptOffset.y;
|
||||
// Some fonts have wrong sign on their subscript offset, bug 410917.
|
||||
mMetrics.subscriptOffset = fabs((double)oMetrics.otmptSubscriptOffset.y);
|
||||
mMetrics.strikeoutSize = (double)oMetrics.otmsStrikeoutSize;
|
||||
mMetrics.strikeoutOffset = (double)oMetrics.otmsStrikeoutPosition;
|
||||
mMetrics.underlineSize = (double)oMetrics.otmsUnderscoreSize;
|
||||
mMetrics.underlineOffset = (double)oMetrics.otmsUnderscorePosition;
|
||||
|
||||
const MAT2 kIdentityMatrix = { {0, 1}, {0, 0}, {0, 0}, {0, 1} };
|
||||
GLYPHMETRICS gm;
|
||||
DWORD len = GetGlyphOutlineW(dc.GetDC(), PRUnichar('x'), GGO_METRICS, &gm, 0, nsnull, &kIdentityMatrix);
|
||||
if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) {
|
||||
// 56% of ascent, best guess for true type
|
||||
mMetrics.xHeight = ROUND((double)metrics.tmAscent * 0.56);
|
||||
} else {
|
||||
mMetrics.xHeight = gm.gmptGlyphOrigin.y;
|
||||
}
|
||||
mMetrics.emHeight = metrics.tmHeight - metrics.tmInternalLeading;
|
||||
gfxFloat typEmHeight = (double)oMetrics.otmAscent - (double)oMetrics.otmDescent;
|
||||
mMetrics.emAscent = ROUND(mMetrics.emHeight * (double)oMetrics.otmAscent / typEmHeight);
|
||||
mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
|
||||
} else {
|
||||
// Make a best-effort guess at extended metrics
|
||||
// this is based on general typographic guidelines
|
||||
|
||||
// GetTextMetrics can fail if the font file has been removed
|
||||
// or corrupted recently.
|
||||
BOOL result = GetTextMetrics(dc.GetDC(), &metrics);
|
||||
if (!result) {
|
||||
NS_WARNING("Missing or corrupt font data, fasten your seatbelt");
|
||||
mIsValid = PR_FALSE;
|
||||
memset(&mMetrics, 0, sizeof(mMetrics));
|
||||
return;
|
||||
}
|
||||
|
||||
mMetrics.xHeight = ROUND((float)metrics.tmAscent * 0.56f); // 56% of ascent, best guess for non-true type
|
||||
mMetrics.superscriptOffset = mMetrics.xHeight;
|
||||
mMetrics.subscriptOffset = mMetrics.xHeight;
|
||||
mMetrics.strikeoutSize = 1;
|
||||
mMetrics.strikeoutOffset = ROUND(mMetrics.xHeight / 2.0f); // 50% of xHeight
|
||||
mMetrics.underlineSize = 1;
|
||||
mMetrics.underlineOffset = -ROUND((float)metrics.tmDescent * 0.30f); // 30% of descent
|
||||
mMetrics.emHeight = metrics.tmHeight - metrics.tmInternalLeading;
|
||||
mMetrics.emAscent = metrics.tmAscent - metrics.tmInternalLeading;
|
||||
mMetrics.emDescent = metrics.tmDescent;
|
||||
}
|
||||
|
||||
mMetrics.internalLeading = metrics.tmInternalLeading;
|
||||
mMetrics.externalLeading = metrics.tmExternalLeading;
|
||||
mMetrics.maxHeight = metrics.tmHeight;
|
||||
mMetrics.maxAscent = metrics.tmAscent;
|
||||
mMetrics.maxDescent = metrics.tmDescent;
|
||||
mMetrics.maxAdvance = metrics.tmMaxCharWidth;
|
||||
mMetrics.aveCharWidth = PR_MAX(1, metrics.tmAveCharWidth);
|
||||
// The font is monospace when TMPF_FIXED_PITCH is *not* set!
|
||||
// See http://msdn2.microsoft.com/en-us/library/ms534202(VS.85).aspx
|
||||
if (!(metrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
|
||||
mMetrics.maxAdvance = mMetrics.aveCharWidth;
|
||||
}
|
||||
|
||||
// Cache the width of a single space.
|
||||
SIZE size;
|
||||
GetTextExtentPoint32W(dc.GetDC(), L" ", 1, &size);
|
||||
mMetrics.spaceWidth = ROUND(size.cx);
|
||||
|
||||
// Cache the width of digit zero.
|
||||
// XXX MSDN (http://msdn.microsoft.com/en-us/library/ms534223.aspx)
|
||||
// does not say what the failure modes for GetTextExtentPoint32 are -
|
||||
// is it safe to assume it will fail iff the font has no '0'?
|
||||
if (GetTextExtentPoint32W(dc.GetDC(), L"0", 1, &size))
|
||||
mMetrics.zeroOrAveCharWidth = ROUND(size.cx);
|
||||
else
|
||||
mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
|
||||
|
||||
mSpaceGlyph = 0;
|
||||
if (metrics.tmPitchAndFamily & TMPF_TRUETYPE) {
|
||||
WORD glyph;
|
||||
DWORD ret = GetGlyphIndicesW(dc.GetDC(), L" ", 1, &glyph,
|
||||
GGI_MARK_NONEXISTING_GLYPHS);
|
||||
if (ret != GDI_ERROR && glyph != 0xFFFF) {
|
||||
mSpaceGlyph = glyph;
|
||||
}
|
||||
}
|
||||
|
||||
SanitizeMetrics(&mMetrics, GetFontEntry()->mIsBadUnderlineFont);
|
||||
mIsValid = PR_TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
gfxGDIFont::FillLogFont(gfxFloat aSize)
|
||||
{
|
||||
GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
|
||||
|
||||
PRUint16 weight = mNeedsBold ? 700 : fe->Weight();
|
||||
PRBool italic = (mStyle.style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE));
|
||||
|
||||
// if user font, disable italics/bold if defined to be italics/bold face
|
||||
// this avoids unwanted synthetic italics/bold
|
||||
if (fe->mIsUserFont) {
|
||||
if (fe->IsItalic())
|
||||
italic = PR_FALSE; // avoid synthetic italic
|
||||
if (fe->IsBold()) {
|
||||
weight = 400; // avoid synthetic bold
|
||||
}
|
||||
}
|
||||
|
||||
fe->FillLogFont(&mLogFont, italic, weight, aSize);
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Foundation code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2005-2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Stuart Parmenter <stuart@mozilla.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
* Jonathan Kew <jfkthame@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef GFX_GDIFONT_H
|
||||
#define GFX_GDIFONT_H
|
||||
|
||||
#include "gfxFont.h"
|
||||
#include "gfxGDIFontList.h"
|
||||
|
||||
#include "cairo.h"
|
||||
|
||||
class gfxGDIFont : public gfxFont
|
||||
{
|
||||
public:
|
||||
gfxGDIFont(GDIFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
PRBool aNeedsBold,
|
||||
AntialiasOption anAAOption = kAntialiasDefault);
|
||||
|
||||
virtual ~gfxGDIFont();
|
||||
|
||||
HFONT GetHFONT() const { return mFont; }
|
||||
|
||||
float GetAdjustedSize() const { return mAdjustedSize; }
|
||||
|
||||
cairo_font_face_t *CairoFontFace() { return mFontFace; }
|
||||
cairo_scaled_font_t *CairoScaledFont() { return mScaledFont; }
|
||||
|
||||
/* overrides for the pure virtual methods in gfxFont */
|
||||
virtual const gfxFont::Metrics& GetMetrics();
|
||||
|
||||
virtual PRUint32 GetSpaceGlyph();
|
||||
|
||||
virtual PRBool SetupCairoFont(gfxContext *aContext);
|
||||
|
||||
/* required for MathML to suppress effects of ClearType "padding" */
|
||||
virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption);
|
||||
|
||||
/* override to check for uniscribe failure and fall back to GDI */
|
||||
virtual void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
|
||||
protected:
|
||||
void InitMetrics();
|
||||
void FillLogFont(gfxFloat aSize);
|
||||
|
||||
float GetCharWidth(PRUnichar aUniChar, PRUint32 *aGlyphID);
|
||||
float GetCharHeight(PRUnichar aUniChar);
|
||||
|
||||
PRBool mNeedsBold;
|
||||
|
||||
LOGFONTW mLogFont;
|
||||
HFONT mFont;
|
||||
|
||||
cairo_font_face_t *mFontFace;
|
||||
cairo_scaled_font_t *mScaledFont;
|
||||
|
||||
Metrics mMetrics;
|
||||
PRUint32 mSpaceGlyph;
|
||||
float mAdjustedSize;
|
||||
};
|
||||
|
||||
#endif /* GFX_GDIFONT_H */
|
|
@ -43,7 +43,7 @@
|
|||
#include "gfxWindowsPlatform.h"
|
||||
#include "gfxUserFontSet.h"
|
||||
#include "gfxFontUtils.h"
|
||||
#include "gfxWindowsFonts.h"
|
||||
#include "gfxGDIFont.h"
|
||||
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsTArray.h"
|
||||
|
@ -55,6 +55,8 @@
|
|||
#include "nsISimpleEnumerator.h"
|
||||
#include "nsIWindowsRegKey.h"
|
||||
|
||||
#include <usp10.h>
|
||||
|
||||
#define ROUND(x) floor((x) + 0.5)
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
|
@ -218,9 +220,9 @@ GDIFontEntry::ReadCMAP()
|
|||
}
|
||||
|
||||
gfxFont *
|
||||
GDIFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle, PRBool /*aNeedsBold*/)
|
||||
GDIFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle, PRBool aNeedsBold)
|
||||
{
|
||||
return new gfxWindowsFont(this, aFontStyle);
|
||||
return new gfxGDIFont(this, aFontStyle, aNeedsBold);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -283,10 +285,10 @@ GDIFontEntry::TestCharacterMap(PRUint32 aCh)
|
|||
fakeStyle.style = FONT_STYLE_ITALIC;
|
||||
fakeStyle.weight = mWeight * 100;
|
||||
|
||||
nsRefPtr<gfxWindowsFont> font =
|
||||
gfxWindowsFont::GetOrMakeFont(this, &fakeStyle);
|
||||
if (!font->IsValid())
|
||||
nsRefPtr<gfxFont> tempFont = FindOrMakeFont(&fakeStyle, PR_FALSE);
|
||||
if (!tempFont || !tempFont->Valid())
|
||||
return PR_FALSE;
|
||||
gfxGDIFont *font = static_cast<gfxGDIFont*>(tempFont.get());
|
||||
|
||||
HDC dc = GetDC((HWND)nsnull);
|
||||
SetGraphicsMode(dc, GM_ADVANCED);
|
||||
|
@ -297,14 +299,15 @@ GDIFontEntry::TestCharacterMap(PRUint32 aCh)
|
|||
WORD glyph[1];
|
||||
|
||||
PRBool hasGlyph = PR_FALSE;
|
||||
if (IsType1()) {
|
||||
if (IsType1() || mForceGDI) {
|
||||
// Type1 fonts and uniscribe APIs don't get along. ScriptGetCMap will return E_HANDLE
|
||||
DWORD ret = GetGlyphIndicesW(dc, str, 1, glyph, GGI_MARK_NONEXISTING_GLYPHS);
|
||||
if (ret != GDI_ERROR && glyph[0] != 0xFFFF)
|
||||
hasGlyph = PR_TRUE;
|
||||
} else {
|
||||
// ScriptGetCMap works better than GetGlyphIndicesW for things like bitmap/vector fonts
|
||||
HRESULT rv = ScriptGetCMap(dc, font->ScriptCache(), str, 1, 0, glyph);
|
||||
SCRIPT_CACHE sc = NULL;
|
||||
HRESULT rv = ScriptGetCMap(dc, &sc, str, 1, 0, glyph);
|
||||
if (rv == S_OK)
|
||||
hasGlyph = PR_TRUE;
|
||||
}
|
||||
|
|
|
@ -69,9 +69,12 @@ private:
|
|||
class AutoSelectFont // select a font into the given DC, and auto-restore
|
||||
{
|
||||
public:
|
||||
AutoSelectFont(HDC aDC, LOGFONTW *aLogFont) {
|
||||
AutoSelectFont(HDC aDC, LOGFONTW *aLogFont)
|
||||
: mOwnsFont(PR_FALSE)
|
||||
{
|
||||
mFont = ::CreateFontIndirectW(aLogFont);
|
||||
if (mFont) {
|
||||
mOwnsFont = PR_TRUE;
|
||||
mDC = aDC;
|
||||
mOldFont = (HFONT)::SelectObject(aDC, mFont);
|
||||
} else {
|
||||
|
@ -79,7 +82,9 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
AutoSelectFont(HDC aDC, HFONT aFont) {
|
||||
AutoSelectFont(HDC aDC, HFONT aFont)
|
||||
: mOwnsFont(PR_FALSE)
|
||||
{
|
||||
mDC = aDC;
|
||||
mFont = aFont;
|
||||
mOldFont = (HFONT)::SelectObject(aDC, aFont);
|
||||
|
@ -88,6 +93,9 @@ public:
|
|||
~AutoSelectFont() {
|
||||
if (mOldFont) {
|
||||
::SelectObject(mDC, mOldFont);
|
||||
if (mOwnsFont) {
|
||||
::DeleteObject(mFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,9 +108,10 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
HDC mDC;
|
||||
HFONT mFont;
|
||||
HFONT mOldFont;
|
||||
HDC mDC;
|
||||
HFONT mFont;
|
||||
HFONT mOldFont;
|
||||
PRBool mOwnsFont;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Foundation code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2005-2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Stuart Parmenter <stuart@mozilla.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* Mats Palmgren <mats.palmgren@bredband.net>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
* Jonathan Kew <jfkthame@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
//#define FORCE_PR_LOG
|
||||
|
||||
#include "gfxGDIShaper.h"
|
||||
|
||||
/**********************************************************************
|
||||
*
|
||||
* class gfxGDIShaper
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
PRBool
|
||||
gfxGDIShaper::InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength)
|
||||
{
|
||||
gfxGDIFont *f = static_cast<gfxGDIFont*>(mFont);
|
||||
DCFromContext dc(aContext);
|
||||
AutoSelectFont fs(dc, f->GetHFONT());
|
||||
|
||||
nsAutoTArray<WORD,500> glyphArray;
|
||||
if (!glyphArray.SetLength(aRunLength)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
WORD *glyphs = glyphArray.Elements();
|
||||
|
||||
DWORD ret = ::GetGlyphIndicesW(dc, aString + aRunStart, aRunLength,
|
||||
glyphs, GGI_MARK_NONEXISTING_GLYPHS);
|
||||
if (ret == GDI_ERROR) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
SIZE size;
|
||||
nsAutoTArray<int,500> partialWidthArray;
|
||||
if (!partialWidthArray.SetLength(aRunLength)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
BOOL success = ::GetTextExtentExPointI(dc,
|
||||
glyphs,
|
||||
aRunLength,
|
||||
INT_MAX,
|
||||
NULL,
|
||||
partialWidthArray.Elements(),
|
||||
&size);
|
||||
if (!success) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
gfxTextRun::CompressedGlyph g;
|
||||
PRUint32 i;
|
||||
PRInt32 lastWidth = 0;
|
||||
PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit();
|
||||
for (i = 0; i < aRunLength; ++i) {
|
||||
PRInt32 advancePixels = partialWidthArray[i] - lastWidth;
|
||||
lastWidth = partialWidthArray[i];
|
||||
PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel;
|
||||
WCHAR glyph = glyphs[i];
|
||||
NS_ASSERTION(!gfxFontGroup::IsInvalidChar(
|
||||
aTextRun->GetChar(aRunStart + i)),
|
||||
"Invalid character detected!");
|
||||
if (advanceAppUnits >= 0 &&
|
||||
gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
|
||||
gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
|
||||
aTextRun->SetSimpleGlyph(aRunStart + i,
|
||||
g.SetSimpleGlyph(advanceAppUnits, glyph));
|
||||
} else {
|
||||
gfxTextRun::DetailedGlyph details;
|
||||
details.mGlyphID = glyph;
|
||||
details.mAdvance = advanceAppUnits;
|
||||
details.mXOffset = 0;
|
||||
details.mYOffset = 0;
|
||||
aTextRun->SetGlyphs(aRunStart + i,
|
||||
g.SetComplex(PR_TRUE, PR_TRUE, 1),
|
||||
&details);
|
||||
}
|
||||
}
|
||||
|
||||
return PR_TRUE;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Foundation code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jonathan Kew <jfkthame@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef GFX_GDISHAPER_H
|
||||
#define GFX_GDISHAPER_H
|
||||
|
||||
#include "gfxGDIFont.h"
|
||||
|
||||
class gfxGDIShaper : public gfxFontShaper
|
||||
{
|
||||
public:
|
||||
gfxGDIShaper(gfxGDIFont *aFont)
|
||||
: gfxFontShaper(aFont) { }
|
||||
|
||||
virtual ~gfxGDIShaper() { }
|
||||
|
||||
virtual PRBool InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
};
|
||||
|
||||
#endif /* GFX_GDISHAPER_H */
|
|
@ -0,0 +1,620 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Foundation code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2005-2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Stuart Parmenter <stuart@mozilla.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* Mats Palmgren <mats.palmgren@bredband.net>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
* Jonathan Kew <jfkthame@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
//#define FORCE_PR_LOG
|
||||
|
||||
#include "prtypes.h"
|
||||
#include "gfxTypes.h"
|
||||
|
||||
#include "gfxContext.h"
|
||||
#include "gfxUniscribeShaper.h"
|
||||
#include "gfxWindowsPlatform.h"
|
||||
#include "gfxAtoms.h"
|
||||
|
||||
#include "gfxFontTest.h"
|
||||
|
||||
#include "cairo.h"
|
||||
#include "cairo-win32.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "nsTArray.h"
|
||||
|
||||
#include "prlog.h"
|
||||
#include "prinit.h"
|
||||
static PRLogModuleInfo *gFontLog = PR_NewLogModule("winfonts");
|
||||
|
||||
/**********************************************************************
|
||||
*
|
||||
* class gfxUniscribeShaper
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
#define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16)
|
||||
|
||||
class UniscribeItem
|
||||
{
|
||||
public:
|
||||
UniscribeItem(gfxContext *aContext, HDC aDC,
|
||||
gfxUniscribeShaper *aShaper,
|
||||
const PRUnichar *aString, PRUint32 aLength,
|
||||
SCRIPT_ITEM *aItem) :
|
||||
mContext(aContext), mDC(aDC),
|
||||
mShaper(aShaper),
|
||||
mItemString(aString), mItemLength(aLength),
|
||||
mAlternativeString(nsnull), mScriptItem(aItem),
|
||||
mScript(aItem->a.eScript),
|
||||
mNumGlyphs(0), mMaxGlyphs(ESTIMATE_MAX_GLYPHS(aLength)),
|
||||
mFontSelected(PR_FALSE)
|
||||
{
|
||||
NS_ASSERTION(mMaxGlyphs < 65535, "UniscribeItem is too big, ScriptShape() will fail!");
|
||||
}
|
||||
|
||||
~UniscribeItem() {
|
||||
free(mAlternativeString);
|
||||
}
|
||||
|
||||
PRBool AllocateBuffers() {
|
||||
return (mGlyphs.SetLength(mMaxGlyphs) &&
|
||||
mClusters.SetLength(mItemLength + 1) &&
|
||||
mAttr.SetLength(mMaxGlyphs));
|
||||
}
|
||||
|
||||
/* possible return values:
|
||||
* S_OK - things succeeded
|
||||
* GDI_ERROR - things failed to shape. Might want to try again after calling DisableShaping()
|
||||
*/
|
||||
|
||||
HRESULT Shape() {
|
||||
HRESULT rv;
|
||||
HDC shapeDC = nsnull;
|
||||
|
||||
const PRUnichar *str = mAlternativeString ? mAlternativeString : mItemString;
|
||||
|
||||
mScriptItem->a.fLogicalOrder = PR_TRUE;
|
||||
SCRIPT_ANALYSIS sa = mScriptItem->a;
|
||||
|
||||
while (PR_TRUE) {
|
||||
|
||||
rv = ScriptShape(shapeDC, mShaper->ScriptCache(),
|
||||
str, mItemLength,
|
||||
mMaxGlyphs, &sa,
|
||||
mGlyphs.Elements(), mClusters.Elements(),
|
||||
mAttr.Elements(), &mNumGlyphs);
|
||||
|
||||
if (rv == E_OUTOFMEMORY) {
|
||||
mMaxGlyphs *= 2;
|
||||
if (!mGlyphs.SetLength(mMaxGlyphs) ||
|
||||
!mAttr.SetLength(mMaxGlyphs)) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Uniscribe can't do shaping with some fonts, so it sets the
|
||||
// fNoGlyphIndex flag in the SCRIPT_ANALYSIS structure to indicate
|
||||
// this. This occurs with CFF fonts loaded with
|
||||
// AddFontMemResourceEx but it's not clear what the other cases
|
||||
// are. We return an error so our caller can try fallback shaping.
|
||||
// see http://msdn.microsoft.com/en-us/library/ms776520(VS.85).aspx
|
||||
|
||||
if (sa.fNoGlyphIndex) {
|
||||
return GDI_ERROR;
|
||||
}
|
||||
|
||||
if (rv == E_PENDING) {
|
||||
if (shapeDC == mDC) {
|
||||
// we already tried this once, something failed, give up
|
||||
return E_PENDING;
|
||||
}
|
||||
|
||||
SelectFont();
|
||||
|
||||
shapeDC = mDC;
|
||||
continue;
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/dd368564(VS.85).aspx:
|
||||
// Uniscribe will return this if "the font corresponding to the
|
||||
// DC does not support the script required by the run...".
|
||||
// In this case, we'll set the script code to SCRIPT_UNDEFINED
|
||||
// and try again, so that we'll at least get glyphs even though
|
||||
// they won't necessarily have proper shaping.
|
||||
// (We probably shouldn't have selected this font at all,
|
||||
// but it's too late to fix that here.)
|
||||
if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
|
||||
sa.eScript = SCRIPT_UNDEFINED;
|
||||
NS_WARNING("Uniscribe says font does not support script needed");
|
||||
continue;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
PRBool ShapingEnabled() {
|
||||
return (mScriptItem->a.eScript != SCRIPT_UNDEFINED);
|
||||
}
|
||||
void DisableShaping() {
|
||||
mScriptItem->a.eScript = SCRIPT_UNDEFINED;
|
||||
// Note: If we disable the shaping by using SCRIPT_UNDEFINED and
|
||||
// the string has the surrogate pair, ScriptShape API is
|
||||
// *sometimes* crashed. Therefore, we should replace the surrogate
|
||||
// pair to U+FFFD. See bug 341500.
|
||||
GenerateAlternativeString();
|
||||
}
|
||||
void EnableShaping() {
|
||||
mScriptItem->a.eScript = mScript;
|
||||
if (mAlternativeString) {
|
||||
free(mAlternativeString);
|
||||
mAlternativeString = nsnull;
|
||||
}
|
||||
}
|
||||
|
||||
PRBool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, PRUint32 aGlyphIndex) {
|
||||
return (mGlyphs[aGlyphIndex] == aSFP->wgDefault);
|
||||
}
|
||||
|
||||
|
||||
HRESULT Place() {
|
||||
HRESULT rv;
|
||||
HDC placeDC = nsnull;
|
||||
|
||||
if (!mOffsets.SetLength(mNumGlyphs) ||
|
||||
!mAdvances.SetLength(mNumGlyphs)) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
SCRIPT_ANALYSIS sa = mScriptItem->a;
|
||||
|
||||
while (PR_TRUE) {
|
||||
rv = ScriptPlace(placeDC, mShaper->ScriptCache(),
|
||||
mGlyphs.Elements(), mNumGlyphs,
|
||||
mAttr.Elements(), &sa,
|
||||
mAdvances.Elements(), mOffsets.Elements(), NULL);
|
||||
|
||||
if (rv == E_PENDING) {
|
||||
SelectFont();
|
||||
placeDC = mDC;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
|
||||
sa.eScript = SCRIPT_UNDEFINED;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) {
|
||||
HRESULT rv;
|
||||
|
||||
memset(sfp, 0, sizeof(SCRIPT_FONTPROPERTIES));
|
||||
sfp->cBytes = sizeof(SCRIPT_FONTPROPERTIES);
|
||||
rv = ScriptGetFontProperties(NULL, mShaper->ScriptCache(),
|
||||
sfp);
|
||||
if (rv == E_PENDING) {
|
||||
SelectFont();
|
||||
rv = ScriptGetFontProperties(mDC, mShaper->ScriptCache(),
|
||||
sfp);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveGlyphs(gfxTextRun *aRun, PRUint32 aRunStart) {
|
||||
PRUint32 offsetInRun = aRunStart + mScriptItem->iCharPos;
|
||||
|
||||
// XXX We should store this in the item and only fetch it once
|
||||
SCRIPT_FONTPROPERTIES sfp;
|
||||
ScriptFontProperties(&sfp);
|
||||
|
||||
PRUint32 offset = 0;
|
||||
nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs;
|
||||
gfxTextRun::CompressedGlyph g;
|
||||
const PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
|
||||
while (offset < mItemLength) {
|
||||
PRUint32 runOffset = offsetInRun + offset;
|
||||
if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) {
|
||||
g.SetComplex(aRun->IsClusterStart(runOffset), PR_FALSE, 0);
|
||||
aRun->SetGlyphs(runOffset, g, nsnull);
|
||||
} else {
|
||||
// Count glyphs for this character
|
||||
PRUint32 k = mClusters[offset];
|
||||
PRUint32 glyphCount = mNumGlyphs - k;
|
||||
PRUint32 nextClusterOffset;
|
||||
PRBool missing = IsGlyphMissing(&sfp, k);
|
||||
for (nextClusterOffset = offset + 1; nextClusterOffset < mItemLength; ++nextClusterOffset) {
|
||||
if (mClusters[nextClusterOffset] > k) {
|
||||
glyphCount = mClusters[nextClusterOffset] - k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
PRUint32 j;
|
||||
for (j = 1; j < glyphCount; ++j) {
|
||||
if (IsGlyphMissing(&sfp, k + j)) {
|
||||
missing = PR_TRUE;
|
||||
}
|
||||
}
|
||||
PRInt32 advance = mAdvances[k]*appUnitsPerDevUnit;
|
||||
WORD glyph = mGlyphs[k];
|
||||
NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString[offset]),
|
||||
"invalid character detected");
|
||||
if (missing) {
|
||||
if (NS_IS_HIGH_SURROGATE(mItemString[offset]) &&
|
||||
offset + 1 < mItemLength &&
|
||||
NS_IS_LOW_SURROGATE(mItemString[offset + 1])) {
|
||||
aRun->SetMissingGlyph(runOffset,
|
||||
SURROGATE_TO_UCS4(mItemString[offset],
|
||||
mItemString[offset + 1]));
|
||||
} else {
|
||||
aRun->SetMissingGlyph(runOffset, mItemString[offset]);
|
||||
}
|
||||
} else if (glyphCount == 1 && advance >= 0 &&
|
||||
mOffsets[k].dv == 0 && mOffsets[k].du == 0 &&
|
||||
gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
|
||||
gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
|
||||
aRun->SetSimpleGlyph(runOffset, g.SetSimpleGlyph(advance, glyph));
|
||||
} else {
|
||||
if (detailedGlyphs.Length() < glyphCount) {
|
||||
if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length()))
|
||||
return;
|
||||
}
|
||||
PRUint32 i;
|
||||
for (i = 0; i < glyphCount; ++i) {
|
||||
gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i];
|
||||
details->mGlyphID = mGlyphs[k + i];
|
||||
details->mAdvance = mAdvances[k + i]*appUnitsPerDevUnit;
|
||||
details->mXOffset = float(mOffsets[k + i].du)*appUnitsPerDevUnit*aRun->GetDirection();
|
||||
details->mYOffset = - float(mOffsets[k + i].dv)*appUnitsPerDevUnit;
|
||||
}
|
||||
aRun->SetGlyphs(runOffset,
|
||||
g.SetComplex(PR_TRUE, PR_TRUE, glyphCount), detailedGlyphs.Elements());
|
||||
}
|
||||
}
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
|
||||
void SelectFont() {
|
||||
if (mFontSelected)
|
||||
return;
|
||||
|
||||
cairo_t *cr = mContext->GetCairo();
|
||||
|
||||
cairo_set_font_face(cr, mShaper->GetFont()->CairoFontFace());
|
||||
cairo_set_font_size(cr, mShaper->GetFont()->GetAdjustedSize());
|
||||
cairo_scaled_font_t *scaledFont = mShaper->GetFont()->CairoScaledFont();
|
||||
cairo_win32_scaled_font_select_font(scaledFont, mDC);
|
||||
|
||||
mFontSelected = PR_TRUE;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void GenerateAlternativeString() {
|
||||
if (mAlternativeString)
|
||||
free(mAlternativeString);
|
||||
mAlternativeString = (PRUnichar *)malloc(mItemLength * sizeof(PRUnichar));
|
||||
if (!mAlternativeString)
|
||||
return;
|
||||
memcpy((void *)mAlternativeString, (const void *)mItemString,
|
||||
mItemLength * sizeof(PRUnichar));
|
||||
for (PRUint32 i = 0; i < mItemLength; i++) {
|
||||
if (NS_IS_HIGH_SURROGATE(mItemString[i]) || NS_IS_LOW_SURROGATE(mItemString[i]))
|
||||
mAlternativeString[i] = PRUnichar(0xFFFD);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<gfxContext> mContext;
|
||||
HDC mDC;
|
||||
gfxUniscribeShaper *mShaper;
|
||||
|
||||
SCRIPT_ITEM *mScriptItem;
|
||||
WORD mScript;
|
||||
|
||||
public:
|
||||
// these point to the full string/length of the item
|
||||
const PRUnichar *mItemString;
|
||||
const PRUint32 mItemLength;
|
||||
|
||||
private:
|
||||
PRUnichar *mAlternativeString;
|
||||
|
||||
#define AVERAGE_ITEM_LENGTH 40
|
||||
|
||||
nsAutoTArray<WORD, PRUint32(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mGlyphs;
|
||||
nsAutoTArray<WORD, AVERAGE_ITEM_LENGTH + 1> mClusters;
|
||||
nsAutoTArray<SCRIPT_VISATTR, PRUint32(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mAttr;
|
||||
|
||||
nsAutoTArray<GOFFSET, 2 * AVERAGE_ITEM_LENGTH> mOffsets;
|
||||
nsAutoTArray<int, 2 * AVERAGE_ITEM_LENGTH> mAdvances;
|
||||
|
||||
#undef AVERAGE_ITEM_LENGTH
|
||||
|
||||
int mMaxGlyphs;
|
||||
int mNumGlyphs;
|
||||
|
||||
PRPackedBool mFontSelected;
|
||||
};
|
||||
|
||||
#define MAX_ITEM_LENGTH 32768
|
||||
|
||||
static PRUint32 FindNextItemStart(int aOffset, int aLimit,
|
||||
nsTArray<SCRIPT_LOGATTR> &aLogAttr,
|
||||
const PRUnichar *aString)
|
||||
{
|
||||
if (aOffset + MAX_ITEM_LENGTH >= aLimit) {
|
||||
// The item starting at aOffset can't be longer than the max length,
|
||||
// so starting the next item at aLimit won't cause ScriptShape() to fail.
|
||||
return aLimit;
|
||||
}
|
||||
|
||||
// Try to start the next item before or after a space, since spaces
|
||||
// don't kern or ligate.
|
||||
PRUint32 off;
|
||||
int boundary = -1;
|
||||
for (off = MAX_ITEM_LENGTH; off > 1; --off) {
|
||||
if (aLogAttr[off].fCharStop) {
|
||||
if (off > boundary) {
|
||||
boundary = off;
|
||||
}
|
||||
if (aString[aOffset+off] == ' ' || aString[aOffset+off - 1] == ' ')
|
||||
return aOffset+off;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to start the next item at the last cluster boundary in the range.
|
||||
if (boundary > 0) {
|
||||
return aOffset+boundary;
|
||||
}
|
||||
|
||||
// No nice cluster boundaries inside MAX_ITEM_LENGTH characters, break
|
||||
// on the size limit. It won't be visually plesaing, but at least it
|
||||
// won't cause ScriptShape() to fail.
|
||||
return aOffset + MAX_ITEM_LENGTH;
|
||||
}
|
||||
|
||||
class Uniscribe
|
||||
{
|
||||
public:
|
||||
Uniscribe(const PRUnichar *aString, PRUint32 aLength, PRBool aIsRTL) :
|
||||
mString(aString), mLength(aLength), mIsRTL(aIsRTL)
|
||||
{
|
||||
}
|
||||
~Uniscribe() {
|
||||
}
|
||||
|
||||
void Init() {
|
||||
memset(&mControl, 0, sizeof(SCRIPT_CONTROL));
|
||||
memset(&mState, 0, sizeof(SCRIPT_STATE));
|
||||
// Lock the direction. Don't allow the itemizer to change directions
|
||||
// based on character type.
|
||||
mState.uBidiLevel = mIsRTL;
|
||||
mState.fOverrideDirection = PR_TRUE;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Append mItems[aIndex] to aDest, adding extra items to aDest to ensure
|
||||
// that no item is too long for ScriptShape() to handle. See bug 366643.
|
||||
nsresult CopyItemSplitOversize(int aIndex, nsTArray<SCRIPT_ITEM> &aDest) {
|
||||
aDest.AppendElement(mItems[aIndex]);
|
||||
const int itemLength = mItems[aIndex+1].iCharPos - mItems[aIndex].iCharPos;
|
||||
if (ESTIMATE_MAX_GLYPHS(itemLength) > 65535) {
|
||||
// This items length would cause ScriptShape() to fail. We need to
|
||||
// add extra items here so that no item's length could cause the fail.
|
||||
|
||||
// Get cluster boundaries, so we can break cleanly if possible.
|
||||
nsTArray<SCRIPT_LOGATTR> logAttr;
|
||||
if (!logAttr.SetLength(itemLength))
|
||||
return NS_ERROR_FAILURE;
|
||||
HRESULT rv = ScriptBreak(mString+mItems[aIndex].iCharPos, itemLength,
|
||||
&mItems[aIndex].a, logAttr.Elements());
|
||||
if (FAILED(rv))
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
const int nextItemStart = mItems[aIndex+1].iCharPos;
|
||||
int start = FindNextItemStart(mItems[aIndex].iCharPos,
|
||||
nextItemStart, logAttr, mString);
|
||||
|
||||
while (start < nextItemStart) {
|
||||
SCRIPT_ITEM item = mItems[aIndex];
|
||||
item.iCharPos = start;
|
||||
aDest.AppendElement(item);
|
||||
start = FindNextItemStart(start, nextItemStart, logAttr, mString);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
int Itemize() {
|
||||
HRESULT rv;
|
||||
|
||||
int maxItems = 5;
|
||||
|
||||
Init();
|
||||
|
||||
// Allocate space for one more item than expected, to handle a rare
|
||||
// overflow in ScriptItemize (pre XP SP2). See bug 366643.
|
||||
if (!mItems.SetLength(maxItems + 1)) {
|
||||
return 0;
|
||||
}
|
||||
while ((rv = ScriptItemize(mString, mLength, maxItems, &mControl, &mState,
|
||||
mItems.Elements(), &mNumItems)) == E_OUTOFMEMORY) {
|
||||
maxItems *= 2;
|
||||
if (!mItems.SetLength(maxItems + 1)) {
|
||||
return 0;
|
||||
}
|
||||
Init();
|
||||
}
|
||||
|
||||
if (ESTIMATE_MAX_GLYPHS(mLength) > 65535) {
|
||||
// Any item of length > 43680 will cause ScriptShape() to fail, as its
|
||||
// mMaxGlyphs value will be greater than 65535 (43680*1.5+16>65535). So we
|
||||
// need to break up items which are longer than that upon cluster boundaries.
|
||||
// See bug 394751 for details.
|
||||
nsTArray<SCRIPT_ITEM> items;
|
||||
for (int i=0; i<mNumItems; i++) {
|
||||
nsresult nrs = CopyItemSplitOversize(i, items);
|
||||
NS_ASSERTION(NS_SUCCEEDED(nrs), "CopyItemSplitOversize() failed");
|
||||
}
|
||||
items.AppendElement(mItems[mNumItems]); // copy terminator.
|
||||
|
||||
mItems = items;
|
||||
mNumItems = items.Length() - 1; // Don't count the terminator.
|
||||
}
|
||||
return mNumItems;
|
||||
}
|
||||
|
||||
PRUint32 ItemsLength() {
|
||||
return mNumItems;
|
||||
}
|
||||
|
||||
SCRIPT_ITEM *ScriptItem(PRUint32 i) {
|
||||
NS_ASSERTION(i <= (PRUint32)mNumItems, "Trying to get out of bounds item");
|
||||
return &mItems[i];
|
||||
}
|
||||
|
||||
private:
|
||||
const PRUnichar *mString;
|
||||
const PRUint32 mLength;
|
||||
const PRBool mIsRTL;
|
||||
|
||||
SCRIPT_CONTROL mControl;
|
||||
SCRIPT_STATE mState;
|
||||
nsTArray<SCRIPT_ITEM> mItems;
|
||||
int mNumItems;
|
||||
};
|
||||
|
||||
gfxUniscribeShaper::gfxUniscribeShaper(gfxGDIFont *aFont)
|
||||
: gfxFontShaper(aFont)
|
||||
, mScriptCache(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
gfxUniscribeShaper::~gfxUniscribeShaper()
|
||||
{
|
||||
}
|
||||
|
||||
PRBool
|
||||
gfxUniscribeShaper::InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength)
|
||||
{
|
||||
DCFromContext aDC(aContext);
|
||||
|
||||
const PRBool isRTL = aTextRun->IsRightToLeft();
|
||||
|
||||
PRBool result = PR_TRUE;
|
||||
HRESULT rv;
|
||||
|
||||
gfxGDIFont *font = static_cast<gfxGDIFont*>(mFont);
|
||||
AutoSelectFont fs(aDC, font->GetHFONT());
|
||||
|
||||
Uniscribe us(aString + aRunStart, aRunLength, isRTL);
|
||||
|
||||
/* itemize the string */
|
||||
int numItems = us.Itemize();
|
||||
|
||||
SaveDC(aDC);
|
||||
for (int i = 0; i < numItems; ++i) {
|
||||
UniscribeItem item(aContext, aDC, this,
|
||||
aString + aRunStart + us.ScriptItem(i)->iCharPos,
|
||||
us.ScriptItem(i+1)->iCharPos - us.ScriptItem(i)->iCharPos,
|
||||
us.ScriptItem(i));
|
||||
if (!item.AllocateBuffers()) {
|
||||
result = PR_FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!item.ShapingEnabled()) {
|
||||
item.EnableShaping();
|
||||
}
|
||||
|
||||
rv = item.Shape();
|
||||
if (FAILED(rv)) {
|
||||
PR_LOG(gFontLog, PR_LOG_DEBUG, ("shaping failed"));
|
||||
// we know we have the glyphs to display this font already
|
||||
// so Uniscribe just doesn't know how to shape the script.
|
||||
// Render the glyphs without shaping.
|
||||
item.DisableShaping();
|
||||
rv = item.Shape();
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (FAILED(rv)) {
|
||||
NS_WARNING("Uniscribe failed to shape with font");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (SUCCEEDED(rv)) {
|
||||
rv = item.Place();
|
||||
#ifdef DEBUG
|
||||
if (FAILED(rv)) {
|
||||
// crap fonts may fail when placing (e.g. funky free fonts)
|
||||
NS_WARNING("Uniscribe failed to place with font");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (FAILED(rv)) {
|
||||
aTextRun->ResetGlyphRuns();
|
||||
// Uniscribe doesn't like this font for some reason.
|
||||
// Returning FALSE will make the gfxGDIFont discard this
|
||||
// shaper and replace it with a "dumb" GDI one.
|
||||
result = PR_FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
item.SaveGlyphs(aTextRun, aRunStart);
|
||||
}
|
||||
|
||||
RestoreDC(aDC, -1);
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Foundation code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2005-2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Stuart Parmenter <stuart@mozilla.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
* Jonathan Kew <jfkthame@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef GFX_UNISCRIBESHAPER_H
|
||||
#define GFX_UNISCRIBESHAPER_H
|
||||
|
||||
#include "prtypes.h"
|
||||
#include "gfxTypes.h"
|
||||
#include "gfxGDIFont.h"
|
||||
|
||||
#include <usp10.h>
|
||||
#include <cairo-win32.h>
|
||||
|
||||
|
||||
class gfxUniscribeShaper : public gfxFontShaper
|
||||
{
|
||||
public:
|
||||
gfxUniscribeShaper(gfxGDIFont *aFont);
|
||||
|
||||
virtual ~gfxUniscribeShaper();
|
||||
|
||||
virtual PRBool InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
|
||||
SCRIPT_CACHE *ScriptCache() { return &mScriptCache; }
|
||||
|
||||
gfxGDIFont *GetFont() { return static_cast<gfxGDIFont*>(mFont); }
|
||||
|
||||
private:
|
||||
SCRIPT_CACHE mScriptCache;
|
||||
};
|
||||
|
||||
#endif /* GFX_UNISCRIBESHAPER_H */
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -62,8 +62,8 @@
|
|||
#include "cairo-ft.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#else
|
||||
#include "gfxWindowsFonts.h"
|
||||
#include "gfxGDIFontList.h"
|
||||
#include "gfxGDIFont.h"
|
||||
#ifdef CAIRO_HAS_DWRITE_FONT
|
||||
#include "gfxDWriteFontList.h"
|
||||
#include "gfxDWriteFonts.h"
|
||||
|
@ -319,7 +319,7 @@ gfxWindowsPlatform::CreateFontGroup(const nsAString &aFamilies,
|
|||
return new gfxDWriteFontGroup(aFamilies, aStyle, aUserFontSet);
|
||||
} else {
|
||||
#endif
|
||||
return new gfxWindowsFontGroup(aFamilies, aStyle, aUserFontSet);
|
||||
return new gfxFontGroup(aFamilies, aStyle, aUserFontSet);
|
||||
#ifdef CAIRO_HAS_DWRITE_FONT
|
||||
}
|
||||
#endif
|
||||
|
@ -443,67 +443,6 @@ gfxWindowsPlatform::GetFTLibrary()
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifndef MOZ_FT2_FONTS
|
||||
void
|
||||
gfxWindowsPlatform::SetupClusterBoundaries(gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(sizeof(WCHAR) == sizeof(PRUnichar),
|
||||
"WCHAR/PRUnichar not compatible");
|
||||
|
||||
PRUint32 length = aTextRun->GetLength();
|
||||
|
||||
nsAutoTArray<SCRIPT_ITEM,4> items;
|
||||
PRUint32 maxItems = 4;
|
||||
int numItems;
|
||||
HRESULT result;
|
||||
PRUint32 i, j;
|
||||
|
||||
do {
|
||||
items.SetLength(maxItems);
|
||||
result = ::ScriptItemize(aString, length,
|
||||
maxItems - 1,
|
||||
NULL,
|
||||
NULL,
|
||||
items.Elements(),
|
||||
&numItems);
|
||||
maxItems <<= 1;
|
||||
if (maxItems > INT_MAX)
|
||||
break;
|
||||
} while (result == E_OUTOFMEMORY);
|
||||
|
||||
if (result != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<SCRIPT_LOGATTR> attrs;
|
||||
for (i = 0; i < PRUint32(numItems); ++i) {
|
||||
PRUint32 offset = items[i].iCharPos;
|
||||
length = items[i + 1].iCharPos - offset;
|
||||
if (attrs.Length() < length) {
|
||||
attrs.SetLength(length);
|
||||
}
|
||||
result = ::ScriptBreak(aString + offset, length,
|
||||
&items[i].a,
|
||||
attrs.Elements());
|
||||
if (result != 0) {
|
||||
break;
|
||||
}
|
||||
for (j = 1; j < length; ++j) {
|
||||
if (!attrs[j].fCharStop) {
|
||||
gfxTextRun::CompressedGlyph g;
|
||||
// Remember that this character is not the start of a cluster
|
||||
// by setting its glyph data to "not a cluster start",
|
||||
// "is a ligature start", with no glyphs.
|
||||
aTextRun->SetGlyphs(offset + j,
|
||||
g.SetComplex(PR_FALSE, PR_TRUE, 0),
|
||||
nsnull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
gfxWindowsPlatform::InitDisplayCaps()
|
||||
{
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
}
|
||||
</style>
|
||||
<body>
|
||||
<p>നെ തുടര്ന്നാണ് ആര.</p>
|
||||
<p>നെ തുടര്ന്നാണ് ആര‍‌.</p>
|
||||
<!-- note the extra zwnj before the period; this is needed to prevent
|
||||
the zwj causing font fallback choices to propagate further
|
||||
(see gfxFontGroup::FindFontForChar). -->
|
||||
</body>
|
||||
</html>
|
|
@ -1258,7 +1258,7 @@ fails == 472020-2.xul 472020-2-ref.xul
|
|||
== 481024-1e.html 481024-1-ref.html
|
||||
!= 481948-1.html 481948-1-ref.html
|
||||
!= 481948-2.html 481948-2-ref.html
|
||||
fails-if(MOZ_WIDGET_TOOLKIT=="gtk2") == 481948-3.html 481948-3-ref.html # different behavior on Linux, see bug 488364
|
||||
random-if(MOZ_WIDGET_TOOLKIT=="windows") fails-if(MOZ_WIDGET_TOOLKIT=="gtk2") == 481948-3.html 481948-3-ref.html # questionable test, see bug 488364
|
||||
== 482398-1.html 482398-1-ref.html
|
||||
== 482592-1a.xhtml 482592-1-ref.html
|
||||
== 482592-1b.xhtml 482592-1-ref.html
|
||||
|
|
Загрузка…
Ссылка в новой задаче