Skip to content

Commit

Permalink
Enhancements to HTML vlist building
Browse files Browse the repository at this point in the history
Working on #6 - which needs a rewrite of the
html vlist builder.
  • Loading branch information
pgundlach committed Jun 21, 2024
1 parent dee3de0 commit 53a959b
Show file tree
Hide file tree
Showing 13 changed files with 421 additions and 272 deletions.
4 changes: 3 additions & 1 deletion backend/node/linebreak.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ func (lb *linebreaker) computeAdjustmentRatio(n Node, a *Breakpoint) (float64, b
thisLineWidth += t.Width
case *Disc:
if !lb.settings.HangingPunctuationEnd {
thisLineWidth += Dimensions(t.Pre, nil, Horizontal)
wd, _, _ := Dimensions(t.Pre, nil, Horizontal)
thisLineWidth += wd
}
case *Glue:
if lb.settings.HangingPunctuationEnd {
Expand Down Expand Up @@ -563,6 +564,7 @@ func Linebreak(n Node, settings *LinebreakSettings) (*VList, []*Breakpoint) {
vert = InsertAfter(vert, hl, lineskip)
}
vl := Vpack(vert)
vl.Attributes = H{"origin": "Linebreak"}
return vl, bps
}

Expand Down
26 changes: 20 additions & 6 deletions backend/node/nodelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,28 @@ func CopyList(nl Node) Node {
return copied
}

// Dimensions returns the width of the node list starting at n. If dir is
// Dimensions returns the width, height and the depth of the node list starting
// at n and ending with the stop node or at the end if stop is nil. If dir is
// Horizontal, then calculate in horizontal mode, otherwise in vertical mode.
func Dimensions(start Node, stop Node, dir Direction) bag.ScaledPoint {
var sumwd bag.ScaledPoint
for e := start; e != stop; e = e.Next() {
sumwd += getWidth(e, dir)
func Dimensions(start Node, stop Node, dir Direction) (bag.ScaledPoint, bag.ScaledPoint, bag.ScaledPoint) {
var sumwd, sumht, sumdp bag.ScaledPoint

for e := start; e != nil; e = e.Next() {
wd := getWidth(e, dir)
sumwd += wd

ht, dp := getHeight(e, dir)
if ht > sumht {
sumht = ht
}
if dp > sumdp {
sumdp = dp
}
if e == stop {
break
}
}
return sumwd
return sumwd, sumht, sumdp
}

type hpackSetting struct {
Expand Down
3 changes: 2 additions & 1 deletion csshtml/css.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,11 @@ caption { display: table-caption }
th { font-weight: bold; text-align: center }
caption { text-align: center }
body { margin: 0pt; line-height: 1.2; hyphens: auto; font-weight: normal; }
p { font-size: 1em; margin: 1.5em 0 }
h1 { font-size: 2em; margin: .67em 0 }
h2 { font-size: 1.5em; margin: .75em 0 }
h3 { font-size: 1.17em; margin: .83em 0 }
h4, p,
h4,
blockquote, ul,
fieldset, form,
ol, dl, dir,
Expand Down
207 changes: 0 additions & 207 deletions frontend/cssbuilder/listbuilder.go
Original file line number Diff line number Diff line change
@@ -1,208 +1 @@
package cssbuilder

import (
"fmt"
"strconv"
"strings"

"github.com/speedata/boxesandglue/backend/bag"
"github.com/speedata/boxesandglue/backend/node"

"github.com/speedata/boxesandglue/frontend"
)

// CreateVlist converts the te into a big vlist.
func (cb *CSSBuilder) CreateVlist(te *frontend.Text, wd bag.ScaledPoint) (*node.VList, error) {
info, err := cb.buildVlistInternal(te, wd, 0, 0)
if err != nil {
return nil, err
}

var list node.Node

for _, n := range info.pagebox {
switch t := n.(type) {
case *node.StartStop:
// ignore for now - should be used for frames
case *node.VList:
if xattr, ok := t.Attributes["x"].(bag.ScaledPoint); ok {
t.ShiftX = xattr
}
list = node.InsertAfter(list, node.Tail(list), t)
}
}

return node.Vpack(list), nil
}

func (cb *CSSBuilder) buildVlistInternal(te *frontend.Text, width bag.ScaledPoint, x bag.ScaledPoint, shiftDown bag.ScaledPoint) (*info, error) {
hv := frontend.SettingsToValues(te.Settings)
hsize := width - hv.MarginLeft - hv.MarginRight - hv.BorderLeftWidth - hv.BorderRightWidth - hv.PaddingLeft - hv.PaddingRight
x += hv.MarginLeft

ret := &info{
marginTop: hv.MarginTop,
marginBottom: hv.MarginBottom,
}

if prepend, ok := te.Settings[frontend.SettingPrepend]; ok {
if p, ok := prepend.(node.Node); ok {
g := node.NewGlue()
g.Stretch = bag.Factor
g.Shrink = bag.Factor
g.StretchOrder = node.StretchFil
g.ShrinkOrder = node.StretchFil
p = node.InsertBefore(p, p, g)
p = node.HpackTo(p, 0)
p.(*node.HList).Depth = 0
vl := node.Vpack(p)
vl.Height = 0
vl.Attributes = node.H{"height": bag.ScaledPoint(0), "x": x, "origin": "v prepend in HTML mode"}
ret.pagebox = append(ret.pagebox, vl)
}
}

var prevMB, height bag.ScaledPoint
if bx, ok := te.Settings[frontend.SettingBox]; ok && bx.(bool) {
// a box, containing one or more item (a div for example)
for _, itm := range te.Items {
if txt, ok := itm.(*frontend.Text); ok {
info, err := cb.buildVlistInternal(txt, hsize, x+hv.BorderLeftWidth+hv.PaddingLeft, shiftDown)
if err != nil {
return nil, err
}

// margin collapse
if prevMB >= info.marginTop {
info.marginTop = 0
} else {
info.marginTop -= prevMB
}
height += info.height
height += info.marginTop + info.marginBottom
height += info.hv.PaddingTop + info.hv.PaddingBottom + info.hv.BorderTopWidth + info.hv.BorderBottomWidth

start := node.NewStartStop()
start.Attributes = node.H{
"shiftDown": info.marginTop,
"hv": info.hv,
"height": info.height,
"hsize": info.hsize,
"x": info.x,
}
ret.pagebox = append(ret.pagebox, start)

if info.vl == nil {
ret.pagebox = append(ret.pagebox, info.pagebox...)
} else {
ret.pagebox = append(ret.pagebox, info.vl)
}

stop := node.NewStartStop()
stop.Attributes = node.H{
"shiftDown": info.marginBottom,
"height": height,
"hv": info.hv,
}
stop.StartNode = start
ret.pagebox = append(ret.pagebox, stop)
prevMB = info.marginBottom
}
}
ret.x = x
ret.hsize = hsize
ret.height = height
ret.hv = hv
return ret, nil
}

// not a box
//
// something like a p tag that contains some stuff to be typeset.
vl, err := cb.createVList(te, hsize, hv)
if err != nil {
return nil, err
}

if prepend, ok := te.Settings[frontend.SettingPrepend]; ok {
if p, ok := prepend.(node.Node); ok {
g := node.NewGlue()
g.Stretch = bag.Factor
g.Shrink = bag.Factor
g.StretchOrder = node.StretchFil
g.ShrinkOrder = node.StretchFil
p = node.InsertBefore(p, p, g)
p = node.HpackTo(p, 0)
p.(*node.HList).Depth = 0
n := node.InsertAfter(p, node.Tail(p), vl)
hl := node.Hpack(n)
hl.VAlign = node.VAlignTop
vl = node.Vpack(hl)
}
}

vl.Attributes = node.H{
"height": vl.Height + vl.Depth,
"x": x + hv.PaddingLeft + hv.BorderLeftWidth,
"hsize": hsize,
}
ret.height = vl.Height + vl.Depth
ret.vl = vl
ret.hv = hv
ret.hsize = hsize
ret.x = x
return ret, nil
}

func (cb *CSSBuilder) fixupWidth(te *frontend.Text, hsize bag.ScaledPoint, hv frontend.HTMLValues) error {
var err error
for _, itm := range te.Items {
switch t := itm.(type) {
case *frontend.Text:
if err = cb.fixupWidth(t, hsize, hv); err != nil {
return err
}
case string:
// ignore
case *node.Image:
if wd, ok := t.Attributes["wd"]; ok {
t.Width = wd.(bag.ScaledPoint)
}
if ht, ok := t.Attributes["ht"]; ok {
t.Height = ht.(bag.ScaledPoint)
} else {
attr := t.Attributes["attr"].(map[string]string)
if k, ok := attr["!width"]; ok {
if str, ok := strings.CutSuffix(k, "%"); ok {
f, err := strconv.ParseFloat(str, 64)
if err != nil {
return err
}
t.Width = bag.MultiplyFloat(hsize, f/100)
}
}
}
case *frontend.Table:
// ignore
default:
return fmt.Errorf("fixupWidth: unknown item %T", t)
}
}
return nil
}

func (cb *CSSBuilder) createVList(te *frontend.Text, wd bag.ScaledPoint, hv frontend.HTMLValues) (*node.VList, error) {
if err := cb.fixupWidth(te, wd, hv); err != nil {
return nil, err
}
vl, _, err := cb.frontend.FormatParagraph(te, wd)
// FIXME: vl can be nil if empty (empty li for example)
if err != nil {
return nil, err
}
vl.Attributes = node.H{
"hv": hv,
"hsize": wd,
}
return vl, nil
}
8 changes: 4 additions & 4 deletions frontend/cssbuilder/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ func (cb *CSSBuilder) outputOnPage(te *frontend.Text) error {
if err != nil {
return err
}
info, err := cb.buildVlistInternal(te, dim.ContentWidth, dim.MarginLeft, 0)
info, err := cb.frontend.BuildVlistInternal(te, dim.ContentWidth, dim.MarginLeft, 0)
if err != nil {
return err
}
cb.pagebox = info.pagebox
cb.pagebox = info.Pagebox

if err = cb.buildPages(); err != nil {
return err
Expand Down Expand Up @@ -273,11 +273,11 @@ func (cb *CSSBuilder) buildPages() error {
// width. OutputAt inserts page breaks if necessary.
func (cb *CSSBuilder) OutputAt(text *frontend.Text, x, y, width bag.ScaledPoint) error {
bag.Logger.Debug("CSSBuilder#OutputAt")
inf, err := cb.buildVlistInternal(text, width, x, 0)
inf, err := cb.frontend.BuildVlistInternal(text, width, x, 0)
if err != nil {
return err
}
cb.pagebox = inf.pagebox
cb.pagebox = inf.Pagebox
if err = cb.buildPages(); err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion frontend/decoration.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ type styles struct {
}

func doUnderline(head, start, stop node.Node, st *styles) node.Node {
pdfUL := pdfdraw.NewStandalone().LineWidth(st.linewidth).Moveto(0, st.ulpos).Lineto(node.Dimensions(start, stop, node.Horizontal), st.ulpos).Stroke().String()
wd, _, _ := node.Dimensions(start, stop, node.Horizontal)
pdfUL := pdfdraw.NewStandalone().LineWidth(st.linewidth).Moveto(0, st.ulpos).Lineto(wd, st.ulpos).Stroke().String()
r := node.NewRule()
r.Hide = true
r.Pre = pdfUL
Expand Down
17 changes: 17 additions & 0 deletions frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,23 @@ const (
// HAlignJustified makes text left and right aligned.
HAlignJustified
)

func (ha HorizontalAlignment) String() string {
switch ha {
case HAlignDefault:
return "default"
case HAlignLeft:
return "left"
case HAlignRight:
return "right"
case HAlignCenter:
return "center"
case HAlignJustified:
return "justified"
}
return "---"
}

const (
// VAlignDefault is an undefined vertical alignment.
VAlignDefault VerticalAlignment = iota
Expand Down
Loading

0 comments on commit 53a959b

Please sign in to comment.