opentype.js icon indicating copy to clipboard operation
opentype.js copied to clipboard

Character rendering issues occur when stroking text.

Open ILOVEPIE opened this issue 1 year ago • 11 comments
trafficstars

Character rendering issues occur when stroking text.

Expected Behavior

The stroke should match the fill.

Current Behavior

Random dots of stroke appear sometimes, also some lines cut inwards into the fill. cutting_in cutting_in_2 dots

Possible Solution

Unknown

Steps to Reproduce (for bugs)

I'll provide a link to the other maintainers so we can narrow down a test-case.

Context

I need to provide psychovisually lossless rendering of Advanced SubStation Alpha, this bug is causing issues for me.

Your Environment

  • Version used: 2.0.0-git
  • Font used: DFKai-SB, Monotype Corsiva,Franklin Gothic Book
  • Browser Name and version: All
  • Operating System and version (desktop or mobile): All
  • Link to your project: https://github.com/SABRE-JS/SABRE.js

ILOVEPIE avatar Apr 30 '24 05:04 ILOVEPIE

Work for me.

index.html:

<html>
	<script src="opentype.js" defer></script>
	<script src="script.js" defer></script>
<body>
<canvas id="canvas"></canvas>
</body>
</html>

script.js:

window.onload =
	async function( )
{
	const buffer = await ( await fetch('franklin.ttf') ).arrayBuffer( );
	const font = opentype.parse( buffer );

	const canvas = document.getElementById( 'canvas' );
	canvas.width = 1000;
	canvas.height = 400;
	const cx = canvas.getContext( '2d' );

	const path = font.getPath( 'Japan! Tokyo', 10, 80, 100, { } );
	path.stroke = 'blue';
	path.strokeWidth = 3;
	path.fill = 'lightgray';
	cx.lineJoin = 'round';
	cx.lineCap = 'round';
	path.draw( cx );
}

franklin.ttf: from here: https://fontsgeek.com/fonts/Franklin-Gothic-Book-Regular

result: Screenshot from 2024-05-09 07-46-18

PS: What I do noticed, is that some glyph would benefit from calling closePath() what I believe is not done.

Otherwise can you modify my minimal example so it makes the error you are seeing?

PPS: for completness, just put the files in a directory for example called web and npm install http-server ./node_modules/.bin/http-server -c-1 web/ to run it.

axkibe avatar May 09 '24 05:05 axkibe

This issue was mostly created so that I could keep track of my progress looking into the issue, but if you're willing to help me track this down, thanks. The way I'm doing this is iterating through the glyphs and drawing them individually instead of the whole thing all at once (because I have to composite them with webgl). The version of the font I'm using is identical to the one you found. Here's a simplified version of the drawGlyph function I'm using:

function drawGlyph (glyph, offsetX, offsetY, stroke) {
    const fontSize = this._fontInfo.size;
    const fontUnitsScale = this._fontInfo.font.unitsPerEm || 1000;
    const fontSizeMultiplier = fontSize / fontUnitsScale;
    const yoffset =
        this._fontInfo.font.ascender * fontSizeMultiplier;
    const path = glyph.getPath(
        offsetX,
        offsetY + yoffset,
        fontSize
    );
    path.fill = null;
    path.stroke = null;
    path.draw(this._ctx);
    if (stroke) {
        this._ctx.stroke();
    } else {
        this._ctx.fill();
    }
}

ILOVEPIE avatar May 09 '24 19:05 ILOVEPIE

I'm going to have to adapt it for variable fonts, but that comes after resolving this bug. adding closePath doesn't seem to fix it

ILOVEPIE avatar May 10 '24 01:05 ILOVEPIE

What I do noticed, is that some glyph would benefit from calling closePath() what I believe is not done.

Do you have examples for this? While implementing variable fonts, I added a performance optimisation so that closePath() is only called when a glyph has a stroke applied, because the command is automatically executed for filled fonts, which resulted in unnecessary overhead because it was then called twice.

Connum avatar May 14 '24 20:05 Connum

What I do noticed, is that some glyph would benefit from calling closePath() what I believe is not done.

Do you have examples for this? While implementing variable fonts, I added a performance optimisation so that closePath() is only called when a glyph has a stroke applied, because the command is automatically executed for filled fonts, which resulted in unnecessary overhead because it was then called twice.

Well I guess there you have the issue, ILOVEPIE introduced it by setting stroke to null, then stroking himself. So paths are not closed.

BTW: I really dislike the API design in the first place. The API should simply be path.sketch() or follow() or whatever verb matches, and the caller should then call stroke and or fill. As you seen in my example the lineJoin/lineCap things are missing for example anyway.

When I was testing this with lineCaps as butts the ! for example wasn't closed.

axkibe avatar May 15 '24 13:05 axkibe

Yeah, There's some issues with the drawing code. I'll rework it some.

ILOVEPIE avatar May 18 '24 19:05 ILOVEPIE

BTW: If all you need it filling and stroking of text.. do you need opentype at all? canvas.fillText and canvas.strokeText should be fine for this. (I needed opentype.js for rendering, because I needed endless zooming where these functions had a size limit and they are missing getAdvanceWith() for caret based editors, but otherwise for this example I see it as unnessary complication to use this library at all..)

axkibe avatar May 28 '24 08:05 axkibe

BTW: If all you need it filling and stroking of text.. do you need opentype at all? canvas.fillText and canvas.strokeText should be fine for this. (I needed opentype.js for rendering, because I needed endless zooming where these functions had a size limit and they are missing getAdvanceWith() for caret based editors, but otherwise for this example I see it as unnessary complication to use this library at all..)

I have to use OpenType.js because I have to have access to the information required to render it like Windows does because I have to emulate Windows font rendering for my project.

ILOVEPIE avatar Jun 02 '24 21:06 ILOVEPIE

OK, how is it different than canvas with converted to .woff does?

axkibe avatar Jun 05 '24 15:06 axkibe

From my understanding this line https://github.com/opentypejs/opentype.js/blob/master/src/glyph.mjs#L235 should be something like this } else if (cmd.type === 'Z' && ((p.stroke && p.strokeWidth) || options.closePaths)) { to enable drawing with completed paths outside of the internal drawing code, this maybe could also be done if options.drawSVG is true. What do you all think? Should I make a PR?

talltyler avatar Jun 22 '24 22:06 talltyler

From my understanding this line https://github.com/opentypejs/opentype.js/blob/master/src/glyph.mjs#L235 should be something like this } else if (cmd.type === 'Z' && ((p.stroke && p.strokeWidth) || options.closePaths)) { to enable drawing with completed paths outside of the internal drawing code, this maybe could also be done if options.drawSVG is true. What do you all think? Should I make a PR?

I think path should also be closed when M command is used. Or at least when a glyph ends. Because I notice Some font just don't use Z command. But maybe that's a mistake of the font. What do you think?

utunnels avatar Aug 13 '24 21:08 utunnels