gfx/thebes/gfxSVGGlyphs.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:88db5628034d
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 }

mercurial