jfreeze-ruby-gdsii 1.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 (54) hide show
  1. data/CHANGELOG.txt +6 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.txt +113 -0
  4. data/Rakefile +30 -0
  5. data/bin/rgds-debug +43 -0
  6. data/bin/rgds-dump +38 -0
  7. data/bin/rgds-join +98 -0
  8. data/bin/rgds-layers +53 -0
  9. data/bin/rgds-sremove +136 -0
  10. data/bin/rgds-ssplit +113 -0
  11. data/bin/rgds-stats +134 -0
  12. data/bin/rgds-structs +41 -0
  13. data/bin/rgds-tree +167 -0
  14. data/bin/rgds2rb +99 -0
  15. data/lib/gdsii.rb +137 -0
  16. data/lib/gdsii/aref.rb +243 -0
  17. data/lib/gdsii/bnf.rb +309 -0
  18. data/lib/gdsii/boundary.rb +53 -0
  19. data/lib/gdsii/box.rb +65 -0
  20. data/lib/gdsii/byte_order.rb +36 -0
  21. data/lib/gdsii/element.rb +172 -0
  22. data/lib/gdsii/group.rb +98 -0
  23. data/lib/gdsii/library.rb +518 -0
  24. data/lib/gdsii/mixins.rb +378 -0
  25. data/lib/gdsii/node.rb +65 -0
  26. data/lib/gdsii/path.rb +169 -0
  27. data/lib/gdsii/property.rb +108 -0
  28. data/lib/gdsii/record.rb +606 -0
  29. data/lib/gdsii/record/consts.rb +384 -0
  30. data/lib/gdsii/record/datatypes/ascii.rb +145 -0
  31. data/lib/gdsii/record/datatypes/bitarray.rb +101 -0
  32. data/lib/gdsii/record/datatypes/data.rb +111 -0
  33. data/lib/gdsii/record/datatypes/int2.rb +67 -0
  34. data/lib/gdsii/record/datatypes/int4.rb +65 -0
  35. data/lib/gdsii/record/datatypes/nodata.rb +60 -0
  36. data/lib/gdsii/record/datatypes/real4.rb +51 -0
  37. data/lib/gdsii/record/datatypes/real8.rb +120 -0
  38. data/lib/gdsii/sref.rb +61 -0
  39. data/lib/gdsii/strans.rb +133 -0
  40. data/lib/gdsii/structure.rb +352 -0
  41. data/lib/gdsii/text.rb +203 -0
  42. data/pkg/ruby-gdsii.gem +46 -0
  43. data/samples/hello.gds +0 -0
  44. data/samples/hello.out.rb +84 -0
  45. data/samples/hello.rb +94 -0
  46. data/test/baseline/dcp1.gds +0 -0
  47. data/test/baseline/h_write.gds +0 -0
  48. data/test/h_pthru.rb +22 -0
  49. data/test/h_write.rb +117 -0
  50. data/test/hs_pthru.rb +31 -0
  51. data/test/l_pthru.rb +23 -0
  52. data/test/test_gds_group.rb +379 -0
  53. data/test/test_gds_record.rb +99 -0
  54. metadata +117 -0
@@ -0,0 +1,384 @@
1
+ module Gdsii
2
+
3
+ #
4
+ # Class to store information about each record type. The Gdsii::RECORD_INFO
5
+ # constant is an array with an index of the record type number and the value
6
+ # being instances of this class. For example:
7
+ #
8
+ # Gdsii::RECORD_INFO[Gdsii::GRT_HEADER].name #=> 'HEADER'
9
+ # Gdsii::RECORD_INFO[Gdsii::GRT_XY].min_len #=> 4
10
+ #
11
+ # See the Gdsii module for a complete listing of record type constants
12
+ # (Gdsii::GRT_*).
13
+ #
14
+ class RecInfo
15
+
16
+ # Name of this record (should be the same as the Gdsii::GRT_* name but
17
+ # without the "Gdsii::GRT_".
18
+ attr_reader :name
19
+
20
+ # Record type is an integer of the constant Gdsii::GRT_* for this record
21
+ # type.
22
+ attr_reader :type
23
+
24
+ # Boolean as to whether or not this record is a valid GDS record (some
25
+ # records are not valid to the GDS specification)
26
+ attr_reader :valid
27
+
28
+ # Data type is an integer of the constant Gdsii::GDT_* for this record.
29
+ # This represents the data type expected for this record.
30
+ attr_reader :data_type
31
+
32
+ # Size indicates how many bytes are necessary to store each element of
33
+ # this record. For example, the size of a Gdsii::GRT_XY record is 8 with a
34
+ # record size of 4 bytes for each record (since it's a Gdsii::GDT_INT4).
35
+ # This means that the minimum number of items needed (#min_items) is 2.
36
+ attr_reader :size
37
+
38
+ # Minimum length (in bytes) to store this record
39
+ attr_reader :min_len
40
+
41
+ # Maximum length (in bytes) to store this record
42
+ attr_reader :max_len
43
+
44
+ # Object constructor. Intended to be used internally only to add elements
45
+ # to the Gdsii::RECORD_INFO array.
46
+ def initialize(name, data_type, valid, size, min_len, max_len)
47
+ @name = name
48
+ @data_type = data_type
49
+ @valid = valid
50
+ @size = size
51
+ @min_len = min_len
52
+ @max_len = max_len
53
+ end
54
+
55
+ # Returns the minimum number of items necessary for this record type.
56
+ def min_items
57
+ case @data_type
58
+ when GDT_NO_DATA : 0
59
+ when GDT_ASCII : (@size == 0) ? 1 : @min_len/@size
60
+ else
61
+ @min_len/@size
62
+ end
63
+ end
64
+
65
+ # Returns the maximum number of items necessary for this record type.
66
+ def max_items
67
+ case @data_type
68
+ when GDT_NO_DATA : 0
69
+ when GDT_ASCII : (@size == 0) ? 1 : @max_len/@size
70
+ else
71
+ @max_len/@size
72
+ end
73
+ end
74
+
75
+ # Returns true if this object has only a single datum; false if it can
76
+ # have multiple data.
77
+ def single_data_value?
78
+ if @data_type == GDT_ASCII
79
+ @size == 0
80
+ else
81
+ @size == @min_len and @size == @max_len
82
+ end
83
+ end
84
+
85
+ # Converts this record to a string (returns the record's name)
86
+ def to_s(); @name; end
87
+
88
+ end
89
+
90
+ ############################################################################
91
+
92
+ #
93
+ # Class to store information about each record data type. The
94
+ # Gdsii::DATATYPE_INFO array has an index of GDT_* integer values with the
95
+ # value being an instance of this class. For example:
96
+ #
97
+ # Gdsii::RECORD_INFO[Gdsii::GDT_ASCII].name #=> 'ASCII'
98
+ # Gdsii::RECORD_INFO[Gdsii::GDT_REAL4].valid #=> false
99
+ #
100
+ class RecDataTypeInfo
101
+
102
+ # Name of this record data type (should be the same as the Gdsii::GDT_* name
103
+ # but without the "Gdsii::GDT_".
104
+ attr_reader :name
105
+
106
+ # Boolean value indicating whether or not this record data type is valid.
107
+ attr_reader :valid
108
+
109
+ # Integer value indicating the size (in bytes) required for this data
110
+ # type. The exception is Gdsii::GDT_ASCII which has a size of 0 but in
111
+ # actuality has a variable-length size.
112
+ attr_reader :size
113
+
114
+ # Object constructor. Intended to be used internally only to add elements
115
+ # to the Gdsii::DATATYPE_INFO array.
116
+ def initialize(name, valid, size)
117
+ @name = name
118
+ @valid = valid
119
+ @size = size
120
+ end
121
+
122
+ # Returns the data type's name
123
+ def to_s(); @name; end
124
+
125
+ end
126
+
127
+ ############################################################################
128
+
129
+ # These numbers correspond to GDSII format indicating the path types
130
+ if not defined?(PATHTYPE_FLUSH)
131
+ PATHTYPE_FLUSH = 0
132
+ PATHTYPE_ROUND = 1
133
+ PATHTYPE_EXTEND = 2
134
+ PATHTYPE_CUSTOM = 4
135
+
136
+ # These numbers correspond to GDSII format
137
+ FORMAT_GDSII_ARCHIVE = 0
138
+ FORMAT_GDSII_FILTERED = 1
139
+ FORMAT_EDSIII_ARCHIVE = 2
140
+ FORMAT_EDSIII_FILTERED = 3
141
+
142
+ # These are GDSII record numbers and correspond to the GDSII number in the
143
+ # GDSII file specification
144
+ GRT_HEADER = 0
145
+ GRT_BGNLIB = 1
146
+ GRT_LIBNAME = 2
147
+ GRT_UNITS = 3
148
+ GRT_ENDLIB = 4
149
+ GRT_BGNSTR = 5
150
+ GRT_STRNAME = 6
151
+ GRT_ENDSTR = 7
152
+ GRT_BOUNDARY = 8
153
+ GRT_PATH = 9
154
+ GRT_SREF = 10
155
+
156
+ GRT_AREF = 11
157
+ GRT_TEXT = 12
158
+ GRT_LAYER = 13
159
+ GRT_DATATYPE = 14
160
+ GRT_WIDTH = 15
161
+ GRT_XY = 16
162
+ GRT_ENDEL = 17
163
+ GRT_SNAME = 18
164
+ GRT_COLROW = 19
165
+ GRT_TEXTNODE = 20
166
+
167
+ GRT_NODE = 21
168
+ GRT_TEXTTYPE = 22
169
+ GRT_PRESENTATION = 23
170
+ GRT_SPACING = 24
171
+ GRT_STRING = 25
172
+ GRT_STRANS = 26
173
+ GRT_MAG = 27
174
+ GRT_ANGLE = 28
175
+ GRT_UINTEGER = 29
176
+ GRT_USTRING = 30
177
+
178
+ GRT_REFLIBS = 31
179
+ GRT_FONTS = 32
180
+ GRT_PATHTYPE = 33
181
+ GRT_GENERATIONS = 34
182
+ GRT_ATTRTABLE = 35
183
+ GRT_STYPTABLE = 36
184
+ GRT_STRTYPE = 37
185
+ GRT_ELFLAGS = 38
186
+ GRT_ELKEY = 39
187
+ GRT_LINKTYPE = 40
188
+
189
+ GRT_LINKKEYS = 41
190
+ GRT_NODETYPE = 42
191
+ GRT_PROPATTR = 43
192
+ GRT_PROPVALUE = 44
193
+ GRT_BOX = 45
194
+ GRT_BOXTYPE = 46
195
+ GRT_PLEX = 47
196
+ GRT_BGNEXTN = 48
197
+ GRT_ENDEXTN = 49
198
+ GRT_TAPENUM = 50
199
+
200
+ GRT_TAPECODE = 51
201
+ GRT_STRCLASS = 52
202
+ GRT_RESERVED = 53
203
+ GRT_FORMAT = 54
204
+ GRT_MASK = 55
205
+ GRT_ENDMASKS = 56
206
+ GRT_LIBDIRSIZE = 57
207
+ GRT_SRFNAME = 58
208
+ GRT_LIBSECUR = 59
209
+ GRT_BORDER = 60
210
+
211
+ GRT_SOFTFENCE = 61
212
+ GRT_HARDFENCE = 62
213
+ GRT_SOFTWIRE = 63
214
+ GRT_HARDWIRE = 64
215
+ GRT_PATHPORT = 65
216
+ GRT_NODEPORT = 66
217
+ GRT_USERCONSTRAINT = 67
218
+ GRT_SPACER_ERROR = 68
219
+ GRT_CONTACT = 69
220
+
221
+ # GDSII record data types
222
+ GDT_NO_DATA = 0
223
+ GDT_BITARRAY = 1
224
+ GDT_INT2 = 2
225
+ GDT_INT4 = 3
226
+ GDT_REAL4 = 4
227
+ GDT_REAL8 = 5
228
+ GDT_ASCII = 6
229
+
230
+ end
231
+
232
+ if not defined?(RECORD_INFO)
233
+
234
+ # Gdsii::RECORD_INFO is an array of Gdsii::RecInfo objects. The array order
235
+ # is significant in that the index of the array is the value of the record
236
+ # and corresponds to the Gdsii::GRT_* constant values. This allows easy
237
+ # validation lookup based upon the record type constants. Example:
238
+ #
239
+ # Gdsii::RECORD_INFO[Gdsii::GRT_HEADER].name # => "HEADER"
240
+ # Gdsii::RECORD_INFO[Gdsii::GRT_HEADER].valid # => true
241
+ # Gdsii::RECORD_INFO[Gdsii::GRT_HEADER].data_type # => 2
242
+ #
243
+ RECORD_INFO =
244
+ [
245
+ # name data_type valid size minlen maxlen num
246
+ RecInfo.new('HEADER', GDT_INT2, true, 2, 2, 2), # 0 - GDS version
247
+ RecInfo.new('BGNLIB', GDT_INT2, true, 2, 24, 24), # 1 - Modification & access time
248
+ RecInfo.new('LIBNAME', GDT_ASCII, true, 0, 0, 65530), # 2
249
+ RecInfo.new('UNITS', GDT_REAL8, true, 8, 16, 16), # 3
250
+ RecInfo.new('ENDLIB', GDT_NO_DATA, true, 0, 0, 0), # 4
251
+ RecInfo.new('BGNSTR', GDT_INT2, true, 2, 24, 24), # 5
252
+
253
+ RecInfo.new('STRNAME', GDT_ASCII, true, 0, 2, 512), # 6
254
+ RecInfo.new('ENDSTR', GDT_NO_DATA, true, 0, 0, 0), # 7
255
+ RecInfo.new('BOUNDARY', GDT_NO_DATA, true, 0, 0, 0), # 8
256
+ RecInfo.new('PATH', GDT_NO_DATA, true, 0, 0, 0), # 9
257
+ RecInfo.new('SREF', GDT_NO_DATA, true, 0, 0, 0), # 10
258
+
259
+ RecInfo.new('AREF', GDT_NO_DATA, true, 0, 0, 0), # 11
260
+ RecInfo.new('TEXT', GDT_NO_DATA, true, 0, 0, 0), # 12
261
+ RecInfo.new('LAYER', GDT_INT2, true, 2, 2, 2), # 13
262
+ RecInfo.new('DATATYPE', GDT_INT2, true, 2, 2, 2), # 14
263
+ RecInfo.new('WIDTH', GDT_INT4, true, 4, 4, 4), # 15
264
+
265
+ RecInfo.new('XY', GDT_INT4, true, 4, 8, 65528), # 16
266
+ RecInfo.new('ENDEL', GDT_NO_DATA, true, 0, 0, 0), # 17
267
+ RecInfo.new('SNAME', GDT_ASCII, true, 0, 2, 65530), # 18
268
+ RecInfo.new('COLROW', GDT_INT2, true, 2, 4, 4), # 19
269
+ RecInfo.new('TEXTNODE', GDT_NO_DATA, true, 0, 0, 0), # 20
270
+
271
+ RecInfo.new('NODE', GDT_NO_DATA, true, 0, 0, 0), # 21
272
+ RecInfo.new('TEXTTYPE', GDT_INT2, true, 2, 2, 2), # 22
273
+ RecInfo.new('PRESENTATION', GDT_BITARRAY, true, 2, 2, 2), # 23
274
+ RecInfo.new('SPACING', 0, false, 0, 0, 0), # 24
275
+ RecInfo.new('STRING', GDT_ASCII, true, 0, 2, 512), # 25
276
+
277
+ RecInfo.new('STRANS', GDT_BITARRAY, true, 2, 2, 2), # 26
278
+ RecInfo.new('MAG', GDT_REAL8, true, 8, 8, 8), # 27
279
+ RecInfo.new('ANGLE', GDT_REAL8, true, 8, 8, 8), # 28
280
+ RecInfo.new('UINTEGER', 0, false, 0, 0, 0), # 29
281
+ RecInfo.new('USTRING', 0, false, 0, 0, 0), # 30
282
+
283
+ RecInfo.new('REFLIBS', GDT_ASCII, true, 44, 88, 748), # 31
284
+ RecInfo.new('FONTS', GDT_ASCII, true, 44, 176, 176), # 32 - paths to text font def files
285
+ RecInfo.new('PATHTYPE', GDT_INT2, true, 2, 2, 2), # 33
286
+ RecInfo.new('GENERATIONS', GDT_INT2, true, 2, 2, 2), # 34
287
+ RecInfo.new('ATTRTABLE', GDT_ASCII, true, 0, 2, 44), # 35 - path of attr def file
288
+
289
+ RecInfo.new('STYPTABLE', 0, false, 0, 0, 0), # 36
290
+ RecInfo.new('STRTYPE', 0, false, 0, 0, 0), # 37
291
+ RecInfo.new('ELFLAGS', GDT_BITARRAY, true, 2, 2, 2), # 38
292
+ RecInfo.new('ELKEY', 0, false, 0, 0, 0), # 39
293
+ RecInfo.new('LINKTYPE', 0, false, 0, 0, 0), # 40
294
+
295
+ RecInfo.new('LINKKEYS', 0, false, 0, 0, 0), # 41
296
+ RecInfo.new('NODETYPE', GDT_INT2, true, 2, 2, 2), # 42
297
+ RecInfo.new('PROPATTR', GDT_INT2, true, 2, 2, 2), # 43
298
+ RecInfo.new('PROPVALUE', GDT_ASCII, true, 0, 2, 126), # 44
299
+ RecInfo.new('BOX', GDT_NO_DATA, true, 0, 0, 0), # 45
300
+
301
+ RecInfo.new('BOXTYPE', GDT_INT2, true, 2, 2, 2), # 46
302
+ RecInfo.new('PLEX', GDT_INT4, true, 4, 4, 4), # 47
303
+ RecInfo.new('BGNEXTN', GDT_INT4, true, 4, 4, 4), # 48
304
+ RecInfo.new('ENDEXTN', GDT_INT4, true, 4, 4, 4), # 49
305
+ RecInfo.new('TAPENUM', GDT_INT2, true, 2, 2, 2), # 50
306
+
307
+ RecInfo.new('TAPECODE', GDT_INT2, true, 2, 12, 12), # 51
308
+ RecInfo.new('STRCLASS', GDT_BITARRAY, true, 2, 2, 2), # 52
309
+ RecInfo.new('RESERVED', 0, false, 0, 0, 0), # 53
310
+ RecInfo.new('FORMAT', GDT_INT2, true, 2, 2, 2), # 54 - GdsiiFormat type
311
+ RecInfo.new('MASK', GDT_ASCII, true, 0, 2, 65530), # 55
312
+
313
+ RecInfo.new('ENDMASKS', GDT_NO_DATA, true, 0, 0, 0), # 56
314
+ RecInfo.new('LIBDIRSIZE', GDT_INT2, true, 2, 2, 2), # 57 - # of pages in lib dir
315
+ RecInfo.new('SRFNAME', GDT_ASCII, true, 0, 2, 65530), # 58 - name of spacing rules file
316
+ RecInfo.new('LIBSECUR', GDT_INT2, true, 2, 6, 192), # 59 - array of ACL data
317
+ RecInfo.new('BORDER', GDT_NO_DATA, true, 0, 0, 0), # 60
318
+
319
+ RecInfo.new('SOFTFENCE', GDT_NO_DATA, true, 0, 0, 0), # 61
320
+ RecInfo.new('HARDFENCE', GDT_NO_DATA, true, 0, 0, 0), # 62
321
+ RecInfo.new('SOFTWIRE', GDT_NO_DATA, true, 0, 0, 0), # 63
322
+ RecInfo.new('HARDWIRE', GDT_NO_DATA, true, 0, 0, 0), # 64
323
+ RecInfo.new('PATHPORT', GDT_NO_DATA, true, 0, 0, 0), # 65
324
+
325
+ RecInfo.new('NODEPORT', GDT_NO_DATA, true, 0, 0, 0), # 66
326
+ RecInfo.new('USERCONSTRAINT', GDT_NO_DATA, true, 0, 0, 0), # 67
327
+ RecInfo.new('SPACER_ERROR', GDT_NO_DATA, true, 0, 0, 0), # 68
328
+ RecInfo.new('CONTACT', GDT_NO_DATA, true, 0, 0, 0), # 69
329
+ ]
330
+
331
+ #
332
+ # Gdsii::DATATYPE_INFO is an array of Gdsii::RecDataTypeInfo objects. The array
333
+ # order is significant in that the index of the array is the value of the
334
+ # record data type constant. This allows easy validation lookup based upon
335
+ # the record data type constants. Example:
336
+ #
337
+ # Gdsii::DATATYPE_INFO[Gdsii::GRT_REAL8].name # => "REAL8"
338
+ # Gdsii::DATATYPE_INFO[Gdsii::GRT_REAL8].valid # => true
339
+ # Gdsii::DATATYPE_INFO[Gdsii::GRT_REAL8].size # => 8
340
+ #
341
+ DATATYPE_INFO =
342
+ [
343
+ RecDataTypeInfo.new('NO_DATA', true, 0 ), # 0
344
+ RecDataTypeInfo.new('BITARRAY', true, 2 ), # 1
345
+ RecDataTypeInfo.new('INT2', true, 2 ), # 2
346
+ RecDataTypeInfo.new('INT4', true, 4 ), # 3
347
+ RecDataTypeInfo.new('REAL4', false, 4 ), # 4
348
+ RecDataTypeInfo.new('REAL8', true, 8 ), # 5
349
+ RecDataTypeInfo.new('ASCII', true, 0 ), # 6 ; string len is variable
350
+ ]
351
+ end
352
+
353
+ # Returns the name for given record type if it is found; if not, then
354
+ # the record number formatted as a String is returned
355
+ def grt_name(grt_number)
356
+ if grt_number.class == Fixnum
357
+ if (0..RECORD_INFO.length-1).member?(grt_number)
358
+ RECORD_INFO[grt_number].name
359
+ else
360
+ grt_number.to_s
361
+ end
362
+ else
363
+ grt_number.inspect
364
+ end
365
+ end
366
+
367
+ # Returns the name for given record data type if it is found; if not, then
368
+ # the record data type number formatted as a String is returned
369
+ def gdt_name(gdt_number)
370
+ if gdt_number.class == Fixnum
371
+ if (0..DATATYPE_INFO.length-1).member?(gdt_number)
372
+ DATATYPE_INFO[gdt_number].name
373
+ else
374
+ gdt_number.to_s
375
+ end
376
+ else
377
+ gdt_number.inspect
378
+ end
379
+ end
380
+
381
+ module_function :grt_name
382
+ module_function :gdt_name
383
+
384
+ end
@@ -0,0 +1,145 @@
1
+ require 'gdsii/record/datatypes/data'
2
+
3
+ module Gdsii
4
+
5
+ module RecData
6
+
7
+ #
8
+ # Class for ASCII data type
9
+ #
10
+ class Ascii < Data
11
+
12
+ # Value is an array of strings. Most Gdsii::RecData::Ascii records only
13
+ # have a single value.
14
+ attr_reader :value
15
+
16
+ # Construct an Gdsii::RecData::Ascii data object. The value is an array
17
+ # of strings (String).
18
+ def initialize(value, record=nil)
19
+ super(GDT_ASCII, value, record)
20
+ end
21
+
22
+ # Set the value for this object; verify that the value items are of
23
+ # type String (or at least can be coerced using "to_s").
24
+ def value=(value)
25
+ @value = Data.coerce_value(value, String, :to_s)
26
+ end
27
+
28
+ # Returns the size of the record *data* in bytes. This will *always*
29
+ # return an even-length (multiple of 2) number since odd-length GDSII
30
+ # strings are padded with null characters when written to a file. If the
31
+ # record type has a specific size (i.e. Gdsii::GRT_FONTS and
32
+ # Gdsii::GRT_REFLIBS) then that size will automatically be used for each
33
+ # element in the value array.
34
+ def byte_size()
35
+ if @record and RECORD_INFO[@record.type].size > 0 then
36
+ RECORD_INFO[@record.type].size * @value.length
37
+ else
38
+ sum = 0
39
+ @value.each do |val|
40
+ sum += (val.length % 2 == 0) ? val.length : val.length + 1
41
+ end
42
+ sum
43
+ end
44
+ end
45
+
46
+ # Return value with stripped off trailing null characters (which are
47
+ # present when reading this record from a file). For example, assuming
48
+ # that a record is read from a file and is a null padded string
49
+ # "hello\0", then:
50
+ #
51
+ # record.inspect #=> ["hello\0"]
52
+ # record.unpad.inspect #=> ["hello"]
53
+ #
54
+ def unpad()
55
+ new_arr = []
56
+ @value.each do |string|
57
+ string = string.dup # to avoid changing the original
58
+ while (string[-1] == 0)
59
+ string.chop!
60
+ end
61
+ new_arr.push string
62
+ end
63
+ new_arr
64
+ end
65
+
66
+ # Same as #unpad but modifies the value of this object.
67
+ def unpad!()
68
+ @value = unpad
69
+ end
70
+
71
+ # Pad the value with a null character if the string is odd (but do not
72
+ # change the value itself). If a desired string length is given, then
73
+ # the string will be padded by the length given. For example, if we
74
+ # created a record with the string "hello", then padding will add a
75
+ # null character since "hello" has an odd number of characters:
76
+ #
77
+ # record = Gdsii::RecType::Ascii.new(["hello"])
78
+ # record.pad.inspect #=> ["hello\0"]
79
+ #
80
+ def pad(str_length=nil)
81
+ new_arr = []
82
+ @value.each_with_index do |string, i|
83
+ string = string.dup # to avoid changing the original
84
+ if str_length.nil? then
85
+ # pad if the string is odd or use the size property if there is a
86
+ # predefined size
87
+ if @record and (size=RECORD_INFO[@record.type].size) > 0 then
88
+ new_arr.push [string].pack("a#{size}")
89
+ elsif (len=string.length)%2 == 1 then
90
+ new_arr.push [string].pack("a#{len+1}")
91
+ else
92
+ new_arr.push string
93
+ end
94
+ else
95
+ # A desired string length was given; ensure that the requested
96
+ # length is a multiple of 2 and is not less than the string given.
97
+ if str_length%2 == 1 then
98
+ raise ArgumentError,
99
+ "Desired string length must be a multiple of 2"
100
+ elsif str_length < string.length then
101
+ raise ArgumentError,
102
+ "Desired string length given #{str_length} is less than actual string length #{string.length}"
103
+ else
104
+ new_arr.push [string].pack("a#{str_length}")
105
+ end
106
+ end
107
+ end
108
+ new_arr
109
+ end
110
+
111
+ # Same as #pad except the value of this object is modified.
112
+ def pad!(str_length=nil)
113
+ @value = pad(str_length)
114
+ end
115
+
116
+ # Reads an ASCII record from the given file for the length of bytes
117
+ # given and returns a new Gdsii::RecData::Ascii object.
118
+ def Ascii.read(file, byte_count)
119
+ # Verify byte count is even
120
+ if byte_count%2 == 1 then
121
+ raise ArgumentError,
122
+ "GDT_ASCII records must have an even length; requested: #{byte_count}"
123
+ end
124
+
125
+ # read the string in; unpad; and return the new Gdsii::Ascii object
126
+ raw = file.read(byte_count)
127
+ string = raw.unpack("a#{byte_count}")
128
+ data = Ascii.new(string)
129
+ data.unpad!
130
+ end
131
+
132
+ # Writes the string values in this Gdsii::RecData::Ascii object to the
133
+ # given file as a GDSII ASCII record.
134
+ def write(file)
135
+ padded_str = self.pad
136
+ file.write padded_str
137
+ end
138
+
139
+ # Joins all strings in the array with spaces and returns the joined
140
+ # string.
141
+ def to_s(); self.unpad[0] end
142
+
143
+ end
144
+ end
145
+ end