diff --git a/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Css.cs b/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Css.cs index 29d0dcd1..306f5637 100644 --- a/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Css.cs +++ b/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Css.cs @@ -1165,6 +1165,25 @@ public partial class WordHandler parts.Add($"width:{w / 50.0:0.#}%"); } + // Cell text direction (tcDir): rotate text 90° or 270° via CSS writing-mode + transform + // Common values: btLr (bottom→top, left→right = 90° CCW), tbRl (top→bottom, right→left = 90° CW) + var tcDir = tcPr.GetFirstChild()?.Val?.InnerText; + if (tcDir != null) + { + var wm = tcDir switch + { + "btLr" => "vertical-rl;transform:rotate(180deg)", // read bottom-up + "tbRl" => "vertical-rl", // read top-down + "lrTb" or null => null, // default horizontal + _ => null, + }; + if (wm != null) parts.Add($"writing-mode:{wm}"); + } + + // Cell noWrap — prevents content wrapping within the cell + if (tcPr.NoWrap != null) + parts.Add("white-space:nowrap"); + // Padding — add vertical compensation for CSS line-height:1 clipping glyph ascenders const double CellPadVComp = 3.0; // pt var margins = tcPr?.TableCellMargin; diff --git a/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Text.cs b/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Text.cs index 99172f95..e80aac60 100644 --- a/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Text.cs +++ b/src/officecli/Handlers/Word/WordHandler.HtmlPreview.Text.cs @@ -93,13 +93,26 @@ public partial class WordHandler } else if (child.LocalName is "ins" or "moveTo") { - // Tracked insertions — render their child runs + // Tracked insertions — underline to match Word's default revision mark style + var author = child.GetAttributes().FirstOrDefault(a => a.LocalName == "author").Value; + var authorAttr = string.IsNullOrEmpty(author) ? "" : $" title=\"Inserted by {HtmlEncodeAttr(author)}\""; + sb.Append($""); foreach (var insRun in child.Elements()) RenderRunHtml(sb, insRun, para); + sb.Append(""); } else if (child.LocalName is "del" or "moveFrom") { - // Tracked deletions — skip (deleted content should not be displayed) + // Tracked deletions — strikethrough with color, preserving the deleted text + // The delText inside del runs carries the actual deleted content; we render it so + // a reader of the preview can see what was removed. + var author = child.GetAttributes().FirstOrDefault(a => a.LocalName == "author").Value; + var authorAttr = string.IsNullOrEmpty(author) ? "" : $" title=\"Deleted by {HtmlEncodeAttr(author)}\""; + var delText = string.Concat(child.Descendants() + .Where(e => e.LocalName == "delText" || e.LocalName == "t") + .Select(e => e.InnerText)); + if (!string.IsNullOrEmpty(delText)) + sb.Append($"{HtmlEncode(delText)}"); } else if (child is Hyperlink hyperlink) {