~eliasnaur/gio#104: 
Support fonts with broad unicode coverage

This is an enhancement request. The goal: display text in the GUI such that the unicode substitute character is unlikely to appear for reasonable human-provided input. This is important in, for example, communication programs.

My understanding is that this is currently not possible if reasonable includes anything beyond a single unicode plane (e.g., both the basic multilingual and supplemental multilingual planes). The reason for this is that each unicode plane contains 2^16 code points, the default text.Shaper is a text.FontRegistry that uses exactly one sfnt.Font for rendering, and the SFNT format is limited to a maximum of 2^16 glyphs (and in practice, slightly less than that).

If this is correct, then it is currently quite difficult to construct a single SFNT file that supports all modern languages in addition to potentially desirable features like emoji. It may currently be impossible (or may soon become impossible) depending on the exact ranges that one wants to support.

There are a couple of potential paths forward that are immediately apparent. Other projects, such as many Linux distributions, have worked around this problem by introducing the notion of fallback fonts, where an ordered list of SFNT files is provided, and if a glyph is not found in a file, the next file in the list is searched. This list could be provided explicitly for a single text.Font within the text.FontRegistry when registering the font face. Another possibility is to introduce support for OpenType Collection files, which are basically multiple fonts included in a single archive. Both solutions would require a list of fonts to be stored somewhere---possibly in the text.Face implementation, or maybe in the text.FontRegistry.

It may be possible to work around this constraint in some applications where the text can be assumed to be from a small subset of languages by allowing the user to choose which font to use, or by inferring this from the detected glyphs. However, for some programs it may be more desirable to have a single font (or font list) that supports all potential inputs.

Status
RESOLVED FIXED
Submitter
~tainted-bit
Assigned to
No-one
Submitted
2 months ago
Updated
11 hours ago
Labels
No labels applied.

~tainted-bit 13 days ago

I've submitted patch 11340 containing one possible way to resolve the issue. We can make the existing opentype.Collection struct implement text.Face by using this fallback font concept with the SFNT data within the collection file. This moves the burden of preparing an appropriate file to the developer, but makes the support within Gio quite trivial (simply use an opentype.Collection instead of an opentype.Font in the text.FontFace slice when calling material.NewTheme).

An advantage of this approach is that by implementing the logic in the opentype package, we have access to the underlying sfnt.GlyphIndex function, which is necessary to determine whether an SFNT font contains a glyph for a given rune, and so the logic is relatively contained (e.g., no changes to the text.Face interface are necessary). A disadvantage is that this approach does not enable preparing a complex font list. For example, it is not possible for a developer to specify a font list composed of a subset of fonts from an opentype.Collection and other text.Face implementations from files in a different format. We could support this by discarding this patch and implementing the fallback logic at a higher layer (e.g., as a text.CompositeFace struct), but this would require expanding the text.Face interface with something like a HasGlyph function, thereby becoming a more invasive change.

To test out this patch, prepare a font collection file and use it as the text.Face for a label. I've prepared a sample font collection for testing: it is a combination of the Roboto Black, Noto Sans CJK Simplified Chinese, and Noto (Black & White) Emoji fonts, in that order. This is good for testing because it clearly shows that ASCII glyphs come from Roboto Black (which takes priority over the Noto fonts that also contain ASCII glyphs), Chinese glyphs are supported, and emoji (from the supplementary multilingual unicode plane) are also supported. Although most guides to preparing OTC/TTC files suggest using FontForge, I've had far more success using the otf2otc tool from github.com/adobe-type-tools/afdko. Given an OTC/TTC file, the example/hello program is easily modified to test strings like Hello, 世界 🎉 with this patch using a theme like this:

f, err := os.Open("weird-combo.ttc")
if err != nil {
    return err
}
ttc, err := opentype.ParseCollectionReaderAt(f)
if err != nil {
    return err
}
th := material.NewTheme([]text.FontFace{{Face: ttc}})

Aside from the overall approach, this initial patch takes a potentially controversial approach to reporting the font.Metrics for the opentype.Collection. I could not find any clear guidance about how metrics for font collections should be interpreted. Specifically, each SFNT file within an OTC/TTC collection comes with its own metrics (x and cap heights, ascent, descent, etc.) and glyph bounds. Rather than requiring that these all match, this patch takes the approach of computing the most extreme values to report as the overall metrics. For example, the cap height is the maximum of all SFNT cap heights, and the glyph bounding box is the union of all SFNT bounding boxes. One place where this gets quite arbitrary is how we should report the caret slope. This patch takes an approach that will usually select the slope from the SFNT with the greatest deviation from vertical, but it is not at all clear that that is the right thing to do (the right thing would probably be to dynamically adjust the caret slope depending on the fonts used for glyphs on either side of the current caret position, but that would require some refactoring that could probably wait for another patch). The patch also caches the computed metrics and bounds for a given ppem without any sort of cache eviction, which may or may not be reasonable.

I've submitted the patch for now to see if this is even an architectural approach that seems reasonable, rather than diving deeply into fiddling with alternate approaches to font metrics.

~eliasnaur 12 days ago

On Fri Jun 26, 2020 at 01:57, ~tainted-bit wrote:

I've submitted patch 11340 containing one possible way to resolve the issue. We can make the existing opentype.Collection struct implement text.Face by using this fallback font concept with the SFNT data within the collection file. This moves the burden of preparing an appropriate file to the developer, but makes the support within Gio quite trivial (simply use an opentype.Collection instead of an opentype.Font in the text.FontFace slice when calling material.NewTheme).

Excellent!

An advantage of this approach is that by implementing the logic in the opentype package, we have access to the underlying sfnt.GlyphIndex function, which is necessary to determine whether an SFNT font contains a glyph for a given rune, and so the logic is relatively contained (e.g., no changes to the text.Face interface are necessary). A disadvantage is that this approach does not enable preparing a complex font list. For example, it is not possible for a developer to specify a font list composed of a subset of fonts from an opentype.Collection and other text.Face implementations from files in a different format. We could support this by discarding this patch and implementing the fallback logic at a higher layer (e.g., as a text.CompositeFace struct), but this would require expanding the text.Face interface with something like a HasGlyph function, thereby becoming a more invasive change.

Your design sounds reasonable to me.

To test out this patch, prepare a font collection file and use it as the text.Face for a label. I've prepared a sample font collection for testing: it is a combination of the Roboto Black, Noto Sans CJK Simplified Chinese, and Noto (Black & White) Emoji fonts, in that order. This is good for testing because it clearly shows that ASCII glyphs come from Roboto Black (which takes priority over the Noto fonts that also contain ASCII glyphs), Chinese glyphs are supported, and emoji (from the supplementary multilingual unicode plane) are also supported. Although most guides to preparing OTC/TTC files suggest using FontForge, I've had far more success using the otf2otc tool from github.com/adobe-type- tools/afdko. Given an OTC/TTC file, the example/hello program is easily modified to test strings like Hello, 世界 🎉 with this patch using a theme like this:

f, err := os.Open(weird-combo.ttc) if err != nil { return err } ttc, err := opentype.ParseCollectionReaderAt(f) if err != nil { return err }

th := material.NewTheme([]text.FontFace{{Face: ttc}})

I'd love to see a demo program published somewhere, or even a convenient font package for https://git.sr.ht/~eliasnaur/font or your own repository.

Aside from the overall approach, this initial patch takes a potentially controversial approach to reporting the font.Metrics for the opentype.Collection. I could not find any clear guidance about how metrics for font collections should be interpreted. Specifically, each SFNT file within an OTC/TTC collection comes with its own metrics (x and cap heights, ascent, descent, etc.) and glyph bounds. Rather than requiring that these all match, this patch takes the approach of computing the most extreme values to report as the overall metrics. For example, the cap height is the maximum of all SFNT cap heights, and the glyph bounding box is the union of all SFNT bounding boxes. One place where this gets quite arbitrary is how we should report the caret slope. This patch takes an approach that will usually select the slope from the SFNT with the greatest deviation from vertical, but it is not at all clear that that is the right thing to do (the right thing would probably be to dynamically adjust the caret slope depending on the fonts used for glyphs on either side of the current caret position, but that would require some refactoring that could probably wait for another patch). The patch also caches the computed metrics and bounds for a given ppem without any sort of cache eviction, which may or may not be reasonable.

I took the liberty of removing the otherwise unused Metrics from the text.Face interface in https://gioui.org/commit/913a780d646. Does that help you? In particular, I'd like to avoid a sfnt.Buffer which makes opentype.Collection unsafe to use from multiple goroutines.

-- elias

~tainted-bit 12 days ago

Removing the Metrics from text.Face will definitely simplify this approach drastically. However, we'll still need a sfnt.Buffer for the new Layout and Shape calls on the opentype.Collection. Is it a significant problem for these functions to be unsafe for concurrent use? The other methods (accessing the underlying sfnt.Font slice) will remain safe for concurrent use.

We'll also need to retain the combined metrics calculation for ascent, height, and the glyph bounds, because these are returned in the text.Line values produced by Layout (and subsequently used by widgets). These are fast enough to compute without a cache and have pretty clear algorithms, though.

My weird combo font is not something that should be used in practice―just for demonstrating the fallback behavior. I can prepare an actual font collection for Noto to submit to the font repository, along with a build script to reproduce it with otf2otc in the future as Noto evolves.

~eliasnaur 11 days ago

On Fri Jun 26, 2020 at 7:41 PM CEST, ~tainted-bit wrote:

Removing the Metrics from text.Face will definitely simplify this approach drastically. However, we'll still need a sfnt.Buffer for the new Layout and Shape calls on the opentype.Collection. Is it a significant problem for these functions to be unsafe for concurrent use? The other methods (accessing the underlying sfnt.Font slice) will remain safe for concurrent use.

The motivating use-case is multiple independent windows (https://gioui.org/issue/19), where you'd expect to only need to parse the font data once, but share it among all windows. That's true for gofont.Collection and eliasnaur.com/font/roboto. See also https://gioui.org/commit/b07d34354ea that introduced Cache.

My weird combo font is not something that should be used in practice―just for demonstrating the fallback behavior. I can prepare an actual font collection for Noto to submit to the font repository, along with a build script to reproduce it with otf2otc in the future as Noto evolves.

Thank you.

-- elias

~tainted-bit 7 days ago

I have the patches for this done and I've written the code to produce embeddable full-coverage Noto fonts for Go. The problem is that these font files end up being 60--100 MB, of which I've confirmed most of the data is the actual glyph shapes. Turning that into embeddable Go in an efficient way results in a 2x--4x file size increase, because we do not yet have an official resource embedding system. Now I have 181 Go repositories that are 120--220 MB each. I need to think more about how to handle this data in a better way than uploading 30 GB to Github.

~liangzi 7 days ago

Is there sample code?

~tainted-bit 5 days ago

Patch 11445 contains the revised code. I was initially confused about why it was okay for opentype.Font to use an sfnt.Buffer in multi-window settings when it was not okay for opentype.Collection to do the same. It turns out that it's not okay for either: there is an existing race condition in opentype.Font. It is triggered very rarely, because it only occurs when multiple windows are rendering strings using the same font simultaneously. Moreover, clobbering the sfnt.Buffer may not actually be noticeable depending on what the sfnt package does with it. In any case, this patch also fixes the existing race condition by adding a lock around buffers for both Font and Collection. The locks will have very low contention, so fast path mutex overhead is almost certainly worth the cost in order to keep buffers around to avoid GC pressure.

I've also packaged the Noto font family in public repositories under github.com/gonoto. This makes it easy to test this patch with a simple change to example/hello:

otc, _ := opentype.ParseCollection(notosans.OTC())
th := material.NewTheme([]text.FontFace{{Face: otc}})

~eliasnaur 4 days ago

On Fri Jul 3, 2020 at 19:24, ~tainted-bit wrote:

Patch 11445 contains the revised code. I was initially confused about why it was okay for opentype.Font to use an sfnt.Buffer in multi-window settings when it was not okay for opentype.Collection to do the same. It turns out that it's not okay for either: there is an existing race condition in opentype.Font. It is triggered very rarely, because it only occurs when multiple windows are rendering strings using the same font simultaneously. Moreover, clobbering the sfnt.Buffer may not actually be noticeable depending on what the sfnt package does with it. In any case, this patch also fixes the existing race condition by adding a lock around buffers for both Font and Collection. The locks will have very low contention, so fast path mutex overhead is almost certainly worth the cost in order to keep buffers around to avoid GC pressure.

Until benchmarks show a definite performance improvement, I prefer the simple approach of just not sharing buffers. I fixed opentype.Font in

https://gioui.org/commit/7bbe0da

Thanks for noticing the race.

I've also packaged the Noto font family in public repositories under github.com/gonoto. This makes it easy to test this patch with a simple change to example/hello:

otc, _ := opentype.ParseCollection(notosans.OTC()) th := material.NewTheme([]text.FontFace{{Face: otc}})

I would love an example/fonts example showing off the new feature. Can you add it? I believe you can reuse example/hello.

-- elias

~tainted-bit 4 days ago

Until benchmarks show a definite performance improvement, I prefer the simple approach of just not sharing buffers.

BenchmarkDrawUI in internal/rendertest is a good place to benchmark the approaches, since it calls Layout and Shape on an opentype.Font, but not concurrently (so it is similar to how a single window performs). The lock-vs-no-lock approach we're talking about here is going to be a tiny cost for most applications for the same reason that lock contention would be very low: text.faceCache will cache the layout and paths for most text drawing calls. Consequently the approach we take mainly affects GUIs that make poor use of the cache, such as a program that changes the text it draws very rapidly (e.g., a stopwatch GUI), or a GUI that is being resized by the user. To simulate this, I modified the drawText function in internal/rendertest/bench_test.go to bypass the cache so that it could test the performance of Layout and Shape:

txt.Text = textRows[x] + strconv.Itoa(rand.Int())

Here are the results of 3 runs with -test.benchtime=5s on my i7-6700K:

This patch (using sync.Mutex):

BenchmarkDrawUI-8            399      14885780 ns/op     5600746 B/op       1571 allocs/op
BenchmarkDrawUI-8            398      15011455 ns/op     5601157 B/op       1571 allocs/op
BenchmarkDrawUI-8            399      15005305 ns/op     5600769 B/op       1571 allocs/op

7bbe0da (not sharing sfnt.Buffer):

BenchmarkDrawUI-8            400      15011438 ns/op     5794994 B/op       1891 allocs/op
BenchmarkDrawUI-8            398      14971487 ns/op     5795127 B/op       1891 allocs/op
BenchmarkDrawUI-8            399      15030301 ns/op     5794782 B/op       1891 allocs/op

So in this degenerate case of constantly changing text, both approaches have roughly the same CPU time, but reusing sfnt.Buffer saves many allocs, as expected. The amount that it saves will be proportional to the number of uncached calls to text.Font. The difference between the approaches will be negligible for most GUIs (where the code is rarely called), but measurable for others. So the sync.Mutex approach has superior or equal performance, but slightly more code complexity. Personally I think that using a sync.Mutex and reusing the buffers is simple enough to justify this performance improvement, but I can see why you might disagree.

I would love an example/fonts example showing off the new feature. Can you add it? I believe you can reuse example/hello.

I can certainly add an example for v3 of this patch. However, if it uses the actual Noto OTCs, that will add a 120 MB download as a dependency. For that reason I'm thinking of adding it as a nested module instead of directly in the gioui.org/example module, if that's okay with you.

~tainted-bit 4 days ago

I can certainly add an example for v3 of this patch. However, if it uses the actual Noto OTCs, that will add a 120 MB download as a dependency. For that reason I'm thinking of adding it as a nested module instead of directly in the gioui.org/example module, if that's okay with you.

I've submitted an example program in patch 11458. It doesn't compile without the opentype.Collection patch applied first, of course.

~eliasnaur 2 days ago

On Sat Jul 4, 2020 at 18:59, ~tainted-bit wrote:

Until benchmarks show a definite performance improvement, I prefer the simple approach of just not sharing buffers.

BenchmarkDrawUI in internal/rendertest is a good place to benchmark the approaches, since it calls Layout and Shape on an opentype.Font, but not concurrently (so it is similar to how a single window performs). The lock-vs-no-lock approach we're talking about here is going to be a tiny cost for most applications for the same reason that lock contention would be very low: text.faceCache will cache the layout and paths for most text drawing calls. Consequently the approach we take mainly affects GUIs that make poor use of the cache, such as a program that changes the text it draws very rapidly (e.g., a stopwatch GUI), or a GUI that is being resized by the user. To simulate this, I modified the drawText function in internal/rendertest/bench_test.go to bypass the cache so that it could test the performance of Layout and Shape:

txt.Text = textRows[x] + strconv.Itoa(rand.Int())

Here are the results of 3 runs with -test.benchtime=5s on my i7-6700K:

This patch (using sync.Mutex):

BenchmarkDrawUI-8          399      14885780 ns/op     5600746 B/op       1571 allocs/op
BenchmarkDrawUI-8          398      15011455 ns/op     5601157 B/op       1571 allocs/op
BenchmarkDrawUI-8          399      15005305 ns/op     5600769 B/op       1571 allocs/op

7bbe0da (not sharing sfnt.Buffer):

BenchmarkDrawUI-8 400 15011438 ns/op 5794994 B/op 1891 allocs/op BenchmarkDrawUI-8 398 14971487 ns/op 5795127 B/op 1891 allocs/op BenchmarkDrawUI-8 399 15030301 ns/op 5794782 B/op 1891 allocs/op

So in this degenerate case of constantly changing text, both approaches have roughly the same CPU time, but reusing sfnt.Buffer saves many allocs, as expected. The amount that it saves will be proportional to the number of uncached calls to text.Font. The difference between the approaches will be negligible for most GUIs (where the code is rarely called), but measurable for others. So the sync.Mutex approach has superior or equal performance, but slightly more code complexity. Personally I think that using a sync.Mutex and reusing the buffers is simple enough to justify this performance improvement, but I can see why you might disagree.

The number of allocations seems to rise by abuot 20%, and the CPU time and bytes allocated are largely unchanged. If it's alright with you, I'd like to avoid the mutex for now, deferring optimization to when it has a bigger impact.

I would love an example/fonts example showing off the new feature. Can you add it? I believe you can reuse example/hello.

I can certainly add an example for v3 of this patch. However, if it uses the actual Noto OTCs, that will add a 120 MB download as a dependency. For that reason I'm thinking of adding it as a nested module instead of directly in the gioui.org/example module, if that's okay with you.

I see your point. Let's keep the cool demo in a separate repository (yours?) and I'll link to that from the newsletter and such.

Without an example, is there a way to add a test that doesn't need large dependencies? I'd like for the new feature not to bit-rot in the future.

Thanks, Elias

~tainted-bit 2 days ago

If it's alright with you, I'd like to avoid the mutex for now, deferring optimization to when it has a bigger impact.

Alright. Perhaps a short comment should be left around the sfnt.Buffer declaration to discourage optimization before the time is right?

I see your point. Let's keep the cool demo in a separate repository (yours?) and I'll link to that from the newsletter and such.

Sure. I'll make a repository for it somewhere and post the link here once the patch is merged.

Without an example, is there a way to add a test that doesn't need large dependencies? I'd like for the new feature not to bit-rot in the future.

That makes sense. My thought is to add internal/rendertest/shaper_test.go with TestCollectionAsFace to do some black-box testing. This would generate an OTC of two fonts, then render the following:

  1. Using font 1, a valid glyph in font 1.
  2. Using font 1, an invalid glyph in font 1 that is also invalid in font 2.
  3. Using font 2, a valid glyph in font 2 that is invalid in font 1.
  4. Using font 2, the same invalid glyph from render 2.
  5. Using the OTC, the glyph from render 1.
  6. Using the OTC, the glyph from render 3.
  7. Using the OTC, the glyph from render 2 / 4.

The tests are then as follows:

  • Renders 1, 2, 3, and 4 are distinct.
  • Render 5 == render 1.
  • Render 6 == render 3.
  • Render 7 == render 2.

The main question is which fonts to use for the test, and how to make the OTC. Right now I'm leaning towards storing sample TTFs and the merged OTC as raw files in the repository (probably under internal/rendertest/refs/ ?). This would avoid the test becoming brittle if the Go fonts change (although admittedly that is unlikely, given that the last update was 3 years ago) and also avoid dependency on Roboto (which is not currently used elsewhere). It also avoids the need to use OTCMerge as a dependency or to include rudimentary OTC merging code (which is admittedly only a few lines if generality is not required).

Does that approach seem sensible to you?

~eliasnaur 2 days ago

On Mon Jul 6, 2020 at 16:17, ~tainted-bit wrote:

If it's alright with you, I'd like to avoid the mutex for now, deferring optimization to when it has a bigger impact.

Alright. Perhaps a short comment should be left around the sfnt.Buffer declaration to discourage optimization before the time is right?

I'm not sure a comment is worth it. It would have to be duplicated on several sfnt.Buffer declarations or risk being overlooked. In any case we're not discouraging optimizations as much as we're avoiding race conditions.

I see your point. Let's keep the cool demo in a separate repository (yours?) and I'll link to that from the newsletter and such.

Sure. I'll make a repository for it somewhere and post the link here once the patch is merged.

Without an example, is there a way to add a test that doesn't need large dependencies? I'd like for the new feature not to bit-rot in the future.

That makes sense. My thought is to add internal/rendertest/shaper_test.go with TestCollectionAsFace to do some black-box testing. This would generate an OTC of two fonts, then render the following:

Rendering text is great for a demo but overkill for a test. It seems to me a test that inspects the result of Layout (and/or Shape) for a string containing fallback character(s) would suffice.

The main question is which fonts to use for the test, and how to make the OTC. Right now I'm leaning towards storing sample TTFs and the merged OTC as raw files in the repository (probably under internal/rendertest/refs/ ?). This would avoid the test becoming brittle if the Go fonts change (although admittedly that is unlikely, given that the last update was 3 years ago) and also avoid dependency on Roboto (which is not currently used elsewhere). It also avoids the need to use OTCMerge as a dependency or to include rudimentary OTC merging code (which is admittedly only a few lines if generality is not required).

I'd like to keep any checked in TTF/OTC files tiny, < 1kb or even < 512 bytes like the other reference files in refs/.

I'm ok with test relying on the Go fonts not changing, and I'm also ok with non-general OTC merging code (since it's only for the test).

Thanks Elias

~tainted-bit 2 days ago

Rendering text is great for a demo but overkill for a test. It seems to me a test that inspects the result of Layout (and/or Shape) for a string containing fallback character(s) would suffice.

This will mostly work, but the tricky part is testing that the replacement glyph comes from the first font in the collection. This is not visible in the []text.Line returned by Layout because the glyph rune will be the same. Distinguishing the replacement characters will require at least a call to Shape and potentially examining the returned op.CallOp to identify the differences.

Alternatively, we could just not test that part of the code and leave it up to Collection to decide where replacement characters come from. After all, if all goes well we'll almost never see them anyway. :)

I'd like to keep any checked in TTF/OTC files tiny, < 1kb or even < 512 bytes like the other reference files in refs/. I'm ok with test relying on the Go fonts not changing, and I'm also ok with non-general OTC merging code (since it's only for the test).

I'm pretty sure that sizes like those are attainable with pyftsubset and potentially some other tools / manual massaging, but I won't know the final size for sure until I try. The plan is to have two TTFs that each contain two glyphs (replacement glyph and one identifiable symbol) and nothing else.

~eliasnaur 2 days ago

On Mon Jul 6, 2020 at 16:54, ~tainted-bit wrote:

Rendering text is great for a demo but overkill for a test. It seems to me a test that inspects the result of Layout (and/or Shape) for a string containing fallback character(s) would suffice.

This will mostly work, but the tricky part is testing that the replacement glyph comes from the first font in the collection. This is not visible in the []text.Line returned by Layout because the glyph rune will be the same. Distinguishing the replacement characters will require at least a call to Shape and potentially examining the returned op.CallOp to identify the differences.

Alternatively, we could just not test that part of the code and leave it up to Collection to decide where replacement characters come from. After all, if all goes well we'll almost never see them anyway. :)

Thanks for the detailed description. I'll leave it to you how best to test :)

I'd like to keep any checked in TTF/OTC files tiny, < 1kb or even < 512 bytes like the other reference files in refs/. I'm ok with test relying on the Go fonts not changing, and I'm also ok with non-general OTC merging code (since it's only for the test).

I'm pretty sure that sizes like those are attainable with pyftsubset and potentially some other tools / manual massaging, but I won't know the final size for sure until I try. The plan is to have two TTFs that each contain two glyphs (replacement glyph and one identifiable symbol) and nothing else.

Great!

-- elias

~tainted-bit 2 days ago

Patch 11495 omits the sfnt.Buffer lock and includes a test with the aforementioned shapings. To determine whether or not two font shapes are equal, it compares the Ops as opaque byte slices. This testing method is very fast and does not require any rendering, but it breaks if the shaper produces non-deterministic Ops or if any of the operations use Refs. I think that these are reasonable assumptions, but I don't know much about the operations subsystem. The test uses two TTF files that I have prepared. As hoped, they are both very small: 1071 bytes combined. The tests also include a rudimentary OTC merging function, as discussed.

~dejadeja9 a day ago

This looks great. I think it also paved the way for supporting labels with multiple font styles, e.g., with some portions being bold, italic and possibly with other colors (like blue to indicate a link)? It will be very useful for rendering markdown texts.

~eliasnaur 18 hours ago

On Tue Jul 7, 2020 at 07:27, ~tainted-bit wrote:

Patch 11495 omits the sfnt.Buffer lock and includes a test with the aforementioned shapings. To determine whether or not two font shapes are equal, it compares the Ops as opaque byte slices. This testing method is very fast and does not require any rendering, but it breaks if the shaper produces non-deterministic Ops or if any of the operations use Refs. I think that these are reasonable assumptions, but I don't know much about the operations subsystem. The test uses two TTF files that I have prepared. As hoped, they are both very small: 1071 bytes combined. The tests also include a rudimentary OTC merging function, as discussed.

Both your patches are now merged. Thank you; Gio gained a feature, and I learned something new.

-- elias

~eliasnaur REPORTED FIXED 18 hours ago

~tainted-bit 15 hours ago

Thanks for your guidance with this patch series. As promised, I've uploaded the fonts example so that others can see the new feature in action: github.com/Nik-U/gioexamples/fonts.

Through this process I've learned that supporting all languages is more difficult than just loading a broad-coverage font collection and that fixing this issue was only the first step. I'll open a new issue shortly with what remains to be done and a potential path forward.

~tainted-bit referenced this from #146 11 hours ago

Register here or Log in to comment, or comment via email.