v0.45.0 — rotation composes, and its angle is readable
Rotation composes now, and an annotation's angle can be read
Two fixes to annotation rotation, shipped together because they interlock.
Turning something repeatedly no longer grows it
Rotating an annotation four times by 15° drew it 1.93× wider and 1.42×
taller than rotating it once by 60°. Every additional turn multiplied it
again.
The rectangle bounding an annotation was derived from the previous
rectangle. That is correct for one rotation and wrong for every one after it:
the second turn bounded an already-enlarged box while the artwork's own matrix
had only accumulated the angle, and ISO 32000-1 §12.5.5's placement step then
scaled the artwork up to fill the surplus.
The rectangle is now derived from the artwork, through one of three rules,
and rotate-annotation reports which one it used (rect_derived=):
| rule | when | composes? |
|---|---|---|
artwork |
the annotation has an appearance stream | yes |
geometry |
no appearance, but /L /Vertices /QuadPoints /CL /InkList |
yes |
previous-rect |
neither | no — see below |
N turns totalling θ now produce the same drawn size as one turn of θ,
asserted to a placement scale of exactly 1.0 after 1, 2, 4, 8 and 24 turns.
One case is still not composable and now says so. An annotation with
neither an appearance stream nor rotatable geometry — a /Square or /Circle
with no /AP — is its rectangle, and §12.5.2 requires that rectangle
upright, so there is nowhere in the file its orientation could be recorded. It
genuinely does still grow on each turn. The CLI warns, and the API reports it,
so a caller can refuse rather than damage the object.
An annotation's rotation angle can now be read, and set directly
Nothing exposed an annotation's orientation, so it could be written and never
read back — a properties field would have had to display a guess, and a
selection outline could only be an upright box visibly larger than a turned
object.
Annotation::appearance_matrix— the appearance's/Matrix, raw.Annotation::appearance_rotation_degrees()— the angle, anticlockwise.
Returns nothing for a shear or a mirror: those are not angles, and reporting
the nearest one would seed a field the operator is about to commit.EditSession::set_annotation_rotation()androtate-annotation --absolute
— set the angle absolutely rather than by a delta, which is what a typed
properties field needs. Idempotent. Refuses rather than assuming zero
when the current angle cannot be read.pdfcer_render::annot::appearance_placement()— §12.5.5's placement
quadrilateral, made public, so a shell can draw a selection outline in the
object's own orientation instead of re-implementing a normative algorithm.
Also
The verb's own documentation claimed "the artwork does not grow; only the
rectangle that bounds it does". That was true of one rotation and false of
the next. All three copies are corrected, with the superseded wording kept
legible rather than quietly deleted.
Verified: fmt, clippy --all-targets --all-features -D warnings, the
wasm32 target check, the no-GUI/no-network dependency check, and all
document/CI gates green; CI green on the tagged commit; the portable folder
copied to a fresh path and the fix reproduced there.
sha256 of the zip is in the accompanying .sha256 file.