{"id":15412452,"url":"https://github.com/robertknight/tex-linebreak","last_synced_at":"2025-04-03T12:11:41.296Z","repository":{"id":41086363,"uuid":"127423874","full_name":"robertknight/tex-linebreak","owner":"robertknight","description":"JavaScript implementation of the Knuth-Plass linebreaking algorithm","archived":false,"fork":false,"pushed_at":"2024-10-01T05:12:27.000Z","size":1306,"stargazers_count":139,"open_issues_count":15,"forks_count":9,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-29T18:07:36.937Z","etag":null,"topics":["algorithms","javascript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robertknight.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-30T12:06:54.000Z","updated_at":"2024-09-23T13:15:11.000Z","dependencies_parsed_at":"2023-02-17T02:16:35.935Z","dependency_job_id":"8f161955-66e2-46d3-b971-9a672b57f050","html_url":"https://github.com/robertknight/tex-linebreak","commit_stats":{"total_commits":136,"total_committers":4,"mean_commits":34.0,"dds":"0.17647058823529416","last_synced_commit":"c97c258e98d1a0648a562bacf85f98040e15dca1"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertknight%2Ftex-linebreak","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertknight%2Ftex-linebreak/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertknight%2Ftex-linebreak/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertknight%2Ftex-linebreak/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robertknight","download_url":"https://codeload.github.com/robertknight/tex-linebreak/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246998216,"owners_count":20866696,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithms","javascript"],"created_at":"2024-10-01T16:53:16.205Z","updated_at":"2025-04-03T12:11:41.279Z","avatar_url":"https://github.com/robertknight.png","language":"TypeScript","readme":"# tex-linebreak\n\n![npm version](https://img.shields.io/npm/v/tex-linebreak.svg)\n\n_tex-linebreak_ is a JavaScript library for laying out justified text as you\nwould find in a newspaper, book or technical paper. It implements the\nKnuth-Plass line-breaking algorithm, as used by TeX.\n\n## Introduction\n\nMost text on the web is presented with \"ragged-right\" margins, as opposed to\nthe justified text you would find in eg. a scientific paper or newspaper.\nText can be justified in web pages using `text-align: justify`.\nHowever this option alone tends to result in large\u0026nbsp;\u0026nbsp;\u0026nbsp;spaces\n\u0026nbsp;\u0026nbsp;\u0026nbsp;between words which is distracting to read. This is due to the\nuse of \"first fit\" line-breaking algorithms where the browser considers only the\ncurrent line when finding the next breakpoint. Some browsers support hyphenation\nvia `hyphens: auto` which reduces this effect. However the first-fit approach\ncan still produce wide lines and it can also produce more hyphenated lines than\nnecessary.\n\nThe Knuth-Plass algorithm on the other hand optimizes the spacing between words\nover the whole paragraph, seeking to minimize the overall \"badness\" of the\nlayout. This factor depends on the amount by which spaces have been shrunk or\nstretched and the number of hyphenated lines. The benefits of this approach are\ngreater when rendering narrower columns of text (eg. on small screens).\n\nThis table compares the same text rendered in the same environment (font, font\nsize, device width, margins) using CSS justification, CSS justification +\nhyphenation and this library:\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eSafari: text-align: justify\u003c/td\u003e\n    \u003ctd\u003eChrome: text-align: justify; hyphens: auto\u003c/td\u003e\n    \u003ctd\u003e_tex-linebreak_\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003cimg width=\"200\" src=\"images/bigint-safari-justify.png\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg width=\"200\" src=\"images/bigint-chrome-justify-hyphens.png\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg width=\"200\" src=\"images/bigint-tex-linebreak.png\"\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eCSS justification produces large spaces on the second and penultimate\n        lines.\u003c/td\u003e\n    \u003ctd\u003eEnabling hyphenation using `hyphens: auto` in browsers that support it\n        (as of 2018-04-07 this appears to be only Chrome) produces better\n        output but still produces wide lines.\u003c/td\u003e\n    \u003ctd\u003eThe TeX algorithm in contrast hyphenates fewer lines and avoids\n        excessive spacing between words.\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n_tex-linebreak_ has no dependencies on a particular JS environment (browser,\nNode) or render target (`\u003ccanvas\u003e`, HTML elements, PDF).\n\n## Try it out\n\nThe easiest way to see what the library can do is to [install the bookmarklet](\nsrc/demos/bookmarklet.js) and activate it on an existing web page, such as this\n[Medium article](https://medium.com/@parismarx/ubers-unrealistic-plan-for-flying-cars-6c9569d6fa8b).\n\nIt will justify and apply hyphenation to the content of any paragraph (`\u003cp\u003e`)\nelements on the page. The difference is more beneficial on smaller screens,\nso try in your browser's responsive design mode.\n\nNote that the bookmarklet does not work on sites that use\n[Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)\nto restrict where scripts can be loaded from.\n\n## Usage\n\nFirst, add the _tex-linebreak_ package to your dependencies:\n\n```sh\nnpm install tex-linebreak\n```\n\nThe library has low-level APIs which implement the core line-breaking and\npositioning algorithm, as well as higher-level APIs that provide a convenient\nway to justify existing HTML content.\n\n### Low-level APIs\n\nThe low-level APIs `breakLines` and `positionItems` work with generic \"box\"\n(typeset material), \"glue\" (spaces with flexible sizing) and \"penalty\" items.\nTypically \"boxes\" are words, \"glue\" items are spaces and \"penalty\" items\nrepresent hyphenation points or the end of a paragraph. However you can use them\nto lay out arbitrary content.\n\n```js\nimport { layoutItemsFromString, breakLines, positionItems } from 'tex-linebreak';\n\n// Convert your text to a set of \"box\", \"glue\" and \"penalty\" items used by the\n// line-breaking process.\n//\n// \"Box\" items are things (typically words) to typeset.\n// \"Glue\" items are spaces that can stretch or shrink or be a breakpoint.\n// \"Penalty\" items are possible breakpoints (hyphens, end of a paragraph etc.).\n//\n// `layoutItemsFromString` is a helper that takes a string and a function to\n// measure the width of a piece of that string and returns a suitable set of\n// items.\nconst measureText = text =\u003e text.length * 5;\nconst items = layoutItemsFromString(yourText, measureText);\n\n// Find where to insert line-breaks in order to optimally lay out the text.\nconst lineWidth = 200;\nconst breakpoints = breakLines(items, lineWidth)\n\n// Compute the (xOffset, line number) at which to draw each box item.\nconst positionedItems = positionItems(items, lineWidth, breakpoints);\n\npositionedItems.forEach(pi =\u003e {\n  const item = items[pi.item];\n\n  // Add code to draw `item.text` at `(box.xOffset, box.line)` to whatever output\n  // you want, eg. `\u003ccanvas\u003e`, HTML elements with spacing created using CSS,\n  // WebGL, ...\n});\n```\n\n### High-level APIs\n\nThe high-level APIs provide convenience methods for justifying content in\nexisting HTML elements and laying out justified lines for rendering to HTML,\ncanvas or other outputs. This includes support for hyphenation using the\n[hypher](https://github.com/bramstein/hypher) library.\n\n#### Justifying existing HTML content\n\nThe contents of an existing HTML element can be justified using the\n`justifyContent` function.\n\n```js\nimport enUsPatterns from 'hyphenation.en-us';\nimport { createHyphenator, justifyContent } from 'tex-linebreak';\n\nconst hyphenate = createHyphenator(enUsPatterns);\nconst paragraphs = Array.from(document.querySelectorAll('p'));\njustifyContent(paragraphs, hyphenate);\n```\n\nAfter an element is justified, its layout will remain fixed until `justifyContent`\nis called again. In order to re-justify content in response to window size\nchanges or other events, your code will need to listen for the appropriate\nevents and re-invoke `justifyContent`.\n\n#### Rendering text\n\nFor rendering justified text into a variety of targets (HTML, canvas, SVG,\nWebGL etc.), the `layoutText` helper can be used to lay out justifed text and\nobtain the positions which each word should be drawn at.\n\n```js\nimport { createHyphenator, layoutText } from 'tex-linebreak';\n\nimport enUsPatterns from 'hyphenation.en-us';\n\nconst hyphenate = createHyphenator(enUsPatterns);\nconst measure = word =\u003e word.length * 5;\n\nconst { items, positions } = layoutText(text, lineWidth, measure, hyphenate);\n\npositions.forEach(pos =\u003e {\n  // Draw text as in the above example for the low-level APIs\n});\n```\n\n## API reference\n\nThe source files in [src/](src/) have documentation in the form of TypeScript\nannotations.\n\n## Examples\n\nFor working code showing different ways to use this library, see [the\ndemos](src/demos/). You can build and run the demos using:\n\n```\nnpm i -g http-server\n\ngit clone https://github.com/robertknight/tex-linebreak.git\ncd tex-linebreak\nyarn\nyarn build-dev\nhttp-server -c-1\n```\n\nThen navigate to http://127.0.0.1:8080/src/demos/layout.html (note that\nhttp-server may choose a different port).\n\n## Caveats\n\nThe library currently has a number of caveats:\n\n- It is [not aware of floated content](https://github.com/robertknight/tex-linebreak/issues/1)\n  which can affect the available space in a paragraph to lay out text into.\n  In the presence of floats lines can exceed the width of the paragraph.\n- Justification of existing HTML content relies on modifying the DOM to insert\n  linebreaks and wrap text nodes in order to adjust inter-word spacing on each\n  line. This can be in slow in large documents. Test it on your content to\n  decide whether the overhead is acceptable for your use case. Also limit the\n  number of elements which you apply justification to.\n\n## References\n\n[1] D. E. Knuth and M. F. Plass, “[Breaking paragraphs into lines](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf),” Softw. Pract. Exp., vol. 11, no. 11, pp. 1119–1184, Nov. 1981.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertknight%2Ftex-linebreak","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobertknight%2Ftex-linebreak","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertknight%2Ftex-linebreak/lists"}