Bug 1700051: part 46) Const-qualify `mozInlineSpellStatus::mAnchorRange`. r=smaug
[gecko.git] / gfx / thebes / gfxSVGGlyphs.cpp
blob223e696a7f74c5ac3860932029648c1c89c1a0e8
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/FontTableURIProtocolHandler.h"
17 #include "mozilla/dom/ImageTracker.h"
18 #include "mozilla/dom/SVGDocument.h"
19 #include "nsError.h"
20 #include "nsString.h"
21 #include "nsICategoryManager.h"
22 #include "nsIDocumentLoaderFactory.h"
23 #include "nsIContentViewer.h"
24 #include "nsIStreamListener.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsNetUtil.h"
27 #include "nsIInputStream.h"
28 #include "nsStringStream.h"
29 #include "nsStreamUtils.h"
30 #include "nsIPrincipal.h"
31 #include "nsContentUtils.h"
32 #include "gfxFont.h"
33 #include "gfxContext.h"
34 #include "harfbuzz/hb.h"
35 #include "zlib.h"
37 #define SVG_CONTENT_TYPE "image/svg+xml"_ns
38 #define UTF8_CHARSET "utf-8"_ns
40 using namespace mozilla;
41 using mozilla::dom::Document;
42 using mozilla::dom::Element;
44 /* static */
45 const mozilla::gfx::DeviceColor SimpleTextContextPaint::sZero;
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;
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;
67 gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData); }
69 void gfxSVGGlyphs::DidRefresh() { mFontEntry->NotifyGlyphsChanged(); }
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)
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;
87 if (key < uint16_t(entry->mStartGlyph)) {
88 return -1;
90 if (key > uint16_t(entry->mEndGlyph)) {
91 return 1;
93 return 0;
96 gfxSVGGlyphsDocument* gfxSVGGlyphs::FindOrCreateGlyphsDocument(
97 uint32_t aGlyphId) {
98 if (!mDocIndex) {
99 // Invalid table
100 return nullptr;
103 IndexEntry* entry = (IndexEntry*)bsearch(
104 &aGlyphId, mDocIndex->mEntries, uint16_t(mDocIndex->mNumEntries),
105 sizeof(IndexEntry), CompareIndexEntries);
106 if (!entry) {
107 return nullptr;
110 return mGlyphDocs.WithEntryHandle(
111 entry->mDocOffset, [&](auto&& glyphDocsEntry) -> gfxSVGGlyphsDocument* {
112 if (!glyphDocsEntry) {
113 unsigned int length;
114 const uint8_t* data =
115 (const uint8_t*)hb_blob_get_data(mSVGData, &length);
116 if (entry->mDocOffset > 0 && uint64_t(mHeader->mDocIndexOffset) +
117 entry->mDocOffset +
118 entry->mDocLength <=
119 length) {
120 return glyphDocsEntry
121 .Insert(MakeUnique<gfxSVGGlyphsDocument>(
122 data + mHeader->mDocIndexOffset + entry->mDocOffset,
123 entry->mDocLength, this))
124 .get();
127 return nullptr;
130 return glyphDocsEntry->get();
134 nsresult gfxSVGGlyphsDocument::SetupPresentation() {
135 nsCOMPtr<nsICategoryManager> catMan =
136 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
137 nsCString contractId;
138 nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
139 "image/svg+xml", contractId);
140 NS_ENSURE_SUCCESS(rv, rv);
142 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
143 do_GetService(contractId.get());
144 NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
146 nsCOMPtr<nsIContentViewer> viewer;
147 rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr,
148 getter_AddRefs(viewer));
149 NS_ENSURE_SUCCESS(rv, rv);
151 rv = viewer->Init(nullptr, gfx::IntRect(0, 0, 1000, 1000), 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(
211 *aContextPaint, *glyph->OwnerDoc()->AsSVGDocument());
213 SVGUtils::PaintSVGGlyph(glyph, aContext);
216 bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId,
217 const gfxMatrix& aSVGToAppSpace,
218 gfxRect* aResult) {
219 Element* glyph = mGlyphIdMap.Get(aGlyphId);
220 NS_ASSERTION(glyph,
221 "No glyph element. Should check with HasSVGGlyph() first!");
223 return SVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult);
226 Element* gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) {
227 return mGlyphIdMap.LookupOrInsertWith(aGlyphId, [&] {
228 Element* elem = nullptr;
229 if (gfxSVGGlyphsDocument* set = FindOrCreateGlyphsDocument(aGlyphId)) {
230 elem = set->GetGlyphElement(aGlyphId);
232 return elem;
236 bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) {
237 return !!GetGlyphElement(aGlyphId);
240 size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
241 // We don't include the size of mSVGData here, because (depending on the
242 // font backend implementation) it will either wrap a block of data owned
243 // by the system (and potentially shared), or a table that's in our font
244 // table cache and therefore already counted.
245 size_t result = aMallocSizeOf(this) +
246 mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) +
247 mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
248 for (const auto& entry : mGlyphDocs.Values()) {
249 result += entry->SizeOfIncludingThis(aMallocSizeOf);
251 return result;
254 Element* gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) {
255 return mGlyphIdMap.Get(aGlyphId);
258 gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t* aBuffer,
259 uint32_t aBufLen,
260 gfxSVGGlyphs* aSVGGlyphs)
261 : mOwner(aSVGGlyphs) {
262 if (aBufLen >= 14 && aBuffer[0] == 31 && aBuffer[1] == 139) {
263 // It's a gzip-compressed document; decompress it before parsing.
264 // The original length (modulo 2^32) is found in the last 4 bytes
265 // of the data, stored in little-endian format. We read it as
266 // individual bytes to avoid possible alignment issues.
267 // (Note that if the original length was >2^32, then origLen here
268 // will be incorrect; but then the inflate() call will not return
269 // Z_STREAM_END and we'll bail out safely.)
270 size_t origLen = (size_t(aBuffer[aBufLen - 1]) << 24) +
271 (size_t(aBuffer[aBufLen - 2]) << 16) +
272 (size_t(aBuffer[aBufLen - 3]) << 8) +
273 size_t(aBuffer[aBufLen - 4]);
274 AutoTArray<uint8_t, 4096> outBuf;
275 if (outBuf.SetLength(origLen, mozilla::fallible)) {
276 z_stream s = {0};
277 s.next_in = const_cast<Byte*>(aBuffer);
278 s.avail_in = aBufLen;
279 s.next_out = outBuf.Elements();
280 s.avail_out = outBuf.Length();
281 // The magic number 16 here is the zlib flag to expect gzip format,
282 // see http://www.zlib.net/manual.html#Advanced
283 if (Z_OK == inflateInit2(&s, 16 + MAX_WBITS)) {
284 int result = inflate(&s, Z_FINISH);
285 if (Z_STREAM_END == result) {
286 MOZ_ASSERT(size_t(s.next_out - outBuf.Elements()) == origLen);
287 ParseDocument(outBuf.Elements(), outBuf.Length());
288 } else {
289 NS_WARNING("Failed to decompress SVG glyphs document");
291 inflateEnd(&s);
293 } else {
294 NS_WARNING("Failed to allocate memory for SVG glyphs document");
296 } else {
297 ParseDocument(aBuffer, aBufLen);
300 if (!mDocument) {
301 NS_WARNING("Could not parse SVG glyphs document");
302 return;
305 Element* root = mDocument->GetRootElement();
306 if (!root) {
307 NS_WARNING("Could not parse SVG glyphs document");
308 return;
311 nsresult rv = SetupPresentation();
312 if (NS_FAILED(rv)) {
313 NS_WARNING("Couldn't setup presentation for SVG glyphs document");
314 return;
317 FindGlyphElements(root);
320 gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() {
321 if (mDocument) {
322 mDocument->OnPageHide(false, nullptr);
324 if (mPresShell) {
325 mPresShell->RemovePostRefreshObserver(this);
327 if (mViewer) {
328 mViewer->Close(nullptr);
329 mViewer->Destroy();
333 static nsresult CreateBufferedStream(const uint8_t* aBuffer, uint32_t aBufLen,
334 nsCOMPtr<nsIInputStream>& aResult) {
335 nsCOMPtr<nsIInputStream> stream;
336 nsresult rv = NS_NewByteInputStream(
337 getter_AddRefs(stream),
338 Span(reinterpret_cast<const char*>(aBuffer), aBufLen),
339 NS_ASSIGNMENT_DEPEND);
340 NS_ENSURE_SUCCESS(rv, rv);
342 nsCOMPtr<nsIInputStream> aBufferedStream;
343 if (!NS_InputStreamIsBuffered(stream)) {
344 rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream),
345 stream.forget(), 4096);
346 NS_ENSURE_SUCCESS(rv, rv);
347 stream = aBufferedStream;
350 aResult = stream;
352 return NS_OK;
355 nsresult gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer,
356 uint32_t aBufLen) {
357 // Mostly pulled from nsDOMParser::ParseFromStream
359 nsCOMPtr<nsIInputStream> stream;
360 nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream);
361 NS_ENSURE_SUCCESS(rv, rv);
363 nsCOMPtr<nsIURI> uri;
364 mozilla::dom::FontTableURIProtocolHandler::GenerateURIString(
365 mSVGGlyphsDocumentURI);
367 rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI);
368 NS_ENSURE_SUCCESS(rv, rv);
370 nsCOMPtr<nsIPrincipal> principal =
371 NullPrincipal::CreateWithoutOriginAttributes();
373 RefPtr<Document> document;
374 rv = NS_NewDOMDocument(getter_AddRefs(document),
375 u""_ns, // aNamespaceURI
376 u""_ns, // aQualifiedName
377 nullptr, // aDoctype
378 uri, uri, principal,
379 false, // aLoadedAsData
380 nullptr, // aEventObject
381 DocumentFlavorSVG);
382 NS_ENSURE_SUCCESS(rv, rv);
384 nsCOMPtr<nsIChannel> channel;
385 rv = NS_NewInputStreamChannel(
386 getter_AddRefs(channel), uri,
387 nullptr, // aStream
388 principal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
389 nsIContentPolicy::TYPE_OTHER, SVG_CONTENT_TYPE, UTF8_CHARSET);
390 NS_ENSURE_SUCCESS(rv, rv);
392 // Set this early because various decisions during page-load depend on it.
393 document->SetIsBeingUsedAsImage();
394 document->SetIsSVGGlyphsDocument();
395 document->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED);
397 nsCOMPtr<nsIStreamListener> listener;
398 rv = document->StartDocumentLoad("external-resource", channel,
399 nullptr, // aLoadGroup
400 nullptr, // aContainer
401 getter_AddRefs(listener), true /* aReset */);
402 if (NS_FAILED(rv) || !listener) {
403 return NS_ERROR_FAILURE;
406 rv = listener->OnStartRequest(channel);
407 if (NS_FAILED(rv)) {
408 channel->Cancel(rv);
411 nsresult status;
412 channel->GetStatus(&status);
413 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
414 rv = listener->OnDataAvailable(channel, stream, 0, aBufLen);
415 if (NS_FAILED(rv)) {
416 channel->Cancel(rv);
418 channel->GetStatus(&status);
421 rv = listener->OnStopRequest(channel, status);
422 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
424 document.swap(mDocument);
426 return NS_OK;
429 void gfxSVGGlyphsDocument::InsertGlyphId(Element* aGlyphElement) {
430 nsAutoString glyphIdStr;
431 static const uint32_t glyphPrefixLength = 5;
432 // The maximum glyph ID is 65535 so the maximum length of the numeric part
433 // is 5.
434 if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) ||
435 !StringBeginsWith(glyphIdStr, u"glyph"_ns) ||
436 glyphIdStr.Length() > glyphPrefixLength + 5) {
437 return;
440 uint32_t id = 0;
441 for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) {
442 char16_t ch = glyphIdStr.CharAt(i);
443 if (ch < '0' || ch > '9') {
444 return;
446 if (ch == '0' && i == glyphPrefixLength) {
447 return;
449 id = id * 10 + (ch - '0');
452 mGlyphIdMap.InsertOrUpdate(id, aGlyphElement);
455 size_t gfxSVGGlyphsDocument::SizeOfIncludingThis(
456 mozilla::MallocSizeOf aMallocSizeOf) const {
457 return aMallocSizeOf(this) +
458 mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf) +
459 mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);