1# man2help2.tcl --
2#
3# This file defines procedures that are used during the second pass of
4# the man page conversion.  It converts the man format input to rtf
5# form suitable for use by the Windows help compiler.
6#
7# Copyright (c) 1996 by Sun Microsystems, Inc.
8#
9# See the file "license.terms" for information on usage and redistribution
10# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11#
12# RCS: @(#) $Id: man2help2.tcl,v 1.17.2.1 2008/10/02 18:56:30 mistachkin Exp $
13#
14
15# Global variables used by these scripts:
16#
17# state -	state variable that controls action of text proc.
18#
19# topics -	array indexed by (package,section,topic) with value
20# 		of topic ID.
21#
22# keywords -	array indexed by keyword string with value of topic ID.
23#
24# curID - 	current topic ID, starts at 0 and is incremented for
25# 		each new topic file.
26#
27# curPkg -	current package name (e.g. Tcl).
28#
29# curSect -	current section title (e.g. "Tcl Built-In Commands").
30#
31
32# initGlobals --
33#
34# This procedure is invoked to set the initial values of all of the
35# global variables, before processing a man page.
36#
37# Arguments:
38# None.
39
40proc initGlobals {} {
41    uplevel \#0 unset state
42    global state chars
43
44    set state(paragraphPending) 0
45    set state(breakPending) 0
46    set state(firstIndent) 0
47    set state(leftIndent) 0
48
49    set state(inTP) 0
50    set state(paragraph) 0
51    set state(textState) 0
52    set state(curFont) ""
53    set state(startCode) "{\\b "
54    set state(startEmphasis) "{\\i "
55    set state(endCode) "}"
56    set state(endEmphasis) "}"
57    set state(noFill) 0
58    set state(charCnt) 0
59    set state(offset) [getTwips 0.5i]
60    set state(leftMargin) [getTwips 0.5i]
61    set state(nestingLevel) 0
62    set state(intl) 0
63    set state(sb) 0
64    setTabs 0.5i
65
66# set up international character table
67
68    array set chars {
69	o^ F4
70    }
71}
72
73
74# beginFont --
75#
76# Arranges for future text to use a special font, rather than
77# the default paragraph font.
78#
79# Arguments:
80# font -		Name of new font to use.
81
82proc beginFont {font} {
83    global file state
84
85    textSetup
86    if {[string equal $state(curFont) $font]} {
87	return
88    }
89    endFont
90    puts -nonewline $file $state(start$font)
91    set state(curFont) $font
92}
93
94
95# endFont --
96#
97# Reverts to the default font for the paragraph type.
98#
99# Arguments:
100# None.
101
102proc endFont {} {
103    global state file
104
105    if {[string compare $state(curFont) ""]} {
106	puts -nonewline $file $state(end$state(curFont))
107	set state(curFont) ""
108    }
109}
110
111
112# textSetup --
113#
114# This procedure is called the first time that text is output for a
115# paragraph.  It outputs the header information for the paragraph.
116#
117# Arguments:
118# None.
119
120proc textSetup {} {
121    global file state
122
123    if $state(breakPending) {
124	puts $file "\\line"
125    }
126    if $state(paragraphPending) {
127	puts $file [format "\\par\n\\pard\\fi%.0f\\li%.0f" \
128			$state(firstIndent) $state(leftIndent)]
129	foreach tab $state(tabs) {
130	    puts $file [format "\\tx%.0f" $tab]
131	}
132	set state(tabs) {}
133	if {$state(sb)} {
134	    puts $file "\\sb$state(sb)"
135	    set state(sb) 0
136	}
137    }
138    set state(breakPending) 0
139    set state(paragraphPending) 0
140}
141
142
143# text --
144#
145# This procedure adds text to the current state(paragraph).  If this is
146# the first text in the state(paragraph) then header information for the
147# state(paragraph) is output before the text.
148#
149# Arguments:
150# string -		Text to output in the state(paragraph).
151
152proc text {string} {
153    global file state chars
154
155    textSetup
156    set string [string map [list \
157	    "\\"	"\\\\" \
158	    "\{"	"\\\{" \
159	    "\}"	"\\\}" \
160	    "\t"	{\tab } \
161	    ''		"\\rdblquote " \
162	    ``		"\\ldblquote " \
163	    "\u00b7"	"\\bullet " \
164	    ] $string]
165
166    # Check if this is the beginning of an international character string.
167    # If so, look up the sequence in the chars table and substitute the
168    # appropriate hex value.
169
170    if {$state(intl)} {
171	if {[regexp {^'([^']*)'} $string dummy ch]} {
172	    if {[info exists chars($ch)]} {
173		regsub {^'[^']*'} $string "\\\\'$chars($ch)" string
174	    } else {
175		puts stderr "Unknown international character '$ch'"
176	    }
177	}
178	set state(intl) 0
179    }
180
181    switch $state(textState) {
182	REF {
183	    if {$state(inTP) == 0} {
184		set string [insertRef $string]
185	    }
186	}
187	SEE {
188	    global topics curPkg curSect
189	    foreach i [split $string] {
190		if {![regexp -nocase {^[a-z_0-9]+} [string trim $i] i ]} {
191		    continue
192		}
193		if {![catch {set ref $topics($curPkg,$curSect,$i)} ]} {
194		    regsub $i $string [link $i $ref] string
195		}
196	    }
197	}
198	KEY {
199	    return
200	}
201    }
202    puts -nonewline $file "$string"
203}
204
205
206
207# insertRef --
208#
209# This procedure looks for a string in the cross reference table and
210# generates a hot-link to the appropriate topic.  Tries to find the
211# nearest reference in the manual.
212#
213# Arguments:
214# string -		Text to output in the state(paragraph).
215
216proc insertRef {string} {
217    global NAME_file curPkg curSect topics curID
218    set path {}
219    set string [string trim $string]
220    set ref {}
221    if {[info exists topics($curPkg,$curSect,$string)]} {
222	set ref $topics($curPkg,$curSect,$string)
223    } else {
224	set sites [array names topics "$curPkg,*,$string"]
225	set count [llength $sites]
226	if {$count > 0} {
227	    set ref $topics([lindex $sites 0])
228	} else {
229	    set sites [array names topics "*,*,$string"]
230	    set count [llength $sites]
231	    if {$count > 0} {
232		set ref $topics([lindex $sites 0])
233	    }
234	}
235    }
236
237    if {($ref != {}) && ($ref != $curID)} {
238	set string [link $string $ref]
239    }
240    return $string
241}
242
243
244
245# macro --
246#
247# This procedure is invoked to process macro invocations that start
248# with "." (instead of ').
249#
250# Arguments:
251# name -		The name of the macro (without the ".").
252# args -		Any additional arguments to the macro.
253
254proc macro {name args} {
255    global state file
256    switch $name {
257	AP {
258	    if {[llength $args] != 3 && [llength $args] != 2} {
259		puts stderr "Bad .AP macro: .$name [join $args " "]"
260	    }
261	    newPara 3.75i -3.75i
262	    setTabs {1.25i 2.5i 3.75i}
263	    font B
264	    text [lindex $args 0]
265	    tab
266	    font I
267	    text [lindex $args 1]
268	    tab
269	    font R
270	    if {[llength $args] == 3} {
271		text "([lindex $args 2])"
272	    }
273	    tab
274	}
275	AS {
276	    # next page and previous page
277	}
278	br {
279	    lineBreak
280	}
281	BS {}
282	BE {}
283	CE {
284	    puts -nonewline $::file "\\f0\\fs20 "
285	    set state(noFill) 0
286	    set state(breakPending) 0
287	    newPara ""
288	    set state(leftIndent) [expr {$state(leftIndent) - $state(offset)}]
289	    set state(sb) 80
290	}
291	CS {
292	    # code section
293	    set state(noFill) 1
294	    newPara ""
295	    set state(leftIndent) [expr {$state(leftIndent) + $state(offset)}]
296	    set state(sb) 80
297	    puts -nonewline $::file "\\f1\\fs18 "
298	}
299	DE {
300	    set state(noFill) 0
301	    decrNestingLevel
302	    newPara 0i
303	}
304	DS {
305	    set state(noFill) 1
306	    incrNestingLevel
307	    newPara 0i
308	}
309	fi {
310	    set state(noFill) 0
311	}
312	IP {
313	    IPmacro $args
314	}
315	LP {
316	    newPara 0i
317	    set state(sb) 80
318	}
319	ne {
320	}
321	nf {
322	    set state(noFill) 1
323	}
324	OP {
325	    if {[llength $args] != 3} {
326		puts stderr "Bad .OP macro: .$name [join $args " "]"
327	    }
328	    set state(nestingLevel) 0
329	    newPara 0i
330	    set state(sb) 120
331	    setTabs 4c
332	    text "Command-Line Name:"
333	    tab
334	    font B
335	    set x [lindex $args 0]
336	    regsub -all {\\-} $x - x
337	    text $x
338	    lineBreak
339	    font R
340	    text "Database Name:"
341	    tab
342	    font B
343	    text [lindex $args 1]
344	    lineBreak
345	    font R
346	    text "Database Class:"
347	    tab
348	    font B
349	    text [lindex $args 2]
350	    font R
351	    set state(inTP) 0
352	    newPara 0.5i
353	    set state(sb) 80
354	}
355	PP {
356	    newPara 0i
357	    set state(sb) 120
358	}
359	RE {
360	    decrNestingLevel
361	}
362	RS {
363	    incrNestingLevel
364	}
365	SE {
366	    font R
367	    set state(noFill) 0
368	    set state(nestingLevel) 0
369	    newPara 0i
370	    text "See the "
371	    font B
372	    set temp $state(textState)
373	    set state(textState) REF
374	    text options
375	    set state(textState) $temp
376	    font R
377	    text " manual entry for detailed descriptions of the above options."
378	}
379	SH {
380	    SHmacro $args
381	}
382	SS {
383	    SHmacro $args subsection
384	}
385	SO {
386	    SHmacro "STANDARD OPTIONS"
387	    set state(nestingLevel) 0
388	    newPara 0i
389	    setTabs {4c 8c 12c}
390	    font B
391	    set state(noFill) 1
392	}
393	so {
394	    if {$args != "man.macros"} {
395		puts stderr "Unknown macro: .$name [join $args " "]"
396	    }
397	}
398	sp {					;# needs work
399	    if {$args == ""} {
400		set count 1
401	    } else {
402		set count [lindex $args 0]
403	    }
404	    while {$count > 0} {
405		lineBreak
406		incr count -1
407	    }
408	}
409	ta {
410	    setTabs $args
411	}
412	TH {
413	    THmacro $args
414	}
415	TP {
416	    TPmacro $args
417	}
418	UL {					;# underline
419	    puts -nonewline $file "{\\ul "
420	    text [lindex $args 0]
421	    puts -nonewline $file "}"
422	    if {[llength $args] == 2} {
423		text [lindex $args 1]
424	    }
425	}
426	VE {}
427	VS {}
428	QW {
429	    formattedText "``[lindex $args 0]''[lindex $args 1] "
430	}
431	MT {
432	    text "``'' "
433	}
434	PQ {
435	    formattedText \
436		"(``[lindex $args 0]''[lindex $args 1])[lindex $args 2] "
437	}
438	QR {
439	    formattedText "``[lindex $args 0]"
440	    dash
441	    formattedText "[lindex $args 1]''[lindex $args 2] "
442	}
443	default {
444	    puts stderr "Unknown macro: .$name [join $args " "]"
445	}
446    }
447}
448
449
450# link --
451#
452# This procedure returns the string for  a hot link to a different
453# context location.
454#
455# Arguments:
456# label -		String to display in hot-spot.
457# id -			Context string to jump to.
458
459proc link {label id} {
460    return "{\\uldb $label}{\\v $id}"
461}
462
463
464# font --
465#
466# This procedure is invoked to handle font changes in the text
467# being output.
468#
469# Arguments:
470# type -		Type of font: R, I, B, or S.
471
472proc font {type} {
473    global state
474    switch $type {
475	P -
476	R {
477	    endFont
478	    if {$state(textState) == "REF"} {
479		set state(textState) INSERT
480	    }
481	}
482	C -
483	B {
484	    beginFont Code
485	    if {$state(textState) == "INSERT"} {
486		set state(textState) REF
487	    }
488	}
489	I {
490	    beginFont Emphasis
491	}
492	S {
493	}
494	default {
495	    puts stderr "Unknown font: $type"
496	}
497    }
498}
499
500
501
502# formattedText --
503#
504# Insert a text string that may also have \fB-style font changes
505# and a few other backslash sequences in it.
506#
507# Arguments:
508# text -		Text to insert.
509
510proc formattedText {text} {
511    global chars
512
513    while {$text != ""} {
514	set index [string first \\ $text]
515	if {$index < 0} {
516	    text $text
517	    return
518	}
519	text [string range $text 0 [expr {$index-1}]]
520	set c [string index $text [expr {$index+1}]]
521	switch -- $c {
522	    f {
523		font [string index $text [expr {$index+2}]]
524		set text [string range $text [expr {$index+3}] end]
525	    }
526	    e {
527		text "\\"
528		set text [string range $text [expr {$index+2}] end]
529	    }
530	    - {
531		dash
532		set text [string range $text [expr {$index+2}] end]
533	    }
534	    & - | {
535		set text [string range $text [expr {$index+2}] end]
536	    }
537	    ( {
538		char [string range $text $index [expr {$index+3}]]
539		set text [string range $text [expr {$index+4}] end]
540	    }
541	    default {
542		puts stderr "Unknown sequence: \\$c"
543		set text [string range $text [expr {$index+2}] end]
544	    }
545	}
546    }
547}
548
549
550# dash --
551#
552# This procedure is invoked to handle dash characters ("\-" in
553# troff).  It outputs a special dash character.
554#
555# Arguments:
556# None.
557
558proc dash {} {
559    global state
560    if {[string equal $state(textState) "NAME"]} {
561    	set state(textState) 0
562    }
563    text "-"
564}
565
566
567# tab --
568#
569# This procedure is invoked to handle tabs in the troff input.
570# Right now it does nothing.
571#
572# Arguments:
573# None.
574
575proc tab {} {
576    global file
577
578    textSetup
579    puts -nonewline $file "\\tab "
580}
581
582
583# setTabs --
584#
585# This procedure handles the ".ta" macro, which sets tab stops.
586#
587# Arguments:
588# tabList -	List of tab stops in *roff format
589
590proc setTabs {tabList} {
591    global file state
592
593    set state(tabs) {}
594    foreach arg $tabList {
595	if {[string match +* $arg]} {
596	    set relativeTo [lindex $state(tabs) end]
597	    set arg [string range $arg 1 end]
598	} else {
599	    # Local left margin
600	    set relativeTo [expr {$state(leftMargin) \
601		    + ($state(offset) * $state(nestingLevel))}]
602	}
603	if {[regexp {^\\w'([^']*)'u$} $arg -> submatch]} {
604	    # Magic factor!
605	    set distance [expr {[string length $submatch] * 86.4}]
606	} else {
607	    set distance [getTwips $arg]
608	}
609	lappend state(tabs) [expr {round($distance + $relativeTo)}]
610    }
611}
612
613
614# lineBreak --
615#
616# Generates a line break in the HTML output.
617#
618# Arguments:
619# None.
620
621proc lineBreak {} {
622    global state
623    textSetup
624    set state(breakPending) 1
625}
626
627
628
629# newline --
630#
631# This procedure is invoked to handle newlines in the troff input.
632# It outputs either a space character or a newline character, depending
633# on fill mode.
634#
635# Arguments:
636# None.
637
638proc newline {} {
639    global state
640
641    if {$state(inTP)} {
642    	set state(inTP) 0
643	lineBreak
644    } elseif {$state(noFill)} {
645	lineBreak
646    } else {
647	text " "
648    }
649}
650
651
652# pageBreak --
653#
654# This procedure is invoked to generate a page break.
655#
656# Arguments:
657# None.
658
659proc pageBreak {} {
660    global file curVer
661    if {[string equal $curVer ""]} {
662	puts $file {\page}
663    } else {
664	puts $file {\par}
665	puts $file {\pard\sb400\qc}
666	puts $file "Last change: $curVer\\page"
667    }
668}
669
670
671# char --
672#
673# This procedure is called to handle a special character.
674#
675# Arguments:
676# name -		Special character named in troff \x or \(xx construct.
677
678proc char {name} {
679    global file state
680
681    switch -exact $name {
682        {\o} {
683	    set state(intl) 1
684	}
685	{\ } {
686	    textSetup
687	    puts -nonewline $file " "
688	}
689	{\0} {
690	    textSetup
691	    puts -nonewline $file " \\emspace "
692	}
693	{\\} - {\e} {
694	    textSetup
695	    puts -nonewline $file "\\\\"
696	}
697	{\(+-} {
698	    textSetup
699	    puts -nonewline $file "\\'b1 "
700	}
701	{\%} - {\|} {
702	}
703	{\(->} {
704	    textSetup
705	    puts -nonewline $file "->"
706	}
707	{\(bu} {
708	    textSetup
709	    puts -nonewline $file "\\bullet "
710	}
711	{\(co} {
712	    textSetup
713	    puts -nonewline $file "\\'a9 "
714	}
715	{\(mu} {
716	    textSetup
717	    puts -nonewline $file "\\'d7 "
718	}
719	{\(em} {
720	    textSetup
721	    puts -nonewline $file "-"
722	}
723	{\(fm} {
724	    textSetup
725	    puts -nonewline $file "\\'27 "
726	}
727	default {
728	    puts stderr "Unknown character: $name"
729	}
730    }
731}
732
733
734# macro2 --
735#
736# This procedure handles macros that are invoked with a leading "'"
737# character instead of space.  Right now it just generates an
738# error diagnostic.
739#
740# Arguments:
741# name -		The name of the macro (without the ".").
742# args -		Any additional arguments to the macro.
743
744proc macro2 {name args} {
745    puts stderr "Unknown macro: '$name [join $args " "]"
746}
747
748
749
750# SHmacro --
751#
752# Subsection head; handles the .SH and .SS macros.
753#
754# Arguments:
755# name -		Section name.
756
757proc SHmacro {argList {style section}} {
758    global file state
759
760    set args [join $argList " "]
761    if {[llength $argList] < 1} {
762	puts stderr "Bad .SH macro: .SH $args"
763    }
764
765    # control what the text proc does with text
766
767    switch $args {
768	NAME {set state(textState) NAME}
769	DESCRIPTION {set state(textState) INSERT}
770	INTRODUCTION {set state(textState) INSERT}
771	"WIDGET-SPECIFIC OPTIONS" {set state(textState) INSERT}
772	"SEE ALSO" {set state(textState) SEE}
773	KEYWORDS {set state(textState) KEY; return}
774    }
775
776    if {$state(breakPending) != -1} {
777	set state(breakPending) 1
778    } else {
779	set state(breakPending) 0
780    }
781    set state(noFill) 0
782    if {[string compare "subsection" $style] == 0} {
783	nextPara .25i
784    } else {
785	nextPara 0i
786    }
787    font B
788    text $args
789    font R
790    nextPara .5i
791}
792
793# IPmacro --
794#
795# This procedure is invoked to handle ".IP" macros, which may take any
796# of the following forms:
797#
798# .IP [1]		Translate to a "1Step" state(paragraph).
799# .IP [x] (x > 1)	Translate to a "Step" state(paragraph).
800# .IP			Translate to a "Bullet" state(paragraph).
801# .IP text count	Translate to a FirstBody state(paragraph) with special
802#			indent and tab stop based on "count", and tab after
803#			"text".
804#
805# Arguments:
806# argList -		List of arguments to the .IP macro.
807#
808# HTML limitations: 'count' in '.IP text count' is ignored.
809
810proc IPmacro {argList} {
811    global file state
812
813    set length [llength $argList]
814    foreach {text indent} $argList break
815    if {$length > 2} {
816	puts stderr "Bad .IP macro: .IP [join $argList " "]"
817    }
818
819    if {$length == 0} {
820	set text {\(bu}
821	set indent 5
822    } elseif {$length == 1} {
823	set indent 5
824    }
825    if {$text == {\(bu}} {
826	set text "\u00b7"
827    }
828
829    set tab [expr $indent * 0.1]i
830    newPara $tab -$tab
831    set state(sb) 80
832    setTabs $tab
833    formattedText $text
834    tab
835}
836
837# TPmacro --
838#
839# This procedure is invoked to handle ".TP" macros, which may take any
840# of the following forms:
841#
842# .TP x		Translate to an state(indent)ed state(paragraph) with the
843# 			specified state(indent) (in 100 twip units).
844# .TP		Translate to an state(indent)ed state(paragraph) with
845# 			default state(indent).
846#
847# Arguments:
848# argList -		List of arguments to the .IP macro.
849#
850# HTML limitations: 'x' in '.TP x' is ignored.
851
852proc TPmacro {argList} {
853    global state
854    set length [llength $argList]
855    if {$length == 0} {
856	set val 0.5i
857    } else {
858	set val [expr {([lindex $argList 0] * 100.0)/1440}]i
859    }
860    newPara $val -$val
861    setTabs $val
862    set state(inTP) 1
863    set state(sb) 120
864}
865
866
867# THmacro --
868#
869# This procedure handles the .TH macro.  It generates the non-scrolling
870# header section for a given man page, and enters information into the
871# table of contents.  The .TH macro has the following form:
872#
873# .TH name section date footer header
874#
875# Arguments:
876# argList -		List of arguments to the .TH macro.
877
878proc THmacro {argList} {
879    global file curPkg curSect curID id_keywords state curVer bitmap
880
881    if {[llength $argList] != 5} {
882	set args [join $argList " "]
883	puts stderr "Bad .TH macro: .TH $args"
884    }
885    incr curID
886    set name	[lindex $argList 0]		;# Tcl_UpVar
887    set page	[lindex $argList 1]		;# 3
888    set curVer	[lindex $argList 2]		;# 7.4
889    set curPkg	[lindex $argList 3]		;# Tcl
890    set curSect	[lindex $argList 4]		;# {Tcl Library Procedures}
891
892    regsub -all {\\ } $curSect { } curSect	;# Clean up for [incr\ Tcl]
893
894    puts $file "#{\\footnote $curID}"		;# Context string
895    puts $file "\${\\footnote $name}"		;# Topic title
896    set browse "${curSect}${name}"
897    regsub -all {[ _-]} $browse {} browse
898    puts $file "+{\\footnote $browse}"		;# Browse sequence
899
900    # Suppress duplicates
901    foreach i $id_keywords($curID) {
902	set keys($i) 1
903    }
904    foreach i [array names keys] {
905	set i [string trim $i]
906	if {[string length $i] > 0} {
907	    puts $file "K{\\footnote $i}"	;# Keyword strings
908	}
909    }
910    unset keys
911    puts $file "\\pard\\tx3000\\sb100\\sa100\\fs24\\keepn"
912    font B
913    text $name
914    tab
915    text $curSect
916    font R
917    if {[info exists bitmap]} {
918	# a right justified bitmap
919	puts $file "\\\{bmrt $bitmap\\\}"
920    }
921    puts $file "\\fs20"
922    set state(breakPending) -1
923}
924
925# nextPara --
926#
927# Set the indents for a new paragraph, and start a paragraph break
928#
929# Arguments:
930# leftIndent -		The new left margin for body lines.
931# firstIndent -		The offset from the left margin for the first line.
932
933proc nextPara {leftIndent {firstIndent 0i}} {
934    global state
935    set state(leftIndent) [getTwips $leftIndent]
936    set state(firstIndent) [getTwips $firstIndent]
937    set state(paragraphPending) 1
938}
939
940
941# newPara --
942#
943# This procedure sets the left and hanging state(indent)s for a line.
944# State(Indent)s are specified in units of inches or centimeters, and are
945# relative to the current nesting level and left margin.
946#
947# Arguments:
948# leftState(Indent) -		The new left margin for lines after the first.
949# firstState(Indent) -		The new left margin for the first line of a state(paragraph).
950
951proc newPara {leftIndent {firstIndent 0i}} {
952    global state file
953    if $state(paragraph) {
954	puts -nonewline $file "\\line\n"
955    }
956    if {$leftIndent != ""} {
957	set state(leftIndent) [expr {$state(leftMargin) \
958		+ ($state(offset) * $state(nestingLevel)) \
959		+ [getTwips $leftIndent]}]
960    }
961    set state(firstIndent) [getTwips $firstIndent]
962    set state(paragraphPending) 1
963}
964
965
966# getTwips --
967#
968# This procedure converts a distance in inches or centimeters into
969# twips (1/1440 of an inch).
970#
971# Arguments:
972# arg -			A number followed by "i" or "c"
973
974proc getTwips {arg} {
975    if {[scan $arg "%f%s" distance units] != 2} {
976	puts stderr "bad distance \"$arg\""
977	return 0
978    }
979    if {[string length $units] > 1} {
980	puts stderr "additional characters after unit \"$arg\""
981	set units [string index $units 0]
982    }
983    switch -- $units {
984	c	{
985	    set distance [expr {$distance * 567}]
986	}
987	i	{
988	    set distance [expr {$distance * 1440}]
989	}
990	default {
991	    puts stderr "bad units in distance \"$arg\""
992	    return 0
993	}
994    }
995    return $distance
996}
997
998# incrNestingLevel --
999#
1000# This procedure does the work of the .RS macro, which increments
1001# the number of state(indent)ations that affect things like .PP.
1002#
1003# Arguments:
1004# None.
1005
1006proc incrNestingLevel {} {
1007    global state
1008
1009    incr state(nestingLevel)
1010    set oldp $state(paragraph)
1011    set state(paragraph) 0
1012    newPara 0i
1013    set state(paragraph) $oldp
1014}
1015
1016# decrNestingLevel --
1017#
1018# This procedure does the work of the .RE macro, which decrements
1019# the number of indentations that affect things like .PP.
1020#
1021# Arguments:
1022# None.
1023
1024proc decrNestingLevel {} {
1025    global state
1026
1027    if {$state(nestingLevel) == 0} {
1028	puts stderr "Nesting level decremented below 0"
1029    } else {
1030	incr state(nestingLevel) -1
1031    }
1032}
1033