112 lines
3.3 KiB
Go
112 lines
3.3 KiB
Go
// Copyright 2020 Google LLC
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// https://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
package qrbill
|
||
|
||
import (
|
||
"bytes"
|
||
"image"
|
||
"image/draw"
|
||
|
||
"github.com/makiuchi-d/gozxing"
|
||
"github.com/makiuchi-d/gozxing/qrcode"
|
||
"github.com/makiuchi-d/gozxing/qrcode/decoder"
|
||
)
|
||
|
||
// This is a port of the Java 1.7 reference example from paymentstandards.ch:
|
||
// https://www.paymentstandards.ch/dam/downloads/qrcodegenerator.java
|
||
//
|
||
// The priority was to write idiomatic Go code first, and match the reference
|
||
// example as good as possible second.
|
||
|
||
const (
|
||
swissCrossEdgeSidePx = 166
|
||
|
||
swissCrossEdgeSideMm = 7
|
||
|
||
// The edge length of the qrcode inclusive its white border.
|
||
qrCodeEdgeSideMm = 42 + 13
|
||
|
||
qrCodeEdgeSidePx = swissCrossEdgeSidePx / swissCrossEdgeSideMm * qrCodeEdgeSideMm
|
||
)
|
||
|
||
func generateSwissQrCode(payload string) (image.Image, error) {
|
||
// generate the qr code from the payload
|
||
qrCodeImage, err := generateQrCodeImage(payload)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// overlay the qr code with a Swiss Cross
|
||
return overlayWithSwissCross(qrCodeImage)
|
||
}
|
||
|
||
func qrEncodeHints() map[gozxing.EncodeHintType]interface{} {
|
||
return map[gozxing.EncodeHintType]interface{}{
|
||
// as per https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf, section 5.1:
|
||
// Error correction level M (redundancy of around 15%)
|
||
gozxing.EncodeHintType_ERROR_CORRECTION: decoder.ErrorCorrectionLevel_M,
|
||
|
||
// Section 4.2.1: Character set:
|
||
// UTF-8 should be used for encoding
|
||
gozxing.EncodeHintType_CHARACTER_SET: "UTF-8",
|
||
}
|
||
}
|
||
|
||
func generateQrCodeImage(payload string) (image.Image, error) {
|
||
matrix, err := qrcode.NewQRCodeWriter().Encode(
|
||
payload, // contents
|
||
gozxing.BarcodeFormat_QR_CODE, // format
|
||
qrCodeEdgeSidePx, // width
|
||
qrCodeEdgeSidePx, // height
|
||
qrEncodeHints()) // hints
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return matrix, nil
|
||
}
|
||
|
||
func overlayWithSwissCross(qrCodeImage image.Image) (image.Image, error) {
|
||
b := swisscross["third_party/swiss-cross/CH-Kreuz_7mm/CH-Kreuz_7mm.png"]
|
||
swissCrossImage, _, err := image.Decode(bytes.NewReader(b))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
combinedQrCodeImage := image.NewRGBA(qrCodeImage.Bounds())
|
||
|
||
{
|
||
sr := qrCodeImage.Bounds() // source rect
|
||
destRect := image.Rectangle{image.Point{0, 0}, sr.Size()}
|
||
draw.Draw(combinedQrCodeImage, destRect, qrCodeImage, sr.Min, draw.Src)
|
||
}
|
||
|
||
{
|
||
sr := swissCrossImage.Bounds() // source rect
|
||
const swissCrossPosition = (qrCodeEdgeSidePx / 2) - (swissCrossEdgeSidePx / 2)
|
||
destPoint := image.Point{
|
||
X: swissCrossPosition,
|
||
Y: swissCrossPosition,
|
||
}
|
||
// Convert the source image bounds into the destination image’s coordinate
|
||
// space:
|
||
destRect := image.Rectangle{
|
||
destPoint,
|
||
destPoint.Add(sr.Size()),
|
||
}
|
||
draw.Draw(combinedQrCodeImage, destRect, swissCrossImage, sr.Min, draw.Src)
|
||
}
|
||
return combinedQrCodeImage, nil
|
||
}
|