{"_id":"@chenglou/pretext","_rev":"11-8d449dd0bac776b84244682c5a2d1890","name":"@chenglou/pretext","dist-tags":{"latest":"1.0.0"},"versions":{"0.0.0":{"name":"@chenglou/pretext","version":"0.0.0","license":"MIT","_id":"@chenglou/pretext@0.0.0","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"dist":{"shasum":"f1ae56b90b34b8be94e3da3ea1023c9bb107a165","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.0.tgz","fileCount":20,"integrity":"sha512-NJI3lKChGmPgk2x/sA3r4PbZoYNPcsjOqGyZeZhYu3luioQ1ld4ZY/1jcmcpg0hhOUpfbf9/mSEWdD3yQ90vtQ==","signatures":[{"sig":"MEQCIDgaHbDpYCDZn1zu/JF4hnWAwqMnaMElW80Qg5mEv1HYAiBG+UhvT6CkSsDpa5B8NHN6t6SGRFQzqv6rxxT9DMSFqg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":397708},"type":"module","types":"./src/layout.ts","exports":{".":"./src/layout.ts","./demos/*":"./pages/demos/*","./assets/*":"./pages/assets/*","./package.json":"./package.json"},"gitHead":"0662f0baba693175e7ec1478e0d6832d6b9475bd","scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --watch --no-clear-screen --host=$HOST:$PORT","start:lan":"HOST=0.0.0.0 bun run start","probe-check":"bun run scripts/probe-check.ts","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","gatsby-check":"bun run scripts/gatsby-check.ts","gatsby-sweep":"bun run scripts/gatsby-sweep.ts","accuracy-check":"bun run scripts/accuracy-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","gatsby-check:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-check.ts","gatsby-sweep:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-sweep.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-representative":"bun run scripts/corpus-representative.ts --output=corpora/representative.json","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"_npmVersion":"10.9.2","description":"Pure JavaScript/TypeScript library for multiline text measurement & layout. Fast, accurate & supports all the languages you didn't even know about. Allows rendering to DOM, Canvas, SVG and soon, server-side.","directories":{},"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","harfbuzzjs":"^0.10.0","oxlint-tsgolint":"^0.15.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.0_1774580679359_0.9586565655372601","host":"s3://npm-registry-packages-npm-production"}},"0.0.1":{"name":"@chenglou/pretext","version":"0.0.1","license":"MIT","_id":"@chenglou/pretext@0.0.1","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"dist":{"shasum":"0a54286d9e5d0f9f63676ff06ce8801ad6a44d04","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.1.tgz","fileCount":23,"integrity":"sha512-PWUuN7kGuWOtbBRgabWG56jrz1cIjEVR5reWUiRVtrKnM7fnSprKh4H9vfCv2JB1xhkjFqZojEJC/paFDurgng==","signatures":[{"sig":"MEQCIAdyL4kopzyP9gCzsztLW+etfxwFVoVqfv+A4RMZVCU2AiAh0m+hepPfrYehYz6HCgCyJmxg1dQgORoxrdz8nVBMQw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":424622},"type":"module","types":"./src/layout.ts","exports":{".":"./src/layout.ts","./demos/*":"./pages/demos/*","./assets/*":"./pages/assets/*","./package.json":"./package.json"},"gitHead":"b7323f2faa7896d53c671eebd0187db3d97e4240","scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","start:lan":"HOST=0.0.0.0 bun run start","probe-check":"bun run scripts/probe-check.ts","start:watch":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --watch --no-clear-screen --host=$HOST:$PORT","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","gatsby-check":"bun run scripts/gatsby-check.ts","gatsby-sweep":"bun run scripts/gatsby-sweep.ts","accuracy-check":"bun run scripts/accuracy-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","gatsby-check:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-check.ts","gatsby-sweep:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-sweep.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-representative":"bun run scripts/corpus-representative.ts --output=corpora/representative.json","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"_npmVersion":"10.9.2","description":"Pure JavaScript/TypeScript library for multiline text measurement & layout. Fast, accurate & supports all the languages you didn't even know about. Allows rendering to DOM, Canvas, SVG and soon, server-side.","directories":{},"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","harfbuzzjs":"^0.10.0","oxlint-tsgolint":"^0.15.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.1_1774612061502_0.6686825545571469","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@chenglou/pretext","version":"0.0.2","license":"MIT","_id":"@chenglou/pretext@0.0.2","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"dist":{"shasum":"625436a62b267c1777510efe978baa893e1653fd","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.2.tgz","fileCount":29,"integrity":"sha512-bMPySDxbU1h+qU3D7HzTsz2vhBFIeO19Yp8gsYTJ219tjuX7WFsKJzk2oPVpwxl2P1VEuQGo6whg6Kr8gztphA==","signatures":[{"sig":"MEYCIQD4X6Vb6eY3mlOZ8peMFPNoPTjN+4H+KYcEzSGMwg1EkQIhAJWL5RKhlao8Y8CUWFSkOcC05feYocer1C0eUbMTTPtn","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":512701},"type":"module","types":"./src/layout.ts","exports":{".":"./src/layout.ts","./demos/*":"./pages/demos/*","./assets/*":"./pages/assets/*","./package.json":"./package.json"},"gitHead":"2604c28ff32a437d1eaa7eadcc1800444b441e1c","scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","start:lan":"HOST=0.0.0.0 bun run start","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","start:watch":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --watch --no-clear-screen --host=$HOST:$PORT","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","gatsby-check":"bun run scripts/gatsby-check.ts","gatsby-sweep":"bun run scripts/gatsby-sweep.ts","accuracy-check":"bun run scripts/accuracy-check.ts","pre-wrap-check":"bun run scripts/pre-wrap-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","gatsby-check:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-check.ts","gatsby-sweep:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-sweep.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-representative":"bun run scripts/corpus-representative.ts --output=corpora/representative.json","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"_npmVersion":"10.9.2","description":"Pure JavaScript/TypeScript library for multiline text measurement & layout. Fast, accurate & supports all the languages you didn't even know about. Allows rendering to DOM, Canvas, SVG and soon, server-side.","directories":{},"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","harfbuzzjs":"^0.10.0","oxlint-tsgolint":"^0.15.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.2_1774692288350_0.5738594800512049","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@chenglou/pretext","version":"0.0.3","license":"MIT","_id":"@chenglou/pretext@0.0.3","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"dist":{"shasum":"877f847afa8ea6a268d530a214369cfa0475dde1","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.3.tgz","fileCount":42,"integrity":"sha512-RQmqMqUAPRCyv4R3LlRi/ao6KbNWYclqLA+V1HS7sWgyUUbjn3JmmlfXZSY/BjM4rbmIaMSyIVisYocYGYftiQ==","signatures":[{"sig":"MEUCIQDWQl4uYQo5aXiyrqR/eEyZtbzcXIdMtoMUKXjzlXjYgAIgOA+bCd+vpdDyun7FyxoZp1fCS02gOk/YFbKCVHpvICo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":641608},"main":"./dist/layout.js","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./demos/*":"./pages/demos/*","./assets/*":"./pages/assets/*","./package.json":"./package.json"},"gitHead":"19451bcdbdf597e3c92183393eaf7e2f3406c7b4","scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","prepack":"rm -rf dist && tsc -p tsconfig.build.json","start:lan":"HOST=0.0.0.0 bun run start","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","start:watch":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --watch --no-clear-screen --host=$HOST:$PORT","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","gatsby-check":"bun run scripts/gatsby-check.ts","gatsby-sweep":"bun run scripts/gatsby-sweep.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","accuracy-check":"bun run scripts/accuracy-check.ts","pre-wrap-check":"bun run scripts/pre-wrap-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","package-smoke-test":"bun run scripts/package-smoke-test.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","gatsby-check:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-check.ts","gatsby-sweep:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-sweep.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-representative":"bun run scripts/corpus-representative.ts --output=corpora/representative.json","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"_npmVersion":"10.9.2","description":"Pure JavaScript/TypeScript library for multiline text measurement & layout. Fast, accurate & supports all the languages you didn't even know about. Allows rendering to DOM, Canvas, SVG and soon, server-side.","directories":{},"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","typescript":"6.0.2","oxlint-tsgolint":"^0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.3_1774776912435_0.04257439531458229","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@chenglou/pretext","version":"0.0.4","license":"MIT","_id":"@chenglou/pretext@0.0.4","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"dist":{"shasum":"6f9b955f406acd7160d5531adb4c20ec9917b0fb","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.4.tgz","fileCount":47,"integrity":"sha512-FnPAFMid1/p1j2V2gRPUVBarGUIb2PhkkC9YNnTOfPtTDgHKh8siO8PP9pCxpFfYlcodWPJpE1UbSHGQqt8pQQ==","signatures":[{"sig":"MEQCIDA/5EfWPdteXwoTbJhEz5mtZR0nkiBsfwA3YPW45kcuAiBEE2ZzUT4TSDNlTJ+NbY1mT4z+YF8UAPkwxofSlpq2uQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":693392},"main":"./dist/layout.js","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./demos/*":"./pages/demos/*","./assets/*":"./pages/assets/*","./package.json":"./package.json"},"gitHead":"4e7139033640e09a299b06c240981eadb5894d74","scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","prepack":"rm -rf dist && tsc -p tsconfig.build.json","start:lan":"HOST=0.0.0.0 bun run start","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","start:watch":"HOST=${HOST:-127.0.0.1}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --watch --no-clear-screen --host=$HOST:$PORT","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","gatsby-check":"bun run scripts/gatsby-check.ts","gatsby-sweep":"bun run scripts/gatsby-sweep.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","corpus-status":"bun run scripts/corpus-status.ts","accuracy-check":"bun run scripts/accuracy-check.ts","pre-wrap-check":"bun run scripts/pre-wrap-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","status-dashboard":"bun run scripts/status-dashboard.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","package-smoke-test":"bun run scripts/package-smoke-test.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","gatsby-check:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-check.ts","gatsby-sweep:safari":"GATSBY_CHECK_BROWSER=safari bun run scripts/gatsby-sweep.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-representative":"bun run scripts/corpus-representative.ts --output=corpora/representative.json","corpus-status:refresh":"bun run scripts/corpus-representative.ts --output=corpora/representative.json && bun run scripts/corpus-sweep.ts --all --samples=9 --output=corpora/chrome-sampled.json && bun run scripts/corpus-sweep.ts --all --start=300 --end=900 --step=10 --output=corpora/chrome-step10.json && bun run scripts/corpus-status.ts","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"_npmVersion":"10.9.2","description":"Pure JavaScript/TypeScript library for multiline text measurement & layout. Fast, accurate & supports all the languages you didn't even know about. Allows rendering to DOM, Canvas, SVG and soon, server-side.","directories":{},"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","typescript":"6.0.2","oxlint-tsgolint":"^0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.4_1775067857522_0.8105399379773799","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@chenglou/pretext","version":"0.0.5","keywords":["text-layout","text-measurement","line-breaking","typography","canvas","svg","unicode"],"license":"MIT","_id":"@chenglou/pretext@0.0.5","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"homepage":"https://github.com/chenglou/pretext#readme","bugs":{"url":"https://github.com/chenglou/pretext/issues"},"dist":{"shasum":"1a6734df005c951c6aecaa0decced0d2a82034f2","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.5.tgz","fileCount":58,"integrity":"sha512-A8GZN10REdFGsyuiUgLV8jjPDDFMg5GmgxGWV0I3igxBOnzj+jgz2VMmVD7g+SFyoctfeqHFxbNatKSzVRWtRg==","signatures":[{"sig":"MEUCICg9npIWMSx7MXIPsl7h2WA9yLy0xXWWJ6RVNin95btrAiEAlaK6s1CHqoP5aeMzEOX1Jw0DdOCE6rGfM7c7PDyBcOM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":861347},"main":"./dist/layout.js","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./demos/*":"./pages/demos/*","./assets/*":"./pages/assets/*","./rich-inline":{"types":"./dist/rich-inline.d.ts","import":"./dist/rich-inline.js","default":"./dist/rich-inline.js"},"./package.json":"./package.json"},"scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-0.0.0.0}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","prepack":"rm -rf dist && tsc -p tsconfig.build.json","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","corpus-status":"bun run scripts/corpus-status.ts","start:windows":"bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=0.0.0.0:3000","accuracy-check":"bun run scripts/accuracy-check.ts","keep-all-check":"bun run scripts/keep-all-check.ts","pre-wrap-check":"bun run scripts/pre-wrap-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","status-dashboard":"bun run scripts/status-dashboard.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","generate:bidi-data":"bun run scripts/generate-bidi-data.ts","package-smoke-test":"bun run scripts/package-smoke-test.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-status:refresh":"bun run scripts/corpus-sweep.ts --all --start=300 --end=900 --step=10 --output=corpora/chrome-step10.json && bun run scripts/corpus-sweep.ts --browser=safari --all --start=300 --end=900 --step=10 --output=corpora/safari-step10.json && bun run scripts/corpus-status.ts","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"repository":{"url":"git+https://github.com/chenglou/pretext.git","type":"git"},"_npmVersion":"10.9.2","description":"Fast, accurate & comprehensive text measurement & layout","directories":{},"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"marked":"^17.0.5","oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","typescript":"6.0.2","oxlint-tsgolint":"^0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.5_1775666950182_0.850236511549961","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@chenglou/pretext","version":"0.0.6","keywords":["text-layout","text-measurement","line-breaking","typography","canvas","svg","unicode"],"license":"MIT","_id":"@chenglou/pretext@0.0.6","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"homepage":"https://github.com/chenglou/pretext#readme","bugs":{"url":"https://github.com/chenglou/pretext/issues"},"dist":{"shasum":"1f4a37d3c8cb24c0e6a2e6b863601ee1337063e5","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.6.tgz","fileCount":61,"integrity":"sha512-U10s4tFeyu3oVHfXuNWwZSKqHXefhaigpcBkGj60qQFRJ+yUoQ+ez3cGJelP7BWDAB58HCgjcTSmOcg+77afBQ==","signatures":[{"sig":"MEYCIQCdeh+4hXy06I4erqYbsAWax/GmcXDVyJqtld/dChLFUQIhAMm9CPgWnwhy+l1syZ30oBr35uAZmywmpv0cGdfte1NF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":880230},"main":"./dist/layout.js","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./demos/*":"./pages/demos/*","./assets/*":"./pages/assets/*","./rich-inline":{"types":"./dist/rich-inline.d.ts","import":"./dist/rich-inline.js","default":"./dist/rich-inline.js"},"./package.json":"./package.json"},"scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-0.0.0.0}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","prepack":"rm -rf dist && tsc -p tsconfig.build.json","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","corpus-status":"bun run scripts/corpus-status.ts","start:windows":"bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=0.0.0.0:3000","accuracy-check":"bun run scripts/accuracy-check.ts","keep-all-check":"bun run scripts/keep-all-check.ts","pre-wrap-check":"bun run scripts/pre-wrap-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","status-dashboard":"bun run scripts/status-dashboard.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","generate:bidi-data":"bun run scripts/generate-bidi-data.ts","package-smoke-test":"bun run scripts/package-smoke-test.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","letter-spacing-check":"bun run scripts/letter-spacing-check.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-status:refresh":"bun run scripts/corpus-sweep.ts --all --start=300 --end=900 --step=10 --output=corpora/chrome-step10.json && bun run scripts/corpus-sweep.ts --browser=safari --all --start=300 --end=900 --step=10 --output=corpora/safari-step10.json && bun run scripts/corpus-status.ts","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","letter-spacing-snapshot":"bun run scripts/letter-spacing-check.ts --output=accuracy/letter-spacing.json","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"repository":{"url":"git+https://github.com/chenglou/pretext.git","type":"git"},"_npmVersion":"10.9.2","description":"Fast, accurate & comprehensive text measurement & layout","directories":{},"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"marked":"^17.0.5","oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","typescript":"6.0.2","oxlint-tsgolint":"^0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.6_1776837105193_0.8758002017488102","host":"s3://npm-registry-packages-npm-production"}},"0.0.7":{"name":"@chenglou/pretext","version":"0.0.7","keywords":["text-layout","text-measurement","line-breaking","typography","canvas","svg","unicode"],"license":"MIT","_id":"@chenglou/pretext@0.0.7","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"homepage":"https://github.com/chenglou/pretext#readme","bugs":{"url":"https://github.com/chenglou/pretext/issues"},"dist":{"shasum":"f31fb73cbf13f381d07e80f1d20c1018e86d2f57","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.7.tgz","fileCount":61,"integrity":"sha512-FV5hj3fGqpBlzbANUbR+s+6XuNRrghVVyuNs33zdH2SzU7MvK+7GlW6xREjDCreixKbexEn7OEkDgAFeWuu5Hg==","signatures":[{"sig":"MEQCIGcj8Eoty/pnUKzJ0rzq0KrGhhwKjpX8YiN45xRa7A/gAiAU/+0vqhD4jCvx54xkmYnAlHGKTOx3KKv9VSSIbfazZQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":879261},"main":"./dist/layout.js","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./rich-inline":{"types":"./dist/rich-inline.d.ts","import":"./dist/rich-inline.js","default":"./dist/rich-inline.js"},"./package.json":"./package.json"},"gitHead":"bd956e3267aea8b516280889dba4e1ab0ffbac72","scripts":{"check":"tsc && oxlint --type-aware src","start":"HOST=${HOST:-0.0.0.0}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","prepack":"rm -rf dist && tsc -p tsconfig.build.json","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","corpus-status":"bun run scripts/corpus-status.ts","start:windows":"bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=0.0.0.0:3000","accuracy-check":"bun run scripts/accuracy-check.ts","keep-all-check":"bun run scripts/keep-all-check.ts","pre-wrap-check":"bun run scripts/pre-wrap-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","status-dashboard":"bun run scripts/status-dashboard.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","generate:bidi-data":"bun run scripts/generate-bidi-data.ts","package-smoke-test":"bun run scripts/package-smoke-test.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","letter-spacing-check":"bun run scripts/letter-spacing-check.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-status:refresh":"bun run scripts/corpus-sweep.ts --all --start=300 --end=900 --step=10 --output=corpora/chrome-step10.json && bun run scripts/corpus-sweep.ts --browser=safari --all --start=300 --end=900 --step=10 --output=corpora/safari-step10.json && bun run scripts/corpus-status.ts","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","letter-spacing-snapshot":"bun run scripts/letter-spacing-check.ts --output=accuracy/letter-spacing.json","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"repository":{"url":"git+https://github.com/chenglou/pretext.git","type":"git"},"_npmVersion":"10.9.2","description":"Fast, accurate & comprehensive text measurement & layout","directories":{},"sideEffects":false,"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"marked":"^17.0.5","oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","typescript":"6.0.2","oxlint-tsgolint":"^0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.7_1778398590542_0.16890908240282654","host":"s3://npm-registry-packages-npm-production"}},"0.0.8":{"name":"@chenglou/pretext","version":"0.0.8","keywords":["text-layout","text-measurement","line-breaking","typography","canvas","svg","unicode"],"license":"MIT","_id":"@chenglou/pretext@0.0.8","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"homepage":"https://github.com/chenglou/pretext#readme","bugs":{"url":"https://github.com/chenglou/pretext/issues"},"dist":{"shasum":"e8cac3533b7229816c3c8a11401d8e8b14bda9a5","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.8.tgz","fileCount":69,"integrity":"sha512-yqm2GMxnPI7VHcHwe84P8ZF0JK/2d2DMKPqMN+s95jQhwDMYYXKVFVJUMEaVWckQStdsjdLav/0Vu+d9YbtGxA==","signatures":[{"sig":"MEUCIQDozMgCRy0roLtfteD3tCGUdeLrbnmIpfidYIS6ZTqFLwIgUMGO5Z52gnd+3SXSyb3IYrzLt0XciSuH3ogsBqm4GcU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":902216},"main":"./dist/layout.js","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./rich-inline":{"types":"./dist/rich-inline.d.ts","import":"./dist/rich-inline.js","default":"./dist/rich-inline.js"},"./package.json":"./package.json"},"gitHead":"a79a6a595d7ea8a07874637b5234ee255b6a4a14","scripts":{"check":"tsc && oxlint --type-aware src && knip","start":"HOST=${HOST:-0.0.0.0}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","prepack":"rm -rf dist && tsc -p tsconfig.build.json","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","corpus-check":"bun run scripts/corpus-check.ts","corpus-sweep":"bun run scripts/corpus-sweep.ts","symbol-check":"bun run scripts/symbol-check.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","corpus-status":"bun run scripts/corpus-status.ts","start:windows":"bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=0.0.0.0:3000","accuracy-check":"bun run scripts/accuracy-check.ts","keep-all-check":"bun run scripts/keep-all-check.ts","pre-wrap-check":"bun run scripts/pre-wrap-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","status-dashboard":"bun run scripts/status-dashboard.ts","accuracy-snapshot":"bun run scripts/accuracy-check.ts --full --output=accuracy/chrome.json","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","generate:bidi-data":"bun run scripts/generate-bidi-data.ts","package-smoke-test":"bun run scripts/package-smoke-test.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","corpus-sweep:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-sweep.ts","letter-spacing-check":"bun run scripts/letter-spacing-check.ts","accuracy-check:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts","corpus-status:refresh":"bun run scripts/corpus-sweep.ts --all --start=300 --end=900 --step=10 --output=corpora/chrome-step10.json && bun run scripts/corpus-sweep.ts --browser=safari --all --start=300 --end=900 --step=10 --output=corpora/safari-step10.json && bun run scripts/corpus-status.ts","accuracy-check:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","letter-spacing-snapshot":"bun run scripts/letter-spacing-check.ts --output=accuracy/letter-spacing.json","accuracy-snapshot:safari":"ACCURACY_CHECK_BROWSER=safari bun run scripts/accuracy-check.ts --full --output=accuracy/safari.json","accuracy-snapshot:firefox":"ACCURACY_CHECK_BROWSER=firefox bun run scripts/accuracy-check.ts --full --output=accuracy/firefox.json","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"repository":{"url":"git+https://github.com/chenglou/pretext.git","type":"git"},"_npmVersion":"10.9.2","description":"Fast, accurate & comprehensive text measurement & layout","directories":{},"sideEffects":false,"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"knip":"^6.14.2","marked":"^17.0.5","oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","typescript":"6.0.2","oxlint-tsgolint":"^0.15.0"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.8_1781248625431_0.5786217666226929","host":"s3://npm-registry-packages-npm-production"}},"0.0.9":{"name":"@chenglou/pretext","version":"0.0.9","keywords":["text-layout","text-measurement","line-breaking","typography","canvas","svg","unicode"],"license":"MIT","_id":"@chenglou/pretext@0.0.9","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"homepage":"https://github.com/chenglou/pretext#readme","bugs":{"url":"https://github.com/chenglou/pretext/issues"},"dist":{"shasum":"e158b60285cce891097b76126b7851a84011ac9b","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.9.tgz","fileCount":69,"integrity":"sha512-vInlpC//3vjRfWTs78k2BBIq/0bUdpjIp8TIn9fXe0NvnChhTQZfjioVzERQQbkQvCVpPpw7PDOrTWMHpq27VA==","signatures":[{"sig":"MEYCIQCtpekFWOmK6+U042qFXv8ebYOB4OqomEHUfIS7oPjM5AIhANujJCAHMHzFl7+R/NBekrLd2Rb6v+3e6YfPj8SlwpiL","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIDB7KpFx4ShyM8lrEOOCiK0mKmrjDQO4+kUCmpcPAtmeAiAli9zmSQ3X8u/UT31LQfpFXq9hWbzkx02n8+QJemNmQw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":887142},"main":"./dist/layout.js","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./rich-inline":{"types":"./dist/rich-inline.d.ts","import":"./dist/rich-inline.js","default":"./dist/rich-inline.js"},"./package.json":"./package.json"},"scripts":{"check":"tsc && oxlint --type-aware src tests/wrapping scripts/wrapping-check.ts && knip","start":"HOST=${HOST:-0.0.0.0}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","prepack":"rm -rf dist && tsc -p tsconfig.build.json","font-probe":"bun run scripts/font-probe.ts","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","probe-check":"bun run scripts/probe-check.ts","corpus-check":"bun run scripts/corpus-check.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","corpus-status":"bun run scripts/corpus-status.ts","start:windows":"bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=0.0.0.0:3000","test:wrapping":"bun run scripts/wrapping-check.ts","benchmark-check":"bun run scripts/benchmark-check.ts","corpus-taxonomy":"bun run scripts/corpus-taxonomy.ts","status-dashboard":"bun run scripts/status-dashboard.ts","corpus-font-matrix":"bun run scripts/corpus-font-matrix.ts","generate:bidi-data":"bun run scripts/generate-bidi-data.ts","package-smoke-test":"bun run scripts/package-smoke-test.ts","probe-check:safari":"PROBE_CHECK_BROWSER=safari bun run scripts/probe-check.ts","corpus-check:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-check.ts","justification-check":"bun run scripts/justification-check.ts","benchmark-check:safari":"BENCHMARK_CHECK_BROWSER=safari bun run scripts/benchmark-check.ts","test:wrapping:snapshot":"bun run scripts/wrapping-check.ts --browser=all --snapshot && bun run scripts/corpus-status.ts && bun run scripts/status-dashboard.ts","corpus-font-matrix:safari":"CORPUS_CHECK_BROWSER=safari bun run scripts/corpus-font-matrix.ts"},"_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"repository":{"url":"git+https://github.com/chenglou/pretext.git","type":"git"},"_npmVersion":"10.9.2","description":"Fast, accurate & comprehensive text measurement & layout","directories":{},"sideEffects":false,"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"knip":"^6.14.2","marked":"^17.0.5","oxlint":"^1.51.0","tsgolint":"^0.0.1","@types/bun":"latest","typescript":"6.0.2","oxlint-tsgolint":"^0.15.0","playwright-core":"^1.62.1"},"_npmOperationalInternal":{"tmp":"tmp/pretext_0.0.9_1788774420759_0.27537795513667374","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"_id":"@chenglou/pretext@1.0.0","bugs":{"url":"https://github.com/chenglou/pretext/issues"},"dist":{"shasum":"631111669669a4dead2431594268fad69a8b0a80","tarball":"https://registry.npmjs.org/@chenglou/pretext/-/pretext-1.0.0.tgz","fileCount":56,"integrity":"sha512-BxKiDMxWJhyzL8asvcgg3T9YnUU7OUFsKO8oRaR2SYAPX4Sh2wM9gqTBjrBaZ4fnl84cEQ8czC0Q1M83z3chsA==","signatures":[{"sig":"MEUCIQCPnfx9uElEmxja55y4NZ5LABp0UnYmPAdesNyv0gILFgIgFGWJjV+ff1u9M9t1TUJx1KgFRo9m+AVz3/CbO6RZgP8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIH8bH7TIHOFSlmPtgdr+ckGu9j7xsZY6Z8eOn9ipjcsZAiBaMctGwfCLNTbT6KYmGzkA9QxxXaxIBUc3S1gzRxFp8g=="}],"unpackedSize":1032358},"main":"./dist/layout.js","name":"@chenglou/pretext","type":"module","types":"./dist/layout.d.ts","exports":{".":{"types":"./dist/layout.d.ts","import":"./dist/layout.js","default":"./dist/layout.js"},"./rich-inline":{"types":"./dist/rich-inline.d.ts","import":"./dist/rich-inline.js","default":"./dist/rich-inline.js"},"./package.json":"./package.json"},"gitHead":"4a98f37b1e5396a73197f17c13f3128c15d8c17c","license":"MIT","scripts":{"check":"tsc && tsc -p tsconfig.build.json --noEmit && oxlint --type-aware --deny-warnings src harness pages scripts && knip && bun run scripts/generate-engine-break-data.ts --check && bun run scripts/generate-webkit-generic-families.ts --check","start":"HOST=${HOST:-0.0.0.0}; PORT=3000; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Freeing port $PORT: terminating $pids\"; kill $pids 2>/dev/null || true; sleep 1; pids=$(lsof -tiTCP:$PORT -sTCP:LISTEN 2>/dev/null); if [ -n \"$pids\" ]; then echo \"Port $PORT still busy: killing $pids\"; kill -9 $pids 2>/dev/null || true; fi; fi; bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=$HOST:$PORT","harness":"bun run harness/cli.ts","prepack":"bun run build:package","site:build":"rm -rf site && bun run scripts/build-demo-site.ts","build:package":"rm -rf dist && tsc -p tsconfig.build.json","start:windows":"bun pages/*.html pages/demos/*.html pages/demos/*/index.html --host=0.0.0.0:3000","package-smoke-test":"bun run scripts/package-smoke-test.ts","generate:engine-break-data":"bun run scripts/generate-engine-break-data.ts","generate:webkit-generic-families":"bun run scripts/generate-webkit-generic-families.ts"},"version":"1.0.0","_npmUser":{"name":"chenglou","email":"chenglou92@gmail.com"},"homepage":"https://github.com/chenglou/pretext#readme","keywords":["text-layout","text-measurement","line-breaking","typography","canvas","svg","unicode"],"repository":{"url":"git+https://github.com/chenglou/pretext.git","type":"git"},"_npmVersion":"10.9.2","description":"Fast, accurate & comprehensive text measurement & layout","directories":{},"maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"sideEffects":false,"_nodeVersion":"23.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"knip":"^6.14.2","marked":"^17.0.5","oxlint":"^1.51.0","@types/bun":"latest","typescript":"7.0.2","oxlint-tsgolint":"^0.15.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pretext_1.0.0_1791981003288_0.7138782849746939"}}},"time":{"created":"2026-03-27T03:04:39.239Z","modified":"2026-10-14T12:30:03.565Z","0.0.0":"2026-03-27T03:04:39.505Z","0.0.1":"2026-03-27T11:47:41.681Z","0.0.2":"2026-03-28T10:04:48.552Z","0.0.3":"2026-03-29T09:35:12.651Z","0.0.4":"2026-04-01T18:24:17.649Z","0.0.5":"2026-04-08T16:49:10.450Z","0.0.6":"2026-04-22T05:51:45.515Z","0.0.7":"2026-05-10T07:36:30.751Z","0.0.8":"2026-06-12T07:17:05.585Z","0.0.9":"2026-09-07T09:47:00.853Z","1.0.0":"2026-10-14T12:30:03.377Z"},"bugs":{"url":"https://github.com/chenglou/pretext/issues"},"license":"MIT","homepage":"https://github.com/chenglou/pretext#readme","keywords":["text-layout","text-measurement","line-breaking","typography","canvas","svg","unicode"],"repository":{"url":"git+https://github.com/chenglou/pretext.git","type":"git"},"description":"Fast, accurate & comprehensive text measurement & layout","maintainers":[{"name":"chenglou","email":"chenglou92@gmail.com"}],"readme":"# Pretext\n\nPure JavaScript/TypeScript library for multiline text measurement & layout. Fast, accurate & supports all the languages you didn't even know about. Allows rendering to DOM, Canvas and SVG.\n\nPretext side-steps the need for DOM measurements (e.g. `getBoundingClientRect`, `offsetHeight`), which trigger layout reflow, one of the most expensive operations in the browser. It implements its own text measurement logic, using the browsers' own font engine as ground truth (very AI-friendly iteration method).\n\n## Installation\n\n```sh\nnpm install @chenglou/pretext\n```\n\n## Demos\n\nThe demos are exemplary API usage patterns we encourage you to read. They don't ship in the npm package, so clone the repo, run `bun install`, then `bun start`, and open <http://localhost:3000/demos> in your browser. On Windows, use `bun run start:windows`.\nAlternatively, see them live at [chenglou.me/pretext](https://chenglou.me/pretext/). Some more at [somnai-dreams.github.io/pretext-demos](https://somnai-dreams.github.io/pretext-demos/)\nBuilding a chat or another long list? [pages/demos/markdown-chat.md](https://github.com/chenglou/pretext/blob/main/pages/demos/markdown-chat.md) walks through the Markdown chat demo's patterns and when you can skip each.\n\n## API\n\nPretext serves 2 use cases:\n\n### 1. Measure a paragraph's height _without ever touching DOM_\n\n```ts\nimport { prepare, layout } from '@chenglou/pretext'\n\nconst prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀‎', '16px Inter')\nconst { height, lineCount } = layout(prepared, 320, 20) // pure arithmetic. No DOM layout & reflow!\n```\n\n`prepare()` does the one-time work: normalize whitespace, segment the text at its break opportunities, measure the segments with canvas, and return an opaque handle. `layout()` is the cheap hot path after that: pure arithmetic over cached widths. Do not rerun `prepare()` for the same text, font and options; that'd defeat its precomputation. For example, on resize, only rerun `layout()`.\n\nTo paint the text, give one element that `font`, the `line-height` and content `width` you passed to `layout()`, and `overflow-wrap: break-word`: the browser wraps it to the same lines. Text in several fonts or with padded pieces goes through the rich inline text API below, and is painted a line at a time.\n\nIf you want textarea-like text where ordinary spaces, `\\t` tabs, and `\\n` hard breaks stay visible, pass `{ whiteSpace: 'pre-wrap' }` to `prepare()`:\n\n```ts\nconst prepared = prepare(textareaValue, '16px Inter', { whiteSpace: 'pre-wrap' })\nconst { height } = layout(prepared, textareaWidth, 20)\n// A <textarea> shows one more line than this when its value is empty or ends with \\n\n\n// Long text edited live: prepare each paragraph apart, keeping its \\n, and re-prepare only the one an edit touches\nconst paragraphs = textareaValue.split(/(?<=\\n)/).map(p => prepare(p, '16px Inter', { whiteSpace: 'pre-wrap' }))\nconst lineCount = paragraphs.reduce((n, p) => n + layout(p, textareaWidth, 20).lineCount, 0)\n```\n\nOther `prepare()` options are `{ wordBreak: 'keep-all' }` for CSS-like `word-break: keep-all`, and `{ letterSpacing: n }` to match CSS `letter-spacing` (`n` is treated as a px value).\n\nThe returned height is the crucial last piece for unlocking web UIs:\n- proper virtualization/occlusion without guesstimates & caching\n- fancy userland layouts: masonry, JS-driven flexbox-like implementations, nudging a few layout values without CSS hacks (imagine that), etc.\n- _development time_ verification (especially now with AI) that labels on e.g. buttons don't overflow to the next line, browser-free\n- prevent layout shift when new text loads and you wanna re-anchor the scroll position\n\nThe same `prepare()` handle also works with the APIs below that don't return text (`measureLineStats()`, `measureNaturalWidth()`, `walkLineRanges()`, `layoutNextLineRange()`), e.g. to shrink-wrap a chat bubble to its widest line.\n\n### 2. Lay out the paragraph lines manually yourself\n\nSwitch out `prepare` with `prepareWithSegments` (the APIs that return a line's text need it: `layoutWithLines()`, `layoutNextLine()`, `materializeLineRange()`), then:\n\n- `layoutWithLines()` gives you all the lines at a fixed width:\n\n```ts\nimport { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'\n\nconst prepared = prepareWithSegments('AGI 春天到了. بدأت الرحلة 🚀', '18px \"Helvetica Neue\"')\nconst { lines } = layoutWithLines(prepared, 320, 26) // 320px max width, 26px line height\nfor (let i = 0; i < lines.length; i++) ctx.fillText(lines[i].text, 0, i * 26)\n```\n\n- `measureLineStats()` and `walkLineRanges()` give you line counts, widths and cursors without building the text strings:\n\n```ts\nimport { measureLineStats, walkLineRanges } from '@chenglou/pretext'\n\nconst { lineCount, maxLineWidth } = measureLineStats(prepared, 320)\nlet maxW = 0\nwalkLineRanges(prepared, 320, line => { if (line.width > maxW) maxW = line.width })\n// maxW is now the widest line — the tightest container width that still fits the text! This multiline \"shrink wrap\" has been missing from web\n```\n\nSize an element to `Math.ceil(maxW)`, capped at the `maxWidth` you passed, as the `/demos/bubbles` demo does. At the exact fractional width the browser can wrap the widest line. Without the cap the element can come out a pixel wider than `maxWidth`.\n\n- `layoutNextLineRange()` lets you route text one row at a time when width changes as you go:\n\n```ts\nimport { layoutNextLineRange, materializeLineRange, prepareWithSegments, type LayoutCursor } from '@chenglou/pretext'\n\nconst prepared = prepareWithSegments(article, BODY_FONT)\nlet cursor: LayoutCursor = { segmentIndex: 0, graphemeIndex: 0 }\nlet y = 0\n\n// Flow text around a floated image: lines beside the image are narrower\nwhile (true) {\n  const width = y < image.bottom ? columnWidth - image.width : columnWidth\n  const range = layoutNextLineRange(prepared, width, cursor)\n  if (range === null) break\n\n  const line = materializeLineRange(prepared, range)\n  ctx.fillText(line.text, 0, y)\n  cursor = range.end\n  y += 26\n}\n```\n\nSee the `/demos/dynamic-layout` demo for a richer example, and the `/demos/ellipsis` demo for a paragraph clamped to a number of lines with an ellipsis, as CSS `-webkit-line-clamp` does, and a path cut in its middle.\n\nFor hyphenation, insert soft hyphens before calling `prepare()` or `prepareWithSegments()`. They stay invisible unless the line breaks there, in which case the line ends with a hyphen, which its `width` counts. For mixed-language or user-generated app text, prefer conservative, locale-aware insertion over aggressive pattern hyphenation.\n\nTo lay out text with mixed fonts, code spans, mentions, chips or images, use `@chenglou/pretext/rich-inline`:\n\n```ts\nimport { materializeRichInlineLineRange, prepareRichInline, walkRichInlineLineRanges } from '@chenglou/pretext/rich-inline'\n\nconst prepared = prepareRichInline([\n  { text: 'Ship ', font: '500 17px Inter' },\n  { text: '@maya', font: '700 12px Inter', break: 'never', extraWidth: 22 },\n  { text: \"'s rich-note \", font: '500 17px Inter' },\n  { width: 20 }, // a custom emoji\n])\n\nwalkRichInlineLineRanges(prepared, 320, range => {\n  const line = materializeRichInlineLineRange(prepared, range)\n  // each fragment keeps its source item index, text slice, where that slice sits in the item's text, gapBefore and gapItemIndex\n})\n```\n\nPass a flat list of items. For an image, a custom emoji, a formula or a badge inside a line, pass a box, `{ width }`: its element's margin box, padding and border included, in whole or quarter pixels. A line can break on either side of a box, as at an `<img>`, and its fragment has no text; a box is an item with no `text`. For a size not known yet, prepare with a placeholder and again when it arrives; for an image capped at `max-width: 100%`, pass `min(its width, the paragraph's width)` and prepare again when that changes. Heights are yours: give each box `vertical-align: top`, and each line is as tall as the paragraph's line height or its tallest box, whichever is taller.\n\nA line count is a height only while no item grows its line. A line is as tall as the paragraph's line height while its text keeps the paragraph font's size, ascent and descent; text in another size, or in a face whose ascent and descent differ (Helvetica Neue's bold on macOS), makes its line taller. Give each item's element `line-height: 1`, as the rich-note demo does, and the count times the paragraph's line height is the block's height. An inline-block item's vertical padding counts in its line too: a chip whose box comes within a few pixels of the line's height makes the line taller.\n\nFor `white-space: pre-wrap` or `word-break: keep-all` on the paragraph, pass `{ whiteSpace: 'pre-wrap' }` or `{ wordBreak: 'keep-all' }` as the second argument; it applies to every item. In `pre-wrap` every item but a `break: 'never'` one keeps its spaces, tabs and newlines. Paint each line with `white-space: pre`: a line painted alone in `pre-wrap` can wrap again. This is not a general CSS inline formatting engine.\n\n### API Glossary\n\nUse-case 1 APIs:\n```ts\nprepare(text: string, font: string, options?: { whiteSpace?: 'normal' | 'pre-wrap', wordBreak?: 'normal' | 'keep-all', letterSpacing?: number }): PreparedText // one-time text analysis + measurement pass, returns an opaque value to pass to `layout()` and to the APIs below that don't return text. Make sure `font` and `letterSpacing` are synced with your CSS for the text you're measuring. `font` is the same format as what you'd use for `myCanvasContext.font = ...`, e.g. `16px Inter`; `letterSpacing` is a CSS pixel value, and must be finite.\nlayout(prepared: PreparedText, maxWidth: number, lineHeight: number): { height: number, lineCount: number } // calculates text height given a max width and lineHeight. Make sure `lineHeight` is synced with your css `line-height` declaration for the text you're measuring.\n```\n\nUse-case 2 APIs:\n```ts\nprepareWithSegments(text: string, font: string, options?: { whiteSpace?: 'normal' | 'pre-wrap', wordBreak?: 'normal' | 'keep-all', letterSpacing?: number }): PreparedTextWithSegments // same as `prepare()`, but returns a richer structure for manual line layout needs\nlayoutWithLines(prepared: PreparedTextWithSegments, maxWidth: number, lineHeight: number): { height: number, lineCount: number, lines: LayoutLine[] } // high-level api for manual layout needs. Accepts a fixed max width for all lines. Similar to `layout()`'s return, but additionally returns the lines info\nwalkLineRanges(prepared: PreparedText, maxWidth: number, onLine: (line: LayoutLineRange) => void): number // low-level api for manual layout needs. Accepts a fixed max width for all lines. Calls `onLine` once per line with its actual calculated line width and start/end cursors, without building line text strings. Very useful for certain cases where you wanna speculatively test a few width and height boundaries (e.g. binary search a nice width value by repeatedly calling walkLineRanges and checking the line count, and therefore height, is \"nice\" too). You can have text messages shrinkwrap and balanced text layout this way. After walkLineRanges calls, you'd call layoutWithLines once, with your satisfying max width, to get the actual lines info.\nmeasureLineStats(prepared: PreparedText, maxWidth: number): { lineCount: number, maxLineWidth: number } // returns only how many lines this width produces, and how wide the widest one is. Avoids line/string allocations.\nmeasureNaturalWidth(prepared: PreparedText): number // Returns the width of the widest line when only explicit line breaks apply.\nlayoutNextLine(prepared: PreparedTextWithSegments, maxWidth: number, start?: LayoutCursor): LayoutLine | null // iterator-like api for laying out each line with a different width! Returns the LayoutLine starting from `start`, or `null` when the paragraph's exhausted. Pass the previous line's `end` cursor as the next `start`.\nlayoutNextLineRange(prepared: PreparedText, maxWidth: number, start?: LayoutCursor): LayoutLineRange | null // same as layoutNextLine(), but without allocating line text strings. Useful for variable-width manual layout, occlusion, and virtualization measurements.\nmaterializeLineRange(prepared: PreparedTextWithSegments, line: LayoutLineRange): LayoutLine // turns a LayoutLineRange from layoutNextLineRange() or walkLineRanges() into a full line with text\ntype PreparedTextWithSegments = PreparedText & {\n  readonly segments: readonly string[] // The text split into segments, e.g. ['hello', ' ', 'world']\n  readonly kinds: readonly ('text' | 'space' | 'preserved-space' | 'tab' | 'zero-width-break' | 'soft-hyphen' | 'zero-width-glue' | 'hard-break' | 'control')[] // Break behavior per segment, e.g. ['text', 'space', 'text']\n  readonly widths: ArrayLike<number> // Width of each segment in px, e.g. [42.5, 4.4, 37.2]\n}\n// The kinds. 'space': a collapsible space; 'preserved-space', 'tab' and 'hard-break': a space, tab or newline kept by `pre-wrap`; 'zero-width-break': a zero-width space the line can break after; 'soft-hyphen': a soft hyphen (U+00AD); 'zero-width-glue': a zero-width space or soft hyphen the browser doesn't break after; 'control': in Safari, a next-line character (U+0085)\ntype LineStats = {\n  lineCount: number // Number of wrapped lines, e.g. 3\n  maxLineWidth: number // Widest wrapped line, e.g. 192.5\n}\ntype LayoutLine = {\n  text: string // Full text content of this line, e.g. 'hello world'\n  width: number // Measured width of this line, e.g. 87.5, leaving out spaces and tabs that hang past its end\n  start: LayoutCursor // Inclusive start cursor in prepared segments/graphemes\n  end: LayoutCursor // Exclusive end cursor in prepared segments/graphemes\n}\ntype LayoutLineRange = {\n  width: number // Measured width of this line, e.g. 87.5, leaving out spaces and tabs that hang past its end\n  start: LayoutCursor // Inclusive start cursor in prepared segments/graphemes\n  end: LayoutCursor // Exclusive end cursor in prepared segments/graphemes\n}\ntype LayoutCursor = {\n  segmentIndex: number // Segment index in `segments`\n  graphemeIndex: number // Grapheme index within that segment; `0` at segment boundaries\n}\n```\n\nHelper for rich-text inline flow:\n```ts\nprepareRichInline(items: Array<RichInlineItem | RichInlineBox>, options?: { whiteSpace?: 'normal' | 'pre-wrap', wordBreak?: 'normal' | 'keep-all' }): PreparedRichInline // prepares the items for layout and, in `white-space: normal`, collapses spaces between them. `whiteSpace` and `wordBreak` are the paragraph's, as in `prepare()`\nlayoutNextRichInlineLineRange(prepared: PreparedRichInline, maxWidth: number, start?: RichInlineCursor): RichInlineLineRange | null // stream one line of rich-text inline flow at a time without building fragment text strings\nwalkRichInlineLineRanges(prepared: PreparedRichInline, maxWidth: number, onLine: (line: RichInlineLineRange) => void): number // non-materializing line walker for rich-text inline flow shrinkwrap/stats work\nmaterializeRichInlineLineRange(prepared: PreparedRichInline, line: RichInlineLineRange): RichInlineLine // turns one previously computed rich-inline line range back into full fragment text\nmeasureRichInlineStats(prepared: PreparedRichInline, maxWidth: number): { lineCount: number, maxLineWidth: number } // returns only how many lines this width produces, and how wide the widest one is. Avoids fragment-text allocations.\ntype RichInlineItem = {\n  text: string // raw text, including leading/trailing collapsible spaces\n  font: string // canvas font shorthand for this item\n  letterSpacing?: number // extra horizontal spacing between graphemes, in CSS px\n  break?: 'normal' | 'never' // `never` makes the item atomic: one box that can't break inside, as CSS `display: inline-block` does. A line can break on either side of it, whatever touches it\n  extraWidth?: number // extra width around the text, e.g. padding and borders\n}\ntype RichInlineBox = {\n  width: number // the room an object inside the line takes, e.g. an image, in CSS px: its element's margin box. Finite and at least 0\n}\ntype RichInlineCursor = {\n  itemIndex: number // Which source item this cursor is currently in\n  segmentIndex: number // Segment index within that item's part of the paragraph\n  graphemeIndex: number // Grapheme index within that segment; `0` at segment boundaries\n}\ntype RichInlineFragment = {\n  itemIndex: number // index back into the items prepareRichInline() took\n  text: string // Text slice for this fragment\n  gapBefore: number // collapsed space before this fragment, in pixels; 0 when there's none\n  gapItemIndex: number // index of the item whose collapsed space gapBefore measures, or -1 when no space precedes this fragment on this line\n  occupiedWidth: number // text width plus extraWidth, or a box's width\n  sourceStart: number // where `text` starts in the item's `text`, as a UTF-16 offset\n  sourceEnd: number // where it ends there\n}\ntype RichInlineLine = {\n  fragments: RichInlineFragment[] // Materialized fragments on this line\n  width: number // Measured width of this line, including gapBefore/extraWidth\n  end: RichInlineCursor // Exclusive end cursor for continuing the next line\n}\ntype RichInlineFragmentRange = {\n  itemIndex: number // index back into the items prepareRichInline() took\n  gapBefore: number // collapsed space before this fragment, in pixels; 0 when there's none\n  gapItemIndex: number // index of the item whose collapsed space gapBefore measures, or -1 when no space precedes this fragment on this line\n  occupiedWidth: number // text width plus extraWidth, or a box's width\n  start: LayoutCursor // Start cursor within the item's part of the paragraph\n  end: LayoutCursor // End cursor within the item's part of the paragraph\n}\ntype RichInlineLineRange = {\n  fragments: RichInlineFragmentRange[] // Non-materialized fragment ownership/ranges on this line\n  width: number // Measured width of this line, including gapBefore/extraWidth\n  end: RichInlineCursor // Exclusive end cursor for continuing the next line\n}\ntype RichInlineStats = {\n  lineCount: number // Number of wrapped lines, e.g. 3\n  maxLineWidth: number // Widest wrapped line, e.g. 192.5\n}\n```\n\nOther helpers:\n```ts\nclearCache(): void // clears Pretext's shared internal caches used by prepare(), prepareWithSegments() and prepareRichInline(). Useful if your app cycles through many different fonts or text variants and you want to release the accumulated cache. After a web font loads, call it and prepare that font's text again: widths measured earlier are the fallback font's\nsetLocale(locale?: string): void // optional (by default we use the page language, from `<html lang>`). Sets locale for future prepare(), prepareWithSegments() and prepareRichInline(). Internally, it also calls clearCache(). Setting a new locale doesn't affect existing prepared states (no mutations to them). A worker has no `<html lang>`, so call it there with the page's `document.documentElement.lang`. In a worker, small emoji measure too wide in Chrome and Firefox on macOS\n```\n\nNotes:\n- `LayoutCursor` is a segment/grapheme cursor, not a raw string offset.\n- Rich inline lays its items out as one paragraph, so a rich-inline cursor, a line's `end` or a fragment range's `start` and `end`, counts segments of the item's part of that paragraph: pass it back to `layoutNextRichInlineLineRange()` or `materializeRichInlineLineRange()`, and don't read it as a cursor into `prepareWithSegments(item.text)`. A materialized fragment's `sourceStart` and `sourceEnd` say where its text sits in its item's `text`.\n- Browsers let the spaces at a line's end run past it without counting toward its width, which CSS calls hanging. A line's `width` leaves out what hangs: all of it where the line wraps, and in `pre-wrap`, before a newline or at the end of the text, only the part that doesn't fit in `maxWidth`. `measureNaturalWidth()` still counts spaces before a newline, like CSS max-content.\n- `layout()` with an empty string returns `{ lineCount: 0, height: 0 }`, as an empty block is 0 tall, and a newline at the very end of `pre-wrap` text adds no line.\n- Pretext doesn't give bidi levels or a visual order. If you're drawing mixed bidi text, like English and Arabic, render each paragraph as one DOM element with its direction set, and the browser orders every line. In `pre-wrap` text every newline starts a paragraph: set `unicode-bidi: plaintext` on the element, or prepare and paint each paragraph apart, so that each takes its own direction. If you draw lines separately, such as with Canvas `fillText()`, each line is ordered as its own paragraph, so numbers or punctuation next to a line break can come out in a different order.\n- Paint a `never` item as `display: inline-block; white-space: nowrap`. A span with only `white-space: nowrap` stays in the text's flow, so the browser keeps a `:` or `'s` right after it on its line, where Pretext can break before them.\n- A rich-inline fragment's `gapBefore` is a space in the font and letter spacing of item `gapItemIndex`: the fragment's own item, the previous fragment's item, or an item holding only whitespace, which gets no fragment. Draw the space inside that item's element so it paints at that width. An atomic item's own leading and trailing white space makes no gap, as browsers trim it inside the item's inline-block.\n- In `pre-wrap`, rich-inline fragments have no gaps: a fragment's `text` keeps its spaces, and its `occupiedWidth`, like the line's `width`, leaves out what hangs at the line's end.\n- A rich-inline item that wraps is charged its whole `extraWidth` on every line it reaches, as CSS `box-decoration-break: clone` pads a span, where CSS's default pads only the span's two ends. Paint each fragment as its own element with the padding on both sides, as the demos do: a padded span the browser wraps itself can break elsewhere, with `clone` too. Spans without padding can too: items are measured apart, where Chrome and Firefox kern across neighbouring elements in one font.\n- Segment widths are browser-canvas widths for line breaking, not enough to position individual characters in Arabic or mixed bidi text. They don't always add up to a line's width either: a tab's is 0, the letter spacing after a segment's last letter isn't in it, and a line broken at a soft hyphen adds its hyphen. For a width, read the line's `width`, or call `measureNaturalWidth()` for the whole text.\n\n## Caveats\n\nPretext doesn't try to be a full font rendering engine (yet?). It currently targets the common text setup:\n- `white-space: normal` and `pre-wrap`\n- `word-break: normal` and `keep-all`\n- `overflow-wrap: break-word`, which isn't CSS's default: set it on the text you paint. A word, a run of symbols or a `keep-all` group that doesn't fit a line breaks between graphemes.\n- `line-break: auto`\n- `letter-spacing` as a numeric pixel value passed to `prepare()` / `prepareWithSegments()`\n- Tabs follow the default browser-style `tab-size: 8`\n- In `pre-wrap`, Pretext treats a lone `\\r` as a line break, but browsers don't. Normalize `\\r` to `\\n` in the text you render, not just the text you measure.\n- `system-ui` and `-apple-system` are unsafe for `layout()` accuracy on macOS. Use a named font, by its English family name (`\"Hiragino Kaku Gothic ProN\"`, not `\"ヒラギノ角ゴ ProN\"` or a face name like `\"Avenir Next Demi Bold\"`). See the [platform bug ledger](https://github.com/chenglou/pretext/blob/main/PLATFORM_BUGS.md) for the Chrome and Firefox issues.\n- Page language changes fonts and line breaks, and without `lang` Chrome and Firefox use the browser's or system's language. A generic font like `sans-serif`, or a character missing from every named font, may use a different font from the one Pretext measures, and curly quotes can wrap differently per language. Set `lang` on `<html>`, list a named font for each script your text uses (`16px \"Helvetica Neue\", \"PingFang SC\", \"Geeza Pro\", sans-serif`), and check in your browser that a painted element's height matches `layout()`'s. Pretext doesn't read an element's own `lang`. If you change `<html lang>`, prepare your text again: existing handles keep the old widths and line-break rules.\n- Prepare text once its web font has loaded: `await document.fonts.load('16px Inter', text)`, passing the text, since a family split by `unicode-range` loads only the parts that text needs, and `document.fonts.ready` doesn't wait for a font nothing has used yet. Add a `FontFace` to `document.fonts` before it loads, or call `clearCache()` after adding it: until then Safari keeps the fallback font's widths.\n- On macOS, a small emoji's painted width depends on `devicePixelRatio`. When the ratio changes, as when the page is zoomed, call `clearCache()` and prepare text with emoji again.\n- Runtime requires Canvas 2D text measurement and Unicode property escapes (`\\p{...}`), and `Intl.Segmenter` for text in Thai, Lao, Khmer, Myanmar and the other Southeast Asian scripts written without spaces. Browsers without these features aren't supported. Without Unicode property escapes, Pretext can't load and throws a `SyntaxError`; without `Intl.Segmenter`, preparing such text throws; without a Canvas 2D context, as in Node, Bun, jsdom or happy-dom, preparing any text throws.\n- Outside a browser, supply the measuring yourself: before the first `prepare()`, define a global `OffscreenCanvas` class whose `getContext()` returns a new object with a writable `font` and a `measureText(text)` that returns `{ width }`, finite and in pixels, for any text and font string. Lines then break by Chrome's rules over your widths: exact where you paint those lines yourself (a terminal, a PDF), an estimate of what a browser or a native text view lays out.\n- Pretext uses the canvas `font` string. A string Canvas can't parse, an empty one included, throws a `RangeError`. Separate CSS settings such as `font-variant-numeric`, `font-optical-sizing`, `font-feature-settings`, and `font-variation-settings` aren't supported. Variable-font settings only apply when expressed through that string, such as font weight.\n- Pass font sizes in px. If your CSS sizes text in `rem` or `em`, resolve them to px once, higher up in your app (for example, when the root font size changes), and pass that string to Pretext. Firefox measures canvas text at a rounded font size, so a fractional size like `13.33px` can wrap differently there; prefer whole-pixel sizes.\n- `maxWidth` is the content width you set on the element, in CSS px. Pass the number you set, not one read back from the page, which is rounded, and scaled under zoom. Round a width you compute, such as a share of a column, to whole or quarter pixels: browsers round other widths to their layout unit.\n- Pretext assumes default font kerning and word spacing. Text painted with a different `font-kerning` or `word-spacing`, including word spacing inherited from the page, can wrap differently.\n- Pretext predicts the browser's default wrapping. Text painted with `text-wrap: balance` or `pretty`, `hyphens: auto`, `hanging-punctuation` or `text-rendering: optimizeLegibility` can wrap differently.\n- Chrome and Firefox let people set a minimum font size. Text below it paints at the minimum, while Pretext measures the size you pass. If your app uses small sizes, measure the height of an element with `font-size: 1px; line-height: 1` once and pass Pretext the larger size.\n- iPhone Safari can enlarge the text of a block wider than the screen. Set `-webkit-text-size-adjust: 100%` on the page.\n\n## Develop\n\nSee [DEVELOPMENT.md](https://github.com/chenglou/pretext/blob/main/DEVELOPMENT.md) for the demo server, engine data and releases.\n\n## Credits\n\nSebastian Markbage first planted the seed with [text-layout](https://github.com/chenglou/text-layout) last decade. His design — canvas `measureText` for shaping, bidi from pdf.js, streaming line breaking — informed the architecture we kept pushing forward here.\n","readmeFilename":"README.md"}