{"id":46565200,"url":"https://github.com/libraz/mejiro","last_synced_at":"2026-03-07T07:03:41.730Z","repository":{"id":342521745,"uuid":"1174183723","full_name":"libraz/mejiro","owner":"libraz","description":"A fast and deterministic Japanese line-breaking engine for the web.","archived":false,"fork":false,"pushed_at":"2026-03-06T08:31:49.000Z","size":320,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-06T12:40:29.465Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/libraz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-06T06:46:00.000Z","updated_at":"2026-03-06T08:31:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/libraz/mejiro","commit_stats":null,"previous_names":["libraz/mejiro"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/libraz/mejiro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libraz%2Fmejiro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libraz%2Fmejiro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libraz%2Fmejiro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libraz%2Fmejiro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/libraz","download_url":"https://codeload.github.com/libraz/mejiro/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libraz%2Fmejiro/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30209428,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T05:23:27.321Z","status":"ssl_error","status_checked_at":"2026-03-07T05:00:17.256Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2026-03-07T07:03:37.820Z","updated_at":"2026-03-07T07:03:41.710Z","avatar_url":"https://github.com/libraz.png","language":"TypeScript","readme":"# mejiro\n\n[![CI](https://img.shields.io/github/actions/workflow/status/libraz/mejiro/ci.yml?branch=main\u0026label=CI)](https://github.com/libraz/mejiro/actions)\n[![npm](https://img.shields.io/npm/v/@libraz/mejiro)](https://www.npmjs.com/package/@libraz/mejiro)\n[![codecov](https://codecov.io/gh/libraz/mejiro/branch/main/graph/badge.svg)](https://codecov.io/gh/libraz/mejiro)\n[![License](https://img.shields.io/github/license/libraz/mejiro)](https://github.com/libraz/mejiro/blob/main/LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5-blue?logo=typescript)](https://www.typescriptlang.org/)\n\nJapanese vertical text layout engine for the web. Handles line breaking, kinsoku shori (禁則処理), hanging punctuation, ruby (furigana) preprocessing, and pagination — all with zero DOM dependencies in the core.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/wagahai.jpg\" alt=\"mejiro demo — Natsume Soseki \u0026quot;I Am a Cat\u0026quot; rendered in vertical text\" width=\"640\"\u003e\n\u003c/p\u003e\n\n## Installation\n\n```bash\nnpm install @libraz/mejiro   # or yarn / pnpm / bun\n```\n\n## Overview\n\nmejiro provides the building blocks for rendering Japanese vertical text (`writing-mode: vertical-rl`) in the browser. The core engine operates on typed arrays and pure math, making it fast, deterministic, and portable. Browser-specific concerns (font measurement, Canvas API) live in a separate subpath, and EPUB parsing is available as a third.\n\n```\n@libraz/mejiro          Core: line breaking, kinsoku, hanging, ruby, pagination\n@libraz/mejiro/browser  Browser: font measurement, width caching, layout integration\n@libraz/mejiro/epub     EPUB: parsing, ruby extraction\n@libraz/mejiro/render   Render: layout data → framework-agnostic page structure + CSS\n```\n\n## Architecture\n\n```\nApplication (React / Vue / vanilla DOM)\n       ↓\n  @libraz/mejiro/render   Layout data → RenderPage structure + CSS\n       ↓\n  @libraz/mejiro/epub     EPUB → text + ruby annotations\n       ↓\n  @libraz/mejiro/browser  Font measurement + ruby font derivation\n       ↓\n  @libraz/mejiro          Line breaking + kinsoku + hanging + ruby + pagination\n```\n\n- **Core** has zero external dependencies\n- **Browser** uses Canvas and FontFace APIs\n- **EPUB** depends on `jszip`\n- **Render** converts layout results into a framework-agnostic `RenderPage` data structure\n\n## Quick Start\n\n```ts\nimport { MejiroBrowser } from '@libraz/mejiro/browser';\nimport { getLineRanges, paginate } from '@libraz/mejiro';\n\nconst mejiro = new MejiroBrowser({\n  fixedFontFamily: '\"Noto Serif JP\"',\n  fixedFontSize: 16,\n});\n\nconst text = '吾輩は猫である。名前はまだ無い。';\n\n// 1. Lay out text (fontFamily/fontSize use instance defaults)\nconst result = await mejiro.layout({\n  text,\n  lineWidth: mejiro.verticalLineWidth(600), // effective line width from container height\n});\n\n// 2. Get line ranges → [[start, end), ...]\nconst lines = getLineRanges(result.breakPoints, text.length);\n\n// 3. Paginate into pages of 400px width\nconst pages = paginate(400, [\n  { lineCount: lines.length, linePitch: 16 * 1.8, gapBefore: 0 },\n]);\n```\n\n### EPUB + Chapter Layout + Render\n\n```ts\nimport { parseEpub } from '@libraz/mejiro/epub';\nimport { MejiroBrowser } from '@libraz/mejiro/browser';\nimport { paginate } from '@libraz/mejiro';\nimport { buildParagraphMeasures, buildRenderPage } from '@libraz/mejiro/render';\nimport type { RenderEntry } from '@libraz/mejiro/render';\nimport '@libraz/mejiro/render/mejiro.css';\n\nconst mejiro = new MejiroBrowser({\n  fixedFontFamily: '\"Noto Serif JP\"',\n  fixedFontSize: 16,\n});\nconst book = await parseEpub(epubArrayBuffer);\nconst chapter = book.chapters[0];\n\n// 1. Lay out all paragraphs (fontFamily/fontSize use instance defaults)\nconst lineWidth = mejiro.verticalLineWidth(600); // effective line width from container height\nconst result = await mejiro.layoutChapter({\n  paragraphs: chapter.paragraphs.map((p) =\u003e ({\n    text: p.text,\n    rubyAnnotations: p.rubyAnnotations,\n  })),\n  lineWidth,\n});\n\n// 2. Build render entries\nconst entries: RenderEntry[] = chapter.paragraphs.map((p, i) =\u003e ({\n  chars: result.paragraphs[i].chars,\n  breakPoints: result.paragraphs[i].breakResult.breakPoints,\n  rubyAnnotations: p.rubyAnnotations,\n  isHeading: !!p.headingLevel,\n}));\n\n// 3. Paginate into pages of 400px width\nconst measures = buildParagraphMeasures(entries, { fontSize: 16, lineHeight: 1.8 });\nconst pages = paginate(400, measures);\n\n// 4. Render a page (framework-agnostic data)\nconst renderPage = buildRenderPage(pages[0], entries);\n// renderPage.paragraphs → lines → segments (text or ruby)\n```\n\n## API\n\nFor the complete API reference, see [API Reference](docs/en/10-api-reference.md).\nFor detailed guides with examples, see [Documentation](docs/en/).\n\n| Subpath | Description |\n|---|---|\n| `@libraz/mejiro` | Core: `computeBreaks()`, `toCodepoints()`, kinsoku, hanging, ruby, pagination |\n| `@libraz/mejiro/browser` | Browser: `MejiroBrowser` class, font measurement, width caching |\n| `@libraz/mejiro/epub` | EPUB: `parseEpub()`, ruby extraction |\n| `@libraz/mejiro/render` | Render: `buildRenderPage()`, `buildParagraphMeasures()`, `mejiro.css` |\n| `@libraz/mejiro-react` | React: `\u003cMejiroPage\u003e` component (experimental) |\n| `@libraz/mejiro-vue` | Vue: `\u003cMejiroPage\u003e` component (experimental) |\n\n## Kinsoku Shori (禁則処理)\n\nKinsoku shori is a set of Japanese typographic rules that prohibit certain characters from appearing at the start or end of a line, defined in [JIS X 4051](https://www.jisc.go.jp/app/jis/general/GnrJISNumberNameSearchList?show\u0026jisStdNo=X4051) and [JLREQ](https://www.w3.org/TR/jlreq/).\n\nmejiro implements these rules with two modes:\n\n- **Strict** (default) — Prohibits closing brackets, punctuation, small kana, long vowel mark, and iteration marks at line start. Prohibits opening brackets at line end.\n- **Loose** — Same as strict, but allows small kana and the long vowel mark (`ー`) at line start. Useful for narrow columns.\n\n**Hanging punctuation** (`。` `、` `，` `．`) can protrude past the line end rather than being pushed to the next line.\n\nCustom kinsoku rules can be passed via `LayoutInput.kinsokuRules` when using the core `computeBreaks()` API directly. See [Line Breaking](docs/en/03-line-breaking.md) for the full character lists, JIS X 4051 / JLREQ conformance table, and custom rules examples.\n\n## Design Decisions\n\n- **TypedArray-based core** — `Uint32Array` for codepoints, `Float32Array` for advances. No string manipulation in the hot path.\n- **O(n) line breaking** — Single-pass greedy algorithm with backtracking for kinsoku. No dynamic programming overhead.\n- **Ruby as preprocessing** — Ruby annotations are resolved to effective advances and cluster IDs before the main loop, keeping the algorithm unchanged.\n- **Deterministic** — Same input always produces the same output.\n- **Separation of concerns** — Core is pure math (no DOM, no Canvas). Browser layer handles measurement. EPUB layer handles parsing. Render layer produces framework-agnostic data; final DOM output is the consumer's responsibility.\n\n## License\n\n[Apache License 2.0](LICENSE)\n\n## Authors\n\n- libraz \u003clibraz@libraz.net\u003e\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibraz%2Fmejiro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flibraz%2Fmejiro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibraz%2Fmejiro/lists"}