Languages. Products. Built here. Run here.

We design languages, systems, web services and infrastructure, the digital layer behind HAZE CANVAS, HAZE SOUND and HAZE STORY.

typeset-en.tpz Two ways to break a paragraph Topaz 5.21.3

Line by line

문단 전체를 보고 줄을나누면 마지막 줄이짧게 남지 않고 줄마다길이가 고르게됩니다.

5 lines · totalCost 730762850

Whole paragraph

문단 전체를 보고줄을 나누면 마지막줄이 짧게 남지않고 줄마다 길이가고르게 됩니다.

5 lines · totalCost 98339632

Hangul 10pt · space 2.5pt · Korean line breaking · greedy / Knuth-Plass

View the running sourceDownload source
import std.ioimport std.typeset {  BreakCluster, BreakPolicy, BreakMode, KinsokuRules, BreakOpportunity, Paragraph, Item,  Box, Penalty, BreakRule, DistanceGrid, CostModel, LineGeometry, JustificationPolicy,  JustificationMode, KnuthPlassPolicy, ParagraphPlan, PlanLine, classifyBreaks,  breakParagraph,}// HAZE adapter. First stdin line is width in millipoints, followed by the sentence.let hazeInput = io.readStdin().split("\n")let lineWidth = hazeInput[0].toInt() ?? 100000// Widths are in thousandths of a point. A Hangul syllable is 10 points wide and a space 2.5 points.function cluster(text: string) -> BreakCluster {    BreakCluster { text: text, advance: if text == " " { 2500 } else { 10000 }, trackingAfter: 0 }}// Unicode line breaking (UAX #14) plus the Korean line-breaking rules (KLReq). A word that fits on a line stays whole.function koreanRules(clusters: Array<BreakCluster>) -> BreakPolicy {    BreakPolicy {        mode: BreakMode.Word, rules: KinsokuRules.Unicode16Ko,        forbidBefore: [], forbidAfter: [], boundaryAllowed: clusters.map((c: BreakCluster) => true),        generatedHyphenEnds: [], protectFittingLatin: false, preserveShortWords: false,        narrowestWordTarget: lineWidth, bodyWordTarget: lineWidth    }}// One box per cluster and a zero penalty at each break opportunity. The last opportunity always ends the line.function paragraph(clusters: Array<BreakCluster>, breaks: Array<BreakOpportunity>) -> Paragraph {    let mut items: Array<Item> = []    for index in 0..<clusters.length {        let c = clusters[index]        items.push(Item.BoxItem(Box { unitIndex: index, text: c.text, width: c.advance, trackingAfter: 0 }))    }    for opportunity in breaks {        let last = opportunity.unitEnd == clusters.length        items.push(Item.PenaltyItem(Penalty {            unitEnd: opportunity.unitEnd, width: 0, kind: opportunity.kind, flagged: false,            rule: if last { BreakRule.Forced } else { BreakRule.Allowed(0) }        }))    }    Paragraph {        grid: DistanceGrid.MilliPoint, items: items, costModel: CostModel.FixedPoint,        geometry: LineGeometry { bodyTarget: lineWidth, firstTarget: lineWidth, dropCapSpan: 0, dropCapInset: 0 },        justification: JustificationPolicy {            mode: JustificationMode.Unjustified, minGapCount: 0, minLineWidthRatioM: 0,            minFinalLineWidthRatioM: None, maxAdjustment: 0, maxGapAdjustment: 0, maxGapRatioR: 0,            latinSpacing: None, cjkStretchRatio: 0.0        },        knuthPlass: KnuthPlassPolicy {            toleranceR: 0, linePenalty: 0, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0        }    }}// Join the clusters of each line and drop the space at the end of the line.function linesOf(clusters: Array<BreakCluster>, plan: ParagraphPlan) -> string {    plan.lines        .map((line: PlanLine) => clusters.slice(line.start, line.trimmedEndExclusive).map((c: BreakCluster) => c.text).join(""))        .join("\n")}let sentence = hazeInput.slice(1, hazeInput.length).join("\n")let clusters = sentence.scalars().map(cluster)let result = classifyBreaks(clusters, koreanRules(clusters))    .flatMap((breaks: Array<BreakOpportunity>) => breakParagraph(paragraph(clusters, breaks)))match result {    case Ok(plans) => {        // HAZE adapter. Return actual plan metrics and scalar boundaries.        io.writeLine("optimized {plans.optimized.lines.length} {plans.optimized.totalCost}")        for line in plans.optimized.lines {            io.writeLine("{line.start} {line.trimmedEndExclusive} {line.trimmedTextWidth}")        }        io.writeLine("greedy {plans.greedy.lines.length} {plans.greedy.totalCost}")        for line in plans.greedy.lines {            io.writeLine("{line.start} {line.trimmedEndExclusive} {line.trimmedTextWidth}")        }    }    case Err(error) => io.writeLine("{error}")}
  1. 01Source3,930 B
  2. 02Check
  3. 03Run
These results were computed with this source. Edit the sentence or width to recalculate in your browser.Runtime topaz_wasm 5.21.3 · Commit b01d0c3 · Apache-2.0
Our own languages
2Topaz · Lispex
Products in operation
14Every one listed below
Runtime on this page
5.21.3The runtime on this page
Division sites
4PARK CANVAS SOUND STORY

01Languages

We design and maintain two languages.

General-Purpose Language

Topaz

Topaz is a general-purpose programming language with typesetting built in. Code that does the same job looks the same no matter who writes it.

paragraph.tpz

import std.ioimport std.typeset { BreakCluster, BreakPolicy, BreakMode, KinsokuRules, classifyBreaks }let chars = "좋은 문장은 (규칙을 지켜) 줄을 나눕니다.".scalars()let width = 100000 // 100 pt, a fixed 10 pt character gridlet clusters = chars.map(ch => BreakCluster {  text: ch, advance: 10000, trackingAfter: 0 })let policy = BreakPolicy {  mode: BreakMode.Word, rules: KinsokuRules.Unicode16Ko,  forbidBefore: [], forbidAfter: [],  boundaryAllowed: chars.map(ch => true), generatedHyphenEnds: [],  protectFittingLatin: false, preserveShortWords: false,  narrowestWordTarget: width, bodyWordTarget: width}match classifyBreaks(clusters, policy) {  case Ok(points) => {    let mut start = 0    while start < chars.length {      let ends = points.filter(p => p.unitEnd > start && (p.unitEnd - start) * 10000 <= width)      let end = ends[ends.length - 1].unitEnd      io.writeLine(chars.slice(start, end).join("").trim())      start = end    }  };  case Err(error) => io.writeLine("{error}")}

Output

좋은 문장은
(규칙을 지켜)
줄을 나눕니다.
topaz.ooo

Business Rules Language

Lispex

Lispex is a language for writing business rules, such as a publisher's house style. It cannot touch files or the network, so it is safe to run inside any program.

royalties.lspx

; Split a book's royalty without rounding away a share.(define price 19900)(define copies 127)(define royalty-rate 1/10)(define author-share 2/3)(define (royalties price copies)  (let* ((pool (* price copies royalty-rate))         (author (* pool author-share))         (translator (- pool author)))    (list author translator)))(define payment (royalties price copies))(define author (car payment))(define translator (cadr payment))(list 'author author      'translator translator      'total (+ author translator)      'balanced (= (+ author translator)                   (* price copies royalty-rate)))

Output

(author 505460/3 translator 252730/3 total 252730 balanced #t)
lispex.com

03Structure

Systems before products.

  1. L4Web & AppsDigital TouchpointsWeb services and practical tools built to run directly in the browser and on the desktop.
  2. L3SystemsWorkflows & ToolsArchitectures and internal workflow tools that bring order to complex tasks.
  3. L2LanguagesLanguage ExperimentsLanguages and runtimes that turn ideas and rules into running software.
  4. L1InfrastructureHosting, Deployment, AutomationDeployment pipelines, automation, and hosting layers that run quietly in the background.

Operating stance

  1. 1Systems before products.
  2. 2Prove utility, then scale.
  3. 3Built for internal work, polished for everyone outside.

04Connections

Tools for the other three divisions.

  1. HAZE STORYSets book pages with ATLAS, our typesetting tool.hazestory.com
  2. HAZE SOUNDBuilds the automated mixing and mastering chain.hazesound.com
  3. HAZE CANVASBuilds the archive app for photos and videos.hazecanvas.com

Applied infrastructure

Brand Hubs
The shared web hub connecting STUDIO HAZE and its division sites into one.
Internal Tools
Internal tools and automation that eliminate repetitive work and keep workflows steady.
Delivery Layer
Delivery infrastructure ensuring reliable content distribution and stable traffic flow.

Curiosity-driven experiments eventually grow into our most resilient systems.