add pdfium rotate

pull/669/merge
Pete Matsyburka 1 month ago
parent 1fa953104a
commit a9dd200919

@ -192,6 +192,71 @@ class Pdfium
attach_function :FPDFDOC_ExitFormFillEnvironment, [:FPDF_FORMHANDLE], :void
attach_function :FPDF_FFLDraw, %i[FPDF_FORMHANDLE FPDF_BITMAP FPDF_PAGE int int int int int int], :void
attach_function :FPDFPage_Flatten, %i[FPDF_PAGE int], :int
FLAT_NORMALDISPLAY = 0
FLAT_PRINT = 1
FLATTEN_FAIL = 0
FLATTEN_SUCCESS = 1
FLATTEN_NOTHINGTODO = 2
# rubocop:disable Naming/ClassAndModuleCamelCase
class FS_MATRIX < FFI::Struct
layout :a, :float,
:b, :float,
:c, :float,
:d, :float,
:e, :float,
:f, :float
end
# rubocop:enable Naming/ClassAndModuleCamelCase
attach_function :FPDFPage_GetRotation, [:FPDF_PAGE], :int
attach_function :FPDFPage_SetRotation, %i[FPDF_PAGE int], :void
attach_function :FPDFPage_TransFormWithClip, %i[FPDF_PAGE pointer pointer], :int
attach_function :FPDFPage_TransformAnnots, %i[FPDF_PAGE double double double double double double], :void
attach_function :FPDFPage_GenerateContent, [:FPDF_PAGE], :int
attach_function :FPDFPage_GetMediaBox, %i[FPDF_PAGE pointer pointer pointer pointer], :int
attach_function :FPDFPage_SetMediaBox, %i[FPDF_PAGE float float float float], :void
attach_function :FPDFPage_GetCropBox, %i[FPDF_PAGE pointer pointer pointer pointer], :int
attach_function :FPDFPage_SetCropBox, %i[FPDF_PAGE float float float float], :void
attach_function :FPDFPage_GetBleedBox, %i[FPDF_PAGE pointer pointer pointer pointer], :int
attach_function :FPDFPage_SetBleedBox, %i[FPDF_PAGE float float float float], :void
attach_function :FPDFPage_GetTrimBox, %i[FPDF_PAGE pointer pointer pointer pointer], :int
attach_function :FPDFPage_SetTrimBox, %i[FPDF_PAGE float float float float], :void
attach_function :FPDFPage_GetArtBox, %i[FPDF_PAGE pointer pointer pointer pointer], :int
attach_function :FPDFPage_SetArtBox, %i[FPDF_PAGE float float float float], :void
PAGE_BOX_ACCESSORS = [
%i[FPDFPage_GetMediaBox FPDFPage_SetMediaBox],
%i[FPDFPage_GetCropBox FPDFPage_SetCropBox],
%i[FPDFPage_GetBleedBox FPDFPage_SetBleedBox],
%i[FPDFPage_GetTrimBox FPDFPage_SetTrimBox],
%i[FPDFPage_GetArtBox FPDFPage_SetArtBox]
].freeze
# rubocop:disable Naming/ClassAndModuleCamelCase
class FPDF_FILEWRITE < FFI::Struct
layout :version, :int,
:WriteBlock, :pointer
end
# rubocop:enable Naming/ClassAndModuleCamelCase
attach_function :FPDF_SaveAsCopy, %i[FPDF_DOCUMENT pointer ulong], :int
FPDF_INCREMENTAL = 1
FPDF_NO_INCREMENTAL = 2
FPDF_REMOVE_SECURITY = 3
attach_function :FPDF_CreateNewDocument, [], :FPDF_DOCUMENT
begin
attach_function :FPDF_ImportPages, %i[FPDF_DOCUMENT FPDF_DOCUMENT string int], :int
rescue FFI::NotFoundError
define_singleton_method(:FPDF_ImportPages) { |*| raise PdfiumError, 'FPDF_ImportPages is not available' } # rubocop:disable Naming/MethodName
end
FPDF_ERR_SUCCESS = 0
FPDF_ERR_UNKNOWN = 1
FPDF_ERR_FILE = 2
@ -257,6 +322,38 @@ class Pdfium
@page_count ||= Pdfium.FPDF_GetPageCount(@document_ptr)
end
def import_pages(src_doc)
ensure_not_closed!
result = Pdfium.FPDF_ImportPages(@document_ptr, src_doc.document_ptr, nil, page_count)
raise PdfiumError, 'Failed to import pages' if result.zero?
@page_count = nil
result
end
def self.create
doc_ptr = Pdfium.FPDF_CreateNewDocument()
if doc_ptr.null?
Pdfium.check_last_error('Failed to create new document')
raise PdfiumError, 'Failed to create new document'
end
doc = new(doc_ptr)
return doc unless block_given?
begin
yield doc
ensure
doc.close
end
end
def self.open_file(file_path, password = nil)
doc_ptr = Pdfium.FPDF_LoadDocument(file_path, password)
@ -318,6 +415,30 @@ class Pdfium
@pages[page_index] ||= Page.new(self, page_index)
end
def save(io, flags: Pdfium::FPDF_NO_INCREMENTAL)
ensure_not_closed!
file_write_mem = FFI::MemoryPointer.new(FPDF_FILEWRITE.size)
file_write_struct = FPDF_FILEWRITE.new(file_write_mem)
file_write_struct[:version] = 1
file_write_struct[:WriteBlock] = FFI::Function.new(:int, %i[pointer pointer ulong]) do |_, data, size|
io.write(data.read_bytes(size))
1
end
result = Pdfium.FPDF_SaveAsCopy(@document_ptr, file_write_mem, flags)
if result.zero?
Pdfium.check_last_error('Failed to save document')
raise PdfiumError, 'Failed to save document'
end
io
end
def close
return if closed?
@ -680,6 +801,88 @@ class Pdfium
@line_nodes = @line_nodes.sort { |a, b| a.endy == b.endy ? a.x <=> b.x : a.endy <=> b.endy }
end
def rotate
ensure_not_closed!
rotation = Pdfium.FPDFPage_GetRotation(page_ptr)
return false if rotation.zero?
l_ptr = FFI::MemoryPointer.new(:float)
b_ptr = FFI::MemoryPointer.new(:float)
r_ptr = FFI::MemoryPointer.new(:float)
t_ptr = FFI::MemoryPointer.new(:float)
has_crop = !Pdfium.FPDFPage_GetCropBox(page_ptr, l_ptr, b_ptr, r_ptr, t_ptr).zero?
Pdfium.FPDFPage_GetMediaBox(page_ptr, l_ptr, b_ptr, r_ptr, t_ptr) unless has_crop
pl = l_ptr.read_float
pb = b_ptr.read_float
pr = r_ptr.read_float
pt = t_ptr.read_float
a, b, c, d, e, f =
case rotation
when 1 then [0, -1, 1, 0, -pb, pr]
when 2 then [-1, 0, 0, -1, pr, pt]
when 3 then [0, 1, -1, 0, pt, -pl]
end
Pdfium::PAGE_BOX_ACCESSORS.each do |getter, setter|
next if Pdfium.public_send(getter, page_ptr, l_ptr, b_ptr, r_ptr, t_ptr).zero?
bl = l_ptr.read_float
bb = b_ptr.read_float
br = r_ptr.read_float
bt = t_ptr.read_float
c1x, c1y, c2x, c2y =
case rotation
when 1 then [br, bb, bl, bt]
when 2 then [br, bt, bl, bb]
when 3 then [bl, bt, br, bb]
end
new_llx = (a * c1x) + (c * c1y) + e
new_lly = (b * c1x) + (d * c1y) + f
new_urx = (a * c2x) + (c * c2y) + e
new_ury = (b * c2x) + (d * c2y) + f
Pdfium.public_send(setter, page_ptr, new_llx, new_lly, new_urx, new_ury)
end
Pdfium.FPDFPage_TransformAnnots(page_ptr, a, b, c, d, e, f)
matrix_ptr = FFI::MemoryPointer.new(FS_MATRIX.size)
matrix_struct = FS_MATRIX.new(matrix_ptr)
matrix_struct[:a] = a
matrix_struct[:b] = b
matrix_struct[:c] = c
matrix_struct[:d] = d
matrix_struct[:e] = e
matrix_struct[:f] = f
Pdfium.FPDFPage_TransFormWithClip(page_ptr, matrix_ptr, FFI::Pointer::NULL)
Pdfium.FPDFPage_SetRotation(page_ptr, 0)
Pdfium.FPDFPage_GenerateContent(page_ptr)
true
end
def flatten(flag = Pdfium::FLAT_NORMALDISPLAY)
ensure_not_closed!
result = Pdfium.FPDFPage_Flatten(page_ptr, flag)
if result == Pdfium::FLATTEN_FAIL
Pdfium.check_last_error("Failed to flatten page #{page_index}")
raise PdfiumError, "Failed to flatten page #{page_index}"
end
result
end
def close
return if closed?

@ -875,6 +875,50 @@ module Submissions
pdf
end
def maybe_rotate_pdfium(io)
pdf = HexaPDF::Document.new(io:)
return pdf if pdf.pages.size > MAX_PAGE_ROTATE
root_rotate = pdf.pages.root[:Rotate].to_i
rotated_indexes = pdf.pages.each_with_index.filter_map do |page, idx|
page_rotate = page[:Rotate]
effective = page_rotate.nil? ? root_rotate : page_rotate.to_i
idx if effective != 0
end
return pdf if rotated_indexes.blank?
has_widgets = pdf.acro_form && pdf.acro_form[:Fields].present?
io.rewind
out_io = StringIO.new
Pdfium::Document.open_bytes(io.string) do |doc|
rotated_indexes.each do |idx|
page = doc.get_page(idx)
page.flatten if has_widgets
page.rotate
end
doc.save(out_io)
end
pdf = HexaPDF::Document.new(io: out_io.tap(&:rewind))
pdf.pages.root[:Rotate] = 0
pdf
rescue StandardError => e
Rollbar.error(e) if defined?(Rollbar)
io.rewind
HexaPDF::Document.new(io:)
end
def on_missing_glyph(character, font_wrapper)
Rails.logger.info("Missing glyph: #{character}") if character.present? && defined?(Rollbar)

Loading…
Cancel
Save