[#712] Optional Content (aka PDF Layers) implementation #819
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.
This is an implementation of the missing Optional Content / PDF Layers functionality (#712).
In order to introduce the support to layers, I tried to leverage the existing code base as much as possible:
on input, the inclusion of HTML contents into layers is defined via non-inheritable extension CSS properties (
-fs-ocg-*
for contents belonging to optional content groups and-fs-ocm-*
for contents belonging to optional content memberships).NOTE: An alternative implementation could decouple these definitions from common rulesets to extension at-rules (
@-fs-ocg
for optional content groups and@-fs-ocm
for optional content memberships), achieving a bit tidier stylesheets at the cost of dedicated structures beside existing standard at-rules (such as@font-face
and@import
rules) incom.openhtmltopdf.css.sheet.Stylesheet
.on output, during page painting, existing tagging calls (see
PdfBoxFastOutputDevice.startStructure(..)
andendStructure(..)
) are intercepted, because of their semantically-compatible granularity, to inject layers inside the content stream. To avoid ghost layer fragments (not all tagging calls wrap actual contents!), layer injection is lazily applied on actual content painting calls.Layer types:
simple layer (aka group):
identity (
-fs-ocg-id
), which is used for reference (as parent of other groups or member of memberships).label (
-fs-ocg-label
), which maps toPDOptionalContentGroup.getName()
(seeName
entry in the Optional Content Group dictionary) and is displayed in the viewer's layer tree.visibility (
-fs-ocg-visibility={visible|hidden}
), which maps toPDOptionalContentProperties.isGroupEnabled(..)
(seeBaseState
,ON
,OFF
entries ofD
entry of the document's Optional Content Configuration dictionary).parent (
-fs-ocg-parent={%ocg-id%}
), which maps toOrder
entry ofD
entry of the document's Optional Content Configuration dictionary for nesting into the viewer's layer tree -- unfortunately, arbitrary nesting seems not to be natively supported by currently-used PDFBox version (2.0), as adding a group viaPDOptionalContentProperties.addGroup(..)
automatically builds a flat list insideOrder
entry instead.compound layer (aka membership):
identity (
-fs-ocm-id
), which is used for internal reference.visibility policy (
-fs-ocm-visible={all-visible|all-hidden|any-visible|any-hidden}
), which maps toPDOptionalContentMembershipDictionary.getVisibilityPolicy()
(seeP
entry of Optional Content Membership dictionary).members (
-fs-ocm-ocgs={%ocg-id%...}
), which map toPDOptionalContentMembershipDictionary.getOCGs()
(seeOCGs
entry of Optional Content Membership dictionary).For the sake of consistency, each content inherits the full layer hierarchy of its ancestor nodes. For example,
that paragraph element is rendered in the following way inside the content stream (NOTE: layer resource name assignment is an implementation detail internal to the PDF library (PDFBox); for clarity, here we assume that
/oc2
maps to layerocg2
and/oc1
maps to layerocg1
):