# -*- encoding: utf-8; frozen_string_literal: true -*-
#
#--
# This file is part of HexaPDF.
#
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
# Copyright (C) 2014-2024 Thomas Leitner
#
# HexaPDF is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License version 3 as
# published by the Free Software Foundation with the addition of the
# following permission added to Section 15 as permitted in Section 7(a):
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
# INFRINGEMENT OF THIRD PARTY RIGHTS.
#
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HexaPDF. If not, see .
#
# The interactive user interfaces in modified source and object code
# versions of HexaPDF must display Appropriate Legal Notices, as required
# under Section 5 of the GNU Affero General Public License version 3.
#
# In accordance with Section 7(b) of the GNU Affero General Public
# License, a covered work must retain the producer line in every PDF that
# is created or manipulated using HexaPDF.
#
# If the GNU Affero General Public License doesn't fit your need,
# commercial licenses are available at .
#++
require 'hexapdf/dictionary'
module HexaPDF
module Type
# Represents an optional content configuration dictionary.
#
# This dictionary is used for the /D and /Configs entries in the optional content properties
# dictionary. It configures the states of the OCGs as well as defines how those states may be
# changed by a PDF processor.
#
# See: PDF2.0 s8.11.4.3
class OptionalContentConfiguration < Dictionary
# Represents an optional content usage application dictionary.
#
# This dictionary is used for the elements in the /AS array of an optional content
# configuration dictionary. It specifies how a PDF processor should use the usage entries of
# OCGs to automatically change their state based on external factors (like magnifacation
# factor or language).
#
# See: PDF2.0 s8.11.4.4
class UsageApplication < Dictionary
define_type :XXOCUsageApplication
define_field :Event, type: Symbol, required: true, allowed_values: [:View, :Print, :Export]
define_field :OCGs, type: PDFArray, default: []
define_field :Category, type: PDFArray, required: true
end
define_type :XXOCConfiguration
define_field :Name, type: String
define_field :Creator, type: String
define_field :BaseState, type: Symbol, default: :ON, allowed_values: [:ON, :OFF, :Unchanged]
define_field :ON, type: PDFArray
define_field :OFF, type: PDFArray
define_field :Intent, type: [Symbol, PDFArray], default: :View
define_field :AS, type: PDFArray
define_field :Order, type: PDFArray
define_field :ListMode, type: Symbol, default: :AllPages,
allowed_values: [:AllPages, :VisiblePages]
define_field :RBGroups, type: PDFArray
define_field :Locked, type: PDFArray, default: []
# :call-seq:
# configuration.ocg_state(ocg) -> state
# configuration.ocg_state(ocg, state) -> state
#
# Returns the state (+:on+, +:off+ or +nil+) of the optional content group if the +state+
# argument is not given. Otherwise sets the state of the OCG to the given state value
# (+:on+/+:ON+ or +:off+/+:OFF+).
#
# The value +nil+ is only returned if the state is not defined by the configuration dictionary
# (which may only be the case if the configuration dictionary is not the default configuration
# dictionary).
def ocg_state(ocg, state = nil)
if state.nil?
case self[:BaseState]
when :ON then self[:OFF]&.include?(ocg) ? :off : :on
when :OFF then self[:ON]&.include?(ocg) ? :on : :off
else self[:OFF]&.include?(ocg) ? :off : (self[:ON]&.include?(ocg) ? :on : nil)
end
elsif state&.downcase == :on
(self[:ON] ||= []) << ocg unless self[:ON]&.include?(ocg)
self[:OFF].delete(ocg) if key?(:OFF)
elsif state&.downcase == :off
(self[:OFF] ||= []) << ocg unless self[:OFF]&.include?(ocg)
self[:ON].delete(ocg) if key?(:ON)
else
raise ArgumentError, "Invalid value #{state.inspect} for state argument"
end
end
# Returns +true+ if the given optional content group is on.
def ocg_on?(ocg)
ocg_state(ocg) == :on
end
# Makes the given optional content group visible in an interactive PDF processor's user
# interface.
#
# The OCG is always added to the end of the specified +path+ or, if +path+ is not specified,
# the top level.
#
# The optional argument +path+ specifies the strings or OCGs under which the given OCG should
# hierarchically be nested. A string is used as a non-selectable label, an OCG reflects an
# actual nesting of the involved OCGs.
#
# Examples:
#
# configuration.add_ocg_to_ui(ocg) # Add the OCG as top-level item
# configuration.add_ocg_to_ui(ocg, path: 'Debug') # Add the OCG under the label 'Debug'
# # Add the OCG under the label 'Page1' which is under the label 'Debug'
# configuration.add_ocg_to_ui(ocg, path: ['Debug', 'Page1'])
# configuration.add_ocg_to_ui(ocg, path: other_ocg) # Add the OCG under the other OCG
def add_ocg_to_ui(ocg, path: nil)
array = self[:Order] ||= []
path = Array(path)
until path.empty?
item = path.shift
index = array.index do |entry|
if (entry.kind_of?(Array) || entry.kind_of?(PDFArray)) && item.kind_of?(String)
entry.first == item
else
entry == item
end
end
if item.kind_of?(String)
unless index
array << [item]
index = -1
end
array = array[index]
else
unless index
array << item << []
index = -2
end
unless array[index + 1].kind_of?(Array) || array[index + 1].kind_of?(PDFArray)
array.insert(index + 1, [])
end
array = array[index + 1]
end
end
array << ocg
end
end
end
end