1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "gfxGraphiteShaper.h"
8 #include "gfxContext.h"
9 #include "gfxFontConstants.h"
11 #include "graphite2/Font.h"
12 #include "graphite2/Segment.h"
14 #include "harfbuzz/hb.h"
16 #define FloatToFixed(f) (65536 * (f))
17 #define FixedToFloat(f) ((f) * (1.0 / 65536.0))
18 // Right shifts of negative (signed) integers are undefined, as are overflows
19 // when converting unsigned to negative signed integers.
20 // (If speed were an issue we could make some 2's complement assumptions.)
21 #define FixedToIntRound(f) ((f) > 0 ? ((32768 + (f)) >> 16) \
22 : -((32767 - (f)) >> 16))
24 using namespace mozilla
; // for AutoSwap_* types
27 * Creation and destruction; on deletion, release any font tables we're holding
30 gfxGraphiteShaper::gfxGraphiteShaper(gfxFont
*aFont
)
31 : gfxFontShaper(aFont
),
32 mGrFace(mFont
->GetFontEntry()->GetGrFace()),
33 mGrFont(nullptr), mFallbackToSmallCaps(false)
35 mCallbackData
.mFont
= aFont
;
36 mCallbackData
.mShaper
= this;
39 gfxGraphiteShaper::~gfxGraphiteShaper()
42 gr_font_destroy(mGrFont
);
44 mFont
->GetFontEntry()->ReleaseGrFace(mGrFace
);
48 gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle
, uint16_t glyphid
)
50 const CallbackData
*cb
=
51 static_cast<const CallbackData
*>(appFontHandle
);
52 return FixedToFloat(cb
->mFont
->GetGlyphWidth(cb
->mContext
, glyphid
));
55 static inline uint32_t
56 MakeGraphiteLangTag(uint32_t aTag
)
58 uint32_t grLangTag
= aTag
;
59 // replace trailing space-padding with NULs for graphite
60 uint32_t mask
= 0x000000FF;
61 while ((grLangTag
& mask
) == ' ') {
68 struct GrFontFeatures
{
70 gr_feature_val
*mFeatures
;
73 static PLDHashOperator
74 AddFeature(const uint32_t& aTag
, uint32_t& aValue
, void *aUserArg
)
76 GrFontFeatures
*f
= static_cast<GrFontFeatures
*>(aUserArg
);
78 const gr_feature_ref
* fref
= gr_face_find_fref(f
->mFace
, aTag
);
80 gr_fref_set_feature_value(fref
, aValue
, f
->mFeatures
);
86 gfxGraphiteShaper::ShapeText(gfxContext
*aContext
,
87 const char16_t
*aText
,
91 gfxShapedText
*aShapedText
)
93 // some font back-ends require this in order to get proper hinted metrics
94 if (!mFont
->SetupCairoFont(aContext
)) {
98 mCallbackData
.mContext
= aContext
;
100 const gfxFontStyle
*style
= mFont
->GetStyle();
107 if (mFont
->ProvidesGlyphWidths()) {
111 nullptr // vertical text not yet implemented
113 mGrFont
= gr_make_font_with_ops(mFont
->GetAdjustedSize(),
114 &mCallbackData
, &ops
, mGrFace
);
116 mGrFont
= gr_make_font(mFont
->GetAdjustedSize(), mGrFace
);
123 // determine whether petite-caps falls back to small-caps
124 if (style
->variantCaps
!= NS_FONT_VARIANT_CAPS_NORMAL
) {
125 switch (style
->variantCaps
) {
126 case NS_FONT_VARIANT_CAPS_ALLPETITE
:
127 case NS_FONT_VARIANT_CAPS_PETITECAPS
:
128 bool synLower
, synUpper
;
129 mFont
->SupportsVariantCaps(aScript
, style
->variantCaps
,
130 mFallbackToSmallCaps
, synLower
,
139 gfxFontEntry
*entry
= mFont
->GetFontEntry();
141 if (style
->languageOverride
) {
142 grLang
= MakeGraphiteLangTag(style
->languageOverride
);
143 } else if (entry
->mLanguageOverride
) {
144 grLang
= MakeGraphiteLangTag(entry
->mLanguageOverride
);
146 nsAutoCString langString
;
147 style
->language
->ToUTF8String(langString
);
148 grLang
= GetGraphiteTagForLang(langString
);
150 gr_feature_val
*grFeatures
= gr_face_featureval_for_lang(mGrFace
, grLang
);
152 // if style contains font-specific features
153 nsDataHashtable
<nsUint32HashKey
,uint32_t> mergedFeatures
;
155 if (MergeFontFeatures(style
,
156 mFont
->GetFontEntry()->mFeatureSettings
,
157 aShapedText
->DisableLigatures(),
158 mFont
->GetFontEntry()->FamilyName(),
159 mFallbackToSmallCaps
,
162 // enumerate result and insert into Graphite feature list
163 GrFontFeatures f
= {mGrFace
, grFeatures
};
164 mergedFeatures
.Enumerate(AddFeature
, &f
);
167 size_t numChars
= gr_count_unicode_characters(gr_utf16
,
168 aText
, aText
+ aLength
,
170 gr_segment
*seg
= gr_make_seg(mGrFont
, mGrFace
, 0, grFeatures
,
171 gr_utf16
, aText
, numChars
,
172 aShapedText
->IsRightToLeft());
174 gr_featureval_destroy(grFeatures
);
180 nsresult rv
= SetGlyphsFromSegment(aContext
, aShapedText
, aOffset
, aLength
,
185 return NS_SUCCEEDED(rv
);
188 #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
189 // for short (typical) runs up to this length
192 uint32_t baseChar
; // in UTF16 code units, not Unicode character indices
194 uint32_t nChars
; // UTF16 code units
196 Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { }
200 gfxGraphiteShaper::SetGlyphsFromSegment(gfxContext
*aContext
,
201 gfxShapedText
*aShapedText
,
204 const char16_t
*aText
,
205 gr_segment
*aSegment
)
207 int32_t dev2appUnits
= aShapedText
->GetAppUnitsPerDevUnit();
208 bool rtl
= aShapedText
->IsRightToLeft();
210 uint32_t glyphCount
= gr_seg_n_slots(aSegment
);
212 // identify clusters; graphite may have reordered/expanded/ligated glyphs.
213 AutoFallibleTArray
<Cluster
,SMALL_GLYPH_RUN
> clusters
;
214 AutoFallibleTArray
<uint16_t,SMALL_GLYPH_RUN
> gids
;
215 AutoFallibleTArray
<float,SMALL_GLYPH_RUN
> xLocs
;
216 AutoFallibleTArray
<float,SMALL_GLYPH_RUN
> yLocs
;
218 if (!clusters
.SetLength(aLength
) ||
219 !gids
.SetLength(glyphCount
) ||
220 !xLocs
.SetLength(glyphCount
) ||
221 !yLocs
.SetLength(glyphCount
))
223 return NS_ERROR_OUT_OF_MEMORY
;
226 // walk through the glyph slots and check which original character
227 // each is associated with
228 uint32_t gIndex
= 0; // glyph slot index
229 uint32_t cIndex
= 0; // current cluster index
230 for (const gr_slot
*slot
= gr_seg_first_slot(aSegment
);
232 slot
= gr_slot_next_in_segment(slot
), gIndex
++)
235 gr_cinfo_base(gr_seg_cinfo(aSegment
, gr_slot_before(slot
)));
237 gr_cinfo_base(gr_seg_cinfo(aSegment
, gr_slot_after(slot
)));
238 gids
[gIndex
] = gr_slot_gid(slot
);
239 xLocs
[gIndex
] = gr_slot_origin_X(slot
);
240 yLocs
[gIndex
] = gr_slot_origin_Y(slot
);
242 // if this glyph has a "before" character index that precedes the
243 // current cluster's char index, we need to merge preceding
244 // clusters until it gets included
245 while (before
< clusters
[cIndex
].baseChar
&& cIndex
> 0) {
246 clusters
[cIndex
-1].nChars
+= clusters
[cIndex
].nChars
;
247 clusters
[cIndex
-1].nGlyphs
+= clusters
[cIndex
].nGlyphs
;
251 // if there's a gap between the current cluster's base character and
252 // this glyph's, extend the cluster to include the intervening chars
253 if (gr_slot_can_insert_before(slot
) && clusters
[cIndex
].nChars
&&
254 before
>= clusters
[cIndex
].baseChar
+ clusters
[cIndex
].nChars
)
256 NS_ASSERTION(cIndex
< aLength
- 1, "cIndex at end of word");
257 Cluster
& c
= clusters
[cIndex
+ 1];
258 c
.baseChar
= clusters
[cIndex
].baseChar
+ clusters
[cIndex
].nChars
;
259 c
.nChars
= before
- c
.baseChar
;
260 c
.baseGlyph
= gIndex
;
265 // increment cluster's glyph count to include current slot
266 NS_ASSERTION(cIndex
< aLength
, "cIndex beyond word length");
267 ++clusters
[cIndex
].nGlyphs
;
269 // extend cluster if necessary to reach the glyph's "after" index
270 if (clusters
[cIndex
].baseChar
+ clusters
[cIndex
].nChars
< after
+ 1) {
271 clusters
[cIndex
].nChars
= after
+ 1 - clusters
[cIndex
].baseChar
;
277 aContext
->GetRoundOffsetsToPixels(&roundX
, &roundY
);
279 gfxShapedText::CompressedGlyph
*charGlyphs
=
280 aShapedText
->GetCharacterGlyphs() + aOffset
;
282 // now put glyphs into the textrun, one cluster at a time
283 for (uint32_t i
= 0; i
<= cIndex
; ++i
) {
284 const Cluster
& c
= clusters
[i
];
286 float adv
; // total advance of the cluster
289 adv
= gr_seg_advance_X(aSegment
) - xLocs
[c
.baseGlyph
];
291 adv
= xLocs
[clusters
[i
-1].baseGlyph
] - xLocs
[c
.baseGlyph
];
295 adv
= gr_seg_advance_X(aSegment
) - xLocs
[c
.baseGlyph
];
297 adv
= xLocs
[clusters
[i
+1].baseGlyph
] - xLocs
[c
.baseGlyph
];
301 // Check for default-ignorable char that didn't get filtered, combined,
302 // etc by the shaping process, and skip it.
303 uint32_t offs
= c
.baseChar
;
304 NS_ASSERTION(offs
< aLength
, "unexpected offset");
305 if (c
.nGlyphs
== 1 && c
.nChars
== 1 &&
306 aShapedText
->FilterIfIgnorable(aOffset
+ offs
, aText
[offs
])) {
310 uint32_t appAdvance
= roundX
? NSToIntRound(adv
) * dev2appUnits
:
311 NSToIntRound(adv
* dev2appUnits
);
312 if (c
.nGlyphs
== 1 &&
313 gfxShapedText::CompressedGlyph::IsSimpleGlyphID(gids
[c
.baseGlyph
]) &&
314 gfxShapedText::CompressedGlyph::IsSimpleAdvance(appAdvance
) &&
315 charGlyphs
[offs
].IsClusterStart() &&
316 yLocs
[c
.baseGlyph
] == 0)
318 charGlyphs
[offs
].SetSimpleGlyph(appAdvance
, gids
[c
.baseGlyph
]);
320 // not a one-to-one mapping with simple metrics: use DetailedGlyph
321 nsAutoTArray
<gfxShapedText::DetailedGlyph
,8> details
;
323 for (uint32_t j
= c
.baseGlyph
; j
< c
.baseGlyph
+ c
.nGlyphs
; ++j
) {
324 gfxShapedText::DetailedGlyph
* d
= details
.AppendElement();
325 d
->mGlyphID
= gids
[j
];
326 d
->mYOffset
= roundY
? NSToIntRound(-yLocs
[j
]) * dev2appUnits
:
327 -yLocs
[j
] * dev2appUnits
;
328 if (j
== c
.baseGlyph
) {
330 d
->mAdvance
= appAdvance
;
331 clusterLoc
= xLocs
[j
];
333 float dx
= rtl
? (xLocs
[j
] - clusterLoc
) :
334 (xLocs
[j
] - clusterLoc
- adv
);
335 d
->mXOffset
= roundX
? NSToIntRound(dx
) * dev2appUnits
:
340 gfxShapedText::CompressedGlyph g
;
341 g
.SetComplex(charGlyphs
[offs
].IsClusterStart(),
342 true, details
.Length());
343 aShapedText
->SetGlyphs(aOffset
+ offs
, g
, details
.Elements());
346 for (uint32_t j
= c
.baseChar
+ 1; j
< c
.baseChar
+ c
.nChars
; ++j
) {
347 NS_ASSERTION(j
< aLength
, "unexpected offset");
348 gfxShapedText::CompressedGlyph
&g
= charGlyphs
[j
];
349 NS_ASSERTION(!g
.IsSimpleGlyph(), "overwriting a simple glyph");
350 g
.SetComplex(g
.IsClusterStart(), false, 0);
357 #undef SMALL_GLYPH_RUN
359 // for language tag validation - include list of tags from the IANA registry
360 #include "gfxLanguageTagList.cpp"
362 nsTHashtable
<nsUint32HashKey
> *gfxGraphiteShaper::sLanguageTags
;
365 gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString
& aLang
)
367 int len
= aLang
.Length();
372 // convert primary language subtag to a left-packed, NUL-padded integer
373 // for the Graphite API
375 for (int i
= 0; i
< 4; ++i
) {
378 uint8_t ch
= aLang
[i
];
380 // found end of primary language subtag, truncate here
384 if (ch
< 'a' || ch
> 'z') {
385 // invalid character in tag, so ignore it completely
392 // valid tags must have length = 2 or 3
393 if (len
< 2 || len
> 3) {
397 if (!sLanguageTags
) {
398 // store the registered IANA tags in a hash for convenient validation
399 sLanguageTags
= new nsTHashtable
<nsUint32HashKey
>(ArrayLength(sLanguageTagList
));
400 for (const uint32_t *tag
= sLanguageTagList
; *tag
!= 0; ++tag
) {
401 sLanguageTags
->PutEntry(*tag
);
405 // only accept tags known in the IANA registry
406 if (sLanguageTags
->GetEntry(grLang
)) {
414 gfxGraphiteShaper::Shutdown()
416 #ifdef NS_FREE_PERMANENT_DATA
418 sLanguageTags
->Clear();
419 delete sLanguageTags
;
420 sLanguageTags
= nullptr;