This commit enables consumers of the text shaper to select a policy for how
line breaking candidates will be chosen. The new default policy can break lines
within "words" (UAX#14 segments) when words do not fit by themselves on a line.
This ensures that text does not horizontally overflow its bounding box unless
the available width is insufficient to display a single UAX#29 grapheme cluster.
Fixes: https://todo.sr.ht/~eliasnaur/gio/467
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit adds back support for loading font collections, which we
lost when switching to the harfbuzz-based shaper last January. In
addition, this commit takes advantage of our new font loading library's
metadata facilities to automatically construct text.FontFaces for all
fonts within a collection. This is significantly more ergonomic for
users, and can be used to load single fonts with automatic metadata
detection as well.
I've exposed a opentype.Face.Font() method that can be used to get the
font metadata for a given face as well, though you have to type assert to
see it:
var myFace text.Face
if asOpentype, ok := myFace.(opentype.Face); ok {
myFont := asOpentype.Font()
}
The one problem with this approach is that the font variant field always
be automatically populated. Mono font detection is supported, but
other variants like SmallCaps are more complicated and may need to be
expressed differently in the future (smallcaps is a feature that any font
file can have, not necessarily a separate font file). See this [0] upstream
issue for details.
Additionally, in order to avoid import cycles, I've moved the declarations
of font attributes to package font. You can fix your code automatically to
refer to the new definitions by running the following:
gofmt -w -r 'text.FontFace -> font.FontFace' .
gofmt -w -r 'text.Variant -> font.Variant' .
gofmt -w -r 'text.Style -> font.Style' .
gofmt -w -r 'text.Typeface -> font.Typeface' .
gofmt -w -r 'text.Font -> font.Font' .
gofmt -w -r 'text.Regular -> font.Regular' .
gofmt -w -r 'text.Italic -> font.Italic' .
gofmt -w -r 'text.Thin -> font.Thin' .
gofmt -w -r 'text.ExtraLight -> font.ExtraLight' .
gofmt -w -r 'text.Light -> font.Light' .
gofmt -w -r 'text.Normal -> font.Normal' .
gofmt -w -r 'text.Medium -> font.Medium' .
gofmt -w -r 'text.SemiBold -> font.SemiBold' .
gofmt -w -r 'text.Bold -> font.Bold' .
gofmt -w -r 'text.ExtraBold -> font.ExtraBold' .
gofmt -w -r 'text.Black -> font.Black' .
gofmt -w -r 'text.Hairline -> font.Thin' .
gofmt -w -r 'text.UltraLight -> font.ExtraLight' .
gofmt -w -r 'text.DemiBold -> font.SemiBold' .
gofmt -w -r 'text.UltraBold -> font.ExtraBold' .
gofmt -w -r 'text.Heavy -> font.Black' .
gofmt -w -r 'text.ExtraBlack -> font.Black+50' .
gofmt -w -r 'text.UltraBlack -> font.ExtraBlack' .
Make sure each affected file imports gioui.org/font.
[0] https://github.com/go-text/typesetting/issues/57
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit picks up improvements in upstream go-text that (among other things)
allow the shaper to reuse a lot of information when shaping the same font face
multiple times (using an LRU cache to keep that information available). I've
tried to pick a reasonable default LRU size of 32 faces.
My simple benchmarks indicate a definitive performance gain and reduction in
memory use across the board, which is especially noticable for complex fonts
like arabic and emoji.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit fixes a subtle problem when trunating text widgets that contain
multiple newline-delimited paragraphs.
Paragraphs are the unit of text shaping, so we divide the text into paragraphs
and then iterate those paragraphs performing shaping and line wrapping. If we
have a maximum number of lines to fill, we stop iterating paragraphs when we
use all of the available lines. Usually, if we fill all of the lines the text
shaper will insert the truncator symbol. However, if we exactly fill all of the
lines with the end of a paragraph, the line wrapper is able to fill the line
quota without actually truncating any of the text in that paragraph. Thus it
doesn't insert a truncator even though subsequent paragraphs were truncated (it
has no way to know).
To fix this, I've taught the line wrapper about an explicit scenario in which
we always want to show the truncator symbol *if* we hit the line limit, even if
all of the text in the current paragraph fit. I've then plumbed support for
that through our text stack.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit adds support for the idea of a text "Truncator", a string
that is shown at the end of truncated text to indicate that it has been
shortened because it would not fit within the requested number of lines.
When specifying a maximum number of lines, a truncator symbol is always
used. If the user does not provide one, the rune `…` is used. This
requirement results in a better user experience and significantly simpler
code, as we can rely upon the presence of one or more truncator glyphs in
the output glyph stream when truncation has occurred.
When interacting with truncated text, the truncator glyphs all act as
a single, indivisible unit. They can be selected or not, and if selected
they act as the entire contents of the truncated portion of the text.
This means that copying all of a truncated label will copy the entire
label text content, with the truncator symbol not appearing at all.
Concretely, the exposed text API now accepts a Truncator string in
text.Parameters, and there is a new glyph flag FlagTruncator which indicates
that the glyph is part of the truncator run. The truncator run will only
have a single FlagClusterBreak (even if the run would usually have many),
and the glyph with both FlagClusterBreak and FlagTruncator will have the
quantity of truncated runes in its Runes field. This necessitated increasing
the size of the Runes field from a byte to an int, as it's theoretically possible
for quite a lot of text to be truncated.
This commit necessarily bumps our go-text/typesetting dependency to the version
exposing truncation in the exported API.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit upgrades our version of eliasnaur.com/font to include a color
emoji font and uses that to benchmark displaying large quantities of emoji.
As expected, this is very slow when the strings change frequently, and uses
silly amounts of memory. Future commits will work to improve this.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit upgrades our go-text version to the latest one which internalizes
harfbuzz and supports text truncators. This allows us to drop our dependency
upon Benoit's textlayout package.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit pushes limiting the maximum number of lines of text into
the shaper implementation. This is more efficient than doing it in
widgets, and also opens the door for future use of the shaper to
insert ellipsis and other truncating characters as appropriate.
I realized that we lost the implementation of limiting the number of
lines of text in my text stack overhaul, so this fixes a regression
from that work.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit restructures the entire text shaping stack to enable lines of shaped text to
have non-homogeneous properties like which font face they belong to and which direction
a segment of text is going.
The text package now provides a concrete type text.Shaper which can be used to convert
strings into sequences of renderable text.Glyphs. At a high level, the API is used
like this:
// Prepare some fonts.
var collection []text.FontFace
// Make a shaper with those fonts loaded.
shaper := text.NewShaper(collection)
// Shape a string.
shaper.LayoutString(text.Parameters{
PxPerEm: fixed.I(12),
}, 0, 100, system.Locale{}, "Hello")
// Iterate the glyphs from that string.
for glyph, ok := shaper.NextGlyph(); ok; glyph, ok = shaper.NextGlyph() {
// Convert the glyph data into a path. In real uses, convert batches of glyphs
// rather than single glyphs to reduce the number of individual paths and offsets
// required to display your text.
shape := shaper.Shape([]text.Glyph{glyph})
// Offset the glyph to the position it declares within its fields. This will
// automatically handle correct bidirectional text glyph positioning.
offset := op.Offset(image.Pt(glyph.X.Floor(), int(glyph.Y))).Push(gtx.Ops)
// Create a clip area from the shape of the glyph.
area := clip.Outline{Path: shape}.Push(gtx.Ops)
// Paint whatever the current color is within the glyph's shape.
paint.PaintOp{}.Add(gtx.Ops)
area.Pop()
offset.Pop()
}
This API will transparently handle both font fallback (choosing appropriate fonts
from those loaded when the primary font doesn't contain a required glyph) and
bidirectional text (mixed left-to-right and right-to-left text). Glyphs are
iterated in order of the input runes, not their visual order, but proper use
of the provided offsets will ensure that text always displays correctly.
Thanks to Elias Naur for suggesting this glyph iterator strategy. It let us cut
through a lot of accumulated complexity from trying to match our old text APIs,
meaning that this change actually is a net negative change in lines of code.
This commit consumes the upstream github.com/go-text/typesetting/shaping API
now that my prior work is merged there, removing the need for the font/opentype/internal
package entirely.
As part of my efforts, I fuzzed both the low-level text shaping stack and the
editor widget extensively. I've committed regression tests found that way into
the appropriate testdata files to ensure the fuzzer re-checks them.
Fixes: https://todo.sr.ht/~eliasnaur/gio/425
Fixes: https://todo.sr.ht/~eliasnaur/gio/211
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Bump golang/x/exp/shiny to use the specific module,
instead of using a single all-encompassing module.
This significantly reduces the number of packages in go.sum.
Unfortunately, this requires us to bump the go.mod language
version to 1.18, otherwise using `go mod tidy` and similar
tools will complain about incompatibility with module lookup
in Go 1.16. The code should still compile with 1.17.
Bump github.com/gioui/uax, which gets rid one additional unneeded
dependency and should also reduce the binary size.
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
This commit updates to a newer version of textlayout
and switches to a fork of the UAX library that builds
properly on 32-bit machines. This should fix 32-bit Gio
compilation for the time being. I hope to switch back
to npillmayer's UAX as soon as he has time to review
the pending pull requests.
Fixes: https://todo.sr.ht/~eliasnaur/gio/384
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit introduces a new text shaping infrastructure
powered by Benoit Kugler's Go source-port of harfbuzz.
This shaper can properly display complex scripts and RTL
text. This commit changes the signature of the text.Shaper
function, which is a breaking API change.
The new functionality is available via opentype.ParseHarfbuzz,
which configures a text.Shaper leveraging the new backend.
References: https://todo.sr.ht/~eliasnaur/gio/146
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Vulkan textures (VkImage) are always in a particular layout, where each
layout is optimized for a particular use (transfer, sampling, compute
storage). Vulkan allows layout transitions everywhere except inside
render passes. This change adds driver.Device.PrepareTexture for
instructing the driver to switch a texture to a layout for sampling
in preparation for using it in a render pass.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Vulkan doesn't support changing uniforms during a render pass. However,
push constants *can* be changed. The gio-shader repository was changed
to use push constants instead of uniforms, this change implements the
corresponding driver and renderer change.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This change implements support for compute programs in the Direct3D 11
driver. The compute renderer doesn't work for me yet; my NVIDIA GTX 970
and Intel GPUs both display corrupted output and hangs.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Change 44adf01768 ugpraded
gioui.org/shader for the OpenGL ES 2.0 fix, but the fix isn't included
in v1.0.0 change d80992fc66 switched to.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Changes to the gioui.org/shader module are generally breaking changes,
which means that a particular version of gioui.org must be build with a
particular version of gioui.org/shader. Until now, the gpu package
checked the module version against an expected version and would fail at
runtime if there's a mismatch.
This change replaces all that complexity with a simple procedural
change: bump the module major version of gioui.org/shader at each such
incompatible change. It doesn't matter that we'll eventually reach
gioui.org/v1234/shader; the module is internal and won't break clients.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The OpenGL (ES) implementations on Apple platforms are deprecated and
don't support GPU compute programs. This change adds support for the
replacement, the Metal GPU API.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This change adds a CPU fallback for devices that don't support the old
renderer nor have GPU support for compute programs.
Most of the hard work is implemented in the gioui.org/cpu module. It
uses the SwiftShader project with light modification to output
statically compiled CPU .o files for each compute program.
The CPU fallback only covers Linux and Android on arm, arm64, amd64
architectures. There is no fundamental reason support can't be extended
to other platforms:
- macOS and iOS are probably easy, but it's likely that virtually every
device has GPU support for compute shaders.
- Windows needs a Cgo-less port, or a build constraint to require a C
compiler (Gio core doesn't).
- FreeBSD and OpenBSD are probably also easy to do because they're so
similar to Linux.
- The 386 binaries didn't work properly in my tests, so fixes to
SwiftShader is probably needed. However, I expect virtually every
Intel device can run amd64 binaries.
Updates gio#49
Fixes gio#228
Signed-off-by: Elias Naur <mail@eliasnaur.com>