shithub: mycel

Download patch

ref: 5aba680979d4c267ad5b279810d24c85fdf208b1
parent: 8425df6daf1bd75c6cfb82683dc278d0831bd33a
author: Philip Silva <[email protected]>
date: Sat Jul 17 18:20:01 EDT 2021

Make relative widths work

- make tree accessible from css
- use resizing from golang.org/x/image

--- a/browser/browser_test.go
+++ b/browser/browser_test.go
@@ -20,7 +20,8 @@
 func init() {
 	logger.Init()
 	SetLogger(&logger.Logger{})
-	style.Init(nil, &logger.Logger{})
+	nodes.SetLogger(log)
+	style.Init(nil, log)
 }
 
 type item struct {
@@ -86,13 +87,14 @@
 				t.Fatalf("%+v", e)
 			}
 		}
+		body := v.UI.(*duitx.Box).Kids[0]
 		if d == "inline" {
-			b := v.UI.(*duitx.Box)
+			b := body.UI.(*duitx.Box)
 			if len(b.Kids) != 3 {
-				t.Fatalf("%+v", b)
+				t.Fatalf("%v %+v", len(b.Kids), b)
 			}
 		} else {
-			if g := v.UI.(*duitx.Grid); g.Columns != 1 || len(g.Kids) != 3 {
+			if g := body.UI.(*duitx.Grid); g.Columns != 1 || len(g.Kids) != 3 {
 				t.Fatalf("%+v", g)
 			}
 		}
--- a/go.mod
+++ b/go.mod
@@ -29,7 +29,6 @@
 	github.com/gorilla/css v1.0.0 // indirect
 	github.com/knusbaum/go9p v1.17.0
 	github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f
-	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
 	github.com/srwiley/oksvg v0.0.0-20210320200257-875f767ac39a
 	github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9
 	golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
--- a/go.sum
+++ b/go.sum
@@ -20,8 +20,6 @@
 github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
-github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
-github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/psilva261/duit v0.0.0-20210627123547-8bc19650d4a2 h1:XVUpKOs8MDCaGCyyrpr5an4rflKIpJpMq/76XhqdC5I=
--- a/img/img.go
+++ b/img/img.go
@@ -2,18 +2,18 @@
 
 import (
 	"bytes"
-	"github.com/nfnt/resize"
 	"encoding/base64"
 	"fmt"
+	"github.com/psilva261/opossum"
+	"github.com/psilva261/opossum/logger"
 	"github.com/srwiley/oksvg"
 	"github.com/srwiley/rasterx"
-	"image"
+	"golang.org/x/image/draw"
 	"image/png"
+	"image"
 	"io"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/logger"
-	"strings"
 	"net/url"
+	"strings"
 
 	_ "image/gif"
 	_ "image/jpeg"
@@ -209,20 +209,53 @@
 			return nil, fmt.Errorf("svg: %v", err)
 		}
 	} else if w != 0 || h != 0 {
-		image, _, err := image.Decode(bytes.NewReader(data))
+		img, _, err := image.Decode(bytes.NewReader(data))
 		if err != nil {
 			return nil, fmt.Errorf("decode %v: %w", imgUrl, err)
 		}
 
-		newImage := resize.Resize(uint(w), uint(h), image, resize.Lanczos3)
+		dx := img.Bounds().Max.X
+		dy := img.Bounds().Max.Y
 
-		// Encode uses a Writer, use a Buffer if you need the raw []byte
-		buf := bytes.NewBufferString("")
-		if err = png.Encode(buf, newImage); err != nil {
-			return nil, fmt.Errorf("encode: %w", err)
+		newX, newY, skip := newSizes(dx, dy, w, h)
+
+		if !skip {
+			dst := image.NewRGBA(image.Rect(0, 0, newX, newY))
+			draw.NearestNeighbor.Scale(dst, dst.Rect, img, img.Bounds(), draw.Over, nil)
+			buf := bytes.NewBufferString("")
+			if err = png.Encode(buf, dst); err != nil {
+				return nil, fmt.Errorf("encode: %w", err)
+			}
+			data = buf.Bytes()
 		}
-		data = buf.Bytes()
 	}
 
 	return bytes.NewReader(data), nil
+}
+
+func newSizes(oldX, oldY, wantedX, wantedY int) (newX, newY int, skip bool) {
+	if oldX == 0 || oldY == 0 {
+		return oldX, oldY, true
+	}
+	if wantedX == 0 {
+		newX = int(float64(oldX) * float64(wantedY)/float64(oldY))
+		newY = wantedY
+	} else if wantedY == 0 {
+		newX = wantedX
+		newY = int(float64(oldY) * float64(wantedX)/float64(oldX))
+	} else {
+		newX = wantedX
+		newY = wantedY
+	}
+
+	if newX > 2000 || newY > 2000 {
+		return oldX, oldY, true
+	}
+
+	r := float64(newX) / float64(oldX)
+	if 0.8 <= r && r <= 1.2 {
+		return oldX, oldY, true
+	}
+
+	return
 }
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -24,7 +24,7 @@
 	Attrs []html.Attribute
 	style.Map
 	Children []*Node
-	Parent *Node `json:"-"`
+	parent *Node `json:"-"`
 }
 
 // NewNodeTree propagates the cascading styles to the leaves
@@ -52,11 +52,11 @@
 		data = strings.ToLower(data)
 	}
 	n = &Node{
-		DomSubtree:   doc,
-		Attrs:           doc.Attr,
-		Map: ncs,
-		Children:       make([]*Node, 0, 2),
-		Parent: parent,
+		DomSubtree: doc,
+		Attrs:      doc.Attr,
+		Map:        ncs,
+		Children:   make([]*Node, 0, 2),
+		parent:     parent,
 	}
 	n.Wrappable = doc.Type == html.TextNode || doc.Data == "span" // TODO: probably this list needs to be extended
 	if doc.Type == html.TextNode {
@@ -69,10 +69,12 @@
 	i := 0
 	for c := doc.FirstChild; c != nil; c = c.NextSibling {
 		if c.Type != html.CommentNode {
-			n.Children = append(n.Children, NewNodeTree(c, ncs, nodeMap, n))
+			cnt := NewNodeTree(c, ncs, nodeMap, n)
+			n.Children = append(n.Children, cnt)
 			i++
 		}
 	}
+	n.Map.DomTree = n
 
 	return
 }
@@ -95,6 +97,21 @@
 	return n.DomSubtree.Data
 }
 
+func (n *Node) Parent() (p style.DomTree, ok bool) {
+	ok = n.parent != nil && n.Data() != "html" && n.Data() != "body"
+	if n.parent == nil && n.Data() != "html" && n.Data() != "body" {
+		log.Errorf("n.Data() = %v but n.parent=nil", n.parent)
+	}
+	if ok {
+		p = n.parent
+	}
+	return
+}
+
+func (n *Node) Style() style.Map {
+	return n.Map
+}
+
 // Ancestor of tag
 func (n *Node) Ancestor(tag string) *Node {
 	if n.DomSubtree == nil {
@@ -105,9 +122,9 @@
 		log.Printf("  I'm a %v :-)", tag)
 		return n
 	}
-	if n.Parent != nil {
+	if n.parent != nil {
 		log.Printf("  go to my parent")
-		return n.Parent.Ancestor(tag)
+		return n.parent.Ancestor(tag)
 	}
 	return nil
 }
@@ -167,7 +184,7 @@
 			path = append(path, nRef)
 		}
 	}
-	for p := n.Parent; p != nil; p = p.Parent {
+	for p := n.parent; p != nil; p = p.parent {
 		if part := p.Data(); part != "html" && part != "body" {
 			if pRef, ok := p.queryRef(); ok {
 				path = append([]string{pRef}, path...)
@@ -194,12 +211,12 @@
 
 	ref = n.Data()
 
-	if n.Parent == nil {
+	if n.parent == nil {
 		return ref, true
 	}
 
 	i := 1
-	for _, c := range n.Parent.Children {
+	for _, c := range n.parent.Children {
 		if c == n {
 			break
 		}
@@ -285,7 +302,7 @@
 			&Node{
 				DomSubtree: c,
 				Wrappable: true,
-				Parent: n,
+				parent: n,
 			},
 		}
 	}
--- a/style/stylesheets.go
+++ b/style/stylesheets.go
@@ -113,7 +113,6 @@
 	if err != nil {
 		return nil, fmt.Errorf("fetch rules: %w", err)
 	}
-	//fmt.Printf("mr=%+v", mr)
 	m = make(map[*html.Node]Map)
 	for n, rs := range mr {
 		ds := make(map[string]css.Declaration)
@@ -125,7 +124,7 @@
 				ds[d.Property] = *d
 			}
 		}
-		m[n] = Map{ds}
+		m[n] = Map{Declarations: ds}
 	}
 	return
 }
@@ -147,9 +146,7 @@
 				log.Printf("cssSel compile %v: %v", sel.Value, err)
 				continue
 			}
-			//fmt.Printf("cs=%+v\n", cs)
 			for _, el := range cascadia.QueryAll(doc, cs) {
-				//fmt.Printf("el==%+v\n", el)
 				existing, ok := m[el]
 				if !ok {
 					existing = make([]*css.Rule, 0, 3)
@@ -172,7 +169,7 @@
 		if rMaxWidth.MatchString(r.Prelude) {
 			m := rMaxWidth.FindStringSubmatch(r.Prelude)
 			l := m[1]+m[2]
-			maxWidth, _, err := length(l)
+			maxWidth, _, err := length(nil, l)
 			if err != nil {
 				return nil, fmt.Errorf("atoi: %w", err)
 			}
@@ -183,7 +180,7 @@
 		if rMinWidth.MatchString(r.Prelude) {
 			m := rMinWidth.FindStringSubmatch(r.Prelude)
 			l := m[1]+m[2]
-			minWidth, _, err := length(l)
+			minWidth, _, err := length(nil, l)
 			if err != nil {
 				return nil, fmt.Errorf("atoi: %w", err)
 			}
@@ -200,8 +197,14 @@
 	return
 }
 
+type DomTree interface {
+	Parent() (p DomTree, ok bool)
+	Style()  Map
+}
+
 type Map struct {
 	Declarations map[string]css.Declaration
+	DomTree      `json:"-"`
 }
 
 func NewMap(n *html.Node) Map {
@@ -509,7 +512,7 @@
 		parts := strings.Split(all.Value, " ")
 		nums := make([]int, len(parts))
 		for i, p := range parts {
-			if f, _, err := length(p); err == nil {
+			if f, _, err := length(cs, p); err == nil {
 				nums[i] = int(f)
 			} else {
 				return s, fmt.Errorf("length: %w", err)
@@ -547,7 +550,7 @@
 	return
 }
 
-func length(l string) (f float64, unit string, err error) {
+func length(cs *Map, l string) (f float64, unit string, err error) {
 	var s string
 
 	if l == "auto" || l == "inherit" || l == "0" {
@@ -577,22 +580,27 @@
 	case "vh":
 		f *= float64(WindowHeight) / 100.0
 	case "%":
-		f = 0
+		if cs == nil {
+			return 0.0, "%", nil
+		}
+		var wp int
+		if p, ok := cs.DomTree.Parent(); ok {
+			wp = p.Style().baseWidth()
+		} else {
+			log.Errorf("%% unit used in root element")
+		}
+		f *= 0.01 * float64(wp)
 	default:
 		return f, unit, fmt.Errorf("unknown suffix: %v", l)
 	}
 
-	if dui != nil {
-		f = float64(dui.Scale(int(f)))
-	}
-
 	return
 }
 
-func (cs Map) Height() int {
+func (cs *Map) Height() int {
 	d, ok := cs.Declarations["height"]
 	if ok {
-		f, _, err := length(d.Value)
+		f, _, err := length(cs, d.Value)
 		if err != nil {
 			log.Errorf("cannot parse height: %v", err)
 		}
@@ -604,15 +612,31 @@
 func (cs Map) Width() int {
 	d, ok := cs.Declarations["width"]
 	if ok {
-		f, _, err := length(d.Value)
+		f, _, err := length(&cs, d.Value)
 		if err != nil {
 			log.Errorf("cannot parse width: %v", err)
 		}
 		return int(f)
 	}
+	if _, ok := cs.DomTree.Parent(); !ok {
+		return WindowWidth
+	}
 	return 0
 }
 
+// baseWidth to calculate relative widths
+func (cs Map) baseWidth() int {
+	if w := cs.Width(); w != 0 {
+		return w
+	}
+	if p, ok := cs.DomTree.Parent(); !ok {
+		return WindowWidth
+	} else {
+		return p.Style().baseWidth()
+	}
+	return 0
+}
+
 func (cs Map) Css(propName string) string {
 	d, ok := cs.Declarations[propName]
 	if !ok {
@@ -621,12 +645,12 @@
 	return d.Value
 }
 
-func (cs Map) CssPx(propName string) (l int, err error) {
+func (cs *Map) CssPx(propName string) (l int, err error) {
 	d, ok := cs.Declarations[propName]
 	if !ok {
 		return 0, fmt.Errorf("property doesn't exist")
 	}
-	f, _, err := length(d.Value)
+	f, _, err := length(cs, d.Value)
 	if err != nil {
 		return 0, err
 	}
--- a/style/stylesheets_test.go
+++ b/style/stylesheets_test.go
@@ -116,13 +116,13 @@
 
 		if w == 400 {
 			_ =m[body][0]
-			if m[body][0].Declarations[0].Value != "lightblue" {
-				t.Fail()
+			if v := m[body][0].Declarations[0].Value; v != "lightblue" {
+				t.Fatalf("%v", v)
 			}
 			t.Logf("%v", m[body][0].Name)
 		} else {
 			if _, ok := m[body]; ok {
-				t.Fail()
+				t.Fatalf("body ok")
 			}
 		}
 	}
@@ -295,7 +295,7 @@
 		"10%": 0,
 	}
 	for l, px := range lpx {
-		f, _, err := length(l)
+		f, _, err := length(nil, l)
 		if err != nil {
 			t.Fatalf("%v: %v", l, err)
 		}