Japanese vertical text layout engine for the web. Handles line breaking, kinsoku shori (禁則処理), hanging punctuation, ruby (furigana) preprocessing, image exclusion (text wrapping), and pagination — all with zero DOM dependencies in the core.
npm install @libraz/mejiro # or yarn / pnpm / bunmejiro 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.
@libraz/mejiro Core: line breaking, kinsoku, hanging, ruby, image exclusion, pagination
@libraz/mejiro/browser Browser: font measurement, width caching, layout integration
@libraz/mejiro/epub EPUB: parsing, ruby extraction
@libraz/mejiro/render Render: layout data → framework-agnostic page structure + CSS
@libraz/mejiro/book Book: high-level API — layout, pagination, image exclusion in one class
Application (React / Vue / vanilla DOM)
↓
@libraz/mejiro/book High-level: MejiroBook → layout, paginate, image exclusion
↓
@libraz/mejiro/render Layout data → RenderPage structure + CSS
↓
@libraz/mejiro/epub EPUB → text + ruby annotations
↓
@libraz/mejiro/browser Font measurement + ruby font derivation
↓
@libraz/mejiro Line breaking + kinsoku + hanging + ruby + pagination
- Core has zero external dependencies
- Browser uses Canvas and FontFace APIs
- EPUB depends on
jszip - Render converts layout results into a framework-agnostic
RenderPagedata structure - Book orchestrates all layers into a simple
MejiroBook→ChapterLayout→SpreadResultworkflow
import { MejiroBrowser } from '@libraz/mejiro/browser';
import { getLineRanges, paginate } from '@libraz/mejiro';
const mejiro = new MejiroBrowser({
fixedFontFamily: '"Noto Serif JP"',
fixedFontSize: 16,
});
const text = '吾輩は猫である。名前はまだ無い。';
// 1. Lay out text (fontFamily/fontSize use instance defaults)
const result = await mejiro.layout({
text,
lineWidth: mejiro.verticalLineWidth(600), // effective line width from container height
});
// 2. Get line ranges → [[start, end), ...]
const lines = getLineRanges(result.breakPoints, text.length);
// 3. Paginate into pages of 400px width
const pages = paginate(400, [
{ lineCount: lines.length, linePitch: 16 * 1.8, gapBefore: 0 },
]);import { DEFAULT_HEADING_STYLES, MejiroBook } from '@libraz/mejiro/book';
import { parseEpub } from '@libraz/mejiro/epub';
const book = new MejiroBook({
fontFamily: '"Noto Serif JP"',
fontSize: 16,
lineSpacing: 1.8,
headingStyles: DEFAULT_HEADING_STYLES,
});
// Auto-compute page size from a container element
book.computePageSize(document.querySelector('.reading-surface')!);
const epub = await parseEpub(epubArrayBuffer);
const layout = await book.layoutChapter(epub.chapters[0]);
// Get a two-page spread
const spread = layout.getSpread(0);
// spread.right.page → RenderPage (paragraphs → lines → segments)
// spread.right.lines / spread.right.slots → for absolute positioning
// spread.totalPages → total page count
// Place images with text wrapping (returns updated spread)
const updated = layout.syncImages(0, [{ x: 80, y: 100, w: 120, h: 160 }]);For the complete API reference, see API Reference. For detailed guides with examples, see Documentation.
| Subpath | Description |
|---|---|
@libraz/mejiro |
Core: computeBreaks(), ExclusionEngine, toCodepoints(), kinsoku, hanging, ruby, pagination |
@libraz/mejiro/browser |
Browser: MejiroBrowser class, font measurement, width caching |
@libraz/mejiro/epub |
EPUB: parseEpub(), ruby extraction |
@libraz/mejiro/render |
Render: buildRenderPage(), buildParagraphMeasures(), mejiro.css |
@libraz/mejiro/book |
Book: MejiroBook, ChapterLayout, DEFAULT_HEADING_STYLES, DEFAULT_PAGE_PADDING — high-level layout, pagination, and image exclusion |
@libraz/mejiro-react |
React: <MejiroPageView>, useImageOverlay hook (experimental) |
@libraz/mejiro-vue |
Vue: <MejiroPageView>, useImageOverlay composable (experimental) |
Kinsoku 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 and JLREQ.
mejiro implements these rules with two modes:
- Strict (default) — Prohibits closing brackets, punctuation, small kana, long vowel mark, and iteration marks at line start. Prohibits opening brackets at line end.
- Loose — Same as strict, but allows small kana and the long vowel mark (
ー) at line start. Useful for narrow columns.
Hanging punctuation (。 、 , .) can protrude past the line end rather than being pushed to the next line.
Custom kinsoku rules can be passed via LayoutInput.kinsokuRules when using the core computeBreaks() API directly. See Line Breaking for the full character lists, JIS X 4051 / JLREQ conformance table, and custom rules examples.
- TypedArray-based core —
Uint32Arrayfor codepoints,Float32Arrayfor advances. No string manipulation in the hot path. - O(n) line breaking — Single-pass greedy algorithm with backtracking for kinsoku. No dynamic programming overhead.
- Ruby as preprocessing — Ruby annotations are resolved to effective advances and cluster IDs before the main loop, keeping the algorithm unchanged.
- Image exclusion —
ExclusionEngineandSpreadExclusionEnginecompute per-column text placement around arbitrarily placed images across single pages or two-page spreads, with automatic gutter handling and real-time drag-and-drop support. - Deterministic — Same input always produces the same output.
- 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.
- libraz libraz@libraz.net
