Line data Source code
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/. */
4 :
5 : #include "gfxSVGGlyphs.h"
6 :
7 : #include "mozilla/SVGContextPaint.h"
8 : #include "nsError.h"
9 : #include "nsIDOMDocument.h"
10 : #include "nsString.h"
11 : #include "nsIDocument.h"
12 : #include "nsICategoryManager.h"
13 : #include "nsIDocumentLoaderFactory.h"
14 : #include "nsIContentViewer.h"
15 : #include "nsIStreamListener.h"
16 : #include "nsServiceManagerUtils.h"
17 : #include "nsIPresShell.h"
18 : #include "nsNetUtil.h"
19 : #include "NullPrincipal.h"
20 : #include "nsIInputStream.h"
21 : #include "nsStringStream.h"
22 : #include "nsStreamUtils.h"
23 : #include "nsIPrincipal.h"
24 : #include "mozilla/BasePrincipal.h"
25 : #include "mozilla/dom/Element.h"
26 : #include "mozilla/LoadInfo.h"
27 : #include "nsSVGUtils.h"
28 : #include "nsHostObjectProtocolHandler.h"
29 : #include "nsContentUtils.h"
30 : #include "gfxFont.h"
31 : #include "nsSMILAnimationController.h"
32 : #include "gfxContext.h"
33 : #include "harfbuzz/hb.h"
34 : #include "mozilla/dom/ImageTracker.h"
35 :
36 : #define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml")
37 : #define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8")
38 :
39 : using namespace mozilla;
40 :
41 : typedef mozilla::dom::Element Element;
42 :
43 3 : /* static */ const Color SimpleTextContextPaint::sZero = Color();
44 :
45 0 : gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t *aSVGTable, gfxFontEntry *aFontEntry)
46 : : mSVGData(aSVGTable)
47 0 : , mFontEntry(aFontEntry)
48 : {
49 : unsigned int length;
50 0 : const char* svgData = hb_blob_get_data(mSVGData, &length);
51 0 : mHeader = reinterpret_cast<const Header*>(svgData);
52 0 : mDocIndex = nullptr;
53 :
54 0 : if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 &&
55 0 : uint64_t(mHeader->mDocIndexOffset) + 2 <= length) {
56 : const DocIndex* docIndex = reinterpret_cast<const DocIndex*>
57 0 : (svgData + mHeader->mDocIndexOffset);
58 : // Limit the number of documents to avoid overflow
59 0 : if (uint64_t(mHeader->mDocIndexOffset) + 2 +
60 0 : uint16_t(docIndex->mNumEntries) * sizeof(IndexEntry) <= length) {
61 0 : mDocIndex = docIndex;
62 : }
63 : }
64 0 : }
65 :
66 0 : gfxSVGGlyphs::~gfxSVGGlyphs()
67 : {
68 0 : hb_blob_destroy(mSVGData);
69 0 : }
70 :
71 : void
72 0 : gfxSVGGlyphs::DidRefresh()
73 : {
74 0 : mFontEntry->NotifyGlyphsChanged();
75 0 : }
76 :
77 : /*
78 : * Comparison operator for finding a range containing a given glyph ID. Simply
79 : * checks whether |key| is less (greater) than every element of |range|, in
80 : * which case return |key| < |range| (|key| > |range|). Otherwise |key| is in
81 : * |range|, in which case return equality.
82 : * The total ordering here is guaranteed by
83 : * (1) the index ranges being disjoint; and
84 : * (2) the (sole) key always being a singleton, so intersection => containment
85 : * (note that this is wrong if we have more than one intersection or two
86 : * sets intersecting of size > 1 -- so... don't do that)
87 : */
88 : /* static */ int
89 0 : gfxSVGGlyphs::CompareIndexEntries(const void *aKey, const void *aEntry)
90 : {
91 0 : const uint32_t key = *(uint32_t*)aKey;
92 0 : const IndexEntry *entry = (const IndexEntry*)aEntry;
93 :
94 0 : if (key < uint16_t(entry->mStartGlyph)) {
95 0 : return -1;
96 : }
97 0 : if (key > uint16_t(entry->mEndGlyph)) {
98 0 : return 1;
99 : }
100 0 : return 0;
101 : }
102 :
103 : gfxSVGGlyphsDocument *
104 0 : gfxSVGGlyphs::FindOrCreateGlyphsDocument(uint32_t aGlyphId)
105 : {
106 0 : if (!mDocIndex) {
107 : // Invalid table
108 0 : return nullptr;
109 : }
110 :
111 0 : IndexEntry *entry = (IndexEntry*)bsearch(&aGlyphId, mDocIndex->mEntries,
112 0 : uint16_t(mDocIndex->mNumEntries),
113 : sizeof(IndexEntry),
114 0 : CompareIndexEntries);
115 0 : if (!entry) {
116 0 : return nullptr;
117 : }
118 :
119 0 : gfxSVGGlyphsDocument *result = mGlyphDocs.Get(entry->mDocOffset);
120 :
121 0 : if (!result) {
122 : unsigned int length;
123 0 : const uint8_t *data = (const uint8_t*)hb_blob_get_data(mSVGData, &length);
124 0 : if (entry->mDocOffset > 0 &&
125 0 : uint64_t(mHeader->mDocIndexOffset) + entry->mDocOffset + entry->mDocLength <= length) {
126 0 : result = new gfxSVGGlyphsDocument(data + mHeader->mDocIndexOffset + entry->mDocOffset,
127 0 : entry->mDocLength, this);
128 0 : mGlyphDocs.Put(entry->mDocOffset, result);
129 : }
130 : }
131 :
132 0 : return result;
133 : }
134 :
135 : nsresult
136 0 : gfxSVGGlyphsDocument::SetupPresentation()
137 : {
138 0 : nsCOMPtr<nsICategoryManager> catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
139 0 : nsXPIDLCString contractId;
140 0 : nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "image/svg+xml", getter_Copies(contractId));
141 0 : NS_ENSURE_SUCCESS(rv, rv);
142 :
143 0 : nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = do_GetService(contractId);
144 0 : NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
145 :
146 0 : nsCOMPtr<nsIContentViewer> viewer;
147 0 : rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr, getter_AddRefs(viewer));
148 0 : NS_ENSURE_SUCCESS(rv, rv);
149 :
150 0 : rv = viewer->Init(nullptr, gfx::IntRect(0, 0, 1000, 1000));
151 0 : if (NS_SUCCEEDED(rv)) {
152 0 : rv = viewer->Open(nullptr, nullptr);
153 0 : NS_ENSURE_SUCCESS(rv, rv);
154 : }
155 :
156 0 : nsCOMPtr<nsIPresShell> presShell;
157 0 : rv = viewer->GetPresShell(getter_AddRefs(presShell));
158 0 : NS_ENSURE_SUCCESS(rv, rv);
159 0 : nsPresContext* presContext = presShell->GetPresContext();
160 0 : presContext->SetIsGlyph(true);
161 :
162 0 : if (!presShell->DidInitialize()) {
163 0 : nsRect rect = presContext->GetVisibleArea();
164 0 : rv = presShell->Initialize(rect.width, rect.height);
165 0 : NS_ENSURE_SUCCESS(rv, rv);
166 : }
167 :
168 0 : mDocument->FlushPendingNotifications(FlushType::Layout);
169 :
170 0 : nsSMILAnimationController* controller = mDocument->GetAnimationController();
171 0 : if (controller) {
172 0 : controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
173 : }
174 0 : mDocument->ImageTracker()->SetAnimatingState(true);
175 :
176 0 : mViewer = viewer;
177 0 : mPresShell = presShell;
178 0 : mPresShell->AddPostRefreshObserver(this);
179 :
180 0 : return NS_OK;
181 : }
182 :
183 : void
184 0 : gfxSVGGlyphsDocument::DidRefresh()
185 : {
186 0 : mOwner->DidRefresh();
187 0 : }
188 :
189 : /**
190 : * Walk the DOM tree to find all glyph elements and insert them into the lookup
191 : * table
192 : * @param aElem The element to search from
193 : */
194 : void
195 0 : gfxSVGGlyphsDocument::FindGlyphElements(Element *aElem)
196 : {
197 0 : for (nsIContent *child = aElem->GetLastChild(); child;
198 0 : child = child->GetPreviousSibling()) {
199 0 : if (!child->IsElement()) {
200 0 : continue;
201 : }
202 0 : FindGlyphElements(child->AsElement());
203 : }
204 :
205 0 : InsertGlyphId(aElem);
206 0 : }
207 :
208 : /**
209 : * If there exists an SVG glyph with the specified glyph id, render it and return true
210 : * If no such glyph exists, or in the case of an error return false
211 : * @param aContext The thebes aContext to draw to
212 : * @param aGlyphId The glyph id
213 : * @return true iff rendering succeeded
214 : */
215 : void
216 0 : gfxSVGGlyphs::RenderGlyph(gfxContext *aContext, uint32_t aGlyphId,
217 : SVGContextPaint* aContextPaint)
218 : {
219 0 : gfxContextAutoSaveRestore aContextRestorer(aContext);
220 :
221 0 : Element *glyph = mGlyphIdMap.Get(aGlyphId);
222 0 : MOZ_ASSERT(glyph, "No glyph element. Should check with HasSVGGlyph() first!");
223 :
224 0 : AutoSetRestoreSVGContextPaint autoSetRestore(aContextPaint, glyph->OwnerDoc());
225 :
226 0 : nsSVGUtils::PaintSVGGlyph(glyph, aContext);
227 0 : }
228 :
229 : bool
230 0 : gfxSVGGlyphs::GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace,
231 : gfxRect *aResult)
232 : {
233 0 : Element *glyph = mGlyphIdMap.Get(aGlyphId);
234 0 : NS_ASSERTION(glyph, "No glyph element. Should check with HasSVGGlyph() first!");
235 :
236 0 : return nsSVGUtils::GetSVGGlyphExtents(glyph, aSVGToAppSpace, aResult);
237 : }
238 :
239 : Element *
240 0 : gfxSVGGlyphs::GetGlyphElement(uint32_t aGlyphId)
241 : {
242 : Element *elem;
243 :
244 0 : if (!mGlyphIdMap.Get(aGlyphId, &elem)) {
245 0 : elem = nullptr;
246 0 : if (gfxSVGGlyphsDocument *set = FindOrCreateGlyphsDocument(aGlyphId)) {
247 0 : elem = set->GetGlyphElement(aGlyphId);
248 : }
249 0 : mGlyphIdMap.Put(aGlyphId, elem);
250 : }
251 :
252 0 : return elem;
253 : }
254 :
255 : bool
256 0 : gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId)
257 : {
258 0 : return !!GetGlyphElement(aGlyphId);
259 : }
260 :
261 : size_t
262 0 : gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
263 : {
264 : // We don't include the size of mSVGData here, because (depending on the
265 : // font backend implementation) it will either wrap a block of data owned
266 : // by the system (and potentially shared), or a table that's in our font
267 : // table cache and therefore already counted.
268 0 : size_t result = aMallocSizeOf(this)
269 0 : + mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf)
270 0 : + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
271 0 : for (auto iter = mGlyphDocs.ConstIter(); !iter.Done(); iter.Next()) {
272 0 : result += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
273 : }
274 0 : return result;
275 : }
276 :
277 : Element *
278 0 : gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId)
279 : {
280 0 : return mGlyphIdMap.Get(aGlyphId);
281 : }
282 :
283 0 : gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t *aBuffer,
284 : uint32_t aBufLen,
285 0 : gfxSVGGlyphs *aSVGGlyphs)
286 0 : : mOwner(aSVGGlyphs)
287 : {
288 0 : ParseDocument(aBuffer, aBufLen);
289 0 : if (!mDocument) {
290 0 : NS_WARNING("Could not parse SVG glyphs document");
291 0 : return;
292 : }
293 :
294 0 : Element *root = mDocument->GetRootElement();
295 0 : if (!root) {
296 0 : NS_WARNING("Could not parse SVG glyphs document");
297 0 : return;
298 : }
299 :
300 0 : nsresult rv = SetupPresentation();
301 0 : if (NS_FAILED(rv)) {
302 0 : NS_WARNING("Couldn't setup presentation for SVG glyphs document");
303 0 : return;
304 : }
305 :
306 0 : FindGlyphElements(root);
307 : }
308 :
309 0 : gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument()
310 : {
311 0 : if (mDocument) {
312 0 : mDocument->OnPageHide(false, nullptr);
313 : }
314 0 : if (mPresShell) {
315 0 : mPresShell->RemovePostRefreshObserver(this);
316 : }
317 0 : if (mViewer) {
318 0 : mViewer->Close(nullptr);
319 0 : mViewer->Destroy();
320 : }
321 0 : }
322 :
323 : static nsresult
324 0 : CreateBufferedStream(const uint8_t *aBuffer, uint32_t aBufLen,
325 : nsCOMPtr<nsIInputStream> &aResult)
326 : {
327 0 : nsCOMPtr<nsIInputStream> stream;
328 0 : nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
329 : reinterpret_cast<const char *>(aBuffer),
330 0 : aBufLen, NS_ASSIGNMENT_DEPEND);
331 0 : NS_ENSURE_SUCCESS(rv, rv);
332 :
333 0 : nsCOMPtr<nsIInputStream> aBufferedStream;
334 0 : if (!NS_InputStreamIsBuffered(stream)) {
335 0 : rv = NS_NewBufferedInputStream(getter_AddRefs(aBufferedStream), stream, 4096);
336 0 : NS_ENSURE_SUCCESS(rv, rv);
337 0 : stream = aBufferedStream;
338 : }
339 :
340 0 : aResult = stream;
341 :
342 0 : return NS_OK;
343 : }
344 :
345 : nsresult
346 0 : gfxSVGGlyphsDocument::ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen)
347 : {
348 : // Mostly pulled from nsDOMParser::ParseFromStream
349 :
350 0 : nsCOMPtr<nsIInputStream> stream;
351 0 : nsresult rv = CreateBufferedStream(aBuffer, aBufLen, stream);
352 0 : NS_ENSURE_SUCCESS(rv, rv);
353 :
354 0 : nsCOMPtr<nsIURI> uri;
355 0 : nsHostObjectProtocolHandler::GenerateURIString(NS_LITERAL_CSTRING(FONTTABLEURI_SCHEME),
356 : nullptr,
357 0 : mSVGGlyphsDocumentURI);
358 :
359 0 : rv = NS_NewURI(getter_AddRefs(uri), mSVGGlyphsDocumentURI);
360 0 : NS_ENSURE_SUCCESS(rv, rv);
361 :
362 0 : nsCOMPtr<nsIPrincipal> principal = NullPrincipal::Create();
363 :
364 0 : nsCOMPtr<nsIDOMDocument> domDoc;
365 0 : rv = NS_NewDOMDocument(getter_AddRefs(domDoc),
366 0 : EmptyString(), // aNamespaceURI
367 0 : EmptyString(), // aQualifiedName
368 : nullptr, // aDoctype
369 : uri, uri, principal,
370 : false, // aLoadedAsData
371 : nullptr, // aEventObject
372 0 : DocumentFlavorSVG);
373 0 : NS_ENSURE_SUCCESS(rv, rv);
374 :
375 0 : nsCOMPtr<nsIDocument> document(do_QueryInterface(domDoc));
376 0 : if (!document) {
377 0 : return NS_ERROR_FAILURE;
378 : }
379 :
380 0 : nsCOMPtr<nsIChannel> channel;
381 0 : rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
382 : uri,
383 : nullptr, //aStream
384 : principal,
385 : nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
386 : nsIContentPolicy::TYPE_OTHER,
387 0 : SVG_CONTENT_TYPE,
388 0 : UTF8_CHARSET);
389 0 : NS_ENSURE_SUCCESS(rv, rv);
390 :
391 : // Set this early because various decisions during page-load depend on it.
392 0 : document->SetIsBeingUsedAsImage();
393 0 : document->SetReadyStateInternal(nsIDocument::READYSTATE_UNINITIALIZED);
394 :
395 0 : nsCOMPtr<nsIStreamListener> listener;
396 0 : rv = document->StartDocumentLoad("external-resource", channel,
397 : nullptr, // aLoadGroup
398 : nullptr, // aContainer
399 0 : getter_AddRefs(listener),
400 0 : true /* aReset */);
401 0 : if (NS_FAILED(rv) || !listener) {
402 0 : return NS_ERROR_FAILURE;
403 : }
404 :
405 0 : rv = listener->OnStartRequest(channel, nullptr /* aContext */);
406 0 : if (NS_FAILED(rv)) {
407 0 : channel->Cancel(rv);
408 : }
409 :
410 : nsresult status;
411 0 : channel->GetStatus(&status);
412 0 : if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
413 0 : rv = listener->OnDataAvailable(channel, nullptr /* aContext */, stream, 0, aBufLen);
414 0 : if (NS_FAILED(rv)) {
415 0 : channel->Cancel(rv);
416 : }
417 0 : channel->GetStatus(&status);
418 : }
419 :
420 0 : rv = listener->OnStopRequest(channel, nullptr /* aContext */, status);
421 0 : NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
422 :
423 0 : document.swap(mDocument);
424 :
425 0 : return NS_OK;
426 : }
427 :
428 : void
429 0 : gfxSVGGlyphsDocument::InsertGlyphId(Element *aGlyphElement)
430 : {
431 0 : nsAutoString glyphIdStr;
432 : static const uint32_t glyphPrefixLength = 5;
433 : // The maximum glyph ID is 65535 so the maximum length of the numeric part
434 : // is 5.
435 0 : if (!aGlyphElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, glyphIdStr) ||
436 0 : !StringBeginsWith(glyphIdStr, NS_LITERAL_STRING("glyph")) ||
437 0 : glyphIdStr.Length() > glyphPrefixLength + 5) {
438 0 : return;
439 : }
440 :
441 0 : uint32_t id = 0;
442 0 : for (uint32_t i = glyphPrefixLength; i < glyphIdStr.Length(); ++i) {
443 0 : char16_t ch = glyphIdStr.CharAt(i);
444 0 : if (ch < '0' || ch > '9') {
445 0 : return;
446 : }
447 0 : if (ch == '0' && i == glyphPrefixLength) {
448 0 : return;
449 : }
450 0 : id = id * 10 + (ch - '0');
451 : }
452 :
453 0 : mGlyphIdMap.Put(id, aGlyphElement);
454 : }
455 :
456 : size_t
457 0 : gfxSVGGlyphsDocument::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
458 : {
459 0 : return aMallocSizeOf(this)
460 0 : + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf)
461 0 : + mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
462 :
463 : }
|