diff options
Diffstat (limited to 'gfx/thebes/gfxSVGGlyphs.cpp')
-rw-r--r-- | gfx/thebes/gfxSVGGlyphs.cpp | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/gfx/thebes/gfxSVGGlyphs.cpp b/gfx/thebes/gfxSVGGlyphs.cpp new file mode 100644 index 000000000..a7615eca8 --- /dev/null +++ b/gfx/thebes/gfxSVGGlyphs.cpp @@ -0,0 +1,463 @@ +/* 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 "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; + +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) +{ + 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); + +} |