Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit e494c15

Browse files
programmistThomasBurleson
authored andcommitted
fix(icon): Allow using data URLs
Refs #6531. Fixes #4126. Closes #7547
1 parent 3e35ef0 commit e494c15

File tree

4 files changed

+98
-13
lines changed

4 files changed

+98
-13
lines changed

src/components/icon/demoLoadSvgIconsFromUrl/index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,27 @@
1212
<md-icon md-svg-src="{{ getAndroid() }}" class="s36" aria-label="Android "></md-icon>
1313
<md-icon md-svg-src="img/icons/addShoppingCart.svg" class="s48" aria-label="Cart" ></md-icon>
1414
</p>
15+
16+
<p>Use data URLs (base64 or un-encoded):</p>
17+
<p>
18+
<md-icon
19+
md-svg-src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGcgaWQ9ImNha2UiPjxwYXRoIGQ9Ik0xMiA2YzEuMTEgMCAyLS45IDItMiAwLS4zOC0uMS0uNzMtLjI5LTEuMDNMMTIgMGwtMS43MSAyLjk3Yy0uMTkuMy0uMjkuNjUtLjI5IDEuMDMgMCAxLjEuOSAyIDIgMnptNC42IDkuOTlsLTEuMDctMS4wNy0xLjA4IDEuMDdjLTEuMyAxLjMtMy41OCAxLjMxLTQuODkgMGwtMS4wNy0xLjA3LTEuMDkgMS4wN0M2Ljc1IDE2LjY0IDUuODggMTcgNC45NiAxN2MtLjczIDAtMS40LS4yMy0xLjk2LS42MVYyMWMwIC41NS40NSAxIDEgMWgxNmMuNTUgMCAxLS40NSAxLTF2LTQuNjFjLS41Ni4zOC0xLjIzLjYxLTEuOTYuNjEtLjkyIDAtMS43OS0uMzYtMi40NC0xLjAxek0xOCA5aC01VjdoLTJ2Mkg2Yy0xLjY2IDAtMyAxLjM0LTMgM3YxLjU0YzAgMS4wOC44OCAxLjk2IDEuOTYgMS45Ni41MiAwIDEuMDItLjIgMS4zOC0uNTdsMi4xNC0yLjEzIDIuMTMgMi4xM2MuNzQuNzQgMi4wMy43NCAyLjc3IDBsMi4xNC0yLjEzIDIuMTMgMi4xM2MuMzcuMzcuODYuNTcgMS4zOC41NyAxLjA4IDAgMS45Ni0uODggMS45Ni0xLjk2VjEyQzIxIDEwLjM0IDE5LjY2IDkgMTggOXoiLz48L2c+PC9zdmc+"
20+
class="s24"
21+
aria-label="Cake">
22+
</md-icon>
23+
24+
<md-icon
25+
md-svg-src="data:image/svg+xml;base64,{{ getAndroidEncoded() }}"
26+
class="s36"
27+
aria-label="Android">
28+
</md-icon>
29+
30+
<!-- un-encoded -->
31+
<md-icon
32+
md-svg-src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g id="add-shopping-cart"><path d="M11 9h2V6h3V4h-3V1h-2v3H8v2h3v3zm-4 9c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2zm-9.83-3.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.86-7.01L19.42 4h-.01l-1.1 2-2.76 5H8.53l-.13-.27L6.16 6l-.95-2-.94-2H1v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.13 0-.25-.11-.25-.25z"/></g></svg>'
33+
class="s48"
34+
aria-label="Cart">
35+
</md-icon>
36+
</p>
1537
</div>
1638

src/components/icon/demoLoadSvgIconsFromUrl/script.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@ angular.module('appDemoSvgIcons', ['ngMaterial'])
66
$scope.getAndroid = function() {
77
return 'img/icons/android.svg';
88
}
9+
/* Returns base64 encoded SVG. */
10+
$scope.getAndroidEncoded = function() {
11+
return 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGcgaWQ9ImFuZHJvaWQiPjxwYXRoIGQ9Ik02IDE4YzAgLjU1LjQ1IDEgMSAxaDF2My41YzAgLjgzLjY3IDEuNSAxLjUgMS41czEuNS0uNjcgMS41LTEuNVYxOWgydjMuNWMwIC44My42NyAxLjUgMS41IDEuNXMxLjUtLjY3IDEuNS0xLjVWMTloMWMuNTUgMCAxLS40NSAxLTFWOEg2djEwek0zLjUgOEMyLjY3IDggMiA4LjY3IDIgOS41djdjMCAuODMuNjcgMS41IDEuNSAxLjVTNSAxNy4zMyA1IDE2LjV2LTdDNSA4LjY3IDQuMzMgOCAzLjUgOHptMTcgMGMtLjgzIDAtMS41LjY3LTEuNSAxLjV2N2MwIC44My42NyAxLjUgMS41IDEuNXMxLjUtLjY3IDEuNS0xLjV2LTdjMC0uODMtLjY3LTEuNS0xLjUtMS41em0tNC45Ny01Ljg0bDEuMy0xLjNjLjItLjIuMi0uNTEgMC0uNzEtLjItLjItLjUxLS4yLS43MSAwbC0xLjQ4IDEuNDhDMTMuODUgMS4yMyAxMi45NSAxIDEyIDFjLS45NiAwLTEuODYuMjMtMi42Ni42M0w3Ljg1LjE1Yy0uMi0uMi0uNTEtLjItLjcxIDAtLjIuMi0uMi41MSAwIC43MWwxLjMxIDEuMzFDNi45NyAzLjI2IDYgNS4wMSA2IDdoMTJjMC0xLjk5LS45Ny0zLjc1LTIuNDctNC44NHpNMTAgNUg5VjRoMXYxem01IDBoLTFWNGgxdjF6Ii8+PC9nPjwvc3ZnPg==';
12+
}
913
});

src/components/icon/icon.spec.js

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,20 @@ describe('mdIcon directive', function() {
177177
return {
178178
then: function(fn) {
179179
switch(id) {
180-
case 'android' : fn('<svg><g id="android"></g></svg>');
181-
case 'cake' : fn('<svg><g id="cake"></g></svg>');
182-
case 'android.svg' : fn('<svg><g id="android"></g></svg>');
183-
case 'cake.svg' : fn('<svg><g id="cake"></g></svg>');
184-
case 'image:android': fn('');
180+
case 'android' : fn('<svg><g id="android"></g></svg>');
181+
break;
182+
case 'cake' : fn('<svg><g id="cake"></g></svg>');
183+
break;
184+
case 'android.svg' : fn('<svg><g id="android"></g></svg>');
185+
break;
186+
case 'cake.svg' : fn('<svg><g id="cake"></g></svg>');
187+
break;
188+
case 'image:android' : fn('');
189+
break;
190+
default :
191+
if (/^data:/.test(id)) {
192+
fn(window.atob(id.split(',')[1]));
193+
}
185194
}
186195
}
187196
}
@@ -240,6 +249,17 @@ describe('mdIcon directive', function() {
240249
expect(el.html()).toEqual('');
241250
}));
242251

252+
describe('with a data URL', function() {
253+
it('should set mdSvgSrc from a function expression', inject(function() {
254+
var svgData = '<svg><g><circle r="50" cx="100" cy="100"></circle></g></svg>';
255+
$scope.getData = function() {
256+
return 'data:image/svg+xml;base64,' + window.btoa(svgData);
257+
}
258+
el = make('<md-icon md-svg-src="{{ getData() }}"></md-icon>');
259+
$scope.$digest();
260+
expect(el[0].innerHTML).toEqual(svgData);
261+
}));
262+
})
243263
});
244264

245265
describe('with ARIA support', function() {
@@ -419,6 +439,29 @@ describe('mdIcon service', function() {
419439
$scope.$digest();
420440
});
421441

442+
describe('and the URL is a data URL', function() {
443+
var svgData = '<svg><g><circle r="50" cx="100" cy="100"></circle></g></svg>';
444+
445+
describe('and the data is base64 encoded', function() {
446+
it('should return correct SVG markup', function() {
447+
var data = 'data:image/svg+xml;base64,' + btoa(svgData);
448+
$mdIcon(data).then(function(el) {
449+
expect(el.outerHTML).toEqual( updateDefaults(svgData) );
450+
})
451+
$scope.$digest();
452+
});
453+
});
454+
455+
describe('and the data is un-encoded', function() {
456+
it('should return correct SVG markup', function() {
457+
var data = 'data:image/svg+xml,' + svgData;
458+
$mdIcon(data).then(function(el) {
459+
expect(el.outerHTML).toEqual( updateDefaults(svgData) );
460+
})
461+
$scope.$digest();
462+
});
463+
});
464+
});
422465
});
423466

424467
describe('icon set URL is not found', function() {

src/components/icon/js/iconService.js

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@
375375
/* @ngInject */
376376
function MdIconService(config, $http, $q, $log, $templateCache) {
377377
var iconCache = {};
378-
var urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;
378+
var urlRegex = /[-\w@:%\+.~#?&//=]{2,}\.[a-z]{2,4}\b(\/[-\w@:%\+.~#?&//=]*)?/i;
379+
var dataUrlRegex = /^data:image\/svg\+xml[\s*;\w\-\=]*?(base64)?,(.*)$/i;
379380

380381
Icon.prototype = { clone : cloneSVG, prepare: prepareAndStyle };
381382
getIcon.fontSet = findRegisteredFontSet;
@@ -392,8 +393,8 @@
392393
// If already loaded and cached, use a clone of the cached icon.
393394
// Otherwise either load by URL, or lookup in the registry and then load by URL, and cache.
394395

395-
if ( iconCache[id] ) return $q.when( iconCache[id].clone() );
396-
if ( urlRegex.test(id) ) return loadByURL(id).then( cacheIcon(id) );
396+
if ( iconCache[id] ) return $q.when( iconCache[id].clone() );
397+
if ( urlRegex.test(id) || dataUrlRegex.test(id) ) return loadByURL(id).then( cacheIcon(id) );
397398
if ( id.indexOf(':') == -1 ) id = '$default:' + id;
398399

399400
var load = config[id] ? loadByID : loadFromIconSet;
@@ -481,11 +482,26 @@
481482
* Extract the data for later conversion to Icon
482483
*/
483484
function loadByURL(url) {
484-
return $http
485-
.get(url, { cache: $templateCache })
486-
.then(function(response) {
487-
return angular.element('<div>').append(response.data).find('svg')[0];
488-
}).catch(announceNotFound);
485+
/* Load the icon from embedded data URL. */
486+
function loadByDataUrl(url) {
487+
var results = dataUrlRegex.exec(url);
488+
var isBase64 = /base64/i.test(url);
489+
var data = isBase64 ? window.atob(results[2]) : results[2];
490+
return $q.when(angular.element(data)[0]);
491+
}
492+
493+
/* Load the icon by URL using HTTP. */
494+
function loadByHttpUrl(url) {
495+
return $http
496+
.get(url, { cache: $templateCache })
497+
.then(function(response) {
498+
return angular.element('<div>').append(response.data).find('svg')[0];
499+
}).catch(announceNotFound);
500+
}
501+
502+
return dataUrlRegex.test(url)
503+
? loadByDataUrl(url)
504+
: loadByHttpUrl(url);
489505
}
490506

491507
/**

0 commit comments

Comments
 (0)