Merge pull request #2062 from fschade/fix-issue-1452-respect-canvas-boundaries

fix(thumbnailer): respect image boundaries and text wrappings
This commit is contained in:
Florian Schade
2025-12-23 12:22:33 +01:00
committed by GitHub

View File

@@ -20,6 +20,7 @@ import (
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
"github.com/dhowden/tag" "github.com/dhowden/tag"
thumbnailerErrors "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/errors" 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 { if canvas.Dot.Y > maxY {
break Scan 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 initialByte = sRangeSpace
} }
if initialByte <= sRange.High { if initialByte <= sRange.High {
// some bytes left to be written // some bytes left to be written
if canvas.Dot.Y > maxY { if canvas.Dot.Y > maxY {
break Scan 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 // need to draw the word in a new line
// //
// Note that the word will likely start with a white space char // 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) { func drawWord(canvas *font.Drawer, word string, minX, maxX, incY, maxY fixed.Int26_6) {
bbox, _ := canvas.BoundString(word) // calculate the actual measurement of the string at a given X position
if bbox.Max.X <= maxX { measure := func(s string, dotX fixed.Int26_6) (min, max fixed.Int26_6) {
// word fits in the current line bbox, _ := canvas.BoundString(s)
canvas.DrawString(word) return dotX + bbox.Min.X, dotX + bbox.Max.X
} else { }
// word doesn't fit -> retry in a new line
trimmedWord := strings.TrimSpace(word)
oldDot := canvas.Dot
canvas.Dot.X = minX // first try to draw the whole word
canvas.Dot.Y += incY absMin, absMax := measure(word, canvas.Dot.X)
bbox2, _ := canvas.BoundString(trimmedWord) if absMin >= minX && absMax <= maxX {
if goToNewLine && bbox2.Max.X <= maxX { canvas.DrawString(word)
if canvas.Dot.Y > maxY { return
// Don't draw if we're over the Y limit }
return
} // try to draw the trimmed word in a new line
canvas.DrawString(trimmedWord) trimmed := strings.TrimSpace(word)
} else { oldDot := canvas.Dot
// word doesn't fit in a new line -> draw as many chars as possible canvas.Dot.X = minX
canvas.Dot = oldDot canvas.Dot.Y += incY
for _, char := range trimmedWord {
charBytes := []byte(string(char)) if canvas.Dot.Y <= maxY {
bbox3, _ := canvas.BoundBytes(charBytes) tMin, tMax := measure(trimmed, canvas.Dot.X)
if bbox3.Max.X > maxX { if tMin >= minX && tMax <= maxX {
canvas.Dot.X = minX canvas.DrawString(trimmed)
canvas.Dot.Y += incY return
if canvas.Dot.Y > maxY {
// Don't draw if we're over the Y limit
return
}
}
canvas.DrawBytes(charBytes)
}
} }
} }
// 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 // ForType returns the converter for the specified mimeType