hi there,
I am trying to understand the model behind writing lines of text in Gio.
more precisely, in gonum/plot, I am trying to devise a bounding box around a piece of text, as tightly as possible.
I hacked a couple of lines in Gio to display the lines bounding boxes to see what gives:
===
diff --git a/widget/label.go b/widget/label.go
index 629731d..6d783ec 100644
--- a/widget/label.go
+++ b/widget/label.go
@@ -5,8 +5,10 @@ package widget
import (
"fmt"
"image"
+ "image/color"
"unicode/utf8"
+ "gioui.org/f32"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
@@ -117,6 +119,59 @@ func (l Label) Layout(gtx layout.Context, s text.Shaper, font text.Font, size un
paint.PaintOp{}.Add(gtx.Ops)
stack.Pop()
}
+
+ if true {
+ // glyphboxes
+ it := lineIterator{
+ Lines: lines,
+ Clip: cl,
+ Alignment: l.Alignment,
+ Width: dims.Size.X,
+ }
+ for {
+ var line text.Line
+ if len(it.Lines) > 0 {
+ line = it.Lines[0]
+ }
+ _, off, ok := it.Next()
+ if !ok {
+ break
+ }
+ stack := op.Push(gtx.Ops)
+ op.Offset(layout.FPt(off)).Add(gtx.Ops)
+ var p clip.Path
+ p.Begin(gtx.Ops)
+ fpt := func(v fixed.Int26_6) float32 {
+ return float32(v.Floor())
+ }
+ p.MoveTo(f32.Point{
+ X: fpt(line.Bounds.Min.X),
+ Y: fpt(line.Bounds.Min.Y),
+ })
+ p.LineTo(f32.Point{
+ X: fpt(line.Bounds.Max.X),
+ Y: fpt(line.Bounds.Min.Y),
+ })
+ p.LineTo(f32.Point{
+ X: fpt(line.Bounds.Max.X),
+ Y: fpt(line.Bounds.Max.Y),
+ })
+ p.LineTo(f32.Point{
+ X: fpt(line.Bounds.Min.X),
+ Y: fpt(line.Bounds.Max.Y),
+ })
+ p.Close()
+ clip.Stroke{
+ Path: p.End(),
+ Style: clip.StrokeStyle{
+ Width: 2,
+ },
+ }.Op().Add(gtx.Ops)
+ paint.Fill(gtx.Ops, color.NRGBA{R: 255, B: 255, A: 125})
+ stack.Pop()
+ }
+
+ }
return dims
}
===
this gave something like:
- https://github.com/gonum/plot/pull/650#issuecomment-745471755
(the red lines are the gonum/plot bounding boxes, the magenta ones are the Gio ones)
from this little experiment, it seems a multi-line piece of text's height is defined as (from the top line to the bottom):
- ascent [line n]
- descent [line n]
- line-gap [line n]
- cap-height [line n-1]
- descent [line n-1]
- line-gap [line n-1]
[...]
- cap-height [line 0]
- descent [line 0]
- line-gap [line 0]
is that mental model correct ?
there's probably something wrong with that mental model, though, because applying this formula to compute the height of a bounding box:
nLines := computeNumberLines(txt)
ftMetrics := sfnt.Font.Metrics(...)
desc := ftMetrics.Height - ftMetrics.Ascent // total descent (descent + line-gap)
height := nLines * (ftMetrics.CapHeight + desc) - ftMetrics.CapHeight + ftMetrics.Ascent
leads to some part of the many-line text blocks to be cut off. (as can be seen on the faint red-lines for the "Bg\nBg\nBg\nBg\nBg" text block)
what am I missing?
cheers,
-s
I haven't thought through your email yet, but wanted to point out that
I've wanted to change the multi-line layout algorithm to match Figma
(and CSS). See
https://www.figma.com/blog/line-height-changes/
With the Figma approach I hope to get rid of some magic padding around
the border of Editor (and maybe Label?). For example textPadding[0] and
pointerPadding[1].
Elias
[0] https://git.sr.ht/~eliasnaur/gio/tree/main/widget/editor.go#L398
[1] https://git.sr.ht/~eliasnaur/gio/tree/main/widget/editor.go#L423
On Wed Dec 16, 2020 at 09:43, Sebastien Binet wrote:
> hi there,>> I am trying to understand the model behind writing lines of text in Gio.>> more precisely, in gonum/plot, I am trying to devise a bounding box around a piece of text, as tightly as possible.>> I hacked a couple of lines in Gio to display the lines bounding boxes to see what gives:>> ===> diff --git a/widget/label.go b/widget/label.go> index 629731d..6d783ec 100644> --- a/widget/label.go> +++ b/widget/label.go> @@ -5,8 +5,10 @@ package widget> import (> "fmt"> "image"> + "image/color"> "unicode/utf8">> + "gioui.org/f32"> "gioui.org/layout"> "gioui.org/op"> "gioui.org/op/clip"> @@ -117,6 +119,59 @@ func (l Label) Layout(gtx layout.Context, s text.Shaper, font text.Font, size un> paint.PaintOp{}.Add(gtx.Ops)> stack.Pop()> }> +> + if true {> + // glyphboxes> + it := lineIterator{> + Lines: lines,> + Clip: cl,> + Alignment: l.Alignment,> + Width: dims.Size.X,> + }> + for {> + var line text.Line> + if len(it.Lines) > 0 {> + line = it.Lines[0]> + }> + _, off, ok := it.Next()> + if !ok {> + break> + }> + stack := op.Push(gtx.Ops)> + op.Offset(layout.FPt(off)).Add(gtx.Ops)> + var p clip.Path> + p.Begin(gtx.Ops)> + fpt := func(v fixed.Int26_6) float32 {> + return float32(v.Floor())> + }> + p.MoveTo(f32.Point{> + X: fpt(line.Bounds.Min.X),> + Y: fpt(line.Bounds.Min.Y),> + })> + p.LineTo(f32.Point{> + X: fpt(line.Bounds.Max.X),> + Y: fpt(line.Bounds.Min.Y),> + })> + p.LineTo(f32.Point{> + X: fpt(line.Bounds.Max.X),> + Y: fpt(line.Bounds.Max.Y),> + })> + p.LineTo(f32.Point{> + X: fpt(line.Bounds.Min.X),> + Y: fpt(line.Bounds.Max.Y),> + })> + p.Close()> + clip.Stroke{> + Path: p.End(),> + Style: clip.StrokeStyle{> + Width: 2,> + },> + }.Op().Add(gtx.Ops)> + paint.Fill(gtx.Ops, color.NRGBA{R: 255, B: 255, A: 125})> + stack.Pop()> + }> +> + }> return dims> }> ===>> this gave something like:> - https://github.com/gonum/plot/pull/650#issuecomment-745471755>> (the red lines are the gonum/plot bounding boxes, the magenta ones are the Gio ones)>> from this little experiment, it seems a multi-line piece of text's height is defined as (from the top line to the bottom):> - ascent [line n]> - descent [line n]> - line-gap [line n]> - cap-height [line n-1]> - descent [line n-1]> - line-gap [line n-1]> [...]> - cap-height [line 0]> - descent [line 0]> - line-gap [line 0]>> is that mental model correct ?> there's probably something wrong with that mental model, though, because applying this formula to compute the height of a bounding box:>> nLines := computeNumberLines(txt)> ftMetrics := sfnt.Font.Metrics(...)> desc := ftMetrics.Height - ftMetrics.Ascent // total descent (descent + line-gap)> height := nLines * (ftMetrics.CapHeight + desc) - ftMetrics.CapHeight + ftMetrics.Ascent>> leads to some part of the many-line text blocks to be cut off. (as can be seen on the faint red-lines for the "Bg\nBg\nBg\nBg\nBg" text block)>> what am I missing?>> cheers,> -s
On Wed Dec 16, 2020 at 10:43 AM CET, Sebastien Binet wrote:
> hi there,>> I am trying to understand the model behind writing lines of text in Gio.>> more precisely, in gonum/plot, I am trying to devise a bounding box> around a piece of text, as tightly as possible.>> I hacked a couple of lines in Gio to display the lines bounding boxes to> see what gives:>> ===> diff --git a/widget/label.go b/widget/label.go> index 629731d..6d783ec 100644> --- a/widget/label.go> +++ b/widget/label.go> @@ -5,8 +5,10 @@ package widget> import (> "fmt"> "image"> + "image/color"> "unicode/utf8">> + "gioui.org/f32"> "gioui.org/layout"> "gioui.org/op"> "gioui.org/op/clip"> @@ -117,6 +119,59 @@ func (l Label) Layout(gtx layout.Context, s> text.Shaper, font text.Font, size un> paint.PaintOp{}.Add(gtx.Ops)> stack.Pop()> }> +> + if true {> + // glyphboxes> + it := lineIterator{> + Lines: lines,> + Clip: cl,> + Alignment: l.Alignment,> + Width: dims.Size.X,> + }> + for {> + var line text.Line> + if len(it.Lines) > 0 {> + line = it.Lines[0]> + }> + _, off, ok := it.Next()> + if !ok {> + break> + }> + stack := op.Push(gtx.Ops)> + op.Offset(layout.FPt(off)).Add(gtx.Ops)> + var p clip.Path> + p.Begin(gtx.Ops)> + fpt := func(v fixed.Int26_6) float32 {> + return float32(v.Floor())> + }> + p.MoveTo(f32.Point{> + X: fpt(line.Bounds.Min.X),> + Y: fpt(line.Bounds.Min.Y),> + })> + p.LineTo(f32.Point{> + X: fpt(line.Bounds.Max.X),> + Y: fpt(line.Bounds.Min.Y),> + })> + p.LineTo(f32.Point{> + X: fpt(line.Bounds.Max.X),> + Y: fpt(line.Bounds.Max.Y),> + })> + p.LineTo(f32.Point{> + X: fpt(line.Bounds.Min.X),> + Y: fpt(line.Bounds.Max.Y),> + })> + p.Close()> + clip.Stroke{> + Path: p.End(),> + Style: clip.StrokeStyle{> + Width: 2,> + },> + }.Op().Add(gtx.Ops)> + paint.Fill(gtx.Ops, color.NRGBA{R: 255, B: 255, A: 125})> + stack.Pop()> + }> +> + }> return dims> }> ===>> this gave something like:> - https://github.com/gonum/plot/pull/650#issuecomment-745471755>> (the red lines are the gonum/plot bounding boxes, the magenta ones are> the Gio ones)>> from this little experiment, it seems a multi-line piece of text's> height is defined as (from the top line to the bottom):> - ascent [line n]> - descent [line n]> - line-gap [line n]> - cap-height [line n-1]> - descent [line n-1]> - line-gap [line n-1]> [...]> - cap-height [line 0]> - descent [line 0]> - line-gap [line 0]>> is that mental model correct ?> there's probably something wrong with that mental model, though, because> applying this formula to compute the height of a bounding box:>
There may still be a layout bug in Editor/Label, but note that the
cap-height is not used anywhere. text.Line[0] only defines Ascent and
Descent (inlcuding line-gap). Did I miss something?
Elias
[0] https://git.sr.ht/~eliasnaur/gio/tree/main/text/text.go#L14
On Fri Dec 18, 2020 at 09:54 CET, Elias Naur wrote:
[...]
> > BTW: any interest in packaging this into a "debugging" layout?> > I surmise it could be useful for some people.> >> > propably guarded behind a top-level 'const enableGlyphBoxes = false` ?> >>> Hmm, I like to idea of debugging guides, but would rather not pollute> the already complex Label/Editor layout code. One idea is to add the> debugging aids to the clip paths of each line or chunk of text, leaving> Editor and Label oblivious to them. And it would work everywhere text is> laid out.
so having the glyphboxes being drawn during the textPath (in
font/opentype)?
I'll give it a stab over the week-end.
-s
On Fri Dec 18, 2020 at 09:39, Sebastien Binet wrote:
> On Fri Dec 18, 2020 at 09:54 CET, Elias Naur wrote:> [...]>> > > BTW: any interest in packaging this into a "debugging" layout?> > > I surmise it could be useful for some people.> > >> > > propably guarded behind a top-level 'const enableGlyphBoxes = false` ?> > >> >> > Hmm, I like to idea of debugging guides, but would rather not pollute> > the already complex Label/Editor layout code. One idea is to add the> > debugging aids to the clip paths of each line or chunk of text, leaving> > Editor and Label oblivious to them. And it would work everywhere text is> > laid out.>> so having the glyphboxes being drawn during the textPath (in> font/opentype)?>> I'll give it a stab over the week-end.>
Sounds good to me, thanks.
Elias