1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/thebes/gfxSVGGlyphs.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,451 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "gfxSVGGlyphs.h" 1.9 + 1.10 +#include "nsError.h" 1.11 +#include "nsIDOMDocument.h" 1.12 +#include "nsString.h" 1.13 +#include "nsIDocument.h" 1.14 +#include "nsICategoryManager.h" 1.15 +#include "nsIDocumentLoaderFactory.h" 1.16 +#include "nsIContentViewer.h" 1.17 +#include "nsIStreamListener.h" 1.18 +#include "nsServiceManagerUtils.h" 1.19 +#include "nsIPresShell.h" 1.20 +#include "nsNetUtil.h" 1.21 +#include "nsIInputStream.h" 1.22 +#include "nsStringStream.h" 1.23 +#include "nsStreamUtils.h" 1.24 +#include "nsIPrincipal.h" 1.25 +#include "mozilla/dom/Element.h" 1.26 +#include "nsSVGUtils.h" 1.27 +#include "nsIScriptSecurityManager.h" 1.28 +#include "nsHostObjectProtocolHandler.h" 1.29 +#include "nsContentUtils.h" 1.30 +#include "gfxFont.h" 1.31 +#include "nsSMILAnimationController.h" 1.32 +#include "gfxContext.h" 1.33 +#include "gfxColor.h" 1.34 +#include "harfbuzz/hb.h" 1.35 + 1.36 +#define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml") 1.37 +#define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8") 1.38 + 1.39 +using namespace mozilla; 1.40 + 1.41 +typedef mozilla::dom::Element Element; 1.42 + 1.43 +mozilla::gfx::UserDataKey gfxTextContextPaint::sUserDataKey; 1.44 + 1.45 +const gfxRGBA SimpleTextContextPaint::sZero = gfxRGBA(0.0f, 0.0f, 0.0f, 0.0f); 1.46 + 1.47 +gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t *aSVGTable, gfxFontEntry *aFontEntry) 1.48 + : mSVGData(aSVGTable) 1.49 + , mFontEntry(aFontEntry) 1.50 +{ 1.51 + unsigned int length; 1.52 + const char* svgData = hb_blob_get_data(mSVGData, &length); 1.53 + mHeader = reinterpret_cast<const Header*>(svgData); 1.54 + mDocIndex = nullptr; 1.55 + 1.56 + if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 && 1.57 + uint64_t(mHeader->mDocIndexOffset) + 2 <= length) { 1.58 + const DocIndex* docIndex = reinterpret_cast<const DocIndex*> 1.59 + (svgData + mHeader->mDocIndexOffset); 1.60 + // Limit the number of documents to avoid overflow 1.61 + if (uint64_t(mHeader->mDocIndexOffset) + 2 + 1.62 + uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <= length) { 1.63 + mDocIndex = docIndex; 1.64 + } 1.65 + } 1.66 +} 1.67 + 1.68 +gfxSVGGlyphs::~gfxSVGGlyphs() 1.69 +{ 1.70 + hb_blob_destroy(mSVGData); 1.71 +} 1.72 + 1.73 +void 1.74 +gfxSVGGlyphs::DidRefresh() 1.75 +{ 1.76 + mFontEntry->NotifyGlyphsChanged(); 1.77 +} 1.78 + 1.79 +/* 1.80 + * Comparison operator for finding a range containing a given glyph ID. Simply 1.81 + * checks whether |key| is less (greater) than every element of |range|, in 1.82 + * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in 1.83 + * |range|, in which case return equality. 1.84 + * The total ordering here is guaranteed by 1.85 + * (1) the index ranges being disjoint; and 1.86 + * (2) the (sole) key always being a singleton, so intersection => containment 1.87 + * (note that this is wrong if we have more than one intersection or two 1.88 + * sets intersecting of size > 1 -- so... don't do that) 1.89 + */ 1.90 +/* static */ int 1.91 +gfxSVGGlyphs::CompareIndexEntries(const void *aKey, const void *aEntry) 1.92 +{ 1.93 + const uint32_t key = *(uint32_t*)aKey; 1.94 + const IndexEntry *entry = (const IndexEntry*)aEntry; 1.95 + 1.96 + if (key < uint16_t(entry->mStartGlyph)) { 1.97 + return -1; 1.98 + } 1.99 + if (key > uint16_t(entry->mEndGlyph)) { 1.100 + return 1; 1.101 + } 1.102 + return 0; 1.103 +} 1.104 + 1.105 +gfxSVGGlyphsDocument * 1.106 +gfxSVGGlyphs::FindOrCreateGlyphsDocument(uint32_t aGlyphId) 1.107 +{ 1.108 + if (!mDocIndex) { 1.109 + // Invalid table 1.110 + return nullptr; 1.111 + } 1.112 + 1.113 + IndexEntry *entry = (IndexEntry*)bsearch(&aGlyphId, mDocIndex->mEntries, 1.114 + uint16_t(mDocIndex->mNumEntries), 1.115 + sizeof(IndexEntry), 1.116 + CompareIndexEntries); 1.117 + if (!entry) { 1.118 + return nullptr; 1.119 + } 1.120 + 1.121 + gfxSVGGlyphsDocument *result = mGlyphDocs.Get(entry->mDocOffset); 1.122 + 1.123 + if (!result) { 1.124 + unsigned int length; 1.125 + const uint8_t *data = (const uint8_t*)hb_blob_get_data(mSVGData, &length); 1.126 + if (entry->mDocOffset > 0 && 1.127 + uint64_t(mHeader->mDocIndexOffset) + entry->mDocOffset + entry->mDocLength <= length) { 1.128 + result = new gfxSVGGlyphsDocument(data + mHeader->mDocIndexOffset + entry->mDocOffset, 1.129 + entry->mDocLength, this); 1.130 + mGlyphDocs.Put(entry->mDocOffset, result); 1.131 + } 1.132 + } 1.133 + 1.134 + return result; 1.135 +} 1.136 + 1.137 +nsresult 1.138 +gfxSVGGlyphsDocument::SetupPresentation() 1.139 +{ 1.140 + nsCOMPtr<nsICategoryManager> catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); 1.141 + nsXPIDLCString contractId; 1.142 + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "image/svg+xml", getter_Copies(contractId)); 1.143 + NS_ENSURE_SUCCESS(rv, rv); 1.144 + 1.145 + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = do_GetService(contractId); 1.146 + NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory"); 1.147 + 1.148 + nsCOMPtr<nsIContentViewer> viewer; 1.149 + rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr, getter_AddRefs(viewer)); 1.150 + NS_ENSURE_SUCCESS(rv, rv); 1.151 + 1.152 + rv = viewer->Init(nullptr, nsIntRect(0, 0, 1000, 1000)); 1.153 + if (NS_SUCCEEDED(rv)) { 1.154 + rv = viewer->Open(nullptr, nullptr); 1.155 + NS_ENSURE_SUCCESS(rv, rv); 1.156 + } 1.157 + 1.158 + nsCOMPtr<nsIPresShell> presShell; 1.159 + rv = viewer->GetPresShell(getter_AddRefs(presShell)); 1.160 + NS_ENSURE_SUCCESS(rv, rv); 1.161 + nsPresContext* presContext = presShell->GetPresContext(); 1.162 + presContext->SetIsGlyph(true); 1.163 + 1.164 + if (!presShell->DidInitialize()) { 1.165 + nsRect rect = presContext->GetVisibleArea(); 1.166 + rv = presShell->Initialize(rect.width, rect.height); 1.167 + NS_ENSURE_SUCCESS(rv, rv); 1.168 + } 1.169 + 1.170 + mDocument->FlushPendingNotifications(Flush_Layout); 1.171 + 1.172 + nsSMILAnimationController* controller = mDocument->GetAnimationController(); 1.173 + if (controller) { 1.174 + controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE); 1.175 + } 1.176 + mDocument->SetImagesNeedAnimating(true); 1.177 + 1.178 + mViewer = viewer; 1.179 + mPresShell = presShell; 1.180 + mPresShell->AddPostRefreshObserver(this); 1.181 + 1.182 + return NS_OK; 1.183 +} 1.184 + 1.185 +void 1.186 +gfxSVGGlyphsDocument::DidRefresh() 1.187 +{ 1.188 + mOwner->DidRefresh(); 1.189 +} 1.190 + 1.191 +/** 1.192 + * Walk the DOM tree to find all glyph elements and insert them into the lookup 1.193 + * table 1.194 + * @param aElem The element to search from 1.195 + */ 1.196 +void 1.197 +gfxSVGGlyphsDocument::FindGlyphElements(Element *aElem) 1.198 +{ 1.199 + for (nsIContent *child = aElem->GetLastChild(); child; 1.200 + child = child->GetPreviousSibling()) { 1.201 + if (!child->IsElement()) { 1.202 + continue; 1.203 + } 1.204 + FindGlyphElements(child->AsElement()); 1.205 + } 1.206 + 1.207 + InsertGlyphId(aElem); 1.208 +} 1.209 + 1.210 +/** 1.211 + * If there exists an SVG glyph with the specified glyph id, render it and return true 1.212 + * If no such glyph exists, or in the case of an error return false 1.213 + * @param aContext The thebes aContext to draw to 1.214 + * @param aGlyphId The glyph id 1.215 + * @param aDrawMode Whether to fill or stroke or both (see |DrawMode|) 1.216 + * @return true iff rendering succeeded 1.217 + */ 1.218 +bool 1.219 +gfxSVGGlyphs::RenderGlyph(gfxContext *aContext, uint32_t aGlyphId, 1.220 + DrawMode aDrawMode, gfxTextContextPaint *aContextPaint) 1.221 +{ 1.222 + if (aDrawMode == DrawMode::GLYPH_PATH) { 1.223 + return false; 1.224 + } 1.225 + 1.226 + gfxContextAutoSaveRestore aContextRestorer(aContext); 1.227 + 1.228 + Element *glyph = mGlyphIdMap.Get(aGlyphId); 1.229 + NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); 1.230 + 1.231 + return nsSVGUtils::PaintSVGGlyph(glyph, aContext, aDrawMode, aContextPaint); 1.232 +} 1.233 + 1.234 +bool 1.235 +gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace, 1.236 + gfxRect *aResult) 1.237 +{ 1.238 + Element *glyph = mGlyphIdMap.Get(aGlyphId); 1.239 + NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!"); 1.240 + 1.241 + return nsSVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult); 1.242 +} 1.243 + 1.244 +Element * 1.245 +gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId) 1.246 +{ 1.247 + Element *elem; 1.248 + 1.249 + if (!mGlyphIdMap.Get(aGlyphId, &elem)) { 1.250 + elem = nullptr; 1.251 + if (gfxSVGGlyphsDocument *set = FindOrCreateGlyphsDocument(aGlyphId)) { 1.252 + elem = set->GetGlyphElement(aGlyphId); 1.253 + } 1.254 + mGlyphIdMap.Put(aGlyphId, elem); 1.255 + } 1.256 + 1.257 + return elem; 1.258 +} 1.259 + 1.260 +bool 1.261 +gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) 1.262 +{ 1.263 + return !!GetGlyphElement(aGlyphId); 1.264 +} 1.265 + 1.266 +Element * 1.267 +gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) 1.268 +{ 1.269 + return mGlyphIdMap.Get(aGlyphId); 1.270 +} 1.271 + 1.272 +gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t *aBuffer, 1.273 + uint32_t aBufLen, 1.274 + gfxSVGGlyphs *aSVGGlyphs) 1.275 + : mOwner(aSVGGlyphs) 1.276 +{ 1.277 + ParseDocument(aBuffer, aBufLen); 1.278 + if (!mDocument) { 1.279 + NS_WARNING("Could not parse SVG glyphs document"); 1.280 + return; 1.281 + } 1.282 + 1.283 + Element *root = mDocument->GetRootElement(); 1.284 + if (!root) { 1.285 + NS_WARNING("Could not parse SVG glyphs document"); 1.286 + return; 1.287 + } 1.288 + 1.289 + nsresult rv = SetupPresentation(); 1.290 + if (NS_FAILED(rv)) { 1.291 + NS_WARNING("Couldn't setup presentation for SVG glyphs document"); 1.292 + return; 1.293 + } 1.294 + 1.295 + FindGlyphElements(root); 1.296 +} 1.297 + 1.298 +gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() 1.299 +{ 1.300 + if (mDocument) { 1.301 + nsSMILAnimationController* controller = mDocument->GetAnimationController(); 1.302 + if (controller) { 1.303 + controller->Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE); 1.304 + } 1.305 + } 1.306 + if (mPresShell) { 1.307 + mPresShell->RemovePostRefreshObserver(this); 1.308 + } 1.309 + if (mViewer) { 1.310 + mViewer->Destroy(); 1.311 + } 1.312 +} 1.313 + 1.314 +static nsresult 1.315 +CreateBufferedStream(const uint8_t *aBuffer, uint32_t aBufLen, 1.316 + nsCOMPtr<nsIInputStream> &aResult) 1.317 +{ 1.318 + nsCOMPtr<nsIInputStream> stream; 1.319 + nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), 1.320 + reinterpret_cast<const char *>(aBuffer), 1.321 + aBufLen, NS_ASSIGNMENT_DEPEND); 1.322 + NS_ENSURE_SUCCESS(rv, rv); 1.323 + 1.324 + nsCOMPtr<nsIInputStream> aBufferedStream; 1.325 + if (!NS_InputStreamIsBuffered(stream)) { 1.326 + rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream), stream, 4096); 1.327 + NS_ENSURE_SUCCESS(rv, rv); 1.328 + stream = aBufferedStream; 1.329 + } 1.330 + 1.331 + aResult = stream; 1.332 + 1.333 + return NS_OK; 1.334 +} 1.335 + 1.336 +nsresult 1.337 +gfxSVGGlyphsDocument::ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen) 1.338 +{ 1.339 + // Mostly pulled from nsDOMParser::ParseFromStream 1.340 + 1.341 + nsCOMPtr<nsIInputStream> stream; 1.342 + nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream); 1.343 + NS_ENSURE_SUCCESS(rv, rv); 1.344 + 1.345 + nsCOMPtr<nsIURI> uri; 1.346 + nsHostObjectProtocolHandler::GenerateURIString(NS_LITERAL_CSTRING(FONTTABLEURI_SCHEME), 1.347 + mSVGGlyphsDocumentURI); 1.348 + 1.349 + rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI); 1.350 + NS_ENSURE_SUCCESS(rv, rv); 1.351 + 1.352 + nsCOMPtr<nsIPrincipal> principal; 1.353 + nsContentUtils::GetSecurityManager()-> 1.354 + GetNoAppCodebasePrincipal(uri, getter_AddRefs(principal)); 1.355 + 1.356 + nsCOMPtr<nsIDOMDocument> domDoc; 1.357 + rv = NS_NewDOMDocument(getter_AddRefs(domDoc), 1.358 + EmptyString(), // aNamespaceURI 1.359 + EmptyString(), // aQualifiedName 1.360 + nullptr, // aDoctype 1.361 + uri, uri, principal, 1.362 + false, // aLoadedAsData 1.363 + nullptr, // aEventObject 1.364 + DocumentFlavorSVG); 1.365 + NS_ENSURE_SUCCESS(rv, rv); 1.366 + 1.367 + nsCOMPtr<nsIDocument> document(do_QueryInterface(domDoc)); 1.368 + if (!document) { 1.369 + return NS_ERROR_FAILURE; 1.370 + } 1.371 + 1.372 + nsCOMPtr<nsIChannel> channel; 1.373 + rv = NS_NewInputStreamChannel(getter_AddRefs(channel), uri, nullptr /* stream */, 1.374 + SVG_CONTENT_TYPE, UTF8_CHARSET); 1.375 + NS_ENSURE_SUCCESS(rv, rv); 1.376 + 1.377 + channel->SetOwner(principal); 1.378 + 1.379 + // Set this early because various decisions during page-load depend on it. 1.380 + document->SetIsBeingUsedAsImage(); 1.381 + document->SetReadyStateInternal(nsIDocument::READYSTATE_UNINITIALIZED); 1.382 + 1.383 + nsCOMPtr<nsIStreamListener> listener; 1.384 + rv = document->StartDocumentLoad("external-resource", channel, 1.385 + nullptr, // aLoadGroup 1.386 + nullptr, // aContainer 1.387 + getter_AddRefs(listener), 1.388 + true /* aReset */); 1.389 + if (NS_FAILED(rv) || !listener) { 1.390 + return NS_ERROR_FAILURE; 1.391 + } 1.392 + 1.393 + rv = listener->OnStartRequest(channel, nullptr /* aContext */); 1.394 + if (NS_FAILED(rv)) { 1.395 + channel->Cancel(rv); 1.396 + } 1.397 + 1.398 + nsresult status; 1.399 + channel->GetStatus(&status); 1.400 + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { 1.401 + rv = listener->OnDataAvailable(channel, nullptr /* aContext */, stream, 0, aBufLen); 1.402 + if (NS_FAILED(rv)) { 1.403 + channel->Cancel(rv); 1.404 + } 1.405 + channel->GetStatus(&status); 1.406 + } 1.407 + 1.408 + rv = listener->OnStopRequest(channel, nullptr /* aContext */, status); 1.409 + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); 1.410 + 1.411 + document.swap(mDocument); 1.412 + 1.413 + return NS_OK; 1.414 +} 1.415 + 1.416 +void 1.417 +gfxSVGGlyphsDocument::InsertGlyphId(Element *aGlyphElement) 1.418 +{ 1.419 + nsAutoString glyphIdStr; 1.420 + static const uint32_t glyphPrefixLength = 5; 1.421 + // The maximum glyph ID is 65535 so the maximum length of the numeric part 1.422 + // is 5. 1.423 + if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) || 1.424 + !StringBeginsWith(glyphIdStr, NS_LITERAL_STRING("glyph")) || 1.425 + glyphIdStr.Length() > glyphPrefixLength + 5) { 1.426 + return; 1.427 + } 1.428 + 1.429 + uint32_t id = 0; 1.430 + for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) { 1.431 + char16_t ch = glyphIdStr.CharAt(i); 1.432 + if (ch < '0' || ch > '9') { 1.433 + return; 1.434 + } 1.435 + if (ch == '0' && i == glyphPrefixLength) { 1.436 + return; 1.437 + } 1.438 + id = id * 10 + (ch - '0'); 1.439 + } 1.440 + 1.441 + mGlyphIdMap.Put(id, aGlyphElement); 1.442 +} 1.443 + 1.444 +void 1.445 +gfxTextContextPaint::InitStrokeGeometry(gfxContext *aContext, 1.446 + float devUnitsPerSVGUnit) 1.447 +{ 1.448 + mStrokeWidth = aContext->CurrentLineWidth() / devUnitsPerSVGUnit; 1.449 + aContext->CurrentDash(mDashes, &mDashOffset); 1.450 + for (uint32_t i = 0; i < mDashes.Length(); i++) { 1.451 + mDashes[i] /= devUnitsPerSVGUnit; 1.452 + } 1.453 + mDashOffset /= devUnitsPerSVGUnit; 1.454 +}