rfil 0.2

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.
data/lib/tex/tfm.rb ADDED
@@ -0,0 +1,1198 @@
1
+ # tfm.rb - Access information of a TeX font metric file.
2
+ #--
3
+ # Last Change: Tue May 16 19:12:26 2006
4
+ #++
5
+
6
+ module TeX # :nodoc:
7
+
8
+ # TFM (TeX font metric) reader/writer class
9
+ class TFM
10
+ class TFMReader
11
+ # reading a tfm file is about 10 times faster than doing
12
+ # `tftop xyz.pl` and using PL#parse. And only a bit slower than
13
+ # `tftop xyz.pl > /dev/null` alone. (1.3 secs. vs. 0.9 secs. - 10 times)
14
+
15
+ # Output more information
16
+ attr_accessor :verbose
17
+
18
+ LIGTAG=1
19
+ STOPFLAG=128
20
+ KERNFLAG=128
21
+ LIGSIZE=5000
22
+ class TFMError < Exception
23
+ end
24
+
25
+ def initialize(tfmobject=nil)
26
+ # type of font: textfont (:vanilla), math symbols (:mathsy), math
27
+ # extension (:mathex)
28
+ @font_type=nil
29
+
30
+ @perfect=true
31
+
32
+ # this is where we store all our data
33
+ @tfm=if tfmobject
34
+ tfmobject
35
+ else
36
+ TFM.new
37
+ end
38
+ end # initialize
39
+
40
+ # _tfmdata_ is a string with the contents of the tfm (binary) file.
41
+ def parse(tfmdata)
42
+ @tfmdata=tfmdata.unpack("C*")
43
+ @index=0
44
+
45
+ @lf=get_dbyte
46
+ @lh=get_dbyte
47
+ @bc=get_dbyte
48
+ @ec=get_dbyte
49
+ @nw=get_dbyte
50
+ @nh=get_dbyte
51
+ @nd=get_dbyte
52
+ @ni=get_dbyte
53
+ @nl=get_dbyte
54
+ @nk=get_dbyte
55
+ @ne=get_dbyte
56
+ @np=get_dbyte
57
+
58
+ if @verbose
59
+ puts "lf=#{@lf}"
60
+ puts "lh=#{@lh}"
61
+ puts "bc=#{@bc}"
62
+ puts "ec=#{@ec}"
63
+ puts "nw=#{@nw}"
64
+ puts "nh=#{@nh}"
65
+ puts "nd=#{@nd}"
66
+ puts "ni=#{@ni}"
67
+ puts "nl=#{@nl}"
68
+ puts "nk=#{@nk}"
69
+ puts "ne=#{@ne}"
70
+ puts "np=#{@np}"
71
+ end
72
+ raise TFMError, "The following condition is not true: bc-1 <= ec and ec <= 255" unless @bc-1 <= @ec and @ec <= 255
73
+ raise TFMError, "The following condition is not true: ne <= 256" unless @ne <= 256
74
+ raise TFMError, "The following condition is not true: lf == 6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ne+np" unless @lf == 6+@lh+(@ec-@bc+1)+@nw+@nh+@nd+@ni+@nl+@nk+@ne+@np
75
+
76
+ # � 23
77
+ @header_base = 6
78
+ @char_base = @header_base + @lh
79
+ @width_base = @char_base + (@ec - @bc) + 1
80
+ @height_base = @width_base + @nw
81
+ @depth_base = @height_base + @nh
82
+ @italic_base = @depth_base + @nd
83
+ @lig_kern_base = @italic_base + @ni
84
+ @kern_base = @lig_kern_base + @nl
85
+ @exten_base = @kern_base + @nk
86
+ @param_base = @exten_base + @ne
87
+
88
+ parse_header
89
+ parse_params
90
+ parse_char_info
91
+ parse_lig_kern
92
+ # exten?
93
+
94
+ return @tfm
95
+ end # parse
96
+
97
+ #######
98
+ private
99
+ #######
100
+
101
+ def parse_header
102
+ @index = @header_base * 4
103
+ @tfm.checksum=get_qbyte
104
+ @tfm.designsize=get_fix_word
105
+ if @lh >= 3
106
+ count = get_byte
107
+ @tfm.codingscheme=get_chars(count)
108
+ @font_type= case @tfm.codingscheme
109
+ when "TeX math symbols"
110
+ :mathsy
111
+ when "TeX math extension"
112
+ :mathex
113
+ else
114
+ :vanilla
115
+ end
116
+ end
117
+ @index = (@header_base + 12) * 4
118
+ if @lh > 12
119
+ count = get_byte
120
+ @tfm.fontfamily=get_chars(count)
121
+ end
122
+ @index = (@header_base + 17 ) * 4
123
+ if @lh >= 17
124
+ @tfm.sevenbitsafeflag=get_byte > 127
125
+ # two bytes ignored
126
+ get_byte ; get_byte
127
+ @tfm.face=get_byte
128
+ end
129
+ # let us ignore the rest of the header (TeX ignores it, so we may
130
+ # do the same)
131
+ end # parse_header
132
+
133
+ def parse_params
134
+ @index=@param_base * 4
135
+ @tfm.params << nil
136
+ @np.times {
137
+ @tfm.params << get_fix_word
138
+ }
139
+ end # parse_params
140
+
141
+ # �78 TFtoPL
142
+ def parse_char_info
143
+ @index=@char_base *4
144
+ (@bc..@ec).each { |n|
145
+ tmp=if @tfm.chars[n]
146
+ @tfm.chars[n]
147
+ else
148
+ Hash.new
149
+ end
150
+ index=get_byte
151
+ tmp[:charwd]=get_fix_word((@width_base + index)*4)
152
+ b=get_byte
153
+ tmp[:charht]=get_fix_word((@height_base + (b >> 4))*4)
154
+ tmp[:chardp]=get_fix_word((@depth_base + (b % 16))*4)
155
+ tmp[:charic]=get_fix_word((@italic_base + (get_byte >> 2))*4)
156
+ # we ignore the remainder and look it up on demand
157
+ get_byte
158
+ if index == 0
159
+ @tfm.chars[n]=nil
160
+ else
161
+ @tfm.chars[n]=tmp
162
+ end
163
+ }
164
+ end
165
+
166
+ # now for the ugly part in the tfm, �63 pp
167
+ # Hey, we do a more clever implementation: we do not check for any
168
+ # errors. So coding is only a few lines instead of a few sections.
169
+ # this one took me so much time (the original, not this
170
+ # implementation), I am really frustrated.
171
+ def parse_lig_kern
172
+ # array that stores 'instruction that starts at x can be found in
173
+ # @tfm.lig_kern at position y'
174
+ start_instr=[]
175
+ @bc.upto(@ec) { |c|
176
+ next unless @tfm.chars[c]
177
+ if char_tag(c) == LIGTAG
178
+ start=get_lig_starting_point(c)
179
+ if start_instr[start] != nil
180
+ # we have already stored this ligkern
181
+ @tfm.chars[c][:lig_kern]=start_instr[start]
182
+ next
183
+ end
184
+ tmp=[]
185
+
186
+ start_instr[start]=@tfm.lig_kern.size
187
+ @tfm.lig_kern.push tmp
188
+ @tfm.chars[c][:lig_kern]=start_instr[start]
189
+
190
+ begin
191
+ s=get_byte(lig_step(start))
192
+ puts "warning: skip > 128 (#{s}) I don't know what to do." if s > 128
193
+ n,op,rem=get_byte(lig_step(start)+1),get_byte(lig_step(start)+2),get_byte(lig_step(start)+3)
194
+
195
+ if op >= 128
196
+ # kern!
197
+ kernamount=get_fix_word((@kern_base + (256 * (op-128) +rem)) *4)
198
+ tmp.push [:krn, n, kernamount]
199
+ else
200
+ tmp.push [TFM::LIGOPS[op], n, rem ]
201
+ end
202
+ tmp.push [:skip, s] if s > 0 and s < 128
203
+ start += 1
204
+ end until s >= 128
205
+ end
206
+ }
207
+ end
208
+
209
+
210
+ # --------------------------------------------------
211
+ def char_tag(c)
212
+ @tfmdata[((@char_base + c - @bc ) *4 + 2)] % 2
213
+ end
214
+ def char_remainder(c)
215
+ @tfmdata[((@char_base + c - @bc ) *4 + 3)]
216
+ end
217
+ def get_lig_starting_point(char)
218
+ # warning: had some wine
219
+ return nil unless char_tag(char) == LIGTAG
220
+ r = char_remainder(char)
221
+ s=get_byte(lig_step(r))
222
+ if s > 128
223
+ # it does not start here, it starts somewhere else
224
+ n,op,rem=get_byte(lig_step(r)+1),get_byte(lig_step(r)+2),get_byte(lig_step(r)+3)
225
+
226
+ 256*op+rem
227
+ else
228
+ r
229
+ end
230
+ end
231
+
232
+ def get_byte(i=nil)
233
+ global = i == nil
234
+ i = @index if global
235
+ r=@tfmdata[i]
236
+ @index += 1 if global
237
+ r
238
+ end
239
+ # 16 bit integer
240
+ def get_dbyte
241
+ r = (@tfmdata[@index] << 8) + @tfmdata[@index + 1]
242
+ @index += 2
243
+ r
244
+ end
245
+ # 32 bit integer
246
+ def get_qbyte
247
+ r = (@tfmdata[@index] << 24) + (@tfmdata[@index+1] << 16) + (@tfmdata[@index+2] << 8) + @tfmdata[@index+3]
248
+ @index += 4
249
+ r
250
+ end
251
+ def get_chars(count)
252
+ ret=""
253
+ count.times { |count|
254
+ c=@tfmdata[@index + count]
255
+ ret << c.chr if c > 0
256
+ }
257
+ @index += count
258
+ ret
259
+ end
260
+ def get_fix_word(i=nil)
261
+ global = i==nil
262
+ i = @index if global
263
+ b=@tfmdata[(i..i+3)]
264
+ @index += 4 if global
265
+ a= (b[0] * 16) + (b[1].div 16)
266
+ f= ((b[1] % 16) * 256 + b[2] ) * 256 + b[3]
267
+
268
+ str = ""
269
+ if a > 03777
270
+ str << "-"
271
+ a = 010000 - a
272
+ if f > 0
273
+ f = 04000000 - f
274
+ a -= 1
275
+ end
276
+ end
277
+ # Knuth, TFtoPL �42
278
+
279
+ delta = 10
280
+ f=10*f+5
281
+
282
+ str << a.to_s + "."
283
+ begin
284
+ if delta > 04000000
285
+ f = f + 02000000 - ( delta / 2 )
286
+ end
287
+ str << (f / 04000000).to_s
288
+ f = 10 * ( f % 04000000)
289
+ delta *= 10
290
+ end until f <= delta
291
+ str.to_f
292
+ end
293
+ def lig_step(num)
294
+ (@lig_kern_base + num )*4
295
+ end
296
+ end
297
+
298
+
299
+
300
+ class TFMWriter
301
+ # More output to stdout
302
+ attr_accessor :verbose
303
+
304
+ WIDTH=1
305
+ HEIGHT=2
306
+ DEPTH=3
307
+ ITALIC=4
308
+
309
+ def initialize(tfmobject)
310
+ @tfm=tfmobject
311
+ @chars=[]
312
+ @lig_kern=nil
313
+ # for the sorting
314
+ @memsize=1028 + 4
315
+ # @memory=Array.new(@memsize)
316
+ @memory=[]
317
+ @whdi_index=[]
318
+ @mem_ptr=nil
319
+ @link=Array.new(@memsize)
320
+ @index=[]
321
+ @memory[0]=017777777777
322
+ @memory[WIDTH]=0
323
+ @memory[HEIGHT]=0
324
+ @memory[DEPTH]=0
325
+ @memory[ITALIC]=0
326
+ @link[WIDTH]=0
327
+ @link[HEIGHT]=0
328
+ @link[DEPTH]=0
329
+ @link[ITALIC]=0
330
+ @mem_ptr = ITALIC
331
+ @next_d=nil
332
+
333
+
334
+ @bchar_label=077777
335
+
336
+ @data=[]
337
+ @lf = 0
338
+ @lh = 0 # ok
339
+ @bc = 0 # ok
340
+ @ec = 0 # ok
341
+ @nw = 0 # ok
342
+ @nh = 0 # ok
343
+ @nd = 0 # ok
344
+ @ni = 0 # ok
345
+ @nl = 0 # ok
346
+ @nk = 0 # ok
347
+ @ne = 0 # ingore
348
+ @np = 0 # ok
349
+ end
350
+ def to_data
351
+ update_bc_ec
352
+ calculate_header
353
+ # width,heigt,dp,ic index
354
+ update_whdi_index
355
+ # @widths, @heights, @depths, @italics finished
356
+ update_lig_kern
357
+ # @kerns finished
358
+ update_parameters
359
+ # @parameters finished
360
+ @lf = 6 + @lh + (@ec - @bc + 1) + @nw + @nh + @nd + @ni + @nl + @nk + @ne + @np
361
+ @data += out_dbyte(@lf)
362
+ @data += out_dbyte(@lh)
363
+ @data += out_dbyte(@bc)
364
+ @data += out_dbyte(@ec)
365
+ @data += out_dbyte(@nw)
366
+ @data += out_dbyte(@nh)
367
+ @data += out_dbyte(@nd)
368
+ @data += out_dbyte(@ni)
369
+ @data += out_dbyte(@nl)
370
+ @data += out_dbyte(@nk)
371
+ @data += out_dbyte(@ne)
372
+ @data += out_dbyte(@np)
373
+ @data += @header
374
+ calculate_chars
375
+ @data += @chars
376
+ @data += @widths
377
+ @data += @heights
378
+ @data += @depths
379
+ @data += @italics
380
+ @data += @lig_kern
381
+ @data += @kerns
382
+ @data += @parameters
383
+
384
+ @data.pack("C*")
385
+ end
386
+
387
+ def calculate_chars
388
+ (@bc..@ec).each { |n|
389
+ if @tfm.chars[n]
390
+ wd_idx=@index[@widths_orig[n]]
391
+ ht_idx=@index[@heights_orig[n]] ? @index[@heights_orig[n]] << 4 : 0
392
+ dp_idx=@index[@depths_orig[n]] ? @index[@depths_orig[n]] : 0
393
+ ic_idx= @index[@italics_orig[n]] ? (@index[@italics_orig[n]] << 2) : 0
394
+ tag = @tfm.chars[n][:lig_kern] ? 1 : 0
395
+ remainder= @tfm.chars[n][:lig_kern] ? @instr_index[@tfm.chars[n][:lig_kern]] : 0
396
+ @chars += [wd_idx,ht_idx + dp_idx, ic_idx + tag, remainder]
397
+ else
398
+ @chars += [0,0,0,0]
399
+ end
400
+ }
401
+ end
402
+
403
+ def update_parameters
404
+ @parameters=[]
405
+
406
+ @tfm.params.each_with_index { |p,i|
407
+ next if i==0
408
+ @parameters += out_fix_word(p)
409
+ }
410
+ @np=@parameters.size / 4
411
+ end
412
+
413
+ def update_whdi_index
414
+ @widths_orig=[]
415
+ @heights_orig=[]
416
+ @depths_orig=[]
417
+ @italics_orig=[]
418
+
419
+ (@bc..@ec).each { |c|
420
+ if @tfm.chars[c]
421
+ @widths_orig[c]= sort_in(WIDTH,@tfm.chars[c][:charwd])
422
+ @heights_orig[c] = sort_in(HEIGHT,@tfm.chars[c][:charht] || 0)
423
+ @depths_orig[c] = sort_in(DEPTH,@tfm.chars[c][:chardp] || 0 )
424
+ @italics_orig[c] = sort_in(ITALIC,@tfm.chars[c][:charic] || 0 )
425
+ else
426
+ @widths_orig[c] = 0
427
+ @depths_orig[c] = 0
428
+ @heights_orig[c] = 0
429
+ @italics_orig[c] = 0
430
+ end
431
+
432
+ }
433
+ delta=shorten(WIDTH,200)
434
+ set_indices(WIDTH,delta)
435
+ delta=shorten(HEIGHT,15)
436
+ set_indices(HEIGHT,delta)
437
+ delta=shorten(DEPTH,15)
438
+ set_indices(DEPTH,delta)
439
+ delta=shorten(ITALIC,63)
440
+ set_indices(ITALIC,delta)
441
+
442
+
443
+ @widths = fill_index(WIDTH)
444
+ @heights = fill_index(HEIGHT)
445
+ @depths = fill_index(DEPTH)
446
+ @italics = fill_index(ITALIC)
447
+ @nw= @widths.size/4
448
+ @nh= @heights.size/4
449
+ @nd= @depths.size/4
450
+ @ni= @italics.size/4
451
+ end
452
+
453
+ def update_lig_kern
454
+ kerns=[]
455
+ instructions=[]
456
+ (@bc..@ec).each { |n|
457
+ next unless @tfm.chars[n]
458
+ next unless @tfm.chars[n][:lig_kern]
459
+ # we can skip aliases
460
+ next if instructions[@tfm.chars[n][:lig_kern]]
461
+ newinstr=[]
462
+ @tfm.lig_kern[@tfm.chars[n][:lig_kern]].each { |instr,*rest|
463
+ skip=nextchar=op=remainder=0
464
+ case instr
465
+ when :krn
466
+ i=nil
467
+ unless i = kerns.index(rest[1])
468
+ kerns << rest[1]
469
+ i=kerns.size - 1
470
+ end
471
+ skip=0
472
+ nextchar=rest[0]
473
+ remainder=i % 256
474
+ op = remainder / 256 + 128
475
+ # :stopdoc:
476
+ when :lig, :"lig/", :"/lig", :"/lig/", :"lig/>", :"/lig>", :"/lig/>", :"/lig/>>"
477
+ # :startdoc:
478
+ skip=0
479
+ nextchar,remainder=rest
480
+ op=TFM::LIGOPS.index(instr)
481
+ when :skip
482
+ # todo: test for incorrect situations
483
+ newinstr[-4] = rest[0]
484
+ next
485
+ else
486
+ raise "don't know instruction #{instr}"
487
+ end
488
+ newinstr += [skip,nextchar,op,remainder]
489
+ }
490
+ newinstr[-4] = 128
491
+ instructions[@tfm.chars[n][:lig_kern]] = newinstr
492
+ }
493
+
494
+ # we have all instructions collected in an array. The problem now
495
+ # is to fill the @lig_kern array so that all start of instruction
496
+ # programs are within the first 256 words of @lig_kern. So we keep
497
+ # filling the @lig_kern array until there would not be enough room
498
+ # left for the indirect nodes for the remaining count of
499
+ # instructions. Say, we have 50 instructions left to go and there
500
+ # are 60 words free in the first 256 words of @lig_kern, but the
501
+ # current instruction would take more then 10 words, we need to
502
+ # stop and fill the @lig_kern array with the indirect nodes and
503
+ # then continue with the instructions. The following
504
+ # implementation seems to work, but I refuse to prove it and it is
505
+ # definitely not the most beautiful piece of code I have written.
506
+
507
+ @instr_index=[]
508
+ @lig_kern=[]
509
+
510
+ total_instr=instructions.size
511
+ if total_instr > 0
512
+ instr_left=total_instr
513
+ thisinstr=instructions.shift
514
+
515
+ while (256 - @lig_kern.size / 4) - instr_left - thisinstr.size / 4 > 0
516
+ @instr_index[total_instr-instr_left]=@lig_kern.size / 4
517
+ @lig_kern += thisinstr
518
+ thisinstr=instructions.shift
519
+ instr_left -= 1
520
+ break if instr_left.zero?
521
+ end
522
+
523
+ unless instr_left.zero?
524
+ # undo last changes, since these don't fit into the @lig_kern
525
+ # array (first 256 elements) (yes, this is ugly)
526
+ instructions.unshift thisinstr
527
+
528
+
529
+
530
+ pos=@lig_kern.size / 4 + instr_left
531
+ count=@instr_index.size
532
+
533
+ # now fill the indirect nodes, calculate the starting points of
534
+ # the instructions
535
+ instructions.each { |i|
536
+ @instr_index[count]=@lig_kern.size / 4
537
+ count += 1
538
+ @lig_kern += [ 129, 0, (pos / 256) , (pos % 256) ]
539
+ pos += i.size / 4
540
+ }
541
+
542
+ # now we continue with the instructions
543
+ instructions.each { |i|
544
+ @lig_kern += i
545
+ }
546
+ end
547
+ end
548
+ @nl = @lig_kern.size / 4
549
+
550
+ @kerns=[]
551
+ kerns.each { |k|
552
+ @kerns += out_fix_word(k)
553
+ }
554
+ @nk=@kerns.size / 4
555
+ end
556
+
557
+
558
+ def fill_index(start)
559
+ i=start
560
+ what=[0,0,0,0]
561
+ while (i=@link[i]) > 0
562
+ what += out_fix_word(@memory[i])
563
+ end
564
+ return what
565
+ end
566
+
567
+ def calculate_header
568
+ @header=[]
569
+ # checksum
570
+ @header += checksum
571
+ # dsize
572
+ @header += out_fix_word(@tfm.designsize)
573
+ # 2..11 coding scheme, bcpl
574
+ out_bcpl(@tfm.codingscheme,40)
575
+ # 12..16 font identifier
576
+ out_bcpl(@tfm.fontfamily,20)
577
+ # calculate 7bitflag!
578
+ # 7bitflag, byte, byte, face
579
+ if @tfm.sevenbitsafeflag
580
+ @header << 128
581
+ else
582
+ @header << 0
583
+ end
584
+ @header << 0
585
+ @header << 0
586
+ @header << @tfm.face
587
+ @lh = @header.size / 4
588
+ end
589
+ def update_bc_ec
590
+ @bc=nil
591
+ @tfm.chars.each_with_index{ |elt,i|
592
+ @bc=i if @bc==nil and elt!=nil
593
+ @ec=i if elt
594
+ }
595
+ end
596
+ def checksum
597
+ return out_qbyte(@tfm.checksum)
598
+ end
599
+
600
+ def out_bcpl(string,len)
601
+ str=string
602
+ l = str.length
603
+ if l > 39
604
+ str=string[0..38]
605
+ end
606
+ l = str.length
607
+ @header << l
608
+ count=1
609
+ str.each_byte { |x|
610
+ count += 1
611
+ @header << x
612
+ }
613
+ while len - count > 0
614
+ @header << 0
615
+ count += 1
616
+ end
617
+ end
618
+ def out_dbyte(int)
619
+ a1=int % 256
620
+ a0=int / 256
621
+ return [a0,a1]
622
+ end
623
+ def out_qbyte(int)
624
+ a3=int % 256
625
+ int = int / 256
626
+ a2 = int % 256
627
+ int = int / 256
628
+ a1=int % 256
629
+ a0=int / 256
630
+ return [a0,a1,a2,a3]
631
+ end
632
+
633
+ # looks ok
634
+ def out_fix_word(b)
635
+ # a=int part, f=after dec point
636
+ a=b.truncate
637
+ f=b-a
638
+ if b < 0
639
+ f = 1 - f.abs
640
+ a = a -1
641
+ end
642
+ x=(2**20.0*f).round
643
+ a3=x.modulo(256)
644
+ # x >>= 8
645
+ x=x/256
646
+ a2=x % 256
647
+ # x >>= 8
648
+ x = x >> 8
649
+ a1=x % 16
650
+ a1 += (a % 16) << 4
651
+ a0=b < 0 ? 256 + a / 16 : a / 16
652
+ [a0,a1, a2, a3]
653
+ end
654
+
655
+ def sort_in(h,d)
656
+ if d==0 and h!=WIDTH
657
+ return 0
658
+ end
659
+ p=h
660
+ while d >= @memory[@link[p]]
661
+ p=@link[p]
662
+ end
663
+ if d==@memory[p] and p!=h
664
+ return p
665
+ end
666
+ raise "Memory overflow: more than 1028 widths etc." if @mem_ptr==@memsize
667
+ @mem_ptr += 1
668
+ @memory[@mem_ptr]=d
669
+ @link[@mem_ptr]=@link[p]
670
+ @link[p]=@mem_ptr
671
+ @memory[h]+=1
672
+ return @mem_ptr
673
+ end
674
+
675
+ # see PLtoTF, �75pp
676
+ def min_cover(h,d)
677
+ m=0
678
+ p=@link[h]
679
+ @next_d=@memory[0] # large value
680
+ while p!=0
681
+ m += 1
682
+ l = @memory[p]
683
+ while @memory[@link[p]]<=l+d
684
+ p=@link[p]
685
+ end
686
+ p=@link[p]
687
+ if @memory[p]-l < @next_d
688
+ @next_d=@memory[p]-l
689
+ end
690
+ end
691
+ return m
692
+ end
693
+
694
+ def shorten(h,m)
695
+ if @memory[h] <= m
696
+ return 0
697
+ end
698
+ @excess=@memory[h]-m
699
+ if @excess > 0 and @verbose
700
+ puts "We need to shorten the list by #@excess"
701
+ end
702
+ k=min_cover(h,0)
703
+ d=@next_d
704
+ begin
705
+ d=d+d
706
+ k=min_cover(h,d)
707
+ end until k <= m
708
+ d = d / 2
709
+ k=min_cover(h,d)
710
+ while k > m
711
+ d=@next_d
712
+ k=min_cover(h,d)
713
+ end
714
+ return d
715
+ end
716
+
717
+ def set_indices(h,d)
718
+ q=h
719
+ p=@link[q]
720
+ m=0
721
+ while p!=0
722
+ m+=1
723
+ l=@memory[p]
724
+ @index[p]=m
725
+ while @memory[@link[p]] <= l+d
726
+ p=@link[p]
727
+ @index[p]=m
728
+ @excess -= 1
729
+ if @excess == 0
730
+ d=0
731
+ end
732
+ end
733
+ @link[q]=p
734
+ @memory[p] = l+(@memory[p]-l) / 2
735
+ q=p
736
+ p=@link[p]
737
+ end
738
+ @memory[h]=m
739
+ end
740
+
741
+ end
742
+
743
+
744
+
745
+ # Parse a pl (property list) file.
746
+ class PLParser
747
+ require 'strscan'
748
+
749
+ # _tfmobj_ is an Object of the TFM class.
750
+ def initialize(tfmobj)
751
+ @tfm=tfmobj
752
+ @s=nil
753
+ @syntax={
754
+ "COMMENT" => :get_balanced,
755
+ "FAMILY" => :get_family,
756
+ "FACE" => :get_face,
757
+ "CODINGSCHEME" => :get_codingscheme,
758
+ "DESIGNSIZE" => :get_designsize,
759
+ "CHECKSUM" => :get_checksum,
760
+ "FONTDIMEN" => :get_fontdimen,
761
+ "LIGTABLE" => :get_ligtable,
762
+ "CHARACTER" => :get_character,
763
+ }
764
+ end
765
+
766
+ # Parse the given pl file. _obj_ should be a string.
767
+ def parse (obj)
768
+ @s=StringScanner.new(obj)
769
+ @level=0
770
+ while k=keyword
771
+ if m=@syntax[k]
772
+ r=self.send(m)
773
+ else
774
+ raise "unknown property #{k}"
775
+ end
776
+ end
777
+ end
778
+
779
+ #######
780
+ private
781
+ #######
782
+
783
+ def get_character
784
+ thischar = @tfm.chars[get_num] ||= {}
785
+ # [:charwd, :charht, :chardp, :charic].each do |s|
786
+ # thischar[s]=0.0
787
+ # end
788
+ thislevel=@level
789
+ while @level >= thislevel
790
+ case k=keyword
791
+ when "COMMENT"
792
+ get_balanced
793
+ eat_closing_paren
794
+ when "CHARWD","CHARHT","CHARDP","CHARIC"
795
+ thischar[k.downcase.to_sym]=get_num
796
+ else
797
+ raise "Unknown property in pl file/character section: #{k}"
798
+ end
799
+ end
800
+ end
801
+ def get_ligtable
802
+ thislevel=@level
803
+ @tfm.lig_kern = []
804
+ instruction=[]
805
+ instrnum=[]
806
+ while @level==thislevel
807
+ case kw=keyword
808
+ when "LABEL"
809
+ instrnum.push get_num
810
+ when /LIG/
811
+ instruction << [kw.downcase.to_sym, get_num, get_num]
812
+ when "KRN"
813
+ instruction << [:krn, get_num,get_num]
814
+ when "STOP"
815
+ n=@tfm.lig_kern.size
816
+ instrnum.each { |x|
817
+ t = @tfm.chars[x] ||= {}
818
+ t[:lig_kern] = n
819
+ }
820
+ instrnum=[]
821
+ @tfm.lig_kern.push instruction
822
+ instruction=[]
823
+ eat_closing_paren
824
+ else
825
+ puts "unknown element in ligtable #{kw}, stop"
826
+ exit
827
+ end
828
+ end
829
+ end
830
+
831
+ def get_fontdimen
832
+ thislevel=@level
833
+ while @level==thislevel
834
+ n=case keyword
835
+ when "SLANT" then 1
836
+ when "SPACE" then 2
837
+ when "STRETCH" then 3
838
+ when "SHRINK" then 4
839
+ when "XHEIGHT" then 5
840
+ when "QUAD" then 6
841
+ when "EXTRASPACE" then 7
842
+ when "NUM1", "DEFAULT_RULE_THICKNESS" then 8
843
+ when "NUM2", "BIG_OP_SPACING1" then 9
844
+ when "NUM3", "BIG_OP_SPACING2" then 10
845
+ when "DENOM1", "BIG_OP_SPACING3" then 11
846
+ when "DENOM2", "BIG_OP_SPACING4" then 12
847
+ when "SUP1", "BIG_OP_SPACING5" then 13
848
+ when "SUP2" then 14
849
+ when "SUP3" then 15
850
+ when "SUB1" then 16
851
+ when "SUB2" then 17
852
+ when "SUPDROP" then 18
853
+ when "PARAMETER"
854
+ get_num
855
+ else
856
+ raise "unknown instruction in fontdimen"
857
+ end
858
+ @tfm.params[n]=get_num
859
+ end
860
+ end
861
+ def get_checksum
862
+ @tfm.checksum=get_num
863
+ end
864
+ def get_designsize
865
+ @tfm.designsize=get_num
866
+ end
867
+ def get_family
868
+ @tfm.fontfamily=get_string
869
+ end
870
+ def get_face
871
+ @tfm.face=get_num
872
+ end
873
+ def get_codingscheme
874
+ @tfm.codingscheme=get_balanced
875
+ eat_closing_paren
876
+ end
877
+ def eat_closing_paren
878
+ while @s.scan(/\s*\n?\)\n?/)
879
+ @level -= 1
880
+ end
881
+ end
882
+ # we are just before an open paren
883
+ def keyword
884
+ @s.skip_until(/\(/)
885
+ @level += 1
886
+ @s.skip(/\s+/)
887
+ ret= @s.scan(/[A-Za-z\/>]+/)
888
+ @s.skip(/\s+/)
889
+ return ret
890
+ end
891
+
892
+ def get_balanced
893
+ str=""
894
+ startlevel=@level
895
+ while @level >= startlevel
896
+ str << @s.scan(/[^\(\)]*/)
897
+ if (tmp = @s.scan(/(\(|\))/)) == "("
898
+ @level += 1
899
+ else
900
+ @level -= 1
901
+ end
902
+ str << tmp if @level >= startlevel
903
+ end
904
+ @s.skip(/\n/)
905
+ str
906
+ end
907
+ def get_string
908
+ @s.skip(/\s/)
909
+ s= @s.scan(/[[:alnum:]`'_\- :]+/)
910
+ @s.scan(/\)\s*\n/)
911
+ @level -= 1
912
+ return s
913
+ end
914
+ def get_num
915
+ @s.skip(/\s+/)
916
+ s=@s.scan(/(R|C|D|O|F|H)/)
917
+ @s.skip(/\s+/)
918
+ value=case s
919
+ when "R"
920
+ @s.scan(/-?\d+(\.\d+)?/).to_f
921
+ when "C"
922
+ @s.scan(/[[:alnum:]]/)[0]
923
+ when "D"
924
+ @s.scan(/\d+/).to_i
925
+ when "O"
926
+ @s.scan(/\d+/).to_i(8)
927
+ when "F"
928
+ t=@s.scan(/(M|B|L)(R|I)(R|C|E)/)
929
+ ['MRR','MIR','BRR','BIR','LRR','LIR','MRC','MIC','BRC','BIC',
930
+ 'LRC','LIC','MRE','MIE','BRE','BIE','LRE','LIE'].index(t)
931
+ else
932
+ raise "not implemented yet"
933
+ end
934
+ eat_closing_paren
935
+ value
936
+ end
937
+ end #class pl parser
938
+
939
+
940
+
941
+ # :stopdoc:
942
+ LIGOPS= [ :lig, :"lig/", :"/lig", :"/lig/",
943
+ nil, :"lig/>", :"/lig>", :"/lig/>",
944
+ nil, nil, nil, :"/lig/>>" ]
945
+
946
+ FACE = ['MRR','MIR','BRR','BIR','LRR','LIR','MRC','MIC','BRC','BIC',
947
+ 'LRC','LIC','MRE','MIE','BRE','BIE','LRE','LIE']
948
+
949
+ NOTAG=0
950
+ LIGTAG=1
951
+ LISTTAG=2
952
+ EXTTAG=3
953
+
954
+ def self.documented_as_accessor(*args) #:nodoc:
955
+ end
956
+ def self.documented_as_reader(*args) #:nodoc:
957
+ end
958
+ # :startdoc:
959
+
960
+ # Print diagnostics
961
+ attr_accessor :verbose
962
+
963
+ # Filename sans path of the tfm file. To change this attribute, set
964
+ # pathname.
965
+ documented_as_reader :tfmfilename
966
+
967
+ # Path to the tfm file.
968
+ attr_accessor :tfmpathname
969
+
970
+ # Checksum of the tfm file.
971
+ attr_accessor :checksum
972
+
973
+ # The designsize (Float). Must be >= 1.0.
974
+ attr_accessor :designsize
975
+
976
+ # Coding scheme of the font. One of "TeX math symbols", "TeX math
977
+ # extension" or anything else. The two have special meaning (more
978
+ # parameters). Maximum length is 40
979
+ attr_accessor :codingscheme
980
+
981
+ # Font family is an arbitrary String. Default is "UNSPECIFIED".
982
+ # Maximum length is 20.
983
+ attr_accessor :fontfamily
984
+
985
+ # This boolean flag denotes if the font has chars with index > 127.
986
+ attr_accessor :sevenbitsafeflag
987
+
988
+ # Face code. 0 <= 17.
989
+ attr_accessor :face
990
+
991
+ # Array of chars. Each entry is a Hash with the following keys:
992
+ # <tt>:charwd</tt> <tt>:charht</tt>, <tt>:chardp</tt>,
993
+ # <tt>:charic</tt> and <tt>:lig_kern</tt>. The first four are in
994
+ # designsize units. The <tt>:lig_kern</tt> key is the instruction
995
+ # number pointing to the entry in the lig_kern attribute of the TFM
996
+ # class.
997
+ attr_accessor :chars
998
+
999
+ # Array of ligkern instructions. Each instruction is an Array of
1000
+ # Arrays where the first element is either <tt>:krn</tt> or one of
1001
+ # <tt>:lig</tt>, <tt>:lig/</tt>, <tt>:/lig</tt>, <tt>:/lig/</tt>,
1002
+ # <tt>:lig/></tt>, <tt>:/lig></tt>, <tt>:/lig/></tt> or
1003
+ # <tt>:/lig/>></tt>. If it is <tt>:krn</tt>, then the second
1004
+ # element is the next char and the third element must be the amount
1005
+ # of kerning in multiples of the designsize. If it is a
1006
+ # <tt>:lig</tt> (or similar), then the second element is the
1007
+ # nextchar. The third element is the resulting char.
1008
+
1009
+ # Example for an instruction:
1010
+ #
1011
+ #
1012
+ # [[:"lig/", 39, 148],
1013
+ # [:krn, 121, -0.029993],
1014
+ # [:krn, 39, -0.159998],
1015
+ # [:krn, 148, -0.13999],
1016
+ # [:krn, 89, -0.13999]]
1017
+ #
1018
+ # The complete <em>lig_kern</em> would be an Array of such instructions.
1019
+ attr_accessor :lig_kern
1020
+
1021
+ # The fontdimensions, index starts at 1.
1022
+ attr_accessor :params
1023
+
1024
+ def initialize
1025
+ @chars=[]
1026
+ @lig_kern=[]
1027
+ @params=[]
1028
+ @face=0
1029
+ @designsize=10.0
1030
+ @checksum=0
1031
+ @fontfamily="UNSPECIFIED"
1032
+ @verbose=false
1033
+ end
1034
+ def tfmfilename # :nodoc:
1035
+ File.basename(@tfmpathname)
1036
+ end
1037
+
1038
+
1039
+ # _plfile_ is a filename (String). (Future: File and String (pathname))
1040
+ def read_pl(plfilename)
1041
+ File.open(plfilename) { |f|
1042
+ parse_pl(f.read)
1043
+ }
1044
+ return self
1045
+ end
1046
+ def parse_pl(plstring)
1047
+ p=PLParser.new(self)
1048
+ p.parse(plstring)
1049
+ return self
1050
+ end
1051
+
1052
+ # _file_ is either a File object (or something similar, it must
1053
+ # respond to :read) or a string containing the full pathname to the
1054
+ # tfm file. Returns the TFM object.
1055
+ def read_tfm(file)
1056
+ p=TFMReader.new(self)
1057
+ p.verbose=@verbose
1058
+ if file.respond_to? :read
1059
+ if file.respond_to? :path
1060
+ @tfmpathname=file.path
1061
+ end
1062
+ p.parse(file.read)
1063
+ else
1064
+ # we assume it is a string
1065
+ @tfmpathname=file
1066
+ case file
1067
+ when /\.tfm$/
1068
+ File.open(file) { |f|
1069
+ p.parse(f.read)
1070
+ }
1071
+ else
1072
+ raise ArgumentError, "unknown Filetype: #{file}"
1073
+ end
1074
+ end
1075
+ return self
1076
+ end # read_file
1077
+
1078
+ # If _overwrite_ is true, we will replace existing files without
1079
+ # raising Errno::EEXIST.
1080
+ def save(overwrite=false)
1081
+ raise Errno::EEXIST if File.exists?(@tfmpathname) and not overwrite
1082
+ puts "saving #{@tfmpathname}..." if @verbose
1083
+ File.open(@tfmpathname,"wb") { |f|
1084
+ write_file(f)
1085
+ }
1086
+ puts "saving #{@tfmpathname}...done" if @verbose
1087
+ end
1088
+
1089
+ # _file_ is a File object (or something similar, it must
1090
+ # respond to <<).
1091
+ def write_file(file)
1092
+ tfmwriter=TFMWriter.new(self)
1093
+ tfmwriter.verbose=@verbose
1094
+ file << tfmwriter.to_data
1095
+ end
1096
+
1097
+ # Return pltotf compatible output.
1098
+ def to_s
1099
+ indent=" "
1100
+ str=""
1101
+ str << out_head(indent)
1102
+ str << out_parameters(indent)
1103
+ str << out_ligtable(indent)
1104
+ str << out_chars(indent)
1105
+ str
1106
+ end
1107
+
1108
+ #######
1109
+ private
1110
+ #######
1111
+
1112
+ def out_head(indent)
1113
+ str ="(FAMILY #{fontfamily.upcase})\n"
1114
+ str << "(FACE F #{FACE[face]})\n"
1115
+ str << "(CODINGSCHEME #{codingscheme.upcase})\n"
1116
+ str << "(DESIGNSIZE R #{designsize})\n"
1117
+ str << "(CHECKSUM O #{sprintf("%o",checksum)})\n"
1118
+ end
1119
+ def out_chars(indent)
1120
+ str = ""
1121
+ chars.each_with_index { |c,i|
1122
+ next unless c
1123
+ # str << "(CHARACTER O #{sprintf("%o",i)}\n"
1124
+ str << "(CHARACTER D %d\n" % i
1125
+ [:charwd,:charht,:chardp,:charic].each { |dim|
1126
+ str << indent + "(#{dim.to_s.upcase} R #{c[dim]})\n" if c[dim]!=0.0
1127
+ }
1128
+ str << indent + ")\n"
1129
+ }
1130
+ str
1131
+ end
1132
+ def out_parameters(indent)
1133
+ paramname=%w( X SLANT SPACE STRETCH SHRINK XHEIGHT QUAD EXTRASPACE )
1134
+ if codingscheme=="TeX math symbols"
1135
+ paramname += %w(NUM1 NUM2 NUM3 DENOM1 DENOM2 SUP1 SUP2 SUP3
1136
+ SUB1 SUB2 SUPDROP)
1137
+ elsif codingscheme=="TeX math extension"
1138
+ paramname += %w(DEFAULT_RULE_THICKNESS BIG_OP_SPACING1
1139
+ BIG_OP_SPACING2 BIG_OP_SPACING3 BIG_OP_SPACING4 BIG_OP_SPACING5)
1140
+ end
1141
+
1142
+ str = "(FONTDIMEN\n"
1143
+ @params.each_with_index { |p,i|
1144
+ next if i==0
1145
+ if paramname[i]
1146
+ str << indent + "(#{paramname[i]} R #{p})\n"
1147
+ else
1148
+ str << indent + "(PARAMETER D #{i} R #{p})\n"
1149
+ end
1150
+ }
1151
+ str << indent + ")\n"
1152
+ str
1153
+ end
1154
+ def out_ligtable(indent)
1155
+ return "" if @lig_kern.size==0
1156
+ str = "(LIGTABLE\n"
1157
+ lk_char=[]
1158
+ # first appearance of a char is the index, all chars for the same
1159
+ # instructions is the value
1160
+ # e.g. firstchar_chars[8]=[8,9] if chars 8 and 9 point to the same instr.
1161
+ firstchar_chars=[]
1162
+ @chars.each_with_index {|c,i|
1163
+ next unless c
1164
+ next unless instr=c[:lig_kern]
1165
+ # we need to find duplicates
1166
+ # some chars point to the same instruction
1167
+ if lk_char[instr]
1168
+ lk_char[instr].push i
1169
+ else
1170
+ lk_char[instr] = [i]
1171
+ end
1172
+ }
1173
+
1174
+ lk_char.each{ |a|
1175
+ firstchar_chars[a[0]]=a
1176
+ }
1177
+ firstchar_chars.each { |a|
1178
+ next unless a
1179
+ a.each { |l|
1180
+ str << indent + "(LABEL D #{l})\n"
1181
+ }
1182
+ @lig_kern[@chars[a[0]][:lig_kern]].each {|la|
1183
+ case la[0]
1184
+ when :skip
1185
+ str << indent + "(SKIP D #{la[1]})\n"
1186
+ when :krn
1187
+ str << indent + "(KRN D #{la[1]} R #{la[2]})\n"
1188
+ when :lig, :"lig/", :"/lig", :"/lig/", :"lig/>", :"/lig>", :"/lig/>", :"/lig/>>"
1189
+ str << indent + "(#{la[0].to_s.upcase} O #{sprintf("%o",la[1])} O #{sprintf("%o",la[2])})\n"
1190
+ end
1191
+ }
1192
+ str << indent + "(STOP)\n"
1193
+ }
1194
+ str << indent + ")\n"
1195
+ str
1196
+ end
1197
+ end # class TFM
1198
+ end # module TeX