Nicely done! (and thanks for the fixes with v0.5.0. I submitted a couple more.)
jstrieb 16 hours ago [-]
Wow, I'm seriously impressed! Questions for the author:
I haven't had a chance to read the code yet, but how does this actually adjust the text? I get that it uses Knuth-Plass to calculate layout, but what does it do in CSS (or SVG?) to apply the layout to the DOM?
Looks like the first commit was sometime last week, which is pretty tight turnaround for something like this. For my own sanity when reading the code (no judgment either way), did you use LLMs to help write this? Knowing is helpful for me to calibrate what I pay attention to when reading through the code.
lyall 15 hours ago [-]
The layout is done entirely in DOM. The layout engine calculates the line breaks, tracking, expansion, etc. then applies the layout using HTML spans with the appropriate attributes applied. There’s generally one span per line, but that can change for more complicated lines! I invite you to inspect the demo page in your browser’s dev console if you’re particularly curious.
And yes, I did use LLMs for the implementation, both Fable 5 and 5.6 Sol specifically. I would not have been able to achieve anywhere close to the current result unaided. My patience and expertise would’ve been exhausted long before getting anywhere especially interesting.
My philosophy with LLMs is to try to use them to achieve levels of quality and polish that would’ve been previously unreasonable. I hope this project is a demonstration of that.
r3trohack3r 7 hours ago [-]
Amazing work! This is beautiful!
> My philosophy with LLMs is to try to use them to achieve levels of quality and polish that would’ve been previously unreasonable. I hope this project is a demonstration of that.
This philosophy has been growing on me - a coworker framed it as “nuance melts away with LLMs”
I recently wanted to render a 100+ page document in an interactive WYSIWYG editor using a single stroke font and join the letters to form single stroke cursive words that appeared handwritten.
The permutations of rulesets that governed connections between letters (I.e. how does t connect to c, and do you need to lift the pen to make that connection) amplified by the randomness injected into the strokes and line width/angle (handwriting doesn’t have uniform letters or lines!) would have been intractable previously.
But, with an agent harness, I could do 3 or 4 examples of what “right” looked like - give it the ability to generate and screenshot test cases to see the output of its work - and it could grind through a reasonable ruleset for about $20 in spend.
Rendello 18 hours ago [-]
I remember when I was learning CSS back in 2012, the guides said to never center-justify text as browsers did a terrible job. Since then, CSS has become way nicer, and yet justified text still looks awful. Great (or even good) typography always feels impossible on the web.
But this demo blew me away! Fantastic!
torgoguys 21 hours ago [-]
I haven't looked at the API yet, but the demo is great on my phone. Instead of just justified blocks, can you have it do a "justified" (even amount of spacing) from an irregularly shaped right edge (in my case, an image on the right side of a design template I made)?
globular-toast 15 hours ago [-]
The fact TeX can do stuff like this is crazy when you think about it. Especially because it was all a yak shave while Knuth was writing TAOCP.
lyall 20 hours ago [-]
That's not something justif supports directly right now. It currently targets rectangular blocks, not arbitrary edge shapes. But the underlying line-breaking approach could accommodate per-line widths, so it would be plausible to add I think.
Feel free to make a GitHub issue with details and I can take a look at supporting it. For now the workaround would be falling back to native justification by excluding the paragraph from justif, and using css float/shape-outside to get the desired edge.
ano-ther 6 hours ago [-]
This is great and it makes me wonder why the algorithm isn’t standard for browsers as a built-in feature (also for word processors).
Sure, it takes a bit longer, but LaTeX pretty much instantly produces a PDF nowadays even for long documents. 30 years ago, I scheduled a coffee break for that.
Users of something like this should ALWAYS use subresource integrity, and the example script tag should, too.
lyall 7 hours ago [-]
Good call. I've updated to release script to always include the SRI hash in the README sample.
svat 5 days ago [-]
This is great! Loved the demo and controls (and nice touch to include The Frog Prince as one of the sample texts lol). I think by doing tracking and expansion you're going even further than original Knuth TeX did/does. Maybe for the demo when hyphenation is turned off, the browser side should also turn it off? Or leave it like this, justif without hyphenation still does better than browser with hyphenation :)
Wish this project great success!
lyall 5 days ago [-]
Thank you! And yes by default TeX doesn't do tracking or expansion, but the microtype [0] package does.
And I did struggle a bit with deciding how best to do the comparison to native browser rendering. I wanted it to be an apples-to-apples comparison, but also didn't want to unfairly handicap the browser by disabling something like hyphenation. It's already bad enough as is...
Side-by-side didn’t do it for me but flicker made all the difference!
This is great, wish that browsers would implement a better algorithm.
Also good execution with the demo page.
airstrike 23 hours ago [-]
This demo feels about as good as eating a spoonful of tiramisu
emil-lp 11 hours ago [-]
Great work. It looks as the space in "No. 12" is too large. Is it possible to distinguish between space and sentence-end-space?
lyall 11 hours ago [-]
Good eye! The space in "No. 12" would traditionally get a thin space [0] in professionally typeset documents, which is smaller than the standard space used for inter-word and inter-sentence spacing.
The solution here is to fix the sample text itself to use U+202F (NARROW NO-BREAK SPACE,  ). As opposed to the standard width no-break space it currently has for "No. 12" and "Fig. 7".
The "standard" method would be to double space after the end of a sentence. This is how "filling" paragraphs works in org-mode to avoid breaking a line in the middle of an abbreviation.
WillAdams 8 hours ago [-]
No, that's a convention from typing.
The actual approach used by a printer would be to be more generous with spacing material at the end of a sentence.
The relevant TeX commands are \frenchspacing or \nofrenchspacing.
asimpletune 8 hours ago [-]
Does this work with drop caps or could it be made to work with them? Cool work by the way.
For OP: I think "Type specimen" should be the default sample text, just to get readers into the right spirit. :)
For anyone else reading this: make sure you click on the "controls & metrics" at the bottom to try out all the options (such as comparing with your browser's "text-wrap: pretty"); it's really cool!
lyall 22 hours ago [-]
It is the default if you open the demo on a narrow screen! It has a more striking difference between browser and justif layouts at narrow widths than the Alice sample. But maybe you’re right and it should be the default for everyone.
globular-toast 15 hours ago [-]
How narrow? On my phone it defaulted to Alice.
lyall 14 hours ago [-]
It should be when the screen width is less than 456 css px with the current configuration.
userbinator 15 hours ago [-]
Beware: the TeX justification algorithm is quadratic-time.
lyall 14 hours ago [-]
Thankfully the unit it operates on is a paragraph, not the whole body of text. Also thankfully computers are very fast these days!
I added the “Alice in Wonderland (long)” text sample to the demo as a stress test, but its paragraphs are still relatively short. I should add a sample that’s one really long paragraph and see what happens. I may need to add a bail-out after X items option if it calls for it.
sebmellen 5 hours ago [-]
Will be switching my blog to this.
heroncoder 10 hours ago [-]
Good,this is very helpful for handling the layout in the print template.
arnoooooo 12 hours ago [-]
This is awesome ! How hard would it be to get something similar in React Native ?
dithernaut 12 hours ago [-]
This is great! I was avoiding justify because it never looks good on web.
pwmglenn 9 hours ago [-]
Absolutely stunning! Thank you so much for sharing!
savoyard 18 hours ago [-]
A most welcome addition to a webmaster’s toolkit. Congratulations!
taevirus 16 hours ago [-]
Good
vhwalke 22 hours ago [-]
Follow-up, since I asked the questions above: I spent the evening running Justif against the hardest page I have: three 356px columns of 18th-century text set in IM Fell at ~17.6px with 24.3px leading. I came in curious and left planning the integration.
The numbers were better than I hoped. A full ~5k-word essay justified in about 280ms, and ~36KB gzipped including the en-US patterns. And the output is the part that sold me. The columns look like typesetting rather than CSS, which is the whole thing I'd been grumbling about.
The only snag I hit: a floated initial letter (drop cap) trips it — the first line ignores the float and overflows the gutter. Excluding that paragraph and leaving it on native justify renders perfectly, so it's cosmetic to work around, but a first-class exclusion selector would be lovely. I'll file an issue with the details and our measurements.
Best part: this experiment exposed that our sheets are currently set ragged right, which no printer in 1787 would have tolerated. Your library may be what finally makes us historically honest. Test page for context: federalistreader.org/papers/10/
Thank you for building this!
imurray 12 hours ago [-]
[flagged]
vhwalke 23 hours ago [-]
Wow! I've spent the past few weeks setting 18th-century newspaper text on the web - justified, up to three narrow columns, period ligature, and the gap between CSS justification and real typesetting was a big part of the whole project. The browser's greedy line breaker plus hyphens: auto gets you maybe 80% there, and the last 20% is rivers, loose lines, and archaic words the hyphenation dictionaries have never met. "Fœderal" produces some genuinely creative break points. The result I got to I feel like is good, but not great.
Questions, from someone who wants this to work:
* Does Justif do its own hyphenation (Liang patterns?) or lean on the browser's? And is there a way to feed it exceptions for unusual or archaic spellings?
* How does it behave at narrow measures, say 35–40ch columns? That's where Knuth-Plass should shine most, but it's also where badness compounds fastest.
* What's the reflow cost on long documents (5–10k words) when the container resizes or the user scales the text — full re-run or incremental?
* Any thoughts on coexisting with text-wrap: pretty as browsers slowly grow real paragraph-level breaking?
Congrats on shipping this. I miss producing documents with TeX!
lyall 22 hours ago [-]
Hyphenation: yes, justif does its own hyphenation. It ships with TeX’s hyphenation dictionaries for a bunch of languages, but supports custom dictionaries as well! See https://github.com/lyallcooper/justif#hyphenation
Narrow columns: open up the controls on the demo and try adjusting the width slider :)
Long documents: try the “Alice in Wonderland (long)” sample text in the demo to get a feel for how it handles long documents. In general it performs well enough I think, though Safari is a little slower than other platforms. It doesn’t do it incrementally (not sure that’s possible), but I spent quite a while trying to make sure it was fast enough to re-layout in real time even on long documents.
I haven’t thought about coexisting. I’m not sure how that could work but I’m open to ideas.
wrs 24 hours ago [-]
Chromium supposedly implemented Knuth-Plass for CSS's "text-wrap: balance", but reading the end of the tracking bug [0] it seems it stops at six lines? (Design doc: [1])
https://federalistreader.org/notes/justif/
Nicely done! (and thanks for the fixes with v0.5.0. I submitted a couple more.)
I haven't had a chance to read the code yet, but how does this actually adjust the text? I get that it uses Knuth-Plass to calculate layout, but what does it do in CSS (or SVG?) to apply the layout to the DOM?
Looks like the first commit was sometime last week, which is pretty tight turnaround for something like this. For my own sanity when reading the code (no judgment either way), did you use LLMs to help write this? Knowing is helpful for me to calibrate what I pay attention to when reading through the code.
And yes, I did use LLMs for the implementation, both Fable 5 and 5.6 Sol specifically. I would not have been able to achieve anywhere close to the current result unaided. My patience and expertise would’ve been exhausted long before getting anywhere especially interesting.
My philosophy with LLMs is to try to use them to achieve levels of quality and polish that would’ve been previously unreasonable. I hope this project is a demonstration of that.
> My philosophy with LLMs is to try to use them to achieve levels of quality and polish that would’ve been previously unreasonable. I hope this project is a demonstration of that.
This philosophy has been growing on me - a coworker framed it as “nuance melts away with LLMs”
I recently wanted to render a 100+ page document in an interactive WYSIWYG editor using a single stroke font and join the letters to form single stroke cursive words that appeared handwritten.
The permutations of rulesets that governed connections between letters (I.e. how does t connect to c, and do you need to lift the pen to make that connection) amplified by the randomness injected into the strokes and line width/angle (handwriting doesn’t have uniform letters or lines!) would have been intractable previously.
But, with an agent harness, I could do 3 or 4 examples of what “right” looked like - give it the ability to generate and screenshot test cases to see the output of its work - and it could grind through a reasonable ruleset for about $20 in spend.
But this demo blew me away! Fantastic!
Feel free to make a GitHub issue with details and I can take a look at supporting it. For now the workaround would be falling back to native justification by excluding the paragraph from justif, and using css float/shape-outside to get the desired edge.
Sure, it takes a bit longer, but LaTeX pretty much instantly produces a PDF nowadays even for long documents. 30 years ago, I scheduled a coffee break for that.
Wish this project great success!
And I did struggle a bit with deciding how best to do the comparison to native browser rendering. I wanted it to be an apples-to-apples comparison, but also didn't want to unfairly handicap the browser by disabling something like hyphenation. It's already bad enough as is...
0: https://ctan.org/pkg/microtype
This is great, wish that browsers would implement a better algorithm.
Also good execution with the demo page.
The solution here is to fix the sample text itself to use U+202F (NARROW NO-BREAK SPACE,  ). As opposed to the standard width no-break space it currently has for "No. 12" and "Fig. 7".
0: https://en.wikipedia.org/wiki/Thin_space
The actual approach used by a printer would be to be more generous with spacing material at the end of a sentence.
The relevant TeX commands are \frenchspacing or \nofrenchspacing.
For anyone else reading this: make sure you click on the "controls & metrics" at the bottom to try out all the options (such as comparing with your browser's "text-wrap: pretty"); it's really cool!
I added the “Alice in Wonderland (long)” text sample to the demo as a stress test, but its paragraphs are still relatively short. I should add a sample that’s one really long paragraph and see what happens. I may need to add a bail-out after X items option if it calls for it.
The numbers were better than I hoped. A full ~5k-word essay justified in about 280ms, and ~36KB gzipped including the en-US patterns. And the output is the part that sold me. The columns look like typesetting rather than CSS, which is the whole thing I'd been grumbling about.
The only snag I hit: a floated initial letter (drop cap) trips it — the first line ignores the float and overflows the gutter. Excluding that paragraph and leaving it on native justify renders perfectly, so it's cosmetic to work around, but a first-class exclusion selector would be lovely. I'll file an issue with the details and our measurements.
Best part: this experiment exposed that our sheets are currently set ragged right, which no printer in 1787 would have tolerated. Your library may be what finally makes us historically honest. Test page for context: federalistreader.org/papers/10/
Thank you for building this!
Questions, from someone who wants this to work:
* Does Justif do its own hyphenation (Liang patterns?) or lean on the browser's? And is there a way to feed it exceptions for unusual or archaic spellings?
* How does it behave at narrow measures, say 35–40ch columns? That's where Knuth-Plass should shine most, but it's also where badness compounds fastest.
* What's the reflow cost on long documents (5–10k words) when the container resizes or the user scales the text — full re-run or incremental?
* Any thoughts on coexisting with text-wrap: pretty as browsers slowly grow real paragraph-level breaking?
Congrats on shipping this. I miss producing documents with TeX!
Narrow columns: open up the controls on the demo and try adjusting the width slider :)
Long documents: try the “Alice in Wonderland (long)” sample text in the demo to get a feel for how it handles long documents. In general it performs well enough I think, though Safari is a little slower than other platforms. It doesn’t do it incrementally (not sure that’s possible), but I spent quite a while trying to make sure it was fast enough to re-layout in real time even on long documents.
I haven’t thought about coexisting. I’m not sure how that could work but I’m open to ideas.
[0] https://issues.chromium.org/issues/40270471 [1] https://docs.google.com/document/d/1jJFD8nAUuiUX6ArFZQqQo8yT...