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