Repository navigation
Claude/gracious hopper qw3t31 - #67
Closed
theolivenbaum wants to merge 2642 commits into
Closed
theolivenbaum wants to merge 2642 commits into
theolivenbaum wants to merge 2642 commits into
Conversation
…O26's mechanism was the wrong one Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…never a second cause `SheetTextLayout.RichLineRanges` now appends the empty range a trailing hard break leaves, so a cell in several formats reserves the same line for it that a cell in one format already did. `LineCount` splits on the break before it wraps anything and has always counted it; the layouter closes a paragraph on its break and never opens the empty one after it, so `"a\n"` laid out as one line where the split answers two. O16 is closed on the measurement rather than on the brief. Row for row against 26.2.4.2's own resolved `style:row-height` for every row of TK-Syllabus-Comparison-Document-v2, 82909 of 82924 already agreed at the base and the fifteen that did not were all one line short; thirteen are this rule. The brief's "534 missing lines over pages 1-60" is an artefact of the instrument -- PyMuPDF groups our `157` and the cell beside it into one `line` and does not group the reference's, and the same sixty pages give 10998 spans for the reference against 11529 for us. The two remaining rows hold no trailing empty paragraph. Reach measured rather than censused: 1261 such cells in 63 of the 550 `.ods` and `.xlsx` sheets documents, and 10 of the 63 change a rendering, with 0 of 61 non-candidate controls moving. Over the ten, summed unsigned ink 759.30 -> 665.13 and MAJOR pages 318 -> 252; TK-Syllabus' `.xlsx` 205.57 -> 158.42 with MAJOR 66 -> 34. No gate column moves on any of them -- an empty line adds no character. O32 and O33 are characterised and left. `TOGAF9-Tool-ConfReqts-CSQ.xls` is a merged cell's background painted only as wide as the printed column block where 26.2.4.2 paints it across the whole merge; its row heights are exact 739 of 739 and its page-1 text agrees span for span. A BIFF shape run's posture is filed nil -- 0 of 367 `TXO` runs in the 64 `.xls` state an italic and the reference's own resolved view of the nine shape-path workbooks holds none -- and its colour is 119 spans in 2 of 64. dotnet/probes/sheet-wrap-r99/results.md has the measurements. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…ing lines were PyMuPDF's grouping Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…t artefact Round 98 measured 8521 reference lines against 7987 of ours over pages 1-60 and drew a 534-line gap from it. Those are PyMuPDF get_text line counts: the reference emits a cell and its neighbour as two lines where we emit one, every character at identical coordinates, and on spans the count runs the other way. The gap survives round 99 fix unchanged, which is what gives it away. Against the reference own resolved style:row-height, 82909 of 82924 rows already agreed at that base. I relayed the figure into round 99 brief; it was refuted there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…a fitted bullet is centred in the box it would have had unscaled O34's premise is refuted and the seat closes as a different defect. 26.2.4.2 does not clip, shrink or drop a text block taller than its shape: over the 33,549 shows it draws in the 51 .ppt, 91 have a baseline off the page -- 10 above the top, 81 below the bottom, in 10 of 51 documents -- which no clipping renderer produces. What round 99 recorded as "our own overflow behaviour" was a hard line break sizing the line it ends. EditEngine's line-metrics loop skips exactly one portion kind, PortionKind::LINEBREAK (impedit3.cxx:1498-1516, whose comment names the case), the break being the line's last character (:1441-1449); a line holding nothing but one is sized by the break's own character through the zero-height fallback (EditLine.cxx:69-71, impedit3.cxx:1478-1491). Round 99's VisibleEnd -> End move is right and brought this in with it, because a line separator is trailing whitespace. O28's baseline half closes on a stale cache in the reference. ImpCalcBulletArea takes the bullet box's height from ImplGetBulletSize, which caches it on the paragraph (outliner.cxx:1315-1355), and the autofit search formats the outliner once unscaled before walking constScaleLevels (impedit3.cxx:303-333) -- so the filling call is the unscaled one while StripBullet paints at the scaled size. This checkout keys that cache on the scaling parameters and declares 27.2.0.0 alpha, so the source leg is a later version's; the measurement is what decides. Measured at 26.2.4.2 on decks built for it. make-break-probe.py, five boxes and six swept sizes, converted to .ppt by the reference itself: 15 of 30 cells beyond 0.10 pt before, 0 of 30 after, with the trailing-blank and visible-glyph boxes moving identically at every size. make-bullet-fit-probe.py, one bulleted body per slide in a box swept 60-420 pt: 6 of 16 before, 0 of 16 after, and the residual is (1 - fontScale) x unscaledBulletHeight / 2 over five distinct scales. Its control, make-bullet-probe.py -- four stated sizes by six line-spacing percentages with noAutofit -- is 0 of 24 at both legs, which is what re-aimed the round. Corpus, the 51 .ppt against the banked 26.2.4.2 reference: match 49 -> 49, page counts unmoved, sum |glyph distance| 1417 -> 1307, summed |ink|% 252.82 -> 250.28, MAJOR 66 -> 65. Inducement-to-Insurance-Business p14 goes from 0 to 29 of 29 shows within 0.03 pt. On the 263 pages whose every body show sits within 0.10 pt of the reference's baseline, bullets beyond 0.10 pt go 315 of 1239 to 0 of 1243. slides-r99/tfy.py is withdrawn and replaced by slides-r100/tfz.py: it miscounts pages on 45 of the 51 banked reference PDFs and on 13 of 13 of ours, so round 99's O28 census -- 42 pages in 5 documents -- is 310 in 16 on a correct enumeration. O15 is unmoved by both fixes -- 17 pages, 13 documents, 66.06 pt, 0 fixed and 0 newly wrong -- and is sharpened: on RESPA_-_Section_8_Webinar the autofit search wins its constScaleLevels row by 7 hundredths of a millimetre, 0.20 pt of block. Suite 0 warnings, all eleven projects reached, Fidelity 542 passed / 10 failed of 552 with exactly the known names. Presentations 1054, seven new tests, of which four fail at 18f9da9 and the three controls pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…zes the line it ends Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
… passes A whole-solution run under a concurrent round reported fidelity 540/12; the two extras were LineBreakPositionTests and SheetDecorationComparisonTests, neither related to the slides-only merge under test and both invoking soffice, which the other round was driving. Isolated: 26 of 26. Whole project alone: 542/10 with exactly the ten known names. A false failure is worse than a false pass because the tempting response is to fix a defect that is not there or revert a good merge. Name the tests, reproduce alone, and ask whether they have anything to do with the change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…s that says which gaps are scope Survey only; no code changed. `dotnet/probes/chart-types-r101/results.md` holds one row per chart type with its spelling in each of the four routes, whether this tree reads it, whether it draws it, and its corpus reach. The four passes, kept separate: - LibreOffice's registry is 76 template services over 74 ids instantiating 19 chart types, plus surface and add-in, both commented out. Grepping the 26.2.4.2 install for each name settles the version question the brief raised: Funnel, Pareto, RegionMap, Sunburst, Treemap, Waterfall, BoxWhisker and ClusteredColumn are all in 26.2 and only Histogram is new in 27.2. - Sixteen `c:` elements, eight `cx:` layout ids, nine BIFF type records and thirteen ODF classes; all three importers substitute ColumnChartType for surface, with the same `// Todo`. - This tree has nine plot kinds, every one with a drawer, so read and drawn coincide; the empty-frame states come from the readers returning nothing. - 947 documents censused twice by methods sharing no code — markup on one side, the reference's own ODF export on the other. They agree on 182 of 183 documents, and the single disagreement is the one document whose charts are MSGraph OLE objects, which `CheckForConvertToSOObj` does not convert. Real reach: 11 documents state `c:smooth` and are drawn as polylines, 2 hold a chartex chart and draw an empty frame, 6 are 3D and drawn flat, 1 loses an ODF of-pie. Nil reach: surface in all four streams, six of the eight chartex types, five BIFF discriminators, histogram, and charts in `.doc`. OPEN-ISSUES gains O35, O36 and N24-N28. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…nd a BIFF shape run's colour is painted O32. `ScOutputData::DrawBackground` accumulates a merged run's width over the merged columns and breaks at `nCol > mnX2 + 2` while adding the width of column `nCol - 1`, so the fill reaches one column past the printed block and no further -- not the whole merge with the paper clipping it. Measured at 26.2.4.2 on a fixture whose four-column and three-column merges on a one-column block are painted at the same 5 cm, where the whole merge would be 11 cm and 8 cm and both fit the paper. Only the merge origin extends: it carries `ATTR_MERGE` and a covered cell carries `ATTR_MERGE_FLAG` and no column count. `TOGAF9-Tool-ConfReqts-CSQ.xls` 16.75 -> 12.92 summed unsigned ink with MAJOR 4 -> 1 and pages 1, 4, 24 and 26 to 0.00. Corpus reach 84 of 614 sheets renderings, 0 failures: 783.39 -> 700.41 over 4487 pages, MAJOR 346 -> 275, 51 improve, 27 level, 6 worsen. The restriction to a merge beginning at the block's first column was built and measured and is worse -- 197.92 against 182.19 over the six worseners and the ten largest improvements. O33. `SheetShapeRun` carries a colour, `XlsCellFormats.StatedColour` resolves a `FONT`'s `icv` through the workbook's palette and answers null for automatic, and `SheetShapePainter` paints it instead of always black. 2 of the 64 `.xls` move and 62 are byte-identical: `PC1000` 1.92 -> 1.59 on the reference's own colour profile exactly, and `TOGAF9` gains the one red span the reference draws. The write-up also corrects r99's `TXO` census (43 -> 48 runs, and the rest of its 43-against-119 gap is that a run is not a span), records that 113 of that seat's 119 spans are boxes this tree does not draw at all, and characterises O17: `alle einzeln.xlsx`'s 225.44 is a pivot table's generated cell formatting, whose `.ods` twin renders at 0.02 over the same 186 pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
… 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
…d was blind to displacement Following the words ranking to its top passing row found the instrument rather than a defect. `Annex-10-to-the-Aircraft-Maintenance-Specialist-Certification` was the worst passing document at 59.56 over 148 pages with no MAJOR page, `aligned` 1.00 and 189432 alphanumerics against the reference's 189432 -- exact. All of it is the cell shading, one grey level: ours 128 and 217, the reference's 127 and 216. Neither renderer is wrong about the colour. The operators are `0.502 ... rg` here and `0.5019607843 ... rg` there, the same byte written to four decimals and to ten, and the rasteriser resolves a literal at or below `v/255` to `v - 1` -- measured on six one-literal PDFs, including the exact `0.50196078431372549`, which also gives 127. So `PdfSyntax.Component`'s four decimals land above the value and 26.2.4.2's ten land at or below it, and every shaded area the two render differs by one level. Writing more digits would reproduce the reference's raster by reproducing an artefact of its own decimal output, so the change is in the metric. The second defect in the metric is worse: it compared each page's MEAN darkness, and a block drawn in the wrong place leaves a page's mean untouched -- so it could not see displacement, which is most of what a layout defect is. `rank.py` now compares the rasters pixel for pixel with a one-level dead band and reports `pixelD`, keeping `meanD` beside it for continuity. The table re-sorts completely and `Annex-10` leaves the top ten outright; the new head is `SPA-02_mcar_part-2_and_IS_v2.9.docx` at 1128.68 with 165 of its 266 pages MAJOR, and that one is not an offset -- 266 pages on both sides, and each low-scoring page best-matches the reference's page of the same index at 0.65 to 0.87. No source changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
Round 175 subtracted the paragraph's end indent from the whole line's fitted width. That also let a long title borrow it and stay on a line the reference wraps. Writer reaches the wider limit only once the right tab is the pending one -- `GetLastTab()`, `sw/source/core/text/inftxt.cxx`:2142-2144 -- so everything in front of that tab was already fitted against `rInf.Width()`. `WidthOf` now floors its answer at where the tab began, so the title still has to fit the line and only the stretch after the tab may reach into the indent. Found by following the corrected ink ranking to its head. `SPA-02_mcar_part-2_and_IS_v2.9.docx` is `02_mcar`'s Spanish sibling with the same TOC geometry, and there this tree fitted an entry's last word one line too high and then pushed the bare page number onto a line of its own with no leader. A blind reading of its page 21 caught exactly that -- the left half loses the dot leader when the trailing word is pushed down, while the right keeps word, leaders and number together. 4 of 337 renderings move, all four better and none worse, 333 byte-identical, no page count changed and so no gate verdict either: SPA-02 1128.68 -> 1050.26 pixelD, `02_mcar` 223.28 -> 222.79, and the two Holdover Tables by 0.18 each. SPA-02's page 21 is now identical -- 53 lines on both sides, the same first and last body baselines, the same footer. What remains on SPA-02 is two other things, both characterised and neither this rule: a nested list that runs one item later than the reference's from page 140 on, and a table whose rows drift about 0.02 pt each so page 3's lines sit 0.6 pt low by the foot. Ten projects green, 7240 tests. Fidelity at its documented 10 failures of 552. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
Three of the last four rounds found the instrument rather than the tree, so this one went looking for the rest. The first finding is that the toolkit already encoded most of the lessons and I had not been using it: `wordsink-r174`'s `rank.py` reinvented `track-ink-sweep.sh` with a worse measure, and `trace-text.py` already solves the repeated-label pairing trap that produced a phantom 55-264 pt displacement in `vmlgroup-r172`. Three real defects remained. Only two of ten scripts honoured `REF_SOFFICE`. `/usr/bin/soffice` is 24.2.7.2 and the tree is calibrated to 26.2.4.2. `batch-check.sh` resolved the override and announced the version, and `verdict.py` runs both binaries by design; every other tool hard-coded `soffice` and said nothing -- including `track-ink-sweep.sh` (the ink sweep), `ref-baseline.sh` (which banks the reference half) and `look.py` (which page-vision names for every page reading). The two disagree: `f445896e...docx` is 16 pages under 24.2.7.2 and 15 under 26.2.4.2, and the sweep over `words/done-005` reads `MATCH 9` on PATH against `MATCH 10` with the variable set, ABS-INK 8.55 against 1.31. All ten now resolve it and print the path and version as their first line. `pdf-image-diff.py` gated page counts but not page alignment. Its existing refusal on unequal counts is right; it does not cover equal counts carrying different content, where a block lost early is made up later. `misaligned_pages` now reads both text layers in one `pdftotext` call each and warns, and `track-ink-sweep.sh` carries the count into `ink.tsv` as a `drift` column. Validated against five documents whose pagination was established separately -- 0, 0, 1, 37 and 49, every one its expected value, and the Holdover's first drifted page is 73, exactly where its MAJOR pages begin from the pixels. Zero false positives in the batch checked by hand. Two of `compare-images.py`'s six metrics lacked the tolerance the other four have: on a half-shaded page differing by one grey level, `differing_fraction` and `differing_tiles` read zero while `mean_abs_error` reads 0.00196 and `max_tile_error` 0.00392. Rather than redefine a published measurement, `diagnose()` now names colour quantisation when no pixel differs past the tolerance and the mean is still not zero. The control is unchanged: a block moved 40 px still reads 6 differing tiles, 1 shifted, and DIFFERS. The documentation gap that explains the round: no SKILL.md mentioned `REF_SOFFICE`, although batch-check.sh had honoured it for a long time. It is now the primary note in libreoffice-reference, with pointers from the other two, which also gained the alignment and quantisation sections and a blunt "do not write your own scorer" -- that has produced a wrong published call twice, both a hand-rolled mean absolute difference. `validate.py` re-runs all three checks; `sweeps.txt` holds both sweep runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…wers what Running the sweep turned up a third tooling defect on top of the two fixed in the previous commit. batch-check.sh moved its text check from tokens with a floor of 3 to alphanumeric characters with a floor of 15 on 2026-09-05. track-ink-sweep.sh carried its own older copy of words_of and of the verdict block, so it kept scoring the corpus under the retired rule: 322 rows match under the old one against 329 under the new. Its gate now reads the same alphanumeric-character difference within max(2%, 15) that batch-check.sh does, ink.tsv carries the two glyph counts it is computed from, and the drift column says how many pages are misaligned so a ranking row can be read as honest or not. The re-run (probes/wordsink-r181) supersedes wordsink-r174, whose hand-rolled rank.py scored pages pairwise without checking they were the same page. With the alignment guard on, Annex-10 falls from 59.56 to 3.97 and ninth place, and the head of the drift-free ranking is HC-Bulletin-template 10.47, docs-quality-MA.IMS.00001 10.22, PK_FlugzeugeStricken 8.32, A_320 7.00 and SPA-06_mcar 6.77. r174's results.md now carries a note saying it is superseded and that rank.py should not be reused. The skills and dotnet/CLAUDE.md gain the routing that cost this round: which question each script answers, that REF_SOFFICE must be exported or every comparison silently measures 24.2.7.2, that an unguarded page pairing invents findings, that a raw mean is biased by antialiasing below the dead band, that --convert-to fodt answers an import question before any rasteriser does, and that the in-process Agent tool can read a PNG, correcting the claim in page-vision and CLAUDE.md that no reader was available in this container. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
`Paginator.HasInk` asked whether a block would draw anything a fly could displace by looking at the paragraph's text, and let a paragraph holding only control characters past as the empty spacer a `w:tblpPr` table may be drawn over. An as-character frame's anchor is such a character. So a paragraph whose whole content is one `wp:inline` picture read as empty, `RunsIntoTheFly` reported the flow clear, the table floated out of the flow, and the flow never left the top of the body. `words/done-016/docx/HC-Bulletin-template.docx` page 2 is exactly that shape: one full-width `w:vertAnchor="text"` fly holding the whole visible page, followed by a paragraph holding nothing but a 496 x 366 pt map. Read out of the two PDFs' image placements, 26.2.4.2 puts the map at y = 413.6, immediately under the fly; we put it at 63.3, over the table. A blind reading of the composed pair, given no numbers, reported the same thing independently and added what the numbers could not: the table is laid out at the same page position in both, so nothing was lost -- the picture was simply painted on top of it, hiding the body paragraph and all five of the table's rows. The distinction the old test could not make is real and both halves of it are now asserted. A `wp:anchor` states its own position and is never displaced, which is why Writer's own DOCX import leaves the paragraph it was written in empty; a `wp:inline` is a portion on the line and `SwTextFly` moves it as it moves a word. So `HasInk` now also counts an as-character frame, and `AFlyOverTheFlowStillSwallowsAnAnchoredDrawing` is the control that says the other case did not change with it. Swept over all 337 words documents against the banked 26.2.4.2 reference: the gate is unmoved at 329 `match`, and **exactly one row of 337 differs** -- this document, from 10.47 to 3.89 unsigned ink and from 2 major pages to 1. Track totals ABS-INK 444.35 -> 437.77, MAJOR PAGES 173 -> 172. Ten non-fidelity projects green; `Paperless.Fidelity.Tests` at its documented 10 failures of 552. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…xt it states `a:spAutoFit` set `PageFrame.HasFixedHeight` to false -- the shape's text was not truncated -- and nothing else, so the shape kept whatever `a:ext/@cy` the file happened to carry. Word stores a fitted height there, fitted in Word's own font metrics, and it is a cache rather than a declaration. Measured on 26.2.4.2 over twelve authored boxes, now in `probes/words-spautofit-r182`: one, three and six 16 pt lines crossed with a stated `cy` of 200 pt and of 40 pt. With `a:noAutofit` the filled rectangle is 200.00 and 40.00 pt, the stated height and nothing else. With `a:spAutoFit` it is **25.55, 62.45 and 117.80 pt whichever `cy` is stated** -- 18.45 pt a line plus 7.2 pt of inset, the stated height read as neither a floor nor a ceiling. That last part is why `PageFrame.HeightFloor` had to exist rather than `GrowsToContent` simply being set: ODF's `fo:min-height` *is* a floor, and an ODF frame with less text than it states keeps its height. Ours now reproduces all three heights to within 0.10 pt, the same 0.10 on each. `words/done-016/docx/HC-Bulletin-template.docx` page 5 is the corpus case: one such box stating 110.55 pt around three 16 pt paragraphs. Both sides fill the rectangle at the same x, the same top edge and the same 508.50 pt width, and the reference's is 65.75 pt deep against our 110.55 -- exactly the 0.9134 in its own `--convert-to fodt` writes for it. A blind reading of the page, given no numbers, reported the surplus as empty fill below the last line with the text itself at identical y, which is what the arithmetic says it is. **A WordArt shape is the exception, and it is the reference's own source rather than a guess.** `WpsContext::correctTextBoxesForWordArt` (`oox/source/shape/WpsContext.cxx`:1021) turns a shape whose body carries an `a:prstTxWarp` other than `textNoShape` into a Fontwork and then clears `TextAutoGrowHeight`, saying why: *"Fontwork stretches the text to the given path. So adapt shape size to text is nonsensical."* The sweep found it before the citation did. `exhibit-06---technical-architecture-template.docx` puts a `fromWordArt="1"` shape in each of its two headers, stating 412 x 248 pt around one run of 1 pt `DRAFT`; fitted to that run the diagonal watermark collapses to a speck, and the document went from 0.13 to 3.18 unsigned ink and from no major page to two. Gating on the Fontwork case -- including the `ooxml-rect` condition the same function checks -- puts it back at 0.13 exactly. `HasFixedHeight` is deliberately left on the wider test, since what truncation means for text stretched along a path is a different question and no measurement was taken on it. Swept over all 337 words documents, `probes/wordsink-r182`: the gate is unmoved at 329 `match` and **exactly one row differs from r181**, HC-Bulletin, 10.47 -> 1.87 unsigned ink and 2 major pages -> 0. Track totals ABS-INK 444.35 -> 435.75, MAJOR PAGES 173 -> 171. Every non-fidelity project green at 7,244 tests; `Paperless.Fidelity.Tests` at its documented 10 failures of 552. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
… put first `abs_ink` is the per-page unsigned ink column SUMMED, so it ranks by length. Round 182 handed this round a drift-free ranking headed by a 44-page manual at 10.22, and most of the round went into it before the arithmetic was noticed: its **mean is 0.23 a page**, which is the raster floor for text that dense, and its worst page is 2.77 -- seventh in the same track. The three one-page chart templates it outranked are 5.18, 4.64 and 4.25 on their only page. `ink.tsv` now carries `worst` and `mean` beside the sum, the sweep prints a WORST PAGE line, and the skill and `dotnet/CLAUDE.md` say to rank on `worst` and to read a mean below about 0.3 as "no defect, only antialiasing". The re-ranked track is `probes/wordsink-r183` -- same sweep, same CLI, totals identical to the hundredth. The two orders disagree about almost everything: `A_320.doc` was fourth at 7.00 and its mean is 0.06 over 118 pages, while four of the new top six are single-page documents whose whole content is one wrong page. What was inside the 44-page manual is banked in `probes/footerlink-r183` and is the reference's artefact, not ours. Its footer sits **11.50 pt lower in ours on pages 8 to 44 and matches exactly on pages 1 to 7** -- the split falling precisely at the first section that states no `w:footerReference` and inherits. Appending a visible paragraph after that footer's table draws it at y 802.40-813.56, filling the gap without moving the copyright block, so the reference had already reserved one paragraph's worth of space; its own `--convert-to fodt` writes `<text:p text:style-name="Footer"/>` after the table in the two master pages built for the inheriting sections and in none of the three built for sections naming their own. The seat is `copyHeaderFooterTextProperty` (`sw/source/writerfilter/dmapper/PropertyMap.cxx`:935-960), whose `removeXTextContent` cannot leave a text body with no paragraph, so one survives the copy; the comment above it says outright that LibreOffice cannot link header or footer content across page styles and copies it instead. A two-section synthetic reproduces the spare paragraph from scratch. Word shows the linked footer unchanged and so do we, so the entry added to `TODO.word-parity.md` is not switched: supplying the paragraph in the file moves this tree from 10.22 to 9.93 and opens a two-page drift, which is a worse reading of the document for 0.29 of ink. Page 30's remaining 2.55 is a 0.5 % taller embedded drawing pushing one caption line onto page 31, also not taken. No product code changed; nothing was rendered differently by this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…eps its own margins `Ww8SectionTable.ResolveContinuousBreaks` copied the previous section's vertical margins onto every compatible continuous section, and `ContinuousPageDescriptors.Inherit` then did the same carry again for the sections that really do lose their descriptor. The duplication was not harmless: `wwSectionManager::InsertSegments`'s "nightmare scenario" arm builds a descriptor for a continuous section that states furniture of its own and hangs it on the first hard page break inside the section, **carrying that section's own margins with it**. `ContinuousPageDescriptors.Resolve` already declines to inherit in exactly that case, and could not put back what the table pass had erased. Measured on `words/done-014/doc/PK_FlugzeugeStricken.doc`, whose second section is continuous, states its own running head and states `sprmSDyaTop` = 1701 twips against the first's 1417. Patching that 1701 to 2500 moves the reference's page-3 body from 85.08 pt to 125.03 and repaginates it from 7 pages to 11, and moved **nothing at all** here -- because 1417 had already replaced it. Patching the *first* section's 1417 instead moved ours, which is what we were using. Pages 3 to 7 now match: unsigned ink 0.10, 0.08, 0.21, 0.36, 0.54 -> 0.01, 0.01, 0.04, 0.05, 0.03, and page 6 from MAJOR to ok. The probe and its `patch-sepx.py` are in `probes/docsection-r184`. `foca_form_1.doc`, the document round 72 measured the squash on, is unchanged at 3 pages: its second section states no furniture of its own, so `ContinuousPageDescriptors` drops its margins exactly as the squash used to. `DocSectionGeometryTests` now pins both arms where they are decided -- own furniture plus a hard break keeps the margins, either condition alone loses them -- instead of asserting the squash at the table reader. Swept over all 337 words documents: the gate is unmoved at 329 `match` and **two rows differ, both improving** -- this document 8.32 -> 7.13 with 3 major pages -> 2, and `150_5300_13_chg12.doc` 7.97 -> 7.96. Track ABS-INK 435.75 -> 434.55, MAJOR PAGES 171 -> 170. Every non-fidelity project green; `Paperless.Fidelity.Tests` at its documented 10 of 552. What is left on that document is one page, and it is the same rule seen from the other side: the reference defers the whole descriptor -- running head included -- to the hard break, so its pages 1 and 2 both wear the first section's letterhead where Word and this tree put the running head on page 2, where the section begins. That half is recorded in `TODO.word-parity.md` and deliberately not reproduced. `probes/wordsink-r184` re-ranks the track: with those 6.42 and with `docs-quality`'s 10.22 both accounted for, the live head is the one-page chart and template cluster at 5.18, 4.64, 4.25 and 3.58. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
`PaintOf` painted only a `v:rect` and a `v:roundrect`, and the remark beside it said why: a `#_x0000_t15` is a pentagon and filling its bounding rectangle would be a confident wrong answer. That was right about the rectangle and wrong about the consequence -- the pentagon went undrawn entirely, and so did the fill it states. A `v:shape` names its geometry by Escher's `MSO_SPT`, either in `type="#_x0000_tN"` or as an `o:spt` on the `v:shapetype` it points at, and `PageFrame.Preset` already routes a DrawingML preset name through `CustomShapeGeometry`, which holds all 187 of them. So the whole of the fix is a number-to-name map and letting a named geometry be painted. The map is the reference's own `GETVMLShapeType` table read the other way round (`filter/source/msfilter/util.cxx`:1072-1280) over the `MSO_SPT` numbering in `include/svx/msdffdef.hxx`:274 -- not a mapping from memory. `t32` and `t136` are left out because the straight-connector and Fontwork paths already draw them. Nothing is defaulted by naming a geometry: a shape is still painted only with the fill and the stroke it states, so the 103 `#_x0000_t75` picture frames that state neither are still not given a white box and a black border. That control is asserted. Measured on `090_Business_Case_Template_Blue_Theme`, whose three section headings are each a `type="#_x0000_t15" fillcolor="#002060"` band with the heading text inside it. A blind reading of the composed page, given no numbers, reported the reference drawing "a solid dark-navy right-pointing chevron/pentagon band" behind each heading and ours drawing "nothing -- no fill, no outline, no shape at all", with our heading black where the reference's is white. Page unsigned ink **4.25 -> 1.53**, and the banner's position, size, fill and knocked-out white text are all right afterwards; what is left is an 8 px vertical offset and a 7-10 px table shift that were there before. Swept over all 337 words documents: the gate is unmoved at 329 `match` and **exactly one row differs**, this one. Track ABS-INK 434.55 -> 431.83. Worth recording is why only one: the corpus holds 202 inked `#_x0000_t202` uses across 27 documents and not one moved, because they are `mc:Fallback` VML beside a `mc:Choice` this reader takes instead. A census that does not separate a bare `w:pict` from a fallback overstates a VML rule's reach by an order of magnitude. `probes/wordsink-r185` records that and what the four blind readings of the chart cluster found. Every non-fidelity project green; `Paperless.Fidelity.Tests` at its documented 10 of 552. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
A review of every chart path against LibreOffice's source found two readers switching on a type's identifier where the reference switches on a field inside the body it had already read, and one sweep establishing that nothing else about the chart types is systematically wrong. BIFF has no record id for a doughnut or a bubble chart. XclImpChType::Finalize (sc/source/filter/excel/xichart.cxx:2288-2303) reads the body first and then decides: (mnPieHole > 0) ? DONUT : PIE, and mnFlags & EXC_CHSCATTER_BUBBLES. XlsChartReader skipped both bodies, so every .xls doughnut was drawn as a pie and every bubble chart as a scatter. A bubble series' sizes arrive on a third source-link destination, EXC_CHSRCLINK_BUBBLES = 3, which had no case either -- switching the kind without it draws every bubble at one radius. Three of those records' six fields are read by the reference and then used by nothing, so they are deliberately left: CHPIE's anStart becomes a StartingAngle that ChartPlot does not model, and CHSCATTER's pcBubble and wBubble are the BIFF spellings of c:bubbleScale and c:sizeRepresents, which oox also parses and ignores. Measured on 26.2.4.2's own --convert-to 'xls:MS Excel 97' of the corpus chart workbooks, so both renderers read identical bytes: worst-page unsigned ink falls 0.88 -> 0.37 on the bubble fixture and 0.36 -> 0.16 on the doughnut, with six controls unmoved to the hundredth -- including their own type-mates, whose CHSCATTER scale and interpretation fields are identical to the bubble fixture's. probes/chartbiff-r186. An unrecognised ODF chart:class is a bar chart, not nothing. SchXMLChartContext::ParseAttributes sets the service name only for a chart:-namespace class GetChartTypeEnum knows, or for the ooo: add-in branch (xmloff/source/chart/SchXMLChartContext.cxx:372-402); everything else leaves it empty and falls through to :470-478, which reads aChartClass_Bar. OdfChartPlot returned null and drew nothing. Over fifteen one-attribute variants of one flat ODS -- only the chart:chart element's own attribute rewritten, its series left at chart:bar, because rewriting every chart:class gave a misleading answer on the first cut -- the reference draws the same 126 path operators for every one of them as it does for chart:bar itself, and we drew 6, the sheet's own furniture. The ooo: spelling stays null: that branch takes the service name verbatim, the fallback never fires, and the reference draws 22 paths there. probes/chartclass-r186. Neither has any reach on this corpus, which is the finding rather than a disappointment. Every chart:class the converted-ODF corpus states is one the reader already drew, and exactly one of the corpus's 180 OLE2 files states a CHPIE or CHSCATTER at all -- a plain scatter, already right. Confinement is measured rather than argued: our half of all 370 OLE2 sheets and converted .ods, plus the 10 converted .odt holding a chart, rendered at the round's base and again with the change, is 380 of 380 byte-identical. Paperless.Fidelity.Tests is 10 failed of 552 before and after, the same four classes each time, all of them ones dotnet/CLAUDE.md records as left failing on purpose and none a chart test. The sweep behind the review is banked too: 168 documents holding a c: or cx: chart part, rendered both ways and grouped by the set of plot elements their parts state. No type is systematically wrong -- barChart 0.76 mean worst-page |ink|% over 55 documents, lineChart 0.58 over 14, doughnutChart 0.68 over 13, bubbleChart 0.13 over 9, areaChart 0.05 over 9, cx: chartex 0.01 over both witnesses -- and every row above that is one or two documents, so the chart work left is per-document layout. Also recorded: nineteen chart types are declared in servicenames_charttypes.hxx and VSeriesPlotter::createSeriesPlotter has a plotter for eleven, so the other eight draw a string and not a plot. probes/chartsweep-r186. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
…was wrong The chart sweep banked one commit ago concluded that no chart type is systematically wrong. That is false and the instrument is why. Both |ink|% and diff% are fractions of the whole page, and a chart is a small part of one -- so a chart drawn entirely wrongly on an A4 page that is otherwise white scores almost nothing. |ink|% is worse than that: it is signed per region before the page's absolute value is taken, so ink we add where the reference removes it cancels within the page, and a wrong-shaped pie in the right colours is exactly the shape that cancels. pie-chart-result.docx reads 0.30 unsigned ink and 2.76 differing pixels, and what it actually shows is the reference drawing an extruded 3-D pie where this tree draws a flat circle. Re-ranked on diff%, the types separate into three groups rather than one. Right: cx: chartex 0.16, areaChart 0.39, bubbleChart 0.58. Right in the main with bad individual documents: barChart 2.81 over 55 with a worst of 28.50, lineChart 2.59, scatterChart 2.93, doughnutChart 3.32, pieChart 3.57. Wrong wherever they appear: ofPieChart 11.00 over both of its documents and pie3DChart 5.06 over all three of its, and two of those three read 0.30 and 0.04 in the column the first cut ranked on. A second defect in the same script, found with it: aggregate.sh averaged column 4, the summed |ink|%, while printing it as the worst page's, so the barChart row read 0.76 mean and 15.17 worst where it is 0.26 and 1.91. rows.tsv now carries a worst-diff% column and aggregate.sh prints both, named. Also recorded from the same measurement: a 3-D chart's reach is three documents and all three are pies -- no bar3DChart, line3DChart, area3DChart or surface3DChart anywhere in the corpus -- and all three state rAngAx 0 with no c:perspective, which View3DConverter::convertFromModel (oox/source/drawingml/chart/plotareaconverter.cxx:262-320) turns into RotationHorizontal = clamp(rotX, 0, 90) - 90, Perspective = 30/2 = 15 and therefore ProjectionMode_PERSPECTIVE rather than parallel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaC5hR8FCp4YHWcibUfM3o
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
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.