Class: Pdfrb::Content::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/pdfrb/content/parser.rb

Overview

Groups a token stream into (operator, operands) invocations. Reuses Source::Tokenizer for operand lexing (content streams use the same PDF object syntax as COS values).

Each invocation is yielded as [operator_class, operands].

Constant Summary collapse

ABBREV_TABLE =
{
  BPC: :BitsPerComponent,
  CS: :ColorSpace,
  D: :Decode,
  DP: :DecodeParms,
  F: :Filter,
  H: :Height,
  IM: :ImageMask,
  Intent: :Intent,
  I: :Interpolate,
  W: :Width,
}.freeze
WHITESPACE_BYTE_VALUES =
[0, 9, 10, 12, 13, 32].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tokenizer) ⇒ Parser

Returns a new instance of Parser.



15
16
17
# File 'lib/pdfrb/content/parser.rb', line 15

def initialize(tokenizer)
  @tokenizer = tokenizer
end

Instance Attribute Details

#tokenizerObject (readonly)

Returns the value of attribute tokenizer.



13
14
15
# File 'lib/pdfrb/content/parser.rb', line 13

def tokenizer
  @tokenizer
end

Class Method Details

.parse(io_or_string) ⇒ Object



19
20
21
22
# File 'lib/pdfrb/content/parser.rb', line 19

def self.parse(io_or_string)
  io = io_or_string.is_a?(::String) ? StringIO.new(io_or_string.b) : io_or_string
  new(Pdfrb::Source::Tokenizer.new(io))
end

Instance Method Details

#abbrev_for(name) ⇒ Object

Map inline-image abbreviation keys to full PDF names per ISO 32000-2 §8.9.7 Table 89.



90
91
92
# File 'lib/pdfrb/content/parser.rb', line 90

def abbrev_for(name)
  ABBREV_TABLE[name.to_sym]
end

#each_invocationObject

Yields (operator_class, operands) pairs. Returns an Enumerator if no block.

Special-cases the BI/ID/EI inline image sequence: when BI is seen, the inline image dict + raw byte payload are read as a single InlineImage invocation rather than as discrete tokens.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/pdfrb/content/parser.rb', line 30

def each_invocation
  return enum_for(:each_invocation) unless block_given?

  operands = []
  while (tok = tokenizer.next_token)
    if tok.type == :keyword && tok.value == "BI"
      yield Pdfrb::Content::Operator::BeginInlineImage, [parse_inline_image]
      operands = []
      next
    end

    case tok.type
    when :keyword
      op = Pdfrb::Content::Operator[tok.value]
      yield(op || Pdfrb::Content::Operator::Unknown, operands) if op
      operands = []
    when :name
      operands << Pdfrb::Model::Cos::NameEncoding.decode(tok.value)
    when :integer, :real, :true, :false, :null
      operands << tok.value
    when :string, :hex_string
      operands << tok.value
    when :array_open
      operands << consume_array
    when :dict_open
      operands << consume_dict
    end
  end
  self
end

#parse_inline_imageObject

Parse a BI ... ID EI inline image sequence. The BI keyword has already been consumed; the next tokens form the image header (key/value pairs), then ID introduces the raw byte payload terminated by EI.

Returns a Hash with :header (the key-value pairs) and :data (the raw image bytes).



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/pdfrb/content/parser.rb', line 68

def parse_inline_image
  header = {}
  # Read header pairs until we hit the ID keyword.
  while (tok = tokenizer.next_token)
    break if tok.type == :keyword && tok.value == "ID"

    if tok.type == :name
      key = abbrev_for(tok.value) || tok.value.to_sym
      val_tok = tokenizer.next_token
      header[key] = token_value(val_tok)
    end
  end

  # After ID, exactly one whitespace byte separates the
  # keyword from the data. Read raw bytes until "\nEI" or
  # " EI" terminator.
  data = read_inline_image_data
  { header: header, data: data }
end

#read_inline_image_dataObject



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/pdfrb/content/parser.rb', line 107

def read_inline_image_data
  # Skip exactly one whitespace byte after ID.
  tokenizer.skip_whitespace
  bytes = +"".b
  # Read until we see the "EI" marker. The marker is usually
  # preceded by whitespace and followed by whitespace or EOF.
  until tokenizer.eof?
    b = tokenizer.read_byte
    break if b.nil?

    bytes << b
    if bytes.bytesize >= 3 &&
        whitespace_byte?(bytes.getbyte(-3)) &&
        bytes.byteslice(-2, 2) == "EI"
      return bytes.byteslice(0, bytes.bytesize - 3).b
    end
  end
  bytes.b
end

#whitespace_byte?(byte) ⇒ Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/pdfrb/content/parser.rb', line 129

def whitespace_byte?(byte)
  WHITESPACE_BYTE_VALUES.include?(byte)
end