1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "gfxSVGGlyphs.h"
7 #include "mozilla/BasePrincipal.h"
8 #include "mozilla/LoadInfo.h"
9 #include "mozilla/NullPrincipal.h"
10 #include "mozilla/PresShell.h"
11 #include "mozilla/SMILAnimationController.h"
12 #include "mozilla/SVGContextPaint.h"
13 #include "mozilla/SVGUtils.h"
14 #include "mozilla/dom/Document.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/ImageTracker.h"
17 #include "mozilla/dom/SVGDocument.h"
20 #include "nsICategoryManager.h"
21 #include "nsIDocumentLoaderFactory.h"
22 #include "nsIDocumentViewer.h"
23 #include "nsIStreamListener.h"
24 #include "nsServiceManagerUtils.h"
25 #include "nsNetUtil.h"
26 #include "nsIInputStream.h"
27 #include "nsStringStream.h"
28 #include "nsStreamUtils.h"
29 #include "nsIPrincipal.h"
30 #include "nsContentUtils.h"
32 #include "gfxContext.h"
33 #include "harfbuzz/hb.h"
36 #define SVG_CONTENT_TYPE "image/svg+xml"_ns
37 #define UTF8_CHARSET "utf-8"_ns
39 using namespace mozilla
;
40 using mozilla::dom::Document
;
41 using mozilla::dom::Element
;
44 const mozilla::gfx::DeviceColor
SimpleTextContextPaint::sZero
;
46 gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t
* aSVGTable
, gfxFontEntry
* aFontEntry
)
47 : mSVGData(aSVGTable
), mFontEntry(aFontEntry
) {
49 const char* svgData
= hb_blob_get_data(mSVGData
, &length
);
50 mHeader
= reinterpret_cast<const Header
*>(svgData
);
53 if (sizeof(Header
) <= length
&& uint16_t(mHeader
->mVersion
) == 0 &&
54 uint64_t(mHeader
->mDocIndexOffset
) + 2 <= length
) {
55 const DocIndex
* docIndex
=
56 reinterpret_cast<const DocIndex
*>(svgData
+ mHeader
->mDocIndexOffset
);
57 // Limit the number of documents to avoid overflow
58 if (uint64_t(mHeader
->mDocIndexOffset
) + 2 +
59 uint16_t(docIndex
->mNumEntries
) * sizeof(IndexEntry
) <=
66 gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData
); }
68 void gfxSVGGlyphs::DidRefresh() { mFontEntry
->NotifyGlyphsChanged(); }
71 * Comparison operator for finding a range containing a given glyph ID. Simply
72 * checks whether |key| is less (greater) than every element of |range|, in
73 * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in
74 * |range|, in which case return equality.
75 * The total ordering here is guaranteed by
76 * (1) the index ranges being disjoint; and
77 * (2) the (sole) key always being a singleton, so intersection => containment
78 * (note that this is wrong if we have more than one intersection or two
79 * sets intersecting of size > 1 -- so... don't do that)
82 int gfxSVGGlyphs::CompareIndexEntries(const void* aKey
, const void* aEntry
) {
83 const uint32_t key
= *(uint32_t*)aKey
;
84 const IndexEntry
* entry
= (const IndexEntry
*)aEntry
;
86 if (key
< uint16_t(entry
->mStartGlyph
)) {
89 if (key
> uint16_t(entry
->mEndGlyph
)) {
95 gfxSVGGlyphsDocument
* gfxSVGGlyphs::FindOrCreateGlyphsDocument(
102 IndexEntry
* entry
= (IndexEntry
*)bsearch(
103 &aGlyphId
, mDocIndex
->mEntries
, uint16_t(mDocIndex
->mNumEntries
),
104 sizeof(IndexEntry
), CompareIndexEntries
);
109 return mGlyphDocs
.WithEntryHandle(
110 entry
->mDocOffset
, [&](auto&& glyphDocsEntry
) -> gfxSVGGlyphsDocument
* {
111 if (!glyphDocsEntry
) {
113 const uint8_t* data
=
114 (const uint8_t*)hb_blob_get_data(mSVGData
, &length
);
115 if (entry
->mDocOffset
> 0 && uint64_t(mHeader
->mDocIndexOffset
) +
119 return glyphDocsEntry
120 .Insert(MakeUnique
<gfxSVGGlyphsDocument
>(
121 data
+ mHeader
->mDocIndexOffset
+ entry
->mDocOffset
,
122 entry
->mDocLength
, this))
129 return glyphDocsEntry
->get();
133 nsresult
gfxSVGGlyphsDocument::SetupPresentation() {
134 nsCOMPtr
<nsICategoryManager
> catMan
=
135 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
);
136 nsCString contractId
;
137 nsresult rv
= catMan
->GetCategoryEntry("Gecko-Content-Viewers",
138 "image/svg+xml", contractId
);
139 NS_ENSURE_SUCCESS(rv
, rv
);
141 nsCOMPtr
<nsIDocumentLoaderFactory
> docLoaderFactory
=
142 do_GetService(contractId
.get());
143 NS_ASSERTION(docLoaderFactory
, "Couldn't get DocumentLoaderFactory");
145 nsCOMPtr
<nsIDocumentViewer
> viewer
;
146 rv
= docLoaderFactory
->CreateInstanceForDocument(nullptr, mDocument
, nullptr,
147 getter_AddRefs(viewer
));
148 NS_ENSURE_SUCCESS(rv
, rv
);
150 auto upem
= mOwner
->FontEntry()->UnitsPerEm();
151 rv
= viewer
->Init(nullptr, gfx::IntRect(0, 0, upem
, upem
), nullptr);
152 if (NS_SUCCEEDED(rv
)) {
153 rv
= viewer
->Open(nullptr, nullptr);
154 NS_ENSURE_SUCCESS(rv
, rv
);
157 RefPtr
<PresShell
> presShell
= viewer
->GetPresShell();
158 if (!presShell
->DidInitialize()) {
159 rv
= presShell
->Initialize();
160 NS_ENSURE_SUCCESS(rv
, rv
);
163 mDocument
->FlushPendingNotifications(FlushType::Layout
);
165 if (mDocument
->HasAnimationController()) {
166 mDocument
->GetAnimationController()->Resume(SMILTimeContainer::PAUSE_IMAGE
);
168 mDocument
->ImageTracker()->SetAnimatingState(true);
171 mPresShell
= presShell
;
172 mPresShell
->AddPostRefreshObserver(this);
177 void gfxSVGGlyphsDocument::DidRefresh() { mOwner
->DidRefresh(); }
180 * Walk the DOM tree to find all glyph elements and insert them into the lookup
182 * @param aElem The element to search from
184 void gfxSVGGlyphsDocument::FindGlyphElements(Element
* aElem
) {
185 for (nsIContent
* child
= aElem
->GetLastChild(); child
;
186 child
= child
->GetPreviousSibling()) {
187 if (!child
->IsElement()) {
190 FindGlyphElements(child
->AsElement());
193 InsertGlyphId(aElem
);
197 * If there exists an SVG glyph with the specified glyph id, render it and
198 * return true If no such glyph exists, or in the case of an error return false
199 * @param aContext The thebes aContext to draw to
200 * @param aGlyphId The glyph id
201 * @return true iff rendering succeeded
203 void gfxSVGGlyphs::RenderGlyph(gfxContext
* aContext
, uint32_t aGlyphId
,
204 SVGContextPaint
* aContextPaint
) {
205 gfxContextAutoSaveRestore
aContextRestorer(aContext
);
207 Element
* glyph
= mGlyphIdMap
.Get(aGlyphId
);
208 MOZ_ASSERT(glyph
, "No glyph element. Should check with HasSVGGlyph() first!");
210 AutoSetRestoreSVGContextPaint
autoSetRestore(aContextPaint
,
213 SVGUtils::PaintSVGGlyph(glyph
, aContext
);
216 // This will not have any effect, because we're about to restore the state
217 // via the aContextRestorer destructor, but it prevents debug builds from
218 // asserting if it turns out that PaintSVGGlyph didn't actually do anything.
219 // This happens if the SVG document consists of just an image, and the image
220 // hasn't finished loading yet so we can't draw it.
221 aContext
->SetOp(gfx::CompositionOp::OP_OVER
);
225 bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId
,
226 const gfxMatrix
& aSVGToAppSpace
,
228 Element
* glyph
= mGlyphIdMap
.Get(aGlyphId
);
230 "No glyph element. Should check with HasSVGGlyph() first!");
232 return SVGUtils::GetSVGGlyphExtents(glyph
, aSVGToAppSpace
, aResult
);
235 Element
* gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId
) {
236 return mGlyphIdMap
.LookupOrInsertWith(aGlyphId
, [&] {
237 Element
* elem
= nullptr;
238 if (gfxSVGGlyphsDocument
* set
= FindOrCreateGlyphsDocument(aGlyphId
)) {
239 elem
= set
->GetGlyphElement(aGlyphId
);
245 bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId
) {
246 return !!GetGlyphElement(aGlyphId
);
249 size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
250 // We don't include the size of mSVGData here, because (depending on the
251 // font backend implementation) it will either wrap a block of data owned
252 // by the system (and potentially shared), or a table that's in our font
253 // table cache and therefore already counted.
254 size_t result
= aMallocSizeOf(this) +
255 mGlyphDocs
.ShallowSizeOfExcludingThis(aMallocSizeOf
) +
256 mGlyphIdMap
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
257 for (const auto& entry
: mGlyphDocs
.Values()) {
258 result
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
263 Element
* gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId
) {
264 return mGlyphIdMap
.Get(aGlyphId
);
267 gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t* aBuffer
,
269 gfxSVGGlyphs
* aSVGGlyphs
)
270 : mOwner(aSVGGlyphs
) {
271 if (aBufLen
>= 14 && aBuffer
[0] == 31 && aBuffer
[1] == 139) {
272 // It's a gzip-compressed document; decompress it before parsing.
273 // The original length (modulo 2^32) is found in the last 4 bytes
274 // of the data, stored in little-endian format. We read it as
275 // individual bytes to avoid possible alignment issues.
276 // (Note that if the original length was >2^32, then origLen here
277 // will be incorrect; but then the inflate() call will not return
278 // Z_STREAM_END and we'll bail out safely.)
279 size_t origLen
= (size_t(aBuffer
[aBufLen
- 1]) << 24) +
280 (size_t(aBuffer
[aBufLen
- 2]) << 16) +
281 (size_t(aBuffer
[aBufLen
- 3]) << 8) +
282 size_t(aBuffer
[aBufLen
- 4]);
283 AutoTArray
<uint8_t, 4096> outBuf
;
284 if (outBuf
.SetLength(origLen
, mozilla::fallible
)) {
286 s
.next_in
= const_cast<Byte
*>(aBuffer
);
287 s
.avail_in
= aBufLen
;
288 s
.next_out
= outBuf
.Elements();
289 s
.avail_out
= outBuf
.Length();
290 // The magic number 16 here is the zlib flag to expect gzip format,
291 // see http://www.zlib.net/manual.html#Advanced
292 if (Z_OK
== inflateInit2(&s
, 16 + MAX_WBITS
)) {
293 int result
= inflate(&s
, Z_FINISH
);
294 if (Z_STREAM_END
== result
) {
295 MOZ_ASSERT(size_t(s
.next_out
- outBuf
.Elements()) == origLen
);
296 ParseDocument(outBuf
.Elements(), outBuf
.Length());
298 NS_WARNING("Failed to decompress SVG glyphs document");
303 NS_WARNING("Failed to allocate memory for SVG glyphs document");
306 ParseDocument(aBuffer
, aBufLen
);
310 NS_WARNING("Could not parse SVG glyphs document");
314 Element
* root
= mDocument
->GetRootElement();
316 NS_WARNING("Could not parse SVG glyphs document");
320 nsresult rv
= SetupPresentation();
322 NS_WARNING("Couldn't setup presentation for SVG glyphs document");
326 FindGlyphElements(root
);
329 gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() {
331 mDocument
->OnPageHide(false, nullptr);
334 mPresShell
->RemovePostRefreshObserver(this);
337 mViewer
->Close(nullptr);
342 static nsresult
CreateBufferedStream(const uint8_t* aBuffer
, uint32_t aBufLen
,
343 nsCOMPtr
<nsIInputStream
>& aResult
) {
344 nsCOMPtr
<nsIInputStream
> stream
;
345 nsresult rv
= NS_NewByteInputStream(
346 getter_AddRefs(stream
),
347 Span(reinterpret_cast<const char*>(aBuffer
), aBufLen
),
348 NS_ASSIGNMENT_DEPEND
);
349 NS_ENSURE_SUCCESS(rv
, rv
);
351 nsCOMPtr
<nsIInputStream
> aBufferedStream
;
352 if (!NS_InputStreamIsBuffered(stream
)) {
353 rv
= NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream
),
354 stream
.forget(), 4096);
355 NS_ENSURE_SUCCESS(rv
, rv
);
356 stream
= aBufferedStream
;
364 nsresult
gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer
,
366 // Mostly pulled from nsDOMParser::ParseFromStream
368 nsCOMPtr
<nsIInputStream
> stream
;
369 nsresult rv
= CreateBufferedStream(aBuffer
, aBufLen
, stream
);
370 NS_ENSURE_SUCCESS(rv
, rv
);
372 // We just need a dummy URI.
373 nsCOMPtr
<nsIURI
> uri
;
374 rv
= NS_NewURI(getter_AddRefs(uri
), "moz-svg-glyphs://"_ns
);
375 NS_ENSURE_SUCCESS(rv
, rv
);
377 nsCOMPtr
<nsIPrincipal
> principal
=
378 NullPrincipal::CreateWithoutOriginAttributes();
380 RefPtr
<Document
> document
;
381 rv
= NS_NewDOMDocument(getter_AddRefs(document
),
382 u
""_ns
, // aNamespaceURI
383 u
""_ns
, // aQualifiedName
386 false, // aLoadedAsData
387 nullptr, // aEventObject
389 NS_ENSURE_SUCCESS(rv
, rv
);
391 nsCOMPtr
<nsIChannel
> channel
;
392 rv
= NS_NewInputStreamChannel(
393 getter_AddRefs(channel
), uri
,
395 principal
, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL
,
396 nsIContentPolicy::TYPE_OTHER
, SVG_CONTENT_TYPE
, UTF8_CHARSET
);
397 NS_ENSURE_SUCCESS(rv
, rv
);
399 // Set this early because various decisions during page-load depend on it.
400 document
->SetIsBeingUsedAsImage();
401 document
->SetIsSVGGlyphsDocument();
402 document
->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED
);
404 nsCOMPtr
<nsIStreamListener
> listener
;
405 rv
= document
->StartDocumentLoad("external-resource", channel
,
406 nullptr, // aLoadGroup
407 nullptr, // aContainer
408 getter_AddRefs(listener
), true /* aReset */);
409 if (NS_FAILED(rv
) || !listener
) {
410 return NS_ERROR_FAILURE
;
413 rv
= listener
->OnStartRequest(channel
);
419 channel
->GetStatus(&status
);
420 if (NS_SUCCEEDED(rv
) && NS_SUCCEEDED(status
)) {
421 rv
= listener
->OnDataAvailable(channel
, stream
, 0, aBufLen
);
425 channel
->GetStatus(&status
);
428 rv
= listener
->OnStopRequest(channel
, status
);
429 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
431 document
.swap(mDocument
);
436 void gfxSVGGlyphsDocument::InsertGlyphId(Element
* aGlyphElement
) {
437 nsAutoString glyphIdStr
;
438 static const uint32_t glyphPrefixLength
= 5;
439 // The maximum glyph ID is 65535 so the maximum length of the numeric part
441 if (!aGlyphElement
->GetAttr(nsGkAtoms::id
, glyphIdStr
) ||
442 !StringBeginsWith(glyphIdStr
, u
"glyph"_ns
) ||
443 glyphIdStr
.Length() > glyphPrefixLength
+ 5) {
448 for (uint32_t i
= glyphPrefixLength
; i
< glyphIdStr
.Length(); ++i
) {
449 char16_t ch
= glyphIdStr
.CharAt(i
);
450 if (ch
< '0' || ch
> '9') {
453 if (ch
== '0' && i
== glyphPrefixLength
) {
456 id
= id
* 10 + (ch
- '0');
459 mGlyphIdMap
.InsertOrUpdate(id
, aGlyphElement
);
462 size_t gfxSVGGlyphsDocument::SizeOfIncludingThis(
463 mozilla::MallocSizeOf aMallocSizeOf
) const {
464 return aMallocSizeOf(this) +
465 mGlyphIdMap
.ShallowSizeOfExcludingThis(aMallocSizeOf
);