feat(sign): make the visible signature configurable, and fix device certificates in self-hosted mode - #7262
Conversation
…te fields it shows The visible signature was drawn at a hardcoded PDRectangle(0, 0, 200, 50), so a caller could pick the page but not where on it, and the contents were fixed to signer name, date and reason. Signing a form meant the signature landed in the bottom-left corner regardless of where the document's signature area was. Add four optional coordinates to the request plus a list of certificate fields to draw. All are optional and, when none are supplied, the appearance is drawn exactly as before - existing callers see no change. Three pieces, each testable on its own: SignatureBox owns the coordinate conversion. Callers place the box the way someone dragging it on screen thinks - y measured downwards from the top edge - while PDF's origin is bottom-left with y growing upwards. Keeping the flip in one place is deliberate: it is the kind of arithmetic that looks right and is wrong by exactly one page height. It also clamps, because a box dragged off the page would otherwise produce an invisible signature that reads as a failure, and an offset media box (cropped or imposed pages) would place it wrongly. CertificateAttributeService reads the fields off the X.509 certificate and omits the ones it does not carry rather than returning blanks, so a caller can offer exactly the fields the user's own certificate can fill. A personal certificate typically has a name and little else; an empty "Email:" line in a signature looks like a defect. SignatureAppearanceLayout scales the type to whatever box was drawn, shrinking until the lines fit and ellipsising or dropping trailing lines when they cannot. Text escaping the box would overlap the document itself. 29 tests, asserting the resulting geometry - measured line widths and total height against the box, exact PDF rectangles for the axis flip - rather than that a result came back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rop endpoint does The box originally took y measured downwards from the top of the page, on the grounds that it reads more naturally to someone dragging a rectangle. That put it at odds with the rest of the API: CropController feeds its request values straight into PDRectangle, so /crop is bottom-left origin with y growing upwards, and the frontend's cropCoordinates helpers already produce exactly that. Keeping the other convention would have meant a second flip in the client, on top of the one those helpers already do - the arithmetic most likely to end up wrong by one page height, and the hardest to notice because the signature still appears, just in the wrong place. Two conventions across two endpoints is also the kind of thing a reviewer would rightly ask about. SignatureBox gets simpler as a result: the flip belongs to the client, and what remains is the part worth keeping - clamping the box inside the page so it cannot be dragged out of sight, and honouring a media box whose origin is not (0,0), as cropped or imposed pages have. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s it shows Exposes the placement and field-selection the backend gained, so the feature is reachable without hand-writing an API call. SignaturePlacementPicker reuses the crop tool's CropAreaSelector rather than growing a second area-selection widget. Users get the interaction they already know - drag to draw, drag inside to move, corner handles to resize - and the DOM-to-PDF coordinate maths stays in one implementation. That reuse is the reason the backend was switched to PDF user space: cropCoordinates already emits exactly the numbers the endpoint now takes, so the picker feeds it straight through with no second conversion to get wrong. It reads page dimensions from the page being signed rather than the first one, since those differ in mixed-size PDFs, and falls back to A4 if the page cannot be read - the backend clamps anything that overflows, so a wrong guess is recoverable rather than fatal. SignatureAttributePicker offers every field the backend understands rather than only those the chosen certificate carries. The keystore is not opened until signing, so the tool genuinely cannot know what is in it beforehand; ticking a field that turns out to be absent costs nothing, because the backend skips it. Both are additive: with no box drawn and no field ticked, the request is byte-identical to what the tool sent before. English and Spanish strings included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two rules the earlier commits missed: HowToAddNewLanguage requires new tags to land in en-US/translation.toml first - it is the reference other locales are generated from - and only then in the individual language files. The strings went into en-GB and es-ES but not the reference, so any locale added later would have been missing these keys. The frontend lint rule rejects Mantine's Button in favour of the shared @app/ui/Button, which the attribute picker was importing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tooltip ADDING_TOOLS recommends tooltips for new controls, and the appearance step now carries two the guide's existing tips say nothing about. Extends the step's existing tooltip rather than adding a second one, since both controls live in that step, and follows the guide's wording rules: what the user gains rather than how it works, concrete rather than abstract - "drag its corners to resize", "the text shrinks to fit" - and short enough to read while deciding. Also corrects a bullet that had gone stale: the visible-signature tip still said the user could choose which page, which was the whole of the old placement story and is now only half of it. Strings land in en-US first as the reference locale requires, then en-GB and es-ES. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Long documents are often initialled on every page so a reader can see at a glance that the whole thing was signed, and the tool had no way to do that. A PDF signature has one widget on one page, so this cannot be what it sounds like: only the page named by pageNumber carries the signature. The other pages get page content drawn to look the same, which carries no cryptographic meaning and no validator will report. The request field says so, and the UI has to as well - a document that looks signed in twelve places but is signed in one is worse than one that is honest about it. SignatureMarkStamper is deliberately a separate class from the signing code, with a comment saying it must never touch the signature dictionary. Putting the two in one file would make it easy to blur that line later by accident. Order matters: marks are stamped before addSignature, so the signature covers them. Stamping afterwards would alter the bytes the signature attests to and every validator would flag the document as modified. Marks are drawn in grey with a hairline border rather than matching the signature exactly, so the two are distinguishable on the page. Ten tests read the text back off each page - the only way to tell "marked" from "skipped" - and cover the two that would break the document: that the signed page is left alone, and that existing page content survives underneath. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… does The box could only be positioned from a thumbnail in the side panel, which is not how anyone expects to place a signature. Acrobat has you drag a rectangle straight onto the page, and that is what this does. SignatureBoxDragOverlay mounts on every page of the viewer but stays inert until the tool announces placement mode. Whichever page the user drags on becomes the signed page: having dragged a box onto page 5 and then signed page 1 would be a surprise, and the drag is the more deliberate of the two choices. Tool and viewer talk over window events rather than a shared context. The viewer is rendered far from the tool panel, so a context would have to be threaded through the whole viewer tree for one optional feature; the project already hands data to a tool this way for the guided tour's crop step. The overlay reports PDF points with a bottom-left origin, which is exactly what the endpoint takes, so the box travels from the user's mouse to the request with a single conversion. Two ways out of a modal gesture, because being stuck in one is worse than the feature is worth: Escape, and the button that started it. The panel also cancels on unmount - navigating away mid-drag would otherwise strand the viewer in placement mode with no visible way back. The side-panel thumbnail is now shown only for "repeat on every page", where the same position applies to all of them and there is no single page to drag on. That mode carries a visible warning that only one page is really signed; the checkbox is disabled until a box exists, since there is otherwise no shape to repeat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d link back Three problems found by using it. Drawing the box scrolled the document instead. The overlay swallowed the mousedown but then listened for mousemove on the window, so the viewer went on receiving the gesture and panned. Pointer capture fixes it properly: the events are routed to the overlay until release, and touch-action/user-select stop the browser interpreting the drag as a pan or a text selection. Click-click now works as well as click-drag. A press released without real movement sets the first corner and the next click closes the box, with the shape previewing under the cursor in between. Both gestures are supported rather than one replacing the other, since a drag that stalls would otherwise be a dead end, and a 4px threshold keeps a shaky hand from losing the corner. Marks on the other pages did not look like the signature: they were drawn in Helvetica grey while the signature uses Times Bold black, which reads as a rendering fault rather than the same signature repeated. They now match. That removes the visual difference that made a mark identifiable, so honesty now comes from behaviour instead: each mark carries a link annotation that jumps to the page holding the real signature, where the reader can open its properties and see who signed and whether it validates. A link is used rather than anything signature-shaped precisely because it is inert - it navigates and does nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y works The placement tip was written when the box was drawn on a thumbnail in the side panel, and still said so: "drag a box on the page preview", "drag its corners to resize it". Neither is true now - the box is drawn on the document and the overlay has no corner handles - and a tooltip that describes an older version of the UI is worse than none, because the user trusts it. It now describes both gestures, and says which page ends up signed, since that is decided by where the user draws rather than by the page field above it. Adds the tip the all-pages option never had, leading with the part that matters: only the page drawn on is really signed, and clicking a mark takes you there to check it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Editing the file through PowerShell's Set-Content -Encoding utf8 re-read the
UTF-8 bytes as ANSI and wrote them back out, mangling every line with a
non-ASCII character and prefixing the file with a BOM:
zeroPad = "Zero‑pad Width" -> "Zero‑pad Width"
pageFormatA4 = "A4 (210×297mm)" -> "A4 (210×297mm)"
144 lines were affected, none of them ones this branch meant to touch, and the
diff carried 198 deletions that were pure collateral - enough noise to bury the
change under it.
Restored from main and reapplied the additions through a UTF-8-safe path. The
file's diff drops from +256/-198 to +52/-1, which is what it should have been.
en-US and es-ES were written by other means and were unaffected; both verified
clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Two checklist items sorted: Translation counter — ran it, no changes needed; the README badges were already accurate. Worth flagging that the script is Docs — opened Stirling-Tools/Stirling-Tools.github.io#123. The every-page option is documented behind a caution rather than a plain description: only the drawn-on page is really signed, and I would rather a reader learned that from the docs than discovered it after signing. If that option gets dropped from this PR, that section comes out with it. On why the every-page option exists at all, since it is the part I would understand you hesitating over: It is aimed at organisations and technical practices rather than one-off signing. In Spain, professional bodies (colegios profesionales — architects, engineers and so on) run a mandatory endorsement process called visado for technical documents, and the endorsement stamp is applied to every page of the project, not just the cover. A drawing set that carried the stamp only on page 1 would not be accepted, because there would be nothing preventing pages being swapped afterwards. The same reasoning drives the practice of initialling every page of a paper contract. Anyone reproducing that workflow digitally today has to stamp the pages by other means and then sign, which is both awkward and easy to get wrong. That is exactly why the marks link back to the signed page rather than merely looking like signatures: the reader gets the visual continuity the process expects, and one click to the signature that actually carries the cryptographic weight. |
Resuelve el conflicto de CertSignController.java, que venia de tres cambios del original fusionados despues de que saliera esta rama: - Stirling-Tools#7400 movio CreateSignatureBase a stirling.software.SPDF.pdf.signature - Stirling-Tools#7204 anadio @toolio(produces = ToolFormat.PDF) - Stirling-Tools#6334 cambio getWidgets().get(0) por getWidgets().getFirst() En los tres gana la version del original; la logica propia (el rect condicionado al box, drawAttributeText y las marcas) se conserva intacta.
The visible signature could only draw the bundled Stirling mark, scaled to 0.08 and pinned at a fixed offset that ignored the signature box entirely. Requested in discussion Stirling-Tools#7277, where the reporter wanted their company logo the way Foxit does it. Adds an optional logoImage upload (PNG or JPEG) and a logoPosition with five values: LEFT, RIGHT, TOP, BOTTOM and BEHIND. SignatureLogoPlacement is a separate, PDFBox-light class holding the geometry, so the five positions are unit-testable without rendering anything. Two rules it enforces: - The image keeps its aspect ratio. A stretched company logo reads as a rendering fault rather than a design choice. - The text keeps at least 65% of the box in both directions, so a banner-shaped logo cannot squeeze the signer out of their own signature. BEHIND is the exception to the split: the logo fills the box as a watermark and the text keeps the whole box, drawn on top. It reuses the MULTIPLY blend at half opacity the bundled mark already used, which is what keeps text readable over it. Nothing changes for existing callers: with no box, no field selection and no logo parameters, the appearance is byte-for-byte what it was. The marks stamped on the other pages now receive the same effective logo as the signature, since a mark meant to match it cannot be missing the logo it has. Also adds the certSign.appearance.tooltip.attributes section that en-GB was missing while en-US and es-ES had it.
Closes Stirling-Tools#7316. The desktop offers the Windows certificate store and plugged-in PKCS#11 tokens as signing sources, but connecting it to a self-hosted server made them disappear. Three separate causes, all of them routing: 1. The app-config comes from whichever backend the app is talking to, and it carries hardwareSigningAvailable. A self-hosted server answers that for itself - false, since it is not a desktop - so CertificateTypeSettings hid "This device" and reset signMode to MANUAL. The desktop now re-answers that one flag from its own machine and leaves the rest of the config alone: the others describe the deployment, where the server is the authority. 2. /api/v1/security/cert-sign/hardware/** matched the tool-endpoint rules and went to the server, which reported on its own hardware or refused outright. 3. Signing itself posted to the server, which cannot reach a key held in the user's store or token. The list of device-local endpoints lives in src/core, MIT, so the routing layer consults it rather than keeping a catalogue of which tools need local hardware - the same shape it already uses for CONVERSION_ENDPOINTS, and what Stirling-Tools#7510 suggested. Signing cannot be decided by path, since the endpoint is the same whichever certificate was picked, so the caller marks that request instead. Auth now follows the destination rather than the connection mode. Before, a request routed to the local backend in self-hosted mode still carried the server's JWT to loopback. Nothing is loosened: HardwareKeyStoreService.assertLocalDesktop already rejects these calls unless they come from the desktop bundle over loopback, and a regression test pins that an uploaded keystore still signs on the server.
The desktop override shadowed the whole of core/api/config.ts: five functions re-exported unchanged so that one could be wrapped. The frontend guide asks for the opposite - "instead of duplicating the entire file, create a new extension module for the core app and override that" - and gives the reason, which is a real bug rather than a style point: a sixth export added to core tomorrow would simply vanish from the desktop build. Core now owns applyDeviceCapabilities(), a no-op that returns the config as it came, and fetchAppConfig calls it. The desktop overrides that one function. Also drops the mentions of specific builds from comments in core. The guide is explicit that core must not reference build targets by name, and the wording was not carrying its weight anyway: what matters is that some builds have a second backend to choose from, not which ones they are.
createToolFlow renders the execute button after the steps, inside the panel's scroll area, so a tall tool pushes it below the fold. Signing a PDF with a certificate builds a 1703px panel against an 811px viewport at a 900px window: the button only appears after scrolling almost twice the visible height, and a user who does not think to scroll cannot run the tool at all. Pin it to the bottom instead. Sticky costs nothing when the panel fits - the button stays exactly where it was, as Convert and Compress still show - and only holds its place once there is more content than room. The wrapper is a flex column on purpose: the button was a direct child of the Stack and stretched to its width, and a plain block would shrink it to its label. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It is the tallest step in the app, and the run button sits after it, so its height is what decides whether the user can find the button at all. The reset for the drawn box was a full-width button that only existed once a box had been placed, so completing the placement made the panel a row taller at exactly the moment the user was looking for the button. It is now the same icon the thumbnail picker already uses, always present and disabled until there is something to reset, so the height cannot change underfoot. The fourteen certificate fields fold away behind a toggle that states what is currently chosen. They are an advanced choice - leave them alone and the backend draws the fields it always has - and they were some 300px of a panel that already ran past the bottom of the window. Measured against the same document and certificate: 1703px of panel before, 1384px after. They are unmounted rather than collapsed. Mantine's Collapse keeps its children in the page, so fourteen invisible tick boxes would still take keyboard focus on the way to the button. The new test was seen to fail against the old code, which is what makes it worth having: seven controls without a box, eight with one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🌐 TOML Translation Verification Summary🔄 Reference Branch:
|
AGENTS.md is explicit that en-US is the only locale to edit by hand, and the PR check makes the cost of ignoring it concrete: check_toml.yml verifies every locale file a PR touches against that PR's en-US, so touching en-GB and es-ES pulled them into a comparison they cannot pass. They are 167 keys ahead of en-US and 150 behind it - and so is main, which is the point: the drift is the project's own, carried by Crowdin, and nothing this branch can fix. en-US keeps both new keys. The other locales fall back to it until Crowdin catches up, which is the arrangement the project already relies on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El PR Stirling-Tools#7262 se queda solo con en-US, porque check_toml.yml compara cada fichero de idioma que toca un PR contra su propio en-US, y en-GB y es-ES arrastran el desfase del proyecto: 167 claves de mas y 150 de menos, las mismas que tiene main. No es algo que la rama pueda arreglar. Pero mi-version no se propone a nadie, asi que aqui si se pueden tener: son 63 cadenas del panel de firma que en el original todavia no existen, y sin ellas la herramienta sale en ingles dentro de una interfaz en espanol. En cuanto el PR se fusione y Crowdin las traduzca, este commit se podra tirar. Se restauran los dos ficheros exactamente como estaban en 1f05319, de modo que el arbol vuelve a ser el mismo del que salio el MSI ya construido. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AppLayout puts banners above the app and rewrites .h-screen to 100% so that everything below shrinks to what is left. The floating tool panel measured itself in viewport units instead, so with a banner up it hung off the bottom of the window by the banner's height - and since it clips its own overflow, that strip was unreachable. No amount of scrolling brought it back. What lived in the strip was the run button, pinned to the bottom of the panel. Signing a PDF was therefore impossible while the desktop's banner offering to make Stirling PDF the default PDF application was showing, and possible again the moment it was dismissed, which is what made the fault look intermittent. Measured at 1280x800 with a 56px banner: the panel ended 48px past the bottom of the window before, and inside it after. The file sidebar has always used 100%; this brings the rail into line with it. The regression test inserts a banner rather than waiting for a real one: all four are desktop-only and which of them shows depends on the machine, while what the panel cares about is only that something above it took height away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d third A third of the box was arbitrary in both directions. In a roomy box it left room the text had no use for; above a short signature it gave the logo a band about a millimetre tall, which users read - reasonably - as the logo not working. The strip is now searched downwards from half the box and stops at the largest share where the text still keeps every line, and type no more than a quarter smaller than it would have had with the whole box to itself. The question is put to SignatureAppearanceLayout, which is the class that already decides how text fits, so nothing new decides it. The old third stays as an unconditional floor, and that is the part worth remembering. Sizing purely by what the text can spare makes a band above a short signature thinner than it used to be, because three fields underneath demand every point of the height - the very case that prompted the change. The floor is what keeps the fix from regressing it, and the test checks it against the old formula rather than against a number, so lowering it later fails there instead of quietly shrinking logos. Measured with a 4:1 image; logo width beside the text, band height above it: 300x120 105 -> 145.2 pt 42 -> 55.2 pt 200x60 67.6 -> 97.6 pt 18.6 -> 18.6 (floor) 60x15 20.4 -> 29.4 pt 4.7 -> 4.7 (floor) The chosen position is never second-guessed: what gives is the size, which is what the user can see and adjust. BEHIND is unchanged - it already scaled to the box - and now has a test pinning that for cramped boxes too. The marks stamped on the other pages inherit all of it, because they go through the same class. That is what the separation is for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
97 commits del original, hasta d3708c1 (version 2.14.3). Dos conflictos, los dos resueltos a favor del original, porque el original ha arreglado por su cuenta el boton de ejecutar que no se veia: - createToolFlow.tsx -> Stirling-Tools#7688, de Frooodle. Hace lo mismo que nuestro 50dca21 casi propiedad por propiedad (position: sticky, bottom: 0, z-index: 2, fondo var(--c-surface), margin-inline negativo), pero lo saca a un createToolFlow.module.css en vez de dejarlo en estilos en linea, lo aplica solo cuando el paso de revision no esta visible -- si no, la barra flotaria sobre los resultados -- y arregla ademas dos mecanismos de scroll muertos desde mayo: el closest() de ReviewToolStep, que nunca casaba porque Mantine escribe overflow: scroll, y un ref de Convert.tsx que no estaba enganchado a ningun elemento. - ToolPanel.css -> la reforma de barras laterales (Stirling-Tools#7518, Stirling-Tools#7660, Stirling-Tools#7695) ya deja .tool-panel--floating en height: 100%, que es lo que buscaba nuestro 81998b1 al sustituir el 100vh. Su version es la mas completa, asi que se queda esa. El diff neto de esta rama en los dos ficheros era exactamente esos dos arreglos y nada mas, de modo que la resolucion no pierde ni una linea del trabajo de la firma: sus ficheros propios pasan de 45 a 43. El resto auto-fusiona, incluido en-US/translation.toml. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
addBanner busca el contenedor de AppLayout por sus estilos en linea para colgarle una barra encima. Comparaba la altura contra "100vh", y el Stirling-Tools#7518 la cambio a "100dvh" para que el cromo del navegador movil no recorte la aplicacion, asi que la busqueda no encontraba nada: addBanner devolvia false y el test caia en su primera asercion, antes de llegar a medir el boton. Acepta las dos unidades. El guard que ya habia -- devolver false si no hay shell -- se conserva a proposito: sin el, un cambio futuro de layout dejaria el test midiendo una ventana sin barra, que pasa siempre. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…o it The visible signature was designed around having most fields enabled. With three, the type stopped at 12pt whatever the box, so a large rectangle came out mostly empty, and a long subject name shrank the whole block to 4pt and was then cut short with an ellipsis while space went unused below it. The type now grows as well as shrinks, and long values are broken across lines. Filling each line before starting the next is what makes the line count rise with the size and never fall, so the largest size that fits can be bisected for: nine passes instead of the previous thirty-three, and one call to the layout per signature instead of ten. Three things had to change for the type to be able to grow at all. The margin is now a share of the box rather than a multiple of the font size, which was circular. The block is measured against the font's bounding box rather than its ascent and descent, so an accented capital - routine in the names this feature exists to stamp - is not shaved by the top of the box. And the height left over once the width is exhausted goes to the leading. The logo split follows: with the text sizing itself to whatever it is handed, there is no longer any text quality to trade a larger logo against, so the search over shares is gone and the logo simply takes the largest strip its shape can use. That also ends a real instability - enabling one more field could shrink the logo by 17% - and makes left and right mirror each other, which they did not. The logo is held one margin off the edge so neither the appearance clip nor the mark's border can shave it. The signer's name is drawn larger than the rest, marked on the attribute rather than on its label so it survives translation. Both drawers now share one routine that consumes already-positioned lines, so the signature and the marks on the other pages cannot drift apart. The legacy appearance is untouched: it reaches neither class.
The certificate picker was a dropdown in the tool panel, which is a few hundred pixels wide. Everything a person needs to tell two certificates apart - the subject, the issuer and the expiry date - was crammed into one line, so with several certificates from the same authority the names ran into each other and choosing between them was guesswork. The list moves to a dialog, where there is room to give the subject, the issuer, the expiry, the validity and the source a column each, and to search by name, issuer or serial number. The panel keeps only the certificate that is chosen. Expired and not-yet-valid certificates are still listed, because knowing one is there and unusable beats wondering where it went, but they cannot be picked - as before. The loading moves to a hook shared by the panel and the dialog, so the two cannot end up reading the store separately and disagreeing about it. The naming and ordering rules move to a module of their own: they were closures inside the component and could not be tested, and they carry the judgement that matters here - a certificate whose subject is a bare GUID is named by its Windows friendly name, a self-signed one does not repeat its own name as its issuer, and what you can sign with sorts above what you cannot. Only the alias is carried in the tool's parameters, so the panel re-derives the rest from the loaded list. A token needs its PIN to enumerate, so after the panel is reopened the alias can be all there is to show; that was true before and is unchanged.
The legacy appearance draws the logo at a size taken from the image's own pixels rather than from the box, which is the one place in this feature where an image can be drawn larger than the area it is clipped to. It does not misbehave today: the bundled mark is 512x512, which at the long-standing 0.08 scale is 41pt inside a 200x50 box, and a custom logo cannot reach this path at all. What is wrong is that nothing says so. An image larger than the box is now letterboxed into it instead, and the two numbers that decide this have names. The path is otherwise left exactly as it was, down to the operators: the guard only diverts an image that would not have fitted, so every existing caller gets the same content stream it did before. The new test pins that by reading the operators back out of the signed document - it fails on a one-hundredth change to the scale.
The field labels drawn inside the signature came from a switch in Java that only speaks English, while the tick boxes that choose those same fields are translated. Someone using the application in Portuguese picked "Assinado por" and got a document stamped "Signed by". There is nowhere in the backend to fix this: the project keeps one messages.properties with no per-language variants, and every translation it has lives in the frontend's TOML files. So the caller sends the labels it already has, alongside the fields they belong to, and Java falls back to English for anything it is not given. That also means the signature is stamped in the language of the person signing rather than of the server, which for an appearance baked into the document is the right end to decide it from. The labels ride on the CreateSignature instance rather than widening sign(...) for the fifteenth time, which is where the custom logo already lives for the same reason.
The box arrives measured against what a viewer shows: the crop box, turned by the page's rotation. It was being read against the media box, unturned. On an ordinary page those are the same rectangle and nothing was wrong; on a trimmed, imposed or rotated page they are not, and the signature landed somewhere the user had not drawn it - which is what being told your logo is "cropped in some signature sizes" looks like from the other side. The new test says it plainly: against the old code, a page with a crop box inset from the sheet renders with nothing drawn on it at all. The mark was placed off the visible area entirely. Rotation needs the appearance turned as well as the rectangle moved, or the signature reads down the side of the page. The layout is done the way up the reader sees things and turned into the page's own coordinates afterwards, once for the signature through the appearance matrix and once for the marks through the content stream. The render test measures the result: against the old code a 200x60 mark came out 44x179. SignatureBox now takes the page rather than a rectangle, since the crop box and the rotation both have to come from it, and the rectangle overload had no callers left.
… module Importing the application's own i18n module to read the field labels pulled its whole bootstrap into every file that touches the cert-sign operation, and two suites that mock react-i18next stopped loading because of it. The bare i18next singleton is the same instance without the bootstrap. When it has no translations loaded there is nothing useful to send, so nothing is sent and the backend falls back to English, which is what it does for a caller that never had them.
A box too small for every field drops the ones that do not fit and marks the last line it kept, so the reader can tell the signature is short of something. The mark was appended to that line whatever it was, so a box that dropped "Location" left "Serial: 3245c6f5c366ecf5..." — a complete serial number wearing the mark that means "this value continues". A verifier comparing it against the certificate reads a mismatch, which is a worse failure than a missing field. The mark now only joins the last character of a value the box cut in half. After a whole value it follows a space, and when even that does not fit the line is left alone: a signature that misstates what it certifies is worse than one that quietly shows a field fewer. Found by rendering the placement grid and looking at it: at 200x60 with the logo in a TOP or BOTTOM band, six fields lose "Location" and the serial grew an ellipsis it had not earned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@GUILHERME-GARCIATECH thank you for testing this on a real Windows desktop and writing it up so 1. Certificate selectionDone. The dropdown is gone, and the list moved into a dedicated dialog with the columns you asked The side panel is then just the summary — the chosen certificate's name, its issuer, its expiry One limitation I would rather state than hide: for the Windows store the list reloads by itself, so 2. Signature layout does not adapt to the selected fieldsThis was the real bug, and your diagnosis was exact. The layout only ever searched downwards from It now finds the size by bisection over the range the box allows, with the ceiling raised from 12 pt I render a grid for this: 4 box sizes × {3 fields, 6 fields} × {no logo, 1:1 logo, 4:1 logo} × 5
The last row is your complaint restated as a number. "Very different visual results depending on how 3. Logo sizing and positioningDone. For LEFT and RIGHT the logo takes the full box height, converted to width by its own aspect ratio, About the clipping specifically. There is now a 4 % inset between the logo and the edge of the And the whole thing now scales with the rectangle you draw. Coverage of the box height by logo One case the grid found that I have not changed. With the logo in a TOP or BOTTOM band, the band And one it found that I did fix. In that same cramped case the layout drops the fields it cannot 4. Comparison with FoxitPartially, and deliberately so. I took two things from it: the signer identity gets its own emphasis, I did not reproduce Foxit's design — no two-column metadata block, no separator rules, no fixed The point you withdrewNoted, and thank you for checking before reporting it. The viewer's rendering is a separate concern Two more things your report turned up
How this was checkedI render a grid of 88 signatures — every combination above — against both the code you tested and What has not been checked is the certificate dialog against a real Windows certificate store. I All of it is on the branch. I would be grateful if you could run your test again — and if |
…oaded The pipeline builder renders every tool's settings so a run can be configured before any document exists, and it mounts no file context on purpose. The appearance panel subscribed to the workbench files at its top level, and the file hooks throw outside their provider, so opening a certificate-signing step there crashed the panel with "File hooks must be used within a FileContextProvider". The subscription moves into a child that is mounted only when the provider is there. In the workbench nothing changes: the picker still receives the loaded document and its thumbnail. In the pipeline builder the picker draws over an empty page, which is what configuring a run without a document can show. Measured both ways: PipelineStepSettings goes from failing on certSign to its six tests passing, and it passes on main without this branch, so the crash came from here and not from upstream. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
El PR Stirling-Tools#7262 se queda solo con en-US, porque check_toml.yml compara cada fichero de idioma que toca un PR contra su propio en-US, y en-GB y es-ES arrastran el desfase del proyecto: 167 claves de mas y 150 de menos, las mismas que tiene main. No es algo que la rama pueda arreglar. Pero mi-version no se propone a nadie, asi que aqui si se pueden tener: son 63 cadenas del panel de firma que en el original todavia no existen, y sin ellas la herramienta sale en ingles dentro de una interfaz en espanol. En cuanto el PR se fusione y Crowdin las traduzca, este commit se podra tirar. Se restauran los dos ficheros exactamente como estaban en 1f05319, de modo que el arbol vuelve a ser el mismo del que salio el MSI ya construido. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 79c64bd)






Description of Changes
Lets the user drag the signature box onto the document and pick which certificate fields appear inside it. Optionally repeats the appearance on every page.
The visible signature was drawn at a hardcoded
PDRectangle(0, 0, 200, 50), so a caller could pick the page but not where on it, and the contents were fixed to signer name, date and reason. Signing a form meant the signature landed in the bottom-left corner regardless of where the document's signature area actually was.What changed
SignatureBox— where the box goes, in PDF user space. Clamps inside the page and honours an offset media box, as cropped or imposed pages have.CertificateAttributeService— reads 14 fields off the X.509 certificate; absent ones are omitted rather than returned blank.SignatureAppearanceLayout— scales the type to whatever box was drawn, shrinking and then ellipsising rather than letting text escape the box and overlap the document.SignatureMarkStamper— the every-page marks. Deliberately a separate class that must never touch the signature dictionary; keeping them in one file would make that line easy to blur later.SignatureBoxDragOverlay— the drag interaction, mounted per page in the viewer. Supports click-drag and click-click, and uses pointer capture so the viewer does not pan mid-gesture.The logo
showLogodrew a bundled Stirling mark scaled to 0.08 and pinned at a fixed offset that ignored the signature box entirely. Requested in discussion #7277, where the reporter wanted their company logo the way Foxit does it.logoImage- an optional PNG or JPEG upload. Absent, the bundled mark is used exactly as before.logoPosition- LEFT, RIGHT, TOP, BOTTOM or BEHIND.SignatureLogoPlacementholds the geometry as a separate class, so the five positions are unit-testable without rendering anything.Two rules it enforces, both covered by tests:
BEHIND is the exception to the split: the logo fills the box as a watermark and the text keeps the whole box, drawn on top. It reuses the MULTIPLY blend at half opacity the bundled mark already used, which is what keeps text readable over it. The marks on the other pages receive the same logo, since a mark meant to match the signature cannot be missing the logo it has.
Why the coordinates use a bottom-left origin
/cropalready works that way and the frontend'scropCoordinateshelpers emit exactly those numbers, so the overlay feeds the endpoint directly. A second convention would have meant converting twice — the arithmetic most likely to end up wrong by one page height, and the hardest to notice, because the signature still appears, just in the wrong place.On "show it on every page"
A PDF signature has one widget on one page, so only the drawn-on page carries the signature; the others get page content that looks the same and carries no cryptographic meaning. Each mark is a link annotation jumping to the signed page, where the reader can open the signature's properties — a link rather than anything signature-shaped, precisely because it is inert. The request field and the UI both say what is happening.
Device certificates against a self-hosted server (#7316)
Reported separately: the desktop offers the Windows certificate store and plugged-in PKCS#11 tokens as signing sources, but connecting it to a self-hosted server made them disappear. Three causes, all routing, none of them a backend restriction:
hardwareSigningAvailable. A self-hosted server answers that for itself -false, since it is not a desktop - soCertificateTypeSettingshid "This device" and resetsignModetoMANUAL. The desktop now re-answers that one flag from its own machine and leaves the rest of the config alone: the others describe the deployment, and there the server is the authority./api/v1/security/cert-sign/hardware/**matched the tool-endpoint rules and went to the server, which reported on its own hardware or refused outright.The list of device-local endpoints lives in
src/coreand the router consults it, rather than the routing layer keeping a catalogue of which tools need local hardware - the same shape it already uses forCONVERSION_ENDPOINTS, and what #7510 suggested. Signing cannot be decided by path, since the endpoint is the same whichever certificate was picked, so the caller marks that one request instead.Auth now follows the destination rather than the connection mode: before, a request routed to the local backend in self-hosted mode still carried the server's JWT to loopback.
Nothing is loosened.
HardwareKeyStoreService.assertLocalDesktopalready rejects these calls unless they come from the desktop bundle over loopback, and a regression test pins that an uploaded keystore still signs on the server rather than quietly moving everyone's signing onto the desktop.Thanks to @Frooodle for confirming in #7510 that PRs are welcome in any directory - this one needed
src/desktop/.Backwards compatible
Every new field is optional. With no box and no field selection, the request produces the same output as before.
Closes #7261
Closes #7316
Checklist
General
Documentation
New tags were added to
en-US/translation.tomlfirst as the reference locale, then translated intoen-GBandes-ES.Translations (if applicable)
scripts/counter_translation.pyUI Changes (if applicable)
Testing (if applicable)
task checkto verify linters, typechecks, and tests passSignature placement, fields and logo. Backend tests cover the box maths, the certificate reading, the text fitting, the marks and the five logo placements. Verified end to end by building the Windows installer and signing real documents: placement at several zoom levels, both gestures, and the every-page marks with their links.
Device certificates against a self-hosted server. Covered by tests on the routing: the hardware endpoints resolve to the bundled backend in all three connection modes, a request marked device-local does the same on a shared path, the local answer for
hardwareSigningAvailablewins over the server's while every other field is left alone, and no server token is attached to either. Each was watched failing before the fix - and only in self-hosted mode, which is where the bug lives.Two regressions are pinned deliberately, since both would be silent: that an uploaded keystore still signs on the server rather than being moved onto the desktop, and that the app-config itself still comes from the server.
What I could not test. This half needs a real self-hosted server with a desktop build pointed at it, which I do not have here, so the end-to-end path - picking a certificate from the Windows store while connected to a server, and signing with it - is unverified. The routing logic is covered; the round trip is not. If a maintainer or @GUILHERME-GARCIATECH can try it, I would rather hear about it now than after merge.
Notes for reviewers
LocalEmbedPDF.tsxgains two lines (an import and the overlay mount). The overlay is inert until the cert-sign tool announces placement mode, so it costs nothing when the tool is not in use — but I would welcome a steer on whether that is the right home for it, which is question 1 on the issue.