I made justif because I've long been a fan of justified text. I think it looks clean and elegant, and makes reading more enjoyable. But bad justification is the opposite, with gaping spaces that distract me to the point of making the text near unreadable.
Browsers have got better recently at handling justified text [0][1], but still use suboptimal greedy algorithms for the most part, and are not near that TeX/InDesign level of quality that I crave. `text-wrap: pretty` exists but is far from a panacea, as you can see for yourself in the demo.
Justif also has the benefit of providing more consistent text layout across browsers. Blink (Chrome), Gecko (Firefox) and WebKit (Safari) all justify text differently, so normally what the user sees depends on what browser they use.
Take a look at the demo, play around with it, see if you can break anything. I'm open to improvements of the API design as well, so please let me know your thoughts.
For more details, see the README: https://github.com/lyallcooper/justif
0: https://cloudfour.com/thinks/justified-text-better-than-expe...
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://issues.chromium.org/issues/40270471 [1] https://docs.google.com/document/d/1jJFD8nAUuiUX6ArFZQqQo8yT...
This is great, wish that browsers would implement a better algorithm.
Also good execution with the demo page.
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!
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.
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!
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.
But this demo blew me away! Fantastic!
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.
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 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".
<script type="module" blocking="render"
src="https://cdn.jsdelivr.net/npm/justif@0.5.0/dist/auto.js"
></script>
Users of something like this should ALWAYS use subresource integrity, and the example script tag should, too.