Bug 1874684 - Part 28: Return DateDuration from DifferenceISODateTime. r=mgaudet
[gecko.git] / gfx / thebes / gfxSVGGlyphs.cpp
blob4d688f5b8c16bca9de4937da306a2b17fe7724e2
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/. */
5 #include "gfxSVGGlyphs.h"
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/SVGUtils.h"
14 #include "mozilla/dom/Document.h"
15 #include "mozilla/dom/Element.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 "nsIDocumentViewer.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 "nsContentUtils.h"
31 #include "gfxFont.h"
32 #include "gfxContext.h"
33 #include "harfbuzz/hb.h"
34 #include "zlib.h"
36 #define SVG_CONTENT_TYPE "image/svg+xml"_ns
37 #define UTF8_CHARSET "utf-8"_ns
39 using namespace mozilla;
40 using mozilla::dom::Document;
41 using mozilla::dom::Element;
43 /* static */
44 const mozilla::gfx::DeviceColor SimpleTextContextPaint::sZero;
46 gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t* aSVGTable, gfxFontEntry* aFontEntry)
47 : mSVGData(aSVGTable), mFontEntry(aFontEntry) {
48 unsigned int length;
49 const char* svgData = hb_blob_get_data(mSVGData, &length);
50 mHeader = reinterpret_cast<const Header*>(svgData);
51 mDocIndex = nullptr;
53 if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 &&
54 uint64_t(mHeader->mDocIndexOffset) + 2 <= length) {
55 const DocIndex* docIndex =
56 reinterpret_cast<const DocIndex*>(svgData + mHeader->mDocIndexOffset);
57 // Limit the number of documents to avoid overflow
58 if (uint64_t(mHeader->mDocIndexOffset) + 2 +
59 uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <=
60 length) {
61 mDocIndex = docIndex;
66 gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData); }
68 void gfxSVGGlyphs::DidRefresh() { mFontEntry->NotifyGlyphsChanged(); }
71 * Comparison operator for finding a range containing a given glyph ID. Simply
72 * checks whether |key| is less (greater) than every element of |range|, in
73 * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in
74 * |range|, in which case return equality.
75 * The total ordering here is guaranteed by
76 * (1) the index ranges being disjoint; and
77 * (2) the (sole) key always being a singleton, so intersection => containment
78 * (note that this is wrong if we have more than one intersection or two
79 * sets intersecting of size > 1 -- so... don't do that)
81 /* static */
82 int gfxSVGGlyphs::CompareIndexEntries(const void* aKey, const void* aEntry) {
83 const uint32_t key = *(uint32_t*)aKey;
84 const IndexEntry* entry = (const IndexEntry*)aEntry;
86 if (key < uint16_t(entry->mStartGlyph)) {
87 return -1;
89 if (key > uint16_t(entry->mEndGlyph)) {
90 return 1;
92 return 0;
95 gfxSVGGlyphsDocument* gfxSVGGlyphs::FindOrCreateGlyphsDocument(
96 uint32_t aGlyphId) {
97 if (!mDocIndex) {
98 // Invalid table
99 return nullptr;
102 IndexEntry* entry = (IndexEntry*)bsearch(
103 &aGlyphId, mDocIndex->mEntries, uint16_t(mDocIndex->mNumEntries),
104 sizeof(IndexEntry), CompareIndexEntries);
105 if (!entry) {
106 return nullptr;
109 return mGlyphDocs.WithEntryHandle(
110 entry->mDocOffset, [&](auto&& glyphDocsEntry) -> gfxSVGGlyphsDocument* {
111 if (!glyphDocsEntry) {
112 unsigned int length;
113 const uint8_t* data =
114 (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 return glyphDocsEntry
120 .Insert(MakeUnique<gfxSVGGlyphsDocument>(
121 data + mHeader->mDocIndexOffset + entry->mDocOffset,
122 entry->mDocLength, this))
123 .get();
126 return nullptr;
129 return glyphDocsEntry->get();
133 nsresult gfxSVGGlyphsDocument::SetupPresentation() {
134 nsCOMPtr<nsICategoryManager> catMan =
135 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
136 nsCString contractId;
137 nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
138 "image/svg+xml", contractId);
139 NS_ENSURE_SUCCESS(rv, rv);
141 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
142 do_GetService(contractId.get());
143 NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
145 nsCOMPtr<nsIDocumentViewer> viewer;
146 rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr,
147 getter_AddRefs(viewer));
148 NS_ENSURE_SUCCESS(rv, rv);
150 auto upem = mOwner->FontEntry()->UnitsPerEm();
151 rv = viewer->Init(nullptr, gfx::IntRect(0, 0, upem, upem), nullptr);
152 if (NS_SUCCEEDED(rv)) {
153 rv = viewer->Open(nullptr, nullptr);
154 NS_ENSURE_SUCCESS(rv, rv);
157 RefPtr<PresShell> presShell = viewer->GetPresShell();
158 if (!presShell->DidInitialize()) {
159 rv = presShell->Initialize();
160 NS_ENSURE_SUCCESS(rv, rv);
163 mDocument->FlushPendingNotifications(FlushType::Layout);
165 if (mDocument->HasAnimationController()) {
166 mDocument->GetAnimationController()->Resume(SMILTimeContainer::PAUSE_IMAGE);
168 mDocument->ImageTracker()->SetAnimatingState(true);
170 mViewer = viewer;
171 mPresShell = presShell;
172 mPresShell->AddPostRefreshObserver(this);
174 return NS_OK;
177 void gfxSVGGlyphsDocument::DidRefresh() { mOwner->DidRefresh(); }
180 * Walk the DOM tree to find all glyph elements and insert them into the lookup
181 * table
182 * @param aElem The element to search from
184 void gfxSVGGlyphsDocument::FindGlyphElements(Element* aElem) {
185 for (nsIContent* child = aElem->GetLastChild(); child;
186 child = child->GetPreviousSibling()) {
187 if (!child->IsElement()) {
188 continue;
190 FindGlyphElements(child->AsElement());
193 InsertGlyphId(aElem);
197 * If there exists an SVG glyph with the specified glyph id, render it and
198 * return true If no such glyph exists, or in the case of an error return false
199 * @param aContext The thebes aContext to draw to
200 * @param aGlyphId The glyph id
201 * @return true iff rendering succeeded
203 void gfxSVGGlyphs::RenderGlyph(gfxContext* aContext, uint32_t aGlyphId,
204 SVGContextPaint* aContextPaint) {
205 gfxContextAutoSaveRestore aContextRestorer(aContext);
207 Element* glyph = mGlyphIdMap.Get(aGlyphId);
208 MOZ_ASSERT(glyph, "No glyph element. Should check with HasSVGGlyph() first!");
210 AutoSetRestoreSVGContextPaint autoSetRestore(aContextPaint,
211 glyph->OwnerDoc());
213 SVGUtils::PaintSVGGlyph(glyph, aContext);
215 #if DEBUG
216 // This will not have any effect, because we're about to restore the state
217 // via the aContextRestorer destructor, but it prevents debug builds from
218 // asserting if it turns out that PaintSVGGlyph didn't actually do anything.
219 // This happens if the SVG document consists of just an image, and the image
220 // hasn't finished loading yet so we can't draw it.
221 aContext->SetOp(gfx::CompositionOp::OP_OVER);
222 #endif
225 bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId,
226 const gfxMatrix& aSVGToAppSpace,
227 gfxRect* aResult) {
228 Element* glyph = mGlyphIdMap.Get(aGlyphId);
229 NS_ASSERTION(glyph,
230 "No glyph element. Should check with HasSVGGlyph() first!");
232 return SVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult);
235 Element* gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) {
236 return mGlyphIdMap.LookupOrInsertWith(aGlyphId, [&] {
237 Element* elem = nullptr;
238 if (gfxSVGGlyphsDocument* set = FindOrCreateGlyphsDocument(aGlyphId)) {
239 elem = set->GetGlyphElement(aGlyphId);
241 return elem;
245 bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) {
246 return !!GetGlyphElement(aGlyphId);
249 size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
250 // We don't include the size of mSVGData here, because (depending on the
251 // font backend implementation) it will either wrap a block of data owned
252 // by the system (and potentially shared), or a table that's in our font
253 // table cache and therefore already counted.
254 size_t result = aMallocSizeOf(this) +
255 mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) +
256 mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
257 for (const auto& entry : mGlyphDocs.Values()) {
258 result += entry->SizeOfIncludingThis(aMallocSizeOf);
260 return result;
263 Element* gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) {
264 return mGlyphIdMap.Get(aGlyphId);
267 gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t* aBuffer,
268 uint32_t aBufLen,
269 gfxSVGGlyphs* aSVGGlyphs)
270 : mOwner(aSVGGlyphs) {
271 if (aBufLen >= 14 && aBuffer[0] == 31 && aBuffer[1] == 139) {
272 // It's a gzip-compressed document; decompress it before parsing.
273 // The original length (modulo 2^32) is found in the last 4 bytes
274 // of the data, stored in little-endian format. We read it as
275 // individual bytes to avoid possible alignment issues.
276 // (Note that if the original length was >2^32, then origLen here
277 // will be incorrect; but then the inflate() call will not return
278 // Z_STREAM_END and we'll bail out safely.)
279 size_t origLen = (size_t(aBuffer[aBufLen - 1]) << 24) +
280 (size_t(aBuffer[aBufLen - 2]) << 16) +
281 (size_t(aBuffer[aBufLen - 3]) << 8) +
282 size_t(aBuffer[aBufLen - 4]);
283 AutoTArray<uint8_t, 4096> outBuf;
284 if (outBuf.SetLength(origLen, mozilla::fallible)) {
285 z_stream s = {0};
286 s.next_in = const_cast<Byte*>(aBuffer);
287 s.avail_in = aBufLen;
288 s.next_out = outBuf.Elements();
289 s.avail_out = outBuf.Length();
290 // The magic number 16 here is the zlib flag to expect gzip format,
291 // see http://www.zlib.net/manual.html#Advanced
292 if (Z_OK == inflateInit2(&s, 16 + MAX_WBITS)) {
293 int result = inflate(&s, Z_FINISH);
294 if (Z_STREAM_END == result) {
295 MOZ_ASSERT(size_t(s.next_out - outBuf.Elements()) == origLen);
296 ParseDocument(outBuf.Elements(), outBuf.Length());
297 } else {
298 NS_WARNING("Failed to decompress SVG glyphs document");
300 inflateEnd(&s);
302 } else {
303 NS_WARNING("Failed to allocate memory for SVG glyphs document");
305 } else {
306 ParseDocument(aBuffer, aBufLen);
309 if (!mDocument) {
310 NS_WARNING("Could not parse SVG glyphs document");
311 return;
314 Element* root = mDocument->GetRootElement();
315 if (!root) {
316 NS_WARNING("Could not parse SVG glyphs document");
317 return;
320 nsresult rv = SetupPresentation();
321 if (NS_FAILED(rv)) {
322 NS_WARNING("Couldn't setup presentation for SVG glyphs document");
323 return;
326 FindGlyphElements(root);
329 gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() {
330 if (mDocument) {
331 mDocument->OnPageHide(false, nullptr);
333 if (mPresShell) {
334 mPresShell->RemovePostRefreshObserver(this);
336 if (mViewer) {
337 mViewer->Close(nullptr);
338 mViewer->Destroy();
342 static nsresult CreateBufferedStream(const uint8_t* aBuffer, uint32_t aBufLen,
343 nsCOMPtr<nsIInputStream>& aResult) {
344 nsCOMPtr<nsIInputStream> stream;
345 nsresult rv = NS_NewByteInputStream(
346 getter_AddRefs(stream),
347 Span(reinterpret_cast<const char*>(aBuffer), aBufLen),
348 NS_ASSIGNMENT_DEPEND);
349 NS_ENSURE_SUCCESS(rv, rv);
351 nsCOMPtr<nsIInputStream> aBufferedStream;
352 if (!NS_InputStreamIsBuffered(stream)) {
353 rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream),
354 stream.forget(), 4096);
355 NS_ENSURE_SUCCESS(rv, rv);
356 stream = aBufferedStream;
359 aResult = stream;
361 return NS_OK;
364 nsresult gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer,
365 uint32_t aBufLen) {
366 // Mostly pulled from nsDOMParser::ParseFromStream
368 nsCOMPtr<nsIInputStream> stream;
369 nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream);
370 NS_ENSURE_SUCCESS(rv, rv);
372 // We just need a dummy URI.
373 nsCOMPtr<nsIURI> uri;
374 rv = NS_NewURI(getter_AddRefs(uri), "moz-svg-glyphs://"_ns);
375 NS_ENSURE_SUCCESS(rv, rv);
377 nsCOMPtr<nsIPrincipal> principal =
378 NullPrincipal::CreateWithoutOriginAttributes();
380 RefPtr<Document> document;
381 rv = NS_NewDOMDocument(getter_AddRefs(document),
382 u""_ns, // aNamespaceURI
383 u""_ns, // aQualifiedName
384 nullptr, // aDoctype
385 uri, uri, principal,
386 false, // aLoadedAsData
387 nullptr, // aEventObject
388 DocumentFlavorSVG);
389 NS_ENSURE_SUCCESS(rv, rv);
391 nsCOMPtr<nsIChannel> channel;
392 rv = NS_NewInputStreamChannel(
393 getter_AddRefs(channel), uri,
394 nullptr, // aStream
395 principal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
396 nsIContentPolicy::TYPE_OTHER, SVG_CONTENT_TYPE, UTF8_CHARSET);
397 NS_ENSURE_SUCCESS(rv, rv);
399 // Set this early because various decisions during page-load depend on it.
400 document->SetIsBeingUsedAsImage();
401 document->SetIsSVGGlyphsDocument();
402 document->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED);
404 nsCOMPtr<nsIStreamListener> listener;
405 rv = document->StartDocumentLoad("external-resource", channel,
406 nullptr, // aLoadGroup
407 nullptr, // aContainer
408 getter_AddRefs(listener), true /* aReset */);
409 if (NS_FAILED(rv) || !listener) {
410 return NS_ERROR_FAILURE;
413 rv = listener->OnStartRequest(channel);
414 if (NS_FAILED(rv)) {
415 channel->Cancel(rv);
418 nsresult status;
419 channel->GetStatus(&status);
420 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
421 rv = listener->OnDataAvailable(channel, stream, 0, aBufLen);
422 if (NS_FAILED(rv)) {
423 channel->Cancel(rv);
425 channel->GetStatus(&status);
428 rv = listener->OnStopRequest(channel, status);
429 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
431 document.swap(mDocument);
433 return NS_OK;
436 void gfxSVGGlyphsDocument::InsertGlyphId(Element* aGlyphElement) {
437 nsAutoString glyphIdStr;
438 static const uint32_t glyphPrefixLength = 5;
439 // The maximum glyph ID is 65535 so the maximum length of the numeric part
440 // is 5.
441 if (!aGlyphElement->GetAttr(nsGkAtoms::id, glyphIdStr) ||
442 !StringBeginsWith(glyphIdStr, u"glyph"_ns) ||
443 glyphIdStr.Length() > glyphPrefixLength + 5) {
444 return;
447 uint32_t id = 0;
448 for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) {
449 char16_t ch = glyphIdStr.CharAt(i);
450 if (ch < '0' || ch > '9') {
451 return;
453 if (ch == '0' && i == glyphPrefixLength) {
454 return;
456 id = id * 10 + (ch - '0');
459 mGlyphIdMap.InsertOrUpdate(id, aGlyphElement);
462 size_t gfxSVGGlyphsDocument::SizeOfIncludingThis(
463 mozilla::MallocSizeOf aMallocSizeOf) const {
464 return aMallocSizeOf(this) +
465 mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);