Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "gfxSVGGlyphs.h"
6
7
#include "mozilla/BasePrincipal.h"
8
#include "mozilla/LoadInfo.h"
9
#include "mozilla/NullPrincipal.h"
10
#include "mozilla/PresShell.h"
11
#include "mozilla/SMILAnimationController.h"
12
#include "mozilla/SVGContextPaint.h"
13
#include "mozilla/dom/Document.h"
14
#include "mozilla/dom/Element.h"
15
#include "mozilla/dom/FontTableURIProtocolHandler.h"
16
#include "mozilla/dom/ImageTracker.h"
17
#include "mozilla/dom/SVGDocument.h"
18
#include "nsError.h"
19
#include "nsString.h"
20
#include "nsICategoryManager.h"
21
#include "nsIDocumentLoaderFactory.h"
22
#include "nsIContentViewer.h"
23
#include "nsIStreamListener.h"
24
#include "nsServiceManagerUtils.h"
25
#include "nsNetUtil.h"
26
#include "nsIInputStream.h"
27
#include "nsStringStream.h"
28
#include "nsStreamUtils.h"
29
#include "nsIPrincipal.h"
30
#include "nsSVGUtils.h"
31
#include "nsContentUtils.h"
32
#include "gfxFont.h"
33
#include "gfxContext.h"
34
#include "harfbuzz/hb.h"
35
#include "zlib.h"
36
37
#define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml")
38
#define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8")
39
40
using namespace mozilla;
41
using mozilla::dom::Document;
42
using mozilla::dom::Element;
43
44
/* static */
45
const mozilla::gfx::Color SimpleTextContextPaint::sZero;
46
47
gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t* aSVGTable, gfxFontEntry* aFontEntry)
48
: mSVGData(aSVGTable), mFontEntry(aFontEntry) {
49
unsigned int length;
50
const char* svgData = hb_blob_get_data(mSVGData, &length);
51
mHeader = reinterpret_cast<const Header*>(svgData);
52
mDocIndex = nullptr;
53
54
if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 &&
55
uint64_t(mHeader->mDocIndexOffset) + 2 <= length) {
56
const DocIndex* docIndex =
57
reinterpret_cast<const DocIndex*>(svgData + mHeader->mDocIndexOffset);
58
// Limit the number of documents to avoid overflow
59
if (uint64_t(mHeader->mDocIndexOffset) + 2 +
60
uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <=
61
length) {
62
mDocIndex = docIndex;
63
}
64
}
65
}
66
67
gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData); }
68
69
void gfxSVGGlyphs::DidRefresh() { mFontEntry->NotifyGlyphsChanged(); }
70
71
/*
72
* Comparison operator for finding a range containing a given glyph ID. Simply
73
* checks whether |key| is less (greater) than every element of |range|, in
74
* which case return |key| < |range| (|key| > |range|). Otherwise |key| is in
75
* |range|, in which case return equality.
76
* The total ordering here is guaranteed by
77
* (1) the index ranges being disjoint; and
78
* (2) the (sole) key always being a singleton, so intersection => containment
79
* (note that this is wrong if we have more than one intersection or two
80
* sets intersecting of size > 1 -- so... don't do that)
81
*/
82
/* static */
83
int gfxSVGGlyphs::CompareIndexEntries(const void* aKey, const void* aEntry) {
84
const uint32_t key = *(uint32_t*)aKey;
85
const IndexEntry* entry = (const IndexEntry*)aEntry;
86
87
if (key < uint16_t(entry->mStartGlyph)) {
88
return -1;
89
}
90
if (key > uint16_t(entry->mEndGlyph)) {
91
return 1;
92
}
93
return 0;
94
}
95
96
gfxSVGGlyphsDocument* gfxSVGGlyphs::FindOrCreateGlyphsDocument(
97
uint32_t aGlyphId) {
98
if (!mDocIndex) {
99
// Invalid table
100
return nullptr;
101
}
102
103
IndexEntry* entry = (IndexEntry*)bsearch(
104
&aGlyphId, mDocIndex->mEntries, uint16_t(mDocIndex->mNumEntries),
105
sizeof(IndexEntry), CompareIndexEntries);
106
if (!entry) {
107
return nullptr;
108
}
109
110
gfxSVGGlyphsDocument* result = mGlyphDocs.Get(entry->mDocOffset);
111
112
if (!result) {
113
unsigned int length;
114
const uint8_t* data = (const uint8_t*)hb_blob_get_data(mSVGData, &length);
115
if (entry->mDocOffset > 0 && uint64_t(mHeader->mDocIndexOffset) +
116
entry->mDocOffset +
117
entry->mDocLength <=
118
length) {
119
result = new gfxSVGGlyphsDocument(
120
data + mHeader->mDocIndexOffset + entry->mDocOffset,
121
entry->mDocLength, this);
122
mGlyphDocs.Put(entry->mDocOffset, result);
123
}
124
}
125
126
return result;
127
}
128
129
nsresult gfxSVGGlyphsDocument::SetupPresentation() {
130
nsCOMPtr<nsICategoryManager> catMan =
131
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
132
nsCString contractId;
133
nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
134
"image/svg+xml", contractId);
135
NS_ENSURE_SUCCESS(rv, rv);
136
137
nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
138
do_GetService(contractId.get());
139
NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
140
141
nsCOMPtr<nsIContentViewer> viewer;
142
rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr,
143
getter_AddRefs(viewer));
144
NS_ENSURE_SUCCESS(rv, rv);
145
146
rv = viewer->Init(nullptr, gfx::IntRect(0, 0, 1000, 1000), nullptr);
147
if (NS_SUCCEEDED(rv)) {
148
rv = viewer->Open(nullptr, nullptr);
149
NS_ENSURE_SUCCESS(rv, rv);
150
}
151
152
RefPtr<PresShell> presShell = viewer->GetPresShell();
153
if (!presShell->DidInitialize()) {
154
rv = presShell->Initialize();
155
NS_ENSURE_SUCCESS(rv, rv);
156
}
157
158
mDocument->FlushPendingNotifications(FlushType::Layout);
159
160
if (mDocument->HasAnimationController()) {
161
mDocument->GetAnimationController()->Resume(SMILTimeContainer::PAUSE_IMAGE);
162
}
163
mDocument->ImageTracker()->SetAnimatingState(true);
164
165
mViewer = viewer;
166
mPresShell = presShell;
167
mPresShell->AddPostRefreshObserver(this);
168
169
return NS_OK;
170
}
171
172
void gfxSVGGlyphsDocument::DidRefresh() { mOwner->DidRefresh(); }
173
174
/**
175
* Walk the DOM tree to find all glyph elements and insert them into the lookup
176
* table
177
* @param aElem The element to search from
178
*/
179
void gfxSVGGlyphsDocument::FindGlyphElements(Element* aElem) {
180
for (nsIContent* child = aElem->GetLastChild(); child;
181
child = child->GetPreviousSibling()) {
182
if (!child->IsElement()) {
183
continue;
184
}
185
FindGlyphElements(child->AsElement());
186
}
187
188
InsertGlyphId(aElem);
189
}
190
191
/**
192
* If there exists an SVG glyph with the specified glyph id, render it and
193
* return true If no such glyph exists, or in the case of an error return false
194
* @param aContext The thebes aContext to draw to
195
* @param aGlyphId The glyph id
196
* @return true iff rendering succeeded
197
*/
198
void gfxSVGGlyphs::RenderGlyph(gfxContext* aContext, uint32_t aGlyphId,
199
SVGContextPaint* aContextPaint) {
200
gfxContextAutoSaveRestore aContextRestorer(aContext);
201
202
Element* glyph = mGlyphIdMap.Get(aGlyphId);
203
MOZ_ASSERT(glyph, "No glyph element. Should check with HasSVGGlyph() first!");
204
205
AutoSetRestoreSVGContextPaint autoSetRestore(
206
*aContextPaint, *glyph->OwnerDoc()->AsSVGDocument());
207
208
nsSVGUtils::PaintSVGGlyph(glyph, aContext);
209
}
210
211
bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId,
212
const gfxMatrix& aSVGToAppSpace,
213
gfxRect* aResult) {
214
Element* glyph = mGlyphIdMap.Get(aGlyphId);
215
NS_ASSERTION(glyph,
216
"No glyph element. Should check with HasSVGGlyph() first!");
217
218
return nsSVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult);
219
}
220
221
Element* gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) {
222
Element* elem;
223
224
if (!mGlyphIdMap.Get(aGlyphId, &elem)) {
225
elem = nullptr;
226
if (gfxSVGGlyphsDocument* set = FindOrCreateGlyphsDocument(aGlyphId)) {
227
elem = set->GetGlyphElement(aGlyphId);
228
}
229
mGlyphIdMap.Put(aGlyphId, elem);
230
}
231
232
return elem;
233
}
234
235
bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) {
236
return !!GetGlyphElement(aGlyphId);
237
}
238
239
size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
240
// We don't include the size of mSVGData here, because (depending on the
241
// font backend implementation) it will either wrap a block of data owned
242
// by the system (and potentially shared), or a table that's in our font
243
// table cache and therefore already counted.
244
size_t result = aMallocSizeOf(this) +
245
mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) +
246
mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
247
for (auto iter = mGlyphDocs.ConstIter(); !iter.Done(); iter.Next()) {
248
result += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
249
}
250
return result;
251
}
252
253
Element* gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) {
254
return mGlyphIdMap.Get(aGlyphId);
255
}
256
257
gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t* aBuffer,
258
uint32_t aBufLen,
259
gfxSVGGlyphs* aSVGGlyphs)
260
: mOwner(aSVGGlyphs) {
261
if (aBufLen >= 14 && aBuffer[0] == 31 && aBuffer[1] == 139) {
262
// It's a gzip-compressed document; decompress it before parsing.
263
// The original length (modulo 2^32) is found in the last 4 bytes
264
// of the data, stored in little-endian format. We read it as
265
// individual bytes to avoid possible alignment issues.
266
// (Note that if the original length was >2^32, then origLen here
267
// will be incorrect; but then the inflate() call will not return
268
// Z_STREAM_END and we'll bail out safely.)
269
size_t origLen = (size_t(aBuffer[aBufLen - 1]) << 24) +
270
(size_t(aBuffer[aBufLen - 2]) << 16) +
271
(size_t(aBuffer[aBufLen - 3]) << 8) +
272
size_t(aBuffer[aBufLen - 4]);
273
AutoTArray<uint8_t, 4096> outBuf;
274
if (outBuf.SetLength(origLen, mozilla::fallible)) {
275
z_stream s = {0};
276
s.next_in = const_cast<Byte*>(aBuffer);
277
s.avail_in = aBufLen;
278
s.next_out = outBuf.Elements();
279
s.avail_out = outBuf.Length();
280
// The magic number 16 here is the zlib flag to expect gzip format,
282
if (Z_OK == inflateInit2(&s, 16 + MAX_WBITS)) {
283
int result = inflate(&s, Z_FINISH);
284
if (Z_STREAM_END == result) {
285
MOZ_ASSERT(size_t(s.next_out - outBuf.Elements()) == origLen);
286
ParseDocument(outBuf.Elements(), outBuf.Length());
287
} else {
288
NS_WARNING("Failed to decompress SVG glyphs document");
289
}
290
inflateEnd(&s);
291
}
292
} else {
293
NS_WARNING("Failed to allocate memory for SVG glyphs document");
294
}
295
} else {
296
ParseDocument(aBuffer, aBufLen);
297
}
298
299
if (!mDocument) {
300
NS_WARNING("Could not parse SVG glyphs document");
301
return;
302
}
303
304
Element* root = mDocument->GetRootElement();
305
if (!root) {
306
NS_WARNING("Could not parse SVG glyphs document");
307
return;
308
}
309
310
nsresult rv = SetupPresentation();
311
if (NS_FAILED(rv)) {
312
NS_WARNING("Couldn't setup presentation for SVG glyphs document");
313
return;
314
}
315
316
FindGlyphElements(root);
317
}
318
319
gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() {
320
if (mDocument) {
321
mDocument->OnPageHide(false, nullptr);
322
}
323
if (mPresShell) {
324
mPresShell->RemovePostRefreshObserver(this);
325
}
326
if (mViewer) {
327
mViewer->Close(nullptr);
328
mViewer->Destroy();
329
}
330
}
331
332
static nsresult CreateBufferedStream(const uint8_t* aBuffer, uint32_t aBufLen,
333
nsCOMPtr<nsIInputStream>& aResult) {
334
nsCOMPtr<nsIInputStream> stream;
335
nsresult rv = NS_NewByteInputStream(
336
getter_AddRefs(stream),
337
MakeSpan(reinterpret_cast<const char*>(aBuffer), aBufLen),
338
NS_ASSIGNMENT_DEPEND);
339
NS_ENSURE_SUCCESS(rv, rv);
340
341
nsCOMPtr<nsIInputStream> aBufferedStream;
342
if (!NS_InputStreamIsBuffered(stream)) {
343
rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream),
344
stream.forget(), 4096);
345
NS_ENSURE_SUCCESS(rv, rv);
346
stream = aBufferedStream;
347
}
348
349
aResult = stream;
350
351
return NS_OK;
352
}
353
354
nsresult gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer,
355
uint32_t aBufLen) {
356
// Mostly pulled from nsDOMParser::ParseFromStream
357
358
nsCOMPtr<nsIInputStream> stream;
359
nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream);
360
NS_ENSURE_SUCCESS(rv, rv);
361
362
nsCOMPtr<nsIURI> uri;
363
mozilla::dom::FontTableURIProtocolHandler::GenerateURIString(
364
mSVGGlyphsDocumentURI);
365
366
rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI);
367
NS_ENSURE_SUCCESS(rv, rv);
368
369
nsCOMPtr<nsIPrincipal> principal =
370
NullPrincipal::CreateWithoutOriginAttributes();
371
372
RefPtr<Document> document;
373
rv = NS_NewDOMDocument(getter_AddRefs(document),
374
EmptyString(), // aNamespaceURI
375
EmptyString(), // aQualifiedName
376
nullptr, // aDoctype
377
uri, uri, principal,
378
false, // aLoadedAsData
379
nullptr, // aEventObject
380
DocumentFlavorSVG);
381
NS_ENSURE_SUCCESS(rv, rv);
382
383
nsCOMPtr<nsIChannel> channel;
384
rv = NS_NewInputStreamChannel(
385
getter_AddRefs(channel), uri,
386
nullptr, // aStream
387
principal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
388
nsIContentPolicy::TYPE_OTHER, SVG_CONTENT_TYPE, UTF8_CHARSET);
389
NS_ENSURE_SUCCESS(rv, rv);
390
391
// Set this early because various decisions during page-load depend on it.
392
document->SetIsBeingUsedAsImage();
393
document->SetIsSVGGlyphsDocument();
394
document->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED);
395
396
nsCOMPtr<nsIStreamListener> listener;
397
rv = document->StartDocumentLoad("external-resource", channel,
398
nullptr, // aLoadGroup
399
nullptr, // aContainer
400
getter_AddRefs(listener), true /* aReset */);
401
if (NS_FAILED(rv) || !listener) {
402
return NS_ERROR_FAILURE;
403
}
404
405
rv = listener->OnStartRequest(channel);
406
if (NS_FAILED(rv)) {
407
channel->Cancel(rv);
408
}
409
410
nsresult status;
411
channel->GetStatus(&status);
412
if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
413
rv = listener->OnDataAvailable(channel, stream, 0, aBufLen);
414
if (NS_FAILED(rv)) {
415
channel->Cancel(rv);
416
}
417
channel->GetStatus(&status);
418
}
419
420
rv = listener->OnStopRequest(channel, status);
421
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
422
423
document.swap(mDocument);
424
425
return NS_OK;
426
}
427
428
void gfxSVGGlyphsDocument::InsertGlyphId(Element* aGlyphElement) {
429
nsAutoString glyphIdStr;
430
static const uint32_t glyphPrefixLength = 5;
431
// The maximum glyph ID is 65535 so the maximum length of the numeric part
432
// is 5.
433
if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) ||
434
!StringBeginsWith(glyphIdStr, NS_LITERAL_STRING("glyph")) ||
435
glyphIdStr.Length() > glyphPrefixLength + 5) {
436
return;
437
}
438
439
uint32_t id = 0;
440
for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) {
441
char16_t ch = glyphIdStr.CharAt(i);
442
if (ch < '0' || ch > '9') {
443
return;
444
}
445
if (ch == '0' && i == glyphPrefixLength) {
446
return;
447
}
448
id = id * 10 + (ch - '0');
449
}
450
451
mGlyphIdMap.Put(id, aGlyphElement);
452
}
453
454
size_t gfxSVGGlyphsDocument::SizeOfIncludingThis(
455
mozilla::MallocSizeOf aMallocSizeOf) const {
456
return aMallocSizeOf(this) +
457
mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf) +
458
mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
459
}