kozakdenys / qr-code-styling

Automaticly generate your styled QR code in your web app.
https://qr-code-styling.com
MIT License
1.47k stars 472 forks source link

Is there any way to add text or a border? #116

Open madmacc opened 2 years ago

madmacc commented 2 years ago

Is it possible to add text and a border?

I would like to add the text "Join Now". It is good practice for QR codes to have a call to action and a border.

See attached. Top is the qr-code-styling one from my code and underneath is one I manually generated using an online tool.

Ideally I need the text on the qr-code-styling one. hivepass-t43-qr-code qr-get-hivepass

chiptu commented 2 years ago

I might check to do it on my local and make a pull request in the coming weeks months

aralroca commented 2 years ago

Any news?

madmacc commented 2 years ago

Not sure if this is helpful but I ended up using the canvas to generate a border myself and it works well.

import * as PImage from "pureimage"
import { Bitmap } from 'pureimage/types/bitmap';
import { saveAs } from 'file-saver';

 async generateQrSticker(download: boolean) {
    const element: any = document.querySelector('#qr-sticker');
    const imgFrameWidth = 1500;
    const imgFrameHeight = 1500;
    const imgQrWidth = 1000 * (this.dynamicLink.qrParams ? this.dynamicLink.qrParams.qrSizePercent : 1); 
    const imgQrHeight = 1000 * (this.dynamicLink.qrParams ? this.dynamicLink.qrParams.qrSizePercent : 1);
    const imgQrAdjust = 200;
    const qrX = imgFrameWidth / 2 - (imgQrWidth / 2);
    const qrY = imgFrameHeight / 2 - (imgQrHeight / 2);
    console.log('imgQrWidth', imgQrWidth)
    console.log('imgQrWidth', imgQrWidth)
    const qrCodeFullSize = await this.getFullSizeQRCode();
    const imgQrBlob = await qrCodeFullSize.getRawData('jpeg');
    const imgQrB64 = await blobToBase64(imgQrBlob);
    const borderThickness = 10;
    const borderFromQrDistance = 50;
    const fontSizeSmall = 50;
    const fontSizeMedium = 80;
    const fontSizeLarge = 120;
    const adjTitleFontSize = fontSizeMedium * this.dynamicLink.qrParams.titleTextSizePercent;
    const adSubTitleFontSize = fontSizeSmall * this.dynamicLink.qrParams.subTitleTextSizePercent;
    const adjCtaFontSize = fontSizeLarge * this.dynamicLink.qrParams.ctaTextSizePercent;
    let borderTextAdjust = 0;

    // Colours
    const qrForegroundColour = this.tenantPrivateData.qrCodeStyle.foregroundColor; 
    const qrCornerDotColor = this.tenantPrivateData.qrCodeStyle.cornerDotColor; 
    const qrBackgroundColour = this.tenantPrivateData.qrCodeStyle.backgroundColor; 
    const fgContrast = this.qrCodeService.getContrastScore(qrForegroundColour, '#ffffff');
    const bgContrast = this.qrCodeService.getContrastScore(qrBackgroundColour, '#ffffff');
    const primaryImgColour = fgContrast > bgContrast ? qrForegroundColour : qrBackgroundColour;

    // Background image
    const img1: Bitmap = PImage.make(imgFrameWidth, imgFrameHeight, null);
    // get canvas context
    const ctx = img1.getContext('2d');
    // Background
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, imgFrameWidth, imgFrameHeight);
    ctx.strokeStyle = primaryImgColour;

    if (this.dynamicLink.qrParams.useBorder) {
      console.log('drawing border')
      ctx.lineWidth = borderThickness;
      borderTextAdjust = (borderThickness + borderFromQrDistance) / 2;
      // QR border
      const x1 = (imgFrameWidth - imgQrWidth) / 2 - borderFromQrDistance;
      const y1 = (imgFrameHeight - imgQrHeight) / 2 - borderFromQrDistance;
      const w = imgQrWidth + (borderFromQrDistance * 2);
      const h = imgQrHeight + (borderFromQrDistance * 2);
      console.log('x1', x1)
      console.log('y1', y1)
      console.log('w', w)
      console.log('h', h)
      ctx.strokeRect(x1, y1, w, h);
    } else {
      // We need to adjust where the text is to center it if no border
      borderTextAdjust = 0;
    }

    console.log('borderTextAdjust', borderTextAdjust)

    // Text
    const fontFamily = `'Poppins', sans-serif`;
    ctx.font = `normal 600 ${adjTitleFontSize}px ${fontFamily}`;
    ctx.fillStyle = primaryImgColour;
    ctx.textAlign = "center";
    if (this.dynamicLink.qrParams.subTitleText) {
      ctx.fillText(`${this.dynamicLink.qrParams.titleText}`, imgFrameWidth / 2, 40 + adjTitleFontSize - borderTextAdjust);
      ctx.font = `normal 600 ${adSubTitleFontSize}px ${fontFamily}`;
      ctx.fillText(`${this.dynamicLink.qrParams.subTitleText}`, imgFrameWidth / 2, 140 + adSubTitleFontSize - borderTextAdjust);
    } else {
      ctx.fillText(`${this.dynamicLink.qrParams.titleText}`, imgFrameWidth / 2, 75 + adjTitleFontSize - borderTextAdjust);
    }
    ctx.font = `normal 700 ${adjCtaFontSize}px ${fontFamily}`;
    ctx.fillText(`${this.dynamicLink.qrParams.ctaText}`, imgFrameWidth / 2, 1297 + adjCtaFontSize + borderTextAdjust);

    const imgBgB64 = (img1 as any).toDataURL("image/jpeg");

    const b64 = await mergeImages([
      { 
        src: imgBgB64,
        x: 0,
        y: 0
      }, 
      { 
        src: imgQrB64,
        x: qrX,
        y: qrY
      },
    ]);
    // preview
    element.src = b64

    if (download) {
      // console.log('b64', b64)
      const fileName = `${this.tenant.name}-qr-code-${this.dynamicLink.name}.png`;
      const blobOutput = convertBase64ToFile(b64, fileName);
      saveAs(blobOutput, fileName);
    }
  }
aralroca commented 2 years ago

In my case, I solved it by adding it to the SVG. But it's a very ugly workaround...

Adding this after qrCode.update(optionsToUpdate):

setTimeout(() => {
  ref.current.lastChild.innerHTML += `<g fill="${color}"><text font-size="14" x="0" y="350">${text}</text></g>`;
}, 0);

I had to set a timeout to 0 to wait for the qrCode.update to update the ref.current...

And then instead of qrCode.download I did my implementation through the SVG.

Casal0x commented 1 year ago

do you have some example of the final code?

lyqht commented 1 month ago

If you would like to add a border, my app can do that for you, it uses qr-code-styling under the hood

https://github.com/lyqht/mini-qr