Skip to content

feat(tools): Annotate tool - #1024

Open
lightscape-jm wants to merge 2 commits into
KnifeLemon:mainfrom
lightscape-jm:feat/annotate
Open

lightscape-jm wants to merge 2 commits into
KnifeLemon:mainfrom
lightscape-jm:feat/annotate

Conversation

@lightscape-jm

@lightscape-jm lightscape-jm commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

What does this change?

Closes #877. Adds Annotate, a palette tool that draws pen lines, arrows, boxes, highlights, text labels and numbered callouts on pictures, as planned in #856 (step 4, after the preview fix in #987). It is in the palette's Edit tab for pictures, not on a default donut.

  • Window: the same full-picture stage as Pixelate / blur. The bar at the bottom has Select (V), Pen (P), Arrow (A), Box (B), Highlight (H), Text (T) and Number (N); six colours; three widths; undo, redo and delete; Skip and Save & next.
  • Drawing: drag to draw a line, arrow, box or highlight; a click alone draws nothing. Text is typed in place, in a box that looks like the tag it will become. Number adds the next number at each click.
  • Select: moves a mark, resizes it by its handles (an arrow's ends, a box's corners), restyles it with a colour or width, or retypes a text on a double-click. A mark just drawn isn't selected, so a colour picked next goes to the next mark.
  • Saving: each picture is an ordinary conversion in the toast, "{name} Annotated" in its own format (PNG when it can't be written, as Pixelate / blur). The colour and width picked last are kept in the tool.

How it's built (also in docs/ARCHITECTURE.md → Annotate tool):

  • One painter for both: Engines/Annotations/AnnotationPainter (SkiaSharp) draws the marks in the window, on Avalonia's own Skia canvas through ISkiaSharpApiLeaseFeature, and in the file, where Annotator paints a layer the size of the picture in ImageEncoder.ApplyOptions, after hidden areas. So the preview is the file.
  • ImageOptions.Annotations: points are parts of the picture and sizes parts of its shorter side, so marks look the same at any size. The enums read leniently. The marks count in ReworksPixels, so they are applied once, in the first transform step.
  • AnnotationSet keeps the marks, the selection and undo / redo, with a drag as one step. AnnotationCanvas does the drawing, hit testing and handles.
  • Text uses the system's bold font, or one the system finds for letters it lacks (Korean, Thai …).

Compatibility: the built-in tool-annotate comes with settings schema 59 (UserDataStore adds it to existing libraries, in the palette only). I can renumber it if another change takes 59 first. An older Filee doesn't know the tool and opens nothing for it, as the Preset.Tool note says. SkiaSharp is already a dependency, so nothing new is added.

Type of change

  • Bug fix
  • New feature or improvement
  • New format or engine
  • Translation
  • Documentation
  • Build, CI or dependencies

How was it tested?

  • dotnet format --verify-no-changes
  • dotnet test: Core, Cli and Platform pass, and all the Annotate tests in App, Core and Engines pass. On my Mac, 6 Engines tests (FFmpeg without some encoders, LibreOffice) and 10 App tests (engine-page renders and A_lost_animation_frame_does_not_keep_the_slice_dark) fail the same way on unmodified main. I didn't run the Windows installer (Setup) tests.
  • Tried it in the app (what you converted or clicked, and on which OS): macOS 26.6.2, Apple silicon, a build of this branch on current main, from the palette's Edit tab on a screenshot of a statement:
    • One of each mark: pen, arrow, box, highlight, a text label and two numbers counting up.
    • Select: moved a mark, resized a box by a corner, recoloured it, and retyped the text with a double-click.
    • Undo, redo and delete.
    • Save: "statement-1 Annotated.png" has the marks as the window showed them.

New tests:

Test Checks
Engines AnnotationTests (6) a box on its edges in its colour, inside and outside untouched; an arrow from tail to a filled head; a see-through highlight; pen, text and numbers, with ink that reads on their colour (Korean text too); lines thicker with the width and the picture; the tool run as a conversion in the picture's own format
Core AnnotateToolTests (1) schema 59 adds the tool once; the marks survive JSON, with an unknown kind read leniently
App AnnotateWindowTests (8) through mouse and keyboard: each tool draws where dragged and a click draws nothing; a mark being drawn shows no outline or handles; numbers count up; Select moves, resizes, restyles and deletes; text typed in place and typed again; the box being typed looks like its tag; Save starts the conversion with the marks and the next picture starts clean; what the window draws is what the file gets (within 4 / 255)

Screenshots (UI changes)

The window with a mark from each tool, and the saved file (the same as in #877):

The Annotate window on a screenshot, with marks made by each tool The saved file, "Settings Annotated.png"

Checklist

  • New UI strings are in all src/Filee.App/Assets/i18n/*.json files (24 strings, translated in every language)
  • New third-party code or binaries are listed in THIRD-PARTY-NOTICES.md (none: SkiaSharp is listed already)
  • The PR does one thing (unrelated changes go in a separate PR)

🤖 Generated with Claude Code

Pen lines, arrows, boxes, highlights, text labels and numbered callouts
drawn on pictures, saved as "{name} Annotated" through an ordinary
conversion in the picture's own format. One SkiaSharp painter draws the
marks for the window and the file, so the preview is the file. In the
palette only; settings schema 59.

Closes KnifeLemon#877

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the feature New features label Nov 2, 2026
Skia's Linux build here finds fonts by family name but never by letter:
MatchCharacter returns null even with a Korean font installed, so a label
in Korean was drawn in the default font as empty boxes (CI on Ubuntu 24.04:
85.96 px against 48.96 px for "Save" alone). When MatchCharacter finds
nothing, the installed families are looked through for one that has the
letter, sans-serif first, and the answer is kept.

The yellow number's check looks for dark ink across the whole circle, not
one row: DejaVu Serif Bold, Linux's default bold here, has a thin diagonal
in its 7 that the middle row crossed at 137.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lightscape-jm

Copy link
Copy Markdown
Contributor Author

Linux CI failure: cause and fix (commit e4b2f4d4)

The Ubuntu 24.04 jobs failed on A_pen_line_text_and_numbers_draw_and_their_ink_reads_on_their_colour: a Korean label came out 85.96 px wide, against 48.96 px for "Save" alone. The Korean letters were drawn as empty boxes.

Cause: with SkiaSharp.NativeAssets.Linux.NoDependencies, SKFontManager.MatchCharacter returns null on Linux even when a font with the letter is installed. MatchFamily by name still works. I checked this in an Ubuntu 24.04 container with the same packages and fonts-noto-cjk:

  • MatchCharacter('저') returned null, with and without ["ko","zh","ja"];
  • MatchFamily("Noto Sans CJK KR") found the font, and it has the letter;
  • the widths matched CI exactly.

Fix: when MatchCharacter finds nothing, Annotate looks through the installed families for one with the letter, sans-serif first, and keeps the answer. Mac and Windows are unchanged.

With that passing, the same test reached a check of the yellow number's dark ink, which sampled one row. On Linux the default bold font is DejaVu Serif Bold, whose "7" has a thin diagonal that row crossed at 137 (the bar is < 80). The check now looks across the whole circle, with the same bar.

Annotate's engine tests (7) and window and palette tests (20) pass in the container, and on macOS.

Possibly affected elsewhere (not changed in this PR):

  • Pdf/PdfFinishing.cs:149 uses MatchCharacter first and then falls back to Arial or the default font, so on Linux it may draw Korean or other non-Latin text as boxes. I haven't tested that output.
  • Cad/CadFonts.cs:180 also calls it, but CAD tries named CJK families first, so its text still works on Linux.

Happy to open an issue for the PDF one if it's useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Annotate tool: arrows, boxes, highlights, text and numbered callouts on pictures

1 participant