# -*- encoding : utf-8 -*-
# frozen_string_literal: true

require 'zlib'
require 'securerandom'
require 'strscan'
require 'matrix'
require 'set'
require 'digest'

# require the RC4 Gem
require 'rc4'

require 'combine_pdf/api'
require 'combine_pdf/renderer'
require 'combine_pdf/page_methods'
require 'combine_pdf/basic_writer'
require 'combine_pdf/decrypt'
require 'combine_pdf/fonts'
require 'combine_pdf/filter'
require 'combine_pdf/parser'
require 'combine_pdf/pdf_public'
require 'combine_pdf/pdf_protected'
require 'combine_pdf/exceptions'

# require 'combine_pdf/operations'

require 'combine_pdf/version'

# This is a pure ruby library to combine/merge, stmap/overlay and number PDF files - as well as to create tables (ment for indexing combined files).
#
# You can also use this library for writing basic text content into new or existing PDF files (For authoring new PDF files look at the Prawn ruby library).
#
# here is the most basic application for the library, a one-liner that combines the PDF files and saves them:
#   (CombinePDF.new("file1.pdf") << CombinePDF.new("file2.pdf") << CombinePDF.new("file3.pdf")).save("combined.pdf")
#
# == Loading PDF data
# Loading PDF data can be done from file system or directly from the memory.
#
# Load data from a file:
#   pdf = CombinePDF.load("file.pdf")
# parse PDF files from memory:
#   pdf = CombinePDF.parse(pdf_data)
#
# == Combine/Merge PDF files or Pages
# To combine PDF files (or data):
#   pdf = CombinePDF.new
#   pdf << CombinePDF.load("file1.pdf")
#   pdf << CombinePDF.load("file2.pdf")
#   pdf.save "combined.pdf"
#
# It is possible to add only specific pages.
# in this example, only even pages will be added:
#   pdf = CombinePDF.new
#   i = 0
#   CombinePDF.load("file.pdf").pages.each do |page|
#     i += 1
#     pdf << page if i.even?
#   end
#   pdf.save "even_pages.pdf"
# Notice that adding the whole file is faster then adding each page seperately.
# == Add content to existing pages (Stamp / Watermark)
# It is possible "stamp" one PDF page using another PDF page. In this example, a company logo will be stamped over each page:
#   company_logo = CombinePDF.load("company_logo.pdf").pages[0]
#   pdf = CombinePDF.load "content_file.pdf"
#   pdf.pages.each {|page| page << company_logo}
#   pdf.save "content_with_logo.pdf"
# Notice the << operator is on a page and not a PDF object. The << operator acts differently on PDF objects and on Pages.
# == Page Numbering
# It is possible to number the pages. in this example we will add very simple numbering:
#   pdf = CombinePDF.load "file_to_number.pdf"
#   pdf.number_pages
#   pdf.save "file_with_numbering.pdf"
#
# numbering can be done with many different options, with different formating, with or without a box object, different locations on each page and even with opacity values.
# == Writing Content
# page numbering actually adds content using the PDFWriter object (a very basic writer).
#
# in this example, all the PDF pages will be stamped, along the top, with a red box, with blue text, stating "Draft, page #".
# here is the easy way (we can even use "number_pages" without page numbers, if we wish):
#   pdf = CombinePDF.load "file_to_stamp.pdf"
#   pdf.number_pages number_format: " - Draft, page %d - ", number_location: [:top], font_color: [0,0,1], box_color: [0.4,0,0], opacity: 0.75, font_size:16
#   pdf.save "draft.pdf"
#
# in this example we will add a first page with the word "Draft", in red over a colored background:
#
#   pdf = CombinePDF.load "file.pdf"
#   pdf_first_page = pdf.pages[0]
#   mediabox = page[:CropBox] || page[:MediaBox] #copy page size
#   title_page = CombinePDF.create_page mediabox #make title page same size as first page
#   title_page.textbox "DRAFT", font_color: [0.8,0,0], font_size: :fit_text, box_color: [1,0.8,0.8], opacity: 1
#   pdf >> title_page # the >> operator adds pages at the begining
#   pdf.save "draft.pdf"
#
# font support for the writer is still in the works and is limited to extracting know fonts by location of the 14 standard fonts.
#
# == Resizing pages
#
# Using the {http://www.prepressure.com/library/paper-size PDF standards for page sizes}, it is now possible to resize
# existing PDF pages, as well as stretch and shrink their content to the new size.
#
#   pdf = CombinePDF.load "file.pdf"
#   a4_size = [0, 0, 595, 842]
#   # keep aspect ratio intact
#   pdf.pages.each {|p| p.resize a4_size}
#   pdf.save "a4.pdf"
#
#   pdf = CombinePDF.load "file.pdf"
#   a4_squared = [0, 0, 595, 595]
#   # stretch or shrink content to fit new size
#   pdf.pages.each {|p| p.resize a4_squared, false}
#   pdf.save "square.pdf"
#
# == Decryption & Filters
#
# Some PDF files are encrypted and some are compressed (the use of filters)... not all files can be opened, merged, stamped or used and stamps.
# == Comments and file structure
#
# If you want to help with the code, please be aware:
#
# The code itself should be very straight forward, but feel free to ask whatever you want.
#
# == Credit
#
# Caige Nichols wrote an amazing RC4 gem which I reference in my code.
# Credit to his wonderful is given here. Please respect his license and copyright... and mine.
#
# == License
#
# MIT
module CombinePDF
end

#########################################################
# this file is part of the CombinePDF library and the code
# is subject to the same license (MIT).
#########################################################
# PDF object types cross reference:
# Indirect objects, references, dictionaries and streams are Hash
# arrays are Array
# strings are String
# names are Symbols (String.to_sym)
# numbers are Integer or Float (Numeric)
# boolean are TrueClass or FalseClass

## test performance with:
## puts Benchmark.measure { pdf = CombinePDF.new(file); pdf.save "test.pdf" } # PDFEditor.new_pdf
## demo: file_name = "~/Ruby/pdfs/encrypted.pdf"; pdf=0; puts Benchmark.measure { pdf = CombinePDF.new(file_name); pdf.save "test.pdf" }
## at the moment... my code is terribly slow for larger files... :(
## The file saving is solved (I hope)... but file loading is an issue.
##  pdf.each_object {|obj| puts "Stream length: #{obj[:raw_stream_content].length} was registered as #{obj[:Length].is_a?(Hash)? obj[:Length][:referenced_object][:indirect_without_dictionary] : obj[:Length]}" if obj[:raw_stream_content] }
##  pdf.objects.each {|obj| puts "#{obj.class.name}: #{obj[:indirect_reference_id]}, #{obj[:indirect_generation_number]} is: #{obj[:Type] || obj[:indirect_without_dictionary]}" }
##  puts Benchmark.measure { 1000.times { (CombinePDF::PDFOperations.get_refernced_object pdf.objects, {indirect_reference_id: 100, indirect_generation_number:0}).object_id } }
##  puts Benchmark.measure { 1000.times { (pdf.objects.select {|o| o[:indirect_reference_id]== 100 && o[:indirect_generation_number] == 0})[0].object_id } }
## puts Benchmark.measure { {}.tap {|out| pdf.objects.each {|o| out[ [o[:indirect_reference_id], o[:indirect_generation_number] ] ] = o }} }
##
#### local test for CombinePDF
## file = "/Users/2Be/Ruby/pdfs/encrypted.pdf"
## puts Benchmark.measure { 1000.times { pdf = CombinePDF.new(file); pdf.save "test.pdf" } }
### gives : 2.540000   0.140000   2.680000 (  2.696524)
## puts Benchmark.measure { pdf = CombinePDF.new() ; 1000.times { pdf << CombinePDF.new(file) } ; pdf.save "test.pdf" }
### gives: 11.770000   0.090000  11.860000 ( 11.879411) #why the difference? NOT the object reference rebuilding...
### file size: 7Kb success
###### gives: 7.440000   0.100000   7.540000 (  7.536460) (!!!) with draft file size 8kb
##
#### local test by pdftk
## pdftk_path = '/Users/2Be/Ruby/pdfs/pdftk_lib/bin/pdftk'
## file_array = []
## 1000.times { file_array << file }
## puts Benchmark.measure { system ( pdftk_path + " '" + file_array.join("' '") + "' input_pw '' output 'test.pdf'" ) }
### gives: 0.000000   0.000000   3.250000 (  3.244724)
### FAILS with no output, unwilling to decrypt.
###### gives:  0.000000   0.000000   2.640000 (  2.661801) with draft file size 1.3MB (!!)
#### local test by pyton
## pyton_path = '/Users/2Be/Ruby/pdfs/pdftk_lib/join.py'
## file_array = []
## 1000.times { file_array << file }
## puts Benchmark.measure { system ( pyton_path + " -o 'test.pdf' '#{file_array.join "' '"}' " ) }
### gives 0.000000   0.000000   1.010000 (  1.147135)
### file merge FAILS with 1,000 empty pages (undecrypted)
####### gives: 0.000000   0.000000   1.770000 (  1.775513) with draft. file size 4.9MB (!!!)
