rpgtextbox

package module
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 12 Imported by: 0

README

Golang RPG Textbox

This is a "simple" library / cli application to generate animated and static RPG style text boxes from a theme and text.

The library uses:

In order to use this library you will need:

  • To know what font you will be using
  • To have your own theme

Usage

Requirement: A theme

You need to setup a theme, that can can be done using something that adheres to the interfaces (found theme/interface.go):

type Theme interface {
	Chevron() image.Image
	Avatar() image.Image
	FontFace() font.Face
	FontDrawer() *font.Drawer
}

type Frame interface {
	Frame() image.Image
	FrameCenter() image.Rectangle
}

Where frame is defined by the requirements for: https://github.com/arran4/golang-frame

For an example implementation of a theme checkout the contents of the theme/*/ directories.

Using the library

First off you need to construct the *TextBox object:

tb, err := rpgtextbox.NewSimpleTextBox(theme, text, image.Pt(width, height), ops...)

Ops will be discussed later.

There are 2 ways of using the library, one for animations and one for just the frame.

Just the frame:

To just generate the frame, you call the function DrawNextPageFrame on a *TextBox object.

            tb, err := rpgtextbox.NewSimpleTextBox(theme, text, image.Pt(width, height), ops...)
                if err != nil {
                log.Panicf("Error %s", err)
            }
			i := image.NewRGBA(image.Rect(0, 0, *width, *height))
			if _, err := tb.DrawNextPageFrame(i); err != nil {
				log.Panicf("Draw next frame error: %s", err)
			}
			if err := util.SavePngFile(i, "out.png"); err != nil {
				log.Panicf("Error with saving file: %s", err)
			}
			log.Printf("Saving %s", ofn)

The core function is:

func (tb *TextBox) DrawNextPageFrame(target wordwrap.Image, opts ...wordwrap.DrawOption) (bool, error)

The target is where it will draw the image. It will attempt to consume all the space available, so if you need it to only target a smaller portion of it, be sure to use the SubImage function like so:

offset := image.Pt(344,100)
target.SubImage(image.Rect(0, 0, *width, *height).Add(offset)).(wordwrap.Image)

This will just generate one frame, the first page. Each time you call the function it will produce the next page of data. (Ie the next text box / speech dialog.)

An animation

You can also use it to generate an animation, this can be wrapped up in your harness any way you like.

DrawNextFrame works very similar to the DrawNextPageFrame function except that it also returns a WaitTime and a UserInput.

WaitTime is the time before the next animation frame.

UserInput is if the animation has finished and that you should call the next frame generation to ensure smooth rendering. However, you do not need to, you can call in any sequence.

In order for DrawNextFrame to work you must specify an animation, see the option section below for a list. Or read the code directly.

Example:

            ops = some options and an animation option
            tb, err := rpgtextbox.NewSimpleTextBox(theme, text, image.Pt(width, height), ops...)
            if err != nil {
                log.Panicf("Error %s", err)
            }
            for {
                i := image.NewRGBA(image.Rect(0, 0, width, height))
                if done, ui, w, err := tb.rtb.DrawNextFrame(i); err != nil {
                    log.Panicf("Draw next frame error: %s", err)
                } else if done && !ui && w <= 0 {
					// The whole thing is done and there is no input
                    break
                } else if ui {
					// We are awaiting user input
                    break
                } else {
                    if w <= 0 {
                        w = time.Second / 2
                    }
                    f++
                    if ui && w <= 0 {
                        page++
                    }
                    log.Printf("%s: Adding frame %d for page %d", tb.Filename, f, page)
                    bounds := i.Bounds()
                    palettedImage := image.NewPaletted(bounds, palette.Plan9)
                    draw.Draw(palettedImage, palettedImage.Rect, i, bounds.Min, draw.Over)
                    gifo.Image = append(gifo.Image, palettedImage) // add the image to a gif
                    gifo.Delay = append(gifo.Delay, int(w/(time.Second/100))) // add the wait time too
                }
            }
            log.Printf("Saving %s", ofn)
            if err := util.SaveGifFile(ofn, gifo); err != nil {
                log.Panicf("Error with saving file: %s", err)
            }
            log.Printf("Saved %s", ofn)

Use it as CLI application

Download it from the releases tab, or compile it yourself using Go. Once you have built it you can run rpgtextbox with the following flags:

rpgtextbox.exe:
  -animation string
    	Use help for list
  -avatar-pos string
    	Use help for list
  -avatar-scale string
    	Use help for list
  -chevron string
    	Use help for list
  -dpi float
    	Doc dpi (default 75)
  -font string
    	Text font (default "goregular")
  -height int
    	Doc height (default 150)
  -out string
    	Prefix of filename to output (default "out-")
  -size float
    	font size (default 16)
  -text string
    	File in, or - for std input
  -themedir string
    	Directory to find the theme (default "./theme")
  -width int
    	Doc width (default 600)

If the arguments are successful it will create the contents in location/filename specified in out-prefix.

Options

There are a bunch of options, options are used in the following way:

    tb, err := rpgtextbox.NewSimpleTextBox(theme, text, image.Pt(width, height), rpgtextbox.LeftAvatar, rpgtextbox.CenterAvatar)
    if err != nil {
        log.Panicf("Error %s", err)
    }

Chevron Location Options

Option Example Image
rpgtextbox.CenterBottomInsideTextFrame
rpgtextbox.CenterBottomInsideFrame
rpgtextbox.CenterBottomOnFrameTextFrame
rpgtextbox.CenterBottomOnFrameFrame
rpgtextbox.RightBottomInsideTextFrame
rpgtextbox.RightBottomInsideFrame
rpgtextbox.RightBottomOnFrameTextFrame
rpgtextbox.RightBottomOnFrameFrame
rpgtextbox.TextEndChevron

Avatar Location Options

Option Example Image
rpgtextbox.LeftAvatar
rpgtextbox.RightAvatar

Avatar Scaling Options

Option Example Image
rpgtextbox.CenterAvatar
rpgtextbox.NearestNeighbour
rpgtextbox.ApproxBiLinear

Animation Options

Option Example Image
rpgtextbox.NewFadeAnimation()
rpgtextbox.NewBoxByBoxAnimation()
rpgtextbox.NewLetterByLetterAnimation()

Other options

Option Example Image / Description
rpgtextbox.Avatar(i image.Image) Replace the theme avatar for a special purpose avatar
rpgtextbox.Name(name string)
rpgtextbox.Name(name string), rpgtextbox.NameTopLeftAboveTextInFrame
rpgtextbox.Name(name string), rpgtextbox.NameTopCenterInFrame
rpgtextbox.Name(name string), rpgtextbox.NameLeftAboveAvatarInFrame

License

TBH I really haven't thought about it. Contact me

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BoxTextBox

func BoxTextBox() *boxTextBox

BoxTextBox creates a PostDrawer that draws a box around the text area.

func NewAlphaSourceImageMapper

func NewAlphaSourceImageMapper(i image.Image, multiplier float64) image.Image

NewAlphaSourceImageMapper Creates a proxy image which will provide a source

Types

type AlphaSourceImageMapper

type AlphaSourceImageMapper struct {
	// original image
	image.Image
	// Multiplier How much to "fade" it by
	Multiplier float64
}

AlphaSourceImageMapper is a draw.Image compatible source image, that allows an image to fade.

func (*AlphaSourceImageMapper) At

func (asim *AlphaSourceImageMapper) At(x, y int) color.Color

At a possible wrong implementation of fading a text box

type AnimationMode

type AnimationMode interface {
	// DrawOption draws with options.. Controls the drawing process to add extra frames and a delay to create an animation
	// finished is true if you're on the last page
	// userInputAccepted is if it's at the stage where you would typically accept user input (ie the animation is waiting
	// user input, doesn't imply anything to do with the animation
	// wait is either 0 or less, or the amount of time before the next animation phase
	// err is err
	// To determine if you're at the end the only way of doing it as of writing is to wait for; lastPage = true,
	// userInputAccepted = false, wait = -1
	DrawOption(target wordwrap.Image) (lastPage bool, userInputAccepted bool, wait time.Duration, err error)
	Option
}

AnimationMode interface defining the optional animation. Used as an Option

type AvatarFit

type AvatarFit int

AvatarFit defines how the avatar is scaled if it doesn't fit the allocated space.

const (
	// NoAvatarFit performs no scaling (undefined behavior if too large).
	NoAvatarFit AvatarFit = iota
	// CenterAvatar centers the avatar without scaling.
	CenterAvatar
	// NearestNeighbour scales using nearest-neighbor interpolation.
	NearestNeighbour
	// ApproxBiLinear scales using approximate bi-linear interpolation.
	ApproxBiLinear
)

type AvatarLocations

type AvatarLocations int

AvatarLocations defines where the avatar is positioned relative to the text box.

const (
	// NoAvatar hides the avatar.
	NoAvatar AvatarLocations = iota
	// LeftAvatar positions the avatar on the left.
	LeftAvatar
	// RightAvatar positions the avatar on the right.
	RightAvatar
)

type BoxByBoxAnimation

type BoxByBoxAnimation struct {

	// The function to calculate the wait time between each box
	WaitTimeFunc func(*BoxByBoxAnimation) time.Duration
	// contains filtered or unexported fields
}

BoxByBoxAnimation is an animation style in which each non-whitespace box comes into visibility one by one

func NewBoxByBoxAnimation

func NewBoxByBoxAnimation() *BoxByBoxAnimation

NewBoxByBoxAnimation creates an animation style where one block comes on at one time. Use WaitTimeFunc to create your own timing for each block

func (*BoxByBoxAnimation) DrawOption

func (byb *BoxByBoxAnimation) DrawOption(target wordwrap.Image) (finished bool, userInputAccepted bool, waitTime time.Duration, err error)

DrawOption draws with options.. Controls the drawing process to add extra frames, a wait time and more finished is true if you're on the last page userInputAccepted is if it's at the stage where you would typically accept user input (ie the animation is waiting user input, doesn't imply anything to do with the animation wait is either 0 or less, or the amount of time before the next animation phase err is err To determine if you're at the end the only way of doing it as of writing is to wait for; lastPage = true, userInputAccepted = false, wait = -1

type BoxShape added in v0.0.5

type BoxShape struct {
	wordwrap.Box
	Rect image.Rectangle
}

func (*BoxShape) Bounds added in v0.0.5

func (b *BoxShape) Bounds() image.Rectangle

func (*BoxShape) ID added in v0.0.5

func (b *BoxShape) ID() interface{}

func (*BoxShape) PointIn added in v0.0.5

func (b *BoxShape) PointIn(x, y int) bool

func (*BoxShape) String added in v0.0.5

func (b *BoxShape) String() string

type FadeAnimation

type FadeAnimation struct {
	// contains filtered or unexported fields
}

FadeAnimation The animation for fading.

func NewFadeAnimation

func NewFadeAnimation() *FadeAnimation

NewFadeAnimation constructs FadeAnimation

func (*FadeAnimation) DrawOption

func (f *FadeAnimation) DrawOption(target wordwrap.Image) (finished bool, userInputAccepted bool, waitTime time.Duration, err error)

DrawOption draws with options.. Controls the drawing process to add extra frames, a wait time and more finished is true if you're on the last page userInputAccepted is if it's at the stage where you would typically accept user input (ie the animation is waiting user input, doesn't imply anything to do with the animation wait is either 0 or less, or the amount of time before the next animation phase err is err To determine if you're at the end the only way of doing it as of writing is to wait for; lastPage = true, userInputAccepted = false, wait = -1

type FadeState

type FadeState int

FadeState is the current picture fading in or out

const (
	// FadeIn picture is going to fade in or is fading in
	FadeIn FadeState = iota
	// FadeOut picture is going to fade out or is fading out
	FadeOut
)

type Layout

type Layout interface {
	// TextRect is the area containing the text.
	TextRect() image.Rectangle
	// CenterRect is the main content area inside the frame.
	CenterRect() image.Rectangle
	// AvatarRect is the area containing the avatar.
	AvatarRect() image.Rectangle
	// ChevronRect is the area containing the chevron.
	ChevronRect() image.Rectangle
	// NameRect is the optional area containing the name tag.
	NameRect() image.Rectangle
	// FrameRect is the area containing the frame.
	FrameRect() image.Rectangle
}

Layout defines the positioning of elements within the text box.

type LetterByLetterAnimation

type LetterByLetterAnimation struct {

	// The function to calculate the wait time between each box
	WaitTimeFunc func(*LetterByLetterAnimation) time.Duration
	// contains filtered or unexported fields
}

LetterByLetterAnimation is an animation style in which each non-whitespace letter comes into visibility one by one

func NewLetterByLetterAnimation

func NewLetterByLetterAnimation() *LetterByLetterAnimation

NewLetterByLetterAnimation creates an animation style where one block comes on at one time. Use WaitTimeFunc to create your own timing for each block

func (*LetterByLetterAnimation) DrawOption

func (lyl *LetterByLetterAnimation) DrawOption(target wordwrap.Image) (finished bool, userInputAccepted bool, waitTime time.Duration, err error)

DrawOption draws with options.. Controls the drawing process to add extra frames, a wait time and more finished is true if you're on the last page userInputAccepted is if it's at the stage where you would typically accept user input (ie the animation is waiting user input, doesn't imply anything to do with the animation wait is either 0 or less, or the amount of time before the next animation phase err is err To determine if you're at the end the only way of doing it as of writing is to wait for; lastPage = true, userInputAccepted = false, wait = -1

type MoreChevronLocations

type MoreChevronLocations int

MoreChevronLocations defines where the "next page" indicator is positioned.

const (
	// NoMoreChevron hides the chevron.
	NoMoreChevron MoreChevronLocations = iota
	// CenterBottomInsideTextFrame positions the chevron centered at the bottom of the text area.
	CenterBottomInsideTextFrame
	// CenterBottomInsideFrame positions the chevron centered at the bottom of the entire frame.
	CenterBottomInsideFrame
	// CenterBottomOnFrameTextFrame positions the chevron centered on the bottom edge of the text area.
	CenterBottomOnFrameTextFrame
	// CenterBottomOnFrameFrame positions the chevron centered on the bottom edge of the frame.
	CenterBottomOnFrameFrame
	// RightBottomInsideTextFrame positions the chevron at the bottom right of the text area.
	RightBottomInsideTextFrame
	// RightBottomInsideFrame positions the chevron at the bottom right of the frame.
	RightBottomInsideFrame
	// RightBottomOnFrameTextFrame positions the chevron on the bottom right edge of the text area.
	RightBottomOnFrameTextFrame
	// RightBottomOnFrameFrame positions the chevron on the bottom right edge of the frame.
	RightBottomOnFrameFrame
	// TextEndChevron positions the chevron inline at the end of the text.
	TextEndChevron
)

type Name

type Name string

Name sets the character name to display.

type NamePositions

type NamePositions int

NamePositions defines where the name tag is positioned.

const (
	// NoName hides the name tag.
	NoName NamePositions = iota
	// NameTopLeftAboveTextInFrame positions the name tag above the text, aligned left.
	NameTopLeftAboveTextInFrame
	// NameTopCenterInFrame positions the name tag above the text, centered.
	NameTopCenterInFrame
	// NameLeftAboveAvatarInFrame positions the name tag above the avatar, aligned left.
	NameLeftAboveAvatarInFrame
	// NameTopLeftAboveFrame positions the name tag above the frame, aligned left.
	NameTopLeftAboveFrame
	// NameTopCenterAboveFrame positions the name tag above the frame, centered.
	NameTopCenterAboveFrame
)

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option defines a configuration option for the TextBox.

func Avatar

func Avatar(i wordwrap.Image) Option

Avatar overrides the default theme avatar.

func WithWordwrapOption added in v0.0.5

func WithWordwrapOption(opt wordwrap.WrapperOption) Option

WithWordwrapOption allows passing wordwrap options as TextBox options

type Page

type Page struct {
	// contains filtered or unexported fields
}

Page where a page of lines is stored with some statistical information. A page being what is visible in the provided rectangle

type PostDrawer

type PostDrawer interface {
	PostDraw(target wordwrap.Image, layout *SimpleLayout, ls []wordwrap.Line, options ...wordwrap.DrawOption) error
}

PostDrawer allows custom components to be drawn after standard elements.

type SimpleLayout

type SimpleLayout struct {
	// contains filtered or unexported fields
}

SimpleLayout implements a standard text box layout.

func NewSimpleLayout

func NewSimpleLayout(tb *TextBox, destRect image.Rectangle) (*SimpleLayout, error)

NewSimpleLayout constructs SimpleLayout simply as possible (for the user.)

func (*SimpleLayout) AvatarRect

func (sl *SimpleLayout) AvatarRect() image.Rectangle

AvatarRect returns the avatar rectangle.

func (*SimpleLayout) CenterRect

func (sl *SimpleLayout) CenterRect() image.Rectangle

CenterRect returns the center content rectangle.

func (*SimpleLayout) ChevronRect

func (sl *SimpleLayout) ChevronRect() image.Rectangle

ChevronRect returns the chevron rectangle.

func (*SimpleLayout) FrameRect added in v0.0.6

func (sl *SimpleLayout) FrameRect() image.Rectangle

FrameRect returns the frame rectangle.

func (*SimpleLayout) NameRect

func (sl *SimpleLayout) NameRect() image.Rectangle

NameRect returns the name tag rectangle.

func (*SimpleLayout) TextRect

func (sl *SimpleLayout) TextRect() image.Rectangle

TextRect returns the text rectangle.

type SpaceMap added in v0.0.5

type SpaceMap interface {
	Add(shape shared.Shape, zIndex int)
}

SpaceMap is an interface for mapping screen space to interactive shapes.

type TextBox

type TextBox struct {
	// contains filtered or unexported fields
}

TextBox is the main component for rendering RPG-style text boxes.

func NewRichTextBox added in v0.0.5

func NewRichTextBox(th theme.Theme, args ...interface{}) (*TextBox, error)

NewRichTextBox creates a TextBox with rich text content (e.g. colors, images). theme is required. args can include string content, image.Point (for size), and Options.

func NewSimpleTextBox

func NewSimpleTextBox(th theme.Theme, text string, destSize image.Point, options ...Option) (*TextBox, error)

NewSimpleTextBox creates a TextBox with simple string content. theme is required. destSize is the intended size of the image, but can be updated per frame.

func (*TextBox) Avatar

func (tb *TextBox) Avatar() image.Image

Avatar returns the correct avatar (if you have overwritten the theme etc.)

func (*TextBox) CalculateAllPages

func (tb *TextBox) CalculateAllPages(destSize image.Point) (int, error)

CalculateAllPages calculates the box positioning of all remain pages in advance

func (*TextBox) DrawNextFrame

func (tb *TextBox) DrawNextFrame(target wordwrap.Image) (lastPage bool, userInputAccepted bool, wait time.Duration, err error)

DrawNextFrame draws the next frame, if there is an animation it will draw the animation frame. lastPage is true if you're on the last page userInputAccepted is if it's at the stage where you would typically accept user input (ie the animation is waiting user input, doesn't imply anything to do with the animation wait is either 0 or less, or the amount of time before the next animation phase err is err To determine if you're at the end the only way of doing it as of writing is to wait for; lastPage = true, userInputAccepted = false, wait = -1

func (*TextBox) DrawNextPageFrame

func (tb *TextBox) DrawNextPageFrame(target wordwrap.Image, opts ...wordwrap.DrawOption) (bool, error)

DrawNextPageFrame Draws the next frame ignores animation. Please use either function but be very careful if you use both, if it's supported is at an animation level

func (*TextBox) HasNext

func (tb *TextBox) HasNext() bool

HasNext returns if there is a next page, this doesn't take into consideration if there is an animation

func (*TextBox) SetSpaceMap added in v0.0.5

func (tb *TextBox) SetSpaceMap(m SpaceMap)

SetSpaceMap sets the space map to populate

Directories

Path Synopsis
cmd
rpgtextbox command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL