Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
12218f4
20.13.0: bump the version
claude Oct 31, 2026
acbb163
UI: checkbox, slider, skinned button, panel layout and scrolling (#1722)
claude Oct 31, 2026
1fbc5c9
UI: fixes from review
claude Oct 31, 2026
1bf6e4b
Examples: a taller level list in the UI example
claude Oct 31, 2026
4cacf94
Examples: the UI example's level list at the top right, beside the bars
claude Oct 31, 2026
32626f9
Examples: the UI example's level list shows the first 16 levels
claude Oct 31, 2026
2e3c8f1
UI: consistent widget API, and fixes from a second review
claude Oct 31, 2026
d4a577c
Examples: UI example lint
claude Oct 31, 2026
99c7c51
UI: drop shadows on UIPanel and ProgressBar, used in the UI example
claude Oct 31, 2026
12bedbd
Gamepad plugin: keep a touch's hover events on the controls from the …
claude Oct 31, 2026
59f6398
UI: keyboard and gamepad focus navigation, and a hover/press bounce
claude Oct 31, 2026
6f1acf6
UI: keep the focus navigation to the minimum
claude Oct 31, 2026
44ee453
UI: fixes from a review of the focus navigation and the bounce
claude Oct 31, 2026
5291863
UI: focus lives on the widgets, with no focus manager to set up
claude Oct 31, 2026
1440ba7
Remove a scratch test file committed by mistake
claude Oct 31, 2026
32a4edc
UI: fill the gaps in the widget tests, and fix what they found
claude Oct 31, 2026
596b23b
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Oct 31, 2026
d706c94
gamepad-plugin 1.0.2
claude Oct 31, 2026
d345737
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Oct 31, 2026
b39dd70
Skill: UI widgets, with a widget map and a complete menu example
claude Oct 31, 2026
ec8e1a4
Docs: UICheckbox and UISlider, with examples
claude Oct 31, 2026
57109da
Docs: UIPanel and ProgressBar, with examples
claude Oct 31, 2026
139c957
Docs: UIBaseElement, UITextButton, UISpriteElement, with examples
claude Oct 31, 2026
0a785e6
Changelog: onCancel also covers a called-off bindKey press
claude Oct 31, 2026
c365da3
UI: UISpriteElement lets events through by default, like the others
claude Oct 31, 2026
1d86e82
UI: a press stops at a UI element by default
claude Oct 31, 2026
6d971f4
UI: UISpriteElement is a UIBaseElement holding a Sprite
claude Nov 1, 2026
515a59a
Tests: UISpriteElement's public behaviour, old and new
claude Nov 1, 2026
71c91de
Input: keep the hover after a click, and end it when the pointer leav…
claude Nov 1, 2026
63bfee5
UI: fix the widget bugs found while writing the docs
claude Nov 1, 2026
8a41b06
UI: a radio group holds one choice from when its checkboxes are made
claude Nov 1, 2026
8a47072
Fixes from review: canvas leave reaches every hovered region; UISprit…
claude Nov 1, 2026
6b9b22b
UISpriteElement: setCurrentAnimation passes preserve_dt on
claude Nov 1, 2026
582b27c
Input: a cancelled pointer, or a lifted legacy touch, ends the hover
claude Nov 1, 2026
c70fe49
Changelog: a cancelled pointer ends the hover too
claude Nov 1, 2026
316a7c2
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Nov 1, 2026
6d4a96a
Rendering: a clip inside a clip is the overlap of the two on WebGL an…
claude Nov 1, 2026
4d1c82e
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Nov 1, 2026
c0c2258
Camera: a fade always calls onComplete before it is removed (#1810)
claude Nov 1, 2026
927b0c9
Tests: fadeIn / fadeOut onComplete through the game loop (#1810)
claude Nov 1, 2026
3ebc32a
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Nov 1, 2026
39f681b
Tiled: seed object GUIDs past the map's ids when it has no nextobject…
claude Nov 1, 2026
26494a3
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Nov 1, 2026
b311be0
fix(line): 선분 연장선의 점을 포함하지 않도록 수정
mmporong Oct 31, 2026
ec612a5
fix(color): hex 알파 정밀도를 보존
mmporong Oct 31, 2026
8c16d0c
fix(math): 2의 거듭제곱 입력을 검증
mmporong Oct 31, 2026
864eddd
Color: keep toHex8() output as it was; the hex alpha fix is in parseH…
claude Nov 1, 2026
404663f
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Nov 1, 2026
6349541
Tests: a full local run of the engine suite comes out clean (#1817)
claude Nov 1, 2026
ba0a426
Tests: the audio unlock test waits for the clip to start (#1817)
claude Nov 1, 2026
22b59cd
Merge remote-tracking branch 'origin/master' into claude/modest-johns…
claude Nov 1, 2026
c77ad59
Text: round outline corners by default, lineJoin / miterLimit, and no…
claude Nov 1, 2026
e91423d
Text: document every outline corner option, with tests for changes an…
claude Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/melonjs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
- UI: `hoverScale` and `pressScale` on every `UIBaseElement`: a bounce under the pointer or the focus, and while held, easing by itself and scaling only the drawing, not the hit area or the layout
- UI: `ProgressBar`'s `onChange` also gets the bar
- UI: the `UISkin`, `UILabel`, `Paint` and settings and skin types of every widget are exported; `Container#clipContains(x, y)` tells whether a container shows its children at a point
- Text: `lineJoin` and `miterLimit` settings, the shape of the outline's corners: `"round"` (the default), `"bevel"`, or `"miter"` for pointed corners as a plain canvas draws them, with `miterLimit` capping how far a point may reach

### Changed
- UI: `UITextButton` no longer writes the size and alignment it worked out back into the settings object, so one object can make several buttons
- UI: `UITextButton#label` is the button's text, a `BitmapText` or, with a CSS font, a `Text`
- UI: `UISpriteElement` is now a `UIBaseElement` holding a `Sprite` (`sprite`), instead of a `Sprite` with its own copy of the pointer handling, so it has everything the other widgets have: `enabled` (drawn at half opacity when off), `cancelPress()` / `onCancel()`, `hoverScale` / `pressScale`, dragging and keyboard / gamepad focus. `(x, y)` is still where the image's anchor point goes, so existing layouts do not move, and `setRegion`, `addAnimation`, `setCurrentAnimation` and `isCurrentAnimation` are kept on the element. As a sprite does, it follows its image's size, a region or animation frame of another size included, with the image's anchor point staying in place. Other `Sprite` members are now on `element.sprite`; `pos` is the element's top left corner; setting `anchorPoint` after construction no longer moves the image (pass it in the settings); and a second sprite drawn in a `draw()` override becomes a child added with `addChild`
- Text: an outlined `Text` (`lineWidth` > 0) is drawn with round corners by default, as game text outlines usually are, instead of the canvas's pointed ones, which grew long spikes from sharp glyphs on a thick outline and were cut off at the top of the text. Set `lineJoin: "miter"` to keep pointed corners; they are now drawn whole
- UI: a press stops at a UI element by default: `UIBaseElement`'s `onClick()` and `onRelease()` now return `false`, as `UISpriteElement`'s already did, so a click on a button, checkbox or widget of your own no longer also reaches the game behind it. A subclass that relied on presses passing through returns `true` from them. Hover is unchanged, and a non-blocking `UIPanel` still lets presses on its background through

### Deprecated
Expand All @@ -37,6 +39,7 @@
- Color: `parseHex()` keeps all 8 bits of alpha instead of rounding to one decimal. Low alpha values no longer become fully transparent, and 4- or 8-digit hex colors round-trip through `toHex8()` without changing alpha (thanks @mmporong)
- Math: `isPowerOfTwo()` rejects zero, negative and fractional values instead of accepting them after 32-bit coercion, and handles powers and non-powers beyond the 32-bit range (thanks @mmporong)
- Input: a child cropped out of view by a container with `clipping`, or scrolled out of a `UIPanel`, no longer gets pointer events where it is not drawn
- Text: an outline is no longer cut at the edges of the text. A pointed corner can reach several times half the line width past a glyph, and the tips of letters such as "A", "V" or "M" were cut flat at the top; and nothing was left to the sides, so the outline (or an italic's ink) on the left of the first glyph and past the end of the line was cut too. The text is baked with room for the outline on all four sides, for whichever corners are set; its position and bounds are unchanged
- Geometry: `Line#contains()` checks the segment endpoints as well as collinearity; a point on the line beyond either endpoint was reported as contained (thanks @mmporong)
- UI: a `UITextButton` moved after it was built draws its background where it is, not where it was first put
- UI: a blocking `UIPanel` stays opaque to hover for as long as the pointer is on it, not only on the frame the pointer enters
Expand Down
32 changes: 31 additions & 1 deletion packages/melonjs/skills/melonjs-ui-and-text/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@ If you have a label carrying a leading `\n` to buy headroom, that workaround is
now dead weight — drop it, and take the line height back off the position you
shifted it by.

The outline's corners are set by `lineJoin`, the same property a canvas
context has, and `miterLimit`:

| setting | corners | use it for |
|---|---|---|
| `lineJoin: "round"` (default) | rounded: the outline is the same width all around | almost everything: HUDs, buttons, titles |
| `lineJoin: "bevel"` | cut flat | a harder edge with no spikes |
| `lineJoin: "miter"` | pointed, as a plain canvas strokes text | a sharp, angular look |
| `miterLimit: n` (default 10) | with `"miter"` only: a corner pointing further than `n` half line widths is cut flat instead | taming the spikes `"miter"` grows on "A", "V", "M" |

At the canvas default of 10, a thick `"miter"` outline grows spikes several
times its width from sharp glyphs; 2 to 4 keeps the corners pointed and the
spikes short:

```js
const outline = {
font: "sans-serif", size: 48,
fillStyle: "#ffffff", strokeStyle: "#000000", lineWidth: 6,
};
new Text(x, y, { ...outline, text: "ROUND" }); // the default
new Text(x, y, { ...outline, lineJoin: "bevel", text: "BEVEL" });
new Text(x, y, { ...outline, lineJoin: "miter", text: "MITER" }); // long spikes
new Text(x, y, { ...outline, lineJoin: "miter", miterLimit: 3, text: "MITER 3" });
```

Whichever you pick, the bake leaves room for it on all four sides, so no corner
is cut and the label's position and bounds do not move; pointed corners just
cost a bigger texture. Changed on a live label, they show from the next
`setText()`.

## Gradient text

`fillStyle` takes a `Gradient` as well as a colour — the same object
Expand Down Expand Up @@ -142,7 +172,7 @@ label from one class to the other:
| white | white glyphs | the ABSENCE of a tint |
| gradient | yes, via `fillGradient` | no: a tint is one colour over a page image |
| recolour it later | `label.fillStyle.parseCSS("#ff4040")` — the field is a `Color` with no setter, and ASSIGNING a string replaces it, after which the glyphs stop filling entirely and nothing is logged | `label.fillStyle = "#ff4040"` — a real setter that parses the string |
| stroke | `strokeStyle` + `lineWidth` | none |
| stroke | `strokeStyle` + `lineWidth`, corners with `lineJoin` / `miterLimit` | none |
| `size` | pixels | a RATIO of the authored size |

### Per-glyph wave, shake and colour: `glyphEffect`
Expand Down
114 changes: 105 additions & 9 deletions packages/melonjs/src/renderable/text/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ const genericFontFamilies = new Set([
* a Text object: draws a string using a system or web font.
*
* The text is rasterised to a cached texture, so it draws as fast as a sprite
* while staying fully styleable — `fillStyle`, `strokeStyle` + `lineWidth`, and
* while staying fully styleable — `fillStyle`, `strokeStyle` + `lineWidth`
* (with the outline's corners set by `lineJoin` / `miterLimit`), and
* per-object opacity — with multi-line text (embedded `\n`), optional
* `wordWrapWidth` word-wrapping, and chainable {@link Text#bold} /
* {@link Text#italic}. `font` accepts any CSS family, including the generic
Expand All @@ -73,6 +74,8 @@ export default class Text extends Renderable {
* @param {boolean} [settings.gradientPerLine=true] - restart a gradient fill on every line; `false` spans one ramp across the whole block, as a plain canvas does
* @param {Color|string} [settings.strokeStyle="#000000"] - a CSS color value used for the glyph outline (drawn when `lineWidth` > 0)
* @param {number} [settings.lineWidth=0] - outline width in pixels (0 = no stroke)
* @param {"round"|"bevel"|"miter"} [settings.lineJoin="round"] - the shape of the outline's corners: "round", "bevel" or "miter" (pointed, as a plain canvas draws them)
* @param {number} [settings.miterLimit=10] - with a "miter" join, how far a corner may point, in half line widths, before it is cut flat
* @param {string} [settings.textAlign="left"] - horizontal text alignment ("left", "center", "right")
* @param {string} [settings.textBaseline="top"] - the text baseline ("top", "hanging", "middle", "alphabetic", "ideographic", "bottom")
* @param {number} [settings.lineHeight=1.0] - line spacing height
Expand Down Expand Up @@ -115,6 +118,34 @@ export default class Text extends Renderable {
* // gradientPerLine: false, // ...or span ONE ramp across both lines
* }));
* @example
* // outlined text and its corners: the outline is centred on the glyph
* // edge, drawn on top of the fill, and its corners are set by `lineJoin`
* const outline = {
* font: "sans-serif",
* size: 48,
* fillStyle: "#ffffff",
* strokeStyle: "#1d3557",
* lineWidth: 6,
* };
*
* // "round" (the default): the outline is the same width all around
* app.world.addChild(new Text(10, 10, { ...outline, text: "ROUND" }));
*
* // "bevel": corners cut flat
* app.world.addChild(new Text(10, 70, { ...outline, lineJoin: "bevel", text: "BEVEL" }));
*
* // "miter": pointed corners, as a plain canvas strokes text. At the
* // canvas's default limit of 10 a sharp glyph ("A", "V", "M") grows a
* // long spike...
* app.world.addChild(new Text(10, 130, { ...outline, lineJoin: "miter", text: "MITER" }));
*
* // ...which `miterLimit` caps: a corner that would point further than
* // this many half line widths is cut flat instead
* app.world.addChild(new Text(10, 190, { ...outline, lineJoin: "miter", miterLimit: 3, text: "MITER 3" }));
*
* // whichever is set, the whole outline is drawn: the label's texture
* // grows to hold it, while its position and bounds stay the same
* @example
* // a web font (loaded via the fontface loader) is used by its family name
* loader.preload(
* [{ name: "kenpixel", type: "fontface", src: "data/font/kenvector.woff2" }],
Expand Down Expand Up @@ -150,6 +181,57 @@ export default class Text extends Renderable {
*/
this.lineWidth = 0;

/**
* The shape of the outline's corners, where the stroke turns on a
* glyph: "round", "bevel" (cut flat) or "miter" (pointed). The
* canvas's own default is "miter", which on a thick outline grows
* long spikes from sharp glyphs such as "A", "V" or "M"; "round"
* keeps the outline the same width all around, as most game text
* outlines are.
*
* The text is baked with room for whichever is set, so no corner is
* cut: "miter" takes more, up to {@link Text#miterLimit} half line
* widths around the text.
*
* Like the other style properties, a change shows from the next
* {@link Text#setText}.
* @type {"round"|"bevel"|"miter"}
* @default "round"
* @example
* // sharp, pointed corners, as a plain canvas strokes them
* const title = new Text(0, 0, {
* font: "sans-serif",
* size: 48,
* fillStyle: "#ffffff",
* strokeStyle: "#000000",
* lineWidth: 6,
* lineJoin: "miter",
* miterLimit: 3, // and no spike past 3 half widths
* text: "VICTORY",
* });
*/
this.lineJoin = "round";

/**
* With a "miter" {@link Text#lineJoin}: how far a corner of the
* outline may point, in half line widths, before it is cut flat
* instead. The canvas's default, 10, lets sharp glyphs grow long
* spikes; 2 to 4 keeps most corners pointed and the spikes short.
* Has no effect with "round" or "bevel" joins. A value the canvas
* would refuse (zero, negative, infinite or NaN) draws with 10.
*
* Like the other style properties, a change shows from the next
* {@link Text#setText}.
* @type {number}
* @default 10
* @example
* // pointed corners with short spikes
* label.lineJoin = "miter";
* label.miterLimit = 2;
* label.setText("VICTORY"); // re-bakes with the new corners
*/
this.miterLimit = 10;

/**
* Set the default text alignment (or justification),<br>
* possible values are "left", "right", and "center".<br>
Expand Down Expand Up @@ -314,6 +396,9 @@ export default class Text extends Renderable {
}

this.lineWidth = settings.lineWidth || 0;
// set every time, for a recycled label not to keep the last one's
this.lineJoin = settings.lineJoin ?? "round";
this.miterLimit = settings.miterLimit ?? 10;
this.textAlign = settings.textAlign || "left";
this.textBaseline = settings.textBaseline || "top";
this.lineHeight = settings.lineHeight || 1.0;
Expand Down Expand Up @@ -505,7 +590,16 @@ export default class Text extends Renderable {
// see `TextMetrics#inkPadTop`.
// At least one bucket: a blank string measures 0 wide, and a canvas with
// a zero side cannot be uploaded (WebGPU throws).
const width = Math.max(1, Math.ceil(this.metrics.width / 32)) * 32;
const width =
Math.max(
1,
Math.ceil(
(this.metrics.width +
this.metrics.inkPadLeft +
this.metrics.inkPadRight) /
32,
),
) * 32;
const height =
Math.max(
1,
Expand Down Expand Up @@ -707,10 +801,11 @@ export default class Text extends Renderable {
this.canvasTexture.invalidate(renderer);
}

// adjust x,y position based on the bounding box. The blit rises by the
// same padding the glyphs were drawn down by, so they land in exactly
// the pixels they always did and only the clipped ink is recovered.
const x = this.metrics.x;
// adjust x,y position based on the bounding box. The blit moves back
// by the same padding the glyphs were drawn in by, so they land in
// exactly the pixels they always did and only the clipped ink is
// recovered.
const x = this.metrics.x - this.metrics.inkPadLeft;
const y = this.metrics.y - this.metrics.inkPadTop;

// draw the text (on whole pixels unless `subPixel` is set, where it
Expand All @@ -723,7 +818,8 @@ export default class Text extends Renderable {
* @internal
*/
_drawFont(context, text, x, y) {
// The bake canvas carries `inkPadTop` of headroom above the layout box.
// The bake canvas carries `inkPadTop` of headroom above the layout box,
// and `inkPadLeft` of room to its left.
// Translating INTO that headroom — rather than adding it to every draw
// position — keeps bake space equal to METRICS space, which is the
// space a caller authored their gradient coordinates in. Add the pad to
Expand All @@ -732,7 +828,7 @@ export default class Text extends Renderable {
// padding differs per browser. The translate is a whole number of
// pixels, so glyph rasterization is untouched.
context.save();
context.translate(0, this.metrics.inkPadTop);
context.translate(this.metrics.inkPadLeft, this.metrics.inkPadTop);

const fill = setContextStyle(context, this);

Expand Down Expand Up @@ -815,7 +911,7 @@ export default class Text extends Renderable {
context.globalCompositeOperation = "source-in";
context.fillStyle = fill;
context.fillRect(
-1,
-this.metrics.inkPadLeft - 1,
-this.metrics.inkPadTop - 1,
canvas.width + 2,
canvas.height + 2,
Expand Down
48 changes: 43 additions & 5 deletions packages/melonjs/src/renderable/text/textmetrics.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Bounds } from "../../physics/bounds.ts";
import Text from "./text.js";
import setContextStyle from "./textstyle.js";
import setContextStyle, { strokeReach } from "./textstyle.js";

/**
* additional import for TypeScript
Expand Down Expand Up @@ -175,14 +175,21 @@ class TextMetrics extends Bounds {
// They describe the outline, though, not the pixels — see `INK_SLACK` —
// and where they are unavailable the padding falls back to `INK_GUESS`
// rather than to nothing.
//
// The same goes across: `width` is the advance of the longest line,
// and a glyph's ink can start before its line's alignment point (an
// "A" whose foot leans out, an italic) or end past its advance, the
// stroke adding its reach on both sides. `inkPadLeft` and
// `inkPadRight` are that overshoot, kept out of `width` the same way.
this.inkPadTop = 0;
this.inkPadBottom = 0;
this.inkPadLeft = 0;
this.inkPadRight = 0;
if (!isBitmapText && strings.length > 0 && typeof context !== "undefined") {
const style = this.ancestor;
const stroke =
style.lineWidth > 0 && style.strokeStyle.alpha > 0
? style.lineWidth / 2
: 0;
// half the line width past the outline, or for a "miter" join as
// far as its corners can point
const stroke = strokeReach(style);
const first = context.measureText(strings[0].trimEnd());
const last =
strings.length === 1
Expand All @@ -205,6 +212,37 @@ class TextMetrics extends Bounds {
Math.ceil(this.inkPadTop + bottom + stroke - this.lineHeight()) +
INK_SLACK,
);

// across, line by line: each sits at its own alignment point in
// the box, and its ink box is measured from that point
const align = style.textAlign;
const anchor =
align === "right"
? this.width
: align === "center"
? this.width / 2
: 0;
let left = 0;
let right = 0;
for (let i = 0; i < strings.length; i++) {
const line =
i === 0
? first
: i === strings.length - 1
? last
: context.measureText(strings[i].trimEnd());
const inkLeft = line.actualBoundingBoxLeft;
const inkRight = line.actualBoundingBoxRight;
if (Number.isFinite(inkLeft) && Number.isFinite(inkRight)) {
left = Math.max(left, inkLeft - anchor);
right = Math.max(right, anchor + inkRight - this.width);
}
}
this.inkPadLeft = Math.max(0, Math.ceil(left + stroke) + INK_SLACK);
// plus a pixel: the box sits on a whole pixel and the glyphs are
// baked at what that leaves over (see `updateOrigin`), so they can
// land up to a pixel right of where they were measured
this.inkPadRight = Math.max(0, Math.ceil(right + stroke + 1) + INK_SLACK);
}

this.width = Math.ceil(this.width);
Expand Down
48 changes: 48 additions & 0 deletions packages/melonjs/src/renderable/text/textstyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,52 @@ function resolveFill(context, style) {
return repeated;
}

/**
* The canvas default for `miterLimit`, kept when a Text is given a value the
* canvas would refuse (zero, negative, infinite or NaN).
* @ignore
* @internal
*/
const DEFAULT_MITER_LIMIT = 10;

/**
* The miter limit a Text's outline is drawn with.
* @param {object} style - the Text being drawn
* @returns {number} its `miterLimit`, or the canvas default for one the
* canvas would refuse
* @ignore
* @internal
*/
function miterLimitOf(style) {
const limit = style.miterLimit;
return Number.isFinite(limit) && limit > 0 ? limit : DEFAULT_MITER_LIMIT;
}

/**
* How far a Text's outline can reach past the glyph's own outline, in pixels.
*
* The stroke is centred on the outline, so it reaches half `lineWidth` past it
* on curves and edges, and at corners too with "round" or "bevel" joins. A
* "miter" join extends a corner to a point instead, and the canvas lets that
* point reach up to `miterLimit` half widths past the corner before it cuts it
* flat: on a sharp glyph like an "A" or an "M", several times further.
* @param {object} style - the Text being drawn
* @returns {number} the reach in pixels, 0 when there is no visible outline
* @ignore
* @internal
*/
export function strokeReach(style) {
if (!(style.lineWidth > 0) || !(style.strokeStyle?.alpha > 0)) {
return 0;
}
const half = style.lineWidth / 2;
if (style.lineJoin !== "miter") {
return half;
}
// below 1 every corner is past the limit and is cut flat: half a width
return half * Math.max(1, miterLimitOf(style));
}

/**
* apply the current text style to the given context
* @param {CanvasRenderingContext2D} context - the bake's context
Expand All @@ -80,6 +126,8 @@ export default function setContextStyle(context, style) {
context.fillStyle = fill;
context.strokeStyle = style.strokeStyle.toRGBA();
context.lineWidth = style.lineWidth;
context.lineJoin = style.lineJoin ?? "round";
context.miterLimit = miterLimitOf(style);
context.textAlign = style.textAlign;
context.textBaseline = style.textBaseline;
return fill;
Expand Down
Loading
Loading