origami 2.0.0 → 2.0.1

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/bin/gui/config.rb +2 -1
  4. data/bin/gui/file.rb +118 -240
  5. data/bin/gui/gtkhex.rb +5 -5
  6. data/bin/gui/hexview.rb +20 -16
  7. data/bin/gui/imgview.rb +1 -1
  8. data/bin/gui/menu.rb +138 -158
  9. data/bin/gui/properties.rb +46 -48
  10. data/bin/gui/signing.rb +183 -214
  11. data/bin/gui/textview.rb +1 -1
  12. data/bin/gui/treeview.rb +13 -7
  13. data/bin/gui/walker.rb +102 -71
  14. data/bin/gui/xrefs.rb +1 -1
  15. data/bin/pdf2ruby +3 -3
  16. data/bin/pdfcop +18 -11
  17. data/bin/pdfextract +14 -5
  18. data/bin/pdfmetadata +3 -3
  19. data/bin/shell/console.rb +8 -8
  20. data/bin/shell/hexdump.rb +4 -4
  21. data/examples/attachments/nested_document.rb +1 -1
  22. data/examples/javascript/hello_world.rb +3 -3
  23. data/lib/origami.rb +0 -1
  24. data/lib/origami/acroform.rb +3 -3
  25. data/lib/origami/array.rb +1 -3
  26. data/lib/origami/boolean.rb +1 -3
  27. data/lib/origami/catalog.rb +3 -9
  28. data/lib/origami/destinations.rb +2 -2
  29. data/lib/origami/dictionary.rb +15 -29
  30. data/lib/origami/encryption.rb +334 -692
  31. data/lib/origami/extensions/fdf.rb +3 -2
  32. data/lib/origami/extensions/ppklite.rb +5 -9
  33. data/lib/origami/filespec.rb +2 -2
  34. data/lib/origami/filters.rb +54 -36
  35. data/lib/origami/filters/ascii.rb +67 -49
  36. data/lib/origami/filters/ccitt.rb +4 -236
  37. data/lib/origami/filters/ccitt/tables.rb +267 -0
  38. data/lib/origami/filters/crypt.rb +1 -1
  39. data/lib/origami/filters/dct.rb +0 -1
  40. data/lib/origami/filters/flate.rb +3 -43
  41. data/lib/origami/filters/lzw.rb +62 -99
  42. data/lib/origami/filters/predictors.rb +135 -105
  43. data/lib/origami/filters/runlength.rb +34 -22
  44. data/lib/origami/graphics.rb +2 -2
  45. data/lib/origami/graphics/colors.rb +89 -63
  46. data/lib/origami/graphics/path.rb +14 -14
  47. data/lib/origami/graphics/patterns.rb +31 -33
  48. data/lib/origami/graphics/render.rb +0 -1
  49. data/lib/origami/graphics/state.rb +9 -9
  50. data/lib/origami/graphics/text.rb +17 -17
  51. data/lib/origami/graphics/xobject.rb +102 -92
  52. data/lib/origami/javascript.rb +91 -68
  53. data/lib/origami/linearization.rb +22 -20
  54. data/lib/origami/metadata.rb +1 -1
  55. data/lib/origami/name.rb +1 -3
  56. data/lib/origami/null.rb +1 -3
  57. data/lib/origami/numeric.rb +3 -13
  58. data/lib/origami/object.rb +100 -72
  59. data/lib/origami/page.rb +24 -28
  60. data/lib/origami/parser.rb +34 -51
  61. data/lib/origami/parsers/fdf.rb +2 -2
  62. data/lib/origami/parsers/pdf.rb +41 -18
  63. data/lib/origami/parsers/pdf/lazy.rb +83 -46
  64. data/lib/origami/parsers/pdf/linear.rb +19 -10
  65. data/lib/origami/parsers/ppklite.rb +1 -1
  66. data/lib/origami/pdf.rb +150 -206
  67. data/lib/origami/reference.rb +4 -6
  68. data/lib/origami/signature.rb +76 -48
  69. data/lib/origami/stream.rb +69 -63
  70. data/lib/origami/string.rb +2 -19
  71. data/lib/origami/trailer.rb +25 -22
  72. data/lib/origami/version.rb +1 -1
  73. data/lib/origami/xfa.rb +6 -4
  74. data/lib/origami/xreftable.rb +29 -29
  75. data/test/test_annotations.rb +16 -38
  76. data/test/test_pdf_attachment.rb +1 -1
  77. data/test/test_pdf_parse.rb +1 -1
  78. data/test/test_xrefs.rb +2 -2
  79. metadata +4 -4
  80. data/lib/origami/export.rb +0 -247
@@ -26,6 +26,7 @@ module Origami
26
26
  end
27
27
 
28
28
  module Predictor
29
+
29
30
  NONE = 1
30
31
  TIFF = 2
31
32
  PNG_NONE = 10
@@ -35,42 +36,88 @@ module Origami
35
36
  PNG_PAETH = 14
36
37
  PNG_OPTIMUM = 15
37
38
 
38
- def self.do_pre_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
39
- return data if predictor == NONE
39
+ class DecodeParms < Dictionary
40
+ include StandardObject
40
41
 
41
- unless (1..4) === colors.to_i
42
- raise PredictorError.new("Colors must be between 1 and 4", input_data: data)
43
- end
42
+ field :Predictor, :Type => Integer, :Default => 1
43
+ field :Colors, :Type => Integer, :Default => 1
44
+ field :BitsPerComponent, :Type => Integer, :Default => 8
45
+ field :Columns, :Type => Integer, :Default => 1
46
+ end
44
47
 
45
- unless [1,2,4,8,16].include?(bpc.to_i)
46
- raise PredictorError.new("BitsPerComponent must be in 1, 2, 4, 8 or 16", input_data: data)
47
- end
48
+ def self.included(receiver)
49
+ raise TypeError, "Predictors only applies to Filters" unless receiver.include?(Filter)
50
+ end
48
51
 
49
- # components per line
50
- nvals = columns * colors
52
+ #
53
+ # Create a new predictive Filter.
54
+ # _parameters_:: A hash of filter options.
55
+ #
56
+ def initialize(parameters = {})
57
+ super(DecodeParms.new(parameters))
58
+ end
51
59
 
52
- # bytes per pixel
53
- bpp = (colors * bpc + 7) >> 3
60
+ private
54
61
 
55
- # bytes per row
56
- bpr = (nvals * bpc + 7) >> 3
62
+ def pre_prediction(data)
63
+ return data unless @params.Predictor.is_a?(Integer)
64
+
65
+ apply_pre_prediction(data, prediction_parameters)
66
+ end
67
+
68
+ def post_prediction(data)
69
+ return data unless @params.Predictor.is_a?(Integer)
70
+
71
+ apply_post_prediction(data, prediction_parameters)
72
+ end
73
+
74
+ def prediction_parameters
75
+ {
76
+ predictor: @params.Predictor.to_i,
77
+ colors: @params.Colors.is_a?(Integer) ? @params.Colors.to_i : 1,
78
+ bpc: @params.BitsPerComponent.is_a?(Integer) ? @params.BitsPerComponent.to_i : 8,
79
+ columns: @params.Columns.is_a?(Integer) ? @params.Columns.to_i : 1,
80
+ }
81
+ end
82
+
83
+ def apply_pre_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
84
+ return data if predictor == NONE
85
+
86
+ bpp, bpr = compute_bpp_bpr(data, columns, colors, bpc)
57
87
 
58
88
  unless data.size % bpr == 0
59
- raise PredictorError.new("Invalid data size #{data.size}, should be multiple of bpr=#{bpr}", input_data: data)
89
+ raise PredictorError.new("Invalid data size #{data.size}, should be multiple of bpr=#{bpr}",
90
+ input_data: data)
60
91
  end
61
92
 
62
93
  if predictor == TIFF
63
- do_tiff_pre_prediction(data, colors, bpc, columns)
94
+ tiff_pre_prediction(data, colors, bpc, columns)
64
95
  elsif predictor >= 10 # PNG
65
- do_png_pre_prediction(data, predictor, bpp, bpr)
96
+ png_pre_prediction(data, predictor, bpp, bpr)
66
97
  else
67
98
  raise PredictorError.new("Unknown predictor : #{predictor}", input_data: data)
68
99
  end
69
100
  end
70
101
 
71
- def self.do_post_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
102
+ def apply_post_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
72
103
  return data if predictor == NONE
73
104
 
105
+ bpp, bpr = compute_bpp_bpr(data, columns, colors, bpc)
106
+
107
+ if predictor == TIFF
108
+ tiff_post_prediction(data, colors, bpc, columns)
109
+ elsif predictor >= 10 # PNG
110
+ # Each line has an extra predictor byte.
111
+ png_post_prediction(data, bpp, bpr + 1)
112
+ else
113
+ raise PredictorError.new("Unknown predictor : #{predictor}", input_data: data)
114
+ end
115
+ end
116
+
117
+ #
118
+ # Computes the number of bytes per pixel and number of bytes per row.
119
+ #
120
+ def compute_bpp_bpr(data, columns, colors, bpc)
74
121
  unless (1..4) === colors
75
122
  raise PredictorError.new("Colors must be between 1 and 4", input_data: data)
76
123
  end
@@ -86,18 +133,16 @@ module Origami
86
133
  bpp = (colors * bpc + 7) >> 3
87
134
 
88
135
  # bytes per row
89
- bpr = ((nvals * bpc + 7) >> 3) + 1
136
+ bpr = (nvals * bpc + 7) >> 3
90
137
 
91
- if predictor == TIFF
92
- do_tiff_post_prediction(data, colors, bpc, columns)
93
- elsif predictor >= 10 # PNG
94
- do_png_post_prediction(data, bpp, bpr)
95
- else
96
- raise PredictorError.new("Unknown predictor : #{predictor}", input_data: data)
97
- end
138
+ [ bpp, bpr ]
98
139
  end
99
140
 
100
- def self.do_png_post_prediction(data, bpp, bpr)
141
+ #
142
+ # Decodes the PNG input data.
143
+ # Each line should be prepended by a byte identifying a PNG predictor.
144
+ #
145
+ def png_post_prediction(data, bpp, bpr)
101
146
  result = ""
102
147
  uprow = "\0" * bpr
103
148
  thisrow = "\0" * bpr
@@ -114,37 +159,18 @@ module Origami
114
159
  if bpp > i
115
160
  left = upleft = 0
116
161
  else
117
- left = line[i-bpp].ord
118
- upleft = uprow[i-bpp].ord
162
+ left = line[i - bpp].ord
163
+ upleft = uprow[i - bpp].ord
119
164
  end
120
165
 
121
- case predictor
122
- when PNG_NONE
123
- thisrow = line
124
- when PNG_SUB
125
- thisrow[i] = ((line[i].ord + left) & 0xFF).chr
126
- when PNG_UP
127
- thisrow[i] = ((line[i].ord + up) & 0xFF).chr
128
- when PNG_AVERAGE
129
- thisrow[i] = ((line[i].ord + ((left + up) / 2)) & 0xFF).chr
130
- when PNG_PAETH
131
- p = left + up - upleft
132
- pa, pb, pc = (p - left).abs, (p - up).abs, (p - upleft).abs
133
-
134
- thisrow[i] = ((line[i].ord +
135
- case [ pa, pb, pc ].min
136
- when pa then left
137
- when pb then up
138
- when pc then upleft
139
- end
140
- ) & 0xFF).chr
141
- else
142
- unless Origami::OPTIONS[:ignore_png_errors]
143
- raise PredictorError.new("Unknown PNG predictor : #{predictor}", input_data: data, decoded_data: result)
144
- end
166
+ begin
167
+ thisrow[i] = png_apply_prediction(predictor, line[i].ord, up, left, upleft, &:+)
168
+ rescue PredictorError => error
169
+ thisrow[i] = line[i] if Origami::OPTIONS[:ignore_png_errors]
145
170
 
146
- # behave as PNG_NONE
147
- thisrow = line
171
+ error.input_data = data
172
+ error.decoded_data = result
173
+ raise(error)
148
174
  end
149
175
  end
150
176
 
@@ -155,47 +181,29 @@ module Origami
155
181
  result
156
182
  end
157
183
 
158
- def self.do_png_pre_prediction(data, predictor, bpp, bpr)
184
+ #
185
+ # Encodes the input data given a PNG predictor.
186
+ #
187
+ def png_pre_prediction(data, predictor, bpp, bpr)
159
188
  result = ""
160
189
  nrows = data.size / bpr
161
190
 
162
191
  line = "\0" + data[-bpr, bpr]
163
192
 
164
- (nrows-1).downto(0) do |irow|
193
+ (nrows - 1).downto(0) do |irow|
165
194
  uprow =
166
195
  if irow == 0
167
- "\0" * (bpr+1)
196
+ "\0" * (bpr + 1)
168
197
  else
169
- "\0" + data[(irow-1)*bpr,bpr]
198
+ "\0" + data[(irow - 1) * bpr, bpr]
170
199
  end
171
200
 
172
201
  bpr.downto(1) do |i|
173
202
  up = uprow[i].ord
174
- left = line[i-bpp].ord
175
- upleft = uprow[i-bpp].ord
176
-
177
- case predictor
178
- when PNG_SUB
179
- line[i] = ((line[i].ord - left) & 0xFF).chr
180
- when PNG_UP
181
- line[i] = ((line[i].ord - up) & 0xFF).chr
182
- when PNG_AVERAGE
183
- line[i] = ((line[i].ord - ((left + up) / 2)) & 0xFF).chr
184
- when PNG_PAETH
185
- p = left + up - upleft
186
- pa, pb, pc = (p - left).abs, (p - up).abs, (p - upleft).abs
187
-
188
- line[i] = ((line[i].ord -
189
- case [ pa, pb, pc ].min
190
- when pa then left
191
- when pb then up
192
- when pc then upleft
193
- end
194
- ) & 0xFF).chr
195
- when PNG_NONE
196
- else
197
- raise PredictorError.new("Unsupported PNG predictor : #{predictor}", input_data: data)
198
- end
203
+ left = line[i - bpp].ord
204
+ upleft = uprow[i - bpp].ord
205
+
206
+ line[i] = png_apply_prediction(predictor, line[i].ord, up, left, upleft, &:-)
199
207
  end
200
208
 
201
209
  line[0] = (predictor - 10).chr
@@ -207,32 +215,54 @@ module Origami
207
215
  result
208
216
  end
209
217
 
210
- def self.do_tiff_post_prediction(data, colors, bpc, columns) #:nodoc:
211
- bpr = (colors * bpc * columns + 7) >> 3
212
- nrows = data.size / bpr
213
- bitmask = (1 << bpc) - 1
214
- result = Utils::BitWriter.new
215
-
216
- nrows.times do |irow|
217
- line = Utils::BitReader.new(data[irow * bpr, bpr])
218
-
219
- pixel = ::Array.new(colors, 0)
220
- columns.times do
221
- diffpixel = ::Array.new(colors) { line.read(bpc) }
222
- pixel = pixel.zip(diffpixel).map!{|c, diff| (c + diff) & bitmask}
223
-
224
- pixel.each do |c|
225
- result.write(c, bpc)
226
- end
218
+ #
219
+ # Computes the next component value given a predictor and adjacent components.
220
+ # A block must be passed to apply the operation.
221
+ #
222
+ def png_apply_prediction(predictor, value, up, left, upleft)
223
+
224
+ result =
225
+ case predictor
226
+ when PNG_NONE
227
+ value
228
+ when PNG_SUB
229
+ yield(value, left)
230
+ when PNG_UP
231
+ yield(value, up)
232
+ when PNG_AVERAGE
233
+ yield(value, (left + up) / 2)
234
+ when PNG_PAETH
235
+ yield(value, png_paeth_choose(up, left, upleft))
236
+ else
237
+ raise PredictorError, "Unsupported PNG predictor : #{predictor}"
227
238
  end
228
239
 
229
- result.final
240
+ (result & 0xFF).chr
241
+ end
242
+
243
+ #
244
+ # Choose the preferred value in a PNG paeth predictor given the left, up and up left samples.
245
+ #
246
+ def png_paeth_choose(left, up, upleft)
247
+ p = left + up - upleft
248
+ pa, pb, pc = (p - left).abs, (p - up).abs, (p - upleft).abs
249
+
250
+ case [pa, pb, pc].min
251
+ when pa then left
252
+ when pb then up
253
+ when pc then upleft
230
254
  end
255
+ end
231
256
 
232
- result.final.to_s
257
+ def tiff_post_prediction(data, colors, bpc, columns) #:nodoc:
258
+ tiff_apply_prediction(data, colors, bpc, columns, &:+)
259
+ end
260
+
261
+ def tiff_pre_prediction(data, colors, bpc, columns) #:nodoc:
262
+ tiff_apply_prediction(data, colors, bpc, columns, &:-)
233
263
  end
234
264
 
235
- def self.do_tiff_pre_prediction(data, colors, bpc, columns) #:nodoc:
265
+ def tiff_apply_prediction(data, colors, bpc, columns) #:nodoc:
236
266
  bpr = (colors * bpc * columns + 7) >> 3
237
267
  nrows = data.size / bpr
238
268
  bitmask = (1 << bpc) - 1
@@ -244,7 +274,7 @@ module Origami
244
274
  diffpixel = ::Array.new(colors, 0)
245
275
  columns.times do
246
276
  pixel = ::Array.new(colors) { line.read(bpc) }
247
- diffpixel = diffpixel.zip(pixel).map!{|diff, c| (c - diff) & bitmask}
277
+ diffpixel = diffpixel.zip(pixel).map!{|diff, c| yield(c, diff) & bitmask}
248
278
 
249
279
  diffpixel.each do |c|
250
280
  result.write(c, bpc)
@@ -42,36 +42,24 @@ module Origami
42
42
  i = 0
43
43
 
44
44
  while i < stream.size
45
- #
45
+
46
46
  # How many identical bytes coming?
47
- #
48
- length = 1
49
- while i+1 < stream.size and length < EOD and stream[i] == stream[i+1]
50
- length = length + 1
51
- i = i + 1
52
- end
47
+ length = compute_run_length(stream, i)
53
48
 
54
- #
55
49
  # If more than 1, then compress them.
56
- #
57
50
  if length > 1
58
51
  result << (257 - length).chr << stream[i]
59
- #
52
+ i += length
53
+
60
54
  # Otherwise how many different bytes to copy?
61
- #
62
55
  else
63
- j = i
64
- while j+1 < stream.size and (j - i + 1) < EOD and stream[j] != stream[j+1]
65
- j = j + 1
66
- end
56
+ next_pos = find_next_run(stream, i)
57
+ length = next_pos - i
67
58
 
68
- length = j - i
69
- result << length.chr << stream[i, length+1]
59
+ result << (length - 1).chr << stream[i, length]
70
60
 
71
- i = j
61
+ i += length
72
62
  end
73
-
74
- i = i + 1
75
63
  end
76
64
 
77
65
  result << EOD.chr
@@ -83,7 +71,6 @@ module Origami
83
71
  #
84
72
  def decode(stream)
85
73
  result = "".b
86
- return result if stream.empty?
87
74
 
88
75
  i = 0
89
76
  until i >= stream.length or stream[i].ord == EOD do
@@ -104,12 +91,37 @@ module Origami
104
91
  end
105
92
 
106
93
  # Check if offset is beyond the end of data.
107
- if i >= stream.length
94
+ if i > stream.length
108
95
  raise InvalidRunLengthDataError.new("Truncated run-length data", input_data: stream, decoded_data: result)
109
96
  end
110
97
 
111
98
  result
112
99
  end
100
+
101
+ private
102
+
103
+ #
104
+ # Find the position of the next byte at which a new run starts.
105
+ #
106
+ def find_next_run(input, pos)
107
+ start = pos
108
+ pos += 1 while pos + 1 < input.size and (pos - start + 1) < EOD and input[pos] != input[pos + 1]
109
+
110
+ pos + 1
111
+ end
112
+
113
+ #
114
+ # Computes the length of the run at the given position.
115
+ #
116
+ def compute_run_length(input, pos)
117
+ run_length = 1
118
+ while pos + 1 < input.size and run_length < EOD and input[pos] == input[pos + 1]
119
+ run_length += 1
120
+ pos += 1
121
+ end
122
+
123
+ run_length
124
+ end
113
125
  end
114
126
  RL = RunLength
115
127
 
@@ -29,10 +29,10 @@ module Origami
29
29
  end
30
30
 
31
31
  require 'origami/graphics/instruction'
32
+ require 'origami/graphics/state'
32
33
  require 'origami/graphics/colors'
33
34
  require 'origami/graphics/path'
34
35
  require 'origami/graphics/xobject'
35
- require 'origami/graphics/patterns'
36
36
  require 'origami/graphics/text'
37
- require 'origami/graphics/state'
37
+ require 'origami/graphics/patterns'
38
38
  require 'origami/graphics/render'
@@ -105,99 +105,125 @@ module Origami
105
105
  def Color.to_a(color)
106
106
  return color if color.is_a?(::Array)
107
107
 
108
- if (color.respond_to? :r and color.respond_to? :g and color.respond_to? :b)
109
- r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
110
- g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
111
- b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
108
+ if %i(r g b).all? {|c| color.respond_to?(c)}
109
+ r = color.r.to_f / 255
110
+ g = color.g.to_f / 255
111
+ b = color.b.to_f / 255
112
112
  return [r, g, b]
113
113
 
114
- elsif (color.respond_to? :c and color.respond_to? :m and color.respond_to? :y and color.respond_to? :k)
115
- c = (color.respond_to?(:c) ? color.c : color[0]).to_f
116
- m = (color.respond_to?(:m) ? color.m : color[1]).to_f
117
- y = (color.respond_to?(:y) ? color.y : color[2]).to_f
118
- k = (color.respond_to?(:k) ? color.k : color[3]).to_f
114
+ elsif %i(c m y k).all? {|c| color.respond_to?(c)}
115
+ c = color.c
116
+ m = color.m
117
+ y = color.y
118
+ k = color.k
119
119
  return [c,m,y,k]
120
120
 
121
- elsif color.respond_to?:g or (0.0..1.0) === color
122
- g = color.respond_to?(:g) ? color.g : color
123
- return [ g ]
121
+ elsif color.respond_to?(:g)
122
+ g = color.g
123
+ return [g]
124
124
 
125
125
  else
126
126
  raise TypeError, "Invalid color : #{color}"
127
127
  end
128
128
  end
129
129
  end
130
- end
131
130
 
132
- class PDF::Instruction
131
+ class State
132
+ def set_stroking_color(color, space = @stroking_color_space)
133
+ check_color(space, color)
134
+
135
+ @stroking_colorspace = space
136
+ @stroking_color = color
137
+ end
133
138
 
134
- insn 'CS', Name do |canvas, cs| canvas.gs.stroking_colorspace = cs end
135
- insn 'cs', Name do |canvas, cs| canvas.gs.nonstroking_colorspace = cs end
136
- insn 'SC', '*' do |canvas, *c| canvas.gs.stroking_color = c end
137
- insn 'sc', '*' do |canvas, *c| canvas.gs.nonstroking_color = c end
139
+ def set_nonstroking_color(color, space = @nonstroking_colorspace)
140
+ check_color(space, color)
138
141
 
139
- insn 'G', Real do |canvas, c|
140
- unless (0..1).include? c
141
- raise Graphics::InvalidColorError,
142
- "Not a valid color for DeviceGray: #{c}"
142
+ @nonstroking_colorspace = space
143
+ @nonstroking_color = color
143
144
  end
144
145
 
145
- canvas.gs.stroking_colorspace = Graphics::Color::Space::DEVICE_GRAY
146
- canvas.gs.stroking_color = [ c ]
147
- end
146
+ def set_stroking_colorspace(space)
147
+ check_color_space(space, @stroking_color)
148
148
 
149
- insn 'g', Real do |canvas, c|
150
- unless (0..1).include? c
151
- raise Graphics::InvalidColorError,
152
- "Not a valid color for DeviceGray: #{c}"
149
+ @stroking_color_space = space
153
150
  end
154
151
 
155
- canvas.gs.nonstroking_colorspace = Graphics::Color::Space::DEVICE_GRAY
156
- canvas.gs.nonstroking_color = [ c ]
157
- end
152
+ def set_nonstroking_colorspace(space)
153
+ check_color_space(space, @nonstroking_color)
158
154
 
159
- insn 'RG', Real, Real, Real do |canvas, r,g,b|
160
- color = [ r, g, b ]
161
- unless color.all? {|comp| (0..1).include? comp}
162
- raise Graphics::InvalidColorError,
163
- "Not a valid color for DeviceRGB: #{color.inspect}"
155
+ @nonstroking_color_space = space
164
156
  end
165
157
 
166
- canvas.gs.stroking_colorspace = Graphics::Color::Space::DEVICE_RGB
167
- canvas.gs.stroking_color = color
168
- end
158
+ private
169
159
 
170
- insn 'rg', Real, Real, Real do |canvas, r,g,b|
171
- color = [ r, g, b ]
172
- unless color.all? {|comp| (0..1).include? comp}
173
- raise Graphics::InvalidColorError,
174
- "Not a valid color for DeviceRGB: #{color.inspect}"
160
+ def check_color_space(space)
161
+ case space
162
+ when Color::Space::DEVICE_GRAY, Color::Space::DEVICE_RGB, Color::Space::DEVICE_CMYK
163
+ else
164
+ raise InvalidColorError, "Unknown color space #{space}"
165
+ end
175
166
  end
176
167
 
177
- canvas.gs.nonstroking_colorspace = Graphics::Color::Space::DEVICE_RGB
178
- canvas.gs.nonstroking_color = color
179
- end
168
+ def check_color(space, color)
169
+ valid_color =
170
+ case space
171
+ when Color::Space::DEVICE_GRAY
172
+ check_gray_color(color)
173
+ when Color::Space::DEVICE_RGB
174
+ check_rgb_color(color)
175
+ when Color::Space::DEVICE_CMYK
176
+ check_cmyk_color(color)
177
+ else
178
+ raise InvalidColorError, "Unknown color space #{space}"
179
+ end
180
180
 
181
- insn 'K', Real, Real, Real, Real do |canvas, c,m,y,k|
182
- color = [ c, m, y, k ]
183
- unless color.all? {|comp| (0..1).include? comp}
184
- raise Graphics::InvalidColorError,
185
- "Not a valid color for DeviceCMYK: #{color.inspect}"
181
+ raise InvalidColorError, "Invalid color #{color.inspect} for #{space}" unless valid_color
186
182
  end
187
183
 
188
- canvas.gs.stroking_colorspace = Graphics::Color::Space::DEVICE_CMYK
189
- canvas.gs.stroking_color = color
190
- end
184
+ def check_gray_color(color)
185
+ color.is_a?(::Array) and color.length == 1 and (0..1).include?(color[0])
186
+ end
191
187
 
192
- insn 'k', Real, Real, Real, Real do |canvas, c,m,y,k|
193
- color = [ c, m, y, k ]
194
- unless color.all? {|comp| (0..1).include? comp}
195
- raise Graphics::InvalidColorError,
196
- "Not a valid color for DeviceCMYK: #{color.inspect}"
188
+ def check_rgb_color(color)
189
+ color.is_a?(::Array) and color.length == 3 and color.all? {|c| (0..1).include?(c) }
197
190
  end
198
191
 
199
- canvas.gs.nonstroking_colorspace = Graphics::Color::Space::DEVICE_CMYK
200
- canvas.gs.nonstroking_color = color
192
+ def check_cmyk_color(color)
193
+ color.is_a?(::Array) and color.length == 4 and color.all? {|c| (0..1).include?(c) }
194
+ end
195
+ end
196
+ end
197
+
198
+ class PDF::Instruction
199
+
200
+ insn 'CS', Name do |canvas, cs| canvas.gs.set_stroking_colorspace(cs) end
201
+ insn 'cs', Name do |canvas, cs| canvas.gs.set_nonstroking_colorspace(cs) end
202
+ insn 'SC', '*' do |canvas, *c| canvas.gs.set_stroking_color(c) end
203
+ insn 'sc', '*' do |canvas, *c| canvas.gs.set_nonstroking_color(c) end
204
+
205
+ insn 'G', Real do |canvas, c|
206
+ canvas.gs.set_stroking_color([c], Graphics::Color::SPACE::DEVICE_GRAY)
207
+ end
208
+
209
+ insn 'g', Real do |canvas, c|
210
+ canvas.gs.set_nonstroking_color([c], Graphics::Color::SPACE::DEVICE_GRAY)
211
+ end
212
+
213
+ insn 'RG', Real, Real, Real do |canvas, r, g, b|
214
+ canvas.gs.set_stroking_color([r, g, b], Graphics::Color::Space::DEVICE_RGB)
215
+ end
216
+
217
+ insn 'rg', Real, Real, Real do |canvas, r, g, b|
218
+ canvas.gs.set_nonstroking_color([r, g, b], Graphics::Color::Space::DEVICE_RGB)
219
+ end
220
+
221
+ insn 'K', Real, Real, Real, Real do |canvas, c, m, y, k|
222
+ canvas.gs.set_stroking_color([c, m, y, k], Graphics::Color::Space::DEVICE_CMYK)
223
+ end
224
+
225
+ insn 'k', Real, Real, Real, Real do |canvas, c, m, y, k|
226
+ canvas.gs.set_nonstroking_color([c, m, y, k], Graphics::Color::Space::DEVICE_CMYK)
201
227
  end
202
228
  end
203
229