prawn-shadings 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/BSDL ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (C) 2013 Alexander Mankuta. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
+ SUCH DAMAGE.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source :rubygems
2
+
3
+ gem 'prawn', github: 'prawnpdf/prawn'
4
+
5
+ group :developemnt do
6
+ gem 'rdoc'
7
+ end
8
+
9
+ group :test do
10
+ gem "pdf-inspector", "~> 1.0.2", :require => "pdf/inspector"
11
+ gem "rspec"
12
+ gem "rake"
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,56 @@
1
+ Prawn-Shadings is copyrighted free software by Alexander Mankuta <alex@pointlessone.org>.
2
+ You can redistribute it and/or modify it under either the terms of the
3
+ 2-clause BSDL (see the file BSDL), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) give non-standard binaries non-standard names, with
21
+ instructions on where to get the original software distribution.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or binary form,
26
+ provided that you do at least ONE of the following:
27
+
28
+ a) distribute the binaries and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard binaries non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under these terms.
43
+
44
+ For the list of those files and their copying conditions, see the
45
+ file LEGAL.
46
+
47
+ 5. The scripts and library files supplied as input to or produced as
48
+ output from the software do not automatically fall under the
49
+ copyright of the software, but belong to whomever generated them,
50
+ and may be sold commercially, and may be aggregated with this
51
+ software.
52
+
53
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+
2
+ Prawn Shadings
3
+ ==============
4
+
5
+ [![Build Status](https://travis-ci.org/cheba/prawn-shadings.png)](https://travis-ci.org/cheba/prawn-shadings)
6
+
7
+ This is a Prawn extension that implements advanced PDF shadings.
8
+
9
+ Specifically, it a bit refactors already implemented linear and radial gradients
10
+ and adds a few more advanced ones.
11
+
12
+ Currently implemented shadings:
13
+
14
+ * Axial Shadings (or linear gradients) (PDF Type 2 Shading)
15
+ * Radial Shading (PDF Type 3 Shading)
16
+ * Free-Form Gouraud-Shaded Triangle Meshes (PDF Type 4 Shading)
17
+ * Lattice-Form Gouraud-Shaded Triangle Meshes (PDF Type 5 Shading)
18
+ * Coons Patch Meshes (PDF Type 6 Shading)
19
+ * Tensor-Product Patch Meshes (PDF Type 7 Shading)
20
+
21
+ API is simple but you need to know what data to supply. Please get familiar with
22
+ Section 4.6.3 of PDF Reference.
23
+
24
+
25
+ A Word of Warning
26
+ -----------------
27
+
28
+ Please note that this gem gives you ability use all these shadings in your
29
+ generated PDF but gives no guarantee that PDF will be rendered correctly.
30
+
31
+ Just a few examples:
32
+
33
+ * OS X Preview has problems with flags other than 0 in Type 4, 6 and 7 type
34
+ shadings.
35
+ * OS X Previews renders Type 7 shadings exactly the same as Type 6 shadings
36
+ ignoring all 4 extra control points.
37
+ * PDF.js can not render pretty much any of these.
38
+
39
+ Installation
40
+ ------------
41
+
42
+ gem install prawn-shadings
43
+
44
+
45
+ Usage
46
+ -----
47
+
48
+ require 'prawn/shadings'
49
+
50
+
51
+ Testing
52
+ -------
53
+
54
+ To run the tests:
55
+
56
+ $ rake
57
+
58
+
59
+ Contributing
60
+ ------------
61
+
62
+ 1. Fork it.
63
+ 2. Create a branch (`git checkout -b my_shading`)
64
+ 3. Commit your changes (`git commit -am "Added Shading"`)
65
+ 4. Push to the branch (`git push origin my_shading`)
66
+ 5. Open a [Pull Request][1]
67
+
68
+ [1]: http://github.com/cheba/prawn-shadings/pulls
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rdoc/task'
3
+
4
+ desc "genrates documentation"
5
+ RDoc::Task.new do |rdoc|
6
+ rdoc.rdoc_files.include( "README.md",
7
+ "LICENSE", "BSDL",
8
+ "lib/" )
9
+ rdoc.main = "README.md"
10
+ rdoc.rdoc_dir = "doc/html"
11
+ rdoc.title = "Prawn Shadings Documentation"
12
+ end
13
+
14
+ desc "Run all rspec files"
15
+ RSpec::Core::RakeTask.new("spec")
16
+
17
+ desc "Run tests"
18
+ task default: :spec
@@ -0,0 +1 @@
1
+ require 'prawn/shadings'
@@ -0,0 +1,649 @@
1
+ # encoding: utf-8
2
+
3
+ require 'prawn/graphics/patterns'
4
+
5
+ module Prawn
6
+ module Graphics
7
+
8
+ ##
9
+ # This module implements Shading patterns.
10
+ #
11
+ # Both fill_gradient and stroke_gradient accept absolutely identical sets of
12
+ # arguments. The only difference, obviously, is that one sets fill shading
13
+ # and the other sets stroke shading.
14
+ #
15
+ # == Basic gradients
16
+ #
17
+ # Linear and radial gradients are one of the simplest shadings supported by
18
+ # PDF.
19
+ #
20
+ # === Linear gradient
21
+ #
22
+ # fill_gradient(from, to, color1, color2)
23
+ #
24
+ # from::
25
+ # This is the starting point of the gradient. It must be an arrays of
26
+ # two values: <tt>[x, y]</tt>.
27
+ #
28
+ # to::
29
+ # This is the ending point of the gradient. Format is the same:
30
+ # <tt>[x, y]</tt>.
31
+ #
32
+ # color1::
33
+ # Color of the gradient at the starting point.
34
+ #
35
+ # color2::
36
+ # Color of the gradient at the ending point of the gradient.
37
+ #
38
+ # Please note that both colors must be in the same color space.
39
+ #
40
+ # === Radial gradient
41
+ #
42
+ # fill_gradient(from, r1, to, r2, color1, color2)
43
+ #
44
+ # from::
45
+ # This is the starting point of the gradient. It must be an arrays of
46
+ # two values: <tt>[x, y]</tt>.
47
+ #
48
+ # r1::
49
+ # Radius of the starting circle.
50
+ #
51
+ # to::
52
+ # This is the ending point of the gradient. Format is the same:
53
+ # <tt>[x, y]</tt>.
54
+ #
55
+ # r2::
56
+ # Radius of the ending circle.
57
+ #
58
+ # color1::
59
+ # Color of the gradient at the starting point.
60
+ #
61
+ # color2::
62
+ # Color of the gradient at the ending point of the gradient.
63
+ #
64
+ # Please note that both colors must be in the same color space.
65
+ #
66
+ #
67
+ # == More complex gradients
68
+ #
69
+ # PDF is capable of creating very complex shadings. Though, this
70
+ # functionality is rarely used.
71
+ #
72
+ # Because this gradients require variable amounts of data to be properly
73
+ # constructed gradient methods are called with a single parameter stating
74
+ # the type of gradient you want to produce and a block that returns actual
75
+ # data for gradient.
76
+ #
77
+ # [Note]
78
+ # This types of shading are not supported by all renderers.
79
+ # For example PDF.js doesn't support any of this shadings. OS X Preview
80
+ # (10.8) has problems with flags other than 0 and renders Tensor-Product
81
+ # Patch Meshes exactly the same as Coons Patch Meshes.
82
+ #
83
+ # === Free-Form Gouraud-Shaded Triangle Meshes
84
+ #
85
+ # fill_gradient(:ffgstm) {
86
+ # [
87
+ # # vertices
88
+ # ]
89
+ # }
90
+ #
91
+ # The data is an Array of vertices. Each vertex has the following format:
92
+ # <tt>[flag, x, y, color]</tt>.
93
+ #
94
+ # Colors of all vertices must be in the same color space.
95
+ #
96
+ # Please refer to <b>PDF Refernce, Section 4.6.3, Shading Types, Type
97
+ # 4 Shadings</b> for more details on the meaning of the vertex fields.
98
+ #
99
+ #
100
+ # === Lattice-Form Gouraud-Shaded Triangle Meshes
101
+ #
102
+ # fill_gradient(:lfgstm) {
103
+ # [
104
+ # # data
105
+ # ]
106
+ # }
107
+ #
108
+ # The data is a rectangular matrix of vertices. That is an array of rows (arrays)
109
+ # of vertices. It must have at least 2 rows and at least 2 column. Each vertex has
110
+ # the following format: <tt>[x, y, color]</tt>.
111
+ #
112
+ # Colors of all vertices must be in the same color space.
113
+ #
114
+ # Please refer to <b>PDF Refernce, Section 4.6.3, Shading Types, Type
115
+ # 5 Shadings</b> for more details on the meaning of the vertex fields.
116
+ #
117
+ #
118
+ # === Coons Patch Meshes
119
+ #
120
+ # fill_gradient(:cpm) {
121
+ # [
122
+ # # data
123
+ # ]
124
+ # }
125
+ #
126
+ # The data is an array of patches. There are two types of patches. They're
127
+ # distinguished by their flag parameter.
128
+ #
129
+ # First one is a stand alone patches. Their flag = 0. They have a form of <tt>[0 (flag),
130
+ # x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, x6, y6, x7, y7, x8, y8, x9, y9, x10, y10,
131
+ # x11, y11, x12, y12, c1, c2, c3, c4]</tt>.
132
+ #
133
+ # The second is a edge-sharing patches. Their flag is either 1, 2, or 3. And
134
+ # they have 4 less points (because they borrow them from previous patch) and
135
+ # 2 less colors. They have a form of <tt>[flag, x5, y5, x6, y6, x7, y7, x8, y8, x9, y9,
136
+ # x10, y10, x11, y11, x12, y12, c3, c4]</tt>.
137
+ #
138
+ # Please refer to <b>PDF Refernce, Section 4.6.3, Shading Types, Type
139
+ # 6 Shadings</b> for more details on the meaning of the patch fields.
140
+ #
141
+ # === Tensor-Product Patch Meshes
142
+ #
143
+ # fill_gradient(:tppm) {
144
+ # [
145
+ # # data
146
+ # ]
147
+ # }
148
+ #
149
+ # This type of shading is very similar to Coons Patch Mesh except it has
150
+ # 4 extra pairs of coordinates in every patch, right before the colors.
151
+ #
152
+ # Please refer to <b>PDF Refernce, Section 4.6.3, Shading Types, Type
153
+ # 7 Shadings</b> for more details on the meaning of the patch fields.
154
+ #
155
+ module Patterns
156
+
157
+ ##
158
+ # :call-seq:
159
+ # fill_gradient(from, to, color1, color2)
160
+ # fill_gradient(from, r1, to, r2, color1, color2)
161
+ # fill_gradient(complex_shading_type) { }
162
+ #
163
+ # Sets the fill gradient.
164
+ def fill_gradient(*args, &block)
165
+ if args[1].is_a?(Array) || args[2].is_a?(Array) || args[0].is_a?(Symbol)
166
+ set_gradient(:fill, *args, &block)
167
+ else
168
+ warn "[DEPRECATION] 'fill_gradient(point, width, height,...)' is deprecated in favor of 'fill_gradient(from, to,...)'. " +
169
+ "Old arguments will be removed in release 1.1"
170
+ set_gradient :fill, args[0], [args[0].first, args[0].last - args[2]], args[3], args[4]
171
+ end
172
+ end
173
+
174
+ ##
175
+ # :call-seq:
176
+ # stroke_gradient(from, to, color1, color2)
177
+ # stroke_gradient(from, r1, to, r2, color1, color2)
178
+ # stroke_gradient(complex_shading_type) { }
179
+ #
180
+ # Sets the stroke gradient.
181
+ def stroke_gradient(*args, &block)
182
+ if args[1].is_a?(Array) || args[2].is_a?(Array) || args[0].is_a?(Symbol)
183
+ set_gradient(:stroke, *args, &block)
184
+ else
185
+ warn "[DEPRECATION] 'stroke_gradient(point, width, height,...)' is deprecated in favor of 'stroke_gradient(from, to,...)'. " +
186
+ "Old arguments will be removed in release 1.1"
187
+ set_gradient :stroke, args[0], [args[0].first, args[0].last - args[2]], args[3], args[4]
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ def set_gradient(type, *grad, &block)
194
+ patterns = page.resources[:Pattern] ||= {}
195
+
196
+ registry_key = gradient_registry_key grad, &block
197
+
198
+ if patterns["SP#{registry_key}"]
199
+ shading = patterns["SP#{registry_key}"]
200
+ else
201
+ unless shading = gradient_registry[registry_key]
202
+ shading = gradient(*grad, &block)
203
+ gradient_registry[registry_key] = shading
204
+ end
205
+
206
+ patterns["SP#{registry_key}"] = shading
207
+ end
208
+
209
+ operator = case type
210
+ when :fill
211
+ 'scn'
212
+ when :stroke
213
+ 'SCN'
214
+ else
215
+ raise ArgumentError, "unknown type '#{type}'"
216
+ end
217
+
218
+ set_color_space type, :Pattern
219
+ add_content "/SP#{registry_key} #{operator}"
220
+ end
221
+
222
+ def gradient_registry_key(gradient)
223
+ if gradient[0].is_a?(Symbol) # all kinds of patches
224
+ [
225
+ gradient[0],
226
+ *yield
227
+ ]
228
+ elsif gradient[1].is_a?(Array) # axial
229
+ [
230
+ map_to_absolute(gradient[0]),
231
+ map_to_absolute(gradient[1]),
232
+ gradient[2], gradient[3]
233
+ ]
234
+ else # radial
235
+ [
236
+ map_to_absolute(gradient[0]),
237
+ gradient[1],
238
+ map_to_absolute(gradient[2]),
239
+ gradient[3],
240
+ gradient[4], gradient[5]
241
+ ]
242
+ end.hash
243
+ end
244
+
245
+ def gradient_registry
246
+ @gradient_registry ||= {}
247
+ end
248
+
249
+ def gradient(*args, &block)
250
+ if args.length != 1 && args.length != 4 && args.length != 6
251
+ raise ArgumentError, "Unknown type of gradient: #{args.inspect}"
252
+ end
253
+
254
+ case args.length
255
+ when 1
256
+ case args.first
257
+ when :ffgstm # Free-Form Gouraud-Shaded Triangle Meshes
258
+ shading_type_4(&block)
259
+ when :lfgstm # Lattice-Form Gouraud-Shaded Triangle Meshes
260
+ shading_type_5(&block)
261
+ when :cpm # Coons Patch Meshes
262
+ shading_type_6(&block)
263
+ when :tppm # Tensor-Product Patch Meshes
264
+ shading_type_7(&block)
265
+ else
266
+ raise ArgumentError, "Unknown type of gradient: #{args.inspect}"
267
+ end
268
+ when 4
269
+ shading_type_2(*args)
270
+ when 6
271
+ shading_type_3(*args)
272
+ else
273
+ raise ArgumentError, "Unknown type of gradient: #{args.inspect}"
274
+ end
275
+ end
276
+
277
+ def shading_type_2(from, to, from_color, to_color)
278
+ color1 = normalize_color(from_color).dup.freeze
279
+ color2 = normalize_color(to_color).dup.freeze
280
+
281
+ if color_type(color1) != color_type(color2)
282
+ raise ArgumentError, "Both colors must be of the same color space: #{color1.inspect} and #{color2.inspect}"
283
+ end
284
+
285
+ process_color color1
286
+ process_color color2
287
+
288
+ shader = ref!({
289
+ :FunctionType => 2,
290
+ :Domain => [0.0, 1.0],
291
+ :C0 => color1,
292
+ :C1 => color2,
293
+ :N => 1.0,
294
+ })
295
+
296
+ shading = ref!({
297
+ :ShadingType => 2, # axial shading
298
+ :ColorSpace => color_space(color1),
299
+ :Coords => [0, 0, to.first - from.first, to.last - from.last],
300
+ :Function => shader,
301
+ :Extend => [true, true],
302
+ })
303
+
304
+ ref!({
305
+ :PatternType => 2, # shading pattern
306
+ :Shading => shading,
307
+ :Matrix => [1, 0, 0, 1] + map_to_absolute(from),
308
+ })
309
+ end
310
+
311
+ def shading_type_3(from, r1, to, r2, from_color, to_color)
312
+ color1 = normalize_color(from_color).dup.freeze
313
+ color2 = normalize_color(to_color).dup.freeze
314
+
315
+ if color_type(color1) != color_type(color2)
316
+ raise ArgumentError, "Both colors must be of the same color space: #{color1.inspect} and #{color2.inspect}"
317
+ end
318
+
319
+ process_color color1
320
+ process_color color2
321
+
322
+ shader = ref!({
323
+ :FunctionType => 2,
324
+ :Domain => [0.0, 1.0],
325
+ :C0 => color1,
326
+ :C1 => color2,
327
+ :N => 1.0,
328
+ })
329
+
330
+ shading = ref!({
331
+ :ShadingType => 3, # radial shading
332
+ :ColorSpace => color_space(color1),
333
+ :Coords => [0, 0, r1, to.first - from.first, to.last - from.last, r2],
334
+ :Function => shader,
335
+ :Extend => [true, true],
336
+ })
337
+
338
+ ref!({
339
+ :PatternType => 2, # shading pattern
340
+ :Shading => shading,
341
+ :Matrix => [1, 0, 0, 1] + map_to_absolute(from),
342
+ })
343
+ end
344
+
345
+ def shading_type_4
346
+ data = yield
347
+
348
+ x_min = x_max = data[0][1]
349
+ y_min = y_max = data[0][2]
350
+ data_color_space = color_space(data[0][3])
351
+
352
+ data.each do |(_, x, y, c, extra)|
353
+ x_min = x if x_min > x
354
+ x_max = x if x_max < x
355
+ y_min = y if y_min > y
356
+ y_max = y if y_max < y
357
+ if color_space(c) != data_color_space
358
+ raise ArgumentError, "Colors of all vertices must be in the same color space. Expected #{data_color_space} got #{color_space(c)}"
359
+ end
360
+ if extra
361
+ raise ArgumentError, "Unexpected extra data in vertices stream: #{extra.inspect}"
362
+ end
363
+ end
364
+ x_min = x_min.to_f
365
+ x_max = x_max.to_f
366
+ y_min = y_min.to_f
367
+ y_max = y_max.to_f
368
+ dx = x_max - x_min
369
+ dy = y_max - y_min
370
+
371
+
372
+ shading = ref!({
373
+ :ShadingType => 4, # Free-Form Gouraud-Shaded Triangle Meshes
374
+ :ColorSpace => data_color_space,
375
+ :BitsPerCoordinate => 32,
376
+ :BitsPerComponent => 8,
377
+ :BitsPerFlag => 8,
378
+ :Decode => [x_min, x_max, y_min, y_max, 0, 1, 0, 1, 0, 1]
379
+ })
380
+
381
+ data.each do |(f, x, y, color)|
382
+ unless [0, 1, 2].include? f
383
+ raise ArgumentError, "Flag must be 0, 1, or 2. Got #{f}."
384
+ end
385
+
386
+ shading << [
387
+ f,
388
+ normalize_coord(x, x_min, dx), normalize_coord(y, y_min, dy),
389
+ *(normalize_color(color).map{|c| (c * 255.0).round })
390
+ ].pack('CNNC*')
391
+ end
392
+
393
+
394
+ ref!({
395
+ :PatternType => 2, # shading pattern
396
+ :Shading => shading,
397
+ :Matrix => [1, 0,
398
+ 0, 1] + map_to_absolute([0, 0]),
399
+ })
400
+ end
401
+
402
+ def shading_type_5
403
+ data = yield
404
+
405
+ x_min = x_max = data[0][0][0]
406
+ y_min = y_max = data[0][0][1]
407
+ data_color_space = color_space(data[0][0][2])
408
+
409
+ if data.length < 2
410
+ raise ArgumentError, "Lattice-Form Gouraud-Shaded Triangle Meshes Shading requires at least 2 rows of vertices"
411
+ end
412
+
413
+ vertices_per_row = data[0].length
414
+ if vertices_per_row < 2
415
+ raise ArgumentError, "Lattice-Form Gouraud-Shaded Triangle Meshes Shading requires at least 2 vertices per row"
416
+ end
417
+
418
+ data.each do |row|
419
+ if vertices_per_row != row.length
420
+ raise ArgumentError, "Lattice-Form Gouraud-Shaded Triangle Meshes Shading requires data to be organized in rectangular matrix"
421
+ end
422
+
423
+ row.each do |(x, y, c)|
424
+ x_min = x if x_min > x
425
+ x_max = x if x_max < x
426
+ y_min = y if y_min > y
427
+ y_max = y if y_max < y
428
+
429
+ if color_space(c) != data_color_space
430
+ raise ArgumentError, "Colors of all vertices must be in the same color space. Expected #{data_color_space} got #{color_space(c)}"
431
+ end
432
+ end
433
+ end
434
+ x_min = x_min.to_f
435
+ x_max = x_max.to_f
436
+ y_min = y_min.to_f
437
+ y_max = y_max.to_f
438
+ dx = x_max - x_min
439
+ dy = y_max - y_min
440
+
441
+
442
+ shading = ref!({
443
+ :ShadingType => 5, # Lattice-Form Gouraud-Shaded Triangle Meshes
444
+ :ColorSpace => :DeviceRGB,
445
+ :BitsPerCoordinate => 32,
446
+ :BitsPerComponent => 8,
447
+ :VerticesPerRow => data[0].length,
448
+ :Decode => [x_min, x_max, y_min, y_max, 0, 1, 0, 1, 0, 1]
449
+ })
450
+
451
+
452
+ data.each do |row|
453
+ row.each do |(x, y, color)|
454
+ shading << [
455
+ normalize_coord(x, x_min, dx), normalize_coord(y, y_min, dy),
456
+ *(normalize_color(color).map{|c| (c * 255.0).round })
457
+ ].pack('NNC*')
458
+ end
459
+ end
460
+
461
+
462
+ ref!({
463
+ :PatternType => 2, # shading pattern
464
+ :Shading => shading,
465
+ :Matrix => [1, 0,
466
+ 0, 1] + map_to_absolute([0, 0]),
467
+ })
468
+ end
469
+
470
+ def shading_type_6
471
+ data = yield
472
+
473
+ x_min = x_max = data[0][1]
474
+ y_min = y_max = data[0][2]
475
+ data_color_space = color_space(data[0][25])
476
+
477
+ data.each do |values|
478
+ f = values[0]
479
+ coords = if f == 0
480
+ values[1..24]
481
+ else
482
+ values[1..16]
483
+ end
484
+ coords.each_with_index do |c, i|
485
+ if i.even? # x coordinate
486
+ x_min = c if x_min > c
487
+ x_max = c if x_max < c
488
+ else # y coordinate
489
+ y_min = c if y_min > c
490
+ y_max = c if y_max < c
491
+ end
492
+ end
493
+ end
494
+ x_min = x_min.to_f
495
+ x_max = x_max.to_f
496
+ y_min = y_min.to_f
497
+ y_max = y_max.to_f
498
+ dx = x_max - x_min
499
+ dy = y_max - y_min
500
+
501
+
502
+ shading = ref!({
503
+ :ShadingType => 6, # Coons Patch Meshes
504
+ :ColorSpace => data_color_space,
505
+ :BitsPerCoordinate => 32,
506
+ :BitsPerComponent => 8,
507
+ :BitsPerFlag => 8,
508
+ :Decode => [x_min, x_max, y_min, y_max, 0, 1, 0, 1, 0, 1]
509
+ })
510
+
511
+
512
+ data.each do |values|
513
+ f = values.shift
514
+ unless [0, 1, 2, 3].include? f
515
+ raise ArgumentError, "Flag must be 0, 1, 2, or 3. Got #{f}."
516
+ end
517
+
518
+ if f == 0
519
+ coords = values.shift(24)
520
+ colors = values.shift(4)
521
+ else
522
+ coords = values.shift(16)
523
+ colors = values.shift(2)
524
+ end
525
+
526
+ shading << [f].pack('C')
527
+
528
+ coords.each_with_index do |c, i|
529
+ if i.even? # x coordinate
530
+ shading << [normalize_coord(c, x_min, dx)].pack('N')
531
+ else
532
+ shading << [normalize_coord(c, y_min, dy)].pack('N')
533
+ end
534
+ end
535
+
536
+ colors.each do |color|
537
+ if color_space(color) != data_color_space
538
+ raise ArgumentError, "Colors of all vertices must be in the same color space. Expected #{data_color_space} got #{color_space(color)}"
539
+ end
540
+ shading << normalize_color(color).map{|c| (c * 255.0).round }.pack('C*')
541
+ end
542
+ end
543
+
544
+
545
+ ref!({
546
+ :PatternType => 2, # shading pattern
547
+ :Shading => shading,
548
+ :Matrix => [1, 0,
549
+ 0, 1] + map_to_absolute([0, 0]),
550
+ })
551
+ end
552
+
553
+ def shading_type_7
554
+ data = yield
555
+
556
+ x_min = x_max = data[0][1]
557
+ y_min = y_max = data[0][2]
558
+ data_color_space = color_space(data[0][33])
559
+
560
+ data.each do |values|
561
+ f = values[0]
562
+ coords = if f == 0
563
+ values[1..32]
564
+ else
565
+ values[1..24]
566
+ end
567
+ coords.each_with_index do |c, i|
568
+ if i.even? # x coordinate
569
+ x_min = c if x_min > c
570
+ x_max = c if x_max < c
571
+ else # y coordinate
572
+ y_min = c if y_min > c
573
+ y_max = c if y_max < c
574
+ end
575
+ end
576
+ end
577
+ x_min = x_min.to_f
578
+ x_max = x_max.to_f
579
+ y_min = y_min.to_f
580
+ y_max = y_max.to_f
581
+ dx = x_max - x_min
582
+ dy = y_max - y_min
583
+
584
+
585
+ shading = ref!({
586
+ :ShadingType => 7, # Tensor-Product Patch Meshes
587
+ :ColorSpace => data_color_space,
588
+ :BitsPerCoordinate => 32,
589
+ :BitsPerComponent => 8,
590
+ :BitsPerFlag => 8,
591
+ :Decode => [x_min, x_max, y_min, y_max, 0, 1, 0, 1, 0, 1]
592
+ })
593
+
594
+
595
+ data.each do |values|
596
+ f = values.shift
597
+ unless [0, 1, 2, 3].include? f
598
+ raise ArgumentError, "Flag must be 0, 1, 2, or 3. Got #{f}."
599
+ end
600
+
601
+ if f == 0
602
+ coords = values.shift(32)
603
+ colors = values.shift(4)
604
+ else
605
+ coords = values.shift(24)
606
+ colors = values.shift(2)
607
+ end
608
+
609
+ shading << [f].pack('C')
610
+
611
+ coords.each_with_index do |c, i|
612
+ if i.even? # x coordinate
613
+ shading << [normalize_coord(c, x_min, dx)].pack('N')
614
+ else
615
+ shading << [normalize_coord(c, y_min, dy)].pack('N')
616
+ end
617
+ end
618
+
619
+ colors.each do |color|
620
+ if color_space(color) != data_color_space
621
+ raise ArgumentError, "Colors of all vertices must be in the same color space. Expected #{data_color_space} got #{color_space(color)}"
622
+ end
623
+ shading << normalize_color(color).map{|c| (c * 255.0).round }.pack('C*')
624
+ end
625
+ end
626
+
627
+
628
+ ref!({
629
+ :PatternType => 2, # shading pattern
630
+ :Shading => shading,
631
+ :Matrix => [1, 0,
632
+ 0, 1] + map_to_absolute([0, 0]),
633
+ })
634
+ end
635
+
636
+ def normalize_coord(coord, min, delta)
637
+ diff = (coord.to_f - min) / delta
638
+ if diff > 1
639
+ diff = 1.0
640
+ elsif diff < 0
641
+ diff = 0.0;
642
+ end
643
+
644
+ (diff * 0xffffffff).round
645
+ end
646
+ end
647
+ end
648
+ end
649
+