Repository navigation
Sync main into builtin-engines (2026-11-15) - #1529
Open
KnifeLemon wants to merge 112 commits into
Open
KnifeLemon wants to merge 112 commits into
KnifeLemon wants to merge 112 commits into
Conversation
…d e-mail to PDF Part of #1403 (Windows; Linux and macOS follow in a separate change). - Optional download pinned in engines.json: WeasyPrint 70.0's Windows "onedir" build (weasyprint-windows-onedir.zip, 32,271,073 bytes), offered in Settings > Engines, never taken from the PATH, not ticked by default. - One HTML -> PDF edge, used only in routes that start from HTML, Markdown, EML, MSG or MHTML (new ConversionEdge.Sources). It wins wherever WeasyPrint is installed; without it no route changes. - Locked down: pictures beside the document become data: URIs, every other reference is removed (LinkedContent), and WeasyPrint runs with --allowed-protocols data. - Stall watch on --verbose output (5 min of silence), 30 min overall. - Tests for routes, the prepared page and, with WeasyPrint, the PDFs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5sY3QRo9xaLZemsLr8
The HTML, Markdown, MHTML and LibreOffice route tests check what the built-in engines do; with WeasyPrint installed its routes are tested in WeasyPrintTests instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5sY3QRo9xaLZemsLr8
- Keep same-document references (#id) in the HTML copy, for WeasyPrint and LibreOffice: an inline SVG's <use href="#s">, a gradient's xlink:href="#base", fill: url(#g). Tests for both, and one that WeasyPrint draws the used symbol. - Off Windows, a WeasyPrint that isn't found reports engine.reason.unsupported_platform, like Whisper (Filee's own copy is still looked up first). - Correct the comment on PYTHONIOENCODING / PYTHONUNBUFFERED: the onedir build ignores them; progress comes from stderr, which stays line-buffered. - Docs: WeasyPrint in the Windows runtime table (ENGINES.md) and the optional engines (ARCHITECTURE.md). - Korean strings in 해요체 like the rest of ko.json. - CI: fetch WeasyPrint for the Windows engine tests, so the end-to-end and lock tests run against the pinned build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5sY3QRo9xaLZemsLr8
…n Cut a clip (#1291) Cut a clip now opens on sound files: the timeline is the waveform, a clip keeps the sound's format (MP3 where FFmpeg can't write it) and has the usual pieces, volume, speed, fades and music. The iPhone ringtone preset can ask for its part every time (MediaOptions.AskRingtonePart, on in the built-in preset): dropping sounds on it opens the same window with only the part to choose (up to 40 s), and Save starts the normal M4R conversion of that part. Also: the close and Delete buttons of the window get the red danger hover. Tests, docs and the preset editor checkbox included.
…ll 22 languages New keys: clip.ringtone_limit and presets.ask_ringtone. Non-English text is machine-translated and not yet reviewed by native speakers.
…and PSD
Filee reads XCF itself instead of through ImageMagick, which reads only
8-bit files without zlib and merges layers its own way: every version GIMP
writes (64-bit pointers from XCF 11), RLE, zlib and uncompressed tiles,
8/16/32-bit integer, half, float and double precision, RGB, grayscale and
indexed, layer groups and layer masks. Layers are merged as GIMP 2.10 and 3
draw them (XcfBlend): every layer mode with its blend and composite spaces
and composite mode, legacy modes, pass-through groups and GIMP's own
shortcuts (a layer over nothing drawn as it is, pass-through groups reduced
to ordinary ones).
XCF -> PSD/PSB and XCF -> PDN keep the layers: names, opacity, visibility,
place on the canvas and shared blend modes. A group becomes its layers
("Group / Layer") where that keeps the picture, else one merged layer.
XcfWriter writes 8-bit RLE XCF at the lowest version holding the layers,
with 32-bit pointers, which GIMP 2.10 and 3 open: any picture as one layer,
PDN and PSD/PSB with their layers in GIMP's legacy (perceptual) modes.
"Merge images" keeps an XCF as XCF. XCF becomes a specialist target in the
Design & print field. The PSD layer writing and reading of the PDN work is
shared (PaintNet.WritePsd, PaintNetWriter.ReadPsdLayers).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5sKTbvUBdxgCJJnzxZ
With WeasyPrint installed (the Windows CI job now fetches it), Markdown goes to PDF through it. The block quote test draws the built-in route's quote, so it converts with a catalog without WeasyPrint; the Pandoc markup test expects Markdown -> HTML -> WeasyPrint when it is installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5sY3QRo9xaLZemsLr8
… setting Every pixel a Size, Hole, Opacity or group ring slider moved saved the settings with SettingsChanged, which applies the theme, the triggers and the system integration again in every window: the sliders stuck. The donut's look now saves quietly (UserDataStore.SaveDonutLook, as the window zoom already does); the donut reads it each time it opens, and nothing that listens to SettingsChanged uses it. The Theme page's donut opacity slider had the same problem and gets the same fix. Tests: DonutLookSaveTests (Core), DonutEditorTests.The_look_sliders_save_quietly_on_this_page_and_on_the_theme_page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ke folders - The editor's donut is laid out at its real size and drawn smaller only when its column is narrower, so a large donut no longer runs under the profile list and Available presets, and names wrap and icons show as on the donut itself. - A click on a group slice (not its ✎) opens the group: the donut shows the presets it fans out, its name and how many it holds in the middle, with a way back to the whole donut in place of the Front / Back switch. The same drags add presets from Available presets, reorder them and take them out; groups aren't offered (a group can't hold one) and six is full. The face's rules hold in the group: no Copy to folder in a group on the front face. - The group closes with Back, another donut, the Front / Back switch, or once it is deleted or leaves the face. - The palette, its search and its judging share one definition of what is placed, which an open group changes: if they disagreed, judging would wait for presets the palette never shows. Tests: DonutEditorTests (page donut at 1080 and 1600 px, a group opened by a click, closing and the face's rules). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Edit Donut Size, a button under the editor's donut, swaps the editor for the donut's size and look settings, each with its value (Size in pixels, the others in percent), Show output format, and this donut's Slice labels and Label scale. They used to be the Appearance card at the bottom of the page and in the profile card; nothing is removed. Done, Esc on the donut, or another page ends it. - Meanwhile, and while a group is open in the editor, the real donut is on screen beside the window at its real size, over the desktop where it is used, filled as it opens for the donut's files (turned-off fields left out). It follows every slider and edit, and can be tried: a group opens under the pointer, Tab or the wheel flips it. Nothing converts from it. An open group is held open on it (DonutMenu.HeldGroupId), changing as the group changes. - It never opens over the window: beside it, else on another screen; with no room, the window slides over to make room, and back on Done. Dragging its middle moves it, and it opens there next time (AppSettings.DonutPreviewPlace, optional, so no schema step). It isn't Topmost: a click on the window brings that in front of it. Tests: DonutEditorTests (the panel, the donut on screen, the held group, placement), DonutLookSaveTests (the place); DonutCaptionTests opens the panel for its switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is adjusted - Changing Group ring size or spread holds this donut's first group open on the donut on screen (front face first, turning to its face), so the change shows without keeping the pointer on it. Until then nothing is held: the donut behaves as the real one. - Group shown (with two or more groups; a back face's say so) or a click on a group of the donut on screen picks another. Done lets go of it. - A donut with no group shows an example ring off its top slice, its centre saying so, so ring size and spread can still be judged. Tests: DonutEditorTests (a ring setting holds the group and the picker or a click chooses it; the example ring). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from review An independent review of #873 found, and this fixes: - Placement (now DonutPreviewPlacement, pure and tested on laptop, wide, scaled and two-screen layouts): on a laptop screen the donut could open over the window and then go behind it at the first click. The window now moves over, narrower down to its minimum if it must; beside it, the donut's edge facing the window stays put as a slider grows it, and it is placed again when it no longer fits or the window is resized or moved. With no room at all it goes over the column not in use, owned by the window so it stays above it. It appears where it belongs instead of jumping. - The window goes back where it was also on Done during its slide, at once when the page is left or the window hidden; closing it meanwhile remembers where the user had it (MainWindow.RestPlace), not where it slid to. - The sliders, and a dragged place, write settings.json once they rest (DonutLookSave, as the window zoom does), not for every pixel; the settings in memory change at once, and leaving the page or quitting writes what waits. - The donut on screen fills again only when what is on it changed (not on each key typed in the palette search), checks its back face after the front is shown, keeps an example ring meanwhile, and shows a mixed profile that takes anything with its slices ready rather than greyed. - A group also on a front face, of this donut or another, takes no Copy to folder from its back face either. - The keyboard goes to the button that replaced the one it was on (Edit Donut Size, Done, Back); Back reads "Back to Images" to screen readers. New strings in all 22 catalogs. - Tests: placement, the window's slide and return, growth, resize and move, focus, the debounced save, the refill, the mixed profile and the group rule, each checked to fail with its fix taken out. The editor tests also dispose the page they open, as the app does, so a closed toolbar page no longer re-judges the palette in every later test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from the second review - No room anywhere (maximized, or a screen too small for both): the donut goes over the window's start side (its navigation and profile list, the right side in Arabic), never over Done, and is placed again as it shrinks. - Minimized while it made room, the window still goes back on Done; a size the user gives it meanwhile is kept and remembered on close; narrowing is recorded before it is applied, so it isn't taken for the user's. - A donut on another screen isn't put under a window that reaches onto it. One dragged onto the window moves back off it once it rests there and isn't remembered there; only a drag by its middle counts as the user's. - Group shown judges groups as the donut on screen fills them: a donut's own field stays with it turned off (FORMAT-RULES rule 4, RadialViewModel.PreviewFormats / TurnedOffBesides shared). - The donut on screen fills again when the library changes (LibraryVersion, counted on every save, here or elsewhere), the language or the turned-off fields, rather than comparing every preset. - Tests: timers run through a dispatcher frame (RunJobs never brings them due), so the debounce and the drop check are tested as they run; new tests for each of the above and a chip dropped into every slot of the smaller donut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en it ends; findings from the third review - A new Copy to folder made while a group is open goes in through Insert, so its rules hold: not into a group that is also on a front face; it waits in the palette instead (AddFolderCopy). - The donut on screen's drag ends when the button is up, or once it rests (the page's drop check): a move after that (a DPI change, the window manager) isn't remembered as the user's. - Another page while the window is maximized or minimized: no old place is left behind for closing to remember or for Done to slide back to later. - Tests: the slide is slowed down where a test must catch it midway (ToolbarPage.SlideTime), and the page left during the slide is caught on its way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng stays on a flip; fourth review - On Windows the system's move loop runs inside BeginMoveDrag: the drag is over, and checked for landing on the window, when it returns, however long the user paused meanwhile. Elsewhere it returns at once, and the donut resting still ends it (DonutPreviewWindow.MoveLoopReturnsAtEnd). - The example ring of a donut with no group stays on each face the donut on screen flips to, with its title. - Comments of the placement say where the donut goes when nothing else is left, and when the window must not move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p; findings from the fifth review - The drag ends on its release, on the pointer seen on the donut with the button up (where a window manager's move keeps the release, as on macOS and X11), or when Windows' move loop returns; never because it paused. Dropped onto the window, it is put back beside it then, not while the system still moves it (no more rest timer). - On native Wayland the donut on screen isn't hidden to be given an owner (hiding destroys it there). - Comments: Fits has no place parameter; the caption tests name Edit Donut Size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oo; findings from the sixth review - Entered and left are direct events: their handlers now listen on that phase (they never ran on the tunnel one), so a drag whose release was lost ends when the pointer is seen leaving or entering the donut with the button up. - Comments and ARCHITECTURE say where the donut goes with no room at all and when a drag ends; positions are points on macOS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p; findings from the seventh review Avalonia's Windows BeginMoveDrag posts the system's move and returns at once (Win32 WindowImpl, 12.1.4); the move ends with a button-up sent to the window. Ending the drag when the call returned ended every drag at the press there: the place wasn't kept, and a drop onto the window wasn't checked. The drag now ends on the button-up on every platform; the test stands in for Avalonia's Windows move as its source shows it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd place stay the user's; eighth review - Avalonia raises its own enter and leave after a repaint, with no button state (PointerPointProperties.None): only those the pointer made end the donut on screen's drag, so moving it onto a screen of other scaling doesn't. - Windows reports a maximized window's new size before its new state: a resize is taken as the user's only once it has settled on a normal window, so maximizing and restoring while the window made room keeps its width for Done. - Closing after the user moved the window that made room remembers where they moved it, as Done leaves it there (MainWindow.Landed, PlaceToRemember; one IsNear for both). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ile move keeps it; ninth review - While its middle is held, nothing places the donut on screen again: resized meanwhile (onto a screen of other scaling, which Windows does mid-move, or by a slider) it stays under the pointer. Let go across two screens, it is placed again then. - Moving the selected profile in the list drops the list's selection for a moment: the page doesn't take that for the donut going (no slide back, no donut over the window). - A height the user gives the window while it made room is kept, as its width is. - RadialViewModel.Load uses TurnedOffBesides, as the preview does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e; a search keeps the window; tenth review - Mixed selection on or off judges the donut for other files: the editor and the donut on screen are filled again. - A donut filled for no file (the mixed profile that takes anything) has its name in the centre: renamed, the centre follows, without filling the donut again for every key typed. - A profile search that hides the profile being edited only puts the donut on screen away until one is picked: the window stays where it made room, and goes back on Done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… away is kept; eleventh review - Moved by the user while the donut on screen was put away (a profile search, the window hidden), and made to make room again: Done and closing go back to where the user moved it, not to where it was before the first move. - On Windows a leave never tells the mouse button (Avalonia builds it with the keyboard's modifiers only): it doesn't end the donut's drag there, where the system's move ends with a button-up anyway (LeaveTellsTheButton). - Comments: OpenGroupIndex, DonutMenu.OpenGroup (for tests), the Group shown note filled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w moved meanwhile keeps its width; twelfth review - Where the window landed after making room is the window's alone (MainWindow.Landed), cleared on its way back too: closed during the slide back, it opens where the user had it, not halfway. - Moved by the user while the donut was put away, while still narrowed: Done gives it its own width back. - The donut on screen is dragged by its primary pointer only (Windows' BeginMoveDrag refuses another one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… shown keeps its pick; thirteenth review - The donut on screen back while the window still slides back (Done, then Edit Donut Size again at once): the slide stops where the window is, taken as where it landed, and the donut is placed from there; Done still takes it back. - Group shown keeps the group picked, by id and face, when that group is renamed elsewhere: read before the picker's list is replaced, which drops its choice for a moment. - A comment on the narrowing moved above its line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng and on X11; fourteenth review - Widths the window was given and reports are compared to a device pixel: Windows truncates to whole pixels, so at 125 % the narrowed width can come back 0.8 short, which was taken for the user's. - Still where it moved over to is judged by where it landed or where it was sent (MainWindow.SlidTo, StillWhereItSlid), by Done, closing and a second move alike: an X11 window reports its new place only once the window manager has moved it. - A look saved with the library (Label scale) restyles the donut on screen without making the next palette search fill it again. - The maximize test lets the new size land before the new state, as Windows reports them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ifteenth review - Beside the window only where the screen is as tall as the donut too: a screen less tall (2560 × 1080 at 150 %, the donut at its largest) tries another screen first, and beside the window past its edges only with none. - A slide stopped midway is taken as landed where it last put the window, not where an X11 window last reported. - On native Wayland the window isn't moved or narrowed to make room: it can't place itself there. - Tests: Motion set, not configured; profile order and the example donut's groups put back however a test ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow goes back once settled; sixteenth review - The macOS / X11 drag test says so (LeaveTellsTheButton on): on Windows, where a leave tells no button, its leave step would have failed; its own Windows test keeps it off. - A window normal again after Done (maximized meanwhile) goes back once its normal size and place are in: some systems report the state first. - A slide stopped midway isn't taken for a move by the user when the window then makes room again (X11 reports the new place late). - Label scale put back however the search test ends; comments say the placement's order (beside only on a screen as tall as the donut) and where a new Copy to folder goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eventeenth review - The page left while the window, made narrower, is maximized or minimized: once it is a normal window again it gets its own width back, and closing meanwhile remembers that width (MainWindow.WidthOwed). - The window's frame is judged at the primary screen's scaling when no screen reports holding it, as placement does. - Comments: what Esc does on a ring held open, when the dragged place is cleared, and why the donut may go over the profile list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd Markdown Obsidian's visual boards (.canvas) follow the open JSON Canvas 1.0 spec. Filee reads them itself, with no new dependency: - canvas -> SVG in the look of Obsidian's light theme: groups with their labels and backgrounds, curved lines with sides, arrow heads and labels, then the cards in z-order. Text cards show their Markdown simply (Markdig), file cards show the note or picture beside the canvas, link cards show their address. PDF, PNG and the other images come from the vector engine. - canvas -> Markdown (cost 15): groups as sections, cards in reading order, pictures copied to _files, and the lines listed at the end. - Files are read only from the canvas's folder or below (DocumentFiles); "..", absolute, UNC and URL references are refused by their text, and nothing is fetched. - Defensive reading: size, card, line, coordinate, extent and text budgets are checked before layout. A .canvas without nodes/edges is refused (error.canvas_not_canvas), and so are an empty board and an over-budget one. Damaged files give an InvalidDataException. - Read only, Vector category, general grade. Settings schema 87: the vector donut learns .canvas (may need renumbering). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5omWAVqxhGiuyqNWAD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5omWAVqxhGiuyqNWAD
Install dmgbuild, ds_store and mac_alias from installer/macos/dmg/requirements.txt with exact versions, so release builds don't pick up new dependency releases. CONTRIBUTING.md explains installing them into a virtual environment, since Homebrew's Python refuses pip installs outside one (PEP 668).
feat(engines): take a download from Filee's mirror when its URL fails
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep extraction presets usable while showing an input-dependent notice in the donut centre and the Available presets tooltip. Cover the three agreed extraction presets, distinguish mixed inputs, and add the notice in every supported language. Test real controls, cached tooltip updates, and per-source extraction flags in a mixed queue. Closes #1091 Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Keep the click/drop guidance at its original size and two-line limit while a notice is shown, so a small centre no longer shrinks it. Only the notice gives way: it keeps at least 78% of its font size and fewer lines or an ellipsis, with a small gap above the guidance. Fit the text and the flip-hint icons inside the round centre, prefer a complete notice whenever one fits, and cache the chosen layout instead of recomputing it every frame. Without a notice the centre is drawn as before. Test the drawn layout with real controls in 22 languages at the default and the smallest centre size, with and without a flip hint. Shorten seven all-files translations so the default notices stay complete. Document where the notice appears and where its preset list lives in format rule 8. Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Measure the ellipsis width and keep complete-notice candidates untrimmed. Fit the secondary hint around tall script glyphs without changing the guidance, notice font floor or ink gap. Shorten the Arabic and Persian all-files notices and cover every default scope and hint combination with the bundled fonts. Let the small-centre checks accept an omitted notice while still checking the guidance.
Search the full circle for complete notices before shortening them, letting the secondary hint yield while preserving guidance glyphs. Return only placements that pass the ink bounds check. If the guidance itself cannot fit, drop the notice and retain the no-notice baseline. Cover Persian and Hindi notices with full guidance and wheel hints under tighter ink budgets, plus exhausted placement searches. Preserve the guidance glyphs, notice font floor, circle bounds and ink gap.
Shorten Arabic, Persian, French and Nepali notices while preserving their ordinary-conversion scope. Measure wide-notice regression inputs from font advances, retain width reserve, and prove the old search fails on Linux and macOS.
Main took schemas 90-93 (nov, Gerber, Canvas, G-code), so the .vtf step is now v94 and its test starts from 93. FormatFields keeps both gcode and vtf; the Specialist count goes to 127. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every engine that works is Ready, with a source line (Filee's download, a copy on this computer, or the user's own copy) giving its folder and version. With Filee's download installed, a copy on the computer is reported as EngineCopy.OtherFolder and can be used instead after a confirmation. The card is read again from disk after Remove and after an install. Filee's copy is offered over a copy on the computer only when that copy is older, guidance and download size show only while needed, and Update recommended is in the warning colour. Closes #867. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Install Filee's copy is offered over a copy whose version can't be read, once its check is done. - Tesseract's and ExifTool's versions are read (ExifTool is asked -ver; its --version prints the manual). - Use this one instead only for a copy that runs, started again before Filee's download goes; links to Filee's own download at any level aren't taken for another copy. - A failed version read isn't cached; a check that fails leaves the version unknown and the card checked. - Removing one download searches again for that engine only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every engine that works is the same green Ready, wherever its copy comes from; amber while its card recommends an update (Filee's download has a new version, or the copy in use is older than the tested one), with the card's dot in the same colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts to Edit Donut Size Follow-ups from the review of #1441 (#873). Buttons whose text sits in a panel with an icon or a count were read as "Avalonia.Controls.StackPanel": Edit Donut Size, Front and Back, New profile, New preset and New group are now named from their own visible text, in the current language, and each profile in the list by its name. The "Show output format" entry in CHANGELOG.md pointed to the Appearance card #873 removed; the switch is in Edit Donut Size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… unused accent tag (#867) Filee never runs a WeasyPrint found on the PATH (#1403), so the card no longer reports one: without Filee's download it stays missing with Install, and with it no "Use this one instead" removes the copy that works. rhwp and 7-Zip, bundled only, are left out of the search too. The accent status tag and its Info brushes lost their last user when every engine that works became the same Ready. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mongolian-localization
…n canvas) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…king PDF/A PDF/A-2 requires annotations other than popups to have the print flag, so Ghostscript left out links and markup without it, which many PDFs do not set. Before the PDF/A step, Ghostscript now reads a copy in the work folder where those annotations print. - Set only the Print bit, and only on annotations that are not hidden, invisible or no-view; leave popups, form fields and the rest as they are. - Change shared annotations once, without copying them, so links between pages, popups and replies stay connected. - Check the page tree before opening the copy for changes and fall back to the original when it loops or runs too deep, or the copy can't be made. - Keep PDFsharp out of the document info when the PDF names no creator. Part of #1496 Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
feat(image): read and write Valve textures (VTF) for Garry's Mod and …
feat(i18n): add Mongolian translation
fix(pdf): keep links and markup that only lack the print flag when making PDF/A
…a663bf Engines page: one Ready status that says where each engine comes from
…n-notice feat(toolbar): show when extraction presets use ordinary conversion
feat(pdf): fit every page of a merged PDF to one paper size
build(macos): pin the disk image tool's dependencies and set it up in a venv
Brings VTF, Gerber, JSON Canvas, G-code, .nov, KRC, NuGet/Android packages, XCF layers, WeasyPrint, Mongolian, PDF/A links, merged-PDF paper size, the Engines page Ready status and status tags, the LibreOffice archive URL and the engine mirror onto the built-in engines. - Settings schema: main's library steps keep 88-94; the built-in engines steps move to 95 (unhwp -> hwp-text), 96 (LibreOffice for HWP -> Filee renderer) and 97 (renderer on, LibreOffice for slides off), so users coming from main run them. CurrentSchemaVersion 97. - Archives: NUPKG, APKS and AAB are read in-process as ZIPs (7-Zip only as a fallback); their tests no longer need 7-Zip. - Engines: WeasyPrint, rhwp and 7-Zip are never taken from the PC; WeasyPrint joins 7-Zip among the downloads not suggested on first run. - i18n: key-wise merge of every locale; mn gets the built-in engines keys and main's newer keys in Mongolian, and loses the retired ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KnifeLemon
commented
Nov 14, 2026
KnifeLemon
left a comment
Owner
Author
There was a problem hiding this comment.
Independent review of the merge resolution at 1de42e7. I didn't review the features.
Nothing lost from either side
- The result differs from git's automatic merge of 0af8abe + 770e982 only in the 38 conflicted files plus 5 that had to follow:
docs/RENDER-QUALITY.md,mn.json,UserDataStore.cs,UserDataStoreTests.csandArchiveTests.cs. Every other file is exactly what git merged. - In those 43 files, every line either side added since e92b546 is still there. The only exceptions are lines that were rewrapped or combined (README/ENGINES Vector and ImageMagick rows, the
ConversionEdgesignature,IsSuggested,NeverFromSystem/UsesSystemCopy, the archive format lists) and the schema numbers that moved. - Every file main added or modified exists in the result. Spot checks: PDF/A links (
PdfPrintableAnnotations), the mirror fallback (MirrorUrlin EngineDownloads, EngineInstaller and fetch-engines.ps1), the LibreOffice archive URL in engines.json, the Ready status text, and the VTF, G-code, Gerber and Canvas code and docs sections. - Branch content is intact: the DrawingConverter, ODP/ODT/PPTX writers, PostScript writer, NSIS, disk images, 7-Zip off the suggested list and never taken from the PC,
FileeRenderer = true, and no jre/H2Orestart/unhwp left in build/, engines.json or the code. - No conflict markers anywhere in the tree. EngineRegistry lists each converter once (DrawingConverter, Gcode.GcodeConverter and WeasyPrintConverter included). FormatRegistry has no duplicate ids or extensions. No test from either side is gone except the intended renames (
Version_88/89/91→94/95/97,…fails_with_7zips_reason→…fails_as_not_an_archive).
Settings schema
ApplyToLibrary: main's 85–94 steps are unchanged and in order (… 91 Gerber, 92 Canvas, 93 G-code, 94 VTF).Apply: the branch steps are now 95/96/97 (UserDataStore.cs:478, 484, 494), andCurrentSchemaVersion = 97(AppSettings.cs:173). A user at main's 94 runs 95–97 once, and the version is written back right away. No stale "schema 89/90/91" is left in docs or code.- The
ApplyToLibrary(N)tests are main's, unchanged. FormatFieldsTests is unchanged and passed in CI (Core 1504/1504 on Linux). - Minor, already in the PR body: a branch preview build at 91 skips main's library steps 88–91.
i18n
- All 23 catalogs are valid JSON with no duplicate keys, and each has exactly en's 1875 keys.
- A 3-way check against e92b546 finds no key where the result doesn't match the side that changed it. The only differences are in mn, and they are intended.
- mn: all 67 texts that differ from main's mn contain Cyrillic. The only values equal to English are names (Filee, Whisper, Letter, Material You …). Placeholders match en.
- Every
DynamicResourcekey and quoted key literal in src/ exists in en.json. The removed keys (engines.state.system_copy,engines.lo_hwp*,engine.unhwp.*) are no longer used.
CI failures (run 43619898112)
tests/Filee.Engines.Tests/ArchiveTests.cs:1031A_cut_off_package_fails_instead_of_hangingfails on Linux and macOS: the job isCompleted, expectedFailed.- Cause: the resolution dropped
SkipWithout7Zip(), so the half-length .xapk now goes throughArchiveExtractor.cs:189-200→LibraryArchives.Extract(SharpCompress), which accepts a ZIP that has no central directory. - This is probably not specific to packages: plain .zip/.jar take the same path. I'd fix it in LibraryArchives (fail when the end-of-central-directory record is missing) rather than skip the test again.
- Cause: the resolution dropped
tests/Filee.Engines.Tests/DiskImageTests.cs:331Apfs_dmgs_made_by_hdiutil_are_left_to_7zipfails on macOS: it getsConversionRefusedException(error.archive_needs_7zip) where it expectsInvalidDataException.- This is not from the sync: the file and the dmg path are the same as on builtin-engines. The test came in with the disk-images PR, and the 7-Zip retirement turned this case into a refusal. It is macOS-only, so it never ran there before full-ci.
- It should call
NeedsSevenZipAsync(line 92) instead ofRefusedAsync(line 86).
- The two "Package on Ubuntu 22.04" jobs fail only because the
portable-linux-x64artifact is missing, a knock-on effect of item 1.
A ZIP whose end-of-central-directory record isn't at its end (a cut-off download) was handed to SharpCompress after .NET's reader gave up, and SharpCompress found the end record of a ZIP stored inside it (an APK in an .xapk) and unpacked that one as if it were the archive. The in-process path now refuses such a ZIP as damaged; 7-Zip, when installed, still gets its retry. A new test cuts a ZIP and a JAR holding a stored ZIP in half. The APFS disk image test (macOS only) expects the "install 7-Zip" refusal that the archive engine gives since 7-Zip became optional. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Merges
main(770e982, 110 commits) intobuiltin-engines(0af8abe). Merge base: e92b546, the previous sync (#1477). No change goes tomain.What comes over from main: VTF (#1483), Gerber / JSON Canvas / G-code (#1493), Mongolian (#1471), PDF/A links (#1504), Engines page Ready status (#1503) and status tags (#1459), merged-PDF paper size (#1451), toolbar notice (#1420), dmg requirements (#1497), LibreOffice archive URL (#1484) and engine mirror (#1487), .nov (#1463), KRC (#1456), NuGet / Android packages (#1470), clip sound (#1430), XCF layers (#1440, #1485), WeasyPrint (#1412) and the rest.
Conflicts (38 files) and how they were resolved
Settings schema (
AppSettings.cs,UserDataStore.cs,UserDataStoreTests.cs)unhwp→hwp-textin the engine orderLibreOfficeForHwpclearedCurrentSchemaVersion = 97. Tests renamed and moved with them (Version_94_settings_put_the_built_in_hwp_text_engine_where_unhwp_was,Version_95_settings_drop_libreoffice_for_hwp…,Settings_before_97_…with main's 94 and a branch build's 96). Doc comments,docs/ENGINES.mdanddocs/RENDER-QUALITY.mdsay 95/96/97.ApplyToLibrary(N)tests (KRC 88, NOV 89, Gerber 90, Canvas 91, G-code 92, VTF 93) are unchanged.FormatFieldsTestscounts pass as they are.Routing (
IConverter.cs,RoutePlanner.cs)ConversionEdgehas both the branch'sCheckedand main'sSources; the planner applies both filters.Archives (
ArchiveExtractor.cs,ArchiveTests.cs)SkipWithout7Zip(), which this branch doesn't have: they now run without 7-Zip. The "no archive" test is renamedA_package_that_is_no_archive_fails_as_not_an_archive(the reason is Filee's own now).Engines (
EngineRegistry.cs,EngineDownloads.cs,EngineEnvironment.cs)DrawingConverterand main'sGcodeConverter, both kept.NeverFromSystem(WeasyPrint, rhwp, 7-Zip) now backs the branch's publicUsesSystemCopy.EngineDownloads.MirrorUrl,EngineInstaller,fetch-engines.ps1) and the LibreOffice archive URL inengines.jsoncame over without conflict and stay. They serve every engine still downloaded, 7-Zip included. The branch's packaging stays: only rhwp is bundled, and there are nojre/h2orestartcomponents.Images and vector (
ImageEncoder.cs,ImageReader.cs,MagickImageConverter.cs,VectorConverter.cs,GhostscriptConverter.cs)VectorConvertergets bothusingblocks: metafiles from the branch, Canvas and Text from main.Tests:
LibreOfficeRouteTestskeeps the branch'sBuildEdges(slides, documents, sheets)andwithoutparameter, and leaves WeasyPrint out as main does.Docs (
README.md,docs/ARCHITECTURE.md,docs/ENGINES.md)i18n (23 locales)
engine.unhwp.descriptionis renamed toengine.hwp-text.description.engines.lo_sheets_hint,engines.lo_hwp*andengines.state.system_copyare removed. Main dropped that last key from en but not from mn.LocalizationTestsfails for mn because of those 23 keys and the stale key; main needs the same fix.Fixes on top of the merge
ZipReader.EndsWithDirectory,LibraryArchives).xapk) and unpacked that one as if it were the archive. This is what made main'sA_cut_off_package_fails_instead_of_hangingfail on every OS.ArchiveInProcessTests.A_zip_cut_off_before_its_directory_is_refused_not_read_from_a_zip_inside_it(zip, jar). It fails without the check.DiskImageTests.Apfs_dmgs_made_by_hdiutil_are_left_to_7zip(macOS only) expects the "install 7-Zip" refusal that the archive engine gives since 7-Zip became optional. This was already broken on the branch and only shows in full CI.Tests (Windows 11 x64, local)
dotnet build Filee.slnx: 0 errors.FILEE_HIDE_7ZIP=1: 385 passed, 46 skipped.dotnet format Filee.slnx --verify-no-changes --severity warn: clean.🤖 Generated with Claude Code