├── .gitignore ├── .npmignore ├── package.json ├── LICENSE.md ├── paper-sizes.js ├── test.js ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | .npmignore 6 | LICENSE.md -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canvas-dimensions", 3 | "version": "1.0.0", 4 | "description": "utilities for paper and print dimensions", 5 | "type": "module", 6 | "main": "index.js", 7 | "license": "MIT", 8 | "author": { 9 | "name": "Matt DesLauriers", 10 | "url": "https://github.com/mattdesl" 11 | }, 12 | "dependencies": { 13 | "convert-length": "^1.0.1" 14 | }, 15 | "scripts": { 16 | "test": "node test.js" 17 | }, 18 | "keywords": [ 19 | "paper", 20 | "size", 21 | "px", 22 | "in", 23 | "cm", 24 | "mm", 25 | "m", 26 | "a4", 27 | "a3", 28 | "a2", 29 | "a1", 30 | "a0", 31 | "a5", 32 | "a6", 33 | "standard", 34 | "print", 35 | "dimensions", 36 | "sizes" 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "git://github.com/mattdesl/canvas-dimensions.git" 41 | }, 42 | "homepage": "https://github.com/mattdesl/canvas-dimensions", 43 | "bugs": { 44 | "url": "https://github.com/mattdesl/canvas-dimensions/issues" 45 | }, 46 | "devDependencies": { 47 | "tape": "^5.7.5" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2024 Matt DesLauriers 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /paper-sizes.js: -------------------------------------------------------------------------------- 1 | // When no units are specified, it will default to mm 2 | const defaultUnits = "mm"; 3 | 4 | const data = [ 5 | // Common Paper Sizes 6 | // (Mostly North-American based) 7 | ["postcard", 101.6, 152.4], 8 | ["poster-small", 280, 430], 9 | ["poster", 460, 610], 10 | ["poster-large", 610, 910], 11 | ["business-card", 50.8, 88.9], 12 | 13 | // Photographic Print Paper Sizes 14 | ["2r", 64, 89], 15 | ["3r", 89, 127], 16 | ["4r", 102, 152], 17 | ["5r", 127, 178], // 5″x7″ 18 | ["6r", 152, 203], // 6″x8″ 19 | ["8r", 203, 254], // 8″x10″ 20 | ["10r", 254, 305], // 10″x12″ 21 | ["11r", 279, 356], // 11″x14″ 22 | ["12r", 305, 381], 23 | 24 | // Standard Paper Sizes 25 | ["a0", 841, 1189], 26 | ["a1", 594, 841], 27 | ["a2", 420, 594], 28 | ["a3", 297, 420], 29 | ["a4", 210, 297], 30 | ["a5", 148, 210], 31 | ["a6", 105, 148], 32 | ["a7", 74, 105], 33 | ["a8", 52, 74], 34 | ["a9", 37, 52], 35 | ["a10", 26, 37], 36 | ["2a0", 1189, 1682], 37 | ["4a0", 1682, 2378], 38 | ["b0", 1000, 1414], 39 | ["b1", 707, 1000], 40 | ["b1+", 720, 1020], 41 | ["b2", 500, 707], 42 | ["b2+", 520, 720], 43 | ["b3", 353, 500], 44 | ["b4", 250, 353], 45 | ["b5", 176, 250], 46 | ["b6", 125, 176], 47 | ["b7", 88, 125], 48 | ["b8", 62, 88], 49 | ["b9", 44, 62], 50 | ["b10", 31, 44], 51 | ["b11", 22, 32], 52 | ["b12", 16, 22], 53 | ["c0", 917, 1297], 54 | ["c1", 648, 917], 55 | ["c2", 458, 648], 56 | ["c3", 324, 458], 57 | ["c4", 229, 324], 58 | ["c5", 162, 229], 59 | ["c6", 114, 162], 60 | ["c7", 81, 114], 61 | ["c8", 57, 81], 62 | ["c9", 40, 57], 63 | ["c10", 28, 40], 64 | ["c11", 22, 32], 65 | ["c12", 16, 22], 66 | 67 | // Use inches for North American sizes, 68 | // as it produces less float precision errors 69 | ["half-letter", 5.5, 8.5, "in"], 70 | ["letter", 8.5, 11, "in"], 71 | ["legal", 8.5, 14, "in"], 72 | ["junior-legal", 5, 8, "in"], 73 | ["ledger", 11, 17, "in"], 74 | ["tabloid", 11, 17, "in"], 75 | ["ansi-a", 8.5, 11.0, "in"], 76 | ["ansi-b", 11.0, 17.0, "in"], 77 | ["ansi-c", 17.0, 22.0, "in"], 78 | ["ansi-d", 22.0, 34.0, "in"], 79 | ["ansi-e", 34.0, 44.0, "in"], 80 | ["arch-a", 9, 12, "in"], 81 | ["arch-b", 12, 18, "in"], 82 | ["arch-c", 18, 24, "in"], 83 | ["arch-d", 24, 36, "in"], 84 | ["arch-e", 36, 48, "in"], 85 | ["arch-e1", 30, 42, "in"], 86 | ["arch-e2", 26, 38, "in"], 87 | ["arch-e3", 27, 39, "in"], 88 | ]; 89 | 90 | export default data.reduce((dict, preset) => { 91 | const item = { 92 | units: preset[3] || defaultUnits, 93 | dimensions: [preset[1], preset[2]], 94 | }; 95 | dict[preset[0]] = item; 96 | return dict; 97 | }, {}); 98 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import test from "tape"; 2 | import sizes from "./paper-sizes.js"; 3 | import getDocument from "./index.js"; 4 | 5 | test("sizes", async (t) => { 6 | t.deepEqual(sizes.a4, { units: "mm", dimensions: [210, 297] }); 7 | t.deepEqual(sizes.letter, { units: "in", dimensions: [8.5, 11] }); 8 | t.deepEqual(sizes["half-letter"], { units: "in", dimensions: [5.5, 8.5] }); 9 | }); 10 | 11 | test("getSize", async (t) => { 12 | t.throws(() => getDocument(null)); 13 | t.throws(() => getDocument({ dimensions: [1] })); 14 | t.throws(() => getDocument({ dimensions: [1, 2, 3] })); 15 | t.throws(() => getDocument({ dimensions: "aha" })); 16 | t.deepEqual(getDocument([128, 128]), { 17 | width: 128, 18 | height: 128, 19 | units: "px", 20 | canvasWidth: 128, 21 | canvasHeight: 128, 22 | pixelsPerInch: 72, 23 | pixelRatio: 1, 24 | }); 25 | t.deepEqual(getDocument({ dimensions: [128, 128], pixelRatio: 2 }), { 26 | width: 128, 27 | height: 128, 28 | units: "px", 29 | canvasWidth: 256, 30 | canvasHeight: 256, 31 | pixelsPerInch: 72, 32 | pixelRatio: 2, 33 | }); 34 | t.equals(getDocument({ dimensions: [120, 10] }).units, "px"); 35 | t.equals(getDocument({ dimensions: "a4" }).units, "px"); 36 | t.equals(getDocument({ dimensions: "A4", units: "cm" }).units, "cm"); 37 | t.deepEqual(getDocument({ dimensions: "A4", units: "cm" }), { 38 | width: 21, 39 | height: 29.7, 40 | units: "cm", 41 | canvasWidth: 595, 42 | canvasHeight: 842, 43 | pixelsPerInch: 72, 44 | pixelRatio: 1, 45 | }); 46 | 47 | t.deepEqual( 48 | getDocument({ dimensions: "A4", units: "cm", pixelsPerInch: 300 }), 49 | { 50 | width: 21, 51 | height: 29.7, 52 | units: "cm", 53 | canvasWidth: 2480, 54 | canvasHeight: 3508, 55 | pixelsPerInch: 300, 56 | pixelRatio: 1, 57 | } 58 | ); 59 | t.deepEqual( 60 | getDocument({ 61 | dimensions: [200, 100], 62 | orientation: "portrait", 63 | }), 64 | { 65 | width: 100, 66 | height: 200, 67 | units: "px", 68 | canvasWidth: 100, 69 | canvasHeight: 200, 70 | pixelsPerInch: 72, 71 | pixelRatio: 1, 72 | } 73 | ); 74 | t.deepEqual( 75 | getDocument({ 76 | dimensions: [200, 100], 77 | orientation: "landscape", 78 | }), 79 | { 80 | width: 200, 81 | height: 100, 82 | units: "px", 83 | canvasWidth: 200, 84 | canvasHeight: 100, 85 | pixelsPerInch: 72, 86 | pixelRatio: 1, 87 | } 88 | ); 89 | 90 | t.deepEqual( 91 | getDocument({ 92 | dimensions: "A4", 93 | units: "cm", 94 | orientation: "landscape", 95 | pixelsPerInch: 300, 96 | }), 97 | { 98 | width: 29.7, 99 | height: 21, 100 | units: "cm", 101 | canvasWidth: 3508, 102 | canvasHeight: 2480, 103 | pixelsPerInch: 300, 104 | pixelRatio: 1, 105 | } 106 | ); 107 | }); 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # canvas-dimensions 2 | 3 | A utility that provides user and screen units for a 2D Canvas. 4 | 5 | ```js 6 | import getDocument from "canvas-dimensions"; 7 | 8 | const settings = { 9 | // a standard paper size or [w, h] 10 | dimensions: "A4", 11 | // pixel resolution 12 | pixelsPerInch: 300, 13 | // a user coordinate space to work in 14 | units: "cm", 15 | }; 16 | 17 | const { 18 | // Size in user space coordinates 19 | width, 20 | height, 21 | // Size in display/screen coordinates 22 | canvasWidth, 23 | canvasHeight, 24 | } = getDocument(settings); 25 | 26 | // Setup your 2D canvas 27 | canvas.width = canvasWidth; 28 | canvas.height = canvasHeight; 29 | 30 | // Scale context to user coordinates 31 | context.scale(canvasWidth / width, canvasHeight / height); 32 | 33 | // Draw your graphics in user space coordinates 34 | context.fillRect(0, 0, width, height); 35 | ``` 36 | 37 | ## Install 38 | 39 | Use [npm](https://npmjs.com/) to install. 40 | 41 | ```sh 42 | npm install canvas-dimensions --save 43 | ``` 44 | 45 | ## Usage 46 | 47 | #### `output = getDocument(settings = {})` 48 | 49 | Gets document size information from the given input options. 50 | 51 | Input options: 52 | 53 | - `dimensions` can be a string defining a paper size preset like `'A4'` or `'letter'` (case insensitive), or an array of `[ width, height ]` 54 | - `units` a string, the output units you would like to work in, can be `'px'`, `'in'`, `'cm'`, `'mm'` (default `'px'`) 55 | - `pixelsPerInch` used when converting physical sizes to canvas pixel sizes, defaults to `72` 56 | - `orientation` an optional string, can be `"landscape"` or `"portrait"` and will flip the input `dimensions` accordingly, mostly useful if you are specifying a paper size preset. Note, the paper size presets are all portrait by default. 57 | - `pixelRatio` a factor to multiply the final canvas size by, default `1` 58 | 59 | The `settings` input can also just be an `[ width, height ]` array, which is the same as passing `{ dimensions: [ width, height ], units: 'px' }`. 60 | 61 | Output: 62 | 63 | - `units` a string identifying the user coordinate space, such as `'px'` or `'cm'` 64 | - `width` the document width in user coordinates 65 | - `height` the document height in user coordinates 66 | - `pixelsPerInch` the document resolution 67 | - `canvasWidth` the display/pixel width of the resulting canvas 68 | - `canvasHeight` the display/pixel height of the resulting canvas 69 | - `pixelRatio` the pixel ratio that has been applied to canvas size 70 | 71 | #### `import paperSizes from 'canvas-dimensions/paper-sizes.js'` 72 | 73 | The raw list of possible paper size keywords, see [./paper-sizes](./paper-sizes.js). 74 | 75 | ## Recipes 76 | 77 | Some more examples and recipes: 78 | 79 | ```js 80 | // 1280 x 1280 pixel image 81 | doc = getDocument([1280, 1280]); 82 | 83 | // Size canvas to browser size 84 | doc = getDocument({ 85 | dimensions: [window.innerWidth, window.innerHeight], 86 | pixelRatio: window.devicePixelRatio, 87 | }); 88 | 89 | // 18 x 18 " artwork ready for print 90 | doc = getDocument({ 91 | dimensions: [18, 18], 92 | units: "in", 93 | pixelsPerInch: 300, 94 | }); 95 | 96 | // A4 landscape artwork, working in millimeters 97 | doc = getDocument({ 98 | dimensions: "A4", 99 | units: "mm", 100 | orientation: "landscape", 101 | pixelsPerInch: 300, 102 | }); 103 | ``` 104 | 105 | ## License 106 | 107 | MIT, see [LICENSE.md](http://github.com/mattdesl/canvas-dimensions/blob/master/LICENSE.md) for details. 108 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import paperSizes from "./paper-sizes.js"; 2 | import convert from "convert-length"; 3 | 4 | /** 5 | * @typedef {Object} Document 6 | * @type {Object} 7 | * @property {string} units - The units used for the user coordinate space ('px', 'cm', etc.). 8 | * @property {number} width - The width of the document in user coordinates. 9 | * @property {number} height - The height of the document in user coordinates. 10 | * @property {number} pixelsPerInch - The resolution of the document in pixels per inch. 11 | * @property {number} canvasWidth - The display/pixel width of the resulting canvas. 12 | * @property {number} canvasHeight - The display/pixel height of the resulting canvas. 13 | * @property {number} pixelRatio - The pixel ratio that has been applied to the canvas size. 14 | */ 15 | 16 | /** 17 | * Gets document size information based on the given opts. 18 | * 19 | * @param {Object} [opts={}] - The input opts for calculating document size. 20 | * @param {(string|Array)} [opts.dimensions='A4'] - Can be a string defining a paper size preset ('A4', 'letter') or an array [width, height]. 21 | * @param {string} [opts.units='px'] - The output units ('px', 'in', 'cm', 'mm'). Defaults to 'px'. 22 | * @param {number} [opts.pixelsPerInch=72] - Used when converting physical sizes to canvas pixel sizes. 23 | * @param {string} [opts.orientation] - Can be 'landscape' or 'portrait'. Flips the input dimensions if using a paper size preset. 24 | * @param {number} [opts.pixelRatio=1] - A factor to multiply the final canvas size by. 25 | * @returns {Document} The document size information, including width, height, canvas size, and pixel ratio in the specified units. 26 | */ 27 | export default function getDocument(opts = {}) { 28 | if (Array.isArray(opts)) { 29 | opts = { 30 | dimensions: opts, 31 | }; 32 | } 33 | 34 | let dimensions = opts.dimensions; 35 | if (!dimensions) throw new Error("Must specify { dimensions }"); 36 | 37 | const units = opts.units || "px"; 38 | if (typeof dimensions === "string") { 39 | // paper size key 40 | const key = dimensions.toLowerCase(); 41 | if (!(key in paperSizes)) 42 | throw new Error(`No paper size by the key "${dimensions}"`); 43 | const inputSize = paperSizes[key]; 44 | dimensions = inputSize.dimensions.map((d) => 45 | inputSize.units === units ? d : convert(d, inputSize.units, units) 46 | ); 47 | } 48 | 49 | if (!Array.isArray(dimensions)) 50 | throw new Error("expected array or string for { dimensions }"); 51 | dimensions = dimensions.slice(); 52 | if (dimensions.length !== 2) 53 | throw new Error("Expected two dimensional { dimensions }"); 54 | 55 | const initialOrientation = 56 | dimensions[0] > dimensions[1] ? "landscape" : "portrait"; 57 | if (opts.orientation) { 58 | const ori = opts.orientation.toLowerCase(); 59 | if (!["landscape", "portrait"].includes(ori)) 60 | throw new Error('Expected orientation to be "landscape" or "portrait"'); 61 | if (ori !== initialOrientation) { 62 | dimensions.reverse(); 63 | } 64 | } 65 | 66 | const pixelsPerInch = opts.pixelsPerInch || 72; 67 | 68 | let [canvasWidth, canvasHeight] = dimensions.map((d) => 69 | units === "px" 70 | ? d 71 | : convert(d, units, "px", { roundPixel: true, pixelsPerInch }) 72 | ); 73 | 74 | const [width, height] = dimensions; 75 | 76 | const pixelRatio = opts.pixelRatio ?? 1; 77 | canvasWidth *= pixelRatio; 78 | canvasHeight *= pixelRatio; 79 | 80 | return { 81 | width, 82 | height, 83 | units, 84 | canvasWidth, 85 | canvasHeight, 86 | pixelsPerInch, 87 | pixelRatio, 88 | }; 89 | } 90 | --------------------------------------------------------------------------------