View the running source
Download source1import std.io2import std.typeset {3 BreakCluster, BreakPolicy, BreakMode, KinsokuRules, BreakOpportunity, Paragraph, Item,4 Box, Penalty, BreakRule, DistanceGrid, CostModel, LineGeometry, JustificationPolicy,5 JustificationMode, KnuthPlassPolicy, ParagraphPlan, PlanLine, classifyBreaks,6 breakParagraph,7}89// HAZE adapter. First stdin line is width in millipoints, followed by the sentence.10let hazeInput = io.readStdin().split("\n")11let lineWidth = hazeInput[0].toInt() ?? 1000001213// Widths are in thousandths of a point. A Hangul syllable is 10 points wide and a space 2.5 points.14function cluster(text: string) -> BreakCluster {15 BreakCluster { text: text, advance: if text == " " { 2500 } else { 10000 }, trackingAfter: 0 }16}1718// Unicode line breaking (UAX #14) plus the Korean line-breaking rules (KLReq). A word that fits on a line stays whole.19function koreanRules(clusters: Array<BreakCluster>) -> BreakPolicy {20 BreakPolicy {21 mode: BreakMode.Word, rules: KinsokuRules.Unicode16Ko,22 forbidBefore: [], forbidAfter: [], boundaryAllowed: clusters.map((c: BreakCluster) => true),23 generatedHyphenEnds: [], protectFittingLatin: false, preserveShortWords: false,24 narrowestWordTarget: lineWidth, bodyWordTarget: lineWidth25 }26}2728// One box per cluster and a zero penalty at each break opportunity. The last opportunity always ends the line.29function paragraph(clusters: Array<BreakCluster>, breaks: Array<BreakOpportunity>) -> Paragraph {30 let mut items: Array<Item> = []31 for index in 0..<clusters.length {32 let c = clusters[index]33 items.push(Item.BoxItem(Box { unitIndex: index, text: c.text, width: c.advance, trackingAfter: 0 }))34 }35 for opportunity in breaks {36 let last = opportunity.unitEnd == clusters.length37 items.push(Item.PenaltyItem(Penalty {38 unitEnd: opportunity.unitEnd, width: 0, kind: opportunity.kind, flagged: false,39 rule: if last { BreakRule.Forced } else { BreakRule.Allowed(0) }40 }))41 }42 Paragraph {43 grid: DistanceGrid.MilliPoint, items: items, costModel: CostModel.FixedPoint,44 geometry: LineGeometry { bodyTarget: lineWidth, firstTarget: lineWidth, dropCapSpan: 0, dropCapInset: 0 },45 justification: JustificationPolicy {46 mode: JustificationMode.Unjustified, minGapCount: 0, minLineWidthRatioM: 0,47 minFinalLineWidthRatioM: None, maxAdjustment: 0, maxGapAdjustment: 0, maxGapRatioR: 0,48 latinSpacing: None, cjkStretchRatio: 0.049 },50 knuthPlass: KnuthPlassPolicy {51 toleranceR: 0, linePenalty: 0, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 052 }53 }54}5556// Join the clusters of each line and drop the space at the end of the line.57function linesOf(clusters: Array<BreakCluster>, plan: ParagraphPlan) -> string {58 plan.lines59 .map((line: PlanLine) => clusters.slice(line.start, line.trimmedEndExclusive).map((c: BreakCluster) => c.text).join(""))60 .join("\n")61}6263let sentence = hazeInput.slice(1, hazeInput.length).join("\n")64let clusters = sentence.scalars().map(cluster)65let result = classifyBreaks(clusters, koreanRules(clusters))66 .flatMap((breaks: Array<BreakOpportunity>) => breakParagraph(paragraph(clusters, breaks)))67match result {68 case Ok(plans) => {69 // HAZE adapter. Return actual plan metrics and scalar boundaries.70 io.writeLine("optimized {plans.optimized.lines.length} {plans.optimized.totalCost}")71 for line in plans.optimized.lines {72 io.writeLine("{line.start} {line.trimmedEndExclusive} {line.trimmedTextWidth}")73 }74 io.writeLine("greedy {plans.greedy.lines.length} {plans.greedy.totalCost}")75 for line in plans.greedy.lines {76 io.writeLine("{line.start} {line.trimmedEndExclusive} {line.trimmedTextWidth}")77 }78 }79 case Err(error) => io.writeLine("{error}")80}