/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gfxSVGGlyphs.h" #include "mozilla/SVGContextPaint.h" #include "nsError.h" #include "nsIDOMDocument.h" #include "nsString.h" #include "nsIDocument.h" #include "nsICategoryManager.h" #include "nsIDocumentLoaderFactory.h" #include "nsIContentViewer.h" #include "nsIStreamListener.h" #include "nsServiceManagerUtils.h" #include "nsIPresShell.h" #include "nsNetUtil.h" #include "nsNullPrincipal.h" #include "nsIInputStream.h" #include "nsStringStream.h" #include "nsStreamUtils.h" #include "nsIPrincipal.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/Element.h" #include "mozilla/LoadInfo.h" #include "nsSVGUtils.h" #include "nsHostObjectProtocolHandler.h" #include "nsContentUtils.h" #include "gfxFont.h" #include "nsSMILAnimationController.h" #include "gfxContext.h" #include "harfbuzz/hb.h" #include "zlib.h" #include "mozilla/dom/ImageTracker.h" #define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml") #define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8") using namespace mozilla; using namespace mozilla::gfx; typedef mozilla::dom::Element Element; /* static */ const Color SimpleTextContextPaint::sZero = Color(); gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t *aSVGTable, gfxFontEntry *aFontEntry) : mSVGData(aSVGTable) , mFontEntry(aFontEntry) { unsigned int length; const char* svgData = hb_blob_get_data(mSVGData, &length); mHeader = reinterpret_cast(svgData); mDocIndex = nullptr; if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 && uint64_t(mHeader->mDocIndexOffset) + 2 <= length) { const DocIndex* docIndex = reinterpret_cast (svgData + mHeader->mDocIndexOffset); // Limit the number of documents to avoid overflow if (uint64_t(mHeader->mDocIndexOffset) + 2 + uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <= length) { mDocIndex = docIndex; } } } gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData); } void gfxSVGGlyphs::DidRefresh() { mFontEntry->NotifyGlyphsChanged(); } /* * Comparison operator for finding a range containing a given glyph ID. Simply * checks whether |key| is less (greater) than every element of |range|, in * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in * |range|, in which case return equality. * The total ordering here is guaranteed by * (1) the index ranges being disjoint; and * (2) the (sole) key always being a singleton, so intersection => containment * (note that this is wrong if we have more than one intersection or two * sets intersecting of size > 1 -- so... don't do that) */ /* static */ int gfxSVGGlyphs::CompareIndexEntries(const void *aKey, const void *aEntry) { const uint32_t key = *(uint32_t*)aKey; const IndexEntry *entry = (const IndexEntry*)aEntry; if (key < uint16_t(entry->mStartGlyph)) { return -1; } if (key > uint16_t(entry->mEndGlyph)) { return 1; } return 0; } gfxSVGGlyphsDocument * gfxSVGGlyphs::FindOrCreateGlyphsDocument(uint32_t aGlyphId) { if (!mDocIndex) { // Invalid table return nullptr; } IndexEntry *entry = (IndexEntry*)bsearch(&aGlyphId, mDocIndex->mEntries, uint16_t(mDocIndex->mNumEntries), sizeof(IndexEntry), CompareIndexEntries); if (!entry) { return nullptr; } gfxSVGGlyphsDocument *result = mGlyphDocs.Get(entry->mDocOffset); if (!result) { unsigned int length; const uint8_t *data = (const uint8_t*)hb_blob_get_data(mSVGData, &length); if (entry->mDocOffset > 0 && uint64_t(mHeader->mDocIndexOffset) + entry->mDocOffset + entry->mDocLength <= length) { result = new gfxSVGGlyphsDocument(data + mHeader->mDocIndexOffset + entry->mDocOffset, entry->mDocLength, this); mGlyphDocs.Put(entry->mDocOffset, result); } } return result; } nsresult gfxSVGGlyphsDocument::SetupPresentation() { nsCOMPtr catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); nsXPIDLCString contractId; nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "image/svg+xml", getter_Copies(contractId)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr docLoaderFactory = do_GetService(contractId); NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory"); nsCOMPtr viewer; rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr, getter_AddRefs(viewer)); NS_ENSURE_SUCCESS(rv, rv); rv = viewer->Init(nullptr, gfx::IntRect(0, 0, 1000, 1000)); if (NS_SUCCEEDED(rv)) { rv = viewer->Open(nullptr, nullptr); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr presShell; rv = viewer->GetPresShell(getter_AddRefs(presShell)); NS_ENSURE_SUCCESS(rv, rv); nsPresContext* presContext = presShell->GetPresContext(); presContext->SetIsGlyph(true); if (!presShell->DidInitialize()) { nsRect rect = presContext->GetVisibleArea(); rv = presShell->Initialize(rect.width, rect.height); NS_ENSURE_SUCCESS(rv, rv); } mDocument->FlushPendingNotifications(Flush_Layout); nsSMILAnimationController* controller = mDocument->GetAnimationController(); if (controller) { controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE); } mDocument->ImageTracker()->SetAnimatingState(true); mViewer = viewer; mPresShell = presShell; mPresShell->AddPostRefreshObserver(this); return NS_OK; } void gfxSVGGlyphsDocument::DidRefresh() { mOwner->DidRefresh(); } /** * Walk the DOM tree to find all glyph elements and insert them into the lookup * table * @param aElem The element to search from */ void gfxSVGGlyphsDocument::FindGlyphElements(Element *aElem) { for (nsIContent *child = aElem->GetLastChild(); child; child = child->GetPreviousSibling()) { if (!child->IsElement()) { continue; } FindGlyphElements(child->AsElement()); } InsertGlyphId(aElem); } /** * If there exists an SVG glyph with the specified glyph id, render it and return true * If no such glyph exists, or in the case of an error return false * @param aContext The thebes aContext to draw to * @param aGlyphId The glyph id * @return true iff rendering succeeded */ bool gfxSVGGlyphs::RenderGlyph(gfxContext *aContext, uint32_t aGlyphId, SVGContextPaint* aContextPaint) { gfxContextAutoSaveRestore aContextRestorer(aContext); Element *glyph = mGlyphIdMap.Get(aGlyphId); NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); AutoSetRestoreSVGContextPaint autoSetRestore(aContextPaint, glyph->OwnerDoc()); return nsSVGUtils::PaintSVGGlyph(glyph, aContext); } bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace, gfxRect *aResult) { Element *glyph = mGlyphIdMap.Get(aGlyphId); NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); return nsSVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult); } Element * gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) { Element *elem; if (!mGlyphIdMap.Get(aGlyphId, &elem)) { elem = nullptr; if (gfxSVGGlyphsDocument *set = FindOrCreateGlyphsDocument(aGlyphId)) { elem = set->GetGlyphElement(aGlyphId); } mGlyphIdMap.Put(aGlyphId, elem); } return elem; } bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) { return !!GetGlyphElement(aGlyphId); } size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { // We don't include the size of mSVGData here, because (depending on the // font backend implementation) it will either wrap a block of data owned // by the system (and potentially shared), or a table that's in our font // table cache and therefore already counted. size_t result = aMallocSizeOf(this) + mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf); for (auto iter = mGlyphDocs.ConstIter(); !iter.Done(); iter.Next()) { result += iter.Data()->SizeOfIncludingThis(aMallocSizeOf); } return result; } Element * gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) { return mGlyphIdMap.Get(aGlyphId); } gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t *aBuffer, uint32_t aBufLen, gfxSVGGlyphs *aSVGGlyphs) : mOwner(aSVGGlyphs) { if (aBufLen >= 14 && aBuffer[0] == 31 && aBuffer[1] == 139) { // It's a gzip-compressed document; decompress it before parsing. // The original length (modulo 2^32) is found in the last 4 bytes // of the data, stored in little-endian format. We read it as // individual bytes to avoid possible alignment issues. // (Note that if the original length was >2^32, then origLen here // will be incorrect; but then the inflate() call will not return // Z_STREAM_END and we'll bail out safely.) size_t origLen = (size_t(aBuffer[aBufLen - 1]) << 24) + (size_t(aBuffer[aBufLen - 2]) << 16) + (size_t(aBuffer[aBufLen - 3]) << 8) + size_t(aBuffer[aBufLen - 4]); AutoTArray outBuf; if (outBuf.SetLength(origLen, mozilla::fallible)) { z_stream s = {0}; s.next_in = const_cast(aBuffer); s.avail_in = aBufLen; s.next_out = outBuf.Elements(); s.avail_out = outBuf.Length(); // The magic number 16 here is the zlib flag to expect gzip format, // see http://www.zlib.net/manual.html#Advanced if (Z_OK == inflateInit2(&s, 16 + MAX_WBITS)) { int result = inflate(&s, Z_FINISH); if (Z_STREAM_END == result) { MOZ_ASSERT(size_t(s.next_out - outBuf.Elements()) == origLen); ParseDocument(outBuf.Elements(), outBuf.Length()); } else { NS_WARNING("Failed to decompress SVG glyphs document"); } inflateEnd(&s); } } else { NS_WARNING("Failed to allocate memory for SVG glyphs document"); } } else { ParseDocument(aBuffer, aBufLen); } if (!mDocument) { NS_WARNING("Could not parse SVG glyphs document"); return; } Element *root = mDocument->GetRootElement(); if (!root) { NS_WARNING("Could not parse SVG glyphs document"); return; } nsresult rv = SetupPresentation(); if (NS_FAILED(rv)) { NS_WARNING("Couldn't setup presentation for SVG glyphs document"); return; } FindGlyphElements(root); } gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() { if (mDocument) { mDocument->OnPageHide(false, nullptr); } if (mPresShell) { mPresShell->RemovePostRefreshObserver(this); } if (mViewer) { mViewer->Close(nullptr); mViewer->Destroy(); } } static nsresult CreateBufferedStream(const uint8_t *aBuffer, uint32_t aBufLen, nsCOMPtr &aResult) { nsCOMPtr stream; nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), reinterpret_cast(aBuffer), aBufLen, NS_ASSIGNMENT_DEPEND); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr aBufferedStream; if (!NS_InputStreamIsBuffered(stream)) { rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream), stream, 4096); NS_ENSURE_SUCCESS(rv, rv); stream = aBufferedStream; } aResult = stream; return NS_OK; } nsresult gfxSVGGlyphsDocument::ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen) { // Mostly pulled from nsDOMParser::ParseFromStream nsCOMPtr stream; nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr uri; nsHostObjectProtocolHandler::GenerateURIString(NS_LITERAL_CSTRING(FONTTABLEURI_SCHEME), nullptr, mSVGGlyphsDocumentURI); rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr principal = nsNullPrincipal::Create(); nsCOMPtr domDoc; rv = NS_NewDOMDocument(getter_AddRefs(domDoc), EmptyString(), // aNamespaceURI EmptyString(), // aQualifiedName nullptr, // aDoctype uri, uri, principal, false, // aLoadedAsData nullptr, // aEventObject DocumentFlavorSVG); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr document(do_QueryInterface(domDoc)); if (!document) { return NS_ERROR_FAILURE; } nsCOMPtr channel; rv = NS_NewInputStreamChannel(getter_AddRefs(channel), uri, nullptr, //aStream principal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, nsIContentPolicy::TYPE_OTHER, SVG_CONTENT_TYPE, UTF8_CHARSET); NS_ENSURE_SUCCESS(rv, rv); // Set this early because various decisions during page-load depend on it. document->SetIsBeingUsedAsImage(); document->SetReadyStateInternal(nsIDocument::READYSTATE_UNINITIALIZED); nsCOMPtr listener; rv = document->StartDocumentLoad("external-resource", channel, nullptr, // aLoadGroup nullptr, // aContainer getter_AddRefs(listener), true /* aReset */); if (NS_FAILED(rv) || !listener) { return NS_ERROR_FAILURE; } rv = listener->OnStartRequest(channel, nullptr /* aContext */); if (NS_FAILED(rv)) { channel->Cancel(rv); } nsresult status; channel->GetStatus(&status); if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { rv = listener->OnDataAvailable(channel, nullptr /* aContext */, stream, 0, aBufLen); if (NS_FAILED(rv)) { channel->Cancel(rv); } channel->GetStatus(&status); } rv = listener->OnStopRequest(channel, nullptr /* aContext */, status); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); document.swap(mDocument); return NS_OK; } void gfxSVGGlyphsDocument::InsertGlyphId(Element *aGlyphElement) { nsAutoString glyphIdStr; static const uint32_t glyphPrefixLength = 5; // The maximum glyph ID is 65535 so the maximum length of the numeric part // is 5. if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) || !StringBeginsWith(glyphIdStr, NS_LITERAL_STRING("glyph")) || glyphIdStr.Length() > glyphPrefixLength + 5) { return; } uint32_t id = 0; for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) { char16_t ch = glyphIdStr.CharAt(i); if (ch < '0' || ch > '9') { return; } if (ch == '0' && i == glyphPrefixLength) { return; } id = id * 10 + (ch - '0'); } mGlyphIdMap.Put(id, aGlyphElement); } size_t gfxSVGGlyphsDocument::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf) + mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf); }