diff --git a/Gruntfile.js b/Gruntfile.js index 0e477bd..7edef73 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -331,6 +331,57 @@ module.exports = function(grunt) { }] } }, + css_rotate: { + src: 'test/src/*.svg', + dest: 'test/tmp/css_rotate', + options: { + cssRotate: { + odnoklassniki: { + 90: 'odnoklassniki-90', + 180: 'odnoklassniki-180', + 270: 'odnoklassniki-270', + 360: 'odnoklassniki-360', + 0: 'odnoklassniki-0' + }, + single: { + 45: 'odnoklassniki-45', + }, + doesNotExist: { + 90: 'doesNotExist-90' + } + } + } + }, + css_rotate_bootstrap: { + src: 'test/src/*.svg', + dest: 'test/tmp/css_rotate_bootstrap', + options: { + syntax: 'bootstrap', + cssRotate: { + odnoklassniki: { + 90: 'odnoklassniki-90' + }, + doesNotExist: { + 90: 'doesNotExist-90' + } + } + } + }, + css_rotate_less: { + src: 'test/src/*.svg', + dest: 'test/tmp/css_rotate_less', + options: { + stylesheet: 'less', + cssRotate: { + odnoklassniki: { + 90: 'odnoklassniki-90' + }, + doesNotExist: { + 90: 'doesNotExist-90' + } + } + } + } }, nodeunit: { all: ['test/webfont_test.js'] diff --git a/Readme.md b/Readme.md index a354fc9..7420161 100644 --- a/Readme.md +++ b/Readme.md @@ -465,6 +465,21 @@ At compile-time each template will have access to the same context as the compil #### execMaxBuffer If you get stderr maxBuffer exceeded warning message, engine probably logged a lot of warning messages. To see this warnings run grunt in verbose mode `grunt --verbose`. To go over this warning you can try to increase buffer size by this option. Default value is `1024 * 200` +#### cssRotate +Type: `object` Default: `null` + +If you want an existing glyph additionally as rotated variant with an angel between 1 and 259 degrees you can define it here. A new CSS rule is written and CSS transform is used to rotate. + +```javascript +options: { + cssRotate: { + 'name-of-the-existing-glyph': { + 90: 'name-of-the-rotated-glyph' // the object key is the angel in degrees + } + } +} +``` + ### Config Examples #### Simple font generation diff --git a/tasks/templates/bem.css b/tasks/templates/bem.css index 6cce3a5..35c9ebb 100644 --- a/tasks/templates/bem.css +++ b/tasks/templates/bem.css @@ -39,7 +39,13 @@ <% if (stylesheet === 'less') { %> .<%= classPrefix %><%= glyphs[glyphIdx] %> { &:before { - content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>"; + content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";<% if (cssRotate[glyphs[glyphIdx]]) { %> + display: inline-block; + -webkit-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -moz-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -ms-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -o-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg);<% } %> }<% if (ie7) {%> *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#x<%= codepoints[glyphIdx] %>;'); <% } %> @@ -50,5 +56,11 @@ } <% } %> .<%= classPrefix %><%= glyphs[glyphIdx] %>:before { - content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>"; + content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";<% if (cssRotate[glyphs[glyphIdx]]) { %> + display: inline-block; + -webkit-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -moz-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -ms-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -o-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg);<% } %> }<% } } } %> diff --git a/tasks/templates/bootstrap.css b/tasks/templates/bootstrap.css index 83c979a..fcfe75f 100644 --- a/tasks/templates/bootstrap.css +++ b/tasks/templates/bootstrap.css @@ -96,7 +96,13 @@ li[class*=" <%= classPrefix %>"].<%= classPrefix %>large:before { <% if (stylesheet === 'less') { %> .<%= classPrefix %><%= glyphs[glyphIdx] %> { &:before { - content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>"; + content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";<% if (cssRotate[glyphs[glyphIdx]]) { %> + display: inline-block; + -webkit-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -moz-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -ms-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -o-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg);<% } %> } <% if (ie7) {%> *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#x<%= codepoints[glyphIdx] %>;'); @@ -107,6 +113,12 @@ li[class*=" <%= classPrefix %>"].<%= classPrefix %>large:before { } <% } %> .<%= classPrefix %><%= glyphs[glyphIdx] %>:before { - content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>"; + content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";<% if (cssRotate[glyphs[glyphIdx]]) { %> + display: inline-block; + -webkit-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -moz-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -ms-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + -o-transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg); + transform: rotate(<%= cssRotate[glyphs[glyphIdx]] %>deg);<% } %> }<% } %> <% } } %> diff --git a/tasks/webfont.js b/tasks/webfont.js index 5070943..fd1020f 100755 --- a/tasks/webfont.js +++ b/tasks/webfont.js @@ -112,7 +112,8 @@ module.exports = function(grunt) { cache: options.cache || path.join(__dirname, '..', '.cache'), callback: options.callback, customOutputs: options.customOutputs, - execMaxBuffer: options.execMaxBuffer || 1024 * 200 + execMaxBuffer: options.execMaxBuffer || 1024 * 200, + cssRotate: options.cssRotate || {} }; o = _.extend(o, { @@ -332,6 +333,38 @@ module.exports = function(grunt) { }); o.fontRawSrcs = fontSrcs; + if (typeof o.cssRotate === 'object' && Object.keys(o.cssRotate).length > 0) { + var cssRotate = {}; + for (var glyph in o.cssRotate) { + var idx = o.glyphs.indexOf(glyph); + if (idx === -1) { + // we can only rotate an existing glyph + continue; + } + if (typeof o.cssRotate[glyph] === 'object' && Object.keys(o.cssRotate[glyph]).length > 0) { + var appendIdx = idx+1; + for (var deg in o.cssRotate[glyph]) { + if (!/^\d{1,3}$/.test(deg)) { + continue; + } + if (typeof deg !== 'number') { + deg = parseInt(deg, 10); + } + if (deg >= 360 || deg === 0 || o.glyphs.indexOf(o.cssRotate[glyph][deg]) !== -1) { + // existing glyph have priority before rotated and an angular degree lower than 1 or greather than 359 makes no sense + continue; + } + // append the rotated glyph direct after the source + o.glyphs.splice(appendIdx, 0, o.cssRotate[glyph][deg]); + o.codepoints[o.cssRotate[glyph][deg]] = o.codepoints[glyph]; + cssRotate[o.cssRotate[glyph][deg]] = deg; + appendIdx++; + } + } + } + o.cssRotate = cssRotate; + } + // Convert codepoints to array of strings var codepoints = []; _.each(o.glyphs, function(name) { @@ -415,11 +448,15 @@ module.exports = function(grunt) { var htmlStyles; - // Prepare relative font paths for injection into @font-face refs in HTML - var relativeRe = new RegExp(_s.escapeRegExp(o.relativeFontPath), 'g'); - var htmlRelativeFontPath = normalizePath(path.relative(o.destHtml, o.relativeFontPath)); - var _fontSrc1 = o.fontSrc1.replace(relativeRe, htmlRelativeFontPath); - var _fontSrc2 = o.fontSrc2.replace(relativeRe, htmlRelativeFontPath); + var _fontSrc1 = o.fontSrc1; + var _fontSrc2 = o.fontSrc2; + if (o.relativeFontPath) { + // Prepare relative font paths for injection into @font-face refs in HTML + var relativeRe = new RegExp(_s.escapeRegExp(o.relativeFontPath), 'g'); + var htmlRelativeFontPath = normalizePath(path.relative(o.destHtml, o.relativeFontPath)); + _fontSrc1 = _fontSrc1.replace(relativeRe, htmlRelativeFontPath); + _fontSrc2 = _fontSrc2.replace(relativeRe, htmlRelativeFontPath); + } _.extend(context, { fontSrc1: _fontSrc1, diff --git a/test/webfont_test.js b/test/webfont_test.js index f8c00ab..1581ccd 100644 --- a/test/webfont_test.js +++ b/test/webfont_test.js @@ -61,6 +61,9 @@ exports.webfont = { 'Second EOT declaration.' ); + // the font is not embeded and we have no relative path. it should be at src:url("icons.eot") + test.ok(/@font-face\{[^}]+src:url\("icons.eot"\)[^}]+\}/.test(html.replace(/\s/g, '')), 'Font should be declared with src:url()'); + // Every SVG file should have corresponding entry in CSS and HTML files svgs.forEach(function(file, index) { var id = path.basename(file, '.svg'); @@ -887,6 +890,46 @@ exports.webfont = { // Files should render with custom context variables test.ok(fs.existsSync('test/tmp/custom_output/context-test.html')); + test.done(); + }, + + css_rotate: function(test) { + var css = grunt.file.read('test/tmp/css_rotate/icons.css').replace(/\s/g, ''); + + test.ok(/\.icon_odnoklassniki-90:before\{[^}]+transform:rotate\(90deg\);\}/.test(css), 'glyph odnoklassniki-90 with deg 90 should be in the CSS file'); + test.ok(/\.icon_odnoklassniki-180:before\{[^}]+transform:rotate\(180deg\);\}/.test(css), 'glyph odnoklassniki-180 with deg 180 should be in the CSS file'); + test.ok(/\.icon_odnoklassniki-270:before\{[^}]+transform:rotate\(270deg\);\}/.test(css), 'glyph odnoklassniki-270 with deg 270 should be in the CSS file'); + + test.ok(!/\.icon_odnoklassniki-270:before\{[^}]+transform:rotate\(271deg\);\}/.test(css), 'glyph odnoklassniki-270 with deg 271 should not be in the CSS file'); + + ['odnoklassniki-360', 'odnoklassniki-0', 'doesNotExist', 'doesNotExist-90'].forEach(function(glyph) { + test.ok(!find(css, '.icon_'+glyph), 'glyph '+glyph+' should not be in the CSS file'); + }); + + test.done(); + }, + + css_rotate_bootstrap: function(test) { + var css = grunt.file.read('test/tmp/css_rotate_bootstrap/icons.css').replace(/\s/g, ''); + + test.ok(/\.icon-odnoklassniki-90:before\{[^}]+transform:rotate\(90deg\);\}/.test(css), 'glyph odnoklassniki-90 with deg 90 should be in the CSS file'); + + ['doesNotExist', 'doesNotExist-90'].forEach(function(glyph) { + test.ok(!find(css, '.icon-'+glyph), 'glyph '+glyph+' should not be in the CSS file'); + }); + + test.done(); + }, + + css_rotate_less: function(test) { + var less = grunt.file.read('test/tmp/css_rotate_less/icons.less').replace(/\s/g, ''); + + test.ok(/\.icon_odnoklassniki-90\{&:before\{[^}]+transform:rotate\(90deg\);\}\}/.test(less), 'glyph odnoklassniki-90 with deg 90 should be in the LESS file'); + + ['doesNotExist', 'doesNotExist-90'].forEach(function(glyph) { + test.ok(!find(less, '.icon_'+glyph), 'glyph '+glyph+' should not be in the LESS file'); + }); + test.done(); }