Skip to content
Topaz6.0 · Before release
Download

Search documentation

Specification 6.0 · Not released · Tested product 5.21.3

Use arrow keys to choose a result. Press Enter to open.

Type an API name, diagnostic number, rule, or project topic.

    Library

    std.typeset

    Classify measured text boundaries and produce deterministic paragraph plans with explicit policies.

    APIs in this module

    136 APIs

    NameModule
    Box.textstd.typeset
    Box.trackingAfterstd.typeset
    Box.unitIndexstd.typeset
    Box.widthstd.typeset
    BreakCluster.advancestd.typeset
    BreakCluster.textstd.typeset
    BreakCluster.trackingAfterstd.typeset
    BreakKind.CjkClusterstd.typeset
    BreakKind.ClosingPunctuationstd.typeset
    BreakKind.ForcedEndstd.typeset
    BreakKind.HardFallbackstd.typeset
    BreakKind.LatinHyphenstd.typeset
    BreakKind.LatinSoftHyphenstd.typeset
    BreakKind.LatinSpacestd.typeset
    BreakMode.Characterstd.typeset
    BreakMode.Wordstd.typeset
    BreakOpportunity.kindstd.typeset
    BreakOpportunity.unitEndstd.typeset
    BreakPolicy.bodyWordTargetstd.typeset
    BreakPolicy.boundaryAllowedstd.typeset
    BreakPolicy.forbidAfterstd.typeset
    BreakPolicy.forbidBeforestd.typeset
    BreakPolicy.generatedHyphenEndsstd.typeset
    BreakPolicy.modestd.typeset
    BreakPolicy.narrowestWordTargetstd.typeset
    BreakPolicy.preserveShortWordsstd.typeset
    BreakPolicy.protectFittingLatinstd.typeset
    BreakPolicy.rulesstd.typeset
    BreakRule.Allowedstd.typeset
    BreakRule.Forbiddenstd.typeset
    BreakRule.Forcedstd.typeset
    CostModel.FixedPointstd.typeset
    CostModel.KnuthPlassstd.typeset
    DistanceGrid.MilliPointstd.typeset
    DistanceGrid.ScaledPointstd.typeset
    Glue.shrinkstd.typeset
    Glue.stretchstd.typeset
    Glue.textstd.typeset
    Glue.trackingAfterstd.typeset
    Glue.unitIndexstd.typeset
    Glue.widthstd.typeset
    Item.BoxItemstd.typeset
    Item.GlueItemstd.typeset
    Item.PenaltyItemstd.typeset
    JustificationMode.CjkInterCharacterstd.typeset
    JustificationMode.LatinWordSpacestd.typeset
    JustificationMode.Unjustifiedstd.typeset
    JustificationPolicy.cjkStretchRatiostd.typeset
    JustificationPolicy.latinSpacingstd.typeset
    JustificationPolicy.maxAdjustmentstd.typeset
    JustificationPolicy.maxGapAdjustmentstd.typeset
    JustificationPolicy.maxGapRatioRstd.typeset
    JustificationPolicy.minFinalLineWidthRatioMstd.typeset
    JustificationPolicy.minGapCountstd.typeset
    JustificationPolicy.minLineWidthRatioMstd.typeset
    JustificationPolicy.modestd.typeset
    KinsokuRules.AtlasDefaultstd.typeset
    KinsokuRules.Unicode16Jastd.typeset
    KinsokuRules.Unicode16Kostd.typeset
    KinsokuRules.Unicode16Zhstd.typeset
    KnuthPlassPolicy.finalFlaggedDemeritstd.typeset
    KnuthPlassPolicy.fitnessDemeritstd.typeset
    KnuthPlassPolicy.flaggedDemeritstd.typeset
    KnuthPlassPolicy.linePenaltystd.typeset
    KnuthPlassPolicy.toleranceRstd.typeset
    LatinSpacing.fontSizePtstd.typeset
    LatinSpacing.maxLetterSpacingEmstd.typeset
    LatinSpacing.maxWordSpaceRatiostd.typeset
    LatinSpacing.minWordSpaceRatiostd.typeset
    Length<Cm>.toCmstd.typeset
    Length<Cm>.toInstd.typeset
    Length<Cm>.toMmstd.typeset
    Length<Cm>.toPtstd.typeset
    Length<Cm>.toScaledPointsstd.typeset
    Length<Em>.resolvestd.typeset
    Length<In>.toCmstd.typeset
    Length<In>.toInstd.typeset
    Length<In>.toMmstd.typeset
    Length<In>.toPtstd.typeset
    Length<In>.toScaledPointsstd.typeset
    Length<Mm>.toCmstd.typeset
    Length<Mm>.toInstd.typeset
    Length<Mm>.toMmstd.typeset
    Length<Mm>.toPtstd.typeset
    Length<Mm>.toScaledPointsstd.typeset
    Length<Pt>.toCmstd.typeset
    Length<Pt>.toInstd.typeset
    Length<Pt>.toMilliPointsstd.typeset
    Length<Pt>.toMmstd.typeset
    Length<Pt>.toPtstd.typeset
    Length<Pt>.toScaledPointsstd.typeset
    LineGeometry.bodyTargetstd.typeset
    LineGeometry.dropCapInsetstd.typeset
    LineGeometry.dropCapSpanstd.typeset
    LineGeometry.firstTargetstd.typeset
    Paragraph.costModelstd.typeset
    Paragraph.geometrystd.typeset
    Paragraph.gridstd.typeset
    Paragraph.itemsstd.typeset
    Paragraph.justificationstd.typeset
    Paragraph.knuthPlassstd.typeset
    ParagraphPlan.boundedFillFeasiblestd.typeset
    ParagraphPlan.gridstd.typeset
    ParagraphPlan.linesstd.typeset
    ParagraphPlan.totalCoststd.typeset
    ParagraphResult.breakOpportunityCountstd.typeset
    ParagraphResult.greedystd.typeset
    ParagraphResult.optimizedstd.typeset
    Penalty.flaggedstd.typeset
    Penalty.kindstd.typeset
    Penalty.rulestd.typeset
    Penalty.unitEndstd.typeset
    Penalty.widthstd.typeset
    PlanLine.adjustmentRatioRstd.typeset
    PlanLine.coststd.typeset
    PlanLine.costTextWidthstd.typeset
    PlanLine.displayTextScalarLengthstd.typeset
    PlanLine.endExclusivestd.typeset
    PlanLine.kindstd.typeset
    PlanLine.postValidationWidthLimitstd.typeset
    PlanLine.rawTextWidthstd.typeset
    PlanLine.selectedPenaltyWidthstd.typeset
    PlanLine.startstd.typeset
    PlanLine.targetstd.typeset
    PlanLine.trimmedEndExclusivestd.typeset
    PlanLine.trimmedTextWidthstd.typeset
    std.typeset.breakParagraphstd.typeset
    std.typeset.classifyBreaksstd.typeset
    std.typeset.fromMilliPointsstd.typeset
    std.typeset.fromScaledPointsstd.typeset
    std.typeset.lineBadnessstd.typeset
    TypesetError.InvalidInputstd.typeset
    TypesetError.InvalidPolicystd.typeset
    TypesetError.NoBreakOpportunitiesstd.typeset
    TypesetError.NoGreedyPlanstd.typeset
    TypesetError.NoPlanstd.typeset

    Module

    Link

    Classify boundaries between measured clusters, then plan the supplied paragraph items with an explicit cost model. Identical inputs and policies produce deterministic boundaries, costs, and errors. Inspect optimized and greedy plans with typed physical widths, source unit intervals, and the recorded search result.

    Why

    Link

    Topaz gives physical lengths a type that application code and paragraph planning can share. This module connects measured text clusters, break policies, and paragraph plans through that common length model. Explicit policies let applications select line breaking and justification behavior while retaining typed geometry.

    Availability

    Link

    Import std.typeset in language mode 5.21. The current compiler selects std/typeset.tpz for this language mode. Availability in other released editions and inclusion in each installed distribution are Pending.

    Exported types

    Link

    enum BreakKind { CjkCluster, LatinSpace, LatinHyphen, LatinSoftHyphen, ClosingPunctuation, HardFallback, ForcedEnd }

    enum TypesetError { InvalidInput(string), InvalidPolicy(string), NoBreakOpportunities, NoGreedyPlan, NoPlan }

    enum DistanceGrid { ScaledPoint, MilliPoint }

    enum BreakMode { Word, Character }

    enum KinsokuRules { AtlasDefault, Unicode16Ko, Unicode16Zh, Unicode16Ja }

    enum JustificationMode { Unjustified, LatinWordSpace, CjkInterCharacter }

    enum CostModel { FixedPoint, KnuthPlass }

    enum BreakRule { Allowed(int), Forced, Forbidden }

    record Box { unitIndex:int, text:string, width:Length<Pt>, trackingAfter:Length<Pt> }

    record Glue { unitIndex:int, text:string, width:Length<Pt>, trackingAfter:Length<Pt>, stretch:Length<Pt>, shrink:Length<Pt> }

    record Penalty { unitEnd:int, width:Length<Pt>, rule:BreakRule, kind:BreakKind, flagged:bool }

    enum Item { BoxItem(Box), GlueItem(Glue), PenaltyItem(Penalty) }

    record BreakPolicy { mode:BreakMode, rules:KinsokuRules, forbidBefore:Array<int>, forbidAfter:Array<int>, boundaryAllowed:Array<bool>, generatedHyphenEnds:Array<int>, protectFittingLatin:bool, preserveShortWords:bool, narrowestWordTarget:Length<Pt>, bodyWordTarget:Length<Pt> }

    record BreakCluster { text:string, advance:Length<Pt>, trackingAfter:Length<Pt> }

    record BreakOpportunity { unitEnd:int, kind:BreakKind }

    record LatinSpacing { minWordSpaceRatio:float, maxWordSpaceRatio:float, maxLetterSpacingEm:Length<Em>, fontSizePt:Length<Pt> }

    record JustificationPolicy { mode:JustificationMode, minGapCount:int, minLineWidthRatioM:int, minFinalLineWidthRatioM:Option<int>, maxAdjustment:Length<Pt>, maxGapAdjustment:Length<Pt>, maxGapRatioR:int, latinSpacing:Option<LatinSpacing>, cjkStretchRatio:float }

    record LineGeometry { bodyTarget:Length<Pt>, firstTarget:Length<Pt>, dropCapSpan:int, dropCapInset:Length<Pt> }

    record KnuthPlassPolicy { toleranceR:int, linePenalty:int, fitnessDemerit:int, flaggedDemerit:int, finalFlaggedDemerit:int }

    record Paragraph { grid:DistanceGrid, items:Array<Item>, geometry:LineGeometry, justification:JustificationPolicy, costModel:CostModel, knuthPlass:KnuthPlassPolicy }

    record PlanLine { start:int, endExclusive:int, trimmedEndExclusive:int, displayTextScalarLength:int, kind:BreakKind, target:Length<Pt>, rawTextWidth:Length<Pt>, trimmedTextWidth:Length<Pt>, costTextWidth:Length<Pt>, selectedPenaltyWidth:Length<Pt>, postValidationWidthLimit:Option<Length<Pt>>, cost:int, adjustmentRatioR:int }

    record ParagraphPlan { lines:Array<PlanLine>, totalCost:int, boundedFillFeasible:Option<bool>, grid:DistanceGrid }

    record ParagraphResult { optimized:ParagraphPlan, greedy:ParagraphPlan, breakOpportunityCount:int }

    Type members keep the compiler-owned type name shown in their declarations. Importing a module does not create a new type identity.

    Public API

    Link

    std.typeset.fromScaledPoints

    std.typeset.fromMilliPoints

    BreakKind.LatinSpace

    BreakKind.LatinHyphen

    BreakKind.LatinSoftHyphen

    BreakKind.ClosingPunctuation

    BreakKind.HardFallback

    BreakKind.ForcedEnd

    TypesetError.InvalidInput

    TypesetError.InvalidPolicy

    TypesetError.NoBreakOpportunities

    TypesetError.NoGreedyPlan

    TypesetError.NoPlan

    DistanceGrid.ScaledPoint

    DistanceGrid.MilliPoint

    BreakMode.Word

    BreakMode.Character

    KinsokuRules.AtlasDefault

    KinsokuRules.Unicode16Ko

    KinsokuRules.Unicode16Zh

    KinsokuRules.Unicode16Ja

    JustificationMode.Unjustified

    JustificationMode.LatinWordSpace

    JustificationMode.CjkInterCharacter

    CostModel.FixedPoint

    CostModel.KnuthPlass

    BreakRule.Allowed

    BreakRule.Forced

    BreakRule.Forbidden

    Box.unitIndex

    Box.text

    Box.width

    Box.trackingAfter

    Glue.unitIndex

    Glue.text

    Glue.width

    Glue.trackingAfter

    Glue.stretch

    Glue.shrink

    Penalty.unitEnd

    Penalty.width

    Penalty.rule

    Penalty.kind

    Penalty.flagged

    Item.BoxItem

    Item.GlueItem

    Item.PenaltyItem

    BreakPolicy.mode

    BreakPolicy.rules

    BreakPolicy.forbidBefore

    BreakPolicy.forbidAfter

    BreakPolicy.boundaryAllowed

    BreakPolicy.generatedHyphenEnds

    BreakPolicy.protectFittingLatin

    BreakPolicy.preserveShortWords

    BreakPolicy.narrowestWordTarget

    BreakPolicy.bodyWordTarget

    BreakCluster.text

    BreakCluster.advance

    BreakCluster.trackingAfter

    BreakOpportunity.unitEnd

    BreakOpportunity.kind

    LatinSpacing.minWordSpaceRatio

    LatinSpacing.maxWordSpaceRatio

    LatinSpacing.maxLetterSpacingEm

    LatinSpacing.fontSizePt

    JustificationPolicy.mode

    JustificationPolicy.minGapCount

    JustificationPolicy.minLineWidthRatioM

    JustificationPolicy.minFinalLineWidthRatioM

    JustificationPolicy.maxAdjustment

    JustificationPolicy.maxGapAdjustment

    JustificationPolicy.maxGapRatioR

    JustificationPolicy.latinSpacing

    JustificationPolicy.cjkStretchRatio

    LineGeometry.bodyTarget

    LineGeometry.firstTarget

    LineGeometry.dropCapSpan

    LineGeometry.dropCapInset

    KnuthPlassPolicy.toleranceR

    KnuthPlassPolicy.linePenalty

    KnuthPlassPolicy.fitnessDemerit

    KnuthPlassPolicy.flaggedDemerit

    KnuthPlassPolicy.finalFlaggedDemerit

    Paragraph.grid

    Paragraph.items

    Paragraph.geometry

    Paragraph.justification

    Paragraph.costModel

    Paragraph.knuthPlass

    PlanLine.start

    PlanLine.endExclusive

    PlanLine.trimmedEndExclusive

    PlanLine.displayTextScalarLength

    PlanLine.kind

    PlanLine.target

    PlanLine.rawTextWidth

    PlanLine.trimmedTextWidth

    PlanLine.costTextWidth

    PlanLine.selectedPenaltyWidth

    PlanLine.postValidationWidthLimit

    PlanLine.cost

    PlanLine.adjustmentRatioR

    ParagraphPlan.lines

    ParagraphPlan.totalCost

    ParagraphPlan.boundedFillFeasible

    ParagraphPlan.grid

    ParagraphResult.optimized

    ParagraphResult.greedy

    ParagraphResult.breakOpportunityCount

    std.typeset.breakParagraph

    std.typeset.classifyBreaks

    Length.toPt for Pt

    Length.toMm for Pt

    Length.toCm for Pt

    Length.toIn for Pt

    Length.toMilliPoints for Pt

    Length.toPt for Mm

    Length.toMm for Mm

    Length.toCm for Mm

    Length.toIn for Mm

    Length.toPt for Cm

    Length.toMm for Cm

    Length.toCm for Cm

    Length.toIn for Cm

    Length.toPt for In

    Length.toMm for In

    Length.toCm for In

    Length.toIn for In

    Length.resolve for Em

    Length.toScaledPoints for Pt

    Length.toScaledPoints for Mm

    Length.toScaledPoints for Cm

    Length.toScaledPoints for In

    BreakKind.CjkCluster

    std.typeset.lineBadness

    Pending contracts

    Link

    Convert physical lengths with toScaledPoints

    Link
    Ruletopaz.stdlib.contract.to-scaled-points.rule

    Convert physical lengths with toScaledPoints

    Physical Length values with the Pt, Mm, Cm, or In marker provide toScaledPoints returning int. One point is 65,536 scaled points. The result is the exact stored signed 64-bit count.

    The scaledPoints member is absent from every Length type. Calling that removed member is rejected with TPZ5006. Length<Em> has no toScaledPoints member and must resolve to a physical length first.

    fromScaledPoints accepts every int from -9223372036854775808 through 9223372036854775807 inclusive. It returns Length<Pt> with that exact scaled-point count. Zero and negative inputs are valid. Construction performs no rounding and has no value-dependent failure.

    fromMilliPoints interprets its int argument as thousandths of a point. Its scaled-point result is roundEven of the exact rational input times 8192 divided by 125.

    fromMilliPoints accepts -140737488355328000 through 140737488355327999 inclusive. An input outside that range stops with runtime diagnostic TPZ4004. It does not return Result, wrap, saturate, or clamp.

    Length<Pt>.toMilliPoints returns roundEven of the exact stored count times 125 divided by 8192. Every representable point length has a representable int result.

    roundEven selects the nearest integer and chooses the even integer at an exact tie. Positive and negative ties follow the same rule. Range checking applies to the final rounded value. Intermediate arithmetic must preserve the exact rational value.

    A count of 4096 scaled points converts to 62 millipoints. A count of 12288 converts to 188. Their negatives convert to -62 and -188. One millipoint constructs 66 scaled points and negative one constructs -66.

    Millipoint conversion can lose scaled-point precision. A conversion to millipoints and back need not recover the original length. The maximum point length converts to 140737488355328000 millipoints, which exceeds the fromMilliPoints input range.

    The constructors require int arguments. A float or Length argument is rejected with TPZ5001. Physical conversions and scalar arithmetic do not provide an implicit integer constructor.

    A constructor evaluates its argument once before checking the result range. A receiver conversion evaluates its receiver once. These operations perform no I/O or mutation and read no font or paragraph state.

    A fromMilliPoints overflow is reported at the public constructor call. Earlier argument effects remain visible and later expressions do not execute.

    Why

    Integer conversions use the same to prefix as unit conversions and collection conversions. Scaled points and millipoints remain distinct because they represent different units and rounding boundaries.

    Source evidence

    Convert every physical length unit to scaled points

    Link
    main.tpzConvert every physical length unit to scaled pointsOpen in Playground
    1. import std.io
    2. import std.typeset as ts
    3. let pt = (1pt).toScaledPoints()
    4. let mm = (25.4mm).toScaledPoints()
    5. let cm = (2.54cm).toScaledPoints()
    6. let inch = (1in).toScaledPoints()
    7. io.writeLine("{pt}|{mm}|{cm}|{inch}|{ts.fromScaledPoints(pt) == 1pt}")
    Expected outputexit 0
    65536|4718592|4718592|4718592|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 65536|4718592|4718592|4718592|true
    Source evidence

    Keep the millipoint conversion boundary

    Link
    main.tpzKeep the millipoint conversion boundaryOpen in Playground
    1. import std.io
    2. import std.typeset as ts
    3. let tie = ts.fromScaledPoints(4096).toMilliPoints()
    4. let rounded = ts.fromMilliPoints(1) == ts.fromScaledPoints(66)
    5. io.writeLine("{tie}|{rounded}")
    Expected outputexit 0
    62|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 62|true
    Source evidence

    Reject scaledPoints on `Length<Pt>`

    Link
    main.tpzReject scaledPoints on `Length<Pt>`Open in Playground
    1. let distance = 1pt
    2. distance.scaledPoints()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5006 stdlib-removed-scaled-points at main.tpz:2:10
    Source evidence

    Reject scaledPoints on `Length<Mm>`

    Link
    main.tpzReject scaledPoints on `Length<Mm>`Open in Playground
    1. let distance = 1mm
    2. distance.scaledPoints()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5006 stdlib-removed-scaled-points at main.tpz:2:10
    Source evidence

    Reject scaledPoints on `Length<Cm>`

    Link
    main.tpzReject scaledPoints on `Length<Cm>`Open in Playground
    1. let distance = 1cm
    2. distance.scaledPoints()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5006 stdlib-removed-scaled-points at main.tpz:2:10
    Source evidence

    Reject scaledPoints on `Length<In>`

    Link
    main.tpzReject scaledPoints on `Length<In>`Open in Playground
    1. let distance = 1in
    2. distance.scaledPoints()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5006 stdlib-removed-scaled-points at main.tpz:2:10
    Source evidence

    Resolve font-relative lengths before integer conversion

    Link
    main.tpzResolve font-relative lengths before integer conversionOpen in Playground
    1. let distance = 1em
    2. distance.toScaledPoints()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5006 stdlib-unresolved-em-scaled-points at main.tpz:2:10
    Source evidence

    Read both endpoints of the exact integer representation

    Link
    main.tpzRead both endpoints of the exact integer representationOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let low = ts.fromScaledPoints(-9223372036854775808).toScaledPoints()
    4. let high = ts.fromScaledPoints(9223372036854775807).toScaledPoints()
    5. io.writeLine("{low}|{high}")
    Expected outputexit 0
    -9223372036854775808|9223372036854775807
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. -9223372036854775808|9223372036854775807
    Source evidence

    Construct the millipoint endpoints and keep signed rounding

    Link
    main.tpzConstruct the millipoint endpoints and keep signed roundingOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let low = ts.fromMilliPoints(-140737488355328000)
    4. let high = ts.fromMilliPoints(140737488355327999)
    5. let negative = ts.fromMilliPoints(-1)
    6. let lowGrid = ts.fromScaledPoints(-9223372036854775808).toMilliPoints()
    7. let highGrid = ts.fromScaledPoints(9223372036854775807).toMilliPoints()
    8. io.writeLine("{low == -140737488355328pt}|{high == ts.fromScaledPoints(9223372036854775742)}|{negative == ts.fromScaledPoints(-66)}")
    9. io.writeLine("{lowGrid}|{highGrid}")
    Expected outputexit 0
    true|true|true
    -140737488355328000|140737488355328000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|true|true
    2. -140737488355328000|140737488355328000
    Source evidence

    Round both signs and both sides of a millipoint tie

    Link
    main.tpzRound both signs and both sides of a millipoint tieOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let below = ts.fromScaledPoints(4095).toMilliPoints()
    4. let tie = ts.fromScaledPoints(4096).toMilliPoints()
    5. let above = ts.fromScaledPoints(4097).toMilliPoints()
    6. let negativeBelow = ts.fromScaledPoints(-4095).toMilliPoints()
    7. let negativeTie = ts.fromScaledPoints(-4096).toMilliPoints()
    8. let negativeAbove = ts.fromScaledPoints(-4097).toMilliPoints()
    9. let upperTie = ts.fromScaledPoints(12288).toMilliPoints()
    10. let negativeUpperTie = ts.fromScaledPoints(-12288).toMilliPoints()
    11. io.writeLine("{below}|{tie}|{above}|{negativeBelow}|{negativeTie}|{negativeAbove}|{upperTie}|{negativeUpperTie}")
    Expected outputexit 0
    62|62|63|-62|-62|-63|188|-188
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 62|62|63|-62|-62|-63|188|-188
    Source evidence

    Reject a millipoint input above the representable range

    Link
    main.tpzReject a millipoint input above the representable rangeOpen in Playground
    1. import std.typeset as ts
    2. ts.fromMilliPoints(140737488355328000)
    Expected outputNonzero exit
    No stdout.
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Nonzero exit.

    • TPZ4004 stdlib-length-unrepresentable at main.tpz:2:1
    Source evidence

    Reject a millipoint input below the representable range

    Link
    main.tpzReject a millipoint input below the representable rangeOpen in Playground
    1. import std.typeset as ts
    2. ts.fromMilliPoints(-140737488355328001)
    Expected outputNonzero exit
    No stdout.
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Nonzero exit.

    • TPZ4004 stdlib-length-unrepresentable at main.tpz:2:1
    Source evidence

    Require an int for fromScaledPoints

    Link
    main.tpzRequire an int for fromScaledPointsOpen in Playground
    1. import std.typeset as ts
    2. ts.fromScaledPoints(1.0)
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-length-int-required at main.tpz:2:21
    Source evidence

    Require an int for fromMilliPoints

    Link
    main.tpzRequire an int for fromMilliPointsOpen in Playground
    1. import std.typeset as ts
    2. ts.fromMilliPoints(1.0)
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-length-int-required at main.tpz:2:20
    Source evidence

    Diagnostics

    Link

    Unknown member or field

    Integer overflow

    TPZ5001

    Give public typesetting distances a point-length type

    Link
    Ruletopaz.stdlib.contract.typeset-length-pt.rule

    Give public typesetting distances a point-length type

    Public measurement fields use Length<Pt>. Widths, advances, tracking, stretch, shrink, physical targets, insets, font sizes, and optional width limits keep that type across input and output records. Explicit Length conversion functions remain the integer interchange boundary.

    Box.width, Box.trackingAfter, Glue.width, Glue.trackingAfter, Glue.stretch, Glue.shrink, and Penalty.width have type Length<Pt>.

    BreakCluster.advance, BreakCluster.trackingAfter, BreakPolicy.narrowestWordTarget, and BreakPolicy.bodyWordTarget have type Length<Pt>.

    LatinSpacing.fontSizePt, JustificationPolicy.maxAdjustment, and JustificationPolicy.maxGapAdjustment have type Length<Pt>.

    LineGeometry.bodyTarget, LineGeometry.firstTarget, and LineGeometry.dropCapInset have type Length<Pt>.

    PlanLine.target, PlanLine.rawTextWidth, PlanLine.trimmedTextWidth, PlanLine.costTextWidth, and PlanLine.selectedPenaltyWidth have type Length<Pt>. PlanLine.postValidationWidthLimit has type Option<Length<Pt>>.

    An int, a float, or a Length with another unit marker is not an implicit point-length argument. Convert physical units explicitly with toPt() before constructing a typesetting record.

    Indices, counts, penalties, costs, and dimensionless ratios retain their declared types. LatinSpacing.maxLetterSpacingEm is a relative Length<Em>. Resolve it to Length<Pt> with fontSizePt before selecting the computation grid.

    Paragraph.grid selects quantization at the computation boundary. ScaledPoint uses the exact scaled-point integer. MilliPoint uses the existing nearest-even conversion to millipoints. The selected grid never changes the physical interpretation of a public Length<Pt> value.

    Returned distances are converted from the computation grid into Length<Pt> at the output boundary. ParagraphPlan.grid reports the computation policy and is not needed to interpret its distances.

    The public type change preserves the selected cost model. FixedPoint retains its MilliPoint computation requirement. Word-space and CJK ratio calculations keep their independent floor rules. The relative letter-spacing distance is resolved and quantized through the shared Length conversions before it contributes to capacity.

    The public line-badness entry point also receives width, target, and hyphen advance as Length<Pt>. It converts those distances to millipoints at entry and preserves its existing integer cost calculation.

    Why

    A physical distance carries the same Length<Pt> type in application geometry and paragraph planning. The type prevents a grid selector or an unlabelled scalar from silently changing the distance.

    Source evidence

    Use point lengths in Box

    Link
    main.tpzUse point lengths in BoxOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.Box = ts.Box {
    3. unitIndex: 0,
    4. text: "A",
    5. width: 12pt,
    6. trackingAfter: 0pt
    7. }
    8. let distance: Length<Pt> = value.width
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in Box

    Link
    main.tpzReject untyped physical distances in BoxOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.Box = ts.Box {
    3. unitIndex: 0,
    4. text: "A",
    5. width: 12,
    6. trackingAfter: 0
    7. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:5:10
    Source evidence

    Use point lengths in Glue

    Link
    main.tpzUse point lengths in GlueOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.Glue = ts.Glue {
    3. unitIndex: 1,
    4. text: " ",
    5. width: 3pt,
    6. trackingAfter: 0pt,
    7. stretch: 1pt,
    8. shrink: 1pt
    9. }
    10. let distance: Length<Pt> = value.width
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in Glue

    Link
    main.tpzReject untyped physical distances in GlueOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.Glue = ts.Glue {
    3. unitIndex: 1,
    4. text: " ",
    5. width: 3,
    6. trackingAfter: 0,
    7. stretch: 1,
    8. shrink: 1
    9. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:5:10
    Source evidence

    Use point lengths in Penalty

    Link
    main.tpzUse point lengths in PenaltyOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.Penalty = ts.Penalty {
    3. unitEnd: 1,
    4. width: 0pt,
    5. rule: ts.BreakRule.Forced(),
    6. kind: ts.BreakKind.ForcedEnd(),
    7. flagged: false
    8. }
    9. let distance: Length<Pt> = value.width
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in Penalty

    Link
    main.tpzReject untyped physical distances in PenaltyOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.Penalty = ts.Penalty {
    3. unitEnd: 1,
    4. width: 0,
    5. rule: ts.BreakRule.Forced(),
    6. kind: ts.BreakKind.ForcedEnd(),
    7. flagged: false
    8. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:4:10
    Source evidence

    Use point lengths in BreakPolicy

    Link
    main.tpzUse point lengths in BreakPolicyOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.BreakPolicy = ts.BreakPolicy {
    3. mode: ts.BreakMode.Character(),
    4. rules: ts.KinsokuRules.Unicode16Ja(),
    5. forbidBefore: [],
    6. forbidAfter: [],
    7. boundaryAllowed: [true],
    8. generatedHyphenEnds: [],
    9. protectFittingLatin: true,
    10. preserveShortWords: true,
    11. narrowestWordTarget: 12pt,
    12. bodyWordTarget: 24pt
    13. }
    14. let distance: Length<Pt> = value.narrowestWordTarget
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in BreakPolicy

    Link
    main.tpzReject untyped physical distances in BreakPolicyOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.BreakPolicy = ts.BreakPolicy {
    3. mode: ts.BreakMode.Character(),
    4. rules: ts.KinsokuRules.Unicode16Ja(),
    5. forbidBefore: [],
    6. forbidAfter: [],
    7. boundaryAllowed: [true],
    8. generatedHyphenEnds: [],
    9. protectFittingLatin: true,
    10. preserveShortWords: true,
    11. narrowestWordTarget: 12,
    12. bodyWordTarget: 24
    13. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:11:24
    Source evidence

    Use point lengths in BreakCluster

    Link
    main.tpzUse point lengths in BreakClusterOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.BreakCluster = ts.BreakCluster {
    3. text: "A",
    4. advance: 12pt,
    5. trackingAfter: 0pt
    6. }
    7. let distance: Length<Pt> = value.advance
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in BreakCluster

    Link
    main.tpzReject untyped physical distances in BreakClusterOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.BreakCluster = ts.BreakCluster {
    3. text: "A",
    4. advance: 12,
    5. trackingAfter: 0
    6. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:4:12
    Source evidence

    Use point lengths in LatinSpacing

    Link
    main.tpzUse point lengths in LatinSpacingOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.LatinSpacing = ts.LatinSpacing {
    3. minWordSpaceRatio: 0.8,
    4. maxWordSpaceRatio: 1.33,
    5. maxLetterSpacingEm: 0.01em,
    6. fontSizePt: 12pt
    7. }
    8. let distance: Length<Pt> = value.fontSizePt
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in LatinSpacing

    Link
    main.tpzReject untyped physical distances in LatinSpacingOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.LatinSpacing = ts.LatinSpacing {
    3. minWordSpaceRatio: 0.8,
    4. maxWordSpaceRatio: 1.33,
    5. maxLetterSpacingEm: 0.01em,
    6. fontSizePt: 12.0
    7. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:6:15
    Source evidence

    Use point lengths in JustificationPolicy

    Link
    main.tpzUse point lengths in JustificationPolicyOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.JustificationPolicy = ts.JustificationPolicy {
    3. mode: ts.JustificationMode.Unjustified(),
    4. minGapCount: 0,
    5. minLineWidthRatioM: 0,
    6. minFinalLineWidthRatioM: None(),
    7. maxAdjustment: 12pt,
    8. maxGapAdjustment: 3pt,
    9. maxGapRatioR: 0,
    10. latinSpacing: None(),
    11. cjkStretchRatio: 0.0
    12. }
    13. let distance: Length<Pt> = value.maxAdjustment
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in JustificationPolicy

    Link
    main.tpzReject untyped physical distances in JustificationPolicyOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.JustificationPolicy = ts.JustificationPolicy {
    3. mode: ts.JustificationMode.Unjustified(),
    4. minGapCount: 0,
    5. minLineWidthRatioM: 0,
    6. minFinalLineWidthRatioM: None(),
    7. maxAdjustment: 12,
    8. maxGapAdjustment: 3,
    9. maxGapRatioR: 0,
    10. latinSpacing: None(),
    11. cjkStretchRatio: 0.0
    12. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:7:18
    Source evidence

    Use point lengths in LineGeometry

    Link
    main.tpzUse point lengths in LineGeometryOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.LineGeometry = ts.LineGeometry {
    3. bodyTarget: 24pt,
    4. firstTarget: 20pt,
    5. dropCapSpan: 0,
    6. dropCapInset: 0pt
    7. }
    8. let distance: Length<Pt> = value.bodyTarget
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in LineGeometry

    Link
    main.tpzReject untyped physical distances in LineGeometryOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.LineGeometry = ts.LineGeometry {
    3. bodyTarget: 24,
    4. firstTarget: 20,
    5. dropCapSpan: 0,
    6. dropCapInset: 0
    7. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:3:15
    Source evidence

    Use point lengths in PlanLine

    Link
    main.tpzUse point lengths in PlanLineOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.PlanLine = ts.PlanLine {
    3. start: 0,
    4. endExclusive: 1,
    5. trimmedEndExclusive: 1,
    6. displayTextScalarLength: 1,
    7. kind: ts.BreakKind.ForcedEnd(),
    8. target: 12pt,
    9. rawTextWidth: 10pt,
    10. trimmedTextWidth: 10pt,
    11. costTextWidth: 10pt,
    12. selectedPenaltyWidth: 0pt,
    13. postValidationWidthLimit: Some(13pt),
    14. cost: 0,
    15. adjustmentRatioR: 0
    16. }
    17. let distance: Length<Pt> = value.target
    18. let limit: Option<Length<Pt>> = value.postValidationWidthLimit
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject untyped physical distances in PlanLine

    Link
    main.tpzReject untyped physical distances in PlanLineOpen in Playground
    1. import std.typeset as ts
    2. let value: ts.PlanLine = ts.PlanLine {
    3. start: 0,
    4. endExclusive: 1,
    5. trimmedEndExclusive: 1,
    6. displayTextScalarLength: 1,
    7. kind: ts.BreakKind.ForcedEnd(),
    8. target: 12,
    9. rawTextWidth: 10,
    10. trimmedTextWidth: 10,
    11. costTextWidth: 10,
    12. selectedPenaltyWidth: 0,
    13. postValidationWidthLimit: Some(13),
    14. cost: 0,
    15. adjustmentRatioR: 0
    16. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:8:11
    Source evidence

    Pass typed distances to the line-cost function

    Link
    main.tpzPass typed distances to the line-cost functionOpen in Playground
    1. import std.typeset as ts
    2. let value = ts.lineBadness(20pt, 20pt, 4, 1, ts.BreakKind.ForcedEnd(), false, 0, 0pt)
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject raw distances at the line-cost boundary

    Link
    main.tpzReject raw distances at the line-cost boundaryOpen in Playground
    1. import std.typeset as ts
    2. let value = ts.lineBadness(20, 20, 4, 1, ts.BreakKind.ForcedEnd(), false, 0, 0)
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:2:28
    Source evidence

    Name the CJK cluster break category

    Link
    Ruletopaz.stdlib.contract.cjk-cluster.rule

    Name the CJK cluster break category

    BreakKind.CjkCluster() constructs the variant for the existing CJK and East Asian cluster classification. BreakKind.KoreanCluster() is absent and rejected.

    Renaming this variant preserves the classification predicates and the effect of the selected Unicode, locale, custom punctuation, and shaping-boundary policies.

    A CjkCluster category does not itself permit a break. classifyBreaks returns the opportunities admitted by its supplied policy.

    Why

    A break category names the text it classifies. CjkCluster covers the existing Chinese, Japanese, and Korean classification path and matches the module's CjkInterCharacter vocabulary. Locale policy continues to determine where a break is allowed.

    Source evidence

    Use the CJK cluster variant

    Link
    main.tpzUse the CJK cluster variantOpen in Playground
    1. import std.typeset as ts
    2. let kind: ts.BreakKind = ts.BreakKind.CjkCluster()
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Reject the former KoreanCluster name

    Link
    main.tpzReject the former KoreanCluster nameOpen in Playground
    1. import std.typeset as ts
    2. let kind = ts.BreakKind.KoreanCluster()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5006 stdlib-removed-korean-cluster at main.tpz:2:12
    Source evidence

    Compute fixed-point line badness from point lengths

    Link
    Ruletopaz.stdlib.contract.line-badness.rule

    Compute fixed-point line badness from point lengths

    lineBadness(width: Length<Pt>, target: Length<Pt>, charCount: int, spaces: int, kind: BreakKind, isLast: bool, justificationB: int, hyphenAdvance: Length<Pt>) -> Result<int, TypesetError> is the public entry point. lineBadnessMpt is absent and importing it is rejected with TPZ3009.

    The three distances require Length<Pt>. A raw scalar or another unit marker is rejected with TPZ5001. Width and hyphen advance may be negative. justificationB accepts every int and can make the resulting cost negative.

    Evaluate all eight argument expressions once in source order before entering the function. Convert width, target, and hyphenAdvance to millipoints in that order with the shared nearest-even rule.

    The converted target must be positive. charCount and spaces must each be nonnegative and need not satisfy a relation to each other. Invalid inputs return Err containing TypesetError.InvalidInput before cost arithmetic. The explanatory message text is not fixed.

    Let w, t, and h be the converted distances, c the character count, s the space count, and j the supplied justification cost. Let B be 1,000,000 and R be 100,000,000.

    For a nonnegative n and positive d, H gives n divided by d rounded to the nearest integer with ties upward. H describes cost rounding. Physical Length conversion keeps its separate nearest-even rule.

    The following sequence uses checked int operations in the stated order. A failing intermediate operation stops with runtime diagnostic TPZ4004. It does not return TypesetError or replace overflow with a bounded cost.

    Set effective to w + h for LatinSoftHyphen and to w for every other kind. Set slack to t - effective and absolute to the absolute value of slack.

    Set raw to 4 R when absolute is at least 4 t. Otherwise set raw to absolute * R / t with integer division. This explicit ratio cap does not change a supplied length.

    Set capacity to 100 + s * 45 when slack is nonnegative and to 100 + s * 25 otherwise. Set ratio = H(raw * 100, capacity), then q = H(ratio * ratio, R), then cost = H(q * (1000 * B), R).

    If isLast is true, replace cost with H(cost * 2, 5). Add 500 B when isLast is true and `w 100 < t * 52`. The strict comparison uses the original converted width and also applies when charCount is zero.

    If slack is negative, add 20000 * B + H(20000 * B * ratio, R). Add 65 B for LatinHyphen or LatinSoftHyphen. Add 50000 B for HardFallback. The other break kinds add no break penalty.

    If c is at most two, add 275 B. Then add 10 B and j in that order and return the result in Ok. The hyphenAdvance input affects width for LatinSoftHyphen and is ignored for the other kinds.

    An exact fit with more than two characters, no special break penalty, and zero justification cost returns 10000000. The documented 90 point line against a 100 point target with nine characters and two spaces returns 12770080.

    The function performs no I/O, changes no input, and reads no font, paragraph, or global grid state. Effects from argument evaluation remain visible on InvalidInput or a later arithmetic fault.

    An arithmetic fault identifies the public lineBadness call as its primary source location. Argument effects already performed remain visible and later expressions do not run.

    Why

    One typed entry point fixes the physical meaning before scoring a line. Explicit intermediate rounding makes costs reproducible. The same last-line width condition applies to every valid character count, including an empty line.

    Source evidence

    Compute a line cost from point lengths

    Link
    main.tpzCompute a line cost from point lengthsOpen in Playground
    1. import std.io
    2. import std.typeset as ts
    3. let cost = ts.lineBadness(20pt, 20pt, 4, 1, ts.BreakKind.ForcedEnd(), false, 0, 0pt)
    4. match (cost) {
    5. case Ok(value) => io.writeLine("{value}")
    6. case Err(_) => io.writeLine("unexpected input error")
    7. }
    Expected outputexit 0
    10000000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 10000000
    Source evidence

    Reject the former millipoint function name

    Link
    main.tpzReject the former millipoint function nameOpen in Playground
    1. import std.typeset { lineBadnessMpt }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during resolve. Nonzero exit.

    • TPZ3009 selected-name-not-exported at main.tpz:1:22
    Source evidence

    Preserve the published fixed-point costs

    Link
    main.tpzPreserve the published fixed-point costsOpen in Playground
    1. import std.typeset as ts
    2. import std.test
    3. import std.io
    4. function show(value: Result<int, ts.TypesetError>) -> () {
    5. let cost = test.assertOk(value)
    6. io.writeLine("{cost}")
    7. }
    8. show(ts.lineBadness(100pt, 100pt, 10, 2, ts.BreakKind.LatinSpace(), false, 0, 0pt))
    9. show(ts.lineBadness(90pt, 100pt, 9, 2, ts.BreakKind.LatinSpace(), false, 0, 0pt))
    10. show(ts.lineBadness(100pt, 100pt, 10, 2, ts.BreakKind.LatinHyphen(), false, 0, 0pt))
    11. show(ts.lineBadness(40pt, 100pt, 4, 0, ts.BreakKind.ForcedEnd(), true, 0, 0pt))
    Expected outputexit 0
    10000000
    12770080
    75000000
    654000000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 10000000
    2. 12770080
    3. 75000000
    4. 654000000
    Source evidence

    Apply break penalties and the small character count boundary

    Link
    main.tpzApply break penalties and the small character count boundaryOpen in Playground
    1. import std.typeset as ts
    2. import std.test
    3. import std.io
    4. function show(value: Result<int, ts.TypesetError>) -> () {
    5. let cost = test.assertOk(value)
    6. io.writeLine("{cost}")
    7. }
    8. show(ts.lineBadness(100pt, 100pt, 3, 0, ts.BreakKind.CjkCluster(), false, 0, 0pt))
    9. show(ts.lineBadness(100pt, 100pt, 3, 0, ts.BreakKind.ClosingPunctuation(), false, 0, 0pt))
    10. show(ts.lineBadness(100pt, 100pt, 3, 0, ts.BreakKind.HardFallback(), false, 0, 0pt))
    11. show(ts.lineBadness(100pt, 100pt, 2, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt))
    12. show(ts.lineBadness(99pt, 100pt, 9, 0, ts.BreakKind.LatinSoftHyphen(), false, 0, 1pt))
    13. show(ts.lineBadness(99pt, 100pt, 9, 0, ts.BreakKind.LatinHyphen(), false, 0, 1pt))
    Expected outputexit 0
    10000000
    10000000
    50010000000
    285000000
    75000000
    75100000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 10000000
    2. 10000000
    3. 50010000000
    4. 285000000
    5. 75000000
    6. 75100000
    Source evidence

    Use a strict last-line width threshold

    Link
    main.tpzUse a strict last-line width thresholdOpen in Playground
    1. import std.typeset as ts
    2. import std.test
    3. import std.io
    4. function show(value: Result<int, ts.TypesetError>) -> () {
    5. let cost = test.assertOk(value)
    6. io.writeLine("{cost}")
    7. }
    8. show(ts.lineBadness(52pt, 100pt, 4, 0, ts.BreakKind.ForcedEnd(), true, 0, 0pt))
    9. show(ts.lineBadness(51pt, 100pt, 4, 0, ts.BreakKind.ForcedEnd(), true, 0, 0pt))
    Expected outputexit 0
    102160000
    606040000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 102160000
    2. 606040000
    Source evidence

    Apply the last-line width condition to zero characters

    Link
    main.tpzApply the last-line width condition to zero charactersOpen in Playground
    1. import std.typeset as ts
    2. import std.test
    3. import std.io
    4. function show(value: Result<int, ts.TypesetError>) -> () {
    5. let cost = test.assertOk(value)
    6. io.writeLine("{cost}")
    7. }
    8. show(ts.lineBadness(0pt, 100pt, 0, 0, ts.BreakKind.ForcedEnd(), true, 0, 0pt))
    Expected outputexit 0
    1185000000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1185000000
    Source evidence

    Keep signed inputs and cap the scoring ratio explicitly

    Link
    main.tpzKeep signed inputs and cap the scoring ratio explicitlyOpen in Playground
    1. import std.typeset as ts
    2. import std.test
    3. import std.io
    4. function show(value: Result<int, ts.TypesetError>) -> () {
    5. let cost = test.assertOk(value)
    6. io.writeLine("{cost}")
    7. }
    8. show(ts.lineBadness(100pt, 100pt, 3, 0, ts.BreakKind.ForcedEnd(), false, -10000001, 0pt))
    9. show(ts.lineBadness(-1000pt, 100pt, 3, 0, ts.BreakKind.ForcedEnd(), false, 0, -1pt))
    10. show(ts.lineBadness(500pt, 100pt, 3, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt))
    11. show(ts.lineBadness(600pt, 100pt, 3, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt))
    Expected outputexit 0
    -1
    16010000000
    116010000000
    116010000000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. -1
    2. 16010000000
    3. 116010000000
    4. 116010000000
    Source evidence

    Return InvalidInput for the target and count boundaries

    Link
    main.tpzReturn InvalidInput for the target and count boundariesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. for result in ([
    4. ts.lineBadness(1pt, 0pt, 3, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt),
    5. ts.lineBadness(1pt, -1pt, 3, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt),
    6. ts.lineBadness(1pt, 1pt, -1, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt),
    7. ts.lineBadness(1pt, 1pt, 3, -1, ts.BreakKind.ForcedEnd(), false, 0, 0pt)
    8. ]) {
    9. match (result) {
    10. case Err(InvalidInput(_)) => io.writeLine("invalid input")
    11. case _ => io.writeLine("unexpected result")
    12. }
    13. }
    Expected outputexit 0
    invalid input
    invalid input
    invalid input
    invalid input
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. invalid input
    2. invalid input
    3. invalid input
    4. invalid input
    Source evidence

    Validate target positivity after millipoint conversion

    Link
    main.tpzValidate target positivity after millipoint conversionOpen in Playground
    1. import std.typeset as ts
    2. import std.test
    3. import std.io
    4. function show(value: Result<int, ts.TypesetError>) -> () {
    5. let cost = test.assertOk(value)
    6. io.writeLine("{cost}")
    7. }
    8. let rejected = ts.lineBadness(0pt, ts.fromScaledPoints(32), 3, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt)
    9. match (rejected) {
    10. case Err(InvalidInput(_)) => io.writeLine("rounded target is zero")
    11. case _ => io.writeLine("unexpected result")
    12. }
    13. show(ts.lineBadness(ts.fromScaledPoints(33), ts.fromScaledPoints(33), 3, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt))
    14. show(ts.lineBadness(ts.fromScaledPoints(4096), ts.fromMilliPoints(62), 3, 0, ts.BreakKind.ForcedEnd(), false, 0, 0pt))
    Expected outputexit 0
    rounded target is zero
    10000000
    10000000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. rounded target is zero
    2. 10000000
    3. 10000000
    Source evidence

    Evaluate every argument before input validation

    Link
    main.tpzEvaluate every argument before input validationOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function length(label: string, value: Length<Pt>) -> Length<Pt> {
    4. io.writeLine(label)
    5. value
    6. }
    7. function count(label: string, value: int) -> int {
    8. io.writeLine(label)
    9. value
    10. }
    11. function kind(value: ts.BreakKind) -> ts.BreakKind {
    12. io.writeLine("kind")
    13. value
    14. }
    15. function last(value: bool) -> bool {
    16. io.writeLine("last")
    17. value
    18. }
    19. let result = ts.lineBadness(
    20. length("width", 1pt), length("target", 0pt),
    21. count("chars", 3), count("spaces", 0),
    22. kind(ts.BreakKind.ForcedEnd()), last(false),
    23. count("justification", 0), length("hyphen", 0pt)
    24. )
    25. match (result) {
    26. case Err(InvalidInput(_)) => io.writeLine("invalid input")
    27. case _ => io.writeLine("unexpected result")
    28. }
    Expected outputexit 0
    width
    target
    chars
    spaces
    kind
    last
    justification
    hyphen
    invalid input
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. width
    2. target
    3. chars
    4. spaces
    5. kind
    6. last
    7. justification
    8. hyphen
    9. invalid input
    Source evidence

    Stop on checked cost overflow

    Link
    main.tpzStop on checked cost overflowOpen in Playground
    1. import std.typeset as ts
    2. ts.lineBadness(1pt, 1pt, 3, 0, ts.BreakKind.ForcedEnd(), false, 9223372036854775807, 0pt)
    Expected outputNonzero exit
    No stdout.
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Nonzero exit.

    • TPZ4004 diagnostic-tpz4004 at main.tpz:2:1
    Source evidence

    Diagnostics

    Link

    Name is not exported

    Resolve relative letter spacing through Length

    Link
    Ruletopaz.stdlib.contract.latin-spacing-em.rule

    Resolve relative letter spacing through Length

    LatinSpacing stores minWordSpaceRatio and maxWordSpaceRatio as dimensionless float values, maxLetterSpacingEm as Length<Em>, and fontSizePt as Length<Pt>. There is no implicit scalar or cross-unit conversion.

    Record construction and field reads store and return the supplied values. They do not validate a paragraph policy or resolve the relative length. Field expressions follow ordinary source-order evaluation once.

    A paragraph validates every supplied LatinSpacing record before planning. This validation also applies in Unjustified mode and with the KnuthPlass cost model.

    minWordSpaceRatio must be finite and between 0.8 and 1.0 inclusive. maxWordSpaceRatio must be finite and between 1.0 and 1.33 inclusive. Invalid ratios return Err containing TypesetError.InvalidPolicy.

    maxLetterSpacingEm must be between 0em and 0.015em inclusive on the stored relative grid. The upper bound is 983 em ticks. A negative tick or 984 ticks returns Err containing TypesetError.InvalidPolicy.

    fontSizePt must have a strictly positive scaled-point count. A one-scaled-point font is valid even when the paragraph uses millipoints. Zero and negative fonts return Err containing TypesetError.InvalidPolicy, including when letter spacing is zero.

    InvalidPolicy carries explanatory text. Its exact message text and the order among independent invalid policy fields are not fixed by this contract.

    Resolve the stored maximum with maxLetterSpacingEm.resolve(fontSizePt). The stored relative count is multiplied by the physical font count and divided by 65,536 with one nearest-even rounding to scaled points. No ambient font is read.

    Convert the resolved result to the selected computation grid. ScaledPoint preserves its integer count. MilliPoint applies the shared nearest-even conversion. Every eligible gap receives this rounded bound before multiplication by the gap count.

    An eligible Latin letter gap lies between adjacent retained text units. Each unit must contain an alphabetic scalar and every scalar must be in U+0041 through U+024F, U+0300 through U+036F, or U+1E00 through U+1EFF. Paragraph trimming determines the retained units.

    The retained line uses this capacity path when it contains a CJK unit, or contains a Latin letter unit and no other alphabetic unit. A non-Latin unit containing an alphabetic scalar counts as another alphabetic unit. Paragraph classification determines CJK units.

    Let S be the sum of the grid widths of retained units whose text is U+0020. Word shrink capacity is floor(float(S) * (1.0 - minWordSpaceRatio)). Word stretch capacity is floor(float(S) * (maxWordSpaceRatio - 1.0)). Each expression keeps that binary64 evaluation order and its independent floor.

    The letter stretch contribution is the eligible gap count times the quantized relative limit. CJK capacity retains its separate floor rule. Combining these contributions does not repeat font resolution or round their total as one relative length.

    Unjustified mode does not apply spacing capacity. KnuthPlass obtains stretch and shrink from glue after the shared policy validation. FixedPoint uses the validated Latin spacing in its justification capacity and requires MilliPoint.

    Resolution, integer accumulation, and capacity arithmetic use their checked numeric contracts. Overflow stops with TPZ4004 and does not return InvalidPolicy. Validation and conversion perform no I/O and do not mutate the record.

    For 0.01em at 12pt, 655 relative ticks resolve to 7,860 scaled points and then 120 millipoints. For 0.0078125em at 24pt, each gap receives 188 millipoints. Two such gaps provide 376 millipoints.

    Why

    One relative length model gives a letter-spacing limit an explicit font and unit. Validating the stored policy before planning separates field construction from recoverable policy errors. Per-gap quantization makes the permitted adjustment reproducible.

    Source evidence

    Store a relative spacing length and an explicit point font size

    Link
    main.tpzStore a relative spacing length and an explicit point font sizeOpen in Playground
    1. import std.typeset as ts
    2. let value = ts.LatinSpacing {
    3. minWordSpaceRatio: 0.8,
    4. maxWordSpaceRatio: 1.33,
    5. maxLetterSpacingEm: 0.01em,
    6. fontSizePt: 12pt
    7. }
    8. let relative: Length<Em> = value.maxLetterSpacingEm
    9. let physical: Length<Pt> = relative.resolve(value.fontSizePt)
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Resolve the stored spacing before converting to the computation grid

    Link
    main.tpzResolve the stored spacing before converting to the computation gridOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let spacing = ts.LatinSpacing {
    4. minWordSpaceRatio: 0.8,
    5. maxWordSpaceRatio: 1.33,
    6. maxLetterSpacingEm: 0.01em,
    7. fontSizePt: 12pt
    8. }
    9. let limit = spacing.maxLetterSpacingEm.resolve(spacing.fontSizePt)
    10. let exact = limit == ts.fromScaledPoints(7860)
    11. let millipoints = limit.toMilliPoints()
    12. io.writeLine("{exact}|{millipoints}")
    Expected outputexit 0
    true|120
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|120
    Source evidence

    Apply nearest-even rounding at each typed value boundary

    Link
    main.tpzApply nearest-even rounding at each typed value boundaryOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let lowTie = (0.5em).resolve(ts.fromScaledPoints(3)) == ts.fromScaledPoints(2)
    4. let highTie = (0.5em).resolve(ts.fromScaledPoints(5)) == ts.fromScaledPoints(2)
    5. let literalGrid = (0.01em).resolve(12pt) == ts.fromScaledPoints(7860)
    6. let gridLow = (0.5em).resolve(0.125pt).toMilliPoints()
    7. let gridHigh = (0.5em).resolve(0.375pt).toMilliPoints()
    8. io.writeLine("{lowTie}|{highTie}|{literalGrid}|{gridLow}|{gridHigh}")
    Expected outputexit 0
    true|true|true|62|188
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|true|true|62|188
    Source evidence

    Reject an untyped multiplier in the relative-length field

    Link
    main.tpzReject an untyped multiplier in the relative-length fieldOpen in Playground
    1. import std.typeset as ts
    2. let spacing = ts.LatinSpacing {
    3. minWordSpaceRatio: 0.8,
    4. maxWordSpaceRatio: 1.33,
    5. maxLetterSpacingEm: 0.01,
    6. fontSizePt: 12pt
    7. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-relative-distance-requires-em at main.tpz:5:23
    Source evidence

    Reject a physical length where an em length is required

    Link
    main.tpzReject a physical length where an em length is requiredOpen in Playground
    1. import std.typeset as ts
    2. let spacing = ts.LatinSpacing {
    3. minWordSpaceRatio: 0.8,
    4. maxWordSpaceRatio: 1.33,
    5. maxLetterSpacingEm: 0.12pt,
    6. fontSizePt: 12pt
    7. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-relative-distance-requires-em at main.tpz:5:23
    Source evidence

    Reject treating the stored relative length as a float

    Link
    main.tpzReject treating the stored relative length as a floatOpen in Playground
    1. import std.typeset as ts
    2. function multiplier(value: ts.LatinSpacing) -> float {
    3. value.maxLetterSpacingEm
    4. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-relative-distance-is-not-float at main.tpz:3:3
    Source evidence

    Admit the inclusive ratio and font bounds

    Link
    main.tpzAdmit the inclusive ratio and font boundsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function paragraph(grid: ts.DistanceGrid, spacing: Option<ts.LatinSpacing>) -> ts.Paragraph {
    4. ts.Paragraph {
    5. grid: grid,
    6. items: [
    7. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "abc", width: 10pt, trackingAfter: 0pt }),
    8. ts.Item.PenaltyItem(ts.Penalty {
    9. unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(),
    10. kind: ts.BreakKind.ForcedEnd(), flagged: false
    11. })
    12. ],
    13. geometry: ts.LineGeometry {
    14. bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt
    15. },
    16. justification: ts.JustificationPolicy {
    17. mode: ts.JustificationMode.Unjustified(),
    18. minGapCount: 0, minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    19. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    20. latinSpacing: spacing, cjkStretchRatio: 0.0
    21. },
    22. costModel: ts.CostModel.FixedPoint(),
    23. knuthPlass: ts.KnuthPlassPolicy {
    24. toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0,
    25. flaggedDemerit: 0, finalFlaggedDemerit: 0
    26. }
    27. }
    28. }
    29. for value in ([ts.LatinSpacing { minWordSpaceRatio: 0.8, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: 0em, fontSizePt: ts.fromScaledPoints(1) },
    30. ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 1.33, maxLetterSpacingEm: 0.015em, fontSizePt: 12pt }]) {
    31. match (ts.breakParagraph(paragraph(ts.DistanceGrid.MilliPoint(), Some(value)))) {
    32. case Ok(_) => io.writeLine("valid policy")
    33. case Err(_) => io.writeLine("unexpected error")
    34. }
    35. }
    Expected outputexit 0
    valid policy
    valid policy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. valid policy
    2. valid policy
    Source evidence

    Reject ratios just outside every allowed endpoint

    Link
    main.tpzReject ratios just outside every allowed endpointOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function paragraph(grid: ts.DistanceGrid, spacing: Option<ts.LatinSpacing>) -> ts.Paragraph {
    4. ts.Paragraph {
    5. grid: grid,
    6. items: [
    7. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "abc", width: 10pt, trackingAfter: 0pt }),
    8. ts.Item.PenaltyItem(ts.Penalty {
    9. unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(),
    10. kind: ts.BreakKind.ForcedEnd(), flagged: false
    11. })
    12. ],
    13. geometry: ts.LineGeometry {
    14. bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt
    15. },
    16. justification: ts.JustificationPolicy {
    17. mode: ts.JustificationMode.Unjustified(),
    18. minGapCount: 0, minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    19. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    20. latinSpacing: spacing, cjkStretchRatio: 0.0
    21. },
    22. costModel: ts.CostModel.FixedPoint(),
    23. knuthPlass: ts.KnuthPlassPolicy {
    24. toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0,
    25. flaggedDemerit: 0, finalFlaggedDemerit: 0
    26. }
    27. }
    28. }
    29. for value in ([ts.LatinSpacing { minWordSpaceRatio: 0.7999, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: 0em, fontSizePt: 12pt },
    30. ts.LatinSpacing { minWordSpaceRatio: 1.0001, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: 0em, fontSizePt: 12pt },
    31. ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 0.9999, maxLetterSpacingEm: 0em, fontSizePt: 12pt },
    32. ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 1.3301, maxLetterSpacingEm: 0em, fontSizePt: 12pt }]) {
    33. match (ts.breakParagraph(paragraph(ts.DistanceGrid.MilliPoint(), Some(value)))) {
    34. case Err(InvalidPolicy(_)) => io.writeLine("invalid ratio")
    35. case _ => io.writeLine("unexpected result")
    36. }
    37. }
    Expected outputexit 0
    invalid ratio
    invalid ratio
    invalid ratio
    invalid ratio
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. invalid ratio
    2. invalid ratio
    3. invalid ratio
    4. invalid ratio
    Source evidence

    Reject negative spacing and nonpositive fonts

    Link
    main.tpzReject negative spacing and nonpositive fontsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function paragraph(grid: ts.DistanceGrid, spacing: Option<ts.LatinSpacing>) -> ts.Paragraph {
    4. ts.Paragraph {
    5. grid: grid,
    6. items: [
    7. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "abc", width: 10pt, trackingAfter: 0pt }),
    8. ts.Item.PenaltyItem(ts.Penalty {
    9. unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(),
    10. kind: ts.BreakKind.ForcedEnd(), flagged: false
    11. })
    12. ],
    13. geometry: ts.LineGeometry {
    14. bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt
    15. },
    16. justification: ts.JustificationPolicy {
    17. mode: ts.JustificationMode.Unjustified(),
    18. minGapCount: 0, minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    19. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    20. latinSpacing: spacing, cjkStretchRatio: 0.0
    21. },
    22. costModel: ts.CostModel.FixedPoint(),
    23. knuthPlass: ts.KnuthPlassPolicy {
    24. toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0,
    25. flaggedDemerit: 0, finalFlaggedDemerit: 0
    26. }
    27. }
    28. }
    29. for value in ([ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: -0.0000152587890625em, fontSizePt: 12pt },
    30. ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: 0.0150146484375em, fontSizePt: 12pt },
    31. ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: 0em, fontSizePt: 0pt },
    32. ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: 0em, fontSizePt: -12pt }]) {
    33. match (ts.breakParagraph(paragraph(ts.DistanceGrid.MilliPoint(), Some(value)))) {
    34. case Err(InvalidPolicy(_)) => io.writeLine("invalid length policy")
    35. case _ => io.writeLine("unexpected result")
    36. }
    37. }
    Expected outputexit 0
    invalid length policy
    invalid length policy
    invalid length policy
    invalid length policy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. invalid length policy
    2. invalid length policy
    3. invalid length policy
    4. invalid length policy
    Source evidence

    Read a stored policy before paragraph validation

    Link
    main.tpzRead a stored policy before paragraph validationOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let value = ts.LatinSpacing { minWordSpaceRatio: 0.0, maxWordSpaceRatio: 2.0, maxLetterSpacingEm: 1em, fontSizePt: 0pt }
    4. io.writeLine("{value.minWordSpaceRatio == 0.0}|{value.maxWordSpaceRatio == 2.0}|{value.maxLetterSpacingEm == 1em}|{value.fontSizePt == 0pt}")
    Expected outputexit 0
    true|true|true|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|true|true|true
    Source evidence

    Fill a line using a rounded bound for every letter gap

    Link
    main.tpzFill a line using a rounded bound for every letter gapOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function paragraph(grid: ts.DistanceGrid, spacing: Option<ts.LatinSpacing>) -> ts.Paragraph {
    5. ts.Paragraph {
    6. grid: grid,
    7. items: [
    8. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    9. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "b", width: 1pt, trackingAfter: 0pt }),
    10. ts.Item.BoxItem(ts.Box { unitIndex: 2, text: "c", width: 1pt, trackingAfter: 0pt }),
    11. ts.Item.PenaltyItem(ts.Penalty {
    12. unitEnd: 3, width: 0pt, rule: ts.BreakRule.Forced(),
    13. kind: ts.BreakKind.LatinSpace(), flagged: false
    14. }),
    15. ts.Item.BoxItem(ts.Box { unitIndex: 3, text: "xyz", width: 3.376pt, trackingAfter: 0pt }),
    16. ts.Item.PenaltyItem(ts.Penalty {
    17. unitEnd: 4, width: 0pt, rule: ts.BreakRule.Forced(),
    18. kind: ts.BreakKind.ForcedEnd(), flagged: false
    19. })
    20. ],
    21. geometry: ts.LineGeometry {
    22. bodyTarget: 3.376pt, firstTarget: 3.376pt, dropCapSpan: 0, dropCapInset: 0pt
    23. },
    24. justification: ts.JustificationPolicy {
    25. mode: ts.JustificationMode.LatinWordSpace(),
    26. minGapCount: 0, minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    27. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    28. latinSpacing: spacing, cjkStretchRatio: 0.0
    29. },
    30. costModel: ts.CostModel.FixedPoint(),
    31. knuthPlass: ts.KnuthPlassPolicy {
    32. toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0,
    33. flaggedDemerit: 0, finalFlaggedDemerit: 0
    34. }
    35. }
    36. }
    37. let value = ts.LatinSpacing { minWordSpaceRatio: 1.0, maxWordSpaceRatio: 1.0, maxLetterSpacingEm: 0.0078125em, fontSizePt: 24pt }
    38. let result = test.assertOk(ts.breakParagraph(paragraph(ts.DistanceGrid.MilliPoint(), Some(value))))
    39. let filled = match (result.optimized.boundedFillFeasible) {
    40. case Some(value) => value
    41. case None() => false
    42. }
    43. io.writeLine("{filled}|{result.optimized.lines.length}|{result.optimized.lines[0].target == 3.376pt}")
    Expected outputexit 0
    true|2|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|2|true
    Source evidence

    Select a computation grid for typed distances

    Link
    Ruletopaz.stdlib.contract.distance-grid.rule

    Select a computation grid for typed distances

    DistanceGrid has the two payload-free values ScaledPoint() and MilliPoint(). They are distinct selectors and have no numeric conversion or ambient state.

    Public physical inputs and outputs remain Length<Pt> under either selector. A grid selects internal quantization and does not reinterpret an integer or change a public unit marker.

    ScaledPoint uses the signed scaled-point integer stored in each physical length. MilliPoint converts each input through Length<Pt>.toMilliPoints with nearest-even rounding.

    Quantize each input once at the computation boundary. Resolve a relative length against its explicit font size before this conversion. Accumulation uses the resulting integers.

    Return a computed distance through fromScaledPoints for ScaledPoint or fromMilliPoints for MilliPoint. The shared constructor range and rounding rules apply at this output boundary.

    Grid conversion accepts signed lengths. Each grid computation applies its sign requirements after quantization. A positive point target that becomes zero millipoints fails a positive-target requirement. Font validation occurs during resolve before grid conversion.

    FixedPoint requires MilliPoint. A paragraph that selects FixedPoint with ScaledPoint returns Err containing TypesetError.InvalidPolicy. KnuthPlass accepts either grid.

    Selecting a grid and converting lengths perform no I/O and do not mutate their inputs. Ordinary receiver and argument evaluation happens once before conversion. Input evaluation failures propagate before the operation begins.

    A millipoint round trip can change the stored scaled-point value. For example, 4096 scaled points become 62 millipoints and then 4063 scaled points.

    Why

    Typed distances retain their meaning while a computation chooses precision. One conversion rule at each boundary makes grid selection reproducible and keeps font resolution explicit.

    Source evidence

    Construct both computation grid selectors

    Link
    main.tpzConstruct both computation grid selectorsOpen in Playground
    1. import std.typeset as ts
    2. let scaled: ts.DistanceGrid = ts.DistanceGrid.ScaledPoint()
    3. let milli: ts.DistanceGrid = ts.DistanceGrid.MilliPoint()
    Expected outputexit 0
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Round signed millipoint ties and expose grid loss

    Link
    main.tpzRound signed millipoint ties and expose grid lossOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. for ticks in ([-12288, -4096, 0, 4096, 12288]) {
    4. let value = ts.fromScaledPoints(ticks).toMilliPoints()
    5. io.writeLine("{value}")
    6. }
    7. let restored = ts.fromMilliPoints(62)
    8. io.writeLine("{restored == ts.fromScaledPoints(4063)}")
    9. io.writeLine("{restored == ts.fromScaledPoints(4096)}")
    Expected outputexit 0
    -188
    -62
    0
    62
    188
    true
    false
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. -188
    2. -62
    3. 0
    4. 62
    5. 188
    6. true
    7. false
    Source evidence

    Reject a scaled-point grid for FixedPoint

    Link
    main.tpzReject a scaled-point grid for FixedPointOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function paragraph(grid: ts.DistanceGrid, spacing: Option<ts.LatinSpacing>) -> ts.Paragraph {
    4. ts.Paragraph {
    5. grid: grid,
    6. items: [
    7. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "abc", width: 10pt, trackingAfter: 0pt }),
    8. ts.Item.PenaltyItem(ts.Penalty {
    9. unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(),
    10. kind: ts.BreakKind.ForcedEnd(), flagged: false
    11. })
    12. ],
    13. geometry: ts.LineGeometry {
    14. bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt
    15. },
    16. justification: ts.JustificationPolicy {
    17. mode: ts.JustificationMode.Unjustified(),
    18. minGapCount: 0, minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    19. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    20. latinSpacing: spacing, cjkStretchRatio: 0.0
    21. },
    22. costModel: ts.CostModel.FixedPoint(),
    23. knuthPlass: ts.KnuthPlassPolicy {
    24. toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0,
    25. flaggedDemerit: 0, finalFlaggedDemerit: 0
    26. }
    27. }
    28. }
    29. match (ts.breakParagraph(paragraph(ts.DistanceGrid.ScaledPoint(), None()))) {
    30. case Err(InvalidPolicy(_)) => io.writeLine("invalid grid policy")
    31. case _ => io.writeLine("unexpected result")
    32. }
    Expected outputexit 0
    invalid grid policy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. invalid grid policy
    Source evidence

    Convert physical units without rounding again

    Link

    Convert physical units without rounding again

    Every physical Length stores a signed 64-bit scaled-point count and a unit marker. The inclusive count range is -9223372036854775808 through 9223372036854775807. Zero and negative lengths are valid.

    One point is 65,536 scaled points. One inch is 72 points and exactly 25.4 millimetres. One centimetre is 10 millimetres.

    A physical literal converts its exact decimal value to scaled points and rounds once to the nearest integer. An exact tie selects the even integer. The same rule applies to either sign.

    The methods toPt, toMm, toCm, and toIn accept no arguments on Length<Pt>, Length<Mm>, Length<Cm>, and Length<In>. They return the named physical unit with the original scaled-point count.

    A unit conversion performs no further rounding and cannot overflow. Converting to the existing unit returns the same typed value. Every conversion sequence preserves the physical distance and stored count.

    The unit marker remains part of the static type. Assignment and function arguments do not convert units implicitly. A required Length<Pt> rejects another unit with TPZ5001. Convert explicitly before the call or assignment.

    Length<Em> has no physical conversion member. Resolve it with an explicit positive point font size before converting units.

    Each method evaluates its receiver once before producing the result. Conversion performs no I/O or mutation and reads no font or paragraph state.

    Use toScaledPoints on any physical unit for the exact stored integer. Convert to Length<Pt> before calling toMilliPoints. The integer conversion contract defines its separate rounding boundary.

    Why

    A unit marker states how a length may be used while one physical grid preserves its distance. Conversion changes the marker without accumulating rounding error. One conversion name per target unit works for every physical source unit.

    Source evidence

    Convert each physical unit to every physical target

    Link
    main.tpzConvert each physical unit to every physical targetOpen in Playground
    1. import std.io
    2. let pt = 72pt
    3. let mm = 25.4mm
    4. let cm = 2.54cm
    5. let inch = 1in
    6. io.writeLine("{pt.toPt() == 72pt}|{pt.toMm() == 25.4mm}|{pt.toCm() == 2.54cm}|{pt.toIn() == 1in}")
    7. io.writeLine("{mm.toPt() == 72pt}|{mm.toMm() == 25.4mm}|{mm.toCm() == 2.54cm}|{mm.toIn() == 1in}")
    8. io.writeLine("{cm.toPt() == 72pt}|{cm.toMm() == 25.4mm}|{cm.toCm() == 2.54cm}|{cm.toIn() == 1in}")
    9. io.writeLine("{inch.toPt() == 72pt}|{inch.toMm() == 25.4mm}|{inch.toCm() == 2.54cm}|{inch.toIn() == 1in}")
    Expected outputexit 0
    true|true|true|true
    true|true|true|true
    true|true|true|true
    true|true|true|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|true|true|true
    2. true|true|true|true
    3. true|true|true|true
    4. true|true|true|true
    Source evidence

    Preserve signed and quantized values through conversion chains

    Link
    main.tpzPreserve signed and quantized values through conversion chainsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let negative = (-1pt).toMm().toCm().toIn().toPt()
    4. let zero = (0mm).toIn().toPt()
    5. let metric = (0.1mm).toPt()
    6. let unchanged = metric.toCm().toMm().toIn().toPt()
    7. io.writeLine("{negative == -1pt}|{zero == 0pt}|{metric == ts.fromScaledPoints(18577)}|{unchanged == ts.fromScaledPoints(18577)}")
    Expected outputexit 0
    true|true|true|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|true|true|true
    Source evidence

    Keep the full range when changing units

    Link
    main.tpzKeep the full range when changing unitsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let low = ts.fromScaledPoints(-9223372036854775808).toMm().toCm().toIn().toPt()
    4. let high = ts.fromScaledPoints(9223372036854775807).toIn().toCm().toMm().toPt()
    5. io.writeLine("{low == -140737488355328pt}|{high == 140737488355327.9999847412109375pt}")
    Expected outputexit 0
    true|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|true
    Source evidence

    Require the declared physical unit after conversion

    Link
    main.tpzRequire the declared physical unit after conversionOpen in Playground
    1. let distance: Length<Pt> = (1pt).toMm()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:1:28
    Source evidence

    Resolve an em length with an explicit positive font size

    Link
    Ruletopaz.stdlib.contract.length-resolve.rule

    Resolve an em length with an explicit positive font size

    Length<Em> stores a signed 64-bit relative count with 65,536 ticks per em. Its inclusive count range is -9223372036854775808 through 9223372036854775807. Zero and negative relative lengths are valid.

    An em literal rounds its exact decimal magnitude to relative ticks with nearest-even rounding. Resolution uses those stored ticks and does not recover discarded literal precision.

    resolve takes one Length<Pt> font size and returns Length<Pt>. The font size must have a strictly positive scaled-point count. A one-scaled-point font is valid.

    If e is the stored em count and f is the stored font-size count, the physical result is roundEven of the exact rational e times f divided by 65,536. Multiply before rounding and round once to the physical grid.

    The result must fit the common signed 64-bit physical range after rounding. Overflow stops with runtime diagnostic TPZ4004. Exact intermediates must not create a smaller accepted domain.

    A zero or negative font size stops with runtime diagnostic TPZ4004, including when the relative length is zero. These failures are runtime faults and are not Result values.

    A font size with a different unit marker, an int, or a float is rejected with TPZ5001. A physical Length has no resolve member. Relative lengths have no direct physical conversion or integer grid member.

    The receiver evaluates once and then the font-size argument evaluates once. Resolution reads no ambient font state and performs no I/O or mutation. It preserves effects already caused by evaluating those inputs.

    After resolution, use the physical conversion methods and integer interchange operations. LatinSpacing uses this same resolution before the selected paragraph grid quantizes the per-gap distance.

    For 0.01em and 12pt, the relative count is 655 and the resolved count is 7,860 scaled points. Its nearest-even millipoint conversion is 120.

    Why

    An explicit positive font size gives a relative length one physical meaning. Shared integer rounding makes standalone lengths and letter-spacing budgets agree, including ties, negative offsets, and range boundaries.

    Source evidence

    Resolve zero and negative relative lengths with a positive font

    Link
    main.tpzResolve zero and negative relative lengths with a positive fontOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let negative = (-0.5em).resolve(12pt)
    4. let zero = (0em).resolve(ts.fromScaledPoints(1))
    5. let large = (140737488355327.9999847412109375em).resolve(ts.fromScaledPoints(1))
    6. io.writeLine("{negative == -6pt}|{zero == 0pt}|{large == ts.fromScaledPoints(140737488355328)}")
    Expected outputexit 0
    true|true|true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true|true|true
    Source evidence

    Evaluate the relative value and font size once in order

    Link
    main.tpzEvaluate the relative value and font size once in orderOpen in Playground
    1. import std.io
    2. function relative() -> Length<Em> {
    3. io.writeLine("relative")
    4. 0.5em
    5. }
    6. function font() -> Length<Pt> {
    7. io.writeLine("font")
    8. 12pt
    9. }
    10. let result = relative().resolve(font())
    11. io.writeLine("{result == 6pt}")
    Expected outputexit 0
    relative
    font
    true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. relative
    2. font
    3. true
    Source evidence

    Reject a zero font size

    Link
    main.tpzReject a zero font sizeOpen in Playground
    1. let relative = 0em
    2. let font = 0pt
    3. relative.resolve(font)
    Expected outputNonzero exit
    No stdout.
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Nonzero exit.

    • TPZ4004 stdlib-length-font-positive at main.tpz:3:1
    Source evidence

    Reject a negative font size

    Link
    main.tpzReject a negative font sizeOpen in Playground
    1. let relative = 1em
    2. let font = -12pt
    3. relative.resolve(font)
    Expected outputNonzero exit
    No stdout.
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Nonzero exit.

    • TPZ4004 stdlib-length-font-positive at main.tpz:3:1
    Source evidence

    Reject an unrepresentable physical result

    Link
    main.tpzReject an unrepresentable physical resultOpen in Playground
    1. import std.typeset as ts
    2. let relative = 2em
    3. let font = ts.fromScaledPoints(9223372036854775807)
    4. relative.resolve(font)
    Expected outputNonzero exit
    No stdout.
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Nonzero exit.

    • TPZ4004 stdlib-length-unrepresentable at main.tpz:4:1
    Source evidence

    Require a point font size

    Link
    main.tpzRequire a point font sizeOpen in Playground
    1. let relative = 1em
    2. relative.resolve(1mm)
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-physical-distance-requires-points at main.tpz:2:18
    Source evidence

    Repeat a paragraph plan deterministically

    Link
    Ruletopaz.stdlib.contract.typeset-determinism.rule

    Repeat a paragraph plan deterministically

    For the same ordered input values, policies, calculation grid, and product release, classifyBreaks and breakParagraph produce the same result. This includes boundary order, selected breaks, line widths, costs, and the returned error variant.

    Planning reads measured text and policy values without changing caller arrays. It does not shape text, load fonts, or write output. Ordinary argument evaluation finishes before the operation begins.

    The selected cost model, admission rules, and search bounds define plan quality. The two reported plans expose their own costs for inspection.

    Why

    Repeatable paragraph layout lets applications cache plans and reproduce published pages. An explicit search contract separates reproducibility from the quality of a chosen plan.

    Source evidence

    Retain a deterministic choice among equal cost candidates

    Link
    main.tpzRetain a deterministic choice among equal cost candidatesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let items: Array<ts.Item> = [
    5. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 10pt, trackingAfter: 0pt }),
    6. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Allowed(0), kind: ts.BreakKind.LatinSpace(), flagged: false }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Allowed(0), kind: ts.BreakKind.ClosingPunctuation(), flagged: false })
    8. ]
    9. let input = ts.Paragraph {
    10. grid: ts.DistanceGrid.MilliPoint(),
    11. items: items,
    12. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    13. justification: ts.JustificationPolicy {
    14. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    15. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    16. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    17. latinSpacing: None(), cjkStretchRatio: 0.0
    18. },
    19. costModel: ts.CostModel.FixedPoint(),
    20. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    21. }
    22. function category(kind: ts.BreakKind) -> string {
    23. match (kind) {
    24. case LatinSpace() => "space"
    25. case ClosingPunctuation() => "punctuation"
    26. case _ => "unexpected kind"
    27. }
    28. }
    29. for pass in ([0, 1]) {
    30. let result = test.assertOk(ts.breakParagraph(input))
    31. let optimized = result.optimized.lines[0]
    32. let greedy = result.greedy.lines[0]
    33. io.writeLine("{pass} {optimized.start} {optimized.endExclusive} {category(optimized.kind)} {category(greedy.kind)}")
    34. }
    Expected outputexit 0
    0 0 1 space punctuation
    1 0 1 space punctuation
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0 0 1 space punctuation
    2. 1 0 1 space punctuation
    Source evidence

    Report the same first failing stage for repeated invalid input

    Link
    main.tpzReport the same first failing stage for repeated invalid inputOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 2, text: "a", width: 1pt, trackingAfter: 0pt })
    5. ]
    6. let input = ts.Paragraph {
    7. grid: ts.DistanceGrid.MilliPoint(),
    8. items: items,
    9. geometry: ts.LineGeometry { bodyTarget: 0pt, firstTarget: 0pt, dropCapSpan: 0, dropCapInset: 0pt },
    10. justification: ts.JustificationPolicy {
    11. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    12. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    13. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    14. latinSpacing: None(), cjkStretchRatio: 0.0
    15. },
    16. costModel: ts.CostModel.FixedPoint(),
    17. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    18. }
    19. for pass in ([0, 1]) {
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("{pass} InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    24. }
    Expected outputexit 0
    0 InvalidPolicy
    1 InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0 InvalidPolicy
    2. 1 InvalidPolicy
    Source evidence

    Classify measured clusters into an ordered boundary sequence

    Link
    Ruletopaz.stdlib.contract.typeset-break-inputs.rule

    Classify measured clusters into an ordered boundary sequence

    classifyBreaks reads measured clusters in array order and returns Result<Array<BreakOpportunity>, TypesetError>. It does not shape text, normalize strings, split a cluster, or change any supplied record or array.

    Each BreakCluster.text is the scalar string for one indivisible measured unit. Empty text still occupies one cluster index. advance includes trackingAfter. A span width sums its advances and removes the trackingAfter of its final cluster.

    advance and advance minus trackingAfter must be nonnegative point lengths. Zero advance and signed trackingAfter are accepted. Invalid extents return TypesetError.InvalidInput. Length arithmetic follows the common Length contract.

    classifyBreaks sums stored point ticks and compares them with the stored point targets. It has no DistanceGrid argument and does not quantize to a paragraph grid. Exact equality with a target counts as fitting.

    boundaryAllowed has exactly one entry for each cluster. Entry i governs the boundary after cluster i. The last entry must be true for nonempty input. A wrong mask length or a false last entry returns TypesetError.InvalidInput.

    Validation checks mask length and terminal permission, word targets, forbidBefore, forbidAfter, generated hyphen indices, and measured extents in that order. Array checks visit entries in input order. Unicode mandatory crossings are checked before boundary conflicts are visited in increasing cluster order. An empty result follows successful structural and policy validation.

    An empty cluster array with a valid empty boundary mask returns Ok with an empty opportunity array. It does not create a synthetic unit or terminal opportunity.

    Successful nonempty results contain increasing, unique unitEnd values from one through the cluster count. unitEnd is an exclusive cluster index rather than a byte or scalar offset. The final opportunity has unitEnd equal to the cluster count and kind ForcedEnd.

    Repeated calls with equal cluster values and equal policies produce the same ordered opportunities and categories. Validation and classification do not read host locale, perform I/O, invoke caller callbacks, or mutate input values. Argument expressions use the ordinary language evaluation order.

    A BreakOpportunity carries a boundary and its category. It does not carry a penalty width or BreakRule. A caller preserving mandatory boundaries maps every ForcedEnd opportunity to a Forced penalty before calling breakParagraph.

    Why

    A measured cluster is the unit that shaping can safely keep together. Exclusive indices connect classification to paragraph items without converting through text offsets. A complete terminal boundary and deterministic order make the output suitable for reproducible layout.

    Source evidence

    Classify atomic clusters in repeatable boundary order

    Link
    main.tpzClassify atomic clusters in repeatable boundary orderOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters = [ts.BreakCluster { text: "한", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "글", advance: 1pt, trackingAfter: 0pt }]
    5. let selected = ts.BreakPolicy {
    6. mode: ts.BreakMode.Character(),
    7. rules: ts.KinsokuRules.AtlasDefault(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [true, true],
    11. generatedHyphenEnds: [],
    12. protectFittingLatin: false,
    13. preserveShortWords: false,
    14. narrowestWordTarget: 0pt,
    15. bodyWordTarget: 0pt
    16. }
    17. for pass in ([0, 1]) {
    18. let breaks = test.assertOk(ts.classifyBreaks(clusters, selected))
    19. for boundary in (breaks) {
    20. let category = match (boundary.kind) {
    21. case CjkCluster() => "CJK"
    22. case ForcedEnd() => "end"
    23. case _ => "unexpected kind"
    24. }
    25. io.writeLine("{pass} {boundary.unitEnd} {category}")
    26. }
    27. }
    28. io.writeLine(clusters[0].text)
    Expected outputexit 0
    0 1 CJK
    0 2 end
    1 1 CJK
    1 2 end
    한
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0 1 CJK
    2. 0 2 end
    3. 1 1 CJK
    4. 1 2 end
    5. 한
    Source evidence

    Classify an empty cluster sequence

    Link
    main.tpzClassify an empty cluster sequenceOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters: Array<ts.BreakCluster> = []
    5. let selected = ts.BreakPolicy {
    6. mode: ts.BreakMode.Character(),
    7. rules: ts.KinsokuRules.AtlasDefault(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [],
    11. generatedHyphenEnds: [],
    12. protectFittingLatin: false,
    13. preserveShortWords: false,
    14. narrowestWordTarget: 0pt,
    15. bodyWordTarget: 0pt
    16. }
    17. let breaks = test.assertOk(ts.classifyBreaks(clusters, selected))
    18. io.writeLine("{breaks.length}")
    Expected outputexit 0
    0
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0
    Source evidence

    Reject a mask with the wrong number of entries

    Link
    main.tpzReject a mask with the wrong number of entriesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function outcome(value: Result<Array<ts.BreakOpportunity>, ts.TypesetError>) -> string {
    5. match (value) {
    6. case Ok(_) => "Ok"
    7. case Err(InvalidInput(_)) => "InvalidInput"
    8. case Err(InvalidPolicy(_)) => "InvalidPolicy"
    9. case Err(_) => "unexpected error"
    10. }
    11. }
    12. let clusters = [ts.BreakCluster { text: "a", advance: 1pt, trackingAfter: 0pt }]
    13. let selected = ts.BreakPolicy {
    14. mode: ts.BreakMode.Character(),
    15. rules: ts.KinsokuRules.AtlasDefault(),
    16. forbidBefore: [],
    17. forbidAfter: [],
    18. boundaryAllowed: [],
    19. generatedHyphenEnds: [],
    20. protectFittingLatin: false,
    21. preserveShortWords: false,
    22. narrowestWordTarget: 0pt,
    23. bodyWordTarget: 0pt
    24. }
    25. io.writeLine(outcome(ts.classifyBreaks(clusters, selected)))
    Expected outputexit 0
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    Source evidence

    Reject negative measured and untracked cluster extents

    Link
    main.tpzReject negative measured and untracked cluster extentsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function outcome(value: Result<Array<ts.BreakOpportunity>, ts.TypesetError>) -> string {
    5. match (value) {
    6. case Ok(_) => "Ok"
    7. case Err(InvalidInput(_)) => "InvalidInput"
    8. case Err(InvalidPolicy(_)) => "InvalidPolicy"
    9. case Err(_) => "unexpected error"
    10. }
    11. }
    12. let selected = ts.BreakPolicy {
    13. mode: ts.BreakMode.Character(),
    14. rules: ts.KinsokuRules.AtlasDefault(),
    15. forbidBefore: [],
    16. forbidAfter: [],
    17. boundaryAllowed: [true],
    18. generatedHyphenEnds: [],
    19. protectFittingLatin: false,
    20. preserveShortWords: false,
    21. narrowestWordTarget: 0pt,
    22. bodyWordTarget: 0pt
    23. }
    24. io.writeLine(outcome(ts.classifyBreaks([ts.BreakCluster { text: "a", advance: -1pt, trackingAfter: 0pt }], selected)))
    25. io.writeLine(outcome(ts.classifyBreaks([ts.BreakCluster { text: "b", advance: 1pt, trackingAfter: 2pt }], selected)))
    Expected outputexit 0
    InvalidInput
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    2. InvalidInput
    Source evidence

    Reject a masked paragraph end in every ruleset

    Link
    main.tpzReject a masked paragraph end in every rulesetOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function outcome(value: Result<Array<ts.BreakOpportunity>, ts.TypesetError>) -> string {
    5. match (value) {
    6. case Ok(_) => "Ok"
    7. case Err(InvalidInput(_)) => "InvalidInput"
    8. case Err(InvalidPolicy(_)) => "InvalidPolicy"
    9. case Err(_) => "unexpected error"
    10. }
    11. }
    12. let clusters = [ts.BreakCluster { text: "a", advance: 1pt, trackingAfter: 0pt }]
    13. let atlas = ts.BreakPolicy {
    14. mode: ts.BreakMode.Character(),
    15. rules: ts.KinsokuRules.AtlasDefault(),
    16. forbidBefore: [],
    17. forbidAfter: [],
    18. boundaryAllowed: [false],
    19. generatedHyphenEnds: [],
    20. protectFittingLatin: false,
    21. preserveShortWords: false,
    22. narrowestWordTarget: 0pt,
    23. bodyWordTarget: 0pt
    24. }
    25. let unicode = ts.BreakPolicy {
    26. mode: ts.BreakMode.Character(),
    27. rules: ts.KinsokuRules.Unicode16Ja(),
    28. forbidBefore: [],
    29. forbidAfter: [],
    30. boundaryAllowed: [false],
    31. generatedHyphenEnds: [],
    32. protectFittingLatin: false,
    33. preserveShortWords: false,
    34. narrowestWordTarget: 0pt,
    35. bodyWordTarget: 0pt
    36. }
    37. io.writeLine(outcome(ts.classifyBreaks(clusters, atlas)))
    38. io.writeLine(outcome(ts.classifyBreaks(clusters, unicode)))
    Expected outputexit 0
    InvalidInput
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    2. InvalidInput
    Source evidence

    Keep exact point measurements at the classifier boundary

    Link
    main.tpzKeep exact point measurements at the classifier boundaryOpen in Playground
    1. import std.typeset as ts
    2. import std.test
    3. import std.io
    4. let clusters = [
    5. ts.BreakCluster { text: "a", advance: 0.5002pt, trackingAfter: 0pt },
    6. ts.BreakCluster { text: "b", advance: 0.5002pt, trackingAfter: 0pt }
    7. ]
    8. let policy = ts.BreakPolicy {
    9. mode: ts.BreakMode.Word(), rules: ts.KinsokuRules.AtlasDefault(),
    10. forbidBefore: [], forbidAfter: [], boundaryAllowed: [true, true],
    11. generatedHyphenEnds: [], protectFittingLatin: false, preserveShortWords: false,
    12. narrowestWordTarget: 0pt, bodyWordTarget: 1pt
    13. }
    14. let result = test.assertOk(ts.classifyBreaks(clusters, policy))
    15. for boundary in (result) {
    16. io.writeLine("{boundary.unitEnd}")
    17. }
    Expected outputexit 0
    1
    2
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1
    2. 2
    Source evidence

    Apply word policy without crossing protected cluster boundaries

    Link
    Ruletopaz.stdlib.contract.typeset-break-policy.rule

    Apply word policy without crossing protected cluster boundaries

    BreakPolicy supplies every classification choice explicitly. rules selects the character boundary policy. mode selects word protection. The two choices are independent and neither consults the host locale.

    forbidBefore and forbidAfter contain Unicode scalar values. Each integer must be between zero and 1114111 and outside 55296 through 57343. Invalid values return TypesetError.InvalidPolicy. Ordering and duplicate entries do not change membership.

    At an ordinary interior boundary, forbidAfter checks the final scalar on the left and forbidBefore checks the first scalar on the right. These custom bans filter admitted candidates and do not create candidates. They do not replace non-tailorable Unicode decisions.

    The shaping boundary mask can remove an ordinary candidate. A mask conflict with a mandatory or non-tailorable required Unicode opportunity returns TypesetError.InvalidInput. A generated hyphen or character mode never bypasses a false mask entry.

    generatedHyphenEnds contains exclusive interior cluster indices. Both neighboring clusters must consist of Unicode Alphabetic scalars or the apostrophes U+0027 and U+2019. An out of range index or nonword neighbor returns TypesetError.InvalidInput. Duplicate indices have no additional effect.

    A generated index changes a missing or HardFallback candidate to LatinSoftHyphen when Unicode permits tailoring there. It does not replace another category or a non-tailorable prohibition. The selected locale, custom bans, shaping mask, and short-word policy still apply. The function does not insert text or measure a hyphen glyph.

    narrowestWordTarget and bodyWordTarget are independent nonnegative point lengths. A negative target returns TypesetError.InvalidPolicy. Zero is accepted and equality with a target counts as fitting.

    For a HardFallback inside a contiguous run of Alphabetic or apostrophe clusters, protectFittingLatin suppresses the break when the measured run width is at most narrowestWordTarget. Word mode also suppresses that break when the run width is at most bodyWordTarget. Character mode removes this second word-fit test while retaining all other constraints.

    Word mode can add emergency boundaries inside an oversized word whose width exceeds bodyWordTarget. Unicode modes retain non-tailorable prohibitions and locale bans. Unicode16Ko also preserves a fitting whitespace-delimited Hangul run at bodyWordTarget.

    With preserveShortWords, an interior boundary between nonwhitespace clusters is suppressed when their whitespace-delimited run has at most three Alphabetic or Number scalars. It is also suppressed when that run fits bodyWordTarget. These tests include generated hyphens. Mandatory and non-tailorable required Unicode opportunities retain their priority.

    Why

    Word fitting and character breaking answer different layout needs. Explicit thresholds let callers retain fitting words while admitting emergency breaks in oversized text. Applying shaping and punctuation constraints after candidate generation prevents a convenience option from splitting protected text.

    Source evidence

    Keep generated hyphens behind the shaping mask

    Link
    main.tpzKeep generated hyphens behind the shaping maskOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters = [ts.BreakCluster { text: "a", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "b", advance: 1pt, trackingAfter: 0pt }]
    5. let selected = ts.BreakPolicy {
    6. mode: ts.BreakMode.Character(),
    7. rules: ts.KinsokuRules.AtlasDefault(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [false, true],
    11. generatedHyphenEnds: [1, 1],
    12. protectFittingLatin: false,
    13. preserveShortWords: false,
    14. narrowestWordTarget: 0pt,
    15. bodyWordTarget: 0pt
    16. }
    17. let breaks = test.assertOk(ts.classifyBreaks(clusters, selected))
    18. for boundary in (breaks) {
    19. io.writeLine("{boundary.unitEnd}")
    20. }
    Expected outputexit 0
    2
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2
    Source evidence

    Keep a word together at the fitting threshold

    Link
    main.tpzKeep a word together at the fitting thresholdOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters = [ts.BreakCluster { text: "a", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "b", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "c", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "d", advance: 1pt, trackingAfter: 0pt }]
    5. let fitting = ts.BreakPolicy {
    6. mode: ts.BreakMode.Word(),
    7. rules: ts.KinsokuRules.AtlasDefault(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [true, true, true, true],
    11. generatedHyphenEnds: [],
    12. protectFittingLatin: false,
    13. preserveShortWords: false,
    14. narrowestWordTarget: 0pt,
    15. bodyWordTarget: 4pt
    16. }
    17. let oversized = ts.BreakPolicy {
    18. mode: ts.BreakMode.Word(),
    19. rules: ts.KinsokuRules.AtlasDefault(),
    20. forbidBefore: [],
    21. forbidAfter: [],
    22. boundaryAllowed: [true, true, true, true],
    23. generatedHyphenEnds: [],
    24. protectFittingLatin: false,
    25. preserveShortWords: false,
    26. narrowestWordTarget: 0pt,
    27. bodyWordTarget: 3pt
    28. }
    29. let guarded = ts.BreakPolicy {
    30. mode: ts.BreakMode.Character(),
    31. rules: ts.KinsokuRules.AtlasDefault(),
    32. forbidBefore: [],
    33. forbidAfter: [],
    34. boundaryAllowed: [true, true, true, true],
    35. generatedHyphenEnds: [],
    36. protectFittingLatin: true,
    37. preserveShortWords: false,
    38. narrowestWordTarget: 4pt,
    39. bodyWordTarget: 0pt
    40. }
    41. let a = test.assertOk(ts.classifyBreaks(clusters, fitting))
    42. let b = test.assertOk(ts.classifyBreaks(clusters, oversized))
    43. let c = test.assertOk(ts.classifyBreaks(clusters, guarded))
    44. io.writeLine("{a.length} {b.length} {c.length}")
    Expected outputexit 0
    1 4 1
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1 4 1
    Source evidence

    Classify one generated hyphen boundary despite duplicate input indices

    Link
    main.tpzClassify one generated hyphen boundary despite duplicate input indicesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters = [ts.BreakCluster { text: "a", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "b", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "c", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "d", advance: 1pt, trackingAfter: 0pt }]
    5. let selected = ts.BreakPolicy {
    6. mode: ts.BreakMode.Word(),
    7. rules: ts.KinsokuRules.AtlasDefault(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [true, true, true, true],
    11. generatedHyphenEnds: [2, 2],
    12. protectFittingLatin: true,
    13. preserveShortWords: false,
    14. narrowestWordTarget: 4pt,
    15. bodyWordTarget: 4pt
    16. }
    17. let breaks = test.assertOk(ts.classifyBreaks(clusters, selected))
    18. for boundary in (breaks) {
    19. let category = match (boundary.kind) {
    20. case LatinSoftHyphen() => "hyphen"
    21. case ForcedEnd() => "end"
    22. case _ => "unexpected kind"
    23. }
    24. io.writeLine("{boundary.unitEnd} {category}")
    25. }
    Expected outputexit 0
    2 hyphen
    4 end
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2 hyphen
    2. 4 end
    Source evidence

    Preserve a word of three counted scalars

    Link
    main.tpzPreserve a word of three counted scalarsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters = [ts.BreakCluster { text: "a", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "b", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "c", advance: 1pt, trackingAfter: 0pt }]
    5. let selected = ts.BreakPolicy {
    6. mode: ts.BreakMode.Character(),
    7. rules: ts.KinsokuRules.AtlasDefault(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [true, true, true],
    11. generatedHyphenEnds: [],
    12. protectFittingLatin: false,
    13. preserveShortWords: true,
    14. narrowestWordTarget: 0pt,
    15. bodyWordTarget: 0pt
    16. }
    17. let breaks = test.assertOk(ts.classifyBreaks(clusters, selected))
    18. io.writeLine("{breaks.length} {breaks[0].unitEnd}")
    Expected outputexit 0
    1 3
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1 3
    Source evidence

    Distinguish invalid policy values from invalid generated boundaries

    Link
    main.tpzDistinguish invalid policy values from invalid generated boundariesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function outcome(value: Result<Array<ts.BreakOpportunity>, ts.TypesetError>) -> string {
    5. match (value) {
    6. case Ok(_) => "Ok"
    7. case Err(InvalidInput(_)) => "InvalidInput"
    8. case Err(InvalidPolicy(_)) => "InvalidPolicy"
    9. case Err(_) => "unexpected error"
    10. }
    11. }
    12. let clusters = [ts.BreakCluster { text: "a", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "b", advance: 1pt, trackingAfter: 0pt }]
    13. let target = ts.BreakPolicy {
    14. mode: ts.BreakMode.Character(),
    15. rules: ts.KinsokuRules.AtlasDefault(),
    16. forbidBefore: [],
    17. forbidAfter: [],
    18. boundaryAllowed: [true, true],
    19. generatedHyphenEnds: [],
    20. protectFittingLatin: false,
    21. preserveShortWords: false,
    22. narrowestWordTarget: 0pt,
    23. bodyWordTarget: -1pt
    24. }
    25. let scalar = ts.BreakPolicy {
    26. mode: ts.BreakMode.Character(),
    27. rules: ts.KinsokuRules.AtlasDefault(),
    28. forbidBefore: [55296],
    29. forbidAfter: [],
    30. boundaryAllowed: [true, true],
    31. generatedHyphenEnds: [],
    32. protectFittingLatin: false,
    33. preserveShortWords: false,
    34. narrowestWordTarget: 0pt,
    35. bodyWordTarget: 0pt
    36. }
    37. let boundary = ts.BreakPolicy {
    38. mode: ts.BreakMode.Character(),
    39. rules: ts.KinsokuRules.AtlasDefault(),
    40. forbidBefore: [],
    41. forbidAfter: [],
    42. boundaryAllowed: [true, true],
    43. generatedHyphenEnds: [2],
    44. protectFittingLatin: false,
    45. preserveShortWords: false,
    46. narrowestWordTarget: 0pt,
    47. bodyWordTarget: 0pt
    48. }
    49. io.writeLine(outcome(ts.classifyBreaks(clusters, target)))
    50. io.writeLine(outcome(ts.classifyBreaks(clusters, scalar)))
    51. io.writeLine(outcome(ts.classifyBreaks(clusters, boundary)))
    Expected outputexit 0
    InvalidPolicy
    InvalidPolicy
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    2. InvalidPolicy
    3. InvalidInput
    Source evidence

    Keep boundary policy separate from break category

    Link

    Keep boundary policy separate from break category

    KinsokuRules is an explicit choice of boundary policy. AtlasDefault retains its fixed Atlas punctuation sets and context checks. It does not acquire new Unicode rules when the host changes.

    Unicode16Ko, Unicode16Zh, and Unicode16Ja use the ordered Unicode 16.0.0 UAX 14 revision 53 decisions. They retain non-tailorable prohibitions, mandatory breaks, and required opportunities before locale and custom typography rules.

    The Korean policy applies KLReq punctuation and fitting Hangul word handling. The Japanese policy applies strict JLReq kinsoku including small kana and numeric prefix and suffix contexts. The Chinese policy applies CLReq basic punctuation handling. The exact versioned character predicates are the referenced policy definitions.

    Unicode policies reject a cluster that crosses a mandatory line break with TypesetError.InvalidInput. A mandatory break after an intact cluster is a ForcedEnd opportunity even when more clusters follow. A cluster boundary inside a forbidden Unicode sequence is not an opportunity.

    LatinSpace marks an admitted boundary after a cluster whose text is U+0020. LatinHyphen marks an admitted boundary after an existing hyphen cluster. LatinSoftHyphen marks an admitted generated hyphen candidate. Neither category inserts a character into the input.

    ClosingPunctuation marks an admitted boundary after the selected closing-punctuation category. CjkCluster marks the admitted CJK or East Asian cluster category. HardFallback marks another admitted boundary or a permitted emergency word boundary. These categories do not authorize a break excluded by the selected policy.

    ForcedEnd marks the paragraph end or a mandatory Unicode boundary. BreakRule.Forced is the distinct planning constraint that prevents a plan from crossing that position. A ForcedEnd category on a caller-created penalty does not replace its BreakRule.

    For Unicode candidates, mandatory boundaries are classified first, followed by space, existing hyphen, closing punctuation, CJK or East Asian text, and HardFallback. Generated hyphens replace a missing or HardFallback category when admissible. Output ordering follows the cluster indices rather than category order.

    Why

    A locale policy decides whether a boundary can be used. A category tells the planner why that admitted boundary exists. Keeping those roles distinct lets the same paragraph model carry Unicode, locale, and caller-supplied constraints without treating a punctuation label as permission.

    Source evidence

    Select Japanese small-kana protection explicitly

    Link
    main.tpzSelect Japanese small-kana protection explicitlyOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters = [ts.BreakCluster { text: "あ", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "ぁ", advance: 1pt, trackingAfter: 0pt }]
    5. let atlas = ts.BreakPolicy {
    6. mode: ts.BreakMode.Character(),
    7. rules: ts.KinsokuRules.AtlasDefault(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [true, true],
    11. generatedHyphenEnds: [],
    12. protectFittingLatin: false,
    13. preserveShortWords: false,
    14. narrowestWordTarget: 0pt,
    15. bodyWordTarget: 0pt
    16. }
    17. let japanese = ts.BreakPolicy {
    18. mode: ts.BreakMode.Character(),
    19. rules: ts.KinsokuRules.Unicode16Ja(),
    20. forbidBefore: [],
    21. forbidAfter: [],
    22. boundaryAllowed: [true, true],
    23. generatedHyphenEnds: [],
    24. protectFittingLatin: false,
    25. preserveShortWords: false,
    26. narrowestWordTarget: 0pt,
    27. bodyWordTarget: 0pt
    28. }
    29. let a = test.assertOk(ts.classifyBreaks(clusters, atlas))
    30. let b = test.assertOk(ts.classifyBreaks(clusters, japanese))
    31. io.writeLine("{a.length} {b.length}")
    Expected outputexit 0
    2 1
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2 1
    Source evidence

    Keep a nonbreaking space protected in character mode

    Link
    main.tpzKeep a nonbreaking space protected in character modeOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let clusters = [ts.BreakCluster { text: "A", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: " ", advance: 1pt, trackingAfter: 0pt }, ts.BreakCluster { text: "B", advance: 1pt, trackingAfter: 0pt }]
    5. let selected = ts.BreakPolicy {
    6. mode: ts.BreakMode.Character(),
    7. rules: ts.KinsokuRules.Unicode16Ja(),
    8. forbidBefore: [],
    9. forbidAfter: [],
    10. boundaryAllowed: [true, true, true],
    11. generatedHyphenEnds: [],
    12. protectFittingLatin: false,
    13. preserveShortWords: false,
    14. narrowestWordTarget: 0pt,
    15. bodyWordTarget: 0pt
    16. }
    17. let breaks = test.assertOk(ts.classifyBreaks(clusters, selected))
    18. io.writeLine("{breaks.length} {breaks[0].unitEnd}")
    Expected outputexit 0
    1 3
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1 3
    Source evidence

    Reject a cluster that hides a mandatory Unicode break

    Link
    main.tpzReject a cluster that hides a mandatory Unicode breakOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function outcome(value: Result<Array<ts.BreakOpportunity>, ts.TypesetError>) -> string {
    5. match (value) {
    6. case Ok(_) => "Ok"
    7. case Err(InvalidInput(_)) => "InvalidInput"
    8. case Err(InvalidPolicy(_)) => "InvalidPolicy"
    9. case Err(_) => "unexpected error"
    10. }
    11. }
    12. let clusters = [ts.BreakCluster { text: "A\nB", advance: 1pt, trackingAfter: 0pt }]
    13. let selected = ts.BreakPolicy {
    14. mode: ts.BreakMode.Character(),
    15. rules: ts.KinsokuRules.Unicode16Ja(),
    16. forbidBefore: [],
    17. forbidAfter: [],
    18. boundaryAllowed: [true],
    19. generatedHyphenEnds: [],
    20. protectFittingLatin: false,
    21. preserveShortWords: false,
    22. narrowestWordTarget: 0pt,
    23. bodyWordTarget: 0pt
    24. }
    25. io.writeLine(outcome(ts.classifyBreaks(clusters, selected)))
    Expected outputexit 0
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    Source evidence

    Express preference, mandatory breaks, and forbidden breaks explicitly

    Link
    Ruletopaz.stdlib.contract.typeset-break-rules.rule

    Express preference, mandatory breaks, and forbidden breaks explicitly

    Allowed carries an int preference for a selectable penalty. Negative values favor a break and positive values discourage it under the selected cost model. Zero is neutral. Every representable int remains an Allowed value rather than becoming a hard rule at a numeric threshold.

    Forced requires every successful paragraph plan to end a line at that boundary. A plan cannot cross it or omit it to improve cost. If the required line cannot be admitted, planning returns the applicable TypesetError instead of ignoring the constraint.

    Forbidden makes that penalty unselectable. It is not represented by a large Allowed preference. Penalty validation and duplicate-boundary rules determine the treatment of caller-supplied penalties at the same position.

    BreakKind records a category and BreakRule records a planning constraint. Selecting a kind does not turn an Allowed penalty into Forced or Forbidden. The selected cost model interprets the Allowed preference and preserves both hard rules.

    Constructing a rule creates an enum value and has no I/O or mutation. Allowed requires an int payload. An argument of another type is rejected with TPZ5001.

    Why

    An explicit variant makes a hard constraint independent of scoring scale. Callers can change the cost model without changing the meaning of a mandatory or forbidden break. Signed preferences remain a separate choice about plan quality.

    Source evidence

    Keep signed penalty preferences distinct from hard break rules

    Link
    main.tpzKeep signed penalty preferences distinct from hard break rulesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. let rules = [ts.BreakRule.Allowed(-10000), ts.BreakRule.Allowed(0), ts.BreakRule.Allowed(10000)]
    5. for rule in (rules) {
    6. match (rule) {
    7. case Allowed(value) => io.writeLine("{value}")
    8. case _ => io.writeLine("unexpected rule")
    9. }
    10. }
    Expected outputexit 0
    -10000
    0
    10000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. -10000
    2. 0
    3. 10000
    Source evidence

    Reject a noninteger break preference

    Link
    main.tpzReject a noninteger break preferenceOpen in Playground
    1. import std.typeset as ts
    2. let rule = ts.BreakRule.Allowed("avoid")
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 stdlib-break-rule-requires-integer at main.tpz:2:33
    Source evidence

    Choose deterministic paragraph plans

    Link

    Choose deterministic paragraph plans

    breakParagraph evaluates its Paragraph argument through the ordinary call rules, then reads its item sequence and policies. Planning performs no input mutation, text shaping, external I/O, or random selection. The same ordered input and computation rules produce the same lines and scores.

    Paragraph.items supplies measured BoxItem and GlueItem units with contiguous indices from zero. PenaltyItem entries refer to boundaries between those units. The planner uses these measurements and supplied penalties directly. It does not call classifyBreaks or insert a terminal penalty.

    Paragraph.grid selects integer quantization at the computation boundary. Every public distance remains Length<Pt>. FixedPoint requires MilliPoint. KnuthPlass accepts either DistanceGrid. The ordinary checked length conversion applies before integer planning.

    Policy validation precedes item validation. Geometry, the cost model and grid combination, justification values, and KnuthPlassPolicy are validated even when a chosen model does not use a policy field for scoring. Invalid configuration returns TypesetError.InvalidPolicy. Invalid unit indices, capacities, or penalty endpoints return TypesetError.InvalidInput.

    A valid empty items array returns two empty plans with zero totalCost and zero breakOpportunityCount. This case still validates the policies. Each empty plan reports its selected grid and None() for boundedFillFeasible.

    For nonempty input, the planner orders penalties by increasing unitEnd and preserves supplied order at equal boundaries. Allowed and Forced entries are candidates. Forbidden entries are excluded individually. Duplicate candidates remain distinct. A Forced entry requires the plan to visit its boundary, where another eligible entry may be selected.

    No candidates returns TypesetError.NoBreakOpportunities. The planner constructs the greedy baseline before the optimized search. Failure to complete that baseline returns TypesetError.NoGreedyPlan. A completed baseline followed by an unsuccessful optimized search returns TypesetError.NoPlan. An Err result contains no partial ParagraphResult.

    For FixedPoint, greedy chooses the farthest raw width that fits the next target before the first overwide candidate or Forced boundary. If the first candidate is already overwide, greedy chooses it to advance. For KnuthPlass, greedy chooses the last ratio-admissible candidate up to the next Forced boundary. Every chosen boundary advances past the previous one.

    FixedPoint optimization starts with the preceding 80 candidates at each destination. It extends the search backward while the preceding span fits the larger base target plus available shrink and one grid unit. It retains the least score for each boundary and semantic state. It admits at most two consecutive LatinSoftHyphen breaks.

    FixedPoint optimized admission uses trimmed text width plus the selected penalty width. The width limit is the target plus available shrink for a nonfinal line, with one grid unit of rounding allowance. A line containing one unit can exceed this limit so an indivisible unit can make progress. Greedy reports the same limit but selects candidates using its raw width rule.

    With FixedPoint, a justified mode, and Some(latinSpacing), optimization first searches for a complete plan whose lines satisfy the bounded fill check. It prefers that plan when found. Otherwise it repeats the search without that check. This preference can select a plan with a higher totalCost than the greedy plan.

    KnuthPlass optimization considers every preceding candidate and retains each distinct line geometry bucket, fitness class, and flagged state at a boundary. FixedPoint also retains its soft hyphen streak. State buckets distinguish drop cap lines and collapse after the inset ends.

    Equal scores retain the first visited predecessor. Candidates are visited in boundary order, with supplied order at equal boundaries. States are visited by increasing line geometry bucket, soft hyphen streak, fitness class, and then unflagged before flagged. Final selection uses the same first-on-equal rule.

    The optimized field reports the selected model search and admission policy. The greedy field reports its baseline from the same input. Each reported cost belongs to the plan selected by these candidate, state, and admission rules. Ordinary checked arithmetic faults remain faults and are not converted into a TypesetError value.

    Why

    Applications need reproducible line boundaries and a clear choice between a bounded fixed point search and a Knuth Plass state search. Reporting both selected plans preserves the baseline for inspection. Keeping policy failure, missing candidates, and unsuccessful searches distinct lets a caller decide which input to revise.

    Source evidence

    Plan a paragraph through its supplied boundaries

    Link
    main.tpzPlan a paragraph through its supplied boundariesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "a", width: 1pt, trackingAfter: 0pt }),
    6. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Allowed(0), kind: ts.BreakKind.LatinSpace(), flagged: false }),
    7. ts.Item.BoxItem(ts.Box { unitIndex: 2, text: "a", width: 1pt, trackingAfter: 0pt }),
    8. ts.Item.BoxItem(ts.Box { unitIndex: 3, text: "a", width: 1pt, trackingAfter: 0pt }),
    9. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 4, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    10. ]
    11. let input = ts.Paragraph {
    12. grid: ts.DistanceGrid.MilliPoint(),
    13. items: items,
    14. geometry: ts.LineGeometry { bodyTarget: 2pt, firstTarget: 2pt, dropCapSpan: 0, dropCapInset: 0pt },
    15. justification: ts.JustificationPolicy {
    16. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    17. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    18. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    19. latinSpacing: None(), cjkStretchRatio: 0.0
    20. },
    21. costModel: ts.CostModel.FixedPoint(),
    22. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    23. }
    24. match (ts.breakParagraph(input)) {
    25. case Ok(result) => {
    26. for line in (result.optimized.lines) {
    27. io.writeLine("{line.start} {line.endExclusive}")
    28. }
    29. io.writeLine("{result.greedy.lines.length} {result.breakOpportunityCount}")
    30. }
    31. case Err(_) => io.writeLine("unexpected error")
    32. }
    Expected outputexit 0
    0 2
    2 4
    2 2
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0 2
    2. 2 4
    3. 2 2
    Source evidence

    Plan a valid empty paragraph

    Link
    main.tpzPlan a valid empty paragraphOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ]
    5. let input = ts.Paragraph {
    6. grid: ts.DistanceGrid.MilliPoint(),
    7. items: items,
    8. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    9. justification: ts.JustificationPolicy {
    10. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    11. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    12. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    13. latinSpacing: None(), cjkStretchRatio: 0.0
    14. },
    15. costModel: ts.CostModel.FixedPoint(),
    16. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    17. }
    18. match (ts.breakParagraph(input)) {
    19. case Ok(result) => {
    20. io.writeLine("{result.optimized.lines.length} {result.greedy.lines.length}")
    21. io.writeLine("{result.optimized.totalCost} {result.greedy.totalCost} {result.breakOpportunityCount}")
    22. }
    23. case Err(_) => io.writeLine("unexpected error")
    24. }
    Expected outputexit 0
    0 0
    0 0 0
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0 0
    2. 0 0 0
    Source evidence

    Report absent break opportunities

    Link
    main.tpzReport absent break opportunitiesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt })
    5. ]
    6. let input = ts.Paragraph {
    7. grid: ts.DistanceGrid.MilliPoint(),
    8. items: items,
    9. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    10. justification: ts.JustificationPolicy {
    11. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    12. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    13. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    14. latinSpacing: None(), cjkStretchRatio: 0.0
    15. },
    16. costModel: ts.CostModel.FixedPoint(),
    17. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    18. }
    19. match (ts.breakParagraph(input)) {
    20. case Err(NoBreakOpportunities()) => io.writeLine("NoBreakOpportunities")
    21. case _ => io.writeLine("unexpected result")
    22. }
    Expected outputexit 0
    NoBreakOpportunities
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. NoBreakOpportunities
    Source evidence

    Report a missing greedy completion

    Link
    main.tpzReport a missing greedy completionOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "a", width: 1pt, trackingAfter: 0pt }),
    6. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Allowed(0), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    7. ]
    8. let input = ts.Paragraph {
    9. grid: ts.DistanceGrid.MilliPoint(),
    10. items: items,
    11. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    12. justification: ts.JustificationPolicy {
    13. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    14. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    15. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    16. latinSpacing: None(), cjkStretchRatio: 0.0
    17. },
    18. costModel: ts.CostModel.FixedPoint(),
    19. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    20. }
    21. match (ts.breakParagraph(input)) {
    22. case Err(NoGreedyPlan()) => io.writeLine("NoGreedyPlan")
    23. case _ => io.writeLine("unexpected result")
    24. }
    Expected outputexit 0
    NoGreedyPlan
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. NoGreedyPlan
    Source evidence

    Report three forced soft hyphens in a row

    Link
    main.tpzReport three forced soft hyphens in a rowOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.LatinSoftHyphen(), flagged: false }),
    6. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "a", width: 1pt, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.LatinSoftHyphen(), flagged: false }),
    8. ts.Item.BoxItem(ts.Box { unitIndex: 2, text: "a", width: 1pt, trackingAfter: 0pt }),
    9. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 3, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.LatinSoftHyphen(), flagged: false })
    10. ]
    11. let input = ts.Paragraph {
    12. grid: ts.DistanceGrid.MilliPoint(),
    13. items: items,
    14. geometry: ts.LineGeometry { bodyTarget: 1pt, firstTarget: 1pt, dropCapSpan: 0, dropCapInset: 0pt },
    15. justification: ts.JustificationPolicy {
    16. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    17. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    18. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    19. latinSpacing: None(), cjkStretchRatio: 0.0
    20. },
    21. costModel: ts.CostModel.FixedPoint(),
    22. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    23. }
    24. match (ts.breakParagraph(input)) {
    25. case Err(NoPlan()) => io.writeLine("NoPlan")
    26. case _ => io.writeLine("unexpected result")
    27. }
    Expected outputexit 0
    NoPlan
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. NoPlan
    Source evidence

    Compute each line target from one geometry

    Link
    Ruletopaz.stdlib.contract.typeset-line-geometry.rule

    Compute each line target from one geometry

    LineGeometry.bodyTarget and firstTarget are positive Length<Pt> distances that remain positive after conversion to the selected grid. The line beginning at unit zero uses firstTarget. Each later line uses bodyTarget.

    dropCapSpan is an integer from zero through three. dropCapInset is a nonnegative Length<Pt>. When both are positive, subtract the inset from the first dropCapSpan line targets. A zero span or zero inset leaves the base targets unchanged.

    An active inset must be smaller than both firstTarget and bodyTarget after quantization. A nonpositive base target, span outside zero through three, negative inset, or inset that consumes a target returns TypesetError.InvalidPolicy before item validation.

    PlanLine.target reports the resulting physical target after the first line choice, quantization, and any inset. Geometry selects the available width and does not alter source unit indices or measured text widths.

    Why

    One geometry record describes first line indentation and the finite span of a drop cap. Checking effective targets before searching prevents different search paths from assigning different meanings to unusable widths.

    Source evidence

    Apply the first target and drop cap inset in line order

    Link
    main.tpzApply the first target and drop cap inset in line orderOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 3pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false }),
    6. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "a", width: 3pt, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    8. ]
    9. let input = ts.Paragraph {
    10. grid: ts.DistanceGrid.MilliPoint(),
    11. items: items,
    12. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 8pt, dropCapSpan: 1, dropCapInset: 2pt },
    13. justification: ts.JustificationPolicy {
    14. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    15. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    16. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    17. latinSpacing: None(), cjkStretchRatio: 0.0
    18. },
    19. costModel: ts.CostModel.FixedPoint(),
    20. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    21. }
    22. match (ts.breakParagraph(input)) {
    23. case Ok(result) => {
    24. let first = result.optimized.lines[0]
    25. let second = result.optimized.lines[1]
    26. io.writeLine("{first.target == 6pt} {second.target == 10pt}")
    27. }
    28. case Err(_) => io.writeLine("unexpected error")
    29. }
    Expected outputexit 0
    true true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true true
    Source evidence

    Reject a zero line target before planning

    Link
    main.tpzReject a zero line target before planningOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 0pt, firstTarget: 0pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    Source evidence

    Reject a negative drop cap inset

    Link
    main.tpzReject a negative drop cap insetOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 1, dropCapInset: -1pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    Source evidence

    Read plan boundaries, measurements, and scores separately

    Link

    Read plan boundaries, measurements, and scores separately

    ParagraphPlan.lines contains line records in source order. Each nonempty line consumes the half-open unit interval from start through endExclusive. The first line starts at zero, adjacent lines share an endpoint, and the last line ends at the unit count. These are BoxItem and GlueItem indices rather than item array offsets, scalar offsets, or byte offsets.

    trimmedEndExclusive removes trailing units whose complete text is an ASCII space or a no-break space. It stays between start and endExclusive. displayTextScalarLength counts Unicode scalars in the consumed text after trailing ASCII spaces and no-break spaces are removed. It excludes the selected penalty insertion and retains leading text.

    A unit width includes its following tracking contribution. rawTextWidth is the sum of consumed unit widths minus the last consumed unit trackingAfter. trimmedTextWidth applies the same formula to the trimmed unit interval. An empty trimmed interval has zero width. Reading a plan does not reshape text or modify source measurements.

    kind reports the selected penalty kind. selectedPenaltyWidth reports that selected penalty width after conversion to the computation grid for every kind and cost model. The insertion contributes at the selected boundary and leaves the stored text, source indices, and displayTextScalarLength unchanged.

    FixedPoint costTextWidth is the raw width, capped at the target minus selectedPenaltyWidth on a nonfinal line with bounded spacing capacity. KnuthPlass costTextWidth excludes leading GlueItem units and excludes trailing GlueItem units for an ordinary break. A Forced KnuthPlass break retains its trailing glue and associated capacity.

    FixedPoint postValidationWidthLimit is Some(limit) with the effective target, nonfinal shrink allowance, and one grid unit of rounding allowance. It reports the optimized admission threshold. Single-unit lines and greedy choices can exceed it. KnuthPlass reports None() because its admission is expressed by glue capacity and adjustment ratios.

    cost is the selected line contribution under the paragraph cost model, including applicable penalty and transition demerits. adjustmentRatioR is zero for FixedPoint. For KnuthPlass it is the signed ratio to the selected stretch or shrink capacity, scaled by 100000000 and rounded to nearest with ties to even.

    ParagraphPlan.totalCost is the sum of its line costs in line order. Scores can be negative when an Allowed penalty rewards a break. A score is interpreted with its CostModel and policies. ParagraphPlan.grid reports the computation grid, while all reported distances retain Length<Pt>.

    For an applicable FixedPoint bounded fill search, optimized.boundedFillFeasible is Some(true) when that search succeeds and Some(false) when the second search supplies the plan. In other cases it is None(). The greedy plan always reports None(). This field records feasibility within the configured bounded search.

    ParagraphResult.optimized and greedy contain the two plans from the same validated input. breakOpportunityCount counts all eligible penalty entries before planning, including equal-boundary duplicates and the supplied terminal candidate. It is independent of the number of output lines.

    Why

    A renderer needs source consumption, display trimming, width admission, and scoring as separate measurements. Explicit indices and optional feasibility results let the renderer inspect source consumption and width fit for every selected line.

    Source evidence

    Keep consumed units distinct from displayed trailing space

    Link
    main.tpzKeep consumed units distinct from displayed trailing spaceOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 3pt, trackingAfter: 0pt }),
    5. ts.Item.GlueItem(ts.Glue { unitIndex: 1, text: " ", width: 1pt, trackingAfter: 0pt, stretch: 0pt, shrink: 0pt }),
    6. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    7. ]
    8. let input = ts.Paragraph {
    9. grid: ts.DistanceGrid.MilliPoint(),
    10. items: items,
    11. geometry: ts.LineGeometry { bodyTarget: 4pt, firstTarget: 4pt, dropCapSpan: 0, dropCapInset: 0pt },
    12. justification: ts.JustificationPolicy {
    13. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    14. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    15. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    16. latinSpacing: None(), cjkStretchRatio: 0.0
    17. },
    18. costModel: ts.CostModel.FixedPoint(),
    19. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    20. }
    21. match (ts.breakParagraph(input)) {
    22. case Ok(result) => {
    23. let plan = result.optimized
    24. let line = plan.lines[0]
    25. io.writeLine("{line.start} {line.endExclusive} {line.trimmedEndExclusive} {line.displayTextScalarLength}")
    26. io.writeLine("{line.rawTextWidth == 4pt} {line.trimmedTextWidth == 3pt} {line.costTextWidth == 4pt}")
    27. io.writeLine("{line.selectedPenaltyWidth == 0pt} {line.adjustmentRatioR}")
    28. match (line.postValidationWidthLimit) {
    29. case Some(limit) => io.writeLine("{limit == 4.001pt}")
    30. case None() => io.writeLine("missing limit")
    31. }
    32. match (plan.boundedFillFeasible) {
    33. case None() => io.writeLine("no fill search")
    34. case Some(_) => io.writeLine("unexpected fill search")
    35. }
    36. }
    37. case Err(_) => io.writeLine("unexpected error")
    38. }
    Expected outputexit 0
    0 2 1 1
    true true true
    true 0
    true
    no fill search
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0 2 1 1
    2. true true true
    3. true 0
    4. true
    5. no fill search
    Source evidence

    Report an indivisible unit wider than its target

    Link
    main.tpzReport an indivisible unit wider than its targetOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 5pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 1pt, firstTarget: 1pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Ok(result) => {
    22. let line = result.optimized.lines[0]
    23. io.writeLine("{line.start} {line.endExclusive} {line.trimmedTextWidth == 5pt} {line.target == 1pt}")
    24. }
    25. case Err(_) => io.writeLine("unexpected error")
    26. }
    Expected outputexit 0
    0 1 true true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0 1 true true
    Source evidence

    Set Knuth Plass tolerance and transition costs

    Link

    Set Knuth Plass tolerance and transition costs

    Every KnuthPlassPolicy field is a nonnegative int. A negative toleranceR, linePenalty, fitnessDemerit, flaggedDemerit, or finalFlaggedDemerit returns TypesetError.InvalidPolicy. Validation applies even when the selected CostModel is FixedPoint.

    KnuthPlass uses explicit GlueItem stretch and shrink. JustificationPolicy values are validated but do not replace these capacities or its ratio score. Leading glue is discarded for the natural width and capacities. An ordinary break discards trailing glue, while a Forced break consumes trailing glue so a caller can represent a ragged final line.

    Let slack be the target minus natural text width and selected penalty width. Nonnegative slack uses total stretch and negative slack uses total shrink. Zero capacity admits an exact fit and rejects a nonzero slack. Shrink admits ratios down to negative one. Positive ratios must be no greater than toleranceR divided by 100000000.

    The reported adjustment ratio is scaled by 100000000 and rounded to nearest with ties to even. Admission and fitness use the exact ratio before rounding. Badness is 100 times the absolute ratio cubed, rounded to nearest with ties to even and capped at 10000.

    Add linePenalty and badness, then square their sum for the basic line score. An Allowed penalty adds its squared value when nonnegative and subtracts its squared value when negative. Forced penalties add no such term. The resulting score is a signed checked int.

    The four fitness classes are ratios below negative one half, ratios from negative one half through one half, ratios above one half through one, and ratios above one. Add fitnessDemerit when consecutive lines differ by more than one class. The first line has no preceding fitness transition.

    Add flaggedDemerit when both the preceding and selected penalties are flagged. Add finalFlaggedDemerit when the final line follows a flagged penalty, whether or not its own penalty is flagged. The first line has no preceding flag.

    An exact fit with linePenalty equal to 10, zero penalty cost, and no transition demerit has line cost 100 and adjustmentRatioR equal to zero. A nonexact line with no usable stretch or shrink is inadmissible, including a final line whose caller supplied no terminal capacity.

    Why

    Tolerance controls acceptable adjustment and demerits control the relative cost of adjacent lines. Explicit glue capacities make final line behavior and flagged transitions reproducible without hidden default glue or a separate final line algorithm.

    Source evidence

    Score an exact Knuth Plass line

    Link
    main.tpzScore an exact Knuth Plass lineOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 10pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.KnuthPlass(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Ok(result) => {
    22. let line = result.optimized.lines[0]
    23. io.writeLine("{line.cost} {line.adjustmentRatioR} {result.optimized.totalCost}")
    24. match (line.postValidationWidthLimit) {
    25. case None() => io.writeLine("ratio admission")
    26. case Some(_) => io.writeLine("unexpected width limit")
    27. }
    28. }
    29. case Err(_) => io.writeLine("unexpected error")
    30. }
    Expected outputexit 0
    100 0 100
    ratio admission
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 100 0 100
    2. ratio admission
    Source evidence

    Validate Knuth Plass policy before choosing a cost model

    Link
    main.tpzValidate Knuth Plass policy before choosing a cost modelOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: -1, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    Source evidence

    Report a line with no capacity to reach its target

    Link
    main.tpzReport a line with no capacity to reach its targetOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 9pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.KnuthPlass(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(NoGreedyPlan()) => io.writeLine("NoGreedyPlan")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    NoGreedyPlan
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. NoGreedyPlan
    Source evidence

    Preserve measured units and validate their physical extents

    Link

    Preserve measured units and validate their physical extents

    Item.BoxItem, Item.GlueItem, and Item.PenaltyItem preserve their record payloads. Constructing these records and variants does not plan a paragraph or validate its measurements.

    BoxItem and GlueItem contribute one measured unit each in their encounter order. Their unitIndex fields must be exactly zero through the unit count minus one. PenaltyItem contributes a boundary without consuming a unit index.

    Box.text and Glue.text retain the supplied string, including empty text and multiple Unicode scalars. A measured unit remains indivisible. Unit indices count these records rather than bytes, scalars, or grapheme clusters.

    Box.width and Glue.width include trackingAfter. Width and width minus trackingAfter must both be nonnegative. Tracking may be positive, zero, or negative while those two conditions hold.

    Each width, tracking amount, stretch, and shrink is a Length<Pt>. Paragraph planning quantizes it through the selected grid. A line span sums its unit widths and removes the final unit's trackingAfter.

    Glue.stretch and Glue.shrink are nonnegative adjustment capacities. A box supplies zero capacity. The selected cost and justification policies determine how capacities participate in planning.

    Positive capacity does not itself insert text or change the input records. Unjustified planning performs no distributed adjustment. Text trimming and model-specific glue treatment follow the PlanLine and paragraph contracts.

    breakParagraph returns InvalidInput for a noncontiguous unitIndex, a negative width or capacity, or a negative width before tracking. Validation applies even when a later candidate would discard that unit.

    The function uses the supplied measurements without shaping the text again. It does not reorder or mutate the caller's item array or its records.

    Why

    Measured text already has a source order and physical advances. Preserving those units keeps line breaking compatible with shaping. Explicit tracking and capacities give every planner the same measured input.

    Source evidence

    Keep measured units and signed tracking in source order

    Link
    main.tpzKeep measured units and signed tracking in source orderOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 3, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false }),
    5. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "AB", width: 3pt, trackingAfter: 1pt }),
    6. ts.Item.GlueItem(ts.Glue { unitIndex: 1, text: " ", width: 2pt, trackingAfter: 0.5pt, stretch: 1pt, shrink: 1pt }),
    7. ts.Item.BoxItem(ts.Box { unitIndex: 2, text: "C", width: 4pt, trackingAfter: -1pt })
    8. ]
    9. let input = ts.Paragraph {
    10. grid: ts.DistanceGrid.MilliPoint(),
    11. items: items,
    12. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    13. justification: ts.JustificationPolicy {
    14. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    15. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    16. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    17. latinSpacing: None(), cjkStretchRatio: 0.0
    18. },
    19. costModel: ts.CostModel.FixedPoint(),
    20. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    21. }
    22. match (ts.breakParagraph(input)) {
    23. case Ok(result) => {
    24. let line = result.optimized.lines[0]
    25. io.writeLine("{line.start}:{line.endExclusive}:{line.displayTextScalarLength}")
    26. io.writeLine("{line.rawTextWidth == 10pt}:{line.trimmedTextWidth == 10pt}")
    27. io.writeLine("{items.length}")
    28. match (items[0]) {
    29. case PenaltyItem(_) => io.writeLine("penalty first")
    30. case _ => io.writeLine("changed input")
    31. }
    32. }
    33. case Err(_) => io.writeLine("unexpected error")
    34. }
    Expected outputexit 0
    0:3:4
    true:true
    4
    penalty first
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 0:3:4
    2. true:true
    3. 4
    4. penalty first
    Source evidence

    Keep a zero width unit with empty display text

    Link
    main.tpzKeep a zero width unit with empty display textOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "", width: 0pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 1pt, firstTarget: 1pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Ok(result) => {
    22. let line = result.optimized.lines[0]
    23. io.writeLine("{line.endExclusive}:{line.displayTextScalarLength}:{line.rawTextWidth == 0pt}")
    24. }
    25. case Err(_) => io.writeLine("unexpected error")
    26. }
    Expected outputexit 0
    1:0:true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1:0:true
    Source evidence

    Reject a gap in measured unit indices

    Link
    main.tpzReject a gap in measured unit indicesOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidInput(_)) => io.writeLine("InvalidInput")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    Source evidence

    Reject negative glue stretch and shrink

    Link
    main.tpzReject negative glue stretch and shrinkOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function report(items: Array<ts.Item>) -> () {
    4. let input = ts.Paragraph {
    5. grid: ts.DistanceGrid.MilliPoint(),
    6. items: items,
    7. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    8. justification: ts.JustificationPolicy {
    9. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    10. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    11. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    12. latinSpacing: None(), cjkStretchRatio: 0.0
    13. },
    14. costModel: ts.CostModel.FixedPoint(),
    15. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    16. }
    17. match (ts.breakParagraph(input)) {
    18. case Err(InvalidInput(_)) => io.writeLine("InvalidInput")
    19. case _ => io.writeLine("unexpected result")
    20. }
    21. }
    22. report([ts.Item.GlueItem(ts.Glue { unitIndex: 0, text: " ", width: 1pt, trackingAfter: 0pt, stretch: -1pt, shrink: 0pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    23. report([ts.Item.GlueItem(ts.Glue { unitIndex: 0, text: " ", width: 1pt, trackingAfter: 0pt, stretch: 0pt, shrink: -1pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    Expected outputexit 0
    InvalidInput
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    2. InvalidInput
    Source evidence

    Reject negative widths and negative extents before tracking

    Link
    main.tpzReject negative widths and negative extents before trackingOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function report(items: Array<ts.Item>) -> () {
    4. let input = ts.Paragraph {
    5. grid: ts.DistanceGrid.MilliPoint(),
    6. items: items,
    7. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    8. justification: ts.JustificationPolicy {
    9. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    10. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    11. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    12. latinSpacing: None(), cjkStretchRatio: 0.0
    13. },
    14. costModel: ts.CostModel.FixedPoint(),
    15. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    16. }
    17. match (ts.breakParagraph(input)) {
    18. case Err(InvalidInput(_)) => io.writeLine("InvalidInput")
    19. case _ => io.writeLine("unexpected result")
    20. }
    21. }
    22. report([ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: -1pt, trackingAfter: 0pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    23. report([ts.Item.GlueItem(ts.Glue { unitIndex: 0, text: " ", width: -1pt, trackingAfter: 0pt, stretch: 0pt, shrink: 0pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    24. report([ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 2pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    25. report([ts.Item.GlueItem(ts.Glue { unitIndex: 0, text: " ", width: 1pt, trackingAfter: 2pt, stretch: 0pt, shrink: 0pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    Expected outputexit 0
    InvalidInput
    InvalidInput
    InvalidInput
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    2. InvalidInput
    3. InvalidInput
    4. InvalidInput
    Source evidence

    Apply penalties at measured unit boundaries

    Link
    Ruletopaz.stdlib.contract.typeset-penalties.rule

    Apply penalties at measured unit boundaries

    Penalty.unitEnd is the exclusive end of a measured unit span. It must be greater than zero and no greater than the paragraph unit count. The penalty's position in the Item array does not set that boundary.

    Planning collects penalties and visits them in increasing unitEnd order. Equal endpoints retain input order. Duplicate endpoints remain separate candidates and are counted separately in breakOpportunityCount.

    Penalty.rule determines whether a candidate is selectable and how its additional cost is interpreted. Forbidden removes that candidate. A selectable duplicate at the same endpoint remains available. A Forced entry prevents any line from crossing that boundary.

    Penalty.kind describes the selected boundary independently of its rule. For example a Forced rule can carry a LatinHyphen kind. The chosen line reports the selected candidate's kind.

    Penalty.width is a nonnegative Length<Pt> that contributes when that candidate ends a line. Both cost models include this width for every kind. Unselected candidates contribute no width.

    PlanLine.selectedPenaltyWidth reports that selected width separately from the measured text widths. Input penalty widths are validated even for Forbidden candidates.

    Penalty.flagged marks a break for KnuthPlassPolicy.flaggedDemerit and finalFlaggedDemerit. FixedPoint does not use this flag. Its consecutive soft hyphen rule depends on kind.

    An out-of-range unitEnd or negative penalty width returns InvalidInput from breakParagraph. Record and Item construction preserve the supplied data until that call validates it.

    A complete plan needs a selectable terminal candidate at the unit count. Paragraph planning does not synthesize a terminal penalty or invoke break classification on the measured items.

    Why

    A boundary candidate separates admissibility, category, visible width, and cost history. The same selected width has one meaning in either cost model. Keeping duplicate candidates distinct permits explicit alternatives without changing source unit indices.

    Source evidence

    Include a selected nonsoft penalty width with fixed costs

    Link
    main.tpzInclude a selected nonsoft penalty width with fixed costsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 4pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 1pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.LatinHyphen(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 5pt, firstTarget: 5pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Ok(result) => {
    22. let line = result.optimized.lines[0]
    23. io.writeLine("{line.selectedPenaltyWidth == 1pt}:{line.costTextWidth == 4pt}:{line.cost}")
    24. }
    25. case Err(_) => io.writeLine("unexpected error")
    26. }
    Expected outputexit 0
    true:true:350000000
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true:true:350000000
    Source evidence

    Include a selected nonsoft penalty width with knuth costs

    Link
    main.tpzInclude a selected nonsoft penalty width with knuth costsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 4pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 1pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.LatinHyphen(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 5pt, firstTarget: 5pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.KnuthPlass(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Ok(result) => {
    22. let line = result.optimized.lines[0]
    23. io.writeLine("{line.selectedPenaltyWidth == 1pt}:{line.costTextWidth == 4pt}:{line.cost}")
    24. }
    25. case Err(_) => io.writeLine("unexpected error")
    26. }
    Expected outputexit 0
    true:true:100
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true:true:100
    Source evidence

    Reject boundaries outside the measured units

    Link
    main.tpzReject boundaries outside the measured unitsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function report(items: Array<ts.Item>) -> () {
    4. let input = ts.Paragraph {
    5. grid: ts.DistanceGrid.MilliPoint(),
    6. items: items,
    7. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    8. justification: ts.JustificationPolicy {
    9. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    10. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    11. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    12. latinSpacing: None(), cjkStretchRatio: 0.0
    13. },
    14. costModel: ts.CostModel.FixedPoint(),
    15. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    16. }
    17. match (ts.breakParagraph(input)) {
    18. case Err(InvalidInput(_)) => io.writeLine("InvalidInput")
    19. case _ => io.writeLine("unexpected result")
    20. }
    21. }
    22. report([ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 0, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    23. report([ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }), ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })])
    Expected outputexit 0
    InvalidInput
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    2. InvalidInput
    Source evidence

    Reject a negative boundary width

    Link
    main.tpzReject a negative boundary widthOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: -1pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidInput(_)) => io.writeLine("InvalidInput")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidInput
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidInput
    Source evidence

    Distinguish invalid data from unavailable plans

    Link
    Ruletopaz.stdlib.contract.typeset-errors.rule

    Distinguish invalid data from unavailable plans

    TypesetError values are returned in Err by the typesetting operations that declare them. InvalidInput carries explanatory text for invalid measured data or boundary structure. InvalidPolicy carries explanatory text for an invalid algorithm configuration.

    The string payload describes a cause. Programs distinguish failure categories by matching the variant rather than comparing library message text. Constructing an error value directly preserves its supplied string.

    NoBreakOpportunities means a nonempty paragraph has no selectable penalty candidate after validation and filtering. It does not mean that the selected cost model searched unsuccessfully.

    NoGreedyPlan means a validated paragraph has selectable candidates but its deterministic greedy baseline cannot reach the final unit. NoPlan means that baseline completed but the selected optimization search found no permitted complete plan.

    A successful empty paragraph has two empty plans and zero candidates under the paragraph contract. It is not a missing-opportunity failure.

    breakParagraph validates policy configuration before measured items. It then checks candidates, builds the greedy plan, and searches for an optimized plan in that order. The first failing stage determines the returned variant.

    The policy stages check geometry, the cost model and grid combination, justification settings, and KnuthPlass settings. Item validation follows source encounter order and checks collected penalty endpoints after the measured units.

    classifyBreaks applies its input and break-policy validation rules. The shared error variants do not impose paragraph validation order on break classification.

    An Err contains no partial ParagraphResult and does not mutate the supplied input. Previously completed argument effects remain completed. A failure while evaluating an argument keeps its ordinary language diagnostic and prevents the typesetting call.

    Why

    Applications can repair malformed data, choose another policy, or report the absence of a plan when these outcomes stay distinct. Named variants provide that decision without relying on diagnostic wording or silently substituting a different algorithm.

    Source evidence

    Preserve explanatory error payloads

    Link
    main.tpzPreserve explanatory error payloadsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let failures: Array<ts.TypesetError> = [
    4. ts.TypesetError.InvalidInput("measured unit"),
    5. ts.TypesetError.InvalidPolicy("line target")
    6. ]
    7. for failure in (failures) {
    8. match (failure) {
    9. case InvalidInput(message) => io.writeLine("input {message}")
    10. case InvalidPolicy(message) => io.writeLine("policy {message}")
    11. case _ => io.writeLine("unexpected variant")
    12. }
    13. }
    Expected outputexit 0
    input measured unit
    policy line target
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. input measured unit
    2. policy line target
    Source evidence

    Report an invalid paragraph policy before an invalid unit index

    Link
    main.tpzReport an invalid paragraph policy before an invalid unit indexOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "a", width: 1pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 0pt, firstTarget: 0pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    Source evidence

    Score justification with explicit policy precedence

    Link
    Ruletopaz.stdlib.contract.typeset-justification.rule

    Score justification with explicit policy precedence

    JustificationPolicy describes how FixedPoint scores a measured line. It does not change text, insert glyphs, or create break opportunities. Constructing the record does not run validation. breakParagraph validates it before measuring items.

    minGapCount, minLineWidthRatioM, maxGapRatioR, and a present minFinalLineWidthRatioM must be nonnegative. maxAdjustment and maxGapAdjustment must be nonnegative point lengths. cjkStretchRatio must be finite and nonnegative. Violations return TypesetError.InvalidPolicy with an explanatory string.

    The width ratio fields use 1000 for one whole target width. maxGapRatioR uses 100000000 for a ratio of one. These ratios have no additional upper bound. A threshold above the attainable width can increase cost without making the policy malformed.

    A present LatinSpacing value follows its typed length contract. minWordSpaceRatio ranges from 0.8 through 1.0. maxWordSpaceRatio ranges from 1.0 through 1.33. maxLetterSpacingEm ranges from 0em through 0.015em and fontSizePt is positive. Both float ratios must be finite. Invalid values return TypesetError.InvalidPolicy even when the selected model does not use their capacities.

    Unjustified contributes zero justification cost and supplies no inferred spacing capacity. LatinWordSpace scores eligible word spaces. CjkInterCharacter also recognizes eligible boundaries between single CJK units. All policy fields are validated in every mode.

    KnuthPlass computes its adjustment ratio from the supplied Glue stretch and shrink values. JustificationPolicy does not alter that ratio or its demerits. The policy still undergoes validation. The KnuthPlass policy defines tolerance and line costs.

    For FixedPoint, a present LatinSpacing value takes precedence when the trimmed line contains Latin letter units without other alphabetic scripts, or contains a recognized single CJK unit. The inferred capacity replaces the fallback minimum gap count, minimum nonfinal width, total adjustment, per gap adjustment, and gap ratio tests for that line.

    Inferred shrink capacity is the floor of the total U+0020 unit width multiplied by one minus minWordSpaceRatio. Stretch capacity adds three separately evaluated contributions. They are the floor of that space width multiplied by maxWordSpaceRatio minus one, the eligible Latin letter gap count multiplied by the resolved typed letter maximum, and the floor of the eligible CJK width multiplied by cjkStretchRatio. The CJK contribution is zero in LatinWordSpace mode.

    An inferred Latin letter unit contains an Alphabetic scalar and has every scalar in U+0041 through U+024F, U+0300 through U+036F, or U+1E00 through U+1EFF. A letter gap joins two adjacent such units. A CJK capacity contribution uses the left width of adjacent single CJK units. Resolve the typed letter maximum with the explicit font size before grid conversion. Preserve each independent floor for dimensionless word and CJK ratios.

    Without an inferred capacity, word gaps are units whose complete text is U+0020. CJK gaps join adjacent recognized single CJK units and count both sides of an intervening U+0020 or U+00A0 unit. Recognized CJK scalars are U+1100 through U+11FF, U+3130 through U+318F, U+3400 through U+4DBF, U+4E00 through U+9FFF, U+AC00 through U+D7A3, and U+F900 through U+FAFF. Punctuation does not provide a CJK gap.

    The fallback nonfinal calculation first scores a line below minLineWidthRatioM. It then checks the total positive adjustment against maxAdjustment. LatinWordSpace uses minGapCount and maxGapAdjustment on word gaps. CjkInterCharacter uses word gaps when present and otherwise uses CJK gaps, minGapCount, maxGapAdjustment, and maxGapRatioR. Falling short raises the justification cost rather than returning an input error.

    FixedPoint calculations use the millipoint grid. A total positive adjustment of at most one millipoint has zero fallback cost. For a larger adjustment, maxAdjustment is the exact total bound. Per gap checks tolerate one millipoint per gap. The CJK ratio comparison tolerates 100000 units of maxGapRatioR. These allowances do not add spacing to the returned text.

    For a final FixedPoint line in a justified mode, None() in minFinalLineWidthRatioM contributes zero justification cost. Some supplies a minimum final width ratio and applies to that final line. It never replaces the nonfinal checks. Compare the attainable width after the permitted positive adjustment with the target times the ratio divided by 1000, rounded halfway upward.

    For that final threshold, attainable width adds inferred stretch when available. Otherwise it adds the smaller of maxAdjustment and the word gap count times maxGapAdjustment. Meeting the threshold contributes zero. A shortfall contributes 25000000000 plus the halfway upward rounded shortfall times 1000000 divided by the target, multiplied by 1000000.

    The optimized FixedPoint planner can try a pass that requires bounded fill before its ordinary pass. The paragraph plan reports whether that pass succeeded. A returned plan does not distribute the requested adjustment into the original units. Consumers use the reported widths and the selected policy when rendering.

    When the exact nonfinal minimum width comparison fails, the scoring shortfall is target minus natural width, with a minimum of zero. Scale that shortfall by 1000000, divide by target with halfway upward rounding, then multiply by 1000000 for LatinWordSpace or 100000 for CjkInterCharacter. Add 25000000000 to this nonnegative contribution. These operations use checked arithmetic.

    For every FixedPoint justification comparison, natural width is the trimmed text width plus selectedPenaltyWidth. The target remains PlanLine.target. A selected insertion supplies no word gap, CJK gap, letter gap, stretch, or shrink capacity.

    Why

    A caller needs to know which spacing controls determine a line cost. Explicit capacity precedence keeps the two cost models predictable. A distinct final line threshold gives its name one meaning and preserves ordinary nonfinal checks.

    Source evidence

    Choose a justification mode explicitly

    Link
    main.tpzChoose a justification mode explicitlyOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. function name(mode: ts.JustificationMode) -> string {
    4. match (mode) {
    5. case Unjustified() => "unjustified"
    6. case LatinWordSpace() => "latin"
    7. case CjkInterCharacter() => "cjk"
    8. }
    9. }
    10. io.writeLine(name(ts.JustificationMode.Unjustified()))
    11. io.writeLine(name(ts.JustificationMode.LatinWordSpace()))
    12. io.writeLine(name(ts.JustificationMode.CjkInterCharacter()))
    Expected outputexit 0
    unjustified
    latin
    cjk
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. unjustified
    2. latin
    3. cjk
    Source evidence

    Reject a negative gap count even in Unjustified mode

    Link
    main.tpzReject a negative gap count even in Unjustified modeOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 10pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: -1,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    Source evidence

    Reject a negative physical adjustment cap

    Link
    main.tpzReject a negative physical adjustment capOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 10pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    14. maxAdjustment: -1pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    Source evidence

    Reject a negative final line ratio

    Link
    main.tpzReject a negative final line ratioOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. let items: Array<ts.Item> = [
    4. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 10pt, trackingAfter: 0pt }),
    5. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    6. ]
    7. let input = ts.Paragraph {
    8. grid: ts.DistanceGrid.MilliPoint(),
    9. items: items,
    10. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    11. justification: ts.JustificationPolicy {
    12. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    13. minLineWidthRatioM: 0, minFinalLineWidthRatioM: Some(-1),
    14. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    15. latinSpacing: None(), cjkStretchRatio: 0.0
    16. },
    17. costModel: ts.CostModel.FixedPoint(),
    18. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    19. }
    20. match (ts.breakParagraph(input)) {
    21. case Err(InvalidPolicy(_)) => io.writeLine("InvalidPolicy")
    22. case _ => io.writeLine("unexpected result")
    23. }
    Expected outputexit 0
    InvalidPolicy
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. InvalidPolicy
    Source evidence

    Score an underfilled nonfinal line without changing measured text

    Link
    main.tpzScore an underfilled nonfinal line without changing measured textOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function sample(justification: ts.JustificationPolicy) -> ts.ParagraphResult {
    5. let items: Array<ts.Item> = [
    6. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 4pt, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false }),
    8. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "next", width: 4pt, trackingAfter: 0pt }),
    9. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    10. ]
    11. let input = ts.Paragraph {
    12. grid: ts.DistanceGrid.MilliPoint(),
    13. items: items,
    14. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    15. justification: justification,
    16. costModel: ts.CostModel.FixedPoint(),
    17. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    18. }
    19. test.assertOk(ts.breakParagraph(input))
    20. }
    21. let natural = sample(ts.JustificationPolicy {
    22. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    23. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    24. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    25. latinSpacing: None(), cjkStretchRatio: 0.0
    26. })
    27. let justified = sample(ts.JustificationPolicy {
    28. mode: ts.JustificationMode.LatinWordSpace(), minGapCount: 1,
    29. minLineWidthRatioM: 900, minFinalLineWidthRatioM: None(),
    30. maxAdjustment: 2pt, maxGapAdjustment: 1pt, maxGapRatioR: 0,
    31. latinSpacing: None(), cjkStretchRatio: 0.0
    32. })
    33. io.writeLine("{justified.optimized.lines.length}")
    34. io.writeLine("{justified.optimized.lines[0].cost > natural.optimized.lines[0].cost}")
    35. io.writeLine("{justified.optimized.lines[1].cost == natural.optimized.lines[1].cost}")
    36. io.writeLine("{justified.optimized.lines[0].adjustmentRatioR}")
    Expected outputexit 0
    2
    true
    true
    0
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2
    2. true
    3. true
    4. 0
    Source evidence

    Apply the final line threshold to the final line

    Link
    main.tpzApply the final line threshold to the final lineOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function sample(justification: ts.JustificationPolicy) -> ts.ParagraphResult {
    5. let items: Array<ts.Item> = [
    6. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 10pt, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    8. ]
    9. let input = ts.Paragraph {
    10. grid: ts.DistanceGrid.MilliPoint(),
    11. items: items,
    12. geometry: ts.LineGeometry { bodyTarget: 20pt, firstTarget: 20pt, dropCapSpan: 0, dropCapInset: 0pt },
    13. justification: justification,
    14. costModel: ts.CostModel.FixedPoint(),
    15. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    16. }
    17. test.assertOk(ts.breakParagraph(input))
    18. }
    19. let unconstrained = sample(ts.JustificationPolicy {
    20. mode: ts.JustificationMode.LatinWordSpace(), minGapCount: 0,
    21. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    22. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    23. latinSpacing: None(), cjkStretchRatio: 0.0
    24. })
    25. let constrained = sample(ts.JustificationPolicy {
    26. mode: ts.JustificationMode.LatinWordSpace(), minGapCount: 0,
    27. minLineWidthRatioM: 0, minFinalLineWidthRatioM: Some(1000),
    28. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    29. latinSpacing: None(), cjkStretchRatio: 0.0
    30. })
    31. io.writeLine("{constrained.optimized.lines[0].cost > unconstrained.optimized.lines[0].cost}")
    Expected outputexit 0
    true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true
    Source evidence

    Keep the final line threshold out of nonfinal scoring

    Link
    main.tpzKeep the final line threshold out of nonfinal scoringOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function sample(justification: ts.JustificationPolicy) -> ts.ParagraphResult {
    5. let items: Array<ts.Item> = [
    6. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 4pt, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false }),
    8. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "next", width: 4pt, trackingAfter: 0pt }),
    9. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    10. ]
    11. let input = ts.Paragraph {
    12. grid: ts.DistanceGrid.MilliPoint(),
    13. items: items,
    14. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    15. justification: justification,
    16. costModel: ts.CostModel.FixedPoint(),
    17. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    18. }
    19. test.assertOk(ts.breakParagraph(input))
    20. }
    21. let ordinary = sample(ts.JustificationPolicy {
    22. mode: ts.JustificationMode.LatinWordSpace(), minGapCount: 1,
    23. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    24. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    25. latinSpacing: None(), cjkStretchRatio: 0.0
    26. })
    27. let finalZero = sample(ts.JustificationPolicy {
    28. mode: ts.JustificationMode.LatinWordSpace(), minGapCount: 1,
    29. minLineWidthRatioM: 0, minFinalLineWidthRatioM: Some(0),
    30. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    31. latinSpacing: None(), cjkStretchRatio: 0.0
    32. })
    33. io.writeLine("{ordinary.optimized.lines[0].cost == finalZero.optimized.lines[0].cost}")
    Expected outputexit 0
    true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true
    Source evidence

    Use explicit glue capacities for the KnuthPlass model

    Link
    main.tpzUse explicit glue capacities for the KnuthPlass modelOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function sample(justification: ts.JustificationPolicy) -> ts.ParagraphResult {
    5. let items: Array<ts.Item> = [
    6. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "word", width: 10pt, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    8. ]
    9. let input = ts.Paragraph {
    10. grid: ts.DistanceGrid.MilliPoint(),
    11. items: items,
    12. geometry: ts.LineGeometry { bodyTarget: 10pt, firstTarget: 10pt, dropCapSpan: 0, dropCapInset: 0pt },
    13. justification: justification,
    14. costModel: ts.CostModel.KnuthPlass(),
    15. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    16. }
    17. test.assertOk(ts.breakParagraph(input))
    18. }
    19. let natural = sample(ts.JustificationPolicy {
    20. mode: ts.JustificationMode.Unjustified(), minGapCount: 0,
    21. minLineWidthRatioM: 0, minFinalLineWidthRatioM: None(),
    22. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    23. latinSpacing: None(), cjkStretchRatio: 0.0
    24. })
    25. let scored = sample(ts.JustificationPolicy {
    26. mode: ts.JustificationMode.CjkInterCharacter(), minGapCount: 2,
    27. minLineWidthRatioM: 900, minFinalLineWidthRatioM: Some(1000),
    28. maxAdjustment: 3pt, maxGapAdjustment: 1pt, maxGapRatioR: 0,
    29. latinSpacing: None(), cjkStretchRatio: 0.2
    30. })
    31. io.writeLine("{natural.optimized.totalCost}")
    32. io.writeLine("{scored.optimized.totalCost}")
    33. io.writeLine("{scored.optimized.lines[0].adjustmentRatioR}")
    Expected outputexit 0
    100
    100
    0
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 100
    2. 100
    3. 0
    Source evidence

    Raise the score when an overwide unit misses a larger minimum

    Link
    main.tpzRaise the score when an overwide unit misses a larger minimumOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function sample(ratio: int) -> ts.ParagraphResult {
    5. let items: Array<ts.Item> = [
    6. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "wide", width: 1.5pt, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false }),
    8. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "end", width: 1pt, trackingAfter: 0pt }),
    9. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    10. ]
    11. let input = ts.Paragraph {
    12. grid: ts.DistanceGrid.MilliPoint(),
    13. items: items,
    14. geometry: ts.LineGeometry { bodyTarget: 1pt, firstTarget: 1pt, dropCapSpan: 0, dropCapInset: 0pt },
    15. justification: ts.JustificationPolicy {
    16. mode: ts.JustificationMode.LatinWordSpace(), minGapCount: 0,
    17. minLineWidthRatioM: ratio, minFinalLineWidthRatioM: None(),
    18. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    19. latinSpacing: None(), cjkStretchRatio: 0.0
    20. },
    21. costModel: ts.CostModel.FixedPoint(),
    22. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    23. }
    24. test.assertOk(ts.breakParagraph(input))
    25. }
    26. let ordinary = sample(0)
    27. let thresholded = sample(2000)
    28. io.writeLine("{thresholded.optimized.lines[0].cost > ordinary.optimized.lines[0].cost}")
    Expected outputexit 0
    true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true
    Source evidence

    Include an inserted width in justification without adding gaps

    Link
    main.tpzInclude an inserted width in justification without adding gapsOpen in Playground
    1. import std.typeset as ts
    2. import std.io
    3. import std.test
    4. function sample(textWidth: Length<Pt>, insertionWidth: Length<Pt>) -> ts.ParagraphResult {
    5. let items: Array<ts.Item> = [
    6. ts.Item.BoxItem(ts.Box { unitIndex: 0, text: "a", width: textWidth, trackingAfter: 0pt }),
    7. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 1, width: insertionWidth, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.LatinHyphen(), flagged: false }),
    8. ts.Item.BoxItem(ts.Box { unitIndex: 1, text: "tail", width: 5pt, trackingAfter: 0pt }),
    9. ts.Item.PenaltyItem(ts.Penalty { unitEnd: 2, width: 0pt, rule: ts.BreakRule.Forced(), kind: ts.BreakKind.ForcedEnd(), flagged: false })
    10. ]
    11. let input = ts.Paragraph {
    12. grid: ts.DistanceGrid.MilliPoint(),
    13. items: items,
    14. geometry: ts.LineGeometry { bodyTarget: 5pt, firstTarget: 5pt, dropCapSpan: 0, dropCapInset: 0pt },
    15. justification: ts.JustificationPolicy {
    16. mode: ts.JustificationMode.LatinWordSpace(), minGapCount: 0,
    17. minLineWidthRatioM: 1000, minFinalLineWidthRatioM: None(),
    18. maxAdjustment: 0pt, maxGapAdjustment: 0pt, maxGapRatioR: 0,
    19. latinSpacing: None(), cjkStretchRatio: 0.0
    20. },
    21. costModel: ts.CostModel.FixedPoint(),
    22. knuthPlass: ts.KnuthPlassPolicy { toleranceR: 100000000, linePenalty: 10, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0 }
    23. }
    24. test.assertOk(ts.breakParagraph(input))
    25. }
    26. let inserted = sample(4pt, 1pt)
    27. let measured = sample(5pt, 0pt)
    28. io.writeLine("{inserted.optimized.lines[0].cost == measured.optimized.lines[0].cost}")
    Expected outputexit 0
    true
    
    $ topaz run main.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. true
    Source evidence

    Menu

    Download