Repository navigation
fix(save): apply the annotation opacity inside the vector appearance stream - #538
philip-tam wants to merge 1 commit into
Conversation
…stream A polygon/filled area with 40% opacity was saved with /CA 0.4 but an appearance stream that drew the fill and border at full strength with no transparency state. Viewers that show the stored appearance then display it at 100% until the opacity is edited and the appearance regenerated. attachVectorAP now reads the annotation /CA, adds an /GSo ExtGState (ca and CA) to the appearance resources and applies it first; a separate fill alpha (/GSf) is multiplied by it. The stored expectation of the dimension snapshot test with /CA 0.8 gains the same state.
|
Thank you for this, @philip-tam. The opacity problem it describes is already fixed on main by #512: |
Thanks for the explanation and for checking the interaction with the existing fix in #512. I understand why this PR should be closed to avoid applying the annotation opacity twice. I also appreciate the confirmation that my other PRs are included in release 2026.42.0. Thanks for reviewing and integrating those contributions! |
Summary
/CA 0.4but an appearance stream (/AP /N) that drew the fill and border at full strength, with no transparency state in its resources. Viewers that show the stored appearance instead of regenerating it from/CA(e.g. a PDF editor like Acrobat) display it at 100% until the opacity is edited there, which makes the viewer rebuild the appearance (and then it shows 40%).attachVectorAPnow reads the annotation's/CA, adds a/GSoExtGState (caandCA) to the appearance resources and applies it first (/GSo gs). A separate fill alpha (/GSf, fill-only) is multiplied by it. This is the same structure the viewer itself writes after the opacity is edited.attachVectorAP, and only when/CAis below 1; nothing changes for opaque annotations.Test plan
ap-opacity(plan: no opacity, 40%, fill alpha combined) andap-opacity-opslaan(saves a filled area with/CA 0.4with pdf-lib, reloads the bytes and checks/GSo gsfirst in the content andca/CA0.4 in the resources; opaque and/CA 1unchanged; fill alpha multiplied)/CA 0.8gains the same/GSostate (only that case changes)