123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- /* 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<const Header*>(svgData);
- mDocIndex = nullptr;
- if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 &&
- uint64_t(mHeader->mDocIndexOffset) + 2 <= length) {
- const DocIndex* docIndex = reinterpret_cast<const DocIndex*>
- (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<nsICategoryManager> 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<nsIDocumentLoaderFactory> docLoaderFactory = do_GetService(contractId);
- NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
- nsCOMPtr<nsIContentViewer> 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<nsIPresShell> 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<uint8_t, 4096> outBuf;
- if (outBuf.SetLength(origLen, mozilla::fallible)) {
- z_stream s = {0};
- s.next_in = const_cast<Byte*>(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<nsIInputStream> &aResult)
- {
- nsCOMPtr<nsIInputStream> stream;
- nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
- reinterpret_cast<const char *>(aBuffer),
- aBufLen, NS_ASSIGNMENT_DEPEND);
- NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIInputStream> 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<nsIInputStream> stream;
- nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream);
- NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIURI> uri;
- nsHostObjectProtocolHandler::GenerateURIString(NS_LITERAL_CSTRING(FONTTABLEURI_SCHEME),
- nullptr,
- mSVGGlyphsDocumentURI);
-
- rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI);
- NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create();
- nsCOMPtr<nsIDOMDocument> 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<nsIDocument> document(do_QueryInterface(domDoc));
- if (!document) {
- return NS_ERROR_FAILURE;
- }
- nsCOMPtr<nsIChannel> 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<nsIStreamListener> 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);
- }
|