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/FontTableURIProtocolHandler.h"
17 #include "mozilla/dom/ImageTracker.h"
18 #include "mozilla/dom/SVGDocument.h"
21 #include "nsICategoryManager.h"
22 #include "nsIDocumentLoaderFactory.h"
23 #include "nsIContentViewer.h"
24 #include "nsIStreamListener.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsNetUtil.h"
27 #include "nsIInputStream.h"
28 #include "nsStringStream.h"
29 #include "nsStreamUtils.h"
30 #include "nsIPrincipal.h"
31 #include "nsContentUtils.h"
33 #include "gfxContext.h"
34 #include "harfbuzz/hb.h"
37 #define SVG_CONTENT_TYPE "image/svg+xml"_ns
38 #define UTF8_CHARSET "utf-8"_ns
40 using namespace mozilla
;
41 using mozilla::dom::Document
;
42 using mozilla::dom::Element
;
45 const mozilla::gfx::DeviceColor
SimpleTextContextPaint::sZero
;
47 gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t
* aSVGTable
, gfxFontEntry
* aFontEntry
)
48 : mSVGData(aSVGTable
), mFontEntry(aFontEntry
) {
50 const char* svgData
= hb_blob_get_data(mSVGData
, &length
);
51 mHeader
= reinterpret_cast<const Header
*>(svgData
);
54 if (sizeof(Header
) <= length
&& uint16_t(mHeader
->mVersion
) == 0 &&
55 uint64_t(mHeader
->mDocIndexOffset
) + 2 <= length
) {
56 const DocIndex
* docIndex
=
57 reinterpret_cast<const DocIndex
*>(svgData
+ mHeader
->mDocIndexOffset
);
58 // Limit the number of documents to avoid overflow
59 if (uint64_t(mHeader
->mDocIndexOffset
) + 2 +
60 uint16_t(docIndex
->mNumEntries
) * sizeof(IndexEntry
) <=
67 gfxSVGGlyphs::~gfxSVGGlyphs() { hb_blob_destroy(mSVGData
); }
69 void gfxSVGGlyphs::DidRefresh() { mFontEntry
->NotifyGlyphsChanged(); }
72 * Comparison operator for finding a range containing a given glyph ID. Simply
73 * checks whether |key| is less (greater) than every element of |range|, in
74 * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in
75 * |range|, in which case return equality.
76 * The total ordering here is guaranteed by
77 * (1) the index ranges being disjoint; and
78 * (2) the (sole) key always being a singleton, so intersection => containment
79 * (note that this is wrong if we have more than one intersection or two
80 * sets intersecting of size > 1 -- so... don't do that)
83 int gfxSVGGlyphs::CompareIndexEntries(const void* aKey
, const void* aEntry
) {
84 const uint32_t key
= *(uint32_t*)aKey
;
85 const IndexEntry
* entry
= (const IndexEntry
*)aEntry
;
87 if (key
< uint16_t(entry
->mStartGlyph
)) {
90 if (key
> uint16_t(entry
->mEndGlyph
)) {
96 gfxSVGGlyphsDocument
* gfxSVGGlyphs::FindOrCreateGlyphsDocument(
103 IndexEntry
* entry
= (IndexEntry
*)bsearch(
104 &aGlyphId
, mDocIndex
->mEntries
, uint16_t(mDocIndex
->mNumEntries
),
105 sizeof(IndexEntry
), CompareIndexEntries
);
110 return mGlyphDocs
.WithEntryHandle(
111 entry
->mDocOffset
, [&](auto&& glyphDocsEntry
) -> gfxSVGGlyphsDocument
* {
112 if (!glyphDocsEntry
) {
114 const uint8_t* data
=
115 (const uint8_t*)hb_blob_get_data(mSVGData
, &length
);
116 if (entry
->mDocOffset
> 0 && uint64_t(mHeader
->mDocIndexOffset
) +
120 return glyphDocsEntry
121 .Insert(MakeUnique
<gfxSVGGlyphsDocument
>(
122 data
+ mHeader
->mDocIndexOffset
+ entry
->mDocOffset
,
123 entry
->mDocLength
, this))
130 return glyphDocsEntry
->get();
134 nsresult
gfxSVGGlyphsDocument::SetupPresentation() {
135 nsCOMPtr
<nsICategoryManager
> catMan
=
136 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
);
137 nsCString contractId
;
138 nsresult rv
= catMan
->GetCategoryEntry("Gecko-Content-Viewers",
139 "image/svg+xml", contractId
);
140 NS_ENSURE_SUCCESS(rv
, rv
);
142 nsCOMPtr
<nsIDocumentLoaderFactory
> docLoaderFactory
=
143 do_GetService(contractId
.get());
144 NS_ASSERTION(docLoaderFactory
, "Couldn't get DocumentLoaderFactory");
146 nsCOMPtr
<nsIContentViewer
> viewer
;
147 rv
= docLoaderFactory
->CreateInstanceForDocument(nullptr, mDocument
, nullptr,
148 getter_AddRefs(viewer
));
149 NS_ENSURE_SUCCESS(rv
, rv
);
151 rv
= viewer
->Init(nullptr, gfx::IntRect(0, 0, 1000, 1000), 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(
211 *aContextPaint
, *glyph
->OwnerDoc()->AsSVGDocument());
213 SVGUtils::PaintSVGGlyph(glyph
, aContext
);
216 bool gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId
,
217 const gfxMatrix
& aSVGToAppSpace
,
219 Element
* glyph
= mGlyphIdMap
.Get(aGlyphId
);
221 "No glyph element. Should check with HasSVGGlyph() first!");
223 return SVGUtils::GetSVGGlyphExtents(glyph
, aSVGToAppSpace
, aResult
);
226 Element
* gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId
) {
227 return mGlyphIdMap
.LookupOrInsertWith(aGlyphId
, [&] {
228 Element
* elem
= nullptr;
229 if (gfxSVGGlyphsDocument
* set
= FindOrCreateGlyphsDocument(aGlyphId
)) {
230 elem
= set
->GetGlyphElement(aGlyphId
);
236 bool gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId
) {
237 return !!GetGlyphElement(aGlyphId
);
240 size_t gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
241 // We don't include the size of mSVGData here, because (depending on the
242 // font backend implementation) it will either wrap a block of data owned
243 // by the system (and potentially shared), or a table that's in our font
244 // table cache and therefore already counted.
245 size_t result
= aMallocSizeOf(this) +
246 mGlyphDocs
.ShallowSizeOfExcludingThis(aMallocSizeOf
) +
247 mGlyphIdMap
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
248 for (const auto& entry
: mGlyphDocs
.Values()) {
249 result
+= entry
->SizeOfIncludingThis(aMallocSizeOf
);
254 Element
* gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId
) {
255 return mGlyphIdMap
.Get(aGlyphId
);
258 gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t* aBuffer
,
260 gfxSVGGlyphs
* aSVGGlyphs
)
261 : mOwner(aSVGGlyphs
) {
262 if (aBufLen
>= 14 && aBuffer
[0] == 31 && aBuffer
[1] == 139) {
263 // It's a gzip-compressed document; decompress it before parsing.
264 // The original length (modulo 2^32) is found in the last 4 bytes
265 // of the data, stored in little-endian format. We read it as
266 // individual bytes to avoid possible alignment issues.
267 // (Note that if the original length was >2^32, then origLen here
268 // will be incorrect; but then the inflate() call will not return
269 // Z_STREAM_END and we'll bail out safely.)
270 size_t origLen
= (size_t(aBuffer
[aBufLen
- 1]) << 24) +
271 (size_t(aBuffer
[aBufLen
- 2]) << 16) +
272 (size_t(aBuffer
[aBufLen
- 3]) << 8) +
273 size_t(aBuffer
[aBufLen
- 4]);
274 AutoTArray
<uint8_t, 4096> outBuf
;
275 if (outBuf
.SetLength(origLen
, mozilla::fallible
)) {
277 s
.next_in
= const_cast<Byte
*>(aBuffer
);
278 s
.avail_in
= aBufLen
;
279 s
.next_out
= outBuf
.Elements();
280 s
.avail_out
= outBuf
.Length();
281 // The magic number 16 here is the zlib flag to expect gzip format,
282 // see http://www.zlib.net/manual.html#Advanced
283 if (Z_OK
== inflateInit2(&s
, 16 + MAX_WBITS
)) {
284 int result
= inflate(&s
, Z_FINISH
);
285 if (Z_STREAM_END
== result
) {
286 MOZ_ASSERT(size_t(s
.next_out
- outBuf
.Elements()) == origLen
);
287 ParseDocument(outBuf
.Elements(), outBuf
.Length());
289 NS_WARNING("Failed to decompress SVG glyphs document");
294 NS_WARNING("Failed to allocate memory for SVG glyphs document");
297 ParseDocument(aBuffer
, aBufLen
);
301 NS_WARNING("Could not parse SVG glyphs document");
305 Element
* root
= mDocument
->GetRootElement();
307 NS_WARNING("Could not parse SVG glyphs document");
311 nsresult rv
= SetupPresentation();
313 NS_WARNING("Couldn't setup presentation for SVG glyphs document");
317 FindGlyphElements(root
);
320 gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument() {
322 mDocument
->OnPageHide(false, nullptr);
325 mPresShell
->RemovePostRefreshObserver(this);
328 mViewer
->Close(nullptr);
333 static nsresult
CreateBufferedStream(const uint8_t* aBuffer
, uint32_t aBufLen
,
334 nsCOMPtr
<nsIInputStream
>& aResult
) {
335 nsCOMPtr
<nsIInputStream
> stream
;
336 nsresult rv
= NS_NewByteInputStream(
337 getter_AddRefs(stream
),
338 Span(reinterpret_cast<const char*>(aBuffer
), aBufLen
),
339 NS_ASSIGNMENT_DEPEND
);
340 NS_ENSURE_SUCCESS(rv
, rv
);
342 nsCOMPtr
<nsIInputStream
> aBufferedStream
;
343 if (!NS_InputStreamIsBuffered(stream
)) {
344 rv
= NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream
),
345 stream
.forget(), 4096);
346 NS_ENSURE_SUCCESS(rv
, rv
);
347 stream
= aBufferedStream
;
355 nsresult
gfxSVGGlyphsDocument::ParseDocument(const uint8_t* aBuffer
,
357 // Mostly pulled from nsDOMParser::ParseFromStream
359 nsCOMPtr
<nsIInputStream
> stream
;
360 nsresult rv
= CreateBufferedStream(aBuffer
, aBufLen
, stream
);
361 NS_ENSURE_SUCCESS(rv
, rv
);
363 nsCOMPtr
<nsIURI
> uri
;
364 mozilla::dom::FontTableURIProtocolHandler::GenerateURIString(
365 mSVGGlyphsDocumentURI
);
367 rv
= NS_NewURI(getter_AddRefs(uri
), mSVGGlyphsDocumentURI
);
368 NS_ENSURE_SUCCESS(rv
, rv
);
370 nsCOMPtr
<nsIPrincipal
> principal
=
371 NullPrincipal::CreateWithoutOriginAttributes();
373 RefPtr
<Document
> document
;
374 rv
= NS_NewDOMDocument(getter_AddRefs(document
),
375 u
""_ns
, // aNamespaceURI
376 u
""_ns
, // aQualifiedName
379 false, // aLoadedAsData
380 nullptr, // aEventObject
382 NS_ENSURE_SUCCESS(rv
, rv
);
384 nsCOMPtr
<nsIChannel
> channel
;
385 rv
= NS_NewInputStreamChannel(
386 getter_AddRefs(channel
), uri
,
388 principal
, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL
,
389 nsIContentPolicy::TYPE_OTHER
, SVG_CONTENT_TYPE
, UTF8_CHARSET
);
390 NS_ENSURE_SUCCESS(rv
, rv
);
392 // Set this early because various decisions during page-load depend on it.
393 document
->SetIsBeingUsedAsImage();
394 document
->SetIsSVGGlyphsDocument();
395 document
->SetReadyStateInternal(Document::READYSTATE_UNINITIALIZED
);
397 nsCOMPtr
<nsIStreamListener
> listener
;
398 rv
= document
->StartDocumentLoad("external-resource", channel
,
399 nullptr, // aLoadGroup
400 nullptr, // aContainer
401 getter_AddRefs(listener
), true /* aReset */);
402 if (NS_FAILED(rv
) || !listener
) {
403 return NS_ERROR_FAILURE
;
406 rv
= listener
->OnStartRequest(channel
);
412 channel
->GetStatus(&status
);
413 if (NS_SUCCEEDED(rv
) && NS_SUCCEEDED(status
)) {
414 rv
= listener
->OnDataAvailable(channel
, stream
, 0, aBufLen
);
418 channel
->GetStatus(&status
);
421 rv
= listener
->OnStopRequest(channel
, status
);
422 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
424 document
.swap(mDocument
);
429 void gfxSVGGlyphsDocument::InsertGlyphId(Element
* aGlyphElement
) {
430 nsAutoString glyphIdStr
;
431 static const uint32_t glyphPrefixLength
= 5;
432 // The maximum glyph ID is 65535 so the maximum length of the numeric part
434 if (!aGlyphElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::id
, glyphIdStr
) ||
435 !StringBeginsWith(glyphIdStr
, u
"glyph"_ns
) ||
436 glyphIdStr
.Length() > glyphPrefixLength
+ 5) {
441 for (uint32_t i
= glyphPrefixLength
; i
< glyphIdStr
.Length(); ++i
) {
442 char16_t ch
= glyphIdStr
.CharAt(i
);
443 if (ch
< '0' || ch
> '9') {
446 if (ch
== '0' && i
== glyphPrefixLength
) {
449 id
= id
* 10 + (ch
- '0');
452 mGlyphIdMap
.InsertOrUpdate(id
, aGlyphElement
);
455 size_t gfxSVGGlyphsDocument::SizeOfIncludingThis(
456 mozilla::MallocSizeOf aMallocSizeOf
) const {
457 return aMallocSizeOf(this) +
458 mGlyphIdMap
.ShallowSizeOfExcludingThis(aMallocSizeOf
) +
459 mSVGGlyphsDocumentURI
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);