From 01b768d33cb2e4e3be5162e769f087f9a0f2a300 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 18 Dec 2025 16:49:04 +0100 Subject: [PATCH 1/3] fix(thumbnailer): respect image boundaries and text wrappings for TxtToImageConverter string drawing --- .../pkg/preprocessor/preprocessor.go | 91 +++++++++++-------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/services/thumbnails/pkg/preprocessor/preprocessor.go b/services/thumbnails/pkg/preprocessor/preprocessor.go index f24b4f80db..81c6bee6ae 100644 --- a/services/thumbnails/pkg/preprocessor/preprocessor.go +++ b/services/thumbnails/pkg/preprocessor/preprocessor.go @@ -20,6 +20,7 @@ import ( "golang.org/x/image/math/fixed" "github.com/dhowden/tag" + thumbnailerErrors "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" ) @@ -156,15 +157,18 @@ Scan: // Label for the scanner loop, so we can break it easily if canvas.Dot.Y > maxY { break Scan } - drawWord(canvas, textResult.Text[initialByte:sRangeSpace], minX, maxX, height, maxY, true) + + drawWord(canvas, textResult.Text[initialByte:sRangeSpace], minX, maxX, height, maxY) initialByte = sRangeSpace } + if initialByte <= sRange.High { // some bytes left to be written if canvas.Dot.Y > maxY { break Scan } - drawWord(canvas, textResult.Text[initialByte:sRange.High+1], minX, maxX, height, maxY, len(sRange.Spaces) > 0) + + drawWord(canvas, textResult.Text[initialByte:sRange.High+1], minX, maxX, height, maxY) } } @@ -235,43 +239,58 @@ func extractBase64ImageFromGGP(ggp *GGPStruct) (string, error) { // need to draw the word in a new line // // Note that the word will likely start with a white space char -func drawWord(canvas *font.Drawer, word string, minX, maxX, incY, maxY fixed.Int26_6, goToNewLine bool) { - bbox, _ := canvas.BoundString(word) - if bbox.Max.X <= maxX { - // word fits in the current line - canvas.DrawString(word) - } else { - // word doesn't fit -> retry in a new line - trimmedWord := strings.TrimSpace(word) - oldDot := canvas.Dot +func drawWord(canvas *font.Drawer, word string, minX, maxX, incY, maxY fixed.Int26_6) { + // calculate the actual measurement of the string at a given X position + measure := func(s string, dotX fixed.Int26_6) (min, max fixed.Int26_6) { + bbox, _ := canvas.BoundString(s) + return dotX + bbox.Min.X, dotX + bbox.Max.X + } - canvas.Dot.X = minX - canvas.Dot.Y += incY - bbox2, _ := canvas.BoundString(trimmedWord) - if goToNewLine && bbox2.Max.X <= maxX { - if canvas.Dot.Y > maxY { - // Don't draw if we're over the Y limit - return - } - canvas.DrawString(trimmedWord) - } else { - // word doesn't fit in a new line -> draw as many chars as possible - canvas.Dot = oldDot - for _, char := range trimmedWord { - charBytes := []byte(string(char)) - bbox3, _ := canvas.BoundBytes(charBytes) - if bbox3.Max.X > maxX { - canvas.Dot.X = minX - canvas.Dot.Y += incY - if canvas.Dot.Y > maxY { - // Don't draw if we're over the Y limit - return - } - } - canvas.DrawBytes(charBytes) - } + // first try to draw the whole word + absMin, absMax := measure(word, canvas.Dot.X) + if absMin >= minX && absMax <= maxX { + canvas.DrawString(word) + return + } + + // try to draw the trimmed word in a new line + trimmed := strings.TrimSpace(word) + oldDot := canvas.Dot + canvas.Dot.X = minX + canvas.Dot.Y += incY + + if canvas.Dot.Y <= maxY { + tMin, tMax := measure(trimmed, canvas.Dot.X) + if tMin >= minX && tMax <= maxX { + canvas.DrawString(trimmed) + return } } + + // if the trimmed word is still too big, draw it char by char + canvas.Dot = oldDot + for _, char := range trimmed { + s := string(char) + _, cMax := measure(s, canvas.Dot.X) + + if cMax > maxX { + canvas.Dot.X = minX + canvas.Dot.Y += incY + } + + // stop drawing if we exceed maxY + if canvas.Dot.Y > maxY { + return + } + + // ensure that we don't start drawing before minX + cMin, _ := measure(s, canvas.Dot.X) + if cMin < minX { + canvas.Dot.X += minX - cMin + } + + canvas.DrawString(s) + } } // ForType returns the converter for the specified mimeType From 50ef4f2ef1b552e753cd0514acc29c907fdefe9a Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 18 Dec 2025 14:03:49 +0100 Subject: [PATCH 2/3] fix: propagate the current edition channel in the edition validation error --- pkg/version/version.go | 7 +++++-- pkg/version/version_test.go | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/version/version.go b/pkg/version/version.go index d7aed0934a..3bbf55506f 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -79,8 +79,11 @@ func initEdition() error { _, err := semver.NewVersion(editionParts[1]) return err == nil }) { - Edition = Dev - return fmt.Errorf(`unknown edition channel "%s"`, Edition) + defer func() { + Edition = Dev + }() + + return fmt.Errorf(`unknown edition channel '%s'`, Edition) } return nil diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go index 217a5099bf..080021064e 100644 --- a/pkg/version/version_test.go +++ b/pkg/version/version_test.go @@ -2,6 +2,7 @@ package version_test import ( "fmt" + "strings" "testing" "github.com/opencloud-eu/opencloud/pkg/version" @@ -59,6 +60,8 @@ func TestChannel(t *testing.T) { fallthrough case test.valid != (err == nil): t.Fatalf("invalid edition: %s", version.Edition) + case !test.valid && !strings.Contains(err.Error(), "'"+test.got+"'"): + t.Fatalf("no mention of invalid edition '%s' in error: %s", test.got, err.Error()) } }) } From dd4b36a4bbf6272fd8de305104f68b066d79ef8a Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 18 Dec 2025 14:04:54 +0100 Subject: [PATCH 3/3] fix(oci): set EDITION ard default value to dev which suppresses the init edition startup error --- opencloud/docker/Dockerfile.multiarch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencloud/docker/Dockerfile.multiarch b/opencloud/docker/Dockerfile.multiarch index 0d859da151..7fc4dec880 100644 --- a/opencloud/docker/Dockerfile.multiarch +++ b/opencloud/docker/Dockerfile.multiarch @@ -3,7 +3,7 @@ ARG TARGETOS ARG TARGETARCH ARG VERSION ARG STRING -ARG EDITION +ARG EDITION="dev" RUN apk add bash make git curl gcc musl-dev libc-dev binutils-gold inotify-tools vips-dev