1 : /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Graphite integration code.
16 : *
17 : * The Initial Developer of the Original Code is Mozilla Foundation.
18 : * Portions created by the Initial Developer are Copyright (C) 2011
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Jonathan Kew <jfkthame@gmail.com>
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "prtypes.h"
39 : #include "prmem.h"
40 : #include "nsString.h"
41 : #include "nsBidiUtils.h"
42 : #include "nsMathUtils.h"
43 :
44 : #include "gfxTypes.h"
45 :
46 : #include "gfxContext.h"
47 : #include "gfxPlatform.h"
48 : #include "gfxGraphiteShaper.h"
49 : #include "gfxFontUtils.h"
50 :
51 : #include "graphite2/Font.h"
52 : #include "graphite2/Segment.h"
53 :
54 : #include "harfbuzz/hb-blob.h"
55 :
56 : #include "cairo.h"
57 :
58 : #include "nsUnicodeRange.h"
59 : #include "nsCRT.h"
60 :
61 : #define FloatToFixed(f) (65536 * (f))
62 : #define FixedToFloat(f) ((f) * (1.0 / 65536.0))
63 : // Right shifts of negative (signed) integers are undefined, as are overflows
64 : // when converting unsigned to negative signed integers.
65 : // (If speed were an issue we could make some 2's complement assumptions.)
66 : #define FixedToIntRound(f) ((f) > 0 ? ((32768 + (f)) >> 16) \
67 : : -((32767 - (f)) >> 16))
68 :
69 : using namespace mozilla; // for AutoSwap_* types
70 :
71 : /*
72 : * Creation and destruction; on deletion, release any font tables we're holding
73 : */
74 :
75 0 : gfxGraphiteShaper::gfxGraphiteShaper(gfxFont *aFont)
76 : : gfxFontShaper(aFont),
77 : mGrFace(nsnull),
78 : mGrFont(nsnull),
79 0 : mUseFontGlyphWidths(false)
80 : {
81 0 : mTables.Init();
82 0 : mCallbackData.mFont = aFont;
83 0 : mCallbackData.mShaper = this;
84 0 : }
85 :
86 : PLDHashOperator
87 0 : ReleaseTableFunc(const PRUint32& /* aKey */,
88 : gfxGraphiteShaper::TableRec& aData,
89 : void* /* aUserArg */)
90 : {
91 0 : hb_blob_destroy(aData.mBlob);
92 0 : return PL_DHASH_REMOVE;
93 : }
94 :
95 0 : gfxGraphiteShaper::~gfxGraphiteShaper()
96 : {
97 0 : if (mGrFont) {
98 0 : gr_font_destroy(mGrFont);
99 : }
100 0 : if (mGrFace) {
101 0 : gr_face_destroy(mGrFace);
102 : }
103 0 : mTables.Enumerate(ReleaseTableFunc, nsnull);
104 0 : }
105 :
106 : static const void*
107 0 : GrGetTable(const void* appFaceHandle, unsigned int name, size_t *len)
108 : {
109 : const gfxGraphiteShaper::CallbackData *cb =
110 0 : static_cast<const gfxGraphiteShaper::CallbackData*>(appFaceHandle);
111 0 : return cb->mShaper->GetTable(name, len);
112 : }
113 :
114 : const void*
115 0 : gfxGraphiteShaper::GetTable(PRUint32 aTag, size_t *aLength)
116 : {
117 : TableRec tableRec;
118 :
119 0 : if (!mTables.Get(aTag, &tableRec)) {
120 0 : hb_blob_t *blob = mFont->GetFontTable(aTag);
121 0 : if (blob) {
122 : // mFont->GetFontTable() gives us a reference to the blob.
123 : // We will destroy (release) it in our destructor.
124 0 : tableRec.mBlob = blob;
125 0 : tableRec.mData = hb_blob_get_data(blob, &tableRec.mLength);
126 0 : mTables.Put(aTag, tableRec);
127 : } else {
128 0 : return nsnull;
129 : }
130 : }
131 :
132 0 : *aLength = tableRec.mLength;
133 0 : return tableRec.mData;
134 : }
135 :
136 : static float
137 0 : GrGetAdvance(const void* appFontHandle, gr_uint16 glyphid)
138 : {
139 : const gfxGraphiteShaper::CallbackData *cb =
140 0 : static_cast<const gfxGraphiteShaper::CallbackData*>(appFontHandle);
141 0 : return FixedToFloat(cb->mFont->GetGlyphWidth(cb->mContext, glyphid));
142 : }
143 :
144 : static inline PRUint32
145 0 : MakeGraphiteLangTag(PRUint32 aTag)
146 : {
147 0 : PRUint32 grLangTag = aTag;
148 : // replace trailing space-padding with NULs for graphite
149 0 : PRUint32 mask = 0x000000FF;
150 0 : while ((grLangTag & mask) == ' ') {
151 0 : grLangTag &= ~mask;
152 0 : mask <<= 8;
153 : }
154 0 : return grLangTag;
155 : }
156 :
157 : bool
158 0 : gfxGraphiteShaper::ShapeWord(gfxContext *aContext,
159 : gfxShapedWord *aShapedWord,
160 : const PRUnichar *aText)
161 : {
162 : // some font back-ends require this in order to get proper hinted metrics
163 0 : mFont->SetupCairoFont(aContext);
164 :
165 0 : mCallbackData.mContext = aContext;
166 :
167 0 : if (!mGrFont) {
168 0 : mGrFace = gr_make_face(&mCallbackData, GrGetTable, gr_face_default);
169 0 : if (!mGrFace) {
170 0 : return false;
171 : }
172 : mGrFont = mUseFontGlyphWidths ?
173 0 : gr_make_font_with_advance_fn(mFont->GetAdjustedSize(),
174 : &mCallbackData, GrGetAdvance,
175 0 : mGrFace) :
176 0 : gr_make_font(mFont->GetAdjustedSize(), mGrFace);
177 0 : if (!mGrFont) {
178 0 : gr_face_destroy(mGrFace);
179 0 : mGrFace = nsnull;
180 0 : return false;
181 : }
182 : }
183 :
184 0 : gfxFontEntry *entry = mFont->GetFontEntry();
185 0 : const gfxFontStyle *style = mFont->GetStyle();
186 0 : PRUint32 grLang = 0;
187 0 : if (style->languageOverride) {
188 0 : grLang = MakeGraphiteLangTag(style->languageOverride);
189 0 : } else if (entry->mLanguageOverride) {
190 0 : grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
191 : } else {
192 0 : nsCAutoString langString;
193 0 : style->language->ToUTF8String(langString);
194 0 : grLang = GetGraphiteTagForLang(langString);
195 : }
196 0 : gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
197 :
198 0 : if (aShapedWord->DisableLigatures()) {
199 : const gr_feature_ref* fref =
200 0 : gr_face_find_fref(mGrFace, TRUETYPE_TAG('l','i','g','a'));
201 0 : if (fref) {
202 0 : gr_fref_set_feature_value(fref, 0, grFeatures);
203 : }
204 : }
205 :
206 0 : const nsTArray<gfxFontFeature> *features = &style->featureSettings;
207 0 : if (features->IsEmpty()) {
208 0 : features = &entry->mFeatureSettings;
209 : }
210 0 : for (PRUint32 i = 0; i < features->Length(); ++i) {
211 : const gr_feature_ref* fref =
212 0 : gr_face_find_fref(mGrFace, (*features)[i].mTag);
213 0 : if (fref) {
214 0 : gr_fref_set_feature_value(fref, (*features)[i].mValue, grFeatures);
215 : }
216 : }
217 :
218 : gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures,
219 : gr_utf16, aText, aShapedWord->Length(),
220 0 : aShapedWord->IsRightToLeft());
221 0 : if (features) {
222 0 : gr_featureval_destroy(grFeatures);
223 : }
224 0 : if (!seg) {
225 0 : return false;
226 : }
227 :
228 0 : nsresult rv = SetGlyphsFromSegment(aShapedWord, seg);
229 :
230 0 : gr_seg_destroy(seg);
231 :
232 0 : return NS_SUCCEEDED(rv);
233 : }
234 :
235 : #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
236 : // for short (typical) runs up to this length
237 :
238 0 : struct Cluster {
239 : PRUint32 baseChar;
240 : PRUint32 baseGlyph;
241 : PRUint32 nChars;
242 : PRUint32 nGlyphs;
243 0 : Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { }
244 : };
245 :
246 : nsresult
247 0 : gfxGraphiteShaper::SetGlyphsFromSegment(gfxShapedWord *aShapedWord,
248 : gr_segment *aSegment)
249 : {
250 0 : PRInt32 dev2appUnits = aShapedWord->AppUnitsPerDevUnit();
251 0 : bool rtl = aShapedWord->IsRightToLeft();
252 :
253 0 : PRUint32 glyphCount = gr_seg_n_slots(aSegment);
254 :
255 : // identify clusters; graphite may have reordered/expanded/ligated glyphs.
256 0 : nsAutoTArray<Cluster,SMALL_GLYPH_RUN> clusters;
257 0 : nsAutoTArray<PRUint16,SMALL_GLYPH_RUN> gids;
258 0 : nsAutoTArray<float,SMALL_GLYPH_RUN> xLocs;
259 0 : nsAutoTArray<float,SMALL_GLYPH_RUN> yLocs;
260 :
261 0 : if (!clusters.SetLength(aShapedWord->Length()) ||
262 0 : !gids.SetLength(glyphCount) ||
263 0 : !xLocs.SetLength(glyphCount) ||
264 0 : !yLocs.SetLength(glyphCount))
265 : {
266 0 : return NS_ERROR_OUT_OF_MEMORY;
267 : }
268 :
269 : // walk through the glyph slots and check which original character
270 : // each is associated with
271 0 : PRUint32 gIndex = 0; // glyph slot index
272 0 : PRUint32 cIndex = 0; // current cluster index
273 0 : for (const gr_slot *slot = gr_seg_first_slot(aSegment);
274 : slot != nsnull;
275 : slot = gr_slot_next_in_segment(slot), gIndex++)
276 : {
277 0 : PRUint32 before = gr_slot_before(slot);
278 0 : PRUint32 after = gr_slot_after(slot);
279 0 : gids[gIndex] = gr_slot_gid(slot);
280 0 : xLocs[gIndex] = gr_slot_origin_X(slot);
281 0 : yLocs[gIndex] = gr_slot_origin_Y(slot);
282 :
283 : // if this glyph has a "before" character index that precedes the
284 : // current cluster's char index, we need to merge preceding
285 : // clusters until it gets included
286 0 : while (before < clusters[cIndex].baseChar && cIndex > 0) {
287 0 : clusters[cIndex-1].nChars += clusters[cIndex].nChars;
288 0 : clusters[cIndex-1].nGlyphs += clusters[cIndex].nGlyphs;
289 0 : --cIndex;
290 : }
291 :
292 : // if there's a gap between the current cluster's base character and
293 : // this glyph's, extend the cluster to include the intervening chars
294 0 : if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars &&
295 0 : before >= clusters[cIndex].baseChar + clusters[cIndex].nChars)
296 : {
297 0 : NS_ASSERTION(cIndex < aShapedWord->Length() - 1, "cIndex at end of word");
298 0 : Cluster& c = clusters[cIndex + 1];
299 0 : c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars;
300 0 : c.nChars = before - c.baseChar;
301 0 : c.baseGlyph = gIndex;
302 0 : c.nGlyphs = 0;
303 0 : ++cIndex;
304 : }
305 :
306 : // increment cluster's glyph count to include current slot
307 0 : NS_ASSERTION(cIndex < aShapedWord->Length(), "cIndex beyond word length");
308 0 : ++clusters[cIndex].nGlyphs;
309 :
310 : // extend cluster if necessary to reach the glyph's "after" index
311 0 : if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) {
312 0 : clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar;
313 : }
314 : }
315 :
316 : // now put glyphs into the textrun, one cluster at a time
317 0 : for (PRUint32 i = 0; i <= cIndex; ++i) {
318 0 : const Cluster& c = clusters[i];
319 :
320 : float adv; // total advance of the cluster
321 0 : if (rtl) {
322 0 : if (i == 0) {
323 0 : adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
324 : } else {
325 0 : adv = xLocs[clusters[i-1].baseGlyph] - xLocs[c.baseGlyph];
326 : }
327 : } else {
328 0 : if (i == cIndex) {
329 0 : adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
330 : } else {
331 0 : adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph];
332 : }
333 : }
334 :
335 : // Check for default-ignorable char that didn't get filtered, combined,
336 : // etc by the shaping process, and skip it.
337 0 : PRUint32 offs = gr_cinfo_base(gr_seg_cinfo(aSegment, c.baseChar));
338 0 : NS_ASSERTION(offs >= c.baseChar && offs < aShapedWord->Length(),
339 : "unexpected offset");
340 0 : if (c.nGlyphs == 1 && c.nChars == 1 &&
341 0 : aShapedWord->FilterIfIgnorable(offs))
342 : {
343 0 : continue;
344 : }
345 :
346 0 : PRUint32 appAdvance = adv * dev2appUnits;
347 0 : if (c.nGlyphs == 1 &&
348 0 : gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
349 0 : gfxShapedWord::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
350 0 : yLocs[c.baseGlyph] == 0)
351 : {
352 0 : gfxShapedWord::CompressedGlyph g;
353 : aShapedWord->SetSimpleGlyph(offs,
354 : g.SetSimpleGlyph(appAdvance,
355 0 : gids[c.baseGlyph]));
356 : } else {
357 : // not a one-to-one mapping with simple metrics: use DetailedGlyph
358 0 : nsAutoTArray<gfxShapedWord::DetailedGlyph,8> details;
359 : float clusterLoc;
360 0 : for (PRUint32 j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) {
361 0 : gfxShapedWord::DetailedGlyph* d = details.AppendElement();
362 0 : d->mGlyphID = gids[j];
363 0 : d->mYOffset = -yLocs[j] * dev2appUnits;
364 0 : if (j == c.baseGlyph) {
365 0 : d->mXOffset = 0;
366 0 : d->mAdvance = appAdvance;
367 0 : clusterLoc = xLocs[j];
368 : } else {
369 0 : d->mXOffset = (xLocs[j] - clusterLoc - adv) * dev2appUnits;
370 0 : d->mAdvance = 0;
371 : }
372 : }
373 0 : gfxShapedWord::CompressedGlyph g;
374 0 : g.SetComplex(aShapedWord->IsClusterStart(offs),
375 0 : true, details.Length());
376 0 : aShapedWord->SetGlyphs(offs, g, details.Elements());
377 : }
378 :
379 0 : for (PRUint32 j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) {
380 0 : offs = gr_cinfo_base(gr_seg_cinfo(aSegment, j));
381 0 : NS_ASSERTION(offs >= j && offs < aShapedWord->Length(),
382 : "unexpected offset");
383 0 : gfxShapedWord::CompressedGlyph g;
384 0 : g.SetComplex(aShapedWord->IsClusterStart(offs), false, 0);
385 0 : aShapedWord->SetGlyphs(offs, g, nsnull);
386 : }
387 : }
388 :
389 0 : return NS_OK;
390 : }
391 :
392 : // for language tag validation - include list of tags from the IANA registry
393 : #include "gfxLanguageTagList.cpp"
394 :
395 1464 : nsTHashtable<nsUint32HashKey> gfxGraphiteShaper::sLanguageTags;
396 :
397 : /*static*/ PRUint32
398 0 : gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang)
399 : {
400 0 : int len = aLang.Length();
401 0 : if (len < 2) {
402 0 : return 0;
403 : }
404 :
405 : // convert primary language subtag to a left-packed, NUL-padded integer
406 : // for the Graphite API
407 0 : PRUint32 grLang = 0;
408 0 : for (int i = 0; i < 4; ++i) {
409 0 : grLang <<= 8;
410 0 : if (i < len) {
411 0 : PRUint8 ch = aLang[i];
412 0 : if (ch == '-') {
413 : // found end of primary language subtag, truncate here
414 0 : len = i;
415 0 : continue;
416 : }
417 0 : if (ch < 'a' || ch > 'z') {
418 : // invalid character in tag, so ignore it completely
419 0 : return 0;
420 : }
421 0 : grLang += ch;
422 : }
423 : }
424 :
425 : // valid tags must have length = 2 or 3
426 0 : if (len < 2 || len > 3) {
427 0 : return 0;
428 : }
429 :
430 0 : if (!sLanguageTags.IsInitialized()) {
431 : // store the registered IANA tags in a hash for convenient validation
432 0 : sLanguageTags.Init(ArrayLength(sLanguageTagList));
433 0 : for (const PRUint32 *tag = sLanguageTagList; *tag != 0; ++tag) {
434 0 : sLanguageTags.PutEntry(*tag);
435 : }
436 : }
437 :
438 : // only accept tags known in the IANA registry
439 0 : if (sLanguageTags.GetEntry(grLang)) {
440 0 : return grLang;
441 : }
442 :
443 0 : return 0;
444 : }
445 :
446 : /*static*/ void
447 4 : gfxGraphiteShaper::Shutdown()
448 : {
449 : #ifdef NS_FREE_PERMANENT_DATA
450 4 : if (sLanguageTags.IsInitialized()) {
451 0 : sLanguageTags.Clear();
452 : }
453 : #endif
454 4396 : }
|