Example #1
0
 it('should return a stream with two sprite objects and the appropriate nameing of sprites', function (done) {
   var count = 0;
   opts.dimension = [{ratio: 1, dpi: 72}, {ratio: 2, dpi: 192}];
   mockLayouts.name = 'first';
   var l2 = layout('top-down');
   l2.addItem({height: 50, width: 50, meta: {}});
   var mockLayouts2 = {
     name: 'second',
     layout: mockLayout.export()
   };
   os.fromArray([mockLayouts, mockLayouts2])
     .pipe(sprite(opts))
     .pipe(spy(function (res) {
       res.sprites.length.should.equal(2);
       if (count === 0) {
         res.should.have.deep.property('sprites[0].name', 'sprite-first');
         res.should.have.deep.property('sprites[1].name', 'sprite-first@2x');
       }
       if (count === 1) {
         res.should.have.deep.property('sprites[0].name', 'sprite-second');
         res.should.have.deep.property('sprites[1].name', 'sprite-second@2x');
       }
       count++;
     }))
     .on('data', noop)
     .on('finish', function () {
       count.should.equal(2);
       done();
     });
 });
Example #2
0
beforeEach(function () {
  opts = {
    name: 'sprite',
    base64: false,
    format: 'png',
    cssPath: '../images',
    engine: mockedImageProcessor,
    dimension: [{ratio: 1, dpi: 72}],
    logger: {
      log: noop,
      warn: noop,
      debug: noop,
      error: noop,
      success: noop
    }
  };

  mockLayout = layout('top-down');
  mockLayout.addItem({
    height: 50,
    width: 50,
    meta: {
      width: 42,
      height: 42,
      x: 0,
      y: 0,
      type: 'png',
      offset: 4
    }
  });
  mockLayouts = {
    name: 'default',
    layout: mockLayout.export()
  };
});
Example #3
0
	constructor(algorithm = "binary-tree", background = "transparent", margin = 4) {
		this.layer = layout(algorithm, {
			sort: false,
			// todo support sort
		});
		this.tiles = [];
		this.background = background;
		this.margin = margin;
	}
    appendCreateImageRequests(images, requests) {
        const layer = boxLayout('left-right'); // TODO - Configurable?

        for(let image of images) {
            debug(`Slide #${this.slide.index}: adding inline image ${image.url}`);
            layer.addItem({
                width: image.width + image.padding * 2,
                height: image.height + image.padding * 2,
                meta: image});
        }

        const box = this.getBodyBoundingBox(false);
        const computedLayout = layer.export();

        let scaleRatio = Math.min(
            box.width / computedLayout.width,
            box.height / computedLayout.height);

        let scaledWidth = computedLayout.width * scaleRatio;
        let scaledHeight = computedLayout.height * scaleRatio;

        let translateX = box.x + ((box.width - scaledWidth) / 2);
        let translateY = box.y + ((box.height - scaledHeight) / 2);

        for(let item of computedLayout.items) {
            let width = item.meta.width * scaleRatio;
            let height = item.meta.height * scaleRatio;
            requests.push({
                createImage: {
                    elementProperties: {
                        pageObjectId: this.slide.objectId,
                        size: {
                            height: {
                                magnitude: height,
                                unit: 'EMU'
                            },
                            width: {
                                magnitude: width,
                                unit: 'EMU'
                            }
                        },
                        transform: {
                            scaleX: 1,
                            scaleY: 1,
                            translateX: translateX + (item.x + item.meta.padding) * scaleRatio,
                            translateY: translateY + (item.y + item.meta.padding) * scaleRatio,
                            shearX: 0,
                            shearY: 0,
                            unit: 'EMU'
                        }
                    },
                    url: item.meta.url
                }
            });
        }
    }
 layout: function() {
     var positions = Layout(
         this.composers.length, this.container.getBoundingClientRect()
     );
     // Applying positions using CSS `left` and `top`.
     this.composers.forEach(function ( composer, index ) {
         var pos = positions[index];
         composer.element.style.left = pos.left + "px";
         composer.element.style.top = pos.top + "px";
     });
 }
Example #6
0
beforeEach(function () {
  opts = {
    'style-indent-char': 'space',
    'style-indent-size': 2,
    'processor': 'css',
    'style': 'style',
    'logger': {
      log: noop,
      warn: noop,
      debug: noop,
      error: noop,
      success: noop
    }
  };

  var l = layout('top-down');
  l.addItem({height: 50, width: 50, meta: {}});
  layouts = {
    name: 'default',
    classname: 'icon',
    layout: l.export(),
    sprites: [{
      name: 'sprite',
      url: '../images/sprite.png',
      type: 'png',
      dpi: null,
      ratio: null,
      width: 50,
      height: 300
    }, {
      name: 'sprite@1.5x',
      url: '../images/sprite@1.5x.png',
      type: 'png',
      dpi: 144,
      ratio: 1.5,
      width: 50,
      height: 300
    }, {
      name: 'sprite@2x',
      url: '../images/sprite@2x.png',
      type: 'png',
      dpi: 192,
      ratio: 2,
      width: 50,
      height: 300
    }]
  };
});
function updateLayer() {
  var layoutOrientation =
      opts.orientation === 'vertical' ? 'top-down' :
      opts.orientation === 'horizontal' ? 'left-right' :
      'binary-tree';

  function appendLayer(list, layer) {
    _.forEach(list, function(image) {
      layer.addItem({
        'height': image.height,
        'width': image.width,
        'meta': {
          buffer: image.buffer,
          path: image.path,
          baseDir: image.baseDir,
          baseName: image.baseName,
          dirPath: image.dirPath,
          name: image.name
        }
      });
    });
  }
  var images = getImages(opts);
  var singleList = [], multiLists = [];
  images.forEach(function(value, key) {
    singleList = singleList.concat(value);
    multiLists[key] = layout(layoutOrientation);
    appendLayer(value, multiLists[key]);
    multiLists[key] = multiLists[key].export();
  });
  var singleLayer = layout(layoutOrientation);
  appendLayer(singleList, singleLayer);
  switch (opts.bundleMode) {
    case opts.oneBundle:
      return singleLayer.export();
      break;
    case opts.multipleBundle:
      return multiLists;
    default:
      return singleLayer.export();
  }
}
module.exports = function (opt) {
  opt = lodash.extend({}, {name: 'sprite', format: 'png', margin: 4, processor: 'css', cssPath: '../images', orientation: 'vertical', sort: true, interpolation: 'grid'}, opt);
  opt.styleExtension = (opt.processor === 'stylus') ? 'styl' : opt.processor;
  var layoutOrientation = opt.orientation === 'vertical' ? 'top-down' : opt.orientation === 'horizontal' ? 'left-right' : 'binary-tree';
  var layer = layout(layoutOrientation, {'sort': opt.sort});

  if (opt.opacity === 0 && opt.format === 'jpg') {
    opt.opacity = 1;
  }

  var color = new Color(opt.background);
  opt.color = color.rgbArray();
  opt.color.push(opt.opacity);

  function queue (file, img) {
    var spriteName = replaceExtension(file.relative, '').replace(/\/|\\|\ /g, '-');
    layer.addItem({
      height: img.height() + 2 * opt.margin,
      width: img.width() + 2 * opt.margin,
      meta: {
        name: spriteName,
        img: img,
        image: opt.cssPath + '/' + opt.name
      }
    });
  }

  function queueImages (file, enc, cb) {
    if (file.isNull()) {
      cb();
      return; // ignore
    }

    if (file.isStream()) {
      cb(new Error('Streaming not supported'));
      return; // ignore
    }
    if (imageinfo(file.contents)) {
      lwip.open(file.contents, imageinfo(file.contents).format.toLowerCase(), function(err, img) {
        if (!err) {
          queue(file, img);
          cb();
        }
        else {
          console.log('Ignoring ' + file.relative + ' -> ' + err.toString());
          cb();
        }
      });
    }
    else {
      console.log('Ignoring ' + file.relative + ' -> no image info');
      cb();
    }
  }

  function createCanvas (layerInfo, cb) {
    lwip.create(layerInfo.width, layerInfo.height, opt.color, function (err, image) {
      async.eachSeries(layerInfo.items, function (sprite, callback) {
        image.paste(sprite.x + opt.margin, sprite.y + opt.margin, sprite.meta.img, callback);
      }, function () {
        cb(image);
      });
    });
  }

  function createNonRetinaCanvas (retinaCanvas, cb) {
    var width = Math.floor(retinaCanvas.width() / 2);
    var height = Math.floor(retinaCanvas.height() / 2);
    retinaCanvas.clone(function(err, clone){
      // tell lwip to use the 'grid' interpolation method when resizing - it makes the resized image look much better
      clone.resize(width, height, opt.interpolation, function (err, image) {
        cb(image);
      });
    });
  }

  function createStyle (layerInfo, sprite, retinaSprite) {
    var sprites = [];
    lodash.forEach(layerInfo.items, function (sprite) {
      sprites.push({
        'name': sprite.meta.name,
        'x': sprite.x + opt.margin,
        'y': sprite.y + opt.margin,
        'width': sprite.width - opt.margin * 2,
        'height': sprite.height - opt.margin * 2,
        'total_width': layerInfo.width,
        'total_height': layerInfo.height,
        'image': sprite.meta.image
      });
    });

    var cachebuster = '';
    if (opt.cachebuster === 'random') {
      cachebuster = '?' + crypto.randomBytes(20).toString('hex');
    }

    if (retinaSprite) {
      sprites.unshift({
        name: retinaSprite.relative,
        type: 'retina',
        image: (!opt.base64) ? url.resolve(opt.cssPath.replace(/\\/g, '/'), retinaSprite.relative) + cachebuster : 'data:' + imageinfo(retinaSprite.buffer).mimeType + ';base64,' + retinaSprite.buffer.toString('base64'),
        total_width: sprite.canvas.width(),
        total_height: sprite.canvas.height()
      });

      lodash.forEach(sprites, function (sprite, i) {
        sprites[i].x = Math.floor(sprite.x / 2);
        sprites[i].y = Math.floor(sprite.y / 2);
        sprites[i].width = Math.floor(sprite.width / 2);
        sprites[i].height = Math.floor(sprite.height / 2);
      });
    }

    sprites.unshift({
      name: sprite.relative,
      type: 'sprite',
      image: (!opt.base64) ? url.resolve(opt.cssPath.replace(/\\/g, '/'), sprite.relative) + cachebuster : 'data:' + imageinfo(sprite.buffer).mimeType + ';base64,' + sprite.buffer.toString('base64'),
      total_width: sprite.canvas.width,
      total_height: sprite.canvas.height
    });

    return json2css(sprites, {'format': 'sprite', formatOpts: {'cssClass': opt.prefix, 'processor': opt.processor, 'template': opt.template}});
  }

  function createSprite (cb) {
    var _this = this;
    var layerInfo = layer.export();
    var sprite, nonRetinaSprite, style;
    if (layerInfo.items.length === 0) {
      cb();
      return; // ignore
    }

    async.waterfall([
      function (callback) {
        createCanvas(layerInfo, function (canvas) {
          sprite = {
            base: opt.out,
            relative: opt.name + '.' + opt.format,
            path: path.join(opt.out, opt.name + '.' + opt.format),
            canvas: canvas
          };
          callback(null, sprite);
        });
      },
      function (sprite, callback) {
        if (opt.retina) {
          sprite.path = replaceExtension(sprite.path, '') + '@2x.' + opt.format;
          sprite.relative = replaceExtension(sprite.relative, '') + '@2x.' + opt.format;
          createNonRetinaCanvas(sprite.canvas, function (canvas) {
            nonRetinaSprite = {
              base: opt.out,
              relative: opt.name + '.' + opt.format,
              path: path.join(opt.out, opt.name + '.' + opt.format),
              canvas: canvas
            };
            callback(null, sprite, nonRetinaSprite);
          });
        }
        else {
          callback(null, sprite, null);
        }
      },
      function (sprite, nonRetinaSprite, callback) {
        if (nonRetinaSprite) {
          nonRetinaSprite.canvas.toBuffer(opt.format, {}, function (err, buf) {
            nonRetinaSprite.buffer = buf;
            callback(null, sprite, nonRetinaSprite);
          });
        }
        else {
          callback(null, sprite, nonRetinaSprite);
        }
      },
      function (sprite, nonRetinaSprite, callback) {
        sprite.canvas.toBuffer(opt.format, {}, function (err, buf) {
          sprite.buffer = buf;
          callback(null, sprite, nonRetinaSprite);
        });
      },
      function (sprite, nonRetinaSprite, callback) {
        if (!opt.base64) {
          if (nonRetinaSprite) {
            _this.push(new File({
              base: nonRetinaSprite.base,
              path: nonRetinaSprite.path,
              contents: nonRetinaSprite.buffer
            }));
          }
          _this.push(new File({
            base: sprite.base,
            path: sprite.path,
            contents: sprite.buffer
          }));
        }
        callback(null, sprite, nonRetinaSprite);
      },
      function (sprite, nonRetinaSprite, callback) {
        if (opt.style || opt.base64) {
          style = opt.retina ? createStyle(layerInfo, nonRetinaSprite, sprite) : createStyle(layerInfo, sprite);
          _this.push(new File({
            base: !opt.base64 ? path.dirname(opt.style) : opt.out,
            path: opt.style ? opt.style : path.join(opt.out, replaceExtension(opt.name, '.' + opt.styleExtension)),
            contents: new Buffer(style)
          }));
        }
        callback(null);
      }
    ], function () {
      cb();
    });
  }

  return through2.obj(queueImages, createSprite);
};
Example #9
0
  return through2.obj(function (css, enc, callback) {

    var color = new Color(opt.background)
    opt.color = color.rgbArray()
    opt.color.push(opt.opacity)

    var layoutOrientation = opt.orientation === 'vertical' ? 'top-down' : opt.orientation === 'horizontal' ? 'left-right' : 'binary-tree'
    var layer = layout(layoutOrientation, {'sort': opt.sort})
    var layer2x = layout(layoutOrientation, {'sort': opt.sort})
    var layer3x = layout(layoutOrientation, {'sort': opt.sort})
    var cssContent
    var originCssContent
    var images = []
    var image1x = null
    var image2x = null
    var image3x = null

    RegExp.escape = function (s) {
      return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
    }

    //保存CSS名称
    var cssBaseName = path.basename(css.path, '.css')

    var _this = this

    async.waterfall([

      //找出需要进行合并的图片
      function (cb) {

        originCssContent = cssContent = css.contents.toString()
        var sliceRegex = new RegExp('background-image:[\\s]*url\\(["\']?(?!http[s]?|/)[^)]*?(' + opt.slicePath + '/[\\w\\d\\s!./\\-\\_@]*\\.[\\w?#]+)["\']?\\)\\s?(\\!important)?[^;]*?', 'ig')
        var codelines = cssContent.match(sliceRegex)

        if (!codelines || codelines.length === 0) {

          _this.push(new File({
            base: opt.cssOut,
            path: path.join(opt.cssOut, cssBaseName + ".css"),
            contents: new Buffer(cssContent)
          }))

          return callback(null)
        }

        var index = opt.slicePath.lastIndexOf('/')

        if (index === -1) {
          opt.spriteOut = 'sprite/'
        } else {
          opt.spriteOut = opt.slicePath.substring(0, index + 1) + 'sprite/'
        }

        async.eachSeries(codelines, function (backgroundCodeLine, eachCb) {

          // 统一为无引号
          var fixedQuoteReg = new RegExp('url\\(\\\\?["\']{1}(' + opt.slicePath.replace(/\./g, '\\.') + '\\/[^\\\\"\']*)\\\\?["\']{1}\\)', 'i')
          var m = backgroundCodeLine.match(fixedQuoteReg);
          var backgroundCodeLineFixedQuote = backgroundCodeLine;
          if(m && m.length) {
            backgroundCodeLineFixedQuote = 'background-image: url(' + m[1] + ')'
            cssContent = cssContent.split(backgroundCodeLine).join(backgroundCodeLineFixedQuote)
          }

          var relativePath = backgroundCodeLine.replace(sliceRegex, '$1')
          var absolutePath = path.join(path.dirname(css.path), relativePath)

          if (lodash.includes(images, absolutePath)) {
            return eachCb()
          }

          images.push(absolutePath)

          var meta = {
            backgroundCodeLine: backgroundCodeLineFixedQuote,//匹配出的代码行内容
            fileName: path.basename(relativePath),//文件名
            relativePath: relativePath,//相对路径
            absolutePath: absolutePath,//绝对路径
            has2x: fs.existsSync(absolutePath.replace('.png', '@2x.png')),//是否有 @2x 图
            absolute2xPath: absolutePath.replace('.png', '@2x.png'),
            has3x: fs.existsSync(absolutePath.replace('.png', '@3x.png')),//是否有 @3x 图
            absolute3xPath: absolutePath.replace('.png', '@3x.png'),
            margin: opt.margin
          }

          //如果直接引用 @2x 图
          if (backgroundCodeLine.indexOf('@2x') > 0) {
            meta.has2x = true
            meta.absolute2xPath = absolutePath

            if (absolutePath.indexOf('@2x') > 0) {
              meta.has3x = fs.existsSync(absolutePath.replace('@2x.png', '@3x.png'))
              meta.absolute3xPath = absolutePath.replace('@2x.png', '@3x.png')
            }
          }

          //如果直接引用 @3x 图
          if (backgroundCodeLine.indexOf('@3x') > 0) {
            meta.has3x = true
            meta.absolute3xPath = absolutePath

            if (absolutePath.indexOf('@3x')) {
              meta.has2x = fs.existsSync(absolutePath.replace('@3x.png', '@2x.png'))
              meta.absolute2xPath = absolutePath.replace('@3x.png', '@2x.png')
            }
          }

          //通过正则,匹配出 className
          var regexClassNameString = '(\\.?[^}]*?)\\s?{\\s?[^}]*?' + RegExp.escape(backgroundCodeLine)
          var regexClassName = new RegExp(regexClassNameString, 'ig')
          var classNameResult = cssContent.match(regexClassName)
          meta.className2x = []
          meta.className3x = []

          async.series([
            function (next) {
              layerAddItem(layer, absolutePath, meta, next)
            },

            function (next) {
              if (meta.has2x) {

                for (var key in classNameResult) {
                  meta.className2x.push(classNameResult[key].replace(regexClassName, '$1'))
                }

                meta.margin = opt.margin * 2

                layerAddItem(layer2x, meta.absolute2xPath, meta, next)
              } else {
                next()
              }
            },

            function (next) {
              if (meta.has3x) {
                for (var key in classNameResult) {
                  meta.className3x.push(classNameResult[key].replace(regexClassName, '$1'))
                }

                meta.margin = opt.margin * 3

                layerAddItem(layer3x, meta.absolute3xPath, meta, next)
              } else {
                next()
              }
            }

          ], function () {

            eachCb()

          })

        }, function () {
          cb(null, cssContent)
        })

      },

      //雪碧图布局排列
      //CSS替换
      function (cssContent, cb) {

        var layerInfo = layer.export()

        if (layerInfo.items.length > 0) {

          lwip.create(layerInfo.width, layerInfo.height, opt.color, function (err, image) {

            async.eachSeries(layerInfo.items, function (sprite, callback) {

              //图片合并
              image.paste(sprite.x + sprite.meta.margin, sprite.y + sprite.meta.margin, sprite.meta.img, callback)

              //CSS替换
              var code = 'background-image: url("' + opt.spriteOut + cssBaseName + '.png");'
              code += '  background-position: -' + (sprite.x + sprite.meta.margin) + 'px -' + (sprite.y + sprite.meta.margin) + 'px;'

              cssContent = cssContent.split(sprite.meta.backgroundCodeLine).join(code)

            }, function () {
              image1x = image
              cb(null, cssContent)
            })

          })

        } else {
          cb(null, cssContent)
        }

      },

      //2x
      function (cssContent, cb) {
        if (typeof cssContent === 'function') {
          cssContent = originCssContent
        }

        var retinaLayerInfo = layer2x.export()

        //存在 @2x 图
        if (retinaLayerInfo.items.length > 0) {

          var retinaCssContent = '\n\n@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (min-resolution: 240dpi) {'

          lwip.create(retinaLayerInfo.width, retinaLayerInfo.height, opt.color, function (err, image) {

            async.eachSeries(retinaLayerInfo.items, function (sprite, callback) {

              //图片合并
              image.paste(sprite.x + sprite.meta.margin, sprite.y + sprite.meta.margin, sprite.meta.img, callback)

              cssContent = cssContent.split(sprite.meta.backgroundCodeLine).join('');

              //添加 media query
              lodash.each(sprite.meta.className2x, function (item) {
                retinaCssContent += item
                retinaCssContent += '{background-image:url("' + opt.spriteOut + cssBaseName + '@2x.png");'
                retinaCssContent += '-webkit-background-size:' + retinaLayerInfo.width / 2 + 'px;'
                retinaCssContent += 'background-size:' + retinaLayerInfo.width / 2 + 'px;'
                retinaCssContent += 'background-position: -' + ((sprite.x + sprite.meta.margin) / 2) + 'px -' + ((sprite.y + sprite.meta.margin) / 2) + 'px;}'
              })

            }, function () {
              retinaCssContent += "}"

              cssContent += retinaCssContent

              image2x = image

              cb(null, cssContent)
            })

          })


        } else {
          if (image1x) {
            cb(null, cssContent)
          } else {
            cb(null)
          }
        }

      },

      //@3x
      function (cssContent, cb) {

        if (typeof cssContent === 'function') {
          cssContent = originCssContent
        }

        var retinaLayerInfo = layer3x.export()

        //存在 @3x 图
        if (retinaLayerInfo.items.length > 0) {

          var retinaCssContent = '\n\n@media only screen and (min-device-width: 414px) and (-webkit-min-device-pixel-ratio: 3) {'

          lwip.create(retinaLayerInfo.width, retinaLayerInfo.height, opt.color, function (err, image) {

            async.eachSeries(retinaLayerInfo.items, function (sprite, callback) {

              //图片合并
              image.paste(sprite.x + sprite.meta.margin, sprite.y + sprite.meta.margin, sprite.meta.img, callback)

              cssContent = cssContent.replace(sprite.meta.backgroundCodeLine, '')

              //添加 media query
              lodash.each(sprite.meta.className3x, function (item) {
                retinaCssContent += item
                retinaCssContent += '{background-image:url("' + opt.spriteOut + cssBaseName + '@3x.png");'
                retinaCssContent += '-webkit-background-size:' + retinaLayerInfo.width / 3 + 'px;'
                retinaCssContent += 'background-size:' + retinaLayerInfo.width / 3 + 'px;'
                retinaCssContent += 'background-position: -' + ((sprite.x + sprite.meta.margin) / 3) + 'px -' + ((sprite.y + sprite.meta.margin) / 3) + 'px;}'
              })

            }, function () {
              retinaCssContent += "}"

              cssContent += retinaCssContent

              image3x = image

              cb(null, cssContent)
            })

          })


        } else {
          if (image2x || image1x) {
            cb(null, cssContent)
          } else {
            cb(null)
          }
        }

      },

      // png to buffer
      // css to buffer
      function (cssContent, cb) {

        _this.push(new File({
          base: opt.cssOut,
          path: path.join(opt.cssOut, cssBaseName + ".css"),
          contents: new Buffer(cssContent)
        }))

        async.series([
          function (next) {
            if (image1x) {
              image1x.toBuffer('png', {}, function (err, spriteBuffer) {

                _this.push(new File({
                  base: opt.spriteOut,
                  path: path.join(opt.spriteOut, cssBaseName + '.png'),
                  contents: spriteBuffer
                }))

                next()
              })
            } else {
              next()
            }
          },

          function (next) {
            if (image2x) {
              image2x.toBuffer('png', {}, function (err, retinaSpriteBuffer) {

                _this.push(new File({
                  base: opt.spriteOut,
                  path: path.join(opt.spriteOut, cssBaseName + '@2x.png'),
                  contents: retinaSpriteBuffer
                }))

                next()

              })
            } else {
              next()
            }
          },

          function (next) {
            if (image3x) {
              image3x.toBuffer('png', {}, function (err, retinaSpriteBuffer) {

                _this.push(new File({
                  base: opt.spriteOut,
                  path: path.join(opt.spriteOut, cssBaseName + '@3x.png'),
                  contents: retinaSpriteBuffer
                }))

                next()

              })
            } else {
              next()
            }
          }
        ], function () {
          cb(null)
        })

      }

    ], function () {
      callback()
    })
  })
 images.forEach(function(value, key) {
   singleList = singleList.concat(value);
   multiLists[key] = layout(layoutOrientation);
   appendLayer(value, multiLists[key]);
   multiLists[key] = multiLists[key].export();
 });