Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/officecli/CommandBuilder.View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ private static Command BuildViewCommand(Option<bool> jsonOption)
var browserOpt = new Option<bool>("--browser") { Description = "Open output in browser (html / svg modes)" };
var outOpt = new Option<string?>("--out", "-o") { Description = "Output file path (html, screenshot, pdf modes; defaults to stdout for html, a temp file for screenshot)" };
var clipOpt = new Option<string?>("--range") { Description = "Restrict output to a region. Screenshot mode: an xlsx cell range ('Sheet1!A1:C3' or '/Sheet1/A1:C3') or any element data-path ('/slide[1]/shape[@id=N]', '/body/table[1]'); the PNG is cropped to the target's bounding box. Text mode (xlsx only): a cell range or single cell — emits just those rows/cells, saving context on large sheets. Not the character-offset `range=` prop of `set` (that one formats a text span like 3:7)." };
var rangeModeOpt = new Option<string>("--range-mode") { Description = "Screenshot range behavior: element (tight crop, default) or pages (complete containing page(s))", DefaultValueFactory = _ => "element" };
var screenshotWidthOpt = new Option<int>("--screenshot-width") { Description = "Screenshot viewport width (default 1600)", DefaultValueFactory = _ => 1600 };
var screenshotHeightOpt = new Option<int>("--screenshot-height") { Description = "Screenshot viewport height (default 1200)", DefaultValueFactory = _ => 1200 };
var gridOpt = new Option<string?>("--grid")
Expand All @@ -47,6 +48,7 @@ private static Command BuildViewCommand(Option<bool> jsonOption)
viewCommand.Add(browserOpt);
viewCommand.Add(outOpt);
viewCommand.Add(clipOpt);
viewCommand.Add(rangeModeOpt);
viewCommand.Add(screenshotWidthOpt);
viewCommand.Add(screenshotHeightOpt);
viewCommand.Add(gridOpt);
Expand All @@ -68,6 +70,13 @@ private static Command BuildViewCommand(Option<bool> jsonOption)
var browser = result.GetValue(browserOpt);
var outArg = result.GetValue(outOpt);
var clipArg = result.GetValue(clipOpt);
var rangeMode = (result.GetValue(rangeModeOpt) ?? "element").ToLowerInvariant();
if (rangeMode is not ("element" or "pages"))
throw new CliException($"Invalid --range-mode value: {rangeMode}. Valid: element, pages")
{ Code = "invalid_value", ValidValues = ["element", "pages"] };
if (rangeMode == "pages" && string.IsNullOrEmpty(clipArg))
throw new CliException("--range-mode pages requires --range <element-path>.")
{ Code = "missing_argument" };
var screenshotWidth = result.GetValue(screenshotWidthOpt);
var screenshotHeight = result.GetValue(screenshotHeightOpt);
// --grid has three states: absent → off (0), present with no value
Expand Down Expand Up @@ -128,6 +137,7 @@ private static Command BuildViewCommand(Option<bool> jsonOption)
if (browser) req.Args["browser"] = "true";
if (outArg != null) req.Args["out"] = outArg;
if (clipArg != null) req.Args["range"] = clipArg;
if (rangeMode != "element") req.Args["range-mode"] = rangeMode;
req.Args["screenshot-width"] = screenshotWidth.ToString();
req.Args["screenshot-height"] = screenshotHeight.ToString();
if (gridCols != 0) req.Args["grid"] = gridCols.ToString(); // -1 = auto
Expand Down Expand Up @@ -455,7 +465,8 @@ private static Command BuildViewCommand(Option<bool> jsonOption)
File.WriteAllText(tmpHtml, html!);
var r = clipArg != null
? OfficeCli.Core.HtmlScreenshot.CaptureClipped(tmpHtml, pngPath,
OfficeCli.Core.HtmlScreenshot.ResolveClipDataPaths(clipArg))
OfficeCli.Core.HtmlScreenshot.ResolveClipDataPaths(clipArg),
containingPages: rangeMode == "pages")
: OfficeCli.Core.HtmlScreenshot.Capture(tmpHtml, pngPath, screenshotWidth, screenshotHeight);
try { File.Delete(tmpHtml); } catch { /* ignore */ }
if (!r.Ok && r.Error == "clip_target_not_found")
Expand Down
11 changes: 8 additions & 3 deletions src/officecli/Core/HtmlScreenshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public static Result Capture(string htmlPath, string outPath, int width = 1600,
/// a rendered element.
/// </summary>
public static Result CaptureClipped(string htmlPath, string outPath, IReadOnlyList<string> dataPaths,
int padPx = 0, int scale = 2)
int padPx = 0, int scale = 2, bool containingPages = false)
{
if (FindChrome() == null)
return new Result(false, "", "clip mode requires a Chrome-family browser (Chrome/Edge/Chromium)");
Expand All @@ -203,8 +203,13 @@ public static Result CaptureClipped(string htmlPath, string outPath, IReadOnlyLi
"if(best)return best;if(cands.length===0)return null;" +
"var el=cands[0];for(var an=el;an&&an!==document.documentElement;an=an.parentElement){" +
"if(getComputedStyle(an).display==='none')an.style.display='block';}return el;}" +
"function _clipEls(){var els=[];_clipPaths.forEach(function(p){" +
"var el=_clipPick(p);if(el)els.push(el);});return els;}";
(containingPages
? "function _clipEls(){var els=[],seen=new Set();_clipPaths.forEach(function(p){" +
"var cands=document.querySelectorAll('[data-path=\"'+p+'\"]');cands.forEach(function(el){" +
"var page=el.closest('.page,.slide,.sheet-content')||el;" +
"if(!seen.has(page)){seen.add(page);els.push(page);}});});return els;}"
: "function _clipEls(){var els=[];_clipPaths.forEach(function(p){" +
"var el=_clipPick(p);if(el)els.push(el);});return els;}");

// The rect is reported via console.log -> --enable-logging=stderr in
// the SAME chrome invocation that captures the full page: headless
Expand Down
8 changes: 7 additions & 1 deletion src/officecli/ResidentServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,11 @@ private void ExecuteView(ResidentRequest req, OutputFormat format)
// --range clips a data-path region out of the HTML preview — mirrors
// CommandBuilder.View.cs: native/direct-PNG backends are bypassed.
var rangeArg = req.GetArgOrNull("range");
var rangeMode = (req.GetArgOrNull("range-mode") ?? "element").ToLowerInvariant();
if (rangeMode is not ("element" or "pages"))
throw new InvalidOperationException($"Invalid range-mode value: {rangeMode}. Valid: element, pages");
if (rangeMode == "pages" && string.IsNullOrEmpty(rangeArg))
throw new InvalidOperationException("range-mode=pages requires a range element path.");
if (rangeArg != null) renderMode = "html";
var sw = req.GetIntArg("screenshot-width") ?? 1600;
var sh = req.GetIntArg("screenshot-height") ?? 1200;
Expand Down Expand Up @@ -1739,7 +1744,8 @@ private void ExecuteView(ResidentRequest req, OutputFormat format)
File.WriteAllText(tmpHtml, html!);
var rs = rangeArg != null
? OfficeCli.Core.HtmlScreenshot.CaptureClipped(tmpHtml, pngPath,
OfficeCli.Core.HtmlScreenshot.ResolveClipDataPaths(rangeArg))
OfficeCli.Core.HtmlScreenshot.ResolveClipDataPaths(rangeArg),
containingPages: rangeMode == "pages")
: OfficeCli.Core.HtmlScreenshot.Capture(tmpHtml, pngPath, sw, sh);
try { File.Delete(tmpHtml); } catch { /* ignore */ }
if (!rs.Ok)
Expand Down