From 76023f9461b13cfa1b9c24f40510480d75ccd6d6 Mon Sep 17 00:00:00 2001 From: George Cushen Date: Fri, 27 Mar 2026 04:35:53 +0000 Subject: [PATCH] refactor(images): adopt Hugo reflect.IsImageResource* API for type-safe image processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace manual format detection (printf "%T", MediaType.SubType whitelists, duck-typing) with Hugo's new reflect.IsResource, reflect.IsImageResource, and reflect.IsImageResourceProcessable functions across 19 template files. Key changes: - process_responsive_image: collapse 15-line format whitelist into single reflect.IsImageResourceProcessable call - Unify three-way SVG/processable/GIF branches into two-way $canProcess guard - Remove duplicate GIF passthrough branches in single.html, events/page.html - Add missing SVG guard in research-areas block (was calling .Fill unprotected) - Replace nil checks with reflect.IsResource in get_cover_image/get_featured_image Design decision: GIFs are intentionally excluded from processing despite Hugo supporting it — processing strips animation frames, so we preserve user-optimized animated GIFs by checking (IsImageResourceProcessable AND not GIF). --- modules/blox/blox/logos/block.html | 40 +++++++-------- modules/blox/blox/portfolio/block.html | 6 +-- modules/blox/blox/research-areas/block.html | 4 +- .../blox/blox/resume-biography-3/block.html | 8 +-- modules/blox/blox/resume-biography/block.html | 9 ++-- .../blox/layouts/_markup/render-image.html | 10 ++-- .../_partials/blox/preact-wrapper.html | 12 +++-- .../layouts/_partials/components/cover.html | 13 +++-- .../_partials/functions/get_cover_image.html | 13 +++-- .../functions/get_featured_image.html | 4 +- .../_partials/functions/parse_block_v3.html | 2 +- .../functions/process_responsive_image.html | 30 ++++------- .../layouts/_partials/page_author_card.html | 3 +- modules/blox/layouts/_partials/site_head.html | 3 +- .../blox/layouts/_partials/views/card.html | 25 ++++----- .../_partials/views/slides-gallery.html | 51 +++++++++---------- modules/blox/layouts/authors/term.html | 3 +- modules/blox/layouts/events/page.html | 34 ++++--------- modules/blox/layouts/single.html | 35 ++++--------- 19 files changed, 135 insertions(+), 170 deletions(-) diff --git a/modules/blox/blox/logos/block.html b/modules/blox/blox/logos/block.html index 71e2424c..c2363072 100644 --- a/modules/blox/blox/logos/block.html +++ b/modules/blox/blox/logos/block.html @@ -149,13 +149,13 @@ {{ $img := $image_resource }} {{ if $img }} - {{ $isSVG := eq $img.MediaType.SubType "svg" }} - {{ if not $isSVG }} + {{ $isProcessable := reflect.IsImageResourceProcessable $img }} + {{ if $isProcessable }} {{ $img = $img.Fit "280x140 webp" }} {{ end }} - {{ $name | default {{ else if $image_url }} @@ -191,13 +191,13 @@
{{ $image := . }} - {{ $isSVG := eq $image.MediaType.SubType "svg" }} - {{ if not $isSVG }} + {{ $isProcessable := reflect.IsImageResourceProcessable $image }} + {{ if $isProcessable }} {{ $image = $image.Fit "280x140 webp" }} {{ end }} - Partner logo
@@ -234,13 +234,13 @@ {{ if .image }} {{ $img := resources.Get (printf "media/%s" .image) }} {{ if $img }} - {{ $isSVG := eq $img.MediaType.SubType "svg" }} - {{ if not $isSVG }} + {{ $isProcessable := reflect.IsImageResourceProcessable $img }} + {{ if $isProcessable }} {{ $img = $img.Fit "280x140 webp" }} {{ end }} - {{ .name | default {{ end }} @@ -254,13 +254,13 @@ {{ if .image }} {{ $img := resources.Get (printf "media/%s" .image) }} {{ if $img }} - {{ $isSVG := eq $img.MediaType.SubType "svg" }} - {{ if not $isSVG }} + {{ $isProcessable := reflect.IsImageResourceProcessable $img }} + {{ if $isProcessable }} {{ $img = $img.Fit "280x140 webp" }} {{ end }} - {{ .name | default {{ end }} @@ -286,13 +286,13 @@ {{ if $image_name }} {{ $img := resources.Get (printf "media/%s" $image_name) }} {{ if $img }} - {{ $isSVG := eq $img.MediaType.SubType "svg" }} - {{ if not $isSVG }} + {{ $isProcessable := reflect.IsImageResourceProcessable $img }} + {{ if $isProcessable }} {{ $img = $img.Fit "280x140 webp" }} {{ end }} - {{ $name | default {{ end }} diff --git a/modules/blox/blox/portfolio/block.html b/modules/blox/blox/portfolio/block.html index fc9f16ac..b4203c18 100644 --- a/modules/blox/blox/portfolio/block.html +++ b/modules/blox/blox/portfolio/block.html @@ -277,9 +277,9 @@
{{ $image := $item.Resources.GetMatch "{featured,cover,thumbnail}*" }} {{ if $image }} - {{ if strings.Contains $image.MediaType.SubType "svg" }} - {{ $item.Title }} diff --git a/modules/blox/blox/research-areas/block.html b/modules/blox/blox/research-areas/block.html index 6c9a6498..39fddae6 100644 --- a/modules/blox/blox/research-areas/block.html +++ b/modules/blox/blox/research-areas/block.html @@ -211,7 +211,9 @@ {{ $img := $image_resource }} {{ if $img }} - {{ $img = $img.Fill "600x400 Center webp" }} + {{ if reflect.IsImageResourceProcessable $img }} + {{ $img = $img.Fill "600x400 Center webp" }} + {{ end }} {{ $alt := $name | plainify }} {{ $header_content = printf "\"%s\"" $img.RelPermalink $alt }} {{ else if $image_url }} diff --git a/modules/blox/blox/resume-biography-3/block.html b/modules/blox/blox/resume-biography-3/block.html index c24713df..a8fd6558 100644 --- a/modules/blox/blox/resume-biography-3/block.html +++ b/modules/blox/blox/resume-biography-3/block.html @@ -131,10 +131,10 @@ {{ $img = $image_resource }} {{ if $img }} - {{ $isSVG := eq $img.MediaType.SubType "svg" }} - {{ $isGIF := eq $img.MediaType.SubType "gif" }} + {{/* reflect.IsImageResourceProcessable → false for SVG; GIF skipped to preserve animation */}} + {{ $canProcess := and (reflect.IsImageResourceProcessable $img) (ne $img.MediaType.SubType "gif") }}
- {{ if not (or $isSVG $isGIF) }} + {{ if $canProcess }} {{ $responsive := partial "functions/process_responsive_image.html" (dict "image" $img "mode" "fill" @@ -147,7 +147,7 @@ src="{{ $responsive.fallback.RelPermalink }}" alt="" /> {{ else }} - {{ if not $isSVG }}{{ $img = $img.Process "webp" }}{{ end }} + {{/* SVG or GIF: pass through without responsive processing */}} diff --git a/modules/blox/blox/resume-biography/block.html b/modules/blox/blox/resume-biography/block.html index a90fa2d8..4a8b88ab 100644 --- a/modules/blox/blox/resume-biography/block.html +++ b/modules/blox/blox/resume-biography/block.html @@ -103,9 +103,9 @@ {{ $img = $image_resource }} {{ if $img }} - {{ $isSVG := eq $img.MediaType.SubType "svg" }} - {{ $isGIF := eq $img.MediaType.SubType "gif" }} - {{ if not (or $isSVG $isGIF) }} + {{/* reflect.IsImageResourceProcessable → false for SVG; GIF skipped to preserve animation */}} + {{ $canProcess := and (reflect.IsImageResourceProcessable $img) (ne $img.MediaType.SubType "gif") }} + {{ if $canProcess }} {{ $responsive := partial "functions/process_responsive_image.html" (dict "image" $img "mode" "fill" @@ -126,8 +126,7 @@
{{ else }} - {{/* Handle SVG/GIF without processing */}} - {{ if not $isSVG }}{{ $img = $img.Process "webp" }}{{ end }} + {{/* SVG or GIF: pass through without responsive processing */}}
diff --git a/modules/blox/layouts/_markup/render-image.html b/modules/blox/layouts/_markup/render-image.html index 09e9b1f8..c0a2492f 100644 --- a/modules/blox/layouts/_markup/render-image.html +++ b/modules/blox/layouts/_markup/render-image.html @@ -27,9 +27,13 @@
{{- if $img -}} - {{- $isSVG := eq $img.MediaType.SubType "svg" -}} - {{- $isGIF := eq $img.MediaType.SubType "gif" -}} - {{- if $isSVG | or $isGIF -}} + {{/* + Image processing gate: + - reflect.IsImageResourceProcessable → false for SVG (not processable by Hugo). + - GIF: processable by Hugo but we skip to preserve animation frames. + */}} + {{- $canProcess := and (reflect.IsImageResourceProcessable $img) (ne $img.MediaType.SubType "gif") -}} + {{- if not $canProcess -}} {{ $alt }} {{/* Cover Image */}}
- {{ if ne $cover.MediaType.SubType "gif" }} - {{ if $avatar }} - {{ $isSVG := eq $avatar.MediaType.SubType "svg" }} - {{ if $isSVG }} + {{ if not (reflect.IsImageResourceProcessable $avatar) }} diff --git a/modules/blox/layouts/_partials/views/card.html b/modules/blox/layouts/_partials/views/card.html index 51169347..46e8ac12 100644 --- a/modules/blox/layouts/_partials/views/card.html +++ b/modules/blox/layouts/_partials/views/card.html @@ -36,17 +36,23 @@ {{ end }} {{ end }} {{ with $resource }} - {{ if strings.Contains .MediaType.SubType "svg" }} + {{/* + Image processing gate: + - reflect.IsImageResourceProcessable → false for SVG (not processable by Hugo). + - GIF: processable by Hugo but we skip to preserve animation frames. + */}} + {{ $canProcess := and (reflect.IsImageResourceProcessable .) (ne .MediaType.SubType "gif") }} + {{ if not $canProcess }} - {{ $item.Title | plainify }} featured image - {{ else if ne .MediaType.SubType "gif" }} + {{ else }} {{ $original_image := "" }} {{ if $fill_image }} {{ $original_image = .Fill (printf "800x450 %s" $anchor) }} @@ -72,17 +78,6 @@ style="position: absolute; height: 100%; width: 100%; inset: 0px; color: transparent;" alt="{{ $item.Title | plainify }} featured image"> - {{ else }} - - {{ $item.Title | plainify }} featured image - {{ end }} {{ else }} diff --git a/modules/blox/layouts/_partials/views/slides-gallery.html b/modules/blox/layouts/_partials/views/slides-gallery.html index 25dab71f..f84f3401 100644 --- a/modules/blox/layouts/_partials/views/slides-gallery.html +++ b/modules/blox/layouts/_partials/views/slides-gallery.html @@ -59,39 +59,34 @@ {{/* Slide Preview Image - 16:9 aspect ratio */}} {{ with $resource }} - {{ if strings.Contains .MediaType.SubType "svg" }} - {{ $item.Title | plainify }} - {{ else if ne .MediaType.SubType "gif" }} - {{ $image := .Fill "800x450 Center" }} - {{ $responsive := partial "functions/process_responsive_image.html" (dict - "image" $image - "mode" "responsive" - "sizes" (slice 400 600 800) - "formats" (slice "avif" "webp" "jpg") - ) }} - {{ $item.Title | plainify }} - {{ else }} - {{ $item.Title | plainify }} + {{ else }} + {{ $image := .Fill "800x450 Center" }} + {{ $responsive := partial "functions/process_responsive_image.html" (dict + "image" $image + "mode" "responsive" + "sizes" (slice 400 600 800) + "formats" (slice "avif" "webp" "jpg") + ) }} + {{ $item.Title | plainify }} {{ end }} {{ else }} {{/* Fallback: Decorative gradient with slide icon - smaller, cleaner */}} diff --git a/modules/blox/layouts/authors/term.html b/modules/blox/layouts/authors/term.html index 247a18fa..42ea452b 100644 --- a/modules/blox/layouts/authors/term.html +++ b/modules/blox/layouts/authors/term.html @@ -9,8 +9,7 @@
{{ if $avatar }} - {{ $isSVG := eq $avatar.MediaType.SubType "svg" }} - {{ if $isSVG }} + {{ if not (reflect.IsImageResourceProcessable $avatar) }}
{{ . | markdownify | emojify }}{{ end }}
- {{ else if ne $featured.MediaType.SubType "gif" }} + {{ else }} {{/* Pre-fit image to placement constraints, then apply responsive processing */}} {{ $fitted_image := $featured }} {{ if eq $placement 2}} @@ -215,27 +220,6 @@ {{ with $image_params.caption }}{{ . | markdownify | emojify }}{{ end }}
- {{ else }} - {{/* Handle GIF - apply same placement constraints without responsive processing */}} - {{ $image := $featured }} - {{ if eq $placement 2}} - {{ $image = $featured.Fit "1200x2500" }} - {{else if eq $placement 3}} - {{ $image = $featured.Fit "2560x2560" }} - {{else}} - {{ $image = $featured.Fit "720x2500" }} - {{end}} -
-
- {{ with $image_params.alt_text }}{{.}}{{ end }} - {{ with $image_params.caption }}{{ . | markdownify | emojify }}{{ end }} -
-
{{ end }} {{end}} {{ end }} diff --git a/modules/blox/layouts/single.html b/modules/blox/layouts/single.html index ad1bbb8c..e5410d84 100644 --- a/modules/blox/layouts/single.html +++ b/modules/blox/layouts/single.html @@ -177,9 +177,14 @@ {{ $sizes_attr = "(max-width: 480px) 100vw, (max-width: 640px) 90vw, (max-width: 720px) 80vw, 720px" }} {{end}} - {{/* Process featured image using utility with placement-specific constraints */}} - {{ if strings.Contains $featured.MediaType.SubType "svg" }} - {{/* SVGs cannot be processed by Hugo (Fit/Fill). Render directly. */}} + {{/* + Image processing gate: + - reflect.IsImageResourceProcessable → false for SVG (not processable by Hugo). + - GIF: processable by Hugo but we skip to preserve animation frames. + */}} + {{ $canProcess := and (reflect.IsImageResourceProcessable $featured) (ne $featured.MediaType.SubType "gif") }} + {{ if not $canProcess }} + {{/* SVG or GIF: render directly without responsive processing */}}
{{ . | markdownify | emojify }}{{ end }}
- {{ else if ne $featured.MediaType.SubType "gif" }} + {{ else }} {{/* Pre-fit image to placement constraints, then apply responsive processing */}} {{ $fitted_image := $featured }} {{ if eq $placement 2}} @@ -222,28 +227,6 @@ {{ with $image_params.caption }}{{ . | markdownify | emojify }}{{ end }}
- {{ else }} - {{/* Handle GIF - apply same placement constraints without responsive processing */}} - {{ $image := $featured }} - {{ if eq $placement 2}} - {{ $image = $featured.Fit "1200x2500" }} - {{else if eq $placement 3}} - {{ $image = $featured.Fit "2560x2560" }} - {{else}} - {{ $image = $featured.Fit "720x2500" }} - {{end}} -
-
- {{ with $image_params.alt_text }}{{.}}{{ end }} - {{ with $image_params.caption }}{{ . | markdownify | emojify }}{{ end }} -
-
{{ end }} {{end}}