This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PDF
|
||||
class Reader
|
||||
|
||||
# Page#walk will execute the content stream of a page, calling methods on a receiver class
|
||||
# provided by the user. Each operator has a specific set of parameters it expects, and we
|
||||
# wrap the users receiver class in this one to verify the PDF uses valid parameters.
|
||||
#
|
||||
# Without these checks, users can't be confident about the number of parameters they'll receive
|
||||
# for an operator, or what the type of those parameters will be. Everyone ends up building their
|
||||
# own type safety guard clauses and it's tedious.
|
||||
#
|
||||
# Not all operators have type safety implemented yet, but we can expand the number over time.
|
||||
class ValidatingReceiver
|
||||
|
||||
def initialize(wrapped)
|
||||
@wrapped = wrapped
|
||||
end
|
||||
|
||||
def page=(page)
|
||||
call_wrapped(:page=, page)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Graphics State Operators
|
||||
#####################################################
|
||||
def save_graphics_state(*args)
|
||||
call_wrapped(:save_graphics_state)
|
||||
end
|
||||
|
||||
def restore_graphics_state(*args)
|
||||
call_wrapped(:restore_graphics_state)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Matrix Operators
|
||||
#####################################################
|
||||
|
||||
def concatenate_matrix(*args)
|
||||
a, b, c, d, e, f = *args
|
||||
call_wrapped(
|
||||
:concatenate_matrix,
|
||||
TypeCheck.cast_to_numeric!(a),
|
||||
TypeCheck.cast_to_numeric!(b),
|
||||
TypeCheck.cast_to_numeric!(c),
|
||||
TypeCheck.cast_to_numeric!(d),
|
||||
TypeCheck.cast_to_numeric!(e),
|
||||
TypeCheck.cast_to_numeric!(f),
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Object Operators
|
||||
#####################################################
|
||||
|
||||
def begin_text_object(*args)
|
||||
call_wrapped(:begin_text_object)
|
||||
end
|
||||
|
||||
def end_text_object(*args)
|
||||
call_wrapped(:end_text_object)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text State Operators
|
||||
#####################################################
|
||||
def set_character_spacing(*args)
|
||||
char_spacing, _ = *args
|
||||
call_wrapped(
|
||||
:set_character_spacing,
|
||||
TypeCheck.cast_to_numeric!(char_spacing)
|
||||
)
|
||||
end
|
||||
|
||||
def set_horizontal_text_scaling(*args)
|
||||
h_scaling, _ = *args
|
||||
call_wrapped(
|
||||
:set_horizontal_text_scaling,
|
||||
TypeCheck.cast_to_numeric!(h_scaling)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_font_and_size(*args)
|
||||
label, size, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_font_and_size,
|
||||
TypeCheck.cast_to_symbol(label),
|
||||
TypeCheck.cast_to_numeric!(size)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_leading(*args)
|
||||
leading, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_leading,
|
||||
TypeCheck.cast_to_numeric!(leading)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_rendering_mode(*args)
|
||||
mode, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_rendering_mode,
|
||||
TypeCheck.cast_to_numeric!(mode)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_rise(*args)
|
||||
rise, _ = *args
|
||||
call_wrapped(
|
||||
:set_text_rise,
|
||||
TypeCheck.cast_to_numeric!(rise)
|
||||
)
|
||||
end
|
||||
|
||||
def set_word_spacing(*args)
|
||||
word_spacing, _ = *args
|
||||
call_wrapped(
|
||||
:set_word_spacing,
|
||||
TypeCheck.cast_to_numeric!(word_spacing)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Positioning Operators
|
||||
#####################################################
|
||||
|
||||
def move_text_position(*args) # Td
|
||||
x, y, _ = *args
|
||||
call_wrapped(
|
||||
:move_text_position,
|
||||
TypeCheck.cast_to_numeric!(x),
|
||||
TypeCheck.cast_to_numeric!(y)
|
||||
)
|
||||
end
|
||||
|
||||
def move_text_position_and_set_leading(*args) # TD
|
||||
x, y, _ = *args
|
||||
call_wrapped(
|
||||
:move_text_position_and_set_leading,
|
||||
TypeCheck.cast_to_numeric!(x),
|
||||
TypeCheck.cast_to_numeric!(y)
|
||||
)
|
||||
end
|
||||
|
||||
def set_text_matrix_and_text_line_matrix(*args) # Tm
|
||||
a, b, c, d, e, f = *args
|
||||
call_wrapped(
|
||||
:set_text_matrix_and_text_line_matrix,
|
||||
TypeCheck.cast_to_numeric!(a),
|
||||
TypeCheck.cast_to_numeric!(b),
|
||||
TypeCheck.cast_to_numeric!(c),
|
||||
TypeCheck.cast_to_numeric!(d),
|
||||
TypeCheck.cast_to_numeric!(e),
|
||||
TypeCheck.cast_to_numeric!(f),
|
||||
)
|
||||
end
|
||||
|
||||
def move_to_start_of_next_line(*args) # T*
|
||||
call_wrapped(:move_to_start_of_next_line)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Text Showing Operators
|
||||
#####################################################
|
||||
def show_text(*args) # Tj (AWAY)
|
||||
string, _ = *args
|
||||
call_wrapped(
|
||||
:show_text,
|
||||
TypeCheck.cast_to_string!(string)
|
||||
)
|
||||
end
|
||||
|
||||
def show_text_with_positioning(*args) # TJ [(A) 120 (WA) 20 (Y)]
|
||||
params, _ = *args
|
||||
unless params.is_a?(Array)
|
||||
raise MalformedPDFError, "TJ operator expects a single Array argument"
|
||||
end
|
||||
|
||||
call_wrapped(
|
||||
:show_text_with_positioning,
|
||||
params
|
||||
)
|
||||
end
|
||||
|
||||
def move_to_next_line_and_show_text(*args) # '
|
||||
string, _ = *args
|
||||
call_wrapped(
|
||||
:move_to_next_line_and_show_text,
|
||||
TypeCheck.cast_to_string!(string)
|
||||
)
|
||||
end
|
||||
|
||||
def set_spacing_next_line_show_text(*args) # "
|
||||
aw, ac, string = *args
|
||||
call_wrapped(
|
||||
:set_spacing_next_line_show_text,
|
||||
TypeCheck.cast_to_numeric!(aw),
|
||||
TypeCheck.cast_to_numeric!(ac),
|
||||
TypeCheck.cast_to_string!(string)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Form XObject Operators
|
||||
#####################################################
|
||||
|
||||
def invoke_xobject(*args)
|
||||
label, _ = *args
|
||||
|
||||
call_wrapped(
|
||||
:invoke_xobject,
|
||||
TypeCheck.cast_to_symbol(label)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Inline Image Operators
|
||||
#####################################################
|
||||
|
||||
def begin_inline_image(*args)
|
||||
call_wrapped(:begin_inline_image)
|
||||
end
|
||||
|
||||
def begin_inline_image_data(*args)
|
||||
# We can't use call_wrapped() here because sorbet won't allow splat args with a dynamic
|
||||
# number of elements
|
||||
@wrapped.begin_inline_image_data(*args) if @wrapped.respond_to?(:begin_inline_image_data)
|
||||
end
|
||||
|
||||
def end_inline_image(*args)
|
||||
data, _ = *args
|
||||
|
||||
call_wrapped(
|
||||
:end_inline_image,
|
||||
TypeCheck.cast_to_string!(data)
|
||||
)
|
||||
end
|
||||
|
||||
#####################################################
|
||||
# Final safety net for any operators that don't have type checking enabled yet
|
||||
#####################################################
|
||||
|
||||
def respond_to?(meth)
|
||||
@wrapped.respond_to?(meth)
|
||||
end
|
||||
|
||||
def method_missing(methodname, *args)
|
||||
@wrapped.send(methodname, *args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def call_wrapped(methodname, *args)
|
||||
@wrapped.send(methodname, *args) if @wrapped.respond_to?(methodname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user