diff --git a/layouts/_default/_markup/render-image.html b/layouts/_default/_markup/render-image.html index a9c07b1..5eb6d57 100644 --- a/layouts/_default/_markup/render-image.html +++ b/layouts/_default/_markup/render-image.html @@ -104,14 +104,14 @@ {{- /* Get image resource. */}} {{- $r := "" }} {{- if $u.IsAbs }} - {{- with resources.GetRemote $u.String }} + {{- with try (resources.GetRemote $u.String) }} {{- with .Err }} {{- if eq $errorLevel "warning" }} - {{- warnf "%s. See %s" . $contentPath }} + {{- warnf "%s. See %s" .Err $contentPath }} {{- else if eq $errorLevel "error" }} - {{- errorf "%s. See %s" . $contentPath }} + {{- errorf "%s. See %s" .Err $contentPath }} {{- end }} - {{- else }} + {{- else with .Value }} {{- /* Destination is a remote resource. */}} {{- $r = . }} {{- end }} diff --git a/layouts/partials/figure.html b/layouts/partials/figure.html index 53c79f7..21c77bc 100644 --- a/layouts/partials/figure.html +++ b/layouts/partials/figure.html @@ -1,289 +1,289 @@ -{{- /* Based on https://www.veriphor.com/articles/link-and-image-render-hooks/#image-render-hook */}} - -{{- /* Last modified: 2023-09-05T11:48:34-07:00 */}} - -{{- /* -Copyright 2023 Veriphor 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. -*/}} - -{{- /* -Renders an HTML figure element, in multiple formats and sizes. - -It resolves internal destinations by looking for a matching: - - 1. Page resource (an image in the current page bundle) - 2. Section resource (an image in the current section) - 3. Global resource (an image in the assets directory) - -It skips the section resource lookup if the current page is a leaf bundle, and -captures external destinations as resources for local hosting. The build will -fail if this partial is unable to resolve a destination. - -You must place global resources in the assets directory. If you have placed -your resources in the static directory, and you are unable or unwilling to move -them, you must mount the static directory to the assets directory by including -both of these entries in your site configuration: - - [[module.mounts]] - source = 'assets' - target = 'assets' - - [[module.mounts]] - source = 'static' - target = 'assets' - -Add this CSS to your site to enable responsive image behavior: - - img { - height: auto; - max-width: 100%; - } - -Add this CSS to your site to remove small gaps between adjacent elements: - - img, picture { - font-size: 0; - } - -@context {page} [page] The current page. -@context {string} [src] The path to the base image: a page resource, a global resource, or a remote resource. -@contect {int} [width] The display width of the image, in pixels, falling back to 100% of the viewport width. -@context {string} [sizes] = "" # "100vw", "75vw", or "auto" for example -@context {string slice} [formats] A slice of image formats, ordered by precedence, to use when creating images for the srcset attribute of each source element. -@context {string} [process] = "" # "fill 1600x900" for example -@context {string} [lqip] = "" # "16x webp q20" or "21x webp q20" for example -@context {string} [decoding] The img element's decoding attribute. -@context {string} [fetchpriority] The img element's fetchpriority attribute. -@context {string} [loading] The img element's loading attribute. -@context {string} [alt] The img element's alt attribute. -@context {string} [title] The img element's title attribute. -@context {string} [caption] The figure element's caption attribute. -@context {string} [class] The img element's class attribute. - -@returns {template.HTML} - -@example (required args only) - - {{- partial "figure.html" (dict "page" . "src" "images/zion.jpg") }} - -@example (all args) - {{- $opts := dict - "page" . - "src" "images/bryce-canyon-national-park.jpg" - "width" 768 - "sizes" "auto" - "formats" (slice "webp" "jpeg") - "process" "fill 1600x900" - "lqip" "16x webp q20" - "decoding" "async" - "fetchpriority" "auto" - "loading" "eager" - "alt" "Bryce Canyon National Park" - "title" "A beautiful day in Bryce Canyon National Park" - "caption" "Bryce Canyon National Park" - "class" "foo" - }} - {{- partial "figure.html" $opts }} - -*/}} - -{{- /* Initialize. */}} -{{- $partialName := "figure" }} - -{{- /* Verify minimum required version. */}} -{{- $minHugoVersion := "0.118.0" }} -{{- if lt hugo.Version $minHugoVersion }} - {{- errorf "The %q partial requires Hugo v%s or later." $partialName $minHugoVersion }} -{{- end }} - -{{- /* Validate page arg. */}} -{{- if not .page }} - {{- errorf "The %q partial requires a page argument." $partialName }} -{{- end }} - -{{- /* Determine content path for warning and error messages. */}} -{{- $contentPath := "" }} -{{- with .page.File }} - {{- $contentPath = .Path }} -{{- else }} - {{- $contentPath = .Path }} -{{- end }} - -{{- /* Set defaults and get args. */}} -{{- $alt := or .alt "" }} -{{- $class := or .class "" }} -{{- $formats := or .formats (slice "webp") }} -{{- $decoding := or .decoding site.Params.thulite_images.defaults.decoding }} -{{- $fetchPriority := or .fetchpriority site.Params.thulite_images.defaults.fetchpriority }} -{{- $loading := or .loading site.Params.thulite_images.defaults.loading }} -{{- $process := or .process site.Params.thulite_images.defaults.process }} -{{- $lqip := or .lqip site.Params.thulite_images.defaults.lqip }} -{{- $src := or .src "" }} -{{- $title := or .title "" }} -{{- $caption := or .caption "" }} -{{- $width := or (int .width) 0 }} -{{- $fallbackFormat := "jpeg" }} -{{- $stdWidths := site.Params.thulite_images.defaults.widths }} -{{- $stdSizes := or .sizes site.Params.thulite_images.defaults.sizes }} - -{{- /* Validate args. */}} -{{- $validFormats := slice "gif" "jpg" "jpeg" "png" "webp"}} -{{- if reflect.IsSlice $formats }} - {{- $formats = apply $formats "strings.ToLower" "." }} - {{- range $formats }} - {{- if not (in $validFormats .) }} - {{- errorf "The formats argument passed to the %q partial is invalid. Valid formats are %s. See %s" $partialName (delimit $validFormats ", " ", and ") $contentPath }} - {{- end }} - {{- end }} -{{- else }} - {{- errorf "The formats argument passed to the %q partial is not a slice. See %s" $partialName $contentPath }} -{{- end }} - -{{- if not $src }} - {{- errorf "The %q partial requires an image path, relative to the assets directory. See %s" $partialName $contentPath }} -{{- end }} - -{{- /* Capture image as a resource. */}} -{{- $r := "" }} -{{- $ctx := dict - "page" .page - "path" $src - "partialName" $partialName - "contentPath" $contentPath -}} -{{- with partial "inline/capture-resource.html" $ctx }} - {{- $r = . }} -{{- end }} - -{{- /* Process image. */}} -{{- with $process }} - {{- $r = $r.Process $process }} -{{- end }} - -{{- /* Process LQIP. */}} -{{- $l := "" }} -{{- with $lqip }} - {{- $l = $r.Resize . }} -{{- end }} - -{{- /* Determine widths for srcset generation. */}} -{{- $widths := slice }} -{{- if $width }} - {{- /* The width was specified; generate 1x, 2x, 3x, and 4x images. */}} - {{- $widths = slice $r.Width }} - {{- range seq 4 }} - {{- with mul . $width }} - {{- if and (le . $r.Width) (le . (math.Max $stdWidths)) }} - {{- /* Do not enlarge, and do not exceed maximum of $stdWidths. */}} - {{- $widths = $widths | append . }} - {{- end }} - {{- end }} - {{- end }} -{{- else }} - {{- /* The width was not speficied, will be using $stdWidths. */}} - {{- $stdWidths = $stdWidths | append $r.Width | sort }} - {{- range $stdWidths }} - {{- /* Do not enlarge. */}} - {{- if (le . $r.Width) }} - {{- $widths = $widths | append . }} - {{- end }} - {{- end }} -{{- end }} -{{- $widths = $widths | uniq | sort}} - -{{- /* Create fallback image (fi) with the smallest of widths. */}} -{{- $fi := $r.Resize (printf "%dx %s" (math.Min $widths | int) $fallbackFormat) }} - -{{- /* Create the image map. */}} -{{- $im := dict }} -{{- range $format := $formats }} - {{- $sizes := slice }} - {{- range sort $widths }} - {{- $sizes = $sizes | append ($r.Resize (printf "%dx %s" . $format)) }} - {{- end }} - {{- $im = merge $im (dict $format $sizes) }} -{{- end }} - -{{- /* Render. */}} -
- {{ $alt }} - {{- with $caption }}
{{ . }}
{{- end }} -
- -{{- define "partials/inline/capture-resource.html" }} - {{- /* Parse destination. */}} - {{- $u := urls.Parse .path }} - - {{- /* Set common message. */}} - {{- $msg := printf "The %q partial was unable to get %q in %s" .partialName $u.String .contentPath }} - - {{- /* Get image resource. */}} - {{- $r := "" }} - {{- if $u.IsAbs }} - {{- with resources.GetRemote $u.String }} - {{- with .Err }} - {{- errorf "%s. See %s" . $.contentPath }} - {{- else }} - {{- /* Destination is a remote resource. */}} - {{- $r = . }} - {{- end }} - {{- else }} - {{- errorf $msg }} - {{- end }} - {{- else }} - {{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }} - {{- /* Destination is a page resource. */}} - {{- $r = . }} - {{- else }} - {{- with (and (ne .page.BundleType "leaf") (.page.CurrentSection.Resources.Get (strings.TrimPrefix "./" $u.Path)) ) }} - {{- /* Destination is a section resource, and current page is not a leaf bundle. */}} - {{- $r = . }} - {{- else }} - {{- with resources.Get $u.Path }} - {{- /* Destination is a global resource. */}} - {{- $r = . }} - {{- else }} - {{- errorf $msg }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} - {{- return $r }} -{{- end -}} +{{- /* Based on https://www.veriphor.com/articles/link-and-image-render-hooks/#image-render-hook */}} + +{{- /* Last modified: 2023-09-05T11:48:34-07:00 */}} + +{{- /* +Copyright 2023 Veriphor 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. +*/}} + +{{- /* +Renders an HTML figure element, in multiple formats and sizes. + +It resolves internal destinations by looking for a matching: + + 1. Page resource (an image in the current page bundle) + 2. Section resource (an image in the current section) + 3. Global resource (an image in the assets directory) + +It skips the section resource lookup if the current page is a leaf bundle, and +captures external destinations as resources for local hosting. The build will +fail if this partial is unable to resolve a destination. + +You must place global resources in the assets directory. If you have placed +your resources in the static directory, and you are unable or unwilling to move +them, you must mount the static directory to the assets directory by including +both of these entries in your site configuration: + + [[module.mounts]] + source = 'assets' + target = 'assets' + + [[module.mounts]] + source = 'static' + target = 'assets' + +Add this CSS to your site to enable responsive image behavior: + + img { + height: auto; + max-width: 100%; + } + +Add this CSS to your site to remove small gaps between adjacent elements: + + img, picture { + font-size: 0; + } + +@context {page} [page] The current page. +@context {string} [src] The path to the base image: a page resource, a global resource, or a remote resource. +@contect {int} [width] The display width of the image, in pixels, falling back to 100% of the viewport width. +@context {string} [sizes] = "" # "100vw", "75vw", or "auto" for example +@context {string slice} [formats] A slice of image formats, ordered by precedence, to use when creating images for the srcset attribute of each source element. +@context {string} [process] = "" # "fill 1600x900" for example +@context {string} [lqip] = "" # "16x webp q20" or "21x webp q20" for example +@context {string} [decoding] The img element's decoding attribute. +@context {string} [fetchpriority] The img element's fetchpriority attribute. +@context {string} [loading] The img element's loading attribute. +@context {string} [alt] The img element's alt attribute. +@context {string} [title] The img element's title attribute. +@context {string} [caption] The figure element's caption attribute. +@context {string} [class] The img element's class attribute. + +@returns {template.HTML} + +@example (required args only) + + {{- partial "figure.html" (dict "page" . "src" "images/zion.jpg") }} + +@example (all args) + {{- $opts := dict + "page" . + "src" "images/bryce-canyon-national-park.jpg" + "width" 768 + "sizes" "auto" + "formats" (slice "webp" "jpeg") + "process" "fill 1600x900" + "lqip" "16x webp q20" + "decoding" "async" + "fetchpriority" "auto" + "loading" "eager" + "alt" "Bryce Canyon National Park" + "title" "A beautiful day in Bryce Canyon National Park" + "caption" "Bryce Canyon National Park" + "class" "foo" + }} + {{- partial "figure.html" $opts }} + +*/}} + +{{- /* Initialize. */}} +{{- $partialName := "figure" }} + +{{- /* Verify minimum required version. */}} +{{- $minHugoVersion := "0.118.0" }} +{{- if lt hugo.Version $minHugoVersion }} + {{- errorf "The %q partial requires Hugo v%s or later." $partialName $minHugoVersion }} +{{- end }} + +{{- /* Validate page arg. */}} +{{- if not .page }} + {{- errorf "The %q partial requires a page argument." $partialName }} +{{- end }} + +{{- /* Determine content path for warning and error messages. */}} +{{- $contentPath := "" }} +{{- with .page.File }} + {{- $contentPath = .Path }} +{{- else }} + {{- $contentPath = .Path }} +{{- end }} + +{{- /* Set defaults and get args. */}} +{{- $alt := or .alt "" }} +{{- $class := or .class "" }} +{{- $formats := or .formats (slice "webp") }} +{{- $decoding := or .decoding site.Params.thulite_images.defaults.decoding }} +{{- $fetchPriority := or .fetchpriority site.Params.thulite_images.defaults.fetchpriority }} +{{- $loading := or .loading site.Params.thulite_images.defaults.loading }} +{{- $process := or .process site.Params.thulite_images.defaults.process }} +{{- $lqip := or .lqip site.Params.thulite_images.defaults.lqip }} +{{- $src := or .src "" }} +{{- $title := or .title "" }} +{{- $caption := or .caption "" }} +{{- $width := or (int .width) 0 }} +{{- $fallbackFormat := "jpeg" }} +{{- $stdWidths := site.Params.thulite_images.defaults.widths }} +{{- $stdSizes := or .sizes site.Params.thulite_images.defaults.sizes }} + +{{- /* Validate args. */}} +{{- $validFormats := slice "gif" "jpg" "jpeg" "png" "webp"}} +{{- if reflect.IsSlice $formats }} + {{- $formats = apply $formats "strings.ToLower" "." }} + {{- range $formats }} + {{- if not (in $validFormats .) }} + {{- errorf "The formats argument passed to the %q partial is invalid. Valid formats are %s. See %s" $partialName (delimit $validFormats ", " ", and ") $contentPath }} + {{- end }} + {{- end }} +{{- else }} + {{- errorf "The formats argument passed to the %q partial is not a slice. See %s" $partialName $contentPath }} +{{- end }} + +{{- if not $src }} + {{- errorf "The %q partial requires an image path, relative to the assets directory. See %s" $partialName $contentPath }} +{{- end }} + +{{- /* Capture image as a resource. */}} +{{- $r := "" }} +{{- $ctx := dict + "page" .page + "path" $src + "partialName" $partialName + "contentPath" $contentPath +}} +{{- with partial "inline/capture-resource.html" $ctx }} + {{- $r = . }} +{{- end }} + +{{- /* Process image. */}} +{{- with $process }} + {{- $r = $r.Process $process }} +{{- end }} + +{{- /* Process LQIP. */}} +{{- $l := "" }} +{{- with $lqip }} + {{- $l = $r.Resize . }} +{{- end }} + +{{- /* Determine widths for srcset generation. */}} +{{- $widths := slice }} +{{- if $width }} + {{- /* The width was specified; generate 1x, 2x, 3x, and 4x images. */}} + {{- $widths = slice $r.Width }} + {{- range seq 4 }} + {{- with mul . $width }} + {{- if and (le . $r.Width) (le . (math.Max $stdWidths)) }} + {{- /* Do not enlarge, and do not exceed maximum of $stdWidths. */}} + {{- $widths = $widths | append . }} + {{- end }} + {{- end }} + {{- end }} +{{- else }} + {{- /* The width was not speficied, will be using $stdWidths. */}} + {{- $stdWidths = $stdWidths | append $r.Width | sort }} + {{- range $stdWidths }} + {{- /* Do not enlarge. */}} + {{- if (le . $r.Width) }} + {{- $widths = $widths | append . }} + {{- end }} + {{- end }} +{{- end }} +{{- $widths = $widths | uniq | sort}} + +{{- /* Create fallback image (fi) with the smallest of widths. */}} +{{- $fi := $r.Resize (printf "%dx %s" (math.Min $widths | int) $fallbackFormat) }} + +{{- /* Create the image map. */}} +{{- $im := dict }} +{{- range $format := $formats }} + {{- $sizes := slice }} + {{- range sort $widths }} + {{- $sizes = $sizes | append ($r.Resize (printf "%dx %s" . $format)) }} + {{- end }} + {{- $im = merge $im (dict $format $sizes) }} +{{- end }} + +{{- /* Render. */}} +
+ {{ $alt }} + {{- with $caption }}
{{ . }}
{{- end }} +
+ +{{- define "partials/inline/capture-resource.html" }} + {{- /* Parse destination. */}} + {{- $u := urls.Parse .path }} + + {{- /* Set common message. */}} + {{- $msg := printf "The %q partial was unable to get %q in %s" .partialName $u.String .contentPath }} + + {{- /* Get image resource. */}} + {{- $r := "" }} + {{- if $u.IsAbs }} + {{- with try (resources.GetRemote $u.String) }} + {{- with .Err }} + {{- errorf "%s. See %s" .Err $.contentPath }} + {{- else with .Value }} + {{- /* Destination is a remote resource. */}} + {{- $r = . }} + {{- end }} + {{- else }} + {{- errorf $msg }} + {{- end }} + {{- else }} + {{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }} + {{- /* Destination is a page resource. */}} + {{- $r = . }} + {{- else }} + {{- with (and (ne .page.BundleType "leaf") (.page.CurrentSection.Resources.Get (strings.TrimPrefix "./" $u.Path)) ) }} + {{- /* Destination is a section resource, and current page is not a leaf bundle. */}} + {{- $r = . }} + {{- else }} + {{- with resources.Get $u.Path }} + {{- /* Destination is a global resource. */}} + {{- $r = . }} + {{- else }} + {{- errorf $msg }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- return $r }} +{{- end -}} diff --git a/layouts/partials/img.html b/layouts/partials/img.html index 20c76d5..757b3d6 100644 --- a/layouts/partials/img.html +++ b/layouts/partials/img.html @@ -1,283 +1,283 @@ -{{- /* Based on https://www.veriphor.com/articles/link-and-image-render-hooks/#image-render-hook */}} - -{{- /* Last modified: 2023-09-05T11:48:34-07:00 */}} - -{{- /* -Copyright 2023 Veriphor 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. -*/}} - -{{- /* -Renders an HTML img element, in multiple formats and sizes. - -It resolves internal destinations by looking for a matching: - - 1. Page resource (an image in the current page bundle) - 2. Section resource (an image in the current section) - 3. Global resource (an image in the assets directory) - -It skips the section resource lookup if the current page is a leaf bundle, and -captures external destinations as resources for local hosting. The build will -fail if this partial is unable to resolve a destination. - -You must place global resources in the assets directory. If you have placed -your resources in the static directory, and you are unable or unwilling to move -them, you must mount the static directory to the assets directory by including -both of these entries in your site configuration: - - [[module.mounts]] - source = 'assets' - target = 'assets' - - [[module.mounts]] - source = 'static' - target = 'assets' - -Add this CSS to your site to enable responsive image behavior: - - img { - height: auto; - max-width: 100%; - } - -Add this CSS to your site to remove small gaps between adjacent elements: - - img, picture { - font-size: 0; - } - -@context {page} [page] The current page. -@context {string} [src] The path to the base image: a page resource, a global resource, or a remote resource. -@contect {int} [width] The display width of the image, in pixels, falling back to 100% of the viewport width. -@context {string} [sizes] = "" # "100vw", "75vw", or "auto" for example -@context {string slice} [formats] A slice of image formats, ordered by precedence, to use when creating images for the srcset attribute of each source element. -@context {string} [process] = "" # "fill 1600x900" for example -@context {string} [lqip] = "" # "16x webp q20" or "21x webp q20" for example -@context {string} [decoding] The img element's decoding attribute. -@context {string} [fetchpriority] The img element's fetchpriority attribute. -@context {string} [loading] The img element's loading attribute. -@context {string} [alt] The img element's alt attribute. -@context {string} [title] The img element's title attribute. -@context {string} [class] The img element's class attribute. - -@returns {template.HTML} - -@example (required args only) - - {{- partial "img.html" (dict "page" . "src" "images/zion.jpg") }} - -@example (all args) - {{- $opts := dict - "page" . - "src" "images/bryce-canyon-national-park.jpg" - "width" 768 - "sizes" "auto" - "formats" (slice "webp" "jpeg") - "process" "fill 1600x900" - "lqip" "16x webp q20" - "decoding" "async" - "fetchpriority" "auto" - "loading" "eager" - "alt" "Bryce Canyon National Park" - "title" "A beautiful day in Bryce Canyon National Park" - "class" "foo" - }} - {{- partial "img.html" $opts }} - -*/}} - -{{- /* Initialize. */}} -{{- $partialName := "img" }} - -{{- /* Verify minimum required version. */}} -{{- $minHugoVersion := "0.118.0" }} -{{- if lt hugo.Version $minHugoVersion }} - {{- errorf "The %q partial requires Hugo v%s or later." $partialName $minHugoVersion }} -{{- end }} - -{{- /* Validate page arg. */}} -{{- if not .page }} - {{- errorf "The %q partial requires a page argument." $partialName }} -{{- end }} - -{{- /* Determine content path for warning and error messages. */}} -{{- $contentPath := "" }} -{{- with .page.File }} - {{- $contentPath = .Path }} -{{- else }} - {{- $contentPath = .Path }} -{{- end }} - -{{- /* Set defaults and get args. */}} -{{- $alt := or .alt "" }} -{{- $class := or .class "" }} -{{- $formats := or .formats (slice "webp") }} -{{- $decoding := or .decoding site.Params.thulite_images.defaults.decoding }} -{{- $fetchPriority := or .fetchpriority site.Params.thulite_images.defaults.fetchpriority }} -{{- $loading := or .loading site.Params.thulite_images.defaults.loading }} -{{- $process := or .process site.Params.thulite_images.defaults.process }} -{{- $lqip := or .lqip site.Params.thulite_images.defaults.lqip }} -{{- $src := or .src "" }} -{{- $title := or .title "" }} -{{- $width := or (int .width) 0 }} -{{- $fallbackFormat := "jpeg" }} -{{- $stdWidths := site.Params.thulite_images.defaults.widths }} -{{- $stdSizes := or .sizes site.Params.thulite_images.defaults.sizes }} - -{{- /* Validate args. */}} -{{- $validFormats := slice "gif" "jpg" "jpeg" "png" "webp"}} -{{- if reflect.IsSlice $formats }} - {{- $formats = apply $formats "strings.ToLower" "." }} - {{- range $formats }} - {{- if not (in $validFormats .) }} - {{- errorf "The formats argument passed to the %q partial is invalid. Valid formats are %s. See %s" $partialName (delimit $validFormats ", " ", and ") $contentPath }} - {{- end }} - {{- end }} -{{- else }} - {{- errorf "The formats argument passed to the %q partial is not a slice. See %s" $partialName $contentPath }} -{{- end }} - -{{- if not $src }} - {{- errorf "The %q partial requires an image path, relative to the assets directory. See %s" $partialName $contentPath }} -{{- end }} - -{{- /* Capture image as a resource. */}} -{{- $r := "" }} -{{- $ctx := dict - "page" .page - "path" $src - "partialName" $partialName - "contentPath" $contentPath -}} -{{- with partial "inline/capture-resource.html" $ctx }} - {{- $r = . }} -{{- end }} - -{{- /* Process image. */}} -{{- with $process }} - {{- $r = $r.Process $process }} -{{- end }} - -{{- /* Process LQIP. */}} -{{- $l := "" }} -{{- with $lqip }} - {{- $l = $r.Resize . }} -{{- end }} - -{{- /* Determine widths for srcset generation. */}} -{{- $widths := slice }} -{{- if $width }} - {{- /* The width was specified; generate 1x, 2x, 3x, and 4x images. */}} - {{- $widths = slice $r.Width }} - {{- range seq 4 }} - {{- with mul . $width }} - {{- if and (le . $r.Width) (le . (math.Max $stdWidths)) }} - {{- /* Do not enlarge, and do not exceed maximum of $stdWidths. */}} - {{- $widths = $widths | append . }} - {{- end }} - {{- end }} - {{- end }} -{{- else }} - {{- /* The width was not speficied, will be using $stdWidths. */}} - {{- $stdWidths = $stdWidths | append $r.Width | sort }} - {{- range $stdWidths }} - {{- /* Do not enlarge. */}} - {{- if (le . $r.Width) }} - {{- $widths = $widths | append . }} - {{- end }} - {{- end }} -{{- end }} -{{- $widths = $widths | uniq | sort}} - -{{- /* Create fallback image (fi) with the smallest of widths. */}} -{{- $fi := $r.Resize (printf "%dx %s" (math.Min $widths | int) $fallbackFormat) }} - -{{- /* Create the image map. */}} -{{- $im := dict }} -{{- range $format := $formats }} - {{- $sizes := slice }} - {{- range sort $widths }} - {{- $sizes = $sizes | append ($r.Resize (printf "%dx %s" . $format)) }} - {{- end }} - {{- $im = merge $im (dict $format $sizes) }} -{{- end }} - -{{- /* Render. */}} -{{ $alt }} - -{{- define "partials/inline/capture-resource.html" }} - {{- /* Parse destination. */}} - {{- $u := urls.Parse .path }} - - {{- /* Set common message. */}} - {{- $msg := printf "The %q partial was unable to get %q in %s" .partialName $u.String .contentPath }} - - {{- /* Get image resource. */}} - {{- $r := "" }} - {{- if $u.IsAbs }} - {{- with resources.GetRemote $u.String }} - {{- with .Err }} - {{- errorf "%s. See %s" . $.contentPath }} - {{- else }} - {{- /* Destination is a remote resource. */}} - {{- $r = . }} - {{- end }} - {{- else }} - {{- errorf $msg }} - {{- end }} - {{- else }} - {{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }} - {{- /* Destination is a page resource. */}} - {{- $r = . }} - {{- else }} - {{- with (and (ne .page.BundleType "leaf") (.page.CurrentSection.Resources.Get (strings.TrimPrefix "./" $u.Path)) ) }} - {{- /* Destination is a section resource, and current page is not a leaf bundle. */}} - {{- $r = . }} - {{- else }} - {{- with resources.Get $u.Path }} - {{- /* Destination is a global resource. */}} - {{- $r = . }} - {{- else }} - {{- errorf $msg }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} - {{- return $r }} -{{- end -}} +{{- /* Based on https://www.veriphor.com/articles/link-and-image-render-hooks/#image-render-hook */}} + +{{- /* Last modified: 2023-09-05T11:48:34-07:00 */}} + +{{- /* +Copyright 2023 Veriphor 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. +*/}} + +{{- /* +Renders an HTML img element, in multiple formats and sizes. + +It resolves internal destinations by looking for a matching: + + 1. Page resource (an image in the current page bundle) + 2. Section resource (an image in the current section) + 3. Global resource (an image in the assets directory) + +It skips the section resource lookup if the current page is a leaf bundle, and +captures external destinations as resources for local hosting. The build will +fail if this partial is unable to resolve a destination. + +You must place global resources in the assets directory. If you have placed +your resources in the static directory, and you are unable or unwilling to move +them, you must mount the static directory to the assets directory by including +both of these entries in your site configuration: + + [[module.mounts]] + source = 'assets' + target = 'assets' + + [[module.mounts]] + source = 'static' + target = 'assets' + +Add this CSS to your site to enable responsive image behavior: + + img { + height: auto; + max-width: 100%; + } + +Add this CSS to your site to remove small gaps between adjacent elements: + + img, picture { + font-size: 0; + } + +@context {page} [page] The current page. +@context {string} [src] The path to the base image: a page resource, a global resource, or a remote resource. +@contect {int} [width] The display width of the image, in pixels, falling back to 100% of the viewport width. +@context {string} [sizes] = "" # "100vw", "75vw", or "auto" for example +@context {string slice} [formats] A slice of image formats, ordered by precedence, to use when creating images for the srcset attribute of each source element. +@context {string} [process] = "" # "fill 1600x900" for example +@context {string} [lqip] = "" # "16x webp q20" or "21x webp q20" for example +@context {string} [decoding] The img element's decoding attribute. +@context {string} [fetchpriority] The img element's fetchpriority attribute. +@context {string} [loading] The img element's loading attribute. +@context {string} [alt] The img element's alt attribute. +@context {string} [title] The img element's title attribute. +@context {string} [class] The img element's class attribute. + +@returns {template.HTML} + +@example (required args only) + + {{- partial "img.html" (dict "page" . "src" "images/zion.jpg") }} + +@example (all args) + {{- $opts := dict + "page" . + "src" "images/bryce-canyon-national-park.jpg" + "width" 768 + "sizes" "auto" + "formats" (slice "webp" "jpeg") + "process" "fill 1600x900" + "lqip" "16x webp q20" + "decoding" "async" + "fetchpriority" "auto" + "loading" "eager" + "alt" "Bryce Canyon National Park" + "title" "A beautiful day in Bryce Canyon National Park" + "class" "foo" + }} + {{- partial "img.html" $opts }} + +*/}} + +{{- /* Initialize. */}} +{{- $partialName := "img" }} + +{{- /* Verify minimum required version. */}} +{{- $minHugoVersion := "0.118.0" }} +{{- if lt hugo.Version $minHugoVersion }} + {{- errorf "The %q partial requires Hugo v%s or later." $partialName $minHugoVersion }} +{{- end }} + +{{- /* Validate page arg. */}} +{{- if not .page }} + {{- errorf "The %q partial requires a page argument." $partialName }} +{{- end }} + +{{- /* Determine content path for warning and error messages. */}} +{{- $contentPath := "" }} +{{- with .page.File }} + {{- $contentPath = .Path }} +{{- else }} + {{- $contentPath = .Path }} +{{- end }} + +{{- /* Set defaults and get args. */}} +{{- $alt := or .alt "" }} +{{- $class := or .class "" }} +{{- $formats := or .formats (slice "webp") }} +{{- $decoding := or .decoding site.Params.thulite_images.defaults.decoding }} +{{- $fetchPriority := or .fetchpriority site.Params.thulite_images.defaults.fetchpriority }} +{{- $loading := or .loading site.Params.thulite_images.defaults.loading }} +{{- $process := or .process site.Params.thulite_images.defaults.process }} +{{- $lqip := or .lqip site.Params.thulite_images.defaults.lqip }} +{{- $src := or .src "" }} +{{- $title := or .title "" }} +{{- $width := or (int .width) 0 }} +{{- $fallbackFormat := "jpeg" }} +{{- $stdWidths := site.Params.thulite_images.defaults.widths }} +{{- $stdSizes := or .sizes site.Params.thulite_images.defaults.sizes }} + +{{- /* Validate args. */}} +{{- $validFormats := slice "gif" "jpg" "jpeg" "png" "webp"}} +{{- if reflect.IsSlice $formats }} + {{- $formats = apply $formats "strings.ToLower" "." }} + {{- range $formats }} + {{- if not (in $validFormats .) }} + {{- errorf "The formats argument passed to the %q partial is invalid. Valid formats are %s. See %s" $partialName (delimit $validFormats ", " ", and ") $contentPath }} + {{- end }} + {{- end }} +{{- else }} + {{- errorf "The formats argument passed to the %q partial is not a slice. See %s" $partialName $contentPath }} +{{- end }} + +{{- if not $src }} + {{- errorf "The %q partial requires an image path, relative to the assets directory. See %s" $partialName $contentPath }} +{{- end }} + +{{- /* Capture image as a resource. */}} +{{- $r := "" }} +{{- $ctx := dict + "page" .page + "path" $src + "partialName" $partialName + "contentPath" $contentPath +}} +{{- with partial "inline/capture-resource.html" $ctx }} + {{- $r = . }} +{{- end }} + +{{- /* Process image. */}} +{{- with $process }} + {{- $r = $r.Process $process }} +{{- end }} + +{{- /* Process LQIP. */}} +{{- $l := "" }} +{{- with $lqip }} + {{- $l = $r.Resize . }} +{{- end }} + +{{- /* Determine widths for srcset generation. */}} +{{- $widths := slice }} +{{- if $width }} + {{- /* The width was specified; generate 1x, 2x, 3x, and 4x images. */}} + {{- $widths = slice $r.Width }} + {{- range seq 4 }} + {{- with mul . $width }} + {{- if and (le . $r.Width) (le . (math.Max $stdWidths)) }} + {{- /* Do not enlarge, and do not exceed maximum of $stdWidths. */}} + {{- $widths = $widths | append . }} + {{- end }} + {{- end }} + {{- end }} +{{- else }} + {{- /* The width was not speficied, will be using $stdWidths. */}} + {{- $stdWidths = $stdWidths | append $r.Width | sort }} + {{- range $stdWidths }} + {{- /* Do not enlarge. */}} + {{- if (le . $r.Width) }} + {{- $widths = $widths | append . }} + {{- end }} + {{- end }} +{{- end }} +{{- $widths = $widths | uniq | sort}} + +{{- /* Create fallback image (fi) with the smallest of widths. */}} +{{- $fi := $r.Resize (printf "%dx %s" (math.Min $widths | int) $fallbackFormat) }} + +{{- /* Create the image map. */}} +{{- $im := dict }} +{{- range $format := $formats }} + {{- $sizes := slice }} + {{- range sort $widths }} + {{- $sizes = $sizes | append ($r.Resize (printf "%dx %s" . $format)) }} + {{- end }} + {{- $im = merge $im (dict $format $sizes) }} +{{- end }} + +{{- /* Render. */}} +{{ $alt }} + +{{- define "partials/inline/capture-resource.html" }} + {{- /* Parse destination. */}} + {{- $u := urls.Parse .path }} + + {{- /* Set common message. */}} + {{- $msg := printf "The %q partial was unable to get %q in %s" .partialName $u.String .contentPath }} + + {{- /* Get image resource. */}} + {{- $r := "" }} + {{- if $u.IsAbs }} + {{- with try (resources.GetRemote $u.String) }} + {{- with .Err }} + {{- errorf "%s. See %s" .Err $.contentPath }} + {{- else with .Value }} + {{- /* Destination is a remote resource. */}} + {{- $r = .Value }} + {{- end }} + {{- else }} + {{- errorf $msg }} + {{- end }} + {{- else }} + {{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }} + {{- /* Destination is a page resource. */}} + {{- $r = . }} + {{- else }} + {{- with (and (ne .page.BundleType "leaf") (.page.CurrentSection.Resources.Get (strings.TrimPrefix "./" $u.Path)) ) }} + {{- /* Destination is a section resource, and current page is not a leaf bundle. */}} + {{- $r = . }} + {{- else }} + {{- with resources.Get $u.Path }} + {{- /* Destination is a global resource. */}} + {{- $r = . }} + {{- else }} + {{- errorf $msg }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- return $r }} +{{- end -}} diff --git a/layouts/partials/picture.html b/layouts/partials/picture.html index e1175c3..b16f0fe 100644 --- a/layouts/partials/picture.html +++ b/layouts/partials/picture.html @@ -253,10 +253,10 @@ {{- /* Get image resource. */}} {{- $r := "" }} {{- if $u.IsAbs }} - {{- with resources.GetRemote $u.String }} + {{- with try (resources.GetRemote $u.String) }} {{- with .Err }} - {{- errorf "%s. See %s" . $.contentPath }} - {{- else }} + {{- errorf "%s. See %s" .Err $.contentPath }} + {{- else with .Value }} {{- /* Destination is a remote resource. */}} {{- $r = . }} {{- end }}