2023-04-09 00:35:13 +02:00
|
|
|
package render
|
2023-04-06 16:21:26 +02:00
|
|
|
|
|
|
|
import (
|
2023-04-07 12:31:30 +02:00
|
|
|
"bytes"
|
2023-04-06 16:21:26 +02:00
|
|
|
"fmt"
|
|
|
|
"log"
|
2023-04-07 12:31:30 +02:00
|
|
|
"strings"
|
2023-04-06 16:21:26 +02:00
|
|
|
)
|
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// Sizer splits dynamic contents into individual segments for browseable pages.
|
2023-04-06 16:21:26 +02:00
|
|
|
type Sizer struct {
|
2023-04-12 19:04:36 +02:00
|
|
|
outputSize uint32 // maximum output for a single page.
|
|
|
|
menuSize uint16 // actual menu size for the dynamic page being sized
|
|
|
|
memberSizes map[string]uint16 // individual byte sizes of all content to be rendered by template.
|
|
|
|
totalMemberSize uint32 // total byte size of all content to be rendered by template (sum of memberSizes)
|
|
|
|
crsrs []uint32 // byte offsets in the sink content for browseable pages indices.
|
|
|
|
sink string // sink symbol.
|
2023-04-06 16:21:26 +02:00
|
|
|
}
|
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// NewSizer creates a new Sizer object with the given output size constraint.
|
2023-04-09 00:35:13 +02:00
|
|
|
func NewSizer(outputSize uint32) *Sizer {
|
|
|
|
return &Sizer{
|
|
|
|
outputSize: outputSize,
|
2023-04-06 16:21:26 +02:00
|
|
|
memberSizes: make(map[string]uint16),
|
|
|
|
}
|
2023-04-09 00:35:13 +02:00
|
|
|
}
|
2023-04-07 12:31:30 +02:00
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// WithMenuSize sets the size of the menu being used in the rendering context.
|
2023-04-09 00:35:13 +02:00
|
|
|
func(szr *Sizer) WithMenuSize(menuSize uint16) *Sizer {
|
|
|
|
szr.menuSize = menuSize
|
|
|
|
return szr
|
|
|
|
}
|
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// Set adds a content symbol in the state it will be used by the renderer.
|
2023-04-09 00:35:13 +02:00
|
|
|
func(szr *Sizer) Set(key string, size uint16) error {
|
|
|
|
szr.memberSizes[key] = size
|
|
|
|
if size == 0 {
|
|
|
|
szr.sink = key
|
2023-04-06 16:21:26 +02:00
|
|
|
}
|
2023-04-09 00:35:13 +02:00
|
|
|
szr.totalMemberSize += uint32(size)
|
|
|
|
return nil
|
2023-04-06 16:21:26 +02:00
|
|
|
}
|
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// Check audits whether the rendered string is within the output size constraint of the sizer.
|
2023-04-06 16:21:26 +02:00
|
|
|
func(szr *Sizer) Check(s string) (uint32, bool) {
|
|
|
|
l := uint32(len(s))
|
2023-04-07 10:30:23 +02:00
|
|
|
if szr.outputSize > 0 {
|
|
|
|
if l > szr.outputSize {
|
|
|
|
log.Printf("sizer check fails with length %v: %s", l, szr)
|
2023-04-14 10:09:53 +02:00
|
|
|
log.Printf("sizer contents:\n%s", s)
|
2023-04-07 10:30:23 +02:00
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
l = szr.outputSize - l
|
2023-04-06 16:21:26 +02:00
|
|
|
}
|
|
|
|
return l, true
|
|
|
|
}
|
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// String implements the String interface.
|
2023-04-06 16:21:26 +02:00
|
|
|
func(szr *Sizer) String() string {
|
|
|
|
var diff uint32
|
|
|
|
if szr.outputSize > 0 {
|
|
|
|
diff = szr.outputSize - szr.totalMemberSize - uint32(szr.menuSize)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("output: %v, member: %v, menu: %v, diff: %v", szr.outputSize, szr.totalMemberSize, szr.menuSize, diff)
|
|
|
|
}
|
2023-04-07 10:30:23 +02:00
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// Size gives the byte size of content for a single symbol.
|
|
|
|
//
|
|
|
|
// Fails if the symbol has not been registered using Set
|
2023-04-07 10:30:23 +02:00
|
|
|
func(szr *Sizer) Size(s string) (uint16, error) {
|
|
|
|
r, ok := szr.memberSizes[s]
|
|
|
|
if !ok {
|
|
|
|
return 0, fmt.Errorf("unknown member: %s", s)
|
|
|
|
}
|
|
|
|
return r, nil
|
|
|
|
}
|
2023-04-07 12:31:30 +02:00
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// Menusize returns the currently defined menu size.
|
2023-04-11 09:35:00 +02:00
|
|
|
func(szr *Sizer) MenuSize() uint16 {
|
|
|
|
return szr.menuSize
|
|
|
|
}
|
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// AddCursor adds a pagination cursor for the paged sink content.
|
2023-04-07 12:31:30 +02:00
|
|
|
func(szr *Sizer) AddCursor(c uint32) {
|
|
|
|
log.Printf("added cursor: %v", c)
|
|
|
|
szr.crsrs = append(szr.crsrs, c)
|
|
|
|
}
|
|
|
|
|
2023-04-12 19:04:36 +02:00
|
|
|
// GetAt the paged symbols for the current page index.
|
|
|
|
//
|
|
|
|
// Fails if index requested is out of range.
|
2023-04-07 12:31:30 +02:00
|
|
|
func(szr *Sizer) GetAt(values map[string]string, idx uint16) (map[string]string, error) {
|
|
|
|
if szr.sink == "" {
|
|
|
|
return values, nil
|
|
|
|
}
|
|
|
|
outValues := make(map[string]string)
|
|
|
|
for k, v := range values {
|
|
|
|
if szr.sink == k {
|
|
|
|
if idx >= uint16(len(szr.crsrs)) {
|
|
|
|
return nil, fmt.Errorf("no more values in index")
|
|
|
|
}
|
|
|
|
c := szr.crsrs[idx]
|
|
|
|
v = v[c:]
|
|
|
|
nl := strings.Index(v, "\n")
|
|
|
|
if nl > 0 {
|
|
|
|
v = v[:nl]
|
|
|
|
}
|
|
|
|
b := bytes.ReplaceAll([]byte(v), []byte{0x00}, []byte{0x0a})
|
|
|
|
v = string(b)
|
|
|
|
}
|
|
|
|
outValues[k] = v
|
|
|
|
}
|
|
|
|
return outValues, nil
|
|
|
|
}
|