gecko/gfx/thebes/gfxGraphiteShaper.cpp
Ehsan Akhgari ef4e513fd2 Bug 969866 - Use fallible allocations in some places in our font/text shaping code; r=jfkthame
It seems like the sizes for these data structures can be controlled from
Web content, and we are already prepared to deal with OOM conditions,
except that we are using infallible allocations by mistake.
2014-02-08 13:10:07 -05:00

406 lines
14 KiB
C++

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gfxGraphiteShaper.h"
#include "nsString.h"
#include "gfxContext.h"
#include "graphite2/Font.h"
#include "graphite2/Segment.h"
#include "harfbuzz/hb.h"
#define FloatToFixed(f) (65536 * (f))
#define FixedToFloat(f) ((f) * (1.0 / 65536.0))
// Right shifts of negative (signed) integers are undefined, as are overflows
// when converting unsigned to negative signed integers.
// (If speed were an issue we could make some 2's complement assumptions.)
#define FixedToIntRound(f) ((f) > 0 ? ((32768 + (f)) >> 16) \
: -((32767 - (f)) >> 16))
using namespace mozilla; // for AutoSwap_* types
/*
* Creation and destruction; on deletion, release any font tables we're holding
*/
gfxGraphiteShaper::gfxGraphiteShaper(gfxFont *aFont)
: gfxFontShaper(aFont),
mGrFace(mFont->GetFontEntry()->GetGrFace()),
mGrFont(nullptr)
{
mCallbackData.mFont = aFont;
mCallbackData.mShaper = this;
}
gfxGraphiteShaper::~gfxGraphiteShaper()
{
if (mGrFont) {
gr_font_destroy(mGrFont);
}
mFont->GetFontEntry()->ReleaseGrFace(mGrFace);
}
/*static*/ float
gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle, uint16_t glyphid)
{
const CallbackData *cb =
static_cast<const CallbackData*>(appFontHandle);
return FixedToFloat(cb->mFont->GetGlyphWidth(cb->mContext, glyphid));
}
static inline uint32_t
MakeGraphiteLangTag(uint32_t aTag)
{
uint32_t grLangTag = aTag;
// replace trailing space-padding with NULs for graphite
uint32_t mask = 0x000000FF;
while ((grLangTag & mask) == ' ') {
grLangTag &= ~mask;
mask <<= 8;
}
return grLangTag;
}
struct GrFontFeatures {
gr_face *mFace;
gr_feature_val *mFeatures;
};
static PLDHashOperator
AddFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg)
{
GrFontFeatures *f = static_cast<GrFontFeatures*>(aUserArg);
const gr_feature_ref* fref = gr_face_find_fref(f->mFace, aTag);
if (fref) {
gr_fref_set_feature_value(fref, aValue, f->mFeatures);
}
return PL_DHASH_NEXT;
}
bool
gfxGraphiteShaper::ShapeText(gfxContext *aContext,
const char16_t *aText,
uint32_t aOffset,
uint32_t aLength,
int32_t aScript,
gfxShapedText *aShapedText)
{
// some font back-ends require this in order to get proper hinted metrics
if (!mFont->SetupCairoFont(aContext)) {
return false;
}
mCallbackData.mContext = aContext;
if (!mGrFont) {
if (!mGrFace) {
return false;
}
if (mFont->ProvidesGlyphWidths()) {
gr_font_ops ops = {
sizeof(gr_font_ops),
&GrGetAdvance,
nullptr // vertical text not yet implemented
};
mGrFont = gr_make_font_with_ops(mFont->GetAdjustedSize(),
&mCallbackData, &ops, mGrFace);
} else {
mGrFont = gr_make_font(mFont->GetAdjustedSize(), mGrFace);
}
if (!mGrFont) {
return false;
}
}
gfxFontEntry *entry = mFont->GetFontEntry();
const gfxFontStyle *style = mFont->GetStyle();
uint32_t grLang = 0;
if (style->languageOverride) {
grLang = MakeGraphiteLangTag(style->languageOverride);
} else if (entry->mLanguageOverride) {
grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
} else {
nsAutoCString langString;
style->language->ToUTF8String(langString);
grLang = GetGraphiteTagForLang(langString);
}
gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
// if style contains font-specific features
if (MergeFontFeatures(style,
mFont->GetFontEntry()->mFeatureSettings,
aShapedText->DisableLigatures(),
mFont->GetFontEntry()->FamilyName(),
mergedFeatures))
{
// enumerate result and insert into Graphite feature list
GrFontFeatures f = {mGrFace, grFeatures};
mergedFeatures.Enumerate(AddFeature, &f);
}
size_t numChars = gr_count_unicode_characters(gr_utf16,
aText, aText + aLength,
nullptr);
gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures,
gr_utf16, aText, numChars,
aShapedText->IsRightToLeft());
gr_featureval_destroy(grFeatures);
if (!seg) {
return false;
}
nsresult rv = SetGlyphsFromSegment(aContext, aShapedText, aOffset, aLength,
aText, seg);
gr_seg_destroy(seg);
return NS_SUCCEEDED(rv);
}
#define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
// for short (typical) runs up to this length
struct Cluster {
uint32_t baseChar; // in UTF16 code units, not Unicode character indices
uint32_t baseGlyph;
uint32_t nChars; // UTF16 code units
uint32_t nGlyphs;
Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { }
};
nsresult
gfxGraphiteShaper::SetGlyphsFromSegment(gfxContext *aContext,
gfxShapedText *aShapedText,
uint32_t aOffset,
uint32_t aLength,
const char16_t *aText,
gr_segment *aSegment)
{
int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit();
bool rtl = aShapedText->IsRightToLeft();
uint32_t glyphCount = gr_seg_n_slots(aSegment);
// identify clusters; graphite may have reordered/expanded/ligated glyphs.
AutoFallibleTArray<Cluster,SMALL_GLYPH_RUN> clusters;
AutoFallibleTArray<uint16_t,SMALL_GLYPH_RUN> gids;
AutoFallibleTArray<float,SMALL_GLYPH_RUN> xLocs;
AutoFallibleTArray<float,SMALL_GLYPH_RUN> yLocs;
if (!clusters.SetLength(aLength) ||
!gids.SetLength(glyphCount) ||
!xLocs.SetLength(glyphCount) ||
!yLocs.SetLength(glyphCount))
{
return NS_ERROR_OUT_OF_MEMORY;
}
// walk through the glyph slots and check which original character
// each is associated with
uint32_t gIndex = 0; // glyph slot index
uint32_t cIndex = 0; // current cluster index
for (const gr_slot *slot = gr_seg_first_slot(aSegment);
slot != nullptr;
slot = gr_slot_next_in_segment(slot), gIndex++)
{
uint32_t before =
gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_before(slot)));
uint32_t after =
gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_after(slot)));
gids[gIndex] = gr_slot_gid(slot);
xLocs[gIndex] = gr_slot_origin_X(slot);
yLocs[gIndex] = gr_slot_origin_Y(slot);
// if this glyph has a "before" character index that precedes the
// current cluster's char index, we need to merge preceding
// clusters until it gets included
while (before < clusters[cIndex].baseChar && cIndex > 0) {
clusters[cIndex-1].nChars += clusters[cIndex].nChars;
clusters[cIndex-1].nGlyphs += clusters[cIndex].nGlyphs;
--cIndex;
}
// if there's a gap between the current cluster's base character and
// this glyph's, extend the cluster to include the intervening chars
if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars &&
before >= clusters[cIndex].baseChar + clusters[cIndex].nChars)
{
NS_ASSERTION(cIndex < aLength - 1, "cIndex at end of word");
Cluster& c = clusters[cIndex + 1];
c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars;
c.nChars = before - c.baseChar;
c.baseGlyph = gIndex;
c.nGlyphs = 0;
++cIndex;
}
// increment cluster's glyph count to include current slot
NS_ASSERTION(cIndex < aLength, "cIndex beyond word length");
++clusters[cIndex].nGlyphs;
// extend cluster if necessary to reach the glyph's "after" index
if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) {
clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar;
}
}
bool roundX;
bool roundY;
aContext->GetRoundOffsetsToPixels(&roundX, &roundY);
gfxShapedText::CompressedGlyph *charGlyphs =
aShapedText->GetCharacterGlyphs() + aOffset;
// now put glyphs into the textrun, one cluster at a time
for (uint32_t i = 0; i <= cIndex; ++i) {
const Cluster& c = clusters[i];
float adv; // total advance of the cluster
if (rtl) {
if (i == 0) {
adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
} else {
adv = xLocs[clusters[i-1].baseGlyph] - xLocs[c.baseGlyph];
}
} else {
if (i == cIndex) {
adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
} else {
adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph];
}
}
// Check for default-ignorable char that didn't get filtered, combined,
// etc by the shaping process, and skip it.
uint32_t offs = c.baseChar;
NS_ASSERTION(offs < aLength, "unexpected offset");
if (c.nGlyphs == 1 && c.nChars == 1 &&
aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) {
continue;
}
uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits :
NSToIntRound(adv * dev2appUnits);
if (c.nGlyphs == 1 &&
gfxShapedText::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
gfxShapedText::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
charGlyphs[offs].IsClusterStart() &&
yLocs[c.baseGlyph] == 0)
{
charGlyphs[offs].SetSimpleGlyph(appAdvance, gids[c.baseGlyph]);
} else {
// not a one-to-one mapping with simple metrics: use DetailedGlyph
nsAutoTArray<gfxShapedText::DetailedGlyph,8> details;
float clusterLoc;
for (uint32_t j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) {
gfxShapedText::DetailedGlyph* d = details.AppendElement();
d->mGlyphID = gids[j];
d->mYOffset = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits :
-yLocs[j] * dev2appUnits;
if (j == c.baseGlyph) {
d->mXOffset = 0;
d->mAdvance = appAdvance;
clusterLoc = xLocs[j];
} else {
float dx = rtl ? (xLocs[j] - clusterLoc) :
(xLocs[j] - clusterLoc - adv);
d->mXOffset = roundX ? NSToIntRound(dx) * dev2appUnits :
dx * dev2appUnits;
d->mAdvance = 0;
}
}
gfxShapedText::CompressedGlyph g;
g.SetComplex(charGlyphs[offs].IsClusterStart(),
true, details.Length());
aShapedText->SetGlyphs(aOffset + offs, g, details.Elements());
}
for (uint32_t j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) {
NS_ASSERTION(j < aLength, "unexpected offset");
gfxShapedText::CompressedGlyph &g = charGlyphs[j];
NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
g.SetComplex(g.IsClusterStart(), false, 0);
}
}
return NS_OK;
}
#undef SMALL_GLYPH_RUN
// for language tag validation - include list of tags from the IANA registry
#include "gfxLanguageTagList.cpp"
nsTHashtable<nsUint32HashKey> *gfxGraphiteShaper::sLanguageTags;
/*static*/ uint32_t
gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang)
{
int len = aLang.Length();
if (len < 2) {
return 0;
}
// convert primary language subtag to a left-packed, NUL-padded integer
// for the Graphite API
uint32_t grLang = 0;
for (int i = 0; i < 4; ++i) {
grLang <<= 8;
if (i < len) {
uint8_t ch = aLang[i];
if (ch == '-') {
// found end of primary language subtag, truncate here
len = i;
continue;
}
if (ch < 'a' || ch > 'z') {
// invalid character in tag, so ignore it completely
return 0;
}
grLang += ch;
}
}
// valid tags must have length = 2 or 3
if (len < 2 || len > 3) {
return 0;
}
if (!sLanguageTags) {
// store the registered IANA tags in a hash for convenient validation
sLanguageTags = new nsTHashtable<nsUint32HashKey>(ArrayLength(sLanguageTagList));
for (const uint32_t *tag = sLanguageTagList; *tag != 0; ++tag) {
sLanguageTags->PutEntry(*tag);
}
}
// only accept tags known in the IANA registry
if (sLanguageTags->GetEntry(grLang)) {
return grLang;
}
return 0;
}
/*static*/ void
gfxGraphiteShaper::Shutdown()
{
#ifdef NS_FREE_PERMANENT_DATA
if (sLanguageTags) {
sLanguageTags->Clear();
delete sLanguageTags;
sLanguageTags = nullptr;
}
#endif
}