gg icon indicating copy to clipboard operation
gg copied to clipboard

Measure DrawWrappedString

Open z-br opened this issue 5 years ago • 5 comments

Any way to measure a wrapped string before drawing? I'd like to reduce font size until it's smaller than Y.

z-br avatar Jul 04 '20 20:07 z-br

Does MeasureMultilineString not give you what you want?

Also see MeasureString

naikrovek avatar Jul 13 '20 14:07 naikrovek

@naikrovek It looks like I can combine lines := dc.WordWrap(s, width) with MeasureMultilineString and get what I want. Thanks!

z-br avatar Aug 08 '20 04:08 z-br

I got this working how I wanted! Thanks for the pointer. For posterity, here is my full code for generating a fixed size image with a multiline string, with that string vertically centered and left aligned. It also does a loop to find a font size that fits the width.

package imagebuilder

import (
	"bytes"
	"image/color"

	"github.com/gregpassmore/cookthebooks/kitchen/env"

	"github.com/fogleman/gg"
)

const (
	coverWidth   = 480
	coverHeight  = 640
	coverPadding = 16
)

func BuildCoverImage(bookTitle string) ([]byte, error) {
	const W = coverWidth
	const H = coverHeight
	const P = coverPadding
	dc := gg.NewContext(W, H)
	bgColor := chooseBackgroundColor()
	dc.SetColor(bgColor)
	dc.DrawRectangle(0, 0, W, H)
	dc.Fill()
	dc.SetRGB(0, 0, 0)
	font := chooseFont()

	var yPad float64 = P
	var fontSize float64 = 96
	var width float64 = W - (P * 2)
	lineSpacing := 1.5
	for {
		if fontSize < 32 {
			break
		}
		if err := dc.LoadFontFace(font, fontSize); err != nil {
			panic(err)
		}

		stringLines := dc.WordWrap(bookTitle, width)
		mls := ""
		for i, sl := range stringLines {
			mls = mls + sl
			if i != len(stringLines)-1 {
				mls = mls + "\n"
			}
		}
		sw, sh := dc.MeasureMultilineString(mls, lineSpacing)
		verticalSpace := H - sh

		if verticalSpace/2 > P {
			yPad = verticalSpace / 2
		}
		if sw < W-(2*P) {
			break
		}
		fontSize = fontSize - 8
	}

	dc.DrawStringWrapped(bookTitle, P, yPad, 0.0, 0.0, width, lineSpacing, gg.AlignLeft)
	buffer := bytes.Buffer{}
	err := dc.EncodePNG(&buffer)
	if err != nil {
		return nil, err
	}
	return buffer.Bytes(), nil
}

func chooseFont() string {
	return env.ProjectDir.Get() + "/resources/fonts/BebasNeue-Regular.ttf"
}

func chooseBackgroundColor() color.Color {
	return color.RGBA{R: 255, G: 182, B: 193, A: 255}
}

z-br avatar Aug 08 '20 04:08 z-br

It might be nice to provide a function that does the "MeasureWrappedString" since there is a draw func for it, its a bit of a dance to get it done as-is. I could do a PR if there is interest in this.

z-br avatar Aug 08 '20 04:08 z-br

It might be nice to provide a function that does the "MeasureWrappedString" since there is a draw func for it, its a bit of a dance to get it done as-is. I could do a PR if there is interest in this.

There's interest, but I guess it's a bit late already (?)

elboletaire avatar Feb 17 '24 20:02 elboletaire