rfil 0.2

Sign up to get free protection for your applications and to get access to all the features.
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