gecko-dev/gfx/thebes/src/gfxPangoFonts.cpp

2303 lines
83 KiB
C++
Raw Normal View History

/* -*- 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):
* Vladimir Vukicevic <vladimir@mozilla.com>
* Masayuki Nakano <masayuki@d-toybox.com>
* Behdad Esfahbod <behdad@gnome.org>
* Mats Palmgren <mats.palmgren@bredband.net>
* Karl Tomlinson <karlt+@karlt.net>, Mozilla Corporation
*
* based on nsFontMetricsPango.cpp by
* Christopher Blizzard <blizzard@mozilla.org>
*
* 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 PANGO_ENABLE_BACKEND
#include "prtypes.h"
#include "prlink.h"
#include "gfxTypes.h"
#include "nsMathUtils.h"
#include "gfxContext.h"
#include "gfxPlatformGtk.h"
#include "gfxPangoFonts.h"
#include "gfxFontconfigUtils.h"
#include <freetype/tttables.h>
#include <cairo.h>
#include <cairo-ft.h>
#include <fontconfig/fcfreetype.h>
#include <pango/pango.h>
#include <pango/pangofc-fontmap.h>
#include <gdk/gdkscreen.h>
#include <math.h>
#define FLOAT_PANGO_SCALE ((gfxFloat)PANGO_SCALE)
#ifndef PANGO_VERSION_CHECK
#define PANGO_VERSION_CHECK(x,y,z) 0
#endif
#ifndef PANGO_GLYPH_UNKNOWN_FLAG
#define PANGO_GLYPH_UNKNOWN_FLAG ((PangoGlyph)0x10000000)
#endif
#ifndef PANGO_GLYPH_EMPTY
#define PANGO_GLYPH_EMPTY ((PangoGlyph)0)
#endif
// For g a PangoGlyph,
#define IS_MISSING_GLYPH(g) ((g) & PANGO_GLYPH_UNKNOWN_FLAG)
#define IS_EMPTY_GLYPH(g) ((g) == PANGO_GLYPH_EMPTY)
// Same as pango_units_from_double from Pango 1.16 (but not in older versions)
int moz_pango_units_from_double(double d) {
return NS_lround(d * FLOAT_PANGO_SCALE);
}
static PangoLanguage *GetPangoLanguage(const nsACString& aLangGroup);
static cairo_scaled_font_t *CreateScaledFont(FcPattern *aPattern);
/* static */ gfxPangoFontCache* gfxPangoFontCache::sPangoFontCache = nsnull;
static PangoFontMap *gPangoFontMap;
/*
* gfxFcFont
*
* This is a gfxFont implementation using a CAIRO_FONT_TYPE_FT
* cairo_scaled_font created from an FcPattern.
*/
class gfxFcFont : public gfxFont {
public:
virtual ~gfxFcFont ();
static already_AddRefed<gfxFcFont> GetOrMakeFont(FcPattern *aPattern);
virtual const gfxFont::Metrics& GetMetrics();
virtual nsString GetUniqueName();
// Get the glyphID of a space
virtual PRUint32 GetSpaceGlyph() {
NS_ASSERTION(GetStyle()->size != 0,
"forgot to short-circuit a text run with zero-sized font?");
GetMetrics();
return mSpaceGlyph;
}
cairo_scaled_font_t *CairoScaledFont() { return mCairoFont; }
void GetGlyphExtents(PRUint32 aGlyph, cairo_text_extents_t* aExtents);
protected:
cairo_scaled_font_t *mCairoFont;
PRUint32 mSpaceGlyph;
Metrics mMetrics;
PRPackedBool mHasMetrics;
gfxFcFont(cairo_scaled_font_t *aCairoFont,
gfxPangoFontEntry *aFontEntry, const gfxFontStyle *aFontStyle);
virtual PRBool SetupCairoFont(gfxContext *aContext);
// key for locating a gfxFcFont corresponding to a cairo_scaled_font
static cairo_user_data_key_t sGfxFontKey;
};
class LockedFTFace {
public:
LockedFTFace(gfxFcFont *aFont)
: mGfxFont(aFont),
mFace(cairo_ft_scaled_font_lock_face(aFont->CairoScaledFont()))
{
}
~LockedFTFace()
{
if (mFace) {
cairo_ft_scaled_font_unlock_face(mGfxFont->CairoScaledFont());
}
}
/**
* Get extents for a simple character representable by a single glyph.
* The return value is the glyph id of that glyph or zero if no such glyph
* exists. aExtents is only set when this returns a non-zero glyph id.
*/
PRUint32 GetCharExtents(char aChar, cairo_text_extents_t* aExtents);
void GetMetrics(gfxFont::Metrics* aMetrics, PRUint32* aSpaceGlyph);
private:
nsRefPtr<gfxFcFont> mGfxFont;
FT_Face mFace;
};
/**
* gfxPangoFcFont:
*
* An implementation of PangoFcFont that wraps a gfxFont so that it can be
* passed to PangoRenderFc shapers.
*/
#define GFX_TYPE_PANGO_FC_FONT (gfx_pango_fc_font_get_type())
#define GFX_PANGO_FC_FONT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GFX_TYPE_PANGO_FC_FONT, gfxPangoFcFont))
#define GFX_IS_PANGO_FC_FONT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GFX_TYPE_PANGO_FC_FONT))
/* static */
GType gfx_pango_fc_font_get_type (void);
#define GFX_PANGO_FC_FONT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GFX_TYPE_PANGO_FC_FONT, gfxPangoFcFontClass))
#define GFX_IS_PANGO_FC_FONT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GFX_TYPE_PANGO_FC_FONT))
#define GFX_PANGO_FC_FONT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GFX_TYPE_PANGO_FC_FONT, gfxPangoFcFontClass))
// This struct is POD so that it can be used as a GObject.
struct gfxPangoFcFont {
PangoFcFont parent_instance;
gfxFcFont *mGfxFont;
static gfxFcFont *GfxFont(gfxPangoFcFont *self)
{
if (!self->mGfxFont) {
FcPattern *pattern = PANGO_FC_FONT(self)->font_pattern;
self->mGfxFont = gfxFcFont::GetOrMakeFont(pattern).get();
}
return self->mGfxFont;
}
static cairo_scaled_font_t *CairoFont(gfxPangoFcFont *self)
{
return gfxPangoFcFont::GfxFont(self)->CairoScaledFont();
}
};
struct gfxPangoFcFontClass {
PangoFcFontClass parent_class;
};
G_DEFINE_TYPE (gfxPangoFcFont, gfx_pango_fc_font, PANGO_TYPE_FC_FONT)
static void
gfx_pango_fc_font_init(gfxPangoFcFont *fontset)
{
}
static void
gfx_pango_fc_font_finalize(GObject *object)
{
gfxPangoFcFont *self = GFX_PANGO_FC_FONT(object);
if (self->mGfxFont)
self->mGfxFont->Release();
G_OBJECT_CLASS(gfx_pango_fc_font_parent_class)->finalize(object);
}
static void
gfx_pango_fc_font_get_glyph_extents(PangoFont *font, PangoGlyph glyph,
PangoRectangle *ink_rect,
PangoRectangle *logical_rect)
{
gfxPangoFcFont *self = GFX_PANGO_FC_FONT(font);
gfxFcFont *gfxFont = gfxPangoFcFont::GfxFont(self);
if (IS_MISSING_GLYPH(glyph)) {
const gfxFont::Metrics& metrics = gfxFont->GetMetrics();
PangoRectangle rect;
rect.x = 0;
rect.y = moz_pango_units_from_double(-metrics.maxAscent);
rect.width = moz_pango_units_from_double(metrics.aveCharWidth);
rect.height = moz_pango_units_from_double(metrics.maxHeight);
if (ink_rect) {
*ink_rect = rect;
}
if (logical_rect) {
*logical_rect = rect;
}
return;
}
if (logical_rect) {
// logical_rect.width is possibly used by pango_ot_buffer_output (used
// by many shapers) and used by fallback_engine_shape (possibly used
// by pango_shape and pango_itemize when no glyphs are found). I
// doubt the other fields will be used but we won't have any way to
// detecting if they are so we'd better set them.
const gfxFont::Metrics& metrics = gfxFont->GetMetrics();
logical_rect->y = moz_pango_units_from_double(-metrics.maxAscent);
logical_rect->height = moz_pango_units_from_double(metrics.maxHeight);
}
cairo_text_extents_t extents;
if (IS_EMPTY_GLYPH(glyph)) {
new (&extents) cairo_text_extents_t(); // zero
} else {
gfxFont->GetGlyphExtents(glyph, &extents);
}
if (ink_rect) {
ink_rect->x = moz_pango_units_from_double(extents.x_bearing);
ink_rect->y = moz_pango_units_from_double(extents.y_bearing);
ink_rect->width = moz_pango_units_from_double(extents.width);
ink_rect->height = moz_pango_units_from_double(extents.height);
}
if (logical_rect) {
logical_rect->x = 0;
logical_rect->width = moz_pango_units_from_double(extents.x_advance);
}
}
#ifdef DEBUG
static PangoFontMetrics *
gfx_pango_fc_font_get_metrics(PangoFont *font, PangoLanguage *language)
{
NS_WARNING("Using PangoFcFont::get_metrics");
return PANGO_FONT_CLASS(gfx_pango_fc_font_parent_class)->
get_metrics(font, language);
}
#endif
static FT_Face
gfx_pango_fc_font_lock_face(PangoFcFont *font)
{
gfxPangoFcFont *self = GFX_PANGO_FC_FONT(font);
return cairo_ft_scaled_font_lock_face(gfxPangoFcFont::CairoFont(self));
}
static void
gfx_pango_fc_font_unlock_face(PangoFcFont *font)
{
gfxPangoFcFont *self = GFX_PANGO_FC_FONT(font);
cairo_ft_scaled_font_unlock_face(gfxPangoFcFont::CairoFont(self));
}
static void
gfx_pango_fc_font_class_init (gfxPangoFcFontClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PangoFontClass *font_class = PANGO_FONT_CLASS (klass);
PangoFcFontClass *fc_font_class = PANGO_FC_FONT_CLASS (klass);
object_class->finalize = gfx_pango_fc_font_finalize;
#if 0
// This will need overriding for user fonts to defeat the PangoFcFontMap
// caching, unless each user font is guaranteed to have a unique filename.
font_class->get_coverage =
#endif
font_class->get_glyph_extents = gfx_pango_fc_font_get_glyph_extents;
#ifdef DEBUG
// non-DEBUG inherits from fc_font_class as this won't be used anyway.
font_class->get_metrics = gfx_pango_fc_font_get_metrics;
#endif
// fc_font_class->has_char,get_glyph are inherited
fc_font_class->lock_face = gfx_pango_fc_font_lock_face;
fc_font_class->unlock_face = gfx_pango_fc_font_unlock_face;
}
/**
* Recording a base PangoFont on a PangoContext
*/
static GQuark GetBaseFontQuark()
{
// Not using g_quark_from_static_string() because this module may be
// unloaded (which would leave a dangling pointer). Using
// g_quark_from_string() instead, which creates a small shutdown leak.
static GQuark quark = g_quark_from_string("moz-base-font");
return quark;
}
static PangoFont *
GetBaseFont(PangoContext *aContext)
{
return static_cast<PangoFont*>
(g_object_get_qdata(G_OBJECT(aContext), GetBaseFontQuark()));
}
static void
SetBaseFont(PangoContext *aContext, PangoFont *aBaseFont)
{
g_object_ref(aBaseFont);
g_object_set_qdata_full(G_OBJECT(aContext), GetBaseFontQuark(),
aBaseFont, g_object_unref);
}
/**
* gfxPangoFontset: An implementation of a PangoFontset for gfxPangoFontMap
*/
#define GFX_TYPE_PANGO_FONTSET (gfx_pango_fontset_get_type())
#define GFX_PANGO_FONTSET(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GFX_TYPE_PANGO_FONTSET, gfxPangoFontset))
#define GFX_IS_PANGO_FONTSET(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GFX_TYPE_PANGO_FONTSET))
/* static */
GType gfx_pango_fontset_get_type (void);
#define GFX_PANGO_FONTSET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GFX_TYPE_PANGO_FONTSET, gfxPangoFontsetClass))
#define GFX_IS_PANGO_FONTSET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GFX_TYPE_PANGO_FONTSET))
#define GFX_PANGO_FONTSET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GFX_TYPE_PANGO_FONTSET, gfxPangoFontsetClass))
// This struct is POD so that it can be used as a GObject.
struct gfxPangoFontset {
PangoFontset parent_instance;
PangoFontMap *mFontMap;
PangoContext *mContext;
PangoFontDescription *mFontDesc;
PangoLanguage *mLanguage;
PangoFont *mBaseFont;
PangoFontset *mFallbackFontset;
static PangoFontset *
NewFontset(PangoFontMap *aFontMap,
PangoContext *aContext,
const PangoFontDescription *aFontDesc,
PangoLanguage *aLanguage)
{
gfxPangoFontset *fontset = static_cast<gfxPangoFontset *>
(g_object_new(GFX_TYPE_PANGO_FONTSET, NULL));
fontset->mFontMap = aFontMap;
g_object_ref(aFontMap);
fontset->mContext = aContext;
g_object_ref(aContext);
fontset->mFontDesc = pango_font_description_copy(aFontDesc);
fontset->mLanguage = aLanguage;
fontset->mBaseFont = GetBaseFont(aContext);
if(fontset->mBaseFont)
g_object_ref(fontset->mBaseFont);
return PANGO_FONTSET(fontset);
}
};
struct gfxPangoFontsetClass {
PangoFontsetClass parent_class;
};
G_DEFINE_TYPE (gfxPangoFontset, gfx_pango_fontset, PANGO_TYPE_FONTSET)
static void
gfx_pango_fontset_init(gfxPangoFontset *fontset)
{
}
static void
gfx_pango_fontset_finalize(GObject *object)
{
gfxPangoFontset *self = GFX_PANGO_FONTSET(object);
if (self->mContext)
g_object_unref(self->mContext);
if (self->mFontDesc)
pango_font_description_free(self->mFontDesc);
if (self->mBaseFont)
g_object_unref(self->mBaseFont);
if (self->mFontMap)
g_object_unref(self->mFontMap);
if (self->mFallbackFontset)
g_object_unref(self->mFallbackFontset);
G_OBJECT_CLASS(gfx_pango_fontset_parent_class)->finalize(object);
}
static PangoLanguage *
gfx_pango_fontset_get_language(PangoFontset *fontset)
{
gfxPangoFontset *self = GFX_PANGO_FONTSET(fontset);
return self->mLanguage;
}
struct ForeachExceptBaseData {
PangoFont *mBaseFont;
PangoFontset *mFontset;
PangoFontsetForeachFunc mFunc;
gpointer mData;
};
static gboolean
foreach_except_base_cb(PangoFontset *fontset, PangoFont *font, gpointer data)
{
ForeachExceptBaseData *baseData =
static_cast<ForeachExceptBaseData *>(data);
// returning false means continue with the other fonts in the set
return font != baseData->mBaseFont &&
(*baseData->mFunc)(baseData->mFontset, font, baseData->mData);
}
static PangoFontset *
gfx_pango_font_map_load_fallback_fontset(PangoFontMap *fontmap,
PangoContext *context,
const PangoFontDescription *desc,
PangoLanguage *language);
static PangoFontset *
EnsureFallbackFontset(gfxPangoFontset *self)
{
if (!self->mFallbackFontset) {
// To consider:
//
// * If this is happening often (e.g. Chinese/Japanese pages where a
// Latin font is specified first), and Pango's 64-entry pattern
// cache is not large enough, then a fontset cache here could be
// helpful. Ideally we'd only cache the fonts that are actually
// accessed rather than all the fonts from the FcFontSort.
//
// * Mozilla's langGroup font prefs could be used to specify preferred
// fallback fonts for the script of the characters (as indicated by
// Pango in mLanguage), by doing the conversion from gfxFontGroup
// "families" to PangoFcFontMap "family" here.
self->mFallbackFontset =
gfx_pango_font_map_load_fallback_fontset(self->mFontMap,
self->mContext,
self->mFontDesc,
self->mLanguage);
}
return self->mFallbackFontset;
}
static void
gfx_pango_fontset_foreach(PangoFontset *fontset, PangoFontsetForeachFunc func,
gpointer data)
{
gfxPangoFontset *self = GFX_PANGO_FONTSET(fontset);
if (self->mBaseFont && (*func)(fontset, self->mBaseFont, data))
return;
// Falling back to secondary fonts
PangoFontset *childFontset = EnsureFallbackFontset(self);
ForeachExceptBaseData baseData = { self->mBaseFont, fontset, func, data };
pango_fontset_foreach(childFontset, foreach_except_base_cb, &baseData);
}
static PangoFont *
gfx_pango_fontset_get_font(PangoFontset *fontset, guint wc)
{
gfxPangoFontset *self = GFX_PANGO_FONTSET(fontset);
PangoCoverageLevel baseLevel = PANGO_COVERAGE_NONE;
if (self->mBaseFont) {
// PangoFcFontMap caches this:
PangoCoverage *coverage =
pango_font_get_coverage(self->mBaseFont, self->mLanguage);
if (coverage) {
baseLevel = pango_coverage_get(coverage, wc);
pango_coverage_unref(coverage);
}
}
if (baseLevel != PANGO_COVERAGE_EXACT) {
PangoFontset *childFontset = EnsureFallbackFontset(self);
PangoFont *childFont = pango_fontset_get_font(childFontset, wc);
if (!self->mBaseFont || childFont == self->mBaseFont)
return childFont;
if (childFont) {
PangoCoverage *coverage =
pango_font_get_coverage(childFont, self->mLanguage);
if (coverage) {
PangoCoverageLevel childLevel =
pango_coverage_get(coverage, wc);
pango_coverage_unref(coverage);
// Only use the child font if better than the base font.
if (childLevel > baseLevel)
return childFont;
}
g_object_unref(childFont);
}
}
g_object_ref(self->mBaseFont);
return self->mBaseFont;
}
static void
gfx_pango_fontset_class_init (gfxPangoFontsetClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PangoFontsetClass *fontset_class = PANGO_FONTSET_CLASS (klass);
object_class->finalize = gfx_pango_fontset_finalize;
fontset_class->get_font = gfx_pango_fontset_get_font;
// inherit fontset_class->get_metrics (which won't be used anyway)
fontset_class->get_language = gfx_pango_fontset_get_language;
fontset_class->foreach = gfx_pango_fontset_foreach;
}
/**
* gfxPangoFontMap: An implementation of a PangoFontMap.
*
* This allows the primary (base) font to be specified. There are two
* advantages to this:
*
* 1. Always using the same base font irrespective of the language that Pango
* chooses for the script means that PANGO_SCRIPT_COMMON characters are
* consistently rendered with the same font. (Bug 339513 and bug 416725)
*
* 2. We normally have the base font from the gfxFont cache so this saves a
* FcFontSort when the entry has expired from Pango's much smaller pattern
* cache.
*/
#define GFX_TYPE_PANGO_FONT_MAP (gfx_pango_font_map_get_type())
#define GFX_PANGO_FONT_MAP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GFX_TYPE_PANGO_FONT_MAP, gfxPangoFontMap))
#define GFX_IS_PANGO_FONT_MAP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GFX_TYPE_PANGO_FONT_MAP))
GType gfx_pango_font_map_get_type (void);
#define GFX_PANGO_FONT_MAP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GFX_TYPE_PANGO_FONT_MAP, gfxPangoFontMapClass))
#define GFX_IS_PANGO_FONT_MAP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GFX_TYPE_PANGO_FONT_MAP))
#define GFX_PANGO_FONT_MAP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GFX_TYPE_PANGO_FONT_MAP, gfxPangoFontMapClass))
// Do not instantiate this class directly, but use NewFontMap.
// This struct is POD so that it can be used as a GObject.
struct gfxPangoFontMap {
PangoFcFontMap parent_instance;
static PangoFontMap *
NewFontMap()
{
gfxPangoFontMap *fontmap = static_cast<gfxPangoFontMap *>
(g_object_new(GFX_TYPE_PANGO_FONT_MAP, NULL));
return PANGO_FONT_MAP(fontmap);
}
};
struct gfxPangoFontMapClass {
PangoFcFontMapClass parent_class;
};
G_DEFINE_TYPE (gfxPangoFontMap, gfx_pango_font_map, PANGO_TYPE_FC_FONT_MAP)
static void
gfx_pango_font_map_init(gfxPangoFontMap *fontset)
{
}
static PangoFont *
gfx_pango_font_map_load_font(PangoFontMap *fontmap, PangoContext *context,
const PangoFontDescription *description)
{
PangoFont *baseFont = GetBaseFont(context);
if (baseFont) {
g_object_ref(baseFont);
return baseFont;
}
return PANGO_FONT_MAP_CLASS(gfx_pango_font_map_parent_class)->
load_font(fontmap, context, description);
}
static PangoFontset *
gfx_pango_font_map_load_fontset(PangoFontMap *fontmap, PangoContext *context,
const PangoFontDescription *desc,
PangoLanguage *language)
{
return gfxPangoFontset::NewFontset(fontmap, context, desc, language);
}
static PangoFontset *
gfx_pango_font_map_load_fallback_fontset(PangoFontMap *fontmap,
PangoContext *context,
const PangoFontDescription *desc,
PangoLanguage *language)
{
return PANGO_FONT_MAP_CLASS(gfx_pango_font_map_parent_class)->
load_fontset(fontmap, context, desc, language);
}
static void
gfx_pango_font_map_list_families(PangoFontMap *fontmap,
PangoFontFamily ***families, int *n_families)
{
return PANGO_FONT_MAP_CLASS(gfx_pango_font_map_parent_class)->
list_families(fontmap, families, n_families);
}
static double
gfx_pango_font_map_get_resolution(PangoFcFontMap *fcfontmap,
PangoContext *context)
{
// This merely enables the FC_SIZE field of the pattern to be accurate.
// We use gfxPlatformGtk::DPI() much of the time...
return gfxPlatformGtk::DPI();
}
static void
gfx_pango_font_map_context_substitute(PangoFcFontMap *fontmap,
PangoContext *context,
FcPattern *pattern)
{
FcConfigSubstitute(NULL, pattern, FcMatchPattern);
// XXXkt This gets cairo_font_options_t for the Screen. We should have
// different font options for printing (no hinting) but we are not told
// what we are measuring for.
const cairo_font_options_t *options =
gdk_screen_get_font_options(gdk_screen_get_default());
cairo_ft_font_options_substitute(options, pattern);
FcDefaultSubstitute(pattern);
}
static PangoFcFont *
gfx_pango_font_map_create_font(PangoFcFontMap *fontmap,
PangoContext *context,
const PangoFontDescription *desc,
FcPattern *pattern)
{
// A pattern is needed for pango_fc_font_finalize.
//
// Adding a ref to one of fontconfig's patterns would use much less memory
// than using the fully resolved pattern here. This could be done by
// setting the PangoFcFont field is_hinted directly. (is_hinted is used
// by pango_fc_font_kern_glyphs, which is sometimes used by
// pango_ot_buffer_output.) is_transformed is not used but maybe it
// should be set too. describe_absolute would need to be overridden if
// required, and description would need to be set if describe is required
// but not overridden.
//
// An alternative memory-saving effort might remove elements from the
// resolved pattern that are unnecessary.
// Protect against any fontconfig settings that may have incorrectly
// modified the pixelsize.
PRBool newPattern = PR_FALSE;
double size;
if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch) {
size = pango_font_description_get_size(desc) / FLOAT_PANGO_SCALE;
pattern = FcPatternDuplicate(pattern);
newPattern = PR_TRUE;
FcPatternDel(pattern, FC_PIXEL_SIZE);
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size);
}
PangoFcFont *font = PANGO_FC_FONT(g_object_new(GFX_TYPE_PANGO_FC_FONT,
"pattern", pattern, NULL));
if (newPattern) {
FcPatternDestroy(pattern);
}
return font;
}
static void
gfx_pango_font_map_class_init(gfxPangoFontMapClass *klass)
{
// inherit GObjectClass::finalize from the parent as there is nothing to
// finalize in this object.
PangoFontMapClass *fontmap_class = PANGO_FONT_MAP_CLASS (klass);
fontmap_class->load_font = gfx_pango_font_map_load_font;
fontmap_class->load_fontset = gfx_pango_font_map_load_fontset;
fontmap_class->list_families = gfx_pango_font_map_list_families;
// inherit fontmap_class->shape_engine_type from PangoFcFontMap
PangoFcFontMapClass *fcfontmap_class = PANGO_FC_FONT_MAP_CLASS (klass);
fcfontmap_class->get_resolution = gfx_pango_font_map_get_resolution;
// context_key_* virtual functions are only necessary if we want to
// dynamically respond to changes in the screen cairo_font_options_t.
fcfontmap_class->context_substitute = gfx_pango_font_map_context_substitute;
fcfontmap_class->create_font = gfx_pango_font_map_create_font;
}
/**
** gfxPangoFontGroup
**/
static int
FFRECountHyphens (const nsAString &aFFREName)
{
int h = 0;
PRInt32 hyphen = 0;
while ((hyphen = aFFREName.FindChar('-', hyphen)) >= 0) {
++h;
++hyphen;
}
return h;
}
static PRBool
FontCallback (const nsAString& fontName, const nsACString& genericName,
void *closure)
{
nsStringArray *sa = static_cast<nsStringArray*>(closure);
// We ignore prefs that have three hypens since they are X style prefs.
if (genericName.Length() && FFRECountHyphens(fontName) >= 3)
return PR_TRUE;
if (sa->IndexOf(fontName) < 0) {
sa->AppendString(fontName);
}
return PR_TRUE;
}
gfxPangoFontGroup::gfxPangoFontGroup (const nsAString& families,
const gfxFontStyle *aStyle)
: gfxFontGroup(families, aStyle),
mBasePangoFont(nsnull), mAdjustedSize(0)
{
mFonts.AppendElements(1);
}
gfxPangoFontGroup::~gfxPangoFontGroup()
{
if (mBasePangoFont)
g_object_unref(mBasePangoFont);
}
gfxFontGroup *
gfxPangoFontGroup::Copy(const gfxFontStyle *aStyle)
{
return new gfxPangoFontGroup(mFamilies, aStyle);
}
// A string of family names suitable for fontconfig
void
gfxPangoFontGroup::GetFcFamilies(nsAString& aFcFamilies)
{
nsStringArray familyArray;
// Leave non-existing fonts in the list so that fontconfig can get the
// best match.
ForEachFontInternal(mFamilies, mStyle.langGroup, PR_TRUE, PR_FALSE,
FontCallback, &familyArray);
if (familyArray.Count()) {
int i = 0;
while (1) {
aFcFamilies.Append(*familyArray[i]);
++i;
if (i >= familyArray.Count())
break;
aFcFamilies.Append(NS_LITERAL_STRING(","));
}
}
else {
// XXX If there are no fonts, we should use dummy family.
// Pango will resolve from this.
// behdad: yep, looks good.
// printf("%s(%s)\n", NS_ConvertUTF16toUTF8(families).get(),
// aStyle->langGroup.get());
aFcFamilies.Append(NS_LITERAL_STRING("sans-serif"));
}
}
gfxFont *
gfxPangoFontGroup::GetFontAt(PRInt32 i) {
NS_PRECONDITION(i == 0, "Only have one font");
if (!mFonts[0]) {
PangoFont *pangoFont = GetBasePangoFont();
mFonts[0] = gfxPangoFcFont::GfxFont(GFX_PANGO_FC_FONT(pangoFont));
}
return mFonts[0];
}
/**
** gfxFcFont
**/
cairo_user_data_key_t gfxFcFont::sGfxFontKey;
gfxFcFont::gfxFcFont(cairo_scaled_font_t *aCairoFont,
gfxPangoFontEntry *aFontEntry,
const gfxFontStyle *aFontStyle)
: gfxFont(aFontEntry, aFontStyle),
mCairoFont(aCairoFont),
mHasMetrics(PR_FALSE)
{
cairo_scaled_font_reference(mCairoFont);
cairo_scaled_font_set_user_data(mCairoFont, &sGfxFontKey, this, NULL);
}
gfxFcFont::~gfxFcFont()
{
cairo_scaled_font_set_user_data(mCairoFont, &sGfxFontKey, NULL, NULL);
cairo_scaled_font_destroy(mCairoFont);
}
/* static */ void
gfxPangoFontGroup::Shutdown()
{
gfxPangoFontCache::Shutdown();
if (gPangoFontMap) {
if (PANGO_IS_FC_FONT_MAP (gPangoFontMap)) {
// This clears circular references from the fontmap to itself
// through its fonts.
pango_fc_font_map_shutdown(PANGO_FC_FONT_MAP(gPangoFontMap));
}
g_object_unref(gPangoFontMap);
gPangoFontMap = NULL;
}
}
static PangoStyle
ThebesStyleToPangoStyle (const gfxFontStyle *fs)
{
if (fs->style == FONT_STYLE_ITALIC)
return PANGO_STYLE_ITALIC;
if (fs->style == FONT_STYLE_OBLIQUE)
return PANGO_STYLE_OBLIQUE;
return PANGO_STYLE_NORMAL;
}
static PangoWeight
ThebesStyleToPangoWeight (const gfxFontStyle *fs)
{
PRInt32 w = fs->weight;
/*
* weights come in two parts crammed into one
* integer -- the "base" weight is weight / 100,
* the rest of the value is the "offset" from that
* weight -- the number of steps to move to adjust
* the weight in the list of supported font weights,
* this value can be negative or positive.
*/
PRInt32 baseWeight = (w + 50) / 100;
PRInt32 offset = w - baseWeight * 100;
/* clip weights to range 0 to 9 */
if (baseWeight < 0)
baseWeight = 0;
if (baseWeight > 9)
baseWeight = 9;
/* Map from weight value to fcWeights index */
static const int fcWeightLookup[10] = {
0, 0, 0, 0, 1, 1, 2, 3, 3, 4,
};
PRInt32 fcWeight = fcWeightLookup[baseWeight];
/*
* adjust by the offset value, make sure we stay inside the
* fcWeights table
*/
fcWeight += offset;
if (fcWeight < 0)
fcWeight = 0;
if (fcWeight > 4)
fcWeight = 4;
/* Map to final PANGO_WEIGHT value */
static const int fcWeights[5] = {
349,
449,
649,
749,
900
};
return (PangoWeight)fcWeights[fcWeight];
}
/* Note this doesn't check sizeAdjust */
static PangoFontDescription *
NewPangoFontDescription(const nsAString &aName, const gfxFontStyle *aFontStyle)
{
PangoFontDescription *fontDesc = pango_font_description_new();
pango_font_description_set_family(fontDesc,
NS_ConvertUTF16toUTF8(aName).get());
pango_font_description_set_absolute_size
(fontDesc, moz_pango_units_from_double(aFontStyle->size));
pango_font_description_set_style(fontDesc,
ThebesStyleToPangoStyle(aFontStyle));
pango_font_description_set_weight(fontDesc,
ThebesStyleToPangoWeight(aFontStyle));
return fontDesc;
}
/**
* The following gfxPangoFonts are accessed from the PangoFont, not from the
* gfxFontCache hash table. The gfxFontCache hash table is keyed by desired
* family and style, whereas here we only know actual family and style. There
* may be more than one of these fonts with the same family and style, but
* different PangoFont and actual font face.
*
* The point of this is to record the exact font face for gfxTextRun glyph
* indices. The style of this font does not necessarily represent the exact
* gfxFontStyle used to build the text run. Notably, the language is not
* recorded.
*/
/* static */
already_AddRefed<gfxFcFont>
gfxFcFont::GetOrMakeFont(FcPattern *aPattern)
{
cairo_scaled_font_t *cairoFont = CreateScaledFont(aPattern);
nsRefPtr<gfxFcFont> font = static_cast<gfxFcFont*>
(cairo_scaled_font_get_user_data(cairoFont, &sGfxFontKey));
if (!font) {
double size;
if (FcPatternGetDouble(aPattern,
FC_PIXEL_SIZE, 0, &size) != FcResultMatch) {
NS_NOTREACHED("No size on pattern");
size = 0.0;
}
// Shouldn't actually need to take too much care about the correct
// name or style, as size is the only thing expected to be important.
PRUint8 style = gfxFontconfigUtils::GetThebesStyle(aPattern);
PRUint16 weight = gfxFontconfigUtils::GetThebesWeight(aPattern);
// The LangSet in the FcPattern does not have an order so there is no
// one particular language to choose and converting the set to a
// string through FcNameUnparse() is more trouble than it's worth.
NS_NAMED_LITERAL_CSTRING(langGroup, "x-unicode");
gfxFontStyle fontStyle(style, weight, size, langGroup, 0.0,
PR_TRUE, PR_FALSE);
FcChar8 *fc_file; // unsigned char
const char *file; // signed for Mozilla string APIs
if (FcPatternGetString(aPattern,
FC_FILE, 0, &fc_file) == FcResultMatch) {
file = reinterpret_cast<char*>(fc_file);
} else {
// cairo won't know which font to open without a file.
// (We don't create fonts from an FT_Face.)
NS_NOTREACHED("Fonts without a file are not supported");
static const char *noFile = "NO FILE";
file = noFile;
}
int index;
if (FcPatternGetInteger(aPattern, FC_INDEX, 0, &index)
!= FcResultMatch) {
// cairo won't know what to do here either.
NS_NOTREACHED("No index in pattern");
index = 0;
}
// Get a unique face name from the file and id.
nsAutoString name;
AppendUTF8toUTF16(file, name);
if (index != 0) {
name.AppendLiteral("/");
name.AppendInt(index);
}
nsRefPtr<gfxPangoFontEntry> fe = new gfxPangoFontEntry(name);
// Note that the unique face in the name/fe and the gfxFontStyle are
// not necessarily enough to provide a key that will describe a unique
// font. cairoFont contains information from aPattern, which is a
// fully resolved pattern from FcFontRenderPrepare.
// FcFontRenderPrepare takes the requested pattern and the face
// pattern as input and can modify elements of the resulting pattern
// that affect rendering but are not included in the gfxFontStyle.
font = new gfxFcFont(cairoFont, fe, &fontStyle);
}
cairo_scaled_font_destroy(cairoFont);
return font.forget();
}
static PangoContext *
GetPangoContext()
{
PangoContext *context = pango_context_new();
// Change the font map to recognize a base font on the context
if (!gPangoFontMap) {
gPangoFontMap = gfxPangoFontMap::NewFontMap();
}
pango_context_set_font_map(context, gPangoFontMap);
return context;
}
static PangoFont*
LoadPangoFont(PangoContext *aPangoCtx, const PangoFontDescription *aPangoFontDesc)
{
gfxPangoFontCache *cache = gfxPangoFontCache::GetPangoFontCache();
if (!cache)
return nsnull; // Error
PangoFont* pangoFont = cache->Get(aPangoFontDesc);
if (!pangoFont) {
pangoFont = pango_context_load_font(aPangoCtx, aPangoFontDesc);
if (pangoFont) {
cache->Put(aPangoFontDesc, pangoFont);
}
}
return pangoFont;
}
// The font group holds the reference to the PangoFont
PangoFont *
gfxPangoFontGroup::GetBasePangoFont()
{
if (mBasePangoFont)
return mBasePangoFont;
nsAutoString fcFamilies;
GetFcFamilies(fcFamilies);
PangoFontDescription *pangoFontDesc =
NewPangoFontDescription(fcFamilies, GetStyle());
PangoContext *pangoCtx = GetPangoContext();
if (!GetStyle()->langGroup.IsEmpty()) {
PangoLanguage *lang = GetPangoLanguage(GetStyle()->langGroup);
if (lang)
pango_context_set_language(pangoCtx, lang);
}
PangoFont *pangoFont = LoadPangoFont(pangoCtx, pangoFontDesc);
gfxFloat size = GetStyle()->size;
if (size != 0.0 && GetStyle()->sizeAdjust != 0.0) {
LockedFTFace
face(gfxPangoFcFont::GfxFont(GFX_PANGO_FC_FONT(pangoFont)));
// Could try xHeight from TrueType/OpenType fonts.
cairo_text_extents_t extents;
if (face.GetCharExtents('x', &extents) &&
extents.y_bearing < 0.0) {
gfxFloat aspect = -extents.y_bearing / size;
size = GetStyle()->GetAdjustedSize(aspect);
pango_font_description_set_absolute_size
(pangoFontDesc, moz_pango_units_from_double(size));
g_object_unref(pangoFont);
pangoFont = LoadPangoFont(pangoCtx, pangoFontDesc);
}
}
if (pangoFontDesc)
pango_font_description_free(pangoFontDesc);
if (pangoCtx)
g_object_unref(pangoCtx);
mBasePangoFont = pangoFont;
mAdjustedSize = size;
return pangoFont;
}
void
gfxFcFont::GetGlyphExtents(PRUint32 aGlyph, cairo_text_extents_t* aExtents)
{
NS_PRECONDITION(aExtents != NULL, "aExtents must not be NULL");
cairo_glyph_t glyphs[1];
glyphs[0].index = aGlyph;
glyphs[0].x = 0.0;
glyphs[0].y = 0.0;
// cairo does some caching for us here but perhaps a small gain could be
// made by caching more. It is usually only the advance that is needed,
// so caching only the advance could allow many requests to be cached with
// little memory use. Ideally this cache would be merged with
// gfxGlyphExtents.
cairo_scaled_font_glyph_extents(CairoScaledFont(), glyphs, 1, aExtents);
}
PRUint32
LockedFTFace::GetCharExtents(char aChar,
cairo_text_extents_t* aExtents)
{
NS_PRECONDITION(aExtents != NULL, "aExtents must not be NULL");
if (!mFace)
return 0;
// pango_fc_font_real_get_glyph uses FcFreeTypeCharIndex which may change
// the charmap currently selected on the FT_Face, so, while
// pango_fc_font_real_get_glyph might be used, we should use the same
// function so as to search the charmaps.
//
// Unfortunately this considers the mac/roman cmap even when there is a
// unicode cmap, which will be bad for symbol fonts, so we should do this
// ourselves, perhaps with a lightweight cache like
// pango_fc_font_real_get_glyph uses.
FT_UInt gid = FcFreeTypeCharIndex(mFace, aChar); // glyph id
if (gid) {
mGfxFont->GetGlyphExtents(gid, aExtents);
}
return gid;
}
// rounding and truncation functions for a Freetype fixed point number
// (FT26Dot6) stored in a 32bit integer with high 26 bits for the integer
// part and low 6 bits for the fractional part.
#define FLOAT_FROM_26_6(x) ((x) / 64.0)
#define FLOAT_FROM_16_16(x) ((x) / 65536.0)
#define ROUND_26_6_TO_INT(x) ((x) >= 0 ? ((32 + (x)) >> 6) \
: -((32 - (x)) >> 6))
// aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics
static inline FT_Long
ScaleRoundDesignUnits(FT_Short aDesignMetric, FT_Fixed aScale)
{
FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale);
return ROUND_26_6_TO_INT(fixed26dot6);
}
// Snap a line to pixels while keeping the center and size of the line as
// close to the original position as possible.
//
// Pango does similar snapping for underline and strikethrough when fonts are
// hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the
// top and size of lines. Optimizing the distance between the line and
// baseline is probably good for the gap between text and underline, but
// optimizing the center of the line is better for positioning strikethough.
static void
SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize)
{
gfxFloat snappedSize = PR_MAX(NS_floor(aSize + 0.5), 1.0);
// Correct offset for change in size
gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize);
// Snap offset
aOffset = NS_floor(offset + 0.5);
aSize = snappedSize;
}
void
LockedFTFace::GetMetrics(gfxFont::Metrics* aMetrics, PRUint32* aSpaceGlyph)
{
NS_PRECONDITION(aMetrics != NULL, "aMetrics must not be NULL");
NS_PRECONDITION(aSpaceGlyph != NULL, "aSpaceGlyph must not be NULL");
if (NS_UNLIKELY(!mFace)) {
// No face. This unfortunate situation might happen if the font
// file is (re)moved at the wrong time.
aMetrics->emHeight = mGfxFont->GetStyle()->size;
aMetrics->emAscent = 0.8 * aMetrics->emHeight;
aMetrics->emDescent = 0.2 * aMetrics->emHeight;
aMetrics->maxAscent = aMetrics->emAscent;
aMetrics->maxDescent = aMetrics->maxDescent;
aMetrics->maxHeight = aMetrics->emHeight;
aMetrics->internalLeading = 0.0;
aMetrics->externalLeading = 0.2 * aMetrics->emHeight;
aSpaceGlyph = 0;
aMetrics->spaceWidth = 0.5 * aMetrics->emHeight;
aMetrics->maxAdvance = aMetrics->spaceWidth;
aMetrics->aveCharWidth = aMetrics->spaceWidth;
aMetrics->zeroOrAveCharWidth = aMetrics->spaceWidth;
aMetrics->xHeight = 0.5 * aMetrics->emHeight;
aMetrics->underlineSize = aMetrics->emHeight / 14.0;
aMetrics->underlineOffset = -aMetrics->underlineSize;
aMetrics->strikeoutOffset = 0.25 * aMetrics->emHeight;
aMetrics->strikeoutSize = aMetrics->underlineSize;
aMetrics->superscriptOffset = aMetrics->xHeight;
aMetrics->subscriptOffset = aMetrics->xHeight;
return;
}
const FT_Size_Metrics& ftMetrics = mFace->size->metrics;
// Scale for vertical design metric conversion: pixels per design unit.
gfxFloat yScale;
if (FT_IS_SCALABLE(mFace)) {
// Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not
// have subpixel accuracy.
//
// FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its
// (fractional) value is a factor that converts vertical metrics from
// design units to units of 1/64 pixels, so that the result may be
// interpreted as pixels in 26.6 fixed point format.
yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale));
aMetrics->emHeight = mFace->units_per_EM * yScale;
} else { // Not scalable.
// FT_Size_Metrics doc says x_scale is "only relevant for scalable
// font formats".
gfxFloat emUnit = mFace->units_per_EM;
aMetrics->emHeight = ftMetrics.y_ppem;
yScale = aMetrics->emHeight / emUnit;
}
TT_OS2 *os2 =
static_cast<TT_OS2*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_os2));
aMetrics->maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender);
aMetrics->maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender);
aMetrics->maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance);
gfxFloat lineHeight;
if (os2 && os2->sTypoAscender) {
aMetrics->emAscent = os2->sTypoAscender * yScale;
aMetrics->emDescent = -os2->sTypoDescender * yScale;
FT_Short typoHeight =
os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap;
lineHeight = typoHeight * yScale;
// maxAscent/maxDescent get used for frame heights, and some fonts
// don't have the HHEA table ascent/descent set (bug 279032).
if (aMetrics->emAscent > aMetrics->maxAscent)
aMetrics->maxAscent = aMetrics->emAscent;
if (aMetrics->emDescent > aMetrics->maxDescent)
aMetrics->maxDescent = aMetrics->emDescent;
} else {
aMetrics->emAscent = aMetrics->maxAscent;
aMetrics->emDescent = aMetrics->maxDescent;
lineHeight = FLOAT_FROM_26_6(ftMetrics.height);
}
cairo_text_extents_t extents;
*aSpaceGlyph = GetCharExtents(' ', &extents);
if (*aSpaceGlyph) {
aMetrics->spaceWidth = extents.x_advance;
} else {
aMetrics->spaceWidth = aMetrics->maxAdvance; // guess
}
aMetrics->zeroOrAveCharWidth = 0.0;
if (GetCharExtents('0', &extents)) {
aMetrics->zeroOrAveCharWidth = extents.x_advance;
}
// Prefering a measured x over sxHeight because sxHeight doesn't consider
// hinting, but maybe the x extents are not quite right in some fancy
// script fonts. CSS 2.1 suggests possibly using the height of an "o",
// which would have a more consistent glyph across fonts.
if (GetCharExtents('x', &extents) && extents.y_bearing < 0.0) {
aMetrics->xHeight = -extents.y_bearing;
aMetrics->aveCharWidth = extents.x_advance;
} else {
if (os2 && os2->sxHeight) {
aMetrics->xHeight = os2->sxHeight * yScale;
} else {
// CSS 2.1, section 4.3.2 Lengths: "In the cases where it is
// impossible or impractical to determine the x-height, a value of
// 0.5em should be used."
aMetrics->xHeight = 0.5 * aMetrics->emHeight;
}
aMetrics->aveCharWidth = 0.0; // updated below
}
// aveCharWidth is used for the width of text input elements so be
// liberal rather than conservative in the estimate.
if (os2 && os2->xAvgCharWidth) {
// Round to pixels as this is compared with maxAdvance to guess
// whether this is a fixed width font.
gfxFloat avgCharWidth =
ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale);
aMetrics->aveCharWidth =
PR_MAX(aMetrics->aveCharWidth, avgCharWidth);
}
aMetrics->aveCharWidth =
PR_MAX(aMetrics->aveCharWidth, aMetrics->zeroOrAveCharWidth);
if (aMetrics->aveCharWidth == 0.0) {
aMetrics->aveCharWidth = aMetrics->spaceWidth;
}
if (aMetrics->zeroOrAveCharWidth == 0.0) {
aMetrics->zeroOrAveCharWidth = aMetrics->aveCharWidth;
}
// Apparently hinting can mean that max_advance is not always accurate.
aMetrics->maxAdvance =
PR_MAX(aMetrics->maxAdvance, aMetrics->aveCharWidth);
// gfxFont::Metrics::underlineOffset is the position of the top of the
// underline.
//
// FT_FaceRec documentation describes underline_position as "the
// center of the underlining stem". This was the original definition
// of the PostScript metric, but in the PostScript table of OpenType
// fonts the metric is "the top of the underline"
// (http://www.microsoft.com/typography/otspec/post.htm), and FreeType
// (up to version 2.3.7) doesn't make any adjustment.
//
// Therefore get the underline position directly from the table
// ourselves when this table exists. Use FreeType's metrics for
// other (including older PostScript) fonts.
if (mFace->underline_position && mFace->underline_thickness) {
aMetrics->underlineSize = mFace->underline_thickness * yScale;
TT_Postscript *post = static_cast<TT_Postscript*>
(FT_Get_Sfnt_Table(mFace, ft_sfnt_post));
if (post && post->underlinePosition) {
aMetrics->underlineOffset = post->underlinePosition * yScale;
} else {
aMetrics->underlineOffset = mFace->underline_position * yScale
+ 0.5 * aMetrics->underlineSize;
}
} else { // No underline info.
// Imitate Pango.
aMetrics->underlineSize = aMetrics->emHeight / 14.0;
aMetrics->underlineOffset = -aMetrics->underlineSize;
}
if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition) {
aMetrics->strikeoutSize = os2->yStrikeoutSize * yScale;
aMetrics->strikeoutOffset = os2->yStrikeoutPosition * yScale;
} else { // No strikeout info.
aMetrics->strikeoutSize = aMetrics->underlineSize;
// Use OpenType spec's suggested position for Roman font.
aMetrics->strikeoutOffset = aMetrics->emHeight * 409.0 / 2048.0
+ 0.5 * aMetrics->strikeoutSize;
}
SnapLineToPixels(aMetrics->strikeoutOffset, aMetrics->strikeoutSize);
if (os2 && os2->ySuperscriptYOffset) {
gfxFloat val = ScaleRoundDesignUnits(os2->ySuperscriptYOffset,
ftMetrics.y_scale);
aMetrics->superscriptOffset = PR_MAX(1.0, val);
} else {
aMetrics->superscriptOffset = aMetrics->xHeight;
}
if (os2 && os2->ySubscriptYOffset) {
gfxFloat val = ScaleRoundDesignUnits(os2->ySubscriptYOffset,
ftMetrics.y_scale);
// some fonts have the incorrect sign.
val = fabs(val);
aMetrics->subscriptOffset = PR_MAX(1.0, val);
} else {
aMetrics->subscriptOffset = aMetrics->xHeight;
}
aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
// Ensure emAscent + emDescent == emHeight
gfxFloat sum = aMetrics->emAscent + aMetrics->emDescent;
aMetrics->emAscent = sum > 0.0 ?
aMetrics->emAscent * aMetrics->emHeight / sum : 0.0;
aMetrics->emDescent = aMetrics->emHeight - aMetrics->emAscent;
aMetrics->internalLeading = aMetrics->maxHeight - aMetrics->emHeight;
// Text input boxes currently don't work well with lineHeight < maxHeight
// (with Verdana, for example).
aMetrics->externalLeading = PR_MAX(lineHeight - aMetrics->maxHeight, 0);
}
const gfxFont::Metrics&
gfxFcFont::GetMetrics()
{
if (mHasMetrics)
return mMetrics;
if (NS_UNLIKELY(GetStyle()->size <= 0.0)) {
new(&mMetrics) gfxFont::Metrics(); // zero initialize
mSpaceGlyph = 0;
} else {
LockedFTFace(this).GetMetrics(&mMetrics, &mSpaceGlyph);
}
SanitizeMetrics(&mMetrics, PR_FALSE);
2006-01-10 22:58:17 +00:00
#if 0
// printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size);
// printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font)));
fprintf (stderr, "Font: %s\n", NS_ConvertUTF16toUTF8(GetName()).get());
fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
fprintf (stderr, " maxAscent: %f maxDescent: %f\n", mMetrics.maxAscent, mMetrics.maxDescent);
fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading);
fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f suOff: %f suSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize, mMetrics.superscriptOffset, mMetrics.subscriptOffset);
#endif
mHasMetrics = PR_TRUE;
return mMetrics;
}
nsString
gfxFcFont::GetUniqueName()
{
return GetName();
}
/**
** gfxTextRun
*
* A serious problem:
*
* -- We draw with a font that's hinted for the CTM, but we measure with a font
* hinted to the identity matrix, so our "bounding metrics" may not be accurate.
*
**/
/**
* We use this to append an LTR or RTL Override character to the start of the
* string. This forces Pango to honour our direction even if there are neutral characters
* in the string.
*/
static PRInt32 AppendDirectionalIndicatorUTF8(PRBool aIsRTL, nsACString& aString)
{
static const PRUnichar overrides[2][2] =
{ { 0x202d, 0 }, { 0x202e, 0 }}; // LRO, RLO
AppendUTF16toUTF8(overrides[aIsRTL], aString);
return 3; // both overrides map to 3 bytes in UTF8
}
gfxTextRun *
gfxPangoFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
const Parameters *aParams, PRUint32 aFlags)
{
NS_ASSERTION(aFlags & TEXT_IS_8BIT, "8bit should have been set");
2007-11-16 01:51:59 +00:00
gfxTextRun *run = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
if (!run)
return nsnull;
PRBool isRTL = run->IsRightToLeft();
if ((aFlags & TEXT_IS_ASCII) && !isRTL) {
// We don't need to send an override character here, the characters must be all LTR
const gchar *utf8Chars = reinterpret_cast<const gchar*>(aString);
InitTextRun(run, utf8Chars, aLength, 0, PR_TRUE);
} else {
// this is really gross...
const char *chars = reinterpret_cast<const char*>(aString);
NS_ConvertASCIItoUTF16 unicodeString(chars, aLength);
nsCAutoString utf8;
PRInt32 headerLen = AppendDirectionalIndicatorUTF8(isRTL, utf8);
AppendUTF16toUTF8(unicodeString, utf8);
InitTextRun(run, utf8.get(), utf8.Length(), headerLen, PR_TRUE);
}
run->FetchGlyphExtents(aParams->mContext);
return run;
}
#if defined(ENABLE_FAST_PATH_8BIT)
PRBool
gfxPangoFontGroup::CanTakeFastPath(PRUint32 aFlags)
{
// Can take fast path only if OPTIMIZE_SPEED is set and IS_RTL isn't.
// We need to always use Pango for RTL text, in case glyph mirroring is
// required.
PRBool speed = aFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
PRBool isRTL = aFlags & gfxTextRunFactory::TEXT_IS_RTL;
return speed && !isRTL && PANGO_IS_FC_FONT(GetBasePangoFont());
}
#endif
gfxTextRun *
gfxPangoFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
const Parameters *aParams, PRUint32 aFlags)
{
2007-11-16 01:51:59 +00:00
gfxTextRun *run = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
if (!run)
return nsnull;
2007-01-16 22:25:20 +00:00
run->RecordSurrogates(aString);
2007-01-16 22:25:20 +00:00
nsCAutoString utf8;
PRInt32 headerLen = AppendDirectionalIndicatorUTF8(run->IsRightToLeft(), utf8);
AppendUTF16toUTF8(Substring(aString, aString + aLength), utf8);
PRBool is8Bit = PR_FALSE;
#if defined(ENABLE_FAST_PATH_8BIT)
if (CanTakeFastPath(aFlags)) {
PRUint32 allBits = 0;
PRUint32 i;
for (i = 0; i < aLength; ++i) {
allBits |= aString[i];
}
is8Bit = (allBits & 0xFF00) == 0;
}
#endif
InitTextRun(run, utf8.get(), utf8.Length(), headerLen, is8Bit);
run->FetchGlyphExtents(aParams->mContext);
return run;
}
void
gfxPangoFontGroup::InitTextRun(gfxTextRun *aTextRun, const gchar *aUTF8Text,
PRUint32 aUTF8Length, PRUint32 aUTF8HeaderLength,
PRBool aTake8BitPath)
{
#if defined(ENABLE_FAST_PATH_ALWAYS)
CreateGlyphRunsFast(aTextRun, aUTF8Text + aUTF8HeaderLength, aUTF8Length - aUTF8HeaderLength);
#else
#if defined(ENABLE_FAST_PATH_8BIT)
if (aTake8BitPath && CanTakeFastPath(aTextRun->GetFlags())) {
nsresult rv = CreateGlyphRunsFast(aTextRun, aUTF8Text + aUTF8HeaderLength, aUTF8Length - aUTF8HeaderLength);
if (NS_SUCCEEDED(rv))
return;
}
#endif
CreateGlyphRunsItemizing(aTextRun, aUTF8Text, aUTF8Length, aUTF8HeaderLength);
#endif
}
// This will fetch an existing scaled_font if one exists.
static cairo_scaled_font_t *
CreateScaledFont(FcPattern *aPattern)
{
cairo_font_face_t *face = cairo_ft_font_face_create_for_pattern(aPattern);
double size;
if (FcPatternGetDouble(aPattern,
FC_PIXEL_SIZE, 0, &size) != FcResultMatch) {
NS_NOTREACHED("No size on pattern");
size = 0.0;
}
cairo_matrix_t fontMatrix;
FcMatrix *fcMatrix;
if (FcPatternGetMatrix(aPattern, FC_MATRIX, 0, &fcMatrix) == FcResultMatch)
cairo_matrix_init(&fontMatrix, fcMatrix->xx, -fcMatrix->yx, -fcMatrix->xy, fcMatrix->yy, 0, 0);
else
cairo_matrix_init_identity(&fontMatrix);
cairo_matrix_scale(&fontMatrix, size, size);
// The cairo_scaled_font is created with a unit ctm so that metrics and
// positions are in user space, but this means that hinting effects will
// not be estimated accurately for non-unit transformations.
cairo_matrix_t identityMatrix;
cairo_matrix_init_identity(&identityMatrix);
// Font options are set explicitly here to improve cairo's caching
// behavior and to record the relevant parts of the pattern for
// SetupCairoFont (so that the pattern can be released).
//
// Most font_options have already been set as defaults on the FcPattern
// with cairo_ft_font_options_substitute(), then user and system
// fontconfig configurations were applied. The resulting font_options
// have been recorded on the face during
// cairo_ft_font_face_create_for_pattern().
//
// None of the settings here cause this scaled_font to behave any
// differently from how it would behave if it were created from the same
// face with default font_options.
//
// We set options explicitly so that the same scaled_font will be found in
// the cairo_scaled_font_map when cairo loads glyphs from a context with
// the same font_face, font_matrix, ctm, and surface font_options.
//
// Unfortunately, _cairo_scaled_font_keys_equal doesn't know about the
// font_options on the cairo_ft_font_face, and doesn't consider default
// option values to not match any explicit values.
//
// Even after cairo_set_scaled_font is used to set font_options for the
// cairo context, when cairo looks for a scaled_font for the context, it
// will look for a font with some option values from the target surface if
// any values are left default on the context font_options. If this
// scaled_font is created with default font_options, cairo will not find
// it.
cairo_font_options_t *fontOptions = cairo_font_options_create();
// The one option not recorded in the pattern is hint_metrics, which will
// affect glyph metrics. The default behaves as CAIRO_HINT_METRICS_ON.
// We should be considering the font_options of the surface on which this
// font will be used, but currently we don't have different gfxFonts for
// different surface font_options, so we'll create a font suitable for the
// Screen. Image and xlib surfaces default to CAIRO_HINT_METRICS_ON.
cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_ON);
// The remaining options have been recorded on the pattern and the face.
// _cairo_ft_options_merge has some logic to decide which options from the
// scaled_font or from the cairo_ft_font_face take priority in the way the
// font behaves.
//
// In the majority of cases, _cairo_ft_options_merge uses the options from
// the cairo_ft_font_face, so sometimes it is not so important which
// values are set here so long as they are not defaults, but we'll set
// them to the exact values that we expect from the font, to be consistent
// and to protect against changes in cairo.
//
// In some cases, _cairo_ft_options_merge uses some options from the
// scaled_font's font_options rather than options on the
// cairo_ft_font_face (from fontconfig).
// https://bugs.freedesktop.org/show_bug.cgi?id=11838
//
// Surface font options were set on the pattern in
// cairo_ft_font_options_substitute. If fontconfig has changed the
// hint_style then that is what the user (or distribution) wants, so we
// use the setting from the FcPattern.
//
// Fallback values here mirror treatment of defaults in cairo-ft-font.c.
FcBool hinting;
if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch) {
hinting = FcTrue;
}
cairo_hint_style_t hint_style;
if (!hinting) {
hint_style = CAIRO_HINT_STYLE_NONE;
} else {
#ifdef FC_HINT_STYLE // FC_HINT_STYLE is available from fontconfig 2.2.91.
int fc_hintstyle;
if (FcPatternGetInteger(aPattern, FC_HINT_STYLE,
0, &fc_hintstyle ) != FcResultMatch) {
fc_hintstyle = FC_HINT_FULL;
}
switch (fc_hintstyle) {
case FC_HINT_NONE:
hint_style = CAIRO_HINT_STYLE_NONE;
break;
case FC_HINT_SLIGHT:
hint_style = CAIRO_HINT_STYLE_SLIGHT;
break;
case FC_HINT_MEDIUM:
default: // This fallback mirrors _get_pattern_ft_options in cairo.
hint_style = CAIRO_HINT_STYLE_MEDIUM;
break;
case FC_HINT_FULL:
hint_style = CAIRO_HINT_STYLE_FULL;
break;
}
#else // no FC_HINT_STYLE
hint_style = CAIRO_HINT_STYLE_FULL;
#endif
}
cairo_font_options_set_hint_style(fontOptions, hint_style);
int rgba;
if (FcPatternGetInteger(aPattern,
FC_RGBA, 0, &rgba) != FcResultMatch) {
rgba = FC_RGBA_UNKNOWN;
}
cairo_subpixel_order_t subpixel_order = CAIRO_SUBPIXEL_ORDER_DEFAULT;
switch (rgba) {
case FC_RGBA_UNKNOWN:
case FC_RGBA_NONE:
default:
// There is no CAIRO_SUBPIXEL_ORDER_NONE. Subpixel antialiasing
// is disabled through cairo_antialias_t.
rgba = FC_RGBA_NONE;
// subpixel_order won't be used by the font as we won't use
// CAIRO_ANTIALIAS_SUBPIXEL, but don't leave it at default for
// caching reasons described above. Fall through:
case FC_RGBA_RGB:
subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB;
break;
case FC_RGBA_BGR:
subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR;
break;
case FC_RGBA_VRGB:
subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB;
break;
case FC_RGBA_VBGR:
subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR;
break;
}
cairo_font_options_set_subpixel_order(fontOptions, subpixel_order);
FcBool fc_antialias;
if (FcPatternGetBool(aPattern,
FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch) {
fc_antialias = FcTrue;
}
cairo_antialias_t antialias;
if (!fc_antialias) {
antialias = CAIRO_ANTIALIAS_NONE;
} else if (rgba == FC_RGBA_NONE) {
antialias = CAIRO_ANTIALIAS_GRAY;
} else {
antialias = CAIRO_ANTIALIAS_SUBPIXEL;
}
cairo_font_options_set_antialias(fontOptions, antialias);
cairo_scaled_font_t *scaledFont =
cairo_scaled_font_create(face, &fontMatrix, &identityMatrix,
fontOptions);
cairo_font_options_destroy(fontOptions);
cairo_font_face_destroy(face);
NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS,
"Failed to create scaled font");
return scaledFont;
}
PRBool
gfxFcFont::SetupCairoFont(gfxContext *aContext)
{
cairo_t *cr = aContext->GetCairo();
// The scaled font ctm is not relevant right here because
// cairo_set_scaled_font does not record the scaled font itself, but
// merely the font_face, font_matrix, font_options. The scaled_font used
// for the target can be different from the scaled_font passed to
// cairo_set_scaled_font. (Unfortunately we have measured only for an
// identity ctm.)
cairo_scaled_font_t *cairoFont = CairoScaledFont();
if (cairo_scaled_font_status(cairoFont) != 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;
}
// Thoughts on which font_options to set on the context:
//
// cairoFont has been created for screen rendering.
//
// When the context is being used for screen rendering, we should set
// font_options such that the same scaled_font gets used (when the ctm is
// the same). The use of explicit font_options recorded in
// CreateScaledFont ensures that this will happen.
//
// XXXkt: For pdf and ps surfaces, I don't know whether it's better to
// remove surface-specific options, or try to draw with the same
// scaled_font that was used to measure. As the same font_face is being
// used, its font_options will often override some values anyway (unless
// perhaps we remove those from the FcPattern at face creation).
//
// I can't see any significant difference in printing, irrespective of
// what is set here. It's too late to change things here as measuring has
// already taken place. We should really be measuring with a different
// font for pdf and ps surfaces (bug 403513).
cairo_set_scaled_font(cr, cairoFont);
return PR_TRUE;
}
static void
SetupClusterBoundaries(gfxTextRun* aTextRun, const gchar *aUTF8, PRUint32 aUTF8Length,
PRUint32 aUTF16Offset, PangoAnalysis *aAnalysis)
{
if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) {
// 8-bit text doesn't have clusters.
// XXX is this true in all languages???
// behdad: don't think so. Czech for example IIRC has a
// 'ch' grapheme.
return;
}
// Pango says "the array of PangoLogAttr passed in must have at least N+1
// elements, if there are N characters in the text being broken".
// Could use g_utf8_strlen(aUTF8, aUTF8Length) + 1 but the memory savings
// may not be worth the call.
nsAutoTArray<PangoLogAttr,2000> buffer;
if (!buffer.AppendElements(aUTF8Length + 1))
return;
pango_break(aUTF8, aUTF8Length, aAnalysis,
buffer.Elements(), buffer.Length());
const gchar *p = aUTF8;
const gchar *end = aUTF8 + aUTF8Length;
const PangoLogAttr *attr = buffer.Elements();
gfxTextRun::CompressedGlyph g;
while (p < end) {
if (!attr->is_cursor_position) {
aTextRun->SetGlyphs(aUTF16Offset, g.SetComplex(PR_FALSE, PR_TRUE, 0), nsnull);
}
++aUTF16Offset;
gunichar ch = g_utf8_get_char(p);
NS_ASSERTION(ch != 0, "Shouldn't have NUL in pango_break");
NS_ASSERTION(!IS_SURROGATE(ch), "Shouldn't have surrogates in UTF8");
if (ch >= 0x10000) {
// set glyph info for the UTF-16 low surrogate
aTextRun->SetGlyphs(aUTF16Offset, g.SetComplex(PR_FALSE, PR_FALSE, 0), nsnull);
++aUTF16Offset;
}
// We produced this utf8 so we don't need to worry about malformed stuff
p = g_utf8_next_char(p);
++attr;
}
}
static PRInt32
ConvertPangoToAppUnits(PRInt32 aCoordinate, PRUint32 aAppUnitsPerDevUnit)
{
PRInt64 v = (PRInt64(aCoordinate)*aAppUnitsPerDevUnit + PANGO_SCALE/2)/PANGO_SCALE;
return PRInt32(v);
}
/**
* Given a run of Pango glyphs that should be treated as a single
* cluster/ligature, store them in the textrun at the appropriate character
* and set the other characters involved to be ligature/cluster continuations
* as appropriate.
*/
static nsresult
SetGlyphsForCharacterGroup(const PangoGlyphInfo *aGlyphs, PRUint32 aGlyphCount,
gfxTextRun *aTextRun,
const gchar *aUTF8, PRUint32 aUTF8Length,
PRUint32 *aUTF16Offset,
PangoGlyphUnit aOverrideSpaceWidth)
{
PRUint32 utf16Offset = *aUTF16Offset;
PRUint32 textRunLength = aTextRun->GetLength();
const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
// Override the width of a space, but only for spaces that aren't
// clustered with something else (like a freestanding diacritical mark)
PangoGlyphUnit width = aGlyphs[0].geometry.width;
if (aOverrideSpaceWidth && aUTF8[0] == ' ' &&
(utf16Offset + 1 == textRunLength ||
charGlyphs[utf16Offset].IsClusterStart())) {
width = aOverrideSpaceWidth;
}
PRInt32 advance = ConvertPangoToAppUnits(width, appUnitsPerDevUnit);
gfxTextRun::CompressedGlyph g;
PRBool atClusterStart = aTextRun->IsClusterStart(utf16Offset);
// See if we fit in the compressed area.
if (aGlyphCount == 1 && advance >= 0 && atClusterStart &&
aGlyphs[0].geometry.x_offset == 0 &&
aGlyphs[0].geometry.y_offset == 0 &&
!IS_EMPTY_GLYPH(aGlyphs[0].glyph) &&
gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
gfxTextRun::CompressedGlyph::IsSimpleGlyphID(aGlyphs[0].glyph)) {
aTextRun->SetSimpleGlyph(utf16Offset,
g.SetSimpleGlyph(advance, aGlyphs[0].glyph));
} else {
nsAutoTArray<gfxTextRun::DetailedGlyph,10> detailedGlyphs;
if (!detailedGlyphs.AppendElements(aGlyphCount))
return NS_ERROR_OUT_OF_MEMORY;
PRInt32 direction = aTextRun->IsRightToLeft() ? -1 : 1;
PRUint32 pangoIndex = direction > 0 ? 0 : aGlyphCount - 1;
PRUint32 detailedIndex = 0;
for (PRUint32 i = 0; i < aGlyphCount; ++i) {
const PangoGlyphInfo &glyph = aGlyphs[pangoIndex];
pangoIndex += direction;
// The zero width characters return empty glyph ID at
// shaping; we should skip these.
if (IS_EMPTY_GLYPH(glyph.glyph))
continue;
gfxTextRun::DetailedGlyph *details = &detailedGlyphs[detailedIndex];
++detailedIndex;
details->mGlyphID = glyph.glyph;
NS_ASSERTION(details->mGlyphID == glyph.glyph,
"Seriously weird glyph ID detected!");
details->mAdvance =
ConvertPangoToAppUnits(glyph.geometry.width,
appUnitsPerDevUnit);
details->mXOffset =
float(glyph.geometry.x_offset)*appUnitsPerDevUnit/PANGO_SCALE;
details->mYOffset =
float(glyph.geometry.y_offset)*appUnitsPerDevUnit/PANGO_SCALE;
}
g.SetComplex(atClusterStart, PR_TRUE, detailedIndex);
aTextRun->SetGlyphs(utf16Offset, g, detailedGlyphs.Elements());
}
// Check for ligatures and set *aUTF16Offset.
const gchar *p = aUTF8;
const gchar *end = aUTF8 + aUTF8Length;
while (1) {
// Skip the CompressedGlyph that we have added, but check if the
// character was supposed to be ignored. If it's supposed to be ignored,
// overwrite the textrun entry with an invisible missing-glyph.
gunichar ch = g_utf8_get_char(p);
NS_ASSERTION(!IS_SURROGATE(ch), "surrogates should not appear in UTF8");
if (ch >= 0x10000) {
// Skip surrogate
++utf16Offset;
}
NS_ASSERTION(!gfxFontGroup::IsInvalidChar(PRUnichar(ch)),
"Invalid character detected");
++utf16Offset;
// We produced this UTF8 so we don't need to worry about malformed stuff
p = g_utf8_next_char(p);
if (p >= end)
break;
if (utf16Offset >= textRunLength) {
NS_ERROR("Someone has added too many glyphs!");
return NS_ERROR_FAILURE;
}
g.SetComplex(aTextRun->IsClusterStart(utf16Offset), PR_FALSE, 0);
aTextRun->SetGlyphs(utf16Offset, g, nsnull);
}
*aUTF16Offset = utf16Offset;
return NS_OK;
}
nsresult
gfxPangoFontGroup::SetGlyphs(gfxTextRun *aTextRun,
const gchar *aUTF8, PRUint32 aUTF8Length,
PRUint32 *aUTF16Offset, PangoGlyphString *aGlyphs,
PangoGlyphUnit aOverrideSpaceWidth,
PRBool aAbortOnMissingGlyph)
{
gint numGlyphs = aGlyphs->num_glyphs;
PangoGlyphInfo *glyphs = aGlyphs->glyphs;
const gint *logClusters = aGlyphs->log_clusters;
// We cannot make any assumptions about the order of glyph clusters
// provided by pango_shape (see 375864), so we work through the UTF8 text
// and process the glyph clusters in logical order.
// logGlyphs is like an inverse of logClusters. For each UTF8 byte:
// >= 0 indicates that the byte is first in a cluster and
// gives the position of the starting glyph for the cluster.
// -1 indicates that the byte does not start a cluster.
nsAutoTArray<gint,2000> logGlyphs;
if (!logGlyphs.AppendElements(aUTF8Length + 1))
return NS_ERROR_OUT_OF_MEMORY;
PRUint32 utf8Index = 0;
for(; utf8Index < aUTF8Length; ++utf8Index)
logGlyphs[utf8Index] = -1;
logGlyphs[aUTF8Length] = numGlyphs;
gint lastCluster = -1; // != utf8Index
for (gint glyphIndex = 0; glyphIndex < numGlyphs; ++glyphIndex) {
gint thisCluster = logClusters[glyphIndex];
if (thisCluster != lastCluster) {
lastCluster = thisCluster;
NS_ASSERTION(0 <= thisCluster && thisCluster < gint(aUTF8Length),
"garbage from pango_shape - this is bad");
logGlyphs[thisCluster] = glyphIndex;
}
}
PRUint32 utf16Offset = *aUTF16Offset;
PRUint32 textRunLength = aTextRun->GetLength();
utf8Index = 0;
// The next glyph cluster in logical order.
gint nextGlyphClusterStart = logGlyphs[utf8Index];
NS_ASSERTION(nextGlyphClusterStart >= 0, "No glyphs! - NUL in string?");
while (utf8Index < aUTF8Length) {
if (utf16Offset >= textRunLength) {
NS_ERROR("Someone has added too many glyphs!");
return NS_ERROR_FAILURE;
}
gint glyphClusterStart = nextGlyphClusterStart;
// Find the utf8 text associated with this glyph cluster.
PRUint32 clusterUTF8Start = utf8Index;
// Check we are consistent with pango_break data.
NS_ASSERTION(aTextRun->GetCharacterGlyphs()->IsClusterStart(),
"Glyph cluster not aligned on character cluster.");
do {
++utf8Index;
nextGlyphClusterStart = logGlyphs[utf8Index];
} while (nextGlyphClusterStart < 0);
const gchar *clusterUTF8 = &aUTF8[clusterUTF8Start];
PRUint32 clusterUTF8Length = utf8Index - clusterUTF8Start;
PRBool haveMissingGlyph = PR_FALSE;
gint glyphIndex = glyphClusterStart;
// It's now unncecessary to do NUL handling here.
do {
if (IS_MISSING_GLYPH(glyphs[glyphIndex].glyph)) {
// Does pango ever provide more than one glyph in the
// cluster if there is a missing glyph?
// behdad: yes
haveMissingGlyph = PR_TRUE;
}
glyphIndex++;
} while (glyphIndex < numGlyphs &&
logClusters[glyphIndex] == gint(clusterUTF8Start));
if (haveMissingGlyph && aAbortOnMissingGlyph)
return NS_ERROR_FAILURE;
nsresult rv;
if (haveMissingGlyph) {
rv = SetMissingGlyphs(aTextRun, clusterUTF8, clusterUTF8Length,
&utf16Offset);
} else {
rv = SetGlyphsForCharacterGroup(&glyphs[glyphClusterStart],
glyphIndex - glyphClusterStart,
aTextRun,
clusterUTF8, clusterUTF8Length,
&utf16Offset, aOverrideSpaceWidth);
}
NS_ENSURE_SUCCESS(rv,rv);
}
*aUTF16Offset = utf16Offset;
return NS_OK;
}
nsresult
gfxPangoFontGroup::SetMissingGlyphs(gfxTextRun *aTextRun,
const gchar *aUTF8, PRUint32 aUTF8Length,
PRUint32 *aUTF16Offset)
{
PRUint32 utf16Offset = *aUTF16Offset;
PRUint32 textRunLength = aTextRun->GetLength();
for (PRUint32 index = 0; index < aUTF8Length;) {
if (utf16Offset >= textRunLength) {
NS_ERROR("Someone has added too many glyphs!");
break;
}
gunichar ch = g_utf8_get_char(aUTF8 + index);
aTextRun->SetMissingGlyph(utf16Offset, ch);
++utf16Offset;
NS_ASSERTION(!IS_SURROGATE(ch), "surrogates should not appear in UTF8");
if (ch >= 0x10000)
++utf16Offset;
// We produced this UTF8 so we don't need to worry about malformed stuff
index = g_utf8_next_char(aUTF8 + index) - aUTF8;
}
*aUTF16Offset = utf16Offset;
return NS_OK;
}
#if defined(ENABLE_FAST_PATH_8BIT) || defined(ENABLE_FAST_PATH_ALWAYS)
nsresult
gfxPangoFontGroup::CreateGlyphRunsFast(gfxTextRun *aTextRun,
const gchar *aUTF8, PRUint32 aUTF8Length)
{
const gchar *p = aUTF8;
PangoFont *pangofont = GetBasePangoFont();
PangoFcFont *fcfont = PANGO_FC_FONT (pangofont);
gfxFcFont *gfxFont = gfxPangoFcFont::GfxFont(GFX_PANGO_FC_FONT(pangofont));
PRUint32 utf16Offset = 0;
gfxTextRun::CompressedGlyph g;
const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
aTextRun->AddGlyphRun(gfxFont, 0);
while (p < aUTF8 + aUTF8Length) {
// glib-2.12.9: "If p does not point to a valid UTF-8 encoded
// character, results are undefined." so it is not easy to assert that
// aUTF8 in fact points to UTF8 data but asserting
// g_unichar_validate(ch) may be mildly useful.
gunichar ch = g_utf8_get_char(p);
p = g_utf8_next_char(p);
if (ch == 0) {
// treat this null byte as a missing glyph. Pango
// doesn't create glyphs for these, not even missing-glyphs.
aTextRun->SetMissingGlyph(utf16Offset, 0);
} else {
NS_ASSERTION(!IsInvalidChar(ch), "Invalid char detected");
FT_UInt glyph = pango_fc_font_get_glyph (fcfont, ch);
if (!glyph) // character not in font,
return NS_ERROR_FAILURE; // fallback to CreateGlyphRunsItemizing
cairo_text_extents_t extents;
gfxFont->GetGlyphExtents(glyph, &extents);
PRInt32 advance = NS_lround(extents.x_advance * appUnitsPerDevUnit);
if (advance >= 0 &&
gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
aTextRun->SetSimpleGlyph(utf16Offset,
g.SetSimpleGlyph(advance, glyph));
} else {
gfxTextRun::DetailedGlyph details;
details.mGlyphID = glyph;
NS_ASSERTION(details.mGlyphID == glyph,
"Seriously weird glyph ID detected!");
details.mAdvance = advance;
details.mXOffset = 0;
details.mYOffset = 0;
g.SetComplex(aTextRun->IsClusterStart(utf16Offset), PR_TRUE, 1);
aTextRun->SetGlyphs(utf16Offset, g, &details);
}
NS_ASSERTION(!IS_SURROGATE(ch), "Surrogates shouldn't appear in UTF8");
if (ch >= 0x10000) {
// This character is a surrogate pair in UTF16
++utf16Offset;
}
}
++utf16Offset;
}
return NS_OK;
}
#endif
void
gfxPangoFontGroup::CreateGlyphRunsItemizing(gfxTextRun *aTextRun,
const gchar *aUTF8, PRUint32 aUTF8Length,
PRUint32 aUTF8HeaderLen)
{
PangoContext *context = GetPangoContext();
// The font description could be cached on the gfxPangoFontGroup...
nsAutoString fcFamilies;
GetFcFamilies(fcFamilies);
PangoFontDescription *fontDesc =
NewPangoFontDescription(fcFamilies, GetStyle());
if (GetStyle()->sizeAdjust != 0.0) {
gfxFloat size = GetAdjustedSize();
pango_font_description_set_absolute_size
(fontDesc, moz_pango_units_from_double(size));
}
pango_context_set_font_description(context, fontDesc);
pango_font_description_free(fontDesc);
PangoLanguage *lang = GetPangoLanguage(GetStyle()->langGroup);
// we should set this to null if we don't have a text language from the page...
// except that we almost always have something...
pango_context_set_language(context, lang);
// Set the primary font for consistent font selection for common
// characters, but use the default Pango behavior
// (selecting generic fonts from the script of the characters)
// in two situations:
// 1. When we don't have a language to make a good choice for the
// primary font.
// 2. For system fonts, use the default Pango behavior
// to give consistency with other apps.
if (lang && !GetStyle()->systemFont) {
SetBaseFont(context, GetBasePangoFont());
}
PangoDirection dir = aTextRun->IsRightToLeft() ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
GList *items = pango_itemize_with_base_dir(context, dir, aUTF8, 0, aUTF8Length, nsnull, nsnull);
2008-01-20 03:28:17 +00:00
PRUint32 utf16Offset = 0;
#ifdef DEBUG
2008-01-20 03:28:17 +00:00
PRBool isRTL = aTextRun->IsRightToLeft();
#endif
2008-01-20 03:28:17 +00:00
GList *pos = items;
PangoGlyphString *glyphString = pango_glyph_string_new();
if (!glyphString)
goto out; // OOM
for (; pos && pos->data; pos = pos->next) {
PangoItem *item = (PangoItem *)pos->data;
NS_ASSERTION(isRTL == item->analysis.level % 2, "RTL assumption mismatch");
PRUint32 offset = item->offset;
PRUint32 length = item->length;
if (offset < aUTF8HeaderLen) {
if (offset + length <= aUTF8HeaderLen)
continue;
length -= aUTF8HeaderLen - offset;
offset = aUTF8HeaderLen;
}
gfxFcFont *font =
gfxPangoFcFont::GfxFont(GFX_PANGO_FC_FONT(item->analysis.font));
nsresult rv = aTextRun->AddGlyphRun(font, utf16Offset, PR_TRUE);
if (NS_FAILED(rv)) {
NS_ERROR("AddGlyphRun Failed");
goto out;
}
PRUint32 spaceWidth =
moz_pango_units_from_double(font->GetMetrics().spaceWidth);
const gchar *p = aUTF8 + offset;
const gchar *end = p + length;
while (p < end) {
if (*p == 0) {
aTextRun->SetMissingGlyph(utf16Offset, 0);
++p;
++utf16Offset;
continue;
}
2007-01-16 22:25:20 +00:00
// It's necessary to loop over pango_shape as it treats
// NULs as string terminators
const gchar *text = p;
do {
++p;
} while(p < end && *p != 0);
gint len = p - text;
pango_shape(text, len, &item->analysis, glyphString);
SetupClusterBoundaries(aTextRun, text, len, utf16Offset, &item->analysis);
SetGlyphs(aTextRun, text, len, &utf16Offset, glyphString, spaceWidth, PR_FALSE);
}
}
aTextRun->SortGlyphRuns();
out:
if (glyphString)
pango_glyph_string_free(glyphString);
for (pos = items; pos; pos = pos->next)
pango_item_free((PangoItem *)pos->data);
if (items)
g_list_free(items);
g_object_unref(context);
}
/* static */
PangoLanguage *
GetPangoLanguage(const nsACString& aLangGroup)
{
// see if the lang group needs to be translated from mozilla's
// internal mapping into fontconfig's
nsCAutoString lang;
gfxFontconfigUtils::GetSampleLangForGroup(aLangGroup, &lang);
if (lang.IsEmpty())
return NULL;
return pango_language_from_string(lang.get());
}
gfxPangoFontCache::gfxPangoFontCache()
{
mPangoFonts.Init(500);
}
gfxPangoFontCache::~gfxPangoFontCache()
{
}
void
gfxPangoFontCache::Put(const PangoFontDescription *aFontDesc, PangoFont *aPangoFont)
{
if (mPangoFonts.Count() > 5000)
mPangoFonts.Clear();
PRUint32 key = pango_font_description_hash(aFontDesc);
gfxPangoFontWrapper *value = new gfxPangoFontWrapper(aPangoFont);
if (!value)
return;
mPangoFonts.Put(key, value);
}
PangoFont*
gfxPangoFontCache::Get(const PangoFontDescription *aFontDesc)
{
PRUint32 key = pango_font_description_hash(aFontDesc);
gfxPangoFontWrapper *value;
if (!mPangoFonts.Get(key, &value))
return nsnull;
PangoFont *font = value->Get();
g_object_ref(font);
return font;
}