feat(exports): render instructor endorsements in the PDF remarks column - #205
Merged
Conversation
A flight locked by a completed FlightSignature now prints that sign-off
with the row it attests: the instructor's captured ink, their name and
"No. <credential> · <signed date>", on a tinted panel with a gold edge
marker in the right-hand part of the REMARKS AND ENDORSEMENTS cell. A
printed logbook previously dropped its endorsements entirely, which is
exactly what an authority looks for.
The block adapts to the column it lands in, because the same remarks cell
is 111 mm wide on an FAA spread and 21 mm on an EASA single-page A5. Two
choices are worth recording:
- Where ink and name cannot both fit, the name takes the space. An
unattributed mark tells an inspector nothing, so text beats ink at
the bottom of the ladder.
- Where the credential line is squeezed, the signing date is dropped
rather than the credential number cut off — the number is what
identifies the signer.
Names abbreviate to initials-plus-surname ("Katrin Vogelsang" ->
"K. Vogelsang") before fitText resorts to truncating them.
Rasters are decoded, box-downscaled to at most 480x200 px and re-encoded
as clean 8-bit PNG, so fpdf never meets an interlaced or 16-bit image and
a career export does not carry hundreds of megabytes of ink. A
DecodeConfig pixel-count check rejects a decompression bomb before it is
decoded; a per-document byte budget caps total embedded raster. Anything
unusable costs the ink and not the signer's details, and fpdf's error
state is cleared so one bad image cannot poison the whole document.
Signatures reach the renderers through FlightSignatureService, which
follows each flight's signature_id lock pointer and filters to rows the
caller owns — voiding a signature clears the pointer and so removes the
block from the next export.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gosec G115 flagged uint8(a >> 8) in downscaleNRGBA: the averaged alpha is a uint64 the analyser cannot bound, even though every channel RGBA() returns is at most 0xffff. The colour channels already narrowed through a clamp, which gosec accepts, so the alpha now takes the same path via a shared to8 helper rather than converting inline. Output is unchanged — the clamp never fires for valid input, and the embedded rasters of a rendered sample are byte-identical. Co-Authored-By: Claude Opus 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.
What
A flight locked by a completed
FlightSignaturenow prints that sign-off with the row it attests. In the right-hand part of theREMARKS AND ENDORSEMENTScell: the instructor's captured ink, their name, andNo. <credential> · <signed date>, on a tinted panel with a gold edge marker and a baseline rule under the ink. The remark text keeps the space to its left.Why
A printed logbook dropped its endorsements entirely — the sign-off existed only in the app. The endorsement beside the flight is exactly what an EASA or FAA inspector looks for on the page.
How it adapts
The same remarks cell is 111 mm wide on an FAA spread and 21 mm on an EASA single-page A5, so the block degrades down a ladder:
rows_per_page=60)K. VogelsangTwo judgment calls worth a reviewer's attention:
Names abbreviate to initials-plus-surname (
Katrin Vogelsang→K. Vogelsang) beforefitTextresorts to truncating them.Raster handling
Signature images are decoded, box-downscaled to at most 480×200 px and re-encoded as clean 8-bit PNG, so
fpdfnever meets an interlaced or 16-bit image and a career export does not carry hundreds of megabytes of ink. Each signature is embedded once per document.DecodeConfigpixel-count check rejects a decompression bomb before it is decoded (the stored image is not validated as PNG at upload time).fpdf's error state is cleared so one bad image cannot poison the whole document.Data path
FlightSignatureService.ListSignedForFlightsfollows each flight'ssignature_idlock pointer and filters tocompletedrows the caller owns, backed by a new batchedFlightSignatureRepository.GetByIDs. Voiding a signature clears the pointer, so the block disappears from the next export. A lookup failure costs the endorsements, not the export.No schema change, no spec change —
make route-checkreports the same 158 operations.Reviewer notes
drawEndorsementleft thefpdfcursor wherever its last cell ended, so the row after every signed row printed at the wrong position — visible as stray cells outside the right margin.drawDataRownow saves and restores the cursor around it.drawDataRowgained aflightIDparameter, which is the bulk of the test-file churn.TestGenerateSamplePDFsemits two dense_60rowsvariants for reviewing the tightest case:Verification
Every format × layout × page size was rendered and inspected at 200–300 dpi.
make fmt,make test,make migrate-check,make route-check— clean.scripts/run-e2e-tests.shgreen, including the newTestExportPDFRendersInstructorEndorsement(signed flight prints signer + embeds the ink for both regulations; voiding removes both).make lintreports the same 57 pre-existing issues before and after this branch (verified by stashing); this change adds none.Docs
docs/API.md(theGET /exports/pdfsection) anddocs/FEATURES.md(the Export bullet) both describe the endorsement block, its adaptation ladder and the raster limits.Not in scope
A certification appendix page listing every signature in full (signer, credential, signed-at UTC, method, signature ID, ink at legible size) would be the strongest verifiable-by-an-authority surface, but it is beyond "render under remarks" and is left for a follow-up.
🤖 Generated with Claude Code