Skip to content
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

Allow a RenderContext to be passed directly to the Renderer constructor #1161

Merged
merged 5 commits into from
Oct 8, 2021

Conversation

tommadams
Copy link
Contributor

@tommadams tommadams commented Sep 28, 2021

fixes #827
fixes #724

This enables users to create their own RenderContext implementations.

The required a couple of changes:

  • Move the CanvasContext resize logic out of Renderer and into CanvasContext.
  • Overload the Renderer constructor to accept a RenderContext.

This PR also fixes a bug in the Factory where it would always create an SVGContext even if you passed it the element ID for a HTMLCanvasElement. Consequently, there are a few image diffs because the Factory.Draw test now actually renders something for the canvas test :)

@ronyeh
Copy link
Collaborator

ronyeh commented Sep 30, 2021

Is this suggestion related to your PR?
#827

Maybe someday there can be a ReactRenderContext or WebGLRenderContext heheh.

@tommadams
Copy link
Contributor Author

Yeah, the suggestion in that issue is similar but this PR bypasses the need for any kind of registration by simply allowing you to pass in any object that fulfills the RenderContext interface.

@ronyeh
Copy link
Collaborator

ronyeh commented Oct 1, 2021

Awesome. After your PR is merged we can close that issue. The commenter referred to D3. It would be fun to have a D3 renderer, haha. One could make a fancy zooming visualization of multiple scores or rearrange the measures of a score or something. :-)

@ronyeh
Copy link
Collaborator

ronyeh commented Oct 1, 2021

This PR also fixes #724

It's good to see multiple devs asking for the same thing. Thanks for finding the solution!

@ronyeh ronyeh added the 4.0 label Oct 1, 2021
@0xfe
Copy link
Owner

0xfe commented Oct 3, 2021

Looks great Tom. Can you fix the conflicts please?

Also, update the wiki (and FAQ), since this is particularly useful.

src/factory.ts Outdated
const { elementId, width, height, background } = this.options.renderer;
if (elementId == null || elementId === '') {
if (elementId === null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to handle undefined? The previous solution did, although not explicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change history around these lines is somewhat complicated but I think the elementId === null change came from a different PR that I merged in. Regardless, I think you're correct and the more lax check is preferred.

@@ -9,29 +9,16 @@ import { RuntimeError } from './util';
// A ContextBuilder is either Renderer.getSVGContext or Renderer.getCanvasContext.
export type ContextBuilder = typeof Renderer.getSVGContext | typeof Renderer.getCanvasContext;

// eslint-disable-next-line
function isRenderContext(obj: any): obj is RenderContext {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this our type guard? Seems like a funny way to check if it's a render context, but if it works, it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is our type guard unfortunately. By design, there's a big overlap between the interfaces in RenderContext and `CanvasRenderingContext2D', so this is the most robust test I can think of.

I think a better fix would be to make RenderContext an abstract base class, but that's a fairly big change that I'd like to make in isolation.

constructor(arg0: string | HTMLCanvasElement | HTMLDivElement | RenderContext, arg1?: number) {
if (isRenderContext(arg0)) {
// The user has provided what looks like a RenderContext, let's just use it.
// TODO(tommadams): RenderContext is an interface, can we introduce a context base class
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind having an abstract base RenderContext class that people are required to extend.

Alternatively, we go the isCategory route (like our type guard file) where if you have a static property CATEGORY that returns 'RenderContext', we will trust that you know what you're doing.

@0xfe any input on this? I'd prefer an abstract class that both SVGContext and CanvasContext extend from.

src/renderer.ts Outdated
}
this.ctx = new CanvasContext(context);
} else if (backend === Renderer.Backends.SVG) {
if (!(element instanceof HTMLDivElement)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we use window.HTMLCanvasElement above, but here we use HTMLDivElement?

Should we drop the window. prefix, for situations where window is not defined, but those types are somehow available in the globalThis scope?

Copy link
Collaborator

@ronyeh ronyeh Oct 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I saw your commit comment: "window.HTMLCanvasElement so code works under node.js").

Does jsdom / node-canvas have window.HTMLCanvasElement, but not HTMLCanvasElement? How come HTMLDivElement works?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I played around with jsdom. We can assume that HTMLCanvasElement and HTMLDivElement are globally available IF the developer does this in node:

const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
global.window = dom.window;
global.document = dom.window.document;
global.HTMLCanvasElement = dom.window.HTMLCanvasElement;
global.HTMLDivElement = dom.window.HTMLDivElement;

Then these will log true:

const d = document.createElement('div');
console.log(d instanceof HTMLDivElement);
const c = document.createElement('canvas');
console.log(c instanceof HTMLCanvasElement);

So I guess it's safest if we add the window. prefix to all our instanceof checks, e.g., instanceof window.HTMLDivElement.

Or perhaps we should expect the developer to make these two types globally available? We could document this in our examples and on the wiki.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I saw your commit comment: "window.HTMLCanvasElement so code works under node.js").

Does jsdom / node-canvas have window.HTMLCanvasElement, but not HTMLCanvasElement? How come HTMLDivElement works?

Thanks for the catch, I missed updating HTMLDivElement, it doesn't actually work :)

I only caught the HTMLCanvasElement because it failed when I was ran the visual regression test and that (of course) doesn't exercise any of the SVG code paths.

@ronyeh
Copy link
Collaborator

ronyeh commented Oct 3, 2021

I believe this all works, but could we get an example file that is a hello world demonstration of this? You can drop it in vexflow/demos/ and it can be a stub RenderContext that just console.logs the method name and params. It can illustrate to others that it is possible to implement your own RenderContext.

@rvilarl
Copy link
Collaborator

rvilarl commented Oct 5, 2021

@tommadams if you write "fixes #827" and "fixes #724" in the first comment of this PR like in #1170 , the issues and the PR will be connected and when the PR is merged, the issues will be closed automatically.
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue

@tommadams
Copy link
Contributor Author

Thanks for all the great comments folks, and sorry for the delayed response. I'm going get cracking on all the suggestions today.

@tommadams
Copy link
Contributor Author

I've added a simple example of a custom RenderContext to demos/node/customcontext.js that logs all method calls; its output is:

setFont("Arial", 10, "")
setBackgroundFillStyle("#eed")
save()
setFillStyle("#999999")
setStrokeStyle("#999999")
setLineWidth(1)
beginPath()
moveTo(10, 80)
lineTo(410, 80)
stroke()
restore()
save()
setFillStyle("#999999")
setStrokeStyle("#999999")
setLineWidth(1)
beginPath()
moveTo(10, 90)
lineTo(410, 90)
stroke()
restore()
save()
setFillStyle("#999999")
setStrokeStyle("#999999")
setLineWidth(1)
beginPath()
moveTo(10, 100)
lineTo(410, 100)
stroke()
restore()
save()
setFillStyle("#999999")
setStrokeStyle("#999999")
setLineWidth(1)
beginPath()
moveTo(10, 110)
lineTo(410, 110)
stroke()
restore()
save()
setFillStyle("#999999")
setStrokeStyle("#999999")
setLineWidth(1)
beginPath()
moveTo(10, 120)
lineTo(410, 120)
stroke()
restore()
fillRect(10, 79.5, 1, 41)
fillRect(410, 79.5, 1, 41)
beginPath()
moveTo(28.280256, 120.199872)
bezierCurveTo(29.116608, 120.199872, 30.079680000000003, 120.12384, 30.814656, 119.971776)
bezierCurveTo(31.194816000000003, 119.89574400000001, 31.270848, 119.8704, 31.346880000000002, 120.301248)
bezierCurveTo(31.777728, 122.759616, 32.335296, 125.927616, 32.335296, 127.651008)
bezierCurveTo(32.335296, 133.04928, 28.685760000000002, 133.708224, 26.53152, 133.708224)
bezierCurveTo(24.554688, 133.708224, 23.61696, 133.125312, 23.61696, 132.643776)
bezierCurveTo(23.61696, 132.390336, 23.946432, 132.28896, 24.782784, 132.010176)
bezierCurveTo(25.923264000000003, 131.680704, 27.215808000000003, 130.717632, 27.215808000000003, 128.588736)
bezierCurveTo(27.215808000000003, 126.58656, 25.948608, 124.863168, 23.718336, 124.863168)
bezierCurveTo(21.285312, 124.863168, 19.81536, 126.814656, 19.81536, 129.070272)
bezierCurveTo(19.81536, 131.427264, 21.234624, 135.026112, 26.759616, 135.026112)
bezierCurveTo(29.19264, 135.026112, 33.931968, 133.910976, 33.931968, 127.72704)
bezierCurveTo(33.931968, 125.62348800000001, 33.27302400000001, 122.176704, 32.892864, 119.89574400000001)
bezierCurveTo(32.816832000000005, 119.464896, 32.842176, 119.515584, 33.349056000000004, 119.287488)
bezierCurveTo(37.04928, 117.817536, 39.482304, 114.725568, 39.482304, 110.594496)
bezierCurveTo(39.482304, 105.9312, 36.060864, 101.800128, 30.687936, 101.800128)
bezierCurveTo(29.750208, 101.800128, 29.750208, 101.800128, 29.623488000000002, 101.141184)
lineTo(28.711104, 95.844288)
bezierCurveTo(28.660416, 95.41344, 28.711104, 95.388096, 28.9392, 95.16)
bezierCurveTo(32.892864, 91.48512, 35.883456, 86.847168, 35.883456, 81.246144)
bezierCurveTo(35.883456, 78.078144, 34.996415999999996, 74.93548799999999, 33.50112, 72.75590399999999)
bezierCurveTo(32.943552, 71.944896, 32.005824000000004, 70.931136, 31.60032, 70.931136)
bezierCurveTo(31.09344, 70.931136, 29.95296, 71.868864, 29.243328, 72.67987199999999)
bezierCurveTo(26.53152, 75.670464, 25.64448, 80.232384, 25.64448, 84.033984)
bezierCurveTo(25.64448, 86.137536, 25.923264000000003, 88.51987199999999, 26.176704, 90.015168)
bezierCurveTo(26.252736, 90.446016, 26.278080000000003, 90.522048, 25.847231999999998, 90.902208)
bezierCurveTo(20.57568, 95.236032, 15, 100.456896, 15, 107.832)
bezierCurveTo(15, 114.168, 19.333824, 120.199872, 28.280256, 120.199872)
moveTo(17.91456, 110.265024)
bezierCurveTo(17.91456, 108.110784, 18.29472, 105.221568, 21.310656, 101.800128)
bezierCurveTo(23.515584, 99.367104, 25.188288, 97.998528, 26.886336, 96.629952)
bezierCurveTo(27.266496, 96.325824, 27.342528, 96.37651199999999, 27.41856, 96.756672)
lineTo(28.17888, 101.445312)
bezierCurveTo(28.280256, 102.1296, 28.280256, 102.10425599999999, 27.621312000000003, 102.307008)
bezierCurveTo(24.427968, 103.3968, 22.324416, 106.286016, 22.324416, 109.403328)
bezierCurveTo(22.324416, 112.672704, 24.047808, 115.004352, 26.53152, 115.866048)
bezierCurveTo(26.835648, 115.967424, 27.266496, 116.0688, 27.519936, 116.0688)
bezierCurveTo(27.798720000000003, 116.0688, 27.950784, 115.891392, 27.950784, 115.663296)
bezierCurveTo(27.950784, 115.409856, 27.672, 115.30848, 27.41856, 115.207104)
bezierCurveTo(25.872576000000002, 114.54816, 24.782784, 112.976832, 24.782784, 111.304128)
bezierCurveTo(24.782784, 109.200576, 26.202048, 107.654592, 28.43232, 107.020992)
bezierCurveTo(29.015232, 106.868928, 29.091264000000002, 106.919616, 29.167296, 107.32512)
lineTo(30.992064, 118.19769600000001)
bezierCurveTo(31.068096, 118.6032, 31.017408, 118.6032, 30.485184000000004, 118.704576)
bezierCurveTo(29.902272000000004, 118.805952, 29.167296, 118.881984, 28.43232, 118.881984)
bezierCurveTo(22.045632, 118.881984, 17.91456, 115.33382399999999, 17.91456, 110.265024)
moveTo(32.157888, 76.582848)
bezierCurveTo(33.349056000000004, 76.582848, 34.337472000000005, 77.571264, 34.337472000000005, 79.57344)
bezierCurveTo(34.337472000000005, 83.62848, 30.865344, 86.9232, 28.001472, 89.432256)
bezierCurveTo(27.748032000000002, 89.660352, 27.595968, 89.609664, 27.519936, 89.128128)
bezierCurveTo(27.367872, 88.1904, 27.29184, 86.948544, 27.29184, 85.78272)
bezierCurveTo(27.29184, 80.08032, 29.927616, 76.582848, 32.157888, 76.582848)
moveTo(31.09344, 106.742208)
bezierCurveTo(34.058688000000004, 106.995648, 36.49171200000001, 109.47936, 36.49171200000001, 112.672704)
bezierCurveTo(36.49171200000001, 114.979008, 35.097792, 116.82912, 33.070272, 117.868224)
bezierCurveTo(32.639424000000005, 118.070976, 32.563392, 118.070976, 32.48736, 117.640128)
lineTo(30.687936, 107.249088)
bezierCurveTo(30.611904000000003, 106.792896, 30.662592000000004, 106.69152, 31.09344, 106.742208)
fill()
beginPath()
moveTo(57.41984, 93.94128)
lineTo(63.956, 93.94128)
lineTo(63.956, 96.16896)
bezierCurveTo(63.956, 97.12368, 63.19712, 97.39296, 62.56064, 97.39296)
bezierCurveTo(61.9976, 97.39296, 61.7528, 97.7112, 61.7528, 98.0784)
bezierCurveTo(61.7528, 98.42112, 61.8752, 98.8128, 62.413759999999996, 98.8128)
lineTo(69.92912, 98.8128)
bezierCurveTo(70.27184, 98.8128, 70.63904, 98.568, 70.63904, 98.0784)
bezierCurveTo(70.63904, 97.58879999999999, 70.19839999999999, 97.36848, 69.85568, 97.36848)
bezierCurveTo(69.51295999999999, 97.36848, 68.75408, 97.14816, 68.75408, 96.02208)
lineTo(68.75408, 93.94128)
lineTo(71.32448, 93.94128)
bezierCurveTo(71.69167999999999, 93.94128, 71.86304, 93.69648, 71.86304, 93.28032)
bezierCurveTo(71.86304, 92.86416, 71.71616, 92.61936, 71.32448, 92.61936)
lineTo(68.75408, 92.61936)
lineTo(68.75408, 85.05504)
bezierCurveTo(68.75408, 84.78576, 68.7296, 84.46752000000001, 68.33792, 84.46752000000001)
bezierCurveTo(68.01968, 84.46752000000001, 67.84832, 84.54096, 67.628, 84.78576)
lineTo(64.27423999999999, 88.82496)
bezierCurveTo(64.15183999999999, 89.0208, 63.956, 89.21664, 63.956, 89.65728)
lineTo(63.956, 92.61936)
lineTo(59.20688, 92.61936)
bezierCurveTo(62.02208, 90.22032, 67.67696, 82.21536, 67.77488, 81.82368)
bezierCurveTo(67.77488, 81.75024, 67.79936, 81.72576000000001, 67.79936, 81.65232)
bezierCurveTo(67.79936, 81.35856, 67.55456, 81.16272000000001, 67.28528, 81.16272000000001)
bezierCurveTo(66.96704, 81.16272000000001, 65.52271999999999, 81.21168, 64.88624, 81.21168)
bezierCurveTo(64.24976, 81.21168, 62.65856, 81.16272000000001, 62.38928, 81.16272000000001)
bezierCurveTo(62.071039999999996, 81.16272000000001, 61.58144, 81.26064, 61.58144, 81.82368)
bezierCurveTo(61.58144, 86.18112, 58.10528, 91.1016, 57.05264, 92.5704)
lineTo(56.8568, 92.86416)
bezierCurveTo(56.8568, 92.88864, 56.80784, 92.9376, 56.80784, 92.96208)
bezierCurveTo(56.7344, 93.10896, 56.70992, 93.23136, 56.70992, 93.35376)
bezierCurveTo(56.70992, 93.69648, 56.9792, 93.94128, 57.41984, 93.94128)
fill()
beginPath()
moveTo(57.41984, 113.94128)
lineTo(63.956, 113.94128)
lineTo(63.956, 116.16896)
bezierCurveTo(63.956, 117.12368, 63.19712, 117.39296, 62.56064, 117.39296)
bezierCurveTo(61.9976, 117.39296, 61.7528, 117.7112, 61.7528, 118.0784)
bezierCurveTo(61.7528, 118.42112, 61.8752, 118.8128, 62.413759999999996, 118.8128)
lineTo(69.92912, 118.8128)
bezierCurveTo(70.27184, 118.8128, 70.63904, 118.568, 70.63904, 118.0784)
bezierCurveTo(70.63904, 117.58879999999999, 70.19839999999999, 117.36848, 69.85568, 117.36848)
bezierCurveTo(69.51295999999999, 117.36848, 68.75408, 117.14816, 68.75408, 116.02208)
lineTo(68.75408, 113.94128)
lineTo(71.32448, 113.94128)
bezierCurveTo(71.69167999999999, 113.94128, 71.86304, 113.69648, 71.86304, 113.28032)
bezierCurveTo(71.86304, 112.86416, 71.71616, 112.61936, 71.32448, 112.61936)
lineTo(68.75408, 112.61936)
lineTo(68.75408, 105.05504)
bezierCurveTo(68.75408, 104.78576, 68.7296, 104.46752000000001, 68.33792, 104.46752000000001)
bezierCurveTo(68.01968, 104.46752000000001, 67.84832, 104.54096, 67.628, 104.78576)
lineTo(64.27423999999999, 108.82496)
bezierCurveTo(64.15183999999999, 109.0208, 63.956, 109.21664, 63.956, 109.65728)
lineTo(63.956, 112.61936)
lineTo(59.20688, 112.61936)
bezierCurveTo(62.02208, 110.22032, 67.67696, 102.21536, 67.77488, 101.82368)
bezierCurveTo(67.77488, 101.75024, 67.79936, 101.72576000000001, 67.79936, 101.65232)
bezierCurveTo(67.79936, 101.35856, 67.55456, 101.16272000000001, 67.28528, 101.16272000000001)
bezierCurveTo(66.96704, 101.16272000000001, 65.52271999999999, 101.21168, 64.88624, 101.21168)
bezierCurveTo(64.24976, 101.21168, 62.65856, 101.16272000000001, 62.38928, 101.16272000000001)
bezierCurveTo(62.071039999999996, 101.16272000000001, 61.58144, 101.26064, 61.58144, 101.82368)
bezierCurveTo(61.58144, 106.18112, 58.10528, 111.1016, 57.05264, 112.5704)
lineTo(56.8568, 112.86416)
bezierCurveTo(56.8568, 112.88864, 56.80784, 112.9376, 56.80784, 112.96208)
bezierCurveTo(56.7344, 113.10896, 56.70992, 113.23136, 56.70992, 113.35376)
bezierCurveTo(56.70992, 113.69648, 56.9792, 113.94128, 57.41984, 113.94128)
fill()

@tommadams
Copy link
Contributor Author

It's my bedtime now, I'll update the wiki tomorrow :)

@0xfe
Copy link
Owner

0xfe commented Oct 8, 2021

Looks good, Tom. Agreed with creating a base abstraction here, and doing it separately.

@0xfe 0xfe merged commit d224ea5 into 0xfe:master Oct 8, 2021
@tommadams tommadams deleted the renderer branch October 8, 2021 16:32
@ronyeh ronyeh mentioned this pull request Nov 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants