fogleman / gg

Go Graphics - 2D rendering in Go with a simple API.
https://godoc.org/github.com/fogleman/gg
MIT License
4.37k stars 354 forks source link

Measure DrawWrappedString #102

Open z-br opened 4 years ago

z-br commented 4 years ago

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

naikrovek commented 4 years ago

Does MeasureMultilineString not give you what you want?

Also see MeasureString

z-br commented 4 years ago

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

z-br commented 4 years ago

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 commented 4 years ago

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.

elboletaire commented 6 months ago

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 (?)