-
Notifications
You must be signed in to change notification settings - Fork 664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvements to Glyph Rendering in SVGContext / CanvasContext #1127
Comments
For what it's worth, the approach of packing commands and command arguments into a data stream is ubiquitous in space or time critical software: video games, computer graphics applications, audio & video codecs, etc. Somewhat more relevant to this discussion, a TrueType font rendering implementation must actually be Turing complete: TrueType font files contain a mix of both curve data and instructions for fairly complex stack machine. Philosophically, one could take the view that I'm curious as to concretely what you think a problem might be? The one hypothetical case I can imagine is that someone writes a new function to interpret the glyph data and doesn't get the number of operands to one of the functions correct. I think it would be pretty obvious if that happened and the cause of the bug would be localised. It's unlikely that all the subsequent read values would correspond to valid op codes, so one way to improve the developer experience there might be to add a Changing the enum values might make things more obvious but I'd suggest using smaller constants. Even though the values you chose are smaller than |
I will just say that after having worked on it a little, I personally think a more confusing and problematic issue with the glyph code is that the order of the values for the quadratic and cubic Bézier curve commands does not match the parameter order for any of the functions that they're passed to! ;) |
Hello Tom! Thanks for your insightful reply, and for your improvements to VexFlow. I agree there is no bug and I'm fine leaving it as is. It reminds me of when I learned about the MIDI spec. At some point I was able to recognize the hex sysex messages that my devices were sending, haha. Summary: I was using console.log() on the parsed glyph data and was surprised to see only numbers (I'm used to seeing SVG path strings). It felt wrong at first, because 0 could be an x or y coordinate, but it can also be a move-to command. Maybe we can just add a comment in glyph.ts that explains how the three switch() statements work in parallel. LONG STORY: I was trying to learn how the glyph internals worked (how we go from an OTF file to a I was trying to debug all the ugly NOGLYPHs in the Petaluma font, so I threw together a font sample page that renders all the glyphs that VexFlow cares about. (See image at the bottom. The red glyphs are missing from the tested font, and are drawn using a fallback font.)
And you are correct that the order of parameters is switched up. The SVG path spec for cubic bezier curves is:
VexFlow's
VexFlow's SVGContext and CanvasContext declare bezierCurveTo to be consistent with CanvasRenderingContext2D's bezierCurveTo():
I wonder if there's a more direct way to go from the OTF path strings to our SVG path strings? We might have to worry about the y axis. I see that smufl_fontgen.js inverts the y axis while it generates the original bravura_glyphs.ts file. (And our glyph.ts code also does some "inverting" since it subtracts the y value from the originY.) Canvas can also accept SVG path strings via the Path2D API, so someday it would be nice if our bravura_glyphs.ts file contained paths that could be used directly (in SVG path, or Canvas Path2D). Thanks for reading my brainstorming :-). Font Glyph TableI was just tired of seeing the NOGLYPH glyphs.... so I downloaded the most recent Petaluma font from Steinberg and generated a new petaluma_glyphs.ts file just to see if any glyphs were added. Then I realized we didn't have a good way to visualize all the available (or missing) glyphs in our three fonts, so I hacked up a glyph table. On the test page, if I mouse over a glyph, it console.logs the glyph code, so I can see which exact glyph is missing. At some point I'll clean this up... and maybe turn it into a PR. |
Thanks for digging into the glyph code, those missing glyphs have been bugging me too. I agree that the string -> number -> string conversion is less than ideal. This is partly because the Instead, if the I've been thinking about this idea for a few weeks now, maybe I'll actually finally try it out... |
Wow if you investigate the svgcontext def/use, I'd definitely help with the
code review (if it would help in any way).
Maybe our CanvasContext could internally cache glyph outline strings, and
then reuse them via Path2D every time drawGlyph() is called. This would
parallel the SVG def/use approach.
…On Fri, Sep 3, 2021, 7:22 AM Tom Madams ***@***.***> wrote:
Thanks for digging into the glyph code, those missing glyphs have been
bugging me too.
I agree that the string -> number -> string conversion is less than ideal.
This is partly because the RenderContext abstraction is at slightly the
wrong level in my opinion: because RenderContext exposes low level
methods like moveTo and bezierCurveTo, the outline data *has* to be
converted from strings to numbers.
Instead, if the RenderContext simply exposed a drawGlyph method, the
SvgContext could embed each outline exactly once in the SVG using a <def>
<https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs> tag, then
draw each glyph with <use>
<https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use> tags. So
the SvgContext wouldn't have to process the outline at all and only the
CanvasContext would have convert from string -> number. You'd still need
to convert from string -> number once to calculate the glyph bounding boxes
but I think you could gain substantial performance improvements.
I've been thinking about this idea for a few weeks now, maybe I'll
actually finally try it out...
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#1127 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAB2MCJJOFW6YSC7QRHIPZDUADKZ5ANCNFSM5DKFWCHA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
An alternative approach for caching glyphs in canvas would be to draw the glyph into a hidden buffer and then use the drawImage method to place the image data every time we need to render the glyph again. This would be useful for canvases with lots of reused glyphs, and maybe would help if vexflow ever decides to support animation (ver. 5 hehe). |
I spent a couple of hours hacking on it this morning and it looks like the approach could work. It seems like you have to use Elements in red are drawn using |
Beautiful! I'm actually curious about the ultimate performance. Will it be faster (or slower)? In the end, we are swapping a bunch of inline |
The huge performance improvement will be that the SVGContext's |
Cool! I have some smaller ideas for improving the glyph pipeline. I think you've inspired me to look into them at some point. For example, I think the
One benefit would be that I could copy the outline ("o") field from the |
Storing the SVG path in the To completely avoid any processing of the The other wrinkle is that the |
Oh, and yes, avoiding the negation of the y values would definitely simplify the rendering code. I spent about an hour tracking down a bug where the |
Bummer. I found an old discussion from 6+ years ago about performance with SVG use symbol. The comments suggested that symbol might be slower than just drawing tons of paths directly. See: stackoverflow: svg performance with symbols
In measureText, there is a line:
I guess that's why you tried the offscreen SVG approach, instead of modifying the SVGContext's internal svg element. What if we just comment out the entire
Does that have any effect? In any case, please don't delete your branch and hard work, haha. Maybe we can look at your approach and learn something from it. If you don't mind sharing your code at some pont, I'd love to take a look. |
Here's another almost 7 year old blog post about SVG performance, by a Khan Academy engineer. I thought it was an interesting read: https://www.crmarsh.com/svg-performance/ Summary: at least 7 years ago, major browsers didn't seem to focus on SVG render performance. They were able to make huge gains in (animation) performance by putting a static SVG inside a DIV, and then moving that DIV around. So it's possible that changes to the SVG element trigger lots of relayout and repainting. We could build a small svg symbol/use experiment and use Chrome's What would happen if we just built a SVG string in memory, without ever calling |
Some good news, it turns out that the terrible performance was caused by multiple After changing all the Looking at a profile, it seems like measuring text still causes loads of "recalculate style" events inside Chrome but these complete in microseconds instead of milliseconds. |
vexflow5 branch no longer renders glyphs in SVGContext/CanvasContext. |
This thread has evolved into discussing ways to improve Glyph rendering. I'll keep this issue open for now, even though there is no bug to fix. If we prefer to move it to the GitHub discussions tab, I can close off this issue.
Original title:
Should OutlineCode enum (in glyph.ts) use ints other than 0, 1, 2, 3?
This isn't a bug, but I was reading glyph.ts and saw that
GlyphOutline.parse()
returns anumber[]
where the position of the number changes its meaning.For example, Bach Demo produces an array like:
[0,0,60,3,140,180,0,135,62,180,3,425,60,268,180,425,62,3,285,180,425,134,367,180,3,0,60,127,180,0,63]
The first 0 is actually a "move to" instruction, and the second 0 is an x coordinate with value 0, and then comes a y coordinate with value 60. The 3 after that starts a bezier curve....
Then,
Glyph.renderOutline()
andGlyph.getOutlineBoundingBox()
loop over these outline arrays to draw the glyphs to the context.So the switch statement is looking for numbers 0, 1, 2, 3.
Anyways, like I said, this isn't a bug because the three methods all follow the same convention (a number 0 implies an x, y and a number 3 is followed by 6 ints specifying the points of a cubic bezier curve).
I know it was part of @tommadams massive improvements on glyph performance & correctness in #1103 and #1077.
Obviously we don't want to go back to the old / slow way.
But do we have any ideas on if / how we could improve this, or make it potentially less brittle / future proof?
I can't think of anything off the top of my head at the moment, other than starting the enum at a really big number (that is still well under
Number.MAX_SAFE_INTEGER
), like:Anyways, can we improve this in any way? If not, I'll close as "not a bug."
The text was updated successfully, but these errors were encountered: