├── .eslintrc.json ├── .gitignore ├── index.js ├── package-lock.json ├── package.json ├── readme.md └── test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "strict": 2, 11 | "indent": 0, 12 | "linebreak-style": 0, 13 | "quotes": 0, 14 | "semi": 0, 15 | "no-cond-assign": 1, 16 | "no-constant-condition": 1, 17 | "no-duplicate-case": 1, 18 | "no-empty": 1, 19 | "no-ex-assign": 1, 20 | "no-extra-boolean-cast": 1, 21 | "no-extra-semi": 1, 22 | "no-fallthrough": 1, 23 | "no-func-assign": 1, 24 | "no-global-assign": 1, 25 | "no-implicit-globals": 2, 26 | "no-inner-declarations": ["error", "functions"], 27 | "no-irregular-whitespace": 2, 28 | "no-loop-func": 1, 29 | "no-multi-str": 1, 30 | "no-mixed-spaces-and-tabs": 1, 31 | "no-proto": 1, 32 | "no-sequences": 1, 33 | "no-throw-literal": 1, 34 | "no-unmodified-loop-condition": 1, 35 | "no-useless-call": 1, 36 | "no-void": 1, 37 | "no-with": 2, 38 | "wrap-iife": 1, 39 | "no-redeclare": 1, 40 | "no-unused-vars": ["error", { "vars": "all", "args": "none" }], 41 | "no-sparse-arrays": 1 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | 3 | node_modules 4 | # Compiled source # 5 | ################### 6 | *.com 7 | *.class 8 | *.dll 9 | *.exe 10 | *.o 11 | *.so 12 | *.bin 13 | 14 | # Packages # 15 | ############ 16 | # it's better to unpack these files and commit the raw source 17 | # git has its own built in compression methods 18 | *.7z 19 | *.dmg 20 | *.gz 21 | *.iso 22 | *.jar 23 | *.rar 24 | *.tar 25 | *.zip 26 | 27 | # Logs and databases # 28 | ###################### 29 | *.log 30 | *.sql 31 | *.sqlite 32 | 33 | # OS generated files # 34 | ###################### 35 | .DS_Store 36 | .DS_Store? 37 | *._* 38 | .Spotlight-V100 39 | .Trashes 40 | Icon? 41 | ehthumbs.db 42 | Thumbs.db 43 | 44 | # My extension # 45 | ################ 46 | *.lock 47 | *.bak 48 | lsn 49 | *.dump 50 | *.beam 51 | *.[0-9] 52 | *._[0-9] 53 | *.ns 54 | Scripting_* 55 | docs 56 | *.pdf 57 | *.pak 58 | 59 | demo 60 | design 61 | instances 62 | *node_modules 63 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module optical-properties 3 | */ 4 | 'use strict' 5 | 6 | module.exports = measure 7 | 8 | var canvas = document.createElement('canvas'), 9 | ctx = canvas.getContext('2d') 10 | 11 | canvas.width = 200, canvas.height = 200 12 | 13 | measure.canvas = canvas 14 | 15 | //returns character [x, y, scale] optical params 16 | function measure (char, options) { 17 | var data, w, h, params 18 | 19 | //figure out argument imageData 20 | if (typeof char === 'string') { 21 | data = getCharImageData(char, options) 22 | w = data.width, h = data.height 23 | } 24 | else if (char instanceof HTMLCanvasElement) { 25 | w = char.width, h = char.height 26 | char = char.getContext('2d') 27 | data = char.getImageData(0, 0, w, h) 28 | } 29 | else if (char instanceof ImageData) { 30 | w = char.width, h = char.height 31 | data = char 32 | } 33 | 34 | params = getOpticalParams(data) 35 | 36 | return params 37 | } 38 | 39 | //draw character in canvas and get it's imagedata 40 | function getCharImageData (char, options) { 41 | if (!options) options = {} 42 | var family = options.family || 'sans-serif' 43 | var w = canvas.width, h = canvas.height 44 | 45 | var size = options.width || options.height || options.size 46 | if (size && size != w) { 47 | w = h = canvas.width = canvas.height = size 48 | } 49 | 50 | var fs = options.fontSize || w/2 51 | 52 | ctx.fillStyle = '#000' 53 | ctx.fillRect(0, 0, w, h) 54 | 55 | ctx.font = fs + 'px ' + family 56 | ctx.textBaseline = 'middle' 57 | ctx.textAlign = 'center' 58 | ctx.fillStyle = 'white' 59 | ctx.fillText(char, w/2, h/2) 60 | 61 | return ctx.getImageData(0, 0, w, h) 62 | } 63 | 64 | 65 | //walks over imagedata, returns params 66 | function getOpticalParams (data) { 67 | var buf = data.data, w = data.width, h = data.height 68 | 69 | var x, y, r, i, j, sum, xSum, ySum, rowAvg = Array(h), rowAvgX = Array(h), cx, cy, bounds, avg, top = 0, bottom = 0, left = w, right = 0, maxR = 0, rowBounds = Array(h), r2 70 | 71 | for (y = 0; y < h; y++) { 72 | sum = 0, xSum = 0, j = y*4*w 73 | 74 | bounds = getBounds(buf.subarray(j, j + 4*w), 4) 75 | 76 | if (bounds[0] === bounds[1]) { 77 | continue 78 | } 79 | else { 80 | if (!top) top = y 81 | bottom = y 82 | } 83 | 84 | for (x = bounds[0]; x < bounds[1]; x++) { 85 | i = x*4 86 | r = buf[j + i] 87 | sum += r 88 | xSum += x*r 89 | } 90 | 91 | rowAvg[y] = sum === 0 ? 0 : sum/w 92 | rowAvgX[y] = sum === 0 ? 0 : xSum/sum 93 | 94 | if (bounds[0] < left) left = bounds[0] 95 | if (bounds[1] > right) right = bounds[1] 96 | 97 | rowBounds[y] = bounds 98 | } 99 | 100 | sum = 0, ySum = 0, xSum = 0 101 | for (y = 0; y < h; y++) { 102 | avg = rowAvg[y] 103 | if (!avg) continue; 104 | 105 | ySum += avg*y 106 | sum += avg 107 | xSum += rowAvgX[y]*avg 108 | } 109 | 110 | cy = ySum/sum 111 | cx = xSum/sum 112 | 113 | maxR = 0, r2 = 0 114 | for (y = 0; y < h; y++) { 115 | bounds = rowBounds[y] 116 | if (!bounds) continue 117 | 118 | r2 = Math.max( 119 | dist2(cx - bounds[0], cy - y), 120 | dist2(cx - bounds[1], cy - y) 121 | ) 122 | if (r2 > maxR) { 123 | maxR = r2 124 | } 125 | } 126 | 127 | return { 128 | center: [cx, cy], 129 | bounds: [left, top, right, bottom+1], 130 | radius: Math.sqrt(maxR) 131 | } 132 | } 133 | 134 | //get [leftId, rightId] pair of bounding values for an array 135 | function getBounds (arr, stride) { 136 | var left = 0, right = arr.length, i = 0 137 | 138 | if (!stride) stride = 4 139 | 140 | //find left non-zero value 141 | while (!arr[i] && i < right) { 142 | i+=stride 143 | } 144 | left = i 145 | 146 | //find right non-zero value 147 | i = arr.length 148 | while (!arr[i] && i > left) { 149 | i-=stride 150 | } 151 | right = i 152 | 153 | return [left/stride, right/stride] 154 | } 155 | 156 | function dist2 (x, y) { 157 | return x*x + y*y 158 | } 159 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optical-properties", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "acorn": { 7 | "version": "1.2.2", 8 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", 9 | "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=", 10 | "dev": true 11 | }, 12 | "amdefine": { 13 | "version": "1.0.1", 14 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 15 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", 16 | "dev": true, 17 | "optional": true 18 | }, 19 | "ansi-regex": { 20 | "version": "2.1.1", 21 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 22 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 23 | "dev": true 24 | }, 25 | "ansi-styles": { 26 | "version": "2.2.1", 27 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 28 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 29 | "dev": true 30 | }, 31 | "babel-code-frame": { 32 | "version": "6.22.0", 33 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", 34 | "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", 35 | "dev": true, 36 | "dependencies": { 37 | "esutils": { 38 | "version": "2.0.2", 39 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 40 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 41 | "dev": true 42 | } 43 | } 44 | }, 45 | "babel-core": { 46 | "version": "6.25.0", 47 | "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz", 48 | "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=", 49 | "dev": true, 50 | "dependencies": { 51 | "source-map": { 52 | "version": "0.5.6", 53 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 54 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", 55 | "dev": true 56 | } 57 | } 58 | }, 59 | "babel-generator": { 60 | "version": "6.25.0", 61 | "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz", 62 | "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=", 63 | "dev": true, 64 | "dependencies": { 65 | "source-map": { 66 | "version": "0.5.6", 67 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 68 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", 69 | "dev": true 70 | } 71 | } 72 | }, 73 | "babel-helpers": { 74 | "version": "6.24.1", 75 | "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", 76 | "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", 77 | "dev": true 78 | }, 79 | "babel-messages": { 80 | "version": "6.23.0", 81 | "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", 82 | "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", 83 | "dev": true 84 | }, 85 | "babel-register": { 86 | "version": "6.24.1", 87 | "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", 88 | "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=", 89 | "dev": true 90 | }, 91 | "babel-runtime": { 92 | "version": "6.23.0", 93 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", 94 | "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", 95 | "dev": true 96 | }, 97 | "babel-template": { 98 | "version": "6.25.0", 99 | "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", 100 | "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", 101 | "dev": true 102 | }, 103 | "babel-traverse": { 104 | "version": "6.25.0", 105 | "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", 106 | "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", 107 | "dev": true 108 | }, 109 | "babel-types": { 110 | "version": "6.25.0", 111 | "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", 112 | "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", 113 | "dev": true, 114 | "dependencies": { 115 | "esutils": { 116 | "version": "2.0.2", 117 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 118 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 119 | "dev": true 120 | } 121 | } 122 | }, 123 | "babelify": { 124 | "version": "7.3.0", 125 | "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", 126 | "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", 127 | "dev": true 128 | }, 129 | "babylon": { 130 | "version": "6.17.2", 131 | "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz", 132 | "integrity": "sha1-IB0l71+JLEG65JSIsI2w3Udun1w=", 133 | "dev": true 134 | }, 135 | "balanced-match": { 136 | "version": "0.4.2", 137 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", 138 | "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", 139 | "dev": true 140 | }, 141 | "brace-expansion": { 142 | "version": "1.1.7", 143 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", 144 | "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", 145 | "dev": true 146 | }, 147 | "brfs": { 148 | "version": "1.4.3", 149 | "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.4.3.tgz", 150 | "integrity": "sha1-22ddb16SPm3wh/ylhZyQkKrtMhY=", 151 | "dev": true 152 | }, 153 | "buffer-equal": { 154 | "version": "0.0.1", 155 | "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", 156 | "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", 157 | "dev": true 158 | }, 159 | "chalk": { 160 | "version": "1.1.3", 161 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 162 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 163 | "dev": true 164 | }, 165 | "concat-map": { 166 | "version": "0.0.1", 167 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 168 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 169 | "dev": true 170 | }, 171 | "concat-stream": { 172 | "version": "1.6.0", 173 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", 174 | "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", 175 | "dev": true 176 | }, 177 | "convert-source-map": { 178 | "version": "1.5.0", 179 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", 180 | "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", 181 | "dev": true 182 | }, 183 | "core-js": { 184 | "version": "2.4.1", 185 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", 186 | "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=", 187 | "dev": true 188 | }, 189 | "core-util-is": { 190 | "version": "1.0.2", 191 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 192 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 193 | "dev": true 194 | }, 195 | "debug": { 196 | "version": "2.6.8", 197 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 198 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 199 | "dev": true 200 | }, 201 | "detect-indent": { 202 | "version": "4.0.0", 203 | "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", 204 | "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", 205 | "dev": true 206 | }, 207 | "duplexer2": { 208 | "version": "0.0.2", 209 | "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", 210 | "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", 211 | "dev": true, 212 | "dependencies": { 213 | "isarray": { 214 | "version": "0.0.1", 215 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 216 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 217 | "dev": true 218 | }, 219 | "readable-stream": { 220 | "version": "1.1.14", 221 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 222 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 223 | "dev": true 224 | }, 225 | "string_decoder": { 226 | "version": "0.10.31", 227 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 228 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 229 | "dev": true 230 | } 231 | } 232 | }, 233 | "enable-mobile": { 234 | "version": "1.0.7", 235 | "resolved": "https://registry.npmjs.org/enable-mobile/-/enable-mobile-1.0.7.tgz", 236 | "integrity": "sha1-cOTkff89ofaqvgAaTidW6LZQEQk=", 237 | "dev": true 238 | }, 239 | "escape-string-regexp": { 240 | "version": "1.0.5", 241 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 242 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 243 | "dev": true 244 | }, 245 | "escodegen": { 246 | "version": "1.3.3", 247 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", 248 | "integrity": "sha1-8CQBb1qI4Eb9EgBQVek5gC5sXyM=", 249 | "dev": true 250 | }, 251 | "esprima": { 252 | "version": "1.1.1", 253 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", 254 | "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=", 255 | "dev": true 256 | }, 257 | "estraverse": { 258 | "version": "1.5.1", 259 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", 260 | "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=", 261 | "dev": true 262 | }, 263 | "esutils": { 264 | "version": "1.0.0", 265 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", 266 | "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=", 267 | "dev": true 268 | }, 269 | "falafel": { 270 | "version": "1.2.0", 271 | "resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz", 272 | "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=", 273 | "dev": true, 274 | "dependencies": { 275 | "isarray": { 276 | "version": "0.0.1", 277 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 278 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 279 | "dev": true 280 | } 281 | } 282 | }, 283 | "foreach": { 284 | "version": "2.0.5", 285 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 286 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 287 | "dev": true 288 | }, 289 | "function-bind": { 290 | "version": "1.1.0", 291 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", 292 | "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", 293 | "dev": true 294 | }, 295 | "get-float-time-domain-data": { 296 | "version": "0.1.0", 297 | "resolved": "https://registry.npmjs.org/get-float-time-domain-data/-/get-float-time-domain-data-0.1.0.tgz", 298 | "integrity": "sha1-XYVZJKQwOITJY4qrEnzOTQBlljs=", 299 | "dev": true 300 | }, 301 | "globals": { 302 | "version": "9.18.0", 303 | "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", 304 | "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", 305 | "dev": true 306 | }, 307 | "has": { 308 | "version": "1.0.1", 309 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", 310 | "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", 311 | "dev": true 312 | }, 313 | "has-ansi": { 314 | "version": "2.0.0", 315 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 316 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 317 | "dev": true 318 | }, 319 | "home-or-tmp": { 320 | "version": "2.0.0", 321 | "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", 322 | "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", 323 | "dev": true 324 | }, 325 | "inherits": { 326 | "version": "2.0.3", 327 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 328 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 329 | "dev": true 330 | }, 331 | "insert-styles": { 332 | "version": "1.2.1", 333 | "resolved": "https://registry.npmjs.org/insert-styles/-/insert-styles-1.2.1.tgz", 334 | "integrity": "sha1-nNhMIJa9VB6j4ZI2Ere2BMnB1TI=", 335 | "dev": true 336 | }, 337 | "invariant": { 338 | "version": "2.2.2", 339 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", 340 | "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", 341 | "dev": true 342 | }, 343 | "is-finite": { 344 | "version": "1.0.2", 345 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", 346 | "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", 347 | "dev": true 348 | }, 349 | "isarray": { 350 | "version": "1.0.0", 351 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 352 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 353 | "dev": true 354 | }, 355 | "js-tokens": { 356 | "version": "3.0.1", 357 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", 358 | "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=", 359 | "dev": true 360 | }, 361 | "jsesc": { 362 | "version": "1.3.0", 363 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", 364 | "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", 365 | "dev": true 366 | }, 367 | "json5": { 368 | "version": "0.5.1", 369 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", 370 | "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", 371 | "dev": true 372 | }, 373 | "lodash": { 374 | "version": "4.17.4", 375 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 376 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", 377 | "dev": true 378 | }, 379 | "loose-envify": { 380 | "version": "1.3.1", 381 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", 382 | "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", 383 | "dev": true 384 | }, 385 | "minimatch": { 386 | "version": "3.0.4", 387 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 388 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 389 | "dev": true 390 | }, 391 | "minimist": { 392 | "version": "1.2.0", 393 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 394 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 395 | "dev": true 396 | }, 397 | "mkdirp": { 398 | "version": "0.5.1", 399 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 400 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 401 | "dev": true, 402 | "dependencies": { 403 | "minimist": { 404 | "version": "0.0.8", 405 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 406 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 407 | "dev": true 408 | } 409 | } 410 | }, 411 | "ms": { 412 | "version": "2.0.0", 413 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 414 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 415 | "dev": true 416 | }, 417 | "normalize.css": { 418 | "version": "7.0.0", 419 | "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-7.0.0.tgz", 420 | "integrity": "sha1-q/sd2CRwZ04DIrU86xqvQSk45L8=", 421 | "dev": true 422 | }, 423 | "number-is-nan": { 424 | "version": "1.0.1", 425 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 426 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 427 | "dev": true 428 | }, 429 | "object-assign": { 430 | "version": "4.1.1", 431 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 432 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 433 | "dev": true 434 | }, 435 | "object-inspect": { 436 | "version": "0.4.0", 437 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-0.4.0.tgz", 438 | "integrity": "sha1-9RV8EWwUVbJDsG7pdwM5LFrYn+w=", 439 | "dev": true 440 | }, 441 | "object-keys": { 442 | "version": "1.0.11", 443 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", 444 | "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", 445 | "dev": true 446 | }, 447 | "os-homedir": { 448 | "version": "1.0.2", 449 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 450 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 451 | "dev": true 452 | }, 453 | "os-tmpdir": { 454 | "version": "1.0.2", 455 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 456 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 457 | "dev": true 458 | }, 459 | "path-is-absolute": { 460 | "version": "1.0.1", 461 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 462 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 463 | "dev": true 464 | }, 465 | "path-parse": { 466 | "version": "1.0.5", 467 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 468 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", 469 | "dev": true 470 | }, 471 | "private": { 472 | "version": "0.1.7", 473 | "resolved": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", 474 | "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=", 475 | "dev": true 476 | }, 477 | "process-nextick-args": { 478 | "version": "1.0.7", 479 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 480 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", 481 | "dev": true 482 | }, 483 | "quote-stream": { 484 | "version": "1.0.2", 485 | "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", 486 | "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", 487 | "dev": true 488 | }, 489 | "readable-stream": { 490 | "version": "2.2.11", 491 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", 492 | "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q==", 493 | "dev": true 494 | }, 495 | "regenerator-runtime": { 496 | "version": "0.10.5", 497 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", 498 | "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", 499 | "dev": true 500 | }, 501 | "repeating": { 502 | "version": "2.0.1", 503 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 504 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 505 | "dev": true 506 | }, 507 | "resolve": { 508 | "version": "1.3.3", 509 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", 510 | "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", 511 | "dev": true 512 | }, 513 | "safe-buffer": { 514 | "version": "5.0.1", 515 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", 516 | "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", 517 | "dev": true 518 | }, 519 | "shallow-copy": { 520 | "version": "0.0.1", 521 | "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", 522 | "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", 523 | "dev": true 524 | }, 525 | "slash": { 526 | "version": "1.0.0", 527 | "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", 528 | "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", 529 | "dev": true 530 | }, 531 | "source-map": { 532 | "version": "0.1.43", 533 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 534 | "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", 535 | "dev": true, 536 | "optional": true 537 | }, 538 | "source-map-support": { 539 | "version": "0.4.15", 540 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", 541 | "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", 542 | "dev": true, 543 | "dependencies": { 544 | "source-map": { 545 | "version": "0.5.6", 546 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 547 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", 548 | "dev": true 549 | } 550 | } 551 | }, 552 | "static-eval": { 553 | "version": "0.2.4", 554 | "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-0.2.4.tgz", 555 | "integrity": "sha1-t9NNg4k3uWn5ZBygfUj47eJj6ns=", 556 | "dev": true, 557 | "dependencies": { 558 | "escodegen": { 559 | "version": "0.0.28", 560 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.28.tgz", 561 | "integrity": "sha1-Dk/xcV8yh3XWyrUaxEpAbNer/9M=", 562 | "dev": true 563 | }, 564 | "esprima": { 565 | "version": "1.0.4", 566 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", 567 | "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", 568 | "dev": true 569 | }, 570 | "estraverse": { 571 | "version": "1.3.2", 572 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.3.2.tgz", 573 | "integrity": "sha1-N8K4k+8T1yPydth41g2FNRUqbEI=", 574 | "dev": true 575 | } 576 | } 577 | }, 578 | "static-module": { 579 | "version": "1.3.2", 580 | "resolved": "https://registry.npmjs.org/static-module/-/static-module-1.3.2.tgz", 581 | "integrity": "sha1-Mp+58iOlZiZr2nGEO32TLHZxdPM=", 582 | "dev": true, 583 | "dependencies": { 584 | "isarray": { 585 | "version": "0.0.1", 586 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 587 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 588 | "dev": true 589 | }, 590 | "minimist": { 591 | "version": "0.0.8", 592 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 593 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 594 | "dev": true 595 | }, 596 | "object-keys": { 597 | "version": "0.4.0", 598 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", 599 | "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", 600 | "dev": true 601 | }, 602 | "quote-stream": { 603 | "version": "0.0.0", 604 | "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-0.0.0.tgz", 605 | "integrity": "sha1-zeKelMQJsW4Z3HCYuJtmWPlyHTs=", 606 | "dev": true 607 | }, 608 | "readable-stream": { 609 | "version": "1.0.34", 610 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", 611 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", 612 | "dev": true 613 | }, 614 | "string_decoder": { 615 | "version": "0.10.31", 616 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 617 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 618 | "dev": true 619 | }, 620 | "through2": { 621 | "version": "0.4.2", 622 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", 623 | "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", 624 | "dev": true 625 | }, 626 | "xtend": { 627 | "version": "2.1.2", 628 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", 629 | "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", 630 | "dev": true 631 | } 632 | } 633 | }, 634 | "string_decoder": { 635 | "version": "1.0.2", 636 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", 637 | "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", 638 | "dev": true 639 | }, 640 | "strip-ansi": { 641 | "version": "3.0.1", 642 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 643 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 644 | "dev": true 645 | }, 646 | "supports-color": { 647 | "version": "2.0.0", 648 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 649 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 650 | "dev": true 651 | }, 652 | "through2": { 653 | "version": "2.0.3", 654 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", 655 | "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", 656 | "dev": true 657 | }, 658 | "to-fast-properties": { 659 | "version": "1.0.3", 660 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", 661 | "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", 662 | "dev": true 663 | }, 664 | "trim-right": { 665 | "version": "1.0.1", 666 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", 667 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", 668 | "dev": true 669 | }, 670 | "typedarray": { 671 | "version": "0.0.6", 672 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 673 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 674 | "dev": true 675 | }, 676 | "typedarray-methods": { 677 | "version": "1.0.0", 678 | "resolved": "https://registry.npmjs.org/typedarray-methods/-/typedarray-methods-1.0.0.tgz", 679 | "integrity": "sha1-1laweKn/YTTtGTeU+OVeFYIErQ0=", 680 | "dev": true 681 | }, 682 | "util-deprecate": { 683 | "version": "1.0.2", 684 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 685 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 686 | "dev": true 687 | }, 688 | "xtend": { 689 | "version": "4.0.1", 690 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 691 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 692 | "dev": true 693 | } 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optical-properties", 3 | "version": "1.0.0", 4 | "description": "Calculate optical properties of a character, imageData or canvas", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "budo test", 8 | "build": "browserify test.js -g babelify | indexhtmlify | metadataify | github-cornerify > demo/index.html" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/dfcreative/optical-properties.git" 13 | }, 14 | "keywords": [ 15 | "optical", 16 | "font", 17 | "typeface", 18 | "canvas", 19 | "visual", 20 | "center", 21 | "mass", 22 | "bitmap", 23 | "align" 24 | ], 25 | "author": "Dima Yv ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/dfcreative/optical-properties/issues" 29 | }, 30 | "homepage": "https://github.com/dfcreative/optical-properties#readme", 31 | "devDependencies": { 32 | "babelify": "^7.3.0", 33 | "enable-mobile": "^1.0.7" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # optical-properties [![unstable](https://img.shields.io/badge/stability-unstable-green.svg)](http://github.com/badges/stability-badges) 2 | 3 | Get optical params of a character, canvas or image data. Useful to do kerning, normalize size or adjust vertical/horizontal alignment. 4 | 5 | ![optical-properties](https://github.com/dfcreative/optical-properties/blob/gh-pages/index.png?raw=true) 6 | 7 | See [demo](https://dy.github.io/optical-properties). 8 | 9 | ## Usage 10 | 11 | [![npm install optical-properties](https://nodei.co/npm/optical-properties.png?mini=true)](https://npmjs.org/package/optical-properties/) 12 | 13 | ```js 14 | const optics = require('optical-properties') 15 | 16 | let w = canvas.width, h = canvas.height, ctx = canvas.getContext('2d') 17 | 18 | //get optical params 19 | let {bounds, center, radius} = optics('▲', {size: h, fontSize: h/2}) 20 | 21 | //make sure radius of char is at least half of canvas height 22 | let scale = h*.5 / (radius*2) 23 | 24 | //optical center shift from the real center 25 | let diff = [w*.5 - center[0], h*.5 - center[1]] 26 | 27 | //draw normalized character 28 | ctx.font = size*cale + 'px sans-serif' 29 | ctx.fillText('▲', w*.5 + diff[0]*scale, h*.5 + diff[1]*scale) 30 | 31 | ``` 32 | 33 | ## API 34 | 35 | ### let props = optics(char|canvas|imageData, options?) 36 | 37 | Measures optical properties of a character, canvas or imageData based on options. Canvas is expected to be rectangular. 38 | 39 | Options: 40 | 41 | * `size` − size of canvas to use, bigger is slower but more precise and vice-versa. Defaults to `200`. 42 | * `fontFamily` − font family to use for the character, defaults to `sans-serif`. 43 | * `fontSize` − size of glyph, defaults to `100`. 44 | 45 | Returns object with properties: 46 | 47 | * `center` − coordinates of optical center as `[cx, cy]`. 48 | * `bounds` − character bounding box `[left, top, right, bottom]`. 49 | * `radius` − distance from the optical center to the outmost point. 50 | 51 | 52 | ## Credits 53 | 54 | © 2017 Dima Yv. MIT License 55 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('enable-mobile') 4 | const optics = require('./') 5 | 6 | document.body.style.padding = '2rem' 7 | 8 | let inputEl = document.body.appendChild(document.createElement('input')) 9 | inputEl.style.display = 'block' 10 | inputEl.style.width = '4rem' 11 | inputEl.style.fontSize = '1.5rem' 12 | inputEl.style.marginBottom = '1rem' 13 | inputEl.maxlength = 1 14 | inputEl.value = '|' 15 | inputEl.onchange = e => { 16 | let v = inputEl.value[0] 17 | inputEl.value = v 18 | 19 | update(v) 20 | } 21 | 22 | let itemsEl = document.body.appendChild(document.createElement('div')) 23 | itemsEl.style.position = 'absolute' 24 | itemsEl.style.top = '2rem' 25 | itemsEl.style.left = '7rem' 26 | itemsEl.style.lineHeight = '2rem' 27 | itemsEl.style.fontSize = '1.5rem' 28 | let chars = '●✝+×✕▲▼_▇◌◦□⧖⧓◆✦✶❇' 29 | for (let i = 0; i < chars.length; i++) { 30 | let el = itemsEl.appendChild(document.createElement('span')) 31 | el.innerHTML = chars[i] 32 | } 33 | 34 | 35 | 36 | //create canvases 37 | let w = 300 38 | let h = 300 39 | let fs = 150 40 | 41 | let canvasIn = document.body.appendChild(document.createElement('canvas')) 42 | let ctxIn = canvasIn.getContext('2d') 43 | canvasIn.width = w 44 | canvasIn.height = h 45 | canvasIn.style.marginRight = '1rem' 46 | 47 | let canvasOut = document.body.appendChild(document.createElement('canvas')) 48 | let ctxOut = canvasOut.getContext('2d') 49 | canvasOut.width = w 50 | canvasOut.height = h 51 | 52 | update(inputEl.value) 53 | 54 | function update (char) { 55 | ctxIn.fillStyle = 'black' 56 | ctxIn.fillRect(0, 0, w, h) 57 | 58 | ctxIn.fillStyle = 'rgba(0, 150, 250, .25)' 59 | ctxIn.fillRect(w/2, 0, 1, h) 60 | ctxIn.fillRect(0, h/2, w, 1) 61 | 62 | ctxIn.textBaseline = 'middle' 63 | ctxIn.textAlign = 'center' 64 | ctxIn.font = fs + 'px sans-serif' 65 | ctxIn.fillStyle = 'white' 66 | ctxIn.fillText(char, w/2, h/2) 67 | 68 | 69 | console.time(char + ' time') 70 | let props = optics(char, {size: w, fontSize: fs}) 71 | console.timeEnd(char + ' time') 72 | console.log(char + ' properties:', props) 73 | 74 | let {bounds: box, center, radius} = props 75 | let maxSide = Math.max( (box[3]-box[1])/2 , (box[2]-box[0])/2 ) 76 | let maxDist = radius*.5 + maxSide*.5 77 | let scale = h*.25/maxDist 78 | let diff = [Math.floor(w/2 - center[0]), h/2 - center[1]] 79 | 80 | //center of mass cross 81 | ctxIn.fillStyle = 'rgba(250, 150, 0, .5)' 82 | ctxIn.fillRect(center[0], 0, 1, h) 83 | ctxIn.fillRect(0, center[1], w, 1) 84 | 85 | //bounding box 86 | ctxIn.strokeStyle = 'rgba(0, 250, 150, .5)' 87 | ctxIn.strokeRect(box[0], box[1], box[2] - box[0], box[3] - box[1]) 88 | 89 | //render output 90 | ctxOut.fillStyle = 'black' 91 | ctxOut.fillRect(0, 0, w, h) 92 | 93 | //font 94 | ctxOut.fillStyle = 'white' 95 | ctxOut.textBaseline = 'middle' 96 | ctxOut.textAlign = 'center' 97 | ctxOut.font = fs*scale + 'px sans-serif' 98 | ctxOut.fillText(char, w/2 + diff[0]*scale, h/2 + diff[1]*scale) 99 | 100 | //center cross 101 | ctxOut.fillStyle = 'rgba(0, 150, 250, .25)' 102 | ctxOut.fillRect(w/2, 0, 1, h) 103 | ctxOut.fillRect(0, h/2, w, 1) 104 | 105 | //circumference 106 | ctxIn.strokeStyle = 'rgba(250, 250, 0, .5)' 107 | ctxIn.beginPath() 108 | ctxIn.arc(center[0], center[1], radius, 0, 2 * Math.PI) 109 | ctxIn.closePath() 110 | ctxIn.stroke(); 111 | 112 | //circumference 113 | ctxOut.strokeStyle = 'rgba(0, 150, 250, .33)' 114 | ctxOut.beginPath() 115 | ctxOut.arc(w/2, h/2, w/4, 0, 2 * Math.PI) 116 | ctxOut.closePath() 117 | ctxOut.stroke(); 118 | 119 | } 120 | 121 | 122 | // drawHTML('●✝+×✕▲▼_▇◌◦□⧖⧓◆✦✶❇') 123 | drawCanvas('●✝+×✕▲▼_▇◌◦□⧖⧓◆✦✶❇') 124 | 125 | 126 | //draw set of letters 127 | function drawHTML(chars) { 128 | let topEl = document.body.appendChild(document.createElement('div')) 129 | topEl.style.marginTop = '1rem' 130 | topEl.style.marginRight = '1rem' 131 | topEl.style.position = 'relative' 132 | topEl.style.minWidth = w + 'px' 133 | topEl.style.lineHeight = '2rem' 134 | 135 | let step = 100 136 | let fs = 50 137 | let fontRatio = 4.5 138 | 139 | for (let i = 0; i < chars.length; i++) { 140 | let char = chars[i] 141 | let el = topEl.appendChild(document.createElement('span')) 142 | el.innerHTML = char 143 | el.style.fontSize = '2rem' 144 | } 145 | 146 | let bottomEl = document.body.appendChild(document.createElement('div')) 147 | bottomEl.style.position = 'relative' 148 | bottomEl.style.minWidth = w + 'px' 149 | bottomEl.style.lineHeight = '2rem' 150 | bottomEl.style.marginTop = '1rem' 151 | 152 | for (let i = 0; i < chars.length; i++) { 153 | let char = chars[i] 154 | 155 | let el = bottomEl.appendChild(document.createElement('span')) 156 | el.innerHTML = char 157 | 158 | let {center, bounds, radius} = optics(chars[i], {size: step, fontSize: fs}) 159 | center[0] /= step, center[1] /= step, radius /= step, bounds[3] /= step, bounds[1] /= step 160 | let scale = .25/radius 161 | let diff = .5 - center[1] 162 | 163 | el.style.fontSize = 2*scale + 'rem'; 164 | el.style.position = 'relative' 165 | el.style.display = 'inline-block' 166 | el.style.verticalAlign = 'middle' 167 | el.style.top = fontRatio*diff*scale + 'rem'; 168 | } 169 | } 170 | 171 | //draw set of letters 172 | function drawCanvas(chars) { 173 | let canvas = document.body.appendChild(document.createElement('canvas')) 174 | canvas.width = 720 175 | canvas.height = 200 176 | canvas.style.marginTop = '1rem' 177 | let ctx = canvas.getContext('2d') 178 | 179 | let w = canvas.width, h = canvas.height 180 | let step = 40 181 | let fs = 20 182 | 183 | // ctx.fillStyle = 'black' 184 | // ctx.fillRect(0, 0, w, h) 185 | 186 | ctx.textBaseline = 'middle' 187 | ctx.textAlign = 'center' 188 | 189 | for (let i = 0; i < chars.length; i++) { 190 | let char = chars[i] 191 | 192 | ctx.fillStyle = 'rgba(0, 150, 250, .25)' 193 | ctx.fillRect(i*step + step/2, 0, 1, step) 194 | ctx.fillRect(i*step, step/2, step, 1) 195 | 196 | ctx.font = fs + 'px sans-serif' 197 | ctx.fillStyle = 'black' 198 | ctx.fillText(chars[i], i*step + step/2, step/2) 199 | 200 | let {center, bounds, radius} = optics(chars[i], {size: step*10, fontSize: fs*10}) 201 | center[0] /= 10, center[1] /= 10, radius /= 10, bounds[3] /= 10, bounds[1] /= 10, bounds[0] /= 10, bounds[2] /= 10 202 | 203 | // let maxDist = radius*.5 + Math.max( bounds[3]-bounds[1] , bounds[2]-bounds[0] )*.5 204 | let maxDist = radius*.5 + Math.max( (bounds[3]-bounds[1])*.5 , (bounds[2]-bounds[0])*.5 )*.5 205 | let scale = step*.25/maxDist 206 | let diff = [Math.floor(step/2 - center[0]), step/2 - center[1]] 207 | let off = (.5*(bounds[3] + bounds[1]) - h*.5) 208 | 209 | ctx.fillStyle = 'rgba(250, 150, 0, .25)' 210 | ctx.fillRect(i*step + step/2, step, 1, step) 211 | ctx.fillRect(i*step, step + step/2, step, 1) 212 | 213 | ctx.fillStyle = 'black' 214 | ctx.font = fs*scale + 'px sans-serif' 215 | ctx.fillText(chars[i], i*step + step/2 + diff[0]*scale + .5, step + step/2 + diff[1]*scale + .5) 216 | } 217 | } 218 | --------------------------------------------------------------------------------