assetgraph-sprite
Advanced tools
Comparing version 3.0.0 to 3.0.1
@@ -1,19 +0,19 @@ | ||
exports.pack = function (imageInfos) { | ||
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]); | ||
imageInfo.x = packingData.width; | ||
imageInfo.y = 0; | ||
packingData.width += imageInfo.width; | ||
previousRightPadding = imageInfo.padding[1]; | ||
packingData.height = Math.max(packingData.height, imageInfo.height); | ||
packingData.imageInfos.push(imageInfo); | ||
} | ||
return packingData; | ||
exports.pack = imageInfos => { | ||
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]); | ||
imageInfo.x = packingData.width; | ||
imageInfo.y = 0; | ||
packingData.width += imageInfo.width; | ||
previousRightPadding = imageInfo.padding[1]; | ||
packingData.height = Math.max(packingData.height, imageInfo.height); | ||
packingData.imageInfos.push(imageInfo); | ||
} | ||
return packingData; | ||
}; |
// Install getters for all packers in this directory: | ||
for (const fileName of require('fs').readdirSync(__dirname)) { | ||
if (/\.js$/.test(fileName) && fileName !== 'index.js') { | ||
Object.defineProperty(exports, fileName.replace(/\.js$/, ''), { | ||
get: () => require('./' + fileName) | ||
}); | ||
} | ||
if (/\.js$/.test(fileName) && fileName !== 'index.js') { | ||
Object.defineProperty(exports, fileName.replace(/\.js$/, ''), { | ||
get: () => require('./' + fileName) | ||
}); | ||
} | ||
} |
@@ -11,90 +11,96 @@ /* | ||
function findCoords(node, width, height) { | ||
// If we are not at a leaf then go deeper | ||
if (node.lft) { | ||
// Check first the left branch if not found then go by the right | ||
return findCoords(node.lft, width, height) || findCoords(node.rgt, width, height); | ||
} else { | ||
// If already used or it's too big then return | ||
if (node.used || width > node.width || height > node.height) { | ||
return; | ||
} | ||
// If we are not at a leaf then go deeper | ||
if (node.lft) { | ||
// Check first the left branch if not found then go by the right | ||
return ( | ||
findCoords(node.lft, width, height) || findCoords(node.rgt, width, height) | ||
); | ||
} else { | ||
// If already used or it's too big then return | ||
if (node.used || width > node.width || height > node.height) { | ||
return; | ||
} | ||
// If it fits perfectly then use this gap | ||
if (width === node.width && height === node.height) { | ||
node.used = true; | ||
return { | ||
x: node.x, | ||
y: node.y | ||
}; | ||
} | ||
} | ||
// If it fits perfectly then use this gap | ||
if (width === node.width && height === node.height) { | ||
node.used = true; | ||
return { | ||
x: node.x, | ||
y: node.y | ||
}; | ||
} | ||
// Partition vertically or horizontally: | ||
if (node.width - width > node.height - height) { | ||
node.lft = { | ||
x: node.x, | ||
y: node.y, | ||
width: width, | ||
height: node.height | ||
}; | ||
node.rgt = { | ||
x: node.x + width, | ||
y: node.y, | ||
width: node.width - width, | ||
height: node.height | ||
}; | ||
} else { | ||
node.lft = { | ||
x: node.x, | ||
y: node.y, | ||
width: node.width, | ||
height: height | ||
}; | ||
node.rgt = { | ||
x: node.x, | ||
y: node.y + height, | ||
width: node.width, | ||
height: node.height - height | ||
}; | ||
} | ||
return findCoords(node.lft, width, height); | ||
// Partition vertically or horizontally: | ||
if (node.width - width > node.height - height) { | ||
node.lft = { | ||
x: node.x, | ||
y: node.y, | ||
width, | ||
height: node.height | ||
}; | ||
node.rgt = { | ||
x: node.x + width, | ||
y: node.y, | ||
width: node.width - width, | ||
height: node.height | ||
}; | ||
} else { | ||
node.lft = { | ||
x: node.x, | ||
y: node.y, | ||
width: node.width, | ||
height | ||
}; | ||
node.rgt = { | ||
x: node.x, | ||
y: node.y + height, | ||
width: node.width, | ||
height: node.height - height | ||
}; | ||
} | ||
return findCoords(node.lft, width, height); | ||
} | ||
exports.pack = function (imageInfos, config) { | ||
config = config || {}; | ||
const root = { | ||
x: 0, | ||
y: 0, | ||
width: config.maxWidth || 999999, | ||
height: config.maxHeight || 999999 | ||
}; | ||
exports.pack = (imageInfos, config) => { | ||
config = config || {}; | ||
const root = { | ||
x: 0, | ||
y: 0, | ||
width: config.maxWidth || 999999, | ||
height: config.maxHeight || 999999 | ||
}; | ||
// Sort by area, descending: | ||
imageInfos.sort( | ||
(a, b) => (b.width * b.height) - (a.width * a.height) | ||
); | ||
// Sort by area, descending: | ||
imageInfos.sort((a, b) => b.width * b.height - a.width * a.height); | ||
const packingData = { | ||
imageInfos: [], | ||
width: 0, | ||
height: 0 | ||
}; | ||
const packingData = { | ||
imageInfos: [], | ||
width: 0, | ||
height: 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 | ||
const coords = findCoords(root, imageInfo.width, imageInfo.height); | ||
// If fitted then recalculate the used dimensions | ||
if (coords) { | ||
packingData.width = Math.max(packingData.width, coords.x + imageInfo.width); | ||
packingData.height = Math.max(packingData.height, coords.y + imageInfo.height); | ||
} else { | ||
throw new Error('jimScott.pack: Cannot fit image'); | ||
} | ||
Object.assign(imageInfo, coords); | ||
packingData.imageInfos.push(imageInfo); | ||
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'); | ||
} | ||
return packingData; | ||
// Perform the search | ||
const coords = findCoords(root, imageInfo.width, imageInfo.height); | ||
// If fitted then recalculate the used dimensions | ||
if (coords) { | ||
packingData.width = Math.max( | ||
packingData.width, | ||
coords.x + imageInfo.width | ||
); | ||
packingData.height = Math.max( | ||
packingData.height, | ||
coords.y + imageInfo.height | ||
); | ||
} else { | ||
throw new Error('jimScott.pack: Cannot fit image'); | ||
} | ||
Object.assign(imageInfo, coords); | ||
packingData.imageInfos.push(imageInfo); | ||
} | ||
return packingData; | ||
}; |
const packers = ['./horizontal', './vertical', './jimScott'].map(require); | ||
exports.pack = imageInfos => { | ||
let bestPacking; | ||
for (const packer of packers) { | ||
let packing; | ||
try { | ||
packing = packer.pack(imageInfos); | ||
} catch (e) { | ||
// The Jim Scott packer doesn't support sprite padding, just skip to the next packer if we get an exception. | ||
continue; | ||
} | ||
if (!bestPacking || (packing.width * packing.height) < (bestPacking.width * bestPacking.height)) { | ||
bestPacking = packing; | ||
} | ||
let bestPacking; | ||
for (const packer of packers) { | ||
let packing; | ||
try { | ||
packing = packer.pack(imageInfos); | ||
} catch (e) { | ||
// The Jim Scott packer doesn't support sprite padding, just skip to the next packer if we get an exception. | ||
continue; | ||
} | ||
return bestPacking; | ||
if ( | ||
!bestPacking || | ||
packing.width * packing.height < bestPacking.width * bestPacking.height | ||
) { | ||
bestPacking = packing; | ||
} | ||
} | ||
return bestPacking; | ||
}; |
@@ -1,20 +0,20 @@ | ||
exports.pack = function (imageInfos) { | ||
let previousBottomPadding = 0; | ||
const packingData = { | ||
width: 0, | ||
height: 0, | ||
imageInfos: [] | ||
}; | ||
exports.pack = imageInfos => { | ||
let previousBottomPadding = 0; | ||
const packingData = { | ||
width: 0, | ||
height: 0, | ||
imageInfos: [] | ||
}; | ||
for (const existingImageInfo of imageInfos) { | ||
const imageInfo = {...existingImageInfo}; | ||
packingData.height += Math.max(previousBottomPadding, imageInfo.padding[0]); | ||
imageInfo.y = packingData.height; | ||
imageInfo.x = 0; | ||
packingData.height += imageInfo.height; | ||
previousBottomPadding = imageInfo.padding[2]; | ||
packingData.width = Math.max(packingData.width, imageInfo.width); | ||
packingData.imageInfos.push(imageInfo); | ||
} | ||
return packingData; | ||
for (const existingImageInfo of imageInfos) { | ||
const imageInfo = { ...existingImageInfo }; | ||
packingData.height += Math.max(previousBottomPadding, imageInfo.padding[0]); | ||
imageInfo.y = packingData.height; | ||
imageInfo.x = 0; | ||
packingData.height += imageInfo.height; | ||
previousBottomPadding = imageInfo.padding[2]; | ||
packingData.width = Math.max(packingData.width, imageInfo.width); | ||
packingData.imageInfos.push(imageInfo); | ||
} | ||
return packingData; | ||
}; |
const queryString = require('querystring'); | ||
const { promisify } = require('util'); | ||
const packers = require('./packers'); | ||
let Canvas; | ||
const { Canvas, Image } = require('canvas-prebuilt'); | ||
try { | ||
Canvas = require('canvas'); | ||
} catch (e) {} | ||
// Helper for extracting all nodes defining a specific property from a postcss rule | ||
function getProperties(container, propertyName) { | ||
return container.nodes.filter( | ||
node => node.prop === propertyName | ||
); | ||
return container.nodes.filter(node => node.prop === propertyName); | ||
} | ||
async function getCanvasImageFromImageAsset(imageAsset) { | ||
const canvasImage = new Canvas.Image(); | ||
await new Promise((resolve, reject) => { | ||
canvasImage.onerror = err => { | ||
err.message += ' (' + imageAsset.urlOrDescription + ')'; | ||
reject(err); | ||
}; | ||
canvasImage.onload = resolve; | ||
canvasImage.src = imageAsset.rawSrc; | ||
}); | ||
return canvasImage; | ||
const canvasImage = new Image(); | ||
await new Promise((resolve, reject) => { | ||
canvasImage.onerror = err => { | ||
if (err.message.includes('node-canvas was built without SVG support')) { | ||
err.message = 'Adding SVG images to a sprite is not possible'; | ||
} | ||
err.message += ` (${imageAsset.urlOrDescription})`; | ||
reject(err); | ||
}; | ||
canvasImage.onload = resolve; | ||
canvasImage.src = imageAsset.rawSrc; | ||
}); | ||
return canvasImage; | ||
} | ||
async function getImageAssetFromCanvas(canvas, assetType, assetGraph) { | ||
if (assetType === 'Png') { | ||
const rawSrc = await promisify(cb => canvas.toBuffer(cb))(); | ||
return { | ||
type: 'Png', | ||
rawSrc | ||
}; | ||
} else { | ||
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 | ||
}; | ||
} | ||
if (assetType === 'Png') { | ||
const rawSrc = await promisify(cb => canvas.toBuffer(cb))(); | ||
return { | ||
type: 'Png', | ||
rawSrc | ||
}; | ||
} else { | ||
const rawSrc = await promisify(cb => { | ||
const jpegChunks = []; | ||
canvas | ||
.createJPEGStream() | ||
.on('data', chunk => { | ||
jpegChunks.push(chunk); | ||
}) | ||
.on('end', () => cb(null, Buffer.concat(jpegChunks))) | ||
.on('error', cb); | ||
})(); | ||
return { | ||
type: 'Jpeg', | ||
rawSrc | ||
}; | ||
} | ||
} | ||
function calculateSpritePadding(paddingStr, asset) { | ||
var padding; | ||
if (paddingStr) { | ||
// Strip units ('px' assumed) | ||
var tokens = []; | ||
paddingStr.split(/[,+]|\s+/).forEach(function (token) { | ||
var num = parseInt(token.replace(/[a-z]+$/, ''), 10); | ||
if (!isNaN(num)) { | ||
tokens.push(num); | ||
} | ||
}); | ||
if (tokens.length === 4) { | ||
padding = tokens; | ||
} else if (tokens.length === 3) { | ||
padding = [tokens[0], tokens[1], tokens[2], tokens[1]]; // T, L+R, B | ||
} else if (tokens.length === 2) { | ||
padding = [tokens[0], tokens[1], tokens[0], tokens[1]]; // T+B, L+R | ||
} else if (tokens.length === 1) { | ||
padding = [tokens[0], tokens[0], tokens[0], tokens[0]]; | ||
} | ||
} else { | ||
padding = [0, 0, 0, 0]; | ||
let padding; | ||
if (paddingStr) { | ||
// Strip units ('px' assumed) | ||
const tokens = []; | ||
paddingStr.split(/[,+]|\s+/).forEach(token => { | ||
const num = parseInt(token.replace(/[a-z]+$/, ''), 10); | ||
if (!isNaN(num)) { | ||
tokens.push(num); | ||
} | ||
}); | ||
if (tokens.length === 4) { | ||
padding = tokens; | ||
} else if (tokens.length === 3) { | ||
padding = [tokens[0], tokens[1], tokens[2], tokens[1]]; // T, L+R, B | ||
} else if (tokens.length === 2) { | ||
padding = [tokens[0], tokens[1], tokens[0], tokens[1]]; // T+B, L+R | ||
} else if (tokens.length === 1) { | ||
padding = [tokens[0], tokens[0], tokens[0], tokens[0]]; | ||
} | ||
} else { | ||
padding = [0, 0, 0, 0]; | ||
} | ||
return padding.map( | ||
size => Math.max(size, Math.max(asset.devicePixelRatio) - 1) | ||
); | ||
return padding.map(size => | ||
Math.max(size, Math.max(asset.devicePixelRatio) - 1) | ||
); | ||
} | ||
function getRelationSpriteInfoFromIncomingRelation(incomingRelation) { | ||
const parsedQueryString = queryString.parse(incomingRelation.href.match(/\?([^#]*)/)[1]); | ||
return { | ||
groupName: parsedQueryString.sprite || 'default', | ||
noGroup: 'spriteNoGroup' in parsedQueryString, | ||
padding: calculateSpritePadding(parsedQueryString.padding, incomingRelation.to), | ||
asset: incomingRelation.to | ||
}; | ||
const parsedQueryString = queryString.parse( | ||
incomingRelation.href.match(/\?([^#]*)/)[1] | ||
); | ||
return { | ||
groupName: parsedQueryString.sprite || 'default', | ||
noGroup: 'spriteNoGroup' in parsedQueryString, | ||
padding: calculateSpritePadding( | ||
parsedQueryString.padding, | ||
incomingRelation.to | ||
), | ||
asset: incomingRelation.to | ||
}; | ||
} | ||
function extractInfoFromCssRule(cssRule, propertyNamePrefix) { | ||
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'); | ||
} | ||
}); | ||
return info; | ||
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'); | ||
} | ||
}); | ||
return info; | ||
} | ||
module.exports = () => | ||
async function spriteBackgroundImages(assetGraph) { | ||
const spriteGroups = {}; | ||
module.exports = () => { | ||
return async function spriteBackgroundImages(assetGraph) { | ||
if (!Canvas) { | ||
assetGraph.warn(new Error('assetgraph-sprite: Canvas not found, skipping')); | ||
return; | ||
// Find sprite annotated images and create a data structure with their information | ||
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) { | ||
relationSpriteInfo.incomingRelations = [relation]; | ||
spriteGroup.imageInfosById[ | ||
relationSpriteInfo.asset.id | ||
] = relationSpriteInfo; | ||
} else { | ||
imageInfo.incomingRelations.push(relation); | ||
for (var i = 0; i < 4; i += 1) { | ||
imageInfo.padding[i] = Math.max( | ||
relationSpriteInfo.padding[i], | ||
imageInfo.padding[i] | ||
); | ||
} | ||
} | ||
} | ||
// Waiting for https://github.com/LearnBoost/node-canvas/issues/52 | ||
const cairoVersion = Canvas.cairoVersion.split('.').map( | ||
str => parseInt(str, 10) | ||
); | ||
if (cairoVersion[0] < 1 || cairoVersion[1] < 10) { | ||
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; | ||
const redefinitionErrors = {}; | ||
// Extract sprite grouping information va -sprite- prefixed properties in stylesheets | ||
for (const cssAsset of assetGraph.findAssets({ | ||
type: 'Css', | ||
isLoaded: true | ||
})) { | ||
cssAsset.eachRuleInParseTree(cssRule => { | ||
if (cssRule.type !== 'rule') { | ||
return; | ||
} | ||
if (getProperties(cssRule, '-sprite-selector-for-group').length > 0) { | ||
const spriteInfo = extractInfoFromCssRule(cssRule, '-sprite-'); | ||
const spriteGroupName = spriteInfo.selectorForGroup; | ||
if (spriteInfo.location) { | ||
const matchLocation = spriteInfo.location.match( | ||
/^url\((['"]|)(.*?)\1\)$/ | ||
); | ||
if (matchLocation) { | ||
spriteInfo.location = matchLocation[2]; | ||
} | ||
} | ||
const group = spriteGroups[spriteGroupName]; | ||
if (group) { | ||
if (!Array.isArray(group.placeHolders)) { | ||
group.placeHolders = []; | ||
} | ||
const spriteGroups = {}; | ||
if (group.placeHolders.length > 0) { | ||
let err; | ||
// Find sprite annotated images and create a data structure with their information | ||
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) { | ||
relationSpriteInfo.incomingRelations = [relation]; | ||
spriteGroup.imageInfosById[relationSpriteInfo.asset.id] = relationSpriteInfo; | ||
if ( | ||
Object.keys(group.placeHolders[0]).every(key => { | ||
if (['asset', 'cssRule'].includes(key)) { | ||
return true; | ||
} | ||
return group.placeHolders[0][key] === spriteInfo[key]; | ||
}) | ||
) { | ||
// Queue up these errors as they tend to come in quite big bunches | ||
if (!Array.isArray(redefinitionErrors[spriteGroupName])) { | ||
redefinitionErrors[spriteGroupName] = []; | ||
} | ||
redefinitionErrors[spriteGroupName].push(cssAsset); | ||
group.placeHolders.push({ | ||
...spriteInfo, | ||
asset: cssAsset, | ||
cssRule | ||
}); | ||
} else { | ||
err = new Error( | ||
`assetgraph-sprite: Multiple differing definitions of ${spriteGroupName} sprite.\nThis is most likely an error.` | ||
); | ||
err.asset = cssAsset; | ||
assetGraph.warn(err); | ||
} | ||
} else { | ||
imageInfo.incomingRelations.push(relation); | ||
for (var i = 0 ; i < 4 ; i += 1) { | ||
imageInfo.padding[i] = Math.max(relationSpriteInfo.padding[i], imageInfo.padding[i]); | ||
} | ||
group.placeHolders.push({ | ||
...spriteInfo, | ||
asset: cssAsset, | ||
cssRule | ||
}); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
const redefinitionErrors = {}; | ||
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( | ||
asset => ' ' + asset.urlOrDescription | ||
) | ||
].join('\n'); | ||
// Extract sprite grouping information va -sprite- prefixed properties in stylesheets | ||
for (const cssAsset of assetGraph.findAssets({type: 'Css', isLoaded: true})) { | ||
cssAsset.eachRuleInParseTree(cssRule => { | ||
if (cssRule.type !== 'rule') { | ||
return; | ||
} | ||
if (getProperties(cssRule, '-sprite-selector-for-group').length > 0) { | ||
const spriteInfo = extractInfoFromCssRule(cssRule, '-sprite-'); | ||
const spriteGroupName = spriteInfo.selectorForGroup; | ||
if (spriteInfo.location) { | ||
const matchLocation = spriteInfo.location.match(/^url\((['"]|)(.*?)\1\)$/); | ||
if (matchLocation) { | ||
spriteInfo.location = matchLocation[2]; | ||
} | ||
} | ||
const group = spriteGroups[spriteGroupName]; | ||
if (group) { | ||
if (!Array.isArray(group.placeHolders)) { | ||
group.placeHolders = []; | ||
} | ||
const err = new Error(message); | ||
if (group.placeHolders.length > 0) { | ||
let err; | ||
assetGraph.info(err); | ||
} | ||
if (Object.keys(group.placeHolders[0]).every(key => { | ||
if (['asset', 'cssRule'].includes(key)) { | ||
return true; | ||
} | ||
return group.placeHolders[0][key] === spriteInfo[key]; | ||
})) { | ||
// Queue up these errors as they tend to come in quite big bunches | ||
if (!Array.isArray(redefinitionErrors[spriteGroupName])) { | ||
redefinitionErrors[spriteGroupName] = []; | ||
} | ||
redefinitionErrors[spriteGroupName].push(cssAsset); | ||
for (const spriteGroupName of Object.keys(spriteGroups)) { | ||
const spriteGroup = spriteGroups[spriteGroupName]; | ||
let imageInfos = Object.values(spriteGroup.imageInfosById); | ||
const spriteInfo = | ||
(spriteGroup.placeHolders && spriteGroup.placeHolders[0]) || {}; | ||
group.placeHolders.push({ | ||
...spriteInfo, | ||
asset: cssAsset, | ||
cssRule | ||
}); | ||
} else { | ||
err = new Error(`assetgraph-sprite: Multiple differing definitions of ${spriteGroupName} sprite.\nThis is most likely an error.`); | ||
err.asset = cssAsset; | ||
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 | ||
}); | ||
} | ||
assetGraph.warn(err); | ||
} | ||
const packerName = | ||
{ | ||
'jim-scott': 'jimScott', | ||
horizontal: 'horizontal', | ||
vertical: 'vertical' | ||
}[spriteInfo.packer] || 'tryAll'; | ||
} else { | ||
group.placeHolders.push({ | ||
...spriteInfo, | ||
asset: cssAsset, | ||
cssRule | ||
}); | ||
} | ||
const packingData = packers[packerName].pack(imageInfos); | ||
const canvas = new Canvas(packingData.width, packingData.height); | ||
const ctx = canvas.getContext('2d'); | ||
} | ||
} | ||
}); | ||
} | ||
if ('backgroundColor' in spriteInfo) { | ||
ctx.fillStyle = spriteInfo.backgroundColor; | ||
ctx.fillRect(0, 0, canvas.width, canvas.height); | ||
} | ||
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( | ||
asset => ' ' + asset.urlOrDescription | ||
) | ||
].join('\n'); | ||
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 | ||
); | ||
const err = new Error(message); | ||
assetGraph.info(err); | ||
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; | ||
} | ||
for (const spriteGroupName of Object.keys(spriteGroups)) { | ||
const spriteGroup = spriteGroups[spriteGroupName]; | ||
let imageInfos = Object.values(spriteGroup.imageInfosById); | ||
const spriteInfo = spriteGroup.placeHolders && spriteGroup.placeHolders[0] || {}; | ||
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 | ||
}); | ||
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; | ||
let 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(); | ||
} | ||
}); | ||
const packerName = { | ||
'jim-scott': 'jimScott', | ||
horizontal: 'horizontal', | ||
vertical: 'vertical' | ||
}[spriteInfo.packer] || 'tryAll'; | ||
// 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'; | ||
} | ||
} | ||
} | ||
const packingData = packers[packerName].pack(imageInfos); | ||
const canvas = new Canvas(packingData.width, packingData.height); | ||
const ctx = canvas.getContext('2d'); | ||
if ('backgroundColor' in spriteInfo) { | ||
ctx.fillStyle = spriteInfo.backgroundColor; | ||
ctx.fillRect(0, 0, canvas.width, canvas.height); | ||
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' | ||
); | ||
} | ||
imageInfos = packingData.imageInfos; | ||
for (const imageInfo of imageInfos) { | ||
ctx.drawImage( | ||
imageInfo.canvasImage, | ||
imageInfo.x, | ||
imageInfo.y, | ||
imageInfo.width, | ||
imageInfo.height | ||
); | ||
existingBackgroundDecls[0].value = backgroundTokens.join(' '); | ||
backgroundOffsetsWereUpdated = true; | ||
} | ||
const spriteImageType = /^jpe?g$/.test(spriteInfo.imageFormat) ? 'Jpeg' : 'Png'; | ||
const spriteAssetConfig = await getImageAssetFromCanvas(canvas, spriteImageType, assetGraph); | ||
} | ||
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 (!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 (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]; | ||
} | ||
} | ||
} | ||
if (!spriteAssetConfig.url) { | ||
spriteAssetConfig.fileName = fileName; | ||
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' : '') | ||
); | ||
} | ||
} | ||
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(); | ||
} | ||
}); | ||
node.walkDecls(decl => { | ||
if ( | ||
[ | ||
'-sprite-group', | ||
'-sprite-padding', | ||
'-sprite-no-group-selector', | ||
'-sprite-important' | ||
].includes(decl.prop) | ||
) { | ||
decl.remove(); | ||
} | ||
}); | ||
// 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-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; | ||
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'); | ||
} | ||
// 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` | ||
); | ||
} | ||
} | ||
existingBackgroundDecls[0].value = backgroundTokens.join(' '); | ||
backgroundOffsetsWereUpdated = true; | ||
} | ||
} | ||
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(); | ||
} | ||
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 (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(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 | ||
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 | ||
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. | ||
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); | ||
} | ||
} | ||
} | ||
// Remove the original image if it has become an orphan: | ||
if (!assetGraph.findRelations({ to: incomingRelation.to }).length) { | ||
assetGraph.removeAsset(incomingRelation.to); | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
} | ||
}; |
@@ -5,7 +5,8 @@ { | ||
"repository": "git://github.com/One-com/assetgraph-sprite.git", | ||
"version": "3.0.0", | ||
"version": "3.0.1", | ||
"license": "BSD-3-Clause", | ||
"maintainers": [ | ||
{ | ||
"name": "Andreas Lind", | ||
"email": "andreas@one.com" | ||
"email": "andreaslindpetersen@gmail.com" | ||
}, | ||
@@ -17,21 +18,17 @@ { | ||
], | ||
"publishConfig": { | ||
"registry": "http://registry.npmjs.org/" | ||
}, | ||
"files": [ | ||
"lib" | ||
], | ||
"optionalDependencies": { | ||
"canvas": "1.6.5" | ||
}, | ||
"devDependencies": { | ||
"assetgraph": "4.0.0", | ||
"coveralls": "^2.11.6", | ||
"eslint": "4.16.0", | ||
"eslint-config-onelint": "3.0.0", | ||
"istanbul": "^0.4.2", | ||
"lodash.pluck": "3.1.2", | ||
"mocha": "^3.0.0", | ||
"assetgraph": "^5.4.0", | ||
"coveralls": "^3.0.0", | ||
"eslint": "^5.7.0", | ||
"eslint-config-pretty-standard": "^2.0.0", | ||
"eslint-plugin-prettier": "^3.0.0", | ||
"lodash.pluck": "^3.1.2", | ||
"mocha": "^5.1.0", | ||
"nyc": "^13.1.0", | ||
"prettier": "^1.14.3", | ||
"unexpected": "^10.26.0", | ||
"urltools": "0.3.1" | ||
"urltools": "^0.4.1" | ||
}, | ||
@@ -43,7 +40,15 @@ "directories": { | ||
"lint": "eslint .", | ||
"test": "npm run lint && mocha", | ||
"coverage": "istanbul cover _mocha", | ||
"test": "mocha", | ||
"coverage": "NODE_ENV=development nyc --reporter=lcov --reporter=text --all -- npm run test && echo google-chrome coverage/lcov-report/index.html", | ||
"travis": "npm run lint && npm run coverage" | ||
}, | ||
"main": "lib/spriteBackgroundImages.js" | ||
"main": "lib/spriteBackgroundImages.js", | ||
"dependencies": { | ||
"canvas-prebuilt": "2.0.0-alpha.14" | ||
}, | ||
"nyc": { | ||
"include": [ | ||
"lib/**" | ||
] | ||
} | ||
} |
@@ -163,16 +163,3 @@ AssetGraph-sprite | ||
For creating the sprite images themselves AssetGraph-sprite uses <a | ||
href="http://github.com/LearnBoost/node-canvas">node-canvas</a>, which | ||
is not a pure-node module and requires the Cairo development sources | ||
(version 1.10 or later), `libjpeg` (version 8 or later) and | ||
`libgif`. On Ubuntu 10.10 and above you should be able to get them | ||
like this: | ||
``` | ||
$ sudo apt-get install libcairo2-dev libgif-dev libjpeg8-dev | ||
``` | ||
Now you can proceed to install AssetGraph-sprite: | ||
``` | ||
$ npm install assetgraph-sprite | ||
@@ -179,0 +166,0 @@ ``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
677
34239
11
206
+ Addedabbrev@1.1.1(transitive)
+ Addedansi-regex@2.1.1(transitive)
+ Addedaproba@1.2.0(transitive)
+ Addedare-we-there-yet@1.1.7(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedcanvas-prebuilt@2.0.0-alpha.14(transitive)
+ Addedchownr@1.1.4(transitive)
+ Addedcode-point-at@1.1.0(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedconsole-control-strings@1.1.0(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addeddebug@3.2.7(transitive)
+ Addeddeep-extend@0.6.0(transitive)
+ Addeddelegates@1.0.0(transitive)
+ Addeddetect-libc@1.0.3(transitive)
+ Addedfs-minipass@1.2.7(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedgauge@2.7.4(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedhas-unicode@2.0.1(transitive)
+ Addediconv-lite@0.4.24(transitive)
+ Addedignore-walk@3.0.4(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedini@1.3.8(transitive)
+ Addedis-fullwidth-code-point@1.0.0(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedminipass@2.9.0(transitive)
+ Addedminizlib@1.3.3(transitive)
+ Addedmkdirp@0.5.6(transitive)
+ Addedms@2.1.3(transitive)
+ Addedneedle@2.9.1(transitive)
+ Addednode-pre-gyp@0.10.3(transitive)
+ Addednopt@4.0.3(transitive)
+ Addednpm-bundled@1.1.2(transitive)
+ Addednpm-normalize-package-bin@1.0.1(transitive)
+ Addednpm-packlist@1.4.8(transitive)
+ Addednpmlog@4.1.2(transitive)
+ Addednumber-is-nan@1.0.1(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedos-homedir@1.0.2(transitive)
+ Addedos-tmpdir@1.0.2(transitive)
+ Addedosenv@0.1.5(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedprocess-nextick-args@2.0.1(transitive)
+ Addedrc@1.2.8(transitive)
+ Addedreadable-stream@2.3.8(transitive)
+ Addedrimraf@2.7.1(transitive)
+ Addedsafe-buffer@5.1.25.2.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsax@1.4.1(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedset-blocking@2.0.0(transitive)
+ Addedsignal-exit@3.0.7(transitive)
+ Addedstring-width@1.0.2(transitive)
+ Addedstring_decoder@1.1.1(transitive)
+ Addedstrip-ansi@3.0.1(transitive)
+ Addedstrip-json-comments@2.0.1(transitive)
+ Addedtar@4.4.19(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
+ Addedwide-align@1.1.5(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedyallist@3.1.1(transitive)
- Removedcanvas@1.6.5(transitive)
- Removedcss-font-size-keywords@1.0.0(transitive)
- Removedcss-font-stretch-keywords@1.0.1(transitive)
- Removedcss-font-style-keywords@1.0.1(transitive)
- Removedcss-font-weight-keywords@1.0.0(transitive)
- Removedcss-global-keywords@1.0.1(transitive)
- Removedcss-list-helpers@1.0.1(transitive)
- Removedcss-system-font-keywords@1.0.0(transitive)
- Removedisnumeric@0.2.0(transitive)
- Removedparse-css-font@2.0.2(transitive)
- Removedtcomb@2.7.0(transitive)
- Removedunits-css@0.4.0(transitive)
- Removedunquote@1.1.1(transitive)
- Removedviewport-dimensions@0.2.0(transitive)