parent
5d2cbee989
commit
4ea94c451d
10 changed files with 495 additions and 28 deletions
31
README.md
31
README.md
|
@ -101,6 +101,7 @@ Build the extended edition:
|
|||
```text
|
||||
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
|
||||
```
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#gohugoio/hugo&Timeline)
|
||||
|
@ -154,7 +155,7 @@ github.com/bep/clocks="v0.5.0"
|
|||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.6.0"
|
||||
github.com/bep/goat="v0.5.0"
|
||||
github.com/bep/godartsass/v2="v2.3.0"
|
||||
github.com/bep/godartsass/v2="v2.3.2"
|
||||
github.com/bep/golibsass="v1.2.0"
|
||||
github.com/bep/gowebp="v0.3.0"
|
||||
github.com/bep/imagemeta="v0.8.3"
|
||||
|
@ -189,7 +190,7 @@ github.com/gohugoio/locales="v0.14.0"
|
|||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/google/go-cmp="v0.6.0"
|
||||
github.com/gorilla/websocket="v1.5.3"
|
||||
github.com/hairyhenderson/go-codeowners="v0.6.1"
|
||||
github.com/hairyhenderson/go-codeowners="v0.7.0"
|
||||
github.com/hashicorp/golang-lru/v2="v2.0.7"
|
||||
github.com/invopop/yaml="v0.2.0"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
|
@ -218,30 +219,32 @@ github.com/russross/blackfriday/v2="v2.1.0"
|
|||
github.com/sass/dart-sass/compiler="1.81.0"
|
||||
github.com/sass/dart-sass/implementation="1.81.0"
|
||||
github.com/sass/dart-sass/protocol="3.1.0"
|
||||
github.com/sass/libsass="3.6.6"
|
||||
github.com/spf13/afero="v1.11.0"
|
||||
github.com/spf13/cast="v1.7.0"
|
||||
github.com/spf13/cast="v1.7.1"
|
||||
github.com/spf13/cobra="v1.8.1"
|
||||
github.com/spf13/fsync="v0.10.1"
|
||||
github.com/spf13/pflag="v1.0.5"
|
||||
github.com/tdewolff/minify/v2="v2.21.1"
|
||||
github.com/tdewolff/parse/v2="v2.7.18"
|
||||
github.com/tetratelabs/wazero="v1.8.1"
|
||||
github.com/tdewolff/minify/v2="v2.20.37"
|
||||
github.com/tdewolff/parse/v2="v2.7.15"
|
||||
github.com/tetratelabs/wazero="v1.8.2"
|
||||
github.com/webmproject/libwebp="v1.3.2"
|
||||
github.com/yuin/goldmark-emoji="v1.0.4"
|
||||
github.com/yuin/goldmark="v1.7.8"
|
||||
go.uber.org/automaxprocs="v1.5.3"
|
||||
golang.org/x/crypto="v0.29.0"
|
||||
golang.org/x/crypto="v0.31.0"
|
||||
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
|
||||
golang.org/x/image="v0.22.0"
|
||||
golang.org/x/mod="v0.22.0"
|
||||
golang.org/x/net="v0.31.0"
|
||||
golang.org/x/sync="v0.9.0"
|
||||
golang.org/x/sys="v0.27.0"
|
||||
golang.org/x/text="v0.20.0"
|
||||
golang.org/x/tools="v0.27.0"
|
||||
google.golang.org/protobuf="v1.35.1"
|
||||
golang.org/x/net="v0.33.0"
|
||||
golang.org/x/sync="v0.10.0"
|
||||
golang.org/x/sys="v0.28.0"
|
||||
golang.org/x/text="v0.21.0"
|
||||
golang.org/x/tools="v0.28.0"
|
||||
google.golang.org/protobuf="v1.35.2"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
gopkg.in/yaml.v3="v3.0.1"
|
||||
howett.net/plist="v1.0.0"
|
||||
rsc.io/qr="v0.2.0"
|
||||
software.sslmate.com/src/go-pkcs12="v0.2.0"
|
||||
```
|
||||
</details>
|
||||
|
|
|
@ -121,7 +121,7 @@ Hugo renders this to:
|
|||
</details>
|
||||
```
|
||||
|
||||
The details shortcode accepts these named arguments:
|
||||
The `details` shortcode accepts these named arguments:
|
||||
|
||||
summary
|
||||
: (`string`) The content of the child `summary` element rendered from Markdown to HTML. Default is `Details`.
|
||||
|
@ -333,6 +333,106 @@ Access nested values by [chaining] the [identifiers]:
|
|||
{{</* param my.nested.param */>}}
|
||||
```
|
||||
|
||||
### qr
|
||||
|
||||
{{% note %}}
|
||||
To override Hugo's embedded `qr` shortcode, copy the [source code] to a file with the same name in the layouts/shortcodes directory.
|
||||
|
||||
[source code]: {{% eturl qr %}}
|
||||
{{% /note %}}
|
||||
|
||||
The `qr` shortcode encodes the given text into a [QR code] using the specified options and renders the resulting image.
|
||||
|
||||
[QR code]: https://en.wikipedia.org/wiki/QR_code
|
||||
|
||||
Use the self-closing syntax to pass the text as an argument:
|
||||
|
||||
```text
|
||||
{{</* qr text="https://gohugo.io" /*/>}}
|
||||
```
|
||||
|
||||
Or insert the text between the opening and closing tags:
|
||||
|
||||
```text
|
||||
{{</* qr */>}}
|
||||
https://gohugo.io
|
||||
{{</* /qr */>}}
|
||||
```
|
||||
|
||||
Both of the above produce this image:
|
||||
|
||||
{{< qr text="https://gohugo.io" class="qrcode" />}}
|
||||
|
||||
To create a QR code for a phone number:
|
||||
|
||||
```text
|
||||
{{</* qr text="tel:+12065550101" /*/>}}
|
||||
```
|
||||
|
||||
{{< qr text="tel:+12065550101" class="qrcode" />}}
|
||||
|
||||
To create a QR code containing contact information in the [vCard] format:
|
||||
|
||||
[vCard]: https://en.wikipedia.org/wiki/VCard
|
||||
|
||||
```text
|
||||
{{</* qr level="low" scale=2 alt="QR code of vCard for John Smith" */>}}
|
||||
BEGIN:VCARD
|
||||
VERSION:2.1
|
||||
N;CHARSET=UTF-8:Smith;John;R.;Dr.;PhD
|
||||
FN;CHARSET=UTF-8:Dr. John R. Smith, PhD.
|
||||
ORG;CHARSET=UTF-8:ABC Widgets
|
||||
TITLE;CHARSET=UTF-8:Vice President Engineering
|
||||
TEL;TYPE=WORK:+12065550101
|
||||
EMAIL;TYPE=WORK:jsmith@example.org
|
||||
END:VCARD
|
||||
{{</* /qr */>}}
|
||||
```
|
||||
|
||||
{{< qr level="low" scale=2 alt="QR code of vCard for John Smith" class="qrcode" >}}
|
||||
BEGIN:VCARD
|
||||
VERSION:2.1
|
||||
N;CHARSET=UTF-8:Smith;John;R.;Dr.;PhD
|
||||
FN;CHARSET=UTF-8:Dr. John R. Smith, PhD.
|
||||
ORG;CHARSET=UTF-8:ABC Widgets
|
||||
TITLE;CHARSET=UTF-8:Vice President Engineering
|
||||
TEL;TYPE=WORK:+12065550101
|
||||
EMAIL;TYPE=WORK:jsmith@example.org
|
||||
END:VCARD
|
||||
{{< /qr >}}
|
||||
|
||||
Internally this shortcode calls the `images.QR` function. Please read the [related documentation] for implementation details and guidance.
|
||||
|
||||
[related documentation]: /functions/images/qr/
|
||||
|
||||
The `qr` shortcode accepts these named arguments:
|
||||
|
||||
text
|
||||
: (`string`) The text to encode, falling back to the text between the opening and closing shortcode tags.
|
||||
|
||||
level
|
||||
: (`string`) The error correction level to use when encoding the text, one of `low`, `medium`, `quartile`, or `high`. Default is `medium`.
|
||||
|
||||
scale
|
||||
: (`int`) The number of image pixels per QR code module. Must be greater than or equal to 2. Default is `4`.
|
||||
|
||||
targetDir
|
||||
: (`string`) The subdirectory within the [`publishDir`] where Hugo will place the generated image.
|
||||
|
||||
[`publishDir`]: /getting-started/configuration/#publishdir
|
||||
|
||||
alt
|
||||
: (`string`) The `alt` attribute of the `img` element.
|
||||
|
||||
class
|
||||
: (`string`) The `class` attribute of the `img` element.
|
||||
|
||||
id
|
||||
: (`string`) The `id` attribute of the `img` element.
|
||||
|
||||
title
|
||||
: (`string`) The `title` attribute of the `img` element.
|
||||
|
||||
### ref
|
||||
|
||||
{{% note %}}
|
||||
|
|
115
docs/content/en/functions/images/QR.md
Normal file
115
docs/content/en/functions/images/QR.md
Normal file
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: images.QR
|
||||
description: Encodes the given text into a QR code using the specified options, returning an image resource.
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related: []
|
||||
returnType: images.ImageResource
|
||||
signatures: ['images.QR OPTIONS']
|
||||
toc: true
|
||||
math: true
|
||||
---
|
||||
|
||||
{{< new-in 0.141.0 >}}
|
||||
|
||||
The `images.QR` function encodes the given text into a [QR code] using the specified options, returning an image resource. The size of the generated image depends on three factors:
|
||||
|
||||
- Data length: Longer text necessitates a larger image to accommodate the increased information density.
|
||||
- Error correction level: Higher error correction levels enhance the QR code's resistance to damage, but this typically results in a slightly larger image size to maintain readability.
|
||||
- Pixels per module: The number of image pixels assigned to each individual module (the smallest unit of the QR code) directly impacts the overall image size. A higher pixel count per module leads to a larger, higher-resolution image.
|
||||
|
||||
Although the default option values are sufficient for most applications, you should test the rendered QR code both on-screen and in print.
|
||||
|
||||
[QR code]: https://en.wikipedia.org/wiki/QR_code
|
||||
|
||||
## Options
|
||||
|
||||
text
|
||||
: (`string`) The text to encode.
|
||||
|
||||
level
|
||||
: (`string`) The error correction level to use when encoding the text, one of `low`, `medium`, `quartile`, or `high`. Default is `medium`.
|
||||
|
||||
Error correction level|Redundancy
|
||||
:--|:--|:--
|
||||
low|20%
|
||||
medium|38%
|
||||
quartile|55%
|
||||
high|65%
|
||||
|
||||
scale
|
||||
: (`int`) The number of image pixels per QR code module. Must be greater than or equal to `2`. Default is `4`.
|
||||
|
||||
targetDir
|
||||
: (`string`) The subdirectory within the [`publishDir`] where Hugo will place the generated image. Use Unix-style slashes (`/`) to separarate path segments. If empty or not provided, the image is placed directly in the `publishDir` root. Hugo automatically creates the necessary subdirectories if they don't exist.
|
||||
|
||||
[`publishDir`]: /getting-started/configuration/#publishdir
|
||||
|
||||
## Examples
|
||||
|
||||
To create a QR code using the default values for `level` and `scale`:
|
||||
|
||||
```go-html-template
|
||||
{{ $opts := dict "text" "https://gohugo.io" }}
|
||||
{{ with images.QR $opts }}
|
||||
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
{{< qr text="https://gohugo.io" class="qrcode" />}}
|
||||
|
||||
Specify `level`, `scale`, and `targetDir` as needed to achieve the desired result:
|
||||
|
||||
```go-html-template
|
||||
{{ $opts := dict
|
||||
"text" "https://gohugo.io"
|
||||
"level" "high"
|
||||
"scale" 3
|
||||
"targetDir" "codes"
|
||||
}}
|
||||
{{ with images.QR $opts }}
|
||||
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
{{< qr text="https://gohugo.io" level="high" scale=3 targetDir="codes" class="qrcode" />}}
|
||||
|
||||
## Scale
|
||||
|
||||
As you decrease the size of a QR code, the maximum distance at which it can be reliably scanned by a device also decreases.
|
||||
|
||||
In the example above, we set the `scale` to `2`, resulting in a QR code where each module consists of 2x2 pixels. While this might be sufficient for on-screen display, it's likely to be problematic when printed at 600 dpi.
|
||||
|
||||
\[ \frac{2\:px}{module} \times \frac{1\:inch}{600\:px} \times \frac{25.4\:mm}{1\:inch} = \frac{0.085\:mm}{module} \]
|
||||
|
||||
This module size is half of the commonly recommended minimum of 0.170 mm.\
|
||||
If the QR code will be printed, use the default `scale` value of `4` pixels per module.
|
||||
|
||||
Avoid using Hugo's image processing methods to resize QR codes. Resizing can introduce blurring due to anti-aliasing when a QR code module occupies a fractional number of pixels.
|
||||
|
||||
{{% note %}}
|
||||
Always test the rendered QR code both on-screen and in print.
|
||||
{{% /note %}}
|
||||
|
||||
## Shortcode
|
||||
|
||||
Call the `qr` shortcode to insert a QR code into your content.
|
||||
|
||||
Use the self-closing syntax to pass the text as an argument:
|
||||
|
||||
```text
|
||||
{{</* qr text="https://gohugo.io" /*/>}}
|
||||
```
|
||||
|
||||
Or insert the text between the opening and closing tags:
|
||||
|
||||
```text
|
||||
{{</* qr */>}}
|
||||
https://gohugo.io
|
||||
{{</* /qr */>}}
|
||||
```
|
||||
|
||||
The `qr` shortcode accepts several arguments including `level` and `scale`. See the [related documentation] for details.
|
||||
|
||||
[related documentation]: /content-management/shortcodes/#qr
|
|
@ -10,18 +10,18 @@
|
|||
'google_analytics' = 'google_analytics.html'
|
||||
'opengraph' = 'opengraph.html'
|
||||
'pagination' = 'pagination.html'
|
||||
'schema' = 'schema.html'
|
||||
'twitter_cards' = 'twitter_cards.html'
|
||||
|
||||
'robots' = '_default/robots.txt'
|
||||
'rss' = '_default/rss.xml'
|
||||
'schema' = 'schema.html'
|
||||
'sitemap' = '_default/sitemap.xml'
|
||||
'sitemapindex' = '_default/sitemapindex.xml'
|
||||
'twitter_cards' = 'twitter_cards.html'
|
||||
|
||||
# Render hooks
|
||||
'render-codeblock-goat' = '_default/_markup/render-codeblock-goat.html'
|
||||
'render-image' = '_default/_markup/render-image.html'
|
||||
'render-link' = '_default/_markup/render-link.html'
|
||||
'render-codeblock-goat' = '_default/_markup/render-codeblock-goat.html'
|
||||
'render-table' = '_default/_markup/render-table.html'
|
||||
|
||||
# Shortcodes
|
||||
'comment' = 'shortcodes/comment.html'
|
||||
|
@ -31,6 +31,7 @@
|
|||
'highlight' = 'shortcodes/highlight.html'
|
||||
'instagram' = 'shortcodes/instagram.html'
|
||||
'param' = 'shortcodes/param.html'
|
||||
'qr' = 'shortcodes/qr.html'
|
||||
'ref' = 'shortcodes/ref.html'
|
||||
'relref' = 'shortcodes/relref.html'
|
||||
'twitter' = 'shortcodes/twitter.html'
|
||||
|
|
1
go.mod
1
go.mod
|
@ -165,6 +165,7 @@ require (
|
|||
google.golang.org/protobuf v1.35.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
|
||||
)
|
||||
|
||||
|
|
2
go.sum
2
go.sum
|
@ -878,6 +878,8 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE=
|
||||
|
|
|
@ -16,11 +16,18 @@ package images
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/bep/overlayfs"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/resources/images"
|
||||
"github.com/gohugoio/hugo/resources/resource_factories/create"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"rsc.io/qr"
|
||||
|
||||
// Importing image codecs for image.DecodeConfig
|
||||
_ "image/gif"
|
||||
|
@ -50,21 +57,22 @@ func New(d *deps.Deps) *Namespace {
|
|||
}
|
||||
|
||||
return &Namespace{
|
||||
readFileFs: readFileFs,
|
||||
Filters: &images.Filters{},
|
||||
cache: map[string]image.Config{},
|
||||
deps: d,
|
||||
readFileFs: readFileFs,
|
||||
Filters: &images.Filters{},
|
||||
cache: map[string]image.Config{},
|
||||
deps: d,
|
||||
createClient: create.New(d.ResourceSpec),
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace provides template functions for the "images" namespace.
|
||||
type Namespace struct {
|
||||
*images.Filters
|
||||
readFileFs afero.Fs
|
||||
cacheMu sync.RWMutex
|
||||
cache map[string]image.Config
|
||||
|
||||
deps *deps.Deps
|
||||
readFileFs afero.Fs
|
||||
cacheMu sync.RWMutex
|
||||
cache map[string]image.Config
|
||||
deps *deps.Deps
|
||||
createClient *create.Client
|
||||
}
|
||||
|
||||
// Config returns the image.Config for the specified path relative to the
|
||||
|
@ -117,3 +125,77 @@ func (ns *Namespace) Filter(args ...any) (images.ImageResource, error) {
|
|||
|
||||
return img.Filter(filtersv...)
|
||||
}
|
||||
|
||||
var qrErrorCorrectionLevels = map[string]qr.Level{
|
||||
"low": qr.L,
|
||||
"medium": qr.M,
|
||||
"quartile": qr.Q,
|
||||
"high": qr.H,
|
||||
}
|
||||
|
||||
// QR encodes the given text into a QR code using the specified options,
|
||||
// returning an image resource.
|
||||
func (ns *Namespace) QR(options any) (images.ImageResource, error) {
|
||||
const (
|
||||
qrDefaultErrorCorrectionLevel = "medium"
|
||||
qrDefaultScale = 4
|
||||
)
|
||||
|
||||
opts := struct {
|
||||
Text string // text to encode
|
||||
Level string // error correction level; one of low, medium, quartile, or high
|
||||
Scale int // number of image pixels per QR code module
|
||||
TargetDir string // target directory relative to publishDir
|
||||
}{
|
||||
Level: qrDefaultErrorCorrectionLevel,
|
||||
Scale: qrDefaultScale,
|
||||
}
|
||||
|
||||
err := mapstructure.WeakDecode(options, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.Text == "" {
|
||||
return nil, errors.New("cannot encode an empty string")
|
||||
}
|
||||
|
||||
level, ok := qrErrorCorrectionLevels[opts.Level]
|
||||
if !ok {
|
||||
return nil, errors.New("error correction level must be one of low, medium, quartile, or high")
|
||||
}
|
||||
|
||||
if opts.Scale < 2 {
|
||||
return nil, errors.New("scale must be an integer greater than or equal to 2")
|
||||
}
|
||||
|
||||
targetPath := path.Join(opts.TargetDir, fmt.Sprintf("qr_%s.png", hashing.HashString(opts)))
|
||||
|
||||
r, err := ns.createClient.FromOpts(
|
||||
create.Options{
|
||||
TargetPath: targetPath,
|
||||
TargetPathHasHash: true,
|
||||
CreateContent: func() (func() (hugio.ReadSeekCloser, error), error) {
|
||||
code, err := qr.Encode(opts.Text, level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code.Scale = opts.Scale
|
||||
png := code.PNG()
|
||||
return func() (hugio.ReadSeekCloser, error) {
|
||||
return hugio.NewReadSeekerNoOpCloserFromBytes(png), nil
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ir, ok := r.(images.ImageResource)
|
||||
if !ok {
|
||||
panic("bug: resource is not an image resource")
|
||||
}
|
||||
|
||||
return ir, nil
|
||||
}
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
package images_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
|
@ -49,3 +51,52 @@ fileExists2 OK: true|
|
|||
imageConfig2 OK: 1|
|
||||
`)
|
||||
}
|
||||
|
||||
func TestQR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
-- layouts/index.html --
|
||||
{{- $text := "https://gohugo.io" }}
|
||||
{{- $optionMaps := slice
|
||||
(dict "text" $text)
|
||||
(dict "text" $text "level" "medium")
|
||||
(dict "text" $text "level" "medium" "scale" 4)
|
||||
(dict "text" $text "level" "low" "scale" 2)
|
||||
(dict "text" $text "level" "medium" "scale" 3)
|
||||
(dict "text" $text "level" "quartile" "scale" 5)
|
||||
(dict "text" $text "level" "high" "scale" 6)
|
||||
(dict "text" $text "level" "high" "scale" 6 "targetDir" "foo/bar")
|
||||
}}
|
||||
{{- range $k, $opts := $optionMaps }}
|
||||
{{- with images.QR $opts }}
|
||||
<img data-id="{{ $k }}" data-img-hash="{{ .Content | hash.XxHash }}" data-level="{{ $opts.level }}" data-scale="{{ $opts.scale }}" data-targetDir="{{ $opts.targetDir }}" src="{{ .RelPermalink }}">
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
b.AssertFileContent("public/index.html",
|
||||
`<img data-id="0" data-img-hash="6ccacf8056c41475" data-level="" data-scale="" data-targetDir="" src="/qr_3891606335523452031.png">`,
|
||||
`<img data-id="1" data-img-hash="6ccacf8056c41475" data-level="medium" data-scale="" data-targetDir="" src="/qr_3891606335523452031.png">`,
|
||||
`<img data-id="2" data-img-hash="6ccacf8056c41475" data-level="medium" data-scale="4" data-targetDir="" src="/qr_3891606335523452031.png">`,
|
||||
`<img data-id="3" data-img-hash="c29338c3d105b156" data-level="low" data-scale="2" data-targetDir="" src="/qr_2532593520456080065.png">`,
|
||||
`<img data-id="4" data-img-hash="8f7a639cea917b0e" data-level="medium" data-scale="3" data-targetDir="" src="/qr_6630115934366238432.png">`,
|
||||
`<img data-id="5" data-img-hash="2d15d6dcb861b5da" data-level="quartile" data-scale="5" data-targetDir="" src="/qr_14167719016579496966.png">`,
|
||||
`<img data-id="6" data-img-hash="113c45f2c091bc4d" data-level="high" data-scale="6" data-targetDir="" src="/qr_1700475212608554630.png">`,
|
||||
`<img data-id="7" data-img-hash="113c45f2c091bc4d" data-level="high" data-scale="6" data-targetDir="foo/bar" src="/foo/bar/qr_12381230670040458986.png">`,
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "low", "foo")
|
||||
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err.Error(), qt.Contains, "error correction level must be one of low, medium, quartile, or high")
|
||||
|
||||
files = strings.ReplaceAll(files, "foo", "low")
|
||||
files = strings.ReplaceAll(files, "https://gohugo.io", "")
|
||||
|
||||
b, err = hugolib.TestE(t, files)
|
||||
b.Assert(err.Error(), qt.Contains, "cannot encode an empty string")
|
||||
}
|
||||
|
|
76
tpl/tplimpl/embedded/templates/shortcodes/qr.html
Normal file
76
tpl/tplimpl/embedded/templates/shortcodes/qr.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
{{- /*
|
||||
Encodes the given text into a QR code using the specified options and renders the resulting image.
|
||||
|
||||
@param {string} text The text to encode, falling back to the text between the opening and closing shortcode tags.
|
||||
@param {string} [level=medium] The error correction level to use when encoding the text, one of low, medium, quartile, or high.
|
||||
@param {int} [scale=4] The number of image pixels per QR code module. Must be greater than or equal to 2.
|
||||
@param {string} [targetDir] The subdirectory within the publishDir where Hugo will place the generated image.
|
||||
@param {string} [alt] The alt attribute of the img element.
|
||||
@param {string} [class] The class attribute of the img element.
|
||||
@param {string} [id] The id attribute of the img element.
|
||||
@param {string} [title] The title attribute of the img element.
|
||||
|
||||
@returns {template.HTML}
|
||||
|
||||
@examples
|
||||
|
||||
{{< qr text="https://gohugo.io" />}}
|
||||
|
||||
{{< qr >}}
|
||||
https://gohugo.io"
|
||||
{{< /qr >}}
|
||||
|
||||
{{< qr
|
||||
text="https://gohugo.io"
|
||||
level="high"
|
||||
scale=4
|
||||
targetDir="codes"
|
||||
alt="QR code linking to https://gohugo.io"
|
||||
class="my-class"
|
||||
id="my-id"
|
||||
title="My Title"
|
||||
/>}}
|
||||
|
||||
*/}}
|
||||
|
||||
{{- /* Constants. */}}
|
||||
{{- $validLevels := slice "low" "medium" "quartile" "high" }}
|
||||
{{- $minimumScale := 2 }}
|
||||
|
||||
{{- /* Get arguments. */}}
|
||||
{{- $text := or (.Get "text") (strings.TrimSpace .Inner) "" }}
|
||||
{{- $level := or (.Get "level") "medium" }}
|
||||
{{- $scale := or (.Get "scale") 4 }}
|
||||
{{- $targetDir := or (.Get "targetDir") "" }}
|
||||
{{- $alt := or (.Get "alt") "" }}
|
||||
{{- $class := or (.Get "class") "" }}
|
||||
{{- $id := or (.Get "id") "" }}
|
||||
{{- $title := or (.Get "title") "" }}
|
||||
|
||||
{{- /* Validate arguments. */}}
|
||||
{{- $errors := false}}
|
||||
{{- if not $text }}
|
||||
{{- errorf "The %q shortcode requires a %q argument. See %s" .Name "text" .Position }}
|
||||
{{- $errors = true }}
|
||||
{{- end }}
|
||||
{{- if not (in $validLevels $level) }}
|
||||
{{- errorf "The %q argument passed to the %q shortcode must be one of %s. See %s" "level" .Name (delimit $validLevels ", " ", or ") .Position }}
|
||||
{{- $errors = true }}
|
||||
{{- end }}
|
||||
{{- if or (lt $scale $minimumScale) (ne $scale (int $scale)) }}
|
||||
{{- errorf "The %q argument passed to the %q shortcode must be an integer greater than or equal to %d. See %s" "scale" .Name $minimumScale .Position }}
|
||||
{{- $errors = true }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Render image. */}}
|
||||
{{- if not $errors }}
|
||||
{{- $opts := dict "text" $text "level" $level "scale" $scale "targetDir" $targetDir }}
|
||||
{{- with images.QR $opts -}}
|
||||
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}"
|
||||
{{- with $alt }} alt="{{ $alt }}" {{- end }}
|
||||
{{- with $class }} class="{{ $class }}" {{- end }}
|
||||
{{- with $id }} id="{{ $id }}" {{- end }}
|
||||
{{- with $title }} title="{{ $title }}" {{- end -}}
|
||||
>
|
||||
{{- end }}
|
||||
{{- end -}}
|
|
@ -698,3 +698,39 @@ Home!
|
|||
b.BuildPartial("/mybundle1/")
|
||||
b.AssertFileContent("public/mybundle1/index.html", "Baseof!!")
|
||||
}
|
||||
|
||||
func TestQRShortcode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
-- layouts/index.html --
|
||||
{{ .Content }}
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: home
|
||||
---
|
||||
{{< qr
|
||||
text="https://gohugo.io"
|
||||
level="high"
|
||||
scale=4
|
||||
targetDir="codes"
|
||||
alt="QR code linking to https://gohugo.io"
|
||||
class="my-class"
|
||||
id="my-id"
|
||||
title="My Title"
|
||||
/>}}
|
||||
|
||||
{{< qr >}}
|
||||
https://gohugo.io"
|
||||
{{< /qr >}}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html",
|
||||
`<img src="/codes/qr_1933601158373371382.png" width="148" height="148" alt="QR code linking to https://gohugo.io" class="my-class" id="my-id" title="My Title">`,
|
||||
`<img src="/qr_8288684942309665993.png" width="132" height="132">`,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue