Commit graph

1446 commits

Author SHA1 Message Date
zmworm
e888f1008c fix(pptx-html): non-solid stroke width in pt, inset by half-stroke, explicit dots
Dashed/dotted/dashDot shape outlines in HTML preview rendered ~25%
thinner than the solid border path and shrunken relative to their
content box. The SVG overlay emitted stroke-width as unitless user
units (rendered as CSS px, not pt) and positioned the rect
edge-centered while CSS borders sit outside the box. Plus dashDot
relied on stroke-linecap=round painting a zero-length dot which
disappeared at thin strokes.

- HtmlPreview.Shapes.cs: plain-rect / ellipse / polygon / rounded-rect
  SVG overlays emit stroke-width in pt; plain-rect + ellipse size in
  real pt via calc(100% - {bw}pt), inset by bw/2 so the stroke sits
  flush inside the content box. stroke-linecap round -> butt.
- HtmlPreview.Css.cs: DashTypeToSvgDasharray replaces 0.1 zero-length
  dot trick with explicit {w} dot segments across dot, sysDot,
  dashDot, lgDashDot, sysDashDot, sysDashDotDot, lgDashDotDot.
2026-04-18 15:43:01 +08:00
zmworm
b445409e40 fix(xlsx): support shape gradient fill via gradientFill=
SH6. Adds a distinct gradientFill= prop for shape/textbox that accepts
C1-C2[-C3][:angle] syntax. fill= stays strictly single-color to avoid
overloading it with gradient semantics (FF0000-0000FF would otherwise
collide with flexible ARGB literal inputs). Emits <a:gradFill> with
two-or-three <a:gs> stops and a <a:lin ang='0' scaled='1'/> default.
2026-04-18 15:36:41 +08:00
zmworm
da6cdbb955 fix(xlsx): emit showLeaderLines on pie/doughnut
CL15. Accepts top-level showLeaderLines=true as an alias of
datalabels.showleaderlines so pie/doughnut callers don't have to
reach for the qualified form. Writes <c:showLeaderLines val='1'/>
under every <c:dLbls> in the chart.
2026-04-18 15:36:18 +08:00
zmworm
a457c30e56 fix(xlsx): honor chart errBars.direction
CL23. Adds errBars.direction / errBarDirection keys that write
<c:errBarType val='plus|minus|both'/> on every series' existing errBars.
Default stays 'both' (from BuildErrorBars). Applied after errBars=
is already set, matching how series-level dotted props compose.
2026-04-18 15:35:21 +08:00
zmworm
e8630f0055 fix(xlsx): support chart trendline label/forecast/order/equation props
CL23. Extends the trendline setter to accept dotted sub-props at the
chart level that fan out across every series: trendline.label,
trendline.forecastForward, trendline.forecastBackward, trendline.order
(polynomial), trendline.period (moving average), trendline.intercept,
trendline.displayEquation, trendline.displayRSquared. Label also emits
<c:trendlineLbl> so Excel actually paints the label.
2026-04-18 15:34:16 +08:00
zmworm
80dbedf21c fix(xlsx): honor cell readingOrder property 2026-04-18 15:22:42 +08:00
zmworm
96d801a01b fix(xlsx): honor picture anchorMode=oneCell|absolute 2026-04-18 15:19:38 +08:00
zmworm
b5caba15ce fix(xlsx): accept logScale=true shorthand for logBase=10 on chart axis 2026-04-18 15:17:24 +08:00
zmworm
e9fad01c02 fix(xlsx): validate row height / outline level / column width ranges 2026-04-18 15:15:57 +08:00
zmworm
73a4b95c23 fix(pptx-html): radial gradient fills to shape edge, not bbox corner
HTML preview emitted radial-gradient(circle, ...) with no size keyword,
so CSS defaulted to farthest-corner — the final stop landed at the
bounding-box corner instead of the shape edge. For a square shape with
gradient=radial:4ECDC4-FFFFFF, the right/bottom edge rendered tinted
teal (~#CAF0EE) instead of white.

OOXML <a:path path="circle"> with no explicit fillToRect/tileRect fills
to shape bounds; the CSS equivalent for a rectangular shape is
closest-side.
2026-04-18 15:12:36 +08:00
zmworm
9e178c1038 fix(xlsx): preserve quotes on validation list formula1 readback 2026-04-18 15:08:38 +08:00
zmworm
05bf4f92aa chore(xlsx): suppress null-argument warning in CommentRunProperties readback 2026-04-18 15:06:15 +08:00
zmworm
c3b7d687fe fix(xlsx): emit a16:decorative on picture 2026-04-18 15:04:25 +08:00
zmworm
a8b033ef04 fix(xlsx): honor comment font properties 2026-04-18 15:02:48 +08:00
zmworm
04ea4ca1ef fix(pptx-html): honor avLst adj for rightArrow and star5 clip-paths
HTML preview hard-coded polygons for parametric shapes, ignoring
<a:avLst> adjustment values. rightArrow rendered a stubby head (30%
wide instead of default 50%) and star5's bottom points stopped at
y=91% with a pinched waist (inner ratio 0 instead of default 0.382).

Add RightArrowPolygon and Star5Polygon helpers that read adj1/adj2
from prstGeom and compute polygon vertices from OOXML defaults
(rightArrow: adj1=50000, adj2=50000; star5: adj=19098 -> ratio=0.382).

Also remove unused System.Text using directive.
2026-04-18 15:00:25 +08:00
zmworm
3bdf6ed231 fix(xlsx): honor picture hyperlink
add picture --prop hyperlink=URL (and alias --prop link=URL) previously
accepted the value but wrote no <a:hlinkClick> under <xdr:cNvPr> and no
drawing-level relationship; the picture was not clickable in Excel. The
picture Add branch now appends HyperlinkOnClick to the cNvPr:
external URLs get a new HyperlinkRelationship on the DrawingsPart and
reference its rId; targets starting with '#' use the Location attribute
with no rel, mirroring the cell-link fix in commit 60e1455.
2026-04-18 14:55:35 +08:00
zmworm
8f8292d62c fix(xlsx): honor picture crop
add picture --prop crop.l/r/t/b=N (and compound --prop srcRect=l=N,r=N,...)
previously accepted the parameters but wrote no <a:srcRect> under
<a:blipFill>; real Excel rendered the image uncropped. BuildPictureBlipFill
now parses percent values (10 or 10%) and emits a SourceRectangle before
the <a:stretch> element with each side scaled to the 1/1000-pct units
Excel expects (10% → 10000).
2026-04-18 14:54:08 +08:00
zmworm
8450b0ee3b fix(xlsx): honor picture opacity
add picture --prop opacity=N previously accepted the parameter but wrote
no <a:alphaModFix> under <a:blip>; real Excel rendered the image fully
opaque. BuildPictureBlipFill now parses opacity (percent 0..100, fraction
0..1, or '50%' string) and emits the alphaModFix element on the blip with
amt scaled to 0..100000. Values at 100%/1.0 (fully opaque) remain no-op.
2026-04-18 14:52:54 +08:00
zmworm
fc151265c1 fix(xlsx): coalesce chart dataLabel entries by idx
Setting both dataLabel{N}.text and dataLabel{N}.delete on the same
point previously wrote two separate <c:dLbl idx="N"> elements and
left their children in non-schema order, which Excel rejects with
0x800A03EC.

HandleDataLabelDottedProperty now:
- Finds-or-creates a single dLbl per idx across successive Set calls
  so text+delete merge into one element.
- Enforces "delete wins" semantics: when delete=true, strips tx,
  numFmt, dLblPos, and show* siblings; a later text/pos/numFmt on an
  already-deleted dLbl is a no-op.
- Reorders dLbl children into CT_DLbl schema order after every
  mutation (idx, delete, layout, tx, numFmt, spPr, txPr, dLblPos,
  show*, separator, extLst) so the chart validates regardless of
  which property was set first.
2026-04-18 14:44:41 +08:00
zmworm
87c91afb7a fix(xlsx): apply built-in Hyperlink cell style to link cells
Cells written with link= now reference the built-in Hyperlink cellStyle
(builtinId=8) with a blue underlined font, so they render as proper
hyperlinks in real Excel instead of plain black text.

ExcelStyleManager.EnsureHyperlinkCellStyle() idempotently creates the
Font (color 0563C1 + underline), the cellStyleXfs + cellXfs entries,
and the CellStyles Name="Hyperlink" BuiltinId=8 record. Both Add and
Set hyperlink paths invoke it when the cell has no user-provided
styling, preserving explicit font=/color= overrides.
2026-04-18 14:38:04 +08:00
zmworm
df87e1dfb7 fix(xlsx): declare mc:Ignorable=x14 on worksheets with sparklines
Without mc:Ignorable="x14" on the worksheet root, Excel silently
drops the entire extLst block and no sparklines render. Add the
mc and x14 namespace declarations and merge x14 into the root
worksheet's MCAttributes.Ignorable when a sparkline is created.
2026-04-18 14:34:04 +08:00
zmworm
21d8d86443 fix(xlsx): grow tableColumns when set ref expands
set /SheetN/table[i] --prop ref=... now grows or shrinks the
<x:tableColumns> list so its Count matches the new column span in
the ref. Expanded columns get default ColumnN names (deduped).
Previously Excel rejected the file because tableColumns.count
mismatched the new ref width. See audit T5.
2026-04-18 14:30:31 +08:00
zmworm
9f0561472a fix(xlsx): reject overlapping add table
Adding a table whose ref intersects an existing table on the same
sheet now throws at Add time with a clear message. Previously both
adds succeeded silently and Excel rejected the file on open
(0x800A03EC). See audit T4.
2026-04-18 14:30:24 +08:00
zmworm
8f0716fde6 chore: bump version to 1.0.52 2026-04-18 13:24:40 +08:00
zmworm
9d298ea3ea fix(resident): redirect stdout on macOS/Linux to prevent pipe inheritance hang
TryStartResidentProcess only set RedirectStandardError=true, so posix_spawn
let the __resident-serve__ child inherit the caller's stdout. Any downstream
pipe (officecli create x.xlsx | tail, $(officecli open ...), CI redirects,
SDK stdout capture) then never saw EOF until the resident itself exited
(60s-12min idle), blocking the caller for the resident's full lifetime.

The Windows path was already guarded by SetHandleInformation. Add
RedirectStandardOutput=true so the child gets a fresh .NET-managed pipe on
Unix too, matching the commented intent. Resident writes no startup stdout
(Console.SetOut is sandboxed during ExecuteCommand) so there is no SIGPIPE
or backpressure concern.
2026-04-18 13:05:34 +08:00
zmworm
25a8217860 refactor(watch): detect excel chrome change server-side
Follow-up to #64. Move the colgroup/thead/table-width change detection
from the browser to WatchServer: when TableChromeSignature differs
between old and new HTML, skip the row-level excel-patch path and fall
through to the existing full-action body refresh.

This replaces the client-side _excelPatchChangesStructure heuristic
(cell-count comparison per patch, followed by fetch('/') re-download)
with a single regex compare at the true source of the diff. Drops the
extra network round-trip and catches column-width-only or thead-style
changes that cell counting misses.

- add TableChromeSignature next to ChartOverlaySignature
- guard the excel-patch branch in HandleWatchMessage with the signature
- remove _excelPatchChangesStructure function and its call site
- _replaceDocumentBody helper kept (still used by the full-action path)
2026-04-18 11:49:54 +08:00
zmworm
fd020815ba chore(xlsx): remove unused System.Text using in PivotTableHelper.Definition 2026-04-18 11:44:38 +08:00
xpfxzxc
d6564fcce3
fix: refresh excel watch when row patches change columns (#64) 2026-04-18 11:44:11 +08:00
zmworm
3150c2ba0b fix(xlsx): round-14 audit P2 batch
- SH3: accept scheme color names (accent1-6, lt1/dk1, bg1/bg2, tx1/tx2,
  hlink, folHlink) for sheet tabColor; maps to TabColor@theme index.
  Query readback echoes the symbolic name rather than the raw index.
- RC1: row `height=` and col `width=` now accept unit-qualified
  strings (40pt, 40px, 1cm, 0.5in). Row height stores points; col
  width stores char units via 7-px-per-char approximation. Bare
  numbers still accepted for backward compat.
- AF3: autofilter Add rejects garbage `range=` that doesn't parse
  as a cell reference, preventing silently-broken OOXML files.
- C10: comment Add rejects a duplicate comment on a cell that already
  has one, mirroring the table overlap-reject pattern. Users must
  remove the existing comment first.
- P13: picture Add accepts `name=` to override the auto-generated
  "Picture {id}" label stamped into xdr:cNvPr @name.
- P9:  picture Add accepts `altText=` as alias for `alt=`.
- P11: picture Add accepts `title=` and stamps it into the
  xdr:cNvPr @title attribute (distinct from @descr).
- H2:  hyperlink Add + Set accept `tooltip=` / `screenTip=`
  and write it to the Hyperlink @tooltip attribute. Set on tooltip
  alone updates an existing hyperlink without touching its URL.
2026-04-18 05:17:29 +08:00
zmworm
8396f265b4 fix(xlsx): persist pivot sort and repeatLabels in OOXML
Previously `sort=desc` and `repeatLabels=true` only affected
write-time rendering / wrote a workbook-wide x14 default; neither
survived Excel reopen. Now:

- sort=asc|desc|locale|locale-desc stamps sortType="ascending" or
  "descending" on each row pivotField.
- repeatLabels=true emits a per-field x14 repeatItemLabels="1" ext
  (2946ED86-A175-432a-8AC1-64E0C546D7DE) on every outer row field
  (all row fields except the innermost). Replaces the prior
  workbook-wide fillDownLabelsDefault attribute, which was a
  default-for-future-pivots rather than a knob for the current one.
2026-04-18 05:16:47 +08:00
zmworm
002db9efdf fix(xlsx): wire silent-dropped CF/validation/table props into Add
CF2 — stopIfTrue=true now honored on all CF types (dataBar, colorScale,
  iconSet, formulacf, cellIs/topN/aboveAverage/etc). Was silently dropped.
CF5 — 3-color colorScale accepts midpoint=N (percentile) instead of
  hard-coded "50". Default 50 preserves existing behavior.
CF6 — dataBar showValue=false hides the numeric value under the bar.
  OOXML default is true; now emits ShowValue=false when the user opts out.
V6 — validation errorStyle={stop,warning,information} wired onto
  DataValidation.ErrorStyle. Was silently dropped on Add.
V7 — validation inCellDropdown / showDropDown: maps user-friendly
  `inCellDropdown=false` to OOXML-inverted `showDropDown=true`
  ("hide the in-cell arrow"). Raw OOXML name also accepted.
T1 — table showHeader=false alias; sets HeaderRowCount=0.
T2 — table showBandedRows/showBandedColumns/showFirstColumn/showLastColumn
  now land on TableStyleInfo. Were hard-coded to defaults.
T6 — table style name validated against built-in whitelist +
  workbook customStyles; unknown names throw ArgumentException
  instead of silently producing an unstyled table.
2026-04-18 04:59:19 +08:00
zmworm
c6334d6907 fix(xlsx): validation list formula-ref pass-through, stopIfTrue helper, table style whitelist
V1 — `validation type=list formula1="=$Z$1:$Z$5"` (leading `=`) was
  being auto-wrapped in quotes, producing a literal-list validation
  instead of a cell-range reference. Now strip the `=` and pass through
  unquoted for both cell refs and named ranges.
T6 — table `style=BogusStyle` silently accepted, leading to Excel
  ignoring the style at load. Now validate against the built-in
  TableStyleLight/Medium/Dark1-28 + PivotStyle* names, plus any
  workbook-level customStyles. Unknown styles throw ArgumentException.
CF2 — centralized `stopIfTrue=true` handling via ApplyStopIfTrue
  helper (wired into every CF Add branch in a follow-up commit).
2026-04-18 04:58:52 +08:00
zmworm
86491ecb47 fix(xlsx): chart trendline Set now appends instead of replacing
CL20 — Setting series trendline twice with different types should yield
two trendlines (Excel allows multiple per series). Previously each
Set cleared all trendlines. Now Set appends a new trendline; if a
trendline of the same type exists it is replaced in place
(idempotent). Pass `trendline=none` to clear.
2026-04-18 04:58:31 +08:00
zmworm
a66025303c chore(xlsx): remove unused Packaging using in ChartHelper.Builder 2026-04-18 04:48:48 +08:00
zmworm
f55f5aaa84 fix(xlsx): round-14 audit P0/P1 batch — validation/namedrange/picture/shape/table/cf/sparkline/comment
Addresses 14 Excel-corruption and silent-drop bugs from the round-14
property audit. Each fix is self-contained; grouping keeps the helper
method additions next to their callers so a bisect lands on the full
change.

V2/V3/V4 (validation formula normalization):
- type=time formula1=HH:MM now converts to the time serial fraction
  (0.375 for 09:00) instead of writing a colon-containing string that
  Excel rejects with 0x800A03EC.
- type=date formula1=YYYY-MM-DD now converts to the date serial.
- type=custom formula1='=ISNUMBER(A1)' strips the leading '=' (OOXML
  <x:formula1> expects the body without one).

N2/N3 (named range):
- refersTo= is now mapped as an alias for ref= (previously wrote empty
  definedName content and corrupted the file).
- name= is validated against the OOXML §18.2.5 identifier rule:
  starts with a letter/underscore, no spaces, must not parse as a cell
  reference. Rejected at Add.

P2/O3 (picture/OLE raw-integer sizing):
- ParseAnchorDimensionEmu now treats bare integers that exceed the
  sheet's column/row max (16384/1048576) as EMU instead of multiplying
  them by the approximate cell size, which produced out-of-range
  ToMarker coordinates that Excel refused to open.

P4/P5/SH6 (picture and shape rotation/flip):
- rotation=<deg> and flip=h|v|both are now applied to the Transform2D
  on both Add paths. Accepts ±angles with wraparound and stores as
  OOXML 60000ths-of-a-degree.

CL1 (chart legend): (already committed separately)

SP1 (sparkline winloss): winloss and win-loss accepted as aliases for
stacked, which is the OOXML enum for the Win/Loss sparkline style.

T3 (table totalsRowFunction tokens): per-column function tokens
('none,sum,average,count,max,min,stdDev,var,countNums,custom') now
route to the right TotalsRowFunctionValues enum AND the matching
SUBTOTAL function code. Previously only 'sum' was honored; the rest
silently fell through to sum.

CE18 (array formula literal braces): formula='{=SUM(...)}' now throws
at Add with a hint to use arrayformula=... instead. Previously produced
a file Excel rejected.

CF1 (topn rank):  is now accepted as an alias for , so
 writes rank='3' instead of the hardcoded default 10.

CF3 (CF type fall-through): wired belowAverage, containsBlanks,
notContainsBlanks, containsErrors, notContainsErrors, contains,
notContains, beginsWith, endsWith to their proper
ConditionalFormatValues enums. Previously all 9 silently became
type='dataBar'.

CF4 (CF timePeriod): dateoccurring accepts both  (docs) and
 (OOXML attribute spelling). Previously only
was read, so invoking with  defaulted to 'today'.

C1 (comment \n): comment text with literal '\n' sequence is now
converted to LF before serialization, matching the shape text
behavior.
2026-04-18 04:45:46 +08:00
zmworm
60e1455170 fix(xlsx): write internal hyperlinks (#Sheet!A1) as location attr
Round-14 audit bug H1: hyperlinks to any '#'-prefixed target (sheet cell
reference or named range) were serialized as TargetMode=External
relationships pointing at a URI starting with '#'. Real Excel rendered
them as broken external links. Internal targets now use the inline
<x:hyperlink location='...'/> form with no relationship, matching Excel's
canonical output.
2026-04-18 04:45:03 +08:00
zmworm
ee0285833f fix(xlsx): map chart legend=topRight to OOXML 'tr' position
Round-14 audit bug CL1: legend=topRight and aliases (tr, top-right) fell
through to the default 'bottom' switch case. Now mapped to
C.LegendPositionValues.TopRight so the legend renders in the requested
corner.
2026-04-18 04:44:54 +08:00
zmworm
56859075f5 fix(xlsx): normalize validation formula1/2 for time/date/custom
Data-validation formulas now accept user-friendly inputs: HH:MM[:SS] for
type=time converts to the Excel time-serial fraction, YYYY-MM-DD for
type=date converts to the Excel date serial, and a leading '=' on
type=custom is stripped (OOXML <x:formula1> expects the equals-less
form). Previously these caused Excel to reject the file with 0x800A03EC.
2026-04-18 04:36:20 +08:00
zmworm
ac872a68c3 fix(xlsx): append table totals row below data instead of overwriting last row
add table ref=A1:C4 totalRow=true used endRow (4) as the totals row,
overwriting the last data row with Total/SUM labels. Now when
totalRow=true is set, the ref is expanded by one row (A1:C5) and the
totals row is appended at row 5, so all data rows survive.
2026-04-18 02:23:22 +08:00
zmworm
04ea00d5b4 fix(xlsx): clear stale CellFormula when Add writes a literal value
Previously, Add(cell, value=X) left any prior CellFormula on the same
cell intact, so the formula kept re-evaluating in html preview and on
open in Excel, overriding the literal the caller just set. The inverse
direction (formula after literal) already nulled CellValue; this
completes the symmetry so the last write wins.
2026-04-18 02:21:32 +08:00
zmworm
8e77e7e6cb fix(xlsx): render double underline as text-decoration-style:double in html preview
Cells with font.underline=double wrote correct OOXML (Excel shows two
lines) but the html preview only emitted text-decoration:underline, so
browsers drew a single line. Now also emits text-decoration-style:double
for UnderlineValues.Double / DoubleAccounting.
2026-04-18 02:17:08 +08:00
zmworm
e7bbc767f3 fix(xlsx): honor shape preset= to set prstGeom prst value
Excel add shape ignored the preset= property and always wrote
prstGeom prst="rect", so preset=roundRect / ellipse / triangle / etc.
rendered as plain rectangles in Excel. Now parses preset via a token
table mirroring PowerPointHandler.ParsePresetShape so PPT and XLSX
accept the same preset vocabulary.
2026-04-18 02:15:17 +08:00
zmworm
cbe6b6f97b chore: bump version to 1.0.51 2026-04-18 01:40:37 +08:00
zmworm
b40c3b5273 fix(watch): main slide white-screen on incremental updates + cheap IsWatching probe
Two related watch bugs:

1. PatchSlideInHtml matched `data-slide="N"` which also hits the sidebar's
   `<div class="thumb" data-slide="N">`. IndexOf found the thumb first, so
   every "replace" patch rewrote a sidebar thumb and left the main
   slide-container stale — user saw a white main view after each add/set.
   Pin marker to `class="slide-container" data-slide="N"`.

2. IsWatching / GetExistingWatchPort used a pipe ping with Connect(100),
   costing ~100ms per no-op and producing false negatives when the pipe
   server was momentarily busy. Replace with a {pid,port} marker file
   written on RunAsync, deleted in DoStopAsync, validated via
   Process.GetProcessById. Probe cost drops from ~100ms to ~10µs, and the
   pipe "ping" handler is removed (no remaining callers).
2026-04-18 01:29:06 +08:00
zmworm
c6ec72d2f4 fix(xlsx): unify table/column sanitization so Excel opens the file
Three paths caused Excel to refuse opening officecli-generated tables:
1. Table name/displayName parsing as a cell reference (e.g. 'tbl1' →
   column TBL=13584 row 1). Auto-suffix '_' rather than throwing —
   officecli-derived defaults shouldn't surprise AI callers.
2. tableColumn name mirroring a numeric header cell ('30'). Excel only
   accepts numeric column names when the header cell is typed as string,
   so during add convert numeric header cells to inlineStr; tableColumn
   name then matches the cell's visible value exactly.
3. Duplicate tableColumn names — auto-dedupe with numeric suffix.

Previous commit threw on (1) but silently let (2) through; replace with a
single SanitizeTableIdentifier helper used consistently for name,
displayName, and column names, plus the header-cell type fix.
2026-04-18 01:10:53 +08:00
zmworm
f60beed4a2 chore(resident): tone down auto-start hint to a single inline note
Drop the standalone stderr hint on auto-started resident commands and
soften the `create` suffix. UX testing showed the verbose "background
process held — call officecli close when done" wording fired on the
wrong command (random mid-batch get) and created low-grade anxiety
without giving the caller a concrete trigger. Auto-close in 60s
already covers cleanup; other officecli commands work normally through
the resident regardless.
2026-04-18 01:07:51 +08:00
zmworm
6220f18adf fix(xlsx): reject table names that parse as cell reference
Excel refuses to open files whose table name or displayName looks like
a cell reference (e.g. 'tbl1' → column TBL=13584 row 1 within XFD1048576).
Validate on add and throw ArgumentException with guidance so callers pick
a safe name (Table1, tbl_1, MyTable) instead of producing a corrupt file.
2026-04-18 00:52:14 +08:00
zmworm
5be1509972 fix(excel-html): non-collapsing gridlines so missing cells don't erase neighbour borders
Default td gridline was `border: 1px solid #e0e0e0`, which competes with
neighbours' explicit inline black borders through border-collapse. With
equal 1px solid on both sides, CSS resolves the tie by position (top-left
cell wins), so a style-0 or omitted cell anywhere in a bordered table
erases its bottom-right neighbours' black borders — visible gaps in the
grid (e.g. test-samples/报价单(1).xlsx G10, which is omitted from the
row XML entirely).

Paint gridlines with `box-shadow: inset` instead. box-shadow is outside
the border-collapse mechanism, so explicit cell borders always render at
boundaries regardless of whether the neighbour is styled. First-row top
and first-column left edges get extra insets via :first-of-type rules.

Covers all related edge cases in one pass: omitted <c> cells, rows with
StyleIndex=0 only, entirely omitted <row> elements, and column-default
`<col style>` cells — none of them can now suppress neighbours' borders.
2026-04-18 00:03:12 +08:00
zmworm
5e48f018ab fix(pptx-svg): CssSanitize font-family in foreignObject
SVG preview embeds HTML inside <foreignObject>, so the span's
font-family value ends up inside a live inline CSS string. HtmlEncode
encoded quotes only at the HTML attribute layer — the browser
unescapes them before the CSS parser sees them, letting a crafted
a:latin typeface like X';background:url(//evil)// inject arbitrary
CSS rules. Route the value through the same CssSanitize allowlist
that PowerPointHandler.HtmlPreview.Css uses.
2026-04-17 21:55:33 +08:00
zmworm
c4f808c8dd fix(preview): hex-gate Excel tab color, sanitize Excel default/Word theme font names
- Tester R11-1: <sheetPr><tabColor rgb=...> flowed into inline
  style="--tab-color:#{rgb}" unvalidated. A crafted sheet with a
  non-hex rgb escapes the style attribute. Hex-gate before emission.
- Tester R11-2: Excel styles.xml font[0] name was interpolated into
  the generated <style> block as font-family: '{defFontName}' with
  no sanitization, letting theme authors break out of the CSS rule.
  Route through CssSanitize like per-cell fonts already do.
- Tester R11-3: Word theme.xml supplemental CJK typeface flowed into
  the font-family chain unsanitized while the sibling docFont went
  through CssSanitize — an inconsistency a malicious theme could
  exploit. CssSanitize it before interpolation.
2026-04-17 21:08:42 +08:00