├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── .travis.yml
├── LICENSE
├── README.md
├── jest.config.js
├── package.json
├── src
├── app.ts
├── assets
│ ├── .DS_Store
│ ├── facebook_example_new.png
│ ├── instagram_example.png
│ ├── qr_code_example.png
│ ├── telegram_example_new.png
│ └── test
│ │ ├── image_from_readme.png
│ │ ├── rounded_dots.png
│ │ ├── simple_dots.png
│ │ ├── simple_qr.png
│ │ ├── simple_qr_with_image.png
│ │ ├── simple_qr_with_image_margin.png
│ │ ├── simple_qr_with_margin_canvas.png
│ │ └── simple_square_dot.png
├── constants
│ ├── cornerDotTypes.ts
│ ├── cornerSquareTypes.ts
│ ├── dotTypes.ts
│ ├── errorCorrectionLevels.ts
│ ├── errorCorrectionPercents.ts
│ ├── gradientTypes.ts
│ ├── modes.ts
│ └── qrTypes.ts
├── core
│ ├── QRCanvas.ts
│ ├── QRCodeStyling.ts
│ ├── QRCornerDot.ts
│ ├── QRCornerSquare.ts
│ ├── QRDot.ts
│ └── QROptions.ts
├── index.d.ts
├── index.js
├── tools
│ ├── calculateImageSize.ts
│ ├── downloadURI.ts
│ ├── getMode.ts
│ ├── merge.ts
│ └── sanitizeOptions.ts
├── types
│ └── index.ts
└── vue3-qr-code-styling.vue
├── tsconfig.json
├── webpack.config.build.js
└── webpack.config.common.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js, ts}]
2 | indent_style = space
3 | indent_size = 2
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | node_modules
3 | coverage
4 | /*.*
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | node: true
4 | },
5 | parser: '@typescript-eslint/parser',
6 | extends: [
7 | "eslint:recommended",
8 | "plugin:prettier/recommended",
9 | "plugin:jest/recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:@typescript-eslint/eslint-recommended"
12 | ],
13 | parserOptions: {
14 | sourceType: "module"
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDEA
2 | .idea
3 |
4 | # Lib folder
5 | /lib
6 |
7 | # npm modules
8 | /node_modules
9 |
10 | # Tests coverage results
11 | /coverage
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | coverage
2 | src
3 | .editorconfig
4 | .eslintignore
5 | .eslintrc.js
6 | .gitignore
7 | .prettierrc.js
8 | jest.config.js
9 | tsconfig.json
10 | webpack.config.build.js
11 | webpack.config.common.js
12 | webpack.config.dev-server.js
13 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: "none",
4 | singleQuote: false,
5 | printWidth: 120,
6 | tabWidth: 2
7 | };
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | {
2 | "language": "node_js",
3 | "os": ["linux"],
4 | "dist": "xenial",
5 | "node_js": ["node", "13", "12", "11", "10", "8", "6"],
6 | "script": ["node lib/index.js"]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Denys Kozak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://snyk.io/test/github/diadal/vue3-qr-code-styling)
2 |
3 | # Vue3 QR Code Styling
4 |
5 | [](https://www.npmjs.org/package/vue3-qr-code-styling)
6 |
7 | JavaScript library for generating QR codes with a logo and styling.
8 |
9 | this clone copy of https://qr-code-styling.com
10 |
11 | If you have issues / suggestions / notes / questions, please open an issue or contact me. Let's create a cool library together.
12 |
13 | ### Examples
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ### Installation
22 |
23 | ```
24 | npm install @diadal/vue3-qr-code-styling
25 | or
26 | yarn add @diadal/vue3-qr-code-styling
27 | ```
28 |
29 | ### Usage
30 |
31 | ```HTML
32 |
33 |
34 |
63 |
64 |
65 |
66 |
67 |
87 |
88 |
92 | ```
93 |
94 | ### API Documentation
95 |
96 | #### VQRCodeStyling instance
97 |
98 | `new VQRCodeStyling(options) => VQRCodeStyling`
99 |
100 | | Param | Type | Description |
101 | | ------- | ------ | ----------- |
102 | | options | object | Init object |
103 |
104 | `options` structure
105 |
106 | | Property | Type | Default Value | Description |
107 | | ----------------------- | ------ | ------------- | ----------------------------------------------------- |
108 | | width | number | `300` | Size of canvas |
109 | | height | number | `300` | Size of canvas |
110 | | download | boolean| false | To endable download button |
111 | | myclass | string | '' | Image DIV class |
112 | | imgclass | string | '' | Image class |
113 | | downloadButton | string | '' | download button class |
114 | | downloadOptions | object | | download option name and extension |
115 | | data | string | | The date will be encoded to the QR code |
116 | | image | string | | The image will be copied to the center of the QR code |
117 | | margin | number | `0` | Margin around canvas |
118 | | qrOptions | object | | Options will be passed to `qrcode-generator` lib |
119 | | imageOptions | object | | Specific image options, details see below |
120 | | dotsOptions | object | | Dots styling options |
121 | | cornersSquareOptions | object | | Square in the corners styling options |
122 | | cornersDotOptionsHelper | object | | Dots in the corners styling options |
123 | | backgroundOptions | object | | QR background styling options |
124 |
125 | `options.qrOptions` structure
126 |
127 | | Property | Type | Default Value |
128 | | -------------------- | -------------------------------------------------- | ------------- |
129 | | typeNumber | number (`0 - 40`) | `0` |
130 | | mode | string (`'Numeric' 'Alphanumeric' 'Byte' 'Kanji'`) |
131 | | errorCorrectionLevel | string (`'L' 'M' 'Q' 'H'`) | `'Q'` |
132 |
133 | `options.imageOptions` structure
134 |
135 | | Property | Type | Default Value | Description |
136 | | ------------------ | --------------------------------------- | ------------- | ------------------------------------------------------------------------------ |
137 | | hideBackgroundDots | boolean | `true` | Hide all dots covered by the image |
138 | | imageSize | number | `0.4` | Coefficient of the image size. Not recommended to use ove 0.5. Lower is better |
139 | | margin | number | `0` | Margin of the image in px |
140 | | crossOrigin | string(`'anonymous' 'use-credentials'`) | | Set "anonymous" if you want to download QR code from other origins. |
141 |
142 | `options.dotsOptions` structure
143 |
144 | | Property | Type | Default Value | Description |
145 | | -------- | ------------------------------------------------------------------------------ | ------------- | ------------------- |
146 | | color | string | `'#000'` | Color of QR dots |
147 | | gradient | object | | Gradient of QR dots |
148 | | type | string (`'rounded' 'dots' 'classy' 'classy-rounded' 'square' 'extra-rounded'`) | `'square'` | Style of QR dots |
149 |
150 | `options.backgroundOptions` structure
151 |
152 | | Property | Type | Default Value |
153 | | -------- | ------ | ------------- |
154 | | color | string | `'#fff'` |
155 | | gradient | object |
156 |
157 | `options.cornersSquareOptions` structure
158 |
159 | | Property | Type | Default Value | Description |
160 | | -------- | ----------------------------------------- | ------------- | -------------------------- |
161 | | color | string | | Color of Corners Square |
162 | | gradient | object | | Gradient of Corners Square |
163 | | type | string (`'dot' 'square' 'extra-rounded'`) | | Style of Corners Square |
164 |
165 | `options.cornersDotOptions` structure
166 |
167 | | Property | Type | Default Value | Description |
168 | | -------- | ------------------------- | ------------- | ----------------------- |
169 | | color | string | | Color of Corners Dot |
170 | | gradient | object | | Gradient of Corners Dot |
171 | | type | string (`'dot' 'square'`) | | Style of Corners Dot |
172 |
173 | Gradient structure
174 |
175 | `options.dotsOptions.gradient`
176 |
177 | `options.backgroundOptions.gradient`
178 |
179 | `options.cornersSquareOptions.gradient`
180 |
181 | `options.cornersDotOptions.gradient`
182 |
183 | | Property | Type | Default Value | Description |
184 | | ---------- | ---------------------------- | ------------- | -------------------------------------------------------------------------------------- |
185 | | type | string (`'linear' 'radial'`) | "linear" | Type of gradient spread |
186 | | rotation | number | 0 | Rotation of gradient in radians (Math.PI === 180 degrees) |
187 | | colorStops | array of objects | | Gradient colors. Example `[{ offset: 0, color: 'blue' }, { offset: 1, color: 'red' }]` |
188 |
189 | Gradient colorStops structure
190 |
191 | `options.dotsOptions.gradient.colorStops[]`
192 |
193 | `options.backgroundOptions.gradient.colorStops[]`
194 |
195 | `options.cornersSquareOptions.gradient.colorStops[]`
196 |
197 | `options.cornersDotOptions.gradient.colorStops[]`
198 |
199 | | Property | Type | Default Value | Description |
200 | | -------- | ---------------- | ------------- | ----------------------------------- |
201 | | offset | number (`0 - 1`) | | Position of color in gradient range |
202 | | color | string | | Color of stop in gradient range |
203 |
204 | #### VQRCodeStyling methods
205 |
206 | `VQRCodeStyling.append(container) => void`
207 |
208 | | Param | Type | Description |
209 | | --------- | ----------- | -------------------------------------------------------- |
210 | | container | DOM element | This container will be used for appending of the QR code |
211 |
212 | `VQRCodeStyling.update(options) => void`
213 |
214 | | Param | Type | Description |
215 | | ------- | ------ | -------------------------------------- |
216 | | options | object | The same options as for initialization |
217 |
218 | `VQRCodeStyling.download(downloadOptions) => void`
219 |
220 | | Param | Type | Description |
221 | | --------------- | ------ | ------------------------------------------------------ |
222 | | downloadOptions | object | Options with extension and name of file (not required) |
223 |
224 | `downloadOptions` structure
225 |
226 | | Property | Type | Default Value | Description |
227 | | --------- | ------------------------------ | ------------- | --------------------------- |
228 | | name | string | `'qr'` | Name of the downloaded file |
229 | | extension | string (`'png' 'jpeg' 'webp'`) | `'png'` | File extension |
230 |
231 | if any issue [check](https://github.com/diadal/vue3-qr-code-styling/issues)
232 |
233 | _also you can buy me a coffee @ [Patreon](https://www.patreon.com/diadal)_
234 |
235 | ### License
236 |
237 | [MIT License](https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/master/LICENSE). Copyright (c) 2021 Diadal Nig
238 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const { defaults: tsjPreset } = require("ts-jest/presets");
2 |
3 | // For a detailed explanation regarding each configuration property, visit:
4 | // https://jestjs.io/docs/en/configuration.html
5 |
6 | module.exports = {
7 | // All imported modules in your tests should be mocked automatically
8 | // automock: false,
9 |
10 | // Stop running tests after `n` failures
11 | // bail: 0,
12 |
13 | // Respect "browser" field in package.json when resolving modules
14 | // browser: false,
15 |
16 | // The directory where Jest should store its cached dependency information
17 | // cacheDirectory: "/tmp/jest_rs",
18 |
19 | // Automatically clear mock calls and instances between every test
20 | // clearMocks: false,
21 |
22 | // Indicates whether the coverage information should be collected while executing the test
23 | collectCoverage: true,
24 |
25 | // An array of glob patterns indicating a set of files for which coverage information should be collected
26 | collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
27 |
28 | // The directory where Jest should output its coverage files
29 | coverageDirectory: "coverage",
30 |
31 | // An array of regexp pattern strings used to skip coverage collection
32 | // coveragePathIgnorePatterns: [
33 | // "/node_modules/"
34 | // ],
35 |
36 | // A list of reporter names that Jest uses when writing coverage reports
37 | // coverageReporters: [
38 | // "json",
39 | // "text",
40 | // "lcov",
41 | // "clover"
42 | // ],
43 |
44 | // An object that configures minimum threshold enforcement for coverage results
45 | // coverageThreshold: null,
46 |
47 | // A path to a custom dependency extractor
48 | // dependencyExtractor: null,
49 |
50 | // Make calling deprecated APIs throw helpful error messages
51 | // errorOnDeprecated: false,
52 |
53 | // Force coverage collection from ignored files using an array of glob patterns
54 | // forceCoverageMatch: [],
55 |
56 | // A path to a module which exports an async function that is triggered once before all test suites
57 | // globalSetup: null,
58 |
59 | // A path to a module which exports an async function that is triggered once after all test suites
60 | // globalTeardown: null,
61 |
62 | // A set of global variables that need to be available in all test environments
63 | // globals: {},
64 |
65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
66 | // maxWorkers: "50%",
67 |
68 | // An array of directory names to be searched recursively up from the requiring module's location
69 | // moduleDirectories: [
70 | // "node_modules"
71 | // ],
72 |
73 | // An array of file extensions your modules use
74 | moduleFileExtensions: ["ts", "js", "json"],
75 |
76 | // A map from regular expressions to module names that allow to stub out resources with a single module
77 | // moduleNameMapper: {},
78 |
79 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
80 | // modulePathIgnorePatterns: [],
81 |
82 | // Activates notifications for test results
83 | // notify: false,
84 |
85 | // An enum that specifies notification mode. Requires { notify: true }
86 | // notifyMode: "failure-change",
87 |
88 | // A preset that is used as a base for Jest's configuration
89 | preset: "ts-jest",
90 |
91 | // Run tests from one or more projects
92 | // projects: null,
93 |
94 | // Use this configuration option to add custom reporters to Jest
95 | // reporters: undefined,
96 |
97 | // Automatically reset mock state between every test
98 | // resetMocks: false,
99 |
100 | // Reset the module registry before running each individual test
101 | // resetModules: false,
102 |
103 | // A path to a custom resolver
104 | // resolver: null,
105 |
106 | // Automatically restore mock state between every test
107 | // restoreMocks: false,
108 |
109 | // The root directory that Jest should scan for tests and modules within
110 | // rootDir: null,
111 |
112 | // A list of paths to directories that Jest should use to search for files in
113 | roots: ["src"],
114 |
115 | // Allows you to use a custom runner instead of Jest's default test runner
116 | // runner: "jest-runner",
117 |
118 | // The paths to modules that run some code to configure or set up the testing environment before each test
119 | // setupFiles: [],
120 |
121 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
122 | // setupFilesAfterEnv: [],
123 |
124 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
125 | // snapshotSerializers: [],
126 |
127 | // The test environment that will be used for testing
128 | testEnvironment: "jest-environment-jsdom-fifteen",
129 |
130 | // Options that will be passed to the testEnvironment
131 | testEnvironmentOptions: {
132 | resources: "usable"
133 | },
134 |
135 | // Adds a location field to test results
136 | // testLocationInResults: false,
137 |
138 | // The glob patterns Jest uses to detect test files
139 | // testMatch: [
140 | // "**/__tests__/**/*.[jt]s?(x)",
141 | // "**/?(*.)+(spec|test).[tj]s?(x)"
142 | // ],
143 |
144 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
145 | // testPathIgnorePatterns: [
146 | // "/node_modules/"
147 | // ],
148 |
149 | // The regexp pattern or array of patterns that Jest uses to detect test files
150 | // testRegex: [],
151 |
152 | // This option allows the use of a custom results processor
153 | // testResultsProcessor: null,
154 |
155 | // This option allows use of a custom test runner
156 | // testRunner: "jasmine2",
157 |
158 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
159 | // testURL: "http://localhost",
160 |
161 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
162 | // timers: "real",
163 |
164 | // A map from regular expressions to paths to transformers
165 | transform: {
166 | "^.+\\.(js|ts)$": "ts-jest"
167 | }
168 |
169 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
170 | // transformIgnorePatterns: [
171 | // "/node_modules/"
172 | // ],
173 |
174 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
175 | // unmockedModulePathPatterns: undefined,
176 |
177 | // Indicates whether each individual test should be reported during the run
178 | // verbose: null,
179 |
180 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
181 | // watchPathIgnorePatterns: [],
182 |
183 | // Whether to use watchman for file crawling
184 | // watchman: true,
185 | };
186 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@diadal/vue3-qr-code-styling",
3 | "version": "1.4.6",
4 | "description": "Add a style and an image to your QR code Vue3",
5 | "main": "src/index.js",
6 | "types": "src/index.d.ts",
7 | "files": [
8 | "src"
9 | ],
10 | "dependencies": {
11 | "@vue/compiler-sfc": "^3.0.6",
12 | "qrcode-generator": "^1.4.3",
13 | "vue": "^3.0.6",
14 | "vue-loader": "^16.1.2"
15 | },
16 | "devDependencies": {
17 | "@typescript-eslint/eslint-plugin": "^4.13.0",
18 | "@typescript-eslint/parser": "^4.13.0",
19 | "canvas": "^2.6.1",
20 | "clean-webpack-plugin": "^3.0.0",
21 | "eslint": "^7.17.0",
22 | "eslint-config-prettier": "^7.1.0",
23 | "eslint-loader": "^4.0.2",
24 | "eslint-plugin-jest": "^24.1.3",
25 | "eslint-plugin-prettier": "^3.3.1",
26 | "html-webpack-plugin": "^4.5.1",
27 | "jest": "^26.6.3",
28 | "jest-environment-jsdom-fifteen": "^1.0.0",
29 | "prettier": "^2.2.1",
30 | "ts-jest": "^26.4.4",
31 | "ts-loader": "^8.0.17",
32 | "typescript": "^4.2.2",
33 | "webpack": "^5.24.2",
34 | "webpack-cli": "^4.5.0",
35 | "webpack-dev-server": "^3.11.2",
36 | "webpack-merge": "^5.7.3"
37 | },
38 | "scripts": {
39 | "build": "webpack --mode=production --config webpack.config.build.js && copyfiles -f ./src/*.vue lib",
40 | "build:dev": "webpack --mode=development --config webpack.config.build.js",
41 | "test": "jest"
42 | },
43 | "repository": {
44 | "type": "git",
45 | "url": "git+https://github.com/diadal/vue3-qr-code-styling.git"
46 | },
47 | "keywords": [
48 | "qr",
49 | "qrcode",
50 | "qr-code",
51 | "js",
52 | "qrjs",
53 | "qrstyling",
54 | "styling",
55 | "qrbranding",
56 | "branding",
57 | "qrimage",
58 | "image",
59 | "qrlogo",
60 | "logo",
61 | "design"
62 | ],
63 | "author": "Diadal ",
64 | "private": false,
65 | "license": "MIT",
66 | "bugs": {
67 | "url": "https://github.com/diadal/vue3-qr-code-styling/issues"
68 | },
69 | "homepage": "https://github.com/diadal/vue3-qr-code-styling"
70 | }
71 |
--------------------------------------------------------------------------------
/src/app.ts:
--------------------------------------------------------------------------------
1 | import VQRCodeStyling from './core/QRCodeStyling'
2 | import dotTypes from './constants/dotTypes'
3 | import cornerDotTypes from './constants/cornerDotTypes'
4 | import cornerSquareTypes from './constants/cornerSquareTypes'
5 | import errorCorrectionLevels from './constants/errorCorrectionLevels'
6 | import errorCorrectionPercents from './constants/errorCorrectionPercents'
7 | import modes from './constants/modes'
8 | import qrTypes from './constants/qrTypes'
9 |
10 | export { dotTypes, cornerDotTypes, cornerSquareTypes, errorCorrectionLevels, errorCorrectionPercents, modes, qrTypes }
11 | export default VQRCodeStyling
12 |
--------------------------------------------------------------------------------
/src/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/.DS_Store
--------------------------------------------------------------------------------
/src/assets/facebook_example_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/facebook_example_new.png
--------------------------------------------------------------------------------
/src/assets/instagram_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/instagram_example.png
--------------------------------------------------------------------------------
/src/assets/qr_code_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/qr_code_example.png
--------------------------------------------------------------------------------
/src/assets/telegram_example_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/telegram_example_new.png
--------------------------------------------------------------------------------
/src/assets/test/image_from_readme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/image_from_readme.png
--------------------------------------------------------------------------------
/src/assets/test/rounded_dots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/rounded_dots.png
--------------------------------------------------------------------------------
/src/assets/test/simple_dots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/simple_dots.png
--------------------------------------------------------------------------------
/src/assets/test/simple_qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/simple_qr.png
--------------------------------------------------------------------------------
/src/assets/test/simple_qr_with_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/simple_qr_with_image.png
--------------------------------------------------------------------------------
/src/assets/test/simple_qr_with_image_margin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/simple_qr_with_image_margin.png
--------------------------------------------------------------------------------
/src/assets/test/simple_qr_with_margin_canvas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/simple_qr_with_margin_canvas.png
--------------------------------------------------------------------------------
/src/assets/test/simple_square_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diadal/vue3-qr-code-styling/e6d53ebac1006be50a2779dce26e205aa84df340/src/assets/test/simple_square_dot.png
--------------------------------------------------------------------------------
/src/constants/cornerDotTypes.ts:
--------------------------------------------------------------------------------
1 | import { CornerDotTypes } from '../types'
2 |
3 | export default {
4 | dot: 'dot',
5 | square: 'square'
6 | } as CornerDotTypes
7 |
--------------------------------------------------------------------------------
/src/constants/cornerSquareTypes.ts:
--------------------------------------------------------------------------------
1 | import { CornerSquareTypes } from '../types'
2 |
3 | export default {
4 | dot: 'dot',
5 | square: 'square',
6 | extraRounded: 'extra-rounded'
7 | } as CornerSquareTypes
8 |
--------------------------------------------------------------------------------
/src/constants/dotTypes.ts:
--------------------------------------------------------------------------------
1 | import { DotTypes } from '../types'
2 |
3 | export default {
4 | dots: 'dots',
5 | rounded: 'rounded',
6 | classy: 'classy',
7 | classyRounded: 'classy-rounded',
8 | square: 'square',
9 | extraRounded: 'extra-rounded'
10 | } as DotTypes
11 |
--------------------------------------------------------------------------------
/src/constants/errorCorrectionLevels.ts:
--------------------------------------------------------------------------------
1 | import { ErrorCorrectionLevel } from '../types'
2 |
3 | interface ErrorCorrectionLevels {
4 | [key: string]: ErrorCorrectionLevel;
5 | }
6 |
7 | export default {
8 | L: 'L',
9 | M: 'M',
10 | Q: 'Q',
11 | H: 'H'
12 | } as ErrorCorrectionLevels
13 |
--------------------------------------------------------------------------------
/src/constants/errorCorrectionPercents.ts:
--------------------------------------------------------------------------------
1 | interface ErrorCorrectionPercents {
2 | [key: string]: number;
3 | }
4 |
5 | export default {
6 | L: 0.07,
7 | M: 0.15,
8 | Q: 0.25,
9 | H: 0.3
10 | } as ErrorCorrectionPercents
11 |
--------------------------------------------------------------------------------
/src/constants/gradientTypes.ts:
--------------------------------------------------------------------------------
1 | import { GradientTypes } from '../types'
2 |
3 | export default {
4 | radial: 'radial',
5 | linear: 'linear'
6 | } as GradientTypes
7 |
--------------------------------------------------------------------------------
/src/constants/modes.ts:
--------------------------------------------------------------------------------
1 | import { Mode } from '../types'
2 |
3 | interface Modes {
4 | [key: string]: Mode;
5 | }
6 |
7 | export default {
8 | numeric: 'Numeric',
9 | alphanumeric: 'Alphanumeric',
10 | byte: 'Byte',
11 | kanji: 'Kanji'
12 | } as Modes
13 |
--------------------------------------------------------------------------------
/src/constants/qrTypes.ts:
--------------------------------------------------------------------------------
1 | import { TypeNumber } from '../types'
2 |
3 | interface TypesMap {
4 | [key: number]: TypeNumber;
5 | }
6 |
7 | const qrTypes: TypesMap = {}
8 |
9 | for (let type = 0; type <= 40; type++) {
10 | qrTypes[type] = type as TypeNumber
11 | }
12 |
13 | // 0 types is autodetect
14 |
15 | // types = {
16 | // 0: 0,
17 | // 1: 1,
18 | // ...
19 | // 40: 40
20 | // }
21 |
22 | export default qrTypes
23 |
--------------------------------------------------------------------------------
/src/core/QRCanvas.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-throw-literal */
2 | import calculateImageSize from '../tools/calculateImageSize'
3 | import errorCorrectionPercents from '../constants/errorCorrectionPercents'
4 | import QRDot from './QRDot'
5 | import QRCornerSquare from './QRCornerSquare'
6 | import QRCornerDot from './QRCornerDot'
7 | import { RequiredOptions, Gradient } from './QROptions'
8 | import gradientTypes from '../constants/gradientTypes'
9 | import { QRCode } from '../types'
10 |
11 | type FilterFunction = (i: number, j: number) => boolean;
12 |
13 | const squareMask = [
14 | [1, 1, 1, 1, 1, 1, 1],
15 | [1, 0, 0, 0, 0, 0, 1],
16 | [1, 0, 0, 0, 0, 0, 1],
17 | [1, 0, 0, 0, 0, 0, 1],
18 | [1, 0, 0, 0, 0, 0, 1],
19 | [1, 0, 0, 0, 0, 0, 1],
20 | [1, 1, 1, 1, 1, 1, 1]
21 | ]
22 |
23 | const dotMask = [
24 | [0, 0, 0, 0, 0, 0, 0],
25 | [0, 0, 0, 0, 0, 0, 0],
26 | [0, 0, 1, 1, 1, 0, 0],
27 | [0, 0, 1, 1, 1, 0, 0],
28 | [0, 0, 1, 1, 1, 0, 0],
29 | [0, 0, 0, 0, 0, 0, 0],
30 | [0, 0, 0, 0, 0, 0, 0]
31 | ]
32 |
33 | export default class QRCanvas {
34 | _canvas: HTMLCanvasElement;
35 | _options: RequiredOptions;
36 | _qr?: QRCode;
37 | _image?: HTMLImageElement;
38 |
39 | // TODO don't pass all options to this class
40 | constructor (options: RequiredOptions) {
41 | this._canvas = document.createElement('canvas')
42 | this._canvas.width = options.width
43 | this._canvas.height = options.height
44 | this._options = options
45 | }
46 |
47 | get context (): CanvasRenderingContext2D | null {
48 | return this._canvas.getContext('2d')
49 | }
50 |
51 | get width (): number {
52 | return this._canvas.width
53 | }
54 |
55 | get height (): number {
56 | return this._canvas.height
57 | }
58 |
59 | getCanvas (): HTMLCanvasElement {
60 | return this._canvas
61 | }
62 |
63 | clear (): void {
64 | const canvasContext = this.context
65 |
66 | if (canvasContext) {
67 | canvasContext.clearRect(0, 0, this._canvas.width, this._canvas.height)
68 | }
69 | }
70 |
71 | async drawQR (qr: QRCode): Promise {
72 | const count = qr.getModuleCount()
73 | const minSize = Math.min(this._options.width, this._options.height) - this._options.margin * 2
74 | const dotSize = Math.floor(minSize / count)
75 | let drawImageSize = {
76 | hideXDots: 0,
77 | hideYDots: 0,
78 | width: 0,
79 | height: 0
80 | }
81 |
82 | this._qr = qr
83 |
84 | if (this._options.image) {
85 | await this.loadImage()
86 | if (!this._image) return
87 | const { imageOptions, qrOptions } = this._options
88 | const coverLevel = imageOptions.imageSize * errorCorrectionPercents[qrOptions.errorCorrectionLevel]
89 | const maxHiddenDots = Math.floor(coverLevel * count * count)
90 |
91 | drawImageSize = calculateImageSize({
92 | originalWidth: this._image.width,
93 | originalHeight: this._image.height,
94 | maxHiddenDots,
95 | maxHiddenAxisDots: count - 14,
96 | dotSize
97 | })
98 | }
99 |
100 | this.clear()
101 | this.drawBackground()
102 | this.drawDots((i: number, j: number): boolean => {
103 | if (this._options.imageOptions.hideBackgroundDots) {
104 | if (
105 | i >= (count - drawImageSize.hideXDots) / 2 &&
106 | i < (count + drawImageSize.hideXDots) / 2 &&
107 | j >= (count - drawImageSize.hideYDots) / 2 &&
108 | j < (count + drawImageSize.hideYDots) / 2
109 | ) {
110 | return false
111 | }
112 | }
113 |
114 | if (squareMask[i]?.[j] || squareMask[i - count + 7]?.[j] || squareMask[i]?.[j - count + 7]) {
115 | return false
116 | }
117 |
118 | if (dotMask[i]?.[j] || dotMask[i - count + 7]?.[j] || dotMask[i]?.[j - count + 7]) {
119 | return false
120 | }
121 |
122 | return true
123 | })
124 | this.drawCorners()
125 |
126 | if (this._options.image) {
127 | this.drawImage({ width: drawImageSize.width, height: drawImageSize.height, count, dotSize })
128 | }
129 | }
130 |
131 | drawBackground (): void {
132 | const canvasContext = this.context
133 | const options = this._options
134 |
135 | if (canvasContext) {
136 | if (options.backgroundOptions.gradient) {
137 | const gradientOptions = options.backgroundOptions.gradient
138 | const gradient = this._createGradient({
139 | context: canvasContext,
140 | options: gradientOptions,
141 | additionalRotation: 0,
142 | x: 0,
143 | y: 0,
144 | size: this._canvas.width > this._canvas.height ? this._canvas.width : this._canvas.height
145 | })
146 |
147 | gradientOptions.colorStops.forEach(({ offset, color }: { offset: number; color: string }) => {
148 | gradient.addColorStop(offset, color)
149 | })
150 |
151 | canvasContext.fillStyle = gradient
152 | } else if (options.backgroundOptions.color) {
153 | canvasContext.fillStyle = options.backgroundOptions.color
154 | }
155 | canvasContext.fillRect(0, 0, this._canvas.width, this._canvas.height)
156 | }
157 | }
158 |
159 | drawDots (filter?: FilterFunction): void {
160 | if (!this._qr) {
161 | throw 'QR code is not defined'
162 | }
163 |
164 | const canvasContext = this.context
165 |
166 | if (!canvasContext) {
167 | throw 'QR code is not defined'
168 | }
169 |
170 | const options = this._options
171 | const count = this._qr.getModuleCount()
172 |
173 | if (count > options.width || count > options.height) {
174 | throw 'The canvas is too small.'
175 | }
176 |
177 | const minSize = Math.min(options.width, options.height) - options.margin * 2
178 | const dotSize = Math.floor(minSize / count)
179 | const xBeginning = Math.floor((options.width - count * dotSize) / 2)
180 | const yBeginning = Math.floor((options.height - count * dotSize) / 2)
181 | const dot = new QRDot({ context: canvasContext, type: options.dotsOptions.type })
182 |
183 | canvasContext.beginPath()
184 |
185 | for (let i = 0; i < count; i++) {
186 | for (let j = 0; j < count; j++) {
187 | if (filter && !filter(i, j)) {
188 | continue
189 | }
190 | if (!this._qr.isDark(i, j)) {
191 | continue
192 | }
193 | dot.draw(
194 | xBeginning + i * dotSize,
195 | yBeginning + j * dotSize,
196 | dotSize,
197 | (xOffset: number, yOffset: number): boolean => {
198 | if (i + xOffset < 0 || j + yOffset < 0 || i + xOffset >= count || j + yOffset >= count) return false
199 | if (filter && !filter(i + xOffset, j + yOffset)) return false
200 | return !!this._qr && this._qr.isDark(i + xOffset, j + yOffset)
201 | }
202 | )
203 | }
204 | }
205 |
206 | if (options.dotsOptions.gradient) {
207 | const gradientOptions = options.dotsOptions.gradient
208 | const gradient = this._createGradient({
209 | context: canvasContext,
210 | options: gradientOptions,
211 | additionalRotation: 0,
212 | x: xBeginning,
213 | y: yBeginning,
214 | size: count * dotSize
215 | })
216 |
217 | gradientOptions.colorStops.forEach(({ offset, color }: { offset: number; color: string }) => {
218 | gradient.addColorStop(offset, color)
219 | })
220 |
221 | canvasContext.fillStyle = canvasContext.strokeStyle = gradient
222 | } else if (options.dotsOptions.color) {
223 | canvasContext.fillStyle = canvasContext.strokeStyle = options.dotsOptions.color
224 | }
225 |
226 | canvasContext.fill('evenodd')
227 | }
228 |
229 | drawCorners (filter?: FilterFunction): void {
230 | if (!this._qr) {
231 | throw 'QR code is not defined'
232 | }
233 |
234 | const canvasContext = this.context
235 |
236 | if (!canvasContext) {
237 | throw 'QR code is not defined'
238 | }
239 |
240 | const options = this._options
241 |
242 | const count = this._qr.getModuleCount()
243 | const minSize = Math.min(options.width, options.height) - options.margin * 2
244 | const dotSize = Math.floor(minSize / count)
245 | const cornersSquareSize = dotSize * 7
246 | const cornersDotSize = dotSize * 3
247 | const xBeginning = Math.floor((options.width - count * dotSize) / 2)
248 | const yBeginning = Math.floor((options.height - count * dotSize) / 2);
249 |
250 | [
251 | [0, 0, 0],
252 | [1, 0, Math.PI / 2],
253 | [0, 1, -Math.PI / 2]
254 | ].forEach(([column, row, rotation]) => {
255 | if (filter && !filter(column, row)) {
256 | return
257 | }
258 |
259 | const x = xBeginning + column * dotSize * (count - 7)
260 | const y = yBeginning + row * dotSize * (count - 7)
261 |
262 | if (options.cornersSquareOptions?.type) {
263 | const cornersSquare = new QRCornerSquare({ context: canvasContext, type: options.cornersSquareOptions?.type })
264 |
265 | canvasContext.beginPath()
266 | cornersSquare.draw(x, y, cornersSquareSize, rotation)
267 | } else {
268 | const dot = new QRDot({ context: canvasContext, type: options.dotsOptions.type })
269 |
270 | canvasContext.beginPath()
271 |
272 | for (let i = 0; i < squareMask.length; i++) {
273 | for (let j = 0; j < squareMask[i].length; j++) {
274 | if (!squareMask[i]?.[j]) {
275 | continue
276 | }
277 |
278 | dot.draw(
279 | x + i * dotSize,
280 | y + j * dotSize,
281 | dotSize,
282 | (xOffset: number, yOffset: number): boolean => !!squareMask[i + xOffset]?.[j + yOffset]
283 | )
284 | }
285 | }
286 | }
287 |
288 | if (options.cornersSquareOptions?.gradient) {
289 | const gradientOptions = options.cornersSquareOptions.gradient
290 | const gradient = this._createGradient({
291 | context: canvasContext,
292 | options: gradientOptions,
293 | additionalRotation: rotation,
294 | x,
295 | y,
296 | size: cornersSquareSize
297 | })
298 |
299 | gradientOptions.colorStops.forEach(({ offset, color }: { offset: number; color: string }) => {
300 | gradient.addColorStop(offset, color)
301 | })
302 |
303 | canvasContext.fillStyle = canvasContext.strokeStyle = gradient
304 | } else if (options.cornersSquareOptions?.color) {
305 | canvasContext.fillStyle = canvasContext.strokeStyle = options.cornersSquareOptions.color
306 | }
307 |
308 | canvasContext.fill('evenodd')
309 |
310 | if (options.cornersDotOptions?.type) {
311 | const cornersDot = new QRCornerDot({ context: canvasContext, type: options.cornersDotOptions?.type })
312 |
313 | canvasContext.beginPath()
314 | cornersDot.draw(x + dotSize * 2, y + dotSize * 2, cornersDotSize, rotation)
315 | } else {
316 | const dot = new QRDot({ context: canvasContext, type: options.dotsOptions.type })
317 |
318 | canvasContext.beginPath()
319 |
320 | for (let i = 0; i < dotMask.length; i++) {
321 | for (let j = 0; j < dotMask[i].length; j++) {
322 | if (!dotMask[i]?.[j]) {
323 | continue
324 | }
325 |
326 | dot.draw(
327 | x + i * dotSize,
328 | y + j * dotSize,
329 | dotSize,
330 | (xOffset: number, yOffset: number): boolean => !!dotMask[i + xOffset]?.[j + yOffset]
331 | )
332 | }
333 | }
334 | }
335 |
336 | if (options.cornersDotOptions?.gradient) {
337 | const gradientOptions = options.cornersDotOptions.gradient
338 | const gradient = this._createGradient({
339 | context: canvasContext,
340 | options: gradientOptions,
341 | additionalRotation: rotation,
342 | x: x + dotSize * 2,
343 | y: y + dotSize * 2,
344 | size: cornersDotSize
345 | })
346 |
347 | gradientOptions.colorStops.forEach(({ offset, color }: { offset: number; color: string }) => {
348 | gradient.addColorStop(offset, color)
349 | })
350 |
351 | canvasContext.fillStyle = canvasContext.strokeStyle = gradient
352 | } else if (options.cornersDotOptions?.color) {
353 | canvasContext.fillStyle = canvasContext.strokeStyle = options.cornersDotOptions.color
354 | }
355 |
356 | canvasContext.fill('evenodd')
357 | })
358 | }
359 |
360 | loadImage (): Promise {
361 | return new Promise((resolve, reject) => {
362 | const options = this._options
363 | const image = new Image()
364 |
365 | if (!options.image) {
366 | return reject('Image is not defined')
367 | }
368 |
369 | if (typeof options.imageOptions.crossOrigin === 'string') {
370 | image.crossOrigin = options.imageOptions.crossOrigin
371 | }
372 |
373 | this._image = image
374 | image.onload = (): void => {
375 | resolve()
376 | }
377 | image.src = options.image
378 | })
379 | }
380 |
381 | drawImage ({
382 | width,
383 | height,
384 | count,
385 | dotSize
386 | }: {
387 | width: number;
388 | height: number;
389 | count: number;
390 | dotSize: number;
391 | }): void {
392 | const canvasContext = this.context
393 |
394 | if (!canvasContext) {
395 | throw 'canvasContext is not defined'
396 | }
397 |
398 | if (!this._image) {
399 | throw 'image is not defined'
400 | }
401 |
402 | const options = this._options
403 | const xBeginning = Math.floor((options.width - count * dotSize) / 2)
404 | const yBeginning = Math.floor((options.height - count * dotSize) / 2)
405 | const dx = xBeginning + options.imageOptions.margin + (count * dotSize - width) / 2
406 | const dy = yBeginning + options.imageOptions.margin + (count * dotSize - height) / 2
407 | const dw = width - options.imageOptions.margin * 2
408 | const dh = height - options.imageOptions.margin * 2
409 |
410 | canvasContext.drawImage(this._image, dx, dy, dw < 0 ? 0 : dw, dh < 0 ? 0 : dh)
411 | }
412 |
413 | _createGradient ({
414 | context,
415 | options,
416 | additionalRotation,
417 | x,
418 | y,
419 | size
420 | }: {
421 | context: CanvasRenderingContext2D;
422 | options: Gradient;
423 | additionalRotation: number;
424 | x: number;
425 | y: number;
426 | size: number;
427 | }): CanvasGradient {
428 | let gradient
429 |
430 | if (options.type === gradientTypes.radial) {
431 | gradient = context.createRadialGradient(x + size / 2, y + size / 2, 0, x + size / 2, y + size / 2, size / 2)
432 | } else {
433 | const rotation = ((options.rotation || 0) + additionalRotation) % (2 * Math.PI)
434 | const positiveRotation = (rotation + 2 * Math.PI) % (2 * Math.PI)
435 | let x0 = x + size / 2
436 | let y0 = y + size / 2
437 | let x1 = x + size / 2
438 | let y1 = y + size / 2
439 |
440 | if (
441 | (positiveRotation >= 0 && positiveRotation <= 0.25 * Math.PI) ||
442 | (positiveRotation > 1.75 * Math.PI && positiveRotation <= 2 * Math.PI)
443 | ) {
444 | x0 = x0 - size / 2
445 | y0 = y0 - (size / 2) * Math.tan(rotation)
446 | x1 = x1 + size / 2
447 | y1 = y1 + (size / 2) * Math.tan(rotation)
448 | } else if (positiveRotation > 0.25 * Math.PI && positiveRotation <= 0.75 * Math.PI) {
449 | y0 = y0 - size / 2
450 | x0 = x0 - size / 2 / Math.tan(rotation)
451 | y1 = y1 + size / 2
452 | x1 = x1 + size / 2 / Math.tan(rotation)
453 | } else if (positiveRotation > 0.75 * Math.PI && positiveRotation <= 1.25 * Math.PI) {
454 | x0 = x0 + size / 2
455 | y0 = y0 + (size / 2) * Math.tan(rotation)
456 | x1 = x1 - size / 2
457 | y1 = y1 - (size / 2) * Math.tan(rotation)
458 | } else if (positiveRotation > 1.25 * Math.PI && positiveRotation <= 1.75 * Math.PI) {
459 | y0 = y0 + size / 2
460 | x0 = x0 + size / 2 / Math.tan(rotation)
461 | y1 = y1 - size / 2
462 | x1 = x1 - size / 2 / Math.tan(rotation)
463 | }
464 |
465 | gradient = context.createLinearGradient(Math.round(x0), Math.round(y0), Math.round(x1), Math.round(y1))
466 | }
467 |
468 | return gradient
469 | }
470 | }
471 |
--------------------------------------------------------------------------------
/src/core/QRCodeStyling.ts:
--------------------------------------------------------------------------------
1 | import getMode from '../tools/getMode'
2 | import mergeDeep from '../tools/merge'
3 | import downloadURI from '../tools/downloadURI'
4 | import QRCanvas from './QRCanvas'
5 | import defaultOptions, { Options, RequiredOptions } from './QROptions'
6 | import sanitizeOptions from '../tools/sanitizeOptions'
7 | import { Extension, QRCode } from '../types'
8 | import qrcode from 'qrcode-generator'
9 |
10 | type DownloadOptions = {
11 | name?: string;
12 | extension?: Extension;
13 | };
14 |
15 | export default class QRCodeStyling {
16 | _options: RequiredOptions;
17 | _container?: HTMLElement;
18 | _canvas?: QRCanvas;
19 | _qr?: QRCode;
20 | _drawingPromise?: Promise;
21 |
22 | constructor (options?: Partial) {
23 | this._options = options ? sanitizeOptions(mergeDeep(defaultOptions, options) as RequiredOptions) : defaultOptions
24 | this.update()
25 | }
26 |
27 | static _clearContainer (container?: HTMLElement): void {
28 | if (container) {
29 | container.innerHTML = ''
30 | }
31 | }
32 |
33 | update (options?: Partial): void {
34 | QRCodeStyling._clearContainer(this._container)
35 | this._options = options ? sanitizeOptions(mergeDeep(this._options, options) as RequiredOptions) : this._options
36 |
37 | if (!this._options.data) {
38 | return
39 | }
40 |
41 | this._qr = qrcode(this._options.qrOptions.typeNumber, this._options.qrOptions.errorCorrectionLevel)
42 | this._qr.addData(this._options.data, this._options.qrOptions.mode || getMode(this._options.data))
43 | this._qr.make()
44 | this._canvas = new QRCanvas(this._options)
45 | this._drawingPromise = this._canvas.drawQR(this._qr)
46 | this.append(this._container)
47 | }
48 |
49 | append (container?: HTMLElement): void {
50 | if (!container) {
51 | return
52 | }
53 |
54 | if (typeof container.appendChild !== 'function') {
55 | // eslint-disable-next-line no-throw-literal
56 | throw 'Container should be a single DOM node'
57 | }
58 |
59 | if (this._canvas) {
60 | container.appendChild(this._canvas.getCanvas())
61 | }
62 |
63 | this._container = container
64 | }
65 |
66 | async getImageUrl (extension: string): Promise {
67 | if (!this._drawingPromise) return ''
68 |
69 | const getImageUrl = await this._drawingPromise
70 | if (getImageUrl === undefined) {
71 | if (!this._canvas) return ''
72 | const data = this._canvas.getCanvas().toDataURL(`image/${extension}`)
73 | return data
74 | }
75 | return ''
76 | }
77 |
78 | download (downloadOptions?: Partial): void {
79 | if (!this._drawingPromise) return
80 |
81 | void this._drawingPromise.then(() => {
82 | if (!this._canvas) return
83 | const opt = downloadOptions
84 | const extension = opt.extension || 'png'
85 | const name = opt.name || 'qr'
86 | const data = this._canvas.getCanvas().toDataURL(`image/${extension}`)
87 | downloadURI(data, `${name}.${extension}`)
88 | })
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/core/QRCornerDot.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/unbound-method */
2 | import cornerDotTypes from '../constants/cornerDotTypes'
3 | import { CornerDotType } from '../types'
4 |
5 | type DrawArgs = {
6 | x: number;
7 | y: number;
8 | size: number;
9 | context: CanvasRenderingContext2D;
10 | rotation: number;
11 | };
12 |
13 | type BasicFigureDrawArgs = {
14 | x: number;
15 | y: number;
16 | size: number;
17 | context: CanvasRenderingContext2D;
18 | rotation: number;
19 | };
20 |
21 | type RotateFigureArgs = {
22 | x: number;
23 | y: number;
24 | size: number;
25 | context: CanvasRenderingContext2D;
26 | rotation: number;
27 | draw: () => void;
28 | };
29 |
30 | export default class QRCornerDot {
31 | _context: CanvasRenderingContext2D;
32 | _type: CornerDotType;
33 |
34 | constructor ({ context, type }: { context: CanvasRenderingContext2D; type: CornerDotType }) {
35 | this._context = context
36 | this._type = type
37 | }
38 |
39 | draw (x: number, y: number, size: number, rotation: number): void {
40 | const context = this._context
41 | const type = this._type
42 | let drawFunction
43 |
44 | switch (type) {
45 | case cornerDotTypes.square:
46 | drawFunction = this._drawSquare
47 | break
48 | case cornerDotTypes.dot:
49 | default:
50 | drawFunction = this._drawDot
51 | }
52 |
53 | drawFunction.call(this, { x, y, size, context, rotation })
54 | }
55 |
56 | _rotateFigure ({ x, y, size, context, rotation, draw }: RotateFigureArgs): void {
57 | const cx = x + size / 2
58 | const cy = y + size / 2
59 |
60 | context.translate(cx, cy)
61 | rotation && context.rotate(rotation)
62 | draw()
63 | context.closePath()
64 | rotation && context.rotate(-rotation)
65 | context.translate(-cx, -cy)
66 | }
67 |
68 | _basicDot (args: BasicFigureDrawArgs): void {
69 | const { size, context } = args
70 |
71 | this._rotateFigure({
72 | ...args,
73 | draw: () => {
74 | context.arc(0, 0, size / 2, 0, Math.PI * 2)
75 | }
76 | })
77 | }
78 |
79 | _basicSquare (args: BasicFigureDrawArgs): void {
80 | const { size, context } = args
81 |
82 | this._rotateFigure({
83 | ...args,
84 | draw: () => {
85 | context.rect(-size / 2, -size / 2, size, size)
86 | }
87 | })
88 | }
89 |
90 | _drawDot ({ x, y, size, context, rotation }: DrawArgs): void {
91 | this._basicDot({ x, y, size, context, rotation })
92 | }
93 |
94 | _drawSquare ({ x, y, size, context, rotation }: DrawArgs): void {
95 | this._basicSquare({ x, y, size, context, rotation })
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/core/QRCornerSquare.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/unbound-method */
2 | import cornerSquareTypes from '../constants/cornerSquareTypes'
3 | import { CornerSquareType } from '../types'
4 |
5 | type DrawArgs = {
6 | x: number;
7 | y: number;
8 | size: number;
9 | context: CanvasRenderingContext2D;
10 | rotation: number;
11 | };
12 |
13 | type BasicFigureDrawArgs = {
14 | x: number;
15 | y: number;
16 | size: number;
17 | context: CanvasRenderingContext2D;
18 | rotation: number;
19 | };
20 |
21 | type RotateFigureArgs = {
22 | x: number;
23 | y: number;
24 | size: number;
25 | context: CanvasRenderingContext2D;
26 | rotation: number;
27 | draw: () => void;
28 | };
29 |
30 | export default class QRCornerSquare {
31 | _context: CanvasRenderingContext2D;
32 | _type: CornerSquareType;
33 |
34 | constructor ({ context, type }: { context: CanvasRenderingContext2D; type: CornerSquareType }) {
35 | this._context = context
36 | this._type = type
37 | }
38 |
39 | draw (x: number, y: number, size: number, rotation: number): void {
40 | const context = this._context
41 | const type = this._type
42 | let drawFunction
43 |
44 | switch (type) {
45 | case cornerSquareTypes.square:
46 | drawFunction = this._drawSquare
47 | break
48 | case cornerSquareTypes.extraRounded:
49 | drawFunction = this._drawExtraRounded
50 | break
51 | case cornerSquareTypes.dot:
52 | default:
53 | drawFunction = this._drawDot
54 | }
55 |
56 | drawFunction.call(this, { x, y, size, context, rotation })
57 | }
58 |
59 | _rotateFigure ({ x, y, size, context, rotation, draw }: RotateFigureArgs): void {
60 | const cx = x + size / 2
61 | const cy = y + size / 2
62 |
63 | context.translate(cx, cy)
64 | rotation && context.rotate(rotation)
65 | draw()
66 | context.closePath()
67 | rotation && context.rotate(-rotation)
68 | context.translate(-cx, -cy)
69 | }
70 |
71 | _basicDot (args: BasicFigureDrawArgs): void {
72 | const { size, context } = args
73 | const dotSize = size / 7
74 |
75 | this._rotateFigure({
76 | ...args,
77 | draw: () => {
78 | context.arc(0, 0, size / 2, 0, Math.PI * 2)
79 | context.arc(0, 0, size / 2 - dotSize, 0, Math.PI * 2)
80 | }
81 | })
82 | }
83 |
84 | _basicSquare (args: BasicFigureDrawArgs): void {
85 | const { size, context } = args
86 | const dotSize = size / 7
87 |
88 | this._rotateFigure({
89 | ...args,
90 | draw: () => {
91 | context.rect(-size / 2, -size / 2, size, size)
92 | context.rect(-size / 2 + dotSize, -size / 2 + dotSize, size - 2 * dotSize, size - 2 * dotSize)
93 | }
94 | })
95 | }
96 |
97 | _basicExtraRounded (args: BasicFigureDrawArgs): void {
98 | const { size, context } = args
99 | const dotSize = size / 7
100 |
101 | this._rotateFigure({
102 | ...args,
103 | draw: () => {
104 | context.arc(-dotSize, -dotSize, 2.5 * dotSize, Math.PI, -Math.PI / 2)
105 | context.lineTo(dotSize, -3.5 * dotSize)
106 | context.arc(dotSize, -dotSize, 2.5 * dotSize, -Math.PI / 2, 0)
107 | context.lineTo(3.5 * dotSize, -dotSize)
108 | context.arc(dotSize, dotSize, 2.5 * dotSize, 0, Math.PI / 2)
109 | context.lineTo(-dotSize, 3.5 * dotSize)
110 | context.arc(-dotSize, dotSize, 2.5 * dotSize, Math.PI / 2, Math.PI)
111 | context.lineTo(-3.5 * dotSize, -dotSize)
112 |
113 | context.arc(-dotSize, -dotSize, 1.5 * dotSize, Math.PI, -Math.PI / 2)
114 | context.lineTo(dotSize, -2.5 * dotSize)
115 | context.arc(dotSize, -dotSize, 1.5 * dotSize, -Math.PI / 2, 0)
116 | context.lineTo(2.5 * dotSize, -dotSize)
117 | context.arc(dotSize, dotSize, 1.5 * dotSize, 0, Math.PI / 2)
118 | context.lineTo(-dotSize, 2.5 * dotSize)
119 | context.arc(-dotSize, dotSize, 1.5 * dotSize, Math.PI / 2, Math.PI)
120 | context.lineTo(-2.5 * dotSize, -dotSize)
121 | }
122 | })
123 | }
124 |
125 | _drawDot ({ x, y, size, context, rotation }: DrawArgs): void {
126 | this._basicDot({ x, y, size, context, rotation })
127 | }
128 |
129 | _drawSquare ({ x, y, size, context, rotation }: DrawArgs): void {
130 | this._basicSquare({ x, y, size, context, rotation })
131 | }
132 |
133 | _drawExtraRounded ({ x, y, size, context, rotation }: DrawArgs): void {
134 | this._basicExtraRounded({ x, y, size, context, rotation })
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/core/QRDot.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/unbound-method */
2 | import dotTypes from '../constants/dotTypes'
3 | import { DotType } from '../types'
4 |
5 | type GetNeighbor = (x: number, y: number) => boolean;
6 | type DrawArgs = {
7 | x: number;
8 | y: number;
9 | size: number;
10 | context: CanvasRenderingContext2D;
11 | getNeighbor: GetNeighbor;
12 | };
13 |
14 | type BasicFigureDrawArgs = {
15 | x: number;
16 | y: number;
17 | size: number;
18 | context: CanvasRenderingContext2D;
19 | rotation: number;
20 | };
21 |
22 | type RotateFigureArgs = {
23 | x: number;
24 | y: number;
25 | size: number;
26 | context: CanvasRenderingContext2D;
27 | rotation: number;
28 | draw: () => void;
29 | };
30 |
31 | export default class QRDot {
32 | _context: CanvasRenderingContext2D;
33 | _type: DotType;
34 |
35 | constructor ({ context, type }: { context: CanvasRenderingContext2D; type: DotType }) {
36 | this._context = context
37 | this._type = type
38 | }
39 |
40 | draw (x: number, y: number, size: number, getNeighbor: GetNeighbor): void {
41 | const context = this._context
42 | const type = this._type
43 | let drawFunction
44 |
45 | switch (type) {
46 | case dotTypes.dots:
47 | drawFunction = this._drawDot
48 | break
49 | case dotTypes.classy:
50 | drawFunction = this._drawClassy
51 | break
52 | case dotTypes.classyRounded:
53 | drawFunction = this._drawClassyRounded
54 | break
55 | case dotTypes.rounded:
56 | drawFunction = this._drawRounded
57 | break
58 | case dotTypes.extraRounded:
59 | drawFunction = this._drawExtraRounded
60 | break
61 | case dotTypes.square:
62 | default:
63 | drawFunction = this._drawSquare
64 | }
65 |
66 | drawFunction.call(this, { x, y, size, context, getNeighbor })
67 | }
68 |
69 | _rotateFigure ({ x, y, size, context, rotation, draw }: RotateFigureArgs): void {
70 | const cx = x + size / 2
71 | const cy = y + size / 2
72 |
73 | context.translate(cx, cy)
74 | rotation && context.rotate(rotation)
75 | draw()
76 | context.closePath()
77 | rotation && context.rotate(-rotation)
78 | context.translate(-cx, -cy)
79 | }
80 |
81 | _basicDot (args: BasicFigureDrawArgs): void {
82 | const { size, context } = args
83 |
84 | this._rotateFigure({
85 | ...args,
86 | draw: () => {
87 | context.arc(0, 0, size / 2, 0, Math.PI * 2)
88 | }
89 | })
90 | }
91 |
92 | _basicSquare (args: BasicFigureDrawArgs): void {
93 | const { size, context } = args
94 |
95 | this._rotateFigure({
96 | ...args,
97 | draw: () => {
98 | context.rect(-size / 2, -size / 2, size, size)
99 | }
100 | })
101 | }
102 |
103 | // if rotation === 0 - right side is rounded
104 | _basicSideRounded (args: BasicFigureDrawArgs): void {
105 | const { size, context } = args
106 |
107 | this._rotateFigure({
108 | ...args,
109 | draw: () => {
110 | context.arc(0, 0, size / 2, -Math.PI / 2, Math.PI / 2)
111 | context.lineTo(-size / 2, size / 2)
112 | context.lineTo(-size / 2, -size / 2)
113 | context.lineTo(0, -size / 2)
114 | }
115 | })
116 | }
117 |
118 | // if rotation === 0 - top right corner is rounded
119 | _basicCornerRounded (args: BasicFigureDrawArgs): void {
120 | const { size, context } = args
121 |
122 | this._rotateFigure({
123 | ...args,
124 | draw: () => {
125 | context.arc(0, 0, size / 2, -Math.PI / 2, 0)
126 | context.lineTo(size / 2, size / 2)
127 | context.lineTo(-size / 2, size / 2)
128 | context.lineTo(-size / 2, -size / 2)
129 | context.lineTo(0, -size / 2)
130 | }
131 | })
132 | }
133 |
134 | // if rotation === 0 - top right corner is rounded
135 | _basicCornerExtraRounded (args: BasicFigureDrawArgs): void {
136 | const { size, context } = args
137 |
138 | this._rotateFigure({
139 | ...args,
140 | draw: () => {
141 | context.arc(-size / 2, size / 2, size, -Math.PI / 2, 0)
142 | context.lineTo(-size / 2, size / 2)
143 | context.lineTo(-size / 2, -size / 2)
144 | }
145 | })
146 | }
147 |
148 | _basicCornersRounded (args: BasicFigureDrawArgs): void {
149 | const { size, context } = args
150 |
151 | this._rotateFigure({
152 | ...args,
153 | draw: () => {
154 | context.arc(0, 0, size / 2, -Math.PI / 2, 0)
155 | context.lineTo(size / 2, size / 2)
156 | context.lineTo(0, size / 2)
157 | context.arc(0, 0, size / 2, Math.PI / 2, Math.PI)
158 | context.lineTo(-size / 2, -size / 2)
159 | context.lineTo(0, -size / 2)
160 | }
161 | })
162 | }
163 |
164 | _basicCornersExtraRounded (args: BasicFigureDrawArgs): void {
165 | const { size, context } = args
166 |
167 | this._rotateFigure({
168 | ...args,
169 | draw: () => {
170 | context.arc(-size / 2, size / 2, size, -Math.PI / 2, 0)
171 | context.arc(size / 2, -size / 2, size, Math.PI / 2, Math.PI)
172 | }
173 | })
174 | }
175 |
176 | _drawDot ({ x, y, size, context }: DrawArgs): void {
177 | this._basicDot({ x, y, size, context, rotation: 0 })
178 | }
179 |
180 | _drawSquare ({ x, y, size, context }: DrawArgs): void {
181 | this._basicSquare({ x, y, size, context, rotation: 0 })
182 | }
183 |
184 | _drawRounded ({ x, y, size, context, getNeighbor }: DrawArgs): void {
185 | const leftNeighbor = +getNeighbor(-1, 0)
186 | const rightNeighbor = +getNeighbor(1, 0)
187 | const topNeighbor = +getNeighbor(0, -1)
188 | const bottomNeighbor = +getNeighbor(0, 1)
189 |
190 | const neighborsCount = leftNeighbor + rightNeighbor + topNeighbor + bottomNeighbor
191 |
192 | if (neighborsCount === 0) {
193 | this._basicDot({ x, y, size, context, rotation: 0 })
194 | return
195 | }
196 |
197 | if (neighborsCount > 2 || (leftNeighbor && rightNeighbor) || (topNeighbor && bottomNeighbor)) {
198 | this._basicSquare({ x, y, size, context, rotation: 0 })
199 | return
200 | }
201 |
202 | if (neighborsCount === 2) {
203 | let rotation = 0
204 |
205 | if (leftNeighbor && topNeighbor) {
206 | rotation = Math.PI / 2
207 | } else if (topNeighbor && rightNeighbor) {
208 | rotation = Math.PI
209 | } else if (rightNeighbor && bottomNeighbor) {
210 | rotation = -Math.PI / 2
211 | }
212 |
213 | this._basicCornerRounded({ x, y, size, context, rotation })
214 | return
215 | }
216 |
217 | if (neighborsCount === 1) {
218 | let rotation = 0
219 |
220 | if (topNeighbor) {
221 | rotation = Math.PI / 2
222 | } else if (rightNeighbor) {
223 | rotation = Math.PI
224 | } else if (bottomNeighbor) {
225 | rotation = -Math.PI / 2
226 | }
227 |
228 | this._basicSideRounded({ x, y, size, context, rotation })
229 | }
230 | }
231 |
232 | _drawExtraRounded ({ x, y, size, context, getNeighbor }: DrawArgs): void {
233 | const leftNeighbor = +getNeighbor(-1, 0)
234 | const rightNeighbor = +getNeighbor(1, 0)
235 | const topNeighbor = +getNeighbor(0, -1)
236 | const bottomNeighbor = +getNeighbor(0, 1)
237 |
238 | const neighborsCount = leftNeighbor + rightNeighbor + topNeighbor + bottomNeighbor
239 |
240 | if (neighborsCount === 0) {
241 | this._basicDot({ x, y, size, context, rotation: 0 })
242 | return
243 | }
244 |
245 | if (neighborsCount > 2 || (leftNeighbor && rightNeighbor) || (topNeighbor && bottomNeighbor)) {
246 | this._basicSquare({ x, y, size, context, rotation: 0 })
247 | return
248 | }
249 |
250 | if (neighborsCount === 2) {
251 | let rotation = 0
252 |
253 | if (leftNeighbor && topNeighbor) {
254 | rotation = Math.PI / 2
255 | } else if (topNeighbor && rightNeighbor) {
256 | rotation = Math.PI
257 | } else if (rightNeighbor && bottomNeighbor) {
258 | rotation = -Math.PI / 2
259 | }
260 |
261 | this._basicCornerExtraRounded({ x, y, size, context, rotation })
262 | return
263 | }
264 |
265 | if (neighborsCount === 1) {
266 | let rotation = 0
267 |
268 | if (topNeighbor) {
269 | rotation = Math.PI / 2
270 | } else if (rightNeighbor) {
271 | rotation = Math.PI
272 | } else if (bottomNeighbor) {
273 | rotation = -Math.PI / 2
274 | }
275 |
276 | this._basicSideRounded({ x, y, size, context, rotation })
277 | }
278 | }
279 |
280 | _drawClassy ({ x, y, size, context, getNeighbor }: DrawArgs): void {
281 | const leftNeighbor = +getNeighbor(-1, 0)
282 | const rightNeighbor = +getNeighbor(1, 0)
283 | const topNeighbor = +getNeighbor(0, -1)
284 | const bottomNeighbor = +getNeighbor(0, 1)
285 |
286 | const neighborsCount = leftNeighbor + rightNeighbor + topNeighbor + bottomNeighbor
287 |
288 | if (neighborsCount === 0) {
289 | this._basicCornersRounded({ x, y, size, context, rotation: Math.PI / 2 })
290 | return
291 | }
292 |
293 | if (!leftNeighbor && !topNeighbor) {
294 | this._basicCornerRounded({ x, y, size, context, rotation: -Math.PI / 2 })
295 | return
296 | }
297 |
298 | if (!rightNeighbor && !bottomNeighbor) {
299 | this._basicCornerRounded({ x, y, size, context, rotation: Math.PI / 2 })
300 | return
301 | }
302 |
303 | this._basicSquare({ x, y, size, context, rotation: 0 })
304 | }
305 |
306 | _drawClassyRounded ({ x, y, size, context, getNeighbor }: DrawArgs): void {
307 | const leftNeighbor = +getNeighbor(-1, 0)
308 | const rightNeighbor = +getNeighbor(1, 0)
309 | const topNeighbor = +getNeighbor(0, -1)
310 | const bottomNeighbor = +getNeighbor(0, 1)
311 |
312 | const neighborsCount = leftNeighbor + rightNeighbor + topNeighbor + bottomNeighbor
313 |
314 | if (neighborsCount === 0) {
315 | this._basicCornersRounded({ x, y, size, context, rotation: Math.PI / 2 })
316 | return
317 | }
318 |
319 | if (!leftNeighbor && !topNeighbor) {
320 | this._basicCornerExtraRounded({ x, y, size, context, rotation: -Math.PI / 2 })
321 | return
322 | }
323 |
324 | if (!rightNeighbor && !bottomNeighbor) {
325 | this._basicCornerExtraRounded({ x, y, size, context, rotation: Math.PI / 2 })
326 | return
327 | }
328 |
329 | this._basicSquare({ x, y, size, context, rotation: 0 })
330 | }
331 | }
332 |
--------------------------------------------------------------------------------
/src/core/QROptions.ts:
--------------------------------------------------------------------------------
1 | import qrTypes from '../constants/qrTypes'
2 | import errorCorrectionLevels from '../constants/errorCorrectionLevels'
3 | import {
4 | DotType,
5 | GradientType,
6 | CornerSquareType,
7 | CornerDotType,
8 | TypeNumber,
9 | ErrorCorrectionLevel,
10 | Mode
11 | } from '../types'
12 |
13 | export type Gradient = {
14 | type: GradientType;
15 | rotation?: number;
16 | colorStops: {
17 | offset: number;
18 | color: string;
19 | }[];
20 | };
21 |
22 | export type Options = {
23 | width?: number;
24 | height?: number;
25 | data?: string;
26 | image?: string;
27 | qrOptions?: {
28 | typeNumber?: TypeNumber;
29 | mode?: Mode;
30 | errorCorrectionLevel?: ErrorCorrectionLevel;
31 | };
32 | imageOptions?: {
33 | hideBackgroundDots?: boolean;
34 | imageSize?: number;
35 | crossOrigin?: string;
36 | margin?: number;
37 | };
38 | dotsOptions?: {
39 | type?: DotType;
40 | color?: string;
41 | gradient?: Gradient;
42 | };
43 | cornersSquareOptions?: {
44 | type?: CornerSquareType;
45 | color?: string;
46 | gradient?: Gradient;
47 | };
48 | cornersDotOptions?: {
49 | type?: CornerDotType;
50 | color?: string;
51 | gradient?: Gradient;
52 | };
53 | backgroundOptions?: {
54 | color?: string;
55 | gradient?: Gradient;
56 | };
57 | };
58 |
59 | export interface RequiredOptions extends Options {
60 | width: number;
61 | height: number;
62 | margin: number;
63 | data: string;
64 | qrOptions: {
65 | typeNumber: TypeNumber;
66 | mode?: Mode;
67 | errorCorrectionLevel: ErrorCorrectionLevel;
68 | };
69 | imageOptions: {
70 | hideBackgroundDots: boolean;
71 | imageSize: number;
72 | crossOrigin?: string;
73 | margin: number;
74 | };
75 | dotsOptions: {
76 | type: DotType;
77 | color: string;
78 | gradient?: Gradient;
79 | };
80 | backgroundOptions: {
81 | color: string;
82 | gradient?: Gradient;
83 | };
84 | }
85 |
86 | const defaultOptions: RequiredOptions = {
87 | width: 300,
88 | height: 300,
89 | data: '',
90 | margin: 0,
91 | qrOptions: {
92 | typeNumber: qrTypes[0],
93 | mode: undefined,
94 | errorCorrectionLevel: errorCorrectionLevels.Q
95 | },
96 | imageOptions: {
97 | hideBackgroundDots: true,
98 | imageSize: 0.4,
99 | crossOrigin: undefined,
100 | margin: 0
101 | },
102 | dotsOptions: {
103 | type: 'square',
104 | color: '#000'
105 | },
106 | backgroundOptions: {
107 | color: '#fff'
108 | }
109 | }
110 |
111 | export default defaultOptions
112 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import VueQr3 from './vue3-qr-code-styling.vue'
2 |
3 | export default VueQr3
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import VueQr3 from "./vue3-qr-code-styling.vue";
2 |
3 | export default VueQr3;
4 |
--------------------------------------------------------------------------------
/src/tools/calculateImageSize.ts:
--------------------------------------------------------------------------------
1 | interface ImageSizeOptions {
2 | originalHeight: number;
3 | originalWidth: number;
4 | maxHiddenDots: number;
5 | maxHiddenAxisDots?: number;
6 | dotSize: number;
7 | }
8 |
9 | interface ImageSizeResult {
10 | height: number;
11 | width: number;
12 | hideYDots: number;
13 | hideXDots: number;
14 | }
15 |
16 | export default function calculateImageSize ({
17 | originalHeight,
18 | originalWidth,
19 | maxHiddenDots,
20 | maxHiddenAxisDots,
21 | dotSize
22 | }: ImageSizeOptions): ImageSizeResult {
23 | const hideDots = { x: 0, y: 0 }
24 | const imageSize = { x: 0, y: 0 }
25 |
26 | if (originalHeight <= 0 || originalWidth <= 0 || maxHiddenDots <= 0 || dotSize <= 0) {
27 | return {
28 | height: 0,
29 | width: 0,
30 | hideYDots: 0,
31 | hideXDots: 0
32 | }
33 | }
34 |
35 | const k = originalHeight / originalWidth
36 |
37 | // Getting the maximum possible axis hidden dots
38 | hideDots.x = Math.floor(Math.sqrt(maxHiddenDots / k))
39 | // The count of hidden dot's can't be less than 1
40 | if (hideDots.x <= 0) hideDots.x = 1
41 | // Check the limit of the maximum allowed axis hidden dots
42 | if (maxHiddenAxisDots && maxHiddenAxisDots < hideDots.x) hideDots.x = maxHiddenAxisDots
43 | // The count of dots should be odd
44 | if (hideDots.x % 2 === 0) hideDots.x--
45 | imageSize.x = hideDots.x * dotSize
46 | // Calculate opposite axis hidden dots based on axis value.
47 | // The value will be odd.
48 | // We use ceil to prevent dots covering by the image.
49 | hideDots.y = 1 + 2 * Math.ceil((hideDots.x * k - 1) / 2)
50 | imageSize.y = Math.round(imageSize.x * k)
51 | // If the result dots count is bigger than max - then decrease size and calculate again
52 | if (hideDots.y * hideDots.x > maxHiddenDots || (maxHiddenAxisDots && maxHiddenAxisDots < hideDots.y)) {
53 | if (maxHiddenAxisDots && maxHiddenAxisDots < hideDots.y) {
54 | hideDots.y = maxHiddenAxisDots
55 | if (hideDots.y % 2 === 0) hideDots.x--
56 | } else {
57 | hideDots.y -= 2
58 | }
59 | imageSize.y = hideDots.y * dotSize
60 | hideDots.x = 1 + 2 * Math.ceil((hideDots.y / k - 1) / 2)
61 | imageSize.x = Math.round(imageSize.y / k)
62 | }
63 |
64 | return {
65 | height: imageSize.y,
66 | width: imageSize.x,
67 | hideYDots: hideDots.y,
68 | hideXDots: hideDots.x
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/tools/downloadURI.ts:
--------------------------------------------------------------------------------
1 | export default function downloadURI (uri: string, name: string): void {
2 | const link = document.createElement('a')
3 | link.download = name
4 | link.href = uri
5 | document.body.appendChild(link)
6 | link.click()
7 | document.body.removeChild(link)
8 | }
9 |
--------------------------------------------------------------------------------
/src/tools/getMode.ts:
--------------------------------------------------------------------------------
1 | import modes from '../constants/modes'
2 | import { Mode } from '../types'
3 |
4 | export default function getMode (data: string): Mode {
5 | switch (true) {
6 | case /^[0-9]*$/.test(data):
7 | return modes.numeric
8 | case /^[0-9A-Z $%*+\-./:]*$/.test(data):
9 | return modes.alphanumeric
10 | default:
11 | return modes.byte
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/tools/merge.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2 | import { UnknownObject } from '../types'
3 |
4 | const isObject = (obj: Record): boolean => !!obj && typeof obj === 'object' && !Array.isArray(obj)
5 |
6 | export default function mergeDeep (target: UnknownObject, ...sources: UnknownObject[]): UnknownObject {
7 | if (!sources.length) return target
8 | const source = sources.shift()
9 | if (source === undefined || !isObject(target) || !isObject(source)) return target
10 | target = { ...target }
11 | Object.keys(source).forEach((key: string): void => {
12 | const targetValue = target[key]
13 | const sourceValue = source[key]
14 |
15 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
16 | target[key] = sourceValue
17 | } else if (isObject(targetValue) && isObject(sourceValue)) {
18 | target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue)
19 | } else {
20 | target[key] = sourceValue
21 | }
22 | })
23 |
24 | return mergeDeep(target, ...sources)
25 | }
26 |
--------------------------------------------------------------------------------
/src/tools/sanitizeOptions.ts:
--------------------------------------------------------------------------------
1 | import { Gradient, RequiredOptions } from '../core/QROptions'
2 |
3 | function sanitizeGradient (gradient: Gradient): Gradient {
4 | const newGradient = { ...gradient }
5 |
6 | if (!newGradient.colorStops || !newGradient.colorStops.length) {
7 | // eslint-disable-next-line no-throw-literal
8 | throw "Field 'colorStops' is required in gradient"
9 | }
10 |
11 | if (newGradient.rotation) {
12 | newGradient.rotation = Number(newGradient.rotation)
13 | } else {
14 | newGradient.rotation = 0
15 | }
16 |
17 | newGradient.colorStops = newGradient.colorStops.map((colorStop: { offset: number; color: string }) => ({
18 | ...colorStop,
19 | offset: Number(colorStop.offset)
20 | }))
21 |
22 | return newGradient
23 | }
24 |
25 | export default function sanitizeOptions (options: RequiredOptions): RequiredOptions {
26 | const newOptions = { ...options }
27 |
28 | newOptions.width = Number(newOptions.width)
29 | newOptions.height = Number(newOptions.height)
30 | newOptions.margin = Number(newOptions.margin)
31 | newOptions.imageOptions = {
32 | ...newOptions.imageOptions,
33 | hideBackgroundDots: Boolean(newOptions.imageOptions.hideBackgroundDots),
34 | imageSize: Number(newOptions.imageOptions.imageSize),
35 | margin: Number(newOptions.imageOptions.margin)
36 | }
37 |
38 | if (newOptions.margin > Math.min(newOptions.width, newOptions.height)) {
39 | newOptions.margin = Math.min(newOptions.width, newOptions.height)
40 | }
41 |
42 | newOptions.dotsOptions = {
43 | ...newOptions.dotsOptions
44 | }
45 | if (newOptions.dotsOptions.gradient) {
46 | newOptions.dotsOptions.gradient = sanitizeGradient(newOptions.dotsOptions.gradient)
47 | }
48 |
49 | if (newOptions.cornersSquareOptions) {
50 | newOptions.cornersSquareOptions = {
51 | ...newOptions.cornersSquareOptions
52 | }
53 | if (newOptions.cornersSquareOptions.gradient) {
54 | newOptions.cornersSquareOptions.gradient = sanitizeGradient(newOptions.cornersSquareOptions.gradient)
55 | }
56 | }
57 |
58 | if (newOptions.cornersDotOptions) {
59 | newOptions.cornersDotOptions = {
60 | ...newOptions.cornersDotOptions
61 | }
62 | if (newOptions.cornersDotOptions.gradient) {
63 | newOptions.cornersDotOptions.gradient = sanitizeGradient(newOptions.cornersDotOptions.gradient)
64 | }
65 | }
66 |
67 | if (newOptions.backgroundOptions) {
68 | newOptions.backgroundOptions = {
69 | ...newOptions.backgroundOptions
70 | }
71 | if (newOptions.backgroundOptions.gradient) {
72 | newOptions.backgroundOptions.gradient = sanitizeGradient(newOptions.backgroundOptions.gradient)
73 | }
74 | }
75 |
76 | return newOptions
77 | }
78 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface UnknownObject {
2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
3 | [key: string]: any;
4 | }
5 |
6 | export type DotType = 'dots' | 'rounded' | 'classy' | 'classy-rounded' | 'square' | 'extra-rounded';
7 | export type CornerDotType = 'dot' | 'square';
8 | export type CornerSquareType = 'dot' | 'square' | 'extra-rounded';
9 | export type Extension = string;
10 | export type GradientType = 'radial' | 'linear';
11 |
12 | export interface DotTypes {
13 | [key: string]: DotType;
14 | }
15 |
16 | export interface GradientTypes {
17 | [key: string]: GradientType;
18 | }
19 |
20 | export interface CornerDotTypes {
21 | [key: string]: CornerDotType;
22 | }
23 |
24 | export interface CornerSquareTypes {
25 | [key: string]: CornerSquareType;
26 | }
27 |
28 | export type TypeNumber =
29 | | 0
30 | | 1
31 | | 2
32 | | 3
33 | | 4
34 | | 5
35 | | 6
36 | | 7
37 | | 8
38 | | 9
39 | | 10
40 | | 11
41 | | 12
42 | | 13
43 | | 14
44 | | 15
45 | | 16
46 | | 17
47 | | 18
48 | | 19
49 | | 20
50 | | 21
51 | | 22
52 | | 23
53 | | 24
54 | | 25
55 | | 26
56 | | 27
57 | | 28
58 | | 29
59 | | 30
60 | | 31
61 | | 32
62 | | 33
63 | | 34
64 | | 35
65 | | 36
66 | | 37
67 | | 38
68 | | 39
69 | | 40;
70 |
71 | export type ErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H';
72 | export type Mode = 'Numeric' | 'Alphanumeric' | 'Byte' | 'Kanji';
73 | export interface QRCode {
74 | addData(data: string, mode?: Mode): void;
75 | make(): void;
76 | getModuleCount(): number;
77 | isDark(row: number, col: number): boolean;
78 | createImgTag(cellSize?: number, margin?: number): string;
79 | createSvgTag(cellSize?: number, margin?: number): string;
80 | createSvgTag(opts?: { cellSize?: number; margin?: number; scalable?: boolean }): string;
81 | createDataURL(cellSize?: number, margin?: number): string;
82 | createTableTag(cellSize?: number, margin?: number): string;
83 | createASCII(cellSize?: number, margin?: number): string;
84 | renderTo2dContext(context: CanvasRenderingContext2D, cellSize?: number): void;
85 | }
86 |
--------------------------------------------------------------------------------
/src/vue3-qr-code-styling.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
![]()
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
148 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./lib/",
4 | "sourceMap": true,
5 | "module": "es6",
6 | "target": "es5",
7 | "allowJs": true,
8 | "moduleResolution": "node",
9 | "declaration": true,
10 | "allowSyntheticDefaultImports": true,
11 | "strict": true,
12 | "noImplicitAny": true,
13 | "noImplicitThis": true,
14 | "alwaysStrict": true,
15 | "strictNullChecks": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "noImplicitReturns": true,
18 | "noUnusedLocals": true,
19 | "esModuleInterop": true
20 | },
21 | "include": ["./src/**/*"]
22 | }
23 |
--------------------------------------------------------------------------------
/webpack.config.build.js:
--------------------------------------------------------------------------------
1 | const commonConfig = require('./webpack.config.common.js');
2 | const config = commonConfig;
3 |
4 | module.exports = (env, argv) => {
5 | config.mode = argv.mode;
6 |
7 | if (argv.mode === "development") {
8 | config.devtool = "inline-source-map";
9 | config.watch = true;
10 | }
11 |
12 | if (argv.mode === "production") {
13 | config.devtool = "source-map";
14 | }
15 |
16 | return config;
17 | };
--------------------------------------------------------------------------------
/webpack.config.common.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const { CleanWebpackPlugin } = require("clean-webpack-plugin");
3 | const { VueLoaderPlugin } = require('vue-loader')
4 |
5 |
6 | const rootPath = path.resolve(__dirname, "./");
7 | const srcPath = path.resolve(rootPath, "src");
8 | const libPath = path.resolve(rootPath, "lib");
9 |
10 | module.exports = {
11 | entry: srcPath + "/index.ts",
12 | output: {
13 | path: libPath,
14 | filename: "index.js",
15 | library: "VQRCodeStyling",
16 | libraryTarget: "umd",
17 | libraryExport: "default"
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.vue$/,
23 | use: 'vue-loader',
24 | exclude: /node_modules/
25 | },
26 | {
27 | test: /\.ts$/,
28 | loader: 'ts-loader',
29 | exclude: /node_modules/,
30 | options: {
31 | appendTsSuffixTo: [/\.vue$/],
32 | }
33 | },
34 | ]
35 | },
36 | plugins: [new VueLoaderPlugin(), new CleanWebpackPlugin()],
37 | resolve: {
38 | extensions: ['.ts', '.js', '.vue'],
39 | alias: {
40 | 'vue': '@vue/runtime-dom'
41 | }
42 | },
43 | };
44 |
--------------------------------------------------------------------------------