Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
96fcd35
basho:0.1.0
tftr0141 May 22, 2026
8ca78d4
update readme for example
tftr0141 May 22, 2026
7869d85
correct syntax for default config in README
tftr0141 May 22, 2026
6b75d7f
Merge branch 'typst:main' into update/basho-0.1.0
KoyaTofu42 May 22, 2026
c60f606
update links in README for extended and novel examples
KoyaTofu42 May 22, 2026
d8ff8c6
update example links in README to point to permalinks
KoyaTofu42 May 22, 2026
2d11ab2
add thumbnail image to README for visual reference
KoyaTofu42 May 23, 2026
98bbeab
update thumbnail image alt text for clarity
KoyaTofu42 May 23, 2026
2b38b80
update description in typst.toml; expand keywords list
KoyaTofu42 May 23, 2026
5045783
adjust available height calculation and update page height in tests
KoyaTofu42 May 23, 2026
81cb4db
improve height calculations for pagination and enhance column renderi…
KoyaTofu42 May 24, 2026
4d1bc29
add architecture, configuration, kinsoku, and layout hooks documentation
KoyaTofu42 May 24, 2026
82b9dbd
adjust margin calculations for accurate pagination and add regression…
KoyaTofu42 May 25, 2026
f88f13e
update parameter descriptions to clarify content types in documentati…
KoyaTofu42 May 25, 2026
0a7068b
Refactor: architecture and enhance documentation
KoyaTofu42 May 25, 2026
a2a4461
Enhance spacing and justification handling in layout and rendering
KoyaTofu42 May 25, 2026
10b9cd5
Improve Japanese vertical rendering and spacing logic
KoyaTofu42 May 25, 2026
c706318
fix: character box and justification handling
KoyaTofu42 May 26, 2026
5ffaa18
chore: format codes
KoyaTofu42 May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/preview/basho/0.1.0/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 KoyaTofu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
145 changes: 145 additions & 0 deletions packages/preview/basho/0.1.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Basho — Vertical Japanese Typesetting for Typst

![Thumbnail of Basho](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/example/thumbnail.png)

Basho (芭蕉) is a vertical Japanese typesetting (tategaki / 縦書き) package for Typst. It handles character boxes, tate-chu-yoko (TCY), ruby (furigana), automatic pagination, multi-column RTL layout, and kinsoku shori (Japanese line-breaking rules).

## Usage

### Minimal example

```typst
#import "@preview/basho:0.1.0": tate

#set text(font: "Harano Aji Mincho")
#set page(paper: "jp-business-card")

#show: tate

閑さや

 岩にしみ入る

  蝉の声
```

![Minimal example](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/example/minimal.svg)

### Full example

An extended example with various features is available [here](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/example/japanese-vertical.pdf). An example of Japanese novel typeset is available [here](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/example/Japanese-novel.pdf).

### Inline macros

| Macro | Description |
|---|---|
| `#tcy[body]` | Tate-chu-yoko — short horizontal text or content in a vertical column |
| `#vert[body]` | Force upright (one char or content per box, no rotation) |
| `#ruby(body, rt)` | Furigana annotation (accepts any content) |
| `#turn[body]` | Rotate content 90° clockwise |
| `#vblock[body]` | Rotated block (unrestricted width) |
| `#hblock[body]` | Horizontal block (no rotation) |

### Inline rendering

`#tate-inline(body, config)` renders content as a vertical stack without pagination — useful inside `#hblock[...]` or other upright contexts.

### Feature peek

![Vertical layout with ruby annotations and multi-column](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/example/features-peek-1.svg)

<details>

<summary>Show code</summary>

```typst
#import "@preview/basho:0.1.0": hblock, ruby, tate, vblock
#set text(font: "Harano Aji Mincho")
#set page(width: 450pt,height: 350pt)


#tate(config: (layout: (columns: 2)))[
= ポラーノの広場

そのころわたくしは、モリーオ市の博物局に勤めて居りました。
 十八等官でしたから役所のなかでも、ずうっと下の方でしたし#ruby("俸給", "ほうきゅう")もほんのわずかでしたが、受持ちが標本の採集や整理で生れ付き好きなことでしたから、わたくしは毎日ずいぶん愉快にはたらきました。殊にそのころ、モリーオ市では競馬場を植物園に#ruby("拵", "こしら")え直すというのでその景色のいいまわりにアカシヤを植え込んだ広い地面が、切符売場や信号所の建物のついたまま、わたくしどもの役所の方へまわって来たものですから、わたくしはすぐ宿直という名前で月賦で買った小さな蓄音器と二十枚ばかりのレコードをもって、その番小屋にひとり住むことになりました。わたくしはそこの馬を置く場所に板で小さなしきいをつけて一疋の山羊を飼いました。毎
]
```

</details>

![Math equations and tables](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/example/features-peek-2.svg)

<details>

<summary>Show code</summary>

```typst
#import "@preview/basho:0.1.0": hblock, ruby, tate, vblock
#set text(font: "Harano Aji Mincho")
#set page(width: 450pt,height: 350pt)

#tate[
== Fourier変換
次によって定義されるFourier変換
$
integral_(-oo)^(oo) f(x) e^(-2 pi i k x) d x, quad "where" x, k in R
$
は位置空間$x$から波数空間$k$への変換である。

== 形容詞の活用表
#hblock(table(
columns: 2,
tate[ク活用], [],
tate[から], tate[未然形],
tate[かり], tate[連用形],
tate[◯], tate[終止形],
tate[かる], tate[連体形],
tate[かれ], tate[命令形],
))

== 短冊
#rect(
fill: rgb(255, 240, 240),
tate(
[奥山に 紅葉踏みわけ 鳴く鹿の

声きく時ぞ 秋は悲しき],
),
)
]
```

</details>

---

## Architecture

Basho renders vertical text through a 5-stage pipeline built on a **Dependency Injection** architecture — every component (rendering transforms, TCY classification, kinsoku rules, list modules) is pluggable via a single `config` dictionary.

```mermaid
flowchart LR
Input["Input content"] --> Flatten["1. Flatten"]
Flatten --> Transform["2. Transform"]
Transform --> Classify["3. Classify"]
Classify --> Paginate["4. Paginate"]
Paginate --> Render["5. Render"]
Render --> Output["Output"]
```

## Learn more

| Document | Topics |
|---|---|
| [docs/architecture.md](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/docs/architecture.md) | Full pipeline details, token types, node-renderer dispatch table, source map |
| [docs/configuration.md](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/docs/configuration.md) | `config` deep-merge, full default-opts, factory function reference, override examples |
| [docs/kinsoku.md](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/docs/kinsoku.md) | JIS X 4051 priority tiers, `default-resolver()` parameters, custom resolve functions |
| [docs/layout-hooks.md](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/docs/layout-hooks.md) | Custom page layouts via hooks, bullet/numbered list module replacement |
| [docs/token-schema.md](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/docs/token-schema.md) | All token types, fields, and helper functions |
| [docs/modules.md](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/docs/modules.md) | Module contracts for TCY, rendering, kinsoku, and list modules |
| [docs/extending.md](https://github.com/KoyaTofu42/typst-basho/blob/7c81c54cfed34d58f80966171f75920a56773482/docs/extending.md) | Step-by-step guide to writing custom modules |

## License

MIT
5 changes: 5 additions & 0 deletions packages/preview/basho/0.1.0/lib.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// lib.typ
// Public API for Basho — Vertical Japanese Typesetting

#import "src/main.typ": hblock, ruby, tate, tate-inline, tcy, turn, vblock, vert
#import "src/core/kinsoku.typ": default-resolver
143 changes: 143 additions & 0 deletions packages/preview/basho/0.1.0/src/config.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// src/config.typ
// Configuration state and merge engine for Basho DI architecture

#import "core/kinsoku.typ": default-resolver
#import "core/tcy.typ": default-tcy
#import "core/spacing.typ": default-spacing

// ---------------------------------------------------------------------------
// Default rendering module factory — self-contained
// ---------------------------------------------------------------------------

/// Default rendering module factory.
/// Bundles character normalization, dash scaling, and custom node renderers.
///
/// - dash-scale (length): Font size for horizontal-bar character. Default: 1.25em.
/// - node-renderers (dictionary): Custom token-type renderers. Default: (:).
/// -> dictionary: A rendering module dict with `dash-scale`, `node-renderers`, and `transform`.
#let default-rendering-params(
dash-scale: 1.25em,
node-renderers: (:),
) = {
(
dash-scale: dash-scale,
node-renderers: node-renderers,
transform: (tokens, config) => {
tokens.map(t => {
if t.type == "char" and (t.text == "—" or t.text == "─") {
t.text = "―"
}
t
})
},
)
}

// ---------------------------------------------------------------------------
// Default sizing factory
// ---------------------------------------------------------------------------

/// Sizing parameters factory.
///
/// - char-box (length): Width/height of the character box. Default: 1em.
/// - ruby-size (length): Font size for ruby text. Default: 0.5em.
/// - ruby-offset (length): Horizontal offset for ruby text from the left edge. Default: 1em.
/// - heading-scales (array): Font scale factors for h1, h2, h3. Default: (1.5, 1.3, 1.15).
/// -> dictionary: A sizing dict.
#let default-sizing-params(
char-box: 1em,
ruby-size: 0.5em,
ruby-offset: 1em,
heading-scales: (1.5, 1.3, 1.15),
) = {
(char-box: char-box, ruby-size: ruby-size, ruby-offset: ruby-offset, heading-scales: heading-scales)
}

// ---------------------------------------------------------------------------
// Default categories factory
// ---------------------------------------------------------------------------

/// Categories parameters factory.
/// Provides the TCY classification function used by the default TCY filter.
///
/// - classify (function): (text, config) => "horizontal" | "rotated" | "char".
/// Default: 1-2 digit numbers → "horizontal", rest → "rotated".
/// -> dictionary: A categories dict.
#let default-categories(
classify: (text, config) => {
if text.match(regex("^[0-9]{1,2}$")) != none { return "horizontal" }
return "rotated"
},
) = {
(classify: classify)
}

// ---------------------------------------------------------------------------
// Default layout factory
// ---------------------------------------------------------------------------

/// Layout parameters factory.
///
/// - columns (int): Number of horizontal rows (段組み). Default: 1.
/// - gap (length): Gap between columns within a row. Default: 1em.
/// - column-gap (length): Gap between rows (vertical). Default: 2em.
/// - hooks (array): Array of (cols, font, gap, config) => content; last wins. Default: ().
/// -> dictionary: A layout config dict.
#let default-layout-params(
columns: 1,
gap: 1em,
column-gap: 2em,
hooks: (),
) = {
(columns: columns, gap: gap, column-gap: column-gap, hooks: hooks)
}

#import "core/turn.typ": default-turn
#import "core/vblock.typ": default-vblock
#import "core/hblock.typ": default-hblock
#import "core/list.typ": default-bullet-list-params, default-numbered-list-params


// ---------------------------------------------------------------------------
// Default options
// ---------------------------------------------------------------------------

/// Default options dictionary for Basho.
#let default-opts = (
font: none,
features: ("vert", "vrt2"),
sizing: default-sizing-params(),
categories: default-categories(),
layout: default-layout-params(),
kinsoku: default-resolver(),
tcy: (default-tcy(),),
rendering: (default-rendering-params(), default-spacing(), default-turn, default-vblock, default-hblock),
list: (
bullet: default-bullet-list-params(),
numbered: default-numbered-list-params(),
),
)

// ---------------------------------------------------------------------------
// Merge engine
// ---------------------------------------------------------------------------

/// Recursively merges a user configuration dictionary into a base configuration.
/// Ensures nested dictionaries are merged rather than overwritten completely.
/// Arrays (like kinsoku, tcy, rendering) are replaced wholesale — this is
/// intentional so users can swap out entire module arrays.
///
/// - base (dictionary): The base configuration (e.g., default-opts).
/// - user (dictionary): The user's configuration overrides.
/// -> dictionary: The merged configuration.
#let merge-config(base, user) = {
let result = base
for (key, val) in user {
if key in result and type(result.at(key)) == dictionary and type(val) == dictionary {
result.insert(key, merge-config(result.at(key), val))
} else {
result.insert(key, val)
}
}
result
}
54 changes: 54 additions & 0 deletions packages/preview/basho/0.1.0/src/core/char-box.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// src/char-box.typ
// Base character box rendering

/// Wraps a single character in a 1em × 1em box with vertical OpenType features.
/// Alignment within the box depends on bracket type:
/// - Opening brackets (「 etc.) → left-aligned (or right-aligned depending on convention)
/// - Closing brackets (」 etc.) → right-aligned (or left-aligned depending on convention)
/// - All other characters → center-aligned
///
/// For U+2015 (Horizontal Bar / vertical dash), the text is rendered at
/// `rendering.dash-scale` size so consecutive dashes concatenate seamlessly.
///
/// - body (content): The character content to render.
/// - font (str): Font family name.
/// - config (dictionary): The layout configuration.
/// - h-align (alignment): Horizontal alignment override.
/// -> content: A box containing the vertically-oriented character.
#let char-box(body, font, config, h-align: center, v-align: horizon, height: none) = {
let render-module = config.rendering.first()
let f-opt = if font != none { (font: font) } else { (:) }

// Half-width spaces render as a narrow vertical gap (default 0.25em)
if body == "\u{0020}" {
let space-width = config.at("space-width", default: 0.25em)
return box(width: config.sizing.char-box, height: space-width)
}

let inner = if type(body) == str {
if body == "―" {
text(
..f-opt,
size: render-module.dash-scale,
features: config.features,
body,
)
} else {
text(
..f-opt,
features: config.features,
body,
)
}
} else {
body
}

let box-height = if height != none { height } else { config.sizing.char-box }
box(
width: config.sizing.char-box,
height: box-height,
clip: false,
align(h-align + v-align, inner),
)
}
13 changes: 13 additions & 0 deletions packages/preview/basho/0.1.0/src/core/hblock.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// src/hblock.typ
#let render-hblock(token, config) = {
box(
height: config.at("usable-height", default: auto),
align(center + horizon, token.text),
)
}

#let default-hblock = (
node-renderers: (
"hblock": render-hblock,
),
)
Loading
Loading