michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxSVGGlyphs.h" michael@0: michael@0: #include "nsError.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsString.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsICategoryManager.h" michael@0: #include "nsIDocumentLoaderFactory.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsHostObjectProtocolHandler.h" michael@0: #include "nsContentUtils.h" michael@0: #include "gfxFont.h" michael@0: #include "nsSMILAnimationController.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxColor.h" michael@0: #include "harfbuzz/hb.h" michael@0: michael@0: #define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml") michael@0: #define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8") michael@0: michael@0: using namespace mozilla; michael@0: michael@0: typedef mozilla::dom::Element Element; michael@0: michael@0: mozilla::gfx::UserDataKey gfxTextContextPaint::sUserDataKey; michael@0: michael@0: const gfxRGBA SimpleTextContextPaint::sZero = gfxRGBA(0.0f, 0.0f, 0.0f, 0.0f); michael@0: michael@0: gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t *aSVGTable, gfxFontEntry *aFontEntry) michael@0: : mSVGData(aSVGTable) michael@0: , mFontEntry(aFontEntry) michael@0: { michael@0: unsigned int length; michael@0: const char* svgData = hb_blob_get_data(mSVGData, &length); michael@0: mHeader = reinterpret_cast(svgData); michael@0: mDocIndex = nullptr; michael@0: michael@0: if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 && michael@0: uint64_t(mHeader->mDocIndexOffset) + 2 <= length) { michael@0: const DocIndex* docIndex = reinterpret_cast michael@0: (svgData + mHeader->mDocIndexOffset); michael@0: // Limit the number of documents to avoid overflow michael@0: if (uint64_t(mHeader->mDocIndexOffset) + 2 + michael@0: uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <= length) { michael@0: mDocIndex = docIndex; michael@0: } michael@0: } michael@0: } michael@0: michael@0: gfxSVGGlyphs::~gfxSVGGlyphs() michael@0: { michael@0: hb_blob_destroy(mSVGData); michael@0: } michael@0: michael@0: void michael@0: gfxSVGGlyphs::DidRefresh() michael@0: { michael@0: mFontEntry->NotifyGlyphsChanged(); michael@0: } michael@0: michael@0: /* michael@0: * Comparison operator for finding a range containing a given glyph ID. Simply michael@0: * checks whether |key| is less (greater) than every element of |range|, in michael@0: * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in michael@0: * |range|, in which case return equality. michael@0: * The total ordering here is guaranteed by michael@0: * (1) the index ranges being disjoint; and michael@0: * (2) the (sole) key always being a singleton, so intersection => containment michael@0: * (note that this is wrong if we have more than one intersection or two michael@0: * sets intersecting of size > 1 -- so... don't do that) michael@0: */ michael@0: /* static */ int michael@0: gfxSVGGlyphs::CompareIndexEntries(const void *aKey, const void *aEntry) michael@0: { michael@0: const uint32_t key = *(uint32_t*)aKey; michael@0: const IndexEntry *entry = (const IndexEntry*)aEntry; michael@0: michael@0: if (key < uint16_t(entry->mStartGlyph)) { michael@0: return -1; michael@0: } michael@0: if (key > uint16_t(entry->mEndGlyph)) { michael@0: return 1; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: gfxSVGGlyphsDocument * michael@0: gfxSVGGlyphs::FindOrCreateGlyphsDocument(uint32_t aGlyphId) michael@0: { michael@0: if (!mDocIndex) { michael@0: // Invalid table michael@0: return nullptr; michael@0: } michael@0: michael@0: IndexEntry *entry = (IndexEntry*)bsearch(&aGlyphId, mDocIndex->mEntries, michael@0: uint16_t(mDocIndex->mNumEntries), michael@0: sizeof(IndexEntry), michael@0: CompareIndexEntries); michael@0: if (!entry) { michael@0: return nullptr; michael@0: } michael@0: michael@0: gfxSVGGlyphsDocument *result = mGlyphDocs.Get(entry->mDocOffset); michael@0: michael@0: if (!result) { michael@0: unsigned int length; michael@0: const uint8_t *data = (const uint8_t*)hb_blob_get_data(mSVGData, &length); michael@0: if (entry->mDocOffset > 0 && michael@0: uint64_t(mHeader->mDocIndexOffset) + entry->mDocOffset + entry->mDocLength <= length) { michael@0: result = new gfxSVGGlyphsDocument(data + mHeader->mDocIndexOffset + entry->mDocOffset, michael@0: entry->mDocLength, this); michael@0: mGlyphDocs.Put(entry->mDocOffset, result); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: gfxSVGGlyphsDocument::SetupPresentation() michael@0: { michael@0: nsCOMPtr catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); michael@0: nsXPIDLCString contractId; michael@0: nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "image/svg+xml", getter_Copies(contractId)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr docLoaderFactory = do_GetService(contractId); michael@0: NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory"); michael@0: michael@0: nsCOMPtr viewer; michael@0: rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr, getter_AddRefs(viewer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = viewer->Init(nullptr, nsIntRect(0, 0, 1000, 1000)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = viewer->Open(nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr presShell; michael@0: rv = viewer->GetPresShell(getter_AddRefs(presShell)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsPresContext* presContext = presShell->GetPresContext(); michael@0: presContext->SetIsGlyph(true); michael@0: michael@0: if (!presShell->DidInitialize()) { michael@0: nsRect rect = presContext->GetVisibleArea(); michael@0: rv = presShell->Initialize(rect.width, rect.height); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mDocument->FlushPendingNotifications(Flush_Layout); michael@0: michael@0: nsSMILAnimationController* controller = mDocument->GetAnimationController(); michael@0: if (controller) { michael@0: controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE); michael@0: } michael@0: mDocument->SetImagesNeedAnimating(true); michael@0: michael@0: mViewer = viewer; michael@0: mPresShell = presShell; michael@0: mPresShell->AddPostRefreshObserver(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: gfxSVGGlyphsDocument::DidRefresh() michael@0: { michael@0: mOwner->DidRefresh(); michael@0: } michael@0: michael@0: /** michael@0: * Walk the DOM tree to find all glyph elements and insert them into the lookup michael@0: * table michael@0: * @param aElem The element to search from michael@0: */ michael@0: void michael@0: gfxSVGGlyphsDocument::FindGlyphElements(Element *aElem) michael@0: { michael@0: for (nsIContent *child = aElem->GetLastChild(); child; michael@0: child = child->GetPreviousSibling()) { michael@0: if (!child->IsElement()) { michael@0: continue; michael@0: } michael@0: FindGlyphElements(child->AsElement()); michael@0: } michael@0: michael@0: InsertGlyphId(aElem); michael@0: } michael@0: michael@0: /** michael@0: * If there exists an SVG glyph with the specified glyph id, render it and return true michael@0: * If no such glyph exists, or in the case of an error return false michael@0: * @param aContext The thebes aContext to draw to michael@0: * @param aGlyphId The glyph id michael@0: * @param aDrawMode Whether to fill or stroke or both (see |DrawMode|) michael@0: * @return true iff rendering succeeded michael@0: */ michael@0: bool michael@0: gfxSVGGlyphs::RenderGlyph(gfxContext *aContext, uint32_t aGlyphId, michael@0: DrawMode aDrawMode, gfxTextContextPaint *aContextPaint) michael@0: { michael@0: if (aDrawMode == DrawMode::GLYPH_PATH) { michael@0: return false; michael@0: } michael@0: michael@0: gfxContextAutoSaveRestore aContextRestorer(aContext); michael@0: michael@0: Element *glyph = mGlyphIdMap.Get(aGlyphId); michael@0: NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); michael@0: michael@0: return nsSVGUtils::PaintSVGGlyph(glyph, aContext, aDrawMode, aContextPaint); michael@0: } michael@0: michael@0: bool michael@0: gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace, michael@0: gfxRect *aResult) michael@0: { michael@0: Element *glyph = mGlyphIdMap.Get(aGlyphId); michael@0: NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); michael@0: michael@0: return nsSVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult); michael@0: } michael@0: michael@0: Element * michael@0: gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) michael@0: { michael@0: Element *elem; michael@0: michael@0: if (!mGlyphIdMap.Get(aGlyphId, &elem)) { michael@0: elem = nullptr; michael@0: if (gfxSVGGlyphsDocument *set = FindOrCreateGlyphsDocument(aGlyphId)) { michael@0: elem = set->GetGlyphElement(aGlyphId); michael@0: } michael@0: mGlyphIdMap.Put(aGlyphId, elem); michael@0: } michael@0: michael@0: return elem; michael@0: } michael@0: michael@0: bool michael@0: gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) michael@0: { michael@0: return !!GetGlyphElement(aGlyphId); michael@0: } michael@0: michael@0: Element * michael@0: gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) michael@0: { michael@0: return mGlyphIdMap.Get(aGlyphId); michael@0: } michael@0: michael@0: gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t *aBuffer, michael@0: uint32_t aBufLen, michael@0: gfxSVGGlyphs *aSVGGlyphs) michael@0: : mOwner(aSVGGlyphs) michael@0: { michael@0: ParseDocument(aBuffer, aBufLen); michael@0: if (!mDocument) { michael@0: NS_WARNING("Could not parse SVG glyphs document"); michael@0: return; michael@0: } michael@0: michael@0: Element *root = mDocument->GetRootElement(); michael@0: if (!root) { michael@0: NS_WARNING("Could not parse SVG glyphs document"); michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = SetupPresentation(); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Couldn't setup presentation for SVG glyphs document"); michael@0: return; michael@0: } michael@0: michael@0: FindGlyphElements(root); michael@0: } michael@0: michael@0: gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() michael@0: { michael@0: if (mDocument) { michael@0: nsSMILAnimationController* controller = mDocument->GetAnimationController(); michael@0: if (controller) { michael@0: controller->Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE); michael@0: } michael@0: } michael@0: if (mPresShell) { michael@0: mPresShell->RemovePostRefreshObserver(this); michael@0: } michael@0: if (mViewer) { michael@0: mViewer->Destroy(); michael@0: } michael@0: } michael@0: michael@0: static nsresult michael@0: CreateBufferedStream(const uint8_t *aBuffer, uint32_t aBufLen, michael@0: nsCOMPtr &aResult) michael@0: { michael@0: nsCOMPtr stream; michael@0: nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), michael@0: reinterpret_cast(aBuffer), michael@0: aBufLen, NS_ASSIGNMENT_DEPEND); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr aBufferedStream; michael@0: if (!NS_InputStreamIsBuffered(stream)) { michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream), stream, 4096); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: stream = aBufferedStream; michael@0: } michael@0: michael@0: aResult = stream; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: gfxSVGGlyphsDocument::ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen) michael@0: { michael@0: // Mostly pulled from nsDOMParser::ParseFromStream michael@0: michael@0: nsCOMPtr stream; michael@0: nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr uri; michael@0: nsHostObjectProtocolHandler::GenerateURIString(NS_LITERAL_CSTRING(FONTTABLEURI_SCHEME), michael@0: mSVGGlyphsDocumentURI); michael@0: michael@0: rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr principal; michael@0: nsContentUtils::GetSecurityManager()-> michael@0: GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal)); michael@0: michael@0: nsCOMPtr domDoc; michael@0: rv = NS_NewDOMDocument(getter_AddRefs(domDoc), michael@0: EmptyString(), // aNamespaceURI michael@0: EmptyString(), // aQualifiedName michael@0: nullptr, // aDoctype michael@0: uri, uri, principal, michael@0: false, // aLoadedAsData michael@0: nullptr, // aEventObject michael@0: DocumentFlavorSVG); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr document(do_QueryInterface(domDoc)); michael@0: if (!document) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr channel; michael@0: rv = NS_NewInputStreamChannel(getter_AddRefs(channel), uri, nullptr /* stream */, michael@0: SVG_CONTENT_TYPE, UTF8_CHARSET); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: channel->SetOwner(principal); michael@0: michael@0: // Set this early because various decisions during page-load depend on it. michael@0: document->SetIsBeingUsedAsImage(); michael@0: document->SetReadyStateInternal(nsIDocument::READYSTATE_UNINITIALIZED); michael@0: michael@0: nsCOMPtr listener; michael@0: rv = document->StartDocumentLoad("external-resource", channel, michael@0: nullptr, // aLoadGroup michael@0: nullptr, // aContainer michael@0: getter_AddRefs(listener), michael@0: true /* aReset */); michael@0: if (NS_FAILED(rv) || !listener) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: rv = listener->OnStartRequest(channel, nullptr /* aContext */); michael@0: if (NS_FAILED(rv)) { michael@0: channel->Cancel(rv); michael@0: } michael@0: michael@0: nsresult status; michael@0: channel->GetStatus(&status); michael@0: if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { michael@0: rv = listener->OnDataAvailable(channel, nullptr /* aContext */, stream, 0, aBufLen); michael@0: if (NS_FAILED(rv)) { michael@0: channel->Cancel(rv); michael@0: } michael@0: channel->GetStatus(&status); michael@0: } michael@0: michael@0: rv = listener->OnStopRequest(channel, nullptr /* aContext */, status); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: document.swap(mDocument); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: gfxSVGGlyphsDocument::InsertGlyphId(Element *aGlyphElement) michael@0: { michael@0: nsAutoString glyphIdStr; michael@0: static const uint32_t glyphPrefixLength = 5; michael@0: // The maximum glyph ID is 65535 so the maximum length of the numeric part michael@0: // is 5. michael@0: if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) || michael@0: !StringBeginsWith(glyphIdStr, NS_LITERAL_STRING("glyph")) || michael@0: glyphIdStr.Length() > glyphPrefixLength + 5) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t id = 0; michael@0: for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) { michael@0: char16_t ch = glyphIdStr.CharAt(i); michael@0: if (ch < '0' || ch > '9') { michael@0: return; michael@0: } michael@0: if (ch == '0' && i == glyphPrefixLength) { michael@0: return; michael@0: } michael@0: id = id * 10 + (ch - '0'); michael@0: } michael@0: michael@0: mGlyphIdMap.Put(id, aGlyphElement); michael@0: } michael@0: michael@0: void michael@0: gfxTextContextPaint::InitStrokeGeometry(gfxContext *aContext, michael@0: float devUnitsPerSVGUnit) michael@0: { michael@0: mStrokeWidth = aContext->CurrentLineWidth() / devUnitsPerSVGUnit; michael@0: aContext->CurrentDash(mDashes, &mDashOffset); michael@0: for (uint32_t i = 0; i < mDashes.Length(); i++) { michael@0: mDashes[i] /= devUnitsPerSVGUnit; michael@0: } michael@0: mDashOffset /= devUnitsPerSVGUnit; michael@0: }