assetgraph-sprite
Advanced tools
Comparing version 2.0.0 to 3.0.0
@@ -1,12 +0,10 @@ | ||
var extend = require('lodash.assign'); | ||
exports.pack = function (imageInfos) { | ||
var previousRightPadding = 0, | ||
packingData = { | ||
width: 0, | ||
height: 0, | ||
imageInfos: [] | ||
}; | ||
imageInfos.forEach(function (existingImageInfo) { | ||
var imageInfo = extend({}, existingImageInfo); | ||
let previousRightPadding = 0; | ||
const packingData = { | ||
width: 0, | ||
height: 0, | ||
imageInfos: [] | ||
}; | ||
for (const existingImageInfo of imageInfos) { | ||
const imageInfo = {...existingImageInfo}; | ||
packingData.width += Math.max(previousRightPadding, imageInfo.padding[3]); | ||
@@ -19,4 +17,4 @@ imageInfo.x = packingData.width; | ||
packingData.imageInfos.push(imageInfo); | ||
}); | ||
} | ||
return packingData; | ||
}; |
// Install getters for all packers in this directory: | ||
require('fs').readdirSync(__dirname).forEach(function (fileName) { | ||
for (const fileName of require('fs').readdirSync(__dirname)) { | ||
if (/\.js$/.test(fileName) && fileName !== 'index.js') { | ||
exports.__defineGetter__(fileName.replace(/\.js$/, ''), function () { | ||
return require('./' + fileName); | ||
Object.defineProperty(exports, fileName.replace(/\.js$/, ''), { | ||
get: () => require('./' + fileName) | ||
}); | ||
} | ||
}); | ||
} |
@@ -10,4 +10,2 @@ /* | ||
var extend = require('lodash.assign'); | ||
function findCoords(node, width, height) { | ||
@@ -66,15 +64,15 @@ // If we are not at a leaf then go deeper | ||
config = config || {}; | ||
var root = { | ||
x: 0, | ||
y: 0, | ||
width: config.maxWidth || 999999, | ||
height: config.maxHeight || 999999 | ||
}; | ||
const root = { | ||
x: 0, | ||
y: 0, | ||
width: config.maxWidth || 999999, | ||
height: config.maxHeight || 999999 | ||
}; | ||
// Sort by area, descending: | ||
imageInfos.sort(function (a, b) { | ||
return (b.width * b.height) - (a.width * a.height); | ||
}); | ||
imageInfos.sort( | ||
(a, b) => (b.width * b.height) - (a.width * a.height) | ||
); | ||
var packingData = { | ||
const packingData = { | ||
imageInfos: [], | ||
@@ -85,9 +83,9 @@ width: 0, | ||
imageInfos.forEach(function (existingImageInfo) { | ||
var imageInfo = extend({}, existingImageInfo); | ||
if (imageInfo.padding && imageInfo.padding.some(function (v) {return v > 0;})) { | ||
for (const existingImageInfo of imageInfos) { | ||
const imageInfo = {...existingImageInfo}; | ||
if (imageInfo.padding && imageInfo.padding.some(v => v > 0)) { | ||
throw new Error('jimScott.pack: Sprite padding not supported'); | ||
} | ||
// Perform the search | ||
var coords = findCoords(root, imageInfo.width, imageInfo.height); | ||
const coords = findCoords(root, imageInfo.width, imageInfo.height); | ||
// If fitted then recalculate the used dimensions | ||
@@ -100,6 +98,6 @@ if (coords) { | ||
} | ||
extend(imageInfo, coords); | ||
Object.assign(imageInfo, coords); | ||
packingData.imageInfos.push(imageInfo); | ||
}); | ||
} | ||
return packingData; | ||
}; |
@@ -1,7 +0,7 @@ | ||
var packers = ['./horizontal', './vertical', './jimScott'].map(require); | ||
const packers = ['./horizontal', './vertical', './jimScott'].map(require); | ||
exports.pack = function (imageInfos) { | ||
var bestPacking; | ||
packers.forEach(function (packer) { | ||
var packing; | ||
exports.pack = imageInfos => { | ||
let bestPacking; | ||
for (const packer of packers) { | ||
let packing; | ||
try { | ||
@@ -11,3 +11,3 @@ packing = packer.pack(imageInfos); | ||
// The Jim Scott packer doesn't support sprite padding, just skip to the next packer if we get an exception. | ||
return; | ||
continue; | ||
} | ||
@@ -17,4 +17,4 @@ if (!bestPacking || (packing.width * packing.height) < (bestPacking.width * bestPacking.height)) { | ||
} | ||
}); | ||
} | ||
return bestPacking; | ||
}; |
@@ -1,12 +0,11 @@ | ||
var extend = require('lodash.assign'); | ||
exports.pack = function (imageInfos) { | ||
let previousBottomPadding = 0; | ||
const packingData = { | ||
width: 0, | ||
height: 0, | ||
imageInfos: [] | ||
}; | ||
exports.pack = function (imageInfos) { | ||
var previousBottomPadding = 0, | ||
packingData = { | ||
width: 0, | ||
height: 0, | ||
imageInfos: [] | ||
}; | ||
imageInfos.forEach(function (existingImageInfo) { | ||
var imageInfo = extend({}, existingImageInfo); | ||
for (const existingImageInfo of imageInfos) { | ||
const imageInfo = {...existingImageInfo}; | ||
packingData.height += Math.max(previousBottomPadding, imageInfo.padding[0]); | ||
@@ -19,4 +18,4 @@ imageInfo.y = packingData.height; | ||
packingData.imageInfos.push(imageInfo); | ||
}); | ||
} | ||
return packingData; | ||
}; |
@@ -1,9 +0,5 @@ | ||
var URL = require('url'), | ||
queryString = require('querystring'), | ||
extend = require('lodash.assign'), | ||
values = require('lodash.values'), | ||
seq = require('seq'), | ||
passError = require('passerror'), | ||
packers = require('./packers'), | ||
Canvas; | ||
const queryString = require('querystring'); | ||
const { promisify } = require('util'); | ||
const packers = require('./packers'); | ||
let Canvas; | ||
@@ -16,39 +12,40 @@ try { | ||
function getProperties(container, propertyName) { | ||
return container.nodes.filter(function (node) { | ||
return node.prop === propertyName; | ||
}); | ||
return container.nodes.filter( | ||
node => node.prop === propertyName | ||
); | ||
} | ||
function getCanvasImageFromImageAsset(imageAsset, cb) { | ||
var canvasImage = new Canvas.Image(); | ||
canvasImage.onerror = function (err) { | ||
process.nextTick(function () { | ||
async function getCanvasImageFromImageAsset(imageAsset) { | ||
const canvasImage = new Canvas.Image(); | ||
await new Promise((resolve, reject) => { | ||
canvasImage.onerror = err => { | ||
err.message += ' (' + imageAsset.urlOrDescription + ')'; | ||
cb(err); | ||
}); | ||
}; | ||
canvasImage.onload = function () { | ||
process.nextTick(function () { | ||
cb(null, canvasImage); | ||
}); | ||
}; | ||
canvasImage.src = imageAsset.rawSrc; | ||
reject(err); | ||
}; | ||
canvasImage.onload = resolve; | ||
canvasImage.src = imageAsset.rawSrc; | ||
}); | ||
return canvasImage; | ||
} | ||
function getImageAssetFromCanvas(canvas, assetType, assetGraph, cb) { | ||
async function getImageAssetFromCanvas(canvas, assetType, assetGraph) { | ||
if (assetType === 'Png') { | ||
canvas.toBuffer(passError(cb, function (pngBuffer) { | ||
cb(null, new assetGraph.Png({ | ||
rawSrc: pngBuffer | ||
})); | ||
})); | ||
const rawSrc = await promisify(cb => canvas.toBuffer(cb))(); | ||
return { | ||
type: 'Png', | ||
rawSrc | ||
}; | ||
} else { | ||
var jpegChunks = []; | ||
canvas.createJPEGStream().on('data', function (chunk) { | ||
jpegChunks.push(chunk); | ||
}).on('end', function () { | ||
cb(null, new assetGraph.Jpeg({ | ||
rawSrc: Buffer.concat(jpegChunks) | ||
})); | ||
}).on('error', cb); | ||
const rawSrc = await promisify(cb => { | ||
const jpegChunks = []; | ||
canvas.createJPEGStream().on('data', function (chunk) { | ||
jpegChunks.push(chunk); | ||
}) | ||
.on('end', () => cb(null, Buffer.concat(jpegChunks))) | ||
.on('error', cb); | ||
})(); | ||
return { | ||
type: 'Jpeg', | ||
rawSrc | ||
}; | ||
} | ||
@@ -81,9 +78,9 @@ } | ||
return padding.map(function (size) { | ||
return Math.max(size, Math.max(asset.devicePixelRatio) - 1); | ||
}); | ||
return padding.map( | ||
size => Math.max(size, Math.max(asset.devicePixelRatio) - 1) | ||
); | ||
} | ||
function getRelationSpriteInfoFromIncomingRelation(incomingRelation) { | ||
var parsedQueryString = queryString.parse(incomingRelation.href.match(/\?([^#]*)/)[1]); | ||
const parsedQueryString = queryString.parse(incomingRelation.href.match(/\?([^#]*)/)[1]); | ||
return { | ||
@@ -98,8 +95,10 @@ groupName: parsedQueryString.sprite || 'default', | ||
function extractInfoFromCssRule(cssRule, propertyNamePrefix) { | ||
var info = {}; | ||
cssRule.walkDecls(function (decl) { | ||
if (!propertyNamePrefix || decl.prop.indexOf(propertyNamePrefix) === 0) { | ||
var keyName = decl.prop.substr(propertyNamePrefix.length).replace(/-([a-z])/g, function ($0, $1) { | ||
return $1.toUpperCase(); | ||
}); | ||
const info = {}; | ||
cssRule.walkDecls(decl => { | ||
if (!propertyNamePrefix || decl.prop.startsWith(propertyNamePrefix)) { | ||
const keyName = decl.prop | ||
.substr(propertyNamePrefix.length) | ||
.replace( | ||
/-([a-z])/g, ($0, $1) => $1.toUpperCase() | ||
); | ||
info[keyName] = decl.value.replace(/^([\'\"])(.*)\1$/, '$2'); | ||
@@ -112,27 +111,27 @@ } | ||
module.exports = function () { | ||
return function spriteBackgroundImages(assetGraph, cb) { | ||
module.exports = () => { | ||
return async function spriteBackgroundImages(assetGraph) { | ||
if (!Canvas) { | ||
assetGraph.emit('warn', new Error('assetgraph-sprite: Canvas not found, skipping')); | ||
return cb(); | ||
assetGraph.warn(new Error('assetgraph-sprite: Canvas not found, skipping')); | ||
return; | ||
} | ||
// Waiting for https://github.com/LearnBoost/node-canvas/issues/52 | ||
var cairoVersion = Canvas.cairoVersion.split('.').map(function (str) { | ||
return parseInt(str, 10); | ||
}); | ||
const cairoVersion = Canvas.cairoVersion.split('.').map( | ||
str => parseInt(str, 10) | ||
); | ||
if (cairoVersion[0] < 1 || cairoVersion[1] < 10) { | ||
assetGraph.emit('warn', new Error('assetgraph-sprite: Cannot create sprites due to missing canvas.getContext("2d").drawImage() support. Please compile node-canvas with Cairo version 1.10.0 or above.')); | ||
return cb(); | ||
assetGraph.warn(new Error('assetgraph-sprite: Cannot create sprites due to missing canvas.getContext("2d").drawImage() support. Please compile node-canvas with Cairo version 1.10.0 or above.')); | ||
return; | ||
} | ||
var spriteGroups = {}; | ||
const spriteGroups = {}; | ||
// Find sprite annotated images and create a data structure with their information | ||
assetGraph.findRelations({type: 'CssImage', to: {isImage: true}, href: /\?(?:|[^#]*&)sprite(?:[=&#]|$)/}).forEach(function (relation) { | ||
var relationSpriteInfo = getRelationSpriteInfoFromIncomingRelation(relation), | ||
spriteGroup = (spriteGroups[relationSpriteInfo.groupName] = spriteGroups[relationSpriteInfo.groupName] || { | ||
imageInfosById: {} | ||
}), | ||
imageInfo = spriteGroup.imageInfosById[relationSpriteInfo.asset.id]; | ||
for (const relation of assetGraph.findRelations({type: 'CssImage', to: {isImage: true}, href: /\?(?:|[^#]*&)sprite(?:[=&#]|$)/})) { | ||
const relationSpriteInfo = getRelationSpriteInfoFromIncomingRelation(relation); | ||
const spriteGroup = (spriteGroups[relationSpriteInfo.groupName] = spriteGroups[relationSpriteInfo.groupName] || { | ||
imageInfosById: {} | ||
}); | ||
const imageInfo = spriteGroup.imageInfosById[relationSpriteInfo.asset.id]; | ||
if (!imageInfo) { | ||
@@ -147,9 +146,9 @@ relationSpriteInfo.incomingRelations = [relation]; | ||
} | ||
}); | ||
} | ||
var redefinitionErrors = {}; | ||
const redefinitionErrors = {}; | ||
// Extract sprite grouping information va -sprite- prefixed properties in stylesheets | ||
assetGraph.findAssets({type: 'Css'}).forEach(function (cssAsset) { | ||
cssAsset.eachRuleInParseTree(function (cssRule) { | ||
for (const cssAsset of assetGraph.findAssets({type: 'Css', isLoaded: true})) { | ||
cssAsset.eachRuleInParseTree(cssRule => { | ||
if (cssRule.type !== 'rule') { | ||
@@ -159,6 +158,6 @@ return; | ||
if (getProperties(cssRule, '-sprite-selector-for-group').length > 0) { | ||
var spriteInfo = extractInfoFromCssRule(cssRule, '-sprite-'), | ||
spriteGroupName = spriteInfo.selectorForGroup; | ||
const spriteInfo = extractInfoFromCssRule(cssRule, '-sprite-'); | ||
const spriteGroupName = spriteInfo.selectorForGroup; | ||
if (spriteInfo.location) { | ||
var matchLocation = spriteInfo.location.match(/^url\((['"]|)(.*?)\1\)$/); | ||
const matchLocation = spriteInfo.location.match(/^url\((['"]|)(.*?)\1\)$/); | ||
if (matchLocation) { | ||
@@ -168,3 +167,3 @@ spriteInfo.location = matchLocation[2]; | ||
} | ||
var group = spriteGroups[spriteGroupName]; | ||
const group = spriteGroups[spriteGroupName]; | ||
if (group) { | ||
@@ -176,6 +175,6 @@ if (!Array.isArray(group.placeHolders)) { | ||
if (group.placeHolders.length > 0) { | ||
var err; | ||
let err; | ||
if (Object.keys(group.placeHolders[0]).every(function (key) { | ||
if (['asset', 'cssRule'].indexOf(key) !== -1) { | ||
if (Object.keys(group.placeHolders[0]).every(key => { | ||
if (['asset', 'cssRule'].includes(key)) { | ||
return true; | ||
@@ -191,18 +190,20 @@ } | ||
group.placeHolders.push(extend(spriteInfo, { | ||
group.placeHolders.push({ | ||
...spriteInfo, | ||
asset: cssAsset, | ||
cssRule: cssRule | ||
})); | ||
cssRule | ||
}); | ||
} else { | ||
err = new Error('assetgraph-sprite: Multiple differing definitions of ' + spriteGroupName + ' sprite.\nThis is most likely an error.'); | ||
err = new Error(`assetgraph-sprite: Multiple differing definitions of ${spriteGroupName} sprite.\nThis is most likely an error.`); | ||
err.asset = cssAsset; | ||
assetGraph.emit('warn', err); | ||
assetGraph.warn(err); | ||
} | ||
} else { | ||
group.placeHolders.push(extend(spriteInfo, { | ||
group.placeHolders.push({ | ||
...spriteInfo, | ||
asset: cssAsset, | ||
cssRule: cssRule | ||
})); | ||
cssRule | ||
}); | ||
} | ||
@@ -213,262 +214,260 @@ | ||
}); | ||
}); | ||
} | ||
Object.keys(redefinitionErrors).forEach(function (spriteGroupName) { | ||
var message = [ | ||
'assetgraph-sprite: Multiple identical definitions of ' + spriteGroupName + ' sprite.', | ||
for (const spriteGroupName of Object.keys(redefinitionErrors)) { | ||
const message = [ | ||
`assetgraph-sprite: Multiple identical definitions of ${spriteGroupName} sprite.`, | ||
'This might happen if you duplicate CSS using a preprocessor.', | ||
redefinitionErrors[spriteGroupName].map(function (asset) { | ||
return '\t' + asset.urlOrDescription; | ||
}).join('\n') | ||
...redefinitionErrors[spriteGroupName].map( | ||
asset => ' ' + asset.urlOrDescription | ||
) | ||
].join('\n'); | ||
var err = new Error(message); | ||
const err = new Error(message); | ||
assetGraph.emit('info', err); | ||
}); | ||
assetGraph.info(err); | ||
} | ||
seq(Object.keys(spriteGroups)) | ||
.seqEach(function (spriteGroupName) { | ||
var callback = this, | ||
spriteGroup = spriteGroups[spriteGroupName], | ||
imageInfos = values(spriteGroup.imageInfosById), | ||
spriteInfo = spriteGroup.placeHolders && spriteGroup.placeHolders[0] || {}, | ||
packingData; | ||
for (const spriteGroupName of Object.keys(spriteGroups)) { | ||
const spriteGroup = spriteGroups[spriteGroupName]; | ||
let imageInfos = Object.values(spriteGroup.imageInfosById); | ||
const spriteInfo = spriteGroup.placeHolders && spriteGroup.placeHolders[0] || {}; | ||
seq(imageInfos) | ||
.parMap(function (imageInfo) { | ||
getCanvasImageFromImageAsset(imageInfo.asset, function (err, canvasImage) { | ||
// For some reason parMap swallows errors! | ||
// Rewrite to use promises or async and get rid of this: | ||
if (err) { | ||
callback(err); | ||
} else { | ||
this(null, canvasImage); | ||
} | ||
}.bind(this)); | ||
}) | ||
.seqEach(function (canvasImage, i) { | ||
extend(imageInfos[i], { | ||
canvasImage: canvasImage, | ||
width: canvasImage.width, | ||
height: canvasImage.height | ||
}); | ||
setImmediate(this); | ||
}) | ||
.seq(function () { | ||
var packerName = { | ||
'jim-scott': 'jimScott', | ||
horizontal: 'horizontal', | ||
vertical: 'vertical' | ||
}[spriteInfo.packer] || 'tryAll'; | ||
const canvasImages = await Promise.all(imageInfos.map( | ||
imageInfo => getCanvasImageFromImageAsset(imageInfo.asset) | ||
)); | ||
for (const [i, imageInfo] of imageInfos.entries()) { | ||
const canvasImage = canvasImages[i]; | ||
Object.assign(imageInfo, { | ||
canvasImage, | ||
width: canvasImage.width, | ||
height: canvasImage.height | ||
}); | ||
} | ||
packingData = packers[packerName].pack(imageInfos); | ||
const packerName = { | ||
'jim-scott': 'jimScott', | ||
horizontal: 'horizontal', | ||
vertical: 'vertical' | ||
}[spriteInfo.packer] || 'tryAll'; | ||
var canvas = new Canvas(packingData.width, packingData.height), | ||
ctx = canvas.getContext('2d'); | ||
const packingData = packers[packerName].pack(imageInfos); | ||
const canvas = new Canvas(packingData.width, packingData.height); | ||
const ctx = canvas.getContext('2d'); | ||
imageInfos = packingData.imageInfos; | ||
if ('backgroundColor' in spriteInfo) { | ||
ctx.fillStyle = spriteInfo.backgroundColor; | ||
ctx.fillRect(0, 0, canvas.width, canvas.height); | ||
} | ||
imageInfos.forEach(function (imageInfo) { | ||
ctx.drawImage(imageInfo.canvasImage, imageInfo.x, imageInfo.y, imageInfo.width, imageInfo.height); | ||
}); | ||
getImageAssetFromCanvas(canvas, /^jpe?g$/.test(spriteInfo.imageFormat) ? 'Jpeg' : 'Png', assetGraph, this); | ||
}) | ||
.seq(function (spriteAsset) { | ||
var baseUrl = assetGraph.root, | ||
href = ['sprite', spriteGroupName, imageInfos.length, spriteAsset.id].join('-') + spriteAsset.defaultExtension; | ||
if ('backgroundColor' in spriteInfo) { | ||
ctx.fillStyle = spriteInfo.backgroundColor; | ||
ctx.fillRect(0, 0, canvas.width, canvas.height); | ||
} | ||
if (Array.isArray(spriteGroup.placeHolders)) { | ||
var location = spriteGroup.placeHolders[0].location; | ||
imageInfos = packingData.imageInfos; | ||
for (const imageInfo of imageInfos) { | ||
ctx.drawImage( | ||
imageInfo.canvasImage, | ||
imageInfo.x, | ||
imageInfo.y, | ||
imageInfo.width, | ||
imageInfo.height | ||
); | ||
} | ||
const spriteImageType = /^jpe?g$/.test(spriteInfo.imageFormat) ? 'Jpeg' : 'Png'; | ||
const spriteAssetConfig = await getImageAssetFromCanvas(canvas, spriteImageType, assetGraph); | ||
if (location) { | ||
if (/^[?#]/.test(location)) { | ||
href += location; | ||
} else { | ||
href = location; | ||
let fileName = `sprite-${spriteGroupName}-${imageInfos.length}${assetGraph[spriteImageType].prototype.defaultExtension}`; | ||
if (Array.isArray(spriteGroup.placeHolders)) { | ||
let location = spriteGroup.placeHolders[0].location; | ||
if (location) { | ||
let href; | ||
if (/^\?/.test(location)) { | ||
href = fileName + location; | ||
} else { | ||
href = location; | ||
} | ||
spriteAssetConfig.url = assetGraph.resolveUrl( | ||
assetGraph.root, | ||
href | ||
); | ||
} | ||
} | ||
if (!spriteAssetConfig.url) { | ||
spriteAssetConfig.fileName = fileName; | ||
} | ||
const spriteAsset = assetGraph.addAsset(spriteAssetConfig); | ||
if (Array.isArray(spriteGroup.placeHolders)) { | ||
for (const spriteInfo of spriteGroup.placeHolders) { | ||
const cssRule = spriteInfo.cssRule; | ||
let propertyName; | ||
let propertyNode; | ||
let tokenNumber; | ||
for (const candidatePropertyName of ['background-image', 'background']) { | ||
const decls = getProperties(cssRule, candidatePropertyName); | ||
if (!propertyName && decls.length > 0) { | ||
propertyName = candidatePropertyName; | ||
var propertyValue = decls[0].value; | ||
if (propertyValue === '!important') { | ||
// Hack so that an existing value of "!important" will DTRT | ||
decls[0].important = true; | ||
propertyValue = 'url(...)'; | ||
} else if (/^\s*$/.test(propertyValue)) { | ||
propertyValue = 'url(...)'; | ||
} else { | ||
const existingUrlTokens = propertyValue.match(assetGraph.CssImage.prototype.tokenRegExp); | ||
if (existingUrlTokens) { | ||
tokenNumber = existingUrlTokens.length; | ||
} | ||
propertyValue += ' url(...)'; | ||
} | ||
decls[0].value = propertyValue; | ||
} | ||
} | ||
if (propertyName) { | ||
propertyNode = getProperties(cssRule, propertyName)[0]; | ||
} else { | ||
cssRule.append('background-image: url(...)'); | ||
propertyNode = cssRule.last; | ||
} | ||
// I can't see why the ordering of CssImage relations should be significant... | ||
const relation = spriteInfo.asset.addRelation({ | ||
type: 'CssImage', | ||
node: cssRule, | ||
to: spriteAsset, | ||
propertyName, | ||
propertyNode, | ||
tokenNumber | ||
}, 'last'); | ||
relation.refreshHref(); | ||
spriteInfo.cssRule.walkDecls(decl => { | ||
if (['-sprite-selector-for-group', '-sprite-packer', '-sprite-image-format', '-sprite-background-color', '-sprite-important'].includes(decl.prop)) { | ||
decl.remove(); | ||
} | ||
}); | ||
spriteAsset.url = URL.resolve(baseUrl, href); | ||
assetGraph.addAsset(spriteAsset); | ||
if (Array.isArray(spriteGroup.placeHolders)) { | ||
spriteGroup.placeHolders.forEach(function (spriteInfo) { | ||
var cssRule = spriteInfo.cssRule, | ||
relation = new assetGraph.CssImage({ | ||
node: cssRule, | ||
to: spriteAsset | ||
}); | ||
// If background-size is set, we should update it, The correct size is now the size of the sprite: | ||
const backgroundSizeDecls = getProperties(spriteInfo.cssRule, 'background-size'); | ||
if (backgroundSizeDecls.length > 0) { | ||
backgroundSizeDecls[0].value = packingData.width + 'px ' + packingData.height + 'px'; | ||
} | ||
} | ||
} | ||
[ 'background-image', 'background' ].forEach(function (propertyName) { | ||
var decls = getProperties(cssRule, propertyName); | ||
if (!relation.propertyName && decls.length > 0) { | ||
relation.propertyName = propertyName; | ||
var propertyValue = decls[0].value; | ||
if (propertyValue === '!important') { | ||
// Hack so that an existing value of "!important" will DTRT | ||
decls[0].important = true; | ||
propertyValue = 'url(...)'; | ||
} else if (/^\s*$/.test(propertyValue)) { | ||
propertyValue = 'url(...)'; | ||
} else { | ||
var existingUrlTokens = propertyValue.match(relation.tokenRegExp); | ||
if (existingUrlTokens) { | ||
relation.tokenNumber = existingUrlTokens.length; | ||
} | ||
propertyValue += ' url(...)'; | ||
} | ||
decls[0].value = propertyValue; | ||
} | ||
}); | ||
if (relation.propertyName) { | ||
relation.propertyNode = getProperties(cssRule, relation.propertyName)[0]; | ||
} else { | ||
cssRule.append('background-image: url(...)'); | ||
relation.propertyNode = cssRule.last; | ||
} | ||
// I can't see why the ordering of CssImage relations should be significant... | ||
spriteInfo.asset.addRelation(relation, 'last'); | ||
relation.refreshHref(); | ||
spriteInfo.cssRule.walkDecls(function (decl) { | ||
if (['-sprite-selector-for-group', '-sprite-packer', '-sprite-image-format', '-sprite-background-color', '-sprite-important'].indexOf(decl.prop) !== -1) { | ||
decl.remove(); | ||
} | ||
}); | ||
for (const imageInfo of imageInfos) { | ||
for (const incomingRelation of imageInfo.incomingRelations) { | ||
incomingRelation.from.markDirty(); | ||
const relationSpriteInfo = getRelationSpriteInfoFromIncomingRelation(incomingRelation); | ||
const node = incomingRelation.node; | ||
const existingBackgroundPositionDecls = getProperties(node, 'background-position'); | ||
const existingBackgroundDecls = getProperties(node, 'background'); | ||
const offsets = [ | ||
Math.round(imageInfo.x / imageInfo.asset.devicePixelRatio), // FIXME: Rounding issues? | ||
Math.round(imageInfo.y / imageInfo.asset.devicePixelRatio) | ||
]; | ||
let backgroundOffsetsWereUpdated = false; | ||
let existingOffsets; | ||
if (existingBackgroundDecls.length > 0) { | ||
// Warn if there's more than one? | ||
const backgroundTokens = existingBackgroundDecls[0].value.split(/\s+/); | ||
const positionTokenIndices = []; | ||
existingOffsets = []; | ||
for (const [i, existingBackgroundValueToken] of backgroundTokens.entries()) { | ||
if (/^(?:-?\d+px|0)$/i.test(existingBackgroundValueToken)) { | ||
positionTokenIndices.push(i); | ||
existingOffsets.push(parseInt(existingBackgroundValueToken, 10)); | ||
} | ||
} | ||
if (existingOffsets.length === 2) { | ||
// Patch up the existing background property by replacing the old offsets with corrected ones: | ||
for (let [i, offset] of offsets.entries()) { | ||
offset -= existingOffsets[i]; | ||
backgroundTokens.splice(positionTokenIndices[i], 1, offset ? -offset + 'px' : '0'); | ||
} | ||
// If background-size is set, we should update it, The correct size is now the size of the sprite: | ||
var backgroundSizeDecls = getProperties(spriteInfo.cssRule, 'background-size'); | ||
if (backgroundSizeDecls.length > 0) { | ||
backgroundSizeDecls[0].value = packingData.width + 'px ' + packingData.height + 'px'; | ||
} | ||
}); | ||
existingBackgroundDecls[0].value = backgroundTokens.join(' '); | ||
backgroundOffsetsWereUpdated = true; | ||
} | ||
imageInfos.forEach(function (imageInfo) { | ||
imageInfo.incomingRelations.forEach(function (incomingRelation) { | ||
incomingRelation.from.markDirty(); | ||
var relationSpriteInfo = getRelationSpriteInfoFromIncomingRelation(incomingRelation), | ||
node = incomingRelation.node, | ||
existingBackgroundPositionDecls = getProperties(node, 'background-position'), | ||
existingBackgroundDecls = getProperties(node, 'background'), | ||
backgroundOffsetsWereUpdated = false, | ||
offsets = [ | ||
Math.round(imageInfo.x / imageInfo.asset.devicePixelRatio), // FIXME: Rounding issues? | ||
Math.round(imageInfo.y / imageInfo.asset.devicePixelRatio) | ||
], | ||
existingOffsets; | ||
if (existingBackgroundDecls.length > 0) { | ||
// Warn if there's more than one? | ||
var backgroundTokens = existingBackgroundDecls[0].value.split(/\s+/), | ||
positionTokenIndices = []; | ||
existingOffsets = []; | ||
backgroundTokens.forEach(function (existingBackgroundValueToken, i) { | ||
if (/^(?:-?\d+px|0)$/i.test(existingBackgroundValueToken)) { | ||
positionTokenIndices.push(i); | ||
existingOffsets.push(parseInt(existingBackgroundValueToken, 10)); | ||
} | ||
}); | ||
if (existingOffsets.length === 2) { | ||
// Patch up the existing background property by replacing the old offsets with corrected ones: | ||
offsets.forEach(function (offset, i) { | ||
offset -= existingOffsets[i]; | ||
backgroundTokens.splice(positionTokenIndices[i], 1, offset ? -offset + 'px' : '0'); | ||
}); | ||
} | ||
existingBackgroundDecls[0].value = backgroundTokens.join(' '); | ||
backgroundOffsetsWereUpdated = true; | ||
} | ||
} | ||
if (!backgroundOffsetsWereUpdated) { | ||
// There was no 'background' property, or it didn't contain something that looked like offsets. | ||
// Create or update the background-position property instead: | ||
let backgroundPositionImportant = false; | ||
if (existingBackgroundPositionDecls.length === 1) { | ||
// FIXME: Silently ignores other units than px | ||
backgroundPositionImportant = existingBackgroundPositionDecls[0].value === '!important' || existingBackgroundPositionDecls[0].important; | ||
if (!backgroundOffsetsWereUpdated) { | ||
// There was no 'background' property, or it didn't contain something that looked like offsets. | ||
// Create or update the background-position property instead: | ||
var backgroundPositionImportant = false; | ||
if (existingBackgroundPositionDecls.length === 1) { | ||
// FIXME: Silently ignores other units than px | ||
backgroundPositionImportant = existingBackgroundPositionDecls[0].value === '!important' || existingBackgroundPositionDecls[0].important; | ||
if (existingBackgroundPositionDecls[0].value !== '!important') { | ||
existingOffsets = existingBackgroundPositionDecls[0].value.split(' ').map(function (item) { | ||
return parseInt(item, 10); | ||
}); | ||
if (existingOffsets.length !== 2 || isNaN(existingOffsets[0]) || isNaN(existingOffsets[1])) { | ||
var err = new Error('WARNING: trying to sprite ' + imageInfo.asset.url + ' with background-position: ' + existingBackgroundPositionDecls[0].value); | ||
assetGraph.emit('warn', err); | ||
} else { | ||
offsets[0] -= existingOffsets[0]; | ||
offsets[1] -= existingOffsets[1]; | ||
} | ||
} | ||
} | ||
var newBackgroundPositionValue = offsets.map(function (item) { | ||
return item ? -item + 'px' : '0'; | ||
}).join(' '); | ||
if (existingBackgroundPositionDecls.length > 0) { | ||
existingBackgroundPositionDecls[0].value = newBackgroundPositionValue; | ||
existingBackgroundPositionDecls[0].important = backgroundPositionImportant; | ||
} else { | ||
node.append('background-position: ' + newBackgroundPositionValue + (backgroundPositionImportant ? ' !important' : '')); | ||
} | ||
if (existingBackgroundPositionDecls[0].value !== '!important') { | ||
existingOffsets = existingBackgroundPositionDecls[0].value.split(' ').map( | ||
item => parseInt(item, 10) | ||
); | ||
if (existingOffsets.length !== 2 || isNaN(existingOffsets[0]) || isNaN(existingOffsets[1])) { | ||
const err = new Error('WARNING: trying to sprite ' + imageInfo.asset.url + ' with background-position: ' + existingBackgroundPositionDecls[0].value); | ||
assetGraph.warn(err); | ||
} else { | ||
offsets[0] -= existingOffsets[0]; | ||
offsets[1] -= existingOffsets[1]; | ||
} | ||
} | ||
} | ||
const newBackgroundPositionValue = offsets.map( | ||
item => item ? -item + 'px' : '0' | ||
).join(' '); | ||
if (existingBackgroundPositionDecls.length > 0) { | ||
existingBackgroundPositionDecls[0].value = newBackgroundPositionValue; | ||
existingBackgroundPositionDecls[0].important = backgroundPositionImportant; | ||
} else { | ||
node.append('background-position: ' + newBackgroundPositionValue + (backgroundPositionImportant ? ' !important' : '')); | ||
} | ||
} | ||
node.walkDecls(function (decl) { | ||
if (['-sprite-group', '-sprite-padding', '-sprite-no-group-selector', '-sprite-important'].indexOf(decl.prop) !== -1) { | ||
decl.remove(); | ||
} | ||
}); | ||
node.walkDecls(decl => { | ||
if (['-sprite-group', '-sprite-padding', '-sprite-no-group-selector', '-sprite-important'].includes(decl.prop)) { | ||
decl.remove(); | ||
} | ||
}); | ||
// Background-sizes change when spriting, upadte appropriately | ||
if (imageInfo.asset.devicePixelRatio === 1) { | ||
// Device pixel ratio is default. Remove property and let the defaults rule | ||
getProperties(incomingRelation.node, 'background-size').forEach(function (backgroundSizeDecl) { | ||
backgroundSizeDecl.remove(); | ||
}); | ||
} else { | ||
// Device pixel ratio is non-default, Set it explicitly with the ratio applied | ||
var dpr = incomingRelation.to.devicePixelRatio; | ||
// Background-sizes change when spriting, upadte appropriately | ||
if (imageInfo.asset.devicePixelRatio === 1) { | ||
// Device pixel ratio is default. Remove property and let the defaults rule | ||
for (const backgroundSizeDecl of getProperties(incomingRelation.node, 'background-size')) { | ||
backgroundSizeDecl.remove(); | ||
} | ||
} else { | ||
// Device pixel ratio is non-default, Set it explicitly with the ratio applied | ||
const dpr = incomingRelation.to.devicePixelRatio; | ||
// TODO: Figure out if rounding might become a problem | ||
var width = packingData.width / dpr; | ||
var height = packingData.height / dpr; | ||
var existingBackgroundSizeDecls = getProperties(incomingRelation.node, 'background-size'); | ||
if (existingBackgroundSizeDecls.length > 0) { | ||
existingBackgroundSizeDecls[0].value = width + 'px ' + height + 'px'; | ||
} else { | ||
incomingRelation.node.append('background-size: ' + width + 'px ' + height + 'px'); | ||
} | ||
} | ||
// TODO: Figure out if rounding might become a problem | ||
const width = packingData.width / dpr; | ||
const height = packingData.height / dpr; | ||
const existingBackgroundSizeDecls = getProperties(incomingRelation.node, 'background-size'); | ||
if (existingBackgroundSizeDecls.length > 0) { | ||
existingBackgroundSizeDecls[0].value = `${width}px ${height}px`; | ||
} else { | ||
incomingRelation.node.append(`background-size: ${width}px ${height}px`); | ||
} | ||
} | ||
if (relationSpriteInfo.noGroup || !spriteGroup.placeHolders) { | ||
// The user specified that this selector needs its own background-image/background | ||
// property pointing at the sprite rather than relying on the Html elements also being | ||
// matched by the sprite group's "main" selector, which would have been preferable. | ||
var relation = new assetGraph.CssImage({ | ||
node: incomingRelation.node, | ||
propertyNode: incomingRelation.propertyNode, | ||
to: spriteAsset | ||
}); | ||
incomingRelation.from.addRelation(relation, 'before', incomingRelation); | ||
relation.refreshHref(); | ||
incomingRelation.remove(); | ||
} else { | ||
incomingRelation.detach(); | ||
} | ||
if (relationSpriteInfo.noGroup || !spriteGroup.placeHolders) { | ||
// The user specified that this selector needs its own background-image/background | ||
// property pointing at the sprite rather than relying on the Html elements also being | ||
// matched by the sprite group's "main" selector, which would have been preferable. | ||
const relation = incomingRelation.from.addRelation({ | ||
type: 'CssImage', | ||
node: incomingRelation.node, | ||
propertyNode: incomingRelation.propertyNode, | ||
to: spriteAsset | ||
}, 'before', incomingRelation); | ||
relation.refreshHref(); | ||
incomingRelation.remove(); | ||
} else { | ||
incomingRelation.detach(); | ||
} | ||
// Remove the original image if it has become an orphan: | ||
if (!assetGraph.findRelations({to: incomingRelation.to}).length) { | ||
assetGraph.removeAsset(incomingRelation.to); | ||
} | ||
}); | ||
}); | ||
callback(); | ||
})['catch'](callback); | ||
}) | ||
.seq(function () { | ||
cb(); | ||
})['catch'](cb); | ||
// Remove the original image if it has become an orphan: | ||
if (!assetGraph.findRelations({to: incomingRelation.to}).length) { | ||
assetGraph.removeAsset(incomingRelation.to); | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"repository": "git://github.com/One-com/assetgraph-sprite.git", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"maintainers": [ | ||
@@ -23,8 +23,2 @@ { | ||
], | ||
"dependencies": { | ||
"lodash.assign": "4.2.0", | ||
"lodash.values": "4.3.0", | ||
"passerror": "1.1.1", | ||
"seq": "0.3.5" | ||
}, | ||
"optionalDependencies": { | ||
@@ -34,6 +28,7 @@ "canvas": "1.6.5" | ||
"devDependencies": { | ||
"assetgraph": "^3.0.0", | ||
"assetgraph": "4.0.0", | ||
"coveralls": "^2.11.6", | ||
"eslint": "4.16.0", | ||
"eslint-config-onelint": "3.0.0", | ||
"istanbul": "^0.4.2", | ||
"jshint": "^2.9.1", | ||
"lodash.pluck": "3.1.2", | ||
@@ -48,3 +43,3 @@ "mocha": "^3.0.0", | ||
"scripts": { | ||
"lint": "jshint .", | ||
"lint": "eslint .", | ||
"test": "npm run lint && mocha", | ||
@@ -51,0 +46,0 @@ "coverage": "istanbul cover _mocha", |
1
37715
9
577
- Removedlodash.assign@4.2.0
- Removedlodash.values@4.3.0
- Removedpasserror@1.1.1
- Removedseq@0.3.5
- Removedchainsaw@0.0.9(transitive)
- Removedhashish@0.0.4(transitive)
- Removedlodash.assign@4.2.0(transitive)
- Removedlodash.values@4.3.0(transitive)
- Removedpasserror@1.1.1(transitive)
- Removedseq@0.3.5(transitive)
- Removedtraverse@0.3.9(transitive)