pdf-core 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 72b9c9e3df4caa328d5e4166d6843e77d4eb2ca8
4
- data.tar.gz: 7833e82447f0f3ae5f25a0c9c5ad3f973442944b
2
+ SHA256:
3
+ metadata.gz: 23799dd91a7e3063a84724ecbdea6989de1b7131f1727bc6923c76b1637f0a87
4
+ data.tar.gz: 73538fe3357a195e9c3a39f7dfb1e498d0c3a49fc3c8cc6b4b0e72cc054dbd54
5
5
  SHA512:
6
- metadata.gz: 1240f1c5ff136fb1df4ca8a34a1cc1461fea31b672ae9340385ed0e8d7c61d300259e4ffa7eabee3362969de5de70d234604dac376e0b9ed822cffaece07931b
7
- data.tar.gz: dcd7098604c252a3cb8315a02ba58ceabca12de4fa51319ecb2ed4a1f4bf3054d79f1defdea0085a32743d8eb3daf9265f1f305c480511fa8d23f56f8f3b02cd
6
+ metadata.gz: e90a4a8303c8633452743ad491b21c57034551beb284458da03c0c69d135c41bcfe349027ed7ded6bf55e71f8f7dee0997898342cea7f4ed8725bdb1de1182fc
7
+ data.tar.gz: 5d048fdd3adf42181118c0340a2df17ec60e9c32e92f4699bc7e2929d508ee70d5d7888ce7d9a8b12ec06aae8423158ff184321f87aa10449f8ddc721b239f22
checksums.yaml.gz.sig CHANGED
Binary file
@@ -10,10 +10,43 @@ module PDF
10
10
  module Core
11
11
  # Provides very low-level support for annotations.
12
12
  #
13
- module Annotations #:nodoc:
14
- # Adds a new annotation (section 8.4 in PDF spec) to the current page.
15
- # +options+ must be a Hash describing the annotation.
13
+ # @api private
14
+ module Annotations
15
+ # Adds a new annotation (section *8.4 Annotations* in PDF 1.7 spec) to the
16
+ # current page.
16
17
  #
18
+ # @param options [Hash] Annotation options. This is basically an `Annot`
19
+ # dict as decribed in the PDF spec.
20
+ # @option options [Symbol<:Text, :Link, :FreeText, :Line, :Square,
21
+ # :Circle, :Polygon, :PolyLine, :Highlight, :Underline, :Squiggly,
22
+ # :StrikeOut, :Stamp, :Caret, :Ink, :Popup, :FileAttachment, :Sound,
23
+ # :Movie, :Widget, :Screen, :PrinterMark, :TrapNet, :Watermark, :3D>]
24
+ # :Subtype The type of annotation
25
+ # @option options [Array<Numeric, 4>] :Rect The annotation rectangle
26
+ # @option options [String] :Contents Text to be displayed for the
27
+ # annotation or, if this type of annotation does not display text, an
28
+ # alternate description of the annotation's contents in human-readable
29
+ # form.
30
+ # @option options [PDF::Core::Reference] :P An indirect reference to the
31
+ # page object with which this annotation is associated.
32
+ # @option options [String] :NM The annotation name, a text string uniquely
33
+ # identifying it among all the annotations on its page.
34
+ # @option options [Date, Time, String] :M The date and time when the
35
+ # annotation was most recently modified
36
+ # @option options [Integer] :F A set of flags specifying various
37
+ # characteristics of the annotation
38
+ # @option options [Hash] :AP An appearance dictionary specifying how the
39
+ # annotation is presented visually on the page
40
+ # @option options [Symbol] :AS The annotation's appearance state
41
+ # @option options [Array<(Numeric, Array<Numeric>)>] :Border the
42
+ # characteristics of the annotation's border
43
+ # @option options [Array<Float>] :C Color
44
+ # @option options [Integer] :StructParent The integer key of the
45
+ # annotation's entry in the structural parent tree
46
+ # @option options [Hash] :OC An optional content group or optional content
47
+ # membership dictionary
48
+ #
49
+ # @return [options]
17
50
  def annotate(options)
18
51
  state.page.dictionary.data[:Annots] ||= []
19
52
  options = sanitize_annotation_hash(options)
@@ -21,23 +54,28 @@ module PDF
21
54
  options
22
55
  end
23
56
 
24
- # A convenience method for creating Text annotations. +rect+ must be an
25
- # array of four numbers, describing the bounds of the annotation.
26
- # +contents+ should be a string, to be shown when the annotation is
27
- # activated.
57
+ # A convenience method for creating `Text` annotations.
58
+ #
59
+ # @param rect [Array<Numeric>] An array of four numbers,
60
+ # describing the bounds of the annotation.
61
+ # @param contents [String] Contents of the annotation
28
62
  #
63
+ # @return [Hash] Annotation dictionary
29
64
  def text_annotation(rect, contents, options = {})
30
65
  options = options.merge(Subtype: :Text, Rect: rect, Contents: contents)
31
66
  annotate(options)
32
67
  end
33
68
 
34
- # A convenience method for creating Link annotations. +rect+ must be an
35
- # array of four numbers, describing the bounds of the annotation. The
36
- # +options+ hash should include either :Dest (describing the target
37
- # destination, usually as a string that has been recorded in the
38
- # document's Dests tree), or :A (describing an action to perform on
39
- # clicking the link), or :PA (for describing a URL to link to).
69
+ # A convenience method for creating `Link` annotations.
70
+ #
71
+ # @param rect [Array<Numeric>] An array of four numbers,
72
+ # describing the bounds of the annotation.
73
+ # @param options [Hash] Should include either `:Dest` (describing the target
74
+ # destination, usually as a string that has been recorded in the
75
+ # document's `Dests` tree), or `:A` (describing an action to perform on
76
+ # clicking the link), or `:PA` (for describing a URL to link to).
40
77
  #
78
+ # @return [Hash] Annotation dictionary
41
79
  def link_annotation(rect, options = {})
42
80
  options = options.merge(Subtype: :Link, Rect: rect)
43
81
  annotate(options)
@@ -1,11 +1,12 @@
1
-
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module PDF
5
4
  module Core
6
5
  # This is used to differentiate strings that must be encoded as
7
6
  # a byte string, such as binary data from encrypted strings.
8
- class ByteString < String #:nodoc:
7
+ #
8
+ # @api private
9
+ class ByteString < String
9
10
  end
10
11
  end
11
12
  end
@@ -1,90 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Implements destination support for PDF
4
- #
5
- # Copyright November 2008, Jamis Buck. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
-
9
3
  module PDF
10
4
  module Core
11
- module Destinations #:nodoc:
5
+ # Implements destination support for PDF
6
+ #
7
+ # @api private
8
+ module Destinations
12
9
  # The maximum number of children to fit into a single node in the Dests
13
10
  # tree.
14
- NAME_TREE_CHILDREN_LIMIT = 20 #:nodoc:
11
+ #
12
+ # @private
13
+ NAME_TREE_CHILDREN_LIMIT = 20
15
14
 
16
- # The Dests name tree in the Name dictionary (see
17
- # Prawn::Document::Internal#names). This name tree is used to store named
18
- # destinations (PDF spec 8.2.1). (For more on name trees, see section
19
- # 3.8.4 in the PDF spec.)
15
+ # The `:Dests` name tree in the Name dictionary. This name tree is used to
16
+ # store named destinations (PDF 1.7 spec 8.2.1). (For more on name trees,
17
+ # see section 3.8.5 in the PDF 1.7 spec.)
20
18
  #
19
+ # @return [PDF::Core::Reference<PDF::Core::NameTree::Node>]
20
+ # @see Prawn::Document::Internal#names
21
21
  def dests
22
22
  names.data[:Dests] ||= ref!(
23
- PDF::Core::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT)
23
+ PDF::Core::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT),
24
24
  )
25
25
  end
26
26
 
27
- # Adds a new destination to the dests name tree (see #dests). The
28
- # +reference+ parameter will be converted into a PDF::Core::Reference if
29
- # it is not already one.
27
+ # Adds a new destination to the Dests name tree.
30
28
  #
29
+ # @param name [Symbol] Destination name
30
+ # @param reference [PDF::Core::Reference, Array, Hash] Destination
31
+ # definition, will be converted into a {PDF::Core::Reference} if it is
32
+ # not already one.
33
+ # @return [void]
34
+ # @see #dests
31
35
  def add_dest(name, reference)
32
36
  reference = ref!(reference) unless reference.is_a?(PDF::Core::Reference)
33
37
  dests.data.add(name, reference)
34
38
  end
35
39
 
36
- # Return a Dest specification for a specific location (and optional zoom
40
+ # Builds a Dest specification for a specific location (and optional zoom
37
41
  # level).
38
42
  #
43
+ # @param left [Numeric]
44
+ # @param top [Numeric]
45
+ # @param zoom [Numeric]
46
+ # @param dest_page [PDF::Core::Page]
47
+ # @return [Array(PDF::Core::Reference, :XYZ, Numeric, Numeric, [Numeric, null])] a Dest
48
+ # specification for a specific location
39
49
  def dest_xyz(left, top, zoom = nil, dest_page = page)
40
50
  [dest_page.dictionary, :XYZ, left, top, zoom]
41
51
  end
42
52
 
43
- # Return a Dest specification that will fit the given page into the
53
+ # builds a Dest specification that will fit the given page into the
44
54
  # viewport.
45
55
  #
56
+ # @param dest_page [PDF::Core::Page]
57
+ # @return [Array(PDF::Core::Reference, :Fit)] a Dest specification for a page fitting
58
+ # viewport
46
59
  def dest_fit(dest_page = page)
47
60
  [dest_page.dictionary, :Fit]
48
61
  end
49
62
 
50
- # Return a Dest specification that will fit the given page horizontally
63
+ # Builds a Dest specification that will fit the given page horizontally
51
64
  # into the viewport, aligned vertically at the given top coordinate.
52
65
  #
66
+ # @param top [Numeric]
67
+ # @param dest_page [PDF::Core::Page]
68
+ # @return [Array(PDF::Core::Reference, :FitH, Numeric)] a Dest specification for a page
69
+ # content fitting horizontally at a given top coordinate
53
70
  def dest_fit_horizontally(top, dest_page = page)
54
71
  [dest_page.dictionary, :FitH, top]
55
72
  end
56
73
 
57
- # Return a Dest specification that will fit the given page vertically
74
+ # Build a Dest specification that will fit the given page vertically
58
75
  # into the viewport, aligned horizontally at the given left coordinate.
59
76
  #
77
+ # @param left [Numeric]
78
+ # @param dest_page [PDF::Core::Page]
79
+ # @return [Array(Hash, :FitV, Numeric)] a Dest specification for a page
80
+ # content fitting vertically at a given left coordinate
60
81
  def dest_fit_vertically(left, dest_page = page)
61
82
  [dest_page.dictionary, :FitV, left]
62
83
  end
63
84
 
64
- # Return a Dest specification that will fit the given rectangle into the
85
+ # Builds a Dest specification that will fit the given rectangle into the
65
86
  # viewport, for the given page.
66
87
  #
88
+ # @param left [Numeric]
89
+ # @param bottom [Numeric]
90
+ # @param right [Numeric]
91
+ # @param top [Numeric]
92
+ # @param dest_page [PDF::Core::Page]
93
+ # @return [Array(Hash, :FitR, Numeric, Numeric, Numeric, Numeric)]
94
+ # a Dest specification for a page fitting the given rectangle in the
95
+ # viewport
67
96
  def dest_fit_rect(left, bottom, right, top, dest_page = page)
68
97
  [dest_page.dictionary, :FitR, left, bottom, right, top]
69
98
  end
70
99
 
71
- # Return a Dest specfication that will fit the given page's bounding box
100
+ # Builds a Dest specification that will fit the given page's bounding box
72
101
  # into the viewport.
73
102
  #
103
+ # @param dest_page [PDF::Core::Page]
104
+ # @return [Array(PDF::Core::Reference, :FitB)] a Dest specification for a page fitting
105
+ # bounding box into viewport
74
106
  def dest_fit_bounds(dest_page = page)
75
107
  [dest_page.dictionary, :FitB]
76
108
  end
77
109
 
78
- # Same as #dest_fit_horizontally, but works on the page's bounding box
110
+ # Same as {#dest_fit_horizontally}, but works on the page's bounding box
79
111
  # instead of the entire page.
80
112
  #
113
+ # @param top [Numeric]
114
+ # @param dest_page [PDF::Core::Page]
115
+ # @return [Array(PDF::Core::Reference, :FitBH, Numeric)] a Dest specification for a page
116
+ # bounding box fitting horizontally at a given top coordinate
81
117
  def dest_fit_bounds_horizontally(top, dest_page = page)
82
118
  [dest_page.dictionary, :FitBH, top]
83
119
  end
84
120
 
85
- # Same as #dest_fit_vertically, but works on the page's bounding box
121
+ # Same as {#dest_fit_vertically}, but works on the page's bounding box
86
122
  # instead of the entire page.
87
123
  #
124
+ # @param left [Numeric]
125
+ # @param dest_page [PDF::Core::Page]
126
+ # @return [Array(PDF::Core::Reference, :FitBV, Numeric)] a Dest specification for a page
127
+ # bounding box fitting vertically at a given top coordinate
88
128
  def dest_fit_bounds_vertically(left, dest_page = page)
89
129
  [dest_page.dictionary, :FitBV, left]
90
130
  end
@@ -2,7 +2,21 @@
2
2
 
3
3
  module PDF
4
4
  module Core
5
- class DocumentState #:nodoc:
5
+ # Low-level PDF document representation mostly for keeping intermediate
6
+ # state while document is being constructed.
7
+ #
8
+ # @api private
9
+ class DocumentState
10
+ # @param options [Hash<Symbol, any>]
11
+ # @option options :info [Hash] Document's information dictionary
12
+ # @option options :print_scaling [:none, nil] Viewr preference for
13
+ # printing scaling
14
+ # @option options :trailer [Hash] ({}) File trailer
15
+ # @option options :compress [Boolean] (false) Whether to compress streams
16
+ # @option options :encrypt [Boolean] (false) Whether to encrypt the
17
+ # document
18
+ # @option options :encryption_key [String] (nil) Encryption key. Must be
19
+ # provided if `:encrypt` is `true`
6
20
  def initialize(options)
7
21
  normalize_metadata(options)
8
22
 
@@ -10,38 +24,87 @@ module PDF
10
24
  if options[:print_scaling]
11
25
  PDF::Core::ObjectStore.new(
12
26
  info: options[:info],
13
- print_scaling: options[:print_scaling]
27
+ print_scaling: options[:print_scaling],
14
28
  )
15
29
  else
16
30
  PDF::Core::ObjectStore.new(info: options[:info])
17
31
  end
18
32
 
19
- @version = 1.3
20
- @pages = []
21
- @page = nil
22
- @trailer = options.fetch(:trailer, {})
23
- @compress = options.fetch(:compress, false)
24
- @encrypt = options.fetch(:encrypt, false)
25
- @encryption_key = options[:encryption_key]
26
- @skip_encoding = options.fetch(:skip_encoding, false)
33
+ @version = 1.3
34
+ @pages = []
35
+ @page = nil
36
+ @trailer = options.fetch(:trailer, {})
37
+ @compress = options.fetch(:compress, false)
38
+ @encrypt = options.fetch(:encrypt, false)
39
+ @encryption_key = options[:encryption_key]
40
+ @skip_encoding = options.fetch(:skip_encoding, false)
27
41
  @before_render_callbacks = []
28
42
  @on_page_create_callback = nil
29
43
  end
30
44
 
31
- attr_accessor :store, :version, :pages, :page, :trailer, :compress,
32
- :encrypt, :encryption_key, :skip_encoding,
33
- :before_render_callbacks, :on_page_create_callback
45
+ # Object store
46
+ # @return [PDF::Core::ObjectStore]
47
+ attr_accessor :store
34
48
 
49
+ # PDF version used in this document
50
+ # @return [Float]
51
+ attr_accessor :version
52
+
53
+ # Document pages
54
+ # @return [Array<PDF::Core::Page>]
55
+ attr_accessor :pages
56
+
57
+ # Current page
58
+ # @return [PDF::Core::Page]
59
+ attr_accessor :page
60
+
61
+ # Document trailer dict
62
+ # @return [Hash]
63
+ attr_accessor :trailer
64
+
65
+ # Whether to compress streams
66
+ # @return [Boolean]
67
+ attr_accessor :compress
68
+
69
+ # Whether to encrypt document
70
+ # @return [Boolean]
71
+ attr_accessor :encrypt
72
+
73
+ # Encryption key
74
+ # @return [String, nil]
75
+ attr_accessor :encryption_key
76
+
77
+ # @deprecated Unused
78
+ attr_accessor :skip_encoding
79
+
80
+ # Before render callbacks
81
+ # @return [Array<Proc>]
82
+ attr_accessor :before_render_callbacks
83
+
84
+ # A block to call when a new page is created
85
+ # @return [Proc, nil]
86
+ attr_accessor :on_page_create_callback
87
+
88
+ # Loads pages from object store. Only does it when there are no pages
89
+ # loaded and there are some pages in the store.
90
+ #
91
+ # @return [0] if no pages were loaded
92
+ # @return [Array<PDF::Core::Page>] if pages were laded
35
93
  def populate_pages_from_store(document)
36
94
  return 0 if @store.page_count <= 0 || !@pages.empty?
37
95
 
38
96
  count = (1..@store.page_count)
39
- @pages = count.map do |index|
40
- orig_dict_id = @store.object_id_for_page(index)
41
- PDF::Core::Page.new(document, object_id: orig_dict_id)
42
- end
97
+ @pages =
98
+ count.map { |index|
99
+ orig_dict_id = @store.object_id_for_page(index)
100
+ PDF::Core::Page.new(document, object_id: orig_dict_id)
101
+ }
43
102
  end
44
103
 
104
+ # Adds Prawn metadata to document info
105
+ #
106
+ # @param options [Hash]
107
+ # @return [Hash] Document `info` hash
45
108
  def normalize_metadata(options)
46
109
  options[:info] ||= {}
47
110
  options[:info][:Creator] ||= 'Prawn'
@@ -50,24 +113,44 @@ module PDF
50
113
  options[:info]
51
114
  end
52
115
 
116
+ # Insert a page at the specified position.
117
+ #
118
+ # @param page [PDF::Core::Page]
119
+ # @param page_number [Integer]
120
+ # @return [void]
53
121
  def insert_page(page, page_number)
54
122
  pages.insert(page_number, page)
55
123
  store.pages.data[:Kids].insert(page_number, page.dictionary)
56
124
  store.pages.data[:Count] += 1
57
125
  end
58
126
 
127
+ # Execute page creation callback if one is defined
128
+ #
129
+ # @param doc [Prawn::Document]
130
+ # @return [void]
59
131
  def on_page_create_action(doc)
60
132
  on_page_create_callback[doc] if on_page_create_callback
61
133
  end
62
134
 
135
+ # Executes before render callbacks
136
+ #
137
+ # @param _doc [Prawn::Document] Unused
138
+ # @return [void]
63
139
  def before_render_actions(_doc)
64
140
  before_render_callbacks.each { |c| c.call(self) }
65
141
  end
66
142
 
143
+ # Number of pages in the document
144
+ #
145
+ # @return [Integer]
67
146
  def page_count
68
147
  pages.length
69
148
  end
70
149
 
150
+ # Renders document body to the output
151
+ #
152
+ # @param output [#<<]
153
+ # @return [void]
71
154
  def render_body(output)
72
155
  store.each do |ref|
73
156
  ref.offset = output.size
@@ -2,11 +2,35 @@
2
2
 
3
3
  module PDF
4
4
  module Core
5
+ # A representation of a list of filters applied to a stream.
5
6
  class FilterList
7
+ # An exception one can expect when adding something to filter list that
8
+ # can not be interpreted as a filter.
9
+ class NotFilter < StandardError
10
+ # Generic default error message
11
+ DEFAULT_MESSAGE = 'Can not interpret input as a filter'
12
+
13
+ # Error message template with more details
14
+ MESSAGE_WITH_FILTER = 'Can not interpret input as a filter: %<filter>s'
15
+
16
+ def initialize(message = DEFAULT_MESSAGE, filter: nil)
17
+ if filter
18
+ super(format(MESSAGE_WITH_FILTER, filter: filter))
19
+ else
20
+ super(message)
21
+ end
22
+ end
23
+ end
24
+
6
25
  def initialize
7
26
  @list = []
8
27
  end
9
28
 
29
+ # Appends a filter to the list
30
+ #
31
+ # @param filter [Symbol, Hash] a filter to append
32
+ # @return [self]
33
+ # @raise [NotFilter]
10
34
  def <<(filter)
11
35
  case filter
12
36
  when Symbol
@@ -16,37 +40,52 @@ module PDF
16
40
  @list << [name, params]
17
41
  end
18
42
  else
19
- raise "Can not interpret input as filter: #{filter.inspect}"
43
+ raise NotFilter.new(filter: filter)
20
44
  end
21
45
 
22
46
  self
23
47
  end
24
48
 
49
+ # A normalized representation of the filter list
50
+ #
51
+ # @return [Array<Array<(Symbol, [Hash, nil])>>]
25
52
  def normalized
26
53
  @list
27
54
  end
28
55
  alias to_a normalized
29
56
 
57
+ # Names of filters in the list
58
+ #
59
+ # @return [Array<Symbol>]
30
60
  def names
31
61
  @list.map do |(name, _)|
32
62
  name
33
63
  end
34
64
  end
35
65
 
66
+ # Parameters of filters
67
+ #
68
+ # @return [Array<[Hash, nil]>]
36
69
  def decode_params
37
70
  @list.map do |(_, params)|
38
71
  params
39
72
  end
40
73
  end
41
74
 
75
+ # @return [String]
42
76
  def inspect
43
77
  @list.inspect
44
78
  end
45
79
 
46
- def each
47
- @list.each do |filter|
48
- yield(filter)
49
- end
80
+ # Iterates over filters
81
+ #
82
+ # @yield [(name, decode_params)] an array of filter name and decode
83
+ # parameters
84
+ # @yieldparam name [Symbol] filter name
85
+ # @yieldparam decode_params [Hash, nil] decode params
86
+ # @return [Array<Array<(Symbol, [Hash, nil])>>] normalized filter list
87
+ def each(&block)
88
+ @list.each(&block)
50
89
  end
51
90
  end
52
91
  end
@@ -1,32 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # prawn/core/filters.rb : Implements stream filters
4
- #
5
- # Copyright February 2013, Alexander Mankuta. All Rights Reserved.
6
- #
7
- # This is free software. Please see the LICENSE and COPYING files for details.
8
-
9
3
  require 'zlib'
10
4
 
11
5
  module PDF
12
6
  module Core
7
+ # Stream filters
13
8
  module Filters
9
+ # zlib/deflate compression
14
10
  module FlateDecode
11
+ # Encode stream data
12
+ #
13
+ # @param stream [String] stream data
14
+ # @param _params [nil] unused, here for API compatibility
15
+ # @return [String]
15
16
  def self.encode(stream, _params = nil)
16
17
  Zlib::Deflate.deflate(stream)
17
18
  end
18
19
 
20
+ # Decode stream data
21
+ #
22
+ # @param stream [String] stream data
23
+ # @param _params [nil] unused, here for API compatibility
24
+ # @return [String]
19
25
  def self.decode(stream, _params = nil)
20
26
  Zlib::Inflate.inflate(stream)
21
27
  end
22
28
  end
23
29
 
24
- # Pass through stub
30
+ # Data encoding using DCT (discrete cosine transform) technique based on
31
+ # the JPEG standard.
32
+ #
33
+ # Pass through stub.
25
34
  module DCTDecode
35
+ # Encode stream data
36
+ #
37
+ # @param stream [String] stream data
38
+ # @param _params [nil] unused, here for API compatibility
39
+ # @return [String]
26
40
  def self.encode(stream, _params = nil)
27
41
  stream
28
42
  end
29
43
 
44
+ # Decode stream data
45
+ #
46
+ # @param stream [String] stream data
47
+ # @param _params [nil] unused, here for API compatibility
48
+ # @return [String]
30
49
  def self.decode(stream, _params = nil)
31
50
  stream
32
51
  end