Topaz is a language for typesetting books and documents, and for many other programs besides. Name functions and variables in Korean, Russian, or English, then run the code on your computer or build it as a package for the browser. You can start in the playground without installing anything.
import std.io
io.writeLine("Hello, Topaz!")Hello, Topaz!
Printing to the screen also uses a standard module you import, so the code shows how a program talks to the outside world.
std.typeset
std.typeset in Topaz 5.21 is the standard module that breaks paragraphs into lines. The program below sets one Korean sentence at a width of ten Hangul syllables. Looking at the whole paragraph keeps a lone word off the last line.
import std.io
import std.typeset as ts
let sentence = "문단 전체를 보고 줄을 나누면 마지막 줄이 짧게 남지 않고 줄마다 길이가 고르게 됩니다."
let clusters = sentence.scalars().map(cluster)
let result = ts.classifyBreaks(clusters, koreanRules(clusters))
.flatMap((breaks: Array<ts.BreakOpportunity>) => ts.breakParagraph(paragraph(clusters, breaks)))
match result {
case Ok(plans) => {
io.writeLine("Whole paragraph\n{linesOf(clusters, plans.optimized)}\n")
io.writeLine("Line by line\n{linesOf(clusters, plans.greedy)}")
}
case Err(error) => io.writeLine("{error}")
}The main part of the program. The full source adds the helpers that set character widths and build the paragraph.
Whole paragraph 문단 전체를 보고 줄을 나누면 마지막 줄이 짧게 남지 않고 줄마다 길이가 고르게 됩니다. Line by line 문단 전체를 보고 줄을 나누면 마지막 줄이 짧게 남지 않고 줄마다 길이가 고르게 됩니다.
Get the breaks chosen for the whole paragraph together with a greedy, line-by-line result. Choose the Knuth-Plass cost model or a fixed-point cost model.
Find break opportunities with the Unicode 16.0 line breaking algorithm (UAX #14).
Add the Korean (KLReq), Japanese (JLReq), or Chinese (CLReq) rules, and an opening bracket never ends a line while a closing bracket or period never starts one. The Japanese rules follow the JLReq character classes.
The Unicode East Asian Width property decides breaks around quotation marks and brackets next to CJK text.
Set how far justification may stretch or shrink the space between Latin words, and how far it may stretch the space between CJK characters.
Write lengths such as 12pt, 3mm, 1cm, 1in, and 1em directly in code and convert between units.
ATLAS
ATLAS turns a manuscript into print-ready book pages. Here is a page it set, with part of its line-breaking rules in Topaz. ATLAS names these functions with Korean typesetting terms, shown here in English.

function nextIsNotLatinSpace(next: Option<string>) -> bool {
match next {
case Some(text) => !isLatinSpace(text)
case None => true
}
}
function nextHasNoLineStartBan(next: Option<string>) -> bool {
match next {
case Some(text) => !hasLineStartBan(text)
case None => true
}
}Part of the ATLAS line-breaking rules, with names translated into English for illustration
Name functions and variables with the words of typesetting, such as badness, in Korean, Russian, or English. Topaz also keeps the characters of a manuscript exactly as entered.
Strings & Templates →Write a typesetting rule once and build the same source as a package for the browser and an executable for your computer. Both follow the same language rules, so the on-screen preview and the print output break lines by the same typesetting rules.
Execution Targets →An int is a signed 64-bit integer, and overflow stops the program at that point instead of continuing with a wrong value. When you measure line widths and layout costs in integers, an overflow cannot hide in the result.
Runtime Behavior →Put a fixed set of cases, such as line-break classes, in an enum, and the compiler checks that each match handles every one. When you add a case later, the build reports each match that does not handle it yet.
Patterns & Control Flow →Lispex
Computing such as line breaking happens in Topaz code. Decisions that change with each publisher and book, such as which page a chapter starts on or how many lines may stand alone, go into separate Lispex rules. The Lispex 1.21 evaluator built into Topaz 5.21 runs those rules within the limits set in a limits file. Change a rule file and lock the package again, and the same manuscript takes another style without any change to the application code.
(let ((min-lines 2))
(if (< input min-lines) "next-page" "keep"))All of rules/widow-lines.lspx
import std.io
import src.values { singleDigit, textOf }
import std.lispex { defaultLimits, evaluate }
import std.lispex.rules as rules
export function main(args: Array<string>, stdin: string) -> Result<int, string> {
let rule = rules.widow_lines()
for linesLeft in [1, 2] {
match evaluate(rule, singleDigit(linesLeft)?, defaultLimits(rule)) {
case Ok(Complete(result)) => io.writeLine("lines left {linesLeft}: {textOf(result)}")
case _ => return Err("the rule did not finish")
}
}
Ok(0)
}lines left 1: next-page lines left 2: keep
All of src/main.tpz. singleDigit and textOf are small helpers in src/values.tpz that convert Lispex values.
Lispex rules run in the interpreter and in native builds.
import std.io
function discount(customer: { tier: string, years: int }) -> float {
return match customer {
case { tier: "vip", years } if years > 5 => 0.3
case { tier: "vip" } => 0.2
case { years } if years > 1 => 0.1
case _ => 0.0
}
}
io.writeLine("{discount({ tier: "vip", years: 7 })}")
io.writeLine("{discount({ tier: "new", years: 0 })}")0.3 0.0
import std.io
function clean(words: Array<string>) -> Array<string> {
return words.filter((w: string) => w != "")
}
function shout(words: Array<string>) -> Array<string> {
return words.map((w: string) => w + "!")
}
let line = ["one", "", "way", "", "to", "say", "it"]
|> clean
|> shout
io.writeLine("{line}")[one!, way!, to!, say!, it!]
Run independent tasks together, and close files you are done with by a cleanup step at the end of the block.
Explore Topaz
The core of the Topaz compiler is written in Topaz too.
See what Topaz is designed for, what its code looks like, and where it fits before writing your first program.
Meet Topaz →02Start with one small program and continue step by step to a checked, tested, two-module application.
Start learning →03Break a paragraph into lines with std.typeset. Set widths such as 12pt or 3mm, the line breaking rules of each language, and the spacing of justified lines.
Open the typesetting reference →04Start from the syntax map, then look up language rules, the standard library, and the tools.
Open the syntax map →