# -*- 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-2021 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/object'
module HexaPDF
# Implementation of the PDF array type.
#
# This is mainly done to provide automatic resolution of indirect object references when using the
# #[] method. Therefore not all Array methods are implemented - use the #value directly if other
# methods are needed.
#
# See: PDF1.7 s7.3.6
class PDFArray < HexaPDF::Object
include Enumerable
# :call-seq:
# array[index] -> obj or nil
# array[start, length] -> new_array or nil
# array[range] -> new_array or nil
#
# Returns the value at the given index, or a subarray using the given +start+ and +length+, or a
# subarray specified by +range+.
#
# This method should be used instead of direct access to a value because it provides some
# advantages:
#
# * References are automatically resolved.
#
# * Returns the native Ruby object for values with class HexaPDF::Object. However, all
# subclasses of HexaPDF::Object are returned as is (it makes no sense, for example, to return
# the hash that describes the Catalog instead of the Catalog object).
#
# Note: Hash or Array values will always be returned as-is, i.e. not wrapped with Dictionary or
# PDFArray.
def [](arg1, arg2 = nil)
data = arg2 ? value[arg1, arg2] : value[arg1]
return if data.nil?
if arg2 || arg1.kind_of?(Range)
index = (arg2 ? arg1 : arg1.begin)
data.map! {|item| process_entry(item, index).tap { index += 1 } }
else
process_entry(data, arg1)
end
end
# Stores the data under the given index in the array.
#
# If the current value for this index has the class HexaPDF::Object (and only this, no
# subclasses) and the given data has not (including subclasses), the data is stored inside the
# HexaPDF::Object.
def []=(index, data)
if value[index].instance_of?(HexaPDF::Object) && !data.kind_of?(HexaPDF::Object) &&
!data.kind_of?(HexaPDF::Reference)
value[index].value = data
else
value[index] = data
end
end
# Returns the values at the given indices.
#
# See #[] for details
def values_at(*indices)
indices.map! {|index| self[index] }
end
# Append a value to the array.
def <<(data)
value << data
end
# Insert one or more values into the array at the given index.
def insert(index, *objects)
value.insert(index, *objects)
end
# Deletes the value at the given index.
def delete_at(index)
value.delete_at(index)
end
# Deletes all values from the PDFArray that are equal to the given object.
#
# Returns the last deleted item, or +nil+ if no matching item is found.
def delete(object)
value.delete(object)
end
# :call-seq:
# array.slice!(index) -> obj or nil
# array.slice!(start, length) -> new_array or nil
# array.slice!(range) -> new_array or nil
#
# Deletes the element(s) given by an index (and optionally a length) or by a range, and returns
# them or +nil+ if the index is out of range.
def slice!(arg1, arg2 = nil)
data = value.slice!(arg1, *arg2)
if arg2 || arg1.kind_of?(Range)
data.map! {|item| process_entry(item) }
else
process_entry(data)
end
end
# :call-seq:
# array.reject! {|item| block } -> array or nil
# array.reject! -> Enumerator
#
# Deletes all elements from the array for which the block returns +true+. If no changes were
# done, returns +nil+.
def reject!
value.reject! {|item| yield(process_entry(item)) }
end
# :call-seq:
# array.index(obj) -> int or nil
# array.index {|item| block } -> int or nil
# array.index -> Enumerator
#
# Returns the index of the first object such that object is == to +obj+, or, if a block is
# given, the index of the first object for which the block returns +true+.
def index(*obj, &block)
find_index(*obj, &block)
end
# Returns the number of elements in the array.
def length
value.length
end
alias size length
# Returns +true+ if the array has no elements.
def empty?
value.empty?
end
# :call-seq:
# array.each {|value| block} -> array
# array.each -> Enumerator
#
# Calls the given block once for every value of the array.
#
# Note that the yielded value is already preprocessed like in #[].
def each
return to_enum(__method__) unless block_given?
value.each_index {|index| yield(self[index]) }
self
end
# Returns an array containing the preprocessed values (like in #[]).
def to_ary
each.to_a
end
private
# Ensures that the value is useful for a PDFArray.
def after_data_change # :nodoc:
super
data.value ||= []
unless value.kind_of?(Array)
raise ArgumentError, "A PDF array object needs an array value, not a #{value.class}"
end
end
# Processes the given array entry with index +index+.
def process_entry(data, index = nil)
if data.kind_of?(HexaPDF::Reference)
data = document.deref(data)
value[index] = data if index
end
if data.instance_of?(HexaPDF::Object) || (data.kind_of?(HexaPDF::Object) && data.value.nil?)
data = data.value
end
data
end
end
end