From cbc7400f92abed12f053bf85dff3e8d0dc8de6ab Mon Sep 17 00:00:00 2001
From: Johan Fagerberg
Date: Thu, 19 Feb 2026 10:24:26 +0100
Subject: [PATCH] feat: align image rendering with Confluence default
---
renderer/fencedcodeblock.go | 44 ++++++++++++-------
renderer/image.go | 79 +++++++++++++++++++++++++++-------
renderer/image_test.go | 84 +++++++++++++++++++++++++++++++++++++
stdlib/stdlib.go | 5 ++-
testdata/links.html | 2 +-
5 files changed, 184 insertions(+), 30 deletions(-)
create mode 100644 renderer/image_test.go
diff --git a/renderer/fencedcodeblock.go b/renderer/fencedcodeblock.go
index 064b099..3763d35 100644
--- a/renderer/fencedcodeblock.go
+++ b/renderer/fencedcodeblock.go
@@ -141,22 +141,30 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
r.Attachments.Attach(attachment)
effectiveAlign := calculateAlign(r.MarkConfig.ImageAlign, attachment.Width)
+ effectiveLayout := calculateLayout(effectiveAlign, attachment.Width)
+ displayWidth := calculateDisplayWidth(attachment.Width, effectiveLayout)
err = r.Stdlib.Templates.ExecuteTemplate(
writer,
"ac:image",
struct {
- Align string
- Width string
- Height string
- Title string
- Alt string
- Attachment string
- Url string
+ Align string
+ Layout string
+ OriginalWidth string
+ OriginalHeight string
+ Width string
+ Height string
+ Title string
+ Alt string
+ Attachment string
+ Url string
}{
effectiveAlign,
+ effectiveLayout,
attachment.Width,
attachment.Height,
+ displayWidth,
+ attachment.Height,
attachment.Name,
"",
attachment.Filename,
@@ -177,22 +185,30 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu
r.Attachments.Attach(attachment)
effectiveAlign := calculateAlign(r.MarkConfig.ImageAlign, attachment.Width)
+ effectiveLayout := calculateLayout(effectiveAlign, attachment.Width)
+ displayWidth := calculateDisplayWidth(attachment.Width, effectiveLayout)
err = r.Stdlib.Templates.ExecuteTemplate(
writer,
"ac:image",
struct {
- Align string
- Width string
- Height string
- Title string
- Alt string
- Attachment string
- Url string
+ Align string
+ Layout string
+ OriginalWidth string
+ OriginalHeight string
+ Width string
+ Height string
+ Title string
+ Alt string
+ Attachment string
+ Url string
}{
effectiveAlign,
+ effectiveLayout,
attachment.Width,
attachment.Height,
+ displayWidth,
+ attachment.Height,
attachment.Name,
"",
attachment.Filename,
diff --git a/renderer/image.go b/renderer/image.go
index 52d08a2..6eb0799 100644
--- a/renderer/image.go
+++ b/renderer/image.go
@@ -40,6 +40,43 @@ func calculateAlign(configuredAlign string, width string) string {
return configuredAlign
}
+// calculateLayout determines the appropriate ac:layout value based on alignment and width
+// Images >= 1800px use "full-width", otherwise based on alignment
+func calculateLayout(align string, width string) string {
+ // Check if full-width should be used
+ if width != "" {
+ widthInt, err := strconv.Atoi(width)
+ if err == nil && widthInt >= 1800 {
+ return "full-width"
+ }
+ }
+
+ // Otherwise use layout based on alignment
+ switch align {
+ case "left":
+ return "align-start"
+ case "center":
+ return "center"
+ case "right":
+ return "align-end"
+ case "wide":
+ return "center"
+ default:
+ return ""
+ }
+}
+
+// calculateDisplayWidth determines the display width
+// Full-width layout uses 1800px, otherwise uses original width
+func calculateDisplayWidth(originalWidth string, layout string) string {
+ if layout == "full-width" {
+ return "1800"
+ }
+ return originalWidth
+}
+
+
+
type ConfluenceImageRenderer struct {
html.Config
Stdlib *stdlib.Lib
@@ -82,15 +119,21 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
writer,
"ac:image",
struct {
- Align string
- Width string
- Height string
- Title string
- Alt string
- Attachment string
- Url string
+ Align string
+ Layout string
+ OriginalWidth string
+ OriginalHeight string
+ Width string
+ Height string
+ Title string
+ Alt string
+ Attachment string
+ Url string
}{
r.ImageAlign,
+ calculateLayout(r.ImageAlign, ""),
+ "",
+ "",
"",
"",
string(n.Title),
@@ -104,22 +147,30 @@ func (r *ConfluenceImageRenderer) renderImage(writer util.BufWriter, source []by
r.Attachments.Attach(attachments[0])
effectiveAlign := calculateAlign(r.ImageAlign, attachments[0].Width)
+ effectiveLayout := calculateLayout(effectiveAlign, attachments[0].Width)
+ displayWidth := calculateDisplayWidth(attachments[0].Width, effectiveLayout)
err = r.Stdlib.Templates.ExecuteTemplate(
writer,
"ac:image",
struct {
- Align string
- Width string
- Height string
- Title string
- Alt string
- Attachment string
- Url string
+ Align string
+ Layout string
+ OriginalWidth string
+ OriginalHeight string
+ Width string
+ Height string
+ Title string
+ Alt string
+ Attachment string
+ Url string
}{
effectiveAlign,
+ effectiveLayout,
attachments[0].Width,
attachments[0].Height,
+ displayWidth,
+ attachments[0].Height,
string(n.Title),
string(nodeToHTMLText(n, source)),
attachments[0].Filename,
diff --git a/renderer/image_test.go b/renderer/image_test.go
new file mode 100644
index 0000000..168ffc5
--- /dev/null
+++ b/renderer/image_test.go
@@ -0,0 +1,84 @@
+package renderer
+
+import "testing"
+
+func TestCalculateAlign(t *testing.T) {
+ tests := []struct {
+ name string
+ configuredAlign string
+ width string
+ expectedAlign string
+ }{
+ {"No alignment configured", "", "1000", ""},
+ {"No width available", "center", "", "center"},
+ {"Below threshold", "center", "500", "center"},
+ {"At threshold", "center", "760", "wide"},
+ {"Above threshold", "center", "1000", "wide"},
+ {"Left below threshold", "left", "700", "left"},
+ {"Left at threshold", "left", "760", "wide"},
+ {"Invalid width", "center", "abc", "center"},
+ {"Large image", "center", "2000", "wide"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := calculateAlign(tt.configuredAlign, tt.width)
+ if result != tt.expectedAlign {
+ t.Errorf("calculateAlign(%q, %q) = %q, want %q", tt.configuredAlign, tt.width, result, tt.expectedAlign)
+ }
+ })
+ }
+}
+
+func TestCalculateLayout(t *testing.T) {
+ tests := []struct {
+ name string
+ align string
+ width string
+ expectedLayout string
+ }{
+ {"Left alignment", "left", "500", "align-start"},
+ {"Center alignment", "center", "500", "center"},
+ {"Right alignment", "right", "500", "align-end"},
+ {"Wide alignment", "wide", "1000", "center"},
+ {"Full-width threshold", "center", "1800", "full-width"},
+ {"Above full-width", "left", "2000", "full-width"},
+ {"Below full-width", "center", "1799", "center"},
+ {"No alignment", "", "1000", ""},
+ {"Unknown alignment", "justify", "500", ""},
+ {"Invalid width", "center", "abc", "center"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := calculateLayout(tt.align, tt.width)
+ if result != tt.expectedLayout {
+ t.Errorf("calculateLayout(%q, %q) = %q, want %q", tt.align, tt.width, result, tt.expectedLayout)
+ }
+ })
+ }
+}
+
+func TestCalculateDisplayWidth(t *testing.T) {
+ tests := []struct {
+ name string
+ originalWidth string
+ layout string
+ expectedWidth string
+ }{
+ {"Full-width layout", "2000", "full-width", "1800"},
+ {"Center layout keeps original", "1000", "center", "1000"},
+ {"Align-start keeps original", "800", "align-start", "800"},
+ {"Empty original", "", "center", ""},
+ {"Empty layout", "1000", "", "1000"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := calculateDisplayWidth(tt.originalWidth, tt.layout)
+ if result != tt.expectedWidth {
+ t.Errorf("calculateDisplayWidth(%q, %q) = %q, want %q", tt.originalWidth, tt.layout, result, tt.expectedWidth)
+ }
+ })
+ }
+}
diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go
index a22665b..fbe1327 100644
--- a/stdlib/stdlib.go
+++ b/stdlib/stdlib.go
@@ -212,7 +212,10 @@ func templates(api *confluence.API) (*template.Template, error) {
`ac:image`: text(
`Use
Use
Use
-
+