Skip to content

Claude/gracious hopper qw3t31 - #68

Closed
theolivenbaum wants to merge 2652 commits into
LibreOffice:masterfrom
curiosity-ai:claude/gracious-hopper-qw3t31
Closed

theolivenbaum wants to merge 2652 commits into
LibreOffice:masterfrom
curiosity-ai:claude/gracious-hopper-qw3t31

Conversation

@theolivenbaum

Copy link
Copy Markdown

No description provided.

… witness could not tell that from the whole merge

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…e the 3-D and of-pie questions

O35, the 3-D determination, the of-pie, and the doc-comment r101 left.

A smoothed line or scatter series is drawn by the reference as a *flattened*
natural cubic spline, not as a curve: AreaChart::impl_createLine strokes the
polygon CalculateCubicSplines returns. ChartSpline is that flattening —
parameterised by point index rather than by x, natural ends (periodic where the
ends coincide), twenty segments per interval. Every one of those three is
measured against 26.2.4.2's own output as well as read out of the 27.2 tree: its
PDF of an eleven-point scatter with uneven x states both the knots and all 201
vertices, and index+natural reproduces them to 0.0495 pt against 0.170 for
clamped ends and 1.822 for an x-parameterised spline. 0.0495 pt is 1.75
hundredths of a millimetre, which is the grid the reference's geometry sits on.

The reach in the register was wrong both ways. By markup it is 12 of 947, not
11 — mbSmooth( !bMSO2007Doc ) makes an absent c:smooth mean *smooth*, and the
three corpus documents with an absent one split exactly on AppVersion 12. In
drawn geometry it is 2 of 947: nine witnesses hold arithmetic-progression data,
whose spline is the chord, and one suppresses its line. On the two that move,
the mean distance from the reference's curve falls on 6 of 6 series.

The 3-D question is a measurement and it forbids most of an implementation: 5 of
the 6 corpus 3-D charts are a raster in the reference — the three OOXML pies and
*both* BIFF 3-D bars, which r99 had not measured — and only the .ppt OLE pie is
vector. No code written; N29 records it.

The of-pie brief is refuted. 26.2.4.2 does drop a pie-of-pie's sub-type when it
renders it, but we drew the second pie, so the reach was 1 and not nil. What
decides it is c:explosion on the series, measured one attribute at a time and in
both directions on both witnesses; |ink|% 3.76 -> 0.55. ODF's own of-pie
spelling, loext:sub-bar on chart:chart, is read too: 3.54 -> 1.41 on 1 of 947.
What is left — a second gate on the pie form that a small series fails — is
seated as O37 with the variants that show it.

Confinement: rendering the 48 corpus documents holding a line or scatter group
and the 12 ODF renderings stating chart:interpolation, before and after, moves
22 and leaves 38 byte-identical.

probes/chart-smooth-r102.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…, and 5 of 6 3-D charts are rasters

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
… sets

`iconSet` is read in both of its spellings and painted. Eighteen of the corpus's
twenty rules are stated only in the worksheet's `x14` extension list, with their
own `xm:sqref` and no main-namespace `cfRule`, and 26.2.4.2 imports and paints
them anyway; the two populations are disjoint, so the union is ten documents.

What 26.2.4.2 actually paints over those ten is sixty icons drawn from seven
distinct assets, because fourteen of the twenty rules are `custom="1"` and most
of their buckets are `NoIcons` -- two rules name `3TrafficLights1` and never
draw a traffic light, six name `3Stars` and never draw a star. The seven were
extracted from the reference's own PDFs with their soft masks applied and
matched to `images_colibre.zip` at 0.007-0.064 mean absolute channel difference,
against 19.7-45.1 for the best any other theme manages, so the theme is
established rather than assumed. They are authored as vector paths transcribed
from `icon-themes/colibre_svg/sc/res/icon-set-*.svg`, which is the source the
reference's own bitmaps are rasterised from: no assets, no raster path in the
sheet renderer, no licensed binary in the tree. The other eighteen sets are
recorded as unreached rather than drawn on a guess.

The reader is `GetIconSetInfo` line for line -- the last entry whose comparison
holds, `reverse` reflected before the custom vector is consulted, `NoIcons` as
no icon at all so the cell keeps its own text however `showValue` is set, and a
single-cell formula threshold resolved. The geometry is `drawIconSets`: the
cell's own font height, square, two device pixels in from the bottom left,
clipped to the cell.

And `ReadConditionalRanges` skipped the `x14` extension list on the stated
grounds that no corpus workbook uses it, which is false: six documents state a
range no main-namespace `conditionalFormatting` covers, and their rows were
measured by the cheap arithmetic branch instead of through `GetNeededSize`.

Reach 7 of 947 renderings, five improving and none worsening, no page count
moved, 300 of the 307 sheets renderings byte-identical. Nine of the ten stating
documents agree with 26.2.4.2 glyph for glyph and count for count; the tenth is
its own `TODAY()` recalculation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
… into seven vector glyphs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…awing's own CONTINUE, and a chart substream's drawing was stepped over

O37 said the loss was `XlsDrawing.Build`'s shape-to-`OBJ` pairing running out. It
was not — that guard cannot fire on the witness — and the shapes were dropped by
three separate defects, each measured against 26.2.4.2.

Once a sheet's Escher stream passes the 8224-byte record ceiling Excel writes the
rest as bare `CONTINUE` records interleaved between the `OBJ` and `TXO` records,
and `BiffRecordReader.MoveNext` joined them into the `TXO`. LibreOffice turns
continuation joining off for the whole drawing block and `ReadTxo` takes its two
by name; `TXO` now joins `OBJ` as a record the stream leaves unjoined, and
`XlsDrawingCollector.ReadText` asks for its characters and its formatting runs
through `BiffRecordReader.StartContinuation`.

A shape inside a group states no client anchor at all — only an
`msofbtChildAnchor` in the group's own coordinate space — so no grouped shape has
ever been drawn from a `.xls`. `Build` now keeps the placed box of every shape and
lays a child inside its parent's by `ImportShape`'s linear map over
`GetGlobalChildAnchor`'s union, in length units rather than column indices; a
group's own shape takes no fill or line, because `ImportShape` branches on the
group flag before the branch that builds an item set.

An embedded chart's substream carries a drawing of its own, anchored in
quarter-thousandths of the chart's rectangle. It goes into a collector of its own
and is expanded against the rectangle the chart shape was placed in.

And the recovered boxes landed 1.6 pt out because the text margin was neither
rule: a shape setting `fAutoTextMargin` gets Excel's 20000 EMU on all four sides
and one that does not gets its own stated `dxTextLeft`, which the reference's Excel
97 filter writes as zero.

`EHEST-Pre-departure-checklist.xls` draws 33 of the reference's 33 colour-stating
shape spans against 0 at the base, within 0.13 pt in x and 0.92 pt in y; ink
8.65 -> 8.06 with 0 MAJOR either side. Reach 9 of the 64 `.xls` with 0 failures and
60 `.xlsx`/`.ppt`/`.doc` renderings byte-identical. Summed unsigned ink over the
nine movers is 31.44 -> 31.58 and probes/sheet-pivot-r103 section 3.4 says why: on
`2012-GA-Survey-Chapter-6` the single span that moves lands on 26.2.4.2's x exactly
while `|ink|%` rises, and `apron-area`'s +0.63 is a pattern fill this change
exposes rather than causes. Both are seated as O38 and O39.

O17 is not fixed. Its reach is now measured on all eleven pivot-bearing documents
rather than bounded: stripping only the pivot-parented borders out of each `.ods`
twin puts the generated grid at 234.20 on `alle einzeln` and 16.54 between the
other seven, and at 0.00 on all three that carry no pivot-parented style.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…as swallowing the drawing's own CONTINUEs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…ated _PID_HLINKS reaches it

O15's third cause is not the autofit arithmetic.  Measured directly rather than argued:
bisecting the box height at which each renderer changes its constScaleLevels answer gives
its measured block height to the unit -- block(k) = H_min + 1 -- and the same sweep on the
box width gives the advance a line breaks at.  84 of 84 block heights and 164 of 164 wrap
thresholds agree with 26.2.4.2, over seven constructs and four faces.  Round 100's "the
block is short by a fifth of a point over thirteen lines" is withdrawn: the 7 hundredths of
a millimetre belonged to a different shape of that document.

What was left is that this tree made an EditEngine field of a .ppt hyperlink where the
reference makes none, and a field's spill lines do not count in the height the shrink-to-fit
search measures -- so the block was one line short and the fit took a larger row.

The rule is an arithmetic defect in the reference's own property reader.  Section::Read
clamps every property's buffer to nSecSize - nSecOfs, a declared length less an absolute
position (propread.cxx:443-447), so a _PID_HLINKS blob in a section that starts late in the
stream is handed to ImplSdPPTImport::Import truncated; its loop stops at the first string it
cannot read, and only the entries it built are given an index from the ExObjList.  On
RESPA_-_Section_8_Webinar.ppt the section is at 1540 and declares 2244, so a 2180-byte blob
arrives as 704 and exactly two entries are built where ten are declared.  Patching one text
range's exHyperlinkId to each of the file's ten ExHyperlinkAtom values and rendering all ten
through 26.2.4.2 draws exactly the first two as links, and no other value.  Confirmed on a
second document at a boundary the first cannot show.

17 -> 14 pages, 13 -> 11 documents, 66.06 -> 62.06 pt of dominant-size error, 3 fixed and 0
newly wrong; |ink|% 250.28 -> 244.13 with 7 renderings moving and all 7 improving; match 49
and every page and glyph count unchanged.  Underline counts land on the reference's exactly
on four of the nine documents the clamp reaches.

O13 is sized and left seated: 2 shapes in 2 of the 51 .ppt, but the AOD one is a MAJOR page
at 16.04 |ink|% whose Escher fill is a plain solid black, and the phuket one is worth 0.01
and is a shade-centre gradient.  probes/slides-r103/results.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…a field's spill lines leave the fit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…a failure

Under memory pressure a whole-solution run gave Test Run Aborted for one project and exit
137 for another, killed at 1063 of 1938, with zero reported failures for either. Re-run
alone both were green at full count.

Three modes now: a short run reporting a clean pass, a loaded run inventing failures, and
a killed run reporting neither. Knowing the expected N per project catches all three; a
project missing from the output entirely is the loudest and the easiest to scroll past.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
… a mask question

Three seats, one implemented and two answered by measurement.

O36 — a chartex (cx:) chart draws an empty frame. `DrawingChartex` reads a
`cx:chartSpace`: every series naming a `cx:dataId` becomes a line series over the
first data block's categories, a series naming none is dropped, and no axis, tick,
label, legend or title is drawn -- because that is all 26.2.4.2 draws. What the
reference resolves is far more than what it draws: its own fods gives a
ClusteredColumnChartType diagram of four series, eleven categories, two axes, wall
and floor, and its page carries a white plot rectangle and two eleven-point
polylines and nothing else. `XlsxChartRanges` now expands a workbook-scope
definedName naming one sheet-qualified area, which chartex needs and which no c:f
in the corpus states. Reach 2 of 947; |ink|% 0.14 -> 0.00 and 0.50 -> 0.03, the
drawn polylines within 0.05 pt of the reference's at every corner and their stroke
colours identical. Confinement: 2 of 243 xlsx-family renderings move and 241 are
byte-identical.

O26 — which charts escape the block clip. None as a kind: the block clip is a
Push(CLIPREGION) + IntersectClipRegion taken once per layer in
ObjectContactOfPageView::DoProcessDisplay, and what replaces it is
VclMetafileProcessor2D::processMaskPrimitive2D's SetClipRegion over a clip polygon
that never contains the block. So the escaping unit is a mask. On
DynamicBubbleChart page 2 the same chart's axis strokes are cut at the block and its
gradient-masked bubble markers are not, which refutes r99's "no chart demonstrably
escapes" on the document it was written about. 36 of the seat's 61 pages are a
one-pixel boundary column. Closed as nil reach for a rule of its own shape; the
residue is re-seated as mask-level parity.

O38 — the pie-of-pie's second condition does not exist. Over 28 one-attribute
variants at 26.2.4.2, the connector count is 0 at three points and 2 at four through
twenty in both forms, and the wedge counts are n+1 for pie and n-split+1 for bar,
which is OfPieDataSrc::getNPoints exactly. This tree already implements it; round
102 §4.5 is retracted. What the same measurement did find is a new seat: the of-pie
unit radius is 1.37x the reference's on the one drawn of-pie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
55 % of the seat's 31,384 pixels and 61 % of the 28,520 that are not a one-pixel
boundary column; the summary bullet quoted the second figure against the first
denominator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…t is a mask rather than an object kind

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…ary off, and sized at two documents

O10 said a chart category axis rotates because 26.2.4.2 hyphenates the label.  r91
established that by swapping words; this round holds every glyph fixed and varies only
the run's locale.  26.2.4.2 ships patterns for exactly four locales and this machine has
no other hyph_*.dic, so tagging 038's axis de-DE, ru-RU or zxx switches the hyphenator
off without changing a character: the reference then wraps the axis upright, where
en-US, en-GB, fr-FR, es-ES and no tag at all make it turn 45 degrees.  The unhyphenatable
control wraps in all eight.  The same retag to en-GB -- a dictionary that IS installed --
moves three documents by one hyphen each and zero arrangements, so the base rate for the
edit itself flipping an axis is 0 of 103.

Census: the corpus needs en_US and nothing else.  102 of the 103 c:catAx documents carry
pure-ASCII English labels, the one French document is unaffected, and 23 state no
language at all -- 038 among them -- so the default locale is what hyphenates them.

Reach, measured by rendering all 169 chart-bearing OOXML containers both ways with the
reference: 8 render differently with the hyphenator off, only 2 change the rotate-or-wrap
decision, and only 1 moves a gate row.  On that one the attribution closes exactly --
our column 9 is 1585 against the reference's 1449, and the reference with its own
hyphenator off is 1585.

Three further rotation defects are separated out and re-seated as O40: 055, 027 and 064
are byte-identical with the hyphenator off and their labels hold no hyphenation point.
r91's "27 fill-excess candidates to screen" is retired -- 1 of the 27 states a category
axis against a base rate of 10.9 per cent, which is below its own null.

No src change: a fix has to vendor hyph_en_US.dic (106,414 bytes, BSD-style/TeX-derived,
no system copy exists) and that is a licensing decision, not a rendering fix.  Build
clean, 0 warnings; nothing compiled changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…d for the one file it needs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…y and a way to supply the rest

`ChartAxisLabels.Wraps` asked one half of `lcl_hasWordBreak`'s question -- is any single word
wider than the slot -- and now asks the other: would the hyphenating fill start a line inside a
word. That is what turns `038_Competitive_Advantage_Card`'s axis 45 degrees where this tree
wrapped it upright, and probes/chart-hyph-r105 established it by holding every glyph of the
witness fixed and switching only the reference's dictionary off.

`hyph_en_US.dic` (106,414 bytes) and `README_hyph_en_US.txt` are vendored into
Paperless.Core/Globalization/Hyphenation and copied beside the assembly like the bundled faces.
The licence is BSD-style and lives in the README rather than in the data, so the two travel
together in the tree, in the output directory and in the package. `hyph_fr.dic` (LGPL-2.1+) and
`hyph_es.dic` (GPL-3.0+/LGPL-3.0+/MPL-1.1+) are deliberately NOT committed: they are supplied
through PAPERLESS_HYPHEN_DICTS, a path list searched one level deep so a LibreOffice
share/extensions resolves dict-fr/hyph_fr.dic, or through Hyphenators.Register(language, stream).
`=0` turns hyphenation off entirely, which is the state a deployment without the data is in.

`IHyphenator` moves from Paperless.Text.Layout to Paperless.Core.Globalization, contract
unchanged and no call sites to update, because the first consumer is ChartAxisLabels and Core may
not reference Text.

Two rules worth keeping. The leading limit EditEngine imposes is a character count and not a
width -- `nMaxBreakPos - nWordStart - 1`, whose `+1` is commented "Before the dickey letter" --
so one character's room is reserved for the hyphen rather than the hyphen being measured. And a
turned label the reference OUTLINES still costs us its characters: `038`'s column 9 is 1585
before and after, because 26.2.4.2 reads 1449 by drawing those labels as filled outlines. r105's
"the reference with its hyphenator off is 1585 too" is an identity between the reference's own
two states and does not say the gate row closes. It does not.

Established twice. The four hyphens 26.2.4.2 itself draws on corpus charts -- Gov-ernance,
Con-sumption, Pes-simistic, Pas-senger -- are reproduced 4 of 4. And 22 authored one-word
fixtures of the witness, rendered with each renderer's dictionary both on and off, agree 19 of 22
against a base rate of 13; the three that differ are one hyphenation false positive (`Service`,
cause not established, corpus reach zero) and two width-limit rows that belong to O43.

Reach, measured rather than censused: 3 renderings of 568 scored move, all chart-bearing, all
three better against 26.2.4.2 and none worse -- summed |ink|% 57.00 -> 54.72 over r105's eight
candidates, with one MAJOR page cleared. No gate verdict moves. 0 of 399 non-chart documents
move, and the call graph makes that structural. Ten test projects green, 6609 passed, 0 skipped;
Paperless.Fidelity.Tests 542 passed / 10 failed with exactly the known names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…es supplied at runtime

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
The container was restarted mid-round. This is whatever was on disk at that moment:
not built, not tested, not measured, not reviewed. Nothing here is a finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
The container was restarted mid-round. This is whatever was on disk at that moment:
not built, not tested, not measured, not reviewed. Nothing here is a finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
The container was restarted mid-round. This is whatever was on disk at that moment:
not built, not tested, not measured, not reviewed. Nothing here is a finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…racle settled

The salvaged WIP built clean and its seven fixture tests pass, but three of its claims
needed checking and one of its rules was wrong.

  - The indent is the drill-down step and nothing else (dpoutput.cxx:1136 with
    pivottablebuffer.cxx:1366), so a pivot stating showDrill="0" has none.
    DynamicBubbleChart states it, has five row fields, and the reference's own .fods of
    it writes no fo:margin-left="0.1354in" anywhere. The WIP indented it regardless.

  - A single compact row field is not a different layout: GetColumnsForRowFields counts
    one column for it (dpoutput.cxx:854-868), bLast drops its indent (:1135-1137) and it
    takes FieldCell not MultiFieldCell (:1087-1090). Declining it cost thirteen pivots in
    three workbooks that the oracle now says agree on every edge.

  - Excel's ref excludes the filter rows and Calc's does not; finalizeImport moves the
    start up by pageFields + 1, clamped at row one (pivottablebuffer.cxx:1421-1426), and
    CalcSizes moves it back down. Modelled including the clamp.

  - A hidden header (firstHeaderRow="0") was tried and refused: with it accepted,
    033_Event_planning_tracker predicts 24 of 131 edges right and states 56 the reference
    does not.

check-grid.py now scores both directions. Scoring only the predicted edges, as it did, is
a precision that a prediction of nothing scores 100% on. Both ways, over the sixteen
pivots accepted in the eight worksheet-sourced pivot-bearing workbooks: 31596 edges, every
generated cell style and every generated indent agree, nothing disagrees, and on
alle einzeln.xlsx that is all 9092 bordered cells and all 30250 edges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…1 refuted as a chart

Validation of 2277709, which was salvaged from a container restart unbuilt and
unmeasured. Nothing in it was taken on trust; every arm was re-derived.

O40, an Escher pattern fill painted as its solid foreground colour, is fixed and the
tile is confirmed against the reference's own resolved view: `--convert-to fods` writes
each recoloured fill out as a `draw:fill-image` PNG, and our four 8x8 image XObjects
match 26.2.4.2's four on all 64 pixels of each, at alphas 0.349/0.4/0.502/0.6 against
its `draw:opacity` 35/40/50/60 %. Tile drawn 6.0000 pt against its 5.9943. Census over
all 64 corpus `.xls` reproduces exactly: 562 shape containers, 4 stating a bitmap fill,
in one document -- and none of the four is grouped, so the stale "2 shapes" count is
*not* explained by r103's group fix and no other explanation was measured.
`apron-area.xls` 1.51 -> 0.44 on summed unsigned ink, MAJOR 1 -> 0.

O39 is fixed by deleting `XlsxHiddenValues`, and the deletion was re-argued rather than
inherited: the paint-time answer `SheetDecoration.HidesValue` was already in the base,
nothing else read the deleted reader, and its coverage is controlled by the seven
pre-existing `SheetHiddenValueTests` cases, all still passing. Drawn row pitch on
`sheet-cf-icon-set-x14-custom.xlsx` 13.7764 -> 14.8819 pt against the reference's own
`style:row-height="0.2071in"` = 14.9112. Three corpus documents state the attribute,
not two, and all three are byte-identical at both legs; what moves is extraction, by
the twelve `1`s 26.2.4.2's own csv export has always written.

O41 stays seated and is re-characterised. Its numbers came from an uncommitted script
and are replaced by `gauge-rules.py`: over pages 13 and 15, all 32 of the sheet's own
full-width rules agree with 26.2.4.2 to 0.02 pt, and the rules r103 measured are the
embedded gauge chart's 18 value-axis gridlines, up to 7.2 pt out with a residual linear
in index through zero near the middle. That is a plot rectangle 404.75 x 150.78 pt
against 413.75 x 160.04 inside a frame the two agree on to 0.2 pt -- a chart-layout
seat, not a sheet one.

Five LibreOffice line-number citations in the new code were off and are corrected
against this tree; two were right as written.

Clean build, 0 warnings. Ten non-fidelity projects 6600 passed / 0 failed / 0 skipped;
`Paperless.Fidelity.Tests` 542 passed / 10 failed / 552 discovered / 0 skipped, the ten
being exactly the known names. Sheets sweep 307 rows, 1 mover, 0 failures -- run twice
and identical row for row including every md5. Control sweep 160 words/slides/`.ods`
rows, 0 movers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…d seat was the gauge chart's gridlines

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…AJOR -> 0

Reach over the eleven pivot-bearing documents, base d07a9ce against head, ink from
pdf-image-diff.py against the banked 26.2.4.2 references:

    alle einzeln                     186pp  225.44 ->   0.07   36 MAJOR -> 0
    Keywords_Mapping_Graphs_Charts    46pp   10.79 ->   1.68   11       -> 0
    DynamicBubbleChart                 5pp    4.42 ->   0.94    2       -> 1
    049_Expenses_calculator            4pp    2.18 ->   0.90    2       -> 0
    007_Contextures_chart_sample       7pp    0.95 ->   0.13    1       -> 0
    037_Personal_money_tracker         5pp    3.45 ->   2.88    1       -> 1
    the other five                             0.00 unchanged
    total                                    265.25 ->  24.62   55      -> 4

alle einzeln at 0.07 is level with the reference's own .ods of the same workbook, which
scores 0.02 over the same 186 pages. 053_Personal_asset_inventory is unscoreable at 4
pages against 2 and its 0.00 is an absence of measurement, at base and at head alike.

Against 26.2.4.2's own .fods, both directions, over all sixteen accepted pivots: 31596
edges, 6324 generated cell styles and every generated indent agree; 0 MISS, 0 EXTRA, 0
WRONG. The null beside it: those rectangles hold 38772 edge slots of which the reference
states 81.5%, and 2494 of the 31596 are the outer rule rather than the inner one.

Sweep: all 307 sheets-track documents at both binaries, no failed or truncated render, 6
moved, and they are exactly the six with an accepted pivot. The 64 .xls are the control
and none moved.

Tests on the committed tree: ten non-fidelity projects 6600 passed 0 failed, run one at a
time; Paperless.Fidelity.Tests 542 passed, 10 failed, 552 total, the ten by name being
PageDrawing x4, TabStop x4, SheetDrawing and JustificationShrink.

Two numbers the salvaged test asserted were wrong and are corrected: removing HeaderCell's
row-subtotal frame costs the fixture 10 of its 99 edges and alle einzeln 30 of its 30250,
not "eighteen" in both places. Every file:line was re-opened; two were off by one and one
named no line.

O17 moves to fixed. O44 is seated for the two layouts still declined -- a hidden header,
where the reference's table start is not understood, and compact packing of more than one
row field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…tain the change

A round resumed after a container restart found its inherited cli-base already contained
the pivot code it was about to measure. Every before/after figure taken against it would
have been base-against-base, showing a clean nil reach for a change that moves 225.44 of
ink to 0.07 — and nil reach reads as the most reassuring possible result, which is what
makes it dangerous.

Rebuild both binaries after a restart, prefer a base published in this run, and if reusing
one, confirm it does not move a document the change is known to move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…t's drop in the line box

O13, closed. A shape is WordArt on one bit and not on its type -- msdffimp.cxx:4424-4425 --
and the type only chooses which of the forty presets is bent, over the contiguous 136-175
band that EnhancedCustomShapeTypeNames.cxx:171 and :179 name. PptFontwork decodes the text,
face, alignment, size and adjustments out of the property table, dividing adjustment 0 by
65536 for the eight polar-handle types 144-151; PptSlideLayout returns the warped outlines
instead of the shape, because EnhancedCustomShapeEngine::render2 replaces the rendered shape
with them outright, and fills them with the shape's own resolved fill, because
CreateSdrObjectFromParagraphOutlines copies the shape's item set onto the path. The second
half needed no new paint kind: PptFills.Resolve already answers fillType 5 with a gradient.

Censused twice with different failure modes -- a record-tree walk and a brute-force
msofbtOPT scan over 15032 candidate records -- at two shapes in two of the 51 .ppt. Measured
base against head: 8.16_AOD_FINAL 20.34 -> 4.56 sum |ink|% with page 59 going 16.04 -> 0.26,
pres_ioc_phuket 4.70 -> 2.96 with page 26 going 1.97 -> 0.23, two MAJOR pages cleared and no
page worse. The brief's sizing of the second shape was wrong in both coordinates: it is on
page 26 rather than page 1 and is worth 1.97 rather than 0.01.

O15, two of fourteen. RecalcFormatterFontMetrics measures an escaped portion twice and keeps
the larger answer on each side separately (impedit3.cxx:3164-3183), so a subscript's descent
grows by the whole of its drop and the block it sits in is a constScaleLevels row taller.
Confirmed by single-variable experiment at 26.2.4.2 rather than derived: through the
reference's own flat ODP, removing only the offset makes it draw what this tree drew and
removing only the shrink changes nothing. 14 -> 12 pages, 11 -> 10 documents, 62.06 -> 58.08,
two fixed and none newly wrong, only two of 1534 pages moving. The .pptx column was checked
rather than assumed: 33 of 251 state an escapement, seven renderings move, none changes its
page count or its alphanumeric count.

Ten non-fidelity projects green at 6613 tests; fidelity 542 passed / 10 failed / 552 total
with the known ten names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…n was measured twice

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…agraph's last portion

`PPTParagraphObj::ApplyTo` converts a positive `PPT_ParaAttr_UpperDist` to master units
using the font height of `m_PortionList.back()` -- the paragraph's LAST portion --
before it becomes an `SvxULSpaceItem` (`filter/source/msfilter/svdfppt.cxx`:6296-6306,
`/home/user/libreoffice-core`, which declares 27.2.0.0.alpha0+ and is not the reference
binary's source). This tree read it off the first portion.

Measured at 26.2.4.2 as well as read: its own flat ODP of
`gillikin_online_user_mtg_2010.ppt` gives page 2's five bullets `fo:margin-top` of
0.3cm, 0.3, 0.318, 0.3 and 0.212 -- 20/80 of 34, 34, 36, 34 and 24 pt, the last portion
of each, where the third and fifth open on 34 pt. Over the 51-document .ppt column the
last portion is admitted for 147 of 178 multi-size paragraphs against base rates of
24.7 % for the first and 34.3 % for the largest.

On the witness the block misses `constScaleLevels` row {1.000, 0.900} by 55 units of a
hundredth of a millimetre before and fits it by 9 after, so the slide is drawn 34 pt
rather than 31 -- the reference's answer.

O15's statistic over 1534 pages: 12 -> 11 differing, 10 -> 9 documents, 58.08 -> 55.10 pt,
1 fixed, 0 newly wrong, exactly one page moving at all. On ink, 21 of 51 renderings move
and 30 are byte-identical; over the 21, summed |ink|% 114.95 -> 113.08 and MAJOR 32 -> 31,
with no page count and no alphanumeric count moving anywhere.

`PptParagraphSpaceSizeTests`: 4 of its 6 fail at the base and all 6 pass here.

`probes/slides-r108/results.md` also classifies O15's remaining eleven pages into three
distinct causes by a four-leg experiment -- 5 import, 3 layout, 3 where the reference's
own ODF round trip is not faithful and the page carries no evidence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
The residual is six same-alphanumeric pages and five that differ, not four and
five; and the IMPORT group is five pages, with Thailand17 page 11 belonging to it.
The measurements in legs.txt and size-summary.txt are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
claude and others added 28 commits September 28, 2026 00:45
DrawingChartPlot read c:pie3DChart as an ordinary pie, so every
three-dimensional pie was a flat circle where 26.2.4.2 draws an extruded solid
with an elliptical top face and a darker curved wall filling the plot area's
width. It is the largest shape error the chart sweep holds, and the reach is
exactly three documents: no bar3DChart, line3DChart, area3DChart or
surface3DChart exists anywhere in the corpus, so the pie is the whole of the 3-D
work there is anything to measure.

Every law here was fitted to a bitmap, because 26.2.4.2 rasterises a 3-D chart:
the solid arrives as one Do of an RGB image with a soft mask and only the labels
and the frame are operators. probes/pie3d-r187 renders 36 one-attribute rewrites
of a corpus document's own chart part -- nine c:rotX by four c:depthPercent --
and fits each silhouette. It says the projection is parallel in practice (the
upper boundary is an ellipse to a mean 0.28-0.45 pixels on a 1370-pixel image,
at every elevation, although View3DConverter asks for
ProjectionMode_PERSPECTIVE because a c:perspective of 30 halves to a non-zero
15); that the squash is sin(rotX), within 0.7% at seven of the nine; and that
c:depthPercent is read by nothing, the four values giving geometry identical to
the hundredth of a point at all nine elevations. The thickness comes from
PieChart::getPreferredDiagramAspectRatio's Direction3D(1, 1, 0.10), and the
solid is fitted to the plot area on its own projected bounding box: predicted
width within 2% at every elevation, with the width-to-height changeover between
20 and 30 degrees reproduced.

The change that mattered most is not the solid. A three-dimensional diagram is
never squared: VDiagram::adjustPosAndSize branches on the dimension count
(chart2/source/view/diagram/VDiagram.cxx:89-101) and adjustPosAndSize_3d fits
the scene's own projected bounding box rather than the preferred ratio
(:409-421). ChartLayout.Squared squared every pie, and with the solid drawn but
the rectangle still squared 021_Unit_Circle_Chart_3D_Pie_Chart came out 274 pt
across against the reference's 469 and went 12.20 -> 16.33 diff%, worse than
before the feature. Without the square it is 5.64, and its drawn solid is
493.8 x 289.7 pt centred at x 291.6 against the reference's 469.3 x 278.0 at
289.5.

Shading is two flat multipliers of the series' own colour, counted over every
opaque pixel of the reference's bitmap: the top face is 0.957 and the wall runs
0.83 at the sides to 0.91 at the front, taken flat at 0.87. One trap worth the
note: (64, 99, 41) is accent6 x 0.574 and is not a wall at all, it is the 10%
sector's top face in the darker shade c:varyColors hands the fourth point.

Reach and cost: 021 goes 12.20 -> 5.64 diff% and 5.18 -> 1.07 |ink|%,
pie-chart-result 2.76 -> 1.66 and 0.30 -> 0.22, and 2 of the 168 chart-bearing
corpus documents move with 166 byte-identical -- the 166 including all 15
pieChart, all 13 doughnutChart and both ofPieChart documents, which is what
exercises the PieLabels refactor that threads the rim's vertical semi-axis
through. pie-chart-template does not move and that is correct: its one series'
cached value is zero, so neither renderer draws a pie on it at all.

Left named on 021: its labels are 14 pt bold and we draw the plot default,
because the size and weight are stated only on each per-point c:dLbl's own
c:txPr and never on the series-level c:dLbls, which DrawingChartPlot reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
FrameChart.ChartFace resolved one face and shaped every chart label through it,
so a chart's bold title, axis labels, axis titles, legend and data labels were
all drawn light on a DOCX, a DOC and an RTF while the model carried the right
value. SlideChart and SheetChart have acted on ChartLabel.IsBold since they were
written; the class's own remarks named this gap and deferred it for want of a
words sweep, which this round has.

021_Unit_Circle_Chart_3D_Pie_Chart is the witness and pdffonts is the
instrument: its series-level c:dLbls/c:txPr states sz="1400" b="1", the drawn
size was already right at 14.00 against 26.2.4.2's 14.01, and the page embedded
Carlito-Regular where the reference embeds Carlito-Bold. It now embeds
Carlito-Bold.

ChartFace resolves the family at weight 700 beside weight 400 and keeps the
pair; Shape, Measure, LineHeightAt and AscentAt all take the weight and
FrameChart passes label.IsBold. A family with one weight collapses back to one
face -- the resolver answers the same file for both requests and its FaceKey is
that file's path, which is what the PDF writer embeds by, so pairing such a face
with itself would put a second identical subset in every document that uses one.
The vertical metrics take the weight too, because a label is drawn at
blockCentre - blockHeight/2 + ascent and the two have to move together.

Reach is 5 of the corpus's 10 chart-bearing words documents -- exactly the 5
that state a b="1" anywhere in a chart part -- with the other 5 and all 10
converted .odt charts byte-identical, and nothing on the slides or sheets tracks
able to move because ChartFace has one consumer. pie-chart-result goes
1.66 -> 1.55 worst diff% and pie-chart-template 0.21 -> 0.11; 021 goes
5.64 -> 5.68 and 028_Unit_Circle_Chart_Optimized_Graph 16.47 -> 16.48, because
bold labels are wider and the extra width moves where a label wraps. diff% is
the wrong column to read for this change and the embedded face set is the right
one.

One census correction worth more than the fix. The first reading was that 021
states its weight only on each per-point c:dLbl, which no reader reads -- taken
from the first c:dLbls in document order, which is the per-point one nested
inside the series-level one. Counted properly, against what the enclosing
c:dLbls already states, a per-point override exists in 4 of the corpus's 699
chart parts and 021 is not one of them: its series-level c:dLbls carries the
same defRPr after its four c:dLbl children. An override that repeats its parent
cannot be seen on the page. probes/chartbold-r188.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
ChartLayout.AddBars put every bar at (double)at / categories whatever the
axis was, so on a date-axis combination chart the line -- which has gone
through ChartDateAxis since it was written -- spanned the plot and the bars
did not. On 171128IPAP.pptx page 40 this drew 67 bars across the left
two-thirds of a plot where 26.2.4.2 draws 44 across the whole of it, at a
pitch that changed partway along.

Four rules, of which only the first two are readable from the source alone:

  - A category is one unit of the axis' own time resolution wide, not one
    n-th of the points (PlottingPositionHelper::setTimeResolution).
  - The axis is linear in that unit and not in days (DateScaling::doScaling
    returns year*12 + month plus the fraction of the month elapsed). Over
    eleven months days are out by 5%: 26.2.4.2 spaces
    044_Cash_flow_forecast's twelve monthly bars at a flat 35.26 pt where
    days give 30.2 to 33.4.
  - A shifted axis carries one extra interval whether or not the range is
    stated, and the interval is chosen after the extension, so every tick
    moves too (ScaleAutomatism.cxx:565-597).
  - A shifted axis' categories lead their own dates where an unshifted
    axis' straddle them. That half is not in the source and was measured.

Which arm applies is the crossing value axis' c:crossBetween, read as
== "between" with a fallback of true for a bar, line or stock group
(axisconverter.cxx:292-301); deleting the attribute renders identically to
"between" and midCat loses the extra interval and straddles.
ChartDateScale.Resolve's own default is false, because that fallback belongs
to the OOXML reader -- the BIFF reader passes nothing and keeps the geometry
it had, CHVALUERANGE's crossing flag being unmeasured.

Page 40 is now 16.01 -> 7.94 diff%, 1.91 -> 0.39 |ink|%, MAJOR -> shifted,
with its bars at 350.07...635.85 pt width 2.22 pitch 6.64 against the
reference's 44 at 350.05...635.75 width 2.21 pitch 6.63. Reach 7 chart parts
in 5 documents by markup; 8 of the 168 chart-bearing documents move and 160
are byte-identical, the three extra being charts whose lines and ticks moved
with the month-linear axis. Two improve (16.01 -> 12.91 and 2.57 -> 2.12),
five are level, one worsens by 0.05 on a chart already within 0.13 pt.

The experiment is the part worth copying. One rendering cannot separate
"centred on the date" from "running from the date", because the plot's left
edge is unknown and both readings fit at different edges; two renderings
with different stated ranges can, and better still the plot's own rule can
be read out of the page -- the widest horizontal is the plot rectangle,
414.31 in both variants. The first cut inferred the edge from the bars,
concluded "centred", and was refuted by the tick row in the same PDF.

Full suite green; Fidelity 10 failed of 552, the standing baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
Three things PieChart decides for itself on a c:ofPieChart, all measured against
26.2.4.2 by reading 028_Unit_Circle_Chart_Optimized_Graph's own fill operators
-- a wedge's path runs centre -> rim -> centre, so the boxes fix the centre, the
radius and the span exactly and the colour is read rather than sampled.

  - The composite wedge's colour is not in the document, and the document states
    one. That series carries sixteen values and seventeen c:dPt, the last of them
    accent4 lumMod 50% = #806000; the reference draws #7E0021. propIndex gives the
    composite the property index getTotalPointCount() (PieChart.cxx:1185-1191) --
    one past the last value, always -- and DataSeries::getDataPointByIndex answers
    an empty reference outside the data's own length (DataSeries.cxx:312-337), so
    the import drops that c:dPt, hasPointOwnColor is false, and createOneRing
    falls through to m_xColorScheme->getColorByIndex (:1325-1331). That is
    Office.Chart/DefaultColor/Series, twelve values indexed nIndex % 12, and
    16 % 12 = 4 is 0x7E0021. It disagrees with the writer's intent and agrees with
    the reference, which is what this tree is calibrated to.
  - The bar stacks upward in data order, so the first split point is its foot:
    createOneBar opens at fBarTop = -0.5 and adds each share, "make the bar go
    from -0.5 to 0.5" (:1416-1430), against a value axis that points up.
  - The main ring opens at MINUS half the composite sweep, so the composite closes
    across three o'clock where the two connecting lines meet it -- sAngle's
    clockwiseWedges() ? 360 - degAng : degAng (:1229-1244). Opening at plus half
    put the whole composite above the axis.

Beside them, an of-pie's labels now go through the pie's own placer at the main
ring rather than through a placer of their own at half the radius, and
PieConsumedRect measures them there instead of on the unit circle, which nothing
is drawn on. That changes nothing on 028 -- it states c:dLblPos val="inEnd" and
both placers take the fixed branch -- but it is what the reference does and it is
where the remaining 1.37x unit radius has to be solved: for 26.2.4.2's final
square to be 270.9 rather than 370.68 its pass-1 consumed rectangle must be 268.27
tall against our 168.49.

The of-pie constants were already right: unit centre to 0.6 pt, bar width over
unit radius 0.747 against 0.750, bar height 1.495 against 1.499.

Reach 1 of the 168 chart-bearing documents, 167 byte-identical; 029, the other
c:ofPieChart, is exploded and both renderers draw it as a plain pie.

028 goes 16.48 -> 16.72 diff%, and that number is measuring something else: its
chart frame sits 71.6 pt higher on our page than on the reference's, so no
chart-internal change can lower a page fraction. Scored against a reference
rendered from a variant that puts 26.2.4.2's chart where ours is and changes
nothing else, the same two renderings give 15.21 -> 14.47. probes/ofpie-r190
records that displacement too -- measured over ten one-attribute variants, with a
minimal fixture that refutes the obvious reading of it -- and leaves it.

Full suite green; Fidelity 10 failed of 552, the standing baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues
takes the ring's OUTER radius for INSIDE and OUTSIDE alike and the middle of the
ring for everything else -- bCenter is exactly
`nLabelPlacement != OUTSIDE && != INSIDE` (PolarLabelPositionHelper.cxx:68-76),
and AVOID_OVERLAP has already become CENTER by the time it is asked
(PieChart.cxx:434-437). createTextLabelShape then pulls an INSIDE anchor BACK
along the radius by the same flat 150 hundredths of a millimetre it pushes an
OUTSIDE one out by (:439-452, applied at :490-497), and the eight-row alignment
table is `bOutside ? A : B` with B the opposite of A on every row (:112-137), so
the block hangs back over the slice instead of away from it. The wrapping width
stays the default 0.8 * fPieRadius: the room-to-the-edge arm is guarded by
`nLabelPlacement == OUTSIDE` and the comment beside it is a TODO asking for a
better guess for INSIDE (:544-576).

So an inEnd label is an outEnd label with one sign flipped, and this tree was
drawing it as a centred one.

Measured on 029_Unit_Circle_Chart_Pie_Theme, each label's distance from the pie's
centre as a fraction of its radius -- the ratio, because the two pies differ by
2 pt:

  reference  0.804  0.839  0.917  0.953  0.890  0.923  0.841
  before     0.414  0.451  0.563  0.588  0.527  0.549  0.467
  after      0.806  0.841  0.918  0.953  0.891  0.924  0.842

Seven of seven within 0.002 with no free parameter, and the distance is measured
to the block rather than to the anchor, so it fixes the mirrored alignment too.

Reach: the corpus's pie charts state inEnd on 76 points in 5 documents and outEnd
on 37 in 4, against 188 points in 19 that state nothing. 5 of the 168
chart-bearing documents move and 163 are byte-identical --
005_Contextures_chart_sample 1.66 -> 1.57, 100_Lime_and_Lemon 10.49 -> 10.37,
029 5.53 -> 5.36, bitesize-writing-a-report level, and 028 16.72 -> 16.78 on the
naive metric and 14.47 -> 14.42 against a reference placed where our chart is.

And it settles where 028's remaining 1.37x radius comes from, which is not the
ring's labels: all sixteen of them sit within 93 pt of a centre whose radius is
90.3 in the reference's own rendering, so they never leave the ring. The bar's do,
through a unit bug -- createBarLabelShape sets
fTextMaximumFrameWidth = 0.8 * (m_fBarRight - m_fBarLeft) (:780) where those two
are 1.25 and 0.75 in unit-circle LOGIC units, so the wrapping width is ceil(0.4)
= 1 hundredth of a millimetre and every bar label is drawn one character per
line: two columns at x 437.8, pitch 10.47, of 17 and 25 rows, centred on the two
segments to 2.4 and 3.2 pt. probes/ofpie-r190 §7 records it, says why neither
obvious model of the line count fits, and leaves it.

Full suite green; Fidelity 10 failed of 552, the standing baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
Diagnosis only; nothing in dotnet/src moves.

The chart-type sweep ranked the Intersil CAN Bus deck at 18.08 diff% and a task
carried that as a barChart seat. The deck's one chart is on slide 30, which
scores 5.38: the 18.08 is slide 37, a bulleted text slide, and 15.91 is slide 13.
A sweep that ranks a document on its worst page attributes that page to whatever
the document was selected for, and that is the third stale task premise this
queue has produced.

Slide 37 was read blind and then measured, and it has one root cause. Its body
placeholder's right edge is 915.14 pt. Our line "One transceiver will be active
while the other transceiver" ends at 906.82 and we keep it; 26.2.4.2 breaks
before the last word. It is not metrics -- five identical lines measure the same
on the two sides to 0.6 pt. Rewriting the placeholder's cx one attribute at a
time puts the boundary between a right edge of 915.0, where the reference still
breaks, and 915.6, where it keeps the word, and the kept line's own extent is
915.70 -- 906.82 plus the 8.88 pt blank. So the blank is charged against the
break to within half a point, and "it hangs past the end of the line", which is
what ParagraphLayouter does, is refuted.

And the leading difference on the same page is that one line rather than a
second defect: the extra line tips the body past its autofit box, so the
reference takes constScaleLevels[0] -- font unscaled, spacing 0.9, applied as
round(GetHeight() * fSpacingY) in the InterLineSpaceRule::Off arm
(impedit3.cxx:1584-1600) -- and draws a 28.15 pt pitch where we draw 31.27 at the
same 25.99 pt size. Widen the placeholder by 1 pt and the reference draws our two
lines at our exact baselines, 241.97 and 274.68. A page that reads as wholly
displaced can be one word's worth of wrap and an autofit that followed it.

Left to task #28 rather than implemented here: the wrap is in
Paperless.Text/Layout/ParagraphLayouter, which all three tracks share, and
Writer genuinely does hang a trailing blank -- so it needs a mode the slides
path opts into and a slides-track confinement sweep behind it, the rule being a
tie-breaker that fires wherever a line's next word lands within one space of the
limit and so cannot be censused from markup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
Documentation only; the rule was implemented, measured over the slides track,
and reverted, and nothing in dotnet/src carries it. The previous commit recorded
it as a finding and this one withdraws that, because a later measurement on the
same page refutes it.

What suggested it: on slide 37 of the Intersil deck our line "One transceiver
will be active while the other transceiver" ends at 906.82 pt, the placeholder's
right edge is 915.14, and 26.2.4.2 breaks before the last word. Sweeping the
placeholder's cx alone puts the boundary between 915.0, where the reference still
breaks, and 915.6, where it keeps the word -- and the kept line's extent WITH its
trailing blank is 915.70, without it 906.82.

Why that is wrong: the placeholder is not the text area. The master's body
placeholder states lIns="0" tIns="45720" rIns="91440" bIns="45720", so the
available width is 859.94 rather than 867.14, and both renderers honour it --
checked by rendering each at rIns 0 and at 14.4. Our visible candidate is 858.83
and fits by 1.11 pt, and the reference breaks it anyway. Sweeping that one
attribute brackets the residue: the reference keeps the word at rIns 6.3 pt and
breaks at 7.2, so it needs between 1.11 and 2.01 pt more than the line's visible
text -- about 0.2% of the line, against a trailing blank of 8.26 pt. Four times
too small.

And the implementation settles it. LineFiller gained a chargesTrailingBlanks mode
and SlideTextLayout set it; slide 37 became exact, all thirteen baselines
matching the reference to the hundredth. Over the slides track, 210 of 302
documents move, and against a freshly rendered 26.2.4.2 the summed worst-page
diff% goes 1750.8 -> 1950.8, every page's summed diff% 15405.8 -> 16839.0, MAJOR
pages 280 -> 401, and 16 documents improve against 109 worse. Both formats
regress and the worst movers are large -- 4.79 -> 16.01, 8.45 -> 18.68,
9.63 -> 19.30 -- which is an extra line tipping a body into autofit where the
reference does not shrink.

The residue may not be a rule at all: 0.2% on one line is the same order as the
+/-0.6 pt spread the five identical lines on that page already show, and "the
reference measured this line a little wider" would explain the track result
exactly. probes/slidewrap-r190 keeps both sweeps, the scored table for all 210
movers, and what to rule out first.

What stands from the previous commit: the chart sweep's worst page is not the
chart's -- this deck's one chart is on slide 30 at 5.38 -- and the leading
difference on slide 37 is a consequence of the extra line rather than a second
defect.

Suite green at the reverted state; Core 612, Text 756, Vector 309,
Presentations 1220.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
An accounting number format ends its positive and zero subformats in `_)`, a
blank the width of a closing parenthesis, so every tick of a currency value
axis is a label ending in whitespace. A chart label is an EditEngine text
shape autogrown around its paragraph whole, so that blank is part of its
width — and the value axis reserves its widest label's width, so losing it
makes the band narrow and the plot area that much too wide, displacing every
gridline and every bar in it.

Only the slides measurer had it. It sums the advances the slide text layout
emitted, and that layout stops a line at its visible end, which is right for
a wrapped body (trailing blanks hang past the right edge rather than pushing a
word onto the next line) and wrong for a label that never wraps. The sheets
and words measurers shape the string and sum every advance, so both were
already correct.

Measured on Demick_JetBlue.pptx page 5 against 26.2.4.2 by deleting that one
token from the format and changing nothing else: the reference's plot area
starts at x = 165.71 pt as authored and at 162.65 without it, the gap from the
widest label's last glyph to the plot edge going 5.91 pt to 2.85 — and this
tree drew 161.99 and 2.66, the without-it answer. Deleting the leading `_(`
moves the edge by the same blank again and deleting the zero row's two `?`
placeholders moves nothing, that row not being the widest label.

On the new fixture the wall's left edge goes 128.70 to 131.41 against the
reference's 131.41, exact with no free parameter.

4 of the 67 chart-bearing decks move and 63 are byte-identical; three improve,
none worsens, and the four movers' alphanumeric and page counts are unchanged,
because a blank is not an alphanumeric character.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
A chart may state `a:pattFill` on its chart area or its plot area's wall, and
LibreOffice draws a pattern fill as a hatch: on one corpus page the reference
draws 298 strokes of which 295 are that hatch, and this tree draws none of
them, because the two backdrops are modelled as colours.

The whole change — the two properties and ChartBox.Fill widened to a paint,
the readers, and one shared hatch expansion in Core that all three tracks
reach — is banked as a patch rather than landed, with everything measured
behind it. Applied, it draws 296 lines against the reference's 295 and scores
flat: 14.42 to 14.43 diff% on the words witness and 26.71 to 26.70 on the
sheets one, with |ink|% about 0.36 worse on each. That is what a correct hatch
half a step out of phase costs, and the phase is the blocker: the reference
phases this family on the fill rectangle's top-left corner while Hatching
phases on its centre, reproducing GeoTexSvxHatch — which has its own
measurement behind it and is right on a slide to 0.3 pt of a 4 pt step. One
rule does not fit both, and choosing needs the corpus's 65 pattern fills
re-measured.

Two findings stand on their own. A Writer document's drawing layer works in
twips, so a hatch's stated hundredths of a millimetre are drawn as twips —
1.7639 times as far apart — measured over four presets at two angles with no
dependence on the angle, confirmed against VCL's own arithmetic, and not done
by the other two tracks. And the corpus's whole non-solid chart-backdrop
surface is three fills in three documents: a census of the string finds 13 in
6, of which 11 sit in the chart style part that LibreOffice never reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
A bulleted paragraph's first line starts at the greater of its margin and its
bullet position plus the bullet's own label width — and the label width is
measured at the run's STATED size, not at the size an autofit draws the text
at. One corpus deck has runs at 56 pt shrunk to 14, so its en-dash bullet is
measured 31.13 pt wide, overruns the level's 22.5 pt hanging indent, and pushes
every second-level first line 8.63 pt right of its own continuation lines. The
bullet glyph itself does not move.

Pinned over ten one-attribute variants of one master: four hanging indents and
two margins give five first-line positions exact and two clamped as predicted,
and the bullet character decides the width across an en dash, a W and an i. The
bullet's own stated size decides nothing, and neither does the stated font
scale, because the reference recomputes the autofit rather than honouring it —
so the shrink cannot be varied through the file.

It is a cascade rather than a cosmetic shift: the witness page is 15.93 diff%
with 0.14 |ink|%, because one line fits 94 glyphs where the reference's fits 92
and a two-line paragraph becomes three. Left unimplemented because the reach is
uncensused and every autofitted bulleted deck is a candidate.

Banked with it: a blind reading of the same deck's other bad page says our drop
shadow is a hard flat bar offset straight down, where the reference blurs over
about 11 rows and offsets down and to the right — an ignored blur radius and
lateral offset, not measured yet. And a census rule the deck taught twice: it
heads the chart ranking and its worst page holds no chart at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
A bulleted paragraph's first line starts at the greater of its margin and its
bullet's own position plus the bullet's label width, and that width is the one
the reference measured before the autofit shrank anything: the bullet is
measured once and cached on the paragraph, and the search formats the outliner
unscaled before it walks its scale levels. This tree already took the cached
HEIGHT that way, with its own measurement behind it; this is the width half of
the same rule.

One corpus deck has runs at 56 pt drawn at 14, so its en-dash bullet is
measured 31.13 pt wide where it is 7.79 pt at the size it is drawn, overruns
the level's 22.5 pt hanging indent, and pushes every second-level first line
8.63 pt right of its own continuation lines. The bullet glyph does not move.

A generated number is the exception and the confinement sweep is what found
it: of the ten documents the unrestricted rule moved, nine improved and one
regressed by 5.70 of summed diff% on a page whose second level is numbered,
where the reference starts those lines at the margin. The cache's own key
explains it — it compares the bullet's text as well as the scale, so a number,
whose text differs per paragraph, is re-measured once the search already has a
scale. Restricted to a fixed character, the rule keeps every gain and loses
nothing.

Pinned over twelve one-attribute variants of one master: four hanging indents
and two margins give five first-line positions exact and two clamped as
predicted; three bullet characters and three stated run sizes agree to a
hundredth of a point. The bullet's own stated size decides nothing, and neither
does the stated font scale, because the reference recomputes the autofit rather
than honouring it.

It is a cascade rather than a shift. The witness page was 15.93 diff% at 0.14
|ink|% — nothing drawn differently and a great deal drawn in the wrong place:
one line fitted 94 glyphs where the reference's fitted 92, so a two-line
paragraph took three and everything below dropped a pitch. It is now 13.13,
with the same 49 text records on both sides.

9 of the 302 slides documents move and 293 are byte-identical; all nine
improve, summed diff% 1148.05 to 1121.17 with MAJOR pages unchanged. The
slides track is the whole reach by construction, and no gate column can see
any of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
An embedded object -- a chart or an OLE object -- follows the text flow
whatever its anchor, because its property write is the one writerfilter
seat with no table guard on it. A chart's `a:graphicData` becomes a
`com.sun.star.drawing.OLE2Shape`, `DomainMapper_Impl` replaces that with a
`SwXTextEmbeddedObject`, and `:9792` then sets `IsFollowingTextFlow`
straight from `layoutInCell` -- where both of `GraphicImport`'s own writes
of the property are gated on `IsInTable()`. `m_bLayoutInCell` is
initialised true, so an anchor stating nothing follows it too.

`GetVertEnvironmentLayoutFrame` then resolves to the page body frame
rather than the page, and four things follow: a `PAGE_FRAME` offset is
measured from the body's top, the object is captured although its wrap
would exempt it, it is captured in the sheet rather than the body since
the compatibilityMode 15 narrowing needs a page frame, and only the top
correction fires. The horizontal is untouched, which is that file's own
walk rather than an omission.

Measured on eight fixtures identical but for whether the anchored object
is a `wps:wsp` or a `c:chart`: 26.2.4.2 draws the shape at 180.00 at
every margin and the chart at 252.35, 324.35 and 180.35 for `w:top`
1440, 2880 and `layoutInCell="0"`, with an absent attribute matching "1".
This tree reproduces 8 of 8 within the writers' own 0.36 pt constant.
Reach 5 embedded-object anchors in 5 documents; the in-table capture and
`PAGE_PRINT_AREA_TOP` stay open and are bounded in the records.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
`bCheckBottom = !DoesObjFollowsTextFlow()` was implemented as a fourth
consequence of following the text flow. The reading was right about the
expression and short by one call site: the offset arm of
`SwToContentAnchoredObjectPosition::CalcPosition` has a fourth
`AdjustVertRelPos` call, at :810-813, which omits the argument and so
takes its `= true` default -- and that call, under "do not follow text
flow respectively align at 'page areas', but stay inside given
environment", is the path an offset which does not fit its upper's print
area takes, which is the path a page-relative chart reaches.

`027_Unit_Circle_Chart_Graphical_Chart` decides it: 470.30 pt of chart at
a body base of 156.95 + 111.65 would reach 738.90 on a 595.30 pt
landscape page, and 26.2.4.2 draws its top at 125.00, exactly
595.30 - 470.30. Skipping the correction drew it at 268.60 and took the
document from 9.48 to 28.42 diff%; keeping it draws 470.30 against the
reference's 470.30.

Confinement over the words track, our half at the base and at the change
under a pinned clock: 2 of 337 renderings move, 335 byte-identical, and
the two are exactly the documents whose chart states relativeFrom="page"
-- the three stating margin do not move by a byte, PAGE_PRINT_AREA
resolving to the body either way. 027 goes 9.48 -> 4.61 worst-page diff%
and 028 16.78 -> 14.18, both drawing their chart where the reference
draws it. |ink|% rises on both, which is the sign to expect from a
signed-per-region measure when a frame moves into place.

Suites at their known-good counts, fidelity at its 10/542 baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…artifact-m1g0wy

Paperless fidelity: chart layout, and an embedded object's anchor counting from the page body
Projects, folders, assemblies and namespaces move from Paperless.* to
XRay.Office.* (git mv, so history follows), the solution becomes
XRay.Office.slnx, and the paperless-corpus skill becomes office-corpus.

Product-named types: PaperlessDocument -> OfficeDocument, PaperlessException
-> OfficeException. Environment variables PAPERLESS_* -> XRAY_OFFICE_*. The
CLI command is now xray-office; its assembly stays XRay.Office.Cli so the
skills' scripts find the binary where they expect it, and it is no longer
packable. The DrawingML preset resource name is spelled out, since
nameof(XRay.Office) would yield only "Office".

Text inside committed fixture documents, and the test assertions and docs
that quote it ("Paperless feature document", the PAPERLESS wordmark), is
deliberately unchanged, as are historical branch names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
The family writes "X-Ray" in prose and keeps "XRay" for code identifiers, so
comments, docs, skills, diagnostic messages and the PDF producer / HTML
generator defaults now say X-Ray.Office, while namespaces, project names,
paths, <c> and cref spans and Markdown code keep XRay.Office. The page-vision
pair label follows. CLAUDE.md records the rename and the packaging rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
Every library under dotnet/src packs as X-Ray.Office.* (PackageId derived from
the project name), with Curiosity GmbH as author, the family icon and a shared
package README, symbols as snupkg, and the curiosity-ai/x-ray-office URLs.
Tests, the test kit, the CLI and probes are not packable; the packable test
now uses this file's own directory instead of any "/src/" in the path.

Licence is MPL-2.0, from LibreOffice; X-Ray.Office.Text declares
MPL-2.0 AND OFL-1.1 AND Apache-2.0 AND Bitstream-Vera for its bundled fonts
and packs their licence table. The facade references every other library so
installing it brings the whole set.

.devops/build-x-ray-office-nuget.yml publishes all thirteen in one run at one
CalVer version, on master (and main) with a dotnet/ path filter. The dotnet
README is rewritten for X-Ray.Office and the root README gains a short section
pointing at it, above LibreOffice's own text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
Rename Paperless to X-Ray.Office and add NuGet packaging and pipeline
…PDFs

Cancellation (family convention): a per-thread OfficeOperation scope carries the
token to the loops that bound latency, so Open/Extract/OpenAsync, Layout,
IDocumentReader.Read, PdfRenderer.Render, RasterRenderer.Render/Rasterise,
SheetHtmlWriter.Write and the Markdown/XHTML writers take a CancellationToken
without threading it through every signature. Zip parts come back wrapped in a
polling stream; parsers, the word paginator and table layouter, the line breaker,
every shaper call, sheet pagination, slide shapes, the metafile readers and the
two drawing sinks check it. LayoutOptions gains Timeout (LayoutTimeoutException,
an OperationCanceledException subtype that a caller's own cancellation is not)
and MaxImagePixels with an OversizedImages policy, carried on the pages so both
renderers apply it; the decoder reads the header and never decodes a picture over
the limit.

PDF: objects are written to the stream as they are produced (no whole-file
buffer, destination need not seek); the file now carries the information
dictionary and an XMP packet from DocumentMetadata, /Lang, /Link annotations
(URI and internal), named destinations, a nested outline, page labels and /ID.
The layout declares them through four default IDrawingSink methods, from
PageRun.Link, PageParagraph.Anchors and OutlineLevel in DOCX, ODT, RTF and DOC,
sheet names, and slide titles.

Tests: 102 new (Rendering 77, Core 9, WordProcessing 16); the existing 7416 pass
unchanged with 474 skipped. xUnit1051 is switched off for the test projects
because the public API now has token overloads the existing suites deliberately
do not call.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
Cancel, time-limit and size-limit every long operation; write richer …
An external service connection with API key authentication needs it; the default installed version
(6.4) fails the push step with 'require NuGet 7.6 or later'.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
Pipeline: install NuGet 7.6 or later before pushing
A glob push stops at the first package that fails: the first runs of the multi-package pipelines
published one package each. One step per package runs even after an earlier push failed (once the
build and pack have succeeded), and allowPackageConflicts skips a package already on nuget.org at
this version, so re-running a build completes the set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
…uGetToolInstaller, no .snupkg

The installer put nuget.exe 6.4 on the path, which refuses the API-key service connection. Drop it
and let the push task use its own nuget.exe. In x-ray-others and x-ray-office each package also had
a .snupkg beside it (a second push); the first package was published and the push stopped there.
Symbols are embedded instead. Replaces the per-package push steps and template.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
Pipeline: publish like sentence-transformers-sharp (no NuGetToolInstaller, no .snupkg)
…t list

The pack step with a semicolon-separated project list packed only the first project, so only one
package was ever pushed (X-Ray.Office.Core). The build now runs with /p:GeneratePackageOnBuild=true,
as sentence-transformers-sharp does, and the push takes **/bin/Release/*.nupkg.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
Write the packages from the build, not from a pack step over a projec…
nuget.org only accepts licence ids approved by the OSI or the FSF and answers a push of a package
declaring Bitstream-Vera with a 400. The DejaVu and DjVu fonts keep their licence in the repository
and in the packages (NOTICE, fonts/README.md); the expression no longer names it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBJAUrwCwDhgrdf9n6Qguq
@theolivenbaum
theolivenbaum force-pushed the claude/gracious-hopper-qw3t31 branch from d5bdc0d to 6bb46d0 Compare October 19, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants