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