From 22c146d5640b034580956554f1a02da04e45b74d Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 14 Aug 2026 16:44:42 +0300 Subject: [PATCH] adjust pdfium --- Dockerfile | 4 +- lib/pdfium.rb | 82 +++++++++++++++-------- lib/templates/build_pdfium_annotations.rb | 6 ++ lib/templates/find_acro_fields.rb | 12 ++-- lib/templates/find_pdfium_acro_fields.rb | 37 +++++++--- 5 files changed, 97 insertions(+), 44 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8b0bd9d5..93c2836e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,8 @@ RUN apk --no-cache add wget unzip && \ wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \ wget -O pdfium-linux.zip "https://github.com/docusealco/pdfium-binaries/releases/download/20260813/pdfium-musl-$(uname -m).zip" && \ case "$(uname -m)" in \ - x86_64) echo "97c584564dfd1d772e98bf6695efc4f8da3ff5cf969460ebc92b3642d136e279 pdfium-linux.zip" ;; \ - aarch64) echo "e004ba036b4dd36649f0116e14030b9216f762b3259d33035ee883659507c917 pdfium-linux.zip" ;; \ + x86_64) echo "c5c7dde243ecb66ab0819c8193515ef38ad53549fe260f3c2dfd93ea56eda2e7 pdfium-linux.zip" ;; \ + aarch64) echo "64c4483449b1b4dccc696ad0c5c96e0b7f74dcc57b4f23c676b7a70671b0bbb5 pdfium-linux.zip" ;; \ esac | sha256sum -c - && \ mkdir -p /pdfium-linux && \ unzip -q pdfium-linux.zip -d /pdfium-linux diff --git a/lib/pdfium.rb b/lib/pdfium.rb index e3c520ff..7e993464 100644 --- a/lib/pdfium.rb +++ b/lib/pdfium.rb @@ -108,6 +108,8 @@ class Pdfium attach_function :FPDF_GetLastError, [], :ulong attach_function :FPDF_GetTrailerEnds, %i[FPDF_DOCUMENT pointer ulong], :ulong attach_function :FPDF_DocumentHasValidCrossReferenceTable, [:FPDF_DOCUMENT], :int + attach_function :FPDF_GetSecurityHandlerRevision, [:FPDF_DOCUMENT], :int + attach_function :FPDF_GetFormType, [:FPDF_DOCUMENT], :int attach_function :FPDFBookmark_GetFirstChild, %i[FPDF_DOCUMENT FPDF_BOOKMARK], :FPDF_BOOKMARK attach_function :FPDFBookmark_GetNextSibling, %i[FPDF_DOCUMENT FPDF_BOOKMARK], :FPDF_BOOKMARK @@ -279,12 +281,7 @@ class Pdfium attach_function :FPDFPage_GetAnnotCount, [:FPDF_PAGE], :int - begin - attach_function :FPDFPage_GetAnnotCountRaw, %i[FPDF_DOCUMENT int], :int - rescue FFI::NotFoundError - define_singleton_method(:FPDFPage_GetAnnotCountRaw) { |*| -1 } # rubocop:disable Naming/MethodName - end - + attach_function :FPDFPage_GetAnnotCountRaw, %i[FPDF_DOCUMENT int], :int attach_function :FPDFPage_GetAnnot, %i[FPDF_PAGE int], :FPDF_ANNOTATION attach_function :FPDFPage_CloseAnnot, [:FPDF_ANNOTATION], :void attach_function :FPDFAnnot_GetSubtype, [:FPDF_ANNOTATION], :int @@ -296,18 +293,31 @@ class Pdfium attach_function :FPDFAnnot_GetFormFieldName, %i[FPDF_FORMHANDLE FPDF_ANNOTATION pointer ulong], :ulong attach_function :FPDFAnnot_GetFormFieldAlternateName, %i[FPDF_FORMHANDLE FPDF_ANNOTATION pointer ulong], :ulong attach_function :FPDFAnnot_GetFormFieldValue, %i[FPDF_FORMHANDLE FPDF_ANNOTATION pointer ulong], :ulong + attach_function :FPDFAnnot_GetFormFieldValueRaw, %i[FPDF_FORMHANDLE FPDF_ANNOTATION pointer ulong], :ulong attach_function :FPDFAnnot_GetFormFieldExportValue, %i[FPDF_FORMHANDLE FPDF_ANNOTATION pointer ulong], :ulong + attach_function :FPDFAnnot_GetFormFieldExportValueRaw, %i[FPDF_FORMHANDLE FPDF_ANNOTATION pointer ulong], :ulong attach_function :FPDFAnnot_GetFormControlCount, %i[FPDF_FORMHANDLE FPDF_ANNOTATION], :int attach_function :FPDFAnnot_GetFormControlIndex, %i[FPDF_FORMHANDLE FPDF_ANNOTATION], :int attach_function :FPDFAnnot_GetOptionCount, %i[FPDF_FORMHANDLE FPDF_ANNOTATION], :int attach_function :FPDFAnnot_GetOptionLabel, %i[FPDF_FORMHANDLE FPDF_ANNOTATION int pointer ulong], :ulong attach_function :FPDFAnnot_IsChecked, %i[FPDF_FORMHANDLE FPDF_ANNOTATION], :int + attach_function :FPDFAnnot_IsCheckedRaw, %i[FPDF_FORMHANDLE FPDF_ANNOTATION], :int + attach_function :FPDFPage_FixFormFields, %i[FPDF_FORMHANDLE FPDF_PAGE], :void attach_function :FPDFAnnot_GetFormAdditionalActionJavaScript, %i[FPDF_FORMHANDLE FPDF_ANNOTATION int pointer ulong], :ulong + attach_function :FPDFAnnot_GetFlags, [:FPDF_ANNOTATION], :int + attach_function :FPDFAnnot_SetFlags, %i[FPDF_ANNOTATION int], :int + attach_function :FPDFAnnot_GetAP, %i[FPDF_ANNOTATION int pointer ulong], :ulong FPDF_ANNOT_LINK = 2 FPDF_ANNOT_WIDGET = 20 + FPDF_ANNOT_FLAG_HIDDEN = 2 + + FPDF_ANNOT_APPEARANCEMODE_NORMAL = 0 + + FORMTYPE_NONE = 0 + FPDF_FORMFIELD_UNKNOWN = 0 FPDF_FORMFIELD_PUSHBUTTON = 1 FPDF_FORMFIELD_CHECKBOX = 2 @@ -413,23 +423,9 @@ class Pdfium 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 - - begin - attach_function :FPDF_RemoveOrphanObjects, [:FPDF_DOCUMENT], :int - rescue FFI::NotFoundError - define_singleton_method(:FPDF_RemoveOrphanObjects) { |*| -1 } # rubocop:disable Naming/MethodName - end - - begin - attach_function :FPDF_ImportAcroForm, %i[FPDF_DOCUMENT FPDF_DOCUMENT], :int - rescue FFI::NotFoundError - define_singleton_method(:FPDF_ImportAcroForm) { |*| -1 } # rubocop:disable Naming/MethodName - end + attach_function :FPDF_ImportPages, %i[FPDF_DOCUMENT FPDF_DOCUMENT string int], :int + attach_function :FPDF_RemoveOrphanObjects, [:FPDF_DOCUMENT], :int + attach_function :FPDF_ImportAcroForm, %i[FPDF_DOCUMENT FPDF_DOCUMENT], :int FPDF_ERR_SUCCESS = 0 FPDF_ERR_UNKNOWN = 1 @@ -559,6 +555,14 @@ class Pdfium @page_count ||= Pdfium.FPDF_GetPageCount(@document_ptr) end + def encrypted? + Pdfium.FPDF_GetSecurityHandlerRevision(@document_ptr) >= 0 + end + + def form? + Pdfium.FPDF_GetFormType(@document_ptr) != Pdfium::FORMTYPE_NONE + end + def import_pages(src_doc, pages: nil, index: nil) ensure_not_closed! @@ -978,6 +982,14 @@ class Pdfium @box ||= read_bounding_box || [0, 0, 612, 792] end + def fix_form_fields + return if @form_fields_fixed || form_handle.null? + + @form_fields_fixed = true + + Pdfium.FPDFPage_FixFormFields(form_handle, @page_ptr) + end + def load_page_view return if @page_view || form_handle.null? @@ -1858,6 +1870,12 @@ class Pdfium Pdfium.FPDFAnnot_GetSubtype(annot_ptr) end + def hide! + flags = Pdfium.FPDFAnnot_GetFlags(annot_ptr) + + Pdfium.FPDFAnnot_SetFlags(annot_ptr, flags | Pdfium::FPDF_ANNOT_FLAG_HIDDEN) == 1 + end + def rect rect = Pdfium::FS_RECTF.new @@ -1888,11 +1906,15 @@ class Pdfium end def field_value - read_wide { |buffer, length| Pdfium.FPDFAnnot_GetFormFieldValue(form_handle, annot_ptr, buffer, length) } + read_wide do |buffer, length| + Pdfium.FPDFAnnot_GetFormFieldValueRaw(form_handle, annot_ptr, buffer, length) + end end def export_value - read_wide { |buffer, length| Pdfium.FPDFAnnot_GetFormFieldExportValue(form_handle, annot_ptr, buffer, length) } + read_wide do |buffer, length| + Pdfium.FPDFAnnot_GetFormFieldExportValueRaw(form_handle, annot_ptr, buffer, length) + end end def control_count @@ -1904,7 +1926,7 @@ class Pdfium end def checked? - Pdfium.FPDFAnnot_IsChecked(form_handle, annot_ptr) == 1 + Pdfium.FPDFAnnot_IsCheckedRaw(form_handle, annot_ptr) == 1 end def option_labels @@ -1951,6 +1973,12 @@ class Pdfium read_wide { |buffer, length| Pdfium.FPDFAnnot_GetStringValue(annot_ptr, key, buffer, length) } end + def appearance + read_wide do |buffer, length| + Pdfium.FPDFAnnot_GetAP(annot_ptr, Pdfium::FPDF_ANNOT_APPEARANCEMODE_NORMAL, buffer, length) + end + end + private def read_wide(&) @@ -2108,7 +2136,7 @@ class Pdfium def read_form_extras(handle) case type when :checkbox, :radio - handle.page.load_page_view + handle.page.fix_form_fields @checked = handle.checked? @export_value = handle.export_value diff --git a/lib/templates/build_pdfium_annotations.rb b/lib/templates/build_pdfium_annotations.rb index bec83ea9..7aad6492 100644 --- a/lib/templates/build_pdfium_annotations.rb +++ b/lib/templates/build_pdfium_annotations.rb @@ -29,6 +29,12 @@ module Templates end annotations + rescue StandardError => e + Rollbar.error(e) if defined?(Rollbar) + + raise if Rails.env.development? + + [] end def build_external_link_hash(url, area, geometry) diff --git a/lib/templates/find_acro_fields.rb b/lib/templates/find_acro_fields.rb index 16d52add..6da0bf72 100644 --- a/lib/templates/find_acro_fields.rb +++ b/lib/templates/find_acro_fields.rb @@ -173,20 +173,20 @@ module Templates **attrs, type: 'select', options: build_options(field[:Opt], 'select'), - default_value: field.field_value.to_s.match?(SELECT_PLACEHOLDER_REGEXP) ? nil : field.field_value + default_value: field.field_value.to_s.match?(SELECT_PLACEHOLDER_REGEXP) ? nil : field.field_value.presence } elsif field.field_type == :Ch && field.concrete_field_type == :multi_select && field[:Opt].present? { **attrs, type: 'multiple', options: build_options(field[:Opt], 'multiple'), - default_value: field.field_value + default_value: field.field_value.presence } elsif field.field_type == :Tx && field.concrete_field_type == :comb_text_field { **attrs, type: 'cells', - default_value: field.field_value + default_value: field.field_value.presence } elsif field.field_type == :Tx if field[:AA] && ((field[:AA][:F] && field[:AA][:F][:JS].include?('AFDate_')) || @@ -199,13 +199,13 @@ module Templates { **attrs, type: 'date', - default_value: field.field_value + default_value: field.field_value.presence } else { **attrs, type: 'text', - default_value: field.field_value + default_value: field.field_value.presence } end elsif field.field_type == :Sig @@ -234,7 +234,7 @@ module Templates { uuid: SecureRandom.uuid, - value: is_option_number || is_skip_single_value ? '' : option + value: is_option_number || is_skip_single_value ? '' : option.presence } end end diff --git a/lib/templates/find_pdfium_acro_fields.rb b/lib/templates/find_pdfium_acro_fields.rb index 32d48cc4..d9133aaa 100644 --- a/lib/templates/find_pdfium_acro_fields.rb +++ b/lib/templates/find_pdfium_acro_fields.rb @@ -4,10 +4,13 @@ module Templates module FindPdfiumAcroFields DATE_JS_PREFIX = 'AFDate_' SKIP_FIELD_TYPES = %i[unknown pushbutton].freeze + TEXT_OPERATOR_REGEXP = /\bT[jJ]\b/ module_function - def call(attachment, doc) + def call(attachment, doc, data) + return [] if !doc.form? && data.exclude?('/Form') + pages = {} widgets = [] @@ -30,6 +33,12 @@ module Templates end group_widgets(widgets).filter_map { |field_widgets| build_field(field_widgets, pages, attachment) } + rescue StandardError => e + Rollbar.error(e) if defined?(Rollbar) + + raise if Rails.env.development? + + [] end def group_widgets(widgets) @@ -110,7 +119,7 @@ module Templates when :checkbox, :radio build_button_properties(attrs, widgets) when :combobox - build_select_properties(attrs, field) + build_select_properties(attrs, widgets.first) when :text build_text_properties(attrs, field) when :signature @@ -141,7 +150,7 @@ module Templates **attrs, type: 'radio', options: build_options(export_values.map(&:to_sym), 'radio'), - default_value: checked&.field&.export_value + default_value: checked&.field&.export_value.presence } else { @@ -152,24 +161,34 @@ module Templates end end - def build_select_properties(attrs, field) + def build_select_properties(attrs, annotation) + field = annotation.field + return {} if field.options.blank? + value = field.value.presence if renders_text?(annotation) + { **attrs, type: 'select', options: build_options(field.options, 'select'), - default_value: field.value.to_s.match?(FindAcroFields::SELECT_PLACEHOLDER_REGEXP) ? nil : field.value + default_value: value.to_s.match?(FindAcroFields::SELECT_PLACEHOLDER_REGEXP) ? nil : value } end + def renders_text?(annotation) + appearance = annotation.page.with_annotation(annotation.index, &:appearance) + + appearance.to_s.match?(TEXT_OPERATOR_REGEXP) + end + def build_text_properties(attrs, field) preferences = { align: FindAcroFields::FIELD_ALIGNMENT.fetch(field.quadding.to_i, 'left') } attrs = { **attrs, preferences: } if field.comb? - { **attrs, type: 'cells', default_value: field.value } + { **attrs, type: 'cells', default_value: field.value.presence } elsif date?(field) format = [field.format_js, field.keystroke_js].compact .filter_map { |js| js[FindAcroFields::DATE_FORMAT_REGEXP] } @@ -177,9 +196,9 @@ module Templates preferences[:format] = format.upcase if format - { **attrs, type: 'date', default_value: field.value } + { **attrs, type: 'date', default_value: field.value.presence } else - { **attrs, type: 'text', default_value: field.value } + { **attrs, type: 'text', default_value: field.value.presence } end end @@ -203,7 +222,7 @@ module Templates { uuid: SecureRandom.uuid, - value: is_option_number || is_skip_single_value ? '' : option + value: is_option_number || is_skip_single_value ? '' : option.presence } end end