ref: 6a5c18f27d9612a4f5e5d0c48f9c3e48fa70f94d
parent: 9d5388d05404585cffbf9092b83301e507110a7d
author: Philip Silva <philip.silva@protonmail.com>
date: Mon Mar 15 09:10:25 EDT 2021
Better text wrapping - handle only a few well-known cases in a first step - wrap texts at duit.UI level and not within duit.Label - only one pass is needed, that is done within InnerNodesToBox
--- a/browser/browser.go
+++ b/browser/browser.go
@@ -280,6 +280,18 @@
log.Errorf("margin: %v", err)
}
+ if n.Css("display") == "inline" {
+ // Actually this doesn't fix the problem to the full extend
+ // exploded texts' elements might still do double and triple
+ // horizontal pads/margins
+ w = 0
+ mw = 0
+ m.Top = 0
+ m.Bottom = 0
+ p.Top = 0
+ p.Bottom = 0
+ }
+
if w == 0 && h == 0 && mw == 0 && i == nil && m == zs && p == zs {
return nil, false
}
@@ -354,7 +366,7 @@
if v := attr(*n.DomSubtree, "value"); v != "" {
t = v
- } else if c := strings.TrimSpace(nodes.ContentFrom(*n)); c != "" {
+ } else if c := strings.TrimSpace(n.ContentString()); c != "" {
t = c
} else {
t = "Submit"
@@ -448,7 +460,7 @@
continue
}
lv := &duit.ListValue{
- Text: nodes.ContentFrom(*c),
+ Text: c.ContentString(),
Value: c.Attr("value"),
Selected: c.HasAttr("selected"),
}
@@ -464,7 +476,7 @@
}
func NewTextArea(n *nodes.Node) *Element {
- t := strings.TrimSpace(nodes.ContentFrom(*n))
+ t := n.ContentString()
formatted := ""
lines := strings.Split(t, "\n")
for _, line := range lines {
@@ -994,7 +1006,7 @@
}
btn := &duit.Button{
- Text: nodes.ContentFrom(*n),
+ Text: n.ContentString(),
Font: n.Font(),
}
@@ -1005,13 +1017,13 @@
return NewElement(NewImage(n), n)
case "pre":
return NewElement(
- NewCodeView(nodes.ContentFrom(*n), n.Map),
+ NewCodeView(n.ContentString(), n.Map),
n,
)
case "li":
var innerContent duit.UI
if nodes.IsPureTextContent(*n) {
- t := nodes.ContentFrom(*n)
+ t := n.ContentString()
if ul := n.Ancestor("ul"); ul != nil {
if ul.Css("list-style") != "none" && n.Css("list-style-type") != "none" {
@@ -1033,7 +1045,7 @@
if nodes.IsPureTextContent(*n) {
innerContent = NewLabel(
- nodes.ContentFrom(*n),
+ n.ContentString(),
n,
)
} else {
@@ -1056,7 +1068,7 @@
// Internal node object
var innerContent duit.UI
if nodes.IsPureTextContent(*n) {
- t := strings.TrimSpace(nodes.ContentFrom(*n))
+ t := n.ContentString()
innerContent = NewLabel(t, n)
} else {
return InnerNodesToBox(r+1, b, n)
@@ -1070,19 +1082,7 @@
} else if n.Type() == html.TextNode {
// Leaf text object
- if text := strings.TrimSpace(nodes.ContentFrom(*n)); text != "" {
- text = strings.ReplaceAll(text, "\n", "")
- text = strings.ReplaceAll(text, "\t", "")
- l := strings.Split(text, " ")
- nn := make([]string, 0, len(l))
-
- for _, w := range l {
- if w != "" {
- nn = append(nn, w)
- }
- }
-
- text = strings.Join(nn, " ")
+ if text := n.ContentString(); text != "" {
ui := NewLabel(text, n)
return NewElement(
@@ -1097,11 +1097,45 @@
}
}
+func isWrapped(n *nodes.Node) bool {
+ isText := nodes.IsPureTextContent(*n)
+ isCTag := false
+ for _, t := range []string{"span", "i", "b", "tt"} {
+ if n.Data() == t {
+ isCTag = true
+ }
+ }
+ return ((isCTag && n.IsInline()) || n.Type() == html.TextNode) && isText
+}
+
func InnerNodesToBox(r int, b *Browser, n *nodes.Node) *Element {
els := make([]*Element, 0, len(n.Children))
for _, c := range n.Children {
- if el := NodeToBox(r+1, b, c); el != nil && !c.IsDisplayNone() {
+ if c.IsDisplayNone() {
+ continue
+ }
+ if isWrapped(c) {
+ tt := strings.Join(c.Content(), " ")
+
+ // '\n' is nowhere visible
+ tt = strings.Replace(tt, "\n", " ", -1)
+
+ ts := strings.Split(tt, " ")
+ for _, t := range ts {
+ t = strings.TrimSpace(t)
+
+ if t == "" {
+ continue
+ }
+
+ el := &Element{
+ UI: NewLabel(t, c),
+ n: c,
+ }
+ els = append(els, el)
+ }
+ } else if el := NodeToBox(r+1, b, c); el != nil {
els = append(els, el)
}
}
--- a/browser/browser_test.go
+++ b/browser/browser_test.go
@@ -310,6 +310,33 @@
}
}
+func TestSpansCanBeWrapped(t *testing.T) {
+ htm := `
+ <body>
+ <span>
+ A text with multiple words.
+ </span>
+ </body>
+ `
+ _, boxed, err := digestHtm(htm)
+ if err != nil {
+ t.Fatalf("digest: %v", err)
+ }
+
+ n := 0
+
+ TraverseTree(boxed, func(ui duit.UI) {
+ if el, ok := ui.(*Label); ok {
+ n++
+ fmt.Printf("n data=%v\n", el.n.Data())
+ fmt.Printf("n cls=%v\n", el.n.Attr("class"))
+ }
+ })
+ if n != 5 {
+ t.Errorf("%v", n)
+ }
+}
+
func TestAlwaysOneElement(t *testing.T) {
h := `
<!DOCTYPE html>
--- a/browser/website.go
+++ b/browser/website.go
@@ -185,7 +185,7 @@
nn := nodes.NewNodeTree(n, style.Map{}, make(map[*html.Node]style.Map), nil)
if nm != "" {
- data.Set(nm, nodes.ContentFrom(*nn))
+ data.Set(nm, nn.ContentString())
}
}
--- a/domino/domino.go
+++ b/domino/domino.go
@@ -430,7 +430,7 @@
}
if isJS {
- fn(src, nodes.ContentFrom(*n))
+ fn(src, n.ContentString())
}
}
for _, c := range n.Children {
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -225,20 +225,30 @@
return true
}
-func ContentFrom(n Node) string {
- var content string
+func (n Node) Content() []string {
+ content := make([]string, 0, len(n.Children))
if n.Text != "" && n.Type() == html.TextNode && !n.Map.IsDisplayNone() {
- content += n.Text
+ t := strings.TrimSpace(n.Text)
+ if t != "" {
+ content = append(content, t)
+ }
}
for _, c := range n.Children {
if !c.Map.IsDisplayNone() {
- content += ContentFrom(*c)
+ content = append(content, c.Content()...)
}
}
- return strings.TrimSpace(content)
+ return content
+}
+
+func (n Node) ContentString() (t string) {
+ t = strings.Join(n.Content(), " ")
+ t = strings.TrimSpace(t)
+
+ return
}
func (n *Node) Serialized() (string, error) {
--- a/nodes/nodes_test.go
+++ b/nodes/nodes_test.go
@@ -39,11 +39,11 @@
doc, err := html.Parse(buf)
if err != nil { t.Fatalf(err.Error()) }
n := NewNodeTree(doc, style.Map{}, make(map[*html.Node]style.Map), nil)
- if s := ContentFrom(*n); s != "initial" {
+ if s := n.ContentString(); s != "initial" {
t.Fatalf(s)
}
n.SetText("123")
- if s := ContentFrom(*n); s != "123" {
+ if s := n.ContentString(); s != "123" {
t.Fatalf(s)
}
}