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