Skip to content

A furawake row laid out from bare advances: the Racket engine loses the Table 1 amount inside a column, and reads the boundary after the block against the wrong character #27

Description

@P4suta

The third coordinate of the family #23 and #24 record, turned up while measuring the two of
them and left out of PR #25 because the fix is wider than either of those was:

While measuring it, one further Racket coordinate turned up that is not fixed here and
is nobody's regression: a furawake row is laid out from bare advances, so a member
before a Table 1 amount inside its row is placed and reported the way #24 described for a
warichu. Fixing it needs the boundary after a furawake block to be read against the
block's last character rather than its first, which is a wider change to how
compose.rkt makes one item of a furawake — also its own issue.

This is that issue. It is a defect in the Racket engine on the footing #19, #23 and #24 were
filed on: the engine's own numbers disagree with each other, which no reading of JLReq
permits.

Input

※〉〈日※ — a §3.7.2 furawake of three full-em members, 〉〈日, set in two columns with a
one-fifth-em line gap, standing between two ordinary full-em characters. The caller states
one break inside the structure, at the third member, which is where §3.7.2's "the text is
broken in the indicated places" divides the columns: 〉〈 share the first row and has
the second.

is Appendix A cl-02 and is cl-01, so spec/captured/table1.en.tsv states the gap
between them as 1/2 be — half of the before character's own em, 500 units here — and it
falls inside the first row. Sixteen-em measure, so nothing adjusts. Schema-valid
protocol-v1 request (jlreq-conformance validate):

{"protocol":"jlreq.conformance/1","spec":"jlreq-2020-08-11+unicode-17.0.0","id":"issue/furawake-interior-boundary-table1-gap","request":{"source":"※〉〈日※","size":{"inline":1000,"block":1000},"frame":"full-em","clusters":[{"range":[0,3],"advance":1000},{"range":[3,6],"advance":1000},{"range":[6,9],"advance":1000},{"range":[9,12],"advance":1000},{"range":[12,15],"advance":1000}],"line_extent":16000,"breaks":[{"offset":9,"kind":"allowed"}],"constructs":[{"kind":"furawake","range":[3,12],"columns":2,"line_gap":200}],"alignment":"start","writing_mode":"horizontal-tb","style":{"profile":"jlreq-2020"}}}

Validated against crates/jlreq-conformance/protocol.schema.json, and accepted by all three
engines with no diagnostics.

What this library produced

The Racket reference engine, notated (inline_extent/block_extent) [inline:block+advance ...]:

(4500/2200) [0:0+1000 1000:-600+1000 2000:-600+1000 1000:600+1000 3500:0+1000]

, the furawake's first member, stands at 1000:-600 with a reported advance of 1000 —
its bare full-em body, with no Table 1 amount added. , the member right after it in the
same row
, stands at 2000:-600, which is 1000 units on, so the row is laid out as though
the boundary carried nothing. And the line's own last character, , stands at 3500:
1500 units past the block's first member, which is 500 more than the 1000-unit row the
engine just laid out.

The 500 units are not lost — they are put in the wrong place. furawake-item in
engines/racket/compose.rkt gives the whole block the class of its first cluster
((classify-cluster para (car clusters) style)), so the boundary after the block is read
as cl-02cl-19 rather than cl-19cl-19, and Table 1's 1/2 be is placed there
instead. Two errors of 500 units each, in opposite directions, and at this coordinate they
cancel in the line's inline_extent while leaving two cluster positions and one advance
wrong.

They do not cancel in general. ※・・日※, the same shape with cl-05 on both sides of the
in-row boundary — Table 1 1/4 be + 1/4 af, 500 units inside the row against 250 after the
block — differs in the line's own width as well:

racket   (4500/2200) [0:0+1250 1250:-600+1000 2250:-600+1000 1250:600+1000 3500:0+1000]
expected (4750/2200) [0:0+1250 1250:-600+1500 2750:-600+1000 1250:600+1000 3750:0+1000]

What you expected instead

The Rust (crates/jlreq/src/pipeline.rs) and OCaml (engines/ocaml/lib/pipeline.ml)
reference engines, byte for byte identical to each other:

(4500/2200) [0:0+1000 1000:-600+1500 2500:-600+1000 1000:600+1000 3500:0+1000]

reports 1500 — its 1000-unit body plus the 500-unit Table 1 gap the row carries after it
— which is exactly the distance to at 2500. The first row is 2500 units wide, that is
what the block charges the line, and the boundary after the block is read against , the
block's last character, where Table 1 states nothing. The line's own inline_extent
comes out the same 4500 by a different route, and still stands at 3500.

Specification basis

JLReq 3.7.2 (a furawake sets its text on as many rows as the caller declared columns, and
the whole structure is one position on the line) and Appendix B Table 1's cl-02cl-01
gap (spec/captured/table1.en.tsv, 1/2 be), read as
docs/decisions/stacked-structure-geometry.md
publishes it for the warichu that #24 was about:

A character inside the block is spaced like any other, and its advance is the step that
reaches the next one. [...] The character that ends a subline reports its body alone,
because nothing of that subline stands after it.

and, for the two edges:

A structure has two edges and they are the same edge twice.

A furawake is the other structure that reading is addressed to — the decision's own
"Applies to" names the furawake rounds — so nothing here is a fresh reading. What is new is
that a furawake block is one item in compose.rkt where a warichu's members are items of
their own, so the block has one class where it needs two: the first character's for the
boundary before it and the last character's for the boundary after it.

Other implementations

Not checked. A furawake holding a closing-then-opening bracket pair inside one of its
columns is a shape no browser composes, and LaTeX's jlreq class has no furawake macro.

Before submitting


The coordinates. Sweeping the shape above over all 529 ordered pairs of the census's own
Appendix A representatives (just census-classes), the Racket engine differs from the Rust
and the OCaml engines at 217 of them, and the Rust and OCaml engines agree with each
other at all 529.

just census constructs does not reach any of them today: its four furawake variants put
the pair under test at the structure's two edges (columned in
engines/ocaml/probe/census.ml) and fill the block itself with ordinary characters that
state nothing between them. A furawake-pair-inside variant — the pair inside one column,
the way warichu-pair-inside puts it inside one subline — is what reaches it, and adding it
is what closes this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    conformanceObservable layout disagreements and protocol cases

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions