forked from mozilla/gecko-dev
-
Notifications
You must be signed in to change notification settings - Fork 1
/
gfxFontEntry.cpp
2196 lines (1917 loc) · 75.1 KB
/
gfxFontEntry.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "gfxFontEntry.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FontPropertyTypes.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Logging.h"
#include "gfxTextRun.h"
#include "gfxPlatform.h"
#include "nsGkAtoms.h"
#include "gfxTypes.h"
#include "gfxContext.h"
#include "gfxFontConstants.h"
#include "gfxGraphiteShaper.h"
#include "gfxHarfBuzzShaper.h"
#include "gfxUserFontSet.h"
#include "gfxPlatformFontList.h"
#include "nsUnicodeProperties.h"
#include "nsMathUtils.h"
#include "nsBidiUtils.h"
#include "nsStyleConsts.h"
#include "mozilla/AppUnits.h"
#include "mozilla/FloatingPoint.h"
#ifdef MOZ_WASM_SANDBOXING_GRAPHITE
# include "mozilla/ipc/LibrarySandboxPreload.h"
#endif
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/Telemetry.h"
#include "gfxSVGGlyphs.h"
#include "gfx2DGlue.h"
#include "harfbuzz/hb.h"
#include "harfbuzz/hb-ot.h"
#include "graphite2/Font.h"
#include "ThebesRLBox.h"
#include <algorithm>
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::unicode;
nsrefcnt gfxCharacterMap::NotifyMaybeReleased() {
auto* pfl = gfxPlatformFontList::PlatformFontList();
pfl->Lock();
// Something may have pulled our raw pointer out of gfxPlatformFontList before
// we were able to complete the release.
if (mRefCnt > 0) {
pfl->Unlock();
return mRefCnt;
}
if (mShared) {
pfl->RemoveCmap(this);
}
pfl->Unlock();
delete this;
return 0;
}
gfxFontEntry::gfxFontEntry(const nsACString& aName, bool aIsStandardFace)
: mName(aName),
mLock("gfxFontEntry lock"),
mFeatureInfoLock("gfxFontEntry featureInfo mutex"),
mFixedPitch(false),
mIsBadUnderlineFont(false),
mIsUserFontContainer(false),
mIsDataUserFont(false),
mIsLocalUserFont(false),
mStandardFace(aIsStandardFace),
mIgnoreGDEF(false),
mIgnoreGSUB(false),
mSkipDefaultFeatureSpaceCheck(false),
mSVGInitialized(false),
mHasCmapTable(false),
mGrFaceInitialized(false),
mCheckedForColorGlyph(false),
mCheckedForVariationAxes(false),
mSpaceGlyphIsInvisible(LazyFlag::Uninitialized),
mHasGraphiteTables(LazyFlag::Uninitialized),
mHasGraphiteSpaceContextuals(LazyFlag::Uninitialized),
mHasColorBitmapTable(LazyFlag::Uninitialized),
mHasSpaceFeatures(SpaceFeatures::Uninitialized) {
mTrakTable.exchange(kTrakTableUninitialized);
memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
}
gfxFontEntry::~gfxFontEntry() {
// Should not be dropped by stylo
MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
hb_blob_destroy(mCOLR.exchange(nullptr));
hb_blob_destroy(mCPAL.exchange(nullptr));
if (TrakTableInitialized()) {
// Only if it was initialized, so that we don't try to call hb_blob_destroy
// on the kTrakTableUninitialized flag value!
hb_blob_destroy(mTrakTable.exchange(nullptr));
}
// For downloaded fonts, we need to tell the user font cache that this
// entry is being deleted.
if (mIsDataUserFont) {
gfxUserFontSet::UserFontCache::ForgetFont(this);
}
if (mFeatureInputs) {
for (auto iter = mFeatureInputs->Iter(); !iter.Done(); iter.Next()) {
hb_set_t*& set = iter.Data();
hb_set_destroy(set);
}
}
delete mFontTableCache.exchange(nullptr);
delete mSVGGlyphs.exchange(nullptr);
delete[] mUVSData.exchange(nullptr);
gfxCharacterMap* cmap = mCharacterMap.exchange(nullptr);
NS_IF_RELEASE(cmap);
// By the time the entry is destroyed, all font instances that were
// using it should already have been deleted, and so the HB and/or Gr
// face objects should have been released.
MOZ_ASSERT(!mHBFace);
MOZ_ASSERT(!mGrFaceInitialized);
}
// Only used during initialization, before any other thread has a chance to see
// the entry, so locking not required.
void gfxFontEntry::InitializeFrom(fontlist::Face* aFace,
const fontlist::Family* aFamily) {
mStyleRange = aFace->mStyle;
mWeightRange = aFace->mWeight;
mStretchRange = aFace->mStretch;
mFixedPitch = aFace->mFixedPitch;
mIsBadUnderlineFont = aFamily->IsBadUnderlineFamily();
mShmemFace = aFace;
auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
mFamilyName = aFamily->DisplayName().AsString(list);
mHasCmapTable = TrySetShmemCharacterMap();
}
bool gfxFontEntry::TrySetShmemCharacterMap() {
MOZ_ASSERT(mShmemFace);
auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
const auto* shmemCmap =
static_cast<const SharedBitSet*>(mShmemFace->mCharacterMap.ToPtr(list));
mShmemCharacterMap.exchange(shmemCmap);
return shmemCmap != nullptr;
}
bool gfxFontEntry::TestCharacterMap(uint32_t aCh) {
if (!mCharacterMap && !mShmemCharacterMap) {
ReadCMAP();
MOZ_ASSERT(mCharacterMap || mShmemCharacterMap,
"failed to initialize character map");
}
return mShmemCharacterMap ? GetShmemCharacterMap()->test(aCh)
: GetCharacterMap()->test(aCh);
}
void gfxFontEntry::EnsureUVSMapInitialized() {
// mUVSOffset will not be initialized
// until cmap is initialized.
if (!mCharacterMap && !mShmemCharacterMap) {
ReadCMAP();
NS_ASSERTION(mCharacterMap || mShmemCharacterMap,
"failed to initialize character map");
}
if (!mUVSOffset) {
return;
}
if (!mUVSData) {
nsresult rv = NS_ERROR_NOT_AVAILABLE;
const uint32_t kCmapTag = TRUETYPE_TAG('c', 'm', 'a', 'p');
AutoTable cmapTable(this, kCmapTag);
if (cmapTable) {
const uint8_t* uvsData = nullptr;
unsigned int cmapLen;
const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen);
rv = gfxFontUtils::ReadCMAPTableFormat14(
(const uint8_t*)cmapData + mUVSOffset, cmapLen - mUVSOffset, uvsData);
if (NS_SUCCEEDED(rv)) {
if (!mUVSData.compareExchange(nullptr, uvsData)) {
delete uvsData;
}
}
}
if (NS_FAILED(rv)) {
mUVSOffset = 0; // don't try to read the table again
}
}
}
uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) {
EnsureUVSMapInitialized();
if (const auto* uvsData = GetUVSData()) {
return gfxFontUtils::MapUVSToGlyphFormat14(uvsData, aCh, aVS);
}
return 0;
}
bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags,
uint32_t aNumTags) {
auto face(GetHBFace());
unsigned int index;
hb_tag_t chosenScript;
bool found = hb_ot_layout_table_select_script(
face, TRUETYPE_TAG('G', 'S', 'U', 'B'), aNumTags, aScriptTags, &index,
&chosenScript);
return found && chosenScript != TRUETYPE_TAG('D', 'F', 'L', 'T');
}
nsresult gfxFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
MOZ_ASSERT(false, "using default no-op implementation of ReadCMAP");
RefPtr<gfxCharacterMap> cmap = new gfxCharacterMap();
if (mCharacterMap.compareExchange(nullptr, cmap.get())) {
Unused << cmap.forget(); // mCharacterMap now owns the reference
}
return NS_OK;
}
nsCString gfxFontEntry::RealFaceName() {
AutoTable nameTable(this, TRUETYPE_TAG('n', 'a', 'm', 'e'));
if (nameTable) {
nsAutoCString name;
nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name);
if (NS_SUCCEEDED(rv)) {
return std::move(name);
}
}
return Name();
}
already_AddRefed<gfxFont> gfxFontEntry::FindOrMakeFont(
const gfxFontStyle* aStyle, gfxCharacterMap* aUnicodeRangeMap) {
RefPtr<gfxFont> font =
gfxFontCache::GetCache()->Lookup(this, aStyle, aUnicodeRangeMap);
if (font) {
return font.forget();
}
font = CreateFontInstance(aStyle);
if (!font || !font->Valid()) {
return nullptr;
}
font->SetUnicodeRangeMap(aUnicodeRangeMap);
return gfxFontCache::GetCache()->MaybeInsert(std::move(font));
}
uint16_t gfxFontEntry::UnitsPerEm() {
if (!mUnitsPerEm) {
AutoTable headTable(this, TRUETYPE_TAG('h', 'e', 'a', 'd'));
if (headTable) {
uint32_t len;
const HeadTable* head =
reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, &len));
if (len >= sizeof(HeadTable)) {
mUnitsPerEm = head->unitsPerEm;
}
}
// if we didn't find a usable 'head' table, or if the value was
// outside the valid range, record it as invalid
if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) {
mUnitsPerEm = kInvalidUPEM;
}
}
return mUnitsPerEm;
}
bool gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) {
NS_ASSERTION(mSVGInitialized,
"SVG data has not yet been loaded. TryGetSVGData() first.");
return GetSVGGlyphs()->HasSVGGlyph(aGlyphId);
}
bool gfxFontEntry::GetSVGGlyphExtents(DrawTarget* aDrawTarget,
uint32_t aGlyphId, gfxFloat aSize,
gfxRect* aResult) {
MOZ_ASSERT(mSVGInitialized,
"SVG data has not yet been loaded. TryGetSVGData() first.");
MOZ_ASSERT(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM,
"font has invalid unitsPerEm");
gfxMatrix svgToApp(aSize / mUnitsPerEm, 0, 0, aSize / mUnitsPerEm, 0, 0);
return GetSVGGlyphs()->GetGlyphExtents(aGlyphId, svgToApp, aResult);
}
void gfxFontEntry::RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId,
SVGContextPaint* aContextPaint) {
NS_ASSERTION(mSVGInitialized,
"SVG data has not yet been loaded. TryGetSVGData() first.");
GetSVGGlyphs()->RenderGlyph(aContext, aGlyphId, aContextPaint);
}
bool gfxFontEntry::TryGetSVGData(const gfxFont* aFont) {
if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) {
return false;
}
// We don't support SVG-in-OT glyphs in offscreen-canvas worker threads.
if (!NS_IsMainThread()) {
return false;
}
if (!mSVGInitialized) {
// If UnitsPerEm is not known/valid, we can't use SVG glyphs
if (UnitsPerEm() == kInvalidUPEM) {
mSVGInitialized = true;
return false;
}
// We don't use AutoTable here because we'll pass ownership of this
// blob to the gfxSVGGlyphs, once we've confirmed the table exists
hb_blob_t* svgTable = GetFontTable(TRUETYPE_TAG('S', 'V', 'G', ' '));
if (!svgTable) {
mSVGInitialized = true;
return false;
}
// gfxSVGGlyphs will hb_blob_destroy() the table when it is finished
// with it.
auto* svgGlyphs = new gfxSVGGlyphs(svgTable, this);
if (!mSVGGlyphs.compareExchange(nullptr, svgGlyphs)) {
delete svgGlyphs;
}
mSVGInitialized = true;
}
if (GetSVGGlyphs()) {
AutoWriteLock lock(mLock);
if (!mFontsUsingSVGGlyphs.Contains(aFont)) {
mFontsUsingSVGGlyphs.AppendElement(aFont);
}
}
return !!GetSVGGlyphs();
}
void gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) {
AutoWriteLock lock(mLock);
mFontsUsingSVGGlyphs.RemoveElement(aFont);
}
void gfxFontEntry::NotifyGlyphsChanged() {
AutoReadLock lock(mLock);
for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
const gfxFont* font = mFontsUsingSVGGlyphs[i];
font->NotifyGlyphsChanged();
}
}
bool gfxFontEntry::TryGetColorGlyphs() {
if (mCheckedForColorGlyph) {
return mCOLR && mCPAL;
}
auto* colr = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'));
auto* cpal = colr ? GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L')) : nullptr;
if (colr && cpal && gfx::COLRFonts::ValidateColorGlyphs(colr, cpal)) {
if (!mCOLR.compareExchange(nullptr, colr)) {
hb_blob_destroy(colr);
}
if (!mCPAL.compareExchange(nullptr, cpal)) {
hb_blob_destroy(cpal);
}
} else {
hb_blob_destroy(colr);
hb_blob_destroy(cpal);
}
mCheckedForColorGlyph = true;
return mCOLR && mCPAL;
}
/**
* FontTableBlobData
*
* See FontTableHashEntry for the general strategy.
*/
class gfxFontEntry::FontTableBlobData {
public:
explicit FontTableBlobData(nsTArray<uint8_t>&& aBuffer)
: mTableData(std::move(aBuffer)), mHashtable(nullptr), mHashKey(0) {
MOZ_COUNT_CTOR(FontTableBlobData);
}
~FontTableBlobData() {
MOZ_COUNT_DTOR(FontTableBlobData);
if (mHashtable && mHashKey) {
mHashtable->RemoveEntry(mHashKey);
}
}
// Useful for creating blobs
const char* GetTable() const {
return reinterpret_cast<const char*>(mTableData.Elements());
}
uint32_t GetTableLength() const { return mTableData.Length(); }
// Tell this FontTableBlobData to remove the HashEntry when this is
// destroyed.
void ManageHashEntry(nsTHashtable<FontTableHashEntry>* aHashtable,
uint32_t aHashKey) {
mHashtable = aHashtable;
mHashKey = aHashKey;
}
// Disconnect from the HashEntry (because the blob has already been
// removed from the hashtable).
void ForgetHashEntry() {
mHashtable = nullptr;
mHashKey = 0;
}
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
return mTableData.ShallowSizeOfExcludingThis(aMallocSizeOf);
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
private:
// The font table data block
nsTArray<uint8_t> mTableData;
// The blob destroy function needs to know the owning hashtable
// and the hashtable key, so that it can remove the entry.
nsTHashtable<FontTableHashEntry>* mHashtable;
uint32_t mHashKey;
// not implemented
FontTableBlobData(const FontTableBlobData&);
};
hb_blob_t* gfxFontEntry::FontTableHashEntry::ShareTableAndGetBlob(
nsTArray<uint8_t>&& aTable, nsTHashtable<FontTableHashEntry>* aHashtable) {
Clear();
// adopts elements of aTable
mSharedBlobData = new FontTableBlobData(std::move(aTable));
mBlob = hb_blob_create(
mSharedBlobData->GetTable(), mSharedBlobData->GetTableLength(),
HB_MEMORY_MODE_READONLY, mSharedBlobData, DeleteFontTableBlobData);
if (mBlob == hb_blob_get_empty()) {
// The FontTableBlobData was destroyed during hb_blob_create().
// The (empty) blob is still be held in the hashtable with a strong
// reference.
return hb_blob_reference(mBlob);
}
// Tell the FontTableBlobData to remove this hash entry when destroyed.
// The hashtable does not keep a strong reference.
mSharedBlobData->ManageHashEntry(aHashtable, GetKey());
return mBlob;
}
void gfxFontEntry::FontTableHashEntry::Clear() {
// If the FontTableBlobData is managing the hash entry, then the blob is
// not owned by this HashEntry; otherwise there is strong reference to the
// blob that must be removed.
if (mSharedBlobData) {
mSharedBlobData->ForgetHashEntry();
mSharedBlobData = nullptr;
} else {
hb_blob_destroy(mBlob);
}
mBlob = nullptr;
}
// a hb_destroy_func for hb_blob_create
/* static */
void gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(
void* aBlobData) {
delete static_cast<FontTableBlobData*>(aBlobData);
}
hb_blob_t* gfxFontEntry::FontTableHashEntry::GetBlob() const {
return hb_blob_reference(mBlob);
}
bool gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob) {
// Accessing the mFontTableCache pointer is atomic, so we don't need to take
// a write lock even if we're initializing it here...
MOZ_PUSH_IGNORE_THREAD_SAFETY
if (MOZ_UNLIKELY(!mFontTableCache)) {
// We do this here rather than on fontEntry construction
// because not all shapers will access the table cache at all.
//
// We're not holding a write lock, so make sure to atomically update
// the cache pointer.
auto* newCache = new FontTableCache(8);
if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) {
delete newCache;
}
}
FontTableCache* cache = GetFontTableCache();
MOZ_POP_THREAD_SAFETY
// ...but we do need a lock to read the actual hashtable contents.
AutoReadLock lock(mLock);
FontTableHashEntry* entry = cache->GetEntry(aTag);
if (!entry) {
return false;
}
*aBlob = entry->GetBlob();
return true;
}
hb_blob_t* gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag,
nsTArray<uint8_t>* aBuffer) {
MOZ_PUSH_IGNORE_THREAD_SAFETY
if (MOZ_UNLIKELY(!mFontTableCache)) {
auto* newCache = new FontTableCache(8);
if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) {
delete newCache;
}
}
FontTableCache* cache = GetFontTableCache();
MOZ_POP_THREAD_SAFETY
AutoWriteLock lock(mLock);
FontTableHashEntry* entry = cache->PutEntry(aTag);
if (MOZ_UNLIKELY(!entry)) { // OOM
return nullptr;
}
if (!aBuffer) {
// ensure the entry is null
entry->Clear();
return nullptr;
}
return entry->ShareTableAndGetBlob(std::move(*aBuffer), cache);
}
already_AddRefed<gfxCharacterMap> gfxFontEntry::GetCMAPFromFontInfo(
FontInfoData* aFontInfoData, uint32_t& aUVSOffset) {
if (!aFontInfoData || !aFontInfoData->mLoadCmaps) {
return nullptr;
}
return aFontInfoData->GetCMAP(mName, aUVSOffset);
}
hb_blob_t* gfxFontEntry::GetFontTable(uint32_t aTag) {
hb_blob_t* blob;
if (GetExistingFontTable(aTag, &blob)) {
return blob;
}
nsTArray<uint8_t> buffer;
bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer));
return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr);
}
// callback for HarfBuzz to get a font table (in hb_blob_t form)
// from the font entry (passed as aUserData)
/*static*/
hb_blob_t* gfxFontEntry::HBGetTable(hb_face_t* face, uint32_t aTag,
void* aUserData) {
gfxFontEntry* fontEntry = static_cast<gfxFontEntry*>(aUserData);
// bug 589682 - ignore the GDEF table in buggy fonts (applies to
// Italic and BoldItalic faces of Times New Roman)
if (aTag == TRUETYPE_TAG('G', 'D', 'E', 'F') && fontEntry->IgnoreGDEF()) {
return nullptr;
}
// bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto,
// at least on some Android ICS devices; set in gfxFT2FontList.cpp)
if (aTag == TRUETYPE_TAG('G', 'S', 'U', 'B') && fontEntry->IgnoreGSUB()) {
return nullptr;
}
return fontEntry->GetFontTable(aTag);
}
struct gfxFontEntry::GrSandboxData {
rlbox_sandbox_gr sandbox;
sandbox_callback_gr<const void* (*)(const void*, unsigned int, unsigned int*)>
grGetTableCallback;
sandbox_callback_gr<void (*)(const void*, const void*)>
grReleaseTableCallback;
// Text Shapers register a callback to get glyph advances
sandbox_callback_gr<float (*)(const void*, uint16_t)>
grGetGlyphAdvanceCallback;
GrSandboxData() {
sandbox.create_sandbox();
grGetTableCallback = sandbox.register_callback(GrGetTable);
grReleaseTableCallback = sandbox.register_callback(GrReleaseTable);
grGetGlyphAdvanceCallback =
sandbox.register_callback(gfxGraphiteShaper::GrGetAdvance);
}
~GrSandboxData() {
grGetTableCallback.unregister();
grReleaseTableCallback.unregister();
grGetGlyphAdvanceCallback.unregister();
sandbox.destroy_sandbox();
}
};
static thread_local gfxFontEntry* tl_grGetFontTableCallbackData = nullptr;
/*static*/
tainted_opaque_gr<const void*> gfxFontEntry::GrGetTable(
rlbox_sandbox_gr& sandbox,
tainted_opaque_gr<const void*> /* aAppFaceHandle */,
tainted_opaque_gr<unsigned int> aName,
tainted_opaque_gr<unsigned int*> aLen) {
gfxFontEntry* fontEntry = tl_grGetFontTableCallbackData;
tainted_gr<unsigned int*> t_aLen = rlbox::from_opaque(aLen);
*t_aLen = 0;
tainted_gr<const void*> ret = nullptr;
if (fontEntry) {
unsigned int fontTableKey =
rlbox::from_opaque(aName).unverified_safe_because(
"This is only being used to index into a hashmap, which is robust "
"for any value. No checks needed.");
gfxFontUtils::AutoHBBlob blob(fontEntry->GetFontTable(fontTableKey));
if (blob) {
unsigned int blobLength;
const void* tableData = hb_blob_get_data(blob, &blobLength);
// tableData is read-only data shared with the sandbox.
// Making a copy in sandbox memory
tainted_gr<void*> t_tableData = rlbox::sandbox_reinterpret_cast<void*>(
sandbox.malloc_in_sandbox<char>(blobLength));
if (t_tableData) {
rlbox::memcpy(sandbox, t_tableData, tableData, blobLength);
*t_aLen = blobLength;
ret = rlbox::sandbox_const_cast<const void*>(t_tableData);
}
}
}
return ret.to_opaque();
}
/*static*/
void gfxFontEntry::GrReleaseTable(
rlbox_sandbox_gr& sandbox,
tainted_opaque_gr<const void*> /* aAppFaceHandle */,
tainted_opaque_gr<const void*> aTableBuffer) {
sandbox.free_in_sandbox(rlbox::from_opaque(aTableBuffer));
}
rlbox_sandbox_gr* gfxFontEntry::GetGrSandbox() {
AutoReadLock lock(mLock);
MOZ_ASSERT(mSandboxData != nullptr);
return &mSandboxData->sandbox;
}
sandbox_callback_gr<float (*)(const void*, uint16_t)>*
gfxFontEntry::GetGrSandboxAdvanceCallbackHandle() {
AutoReadLock lock(mLock);
MOZ_ASSERT(mSandboxData != nullptr);
return &mSandboxData->grGetGlyphAdvanceCallback;
}
tainted_opaque_gr<gr_face*> gfxFontEntry::GetGrFace() {
if (!mGrFaceInitialized) {
// When possible, the below code will use WASM as a sandboxing mechanism.
// At this time the wasm sandbox does not support threads.
// If Thebes is updated to make callst to the sandbox on multiple threaads,
// we need to make sure the underlying sandbox supports threading.
MOZ_ASSERT(NS_IsMainThread());
mSandboxData = new GrSandboxData();
auto p_faceOps = mSandboxData->sandbox.malloc_in_sandbox<gr_face_ops>();
if (!p_faceOps) {
MOZ_CRASH("Graphite sandbox memory allocation failed");
}
p_faceOps->size = sizeof(*p_faceOps);
p_faceOps->get_table = mSandboxData->grGetTableCallback;
p_faceOps->release_table = mSandboxData->grReleaseTableCallback;
tl_grGetFontTableCallbackData = this;
auto face = sandbox_invoke(
mSandboxData->sandbox, gr_make_face_with_ops,
// For security, we do not pass the callback data to this arg, and use
// a TLS var instead. However, gr_make_face_with_ops expects this to
// be a non null ptr. Therefore, we should pass some dummy non null
// pointer which will be passed to callbacks, but never used. Let's just
// pass p_faceOps again, as this is a non-null tainted pointer.
p_faceOps /* appFaceHandle */, p_faceOps, gr_face_default);
tl_grGetFontTableCallbackData = nullptr;
mGrFace = face.to_opaque();
mGrFaceInitialized = true;
mSandboxData->sandbox.free_in_sandbox(p_faceOps);
}
++mGrFaceRefCnt;
return mGrFace;
}
void gfxFontEntry::ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace) {
MOZ_ASSERT(
(rlbox::from_opaque(aFace) == rlbox::from_opaque(mGrFace))
.unverified_safe_because(
"This is safe as the only thing we are doing is comparing "
"addresses of two tainted pointers. Furthermore this is used "
"merely as a debugging aid in the debug builds. This function is "
"called only from the trusted Firefox code rather than the "
"untrusted libGraphite.")); // sanity-check
MOZ_ASSERT(mGrFaceRefCnt > 0);
if (--mGrFaceRefCnt == 0) {
auto t_mGrFace = rlbox::from_opaque(mGrFace);
tl_grGetFontTableCallbackData = this;
sandbox_invoke(mSandboxData->sandbox, gr_face_destroy, t_mGrFace);
tl_grGetFontTableCallbackData = nullptr;
t_mGrFace = nullptr;
mGrFace = t_mGrFace.to_opaque();
delete mSandboxData;
mSandboxData = nullptr;
mGrFaceInitialized = false;
}
}
void gfxFontEntry::DisconnectSVG() {
if (mSVGInitialized && mSVGGlyphs) {
mSVGGlyphs = nullptr;
mSVGInitialized = false;
}
}
bool gfxFontEntry::HasFontTable(uint32_t aTableTag) {
AutoTable table(this, aTableTag);
return table && hb_blob_get_length(table) > 0;
}
tainted_boolean_hint gfxFontEntry::HasGraphiteSpaceContextuals() {
LazyFlag flag = mHasGraphiteSpaceContextuals;
if (flag == LazyFlag::Uninitialized) {
auto face = GetGrFace();
auto t_face = rlbox::from_opaque(face);
if (t_face) {
tainted_gr<const gr_faceinfo*> faceInfo =
sandbox_invoke(mSandboxData->sandbox, gr_face_info, t_face, 0);
// Comparison with a value in sandboxed memory returns a
// tainted_boolean_hint, i.e. a "hint", since the value could be changed
// maliciously at any moment.
tainted_boolean_hint is_not_none =
faceInfo->space_contextuals != gr_faceinfo::gr_space_none;
flag = is_not_none.unverified_safe_because(
"Note ideally mHasGraphiteSpaceContextuals would be "
"tainted_boolean_hint, but RLBox does not yet support "
"bitfields, so it is not wrapped. However, its value is only "
"ever accessed through this function which returns a "
"tainted_boolean_hint, so unwrapping temporarily is safe. "
"We remove the wrapper now and re-add it below.")
? LazyFlag::Yes
: LazyFlag::No;
}
ReleaseGrFace(face); // always balance GetGrFace, even if face is null
mHasGraphiteSpaceContextuals = flag;
}
return tainted_boolean_hint(flag == LazyFlag::Yes);
}
#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag
static_assert(int(intl::Script::NUM_SCRIPT_CODES) <= FEATURE_SCRIPT_MASK,
"Too many script codes");
// high-order three bytes of tag with script in low-order byte
#define SCRIPT_FEATURE(s, tag) \
(((~FEATURE_SCRIPT_MASK) & (tag)) | \
((FEATURE_SCRIPT_MASK) & static_cast<uint32_t>(s)))
bool gfxFontEntry::SupportsOpenTypeFeature(Script aScript,
uint32_t aFeatureTag) {
MutexAutoLock lock(mFeatureInfoLock);
if (!mSupportedFeatures) {
mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>();
}
// note: high-order three bytes *must* be unique for each feature
// listed below (see SCRIPT_FEATURE macro def'n)
NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') ||
aFeatureTag == HB_TAG('c', '2', 's', 'c') ||
aFeatureTag == HB_TAG('p', 'c', 'a', 'p') ||
aFeatureTag == HB_TAG('c', '2', 'p', 'c') ||
aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
aFeatureTag == HB_TAG('s', 'u', 'b', 's') ||
aFeatureTag == HB_TAG('v', 'e', 'r', 't'),
"use of unknown feature tag");
// note: graphite feature support uses the last script index
NS_ASSERTION(int(aScript) < FEATURE_SCRIPT_MASK - 1,
"need to bump the size of the feature shift");
uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
return mSupportedFeatures->LookupOrInsertWith(scriptFeature, [&] {
bool result = false;
auto face(GetHBFace());
if (hb_ot_layout_has_substitution(face)) {
hb_script_t hbScript =
gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
// Get the OpenType tag(s) that match this script code
unsigned int scriptCount = 4;
hb_tag_t scriptTags[4];
hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID,
&scriptCount, scriptTags, nullptr,
nullptr);
// Append DEFAULT to the returned tags, if room
if (scriptCount < 4) {
scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT;
}
// Now check for 'smcp' under the first of those scripts that is present
const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B');
result = std::any_of(scriptTags, scriptTags + scriptCount,
[&](const hb_tag_t& scriptTag) {
unsigned int scriptIndex;
return hb_ot_layout_table_find_script(
face, kGSUB, scriptTag, &scriptIndex) &&
hb_ot_layout_language_find_feature(
face, kGSUB, scriptIndex,
HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
aFeatureTag, nullptr);
});
}
return result;
});
}
const hb_set_t* gfxFontEntry::InputsForOpenTypeFeature(Script aScript,
uint32_t aFeatureTag) {
MutexAutoLock lock(mFeatureInfoLock);
if (!mFeatureInputs) {
mFeatureInputs = MakeUnique<nsTHashMap<nsUint32HashKey, hb_set_t*>>();
}
NS_ASSERTION(aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
aFeatureTag == HB_TAG('s', 'u', 'b', 's') ||
aFeatureTag == HB_TAG('v', 'e', 'r', 't'),
"use of unknown feature tag");
uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
hb_set_t* inputGlyphs;
if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) {
return inputGlyphs;
}
inputGlyphs = hb_set_create();
auto face(GetHBFace());
if (hb_ot_layout_has_substitution(face)) {
hb_script_t hbScript =
gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
// Get the OpenType tag(s) that match this script code
unsigned int scriptCount = 4;
hb_tag_t scriptTags[5]; // space for null terminator
hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID,
&scriptCount, scriptTags, nullptr,
nullptr);
// Append DEFAULT to the returned tags, if room
if (scriptCount < 4) {
scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT;
}
scriptTags[scriptCount++] = 0;
const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B');
hb_tag_t features[2] = {aFeatureTag, HB_TAG_NONE};
hb_set_t* featurelookups = hb_set_create();
hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr, features,
featurelookups);
hb_codepoint_t index = -1;
while (hb_set_next(featurelookups, &index)) {
hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index, nullptr,
inputGlyphs, nullptr, nullptr);
}
hb_set_destroy(featurelookups);
}
mFeatureInputs->InsertOrUpdate(scriptFeature, inputGlyphs);
return inputGlyphs;
}
bool gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag) {
MutexAutoLock lock(mFeatureInfoLock);
if (!mSupportedFeatures) {
mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>();
}
// note: high-order three bytes *must* be unique for each feature
// listed below (see SCRIPT_FEATURE macro def'n)
NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') ||
aFeatureTag == HB_TAG('c', '2', 's', 'c') ||
aFeatureTag == HB_TAG('p', 'c', 'a', 'p') ||
aFeatureTag == HB_TAG('c', '2', 'p', 'c') ||
aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
aFeatureTag == HB_TAG('s', 'u', 'b', 's'),
"use of unknown feature tag");
// graphite feature check uses the last script slot
uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag);
bool result;
if (mSupportedFeatures->Get(scriptFeature, &result)) {
return result;
}
auto face = GetGrFace();
auto t_face = rlbox::from_opaque(face);
result = t_face ? sandbox_invoke(mSandboxData->sandbox, gr_face_find_fref,
t_face, aFeatureTag) != nullptr
: false;
ReleaseGrFace(face);
mSupportedFeatures->InsertOrUpdate(scriptFeature, result);
return result;
}
void gfxFontEntry::GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo) {
// TODO: implement alternative code path for graphite fonts
auto autoFace(GetHBFace());
// Expose the raw hb_face_t to be captured by the lambdas (not the
// AutoHBFace wrapper).
hb_face_t* face = autoFace;
// Get the list of features for a specific <script,langSys> pair and
// append them to aFeatureInfo.
auto collectForLang = [=, &aFeatureInfo](
hb_tag_t aTableTag, unsigned int aScript,
hb_tag_t aScriptTag, unsigned int aLang,
hb_tag_t aLangTag) {
unsigned int featCount = hb_ot_layout_language_get_feature_tags(
face, aTableTag, aScript, aLang, 0, nullptr, nullptr);
AutoTArray<hb_tag_t, 32> featTags;
featTags.SetLength(featCount);
hb_ot_layout_language_get_feature_tags(face, aTableTag, aScript, aLang, 0,
&featCount, featTags.Elements());
MOZ_ASSERT(featCount <= featTags.Length());
// Just in case HB didn't fill featTags (i.e. in case it returned fewer
// tags than it promised), we truncate at the length it says it filled:
featTags.SetLength(featCount);
for (hb_tag_t t : featTags) {
aFeatureInfo.AppendElement(gfxFontFeatureInfo{t, aScriptTag, aLangTag});
}
};
// Iterate over the language systems supported by a given script,
// and call collectForLang for each of them.
auto collectForScript = [=](hb_tag_t aTableTag, unsigned int aScript,
hb_tag_t aScriptTag) {
collectForLang(aTableTag, aScript, aScriptTag,
HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
HB_TAG('d', 'f', 'l', 't'));
unsigned int langCount = hb_ot_layout_script_get_language_tags(
face, aTableTag, aScript, 0, nullptr, nullptr);
AutoTArray<hb_tag_t, 32> langTags;
langTags.SetLength(langCount);