[
  {
    "path": ".gitignore",
    "content": ".idea/\nnode_modules/"
  },
  {
    "path": ".npmignore",
    "content": "node_modules\nexamples\nbower.json\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "module.exports = function (grunt) {\n\n  var saveLicense = require('uglify-save-license');\n  // project configuration\n  grunt.initConfig({\n    pkg: grunt.file.readJSON('package.json'),\n\n    uglify: {\n      my_target: {\n        options: {\n          preserveComments: saveLicense\n        },\n        src: ['canvas-text-wrapper.js'],\n        dest: 'canvas-text-wrapper.min.js'\n      }\n    }\n  });\n\n  // load plugins\n  grunt.loadNpmTasks('uglify-save-license');\n  grunt.loadNpmTasks('grunt-contrib-uglify');\n\n  // default tasks.\n  grunt.registerTask('default', [\n    'uglify'\n  ]);\n};"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\nCopyright (c) 2016 Vadim Namniak\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,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE\nOR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# canvas-text-wrapper\n\n\n## Syntax\n```javascript\nCanvasTextWrapper(HTMLCanvasElement, String [, options]);\n```\n\n## Options\n\n| Option  | Value | Description |\n| ------------- | ------------- | ------------- |\n| **font**  | *String*  | Text style that includes font size (in px), font weight, font family, etc.  |\n| **lineHeight**  | *String* or *Number* | Number - 'n' times font size where 1 is equivalent to '100%'. Also the property can be set in '%' or 'px'. |\n| **textAlign**  | `\"left\"` `\"center\"` `\"right\"` | Horizontal alignment of each line. |\n| **verticalAlign** | `\"top\"`  `\"middle\"` `\"bottom\"` | Vertical alignment of the whole text block. |\n| **paddingX**  | *Number* | Horizontal padding (in px) that is equally set on left and right sides. |\n| **paddingY**  | *Number* | Vertical padding (in px) that is equally set on top and bottoms. |\n| **fitParent**  | *Boolean* | Fit canvas' container size instead of its own size. |\n| **lineBreak**  | `\"auto\"` `\"word\"` |  `\"auto\"` - text goes to the next line on a whole word when there's no room  `\"word\"` - each next word is placed on a new line |\n| **sizeToFill**  | *Boolean* |  Ignore given font size and line height and resize text to fill its padded container. |\n| **maxFontSizeToFill**  | *Number* |  If above option is `true` text won't be bigger than set. |\n| **strokeText**  | *Boolean* |  Allow text outline based on canvas context configuration. |\n| **justifyLines**  | *Boolean* |  All lines will try to match the same width with flexed spaces between the words. |\n| **allowNewLine**  | *Boolean* |  Text breaks on a new line character \"\\n\". Supports multiple consecutive new lines. |\n| **renderHDPI**  | *Boolean* |  Text is rendered based on device pixel ratio. |\n| **textDecoration**  | `\"none\"`  `\"underline\"` |  Text is underlined according to `context.strokeStyle` and `context.lineWidth` |\n\nNOTE: if a single word is too long to fit the width with specified font size, it will break on any letter unless ```sizeToFill``` option is enabled.\n\n\n## Default options\n```javascript\n   { \n        font: '18px Arial, sans-serif',\n        lineHeight: 1,\n        textAlign: 'left',\n        verticalAlign: 'top',\n        paddingX: 0,\n        paddingY: 0,\n        fitParent: false,\n        lineBreak: 'auto',\n        strokeText: false\n        sizeToFill: false,\n        maxFontSizeToFill: false,\n        allowNewLine: true,\n        justifyLines: false,\n        renderHDPI: true,\n        textDecoration: 'none'\n    }\n```\n\n\n## Usage\nConfigure context properties such as ```fillStyle```, ```lineWidth```, ```strokeStyle``` etc. before passing it to CanvasTextWrapper like so:\n\n```javascript\nvar CanvasTextWrapper = require('canvas-text-wrapper').CanvasTextWrapper;\n\nvar canvas = document.getElementById('#canvasText');\ncanvas.width = 200;\ncanvas.height = 200;\ncontext = canvas.getContext('2d');\ncontext.lineWidth = 2;\ncontext.strokeStyle = '#ff0000';\n\nCanvasTextWrapper(canvas, 'Hello', {strokeText: true});\n```\n\n\n## Test\nRun ```npm t```\nNOTE: Test requires [beefy](http://didact.us/beefy/) to be installed globally \n\n\n## Examples\n[see here](http://namniak.github.io/canvas-text-wrapper/)\n\n\n## Install\n```sh\nnpm i canvas-text-wrapper --save\nbower install canvas-text-wrapper\n```\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"canvas-text-wrapper\",\n  \"version\": \"0.9.2\",\n  \"ignore\": [\n    \"**/.*\",\n    \"**/*.log\",\n    \"**/*.json\",\n    \"Gruntfile.js\",\n    \"examples\",\n    \"node_modules\",\n    \"README.md\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/namniak/canvas-text-wrapper.git\"\n  },\n  \"homepage\": \"http://namniak.github.io/canvas-text-wrapper/\"\n}"
  },
  {
    "path": "canvas-text-wrapper.js",
    "content": "(function (root) {\n\n  function CanvasTextWrapper(canvas, text, options) {\n    'use strict';\n\n    var defaults = {\n      font: '18px Arial, sans-serif',\n      sizeToFill: false,\n      maxFontSizeToFill: false,\n      lineHeight: 1,\n      allowNewLine: true,\n      lineBreak: 'auto',\n      textAlign: 'left',\n      verticalAlign: 'top',\n      justifyLines: false,\n      paddingX: 0,\n      paddingY: 0,\n      fitParent: false,\n      strokeText: false,\n      renderHDPI: true,\n      textDecoration: 'none'\n    };\n\n    var opts = {};\n\n    for (var key in defaults) {\n      opts[key] = options.hasOwnProperty(key) ? options[key] : defaults[key];\n    }\n\n    var context = canvas.getContext('2d');\n    context.font = opts.font;\n    context.textBaseline = 'bottom';\n\n    var scale = 1;\n    var devicePixelRatio = (typeof global !== 'undefined') ? global.devicePixelRatio : root.devicePixelRatio;\n\n    if (opts.renderHDPI && devicePixelRatio > 1) {\n      var tempCtx = {};\n\n      // store context settings in a temp object before scaling otherwise they will be lost\n      for (var key in context) {\n        tempCtx[key] = context[key];\n      }\n\n      var canvasWidth = canvas.width;\n      var canvasHeight = canvas.height;\n      scale = devicePixelRatio;\n\n      canvas.width = canvasWidth * scale;\n      canvas.height = canvasHeight * scale;\n      canvas.style.width = canvasWidth * scale * 0.5 + 'px';\n      canvas.style.height = canvasHeight * scale * 0.5 + 'px';\n\n      // restore context settings\n      for (var key in tempCtx) {\n        try {\n          context[key] = tempCtx[key];\n        } catch (e) {\n\n        }\n      }\n\n      context.scale(scale, scale);\n    }\n\n    var EL_WIDTH = (!opts.fitParent ? canvas.width : canvas.parentNode.clientWidth) / scale;\n    var EL_HEIGHT = (!opts.fitParent ? canvas.height : canvas.parentNode.clientHeight) / scale;\n    var MAX_TXT_WIDTH = EL_WIDTH - (opts.paddingX * 2);\n    var MAX_TXT_HEIGHT = EL_HEIGHT - (opts.paddingY * 2);\n\n    var fontSize = opts.font.match(/\\d+(px|em|%)/g) ? +opts.font.match(/\\d+(px|em|%)/g)[0].match(/\\d+/g) : 18;\n    var textBlockHeight = 0;\n    var lines = [];\n    var newLineIndexes = [];\n    var textPos = {x: 0, y: 0};\n    var lineHeight = 0;\n    var fontParts;\n    var multiNewLineDelimiter = '\\u200B';\n\n    text = handleMultipleNewline(text);\n    setFont(fontSize);\n    setLineHeight();\n    validate();\n    render();\n\n    function handleMultipleNewline (text) {\n      do {\n        text = text.replace(/\\n\\n/g, '\\n' + multiNewLineDelimiter + '\\n');\n      } while (text.indexOf('\\n\\n') > -1);\n      return text;\n    }\n    \n    function setFont(fontSize) {\n      if (!fontParts) fontParts = (!opts.sizeToFill) ? opts.font.split(/\\b\\d+px\\b/i) : context.font.split(/\\b\\d+px\\b/i);\n      context.font = fontParts[0] + fontSize + 'px' + fontParts[1];\n    }\n\n    function setLineHeight() {\n      if (!isNaN(opts.lineHeight)) {\n        lineHeight = fontSize * opts.lineHeight;\n      } else if (opts.lineHeight.toString().indexOf('px') !== -1) {\n        lineHeight = parseInt(opts.lineHeight);\n      } else if (opts.lineHeight.toString().indexOf('%') !== -1) {\n        lineHeight = (parseInt(opts.lineHeight) / 100) * fontSize;\n      }\n    }\n\n    function render() {\n      if (opts.sizeToFill) {\n        var wordsCount = text.trim().split(/\\s+/).length;\n        var newFontSize = 0;\n        var fontSizeHasLimit = opts.maxFontSizeToFill !== false;\n\n        do {\n          if (fontSizeHasLimit) {\n            if (++newFontSize <= opts.maxFontSizeToFill) {\n              adjustFontSize(newFontSize);\n            } else {\n              break;\n            }\n          } else {\n            adjustFontSize(++newFontSize);\n          }\n        } while (textBlockHeight < MAX_TXT_HEIGHT && (lines.join(' ').split(/\\s+/).length == wordsCount));\n\n        adjustFontSize(--newFontSize);\n      } else {\n        wrap();\n      }\n\n      if (opts.justifyLines && opts.lineBreak === 'auto') {\n        justify();\n      }\n\n      setVertAlign();\n      setHorizAlign();\n      drawText();\n    }\n\n    function adjustFontSize(size) {\n      setFont(size);\n      lineHeight = size;\n      wrap();\n    }\n\n    function wrap() {\n      if (opts.allowNewLine) {\n        var newLines = text.trim().split('\\n');\n        for (var i = 0, idx = 0; i < newLines.length - 1; i++) {\n          idx += newLines[i].trim().split(/\\s+/).length;\n          newLineIndexes.push(idx)\n        }\n      }\n\n      var words = text.trim().split(/\\s+/);\n      checkLength(words);\n      breakText(words);\n\n      textBlockHeight = lines.length * lineHeight;\n    }\n\n    function checkLength(words) {\n      var testString, tokenLen, sliced, leftover;\n\n      words.forEach(function (word, index) {\n        testString = '';\n        tokenLen = context.measureText(word).width;\n\n        if (tokenLen > MAX_TXT_WIDTH) {\n          for (var k = 0; (context.measureText(testString + word[k]).width <= MAX_TXT_WIDTH) && (k < word.length); k++) {\n            testString += word[k];\n          }\n\n          sliced = word.slice(0, k);\n          leftover = word.slice(k);\n          words.splice(index, 1, sliced, leftover);\n        }\n      });\n    }\n\n    function breakText(words) {\n      lines = [];\n      for (var i = 0, j = 0; i < words.length; j++) {\n        lines[j] = '';\n\n        if (opts.lineBreak === 'auto') {\n          if (context.measureText(lines[j] + words[i]).width > MAX_TXT_WIDTH) {\n            break;\n          } else {\n            while ((context.measureText(lines[j] + words[i]).width <= MAX_TXT_WIDTH) && (i < words.length)) {\n\n              lines[j] += words[i] + ' ';\n              i++;\n\n              if (opts.allowNewLine) {\n                for (var k = 0; k < newLineIndexes.length; k++) {\n                  if (newLineIndexes[k] === i) {\n                    j++;\n                    lines[j] = '';\n                    break;\n                  }\n                }\n              }\n            }\n          }\n          lines[j] = lines[j].trim();\n        } else {\n          lines[j] = words[i];\n          i++;\n        }\n      }\n    }\n\n    function justify() {\n      var maxLen, longestLineIndex, tokenLen;\n      for (var i = 0; i < lines.length; i++) {\n        tokenLen = context.measureText(lines[i]).width;\n\n        if (!maxLen || tokenLen > maxLen) {\n          maxLen = tokenLen;\n          longestLineIndex = i;\n        }\n      }\n\n      // fill lines with extra spaces\n      var numWords, spaceLength, numOfSpaces, num, filler;\n      var delimiter = '\\u200A';\n      for (i = 0; i < lines.length; i++) {\n        if (i === longestLineIndex) continue;\n\n        numWords = lines[i].trim().split(/\\s+/).length;\n        if (numWords <= 1) continue;\n\n        lines[i] = lines[i].trim().split(/\\s+/).join(delimiter);\n\n        spaceLength = context.measureText(delimiter).width;\n        numOfSpaces = (maxLen - context.measureText(lines[i]).width) / spaceLength;\n        num = numOfSpaces / (numWords - 1);\n\n        filler = '';\n        for (var j = 0; j < num; j++) {\n          filler += delimiter;\n        }\n\n        lines[i] = lines[i].trim().split(delimiter).join(filler);\n      }\n    }\n\n    function underline(text, x, y) {\n      var width = context.measureText(text).width;\n\n      switch (context.textAlign) {\n        case 'center':\n          x -= (width / 2);\n          break;\n        case 'right':\n          x -= width;\n          break;\n      }\n\n      context.beginPath();\n      context.moveTo(x, y);\n      context.lineTo(x + width, y);\n      context.stroke();\n    }\n\n    function drawText() {\n      var skipLineOnMatch = multiNewLineDelimiter + ' ';\n      for (var i = 0; i < lines.length; i++) {\n        textPos.y = parseInt(textPos.y) + lineHeight;\n        if (lines[i] !== skipLineOnMatch) {\n          context.fillText(lines[i], textPos.x, textPos.y);\n        \n          if (opts.strokeText) {\n            context.strokeText(lines[i], textPos.x, textPos.y);\n          }\n\n          if (opts.textDecoration.toLocaleLowerCase() === 'underline') {\n            underline(lines[i], textPos.x, textPos.y);\n          }\n        }\n      }\n    }\n\n    function setHorizAlign() {\n      context.textAlign = opts.textAlign;\n\n      if (opts.textAlign == 'center') {\n        textPos.x = EL_WIDTH / 2;\n      } else if (opts.textAlign == 'right') {\n        textPos.x = EL_WIDTH - opts.paddingX;\n      } else {\n        textPos.x = opts.paddingX;\n      }\n    }\n\n    function setVertAlign() {\n      if (opts.verticalAlign == 'middle') {\n        textPos.y = (EL_HEIGHT - textBlockHeight) / 2;\n      } else if (opts.verticalAlign == 'bottom') {\n        textPos.y = EL_HEIGHT - textBlockHeight - opts.paddingY;\n      } else {\n        textPos.y = opts.paddingY;\n      }\n    }\n\n    function validate() {\n      if (typeof text !== 'string')\n        throw new TypeError('The second parameter must be a String.');\n\n      if (isNaN(fontSize))\n        throw new TypeError('Cannot parse \"font\".');\n\n      if (isNaN(lineHeight))\n        throw new TypeError('Cannot parse \"lineHeight\".');\n\n      if (opts.textAlign.toLocaleLowerCase() !== 'left' && opts.textAlign.toLocaleLowerCase() !== 'center' && opts.textAlign.toLocaleLowerCase() !== 'right')\n        throw new TypeError('Property \"textAlign\" must be set to either \"left\", \"center\", or \"right\".');\n\n      if (opts.verticalAlign.toLocaleLowerCase() !== 'top' && opts.verticalAlign.toLocaleLowerCase() !== 'middle' && opts.verticalAlign.toLocaleLowerCase() !== 'bottom')\n        throw new TypeError('Property \"verticalAlign\" must be set to either \"top\", \"middle\", or \"bottom\".');\n\n      if (typeof opts.justifyLines !== 'boolean')\n        throw new TypeError('Property \"justifyLines\" must be a Boolean.');\n\n      if (isNaN(opts.paddingX))\n        throw new TypeError('Property \"paddingX\" must be a Number.');\n\n      if (isNaN(opts.paddingY))\n        throw new TypeError('Property \"paddingY\" must be a Number.');\n\n      if (typeof opts.fitParent !== 'boolean')\n        throw new TypeError('Property \"fitParent\" must be a Boolean.');\n\n      if (opts.lineBreak.toLocaleLowerCase() !== 'auto' && opts.lineBreak.toLocaleLowerCase() !== 'word')\n        throw new TypeError('Property \"lineBreak\" must be set to either \"auto\" or \"word\".');\n\n      if (typeof opts.sizeToFill !== 'boolean')\n        throw new TypeError('Property \"sizeToFill\" must be a Boolean.');\n\n      if (typeof opts.strokeText !== 'boolean')\n        throw new TypeError('Property \"strokeText\" must be a Boolean.');\n\n      if (typeof opts.renderHDPI !== 'boolean')\n        throw new TypeError('Property \"renderHDPI\" must be a Boolean.');\n\n      if (opts.textDecoration.toLocaleLowerCase() !== 'none' && opts.textDecoration.toLocaleLowerCase() !== 'underline')\n        throw new TypeError('Property \"textDecoration\" must be set to either \"none\" or \"underline\".');\n    }\n\n    return(lines);\n  }\n\n  if ('module' in root && 'exports' in module) {\n    module.exports = CanvasTextWrapper;\n  } else {\n    root.CanvasTextWrapper = CanvasTextWrapper;\n  }\n})(this);\n"
  },
  {
    "path": "index.d.ts",
    "content": "\nexport function CanvasTextWrapper(element: HTMLCanvasElement, text: string, options?: CanvasTextWrapperOptions): void;\n\nexport interface CanvasTextWrapperOptions {\n   /**\n    *  Text style that includes font size (in px), font weight, font family, etc.\n    */\n   font?: string,\n   /**\n    * Number - 'n' times font size where 1 is equivalent to '100%'. Also the property can be set in '%' or 'px'.\n    */\n   lineHeight?: string | number,\n   /**\n    * Horizontal alignment of each line.\n    */\n   textAlign?: \"left\" | \"center\" | \"right\",\n   /**\n    * Vertical alignment of the whole text block.\n    */\n   verticalAlign?: \"top\" | \"middle\" | \"bottom\",\n   /**\n    * Horizontal padding (in px) that is equally set on left and right sides.\n    */\n   paddingX?: number,\n   /**\n    * Vertical padding (in px) that is equally set on top and bottoms.\n    */\n   paddingY?: number,\n   /**\n    * Fit canvas' container size instead of its own size.\n    */\n   fitParent?: boolean,\n   /**\n    * \"auto\" - text goes to the next line on a whole word when there's no room\n    * \"word\" - each next word is placed on a new line\n    */\n   lineBreak?: \"auto\" | \"word\",\n   /**\n    * Ignore given font size and line height and resize text to fill its padded container.\n    */\n   sizeToFill?: boolean,\n   /**\n    * If above option is true text won't be bigger than set.\n    */\n   maxFontSizeToFill?: number,\n   /**\n    * Allow text outline based on canvas context configuration.\n    */\n   strokeText?: boolean,\n   /**\n    * All lines will try to match the same width with flexed spaces between the words.\n    */\n   justifyLines?: boolean,\n   /**\n    * Text breaks on a new line character \"\\n\". Supports multiple consecutive new lines.\n    */\n   allowNewLine?: boolean,\n   /**\n    * Text is rendered based on device pixel ratio.\n    */\n   renderHDPI?: boolean,\n   /**\n    * Text is underlined according to context.strokeStyle and context.lineWidth\n    */\n   textDecoration?: \"none\" | \"underline\"\n}\n\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"canvas-text-wrapper\",\n  \"description\": \"Split canvas text into lines on specified rule with optional alignment, padding, and more. Supports HDPI screens.\",\n  \"version\": \"0.10.2\",\n  \"license\": \"MIT\",\n  \"main\": \"canvas-text-wrapper.js\",\n  \"keywords\": [\n    \"canvas\",\n    \"canvas-text\",\n    \"text\",\n    \"split\",\n    \"wrap\",\n    \"hdpi-canvas\",\n    \"text-split\"\n  ],\n  \"homepage\": \"http://namniak.github.io/canvas-text-wrapper/\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/namniak/canvas-text-wrapper\"\n  },\n  \"author\": \"Vadim Namniak <vnamnyak@gmail.com> (https://github.com/namniak)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/namniak/canvas-text-wrapper/issues\"\n  },\n  \"scripts\": {\n    \"test\": \"beefy test/test.js --live --open\"\n  },\n  \"devDependencies\": {\n    \"grunt\": \"^0.4.5\",\n    \"grunt-contrib-uglify\": \"^0.9.1\",\n    \"uglify-save-license\": \"^0.4.1\"\n  }\n}\n"
  },
  {
    "path": "test/test.js",
    "content": "var CanvasTextWrapper = require('../canvas-text-wrapper.js').CanvasTextWrapper;\n\nvar body = document.getElementsByTagName('body')[0];\nbody.style.margin = 0;\n\nvar canvas = document.createElement('canvas');\ncanvas.width = 600;\ncanvas.height = 600;\ncanvas.style.position = 'absolute';\ncanvas.style.left = '50%';\ncanvas.style.top = '50%';\ncanvas.style.border = '1px solid #212121';\ncanvas.style.transform = 'translateX(-50%) translateY(-50%)';\n\nbody.appendChild(canvas);\n\nvar ctx = canvas.getContext('2d');\nvar gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);\ngradient.addColorStop('0.2', 'magenta');\ngradient.addColorStop('0.5', 'blue');\ngradient.addColorStop('0.7', 'purple');\nctx.fillStyle = gradient;\n\nvar opts = {\n  sizeToFill: true,\n  textAlign: 'center',\n  verticalAlign: 'middle',\n  paddingX: 20\n};\n\nCanvasTextWrapper(canvas, 'What an awesome library!', opts);"
  }
]