[
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Andrea Moretti\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Perfect Layout\n\n[Medium Article](https://medium.com/@axyz/in-search-of-the-perfect-image-gallery-34f46f7615a1)\n\n[DEMO](http://codepen.io/axyz/full/VLJrKr/)\n\ngiven an array of images in the form\n\n```\n{\n  data: WATHEVER_YOU_WANT,\n  src: \"path/to/image.jpg\",\n  ratio: 1.5\n}\n```\n\nreturns an array of images in the form\n```\n{\n  data: WATHEVER_YOU_WANT,\n  src: \"path/to/image.jpg\",\n  width: WIDTH,\n  height: HEIGHT\n}\n```\n\nwhere WIDTH and HEIGHT are the dimensions that image must have to fit the layout.\n\n## Usage\n\non node\n```\n$ npm install --save perfect-layout\n```\nand\n```\nvar perfectLayout = require('perfect-layout')\n```\nwhile on the browser you can just\n```\n<script src=\"perfectLayout.min.js\"></script>\n```\nthen\n```\nvar perfectRows = perfectLayout(photos, width, height, {\n  // default options\n  margin: 0\n});\n```\n\n### Options\n\n- margin: [number]\nIf you are going to use a css margin for your images you need to specify it here\nas well, so that the layout will adapt to scale down the images accordingly.\n\n## Motivations\n\nThis was inspired by [chromatic.io](http://www.chromatic.io/FQrLQsb) galleries\nand I want to credit the [@crispymtn](https://github.com/crispymtn) team for the\noriginal implementation.\n\nThis version aim to be more lightweight using a greedy algorithm instead of the\noptimal one and also leave to the user the responsability to choose how to\nmanipulate the DOM accordingly to the returned array.\n\n## Example jQuery plugin\n\nfor convenience a jquery plugin is included for a basic usage.\n\nassuming that a global `window.photos` array exists as specified above\n\n```\n<div id=\"gallery\"></div>\n\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js\"></script>\n<script src=\"jquery.perfectLayout.min.js\"></script>\n\n<script>\n$(document).ready(function() {\n  $('#gallery').perfectGallery(photos);\n\n  $(window).resize(function() {\n    $('#gallery').perfectGallery(photos);\n  });\n});\n\n</script>\n```\n\n*N.B.* Please note that this is only an example on how to use the `perfectLayout` function.\nThe jQuery plugin is not to be used in production as it do not provide any\ncrossbrowser optimization, at the time of writing it should however correctly\nwork on the latest chrome and firefox browsers on linux.\n\nFor custom behaviour give a look at the [jqueryPlugin.js](https://github.com/axyz/perfect-layout/blob/master/jqueryPlugin.js) \nand use it as a starting point to generate the desired DOM nodes.\n\nthe data field can be used to populate the images with any needed metadata\nyou may need and is probably a good idea to provide it from your backend.\n\n## Changelog\n\n## [v1.2.0]\n### Changed\n- using breakPointPartition thanks to @GreenGremlin\n\n## [v1.1.1]\n### Changed\n- using BST based linear partitioning instead of greedy one\n- huge speed improvement\n- the resulting set is now optimal\n### Fixed\n- the partition will now keep the same order as the input array\n- the layout should now be equal to the chromatic.io one in all the cases\n\n## [v1.1.0]\n### Added\n- margin option\n\n## [v1.0.0]\n### Initial Release\n<!--stackedit_data:\neyJoaXN0b3J5IjpbLTE0OTUwNzc5OTksLTExNjE2ODIwOTFdfQ\n==\n-->"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"perfect-layout\",\n  \"description\": \"Image layout generator based on linear partitioning\",\n  \"main\": \"dist/perfectLayout.min.js\",\n  \"version\": \"1.0.0\",\n  \"authors\": [\n    \"Andrea Moretti <axyzxp@gmail.com>\"\n  ],\n  \"moduleType\": [\n    \"es6\",\n    \"globals\",\n    \"node\"\n  ],\n  \"keywords\": [\n    \"gallery\",\n    \"images\",\n    \"layout\",\n    \"linear\",\n    \"partition\"\n  ],\n  \"license\": \"MIT\",\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"test\",\n    \"tests\"\n  ]\n}\n"
  },
  {
    "path": "dist/jquery.perfectLayout.js",
    "content": "(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, '__esModule', {\n  value: true\n});\nexports['default'] = perfectLayout;\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar _libBreakpointPartitionJs = require('./lib/BreakpointPartition.js');\n\nvar _libBreakpointPartitionJs2 = _interopRequireDefault(_libBreakpointPartitionJs);\n\nfunction perfectLayout(photos, screenWidth, screenHeight, opts) {\n  opts = opts || {};\n  opts.margin = opts.margin || 0;\n\n  var rows = _perfectRowsNumber(photos, screenWidth, screenHeight);\n  var idealHeight = parseInt(screenHeight / 2, 10);\n\n  if (rows < 1) {\n    return photos.map(function (img) {\n      return {\n        data: img.data,\n        src: img.src,\n        width: parseInt(idealHeight * img.ratio) - opts.margin * 2,\n        height: idealHeight\n      };\n    });\n  } else {\n    var _ret = (function () {\n      var weights = photos.map(function (img) {\n        return parseInt(img.ratio * 100, 10);\n      });\n      var partitions = (0, _libBreakpointPartitionJs2['default'])(weights, rows);\n\n      var current = 0;\n\n      return {\n        v: partitions.map(function (row) {\n          var summedRatios = row.reduce(function (sum, el, i) {\n            return sum + photos[current + i].ratio;\n          }, 0);\n\n          return row.map(function () {\n            var img = photos[current++];\n\n            return {\n              data: img.data,\n              src: img.src,\n              width: parseInt(screenWidth / summedRatios * img.ratio, 10) - opts.margin * 2,\n              height: parseInt(screenWidth / summedRatios, 10)\n            };\n          });\n        })\n      };\n    })();\n\n    if (typeof _ret === 'object') return _ret.v;\n  }\n}\n\nfunction _perfectRowsNumber(photos, screenWidth, screenHeight) {\n  var idealHeight = parseInt(screenHeight / 2);\n  var totalWidth = photos.reduce(function (sum, img) {\n    return sum + img.ratio * idealHeight;\n  }, 0);\n  return Math.round(totalWidth / screenWidth);\n}\nmodule.exports = exports['default'];\n\n},{\"./lib/BreakpointPartition.js\":3}],2:[function(require,module,exports){\n'use strict';\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar _ = require('.');\n\nvar _2 = _interopRequireDefault(_);\n\n$.fn.perfectLayout = function (photos) {\n  var node = this;\n  var scrollBarSize = $('html').hasClass('touch') ? 0 : 15;\n  var perfectRows = (0, _2['default'])(photos, window.innerWidth - scrollBarSize, $(window).height());\n  node.empty();\n\n  perfectRows.forEach(function (row) {\n    row.forEach(function (img) {\n      var imgNode = $('<div style=\"float: left;\" class=\"image\"></div>');\n      imgNode.css({\n        'width': img.width + 'px',\n        'height': img.height + 'px',\n        'background': 'url(' + img.src + ')',\n        'background-size': 'cover'\n      });\n      node.append(imgNode);\n    });\n  });\n};\n\n},{\".\":1}],3:[function(require,module,exports){\n// Rather than blindly perform a binary search from the maximum width. It starts\n// from the ideal width (The ideal width being the width if the images fit\n// perfectly in the given container.) and expands to the next width that will\n// allow an item to move up a row. This algorithm will find the exact width that\n// produces the \"ideal\" layout and should generally find it in two or three\n// passes.\n'use strict';\n\nObject.defineProperty(exports, '__esModule', {\n  value: true\n});\nexports['default'] = BreakpointPartition;\n\nfunction BreakpointPartition(imageRatioSequence, expectedRowCount) {\n  if (imageRatioSequence.length <= 1) return [imageRatioSequence];\n  if (expectedRowCount >= imageRatioSequence.length) return imageRatioSequence.map(function (item) {\n    return [item];\n  });\n\n  var layoutWidth = findLayoutWidth(imageRatioSequence, expectedRowCount);\n  var currentRow = 0;\n\n  return imageRatioSequence.reduce(function (rows, imageRatio) {\n    if (sum(rows[currentRow]) + imageRatio > layoutWidth) currentRow++;\n    rows[currentRow].push(imageRatio);\n    return rows;\n    // waiting for more elegant solutions (Array.fill) to work correctly\n  }, new Array(expectedRowCount).join().split(',').map(function () {\n    return [];\n  }));\n}\n\n// starting at the ideal width, expand to the next breakpoint until we find\n// a width that produces the expected number of rows\nfunction findLayoutWidth(imageRatioSequence, expectedRowCount) {\n  var idealWidth = sum(imageRatioSequence) / expectedRowCount;\n  var widestItem = Math.max.apply(null, imageRatioSequence);\n  var galleryWidth = Math.max(idealWidth, widestItem);\n  var layout = getLayoutDetails(imageRatioSequence, galleryWidth);\n\n  while (layout.rowCount > expectedRowCount) {\n    galleryWidth += layout.nextBreakpoint;\n\n    layout = getLayoutDetails(imageRatioSequence, galleryWidth);\n  }\n  return galleryWidth;\n}\n\n// find the\nfunction getLayoutDetails(imageRatioSequence, expectedWidth) {\n  var startingLayout = {\n    currentRowWidth: 0,\n    rowCount: 1,\n    // the largest possible step to the next breakpoint is the smallest image ratio\n    nextBreakpoint: Math.min.apply(null, imageRatioSequence)\n  };\n\n  var finalLayout = imageRatioSequence.reduce(function (layout, itemWidth) {\n    var rowWidth = layout.currentRowWidth + itemWidth;\n    var currentRowsNextBreakpoint = undefined;\n\n    if (rowWidth > expectedWidth) {\n      currentRowsNextBreakpoint = rowWidth - expectedWidth;\n      if (currentRowsNextBreakpoint < layout.nextBreakpoint) {\n        layout.nextBreakpoint = currentRowsNextBreakpoint;\n      }\n      layout.rowCount += 1;\n      layout.currentRowWidth = itemWidth;\n    } else {\n      layout.currentRowWidth = rowWidth;\n    }\n\n    return layout;\n  }, startingLayout);\n  return {\n    rowCount: finalLayout.rowCount,\n    nextBreakpoint: finalLayout.nextBreakpoint\n  };\n}\n\nfunction sum(arr) {\n  return arr.reduce(function (sum, el) {\n    return sum + el;\n  }, 0);\n}\nmodule.exports = exports['default'];\n\n},{}]},{},[2]);\n"
  },
  {
    "path": "dist/perfectLayout.js",
    "content": "(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.perfectLayout = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\n'use strict';\n\nObject.defineProperty(exports, '__esModule', {\n  value: true\n});\nexports['default'] = perfectLayout;\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar _libBreakpointPartitionJs = require('./lib/BreakpointPartition.js');\n\nvar _libBreakpointPartitionJs2 = _interopRequireDefault(_libBreakpointPartitionJs);\n\nfunction perfectLayout(photos, screenWidth, screenHeight, opts) {\n  opts = opts || {};\n  opts.margin = opts.margin || 0;\n\n  var rows = _perfectRowsNumber(photos, screenWidth, screenHeight);\n  var idealHeight = parseInt(screenHeight / 2, 10);\n\n  if (rows < 1) {\n    return photos.map(function (img) {\n      return {\n        data: img.data,\n        src: img.src,\n        width: parseInt(idealHeight * img.ratio) - opts.margin * 2,\n        height: idealHeight\n      };\n    });\n  } else {\n    var _ret = (function () {\n      var weights = photos.map(function (img) {\n        return parseInt(img.ratio * 100, 10);\n      });\n      var partitions = (0, _libBreakpointPartitionJs2['default'])(weights, rows);\n\n      var current = 0;\n\n      return {\n        v: partitions.map(function (row) {\n          var summedRatios = row.reduce(function (sum, el, i) {\n            return sum + photos[current + i].ratio;\n          }, 0);\n\n          return row.map(function () {\n            var img = photos[current++];\n\n            return {\n              data: img.data,\n              src: img.src,\n              width: parseInt(screenWidth / summedRatios * img.ratio, 10) - opts.margin * 2,\n              height: parseInt(screenWidth / summedRatios, 10)\n            };\n          });\n        })\n      };\n    })();\n\n    if (typeof _ret === 'object') return _ret.v;\n  }\n}\n\nfunction _perfectRowsNumber(photos, screenWidth, screenHeight) {\n  var idealHeight = parseInt(screenHeight / 2);\n  var totalWidth = photos.reduce(function (sum, img) {\n    return sum + img.ratio * idealHeight;\n  }, 0);\n  return Math.round(totalWidth / screenWidth);\n}\nmodule.exports = exports['default'];\n\n},{\"./lib/BreakpointPartition.js\":2}],2:[function(require,module,exports){\n// Rather than blindly perform a binary search from the maximum width. It starts\n// from the ideal width (The ideal width being the width if the images fit\n// perfectly in the given container.) and expands to the next width that will\n// allow an item to move up a row. This algorithm will find the exact width that\n// produces the \"ideal\" layout and should generally find it in two or three\n// passes.\n'use strict';\n\nObject.defineProperty(exports, '__esModule', {\n  value: true\n});\nexports['default'] = BreakpointPartition;\n\nfunction BreakpointPartition(imageRatioSequence, expectedRowCount) {\n  if (imageRatioSequence.length <= 1) return [imageRatioSequence];\n  if (expectedRowCount >= imageRatioSequence.length) return imageRatioSequence.map(function (item) {\n    return [item];\n  });\n\n  var layoutWidth = findLayoutWidth(imageRatioSequence, expectedRowCount);\n  var currentRow = 0;\n\n  return imageRatioSequence.reduce(function (rows, imageRatio) {\n    if (sum(rows[currentRow]) + imageRatio > layoutWidth) currentRow++;\n    rows[currentRow].push(imageRatio);\n    return rows;\n    // waiting for more elegant solutions (Array.fill) to work correctly\n  }, new Array(expectedRowCount).join().split(',').map(function () {\n    return [];\n  }));\n}\n\n// starting at the ideal width, expand to the next breakpoint until we find\n// a width that produces the expected number of rows\nfunction findLayoutWidth(imageRatioSequence, expectedRowCount) {\n  var idealWidth = sum(imageRatioSequence) / expectedRowCount;\n  var widestItem = Math.max.apply(null, imageRatioSequence);\n  var galleryWidth = Math.max(idealWidth, widestItem);\n  var layout = getLayoutDetails(imageRatioSequence, galleryWidth);\n\n  while (layout.rowCount > expectedRowCount) {\n    galleryWidth += layout.nextBreakpoint;\n\n    layout = getLayoutDetails(imageRatioSequence, galleryWidth);\n  }\n  return galleryWidth;\n}\n\n// find the\nfunction getLayoutDetails(imageRatioSequence, expectedWidth) {\n  var startingLayout = {\n    currentRowWidth: 0,\n    rowCount: 1,\n    // the largest possible step to the next breakpoint is the smallest image ratio\n    nextBreakpoint: Math.min.apply(null, imageRatioSequence)\n  };\n\n  var finalLayout = imageRatioSequence.reduce(function (layout, itemWidth) {\n    var rowWidth = layout.currentRowWidth + itemWidth;\n    var currentRowsNextBreakpoint = undefined;\n\n    if (rowWidth > expectedWidth) {\n      currentRowsNextBreakpoint = rowWidth - expectedWidth;\n      if (currentRowsNextBreakpoint < layout.nextBreakpoint) {\n        layout.nextBreakpoint = currentRowsNextBreakpoint;\n      }\n      layout.rowCount += 1;\n      layout.currentRowWidth = itemWidth;\n    } else {\n      layout.currentRowWidth = rowWidth;\n    }\n\n    return layout;\n  }, startingLayout);\n  return {\n    rowCount: finalLayout.rowCount,\n    nextBreakpoint: finalLayout.nextBreakpoint\n  };\n}\n\nfunction sum(arr) {\n  return arr.reduce(function (sum, el) {\n    return sum + el;\n  }, 0);\n}\nmodule.exports = exports['default'];\n\n},{}]},{},[1])(1)\n});"
  },
  {
    "path": "index.js",
    "content": "import BreakpointPartition from './lib/BreakpointPartition.js';\n\nexport default function perfectLayout(photos, screenWidth, screenHeight, opts) {\n  opts = opts || {};\n  opts.margin = opts.margin || 0;\n\n  const rows = _perfectRowsNumber(photos, screenWidth, screenHeight);\n  const idealHeight = parseInt(screenHeight / 2, 10);\n\n  if (rows < 1) {\n    return photos.map(img => {\n      return {\n        data: img.data,\n        src: img.src,\n        width: parseInt(idealHeight * img.ratio) - (opts.margin * 2),\n        height: idealHeight\n      };\n    });\n  } else {\n    const weights = photos.map(img => parseInt(img.ratio * 100, 10));\n    const partitions = BreakpointPartition(weights, rows);\n\n    let current = 0;\n\n    return partitions.map(row => {\n      const summedRatios = row.reduce((sum, el, i) => sum + photos[current + i].ratio, 0);\n\n      return row.map(() => {\n        const img = photos[current++];\n\n        return {\n          data: img.data,\n          src: img.src,\n          width: parseInt((screenWidth / summedRatios) * img.ratio, 10) - (opts.margin * 2),\n          height: parseInt(screenWidth / summedRatios, 10)\n        };\n      });\n    });\n  }\n}\n\nfunction _perfectRowsNumber(photos, screenWidth, screenHeight) {\n  const idealHeight = parseInt(screenHeight / 2);\n  const totalWidth = photos.reduce((sum, img) => sum + img.ratio * idealHeight, 0);\n  return Math.round(totalWidth / screenWidth);\n}\n"
  },
  {
    "path": "jqueryPlugin.js",
    "content": "import perfectLayout from '.';\n\n$.fn.perfectLayout = function(photos) {\n  const node = this;\n  const scrollBarSize = $('html').hasClass('touch') ? 0 : 15;\n  const perfectRows = perfectLayout(photos, window.innerWidth - scrollBarSize, $(window).height());\n  node.empty();\n\n  perfectRows.forEach(function (row) {\n    row.forEach(function (img) {\n      var imgNode = $('<div style=\"float: left;\" class=\"image\"></div>');\n      imgNode.css({\n        'width': img.width + 'px',\n        'height': img.height + 'px',\n        'background': 'url(' + img.src + ')',\n        'background-size': 'cover'\n      });\n      node.append(imgNode);\n    });\n  });\n};\n"
  },
  {
    "path": "lib/.tern-port",
    "content": "34063"
  },
  {
    "path": "lib/BSTLinearPartition.js",
    "content": "export default function BSTLinearPartition(seq, k) {\n  if (seq.length <= 1) return [seq];\n  if (k >= seq.length) return seq.map(el => [el]);\n\n  const limit = threshold(seq, k);\n  let current = 0;\n\n  return seq.reduce((res, el) => {\n    if (sum(res[current]) + el > limit) current++;\n    res[current].push(el);\n    return res;\n    // waiting for more elegant solutions (Array.fill) to work correctly\n  }, new Array(k).join().split(',').map(() => []));\n}\n\n// find the perfect limit that we should not pass when adding elements\n// to a single partition.\nfunction threshold(seq, k) {\n  let bottom = max(seq);\n  let top = sum(seq);\n\n  while (bottom < top) {\n    const mid = bottom + ( top - bottom) / 2;\n\n    if (requiredElements(seq, mid) <= k) {\n      top = mid;\n    } else {\n      bottom = mid + 1;\n    }\n  }\n  return bottom;\n}\n\n// find how many elements from [seq] we cann group together stating below\n// [limit] by adding their weights\nfunction requiredElements(seq, limit) {\n  return seq.reduce((res, el) => {\n    res.tot += el;\n    if (res.tot > limit) {\n      res.tot = el;\n      res.n++;\n    }\n    return res;\n  }, {tot: 0, n: 1}).n;\n}\n\nfunction sum(arr) {\n  return arr.reduce((sum, el) => sum + el, 0);\n}\n\nfunction max(arr) {\n  return arr.reduce((max, el) => el > max ? el : max, 0);\n}\n"
  },
  {
    "path": "lib/BreakpointPartition.js",
    "content": "// Rather than blindly perform a binary search from the maximum width. It starts\n// from the ideal width (The ideal width being the width if the images fit\n// perfectly in the given container.) and expands to the next width that will\n// allow an item to move up a row. This algorithm will find the exact width that\n// produces the \"ideal\" layout and should generally find it in two or three\n// passes.\nexport default function BreakpointPartition(imageRatioSequence, expectedRowCount) {\n  if (imageRatioSequence.length <= 1) return [imageRatioSequence];\n  if (expectedRowCount >= imageRatioSequence.length)\n    return imageRatioSequence.map(item => [item]);\n\n  const layoutWidth = findLayoutWidth(imageRatioSequence, expectedRowCount);\n  let currentRow = 0;\n\n  return imageRatioSequence.reduce((rows, imageRatio) => {\n    if (sum(rows[currentRow]) + imageRatio > layoutWidth) currentRow++;\n    rows[currentRow].push(imageRatio);\n    return rows;\n    // waiting for more elegant solutions (Array.fill) to work correctly\n  }, new Array(expectedRowCount).join().split(',').map(() => []));\n}\n\n// starting at the ideal width, expand to the next breakpoint until we find\n// a width that produces the expected number of rows\nfunction findLayoutWidth(imageRatioSequence, expectedRowCount) {\n  let idealWidth = sum(imageRatioSequence) / expectedRowCount;\n  let widestItem = Math.max.apply(null, imageRatioSequence);\n  let galleryWidth = Math.max(idealWidth, widestItem);\n  let layout = getLayoutDetails(imageRatioSequence, galleryWidth);\n\n  while (layout.rowCount > expectedRowCount) {\n    galleryWidth += layout.nextBreakpoint;\n\n    layout = getLayoutDetails(imageRatioSequence, galleryWidth);\n  }\n  return galleryWidth;\n}\n\n// find the\nfunction getLayoutDetails(imageRatioSequence, expectedWidth) {\n  const startingLayout = {\n    currentRowWidth: 0,\n    rowCount: 1,\n    // the largest possible step to the next breakpoint is the smallest image ratio\n    nextBreakpoint: Math.min.apply(null, imageRatioSequence)\n  };\n\n  const finalLayout = imageRatioSequence.reduce((layout, itemWidth) => {\n    const rowWidth = layout.currentRowWidth + itemWidth;\n    let currentRowsNextBreakpoint;\n\n    if (rowWidth > expectedWidth) {\n      currentRowsNextBreakpoint = rowWidth - expectedWidth;\n      if (currentRowsNextBreakpoint < layout.nextBreakpoint) {\n        layout.nextBreakpoint = currentRowsNextBreakpoint;\n      }\n      layout.rowCount += 1;\n      layout.currentRowWidth = itemWidth;\n    } else {\n      layout.currentRowWidth = rowWidth;\n    }\n\n    return layout;\n  }, startingLayout);\n  return {\n    rowCount: finalLayout.rowCount,\n    nextBreakpoint: finalLayout.nextBreakpoint\n  };\n}\n\nfunction sum(arr) {\n  return arr.reduce((sum, el) => sum + el, 0);\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"perfect-layout\",\n  \"version\": \"1.2.1\",\n  \"description\": \"Image layout generator based on linear partitioning\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"build\": \"./node_modules/.bin/browserify index.js -t babelify -t uglifyify --standalone perfectLayout -o dist/perfectLayout.min.js\",\n    \"build-dev\": \"./node_modules/.bin/browserify index.js -t babelify --standalone perfectLayout -o dist/perfectLayout.js\",\n    \"build-jquery\": \"npm run build && ./node_modules/.bin/browserify jqueryPlugin.js -t babelify -t uglifyify -o dist/jquery.perfectLayout.min.js\",\n    \"build-jquery-dev\": \"npm run build-dev && ./node_modules/.bin/browserify jqueryPlugin.js -t babelify -o dist/jquery.perfectLayout.js\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"dist\": \"npm run build && npm run build-jquery && npm run build-dev && npm run build-jquery-dev\"\n  },\n  \"author\": \"Andrea Moretti (@axyz) <axyzxp@gmail.com>\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"babelify\": \"^6.2.0\",\n    \"browserify\": \"^11.0.1\",\n    \"uglifyify\": \"^3.0.1\"\n  }\n}\n"
  }
]