gfx/thebes/gfxSVGGlyphs.cpp

changeset 0
6474c204b198
     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 +}

mercurial