diff --git a/package.json b/package.json index 64be3da..052b115 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,10 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "canvg": "^3.0.7", "intl-pluralrules": "^1.2.2", "iso-639-1": "^2.1.9", - "qr-code-styling": "^1.6.0-rc.0", + "qrcode-generator": "^1.4.4", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.0", "react-dom": "^17.0.2", diff --git a/src/images/Volt_Logo.svg b/src/images/Volt_Logo.svg new file mode 100644 index 0000000..feef070 --- /dev/null +++ b/src/images/Volt_Logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/images/volt-logo-purple.svg b/src/images/volt-logo-purple.svg new file mode 100644 index 0000000..9de6bed --- /dev/null +++ b/src/images/volt-logo-purple.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/images/volt-logo-white.svg b/src/images/volt-logo-white.svg new file mode 100644 index 0000000..eaa0a0c --- /dev/null +++ b/src/images/volt-logo-white.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/locales/de.ftl b/src/locales/de.ftl index 31371f9..84c7f28 100644 --- a/src/locales/de.ftl +++ b/src/locales/de.ftl @@ -8,4 +8,18 @@ source_code = Quellcode text_content_input_placeholder = https:// … oder Text filetype_headline = Als Vektor (VSG) oder Pixel (PNG) laden +size_headline = Größe der Pixel Graphiken + +background_color_headline = Hintergrund Farbe +foreground_color_headline = Fordergrund Farbe +purple = Lila +white = Weiß +black = Schwarz +yellow = Geld +green = Grün +blue = Blau +red = Rot + +display_logo_headline = Logo Wassermarke + error_correction_level_headline = Fehlerresistenz diff --git a/src/locales/en.ftl b/src/locales/en.ftl index e451a92..de835fc 100644 --- a/src/locales/en.ftl +++ b/src/locales/en.ftl @@ -8,5 +8,19 @@ source_code = Source Code content_placeholder = https:// … or Text filetype_headline = Choose between Vector (VSG) or Pixel (PNG) Image. +size_headline = Size of the final pixel graphics + +background_color_headline = Background Color +foreground_color_headline = Foreground Color +purple = Purple +white = White +black = Black +yellow = Yellow +green = Green +blue = Blue +red = Red + +display_logo_headline = Display Logo + error_correction_level_headline = Error Resistance diff --git a/src/pages/Generator.js b/src/pages/Generator.js index d23a672..d7bb873 100644 --- a/src/pages/Generator.js +++ b/src/pages/Generator.js @@ -1,68 +1,65 @@ -import React, { useState, useCallback, useEffect } from 'react' +import React, { useState, useCallback, useEffect, useRef } from 'react' import classes from './Generator.module.css' import { withLocalization, Localized } from '../fluent/Localized.js' import MultiButton from '../components/MultiButton.js' -import QRCodeStyling from 'qr-code-styling' +import qrcode_generator from 'qrcode-generator' +import Canvg from 'canvg' -const size = 1000 - -const qrCode = new QRCodeStyling({ - experimental: false, - width: size, - height: size, - type: 'svg', - image: '', // 'http://localhost:3000/volt-logo-white-192.png', // 'https://volt.link/volt-logo-white-192.png', - dotsOptions: { - color: '#502379', - type: 'square' - }, - backgroundOptions: { - color: '#ffffff', - }, - margin: 50, - qrOptions: { - errorCorrectionLevel: 'M', - }, -}) - -console.log('qrCode', qrCode) - -// function trigger_download(name, data) { -// const a = document.createElement('a') -// document.body.appendChild(a) -// // a.target = '_blank' -// a.download = name -// a.href = data -// a.click() -// a.remove() -// } +function trigger_download(name, data) { + const a = document.createElement('a') + document.body.appendChild(a) + // a.target = '_blank' + a.download = name + a.href = data + a.click() + a.remove() +} // function getQrcodeBounds(svgText){ // const matches = [...svgText.matchAll(/ match[3])) - +// // const x_all = matches.map(match => match[1]) // const x_min = Math.min(...x_all) // const x_max = Math.max(...x_all) + dot_size - x_min - +// // const y_all = matches.map(match => match[2]) // const y_min = Math.min(...y_all) // const y_max = Math.max(...y_all) + dot_size - y_min - +// // return { x_min, x_max, y_min, y_max, dot_size } // } +const colors = { + 'black': '#000000', + 'white': '#ffffff', + 'purple': '#502379', + 'yellow': '#FDC220', + 'green': '#1BBE6F', + 'blue': '#82D0F4', + 'red': '#E63E12', +} + function Generator({ getString }) { + const conversion_canvas_ref = useRef(null) + + const [size, setSize] = useState('1000') + const [realSize, setRealSize] = useState(null) + const [errorCorrectionLevel, setErrorCorrectionLevel] = useState('M') const [content, setContent] = useState('') const [qrcode, setQrcode] = useState(null) + const [backgroundColor, setBackgroundColor] = useState('white') + const [foregroundColor, setForegroundColor] = useState('purple') + const [displayLogo, setDisplayLogo] = useState('yes') + const handleContentChange = useCallback(event => { const value = event.target.value - if (value.length > 0 && value.length < 1000) { // todo: find out the actual value length limit + if (value.length > 0 && value.length < 1200) { // 1273 is the maximum for an ECL of H. 2953 for L. setContent(value) } else { setContent('') @@ -70,68 +67,219 @@ function Generator({ getString }) { }, [setContent]) useEffect(() => { - qrCode.update({ - data: content, - qrOptions: { - errorCorrectionLevel - }, - }) - if (content === '') { setQrcode(null) } else { - qrCode.getRawData('svg') - .then(async data => { - let svg = await data.text() + // generate qr code + const size_int = parseInt(size) - // const { x_min, x_max, y_min, y_max } = getQrcodeBounds(svg) + const qr = qrcode_generator(0, errorCorrectionLevel) + qr.addData(content) + qr.make() - svg = svg.replace('', ` + + + + + + + + + `) + } + + // Colors + let this_backgroundColor = backgroundColor + let this_foregroundColor = foregroundColor + + if ( + this_foregroundColor === this_backgroundColor + ) { + if (this_backgroundColor === 'white') { + this_foregroundColor = 'purple' + } else { + this_backgroundColor = 'white' + } + } + + if (this_foregroundColor !== 'white') { + this_backgroundColor = 'white' + } + + let this_backgroundColor_logo = 'white' + let this_foregroundColor_logo = 'purple' + + if (this_backgroundColor === 'purple' && this_foregroundColor === 'white') { + this_backgroundColor_logo = 'purple' + this_foregroundColor_logo = 'white' + } + + let backgroundColor_hex = colors[this_backgroundColor] || 'white' + let foregroundColor_hex = colors[this_foregroundColor] || 'black' + let backgroundColor_logo_hex = colors[this_backgroundColor_logo] || 'white' + let foregroundColor_logo_hex = colors[this_foregroundColor_logo] || 'black' + + svg = svg + .replace(/fill="black"/g, `fill="${foregroundColor_hex}"`) + .replace(/fill="white"/g, `fill="${backgroundColor_hex}"`) + .replace(/fill="logo_bg"/g, `fill="${backgroundColor_logo_hex}"`) + .replace(/fill="logo_fg"/g, `fill="${foregroundColor_logo_hex}"`) + + + // use the qrcode + setQrcode(svg) } - }, [content, errorCorrectionLevel, setQrcode]) - - // const handleDownload = useCallback(() => { - // if (filetype === 'svg') { - // const b64 = 'data:image/svg+xml;base64,' + btoa(qrcode) - // trigger_download('volt-qrcode.svg', b64) - // } else { - // const b64 = 'data:image/png;base64,' - // trigger_download('volt-qrcode.png', b64) - // } - // // window.umami.trackEvent('F: ' + filetype) - // }, [ - // filetype, - // qrcode - // ]) + }, [content, size, errorCorrectionLevel, backgroundColor, foregroundColor, displayLogo, setRealSize, setQrcode]) const handleDownload_svg = () => { - qrCode.download({ name: 'volt-qrcode', extension: 'svg' }) + const b64 = 'data:image/svg+xml;base64,' + btoa(qrcode) + trigger_download('volt-qrcode.svg', b64) window.umami.trackEvent('E: svg') } - const handleDownload_jpeg = () => { - qrCode.download({ name: 'volt-qrcode', extension: 'jpeg' }) + const handleDownload_jpeg = async () => { + var canvas = conversion_canvas_ref.current + const ctx = canvas.getContext('2d') + + const v = Canvg.fromString(ctx, qrcode) + v.resize(realSize, realSize) + await v.render() + + var b64 = canvas.toDataURL('image/jpeg') + + trigger_download('volt-qrcode.jpeg', b64) window.umami.trackEvent('E: jpeg') } - const handleDownload_png = () => { - qrCode.download({ name: 'volt-qrcode', extension: 'png' }) + const handleDownload_png = async () => { + var canvas = conversion_canvas_ref.current + const ctx = canvas.getContext('2d') + + const v = Canvg.fromString(ctx, qrcode) + v.resize(realSize, realSize) + await v.render() + + var b64 = canvas.toDataURL('image/png') + + trigger_download('volt-qrcode.png', b64) window.umami.trackEvent('E: png') } - // const handleDownload_webp = () => { - // qrCode.download({ name: 'volt-qrcode', extension: 'webp' }) - // window.umami.trackEvent('E: webp') - // } - return
-

- + + let background_colors = ['purple', 'white', 'yellow', 'green', 'blue', 'red', 'black'] + if (foregroundColor !== 'white') { + background_colors = ['white'] + } else if (foregroundColor === 'white') { + background_colors = ['purple', 'yellow', 'green', 'blue', 'red', 'black'] + } + background_colors = background_colors.map(color => ({ + value: color, + title: getString(color) + })) + + + const options = <> +

+ +

+ +
+ + { + background_colors.length <= 1 + ? null + : <> +

+ +

+ +
+ + } + +

+ +

+ +

@@ -160,11 +308,58 @@ function Generator({ getString }) { ]} />
+ +

+ +

+
+
+ + + return
+

+
+ +

Enter a URL or other text.

+ + + { + content.length > 0 + ? options + : null + } { qrcode === null - ?

Enter content for the QR-Code to download it.

+ ? null : <> @@ -172,9 +367,10 @@ function Generator({ getString }) { - {/* */} } + +
} diff --git a/yarn.lock b/yarn.lock index 809caca..e6c0d89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1142,7 +1142,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-transform-typescript" "^7.12.1" -"@babel/runtime-corejs3@^7.10.2": +"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.9.6": version "7.14.7" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz#0ef292bbce40ca00f874c9724ef175a12476465c" integrity sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA== @@ -1952,6 +1952,11 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== +"@types/raf@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2" + integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw== + "@types/react-redux@^7.1.16": version "7.1.16" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21" @@ -3298,6 +3303,18 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, can resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001241.tgz#cd3fae47eb3d7691692b406568d7a3e5b23c7598" integrity sha512-1uoSZ1Pq1VpH0WerIMqwptXHNNGfdl7d1cJUFs80CwQ/lVzdhTvsFZCeNFslze7AjsQnb4C85tzclPa1VShbeQ== +canvg@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/canvg/-/canvg-3.0.7.tgz#e45b87a64116af906917f7cad57d370ea372d682" + integrity sha512-4sq6iL5Q4VOXS3PL1BapiXIZItpxYyANVzsAKpTPS5oq4u3SKbGfUcbZh2gdLCQ3jWpG/y5wRkMlBBAJhXeiZA== + dependencies: + "@babel/runtime-corejs3" "^7.9.6" + "@types/raf" "^3.4.0" + raf "^3.4.1" + rgbcolor "^1.0.1" + stackblur-canvas "^2.0.0" + svg-pathdata "^5.0.5" + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -9225,14 +9242,7 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qr-code-styling@^1.6.0-rc.0: - version "1.6.0-rc.0" - resolved "https://registry.yarnpkg.com/qr-code-styling/-/qr-code-styling-1.6.0-rc.0.tgz#e9ddace6675b519731bc6b24c3e56658672d1507" - integrity sha512-Q0WKtSgL3WHzc4SGS/7IVBvlkSlkSbK+guxnH87mRPsxijTP5yJN5KzN0Qw/Bx9W9atZawa+q2IyZVyYgbwtFA== - dependencies: - qrcode-generator "^1.4.3" - -qrcode-generator@^1.4.3: +qrcode-generator@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7" integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw== @@ -9882,6 +9892,11 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d" + integrity sha1-1lBezbMEplldom+ktDMHMGd1lF0= + rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -10497,6 +10512,11 @@ stack-utils@^2.0.2: dependencies: escape-string-regexp "^2.0.0" +stackblur-canvas@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz#aa87bbed1560fdcd3138fff344fc6a1c413ebac4" + integrity sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ== + stackframe@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" @@ -10748,6 +10768,11 @@ svg-parser@^2.0.2: resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== +svg-pathdata@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/svg-pathdata/-/svg-pathdata-5.0.5.tgz#65e8d765642ba15fe15434444087d082bc526b29" + integrity sha512-TAAvLNSE3fEhyl/Da19JWfMAdhSXTYeviXsLSoDT1UM76ADj5ndwAPX1FKQEgB/gFMPavOy6tOqfalXKUiXrow== + svgo@^1.0.0, svgo@^1.2.2: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"