Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow users to define a variable number of lines in font_config #59

Merged
merged 23 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
78445a9
Parser now correctly breaks at 3 lines
pkmnsnfrn Sep 6, 2023
8c777fb
Change maxLineWidth for RSE fonts
pkmnsnfrn Sep 6, 2023
55ee476
Updated originalMaxLineLength to stop cutoffs https://i.imgur.com/URm…
pkmnsnfrn Oct 2, 2023
024a6ac
Merged in latest poryscript
pkmnsnfrn Dec 4, 2023
2539bc8
Create a modular version that takes a number of lines from
pkmnsnfrn Dec 21, 2023
9296e06
Simplified block to use two statements as suggested in https://github…
pkmnsnfrn Dec 25, 2023
adc1855
renamed isMaxLineOrGreater to shouldUseLineFeed https://github.com/hu…
pkmnsnfrn Dec 25, 2023
9d543a6
renamed lineNumber to curLineNum https://github.com/huderlem/poryscr…
pkmnsnfrn Dec 25, 2023
5d2bde4
zero-indexed curLineNum https://github.com/huderlem/poryscript/pull/5…
pkmnsnfrn Dec 25, 2023
8f4a7a4
Added numLines, maxLineLength, fontId as named parameters
pkmnsnfrn Dec 26, 2023
407bce1
renamed numLines
pkmnsnfrn Dec 26, 2023
5dd965d
Added maxLineLength as unnamed paramter
pkmnsnfrn Dec 26, 2023
9cb78e9
Added cursorOverlapWidth as a named parameter
pkmnsnfrn Dec 26, 2023
e70de91
Fixed typo with cursorOverlapWidth
pkmnsnfrn Dec 26, 2023
58583e9
Reset curLineNum to zero when the user enters their own paragraph bre…
pkmnsnfrn Dec 26, 2023
5e52164
Changed isFirstLine to use bang operator instead of checking for fals…
pkmnsnfrn Dec 26, 2023
3cf189f
Removed redundant parenthesis in shouldUseLineFeed https://github.com…
pkmnsnfrn Dec 26, 2023
667acc2
Created setEmptyParametersToDefault to warn users and handle unset va…
pkmnsnfrn Dec 26, 2023
66a2742
Reworked high-level logic and created isNamedParameter, handleUnnamed…
pkmnsnfrn Dec 26, 2023
b834f9d
Created reportDuplicateParameterError https://github.com/huderlem/por…
pkmnsnfrn Dec 27, 2023
f9ef671
Fixup format() named parameters parsing and tests
huderlem Jan 1, 2024
7d86835
Update README
huderlem Jan 1, 2024
3e872d7
Improve format() error message and disallow duplicated named parameters
huderlem Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,8 @@ The font configuration JSON file informs Poryscript how many pixels wide each ch

`cursorOverlapWidth` can be used to ensure there is always enough room for the cursor icon to be displayed in the text box. (This "cursor icon" is the small icon that's shown when the player needs to press A to advance the text box.)

`numLines` is the number of lines displayed within a single message box. If editing text for a taller space, this can be adjusted in `font_config.json`.

The length of a line can optionally be specified as the third parameter to `format()` if a font id was specified as the second parameter.

```
Expand Down
2 changes: 2 additions & 0 deletions font_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"fonts": {
"1_latin_rse": {
"maxLineLength": 208,
"numLines": 2,
"cursorOverlapWidth": 0,
"widths": {
" ": 3,
Expand Down Expand Up @@ -177,6 +178,7 @@
},
"1_latin_frlg": {
"maxLineLength": 208,
"numLines": 2,
"cursorOverlapWidth": 10,
"widths": {
" ": 6,
Expand Down
33 changes: 21 additions & 12 deletions parser/formattext.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Fonts struct {
Widths map[string]int `json:"widths"`
CursorOverlapWidth int `json:"cursorOverlapWidth"`
MaxLineLength int `json:"maxLineLength"`
NumLines int `json:"numLines"`
}

// LoadFontConfig reads a font width config JSON file.
Expand All @@ -40,7 +41,7 @@ const testFontID = "TEST"

// FormatText automatically inserts line breaks into text
// according to in-game text box widths.
func (fc *FontConfig) FormatText(text string, maxWidth int, cursorOverlapWidth int, fontID string) (string, error) {
func (fc *FontConfig) FormatText(text string, maxWidth int, cursorOverlapWidth int, fontID string, numLines int) (string, error) {
if !fc.isFontIDValid(fontID) && len(fontID) > 0 && fontID != testFontID {
validFontIDs := make([]string, len(fc.Fonts))
i := 0
Expand All @@ -56,7 +57,7 @@ func (fc *FontConfig) FormatText(text string, maxWidth int, cursorOverlapWidth i
var formattedSb strings.Builder
var curLineSb strings.Builder
curWidth := 0
isFirstLine := true
curLineNum := 0
isFirstWord := true
spaceCharWidth := fc.getRunePixelWidth(' ', fontID)
pos, word := fc.getNextWord(text)
Expand All @@ -71,7 +72,7 @@ func (fc *FontConfig) FormatText(text string, maxWidth int, cursorOverlapWidth i
curWidth = 0
formattedSb.WriteString(curLineSb.String())
if fc.isAutoLineBreak(word) {
if isFirstLine {
if fc.isFirstLine(curLineNum) {
formattedSb.WriteString(`\n`)
} else {
formattedSb.WriteString(`\l`)
Expand All @@ -80,10 +81,8 @@ func (fc *FontConfig) FormatText(text string, maxWidth int, cursorOverlapWidth i
formattedSb.WriteString(word)
}
formattedSb.WriteByte('\n')
if fc.isParagraphBreak(word) {
isFirstLine = true
} else {
isFirstLine = false
if !fc.isParagraphBreak(word) {
curLineNum++
}
pkmnsnfrn marked this conversation as resolved.
Show resolved Hide resolved
isFirstWord = true
curLineSb.Reset()
Expand All @@ -98,17 +97,19 @@ func (fc *FontConfig) FormatText(text string, maxWidth int, cursorOverlapWidth i
// it could span multiple words. The true solution would require optimistically trying to fit all
// remaining words onto the same line, rather than only looking at the current word + cursor. However,
// this is "good enough" and likely works for almost all actual use cases in practice.
if len(nextWord) > 0 && (!isFirstLine || fc.isParagraphBreak(nextWord)) {
if len(nextWord) > 0 && (fc.isFirstLine(curLineNum) == false || fc.isParagraphBreak(nextWord)) {
pkmnsnfrn marked this conversation as resolved.
Show resolved Hide resolved
nextWidth += cursorOverlapWidth
}
if nextWidth > maxWidth && curLineSb.Len() > 0 {
formattedSb.WriteString(curLineSb.String())
if isFirstLine {
formattedSb.WriteString(`\n`)
isFirstLine = false
} else {

if fc.shouldUseLineFeed(curLineNum, numLines) {
formattedSb.WriteString(`\l`)
} else {
formattedSb.WriteString(`\n`)
}

curLineNum++
formattedSb.WriteByte('\n')
isFirstWord = false
curLineSb.Reset()
Expand All @@ -133,6 +134,14 @@ func (fc *FontConfig) FormatText(text string, maxWidth int, cursorOverlapWidth i
return formattedSb.String(), nil
}

func (fc *FontConfig) isFirstLine(curLineNum int) bool {
return curLineNum == 0
}

func (fc *FontConfig) shouldUseLineFeed(curLineNum int, numLines int) bool {
return (curLineNum >= (numLines - 1))
pkmnsnfrn marked this conversation as resolved.
Show resolved Hide resolved
}

func (fc *FontConfig) getNextWord(text string) (int, string) {
escape := false
endPos := 0
Expand Down
96 changes: 69 additions & 27 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type Parser struct {
defaultFontID string
fonts *FontConfig
maxLineLength int
numLines int
cursorOverlapWidth int
compileSwitches map[string]string
constants map[string]string
enableEnvironmentErrors bool
Expand Down Expand Up @@ -1075,46 +1077,86 @@ func (p *Parser) parseFormatStringOperator() (token.Token, string, string, error
}

maxTextLength := p.maxLineLength
numLines := p.numLines
cursorOverlapWidth := p.cursorOverlapWidth
paramValue := ""

namedParameters := map[string]func(){
"fontId": func() {
fontID = paramValue
fontIdToken = p.peekToken
},
"maxLineLength": func() {
num, _ := strconv.ParseInt(paramValue, 0, 64)
maxTextLength = int(num)
},
"numLines": func() {
num, _ := strconv.ParseInt(paramValue, 0, 64)
numLines = int(num)
},
"cursorOverlapWidth": func() {
num, _ := strconv.ParseInt(paramValue, 0, 64)
cursorOverlapWidth = int(num)
},
}
pkmnsnfrn marked this conversation as resolved.
Show resolved Hide resolved

for !p.peekTokenIs(token.RPAREN) {
if !p.peekTokenIs(token.COMMA) {
return token.Token{}, "", "", NewParseError(p.peekToken, fmt.Sprintf("invalid format() parameter '%s'. Expected a comma", p.peekToken.Literal))
}

if p.peekTokenIs(token.COMMA) {
p.nextToken()
if p.peekTokenIs(token.STRING) {

if p.peekTokenIs(token.INT) {
num, _ := strconv.ParseInt(p.peekToken.Literal, 0, 64)
maxTextLength = int(num)
p.nextToken()
fontID = p.curToken.Literal
fontIdToken = p.curToken
if p.peekTokenIs(token.COMMA) {
p.nextToken()
if err := p.expectPeek(token.INT); err != nil {
return token.Token{}, "", "", NewParseError(p.peekToken, fmt.Sprintf("invalid format() maxLineLength '%s'. Expected integer", p.peekToken.Literal))
}
num, _ := strconv.ParseInt(p.curToken.Literal, 0, 64)
maxTextLength = int(num)
}
} else if p.peekTokenIs(token.INT) {
continue
}
pkmnsnfrn marked this conversation as resolved.
Show resolved Hide resolved

paramName := p.peekToken.Literal
_, ok := namedParameters[paramName]
if !ok {
fontID = p.peekToken.Literal
fontIdToken = p.peekToken
p.nextToken()
num, _ := strconv.ParseInt(p.curToken.Literal, 0, 64)
maxTextLength = int(num)
if p.peekTokenIs(token.COMMA) {
p.nextToken()
if err := p.expectPeek(token.STRING); err != nil {
return token.Token{}, "", "", NewParseError(p.peekToken, fmt.Sprintf("invalid format() fontId '%s'. Expected string", p.peekToken.Literal))
}
fontID = p.curToken.Literal
fontIdToken = p.curToken
}
} else {
return token.Token{}, "", "", NewParseError(p.peekToken, fmt.Sprintf("invalid format() parameter '%s'. Expected either fontId (string) or maxLineLength (integer)", p.peekToken.Literal))
continue
}

p.nextToken()

if !p.peekTokenIs(token.ASSIGN) {
return token.Token{}, "", "", NewParseError(p.peekToken, fmt.Sprintf("invalid format() parameter '%s'. Expected an equals sign", p.peekToken.Literal))
}

p.nextToken()
paramValue = p.peekToken.Literal

setNamedParameters, ok := namedParameters[paramName]

if !ok {
return token.Token{}, "", "", NewParseError(p.curToken, fmt.Sprintf("invalid format() parameter '%s'. Expected one of the following: fontId (string), maxLineLength (integer), cursorOverlapWidth (integer), numLines (integer).", paramName))
}
setNamedParameters()
p.nextToken()
}
if err := p.expectPeek(token.RPAREN); err != nil {
return token.Token{}, "", "", NewParseError(p.peekToken, "missing closing parenthesis ')' for format()")
return token.Token{}, "", "", NewParseError(p.curToken, "missing closing parenthesis ')' for format()")
}

if maxTextLength <= 0 {
maxTextLength = p.fonts.Fonts[fontID].MaxLineLength
}

formatted, err := p.fonts.FormatText(textToken.Literal, maxTextLength, p.fonts.Fonts[fontID].CursorOverlapWidth, fontID)
if numLines <= 0 {
numLines = p.fonts.Fonts[fontID].NumLines
}
pkmnsnfrn marked this conversation as resolved.
Show resolved Hide resolved

if cursorOverlapWidth <= 0 {
cursorOverlapWidth = p.fonts.Fonts[fontID].CursorOverlapWidth
}
pkmnsnfrn marked this conversation as resolved.
Show resolved Hide resolved

formatted, err := p.fonts.FormatText(textToken.Literal, maxTextLength, cursorOverlapWidth, fontID, numLines)
if err != nil && p.enableEnvironmentErrors {
return token.Token{}, "", "", NewParseError(fontIdToken, err.Error())
}
Expand Down