-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[api-minor] Add support for toggling of Optional Content in the viewer (issue 12096) #12170
Changes from all commits
9ba5f9f
2393443
66aabe3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -26,42 +26,44 @@ class OptionalContentConfig { | |||||||||||||||||
constructor(data) { | ||||||||||||||||||
this.name = null; | ||||||||||||||||||
this.creator = null; | ||||||||||||||||||
this.groups = new Map(); | ||||||||||||||||||
this._order = null; | ||||||||||||||||||
this._groups = new Map(); | ||||||||||||||||||
|
||||||||||||||||||
if (data === null) { | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
this.name = data.name; | ||||||||||||||||||
this.creator = data.creator; | ||||||||||||||||||
this._order = data.order; | ||||||||||||||||||
for (const group of data.groups) { | ||||||||||||||||||
this.groups.set( | ||||||||||||||||||
this._groups.set( | ||||||||||||||||||
group.id, | ||||||||||||||||||
new OptionalContentGroup(group.name, group.intent) | ||||||||||||||||||
); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
if (data.baseState === "OFF") { | ||||||||||||||||||
for (const group of this.groups) { | ||||||||||||||||||
for (const group of this._groups) { | ||||||||||||||||||
group.visible = false; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
for (const on of data.on) { | ||||||||||||||||||
this.groups.get(on).visible = true; | ||||||||||||||||||
this._groups.get(on).visible = true; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
for (const off of data.off) { | ||||||||||||||||||
this.groups.get(off).visible = false; | ||||||||||||||||||
this._groups.get(off).visible = false; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
isVisible(group) { | ||||||||||||||||||
if (group.type === "OCG") { | ||||||||||||||||||
if (!this.groups.has(group.id)) { | ||||||||||||||||||
if (!this._groups.has(group.id)) { | ||||||||||||||||||
warn(`Optional content group not found: ${group.id}`); | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
return this.groups.get(group.id).visible; | ||||||||||||||||||
return this._groups.get(group.id).visible; | ||||||||||||||||||
} else if (group.type === "OCMD") { | ||||||||||||||||||
// Per the spec, the expression should be preferred if available. Until | ||||||||||||||||||
// we implement this, just fallback to using the group policy for now. | ||||||||||||||||||
|
@@ -71,44 +73,44 @@ class OptionalContentConfig { | |||||||||||||||||
if (!group.policy || group.policy === "AnyOn") { | ||||||||||||||||||
// Default | ||||||||||||||||||
for (const id of group.ids) { | ||||||||||||||||||
if (!this.groups.has(id)) { | ||||||||||||||||||
if (!this._groups.has(id)) { | ||||||||||||||||||
warn(`Optional content group not found: ${id}`); | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
if (this.groups.get(id).visible) { | ||||||||||||||||||
if (this._groups.get(id).visible) { | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
return false; | ||||||||||||||||||
} else if (group.policy === "AllOn") { | ||||||||||||||||||
for (const id of group.ids) { | ||||||||||||||||||
if (!this.groups.has(id)) { | ||||||||||||||||||
if (!this._groups.has(id)) { | ||||||||||||||||||
warn(`Optional content group not found: ${id}`); | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
if (!this.groups.get(id).visible) { | ||||||||||||||||||
if (!this._groups.get(id).visible) { | ||||||||||||||||||
return false; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
return true; | ||||||||||||||||||
} else if (group.policy === "AnyOff") { | ||||||||||||||||||
for (const id of group.ids) { | ||||||||||||||||||
if (!this.groups.has(id)) { | ||||||||||||||||||
if (!this._groups.has(id)) { | ||||||||||||||||||
warn(`Optional content group not found: ${id}`); | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
if (!this.groups.get(id).visible) { | ||||||||||||||||||
if (!this._groups.get(id).visible) { | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
return false; | ||||||||||||||||||
} else if (group.policy === "AllOff") { | ||||||||||||||||||
for (const id of group.ids) { | ||||||||||||||||||
if (!this.groups.has(id)) { | ||||||||||||||||||
if (!this._groups.has(id)) { | ||||||||||||||||||
warn(`Optional content group not found: ${id}`); | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
if (this.groups.get(id).visible) { | ||||||||||||||||||
if (this._groups.get(id).visible) { | ||||||||||||||||||
return false; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
@@ -120,6 +122,35 @@ class OptionalContentConfig { | |||||||||||||||||
warn(`Unknown group type ${group.type}.`); | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
setVisibility(id, visible = true) { | ||||||||||||||||||
if (!this._groups.has(id)) { | ||||||||||||||||||
warn(`Optional content group not found: ${id}`); | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
this._groups.get(id).visible = !!visible; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
getOrder() { | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's already very common, in the API, for various methods to return Furthermore, if an empty array was returned that wouldn't really help all that much as far as I'm concerned, since you'd then need to check for pdf.js/web/pdf_outline_viewer.js Lines 121 to 124 in 37c5660
pdf.js/web/pdf_attachment_viewer.js Lines 153 to 156 in 37c5660
|
||||||||||||||||||
if (!this._groups.size) { | ||||||||||||||||||
return null; | ||||||||||||||||||
} | ||||||||||||||||||
if (this._order) { | ||||||||||||||||||
return this._order.slice(); | ||||||||||||||||||
} | ||||||||||||||||||
return Array.from(this._groups.keys()); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
getGroups() { | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks to be unused, is this needed in the future? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I figured that it'd be helpful to provide a way to get all of the groups directly, in cases where an API user don't care about the |
||||||||||||||||||
if (!this._groups.size) { | ||||||||||||||||||
return null; | ||||||||||||||||||
} | ||||||||||||||||||
return Object.fromEntries(this._groups); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
getGroup(id) { | ||||||||||||||||||
return this._groups.get(id) || null; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
export { OptionalContentConfig }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first object in a subarray of the
Order
entry can be also an OCG instead of a string. The OCG becomes an expandable node. If it is set to invisible, all child nodes should get grayed out. See these files:PDFill_layers.pdf
pdflib_hierarchical_layers.pdf
Also the mutually exclusive feature of radio buttons (
RBGroups
) and locked OCGs seems to be still unimplemented, if I'm right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very interesting, I hadn't seen those types of PDF documents when writing the patches.
I'll try to see how difficult implementing that would be; or if we could possibly defer that for a follow-up PR.
Yes, those weren't implemented in PR #12095 and here I simply implemented the bare minimum of additional "core" functionality to get the viewer integration to work OK.
I'd really prefer if those things were implemented separately though, since this PR is already quite large (and those features also don't seem that common); there's also the question if we'd even want to respect the "locked OCGs" in the viewer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having looking (quickly) at this, I'd really prefer if we could defer that to a follow-up patch instead.