Repository navigation
Turn a folder into one printable PDF — and let users add their own actions #966
Description
One line: Filee is one option away from "folder → one PDF I can actually print", and beyond that I would like a supported way for users to add their own actions.
What Filee already does — 2.2.0, verified
filee convert D:\project --recursive --preset merge-pdf -o out
This walks the tree, routes every leaf file through RoutePlanner, and merges everything into one PDF. On my test folder (7 mixed files — XLSX/DOCX/TXT/CSV/PNG, three levels deep) it produced a single 9-page PDF with zero failures.
So the thing I set out to ask for mostly exists already. What is left is small, specific and measurable:
What stands between that and something I can print
| # | What I measured | Effect on paper |
|---|---|---|
| 1 | Spreadsheet sources are paginated by raw column width: a 14-column sheet came out as 2 pages, the second holding a 35 pt strip of two leftover rows | 9 pages instead of 7; orphan rows |
| 2 | No fit-to-width and no centring: content landed at 46 pt left / 24 pt right, 49 pt top / 41 pt bottom | off-centre, looks wrong on the page |
| 3 | Page setup stored inside the workbook is not honoured | the scaling and area the author saved are ignored |
| 4 | Paper size follows each source: 842×596 (sheet), 612×792 (DOCX, Letter), 1200×675 for a PNG page | one print job, three paper sizes |
| 5 | In my test the merge order was breadth-first (root files → depth 1 → depth 2 …), and there are no bookmarks | people expect Explorer order; no way to jump per source file |
Proposal A — page setup for spreadsheet sources (the small one)
New options on the existing convert/merge path, not a new action:
filee convert D:\project --recursive --preset merge-pdf \
--fit-width --center-both --paper A4 --orientation auto --bookmarks
| Option | Meaning |
|---|---|
--fit-width |
scale so all columns fit one page width (Excel's fitToWidth = 1) |
--fit-page |
whole sheet on one page (fitToWidth = fitToHeight = 1) |
--center-both |
CenterHorizontally + CenterVertically |
--paper A4/A3 |
one paper size for the whole document |
--orientation auto |
pick per sheet by comparing real page counts |
--bookmarks |
one outline entry per source file, once page offsets are known |
Three details from building a workaround, in case they save you time:
- Honour the print settings already stored in the workbook when the user has not overridden them. Most such files ship with their own area and scaling; respecting it is both correct and cheaper than guessing.
- Include floating objects when deriving a print area. Charts and logos usually sit outside the used cell range (
Shape.TopLeftCell/Shape.BottomRightCell) and silently disappear from the PDF if excluded. - Keep one paper size per document — today a PNG source produces a 1200×675 page, which cannot be printed with the A4 pages around it.
On my test folder this turns 9 pages into 7, centred, on one paper size. You already have the machinery: N→1 jobs (IFileCombiner, IMediaJoiner, IImageJoiner, PDF merge via PdfSharp), IPdfFinisher running per result and over the merged file, and tool-sheet-studio / SpreadsheetPrintService already working with print regions. I read this as extending that line rather than starting a new subsystem — though I will not guess the effort from outside.
Proposal B — user-defined actions (the extensibility ask)
This is the part I care about most long term, because it generalises past my own case. Presets already persist in UserDataStore.presets.json and are exposed through ToolbarProfile / ProfileSelector / PresetAvailability, so the missing piece is mostly an execution path:
{
"id": "folder-print-pdf", "name": "Folder → one PDF",
"inputs": "all", // single | each | all (1→N vs N→1)
"accepts": ["folder", ".xlsx", ".pdf", ".docx"],
"target": "pdf",
"command": "python \"C:\\tools\\x.py\" {inputs} -o {outputDir}",
"surfaces": ["donut", "contextMenu", "watchFolder", "cli"]
}Contract: {inputs}, {outputDir}, {target}, exit code, stdout/stderr surfaced in toast and history. Nothing is downloaded, nothing runs that the user did not write themselves, the command is visible in the preset editor, each action can be disabled, and it runs out-of-process so a badly behaved tool cannot take Filee down.
That one mechanism covers the workflows people keep rebuilding around Filee: OCR post-passes, batch rename/normalise, sync to a local NAS, template printing — and, yes, the folder→PDF case above. Most requests like mine would then arrive as a preset someone shares, instead of an issue asking you to ship somebody's niche step.
Proposal C — plugin API (longer term)
If extensibility is something you want to own properly: a plugin registers converter edges into ConverterCatalog under an EngineRegistry entry (inheriting Settings → Engines priority and JobQueue's per-engine concurrency), implements IConverter (1→1) or a combiner seam for N→1, optionally IPdfFinisher for post-processing, and surfaces via Preset + ToolbarProfile + PresetAvailability. In-process assemblies vs out-of-process tools speaking a documented protocol is your call; either works from where I stand, as long as the boundary is documented.
Why an extension point is worth it
The people who chose Filee for offline conversion eventually grow a local workflow around it; today the only ways to extend it are forking or wrapping, and both duplicate plumbing Filee already has. One supported seam turns those into presets, keeps Filee focused on conversion quality, and reduces feature issues rather than adding to them.
What I am offering
- Implement A (page-setup options + tests) or B (external-command presets) as a focused PR —
dotnet format, tests intests/Filee.Core.Tests/tests/Filee.Engines.Tests, i18n keys in everysrc/Filee.App/Assets/i18n/*.json, followingCONTRIBUTING.md. - Test material: an anonymised project tree (200+ spreadsheets, three levels deep), plus the 9-page vs 7-page comparison above.
I would rather agree on the shape than hand you a surprise PR: tell me which tier you would accept (A, B, or a design you already have in mind) and I will build to that.
Maintainer note: my current workaround does the layout step through Microsoft Excel via COM. Since CONTRIBUTING.md states Filee deliberately does not use Microsoft Office, Proposal A is written as "Filee's own renderer respecting (or overriding) the print settings stored in the workbook" — not as "call Excel".
Activity
KnifeLemon commentedon Nov 1, 2026
Thanks for the careful write-up and the measurements, @pokiimon. And thanks for checking
CONTRIBUTING.mdfirst: Proposal A written as "Filee's own renderer" is exactly the right framing.Proposal A: mostly done already, not released yet
Filee now has its own spreadsheet print engine (#951, in the
builtin-enginesbranch for the next big release). XLSX, XLS and ODS go to PDF with Excel's page logic:So rows 1–3 of your table (the orphan strip, no fit or centring, the workbook's page setup ignored) are what that engine fixes. Charts and shapes aren't printed yet. Your note about floating objects outside the used range is useful for that, thanks.
The two points left from your table are tracked separately:
--recursivemerges is a fair expectation. We'll look at it with the merge work.So there's no need for a PR for A.
Proposal B: yes, let's do it
A preset that runs the user's own program is the first step we promised in #465, and you're welcome to build it. Here's the shape we'd accept. Please say if anything doesn't fit your case before you start.
The preset
{input},{inputs}(all mode),{name},{ext},{dir},{output}(the path Filee picked under the usual output rules),{outputDir}.How it runs
ProcessStartInfo.ArgumentList, one entry per argument, so a file name can never inject a command. Use the existingProcessRunner.cmd /corsh -cin the program field themselves.{output}if the program wrote it, or else new files in{outputDir}.Safety
First version: leave out
Housekeeping
Filee.Core.TestsandFilee.App.Tests.mainright before you push).main.This also covers #962 (DOCX → PDF through an installed WPS): with Run a program, that worker can be a preset instead of a built-in engine.
I've assigned this to you. Discussion of B continues here; #465 stays as the place for the wider plugin idea.