summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
commitd318611dd6f23fcfedd50e9b9e24620b102ba96a (patch)
tree8b9eef82ca40fdd5a8deeabf07572074c236095d /src
parentInitial commit. (diff)
downloadgroff-d318611dd6f23fcfedd50e9b9e24620b102ba96a.tar.xz
groff-d318611dd6f23fcfedd50e9b9e24620b102ba96a.zip
Adding upstream version 1.23.0.upstream/1.23.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r--src/devices/grodvi/dvi.cpp988
-rw-r--r--src/devices/grodvi/grodvi.1.man633
-rw-r--r--src/devices/grodvi/grodvi.am32
-rw-r--r--src/devices/grohtml/grohtml.1.man731
-rw-r--r--src/devices/grohtml/grohtml.am40
-rw-r--r--src/devices/grohtml/html-table.cpp848
-rw-r--r--src/devices/grohtml/html-table.h133
-rw-r--r--src/devices/grohtml/html-text.cpp1056
-rw-r--r--src/devices/grohtml/html-text.h138
-rw-r--r--src/devices/grohtml/html.h97
-rw-r--r--src/devices/grohtml/output.cpp363
-rw-r--r--src/devices/grohtml/post-html.cpp5684
-rw-r--r--src/devices/grolbp/charset.h89
-rw-r--r--src/devices/grolbp/grolbp.1.man504
-rw-r--r--src/devices/grolbp/grolbp.am36
-rw-r--r--src/devices/grolbp/lbp.cpp738
-rw-r--r--src/devices/grolbp/lbp.h544
-rw-r--r--src/devices/grolj4/grolj4.1.man896
-rw-r--r--src/devices/grolj4/grolj4.am32
-rw-r--r--src/devices/grolj4/lj4.cpp715
-rw-r--r--src/devices/gropdf/TODO31
-rw-r--r--src/devices/gropdf/gropdf.1.man1845
-rw-r--r--src/devices/gropdf/gropdf.am58
-rw-r--r--src/devices/gropdf/gropdf.pl3928
-rw-r--r--src/devices/gropdf/pdfmom.1.man229
-rw-r--r--src/devices/gropdf/pdfmom.pl150
-rw-r--r--src/devices/grops/TODO24
-rw-r--r--src/devices/grops/grops.1.man1831
-rw-r--r--src/devices/grops/grops.am38
-rw-r--r--src/devices/grops/ps.cpp1894
-rw-r--r--src/devices/grops/ps.h129
-rw-r--r--src/devices/grops/psfig.diff106
-rw-r--r--src/devices/grops/psrm.cpp1189
-rw-r--r--src/devices/grotty/TODO3
-rw-r--r--src/devices/grotty/grotty.1.man810
-rw-r--r--src/devices/grotty/grotty.am39
-rwxr-xr-xsrc/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh205
-rwxr-xr-xsrc/devices/grotty/tests/osc8_works.sh119
-rw-r--r--src/devices/grotty/tty.cpp1043
-rw-r--r--src/devices/xditview/ChangeLog556
-rw-r--r--src/devices/xditview/DESC.in9
-rw-r--r--src/devices/xditview/Dvi.c603
-rw-r--r--src/devices/xditview/Dvi.h46
-rw-r--r--src/devices/xditview/DviP.h235
-rw-r--r--src/devices/xditview/FontMap-X1117
-rw-r--r--src/devices/xditview/GXditview-color.ad15
-rw-r--r--src/devices/xditview/GXditview.ad71
-rw-r--r--src/devices/xditview/Menu.h46
-rw-r--r--src/devices/xditview/README13
-rw-r--r--src/devices/xditview/TODO21
-rw-r--r--src/devices/xditview/ad2c64
-rw-r--r--src/devices/xditview/device.c565
-rw-r--r--src/devices/xditview/device.h21
-rw-r--r--src/devices/xditview/draw.c709
-rw-r--r--src/devices/xditview/draw.h18
-rw-r--r--src/devices/xditview/font.c446
-rw-r--r--src/devices/xditview/font.h6
-rw-r--r--src/devices/xditview/gray1.bm4
-rw-r--r--src/devices/xditview/gray2.bm4
-rw-r--r--src/devices/xditview/gray3.bm4
-rw-r--r--src/devices/xditview/gray4.bm4
-rw-r--r--src/devices/xditview/gray5.bm4
-rw-r--r--src/devices/xditview/gray6.bm4
-rw-r--r--src/devices/xditview/gray7.bm4
-rw-r--r--src/devices/xditview/gray8.bm4
-rw-r--r--src/devices/xditview/gxditview.1.man815
-rw-r--r--src/devices/xditview/lex.c100
-rw-r--r--src/devices/xditview/lex.h1
-rw-r--r--src/devices/xditview/page.c86
-rw-r--r--src/devices/xditview/page.h5
-rw-r--r--src/devices/xditview/parse.c343
-rw-r--r--src/devices/xditview/parse.h1
-rw-r--r--src/devices/xditview/xdit.bm14
-rw-r--r--src/devices/xditview/xdit_mask.bm14
-rw-r--r--src/devices/xditview/xditview.am130
-rw-r--r--src/devices/xditview/xditview.c684
-rw-r--r--src/include/DviChar.h55
-rw-r--r--src/include/XFontName.h50
-rw-r--r--src/include/cmap.h55
-rw-r--r--src/include/color.h88
-rw-r--r--src/include/config.hin1584
-rw-r--r--src/include/cset.h74
-rw-r--r--src/include/curtime.h27
-rw-r--r--src/include/device.h25
-rw-r--r--src/include/driver.h38
-rw-r--r--src/include/errarg.h46
-rw-r--r--src/include/error.h69
-rw-r--r--src/include/font.h343
-rw-r--r--src/include/geometry.h26
-rw-r--r--src/include/getopt.h226
-rw-r--r--src/include/getopt_int.h130
-rw-r--r--src/include/gettext.h22
-rw-r--r--src/include/html-strings.h26
-rw-r--r--src/include/htmlhint.h36
-rw-r--r--src/include/include.am47
-rw-r--r--src/include/index.h41
-rw-r--r--src/include/itable.h193
-rw-r--r--src/include/lf.h21
-rw-r--r--src/include/lib.h161
-rw-r--r--src/include/localcharset.h40
-rw-r--r--src/include/macropath.h22
-rw-r--r--src/include/nonposix.h230
-rw-r--r--src/include/paper.h36
-rw-r--r--src/include/posix.h66
-rw-r--r--src/include/printer.h97
-rw-r--r--src/include/ptable.h233
-rw-r--r--src/include/refid.h34
-rw-r--r--src/include/relocate.h37
-rw-r--r--src/include/search.h100
-rw-r--r--src/include/searchpath.h30
-rw-r--r--src/include/stringclass.h194
-rw-r--r--src/include/symbol.h88
-rw-r--r--src/include/unicode.h63
-rw-r--r--src/libs/libbib/common.cpp37
-rw-r--r--src/libs/libbib/index.cpp688
-rw-r--r--src/libs/libbib/libbib.am35
-rw-r--r--src/libs/libbib/linear.cpp506
-rw-r--r--src/libs/libbib/map.c86
-rw-r--r--src/libs/libbib/search.cpp136
-rw-r--r--src/libs/libdriver/input.cpp1841
-rw-r--r--src/libs/libdriver/libdriver.am31
-rw-r--r--src/libs/libdriver/printer.cpp268
-rw-r--r--src/libs/libgroff/assert.cpp38
-rw-r--r--src/libs/libgroff/change_lf.cpp37
-rw-r--r--src/libs/libgroff/cmap.cpp56
-rw-r--r--src/libs/libgroff/color.cpp404
-rwxr-xr-xsrc/libs/libgroff/config.charset684
-rw-r--r--src/libs/libgroff/cset.cpp104
-rw-r--r--src/libs/libgroff/curtime.cpp55
-rw-r--r--src/libs/libgroff/device.cpp39
-rw-r--r--src/libs/libgroff/errarg.cpp136
-rw-r--r--src/libs/libgroff/error.cpp185
-rw-r--r--src/libs/libgroff/fatal.cpp30
-rw-r--r--src/libs/libgroff/filename.cpp31
-rw-r--r--src/libs/libgroff/fmod.c27
-rw-r--r--src/libs/libgroff/font.cpp1321
-rw-r--r--src/libs/libgroff/fontfile.cpp80
-rw-r--r--src/libs/libgroff/geometry.cpp180
-rw-r--r--src/libs/libgroff/getcwd.c54
-rw-r--r--src/libs/libgroff/getopt.c1241
-rw-r--r--src/libs/libgroff/getopt1.c172
-rw-r--r--src/libs/libgroff/glyphuni.cpp523
-rw-r--r--src/libs/libgroff/htmlhint.cpp60
-rw-r--r--src/libs/libgroff/hypot.cpp34
-rw-r--r--src/libs/libgroff/iftoa.c76
-rw-r--r--src/libs/libgroff/invalid.cpp59
-rw-r--r--src/libs/libgroff/itoa.c67
-rw-r--r--src/libs/libgroff/lf.cpp78
-rw-r--r--src/libs/libgroff/libgroff.am182
-rw-r--r--src/libs/libgroff/lineno.cpp20
-rw-r--r--src/libs/libgroff/localcharset.c579
-rw-r--r--src/libs/libgroff/macropath.cpp29
-rwxr-xr-xsrc/libs/libgroff/make-uniuni162
-rw-r--r--src/libs/libgroff/matherr.c48
-rw-r--r--src/libs/libgroff/maxfilename.cpp75
-rw-r--r--src/libs/libgroff/maxpathname.cpp70
-rw-r--r--src/libs/libgroff/mksdir.cpp33
-rw-r--r--src/libs/libgroff/mkstemp.cpp33
-rw-r--r--src/libs/libgroff/nametoindex.cpp167
-rw-r--r--src/libs/libgroff/new.cpp75
-rw-r--r--src/libs/libgroff/paper.cpp82
-rw-r--r--src/libs/libgroff/prime.cpp55
-rw-r--r--src/libs/libgroff/progname.c18
-rw-r--r--src/libs/libgroff/ptable.cpp57
-rw-r--r--src/libs/libgroff/putenv.c97
-rw-r--r--src/libs/libgroff/quotearg.c213
-rw-r--r--src/libs/libgroff/ref-add.sin29
-rw-r--r--src/libs/libgroff/ref-del.sin24
-rw-r--r--src/libs/libgroff/relocatable.h19
-rw-r--r--src/libs/libgroff/relocate.cpp244
-rw-r--r--src/libs/libgroff/searchpath.cpp215
-rw-r--r--src/libs/libgroff/spawnvp.c120
-rw-r--r--src/libs/libgroff/strcasecmp.c65
-rw-r--r--src/libs/libgroff/strerror.c46
-rw-r--r--src/libs/libgroff/string.cpp354
-rw-r--r--src/libs/libgroff/strncasecmp.c19
-rw-r--r--src/libs/libgroff/strsave.cpp40
-rw-r--r--src/libs/libgroff/strtol.c131
-rw-r--r--src/libs/libgroff/symbol.cpp157
-rw-r--r--src/libs/libgroff/tmpfile.cpp188
-rw-r--r--src/libs/libgroff/tmpname.cpp117
-rw-r--r--src/libs/libgroff/unicode.cpp65
-rw-r--r--src/libs/libgroff/uniglyph.cpp497
-rw-r--r--src/libs/libgroff/uniuni.cpp2128
-rw-r--r--src/libs/libxutil/DviChar.c679
-rw-r--r--src/libs/libxutil/XFontName.c260
-rw-r--r--src/libs/libxutil/libxutil.am35
-rw-r--r--src/libs/libxutil/xmalloc.c28
-rw-r--r--src/preproc/eqn/TODO49
-rw-r--r--src/preproc/eqn/box.cpp651
-rw-r--r--src/preproc/eqn/box.h278
-rw-r--r--src/preproc/eqn/delim.cpp418
-rw-r--r--src/preproc/eqn/eqn.1.man2240
-rw-r--r--src/preproc/eqn/eqn.am79
-rw-r--r--src/preproc/eqn/eqn.cpp2112
-rw-r--r--src/preproc/eqn/eqn.h57
-rw-r--r--src/preproc/eqn/eqn.hpp210
-rw-r--r--src/preproc/eqn/eqn.ypp333
-rw-r--r--src/preproc/eqn/lex.cpp1236
-rw-r--r--src/preproc/eqn/limit.cpp217
-rw-r--r--src/preproc/eqn/list.cpp241
-rw-r--r--src/preproc/eqn/main.cpp485
-rw-r--r--src/preproc/eqn/mark.cpp120
-rw-r--r--src/preproc/eqn/neqn.1.man92
-rw-r--r--src/preproc/eqn/neqn.sh26
-rw-r--r--src/preproc/eqn/other.cpp706
-rw-r--r--src/preproc/eqn/over.cpp204
-rw-r--r--src/preproc/eqn/pbox.h140
-rw-r--r--src/preproc/eqn/pile.cpp354
-rw-r--r--src/preproc/eqn/script.cpp250
-rw-r--r--src/preproc/eqn/special.cpp117
-rw-r--r--src/preproc/eqn/sqrt.cpp186
-rwxr-xr-xsrc/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh55
-rw-r--r--src/preproc/eqn/text.cpp957
-rw-r--r--src/preproc/grn/README68
-rw-r--r--src/preproc/grn/gprint.h90
-rw-r--r--src/preproc/grn/grn.1.man978
-rw-r--r--src/preproc/grn/grn.am35
-rw-r--r--src/preproc/grn/hdb.cpp441
-rw-r--r--src/preproc/grn/hgraph.cpp1060
-rw-r--r--src/preproc/grn/hpoint.cpp59
-rw-r--r--src/preproc/grn/main.cpp977
-rw-r--r--src/preproc/html/html.am32
-rw-r--r--src/preproc/html/pre-html.cpp1819
-rw-r--r--src/preproc/html/pre-html.h38
-rw-r--r--src/preproc/html/pushback.cpp336
-rw-r--r--src/preproc/html/pushback.h52
-rw-r--r--src/preproc/pic/TODO35
-rw-r--r--src/preproc/pic/common.cpp647
-rw-r--r--src/preproc/pic/common.h79
-rw-r--r--src/preproc/pic/lex.cpp2039
-rw-r--r--src/preproc/pic/main.cpp664
-rw-r--r--src/preproc/pic/object.cpp2079
-rw-r--r--src/preproc/pic/object.h227
-rw-r--r--src/preproc/pic/output.h82
-rw-r--r--src/preproc/pic/pic.1.man1569
-rw-r--r--src/preproc/pic/pic.am56
-rw-r--r--src/preproc/pic/pic.cpp5080
-rw-r--r--src/preproc/pic/pic.h122
-rw-r--r--src/preproc/pic/pic.hpp348
-rw-r--r--src/preproc/pic/pic.ypp1957
-rw-r--r--src/preproc/pic/position.h46
-rw-r--r--src/preproc/pic/tex.cpp458
-rw-r--r--src/preproc/pic/text.h46
-rw-r--r--src/preproc/pic/troff.cpp579
-rw-r--r--src/preproc/preconv/preconv.1.man559
-rw-r--r--src/preproc/preconv/preconv.am37
-rw-r--r--src/preproc/preconv/preconv.cpp1318
-rwxr-xr-xsrc/preproc/preconv/tests/do-not-seek-the-unseekable.sh59
-rwxr-xr-xsrc/preproc/preconv/tests/smoke-test.sh88
-rw-r--r--src/preproc/refer/TODO124
-rw-r--r--src/preproc/refer/command.cpp814
-rw-r--r--src/preproc/refer/command.h35
-rw-r--r--src/preproc/refer/label.cpp2607
-rw-r--r--src/preproc/refer/label.hpp98
-rw-r--r--src/preproc/refer/label.ypp1195
-rw-r--r--src/preproc/refer/ref.cpp1161
-rw-r--r--src/preproc/refer/ref.h127
-rw-r--r--src/preproc/refer/refer.1.man2020
-rw-r--r--src/preproc/refer/refer.am61
-rw-r--r--src/preproc/refer/refer.cpp1267
-rw-r--r--src/preproc/refer/refer.h81
-rw-r--r--src/preproc/refer/tests/artifacts/62124.bib4
-rwxr-xr-xsrc/preproc/refer/tests/report-correct-line-numbers.sh136
-rw-r--r--src/preproc/refer/token.cpp377
-rw-r--r--src/preproc/refer/token.h87
-rw-r--r--src/preproc/soelim/TODO1
-rw-r--r--src/preproc/soelim/soelim.1.man456
-rw-r--r--src/preproc/soelim/soelim.am31
-rw-r--r--src/preproc/soelim/soelim.cpp315
-rw-r--r--src/preproc/tbl/main.cpp1692
-rw-r--r--src/preproc/tbl/table.cpp3161
-rw-r--r--src/preproc/tbl/table.h179
-rw-r--r--src/preproc/tbl/tbl.1.man2018
-rw-r--r--src/preproc/tbl/tbl.am54
-rwxr-xr-xsrc/preproc/tbl/tests/boxes-and-vertical-rules.sh174
-rwxr-xr-xsrc/preproc/tbl/tests/check-horizontal-line-length.sh78
-rwxr-xr-xsrc/preproc/tbl/tests/check-line-intersections.sh52
-rwxr-xr-xsrc/preproc/tbl/tests/check-vertical-line-length.sh49
-rwxr-xr-xsrc/preproc/tbl/tests/cooperate-with-nm-request.sh47
-rwxr-xr-xsrc/preproc/tbl/tests/count-continued-input-lines.sh43
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh225
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh62
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh41
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh75
-rwxr-xr-xsrc/preproc/tbl/tests/expand-region-option-works.sh173
-rwxr-xr-xsrc/preproc/tbl/tests/format-time-diagnostics-work.sh268
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh58
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh79
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-line-numbering.sh85
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-tab-stops.sh84
-rwxr-xr-xsrc/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh56
-rwxr-xr-xsrc/preproc/tbl/tests/x-column-modifier-works.sh172
-rw-r--r--src/roff/groff/groff.1.man2403
-rw-r--r--src/roff/groff/groff.am87
-rw-r--r--src/roff/groff/groff.cpp866
-rw-r--r--src/roff/groff/pipeline.c589
-rw-r--r--src/roff/groff/pipeline.h30
-rwxr-xr-xsrc/roff/groff/tests/ab_works.sh46
-rwxr-xr-xsrc/roff/groff/tests/adjustment_works.sh92
-rw-r--r--src/roff/groff/tests/artifacts/HONEYPOT15
-rw-r--r--src/roff/groff/tests/artifacts/devascii/README1
-rwxr-xr-xsrc/roff/groff/tests/break_zero-length_output_line_sanely.sh43
-rwxr-xr-xsrc/roff/groff/tests/device_control_escapes_express_basic_latin.sh60
-rwxr-xr-xsrc/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh29
-rwxr-xr-xsrc/roff/groff/tests/dot-cp_register_works.sh48
-rwxr-xr-xsrc/roff/groff/tests/dot-nm_register_works.sh40
-rwxr-xr-xsrc/roff/groff/tests/dot-nn_register_works.sh77
-rwxr-xr-xsrc/roff/groff/tests/evc_produces_no_output_if_invalid.sh25
-rwxr-xr-xsrc/roff/groff/tests/fp_should_not_traverse_directories.sh63
-rwxr-xr-xsrc/roff/groff/tests/handle_special_input_code_points.sh47
-rwxr-xr-xsrc/roff/groff/tests/html_works_with_grn_and_eqn.sh46
-rwxr-xr-xsrc/roff/groff/tests/initialization_is_quiet.sh59
-rwxr-xr-xsrc/roff/groff/tests/localization_works.sh61
-rwxr-xr-xsrc/roff/groff/tests/msoquiet_works.sh35
-rwxr-xr-xsrc/roff/groff/tests/on_latin1_device_oq_is_0x27.sh30
-rwxr-xr-xsrc/roff/groff/tests/output_driver_C_and_G_options_work.sh50
-rwxr-xr-xsrc/roff/groff/tests/recognize_end_of_sentence.sh33
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_56555.sh27
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_58153.sh34
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_58162.sh26
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_58337.sh32
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_59202.sh29
-rwxr-xr-xsrc/roff/groff/tests/smoke-test_html_device.sh90
-rwxr-xr-xsrc/roff/groff/tests/some_escapes_accept_newline_delimiters.sh128
-rwxr-xr-xsrc/roff/groff/tests/soquiet_works.sh34
-rwxr-xr-xsrc/roff/groff/tests/string_case_xform_errors.sh30
-rwxr-xr-xsrc/roff/groff/tests/string_case_xform_requests.sh32
-rwxr-xr-xsrc/roff/groff/tests/string_case_xform_unicode_escape.sh42
-rwxr-xr-xsrc/roff/groff/tests/substring_works.sh120
-rwxr-xr-xsrc/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh44
-rw-r--r--src/roff/nroff/nroff.1.man358
-rw-r--r--src/roff/nroff/nroff.am44
-rw-r--r--src/roff/nroff/nroff.sh204
-rwxr-xr-xsrc/roff/nroff/tests/verbose_option_works.sh68
-rw-r--r--src/roff/troff/TODO125
-rw-r--r--src/roff/troff/charinfo.h301
-rw-r--r--src/roff/troff/column.cpp731
-rw-r--r--src/roff/troff/dictionary.cpp209
-rw-r--r--src/roff/troff/dictionary.h91
-rw-r--r--src/roff/troff/div.cpp1212
-rw-r--r--src/roff/troff/div.h175
-rw-r--r--src/roff/troff/env.cpp4138
-rw-r--r--src/roff/troff/env.h421
-rw-r--r--src/roff/troff/hvunits.h339
-rw-r--r--src/roff/troff/input.cpp9209
-rw-r--r--src/roff/troff/input.h120
-rw-r--r--src/roff/troff/mtsm.cpp651
-rw-r--r--src/roff/troff/mtsm.h165
-rw-r--r--src/roff/troff/node.cpp6656
-rw-r--r--src/roff/troff/node.h670
-rw-r--r--src/roff/troff/number.cpp712
-rw-r--r--src/roff/troff/reg.cpp479
-rw-r--r--src/roff/troff/reg.h74
-rw-r--r--src/roff/troff/request.h97
-rw-r--r--src/roff/troff/token.h249
-rw-r--r--src/roff/troff/troff.1.man1047
-rw-r--r--src/roff/troff/troff.am66
-rw-r--r--src/roff/troff/troff.h97
-rw-r--r--src/utils/addftinfo/addftinfo.1.man236
-rw-r--r--src/utils/addftinfo/addftinfo.am35
-rw-r--r--src/utils/addftinfo/addftinfo.cpp237
-rw-r--r--src/utils/addftinfo/guess.cpp489
-rw-r--r--src/utils/addftinfo/guess.h43
-rw-r--r--src/utils/afmtodit/afmtodit.1.man635
-rw-r--r--src/utils/afmtodit/afmtodit.am56
-rw-r--r--src/utils/afmtodit/afmtodit.pl645
-rw-r--r--src/utils/afmtodit/afmtodit.tables6163
-rwxr-xr-xsrc/utils/afmtodit/make-afmtodit-tables139
-rw-r--r--src/utils/grog/grog.1.man628
-rw-r--r--src/utils/grog/grog.am50
-rw-r--r--src/utils/grog/grog.pl721
-rwxr-xr-xsrc/utils/grog/tests/PF-does-not-start-pic-region.sh33
-rwxr-xr-xsrc/utils/grog/tests/avoid-refer-fakeout.sh34
-rw-r--r--src/utils/grog/tests/foo.man146
-rwxr-xr-xsrc/utils/grog/tests/preserve-groff-options.sh30
-rwxr-xr-xsrc/utils/grog/tests/recognize-perl-pod.sh31
-rwxr-xr-xsrc/utils/grog/tests/smoke-test.sh153
-rw-r--r--src/utils/hpftodit/hpftodit.1.man476
-rw-r--r--src/utils/hpftodit/hpftodit.am34
-rw-r--r--src/utils/hpftodit/hpftodit.cpp1465
-rw-r--r--src/utils/hpftodit/hpuni.cpp697
-rw-r--r--src/utils/indxbib/eign133
-rw-r--r--src/utils/indxbib/indxbib.1.man347
-rw-r--r--src/utils/indxbib/indxbib.am57
-rw-r--r--src/utils/indxbib/indxbib.cpp803
-rw-r--r--src/utils/indxbib/signal.c77
-rw-r--r--src/utils/lkbib/lkbib.1.man212
-rw-r--r--src/utils/lkbib/lkbib.am30
-rw-r--r--src/utils/lkbib/lkbib.cpp144
-rw-r--r--src/utils/lookbib/lookbib.1.man166
-rw-r--r--src/utils/lookbib/lookbib.am29
-rw-r--r--src/utils/lookbib/lookbib.cpp146
-rw-r--r--src/utils/pfbtops/pfbtops.1.man129
-rw-r--r--src/utils/pfbtops/pfbtops.am32
-rw-r--r--src/utils/pfbtops/pfbtops.c243
-rw-r--r--src/utils/tfmtodit/tfmtodit.1.man415
-rw-r--r--src/utils/tfmtodit/tfmtodit.am29
-rw-r--r--src/utils/tfmtodit/tfmtodit.cpp889
-rw-r--r--src/utils/xtotroff/xtotroff.1.man237
-rw-r--r--src/utils/xtotroff/xtotroff.am41
-rw-r--r--src/utils/xtotroff/xtotroff.c368
402 files changed, 163009 insertions, 0 deletions
diff --git a/src/devices/grodvi/dvi.cpp b/src/devices/grodvi/dvi.cpp
new file mode 100644
index 0000000..f9e8a57
--- /dev/null
+++ b/src/devices/grodvi/dvi.cpp
@@ -0,0 +1,988 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+#include "driver.h"
+#include "nonposix.h"
+#include "paper.h"
+
+extern "C" const char *Version_string;
+
+#define DEFAULT_LINEWIDTH 40
+static int linewidth = DEFAULT_LINEWIDTH;
+
+static int draw_flag = 1;
+
+static int landscape_flag = 0;
+static double user_paper_length = 0;
+static double user_paper_width = 0;
+
+/* These values were chosen because:
+
+(MULTIPLIER*SIZESCALE)/(RES*UNITWIDTH) == 1/(2^20 * 72.27)
+
+and 57816 is an exact multiple of both 72.27*SIZESCALE and 72.
+
+The width in the groff font file is the product of MULTIPLIER and the
+width in the tfm file. */
+
+#define RES 57816
+#define RES_7227 (RES/7227)
+#define UNITWIDTH 131072
+#define SIZESCALE 100
+#define MULTIPLIER 1
+
+class dvi_font : public font {
+ dvi_font(const char *);
+public:
+ int checksum;
+ int design_size;
+ ~dvi_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static dvi_font *load_dvi_font(const char *);
+};
+
+dvi_font *dvi_font::load_dvi_font(const char *s)
+{
+ dvi_font *f = new dvi_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+dvi_font::dvi_font(const char *nm)
+: font(nm), checksum(0), design_size(0)
+{
+}
+
+dvi_font::~dvi_font()
+{
+}
+
+void dvi_font::handle_unknown_font_command(const char *command,
+ const char *arg,
+ const char *filename, int lineno)
+{
+ char *ptr;
+ if (strcmp(command, "checksum") == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'checksum' command requires an argument");
+ checksum = int(strtol(arg, &ptr, 10));
+ if (checksum == 0 && ptr == arg) {
+ fatal_with_file_and_line(filename, lineno, "bad checksum");
+ }
+ }
+ else if (strcmp(command, "designsize") == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'designsize' command requires an argument");
+ design_size = int(strtol(arg, &ptr, 10));
+ if (design_size == 0 && ptr == arg) {
+ fatal_with_file_and_line(filename, lineno, "bad design size");
+ }
+ }
+}
+
+#define FONTS_MAX 256
+
+struct output_font {
+ dvi_font *f;
+ int point_size;
+ output_font() : f(0) { }
+};
+
+class dvi_printer : public printer {
+ FILE *fp;
+ int max_drift;
+ int byte_count;
+ int last_bop;
+ int page_count;
+ int cur_h;
+ int cur_v;
+ int end_h;
+ int max_h;
+ int max_v;
+ output_font output_font_table[FONTS_MAX];
+ font *cur_font;
+ int cur_point_size;
+ color cur_color;
+ int pushed;
+ int pushed_h;
+ int pushed_v;
+ int have_pushed;
+ void preamble();
+ void postamble();
+ void define_font(int);
+ void set_font(int);
+ void possibly_begin_line();
+ void set_color(color *);
+protected:
+ enum {
+ id_byte = 2,
+ set1 = 128,
+ put1 = 133,
+ put_rule = 137,
+ bop = 139,
+ eop = 140,
+ push = 141,
+ pop = 142,
+ right1 = 143,
+ down1 = 157,
+ fnt_num_0 = 171,
+ fnt1 = 235,
+ xxx1 = 239,
+ fnt_def1 = 243,
+ pre = 247,
+ post = 248,
+ post_post = 249,
+ filler = 223
+ };
+ int line_thickness;
+
+ void out1(int);
+ void out2(int);
+ void out3(int);
+ void out4(int);
+ void moveto(int, int);
+ void out_string(const char *);
+ void out_signed(unsigned char, int);
+ void out_unsigned(unsigned char, int);
+ void do_special(const char *);
+public:
+ dvi_printer();
+ ~dvi_printer();
+ font *make_font(const char *);
+ void begin_page(int);
+ void end_page(int);
+ void set_char(glyph *, font *, const environment *, int, const char *);
+ void special(char *, const environment *, char);
+ void end_of_line();
+ void draw(int, int *, int, const environment *);
+};
+
+
+class draw_dvi_printer : public dvi_printer {
+ int output_pen_size;
+ void set_line_thickness(const environment *);
+ void fill_next(const environment *);
+public:
+ draw_dvi_printer();
+ ~draw_dvi_printer();
+ void draw(int code, int *p, int np, const environment *env);
+ void end_page(int);
+};
+
+dvi_printer::dvi_printer()
+: fp(stdout), byte_count(0), last_bop(-1), page_count(0), max_h(0), max_v(0),
+ cur_font(0), cur_point_size(-1), pushed(0), line_thickness(-1)
+{
+ if (font::res != RES)
+ fatal("resolution must be %1", RES);
+ if (font::unitwidth != UNITWIDTH)
+ fatal("unitwidth must be %1", UNITWIDTH);
+ if (font::hor != 1)
+ fatal("hor must be equal to 1");
+ if (font::vert != 1)
+ fatal("vert must be equal to 1");
+ if (font::sizescale != SIZESCALE)
+ fatal("sizescale must be equal to %1", SIZESCALE);
+ max_drift = font::res/1000; // this is fairly arbitrary
+ preamble();
+}
+
+dvi_printer::~dvi_printer()
+{
+ postamble();
+}
+
+
+draw_dvi_printer::draw_dvi_printer()
+: output_pen_size(-1)
+{
+}
+
+draw_dvi_printer::~draw_dvi_printer()
+{
+}
+
+
+void dvi_printer::out1(int n)
+{
+ byte_count += 1;
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out2(int n)
+{
+ byte_count += 2;
+ putc((n >> 8) & 0xff, fp);
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out3(int n)
+{
+ byte_count += 3;
+ putc((n >> 16) & 0xff, fp);
+ putc((n >> 8) & 0xff, fp);
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out4(int n)
+{
+ byte_count += 4;
+ putc((n >> 24) & 0xff, fp);
+ putc((n >> 16) & 0xff, fp);
+ putc((n >> 8) & 0xff, fp);
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out_string(const char *s)
+{
+ out1(strlen(s));
+ while (*s != 0)
+ out1(*s++);
+}
+
+
+void dvi_printer::end_of_line()
+{
+ if (pushed) {
+ out1(pop);
+ pushed = 0;
+ cur_h = pushed_h;
+ cur_v = pushed_v;
+ }
+}
+
+void dvi_printer::possibly_begin_line()
+{
+ if (!pushed) {
+ have_pushed = pushed = 1;
+ pushed_h = cur_h;
+ pushed_v = cur_v;
+ out1(push);
+ }
+}
+
+int scale(int x, int z)
+{
+ int sw;
+ int a, b, c, d;
+ int alpha, beta;
+ alpha = 16*z; beta = 16;
+ while (z >= 040000000L) {
+ z /= 2; beta /= 2;
+ }
+ d = x & 255;
+ c = (x >> 8) & 255;
+ b = (x >> 16) & 255;
+ a = (x >> 24) & 255;
+ sw = (((((d * z) / 0400) + (c * z)) / 0400) + (b * z)) / beta;
+ if (a == 255)
+ sw -= alpha;
+ else
+ assert(a == 0);
+ return sw;
+}
+
+void dvi_printer::set_color(color *col)
+{
+ cur_color = *col;
+ char buf[256];
+ unsigned int components[4];
+ color_scheme cs = col->get_components(components);
+ switch (cs) {
+ case DEFAULT:
+ sprintf(buf, "color gray 0");
+ break;
+ case RGB:
+ sprintf(buf, "color rgb %.3g %.3g %.3g",
+ double(Red) / double(color::MAX_COLOR_VAL),
+ double(Green) / double(color::MAX_COLOR_VAL),
+ double(Blue) / double(color::MAX_COLOR_VAL));
+ break;
+ case CMY:
+ col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black);
+ // fall through
+ case CMYK:
+ sprintf(buf, "color cmyk %.3g %.3g %.3g %.3g",
+ double(Cyan) / double(color::MAX_COLOR_VAL),
+ double(Magenta) / double(color::MAX_COLOR_VAL),
+ double(Yellow) / double(color::MAX_COLOR_VAL),
+ double(Black) / double(color::MAX_COLOR_VAL));
+ break;
+ case GRAY:
+ sprintf(buf, "color gray %.3g",
+ double(Gray) / double(color::MAX_COLOR_VAL));
+ break;
+ }
+ do_special(buf);
+}
+
+void dvi_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ if (*env->col != cur_color)
+ set_color(env->col);
+ int code = f->get_code(g);
+ if (env->size != cur_point_size || f != cur_font) {
+ cur_font = f;
+ cur_point_size = env->size;
+ int i;
+ for (i = 0;; i++) {
+ if (i >= FONTS_MAX) {
+ fatal("too many output fonts required");
+ }
+ if (output_font_table[i].f == 0) {
+ output_font_table[i].f = (dvi_font *)cur_font;
+ output_font_table[i].point_size = cur_point_size;
+ define_font(i);
+ }
+ if (output_font_table[i].f == cur_font
+ && output_font_table[i].point_size == cur_point_size)
+ break;
+ }
+ set_font(i);
+ }
+ int distance = env->hpos - cur_h;
+ if (env->hpos != end_h && distance != 0) {
+ out_signed(right1, distance);
+ cur_h = env->hpos;
+ }
+ else if (distance > max_drift) {
+ out_signed(right1, distance - max_drift);
+ cur_h = env->hpos - max_drift;
+ }
+ else if (distance < -max_drift) {
+ out_signed(right1, distance + max_drift);
+ cur_h = env->hpos + max_drift;
+ }
+ if (env->vpos != cur_v) {
+ out_signed(down1, env->vpos - cur_v);
+ cur_v = env->vpos;
+ }
+ possibly_begin_line();
+ end_h = env->hpos + w;
+ cur_h += scale(f->get_width(g, UNITWIDTH) / MULTIPLIER,
+ cur_point_size * RES_7227);
+ if (cur_h > max_h)
+ max_h = cur_h;
+ if (cur_v > max_v)
+ max_v = cur_v;
+ if (code >= 0 && code <= 127)
+ out1(code);
+ else
+ out_unsigned(set1, code);
+}
+
+void dvi_printer::define_font(int i)
+{
+ out_unsigned(fnt_def1, i);
+ dvi_font *f = output_font_table[i].f;
+ out4(f->checksum);
+ out4(output_font_table[i].point_size*RES_7227);
+ out4(int((double(f->design_size)/(1<<20))*RES_7227*100 + .5));
+ const char *nm = f->get_internal_name();
+ out1(0);
+ out_string(nm);
+}
+
+void dvi_printer::set_font(int i)
+{
+ if (i >= 0 && i <= 63)
+ out1(fnt_num_0 + i);
+ else
+ out_unsigned(fnt1, i);
+}
+
+void dvi_printer::out_signed(unsigned char base, int param)
+{
+ if (-128 <= param && param < 128) {
+ out1(base);
+ out1(param);
+ }
+ else if (-32768 <= param && param < 32768) {
+ out1(base+1);
+ out2(param);
+ }
+ else if (-(1 << 23) <= param && param < (1 << 23)) {
+ out1(base+2);
+ out3(param);
+ }
+ else {
+ out1(base+3);
+ out4(param);
+ }
+}
+
+void dvi_printer::out_unsigned(unsigned char base, int param)
+{
+ if (param >= 0) {
+ if (param < 256) {
+ out1(base);
+ out1(param);
+ }
+ else if (param < 65536) {
+ out1(base+1);
+ out2(param);
+ }
+ else if (param < (1 << 24)) {
+ out1(base+2);
+ out3(param);
+ }
+ else {
+ out1(base+3);
+ out4(param);
+ }
+ }
+ else {
+ out1(base+3);
+ out4(param);
+ }
+}
+
+void dvi_printer::preamble()
+{
+ out1(pre);
+ out1(id_byte);
+ out4(254000);
+ out4(font::res);
+ out4(1000);
+ out1(0);
+}
+
+void dvi_printer::postamble()
+{
+ int tem = byte_count;
+ out1(post);
+ out4(last_bop);
+ out4(254000);
+ out4(font::res);
+ out4(1000);
+ out4(max_v);
+ out4(max_h);
+ out2(have_pushed); // stack depth
+ out2(page_count);
+ int i;
+ for (i = 0; i < FONTS_MAX && output_font_table[i].f != 0; i++)
+ define_font(i);
+ out1(post_post);
+ out4(tem);
+ out1(id_byte);
+ for (i = 0; i < 4 || byte_count % 4 != 0; i++)
+ out1(filler);
+}
+
+void dvi_printer::begin_page(int i)
+{
+ page_count++;
+ int tem = byte_count;
+ out1(bop);
+ out4(i);
+ for (int j = 1; j < 10; j++)
+ out4(0);
+ out4(last_bop);
+ last_bop = tem;
+ // By convention position (0,0) in a dvi file is placed at (1in, 1in).
+ cur_h = font::res;
+ cur_v = font::res;
+ end_h = 0;
+ if (page_count == 1) {
+ char buf[256];
+ // at least dvips uses this
+ double length = user_paper_length ? user_paper_length :
+ double(font::paperlength) / font::res;
+ double width = user_paper_width ? user_paper_width :
+ double(font::paperwidth) / font::res;
+ if (width > 0 && length > 0) {
+ sprintf(buf, "papersize=%.3fin,%.3fin",
+ landscape_flag ? length : width,
+ landscape_flag ? width : length);
+ do_special(buf);
+ }
+ }
+ if (cur_color != default_color)
+ set_color(&cur_color);
+}
+
+void dvi_printer::end_page(int)
+{
+ set_color(&default_color);
+ if (pushed)
+ end_of_line();
+ out1(eop);
+ cur_font = 0;
+}
+
+void draw_dvi_printer::end_page(int len)
+{
+ dvi_printer::end_page(len);
+ output_pen_size = -1;
+}
+
+void dvi_printer::do_special(const char *s)
+{
+ int len = strlen(s);
+ if (len == 0)
+ return;
+ possibly_begin_line();
+ out_unsigned(xxx1, len);
+ while (*s)
+ out1(*s++);
+}
+
+void dvi_printer::special(char *arg, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+ moveto(env->hpos, env->vpos);
+ do_special(arg);
+}
+
+void dvi_printer::moveto(int h, int v)
+{
+ if (h != cur_h) {
+ out_signed(right1, h - cur_h);
+ cur_h = h;
+ if (cur_h > max_h)
+ max_h = cur_h;
+ }
+ if (v != cur_v) {
+ out_signed(down1, v - cur_v);
+ cur_v = v;
+ if (cur_v > max_v)
+ max_v = cur_v;
+ }
+ end_h = 0;
+}
+
+void dvi_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if (code == 'l') {
+ int x = 0, y = 0;
+ int height = 0, width = 0;
+ int thickness;
+ if (line_thickness < 0)
+ thickness = env->size*RES_7227*linewidth/1000;
+ else if (line_thickness > 0)
+ thickness = line_thickness;
+ else
+ thickness = 1;
+ if (np != 2) {
+ error("2 arguments required for line");
+ }
+ else if (p[0] == 0) {
+ // vertical rule
+ if (p[1] > 0) {
+ x = env->hpos - thickness/2;
+ y = env->vpos + p[1] + thickness/2;
+ height = p[1] + thickness;
+ width = thickness;
+ }
+ else if (p[1] < 0) {
+ x = env->hpos - thickness/2;
+ y = env->vpos + thickness/2;
+ height = thickness - p[1];
+ width = thickness;
+ }
+ }
+ else if (p[1] == 0) {
+ if (p[0] > 0) {
+ x = env->hpos - thickness/2;
+ y = env->vpos + thickness/2;
+ height = thickness;
+ width = p[0] + thickness;
+ }
+ else if (p[0] < 0) {
+ x = env->hpos - p[0] - thickness/2;
+ y = env->vpos + thickness/2;
+ height = thickness;
+ width = thickness - p[0];
+ }
+ }
+ if (height != 0) {
+ moveto(x, y);
+ out1(put_rule);
+ out4(height);
+ out4(width);
+ }
+ }
+ else if (code == 't') {
+ if (np == 0) {
+ line_thickness = -1;
+ }
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2)
+ error("0 or 1 argument required for thickness");
+ else
+ line_thickness = p[0];
+ }
+ }
+ else if (code == 'R') {
+ if (np != 2)
+ error("2 arguments required for rule");
+ else if (p[0] != 0 || p[1] != 0) {
+ int dh = p[0];
+ int dv = p[1];
+ int oh = env->hpos;
+ int ov = env->vpos;
+ if (dv > 0) {
+ ov += dv;
+ dv = -dv;
+ }
+ if (dh < 0) {
+ oh += dh;
+ dh = -dh;
+ }
+ moveto(oh, ov);
+ out1(put_rule);
+ out4(-dv);
+ out4(dh);
+ }
+ }
+}
+
+// XXX Will this overflow?
+
+inline int milliinches(int n)
+{
+ return (n*1000 + font::res/2)/font::res;
+}
+
+void draw_dvi_printer::set_line_thickness(const environment *env)
+{
+ int desired_pen_size
+ = milliinches(line_thickness < 0
+ // Will this overflow?
+ ? env->size*RES_7227*linewidth/1000
+ : line_thickness);
+ if (desired_pen_size != output_pen_size) {
+ char buf[256];
+ sprintf(buf, "pn %d", desired_pen_size);
+ do_special(buf);
+ output_pen_size = desired_pen_size;
+ }
+}
+
+void draw_dvi_printer::fill_next(const environment *env)
+{
+ unsigned int g;
+ if (env->fill->is_default())
+ g = 0;
+ else {
+ // currently, only BW support
+ env->fill->get_gray(&g);
+ }
+ char buf[256];
+ sprintf(buf, "sh %.3g", 1 - double(g) / double(color::MAX_COLOR_VAL));
+ do_special(buf);
+}
+
+void draw_dvi_printer::draw(int code, int *p, int np, const environment *env)
+{
+ char buf[1024];
+ int fill_flag = 0;
+ switch (code) {
+ case 'C':
+ fill_flag = 1;
+ // fall through
+ case 'c':
+ {
+ // troff adds an extra argument to C
+ if (np != 1 && !(code == 'C' && np == 2)) {
+ error("1 argument required for circle");
+ break;
+ }
+ moveto(env->hpos+p[0]/2, env->vpos);
+ if (fill_flag)
+ fill_next(env);
+ else
+ set_line_thickness(env);
+ int rad;
+ rad = milliinches(p[0]/2);
+ sprintf(buf, "%s 0 0 %d %d 0 6.28319",
+ (fill_flag ? "ia" : "ar"),
+ rad,
+ rad);
+ do_special(buf);
+ break;
+ }
+ case 'l':
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ moveto(env->hpos, env->vpos);
+ set_line_thickness(env);
+ do_special("pa 0 0");
+ sprintf(buf, "pa %d %d", milliinches(p[0]), milliinches(p[1]));
+ do_special(buf);
+ do_special("fp");
+ break;
+ case 'E':
+ fill_flag = 1;
+ // fall through
+ case 'e':
+ if (np != 2) {
+ error("2 arguments required for ellipse");
+ break;
+ }
+ moveto(env->hpos+p[0]/2, env->vpos);
+ if (fill_flag)
+ fill_next(env);
+ else
+ set_line_thickness(env);
+ sprintf(buf, "%s 0 0 %d %d 0 6.28319",
+ (fill_flag ? "ia" : "ar"),
+ milliinches(p[0]/2),
+ milliinches(p[1]/2));
+ do_special(buf);
+ break;
+ case 'P':
+ fill_flag = 1;
+ // fall through
+ case 'p':
+ {
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ break;
+ }
+ moveto(env->hpos, env->vpos);
+ if (fill_flag)
+ fill_next(env);
+ else
+ set_line_thickness(env);
+ do_special("pa 0 0");
+ int h = 0, v = 0;
+ for (int i = 0; i < np; i += 2) {
+ h += p[i];
+ v += p[i+1];
+ sprintf(buf, "pa %d %d", milliinches(h), milliinches(v));
+ do_special(buf);
+ }
+ do_special("pa 0 0");
+ do_special(fill_flag ? "ip" : "fp");
+ break;
+ }
+ case '~':
+ {
+ if (np & 1) {
+ error("even number of arguments required for spline");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for spline");
+ break;
+ }
+ moveto(env->hpos, env->vpos);
+ set_line_thickness(env);
+ do_special("pa 0 0");
+ int h = 0, v = 0;
+ for (int i = 0; i < np; i += 2) {
+ h += p[i];
+ v += p[i+1];
+ sprintf(buf, "pa %d %d", milliinches(h), milliinches(v));
+ do_special(buf);
+ }
+ do_special("sp");
+ break;
+ }
+ case 'a':
+ {
+ if (np != 4) {
+ error("4 arguments required for arc");
+ break;
+ }
+ set_line_thickness(env);
+ double c[2];
+ if (adjust_arc_center(p, c)) {
+ int rad = milliinches(int(sqrt(c[0]*c[0] + c[1]*c[1]) + .5));
+ moveto(env->hpos + int(c[0]), env->vpos + int(c[1]));
+ double start = atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0]);
+ double end = atan2(-c[1], -c[0]);
+ if (end - start < 0)
+ start -= 2 * 3.14159265358;
+ sprintf(buf, "ar 0 0 %d %d %f %f", rad, rad, start, end);
+ do_special(buf);
+ }
+ else {
+ moveto(env->hpos, env->vpos);
+ do_special("pa 0 0");
+ sprintf(buf,
+ "pa %d %d",
+ milliinches(p[0] + p[2]),
+ milliinches(p[1] + p[3]));
+ do_special(buf);
+ do_special("fp");
+ }
+ break;
+ }
+ case 't':
+ {
+ if (np == 0) {
+ line_thickness = -1;
+ }
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ }
+ case 'R':
+ {
+ if (np != 2) {
+ error("2 arguments required for rule");
+ break;
+ }
+ int dh = p[0];
+ if (dh == 0)
+ break;
+ int dv = p[1];
+ if (dv == 0)
+ break;
+ int oh = env->hpos;
+ int ov = env->vpos;
+ if (dv > 0) {
+ ov += dv;
+ dv = -dv;
+ }
+ if (dh < 0) {
+ oh += dh;
+ dh = -dh;
+ }
+ moveto(oh, ov);
+ out1(put_rule);
+ out4(-dv);
+ out4(dh);
+ break;
+ }
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+}
+
+font *dvi_printer::make_font(const char *nm)
+{
+ return dvi_font::load_dvi_font(nm);
+}
+
+printer *make_printer()
+{
+ if (draw_flag)
+ return new draw_dvi_printer;
+ else
+ return new dvi_printer;
+}
+
+static void usage(FILE *stream);
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "dF:I:lp:vw:", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'd':
+ draw_flag = 0;
+ break;
+ case 'l':
+ landscape_flag = 1;
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'I':
+ // ignore include search path
+ break;
+ case 'p':
+ if (!font::scan_papersize(optarg, 0,
+ &user_paper_length, &user_paper_width))
+ error("ignoring invalid paper format '%1'", optarg);
+ break;
+ case 'v':
+ {
+ printf("GNU grodvi (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case 'w':
+ if (sscanf(optarg, "%d", &linewidth) != 1
+ || linewidth < 0 || linewidth > 1000) {
+ error("invalid line width '%1' ignored", optarg);
+ linewidth = DEFAULT_LINEWIDTH;
+ }
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ SET_BINARY(fileno(stdout));
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-dl] [-F dir] [-p paper-format] [-w n] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grodvi/grodvi.1.man b/src/devices/grodvi/grodvi.1.man
new file mode 100644
index 0000000..05cbe0d
--- /dev/null
+++ b/src/devices/grodvi/grodvi.1.man
@@ -0,0 +1,633 @@
+.TH grodvi @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grodvi \-
+.I groff
+output driver for TeX DVI format
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020, 2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grodvi_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el .ds tx TeX
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'dvi' .ft \\$1
+..
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grodvi
+.RB [ \-dl ]
+.RB [ \-F\~\c
+.IR dir ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-w\~\c
+.IR n ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grodvi
+.B \-\-help
+.YS
+.
+.
+.SY grodvi
+.B \-v
+.
+.SY grodvi
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+DVI output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into \*[tx] DVI format.
+.
+Normally,
+.I grodvi
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~dvi \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grodvi .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grodvi
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+The DVI file generated by
+.I grodvi
+can interpreted by any correctly written DVI driver.
+.
+.I troff \" generic
+drawing primitives are implemented using
+.I tpic
+version\~2 specials.
+.
+If the driver does not support these,
+.B \[rs]D
+escape sequences will not produce any output.
+.
+.
+.P
+Encapsulated PostScript (EPS) files can be easily included;
+use the
+.B PSPIC
+macro.
+.
+.I pspic.tmac
+is loaded automatically by
+.IR dvi.tmac .
+.
+See
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.P
+The default color used by the
+.B \[rs]m
+and
+.B \[rs]M
+escape sequences is black.
+.
+Currently,
+the stroke color for
+.B \[rs]D
+drawing escape sequences is black;
+fill color values are translated to gray.
+.
+.
+.P
+In
+.IR groff ,
+as in AT&T
+.IR troff , \" AT&T
+the
+.B \[rs]N
+escape sequence can be used to access any glyph in the current font by
+its position in the corresponding TFM file.
+.
+.
+.P
+By design,
+the DVI format doesn't care about the physical dimensions of the output
+medium.
+.
+Instead,
+.I grodvi
+emits the equivalent to \*[tx]'s
+.BI \%\[rs]special{\:\%papersize= width , length }
+on the first page;
+.I dvips
+(or another DVI driver)
+then sets the page size accordingly.
+.
+If either the page width or length is not positive,
+no
+.B \%papersize
+special is output.
+.
+.
+.P
+A device control escape sequence
+.BI \[rs]X\[aq] anything \[aq]
+is translated to the same DVI file instructions as would be produced by
+.BI \%\[rs]special{ anything }
+in \*[tx];
+.I anything
+cannot contain a newline.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I grodvi
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Fonts are grouped into families
+.B T
+and
+.B H
+having members in each style.
+.
+\[lq]CM\[rq] abbreviates \[lq]Computer Modern\[rq].
+.
+.
+.RS
+.TP
+.B TR
+.FT TR
+CM Roman (cmr10)
+.FT
+.
+.TQ
+.B TI
+.FT TI
+CM Text Italic (cmti10)
+.FT
+.
+.TQ
+.B TB
+.FT TB
+CM Bold Extended Roman (cmbx10)
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+CM Bold Extended Text Italic (cmbxti10)
+.FT
+.
+.TQ
+.B HR
+.FT HR
+CM Sans Serif (cmss10)
+.FT
+.
+.TQ
+.B HI
+.FT HI
+CM Slanted Sans Serif (cmssi10)
+.FT
+.
+.TQ
+.B HB
+.FT HB
+CM Sans Serif Bold Extended (cmssbx10)
+.FT
+.
+.TQ
+.B HBI
+.FT HBI
+CM Slanted Sans Serif Bold Extended (cmssbxo10)
+.FT
+.RE
+.
+.
+.LP
+The following fonts are not members of a family.
+.
+.
+.RS
+.TP
+.B CW
+.FT CW
+CM Typewriter Text (cmtt10)
+.FT
+.
+.TQ
+.B CWI
+.FT CWI
+CM Italic Typewriter Text (cmitt10)
+.FT
+.RE
+.
+.
+.P
+Special fonts include
+.B MI
+(cmmi10),
+.B S
+(cmsy10),
+.B EX
+(cmex10),
+.B SC
+(cmtex10,
+only for
+.BR CW ),
+and,
+perhaps surprisingly,
+.BR TR ,
+.BR TI ,
+and
+.BR CW ,
+.\" See font/devdvi/generate/Makefile for details.
+because \*[tx] places some glyphs in text fonts that
+.I troff \" generic
+generally does not.
+.
+For italic fonts,
+.B CWI
+is used instead of
+.BR CW .
+.
+.
+.P
+Finally,
+the symbol fonts of the American Mathematical Society are available as
+special fonts
+.B SA
+(msam10) and
+.B SB
+(msbm10).
+.
+They are are not mounted by default.
+.
+.
+.br
+.ne 2v
+.P
+The
+.I @g@troff
+option
+.B \-mec
+loads the
+.I ec.tmac
+macro file,
+employing the EC and TC fonts instead of CM.
+.
+These are designed similarly to the Computer Modern fonts;
+further,
+they provide Euro
+.B \[rs][Eu]
+and per mille
+.B \[rs][%0]
+glyphs.
+.
+.I ec.tmac
+must be loaded before any language-specific macro files because it does
+not set up the codes necessary for automatic hyphenation.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+Use
+.MR tfmtodit @MAN1EXT@
+to create
+.I groff
+font description files from TFM
+(\*[tx] font metrics)
+files.
+.
+The font description file should contain the following additional
+directives,
+which
+.I tfmtodit
+generates automatically.
+.
+.
+.TP
+.BI internalname\~ name
+The name of the TFM file
+(without the
+.I .tfm
+extension) is
+.IR name .
+.
+.
+.TP
+.BI checksum\~ n
+The checksum in the TFM file is
+.IR n .
+.
+.
+.TP
+.BI designsize\~ n
+The design size in the TFM file is
+.IR n .
+.
+.
+.\" ====================================================================
+.SS "Drawing commands"
+.\" ====================================================================
+.
+.I grodvi
+supports an additional drawing command.
+.
+.
+.TP
+.BI \[rs]D\[aq]R\~ "dh dv" \[aq]
+Draw a rule
+(solid black rectangle)
+with one corner at the drawing position,
+and the diagonally opposite corner at the drawing position
+.RI +( dh , dv ),
+which becomes the new drawing position afterward.
+.
+This command produces a rule in the DVI file and so can be printed even
+with a driver that does not support
+.I tpic
+specials,
+unlike the other
+.B \[rs]D
+commands.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-d
+Do not use
+.I tpic
+specials to implement drawing commands.
+.
+Horizontal and vertical lines are implemented by rules.
+.
+Other drawing commands are ignored.
+.
+.
+.TP
+.BI \-F\~ dir
+Prepend directory
+.RI dir /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR dvi .
+.
+.
+.TP
+.B \-l
+Use landscape orientation rather than portrait.
+.
+.
+.TP
+.BI \-p\~ paper-format
+Set physical dimensions of output medium,
+overriding the
+.BR \%papersize ,
+.BR \%paperlength ,
+and
+.B \%paperwidth
+directives in the
+.I DESC
+file.
+.
+.I paper-format
+can be any argument accepted by the
+.B \%papersize
+directive;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI \-w\~ n
+Draw rules (lines) with a thickness of
+.IR n \~thousandths
+of an em.
+.
+The default thickness is
+.B 40
+(0.04\~em).
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to search for
+.IR devdvi ,
+.IR grodvi 's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devdvi/\:DESC
+describes the
+.B dvi
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devdvi/ F
+describes the font known
+.RI as\~ F
+on device
+.BR dvi .
+.
+.
+.TP
+.I @MACRODIR@/\:dvi\:.tmac
+defines font mappings,
+special characters,
+and colors for use with the
+.B dvi
+output device.
+.
+It is automatically loaded by
+.I \%troffrc
+when the
+.B dvi
+output device is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:ec\:.tmac
+configures the
+.B dvi
+output device to use
+the EC and TC font families instead of CM
+(Computer Modern).
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+DVI files produced by
+.I grodvi
+use a different resolution
+(57,816 units per inch)
+from those produced by \*[tx].
+.
+Incorrectly written drivers which assume the resolution used by \*[tx],
+rather than using the resolution specified in the DVI file,
+will not work with
+.IR grodvi .
+.
+.
+.LP
+When using the
+.B \-d
+option with boxed tables,
+vertical and horizontal lines can sometimes protrude by one pixel.
+.
+This is a consequence of the way \*[tx] requires that the heights
+and widths of rules be rounded.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR https://\:texfaq\:.org/\:FAQ\-\:ECfonts
+\[lq]What are the EC fonts?\[rq]
+.UE ;
+\*[tx] FAQ: Frequently Asked Question List for \*[tx]
+.
+.
+.P
+.MR tfmtodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@ ,
+.MR groff_tmac @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm FT
+.rm tx
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grodvi_1_man_C]
+.do rr *groff_grodvi_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grodvi/grodvi.am b/src/devices/grodvi/grodvi.am
new file mode 100644
index 0000000..ce93359
--- /dev/null
+++ b/src/devices/grodvi/grodvi.am
@@ -0,0 +1,32 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grodvi
+grodvi_SOURCES = src/devices/grodvi/dvi.cpp
+grodvi_LDADD = \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a $(LIBM)
+man1_MANS += src/devices/grodvi/grodvi.1
+EXTRA_DIST += src/devices/grodvi/grodvi.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grohtml/grohtml.1.man b/src/devices/grohtml/grohtml.1.man
new file mode 100644
index 0000000..2243b47
--- /dev/null
+++ b/src/devices/grohtml/grohtml.1.man
@@ -0,0 +1,731 @@
+.TH grohtml @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grohtml, post\-grohtml, pre\-grohtml \-
+.I groff
+output driver for HTML
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1999-2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grohtml_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY pre\-grohtml
+.RB [ \-epV ]
+.RB [ \-a
+.IR anti-aliasing-text-bits ]
+.RB [ \-D
+.IR image-directory ]
+.RB [ \-F
+.IR font-directory ]
+.RB [ \-g
+.IR anti-aliasing-graphic-bits ]
+.RB [ \-i
+.IR resolution ]
+.RB [ \-I
+.IR image-stem ]
+.RB [ \-o
+.IR image-vertical-offset ]
+.RB [ \-x
+.IR html-dialect ]
+.I troff-command
+.I troff-argument
+\&.\|.\|.
+.YS
+.
+.
+.SY pre\-grohtml
+.B \-\-help
+.YS
+.
+.
+.SY pre\-grohtml
+.B \-v
+.
+.SY pre\-grohtml
+.B \-\-version
+.YS
+.
+.
+.SY post\-grohtml
+.RB [ \-bCGhlnrVy ]
+.RB [ \-F
+.IR font-directory ]
+.RB [ \-j
+.IR output-stem ]
+.RB [ \-s
+.IR base-point-size ]
+.RB [ \-S
+.IR heading-level ]
+.RB [ \-x
+.IR html-dialect ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY post\-grohtml
+.B \-\-help
+.YS
+.
+.
+.SY post\-grohtml
+.B \-v
+.
+.SY post\-grohtml
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+system's HTML support consists of a preprocessor,
+.IR \%pre\-grohtml ,
+and an output driver,
+.IR \%post\-grohtml ;
+together,
+they translate
+.MR roff @MAN7EXT@
+documents to HTML.
+.
+Because a preprocessor is (uniquely) required for this output driver,
+users should invoke
+.I \%grohtml
+via the
+.MR groff @MAN1EXT@
+command with the
+.B \-Thtml
+or
+.B \-Txhtml
+options.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR \%grohtml .
+.
+If no operands are given,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I \%grohtml
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+.I \%grohtml
+invokes
+.I groff
+twice.
+.
+In the first pass,
+the preprocessor
+.I \%pre\-grohtml
+renders
+pictures,
+equations,
+and tables as images in PostScript format using the
+.B ps
+output device.
+.
+In the second pass,
+the output driver
+.I \%post\-grohtml
+translates the output of
+.MR @g@troff @MAN1EXT@
+to HTML.
+.
+.
+.P
+.I \%grohtml
+writes output encoded in \%UTF-8 and has built-in HTML entities for all
+non-composite Unicode characters.
+.
+In spite of this,
+.I groff
+may issue warnings about unknown special characters if they can't be
+found during the first pass.
+.
+Such warnings can be safely ignored unless the special characters
+appear inside a table or equation.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I \%grohtml
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Fonts are grouped into families
+.B T
+and
+.B C
+having members in each style.
+.
+.
+.RS
+.TP
+.B TR
+Times roman
+.
+.TQ
+.B TI
+Times italic
+.
+.TQ
+.B TB
+Times bold
+.
+.TQ
+.B TBI
+Times bold-italic
+.
+.TQ
+.B CR
+Courier roman
+.
+.TQ
+.B CI
+Courier italic
+.
+.TQ
+.B CB
+Courier bold
+.
+.TQ
+.B CBI
+Courier bold-italic
+.RE
+.
+.
+.P
+A special font,
+.BR S ,
+is also provided to accommodate
+.I roff
+documents that expect it to always be available.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+The font description files used with
+.I \%grohtml
+expose the same glyph repertoire in their
+.B charset
+sections.
+.
+See
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS Dependencies
+.\" ====================================================================
+.
+.I \%pre\-grohtml
+generates an image whenever an
+.I @g@eqn
+equation,
+.I @g@tbl
+table,
+or
+.I @g@pic
+picture is encountered in the input.
+.
+.I \%grohtml
+therefore may run several commands as part of its operation.
+.
+These include the \%Netpbm tools
+.IR \%pnmcrop ,
+.IR \%pnmcut ,
+and
+.IR \%pnmtopng ;
+\%Ghostscript
+.RI ( gs );
+and the \%PSUtils tool
+.IR \%psselect .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-a \~anti-aliasing-text-bits
+Number of bits of antialiasing information to be used by text when
+generating PNG images.
+.
+The default
+.RB is\~ 4
+but
+.BR 0 ,
+.BR 1 ,
+and
+.B 2
+are also valid.
+.
+Your system's version of
+.I gs
+must support the
+.B \%\-dTextAlphaBits
+option in order to exploit antialiasing.
+.\" XXX: How antiquated are the ones that don't? Get rid of this?
+.
+A value
+.RB of\~ 0
+stops
+.I \%grohtml
+from issuing antialiasing commands to
+.IR gs .
+.
+.
+.TP
+.B \-b
+Initialize the background color to white.
+.
+.
+.TP
+.B \-C
+Suppress output of \[lq]CreationDate:\[rq] HTML comment.
+.
+.
+.TP
+.BI \-D \~image-directory
+Instruct
+.I \%grohtml
+to place all image files into directory
+.IR image-directory .
+.
+.
+.TP
+.B \-e
+Direct
+.I @g@eqn
+to produce MathML.
+.
+.
+.IP
+This option should not be manually specified;
+it is synthesized by
+.I groff
+depending on whether it was given the
+.B \-Thtml
+or
+.B \-Txhtml
+option.
+.
+.
+.TP
+.BI \-F \~font-directory
+Prepend directory
+.RI font-directory /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR html .
+.
+.
+.TP
+.BI \-g \~anti-aliasing-graphic-bits
+Number of bits of antialiasing information to be used by graphics when
+generating PNG images.
+.
+The default
+.RB is\~ 4
+but
+.BR 0 ,
+.BR 1 ,
+and
+.B 2
+are also valid.
+.
+Your system's version of
+.I gs
+must support the
+.B \%\-dGraphicAlphaBits
+option in order to exploit antialiasing.
+.\" XXX: How antiquated are the ones that don't? Get rid of this?
+.
+A value
+.RB of\~ 0
+stops
+.I \%grohtml
+from issuing antialiasing commands to
+.IR gs .
+.
+.
+.TP
+.B \-G
+Suppress output of \[lq]Creator:\[rq] HTML comment.
+.
+.
+.TP
+.B \-h
+Generate section headings by using HTML
+.B B
+elements and increasing the font size,
+rather than HTML
+.B H
+elements.
+.
+.
+.TP
+.BI \-i \~resolution
+Set the image resolution in pixels per inch;
+the default
+.RB is\~ 100 .
+.
+.
+.TP
+.BI \-I \~image-stem
+Determine the image file name stem.
+.
+If omitted,
+.I \%grohtml
+uses
+.IR \%grohtml\- XXXXX
+(where
+.I XXXXX
+is the process ID).
+.
+A dash is appended to the stem to separate it from the following image
+number.
+.
+.
+.TP
+.BI \-j \~output-stem
+Instruct
+.I \%grohtml
+to split the HTML output into multiple files.
+.
+Output is written to a new file at each section heading
+(but see option
+.B \-S
+below)
+named
+.IR output-stem\- n .html .
+.
+.
+.TP
+.B \-l
+Turn off the production of automatic section links at the top of the
+document.
+.
+.
+.TP
+.B \-n
+Generate simple heading anchors whenever a section/number heading is
+found.
+.
+Without the option the anchor value is the textual heading.
+.
+This can cause problems when a heading contains a \[lq]?\[rq] on older
+versions of some browsers.
+.
+This feature is automatically enabled if a heading contains an image.
+.
+.
+.TP
+.BI \-o \~image-vertical-offset
+Specify the vertical offset of images in points.
+.
+.
+.TP
+.B \-p
+Display page rendering progress to the standard error stream.
+.
+.I \%grohtml
+displays a page number only when an image is required.
+.
+.
+.TP
+.B \-r
+Turn off the automatic header and footer line
+(HTML rule).
+.
+.
+.TP
+.BI \-s \~base-type-size
+Set the document's base type size in points.
+.
+When this size is used in the source,
+it corresponds to the HTML base type size.
+.
+Every increase of two points in the source will produce a
+.RB \[lq] big \[rq]
+element,
+and conversely when a decrease of two points is seen,
+a
+.RB \[lq] small \[rq]
+element is emitted.
+.
+.
+.TP
+.BI \-S \~heading-level
+When splitting HTML output
+(see option
+.B \-j
+above),
+split at each nested heading level defined by
+.IR heading-level ,
+or higher).
+.
+The default is
+.BR 1 .
+.
+.
+.TP
+.B \-V
+Create an XHTML or HTML validator button at the bottom of each page of
+the document.
+.
+.
+.TP
+.BI \-x \~html-dialect
+Select HTML dialect.
+.
+Currently,
+.I html-dialect
+should be either the
+.RB digit\~ 4
+or the
+.RB letter\~ x ,
+which indicates whether
+.I \%grohtml
+should generate HTML\~4 or XHTML,
+respectively.
+.
+.
+.IP
+This option should not be manually specified;
+it is synthesized by
+.I groff
+depending on whether it was given the
+.B \-Thtml
+or
+.B \-Txhtml
+option.
+.
+.
+.TP
+.B \-y
+Produce a right-aligned
+.I groff
+signature at the end of the document
+(only if
+.B \-V
+is also specified).
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to search for
+.IR devhtml ,
+.IR grohtml 's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR ctime 3
+and recorded in an HTML comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devhtml/\:DESC
+describes the
+.B html
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devhtml/ F
+describes the font known
+.RI as\~ F
+on device
+.BR html .
+.
+.
+.TP
+.I @MACRODIR@/\:html\:.tmac
+defines font mappings,
+special characters,
+and colors for use with the
+.B html
+output device.
+.
+It is automatically loaded by
+.I \%troffrc
+when either of the
+.B html
+or
+.B xhtml
+output devices is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:html\-end\:.tmac
+finalizes setup of the
+.B html
+output device.
+.
+It is automatically loaded by
+.I \%troffrc\-end
+when either of the
+.B html
+or
+.B xhtml
+output devices is selected.
+.
+.
+.P
+.I \%grohtml
+uses temporary files.
+.
+See
+.MR groff @MAN1EXT@
+for details about where such files are created.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+.I \%grohtml
+is still beta code.
+.
+.
+.PP
+.I \%grohtml
+does not truly support hyphenation,
+but you can fool it into hyphenating long input lines,
+which can appear in HTML output with a hyphenated word followed by a
+space but no line break.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.\" IR afmtodit (@MAN1EXT@),
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.\" IR psbb (1), \" XXX: what is this?
+.\" IR groff_out (@MAN5EXT@),
+.\" IR groff_char (@MAN7EXT@),
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grohtml_1_man_C]
+.do rr *groff_grohtml_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grohtml/grohtml.am b/src/devices/grohtml/grohtml.am
new file mode 100644
index 0000000..a87cad2
--- /dev/null
+++ b/src/devices/grohtml/grohtml.am
@@ -0,0 +1,40 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += post-grohtml
+post_grohtml_SOURCES = \
+ src/devices/grohtml/post-html.cpp \
+ src/devices/grohtml/html-table.cpp \
+ src/devices/grohtml/html-text.cpp \
+ src/devices/grohtml/output.cpp \
+ src/devices/grohtml/html.h \
+ src/devices/grohtml/html-text.h \
+ src/devices/grohtml/html-table.h
+
+post_grohtml_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grohtml/grohtml.1
+EXTRA_DIST += src/devices/grohtml/grohtml.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grohtml/html-table.cpp b/src/devices/grohtml/html-table.cpp
new file mode 100644
index 0000000..3d7dfcd
--- /dev/null
+++ b/src/devices/grohtml/html-table.cpp
@@ -0,0 +1,848 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.cpp
+ *
+ * html-table.h
+ *
+ * provides the methods necessary to handle indentation and tab
+ * positions using html tables.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "html-table.h"
+#include "ctype.h"
+#include "html.h"
+#include "html-text.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+extern html_dialect dialect;
+
+
+tabs::tabs ()
+ : tab(NULL)
+{
+}
+
+tabs::~tabs ()
+{
+ delete_list();
+}
+
+/*
+ * delete_list - frees the tab list and sets tab to NULL.
+ */
+
+void tabs::delete_list (void)
+{
+ tab_position *p = tab;
+ tab_position *q;
+
+ while (p != NULL) {
+ q = p;
+ p = p->next;
+ delete q;
+ }
+ tab = NULL;
+}
+
+void tabs::clear (void)
+{
+ delete_list();
+}
+
+/*
+ * compatible - returns TRUE if the tab stops in, s, do
+ * not conflict with the current tab stops.
+ * The new tab stops are _not_ placed into
+ * this class.
+ */
+
+int tabs::compatible (const char *s)
+{
+ char align;
+ int total=0;
+ tab_position *last = tab;
+
+ if (last == NULL)
+ return FALSE; // no tab stops defined
+
+ // move over tag name
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+
+ while (*s != (char)0 && last != NULL) {
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect alignment
+ align = *s;
+ // move over alignment
+ s++;
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect tab position
+ total = atoi(s);
+ // move over tab position
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+ if (last->alignment != align || last->position != total)
+ return FALSE;
+
+ last = last->next;
+ }
+ return TRUE;
+}
+
+/*
+ * init - scans the string, s, and initializes the tab stops.
+ */
+
+void tabs::init (const char *s)
+{
+ char align;
+ int total=0;
+ tab_position *last = NULL;
+
+ clear(); // remove any tab stops
+
+ // move over tag name
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+
+ while (*s != (char)0) {
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect alignment
+ align = *s;
+ // move over alignment
+ s++;
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect tab position
+ total = atoi(s);
+ // move over tab position
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+ if (last == NULL) {
+ tab = new tab_position;
+ last = tab;
+ } else {
+ last->next = new tab_position;
+ last = last->next;
+ }
+ last->alignment = align;
+ last->position = total;
+ last->next = NULL;
+ }
+}
+
+/*
+ * check_init - define tab stops using, s, providing none already exist.
+ */
+
+void tabs::check_init (const char *s)
+{
+ if (tab == NULL)
+ init(s);
+}
+
+/*
+ * find_tab - returns the tab number corresponding to the position, pos.
+ */
+
+int tabs::find_tab (int pos)
+{
+ tab_position *p;
+ int i=0;
+
+ for (p = tab; p != NULL; p = p->next) {
+ i++;
+ if (p->position == pos)
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * get_tab_pos - returns the, nth, tab position
+ */
+
+int tabs::get_tab_pos (int n)
+{
+ tab_position *p;
+
+ n--;
+ for (p = tab; (p != NULL) && (n>0); p = p->next) {
+ n--;
+ if (n == 0)
+ return p->position;
+ }
+ return 0;
+}
+
+char tabs::get_tab_align (int n)
+{
+ tab_position *p;
+
+ n--;
+ for (p = tab; (p != NULL) && (n>0); p = p->next) {
+ n--;
+ if (n == 0)
+ return p->alignment;
+ }
+ return 'L';
+}
+
+/*
+ * dump_tab - display tab positions
+ */
+
+void tabs::dump_tabs (void)
+{
+ int i=1;
+ tab_position *p;
+
+ for (p = tab; p != NULL; p = p->next) {
+ printf("tab %d is %d\n", i, p->position);
+ i++;
+ }
+}
+
+/*
+ * html_table - methods
+ */
+
+html_table::html_table (simple_output *op, int linelen)
+ : out(op), columns(NULL), linelength(linelen), last_col(NULL), start_space(FALSE)
+{
+ tab_stops = new tabs();
+}
+
+html_table::~html_table ()
+{
+ cols *c;
+ if (tab_stops != NULL)
+ delete tab_stops;
+
+ c = columns;
+ while (columns != NULL) {
+ columns = columns->next;
+ delete c;
+ c = columns;
+ }
+}
+
+/*
+ * remove_cols - remove a list of columns as defined by, c.
+ */
+
+void html_table::remove_cols (cols *c)
+{
+ cols *p;
+
+ while (c != NULL) {
+ p = c;
+ c = c->next;
+ delete p;
+ }
+}
+
+/*
+ * set_linelength - sets the line length value in this table.
+ * It also adds an extra blank column to the
+ * table should linelen exceed the last column.
+ */
+
+void html_table::set_linelength (int linelen)
+{
+ cols *p = NULL;
+ cols *c;
+ linelength = linelen;
+
+ for (c = columns; c != NULL; c = c->next) {
+ if (c->right > linelength) {
+ c->right = linelength;
+ remove_cols(c->next);
+ c->next = NULL;
+ return;
+ }
+ p = c;
+ }
+ if (p != NULL && p->right > 0 && linelength > p->right)
+ add_column(p->no+1, p->right, linelength, 'L');
+}
+
+/*
+ * get_effective_linelength -
+ */
+
+int html_table::get_effective_linelength (void)
+{
+ if (columns != NULL)
+ return linelength - columns->left;
+ else
+ return linelength;
+}
+
+/*
+ * add_indent - adds the indent to a table.
+ */
+
+void html_table::add_indent (int indent)
+{
+ if (columns != NULL && columns->left > indent)
+ add_column(0, indent, columns->left, 'L');
+}
+
+/*
+ * emit_table_header - emits the html header for this table.
+ */
+
+void html_table::emit_table_header (int space)
+{
+ if (columns == NULL)
+ return;
+
+ // dump_table();
+
+ last_col = NULL;
+ if (linelength > 0) {
+ out->nl();
+ out->nl();
+
+ out->put_string("<table width=\"100%\"")
+ .put_string(" border=\"0\" rules=\"none\" frame=\"void\"\n")
+ .put_string(" cellspacing=\"0\" cellpadding=\"0\"");
+ out->put_string(">")
+ .nl();
+ if (dialect == xhtml)
+ emit_colspan();
+ out->put_string("<tr valign=\"top\" align=\"left\"");
+ if (space) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+ out->put_string(">").nl();
+ }
+}
+
+/*
+ * get_right - returns the right most position of this column.
+ */
+
+int html_table::get_right (cols *c)
+{
+ if (c != NULL && c->right > 0)
+ return c->right;
+ if (c->next != NULL)
+ return c->left;
+ return linelength;
+}
+
+/*
+ * set_space - assigns start_space. Used to determine the
+ * vertical alignment when generating the next table row.
+ */
+
+void html_table::set_space (int space)
+{
+ start_space = space;
+}
+
+/*
+ * emit_colspan - emits a series of colspan entries defining the
+ * table columns.
+ */
+
+void html_table::emit_colspan (void)
+{
+ cols *b = columns;
+ cols *c = columns;
+ int width = 0;
+
+ out->put_string("<colgroup>");
+ while (c != NULL) {
+ if (b != NULL && b != c && is_gap(b))
+ /*
+ * blank column for gap
+ */
+ out->put_string("<col width=\"")
+ .put_number(is_gap(b))
+ .put_string("%\" class=\"center\"></col>")
+ .nl();
+
+ width = (get_right(c)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (c->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ switch (c->alignment) {
+ case 'C':
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\" class=\"center\"></col>")
+ .nl();
+ break;
+ case 'R':
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\" class=\"right\"></col>")
+ .nl();
+ break;
+ default:
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\"></col>")
+ .nl();
+ }
+ b = c;
+ c = c->next;
+ }
+ out->put_string("</colgroup>").nl();
+}
+
+/*
+ * emit_td - writes out a <td> tag with a corresponding width
+ * if the dialect is html4.
+ */
+
+void html_table::emit_td (int percentage, const char *s)
+{
+ if (percentage) {
+ if (dialect == html4) {
+ out->put_string("<td width=\"")
+ .put_number(percentage)
+ .put_string("%\"");
+ if (s != NULL)
+ out->put_string(s);
+ out->nl();
+ }
+ else {
+ out->put_string("<td");
+ if (s != NULL)
+ out->put_string(s);
+ out->nl();
+ }
+ }
+}
+
+/*
+ * emit_col - moves onto column, n.
+ */
+
+void html_table::emit_col (int n)
+{
+ cols *c = columns;
+ cols *b = columns;
+ int width = 0;
+
+ // must be a different row
+ if (last_col != NULL && n <= last_col->no)
+ emit_new_row();
+
+ while (c != NULL && c->no < n)
+ c = c->next;
+
+ // can we find column, n?
+ if (c != NULL && c->no == n) {
+ // shutdown previous column
+ if (last_col != NULL)
+ out->put_string("</td>").nl();
+
+ // find previous column
+ if (last_col == NULL)
+ b = columns;
+ else
+ b = last_col;
+
+ // have we a gap?
+ if (last_col != NULL) {
+ emit_td(is_gap(b), "></td>");
+ b = b->next;
+ }
+
+ // move across to column n
+ while (b != c) {
+ // we compute the difference after converting positions
+ // to avoid rounding errors
+ width = (get_right(b)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (b->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ emit_td(width, "></td>");
+ // have we a gap?
+ emit_td(is_gap(b), "></td>");
+ b = b->next;
+ }
+ width = (get_right(b)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (b->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ switch (b->alignment) {
+ case 'C':
+ emit_td(width, " align=center>");
+ break;
+ case 'R':
+ emit_td(width, " align=right>");
+ break;
+ default:
+ emit_td(width);
+ }
+ // remember column, b
+ last_col = b;
+ }
+}
+
+/*
+ * finish_row -
+ */
+
+void html_table::finish_row (void)
+{
+ int n = 0;
+ cols *c;
+
+ if (last_col != NULL) {
+ for (c = last_col->next; c != NULL; c = c->next)
+ n = c->no;
+
+ if (n > 0)
+ emit_col(n);
+#if 1
+ if (last_col != NULL) {
+ out->put_string("</td>");
+ last_col = NULL;
+ }
+#endif
+ out->put_string("</tr>").nl();
+ }
+}
+
+/*
+ * emit_new_row - move to the next row.
+ */
+
+void html_table::emit_new_row (void)
+{
+ finish_row();
+
+ out->put_string("<tr valign=\"top\" align=\"left\"");
+ if (start_space) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+ out->put_string(">").nl();
+ start_space = FALSE;
+ last_col = NULL;
+}
+
+void html_table::emit_finish_table (void)
+{
+ finish_row();
+ out->put_string("</table>");
+}
+
+/*
+ * add_column - adds a column. It returns FALSE if hstart..hend
+ * crosses into a different columns.
+ */
+
+int html_table::add_column (int coln, int hstart, int hend, char align)
+{
+ cols *c = get_column(coln);
+
+ if (c == NULL)
+ return insert_column(coln, hstart, hend, align);
+ else
+ return modify_column(c, hstart, hend, align);
+}
+
+/*
+ * get_column - returns the column, coln.
+ */
+
+cols *html_table::get_column (int coln)
+{
+ cols *c = columns;
+
+ while (c != NULL && coln != c->no)
+ c = c->next;
+
+ if (c != NULL && coln == c->no)
+ return c;
+ else
+ return NULL;
+}
+
+/*
+ * insert_column - inserts a column, coln.
+ * It returns TRUE if it does not bump into
+ * another column.
+ */
+
+int html_table::insert_column (int coln, int hstart, int hend, char align)
+{
+ cols *c = columns;
+ cols *l = columns;
+ cols *n = NULL;
+
+ while (c != NULL && c->no < coln) {
+ l = c;
+ c = c->next;
+ }
+ if (l != NULL && l->no>coln && hend > l->left)
+ return FALSE; // new column bumps into previous one
+
+ l = NULL;
+ c = columns;
+ while (c != NULL && c->no < coln) {
+ l = c;
+ c = c->next;
+ }
+
+ if ((l != NULL) && (hstart < l->right))
+ return FALSE; // new column bumps into previous one
+
+ if ((l != NULL) && (l->next != NULL) &&
+ (l->next->left < hend))
+ return FALSE; // new column bumps into next one
+
+ n = new cols;
+ if (l == NULL) {
+ n->next = columns;
+ columns = n;
+ } else {
+ n->next = l->next;
+ l->next = n;
+ }
+ n->left = hstart;
+ n->right = hend;
+ n->no = coln;
+ n->alignment = align;
+ return TRUE;
+}
+
+/*
+ * modify_column - given a column, c, modify the width to
+ * contain hstart..hend.
+ * It returns TRUE if it does not clash with
+ * the next or previous column.
+ */
+
+int html_table::modify_column (cols *c, int hstart, int hend, char align)
+{
+ cols *l = columns;
+
+ while (l != NULL && l->next != c)
+ l = l->next;
+
+ if ((l != NULL) && (hstart < l->right))
+ return FALSE; // new column bumps into previous one
+
+ if ((c->next != NULL) && (c->next->left < hend))
+ return FALSE; // new column bumps into next one
+
+ if (c->left > hstart)
+ c->left = hstart;
+
+ if (c->right < hend)
+ c->right = hend;
+
+ c->alignment = align;
+
+ return TRUE;
+}
+
+/*
+ * find_tab_column - finds the column number for position, pos.
+ * It searches through the list tab stops.
+ */
+
+int html_table::find_tab_column (int pos)
+{
+ // remember the first column is reserved for untabbed glyphs
+ return tab_stops->find_tab(pos)+1;
+}
+
+/*
+ * find_column - find the column number for position, pos.
+ * It searches through the list of columns.
+ */
+
+int html_table::find_column (int pos)
+{
+ int p=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next) {
+ if (c->left > pos)
+ return p;
+ p = c->no;
+ }
+ return p;
+}
+
+/*
+ * no_columns - returns the number of table columns (rather than tabs)
+ */
+
+int html_table::no_columns (void)
+{
+ int n=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next)
+ n++;
+ return n;
+}
+
+/*
+ * is_gap - returns the gap between column, c, and the next column.
+ */
+
+int html_table::is_gap (cols *c)
+{
+ if (c == NULL || c->right <= 0 || c->next == NULL)
+ return 0;
+ else
+ // we compute the difference after converting positions
+ // to avoid rounding errors
+ return (c->next->left*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (c->right*100 + get_effective_linelength()/2)
+ / get_effective_linelength();
+}
+
+/*
+ * no_gaps - returns the number of table gaps between the columns
+ */
+
+int html_table::no_gaps (void)
+{
+ int n=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next)
+ if (is_gap(c))
+ n++;
+ return n;
+}
+
+/*
+ * get_tab_pos - returns the, nth, tab position
+ */
+
+int html_table::get_tab_pos (int n)
+{
+ return tab_stops->get_tab_pos(n);
+}
+
+char html_table::get_tab_align (int n)
+{
+ return tab_stops->get_tab_align(n);
+}
+
+
+void html_table::dump_table (void)
+{
+ if (columns != NULL) {
+ cols *c;
+ for (c = columns; c != NULL; c = c->next) {
+ printf("column %d %d..%d %c\n", c->no, c->left, c->right, c->alignment);
+ }
+ } else
+ tab_stops->dump_tabs();
+}
+
+/*
+ * html_indent - creates an indent with indentation, ind, given
+ * a line length of linelength.
+ */
+
+html_indent::html_indent (simple_output *op, int ind, int pageoffset, int linelength)
+{
+ table = new html_table(op, linelength);
+
+ table->add_column(1, ind+pageoffset, linelength, 'L');
+ table->add_indent(pageoffset);
+ in = ind;
+ pg = pageoffset;
+ ll = linelength;
+}
+
+html_indent::~html_indent (void)
+{
+ end();
+ delete table;
+}
+
+void html_indent::begin (int space)
+{
+ if (in + pg == 0) {
+ if (space) {
+ table->out->put_string(" style=\"margin-top: ");
+ table->out->put_string(STYLE_VERTICAL_SPACE);
+ table->out->put_string("\"");
+ }
+ }
+ else {
+ //
+ // we use exactly the same mechanism for calculating
+ // indentation as html_table::emit_col
+ //
+ table->out->put_string(" style=\"margin-left:")
+ .put_number(((in + pg) * 100 + ll/2) / ll -
+ (ll/2)/ll)
+ .put_string("%;");
+
+ if (space) {
+ table->out->put_string(" margin-top: ");
+ table->out->put_string(STYLE_VERTICAL_SPACE);
+ }
+ table->out->put_string("\"");
+ }
+}
+
+void html_indent::end (void)
+{
+}
+
+/*
+ * get_reg - collects the registers as supplied during initialization.
+ */
+
+void html_indent::get_reg (int *ind, int *pageoffset, int *linelength)
+{
+ *ind = in;
+ *pageoffset = pg;
+ *linelength = ll;
+}
diff --git a/src/devices/grohtml/html-table.h b/src/devices/grohtml/html-table.h
new file mode 100644
index 0000000..7ed27a9
--- /dev/null
+++ b/src/devices/grohtml/html-table.h
@@ -0,0 +1,133 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.h
+ *
+ * html-table.h
+ *
+ * provides the methods necessary to handle indentation and tab
+ * positions using html tables.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "html.h"
+
+#if !defined(HTML_TABLE_H)
+#define HTML_TABLE_H
+
+typedef struct tab_position {
+ char alignment;
+ int position;
+ struct tab_position *next;
+} tab_position;
+
+
+class tabs {
+public:
+ tabs ();
+ ~tabs ();
+ void clear (void);
+ int compatible (const char *s);
+ void init (const char *s);
+ void check_init (const char *s);
+ int find_tab (int pos);
+ int get_tab_pos (int n);
+ char get_tab_align (int n);
+ void dump_tabs (void);
+
+private:
+ void delete_list (void);
+ tab_position *tab;
+};
+
+/*
+ * define a column
+ */
+
+typedef struct cols {
+ int left, right;
+ int no;
+ char alignment;
+ struct cols *next;
+} cols;
+
+class html_table {
+public:
+ html_table (simple_output *op, int linelen);
+ ~html_table (void);
+ int add_column (int coln, int hstart, int hend, char align);
+ cols *get_column (int coln);
+ int insert_column (int coln, int hstart, int hend, char align);
+ int modify_column (cols *c, int hstart, int hend, char align);
+ int find_tab_column (int pos);
+ int find_column (int pos);
+ int get_tab_pos (int n);
+ char get_tab_align (int n);
+ void set_linelength (int linelen);
+ int no_columns (void);
+ int no_gaps (void);
+ int is_gap (cols *c);
+ void dump_table (void);
+ void emit_table_header (int space);
+ void emit_col (int n);
+ void emit_new_row (void);
+ void emit_finish_table (void);
+ int get_right (cols *c);
+ void add_indent (int indent);
+ void finish_row (void);
+ int get_effective_linelength (void);
+ void set_space (int space);
+ void emit_colspan (void);
+ void emit_td (int percentage, const char *s = ">");
+
+ tabs *tab_stops; /* tab stop positions */
+ simple_output *out;
+private:
+ cols *columns; /* column entries */
+ int linelength;
+ cols *last_col; /* last column started */
+ int start_space; /* have we seen a '.sp' tag? */
+
+ void remove_cols (cols *c);
+};
+
+/*
+ * the indentation wrapper.
+ * Builds an indentation from a html-table.
+ * This table is only emitted if the paragraph is emitted.
+ */
+
+class html_indent {
+public:
+ html_indent (simple_output *op, int ind, int pageoffset, int linelength);
+ ~html_indent (void);
+ void begin (int space); // called if we need to use the indent
+ void get_reg (int *ind, int *pageoffset, int *linelength);
+
+ // the indent is shutdown when it is deleted
+
+private:
+ void end (void);
+ int is_used;
+ int pg; // values of the registers as passed via initialization
+ int ll;
+ int in;
+ html_table *table;
+};
+
+#endif
diff --git a/src/devices/grohtml/html-text.cpp b/src/devices/grohtml/html-text.cpp
new file mode 100644
index 0000000..b07cbe7
--- /dev/null
+++ b/src/devices/grohtml/html-text.cpp
@@ -0,0 +1,1056 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.cpp
+ *
+ * html-text.cpp
+ *
+ * provide a troff like state machine interface which
+ * generates html text.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+
+#include "html-text.h"
+
+html_text::html_text (simple_output *op, html_dialect d) :
+ stackptr(NULL), lastptr(NULL), out(op), dialect(d),
+ space_emitted(TRUE), current_indentation(-1),
+ pageoffset(-1), linelength(-1), blank_para(TRUE),
+ start_space(FALSE)
+{
+}
+
+html_text::~html_text ()
+{
+ flush_text();
+}
+
+
+#if defined(DEBUGGING)
+static int debugStack = FALSE;
+
+
+/*
+ * turnDebug - flip the debugStack boolean and return the new value.
+ */
+
+static int turnDebug (void)
+{
+ debugStack = 1-debugStack;
+ return debugStack;
+}
+
+/*
+ * dump_stack_element - display an element of the html stack, p.
+ */
+
+void html_text::dump_stack_element (tag_definition *p)
+{
+ fprintf(stderr, " | ");
+ switch (p->type) {
+
+ case P_TAG: if (p->indent == NULL) {
+ fprintf(stderr, "<P %s>", (char *)p->arg1); break;
+ } else {
+ fprintf(stderr, "<P %s [TABLE]>", (char *)p->arg1); break;
+ }
+ case I_TAG: fprintf(stderr, "<I>"); break;
+ case B_TAG: fprintf(stderr, "<B>"); break;
+ case SUB_TAG: fprintf(stderr, "<SUB>"); break;
+ case SUP_TAG: fprintf(stderr, "<SUP>"); break;
+ case TT_TAG: fprintf(stderr, "<TT>"); break;
+ case PRE_TAG: if (p->indent == NULL) {
+ fprintf(stderr, "<PRE>"); break;
+ } else {
+ fprintf(stderr, "<PRE [TABLE]>"); break;
+ }
+ case SMALL_TAG: fprintf(stderr, "<SMALL>"); break;
+ case BIG_TAG: fprintf(stderr, "<BIG>"); break;
+ case BREAK_TAG: fprintf(stderr, "<BREAK>"); break;
+ case COLOR_TAG: {
+ if (p->col.is_default())
+ fprintf(stderr, "<COLOR (default)>");
+ else {
+ unsigned int r, g, b;
+
+ p->col.get_rgb(&r, &g, &b);
+ fprintf(stderr, "<COLOR %x %x %x>", r/0x101, g/0x101, b/0x101);
+ }
+ break;
+ }
+ default: fprintf(stderr, "unknown tag");
+ }
+ if (p->text_emitted)
+ fprintf(stderr, "[t] ");
+}
+
+/*
+ * dump_stack - debugging function only.
+ */
+
+void html_text::dump_stack (void)
+{
+ if (debugStack) {
+ tag_definition *p = stackptr;
+
+ while (p != NULL) {
+ dump_stack_element(p);
+ p = p->next;
+ }
+ }
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+#else
+void html_text::dump_stack (void) {}
+#endif
+
+
+/*
+ * end_tag - shuts down the tag.
+ */
+
+void html_text::end_tag (tag_definition *t)
+{
+ switch (t->type) {
+
+ case I_TAG: out->put_string("</i>"); break;
+ case B_TAG: out->put_string("</b>"); break;
+ case P_TAG: if (t->indent == NULL) {
+ out->put_string("</p>");
+ } else {
+ delete t->indent;
+ t->indent = NULL;
+ out->put_string("</p>");
+ }
+ out->enable_newlines(FALSE);
+ blank_para = TRUE; break;
+ case SUB_TAG: out->put_string("</sub>"); break;
+ case SUP_TAG: out->put_string("</sup>"); break;
+ case TT_TAG: out->put_string("</tt>"); break;
+ case PRE_TAG: out->put_string("</pre>"); out->enable_newlines(TRUE);
+ blank_para = TRUE;
+ if (t->indent != NULL)
+ delete t->indent;
+ t->indent = NULL;
+ break;
+ case SMALL_TAG: if (! is_in_pre ())
+ out->put_string("</small>");
+ break;
+ case BIG_TAG: if (! is_in_pre ())
+ out->put_string("</big>");
+ break;
+ case COLOR_TAG: if (! is_in_pre ())
+ out->put_string("</font>");
+ break;
+
+ default:
+ error("unrecognised tag");
+ }
+}
+
+/*
+ * issue_tag - writes out an html tag with argument.
+ * space == 0 if no space is requested
+ * space == 1 if a space is requested
+ * space == 2 if tag should not have a space style
+ */
+
+void html_text::issue_tag (const char *tagname, const char *arg,
+ int space)
+{
+ if ((arg == 0) || (strlen(arg) == 0))
+ out->put_string(tagname);
+ else {
+ out->put_string(tagname);
+ out->put_string(" ");
+ out->put_string(arg);
+ }
+ if (space == TRUE) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+#if 0
+ if (space == TRUE || space == FALSE)
+ out->put_string(" valign=\"top\"");
+#endif
+ out->put_string(">");
+}
+
+/*
+ * issue_color_begin - writes out an html color tag.
+ */
+
+void html_text::issue_color_begin (color *c)
+{
+ char buf[(INT_HEXDIGITS * 3) + 1];
+ unsigned int r, g, b;
+
+ out->put_string("<font color=\"#");
+ if (c->is_default())
+ sprintf(buf, "000000");
+ else {
+ c->get_rgb(&r, &g, &b);
+ // we have to scale 0..0xFFFF to 0..0xFF
+ sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
+ }
+ out->put_string(buf);
+ out->put_string("\">");
+}
+
+/*
+ * start_tag - starts a tag.
+ */
+
+void html_text::start_tag (tag_definition *t)
+{
+ switch (t->type) {
+
+ case I_TAG: issue_tag("<i", (char *)t->arg1); break;
+ case B_TAG: issue_tag("<b", (char *)t->arg1); break;
+ case P_TAG: if (t->indent != NULL) {
+ out->nl();
+#if defined(DEBUGGING)
+ out->simple_comment("INDENTATION");
+#endif
+ out->put_string("\n<p");
+ t->indent->begin(start_space);
+ issue_tag("", (char *)t->arg1);
+ } else {
+ out->nl();
+ issue_tag("\n<p", (char *)t->arg1, start_space);
+ }
+
+ out->enable_newlines(TRUE); break;
+ case SUB_TAG: issue_tag("<sub", (char *)t->arg1); break;
+ case SUP_TAG: issue_tag("<sup", (char *)t->arg1); break;
+ case TT_TAG: issue_tag("<tt", (char *)t->arg1); break;
+ case PRE_TAG: out->enable_newlines(TRUE);
+ out->nl(); out->put_string("<pre");
+ if (t->indent == NULL)
+ issue_tag("", (char *)t->arg1, start_space);
+ else {
+ t->indent->begin(start_space);
+ issue_tag("", (char *)t->arg1);
+ }
+ out->enable_newlines(FALSE); break;
+ case SMALL_TAG: if (! is_in_pre ())
+ issue_tag("<small", (char *)t->arg1);
+ break;
+ case BIG_TAG: if (! is_in_pre ())
+ issue_tag("<big", (char *)t->arg1);
+ break;
+ case BREAK_TAG: break;
+ case COLOR_TAG: if (! is_in_pre ())
+ issue_color_begin(&t->col);
+ break;
+
+ default:
+ error("unrecognised tag");
+ }
+}
+
+/*
+ * flush_text - flushes html tags which are outstanding on the html stack.
+ */
+
+void html_text::flush_text (void)
+{
+ int notext=TRUE;
+ tag_definition *p=stackptr;
+
+ while (stackptr != 0) {
+ notext = (notext && (! stackptr->text_emitted));
+ if (! notext) {
+ end_tag(stackptr);
+ }
+ p = stackptr;
+ stackptr = stackptr->next;
+ delete p;
+ }
+ lastptr = NULL;
+}
+
+/*
+ * is_present - returns TRUE if tag is already present on the stack.
+ */
+
+int html_text::is_present (HTML_TAG t)
+{
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (t == p->type)
+ return TRUE;
+ p = p->next;
+ }
+ return FALSE;
+}
+
+/*
+ * uses_indent - returns TRUE if the current paragraph is using a
+ * html table to effect an indent.
+ */
+
+int html_text::uses_indent (void)
+{
+ tag_definition *p = stackptr;
+
+ while (p != NULL) {
+ if (p->indent != NULL)
+ return TRUE;
+ p = p->next;
+ }
+ return FALSE;
+}
+
+extern void stop();
+
+/*
+ * do_push - places, tag_definition, p, onto the stack
+ */
+
+void html_text::do_push (tag_definition *p)
+{
+ HTML_TAG t = p->type;
+
+#if defined(DEBUGGING)
+ if (t == PRE_TAG)
+ stop();
+ debugStack = TRUE;
+ fprintf(stderr, "\nentering do_push (");
+ dump_stack_element(p);
+ fprintf(stderr, ")\n");
+ dump_stack();
+ fprintf(stderr, ")\n");
+ fflush(stderr);
+#endif
+
+ /*
+ * if t is a P_TAG or PRE_TAG make sure it goes on the end of the stack.
+ */
+
+ if (((t == P_TAG) || (t == PRE_TAG)) && (lastptr != NULL)) {
+ /*
+ * store, p, at the end
+ */
+ lastptr->next = p;
+ lastptr = p;
+ p->next = NULL;
+ } else {
+ p->next = stackptr;
+ if (stackptr == NULL)
+ lastptr = p;
+ stackptr = p;
+ }
+
+#if defined(DEBUGGING)
+ dump_stack();
+ fprintf(stderr, "exiting do_push\n");
+#endif
+}
+
+/*
+ * push_para - adds a new entry onto the html paragraph stack.
+ */
+
+void html_text::push_para (HTML_TAG t, void *arg, html_indent *in)
+{
+ tag_definition *p= new tag_definition;
+
+ p->type = t;
+ p->arg1 = arg;
+ p->text_emitted = FALSE;
+ p->indent = in;
+
+ if (t == PRE_TAG && is_present(PRE_TAG))
+ fatal("cannot have multiple PRE_TAGs");
+
+ do_push(p);
+}
+
+void html_text::push_para (HTML_TAG t)
+{
+ push_para(t, (void *)"", NULL);
+}
+
+void html_text::push_para (color *c)
+{
+ tag_definition *p = new tag_definition;
+
+ p->type = COLOR_TAG;
+ p->arg1 = NULL;
+ p->col = *c;
+ p->text_emitted = FALSE;
+ p->indent = NULL;
+
+ do_push(p);
+}
+
+/*
+ * do_italic - changes to italic
+ */
+
+void html_text::do_italic (void)
+{
+ if (! is_present(I_TAG))
+ push_para(I_TAG);
+}
+
+/*
+ * do_bold - changes to bold.
+ */
+
+void html_text::do_bold (void)
+{
+ if (! is_present(B_TAG))
+ push_para(B_TAG);
+}
+
+/*
+ * do_tt - changes to teletype.
+ */
+
+void html_text::do_tt (void)
+{
+ if ((! is_present(TT_TAG)) && (! is_present(PRE_TAG)))
+ push_para(TT_TAG);
+}
+
+/*
+ * do_pre - changes to preformated text.
+ */
+
+void html_text::do_pre (void)
+{
+ done_tt();
+ if (is_present(P_TAG)) {
+ html_indent *i = remove_indent(P_TAG);
+ int space = retrieve_para_space();
+ (void)done_para();
+ if (! is_present(PRE_TAG))
+ push_para(PRE_TAG, NULL, i);
+ start_space = space;
+ } else if (! is_present(PRE_TAG))
+ push_para(PRE_TAG, NULL, NULL);
+ dump_stack();
+}
+
+/*
+ * is_in_pre - returns TRUE if we are currently within a preformatted
+ * <pre> block.
+ */
+
+int html_text::is_in_pre (void)
+{
+ return is_present(PRE_TAG);
+}
+
+/*
+ * do_color - initiates a new color tag.
+ */
+
+void html_text::do_color (color *c)
+{
+ shutdown(COLOR_TAG); // shutdown a previous color tag, if present
+ push_para(c);
+}
+
+/*
+ * done_color - shutdown an outstanding color tag, if it exists.
+ */
+
+void html_text::done_color (void)
+{
+ shutdown(COLOR_TAG);
+}
+
+/*
+ * shutdown - shuts down an html tag.
+ */
+
+char *html_text::shutdown (HTML_TAG t)
+{
+ char *arg=NULL;
+
+ if (is_present(t)) {
+ tag_definition *p =stackptr;
+ tag_definition *temp =NULL;
+ int notext =TRUE;
+
+ dump_stack();
+ while ((stackptr != NULL) && (stackptr->type != t)) {
+ notext = (notext && (! stackptr->text_emitted));
+ if (! notext) {
+ end_tag(stackptr);
+ }
+
+ /*
+ * pop tag
+ */
+ p = stackptr;
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+
+ /*
+ * push tag onto temp stack
+ */
+ p->next = temp;
+ temp = p;
+ }
+
+ /*
+ * and examine stackptr
+ */
+ if ((stackptr != NULL) && (stackptr->type == t)) {
+ if (stackptr->text_emitted) {
+ end_tag(stackptr);
+ }
+ if (t == P_TAG) {
+ arg = (char *)stackptr->arg1;
+ }
+ p = stackptr;
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ if (p->indent != NULL)
+ delete p->indent;
+ delete p;
+ }
+
+ /*
+ * and restore unaffected tags
+ */
+ while (temp != NULL) {
+ if (temp->type == COLOR_TAG)
+ push_para(&temp->col);
+ else
+ push_para(temp->type, temp->arg1, temp->indent);
+ p = temp;
+ temp = temp->next;
+ delete p;
+ }
+ }
+ return arg;
+}
+
+/*
+ * done_bold - shuts downs a bold tag.
+ */
+
+void html_text::done_bold (void)
+{
+ shutdown(B_TAG);
+}
+
+/*
+ * done_italic - shuts downs an italic tag.
+ */
+
+void html_text::done_italic (void)
+{
+ shutdown(I_TAG);
+}
+
+/*
+ * done_sup - shuts downs a sup tag.
+ */
+
+void html_text::done_sup (void)
+{
+ shutdown(SUP_TAG);
+}
+
+/*
+ * done_sub - shuts downs a sub tag.
+ */
+
+void html_text::done_sub (void)
+{
+ shutdown(SUB_TAG);
+}
+
+/*
+ * done_tt - shuts downs a tt tag.
+ */
+
+void html_text::done_tt (void)
+{
+ shutdown(TT_TAG);
+}
+
+/*
+ * done_pre - shuts downs a pre tag.
+ */
+
+void html_text::done_pre (void)
+{
+ shutdown(PRE_TAG);
+}
+
+/*
+ * done_small - shuts downs a small tag.
+ */
+
+void html_text::done_small (void)
+{
+ shutdown(SMALL_TAG);
+}
+
+/*
+ * done_big - shuts downs a big tag.
+ */
+
+void html_text::done_big (void)
+{
+ shutdown(BIG_TAG);
+}
+
+/*
+ * check_emit_text - ensures that all previous tags have been emitted (in order)
+ * before the text is written.
+ */
+
+void html_text::check_emit_text (tag_definition *t)
+{
+ if ((t != NULL) && (! t->text_emitted)) {
+ check_emit_text(t->next);
+ t->text_emitted = TRUE;
+ start_tag(t);
+ }
+}
+
+/*
+ * do_emittext - tells the class that text was written during the current tag.
+ */
+
+void html_text::do_emittext (const char *s, int length)
+{
+ if ((! is_present(P_TAG)) && (! is_present(PRE_TAG)))
+ do_para("", FALSE);
+
+ if (is_present(BREAK_TAG)) {
+ int text = remove_break();
+ check_emit_text(stackptr);
+ if (text) {
+ if (is_present(PRE_TAG))
+ out->nl();
+ else if (dialect == xhtml)
+ out->put_string("<br/>").nl();
+ else
+ out->put_string("<br>").nl();
+ }
+ } else
+ check_emit_text(stackptr);
+
+ out->put_string(s, length);
+ space_emitted = FALSE;
+ blank_para = FALSE;
+}
+
+/*
+ * do_para - starts a new paragraph
+ */
+
+void html_text::do_para (const char *arg, html_indent *in, int space)
+{
+ if (! is_present(P_TAG)) {
+ if (is_present(PRE_TAG)) {
+ html_indent *i = remove_indent(PRE_TAG);
+ done_pre();
+ if ((arg == NULL || (strcmp(arg, "") == 0)) &&
+ (i == in || in == NULL))
+ in = i;
+ else
+ delete i;
+ }
+ remove_sub_sup();
+ push_para(P_TAG, (void *)arg, in);
+ start_space = space;
+ }
+}
+
+void html_text::do_para (const char *arg, int space)
+{
+ do_para(arg, NULL, space);
+}
+
+void html_text::do_para (simple_output *op, const char *arg1,
+ int indentation_value, int page_offset,
+ int line_length, int space)
+{
+ html_indent *ind;
+
+ if (indentation_value == 0)
+ ind = NULL;
+ else
+ ind = new html_indent(op, indentation_value, page_offset, line_length);
+ do_para(arg1, ind, space);
+}
+
+/*
+ * done_para - shuts down a paragraph tag.
+ */
+
+char *html_text::done_para (void)
+{
+ char *result;
+ space_emitted = TRUE;
+ result = shutdown(P_TAG);
+ start_space = FALSE;
+ return result;
+}
+
+/*
+ * remove_indent - returns the indent associated with, tag.
+ * The indent associated with tag is set to NULL.
+ */
+
+html_indent *html_text::remove_indent (HTML_TAG tag)
+{
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (tag == p->type) {
+ html_indent *i = p->indent;
+ p->indent = NULL;
+ return i;
+ }
+ p = p->next;
+ }
+ return NULL;
+}
+
+/*
+ * remove_para_space - removes the leading space to a paragraph
+ * (effectively this trims off a leading '.sp' tag).
+ */
+
+void html_text::remove_para_space (void)
+{
+ start_space = FALSE;
+}
+
+/*
+ * do_space - issues an end of paragraph
+ */
+
+void html_text::do_space (void)
+{
+ if (is_in_pre()) {
+ do_emittext("", 0);
+ out->force_nl();
+ space_emitted = TRUE;
+ } else {
+ html_indent *i = remove_indent(P_TAG);
+
+ do_para(done_para(), i, TRUE);
+ space_emitted = TRUE;
+ }
+}
+
+/*
+ * do_break - issue a break tag.
+ */
+
+void html_text::do_break (void)
+{
+ if (! is_present(PRE_TAG))
+ if (emitted_text())
+ if (! is_present(BREAK_TAG))
+ push_para(BREAK_TAG);
+
+ space_emitted = TRUE;
+}
+
+/*
+ * do_newline - issue a newline providing that we are inside a <pre> tag.
+ */
+
+void html_text::do_newline (void)
+{
+ if (is_present(PRE_TAG)) {
+ do_emittext("\n", 1);
+ space_emitted = TRUE;
+ }
+}
+
+/*
+ * emitted_text - returns FALSE if white space has just been written.
+ */
+
+int html_text::emitted_text (void)
+{
+ return !space_emitted;
+}
+
+/*
+ * ever_emitted_text - returns TRUE if we have ever emitted text in this
+ * paragraph.
+ */
+
+int html_text::ever_emitted_text (void)
+{
+ return !blank_para;
+}
+
+/*
+ * starts_with_space - returns TRUE if we started this paragraph with a .sp
+ */
+
+int html_text::starts_with_space (void)
+{
+ return start_space;
+}
+
+/*
+ * retrieve_para_space - returns TRUE, if the paragraph starts with
+ * a space and text has not yet been emitted.
+ * If TRUE is returned, then the, start_space,
+ * variable is set to FALSE.
+ */
+
+int html_text::retrieve_para_space (void)
+{
+ if (start_space && blank_para) {
+ start_space = FALSE;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/*
+ * emit_space - writes a space providing that text was written beforehand.
+ */
+
+void html_text::emit_space (void)
+{
+ if (is_present(PRE_TAG))
+ do_emittext(" ", 1);
+ else
+ out->space_or_newline();
+
+ space_emitted = TRUE;
+}
+
+/*
+ * remove_def - removes a definition, t, from the stack.
+ */
+
+void html_text::remove_def (tag_definition *t)
+{
+ tag_definition *p = stackptr;
+ tag_definition *l = 0;
+
+ while ((p != 0) && (p != t)) {
+ l = p;
+ p = p->next;
+ }
+ if ((p != 0) && (p == t)) {
+ if (p == stackptr) {
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ } else if (l == 0) {
+ error("stack list pointers are wrong");
+ } else {
+ l->next = p->next;
+ if (l->next == NULL)
+ lastptr = l;
+ }
+ delete p;
+ }
+}
+
+/*
+ * remove_tag - removes a tag from the stack.
+ */
+
+void html_text::remove_tag (HTML_TAG tag)
+{
+ tag_definition *p = stackptr;
+
+ while ((p != 0) && (p->type != tag)) {
+ p = p->next;
+ }
+ if ((p != 0) && (p->type == tag))
+ remove_def(p);
+}
+
+/*
+ * remove_sub_sup - removes a sub or sup tag, should either exist
+ * on the stack.
+ */
+
+void html_text::remove_sub_sup (void)
+{
+ if (is_present(SUB_TAG)) {
+ remove_tag(SUB_TAG);
+ }
+ if (is_present(SUP_TAG)) {
+ remove_tag(SUP_TAG);
+ }
+ if (is_present(PRE_TAG)) {
+ remove_tag(PRE_TAG);
+ }
+}
+
+/*
+ * remove_break - break tags are not balanced thus remove it once it has been emitted.
+ * It returns TRUE if text was emitted before the <br> was issued.
+ */
+
+int html_text::remove_break (void)
+{
+ tag_definition *p = stackptr;
+ tag_definition *l = 0;
+ tag_definition *q = 0;
+
+ while ((p != 0) && (p->type != BREAK_TAG)) {
+ l = p;
+ p = p->next;
+ }
+ if ((p != 0) && (p->type == BREAK_TAG)) {
+ if (p == stackptr) {
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ q = stackptr;
+ } else if (l == 0)
+ error("stack list pointers are wrong");
+ else {
+ l->next = p->next;
+ q = p->next;
+ if (l->next == NULL)
+ lastptr = l;
+ }
+ delete p;
+ }
+ /*
+ * now determine whether text was issued before <br>
+ */
+ while (q != 0) {
+ if (q->text_emitted)
+ return TRUE;
+ else
+ q = q->next;
+ }
+ return FALSE;
+}
+
+/*
+ * remove_para_align - removes a paragraph which has a text
+ * argument. If the paragraph has no text
+ * argument then it is left alone.
+ */
+
+void html_text::remove_para_align (void)
+{
+ if (is_present(P_TAG)) {
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (p->type == P_TAG && p->arg1 != NULL) {
+ html_indent *i = remove_indent(P_TAG);
+ int space = retrieve_para_space();
+ done_para();
+ do_para("", i, space);
+ return;
+ }
+ p = p->next;
+ }
+ }
+}
+
+/*
+ * get_alignment - returns the alignment for the paragraph.
+ * If no alignment was given then we return "".
+ */
+
+char *html_text::get_alignment (void)
+{
+ if (is_present(P_TAG)) {
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (p->type == P_TAG && p->arg1 != NULL)
+ return (char *)p->arg1;
+ p = p->next;
+ }
+ }
+ return (char *)"";
+}
+
+/*
+ * do_small - potentially inserts a <small> tag into the html stream.
+ * However we check for a <big> tag, if present then we terminate it.
+ * Otherwise a <small> tag is inserted.
+ */
+
+void html_text::do_small (void)
+{
+ if (is_present(BIG_TAG))
+ done_big();
+ else
+ push_para(SMALL_TAG);
+}
+
+/*
+ * do_big - is the mirror image of do_small.
+ */
+
+void html_text::do_big (void)
+{
+ if (is_present(SMALL_TAG))
+ done_small();
+ else
+ push_para(BIG_TAG);
+}
+
+/*
+ * do_sup - save a superscript tag on the stack of tags.
+ */
+
+void html_text::do_sup (void)
+{
+ push_para(SUP_TAG);
+}
+
+/*
+ * do_sub - save a subscript tag on the stack of tags.
+ */
+
+void html_text::do_sub (void)
+{
+ push_para(SUB_TAG);
+}
diff --git a/src/devices/grohtml/html-text.h b/src/devices/grohtml/html-text.h
new file mode 100644
index 0000000..ee58601
--- /dev/null
+++ b/src/devices/grohtml/html-text.h
@@ -0,0 +1,138 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.h
+ *
+ * html-text.h
+ *
+ * provides a state machine interface which generates html text.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "html.h"
+#include "html-table.h"
+
+#define STYLE_VERTICAL_SPACE "1em"
+
+/*
+ * supported html dialects.
+ */
+
+typedef enum {xhtml, html4} html_dialect;
+
+/*
+ * html tags
+ */
+
+typedef enum {I_TAG, B_TAG, P_TAG, SUB_TAG, SUP_TAG, TT_TAG,
+ PRE_TAG, SMALL_TAG, BIG_TAG, BREAK_TAG,
+ COLOR_TAG} HTML_TAG;
+
+typedef struct tag_definition {
+ HTML_TAG type;
+ void *arg1;
+ int text_emitted;
+ color col;
+ html_indent *indent;
+ tag_definition *next;
+} tag_definition ;
+
+/*
+ * the state of the current paragraph.
+ * It allows post-html.cpp to request font changes, paragraph start/end
+ * and emits balanced tags with a small amount of peephole optimization.
+ */
+
+class html_text {
+public:
+ html_text (simple_output *op, html_dialect d);
+ ~html_text (void);
+ void flush_text (void);
+ void do_emittext (const char *s, int length);
+ void do_italic (void);
+ void do_bold (void);
+ void do_roman (void);
+ void do_tt (void);
+ void do_pre (void);
+ void do_small (void);
+ void do_big (void);
+ void do_para (const char *arg, int space); // used for no indentation
+ void do_para (simple_output *op, const char *arg1,
+ int indentation, int pageoffset, int linelength,
+ int space);
+ void do_sup (void);
+ void do_sub (void);
+ void do_space (void);
+ void do_break (void);
+ void do_newline (void);
+ void do_table (const char *arg);
+ void done_bold (void);
+ void done_italic (void);
+ char *done_para (void);
+ void done_sup (void);
+ void done_sub (void);
+ void done_tt (void);
+ void done_pre (void);
+ void done_small (void);
+ void done_big (void);
+ void do_color (color *c);
+ void done_color (void);
+ int emitted_text (void);
+ int ever_emitted_text (void);
+ int starts_with_space (void);
+ int retrieve_para_space (void);
+ void emit_space (void);
+ int is_in_pre (void);
+ int uses_indent (void);
+ void remove_tag (HTML_TAG tag);
+ void remove_sub_sup (void);
+ void remove_para_align (void);
+ void remove_para_space (void);
+ char *get_alignment (void);
+
+private:
+ tag_definition *stackptr; /* the current paragraph state */
+ tag_definition *lastptr; /* the end of the stack */
+ simple_output *out;
+ html_dialect dialect; /* which dialect of html? */
+ int space_emitted; /* just emitted a space? */
+ int current_indentation; /* current .in value */
+ int pageoffset; /* .po value */
+ int linelength; /* current line length */
+ int blank_para; /* have we ever written text? */
+ int start_space; /* does para start with a .sp */
+ html_indent *indent; /* our indent class */
+
+ int is_present (HTML_TAG t);
+ void end_tag (tag_definition *t);
+ void start_tag (tag_definition *t);
+ void do_para (const char *arg, html_indent *in, int space);
+ void push_para (HTML_TAG t);
+ void push_para (HTML_TAG t, void *arg, html_indent *in);
+ void push_para (color *c);
+ void do_push (tag_definition *p);
+ char *shutdown (HTML_TAG t);
+ void check_emit_text (tag_definition *t);
+ int remove_break (void);
+ void issue_tag (const char *tagname, const char *arg, int space=2);
+ void issue_color_begin (color *c);
+ void remove_def (tag_definition *t);
+ html_indent *remove_indent (HTML_TAG tag);
+ void dump_stack_element (tag_definition *p);
+ void dump_stack (void);
+};
diff --git a/src/devices/grohtml/html.h b/src/devices/grohtml/html.h
new file mode 100644
index 0000000..4828646
--- /dev/null
+++ b/src/devices/grohtml/html.h
@@ -0,0 +1,97 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#if !defined(HTML_H)
+# define HTML_H
+
+const int INT_HEXDIGITS = 16; // enough for 64-bit ints
+
+/*
+ * class and structure needed to buffer words
+ */
+
+struct word {
+ char *s;
+ word *next;
+
+ word (const char *w, int n);
+ ~word ();
+};
+
+class word_list {
+public:
+ word_list ();
+ int flush (FILE *f);
+ void add_word (const char *s, int n);
+ int get_length (void);
+
+private:
+ int length;
+ word *head;
+ word *tail;
+};
+
+class simple_output {
+public:
+ simple_output(FILE *, int max_line_length);
+ simple_output &put_string(const char *, int);
+ simple_output &put_string(const char *s);
+ simple_output &put_string(const string &s);
+ simple_output &put_troffps_char (const char *s);
+ simple_output &put_translated_string(const char *s);
+ simple_output &put_number(int);
+ simple_output &put_float(double);
+ simple_output &put_symbol(const char *);
+ simple_output &put_literal_symbol(const char *);
+ simple_output &set_fixed_point(int);
+ simple_output &simple_comment(const char *);
+ simple_output &begin_comment(const char *);
+ simple_output &comment_arg(const char *);
+ simple_output &end_comment();
+ simple_output &set_file(FILE *);
+ simple_output &include_file(FILE *);
+ simple_output &copy_file(FILE *);
+ simple_output &end_line();
+ simple_output &put_raw_char(char);
+ simple_output &special(const char *);
+ simple_output &enable_newlines(int);
+ simple_output &check_newline(int n);
+ simple_output &nl(void);
+ simple_output &force_nl(void);
+ simple_output &space_or_newline (void);
+ simple_output &begin_tag (void);
+ FILE *get_file();
+private:
+ FILE *fp;
+ int max_line_length; // not including newline
+ int col;
+ int fixed_point;
+ int newlines; // can we issue newlines automatically?
+ word_list last_word;
+
+ void flush_last_word (void);
+ int check_space (const char *s, int n);
+};
+
+inline FILE *simple_output::get_file()
+{
+ return fp;
+}
+
+#endif
diff --git a/src/devices/grohtml/output.cpp b/src/devices/grohtml/output.cpp
new file mode 100644
index 0000000..0ffeb58
--- /dev/null
+++ b/src/devices/grohtml/output.cpp
@@ -0,0 +1,363 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote output.cpp
+ * but it owes a huge amount of ideas and raw code from
+ * James Clark (jjc@jclark.com) grops/ps.cpp.
+ *
+ * output.cpp
+ *
+ * provide the simple low level output routines needed by html.cpp
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#include <time.h>
+#include "html.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+
+#if defined(DEBUGGING)
+# define FPUTC(X,Y) do { fputc((X),(Y)); fputc((X), stderr); fflush(stderr); } while (0)
+# define FPUTS(X,Y) do { fputs((X),(Y)); fputs((X), stderr); fflush(stderr); } while (0)
+# define PUTC(X,Y) do { putc((X),(Y)); putc((X), stderr); fflush(stderr); } while (0)
+#else
+# define FPUTC(X,Y) do { fputc((X),(Y)); } while (0)
+# define FPUTS(X,Y) do { fputs((X),(Y)); } while (0)
+# define PUTC(X,Y) do { putc((X),(Y)); } while (0)
+#endif
+
+
+/*
+ * word - initialise a word and set next to NULL
+ */
+
+word::word (const char *w, int n)
+ : next(0)
+{
+ s = new char[n+1];
+ strncpy(s, w, n);
+ s[n] = (char)0;
+}
+
+/*
+ * destroy word and the string copy.
+ */
+
+word::~word ()
+{
+ delete[] s;
+}
+
+/*
+ * word_list - create an empty word list.
+ */
+
+word_list::word_list ()
+ : length(0), head(0), tail(0)
+{
+}
+
+/*
+ * flush - flush a word list to a FILE, f, and return the
+ * length of the buffered string.
+ */
+
+int word_list::flush (FILE *f)
+{
+ word *t;
+ int len=length;
+
+ while (head != 0) {
+ t = head;
+ head = head->next;
+ FPUTS(t->s, f);
+ delete t;
+ }
+ head = 0;
+ tail = 0;
+ length = 0;
+#if defined(DEBUGGING)
+ fflush(f); // just for testing
+#endif
+ return( len );
+}
+
+/*
+ * add_word - adds a word to the outstanding word list.
+ */
+
+void word_list::add_word (const char *s, int n)
+{
+ if (head == 0) {
+ head = new word(s, n);
+ tail = head;
+ } else {
+ tail->next = new word(s, n);
+ tail = tail->next;
+ }
+ length += n;
+}
+
+/*
+ * get_length - returns the number of characters buffered
+ */
+
+int word_list::get_length (void)
+{
+ return( length );
+}
+
+/*
+ * the classes and methods for simple_output manipulation
+ */
+
+simple_output::simple_output(FILE *f, int n)
+: fp(f), max_line_length(n), col(0), fixed_point(0), newlines(0)
+{
+}
+
+simple_output &simple_output::set_file(FILE *f)
+{
+ if (fp)
+ fflush(fp);
+ fp = f;
+ return *this;
+}
+
+simple_output &simple_output::copy_file(FILE *infp)
+{
+ int c;
+ while ((c = getc(infp)) != EOF)
+ PUTC(c, fp);
+ return *this;
+}
+
+simple_output &simple_output::end_line()
+{
+ flush_last_word();
+ if (col != 0) {
+ PUTC('\n', fp);
+ col = 0;
+ }
+ return *this;
+}
+
+simple_output &simple_output::special(const char *)
+{
+ return *this;
+}
+
+simple_output &simple_output::simple_comment(const char *s)
+{
+ flush_last_word();
+ if (col != 0)
+ PUTC('\n', fp);
+ FPUTS("<!-- ", fp);
+ FPUTS(s, fp);
+ FPUTS(" -->\n", fp);
+ col = 0;
+ return *this;
+}
+
+simple_output &simple_output::begin_comment(const char *s)
+{
+ flush_last_word();
+ if (col != 0)
+ PUTC('\n', fp);
+ col = 0;
+ put_string("<!--");
+ space_or_newline();
+ last_word.add_word(s, strlen(s));
+ return *this;
+}
+
+simple_output &simple_output::end_comment()
+{
+ flush_last_word();
+ space_or_newline();
+ put_string("-->").nl();
+ return *this;
+}
+
+/*
+ * check_newline - checks to see whether we are able to issue
+ * a newline and that one is needed.
+ */
+
+simple_output &simple_output::check_newline(int n)
+{
+ if ((col + n + last_word.get_length() + 1 > max_line_length) && (newlines)) {
+ FPUTC('\n', fp);
+ col = last_word.flush(fp);
+ }
+ return *this;
+}
+
+/*
+ * space_or_newline - will emit a newline or a space later on
+ * depending upon the current column.
+ */
+
+simple_output &simple_output::space_or_newline (void)
+{
+ if ((col + last_word.get_length() + 1 > max_line_length) && (newlines)) {
+ FPUTC('\n', fp);
+ if (last_word.get_length() > 0) {
+ col = last_word.flush(fp);
+ } else {
+ col = 0;
+ }
+ } else {
+ if (last_word.get_length() != 0) {
+ if (col > 0) {
+ FPUTC(' ', fp);
+ col++;
+ }
+ col += last_word.flush(fp);
+ }
+ }
+ return *this;
+}
+
+/*
+ * force_nl - forces a newline.
+ */
+
+simple_output &simple_output::force_nl (void)
+{
+ space_or_newline();
+ col += last_word.flush(fp);
+ FPUTC('\n', fp);
+ col = 0;
+ return *this ;
+}
+
+/*
+ * nl - writes a newline providing that we
+ * are not in the first column.
+ */
+
+simple_output &simple_output::nl (void)
+{
+ space_or_newline();
+ col += last_word.flush(fp);
+ FPUTC('\n', fp);
+ col = 0;
+ return *this ;
+}
+
+simple_output &simple_output::set_fixed_point(int n)
+{
+ assert(n >= 0 && n <= 10);
+ fixed_point = n;
+ return *this;
+}
+
+simple_output &simple_output::put_raw_char(char c)
+{
+ col += last_word.flush(fp);
+ PUTC(c, fp);
+ col++;
+ return *this;
+}
+
+simple_output &simple_output::put_string(const char *s, int n)
+{
+ last_word.add_word(s, n);
+ return *this;
+}
+
+simple_output &simple_output::put_string(const char *s)
+{
+ last_word.add_word(s, strlen(s));
+ return *this;
+}
+
+simple_output &simple_output::put_string(const string &s)
+{
+ last_word.add_word(s.contents(), s.length());
+ return *this;
+}
+
+simple_output &simple_output::put_number(int n)
+{
+ char buf[1 + INT_DIGITS + 1];
+ sprintf(buf, "%d", n);
+ put_string(buf);
+ return *this;
+}
+
+simple_output &simple_output::put_float(double d)
+{
+ char buf[128];
+
+ sprintf(buf, "%.4f", d);
+ put_string(buf);
+ return *this;
+}
+
+simple_output &simple_output::enable_newlines (int auto_newlines)
+{
+ check_newline(0);
+ newlines = auto_newlines;
+ check_newline(0);
+ return *this;
+}
+
+/*
+ * flush_last_word - flushes the last word and adjusts the
+ * col position. It will insert a newline
+ * before the last word if allowed and if
+ * necessary.
+ */
+
+void simple_output::flush_last_word (void)
+{
+ int len=last_word.get_length();
+
+ if (len > 0) {
+ if (newlines) {
+ if (col + len + 1 > max_line_length) {
+ FPUTS("\n", fp);
+ col = 0;
+ } else {
+ FPUTS(" ", fp);
+ col++;
+ }
+ len += last_word.flush(fp);
+ } else {
+ FPUTS(" ", fp);
+ col++;
+ col += last_word.flush(fp);
+ }
+ }
+}
diff --git a/src/devices/grohtml/post-html.cpp b/src/devices/grohtml/post-html.cpp
new file mode 100644
index 0000000..4e02b5c
--- /dev/null
+++ b/src/devices/grohtml/post-html.cpp
@@ -0,0 +1,5684 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote post-html.cpp
+ * but it owes a huge amount of ideas and raw code from
+ * James Clark (jjc@jclark.com) grops/ps.cpp.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "html.h"
+#include "html-text.h"
+#include "html-table.h"
+#include "curtime.h"
+
+#include <time.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+extern "C" const char *Version_string;
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+#define MAX_LINE_LENGTH 60 /* maximum characters we want in a line */
+#define SIZE_INCREMENT 2 /* font size increment <big> = +2 */
+#define CENTER_TOLERANCE 2 /* how many pixels off center do we allow */
+#define ANCHOR_TEMPLATE "heading" /* if simple anchor is set we use this */
+#define UNICODE_DESC_START 0x80 /* all character entities above this are */
+ /* either encoded by their glyph names or if */
+ /* there is no name then we use &#nnn; */
+typedef enum {CENTERED, LEFT, RIGHT, INLINE} TAG_ALIGNMENT;
+typedef enum {col_tag, tab_tag, tab0_tag, none} colType;
+
+#undef DEBUG_TABLES
+// #define DEBUG_TABLES
+
+/*
+ * prototypes
+ */
+
+const char *get_html_translation (font *f, const string &name);
+static const char *get_html_entity(unsigned int code);
+int char_translate_to_html (font *f, char *buf, int buflen, unsigned char ch, int b, int and_single);
+
+
+static int auto_links = TRUE; /* by default we enable automatic links at */
+ /* top of the document. */
+static int auto_rule = TRUE; /* by default we enable an automatic rule */
+ /* at the top and bottom of the document */
+static int simple_anchors = FALSE; /* default to anchors with heading text */
+static int manufacture_headings = FALSE; /* default is to use the Hn html headings, */
+ /* rather than manufacture our own. */
+static int do_write_creator_comment = TRUE; /* write Creator HTML comment */
+static int do_write_date_comment = TRUE; /* write CreationDate HTML comment */
+static color *default_background = 0; /* has user requested initial bg color? */
+static string job_name; /* if set then the output is split into */
+ /* multiple files with 'job_name'-%d.html */
+static int multiple_files = FALSE; /* must we the output be divided into */
+ /* multiple html files, one for each */
+ /* heading? */
+static int base_point_size = 0; /* which troff font size maps onto html */
+ /* size 3? */
+static int split_level = 2; /* what heading level to split at? */
+static string head_info; /* user supplied information to be placed */
+ /* into <head> </head> */
+static int valid_flag = FALSE; /* has user requested a valid flag at the */
+ /* end of each page? */
+static int groff_sig = FALSE; /* "This document was produced using" */
+html_dialect dialect = html4; /* which html dialect should grohtml output */
+
+
+/*
+ * start with a few favorites
+ */
+
+void stop () {}
+
+static int min (int a, int b)
+{
+ if (a < b)
+ return a;
+ else
+ return b;
+}
+
+static int max (int a, int b)
+{
+ if (a > b)
+ return a;
+ else
+ return b;
+}
+
+/*
+ * is_intersection - returns TRUE if range a1..a2 intersects with
+ * b1..b2
+ */
+
+static int is_intersection (int a1, int a2, int b1, int b2)
+{
+ // easier to prove NOT outside limits
+ return ! ((a1 > b2) || (a2 < b1));
+}
+
+/*
+ * is_digit - returns TRUE if character, ch, is a digit.
+ */
+
+static int is_digit (char ch)
+{
+ return (ch >= '0') && (ch <= '9');
+}
+
+/*
+ * the classes and methods for maintaining a list of files.
+ */
+
+struct file {
+ FILE *fp;
+ file *next;
+ int new_output_file;
+ int require_links;
+ string output_file_name;
+
+ file (FILE *f);
+};
+
+/*
+ * file - initialize all fields to null pointers
+ */
+
+file::file (FILE *f)
+ : fp(f), next(0), new_output_file(FALSE),
+ require_links(FALSE), output_file_name("")
+{
+}
+
+class files {
+public:
+ files ();
+ FILE *get_file (void);
+ void start_of_list (void);
+ void move_next (void);
+ void add_new_file (FILE *f);
+ void set_file_name (string name);
+ void set_links_required (void);
+ int are_links_required (void);
+ int is_new_output_file (void);
+ string file_name (void);
+ string next_file_name (void);
+private:
+ file *head;
+ file *tail;
+ file *ptr;
+};
+
+/*
+ * files - create an empty list of files.
+ */
+
+files::files ()
+ : head(0), tail(0), ptr(0)
+{
+}
+
+/*
+ * get_file - returns the FILE associated with ptr.
+ */
+
+FILE *files::get_file (void)
+{
+ if (ptr)
+ return ptr->fp;
+ else
+ return 0;
+}
+
+/*
+ * start_of_list - reset the ptr to the start of the list.
+ */
+
+void files::start_of_list (void)
+{
+ ptr = head;
+}
+
+/*
+ * move_next - moves the ptr to the next element on the list.
+ */
+
+void files::move_next (void)
+{
+ if (ptr != 0)
+ ptr = ptr->next;
+}
+
+/*
+ * add_new_file - adds a new file, f, to the list.
+ */
+
+void files::add_new_file (FILE *f)
+{
+ if (0 /* nullptr */ == head) {
+ head = new file(f);
+ tail = head;
+ } else {
+ tail->next = new file(f);
+ tail = tail->next;
+ }
+ ptr = tail;
+}
+
+/*
+ * set_file_name - sets the final file name to contain the html
+ * data to name.
+ */
+
+void files::set_file_name (string name)
+{
+ if (ptr != 0) {
+ ptr->output_file_name = name;
+ ptr->new_output_file = TRUE;
+ }
+}
+
+/*
+ * set_links_required - issue links when processing this component
+ * of the file.
+ */
+
+void files::set_links_required (void)
+{
+ if (ptr != 0)
+ ptr->require_links = TRUE;
+}
+
+/*
+ * are_links_required - returns TRUE if this section of the file
+ * requires that links should be issued.
+ */
+
+int files::are_links_required (void)
+{
+ if (ptr != 0)
+ return ptr->require_links;
+ return FALSE;
+}
+
+/*
+ * is_new_output_file - returns TRUE if this component of the file
+ * is the start of a new output file.
+ */
+
+int files::is_new_output_file (void)
+{
+ if (ptr != 0)
+ return ptr->new_output_file;
+ return FALSE;
+}
+
+/*
+ * file_name - returns the name of the file.
+ */
+
+string files::file_name (void)
+{
+ if (ptr != 0)
+ return ptr->output_file_name;
+ return string("");
+}
+
+/*
+ * next_file_name - returns the name of the next file.
+ */
+
+string files::next_file_name (void)
+{
+ if (ptr != 0 && ptr->next != 0)
+ return ptr->next->output_file_name;
+ return string("");
+}
+
+/*
+ * the class and methods for styles
+ */
+
+struct style {
+ font *f;
+ int point_size;
+ int font_no;
+ int height;
+ int slant;
+ color col;
+ style ();
+ style (font *, int, int, int, int, color);
+ int operator == (const style &) const;
+ int operator != (const style &) const;
+};
+
+style::style()
+ : f(0), point_size(-1)
+{
+}
+
+style::style(font *p, int sz, int h, int sl, int no, color c)
+ : f(p), point_size(sz), font_no(no), height(h), slant(sl), col(c)
+{
+}
+
+int style::operator==(const style &s) const
+{
+ return (f == s.f && point_size == s.point_size
+ && height == s.height && slant == s.slant && col == s.col);
+}
+
+int style::operator!=(const style &s) const
+{
+ return !(*this == s);
+}
+
+/*
+ * the class and methods for retaining ascii text
+ */
+
+struct char_block {
+ enum { SIZE = 256 };
+ char *buffer;
+ int used;
+ char_block *next;
+
+ char_block();
+ char_block(int length);
+ ~char_block();
+};
+
+char_block::char_block()
+: buffer(0), used(0), next(0)
+{
+}
+
+char_block::char_block(int length)
+: used(0), next(0)
+{
+ buffer = new char[max(length, char_block::SIZE)];
+ if (0 /* nullptr */ == buffer)
+ fatal("out of memory error");
+}
+
+char_block::~char_block()
+{
+ if (buffer != 0)
+ delete[] buffer;
+}
+
+class char_buffer {
+public:
+ char_buffer();
+ ~char_buffer();
+ char *add_string(const char *, unsigned int);
+ char *add_string(const string &);
+private:
+ char_block *head;
+ char_block *tail;
+};
+
+char_buffer::char_buffer()
+: head(0), tail(0)
+{
+}
+
+char_buffer::~char_buffer()
+{
+ while (head != 0) {
+ char_block *temp = head;
+ head = head->next;
+ delete temp;
+ }
+}
+
+char *char_buffer::add_string (const char *s, unsigned int length)
+{
+ int i = 0;
+ unsigned int old_used;
+
+ if (0 /* nullptr */ == s|| length == 0)
+ return 0;
+
+ if (0 /* nullptr */ == tail) {
+ tail = new char_block(length+1);
+ head = tail;
+ } else {
+ if (tail->used + length+1 > char_block::SIZE) {
+ tail->next = new char_block(length+1);
+ tail = tail->next;
+ }
+ }
+
+ old_used = tail->used;
+ do {
+ tail->buffer[tail->used] = s[i];
+ tail->used++;
+ i++;
+ length--;
+ } while (length>0);
+
+ // add terminating nul character
+
+ tail->buffer[tail->used] = '\0';
+ tail->used++;
+
+ // and return start of new string
+
+ return &tail->buffer[old_used];
+}
+
+char *char_buffer::add_string (const string &s)
+{
+ return add_string(s.contents(), s.length());
+}
+
+/*
+ * the classes and methods for maintaining glyph positions.
+ */
+
+class text_glob {
+public:
+ void text_glob_html (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_special (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_line (style *s,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int thickness);
+ void text_glob_auto_image(style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_tag (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+
+ text_glob (void);
+ ~text_glob (void);
+ int is_a_line (void);
+ int is_a_tag (void);
+ int is_eol (void);
+ int is_auto_img (void);
+ int is_br (void);
+ int is_in (void);
+ int is_po (void);
+ int is_ti (void);
+ int is_ll (void);
+ int is_ce (void);
+ int is_tl (void);
+ int is_eo_tl (void);
+ int is_eol_ce (void);
+ int is_col (void);
+ int is_tab (void);
+ int is_tab0 (void);
+ int is_ta (void);
+ int is_tab_ts (void);
+ int is_tab_te (void);
+ int is_nf (void);
+ int is_fi (void);
+ int is_eo_h (void);
+ int get_arg (void);
+ int get_tab_args (char *align);
+
+ void remember_table (html_table *t);
+ html_table *get_table (void);
+
+ style text_style;
+ const char *text_string;
+ unsigned int text_length;
+ int minv, minh, maxv, maxh;
+ int is_tag; // is this a .br, .sp, .tl etc
+ int is_img_auto; // image created by eqn delim
+ int is_special; // text has come via 'x X html:'
+ int is_line; // is the command a <line>?
+ int thickness; // the thickness of a line
+ html_table *tab; // table description
+
+private:
+ text_glob (style *s, const char *str, int length,
+ int min_vertical , int min_horizontal,
+ int max_vertical , int max_horizontal,
+ bool is_troff_command,
+ bool is_auto_image, bool is_special_command,
+ bool is_a_line , int thickness);
+};
+
+text_glob::text_glob (style *s, const char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ bool is_troff_command,
+ bool is_auto_image, bool is_special_command,
+ bool is_a_line_flag, int line_thickness)
+ : text_style(*s), text_string(str), text_length(length),
+ minv(min_vertical), minh(min_horizontal), maxv(max_vertical),
+ maxh(max_horizontal), is_tag(is_troff_command),
+ is_img_auto(is_auto_image), is_special(is_special_command),
+ is_line(is_a_line_flag), thickness(line_thickness), tab(0)
+{
+}
+
+text_glob::text_glob ()
+ : text_string(0), text_length(0), minv(-1), minh(-1), maxv(-1),
+ maxh(-1), is_tag(FALSE), is_special(FALSE), is_line(FALSE),
+ thickness(0), tab(0)
+{
+}
+
+text_glob::~text_glob ()
+{
+ if (tab != 0)
+ delete tab;
+}
+
+/*
+ * text_glob_html - used to place html text into the glob buffer.
+ */
+
+void text_glob::text_glob_html (style *s, char *str, int length,
+ int min_vertical , int min_horizontal,
+ int max_vertical , int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_html - used to place html specials into the glob buffer.
+ * This text is essentially html commands coming
+ * through from the macro sets, with special
+ * designated sequences of characters translated into
+ * html. See add_and_encode.
+ */
+
+void text_glob::text_glob_special (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, TRUE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_line - record horizontal draw line commands.
+ */
+
+void text_glob::text_glob_line (style *s,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int thickness_value)
+{
+ text_glob *g = new text_glob(s, "", 0,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, FALSE, TRUE,
+ thickness_value);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_auto_image - record the presence of a .auto-image tag
+ * command. Used to mark that an image has been
+ * created automatically by a preprocessor and
+ * (pre-grohtml/troff) combination. Under some
+ * circumstances images may not be created.
+ * (consider .EQ
+ * delim $$
+ * .EN
+ * .TS
+ * tab(!), center;
+ * l!l.
+ * $1 over x$!recripical of x
+ * .TE
+ * the first auto-image marker is created via
+ * .EQ/.EN pair and no image is created. The
+ * second auto-image marker occurs at $1 over
+ * x$ Currently this image will not be created
+ * as the whole of the table is created as an
+ * image. (Once html tables are handled by
+ * grohtml this will change. Shortly this will
+ * be the case).
+ */
+
+void text_glob::text_glob_auto_image(style *s, char *str, int length,
+ int min_vertical,
+ int min_horizontal,
+ int max_vertical,
+ int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ TRUE, TRUE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_tag - records a troff tag.
+ */
+
+void text_glob::text_glob_tag (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ TRUE, FALSE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * is_a_line - returns TRUE if glob should be converted into an <hr>
+ */
+
+int text_glob::is_a_line (void)
+{
+ return is_line;
+}
+
+/*
+ * is_a_tag - returns TRUE if glob contains a troff directive.
+ */
+
+int text_glob::is_a_tag (void)
+{
+ return is_tag;
+}
+
+/*
+ * is_eol - returns TRUE if glob contains the tag eol
+ */
+
+int text_glob::is_eol (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eol") == 0);
+}
+
+/*
+ * is_eol_ce - returns TRUE if glob contains the tag eol.ce
+ */
+
+int text_glob::is_eol_ce (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:eol.ce") == 0);
+}
+
+/*
+ * is_tl - returns TRUE if glob contains the tag .tl
+ */
+
+int text_glob::is_tl (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tl") == 0);
+}
+
+/*
+ * is_eo_tl - returns TRUE if glob contains the tag eo.tl
+ */
+
+int text_glob::is_eo_tl (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eo.tl") == 0);
+}
+
+/*
+ * is_nf - returns TRUE if glob contains the tag .fi 0
+ */
+
+int text_glob::is_nf (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.fi",
+ strlen("devtag:.fi")) == 0) &&
+ (get_arg() == 0);
+}
+
+/*
+ * is_fi - returns TRUE if glob contains the tag .fi 1
+ */
+
+int text_glob::is_fi (void)
+{
+ return (is_tag && (strncmp(text_string, "devtag:.fi",
+ strlen("devtag:.fi")) == 0) &&
+ (get_arg() == 1));
+}
+
+/*
+ * is_eo_h - returns TRUE if glob contains the tag .eo.h
+ */
+
+int text_glob::is_eo_h (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eo.h") == 0);
+}
+
+/*
+ * is_ce - returns TRUE if glob contains the tag .ce
+ */
+
+int text_glob::is_ce (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ce",
+ strlen("devtag:.ce")) == 0);
+}
+
+/*
+ * is_in - returns TRUE if glob contains the tag .in
+ */
+
+int text_glob::is_in (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.in ",
+ strlen("devtag:.in ")) == 0);
+}
+
+/*
+ * is_po - returns TRUE if glob contains the tag .po
+ */
+
+int text_glob::is_po (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.po ",
+ strlen("devtag:.po ")) == 0);
+}
+
+/*
+ * is_ti - returns TRUE if glob contains the tag .ti
+ */
+
+int text_glob::is_ti (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ti ",
+ strlen("devtag:.ti ")) == 0);
+}
+
+/*
+ * is_ll - returns TRUE if glob contains the tag .ll
+ */
+
+int text_glob::is_ll (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ll ",
+ strlen("devtag:.ll ")) == 0);
+}
+
+/*
+ * is_col - returns TRUE if glob contains the tag .col
+ */
+
+int text_glob::is_col (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.col",
+ strlen("devtag:.col")) == 0);
+}
+
+/*
+ * is_tab_ts - returns TRUE if glob contains the tag .tab_ts
+ */
+
+int text_glob::is_tab_ts (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tab-ts") == 0);
+}
+
+/*
+ * is_tab_te - returns TRUE if glob contains the tag .tab_te
+ */
+
+int text_glob::is_tab_te (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tab-te") == 0);
+}
+
+/*
+ * is_ta - returns TRUE if glob contains the tag .ta
+ */
+
+int text_glob::is_ta (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ta ",
+ strlen("devtag:.ta ")) == 0);
+}
+
+/*
+ * is_tab - returns TRUE if glob contains the tag tab
+ */
+
+int text_glob::is_tab (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:tab ",
+ strlen("devtag:tab ")) == 0);
+}
+
+/*
+ * is_tab0 - returns TRUE if glob contains the tag tab0
+ */
+
+int text_glob::is_tab0 (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:tab0",
+ strlen("devtag:tab0")) == 0);
+}
+
+/*
+ * is_auto_img - returns TRUE if the glob contains an automatically
+ * generated image.
+ */
+
+int text_glob::is_auto_img (void)
+{
+ return is_img_auto;
+}
+
+/*
+ * is_br - returns TRUE if the glob is a tag containing a .br
+ * or an implied .br. Note that we do not include .nf or .fi
+ * as grohtml will place a .br after these commands if they
+ * should break the line.
+ */
+
+int text_glob::is_br (void)
+{
+ return is_a_tag() && ((strcmp ("devtag:.br", text_string) == 0) ||
+ (strncmp("devtag:.sp", text_string,
+ strlen("devtag:.sp")) == 0));
+}
+
+int text_glob::get_arg (void)
+{
+ if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
+ const char *p = text_string;
+
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ if (*p == (char)0)
+ return -1;
+ return atoi(p);
+ }
+ return -1;
+}
+
+/*
+ * get_tab_args - returns the tab position and alignment of the tab tag
+ */
+
+int text_glob::get_tab_args (char *align)
+{
+ if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
+ const char *p = text_string;
+
+ // firstly the alignment C|R|L
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ *align = *p;
+ // now the int value
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ if (*p == (char)0)
+ return -1;
+ return atoi(p);
+ }
+ return -1;
+}
+
+/*
+ * remember_table - saves table, t, in the text_glob.
+ */
+
+void text_glob::remember_table (html_table *t)
+{
+ if (tab != 0)
+ delete tab;
+ tab = t;
+}
+
+/*
+ * get_table - returns the stored table description.
+ */
+
+html_table *text_glob::get_table (void)
+{
+ return tab;
+}
+
+/*
+ * the class and methods used to construct ordered double linked
+ * lists. In a previous implementation we used templates via
+ * #include "ordered-list.h", but this does assume that all C++
+ * compilers can handle this feature. Pragmatically it is safer to
+ * assume this is not the case.
+ */
+
+struct element_list {
+ element_list *right;
+ element_list *left;
+ text_glob *datum;
+ int lineno;
+ int minv, minh, maxv, maxh;
+
+ element_list (text_glob *d,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ element_list ();
+ ~element_list ();
+};
+
+element_list::element_list ()
+ : right(0), left(0), datum(0), lineno(0), minv(-1), minh(-1),
+ maxv(-1), maxh(-1)
+{
+}
+
+/*
+ * element_list - create a list element assigning the datum and region
+ * parameters.
+ */
+
+element_list::element_list (text_glob *in,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+ : right(0), left(0), datum(in), lineno(line_number),
+ minv(min_vertical), minh(min_horizontal),
+ maxv(max_vertical), maxh(max_horizontal)
+{
+}
+
+element_list::~element_list ()
+{
+ if (datum != 0)
+ delete datum;
+}
+
+class list {
+public:
+ list ();
+ ~list ();
+ int is_less (element_list *a, element_list *b);
+ void add (text_glob *in,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void sub_move_right (void);
+ void move_right (void);
+ void move_left (void);
+ int is_empty (void);
+ int is_equal_to_tail (void);
+ int is_equal_to_head (void);
+ void start_from_head (void);
+ void start_from_tail (void);
+ void insert (text_glob *in);
+ void move_to (text_glob *in);
+ text_glob *move_right_get_data (void);
+ text_glob *move_left_get_data (void);
+ text_glob *get_data (void);
+private:
+ element_list *head;
+ element_list *tail;
+ element_list *ptr;
+};
+
+/*
+ * list - construct an empty list.
+ */
+
+list::list ()
+ : head(0), tail(0), ptr(0)
+{
+}
+
+/*
+ * ~list - destroy a complete list.
+ */
+
+list::~list()
+{
+ element_list *temp=head;
+
+ do {
+ temp = head;
+ if (temp != 0) {
+ head = head->right;
+ delete temp;
+ }
+ } while ((head != 0) && (head != tail));
+}
+
+/*
+ * is_less - returns TRUE if a is left of b if on the same line or
+ * if a is higher up the page than b.
+ */
+
+int list::is_less (element_list *a, element_list *b)
+{
+ // was:
+ // if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) {
+ if (a->lineno < b->lineno) {
+ return TRUE;
+ } else if (a->lineno > b->lineno) {
+ return FALSE;
+ } else if (is_intersection(a->minv, a->maxv, b->minv, b->maxv)) {
+ return (a->minh < b->minh);
+ } else {
+ return (a->maxv < b->maxv);
+ }
+}
+
+/*
+ * add - adds a datum to the list in the order specified by the
+ * region position.
+ */
+
+void list::add (text_glob *in, int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ // create a new list element with datum and position fields
+ // initialized
+ element_list *t = new element_list(in, line_number,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ element_list *last;
+
+#if 0
+ fprintf(stderr, "[%s %d,%d,%d,%d] ",
+ in->text_string, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ fflush(stderr);
+#endif
+
+ if (0 /* nullptr */ == head) {
+ head = t;
+ tail = t;
+ ptr = t;
+ t->left = t;
+ t->right = t;
+ } else {
+ last = tail;
+
+ while ((last != head) && (is_less(t, last)))
+ last = last->left;
+
+ if (is_less(t, last)) {
+ t->right = last;
+ last->left->right = t;
+ t->left = last->left;
+ last->left = t;
+ // now check for a new head
+ if (last == head)
+ head = t;
+ } else {
+ // add t beyond last
+ t->right = last->right;
+ t->left = last;
+ last->right->left = t;
+ last->right = t;
+ // now check for a new tail
+ if (last == tail)
+ tail = t;
+ }
+ }
+}
+
+/*
+ * sub_move_right - removes the element which is currently pointed to
+ * by ptr from the list and moves ptr to the right.
+ */
+
+void list::sub_move_right (void)
+{
+ element_list *t=ptr->right;
+
+ if (head == tail) {
+ head = 0;
+ if (tail != 0)
+ delete tail;
+
+ tail = 0;
+ ptr = 0;
+ } else {
+ if (head == ptr)
+ head = head->right;
+ if (tail == ptr)
+ tail = tail->left;
+ ptr->left->right = ptr->right;
+ ptr->right->left = ptr->left;
+ ptr = t;
+ }
+}
+
+/*
+ * start_from_head - assigns ptr to the head.
+ */
+
+void list::start_from_head (void)
+{
+ ptr = head;
+}
+
+/*
+ * start_from_tail - assigns ptr to the tail.
+ */
+
+void list::start_from_tail (void)
+{
+ ptr = tail;
+}
+
+/*
+ * is_empty - returns TRUE if the list has no elements.
+ */
+
+int list::is_empty (void)
+{
+ return 0 /* nullptr */ == head;
+}
+
+/*
+ * is_equal_to_tail - returns TRUE if the ptr equals the tail.
+ */
+
+int list::is_equal_to_tail (void)
+{
+ return ptr == tail;
+}
+
+/*
+ * is_equal_to_head - returns TRUE if the ptr equals the head.
+ */
+
+int list::is_equal_to_head (void)
+{
+ return ptr == head;
+}
+
+/*
+ * move_left - moves the ptr left.
+ */
+
+void list::move_left (void)
+{
+ ptr = ptr->left;
+}
+
+/*
+ * move_right - moves the ptr right.
+ */
+
+void list::move_right (void)
+{
+ ptr = ptr->right;
+}
+
+/*
+ * get_datum - returns the datum referenced via ptr.
+ */
+
+text_glob* list::get_data (void)
+{
+ return ptr->datum;
+}
+
+/*
+ * move_right_get_data - returns the datum referenced via ptr and moves
+ * ptr right.
+ */
+
+text_glob* list::move_right_get_data (void)
+{
+ ptr = ptr->right;
+ if (ptr == head)
+ return 0;
+ else
+ return ptr->datum;
+}
+
+/*
+ * move_left_get_data - returns the datum referenced via ptr and moves
+ * ptr right.
+ */
+
+text_glob* list::move_left_get_data (void)
+{
+ ptr = ptr->left;
+ if (ptr == tail)
+ return 0;
+ else
+ return ptr->datum;
+}
+
+/*
+ * insert - inserts data after the current position.
+ */
+
+void list::insert (text_glob *in)
+{
+ if (is_empty())
+ fatal("list must not be empty if we are inserting data");
+ else {
+ if (0 /* nullptr */ == ptr)
+ ptr = head;
+
+ element_list *t = new element_list(in, ptr->lineno,
+ ptr->minv, ptr->minh,
+ ptr->maxv, ptr->maxh);
+ if (ptr == tail)
+ tail = t;
+ ptr->right->left = t;
+ t->right = ptr->right;
+ ptr->right = t;
+ t->left = ptr;
+ }
+}
+
+/*
+ * move_to - moves the current position to the point where data, in,
+ * exists. This is an expensive method and should be used
+ * sparingly.
+ */
+
+void list::move_to (text_glob *in)
+{
+ ptr = head;
+ while (ptr != tail && ptr->datum != in)
+ ptr = ptr->right;
+}
+
+/*
+ * page class and methods
+ */
+
+class page {
+public:
+ page (void);
+ void add (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void add_tag (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void add_and_encode (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int is_tag);
+ void add_line (style *s,
+ int line_number,
+ int x1, int y1, int x2, int y2,
+ int thickness);
+ void insert_tag (const string &str);
+ void dump_page (void); // debugging method
+
+ // and the data
+
+ list glyphs; // position of glyphs and specials on page
+ char_buffer buffer; // all characters for this page
+};
+
+page::page()
+{
+}
+
+/*
+ * insert_tag - inserts a tag after the current position.
+ */
+
+void page::insert_tag (const string &str)
+{
+ if (str.length() > 0) {
+ text_glob *g=new text_glob();
+ text_glob *f=glyphs.get_data();
+ g->text_glob_tag(&f->text_style, buffer.add_string(str),
+ str.length(), f->minv, f->minh, f->maxv, f->maxh);
+ glyphs.insert(g);
+ }
+}
+
+/*
+ * add - add html text to the list of glyphs.
+ */
+
+void page::add (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ if (str.length() > 0) {
+ text_glob *g=new text_glob();
+ g->text_glob_html(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ glyphs.add(g, line_number, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * add_tag - adds a troff tag, for example: .tl .sp .br
+ */
+
+void page::add_tag (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ if (str.length() > 0) {
+ text_glob *g;
+
+ if (strncmp((str+'\0').contents(), "devtag:.auto-image",
+ strlen("devtag:.auto-image")) == 0) {
+ g = new text_glob();
+ g->text_glob_auto_image(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ } else {
+ g = new text_glob();
+ g->text_glob_tag(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+ glyphs.add(g, line_number, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * add_line - adds the <line> primitive providing that y1==y2
+ */
+
+void page::add_line (style *s,
+ int line_number,
+ int x_1, int y_1, int x_2, int y_2,
+ int thickness)
+{
+ if (y_1 == y_2) {
+ text_glob *g = new text_glob();
+ g->text_glob_line(s,
+ min(y_1, y_2), min(x_1, x_2),
+ max(y_1, y_2), max(x_1, x_2),
+ thickness);
+ glyphs.add(g, line_number,
+ min(y_1, y_2), min(x_1, x_2),
+ max(y_1, y_2), max(x_1, x_2));
+ }
+}
+
+/*
+ * to_unicode - returns a unicode translation of int, ch.
+ */
+
+static char *to_unicode (unsigned int ch)
+{
+ static char buf[30];
+
+ sprintf(buf, "&#%u;", ch);
+ return buf;
+}
+
+/*
+ * add_and_encode - adds a special string to the page, it translates
+ * the string into html glyphs. The special string
+ * will have come from x X html: and can contain troff
+ * character encodings which appear as \[char]. A
+ * sequence of \\ represents \.
+ * So for example we can write:
+ * "cost = \[Po]3.00 file = \\foo\\bar"
+ * which is translated into:
+ * "cost = &pound;3.00 file = \foo\bar"
+ */
+
+void page::add_and_encode (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int is_tag)
+{
+ string html_string;
+ const char *html_glyph;
+ int i = 0;
+ const int len = str.length();
+
+ if (0 /* nullptr */ == s->f)
+ return;
+ while (i < len) {
+ if ((i + 1 < len) && (str.substring(i, 2) == string("\\["))) {
+ // start of escape
+ i += 2; // move over \[
+ int a = i;
+ while ((i < len) && (str[i] != ']'))
+ i++;
+ if (i > 0) {
+ string troff_charname = str.substring(a, i - a);
+ html_glyph = get_html_translation(s->f, troff_charname);
+ if (html_glyph)
+ html_string += html_glyph;
+ else {
+ glyph *g = name_to_glyph((troff_charname + '\0').contents());
+ if (s->f->contains(g))
+ html_string += s->f->get_code(g);
+ }
+ }
+ }
+ else
+ html_string += str[i];
+ i++;
+ }
+ if (html_string.length() > 0) {
+ text_glob *g=new text_glob();
+ if (is_tag)
+ g->text_glob_tag(s, buffer.add_string(html_string),
+ html_string.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ else
+ g->text_glob_special(s, buffer.add_string(html_string),
+ html_string.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ glyphs.add(g, line_number, min_vertical,
+ min_horizontal, max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * dump_page - dump the page contents for debugging purposes.
+ */
+
+void page::dump_page(void)
+{
+#if defined(DEBUG_TABLES)
+ text_glob *old_pos = glyphs.get_data();
+ text_glob *g;
+
+ printf("\n<!--\n");
+ printf("\n\ndebugging start\n");
+ glyphs.start_from_head();
+ do {
+ g = glyphs.get_data();
+ if (g->is_tab_ts()) {
+ printf("\n\n");
+ if (g->get_table() != 0)
+ g->get_table()->dump_table();
+ }
+ printf("%s ", g->text_string);
+ if (g->is_tab_te())
+ printf("\n\n");
+ glyphs.move_right();
+ } while (! glyphs.is_equal_to_head());
+ glyphs.move_to(old_pos);
+ printf("\ndebugging end\n\n");
+ printf("\n-->\n");
+ fflush(stdout);
+#endif
+}
+
+/*
+ * font classes and methods
+ */
+
+class html_font : public font {
+ html_font(const char *);
+public:
+ int encoding_index;
+ char *encoding;
+ char *reencoded_name;
+ ~html_font();
+ static html_font *load_html_font(const char *);
+};
+
+html_font *html_font::load_html_font(const char *s)
+{
+ html_font *f = new html_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+html_font::html_font(const char *nm)
+: font(nm)
+{
+}
+
+html_font::~html_font()
+{
+}
+
+/*
+ * a simple class to contain the header to this document
+ */
+
+class title_desc {
+public:
+ title_desc ();
+ ~title_desc ();
+
+ int has_been_written;
+ int has_been_found;
+ int with_h1;
+ string text;
+};
+
+
+title_desc::title_desc ()
+ : has_been_written(FALSE), has_been_found(FALSE), with_h1(FALSE)
+{
+}
+
+title_desc::~title_desc ()
+{
+}
+
+class header_desc {
+public:
+ header_desc ();
+ ~header_desc ();
+
+ int no_of_level_one_headings; // how many .SH or .NH 1 have we found?
+ int no_of_headings; // how many headings have we found?
+ char_buffer headings; // all the headings used in the document
+ list headers; // list of headers built from .NH and .SH
+ list header_filename; // in which file is this header?
+ int header_level; // current header level
+ int written_header; // have we written the header yet?
+ string header_buffer; // current header text
+
+ void write_headings (FILE *f, int force);
+};
+
+header_desc::header_desc ()
+ : no_of_level_one_headings(0), no_of_headings(0), header_level(2),
+ written_header(0)
+{
+}
+
+header_desc::~header_desc ()
+{
+}
+
+/*
+ * write_headings - emits a list of links for the headings in this
+ * document
+ */
+
+void header_desc::write_headings (FILE *f, int force)
+{
+ text_glob *g;
+
+ if (auto_links || force) {
+ if (! headers.is_empty()) {
+ int h=1;
+
+ headers.start_from_head();
+ header_filename.start_from_head();
+ if (dialect == xhtml)
+ fputs("<p>", f);
+ do {
+ g = headers.get_data();
+ fputs("<a href=\"", f);
+ if (multiple_files && (! header_filename.is_empty())) {
+ text_glob *fn = header_filename.get_data();
+ fputs(fn->text_string, f);
+ }
+ fputs("#", f);
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(h);
+ buffer += '\0';
+ fprintf(f, "%s", buffer.contents());
+ } else
+ fputs(g->text_string, f);
+ h++;
+ fputs("\">", f);
+ fputs(g->text_string, f);
+ fputs("</a>", f);
+ if (dialect == xhtml)
+ fputs("<br/>\n", f);
+ else
+ fputs("<br>\n", f);
+ headers.move_right();
+ if (multiple_files && (! header_filename.is_empty()))
+ header_filename.move_right();
+ } while (! headers.is_equal_to_head());
+ fputs("\n", f);
+ if (dialect == xhtml)
+ fputs("</p>\n", f);
+ }
+ }
+}
+
+struct assert_pos {
+ assert_pos *next;
+ const char *val;
+ const char *id;
+};
+
+class assert_state {
+public:
+ assert_state ();
+ ~assert_state ();
+
+ void addx (const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void addy (const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void build(const char *c, const char *v,
+ const char *f, const char *l);
+ void check_br (int br);
+ void check_ce (int ce);
+ void check_fi (int fi);
+ void check_sp (int sp);
+ void reset (void);
+
+private:
+ int check_br_flag;
+ int check_ce_flag;
+ int check_fi_flag;
+ int check_sp_flag;
+ const char *val_br;
+ const char *val_ce;
+ const char *val_fi;
+ const char *val_sp;
+ const char *file_br;
+ const char *file_ce;
+ const char *file_fi;
+ const char *file_sp;
+ const char *line_br;
+ const char *line_ce;
+ const char *line_fi;
+ const char *line_sp;
+
+ assert_pos *xhead;
+ assert_pos *yhead;
+
+ void add (assert_pos **h,
+ const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void compare(assert_pos *t,
+ const char *v, const char *f, const char *l);
+ void close (const char *c);
+ void set (const char *c, const char *v,
+ const char *f, const char *l);
+ void check_value (const char *s, int v, const char *name,
+ const char *f, const char *l, int *flag);
+ int check_value_error (int c, int v, const char *s,
+ const char *name,
+ const char *f, const char *l, int flag);
+};
+
+assert_state::assert_state ()
+{
+ reset();
+ val_br = 0;
+ val_ce = 0;
+ val_fi = 0;
+ val_sp = 0;
+ file_br = 0;
+ file_ce = 0;
+ file_fi = 0;
+ file_sp = 0;
+ line_br = 0;
+ line_ce = 0;
+ line_fi = 0;
+ line_sp = 0;
+ xhead = 0;
+ yhead = 0;
+}
+
+assert_state::~assert_state ()
+{
+ assert_pos *t;
+
+ while (xhead != 0) {
+ t = xhead;
+ xhead = xhead->next;
+ delete[] (char *)t->val;
+ delete[] (char *)t->id;
+ delete t;
+ }
+ while (yhead != 0) {
+ t = yhead;
+ yhead = yhead->next;
+ delete[] (char *)t->val;
+ delete[] (char *)t->id;
+ delete t;
+ }
+}
+
+void assert_state::reset (void)
+{
+ check_br_flag = 0;
+ check_ce_flag = 0;
+ check_fi_flag = 0;
+ check_sp_flag = 0;
+}
+
+void assert_state::add (assert_pos **h,
+ const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ assert_pos *t = *h;
+
+ while (t != 0) {
+ if (strcmp(t->id, i) == 0)
+ break;
+ t = t->next;
+ }
+ if (t != 0 && v != 0 && (v[0] != '='))
+ compare(t, v, f, l);
+ else {
+ if (0 /* nullptr */ == t) {
+ t = new assert_pos;
+ t->next = *h;
+ (*h) = t;
+ }
+ if (v == 0 || v[0] != '=') {
+ if (0 /* nullptr */ == f)
+ f = strsave("stdin");
+ if (0 /* nullptr */ == l)
+ l = strsave("<none>");
+ if (0 /* nullptr */ == v)
+ v = "no value at all";
+ fprintf(stderr, "%s:%s:%s: error in assertion format of id=%s;"
+ " expected value prefixed with an '=', got %s\n",
+ program_name, f, l, i, v);
+ }
+ t->id = i;
+ t->val = v;
+ delete[] (char *)c;
+ delete[] (char *)f;
+ delete[] (char *)l;
+ }
+}
+
+void assert_state::addx (const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ add(&xhead, c, i, v, f, l);
+}
+
+void assert_state::addy (const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ add(&yhead, c, i, v, f, l);
+}
+
+void assert_state::compare(assert_pos *t,
+ const char *v, const char *f, const char *l)
+{
+ const char *s=t->val;
+
+ while ((*v) == '=')
+ v++;
+ while ((*s) == '=')
+ s++;
+
+ if (strcmp(v, s) != 0) {
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ fprintf(stderr, "%s:%s: grohtml assertion failed at id%s: "
+ "expected %s, got %s\n", f, l, t->id, s, v);
+ }
+}
+
+void assert_state::close (const char *c)
+{
+ if (strcmp(c, "sp") == 0)
+ check_sp_flag = 0;
+ else if (strcmp(c, "br") == 0)
+ check_br_flag = 0;
+ else if (strcmp(c, "fi") == 0)
+ check_fi_flag = 0;
+ else if (strcmp(c, "nf") == 0)
+ check_fi_flag = 0;
+ else if (strcmp(c, "ce") == 0)
+ check_ce_flag = 0;
+ else
+ fprintf(stderr, "internal error: unrecognised tag in grohtml "
+ "(%s)\n", c);
+}
+
+const char *replace_negate_str (const char *before, char *after)
+{
+ if (before != 0)
+ delete[] (char *)before;
+
+ if (strlen(after) > 0) {
+ int d = atoi(after);
+
+ if (d < 0 || d > 1) {
+ fprintf(stderr, "expected nf/fi value of 0 or 1, got %d\n", d);
+ d = 0;
+ }
+ if (d == 0)
+ after[0] = '1';
+ else
+ after[0] = '0';
+ after[1] = (char)0;
+ }
+ return after;
+}
+
+const char *replace_str (const char *before, const char *after)
+{
+ if (before != 0)
+ delete[] (char *)before;
+ return after;
+}
+
+void assert_state::set (const char *c, const char *v,
+ const char *f, const char *l)
+{
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+
+ // fprintf(stderr, "%s:%s:setting %s to %s\n", f, l, c, v);
+ if (strcmp(c, "sp") == 0) {
+ check_sp_flag = 1;
+ val_sp = replace_str(val_sp, strsave(v));
+ file_sp = replace_str(file_sp, strsave(f));
+ line_sp = replace_str(line_sp, strsave(l));
+ } else if (strcmp(c, "br") == 0) {
+ check_br_flag = 1;
+ val_br = replace_str(val_br, strsave(v));
+ file_br = replace_str(file_br, strsave(f));
+ line_br = replace_str(line_br, strsave(l));
+ } else if (strcmp(c, "fi") == 0) {
+ check_fi_flag = 1;
+ val_fi = replace_str(val_fi, strsave(v));
+ file_fi = replace_str(file_fi, strsave(f));
+ line_fi = replace_str(line_fi, strsave(l));
+ } else if (strcmp(c, "nf") == 0) {
+ check_fi_flag = 1;
+ val_fi = replace_negate_str(val_fi, strsave(v));
+ file_fi = replace_str(file_fi, strsave(f));
+ line_fi = replace_str(line_fi, strsave(l));
+ } else if (strcmp(c, "ce") == 0) {
+ check_ce_flag = 1;
+ val_ce = replace_str(val_ce, strsave(v));
+ file_ce = replace_str(file_ce, strsave(f));
+ line_ce = replace_str(line_ce, strsave(l));
+ }
+}
+
+/*
+ * build - builds the troff state assertion.
+ * see tmac/www.tmac for cmd examples.
+ */
+
+void assert_state::build (const char *c, const char *v,
+ const char *f, const char *l)
+{
+ if (c[0] == '{')
+ set(&c[1], v, f, l);
+ if (c[0] == '}')
+ close(&c[1]);
+}
+
+int assert_state::check_value_error (int c, int v, const char *s,
+ const char *name, const char *f,
+ const char *l, int flag)
+{
+ if (! c) {
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ fprintf(stderr, "%s:%s:grohtml (troff state) assertion failed; "
+ "expected %s to be %s, got %d\n", f, l, name, s, v);
+ return 0;
+ }
+ return flag;
+}
+
+void assert_state::check_value (const char *s, int v, const char *name,
+ const char *f, const char *l, int *flag)
+{
+ if (strncmp(s, "<=", 2) == 0)
+ *flag = check_value_error(v <= atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, ">=", 2) == 0)
+ *flag = check_value_error(v >= atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "==", 2) == 0)
+ *flag = check_value_error(v == atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "!=", 2) == 0)
+ *flag = check_value_error(v != atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "<", 1) == 0)
+ *flag = check_value_error(v < atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, ">", 1) == 0)
+ *flag = check_value_error(v > atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "=", 1) == 0)
+ *flag = check_value_error(v == atoi(&s[1]), v, s, name, f, l, *flag);
+ else
+ *flag = check_value_error(v == atoi(s), v, s, name, f, l, *flag);
+}
+
+void assert_state::check_sp (int sp)
+{
+ if (check_sp_flag)
+ check_value(val_sp, sp, "sp", file_sp, line_sp, &check_sp_flag);
+}
+
+void assert_state::check_fi (int fi)
+{
+ if (check_fi_flag)
+ check_value(val_fi, fi, "fi", file_fi, line_fi, &check_fi_flag);
+}
+
+void assert_state::check_br (int br)
+{
+ if (check_br_flag)
+ check_value(val_br, br, "br", file_br, line_br, &check_br_flag);
+}
+
+void assert_state::check_ce (int ce)
+{
+ if (check_ce_flag)
+ check_value(val_ce, ce, "ce", file_ce, line_ce, &check_ce_flag);
+}
+
+class html_printer : public printer {
+ files file_list;
+ simple_output html;
+ int res;
+ glyph *space_glyph;
+ int space_width;
+ int no_of_printed_pages;
+ int paper_length;
+ string sbuf;
+ int sbuf_start_hpos;
+ int sbuf_vpos;
+ int sbuf_end_hpos;
+ int sbuf_prev_hpos;
+ int sbuf_kern;
+ style sbuf_style;
+ int last_sbuf_length;
+ int overstrike_detected;
+ style output_style;
+ int output_hpos;
+ int output_vpos;
+ int output_vpos_max;
+ int output_draw_point_size;
+ int line_thickness;
+ int output_line_thickness;
+ unsigned char output_space_code;
+ char *inside_font_style;
+ int page_number;
+ title_desc title;
+ header_desc header;
+ int header_indent;
+ int suppress_sub_sup;
+ int cutoff_heading;
+ page *page_contents;
+ html_text *current_paragraph;
+ html_indent *indent;
+ html_table *table;
+ int end_center;
+ int end_tempindent;
+ TAG_ALIGNMENT next_tag;
+ int fill_on;
+ int max_linelength;
+ int linelength;
+ int pageoffset;
+ int troff_indent;
+ int device_indent;
+ int temp_indent;
+ int pointsize;
+ int vertical_spacing;
+ int line_number;
+ color *background;
+ int seen_indent;
+ int next_indent;
+ int seen_pageoffset;
+ int next_pageoffset;
+ int seen_linelength;
+ int next_linelength;
+ int seen_center;
+ int next_center;
+ int seen_space;
+ int seen_break;
+ int current_column;
+ int row_space;
+ assert_state as;
+
+ void flush_sbuf ();
+ void set_style (const style &);
+ void set_space_code (unsigned char c);
+ void do_exec (char *, const environment *);
+ void do_import (char *, const environment *);
+ void do_def (char *, const environment *);
+ void do_mdef (char *, const environment *);
+ void do_file (char *, const environment *);
+ void set_line_thickness (const environment *);
+ void terminate_current_font (void);
+ void flush_font (void);
+ void add_to_sbuf (glyph *g, const string &s);
+ void write_title (int in_head);
+ int sbuf_continuation (glyph *g, const char *name,
+ const environment *env, int w);
+ void flush_page (void);
+ void troff_tag (text_glob *g);
+ void flush_globs (void);
+ void emit_line (text_glob *g);
+ void emit_raw (text_glob *g);
+ void emit_html (text_glob *g);
+ void determine_space (text_glob *g);
+ void start_font (const char *name);
+ void end_font (const char *name);
+ int is_font_courier (font *f);
+ int is_line_start (int nf);
+ int is_courier_until_eol (void);
+ void start_size (int from, int to);
+ void do_font (text_glob *g);
+ void do_center (char *arg);
+ void do_check_center (void);
+ void do_break (void);
+ void do_space (char *arg);
+ void do_eol (void);
+ void do_eol_ce (void);
+ void do_title (void);
+ void do_fill (char *arg);
+ void do_heading (char *arg);
+ void write_header (void);
+ void determine_header_level (int level);
+ void do_linelength (char *arg);
+ void do_pageoffset (char *arg);
+ void do_indentation (char *arg);
+ void do_tempindent (char *arg);
+ void do_indentedparagraph (void);
+ void do_verticalspacing (char *arg);
+ void do_pointsize (char *arg);
+ void do_centered_image (void);
+ void do_left_image (void);
+ void do_right_image (void);
+ void do_auto_image (text_glob *g,
+ const char *filename);
+ void do_links (void);
+ void do_flush (void);
+ void do_job_name (char *name);
+ void do_head (char *name);
+ void insert_split_file (void);
+ int is_in_middle (int left, int right);
+ void do_sup_or_sub (text_glob *g);
+ int start_subscript (text_glob *g);
+ int end_subscript (text_glob *g);
+ int start_superscript (text_glob *g);
+ int end_superscript (text_glob *g);
+ void outstanding_eol (int n);
+ int is_bold (font *f);
+ font *make_bold (font *f);
+ int overstrike (glyph *g, const char *name,
+ const environment *env, int w);
+ void do_body (void);
+ int next_horiz_pos (text_glob *g, int nf);
+ void lookahead_for_tables (void);
+ void insert_tab_te (void);
+ text_glob *insert_tab_ts (text_glob *where);
+ void insert_tab0_foreach_tab (void);
+ void insert_tab_0 (text_glob *where);
+ void do_indent (int in, int pageoff,
+ int linelen);
+ void shutdown_table (void);
+ void do_tab_ts (text_glob *g);
+ void do_tab_te (void);
+ void do_col (char *s);
+ void do_tab (char *s);
+ void do_tab0 (void);
+ int calc_nf (text_glob *g, int nf);
+ void calc_po_in (text_glob *g, int nf);
+ void remove_tabs (void);
+ void remove_courier_tabs (void);
+ void update_min_max (colType type_of_col,
+ int *minimum, int *maximum,
+ text_glob *g);
+ void add_table_end (const char *);
+ void do_file_components (void);
+ void write_navigation (const string &top,
+ const string &prev,
+ const string &next,
+ const string &current);
+ void emit_link (const string &to,
+ const char *name);
+ int get_troff_indent (void);
+ void restore_troff_indent (void);
+ void handle_assertion (int minv, int minh,
+ int maxv, int maxh,
+ const char *s);
+ void handle_state_assertion (text_glob *g);
+ void do_end_para (text_glob *g);
+ int round_width (int x);
+ void handle_tag_within_title (text_glob *g);
+ void writeHeadMetaStyle (void);
+ void handle_valid_flag (int needs_para);
+ void do_math (text_glob *g);
+ void write_html_anchor (text_glob *h);
+ void write_xhtml_anchor (text_glob *h);
+ // ADD HERE
+
+public:
+ html_printer ();
+ ~html_printer ();
+ void set_char (glyph *g, font *f, const environment *env,
+ int w, const char *name);
+ void set_numbered_char(int num, const environment *env, int *widthp);
+ glyph *set_char_and_width(const char *nm, const environment *env,
+ int *widthp, font **f);
+ void draw (int code, int *p, int np,
+ const environment *env);
+ void begin_page (int);
+ void end_page (int);
+ void special (char *arg, const environment *env, char type);
+ void devtag (char *arg, const environment *env, char type);
+ font *make_font (const char *);
+ void end_of_line ();
+};
+
+printer *make_printer()
+{
+ return new html_printer;
+}
+
+static void usage(FILE *stream);
+
+void html_printer::set_style(const style &sty)
+{
+ const char *fontname = sty.f->get_name();
+ if (0 /* nullptr */ == fontname)
+ fatal("no internalname specified for font");
+
+#if 0
+ change_font(fontname, (font::res / (72 * font::sizescale))
+ * sty.point_size);
+#endif
+}
+
+/*
+ * is_bold - returns TRUE if font, f, is bold.
+ */
+
+int html_printer::is_bold (font *f)
+{
+ const char *fontname = f->get_name();
+ return (strcmp(fontname, "B") == 0) || (strcmp(fontname, "BI") == 0);
+}
+
+/*
+ * make_bold - if a bold style for f exists, return it.
+ */
+
+font *html_printer::make_bold (font *f)
+{
+ const char *fontname = f->get_name();
+
+ if (strcmp(fontname, "B") == 0)
+ return f;
+ if (strcmp(fontname, "I") == 0)
+ return font::load_font("BI");
+ if (strcmp(fontname, "BI") == 0)
+ return f;
+ return 0;
+}
+
+void html_printer::end_of_line()
+{
+ flush_sbuf();
+ line_number++;
+}
+
+/*
+ * emit_line - writes out a horizontal rule.
+ */
+
+void html_printer::emit_line (text_glob *)
+{
+ // --fixme-- needs to know the length in percentage
+ if (dialect == xhtml)
+ html.put_string("<hr/>");
+ else
+ html.put_string("<hr>");
+}
+
+/*
+ * restore_troff_indent - is called when we have temporarily shutdown
+ * indentation (typically done when we have
+ * centered an image).
+ */
+
+void html_printer::restore_troff_indent (void)
+{
+ troff_indent = next_indent;
+ if (troff_indent > 0) {
+ /*
+ * force device indentation
+ */
+ device_indent = 0;
+ do_indent(get_troff_indent(), pageoffset, linelength);
+ }
+}
+
+/*
+ * emit_raw - writes the raw html information directly to the device.
+ */
+
+void html_printer::emit_raw (text_glob *g)
+{
+ do_font(g);
+ if (next_tag == INLINE) {
+ determine_space(g);
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ } else {
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+
+ current_paragraph->done_para();
+ shutdown_table();
+ switch (next_tag) {
+
+ case CENTERED:
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ break;
+ case LEFT:
+ if (dialect == html4)
+ current_paragraph->do_para(&html, "align=\"left\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ else
+ current_paragraph->do_para(&html, "class=\"left\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ break;
+ case RIGHT:
+ if (dialect == html4)
+ current_paragraph->do_para(&html, "align=\"right\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ else
+ current_paragraph->do_para(&html, "class=\"right\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ break;
+ default:
+ fatal("unknown enumeration");
+ }
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ current_paragraph->done_para();
+ next_tag = INLINE;
+ suppress_sub_sup = TRUE;
+ seen_space = FALSE;
+ restore_troff_indent();
+ }
+}
+
+/*
+ * handle_tag_within_title - handle a limited number of tags within
+ * the context of a table. Those tags which
+ * set values rather than generate spaces
+ * and paragraphs.
+ */
+
+void html_printer::handle_tag_within_title (text_glob *g)
+{
+ if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll()
+ || g->is_fi() || g->is_nf())
+ troff_tag(g);
+}
+
+/*
+ * do_center - handle the .ce commands from troff.
+ */
+
+void html_printer::do_center (char *arg)
+{
+ next_center = atoi(arg);
+ seen_center = TRUE;
+}
+
+/*
+ * do_centered_image - set a flag such that the next devtag is
+ * placed inside a centered paragraph.
+ */
+
+void html_printer::do_centered_image (void)
+{
+ next_tag = CENTERED;
+}
+
+/*
+ * do_right_image - set a flag such that the next devtag is
+ * placed inside a right aligned paragraph.
+ */
+
+void html_printer::do_right_image (void)
+{
+ next_tag = RIGHT;
+}
+
+/*
+ * do_left_image - set a flag such that the next devtag is
+ * placed inside a left aligned paragraph.
+ */
+
+void html_printer::do_left_image (void)
+{
+ next_tag = LEFT;
+}
+
+/*
+ * exists - returns TRUE if filename exists.
+ */
+
+static int exists (const char *filename)
+{
+ FILE *fp = fopen(filename, "r");
+
+ if (fp == 0) {
+ return FALSE;
+ } else {
+ fclose(fp);
+ return TRUE;
+ }
+}
+
+/*
+ * generate_img_src - returns a html image tag for the filename
+ * providing that the image exists.
+ */
+
+static string &generate_img_src (const char *filename)
+{
+ string *s = new string("");
+
+ while (filename && (filename[0] == ' ')) {
+ filename++;
+ }
+ if (exists(filename)) {
+ *s += string("<img src=\"") + filename + "\" "
+ + "alt=\"Image " + filename + "\">";
+ if (dialect == xhtml)
+ *s += "</img>";
+ }
+ return *s;
+}
+
+/*
+ * do_auto_image - tests whether the image, indicated by filename,
+ * is present, if so then it emits an html image tag.
+ * An image tag may be passed through from pic, eqn
+ * but the corresponding image might not be created.
+ * Consider .EQ delim $$ .EN or an empty .PS .PE.
+ */
+
+void html_printer::do_auto_image (text_glob *g, const char *filename)
+{
+ string buffer = generate_img_src(filename);
+
+ if (! buffer.empty()) {
+ /*
+ * utilize emit_raw by creating a new text_glob.
+ */
+ text_glob h = *g;
+
+ h.text_string = buffer.contents();
+ h.text_length = buffer.length();
+ emit_raw(&h);
+ } else
+ next_tag = INLINE;
+}
+
+/*
+ * outstanding_eol - call do_eol, n, times.
+ */
+
+void html_printer::outstanding_eol (int n)
+{
+ while (n > 0) {
+ do_eol();
+ n--;
+ }
+}
+
+/*
+ * do_title - handle the .tl commands from troff.
+ */
+
+void html_printer::do_title (void)
+{
+ text_glob *t;
+ int removed_from_head;
+
+ if (page_number == 1) {
+ int found_title_start = FALSE;
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ do {
+ t = page_contents->glyphs.get_data();
+ removed_from_head = FALSE;
+ if (t->is_auto_img()) {
+ string img = generate_img_src((char *)(t->text_string + 20));
+
+ if (! img.empty()) {
+ if (found_title_start)
+ title.text += " ";
+ found_title_start = TRUE;
+ title.has_been_found = TRUE;
+ title.text += img;
+ }
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else if (t->is_eo_tl()) {
+ // end of title found
+ title.has_been_found = TRUE;
+ return;
+ } else if (t->is_a_tag()) {
+ handle_tag_within_title(t);
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else if (found_title_start) {
+ title.text += " " + string(t->text_string, t->text_length);
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else {
+ title.text += string(t->text_string, t->text_length);
+ found_title_start = TRUE;
+ title.has_been_found = TRUE;
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ }
+ } while ((! page_contents->glyphs.is_equal_to_head()) ||
+ (removed_from_head));
+ }
+ }
+}
+
+/*
+ * write_html_anchor - writes out an anchor. The style of the anchor
+ * dependent upon simple_anchor.
+ */
+
+void html_printer::write_html_anchor (text_glob *h)
+{
+ if (dialect == html4) {
+ if (h != 0) {
+ html.put_string("<a name=\"");
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(header.no_of_headings);
+ buffer += '\0';
+ html.put_string(buffer.contents());
+ } else
+ html.put_string(header.header_buffer);
+ html.put_string("\"></a>").nl();
+ }
+ }
+}
+
+/*
+ * write_xhtml_anchor - writes out an anchor. The style of the anchor
+ * dependent upon simple_anchor.
+ */
+
+void html_printer::write_xhtml_anchor (text_glob *h)
+{
+ if (dialect == xhtml) {
+ if (h != 0) {
+ html.put_string(" id=\"");
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(header.no_of_headings);
+ buffer += '\0';
+ html.put_string(buffer.contents());
+ } else
+ html.put_string(header.header_buffer);
+ html.put_string("\"");
+ }
+ }
+}
+
+void html_printer::write_header (void)
+{
+ if (! header.header_buffer.empty()) {
+ text_glob *a = 0;
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+
+ if (header.header_level > 7)
+ header.header_level = 7;
+
+ // firstly we must terminate any font and type faces
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+
+ if (cutoff_heading+2 > header.header_level) {
+ // now we save the header so we can issue a list of links
+ header.no_of_headings++;
+ style st;
+
+ a = new text_glob();
+ a->text_glob_html(&st,
+ header.headings
+ .add_string(header.header_buffer),
+ header.header_buffer.length(),
+ header.no_of_headings, header.header_level,
+ header.no_of_headings, header.header_level);
+
+ // and add this header to the header list
+ header.headers.add(a,
+ header.no_of_headings,
+ header.no_of_headings, header.no_of_headings,
+ header.no_of_headings, header.no_of_headings);
+ }
+
+ html.nl().nl();
+
+ if (manufacture_headings) {
+ // line break before a header
+ if (!current_paragraph->emitted_text())
+ current_paragraph->do_space();
+ // user wants manufactured headings which look better than
+ // <Hn></Hn>
+ if (header.header_level<4) {
+ html.put_string("<b><font size=\"+1\">");
+ html.put_string(header.header_buffer);
+ html.put_string("</font>").nl();
+ write_html_anchor(a);
+ html.put_string("</b>").nl();
+ }
+ else {
+ html.put_string("<b>");
+ html.put_string(header.header_buffer).nl();
+ write_html_anchor(a);
+ html.put_string("</b>").nl();
+ }
+ }
+ else {
+ // and now we issue the real header
+ html.put_string("<h");
+ html.put_number(header.header_level);
+ write_xhtml_anchor(a);
+ html.put_string(">");
+ html.put_string(header.header_buffer).nl();
+ write_html_anchor(a);
+ html.put_string("</h");
+ html.put_number(header.header_level);
+ html.put_string(">").nl();
+ }
+
+ /* and now we save the file name in which this header will occur */
+
+ style st; // fake style to enable us to use the list data structure
+
+ text_glob *h=new text_glob();
+ h->text_glob_html(&st,
+ header.headings.add_string(file_list.file_name()),
+ file_list.file_name().length(),
+ header.no_of_headings, header.header_level,
+ header.no_of_headings, header.header_level);
+
+ header.header_filename.add(h,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings);
+
+ current_paragraph->do_para(&html, "", get_troff_indent(),
+ pageoffset, linelength, space);
+ }
+}
+
+void html_printer::determine_header_level (int level)
+{
+ if (level == 0) {
+ int i;
+
+ for (i = 0; ((i<header.header_buffer.length())
+ && ((header.header_buffer[i] == '.')
+ || is_digit(header.header_buffer[i]))) ; i++) {
+ if (header.header_buffer[i] == '.') {
+ level++;
+ }
+ }
+ }
+ header.header_level = level+1;
+ if (header.header_level >= 2 && header.header_level <= split_level) {
+ header.no_of_level_one_headings++;
+ insert_split_file();
+ }
+}
+
+/*
+ * do_heading - handle the .SH and .NH and equivalent commands from
+ * troff.
+ */
+
+void html_printer::do_heading (char *arg)
+{
+ text_glob *g;
+ int level=atoi(arg);
+ int horiz;
+
+ header.header_buffer.clear();
+ page_contents->glyphs.move_right();
+ if (! page_contents->glyphs.is_equal_to_head()) {
+ g = page_contents->glyphs.get_data();
+ horiz = g->minh;
+ do {
+ if (g->is_auto_img()) {
+ string img=generate_img_src((char *)(g->text_string + 20));
+
+ if (! img.empty()) {
+ // we cannot use full heading anchors with images
+ simple_anchors = TRUE;
+ if (horiz < g->minh)
+ header.header_buffer += " ";
+
+ header.header_buffer += img;
+ }
+ }
+ else if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce()
+ || g->is_ll())
+ troff_tag(g);
+ else if (g->is_fi())
+ fill_on = 1;
+ else if (g->is_nf())
+ fill_on = 0;
+ else if (! (g->is_a_line() || g->is_a_tag())) {
+ /*
+ * we ignore the other tag commands when constructing a heading
+ */
+ if (horiz < g->minh)
+ header.header_buffer += " ";
+
+ horiz = g->maxh;
+ header.header_buffer += string(g->text_string, g->text_length);
+ }
+ page_contents->glyphs.move_right();
+ g = page_contents->glyphs.get_data();
+ } while ((! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_eo_h()));
+ }
+
+ determine_header_level(level);
+ write_header();
+
+ /*
+ * finally set the output font to uninitialized, thus forcing
+ * the new paragraph to start a new font block.
+ */
+
+ output_style.f = 0;
+ g = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_left(); // so that next time we use old g
+}
+
+/*
+ * is_courier_until_eol - returns TRUE if we can see a whole line which
+ * is courier
+ */
+
+int html_printer::is_courier_until_eol (void)
+{
+ text_glob *orig = page_contents->glyphs.get_data();
+ int result = TRUE;
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_tail()) {
+ page_contents->glyphs.move_right();
+ do {
+ g = page_contents->glyphs.get_data();
+ if (! g->is_a_tag() && (! is_font_courier(g->text_style.f)))
+ result = FALSE;
+ page_contents->glyphs.move_right();
+ } while (result &&
+ (! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_fi()) && (! g->is_eol()));
+
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_left();
+ }
+ return result;
+}
+
+/*
+ * do_linelength - handle the .ll command from troff.
+ */
+
+void html_printer::do_linelength (char *arg)
+{
+ if (max_linelength == -1)
+ max_linelength = atoi(arg);
+
+ next_linelength = atoi(arg);
+ seen_linelength = TRUE;
+}
+
+/*
+ * do_pageoffset - handle the .po command from troff.
+ */
+
+void html_printer::do_pageoffset (char *arg)
+{
+ next_pageoffset = atoi(arg);
+ seen_pageoffset = TRUE;
+}
+
+/*
+ * get_troff_indent - returns the indent value.
+ */
+
+int html_printer::get_troff_indent (void)
+{
+ if (end_tempindent > 0)
+ return temp_indent;
+ else
+ return troff_indent;
+}
+
+/*
+ * do_indentation - handle the .in command from troff.
+ */
+
+void html_printer::do_indentation (char *arg)
+{
+ next_indent = atoi(arg);
+ seen_indent = TRUE;
+}
+
+/*
+ * do_tempindent - handle the .ti command from troff.
+ */
+
+void html_printer::do_tempindent (char *arg)
+{
+ if (fill_on) {
+ /*
+ * we set the end_tempindent to 2 as the first .br
+ * activates the .ti and the second terminates it.
+ */
+ end_tempindent = 2;
+ temp_indent = atoi(arg);
+ }
+}
+
+/*
+ * shutdown_table - shuts down the current table.
+ */
+
+void html_printer::shutdown_table (void)
+{
+ if (table != 0) {
+ current_paragraph->done_para();
+ table->emit_finish_table();
+ // don't delete this table as it will be deleted when we destroy the
+ // text_glob
+ table = 0;
+ }
+}
+
+/*
+ * do_indent - remember the indent parameters and if
+ * indent is > pageoff and indent has changed
+ * then we start a html table to implement the indentation.
+ */
+
+void html_printer::do_indent (int in, int pageoff, int linelen)
+{
+ if ((device_indent != -1) &&
+ (pageoffset+device_indent != in+pageoff)) {
+
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+ current_paragraph->done_para();
+
+ device_indent = in;
+ pageoffset = pageoff;
+ if (linelen <= max_linelength)
+ linelength = linelen;
+
+ current_paragraph->do_para(&html, "", device_indent,
+ pageoffset, max_linelength, space);
+ }
+}
+
+/*
+ * do_verticalspacing - handle the .vs command from troff.
+ */
+
+void html_printer::do_verticalspacing (char *arg)
+{
+ vertical_spacing = atoi(arg);
+}
+
+/*
+ * do_pointsize - handle the .ps command from troff.
+ */
+
+void html_printer::do_pointsize (char *arg)
+{
+ /*
+ * firstly check to see whether this point size is really associated
+ * with a .tl tag
+ */
+
+ if (! page_contents->glyphs.is_empty()) {
+ text_glob *g = page_contents->glyphs.get_data();
+ text_glob *t = page_contents->glyphs.get_data();
+
+ while (t->is_a_tag() && (!page_contents->glyphs.is_equal_to_head()))
+ {
+ if (t->is_tl()) {
+ /*
+ * found title therefore ignore this .ps tag
+ */
+ while (t != g) {
+ page_contents->glyphs.move_left();
+ t = page_contents->glyphs.get_data();
+ }
+ return;
+ }
+ page_contents->glyphs.move_right();
+ t = page_contents->glyphs.get_data();
+ }
+ /*
+ * move back to original position
+ */
+ while (t != g) {
+ page_contents->glyphs.move_left();
+ t = page_contents->glyphs.get_data();
+ }
+ /*
+ * collect valid pointsize
+ */
+ pointsize = atoi(arg);
+ }
+}
+
+/*
+ * do_fill - records whether troff has requested that text be filled.
+ */
+
+void html_printer::do_fill (char *arg)
+{
+ int on = atoi(arg);
+
+ output_hpos = get_troff_indent()+pageoffset;
+ suppress_sub_sup = TRUE;
+
+ if (fill_on != on) {
+ if (on)
+ current_paragraph->do_para("", seen_space);
+ fill_on = on;
+ }
+}
+
+/*
+ * do_eol - handle the end of line
+ */
+
+void html_printer::do_eol (void)
+{
+ if (! fill_on) {
+ if (current_paragraph->ever_emitted_text()) {
+ current_paragraph->do_newline();
+ current_paragraph->do_break();
+ }
+ }
+ output_hpos = get_troff_indent()+pageoffset;
+}
+
+/*
+ * do_check_center - checks to see whether we have seen a '.ce' tag
+ * during the previous line.
+ */
+
+void html_printer::do_check_center(void)
+{
+ if (seen_center) {
+ seen_center = FALSE;
+ if (next_center > 0) {
+ if (end_center == 0) {
+ int space = current_paragraph->retrieve_para_space()
+ || seen_space;
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ } else
+ if ((strcmp("align=\"center\"",
+ current_paragraph->get_alignment()) != 0) &&
+ (strcmp("class=\"center\"",
+ current_paragraph->get_alignment()) != 0)) {
+ /*
+ * different alignment, so shutdown paragraph and open
+ * a new one.
+ */
+ int space = current_paragraph->retrieve_para_space()
+ || seen_space;
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ } else
+ // same alignment; if we have emitted text, issue a break.
+ if (current_paragraph->emitted_text())
+ current_paragraph->do_break();
+ } else
+ /*
+ * next_center == 0
+ */
+ if (end_center > 0) {
+ seen_space = seen_space
+ || current_paragraph->retrieve_para_space();
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ current_paragraph->do_para("", seen_space);
+ }
+ end_center = next_center;
+ }
+}
+
+/*
+ * do_eol_ce - handle end of line specifically for a .ce
+ */
+
+void html_printer::do_eol_ce (void)
+{
+ if (end_center > 0) {
+ if (end_center > 1)
+ if (current_paragraph->emitted_text())
+ current_paragraph->do_break();
+
+ end_center--;
+ if (end_center == 0) {
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ }
+ }
+}
+
+/*
+ * do_flush - flushes all output and tags.
+ */
+
+void html_printer::do_flush (void)
+{
+ current_paragraph->done_para();
+}
+
+/*
+ * do_links - moves onto a new temporary file and sets auto_links to
+ * false.
+ */
+
+void html_printer::do_links (void)
+{
+ html.end_line(); // flush line
+ auto_links = FALSE; // from now on only emit under user request
+ file_list.add_new_file(xtmpfile());
+ file_list.set_links_required();
+ html.set_file(file_list.get_file());
+}
+
+/*
+ * insert_split_file -
+ */
+
+void html_printer::insert_split_file (void)
+{
+ if (multiple_files) {
+ current_paragraph->done_para(); // flush paragraph
+ html.end_line(); // flush line
+ html.set_file(file_list.get_file()); // flush current file
+ file_list.add_new_file(xtmpfile());
+ string split_file = job_name;
+
+ split_file += string("-");
+ split_file += as_string(header.no_of_level_one_headings);
+ if (dialect == xhtml)
+ split_file += string(".xhtml");
+ else
+ split_file += string(".html");
+ split_file += '\0';
+
+ file_list.set_file_name(split_file);
+ html.set_file(file_list.get_file());
+ }
+}
+
+/*
+ * do_job_name - assigns the job_name to name.
+ */
+
+void html_printer::do_job_name (char *name)
+{
+ if (! multiple_files) {
+ multiple_files = TRUE;
+ while (name != 0 && (*name != (char)0) && (*name == ' '))
+ name++;
+ job_name = name;
+ }
+}
+
+/*
+ * do_head - adds a string to head_info which is to be included into
+ * the <head> </head> section of the html document.
+ */
+
+void html_printer::do_head (char *name)
+{
+ head_info += string(name);
+ head_info += '\n';
+}
+
+/*
+ * do_break - handles the ".br" request and also undoes an outstanding
+ * ".ti" command and calls indent if the indentation related
+ * registers have changed.
+ */
+
+void html_printer::do_break (void)
+{
+ int seen_temp_indent = FALSE;
+
+ current_paragraph->do_break();
+ if (end_tempindent > 0) {
+ end_tempindent--;
+ if (end_tempindent > 0)
+ seen_temp_indent = TRUE;
+ }
+ if (seen_indent || seen_pageoffset || seen_linelength
+ || seen_temp_indent) {
+ if (seen_indent && (! seen_temp_indent))
+ troff_indent = next_indent;
+ if (! seen_pageoffset)
+ next_pageoffset = pageoffset;
+ if (! seen_linelength)
+ next_linelength = linelength;
+ do_indent(get_troff_indent(), next_pageoffset, next_linelength);
+ }
+ seen_indent = seen_temp_indent;
+ seen_linelength = FALSE;
+ seen_pageoffset = FALSE;
+ do_check_center();
+ output_hpos = get_troff_indent()+pageoffset;
+ suppress_sub_sup = TRUE;
+}
+
+void html_printer::do_space (char *arg)
+{
+ int n = atoi(arg);
+
+ seen_space = atoi(arg);
+ as.check_sp(seen_space);
+#if 0
+ if (n>0 && table)
+ table->set_space(TRUE);
+#endif
+
+ while (n>0) {
+ current_paragraph->do_space();
+ n--;
+ }
+ suppress_sub_sup = TRUE;
+}
+
+/*
+ * do_tab_ts - start a table, which will have already been defined.
+ */
+
+void html_printer::do_tab_ts (text_glob *g)
+{
+ html_table *t = g->get_table();
+
+ if (t != 0) {
+ current_column = 0;
+ current_paragraph->done_pre();
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+
+#if defined(DEBUG_TABLES)
+ html.simple_comment("TABS");
+#endif
+
+ t->set_linelength(max_linelength);
+ t->add_indent(pageoffset);
+#if 0
+ t->emit_table_header(seen_space);
+#else
+ t->emit_table_header(FALSE);
+ row_space = current_paragraph->retrieve_para_space() || seen_space;
+ seen_space = FALSE;
+#endif
+ }
+
+ table = t;
+}
+
+/*
+ * do_tab_te - finish a table.
+ */
+
+void html_printer::do_tab_te (void)
+{
+ if (table) {
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+ table->emit_finish_table();
+ }
+
+ table = 0;
+ restore_troff_indent();
+}
+
+/*
+ * do_tab - handle the "devtag:tab" tag
+ */
+
+void html_printer::do_tab (char *s)
+{
+ if (table) {
+ while (isspace(*s))
+ s++;
+ s++;
+ int col = table->find_column(atoi(s) + pageoffset
+ + get_troff_indent());
+ if (col > 0) {
+ current_paragraph->done_para();
+ table->emit_col(col);
+ }
+ }
+}
+
+/*
+ * do_tab0 - handle the "devtag:tab0" tag
+ */
+
+void html_printer::do_tab0 (void)
+{
+ if (table) {
+ int col = table->find_column(pageoffset+get_troff_indent());
+ if (col > 0) {
+ current_paragraph->done_para();
+ table->emit_col(col);
+ }
+ }
+}
+
+/*
+ * do_col - start column, s.
+ */
+
+void html_printer::do_col (char *s)
+{
+ if (table) {
+ if (atoi(s) < current_column)
+ row_space = seen_space;
+
+ current_column = atoi(s);
+ current_paragraph->done_para();
+ table->emit_col(current_column);
+ current_paragraph->do_para("", row_space);
+ }
+}
+
+/*
+ * troff_tag - processes the troff tag and manipulates the troff
+ * state machine.
+ */
+
+void html_printer::troff_tag (text_glob *g)
+{
+ /*
+ * firstly skip over devtag:
+ */
+ char *t=(char *)g->text_string+strlen("devtag:");
+ if (strncmp(g->text_string, "html</p>:", strlen("html</p>:")) == 0) {
+ do_end_para(g);
+ } else if (strncmp(g->text_string, "html<?p>:", strlen("html<?p>:"))
+ == 0) {
+ if (current_paragraph->emitted_text())
+ html.put_string(g->text_string+9);
+ else
+ do_end_para(g);
+ } else if (strncmp(g->text_string, "math<?p>:", strlen("math<?p>:"))
+ == 0) {
+ do_math(g);
+ } else if (g->is_eol()) {
+ do_eol();
+ } else if (g->is_eol_ce()) {
+ do_eol_ce();
+ } else if (strncmp(t, ".sp", 3) == 0) {
+ char *a = (char *)t+3;
+ do_space(a);
+ } else if (strncmp(t, ".br", 3) == 0) {
+ seen_break = 1;
+ as.check_br(1);
+ do_break();
+ } else if (strcmp(t, ".centered-image") == 0) {
+ do_centered_image();
+ } else if (strcmp(t, ".right-image") == 0) {
+ do_right_image();
+ } else if (strcmp(t, ".left-image") == 0) {
+ do_left_image();
+ } else if (strncmp(t, ".auto-image", 11) == 0) {
+ char *a = (char *)t+11;
+ do_auto_image(g, a);
+ } else if (strncmp(t, ".ce", 3) == 0) {
+ char *a = (char *)t+3;
+ suppress_sub_sup = TRUE;
+ do_center(a);
+ } else if (g->is_tl()) {
+ suppress_sub_sup = TRUE;
+ title.with_h1 = TRUE;
+ do_title();
+ } else if (strncmp(t, ".html-tl", 8) == 0) {
+ suppress_sub_sup = TRUE;
+ title.with_h1 = FALSE;
+ do_title();
+ } else if (strncmp(t, ".fi", 3) == 0) {
+ char *a = (char *)t+3;
+ do_fill(a);
+ } else if ((strncmp(t, ".SH", 3) == 0)
+ || (strncmp(t, ".NH", 3) == 0)) {
+ char *a = (char *)t+3;
+ do_heading(a);
+ } else if (strncmp(t, ".ll", 3) == 0) {
+ char *a = (char *)t+3;
+ do_linelength(a);
+ } else if (strncmp(t, ".po", 3) == 0) {
+ char *a = (char *)t+3;
+ do_pageoffset(a);
+ } else if (strncmp(t, ".in", 3) == 0) {
+ char *a = (char *)t+3;
+ do_indentation(a);
+ } else if (strncmp(t, ".ti", 3) == 0) {
+ char *a = (char *)t+3;
+ do_tempindent(a);
+ } else if (strncmp(t, ".vs", 3) == 0) {
+ char *a = (char *)t+3;
+ do_verticalspacing(a);
+ } else if (strncmp(t, ".ps", 3) == 0) {
+ char *a = (char *)t+3;
+ do_pointsize(a);
+ } else if (strcmp(t, ".links") == 0) {
+ do_links();
+ } else if (strncmp(t, ".job-name", 9) == 0) {
+ char *a = (char *)t+9;
+ do_job_name(a);
+ } else if (strncmp(t, ".head", 5) == 0) {
+ char *a = (char *)t+5;
+ do_head(a);
+ } else if (strcmp(t, ".no-auto-rule") == 0) {
+ auto_rule = FALSE;
+ } else if (strcmp(t, ".tab-ts") == 0) {
+ do_tab_ts(g);
+ } else if (strcmp(t, ".tab-te") == 0) {
+ do_tab_te();
+ } else if (strncmp(t, ".col ", 5) == 0) {
+ char *a = (char *)t+4;
+ do_col(a);
+ } else if (strncmp(t, "tab ", 4) == 0) {
+ char *a = (char *)t+3;
+ do_tab(a);
+ } else if (strncmp(t, "tab0", 4) == 0) {
+ do_tab0();
+ }
+}
+
+/*
+ * do_math - prints out the equation
+ */
+
+void html_printer::do_math (text_glob *g)
+{
+ do_font(g);
+ if (current_paragraph->emitted_text())
+ html.put_string(g->text_string+9);
+ else
+ do_end_para(g);
+}
+
+/*
+ * is_in_middle - returns TRUE if the positions left..right are in the
+ * center of the page.
+ */
+
+int html_printer::is_in_middle (int left, int right)
+{
+ return( abs(abs(left-pageoffset) - abs(pageoffset+linelength-right))
+ <= CENTER_TOLERANCE );
+}
+
+/*
+ * flush_globs - runs through the text glob list and emits html.
+ */
+
+void html_printer::flush_globs (void)
+{
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ do {
+ g = page_contents->glyphs.get_data();
+#if 0
+ fprintf(stderr, "[%s:%d:%d:%d:%d]",
+ g->text_string, g->minv, g->minh, g->maxv, g->maxh) ;
+ fflush(stderr);
+#endif
+
+ handle_state_assertion(g);
+
+ if (strcmp(g->text_string, "XXXXXXX") == 0)
+ stop();
+
+ if (g->is_a_tag())
+ troff_tag(g);
+ else if (g->is_a_line())
+ emit_line(g);
+ else {
+ as.check_sp(seen_space);
+ as.check_br(seen_break);
+ seen_break = 0;
+ seen_space = 0;
+ emit_html(g);
+ }
+
+ as.check_fi(fill_on);
+ as.check_ce(end_center);
+ /*
+ * after processing the title (and removing it) the glyph list
+ * might be empty
+ */
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.move_right();
+ }
+ } while (! page_contents->glyphs.is_equal_to_head());
+ }
+}
+
+/*
+ * calc_nf - calculates the _no_ format flag, given the
+ * text glob, g.
+ */
+
+int html_printer::calc_nf (text_glob *g, int nf)
+{
+ if (g != 0) {
+ if (g->is_fi()) {
+ as.check_fi(TRUE);
+ return FALSE;
+ }
+ if (g->is_nf()) {
+ as.check_fi(FALSE);
+ return TRUE;
+ }
+ }
+ as.check_fi(! nf);
+ return nf;
+}
+
+/*
+ * calc_po_in - calculates the, in, po, registers
+ */
+
+void html_printer::calc_po_in (text_glob *g, int nf)
+{
+ if (g->is_in())
+ troff_indent = g->get_arg();
+ else if (g->is_po())
+ pageoffset = g->get_arg();
+ else if (g->is_ti()) {
+ temp_indent = g->get_arg();
+ end_tempindent = 2;
+ } else if (g->is_br() || (nf && g->is_eol())) {
+ if (end_tempindent > 0)
+ end_tempindent--;
+ }
+}
+
+/*
+ * next_horiz_pos - returns the next horiz position.
+ * -1 is returned if it doesn't exist.
+ */
+
+int html_printer::next_horiz_pos (text_glob *g, int nf)
+{
+ int next = -1;
+
+ if ((g != 0) && (g->is_br() || (nf && g->is_eol())))
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.move_right_get_data();
+ if (0 /* nullptr */ == g) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ }
+ else {
+ next = g->minh;
+ page_contents->glyphs.move_left();
+ }
+ }
+ return next;
+}
+
+/*
+ * insert_tab_ts - inserts a tab-ts before, where.
+ */
+
+text_glob *html_printer::insert_tab_ts (text_glob *where)
+{
+ text_glob *start_of_table;
+ text_glob *old_pos = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_to(where);
+ page_contents->glyphs.move_left();
+ // tab table start
+ page_contents->insert_tag(string("devtag:.tab-ts"));
+ page_contents->glyphs.move_right();
+ start_of_table = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_to(old_pos);
+ return start_of_table;
+}
+
+/*
+ * insert_tab_te - inserts a tab-te before the current position
+ * (it skips backwards over .sp/.br)
+ */
+
+void html_printer::insert_tab_te (void)
+{
+ text_glob *g = page_contents->glyphs.get_data();
+ page_contents->dump_page();
+ while (page_contents->glyphs.get_data()->is_a_tag())
+ page_contents->glyphs.move_left();
+ // tab table end
+ page_contents->insert_tag(string("devtag:.tab-te"));
+ while (g != page_contents->glyphs.get_data())
+ page_contents->glyphs.move_right();
+ page_contents->dump_page();
+}
+
+/*
+ * insert_tab_0 - inserts a tab0 before, where.
+ */
+
+void html_printer::insert_tab_0 (text_glob *where)
+{
+ text_glob *old_pos = page_contents->glyphs.get_data();
+
+ page_contents->glyphs.move_to(where);
+ page_contents->glyphs.move_left();
+ // tab0 start of line
+ page_contents->insert_tag(string("devtag:tab0"));
+ page_contents->glyphs.move_right();
+ page_contents->glyphs.move_to(old_pos);
+}
+
+/*
+ * remove_tabs - removes the tabs tags on this line.
+ */
+
+void html_printer::remove_tabs (void)
+{
+ text_glob *orig = page_contents->glyphs.get_data();
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_tail()) {
+ do {
+ g = page_contents->glyphs.get_data();
+ if (g->is_tab()) {
+ page_contents->glyphs.sub_move_right();
+ if (g == orig)
+ orig = page_contents->glyphs.get_data();
+ } else
+ page_contents->glyphs.move_right();
+ } while ((! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_eol()));
+
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_left();
+ }
+}
+
+void html_printer::remove_courier_tabs (void)
+{
+ text_glob *g;
+ int line_start = TRUE;
+ int nf = FALSE;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ line_start = TRUE;
+ do {
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+
+ if (line_start) {
+ if (line_start && nf && is_courier_until_eol()) {
+ remove_tabs();
+ g = page_contents->glyphs.get_data();
+ }
+ }
+
+ // line_start = g->is_br() || g->is_nf() || g->is_fi()
+ // || (nf && g->is_eol());
+ line_start = g->is_br() || (nf && g->is_eol());
+ page_contents->glyphs.move_right();
+ } while (! page_contents->glyphs.is_equal_to_head());
+ }
+}
+
+void html_printer::insert_tab0_foreach_tab (void)
+{
+ text_glob *start_of_line = 0;
+ text_glob *g = 0;
+ int seen_tab = FALSE;
+ int seen_col = FALSE;
+ int nf = FALSE;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ start_of_line = page_contents->glyphs.get_data();
+ do {
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+
+ if (g->is_tab())
+ seen_tab = TRUE;
+
+ if (g->is_col())
+ seen_col = TRUE;
+
+ if (g->is_br() || (nf && g->is_eol())) {
+ do {
+ page_contents->glyphs.move_right();
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+ if (page_contents->glyphs.is_equal_to_head()) {
+ if (seen_tab && !seen_col)
+ insert_tab_0(start_of_line);
+ return;
+ }
+ } while (g->is_br() || (nf && g->is_eol()) || g->is_ta());
+ // printf("\nstart_of_line is: %s\n", g->text_string);
+ if (seen_tab && !seen_col) {
+ insert_tab_0(start_of_line);
+ page_contents->glyphs.move_to(g);
+ }
+
+ seen_tab = FALSE;
+ seen_col = FALSE;
+ start_of_line = g;
+ }
+ page_contents->glyphs.move_right();
+ } while (! page_contents->glyphs.is_equal_to_head());
+ if (seen_tab && !seen_col)
+ insert_tab_0(start_of_line);
+
+ }
+}
+
+/*
+ * update_min_max - updates the extent of a column, given the left and
+ * right extents of a glyph, g.
+ */
+
+void html_printer::update_min_max (colType type_of_col,
+ int *minimum, int *maximum,
+ text_glob *g)
+{
+ switch (type_of_col) {
+
+ case tab_tag:
+ break;
+ case tab0_tag:
+ *minimum = g->minh;
+ break;
+ case col_tag:
+ *minimum = g->minh;
+ *maximum = g->maxh;
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * add_table_end - moves left one glyph, adds a table end tag and adds
+ * a debugging string.
+ */
+
+void html_printer::add_table_end (const char *
+#if defined(DEBUG_TABLES)
+ debug_string
+#endif
+)
+{
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+#if defined(DEBUG_TABLES)
+ page_contents->insert_tag(string(debug_string));
+#endif
+}
+
+/*
+ * lookahead_for_tables - checks for .col tags and inserts table
+ * start/end tags
+ */
+
+void html_printer::lookahead_for_tables (void)
+{
+ text_glob *g;
+ text_glob *start_of_line = 0;
+ text_glob *start_of_table = 0;
+ text_glob *last = 0;
+ colType type_of_col = none;
+ int found_col = FALSE;
+ int ncol = 0;
+ int colmin = 0; // pacify compiler
+ int colmax = 0; // pacify compiler
+ html_table *tbl = new html_table(&html, -1);
+ const char *tab_defs = 0;
+ char align = 'L';
+ int nf = FALSE;
+ int old_pageoffset = pageoffset;
+
+ remove_courier_tabs();
+ page_contents->dump_page();
+ insert_tab0_foreach_tab();
+ page_contents->dump_page();
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ g = page_contents->glyphs.get_data();
+ if (g->is_br()) {
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ if (page_contents->glyphs.is_equal_to_head()) {
+ if (tbl != 0) {
+ delete tbl;
+ tbl = 0;
+ }
+ return;
+ }
+
+ start_of_line = g;
+ ncol = 0;
+ if (found_col)
+ last = g;
+ found_col = FALSE;
+ }
+
+ do {
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, " [") ;
+ fprintf(stderr, g->text_string) ;
+ fprintf(stderr, "] ") ;
+ fflush(stderr);
+ if (strcmp(g->text_string, "XXXXXXX") == 0)
+ stop();
+#endif
+
+ nf = calc_nf(g, nf);
+ calc_po_in(g, nf);
+ if (g->is_col()) {
+ if (type_of_col == tab_tag && start_of_table != 0) {
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ page_contents->insert_tag(string("*** TAB -> COL ***"));
+ if (tab_defs != 0)
+ tbl->tab_stops->init(tab_defs);
+ start_of_table = 0;
+ last = 0;
+ }
+ type_of_col = col_tag;
+ found_col = TRUE;
+ ncol = g->get_arg();
+ align = 'L';
+ colmin = 0;
+ colmax = 0;
+ } else if (g->is_tab()) {
+ type_of_col = tab_tag;
+ colmin = g->get_tab_args(&align);
+ align = 'L'; // for now as 'C' and 'R' are broken
+ ncol = tbl->find_tab_column(colmin);
+ colmin += pageoffset + get_troff_indent();
+ colmax = tbl->get_tab_pos(ncol+1);
+ if (colmax > 0)
+ colmax += pageoffset + get_troff_indent();
+ } else if (g->is_tab0()) {
+ if (type_of_col == col_tag && start_of_table != 0) {
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ page_contents->insert_tag(string("*** COL -> TAB ***"));
+ start_of_table = 0;
+ last = 0;
+ }
+ if (tab_defs != 0)
+ tbl->tab_stops->init(tab_defs);
+ type_of_col = tab0_tag;
+ ncol = 1;
+ colmin = 0;
+ colmax = tbl->get_tab_pos(2) + pageoffset + get_troff_indent();
+ } else if (! g->is_a_tag())
+ update_min_max(type_of_col, &colmin, &colmax, g);
+ if ((g->is_col() || g->is_tab() || g->is_tab0())
+ && (start_of_line != 0)
+ && (0 /* nullptr */ == start_of_table)) {
+ start_of_table = insert_tab_ts(start_of_line);
+ start_of_line = 0;
+ } else if (g->is_ce() && (start_of_table != 0)) {
+ add_table_end("*** CE ***");
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ last = 0;
+ } else if (g->is_ta()) {
+ tab_defs = g->text_string;
+ if (type_of_col == col_tag)
+ tbl->tab_stops->check_init(tab_defs);
+ if (!tbl->tab_stops->compatible(tab_defs)) {
+ if (start_of_table != 0) {
+ add_table_end("*** TABS ***");
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ type_of_col = none;
+ last = 0;
+ }
+ tbl->tab_stops->init(tab_defs);
+ }
+ }
+ if (((! g->is_a_tag()) || g->is_tab()) && (start_of_table != 0)) {
+ // we are in a table and have a glyph
+ if ((ncol == 0)
+ || (! tbl->add_column(ncol, colmin, colmax, align))) {
+ if (ncol == 0)
+ add_table_end("*** NCOL == 0 ***");
+ else
+ add_table_end("*** CROSSED COLS ***");
+
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ type_of_col = none;
+ last = 0;
+ }
+ }
+ /*
+ * move onto next glob, check whether we are starting a new line
+ */
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ if (0 /* nullptr */ == g) {
+ if (found_col) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ last = g;
+ found_col = FALSE;
+ }
+ } else if (g->is_br() || (nf && g->is_eol())) {
+ do {
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+ } while ((g != 0) && (g->is_br() || (nf && g->is_eol())));
+ start_of_line = g;
+ ncol = 0;
+ if (found_col)
+ last = g;
+ found_col = FALSE;
+ }
+ } while ((g != 0) && (! page_contents->glyphs.is_equal_to_head()));
+
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, "finished scanning for tables\n");
+#endif
+
+ page_contents->glyphs.start_from_head();
+ if (start_of_table != 0) {
+ if (last != 0)
+ while (last != page_contents->glyphs.get_data())
+ page_contents->glyphs.move_left();
+
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = 0;
+ page_contents->insert_tag(string("*** LAST ***"));
+ }
+ }
+ if (tbl != 0) {
+ delete tbl;
+ tbl = 0;
+ }
+
+ // and reset the registers
+ pageoffset = old_pageoffset;
+ troff_indent = 0;
+ temp_indent = 0;
+ end_tempindent = 0;
+}
+
+void html_printer::flush_page (void)
+{
+ suppress_sub_sup = TRUE;
+ flush_sbuf();
+ page_contents->dump_page();
+ lookahead_for_tables();
+ page_contents->dump_page();
+ flush_globs();
+ current_paragraph->done_para();
+ current_paragraph->flush_text();
+ // move onto a new page
+ delete page_contents;
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, "\n\n*** flushed page ***\n\n");
+ html.simple_comment("new page called");
+#endif
+ page_contents = new page;
+}
+
+/*
+ * determine_space - works out whether we need to write a space.
+ * If last glyph is adjoining, then emit no space.
+ */
+
+void html_printer::determine_space (text_glob *g)
+{
+ if (current_paragraph->is_in_pre()) {
+ /*
+ * .nf has been specified
+ */
+ while (output_hpos < g->minh) {
+ output_hpos += space_width;
+ current_paragraph->emit_space();
+ }
+ } else {
+ if ((output_vpos != g->minv) || (output_hpos < g->minh)) {
+ current_paragraph->emit_space();
+ }
+ }
+}
+
+/*
+ * is_line_start - returns TRUE if we are at the start of a line.
+ */
+
+int html_printer::is_line_start (int nf)
+{
+ int line_start = FALSE;
+ int result = TRUE;
+ text_glob *orig = page_contents->glyphs.get_data();
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_head()) {
+ do {
+ page_contents->glyphs.move_left();
+ g = page_contents->glyphs.get_data();
+ result = g->is_a_tag();
+ if (g->is_fi())
+ nf = FALSE;
+ else if (g->is_nf())
+ nf = TRUE;
+ line_start = g->is_col() || g->is_br() || (nf && g->is_eol());
+ } while ((!line_start) && (result));
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_right();
+ }
+ return result;
+}
+
+/*
+ * is_font_courier - returns TRUE if the font, f, is courier.
+ */
+
+int html_printer::is_font_courier (font *f)
+{
+ if (f != 0) {
+ const char *fontname = f->get_name();
+
+ return( (fontname != 0) && (fontname[0] == 'C') );
+ }
+ return FALSE;
+}
+
+/*
+ * end_font - shuts down the font corresponding to fontname.
+ */
+
+void html_printer::end_font (const char *fontname)
+{
+ if (strcmp(fontname, "B") == 0) {
+ current_paragraph->done_bold();
+ } else if (strcmp(fontname, "I") == 0) {
+ current_paragraph->done_italic();
+ } else if (strcmp(fontname, "BI") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ } else if (strcmp(fontname, "CR") == 0) {
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CI") == 0) {
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CB") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CBI") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ }
+}
+
+/*
+ * start_font - starts the font corresponding to name.
+ */
+
+void html_printer::start_font (const char *fontname)
+{
+ if (strcmp(fontname, "R") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "B") == 0) {
+ current_paragraph->do_bold();
+ } else if (strcmp(fontname, "I") == 0) {
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "BI") == 0) {
+ current_paragraph->do_bold();
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "CR") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ } else if (strcmp(fontname, "CI") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "CB") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_bold();
+ } else if (strcmp(fontname, "CBI") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_italic();
+ current_paragraph->do_bold();
+ }
+}
+
+/*
+ * start_size - from is old font size, to is the new font size.
+ * The HTML elements <big> and <small> respectively
+ * increase and decrease the font size by 20%. We try and
+ * map these onto glyph sizes.
+ */
+
+void html_printer::start_size (int from, int to)
+{
+ if (from < to) {
+ while (from < to) {
+ current_paragraph->do_big();
+ from += SIZE_INCREMENT;
+ }
+ } else if (from > to) {
+ while (from > to) {
+ current_paragraph->do_small();
+ from -= SIZE_INCREMENT;
+ }
+ }
+}
+
+/*
+ * do_font - checks to see whether we need to alter the html font.
+ */
+
+void html_printer::do_font (text_glob *g)
+{
+ /*
+ * check if the output_style.point_size has not been set yet
+ * this allow users to place .ps at the top of their troff files
+ * and grohtml can then treat the .ps value as the base font size (3)
+ */
+ if (output_style.point_size == -1) {
+ output_style.point_size = pointsize;
+ }
+
+ if (g->text_style.f != output_style.f) {
+ if (output_style.f != 0) {
+ end_font(output_style.f->get_name());
+ }
+ output_style.f = g->text_style.f;
+ if (output_style.f != 0) {
+ start_font(output_style.f->get_name());
+ }
+ }
+ if (output_style.point_size != g->text_style.point_size) {
+ do_sup_or_sub(g);
+ if ((output_style.point_size > 0) &&
+ (g->text_style.point_size > 0)) {
+ start_size(output_style.point_size, g->text_style.point_size);
+ }
+ if (g->text_style.point_size > 0) {
+ output_style.point_size = g->text_style.point_size;
+ }
+ }
+ if (output_style.col != g->text_style.col) {
+ current_paragraph->done_color();
+ output_style.col = g->text_style.col;
+ current_paragraph->do_color(&output_style.col);
+ }
+}
+
+/*
+ * start_subscript - returns TRUE if, g, looks like a subscript start.
+ */
+
+int html_printer::start_subscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (output_vpos < g->minv) &&
+ (output_vpos-height > g->maxv) &&
+ (output_style.point_size > g->text_style.point_size));
+}
+
+/*
+ * start_superscript - returns TRUE if, g, looks like a superscript
+ * start.
+ */
+
+int html_printer::start_superscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (output_vpos > g->minv) &&
+ (output_vpos-height < g->maxv) &&
+ (output_style.point_size > g->text_style.point_size));
+}
+
+/*
+ * end_subscript - returns TRUE if, g, looks like the end of a
+ * subscript.
+ */
+
+int html_printer::end_subscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (g->minv < output_vpos) &&
+ (output_vpos-height > g->maxv) &&
+ (output_style.point_size < g->text_style.point_size));
+}
+
+/*
+ * end_superscript - returns TRUE if, g, looks like the end of a
+ * superscript.
+ */
+
+int html_printer::end_superscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (g->minv > output_vpos) &&
+ (output_vpos-height < g->maxv) &&
+ (output_style.point_size < g->text_style.point_size));
+}
+
+/*
+ * do_sup_or_sub - checks to see whether the next glyph is a
+ * subscript/superscript start/end and it calls the
+ * services of html-text to issue the appropriate tags.
+ */
+
+void html_printer::do_sup_or_sub (text_glob *g)
+{
+ if (! suppress_sub_sup) {
+ if (start_subscript(g)) {
+ current_paragraph->do_sub();
+ } else if (start_superscript(g)) {
+ current_paragraph->do_sup();
+ } else if (end_subscript(g)) {
+ current_paragraph->done_sub();
+ } else if (end_superscript(g)) {
+ current_paragraph->done_sup();
+ }
+ }
+}
+
+/*
+ * do_end_para - writes out the html text after shutting down the
+ * current paragraph.
+ */
+
+void html_printer::do_end_para (text_glob *g)
+{
+ do_font(g);
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+ html.put_string(g->text_string+9);
+ output_vpos = g->minv;
+ output_hpos = g->maxh;
+ output_vpos_max = g->maxv;
+ suppress_sub_sup = FALSE;
+}
+
+/*
+ * emit_html - write out the html text
+ */
+
+void html_printer::emit_html (text_glob *g)
+{
+ do_font(g);
+ determine_space(g);
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ output_vpos = g->minv;
+ output_hpos = g->maxh;
+ output_vpos_max = g->maxv;
+ suppress_sub_sup = FALSE;
+}
+
+/*
+ * flush_sbuf - flushes the current sbuf into the list of glyphs.
+ */
+
+void html_printer::flush_sbuf()
+{
+ if (sbuf.length() > 0) {
+ int r=font::res; // resolution of the device
+ set_style(sbuf_style);
+
+ if (overstrike_detected && (! is_bold(sbuf_style.f))) {
+ font *bold_font = make_bold(sbuf_style.f);
+ if (bold_font != 0)
+ sbuf_style.f = bold_font;
+ }
+
+ page_contents->add(&sbuf_style, sbuf, line_number,
+ (sbuf_vpos - (sbuf_style.point_size * r / 72)),
+ sbuf_start_hpos, sbuf_vpos, sbuf_end_hpos);
+ output_hpos = sbuf_end_hpos;
+ output_vpos = sbuf_vpos;
+ last_sbuf_length = 0;
+ sbuf_prev_hpos = sbuf_end_hpos;
+ overstrike_detected = FALSE;
+ sbuf.clear();
+ }
+}
+
+void html_printer::set_line_thickness(const environment *env)
+{
+ line_thickness = env->size;
+}
+
+void html_printer::draw(int code, int *p, int np,
+ const environment *env)
+{
+ switch (code) {
+
+ case 'l':
+# if 0
+ if (np == 2) {
+ page_contents->add_line(&sbuf_style,
+ line_number,
+ env->hpos, env->vpos,
+ (env->hpos + p[0]), (env->vpos + p[1]),
+ line_thickness);
+ } else {
+ error("2 arguments required for line");
+ }
+# endif
+ break;
+ case 't':
+ {
+ if (np == 0) {
+ line_thickness = -1;
+ } else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ }
+
+ case 'P':
+ break;
+ case 'p':
+ break;
+ case 'E':
+ break;
+ case 'e':
+ break;
+ case 'C':
+ break;
+ case 'c':
+ break;
+ case 'a':
+ break;
+ case '~':
+ break;
+ case 'f':
+ break;
+ case 'F':
+ // fill with color env->fill
+ if (background != 0)
+ delete background;
+ background = new color;
+ *background = *env->fill;
+ break;
+
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+}
+
+html_printer::html_printer()
+: html(0, MAX_LINE_LENGTH),
+ no_of_printed_pages(0),
+ last_sbuf_length(0),
+ overstrike_detected(FALSE),
+ output_hpos(-1),
+ output_vpos(-1),
+ output_vpos_max(-1),
+ line_thickness(-1),
+ inside_font_style(0),
+ page_number(0),
+ header_indent(-1),
+ suppress_sub_sup(TRUE),
+ cutoff_heading(100),
+ indent(0),
+ table(0),
+ end_center(0),
+ end_tempindent(0),
+ next_tag(INLINE),
+ fill_on(TRUE),
+ max_linelength(-1),
+ linelength(0),
+ pageoffset(0),
+ troff_indent(0),
+ device_indent(0),
+ temp_indent(0),
+ pointsize(base_point_size),
+ line_number(0),
+ background(default_background),
+ seen_indent(FALSE),
+ next_indent(0),
+ seen_pageoffset(FALSE),
+ next_pageoffset(0),
+ seen_linelength(FALSE),
+ next_linelength(0),
+ seen_center(FALSE),
+ next_center(0),
+ seen_space(0),
+ seen_break(0),
+ current_column(0),
+ row_space(FALSE)
+{
+ file_list.add_new_file(xtmpfile());
+ html.set_file(file_list.get_file());
+ if (font::hor != 24)
+ fatal("horizontal motion quantum must be 24");
+ if (font::vert != 40)
+ fatal("vertical motion quantum must be 40");
+#if 0
+ // should be sorted html..
+ if (font::res % (font::sizescale*72) != 0)
+ fatal("res must be a multiple of 72*sizescale");
+#endif
+ int r = font::res;
+ int point = 0;
+ while (r % 10 == 0) {
+ r /= 10;
+ point++;
+ }
+ res = r;
+ html.set_fixed_point(point);
+ space_glyph = name_to_glyph("space");
+ space_width = font::hor;
+ paper_length = font::paperlength;
+ linelength = font::res*13/2;
+ if (paper_length == 0)
+ paper_length = 11*font::res;
+
+ page_contents = new page();
+}
+
+/*
+ * add_to_sbuf - adds character code or name to the sbuf.
+ */
+
+void html_printer::add_to_sbuf (glyph *g, const string &s)
+{
+ if (0 /* nullptr */ == sbuf_style.f)
+ return;
+
+ const char *html_glyph = 0;
+ unsigned int code = sbuf_style.f->get_code(g);
+
+ if (s.empty()) {
+ if (sbuf_style.f->contains(g))
+ html_glyph = get_html_entity(sbuf_style.f->get_code(g));
+ else
+ html_glyph = 0;
+
+ if ((0 /* nullptr */ == html_glyph) && (code >= UNICODE_DESC_START))
+ html_glyph = to_unicode(code);
+ } else
+ html_glyph = get_html_translation(sbuf_style.f, s);
+
+ last_sbuf_length = sbuf.length();
+ if (0 /* nullptr */ == html_glyph)
+ sbuf += ((char)code);
+ else
+ sbuf += html_glyph;
+}
+
+int html_printer::sbuf_continuation (glyph *g, const char *name,
+ const environment *env, int w)
+{
+ /*
+ * lets see whether the glyph is closer to the end of sbuf
+ */
+ if ((sbuf_end_hpos == env->hpos)
+ || ((sbuf_prev_hpos < sbuf_end_hpos)
+ && (env->hpos < sbuf_end_hpos)
+ && ((sbuf_end_hpos-env->hpos < env->hpos-sbuf_prev_hpos)))) {
+ add_to_sbuf(g, name);
+ sbuf_prev_hpos = sbuf_end_hpos;
+ sbuf_end_hpos += w + sbuf_kern;
+ return TRUE;
+ } else {
+ if ((env->hpos >= sbuf_end_hpos)
+ && ((sbuf_kern == 0)
+ || (sbuf_end_hpos - sbuf_kern != env->hpos))) {
+ /*
+ * lets see whether a space is needed or not
+ */
+
+ if (env->hpos-sbuf_end_hpos < space_width) {
+ add_to_sbuf(g, name);
+ sbuf_prev_hpos = sbuf_end_hpos;
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * get_html_translation - given the position of the character and its
+ * name return the device encoding for such
+ * character.
+ */
+
+const char *get_html_translation (font *f, const string &name)
+{
+ if ((0 /* nullptr */ == f) || name.empty())
+ return 0;
+ else {
+ glyph *g = name_to_glyph((char *)(name + '\0').contents());
+ if (f->contains(g))
+ return get_html_entity(f->get_code(g));
+ else
+ return 0;
+ }
+}
+
+/*
+ * get_html_entity - given a Unicode character's code point, return an
+ * HTML entity that represents the character, if the
+ * character cannot represent itself in all contexts.
+ * the return value, if not a null pointer, is
+ * allocated in a static buffer and is only valid
+ * until the next call of this function.
+ */
+static const char *get_html_entity (unsigned int code)
+{
+ if (code < UNICODE_DESC_START) {
+ switch (code) {
+ case 0x0022: return "&quot;";
+ case 0x0026: return "&amp;";
+ case 0x003C: return "&lt;";
+ case 0x003E: return "&gt;";
+ default: return 0;
+ }
+ } else {
+ switch (code) {
+ case 0x00A0: return "&nbsp;";
+ case 0x00A1: return "&iexcl;";
+ case 0x00A2: return "&cent;";
+ case 0x00A3: return "&pound;";
+ case 0x00A4: return "&curren;";
+ case 0x00A5: return "&yen;";
+ case 0x00A6: return "&brvbar;";
+ case 0x00A7: return "&sect;";
+ case 0x00A8: return "&uml;";
+ case 0x00A9: return "&copy;";
+ case 0x00AA: return "&ordf;";
+ case 0x00AB: return "&laquo;";
+ case 0x00AC: return "&not;";
+ case 0x00AE: return "&reg;";
+ case 0x00AF: return "&macr;";
+ case 0x00B0: return "&deg;";
+ case 0x00B1: return "&plusmn;";
+ case 0x00B2: return "&sup2;";
+ case 0x00B3: return "&sup3;";
+ case 0x00B4: return "&acute;";
+ case 0x00B5: return "&micro;";
+ case 0x00B6: return "&para;";
+ case 0x00B7: return "&middot;";
+ case 0x00B8: return "&cedil;";
+ case 0x00B9: return "&sup1;";
+ case 0x00BA: return "&ordm;";
+ case 0x00BB: return "&raquo;";
+ case 0x00BC: return "&frac14;";
+ case 0x00BD: return "&frac12;";
+ case 0x00BE: return "&frac34;";
+ case 0x00BF: return "&iquest;";
+ case 0x00C0: return "&Agrave;";
+ case 0x00C1: return "&Aacute;";
+ case 0x00C2: return "&Acirc;";
+ case 0x00C3: return "&Atilde;";
+ case 0x00C4: return "&Auml;";
+ case 0x00C5: return "&Aring;";
+ case 0x00C6: return "&AElig;";
+ case 0x00C7: return "&Ccedil;";
+ case 0x00C8: return "&Egrave;";
+ case 0x00C9: return "&Eacute;";
+ case 0x00CA: return "&Ecirc;";
+ case 0x00CB: return "&Euml;";
+ case 0x00CC: return "&Igrave;";
+ case 0x00CD: return "&Iacute;";
+ case 0x00CE: return "&Icirc;";
+ case 0x00CF: return "&Iuml;";
+ case 0x00D0: return "&ETH;";
+ case 0x00D1: return "&Ntilde;";
+ case 0x00D2: return "&Ograve;";
+ case 0x00D3: return "&Oacute;";
+ case 0x00D4: return "&Ocirc;";
+ case 0x00D5: return "&Otilde;";
+ case 0x00D6: return "&Ouml;";
+ case 0x00D7: return "&times;";
+ case 0x00D8: return "&Oslash;";
+ case 0x00D9: return "&Ugrave;";
+ case 0x00DA: return "&Uacute;";
+ case 0x00DB: return "&Ucirc;";
+ case 0x00DC: return "&Uuml;";
+ case 0x00DD: return "&Yacute;";
+ case 0x00DE: return "&THORN;";
+ case 0x00DF: return "&szlig;";
+ case 0x00E0: return "&agrave;";
+ case 0x00E1: return "&aacute;";
+ case 0x00E2: return "&acirc;";
+ case 0x00E3: return "&atilde;";
+ case 0x00E4: return "&auml;";
+ case 0x00E5: return "&aring;";
+ case 0x00E6: return "&aelig;";
+ case 0x00E7: return "&ccedil;";
+ case 0x00E8: return "&egrave;";
+ case 0x00E9: return "&eacute;";
+ case 0x00EA: return "&ecirc;";
+ case 0x00EB: return "&euml;";
+ case 0x00EC: return "&igrave;";
+ case 0x00ED: return "&iacute;";
+ case 0x00EE: return "&icirc;";
+ case 0x00EF: return "&iuml;";
+ case 0x00F0: return "&eth;";
+ case 0x00F1: return "&ntilde;";
+ case 0x00F2: return "&ograve;";
+ case 0x00F3: return "&oacute;";
+ case 0x00F4: return "&ocirc;";
+ case 0x00F5: return "&otilde;";
+ case 0x00F6: return "&ouml;";
+ case 0x00F7: return "&divide;";
+ case 0x00F8: return "&oslash;";
+ case 0x00F9: return "&ugrave;";
+ case 0x00FA: return "&uacute;";
+ case 0x00FB: return "&ucirc;";
+ case 0x00FC: return "&uuml;";
+ case 0x00FD: return "&yacute;";
+ case 0x00FE: return "&thorn;";
+ case 0x00FF: return "&yuml;";
+ case 0x0152: return "&OElig;";
+ case 0x0153: return "&oelig;";
+ case 0x0160: return "&Scaron;";
+ case 0x0161: return "&scaron;";
+ case 0x0178: return "&Yuml;";
+ case 0x0192: return "&fnof;";
+ case 0x0391: return "&Alpha;";
+ case 0x0392: return "&Beta;";
+ case 0x0393: return "&Gamma;";
+ case 0x0394: return "&Delta;";
+ case 0x0395: return "&Epsilon;";
+ case 0x0396: return "&Zeta;";
+ case 0x0397: return "&Eta;";
+ case 0x0398: return "&Theta;";
+ case 0x0399: return "&Iota;";
+ case 0x039A: return "&Kappa;";
+ case 0x039B: return "&Lambda;";
+ case 0x039C: return "&Mu;";
+ case 0x039D: return "&Nu;";
+ case 0x039E: return "&Xi;";
+ case 0x039F: return "&Omicron;";
+ case 0x03A0: return "&Pi;";
+ case 0x03A1: return "&Rho;";
+ case 0x03A3: return "&Sigma;";
+ case 0x03A4: return "&Tau;";
+ case 0x03A5: return "&Upsilon;";
+ case 0x03A6: return "&Phi;";
+ case 0x03A7: return "&Chi;";
+ case 0x03A8: return "&Psi;";
+ case 0x03A9: return "&Omega;";
+ case 0x03B1: return "&alpha;";
+ case 0x03B2: return "&beta;";
+ case 0x03B3: return "&gamma;";
+ case 0x03B4: return "&delta;";
+ case 0x03B5: return "&epsilon;";
+ case 0x03B6: return "&zeta;";
+ case 0x03B7: return "&eta;";
+ case 0x03B8: return "&theta;";
+ case 0x03B9: return "&iota;";
+ case 0x03BA: return "&kappa;";
+ case 0x03BB: return "&lambda;";
+ case 0x03BC: return "&mu;";
+ case 0x03BD: return "&nu;";
+ case 0x03BE: return "&xi;";
+ case 0x03BF: return "&omicron;";
+ case 0x03C0: return "&pi;";
+ case 0x03C1: return "&rho;";
+ case 0x03C2: return "&sigmaf;";
+ case 0x03C3: return "&sigma;";
+ case 0x03C4: return "&tau;";
+ case 0x03C5: return "&upsilon;";
+ case 0x03C6: return "&phi;";
+ case 0x03C7: return "&chi;";
+ case 0x03C8: return "&psi;";
+ case 0x03C9: return "&omega;";
+ case 0x03D1: return "&thetasym;";
+ case 0x03D6: return "&piv;";
+ case 0x2013: return "&ndash;";
+ case 0x2014: return "&mdash;";
+ case 0x2018: return "&lsquo;";
+ case 0x2019: return "&rsquo;";
+ case 0x201A: return "&sbquo;";
+ case 0x201C: return "&ldquo;";
+ case 0x201D: return "&rdquo;";
+ case 0x201E: return "&bdquo;";
+ case 0x2020: return "&dagger;";
+ case 0x2021: return "&Dagger;";
+ case 0x2022: return "&bull;";
+ case 0x2030: return "&permil;";
+ case 0x2032: return "&prime;";
+ case 0x2033: return "&Prime;";
+ case 0x2039: return "&lsaquo;";
+ case 0x203A: return "&rsaquo;";
+ case 0x203E: return "&oline;";
+ case 0x2044: return "&frasl;";
+ case 0x20AC: return "&euro;";
+ case 0x2111: return "&image;";
+ case 0x2118: return "&weierp;";
+ case 0x211C: return "&real;";
+ case 0x2122: return "&trade;";
+ case 0x2135: return "&alefsym;";
+ case 0x2190: return "&larr;";
+ case 0x2191: return "&uarr;";
+ case 0x2192: return "&rarr;";
+ case 0x2193: return "&darr;";
+ case 0x2194: return "&harr;";
+ case 0x21D0: return "&lArr;";
+ case 0x21D1: return "&uArr;";
+ case 0x21D2: return "&rArr;";
+ case 0x21D3: return "&dArr;";
+ case 0x21D4: return "&hArr;";
+ case 0x2200: return "&forall;";
+ case 0x2202: return "&part;";
+ case 0x2203: return "&exist;";
+ case 0x2205: return "&empty;";
+ case 0x2207: return "&nabla;";
+ case 0x2208: return "&isin;";
+ case 0x2209: return "&notin;";
+ case 0x220B: return "&ni;";
+ case 0x220F: return "&prod;";
+ case 0x2211: return "&sum;";
+ case 0x2212: return "&minus;";
+ case 0x2217: return "&lowast;";
+ case 0x221A: return "&radic;";
+ case 0x221D: return "&prop;";
+ case 0x221E: return "&infin;";
+ case 0x2220: return "&ang;";
+ case 0x2227: return "&and;";
+ case 0x2228: return "&or;";
+ case 0x2229: return "&cap;";
+ case 0x222A: return "&cup;";
+ case 0x222B: return "&int;";
+ case 0x2234: return "&there4;";
+ case 0x223C: return "&sim;";
+ case 0x2245: return "&cong;";
+ case 0x2248: return "&asymp;";
+ case 0x2260: return "&ne;";
+ case 0x2261: return "&equiv;";
+ case 0x2264: return "&le;";
+ case 0x2265: return "&ge;";
+ case 0x2282: return "&sub;";
+ case 0x2283: return "&sup;";
+ case 0x2284: return "&nsub;";
+ case 0x2286: return "&sube;";
+ case 0x2287: return "&supe;";
+ case 0x2295: return "&oplus;";
+ case 0x2297: return "&otimes;";
+ case 0x22A5: return "&perp;";
+ case 0x22C5: return "&sdot;";
+ case 0x2308: return "&lceil;";
+ case 0x2309: return "&rceil;";
+ case 0x230A: return "&lfloor;";
+ case 0x230B: return "&rfloor;";
+ case 0x2329: return "&lang;";
+ case 0x232A: return "&rang;";
+ case 0x25CA: return "&loz;";
+ case 0x2660: return "&spades;";
+ case 0x2663: return "&clubs;";
+ case 0x2665: return "&hearts;";
+ case 0x2666: return "&diams;";
+ case 0x27E8: return "&lang;";
+ case 0x27E9: return "&rang;";
+ default: return to_unicode(code);
+ }
+ }
+}
+
+/*
+ * overstrike - returns TRUE if the glyph (i, name) is going to
+ * overstrike a previous glyph in sbuf. If TRUE the font
+ * is changed to bold and the previous sbuf is flushed.
+ */
+
+int html_printer::overstrike(glyph *g, const char *name,
+ const environment *env, int w)
+{
+ if ((env->hpos < sbuf_end_hpos)
+ || ((sbuf_kern != 0) && (sbuf_end_hpos - sbuf_kern < env->hpos)))
+ {
+ /*
+ * at this point we have detected an overlap
+ */
+ if (overstrike_detected) {
+ /* already detected, remove previous glyph and use this glyph */
+ sbuf.set_length(last_sbuf_length);
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ } else {
+ /* first time we have detected an overstrike in the sbuf */
+ sbuf.set_length(last_sbuf_length); /* remove previous glyph */
+ if (! is_bold(sbuf_style.f))
+ flush_sbuf();
+ overstrike_detected = TRUE;
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * set_char - adds a character into the sbuf if it is a continuation
+ * with the previous word otherwise flush the current sbuf
+ * and add character anew.
+ */
+
+void html_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *name)
+{
+ style sty(f, env->size, env->height, env->slant, env->fontno,
+ *env->col);
+ if (sty.slant != 0) {
+ if (sty.slant > 80 || sty.slant < -80) {
+ error("slant of %1 degrees out of range", sty.slant);
+ sty.slant = 0;
+ }
+ }
+ if (((!sbuf.empty())
+ && (sty == sbuf_style)
+ && (sbuf_vpos == env->vpos))
+ && (sbuf_continuation(g, name, env, w)
+ || overstrike(g, name, env, w)))
+ return;
+
+ flush_sbuf();
+ if (0 /* nullptr */ == sbuf_style.f)
+ sbuf_style = sty;
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ sbuf_start_hpos = env->hpos;
+ sbuf_prev_hpos = env->hpos;
+ sbuf_vpos = env->vpos;
+ sbuf_style = sty;
+ sbuf_kern = 0;
+}
+
+/*
+ * set_numbered_char - handle numbered characters. Negative values are
+ * interpreted as unbreakable spaces; the value
+ * (taken positive) gives the width.
+ */
+
+void html_printer::set_numbered_char(int num, const environment *env,
+ int *widthp)
+{
+ int nbsp_width = 0;
+ if (num < 0) {
+ nbsp_width = -num;
+ num = 160; // &nbsp;
+ }
+ glyph *g = number_to_glyph(num);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return;
+ }
+ font *f = font_table[fn];
+ if (f == 0) {
+ error("no font mounted at position %1", fn);
+ return;
+ }
+ if (!f->contains(g)) {
+ error("font '%1' does not contain numbered character %2",
+ f->get_name(),
+ num);
+ return;
+ }
+ int w;
+ if (nbsp_width)
+ w = nbsp_width;
+ else
+ w = f->get_width(g, env->size);
+ w = round_width(w);
+ if (widthp)
+ *widthp = w;
+ set_char(g, f, env, w, 0);
+}
+
+glyph *html_printer::set_char_and_width(const char *nm,
+ const environment *env,
+ int *widthp, font **f)
+{
+ glyph *g = name_to_glyph(nm);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return UNDEFINED_GLYPH;
+ }
+ *f = font_table[fn];
+ if (*f == 0) {
+ error("no font mounted at position %1", fn);
+ return UNDEFINED_GLYPH;
+ }
+ if (!(*f)->contains(g)) {
+ if (nm[0] != '\0' && nm[1] == '\0')
+ error("font '%1' does not contain ordinary character '%2'",
+ (*f)->get_name(), nm[0]);
+ else
+ error("font '%1' does not contain special character '%2'",
+ (*f)->get_name(), nm);
+ return UNDEFINED_GLYPH;
+ }
+ int w = (*f)->get_width(g, env->size);
+ w = round_width(w);
+ if (widthp)
+ *widthp = w;
+ return g;
+}
+
+/*
+ * write_title - writes the title to this document
+ */
+
+void html_printer::write_title (int in_head)
+{
+ if (title.has_been_found) {
+ if (in_head) {
+ html.put_string("<title>");
+ html.put_string(title.text);
+ html.put_string("</title>").nl().nl();
+ } else {
+ title.has_been_written = TRUE;
+ if (title.with_h1) {
+ if (dialect == xhtml)
+ html.put_string("<h1>");
+ else
+ html.put_string("<h1 align=\"center\">");
+ html.put_string(title.text);
+ html.put_string("</h1>").nl().nl();
+ }
+ }
+ } else if (in_head) {
+ // place empty title tags to help conform to 'tidy'
+ html.put_string("<title></title>").nl();
+ }
+}
+
+/*
+ * write_rule - emits HTML rule element if the auto_rule is TRUE.
+ */
+
+static void write_rule (void)
+{
+ if (auto_rule) {
+ if (dialect == xhtml)
+ fputs("<hr/>\n", stdout);
+ else
+ fputs("<hr>\n", stdout);
+ }
+}
+
+void html_printer::begin_page(int n)
+{
+ page_number = n;
+#if defined(DEBUGGING)
+ html.begin_comment("Page: ")
+ .put_string(i_to_a(page_number)).end_comment();;
+#endif
+ no_of_printed_pages++;
+
+ output_style.f = 0;
+ output_style.point_size= -1;
+ output_space_code = 32;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ output_hpos = -1;
+ output_vpos = -1;
+ output_vpos_max = -1;
+ current_paragraph = new html_text(&html, dialect);
+ do_indent(get_troff_indent(), pageoffset, linelength);
+ current_paragraph->do_para("", FALSE);
+}
+
+void html_printer::end_page(int)
+{
+ flush_sbuf();
+ flush_page();
+}
+
+font *html_printer::make_font(const char *nm)
+{
+ return html_font::load_html_font(nm);
+}
+
+void html_printer::do_body (void)
+{
+ if (0 /* nullptr */ == background)
+ fputs("<body>\n\n", stdout);
+ else {
+ char buf[(INT_HEXDIGITS * 3) + 1];
+ unsigned int r, g, b;
+
+ background->get_rgb(&r, &g, &b);
+ // we have to scale 0..0xFFFF to 0..0xFF
+ sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
+
+ fputs("<body bgcolor=\"#", stdout);
+ fputs(buf, stdout);
+ fputs("\">\n\n", stdout);
+ }
+}
+
+/*
+ * emit_link - generates: <a href="to">name</a>
+ */
+
+void html_printer::emit_link (const string &to, const char *name)
+{
+ fputs("<a href=\"", stdout);
+ fputs(to.contents(), stdout);
+ fputs("\">", stdout);
+ fputs(name, stdout);
+ fputs("</a>", stdout);
+}
+
+/*
+ * write_navigation - writes out the links which navigate between
+ * file fragments.
+ */
+
+void html_printer::write_navigation (const string &top,
+ const string &prev,
+ const string &next,
+ const string &current)
+{
+ int need_bar = FALSE;
+
+ if (multiple_files) {
+ current_paragraph->done_para();
+ write_rule();
+ if (groff_sig)
+ fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n"
+ "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n"
+ "<colgroup><col class=\"left\"></col>"
+ "<col class=\"right\"></col></colgroup>\n"
+ "<tr><td class=\"left\">", stdout);
+ handle_valid_flag(FALSE);
+ fputs("[ ", stdout);
+ if ((strcmp(prev.contents(), "") != 0)
+ && prev != top
+ && prev != current) {
+ emit_link(prev, "prev");
+ need_bar = TRUE;
+ }
+ if ((strcmp(next.contents(), "") != 0)
+ && next != top
+ && next != current) {
+ if (need_bar)
+ fputs(" | ", stdout);
+ emit_link(next, "next");
+ need_bar = TRUE;
+ }
+ if (top != "<standard input>"
+ && (strcmp(top.contents(), "") != 0)
+ && top != current) {
+ if (need_bar)
+ fputs(" | ", stdout);
+ emit_link(top, "top");
+ }
+ fputs(" ]\n", stdout);
+ if (groff_sig) {
+ fputs("</td><td class=\"right\"><i><small>"
+ "This document was produced using "
+ "<a href=\"http://www.gnu.org/software/groff/\">"
+ "groff-", stdout);
+ fputs(Version_string, stdout);
+ fputs("</a>.</small></i></td></tr></table>\n", stdout);
+ }
+ write_rule();
+ }
+}
+
+/*
+ * do_file_components - scan the file list copying each temporary file
+ * in turn. This has twofold use: firstly to emit
+ * section heading links, between file fragments
+ * if required and secondly to generate jobname
+ * file fragments if required.
+ */
+
+void html_printer::do_file_components (void)
+{
+ int fragment_no = 1;
+ string top;
+ string prev;
+ string next;
+ string current;
+
+ file_list.start_of_list();
+ top = string(job_name);
+ if (dialect == xhtml)
+ top += string(".xhtml");
+ else
+ top += string(".html");
+ top += '\0';
+ next = file_list.next_file_name();
+ next += '\0';
+ current = next;
+ while (file_list.get_file() != 0) {
+ if (fseek(file_list.get_file(), 0L, 0) < 0)
+ fatal("fseek on temporary file failed");
+ html.copy_file(file_list.get_file());
+ fclose(file_list.get_file());
+ file_list.move_next();
+ if (file_list.is_new_output_file()) {
+#ifdef LONG_FOR_TIME_T
+ long t;
+#else
+ time_t t;
+#endif
+
+ if (fragment_no > 1)
+ write_navigation(top, prev, next, current);
+ prev = current;
+ current = next;
+ next = file_list.next_file_name();
+ next += '\0';
+ string split_file = file_list.file_name();
+ split_file += '\0';
+ fflush(stdout);
+ if (!freopen(split_file.contents(), "w", stdout)) {
+ fatal("unable to reopen standard output stream: %1",
+ strerror(errno));
+ }
+ fragment_no++;
+ if (dialect == xhtml)
+ writeHeadMetaStyle();
+
+ if (do_write_creator_comment) {
+ html.begin_comment("Creator : ")
+ .put_string("groff ")
+ .put_string("version ")
+ .put_string(Version_string)
+ .end_comment();
+ }
+
+ if (do_write_date_comment) {
+ t = current_time();
+ html.begin_comment("CreationDate: ")
+ .put_string(ctime(&t), strlen(ctime(&t))-1)
+ .end_comment();
+ }
+
+ if (dialect == html4)
+ writeHeadMetaStyle();
+
+ html.put_string("<title>");
+ html.put_string(split_file.contents());
+ html.put_string("</title>").nl().nl();
+
+ fputs(head_info.contents(), stdout);
+ fputs("</head>\n", stdout);
+ write_navigation(top, prev, next, current);
+ }
+ if (file_list.are_links_required())
+ header.write_headings(stdout, TRUE);
+ }
+ if (fragment_no > 1)
+ write_navigation(top, prev, next, current);
+ else {
+ assert(current_paragraph != 0);
+ current_paragraph->done_para();
+ write_rule();
+ if (valid_flag) {
+ if (groff_sig)
+ fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n"
+ "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n"
+ "<colgroup><col class=\"left\"></col>"
+ "<col class=\"right\"></col></colgroup>\n"
+ "<tr><td class=\"left\">", stdout);
+ handle_valid_flag(TRUE);
+ if (groff_sig) {
+ fputs("</td><td class=\"right\"><i><small>"
+ "This document was produced using "
+ "<a href=\"http://www.gnu.org/software/groff/\">"
+ "groff-", stdout);
+ fputs(Version_string, stdout);
+ fputs("</a>.</small></i></td></tr></table>\n", stdout);
+ }
+ write_rule();
+ }
+ }
+}
+
+/*
+ * writeHeadMetaStyle - emits the <head> <meta> and <style> tags and
+ * related information.
+ */
+
+void html_printer::writeHeadMetaStyle (void)
+{
+ if (dialect == html4) {
+ fputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional"
+ "//EN\"\n", stdout);
+ fputs("\"http://www.w3.org/TR/html4/loose.dtd\">\n", stdout);
+ fputs("<html>\n", stdout);
+ fputs("<head>\n", stdout);
+ fputs("<meta name=\"generator\" "
+ "content=\"groff -Thtml, see www.gnu.org\">\n", stdout);
+ fputs("<meta http-equiv=\"Content-Type\" "
+ "content=\"text/html; charset=US-ASCII\">\n", stdout);
+ fputs("<meta name=\"Content-Style\" content=\"text/css\">\n",
+ stdout);
+ fputs("<style type=\"text/css\">\n", stdout);
+ }
+ else {
+ fputs("<?xml version=\"1.0\" encoding=\"us-ascii\"?>\n", stdout);
+ fputs("<!DOCTYPE html PUBLIC \"-//W3C//"
+ "DTD XHTML 1.1 plus MathML 2.0//EN\"\n", stdout);
+ fputs(" \"http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd\"\n",
+ stdout);
+ fputs(" [<!ENTITY mathml \"http://www.w3.org/1998/Math/"
+ "MathML\">]>\n", stdout);
+
+ fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\" "
+ "xml:lang=\"en\">\n", stdout);
+ fputs("<head>\n", stdout);
+ fputs("<meta name=\"generator\" "
+ "content=\"groff -Txhtml, see www.gnu.org\"/>\n", stdout);
+ fputs("<meta http-equiv=\"Content-Type\" "
+ "content=\"text/html; charset=US-ASCII\"/>\n", stdout);
+ fputs("<meta name=\"Content-Style\" content=\"text/css\"/>\n",
+ stdout);
+ fputs("<style type=\"text/css\">\n", stdout);
+ fputs(" .center { text-align: center }\n", stdout);
+ fputs(" .right { text-align: right }\n", stdout);
+ }
+ fputs(" p { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" pre { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" table { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" h1 { text-align: center }\n", stdout);
+ fputs("</style>\n", stdout);
+}
+
+html_printer::~html_printer()
+{
+#ifdef LONG_FOR_TIME_T
+ long t;
+#else
+ time_t t;
+#endif
+
+ if (current_paragraph)
+ current_paragraph->flush_text();
+ html.end_line();
+ html.set_file(stdout);
+
+ if (dialect == xhtml)
+ writeHeadMetaStyle();
+
+ if (do_write_creator_comment) {
+ html.begin_comment("Creator : ")
+ .put_string("groff ")
+ .put_string("version ")
+ .put_string(Version_string)
+ .end_comment();
+ }
+
+ if (do_write_date_comment) {
+ t = current_time();
+ html.begin_comment("CreationDate: ")
+ .put_string(ctime(&t), strlen(ctime(&t))-1)
+ .end_comment();
+ }
+
+ if (dialect == html4)
+ writeHeadMetaStyle();
+
+ write_title(TRUE);
+ head_info += '\0';
+ fputs(head_info.contents(), stdout);
+ fputs("</head>\n", stdout);
+ do_body();
+
+ write_title(FALSE);
+ header.write_headings(stdout, FALSE);
+ write_rule();
+#if defined(DEBUGGING)
+ html.begin_comment("Total number of pages: ")
+ .put_string(i_to_a(no_of_printed_pages)).end_comment();
+#endif
+ html.end_line();
+ html.end_line();
+
+ if (multiple_files) {
+ fputs("</body>\n", stdout);
+ fputs("</html>\n", stdout);
+ do_file_components();
+ } else {
+ do_file_components();
+ fputs("</body>\n", stdout);
+ fputs("</html>\n", stdout);
+ }
+}
+
+/*
+ * get_str - returns a duplicate of string, s. The duplicate
+ * string is terminated at the next ',' or ']'.
+ */
+
+static char *get_str (const char *s, char **n)
+{
+ int i = 0;
+ char *v;
+
+ while ((s[i] != (char)0) && (s[i] != ',') && (s[i] != ']'))
+ i++;
+ if (i>0) {
+ v = new char[i+1];
+ memcpy(v, s, i+1);
+ v[i] = (char)0;
+ if (s[i] == ',')
+ (*n) = (char *)&s[i+1];
+ else
+ (*n) = (char *)&s[i];
+ return v;
+ }
+ if (s[i] == ',')
+ (*n) = (char *)&s[1];
+ else
+ (*n) = (char *)s;
+ return 0;
+}
+
+/*
+ * make_val - creates a string from if s is a null pointer.
+ */
+
+char *make_val (char *s, int v, char *id, char *f, char *l)
+{
+ if (0 /* nullptr */ == s) {
+ char buf[30];
+
+ sprintf(buf, "%d", v);
+ return strsave(buf);
+ }
+ else {
+ /*
+ * check that value, s, is the same as, v.
+ */
+ char *t = s;
+
+ while (*t == '=')
+ t++;
+ if (atoi(t) != v) {
+ if (0 /* nullptr */ == f)
+ f = (char *)"stdin";
+ if (0 /* nullptr */ == l)
+ l = (char *)"<none>";
+ fprintf(stderr, "%s:%s: grohtml assertion failed at id%s; "
+ "expected %d, got %s\n", f, l, id, v, s);
+ }
+ return s;
+ }
+}
+
+/*
+ * handle_assertion - handles the assertions created via .www:ASSERT
+ * in www.tmac. See www.tmac for examples. This
+ * method should be called as we are parsing the
+ * ditroff input. It checks the x, y position
+ * assertions. It does _not_ check the troff state
+ * assertions as these are unknown at this point.
+ */
+
+void html_printer::handle_assertion (int minv, int minh,
+ int maxv, int maxh, const char *s)
+{
+ char *n;
+ char *cmd = get_str(s, &n);
+ char *id = get_str(n, &n);
+ char *val = get_str(n, &n);
+ char *file= get_str(n, &n);
+ char *line= get_str(n, &n);
+
+ if (strcmp(cmd, "assertion:[x") == 0)
+ as.addx(cmd, id, make_val(val, minh, id, file, line), file, line);
+ else if (strcmp(cmd, "assertion:[y") == 0)
+ as.addy(cmd, id, make_val(val, minv, id, file, line), file, line);
+ else
+ if (strncmp(cmd, "assertion:[", strlen("assertion:[")) == 0)
+ page_contents->add_tag(&sbuf_style, string(s),
+ line_number, minv, minh, maxv, maxh);
+}
+
+/*
+ * build_state_assertion - builds the troff state assertions.
+ */
+
+void html_printer::handle_state_assertion (text_glob *g)
+{
+ if (g != 0 && g->is_a_tag()
+ && (strncmp(g->text_string, "assertion:[", 11) == 0)) {
+ char *n = (char *)&g->text_string[11];
+ char *cmd = get_str(n, &n);
+ char *val = get_str(n, &n);
+ (void)get_str(n, &n); // unused
+ char *file= get_str(n, &n);
+ char *line= get_str(n, &n);
+
+ as.build(cmd, val, file, line);
+ }
+}
+
+/*
+ * special - handle all x X requests from troff. For post-html they
+ * allow users to pass raw HTML commands, turn auto linked
+ * headings off/on, and so forth.
+ */
+
+void html_printer::special(char *s, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+ if (s != 0) {
+ flush_sbuf();
+ if (env->fontno >= 0) {
+ style sty(get_font_from_index(env->fontno), env->size,
+ env->height, env->slant, env->fontno, *env->col);
+ sbuf_style = sty;
+ }
+
+ if (strncmp(s, "html:", 5) == 0) {
+ int r=font::res; /* resolution of the device */
+ font *f=sbuf_style.f;
+
+ if (0 /* nullptr */ == f)
+ f = font::load_font("TR");
+
+ /*
+ * pass rest of string through to html output during flush
+ */
+ page_contents->add_and_encode(&sbuf_style, string(&s[5]),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos,
+ FALSE);
+
+ /*
+ * assume that the html command has no width, if it does then
+ * hopefully troff will have fudged this in a macro by requesting
+ * that the formatting move right by the appropriate amount.
+ */
+ } else if ((strncmp(s, "html</p>:", 9) == 0) ||
+ (strncmp(s, "html<?p>:", 9) == 0) ||
+ (strncmp(s, "math<?p>:", 9) == 0)) {
+ int r=font::res; /* resolution of the device */
+ font *f=sbuf_style.f;
+ string t;
+
+ if (0 /* nullptr */ == f)
+ f = font::load_font("TR");
+
+ if (strncmp(s, "math<?p>:", 9) == 0) {
+ if (strncmp((char *)&s[9], "<math>", 6) == 0) {
+ s[9] = '\0';
+ t = s;
+ t += "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">";
+ t += (char *)&s[15];
+ t += '\0';
+ s = (char *)&t[0];
+ }
+ }
+
+ /*
+ * need to pass all of string through to html output during flush
+ */
+ page_contents->add_and_encode(&sbuf_style, string(s),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos,
+ TRUE);
+
+ /*
+ * assume that the html command has no width, if it does then
+ * hopefully troff will have fudged this in a macro by
+ * requesting that the formatting move right by the appropriate
+ * amount.
+ */
+
+ } else if (strncmp(s, "index:", 6) == 0) {
+ cutoff_heading = atoi(&s[6]);
+ } else if (strncmp(s, "assertion:[", 11) == 0) {
+ int r=font::res; /* resolution of the device */
+
+ handle_assertion(env->vpos-env->size*r/72, env->hpos,
+ env->vpos, env->hpos, s);
+ }
+ }
+}
+
+/*
+ * devtag - handles device troff tags sent from the 'troff'.
+ * These include the troff state machine tags:
+ * .br, .sp, .in, .tl, .ll etc
+ *
+ * (see man 5 grohtml_tags).
+ */
+
+void html_printer::devtag (char *s, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+
+ if (s != 0) {
+ flush_sbuf();
+ if (env->fontno >= 0) {
+ style sty(get_font_from_index(env->fontno), env->size,
+ env->height, env->slant, env->fontno, *env->col);
+ sbuf_style = sty;
+ }
+
+ if (strncmp(s, "devtag:", strlen("devtag:")) == 0) {
+ int r=font::res; /* resolution of the device */
+
+ page_contents->add_tag(&sbuf_style, string(s),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos);
+ }
+ }
+}
+
+
+/*
+ * taken from number.cpp in src/roff/troff, [hunits::hunits(units x)]
+ */
+
+int html_printer::round_width(int x)
+{
+ int r = font::hor;
+ int n;
+
+ // don't depend on rounding direction for division of negative ints
+ if (r == 1)
+ n = x;
+ else
+ n = (x < 0
+ ? -((-x + r/2 - 1)/r)
+ : (x + r/2 - 1)/r);
+ return n * r;
+}
+
+/*
+ * handle_valid_flag - emits a valid XHTML 1.1 or HTML 4.01 button,
+ * provided -V was supplied on the command line.
+ */
+
+void html_printer::handle_valid_flag (int needs_para)
+{
+ if (valid_flag) {
+ if (needs_para)
+ fputs("<p>", stdout);
+ if (dialect == xhtml)
+ fputs("<a href=\"http://validator.w3.org/check?uri=referer\">"
+ "<img src=\"http://www.w3.org/Icons/valid-xhtml11-blue\" "
+ "alt=\"Valid XHTML 1.1 Transitional\" "
+ "height=\"31\" width=\"88\" /></a>\n", stdout);
+ else
+ fputs("<a href=\"http://validator.w3.org/check?uri=referer\">"
+ "<img src=\"http://www.w3.org/Icons/valid-html401-blue\" "
+ "alt=\"Valid HTML 4.01 Transitional\" "
+ "height=\"31\" width=\"88\"></a>\n", stdout);
+ if (needs_para)
+ fputs("</p>", stdout);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv,
+ "a:bCdD:eF:g:Ghi:I:j:lno:prs:S:vVx:y", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'a':
+ /* text antialiasing bits - handled by pre-html */
+ break;
+ case 'b':
+ // set background color to white
+ default_background = new color;
+ default_background->set_gray(color::MAX_COLOR_VAL);
+ break;
+ case 'C':
+ // Don't write CreationDate HTML comments.
+ do_write_date_comment = FALSE;
+ break;
+ case 'd':
+ /* handled by pre-html */
+ break;
+ case 'D':
+ /* handled by pre-html */
+ break;
+ case 'e':
+ /* handled by pre-html */
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'g':
+ /* graphic antialiasing bits - handled by pre-html */
+ break;
+ case 'G':
+ // Don't write Creator HTML comments.
+ do_write_creator_comment = FALSE;
+ break;
+ case 'h':
+ /* do not use the Hn headings of html, but manufacture our own */
+ manufacture_headings = TRUE;
+ break;
+ case 'i':
+ /* handled by pre-html */
+ break;
+ case 'I':
+ /* handled by pre-html */
+ break;
+ case 'j':
+ multiple_files = TRUE;
+ job_name = optarg;
+ break;
+ case 'l':
+ auto_links = FALSE;
+ break;
+ case 'n':
+ simple_anchors = TRUE;
+ break;
+ case 'o':
+ /* handled by pre-html */
+ break;
+ case 'p':
+ /* handled by pre-html */
+ break;
+ case 'r':
+ auto_rule = FALSE;
+ break;
+ case 's':
+ base_point_size = atoi(optarg);
+ break;
+ case 'S':
+ split_level = atoi(optarg) + 1;
+ break;
+ case 'v':
+ printf("GNU post-grohtml (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'V':
+ valid_flag = TRUE;
+ break;
+ case 'x':
+ if (strcmp(optarg, "x") == 0) {
+ dialect = xhtml;
+ simple_anchors = TRUE;
+ } else if (strcmp(optarg, "4") == 0)
+ dialect = html4;
+ else
+ warning("unsupported HTML dialect: '%1'", optarg);
+ break;
+ case 'y':
+ groff_sig = TRUE;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ if (optind >= argc) {
+ do_file("-");
+ } else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-bCGhlnrVy] [-F font-directory] [-j output-stem]"
+" [-s base-type-size] [-S heading-level] [-x html-dialect] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grolbp/charset.h b/src/devices/grolbp/charset.h
new file mode 100644
index 0000000..c4e43a0
--- /dev/null
+++ b/src/devices/grolbp/charset.h
@@ -0,0 +1,89 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Francisco Andrés Verdú <pandres@dragonet.es> with many ideas
+ taken from the other groff drivers.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// Definition of the WP54 character set
+
+unsigned char symset[] = {
+0x57,0x50,0x35,0x34,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61,
+0x72,0x64,0x65,0x2d,0x42,0x6f,0x6f,0x6b,0x00,0x41,0x76,
+0x61,0x6e,0x74,0x47,0x61,0x72,0x64,0x65,0x2d,0x44,0x65,
+0x6d,0x69,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61,0x72,
+0x64,0x65,0x2d,0x42,0x6f,0x6f,0x6b,0x4f,0x62,0x6c,0x69,
+0x71,0x75,0x65,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61,
+0x72,0x64,0x65,0x2d,0x44,0x65,0x6d,0x69,0x4f,0x62,0x6c,
+0x69,0x71,0x75,0x65,0x00,0x42,0x6f,0x6f,0x6b,0x6d,0x61,
+0x6e,0x2d,0x4c,0x69,0x67,0x68,0x74,0x00,0x42,0x6f,0x6f,
+0x6b,0x6d,0x61,0x6e,0x2d,0x44,0x65,0x6d,0x69,0x00,0x42,
+0x6f,0x6f,0x6b,0x6d,0x61,0x6e,0x2d,0x4c,0x69,0x67,0x68,
+0x74,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x42,0x6f,0x6f,
+0x6b,0x6d,0x61,0x6e,0x2d,0x44,0x65,0x6d,0x69,0x49,0x74,
+0x61,0x6c,0x69,0x63,0x00,0x43,0x65,0x6e,0x74,0x75,0x72,
+0x79,0x53,0x63,0x68,0x6c,0x62,0x6b,0x2d,0x52,0x6f,0x6d,
+0x61,0x6e,0x00,0x43,0x65,0x6e,0x74,0x75,0x72,0x79,0x53,
+0x63,0x68,0x6c,0x62,0x6b,0x2d,0x42,0x6f,0x6c,0x64,0x00,
+0x43,0x65,0x6e,0x74,0x75,0x72,0x79,0x53,0x63,0x68,0x6c,
+0x62,0x6b,0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x43,
+0x65,0x6e,0x74,0x75,0x72,0x79,0x53,0x63,0x68,0x6c,0x62,
+0x6b,0x2d,0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,0x69,
+0x63,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x52,0x6f,0x6d,
+0x61,0x6e,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x42,0x6f,
+0x6c,0x64,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x49,0x74,
+0x61,0x6c,0x69,0x63,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,
+0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,
+0x53,0x77,0x69,0x73,0x73,0x00,0x53,0x77,0x69,0x73,0x73,
+0x2d,0x42,0x6f,0x6c,0x64,0x00,0x53,0x77,0x69,0x73,0x73,
+0x2d,0x4f,0x62,0x6c,0x69,0x71,0x75,0x65,0x00,0x53,0x77,
+0x69,0x73,0x73,0x2d,0x42,0x6f,0x6c,0x64,0x4f,0x62,0x6c,
+0x69,0x71,0x75,0x65,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,
+0x4e,0x61,0x72,0x72,0x6f,0x77,0x00,0x53,0x77,0x69,0x73,
+0x73,0x2d,0x4e,0x61,0x72,0x72,0x6f,0x77,0x2d,0x42,0x6f,
+0x6c,0x64,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,0x4e,0x61,
+0x72,0x72,0x6f,0x77,0x2d,0x4f,0x62,0x6c,0x69,0x71,0x75,
+0x65,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,0x4e,0x61,0x72,
+0x72,0x6f,0x77,0x2d,0x42,0x6f,0x6c,0x64,0x4f,0x62,0x6c,
+0x69,0x71,0x75,0x65,0x00,0x5a,0x61,0x70,0x66,0x43,0x61,
+0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x2d,
+0x52,0x6f,0x6d,0x61,0x6e,0x00,0x5a,0x61,0x70,0x66,0x43,
+0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63,
+0x2d,0x42,0x6f,0x6c,0x64,0x00,0x5a,0x61,0x70,0x66,0x43,
+0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63,
+0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x5a,0x61,0x70,
+0x66,0x43,0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,
+0x69,0x63,0x2d,0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,
+0x69,0x63,0x00,0x5a,0x61,0x70,0x66,0x43,0x68,0x61,0x6e,
+0x63,0x65,0x72,0x79,0x2d,0x4d,0x65,0x64,0x69,0x75,0x6d,
+0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x00,0x09,0x00,0x0A,
+0x00,0x0B,0x00,0x0E,0x00,0x14,0x00,0x17,0x00,0x18,0x00,
+0x1F,0x00,0x20,0x00,0x36,0x00,0x37,0x00,0x38,0x00,0x45,0x00,
+0x47,0x00,0x48,0x00,0x80,0x00,0x82,0x00,0x83,0x00,0x84,
+0x00,0x85,0x00,0x87,0x00,0x8B,0x00,0x8C,0x00,0x8D,0x00,0x8E,
+0x00,0x8F,0x00,0x90,0x00,0x91,0x00,0x92,0x00,0x95,0x00,0x96,
+0x00,0x97,0x00,0x98,0x00,0x99,0x00,0x9C,0x00,0x9E,0x00,
+0x9F,0x00,0xA0,0x00,0xA1,0x00,0xA2,0x00,0xA3,0x00,0xCB,0x00,
+0xCC,0x00,0xCD,0x00,0xCE,0x00,0xD1,0x00,0xD3,0x00,0xD4,
+0x00,0xD5,0x00,0xD6,0x00,0xFA,0x00,0xFB,0x00,0xFC,0x00,0xFD,
+0x00,0xCF,0x00,0x26,0x00,0x7E,0x03,0x05,0x00,0xA5,0x00,
+0xA6,0x00,0xA8,0x00,0xAA,0x00,0xAD,0x00,0xAE,0x00,0xAF,0x00,
+0xB0,0x00,0xB1,0x00,0xB2,0x00,0xB3,0x00,0xB5,0x00,0xB6,0x00,
+0xB8,0x00,0xB9,0x00,0xBA,0x00,0xBB,0x00,0xBC,0x00,0xBE,
+0x00,0xBF,0x00,0xC0,0x00,0xC1,0x00,0xC6,0x00,0xDC,0x00,0xEB,
+0x00,0xEC,0x00,0xF2,0x00,0xF3,0x00,0x15,0x00,0x16,0x00,
+0x86
+};
diff --git a/src/devices/grolbp/grolbp.1.man b/src/devices/grolbp/grolbp.1.man
new file mode 100644
index 0000000..c8cda76
--- /dev/null
+++ b/src/devices/grolbp/grolbp.1.man
@@ -0,0 +1,504 @@
+'\" t
+.TH grolbp @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grolbp \-
+.I groff
+output driver for Canon CaPSL printers
+.
+.
+.\" Modified from grolj4 man page by Francisco Andrés Verdú
+.\" <pandres@dragonet.es> for the grolbp program.
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1994-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grolbp_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grolbp
+.RB [ \-l ]
+.RB [ \-c\~\c
+.IR num-copies ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-o\~\c
+.IR orientation ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-w\~\c
+.IR width ]
+.RI [ file\~ .\|.\|.]
+.
+.SY grolbp
+[\c
+.BI \-\-copies= num-copies\c
+] [\c
+.BI \-\-fontdir= font-directory\c
+] [\c
+.B \-\-landscape\c
+] [\c
+.BI \-\-linewidth= width\c
+] [\c
+.BI \-\-orientation= orientation\c
+] [\c
+.BI \-\-papersize= paper-format\c
+]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grolbp
+.B \-h
+.
+.SY grolbp
+.B \-\-help
+.YS
+.
+.
+.SY grolbp
+.B \-v
+.
+.SY grolbp
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+This GNU
+.I roff
+output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into a CaPSL and VDM format suitable for Canon LBP-4 and LBP-8 printers.
+.
+Normally,
+.I grolbp
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~lbp \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grolbp .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grolbp
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+The driver supports the Dutch,
+Swiss,
+and Swiss-Narrow scalable typefaces,
+each in the regular,
+bold,
+italic,
+and bold-italic styles.
+.
+Additionally,
+the bitmapped,
+monospaced Courier and Elite typefaces are available in regular,
+bold,
+and
+italic styles;
+Courier at 8 and 12 points,
+Elite at 8 and 10 points.
+.
+The following chart summarizes the
+.I groff
+font names used to access them.
+.
+.
+.P
+.TS
+tab(|) allbox center;
+Cb Cb Cb Cb Cb
+L L L L L
+.
+Typeface | Roman | Bold | Italic | Bold-Italic
+Dutch | TR | TB | TI | TBI
+Swiss | HR | HB | HI | HBI
+Swiss Narrow | HNR | HNB | HNI | HNBI
+Courier | CR | CB | CI |
+Elite | ER | EB | EI |
+.TE
+.
+.
+.\" ====================================================================
+.SS "Paper format, orientation, and device description file"
+.\" ====================================================================
+.
+.I grolbp
+supports paper formats
+.RB \[lq] A4 \[rq],
+.RB \[lq] letter \[rq],
+.RB \[lq] legal \[rq],
+and
+.RB \[lq] executive \[rq].
+.
+These are matched case-insensitively.
+.
+The
+.BR \-p ,
+.B \-\-papersize
+option overrides any setting in the device description file
+.IR DESC .
+.
+If neither specifies a paper format,
+A4 is assumed.
+.
+.
+.P
+In its
+.I DESC
+file,
+.I grolbp
+(case-insensitively) recognizes an
+.B orientation
+directive accepting one mandatory argument,
+.B portrait
+or
+.BR landscape .
+.
+The first valid orientation directive encountered controls.
+.\" XXX: This is inconsistent with other description file processing.
+.
+The
+.BR \-l ,
+.BR \-o ,
+and
+.B \-\-orientation
+command-line options
+override any setting in
+.IR DESC .
+.
+If none of the foregoing specify the orientation,
+portrait is assumed.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+In addition to the font description file directives documented in
+.MR groff_font @MAN5EXT@ ,
+.I grolbp
+recognizes
+.BR lbpname ,
+which maps the
+.I groff
+font name to the font name used internally by the printer.
+.
+Its syntax is as follows.
+.RS
+.EX
+.RI lbpname\~ printer-font-name
+.EE
+.RE
+.
+.
+.BR lbpname 's
+argument is case-sensitive.
+.
+The printer's font names are encoded as follows.
+.
+.
+.P
+For bitmapped fonts,
+.I printer-font_name
+has the form
+.RS
+.EX
+.RI N\[la] base-font-name \[ra]\[la] font-style \[ra]
+.EE
+.RE
+.I base-font-name
+is the font name as it appears in the printer's font listings without
+the first letter,
+up to
+(but not including)
+the font size.
+.
+.I font-style
+can be one of the letters
+.BR R ,
+.BR I ,
+or
+.BR B ,
+.\" The bold-italic style apparently was not supported for bitmap fonts.
+indicating the roman,
+italic,
+and bold styles,
+respectively.
+.
+For instance,
+if the printer's \[lq]font listing A\[rq]
+shows \[lq]Nelite12I.ISO_USA\[rq],
+the corresponding entry in the
+.I groff
+font description file is
+.RS
+.EX
+lbpname NeliteI
+.EE
+.RE
+.
+You may need to modify
+.I grolbp
+to add support for new bitmapped fonts,
+since the available font names and font sizes of bitmapped fonts
+(as documented above)
+are hard-coded into the program.
+.
+.
+.P
+For scalable fonts,
+.I printer-font-name
+is identical to the font name as it appears in the printer's \[lq]font
+listing A\[rq].
+.
+For instance,
+to select the \[lq]Swiss\[rq] font in bold-italic style,
+which appears in the font listing
+as \%\[lq]Swiss\-BoldOblique\[rq],
+.RS
+.EX
+lbpname Swiss\-BoldOblique
+.EE
+.RE
+is the required directive,
+and this is what we find in the
+.I groff
+font description file
+.I HBI
+for the
+.B lbp
+device.
+.
+.
+.\" ====================================================================
+.SS "Drawing commands"
+.\" ====================================================================
+.
+For compatibility with
+.MR grolj4 @MAN1EXT@ ,
+an additional drawing command is available.
+.
+.
+.TP
+.BI \[rs]D\[aq]R\~ "dh dv" \[aq]
+Draw a rule
+(solid black rectangle)
+with one corner at the drawing position,
+and the diagonally opposite corner at the drawing position
+.RI +( dh , dv ).
+.\" XXX , at which the drawing position will be afterward. ?
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-c " num-copies"
+.TQ
+.BI \-\-copies= num-copies
+Produce
+.I num-copies
+copies of each page.
+.
+.
+.TP
+.BI \-F " font-directory"
+.TQ
+.BI \-\-fontdir= font-directory
+Prepend directory
+.RI font-directory /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR lbp .
+.
+.
+.TP
+.B \-l
+.TQ
+.B \-\-landscape
+Format the document in landscape orientation.
+.
+.
+.TP
+.BI \-o " orientation"
+.TQ
+.BI \-\-orientation= orientation
+Format the document in the given
+.IR orientation ,
+which must be
+.RB \%\[lq] portrait \[rq]
+or
+.RB \%\[lq] landscape \[rq].
+.
+.
+.TP
+.BI \-p " paper-format"
+.TQ
+.BI \-\-papersize= paper-format
+Set the paper format to
+.IR paper-format ,
+which must be a valid paper format as described above.
+.
+.
+.TP
+.BI \-w " width"
+.TQ
+.BI \-\-linewidth= width
+Set the default line thickness to
+.I width
+thousandths of an em;
+the default is
+.B 40
+(0.04\~em).
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devlbp/\:DESC
+describes the
+.B lbp
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devlbp/ F
+describes the font known
+.RI as\~ F
+on device
+.BR lbp .
+.
+.
+.TP
+.I @MACRODIR@/\:lbp\:.tmac
+defines macros for use with the
+.B lbp
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B lbp
+output device is selected.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grolbp_1_man_C]
+.do rr *groff_grolbp_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grolbp/grolbp.am b/src/devices/grolbp/grolbp.am
new file mode 100644
index 0000000..3ee3a0a
--- /dev/null
+++ b/src/devices/grolbp/grolbp.am
@@ -0,0 +1,36 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grolbp
+grolbp_SOURCES = \
+ src/devices/grolbp/lbp.cpp \
+ src/devices/grolbp/lbp.h \
+ src/devices/grolbp/charset.h
+
+grolbp_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grolbp/grolbp.1
+EXTRA_DIST += src/devices/grolbp/grolbp.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grolbp/lbp.cpp b/src/devices/grolbp/lbp.cpp
new file mode 100644
index 0000000..35cd54e
--- /dev/null
+++ b/src/devices/grolbp/lbp.cpp
@@ -0,0 +1,738 @@
+/* Copyright (C) 1994-2020 Free Software Foundation, Inc.
+ Written by Francisco Andrés Verdú <pandres@dragonet.es> with many
+ ideas taken from the other groff drivers.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+TODO
+
+ - Add X command to include bitmaps
+*/
+
+#include <assert.h>
+
+#include "driver.h"
+#include "lbp.h"
+#include "charset.h"
+#include "paper.h"
+
+#include "nonposix.h"
+
+extern "C" const char *Version_string;
+
+static int user_papersize = -1;
+static int orientation = -1;
+
+// custom paper format
+static double user_paperlength = 0;
+static double user_paperwidth = 0;
+
+static int ncopies = 1;
+
+#define DEFAULT_LINEWIDTH_FACTOR 40 // 0.04em
+static int linewidth_factor = DEFAULT_LINEWIDTH_FACTOR;
+
+static int set_papersize(const char *paperformat);
+
+class lbp_font : public font {
+public:
+ ~lbp_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static lbp_font *load_lbp_font(const char *);
+ char *lbpname;
+ char is_scalable;
+private:
+ lbp_font(const char *);
+};
+
+class lbp_printer : public printer {
+public:
+ lbp_printer(int, double, double);
+ ~lbp_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *name);
+ void draw(int code, int *p, int np, const environment *env);
+ void begin_page(int);
+ void end_page(int page_length);
+ font *make_font(const char *);
+ void end_of_line();
+private:
+ void set_line_thickness(int size,const environment *env);
+ void vdmstart();
+ void vdmflush(); // the name vdmend was already used in lbp.h
+ void setfillmode(int mode);
+ void polygon( int hpos,int vpos,int np,int *p);
+ char *font_name(const lbp_font *f, const int siz);
+
+ int fill_pattern;
+ int fill_mode;
+ int cur_hpos;
+ int cur_vpos;
+ lbp_font *cur_font;
+ int cur_size;
+ unsigned short cur_symbol_set;
+ int line_thickness;
+ int req_linethickness; // requested line thickness
+ // custom paper format
+ int papersize;
+ int paperlength;
+ int paperwidth;
+};
+
+lbp_font::lbp_font(const char *nm)
+: font(nm)
+{
+}
+
+lbp_font::~lbp_font()
+{
+}
+
+lbp_font *lbp_font::load_lbp_font(const char *s)
+{
+ lbp_font *f = new lbp_font(s);
+ f->lbpname = NULL;
+ f->is_scalable = 1; // Default is that fonts are scalable
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+
+void lbp_font::handle_unknown_font_command(const char *command,
+ const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "lbpname") == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'%1' command requires an argument",
+ command);
+ this->lbpname = new char[strlen(arg) + 1];
+ strcpy(this->lbpname, arg);
+ // we recognize bitmapped fonts by the first character of its name
+ if (arg[0] == 'N')
+ this->is_scalable = 0;
+ // fprintf(stderr, "Loading font \"%s\" \n", arg);
+ }
+ // fprintf(stderr, "Loading font %s \"%s\" in %s at %d\n",
+ // command, arg, filename, lineno);
+}
+
+static void wp54charset()
+{
+ unsigned int i;
+ lbpputs("\033[714;100;29;0;32;120.}");
+ for (i = 0; i < sizeof(symset); i++)
+ lbpputc(symset[i]);
+ lbpputs("\033[100;0 D");
+ return;
+}
+
+lbp_printer::lbp_printer(int ps, double pw, double pl)
+: fill_pattern(1),
+ fill_mode(0),
+ cur_hpos(-1),
+ cur_font(0),
+ cur_size(0),
+ cur_symbol_set(0),
+ req_linethickness(-1)
+{
+ SET_BINARY(fileno(stdout));
+ lbpinit(stdout);
+ lbpputs("\033c\033;\033[2&z\033[7 I\033[?32h\033[?33h\033[11h");
+ wp54charset(); // Define the new symbol set
+ lbpputs("\033[7 I\033[?32h\033[?33h\033[11h");
+ // Paper format handling
+ if (orientation < 0)
+ orientation = 0; // Default orientation is portrait
+ papersize = 14; // Default paper format is A4
+ if (font::papersize) {
+ papersize = set_papersize(font::papersize);
+ paperlength = font::paperlength;
+ paperwidth = font::paperwidth;
+ }
+ if (ps >= 0) {
+ papersize = ps;
+ paperlength = int(pl * font::res + 0.5);
+ paperwidth = int(pw * font::res + 0.5);
+ }
+ if (papersize < 80) // standard paper
+ lbpprintf("\033[%dp", (papersize | orientation));
+ else // Custom paper
+ lbpprintf("\033[%d;%d;%dp", (papersize | orientation),
+ paperlength, paperwidth);
+ // Number of copies
+ lbpprintf("\033[%dv\n", ncopies);
+ lbpputs("\033[0u\033[1u\033P1y Grolbp\033\\");
+ lbpmoveabs(0, 0);
+ lbpputs("\033[0t\033[2t");
+ lbpputs("\033('$2\033)' 1"); // Primary symbol set IBML
+ // Secondary symbol set IBMR1
+ cur_symbol_set = 0;
+}
+
+lbp_printer::~lbp_printer()
+{
+ lbpputs("\033P1y\033\\");
+ lbpputs("\033c\033<");
+}
+
+inline void lbp_printer::set_line_thickness(int size,const environment *env)
+{
+ if (size == 0)
+ line_thickness = 1;
+ else {
+ if (size < 0)
+ // line_thickness =
+ // (env->size * (font::res/72)) * (linewidth_factor/1000)
+ // we ought to check for overflow
+ line_thickness =
+ env->size * linewidth_factor * font::res / 72000;
+ else // size > 0
+ line_thickness = size;
+ } // else from if (size == 0)
+ if (line_thickness < 1)
+ line_thickness = 1;
+ if (vdminited())
+ vdmlinewidth(line_thickness);
+ req_linethickness = size; // an size requested
+ /* fprintf(stderr, "thickness: %d == %d, size %d, %d \n",
+ size, line_thickness, env->size,req_linethickness); */
+ return;
+} // lbp_printer::set_line_thickness
+
+void lbp_printer::begin_page(int)
+{
+}
+
+void lbp_printer::end_page(int)
+{
+ if (vdminited())
+ vdmflush();
+ lbpputc('\f');
+ cur_hpos = -1;
+}
+
+void lbp_printer::end_of_line()
+{
+ cur_hpos = -1; // force absolute motion
+}
+
+char *lbp_printer::font_name(const lbp_font *f, const int siz)
+{
+ static char bfont_name[255]; // The resulting font name
+ char type, // Italic, Roman, Bold
+ ori, // Normal or Rotated
+ *nam; // The font name without other data.
+ int cpi; // The font size in characters per inch
+ // (bitmapped fonts are monospaced).
+ /* Bitmap font selection is ugly in this printer, so don't expect
+ this function to be elegant. */
+ bfont_name[0] = 0x00;
+ if (orientation) // Landscape
+ ori = 'R';
+ else // Portrait
+ ori = 'N';
+ type = f->lbpname[strlen(f->lbpname) - 1];
+ nam = new char[strlen(f->lbpname) - 2];
+ strncpy(nam, &(f->lbpname[1]), strlen(f->lbpname) - 2);
+ nam[strlen(f->lbpname) - 2] = 0x00;
+ // fprintf(stderr, "Bitmap font '%s' %d %c %c \n", nam, siz, type, ori);
+ /* Since these fonts are available only at certain sizes,
+ 10 and 17 cpi for courier, 12 and 17 cpi for elite,
+ we adjust the resulting size. */
+ cpi = 17;
+ // Fortunately there are only two bitmapped fonts shipped with the printer.
+ if (!strcasecmp(nam, "courier")) {
+ // Courier font
+ if (siz >= 12)
+ cpi = 10;
+ else cpi = 17;
+ }
+ if (!strcasecmp(nam, "elite")) {
+ if (siz >= 10)
+ cpi = 12;
+ else cpi = 17;
+ }
+ // Now that we have all the data, let's generate the font name.
+ if ((type != 'B') && (type != 'I')) // Roman font
+ sprintf(bfont_name, "%c%s%d", ori, nam, cpi);
+ else
+ sprintf(bfont_name, "%c%s%d%c", ori, nam, cpi, type);
+ return bfont_name;
+}
+
+void lbp_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ int code = f->get_code(g);
+ unsigned char ch = code & 0xff;
+ unsigned short symbol_set = code >> 8;
+ if (f != cur_font) {
+ lbp_font *psf = (lbp_font *)f;
+ // fprintf(stderr, "Loading font %s \"%d\" \n", psf->lbpname, env->size);
+ if (psf->is_scalable) {
+ // Scalable font selection is different from bitmaped
+ lbpprintf("\033Pz%s.IBML\033\\\033[%d C", psf->lbpname,
+ (int)((env->size * font::res) / 72));
+ }
+ else
+ // bitmapped font
+ lbpprintf("\033Pz%s.IBML\033\\\n", font_name(psf, env->size));
+ lbpputs("\033)' 1"); // Select IBML and IBMR1 symbol set
+ cur_font = psf;
+ cur_symbol_set = 0;
+ // Update the line thickness if needed
+ if ((req_linethickness < 0 ) && (env->size != cur_size))
+ set_line_thickness(req_linethickness,env);
+ cur_size = env->size;
+ }
+ if (symbol_set != cur_symbol_set) {
+ if (cur_symbol_set == 3)
+ // if current symbol set is Symbol we must restore the font
+ lbpprintf("\033Pz%s.IBML\033\\\033[%d C", cur_font->lbpname,
+ (int)((env->size * font::res) / 72));
+ switch (symbol_set) {
+ case 0:
+ lbpputs("\033('$2\033)' 1"); // Select IBML and IBMR1 symbol sets
+ break;
+ case 1:
+ lbpputs("\033(d\033)' 1"); // Select wp54 symbol set
+ break;
+ case 2:
+ lbpputs("\033('$2\033)'!0"); // Select IBMP symbol set
+ break;
+ case 3:
+ lbpprintf("\033PzSymbol.SYML\033\\\033[%d C",
+ (int)((env->size * font::res) / 72));
+ lbpputs("\033(\"!!0\033)\"!!1"); // Select symbol font
+ break;
+ case 4:
+ lbpputs("\033)\"! 1\033(\"!$2"); // Select PS symbol set
+ break;
+ }
+ cur_symbol_set = symbol_set;
+ }
+ if (env->size != cur_size) {
+ if (!cur_font->is_scalable)
+ lbpprintf("\033Pz%s.IBML\033\\\n", font_name(cur_font, env->size));
+ else
+ lbpprintf("\033[%d C", (int)((env->size * font::res) / 72));
+ cur_size = env->size;
+ // Update the line thickness if needed
+ if (req_linethickness < 0 )
+ set_line_thickness(req_linethickness,env);
+ }
+ if ((env->hpos != cur_hpos) || (env->vpos != cur_vpos)) {
+ // lbpmoveabs(env->hpos - ((5 * 300) / 16), env->vpos);
+ lbpmoveabs(env->hpos - 64, env->vpos - 64);
+ cur_vpos = env->vpos;
+ cur_hpos = env->hpos;
+ }
+ if ((ch & 0x7F) < 32)
+ lbpputs("\033[1.v");
+ lbpputc(ch);
+ cur_hpos += w;
+}
+
+void lbp_printer::vdmstart()
+{
+ FILE *f;
+ static int changed_origin = 0;
+ errno = 0;
+ f = tmpfile();
+ // f = fopen("/tmp/gtmp","w+");
+ if (f == NULL)
+ perror("Opening temporary file");
+ vdminit(f);
+ if (!changed_origin) { // we should change the origin only one time
+ changed_origin = 1;
+ vdmorigin(-63, 0);
+ }
+ vdmlinewidth(line_thickness);
+}
+
+void
+lbp_printer::vdmflush()
+{
+ char buffer[1024];
+ int bytes_read = 1;
+ vdmend();
+ fflush(lbpoutput);
+ /* let's copy the vdm code to the output */
+ rewind(vdmoutput);
+ do {
+ bytes_read = fread(buffer, 1, sizeof(buffer), vdmoutput);
+ bytes_read = fwrite(buffer, 1, bytes_read, lbpoutput);
+ } while (bytes_read == sizeof(buffer));
+ fclose(vdmoutput); // This will also delete the file,
+ // since it is created by tmpfile()
+ vdmoutput = NULL;
+}
+
+inline void lbp_printer::setfillmode(int mode)
+{
+ if (mode != fill_mode) {
+ if (mode != 1)
+ vdmsetfillmode(mode, 1, 0);
+ else
+ // To get black, we must use white inverted.
+ vdmsetfillmode(mode, 1, 1);
+
+ fill_mode = mode;
+ }
+}
+
+inline void lbp_printer::polygon(int hpos, int vpos, int np, int *p)
+{
+ int *points, i;
+ points = new int[np + 2];
+ points[0] = hpos;
+ points[1] = vpos;
+ // fprintf(stderr, "Polygon (%d,%d) ", points[0], points[1]);
+ for (i = 0; i < np; i++)
+ points[i + 2] = p[i];
+ // for (i = 0; i < np; i++) fprintf(stderr, " %d ", p[i]);
+ // fprintf(stderr, "\n");
+ vdmpolygon((np /2) + 1, points);
+}
+
+void lbp_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if ((req_linethickness < 0 ) && (env->size != cur_size))
+ set_line_thickness(req_linethickness,env);
+
+ switch (code) {
+ case 't':
+ if (np == 0)
+ line_thickness = 1;
+ else { // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ set_line_thickness(p[0],env);
+ }
+ break;
+ case 'l': // Line
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ if (!vdminited())
+ vdmstart();
+ vdmline(env->hpos, env->vpos, p[0], p[1]);
+/* fprintf(stderr, "\nline: %d,%d - %d,%d thickness %d == %d\n",
+ env->hpos - 64,env->vpos -64, env->hpos - 64 + p[0],
+ env->vpos -64 + p[1], env->size, line_thickness);*/
+ break;
+ case 'R': // Rule
+ if (np != 2) {
+ error("2 arguments required for Rule");
+ break;
+ }
+ if (vdminited()) {
+ setfillmode(fill_pattern); // Solid Rule
+ vdmrectangle(env->hpos, env->vpos, p[0], p[1]);
+ }
+ else {
+ lbpruleabs(env->hpos - 64, env->vpos -64, p[0], p[1]);
+ cur_vpos = p[1];
+ cur_hpos = p[0];
+ }
+ // fprintf(stderr, "\nrule: thickness %d == %d\n",
+ // env->size, line_thickness);
+ break;
+ case 'P': // Filled Polygon
+ if (!vdminited())
+ vdmstart();
+ setfillmode(fill_pattern);
+ polygon(env->hpos, env->vpos, np, p);
+ break;
+ case 'p': // Empty Polygon
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ polygon(env->hpos, env->vpos, np, p);
+ break;
+ case 'C': // Filled Circle
+ if (!vdminited())
+ vdmstart();
+ // fprintf(stderr, "Circle (%d,%d) Fill %d\n",
+ // env->hpos, env->vpos, fill_pattern);
+ setfillmode(fill_pattern);
+ vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2);
+ break;
+ case 'c': // Empty Circle
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2);
+ break;
+ case 'E': // Filled Ellipse
+ if (!vdminited())
+ vdmstart();
+ setfillmode(fill_pattern);
+ vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0);
+ break;
+ case 'e': // Empty Ellipse
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0);
+ break;
+ case 'a': // Arc
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ // VDM draws arcs clockwise and pic counterclockwise
+ // We must compensate for that, exchanging the starting and
+ // ending points
+ vdmvarc(env->hpos + p[0], env->vpos+p[1],
+ int(sqrt(double((p[0]*p[0]) + (p[1]*p[1])))),
+ p[2], p[3],
+ (-p[0]), (-p[1]), 1, 2);
+ break;
+ case '~': // Spline
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ vdmspline(np/2, env->hpos, env->vpos, p);
+ break;
+ case 'f':
+ if (np != 1 && np != 2) {
+ error("1 argument required for fill");
+ break;
+ }
+ // fprintf(stderr, "Fill %d\n", p[0]);
+ if ((p[0] == 1) || (p[0] >= 1000)) { // Black
+ fill_pattern = 1;
+ break;
+ }
+ if (p[0] == 0) { // White
+ fill_pattern = 0;
+ break;
+ }
+ if ((p[0] > 1) && (p[0] < 1000))
+ {
+ if (p[0] >= 990) fill_pattern = -23;
+ else if (p[0] >= 700) fill_pattern = -28;
+ else if (p[0] >= 500) fill_pattern = -27;
+ else if (p[0] >= 400) fill_pattern = -26;
+ else if (p[0] >= 300) fill_pattern = -25;
+ else if (p[0] >= 200) fill_pattern = -22;
+ else if (p[0] >= 100) fill_pattern = -24;
+ else fill_pattern = -21;
+ }
+ break;
+ case 'F':
+ // not implemented yet
+ break;
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+ return;
+}
+
+font *lbp_printer::make_font(const char *nm)
+{
+ return lbp_font::load_lbp_font(nm);
+}
+
+printer *make_printer()
+{
+ return new lbp_printer(user_papersize, user_paperwidth, user_paperlength);
+}
+
+static struct {
+ const char *name;
+ int code;
+} lbp_papersizes[] =
+ {{ "A4", 14 },
+ { "letter", 30 },
+ { "legal", 32 },
+ { "executive", 40 },
+ };
+
+static int set_papersize(const char *paperformat)
+{
+ unsigned int i;
+ // First, test for a standard (i.e. supported directly by the printer)
+ // paper format.
+ for (i = 0 ; i < sizeof(lbp_papersizes) / sizeof(lbp_papersizes[0]); i++)
+ {
+ if (strcasecmp(lbp_papersizes[i].name,paperformat) == 0)
+ return lbp_papersizes[i].code;
+ }
+ // Otherwise, we assume a custom paper format.
+ return 82; // XXX: magic number
+}
+
+static void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ // orientation command
+ if (strcasecmp(command, "orientation") == 0) {
+ // We give priority to command-line options
+ if (orientation > 0)
+ return;
+ if (arg == 0)
+ error_with_file_and_line(filename, lineno,
+ "'orientation' command requires an argument");
+ else {
+ if (strcasecmp(arg, "portrait") == 0)
+ orientation = 0;
+ else {
+ if (strcasecmp(arg, "landscape") == 0)
+ orientation = 1;
+ else
+ error_with_file_and_line(filename, lineno,
+ "invalid argument to 'orientation' command");
+ }
+ }
+ }
+}
+
+static struct option long_options[] = {
+ { "orientation", required_argument, NULL, 'o' },
+ { "version", no_argument, NULL, 'v' },
+ { "copies", required_argument, NULL, 'c' },
+ { "landscape", no_argument, NULL, 'l' },
+ { "papersize", required_argument, NULL, 'p' },
+ { "linewidth", required_argument, NULL, 'w' },
+ { "fontdir", required_argument, NULL, 'F' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, 0, 0 }
+};
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-l] [-c num-copies] [-F font-directory] [-o orientation]"
+" [-p paper-format] [-w width] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"Translate the output of troff(1) into a CaPSL and VDM format suitable"
+"\n"
+"for Canon LBP-4 and LBP-8 printers. See the grolbp(1) manual page.\n",
+ stream);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ if (program_name == NULL)
+ program_name = strsave(argv[0]);
+ font::set_unknown_desc_command_handler(handle_unknown_desc_command);
+ // command line parsing
+ int c;
+ int option_index = 0;
+ while ((c = getopt_long(argc, argv, "c:F:hI:lo:p:vw:", long_options,
+ &option_index))
+ != EOF) {
+ switch (c) {
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'I':
+ // ignore include path arguments
+ break;
+ case 'p':
+ {
+ const char *s;
+ if (!font::scan_papersize(optarg, &s,
+ &user_paperlength, &user_paperwidth))
+ error("ignoring invalid paper format '%1'", optarg);
+ else
+ user_papersize = set_papersize(s);
+ break;
+ }
+ case 'l':
+ orientation = 1;
+ break;
+ case 'v':
+ printf("GNU grolbp (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'o':
+ if (strcasecmp(optarg, "portrait") == 0)
+ orientation = 0;
+ else {
+ if (strcasecmp(optarg, "landscape") == 0)
+ orientation = 1;
+ else
+ error("unknown orientation '%1'", optarg);
+ }
+ break;
+ case 'c':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if ((n <= 0) && (ptr == optarg))
+ error("argument for -c must be a positive integer");
+ else if (n <= 0 || n > 32767)
+ error("out of range argument for -c");
+ else
+ ncopies = unsigned(n);
+ break;
+ }
+ case 'w':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg)
+ error("argument for -w must be a non-negative integer");
+ else if (n < 0 || n > INT_MAX)
+ error("out of range argument for -w");
+ else
+ linewidth_factor = int(n);
+ break;
+ }
+ case 'h':
+ usage(stdout);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ }
+ if (optind >= argc)
+ do_file("-");
+ while (optind < argc)
+ do_file(argv[optind++]);
+ if (lbpoutput)
+ lbpputs("\033c\033<");
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grolbp/lbp.h b/src/devices/grolbp/lbp.h
new file mode 100644
index 0000000..ee1c7b9
--- /dev/null
+++ b/src/devices/grolbp/lbp.h
@@ -0,0 +1,544 @@
+// -*- C -*-
+/* Copyright (C) 1994-2020 Free Software Foundation, Inc.
+ Written by Francisco Andrés Verdú <pandres@dragonet.es>
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This file contains a set of utility functions to use canon CaPSL printers
+ * (lbp-4 and lbp-8 series printers) */
+
+#ifndef LBP_H
+#define LBP_H
+
+#include <stdio.h>
+#include <stdarg.h>
+
+static FILE *lbpoutput = NULL;
+static FILE *vdmoutput = NULL;
+
+
+static inline void
+lbpinit(FILE *outfile)
+{
+ lbpoutput = outfile;
+}
+
+
+static void
+lbpprintf(const char *format, ... )
+{ /* Taken from cjet */
+ va_list stuff;
+
+ va_start(stuff, format);
+ vfprintf(lbpoutput, format, stuff);
+ va_end(stuff);
+}
+
+
+static inline void
+lbpputs(const char *data)
+{
+ fputs(data,lbpoutput);
+}
+
+
+static inline void
+lbpputc(unsigned char c)
+{
+ fputc(c,lbpoutput);
+}
+
+
+static inline void
+lbpsavestatus(int idx )
+{
+ fprintf(lbpoutput,"\033[%d%%y",idx);
+}
+
+
+static inline void
+lbprestorestatus(int idx )
+{
+ fprintf(lbpoutput,"\033[%d%cz",idx ,'%');
+}
+
+
+static inline void
+lbpsavepos(int idx)
+{
+ fprintf(lbpoutput,"\033[1;%d;0x",idx);
+}
+
+
+static inline void
+lbprestorepos(int idx)
+{
+ fprintf(lbpoutput,"\033[0;%d;0x",idx);
+}
+
+
+static inline void
+lbprestoreposx(int idx)
+{
+ fprintf(lbpoutput,"\033[0;%d;1x",idx);
+}
+
+
+static inline void
+lbpmoverel(int despl, char direction)
+{
+ fprintf(lbpoutput,"\033[%d%c",despl,direction);
+}
+
+
+static inline void
+lbplinerel(int width,int despl,char direction )
+{
+ fprintf(lbpoutput,"\033[%d;0;9{\033[%d%c\033[9}",width,despl,direction);
+}
+
+
+static inline void
+lbpmoveabs(int x, int y)
+{
+ fprintf(lbpoutput,"\033[%d;%df",y,x);
+}
+
+
+static inline void
+lbplineto(int x,int y, int width )
+{
+ fprintf(lbpoutput,"\033[%d;0;9{",width);
+ lbpmoveabs(x,y);
+ fprintf(lbpoutput,"\033[9}\n");
+}
+
+
+static inline void
+lbpruleabs(int x, int y, int hsize, int vsize)
+{
+ lbpmoveabs(x,y);
+ fprintf(lbpoutput,"\033[0;9;000s");
+ lbpmoveabs(x+hsize,y+vsize);
+ fprintf(lbpoutput,"\033[9r");
+}
+
+
+static void vdmprintf(const char *format, ... );
+
+
+static inline char *
+vdmnum(int num,char *result)
+{
+ char b1,b2,b3;
+ char *p = result;
+ int nm;
+
+ nm = abs(num);
+ /* First byte 1024 - 32768 */
+ b1 = ((nm >> 10) & 0x3F);
+ if (b1) *p++ = b1 | 0x40;
+
+ /* Second Byte 16 - 1024 */
+ b2 = ((nm >> 4) & 0x3F);
+ if ( b1 || b2) *p++= b2 | 0x40;
+
+ /* Third byte 0 - 15 */
+ b3 = ((nm & 0x0F) | 32);
+ if (num >= 0) b3 |= 16;
+ *p++ = b3;
+ *p = 0x00; /* End of the resulting string */
+ return result;
+}
+
+
+static inline void
+vdmorigin(int newx, int newy)
+{
+ char nx[4],ny[4];
+
+ vdmprintf("}\"%s%s\x1e",vdmnum(newx,nx),vdmnum(newy,ny));
+}
+
+
+static inline FILE *
+vdminit(FILE *vdmfile)
+{
+ char scale[4],size[4],lineend[4];
+
+/* vdmoutput = tmpfile();*/
+ vdmoutput = vdmfile;
+ /* Initialize the VDM mode */
+ vdmprintf("\033[0&}#GROLBP\x1e!0%s%s\x1e$\x1e}F%s\x1e",\
+ vdmnum(-3,scale),vdmnum(1,size),vdmnum(1,lineend));
+ return vdmoutput;
+
+}
+
+
+static inline void
+vdmend()
+{
+ vdmprintf("}p\x1e");
+}
+
+
+static void
+vdmprintf(const char *format, ... )
+{ /* Taken from cjet */
+ va_list stuff;
+
+ if (vdmoutput == NULL) vdminit(tmpfile());
+ va_start(stuff, format);
+ vfprintf(vdmoutput, format, stuff);
+ va_end(stuff);
+}
+
+
+static inline void
+vdmsetfillmode(int pattern,int perimeter, int inverted)
+{
+ char patt[4],perim[4],
+ rot[4], /* rotation */
+ espejo[4], /* espejo */
+ inv[4]; /* Inverted */
+
+ vdmprintf("I%s%s%s%s%s\x1e",vdmnum(pattern,patt),\
+ vdmnum(perimeter,perim),vdmnum(0,rot),
+ vdmnum(0,espejo),vdmnum(inverted,inv));
+}
+
+
+static inline void
+vdmcircle(int centerx, int centery, int radius)
+{
+ char x[4],y[4],rad[4];
+
+ vdmprintf("5%s%s%s\x1e",vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radius,rad));
+}
+
+
+static inline void
+vdmaarc(int centerx, int centery, int radius,int startangle,int angle,int style,int arcopen)
+{
+ char x[4],y[4],rad[4],stx[4],sty[4],styl[4],op[4];
+
+ vdmprintf("}6%s%s%s%s%s%s%s\x1e",vdmnum(arcopen,op),\
+ vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radius,rad),vdmnum(startangle,stx),vdmnum(angle,sty),\
+ vdmnum(style,styl));
+}
+
+
+static inline void
+vdmvarc(int centerx, int centery,int radius, int startx, int starty, int endx, int endy,\
+ int style,int arcopen)
+{
+ char x[4],y[4],rad[4],stx[4],sty[4],enx[4],eny[4],styl[4],op[4];
+
+ vdmprintf("}6%s%s%s%s%s%s%s%s%s\x1e",vdmnum(arcopen,op),\
+ vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radius,rad),vdmnum(startx,stx),vdmnum(starty,sty),\
+ vdmnum(endx,enx),vdmnum(endy,eny),vdmnum(style,styl));
+}
+
+
+static inline void
+vdmellipse(int centerx, int centery, int radiusx, int radiusy,int rotation)
+{
+ char x[4],y[4],radx[4],rady[4],rotat[4];
+
+ vdmprintf("}7%s%s%s%s%s\x1e\n",vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radiusx,radx),vdmnum(radiusy,rady),\
+ vdmnum(rotation,rotat));
+}
+
+
+static inline void
+vdmsetlinetype(int lintype)
+{
+ char ltyp[4], expfact[4];
+
+ vdmprintf("E1%s%s\x1e",vdmnum(lintype,ltyp),vdmnum(1,expfact));
+
+}
+
+
+static inline void
+vdmsetlinestyle(int lintype, int pattern,int unionstyle)
+{
+ char patt[4],ltip[4],
+ rot[4], /* rotation */
+ espejo[4], /* espejo */
+ in[4]; /* Inverted */
+
+ vdmprintf("}G%s%s%s%s%s\x1e",vdmnum(lintype,ltip),\
+ vdmnum(pattern,patt),vdmnum(0,rot),
+ vdmnum(0,espejo),vdmnum(0,in));
+ vdmprintf("}F%s",vdmnum(unionstyle,rot));
+}
+
+
+static inline void
+vdmlinewidth(int width)
+{
+ char wh[4];
+
+ vdmprintf("F1%s\x1e",vdmnum(width,wh));
+}
+
+
+static inline void
+vdmrectangle(int origx, int origy,int dstx, int dsty)
+{
+ char xcoord[4],ycoord[4],sdstx[4],sdsty[4];
+
+ vdmprintf("}:%s%s%s%s\x1e\n",vdmnum(origx,xcoord),vdmnum(dstx,sdstx),\
+ vdmnum(origy,ycoord),vdmnum(dsty,sdsty));
+}
+
+
+static inline void
+vdmpolyline(int numpoints, int *points)
+{
+ int i,*p = points;
+ char xcoord[4],ycoord[4];
+
+ if (numpoints < 2) return;
+ vdmprintf("1%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ for (i = 1; i < numpoints ; i++) {
+ vdmprintf("%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ } /* for */
+ vdmprintf("\x1e\n");
+}
+
+
+static inline void
+vdmpolygon(int numpoints, int *points)
+{
+ int i,*p = points;
+ char xcoord[4],ycoord[4];
+
+ if (numpoints < 2) return;
+ vdmprintf("2%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ for (i = 1; i < numpoints ; i++) {
+ vdmprintf("%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ } /* for */
+ vdmprintf("\x1e\n");
+
+}
+
+
+/************************************************************************
+ * Higher level auxiliary functions *
+ ************************************************************************/
+static inline int
+vdminited()
+{
+ return (vdmoutput != NULL);
+}
+
+
+static inline void
+vdmline(int startx, int starty, int sizex, int sizey)
+{
+ int points[4];
+
+ points[0] = startx;
+ points[1] = starty;
+ points[2] = sizex;
+ points[3] = sizey;
+
+ vdmpolyline(2,points);
+
+}
+
+
+/*#define THRESHOLD .05 */ /* inch */
+#define THRESHOLD 1 /* points (1/300 inch) */
+static inline void
+splinerel(double px,double py,int flush)
+{
+ static int lx = 0 ,ly = 0;
+ static double pend = 0.0;
+ static int dy = 0, despx = 0, despy = 0, sigpend = 0;
+ int dxnew = 0, dynew = 0, sg;
+ char xcoord[4],ycoord[4];
+ double npend ;
+
+ if (flush == -1) {lx = (int)px; ly = (int)py; return;}
+
+ if (flush == 0) {
+ dxnew = (int)px -lx;
+ dynew = (int)py -ly;
+ if ((dxnew == 0) && (dynew == 0)) return;
+ sg = (dxnew < 0)? -1 : 0;
+/* fprintf(stderr,"s (%d,%d) (%d,%d)\n",dxnew,dynew,despx,despy);*/
+ if (dynew == 0) {
+ despx = dxnew;
+ if ((sg == sigpend) && (dy == 0)){
+ return;
+ }
+ dy = 0;
+ }
+ else {
+ dy = 1;
+ npend = (1.0*dxnew)/dynew;
+ if (( npend == pend) && (sigpend == sg))
+ { despy = dynew; despx = dxnew; return; }
+ else
+ { sigpend = sg;
+ pend = npend;
+ } /* else (( npend == pend) && ... */
+ } /* else (if (dynew == 0)) */
+ } /* if (!flush ) */
+
+ /* if we've changed direction we must draw the line */
+/* fprintf(stderr," (%d) %.2f,%.2f\n",flush,(float)px,(float)py);*/
+ if ((despx != 0) || (despy != 0)) vdmprintf("%s%s",vdmnum(despx,xcoord),\
+ vdmnum(despy,ycoord));
+ /*if ((despx != 0) || (despy != 0)) fprintf(stderr,"2
+ *%d,%d\n",despx,despy);*/
+ if (flush) {
+ dxnew = dy = despx = despy = 0;
+ return;
+ } /* if (flush) */
+ dxnew -= despx;
+ dynew -= despy;
+ if ((dxnew != 0) || (dynew != 0)) vdmprintf("%s%s",vdmnum(dxnew,xcoord),\
+ vdmnum(dynew,ycoord));
+
+/* if ((dxnew != 0) || (dynew != 0)) fprintf(stderr,"3
+ * %d,%d\n",dxnew,dynew);*/
+ lx = (int)px; ly = (int)py;
+ dxnew = dy = despx = despy = 0;
+
+}
+
+
+/**********************************************************************
+ * The following code to draw splines is adapted from the transfig package
+ */
+static void
+quadratic_spline(double a_1, double b_1, double a_2, double b_2, \
+ double a_3, double b_3, double a_4, double b_4)
+{
+ double x_1, y_1, x_4, y_4;
+ double x_mid, y_mid;
+
+ x_1 = a_1; y_1 = b_1;
+ x_4 = a_4; y_4 = b_4;
+ x_mid = (a_2 + a_3)/2.0;
+ y_mid = (b_2 + b_3)/2.0;
+ if ((fabs(x_1 - x_mid) < THRESHOLD)
+ && (fabs(y_1 - y_mid) < THRESHOLD)) {
+ splinerel(x_mid, y_mid, 0);
+/* fprintf(tfp, "PA%.4f,%.4f;\n", x_mid, y_mid);*/
+ }
+ else {
+ quadratic_spline(x_1, y_1, ((x_1+a_2)/2.0), ((y_1+b_2)/2.0),
+ ((3.0*a_2+a_3)/4.0), ((3.0*b_2+b_3)/4.0), x_mid, y_mid);
+ }
+
+ if ((fabs(x_mid - x_4) < THRESHOLD)
+ && (fabs(y_mid - y_4) < THRESHOLD)) {
+ splinerel(x_4, y_4, 0);
+/* fprintf(tfp, "PA%.4f,%.4f;\n", x_4, y_4);*/
+ }
+ else {
+ quadratic_spline(x_mid, y_mid,
+ ((a_2+3.0*a_3)/4.0), ((b_2+3.0*b_3)/4.0),
+ ((a_3+x_4)/2.0), ((b_3+y_4)/2.0), x_4, y_4);
+ }
+}
+
+
+#define XCOORD(i) numbers[(2*i)]
+#define YCOORD(i) numbers[(2*i)+1]
+static void
+vdmspline(int numpoints, int o_x, int o_y, int *numbers)
+{
+ double cx_1, cy_1, cx_2, cy_2, cx_3, cy_3, cx_4, cy_4;
+ double x_1, y_1, x_2, y_2;
+ char xcoord[4],ycoord[4];
+ int i;
+
+ /*p = s->points;
+ x_1 = p->x/ppi;*/
+ x_1 = o_x;
+ y_1 = o_y;
+/* p = p->next;
+ x_2 = p->x/ppi;
+ y_2 = p->y/ppi;*/
+ x_2 = o_x + XCOORD(0);
+ y_2 = o_y + YCOORD(0);
+ cx_1 = (x_1 + x_2)/2.0;
+ cy_1 = (y_1 + y_2)/2.0;
+ cx_2 = (x_1 + 3.0*x_2)/4.0;
+ cy_2 = (y_1 + 3.0*y_2)/4.0;
+
+/* fprintf(stderr,"Spline %d (%d,%d)\n",numpoints,(int)x_1,(int)y_1);*/
+ vdmprintf("1%s%s",vdmnum((int)x_1,xcoord),vdmnum((int)y_1,ycoord));
+ splinerel(x_1,y_1,-1);
+ splinerel(cx_1,cy_1,0);
+/* fprintf(tfp, "PA%.4f,%.4f;PD%.4f,%.4f;\n",
+ x_1, y_1, cx_1, cy_1);*/
+
+ /*for (p = p->next; p != NULL; p = p->next) {*/
+ for (i = 1; i < (numpoints); i++) {
+ x_1 = x_2;
+ y_1 = y_2;
+/* x_2 = p->x/ppi;
+ y_2 = p->y/ppi;*/
+ x_2 = x_1 + XCOORD(i);
+ y_2 = y_1 + YCOORD(i);
+ cx_3 = (3.0*x_1 + x_2)/4.0;
+ cy_3 = (3.0*y_1 + y_2)/4.0;
+ cx_4 = (x_1 + x_2)/2.0;
+ cy_4 = (y_1 + y_2)/2.0;
+ /* fprintf(stderr,"Point (%d,%d) - (%d,%d)\n",(int)x_1,(int)(y_1),(int)x_2,(int)y_2);*/
+ quadratic_spline(cx_1, cy_1, cx_2, cy_2, cx_3, cy_3, cx_4, cy_4);
+ cx_1 = cx_4;
+ cy_1 = cy_4;
+ cx_2 = (x_1 + 3.0*x_2)/4.0;
+ cy_2 = (y_1 + 3.0*y_2)/4.0;
+ }
+ x_1 = x_2;
+ y_1 = y_2;
+/* p = s->points->next;
+ x_2 = p->x/ppi;
+ y_2 = p->y/ppi;*/
+ x_2 = o_x + XCOORD(0);
+ y_2 = o_y + YCOORD(0);
+ cx_3 = (3.0*x_1 + x_2)/4.0;
+ cy_3 = (3.0*y_1 + y_2)/4.0;
+ cx_4 = (x_1 + x_2)/2.0;
+ cy_4 = (y_1 + y_2)/2.0;
+ splinerel(x_1, y_1, 0);
+ splinerel(x_1, y_1, 1);
+ /*vdmprintf("%s%s",vdmnum((int)(x_1-lx),xcoord),\
+ vdmnum((int)(y_1-ly),ycoord));*/
+ vdmprintf("\x1e\n");
+/* fprintf(tfp, "PA%.4f,%.4f;PU;\n", x_1, y_1);*/
+
+
+}
+
+
+#endif
diff --git a/src/devices/grolj4/grolj4.1.man b/src/devices/grolj4/grolj4.1.man
new file mode 100644
index 0000000..6227779
--- /dev/null
+++ b/src/devices/grolj4/grolj4.1.man
@@ -0,0 +1,896 @@
+.TH grolj4 @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grolj4 \-
+.I groff
+output driver for HP LaserJet 4 and compatible printers
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1994-2020, 2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grolj4_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'lj4' .ft \\$1
+..
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grolj4
+.RB [ \-l ]
+.RB [ \-c\~\c
+.IR num-copies ]
+.RB [ \-d
+.RI [ n ]]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-w\~\c
+.IR line-width ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grolj4
+.B \-\-help
+.YS
+.
+.
+.SY grolj4
+.B \-v
+.
+.SY grolj4
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+This GNU
+.I roff
+output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into a PCL5 format suitable for an HP LaserJet 4 printer.
+.
+Normally,
+.I grolj4
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~lj4 \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grolj4 .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grolj4
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I grolj4
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Fonts are grouped into families
+.BR A ,
+.BR C ,
+.BR G ,
+.BR O ,
+.BR T ,
+.BR TN ,
+.BR U ,
+and
+.B UC
+having members in each style.
+.
+.
+.RS
+.TP 14n
+.B AB
+.FT AB
+Arial Bold
+.FT
+.
+.TQ
+.B ABI
+.FT ABI
+Arial Bold Italic
+.FT
+.
+.TQ
+.B AI
+.FT AI
+Arial Italic
+.FT
+.
+.TQ
+.B AR
+.FT AR
+Arial Roman
+.FT
+.
+.TQ
+.B CB
+.FT CB
+Courier Bold
+.FT
+.
+.TQ
+.B CBI
+.FT CBI
+Courier Bold Italic
+.FT
+.
+.TQ
+.B CI
+.FT CI
+Courier Italic
+.FT
+.
+.TQ
+.B CR
+.FT CR
+Courier Roman
+.FT
+.
+.TQ
+.B GB
+.FT GB
+Garamond Halbfett
+.FT
+.
+.TQ
+.B GBI
+.FT GBI
+Garamond Kursiv Halbfett
+.FT
+.
+.TQ
+.B GI
+.FT GI
+Garamond Kursiv
+.FT
+.
+.TQ
+.B GR
+.FT GR
+Garamond Antiqua
+.FT
+.
+.TQ
+.B OB
+.FT OB
+CG Omega Bold
+.FT
+.
+.TQ
+.B OBI
+.FT OBI
+CG Omega Bold Italic
+.FT
+.
+.TQ
+.B OI
+.FT OI
+CG Omega Italic
+.FT
+.
+.TQ
+.B OR
+.FT OR
+CG Omega Roman
+.
+.TQ
+.B OB
+.FT OB
+CG Omega Bold
+.FT
+.
+.TQ
+.B OBI
+.FT OBI
+CG Omega Bold Italic
+.FT
+.
+.TQ
+.B OI
+.FT OI
+CG Omega Italic
+.FT
+.
+.TQ
+.B OR
+.FT OR
+CG Omega Roman
+.FT
+.
+.TQ
+.B TB
+.FT TB
+CG Times Bold
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+CG Times Bold Italic
+.FT
+.
+.TQ
+.B TI
+.FT TI
+CG Times Italic
+.FT
+.
+.TQ
+.B TR
+.FT TR
+CG Times Roman
+.FT
+.
+.TQ
+.B TNRB
+.FT TNRB
+M Times Bold
+.FT
+.
+.TQ
+.B TNRBI
+.FT TNRBI
+M Times Bold Italic
+.FT
+.
+.TQ
+.B TNRI
+.FT TNRI
+M Times Italic
+.FT
+.
+.TQ
+.B TNRR
+.FT TNRR
+M Times Roman
+.FT
+.
+.TQ
+.B UB
+.FT UB
+Univers Bold
+.FT
+.
+.TQ
+.B UBI
+.FT UBI
+Univers Bold Italic
+.FT
+.
+.TQ
+.B UI
+.FT UI
+Univers Medium Italic
+.FT
+.
+.TQ
+.B UR
+.FT UR
+Univers Medium
+.FT
+.
+.TQ
+.B UCB
+.FT UCB
+Univers Condensed Bold
+.FT
+.
+.TQ
+.B UCBI
+.FT UCBI
+Univers Condensed Bold Italic
+.FT
+.
+.TQ
+.B UCI
+.FT UCI
+Univers Condensed Medium Italic
+.FT
+.
+.TQ
+.B UCR
+.FT UCR
+Univers Condensed Medium
+.FT
+.RE
+.
+.
+.P
+The following fonts are not members of a family.
+.
+.
+.RS
+.TP 14n
+.B ALBB
+.FT ALBB
+Albertus Extra Bold
+.FT
+.
+.TQ
+.B ALBR
+.FT ALBR
+Albertus Medium
+.FT
+.
+.TQ
+.B AOB
+.FT AOB
+Antique Olive Bold
+.
+.TQ
+.B AOI
+.FT AOI
+Antique Olive Italic
+.
+.TQ
+.B AOR
+.FT AOR
+Antique Olive Roman
+.
+.TQ
+.B CLARENDON
+.FT CLARENDON
+Clarendon
+.
+.TQ
+.B CORONET
+.FT CORONET
+Coronet
+.
+.TQ
+.B LGB
+.FT LGB
+Letter Gothic Bold
+.
+.TQ
+.B LGI
+.FT LGI
+Letter Gothic Italic
+.
+.TQ
+.B LGR
+.FT LGR
+Letter Gothic Roman
+.
+.TQ
+.B MARIGOLD
+.FT MARIGOLD
+Marigold
+.RE
+.
+.
+.P
+The special font is
+.B S
+(PostScript Symbol);
+.B SYMBOL
+(M Symbol),
+and
+.B WINGDINGS
+(Wingdings)
+are also available but not mounted by default.
+.
+.
+.\" ====================================================================
+.SS "Paper format and device description file"
+.\" ====================================================================
+.
+.I grolj4
+supports paper formats
+.RB \[lq] A4 \[rq],
+.RB \[lq] B5 \[rq],
+.RB \[lq] C5 \[rq],
+.RB \[lq] com10 \[rq],
+.RB \[lq] DL \[rq],
+.RB \%\[lq] executive \[rq],
+.RB \%\[lq] legal \[rq],
+.RB \%\[lq] letter \[rq],
+and
+.RB \[lq] monarch \[rq].
+.
+These are matched case-insensitively.
+.
+The
+.B \-p
+option overrides any setting in the device description file
+.IR DESC .
+.
+If neither specifies a paper format,
+\[lq]letter\[rq] is assumed.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+.I grolj4
+recognizes four font description file directives in addition to those
+documented in
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI pclweight\~ n
+Set the stroke weight to
+.IR n ,
+an integer in the range \-7 to +7;
+the default is\~0.
+.
+.
+.TP
+.BI pclstyle\~ n
+Set the style to
+.IR n ,
+an integer in the range 0 to 32767;
+the default is\~0.
+.
+.
+.TP
+.BI pclproportional\~ n
+Set the proportional spacing Boolean flag to
+.IR n ,
+which can be either 0 or\~1;
+the default is\~0.
+.
+.
+.TP
+.BI pcltypeface\~ n
+Set the typeface family to
+.IR n ,
+an integer in the range 0 to 65535;
+the default is\~0.
+.
+.
+.\" ====================================================================
+.SS "Drawing commands"
+.\" ====================================================================
+.
+An additional drawing command is recognized as an extension to those
+documented in
+.MR groff @MAN7EXT@ .
+.
+.
+.TP
+.BI \[rs]D\[aq]R\~ "dh dv" \[aq]
+Draw a rule
+(solid black rectangle)
+with one corner at the drawing position,
+and the diagonally opposite corner at the drawing position
+.RI +( dh , dv ),
+at which the drawing position will be afterward.
+.
+This generates a PCL fill rectangle command,
+and so will work on printers that do not support HP-GL/2,
+unlike the other
+.B \[rs]D
+commands.
+.
+.
+.\" ====================================================================
+.SS Fonts
+.\" ====================================================================
+.
+Nominally,
+all Hewlett-Packard LaserJet\~\%4-series and newer printers have the
+same internal fonts:
+45 scalable fonts and one bitmapped Lineprinter font.
+.
+The scalable fonts are available in sizes between 0.25 points and 999.75
+points,
+in 0.25-point increments;
+the Lineprinter font is available only in 8.5-point size.
+.
+.
+.P
+The LaserJet font files included with
+.I groff
+assume that all printers since the LaserJet\~4 are identical.
+.
+There are some differences between fonts in the earlier and more recent
+printers,
+however.
+.
+The LaserJet\~4 printer used Agfa Intellifont technology for 35 of the
+internal scalable fonts;
+the remaining 10 scalable fonts were TrueType.
+.
+Beginning with the LaserJet\~\%4000-series printers introduced in 1997,
+all scalable internal fonts have been TrueType.
+.
+The number of printable glyphs differs slightly between Intellifont and
+TrueType fonts
+(generally,
+the TrueType fonts include more glyphs),
+and
+there are some minor differences in glyph metrics.
+.
+Differences among printer models are described in the
+.I "PCL\~5 Comparison Guide"
+and the
+.I "PCL\~5 Comparison Guide Addendum"
+(for printers introduced since approximately 2001).
+.
+.
+.P
+LaserJet printers reference a glyph by a combination of a 256-glyph
+symbol set and an index within that symbol set.
+.
+Many glyphs appear in more than one symbol set;
+all combinations of symbol set and index that reference the same glyph
+are equivalent.
+.
+For each glyph,
+.MR hpftodit @MAN1EXT@
+searches a list of symbol sets,
+and selects the first set that contains the glyph.
+.
+The printing code generated by
+.I hpftodit
+is an integer that encodes a numerical value for the symbol set in the
+high byte(s),
+and the index in the low byte.
+.
+See
+.MR groff_font @MAN5EXT@
+for a complete description of the font file format;
+symbol sets are described in greater detail in the
+.IR "PCL\~5 Printer Language Technical Reference Manual" .
+.
+.
+.P
+Two of the scalable fonts,
+Symbol and Wingdings,
+are bound to 256-glyph symbol sets;
+the remaining scalable fonts,
+as well as the Lineprinter font,
+support numerous symbol sets,
+sufficient to enable printing of more than 600 glyphs.
+.
+.
+.P
+The metrics generated by
+.I hpftodit
+assume that the
+.I DESC
+file contains values of 1200 for
+.I res
+and 6350 for
+.IR unitwidth ,
+or any combination
+(e.g.,
+2400 and 3175)
+for which
+.IR res \~\[tmu]\~ unitwidth \~=\~7\|620\|000.
+.
+Although HP PCL\~5 LaserJet printers support an internal resolution of
+7200 units per inch,
+they use a 16-bit signed integer for positioning;
+if
+.B devlj4
+is to support U.S.\& ledger paper (11\~in\~\[mu]\~17\~in;
+in = inch),
+the maximum usable resolution is 32\|767\~\[di]\~17,
+or 1927 units per inch,
+which rounds down to 1200 units per inch.
+.
+If the largest required paper dimension is less
+(e.g.,
+8.5\~in\~\[mu]\~11\~in,
+or A5),
+a greater
+.I res
+(and lesser
+.IR unitwidth )
+can be specified.
+.
+.
+.P
+Font metrics for Intellifont fonts were provided by Tagged Font Metric
+(TFM) files originally developed by Agfa/Compugraphic.
+.
+The TFM files provided for these fonts supported 600+ glyphs and
+contained extensive lists of kerning pairs.
+.
+.
+.P
+To accommodate developers who had become accustomed to TFM files,
+HP also provided TFM files for the 10 TrueType fonts included in the
+LaserJet\~4.
+.
+The TFM files for TrueType fonts generally included less information
+than the Intellifont TFMs,
+supporting fewer glyphs,
+and in most cases,
+providing no kerning information.
+.
+By the time the LaserJet\~4000 printer was introduced,
+most developers had migrated to other means of obtaining font metrics,
+and support for new TFM files was very limited.
+.
+The TFM files provided for the TrueType fonts in the LaserJet\~4000
+support only the Latin 2 (ISO 8859-2) symbol set,
+and include no kerning information;
+consequently,
+they are of little value for any but the most rudimentary documents.
+.
+.
+.P
+Because the Intellifont TFM files contain considerably more information,
+they generally are preferable to the TrueType TFM files even for use
+with the TrueType fonts in the newer printers.
+.
+The metrics for the TrueType fonts are very close,
+though not identical,
+to those for the earlier Intellifont fonts of the same names.
+.
+Although most output using the Intellifont metrics with the newer
+printers is quite acceptable,
+a few glyphs may fail to print as expected.
+.
+The differences in glyph metrics may be particularly noticeable with
+composite parentheses,
+brackets,
+and braces used by
+.MR eqn @MAN1EXT@ .
+.
+A script,
+located in
+.IR @FONTDIR@/\:\%devlj4/\:generate ,
+can be used to adjust the metrics for these glyphs in the special font
+\[lq]S\[rq] for use with printers that have all TrueType fonts.
+.
+.
+.P
+At the time HP last supported TFM files,
+only version 1.0 of the Unicode standard was available.
+.
+Consequently,
+many glyphs lacking assigned code points were assigned by HP to the
+Private Use Area (PUA).
+.
+Later versions of the Unicode standard included code points outside the
+PUA for many of these glyphs.
+.
+The HP-supplied TrueType TFM files use the PUA assignments;
+TFM files generated from more recent TrueType font files require the
+later Unicode values to access the same glyphs.
+.
+Consequently,
+two different mapping files may be required:
+one for the HP-supplied TFM files,
+and one for more recent TFM files.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-c\~ num-copies
+Format
+.I num-copies
+copies of each page.
+.
+.
+.TP
+.BR \-d \~[\c
+.IR n ]
+Use duplex mode
+.IR n :
+1\~is long-side binding (default),
+and 2\~is short-side binding.
+.
+.
+.TP
+.BI \-F " font-directory"
+Prepend directory
+.IR font-directory /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR lj4 .
+.
+.
+.TP
+.B \-l
+Format the document in landscape orientation.
+.
+.
+.TP
+.BI \-p " paper-format"
+Set the paper format to
+.IR paper-format ,
+which must be a valid paper format as described above.
+.
+.
+.TP
+.BI \-w " line-width"
+Set the default line thickness to
+.I line-width
+thousandths of an em;
+the default is
+.B 40
+(0.04\~em).
+.
+.
+.br
+.ne 4v \" Keep section heading and paragraph together.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devlj4/\:DESC
+describes the
+.B lj4
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devlj4/ F
+describes the font known
+.RI as\~ F
+on device
+.BR lj4 .
+.
+.
+.TP
+.I @MACRODIR@/\:lj4\:.tmac
+defines macros for use with the
+.B lj4
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B lj4
+output device is selected.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+.\" XXX: What does this mean? The period/full stop glyph? Flyspecks?
+Small dots.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR http://\:www\:.hp\:.com/\:ctg/\:Manual/\:bpl13210\:.pdf
+.I HP PCL/PJL Reference:
+.I PCL\~5 Printer Language Technical Reference Manual,
+.I Part I
+.UE
+.
+.
+.P
+.MR hpftodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@
+.
+.
+.\" Clean up.
+.rm FT
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grolj4_1_man_C]
+.do rr *groff_grolj4_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grolj4/grolj4.am b/src/devices/grolj4/grolj4.am
new file mode 100644
index 0000000..37138b2
--- /dev/null
+++ b/src/devices/grolj4/grolj4.am
@@ -0,0 +1,32 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grolj4
+grolj4_SOURCES = src/devices/grolj4/lj4.cpp
+grolj4_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grolj4/grolj4.1
+EXTRA_DIST += \
+ src/devices/grolj4/grolj4.1.man
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grolj4/lj4.cpp b/src/devices/grolj4/lj4.cpp
new file mode 100644
index 0000000..a429a7d
--- /dev/null
+++ b/src/devices/grolj4/lj4.cpp
@@ -0,0 +1,715 @@
+/* Copyright (C) 1994-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+TODO
+
+option to use beziers for circle/ellipse/arc
+option to use lines for spline (for LJ3)
+left/top offset registration
+output bin selection option
+paper source option
+output non-integer parameters using fixed point numbers
+X command to insert contents of file
+X command to specify inline escape sequence (how to specify unprintable chars?)
+X command to include bitmap graphics
+*/
+
+#include <assert.h>
+
+#include "driver.h"
+#include "nonposix.h"
+
+extern "C" const char *Version_string;
+
+static struct {
+ const char *name;
+ int code;
+ // at 300dpi
+ int x_offset_portrait;
+ int x_offset_landscape;
+} paper_table[] = {
+ { "letter", 2, 75, 60 },
+ { "legal", 3, 75, 60 },
+ { "executive", 1, 75, 60 },
+ { "a4", 26, 71, 59 },
+ { "com10", 81, 75, 60 },
+ { "monarch", 80, 75, 60 },
+ { "c5", 91, 71, 59 },
+ { "b5", 100, 71, 59 },
+ { "dl", 90, 71, 59 },
+};
+
+static int user_paper_size = -1;
+static int landscape_flag = 0;
+static int duplex_flag = 0;
+
+// An upper limit on the paper dimensions in centipoints,
+// used for setting HPGL picture frame.
+#define MAX_PAPER_WIDTH (12*720)
+#define MAX_PAPER_HEIGHT (17*720)
+
+// Dotted lines that are thinner than this don't work right.
+#define MIN_DOT_PEN_WIDTH .351
+
+#ifndef DEFAULT_LINE_WIDTH_FACTOR
+// in ems/1000
+#define DEFAULT_LINE_WIDTH_FACTOR 40
+#endif
+
+const int DEFAULT_HPGL_UNITS = 1016;
+int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR;
+unsigned ncopies = 0; // 0 means don't send ncopies command
+
+static int lookup_paper_size(const char *);
+
+class lj4_font : public font {
+public:
+ ~lj4_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static lj4_font *load_lj4_font(const char *);
+ int weight;
+ int style;
+ int proportional;
+ int typeface;
+private:
+ lj4_font(const char *);
+};
+
+lj4_font::lj4_font(const char *nm)
+: font(nm), weight(0), style(0), proportional(0), typeface(0)
+{
+}
+
+lj4_font::~lj4_font()
+{
+}
+
+lj4_font *lj4_font::load_lj4_font(const char *s)
+{
+ lj4_font *f = new lj4_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+static struct {
+ const char *s;
+ int lj4_font::*ptr;
+ int min;
+ int max;
+} command_table[] = {
+ { "pclweight", &lj4_font::weight, -7, 7 },
+ { "pclstyle", &lj4_font::style, 0, 32767 },
+ { "pclproportional", &lj4_font::proportional, 0, 1 },
+ { "pcltypeface", &lj4_font::typeface, 0, 65535 },
+};
+
+void lj4_font::handle_unknown_font_command(const char *command,
+ const char *arg,
+ const char *filename, int lineno)
+{
+ for (unsigned int i = 0;
+ i < sizeof(command_table)/sizeof(command_table[0]); i++) {
+ if (strcmp(command, command_table[i].s) == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'%1' command requires an argument",
+ command);
+ char *ptr;
+ long n = strtol(arg, &ptr, 10);
+ if (n == 0 && ptr == arg)
+ fatal_with_file_and_line(filename, lineno,
+ "'%1' command requires numeric argument",
+ command);
+ if (n < command_table[i].min) {
+ error_with_file_and_line(filename, lineno,
+ "argument for '%1' command must not be less than %2",
+ command, command_table[i].min);
+ n = command_table[i].min;
+ }
+ else if (n > command_table[i].max) {
+ error_with_file_and_line(filename, lineno,
+ "argument for '%1' command must not be greater than %2",
+ command, command_table[i].max);
+ n = command_table[i].max;
+ }
+ this->*command_table[i].ptr = int(n);
+ break;
+ }
+ }
+}
+
+class lj4_printer : public printer {
+public:
+ lj4_printer(int);
+ ~lj4_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *name);
+ void draw(int code, int *p, int np, const environment *env);
+ void begin_page(int);
+ void end_page(int page_length);
+ font *make_font(const char *);
+ void end_of_line();
+private:
+ void set_line_thickness(int size, int dot = 0);
+ void hpgl_init();
+ void hpgl_start();
+ void hpgl_end();
+ int moveto(int hpos, int vpos);
+ int moveto1(int hpos, int vpos);
+
+ int cur_hpos;
+ int cur_vpos;
+ lj4_font *cur_font;
+ int cur_size;
+ unsigned short cur_symbol_set;
+ int x_offset;
+ int line_thickness;
+ double pen_width;
+ double hpgl_scale;
+ int hpgl_inited;
+ int paper_size;
+};
+
+inline
+int lj4_printer::moveto(int hpos, int vpos)
+{
+ if (cur_hpos != hpos || cur_vpos != vpos || cur_hpos < 0)
+ return moveto1(hpos, vpos);
+ else
+ return 1;
+}
+
+inline
+void lj4_printer::hpgl_start()
+{
+ fputs("\033%1B", stdout);
+}
+
+inline
+void lj4_printer::hpgl_end()
+{
+ fputs(";\033%0A", stdout);
+}
+
+lj4_printer::lj4_printer(int ps)
+: cur_hpos(-1),
+ cur_font(0),
+ cur_size(0),
+ cur_symbol_set(0),
+ line_thickness(-1),
+ pen_width(-1.0),
+ hpgl_inited(0)
+{
+ if (7200 % font::res != 0)
+ fatal("invalid resolution %1: resolution must be a factor of 7200",
+ font::res);
+ fputs("\033E", stdout); // reset
+ if (font::res != 300)
+ printf("\033&u%dD", font::res); // unit of measure
+ if (ncopies > 0)
+ printf("\033&l%uX", ncopies);
+ paper_size = 0; // default to letter
+ if (font::papersize) {
+ int n = lookup_paper_size(font::papersize);
+ if (n < 0)
+ error("ignoring invalid paper format '%1'", font::papersize);
+ else
+ paper_size = n;
+ }
+ if (ps >= 0)
+ paper_size = ps;
+ printf("\033&l%dA" // paper format
+ "\033&l%dO" // orientation
+ "\033&l0E", // no top margin
+ paper_table[paper_size].code,
+ landscape_flag != 0);
+ if (landscape_flag)
+ x_offset = paper_table[paper_size].x_offset_landscape;
+ else
+ x_offset = paper_table[paper_size].x_offset_portrait;
+ x_offset = (x_offset * font::res) / 300;
+ if (duplex_flag)
+ printf("\033&l%dS", duplex_flag);
+}
+
+lj4_printer::~lj4_printer()
+{
+ fputs("\033E", stdout);
+}
+
+void lj4_printer::begin_page(int)
+{
+}
+
+void lj4_printer::end_page(int)
+{
+ putchar('\f');
+ cur_hpos = -1;
+}
+
+void lj4_printer::end_of_line()
+{
+ cur_hpos = -1; // force absolute motion
+}
+
+inline
+int is_unprintable(unsigned char c)
+{
+ return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27);
+}
+
+void lj4_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ int code = f->get_code(g);
+
+ unsigned char ch = code & 0xff;
+ unsigned short symbol_set = code >> 8;
+ if (symbol_set != cur_symbol_set) {
+ printf("\033(%d%c", symbol_set/32, (symbol_set & 31) + 64);
+ cur_symbol_set = symbol_set;
+ }
+ if (f != cur_font) {
+ lj4_font *psf = (lj4_font *)f;
+ // FIXME only output those that are needed
+ printf("\033(s%dp%ds%db%dT",
+ psf->proportional,
+ psf->style,
+ psf->weight,
+ psf->typeface);
+ if (!psf->proportional || !cur_font || !cur_font->proportional)
+ cur_size = 0;
+ cur_font = psf;
+ }
+ if (env->size != cur_size) {
+ if (cur_font->proportional) {
+ static const char *quarters[] = { "", ".25", ".5", ".75" };
+ printf("\033(s%d%sV", env->size/4, quarters[env->size & 3]);
+ }
+ else {
+ double pitch = double(font::res)/w;
+ // PCL uses the next largest pitch, so round it down.
+ pitch = floor(pitch*100.0)/100.0;
+ printf("\033(s%.2fH", pitch);
+ }
+ cur_size = env->size;
+ }
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ if (is_unprintable(ch))
+ fputs("\033&p1X", stdout);
+ putchar(ch);
+ cur_hpos += w;
+}
+
+int lj4_printer::moveto1(int hpos, int vpos)
+{
+ if (hpos < x_offset || vpos < 0)
+ return 0;
+ fputs("\033*p", stdout);
+ if (cur_hpos < 0)
+ printf("%dx%dY", hpos - x_offset, vpos);
+ else {
+ if (cur_hpos != hpos)
+ printf("%s%d%c", hpos > cur_hpos ? "+" : "",
+ hpos - cur_hpos, vpos == cur_vpos ? 'X' : 'x');
+ if (cur_vpos != vpos)
+ printf("%s%dY", vpos > cur_vpos ? "+" : "", vpos - cur_vpos);
+ }
+ cur_hpos = hpos;
+ cur_vpos = vpos;
+ return 1;
+}
+
+void lj4_printer::draw(int code, int *p, int np, const environment *env)
+{
+ switch (code) {
+ case 'R':
+ {
+ if (np != 2) {
+ error("2 arguments required for rule");
+ break;
+ }
+ int hpos = env->hpos;
+ int vpos = env->vpos;
+ int hsize = p[0];
+ int vsize = p[1];
+ if (hsize < 0) {
+ hpos += hsize;
+ hsize = -hsize;
+ }
+ if (vsize < 0) {
+ vpos += vsize;
+ vsize = -vsize;
+ }
+ if (!moveto(hpos, vpos))
+ return;
+ printf("\033*c%da%db0P", hsize, vsize);
+ break;
+ }
+ case 'l':
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ set_line_thickness(env->size, p[0] == 0 && p[1] == 0);
+ printf("PD%d,%d", p[0], p[1]);
+ hpgl_end();
+ break;
+ case 'p':
+ case 'P':
+ {
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ if (code == 'p')
+ set_line_thickness(env->size);
+ printf("PMPD%d", p[0]);
+ for (int i = 1; i < np; i++)
+ printf(",%d", p[i]);
+ printf("PM2%cP", code == 'p' ? 'E' : 'F');
+ hpgl_end();
+ break;
+ }
+ case '~':
+ {
+ if (np & 1) {
+ error("even number of arguments required for spline");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for spline");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ set_line_thickness(env->size);
+ printf("PD%d,%d", p[0]/2, p[1]/2);
+ const int tnum = 2;
+ const int tden = 3;
+ if (np > 2) {
+ fputs("BR", stdout);
+ for (int i = 0; i < np - 2; i += 2) {
+ if (i != 0)
+ putchar(',');
+ printf("%d,%d,%d,%d,%d,%d",
+ (p[i]*tnum)/(2*tden),
+ (p[i + 1]*tnum)/(2*tden),
+ p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden),
+ p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden),
+ (p[i] - p[i]/2) + p[i + 2]/2,
+ (p[i + 1] - p[i + 1]/2) + p[i + 3]/2);
+ }
+ }
+ printf("PR%d,%d", p[np - 2] - p[np - 2]/2, p[np - 1] - p[np - 1]/2);
+ hpgl_end();
+ break;
+ }
+ case 'c':
+ case 'C':
+ // troff adds an extra argument to C
+ if (np != 1 && !(code == 'C' && np == 2)) {
+ error("1 argument required for circle");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos + p[0]/2, env->vpos))
+ return;
+ hpgl_start();
+ if (code == 'c') {
+ set_line_thickness(env->size);
+ printf("CI%d", p[0]/2);
+ }
+ else
+ printf("WG%d,0,360", p[0]/2);
+ hpgl_end();
+ break;
+ case 'e':
+ case 'E':
+ if (np != 2) {
+ error("2 arguments required for ellipse");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos + p[0]/2, env->vpos))
+ return;
+ hpgl_start();
+ printf("SC0,%.4f,0,-%.4f,2", hpgl_scale * double(p[0])/p[1], hpgl_scale);
+ if (code == 'e') {
+ set_line_thickness(env->size);
+ printf("CI%d", p[1]/2);
+ }
+ else
+ printf("WG%d,0,360", p[1]/2);
+ printf("SC0,%.4f,0,-%.4f,2", hpgl_scale, hpgl_scale);
+ hpgl_end();
+ break;
+ case 'a':
+ {
+ if (np != 4) {
+ error("4 arguments required for arc");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ set_line_thickness(env->size);
+ double c[2];
+ if (adjust_arc_center(p, c)) {
+ double sweep = ((atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])
+ - atan2(-c[1], -c[0]))
+ * 180.0/PI);
+ if (sweep > 0.0)
+ sweep -= 360.0;
+ printf("PDAR%d,%d,%f", int(c[0]), int(c[1]), sweep);
+ }
+ else
+ printf("PD%d,%d", p[0] + p[2], p[1] + p[3]);
+ hpgl_end();
+ }
+ break;
+ case 'f':
+ if (np != 1 && np != 2) {
+ error("1 argument required for fill");
+ break;
+ }
+ hpgl_init();
+ hpgl_start();
+ if (p[0] >= 0 && p[0] <= 1000)
+ printf("FT10,%d", p[0]/10);
+ hpgl_end();
+ break;
+ case 'F':
+ // not implemented yet
+ break;
+ case 't':
+ {
+ if (np == 0) {
+ line_thickness = -1;
+ }
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ }
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+}
+
+void lj4_printer::hpgl_init()
+{
+ if (hpgl_inited)
+ return;
+ hpgl_inited = 1;
+ hpgl_scale = double(DEFAULT_HPGL_UNITS)/font::res;
+ printf("\033&f0S" // push position
+ "\033*p0x0Y" // move to 0,0
+ "\033*c%dx%dy0T" // establish picture frame
+ "\033%%1B" // switch to HPGL
+ "SP1SC0,%.4f,0,-%.4f,2IR0,100,0,100" // set up scaling
+ "LA1,4,2,4" // round line ends and joins
+ "PR" // relative plotting
+ "TR0" // opaque
+ ";\033%%1A" // back to PCL
+ "\033&f1S", // pop position
+ MAX_PAPER_WIDTH, MAX_PAPER_HEIGHT,
+ hpgl_scale, hpgl_scale);
+}
+
+void lj4_printer::set_line_thickness(int size, int dot)
+{
+ double pw;
+ if (line_thickness < 0)
+ pw = (size * (line_width_factor * 25.4))/(font::sizescale * 72000.0);
+ else
+ pw = line_thickness*25.4/font::res;
+ if (dot && pw < MIN_DOT_PEN_WIDTH)
+ pw = MIN_DOT_PEN_WIDTH;
+ if (pw != pen_width) {
+ printf("PW%f", pw);
+ pen_width = pw;
+ }
+}
+
+font *lj4_printer::make_font(const char *nm)
+{
+ return lj4_font::load_lj4_font(nm);
+}
+
+printer *make_printer()
+{
+ return new lj4_printer(user_paper_size);
+}
+
+static
+int lookup_paper_size(const char *s)
+{
+ for (unsigned int i = 0;
+ i < sizeof(paper_table)/sizeof(paper_table[0]); i++) {
+ // FIXME Perhaps allow unique prefix.
+ if (strcasecmp(s, paper_table[i].name) == 0)
+ return i;
+ }
+ return -1;
+}
+
+static void usage(FILE *stream);
+
+extern "C" int optopt, optind;
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "c:d:F:I:lp:vw:", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'l':
+ landscape_flag = 1;
+ break;
+ case 'I':
+ // ignore include search path
+ break;
+ case ':':
+ if (optopt == 'd') {
+ fprintf(stderr, "duplex assumed to be long-side\n");
+ duplex_flag = 1;
+ } else
+ fprintf(stderr, "option -%c requires an argument\n", optopt);
+ fflush(stderr);
+ break;
+ case 'd':
+ if (!isdigit(*optarg)) // this ugly hack prevents -d without
+ optind--; // args from messing up the arg list
+ duplex_flag = atoi(optarg);
+ if (duplex_flag != 1 && duplex_flag != 2) {
+ fprintf(stderr, "odd value for duplex; assumed to be long-side\n");
+ duplex_flag = 1;
+ }
+ break;
+ case 'p':
+ {
+ int n = lookup_paper_size(optarg);
+ if (n < 0)
+ error("ignoring invalid paper format '%1'", font::papersize);
+ else
+ user_paper_size = n;
+ break;
+ }
+ case 'v':
+ printf("GNU grolj4 (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'c':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg)
+ error("argument for -c must be a positive integer");
+ else if (n <= 0 || n > 32767)
+ error("out of range argument for -c");
+ else
+ ncopies = unsigned(n);
+ break;
+ }
+ case 'w':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg)
+ error("argument for -w must be a non-negative integer");
+ else if (n < 0 || n > INT_MAX)
+ error("out of range argument for -w");
+ else
+ line_width_factor = int(n);
+ break;
+ }
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ SET_BINARY(fileno(stdout));
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-l] [-c n] [-d [n]] [-F dir] [-p paper-format]"
+ " [-w n] [file ...]\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/gropdf/TODO b/src/devices/gropdf/TODO
new file mode 100644
index 0000000..12042c2
--- /dev/null
+++ b/src/devices/gropdf/TODO
@@ -0,0 +1,31 @@
+pspic.tmac
+----------
+
+Equiv for gropdf is pdfpic (which is dependent on a program pdfbb (to
+extract MediaBox (etc.) from the pdf) which is not written yet! Meanwhile
+you could use \X'pdf: pdfpic filename -L|R|C wid (hgt) (linelen)' (-R and -C
+require a linelen) Wid or hgt may be zero (in which case the same scaling as
+the other axis is used). The disadvantage of this call (over pdfpic macro)
+is that it is transparent to groff, after placing the image the current X/Y
+position remains what it was, so you need to do your own 'motion control'
+(like a .sp) to "step over" the image you just placed.
+
+psfig.tmac
+----------
+
+No equiv for gropdf.
+
+psatk.tmac
+----------
+
+No equiv for gropdf.
+
+-I : search -I directory for included files
+
+-w : set line width
+
+Another \X : \X'ps: exec 0 setlinejoin'\X'ps: exec 0 setlinecap' for mom
+
+Cater for fonts with >255 glyphs (currently accessing a glyph above 255
+(i.e. \N[260]) causes a fail). This will be fixed when font subsetting is
+implemented.
diff --git a/src/devices/gropdf/gropdf.1.man b/src/devices/gropdf/gropdf.1.man
new file mode 100644
index 0000000..d1d39bb
--- /dev/null
+++ b/src/devices/gropdf/gropdf.1.man
@@ -0,0 +1,1845 @@
+.TH gropdf @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+gropdf \-
+.I groff
+output driver for Portable Document Format
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2011-2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_gropdf_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'ps' .ft \\$1
+. if '\\*(.T'pdf' .ft \\$1
+..
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY gropdf
+.RB [ \-dels ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-p\~\c
+.IR paper-format ]
+\#.RB [ \-w\~\c
+\#.IR n ]
+.RB [ \-u
+.RI [ cmap-file ]]
+.RB [ \-y\~\c
+.IR foundry ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY gropdf
+.B \-\-help
+.YS
+.
+.
+.SY gropdf
+.B \-v
+.
+.SY gropdf
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+PDF output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into Portable Document Format.
+.
+Normally,
+.I gropdf
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~pdf \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR gropdf .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I gropdf
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+See section \[lq]Font installation\[rq] below for a guide to installing
+fonts for
+.IR gropdf .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-d
+Include debug information as comments within the PDF.
+.
+Also produces an uncompressed PDF.
+.
+.
+.TP
+.B \-e
+Forces
+.I gropdf
+to embed
+.I all
+fonts (even the 14 base PDF fonts).
+.
+.
+.TP
+.BI \-F " dir"
+Prepend directory
+.IR dir /dev name
+to the search path for font, and device description files;
+.I name
+is the name of the device, usually
+.BR pdf .
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+for files named in
+.B \[rs]X\[aq]pdf: pdfpic\[aq]
+device control commands.
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.
+.TP
+.B \-l
+Orient the document in landscape format.
+.
+.TP
+.BI \-p " paper-format"
+Set the physical dimensions of the output medium.
+.
+This overrides the
+.BR papersize ,
+.BR paperlength ,
+and
+.B paperwidth
+directives in the
+.I DESC
+file;
+it accepts the same arguments as the
+.B papersize
+directive.
+.
+See
+.MR groff_font @MAN5EXT@
+for details.
+.
+.
+.TP
+.B \-s
+Append a comment line to end of PDF showing statistics,
+i.e.\& number of pages in document.
+.
+Ghostscript's
+.B ps2pdf
+complains about this line if it is included, but works anyway.
+.
+.
+.TP
+.BR \-u \~[\c
+.IR cmap-file ]
+.I gropdf
+normally includes a ToUnicode CMap with any font created using
+.I text.enc
+as the encoding file,
+this makes it easier to search for words which contain ligatures.
+.
+You can include your own CMap by specifying a
+.I cmap-file
+or have no CMap at all by omitting the argument.
+.
+.
+.\" .TP
+.\" .BI \-w n
+.\" Lines should be drawn using a thickness of
+.\" .IR n \~\c
+.\" thousandths of an em.
+.\" .
+.\" If this option is not given, the line thickness defaults to
+.\" 0.04\~em.
+.\" .
+.\" .
+.TP
+.BI \-y " foundry"
+Set the foundry to use for selecting fonts of the same name.
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+The input to
+.I gropdf
+must be in the format output by
+.MR @g@troff @MAN1EXT@ .
+.
+This is described in
+.MR groff_out @MAN5EXT@ .
+.
+In addition, the device and font description files for the device used
+must meet certain requirements:
+.
+The resolution must be an integer multiple of\~72 times the
+.BR sizescale .
+.
+The
+.B pdf
+device uses a resolution of 72000 and a sizescale of 1000.
+.
+.
+.LP
+The device description file must contain a valid paper format;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.I gropdf
+uses the same Type\~1 Adobe PostScript fonts as the
+.B grops
+device driver.
+.
+Although the PDF Standard allows the use of other font types (like
+TrueType) this implementation only accepts the Type\~1 PostScript
+font.
+.
+Fewer Type\~1 fonts are supported natively in PDF documents than the
+standard 35 fonts supported by
+.B grops
+and all PostScript printers, but all the fonts are available since any
+which aren't supported natively are automatically embedded in the
+PDF.
+.
+.
+.LP
+.I gropdf
+supports the concept of foundries,
+that is different versions of basically the same font.
+.
+During install a
+.I Foundry
+file controls where fonts are found and builds
+.I groff
+fonts from the files it discovers on your system.
+.
+.
+.LP
+Each font description file must contain a command
+.
+.IP
+.BI internalname\ psname
+.
+.LP
+which says that the PostScript name of the font is
+.IR psname .
+.
+Lines starting with
+.B #
+and blank lines are ignored.
+.
+The code for each character given in the font file must correspond
+to the code in the default encoding for the font.
+.
+This code can be used with the
+.B \[rs]N
+escape sequence in
+.B troff
+to select the character,
+even if the character does not have a
+.I groff
+name.
+.
+Every character in the font file must exist in the PostScript font, and
+the widths given in the font file must match the widths used
+in the PostScript font.
+.
+.
+.LP
+Note that
+.I gropdf
+is currently only able to display the first 256 glyphs in any font.
+This restriction will be lifted in a later version.
+.
+.
+.\" .LP
+.\" Note that
+.\" .B grops
+.\" is able to display all glyphs in a PostScript font, not only 256.
+.\" .I enc_file
+.\" (or the default encoding if no encoding file specified) just defines
+.\" the order of glyphs for the first 256 characters;
+.\" all other glyphs are accessed with additional encoding vectors which
+.\" .B grops
+.\" produces on the fly.
+.
+.
+.LP
+.I gropdf
+can automatically include the downloadable fonts necessary
+to print the document.
+.
+Fonts may be in PFA or PFB format.
+.LP
+.
+Any downloadable fonts which should, when required, be included by
+.I gropdf
+must be listed in the file
+.IR @FONTDIR@/\:\%devpdf/\:\%download ;
+this should consist of lines of the form
+.
+.IP
+.I
+foundry font filename
+.
+.LP
+where
+.I foundry
+is the foundry name or blank for the default foundry.
+.
+.I font
+is the PostScript name of the font,
+and
+.I filename
+is the name of the file containing the font;
+lines beginning with
+.B #
+and blank lines are ignored;
+fields must be separated by tabs
+(spaces are
+.B not
+allowed);
+.I filename
+is searched for using the same mechanism that is used
+for
+.I groff
+font metric files.
+.
+The
+.I download
+file itself is also sought using this mechanism.
+.
+Foundry names are usually a single character
+(such as \[oq]U\[cq] for the URW foundry)
+or empty for the default foundry.
+.
+This default uses the same fonts as
+.I ghostscript
+uses when it embeds fonts in a PDF file.
+.
+.
+.LP
+In the default setup there are styles called
+.BR R ,
+.BR I ,
+.BR B ,
+and
+.B BI
+mounted at font positions 1 to\~4.
+.
+The fonts are grouped into families
+.BR A ,
+.BR BM ,
+.BR C ,
+.BR H ,
+.BR HN ,
+.BR N ,
+.BR P ,
+and\~\c
+.B T
+having members in each of these styles:
+.
+.RS
+.TP
+.B AR
+.FT AR
+AvantGarde-Book
+.FT
+.
+.TQ
+.B AI
+.FT AI
+AvantGarde-BookOblique
+.FT
+.
+.TQ
+.B AB
+.FT AB
+AvantGarde-Demi
+.FT
+.
+.TQ
+.B ABI
+.FT ABI
+AvantGarde-DemiOblique
+.FT
+.
+.TQ
+.B BMR
+.FT BMR
+Bookman-Light
+.FT
+.
+.TQ
+.B BMI
+.FT BMI
+Bookman-LightItalic
+.FT
+.
+.TQ
+.B BMB
+.FT BMB
+Bookman-Demi
+.FT
+.
+.TQ
+.B BMBI
+.FT BMBI
+Bookman-DemiItalic
+.FT
+.
+.TQ
+.B CR
+.FT CR
+Courier
+.FT
+.
+.TQ
+.B CI
+.FT CI
+Courier-Oblique
+.FT
+.
+.TQ
+.B CB
+.FT CB
+Courier-Bold
+.FT
+.
+.TQ
+.B CBI
+.FT CBI
+Courier-BoldOblique
+.FT
+.
+.TQ
+.B HR
+.FT HR
+Helvetica
+.FT
+.
+.TQ
+.B HI
+.FT HI
+Helvetica-Oblique
+.FT
+.
+.TQ
+.B HB
+.FT HB
+Helvetica-Bold
+.FT
+.
+.TQ
+.B HBI
+.FT HBI
+Helvetica-BoldOblique
+.FT
+.
+.TQ
+.B HNR
+.FT HNR
+Helvetica-Narrow
+.FT
+.
+.TQ
+.B HNI
+.FT HNI
+Helvetica-Narrow-Oblique
+.FT
+.
+.TQ
+.B HNB
+.FT HNB
+Helvetica-Narrow-Bold
+.FT
+.
+.TQ
+.B HNBI
+.FT HNBI
+Helvetica-Narrow-BoldOblique
+.FT
+.
+.TQ
+.B NR
+.FT NR
+NewCenturySchlbk-Roman
+.FT
+.
+.TQ
+.B NI
+.FT NI
+NewCenturySchlbk-Italic
+.FT
+.
+.TQ
+.B NB
+.FT NB
+NewCenturySchlbk-Bold
+.FT
+.
+.TQ
+.B NBI
+.FT NBI
+NewCenturySchlbk-BoldItalic
+.FT
+.
+.TQ
+.B PR
+.FT PR
+Palatino-Roman
+.FT
+.
+.TQ
+.B PI
+.FT PI
+Palatino-Italic
+.FT
+.
+.TQ
+.B PB
+.FT PB
+Palatino-Bold
+.FT
+.
+.TQ
+.B PBI
+.FT PBI
+Palatino-BoldItalic
+.FT
+.
+.TQ
+.B TR
+.FT TR
+Times-Roman
+.FT
+.
+.TQ
+.B TI
+.FT TI
+Times-Italic
+.FT
+.
+.TQ
+.B TB
+.FT TB
+Times-Bold
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+Times-BoldItalic
+.FT
+.RE
+.
+.
+.LP
+There is also the following font which is not a member of a family:
+.
+.RS
+.TP
+.B ZCMI
+.FT ZCMI
+ZapfChancery-MediumItalic
+.FT
+.RE
+.
+.
+.LP
+There are also some special fonts called
+.B S
+for the PS Symbol font.
+.
+The lower case greek characters are automatically slanted (to match
+the SymbolSlanted font (SS) available to PostScript).
+.
+Zapf Dingbats is available as
+.BR ZD ;
+the \[lq]hand pointing left\[rq] glyph
+.RB ( \[rs][lh] )
+is available since it has been defined using the
+.B \[rs]X\[aq]pdf: xrev\[aq]
+device control command,
+which reverses the direction of letters within words.
+.
+.
+.LP
+The default color for
+.B \[rs]m
+and
+.B \[rs]M
+is black.
+.
+.
+.LP
+.I gropdf
+understands some of the device control commands supported by
+.MR grops 1 .
+.
+.
+.TP
+.B \[rs]X\[aq]ps: invis\[aq]
+Suppress output.
+.
+.
+.TP
+.B \[rs]X\[aq]ps: endinvis\[aq]
+Stop suppressing output.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec gsave currentpoint 2 copy translate\~" n\~\c
+.B rotate neg exch neg exch translate\[aq]
+where
+.I n
+is the angle of rotation.
+.
+This is to support the
+.B align
+command in
+.MR @g@pic 1 .
+.
+.
+.TP
+.B \[rs]X\[aq]ps: exec grestore\[aq]
+Used by
+.MR @g@pic 1
+to restore state after rotation.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec " "n\~" "setlinejoin\[aq]"
+where
+.I n
+can be one of the following values.
+.
+.
+.IP
+0 = Miter join
+.br
+1 = Round join
+.br
+2 = Bevel join
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec " "n " "setlinecap\[aq]"
+where
+.I n
+can be one of the following values.
+.
+.
+.IP
+0 = Butt cap
+.br
+1 = Round cap, and
+.br
+2 = Projecting square cap
+.
+.
+.LP
+.TP
+.BR "\[rs]X\[aq]ps:\~" .\|.\|.\& "\~pdfmark\[aq]"
+All the
+.I pdfmark
+macros installed by using
+.I \-m pdfmark
+or
+.I \-m mspdf
+(see documentation in
+.IR pdfmark.pdf ).
+.
+A subset of these macros are installed automatically when you use
+.B \-Tpdf
+so you should not need to use
+.RB \[lq] "\-m pdfmark" \[rq]
+to access most PDF functionality.
+.
+.
+.LP
+.I gropdf
+also supports a subset of the commands introduced in
+.IR present.tmac .
+.
+Specifically it supports:-
+.
+.
+.IP
+PAUSE
+.br
+BLOCKS
+.br
+BLOCKE
+.
+.
+.LP
+Which allows you to create presentation type PDFs.
+.
+Many of the other
+commands are already available in other macro packages.
+.
+.
+.LP
+These commands are implemented with
+.I groff
+X commands:-
+.
+.
+.LP
+.TP
+.B \[rs]X\[aq]ps: exec %%%%PAUSE\[aq]
+The section before this is treated as a block and is introduced using
+the current
+.B BLOCK
+transition setting
+(see
+.RB \[lq] "\[rs]X\[aq]pdf: transition\[aq]" \[rq]
+below).
+.
+Equivalently,
+.B \%.pdfpause
+is available as a macro.
+.TP
+.B \[rs]X\[aq]ps: exec %%%%BEGINONCE\[aq]
+Any text following this command (up to %%%%ENDONCE) is shown only once,
+the next %%%%PAUSE will remove it.
+If producing a non-presentation PDF, i.e.\&
+ignoring the pauses, see
+.I \%GROPDF_NOSLIDE
+below, this text is ignored.
+.LP
+.TP
+.B \[rs]X\[aq]ps: exec %%%%ENDONCE\[aq]
+This terminates the block defined by %%%%BEGINONCE.
+This pair of commands
+is what implements the \&.BLOCKS Once/.BLOCKE commands in
+.IR present.tmac .
+.
+.
+.LP
+The
+.I mom
+macro package already integrates these extensions,
+so you can build slides with
+.IR mom .
+.
+.
+.LP
+If you use
+.I present.tmac
+with
+.I gropdf
+there is no need to run the program
+.MR presentps @MAN1EXT@
+since the output will already be a presentation PDF.
+.
+.
+.LP
+All other
+.B ps:
+tags are silently ignored.
+.
+.
+.LP
+One
+.B \[rs]X
+device control command used by the DVI driver is also recognised.
+.
+.
+.TP
+.BI \[rs]X\[aq]papersize= paper-format \[aq]
+where the
+.I paper-format
+parameter is the same as that to the
+.B papersize
+directive.
+.
+See
+.MR groff_font @MAN5EXT@ .
+.
+This means that you can alter the page size at will within the PDF file
+being created by
+.IR gropdf .
+.
+If you do want to change the paper format,
+it must be done before you start creating the page.
+.
+.
+.LP
+.I gropdf
+supports several more device control features using the
+.B pdf:
+tag.
+.
+Some have counterpart
+.I convenience macros
+that take the same arguments and behave equivalently.
+.
+.
+.TP
+.BI "\[rs]X\[aq]pdf: pdfpic\~" file\~\c
+.IR "alignment width height line-length" \[aq]
+Place an image of the specified
+.I width
+containing the PDF drawing from file
+.I file
+of desired
+.I width
+and
+.I height
+(if
+.I height
+is missing or zero then it is scaled proportionally).
+.
+If
+.I alignment
+is
+.B \-L
+the drawing is left-aligned.
+.
+If it is
+.B \-C
+or
+.B \-R
+a
+.I line-length
+greater than the width of the drawing is required as well.
+.
+If
+.I width
+is specified as zero then the width is scaled in proportion to the
+height.
+.
+.\" .IP
+.\" See
+.\" .BR groff_tmac (@MAN7EXT@)
+.\" for a description of the
+.\" .B PSPIC
+.\" macro which provides a convenient high-level interface for inclusion
+.\" of PostScript graphics.
+.
+.TP
+.B \[rs]X\[aq]pdf: xrev\[aq]
+Toggle the reversal of glyph direction.
+.
+This feature works \[lq]letter by letter\[rq],
+that is,
+each letter in a word is reversed left-to-right,
+not the entire word.
+.
+One application is the reversal of glyphs in the Zapf Dingbats font.
+.
+To restore the normal glyph orientation,
+repeat the command.
+.
+.
+.TP
+.BI "\[rs]X\[aq]pdf: markstart " "/ANN-definition" \[aq]
+.TQ
+.B \[rs]X\[aq]pdf: markend\[aq]
+Macros that support PDF bookmarks use these calls internally to
+start and stop (respectively) the placement of the bookmark's
+.I hot spot;
+the user will have called
+.RB \[lq] .pdfhref\~L \[rq]
+with the text of the hot spot.
+.
+Normally,
+these are never used except from within the
+.I pdfmark
+macros.
+.
+.
+.TP
+.B \[rs]X\[aq]pdf: marksuspend\[aq]
+.TQ
+.B \[rs]X\[aq]pdf: markrestart\[aq]
+If you use a page location trap to produce a header or footer,
+or otherwise interrupt a document's text,
+you need to use these commands if a PDF
+.I hot spot
+crosses a trap boundary;
+otherwise any text output by the trap will be marked as part of the hot
+spot.
+.
+To prevent this error,
+place these device control commands or their corresponding
+convenience macros
+.B \%.pdfmarksuspend
+and
+.B \%.pdfmarkrestart
+at the start and end of the trap macro,
+respectively.
+.
+.
+.TP
+.BI "\[rs]X\[aq]pdf: pagename\~" name \[aq]
+Assign the current page a
+.IR name .
+.
+All documents bear two default names,
+.RB \[oq] top "\[cq] and \[oq]" bottom \[cq].
+.
+The convenience macro for this command is
+.BR \%.pdfpagename .
+.
+.
+.TP
+.BI "\[rs]X'pdf: switchtopage\~" "when name" \[aq]
+Normally each new page is appended to the end of the document,
+this command allows following pages to be inserted at a
+.I \[oq]named\[cq]
+position within the document (see pagename command above).
+.I \[oq]when\[cq]
+can be either
+.RI \[oq] after "\[cq] or \[oq]" before \[cq].
+If it is omitted it defaults to
+.RI \[oq] before \[cq].
+.
+It should be used at the end of the page before you want the switch to
+happen.
+.
+This allows pages such as a TOC to be moved to elsewhere in the
+document,
+but more esoteric uses are possible.
+.
+The convenience macro for this command is
+.BR \%.pdfswitchtopage .
+.
+.
+.TP
+.BI \[rs]X\[aq]pdf:\~transition\~ feature\~\c
+.IB "mode duration dimension motion direction scale bool" \[aq]
+where
+.I feature
+can be either SLIDE or BLOCK.
+When it is SLIDE the transition is used
+when a new slide is introduced to the screen,
+if BLOCK then this transition is used for the individual blocks which
+make up the slide.
+.
+.
+.IP
+.I mode
+is the transition type between slides:-
+.RS
+.IP
+.B Split
+- Two lines sweep across the screen, revealing the new page.
+The lines
+may be either horizontal or vertical and may move inward from the
+edges of the page or outward from the center, as specified by the
+.I dimension
+and
+.I motion
+entries, respectively.
+.br
+.B Blinds
+- Multiple lines, evenly spaced across the screen, synchronously
+sweep in the same direction to reveal the new page.
+The lines may be
+either horizontal or vertical, as specified by the
+.I dimension
+entry.
+Horizontal
+lines move downward; vertical lines move to the right.
+.br
+.B Box
+- A rectangular box sweeps inward from the edges of the page or
+outward from the center, as specified by the
+.I motion
+entry, revealing the new page.
+.br
+.B Wipe
+- A single line sweeps across the screen from one edge to the other in
+the direction specified by the
+.I direction
+entry, revealing the new page.
+.br
+.B Dissolve
+- The old page dissolves gradually to reveal the new one.
+.br
+.B Glitter
+- Similar to Dissolve,
+except that the effect sweeps across the page in a wide band moving from
+one side of the screen to the other in the direction specified by the
+.I direction
+entry.
+.br
+.B R
+- The new page simply replaces the old one with no special transition
+effect; the
+.I direction
+entry shall be ignored.
+.br
+.B Fly
+- (PDF 1.5) Changes are flown out or in (as specified by
+.IR motion ),
+in the
+direction specified by
+.IR direction ,
+to or from a location that is offscreen except
+when
+.I direction
+is
+.BR None .
+.br
+.B Push
+- (PDF 1.5) The old page slides off the screen while the new page
+slides in, pushing the old page out in the direction specified by
+.IR direction .
+.br
+.B Cover
+- (PDF 1.5) The new page slides on to the screen in the direction
+specified by
+.IR direction ,
+covering the old page.
+.br
+.B Uncover
+- (PDF 1.5) The old page slides off the screen in the direction
+specified by
+.IR direction ,
+uncovering the new page in the direction
+specified by
+.IR direction .
+.br
+.B Fade
+- (PDF 1.5) The new page gradually becomes visible through the
+old one.
+.LP
+.RE
+.IP
+.I duration
+is the length of the transition in seconds (default 1).
+.LP
+.IP
+.I dimension
+(Optional;
+.BR Split " and " Blinds
+transition styles only) The dimension in which the
+specified transition effect shall occur:
+.B H
+Horizontal, or
+.B V
+Vertical.
+.LP
+.IP
+.I motion
+(Optional;
+.BR Split ,
+.BR Box " and " Fly
+transition styles only) The direction of motion for
+the specified transition effect:
+.B I
+Inward from the edges of the page, or
+.B O
+Outward from the center of the page.
+.LP
+.IP
+.I direction
+(Optional;
+.BR Wipe ,
+.BR Glitter ,
+.BR Fly ,
+.BR Cover ,
+.BR Uncover " and " Push
+transition styles only)
+The direction in which the specified transition effect shall moves,
+expressed in degrees counterclockwise starting from a left-to-right
+direction.
+If the value is a number, it shall be one of:
+.B 0
+= Left to right,
+.B 90
+= Bottom to top (Wipe only),
+.B 180
+= Right to left (Wipe only),
+.B 270
+= Top to bottom,
+.B 315
+= Top-left to bottom-right (Glitter only)
+The value can be
+.BR None ,
+which is relevant only for the
+.B Fly
+transition when the value of
+.I scale
+is not 1.0.
+.LP
+.IP
+.I scale
+(Optional; PDF 1.5;
+.B Fly
+transition style only) The starting or ending scale at
+which the changes shall be drawn.
+If
+.I motion
+specifies an inward transition, the scale
+of the changes drawn shall progress from
+.I scale
+to 1.0 over the course of the
+transition.
+If
+.I motion
+specifies an outward transition, the scale of the changes drawn
+shall progress from 1.0 to
+.I scale
+over the course of the transition
+.LP
+.IP
+.I bool
+(Optional; PDF 1.5;
+.B Fly
+transition style only) If
+.BR true ,
+the area that shall be flown
+in is rectangular and opaque.
+.LP
+.IP
+This command can be used by calling the macro
+.B .pdftransition
+using the parameters described above.
+Any of the parameters may be
+replaced with a "." which signifies the parameter retains its
+previous value, also any trailing missing parameters are ignored.
+.LP
+.IP
+.B Note:
+not all PDF Readers support any or all these transitions.
+.LP
+.
+.
+.TP
+.BI "\eX\[aq]pdf: background\~" "cmd left top right bottom weight" \[aq]
+.TQ
+.B "\eX\[aq]pdf: background off\[aq]"
+.TQ
+.BI "\eX\[aq]pdf: background footnote\~" bottom \[aq]
+produces a background rectangle on the page,
+where
+.RS
+.TP
+.I cmd
+is the command,
+which can be any of
+.RB \[lq] page | fill | box \[rq]
+in combination.
+.
+Thus,
+.RB \[lq] pagefill \[rq]
+would draw a rectangle which covers the whole current page size
+(in which case the rest of the parameters can be omitted because the box
+dimensions are taken from the current media size).
+.
+.RB \[lq] boxfill \[rq],
+on the other hand,
+requires the given dimensions to place the box.
+.
+Including
+.RB \[lq] fill \[rq]
+in the command will paint the rectangle with the current fill colour
+(as with
+.BR \[rs]M[] )
+and including
+.RB \[lq] box \[rq]
+will give the rectangle a border in the current stroke colour
+(as with
+.BR \[rs]m[] ).
+.
+.
+.IP
+.I cmd
+may also be
+.RB \[lq] off \[rq]
+on its own,
+which will terminate drawing the current box.
+.
+If you have specified a page colour with
+.RB \[lq] pagefill \[rq],
+it is always the first box in the stack,
+and if you specify it again,
+it will replace the first entry.
+.
+Be aware that the
+.RB \[lq] pagefill \[rq]
+box renders the page opaque,
+so tools that \[lq]watermark\[rq] PDF pages are unlikely to be
+successful.
+.
+To return the background to transparent,
+issue an
+.RB \[lq] off \[rq]
+command with no other boxes open.
+.
+.
+.IP
+Finally,
+.I cmd
+may be
+.RB \[lq] footnote \[rq]
+followed by a new value for
+.IR bottom ,
+which will be used for all open boxes on the current page.
+This is to allow room for footnote areas that grow while a page is
+processed
+(to accommodate multiple footnotes,
+for instance).
+.
+(If the value is negative,
+it is used as an offset from the bottom of the page.)
+.
+.
+.TP
+.I left
+.TQ
+.I top
+.TQ
+.I right
+.TQ
+.I bottom
+are the coordinates of the box.
+.
+The
+.I top
+and
+.I bottom
+coordinates are the minimum and maximum for the box,
+since the actual start of the box is
+.IR groff 's
+drawing position when you issue the command,
+and the bottom of the box is the point where you turn the box
+.RB \[lq] off \[rq].
+.
+The top and bottom coordinates are used only if the box drawing extends
+onto the next page;
+ordinarily,
+they would be set to the header and footer margins.
+.
+.
+.TP
+.I weight
+provides the line width for the border if
+.RB \[lq] box \[rq]
+is included in the command.
+.
+.
+.P
+The convenience macro for this escape sequence is
+.BR .pdfbackground .
+.
+An
+.I sboxes
+macro file is also available;
+see
+.MR groff_tmac @MAN5EXT@ .
+.RE
+.
+.
+.\" ====================================================================
+.SS Macros
+.\" ====================================================================
+.
+.IR gropdf 's
+support macros in
+.I pdf\.tmac
+define the convenience macros described above.
+.
+Some features have no direct device control command counterpart.
+.
+.
+.\" pdfhref
+.
+.
+.TP
+.BI ".pdfinfo /" "field content"\~\c
+\&.\|.\|.
+Define PDF metadata.
+.
+.I field
+may be be one of
+.BR Title ,
+.BR Author ,
+.BR Subject ,
+.BR Keywords ,
+or another datum supported by the PDF standard or your reader.
+.
+.I field
+must be prefixed with a slash.
+.
+.
+.\" ====================================================================
+.SS "Importing graphics"
+.\" ====================================================================
+.
+.I gropdf
+supports only the inclusion of other PDF files for inline images.
+.
+Such a PDF file may,
+however,
+contain any of the graphic formats supported by
+the PDF standard,
+such as JPEG/JFIF,
+PNG,
+and GIF.
+.
+Any application that outputs PDF can thus be used to prepare files for
+embedding in documents processed by
+.I groff
+and
+.IR gropdf .
+.
+.
+.P
+The PDF file you wish to insert must be a single page and the drawing
+must just fit inside the media size of the PDF file.
+.
+In
+.MR inkscape 1
+or
+.MR gimp 1 ,
+for example,
+make sure the canvas size just fits the image.
+.
+.
+.P
+The PDF parser
+.I gropdf
+implements has not been rigorously tested with all applications that
+produce PDF.
+.
+If you find a single-page PDF which fails to import properly,
+try processing it with the
+.MR pdftk 1
+program.
+.
+.
+.RS
+.EX
+pdftk\~\c
+.I existing-file\~\c
+output\~\c
+.I new-file
+.EE
+.RE
+.
+You may find that
+.I new-file
+imports successfully.
+.
+.
+.\" ====================================================================
+.SS "TrueType and other font formats"
+.\" ====================================================================
+.
+.I gropdf
+does not yet support any font formats besides Adobe Type 1
+(PFA or PFB).
+.
+.
+.\" ====================================================================
+.SH "Font installation"
+.\" ====================================================================
+.
+The following is a step-by-step font installation guide for
+.I gropdf.
+.
+.
+.IP \[bu] 2n
+Convert your font to something
+.I groff
+understands.
+.
+This is a PostScript Type\~1 font in PFA or PFB format,
+together with an AFM file.
+.
+A PFA file begins as follows.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+%!PS\-AdobeFont\-1.0:
+.EE
+.RE \" but only one to get back to it
+.
+A PFB file contains this string as well,
+preceded by some non-printing bytes.
+.
+In the following steps,
+we will consider the use of CTAN's
+.UR https://\:ctan.org/\:tex\-archive/\:fonts/\:brushscr
+BrushScriptX-Italic
+.UE
+font in PFA format.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Convert the AFM file to a
+.I groff
+font description file with the
+.MR afmtodit @MAN1EXT@
+program.
+.
+For instance,
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+$ \c
+.B afmtodit BrushScriptX\-Italic.afm text.map BSI
+.EE
+.RE \" but only one to get back to it
+.
+converts the Adobe Font Metric file
+.I BrushScriptX\-Italic.afm
+to the
+.I groff
+font description file
+.IR BSI .
+.RE \" now restore left margin
+.
+.
+.IP
+If you have a font family which provides regular upright (roman),
+bold,
+italic,
+and
+bold-italic styles,
+(where \[lq]italic\[rq] may be \[lq]oblique\[rq] or \[lq]slanted\[rq]),
+we recommend using
+.BR R ,
+.BR B ,
+.BR I ,
+and
+.BR BI ,
+respectively,
+as suffixes to the
+.I groff
+font family name to enable
+.IR groff 's
+font family and style selection features.
+.
+An example is
+.IR groff 's
+built-in support for Times:
+the font family
+name is abbreviated as
+.BR T ,
+and the
+.I groff
+font names are therefore
+.BR TR ,
+.BR TB ,
+.BR TI ,
+and
+.BR TBI .
+.
+In our example,
+however,
+the BrushScriptX font is available in a single style only,
+italic.
+.
+.
+.IP \[bu]
+Install the
+.I groff
+font description file(s) in a
+.I devpdf
+subdirectory in the search path that
+.I groff
+uses for device and font file descriptions.
+.
+See the
+.I GROFF_FONT_PATH
+entry in section \[lq]Environment\[rq] of
+.MR @g@troff @MAN1EXT@
+for the current value of the font search path.
+.
+While
+.I groff
+doesn't directly use AFM files,
+it is a good idea to store them alongside its font description files.
+.
+.
+.IP \[bu]
+Register fonts in the
+.I devpdf/download
+file so they can be located for embedding in PDF files
+.I gropdf
+generates.
+.
+Only the first
+.I download
+file encountered in the font search path is read.
+.
+If in doubt,
+copy the default
+.I download
+file
+(see section \[lq]Files\[rq] below)
+to the first directory in the font search path and add your fonts there.
+.
+The PostScript font name used by
+.I gropdf
+is stored in the
+.B internalname
+field in the
+.I groff
+font description file.
+.
+(This name does not necessarily resemble the font's file name.)
+.
+If the font in our example had originated from a foundry named
+.BR Z ,
+we would add the following line to
+.IR download .
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+Z\[->]BrushScriptX\-Italic\[->]BrushScriptX\-Italic.pfa
+.EE
+.RE \" but only one to get back to it
+.
+A tab character,
+depicted as \[->],
+separates the fields.
+.
+The default foundry has no name:
+its field is empty and
+entries corresponding to it start with a tab character,
+as will the one in our example.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Test the selection and embedding of the new font.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+printf "\[rs]\[rs]f[BSI]Hello, world!\[rs]n" \
+| groff \-T pdf \-P \-e >hello.pdf
+see hello.pdf
+.EE
+.RE
+.RE
+.
+.
+.br
+.ne 5v
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+If,
+in the
+.I download
+file,
+the font file has been specified with a full path,
+no directories are searched.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROPDF_NOSLIDE
+If set and evaluates to a true value
+(to Perl),
+.\" XXX: The above is inconsistent with the way grotty(1) handles
+.\" "GROFF_NO_SGR".
+.I gropdf
+ignores commands specific to presentation PDFs,
+producing a normal PDF instead.
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using Perl's
+.I \%localtime()
+function and recorded in a PDF comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devpdf/\:DESC
+describes the
+.B pdf
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devpdf/ F
+describes the font known
+.RI as\~ F
+on device
+.BR pdf .
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devpdf/\:U\- F
+describes the font
+from the URW foundry
+(versus the Adobe default)
+known
+.RI as\~ F
+on device
+.BR pdf .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devpdf/\%download
+lists fonts available for embedding within the PDF document
+(by analogy to the
+.B ps
+device's downloadable font support).
+.
+.
+.\" XXX: Why are we shipping this but not BuildFoundries.pl?
+.TP
+.I @FONTDIR@/\:\%devpdf/\%Foundry
+is a data file used by the
+.I groff
+build system to locate PostScript Type\~1 fonts.
+.
+.
+.TP
+.I @FONTDIR@/\:\%devpdf/\:enc/\:\%text\:.enc
+describes the encoding scheme used by most PostScript Type\~1 fonts;
+the
+.B \%encoding
+directive of
+font description files for the
+.B pdf
+device refers to it.
+.
+.
+.TP
+.I @MACRODIR@/\:pdf\:.tmac
+defines macros for use with the
+.B pdf
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B pdf
+output device is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:\%pdfpic\:.tmac
+defines the
+.B PDFPIC
+macro for embedding images in a document;
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+It is automatically loaded by
+.I troffrc.
+.\"
+.\"
+.\" .TP
+.\" .B @MACRODIR@/pspic.tmac
+.\" Definition of
+.\" .B PSPIC
+.\" macro,
+.\" automatically loaded by
+.\" .BR ps.tmac .
+.\" .
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I gropdf
+was written and is maintained by
+.MT deri@\:chuzzlewit\:.myzen\:.co\:.uk
+Deri James
+.ME .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.TP
+.I @DOCDIR@/\:\%sboxes/\:\%msboxes\:.ms
+.TQ
+.I @DOCDIR@/\:\%sboxes/\:\%msboxes\:.pdf
+\[lq]Using PDF boxes with
+.I groff
+and the
+.I ms
+macros\[rq],
+by Deri James.
+.
+.
+.TP
+.I present.tmac
+is part of
+.UR https://\:bob\:.diertens\:.org/\:corner/\:useful/\:gpresent/
+.I gpresent
+.UE ,
+a software package by Bob Diertens that works with
+.I groff
+to produce presentations
+(\[lq]foils\[rq],
+or \[lq]slide decks\[rq]).
+.
+.
+.P
+.MR afmtodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_out @MAN5EXT@
+.\" Not actually referenced in above discussion.
+.\" .BR \%pfbtops (@MAN1EXT@),
+.\" .BR \%groff_tmac (@MAN5EXT@),
+.
+.
+.\" Clean up.
+.rm FT
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_gropdf_1_man_C]
+.do rr *groff_gropdf_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/gropdf/gropdf.am b/src/devices/gropdf/gropdf.am
new file mode 100644
index 0000000..3dbd865
--- /dev/null
+++ b/src/devices/gropdf/gropdf.am
@@ -0,0 +1,58 @@
+# Copyright (C) 2011-2020 Free Software Foundation, Inc.
+# Written by Deri James <deri@chuzzlewit.myzen.co.uk>
+# Automake migration by Bertrand Garrigues
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+gropdf_dir = $(top_srcdir)/src/devices/gropdf
+
+bin_SCRIPTS += gropdf pdfmom
+EXTRA_DIST += \
+ src/devices/gropdf/TODO \
+ src/devices/gropdf/gropdf.pl \
+ src/devices/gropdf/pdfmom.pl \
+ src/devices/gropdf/gropdf.1.man \
+ src/devices/gropdf/pdfmom.1.man
+
+man1_MANS += \
+ src/devices/gropdf/gropdf.1 \
+ src/devices/gropdf/pdfmom.1
+
+gropdf: $(gropdf_dir)/gropdf.pl $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)$(RM) $@ \
+ && sed -f $(SH_DEPS_SED_SCRIPT) \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ -e "s|[@]PERL[@]|$(PERL)|" \
+ -e "s|[@]GROFF_FONT_DIR[@]|$(fontpath)|" \
+ -e "s|[@]RT_SEP[@]|$(RT_SEP)|" $(gropdf_dir)/gropdf.pl \
+ >$@ \
+ && chmod +x $@
+
+pdfmom: $(gropdf_dir)/pdfmom.pl $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)$(RM) $@ \
+ && sed -f $(SH_DEPS_SED_SCRIPT) \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ -e "s|[@]RT_SEP[@]|$(RT_SEP)|" \
+ -e "s|[@]PERL[@]|$(PERL)|" $(gropdf_dir)/pdfmom.pl \
+ >$@ \
+ && chmod +x $@
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/gropdf/gropdf.pl b/src/devices/gropdf/gropdf.pl
new file mode 100644
index 0000000..c65a105
--- /dev/null
+++ b/src/devices/gropdf/gropdf.pl
@@ -0,0 +1,3928 @@
+#!@PERL@
+#
+# gropdf : PDF post processor for groff
+#
+# Copyright (C) 2011-2020 Free Software Foundation, Inc.
+# Written by Deri James <deri@chuzzlewit.myzen.co.uk>
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use Getopt::Long qw(:config bundling);
+
+use constant
+{
+ WIDTH => 0,
+ CHRCODE => 1,
+ PSNAME => 2,
+ ASSIGNED => 3,
+ USED => 4,
+};
+
+my $prog=$0;
+
+my $gotzlib=0;
+
+my $rc = eval
+{
+ require Compress::Zlib;
+ Compress::Zlib->import();
+ 1;
+};
+
+if($rc)
+{
+ $gotzlib=1;
+}
+else
+{
+ Warn("Perl module 'Compress::Zlib' not available; cannot compress"
+ . " this PDF");
+}
+
+my %cfg;
+
+$cfg{GROFF_VERSION}='@VERSION@';
+$cfg{GROFF_FONT_PATH}='@GROFF_FONT_DIR@';
+$cfg{RT_SEP}='@RT_SEP@';
+binmode(STDOUT);
+
+my @obj; # Array of PDF objects
+my $objct=0; # Count of Objects
+my $fct=0; # Output count
+my %fnt; # Used fonts
+my $lct=0; # Input Line Count
+my $src_name='';
+my %env; # Current environment
+my %fontlst; # Fonts Loaded
+my $rot=0; # Portrait
+my %desc; # Contents of DESC
+my %download; # Contents of downlopad file
+my $pages; # Pointer to /Pages object
+my $devnm='devpdf';
+my $cpage; # Pointer to current pages
+my $cpageno=0; # Object no of current page
+my $cat; # Pointer to catalogue
+my $dests; # Pointer to Dests
+my @mediabox=(0,0,595,842);
+my @defaultmb=(0,0,595,842);
+my $stream=''; # Current Text/Graphics stream
+my $cftsz=10; # Current font sz
+my $cft; # Current Font
+my $lwidth=1; # current linewidth
+my $linecap=1;
+my $linejoin=1;
+my $textcol=''; # Current groff text
+my $fillcol=''; # Current groff fill
+my $curfill=''; # Current PDF fill
+my $strkcol='';
+my $curstrk='';
+my @lin=(); # Array holding current line of text
+my @ahead=(); # Buffer used to hol the next line
+my $mode='g'; # Graphic (g) or Text (t) mode;
+my $xpos=0; # Current X position
+my $ypos=0; # Current Y position
+my $tmxpos=0;
+my $kernadjust=0;
+my $curkern=0;
+my $widtbl; # Pointer to width table for current font size
+my $origwidtbl; # Pointer to width table
+my $krntbl; # Pointer to kern table
+my $matrix="1 0 0 1";
+my $whtsz; # Current width of a space
+my $poschg=0; # V/H pending
+my $fontchg=0; # font change pending
+my $tnum=2; # flatness of B-Spline curve
+my $tden=3; # flatness of B-Spline curve
+my $linewidth=40;
+my $w_flg=0;
+my $nomove=0;
+my $pendmv=0;
+my $gotT=0;
+my $suppress=0; # Suppress processing?
+my %incfil; # Included Files
+my @outlev=([0,undef,0,0]); # Structure pdfmark /OUT entries
+my $curoutlev=\@outlev;
+my $curoutlevno=0; # Growth point for @curoutlev
+my $Foundry='';
+my $xrev=0; # Reverse x direction of font
+my $matrixchg=0;
+my $wt=-1;
+my $thislev=1;
+my $mark=undef;
+my $suspendmark=undef;
+my $boxmax=0;
+my %missing; # fonts in download files which are not found/readable
+
+
+
+my $n_flg=1;
+my $pginsert=-1; # Growth point for kids array
+my %pgnames; # 'names' of pages for switchtopage
+my @outlines=(); # State of Bookmark Outlines at end of each page
+my $custompaper=0; # Has there been an X papersize
+my $textenccmap=''; # CMap for groff text.enc encoding
+my @XOstream=();
+my @PageAnnots={};
+my $noslide=0;
+my $transition={PAGE => {Type => '/Trans', S => '', D => 1, Dm => '/H', M => '/I', Di => 0, SS => 1.0, B => 0},
+ BLOCK => {Type => '/Trans', S => '', D => 1, Dm => '/H', M => '/I', Di => 0, SS => 1.0, B => 0}};
+my $firstpause=0;
+my $present=0;
+my @bgstack; # Stack of background boxes
+my $bgbox=''; # Draw commands for boxes on this page
+
+$noslide=1 if exists($ENV{GROPDF_NOSLIDE}) and $ENV{GROPDF_NOSLIDE};
+
+my %ppsz=(
+ 'ledger'=>[1224,792],
+ 'legal'=>[612,1008],
+ 'letter'=>[612,792],
+ 'a0'=>[2384,3370],
+ 'a1'=>[1684,2384],
+ 'a2'=>[1191,1684],
+ 'a3'=>[842,1191],
+ 'a4'=>[595,842],
+ 'a5'=>[420,595],
+ 'a6'=>[297,420],
+ 'a7'=>[210,297],
+ 'a8'=>[148,210],
+ 'a9'=>[105,148],
+ 'a10'=>[73,105],
+ 'b0'=>[2835,4008],
+ 'b1'=>[2004,2835],
+ 'b2'=>[1417,2004],
+ 'b3'=>[1001,1417],
+ 'b4'=>[709,1001],
+ 'b5'=>[499,709],
+ 'b6'=>[354,499],
+ 'c0'=>[2599,3677],
+ 'c1'=>[1837,2599],
+ 'c2'=>[1298,1837],
+ 'c3'=>[918,1298],
+ 'c4'=>[649,918],
+ 'c5'=>[459,649],
+ 'c6'=>[323,459],
+ 'com10'=>[297,684],
+);
+
+my $ucmap=<<'EOF';
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<< /Registry (Adobe)
+/Ordering (UCS)
+/Supplement 0
+>> def
+/CMapName /Adobe-Identity-UCS def
+/CMapType 2 def
+1 begincodespacerange
+<0000> <FFFF>
+endcodespacerange
+2 beginbfrange
+<008b> <008f> [<00660066> <00660069> <0066006c> <006600660069> <00660066006C>]
+<00ad> <00ad> <002d>
+endbfrange
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+EOF
+
+sub usage
+{
+ my $stream = *STDOUT;
+ my $had_error = shift;
+ $stream = *STDERR if $had_error;
+ print $stream
+"usage: $prog [-dels] [-F font-directory] [-I inclusion-directory]" .
+" [-p paper-format] [-u [cmap-file]] [-y foundry] [file ...]\n" .
+"usage: $prog {-v | --version}\n" .
+"usage: $prog --help\n";
+ if (!$had_error)
+ {
+ print $stream "\n" .
+"Translate the output of troff(1) into Portable Document Format.\n" .
+"See the gropdf(1) manual page.\n";
+ }
+ exit($had_error);
+}
+
+my $fd;
+my $frot;
+my $fpsz;
+my $embedall=0;
+my $debug=0;
+my $want_help=0;
+my $version=0;
+my $stats=0;
+my $unicodemap;
+my @idirs;
+
+if (!GetOptions('F=s' => \$fd, 'I=s' => \@idirs, 'l' => \$frot,
+ 'p=s' => \$fpsz, 'd!' => \$debug, 'help' => \$want_help,
+ 'v' => \$version, 'version' => \$version,
+ 'e' => \$embedall, 'y=s' => \$Foundry, 's' => \$stats,
+ 'u:s' => \$unicodemap))
+{
+ &usage(1);
+}
+
+unshift(@idirs,'.');
+
+&usage(0) if ($want_help);
+
+if ($version)
+{
+ print "GNU gropdf (groff) version $cfg{GROFF_VERSION}\n";
+ exit;
+}
+
+if (defined($unicodemap))
+{
+ if ($unicodemap eq '')
+ {
+ $ucmap='';
+ }
+ elsif (-r $unicodemap)
+ {
+ local $/;
+ open(F,"<$unicodemap") or Die("failed to open '$unicodemap'");
+ ($ucmap)=(<F>);
+ close(F);
+ }
+ else
+ {
+ Warn("failed to find '$unicodemap'; ignoring");
+ }
+}
+
+# Search for 'font directory': paths in -f opt, shell var
+# GROFF_FONT_PATH, default paths
+
+my $fontdir=$cfg{GROFF_FONT_PATH};
+$fontdir=$ENV{GROFF_FONT_PATH}.$cfg{RT_SEP}.$fontdir if exists($ENV{GROFF_FONT_PATH});
+$fontdir=$fd.$cfg{RT_SEP}.$fontdir if defined($fd);
+
+$rot=90 if $frot;
+$matrix="0 1 -1 0" if $frot;
+
+LoadDownload();
+LoadDesc();
+
+my $unitwidth=$desc{unitwidth};
+
+$env{FontHT}=0;
+$env{FontSlant}=0;
+MakeMatrix();
+
+my $possiblesizes = $desc{papersize};
+$possiblesizes = $fpsz if $fpsz;
+my $papersz;
+for $papersz ( split(" ", lc($possiblesizes).' #duff#') )
+{
+ # No valid papersize found?
+ if ($papersz eq '#duff#')
+ {
+ Warn("ignoring unrecognized paper format(s) '$possiblesizes'");
+ last;
+ }
+
+ # Check for "/etc/papersize"
+ elsif (substr($papersz,0,1) eq '/' and -r $papersz)
+ {
+ if (open(P,"<$papersz"))
+ {
+ while (<P>)
+ {
+ chomp;
+ s/# .*//;
+ next if $_ eq '';
+ $papersz=lc($_);
+ last;
+ }
+ close(P);
+ }
+ }
+
+ # Allow height,width specified directly in centimeters, inches, or points.
+ if ($papersz=~m/([\d.]+)([cipP]),([\d.]+)([cipP])/)
+ {
+ @defaultmb=@mediabox=(0,0,ToPoints($3,$4),ToPoints($1,$2));
+ last;
+ }
+ # Look $papersz up as a name such as "a4" or "letter".
+ elsif (exists($ppsz{$papersz}))
+ {
+ @defaultmb=@mediabox=(0,0,$ppsz{$papersz}->[0],$ppsz{$papersz}->[1]);
+ last;
+ }
+ # Check for a landscape version
+ elsif (substr($papersz,-1) eq 'l' and exists($ppsz{substr($papersz,0,-1)}))
+ {
+ # Note 'legal' ends in 'l' but will be caught above
+ @defaultmb=@mediabox=(0,0,$ppsz{substr($papersz,0,-1)}->[1],$ppsz{substr($papersz,0,-1)}->[0]);
+ last;
+ }
+
+ # If we get here, $papersz was invalid, so try the next one.
+}
+
+my (@dt)=localtime($ENV{SOURCE_DATE_EPOCH} || time);
+my $dt=PDFDate(\@dt);
+
+my %info=('Creator' => "(groff version $cfg{GROFF_VERSION})",
+ 'Producer' => "(gropdf version $cfg{GROFF_VERSION})",
+ 'ModDate' => "($dt)",
+ 'CreationDate' => "($dt)");
+map { $_="< ".$_."\0" } @ARGV;
+
+while (<>)
+{
+ chomp;
+ s/\r$//;
+ $lct++;
+
+ do # The ahead buffer behaves like 'ungetc'
+ {{
+ if (scalar(@ahead))
+ {
+ $_=shift(@ahead);
+ }
+
+
+ my $cmd=substr($_,0,1);
+ next if $cmd eq '#'; # just a comment
+ my $lin=substr($_,1);
+
+ while ($cmd eq 'w')
+ {
+ $cmd=substr($lin,0,1);
+ $lin=substr($lin,1);
+ $w_flg=1 if $gotT;
+ }
+
+ $lin=~s/^\s+//;
+# $lin=~s/\s#.*?$//; # remove comment
+ $stream.="\% $_\n" if $debug;
+
+ do_x($lin),next if ($cmd eq 'x');
+ next if $suppress;
+ do_p($lin),next if ($cmd eq 'p');
+ do_f($lin),next if ($cmd eq 'f');
+ do_s($lin),next if ($cmd eq 's');
+ do_m($lin),next if ($cmd eq 'm');
+ do_D($lin),next if ($cmd eq 'D');
+ do_V($lin),next if ($cmd eq 'V');
+ do_v($lin),next if ($cmd eq 'v');
+ do_t($lin),next if ($cmd eq 't');
+ do_u($lin),next if ($cmd eq 'u');
+ do_C($lin),next if ($cmd eq 'C');
+ do_c($lin),next if ($cmd eq 'c');
+ do_N($lin),next if ($cmd eq 'N');
+ do_h($lin),next if ($cmd eq 'h');
+ do_H($lin),next if ($cmd eq 'H');
+ do_n($lin),next if ($cmd eq 'n');
+
+ my $tmp=scalar(@ahead);
+ }} until scalar(@ahead) == 0;
+
+}
+
+exit 0 if $lct==0;
+
+if ($cpageno > 0)
+{
+ my $trans='BLOCK';
+
+ $trans='PAGE' if $firstpause;
+
+ if (scalar(@XOstream))
+ {
+ MakeXO() if $stream;
+ $stream=join("\n",@XOstream)."\n";
+ }
+
+ my %t=%{$transition->{$trans}};
+ $cpage->{MediaBox}=\@mediabox if $custompaper;
+ $cpage->{Trans}=FixTrans(\%t) if $t{S};
+
+ if ($#PageAnnots >= 0)
+ {
+ @{$cpage->{Annots}}=@PageAnnots;
+ }
+
+ if ($#bgstack > -1 or $bgbox)
+ {
+ my $box="q 1 0 0 1 0 0 cm ";
+
+ foreach my $bg (@bgstack)
+ {
+ # 0=$bgtype # 1=stroke 2=fill. 4=page
+ # 1=$strkcol
+ # 2=$fillcol
+ # 3=(Left,Top,Right,bottom,LineWeight)
+ # 4=Start ypos
+ # 5=Endypos
+ # 6=Line Weight
+
+ my $pg=$bg->[3] || \@mediabox;
+
+ $bg->[5]=$pg->[3]; # box is continuing to next page
+ $box.=DrawBox($bg);
+ $bg->[4]=$pg->[1]; # will continue from page top
+ }
+
+ $stream=$box.$bgbox."Q\n".$stream;
+ $bgbox='';
+ }
+
+ $boxmax=0;
+ PutObj($cpageno);
+ OutStream($cpageno+1);
+}
+
+$cat->{PageMode}='/FullScreen' if $present;
+
+PutOutlines(\@outlev);
+
+PutObj(1);
+
+my $info=BuildObj(++$objct,\%info);
+
+PutObj($objct);
+
+foreach my $fontno (sort keys %fontlst)
+{
+ my $o=$fontlst{$fontno}->{FNT};
+
+ foreach my $ch (@{$o->{NO}})
+ {
+ my $psname=$o->{NAM}->{$ch->[1]}->[PSNAME] || '/.notdef';
+ my $wid=$o->{NAM}->{$ch->[1]}->[WIDTH] || 0;
+
+ push(@{$o->{DIFF}},$psname);
+ push(@{$o->{WIDTH}},$wid);
+ last if $#{$o->{DIFF}} >= 256;
+ }
+ unshift(@{$o->{DIFF}},0);
+ my $p=GetObj($fontlst{$fontno}->{OBJ});
+
+ if (exists($p->{LastChar}) and $p->{LastChar} > 255)
+ {
+ $p->{LastChar} = 255;
+ splice(@{$o->{DIFF}},257);
+ splice(@{$o->{WIDTH}},257);
+ }
+}
+
+foreach my $o (3..$objct)
+{
+ PutObj($o) if (!exists($obj[$o]->{XREF}));
+}
+
+#my $encrypt=BuildObj(++$objct,{'Filter' => '/Standard', 'V' => 1, 'R' => 2, 'P' => 252});
+#PutObj($objct);
+PutObj(2);
+
+my $xrefct=$fct;
+
+$objct+=1;
+print "xref\n0 $objct\n0000000000 65535 f \n";
+
+foreach my $xr (@obj)
+{
+ next if !defined($xr);
+ printf("%010d 00000 n \n",$xr->{XREF});
+}
+
+print "trailer\n<<\n/Info $info\n/Root 1 0 R\n/Size $objct\n>>\nstartxref\n$fct\n\%\%EOF\n";
+print "\% Pages=$pages->{Count}\n" if $stats;
+
+
+sub MakeMatrix
+{
+ my $fontxrev=shift||0;
+ my @mat=($frot)?(0,1,-1,0):(1,0,0,1);
+
+ if (!$frot)
+ {
+ if ($env{FontHT} != 0)
+ {
+ $mat[3]=sprintf('%.3f',$env{FontHT}/$cftsz);
+ }
+
+ if ($env{FontSlant} != 0)
+ {
+ my $slant=$env{FontSlant};
+ $slant*=$env{FontHT}/$cftsz if $env{FontHT} != 0;
+ my $ang=rad($slant);
+
+ $mat[2]=sprintf('%.3f',sin($ang)/cos($ang));
+ }
+
+ if ($fontxrev)
+ {
+ $mat[0]=-$mat[0];
+ }
+ }
+
+ $matrix=join(' ',@mat);
+ $matrixchg=1;
+}
+
+sub PutOutlines
+{
+ my $o=shift;
+ my $outlines;
+
+ if ($#{$o} > 0)
+ {
+ # We've got Outlines to deal with
+ my $openct=$curoutlev->[0]->[2];
+
+ while ($thislev-- > 1)
+ {
+ my $nxtoutlev=$curoutlev->[0]->[1];
+ $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1;
+ $openct=0 if $nxtoutlev->[0]->[3]==-1;
+ $curoutlev=$nxtoutlev;
+ }
+
+ $cat->{Outlines}=BuildObj(++$objct,{'Count' => abs($o->[0]->[0])+$o->[0]->[2]});
+ $outlines=$obj[$objct]->{DATA};
+ }
+ else
+ {
+ return;
+ }
+
+ SetOutObj($o);
+
+ $outlines->{First}=$o->[1]->[2];
+ $outlines->{Last}=$o->[$#{$o}]->[2];
+
+ LinkOutObj($o,$cat->{Outlines});
+}
+
+sub SetOutObj
+{
+ my $o=shift;
+
+ for my $j (1..$#{$o})
+ {
+ my $ono=BuildObj(++$objct,$o->[$j]->[0]);
+ $o->[$j]->[2]=$ono;
+
+ SetOutObj($o->[$j]->[1]) if $#{$o->[$j]->[1]} > -1;
+ }
+}
+
+sub LinkOutObj
+{
+ my $o=shift;
+ my $parent=shift;
+
+ for my $j (1..$#{$o})
+ {
+ my $op=GetObj($o->[$j]->[2]);
+
+ $op->{Next}=$o->[$j+1]->[2] if ($j < $#{$o});
+ $op->{Prev}=$o->[$j-1]->[2] if ($j > 1);
+ $op->{Parent}=$parent;
+
+ if ($#{$o->[$j]->[1]} > -1)
+ {
+ $op->{Count}=$o->[$j]->[1]->[0]->[2]*$o->[$j]->[1]->[0]->[3];# if exists($op->{Count}) and $op->{Count} > 0;
+ $op->{First}=$o->[$j]->[1]->[1]->[2];
+ $op->{Last}=$o->[$j]->[1]->[$#{$o->[$j]->[1]}]->[2];
+ LinkOutObj($o->[$j]->[1],$o->[$j]->[2]);
+ }
+ }
+}
+
+sub GetObj
+{
+ my $ono=shift;
+ ($ono)=split(' ',$ono);
+ return($obj[$ono]->{DATA});
+}
+
+
+
+sub PDFDate
+{
+ my $dt=shift;
+ return(sprintf("D:%04d%02d%02d%02d%02d%02d%+03d'00'",$dt->[5]+1900,$dt->[4]+1,$dt->[3],$dt->[2],$dt->[1],$dt->[0],( localtime time() + 3600*( 12 - (gmtime)[2] ) )[2] - 12));
+}
+
+sub ToPoints
+{
+ my $num=shift;
+ my $unit=shift;
+
+ if ($unit eq 'i')
+ {
+ return($num*72);
+ }
+ elsif ($unit eq 'c')
+ {
+ return int($num*72/2.54);
+ }
+ elsif ($unit eq 'm') # millimetres
+ {
+ return int($num*72/25.4);
+ }
+ elsif ($unit eq 'p')
+ {
+ return($num);
+ }
+ elsif ($unit eq 'P')
+ {
+ return($num*6);
+ }
+ elsif ($unit eq 'z')
+ {
+ return($num/$unitwidth);
+ }
+ else
+ {
+ Die("invalid scaling unit '$unit'");
+ }
+}
+
+sub LoadDownload
+{
+ my $f;
+ my $found=0;
+
+ my (@dirs)=split($cfg{RT_SEP},$fontdir);
+
+ foreach my $dir (@dirs)
+ {
+ $f=undef;
+ OpenFile(\$f,$dir,"download");
+ next if !defined($f);
+ $found++;
+
+ while (<$f>)
+ {
+ chomp;
+ s/#.*$//;
+ next if $_ eq '';
+ my ($foundry,$name,$file)=split(/\t+/);
+ if (substr($file,0,1) eq '*')
+ {
+ next if !$embedall;
+ $file=substr($file,1);
+ }
+
+ my $pth=$file;
+ $pth=$dir."/$devnm/$file" if substr($file,0,1) ne '/';
+
+ if (!-r $pth)
+ {
+ $missing{"$foundry $name"}="$dir/$devnm";
+ next;
+ }
+
+ $download{"$foundry $name"}=$file if !exists($download{"$foundry $name"});
+ }
+
+ close($f);
+ }
+
+ Die("failed to open 'download' file") if !$found;
+}
+
+sub OpenFile
+{
+ my $f=shift;
+ my $dirs=shift;
+ my $fnm=shift;
+
+ if (substr($fnm,0,1) eq '/' or substr($fnm,1,1) eq ':') # dos
+ {
+ return if -r "$fnm" and open($$f,"<$fnm");
+ }
+
+ my (@dirs)=split($cfg{RT_SEP},$dirs);
+
+ foreach my $dir (@dirs)
+ {
+ last if -r "$dir/$devnm/$fnm" and open($$f,"<$dir/$devnm/$fnm");
+ }
+}
+
+sub LoadDesc
+{
+ my $f;
+
+ OpenFile(\$f,$fontdir,"DESC");
+ Die("failed to open device description file 'DESC'")
+ if !defined($f);
+
+ while (<$f>)
+ {
+ chomp;
+ s/#.*$//;
+ next if $_ eq '';
+ my ($name,$prms)=split(' ',$_,2);
+ $desc{lc($name)}=$prms;
+ }
+
+ close($f);
+
+ foreach my $directive ('unitwidth', 'res', 'sizescale')
+ {
+ Die("device description file 'DESC' missing mandatory directive"
+ . " '$directive'") if !exists($desc{$directive});
+ }
+
+ foreach my $directive ('unitwidth', 'res', 'sizescale')
+ {
+ my $val=$desc{$directive};
+ Die("device description file 'DESC' directive '$directive'"
+ . " value must be positive; got '$val'")
+ if ($val !~ m/^\d+$/ or $val <= 0);
+ }
+
+ if (exists($desc{'hor'}))
+ {
+ my $hor=$desc{'hor'};
+ Die("device horizontal motion quantum must be 1, got '$hor'")
+ if ($hor != 1);
+ }
+
+ if (exists($desc{'vert'}))
+ {
+ my $vert=$desc{'vert'};
+ Die("device vertical motion quantum must be 1, got '$vert'")
+ if ($vert != 1);
+ }
+
+ my ($res,$ss)=($desc{'res'},$desc{'sizescale'});
+ Die("device resolution must be a multiple of 72*sizescale, got"
+ . " '$res' ('sizescale'=$ss)") if (($res % ($ss * 72)) != 0);
+}
+
+sub rad { $_[0]*3.14159/180 }
+
+my $InPicRotate=0;
+
+sub do_x
+{
+ my $l=shift;
+ my ($xcmd,@xprm)=split(' ',$l);
+ $xcmd=substr($xcmd,0,1);
+
+ if ($xcmd eq 'T')
+ {
+ Warn("expecting a PDF pipe (got $xprm[0])")
+ if $xprm[0] ne substr($devnm,3);
+ }
+ elsif ($xcmd eq 'f') # Register Font
+ {
+ $xprm[1]="${Foundry}-$xprm[1]" if $Foundry ne '';
+ LoadFont($xprm[0],$xprm[1]);
+ }
+ elsif ($xcmd eq 'F') # Source File (for errors)
+ {
+ $env{SourceFile}=$xprm[0];
+ }
+ elsif ($xcmd eq 'H') # FontHT
+ {
+ $xprm[0]/=$unitwidth;
+ $xprm[0]=0 if $xprm[0] == $cftsz;
+ $env{FontHT}=$xprm[0];
+ MakeMatrix();
+ }
+ elsif ($xcmd eq 'S') # FontSlant
+ {
+ $env{FontSlant}=$xprm[0];
+ MakeMatrix();
+ }
+ elsif ($xcmd eq 'i') # Initialise
+ {
+ if ($objct == 0)
+ {
+ $objct++;
+ @defaultmb=@mediabox;
+ BuildObj($objct,{'Pages' => BuildObj($objct+1,
+ {'Kids' => [],
+ 'Count' => 0,
+ 'Type' => '/Pages',
+ 'Rotate' => $rot,
+ 'MediaBox' => \@defaultmb,
+ 'Resources' =>
+ {'Font' => {},
+ 'ProcSet' => ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI']}
+ }
+ ),
+ 'Type' => '/Catalog'});
+
+ $cat=$obj[$objct]->{DATA};
+ $objct++;
+ $pages=$obj[2]->{DATA};
+ Put("%PDF-1.4\n\x25\xe2\xe3\xcf\xd3\n");
+ }
+ }
+ elsif ($xcmd eq 'X')
+ {
+ # There could be extended args
+ do
+ {{
+ LoadAhead(1);
+ if (substr($ahead[0],0,1) eq '+')
+ {
+ $l.="\n".substr($ahead[0],1);
+ shift(@ahead);
+ }
+ }} until $#ahead==0;
+
+ ($xcmd,@xprm)=split(' ',$l);
+ $xcmd=substr($xcmd,0,1);
+
+ if ($xprm[0]=~m/^(.+:)(.+)/)
+ {
+ splice(@xprm,1,0,$2);
+ $xprm[0]=$1;
+ }
+
+ my $par=join(' ',@xprm[1..$#xprm]);
+
+ if ($xprm[0] eq 'ps:')
+ {
+ if ($xprm[1] eq 'invis')
+ {
+ $suppress=1;
+ }
+ elsif ($xprm[1] eq 'endinvis')
+ {
+ $suppress=0;
+ }
+ elsif ($par=~m/exec gsave currentpoint 2 copy translate (.+) rotate neg exch neg exch translate/)
+ {
+ # This is added by gpic to rotate a single object
+
+ my $theta=-rad($1);
+
+ IsGraphic();
+ my ($curangle,$hyp)=RtoP($xpos,GraphY($ypos));
+ my ($x,$y)=PtoR($theta+$curangle,$hyp);
+ my ($tx, $ty) = ($xpos - $x, GraphY($ypos) - $y);
+ if ($frot) {
+ ($tx, $ty) = ($tx * sin($theta) + $ty * -cos($theta),
+ $tx * -cos($theta) + $ty * -sin($theta));
+ }
+ $stream.="q\n".sprintf("%.3f %.3f %.3f %.3f %.3f %.3f cm",cos($theta),sin($theta),-sin($theta),cos($theta),$tx,$ty)."\n";
+ $InPicRotate=1;
+ }
+ elsif ($par=~m/exec grestore/ and $InPicRotate)
+ {
+ IsGraphic();
+ $stream.="Q\n";
+ $InPicRotate=0;
+ }
+ elsif ($par=~m/exec (\d) setlinejoin/)
+ {
+ IsGraphic();
+ $linejoin=$1;
+ $stream.="$linejoin j\n";
+ }
+ elsif ($par=~m/exec (\d) setlinecap/)
+ {
+ IsGraphic();
+ $linecap=$1;
+ $stream.="$linecap J\n";
+ }
+ elsif ($par=~m/exec %%%%PAUSE/i and !$noslide)
+ {
+ my $trans='BLOCK';
+
+ if ($firstpause)
+ {
+ $trans='PAGE';
+ $firstpause=0;
+ }
+ MakeXO();
+ NewPage($trans);
+ $present=1;
+ }
+ elsif ($par=~m/exec %%%%BEGINONCE/)
+ {
+ if ($noslide)
+ {
+ $suppress=1;
+ }
+ else
+ {
+ my $trans='BLOCK';
+
+ if ($firstpause)
+ {
+ $trans='PAGE';
+ $firstpause=0;
+ }
+ MakeXO();
+ NewPage($trans);
+ $present=1;
+ }
+ }
+ elsif ($par=~m/exec %%%%ENDONCE/)
+ {
+ if ($noslide)
+ {
+ $suppress=0;
+ }
+ else
+ {
+ MakeXO();
+ NewPage('BLOCK');
+ $cat->{PageMode}='/FullScreen';
+ pop(@XOstream);
+ }
+ }
+ elsif ($par=~m/\[(.+) pdfmark/)
+ {
+ my $pdfmark=$1;
+ $pdfmark=~s((\d{4,6}) u)(sprintf("%.1f",$1/$desc{sizescale}))eg;
+ $pdfmark=~s(\\\[u00(..)\])(chr(hex($1)))eg;
+ $pdfmark=~s/\\n/\n/g;
+
+ if ($pdfmark=~m/(.+) \/DOCINFO\s*$/s)
+ {
+ my @xwds=split(/ /,"<< $1 >>");
+ my $docinfo=ParsePDFValue(\@xwds);
+
+ foreach my $k (sort keys %{$docinfo})
+ {
+ $info{$k}=$docinfo->{$k} if $k ne 'Producer';
+ }
+ }
+ elsif ($pdfmark=~m/(.+) \/DOCVIEW\s*$/)
+ {
+ my @xwds=split(' ',"<< $1 >>");
+ my $docview=ParsePDFValue(\@xwds);
+
+ foreach my $k (sort keys %{$docview})
+ {
+ $cat->{$k}=$docview->{$k} if !exists($cat->{$k});
+ }
+ }
+ elsif ($pdfmark=~m/(.+) \/DEST\s*$/)
+ {
+ my @xwds=split(' ',"<< $1 >>");
+ my $dest=ParsePDFValue(\@xwds);
+ $dest->{View}->[1]=GraphY($dest->{View}->[1]*-1);
+ unshift(@{$dest->{View}},"$cpageno 0 R");
+
+ if (!defined($dests))
+ {
+ $cat->{Dests}=BuildObj(++$objct,{});
+ $dests=$obj[$objct]->{DATA};
+ }
+
+ my $k=substr($dest->{Dest},1);
+ $dests->{$k}=$dest->{View};
+ }
+ elsif ($pdfmark=~m/(.+) \/ANN\s*$/)
+ {
+ my $l=$1;
+ $l=~s/Color/C/;
+ $l=~s/Action/A/;
+ $l=~s/Title/T/;
+ $l=~s'/Subtype /URI'/S /URI';
+ my @xwds=split(' ',"<< $l >>");
+ my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds));
+ my $annot=$obj[$objct];
+ $annot->{DATA}->{Type}='/Annot';
+ FixRect($annot->{DATA}->{Rect}); # Y origin to ll
+ FixPDFColour($annot->{DATA});
+ push(@PageAnnots,$annotno);
+ }
+ elsif ($pdfmark=~m/(.+) \/OUT\s*$/)
+ {
+ my $t=$1;
+ $t=~s/\\\) /\\\\\) /g;
+ $t=~s/\\e/\\\\/g;
+ $t=~m/(^.*\/Title \()(.*)(\).*)/;
+ my ($pre,$title,$post)=($1,$2,$3);
+ $title=~s/(?<!\\)\(/\\\(/g;
+ $title=~s/(?<!\\)\)/\\\)/g;
+ my @xwds=split(' ',"<< $pre$title$post >>");
+ my $out=ParsePDFValue(\@xwds);
+
+ my $this=[$out,[]];
+
+ if (exists($out->{Level}))
+ {
+ my $lev=abs($out->{Level});
+ my $levsgn=sgn($out->{Level});
+ delete($out->{Level});
+
+ if ($lev > $thislev)
+ {
+ my $thisoutlev=$curoutlev->[$#{$curoutlev}]->[1];
+ $thisoutlev->[0]=[0,$curoutlev,0,$levsgn];
+ $curoutlev=$thisoutlev;
+ $curoutlevno=$#{$curoutlev};
+ $thislev++;
+ }
+ elsif ($lev < $thislev)
+ {
+ my $openct=$curoutlev->[0]->[2];
+
+ while ($thislev > $lev)
+ {
+ my $nxtoutlev=$curoutlev->[0]->[1];
+ $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1;
+ $openct=0 if $nxtoutlev->[0]->[3]==-1;
+ $curoutlev=$nxtoutlev;
+ $thislev--;
+ }
+
+ $curoutlevno=$#{$curoutlev};
+ }
+
+# push(@{$curoutlev},$this);
+ splice(@{$curoutlev},++$curoutlevno,0,$this);
+ $curoutlev->[0]->[2]++;
+ }
+ else
+ {
+ # This code supports old pdfmark.tmac, unused by pdf.tmac
+ while ($curoutlev->[0]->[0] == 0 and defined($curoutlev->[0]->[1]))
+ {
+ $curoutlev=$curoutlev->[0]->[1];
+ }
+
+ $curoutlev->[0]->[0]--;
+ $curoutlev->[0]->[2]++;
+ push(@{$curoutlev},$this);
+
+
+ if (exists($out->{Count}) and $out->{Count} != 0)
+ {
+ push(@{$this->[1]},[abs($out->{Count}),$curoutlev,0,sgn($out->{Count})]);
+ $curoutlev=$this->[1];
+
+ if ($out->{Count} > 0)
+ {
+ my $p=$curoutlev;
+
+ while (defined($p))
+ {
+ $p->[0]->[2]+=$out->{Count};
+ $p=$p->[0]->[1];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ elsif (lc($xprm[0]) eq 'pdf:')
+ {
+ if (lc($xprm[1]) eq 'import')
+ {
+ my $fil=$xprm[2];
+ my $llx=$xprm[3];
+ my $lly=$xprm[4];
+ my $urx=$xprm[5];
+ my $ury=$xprm[6];
+ my $wid=GetPoints($xprm[7]);
+ my $hgt=GetPoints($xprm[8])||-1;
+ my $mat=[1,0,0,1,0,0];
+
+ if (!exists($incfil{$fil}))
+ {
+ if ($fil=~m/\.pdf$/)
+ {
+ $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"import");
+ }
+ elsif ($fil=~m/\.swf$/)
+ {
+ my $xscale=$wid/($urx-$llx+1);
+ my $yscale=($hgt<=0)?$xscale:($hgt/($ury-$lly+1));
+ $hgt=($ury-$lly+1)*$yscale;
+
+ if ($rot)
+ {
+ $mat->[3]=$xscale;
+ $mat->[0]=$yscale;
+ }
+ else
+ {
+ $mat->[0]=$xscale;
+ $mat->[3]=$yscale;
+ }
+
+ $incfil{$fil}=LoadSWF($fil,[$llx,$lly,$urx,$ury],$mat);
+ }
+ else
+ {
+ Warn("unrecognized 'import' file type '$fil'");
+ return undef;
+ }
+ }
+
+ if (defined($incfil{$fil}))
+ {
+ IsGraphic();
+ if ($fil=~m/\.pdf$/)
+ {
+ my $bbox=$incfil{$fil}->[1];
+ my $xscale=d3($wid/($bbox->[2]-$bbox->[0]+1));
+ my $yscale=d3(($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1]+1)));
+ $wid=($bbox->[2]-$bbox->[0])*$xscale;
+ $hgt=($bbox->[3]-$bbox->[1])*$yscale;
+ $ypos+=$hgt;
+ $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm";
+ $stream.=" 0 1 -1 0 0 0 cm" if $rot;
+ $stream.=" /$incfil{$fil}->[0] Do Q\n";
+ }
+ elsif ($fil=~m/\.swf$/)
+ {
+ $stream.=PutXY($xpos,$ypos)." m /$incfil{$fil} Do\n";
+ }
+ }
+ }
+ elsif (lc($xprm[1]) eq 'pdfpic')
+ {
+ my $fil=$xprm[2];
+ my $flag=uc($xprm[3]||'-L');
+ my $wid=GetPoints($xprm[4])||-1;
+ my $hgt=GetPoints($xprm[5]||-1);
+ my $ll=GetPoints($xprm[6]||0);
+ my $mat=[1,0,0,1,0,0];
+
+ if (!exists($incfil{$fil}))
+ {
+ $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"pdfpic");
+ }
+
+ if (defined($incfil{$fil}))
+ {
+ IsGraphic();
+ my $bbox=$incfil{$fil}->[1];
+ $wid=($bbox->[2]-$bbox->[0]) if $wid <= 0;
+ my $xscale=d3($wid/($bbox->[2]-$bbox->[0]));
+ my $yscale=d3(($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1])));
+ $xscale=($wid<=0)?$yscale:$xscale;
+ $xscale=$yscale if $yscale < $xscale;
+ $yscale=$xscale if $xscale < $yscale;
+ $wid=($bbox->[2]-$bbox->[0])*$xscale;
+ $hgt=($bbox->[3]-$bbox->[1])*$yscale;
+
+ if ($flag eq '-C' and $ll > $wid)
+ {
+ $xpos+=int(($ll-$wid)/2);
+ }
+ elsif ($flag eq '-R' and $ll > $wid)
+ {
+ $xpos+=$ll-$wid;
+ }
+
+ $ypos+=$hgt;
+ $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm";
+ $stream.=" 0 1 -1 0 0 0 cm" if $rot;
+ $stream.=" /$incfil{$fil}->[0] Do Q\n";
+ }
+ }
+ elsif (lc($xprm[1]) eq 'xrev')
+ {
+ $xrev=!$xrev;
+ }
+ elsif (lc($xprm[1]) eq 'markstart')
+ {
+ $mark={'rst' => ($xprm[2]+$xprm[4])/$unitwidth, 'rsb' => ($xprm[3]-$xprm[4])/$unitwidth, 'xpos' => $xpos-($xprm[4]/$unitwidth),
+ 'ypos' => $ypos, 'lead' => $xprm[4]/$unitwidth, 'pdfmark' => join(' ',@xprm[5..$#xprm])};
+ }
+ elsif (lc($xprm[1]) eq 'markend')
+ {
+ PutHotSpot($xpos) if defined($mark);
+ $mark=undef;
+ }
+ elsif (lc($xprm[1]) eq 'marksuspend')
+ {
+ $suspendmark=$mark;
+ $mark=undef;
+ }
+ elsif (lc($xprm[1]) eq 'markrestart')
+ {
+ $mark=$suspendmark;
+ $suspendmark=undef;
+ }
+ elsif (lc($xprm[1]) eq 'pagename')
+ {
+ if ($pginsert > -1)
+ {
+ $pgnames{$xprm[2]}=$pages->{Kids}->[$pginsert];
+ }
+ else
+ {
+ $pgnames{$xprm[2]}='top';
+ }
+ }
+ elsif (lc($xprm[1]) eq 'switchtopage')
+ {
+ my $ba=$xprm[2];
+ my $want=$xprm[3];
+
+ if ($pginsert > -1)
+ {
+ if (!defined($want) or $want eq '')
+ {
+ # no before/after
+ $want=$ba;
+ $ba='before';
+ }
+
+ if (!defined($ba) or $ba eq '' or $want eq 'bottom')
+ {
+ $pginsert=$#{$pages->{Kids}};
+ }
+ elsif ($want eq 'top')
+ {
+ $pginsert=-1;
+ }
+ else
+ {
+ if (exists($pgnames{$want}))
+ {
+ my $ref=$pgnames{$want};
+
+ if ($ref eq 'top')
+ {
+ $pginsert=-1;
+ }
+ else
+ {
+ FIND: while (1)
+ {
+ foreach my $j (0..$#{$pages->{Kids}})
+ {
+ if ($ref eq $pages->{Kids}->[$j])
+ {
+ if ($ba eq 'before')
+ {
+ $pginsert=$j-1;
+ last FIND;
+ }
+ elsif ($ba eq 'after')
+ {
+ $pginsert=$j;
+ last FIND;
+ }
+ else
+ {
+ # XXX: indentation wince
+ Warn(
+"expected 'switchtopage' parameter to be one of"
+. "'top|bottom|before|after', got '$ba'");
+ last FIND;
+ }
+ }
+
+ }
+
+ Warn("cannot find page ref '$ref'");
+ last FIND
+
+ }
+ }
+ }
+ else
+ {
+ Warn("cannot find page named '$want'");
+ }
+ }
+
+ if ($pginsert < 0)
+ {
+ ($curoutlev,$curoutlevno,$thislev)=(\@outlev,0,1);
+ }
+ else
+ {
+ ($curoutlev,$curoutlevno,$thislev)=(@{$outlines[$pginsert]});
+ }
+ }
+ }
+ elsif (lc($xprm[1]) eq 'transition' and !$noslide)
+ {
+ if (uc($xprm[2]) eq 'PAGE' or uc($xprm[2] eq 'SLIDE'))
+ {
+ $transition->{PAGE}->{S}='/'.ucfirst($xprm[3]) if $xprm[3] and $xprm[3] ne '.';
+ $transition->{PAGE}->{D}=$xprm[4] if $xprm[4] and $xprm[4] ne '.';
+ $transition->{PAGE}->{Dm}='/'.$xprm[5] if $xprm[5] and $xprm[5] ne '.';
+ $transition->{PAGE}->{M}='/'.$xprm[6] if $xprm[6] and $xprm[6] ne '.';
+ $xprm[7]='/None' if $xprm[7] and uc($xprm[7]) eq 'NONE';
+ $transition->{PAGE}->{Di}=$xprm[7] if $xprm[7] and $xprm[7] ne '.';
+ $transition->{PAGE}->{SS}=$xprm[8] if $xprm[8] and $xprm[8] ne '.';
+ $transition->{PAGE}->{B}=$xprm[9] if $xprm[9] and $xprm[9] ne '.';
+ }
+ elsif (uc($xprm[2]) eq 'BLOCK')
+ {
+ $transition->{BLOCK}->{S}='/'.ucfirst($xprm[3]) if $xprm[3] and $xprm[3] ne '.';
+ $transition->{BLOCK}->{D}=$xprm[4] if $xprm[4] and $xprm[4] ne '.';
+ $transition->{BLOCK}->{Dm}='/'.$xprm[5] if $xprm[5] and $xprm[5] ne '.';
+ $transition->{BLOCK}->{M}='/'.$xprm[6] if $xprm[6] and $xprm[6] ne '.';
+ $xprm[7]='/None' if $xprm[7] and uc($xprm[7]) eq 'NONE';
+ $transition->{BLOCK}->{Di}=$xprm[7] if $xprm[7] and $xprm[7] ne '.';
+ $transition->{BLOCK}->{SS}=$xprm[8] if $xprm[8] and $xprm[8] ne '.';
+ $transition->{BLOCK}->{B}=$xprm[9] if $xprm[9] and $xprm[9] ne '.';
+ }
+
+ $present=1;
+ }
+ elsif (lc($xprm[1]) eq 'background')
+ {
+ splice(@xprm,0,2);
+ my $type=shift(@xprm);
+# print STDERR "ypos=$ypos\n";
+
+ if (lc($type) eq 'off')
+ {
+ my $sptr=$#bgstack;
+ if ($sptr > -1)
+ {
+ if ($sptr == 0 and $bgstack[0]->[0] & 4)
+ {
+ pop(@bgstack);
+ }
+ else
+ {
+ $bgstack[$sptr]->[5]=GraphY($ypos);
+ $bgbox=DrawBox(pop(@bgstack)).$bgbox;
+ }
+ }
+ }
+ elsif (lc($type) eq 'footnote')
+ {
+ my $t=GetPoints($xprm[0]);
+ $boxmax=($t<0)?abs($t):GraphY($t);
+ }
+ else
+ {
+ my $bgtype=0;
+
+ foreach (@xprm)
+ {
+ $_=GetPoints($_);
+ }
+
+ $bgtype|=2 if $type=~m/box/i;
+ $bgtype|=1 if $type=~m/fill/i;
+ $bgtype|=4 if $type=~m/page/i;
+ $bgtype=5 if $bgtype==4;
+ my $bgwt=$xprm[4];
+ $bgwt=$xprm[0] if !defined($bgwt) and $#xprm == 0;
+ my (@bg)=(@xprm);
+ my $bg=\@bg;
+
+ if (!defined($bg[3]) or $bgtype & 4)
+ {
+ $bg=undef;
+ }
+ else
+ {
+ FixRect($bg);
+ }
+
+ if ($bgtype)
+ {
+ if ($bgtype & 4)
+ {
+ shift(@bgstack) if $#bgstack >= 0 and $bgstack[0]->[0] & 4;
+ unshift(@bgstack,[$bgtype,$strkcol,$fillcol,$bg,GraphY($ypos),GraphY($bg[3]||0),$bgwt || 0.4]);
+ }
+ else
+ {
+ push(@bgstack,[$bgtype,$strkcol,$fillcol,$bg,GraphY($ypos),GraphY($bg[3]||0),$bgwt || 0.4]);
+ }
+ }
+ }
+ }
+ }
+ elsif (lc(substr($xprm[0],0,9)) eq 'papersize')
+ {
+ my ($px,$py)=split(',',substr($xprm[0],10));
+ $px=GetPoints($px);
+ $py=GetPoints($py);
+ @mediabox=(0,0,$px,$py);
+ my @mb=@mediabox;
+ $matrixchg=1;
+ $custompaper=1;
+ $cpage->{MediaBox}=\@mb;
+ }
+ }
+}
+
+sub FixPDFColour
+{
+ my $o=shift;
+ my $a=$o->{C};
+ my @r=();
+ my $c=$a->[0];
+
+ if ($#{$a}==3)
+ {
+ if ($c > 1)
+ {
+ foreach my $j (0..2)
+ {
+ push(@r,sprintf("%1.3f",$a->[$j]/0xffff));
+ }
+
+ $o->{C}=\@r;
+ }
+ }
+ elsif (substr($c,0,1) eq '#')
+ {
+ if (length($c) == 7)
+ {
+ foreach my $j (0..2)
+ {
+ push(@r,sprintf("%1.3f",hex(substr($c,$j*2+1,2))/0xff));
+ }
+
+ $o->{C}=\@r;
+ }
+ elsif (length($c) == 14)
+ {
+ foreach my $j (0..2)
+ {
+ push(@r,sprintf("%1.3f",hex(substr($c,$j*4+2,4))/0xffff));
+ }
+
+ $o->{C}=\@r;
+ }
+ }
+}
+
+sub PutHotSpot
+{
+ my $endx=shift;
+ my $l=$mark->{pdfmark};
+ $l=~s/Color/C/;
+ $l=~s/Action/A/;
+ $l=~s'/Subtype /URI'/S /URI';
+ $l=~s(\\\[u00(..)\])(chr(hex($1)))eg;
+ my @xwds=split(' ',"<< $l >>");
+ my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds));
+ my $annot=$obj[$objct];
+ $annot->{DATA}->{Type}='/Annot';
+ $annot->{DATA}->{Rect}=[$mark->{xpos},$mark->{ypos}-$mark->{rsb},$endx+$mark->{lead},$mark->{ypos}-$mark->{rst}];
+ FixPDFColour($annot->{DATA});
+ FixRect($annot->{DATA}->{Rect}); # Y origin to ll
+ push(@PageAnnots,$annotno);
+}
+
+sub sgn
+{
+ return(1) if $_[0] > 0;
+ return(-1) if $_[0] < 0;
+ return(0);
+}
+
+sub FixRect
+{
+ my $rect=shift;
+
+ return if !defined($rect);
+ $rect->[1]=GraphY($rect->[1]);
+ $rect->[3]=GraphY($rect->[3]);
+
+ if ($rot)
+ {
+ ($rect->[0],$rect->[1])=Rotate($rect->[0],$rect->[1]);
+ ($rect->[2],$rect->[3])=Rotate($rect->[2],$rect->[3]);
+ }
+}
+
+sub Rotate
+{
+ my ($tx,$ty)=(@_);
+ my $theta=rad($rot);
+
+ ($tx,$ty)=(d3($tx * cos(-$theta) - $ty * sin(-$theta)),
+ d3($tx * sin( $theta) + $ty * cos( $theta)));
+ return($tx,$ty);
+}
+
+sub GetPoints
+{
+ my $val=shift;
+
+ $val=ToPoints($1,$2) if ($val and $val=~m/(-?[\d.]+)([cipnz])/);
+
+ return $val;
+}
+
+# Although the PDF reference mentions XObject/Form as a way of
+# incorporating an external PDF page into the current PDF, it seems not
+# to work with any current PDF reader (although I am told (by Leonard
+# Rosenthol, who helped author the PDF ISO standard) that Acroread 9
+# does support it, empirical observation shows otherwise!!). So... do
+# it the hard way - full PDF parser and merge required objects!!!
+
+# sub BuildRef
+# {
+# my $fil=shift;
+# my $bbox=shift;
+# my $mat=shift;
+# my $wid=($bbox->[2]-$bbox->[0])*$mat->[0];
+# my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3];
+#
+# if (!open(PDF,"<$fil"))
+# {
+# Warn("failed to open '$fil'");
+# return(undef);
+# }
+#
+# my (@f)=(<PDF>);
+#
+# close(PDF);
+#
+# $objct++;
+# my $xonm="XO$objct";
+#
+# $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject',
+# 'Subtype' => '/Form',
+# 'BBox' => $bbox,
+# 'Matrix' => $mat,
+# 'Resources' => $pages->{'Resources'},
+# 'Ref' => {'Page' => '1',
+# 'F' => BuildObj($objct+1,{'Type' => '/Filespec',
+# 'F' => "($fil)",
+# 'EF' => {'F' => BuildObj($objct+2,{'Type' => '/EmbeddedFile'})}
+# })
+# }
+# });
+#
+# $obj[$objct]->{STREAM}="q 1 0 0 1 0 0 cm
+# q BT
+# 1 0 0 1 0 0 Tm
+# .5 g .5 G
+# /F5 20 Tf
+# (Proxy) Tj
+# ET Q
+# 0 0 m 72 0 l s
+# Q\n";
+#
+# # $obj[$objct]->{STREAM}=PutXY($xpos,$ypos)." m ".PutXY($xpos+$wid,$ypos)." l ".PutXY($xpos+$wid,$ypos+$hgt)." l ".PutXY($xpos,$ypos+$hgt)." l f\n";
+# $obj[$objct+2]->{STREAM}=join('',@f);
+# PutObj($objct);
+# PutObj($objct+1);
+# PutObj($objct+2);
+# $objct+=2;
+# return($xonm);
+# }
+
+sub LoadSWF
+{
+ my $fil=shift;
+ my $bbox=shift;
+ my $mat=shift;
+ my $wid=($bbox->[2]-$bbox->[0])*$mat->[0];
+ my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3];
+ my (@path)=split('/',$fil);
+ my $node=pop(@path);
+
+ if (!open(PDF,"<$fil"))
+ {
+ Warn("failed to open SWF '$fil'");
+ return(undef);
+ }
+
+ my (@f)=(<PDF>);
+
+ close(PDF);
+
+ $objct++;
+ my $xonm="XO$objct";
+
+ $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject', 'BBox' => $bbox, 'Matrix' => $mat, 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject"});
+ $obj[$objct]->{STREAM}='';
+ PutObj($objct);
+ $objct++;
+ my $asset=BuildObj($objct,{'EF' => {'F' => BuildObj($objct+1,{})},
+ 'F' => "($node)",
+ 'Type' => '/Filespec',
+ 'UF' => "($node)"});
+
+ PutObj($objct);
+ $objct++;
+ $obj[$objct]->{STREAM}=join('',@f);
+ PutObj($objct);
+ $objct++;
+ my $config=BuildObj($objct,{'Instances' => [BuildObj($objct+1,{'Params' => { 'Binding' => '/Background'}, 'Asset' => $asset})],
+ 'Subtype' => '/Flash'});
+
+ PutObj($objct);
+ $objct++;
+ PutObj($objct);
+ $objct++;
+
+ my ($x,$y)=split(' ',PutXY($xpos,$ypos));
+
+ push(@{$cpage->{Annots}},BuildObj($objct,{'RichMediaContent' => {'Subtype' => '/Flash', 'Configurations' => [$config], 'Assets' => {'Names' => [ "($node)", $asset ] }},
+ 'P' => "$cpageno 0 R",
+ 'RichMediaSettings' => { 'Deactivation' => { 'Condition' => '/PI',
+ 'Type' => '/RichMediaDeactivation'},
+ 'Activation' => { 'Condition' => '/PV',
+ 'Type' => '/RichMediaActivation'}},
+ 'F' => 68,
+ 'Subtype' => '/RichMedia',
+ 'Type' => '/Annot',
+ 'Rect' => "[ $x $y ".($x+$wid)." ".($y+$hgt)." ]",
+ 'Border' => [0,0,0]}));
+
+ PutObj($objct);
+
+ return $xonm;
+}
+
+sub OpenInc
+{
+ my $fn=shift;
+ my $fnm=$fn;
+ my $F;
+
+ if (substr($fnm,0,1) eq '/' or substr($fnm,1,1) eq ':') # dos
+ {
+ if (-r $fnm and open($F,"<$fnm"))
+ {
+ return($F,$fnm);
+ }
+ }
+ else
+ {
+ foreach my $dir (@idirs)
+ {
+ $fnm="$dir/$fn";
+
+ if (-r "$fnm" and open($F,"<$fnm"))
+ {
+ return($F,$fnm);
+ }
+ }
+ }
+
+ return(undef,$fn);
+}
+
+sub LoadPDF
+{
+ my $pdfnm=shift;
+ my $mat=shift;
+ my $wid=shift;
+ my $hgt=shift;
+ my $type=shift;
+ my $pdf;
+ my $pdftxt='';
+ my $strmlen=0;
+ my $curobj=-1;
+ my $instream=0;
+ my $cont;
+ my $adj=0;
+ my $keepsep=$/;
+
+ my ($PD,$PDnm)=OpenInc($pdfnm);
+
+ if (!defined($PD))
+ {
+ Warn("failed to open PDF '$pdfnm'");
+ return undef;
+ }
+
+ my $hdr=<$PD>;
+
+ $/="\r",$adj=1 if (length($hdr) > 10);
+
+ while (<$PD>)
+ {
+ chomp;
+
+ s/\n//;
+
+ if (m/endstream(\s+.*)?$/)
+ {
+ $instream=0;
+ $_="endstream";
+ $_.=$1 if defined($1)
+ }
+
+ next if $instream;
+
+ if (m'/Length\s+(\d+)(\s+\d+\s+R)?')
+ {
+ if (!defined($2))
+ {
+ $strmlen=$1;
+ }
+ else
+ {
+ $strmlen=0;
+ }
+ }
+
+ if (m'^(\d+) \d+ obj')
+ {
+ $curobj=$1;
+ $pdf->[$curobj]->{OBJ}=undef;
+ }
+
+ if (m'stream\s*$' and ! m/^endstream/)
+ {
+ if ($curobj > -1)
+ {
+ $pdf->[$curobj]->{STREAMPOS}=[tell($PD)+$adj,$strmlen];
+ seek($PD,$strmlen,1);
+ $instream=1;
+ }
+ else
+ {
+ Warn("parsing PDF '$pdfnm' failed");
+ return undef;
+ }
+ }
+
+ s/%.*?$//;
+ $pdftxt.=$_.' ';
+ }
+
+ close($PD);
+
+ open(PD,"<$PDnm");
+# $pdftxt=~s/\]/ \]/g;
+ my (@pdfwds)=split(' ',$pdftxt);
+ my $wd;
+ my $root;
+
+ while ($wd=nextwd(\@pdfwds),length($wd))
+ {
+ if ($wd=~m/\d+/ and defined($pdfwds[1]) and $pdfwds[1]=~m/^obj(.*)/)
+ {
+ $curobj=$wd;
+ shift(@pdfwds); shift(@pdfwds);
+ unshift(@pdfwds,$1) if defined($1) and length($1);
+ $pdf->[$curobj]->{OBJ}=ParsePDFObj(\@pdfwds);
+ my $o=$pdf->[$curobj];
+
+ if (ref($o->{OBJ}) eq 'HASH' and exists($o->{OBJ}->{Type}) and $o->{OBJ}->{Type} eq '/ObjStm')
+ {
+ LoadStream($o,$pdf);
+ my $pos=$o->{OBJ}->{First};
+ my $s=$o->{STREAM};
+ my @o=split(' ',substr($s,0,$pos));
+ substr($s,0,$pos)='';
+ push(@o,-1,length($s));
+
+ for (my $j=0; $j<=$#o-2; $j+=2)
+ {
+ my @w=split(' ',substr($s,$o[$j+1],$o[$j+3]-$o[$j+1]));
+ $pdf->[$o[$j]]->{OBJ}=ParsePDFObj(\@w);
+ }
+
+ $pdf->[$curobj]=undef;
+ }
+
+ $root=$curobj if ref($pdf->[$curobj]->{OBJ}) eq 'HASH' and exists($pdf->[$curobj]->{OBJ}->{Type}) and $pdf->[$curobj]->{OBJ}->{Type} eq '/XRef';
+ }
+ elsif ($wd eq 'trailer' and !exists($pdf->[0]->{OBJ}))
+ {
+ $pdf->[0]->{OBJ}=ParsePDFObj(\@pdfwds);
+ }
+ else
+ {
+# print "Skip '$wd'\n";
+ }
+ }
+
+ $pdf->[0]=$pdf->[$root] if !defined($pdf->[0]);
+ my $catalog=${$pdf->[0]->{OBJ}->{Root}};
+ my $page=FindPage(1,$pdf);
+ my $xobj=++$objct;
+
+ # Load the streamas
+
+ foreach my $o (@{$pdf})
+ {
+ if (exists($o->{STREAMPOS}) and !exists($o->{STREAM}))
+ {
+ LoadStream($o,$pdf);
+ }
+ }
+
+ close(PD);
+
+ # Find BBox
+ my $BBox;
+ my $insmap={};
+
+ foreach my $k (qw( ArtBox TrimBox BleedBox CropBox MediaBox ))
+ {
+ $BBox=FindKey($pdf,$page,$k);
+ last if $BBox;
+ }
+
+ $BBox=[0,0,595,842] if !defined($BBox);
+
+ $wid=($BBox->[2]-$BBox->[0]+1) if $wid==0;
+ my $xscale=d3(abs($wid)/($BBox->[2]-$BBox->[0]+1));
+ my $yscale=d3(($hgt<=0)?$xscale:(abs($hgt)/($BBox->[3]-$BBox->[1]+1)));
+ $hgt=($BBox->[3]-$BBox->[1]+1)*$yscale;
+
+ if ($type eq "import")
+ {
+ $mat->[0]=$xscale;
+ $mat->[3]=$yscale;
+ }
+
+ # Find Resource
+
+ my $res=FindKey($pdf,$page,'Resources');
+ my $xonm="XO$xobj";
+
+ # Map inserted objects to current PDF
+
+ MapInsValue($pdf,$page,'',$insmap,$xobj,$pdf->[$page]->{OBJ});
+#
+# Many PDFs include 'Resources' at the 'Page' level but if 'Resources' is held at a higher level (i.e 'Pages')
+# then we need to include its objects as well.
+#
+ MapInsValue($pdf,$page,'',$insmap,$xobj,$res) if !exists($pdf->[$page]->{OBJ}->{Resources});
+
+ # Copy Resources
+
+ my %incres=%{$res};
+
+ $incres{ProcSet}=['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI'];
+
+ ($mat->[4],$mat->[5])=split(' ',PutXY($xpos,$ypos));
+ $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => '/XObject', 'BBox' => $BBox, 'Name' => "/$xonm", 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject", 'Resources' => \%incres});
+
+ if ($BBox->[0] != 0 or $BBox->[1] != 0)
+ {
+ my (@matrix)=(1,0,0,1,-$BBox->[0],-$BBox->[1]);
+ $obj[$xobj]->{DATA}->{Matrix}=\@matrix;
+ }
+
+ BuildStream($xobj,$pdf,$pdf->[$page]->{OBJ}->{Contents});
+
+ $/=$keepsep;
+ return([$xonm,$BBox] );
+}
+
+sub LoadStream
+{
+ my $o=shift;
+ my $pdf=shift;
+ my $l;
+
+ $l=$o->{OBJ}->{Length} if exists($o->{OBJ}->{Length});
+
+ $l=$pdf->[$$l]->{OBJ} if (defined($l) && ref($l) eq 'OBJREF');
+
+ Die("unable to determine length of stream \@$o->{STREAMPOS}->[0]")
+ if !defined($l);
+
+ sysseek(PD,$o->{STREAMPOS}->[0],0);
+ Warn("failed to read all of the stream")
+ if $l != sysread(PD,$o->{STREAM},$l);
+
+ if ($gotzlib and exists($o->{OBJ}->{'Filter'}) and $o->{OBJ}->{'Filter'} eq '/FlateDecode')
+ {
+ $o->{STREAM}=Compress::Zlib::uncompress($o->{STREAM});
+ delete($o->{OBJ }->{'Filter'});
+ }
+}
+
+sub BuildStream
+{
+ my $xobj=shift;
+ my $pdf=shift;
+ my $val=shift;
+ my $strm='';
+ my $objs;
+ my $refval=ref($val);
+
+ if ($refval eq 'OBJREF')
+ {
+ push(@{$objs}, $val);
+ }
+ elsif ($refval eq 'ARRAY')
+ {
+ $objs=$val;
+ }
+ else
+ {
+ Warn("unexpected 'Contents'");
+ }
+
+ foreach my $o (@{$objs})
+ {
+ $strm.="\n" if $strm;
+ $strm.=$pdf->[$$o]->{STREAM} if exists($pdf->[$$o]->{STREAM});
+ }
+
+ $obj[$xobj]->{STREAM}=$strm;
+}
+
+
+sub MapInsHash
+{
+ my $pdf=shift;
+ my $o=shift;
+ my $insmap=shift;
+ my $parent=shift;
+ my $val=shift;
+
+
+ foreach my $k (sort keys(%{$val}))
+ {
+ MapInsValue($pdf,$o,$k,$insmap,$parent,$val->{$k}) if $k ne 'Contents';
+ }
+}
+
+sub MapInsValue
+{
+ my $pdf=shift;
+ my $o=shift;
+ my $k=shift;
+ my $insmap=shift;
+ my $parent=shift;
+ my $val=shift;
+ my $refval=ref($val);
+
+ if ($refval eq 'OBJREF')
+ {
+ if ($k ne 'Parent')
+ {
+ if (!exists($insmap->{IMP}->{$$val}))
+ {
+ $objct++;
+ $insmap->{CUR}->{$objct}=$$val;
+ $insmap->{IMP}->{$$val}=$objct;
+ $obj[$objct]->{DATA}=$pdf->[$$val]->{OBJ};
+ $obj[$objct]->{STREAM}=$pdf->[$$val]->{STREAM} if exists($pdf->[$$val]->{STREAM});
+ MapInsValue($pdf,$$val,'',$insmap,$o,$pdf->[$$val]->{OBJ});
+ }
+
+ $$val=$insmap->{IMP}->{$$val};
+ }
+ else
+ {
+ $$val=$parent;
+ }
+ }
+ elsif ($refval eq 'ARRAY')
+ {
+ foreach my $v (@{$val})
+ {
+ MapInsValue($pdf,$o,'',$insmap,$parent,$v)
+ }
+ }
+ elsif ($refval eq 'HASH')
+ {
+ MapInsHash($pdf,$o,$insmap,$parent,$val);
+ }
+
+}
+
+sub FindKey
+{
+ my $pdf=shift;
+ my $page=shift;
+ my $k=shift;
+
+ if (exists($pdf->[$page]->{OBJ}->{$k}))
+ {
+ my $val=$pdf->[$page]->{OBJ}->{$k};
+ $val=$pdf->[$$val]->{OBJ} if ref($val) eq 'OBJREF';
+ return($val);
+ }
+ else
+ {
+ if (exists($pdf->[$page]->{OBJ}->{Parent}))
+ {
+ return(FindKey($pdf,${$pdf->[$page]->{OBJ}->{Parent}},$k));
+ }
+ }
+
+ return(undef);
+}
+
+sub FindPage
+{
+ my $wantpg=shift;
+ my $pdf=shift;
+ my $catalog=${$pdf->[0]->{OBJ}->{Root}};
+ my $pages=${$pdf->[$catalog]->{OBJ}->{Pages}};
+
+ return(NextPage($pdf,$pages,\$wantpg));
+}
+
+sub NextPage
+{
+ my $pdf=shift;
+ my $pages=shift;
+ my $wantpg=shift;
+ my $ret;
+
+ if ($pdf->[$pages]->{OBJ}->{Type} eq '/Pages')
+ {
+ foreach my $kid (@{$pdf->[$pages]->{OBJ}->{Kids}})
+ {
+ $ret=NextPage($pdf,$$kid,$wantpg);
+ last if $$wantpg<=0;
+ }
+ }
+ elsif ($pdf->[$pages]->{OBJ}->{Type} eq '/Page')
+ {
+ $$wantpg--;
+ $ret=$pages;
+ }
+
+ return($ret);
+}
+
+sub nextwd
+{
+ my $pdfwds=shift;
+
+ my $wd=shift(@{$pdfwds});
+
+ return('') if !defined($wd);
+
+ if ($wd=~m/^(.*?)(<<|>>|(?:(?<!\\)\[|\]))(.*)/)
+ {
+ if (defined($1) and length($1))
+ {
+ unshift(@{$pdfwds},$3) if defined($3) and length($3);
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ else
+ {
+ unshift(@{$pdfwds},$3) if defined($3) and length($3);
+ $wd=$2;
+ }
+ }
+
+ return($wd);
+}
+
+sub ParsePDFObj
+{
+
+ my $pdfwds=shift;
+ my $rtn;
+ my $wd;
+
+ while ($wd=nextwd($pdfwds),length($wd))
+ {
+ if ($wd eq 'stream' or $wd eq 'endstream')
+ {
+ next;
+ }
+ elsif ($wd eq 'endobj' or $wd eq 'startxref')
+ {
+ last;
+ }
+ else
+ {
+ unshift(@{$pdfwds},$wd);
+ $rtn=ParsePDFValue($pdfwds);
+ }
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFHash
+{
+ my $pdfwds=shift;
+ my $rtn={};
+ my $wd;
+
+ while ($wd=nextwd($pdfwds),length($wd))
+ {
+ if ($wd eq '>>')
+ {
+ last;
+ }
+
+ my (@w)=split('/',$wd,3);
+
+ if ($w[0])
+ {
+ Warn("PDF Dict Key '$wd' does not start with '/'");
+ exit 1;
+ }
+ else
+ {
+ unshift(@{$pdfwds},"/$w[2]") if $w[2];
+ $wd=$w[1];
+ (@w)=split('\(',$wd,2);
+ $wd=$w[0];
+ unshift(@{$pdfwds},"($w[1]") if defined($w[1]);
+ (@w)=split('\<',$wd,2);
+ $wd=$w[0];
+ unshift(@{$pdfwds},"<$w[1]") if defined($w[1]);
+
+ $rtn->{$wd}=ParsePDFValue($pdfwds);
+ }
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFValue
+{
+ my $pdfwds=shift;
+ my $rtn;
+ my $wd=nextwd($pdfwds);
+
+ if ($wd=~m/^\d+$/ and $pdfwds->[0]=~m/^\d+$/ and $pdfwds->[1]=~m/^R(\]|\>|\/)?/)
+ {
+ shift(@{$pdfwds});
+ if (defined($1) and length($1))
+ {
+ $pdfwds->[0]=substr($pdfwds->[0],1);
+ }
+ else
+ {
+ shift(@{$pdfwds});
+ }
+ return(bless(\$wd,'OBJREF'));
+ }
+
+ if ($wd eq '<<')
+ {
+ return(ParsePDFHash($pdfwds));
+ }
+
+ if ($wd eq '[')
+ {
+ return(ParsePDFArray($pdfwds));
+ }
+
+ if ($wd=~m/(.*?)(\(.*)$/)
+ {
+ if (defined($1) and length($1))
+ {
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ else
+ {
+ return(ParsePDFString($wd,$pdfwds));
+ }
+ }
+
+ if ($wd=~m/(.*?)(\<.*)$/)
+ {
+ if (defined($1) and length($1))
+ {
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ else
+ {
+ return(ParsePDFHexString($wd,$pdfwds));
+ }
+ }
+
+ if ($wd=~m/(.+?)(\/.*)$/)
+ {
+ if (defined($2) and length($2))
+ {
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ }
+
+ return($wd);
+}
+
+sub ParsePDFString
+{
+ my $wd=shift;
+ my $rtn='';
+ my $pdfwds=shift;
+ my $lev=0;
+
+ while (length($wd))
+ {
+ $rtn.=' ' if length($rtn);
+
+ while ($wd=~m/(?<!\\)\(/g) {$lev++;}
+ while ($wd=~m/(?<!\\)\)/g) {$lev--;}
+
+
+ if ($lev<=0 and $wd=~m/^(.*?\))([^)]+)$/)
+ {
+ unshift(@{$pdfwds},$2) if defined($2) and length($2);
+ $wd=$1;
+ }
+
+ $rtn.=$wd;
+
+ last if $lev <= 0;
+
+ $wd=nextwd($pdfwds);
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFHexString
+{
+ my $wd=shift;
+ my $rtn='';
+ my $pdfwds=shift;
+ my $lev=0;
+
+ if ($wd=~m/^(<.+?>)(.*)/)
+ {
+ unshift(@{$pdfwds},$2) if defined($2) and length($2);
+ $rtn=$1;
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFArray
+{
+ my $pdfwds=shift;
+ my $rtn=[];
+ my $wd;
+
+ while (1)
+ {
+ $wd=ParsePDFValue($pdfwds);
+ last if $wd eq ']' or length($wd)==0;
+ push(@{$rtn},$wd);
+ }
+
+ return($rtn);
+}
+
+sub Warn
+{
+ Msg(0,(@_));
+}
+
+sub Die
+{
+ Msg(1,(@_));
+}
+
+sub Msg
+{
+ my ($fatal,$msg)=@_;
+
+ print STDERR "$prog:";
+ print STDERR "$env{SourceFile}:" if exists($env{SourceFile});
+ print STDERR " ";
+
+ if ($fatal)
+ {
+ print STDERR "fatal error: ";
+ }
+ else
+ {
+ print STDERR "warning: ";
+ }
+
+ print STDERR "$msg\n";
+ exit 1 if $fatal;
+}
+
+sub PutXY
+{
+ my ($x,$y)=(@_);
+
+ if ($frot)
+ {
+ return(d3($y)." ".d3($x));
+ }
+ else
+ {
+ $y=$mediabox[3]-$y;
+ return(d3($x)." ".d3($y));
+ }
+}
+
+sub GraphY
+{
+ my $y=shift;
+
+ if ($frot)
+ {
+ return($y);
+ }
+ else
+ {
+ return($mediabox[3]-$y);
+ }
+}
+
+sub Put
+{
+ my $msg=shift;
+
+ print $msg;
+ $fct+=length($msg);
+}
+
+sub PutObj
+{
+ my $ono=shift;
+ my $msg="$ono 0 obj ";
+ $obj[$ono]->{XREF}=$fct;
+ if (exists($obj[$ono]->{STREAM}))
+ {
+ if ($gotzlib && !$debug && !exists($obj[$ono]->{DATA}->{'Filter'}))
+ {
+ $obj[$ono]->{STREAM}=Compress::Zlib::compress($obj[$ono]->{STREAM});
+ $obj[$ono]->{DATA}->{'Filter'}='/FlateDecode';
+ }
+
+ $obj[$ono]->{DATA}->{'Length'}=length($obj[$ono]->{STREAM});
+ }
+ PutField(\$msg,$obj[$ono]->{DATA});
+ PutStream(\$msg,$ono) if exists($obj[$ono]->{STREAM});
+ Put($msg."endobj\n");
+}
+
+sub PutStream
+{
+ my $msg=shift;
+ my $ono=shift;
+
+ # We could 'flate' here
+ $$msg.="stream\n$obj[$ono]->{STREAM}endstream\n";
+}
+
+sub PutField
+{
+ my $pmsg=shift;
+ my $fld=shift;
+ my $term=shift||"\n";
+ my $typ=ref($fld);
+
+ if ($typ eq '')
+ {
+ $$pmsg.="$fld$term";
+ }
+ elsif ($typ eq 'ARRAY')
+ {
+ $$pmsg.='[';
+ foreach my $cell (@{$fld})
+ {
+ PutField($pmsg,$cell,' ');
+ }
+ $$pmsg.="]$term";
+ }
+ elsif ($typ eq 'HASH')
+ {
+ $$pmsg.='<< ';
+ foreach my $key (sort keys %{$fld})
+ {
+ $$pmsg.="/$key ";
+ PutField($pmsg,$fld->{$key});
+ }
+ $$pmsg.=">>$term";
+ }
+ elsif ($typ eq 'OBJREF')
+ {
+ $$pmsg.="$$fld 0 R$term";
+ }
+}
+
+sub BuildObj
+{
+ my $ono=shift;
+ my $val=shift;
+
+ $obj[$ono]->{DATA}=$val;
+
+ return("$ono 0 R ");
+}
+
+sub LoadFont
+{
+ my $fontno=shift;
+ my $fontnm=shift;
+ my $ofontnm=$fontnm;
+
+ return $fontlst{$fontno}->{OBJ} if (exists($fontlst{$fontno}));
+
+ my $f;
+ OpenFile(\$f,$fontdir,"$fontnm");
+
+ if (!defined($f) and $Foundry)
+ {
+ # Try with no foundry
+ $fontnm=~s/.*?-//;
+ OpenFile(\$f,$fontdir,$fontnm);
+ }
+
+ Die("unable to open font '$ofontnm' for mounting") if !defined($f);
+
+ my $foundry='';
+ $foundry=$1 if $fontnm=~m/^(.*?)-/;
+ my $stg=1;
+ my %fnt;
+ my @fntbbox=(0,0,0,0);
+ my $capheight=0;
+ my $lastchr=0;
+ my $lastnm;
+ my $t1flags=0;
+ my $fixwid=-1;
+ my $ascent=0;
+ my $charset='';
+
+ while (<$f>)
+ {
+ chomp;
+
+ s/^ +//;
+ s/^#.*// if $stg == 1;
+ next if $_ eq '';
+
+ if ($stg == 1)
+ {
+ my ($key,$val)=split(' ',$_,2);
+
+ $key=lc($key);
+ $stg=2,next if $key eq 'kernpairs';
+ $stg=3,next if lc($_) eq 'charset';
+
+ $fnt{$key}=$val
+ }
+ elsif ($stg == 2)
+ {
+ $stg=3,next if lc($_) eq 'charset';
+
+ my ($ch1,$ch2,$k)=split;
+# $fnt{KERN}->{$ch1}->{$ch2}=$k;
+ }
+ else
+ {
+ my (@r)=split;
+ my (@p)=split(',',$r[1]);
+
+ if ($r[1] eq '"')
+ {
+ $fnt{NAM}->{$r[0]}=[@{$fnt{NAM}->{$lastnm}}];
+ next;
+ }
+
+ $r[3]=oct($r[3]) if substr($r[3],0,1) eq '0';
+ $r[0]='u0020' if $r[3] == 32;
+ $r[0]="u00".hex($r[3]) if $r[0] eq '---';
+# next if $r[3] >255;
+ $r[4]=$r[0] if !defined($r[4]);
+ $fnt{NAM}->{$r[0]}=[$p[0],$r[3],'/'.$r[4],$r[3],0];
+ $fnt{NO}->[$r[3]]=[$r[0],$r[0]];
+ $lastnm=$r[0];
+ $lastchr=$r[3] if $r[3] > $lastchr;
+ $fixwid=$p[0] if $fixwid == -1;
+ $fixwid=-2 if $fixwid > 0 and $p[0] != $fixwid;
+
+ $fntbbox[1]=-$p[2] if defined($p[2]) and -$p[2] < $fntbbox[1];
+ $fntbbox[2]=$p[0] if $p[0] > $fntbbox[2];
+ $fntbbox[3]=$p[1] if defined($p[1]) and $p[1] > $fntbbox[3];
+ $ascent=$p[1] if defined($p[1]) and $p[1] > $ascent and $r[3] >= 32 and $r[3] < 128;
+ $charset.='/'.$r[4] if defined($r[4]);
+ $capheight=$p[1] if length($r[4]) == 1 and $r[4] ge 'A' and $r[4] le 'Z' and $p[1] > $capheight;
+ }
+ }
+
+ close($f);
+
+ foreach my $j (0..$lastchr)
+ {
+ $fnt{NO}->[$j]=['',''] if !defined($fnt{NO}->[$j]);
+ }
+
+ my $fno=0;
+ my $slant=0;
+ $fnt{DIFF}=[];
+ $fnt{WIDTH}=[];
+ $fnt{NAM}->{''}=[0,-1,'/.notdef',-1,0];
+ $slant=-$fnt{'slant'} if exists($fnt{'slant'});
+ $fnt{'spacewidth'}=700 if !exists($fnt{'spacewidth'});
+
+ $t1flags|=2**0 if $fixwid > -1;
+ $t1flags|=(exists($fnt{'special'}))?2**2:2**5;
+ $t1flags|=2**6 if $slant != 0;
+ my $fontkey="$foundry $fnt{internalname}";
+
+ if (exists($download{$fontkey}))
+ {
+ # Not a Base Font
+ my ($l1,$l2,$l3,$t1stream)=GetType1($download{$fontkey});
+ Warn("incorrect font format for '$fontkey' ($l1)")
+ if !defined($t1stream);
+ $fno=++$objct;
+ $fontlst{$fontno}->{OBJ}=BuildObj($objct,
+ {'Type' => '/Font',
+ 'Subtype' => '/Type1',
+ 'BaseFont' => '/'.$fnt{internalname},
+ 'Widths' => $fnt{WIDTH},
+ 'FirstChar' => 0,
+ 'LastChar' => $lastchr,
+ 'Encoding' => BuildObj($objct+1,
+ {'Type' => '/Encoding',
+ 'Differences' => $fnt{DIFF}
+ }
+ ),
+ 'FontDescriptor' => BuildObj($objct+2,
+ {'Type' => '/FontDescriptor',
+ 'FontName' => '/'.$fnt{internalname},
+ 'Flags' => $t1flags,
+ 'FontBBox' => \@fntbbox,
+ 'ItalicAngle' => $slant,
+ 'Ascent' => $ascent,
+ 'Descent' => $fntbbox[1],
+ 'CapHeight' => $capheight,
+ 'StemV' => 0,
+# 'CharSet' => "($charset)",
+ 'FontFile' => BuildObj($objct+3,
+ {'Length1' => $l1,
+ 'Length2' => $l2,
+ 'Length3' => $l3
+ }
+ )
+ }
+ )
+ }
+ );
+
+ $objct+=3;
+ $fontlst{$fontno}->{NM}='/F'.$fontno;
+ $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ};
+ $fontlst{$fontno}->{FNT}=\%fnt;
+ $obj[$objct]->{STREAM}=$t1stream;
+
+ }
+ else
+ {
+ if (exists($missing{$fontkey}))
+ {
+ Warn("The download file in '$missing{$fontkey}' "
+ . " has erroneous entry for '$fnt{internalname} ($ofontnm)'");
+ }
+ else
+ {
+ Warn("unable to embed font file for '$fnt{internalname}'"
+ . " ($ofontnm) (missing entry in 'download' file?)")
+ if $embedall;
+ }
+ $fno=++$objct;
+ $fontlst{$fontno}->{OBJ}=BuildObj($objct,
+ {'Type' => '/Font',
+ 'Subtype' => '/Type1',
+ 'BaseFont' => '/'.$fnt{internalname},
+ 'Widths' => $fnt{WIDTH},
+ 'FirstChar' => 0,
+ 'LastChar' => $lastchr,
+ 'Encoding' => BuildObj($objct+1,
+ {'Type' => '/Encoding',
+ 'Differences' => $fnt{DIFF}
+ }
+ ),
+ 'FontDescriptor' => BuildObj($objct+2,
+ {'Type' => '/FontDescriptor',
+ 'FontName' => '/'.$fnt{internalname},
+ 'Flags' => $t1flags,
+ 'FontBBox' => \@fntbbox,
+ 'ItalicAngle' => $slant,
+ 'Ascent' => $ascent,
+ 'Descent' => $fntbbox[1],
+ 'CapHeight' => $capheight,
+ 'StemV' => 0,
+ 'CharSet' => "($charset)",
+ }
+ )
+ }
+ );
+
+ $objct+=2;
+ $fontlst{$fontno}->{NM}='/F'.$fontno;
+ $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ};
+ $fontlst{$fontno}->{FNT}=\%fnt;
+ }
+
+ if (defined($fnt{encoding}) and $fnt{encoding} eq 'text.enc' and $ucmap ne '')
+ {
+ if ($textenccmap eq '')
+ {
+ $textenccmap = BuildObj($objct+1,{});
+ $objct++;
+ $obj[$objct]->{STREAM}=$ucmap;
+ }
+ $obj[$fno]->{DATA}->{'ToUnicode'}=$textenccmap;
+ }
+
+# PutObj($fno);
+# PutObj($fno+1);
+# PutObj($fno+2) if defined($obj[$fno+2]);
+# PutObj($fno+3) if defined($obj[$fno+3]);
+}
+
+sub GetType1
+{
+ my $file=shift;
+ my ($l1,$l2,$l3); # Return lengths
+ my ($head,$body,$tail); # Font contents
+ my $f;
+
+ OpenFile(\$f,$fontdir,"$file");
+ Die("unable to open font '$file' for embedding") if !defined($f);
+
+ $head=GetChunk($f,1,"currentfile eexec");
+ $body=GetChunk($f,2,"00000000") if !eof($f);
+ $tail=GetChunk($f,3,"cleartomark") if !eof($f);
+
+ $l1=length($head);
+ $l2=length($body);
+ $l3=length($tail);
+
+ return($l1,$l2,$l3,"$head$body$tail");
+}
+
+sub GetChunk
+{
+ my $F=shift;
+ my $segno=shift;
+ my $ascterm=shift;
+ my ($type,$hdr,$chunk,@msg);
+ binmode($F);
+ my $enc="ascii";
+
+ while (1)
+ {
+ # There may be multiple chunks of the same type
+
+ my $ct=read($F,$hdr,2);
+
+ if ($ct==2)
+ {
+ if (substr($hdr,0,1) eq "\x80")
+ {
+ # binary chunk
+
+ my $chunktype=ord(substr($hdr,1,1));
+ $enc="binary";
+
+ if (defined($type) and $type != $chunktype)
+ {
+ seek($F,-2,1);
+ last;
+ }
+
+ $type=$chunktype;
+ return if $chunktype == 3;
+
+ $ct=read($F,$hdr,4);
+ Die("failed to read binary segment length") if $ct != 4;
+ my $sl=unpack('V',$hdr);
+ my $data;
+ my $chk=read($F,$data,$sl);
+ Die("failed to read binary segment") if $chk != $sl;
+ $chunk.=$data;
+ }
+ else
+ {
+ # ascii chunk
+
+ my $hex=0;
+ seek($F,-2,1);
+ my $ct=0;
+
+ while (1)
+ {
+ my $lin=<$F>;
+
+ last if !$lin;
+
+ $hex=1,$enc.=" hex" if $segno == 2 and !$ct and $lin=~m/^[A-F0-9a-f]{4,4}/;
+
+ if ($segno !=2 and $lin=~m/^(.*$ascterm\n?)(.*)/)
+ {
+ $chunk.=$1;
+ seek($F,-length($2)-1,1) if $2;
+ last;
+ }
+ elsif ($segno == 2 and $lin=~m/^(.*?)($ascterm.*)/)
+ {
+ $chunk.=$1;
+ seek($F,-length($2)-1,1) if $2;
+ last;
+ }
+
+ chomp($lin), $lin=pack('H*',$lin) if $hex;
+ $chunk.=$lin; $ct++;
+ }
+
+ last;
+ }
+ }
+ else
+ {
+ push(@msg,"Failed to read 2 header bytes");
+ }
+ }
+
+ return $chunk;
+}
+
+sub OutStream
+{
+ my $ono=shift;
+
+ IsGraphic();
+ $stream.="Q\n";
+ $obj[$ono]->{STREAM}=$stream;
+ $obj[$ono]->{DATA}->{Length}=length($stream);
+ $stream='';
+ PutObj($ono);
+}
+
+sub do_p
+{
+ my $trans='BLOCK';
+
+ $trans='PAGE' if $firstpause;
+ NewPage($trans);
+ @XOstream=();
+ @PageAnnots=();
+ $firstpause=1;
+}
+
+sub FixTrans
+{
+ my $t=shift;
+ my $style=$t->{S};
+
+ if ($style)
+ {
+ delete($t->{Dm}) if $style ne '/Split' and $style ne '/Blinds';
+ delete($t->{M}) if !($style eq '/Split' or $style eq '/Box' or $style eq '/Fly');
+ delete($t->{Di}) if !($style eq '/Wipe' or $style eq '/Glitter' or $style eq '/Fly' or $style eq '/Cover' or $style eq '/Uncover' or $style eq '/Push') or ($style eq '/Fly' and $t->{Di} eq '/None' and $t->{SS} != 1);
+ delete($t->{SS}) if !($style eq '/Fly');
+ delete($t->{B}) if !($style eq '/Fly');
+ }
+
+ return($t);
+}
+
+sub NewPage
+{
+ my $trans=shift;
+ # Start of pages
+
+ if ($cpageno > 0)
+ {
+ if ($#XOstream>=0)
+ {
+ MakeXO() if $stream;
+ $stream=join("\n",@XOstream,'');
+ }
+
+ my %t=%{$transition->{$trans}};
+ $cpage->{MediaBox}=\@mediabox if $custompaper;
+ $cpage->{Trans}=FixTrans(\%t) if $t{S};
+
+ if ($#PageAnnots >= 0)
+ {
+ @{$cpage->{Annots}}=@PageAnnots;
+ }
+
+ if ($#bgstack > -1 or $bgbox)
+ {
+ my $box="q 1 0 0 1 0 0 cm ";
+
+ foreach my $bg (@bgstack)
+ {
+ # 0=$bgtype # 1=stroke 2=fill. 4=page
+ # 1=$strkcol
+ # 2=$fillcol
+ # 3=(Left,Top,Right,bottom,LineWeight)
+ # 4=Start ypos
+ # 5=Endypos
+ # 6=Line Weight
+
+ my $pg=$bg->[3] || \@defaultmb;
+
+ $bg->[5]=$pg->[3]; # box is continuing to next page
+ $box.=DrawBox($bg);
+ $bg->[4]=$pg->[1]; # will continue from page top
+ }
+
+ $stream=$box.$bgbox."Q\n".$stream;
+ $bgbox='';
+ $boxmax=0;
+ }
+
+ PutObj($cpageno);
+ OutStream($cpageno+1);
+ }
+
+ $cpageno=++$objct;
+
+ my $thispg=BuildObj($objct,
+ {'Type' => '/Page',
+ 'Group' => {'CS' => '/DeviceRGB', 'S' => '/Transparency'},
+ 'Parent' => '2 0 R',
+ 'Contents' => [ BuildObj($objct+1,
+ {'Length' => 0}
+ ) ],
+ }
+ );
+
+ splice(@{$pages->{Kids}},++$pginsert,0,$thispg);
+ splice(@outlines,$pginsert,0,[$curoutlev,$#{$curoutlev}+1,$thislev]);
+
+ $objct+=1;
+ $cpage=$obj[$cpageno]->{DATA};
+ $pages->{'Count'}++;
+ $stream="q 1 0 0 1 0 0 cm\n$linejoin J\n$linecap j\n0.4 w\n";
+ $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne '';
+ $mode='g';
+ $curfill='';
+# @mediabox=@defaultmb;
+}
+
+sub DrawBox
+{
+ my $bg=shift;
+ my $res='';
+ my $pg=$bg->[3] || \@mediabox;
+ $bg->[4]=$pg->[1], $bg->[5]=$pg->[3] if $bg->[0] & 4;
+ my $bot=$bg->[5];
+ $bot=$boxmax if $boxmax > $bot;
+ my $wid=$pg->[2]-$pg->[0];
+ my $dep=$bot-$bg->[4];
+
+ $res="$bg->[1] $bg->[2] $bg->[6] w\n";
+ $res.="$pg->[0] $bg->[4] $wid $dep re f " if $bg->[0] & 1;
+ $res.="$pg->[0] $bg->[4] $wid $dep re s " if $bg->[0] & 2;
+ return("$res\n");
+}
+
+sub MakeXO
+{
+ $stream.="%mode=$mode\n";
+ IsGraphic();
+ $stream.="Q\n";
+ my $xobj=++$objct;
+ my $xonm="XO$xobj";
+ $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => '/XObject', 'BBox' => \@mediabox, 'Name' => "/$xonm", 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject"});
+ $obj[$xobj]->{STREAM}=$stream;
+ $stream='';
+ push(@XOstream,"q") if $#XOstream==-1;
+ push(@XOstream,"/$xonm Do");
+}
+
+sub do_f
+{
+ my $par=shift;
+ my $fnt=$fontlst{$par}->{FNT};
+
+# IsText();
+ $cft="$par";
+ $fontchg=1;
+# $stream.="/F$cft $cftsz Tf\n" if $cftsz;
+ $widtbl=CacheWid($par);
+ $origwidtbl=[];
+
+ foreach my $w (@{$fnt->{NO}})
+ {
+ push(@{$origwidtbl},$fnt->{NAM}->{$w->[1]}->[WIDTH]);
+ }
+
+# $krntbl=$fnt->{KERN};
+}
+
+sub CacheWid
+{
+ my $par=shift;
+
+ if (!defined($fontlst{$par}->{CACHE}->{$cftsz}))
+ {
+ $fontlst{$par}->{CACHE}->{$cftsz}=BuildCache($fontlst{$par}->{FNT});
+ }
+
+ return($fontlst{$par}->{CACHE}->{$cftsz});
+}
+
+sub BuildCache
+{
+ my $fnt=shift;
+ my @cwid;
+ $origwidtbl=[];
+
+ foreach my $w (@{$fnt->{NO}})
+ {
+ my $wid=(defined($w) and defined($w->[1]))?$fnt->{NAM}->{$w->[1]}->[WIDTH]:0;
+ push(@cwid,$wid*$cftsz);
+ push(@{$origwidtbl},$wid);
+ }
+
+ return(\@cwid);
+}
+
+sub IsText
+{
+ if ($mode eq 'g')
+ {
+ $xpos+=$pendmv/$unitwidth;
+ $stream.="q BT\n$matrix ".PutXY($xpos,$ypos)." Tm\n";
+ $poschg=0;
+ $fontchg=0;
+ $pendmv=0;
+ $matrixchg=0;
+ $tmxpos=$xpos;
+ $stream.=$textcol."\n", $curfill=$textcol if $textcol ne $curfill;
+ if (defined($cft))
+ {
+ $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
+ $stream.="/F$cft $cftsz Tf\n";
+ }
+ $stream.="$curkern Tc\n";
+ }
+
+ if ($poschg or $matrixchg)
+ {
+ PutLine(0) if $matrixchg;
+ $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
+ $tmxpos=$xpos;
+ $matrixchg=0;
+ $stream.="$curkern Tc\n";
+ }
+
+ if ($fontchg)
+ {
+ PutLine(0);
+ $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
+ $stream.="/F$cft $cftsz Tf\n" if $cftsz and defined($cft);
+ $fontchg=0;
+ }
+
+ $mode='t';
+}
+
+sub IsGraphic
+{
+ if ($mode eq 't')
+ {
+ PutLine();
+ $stream.="ET Q\n";
+ $xpos+=($pendmv-$nomove)/$unitwidth;
+ $pendmv=0;
+ $nomove=0;
+ $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne $curstrk;
+ $curfill=$fillcol;
+ }
+ $mode='g';
+}
+
+sub do_s
+{
+ my $par=shift;
+ $par/=$unitwidth;
+
+ if ($par != $cftsz and defined($cft))
+ {
+ PutLine();
+ $cftsz=$par;
+ Set_LWidth() if $lwidth < 1;
+# $stream.="/F$cft $cftsz Tf\n";
+ $fontchg=1;
+ $widtbl=CacheWid($cft);
+ }
+ else
+ {
+ $cftsz=$par;
+ Set_LWidth() if $lwidth < 1;
+ }
+}
+
+sub Set_LWidth
+{
+ IsGraphic();
+ $stream.=((($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000)." w\n";
+ return;
+}
+
+sub do_m
+{
+ # Groff uses /m[] for text & graphic stroke, and /M[] (DF?) for graphic fill.
+ # PDF uses G/RG/K for graphic stroke, and g/rg/k for text & graphic fill.
+ #
+ # This means that we must maintain g/rg/k state separately for text colour & graphic fill (this is
+ # probably why 'gs' maintains separate graphic states for text & graphics when distilling PS -> PDF).
+ #
+ # To facilitate this:-
+ #
+ # $textcol = current groff stroke colour
+ # $fillcol = current groff fill colour
+ # $curfill = current PDF fill colour
+
+ my $par=shift;
+ my $mcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $par=~s/^ +//;
+
+# IsGraphic();
+
+ $textcol=set_col($mcmd,$par,0);
+ $strkcol=set_col($mcmd,$par,1);
+
+ if ($mode eq 't')
+ {
+ PutLine();
+ $stream.=$textcol."\n";
+ $curfill=$textcol;
+ }
+ else
+ {
+ $stream.="$strkcol\n";
+ $curstrk=$strkcol;
+ }
+}
+
+sub set_col
+{
+ my $mcmd=shift;
+ my $par=shift;
+ my $upper=shift;
+ my @oper=('g','k','rg');
+
+ @oper=('G','K','RG') if $upper;
+
+ if ($mcmd eq 'd')
+ {
+ # default colour
+ return("0 $oper[0]");
+ }
+
+ my (@c)=split(' ',$par);
+
+ if ($mcmd eq 'c')
+ {
+ # Text CMY
+ return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535)." 0 $oper[1]");
+ }
+ elsif ($mcmd eq 'k')
+ {
+ # Text CMYK
+ return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535).' '.d3($c[3]/65535)." $oper[1]");
+ }
+ elsif ($mcmd eq 'g')
+ {
+ # Text Grey
+ return(d3($c[0]/65535)." $oper[0]");
+ }
+ elsif ($mcmd eq 'r')
+ {
+ # Text RGB0
+ return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535)." $oper[2]");
+ }
+}
+
+sub do_D
+{
+ my $par=shift;
+ my $Dcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+
+ IsGraphic();
+
+ if ($Dcmd eq 'F')
+ {
+ my $mcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $par=~s/^ +//;
+
+ $fillcol=set_col($mcmd,$par,0);
+ $stream.="$fillcol\n";
+ $curfill=$fillcol;
+ }
+ elsif ($Dcmd eq 'f')
+ {
+ my $mcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $par=~s/^ +//;
+ ($par)=split(' ',$par);
+
+ if ($par >= 0 and $par <= 1000)
+ {
+ $fillcol=set_col('g',int((1000-$par)*65535/1000),0);
+ }
+ else
+ {
+ $fillcol=lc($textcol);
+ }
+
+ $stream.="$fillcol\n";
+ $curfill=$fillcol;
+ }
+ elsif ($Dcmd eq '~')
+ {
+ # B-Spline
+ my (@p)=split(' ',$par);
+ my ($nxpos,$nypos);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ $stream.=PutXY($xpos,$ypos)." m\n";
+ $xpos+=($p[0]/2);
+ $ypos+=($p[1]/2);
+ $stream.=PutXY($xpos,$ypos)." l\n";
+
+ for (my $i=0; $i < $#p-1; $i+=2)
+ {
+ $nxpos=(($p[$i]*$tnum)/(2*$tden));
+ $nypos=(($p[$i+1]*$tnum)/(2*$tden));
+ $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." ";
+ $nxpos=($p[$i]/2 + ($p[$i+2]*($tden-$tnum))/(2*$tden));
+ $nypos=($p[$i+1]/2 + ($p[$i+3]*($tden-$tnum))/(2*$tden));
+ $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." ";
+ $nxpos=(($p[$i]-$p[$i]/2) + $p[$i+2]/2);
+ $nypos=(($p[$i+1]-$p[$i+1]/2) + $p[$i+3]/2);
+ $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." c\n";
+ $xpos+=$nxpos;
+ $ypos+=$nypos;
+ }
+
+ $xpos+=($p[$#p-1]-$p[$#p-1]/2);
+ $ypos+=($p[$#p]-$p[$#p]/2);
+ $stream.=PutXY($xpos,$ypos)." l\nS\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'p' or $Dcmd eq 'P')
+ {
+ # Polygon
+ my (@p)=split(' ',$par);
+ my ($nxpos,$nypos);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ $stream.=PutXY($xpos,$ypos)." m\n";
+
+ for (my $i=0; $i < $#p; $i+=2)
+ {
+ $xpos+=($p[$i]);
+ $ypos+=($p[$i+1]);
+ $stream.=PutXY($xpos,$ypos)." l\n";
+ }
+
+ if ($Dcmd eq 'p')
+ {
+ $stream.="s\n";
+ }
+ else
+ {
+ $stream.="f\n";
+ }
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'c')
+ {
+ # Stroke circle
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[0]);
+ $stream.="s\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'C')
+ {
+ # Fill circle
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[0]);
+ $stream.="f\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'e')
+ {
+ # Stroke ellipse
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[1]);
+ $stream.="s\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'E')
+ {
+ # Fill ellipse
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[1]);
+ $stream.="f\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'l')
+ {
+ # Line To
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ $stream.=PutXY($xpos,$ypos)." m\n";
+ $xpos+=$p[0];
+ $ypos+=$p[1];
+ $stream.=PutXY($xpos,$ypos)." l\n";
+
+ $stream.="S\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 't')
+ {
+ # Line Thickness
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ # $xpos+=$p[0]*100; # WTF!!!
+ #int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000;
+ $p[0]=(($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000 if $p[0] < 0;
+ $lwidth=$p[0];
+ $stream.="$p[0] w\n";
+ $poschg=1;
+ $xpos+=$lwidth;
+ }
+ elsif ($Dcmd eq 'a')
+ {
+ # Arc
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+ my $rad180=3.14159;
+ my $rad360=$rad180*2;
+ my $rad90=$rad180/2;
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+
+ # Documentation is wrong. Groff does not use Dh1,Dv1 as centre of the circle!
+
+ my $centre=adjust_arc_centre(\@p);
+
+ # Using formula here : http://www.tinaja.com/glib/bezcirc2.pdf
+ # First calculate angle between start and end point
+
+ my ($startang,$r)=RtoP(-$centre->[0],$centre->[1]);
+ my ($endang,$r2)=RtoP(($p[0]+$p[2])-$centre->[0],-($p[1]+$p[3]-$centre->[1]));
+ $endang+=$rad360 if $endang < $startang;
+ my $totang=($endang-$startang)/4; # do it in 4 pieces
+
+ # Now 1 piece
+
+ my $x0=cos($totang/2);
+ my $y0=sin($totang/2);
+ my $x3=$x0;
+ my $y3=-$y0;
+ my $x1=(4-$x0)/3;
+ my $y1=((1-$x0)*(3-$x0))/(3*$y0);
+ my $x2=$x1;
+ my $y2=-$y1;
+
+ # Rotate to start position and draw 4 pieces
+
+ foreach my $j (0..3)
+ {
+ PlotArcSegment($totang/2+$startang+$j*$totang,$r,$xpos+$centre->[0],GraphY($ypos+$centre->[1]),$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3);
+ }
+
+ $xpos+=$p[0]+$p[2];
+ $ypos+=$p[1]+$p[3];
+
+ $poschg=1;
+ }
+}
+
+sub deg
+{
+ return int($_[0]*180/3.14159);
+}
+
+sub adjust_arc_centre
+{
+ # Taken from geometry.cpp
+
+ # We move the center along a line parallel to the line between
+ # the specified start point and end point so that the center
+ # is equidistant between the start and end point.
+ # It can be proved (using Lagrange multipliers) that this will
+ # give the point nearest to the specified center that is equidistant
+ # between the start and end point.
+
+ my $p=shift;
+ my @c;
+ my $x = $p->[0] + $p->[2]; # (x, y) is the end point
+ my $y = $p->[1] + $p->[3];
+ my $n = $x*$x + $y*$y;
+ if ($n != 0)
+ {
+ $c[0]= $p->[0];
+ $c[1] = $p->[1];
+ my $k = .5 - ($c[0]*$x + $c[1]*$y)/$n;
+ $c[0] += $k*$x;
+ $c[1] += $k*$y;
+ return(\@c);
+ }
+ else
+ {
+ return(undef);
+ }
+}
+
+
+sub PlotArcSegment
+{
+ my ($ang,$r,$transx,$transy,$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3)=@_;
+ my $cos=cos($ang);
+ my $sin=sin($ang);
+ my @mat=($cos,$sin,-$sin,$cos,0,0);
+ my $lw=$lwidth/$r;
+
+ $stream.="q $r 0 0 $r $transx $transy cm ".join(' ',@mat)." cm $lw w $x0 $y0 m $x1 $y1 $x2 $y2 $x3 $y3 c S Q\n";
+}
+
+sub DrawCircle
+{
+ my $hd=shift;
+ my $vd=shift;
+ my $hr=$hd/2/$unitwidth;
+ my $vr=$vd/2/$unitwidth;
+ my $kappa=0.5522847498;
+ $hd/=$unitwidth;
+ $vd/=$unitwidth;
+
+
+ $stream.=PutXY(($xpos+$hd),$ypos)." m\n";
+ $stream.=PutXY(($xpos+$hd),($ypos+$vr*$kappa))." ".PutXY(($xpos+$hr+$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos+$hr),($ypos+$vr))." c\n";
+ $stream.=PutXY(($xpos+$hr-$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos),($ypos+$vr*$kappa))." ".PutXY(($xpos),($ypos))." c\n";
+ $stream.=PutXY(($xpos),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hr-$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hr),($ypos-$vr))." c\n";
+ $stream.=PutXY(($xpos+$hr+$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hd),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hd),($ypos))." c\n";
+ $xpos+=$hd;
+
+ $poschg=1;
+}
+
+sub FindCircle
+{
+ my ($x1,$y1,$x2,$y2,$x3,$y3)=@_;
+ my ($Xo, $Yo);
+
+ my $x=$x2+$x3;
+ my $y=$y2+$y3;
+ my $n=$x**2+$y**2;
+
+ if ($n)
+ {
+ my $k=.5-($x2*$x + $y2*$y)/$n;
+ return(sqrt($n),$x2+$k*$x,$y2+$k*$y);
+ }
+ else
+ {
+ return(-1);
+ }
+
+}
+
+sub PtoR
+{
+ my ($theta,$r)=@_;
+
+ return($r*cos($theta),$r*sin($theta));
+}
+
+sub RtoP
+{
+ my ($x,$y)=@_;
+
+ return(atan2($y,$x),sqrt($x**2+$y**2));
+}
+
+sub PutLine
+{
+
+ my $f=shift;
+
+ IsText() if !defined($f);
+
+ return if (scalar(@lin) == 0) or (!defined($lin[0]->[0]) and $#lin == 0);
+
+# $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug;
+ $pendmv-=$nomove;
+ $lin[$#lin]->[1]=-$pendmv/$cftsz if ($pendmv != 0);
+
+ foreach my $wd (@lin)
+ {
+ next if !defined($wd->[0]);
+ $wd->[0]=~s/\\/\\\\/g;
+ $wd->[0]=~s/\(/\\(/g;
+ $wd->[0]=~s/\)/\\)/g;
+ $wd->[0]=~s/!\|!\|/\\/g;
+ $wd->[1]=d3($wd->[1]);
+ }
+
+ if (0)
+ {
+ if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0))
+ {
+ $stream.="($lin[0]->[0]) Tj\n";
+ }
+ else
+ {
+ $stream.="[";
+
+ foreach my $wd (@lin)
+ {
+ $stream.="($wd->[0]) " if defined($wd->[0]);
+ $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
+ }
+
+ $stream.="] TJ\n";
+ }
+ }
+ else
+ {
+ if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0))
+ {
+ $stream.="0 Tw ($lin[0]->[0]) Tj\n";
+ }
+ else
+ {
+ if ($wt>=-1 or $#lin == 0 or $lin[0]->[1]>=0)
+ {
+ $stream.="0 Tw [";
+
+ foreach my $wd (@lin)
+ {
+ $stream.="($wd->[0]) " if defined($wd->[0]);
+ $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
+ }
+
+ $stream.="] TJ\n";
+ }
+ else
+ {
+ # $stream.="\%dg 0 Tw [";
+ #
+ # foreach my $wd (@lin)
+ # {
+ # $stream.="($wd->[0]) " if defined($wd->[0]);
+ # $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
+ # }
+ #
+ # $stream.="] TJ\n";
+ #
+ # my $wt=$lin[0]->[1]||0;
+
+ # while ($wt < -$whtsz/$cftsz)
+ # {
+ # $wt+=$whtsz/$cftsz;
+ # }
+
+ $stream.=sprintf( "%.3f Tw ",-($whtsz+$wt*$cftsz)/$unitwidth-$curkern );
+ if (!defined($lin[0]->[0]) and defined($lin[0]->[1]))
+ {
+ $stream.="[ $lin[0]->[1] (";
+ shift @lin;
+ }
+ else
+ {
+ $stream.="[(";
+ }
+
+ foreach my $wd (@lin)
+ {
+ my $wwt=$wd->[1]||0;
+
+ while ($wwt <= $wt+.1)
+ {
+ $wwt-=$wt;
+ $wd->[0].=' ';
+ }
+
+ if (abs($wwt) < .1 or $wwt == 0)
+ {
+ $stream.="$wd->[0]" if defined($wd->[0]);
+ }
+ else
+ {
+ $wwt=sprintf("%.3f",$wwt);
+ $stream.="$wd->[0]) $wwt (" if defined($wd->[0]);
+ }
+ }
+ $stream.=")] TJ\n";
+ }
+ }
+ }
+
+ @lin=();
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+ $nomove=0;
+ $wt=-1;
+}
+
+sub d3
+{
+ return(sprintf("%.3f",shift || 0));
+}
+
+sub LoadAhead
+{
+ my $no=shift;
+
+ foreach my $j (1..$no)
+ {
+ my $lin=<>;
+ chomp($lin);
+ $lin=~s/\r$//;
+ $lct++;
+
+ push(@ahead,$lin);
+ $stream.="%% $lin\n" if $debug;
+ }
+}
+
+sub do_V
+{
+ my $par=shift;
+
+ if ($mode eq 't')
+ {
+ PutLine();
+ }
+ else
+ {
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+ }
+
+ $ypos=$par/$unitwidth;
+
+ LoadAhead(1);
+
+ if (substr($ahead[0],0,1) eq 'H')
+ {
+ $xpos=substr($ahead[0],1)/$unitwidth;
+
+ $nomove=$pendmv=0;
+ @ahead=();
+
+ }
+
+ $poschg=1;
+}
+
+sub do_v
+{
+ my $par=shift;
+
+ PutLine() if $mode eq 't';
+
+ $ypos+=$par/$unitwidth;
+
+ $poschg=1;
+}
+
+sub TextWid
+{
+ my $txt=shift;
+ my $fnt=shift;
+ my $w=0;
+ my $ck=0;
+
+ foreach my $c (split('',$txt))
+ {
+ my $cn=ord($c);
+ $widtbl->[$cn]=$origwidtbl->[$cn]*$cftsz if !defined($widtbl->[$cn]);
+ $w+=$widtbl->[$cn];
+ }
+
+ $ck=length($txt)*$curkern;
+
+ return(($w/$unitwidth)+$ck);
+}
+
+sub do_t
+{
+ my $par=shift;
+ my $fnt=$fontlst{$cft}->{FNT};
+
+ if ($kernadjust != $curkern)
+ {
+ PutLine();
+ $stream.="$kernadjust Tc\n";
+ $curkern=$kernadjust;
+ }
+
+ my $par2=$par;
+ $par2=~s/^!\|!\|(\d\d\d)/chr(oct($1))/e;
+
+ foreach my $j (0..length($par2)-1)
+ {
+ my $cn=ord(substr($par2,$j,1));
+ my $chnm=$fnt->{NAM}->{$fnt->{NO}->[$cn]->[1]};
+
+ if ($chnm->[USED]==0)
+ {
+ $chnm->[USED]=1;
+ }
+ elsif ($fnt->{NO}->[$cn]->[0] ne $fnt->{NO}->[$cn]->[1])
+ {
+ # A glyph has already been remapped to this char, so find a spare
+
+ my $cn2=RemapChr($cn,$fnt,$fnt->{NO}->[$cn]->[0]);
+ $stream.="% MMM Remap $cn to $cn2\n" if $debug;
+
+ if ($cn2)
+ {
+ substr($par2,$j,1)=chr($cn2);
+
+ if ($par=~m/^!\|!\|(\d\d\d)/)
+ {
+ substr($par,4,3)=sprintf("%03o",$cn2);
+ }
+ else
+ {
+ substr($par,$j,1)=chr($cn2);
+ }
+ }
+ }
+ }
+ my $wid=TextWid($par2,$fnt);
+
+ $par=reverse(split('',$par)) if $xrev and $par!~m/^!\|!\|(\d\d\d)/;
+
+ if ($n_flg and defined($mark))
+ {
+ $mark->{ypos}=$ypos;
+ $mark->{xpos}=$xpos;
+ }
+
+ $n_flg=0;
+ IsText();
+
+ $xpos+=$wid;
+ $xpos+=($pendmv-$nomove)/$unitwidth;
+
+ $stream.="% == '$par'=$wid 'xpos=$xpos\n" if $debug;
+
+ # $pendmv = 'h' move since last 't'
+ # $nomove = width of char(s) added by 'C', 'N' or 'c'
+ # $w-flg = 'w' seen since last t
+
+ if ($fontchg)
+ {
+ PutLine();
+ $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
+ $stream.="/F$cft $cftsz Tf\n", $fontchg=0 if $fontchg && defined($cft);
+ }
+
+ $gotT=1;
+
+ $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug;
+
+# if ($w_flg && $#lin > -1)
+# {
+# $lin[$#lin]->[0].=' ';
+# $pendmv-=$whtsz;
+# $dontglue=1 if $pendmv==0;
+# }
+
+ $wt=-$pendmv/$cftsz if $w_flg and $wt==-1;
+ $pendmv-=$nomove;
+ $nomove=0;
+ $w_flg=0;
+
+ if ($xrev)
+ {
+ PutLine(0) if $#lin > -1;
+ MakeMatrix(1);
+ $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
+ $stream.="$curkern Tc\n";
+ $stream.="0 Tw ";
+ $stream.="($par) Tj\n";
+ MakeMatrix();
+ $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
+ $matrixchg=0;
+ $stream.="$curkern Tc\n";
+ return;
+ }
+
+ if ($pendmv)
+ {
+ if ($#lin == -1)
+ {
+ push(@lin,[undef,-$pendmv/$cftsz]);
+ }
+ else
+ {
+ $lin[$#lin]->[1]=-$pendmv/$cftsz;
+ }
+
+ push(@lin,[$par,undef]);
+# $xpos+=$pendmv/$unitwidth;
+ $pendmv=0
+ }
+ else
+ {
+ if ($#lin == -1)
+ {
+ push(@lin,[$par,undef]);
+ }
+ else
+ {
+ $lin[$#lin]->[0].=$par;
+ }
+ }
+}
+
+sub do_u
+{
+ my $par=shift;
+
+ $par=m/([+-]?\d+) (.*)/;
+ $kernadjust=$1/$unitwidth;
+ do_t($2);
+ $kernadjust=0;
+}
+
+sub do_h
+{
+ $pendmv+=shift;
+}
+
+sub do_H
+{
+ my $par=shift;
+
+ if ($mode eq 't')
+ {
+ PutLine();
+ }
+ else
+ {
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+ }
+
+ my $newx=$par/$unitwidth;
+ $stream.=sprintf("%.3f",$newx-$tmxpos)." 0 Td\n" if $mode eq 't';
+ $tmxpos=$xpos=$newx;
+ $pendmv=$nomove=0;
+}
+
+sub do_C
+{
+ my $par=shift;
+
+ my ($par2,$nm)=FindChar($par);
+
+ do_t($par2);
+ $nomove=$fontlst{$cft}->{FNT}->{NAM}->{$par}->[WIDTH]*$cftsz ;
+}
+
+sub FindChar
+{
+ my $chnm=shift;
+ my $fnt=$fontlst{$cft}->{FNT};
+
+ if (exists($fnt->{NAM}->{$chnm}))
+ {
+ my $ch=$fnt->{NAM}->{$chnm}->[ASSIGNED];
+ $ch=RemapChr($ch,$fnt,$chnm) if ($ch > 255);
+ $fnt->{NAM}->{$chnm}->[USED]=0 if $fnt->{NO}->[$ch]->[1] eq $chnm;
+
+ return(($ch<32)?sprintf("!|!|%03o",$ch):chr($ch),$widtbl->[$ch]);
+ }
+ else
+ {
+ return(' ');
+ }
+}
+
+sub RemapChr
+{
+ my $ch=shift;
+ my $fnt=shift;
+ my $chnm=shift;
+ my $unused=0;
+
+ foreach my $un (0..$#{$fnt->{NO}})
+ {
+ next if $un >= 139 and $un <= 144;
+ $unused=$un,last if $fnt->{NO}->[$un]->[1] eq '';
+ }
+
+ if (!$unused)
+ {
+ foreach my $un (128..255)
+ {
+ next if $un >= 139 and $un <= 144;
+ my $glyph=$fnt->{NO}->[$un]->[1];
+ $unused=$un,last if $fnt->{NAM}->{$glyph}->[USED] == 0;
+ }
+ }
+
+ if ($unused && $unused <= 255)
+ {
+ my $glyph=$fnt->{NO}->[$unused]->[1];
+ delete($fontlst{$cft}->{CACHE}->{$cftsz});
+ $fnt->{NAM}->{$chnm}->[ASSIGNED]=$unused;
+ $fnt->{NAM}->{$chnm}->[USED]=1;
+ $fnt->{NO}->[$unused]->[1]=$chnm;
+ $widtbl=CacheWid($cft);
+
+ $stream.="% AAA Assign $chnm ($ch) to $unused\n" if $debug;
+
+ $ch=$unused;
+ return($ch);
+ }
+ else
+ {
+ Warn("too many glyphs used in font '$cft'");
+ return(32);
+ }
+}
+
+sub do_c
+{
+ my $par=shift;
+
+ push(@ahead,substr($par,1));
+ $par=substr($par,0,1);
+ my $ch=ord($par);
+ do_N($ch);
+}
+
+sub do_N
+{
+ my $par=shift;
+ my $fnt=$fontlst{$cft}->{FNT};
+
+ if (!defined($fnt->{NO}->[$par]))
+ {
+ Warn("no chr($par) in font $fnt->{internalname}");
+ return;
+ }
+
+ my $chnm=$fnt->{NO}->[$par]->[0];
+ do_C($chnm);
+}
+
+sub do_n
+{
+ $gotT=0;
+ PutLine(0);
+ $pendmv=$nomove=0;
+ $n_flg=1;
+ @lin=();
+ PutHotSpot($xpos) if defined($mark);
+}
+
+1;
+
+# Local Variables:
+# fill-column: 72
+# mode: CPerl
+# End:
+# vim: set cindent noexpandtab shiftwidth=4 softtabstop=4 textwidth=72:
diff --git a/src/devices/gropdf/pdfmom.1.man b/src/devices/gropdf/pdfmom.1.man
new file mode 100644
index 0000000..08d789c
--- /dev/null
+++ b/src/devices/gropdf/pdfmom.1.man
@@ -0,0 +1,229 @@
+.TH pdfmom @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+pdfmom \- produce PDF documents using the
+.I mom
+macro package for
+.I groff
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2012-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_pdfmom_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY pdfmom
+.RB [ \-Tpdf ]
+.RI [ groff-options ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY pdfmom
+.B \-Tps
+.RI [ pdfroff-options ]
+.RI [ groff-options ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY pdfmom
+.B \-v
+.
+.SY pdfmom
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I pdfmom
+is a wrapper around
+.MR groff @MAN1EXT@
+that facilitates the production of PDF documents from files
+formatted with the
+.I mom
+macros.
+.
+.
+.P
+.I pdfmom
+prints to the standard output,
+so output must usually be redirected to a destination file.
+.
+The size of the final PDF can be reduced by piping the output
+through
+.MR ps2pdf 1 .
+.
+.
+.P
+If called with the
+.B \-Tpdf
+option (which is the default),
+.I pdfmom
+processes files using
+.IR groff 's
+native PDF driver,
+.MR gropdf @MAN1EXT@ .
+.
+If
+.B \-Tps
+is given,
+processing is passed over to
+.IR pdfroff ,
+which uses
+.IR groff 's
+PostScript driver.
+.
+In either case,
+multiple runs of the source file are performed in order to satisfy any
+forward references in the document.
+.
+.
+.P
+.I pdfmom
+accepts all the same options as
+.IR groff .
+.
+If
+.B \-Tps
+is given,
+the options associated with
+.I pdfroff
+are accepted as well.
+.
+When
+.I pdfmom
+calls
+.IR pdfroff ,
+the options
+.RB \[lq] "\-mpdfmark \-mom \-\-no\-toc" \[rq]
+options are implied and should not be given on the command line.
+.
+Equally,
+it is not necessary to supply the
+.B \-mom
+or
+.B "\-m\~mom"
+options when
+.B \-Tps
+is absent.
+.
+.
+.P
+PDF integration with the
+.I mom
+macros is discussed in full in the manual
+\[lq]Producing PDFs with
+.I groff
+and
+.IR mom \[rq],
+which was itself produced with
+.IR pdfmom .
+.
+.
+.P
+If called with the
+.B \-v
+or
+.B \-\-version
+options,
+.I pdfmom
+displays its version information and exits.
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I pdfmom
+was written by
+.MT deri@\:chuzzlewit\:.myzen\:.co\:.uk
+Deri James
+.ME
+and
+.MT peter@\:schaffter\:.ca
+Peter Schaffter
+.ME ,
+and is maintained by James.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.TP
+.I @PDFDOCDIR@/\:mom\-pdf.pdf
+\[lq]Producing PDFs with
+.I groff
+and
+.IR mom \[rq],
+by Deri James and Peter Schaffter.
+.
+This file,
+together with its source,
+.IR mom\-pdf.mom ,
+is part of the
+.I groff
+distribution.
+.
+.
+.P
+.MR groff @MAN1EXT@ ,
+.MR gropdf @MAN1EXT@ ,
+.MR pdfroff @MAN1EXT@ ,
+.MR ps2pdf 1
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_pdfmom_1_man_C]
+.do rr *groff_pdfmom_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/gropdf/pdfmom.pl b/src/devices/gropdf/pdfmom.pl
new file mode 100644
index 0000000..89977d4
--- /dev/null
+++ b/src/devices/gropdf/pdfmom.pl
@@ -0,0 +1,150 @@
+#!@PERL@
+#
+# pdfmom : Frontend to run groff -mom to produce PDFs
+# Deri James : Friday 16 Mar 2012
+#
+
+# Copyright (C) 2012-2020 Free Software Foundation, Inc.
+# Written by Deri James <deri@chuzzlewit.myzen.co.uk>
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use File::Temp qw/tempfile/;
+my @cmd;
+my $dev='pdf';
+my $preconv='';
+my $readstdin=1;
+my $RT_SEP='@RT_SEP@';
+
+$ENV{PATH}=$ENV{GROFF_BIN_PATH}.$RT_SEP.$ENV{PATH} if exists($ENV{GROFF_BIN_PATH});
+$ENV{TMPDIR}=$ENV{GROFF_TMPDIR} if exists($ENV{GROFF_TMPDIR});
+
+while (my $c=shift)
+{
+ $c=~s/(?<!\\)"/\\"/g;
+
+ if (substr($c,0,2) eq '-T')
+ {
+ if (length($c) > 2)
+ {
+ $dev=substr($c,2);
+ }
+ else
+ {
+ $dev=shift;
+ }
+ next;
+ }
+ elsif (substr($c,0,2) eq '-K')
+ {
+ if (length($c) > 2)
+ {
+ $preconv=$c;
+ }
+ else
+ {
+ $preconv=$c;
+ $preconv.=shift;
+ }
+ next;
+ }
+ elsif (substr($c,0,2) eq '-k')
+ {
+ $preconv=$c;
+ next;
+ }
+ elsif ($c eq '-z' or $c eq '-Z')
+ {
+ $dev=$c;
+ next;
+ }
+ elsif ($c eq '-v' or $c eq '--version')
+ {
+ print "GNU pdfmom (groff) version @VERSION@\n";
+ exit;
+ }
+ elsif (substr($c,0,1) eq '-')
+ {
+ if (length($c) > 1)
+ {
+ push(@cmd,"\"$c\"");
+ push(@cmd,"'".(shift)."'") if length($c)==2 and index('dDfFIKLmMnoPrwW',substr($c,-1)) >= 0;
+ }
+ else
+ {
+ # Just a '-'
+
+ push(@cmd,$c);
+ $readstdin=2;
+ }
+ }
+ else
+ {
+ # Got a filename?
+
+ push(@cmd,"\"$c\"");
+ $readstdin=0 if $readstdin == 1;
+
+ }
+
+}
+
+my $cmdstring=' '.join(' ',@cmd).' ';
+
+if ($readstdin)
+{
+ my ($fh,$tmpfn)=tempfile('pdfmom-XXXXX', UNLINK=>1);
+
+ while (<STDIN>)
+ {
+ print $fh ($_);
+ }
+
+ close($fh);
+
+ $cmdstring=~s/ - / $tmpfn / if $readstdin == 2;
+ $cmdstring.=" $tmpfn " if $readstdin == 1;
+}
+
+if ($dev eq 'pdf')
+{
+ system("groff -Tpdf -dLABEL.REFS=1 -mom -z $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | groff -Tpdf -dPDF.EXPORT=1 -dLABEL.REFS=1 -mom -z - $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | groff -Tpdf -mom $preconv - $cmdstring");
+}
+elsif ($dev eq 'ps')
+{
+ system("groff -Tpdf -dLABEL.REFS=1 -mom -z $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | pdfroff -mpdfmark -mom --no-toc - $preconv $cmdstring");
+}
+elsif ($dev eq '-z') # pseudo dev - just compile for warnings
+{
+ system("groff -Tpdf -mom -z $cmdstring");
+}
+elsif ($dev eq '-Z') # pseudo dev - produce troff output
+{
+ system("groff -Tpdf -mom -Z $cmdstring");
+}
+else
+{
+ print STDERR "Not compatible with device '-T $dev'\n";
+ exit 1;
+}
+
+# Local Variables:
+# fill-column: 72
+# mode: CPerl
+# End:
+# vim: set cindent noexpandtab shiftwidth=2 softtabstop=2 textwidth=72:
diff --git a/src/devices/grops/TODO b/src/devices/grops/TODO
new file mode 100644
index 0000000..eab8f83
--- /dev/null
+++ b/src/devices/grops/TODO
@@ -0,0 +1,24 @@
+Read PFB files directly.
+
+Generate %%For comment.
+
+Generate %%Title comment.
+
+Angles in arc command: don't generate more digits after the decimal
+point than are necessary.
+
+Possibly generate BoundingBox comment.
+
+Per font composite character mechanism (sufficient for fractions).
+
+Consider whether we ought to do rounding of graphical objects other
+than lines. What's the point?
+
+Error messages should refer to output page number.
+
+Search for downloadable fonts using their PostScript names if not
+found in download file.
+
+Separate path for searching for downloadable font files.
+
+Clip to the BoundingBox when importing documents.
diff --git a/src/devices/grops/grops.1.man b/src/devices/grops/grops.1.man
new file mode 100644
index 0000000..d0ec21d
--- /dev/null
+++ b/src/devices/grops/grops.1.man
@@ -0,0 +1,1831 @@
+.TH grops @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grops \-
+.I groff
+output driver for PostScript
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2018, 2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grops_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'ps' .ft \\$1
+. if '\\*(.T'pdf' .ft \\$1
+..
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grops
+.RB [ \-glm ]
+.RB [ \-b\~\c
+.IR brokenness-flags ]
+.RB [ \-c\~\c
+.IR num-copies ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-P\~\c
+.IR prologue-file ]
+.RB [ \-w\~\c
+.IR rule-thickness ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grops
+.B \-\-help
+.YS
+.
+.
+.SY grops
+.B \-v
+.
+.SY grops
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+PostScript output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into PostScript.
+.
+Normally,
+.I grops
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~ps \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grops .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grotty
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+When called with multiple
+.I file
+arguments,
+.I grops
+doesn't produce a valid document structure
+(one conforming to the Document Structuring Conventions).
+.
+To print such concatenated output,
+it is necessary to deactivate DSC handling in the printing program or
+previewer.
+.
+.
+.P
+See section \[lq]Font installation\[rq] below for a guide to installing
+fonts for
+.IR grops .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-b\~ n
+Work around problems with spoolers,
+previewers,
+and older printers.
+.
+Normally,
+.I grops
+produces output at PostScript \%LanguageLevel\~2 that conforms to
+version 3.0 of the Document Structuring Conventions.
+.
+Some software and devices can't handle such a data stream.
+.
+The value
+.RI of\~ n
+determines what
+.I grops
+does to make its output acceptable to such consumers.
+.
+If
+.I n
+is
+.BR 0 ,
+.I grops
+employs no workarounds,
+which is the default;
+it can be changed by modifying the
+.B broken
+directive in
+.IR grops 's
+.I DESC
+file.
+.
+.
+.IP
+Add\~1 to suppress generation of
+.B %%Begin\%Document\%Setup
+and
+.B %%End\%Document\%Setup
+comments;
+this is needed for early versions of TranScript that get confused by
+anything between the
+.B %%End\%Prolog
+comment and the first
+.B %%Page
+comment.
+.
+.
+.IP
+Add\~2 to omit lines in included files beginning with
+.BR %!\& ,
+which confuse Sun's
+.I pageview
+previewer.
+.
+.
+.IP
+Add\~4 to omit lines in included files beginning with
+.BR %%Page ,
+.B %%Trailer
+and
+.BR %%End\%Prolog ;
+this is needed for spoolers that don't understand
+.B %%Begin\%Document
+and
+.B %%End\%Document
+comments.
+.
+.
+.IP
+Add\~8 to write
+.B %!PS\-Adobe\-2.0
+rather than
+.B %!PS\-Adobe\-3.0
+as the first line of the PostScript output;
+this is needed when using Sun's Newsprint with a printer that requires
+page reversal.
+.
+.
+.IP
+Add\~16 to omit media size information
+(that is,
+output neither a
+.B %%Document\%Media
+comment nor the
+.B setpagedevice
+PostScript command).
+.
+This was the behavior of
+.I groff
+1.18.1 and earlier;
+it is
+needed for older printers that don't understand PostScript
+\%LanguageLevel\~2,
+and is also necessary if the output is further processed to produce an
+EPS file;
+see subsection \[lq]Escapsulated PostScript\[rq] below.
+.
+.
+.TP
+.BI \-c\~ n
+Output
+.I n
+copies of each page.
+.
+.
+.TP
+.BI \-F\~ dir
+Prepend directory
+.RI dir /dev name
+to the search path for
+font and device description and PostScript prologue files;
+.I name
+is the name of the device,
+usually
+.BR ps .
+.
+.
+.TP
+.B \-g
+Generate PostScript code to guess the page length.
+.
+The guess is correct only if the imageable area is vertically centered
+on the page.
+.
+This option allows you to generate documents that can be printed on both
+U.S.\& letter and A4 paper formats without change.
+.
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+for files named in
+.B \[rs]X\[aq]ps: file\[aq]
+and
+.B \[rs]X\[aq]ps: import\[aq]
+escape sequences.
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.
+.TP
+.B \-l
+Use landscape orientation rather than portrait.
+.
+.
+.TP
+.B \-m
+Turn on manual feed for the document.
+.
+.
+.TP
+.BI \-p\~ fmt
+Set physical dimensions of output medium,
+overriding the
+.BR \%papersize ,
+.BR \%paperlength ,
+and
+.B \%paperwidth
+directives in the
+.I DESC
+file.
+.
+.I fmt
+can be any argument accepted by the
+.B \%papersize
+directive;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI \-P\~ prologue
+Use the file
+.IR prologue ,
+sought in the
+.I groff
+font search path,
+as the PostScript prologue,
+overriding the default
+(see section \[lq]Files\[rq] below)
+and the environment variable
+.I GROPS_PROLOGUE.
+.
+.
+.TP
+.BI \-w\~ n
+Draw rules (lines) with a thickness of
+.IR n \~thousandths
+of an em.
+.
+The default thickness is
+.B 40
+(0.04\~em).
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+The input to
+.I grops
+must be in the format output by
+.MR @g@troff @MAN1EXT@ ,
+described in
+.MR groff_out @MAN5EXT@ .
+.
+In addition,
+the device and font description files for the device used must meet
+certain requirements.
+.
+The device resolution must be an integer multiple of\~72 times the
+.BR sizescale .
+.
+The device description file must contain a valid paper format;
+see
+.MR groff_font @MAN5EXT@ .
+.
+Each font description file must contain a directive
+.
+.RS
+.EX
+.RI internalname\~ psname
+.EE
+.RE
+.
+which says that the PostScript name of the font is
+.IR psname .
+.
+.
+.P
+A font description file may also contain a directive
+.
+.RS
+.EX
+.RI encoding\~ enc-file
+.EE
+.RE
+.
+which says that
+the PostScript font should be reencoded using the encoding described in
+.IR enc-file ;
+this file should consist of a sequence of lines of the form
+.
+.
+.RS
+.EX
+.I pschar code
+.EE
+.RE
+.
+where
+.I pschar
+is the PostScript name of the character,
+and
+.I code
+is its position in the encoding expressed as a decimal integer;
+valid values are in the range 0 to\~255.
+.
+Lines starting with
+.B #
+and blank lines are ignored.
+.
+The code for each character given in the font description file must
+correspond to the code for the character in encoding file,
+or to the code in the default encoding for the font if the PostScript
+font is not to be reencoded.
+.
+This code can be used with the
+.B \[rs]N
+escape sequence in
+.I @g@troff
+to select the character,
+even if it does not have a
+.I groff
+glyph name.
+.
+Every character in the font description file must exist in the
+PostScript font,
+and the widths given in the font description file must match the widths
+used in the PostScript font.
+.
+.I grops
+assumes that a character with a
+.I groff
+name of
+.B space
+is blank
+(makes no marks on the page);
+it can make use of such a character to generate more efficient and
+compact PostScript output.
+.
+.
+.P
+.I grops
+is able to display all glyphs in a PostScript font;
+it is not limited to 256 of them.
+.
+.I enc-file
+(or the default encoding if no encoding file is specified)
+just defines the
+order of glyphs for the first 256 characters;
+all other glyphs are accessed with additional encoding vectors which
+.I grops
+produces on the fly.
+.
+.
+.P
+.I grops
+can embed fonts in a document that are necessary to render it;
+this is called \[lq]downloading\[rq].
+.
+Such fonts must be in PFA format.
+.
+Use
+.MR pfbtops @MAN1EXT@
+to convert a Type\~1 font in PFB format.
+.
+Downloadable fonts must be listed a
+.I download
+file containing lines of the form
+.
+.RS
+.EX
+.I psname file
+.EE
+.RE
+.
+where
+.I psname
+is the PostScript name of the font,
+and
+.I file
+is the name of the file containing it;
+lines beginning with
+.B #
+and blank lines are ignored;
+fields may be separated by tabs or spaces.
+.
+.I file
+is sought using the same mechanism as that for
+.I groff
+font description files.
+.
+The
+.I download
+file itself is also sought using this mechanism;
+currently,
+only the first matching file found in the device and font description
+search path is used.
+.
+.
+.P
+If the file containing a downloadable font or imported document
+conforms to the Adobe Document Structuring Conventions,
+then
+.I grops
+interprets any comments in the files sufficiently to ensure that its
+own output is conforming.
+.
+It also supplies any needed font resources that are listed in the
+.I download
+file
+as well as any needed file resources.
+.
+It is also able to handle inter-resource dependencies.
+.
+For example,
+suppose that you have a downloadable font called Garamond,
+and also a downloadable font called Garamond-Outline which depends on
+Garamond
+(typically it would be defined to copy Garamond's font dictionary,
+and change the PaintType),
+then it is necessary for Garamond to appear before Garamond-Outline in
+the PostScript document.
+.
+.I grops
+handles this automatically provided that the downloadable font file
+for Garamond-Outline indicates its dependence on Garamond by means of
+the Document Structuring Conventions,
+for example by beginning with the following lines.
+.
+.RS
+.EX
+%!PS\-Adobe\-3.0 Resource\-Font
+%%DocumentNeededResources: font Garamond
+%%EndComments
+%%IncludeResource: font Garamond
+.EE
+.RE
+.
+In this case,
+both Garamond and Garamond-Outline would need to be listed
+in the
+.I download
+file.
+.
+A downloadable font should not include its own name in a
+.B %%Document\%Supplied\%Resources
+comment.
+.
+.
+.P
+.I grops
+does not interpret
+.B %%Document\%Fonts
+comments.
+.
+The
+.BR %%Document\%Needed\%Resources ,
+.BR %%Document\%Supplied\%Resources ,
+.BR %%Include\%Resource ,
+.BR %%Begin\%Resource ,
+and
+.B %%End\%Resource
+comments
+(or possibly the old
+.BR %%Document\%Needed\%Fonts ,
+.BR %%Document\%Supplied\%Fonts ,
+.BR %%Include\%Font ,
+.BR %%Begin\%Font ,
+and
+.B %%End\%Font
+comments)
+should be used.
+.
+.
+.P
+The default stroke and fill color is black.
+.
+For colors defined in the \[lq]rgb\[rq] color space,
+.B setrgbcolor
+is used;
+for \[lq]cmy\[rq] and \[lq]cmyk\[rq],
+.BR setcmykcolor ;
+and for \[lq]gray\[rq],
+.BR setgray .
+.
+.B setcmykcolor
+is a PostScript \%LanguageLevel\~2 command and thus not available on
+some older printers.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.P
+Styles called
+.BR R ,
+.BR I ,
+.BR B ,
+and
+.B BI
+mounted at font positions 1 to\~4.
+.
+Text fonts are grouped into families
+.BR A ,
+.BR BM ,
+.BR C ,
+.BR H ,
+.BR HN ,
+.BR N ,
+.BR P ,
+.RB and\~ T ,
+each having members in each of these styles.
+.
+.
+.RS
+.TP
+.B AR
+.FT AR
+AvantGarde-Book
+.FT
+.
+.TQ
+.B AI
+.FT AI
+AvantGarde-BookOblique
+.FT
+.
+.TQ
+.B AB
+.FT AB
+AvantGarde-Demi
+.FT
+.
+.TQ
+.B ABI
+.FT ABI
+AvantGarde-DemiOblique
+.FT
+.
+.TQ
+.B BMR
+.FT BMR
+Bookman-Light
+.FT
+.
+.TQ
+.B BMI
+.FT BMI
+Bookman-LightItalic
+.FT
+.
+.TQ
+.B BMB
+.FT BMB
+Bookman-Demi
+.FT
+.
+.TQ
+.B BMBI
+.FT BMBI
+Bookman-DemiItalic
+.FT
+.
+.TQ
+.B CR
+.FT CR
+Courier
+.FT
+.
+.TQ
+.B CI
+.FT CI
+Courier-Oblique
+.FT
+.
+.TQ
+.B CB
+.FT CB
+Courier-Bold
+.FT
+.
+.TQ
+.B CBI
+.FT CBI
+Courier-BoldOblique
+.FT
+.
+.TQ
+.B HR
+.FT HR
+Helvetica
+.FT
+.
+.TQ
+.B HI
+.FT HI
+Helvetica-Oblique
+.FT
+.
+.TQ
+.B HB
+.FT HB
+Helvetica-Bold
+.FT
+.
+.TQ
+.B HBI
+.FT HBI
+Helvetica-BoldOblique
+.FT
+.
+.TQ
+.B HNR
+.FT HNR
+Helvetica-Narrow
+.FT
+.
+.TQ
+.B HNI
+.FT HNI
+Helvetica-Narrow-Oblique
+.FT
+.
+.TQ
+.B HNB
+.FT HNB
+Helvetica-Narrow-Bold
+.FT
+.
+.TQ
+.B HNBI
+.FT HNBI
+Helvetica-Narrow-BoldOblique
+.FT
+.
+.TQ
+.B NR
+.FT NR
+NewCenturySchlbk-Roman
+.FT
+.
+.TQ
+.B NI
+.FT NI
+NewCenturySchlbk-Italic
+.FT
+.
+.TQ
+.B NB
+.FT NB
+NewCenturySchlbk-Bold
+.FT
+.
+.TQ
+.B NBI
+.FT NBI
+NewCenturySchlbk-BoldItalic
+.FT
+.
+.TQ
+.B PR
+.FT PR
+Palatino-Roman
+.FT
+.
+.TQ
+.B PI
+.FT PI
+Palatino-Italic
+.FT
+.
+.TQ
+.B PB
+.FT PB
+Palatino-Bold
+.FT
+.
+.TQ
+.B PBI
+.FT PBI
+Palatino-BoldItalic
+.FT
+.
+.TQ
+.B TR
+.FT TR
+Times-Roman
+.FT
+.
+.TQ
+.B TI
+.FT TI
+Times-Italic
+.FT
+.
+.TQ
+.B TB
+.FT TB
+Times-Bold
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+Times-BoldItalic
+.FT
+.RE
+.
+.
+.br
+.ne 2v
+.P
+Another text font is not a member of a family.
+.
+.
+.RS
+.TP
+.B ZCMI
+.FT ZCMI
+ZapfChancery-MediumItalic
+.FT
+.RE
+.
+.
+.P
+Special fonts include
+.BR S ,
+the PostScript Symbol font;
+.BR ZD ,
+Zapf Dingbats;
+.B SS
+(slanted symbol),
+which contains oblique forms of lowercase Greek letters derived from
+Symbol;
+.BR EURO ,
+which offers a Euro glyph for use with old devices lacking it;
+and
+.BR ZDR ,
+a reversed version of ZapfDingbats
+(with symbols flipped about the vertical axis).
+.
+Most glyphs in these fonts are unnamed and must be accessed using
+.BR \[rs]N .
+.
+The last three are not standard PostScript fonts,
+but supplied by
+.I groff
+and therefore included in the default
+.I download
+file.
+.
+.
+.\" ====================================================================
+.SS "Device control commands"
+.\" ====================================================================
+.
+.I grops
+recognizes device control commands produced by the
+.B \[rs]X
+escape sequence,
+but interprets only those that begin with a
+.RB \[lq] ps: \[rq]
+tag.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec\~" code \[aq]
+.RS
+Execute the arbitrary PostScript commands
+.IR code .
+.
+The PostScript
+.I \%currentpoint
+is set to the
+.I groff
+drawing position when the
+.B \[rs]X
+escape sequence is interpreted before executing
+.IR code .
+.
+The origin is at the top left corner of the page;
+.IR x \~coordinates
+increase to the right,
+and
+.IR y \~coordinates
+down the page.
+.
+A
+.RB procedure\~ u
+is defined that converts
+.I groff
+basic units to the coordinate system in effect
+(provided the user doesn't change the scale).
+.
+For example,
+.
+.RS
+.EX
+\&.nr x 1i
+\[rs]X\[aq]ps: exec \[rs]nx u 0 rlineto stroke\[aq]
+.EE
+.RE
+.
+draws a horizontal line one inch long.
+.
+.I code
+may make changes to the graphics state,
+but any changes persist only to the end of the page.
+.
+A dictionary containing the definitions specified by the
+.B def
+and
+.B mdef
+commands is on top of the dictionary stack.
+.
+If your code adds definitions to this dictionary,
+you should allocate space for them using
+.RB \[lq] "\[rs]X\[aq]ps: mdef\~"
+.IB n \[aq]\c
+\[rq].
+.
+Any definitions persist only until the end of the page.
+.
+If you use the
+.B \[rs]Y
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+.
+For example,
+.
+.RS
+.EX
+\&.nr x 1i
+\&.de y
+\&ps: exec
+\&\[rs]nx u 0 rlineto
+\&stroke
+\&..
+\&\[rs]Yy
+.EE
+.RE
+.
+is another way to draw a horizontal line one inch long.
+.
+The single backslash before
+.RB \[lq] nx \[rq]\[em]the
+only reason to use a register while defining the macro
+.RB \[lq] y \[rq]\[em]is
+to convert a user-specified dimension
+.RB \[lq] 1i \[rq]
+to
+.I groff
+basic units which are in turn converted to PostScript units with the
+.B u
+procedure.
+.
+.
+.P
+.I grops
+wraps user-specified PostScript code into a dictionary,
+nothing more.
+.
+In particular,
+it doesn't start and end the inserted code with
+.B save
+and
+.BR restore ,
+respectively.
+.
+This must be supplied by the user,
+if necessary.
+.RE
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: file\~" name \[aq]
+This is the same as the
+.B exec
+command except that the PostScript code is read from file
+.IR name .
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: def\~" code \[aq]
+Place a PostScript definition contained in
+.I code
+in the prologue.
+.
+There should be at most one definition per
+.B \[rs]X
+command.
+.
+Long definitions can be split over several
+.B \[rs]X
+commands;
+all the
+.I code
+arguments are simply joined together separated by newlines.
+.
+The definitions are placed in a dictionary which is automatically
+pushed on the dictionary stack when an
+.B exec
+command is executed.
+.
+If you use the
+.B \[rs]Y
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: mdef\~" "n code" \[aq]
+Like
+.BR def ,
+except that
+.I code
+may contain up to
+.IR n \~definitions.
+.
+.I grops
+needs to know how many definitions
+.I code
+contains
+so that it can create an appropriately sized PostScript dictionary
+to contain them.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: import\~" "file llx lly urx ury width\~"\c
+.RI [ height ]\c
+.B \[aq]
+Import a PostScript graphic from
+.IR file .
+.
+The arguments
+.IR llx ,
+.IR lly ,
+.IR urx ,
+and
+.I ury
+give the bounding box of the graphic in the default PostScript
+coordinate system.
+.
+They should all be integers:
+.I llx
+and
+.I lly
+are the
+.I x
+and
+.IR y \~coordinates
+of the lower left corner of the graphic;
+.I urx
+and
+.I ury
+are the
+.I x
+and
+.IR y \~coordinates
+of the upper right corner of the graphic;
+.I width
+and
+.I height
+are integers that give the desired width and height in
+.I groff
+basic units of the graphic.
+.
+.
+.IP
+The graphic is scaled so that it has this width and height
+and translated so that the lower left corner of the graphic is
+located at the position associated with
+.B \[rs]X
+command.
+.
+If the height argument is omitted it is scaled uniformly in the
+.I x
+and
+.IR y \~axes
+so that it has the specified width.
+.
+.
+.IP
+The contents of the
+.B \[rs]X
+command are not interpreted by
+.IR @g@troff ,
+so vertical space for the graphic is not automatically added,
+and the
+.I width
+and
+.I height
+arguments are not allowed to have attached scaling indicators.
+.
+.
+.IP
+If the PostScript file complies with the Adobe Document Structuring
+Conventions and contains a
+.B %%Bounding\%Box
+comment,
+then the bounding box can be automatically extracted from within
+.I groff
+input by using the
+.B psbb
+request.
+.
+.
+.IP
+See
+.MR groff_tmac @MAN5EXT@
+for a description of the
+.B PSPIC
+macro which provides a convenient high-level interface for inclusion of
+PostScript graphics.
+.
+.
+.TP
+.B \[rs]X\[aq]ps: invis\[aq]
+.TQ
+.B \[rs]X\[aq]ps: endinvis\[aq]
+No output is generated for text and drawing commands
+that are bracketed with these
+.B \[rs]X
+commands.
+.
+These commands are intended for use when output from
+.I @g@troff
+is previewed before being processed with
+.IR grops ;
+if the previewer is unable to display certain characters
+or other constructs,
+then other substitute characters or constructs can be used for
+previewing by bracketing them with these
+.B \[rs]X
+commands.
+.
+.
+.RS
+.P
+For example,
+.I \%gxditview
+is not able to display a proper
+.B \[rs][em]
+character because the standard X11 fonts do not provide it;
+this problem can be overcome by executing the following
+request
+.
+.
+.IP
+.EX
+\&.char \[rs][em] \[rs]X\[aq]ps: invis\[aq]\[rs]
+\[rs]Z\[aq]\[rs]v\[aq]-.25m\[aq]\[rs]h\[aq].05m\[aq]\c
+\[rs]D\[aq]l .9m 0\[aq]\[rs]h\[aq].05m\[aq]\[aq]\[rs]
+\[rs]X\[aq]ps: endinvis\[aq]\[rs][em]
+.EE
+.
+.
+.P
+In this case,
+.I \%gxditview
+is unable to display the
+.B \[rs][em]
+character and draws the line,
+whereas
+.I grops
+prints the
+.B \[rs][em]
+character
+and ignores the line
+(this code is already in file
+.IR Xps.tmac ,
+which is loaded if a document intended for
+.I grops
+is previewed with
+.IR \%gxditview ).
+.RE
+.
+.
+.P
+If a PostScript procedure
+.B BPhook
+has been defined via a
+.RB \[lq] "ps: def" \[rq]
+or
+.RB \[lq] "ps: mdef" \[rq]
+device control command,
+it is executed at the beginning of every page
+(before anything is drawn or written by
+.IR groff ).
+.
+For example,
+to underlay the page contents with the word \[lq]DRAFT\[rq] in light
+gray,
+you might use
+.
+.
+.RS
+.P
+.EX
+\&.de XX
+ps: def
+/BPhook
+{ gsave .9 setgray clippath pathbbox exch 2 copy
+ .5 mul exch .5 mul translate atan rotate pop pop
+ /NewCenturySchlbk-Roman findfont 200 scalefont setfont
+ (DRAFT) dup stringwidth pop \-.5 mul \-70 moveto show
+ grestore }
+def
+\&..
+\&.devicem XX
+.EE
+.RE
+.
+.
+.P
+Or,
+to cause lines and polygons to be drawn with square linecaps and mitered
+linejoins instead of the round linecaps and linejoins normally used by
+.IR grops ,
+use
+.
+.RS
+.EX
+\&.de XX
+ps: def
+/BPhook { 2 setlinecap 0 setlinejoin } def
+\&..
+\&.devicem XX
+.EE
+.RE
+.
+(square linecaps,
+as opposed to butt linecaps
+.RB (\[lq] "0 setlinecap" \[rq]),
+give true corners in boxed tables even though the lines are drawn
+unconnected).
+.
+.
+.\" ====================================================================
+.SS "Encapsulated PostScript"
+.\" ====================================================================
+.
+.I grops
+itself doesn't emit bounding box information.
+.
+The following script,
+.IR groff2eps ,
+produces an EPS file.
+.
+.
+.RS
+.P
+.EX
+#! /bin/sh
+groff \-P\-b16 "$1" > "$1".ps
+gs \-dNOPAUSE \-sDEVICE=bbox \-\- "$1".ps 2> "$1".bbox
+sed \-e "/\[ha]%%Orientation/r $1.bbox" \[rs]
+ \-e "/\[ha]%!PS\-Adobe\-3.0/s/$/ EPSF\-3.0/" "$1".ps > "$1".eps
+rm "$1".ps "$1".bbox
+.EE
+.RE
+.
+.
+.P
+You can then use
+.RB \[lq] "groff2eps foo" \[rq]
+to convert file
+.I foo
+to
+.IR foo.eps .
+.
+.
+.\" ====================================================================
+.SS "TrueType and other font formats"
+.\" ====================================================================
+.
+TrueType fonts can be used with
+.I grops
+if converted first to Type\~42 format,
+a PostScript wrapper equivalent to the PFA format described in
+.MR pfbtops @MAN1EXT@ .
+.
+Several methods exist to generate a Type\~42 wrapper;
+some of them involve the use of a PostScript interpreter such as
+Ghostscript\[em]see
+.MR gs 1 .
+.
+.
+.P
+One approach is to use
+.UR https://fontforge.org/
+FontForge
+.UE ,
+a font editor that can convert most outline font formats.
+.
+Here's an example of using the Roboto Slab Serif font with
+.IR groff .
+.
+Several variables are used so that you can more easily adapt it into
+your own script.
+.
+.
+.RS 4
+.P
+.EX
+MAP=@FONTDIR@/devps/generate/text.map
+TTF=/usr/share/fonts/truetype/roboto/slab/RobotoSlab\-Regular.ttf
+BASE=$(basename \[dq]$TTF\[dq])
+INT=${BASE%.ttf}
+PFA=$INT.pfa
+AFM=$INT.afm
+GFN=RSR
+DIR=$HOME/.local/groff/font
+mkdir \-p \[dq]$DIR\[dq]/devps
+fontforge \-lang=ff \-c \[dq]Open(\[rs]\[dq]$TTF\[rs]\[dq]);\[rs]
+\tGenerate(\[rs]\[dq]$DIR/devps/$PFA\[rs]\[dq]);\[dq]
+afmtodit \[dq]$DIR/devps/$AFM\[dq] \[dq]$MAP\[dq] \
+\[dq]$DIR/devps/$GFN\[dq]
+printf \[dq]$BASE\[rs]t$PFA\[rs]n\[dq] >> \[dq]$DIR/devps/download\[dq]
+.EE
+.RE
+.
+.
+.P
+.I fontforge
+and
+.I afmtodit
+may generate warnings depending on the attributes of the font.
+.
+The test procedure is simple.
+.
+.
+.RS 4
+.P
+.EX
+printf \[dq].ft RSR\[rs]nHello, world!\[rs]n\[dq] | groff \-F \
+\[dq]$DIR\[dq] > hello.ps
+.EE
+.RE
+.
+.
+.P
+Once you're satisfied that the font works,
+you may want to generate any available related styles
+(for instance,
+Roboto Slab
+also has \[lq]Bold\[rq],
+\[lq]Light\[rq],
+and
+\[lq]Thin\[rq]
+styles)
+and set up
+.I GROFF_FONT_PATH
+in your environment to include the directory you keep the generated
+fonts in so that you don't have to use the
+.B \-F
+option.
+.
+.
+.\" ====================================================================
+.SH "Font installation"
+.\" ====================================================================
+.
+The following is a step-by-step font installation guide for
+.I grops.
+.
+.
+.IP \[bu] 2n
+Convert your font to something
+.I groff
+understands.
+.
+This is a PostScript Type\~1 font in PFA format or a PostScript
+Type\~42 font,
+together with an AFM file.
+.
+A PFA file begins as follows.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+%!PS\-AdobeFont\-1.0:
+.EE
+.RE
+.
+A PFB file contains this string as well,
+preceded by some non-printing bytes.
+.
+If your font is in PFB format,
+use
+.IR groff 's
+.MR pfbtops @MAN1EXT@
+program to convert it to PFA.
+.
+For TrueType and other font formats,
+we recommend
+.IR fontforge ,
+which can convert most outline font formats.
+.
+A Type\~42 font file begins as follows.
+.
+.RS
+.EX
+%!PS\-TrueTypeFont
+.EE
+.RE
+.
+This is a wrapper format for TrueType fonts.
+.
+Old PostScript printers might not support them
+(that is,
+they might not have a built-in TrueType font interpreter).
+.
+In the following steps,
+we will consider the use of CTAN's
+.UR https://\:ctan.org/\:tex\-archive/\:fonts/\:brushscr
+BrushScriptX-Italic
+.UE
+font in PFA format.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Convert the AFM file to a
+.I groff
+font description file with the
+.MR afmtodit @MAN1EXT@
+program.
+.
+For instance,
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+$ \c
+.B afmtodit BrushScriptX\-Italic.afm text.map BSI
+.EE
+.RE
+.
+converts the Adobe Font Metric file
+.I BrushScriptX\-Italic.afm
+to the
+.I groff
+font description file
+.IR BSI .
+.RE \" now restore left margin
+.
+.
+.IP
+If you have a font family which provides regular upright (roman),
+bold,
+italic,
+and
+bold-italic styles
+(where \[lq]italic\[rq] may be \[lq]oblique\[rq] or \[lq]slanted\[rq]),
+we recommend using the letters
+.BR R ,
+.BR B ,
+.BR I ,
+and
+.BR BI ,
+respectively,
+as suffixes to the
+.I groff
+font family name to enable
+.IR groff 's
+font family and style selection features.
+.
+An example is
+.IR groff 's
+built-in support for Times:
+the font family
+name is abbreviated as
+.BR T ,
+and the
+.I groff
+font names are therefore
+.BR TR ,
+.BR TB ,
+.BR TI ,
+and
+.BR TBI .
+.
+In our example,
+however,
+the BrushScriptX font is available in a single style only,
+italic.
+.
+.
+.IP \[bu]
+Install the
+.I groff
+font description file(s) in a
+.I devps
+subdirectory in the search path that
+.I groff
+uses for device and font file descriptions.
+.
+See the
+.I GROFF_FONT_PATH
+entry in section \[lq]Environment\[rq] of
+.MR @g@troff @MAN1EXT@
+for the current value of the font search path.
+.
+While
+.I groff
+doesn't directly use AFM files,
+it is a good idea to store them alongside its font description files.
+.
+.
+.IP \[bu]
+Register fonts in the
+.I devps/download
+file so they can be located for embedding in PostScript files
+.I grops
+generates.
+.
+Only the first
+.I download
+file encountered in the font search path is read.
+.
+If in doubt,
+copy the default
+.I download
+file
+(see section \[lq]Files\[rq] below)
+to the first directory in the font search path and add your fonts there.
+.
+The PostScript font name used by
+.I grops
+is stored in the
+.B internalname
+field in the
+.I groff
+font description file.
+.
+(This name does not necessarily resemble the font's file name.)
+.
+We add the following line to
+.IR download .
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+BrushScriptX\-Italic\[->]BrushScriptX\-Italic.pfa
+.EE
+.RE \" but only one to get back to it
+.
+A tab character,
+depicted as \[->],
+separates the fields.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Test the selection and embedding of the new font.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+printf "\[rs]\[rs]f[BSI]Hello, world!\[rs]n" \
+| groff \-T ps \-P \-e >hello.ps
+see hello.pdf
+.EE
+.RE
+.RE \" now restore left margin
+.
+.
+.\" ====================================================================
+.SH "Old fonts"
+.\" ====================================================================
+.
+.I groff
+versions 1.19.2 and earlier contained descriptions of a slightly
+different set of the base 35 PostScript level 2 fonts defined by Adobe.
+.
+The older set has 229 glyphs and a larger set of kerning pairs;
+the newer one has 314 glyphs and includes the Euro glyph.
+.
+For backwards compatibility,
+these old font descriptions are also installed in the
+.I @OLDFONTDIR@/\:\%devps
+directory.
+.
+.
+.P
+To use them,
+make sure that
+.I grops
+finds the fonts before the default system fonts
+(with the same names):
+either give
+.I grops
+the
+.B \-F
+command-line option,
+.
+.RS
+.EX
+$ \c
+.B groff \-Tps \-P\-F \-P@OLDFONTDIR@ \c
+\&.\|.\|.
+.EE
+.RE
+.
+or add the directory to
+.IR groff 's
+font and device description search path environment variable,
+.
+.RS
+.EX
+$ \c
+.B GROFF_FONT_PATH=\:@OLDFONTDIR@ \[rs]
+.RS
+.B groff \-Tps \c
+\&.\|.\|.
+.RE
+.EE
+.RE
+.
+when the command runs.
+.
+.
+.br
+.ne 3v
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROPS_PROLOGUE
+If this is set to
+.IR foo ,
+then
+.I grops
+uses the file
+.I foo
+(in the font path) instead of the default prologue file
+.IR prologue .
+.
+The option
+.B \-P
+overrides this environment variable.
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR ctime 3
+and recorded in a PostScript comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:DESC
+describes the
+.B ps
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devps/ F
+describes the font known
+.RI as\~ F
+on device
+.BR ps .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%download
+lists fonts available for embedding within the PostScript document
+(or download to the device).
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%prologue
+is the default PostScript prologue prefixed to every output file.
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:text.enc
+describes the encoding scheme used by most PostScript Type\~1 fonts;
+the
+.B \%encoding
+directive of
+font description files for the
+.B ps
+device refers to it.
+.
+.
+.TP
+.I @MACRODIR@/\:ps.tmac
+defines macros for use with the
+.B ps
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B ps
+output device is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:pspic.tmac
+defines the
+.B PSPIC
+macro for embedding images in a document;
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+It is automatically loaded by
+.I troffrc.
+.
+.
+.TP
+.I @MACRODIR@/psold.tmac
+provides replacement glyphs for text fonts that lack complete coverage
+of the ISO Latin-1 character set;
+using it,
+.I groff
+can produce glyphs like eth (\[Sd]) and thorn (\[Tp]) that older
+PostScript printers do not natively support.
+.
+.
+.P
+.I grops
+creates temporary files using the template
+.RI \[lq] grops XXXXXX\[rq];
+see
+.MR groff @MAN1EXT@
+for details on their storage location.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR http://\:partners\:.adobe\:.com/\:public/\:developer/\:en/\:ps/\
+\:5001\:.DSC_Spec\:.pdf
+PostScript Language Document Structuring Conventions Specification
+.UE
+.
+.
+.P
+.MR afmtodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR pfbtops @MAN1EXT@ ,
+.MR groff_char @MAN7EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_tmac @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm FT
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grops_1_man_C]
+.do rr *groff_grops_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grops/grops.am b/src/devices/grops/grops.am
new file mode 100644
index 0000000..cb5532a
--- /dev/null
+++ b/src/devices/grops/grops.am
@@ -0,0 +1,38 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grops
+grops_SOURCES = \
+ src/devices/grops/ps.cpp \
+ src/devices/grops/psrm.cpp \
+ src/devices/grops/ps.h
+grops_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grops/grops.1
+EXTRA_DIST += \
+ src/devices/grops/grops.1.man \
+ src/devices/grops/psfig.diff \
+ src/devices/grops/TODO
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grops/ps.cpp b/src/devices/grops/ps.cpp
new file mode 100644
index 0000000..807945f
--- /dev/null
+++ b/src/devices/grops/ps.cpp
@@ -0,0 +1,1894 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+ * PostScript documentation:
+ * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
+ * http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf
+ */
+
+#include "lib.h" // PI
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "nonposix.h"
+#include "paper.h"
+#include "curtime.h"
+
+#include "ps.h"
+#include <time.h>
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+extern "C" const char *Version_string;
+
+// search path defaults to the current directory
+search_path include_search_path(0, 0, 0, 1);
+
+static int landscape_flag = 0;
+static int manual_feed_flag = 0;
+static int ncopies = 1;
+static int linewidth = -1;
+// Non-zero means generate PostScript code that guesses the paper
+// length using the imageable area.
+static int guess_flag = 0;
+static double user_paper_length = 0;
+static double user_paper_width = 0;
+
+// Non-zero if -b was specified on the command line.
+static int bflag = 0;
+unsigned broken_flags = 0;
+
+// Non-zero means we need the CMYK extension for PostScript Level 1
+static int cmyk_flag = 0;
+
+#define DEFAULT_LINEWIDTH 40 /* in ems/1000 */
+#define MAX_LINE_LENGTH 72
+#define FILL_MAX 1000
+
+const char *const dict_name = "grops";
+const char *const defs_dict_name = "DEFS";
+const int DEFS_DICT_SPARE = 50;
+
+double degrees(double r)
+{
+ return r*180.0/PI;
+}
+
+double radians(double d)
+{
+ return d*PI/180.0;
+}
+
+// This is used for testing whether a character should be output in the
+// PostScript file using \nnn, so we really want the character to be
+// less than 0200.
+
+inline int is_ascii(char c)
+{
+ return (unsigned char)c < 0200;
+}
+
+ps_output::ps_output(FILE *f, int n)
+: fp(f), col(0), max_line_length(n), need_space(0), fixed_point(0)
+{
+}
+
+ps_output &ps_output::set_file(FILE *f)
+{
+ fp = f;
+ col = 0;
+ return *this;
+}
+
+ps_output &ps_output::copy_file(FILE *infp)
+{
+ int c;
+ while ((c = getc(infp)) != EOF)
+ putc(c, fp);
+ return *this;
+}
+
+ps_output &ps_output::end_line()
+{
+ if (col != 0) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ return *this;
+}
+
+ps_output &ps_output::special(const char *s)
+{
+ if (s == 0 || *s == '\0')
+ return *this;
+ if (col != 0) {
+ putc('\n', fp);
+ col = 0;
+ }
+ fputs(s, fp);
+ if (strchr(s, '\0')[-1] != '\n')
+ putc('\n', fp);
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::simple_comment(const char *s)
+{
+ if (col != 0)
+ putc('\n', fp);
+ putc('%', fp);
+ putc('%', fp);
+ fputs(s, fp);
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::begin_comment(const char *s)
+{
+ if (col != 0)
+ putc('\n', fp);
+ putc('%', fp);
+ putc('%', fp);
+ fputs(s, fp);
+ col = 2 + strlen(s);
+ return *this;
+}
+
+ps_output &ps_output::end_comment()
+{
+ if (col != 0) {
+ putc('\n', fp);
+ col = 0;
+ }
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::comment_arg(const char *s)
+{
+ int len = strlen(s);
+ if (col + len + 1 > max_line_length) {
+ putc('\n', fp);
+ fputs("%%+", fp);
+ col = 3;
+ }
+ putc(' ', fp);
+ fputs(s, fp);
+ col += len + 1;
+ return *this;
+}
+
+ps_output &ps_output::set_fixed_point(int n)
+{
+ assert(n >= 0 && n <= 10);
+ fixed_point = n;
+ return *this;
+}
+
+ps_output &ps_output::put_delimiter(char c)
+{
+ if (col + 1 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc(c, fp);
+ col++;
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::put_string(const char *s, int n)
+{
+ int len = 0;
+ int i;
+ for (i = 0; i < n; i++) {
+ char c = s[i];
+ if (is_ascii(c) && csprint(c)) {
+ if (c == '(' || c == ')' || c == '\\')
+ len += 2;
+ else
+ len += 1;
+ }
+ else
+ len += 4;
+ }
+ if (len > n*2) {
+ if (col + n*2 + 2 > max_line_length && n*2 + 2 <= max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ if (col + 1 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc('<', fp);
+ col++;
+ for (i = 0; i < n; i++) {
+ if (col + 2 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ fprintf(fp, "%02x", s[i] & 0377);
+ col += 2;
+ }
+ putc('>', fp);
+ col++;
+ }
+ else {
+ if (col + len + 2 > max_line_length && len + 2 <= max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ if (col + 2 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc('(', fp);
+ col++;
+ for (i = 0; i < n; i++) {
+ char c = s[i];
+ if (is_ascii(c) && csprint(c)) {
+ if (c == '(' || c == ')' || c == '\\')
+ len = 2;
+ else
+ len = 1;
+ }
+ else
+ len = 4;
+ if (col + len + 1 > max_line_length) {
+ putc('\\', fp);
+ putc('\n', fp);
+ col = 0;
+ }
+ switch (len) {
+ case 1:
+ putc(c, fp);
+ break;
+ case 2:
+ putc('\\', fp);
+ putc(c, fp);
+ break;
+ case 4:
+ fprintf(fp, "\\%03o", c & 0377);
+ break;
+ default:
+ assert(0);
+ }
+ col += len;
+ }
+ putc(')', fp);
+ col++;
+ }
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::put_number(int n)
+{
+ char buf[1 + INT_DIGITS + 1];
+ sprintf(buf, "%d", n);
+ int len = strlen(buf);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(buf, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_fix_number(int i)
+{
+ const char *p = if_to_a(i, fixed_point);
+ int len = strlen(p);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(p, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_float(double d)
+{
+ char buf[128];
+ sprintf(buf, "%.4f", d);
+ int last = strlen(buf) - 1;
+ while (buf[last] == '0')
+ last--;
+ if (buf[last] == '.')
+ last--;
+ buf[++last] = '\0';
+ if (col > 0 && col + last + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(buf, fp);
+ col += last;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_symbol(const char *s)
+{
+ int len = strlen(s);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(s, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_color(unsigned int c)
+{
+ char buf[128];
+ sprintf(buf, "%.3g", double(c) / double(color::MAX_COLOR_VAL));
+ int len = strlen(buf);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(buf, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_literal_symbol(const char *s)
+{
+ int len = strlen(s);
+ if (col > 0 && col + len + 1 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc('/', fp);
+ fputs(s, fp);
+ col += len + 1;
+ need_space = 1;
+ return *this;
+}
+
+class ps_font : public font {
+ ps_font(const char *);
+public:
+ int encoding_index;
+ char *encoding;
+ char *reencoded_name;
+ ~ps_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static ps_font *load_ps_font(const char *);
+};
+
+ps_font *ps_font::load_ps_font(const char *s)
+{
+ ps_font *f = new ps_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+ps_font::ps_font(const char *nm)
+: font(nm), encoding_index(-1), encoding(0), reencoded_name(0)
+{
+}
+
+ps_font::~ps_font()
+{
+ free(encoding);
+ delete[] reencoded_name;
+}
+
+void ps_font::handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "encoding") == 0) {
+ if (arg == 0)
+ error_with_file_and_line(filename, lineno,
+ "'encoding' command requires an argument");
+ else
+ encoding = strsave(arg);
+ }
+}
+
+static void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "broken") == 0) {
+ if (arg == 0)
+ error_with_file_and_line(filename, lineno,
+ "'broken' command requires an argument");
+ else if (!bflag)
+ broken_flags = atoi(arg);
+ }
+}
+
+struct subencoding {
+ font *p;
+ unsigned int num;
+ int idx;
+ char *subfont;
+ const char *glyphs[256];
+ subencoding *next;
+
+ subencoding(font *, unsigned int, int, subencoding *);
+ ~subencoding();
+};
+
+subencoding::subencoding(font *f, unsigned int n, int ix, subencoding *s)
+: p(f), num(n), idx(ix), subfont(0), next(s)
+{
+ for (int i = 0; i < 256; i++)
+ glyphs[i] = 0;
+}
+
+subencoding::~subencoding()
+{
+ delete[] subfont;
+}
+
+struct style {
+ font *f;
+ subencoding *sub;
+ int point_size;
+ int height;
+ int slant;
+ style();
+ style(font *, subencoding *, int, int, int);
+ int operator==(const style &) const;
+ int operator!=(const style &) const;
+};
+
+style::style() : f(0)
+{
+}
+
+style::style(font *p, subencoding *s, int sz, int h, int sl)
+: f(p), sub(s), point_size(sz), height(h), slant(sl)
+{
+}
+
+int style::operator==(const style &s) const
+{
+ return (f == s.f
+ && sub == s.sub
+ && point_size == s.point_size
+ && height == s.height
+ && slant == s.slant);
+}
+
+int style::operator!=(const style &s) const
+{
+ return !(*this == s);
+}
+
+class ps_printer : public printer {
+ FILE *tempfp;
+ ps_output out;
+ int res;
+ glyph *space_glyph;
+ int pages_output;
+ int paper_length;
+ int equalise_spaces;
+ enum { SBUF_SIZE = 256 };
+ char sbuf[SBUF_SIZE];
+ int sbuf_len;
+ int sbuf_start_hpos;
+ int sbuf_vpos;
+ int sbuf_end_hpos;
+ int sbuf_space_width;
+ int sbuf_space_count;
+ int sbuf_space_diff_count;
+ int sbuf_space_code;
+ int sbuf_kern;
+ style sbuf_style;
+ color sbuf_color; // the current PS color
+ style output_style;
+ subencoding *subencodings;
+ int output_hpos;
+ int output_vpos;
+ int output_draw_point_size;
+ int line_thickness;
+ int output_line_thickness;
+ unsigned char output_space_code;
+ enum { MAX_DEFINED_STYLES = 50 };
+ style defined_styles[MAX_DEFINED_STYLES];
+ int ndefined_styles;
+ int next_encoding_index;
+ int next_subencoding_index;
+ string defs;
+ int ndefs;
+ resource_manager rm;
+ int invis_count;
+
+ void flush_sbuf();
+ void set_style(const style &);
+ void set_space_code(unsigned char);
+ int set_encoding_index(ps_font *);
+ subencoding *set_subencoding(font *, glyph *, unsigned char *);
+ char *get_subfont(subencoding *, const char *);
+ void do_exec(char *, const environment *);
+ void do_import(char *, const environment *);
+ void do_def(char *, const environment *);
+ void do_mdef(char *, const environment *);
+ void do_file(char *, const environment *);
+ void do_invis(char *, const environment *);
+ void do_endinvis(char *, const environment *);
+ void set_line_thickness_and_color(const environment *);
+ void fill_path(const environment *);
+ void encode_fonts();
+ void encode_subfont(subencoding *);
+ void define_encoding(const char *, int);
+ void reencode_font(ps_font *);
+ void set_color(color *, int = 0);
+
+ const char *media_name();
+ int media_width();
+ int media_height();
+ void media_set();
+
+public:
+ ps_printer(double);
+ ~ps_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *);
+ void draw(int, int *, int, const environment *);
+ void begin_page(int);
+ void end_page(int);
+ void special(char *, const environment *, char);
+ font *make_font(const char *);
+ void end_of_line();
+};
+
+// 'pl' is in inches
+ps_printer::ps_printer(double pl)
+: out(0, MAX_LINE_LENGTH),
+ pages_output(0),
+ sbuf_len(0),
+ subencodings(0),
+ output_hpos(-1),
+ output_vpos(-1),
+ line_thickness(-1),
+ ndefined_styles(0),
+ next_encoding_index(0),
+ next_subencoding_index(0),
+ ndefs(0),
+ invis_count(0)
+{
+ tempfp = xtmpfile();
+ out.set_file(tempfp);
+ if (linewidth < 0)
+ linewidth = DEFAULT_LINEWIDTH;
+ if (font::hor != 1)
+ fatal("device horizontal motion quantum must be 1, got %1",
+ font::hor);
+ if (font::vert != 1)
+ fatal("device vertical motion quantum must be 1, got %1",
+ font::vert);
+ if (font::res % (font::sizescale*72) != 0)
+ fatal("device resolution must be a multiple of 72*'sizescale', got"
+ " %1 ('sizescale'=%2)", font::res, font::sizescale);
+ int r = font::res;
+ int point = 0;
+ while (r % 10 == 0) {
+ r /= 10;
+ point++;
+ }
+ res = r;
+ out.set_fixed_point(point);
+ space_glyph = name_to_glyph("space");
+ if (pl == 0)
+ paper_length = font::paperlength;
+ else
+ paper_length = int(pl * font::res + 0.5);
+ if (paper_length == 0)
+ paper_length = 11 * font::res;
+ equalise_spaces = font::res >= 72000;
+}
+
+int ps_printer::set_encoding_index(ps_font *f)
+{
+ if (f->encoding_index >= 0)
+ return f->encoding_index;
+ for (font_pointer_list *p = font_list; p; p = p->next)
+ if (p->p != f) {
+ char *encoding = ((ps_font *)p->p)->encoding;
+ int encoding_index = ((ps_font *)p->p)->encoding_index;
+ if (encoding != 0 && encoding_index >= 0
+ && strcmp(f->encoding, encoding) == 0) {
+ return f->encoding_index = encoding_index;
+ }
+ }
+ return f->encoding_index = next_encoding_index++;
+}
+
+subencoding *ps_printer::set_subencoding(font *f, glyph *g,
+ unsigned char *codep)
+{
+ unsigned int idx = f->get_code(g);
+ *codep = idx % 256;
+ unsigned int num = idx >> 8;
+ if (num == 0)
+ return 0;
+ subencoding *p = 0;
+ for (p = subencodings; p; p = p->next)
+ if (p->p == f && p->num == num)
+ break;
+ if (p == 0)
+ p = subencodings = new subencoding(f, num, next_subencoding_index++,
+ subencodings);
+ p->glyphs[*codep] = f->get_special_device_encoding(g);
+ return p;
+}
+
+char *ps_printer::get_subfont(subencoding *sub, const char *stem)
+{
+ assert(sub != 0);
+ if (!sub->subfont) {
+ char *tem = new char[strlen(stem) + 2 + INT_DIGITS + 1];
+ sprintf(tem, "%s@@%d", stem, sub->idx);
+ sub->subfont = tem;
+ }
+ return sub->subfont;
+}
+
+void ps_printer::set_char(glyph *g, font *f, const environment *env, int w,
+ const char *)
+{
+ if (g == space_glyph || invis_count > 0)
+ return;
+ unsigned char code;
+ subencoding *sub = set_subencoding(f, g, &code);
+ style sty(f, sub, env->size, env->height, env->slant);
+ if (sty.slant != 0) {
+ if (sty.slant > 80 || sty.slant < -80) {
+ error("silly slant '%1' degrees", sty.slant);
+ sty.slant = 0;
+ }
+ }
+ if (sbuf_len > 0) {
+ if (sbuf_len < SBUF_SIZE
+ && sty == sbuf_style
+ && sbuf_vpos == env->vpos
+ && sbuf_color == *env->col) {
+ if (sbuf_end_hpos == env->hpos) {
+ sbuf[sbuf_len++] = code;
+ sbuf_end_hpos += w + sbuf_kern;
+ return;
+ }
+ if (sbuf_len == 1 && sbuf_kern == 0) {
+ sbuf_kern = env->hpos - sbuf_end_hpos;
+ sbuf_end_hpos = env->hpos + sbuf_kern + w;
+ sbuf[sbuf_len++] = code;
+ return;
+ }
+ /* If sbuf_end_hpos - sbuf_kern == env->hpos, we are better off
+ starting a new string. */
+ if (sbuf_len < SBUF_SIZE - 1 && env->hpos >= sbuf_end_hpos
+ && (sbuf_kern == 0 || sbuf_end_hpos - sbuf_kern != env->hpos)) {
+ if (sbuf_space_code < 0) {
+ if (f->contains(space_glyph) && !sub) {
+ sbuf_space_code = f->get_code(space_glyph);
+ sbuf_space_width = env->hpos - sbuf_end_hpos;
+ sbuf_end_hpos = env->hpos + w + sbuf_kern;
+ sbuf[sbuf_len++] = sbuf_space_code;
+ sbuf[sbuf_len++] = code;
+ sbuf_space_count++;
+ return;
+ }
+ }
+ else {
+ int diff = env->hpos - sbuf_end_hpos - sbuf_space_width;
+ if (diff == 0 || (equalise_spaces && (diff == 1 || diff == -1))) {
+ sbuf_end_hpos = env->hpos + w + sbuf_kern;
+ sbuf[sbuf_len++] = sbuf_space_code;
+ sbuf[sbuf_len++] = code;
+ sbuf_space_count++;
+ if (diff == 1)
+ sbuf_space_diff_count++;
+ else if (diff == -1)
+ sbuf_space_diff_count--;
+ return;
+ }
+ }
+ }
+ }
+ flush_sbuf();
+ }
+ sbuf_len = 1;
+ sbuf[0] = code;
+ sbuf_end_hpos = env->hpos + w;
+ sbuf_start_hpos = env->hpos;
+ sbuf_vpos = env->vpos;
+ sbuf_style = sty;
+ sbuf_space_code = -1;
+ sbuf_space_width = 0;
+ sbuf_space_count = sbuf_space_diff_count = 0;
+ sbuf_kern = 0;
+ if (sbuf_color != *env->col)
+ set_color(env->col);
+}
+
+static char *make_encoding_name(int encoding_index)
+{
+ static char buf[3 + INT_DIGITS + 1];
+ sprintf(buf, "ENC%d", encoding_index);
+ return buf;
+}
+
+static char *make_subencoding_name(int subencoding_index)
+{
+ static char buf[6 + INT_DIGITS + 1];
+ sprintf(buf, "SUBENC%d", subencoding_index);
+ return buf;
+}
+
+const char *const WS = " \t\n\r";
+
+void ps_printer::define_encoding(const char *encoding, int encoding_index)
+{
+ char *vec[256];
+ int i;
+ for (i = 0; i < 256; i++)
+ vec[i] = 0;
+ char *path;
+ FILE *fp = font::open_file(encoding, &path);
+ if (fp == 0)
+ fatal("can't open encoding file '%1'", encoding);
+ int lineno = 1;
+ const int BUFFER_SIZE = 512;
+ char buf[BUFFER_SIZE];
+ while (fgets(buf, BUFFER_SIZE, fp) != 0) {
+ char *p = buf;
+ while (csspace(*p))
+ p++;
+ if (*p != '#' && *p != '\0' && (p = strtok(buf, WS)) != 0) {
+ char *q = strtok(0, WS);
+ int n = 0; // pacify compiler
+ if (q == 0 || sscanf(q, "%d", &n) != 1 || n < 0 || n >= 256)
+ fatal_with_file_and_line(path, lineno, "bad second field");
+ vec[n] = new char[strlen(p) + 1];
+ strcpy(vec[n], p);
+ }
+ lineno++;
+ }
+ free(path);
+ out.put_literal_symbol(make_encoding_name(encoding_index))
+ .put_delimiter('[');
+ for (i = 0; i < 256; i++) {
+ if (vec[i] == 0)
+ out.put_literal_symbol(".notdef");
+ else {
+ out.put_literal_symbol(vec[i]);
+ delete[] vec[i];
+ }
+ }
+ out.put_delimiter(']')
+ .put_symbol("def");
+ fclose(fp);
+}
+
+void ps_printer::reencode_font(ps_font *f)
+{
+ out.put_literal_symbol(f->reencoded_name)
+ .put_symbol(make_encoding_name(f->encoding_index))
+ .put_literal_symbol(f->get_internal_name())
+ .put_symbol("RE");
+}
+
+void ps_printer::encode_fonts()
+{
+ if (next_encoding_index == 0)
+ return;
+ char *done_encoding = new char[next_encoding_index];
+ for (int i = 0; i < next_encoding_index; i++)
+ done_encoding[i] = 0;
+ for (font_pointer_list *f = font_list; f; f = f->next) {
+ int encoding_index = ((ps_font *)f->p)->encoding_index;
+ if (encoding_index >= 0) {
+ assert(encoding_index < next_encoding_index);
+ if (!done_encoding[encoding_index]) {
+ done_encoding[encoding_index] = 1;
+ define_encoding(((ps_font *)f->p)->encoding, encoding_index);
+ }
+ reencode_font((ps_font *)f->p);
+ }
+ }
+ delete[] done_encoding;
+}
+
+void ps_printer::encode_subfont(subencoding *sub)
+{
+ assert(sub != 0);
+ out.put_literal_symbol(make_subencoding_name(sub->idx))
+ .put_delimiter('[');
+ for (int i = 0; i < 256; i++)
+ {
+ if (sub->glyphs[i])
+ out.put_literal_symbol(sub->glyphs[i]);
+ else
+ out.put_literal_symbol(".notdef");
+ }
+ out.put_delimiter(']')
+ .put_symbol("def");
+}
+
+void ps_printer::set_style(const style &sty)
+{
+ char buf[1 + INT_DIGITS + 1];
+ for (int i = 0; i < ndefined_styles; i++)
+ if (sty == defined_styles[i]) {
+ sprintf(buf, "F%d", i);
+ out.put_symbol(buf);
+ return;
+ }
+ if (ndefined_styles >= MAX_DEFINED_STYLES)
+ ndefined_styles = 0;
+ sprintf(buf, "F%d", ndefined_styles);
+ out.put_literal_symbol(buf);
+ const char *psname = sty.f->get_internal_name();
+ if (psname == 0)
+ fatal("no internalname specified for font '%1'", sty.f->get_name());
+ char *encoding = ((ps_font *)sty.f)->encoding;
+ if (sty.sub == 0) {
+ if (encoding != 0) {
+ char *s = ((ps_font *)sty.f)->reencoded_name;
+ if (s == 0) {
+ int ei = set_encoding_index((ps_font *)sty.f);
+ char *tem = new char[strlen(psname) + 1 + INT_DIGITS + 1];
+ sprintf(tem, "%s@%d", psname, ei);
+ psname = tem;
+ ((ps_font *)sty.f)->reencoded_name = tem;
+ }
+ else
+ psname = s;
+ }
+ }
+ else
+ psname = get_subfont(sty.sub, psname);
+ out.put_fix_number((font::res/(72*font::sizescale))*sty.point_size);
+ if (sty.height != 0 || sty.slant != 0) {
+ int h = sty.height == 0 ? sty.point_size : sty.height;
+ h *= font::res/(72*font::sizescale);
+ int c = int(h*tan(radians(sty.slant)) + .5);
+ out.put_fix_number(c)
+ .put_fix_number(h)
+ .put_literal_symbol(psname)
+ .put_symbol("MF");
+ }
+ else {
+ out.put_literal_symbol(psname)
+ .put_symbol("SF");
+ }
+ defined_styles[ndefined_styles++] = sty;
+}
+
+void ps_printer::set_color(color *col, int fill)
+{
+ sbuf_color = *col;
+ unsigned int components[4];
+ char s[3];
+ color_scheme cs = col->get_components(components);
+ s[0] = fill ? 'F' : 'C';
+ s[2] = 0;
+ switch (cs) {
+ case DEFAULT: // black
+ out.put_symbol("0");
+ s[1] = 'g';
+ break;
+ case RGB:
+ out.put_color(Red)
+ .put_color(Green)
+ .put_color(Blue);
+ s[1] = 'r';
+ break;
+ case CMY:
+ col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black);
+ // fall through
+ case CMYK:
+ out.put_color(Cyan)
+ .put_color(Magenta)
+ .put_color(Yellow)
+ .put_color(Black);
+ s[1] = 'k';
+ cmyk_flag = 1;
+ break;
+ case GRAY:
+ out.put_color(Gray);
+ s[1] = 'g';
+ break;
+ }
+ out.put_symbol(s);
+}
+
+void ps_printer::set_space_code(unsigned char c)
+{
+ out.put_literal_symbol("SC")
+ .put_number(c)
+ .put_symbol("def");
+}
+
+void ps_printer::end_of_line()
+{
+ flush_sbuf();
+ // this ensures that we do an absolute motion to the beginning of a line
+ output_vpos = output_hpos = -1;
+}
+
+void ps_printer::flush_sbuf()
+{
+ enum {
+ NONE,
+ RELATIVE_H,
+ RELATIVE_V,
+ RELATIVE_HV,
+ ABSOLUTE
+ } motion = NONE;
+ int space_flag = 0;
+ if (sbuf_len == 0)
+ return;
+ if (output_style != sbuf_style) {
+ set_style(sbuf_style);
+ output_style = sbuf_style;
+ }
+ int extra_space = 0;
+ if (output_hpos < 0 || output_vpos < 0)
+ motion = ABSOLUTE;
+ else {
+ if (output_hpos != sbuf_start_hpos)
+ motion = RELATIVE_H;
+ if (output_vpos != sbuf_vpos) {
+ if (motion != NONE)
+ motion = RELATIVE_HV;
+ else
+ motion = RELATIVE_V;
+ }
+ }
+ if (sbuf_space_code >= 0) {
+ int w = sbuf_style.f->get_width(space_glyph, sbuf_style.point_size);
+ if (w + sbuf_kern != sbuf_space_width) {
+ if (sbuf_space_code != output_space_code) {
+ set_space_code(sbuf_space_code);
+ output_space_code = sbuf_space_code;
+ }
+ space_flag = 1;
+ extra_space = sbuf_space_width - w - sbuf_kern;
+ if (sbuf_space_diff_count > sbuf_space_count/2)
+ extra_space++;
+ else if (sbuf_space_diff_count < -(sbuf_space_count/2))
+ extra_space--;
+ }
+ }
+ if (space_flag)
+ out.put_fix_number(extra_space);
+ if (sbuf_kern != 0)
+ out.put_fix_number(sbuf_kern);
+ out.put_string(sbuf, sbuf_len);
+ char command_array[] = {'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T'};
+ char sym[2];
+ sym[0] = command_array[motion*4 + space_flag + 2*(sbuf_kern != 0)];
+ sym[1] = '\0';
+ switch (motion) {
+ case NONE:
+ break;
+ case ABSOLUTE:
+ out.put_fix_number(sbuf_start_hpos)
+ .put_fix_number(sbuf_vpos);
+ break;
+ case RELATIVE_H:
+ out.put_fix_number(sbuf_start_hpos - output_hpos);
+ break;
+ case RELATIVE_V:
+ out.put_fix_number(sbuf_vpos - output_vpos);
+ break;
+ case RELATIVE_HV:
+ out.put_fix_number(sbuf_start_hpos - output_hpos)
+ .put_fix_number(sbuf_vpos - output_vpos);
+ break;
+ default:
+ assert(0);
+ }
+ out.put_symbol(sym);
+ output_hpos = sbuf_end_hpos;
+ output_vpos = sbuf_vpos;
+ sbuf_len = 0;
+}
+
+void ps_printer::set_line_thickness_and_color(const environment *env)
+{
+ if (line_thickness < 0) {
+ if (output_draw_point_size != env->size) {
+ // we ought to check for overflow here
+ int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000;
+ out.put_fix_number(lw)
+ .put_symbol("LW");
+ output_draw_point_size = env->size;
+ output_line_thickness = -1;
+ }
+ }
+ else {
+ if (output_line_thickness != line_thickness) {
+ out.put_fix_number(line_thickness)
+ .put_symbol("LW");
+ output_line_thickness = line_thickness;
+ output_draw_point_size = -1;
+ }
+ }
+ if (sbuf_color != *env->col)
+ set_color(env->col);
+}
+
+void ps_printer::fill_path(const environment *env)
+{
+ if (sbuf_color == *env->fill)
+ out.put_symbol("FL");
+ else
+ set_color(env->fill, 1);
+}
+
+void ps_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if (invis_count > 0)
+ return;
+ flush_sbuf();
+ int fill_flag = 0;
+ switch (code) {
+ case 'C':
+ fill_flag = 1;
+ // fall through
+ case 'c':
+ // troff adds an extra argument to C
+ if (np != 1 && !(code == 'C' && np == 2)) {
+ error("1 argument required for circle");
+ break;
+ }
+ out.put_fix_number(env->hpos + p[0]/2)
+ .put_fix_number(env->vpos)
+ .put_fix_number(p[0]/2)
+ .put_symbol("DC");
+ if (fill_flag)
+ fill_path(env);
+ else {
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ case 'l':
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ set_line_thickness_and_color(env);
+ out.put_fix_number(p[0] + env->hpos)
+ .put_fix_number(p[1] + env->vpos)
+ .put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("DL");
+ break;
+ case 'E':
+ fill_flag = 1;
+ // fall through
+ case 'e':
+ if (np != 2) {
+ error("2 arguments required for ellipse");
+ break;
+ }
+ out.put_fix_number(p[0])
+ .put_fix_number(p[1])
+ .put_fix_number(env->hpos + p[0]/2)
+ .put_fix_number(env->vpos)
+ .put_symbol("DE");
+ if (fill_flag)
+ fill_path(env);
+ else {
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ case 'P':
+ fill_flag = 1;
+ // fall through
+ case 'p':
+ {
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ break;
+ }
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("MT");
+ for (int i = 0; i < np; i += 2)
+ out.put_fix_number(p[i])
+ .put_fix_number(p[i+1])
+ .put_symbol("RL");
+ out.put_symbol("CL");
+ if (fill_flag)
+ fill_path(env);
+ else {
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ }
+ case '~':
+ {
+ if (np & 1) {
+ error("even number of arguments required for spline");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for spline");
+ break;
+ }
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("MT");
+ out.put_fix_number(p[0]/2)
+ .put_fix_number(p[1]/2)
+ .put_symbol("RL");
+ /* tnum/tden should be between 0 and 1; the closer it is to 1
+ the tighter the curve will be to the guiding lines; 2/3
+ is the standard value */
+ const int tnum = 2;
+ const int tden = 3;
+ for (int i = 0; i < np - 2; i += 2) {
+ out.put_fix_number((p[i]*tnum)/(2*tden))
+ .put_fix_number((p[i + 1]*tnum)/(2*tden))
+ .put_fix_number(p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden))
+ .put_fix_number(p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden))
+ .put_fix_number((p[i] - p[i]/2) + p[i + 2]/2)
+ .put_fix_number((p[i + 1] - p[i + 1]/2) + p[i + 3]/2)
+ .put_symbol("RC");
+ }
+ out.put_fix_number(p[np - 2] - p[np - 2]/2)
+ .put_fix_number(p[np - 1] - p[np - 1]/2)
+ .put_symbol("RL");
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ case 'a':
+ {
+ if (np != 4) {
+ error("4 arguments required for arc");
+ break;
+ }
+ set_line_thickness_and_color(env);
+ double c[2];
+ if (adjust_arc_center(p, c))
+ out.put_fix_number(env->hpos + int(c[0]))
+ .put_fix_number(env->vpos + int(c[1]))
+ .put_fix_number(int(sqrt(c[0]*c[0] + c[1]*c[1])))
+ .put_float(degrees(atan2(-c[1], -c[0])))
+ .put_float(degrees(atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])))
+ .put_symbol("DA");
+ else
+ out.put_fix_number(p[0] + p[2] + env->hpos)
+ .put_fix_number(p[1] + p[3] + env->vpos)
+ .put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("DL");
+ }
+ break;
+ case 't':
+ if (np == 0)
+ line_thickness = -1;
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+ output_hpos = output_vpos = -1;
+}
+
+const char *ps_printer::media_name()
+{
+ return "Default";
+}
+
+int ps_printer::media_width()
+{
+ /*
+ * NOTE:
+ * Although paper dimensions are defined as a pair of real numbers,
+ * it seems to be a common convention to round to the nearest
+ * PostScript unit. For example, A4 is really 595.276 by 841.89 but
+ * we use 595 by 842.
+ *
+ * This is probably a good compromise, especially since the
+ * PostScript definition specifies that media matching should be done
+ * within a tolerance of 5 units.
+ */
+ return int(user_paper_width ? user_paper_width*72.0 + 0.5
+ : font::paperwidth*72.0/font::res + 0.5);
+}
+
+int ps_printer::media_height()
+{
+ return int(user_paper_length ? user_paper_length*72.0 + 0.5
+ : paper_length*72.0/font::res + 0.5);
+}
+
+void ps_printer::media_set()
+{
+ /*
+ * The setpagedevice implies an erasepage and initgraphics, and
+ * must thus precede any descriptions for a particular page.
+ *
+ * NOTE:
+ * This does not work with ps2pdf when there are included eps
+ * segments that contain PageSize/setpagedevice.
+ * This might be a bug in ghostscript -- must be investigated.
+ * Using setpagedevice in an .eps is really the wrong concept, anyway.
+ *
+ * NOTE:
+ * For the future, this is really the place to insert other
+ * media selection features, like:
+ * MediaColor
+ * MediaPosition
+ * MediaType
+ * MediaWeight
+ * MediaClass
+ * TraySwitch
+ * ManualFeed
+ * InsertSheet
+ * Duplex
+ * Collate
+ * ProcessColorModel
+ * etc.
+ */
+ if (!(broken_flags & (USE_PS_ADOBE_2_0|NO_PAPERSIZE))) {
+ out.begin_comment("BeginFeature:")
+ .comment_arg("*PageSize")
+ .comment_arg(media_name())
+ .end_comment();
+ int w = media_width();
+ int h = media_height();
+ if (w > 0 && h > 0)
+ // warning to user is done elsewhere
+ fprintf(out.get_file(),
+ "<< /PageSize [ %d %d ] /ImagingBBox null >> setpagedevice\n",
+ w, h);
+ out.simple_comment("EndFeature");
+ }
+}
+
+void ps_printer::begin_page(int n)
+{
+ out.begin_comment("Page:")
+ .comment_arg(i_to_a(n));
+ out.comment_arg(i_to_a(++pages_output))
+ .end_comment();
+ output_style.f = 0;
+ output_space_code = 32;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ output_hpos = output_vpos = -1;
+ ndefined_styles = 0;
+ out.simple_comment("BeginPageSetup");
+
+#if 0
+ /*
+ * NOTE:
+ * may decide to do this once per page
+ */
+ media_set();
+#endif
+
+ out.put_symbol("BP")
+ .simple_comment("EndPageSetup");
+ if (sbuf_color != default_color)
+ set_color(&sbuf_color);
+}
+
+void ps_printer::end_page(int)
+{
+ flush_sbuf();
+ set_color(&default_color);
+ out.put_symbol("EP");
+ if (invis_count != 0) {
+ error("missing 'endinvis' command");
+ invis_count = 0;
+ }
+}
+
+font *ps_printer::make_font(const char *nm)
+{
+ return ps_font::load_ps_font(nm);
+}
+
+ps_printer::~ps_printer()
+{
+ out.simple_comment("Trailer")
+ .put_symbol("end")
+ .simple_comment("EOF");
+ if (fseek(tempfp, 0L, 0) < 0)
+ fatal("fseek on temporary file failed");
+ fputs("%!PS-Adobe-", stdout);
+ fputs((broken_flags & USE_PS_ADOBE_2_0) ? "2.0" : "3.0", stdout);
+ putchar('\n');
+ out.set_file(stdout);
+ if (cmyk_flag)
+ out.begin_comment("Extensions:")
+ .comment_arg("CMYK")
+ .end_comment();
+ out.begin_comment("Creator:")
+ .comment_arg("groff")
+ .comment_arg("version")
+ .comment_arg(Version_string)
+ .end_comment();
+ {
+ fputs("%%CreationDate: ", out.get_file());
+#ifdef LONG_FOR_TIME_T
+ long
+#else
+ time_t
+#endif
+ t = current_time();
+ fputs(ctime(&t), out.get_file());
+ }
+ for (font_pointer_list *f = font_list; f; f = f->next) {
+ ps_font *psf = (ps_font *)(f->p);
+ rm.need_font(psf->get_internal_name());
+ }
+ rm.print_header_comments(out);
+ out.begin_comment("Pages:")
+ .comment_arg(i_to_a(pages_output))
+ .end_comment();
+ out.begin_comment("PageOrder:")
+ .comment_arg("Ascend")
+ .end_comment();
+ if (!(broken_flags & NO_PAPERSIZE)) {
+ int w = media_width();
+ int h = media_height();
+ if (w > 0 && h > 0)
+ fprintf(out.get_file(),
+ "%%%%DocumentMedia: %s %d %d %d %s %s\n",
+ media_name(), // tag name of media
+ w, // media width
+ h, // media height
+ 0, // weight in g/m2
+ "()", // paper color, e.g. white
+ "()" // preprinted form type
+ );
+ else {
+ if (h <= 0)
+ // see ps_printer::ps_printer
+ warning("bad paper height, defaulting to 11i");
+ if (w <= 0)
+ warning("bad paper width");
+ }
+ }
+ out.begin_comment("Orientation:")
+ .comment_arg(landscape_flag ? "Landscape" : "Portrait")
+ .end_comment();
+ if (ncopies != 1) {
+ out.end_line();
+ fprintf(out.get_file(), "%%%%Requirements: numcopies(%d)\n", ncopies);
+ }
+ out.simple_comment("EndComments");
+ if (!(broken_flags & NO_PAPERSIZE)) {
+ /* gv works fine without this one, but it really should be there. */
+ out.simple_comment("BeginDefaults");
+ fprintf(out.get_file(), "%%%%PageMedia: %s\n", media_name());
+ out.simple_comment("EndDefaults");
+ }
+ out.simple_comment("BeginProlog");
+ rm.output_prolog(out);
+ if (!(broken_flags & NO_SETUP_SECTION)) {
+ out.simple_comment("EndProlog");
+ out.simple_comment("BeginSetup");
+ }
+#if 1
+ /*
+ * Define paper (i.e., media) size for entire document here.
+ * This allows ps2pdf to correctly determine page size, for instance.
+ */
+ media_set();
+#endif
+ rm.document_setup(out);
+ out.put_symbol(dict_name)
+ .put_symbol("begin");
+ if (ndefs > 0)
+ ndefs += DEFS_DICT_SPARE;
+ out.put_literal_symbol(defs_dict_name)
+ .put_number(ndefs + 1)
+ .put_symbol("dict")
+ .put_symbol("def");
+ out.put_symbol(defs_dict_name)
+ .put_symbol("begin");
+ out.put_literal_symbol("u")
+ .put_delimiter('{')
+ .put_fix_number(1)
+ .put_symbol("mul")
+ .put_delimiter('}')
+ .put_symbol("bind")
+ .put_symbol("def");
+ defs += '\0';
+ out.special(defs.contents());
+ out.put_symbol("end");
+ if (ncopies != 1)
+ out.put_literal_symbol("#copies")
+ .put_number(ncopies)
+ .put_symbol("def");
+ out.put_literal_symbol("RES")
+ .put_number(res)
+ .put_symbol("def");
+ out.put_literal_symbol("PL");
+ if (guess_flag)
+ out.put_symbol("PLG");
+ else
+ out.put_fix_number(paper_length);
+ out.put_symbol("def");
+ out.put_literal_symbol("LS")
+ .put_symbol(landscape_flag ? "true" : "false")
+ .put_symbol("def");
+ if (manual_feed_flag) {
+ out.begin_comment("BeginFeature:")
+ .comment_arg("*ManualFeed")
+ .comment_arg("True")
+ .end_comment()
+ .put_symbol("MANUAL")
+ .simple_comment("EndFeature");
+ }
+ encode_fonts();
+ while (subencodings) {
+ subencoding *tem = subencodings;
+ subencodings = subencodings->next;
+ encode_subfont(tem);
+ out.put_literal_symbol(tem->subfont)
+ .put_symbol(make_subencoding_name(tem->idx))
+ .put_literal_symbol(tem->p->get_internal_name())
+ .put_symbol("RE");
+ delete tem;
+ }
+ out.simple_comment((broken_flags & NO_SETUP_SECTION)
+ ? "EndProlog"
+ : "EndSetup");
+ out.end_line();
+ out.copy_file(tempfp);
+ fclose(tempfp);
+}
+
+void ps_printer::special(char *arg, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+ typedef void (ps_printer::*SPECIAL_PROCP)(char *, const environment *);
+ static const struct {
+ const char *name;
+ SPECIAL_PROCP proc;
+ } proc_table[] = {
+ { "exec", &ps_printer::do_exec },
+ { "def", &ps_printer::do_def },
+ { "mdef", &ps_printer::do_mdef },
+ { "import", &ps_printer::do_import },
+ { "file", &ps_printer::do_file },
+ { "invis", &ps_printer::do_invis },
+ { "endinvis", &ps_printer::do_endinvis },
+ };
+ char *p;
+ for (p = arg; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *tag = p;
+ for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*p == '\0' || strncmp(tag, "ps", p - tag) != 0) {
+ error("X command without 'ps:' tag ignored");
+ return;
+ }
+ p++;
+ for (; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *command = p;
+ for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*command == '\0') {
+ error("empty X command ignored");
+ return;
+ }
+ for (unsigned int i = 0; i < sizeof(proc_table)/sizeof(proc_table[0]); i++)
+ if (strncmp(command, proc_table[i].name, p - command) == 0) {
+ flush_sbuf();
+ if (sbuf_color != *env->col)
+ set_color(env->col);
+ (this->*(proc_table[i].proc))(p, env);
+ return;
+ }
+ error("X command '%1' not recognised", command);
+}
+
+// A conforming PostScript document must not have lines longer
+// than 255 characters (excluding line termination characters).
+
+static int check_line_lengths(const char *p)
+{
+ for (;;) {
+ const char *end = strchr(p, '\n');
+ if (end == 0)
+ end = strchr(p, '\0');
+ if (end - p > 255)
+ return 0;
+ if (*end == '\0')
+ break;
+ p = end + 1;
+ }
+ return 1;
+}
+
+void ps_printer::do_exec(char *arg, const environment *env)
+{
+ while (csspace(*arg))
+ arg++;
+ if (*arg == '\0') {
+ error("missing argument to X exec command");
+ return;
+ }
+ if (!check_line_lengths(arg))
+ warning("lines in X exec command should"
+ " not be more than 255 characters long");
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("EBEGIN")
+ .special(arg)
+ .put_symbol("EEND");
+ output_hpos = output_vpos = -1;
+ output_style.f = 0;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ ndefined_styles = 0;
+ if (!ndefs)
+ ndefs = 1;
+}
+
+void ps_printer::do_file(char *arg, const environment *env)
+{
+ while (csspace(*arg))
+ arg++;
+ if (*arg == '\0') {
+ error("missing argument to X file command");
+ return;
+ }
+ const char *filename = arg;
+ do {
+ ++arg;
+ } while (*arg != '\0' && *arg != ' ' && *arg != '\n');
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("EBEGIN");
+ rm.import_file(filename, out);
+ out.put_symbol("EEND");
+ output_hpos = output_vpos = -1;
+ output_style.f = 0;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ ndefined_styles = 0;
+ if (!ndefs)
+ ndefs = 1;
+}
+
+void ps_printer::do_def(char *arg, const environment *)
+{
+ while (csspace(*arg))
+ arg++;
+ if (!check_line_lengths(arg))
+ warning("lines in X def command should"
+ " not be more than 255 characters long");
+ defs += arg;
+ if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
+ defs += '\n';
+ ndefs++;
+}
+
+// Like def, but the first argument says how many definitions it contains.
+
+void ps_printer::do_mdef(char *arg, const environment *)
+{
+ char *p;
+ int n = (int)strtol(arg, &p, 10);
+ if (n == 0 && p == arg) {
+ error("first argument to X mdef must be an integer");
+ return;
+ }
+ if (n < 0) {
+ error("out of range argument '%1' to X mdef command", int(n));
+ return;
+ }
+ arg = p;
+ while (csspace(*arg))
+ arg++;
+ if (!check_line_lengths(arg))
+ warning("lines in X mdef command should"
+ " not be more than 255 characters long");
+ defs += arg;
+ if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
+ defs += '\n';
+ ndefs += n;
+}
+
+void ps_printer::do_import(char *arg, const environment *env)
+{
+ while (*arg == ' ' || *arg == '\n')
+ arg++;
+ char *p;
+ for (p = arg; *p != '\0' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*p != '\0')
+ *p++ = '\0';
+ int parms[6];
+ int nparms = 0;
+ while (nparms < 6) {
+ char *end;
+ long n = strtol(p, &end, 10);
+ if (n == 0 && end == p)
+ break;
+ parms[nparms++] = int(n);
+ p = end;
+ }
+ if (csalpha(*p) && (p[1] == '\0' || p[1] == ' ' || p[1] == '\n')) {
+ error("scaling units not allowed in arguments for X import command");
+ return;
+ }
+ while (*p == ' ' || *p == '\n')
+ p++;
+ if (nparms < 5) {
+ if (*p == '\0')
+ error("too few arguments for X import command");
+ else
+ error("invalid argument '%1' for X import command", p);
+ return;
+ }
+ if (*p != '\0') {
+ error("superfluous argument '%1' for X import command", p);
+ return;
+ }
+ int llx = parms[0];
+ int lly = parms[1];
+ int urx = parms[2];
+ int ury = parms[3];
+ int desired_width = parms[4];
+ int desired_height = parms[5];
+ if (desired_width <= 0) {
+ error("bad width argument '%1' for X import command: must be > 0",
+ desired_width);
+ return;
+ }
+ if (nparms == 6 && desired_height <= 0) {
+ error("bad height argument '%1' for X import command: must be > 0",
+ desired_height);
+ return;
+ }
+ if (llx == urx) {
+ error("llx and urx arguments for X import command must not be equal");
+ return;
+ }
+ if (lly == ury) {
+ error("lly and ury arguments for X import command must not be equal");
+ return;
+ }
+ if (nparms == 5) {
+ int old_wid = urx - llx;
+ int old_ht = ury - lly;
+ if (old_wid < 0)
+ old_wid = -old_wid;
+ if (old_ht < 0)
+ old_ht = -old_ht;
+ desired_height = int(desired_width*(double(old_ht)/double(old_wid)) + .5);
+ }
+ if (env->vpos - desired_height < 0)
+ warning("top of imported graphic is above the top of the page");
+ out.put_number(llx)
+ .put_number(lly)
+ .put_fix_number(desired_width)
+ .put_number(urx - llx)
+ .put_fix_number(-desired_height)
+ .put_number(ury - lly)
+ .put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("PBEGIN");
+ rm.import_file(arg, out);
+ // do this here just in case application defines PEND
+ out.put_symbol("end")
+ .put_symbol("PEND");
+}
+
+void ps_printer::do_invis(char *, const environment *)
+{
+ invis_count++;
+}
+
+void ps_printer::do_endinvis(char *, const environment *)
+{
+ if (invis_count == 0)
+ error("unbalanced 'endinvis' command");
+ else
+ --invis_count;
+}
+
+printer *make_printer()
+{
+ return new ps_printer(user_paper_length);
+}
+
+static void usage(FILE *stream);
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+ program_name = argv[0];
+ string env;
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "b:c:F:gI:lmp:P:vw:", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'b':
+ // XXX check this
+ broken_flags = atoi(optarg);
+ bflag = 1;
+ break;
+ case 'c':
+ if (sscanf(optarg, "%d", &ncopies) != 1 || ncopies <= 0) {
+ error("bad number of copies '%1'", optarg);
+ ncopies = 1;
+ }
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'g':
+ guess_flag = 1;
+ break;
+ case 'I':
+ include_search_path.command_line_dir(optarg);
+ break;
+ case 'l':
+ landscape_flag = 1;
+ break;
+ case 'm':
+ manual_feed_flag = 1;
+ break;
+ case 'p':
+ if (!font::scan_papersize(optarg, 0,
+ &user_paper_length, &user_paper_width))
+ error("ignoring invalid custom paper format '%1'", optarg);
+ break;
+ case 'P':
+ env = "GROPS_PROLOGUE";
+ env += '=';
+ env += optarg;
+ env += '\0';
+ if (putenv(strsave(env.contents())))
+ fatal("putenv failed");
+ break;
+ case 'v':
+ printf("GNU grops (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'w':
+ if (sscanf(optarg, "%d", &linewidth) != 1 || linewidth < 0) {
+ error("invalid line width '%1' ignored", optarg);
+ linewidth = -1;
+ }
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ font::set_unknown_desc_command_handler(handle_unknown_desc_command);
+ SET_BINARY(fileno(stdout));
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-glm] [-b brokenness-flags] [-c num-copies]"
+" [-F font-directory] [-I inclusion-directory] [-p paper-format]"
+" [-P prologue-file] [-w rule-thickness] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+ if (stdout == stream)
+ fputs(
+"\n"
+"Translate the output of troff(1) into PostScript. See the grops(1)\n"
+"manual page.\n",
+ stream);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grops/ps.h b/src/devices/grops/ps.h
new file mode 100644
index 0000000..5cef694
--- /dev/null
+++ b/src/devices/grops/ps.h
@@ -0,0 +1,129 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class ps_output {
+public:
+ ps_output(FILE *, int max_line_length);
+ ps_output &put_string(const char *, int);
+ ps_output &put_number(int);
+ ps_output &put_fix_number(int);
+ ps_output &put_float(double);
+ ps_output &put_symbol(const char *);
+ ps_output &put_color(unsigned int);
+ ps_output &put_literal_symbol(const char *);
+ ps_output &set_fixed_point(int);
+ ps_output &simple_comment(const char *);
+ ps_output &begin_comment(const char *);
+ ps_output &comment_arg(const char *);
+ ps_output &end_comment();
+ ps_output &set_file(FILE *);
+ ps_output &include_file(FILE *);
+ ps_output &copy_file(FILE *);
+ ps_output &end_line();
+ ps_output &put_delimiter(char);
+ ps_output &special(const char *);
+ FILE *get_file();
+private:
+ FILE *fp;
+ int col;
+ int max_line_length; // not including newline
+ int need_space;
+ int fixed_point;
+};
+
+inline FILE *ps_output::get_file()
+{
+ return fp;
+}
+
+// this must stay in sync with 'resource_table' in 'psrm.cpp'
+enum resource_type {
+ RESOURCE_FONT,
+ RESOURCE_FONTSET,
+ RESOURCE_PROCSET,
+ RESOURCE_FILE,
+ RESOURCE_ENCODING,
+ RESOURCE_FORM,
+ RESOURCE_PATTERN
+};
+
+struct resource;
+
+extern string an_empty_string;
+
+class resource_manager {
+public:
+ resource_manager();
+ ~resource_manager();
+ void import_file(const char *filename, ps_output &);
+ void need_font(const char *name);
+ void print_header_comments(ps_output &);
+ void document_setup(ps_output &);
+ void output_prolog(ps_output &);
+private:
+ unsigned extensions;
+ unsigned language_level;
+ resource *procset_resource;
+ resource *resource_list;
+ resource *lookup_resource(resource_type type, string &name,
+ string &version = an_empty_string,
+ unsigned revision = 0);
+ resource *lookup_font(const char *name);
+ void read_download_file();
+ void supply_resource(resource *r, int rank, FILE *outfp,
+ int is_document = 0);
+ void process_file(int rank, FILE *fp, const char *filename, FILE *outfp);
+ resource *read_file_arg(const char **);
+ resource *read_procset_arg(const char **);
+ resource *read_font_arg(const char **);
+ resource *read_resource_arg(const char **);
+ void print_resources_comment(unsigned flag, FILE *outfp);
+ void print_extensions_comment(FILE *outfp);
+ void print_language_level_comment(FILE *outfp);
+ int do_begin_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_document(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_document(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_procset(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_procset(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_font(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_font(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_file(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_file(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int change_to_end_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_preview(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_data(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_binary(const char *ptr, int rank, FILE *fp, FILE *outfp);
+};
+
+extern unsigned broken_flags;
+
+// broken_flags is ored from these
+
+enum {
+ NO_SETUP_SECTION = 01,
+ STRIP_PERCENT_BANG = 02,
+ STRIP_STRUCTURE_COMMENTS = 04,
+ USE_PS_ADOBE_2_0 = 010,
+ NO_PAPERSIZE = 020
+};
+
+#include "searchpath.h"
+
+extern search_path include_search_path;
diff --git a/src/devices/grops/psfig.diff b/src/devices/grops/psfig.diff
new file mode 100644
index 0000000..4e0d1f9
--- /dev/null
+++ b/src/devices/grops/psfig.diff
@@ -0,0 +1,106 @@
+These are patches to makes psfig work with groff. They apply to the
+version of psfig in comp.sources.unix/Volume11. After applying them,
+psfig should be recompiled with -DGROFF. The resulting psfig will
+work only with groff, so you might want to install it under a
+different name. The output of this psfig must be processed using the
+macros in the file ../tmac/tmac.psfig. These will automatically add
+the necessary PostScript code to the prologue output by grops. Use of
+the 'global' feature in psfig will result in non-conformant PostScript
+which will fail if processed by a page reversal program. Note that
+psfig is unsupported by me (I'm not interested in hearing about psfig
+problems.) For new documents, I recommend using the PostScript
+inclusion features provided by grops.
+
+James Clark
+jjc@jclark.com
+
+*** cmds.c.~1~ Thu Feb 14 16:09:45 1991
+--- cmds.c Mon Mar 4 12:49:26 1991
+***************
+*** 245,253 ****
+--- 245,261 ----
+ (void) sprintf(x, "%.2fp", fx);
+ (void) sprintf(y, "%.2fp", fy);
+ } else if (!*x) {
++ #ifndef GROFF
+ (void) sprintf(x,"(%.2fp*%s/%.2fp)", fx, y, fy);
++ #else /* GROFF */
++ (void) sprintf(x,"(%.0fu*%s/%.0fu)", fx, y, fy);
++ #endif /* GROFF */
+ } else if (!*y) {
++ #ifndef GROFF
+ (void) sprintf(y,"(%.2fp*%s/%.2fp)", fy, x, fx);
++ #else /* GROFF */
++ (void) sprintf(y,"(%.0fu*%s/%.0fu)", fy, x, fx);
++ #endif /* GROFF */
+ }
+
+ /*
+*** troff.c.~1~ Thu Feb 14 16:09:48 1991
+--- troff.c Mon Mar 4 12:48:46 1991
+***************
+*** 26,32 ****
+--- 26,36 ----
+ }
+
+
++ #ifndef GROFF
+ char incl_file_s[] = "\\X'f%s'";
++ #else /* GROFF */
++ char incl_file_s[] = "\\X'ps: file %s'";
++ #endif /* GROFF */
+ includeFile(filenm)
+ char *filenm; {
+ printf(incl_file_s, filenm);
+***************
+*** 40,52 ****
+--- 44,64 ----
+ error("buffer overflow");
+ }
+
++ #ifndef GROFF
+ char endfig_s[] = "\\X'pendFig'";
++ #else /* GROFF */
++ char endfig_s[] = "\\X'ps: exec psfigend'";
++ #endif /* GROFF */
+ endfig() {
+ printf(endfig_s);
+ }
+
+ char startfig_s[] =
++ #ifndef GROFF
+ "\\X'p\\w@\\h@%s@@'\\X'p\\w@\\h@%s@@'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'pstartFig'";
++ #else /* GROFF */
++ "\\X'ps: exec \\w@\\h@%s@@ \\w@\\h@%s@@ %.2f %.2f %.2f %.2f psfigstart'";
++ #endif /* GROFF */
+
+ startfig(x, y, llx, lly, urx, ury)
+ char *x, *y;
+***************
+*** 57,63 ****
+--- 69,79 ----
+ }
+
+ emitDoClip() {
++ #ifndef GROFF
+ printf("\\X'pdoclip'");
++ #else /* GROFF */
++ printf("\\X'ps: exec psfigclip'");
++ #endif /* GROFF */
+ }
+
+ flushX()
+***************
+*** 116,122 ****
+--- 132,142 ----
+
+ #define isWhite(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n')
+
++ #ifndef GROFF
+ char literal_s[] = "\\X'p%s'";
++ #else /* GROFF */
++ char literal_s[] = "\\X'ps: exec %s'";
++ #endif /* GROFF */
+ emitLiteral(text)
+ char *text; {
+ static char litbuf[BUFSZ];
diff --git a/src/devices/grops/psrm.cpp b/src/devices/grops/psrm.cpp
new file mode 100644
index 0000000..3c9a8b7
--- /dev/null
+++ b/src/devices/grops/psrm.cpp
@@ -0,0 +1,1189 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#include "ps.h"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+#define GROPS_PROLOGUE "prologue"
+
+static void print_ps_string(const string &s, FILE *outfp);
+
+cset white_space("\n\r \t\f");
+string an_empty_string;
+
+char valid_input_table[256]= {
+#ifndef IS_EBCDIC_HOST
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+#else
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+#endif
+};
+
+const char *extension_table[] = {
+ "DPS",
+ "CMYK",
+ "Composite",
+ "FileSystem",
+};
+
+const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]);
+
+// this must stay in sync with 'resource_type' in 'ps.h'
+const char *resource_table[] = {
+ "font",
+ "fontset",
+ "procset",
+ "file",
+ "encoding",
+ "form",
+ "pattern",
+};
+
+const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]);
+
+static int read_uint_arg(const char **pp, unsigned *res)
+{
+ while (white_space(**pp))
+ *pp += 1;
+ if (**pp == '\0') {
+ error("missing argument");
+ return 0;
+ }
+ const char *start = *pp;
+ // XXX use strtoul
+ long n = strtol(start, (char **)pp, 10);
+ if (n == 0 && *pp == start) {
+ error("not an integer");
+ return 0;
+ }
+ if (n < 0) {
+ error("argument must not be negative");
+ return 0;
+ }
+ *res = unsigned(n);
+ return 1;
+}
+
+struct resource {
+ resource *next;
+ resource_type type;
+ string name;
+ enum { NEEDED = 01, SUPPLIED = 02, FONT_NEEDED = 04, BUSY = 010 };
+ unsigned flags;
+ string version;
+ unsigned revision;
+ char *filename;
+ int rank;
+ resource(resource_type, string &, string & = an_empty_string, unsigned = 0);
+ ~resource();
+ void print_type_and_name(FILE *outfp);
+};
+
+resource::resource(resource_type t, string &n, string &v, unsigned r)
+: next(0), type(t), flags(0), revision(r), filename(0), rank(-1)
+{
+ name.move(n);
+ version.move(v);
+ if (type == RESOURCE_FILE) {
+ if (name.search('\0') >= 0)
+ error("filename contains a character with code 0");
+ filename = name.extract();
+ }
+}
+
+resource::~resource()
+{
+ free(filename);
+}
+
+void resource::print_type_and_name(FILE *outfp)
+{
+ fputs(resource_table[type], outfp);
+ putc(' ', outfp);
+ print_ps_string(name, outfp);
+ if (type == RESOURCE_PROCSET) {
+ putc(' ', outfp);
+ print_ps_string(version, outfp);
+ fprintf(outfp, " %u", revision);
+ }
+}
+
+resource_manager::resource_manager()
+: extensions(0), language_level(0), resource_list(0)
+{
+ read_download_file();
+ string procset_name("grops");
+ extern const char *version_string;
+ extern const char *revision_string;
+ unsigned revision_uint;
+ if (!read_uint_arg(&revision_string, &revision_uint))
+ revision_uint = 0;
+ string procset_version(version_string);
+ procset_resource = lookup_resource(RESOURCE_PROCSET, procset_name,
+ procset_version, revision_uint);
+ procset_resource->flags |= resource::SUPPLIED;
+}
+
+resource_manager::~resource_manager()
+{
+ while (resource_list) {
+ resource *tem = resource_list;
+ resource_list = resource_list->next;
+ delete tem;
+ }
+}
+
+resource *resource_manager::lookup_resource(resource_type type,
+ string &name,
+ string &version,
+ unsigned revision)
+{
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->type == type
+ && r->name == name
+ && r->version == version
+ && r->revision == revision)
+ return r;
+ r = new resource(type, name, version, revision);
+ r->next = resource_list;
+ resource_list = r;
+ return r;
+}
+
+// Just a specialized version of lookup_resource().
+
+resource *resource_manager::lookup_font(const char *name)
+{
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT
+ && strlen(name) == (size_t)r->name.length()
+ && memcmp(name, r->name.contents(), r->name.length()) == 0)
+ return r;
+ string s(name);
+ r = new resource(RESOURCE_FONT, s);
+ r->next = resource_list;
+ resource_list = r;
+ return r;
+}
+
+void resource_manager::need_font(const char *name)
+{
+ lookup_font(name)->flags |= resource::FONT_NEEDED;
+}
+
+typedef resource *Presource; // Work around g++ bug.
+
+void resource_manager::document_setup(ps_output &out)
+{
+ int nranks = 0;
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->rank >= nranks)
+ nranks = r->rank + 1;
+ if (nranks > 0) {
+ // Sort resource_list in reverse order of rank.
+ Presource *head = new Presource[nranks + 1];
+ Presource **tail = new Presource *[nranks + 1];
+ int i;
+ for (i = 0; i < nranks + 1; i++) {
+ head[i] = 0;
+ tail[i] = &head[i];
+ }
+ for (r = resource_list; r; r = r->next) {
+ i = r->rank < 0 ? 0 : r->rank + 1;
+ *tail[i] = r;
+ tail[i] = &(*tail[i])->next;
+ }
+ resource_list = 0;
+ for (i = 0; i < nranks + 1; i++)
+ if (head[i]) {
+ *tail[i] = resource_list;
+ resource_list = head[i];
+ }
+ delete[] head;
+ delete[] tail;
+ // check it
+ for (r = resource_list; r; r = r->next)
+ if (r->next)
+ assert(r->rank >= r->next->rank);
+ for (r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT && r->rank >= 0)
+ supply_resource(r, -1, out.get_file());
+ }
+}
+
+void resource_manager::print_resources_comment(unsigned flag,
+ FILE *outfp)
+{
+ int continued = 0;
+ for (resource *r = resource_list; r; r = r->next)
+ if (r->flags & flag) {
+ if (continued)
+ fputs("%%+ ", outfp);
+ else {
+ fputs(flag == resource::NEEDED
+ ? "%%DocumentNeededResources: "
+ : "%%DocumentSuppliedResources: ",
+ outfp);
+ continued = 1;
+ }
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+}
+
+void resource_manager::print_header_comments(ps_output &out)
+{
+ for (resource *r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT && (r->flags & resource::FONT_NEEDED))
+ supply_resource(r, 0, 0);
+ print_resources_comment(resource::NEEDED, out.get_file());
+ print_resources_comment(resource::SUPPLIED, out.get_file());
+ print_language_level_comment(out.get_file());
+ print_extensions_comment(out.get_file());
+}
+
+void resource_manager::output_prolog(ps_output &out)
+{
+ FILE *outfp = out.get_file();
+ out.end_line();
+ char *path;
+ if (!getenv("GROPS_PROLOGUE")) {
+ string e = "GROPS_PROLOGUE";
+ e += '=';
+ e += GROPS_PROLOGUE;
+ e += '\0';
+ if (putenv(strsave(e.contents())))
+ fatal("putenv failed");
+ }
+ char *prologue = getenv("GROPS_PROLOGUE");
+ FILE *fp = font::open_file(prologue, &path);
+ if (!fp)
+ fatal("failed to open PostScript prologue '%1': %2", prologue,
+ strerror(errno));
+ fputs("%%BeginResource: ", outfp);
+ procset_resource->print_type_and_name(outfp);
+ putc('\n', outfp);
+ process_file(-1, fp, path, outfp);
+ fclose(fp);
+ free(path);
+ fputs("%%EndResource\n", outfp);
+}
+
+void resource_manager::import_file(const char *filename, ps_output &out)
+{
+ out.end_line();
+ string name(filename);
+ resource *r = lookup_resource(RESOURCE_FILE, name);
+ supply_resource(r, -1, out.get_file(), 1);
+}
+
+void resource_manager::supply_resource(resource *r, int rank,
+ FILE *outfp, int is_document)
+{
+ if (r->flags & resource::BUSY) {
+ r->name += '\0';
+ fatal("loop detected in dependency graph for %1 '%2'",
+ resource_table[r->type],
+ r->name.contents());
+ }
+ r->flags |= resource::BUSY;
+ if (rank > r->rank)
+ r->rank = rank;
+ char *path = 0 /* nullptr */;
+ FILE *fp = 0 /* nullptr */;
+ if (r->filename != 0 /* nullptr */) {
+ if (r->type == RESOURCE_FONT) {
+ fp = font::open_file(r->filename, &path);
+ if (!fp) {
+ error("failed to open PostScript resource '%1': %2",
+ r->filename, strerror(errno));
+ delete[] r->filename;
+ r->filename = 0 /* nullptr */;
+ }
+ }
+ else {
+ errno = 0;
+ fp = include_search_path.open_file_cautious(r->filename);
+ if (!fp) {
+ error("can't open '%1': %2", r->filename, strerror(errno));
+ delete[] r->filename;
+ r->filename = 0 /* nullptr */;
+ }
+ else
+ path = r->filename;
+ }
+ }
+ if (fp) {
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document) {
+ fputs("%%BeginDocument: ", outfp);
+ print_ps_string(r->name, outfp);
+ putc('\n', outfp);
+ }
+ else {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ process_file(rank, fp, path, outfp);
+ fclose(fp);
+ if (r->type == RESOURCE_FONT)
+ free(path);
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document)
+ fputs("%%EndDocument\n", outfp);
+ else
+ fputs("%%EndResource\n", outfp);
+ }
+ r->flags |= resource::SUPPLIED;
+ }
+ else {
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document) {
+ fputs("%%IncludeDocument: ", outfp);
+ print_ps_string(r->name, outfp);
+ putc('\n', outfp);
+ }
+ else {
+ fputs("%%IncludeResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ r->flags |= resource::NEEDED;
+ }
+ r->flags &= ~resource::BUSY;
+}
+
+#define PS_MAGIC "%!PS-Adobe-"
+
+static int ps_get_line(string &buf, FILE *fp)
+{
+ buf.clear();
+ int c = getc(fp);
+ if (c == EOF)
+ return 0;
+ current_lineno++;
+ while (c != '\r' && c != '\n' && c != EOF) {
+ if (!valid_input_table[c])
+ error("invalid input character code %1", int(c));
+ buf += c;
+ c = getc(fp);
+ }
+ buf += '\n';
+ buf += '\0';
+ if (c == '\r') {
+ c = getc(fp);
+ if (c != EOF && c != '\n')
+ ungetc(c, fp);
+ }
+ return 1;
+}
+
+static int read_text_arg(const char **pp, string &res)
+{
+ res.clear();
+ while (white_space(**pp))
+ *pp += 1;
+ if (**pp == '\0') {
+ error("missing argument");
+ return 0;
+ }
+ if (**pp != '(') {
+ for (; **pp != '\0' && !white_space(**pp); *pp += 1)
+ res += **pp;
+ return 1;
+ }
+ *pp += 1;
+ res.clear();
+ int level = 0;
+ for (;;) {
+ if (**pp == '\0' || **pp == '\r' || **pp == '\n') {
+ error("missing ')'");
+ return 0;
+ }
+ if (**pp == ')') {
+ if (level == 0) {
+ *pp += 1;
+ break;
+ }
+ res += **pp;
+ level--;
+ }
+ else if (**pp == '(') {
+ level++;
+ res += **pp;
+ }
+ else if (**pp == '\\') {
+ *pp += 1;
+ switch (**pp) {
+ case 'n':
+ res += '\n';
+ break;
+ case 'r':
+ res += '\n';
+ break;
+ case 't':
+ res += '\t';
+ break;
+ case 'b':
+ res += '\b';
+ break;
+ case 'f':
+ res += '\f';
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ int val = **pp - '0';
+ if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
+ *pp += 1;
+ val = val*8 + (**pp - '0');
+ if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
+ *pp += 1;
+ val = val*8 + (**pp - '0');
+ }
+ }
+ }
+ break;
+ default:
+ res += **pp;
+ break;
+ }
+ }
+ else
+ res += **pp;
+ *pp += 1;
+ }
+ return 1;
+}
+
+resource *resource_manager::read_file_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(RESOURCE_FILE, arg);
+}
+
+resource *resource_manager::read_font_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(RESOURCE_FONT, arg);
+}
+
+resource *resource_manager::read_procset_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ string version;
+ if (!read_text_arg(ptr, version))
+ return 0;
+ unsigned revision;
+ if (!read_uint_arg(ptr, &revision))
+ return 0;
+ return lookup_resource(RESOURCE_PROCSET, arg, version, revision);
+}
+
+resource *resource_manager::read_resource_arg(const char **ptr)
+{
+ while (white_space(**ptr))
+ *ptr += 1;
+ const char *name = *ptr;
+ while (**ptr != '\0' && !white_space(**ptr))
+ *ptr += 1;
+ if (name == *ptr) {
+ error("missing resource type");
+ return 0;
+ }
+ int ri;
+ for (ri = 0; ri < NRESOURCES; ri++)
+ if (strlen(resource_table[ri]) == size_t(*ptr - name)
+ && strncasecmp(resource_table[ri], name, *ptr - name) == 0)
+ break;
+ if (ri >= NRESOURCES) {
+ error("unknown resource type");
+ return 0;
+ }
+ if (ri == RESOURCE_PROCSET)
+ return read_procset_arg(ptr);
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(resource_type(ri), arg);
+}
+
+static const char *matches_comment(string &buf, const char *comment)
+{
+ if ((size_t)buf.length() < strlen(comment) + 3)
+ return 0;
+ if (buf[0] != '%' || buf[1] != '%')
+ return 0;
+ const char *bufp = buf.contents() + 2;
+ for (; *comment; comment++, bufp++)
+ if (*bufp != *comment)
+ return 0;
+ if (comment[-1] == ':')
+ return bufp;
+ if (*bufp == '\0' || white_space(*bufp))
+ return bufp;
+ return 0;
+}
+
+// Return 1 if the line should be copied out.
+
+int resource_manager::do_begin_resource(const char *ptr, int, FILE *,
+ FILE *)
+{
+ resource *r = read_resource_arg(&ptr);
+ if (r)
+ r->flags |= resource::SUPPLIED;
+ return 1;
+}
+
+int resource_manager::do_include_resource(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_resource_arg(&ptr);
+ if (r) {
+ if (r->type == RESOURCE_FONT) {
+ if (rank >= 0)
+ supply_resource(r, rank + 1, outfp);
+ else
+ r->flags |= resource::FONT_NEEDED;
+ }
+ else
+ supply_resource(r, rank, outfp);
+ }
+ return 0;
+}
+
+int resource_manager::do_begin_document(const char *ptr, int, FILE *,
+ FILE *)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ r->flags |= resource::SUPPLIED;
+ return 1;
+}
+
+int resource_manager::do_include_document(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp, 1);
+ return 0;
+}
+
+int resource_manager::do_begin_procset(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_procset_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_procset(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_procset_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_file(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_file(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_font(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_font_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_font(const char *ptr, int rank, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_font_arg(&ptr);
+ if (r) {
+ if (rank >= 0)
+ supply_resource(r, rank + 1, outfp);
+ else
+ r->flags |= resource::FONT_NEEDED;
+ }
+ return 0;
+}
+
+int resource_manager::change_to_end_resource(const char *, int, FILE *,
+ FILE *outfp)
+{
+ if (outfp)
+ fputs("%%EndResource\n", outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_preview(const char *, int, FILE *fp,
+ FILE *)
+{
+ string buf;
+ do {
+ if (!ps_get_line(buf, fp)) {
+ error("end of file in preview section");
+ break;
+ }
+ } while (!matches_comment(buf, "EndPreview"));
+ return 0;
+}
+
+int read_one_of(const char **ptr, const char **s, int n)
+{
+ while (white_space(**ptr))
+ *ptr += 1;
+ if (**ptr == '\0')
+ return -1;
+ const char *start = *ptr;
+ do {
+ ++(*ptr);
+ } while (**ptr != '\0' && !white_space(**ptr));
+ for (int i = 0; i < n; i++)
+ if (strlen(s[i]) == size_t(*ptr - start)
+ && memcmp(s[i], start, *ptr - start) == 0)
+ return i;
+ return -1;
+}
+
+void skip_possible_newline(FILE *fp, FILE *outfp)
+{
+ int c = getc(fp);
+ if (c == '\r') {
+ current_lineno++;
+ if (outfp)
+ putc(c, outfp);
+ int cc = getc(fp);
+ if (cc != '\n') {
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else {
+ if (outfp)
+ putc(cc, outfp);
+ }
+ }
+ else if (c == '\n') {
+ current_lineno++;
+ if (outfp)
+ putc(c, outfp);
+ }
+ else if (c != EOF)
+ ungetc(c, fp);
+}
+
+int resource_manager::do_begin_data(const char *ptr, int, FILE *fp,
+ FILE *outfp)
+{
+ while (white_space(*ptr))
+ ptr++;
+ const char *start = ptr;
+ unsigned numberof;
+ if (!read_uint_arg(&ptr, &numberof))
+ return 0;
+ static const char *types[] = { "Binary", "Hex", "ASCII" };
+ const int Binary = 0;
+ int type = 0;
+ static const char *units[] = { "Bytes", "Lines" };
+ const int Bytes = 0;
+ int unit = Bytes;
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr != '\0') {
+ type = read_one_of(&ptr, types, 3);
+ if (type < 0) {
+ error("bad data type");
+ return 0;
+ }
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr != '\0') {
+ unit = read_one_of(&ptr, units, 2);
+ if (unit < 0) {
+ error("expected 'Bytes' or 'Lines'");
+ return 0;
+ }
+ }
+ }
+ if (type != Binary)
+ return 1;
+ if (outfp) {
+ fputs("%%BeginData: ", outfp);
+ fputs(start, outfp);
+ }
+ if (numberof > 0) {
+ unsigned bytecount = 0;
+ unsigned linecount = 0;
+ do {
+ int c = getc(fp);
+ if (c == EOF) {
+ error("end of file within data section");
+ return 0;
+ }
+ if (outfp)
+ putc(c, outfp);
+ bytecount++;
+ if (c == '\r') {
+ int cc = getc(fp);
+ if (cc != '\n') {
+ linecount++;
+ current_lineno++;
+ }
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else if (c == '\n') {
+ linecount++;
+ current_lineno++;
+ }
+ } while ((unit == Bytes ? bytecount : linecount) < numberof);
+ }
+ skip_possible_newline(fp, outfp);
+ string buf;
+ if (!ps_get_line(buf, fp)) {
+ error("missing %%%%EndData line");
+ return 0;
+ }
+ if (!matches_comment(buf, "EndData"))
+ error("bad %%%%EndData line");
+ if (outfp)
+ fputs(buf.contents(), outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_binary(const char *ptr, int, FILE *fp,
+ FILE *outfp)
+{
+ if (!outfp)
+ return 0;
+ unsigned count;
+ if (!read_uint_arg(&ptr, &count))
+ return 0;
+ if (outfp)
+ fprintf(outfp, "%%%%BeginData: %u Binary Bytes\n", count);
+ while (count != 0) {
+ int c = getc(fp);
+ if (c == EOF) {
+ error("end of file within binary section");
+ return 0;
+ }
+ if (outfp)
+ putc(c, outfp);
+ --count;
+ if (c == '\r') {
+ int cc = getc(fp);
+ if (cc != '\n')
+ current_lineno++;
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else if (c == '\n')
+ current_lineno++;
+ }
+ skip_possible_newline(fp, outfp);
+ string buf;
+ if (!ps_get_line(buf, fp)) {
+ error("missing %%%%EndBinary line");
+ return 0;
+ }
+ if (!matches_comment(buf, "EndBinary")) {
+ error("bad %%%%EndBinary line");
+ if (outfp)
+ fputs(buf.contents(), outfp);
+ }
+ else if (outfp)
+ fputs("%%EndData\n", outfp);
+ return 0;
+}
+
+static unsigned parse_extensions(const char *ptr)
+{
+ unsigned flags = 0;
+ for (;;) {
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr == '\0')
+ break;
+ const char *name = ptr;
+ do {
+ ++ptr;
+ } while (*ptr != '\0' && !white_space(*ptr));
+ int i;
+ for (i = 0; i < NEXTENSIONS; i++)
+ if (strlen(extension_table[i]) == size_t(ptr - name)
+ && memcmp(extension_table[i], name, ptr - name) == 0) {
+ flags |= (1 << i);
+ break;
+ }
+ if (i >= NEXTENSIONS) {
+ string s(name, ptr - name);
+ s += '\0';
+ error("unknown extension '%1'", s.contents());
+ }
+ }
+ return flags;
+}
+
+// XXX if it has not been surrounded with {Begin,End}Document need to
+// strip out Page: Trailer {Begin,End}Prolog {Begin,End}Setup sections.
+
+// XXX Perhaps the decision whether to use BeginDocument or
+// BeginResource: file should be postponed till we have seen
+// the first line of the file.
+
+void resource_manager::process_file(int rank, FILE *fp,
+ const char *filename, FILE *outfp)
+{
+ // If none of these comments appear in the header section, and we are
+ // just analyzing the file (i.e., outfp is 0), then we can return
+ // immediately.
+ static const char *header_comment_table[] = {
+ "DocumentNeededResources:",
+ "DocumentSuppliedResources:",
+ "DocumentNeededFonts:",
+ "DocumentSuppliedFonts:",
+ "DocumentNeededProcSets:",
+ "DocumentSuppliedProcSets:",
+ "DocumentNeededFiles:",
+ "DocumentSuppliedFiles:",
+ };
+
+ const int NHEADER_COMMENTS = sizeof(header_comment_table)
+ / sizeof(header_comment_table[0]);
+ struct comment_info {
+ const char *name;
+ int (resource_manager::*proc)(const char *, int, FILE *, FILE *);
+ };
+
+ static const comment_info comment_table[] = {
+ { "BeginResource:", &resource_manager::do_begin_resource },
+ { "IncludeResource:", &resource_manager::do_include_resource },
+ { "BeginDocument:", &resource_manager::do_begin_document },
+ { "IncludeDocument:", &resource_manager::do_include_document },
+ { "BeginProcSet:", &resource_manager::do_begin_procset },
+ { "IncludeProcSet:", &resource_manager::do_include_procset },
+ { "BeginFont:", &resource_manager::do_begin_font },
+ { "IncludeFont:", &resource_manager::do_include_font },
+ { "BeginFile:", &resource_manager::do_begin_file },
+ { "IncludeFile:", &resource_manager::do_include_file },
+ { "EndProcSet", &resource_manager::change_to_end_resource },
+ { "EndFont", &resource_manager::change_to_end_resource },
+ { "EndFile", &resource_manager::change_to_end_resource },
+ { "BeginPreview:", &resource_manager::do_begin_preview },
+ { "BeginData:", &resource_manager::do_begin_data },
+ { "BeginBinary:", &resource_manager::do_begin_binary },
+ };
+
+ const int NCOMMENTS = sizeof(comment_table)/sizeof(comment_table[0]);
+ string buf;
+ int saved_lineno = current_lineno;
+ const char *saved_filename = current_filename;
+ current_filename = filename;
+ current_lineno = 0;
+ if (!ps_get_line(buf, fp)) {
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+ return;
+ }
+ if ((size_t)buf.length() < sizeof(PS_MAGIC)
+ || memcmp(buf.contents(), PS_MAGIC, sizeof(PS_MAGIC) - 1) != 0) {
+ if (outfp) {
+ do {
+ if (!(broken_flags & STRIP_PERCENT_BANG)
+ || buf[0] != '%' || buf[1] != '!')
+ fputs(buf.contents(), outfp);
+ } while (ps_get_line(buf, fp));
+ }
+ }
+ else {
+ if (!(broken_flags & STRIP_PERCENT_BANG) && outfp)
+ fputs(buf.contents(), outfp);
+ int in_header = 1;
+ int interesting = 0;
+ int had_extensions_comment = 0;
+ int had_language_level_comment = 0;
+ for (;;) {
+ if (!ps_get_line(buf, fp))
+ break;
+ int copy_this_line = 1;
+ if (buf[0] == '%') {
+ if (buf[1] == '%') {
+ const char *ptr;
+ int i;
+ for (i = 0; i < NCOMMENTS; i++)
+ if ((ptr = matches_comment(buf, comment_table[i].name))) {
+ copy_this_line
+ = (this->*(comment_table[i].proc))(ptr, rank, fp, outfp);
+ break;
+ }
+ if (i >= NCOMMENTS && in_header) {
+ if ((ptr = matches_comment(buf, "EndComments")))
+ in_header = 0;
+ else if (!had_extensions_comment
+ && (ptr = matches_comment(buf, "Extensions:"))) {
+ extensions |= parse_extensions(ptr);
+ // XXX handle possibility that next line is %%+
+ had_extensions_comment = 1;
+ }
+ else if (!had_language_level_comment
+ && (ptr = matches_comment(buf, "LanguageLevel:"))) {
+ unsigned ll;
+ if (read_uint_arg(&ptr, &ll) && ll > language_level)
+ language_level = ll;
+ had_language_level_comment = 1;
+ }
+ else {
+ for (i = 0; i < NHEADER_COMMENTS; i++)
+ if (matches_comment(buf, header_comment_table[i])) {
+ interesting = 1;
+ break;
+ }
+ }
+ }
+ if ((broken_flags & STRIP_STRUCTURE_COMMENTS)
+ && (matches_comment(buf, "EndProlog")
+ || matches_comment(buf, "Page:")
+ || matches_comment(buf, "Trailer")))
+ copy_this_line = 0;
+ }
+ else if (buf[1] == '!') {
+ if (broken_flags & STRIP_PERCENT_BANG)
+ copy_this_line = 0;
+ }
+ }
+ else
+ in_header = 0;
+ if (!outfp && !in_header && !interesting)
+ break;
+ if (copy_this_line && outfp)
+ fputs(buf.contents(), outfp);
+ }
+ }
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+}
+
+void resource_manager::read_download_file()
+{
+ char *path = 0 /* nullptr */;
+ FILE *fp = font::open_file("download", &path);
+ if (0 /* nullptr */ == fp)
+ fatal("failed to open 'download' file: %1", strerror(errno));
+ char buf[512];
+ int lineno = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ lineno++;
+ char *p = strtok(buf, " \t\r\n");
+ if (p == 0 /* nullptr */ || *p == '#')
+ continue;
+ char *q = strtok(0 /* nullptr */, " \t\r\n");
+ if (!q)
+ fatal_with_file_and_line(path, lineno, "file name missing for"
+ " font '%1'", p);
+ lookup_font(p)->filename = strsave(q);
+ }
+ free(path);
+ fclose(fp);
+}
+
+// XXX Can we share some code with ps_output::put_string()?
+
+static void print_ps_string(const string &s, FILE *outfp)
+{
+ int len = s.length();
+ const char *str = s.contents();
+ int funny = 0;
+ if (str[0] == '(')
+ funny = 1;
+ else {
+ for (int i = 0; i < len; i++)
+ if (str[i] <= 040 || str[i] > 0176) {
+ funny = 1;
+ break;
+ }
+ }
+ if (!funny) {
+ put_string(s, outfp);
+ return;
+ }
+ int level = 0;
+ int i;
+ for (i = 0; i < len; i++)
+ if (str[i] == '(')
+ level++;
+ else if (str[i] == ')' && --level < 0)
+ break;
+ putc('(', outfp);
+ for (i = 0; i < len; i++)
+ switch (str[i]) {
+ case '(':
+ case ')':
+ if (level != 0)
+ putc('\\', outfp);
+ putc(str[i], outfp);
+ break;
+ case '\\':
+ fputs("\\\\", outfp);
+ break;
+ case '\n':
+ fputs("\\n", outfp);
+ break;
+ case '\r':
+ fputs("\\r", outfp);
+ break;
+ case '\t':
+ fputs("\\t", outfp);
+ break;
+ case '\b':
+ fputs("\\b", outfp);
+ break;
+ case '\f':
+ fputs("\\f", outfp);
+ break;
+ default:
+ if (str[i] < 040 || str[i] > 0176)
+ fprintf(outfp, "\\%03o", str[i] & 0377);
+ else
+ putc(str[i], outfp);
+ break;
+ }
+ putc(')', outfp);
+}
+
+void resource_manager::print_extensions_comment(FILE *outfp)
+{
+ if (extensions) {
+ fputs("%%Extensions:", outfp);
+ for (int i = 0; i < NEXTENSIONS; i++)
+ if (extensions & (1 << i)) {
+ putc(' ', outfp);
+ fputs(extension_table[i], outfp);
+ }
+ putc('\n', outfp);
+ }
+}
+
+void resource_manager::print_language_level_comment(FILE *outfp)
+{
+ if (language_level)
+ fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grotty/TODO b/src/devices/grotty/TODO
new file mode 100644
index 0000000..3f23dc3
--- /dev/null
+++ b/src/devices/grotty/TODO
@@ -0,0 +1,3 @@
+Document font and device description file usage of grotty.
+
+With -h avoid using a tab when a single space will do.
diff --git a/src/devices/grotty/grotty.1.man b/src/devices/grotty/grotty.1.man
new file mode 100644
index 0000000..3dcafae
--- /dev/null
+++ b/src/devices/grotty/grotty.1.man
@@ -0,0 +1,810 @@
+.TH grotty @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grotty \-
+.I groff
+output driver for typewriter-like (terminal) devices
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grotty_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grotty
+.RB [ \-dfho ]
+.RB [ \-i | \-r ]
+.RB [ \-F\~\c
+.IR dir ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY "grotty \-c"
+.RB [ \-bBdfhouU ]
+.RB [ \-F\~\c
+.IR dir ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grotty
+.B \-\-help
+.YS
+.
+.
+.SY grotty
+.B \-v
+.
+.SY grotty
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+TTY
+(\[lq]Teletype\[rq])
+output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into a form suitable for typewriter-like devices,
+including terminal emulators.
+.
+Normally,
+.I grotty
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given one of the
+.RB \[lq] \-T\~ascii \[rq],
+.RB \[lq] \-T\~latin1 \[rq],
+.BR \-Tlatin1 ,
+or
+.RB \[lq] \-T\~utf8 \[rq]
+options on systems using ISO character encoding standards,
+or with
+.RB \[lq] \-T\~cp1047 \[rq]
+or
+.RB \[lq] \-T\~utf8 \[rq]
+on EBCDIC-based hosts.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grotty .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grotty
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+By default,
+.I grotty
+emits SGR escape sequences
+(from ISO\~6429,
+popularly called \[lq]ANSI escapes\[rq])
+to change text attributes
+(bold,
+italic,
+underline,
+reverse video
+.\" ECMA-48, 2nd edition (1979) calls it "negative image".
+[\[lq]negative image\[rq]]
+and colors).
+.
+Devices supporting the appropriate sequences can view
+.I roff
+documents using eight different background and foreground colors.
+.
+Following ISO\~6429,
+the following colors are defined in
+.IR tty.tmac :
+black,
+white,
+red,
+green,
+blue,
+yellow,
+magenta,
+and cyan.
+.
+Unrecognized colors are mapped to the default color,
+which is dependent on the settings of the terminal.
+.
+OSC\~8 hyperlinks are produced for these devices.
+.
+.
+.P
+In keeping with long-standing practice and the rarity of terminals
+(and emulators)
+that support oblique or italic fonts,
+italicized text is represented with underlining by default\[em]but see
+the
+.B \-i
+option below.
+.
+.
+.\" ====================================================================
+.SS "SGR and OSC support in pagers"
+.\" ====================================================================
+.
+When paging
+.IR grotty 's
+output with
+.MR less 1 ,
+the latter program must be instructed to pass SGR and OSC sequences
+through to the device;
+its
+.B \-R
+option is one way to achieve this
+.RI ( less
+version 566 or later is required for OSC\~8 support).
+.
+Consequently,
+programs like
+.MR man 1
+that page
+.I roff
+documents with
+.I less
+must call it with an appropriate option.
+.
+.
+.\" ====================================================================
+.SS "Legacy output format"
+.\" ====================================================================
+.
+The
+.B \-c
+option tells
+.I grotty
+to use an output format compatible with paper terminals,
+like the Teletype machines for which
+.I roff
+and
+.I nroff
+were first developed but which are no longer in wide use.
+.
+SGR escape sequences are not emitted;
+bold,
+italic,
+and underlining character attributes are thus not manipulated.
+.
+Instead,
+.I grotty
+overstrikes,
+representing a bold character
+.I c
+with the sequence
+.RI \[lq] c\~\c
+BACKSPACE\~\c
+.IR c \[rq],
+an italic character
+.I c
+with the sequence
+.RB \[lq] _\~\c
+BACKSPACE\~\c
+.IR c \[rq],
+and bold italics with
+.RB \[lq] _\~\c
+BACKSPACE\~\c
+.I c
+BACKSPACE\~\c
+.IR c \[rq].
+.
+This rendering is inherently ambiguous when the character
+.I c
+is itself the underscore.
+.
+.
+.P
+The legacy output format can be rendered on a video terminal
+(or emulator)
+by piping
+.IR grotty 's
+output through
+.MR ul 1 ,
+.\" from bsdmainutils 11.1.2+b1 (on Debian Buster)
+which may render bold italics as reverse video.
+.
+.\" 'more' from util-linux 2.33.1 (on Debian Buster) neither renders
+.\" double-struck characters as bold nor supports -b, but does render
+.\" SGR sequences (including color) with no flags required.
+Some implementations of
+.MR more 1
+are also able to display these sequences;
+you may wish to experiment with that command's
+.B \-b
+option.
+.
+.\" Version 487 of...
+.I less
+renders legacy bold and italics without requiring options.
+.
+In contrast to the terminal output drivers of some other
+.I roff
+implementations,
+.I grotty
+never outputs reverse line feeds.
+.
+There is therefore no need to filter its output through
+.MR col 1 .
+.
+.
+.\" ====================================================================
+.SS "Device control commands"
+.\" ====================================================================
+.
+.I grotty
+understands one device control function produced by the
+.I roff
+.B \[rs]X
+escape sequence in a document.
+.
+.
+.TP
+.BR "\[rs]X\[aq]tty: link " [\c
+.IR uri \~[ key\c
+.BI = value\c
+] \|.\|.\|.\|]\c
+.B \[aq]
+.
+Embed a hyperlink using the OSC 8 terminal escape sequence.
+.
+Specifying
+.I uri
+starts hyperlinked text,
+and omitting it ends the hyperlink.
+.
+When
+.I uri
+is present,
+any number of additional key/value pairs can be specified;
+their interpretation is the responsibility of the pager or terminal.
+.
+Spaces or tabs cannot appear literally in
+.IR uri ,
+.IR key ,
+or
+.IR value ;
+they must be represented in an alternate form.
+.
+.
+.\" ====================================================================
+.SS "Device description files"
+.\" ====================================================================
+.
+If the
+.I DESC
+file for the character encoding contains the
+.RB \[lq] unicode \[rq]
+directive,
+.I grotty
+emits Unicode characters in UTF-8 encoding.
+.
+Otherwise,
+it emits characters in a single-byte encoding depending on the data in
+the font description files.
+.
+See
+.MR groff_font @MAN5EXT@ .
+.
+.
+.P
+A font description file may contain a directive
+.RB \[lq] internalname\~\c
+.IR n \[rq]
+where
+.I n
+is a decimal integer.
+.
+If the 01 bit in
+.I n
+is set,
+then the font is treated as an italic font;
+if the 02 bit is set,
+then it is treated as a bold font.
+.
+.\" The following seems to say nothing that is not true of font
+.\" description files in general; if so, it belongs in groff_font(5).
+.\"The code field in the font description field gives the code which is
+.\"used to output the character.
+.\".
+.\"This code can also be used in the
+.\".I groff
+.\".B \[rs]N
+.\"escape sequence in a document.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I grotty
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Because the output driver operates in
+.I nroff
+mode,
+attempts to set or change the font family or type size are ignored.
+.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-b
+Suppress the use of overstriking for bold characters in legacy output
+format.
+.
+.
+.TP
+.B \-B
+Use only overstriking for bold-italic characters in legacy output
+format.
+.
+.
+.TP
+.B \-c
+Use
+.IR grotty 's
+legacy output format
+(see subsection \[lq]Legacy output format\[rq] above).
+.
+SGR and OSC escape sequences are not emitted.
+.
+.
+.TP
+.B \-d
+Ignore all
+.B \[rs]D
+drawing escape sequences in the input.
+.
+By default,
+.I grotty
+renders
+.BR \[rs]D\[aq]l \|.\|.\|.\& \[aq]
+escape sequences that have at least one zero argument
+(and so are either horizontal or vertical)
+using Unicode box drawing characters
+(for the
+.B utf8
+device)
+or the
+.BR \- ,
+.BR | ,
+and
+.B +
+characters
+(for all other devices).
+.
+.I grotty
+handles
+.BR \[rs]D\[aq]p \|.\|.\|.\& \[aq]
+escape sequences that consist entirely of horizontal and vertical
+lines similarly.
+.
+.
+.TP
+.B \-f
+Emit a form feed at the end of each page having no output on its last
+line.
+.
+.
+.TP
+.BI \-F\~ dir
+Prepend directory
+.RI dir /dev name
+to the search path for font and device description files;
+.I name
+describes the output device's character encoding,
+one of
+.BR ascii ,
+.BR latin1 ,
+.BR utf8 ,
+or
+.BR cp1047 .
+.
+.
+.TP
+.B \-h
+Use literal horizontal tab characters in the output.
+.
+Tabs are assumed to be set every 8 columns.
+.
+.
+.TP
+.B \-i
+Render oblique-styled fonts
+.RB ( I
+and
+.BR BI )
+with the SGR attribute for italic text
+rather than underlined text.
+.
+Many terminals don't support this attribute;
+however,
+.MR xterm 1 ,
+since patch\~#314 (2014-12-28),
+does.
+.
+Ignored if
+.B \-c
+is also specified.
+.
+.
+.TP
+.B \-o
+Suppress overstriking
+(other than for bold and/or underlined characters when the legacy output
+format is in use).
+.
+.
+.TP
+.B \-r
+Render oblique-styled fonts
+.RB ( I
+and
+.BR BI )
+with the SGR attribute for reverse video text
+rather than underlined text.
+.
+Ignored if
+.B \-c
+or
+.B \-i
+is also specified.
+.
+.
+.TP
+.B \-u
+Suppress the use of underlining for italic characters in legacy output
+format.
+.
+.
+.TP
+.B \-U
+Use only underlining for bold-italic characters in legacy output format.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROFF_NO_SGR
+If set,
+.IR grotty 's
+legacy output format is used just as if the
+.B \-c
+option were specified;
+see subsection \[lq]Legacy output format\[rq] above.
+.
+.
+.br
+.ne 3v \" Keep section heading and paragraph tag together.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devascii/\:DESC
+describes the
+.B ascii
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devascii/ F
+describes the font known
+.RI as\~ F
+on device
+.BR ascii .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devcp1047/\:DESC
+describes the
+.B cp1047
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devcp1047/ F
+describes the font known
+.RI as\~ F
+on device
+.BR cp1047 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devlatin1/\:DESC
+describes the
+.B latin1
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devlatin1/ F
+describes the font known
+.RI as\~ F
+on device
+.BR latin1 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devutf8/\:DESC
+describes the
+.B utf8
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devutf8/ F
+describes the font known
+.RI as\~ F
+on device
+.BR utf8 .
+.
+.
+.TP
+.I @MACRODIR@/\:tty\:.tmac
+defines macros for use with the
+.BR ascii ,
+.BR cp1047 ,
+.BR latin1 ,
+and
+.B utf8
+output devices.
+.
+It is automatically loaded by
+.I troffrc
+when any of those output devices is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:tty\-char\:.tmac
+defines fallback characters for use with
+.I grotty.
+.
+See
+.MR nroff @MAN1EXT@ .
+.
+.
+.\" XXX: The following no longer seems to be true; an inspection of the
+.\" font/*/dev*.am files suggests no evidence of it, at any rate.
+.\".P
+.\"Note that on EBCDIC hosts,
+.\"only files for the
+.\".B cp1047
+.\"device are installed.
+.
+.
+.\" ====================================================================
+.SH Limitations
+.\" ====================================================================
+.
+.I grotty
+is intended only for simple documents.
+.
+.
+.IP \[bu] 2n
+There is no support for fractional horizontal or vertical motions.
+.
+.
+.IP \[bu]
+.I roff
+.B \[rs]D
+escape sequences producing anything other than horizontal and vertical
+lines are not supported.
+.
+.
+.IP \[bu]
+Characters above the first line
+(that is,
+with a vertical drawing position of\~0)
+cannot be rendered.
+.
+.
+.IP \[bu]
+Color handling differs from other output drivers.
+.
+The
+.I groff
+requests and escape sequences that set the stroke and fill colors
+instead set the foreground and background character cell colors,
+respectively.
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+The following
+.I groff
+document exercises several features for which output device support
+varies:
+(1)\~bold style;
+(2)\~italic (underline) style;
+(3)\~bold-italic style;
+(4)\~character composition by overstriking (\[lq]co\[:o]perate\[rq]);
+(5)\~foreground color;
+(6)\~background color;
+and
+(7)\~horizontal and vertical line-drawing.
+.
+.
+.P
+.RS
+.EX
+You might see \ef[B]bold\ef[] and \ef[I]italic\ef[].
+Some people see \ef[BI]both\ef[].
+If the output device does (not) co\ez\e[ad]operate,
+you might see \em[red]red\em[].
+Black on cyan can have a \eM[cyan]\em[black]prominent\em[]\eM[]
+\eD\[aq]l 1i 0\[aq]\eD\[aq]l 0 2i\[aq]\eD\[aq]l 1i 0\[aq] look.
+\&.\e" If in nroff mode, end page now.
+\&.if n .pl \en[nl]u
+.EE
+.RE
+.
+.
+.P
+Given the foregoing input,
+compare and contrast the output of the following.
+.
+.
+.P
+.RS
+.EX
+$ \c
+.B groff \-T ascii \c
+.I file
+$ \c
+.B groff \-T utf8 \-P \-i \c
+.I file
+$ \c
+.B groff \-T utf8 \-P \-c \c
+.I file \c
+.B | ul
+.EE
+.RE
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Control Functions for Coded Character Sets\[rq]
+(ECMA-48)
+5th\~edition,
+Ecma International,
+June\~1991.
+.
+A gratis version of ISO\~6429,
+this document includes a normative description of SGR escape sequences.
+.
+Available at
+.UR http://\:www\:.ecma\-international\:.org/\:publications/\:files/\:\
+ECMA\-ST/\:Ecma\-048\:.pdf
+.UE .
+.
+.
+.P
+.UR https://\:gist\:.github\:.com/\:egmontkob/\:\
+eb114294efbcd5ad\:b1944c9f3cb5feda
+\[lq]Hyperlinks in Terminal Emulators\[rq]
+.UE ,
+Egmont Koblinger.
+.
+.
+.P
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@ ,
+.MR ul 1 ,
+.MR more 1 ,
+.MR less 1 ,
+.MR man 1
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grotty_1_man_C]
+.do rr *groff_grotty_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grotty/grotty.am b/src/devices/grotty/grotty.am
new file mode 100644
index 0000000..14921c5
--- /dev/null
+++ b/src/devices/grotty/grotty.am
@@ -0,0 +1,39 @@
+# Copyright (C) 2014-2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grotty
+grotty_SOURCES = src/devices/grotty/tty.cpp
+grotty_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grotty/grotty.1
+EXTRA_DIST += \
+ src/devices/grotty/grotty.1.man \
+ src/devices/grotty/TODO
+
+grotty_TESTS = \
+ src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh \
+ src/devices/grotty/tests/osc8_works.sh
+TESTS += $(grotty_TESTS)
+EXTRA_DIST += $(grotty_TESTS)
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh b/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh
new file mode 100755
index 0000000..92552d2
--- /dev/null
+++ b/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh
@@ -0,0 +1,205 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+grotty="${abs_top_builddir:-.}/grotty"
+
+fail=
+
+wail () {
+ printf "FAILED " >&2
+ fail=YES
+}
+
+# Ensure that characters are mapped to glyphs normatively.
+
+input='x T @DEVICE@
+x res 240 24 40
+x init
+p1
+x font 1 R
+f1
+s10
+V40
+H0
+md
+DFd
+t!#$%&()*+,./0123456789:;<=>?@
+n40 0
+V80
+H0
+tABCDEFGHIJKLMNOPQRSTUVWXYZ[]_
+n40 0
+V120
+H0
+tabcdefghijklmnopqrstuvwxyz{|}
+n40 0
+V160
+H0
+tneutral
+wh24
+tdouble
+wh24
+tquote:
+wh24
+Cdq
+h24
+n40 0
+V200
+H0
+tclosing
+wh24
+tsingle
+wh24
+tquote:
+wh24
+t'"'"'
+n40 0
+V240
+H0
+thyphen:
+wh24
+t-
+n40 0
+V280
+H0
+tbackslash:
+wh24
+Crs
+h24
+n40 0
+V320
+H0
+tmodifier
+wh24
+tcircumflex:
+wh24
+t^
+n40 0
+V360
+H0
+topening
+wh24
+tsingle
+wh24
+tquote:
+wh24
+t`
+n40 0
+V400
+H0
+tmodifier
+wh24
+ttilde:
+wh24
+t~
+n40 0
+x trailer
+V2640
+x stop'
+
+# TODO: Test cp1047 when we have access to a host environment using it.
+
+for D in ascii latin1 utf8
+do
+ if [ "$D" = "utf8" ]
+ then
+ # We can't test UTF-8 if the environment doesn't support it.
+ if [ "$(locale charmap)" != UTF-8 ]
+ then
+ # If we've already seen a failure case, report it.
+ if [ -n "$fail" ]
+ then
+ exit 1 # fail
+ else
+ exit 77 # skip
+ fi
+ fi
+ fi
+
+ printf 'checking "%s" output device...' $D >&2
+ output=$(echo "$input" | sed s/@DEVICE@/$D/ \
+ | "$grotty" -F font -F build/font)
+ printf 'group1 ' >&2
+ echo "$output" | grep -Fqx '!#$%&()*+,./0123456789:;<=>?@' || wail
+ printf 'group2 ' >&2
+ echo "$output" | grep -Fqx 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_' || wail
+ printf 'group3 ' >&2
+ echo "$output" | grep -Fqx 'abcdefghijklmnopqrstuvwxyz{|}' || wail
+ printf '" ' >&2
+ echo "$output" | grep -Fqx 'neutral double quote: "' || wail
+ printf '\\ ' >&2
+ echo "$output" | grep -Fqx 'backslash: \' || wail
+ case $D in
+ (utf8)
+# Expected:
+#0000000 ! # $ % & ( ) * + , . / 0 1 2 3
+#0000020 4 5 6 7 8 9 : ; < = > ? @ \n A B
+#0000040 C D E F G H I J K L M N O P Q R
+#0000060 S T U V W X Y Z [ ] _ \n a b c d
+#0000100 e f g h i j k l m n o p q r s t
+#0000120 u v w x y z { | } \n n e u t r a
+#0000140 l d o u b l e q u o t e :
+#0000160 " \n c l o s i n g s i n g l e
+#0000200 q u o t e : 342 200 231 \n h y p h
+#0000220 e n : 342 200 220 \n b a c k s l a s
+#0000240 h : \ \n m o d i f i e r c i
+#0000260 r c u m f l e x : 313 206 \n o p e
+#0000300 n i n g s i n g l e q u o t
+#0000320 e : 342 200 230 \n m o d i f i e r
+#0000340 t i l d e : 313 234 \n
+#0000352
+ output_od=$(echo "$output" | LC_ALL=C od -t c)
+ printf "' " >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000200 +q +u +o +t +e +: +342 +200 +231' \
+ || wail
+ printf '` ' >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000320 +e +: +342 +200 +230' || wail
+ printf "%s " '-' >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000220 +e +n +: +342 +200 +220' || wail
+ printf '^ ' >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000260 +r +c +u +m +f +l +e +x +: +313 +206' \
+ || wail
+ printf "~ " >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000340 +t +i +l +d +e +: +313 +234' || wail
+ ;;
+ (*)
+ printf '` ' >&2
+ echo "$output" | grep -Fqx 'opening single quote: `' || wail
+ printf "' " >&2
+ echo "$output" | grep -Fqx "closing single quote: '" || wail
+ printf "%s " '-' >&2
+ echo "$output" | grep -Fqx "hyphen: -" || wail
+ printf '^ ' >&2
+ echo "$output" | grep -Fqx "modifier circumflex: ^" || wail
+ printf "~ " >&2
+ echo "$output" | grep -Fqx "modifier tilde: ~" || wail
+ ;;
+ esac
+ echo >&2
+done
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/devices/grotty/tests/osc8_works.sh b/src/devices/grotty/tests/osc8_works.sh
new file mode 100755
index 0000000..db6480d
--- /dev/null
+++ b/src/devices/grotty/tests/osc8_works.sh
@@ -0,0 +1,119 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+set -e
+
+grotty="${abs_top_builddir:-.}/grotty"
+
+input="x T utf8
+x res 240 24 40
+x init
+p1
+x font 1 R
+f1
+s10
+V40
+H0
+md
+DFd
+tA
+n40 0
+x X tty: link
+x X tty: link h
+x X tty: link http://example.com/1
+x X tty: link
+x X tty: link http://example.com/2
+tB
+x X tty: link
+x X tty: link mailto:g.branden.robinson@gmail.com
+tBranden
+x X tty: link
+x trailer
+V2640
+x stop"
+
+# We expect diagnostics from the first few "x X tty: link" lines. The
+# first should complain about a link ending without having been started.
+# The second is bogus ("h") but it's not grotty's job to validate the
+# structure of a URI. The third should draw complaint because we didn't
+# end the (bogus) URI that we started with the second.
+
+# The remaining input is well-formed. The URI ending in "1" is
+# effectively hidden because no character cells are drawn while it is
+# active.
+echo "expect two diagnostic messages regarding ill-formed links" >&2
+output=$(echo "$input" | "$grotty" -F font -F build/font | od -t c)
+
+# Expected:
+#0000000 A 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h 033 \
+#0000020 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t t p
+#0000040 : / / e x a m p l e . c o m / 1
+#0000060 033 \ 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t
+#0000100 t p : / / e x a m p l e . c o m
+#0000120 / 2 033 \ B 033 ] 8 ; ; 033 \ 033 ] 8 ;
+#0000140 ; m a i l t o : g . b r a n d e
+#0000160 n . r o b i n s o n @ g m a i l
+#0000200 . c o m 033 \ B r a n d e n 033 ] 8
+#0000220 ; ; 033 \ \n \n \n \n \n \n \n \n \n \n \n \n
+#0000240 \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
+#*
+#0000320 \n \n \n \n \n \n
+#0000326
+
+echo "testing for URI that corresponds to no character cells" >&2
+echo "$output" | grep -Eq 'A 033 +] +8 +; +; +033 +\\'
+
+echo "testing http URI (1)" >&2
+echo "$output" | grep -Eq '0000020 +.*033 +] +8 +; +; +h + t +t +p'
+
+echo "testing http URI (2)" >&2
+echo "$output" | grep -Eq '0000040 +: +/ +/ +e +x +a +m +p +l +e +\. +c'
+
+echo "testing http URI (3)" >&2
+echo "$output" | grep -Eq '0000040.* +o +m +/ +1'
+
+echo "testing http URI (4)" >&2
+echo "$output" | grep -Eq '0000060 +033 +\\'
+
+echo "testing mailto URI (1)" >&2
+echo "$output" | grep -Eq '0000120 +.* +033 +] +8 +;$'
+
+echo "testing mailto URI (2)" >&2
+echo "$output" | grep -Eq '0000140 +; +m +a +i +l +t +o +: +g +\. +b'
+
+echo "testing mailto URI (3)" >&2
+echo "$output" | grep -Eq '0000140.* +r +a +n +d +e$'
+
+echo "testing mailto URI (4)" >&2
+echo "$output" | grep -Eq '0000160 +n +\. +r +o +b +i +n +s +o +n +@'
+
+echo "testing mailto URI (5)" >&2
+echo "$output" | grep -Eq '0000160.* +g +m +a +i +l$'
+
+echo "testing mailto URI (6)" >&2
+echo "$output" | grep -Eq '0000200 +\. +c +o +m +033 +\\ +B +r +a +n +d'
+
+echo "testing mailto URI (7)" >&2
+echo "$output" | grep -Eq '0000200.* +e +n +033 +] +8$'
+
+echo "testing mailto URI (8)" >&2
+echo "$output" | grep -Eq '0000220 +; +; +033 +\\'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/devices/grotty/tty.cpp b/src/devices/grotty/tty.cpp
new file mode 100644
index 0000000..45926f6
--- /dev/null
+++ b/src/devices/grotty/tty.cpp
@@ -0,0 +1,1043 @@
+/* Copyright (C) 1989-2021 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+ OSC 8 support by G. Branden Robinson
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "device.h"
+#include "ptable.h"
+
+typedef signed char schar;
+
+declare_ptable(schar)
+implement_ptable(schar)
+
+extern "C" const char *Version_string;
+
+#define putstring(s) fputs(s, stdout)
+
+#ifndef SHRT_MIN
+#define SHRT_MIN (-32768)
+#endif
+
+#ifndef SHRT_MAX
+#define SHRT_MAX 32767
+#endif
+
+#define TAB_WIDTH 8
+
+// A character of the output device fits in a 32-bit word.
+typedef unsigned int output_character;
+
+static bool want_horizontal_tabs = false;
+static bool want_form_feeds = false;
+static bool want_emboldening_by_overstriking = true;
+static bool do_bold;
+static bool want_italics_by_underlining = true;
+static bool do_underline;
+static bool want_glyph_composition_by_overstriking = true;
+static bool allow_drawing_commands = true;
+static bool want_sgr_italics = false;
+static bool do_sgr_italics;
+static bool want_reverse_video_for_italics = false;
+static bool do_reverse_video;
+static bool use_overstriking_drawing_scheme = false;
+
+static void update_options();
+static void usage(FILE *stream);
+
+static int hline_char = '-';
+static int vline_char = '|';
+
+enum {
+ UNDERLINE_MODE = 0x01,
+ BOLD_MODE = 0x02,
+ VDRAW_MODE = 0x04,
+ HDRAW_MODE = 0x08,
+ CU_MODE = 0x10,
+ COLOR_CHANGE = 0x20,
+ START_LINE = 0x40,
+ END_LINE = 0x80
+};
+
+// Mode to use for bold-underlining.
+static unsigned char bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
+static unsigned char bold_underline_mode;
+
+#ifndef IS_EBCDIC_HOST
+#define CSI "\033["
+#define OSC8 "\033]8"
+#define ST "\033\\"
+#else
+#define CSI "\047["
+#define OSC8 "\047]8"
+#define ST "\047\\"
+#endif
+
+// SGR handling (ISO 6429)
+#define SGR_BOLD CSI "1m"
+#define SGR_NO_BOLD CSI "22m"
+#define SGR_ITALIC CSI "3m"
+#define SGR_NO_ITALIC CSI "23m"
+#define SGR_UNDERLINE CSI "4m"
+#define SGR_NO_UNDERLINE CSI "24m"
+#define SGR_REVERSE CSI "7m"
+#define SGR_NO_REVERSE CSI "27m"
+// many terminals can't handle 'CSI 39 m' and 'CSI 49 m' to reset
+// the foreground and background color, respectively; we thus use
+// 'CSI 0 m' exclusively
+#define SGR_DEFAULT CSI "0m"
+
+#define DEFAULT_COLOR_IDX -1
+
+class tty_font : public font {
+ tty_font(const char *);
+ unsigned char mode;
+public:
+ ~tty_font();
+ unsigned char get_mode() { return mode; }
+#if 0
+ void handle_x_command(int argc, const char **argv);
+#endif
+ static tty_font *load_tty_font(const char *);
+};
+
+tty_font *tty_font::load_tty_font(const char *s)
+{
+ tty_font *f = new tty_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ const char *num = f->get_internal_name();
+ long n;
+ if (num != 0 && (n = strtol(num, 0, 0)) != 0)
+ f->mode = (unsigned char)(n & (BOLD_MODE|UNDERLINE_MODE));
+ if (!do_underline)
+ f->mode &= ~UNDERLINE_MODE;
+ if (!do_bold)
+ f->mode &= ~BOLD_MODE;
+ if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
+ f->mode = (unsigned char)((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
+ | bold_underline_mode);
+ return f;
+}
+
+tty_font::tty_font(const char *nm)
+: font(nm), mode(0)
+{
+}
+
+tty_font::~tty_font()
+{
+}
+
+#if 0
+void tty_font::handle_x_command(int argc, const char **argv)
+{
+ if (argc >= 1 && strcmp(argv[0], "bold") == 0)
+ mode |= BOLD_MODE;
+ else if (argc >= 1 && strcmp(argv[0], "underline") == 0)
+ mode |= UNDERLINE_MODE;
+}
+#endif
+
+class tty_glyph {
+public:
+ tty_glyph *next;
+ int w;
+ int hpos;
+ unsigned int code;
+ unsigned char mode;
+ schar back_color_idx;
+ schar fore_color_idx;
+ inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
+ inline int order() {
+ return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); }
+};
+
+
+class tty_printer : public printer {
+ tty_glyph **lines;
+ int nlines;
+ int cached_v;
+ int cached_vpos;
+ schar curr_fore_idx;
+ schar curr_back_idx;
+ bool is_underlining;
+ bool is_boldfacing;
+ bool is_continuously_underlining;
+ PTABLE(schar) tty_colors;
+ void make_underline(int);
+ void make_bold(output_character, int);
+ schar color_to_idx(color *);
+ void add_char(output_character, int, int, int, color *, color *,
+ unsigned char);
+ void simple_add_char(const output_character, const environment *);
+ char *make_rgb_string(unsigned int, unsigned int, unsigned int);
+ bool tty_color(unsigned int, unsigned int, unsigned int, schar *,
+ schar = DEFAULT_COLOR_IDX);
+ void line(int, int, int, int, color *, color *);
+ void draw_line(int *, int, const environment *);
+ void draw_polygon(int *, int, const environment *);
+ void special_link(const char *, const environment *);
+public:
+ tty_printer();
+ ~tty_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *);
+ void draw(int, int *, int, const environment *);
+ void special(char *, const environment *, char);
+ void change_color(const environment * const);
+ void change_fill_color(const environment * const);
+ void put_char(output_character);
+ void put_color(schar, int);
+ void begin_page(int) { }
+ void end_page(int);
+ font *make_font(const char *);
+};
+
+char *tty_printer::make_rgb_string(unsigned int r,
+ unsigned int g,
+ unsigned int b)
+{
+ char *s = new char[8];
+ s[0] = char(r >> 8);
+ s[1] = char(r & 0xff);
+ s[2] = char(g >> 8);
+ s[3] = char(g & 0xff);
+ s[4] = char(b >> 8);
+ s[5] = char(b & 0xff);
+ s[6] = char(0x80);
+ s[7] = 0;
+ // avoid null-bytes in string
+ for (int i = 0; i < 6; i++)
+ if (!s[i]) {
+ s[i] = 1;
+ s[6] |= 1 << i;
+ }
+ return s;
+}
+
+bool tty_printer::tty_color(unsigned int r,
+ unsigned int g,
+ unsigned int b, schar *idx, schar value)
+{
+ bool is_known_color = true;
+ char *s = make_rgb_string(r, g, b);
+ schar *i = tty_colors.lookup(s);
+ if (!i) {
+ is_known_color = false;
+ i = new schar[1];
+ *i = value;
+ tty_colors.define(s, i);
+ }
+ *idx = *i;
+ delete[] s;
+ return is_known_color;
+}
+
+tty_printer::tty_printer() : cached_v(0)
+{
+ if (font::is_unicode) {
+ hline_char = 0x2500;
+ vline_char = 0x2502;
+ }
+ schar dummy;
+ // black, white
+ (void)tty_color(0, 0, 0, &dummy, 0);
+ (void)tty_color(color::MAX_COLOR_VAL,
+ color::MAX_COLOR_VAL,
+ color::MAX_COLOR_VAL, &dummy, 7);
+ // red, green, blue
+ (void)tty_color(color::MAX_COLOR_VAL, 0, 0, &dummy, 1);
+ (void)tty_color(0, color::MAX_COLOR_VAL, 0, &dummy, 2);
+ (void)tty_color(0, 0, color::MAX_COLOR_VAL, &dummy, 4);
+ // yellow, magenta, cyan
+ (void)tty_color(color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, 0, &dummy, 3);
+ (void)tty_color(color::MAX_COLOR_VAL, 0, color::MAX_COLOR_VAL, &dummy, 5);
+ (void)tty_color(0, color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, &dummy, 6);
+ nlines = 66;
+ lines = new tty_glyph *[nlines];
+ for (int i = 0; i < nlines; i++)
+ lines[i] = 0;
+ is_continuously_underlining = false;
+}
+
+tty_printer::~tty_printer()
+{
+ delete[] lines;
+}
+
+void tty_printer::make_underline(int w)
+{
+ if (use_overstriking_drawing_scheme) {
+ if (!w)
+ warning("can't underline zero-width character");
+ else {
+ putchar('_');
+ putchar('\b');
+ }
+ }
+ else {
+ if (!is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_REVERSE);
+ else
+ putstring(SGR_UNDERLINE);
+ }
+ is_underlining = true;
+ }
+}
+
+void tty_printer::make_bold(output_character c, int w)
+{
+ if (use_overstriking_drawing_scheme) {
+ if (!w)
+ warning("can't print zero-width character in bold");
+ else {
+ put_char(c);
+ putchar('\b');
+ }
+ }
+ else {
+ if (!is_boldfacing)
+ putstring(SGR_BOLD);
+ is_boldfacing = true;
+ }
+}
+
+schar tty_printer::color_to_idx(color *col)
+{
+ if (col->is_default())
+ return DEFAULT_COLOR_IDX;
+ unsigned int r, g, b;
+ col->get_rgb(&r, &g, &b);
+ schar idx;
+ if (!tty_color(r, g, b, &idx)) {
+ char *s = col->print_color();
+ error("unrecognized color '%1' mapped to default", s);
+ delete[] s;
+ }
+ return idx;
+}
+
+void tty_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ if (w % font::hor != 0)
+ fatal("glyph width is not a multiple of horizontal motion quantum");
+ add_char(f->get_code(g), w,
+ env->hpos, env->vpos,
+ env->col, env->fill,
+ ((tty_font *)f)->get_mode());
+}
+
+void tty_printer::add_char(output_character c, int w,
+ int h, int v,
+ color *fore, color *back,
+ unsigned char mode)
+{
+#if 0
+ // This is too expensive.
+ if (h % font::hor != 0)
+ fatal("horizontal position not a multiple of horizontal motion quantum");
+#endif
+ int hpos = h / font::hor;
+ if (hpos < SHRT_MIN || hpos > SHRT_MAX) {
+ error("character with ridiculous horizontal position discarded");
+ return;
+ }
+ int vpos;
+ if (v == cached_v && cached_v != 0)
+ vpos = cached_vpos;
+ else {
+ if (v % font::vert != 0)
+ fatal("vertical position not a multiple of vertical motion"
+ " quantum");
+ vpos = v / font::vert;
+ if (vpos > nlines) {
+ tty_glyph **old_lines = lines;
+ lines = new tty_glyph *[vpos + 1];
+ memcpy(lines, old_lines, nlines * sizeof(tty_glyph *));
+ for (int i = nlines; i <= vpos; i++)
+ lines[i] = 0;
+ delete[] old_lines;
+ nlines = vpos + 1;
+ }
+ // Note that the first output line corresponds to groff
+ // position font::vert.
+ if (vpos <= 0) {
+ error("output above first line discarded");
+ return;
+ }
+ cached_v = v;
+ cached_vpos = vpos;
+ }
+ tty_glyph *g = new tty_glyph;
+ g->w = w;
+ g->hpos = hpos;
+ g->code = c;
+ g->fore_color_idx = color_to_idx(fore);
+ g->back_color_idx = color_to_idx(back);
+ g->mode = mode;
+
+ // The list will be reversed later. After reversal, it must be in
+ // increasing order of hpos, with COLOR_CHANGE and CU specials before
+ // HDRAW characters before VDRAW characters before normal characters
+ // at each hpos, and otherwise in order of occurrence.
+
+ tty_glyph **pp;
+ for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next)
+ if ((*pp)->hpos < hpos
+ || ((*pp)->hpos == hpos && (*pp)->order() >= g->order()))
+ break;
+ g->next = *pp;
+ *pp = g;
+}
+
+void tty_printer::simple_add_char(const output_character c,
+ const environment *env)
+{
+ add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+}
+
+void tty_printer::special(char *arg, const environment *env, char type)
+{
+ if (type == 'u') {
+ add_char(*arg - '0', 0, env->hpos, env->vpos, env->col, env->fill,
+ CU_MODE);
+ return;
+ }
+ if (type != 'p')
+ return;
+ char *p;
+ for (p = arg; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *tag = p;
+ for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*p == '\0' || strncmp(tag, "tty", p - tag) != 0) {
+ error("X command without 'tty:' tag ignored");
+ return;
+ }
+ p++;
+ for (; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *command = p;
+ for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*command == '\0') {
+ error("empty X command ignored");
+ return;
+ }
+ if (strncmp(command, "link", p - command) == 0)
+ special_link(p, env);
+ else
+ warning("unrecognized X command '%1' ignored", command);
+}
+
+// Produce an OSC 8 hyperlink. Given ditroff input of the form:
+// x X tty: link [URI[ KEY=VALUE] ...]
+// produce "OSC 8 [;KEY=VALUE];[URI] ST". KEY/VALUE pairs can be
+// repeated arbitrarily and are separated by colons. Omission of the
+// URI ends the hyperlink that was begun by specifying it. See
+// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>.
+void tty_printer::special_link(const char *arg, const environment *env)
+{
+ static bool is_link_active = false;
+ if (use_overstriking_drawing_scheme)
+ return;
+ for (const char *s = OSC8; *s != '\0'; s++)
+ simple_add_char(*s, env);
+ simple_add_char(';', env);
+ char c = *arg;
+ if ('\0' == c || '\n' == c) {
+ simple_add_char(';', env);
+ if (!is_link_active)
+ warning("ending hyperlink when none was started");
+ is_link_active = false;
+ }
+ else {
+ // Our caller ensures that we see white space after 'link'.
+ assert(c == ' ' || c == '\t');
+ if (is_link_active) {
+ warning("new hyperlink started without ending previous one;"
+ " recovering");
+ simple_add_char(';', env);
+ for (const char *s = ST OSC8; *s != '\0'; s++)
+ simple_add_char(*s, env);
+ simple_add_char(';', env);
+ }
+ is_link_active = true;
+ do
+ c = *arg++;
+ while (c == ' ' || c == '\t');
+ arg--;
+ // The first argument is the URI.
+ const char *uri = arg;
+ do
+ c = *arg++;
+ while (c != '\0' && c != ' ' && c != '\t');
+ arg--;
+ ptrdiff_t uri_len = arg - uri;
+ // Any remaining arguments are "key=value" pairs.
+ const char *pair = 0;
+ bool done = false;
+ do {
+ if (pair != 0)
+ simple_add_char(':', env);
+ pair = arg;
+ bool in_pair = true;
+ do {
+ c = *arg++;
+ if ('\0' == c)
+ done = true;
+ else if (' ' == c || '\t' == c)
+ in_pair = false;
+ else
+ simple_add_char(c, env);
+ } while (!done && in_pair);
+ } while (!done);
+ simple_add_char(';', env);
+ for (size_t i = uri_len; i > 0; i--)
+ simple_add_char(*uri++, env);
+ }
+ for (const char *s = ST; *s != '\0'; s++)
+ simple_add_char(*s, env);
+}
+
+void tty_printer::change_color(const environment * const env)
+{
+ add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
+}
+
+void tty_printer::change_fill_color(const environment * const env)
+{
+ add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
+}
+
+void tty_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if (!allow_drawing_commands)
+ return;
+ if (code == 'l')
+ draw_line(p, np, env);
+ else if (code == 'p')
+ draw_polygon(p, np, env);
+ else
+ warning("ignoring unsupported drawing command '%1'", char(code));
+}
+
+void tty_printer::draw_polygon(int *p, int np, const environment *env)
+{
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ return;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ return;
+ }
+ // We only draw polygons which consist entirely of horizontal and
+ // vertical lines.
+ int hpos = 0;
+ int vpos = 0;
+ for (int i = 0; i < np; i += 2) {
+ if (!(p[i] == 0 || p[i + 1] == 0))
+ return;
+ hpos += p[i];
+ vpos += p[i + 1];
+ }
+ if (!(hpos == 0 || vpos == 0))
+ return;
+ int start_hpos = env->hpos;
+ int start_vpos = env->vpos;
+ hpos = start_hpos;
+ vpos = start_vpos;
+ for (int i = 0; i < np; i += 2) {
+ line(hpos, vpos, p[i], p[i + 1], env->col, env->fill);
+ hpos += p[i];
+ vpos += p[i + 1];
+ }
+ line(hpos, vpos, start_hpos - hpos, start_vpos - vpos,
+ env->col, env->fill);
+}
+
+void tty_printer::draw_line(int *p, int np, const environment *env)
+{
+ if (np != 2) {
+ error("2 arguments required for line");
+ return;
+ }
+ line(env->hpos, env->vpos, p[0], p[1], env->col, env->fill);
+}
+
+void tty_printer::line(int hpos, int vpos, int dx, int dy,
+ color *col, color *fill)
+{
+ // XXX: zero-length lines get drawn as '+' crossings in nroff, even
+ // when there is no crossing, but they nevertheless occur frequently
+ // in input. Does tbl produce them?
+#if 0
+ if (0 == dx)
+ fatal("cannot draw zero-length horizontal line");
+ if (0 == dy)
+ fatal("cannot draw zero-length vertical line");
+#endif
+ if ((dx != 0) && (dy != 0))
+ warning("cannot draw diagonal line");
+ if (dx % font::hor != 0)
+ fatal("length of horizontal line %1 is not a multiple of horizontal"
+ " motion quantum %2", dx, font::hor);
+ if (dy % font::vert != 0)
+ fatal("length of vertical line %1 is not a multiple of vertical"
+ " motion quantum %2", dy, font::vert);
+ if (dx == 0) {
+ // vertical line
+ int v = vpos;
+ int len = dy;
+ if (len < 0) {
+ v += len;
+ len = -len;
+ }
+ if (len == 0)
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|START_LINE|END_LINE);
+ else {
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|START_LINE);
+ len -= font::vert;
+ v += font::vert;
+ while (len > 0) {
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|START_LINE|END_LINE);
+ len -= font::vert;
+ v += font::vert;
+ }
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|END_LINE);
+ }
+ }
+ if (dy == 0) {
+ // horizontal line
+ int h = hpos;
+ int len = dx;
+ if (len < 0) {
+ h += len;
+ len = -len;
+ }
+ if (len == 0)
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|START_LINE|END_LINE);
+ else {
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|START_LINE);
+ len -= font::hor;
+ h += font::hor;
+ while (len > 0) {
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|START_LINE|END_LINE);
+ len -= font::hor;
+ h += font::hor;
+ }
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|END_LINE);
+ }
+ }
+}
+
+void tty_printer::put_char(output_character wc)
+{
+ if (font::is_unicode && wc >= 0x80) {
+ char buf[6 + 1];
+ int count;
+ char *p = buf;
+ if (wc < 0x800)
+ count = 1, *p = (unsigned char)((wc >> 6) | 0xc0);
+ else if (wc < 0x10000)
+ count = 2, *p = (unsigned char)((wc >> 12) | 0xe0);
+ else if (wc < 0x200000)
+ count = 3, *p = (unsigned char)((wc >> 18) | 0xf0);
+ else if (wc < 0x4000000)
+ count = 4, *p = (unsigned char)((wc >> 24) | 0xf8);
+ else if (wc <= 0x7fffffff)
+ count = 5, *p = (unsigned char)((wc >> 30) | 0xfC);
+ else
+ return;
+ do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80);
+ while (count > 0);
+ *++p = '\0';
+ putstring(buf);
+ }
+ else
+ putchar(wc);
+}
+
+void tty_printer::put_color(schar color_index, int back)
+{
+ if (color_index == DEFAULT_COLOR_IDX) {
+ putstring(SGR_DEFAULT);
+ // set bold and underline again
+ if (is_boldfacing)
+ putstring(SGR_BOLD);
+ if (is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_REVERSE);
+ else
+ putstring(SGR_UNDERLINE);
+ }
+ // set other color again
+ back = !back;
+ color_index = back ? curr_back_idx : curr_fore_idx;
+ }
+ if (color_index != DEFAULT_COLOR_IDX) {
+ putstring(CSI);
+ if (back)
+ putchar('4');
+ else
+ putchar('3');
+ putchar(color_index + '0');
+ putchar('m');
+ }
+}
+
+// The possible Unicode combinations for crossing characters.
+//
+// ' ' = 0, ' -' = 4, '- ' = 8, '--' = 12,
+//
+// ' ' = 0, ' ' = 1, '|' = 2, '|' = 3
+// | |
+
+static output_character crossings[4*4] = {
+ 0x0000, 0x2577, 0x2575, 0x2502,
+ 0x2576, 0x250C, 0x2514, 0x251C,
+ 0x2574, 0x2510, 0x2518, 0x2524,
+ 0x2500, 0x252C, 0x2534, 0x253C
+};
+
+void tty_printer::end_page(int page_length)
+{
+ if (page_length % font::vert != 0)
+ error("vertical position at end of page not multiple of vertical"
+ " motion quantum");
+ int lines_per_page = page_length / font::vert;
+ int last_line;
+ for (last_line = nlines; last_line > 0; last_line--)
+ if (lines[last_line - 1])
+ break;
+#if 0
+ if (last_line > lines_per_page) {
+ error("characters past last line discarded");
+ do {
+ --last_line;
+ while (lines[last_line]) {
+ tty_glyph *tem = lines[last_line];
+ lines[last_line] = tem->next;
+ delete tem;
+ }
+ } while (last_line > lines_per_page);
+ }
+#endif
+ for (int i = 0; i < last_line; i++) {
+ tty_glyph *p = lines[i];
+ lines[i] = 0;
+ tty_glyph *g = 0;
+ while (p) {
+ tty_glyph *tem = p->next;
+ p->next = g;
+ g = p;
+ p = tem;
+ }
+ int hpos = 0;
+ tty_glyph *nextp;
+ curr_fore_idx = DEFAULT_COLOR_IDX;
+ curr_back_idx = DEFAULT_COLOR_IDX;
+ is_underlining = false;
+ is_boldfacing = false;
+ for (p = g; p; delete p, p = nextp) {
+ nextp = p->next;
+ if (p->mode & CU_MODE) {
+ is_continuously_underlining = (p->code != 0);
+ continue;
+ }
+ if (nextp && p->hpos == nextp->hpos) {
+ if (p->draw_mode() == HDRAW_MODE &&
+ nextp->draw_mode() == VDRAW_MODE) {
+ if (font::is_unicode)
+ nextp->code =
+ crossings[((p->mode & (START_LINE|END_LINE)) >> 4)
+ + ((nextp->mode & (START_LINE|END_LINE)) >> 6)];
+ else
+ nextp->code = '+';
+ continue;
+ }
+ if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode()) {
+ nextp->code = p->code;
+ continue;
+ }
+ if (!want_glyph_composition_by_overstriking)
+ continue;
+ }
+ if (hpos > p->hpos) {
+ do {
+ putchar('\b');
+ hpos--;
+ } while (hpos > p->hpos);
+ }
+ else {
+ if (want_horizontal_tabs) {
+ for (;;) {
+ int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH;
+ if (next_tab_pos > p->hpos)
+ break;
+ if (is_continuously_underlining)
+ make_underline(p->w);
+ else if (!use_overstriking_drawing_scheme
+ && is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_NO_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_NO_REVERSE);
+ else
+ putstring(SGR_NO_UNDERLINE);
+ is_underlining = false;
+ }
+ putchar('\t');
+ hpos = next_tab_pos;
+ }
+ }
+ for (; hpos < p->hpos; hpos++) {
+ if (is_continuously_underlining)
+ make_underline(p->w);
+ else if (!use_overstriking_drawing_scheme && is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_NO_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_NO_REVERSE);
+ else
+ putstring(SGR_NO_UNDERLINE);
+ is_underlining = false;
+ }
+ putchar(' ');
+ }
+ }
+ assert(hpos == p->hpos);
+ if (p->mode & COLOR_CHANGE) {
+ if (!use_overstriking_drawing_scheme) {
+ if (p->fore_color_idx != curr_fore_idx) {
+ put_color(p->fore_color_idx, 0);
+ curr_fore_idx = p->fore_color_idx;
+ }
+ if (p->back_color_idx != curr_back_idx) {
+ put_color(p->back_color_idx, 1);
+ curr_back_idx = p->back_color_idx;
+ }
+ }
+ continue;
+ }
+ if (p->mode & UNDERLINE_MODE)
+ make_underline(p->w);
+ else if (!use_overstriking_drawing_scheme && is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_NO_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_NO_REVERSE);
+ else
+ putstring(SGR_NO_UNDERLINE);
+ is_underlining = false;
+ }
+ if (p->mode & BOLD_MODE)
+ make_bold(p->code, p->w);
+ else if (!use_overstriking_drawing_scheme && is_boldfacing) {
+ putstring(SGR_NO_BOLD);
+ is_boldfacing = false;
+ }
+ if (!use_overstriking_drawing_scheme) {
+ if (p->fore_color_idx != curr_fore_idx) {
+ put_color(p->fore_color_idx, 0);
+ curr_fore_idx = p->fore_color_idx;
+ }
+ if (p->back_color_idx != curr_back_idx) {
+ put_color(p->back_color_idx, 1);
+ curr_back_idx = p->back_color_idx;
+ }
+ }
+ put_char(p->code);
+ hpos += p->w / font::hor;
+ }
+ if (!use_overstriking_drawing_scheme
+ && (is_boldfacing || is_underlining
+ || curr_fore_idx != DEFAULT_COLOR_IDX
+ || curr_back_idx != DEFAULT_COLOR_IDX))
+ putstring(SGR_DEFAULT);
+ putchar('\n');
+ }
+ if (want_form_feeds) {
+ if (last_line < lines_per_page)
+ putchar('\f');
+ }
+ else {
+ for (; last_line < lines_per_page; last_line++)
+ putchar('\n');
+ }
+}
+
+font *tty_printer::make_font(const char *nm)
+{
+ return tty_font::load_tty_font(nm);
+}
+
+printer *make_printer()
+{
+ return new tty_printer();
+}
+
+static void update_options()
+{
+ if (use_overstriking_drawing_scheme) {
+ do_sgr_italics = false;
+ do_reverse_video = false;
+ bold_underline_mode = bold_underline_mode_option;
+ do_bold = want_emboldening_by_overstriking;
+ do_underline = want_italics_by_underlining;
+ }
+ else {
+ do_sgr_italics = want_sgr_italics;
+ do_reverse_video = want_reverse_video_for_italics;
+ bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
+ do_bold = true;
+ do_underline = true;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ if (getenv("GROFF_NO_SGR"))
+ use_overstriking_drawing_scheme = true;
+ setbuf(stderr, stderr_buf);
+ setlocale(LC_CTYPE, "");
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "bBcdfF:hiI:oruUv", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'v':
+ printf("GNU grotty (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'i':
+ // Use italic font instead of underlining.
+ want_sgr_italics = true;
+ break;
+ case 'I':
+ // ignore include search path
+ break;
+ case 'b':
+ // Do not embolden by overstriking.
+ want_emboldening_by_overstriking = false;
+ break;
+ case 'c':
+ // Use old scheme for emboldening and underline.
+ use_overstriking_drawing_scheme = true;
+ break;
+ case 'u':
+ // Do not underline.
+ want_italics_by_underlining = false;
+ break;
+ case 'o':
+ // Do not overstrike (other than emboldening and underlining).
+ want_glyph_composition_by_overstriking = false;
+ break;
+ case 'r':
+ // Use reverse mode instead of underlining.
+ want_reverse_video_for_italics = true;
+ break;
+ case 'B':
+ // Do bold-underlining as bold.
+ bold_underline_mode_option = BOLD_MODE;
+ break;
+ case 'U':
+ // Do bold-underlining as underlining.
+ bold_underline_mode_option = UNDERLINE_MODE;
+ break;
+ case 'h':
+ // Use horizontal tabs.
+ want_horizontal_tabs = true;
+ break;
+ case 'f':
+ want_form_feeds = true;
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'd':
+ // Ignore \D commands.
+ allow_drawing_commands = false;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ update_options();
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-bBcdfhioruU] [-F font-directory] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"Translate the output of troff(1) into a form suitable for\n"
+"typewriterâ€like devices, including terminal emulators. See the\n"
+"grotty(1) manual page.\n",
+ stream);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/xditview/ChangeLog b/src/devices/xditview/ChangeLog
new file mode 100644
index 0000000..a33297b
--- /dev/null
+++ b/src/devices/xditview/ChangeLog
@@ -0,0 +1,556 @@
+2004-05-29 Werner LEMBERG <wl@gnu.org>
+
+ gxditview and xtotroff have been integrated into the normal groff
+ directory structure; future changes are logged in the main
+ ChangeLog file.
+
+2004-05-13 Werner LEMBERG <wl@gnu.org>
+
+Version 1.19.1 released
+=======================
+
+2004-04-17 Werner LEMBERG <wl@gnu.org>
+
+ * device.c (scale_round): Round correctly for negative values
+ (this is the same function as in src/libs/libgroff/font.c).
+ Found by Paul Eggert.
+
+2003-11-10 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in: s/@top_srcdir@/@abs_top_srcdir@/,
+ s/@groff_top_builddir@/@abs_top_builddir@/.
+
+Version 1.19 released
+=====================
+
+2003-03-03 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in (extraclean): Added gxditview._man.
+
+2003-01-28 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in (SEP): New variable; set to @PATH_SEPARATOR@.
+ (GROFF_FONTPATH): Use it.
+
+2003-01-07 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Add `sqrt'.
+
+2003-01-06 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Add `integral'.
+
+2002-12-29 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Remove `ap' and `eq'.
+
+2002-12-20 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Don't include `or'.
+ * draw.c (AdjustCharDeltas): Apply correction only if nadj > 1.
+ (DoCharacter): Call FlushCharCache if font size and font number
+ differ.
+ Reset `dw->dvi.cache.adjustable' properly.
+
+2002-12-09 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Use `tno' symbol instead of `no'.
+
+2002-12-01 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in: Use `InstallAppDefaultsLong' instead of
+ `InstallAppDefaults' to make it work if build directory isn't
+ $srcdir.
+
+2002-11-24 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Add glyph `braceex'.
+
+2002-11-14 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Don't include `or'.
+
+Version 1.18.1 released
+=======================
+
+2002-09-16 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in (GROFF_LOCALFONTDIR): New variable.
+ (GROFF_FONTPATH): Use it.
+ Remove /usr/local/lib/font.
+
+Version 1.18.0 released
+=======================
+
+2002-06-22 Werner LEMBERG <wl@gnu.org>
+
+ * gxditview.c (main): Handle `-help' and `--help' correctly.
+
+2002-06-17 Colin Watson <cjwatson@debian.org>
+
+ * Imakefile.in: s/@top_builddir@/@groff_top_builddir@/.
+
+2002-04-06 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map, Adobe_Symbol_map): Remove all
+ characters > 0x80.
+ * parse.c (ParseInput): Ignore `m' command.
+ (ParseDrawFunction): Don't move for unknown drawing functions.
+ Don't move for `f' drawing function.
+
+2002-03-25 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Use `t+-', `tmu', and `tdi' symbols
+ instead of `+-', `mu', and `di', respectively.
+
+2002-02-23 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Add `mc' symbol.
+
+2001-09-22 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in: Redefine `ProgramTargetHelper' as
+ `ProgramTargetHelperNoMan' and add a call to `InstallManPageLong'
+ to make the `install.man' target work if the build directory isn't
+ $srcdir.
+
+Version 1.17.2 released
+=======================
+
+Version 1.17.1 released
+=======================
+
+2001-04-21 Albert Chin-A-Young <china@thewrittenword.com>
+
+ * Imakefile.in: Add support for recent HP architectures.
+
+Version 1.17 released
+=====================
+
+2001-01-04 Rob Daasch <daasch@ece.pdx.edu>
+
+ * parse.c (ParseInput): Added 'F' to command switch to swallow
+ filename strings as ignored comments.
+
+2000-12-02 Werner LEMBERG <wl@gnu.org>
+
+ * device.c (find_file): Remove home directory in search path.
+
+2000-11-14 Werner LEMBERG <wl@gnu.org>
+
+ * device.c (open_device_file): Remove `path' parameter.
+ (find_file): Construct font path similar to groff: First the contents
+ of GROFF_FONT_PATH, then the home directory, and finally the default
+ font path.
+ * Imakefile.in: Fix GROFF_DATAPROGRAMDIR and GROFF_FONTPATH.
+
+2000-10-23 Werner LEMBERG <wl@gnu.org>
+
+ Change installation structure for data files from .../groff/... to
+ .../groff/<version><revision>/... to be conform with other GNU
+ programs.
+
+ * Imakefile.in: Implement it.
+
+Version 1.16.1 released
+=======================
+
+Version 1.16 released
+=====================
+
+2000-05-18 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c: Adding `cq' as an alias for "'" in latin-1 map.
+
+2000-05-03 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c: Adding `dq' as an alias for `"' in latin-1 map.
+
+2000-04-28 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c: Replacing `md' glyph name with `pc' in latin-1 map to
+ make it distinct from the `md' glyph in the symbol font.
+
+2000-03-03 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile replaced with Imakefile.in which will be configured by
+ the main configure script of groff. This will set the correct font
+ path, and it will make it possible to build xditview in a directory
+ different from $srcdir.
+
+2000-03-01 Colin Phipps <crp22@cam.ac.uk>
+
+ * Dvi.c (OpenFile): Use tmpdir() for security reasons.
+ * xtotroff.c (MapFont): Avoid race while opening file.
+
+2000-02-06 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile: Adapted to new directory structure.
+
+ * README: Updated.
+
+Version 1.15 released
+=====================
+
+1999-12-21 Werner LEMBERG <wl@gnu.org>
+
+ * README: Fixed ftp GNU address.
+
+1999-12-13 Werner LEMBERG <wl@gnu.org>
+
+ * device.c: Use extern declarations of strtok(), strchr(), and
+ getenv() only if not defined as macros.
+
+1999-11-18 Larry Jones <larry.jones@sdrc.com>
+
+ * xditview.c: Add fallback_resources to allow running without
+ access to the app-defaults file.
+
+ * Imakefile: Added rule to create app-defaults to a C header file.
+
+ * GXditview-ad.h: New file containing fallback default resources.
+
+ * ad2c: New file to do the app-defaults -> C header file
+ conversion.
+
+1999-10-27 Larry Jones <larry.jones@sdrc.com>
+
+ * font.c (DisposeFontSizes): If there's a problem loading a font,
+ xditview will fall-back and use the default font, but it hasn't
+ checked before unloading fonts which could result in unloading the
+ default font (possibly multiple times) and then X errors.
+
+1999-09-13 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile (extraclean): Added Makefile.
+
+ * xditview.c (main, MakePrompt): Fixing compilation warnings.
+
+ * TODO: Imakefile should be replaced with a configure script.
+
+1999-09-13 Werner LEMBERG <wl@gnu.org>
+
+ * Makefile: Removed.
+
+1999-09-12 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile (GROFF_FONTPATH): Another addition.
+
+ * device.c (FONTPATH): Update to match current groff version.
+
+1999-09-11 Larry Jones <larry.jones@sdrc.com>
+
+ * Imakefile (GROFF_LIBDIR, GROFF_FONTPATH): Update to match
+ current groff version.
+
+ * Dvi.c (Realize, Destroy), DviP.h, draw.c (setFillGC), gray*.bm:
+ Allow 8 levels of gray rather than just 1.
+
+ * draw.c (DrawFilledCircle, DrawFilledEllipse, DrawFilledPolygon):
+ Draw outlines to prevent gaps between abutting figures.
+
+1999-05-27 Werner LEMBERG <wl@gnu.org>
+
+ * xtotroff.c (usage): Fixed typo.
+
+Mon Sep 11 10:40:33 1995 James Clark <jjc@jclark.com>
+
+ * device.c (INT_MIN, INT_MAX): Don't define if already defined.
+
+Mon Aug 8 11:14:11 1994 James Clark (jjc@jclark.com)
+
+ * DviChar.c (Adobe_Symbol_map): Use \(nb for notsubset.
+
+Tue Apr 19 04:41:16 1994 James Clark (jjc@jclark.com)
+
+ * Dvi.c (resources): Change default for background and foreground
+ to "XtDefaultBackground" and "XtDefaultForeground".
+
+Sat Feb 12 10:38:47 1994 James Clark (jjc@jclark.com)
+
+ * DviChar.c (Adobe_Symbol_map): Rename radicalex to rn.
+
+Thu May 27 20:30:12 1993 James Clark (jjc@jclark.com)
+
+ * device.c (isascii): Define if necessary.
+ (canonicalize_name): Cast argument to isdigit() to unsigned char.
+
+Thu Apr 29 18:36:57 1993 James Clark (jjc at jclark.com)
+
+ * xditview.c: Include <X11/Xos.h>.
+ (NewFile): Don't declare rindex(). Use strrchr() rather than
+ rindex().
+
+Tue Mar 30 15:12:09 1993 James Clark (jjc at jclark)
+
+ * draw.c (charExists): Check that fi->per_char is not NULL.
+
+Sat Dec 12 17:42:40 1992 James Clark (jjc at jclark)
+
+ * Dvi.c (SetGeometry): Cast XtMakeGeometryRequest arguments.
+
+ * draw.c (DrawPolygon, DrawFilledPolygon): Cast Xtfree argument.
+
+ * font.c (DisposeFontSizes): Add declaration.
+
+ * draw.c (FakeCharacter): Add declaration.
+
+Wed Oct 28 13:24:00 1992 James Clark (jjc at jclark)
+
+ * Imakefile (install.dev): Deleted.
+ (fonts): New target.
+
+Mon Oct 12 10:50:44 1992 James Clark (jjc at jclark)
+
+ * Imakefile (install.dev): Say when we're installing devX*-12.
+
+ * Imakefile (install.dev): Depends on DESC and FontMap.
+
+Thu Oct 1 20:03:45 1992 James Clark (jjc at jclark)
+
+ * xditview.c (Syntax): Mention -filename option.
+
+Sat Aug 15 12:56:39 1992 James Clark (jjc at jclark)
+
+ * GXditview.ad: Bind space and return to NextPage. Bind backspace
+ and delete to previous page.
+
+ * DviChar.c (Adobe_Symbol_map): Add `an'.
+
+ * DviChar.c (Adobe_Symbol_map): Add arrowvertex, arrowverttp, and
+ arrowvertbt.
+
+Mon Aug 10 11:54:27 1992 James Clark (jjc at jclark)
+
+ * FontMap: Add m/p fields to the fonts names.
+
+Sat Aug 8 12:00:28 1992 James Clark (jjc at jclark)
+
+ * DESC: Leave font positions 5-9 blank.
+
+Tue Jul 28 11:37:05 1992 James Clark (jjc at jclark)
+
+ * Imakefile: Don't use gendef. Pass definition of FONTPATH using
+ DEFINES.
+ (path.h): Deleted.
+ (device.c): Don't include path.h. Provide default definition of
+ FONTPATH.
+
+Mon Jul 6 14:06:53 1992 James Clark (jjc at jclark)
+
+ * Imakefile: Don't install tmac.X and tmac.Xps.
+ * tmac.X, tmac.Xps: Moved to ../macros.
+
+ * Imakefile: Don't install eqnchar.
+ * eqnchar: Deleted.
+
+Sun Jun 14 12:55:02 1992 James Clark (jjc@jclark)
+
+ * tmac.Xps: Handle OE, oe, lq, rq.
+ * draw.c (FakeCharacter): Don't handle these.
+
+ * draw.c (FakeCharacter): Don't handle f/.
+
+Mon Jun 8 11:46:37 1992 James Clark (jjc@jclark)
+
+ * tmac.X: Translate char160 to space.
+
+Sun Jun 7 14:39:53 1992 James Clark (jjc@jclark)
+
+ * tmac.X: Do `mso tmac.psic' before restoring compatibility mode.
+
+ * tmac.X: Add \(OE, \(oe, \(ah, \(ao, \(ho.
+
+ * tmac.Xps: Make it work in compatibility mode.
+ Redo existing character definitions with .Xps-char.
+ Add more character definitions.
+ (Xps-char): New macro.
+
+Sat Jun 6 21:46:03 1992 James Clark (jjc@jclark)
+
+ * DviChar.c (Adobe_Symbol_map): Add +h, +f, +p, Fn, lz.
+ * tmac.X: Add \(bq, \(Bq, \(aq.
+ * tmac.Xps: Handle \(aq, \(bq, \(Bq, \(Fn.
+
+Wed Jun 3 11:11:15 1992 James Clark (jjc@jclark)
+
+ * DviChar.c (Adobe_Symbol_map): Add wp.
+
+Tue Apr 21 09:21:59 1992 James Clark (jjc at jclark)
+
+ * GXditview.ad: Bind n, p, q keys to NextPage, PreviousPage and
+ Quit actions.
+
+ * xditview.c (RerasterizeAction): New function.
+ (xditview_actions): Add RerasterizeAction.
+ * GXditview.ad: Bind r key to Rerasterize action.
+
+Fri Apr 17 08:25:36 1992 James Clark (jjc at jclark)
+
+ * xditview.c: Add -filename option.
+ (main): Copy any -filename argument into current_file_name.
+
+Mon Mar 16 10:21:58 1992 James Clark (jjc at jclark)
+
+ * tmac.X: Load tmac.pspic.
+
+Sun Mar 8 11:27:19 1992 James Clark (jjc at jclark)
+
+ * Lex.c (GetLine, GetWord, GetNumber): Rewrite.
+
+Sat Oct 12 22:58:52 1991 James Clark (jjc at jclark)
+
+ * Dvi.c (SetDevice): If the size change request is refused but a
+ larger geometry is offered, request that.
+
+Wed Oct 9 12:27:48 1991 James Clark (jjc at jclark)
+
+ * font.c (InstallFontSizes): Ignore FontNameAverageWidth component.
+
+ * Dvi.c (default_font_map): Add `adobe' to font names to avoid
+ ambiguity.
+
+ * FontMap: New file.
+ * FontMap.X100, FontMap.X75: Deleted.
+ * xtotroff.c (main, usage): Add -s and -r options.
+ (MapFont): Change the font pattern to have the selected resolution and
+ size.
+ * Imakefile (install.dev): Use FontMap and supply appropriate -s
+ and -r options.
+
+ * xtotroff.c (MapFont): Check for ambiguity by comparing canonicalized
+ font names.
+
+ * DviP.h (DviFontList): Add initialized and scalable members.
+ (font.c): Add support for scalable fonts based on R5 xditview.
+
+ * DviChar.c: Use xmalloc rather than malloc.
+ * xditview.c (xmalloc): New function.
+ * xtotroff.c (xmalloc): New function.
+ * other files: Use XtMalloc and XtFree instead of malloc and free.
+
+Thu Aug 29 20:15:31 1991 James Clark (jjc at jclark)
+
+ * draw.c (setGC): Do multiplication in floating point to avoid
+ overflow.
+
+Tue Aug 13 12:04:41 1991 James Clark (jjc at jclark)
+
+ * draw.c (FakeCharacter): Remove casts in definition of pack2.
+
+Tue Jul 30 11:42:39 1991 James Clark (jjc at jclark)
+
+ * tmac.Xps: New file.
+ * Imakefile (install): Install tmac.Xps.
+
+Tue Jul 2 09:31:37 1991 James Clark (jjc at jclark)
+
+ * xtotroff.c (main): Pass argv[0] to usage().
+
+Sun Jun 30 12:34:06 1991 James Clark (jjc at jclark)
+
+ * xtotroff.c (MapFont): Handle the case where XLoadQueryFont
+ returns NULL.
+
+Sat Jun 29 12:32:52 1991 James Clark (jjc at jclark)
+
+ * Imakefile: Use ../gendef to generate path.h.
+
+Sun Jun 16 13:26:34 1991 James Clark (jjc at jclark)
+
+ * Imakefile (depend.o): Change to device.o.
+
+Sun Jun 2 12:17:56 1991 James Clark (jjc at jclark)
+
+ * Imakefile: Remove spaces from the beginning of variable
+ assignment lines.
+
+Sun May 26 14:14:01 1991 James Clark (jjc at jclark)
+
+ * xditview.c (Syntax): Update.
+
+ * Dvi.c (DviSaveToFile, SaveToFile): New functions.
+ (FindPage): Check that we're not readingTmp before checking for
+ end of file of normal input file.
+ (ClassPartInitialize): New function.
+ * Dvi.h: Add declaration of DviSaveToFile.
+ * DviP.h: Add save method to DviClassPart. Declare
+ InheritSaveToFile.
+ * xditview.c (DoPrint, Print, PrintAction): New functions.
+ * xditview.c: Add print menu entry.
+ * xditview.c: Provide printCommand application resource.
+ * lex.c: Don't output EOF to temporary file.
+
+ * Dvi.c (QueryGeometry): Check request->request_mode.
+
+ * Dvi.c (SetDevice): New function.
+ (SetDeviceResolution): Deleted.
+
+ * Dvi.c: Add resolution resource.
+ * DviP.h: Add definitions of XtNResolution and XtCResolution.
+ * xditview.c: Add -resolution argument.
+ * GXditview.ad: Add default for GXditview.height.
+ * Dvi.c (Initialize, SetDevice): Use default_resolution.
+
+ * Dvi.c: Make MY_HEIGHT and MY_WIDTH use the paperlength and
+ paperwidth commands in the DESC file.
+
+ * Dvi.c: Add SS font to default font map.
+
+ * draw.c: Rewritten so as not to assume device and display
+ resolution is the same.
+ * DviP.h: Include device.h. Add device_font member to DviFontList.
+ Add adjustable array to DviCharCache. Add text_x_width,
+ text_device_width, word_flag, device_font, device_font_number,
+ device, native, device_resolution, display_resolution,
+ paperlength, paperwidth, scale_factor, sizescale members.
+ * Dvi.c (Initialize): Initialize new variable used by draw.c.
+ (Destroy): Call device_destroy.
+ * font.c (MaxFontPosition): New function.
+ (LookupFontSizeBySize): Handle sizescale.
+ (InstallFont): Load the device font.
+ (ForgetFonts): New function.
+ (QueryDeviceFont): New function.
+ * parse.c (ParseInput): Handle t and u commands. Split off
+ character output into draw.c.
+ (ParseDeviceControl): Ignore res command. Use the device argument
+ to the T command.
+
+ * font.c (MapXNameToDviName): Ifdefed out.
+
+ * path.h: New file.
+ * device.c, device.h: New files.
+
+ * DviChar.c: Add entries for lB, rB, oq, lC, rC, md.
+
+ * INSTALL: New file.
+
+ * libxdvi: Merged into main directory.
+ * xtotroff.c, xditview.c: Change includes accordingly.
+
+ * devX75, devX100: Merged into main directory.
+ * xditview.man: Renamed to gxditview.man.
+
+ * Xditview.ad: Renamed to GXditview.ad.
+ * xditview.c (main): Use class of GXditview rather than xditview.
+
+ * Imakefile: New file.
+ * Makefile: Deleted.
+
+ * xtotroff.c (MapFont): Unlink output file before opening it.
+
+ * Started separate ChangeLog.
+
+________________________________________________________________________
+
+Copyright 1991-2020 Free Software Foundation, Inc.
+
+Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.
+
+Local Variables:
+version-control: never
+mode: change-log
+coding: latin-1
+End:
diff --git a/src/devices/xditview/DESC.in b/src/devices/xditview/DESC.in
new file mode 100644
index 0000000..172170c
--- /dev/null
+++ b/src/devices/xditview/DESC.in
@@ -0,0 +1,9 @@
+styles R I B BI
+fonts 6 0 0 0 0 0 S
+sizes 8 10 12 14 18 24 0
+res 75
+X11
+hor 1
+vert 1
+unitwidth 10
+postpro gxditview
diff --git a/src/devices/xditview/Dvi.c b/src/devices/xditview/Dvi.c
new file mode 100644
index 0000000..3520676
--- /dev/null
+++ b/src/devices/xditview/Dvi.c
@@ -0,0 +1,603 @@
+#include <config.h>
+
+#ifndef SABER
+#ifndef lint
+static char Xrcsid[] = "$XConsortium: Dvi.c,v 1.9 89/12/10 16:12:25 rws Exp $";
+#endif /* lint */
+#endif /* SABER */
+
+/*
+ * Dvi.c - Dvi display widget
+ *
+ */
+
+#define XtStrlen(s) ((s) ? strlen(s) : 0)
+
+ /* The following are defined for the reader's convenience. Any
+ Xt..Field macro in this code just refers to some field in
+ one of the substructures of the WidgetRec. */
+
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <X11/Xmu/Converters.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "DviP.h"
+#include "font.h"
+#include "page.h"
+#include "parse.h"
+
+/****************************************************************
+ *
+ * Full class record constant
+ *
+ ****************************************************************/
+
+/* Private Data */
+
+static char default_font_map_1[] = "\
+TR -adobe-times-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+TI -adobe-times-medium-i-normal--*-100-*-*-*-*-iso8859-1\n\
+TB -adobe-times-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+TBI -adobe-times-bold-i-normal--*-100-*-*-*-*-iso8859-1\n\
+CR -adobe-courier-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+CI -adobe-courier-medium-o-normal--*-100-*-*-*-*-iso8859-1\n\
+CB -adobe-courier-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+CBI -adobe-courier-bold-o-normal--*-100-*-*-*-*-iso8859-1\n\
+";
+static char default_font_map_2[] = "\
+HR -adobe-helvetica-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+HI -adobe-helvetica-medium-o-normal--*-100-*-*-*-*-iso8859-1\n\
+HB -adobe-helvetica-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+HBI -adobe-helvetica-bold-o-normal--*-100-*-*-*-*-iso8859-1\n\
+";
+static char default_font_map_3[] = "\
+NR -adobe-new century schoolbook-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+NI -adobe-new century schoolbook-medium-i-normal--*-100-*-*-*-*-iso8859-1\n\
+NB -adobe-new century schoolbook-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+NBI -adobe-new century schoolbook-bold-i-normal--*-100-*-*-*-*-iso8859-1\n\
+S -adobe-symbol-medium-r-normal--*-100-*-*-*-*-adobe-fontspecific\n\
+SS -adobe-symbol-medium-r-normal--*-100-*-*-*-*-adobe-fontspecific\n\
+";
+
+#define offset(field) XtOffset(DviWidget, field)
+
+#define MY_WIDTH(dw) ((int)(dw->dvi.paperwidth * dw->dvi.scale_factor + .5))
+#define MY_HEIGHT(dw) ((int)(dw->dvi.paperlength * dw->dvi.scale_factor + .5))
+
+static XtResource resources[] = {
+ {(String)XtNfontMap, (String)XtCFontMap, (String)XtRString,
+ sizeof (char *), offset(dvi.font_map_string),
+ (String)XtRString, NULL /* set in code */},
+ {(String)XtNforeground, (String)XtCForeground, (String)XtRPixel,
+ sizeof (unsigned long), offset(dvi.foreground),
+ (String)XtRString, (XtPointer)"XtDefaultForeground"},
+ {(String)XtNbackground, (String)XtCBackground, (String)XtRPixel,
+ sizeof (unsigned long), offset(dvi.background),
+ (String)XtRString, (XtPointer)"XtDefaultBackground"},
+ {(String)XtNpageNumber, (String)XtCPageNumber, (String)XtRInt,
+ sizeof (int), offset(dvi.requested_page),
+ (String)XtRString, (XtPointer)"1"},
+ {(String)XtNlastPageNumber, (String)XtCLastPageNumber, (String)XtRInt,
+ sizeof (int), offset (dvi.last_page),
+ (String)XtRString, (XtPointer)"0"},
+ {(String)XtNfile, (String)XtCFile, (String)XtRFile,
+ sizeof (FILE *), offset (dvi.file),
+ (String)XtRFile, (XtPointer)0},
+ {(String)XtNseek, (String)XtCSeek, (String)XtRBoolean,
+ sizeof (Boolean), offset(dvi.seek),
+ (String)XtRString, (XtPointer)"false"},
+ {(String)XtNfont, (String)XtCFont, (String)XtRFontStruct,
+ sizeof (XFontStruct *), offset(dvi.default_font),
+ (String)XtRString, (XtPointer)"xtdefaultfont"},
+ {(String)XtNbackingStore, (String)XtCBackingStore, (String)XtRBackingStore,
+ sizeof (int), offset(dvi.backing_store),
+ (String)XtRString, (XtPointer)"default"},
+ {(String)XtNnoPolyText, (String)XtCNoPolyText, (String)XtRBoolean,
+ sizeof (Boolean), offset(dvi.noPolyText),
+ (String)XtRString, (XtPointer)"false"},
+ {(String)XtNresolution, (String)XtCResolution, (String)XtRInt,
+ sizeof(int), offset(dvi.default_resolution),
+ (String)XtRString, (XtPointer)"75"},
+};
+
+#undef offset
+
+static void ClassInitialize (void);
+static void ClassPartInitialize(WidgetClass);
+static void Initialize(Widget, Widget, ArgList, Cardinal *);
+static void Realize (Widget, XtValueMask *, XSetWindowAttributes *);
+static void Destroy (Widget);
+static void Redisplay (Widget, XEvent *, Region);
+static Boolean SetValues (Widget, Widget, Widget,
+ ArgList, Cardinal *);
+static Boolean SetValuesHook (Widget, ArgList, Cardinal *);
+static XtGeometryResult QueryGeometry (Widget, XtWidgetGeometry *,
+ XtWidgetGeometry *);
+static void ShowDvi (DviWidget);
+static void CloseFile (DviWidget);
+static void OpenFile (DviWidget);
+static void FindPage (DviWidget);
+
+static void SaveToFile (Widget, FILE *);
+
+DviClassRec dviClassRec = {
+{
+ &widgetClassRec, /* superclass */
+ (String)"Dvi", /* class_name */
+ sizeof(DviRec), /* size */
+ ClassInitialize, /* class_initialize */
+ ClassPartInitialize, /* class_part_initialize */
+ FALSE, /* class_inited */
+ Initialize, /* initialize */
+ NULL, /* initialize_hook */
+ Realize, /* realize */
+ NULL, /* actions */
+ 0, /* num_actions */
+ resources, /* resources */
+ XtNumber(resources), /* resource_count */
+ NULLQUARK, /* xrm_class */
+ FALSE, /* compress_motion */
+ TRUE, /* compress_exposure */
+ TRUE, /* compress_enterleave */
+ FALSE, /* visible_interest */
+ Destroy, /* destroy */
+ NULL, /* resize */
+ Redisplay, /* expose */
+ SetValues, /* set_values */
+ SetValuesHook, /* set_values_hook */
+ NULL, /* set_values_almost */
+ NULL, /* get_values_hook */
+ NULL, /* accept_focus */
+ XtVersion, /* version */
+ NULL, /* callback_private */
+ 0, /* tm_table */
+ QueryGeometry, /* query_geometry */
+ NULL, /* display_accelerator */
+ NULL /* extension */
+},{
+ SaveToFile, /* save */
+},
+};
+
+WidgetClass dviWidgetClass = (WidgetClass) &dviClassRec;
+
+static void ClassInitialize (void)
+{
+ int len1 = strlen(default_font_map_1);
+ int len2 = strlen(default_font_map_2);
+ int len3 = strlen(default_font_map_3);
+ char *dfm = XtMalloc(len1 + len2 + len3 + 1);
+ char *ptr = dfm;
+ strcpy(ptr, default_font_map_1); ptr += len1;
+ strcpy(ptr, default_font_map_2); ptr += len2;
+ strcpy(ptr, default_font_map_3);
+ resources[0].default_addr = dfm;
+
+ XtAddConverter( XtRString, XtRBackingStore, XmuCvtStringToBackingStore,
+ NULL, 0 );
+}
+
+/****************************************************************
+ *
+ * Private Procedures
+ *
+ ****************************************************************/
+
+/* ARGSUSED */
+static void Initialize(Widget request, Widget new_wd,
+ ArgList args, Cardinal *num_args)
+{
+ DviWidget dw = (DviWidget) new_wd;
+
+ dw->dvi.current_page = 0;
+ dw->dvi.font_map = 0;
+ dw->dvi.cache.index = 0;
+ dw->dvi.text_x_width = 0;
+ dw->dvi.text_device_width = 0;
+ dw->dvi.word_flag = 0;
+ dw->dvi.file = 0;
+ dw->dvi.tmpFile = 0;
+ dw->dvi.state = 0;
+ dw->dvi.readingTmp = 0;
+ dw->dvi.cache.char_index = 0;
+ dw->dvi.cache.font_size = -1;
+ dw->dvi.cache.font_number = -1;
+ dw->dvi.cache.adjustable[0] = 0;
+ dw->dvi.file_map = 0;
+ dw->dvi.fonts = 0;
+ dw->dvi.seek = False;
+ dw->dvi.device_resolution = dw->dvi.default_resolution;
+ dw->dvi.display_resolution = dw->dvi.default_resolution;
+ dw->dvi.paperlength = dw->dvi.default_resolution*11;
+ dw->dvi.paperwidth = (dw->dvi.default_resolution*8
+ + dw->dvi.default_resolution/2);
+ dw->dvi.scale_factor = 1.0;
+ dw->dvi.sizescale = 1;
+ dw->dvi.line_thickness = -1;
+ dw->dvi.line_width = 1;
+ dw->dvi.fill = DVI_FILL_MAX;
+ dw->dvi.device_font = 0;
+ dw->dvi.device_font_number = -1;
+ dw->dvi.device = 0;
+ dw->dvi.native = 0;
+
+ request = request; /* unused; suppress compiler warning */
+ args = args;
+ num_args = num_args;
+}
+
+#include "gray1.bm"
+#include "gray2.bm"
+#include "gray3.bm"
+#include "gray4.bm"
+#include "gray5.bm"
+#include "gray6.bm"
+#include "gray7.bm"
+#include "gray8.bm"
+
+static void
+Realize (Widget w, XtValueMask *valueMask, XSetWindowAttributes *attrs)
+{
+ DviWidget dw = (DviWidget) w;
+ XGCValues values;
+
+ if (dw->dvi.backing_store != Always + WhenMapped + NotUseful) {
+ attrs->backing_store = dw->dvi.backing_store;
+ *valueMask |= CWBackingStore;
+ }
+ XtCreateWindow (w, (unsigned)InputOutput, (Visual *) CopyFromParent,
+ *valueMask, attrs);
+ values.foreground = dw->dvi.foreground;
+ values.cap_style = CapRound;
+ values.join_style = JoinRound;
+ values.line_width = dw->dvi.line_width;
+ dw->dvi.normal_GC = XCreateGC (XtDisplay (w), XtWindow (w),
+ GCForeground|GCCapStyle|GCJoinStyle
+ |GCLineWidth,
+ &values);
+ dw->dvi.gray[0] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray1_bits,
+ gray1_width, gray1_height);
+ dw->dvi.gray[1] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray2_bits,
+ gray2_width, gray2_height);
+ dw->dvi.gray[2] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray3_bits,
+ gray3_width, gray3_height);
+ dw->dvi.gray[3] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray4_bits,
+ gray4_width, gray4_height);
+ dw->dvi.gray[4] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray5_bits,
+ gray5_width, gray5_height);
+ dw->dvi.gray[5] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray6_bits,
+ gray6_width, gray6_height);
+ dw->dvi.gray[6] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray7_bits,
+ gray7_width, gray7_height);
+ dw->dvi.gray[7] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray8_bits,
+ gray8_width, gray8_height);
+ values.background = dw->dvi.background;
+ values.stipple = dw->dvi.gray[5];
+ dw->dvi.fill_GC = XCreateGC (XtDisplay (w), XtWindow (w),
+ GCForeground|GCBackground|GCStipple,
+ &values);
+
+ dw->dvi.fill_type = 9;
+
+ if (dw->dvi.file)
+ OpenFile (dw);
+ ParseFontMap (dw);
+}
+
+static void
+Destroy(Widget w)
+{
+ DviWidget dw = (DviWidget) w;
+
+ XFreeGC (XtDisplay (w), dw->dvi.normal_GC);
+ XFreeGC (XtDisplay (w), dw->dvi.fill_GC);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[0]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[1]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[2]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[3]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[4]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[5]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[6]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[7]);
+ DestroyFontMap (dw->dvi.font_map);
+ DestroyFileMap (dw->dvi.file_map);
+ device_destroy (dw->dvi.device);
+}
+
+/*
+ * Repaint the widget window
+ */
+
+/* ARGSUSED */
+static void
+Redisplay(Widget w, XEvent *event, Region region)
+{
+ DviWidget dw = (DviWidget) w;
+ XRectangle extents;
+
+ XClipBox (region, &extents);
+ dw->dvi.extents.x1 = extents.x;
+ dw->dvi.extents.y1 = extents.y;
+ dw->dvi.extents.x2 = extents.x + extents.width;
+ dw->dvi.extents.y2 = extents.y + extents.height;
+ ShowDvi (dw);
+
+ event = event; /* unused; suppress compiler warning */
+}
+
+/*
+ * Set specified arguments into widget
+ */
+/* ARGSUSED */
+static Boolean
+SetValues (Widget wcurrent, Widget wrequest, Widget wnew,
+ ArgList args, Cardinal *num_args)
+{
+ Boolean redisplay = FALSE;
+ char *new_map;
+ int cur, req;
+ DviWidget current = (DviWidget)wcurrent;
+ DviWidget request = (DviWidget)wrequest;
+ DviWidget new_wd = (DviWidget)wnew;
+
+ if (current->dvi.font_map_string != request->dvi.font_map_string) {
+ new_map = XtMalloc (strlen (request->dvi.font_map_string) + 1);
+ if (new_map) {
+ redisplay = TRUE;
+ strcpy (new_map, request->dvi.font_map_string);
+ new_wd->dvi.font_map_string = new_map;
+ if (current->dvi.font_map_string)
+ XtFree (current->dvi.font_map_string);
+ current->dvi.font_map_string = 0;
+ ParseFontMap (new_wd);
+ }
+ }
+
+ req = request->dvi.requested_page;
+ cur = current->dvi.requested_page;
+ if (cur != req) {
+ if (!request->dvi.file)
+ req = 0;
+ else {
+ if (req < 1)
+ req = 1;
+ if (current->dvi.last_page != 0 &&
+ req > current->dvi.last_page)
+ req = current->dvi.last_page;
+ }
+ if (cur != req)
+ redisplay = TRUE;
+ new_wd->dvi.requested_page = req;
+ if (current->dvi.last_page == 0 && req > cur)
+ FindPage (new_wd);
+ }
+
+ args = args; /* unused; suppress compiler warning */
+ num_args = num_args;
+
+ return redisplay;
+}
+
+/*
+ * use the set_values_hook entry to check when
+ * the file is set
+ */
+
+static Boolean
+SetValuesHook (Widget wdw, ArgList args, Cardinal *num_argsp)
+{
+ Cardinal i;
+ DviWidget dw = (DviWidget)wdw;
+
+ for (i = 0; i < *num_argsp; i++) {
+ if (!strcmp (args[i].name, XtNfile)) {
+ CloseFile (dw);
+ OpenFile (dw);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void CloseFile (DviWidget dw)
+{
+ if (dw->dvi.tmpFile)
+ fclose (dw->dvi.tmpFile);
+ ForgetPagePositions (dw);
+}
+
+static void OpenFile (DviWidget dw)
+{
+ dw->dvi.tmpFile = 0;
+ if (!dw->dvi.seek)
+ dw->dvi.tmpFile = tmpfile();
+ dw->dvi.requested_page = 1;
+ dw->dvi.last_page = 0;
+}
+
+static XtGeometryResult
+QueryGeometry (Widget w, XtWidgetGeometry *request,
+ XtWidgetGeometry *geometry_return)
+{
+ XtGeometryResult ret;
+ DviWidget dw = (DviWidget) w;
+
+ ret = XtGeometryYes;
+ if (((request->request_mode & CWWidth)
+ && request->width < MY_WIDTH(dw))
+ || ((request->request_mode & CWHeight)
+ && request->height < MY_HEIGHT(dw)))
+ ret = XtGeometryAlmost;
+ geometry_return->width = MY_WIDTH(dw);
+ geometry_return->height = MY_HEIGHT(dw);
+ geometry_return->request_mode = CWWidth|CWHeight;
+ return ret;
+}
+
+void
+SetDevice (DviWidget dw, const char *name)
+{
+ XtWidgetGeometry request, reply;
+ XtGeometryResult ret;
+
+ ForgetFonts (dw);
+ dw->dvi.device = device_load (name);
+ if (!dw->dvi.device)
+ return;
+ dw->dvi.sizescale = dw->dvi.device->sizescale;
+ dw->dvi.device_resolution = dw->dvi.device->res;
+ dw->dvi.native = dw->dvi.device->X11;
+ dw->dvi.paperlength = dw->dvi.device->paperlength;
+ dw->dvi.paperwidth = dw->dvi.device->paperwidth;
+ if (dw->dvi.native) {
+ dw->dvi.display_resolution = dw->dvi.device_resolution;
+ dw->dvi.scale_factor = 1.0;
+ }
+ else {
+ dw->dvi.display_resolution = dw->dvi.default_resolution;
+ dw->dvi.scale_factor = ((double)dw->dvi.display_resolution
+ / dw->dvi.device_resolution);
+ }
+ request.request_mode = CWWidth|CWHeight;
+ request.width = MY_WIDTH(dw);
+ request.height = MY_HEIGHT(dw);
+ ret = XtMakeGeometryRequest ((Widget)dw, &request, &reply);
+ if (ret == XtGeometryAlmost
+ && reply.height >= request.height
+ && reply.width >= request.width) {
+ request.width = reply.width;
+ request.height = reply.height;
+ XtMakeGeometryRequest ((Widget)dw, &request, &reply);
+ }
+}
+
+static void
+ShowDvi (DviWidget dw)
+{
+ if (!dw->dvi.file) {
+ static char Error[] = "No file selected";
+
+ XSetFont (XtDisplay(dw), dw->dvi.normal_GC,
+ dw->dvi.default_font->fid);
+ XDrawString (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ 20, 20, Error, strlen (Error));
+ return;
+ }
+
+ FindPage (dw);
+
+ dw->dvi.display_enable = 1;
+ ParseInput (dw);
+ if (dw->dvi.last_page && dw->dvi.requested_page > dw->dvi.last_page)
+ dw->dvi.requested_page = dw->dvi.last_page;
+}
+
+static void
+FindPage (DviWidget dw)
+{
+ int i;
+ long file_position;
+
+ if (dw->dvi.requested_page < 1)
+ dw->dvi.requested_page = 1;
+
+ if (dw->dvi.last_page != 0 && dw->dvi.requested_page > dw->dvi.last_page)
+ dw->dvi.requested_page = dw->dvi.last_page;
+
+ file_position = SearchPagePosition (dw, dw->dvi.requested_page);
+ if (file_position != -1) {
+ FileSeek(dw, file_position);
+ dw->dvi.current_page = dw->dvi.requested_page;
+ } else {
+ for (i=dw->dvi.requested_page; i > 0; i--) {
+ file_position = SearchPagePosition (dw, i);
+ if (file_position != -1)
+ break;
+ }
+ if (file_position == -1)
+ file_position = 0;
+ FileSeek (dw, file_position);
+
+ dw->dvi.current_page = i;
+
+ dw->dvi.display_enable = 0;
+ while (dw->dvi.current_page != dw->dvi.requested_page) {
+ dw->dvi.current_page = ParseInput (dw);
+ /*
+ * at EOF, seek back to the beginning of this page.
+ */
+ if (!dw->dvi.readingTmp && feof (dw->dvi.file)) {
+ file_position = SearchPagePosition (dw,
+ dw->dvi.current_page);
+ if (file_position != -1)
+ FileSeek (dw, file_position);
+ dw->dvi.requested_page = dw->dvi.current_page;
+ break;
+ }
+ }
+ }
+}
+
+void DviSaveToFile(Widget w, FILE *fp)
+{
+ XtCheckSubclass(w, dviWidgetClass, NULL);
+ (*((DviWidgetClass) XtClass(w))->command_class.save)(w, fp);
+}
+
+static
+void SaveToFile(Widget w, FILE *fp)
+{
+ DviWidget dw = (DviWidget)w;
+ long pos;
+ int c;
+
+ if (dw->dvi.tmpFile) {
+ pos = ftell(dw->dvi.tmpFile);
+ if (dw->dvi.ungot) {
+ pos--;
+ dw->dvi.ungot = 0;
+ /* The ungot character is in the tmpFile, so we don't
+ want to read it from file. */
+ (void)getc(dw->dvi.file);
+ }
+ }
+ else
+ pos = ftell(dw->dvi.file);
+ FileSeek(dw, 0L);
+ while (DviGetC(dw, &c) != EOF)
+ if (putc(c, fp) == EOF) {
+ /* XXX print error message */
+ break;
+ }
+ FileSeek(dw, pos);
+}
+
+static
+void ClassPartInitialize(WidgetClass widget_class)
+{
+ DviWidgetClass wc = (DviWidgetClass)widget_class;
+ DviWidgetClass super = (DviWidgetClass) wc->core_class.superclass;
+ if (wc->command_class.save == InheritSaveToFile)
+ wc->command_class.save = super->command_class.save;
+}
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/Dvi.h b/src/devices/xditview/Dvi.h
new file mode 100644
index 0000000..bf97374
--- /dev/null
+++ b/src/devices/xditview/Dvi.h
@@ -0,0 +1,46 @@
+/*
+* $XConsortium: Dvi.h,v 1.4 89/07/21 14:22:06 jim Exp $
+*/
+
+#ifndef _XtDvi_h
+#define _XtDvi_h
+
+/***********************************************************************
+ *
+ * Dvi Widget
+ *
+ ***********************************************************************/
+
+/* Parameters:
+
+ Name Class RepType Default Value
+ ---- ----- ------- -------------
+ background Background pixel White
+ foreground Foreground Pixel Black
+ fontMap FontMap char * ...
+ pageNumber PageNumber int 1
+*/
+
+#define XtNfontMap (String)"fontMap"
+#define XtNpageNumber (String)"pageNumber"
+#define XtNlastPageNumber (String)"lastPageNumber"
+#define XtNnoPolyText (String)"noPolyText"
+#define XtNseek (String)"seek"
+#define XtNresolution (String)"resolution"
+
+#define XtCFontMap (String)"FontMap"
+#define XtCPageNumber (String)"PageNumber"
+#define XtCLastPageNumber (String)"LastPageNumber"
+#define XtCNoPolyText (String)"NoPolyText"
+#define XtCSeek (String)"Seek"
+#define XtCResolution (String)"Resolution"
+
+typedef struct _DviRec *DviWidget; /* completely defined in DviP.h */
+typedef struct _DviClassRec *DviWidgetClass; /* completely defined in DviP.h */
+
+extern WidgetClass dviWidgetClass;
+
+void DviSaveToFile(Widget, FILE *);
+
+#endif /* _XtDvi_h */
+/* DON'T ADD STUFF AFTER THIS #endif */
diff --git a/src/devices/xditview/DviP.h b/src/devices/xditview/DviP.h
new file mode 100644
index 0000000..8b71c69
--- /dev/null
+++ b/src/devices/xditview/DviP.h
@@ -0,0 +1,235 @@
+/*
+ * $XConsortium: DviP.h,v 1.5 89/07/22 19:44:08 keith Exp $
+ */
+
+/*
+ * DviP.h - Private definitions for Dvi widget
+ */
+
+#ifndef _XtDviP_h
+#define _XtDviP_h
+
+#include "Dvi.h"
+#include "DviChar.h"
+#include "device.h"
+
+/***********************************************************************
+ *
+ * Dvi Widget Private Data
+ *
+ ***********************************************************************/
+
+/************************************
+ *
+ * Class structure
+ *
+ ***********************************/
+
+/* Type for save method. */
+
+typedef void (*DviSaveProc)(Widget, FILE *);
+
+/*
+ * New fields for the Dvi widget class record
+ */
+
+
+typedef struct _DviClass {
+ DviSaveProc save;
+} DviClassPart;
+
+/*
+ * Full class record declaration
+ */
+
+typedef struct _DviClassRec {
+ CoreClassPart core_class;
+ DviClassPart command_class;
+} DviClassRec;
+
+extern DviClassRec dviClassRec;
+
+/***************************************
+ *
+ * Instance (widget) structure
+ *
+ **************************************/
+
+/*
+ * a list of fonts we've used for this widget
+ */
+
+typedef struct _dviFontSizeList {
+ struct _dviFontSizeList *next;
+ int size;
+ char *x_name;
+ XFontStruct *font;
+ int doesnt_exist;
+} DviFontSizeList;
+
+typedef struct _dviFontList {
+ struct _dviFontList *next;
+ char *dvi_name;
+ char *x_name;
+ int dvi_number;
+ Boolean initialized;
+ Boolean scalable;
+ DviFontSizeList *sizes;
+ DviCharNameMap *char_map;
+ DeviceFont *device_font;
+} DviFontList;
+
+typedef struct _dviFontMap {
+ struct _dviFontMap *next;
+ char *dvi_name;
+ char *x_name;
+} DviFontMap;
+
+#define DVI_TEXT_CACHE_SIZE 256
+#define DVI_CHAR_CACHE_SIZE 1024
+
+typedef struct _dviCharCache {
+ XTextItem cache[DVI_TEXT_CACHE_SIZE];
+ char adjustable[DVI_TEXT_CACHE_SIZE];
+ char char_cache[DVI_CHAR_CACHE_SIZE];
+ int index;
+ int max;
+ int char_index;
+ int font_size;
+ int font_number;
+ XFontStruct *font;
+ int start_x, start_y;
+ int x, y;
+} DviCharCache;
+
+typedef struct _dviState {
+ struct _dviState *next;
+ int font_size;
+ int font_number;
+ int x;
+ int y;
+} DviState;
+
+typedef struct _dviFileMap {
+ struct _dviFileMap *next;
+ long position;
+ int page_number;
+} DviFileMap;
+
+/*
+ * New fields for the Dvi widget record
+ */
+
+typedef struct {
+ /*
+ * resource specifiable items
+ */
+ char *font_map_string;
+ unsigned long foreground;
+ unsigned long background;
+ int requested_page;
+ int last_page;
+ XFontStruct *default_font;
+ FILE *file;
+ Boolean noPolyText;
+ Boolean seek; /* file is 'seekable' */
+ int default_resolution;
+ /*
+ * private state
+ */
+ FILE *tmpFile; /* used when reading stdin */
+ char readingTmp; /* reading now from tmp */
+ char ungot; /* have ungetc'd a char */
+ GC normal_GC;
+ GC fill_GC;
+ DviFileMap *file_map;
+ DviFontList *fonts;
+ DviFontMap *font_map;
+ int current_page;
+ int font_size;
+ int font_number;
+ DeviceFont *device_font;
+ int device_font_number;
+ Device *device;
+ int native;
+ int device_resolution;
+ int display_resolution;
+ int paperlength;
+ int paperwidth;
+ double scale_factor; /* display res / device res */
+ int sizescale;
+ int line_thickness;
+ int line_width;
+
+#define DVI_FILL_MAX 1000
+
+ int fill;
+#define DVI_FILL_WHITE 0
+#define DVI_FILL_GRAY 1
+#define DVI_FILL_BLACK 2
+ int fill_type;
+ Pixmap gray[8];
+ int backing_store;
+ XFontStruct *font;
+ int display_enable;
+ struct ExposedExtents {
+ int x1, y1, x2, y2;
+ } extents;
+ DviState *state;
+ DviCharCache cache;
+ int text_x_width;
+ int text_device_width;
+ int word_flag;
+} DviPart;
+
+int DviGetAndPut(DviWidget, int *);
+#define DviGetIn(dw,cp)\
+ (dw->dvi.tmpFile ? (\
+ DviGetAndPut (dw, cp) \
+ ) :\
+ (*cp = getc (dw->dvi.file))\
+)
+
+#define DviGetC(dw, cp)\
+ (dw->dvi.readingTmp ? (\
+ ((*cp = getc (dw->dvi.tmpFile)) == EOF) ? (\
+ fseek (dw->dvi.tmpFile, 0l, 2),\
+ (dw->dvi.readingTmp = 0),\
+ DviGetIn (dw,cp)\
+ ) : (\
+ *cp\
+ )\
+ ) : (\
+ DviGetIn(dw,cp)\
+ )\
+)
+
+#define DviUngetC(dw, c)\
+ (dw->dvi.readingTmp ? (\
+ ungetc (c, dw->dvi.tmpFile)\
+ ) : ( \
+ (dw->dvi.ungot = 1),\
+ ungetc (c, dw->dvi.file)))
+
+/*
+ * Full widget declaration
+ */
+
+typedef struct _DviRec {
+ CorePart core;
+ DviPart dvi;
+} DviRec;
+
+#define InheritSaveToFile ((DviSaveProc)_XtInherit)
+
+XFontStruct *QueryFont (DviWidget, int, int);
+
+DviCharNameMap *QueryFontMap (DviWidget, int);
+
+DeviceFont *QueryDeviceFont (DviWidget, int);
+
+char *GetWord(DviWidget, char *, int);
+char *GetLine(DviWidget, char *, int);
+
+void SetDevice (DviWidget dw, const char *name);
+#endif /* _XtDviP_h */
diff --git a/src/devices/xditview/FontMap-X11 b/src/devices/xditview/FontMap-X11
new file mode 100644
index 0000000..90911f0
--- /dev/null
+++ b/src/devices/xditview/FontMap-X11
@@ -0,0 +1,17 @@
+TR -adobe-times-medium-r-normal--*-*-*-*-p-*-iso8859-1
+TI -adobe-times-medium-i-normal--*-*-*-*-p-*-iso8859-1
+TB -adobe-times-bold-r-normal--*-*-*-*-p-*-iso8859-1
+TBI -adobe-times-bold-i-normal--*-*-*-*-p-*-iso8859-1
+CR -adobe-courier-medium-r-normal--*-*-*-*-m-*-iso8859-1
+CI -adobe-courier-medium-o-normal--*-*-*-*-m-*-iso8859-1
+CB -adobe-courier-bold-r-normal--*-*-*-*-m-*-iso8859-1
+CBI -adobe-courier-bold-o-normal--*-*-*-*-m-*-iso8859-1
+HR -adobe-helvetica-medium-r-normal--*-*-*-*-p-*-iso8859-1
+HI -adobe-helvetica-medium-o-normal--*-*-*-*-p-*-iso8859-1
+HB -adobe-helvetica-bold-r-normal--*-*-*-*-p-*-iso8859-1
+HBI -adobe-helvetica-bold-o-normal--*-*-*-*-p-*-iso8859-1
+NR -adobe-new century schoolbook-medium-r-normal--*-*-*-*-p-*-iso8859-1
+NI -adobe-new century schoolbook-medium-i-normal--*-*-*-*-p-*-iso8859-1
+NB -adobe-new century schoolbook-bold-r-normal--*-*-*-*-p-*-iso8859-1
+NBI -adobe-new century schoolbook-bold-i-normal--*-*-*-*-p-*-iso8859-1
+S -adobe-symbol-medium-r-normal--*-*-*-*-p-*-adobe-fontspecific
diff --git a/src/devices/xditview/GXditview-color.ad b/src/devices/xditview/GXditview-color.ad
new file mode 100644
index 0000000..61abd8f
--- /dev/null
+++ b/src/devices/xditview/GXditview-color.ad
@@ -0,0 +1,15 @@
+
+#include "GXditview"
+
+GXditview.paned.viewport.Scrollbar.background: Thistle
+GXditview.paned.viewport.Scrollbar.foreground: Orchid
+GXditview.paned.viewport.Scrollbar.thumb: None
+GXditview.paned.viewport.dvi.background: LemonChiffon
+GXditview.paned.viewport.background: Thistle
+GXditview.paned.label.background: PeachPuff
+GXditview.menu.background: Gold
+GXditview.promptShell.promptDialog*background: Gold
+GXditview.promptShell.promptDialog.accept.background: DarkOliveGreen1
+GXditview.promptShell.promptDialog.cancel.background: RosyBrown1
+GXditview.promptShell.promptDialog.value.background: Khaki
+
diff --git a/src/devices/xditview/GXditview.ad b/src/devices/xditview/GXditview.ad
new file mode 100644
index 0000000..549f0d7
--- /dev/null
+++ b/src/devices/xditview/GXditview.ad
@@ -0,0 +1,71 @@
+
+GXditview*shapeStyle: rectangle
+
+GXditview.paned.allowResize: true
+GXditview.paned.label.skipAdjust: true
+GXditview.paned.viewport.skipAdjust: false
+GXditview.paned.viewport.showGrip: false
+GXditview.paned.viewport.allowVert: true
+GXditview.paned.viewport.allowHoriz: true
+GXditview.paned.viewport.forceBars: true
+! viewport size = papersize * resol + scrollbarthickness + 1
+! letter size paper
+!GXditview.paned.viewport.width: 652
+!GXditview.paned.viewport.height: 840
+! a4 size paper
+! 865 is wide enough for a page of X100-12.
+GXditview.paned.viewport.width: 865
+GXditview.paned.viewport.height: 892
+GXditview.paned.viewport.Scrollbar.thickness: 14
+
+GXditview.geometry: 865x892
+
+GXditview.paned.translations: #augment \
+ <Key>Next: NextPage()\n\
+ <Key>n: NextPage()\n\
+ <Key>space: NextPage()\n\
+ <Key>Return: NextPage()\n\
+ <Key>Prior: PreviousPage()\n\
+ <Key>p: PreviousPage()\n\
+ <Key>b: PreviousPage()\n\
+ <Key>BackSpace: PreviousPage()\n\
+ <Key>Delete: PreviousPage()\n\
+ <Key>g: SelectPage()\n\
+ <Key>o: OpenFile()\n\
+ <Key>r: Rerasterize()\n\
+ <Key>q: Quit()
+GXditview.paned.viewport.clip.translations: #augment \
+ <Btn1Down>: XawPositionSimpleMenu(menu) MenuPopup(menu)
+GXditview.paned.label.translations: #augment \
+ <Btn1Down>: XawPositionSimpleMenu(menu) MenuPopup(menu)
+GXditview.paned.viewport.vertical.accelerators: #override \
+ <Key>k: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>j: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Up: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Down: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()
+GXditview.paned.viewport.horizontal.accelerators: #override \
+ <Key>h: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>l: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Left: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Right: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()
+
+GXditview.menu.nextPage.label: Next Page
+GXditview.menu.previousPage.label: Previous Page
+GXditview.menu.selectPage.label: Goto Page
+GXditview.menu.print.label: Print
+GXditview.menu.openFile.label: Open
+GXditview.menu.quit.label: Quit
+
+GXditview.promptShell.allowShellResize: true
+GXditview.promptShell.promptDialog.value.translations: #override \
+ <Key>Return: Accept() \n\
+ <Key>Escape: Cancel()
+
+GXditview.promptShell.promptDialog.accept.label: Accept
+GXditview.promptShell.promptDialog.accept.translations: #override \
+ <BtnUp>: Accept() unset()
+
+GXditview.promptShell.promptDialog.cancel.label: Cancel
+GXditview.promptShell.promptDialog.cancel.translations: #override \
+ <BtnUp>: Cancel() unset()
+
diff --git a/src/devices/xditview/Menu.h b/src/devices/xditview/Menu.h
new file mode 100644
index 0000000..c306b27
--- /dev/null
+++ b/src/devices/xditview/Menu.h
@@ -0,0 +1,46 @@
+/*
+ * $XConsortium: Menu.h,v 1.2 89/07/21 14:22:10 jim Exp $
+ */
+
+#ifndef _XtMenu_h
+#define _XtMenu_h
+
+/***********************************************************************
+ *
+ * Menu Widget
+ *
+ ***********************************************************************/
+
+/* Parameters:
+
+ Name Class RepType Default Value
+ ---- ----- ------- -------------
+ background Background pixel White
+ border BorderColor pixel Black
+ borderWidth BorderWidth int 1
+ height Height int 120
+ mappedWhenManaged MappedWhenManaged Boolean True
+ reverseVideo ReverseVideo Boolean False
+ width Width int 120
+ x Position int 0
+ y Position int 0
+
+*/
+
+#define XtNmenuEntries "menuEntries"
+#define XtNhorizontalPadding "horizontalPadding"
+#define XtNverticalPadding "verticalPadding"
+#define XtNselection "Selection"
+
+#define XtCMenuEntries "MenuEntries"
+#define XtCPadding "Padding"
+#define XtCSelection "Selection"
+
+typedef struct _MenuRec *MenuWidget; /* completely defined in MenuPrivate.h */
+typedef struct _MenuClassRec *MenuWidgetClass; /* completely defined in MenuPrivate.h */
+
+extern WidgetClass menuWidgetClass;
+
+extern Widget XawMenuCreate ();
+#endif /* _XtMenu_h */
+/* DON'T ADD STUFF AFTER THIS #endif */
diff --git a/src/devices/xditview/README b/src/devices/xditview/README
new file mode 100644
index 0000000..9de7869
--- /dev/null
+++ b/src/devices/xditview/README
@@ -0,0 +1,13 @@
+This is gxditview, an X11 previewer for groff based on MIT's xditview.
+This version can be used with the output of gtroff -Tps as well as
+with -TX75 and -TX100. You will need X11R5 or newer to install it (it
+might work on X11R4, but I haven't tested it.)
+
+Previously, gxditview was installed in the usual place for X binaries
+(e.g., /usr/bin/X11); you have to remove it manually.
+
+xditview is copyrighted by MIT under the usual X terms (see
+gxditview.man); the changes to that which come along with the groff package
+are in the public domain.
+
+Please report bugs at http://savannah.gnu.org/bugs/?group=groff.
diff --git a/src/devices/xditview/TODO b/src/devices/xditview/TODO
new file mode 100644
index 0000000..0b5b140
--- /dev/null
+++ b/src/devices/xditview/TODO
@@ -0,0 +1,21 @@
+Open the main window with the correct width and height, depending on
+both the selected device and the paper dimensions.
+
+Add a command-line option to specify the paper dimensions (similar to
+other groff devices).
+
+Better error handling.
+
+Resource and command-line option to specify font path.
+
+Resource to specify name of environment variable from which to get the
+font path.
+
+Have character substitutions (currently done in draw.c:FakeCharacter)
+specified in a resource (similar format to FontMap).
+
+The initial width of the dialog box should expand to accommodate the
+default value.
+
+Option in Print dialog to specify that only the current page should be
+printed.
diff --git a/src/devices/xditview/ad2c b/src/devices/xditview/ad2c
new file mode 100644
index 0000000..2ec45ed
--- /dev/null
+++ b/src/devices/xditview/ad2c
@@ -0,0 +1,64 @@
+#! /bin/sh
+#
+# ad2c : Convert app-defaults file to C strings decls.
+#
+# George Ferguson, ferguson@cs.rcohester.edu, 12 Nov 1990.
+# 19 Mar 1991: gf
+# Made it self-contained.
+# 6 Jan 1992: mycroft@gnu.ai.mit.edu (Charles Hannum)
+# Removed use of "-n" and ":read" label since Gnu and
+# IBM sed print pattern space on "n" command. Still works
+# with Sun sed, of course.
+# 7 Jan 1992: matthew@sunpix.East.Sun.COM (Matthew Stier)
+# Escape quotes after escaping backslashes.
+# 8 Jul 1992: Version 1.6
+# Manpage fixes.
+# 19 Apr 1993: Version 1.7
+# Remove comments that were inside the sed command since
+# some versions of sed don't like them. The comments are
+# now given here in the header.
+# 31 May 2004: Werner Lemberg <wl@gnu.org>
+# Force casts to 'String'.
+#
+# Comments on the script by line:
+# /^!/d Remove comments
+# /^$/d Remove blanks
+# s/\\/\\\\/g Escape backslashes...
+# s/\\$//g ...except the line continuation ones
+# s/"/\\"/g Escape quotes
+# s/^/"/ Add leading quote and cast
+# : test Establish label for later branch
+# /\\$/b slash Branch to label "slash" if line ends in backslash
+# s/$/",/ Otherwise add closing quote and comma...
+# p ...output the line...
+# d ...and clear the pattern space so it's not printed again
+# : slash Branch comes here if line ends in backslash
+# n Read next line, append to pattern space
+# [...] The "d" and "s" commands that follow just delete
+# comments and blank lines and escape control sequences
+# b test Branch up to see if the line ends in backslash or not
+#
+
+sed '
+/^!/d
+/^$/d
+s/\\/\\\\/g
+s/\\$//g
+s/"/\\"/g
+s/^/(String)"/
+: test
+/\\$/b slash
+s/$/",/
+p
+d
+: slash
+n
+/^!/d
+/^$/d
+s/"/\\"/g
+s/\\\\/\\/g
+s/\\n/\\\\n/g
+s/\\t/\\\\t/g
+s/\\f/\\\\f/g
+s/\\b/\\\\b/g
+b test' "$@"
diff --git a/src/devices/xditview/device.c b/src/devices/xditview/device.c
new file mode 100644
index 0000000..1eaafbe
--- /dev/null
+++ b/src/devices/xditview/device.c
@@ -0,0 +1,565 @@
+/* device.c */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xos.h>
+#include <X11/Intrinsic.h>
+
+#include "device.h"
+#include "defs.h"
+
+#ifndef isascii
+#define isascii(c) (1)
+#endif
+
+/* Name of environment variable containing path to be used for
+searching for device and font description files. */
+#define FONTPATH_ENV_VAR "GROFF_FONT_PATH"
+
+#define WS " \t\r\n"
+
+#ifndef INT_MIN
+/* Minimum and maximum values a 'signed int' can hold. */
+#define INT_MIN (-INT_MAX-1)
+#define INT_MAX 2147483647
+#endif
+
+#define CHAR_TABLE_SIZE 307
+
+struct _DeviceFont {
+ char *name;
+ int special;
+ DeviceFont *next;
+ Device *dev;
+ struct charinfo *char_table[CHAR_TABLE_SIZE];
+ struct charinfo *code_table[256];
+};
+
+struct charinfo {
+ int width;
+ int code;
+ struct charinfo *next;
+ struct charinfo *code_next;
+ char name[1];
+};
+
+static char *current_filename = 0;
+static int current_lineno = -1;
+
+static void error(const char *s);
+static FILE *open_device_file(const char *, const char *, char **);
+static DeviceFont *load_font(Device *, const char *);
+static Device *new_device(const char *);
+static DeviceFont *new_font(const char *, Device *);
+static void delete_font(DeviceFont *);
+static unsigned hash_name(const char *);
+static struct charinfo *add_char(DeviceFont *, const char *, int, int);
+static int read_charset_section(DeviceFont *, FILE *);
+static char *canonicalize_name(const char *);
+static int scale_round(int, int, int);
+
+static
+Device *new_device(const char *name)
+{
+ Device *dev;
+
+ dev = XtNew(Device);
+ dev->sizescale = 1;
+ dev->res = 0;
+ dev->unitwidth = 0;
+ dev->fonts = 0;
+ dev->X11 = 0;
+ dev->paperlength = 0;
+ dev->paperwidth = 0;
+ dev->name = XtNewString(name);
+ return dev;
+}
+
+void device_destroy(Device *dev)
+{
+ DeviceFont *f;
+
+ if (!dev)
+ return;
+ f = dev->fonts;
+ while (f) {
+ DeviceFont *tem = f;
+ f = f->next;
+ delete_font(tem);
+ }
+
+ XtFree(dev->name);
+ XtFree((char *)dev);
+}
+
+Device *device_load(const char *name)
+{
+ Device *dev;
+ FILE *fp;
+ int err = 0;
+ char buf[256];
+
+ fp = open_device_file(name, "DESC", &current_filename);
+ if (!fp)
+ return 0;
+ dev = new_device(name);
+ current_lineno = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *p;
+ current_lineno++;
+ p = strtok(buf, WS);
+ if (p) {
+ int *np = 0;
+ char *q;
+
+ if (strcmp(p, "charset") == 0)
+ break;
+ if (strcmp(p, "X11") == 0)
+ dev->X11 = 1;
+ else if (strcmp(p, "sizescale") == 0)
+ np = &dev->sizescale;
+ else if (strcmp(p, "res") == 0)
+ np = &dev->res;
+ else if (strcmp(p, "unitwidth") == 0)
+ np = &dev->unitwidth;
+ else if (strcmp(p, "paperwidth") == 0)
+ np = &dev->paperwidth;
+ else if (strcmp(p, "paperlength") == 0)
+ np = &dev->paperlength;
+
+ if (np) {
+ q = strtok((char *)0, WS);
+ if (!q || sscanf(q, "%d", np) != 1 || *np <= 0) {
+ error("bad argument");
+ err = 1;
+ break;
+ }
+ }
+ }
+ }
+ fclose(fp);
+ current_lineno = -1;
+ if (!err) {
+ if (dev->res == 0) {
+ error("missing res line");
+ err = 1;
+ }
+ else if (dev->unitwidth == 0) {
+ error("missing unitwidth line");
+ err = 1;
+ }
+ }
+ if (dev->paperlength == 0)
+ dev->paperlength = dev->res*11;
+ if (dev->paperwidth == 0)
+ dev->paperwidth = dev->res*8 + dev->res/2;
+ if (err) {
+ device_destroy(dev);
+ dev = 0;
+ }
+ XtFree(current_filename);
+ current_filename = 0;
+ return dev;
+}
+
+
+DeviceFont *device_find_font(Device *dev, const char *name)
+{
+ DeviceFont *f;
+
+ if (!dev)
+ return 0;
+ for (f = dev->fonts; f; f = f->next)
+ if (strcmp(f->name, name) == 0)
+ return f;
+ return load_font(dev, name);
+}
+
+static
+DeviceFont *load_font(Device *dev, const char *name)
+{
+ FILE *fp;
+ char buf[256];
+ DeviceFont *f;
+ int special = 0;
+
+ fp = open_device_file(dev->name, name, &current_filename);
+ if (!fp)
+ return 0;
+ current_lineno = 0;
+ for (;;) {
+ char *p;
+
+ if (!fgets(buf, sizeof(buf), fp)) {
+ error("no charset line");
+ return 0;
+ }
+ current_lineno++;
+ p = strtok(buf, WS);
+ /* charset must be on a line by itself */
+ if (p && strcmp(p, "charset") == 0 && strtok((char *)0, WS) == 0)
+ break;
+ if (p && strcmp(p, "special") == 0)
+ special = 1;
+ }
+ f = new_font(name, dev);
+ f->special = special;
+ if (!read_charset_section(f, fp)) {
+ delete_font(f);
+ f = 0;
+ }
+ else {
+ f->next = dev->fonts;
+ dev->fonts = f;
+ }
+ fclose(fp);
+ XtFree(current_filename);
+ current_filename = 0;
+ return f;
+}
+
+static
+DeviceFont *new_font(const char *name, Device *dev)
+{
+ int i;
+ DeviceFont *f;
+
+ f = XtNew(DeviceFont);
+ f->name = XtNewString(name);
+ f->dev = dev;
+ f->special = 0;
+ f->next = 0;
+ for (i = 0; i < CHAR_TABLE_SIZE; i++)
+ f->char_table[i] = 0;
+ for (i = 0; i < 256; i++)
+ f->code_table[i] = 0;
+ return f;
+}
+
+static
+void delete_font(DeviceFont *f)
+{
+ int i;
+
+ if (!f)
+ return;
+ XtFree(f->name);
+ for (i = 0; i < CHAR_TABLE_SIZE; i++) {
+ struct charinfo *ptr = f->char_table[i];
+ while (ptr) {
+ struct charinfo *tem = ptr;
+ ptr = ptr->next;
+ XtFree((char *)tem);
+ }
+ }
+ XtFree((char *)f);
+}
+
+
+static
+unsigned hash_name(const char *name)
+{
+ unsigned n = 0;
+ /* XXX do better than this */
+ while (*name)
+ n = (n << 1) ^ *name++;
+
+ return n;
+}
+
+static
+int scale_round(int n, int x, int y)
+{
+ int y2;
+
+ if (x == 0)
+ return 0;
+ y2 = y/2;
+ if (n >= 0) {
+ if (n <= (INT_MAX - y2)/x)
+ return (n*x + y2)/y;
+ return (int)(n*(double)x/(double)y + .5);
+ }
+ else {
+ if (-(unsigned)n <= (-(unsigned)INT_MIN - y2)/x)
+ return (n*x - y2)/y;
+ return (int)(n*(double)x/(double)y + .5);
+ }
+}
+
+static
+char *canonicalize_name(const char *s)
+{
+ static char ch[2];
+ if (s[0] == 'c' && s[1] == 'h' && s[2] == 'a' && s[3] == 'r') {
+ const char *p;
+ int n;
+
+ for (p = s + 4; *p; p++)
+ if (!isascii(*p) || !isdigit((unsigned char)*p))
+ return (char *)s;
+ n = atoi(s + 4);
+ if (n >= 0 && n <= 0xff) {
+ ch[0] = (char)n;
+ return ch;
+ }
+ }
+ return (char *)s;
+}
+
+/* Return 1 if the character is present in the font; widthp gets the
+width if non-null. */
+
+int device_char_width(DeviceFont *f, int ps, const char *name, int *widthp)
+{
+ struct charinfo *p;
+
+ name = canonicalize_name(name);
+ for (p = f->char_table[hash_name(name) % CHAR_TABLE_SIZE];; p = p->next) {
+ if (!p)
+ return 0;
+ if (strcmp(p->name, name) == 0)
+ break;
+ }
+ *widthp = scale_round(p->width, ps, f->dev->unitwidth);
+ return 1;
+}
+
+int device_code_width(DeviceFont *f, int ps, int code, int *widthp)
+{
+ struct charinfo *p;
+
+ for (p = f->code_table[code & 0xff];; p = p->code_next) {
+ if (!p)
+ return 0;
+ if (p->code == code)
+ break;
+ }
+ *widthp = scale_round(p->width, ps, f->dev->unitwidth);
+ return 1;
+}
+
+char *device_name_for_code(DeviceFont *f, int code)
+{
+ static struct charinfo *state = 0;
+ if (f)
+ state = f->code_table[code & 0xff];
+ for (; state; state = state->code_next)
+ if (state->code == code && state->name[0] != '\0') {
+ char *name = state->name;
+ state = state->code_next;
+ return name;
+ }
+ return 0;
+}
+
+int device_font_special(DeviceFont *f)
+{
+ return f->special;
+}
+
+static
+struct charinfo *add_char(DeviceFont *f, const char *name, int width, int code)
+{
+ struct charinfo **pp;
+ struct charinfo *ci;
+
+ name = canonicalize_name(name);
+ if (strcmp(name, "---") == 0)
+ name = "";
+
+ ci = (struct charinfo *)XtMalloc(XtOffsetOf(struct charinfo, name[0])
+ + strlen(name) + 1);
+
+ strcpy(ci->name, name);
+ ci->width = width;
+ ci->code = code;
+
+ if (*name != '\0') {
+ pp = &f->char_table[hash_name(name) % CHAR_TABLE_SIZE];
+ ci->next = *pp;
+ *pp = ci;
+ }
+ pp = &f->code_table[code & 0xff];
+ ci->code_next = *pp;
+ *pp = ci;
+ return ci;
+}
+
+/* Return non-zero for success. */
+
+static
+int read_charset_section(DeviceFont *f, FILE *fp)
+{
+ struct charinfo *last_charinfo = 0;
+ char buf[256];
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *name;
+ int width;
+ int code;
+ char *p;
+
+ current_lineno++;
+ name = strtok(buf, WS);
+ if (!name)
+ continue; /* ignore blank lines */
+ p = strtok((char *)0, WS);
+ if (!p) /* end of charset section */
+ break;
+ if (strcmp(p, "\"") == 0) {
+ if (!last_charinfo) {
+ error("first line of charset section cannot use '\"'");
+ return 0;
+ }
+ else
+ (void)add_char(f, name,
+ last_charinfo->width, last_charinfo->code);
+ }
+ else {
+ char *q;
+ if (sscanf(p, "%d", &width) != 1) {
+ error("bad width field");
+ return 0;
+ }
+ p = strtok((char *)0, WS);
+ if (!p) {
+ error("missing type field");
+ return 0;
+ }
+ p = strtok((char *)0, WS);
+ if (!p) {
+ error("missing code field");
+ return 0;
+ }
+ code = (int)strtol(p, &q, 0);
+ if (q == p) {
+ error("bad code field");
+ return 0;
+ }
+ last_charinfo = add_char(f, name, width, code);
+ }
+ }
+ return 1;
+}
+
+static
+FILE *find_file(const char *file, char **result)
+{
+ char *buf = NULL;
+ int bufsiz = 0;
+ int flen;
+ FILE *fp;
+ char *path;
+ char *env;
+
+ env = getenv(FONTPATH_ENV_VAR);
+ path = XtMalloc(((env && *env) ? strlen(env) + 1 : 0)
+ + strlen(FONTPATH) + 1);
+ *path = '\0';
+ if (env && *env) {
+ strcat(path, env);
+ strcat(path, ":");
+ }
+ strcat(path, FONTPATH);
+
+ *result = NULL;
+
+ if (file == NULL)
+ return NULL;
+ if (*file == '\0')
+ return NULL;
+
+ if (*file == '/') {
+ fp = fopen(file, "r");
+ if (fp)
+ *result = XtNewString(file);
+ return fp;
+ }
+
+ flen = strlen(file);
+
+ while (*path) {
+ int len;
+ char *start, *end;
+
+ start = path;
+ end = strchr(path, ':');
+ if (end)
+ path = end + 1;
+ else
+ path = end = strchr(path, '\0');
+ if (start >= end)
+ continue;
+ if (end[-1] == '/')
+ --end;
+ len = (end - start) + 1 + flen + 1;
+ if (len > bufsiz) {
+ if (buf)
+ buf = XtRealloc(buf, len);
+ else
+ buf = XtMalloc(len);
+ bufsiz = len;
+ }
+ memcpy(buf, start, end - start);
+ buf[end - start] = '/';
+ strcpy(buf + (end - start) + 1, file);
+ fp = fopen(buf, "r");
+ if (fp) {
+ *result = buf;
+ return fp;
+ }
+ }
+ XtFree(buf);
+ return NULL;
+}
+
+static
+FILE *open_device_file(const char *device_name, const char *file_name,
+ char **result)
+{
+ char *buf;
+ FILE *fp;
+
+ buf = XtMalloc(3 + strlen(device_name) + 1 + strlen(file_name) + 1);
+ sprintf(buf, "dev%s/%s", device_name, file_name);
+ fp = find_file(buf, result);
+ if (!fp) {
+ fprintf(stderr, "can't find device file '%s'\n", file_name);
+ fflush(stderr);
+ }
+ XtFree(buf);
+ return fp;
+}
+
+static
+void error(const char *s)
+{
+ if (current_filename) {
+ fprintf(stderr, "%s:", current_filename);
+ if (current_lineno > 0)
+ fprintf(stderr, "%d:", current_lineno);
+ putc(' ', stderr);
+ }
+ fputs(s, stderr);
+ putc('\n', stderr);
+ fflush(stderr);
+}
+
+/*
+Local Variables:
+c-indent-level: 4
+c-continued-statement-offset: 4
+c-brace-offset: -4
+c-argdecl-indent: 4
+c-label-offset: -4
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/device.h b/src/devices/xditview/device.h
new file mode 100644
index 0000000..6f2944b
--- /dev/null
+++ b/src/devices/xditview/device.h
@@ -0,0 +1,21 @@
+
+typedef struct _DeviceFont DeviceFont;
+
+typedef struct _Device {
+ char *name;
+ int sizescale;
+ int res;
+ int unitwidth;
+ int paperlength;
+ int paperwidth;
+ int X11;
+ DeviceFont *fonts;
+} Device;
+
+void device_destroy(Device *);
+Device *device_load(const char *);
+DeviceFont *device_find_font(Device *, const char *);
+int device_char_width(DeviceFont *, int, const char *, int *);
+char *device_name_for_code(DeviceFont *, int);
+int device_code_width(DeviceFont *, int, int, int *);
+int device_font_special(DeviceFont *);
diff --git a/src/devices/xditview/draw.c b/src/devices/xditview/draw.c
new file mode 100644
index 0000000..288d98a
--- /dev/null
+++ b/src/devices/xditview/draw.c
@@ -0,0 +1,709 @@
+/*
+ * draw.c
+ *
+ * accept dvi function calls and translate to X
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <math.h>
+
+/* math.h on a Sequent doesn't define M_PI, apparently */
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#include "DviP.h"
+#include "draw.h"
+#include "font.h"
+
+#define DeviceToX(dw, n) ((int)((n) * (dw)->dvi.scale_factor + .5))
+#define XPos(dw) (DeviceToX((dw), (dw)->dvi.state->x - \
+ (dw)->dvi.text_device_width) + (dw)->dvi.text_x_width)
+#define YPos(dw) (DeviceToX((dw), (dw)->dvi.state->y))
+
+/* forward reference */
+static int FakeCharacter(DviWidget, char *, int);
+
+/* shadowed by a macro definition in parse.c, and unused elsewhere */
+#if 0
+static void
+HorizontalMove(DviWidget dw, int delta)
+{
+ dw->dvi.state->x += delta;
+}
+#endif
+
+void
+HorizontalGoto(DviWidget dw, int NewPosition)
+{
+ dw->dvi.state->x = NewPosition;
+}
+
+void
+VerticalMove(DviWidget dw, int delta)
+{
+ dw->dvi.state->y += delta;
+}
+
+void
+VerticalGoto(DviWidget dw, int NewPosition)
+{
+ dw->dvi.state->y = NewPosition;
+}
+
+static void
+AdjustCacheDeltas (DviWidget dw)
+{
+ int extra;
+ int nadj;
+ int i;
+
+ nadj = 0;
+ extra = DeviceToX(dw, dw->dvi.text_device_width)
+ - dw->dvi.text_x_width;
+ if (extra == 0)
+ return;
+ for (i = 0; i <= dw->dvi.cache.index; i++)
+ if (dw->dvi.cache.adjustable[i])
+ ++nadj;
+ dw->dvi.text_x_width += extra;
+ if (nadj <= 1)
+ return;
+ for (i = 0; i <= dw->dvi.cache.index; i++)
+ if (dw->dvi.cache.adjustable[i]) {
+ int x;
+ int *deltap;
+
+ x = extra/nadj;
+ deltap = &dw->dvi.cache.cache[i].delta;
+#define MIN_DELTA 2
+ if (*deltap > 0 && x + *deltap < MIN_DELTA) {
+ x = MIN_DELTA - *deltap;
+ if (x <= 0)
+ *deltap = MIN_DELTA;
+ else
+ x = 0;
+ }
+ else
+ *deltap += x;
+ extra -= x;
+ --nadj;
+ dw->dvi.cache.adjustable[i] = 0;
+ }
+}
+
+void
+FlushCharCache (DviWidget dw)
+{
+ if (dw->dvi.cache.char_index != 0) {
+ AdjustCacheDeltas (dw);
+ XDrawText (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ dw->dvi.cache.start_x, dw->dvi.cache.start_y,
+ dw->dvi.cache.cache, dw->dvi.cache.index + 1);
+ }
+ dw->dvi.cache.index = 0;
+ dw->dvi.cache.max = DVI_TEXT_CACHE_SIZE;
+#if 0
+ if (dw->dvi.noPolyText)
+ dw->dvi.cache.max = 1;
+#endif
+ dw->dvi.cache.char_index = 0;
+ dw->dvi.cache.cache[0].nchars = 0;
+ dw->dvi.cache.start_x = dw->dvi.cache.x = XPos (dw);
+ dw->dvi.cache.start_y = dw->dvi.cache.y = YPos (dw);
+}
+
+void
+Newline (DviWidget dw)
+{
+ FlushCharCache (dw);
+ dw->dvi.text_x_width = dw->dvi.text_device_width = 0;
+ dw->dvi.word_flag = 0;
+}
+
+void
+Word (DviWidget dw)
+{
+ dw->dvi.word_flag = 1;
+}
+
+#define charWidth(fi,c) (\
+ (fi)->per_char ?\
+ (fi)->per_char[(c) - (fi)->min_char_or_byte2].width\
+ :\
+ (fi)->max_bounds.width\
+)
+
+
+static
+int charExists (XFontStruct *fi, int c)
+{
+ XCharStruct *p;
+
+ /* 'c' is always >= 0 */
+ if (fi->per_char == NULL
+ || (unsigned int)c < fi->min_char_or_byte2
+ || (unsigned int)c > fi->max_char_or_byte2)
+ return 0;
+ p = fi->per_char + (c - fi->min_char_or_byte2);
+ return (p->lbearing != 0 || p->rbearing != 0 || p->width != 0
+ || p->ascent != 0 || p->descent != 0 || p->attributes != 0);
+}
+
+/* 'wid' is in device units */
+static void
+DoCharacter (DviWidget dw, int c, int wid)
+{
+ register XFontStruct *font;
+ register XTextItem *text;
+ int x, y;
+
+ x = XPos(dw);
+ y = YPos(dw);
+
+ /*
+ * quick and dirty extents calculation:
+ */
+ if (!(y + 24 >= dw->dvi.extents.y1
+ && y - 24 <= dw->dvi.extents.y2
+#if 0
+ && x + 24 >= dw->dvi.extents.x1
+ && x - 24 <= dw->dvi.extents.x2
+#endif
+ ))
+ return;
+
+ if (y != dw->dvi.cache.y
+ || dw->dvi.cache.char_index >= DVI_CHAR_CACHE_SIZE) {
+ FlushCharCache (dw);
+ x = dw->dvi.cache.x;
+ dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
+ }
+ /*
+ * load a new font, if the current block is not empty,
+ * step to the next.
+ */
+ if (dw->dvi.cache.font_size != dw->dvi.state->font_size ||
+ dw->dvi.cache.font_number != dw->dvi.state->font_number)
+ {
+ FlushCharCache (dw);
+ x = dw->dvi.cache.x;
+ dw->dvi.cache.font_size = dw->dvi.state->font_size;
+ dw->dvi.cache.font_number = dw->dvi.state->font_number;
+ dw->dvi.cache.font = QueryFont (dw,
+ dw->dvi.cache.font_number,
+ dw->dvi.cache.font_size);
+ if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) {
+ ++dw->dvi.cache.index;
+ if (dw->dvi.cache.index >= dw->dvi.cache.max)
+ FlushCharCache (dw);
+ dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0;
+ dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
+ }
+ }
+ if (x != dw->dvi.cache.x || dw->dvi.word_flag) {
+ if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) {
+ ++dw->dvi.cache.index;
+ if (dw->dvi.cache.index >= dw->dvi.cache.max)
+ FlushCharCache (dw);
+ dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0;
+ dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
+ }
+ dw->dvi.cache.adjustable[dw->dvi.cache.index]
+ = dw->dvi.word_flag;
+ dw->dvi.word_flag = 0;
+ }
+ font = dw->dvi.cache.font;
+ text = &dw->dvi.cache.cache[dw->dvi.cache.index];
+ if (text->nchars == 0) {
+ text->chars = &dw->dvi.cache.char_cache[dw->dvi.cache.char_index];
+ text->delta = x - dw->dvi.cache.x;
+ if (font != dw->dvi.font) {
+ text->font = font->fid;
+ dw->dvi.font = font;
+ } else
+ text->font = None;
+ dw->dvi.cache.x += text->delta;
+ }
+ if (charExists(font, c)) {
+ int w;
+ dw->dvi.cache.char_cache[dw->dvi.cache.char_index++] = (char) c;
+ ++text->nchars;
+ w = charWidth(font, c);
+ dw->dvi.cache.x += w;
+ if (wid != 0) {
+ dw->dvi.text_x_width += w;
+ dw->dvi.text_device_width += wid;
+ }
+ }
+}
+
+static
+int FindCharWidth (DviWidget dw, char *buf, int *widp)
+{
+ int maxpos;
+ int i;
+
+ if (dw->dvi.device_font == 0
+ || dw->dvi.state->font_number != dw->dvi.device_font_number) {
+ dw->dvi.device_font_number = dw->dvi.state->font_number;
+ dw->dvi.device_font
+ = QueryDeviceFont (dw, dw->dvi.device_font_number);
+ }
+ if (dw->dvi.device_font
+ && device_char_width (dw->dvi.device_font,
+ dw->dvi.state->font_size, buf, widp))
+ return 1;
+
+ maxpos = MaxFontPosition (dw);
+ for (i = 1; i <= maxpos; i++) {
+ DeviceFont *f = QueryDeviceFont (dw, i);
+ if (f && device_font_special (f)
+ && device_char_width (f, dw->dvi.state->font_size,
+ buf, widp)) {
+ dw->dvi.state->font_number = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Return the width of the character in device units. */
+
+int
+PutCharacter (DviWidget dw, char *buf)
+{
+ int prevFont;
+ int c = -1;
+ int wid = 0;
+ DviCharNameMap *map;
+
+ if (!dw->dvi.display_enable)
+ return 0; /* The width doesn't matter in this case. */
+ prevFont = dw->dvi.state->font_number;
+ if (!FindCharWidth (dw, buf, &wid))
+ return 0;
+ map = QueryFontMap (dw, dw->dvi.state->font_number);
+ if (map)
+ c = DviCharIndex (map, buf);
+ if (c >= 0)
+ DoCharacter (dw, c, wid);
+ else
+ (void) FakeCharacter (dw, buf, wid);
+ dw->dvi.state->font_number = prevFont;
+ return wid;
+}
+
+/* Return 1 if we can fake it; 0 otherwise. */
+
+static
+int FakeCharacter (DviWidget dw, char *buf, int wid)
+{
+ int oldx, oldw;
+ char ch[2];
+ const char *chars = 0;
+
+ if (buf[0] == '\0' || buf[1] == '\0' || buf[2] != '\0')
+ return 0;
+#define pack2(c1, c2) (((c1) << 8) | (c2))
+
+ switch (pack2(buf[0], buf[1])) {
+ case pack2('f', 'i'):
+ chars = "fi";
+ break;
+ case pack2('f', 'l'):
+ chars = "fl";
+ break;
+ case pack2('f', 'f'):
+ chars = "ff";
+ break;
+ case pack2('F', 'i'):
+ chars = "ffi";
+ break;
+ case pack2('F', 'l'):
+ chars = "ffl";
+ break;
+ }
+ if (!chars)
+ return 0;
+ oldx = dw->dvi.state->x;
+ oldw = dw->dvi.text_device_width;
+ ch[1] = '\0';
+ for (; *chars; chars++) {
+ ch[0] = *chars;
+ dw->dvi.state->x += PutCharacter (dw, ch);
+ }
+ dw->dvi.state->x = oldx;
+ dw->dvi.text_device_width = oldw + wid;
+ return 1;
+}
+
+void
+PutNumberedCharacter (DviWidget dw, int c)
+{
+ char *name;
+ int wid;
+ DviCharNameMap *map;
+
+ if (!dw->dvi.display_enable)
+ return;
+
+ if (dw->dvi.device_font == 0
+ || dw->dvi.state->font_number != dw->dvi.device_font_number) {
+ dw->dvi.device_font_number = dw->dvi.state->font_number;
+ dw->dvi.device_font
+ = QueryDeviceFont (dw, dw->dvi.device_font_number);
+ }
+
+ if (dw->dvi.device_font == 0
+ || !device_code_width (dw->dvi.device_font,
+ dw->dvi.state->font_size, c, &wid))
+ return;
+ if (dw->dvi.native) {
+ DoCharacter (dw, c, wid);
+ return;
+ }
+ map = QueryFontMap (dw, dw->dvi.state->font_number);
+ if (!map)
+ return;
+ for (name = device_name_for_code (dw->dvi.device_font, c);
+ name;
+ name = device_name_for_code ((DeviceFont *)0, c)) {
+ int code = DviCharIndex (map, name);
+ if (code >= 0) {
+ DoCharacter (dw, code, wid);
+ break;
+ }
+ if (FakeCharacter (dw, name, wid))
+ break;
+ }
+}
+
+/* unused */
+#if 0
+static void
+ClearPage (DviWidget dw)
+{
+ XClearWindow (XtDisplay (dw), XtWindow (dw));
+}
+#endif
+
+static void
+setGC (DviWidget dw)
+{
+ int desired_line_width;
+
+ if (dw->dvi.line_thickness < 0)
+ desired_line_width = (int)(((double)dw->dvi.device_resolution
+ * dw->dvi.state->font_size)
+ / (10.0*72.0*dw->dvi.sizescale));
+ else
+ desired_line_width = dw->dvi.line_thickness;
+
+ if (desired_line_width != dw->dvi.line_width) {
+ XGCValues values;
+ values.line_width = DeviceToX(dw, desired_line_width);
+ if (values.line_width == 0)
+ values.line_width = 1;
+ XChangeGC(XtDisplay (dw), dw->dvi.normal_GC,
+ GCLineWidth, &values);
+ dw->dvi.line_width = desired_line_width;
+ }
+}
+
+static void
+setFillGC (DviWidget dw)
+{
+ int fill_type;
+ unsigned long mask = GCFillStyle | GCForeground;
+
+ fill_type = (dw->dvi.fill * 10) / (DVI_FILL_MAX + 1);
+ if (dw->dvi.fill_type != fill_type) {
+ XGCValues values;
+ if (fill_type <= 0) {
+ values.foreground = dw->dvi.background;
+ values.fill_style = FillSolid;
+ } else if (fill_type >= 9) {
+ values.foreground = dw->dvi.foreground;
+ values.fill_style = FillSolid;
+ } else {
+ values.foreground = dw->dvi.foreground;
+ values.fill_style = FillOpaqueStippled;
+ values.stipple = dw->dvi.gray[fill_type - 1];
+ mask |= GCStipple;
+ }
+ XChangeGC(XtDisplay (dw), dw->dvi.fill_GC, mask, &values);
+ dw->dvi.fill_type = fill_type;
+ }
+}
+
+void
+DrawLine (DviWidget dw, int x, int y)
+{
+ int xp, yp;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ xp = XPos (dw);
+ yp = YPos (dw);
+ XDrawLine (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ xp, yp,
+ xp + DeviceToX (dw, x), yp + DeviceToX (dw, y));
+}
+
+void
+DrawCircle (DviWidget dw, int diam)
+{
+ int d;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ d = DeviceToX (dw, diam);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ XPos (dw), YPos (dw) - d/2,
+ d, d, 0, 64*360);
+}
+
+void
+DrawFilledCircle (DviWidget dw, int diam)
+{
+ int d;
+
+ AdjustCacheDeltas (dw);
+ setFillGC (dw);
+ d = DeviceToX (dw, diam);
+ XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - d/2,
+ d, d, 0, 64*360);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - d/2,
+ d, d, 0, 64*360);
+}
+
+void
+DrawEllipse (DviWidget dw, int a, int b)
+{
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
+ DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
+}
+
+void
+DrawFilledEllipse (DviWidget dw, int a, int b)
+{
+ AdjustCacheDeltas (dw);
+ setFillGC (dw);
+ XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
+ DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
+ DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
+}
+
+void
+DrawArc (DviWidget dw, int x_0, int y_0, int x_1, int y_1)
+{
+ int angle1, angle2;
+ int rad = (int)((sqrt ((double)x_0*x_0 + (double)y_0*y_0)
+ + sqrt ((double)x_1*x_1 + (double)y_1*y_1)
+ + 1.0)/2.0);
+ if ((x_0 == 0 && y_0 == 0) || (x_1 == 0 && y_1 == 0))
+ return;
+ angle1 = (int)(atan2 ((double)y_0, (double)-x_0)*180.0*64.0/M_PI);
+ angle2 = (int)(atan2 ((double)-y_1, (double)x_1)*180.0*64.0/M_PI);
+
+ angle2 -= angle1;
+ if (angle2 < 0)
+ angle2 += 64*360;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+
+ rad = DeviceToX (dw, rad);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ XPos (dw) + DeviceToX (dw, x_0) - rad,
+ YPos (dw) + DeviceToX (dw, y_0) - rad,
+ rad*2, rad*2, angle1, angle2);
+}
+
+void
+DrawPolygon (DviWidget dw, int *v, int n)
+{
+ XPoint *p;
+ int i;
+ int dx, dy;
+
+ n /= 2;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint));
+ p[0].x = XPos (dw);
+ p[0].y = YPos (dw);
+ dx = 0;
+ dy = 0;
+ for (i = 0; i < n; i++) {
+ dx += v[2*i];
+ p[i + 1].x = DeviceToX (dw, dx) + p[0].x;
+ dy += v[2*i + 1];
+ p[i + 1].y = DeviceToX (dw, dy) + p[0].y;
+ }
+ p[n+1].x = p[0].x;
+ p[n+1].y = p[0].y;
+ XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ p, n + 2, CoordModeOrigin);
+ XtFree((char *)p);
+}
+
+void
+DrawFilledPolygon (DviWidget dw, int *v, int n)
+{
+ XPoint *p;
+ int i;
+ int dx, dy;
+
+ n /= 2;
+ if (n < 2)
+ return;
+
+ AdjustCacheDeltas (dw);
+ setFillGC (dw);
+ p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint));
+ p[0].x = p[n+1].x = XPos (dw);
+ p[0].y = p[n+1].y = YPos (dw);
+ dx = 0;
+ dy = 0;
+ for (i = 0; i < n; i++) {
+ dx += v[2*i];
+ p[i + 1].x = DeviceToX (dw, dx) + p[0].x;
+ dy += v[2*i + 1];
+ p[i + 1].y = DeviceToX (dw, dy) + p[0].y;
+ }
+ XFillPolygon (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ p, n + 1, Complex, CoordModeOrigin);
+ XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ p, n + 2, CoordModeOrigin);
+ XtFree((char *)p);
+}
+
+#define POINTS_MAX 10000
+
+static void
+appendPoint(XPoint *points, int *pointi, int x, int y)
+{
+ if (*pointi < POINTS_MAX) {
+ points[*pointi].x = x;
+ points[*pointi].y = y;
+ *pointi += 1;
+ }
+}
+
+#define FLATNESS 1
+
+static void
+flattenCurve(XPoint *points, int *pointi,
+ int x_2, int y_2, int x_3, int y_3, int x_4, int y_4)
+{
+ int x_1, y_1, dx, dy, n1, n2, n;
+
+ x_1 = points[*pointi - 1].x;
+ y_1 = points[*pointi - 1].y;
+
+ dx = x_4 - x_1;
+ dy = y_4 - y_1;
+
+ n1 = dy*(x_2 - x_1) - dx*(y_2 - y_1);
+ n2 = dy*(x_3 - x_1) - dx*(y_3 - y_1);
+ if (n1 < 0)
+ n1 = -n1;
+ if (n2 < 0)
+ n2 = -n2;
+ n = n1 > n2 ? n1 : n2;
+
+ if (n*n / (dy*dy + dx*dx) <= FLATNESS*FLATNESS)
+ appendPoint (points, pointi, x_4, y_4);
+ else {
+ flattenCurve (points, pointi,
+ (x_1 + x_2)/2,
+ (y_1 + y_2)/2,
+ (x_1 + x_2*2 + x_3)/4,
+ (y_1 + y_2*2 + y_3)/4,
+ (x_1 + 3*x_2 + 3*x_3 + x_4)/8,
+ (y_1 + 3*y_2 + 3*y_3 + y_4)/8);
+ flattenCurve (points, pointi,
+ (x_2 + x_3*2 + x_4)/4,
+ (y_2 + y_3*2 + y_4)/4,
+ (x_3 + x_4)/2,
+ (y_3 + y_4)/2,
+ x_4,
+ y_4);
+ }
+}
+
+void
+DrawSpline (DviWidget dw, int *v, int n)
+{
+ int sx, sy, tx, ty;
+ int ox, oy, dx, dy;
+ int i;
+ int pointi;
+ XPoint points[POINTS_MAX];
+
+ if (n == 0 || (n & 1) != 0)
+ return;
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ ox = XPos (dw);
+ oy = YPos (dw);
+ dx = v[0];
+ dy = v[1];
+ sx = ox;
+ sy = oy;
+ tx = sx + DeviceToX (dw, dx);
+ ty = sy + DeviceToX (dw, dy);
+
+ pointi = 0;
+
+ appendPoint (points, &pointi, sx, sy);
+ appendPoint (points, &pointi, (sx + tx)/2, (sy + ty)/2);
+
+ for (i = 2; i < n; i += 2) {
+ int ux = ox + DeviceToX (dw, dx += v[i]);
+ int uy = oy + DeviceToX (dw, dy += v[i+1]);
+ flattenCurve (points, &pointi,
+ (sx + tx*5)/6, (sy + ty*5)/6,
+ (tx*5 + ux)/6, (ty*5 + uy)/6,
+ (tx + ux)/2, (ty + uy)/2);
+ sx = tx;
+ sy = ty;
+ tx = ux;
+ ty = uy;
+ }
+
+ appendPoint (points, &pointi, tx, ty);
+
+ XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ points, pointi, CoordModeOrigin);
+}
+
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/draw.h b/src/devices/xditview/draw.h
new file mode 100644
index 0000000..7a3772c
--- /dev/null
+++ b/src/devices/xditview/draw.h
@@ -0,0 +1,18 @@
+void HorizontalGoto(DviWidget, int);
+void VerticalGoto(DviWidget, int);
+void VerticalMove(DviWidget, int);
+void FlushCharCache(DviWidget);
+void Newline(DviWidget);
+void Word(DviWidget);
+int PutCharacter(DviWidget, char *);
+void PutNumberedCharacter(DviWidget, int);
+void DrawLine(DviWidget, int, int);
+void DrawCircle(DviWidget, int);
+void DrawFilledCircle(DviWidget, int);
+void DrawEllipse(DviWidget, int, int);
+void DrawFilledEllipse(DviWidget, int, int);
+void DrawArc(DviWidget, int, int, int, int);
+void DrawPolygon(DviWidget, int *, int);
+void DrawFilledPolygon(DviWidget, int *, int);
+void DrawSpline(DviWidget, int *, int);
+
diff --git a/src/devices/xditview/font.c b/src/devices/xditview/font.c
new file mode 100644
index 0000000..8462608
--- /dev/null
+++ b/src/devices/xditview/font.c
@@ -0,0 +1,446 @@
+/*
+ * font.c
+ *
+ * map dvi fonts to X fonts
+ */
+
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "DviP.h"
+#include "XFontName.h"
+#include "font.h"
+
+/* forward reference */
+static void DisposeFontSizes(DviWidget, DviFontSizeList *);
+
+static char *
+savestr (const char *s)
+{
+ char *n;
+
+ if (!s)
+ return 0;
+ n = XtMalloc (strlen (s) + 1);
+ if (n)
+ strcpy (n, s);
+ return n;
+}
+
+static DviFontList *
+LookupFontByPosition (DviWidget dw, int position)
+{
+ DviFontList *f;
+
+ for (f = dw->dvi.fonts; f; f = f->next)
+ if (f->dvi_number == position)
+ break;
+ return f;
+}
+
+int
+MaxFontPosition (DviWidget dw)
+{
+ DviFontList *f;
+ int n = -1;
+
+ for (f = dw->dvi.fonts; f; f = f->next)
+ if (f->dvi_number > n)
+ n = f->dvi_number;
+ return n;
+}
+
+static DviFontSizeList *
+LookupFontSizeBySize (DviWidget dw, DviFontList *f, int size)
+{
+ DviFontSizeList *fs, *best = 0, *smallest = 0;
+ int bestsize = 0;
+ XFontName fontName;
+ unsigned int fontNameAttributes;
+ char fontNameString[2048];
+ int decipointsize;
+
+ if (f->scalable) {
+ decipointsize = (10*size)/dw->dvi.sizescale;
+ for (best = f->sizes; best; best = best->next)
+ if (best->size == decipointsize)
+ return best;
+ best = (DviFontSizeList *) XtMalloc(sizeof *best);
+ best->next = f->sizes;
+ best->size = decipointsize;
+ f->sizes = best;
+ XParseFontName (f->x_name, &fontName, &fontNameAttributes);
+ fontNameAttributes &= ~(FontNamePixelSize|FontNameAverageWidth);
+ fontNameAttributes |= FontNameResolutionX;
+ fontNameAttributes |= FontNameResolutionY;
+ fontNameAttributes |= FontNamePointSize;
+ fontName.ResolutionX = dw->dvi.display_resolution;
+ fontName.ResolutionY = dw->dvi.display_resolution;
+ fontName.PointSize = decipointsize;
+ XFormatFontName (&fontName, fontNameAttributes, fontNameString);
+ best->x_name = savestr (fontNameString);
+ best->doesnt_exist = 0;
+ best->font = 0;
+ return best;
+ }
+ for (fs = f->sizes; fs; fs=fs->next) {
+ if (dw->dvi.sizescale*fs->size <= 10*size
+ && fs->size >= bestsize) {
+ best = fs;
+ bestsize = fs->size;
+ }
+ if (smallest == 0 || fs->size < smallest->size)
+ smallest = fs;
+ }
+ return best ? best : smallest;
+}
+
+static char *
+SkipFontNameElement (char *n)
+{
+ while (*n != '-')
+ if (!*++n)
+ return 0;
+ return n+1;
+}
+
+# define SizePosition 8
+# define EncodingPosition 13
+
+static int
+ConvertFontNameToSize (char *n)
+{
+ int i, size;
+
+ for (i = 0; i < SizePosition; i++) {
+ n = SkipFontNameElement (n);
+ if (!n)
+ return -1;
+ }
+ size = atoi (n);
+ return size;
+}
+
+static char *
+ConvertFontNameToEncoding (char *n)
+{
+ int i;
+ for (i = 0; i < EncodingPosition; i++) {
+ n = SkipFontNameElement (n);
+ if (!n)
+ return 0;
+ }
+ return n;
+}
+
+static DviFontSizeList *
+InstallFontSizes (DviWidget dw, const char *x_name, Boolean *scalablep)
+{
+ char fontNameString[2048];
+ char **fonts;
+ int i, count;
+ int size;
+ DviFontSizeList *sizes, *new_size;
+ XFontName fontName;
+ unsigned int fontNameAttributes;
+
+ *scalablep = FALSE;
+ if (!XParseFontName ((XFontNameString)x_name, &fontName,
+ &fontNameAttributes))
+ return 0;
+ fontNameAttributes &= ~(FontNamePixelSize|FontNamePointSize
+ |FontNameAverageWidth);
+ fontNameAttributes |= FontNameResolutionX;
+ fontNameAttributes |= FontNameResolutionY;
+ fontName.ResolutionX = dw->dvi.display_resolution;
+ fontName.ResolutionY = dw->dvi.display_resolution;
+ XFormatFontName (&fontName, fontNameAttributes, fontNameString);
+ fonts = XListFonts (XtDisplay (dw), fontNameString, 10000000, &count);
+ sizes = 0;
+ for (i = 0; i < count; i++) {
+ size = ConvertFontNameToSize (fonts[i]);
+ if (size == 0) {
+ DisposeFontSizes (dw, sizes);
+ sizes = 0;
+ *scalablep = TRUE;
+ break;
+ }
+ if (size != -1) {
+ new_size = (DviFontSizeList *) XtMalloc (sizeof *new_size);
+ new_size->next = sizes;
+ new_size->size = size;
+ new_size->x_name = savestr (fonts[i]);
+ new_size->doesnt_exist = 0;
+ new_size->font = 0;
+ sizes = new_size;
+ }
+ }
+ XFreeFontNames (fonts);
+ return sizes;
+}
+
+static void
+DisposeFontSizes (DviWidget dw, DviFontSizeList *fs)
+{
+ DviFontSizeList *next;
+
+ for (; fs; fs=next) {
+ next = fs->next;
+ if (fs->x_name)
+ XtFree (fs->x_name);
+ if (fs->font && fs->font != dw->dvi.default_font) {
+ XUnloadFont (XtDisplay (dw), fs->font->fid);
+ XFree ((char *)fs->font);
+ }
+ XtFree ((char *) fs);
+ }
+}
+
+static DviFontList *
+InstallFont (DviWidget dw, int position,
+ const char *dvi_name, const char *x_name)
+{
+ DviFontList *f;
+ char *encoding;
+
+ if ((f = LookupFontByPosition (dw, position)) != NULL) {
+ /*
+ * ignore gratuitous font loading
+ */
+ if (!strcmp (f->dvi_name, dvi_name) &&
+ !strcmp (f->x_name, x_name))
+ return f;
+
+ DisposeFontSizes (dw, f->sizes);
+ if (f->dvi_name)
+ XtFree (f->dvi_name);
+ if (f->x_name)
+ XtFree (f->x_name);
+ f->device_font = 0;
+ } else {
+ f = (DviFontList *) XtMalloc (sizeof (*f));
+ f->next = dw->dvi.fonts;
+ dw->dvi.fonts = f;
+ }
+ f->initialized = FALSE;
+ f->dvi_name = savestr (dvi_name);
+ f->device_font = device_find_font (dw->dvi.device, dvi_name);
+ f->x_name = savestr (x_name);
+ f->dvi_number = position;
+ f->sizes = 0;
+ f->scalable = FALSE;
+ if (f->x_name) {
+ encoding = ConvertFontNameToEncoding (f->x_name);
+ f->char_map = DviFindMap (encoding);
+ } else
+ f->char_map = 0;
+ /*
+ * force requery of fonts
+ */
+ dw->dvi.font = 0;
+ dw->dvi.font_number = -1;
+ dw->dvi.cache.font = 0;
+ dw->dvi.cache.font_number = -1;
+ dw->dvi.device_font = 0;
+ dw->dvi.device_font_number = -1;
+ return f;
+}
+
+void
+ForgetFonts (DviWidget dw)
+{
+ DviFontList *f = dw->dvi.fonts;
+
+ while (f) {
+ DviFontList *tem = f;
+
+ if (f->sizes)
+ DisposeFontSizes (dw, f->sizes);
+ if (f->dvi_name)
+ XtFree (f->dvi_name);
+ if (f->x_name)
+ XtFree (f->x_name);
+ f = f->next;
+ XtFree ((char *) tem);
+ }
+
+ /*
+ * force requery of fonts
+ */
+ dw->dvi.font = 0;
+ dw->dvi.font_number = -1;
+ dw->dvi.cache.font = 0;
+ dw->dvi.cache.font_number = -1;
+ dw->dvi.device_font = 0;
+ dw->dvi.device_font_number = -1;
+ dw->dvi.fonts = 0;
+}
+
+
+static char *
+MapDviNameToXName (DviWidget dw, const char *dvi_name)
+{
+ DviFontMap *fm;
+
+ for (fm = dw->dvi.font_map; fm; fm=fm->next)
+ if (!strcmp (fm->dvi_name, dvi_name))
+ return fm->x_name;
+ return 0;
+}
+
+#if 0
+static char *
+MapXNameToDviName (DviWidget dw, const char *x_name)
+{
+ DviFontMap *fm;
+
+ for (fm = dw->dvi.font_map; fm; fm=fm->next)
+ if (!strcmp (fm->x_name, x_name))
+ return fm->dvi_name;
+ return 0;
+}
+#endif
+
+void
+ParseFontMap (DviWidget dw)
+{
+ char dvi_name[1024];
+ char x_name[2048];
+ char *m, *s;
+ DviFontMap *fm, *new_map;
+
+ if (dw->dvi.font_map)
+ DestroyFontMap (dw->dvi.font_map);
+ fm = 0;
+ m = dw->dvi.font_map_string;
+ while (*m) {
+ s = m;
+ while (*m && !isspace (*m))
+ ++m;
+ strncpy (dvi_name, s, m-s);
+ dvi_name[m-s] = '\0';
+ while (isspace (*m))
+ ++m;
+ s = m;
+ while (*m && *m != '\n')
+ ++m;
+ strncpy (x_name, s, m-s);
+ x_name[m-s] = '\0';
+ new_map = (DviFontMap *) XtMalloc (sizeof *new_map);
+ new_map->x_name = savestr (x_name);
+ new_map->dvi_name = savestr (dvi_name);
+ new_map->next = fm;
+ fm = new_map;
+ ++m;
+ }
+ dw->dvi.font_map = fm;
+}
+
+void
+DestroyFontMap (DviFontMap *font_map)
+{
+ DviFontMap *next;
+
+ for (; font_map; font_map = next) {
+ next = font_map->next;
+ if (font_map->x_name)
+ XtFree (font_map->x_name);
+ if (font_map->dvi_name)
+ XtFree (font_map->dvi_name);
+ XtFree ((char *) font_map);
+ }
+}
+
+/* ARGSUSED */
+
+void
+SetFontPosition (DviWidget dw, int position,
+ const char *dvi_name, const char *extra)
+{
+ char *x_name;
+
+ x_name = MapDviNameToXName (dw, dvi_name);
+ if (x_name)
+ (void) InstallFont (dw, position, dvi_name, x_name);
+
+ extra = extra; /* unused; suppress compiler warning */
+}
+
+XFontStruct *
+QueryFont (DviWidget dw, int position, int size)
+{
+ DviFontList *f;
+ DviFontSizeList *fs;
+
+ f = LookupFontByPosition (dw, position);
+ if (!f)
+ return dw->dvi.default_font;
+ if (!f->initialized) {
+ f->sizes = InstallFontSizes (dw, f->x_name, &f->scalable);
+ f->initialized = TRUE;
+ }
+ fs = LookupFontSizeBySize (dw, f, size);
+ if (!fs)
+ return dw->dvi.default_font;
+ if (!fs->font) {
+ if (fs->x_name)
+ fs->font = XLoadQueryFont (XtDisplay (dw), fs->x_name);
+ if (!fs->font)
+ fs->font = dw->dvi.default_font;
+ }
+ return fs->font;
+}
+
+DeviceFont *
+QueryDeviceFont (DviWidget dw, int position)
+{
+ DviFontList *f;
+
+ f = LookupFontByPosition (dw, position);
+ if (!f)
+ return 0;
+ return f->device_font;
+}
+
+DviCharNameMap *
+QueryFontMap (DviWidget dw, int position)
+{
+ DviFontList *f;
+
+ f = LookupFontByPosition (dw, position);
+ if (f)
+ return f->char_map;
+ else
+ return 0;
+}
+
+#if 0
+LoadFont (DviWidget dw, int position, int size)
+{
+ XFontStruct *font;
+
+ font = QueryFont (dw, position, size);
+ dw->dvi.font_number = position;
+ dw->dvi.font_size = size;
+ dw->dvi.font = font;
+ XSetFont (XtDisplay (dw), dw->dvi.normal_GC, font->fid);
+ return;
+}
+#endif
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/font.h b/src/devices/xditview/font.h
new file mode 100644
index 0000000..ae6c765
--- /dev/null
+++ b/src/devices/xditview/font.h
@@ -0,0 +1,6 @@
+void DestroyFontMap(DviFontMap *);
+void ForgetFonts (DviWidget dw);
+int MaxFontPosition (DviWidget dw);
+void ParseFontMap (DviWidget dw);
+void SetFontPosition (DviWidget dw, int position, const char *dvi_name,
+ const char *extra);
diff --git a/src/devices/xditview/gray1.bm b/src/devices/xditview/gray1.bm
new file mode 100644
index 0000000..c40a95e
--- /dev/null
+++ b/src/devices/xditview/gray1.bm
@@ -0,0 +1,4 @@
+#define gray1_width 3
+#define gray1_height 3
+static char gray1_bits[] = {
+ 0x00, 0x02, 0x00};
diff --git a/src/devices/xditview/gray2.bm b/src/devices/xditview/gray2.bm
new file mode 100644
index 0000000..e87a1bc
--- /dev/null
+++ b/src/devices/xditview/gray2.bm
@@ -0,0 +1,4 @@
+#define gray2_width 3
+#define gray2_height 3
+static char gray2_bits[] = {
+ 0x00, 0x03, 0x00};
diff --git a/src/devices/xditview/gray3.bm b/src/devices/xditview/gray3.bm
new file mode 100644
index 0000000..d9313eb
--- /dev/null
+++ b/src/devices/xditview/gray3.bm
@@ -0,0 +1,4 @@
+#define gray3_width 3
+#define gray3_height 3
+static char gray3_bits[] = {
+ 0x00, 0x03, 0x02};
diff --git a/src/devices/xditview/gray4.bm b/src/devices/xditview/gray4.bm
new file mode 100644
index 0000000..dad142a
--- /dev/null
+++ b/src/devices/xditview/gray4.bm
@@ -0,0 +1,4 @@
+#define gray4_width 3
+#define gray4_height 3
+static char gray4_bits[] = {
+ 0x00, 0x07, 0x02};
diff --git a/src/devices/xditview/gray5.bm b/src/devices/xditview/gray5.bm
new file mode 100644
index 0000000..5f57618
--- /dev/null
+++ b/src/devices/xditview/gray5.bm
@@ -0,0 +1,4 @@
+#define gray5_width 3
+#define gray5_height 3
+static char gray5_bits[] = {
+ 0x04, 0x07, 0x02};
diff --git a/src/devices/xditview/gray6.bm b/src/devices/xditview/gray6.bm
new file mode 100644
index 0000000..b76701d
--- /dev/null
+++ b/src/devices/xditview/gray6.bm
@@ -0,0 +1,4 @@
+#define gray6_width 3
+#define gray6_height 3
+static char gray6_bits[] = {
+ 0x04, 0x07, 0x03};
diff --git a/src/devices/xditview/gray7.bm b/src/devices/xditview/gray7.bm
new file mode 100644
index 0000000..ef47bc6
--- /dev/null
+++ b/src/devices/xditview/gray7.bm
@@ -0,0 +1,4 @@
+#define gray7_width 3
+#define gray7_height 3
+static char gray7_bits[] = {
+ 0x05, 0x07, 0x03};
diff --git a/src/devices/xditview/gray8.bm b/src/devices/xditview/gray8.bm
new file mode 100644
index 0000000..12de7cb
--- /dev/null
+++ b/src/devices/xditview/gray8.bm
@@ -0,0 +1,4 @@
+#define gray8_width 3
+#define gray8_height 3
+static char gray8_bits[] = {
+ 0x05, 0x07, 0x07};
diff --git a/src/devices/xditview/gxditview.1.man b/src/devices/xditview/gxditview.1.man
new file mode 100644
index 0000000..920ccfd
--- /dev/null
+++ b/src/devices/xditview/gxditview.1.man
@@ -0,0 +1,815 @@
+.TH gxditview @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+gxditview \- display
+.I groff
+intermediate output files in X11
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright 1991 Massachusetts Institute of Technology
+.\"
+.\" Permission to use, copy, modify, distribute, and sell this software
+.\" and its documentation for any purpose is hereby granted without fee,
+.\" provided that the above copyright notice appear in all copies and
+.\" that both that copyright notice and this permission notice appear in
+.\" supporting documentation, and that the name of M.I.T. not be used in
+.\" advertising or publicity pertaining to distribution of the software
+.\" without specific, written prior permission. M.I.T. makes no
+.\" representations about the suitability of this software for any
+.\" purpose. It is provided "as is" without express or implied
+.\" warranty.
+.\"
+.\" M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+.\" INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+.\" NO EVENT SHALL M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+.\" CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+.\" OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+.\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+.\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_gxditview_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY gxditview
+.RI [ X-toolkit-option \~.\|.\|.\&]
+.RB [ \-backingStore\~\c
+.IR backing-store-type ]
+.RB [ \-filename\~\c
+.IR file ]
+.\" While recognized, the relevant logic is "#if 0"ed out in draw.c.
+.\" .RB [ \-noPolyText ]
+.RB [ \-page\~\c
+.IR page-number ]
+.RB [ \-printCommand\~\c
+.IR command ]
+.RB [ \-resolution\~\c
+.IR resolution ]
+.I file
+.YS
+.
+.
+.SY gxditview
+.B \-help
+.
+.SY gxditview
+.B \-\-help
+.YS
+.
+.
+.SY gxditview
+.B \-version
+.
+.SY gxditview
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I gxditview
+interprets and displays the intermediate output format of
+.MR groff @MAN1EXT@
+on an X11\~display.
+.
+It uses the standard X11 fonts,
+so it does not require access to the server machine for font loading.
+.
+There are several ways to use
+.IR gxditview .
+.
+.
+.PP
+The
+intermediate output format of
+.IR groff ,
+documented in
+.MR groff_out @MAN5EXT@ ,
+is produced by
+.I @g@troff
+or the
+.B \-Z
+option to
+.IR groff .
+.
+.
+It can be viewed by explicitly calling
+.RB \[lq] gxditview
+.IR file \[rq].
+.
+If the
+.I file
+operand is
+.RB \[lq] \- \[rq],
+.I gxditview
+will read the standard input stream;
+.I file
+cannot be omitted.
+.
+The intermediate output format of
+.I groff
+is device-independent but not device-agnostic.
+.
+.I gxditview
+can view it for all typesetter devices,
+but the quality is device-dependent.
+.
+.I gxditview
+will not display output for terminal
+.RI ( nroff )
+devices.
+.
+.
+.PP
+The best results are achieved with the
+.BR X *
+devices for
+.IR groff 's
+.B \-T
+option,
+of which there are four:
+.BR \-TX75 ,
+.BR \-TX75\-12 ,
+.BR \-TX100 ,
+and
+.BR \-TX100\-12 .
+.
+They differ by the X\~resolution
+(75 or 100 dots per inch)
+and the base point size
+(10 or 12 points).
+.
+They are especially built for
+.IR gxditview .
+.
+When using one of these,
+.I groff
+generates the intermediate output for this device and calls
+.I gxditview
+automatically for viewing.
+.
+.
+.P
+.B \-X
+produces good results only with
+.BR \-Tps ,
+.BR \-TX75 ,
+.BR \-TX75\-12 ,
+.BR \-TX100 ,
+and
+.BR \-TX100\-12 .
+.
+The default resolution for previewing
+.B \-Tps
+output is 75\|dpi;
+this can be changed with the
+.B \-resolution
+option.
+.
+.
+.PP
+While
+.I gxditview
+is running,
+the left mouse button brings up a menu with several entries.
+.
+.
+.TP 13n
+.B Next Page
+Display the next page.
+.
+.
+.TP
+.B Previous Page
+Display the previous page.
+.
+.
+.TP
+.B Select Page
+Select a particular numbered page specified by a dialog box.
+.
+.
+.TP
+.B Print
+Print the
+.I groff
+intermediate output using a command specified by a dialog box.
+.
+The default command initially displayed is controlled by the
+.B printCommand
+application resource,
+and by the
+.B \-printCommand
+option.
+.
+.
+.TP
+.B Open
+Open for display a new file specified by a dialog box.
+.
+The file should contain
+.I groff
+intermediate output.
+.
+If the filename starts with a bar or pipe symbol,
+.RB \[lq] | \[rq]
+it will be interpreted as a command from which to read.
+.
+.
+.TP
+.B Quit
+Exit from
+.BR gxditview .
+.
+.
+.PP
+The menu entries correspond to actions with similar but not identical
+names,
+which can also be accessed with keyboard accelerators.
+.
+The
+.IR n ,
+.IR Space ,
+.IR Return ,
+and
+.I Next
+.RI ( PgDn )
+keys are bound to the
+.B NextPage
+action.
+.
+The
+.IR p ,
+.IR b ,
+.IR BackSpace ,
+.IR Delete ,
+and
+.I Prior
+.RI ( PgUp )
+keys are bound to the
+.B PreviousPage
+action.
+.
+The
+.I g
+key is bound to the
+.B SelectPage
+action.
+.
+The
+.I o
+key is bound to the
+.B OpenFile
+action.
+.
+The
+.I q
+key is bound to the
+.B Quit
+action.
+.
+The
+.I r
+key is bound to a
+.B Rerasterize
+action which rereads the current file,
+and redisplays the current page;
+if the current file is a command,
+the command will be re-executed.
+.
+Vertical scrolling can be done with the
+.I k
+and
+.I j
+keys;
+horizontal scrolling is bound to the
+.I h
+and
+.I l
+keys.
+.
+The arrow keys
+.RI ( up ,
+.IR down ,
+.IR left ,
+and
+.IR right )
+are also bound to the obvious scrolling actions.
+.
+.
+.PP
+The
+.B paperlength
+and
+.B paperwidth
+commands in the
+.I DESC
+file describing a
+.I groff
+output device specify the length and width in machine units of the
+virtual page displayed by
+.IR gxditview ;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS "X defaults"
+.\" ====================================================================
+.
+This program uses the
+.I Dvi
+widget from the X\~Toolkit.
+.
+It understands all of the core resource names and classes as well as:
+.
+.
+.TP
+.BR width\~ (class\~ Width )
+Specifies the width of the window.
+.
+.
+.TP
+.BR height\~ (class\~ Height )
+Specifies the height of the window.
+.
+.
+.TP
+.BR foreground\~ (class\~ Foreground )
+Specifies the default foreground color.
+.
+.
+.TP
+.BR font\~ (class\~ Font )
+Specifies the font to be used for error messages.
+.
+.
+.TP
+.BR fontMap\~ (class\~ FontMap )
+Specifies the mapping from
+.I groff
+font names to X\~font names.
+.
+This must be a string containing a sequence of lines.
+.
+Each line contains two whitespace-separated fields:
+firstly the
+.I groff
+font name,
+and secondly the XLFD
+(X Logical Font Description).
+.
+The default is shown in subsection \[lq]Default font map\[rq] below.
+.
+.
+.\" ====================================================================
+.SS "Default font map"
+.\" ====================================================================
+.
+XLFDs are long and unwieldy,
+so some lines are shown broken and indented below.
+.\" Break them after the POINT_SIZE field (in "decipoints", so "100").
+.
+.
+.PP
+.EX
+TR \-adobe\-times\-medium\-r\-normal\-\-*\-100\
+\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+TI \-adobe\-times\-medium\-i\-normal\-\-*\-100\
+\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+TB \-adobe\-times\-bold\-r\-normal\-\-*\-100\
+\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+TBI \-adobe\-times\-bold\-i\-normal\
+\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CR \-adobe\-courier\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CI \-adobe\-courier\-medium\-o\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CB \-adobe\-courier\-bold\-r\-normal\
+\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CBI \-adobe\-courier\-bold\-o\-normal\
+\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HR \-adobe\-helvetica\-medium\-r\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HI \-adobe\-helvetica\-medium\-o\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HB \-adobe\-helvetica\-bold\-r\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HBI \-adobe\-helvetica\-bold\-o\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NR \-adobe\-new century schoolbook\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NI \-adobe\-new century schoolbook\-medium\-i\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NB \-adobe\-new century schoolbook\-bold\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NBI \-adobe\-new century schoolbook\-bold\-i\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+S \-adobe\-symbol\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-adobe\-fontspecific\[rs]n\[rs]
+SS \-adobe\-symbol\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-adobe\-fontspecific\[rs]n\[rs]
+.EE
+.
+.
+.br
+.ne 3v
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-help
+and
+.B \-\-help
+display a usage message,
+while
+.B \-version
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.P
+.I gxditview
+accepts all of the standard X\~Toolkit command-line options along with
+the additional options listed below.
+.
+.
+.TP
+.B \-page
+This option specifies the page number of the document to be displayed.
+.
+.
+.TP
+.BI \-backingStore\~ backing-store-type
+Because redisplay of the
+.I groff
+intermediate output window can take a perceiptible amount of time,
+this option causes the server to save the window contents so that when
+it is scrolled around the viewport,
+the window is painted from contents saved in backing store.
+.
+.I backing-store-type
+can be one of
+.BR Always ,
+.B WhenMapped
+or
+.BR NotUseful .
+.
+.
+.TP
+.BI \-printCommand\~ command
+The default command displayed in the dialog box for the
+.B Print
+menu entry will be
+.IR command .
+.
+.
+.TP
+.BI \-resolution\~ res
+The
+.I groff
+intermediate output file will be displayed at a resolution of
+.I res
+dots per inch,
+unless the
+.I DESC
+file contains the
+.B X11
+command,
+in which case the device resolution will be used.
+.
+This corresponds to the
+.I Dvi
+widget's
+.B resolution
+resource.
+.
+The default is
+.BR 75 .
+.
+.
+.TP
+.BI \-filename\~ string
+The default filename displayed in the dialog box for the
+.B Open
+menu entry will be
+.IR string .
+.
+This can be either a filename,
+or a command starting with
+.RB \[lq] | \[rq].
+.
+.
+.PP
+The following standard X\~Toolkit command-line arguments are commonly
+used with
+.IR gxditview .
+.
+.
+.TP
+.BI \-bg\~ color
+This option specifies the color to use for the background of the window.
+.
+The default is
+.RB \[lq] white \[rq].
+.
+.
+.TP
+.BI \-bd\~ color
+This option specifies the color to use for the border of the window.
+.
+The default is
+.RB \[lq] black \[rq].
+.
+.
+.TP
+.BI \-bw\~ number
+This option specifies the width in pixels of the border surrounding the
+window.
+.
+.
+.TP
+.BI \-fg\~ color
+This option specifies the color to use for displaying text.
+.
+The default is
+.RB \[lq] black \[rq].
+.
+.
+.TP
+.BI \-fn\~ font
+This option specifies the font to be used for displaying widget text.
+.
+The default is
+.RB \[lq] fixed \[rq].
+.
+.
+.TP
+.B \-rv
+This option indicates that reverse video should be simulated by swapping
+the foreground and background colors.
+.
+.
+.TP
+.BI \-geometry\~ geometry
+This option specifies the preferred size and position of the window.
+.
+.
+.TP
+.BI \-display\~ host : display
+This option specifies the X\~server to contact.
+.
+.
+.TP
+.BI \-xrm\~ resourcestring
+This option specifies a resource string to be used.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @APPDEFDIR@/\:\%GXditview
+.TQ
+.I @APPDEFDIR@/\:\%GXditview\-color
+define X application defaults for
+.IR gxditview .
+.
+Users can override these values in the
+.I .Xdefaults
+file,
+normally located in the user's home directory.
+.
+See
+.MR appres 1
+and
+.MR xrdb 1 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX100/\:DESC
+describes the
+.B X100
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX100/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X100 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX100\-12/\:DESC
+describes the
+.B X100\-12
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX100\-12/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X100\-12 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX75/\:DESC
+describes the
+.B X75
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX75/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X75 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX75\-12/\:DESC
+describes the
+.B X75\-12
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX75\-12/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X75\-12 .
+.
+.
+.TP
+.I @MACRODIR@/\:X.tmac
+defines macros for use with the
+.BR X100 ,
+.BR X100\-12 ,
+.BR X75 ,
+and
+.B X75\-12
+output devices.
+.
+It is automatically loaded by
+.I troffrc
+when any of those output devices is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:Xps\:.tmac
+sets up
+.I @g@troff
+to use
+.I \%gxditview
+as a previewer for device-independent output targeting the
+.B ps
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when
+.I @g@troff
+is given the options
+.B \-X
+and
+.BR \-Tps .
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+The following command views this man page with a base point size of 12.
+.
+.
+.RS
+.P
+.EX
+groff \-TX100\-12 \-man gxditview.1
+.EE
+.RE
+.
+.
+.P
+The quality of the result depends mainly on the chosen point size and
+display resolution;
+for rapid previewing,
+however,
+something like
+.
+.RS
+.EX
+.RI "groff \-X \-P\-resolution \-P100\~" document
+.EE
+.RE
+.
+yields acceptable results.
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I gxditview
+and its predecessor
+.I xditview
+were written by
+Keith Packard (MIT X Consortium),
+Richard L.\& Hyde (Purdue),
+David Slattengren (Berkeley),
+Malcolm Slaney (Schlumberger Palo Alto Research),
+Mark Moraes (University of Toronto),
+and
+James Clark.
+.
+.
+.PP
+This program is derived from
+.IR xditview ;
+portions of
+.I xditview
+originated in
+.IR xtroff ,
+which was derived from
+.IR \%suntroff .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR https://\:www\:.x\:.org/\:releases/\:X11R7.6/\:doc/\:xorg\-docs/\
+\:specs/\:XLFD/xlfd\:.html
+\[lq]X Logical Font Description Conventions\[rq]
+.UE ,
+by Jim Flowers and Stephen Gildea.
+.
+.
+.PP
+.MR X 7 ,
+.MR xrdb 1 ,
+.MR xditview 1 ,
+.MR groff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_gxditview_1_man_C]
+.do rr *groff_gxditview_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/xditview/lex.c b/src/devices/xditview/lex.c
new file mode 100644
index 0000000..19cf292
--- /dev/null
+++ b/src/devices/xditview/lex.c
@@ -0,0 +1,100 @@
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+
+#include "DviP.h"
+#include "lex.h"
+
+int
+DviGetAndPut(DviWidget dw, int *cp)
+{
+ if (dw->dvi.ungot) {
+ dw->dvi.ungot = 0;
+ *cp = getc (dw->dvi.file);
+ }
+ else {
+ *cp = getc (dw->dvi.file);
+ if (*cp != EOF)
+ putc (*cp, dw->dvi.tmpFile);
+ }
+ return *cp;
+}
+
+char *
+GetLine(DviWidget dw, char *Buffer, int Length)
+{
+ int i = 0, c;
+
+ Length--; /* Save room for final '\0' */
+
+ while (DviGetC (dw, &c) != EOF) {
+ if (Buffer && i < Length)
+ Buffer[i++] = c;
+ if (c == '\n') {
+ DviUngetC(dw, c);
+ break;
+ }
+ }
+ if (Buffer)
+ Buffer[i] = '\0';
+ return Buffer;
+}
+
+char *
+GetWord(DviWidget dw, char *Buffer, int Length)
+{
+ int i = 0, c;
+
+ Length--; /* Save room for final '\0' */
+ while (DviGetC(dw, &c) == ' ' || c == '\n')
+ ;
+ while (c != EOF) {
+ if (Buffer && i < Length)
+ Buffer[i++] = c;
+ if (DviGetC(dw, &c) == ' ' || c == '\n') {
+ DviUngetC(dw, c);
+ break;
+ }
+ }
+ if (Buffer)
+ Buffer[i] = '\0';
+ return Buffer;
+}
+
+int
+GetNumber(DviWidget dw)
+{
+ int i = 0, c;
+ int negative = 0;
+
+ while (DviGetC(dw, &c) == ' ' || c == '\n')
+ ;
+ if (c == '-') {
+ negative = 1;
+ DviGetC(dw, &c);
+ }
+
+ for (; c >= '0' && c <= '9'; DviGetC(dw, &c)) {
+ if (negative)
+ i = i*10 - (c - '0');
+ else
+ i = i*10 + c - '0';
+ }
+ if (c != EOF)
+ DviUngetC(dw, c);
+ return i;
+}
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/lex.h b/src/devices/xditview/lex.h
new file mode 100644
index 0000000..0a4cba0
--- /dev/null
+++ b/src/devices/xditview/lex.h
@@ -0,0 +1 @@
+int GetNumber(DviWidget);
diff --git a/src/devices/xditview/page.c b/src/devices/xditview/page.c
new file mode 100644
index 0000000..352d871
--- /dev/null
+++ b/src/devices/xditview/page.c
@@ -0,0 +1,86 @@
+/*
+ * page.c
+ *
+ * map page numbers to file position
+ */
+
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "DviP.h"
+#include "page.h"
+
+#ifdef X_NOT_STDC_ENV
+extern long ftell();
+#endif
+
+static DviFileMap *
+MapPageNumberToFileMap (DviWidget dw, int number)
+{
+ DviFileMap *m;
+
+ for (m = dw->dvi.file_map; m; m=m->next)
+ if (m->page_number == number)
+ break;
+ return m;
+}
+
+void
+DestroyFileMap (DviFileMap *m)
+{
+ DviFileMap *next;
+
+ for (; m; m = next) {
+ next = m->next;
+ XtFree ((char *) m);
+ }
+}
+
+void
+ForgetPagePositions (DviWidget dw)
+{
+ DestroyFileMap (dw->dvi.file_map);
+ dw->dvi.file_map = 0;
+}
+
+void
+RememberPagePosition(DviWidget dw, int number)
+{
+ DviFileMap *m;
+
+ if (!(m = MapPageNumberToFileMap (dw, number))) {
+ m = (DviFileMap *) XtMalloc (sizeof *m);
+ m->page_number = number;
+ m->next = dw->dvi.file_map;
+ dw->dvi.file_map = m;
+ }
+ if (dw->dvi.tmpFile)
+ m->position = ftell (dw->dvi.tmpFile);
+ else
+ m->position = ftell (dw->dvi.file);
+}
+
+long
+SearchPagePosition (DviWidget dw, int number)
+{
+ DviFileMap *m;
+
+ if (!(m = MapPageNumberToFileMap (dw, number)))
+ return -1;
+ return m->position;
+}
+
+void
+FileSeek(DviWidget dw, long position)
+{
+ if (dw->dvi.tmpFile) {
+ dw->dvi.readingTmp = 1;
+ fseek (dw->dvi.tmpFile, position, 0);
+ } else
+ fseek (dw->dvi.file, position, 0);
+}
diff --git a/src/devices/xditview/page.h b/src/devices/xditview/page.h
new file mode 100644
index 0000000..e2c938d
--- /dev/null
+++ b/src/devices/xditview/page.h
@@ -0,0 +1,5 @@
+void DestroyFileMap(DviFileMap *);
+void FileSeek(DviWidget, long);
+void ForgetPagePositions(DviWidget);
+void RememberPagePosition(DviWidget, int);
+long SearchPagePosition(DviWidget, int);
diff --git a/src/devices/xditview/parse.c b/src/devices/xditview/parse.c
new file mode 100644
index 0000000..456c7da
--- /dev/null
+++ b/src/devices/xditview/parse.c
@@ -0,0 +1,343 @@
+/*
+ * parse.c
+ *
+ * parse dvi input
+ */
+
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "DviP.h"
+#include "draw.h"
+#include "font.h"
+#include "lex.h"
+#include "page.h"
+#include "parse.h"
+
+static int StopSeen = 0;
+static void ParseDrawFunction(DviWidget, char *);
+static void ParseDeviceControl(DviWidget);
+static void push_env(DviWidget);
+static void pop_env(DviWidget);
+
+#define HorizontalMove(dw, delta) ((dw)->dvi.state->x += (delta))
+
+
+int
+ParseInput(register DviWidget dw)
+{
+ int n, k;
+ int c;
+ char Buffer[BUFSIZ];
+ int NextPage;
+ int otherc;
+
+ StopSeen = 0;
+
+ /*
+ * make sure some state exists
+ */
+
+ if (!dw->dvi.state)
+ push_env (dw);
+ for (;;) {
+ switch (DviGetC(dw, &c)) {
+ case '\n':
+ break;
+ case ' ': /* when input is text */
+ case 0: /* occasional noise creeps in */
+ break;
+ case '{': /* push down current environment */
+ push_env(dw);
+ break;
+ case '}':
+ pop_env(dw);
+ break;
+ /*
+ * two motion digits plus a character
+ */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ HorizontalMove(dw, (c-'0')*10 +
+ DviGetC(dw,&otherc)-'0');
+ /* fall through */
+ case 'c': /* single ascii character */
+ DviGetC(dw,&c);
+ if (c == ' ')
+ break;
+ Buffer[0] = c;
+ Buffer[1] = '\0';
+ (void) PutCharacter (dw, Buffer);
+ break;
+ case 'C':
+ GetWord (dw, Buffer, BUFSIZ);
+ (void) PutCharacter (dw, Buffer);
+ break;
+ case 't':
+ Buffer[1] = '\0';
+ while (DviGetC (dw, &c) != EOF
+ && c != ' ' && c != '\n') {
+ Buffer[0] = c;
+ HorizontalMove (dw, PutCharacter (dw, Buffer));
+ }
+ break;
+ case 'u':
+ n = GetNumber(dw);
+ Buffer[1] = '\0';
+ while (DviGetC (dw, &c) == ' ')
+ ;
+ while (c != EOF && c != ' ' && c != '\n') {
+ Buffer[0] = c;
+ HorizontalMove (dw,
+ PutCharacter (dw, Buffer) + n);
+ DviGetC (dw, &c);
+ }
+ break;
+
+ case 'D': /* draw function */
+ (void) GetLine(dw, Buffer, BUFSIZ);
+ if (dw->dvi.display_enable)
+ ParseDrawFunction(dw, Buffer);
+ break;
+ case 's': /* ignore fractional sizes */
+ n = GetNumber(dw);
+ dw->dvi.state->font_size = n;
+ break;
+ case 'f':
+ n = GetNumber(dw);
+ dw->dvi.state->font_number = n;
+ break;
+ case 'H': /* absolute horizontal motion */
+ k = GetNumber(dw);
+ HorizontalGoto(dw, k);
+ break;
+ case 'h': /* relative horizontal motion */
+ k = GetNumber(dw);
+ HorizontalMove(dw, k);
+ break;
+ case 'w': /* word space */
+ Word (dw);
+ break;
+ case 'V':
+ n = GetNumber(dw);
+ VerticalGoto(dw, n);
+ break;
+ case 'v':
+ n = GetNumber(dw);
+ VerticalMove(dw, n);
+ break;
+ case 'P': /* new spread */
+ break;
+ case 'p': /* new page */
+ (void) GetNumber(dw);
+ NextPage = dw->dvi.current_page + 1;
+ RememberPagePosition(dw, NextPage);
+ FlushCharCache (dw);
+ return(NextPage);
+ case 'N':
+ n = GetNumber(dw);
+ PutNumberedCharacter (dw, n);
+ break;
+ case 'n': /* end of line */
+ GetNumber(dw);
+ GetNumber(dw);
+ Newline (dw);
+ HorizontalGoto(dw, 0);
+ break;
+ case 'F': /* input files */
+ case '+': /* continuation of X device control */
+ case 'm': /* color */
+ case '#': /* comment */
+ GetLine(dw, NULL, 0);
+ break;
+ case 'x': /* device control */
+ ParseDeviceControl(dw);
+ break;
+ case EOF:
+ dw->dvi.last_page = dw->dvi.current_page;
+ FlushCharCache (dw);
+ return dw->dvi.current_page;
+ default:
+ break;
+ }
+ }
+}
+
+static void
+push_env(DviWidget dw)
+{
+ DviState *new_state;
+
+ new_state = (DviState *) XtMalloc (sizeof (*new_state));
+ if (dw->dvi.state)
+ *new_state = *(dw->dvi.state);
+ else {
+ new_state->font_size = 10;
+ new_state->font_number = 1;
+ new_state->x = 0;
+ new_state->y = 0;
+ }
+ new_state->next = dw->dvi.state;
+ dw->dvi.state = new_state;
+}
+
+static void
+pop_env(DviWidget dw)
+{
+ DviState *old;
+
+ old = dw->dvi.state;
+ dw->dvi.state = old->next;
+ XtFree ((char *) old);
+}
+
+static void
+InitTypesetter (DviWidget dw)
+{
+ while (dw->dvi.state)
+ pop_env (dw);
+ push_env (dw);
+ FlushCharCache (dw);
+}
+
+#define DRAW_ARGS_MAX 128
+
+static void
+ParseDrawFunction(DviWidget dw, char *buf)
+{
+ int v[DRAW_ARGS_MAX];
+ int i, no_move = 0;
+ char *ptr;
+
+ v[0] = v[1] = v[2] = v[3] = 0;
+
+ if (buf[0] == '\0')
+ return;
+ ptr = buf+1;
+
+ for (i = 0; i < DRAW_ARGS_MAX; i++) {
+ if (sscanf(ptr, "%d", v + i) != 1)
+ break;
+ while (*ptr == ' ')
+ ptr++;
+ while (*ptr != '\0' && *ptr != ' ')
+ ptr++;
+ }
+
+ switch (buf[0]) {
+ case 'l': /* draw a line */
+ DrawLine(dw, v[0], v[1]);
+ break;
+ case 'c': /* circle */
+ DrawCircle(dw, v[0]);
+ break;
+ case 'C':
+ DrawFilledCircle(dw, v[0]);
+ break;
+ case 'e': /* ellipse */
+ DrawEllipse(dw, v[0], v[1]);
+ break;
+ case 'E':
+ DrawFilledEllipse(dw, v[0], v[1]);
+ break;
+ case 'a': /* arc */
+ DrawArc(dw, v[0], v[1], v[2], v[3]);
+ break;
+ case 'p':
+ DrawPolygon(dw, v, i);
+ break;
+ case 'P':
+ DrawFilledPolygon(dw, v, i);
+ break;
+ case '~': /* wiggly line */
+ DrawSpline(dw, v, i);
+ break;
+ case 't':
+ dw->dvi.line_thickness = v[0];
+ break;
+ case 'f':
+ if (i > 0 && v[0] >= 0 && v[0] <= DVI_FILL_MAX)
+ dw->dvi.fill = v[0];
+ no_move = 1;
+ break;
+ default:
+#if 0
+ warning("unknown drawing function %s", buf);
+#endif
+ no_move = 1;
+ break;
+ }
+
+ if (!no_move) {
+ if (buf[0] == 'e') {
+ if (i > 0)
+ dw->dvi.state->x += v[0];
+ }
+ else {
+ while (--i >= 0) {
+ if (i & 1)
+ dw->dvi.state->y += v[i];
+ else
+ dw->dvi.state->x += v[i];
+ }
+ }
+ }
+}
+
+static void
+ParseDeviceControl(DviWidget dw) /* Parse the x commands */
+{
+ char str[20], str1[50];
+ int c, n;
+
+ GetWord (dw, str, 20);
+ switch (str[0]) { /* crude for now */
+ case 'T': /* output device */
+ GetWord (dw, str, 20);
+ SetDevice (dw, str);
+ break;
+ case 'i': /* initialize */
+ InitTypesetter (dw);
+ break;
+ case 't': /* trailer */
+ break;
+ case 'p': /* pause -- can restart */
+ break;
+ case 's': /* stop */
+ StopSeen = 1;
+ return;
+ case 'r': /* resolution when prepared */
+ break;
+ case 'f': /* font used */
+ n = GetNumber (dw);
+ GetWord (dw, str, 20);
+ GetLine (dw, str1, 50);
+ SetFontPosition (dw, n, str, str1);
+ break;
+ case 'H': /* char height */
+ break;
+ case 'S': /* slant */
+ break;
+ }
+ while (DviGetC (dw, &c) != '\n') /* skip rest of input line */
+ if (c == EOF)
+ return;
+ return;
+}
+
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/parse.h b/src/devices/xditview/parse.h
new file mode 100644
index 0000000..636f3a6
--- /dev/null
+++ b/src/devices/xditview/parse.h
@@ -0,0 +1 @@
+int ParseInput(register DviWidget dw);
diff --git a/src/devices/xditview/xdit.bm b/src/devices/xditview/xdit.bm
new file mode 100644
index 0000000..0c7aa8c
--- /dev/null
+++ b/src/devices/xditview/xdit.bm
@@ -0,0 +1,14 @@
+#define xdit_width 32
+#define xdit_height 32
+static unsigned char xdit_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x03, 0x02, 0x00, 0x00, 0x02,
+ 0x8a, 0xa2, 0xfc, 0x03, 0x52, 0x14, 0x03, 0x04, 0x02, 0x80, 0x00, 0x08,
+ 0x52, 0x54, 0x00, 0x10, 0x8a, 0x22, 0x8f, 0x23, 0x02, 0x20, 0x06, 0x21,
+ 0x8a, 0x12, 0x8c, 0x40, 0x52, 0x14, 0x8c, 0x40, 0x02, 0x10, 0x58, 0x40,
+ 0x52, 0x14, 0x30, 0x40, 0x8a, 0x12, 0x30, 0x40, 0x02, 0x10, 0x70, 0x40,
+ 0x8a, 0x12, 0xc8, 0x40, 0x52, 0x24, 0xc4, 0xe0, 0x02, 0x20, 0x84, 0xe1,
+ 0x52, 0x54, 0xce, 0xf3, 0x8a, 0xa2, 0x00, 0xf8, 0x02, 0x00, 0x03, 0xfc,
+ 0x8a, 0x22, 0xfc, 0xf3, 0x52, 0x14, 0x00, 0xc2, 0x02, 0x00, 0x00, 0x02,
+ 0x52, 0x14, 0x45, 0x02, 0x8a, 0xa2, 0x28, 0x02, 0x02, 0x00, 0x00, 0x02,
+ 0x02, 0x00, 0x00, 0x02, 0xfe, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
diff --git a/src/devices/xditview/xdit_mask.bm b/src/devices/xditview/xdit_mask.bm
new file mode 100644
index 0000000..a584629
--- /dev/null
+++ b/src/devices/xditview/xdit_mask.bm
@@ -0,0 +1,14 @@
+#define xdit_mask_width 32
+#define xdit_mask_height 32
+static unsigned char xdit_mask_bits[] = {
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07,
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x1f,
+ 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc7,
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07,
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
diff --git a/src/devices/xditview/xditview.am b/src/devices/xditview/xditview.am
new file mode 100644
index 0000000..7bb32ba
--- /dev/null
+++ b/src/devices/xditview/xditview.am
@@ -0,0 +1,130 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+GXDITVIEWSOURCES = \
+ src/devices/xditview/device.c \
+ src/devices/xditview/draw.c \
+ src/devices/xditview/draw.h \
+ src/devices/xditview/Dvi.c \
+ src/devices/xditview/font.c \
+ src/devices/xditview/font.h \
+ src/devices/xditview/lex.c \
+ src/devices/xditview/lex.h \
+ src/devices/xditview/page.c \
+ src/devices/xditview/page.h \
+ src/devices/xditview/parse.c \
+ src/devices/xditview/parse.h \
+ src/devices/xditview/xditview.c \
+ src/devices/xditview/device.h \
+ src/devices/xditview/DviP.h \
+ src/devices/xditview/Menu.h \
+ src/devices/xditview/Dvi.h
+
+GXDITVIEW_GROFF_VERSION_H=src/devices/xditview/groff_version.h
+
+$(GXDITVIEW_GROFF_VERSION_H): $(top_srcdir)/.version
+ $(AM_V_at)$(MKDIR_P) `dirname $@`
+ $(AM_V_GEN)printf \
+ 'const char *Version_string = "%s";\n' '@VERSION@' > $@
+
+if WITHOUT_X11
+GXDITVIEW_MAN1 =
+EXTRA_DIST += $(GXDITVIEWSOURCES)
+else
+GXDITVIEW_MAN1 = src/devices/xditview/gxditview.1
+xditview_srcdir = $(top_srcdir)/src/devices/xditview
+bin_PROGRAMS += gxditview
+gxditview_CPPFLAGS = $(AM_CPPFLAGS) $(X_CFLAGS) -Dlint \
+ -I$(top_builddir)/src/devices/xditview
+gxditview_LDADD = $(X_LIBS) $(X_PRE_LIBS) -lXaw -lXmu -lXt -lX11 \
+ $(X_EXTRA_LIBS) $(LIBM) libxutil.a lib/libgnu.a
+XDITVIEW_GENHDRS = \
+ src/devices/xditview/GXditview-ad.h \
+ $(GXDITVIEW_GROFF_VERSION_H)
+gxditview_SOURCES = $(GXDITVIEWSOURCES)
+nodist_gxditview_SOURCES = $(XDITVIEW_GENHDRS)
+CLEANFILES += $(XDITVIEW_GENHDRS)
+
+man1_MANS += $(GXDITVIEW_MAN1)
+
+# Because we defined gxditview_CPPFLAGS, automake renames all of
+# xditview's objects, adding an "gxditview-" prefix.
+src/devices/xditview/gxditview-device.$(OBJEXT): defs.h
+src/devices/xditview/gxditview-xditview.$(OBJEXT): $(XDITVIEW_GENHDRS)
+
+src/devices/xditview/GXditview-ad.h: $(xditview_srcdir)/GXditview.ad
+ $(AM_V_GEN)$(SHELL) $(xditview_srcdir)/ad2c \
+ $(xditview_srcdir)/GXditview.ad > $@
+endif
+
+EXTRA_DIST += \
+ src/devices/xditview/ad2c \
+ src/devices/xditview/ChangeLog \
+ src/devices/xditview/DESC.in \
+ src/devices/xditview/FontMap-X11 \
+ src/devices/xditview/GXditview-color.ad \
+ src/devices/xditview/GXditview.ad \
+ src/devices/xditview/README \
+ src/devices/xditview/TODO \
+ src/devices/xditview/gray1.bm \
+ src/devices/xditview/gray2.bm \
+ src/devices/xditview/gray3.bm \
+ src/devices/xditview/gray4.bm \
+ src/devices/xditview/gray5.bm \
+ src/devices/xditview/gray6.bm \
+ src/devices/xditview/gray7.bm \
+ src/devices/xditview/gray8.bm \
+ src/devices/xditview/xdit.bm \
+ src/devices/xditview/xdit_mask.bm \
+ src/devices/xditview/gxditview.1.man
+
+# Custom installation of GXditview.ad and GXditview-color.ad
+install-data-local: install_xditview
+uninstall-local: uninstall_xditview
+
+if WITHOUT_X11
+install_xditview:
+uninstall_xditview:
+else
+install_xditview: \
+ $(xditview_srcdir)/FontMap-X11 \
+ $(xditview_srcdir)/GXditview.ad \
+ $(xditview_srcdir)/GXditview-color.ad
+ $(MKDIR_P) $(DESTDIR)$(fontdir)
+ $(INSTALL_DATA) $(xditview_srcdir)/FontMap-X11 \
+ $(DESTDIR)$(fontdir)/FontMap-X11
+ -test -d $(DESTDIR)$(appdefdir) \
+ || $(mkinstalldirs) $(DESTDIR)$(appdefdir)
+ $(INSTALL_DATA) $(xditview_srcdir)/GXditview.ad \
+ $(DESTDIR)$(appdefdir)/GXditview
+ $(INSTALL_DATA) $(xditview_srcdir)/GXditview-color.ad \
+ $(DESTDIR)$(appdefdir)/GXditview-color
+
+uninstall_xditview:
+ rm -f $(DESTDIR)$(appdefdir)/GXditview
+ rm -f $(DESTDIR)$(appdefdir)/GXditview-color
+ rm -f $(DESTDIR)$(fontdir)/FontMap-X11
+ -rmdir $(DESTDIR)$(fontdir) 2>/dev/null
+
+endif
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/xditview/xditview.c b/src/devices/xditview/xditview.c
new file mode 100644
index 0000000..1f56940
--- /dev/null
+++ b/src/devices/xditview/xditview.c
@@ -0,0 +1,684 @@
+/*
+ * Copyright 1991 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. M.I.T. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+/*
+ * xditview --
+ *
+ * Display ditroff output in an X window
+ */
+
+#ifndef SABER
+#ifndef lint
+static char rcsid[] = "$XConsortium: xditview.c,v 1.17 89/12/10 17:05:08 rws Exp $";
+#endif /* lint */
+#endif /* SABER */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xos.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <X11/Shell.h>
+#include <X11/Xaw/Paned.h>
+#include <X11/Xaw/Viewport.h>
+#include <X11/Xaw/Box.h>
+#include <X11/Xaw/Command.h>
+#include <X11/Xaw/Dialog.h>
+#include <X11/Xaw/Label.h>
+#include <X11/Xaw/SimpleMenu.h>
+#include <X11/Xaw/SmeBSB.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "Dvi.h"
+#include "groff_version.h"
+
+#include "xdit.bm"
+#include "xdit_mask.bm"
+
+#ifdef NEED_DECLARATION_POPEN
+FILE *popen(const char *, const char *);
+#endif /* NEED_DECLARATION_POPEN */
+
+#ifdef NEED_DECLARATION_PCLOSE
+int pclose (FILE *);
+#endif /* NEED_DECLARATION_PCLOSE */
+
+typedef void (*MakePromptFunc)(const char *);
+
+static String fallback_resources[] = {
+#include "GXditview-ad.h"
+ NULL
+};
+
+static struct app_resources {
+ char *print_command;
+ char *filename;
+} app_resources;
+
+#define offset(field) XtOffset(struct app_resources *, field)
+
+/* Application resources. */
+
+static XtResource resources[] = {
+ {(String)"printCommand", (String)"PrintCommand", (String)XtRString,
+ sizeof(char*), offset(print_command), (String)XtRString, NULL},
+ {(String)"filename", (String)"Filename", (String)XtRString,
+ sizeof(char*), offset(filename), (String)XtRString, NULL},
+};
+
+#undef offset
+
+/* Command line options table. Only resources are entered here...there is a
+ pass over the remaining options after XrmParseCommand is let loose. */
+
+static XrmOptionDescRec options[] = {
+{(char *)"-page", (char *)"*dvi.pageNumber",
+ XrmoptionSepArg, NULL},
+{(char *)"-backingStore", (char *)"*dvi.backingStore",
+ XrmoptionSepArg, NULL},
+{(char *)"-resolution", (char *)"*dvi.resolution",
+ XrmoptionSepArg, NULL},
+{(char *)"-printCommand", (char *)".printCommand",
+ XrmoptionSepArg, NULL},
+{(char *)"-filename", (char *)".filename",
+ XrmoptionSepArg, NULL},
+{(char *)"-noPolyText", (char *)"*dvi.noPolyText",
+ XrmoptionNoArg, (XPointer)"TRUE"},
+};
+
+static char current_print_command[1024];
+
+static char current_file_name[1024];
+static FILE *current_file;
+
+/*
+ * Report the syntax for calling xditview.
+ */
+
+static void
+Syntax(const char *progname, bool had_error)
+{
+ FILE *stream = stdout;
+ if (had_error)
+ stream = stderr;
+ (void) fprintf (stream, "usage: %s [X-toolkit-option]"
+ " [-backingStore backing-store-type]"
+ " [-filename file]"
+// See draw.c:FlushCharCache().
+#if 0
+ " [-noPolyText]"
+#endif
+ " [-page page-number]"
+ " [-printCommand command]"
+ " [-resolution resolution]"
+ " [file]\n", progname);
+ (void) fprintf (stream, "usage: %s {-version | --version}\n",
+ progname);
+ (void) fprintf (stream, "usage: %s {-help | --help}\n",
+ progname);
+ if (had_error)
+ exit(EXIT_FAILURE);
+ else
+ exit(EXIT_SUCCESS);
+}
+
+static void NewFile (const char *);
+static void SetPageNumber (int);
+static Widget toplevel, paned, viewport, dvi;
+static Widget page;
+static Widget simpleMenu;
+
+static void NextPage(Widget, XtPointer, XtPointer);
+static void PreviousPage(Widget, XtPointer, XtPointer);
+static void SelectPage(Widget, XtPointer, XtPointer);
+static void OpenFile(Widget, XtPointer, XtPointer);
+static void Quit(Widget, XtPointer, XtPointer);
+static void Print(Widget, XtPointer, XtPointer);
+
+static struct menuEntry {
+ const char *name;
+ XtCallbackProc function;
+} menuEntries[] = {
+ {"nextPage", NextPage},
+ {"previousPage", PreviousPage},
+ {"selectPage", SelectPage},
+ {"print", Print},
+ {"openFile", OpenFile},
+ {"quit", Quit},
+};
+
+static void NextPageAction(Widget, XEvent *, String *, Cardinal *);
+static void PreviousPageAction(Widget, XEvent *, String *, Cardinal *);
+static void SelectPageAction(Widget, XEvent *, String *, Cardinal *);
+static void OpenFileAction(Widget, XEvent *, String *, Cardinal *);
+static void QuitAction(Widget, XEvent *, String *, Cardinal *);
+static void AcceptAction(Widget, XEvent *, String *, Cardinal *);
+static void CancelAction(Widget, XEvent *, String *, Cardinal *);
+static void PrintAction(Widget, XEvent *, String *, Cardinal *);
+static void RerasterizeAction(Widget, XEvent *, String *, Cardinal *);
+
+static void MakePrompt(Widget, const char *, MakePromptFunc, const char *);
+
+XtActionsRec xditview_actions[] = {
+ {(String)"NextPage", NextPageAction},
+ {(String)"PreviousPage", PreviousPageAction},
+ {(String)"SelectPage", SelectPageAction},
+ {(String)"Print", PrintAction},
+ {(String)"OpenFile", OpenFileAction},
+ {(String)"Rerasterize", RerasterizeAction},
+ {(String)"Quit", QuitAction},
+ {(String)"Accept", AcceptAction},
+ {(String)"Cancel", CancelAction},
+};
+
+#define MenuNextPage 0
+#define MenuPreviousPage 1
+#define MenuSelectPage 2
+#define MenuPrint 3
+#define MenuOpenFile 4
+#define MenuQuit 5
+
+static char pageLabel[256] = "Page <none>";
+
+int main(int argc, char **argv)
+{
+ char *file_name = 0;
+ Cardinal i;
+ static Arg labelArgs[] = {
+ {XtNlabel, (XtArgVal) pageLabel},
+ };
+ XtAppContext xtcontext;
+ Arg topLevelArgs[2];
+ Widget entry;
+ Arg pageNumberArgs[1];
+ int page_number;
+
+ toplevel = XtAppInitialize(&xtcontext, "GXditview",
+ options, XtNumber (options),
+ &argc, argv, fallback_resources, NULL, 0);
+ if (argc > 2)
+ Syntax(argv[0], true /* had error */);
+ else if (argc == 2) {
+ if ((strcmp(argv[1], "-help") == 0)
+ || (strcmp(argv[1], "--help") == 0))
+ Syntax(argv[0], false /* did not have error */);
+ else if ((strcmp(argv[1], "-version") == 0)
+ || (strcmp(argv[1], "--version") == 0)) {
+ (void) printf("GNU gxditview (groff) version %s\n",
+ Version_string);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ XtGetApplicationResources(toplevel, (XtPointer)&app_resources,
+ resources, XtNumber(resources),
+ NULL, (Cardinal) 0);
+ if (app_resources.print_command)
+ strcpy(current_print_command, app_resources.print_command);
+
+ XtAppAddActions(xtcontext, xditview_actions, XtNumber (xditview_actions));
+
+ XtSetArg (topLevelArgs[0], XtNiconPixmap,
+ XCreateBitmapFromData (XtDisplay (toplevel),
+ XtScreen(toplevel)->root,
+ (char *)xdit_bits,
+ xdit_width, xdit_height));
+
+ XtSetArg (topLevelArgs[1], XtNiconMask,
+ XCreateBitmapFromData (XtDisplay (toplevel),
+ XtScreen(toplevel)->root,
+ (char *)xdit_mask_bits,
+ xdit_mask_width, xdit_mask_height));
+ XtSetValues (toplevel, topLevelArgs, 2);
+ if (argc > 1)
+ file_name = argv[1];
+
+ /*
+ * create the menu and insert the entries
+ */
+ simpleMenu = XtCreatePopupShell ("menu", simpleMenuWidgetClass, toplevel,
+ NULL, 0);
+ for (i = 0; i < XtNumber (menuEntries); i++) {
+ entry = XtCreateManagedWidget(menuEntries[i].name,
+ smeBSBObjectClass, simpleMenu,
+ NULL, (Cardinal) 0);
+ XtAddCallback(entry, XtNcallback, menuEntries[i].function, NULL);
+ }
+
+ paned = XtCreateManagedWidget("paned", panedWidgetClass, toplevel,
+ NULL, (Cardinal) 0);
+ viewport = XtCreateManagedWidget("viewport", viewportWidgetClass, paned,
+ NULL, (Cardinal) 0);
+ dvi = XtCreateManagedWidget ("dvi", dviWidgetClass, viewport, NULL, 0);
+ page = XtCreateManagedWidget ("label", labelWidgetClass, paned,
+ labelArgs, XtNumber (labelArgs));
+ XtSetArg (pageNumberArgs[0], XtNpageNumber, &page_number);
+ XtGetValues (dvi, pageNumberArgs, 1);
+ if (file_name)
+ NewFile (file_name);
+ /* NewFile modifies current_file_name, so do this here. */
+ if (app_resources.filename)
+ strcpy(current_file_name, app_resources.filename);
+ XtRealizeWidget (toplevel);
+ if (file_name)
+ SetPageNumber (page_number);
+ XtInstallAllAccelerators(paned,paned);
+ XtAppMainLoop(xtcontext);
+ return 0;
+}
+
+static void
+SetPageNumber (int number)
+{
+ Arg arg[2];
+ int actual_number, last_page;
+
+ XtSetArg (arg[0], XtNpageNumber, number);
+ XtSetValues (dvi, arg, 1);
+ XtSetArg (arg[0], XtNpageNumber, &actual_number);
+ XtSetArg (arg[1], XtNlastPageNumber, &last_page);
+ XtGetValues (dvi, arg, 2);
+ if (actual_number == 0)
+ sprintf (pageLabel, "Page <none>");
+ else if (last_page > 0)
+ sprintf (pageLabel, "Page %d of %d", actual_number, last_page);
+ else
+ sprintf (pageLabel, "Page %d", actual_number);
+ XtSetArg (arg[0], XtNlabel, pageLabel);
+ XtSetValues (page, arg, 1);
+}
+
+static void
+SelectPageNumber (const char *number_string)
+{
+ SetPageNumber (atoi(number_string));
+}
+
+static int hadFile = 0;
+
+static void
+NewFile (const char *name)
+{
+ Arg arg[2];
+ char *n;
+ FILE *new_file;
+ Boolean seek = 0;
+
+ if (current_file) {
+ if (!strcmp (current_file_name, "-"))
+ ;
+ else if (current_file_name[0] == '|')
+ pclose (current_file);
+ else
+ fclose (current_file);
+ }
+ if (!strcmp (name, "-"))
+ new_file = stdin;
+ else if (name[0] == '|')
+ new_file = popen (name+1, "r");
+ else {
+ new_file = fopen (name, "r");
+ seek = 1;
+ }
+ if (!new_file) {
+ /* XXX display error message */
+ return;
+ }
+ XtSetArg (arg[0], XtNfile, new_file);
+ XtSetArg (arg[1], XtNseek, seek);
+ XtSetValues (dvi, arg, 2);
+ if (hadFile || name[0] != '-' || name[1] != '\0') {
+ XtSetArg (arg[0], XtNtitle, name);
+ if (name[0] != '/' && (n = strrchr (name, '/')))
+ n = n + 1;
+ else
+ n = (char *)name;
+ XtSetArg (arg[1], XtNiconName, n);
+ XtSetValues (toplevel, arg, 2);
+ }
+ hadFile = 1;
+ SelectPageNumber ("1");
+ strcpy (current_file_name, name);
+ current_file = new_file;
+}
+
+static char fileBuf[1024];
+
+static void
+ResetMenuEntry (Widget entry)
+{
+ Arg arg[1];
+
+ XtSetArg (arg[0], (String)XtNpopupOnEntry, entry);
+ XtSetValues (XtParent(entry) , arg, (Cardinal) 1);
+}
+
+/*ARGSUSED*/
+
+static void
+NextPage (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ NextPageAction((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+NextPageAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ Arg args[1];
+ int number;
+
+ XtSetArg (args[0], XtNpageNumber, &number);
+ XtGetValues (dvi, args, 1);
+ SetPageNumber (number+1);
+
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+}
+
+/*ARGSUSED*/
+
+static void
+PreviousPage (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ PreviousPageAction ((Widget)NULL, (XEvent *) 0, (String *) 0,
+ (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+PreviousPageAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ Arg args[1];
+ int number;
+
+ XtSetArg (args[0], XtNpageNumber, &number);
+ XtGetValues (dvi, args, 1);
+ SetPageNumber (number-1);
+
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+}
+
+/* ARGSUSED */
+
+static void
+SelectPage (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ SelectPageAction ((Widget)NULL, (XEvent *) 0, (String *) 0,
+ (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+SelectPageAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ MakePrompt (toplevel, "Page number", SelectPageNumber, "");
+}
+
+
+static void
+DoPrint (const char *name)
+{
+ FILE *print_file;
+ RETSIGTYPE (*handler)(int);
+
+ /* Avoid dying because of an invalid command. */
+ handler = signal(SIGPIPE, SIG_IGN);
+
+ print_file = popen(name, "w");
+ if (!print_file)
+ /* XXX print error message */
+ return;
+ DviSaveToFile(dvi, print_file);
+ pclose(print_file);
+ signal(SIGPIPE, handler);
+ strcpy(current_print_command, name);
+}
+
+static void
+RerasterizeAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ Arg args[1];
+ int number;
+
+ if (current_file_name[0] == 0) {
+ /* XXX display an error message */
+ return;
+ }
+ XtSetArg (args[0], XtNpageNumber, &number);
+ XtGetValues (dvi, args, 1);
+ NewFile(current_file_name);
+ SetPageNumber (number);
+
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+}
+
+/* ARGSUSED */
+
+static void
+Print (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ PrintAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+PrintAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ if (current_print_command[0])
+ strcpy (fileBuf, current_print_command);
+ else
+ fileBuf[0] = '\0';
+ MakePrompt (toplevel, "Print command:", DoPrint, fileBuf);
+}
+
+
+/* ARGSUSED */
+
+static void
+OpenFile (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ OpenFileAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+OpenFileAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ if (current_file_name[0])
+ strcpy (fileBuf, current_file_name);
+ else
+ fileBuf[0] = '\0';
+ MakePrompt (toplevel, "File to open:", NewFile, fileBuf);
+}
+
+/* ARGSUSED */
+
+static void
+Quit (Widget entry, XtPointer closure, XtPointer data)
+{
+ entry = entry; /* unused; suppress compiler warning */
+ closure = closure;
+ data = data;
+
+ QuitAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+}
+
+static void
+QuitAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ exit (0);
+}
+
+Widget promptShell, promptDialog;
+MakePromptFunc promptfunction;
+
+/* ARGSUSED */
+static
+void CancelAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ if (promptShell) {
+ XtSetKeyboardFocus(toplevel, (Widget) None);
+ XtDestroyWidget(promptShell);
+ promptShell = (Widget) 0;
+ }
+}
+
+static
+void AcceptAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ (*promptfunction)(XawDialogGetValueString(promptDialog));
+ CancelAction (widget, event, params, num_params);
+}
+
+static void
+MakePrompt(Widget centerw, const char *prompt,
+ MakePromptFunc func, const char *def)
+{
+ static Arg dialogArgs[] = {
+ {XtNlabel, 0},
+ {XtNvalue, 0},
+ };
+ Arg valueArgs[1];
+ Arg centerArgs[2];
+ Position source_x, source_y;
+ Position dest_x, dest_y;
+ Dimension center_width, center_height;
+ Dimension prompt_width, prompt_height;
+ Widget valueWidget;
+
+ CancelAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ promptShell = XtCreatePopupShell ("promptShell", transientShellWidgetClass,
+ toplevel, NULL, (Cardinal) 0);
+ dialogArgs[0].value = (XtArgVal)prompt;
+ dialogArgs[1].value = (XtArgVal)def;
+ promptDialog = XtCreateManagedWidget( "promptDialog", dialogWidgetClass,
+ promptShell, dialogArgs, XtNumber (dialogArgs));
+ XawDialogAddButton(promptDialog, "accept", NULL, (XtPointer) 0);
+ XawDialogAddButton(promptDialog, "cancel", NULL, (XtPointer) 0);
+ valueWidget = XtNameToWidget (promptDialog, "value");
+ if (valueWidget) {
+ XtSetArg (valueArgs[0], (String)XtNresizable, TRUE);
+ XtSetValues (valueWidget, valueArgs, 1);
+ /*
+ * as resizable isn't set until just above, the
+ * default value will be displayed incorrectly.
+ * rectify the situation by resetting the values
+ */
+ XtSetValues (promptDialog, dialogArgs, XtNumber (dialogArgs));
+ }
+ XtSetKeyboardFocus (promptDialog, valueWidget);
+ XtSetKeyboardFocus (toplevel, valueWidget);
+ XtRealizeWidget (promptShell);
+ /*
+ * place the widget in the center of the "parent"
+ */
+ XtSetArg (centerArgs[0], XtNwidth, &center_width);
+ XtSetArg (centerArgs[1], XtNheight, &center_height);
+ XtGetValues (centerw, centerArgs, 2);
+ XtSetArg (centerArgs[0], XtNwidth, &prompt_width);
+ XtSetArg (centerArgs[1], XtNheight, &prompt_height);
+ XtGetValues (promptShell, centerArgs, 2);
+ source_x = (center_width - prompt_width) / 2;
+ source_y = (center_height - prompt_height) / 3;
+ XtTranslateCoords (centerw, source_x, source_y, &dest_x, &dest_y);
+ XtSetArg (centerArgs[0], XtNx, dest_x);
+ XtSetArg (centerArgs[1], XtNy, dest_y);
+ XtSetValues (promptShell, centerArgs, 2);
+ XtMapWidget(promptShell);
+ promptfunction = func;
+}
+
+/*
+Local Variables:
+c-indent-level: 4
+c-continued-statement-offset: 4
+c-brace-offset: -4
+c-argdecl-indent: 4
+c-label-offset: -4
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/include/DviChar.h b/src/include/DviChar.h
new file mode 100644
index 0000000..1100d81
--- /dev/null
+++ b/src/include/DviChar.h
@@ -0,0 +1,55 @@
+/* -*- C -*- */
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You can find the license text at
+<http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+/*
+ * DviChar.h
+ *
+ * descriptions for mapping dvi names to
+ * font indexes and back. Dvi fonts are all
+ * 256 elements (actually only 256-32 are usable).
+ *
+ * The encoding names are taken from X -
+ * case insensitive, a dash separating the
+ * CharSetRegistry from the CharSetEncoding
+ */
+
+# define DVI_MAX_SYNONYMS 10
+# define DVI_MAP_SIZE 256
+# define DVI_HASH_SIZE 256
+
+typedef struct _dviCharNameHash {
+ struct _dviCharNameHash *next;
+ const char *name;
+ int position;
+} DviCharNameHash;
+
+typedef struct _dviCharNameMap {
+ const char *encoding;
+ int special;
+ const char *dvi_names[DVI_MAP_SIZE][DVI_MAX_SYNONYMS];
+ DviCharNameHash *buckets[DVI_HASH_SIZE];
+} DviCharNameMap;
+
+DviCharNameMap *DviFindMap (char *);
+void DviRegisterMap (DviCharNameMap *);
+#ifdef NOTDEF
+char *DviCharName (DviCharNameMap *, int, int);
+#else
+#define DviCharName(map,index,synonym) ((map)->dvi_names[index][synonym])
+#endif
+int DviCharIndex (DviCharNameMap *, const char *);
diff --git a/src/include/XFontName.h b/src/include/XFontName.h
new file mode 100644
index 0000000..4e9c694
--- /dev/null
+++ b/src/include/XFontName.h
@@ -0,0 +1,50 @@
+typedef struct _xFontName {
+ char Registry[256];
+ char Foundry[256];
+ char FamilyName[256];
+ char WeightName[256];
+ char Slant[3];
+ char SetwidthName[256];
+ char AddStyleName[256];
+ unsigned int PixelSize;
+ unsigned int PointSize;
+ unsigned int ResolutionX;
+ unsigned int ResolutionY;
+ char Spacing[2];
+ unsigned int AverageWidth;
+ char CharSetRegistry[256];
+ char CharSetEncoding[256];
+} XFontName;
+
+#define FontNameRegistry (1<<0)
+#define FontNameFoundry (1<<1)
+#define FontNameFamilyName (1<<2)
+#define FontNameWeightName (1<<3)
+#define FontNameSlant (1<<4)
+#define FontNameSetwidthName (1<<5)
+#define FontNameAddStyleName (1<<6)
+#define FontNamePixelSize (1<<7)
+#define FontNamePointSize (1<<8)
+#define FontNameResolutionX (1<<9)
+#define FontNameResolutionY (1<<10)
+#define FontNameSpacing (1<<11)
+#define FontNameAverageWidth (1<<12)
+#define FontNameCharSetRegistry (1<<13)
+#define FontNameCharSetEncoding (1<<14)
+
+#define SlantRoman "R"
+#define SlantItalic "I"
+#define SlantOblique "O"
+#define SlantReverseItalic "RI"
+#define SlantReverseOblique "RO"
+
+#define SpacingMonoSpaced "M"
+#define SpacingProportional "P"
+#define SpacingCharacterCell "C"
+
+typedef char *XFontNameString;
+
+Bool XParseFontName (XFontNameString, XFontName *, unsigned int *);
+Bool XFormatFontName (XFontName *, unsigned int, XFontNameString);
+Bool XCompareFontName (XFontName *, XFontName *, unsigned int);
+Bool XCopyFontName (XFontName *, XFontName *, unsigned int);
diff --git a/src/include/cmap.h b/src/include/cmap.h
new file mode 100644
index 0000000..3a62da1
--- /dev/null
+++ b/src/include/cmap.h
@@ -0,0 +1,55 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 255
+#endif
+
+enum cmap_builtin { CMAP_BUILTIN };
+
+class cmap {
+public:
+ cmap();
+ cmap(cmap_builtin);
+ int operator()(unsigned char) const;
+ unsigned char &operator[](unsigned char);
+
+ friend class cmap_init;
+private:
+ unsigned char v[UCHAR_MAX+1];
+};
+
+inline int cmap::operator()(unsigned char c) const
+{
+ return v[c];
+}
+
+inline unsigned char &cmap::operator[](unsigned char c)
+{
+ return v[c];
+}
+
+extern cmap cmlower;
+extern cmap cmupper;
+
+static class cmap_init {
+ static int initialised;
+public:
+ cmap_init();
+} _cmap_init;
diff --git a/src/include/color.h b/src/include/color.h
new file mode 100644
index 0000000..e3f945b
--- /dev/null
+++ b/src/include/color.h
@@ -0,0 +1,88 @@
+// -*- C++ -*-
+/* <groff_src_dir>/src/include/color.h
+Copyright (C) 2001-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley <gaius@glam.ac.uk>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stddef.h>
+#include "symbol.h"
+
+enum color_scheme {DEFAULT, CMY, CMYK, RGB, GRAY};
+
+class color {
+private:
+ color_scheme scheme;
+ unsigned int components[4];
+ color *next;
+
+ int read_encoding(const color_scheme, const char * const,
+ const size_t);
+
+public:
+ symbol nm;
+ enum {MAX_COLOR_VAL = 0xffff};
+ color(symbol s = default_symbol) : scheme(DEFAULT), nm(s) {}
+ color(const color * const);
+ ~color();
+
+ int operator==(const color & c) const;
+ int operator!=(const color & c) const;
+
+ int is_default() { return scheme == DEFAULT; }
+
+ // set color from given color component values
+ void set_default();
+ void set_rgb(const unsigned int r, const unsigned int g,
+ const unsigned int b);
+ void set_cmy(const unsigned int c, const unsigned int m,
+ const unsigned int y);
+ void set_cmyk(const unsigned int c, const unsigned int m,
+ const unsigned int y, const unsigned int k);
+ void set_gray(const unsigned int g);
+
+ // set color from a color string
+ int read_rgb(const char * const s);
+ int read_cmy(const char * const s);
+ int read_cmyk(const char * const s);
+ int read_gray(const char * const s);
+
+ // Return the actual color scheme and retrieve the color components
+ // into a predefined vector (of length at least 4).
+ color_scheme get_components(unsigned int *c) const;
+
+ // retrieve the components of a color
+ void get_rgb(unsigned int *r, unsigned int *g, unsigned int *b) const;
+ void get_cmy(unsigned int *c, unsigned int *m, unsigned int *y) const;
+ void get_cmyk(unsigned int *c, unsigned int *m,
+ unsigned int *y, unsigned int *k) const;
+ void get_gray(unsigned int *g) const;
+
+ char *print_color();
+};
+
+#define Cyan components[0]
+#define Magenta components[1]
+#define Yellow components[2]
+#define Black components[3]
+
+#define Red components[0]
+#define Green components[1]
+#define Blue components[2]
+
+#define Gray components[0]
+
+extern color default_color;
diff --git a/src/include/config.hin b/src/include/config.hin
new file mode 100644
index 0000000..374b4c6
--- /dev/null
+++ b/src/include/config.hin
@@ -0,0 +1,1584 @@
+/* src/include/config.hin. Generated from configure.ac by autoheader. */
+
+/* Define if building universal (internal helper macro) */
+#undef AC_APPLE_UNIVERSAL_BUILD
+
+/* Define to the number of bits in type 'ptrdiff_t'. */
+#undef BITSIZEOF_PTRDIFF_T
+
+/* Define to the number of bits in type 'sig_atomic_t'. */
+#undef BITSIZEOF_SIG_ATOMIC_T
+
+/* Define to the number of bits in type 'size_t'. */
+#undef BITSIZEOF_SIZE_T
+
+/* Define to the number of bits in type 'wchar_t'. */
+#undef BITSIZEOF_WCHAR_T
+
+/* Define to the number of bits in type 'wint_t'. */
+#undef BITSIZEOF_WINT_T
+
+/* Define if you wish *printf() functions that have a safe handling of
+ non-IEEE-754 'long double' values. */
+#undef CHECK_PRINTF_SAFE
+
+/* Define to 1 if using 'alloca.c'. */
+#undef C_ALLOCA
+
+/* Define as the bit index in the word where to find bit 0 of the exponent of
+ 'double'. */
+#undef DBL_EXPBIT0_BIT
+
+/* Define as the word index where to find the exponent of 'double'. */
+#undef DBL_EXPBIT0_WORD
+
+/* Define as the bit index in the word where to find the sign of 'double'. */
+#undef DBL_SIGNBIT_BIT
+
+/* Define as the word index where to find the sign of 'double'. */
+#undef DBL_SIGNBIT_WORD
+
+/* Define as the bit index in the word where to find bit 0 of the exponent of
+ 'float'. */
+#undef FLT_EXPBIT0_BIT
+
+/* Define as the word index where to find the exponent of 'float'. */
+#undef FLT_EXPBIT0_WORD
+
+/* Define as the bit index in the word where to find the sign of 'float'. */
+#undef FLT_SIGNBIT_BIT
+
+/* Define as the word index where to find the sign of 'float'. */
+#undef FLT_SIGNBIT_WORD
+
+/* Define to a C preprocessor expression that evaluates to 1 or 0, depending
+ whether the gnulib module fscanf shall be considered present. */
+#undef GNULIB_FSCANF
+
+/* Define to 1 if printf and friends should be labeled with attribute
+ "__gnu_printf__" instead of "__printf__" */
+#undef GNULIB_PRINTF_ATTRIBUTE_FLAVOR_GNU
+
+/* Define to a C preprocessor expression that evaluates to 1 or 0, depending
+ whether the gnulib module scanf shall be considered present. */
+#undef GNULIB_SCANF
+
+/* Define to a C preprocessor expression that evaluates to 1 or 0, depending
+ whether the gnulib module snprintf shall be considered present. */
+#undef GNULIB_SNPRINTF
+
+/* Define to 1 when the gnulib module fgetc should be tested. */
+#undef GNULIB_TEST_FGETC
+
+/* Define to 1 when the gnulib module fgets should be tested. */
+#undef GNULIB_TEST_FGETS
+
+/* Define to 1 when the gnulib module fprintf should be tested. */
+#undef GNULIB_TEST_FPRINTF
+
+/* Define to 1 when the gnulib module fprintf-posix should be tested. */
+#undef GNULIB_TEST_FPRINTF_POSIX
+
+/* Define to 1 when the gnulib module fputc should be tested. */
+#undef GNULIB_TEST_FPUTC
+
+/* Define to 1 when the gnulib module fputs should be tested. */
+#undef GNULIB_TEST_FPUTS
+
+/* Define to 1 when the gnulib module fread should be tested. */
+#undef GNULIB_TEST_FREAD
+
+/* Define to 1 when the gnulib module free-posix should be tested. */
+#undef GNULIB_TEST_FREE_POSIX
+
+/* Define to 1 when the gnulib module frexp should be tested. */
+#undef GNULIB_TEST_FREXP
+
+/* Define to 1 when the gnulib module frexpl should be tested. */
+#undef GNULIB_TEST_FREXPL
+
+/* Define to 1 when the gnulib module fscanf should be tested. */
+#undef GNULIB_TEST_FSCANF
+
+/* Define to 1 when the gnulib module fwrite should be tested. */
+#undef GNULIB_TEST_FWRITE
+
+/* Define to 1 when the gnulib module getc should be tested. */
+#undef GNULIB_TEST_GETC
+
+/* Define to 1 when the gnulib module getchar should be tested. */
+#undef GNULIB_TEST_GETCHAR
+
+/* Define to 1 when the gnulib module memchr should be tested. */
+#undef GNULIB_TEST_MEMCHR
+
+/* Define to 1 when the gnulib module printf should be tested. */
+#undef GNULIB_TEST_PRINTF
+
+/* Define to 1 when the gnulib module putc should be tested. */
+#undef GNULIB_TEST_PUTC
+
+/* Define to 1 when the gnulib module putchar should be tested. */
+#undef GNULIB_TEST_PUTCHAR
+
+/* Define to 1 when the gnulib module puts should be tested. */
+#undef GNULIB_TEST_PUTS
+
+/* Define to 1 when the gnulib module scanf should be tested. */
+#undef GNULIB_TEST_SCANF
+
+/* Define to 1 when the gnulib module signbit should be tested. */
+#undef GNULIB_TEST_SIGNBIT
+
+/* Define to 1 when the gnulib module snprintf should be tested. */
+#undef GNULIB_TEST_SNPRINTF
+
+/* Define to 1 when the gnulib module vfprintf should be tested. */
+#undef GNULIB_TEST_VFPRINTF
+
+/* Define to 1 when the gnulib module vprintf should be tested. */
+#undef GNULIB_TEST_VPRINTF
+
+/* Define to 1 when the gnulib module vsnprintf should be tested. */
+#undef GNULIB_TEST_VSNPRINTF
+
+/* Define to 1 when the gnulib module wcwidth should be tested. */
+#undef GNULIB_TEST_WCWIDTH
+
+/* Define to 1 if you have 'alloca' after including <alloca.h>, a header that
+ may be supplied by this distribution. */
+#undef HAVE_ALLOCA
+
+/* Define to 1 if <alloca.h> works. */
+#undef HAVE_ALLOCA_H
+
+/* Define to 1 if you have the <bp-sym.h> header file. */
+#undef HAVE_BP_SYM_H
+
+/* Define if you have a C++ <inttypes.h>. */
+#undef HAVE_CC_INTTYPES_H
+
+/* Define if you have a C++ <limits.h>. */
+#undef HAVE_CC_LIMITS_H
+
+/* Define if you have a C++ <osfcn.h>. */
+#undef HAVE_CC_OSFCN_H
+
+/* Define if the copysignf function is declared in <math.h> and available in
+ libc. */
+#undef HAVE_COPYSIGNF_IN_LIBC
+
+/* Define if the copysignl function is declared in <math.h> and available in
+ libc. */
+#undef HAVE_COPYSIGNL_IN_LIBC
+
+/* Define if the copysign function is declared in <math.h> and available in
+ libc. */
+#undef HAVE_COPYSIGN_IN_LIBC
+
+/* Define to 1 if you have the <crtdefs.h> header file. */
+#undef HAVE_CRTDEFS_H
+
+/* Define to 1 if the static_assert keyword works. */
+#undef HAVE_C_STATIC_ASSERT
+
+/* Define to 1 if you have the declaration of `alarm', and to 0 if you don't.
+ */
+#undef HAVE_DECL_ALARM
+
+/* Define to 1 if you have the declaration of `copysign', and to 0 if you
+ don't. */
+#undef HAVE_DECL_COPYSIGN
+
+/* Define to 1 if you have the declaration of `copysignf', and to 0 if you
+ don't. */
+#undef HAVE_DECL_COPYSIGNF
+
+/* Define to 1 if you have the declaration of `copysignl', and to 0 if you
+ don't. */
+#undef HAVE_DECL_COPYSIGNL
+
+/* Define to 1 if you have the declaration of `ecvt', and to 0 if you don't.
+ */
+#undef HAVE_DECL_ECVT
+
+/* Define to 1 if you have the declaration of `execvpe', and to 0 if you
+ don't. */
+#undef HAVE_DECL_EXECVPE
+
+/* Define to 1 if you have the declaration of `fcloseall', and to 0 if you
+ don't. */
+#undef HAVE_DECL_FCLOSEALL
+
+/* Define to 1 if you have the declaration of `fcvt', and to 0 if you don't.
+ */
+#undef HAVE_DECL_FCVT
+
+/* Define to 1 if you have the declaration of `gcvt', and to 0 if you don't.
+ */
+#undef HAVE_DECL_GCVT
+
+/* Define to 1 if you have the declaration of `getc_unlocked', and to 0 if you
+ don't. */
+#undef HAVE_DECL_GETC_UNLOCKED
+
+/* Define to 1 if you have the declaration of `getw', and to 0 if you don't.
+ */
+#undef HAVE_DECL_GETW
+
+/* Define to 1 if you have the declaration of `putw', and to 0 if you don't.
+ */
+#undef HAVE_DECL_PUTW
+
+/* Define to 1 if you have the declaration of `snprintf', and to 0 if you
+ don't. */
+#undef HAVE_DECL_SNPRINTF
+
+/* Define to 1 if you have the declaration of `strsignal', and to 0 if you
+ don't. */
+#undef HAVE_DECL_STRSIGNAL
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#undef HAVE_DECL_SYS_SIGLIST
+
+/* Define to 1 if you have the declaration of `towlower', and to 0 if you
+ don't. */
+#undef HAVE_DECL_TOWLOWER
+
+/* Define to 1 if you have the declaration of `vsnprintf', and to 0 if you
+ don't. */
+#undef HAVE_DECL_VSNPRINTF
+
+/* Define to 1 if you have the declaration of `wcsdup', and to 0 if you don't.
+ */
+#undef HAVE_DECL_WCSDUP
+
+/* Define to 1 if you have the declaration of `wcwidth', and to 0 if you
+ don't. */
+#undef HAVE_DECL_WCWIDTH
+
+/* Define to 1 if you have the declaration of `_snprintf', and to 0 if you
+ don't. */
+#undef HAVE_DECL__SNPRINTF
+
+/* Define to 1 if you have the <direct.h> header file. */
+#undef HAVE_DIRECT_H
+
+/* Define to 1 if you have the <dirent.h> header file. */
+#undef HAVE_DIRENT_H
+
+/* Define to 1 if you have the <features.h> header file. */
+#undef HAVE_FEATURES_H
+
+/* Define to 1 if you have the `fmod' function. */
+#undef HAVE_FMOD
+
+/* Define if the 'free' function is guaranteed to preserve errno. */
+#undef HAVE_FREE_POSIX
+
+/* Define if the frexpl function is available in libc. */
+#undef HAVE_FREXPL_IN_LIBC
+
+/* Define if the frexp function is available in libc. */
+#undef HAVE_FREXP_IN_LIBC
+
+/* Define to 1 if you have the `getcwd' function. */
+#undef HAVE_GETCWD
+
+/* Define to 1 if you have the `getpagesize' function. */
+#undef HAVE_GETPAGESIZE
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#undef HAVE_GETTIMEOFDAY
+
+/* Define if you have the iconv() function and it works. */
+#undef HAVE_ICONV
+
+/* Define if you have the 'intmax_t' type in <stdint.h> or <inttypes.h>. */
+#undef HAVE_INTMAX_T
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define if <inttypes.h> exists, doesn't clash with <sys/types.h>, and
+ declares uintmax_t. */
+#undef HAVE_INTTYPES_H_WITH_UINTMAX
+
+/* Define to 1 if you have the `isatty' function. */
+#undef HAVE_ISATTY
+
+/* Define if the isnan(double) function is available in libc. */
+#undef HAVE_ISNAND_IN_LIBC
+
+/* Define if the isnan(float) function is available in libc. */
+#undef HAVE_ISNANF_IN_LIBC
+
+/* Define if the isnan(long double) function is available in libc. */
+#undef HAVE_ISNANL_IN_LIBC
+
+/* Define to 1 if you have the `iswcntrl' function. */
+#undef HAVE_ISWCNTRL
+
+/* Define to 1 if you have the `kill' function. */
+#undef HAVE_KILL
+
+/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
+#undef HAVE_LANGINFO_CODESET
+
+/* Define if the ldexpl function is available in libc. */
+#undef HAVE_LDEXPL_IN_LIBC
+
+/* Define if the ldexp function is available in libc. */
+#undef HAVE_LDEXP_IN_LIBC
+
+/* Define to 1 if you have the <limits.h> header file. */
+#undef HAVE_LIMITS_H
+
+/* Define to 1 if the system has the type 'long long int'. */
+#undef HAVE_LONG_LONG_INT
+
+/* Define to 1 if mmap()'s MAP_ANONYMOUS flag is available after including
+ config.h and <sys/mman.h>. */
+#undef HAVE_MAP_ANONYMOUS
+
+/* Define to 1 if you have the <math.h> header file. */
+#undef HAVE_MATH_H
+
+/* Define to 1 if you have the `mbrtowc' function. */
+#undef HAVE_MBRTOWC
+
+/* Define to 1 if you have the <minix/config.h> header file. */
+#undef HAVE_MINIX_CONFIG_H
+
+/* Define if you have mkstemp(). */
+#undef HAVE_MKSTEMP
+
+/* Define to 1 if you have a working `mmap' system call. */
+#undef HAVE_MMAP
+
+/* Define to 1 if you have the `mprotect' function. */
+#undef HAVE_MPROTECT
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+#undef HAVE_NL_LANGINFO
+
+/* Define to 1 if you have the <process.h> header file. */
+#undef HAVE_PROCESS_H
+
+/* Define to 1 if you have the `putenv' function. */
+#undef HAVE_PUTENV
+
+/* Define to 1 if you have the `rename' function. */
+#undef HAVE_RENAME
+
+/* Define to 1 if 'long double' and 'double' have the same representation. */
+#undef HAVE_SAME_LONG_DOUBLE_AS_DOUBLE
+
+/* Define to 1 if you have the `setlocale' function. */
+#undef HAVE_SETLOCALE
+
+/* Define to 1 if 'sig_atomic_t' is a signed integer type. */
+#undef HAVE_SIGNED_SIG_ATOMIC_T
+
+/* Define to 1 if 'wchar_t' is a signed integer type. */
+#undef HAVE_SIGNED_WCHAR_T
+
+/* Define to 1 if 'wint_t' is a signed integer type. */
+#undef HAVE_SIGNED_WINT_T
+
+/* Define to 1 if you have the `snprintf' function. */
+#undef HAVE_SNPRINTF
+
+/* Define if the return value of the snprintf function is the number of of
+ bytes (excluding the terminating NUL) that would have been produced if the
+ buffer had been large enough. */
+#undef HAVE_SNPRINTF_RETVAL_C99
+
+/* Define if the string produced by the snprintf function is always NUL
+ terminated. */
+#undef HAVE_SNPRINTF_TRUNCATION_C99
+
+/* Define to 1 if you have the <stdbool.h> header file. */
+#undef HAVE_STDBOOL_H
+
+/* Define to 1 if you have the <stddef.h> header file. */
+#undef HAVE_STDDEF_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define if <stdint.h> exists, doesn't clash with <sys/types.h>, and declares
+ uintmax_t. */
+#undef HAVE_STDINT_H_WITH_UINTMAX
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#undef HAVE_STDIO_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#undef HAVE_STRCASECMP
+
+/* Define to 1 if you have the `strdup' function. */
+#undef HAVE_STRDUP
+
+/* Define to 1 if you have the `strerror' function. */
+#undef HAVE_STRERROR
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#undef HAVE_STRNCASECMP
+
+/* Define to 1 if you have the `strnlen' function. */
+#undef HAVE_STRNLEN
+
+/* Define to 1 if you have the `strsep' function. */
+#undef HAVE_STRSEP
+
+/* Define to 1 if you have the `strtol' function. */
+#undef HAVE_STRTOL
+
+/* Define if <math.h> defines struct exception. */
+#undef HAVE_STRUCT_EXCEPTION
+
+/* Define to 1 if you have the `symlink' function. */
+#undef HAVE_SYMLINK
+
+/* Define to 1 if you have the <sys/bitypes.h> header file. */
+#undef HAVE_SYS_BITYPES_H
+
+/* Define to 1 if you have the <sys/dir.h> header file. */
+#undef HAVE_SYS_DIR_H
+
+/* Define if you have sys_errlist in <errno.h>, <stdio.h>, or <stdlib.h>. */
+#undef HAVE_SYS_ERRLIST
+
+/* Define to 1 if you have the <sys/inttypes.h> header file. */
+#undef HAVE_SYS_INTTYPES_H
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#undef HAVE_SYS_MMAN_H
+
+/* Define if you have sys_nerr in <errno.h>, <stdio.h>, or <stdio.h>. */
+#undef HAVE_SYS_NERR
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#undef HAVE_SYS_PARAM_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the `towlower' function. */
+#undef HAVE_TOWLOWER
+
+/* uchardet library availability */
+#undef HAVE_UCHARDET
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if the system has the type 'unsigned long long int'. */
+#undef HAVE_UNSIGNED_LONG_LONG_INT
+
+/* Define to 1 if you have the `vasnprintf' function. */
+#undef HAVE_VASNPRINTF
+
+/* Define to 1 if you have the `vsnprintf' function. */
+#undef HAVE_VSNPRINTF
+
+/* Define to 1 if you have the <wchar.h> header file. */
+#undef HAVE_WCHAR_H
+
+/* Define if you have the 'wchar_t' type. */
+#undef HAVE_WCHAR_T
+
+/* Define to 1 if you have the `wcrtomb' function. */
+#undef HAVE_WCRTOMB
+
+/* Define to 1 if you have the `wcslen' function. */
+#undef HAVE_WCSLEN
+
+/* Define to 1 if you have the `wcsnlen' function. */
+#undef HAVE_WCSNLEN
+
+/* Define to 1 if you have the <wctype.h> header file. */
+#undef HAVE_WCTYPE_H
+
+/* Define to 1 if you have the `wcwidth' function. */
+#undef HAVE_WCWIDTH
+
+/* Define if you have the 'wint_t' type. */
+#undef HAVE_WINT_T
+
+/* Define to 1 if O_NOATIME works. */
+#undef HAVE_WORKING_O_NOATIME
+
+/* Define to 1 if O_NOFOLLOW works. */
+#undef HAVE_WORKING_O_NOFOLLOW
+
+/* Define to 1 if the system has the type `_Bool'. */
+#undef HAVE__BOOL
+
+/* Define to 1 if you have the `__fseterr' function. */
+#undef HAVE___FSETERR
+
+/* Define to 1 if ctype.h defines __header_inline. */
+#undef HAVE___HEADER_INLINE
+
+/* Please see the Gnulib manual for how to use these macros.
+
+ Suppress extern inline with HP-UX cc, as it appears to be broken; see
+ <https://lists.gnu.org/r/bug-texinfo/2013-02/msg00030.html>.
+
+ Suppress extern inline with Sun C in standards-conformance mode, as it
+ mishandles inline functions that call each other. E.g., for 'inline void f
+ (void) { } inline void g (void) { f (); }', c99 incorrectly complains
+ 'reference to static identifier "f" in extern inline function'.
+ This bug was observed with Oracle Developer Studio 12.6
+ (Sun C 5.15 SunOS_sparc 2017/05/30).
+
+ Suppress extern inline (with or without __attribute__ ((__gnu_inline__)))
+ on configurations that mistakenly use 'static inline' to implement
+ functions or macros in standard C headers like <ctype.h>. For example,
+ if isdigit is mistakenly implemented via a static inline function,
+ a program containing an extern inline function that calls isdigit
+ may not work since the C standard prohibits extern inline functions
+ from calling static functions (ISO C 99 section 6.7.4.(3).
+ This bug is known to occur on:
+
+ OS X 10.8 and earlier; see:
+ https://lists.gnu.org/r/bug-gnulib/2012-12/msg00023.html
+
+ DragonFly; see
+ http://muscles.dragonflybsd.org/bulk/clang-master-potential/20141111_102002/logs/ah-tty-0.3.12.log
+
+ FreeBSD; see:
+ https://lists.gnu.org/r/bug-gnulib/2014-07/msg00104.html
+
+ OS X 10.9 has a macro __header_inline indicating the bug is fixed for C and
+ for clang but remains for g++; see <https://trac.macports.org/ticket/41033>.
+ Assume DragonFly and FreeBSD will be similar.
+
+ GCC 4.3 and above with -std=c99 or -std=gnu99 implements ISO C99
+ inline semantics, unless -fgnu89-inline is used. It defines a macro
+ __GNUC_STDC_INLINE__ to indicate this situation or a macro
+ __GNUC_GNU_INLINE__ to indicate the opposite situation.
+ GCC 4.2 with -std=c99 or -std=gnu99 implements the GNU C inline
+ semantics but warns, unless -fgnu89-inline is used:
+ warning: C99 inline functions are not supported; using GNU89
+ warning: to disable this warning use -fgnu89-inline or the gnu_inline function attribute
+ It defines a macro __GNUC_GNU_INLINE__ to indicate this situation.
+ */
+#if (((defined __APPLE__ && defined __MACH__) \
+ || defined __DragonFly__ || defined __FreeBSD__) \
+ && (defined HAVE___HEADER_INLINE \
+ ? (defined __cplusplus && defined __GNUC_STDC_INLINE__ \
+ && ! defined __clang__) \
+ : ((! defined _DONT_USE_CTYPE_INLINE_ \
+ && (defined __GNUC__ || defined __cplusplus)) \
+ || (defined _FORTIFY_SOURCE && 0 < _FORTIFY_SOURCE \
+ && defined __GNUC__ && ! defined __cplusplus))))
+# define _GL_EXTERN_INLINE_STDHEADER_BUG
+#endif
+#if ((__GNUC__ \
+ ? defined __GNUC_STDC_INLINE__ && __GNUC_STDC_INLINE__ \
+ : (199901L <= __STDC_VERSION__ \
+ && !defined __HP_cc \
+ && !defined __PGI \
+ && !(defined __SUNPRO_C && __STDC__))) \
+ && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
+# define _GL_INLINE inline
+# define _GL_EXTERN_INLINE extern inline
+# define _GL_EXTERN_INLINE_IN_USE
+#elif (2 < __GNUC__ + (7 <= __GNUC_MINOR__) && !defined __STRICT_ANSI__ \
+ && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
+# if defined __GNUC_GNU_INLINE__ && __GNUC_GNU_INLINE__
+ /* __gnu_inline__ suppresses a GCC 4.2 diagnostic. */
+# define _GL_INLINE extern inline __attribute__ ((__gnu_inline__))
+# else
+# define _GL_INLINE extern inline
+# endif
+# define _GL_EXTERN_INLINE extern
+# define _GL_EXTERN_INLINE_IN_USE
+#else
+# define _GL_INLINE _GL_UNUSED static
+# define _GL_EXTERN_INLINE _GL_UNUSED static
+#endif
+
+/* In GCC 4.6 (inclusive) to 5.1 (exclusive),
+ suppress bogus "no previous prototype for 'FOO'"
+ and "no previous declaration for 'FOO'" diagnostics,
+ when FOO is an inline function in the header; see
+ <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54113> and
+ <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63877>. */
+#if __GNUC__ == 4 && 6 <= __GNUC_MINOR__
+# if defined __GNUC_STDC_INLINE__ && __GNUC_STDC_INLINE__
+# define _GL_INLINE_HEADER_CONST_PRAGMA
+# else
+# define _GL_INLINE_HEADER_CONST_PRAGMA \
+ _Pragma ("GCC diagnostic ignored \"-Wsuggest-attribute=const\"")
+# endif
+# define _GL_INLINE_HEADER_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wmissing-prototypes\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmissing-declarations\"") \
+ _GL_INLINE_HEADER_CONST_PRAGMA
+# define _GL_INLINE_HEADER_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define _GL_INLINE_HEADER_BEGIN
+# define _GL_INLINE_HEADER_END
+#endif
+
+/* Define as const if the declaration of iconv() needs const. */
+#undef ICONV_CONST
+
+/* Define if the host's encoding is EBCDIC. */
+#undef IS_EBCDIC_HOST
+
+/* Define as the bit index in the word where to find bit 0 of the exponent of
+ 'long double'. */
+#undef LDBL_EXPBIT0_BIT
+
+/* Define as the word index where to find the exponent of 'long double'. */
+#undef LDBL_EXPBIT0_WORD
+
+/* Define as the bit index in the word where to find the sign of 'long
+ double'. */
+#undef LDBL_SIGNBIT_BIT
+
+/* Define as the word index where to find the sign of 'long double'. */
+#undef LDBL_SIGNBIT_WORD
+
+/* Define if localtime() takes a long * not a time_t *. */
+#undef LONG_FOR_TIME_T
+
+/* Define to a substitute value for mmap()'s MAP_ANONYMOUS flag. */
+#undef MAP_ANONYMOUS
+
+/* Use GNU style printf and scanf. */
+#ifndef __USE_MINGW_ANSI_STDIO
+# undef __USE_MINGW_ANSI_STDIO
+#endif
+
+
+/* Define if your C++ doesn't declare gettimeofday(). */
+#undef NEED_DECLARATION_GETTIMEOFDAY
+
+/* Define if your C++ doesn't declare pclose(). */
+#undef NEED_DECLARATION_PCLOSE
+
+/* Define if your C++ doesn't declare popen(). */
+#undef NEED_DECLARATION_POPEN
+
+/* Define if your C++ doesn't declare putenv(). */
+#undef NEED_DECLARATION_PUTENV
+
+/* Define if your C++ doesn't declare rand(). */
+#undef NEED_DECLARATION_RAND
+
+/* Define if your C++ doesn't declare srand(). */
+#undef NEED_DECLARATION_SRAND
+
+/* Define if your C++ doesn't declare strcasecmp(). */
+#undef NEED_DECLARATION_STRCASECMP
+
+/* Define if your C++ doesn't declare strncasecmp(). */
+#undef NEED_DECLARATION_STRNCASECMP
+
+/* Define if your C++ doesn't declare vfprintf(). */
+#undef NEED_DECLARATION_VFPRINTF
+
+/* Define if the vasnprintf implementation needs special code for the 'a' and
+ 'A' directives. */
+#undef NEED_PRINTF_DIRECTIVE_A
+
+/* Define if the vasnprintf implementation needs special code for the 'F'
+ directive. */
+#undef NEED_PRINTF_DIRECTIVE_F
+
+/* Define if the vasnprintf implementation needs special code for the 'ls'
+ directive. */
+#undef NEED_PRINTF_DIRECTIVE_LS
+
+/* Define if the vasnprintf implementation needs special code for 'double'
+ arguments. */
+#undef NEED_PRINTF_DOUBLE
+
+/* Define if the vasnprintf implementation needs special code for surviving
+ out-of-memory conditions. */
+#undef NEED_PRINTF_ENOMEM
+
+/* Define if the vasnprintf implementation needs special code for the ' flag.
+ */
+#undef NEED_PRINTF_FLAG_GROUPING
+
+/* Define if the vasnprintf implementation needs special code for the '-'
+ flag. */
+#undef NEED_PRINTF_FLAG_LEFTADJUST
+
+/* Define if the vasnprintf implementation needs special code for the 0 flag.
+ */
+#undef NEED_PRINTF_FLAG_ZERO
+
+/* Define if the vasnprintf implementation needs special code for infinite
+ 'double' arguments. */
+#undef NEED_PRINTF_INFINITE_DOUBLE
+
+/* Define if the vasnprintf implementation needs special code for infinite
+ 'long double' arguments. */
+#undef NEED_PRINTF_INFINITE_LONG_DOUBLE
+
+/* Define if the vasnprintf implementation needs special code for 'long
+ double' arguments. */
+#undef NEED_PRINTF_LONG_DOUBLE
+
+/* Define if the vasnprintf implementation needs special code for supporting
+ large precisions without arbitrary bounds. */
+#undef NEED_PRINTF_UNBOUNDED_PRECISION
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to l, ll, u, ul, ull, etc., as suitable for constants of type
+ 'ptrdiff_t'. */
+#undef PTRDIFF_T_SUFFIX
+
+/* Define if fprintf is overridden by a POSIX compliant gnulib implementation.
+ */
+#undef REPLACE_FPRINTF_POSIX
+
+/* Define if vasnprintf exists but is overridden by gnulib. */
+#undef REPLACE_VASNPRINTF
+
+/* Define as the return type of signal handlers ('int' or 'void'). */
+#undef RETSIGTYPE
+
+/* Define if srand() returns void not int. */
+#undef RET_TYPE_SRAND_IS_VOID
+
+/* Define to l, ll, u, ul, ull, etc., as suitable for constants of type
+ 'sig_atomic_t'. */
+#undef SIG_ATOMIC_T_SUFFIX
+
+/* Define as the maximum value of type 'size_t', if the system doesn't define
+ it. */
+#ifndef SIZE_MAX
+# undef SIZE_MAX
+#endif
+
+/* Define to l, ll, u, ul, ull, etc., as suitable for constants of type
+ 'size_t'. */
+#undef SIZE_T_SUFFIX
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+#undef STACK_DIRECTION
+
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+ required in a freestanding environment). This macro is provided for
+ backward compatibility; new code need not use it. */
+#undef STDC_HEADERS
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
+/* Enable general extensions on macOS. */
+#ifndef _DARWIN_C_SOURCE
+# undef _DARWIN_C_SOURCE
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# undef _GNU_SOURCE
+#endif
+/* Enable X/Open compliant socket functions that do not require linking
+ with -lxnet on HP-UX 11.11. */
+#ifndef _HPUX_ALT_XOPEN_SOCKET_API
+# undef _HPUX_ALT_XOPEN_SOCKET_API
+#endif
+/* Identify the host operating system as Minix.
+ This macro does not affect the system headers' behavior.
+ A future release of Autoconf may stop defining this macro. */
+#ifndef _MINIX
+# undef _MINIX
+#endif
+/* Enable general extensions on NetBSD.
+ Enable NetBSD compatibility extensions on Minix. */
+#ifndef _NETBSD_SOURCE
+# undef _NETBSD_SOURCE
+#endif
+/* Enable OpenBSD compatibility extensions on NetBSD.
+ Oddly enough, this does nothing on OpenBSD. */
+#ifndef _OPENBSD_SOURCE
+# undef _OPENBSD_SOURCE
+#endif
+/* Define to 1 if needed for POSIX-compatible behavior. */
+#ifndef _POSIX_SOURCE
+# undef _POSIX_SOURCE
+#endif
+/* Define to 2 if needed for POSIX-compatible behavior. */
+#ifndef _POSIX_1_SOURCE
+# undef _POSIX_1_SOURCE
+#endif
+/* Enable POSIX-compatible threading on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# undef _POSIX_PTHREAD_SEMANTICS
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-5:2014. */
+#ifndef __STDC_WANT_IEC_60559_ATTRIBS_EXT__
+# undef __STDC_WANT_IEC_60559_ATTRIBS_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-1:2014. */
+#ifndef __STDC_WANT_IEC_60559_BFP_EXT__
+# undef __STDC_WANT_IEC_60559_BFP_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-2:2015. */
+#ifndef __STDC_WANT_IEC_60559_DFP_EXT__
+# undef __STDC_WANT_IEC_60559_DFP_EXT__
+#endif
+/* Enable extensions specified by C23 Annex F. */
+#ifndef __STDC_WANT_IEC_60559_EXT__
+# undef __STDC_WANT_IEC_60559_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TS 18661-4:2015. */
+#ifndef __STDC_WANT_IEC_60559_FUNCS_EXT__
+# undef __STDC_WANT_IEC_60559_FUNCS_EXT__
+#endif
+/* Enable extensions specified by C23 Annex H and ISO/IEC TS 18661-3:2015. */
+#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__
+# undef __STDC_WANT_IEC_60559_TYPES_EXT__
+#endif
+/* Enable extensions specified by ISO/IEC TR 24731-2:2010. */
+#ifndef __STDC_WANT_LIB_EXT2__
+# undef __STDC_WANT_LIB_EXT2__
+#endif
+/* Enable extensions specified by ISO/IEC 24747:2009. */
+#ifndef __STDC_WANT_MATH_SPEC_FUNCS__
+# undef __STDC_WANT_MATH_SPEC_FUNCS__
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# undef _TANDEM_SOURCE
+#endif
+/* Enable X/Open extensions. Define to 500 only if necessary
+ to make mbstate_t available. */
+#ifndef _XOPEN_SOURCE
+# undef _XOPEN_SOURCE
+#endif
+
+
+/* Version number of package */
+#undef VERSION
+
+/* Define to l, ll, u, ul, ull, etc., as suitable for constants of type
+ 'wchar_t'. */
+#undef WCHAR_T_SUFFIX
+
+/* Define if the 0200 bit of the status returned by wait() indicates whether a
+ core image was produced for a process that was terminated by a signal. */
+#undef WCOREFLAG
+
+/* Define to l, ll, u, ul, ull, etc., as suitable for constants of type
+ 'wint_t'. */
+#undef WINT_T_SUFFIX
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+# define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+# undef WORDS_BIGENDIAN
+# endif
+#endif
+
+/* Define to 1 if the X Window System is missing or not being used. */
+#undef X_DISPLAY_MISSING
+
+/* True if the compiler says it groks GNU C version MAJOR.MINOR. */
+#if defined __GNUC__ && defined __GNUC_MINOR__
+# define _GL_GNUC_PREREQ(major, minor) \
+ ((major) < __GNUC__ + ((minor) <= __GNUC_MINOR__))
+#else
+# define _GL_GNUC_PREREQ(major, minor) 0
+#endif
+
+
+/* Define to enable the declarations of ISO C 11 types and functions. */
+#undef _ISOC11_SOURCE
+
+/* The _Noreturn keyword of C11. */
+#ifndef _Noreturn
+# if (defined __cplusplus \
+ && ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \
+ || (defined _MSC_VER && 1900 <= _MSC_VER)) \
+ && 0)
+ /* [[noreturn]] is not practically usable, because with it the syntax
+ extern _Noreturn void func (...);
+ would not be valid; such a declaration would only be valid with 'extern'
+ and '_Noreturn' swapped, or without the 'extern' keyword. However, some
+ AIX system header files and several gnulib header files use precisely
+ this syntax with 'extern'. */
+# define _Noreturn [[noreturn]]
+# elif (defined __clang__ && __clang_major__ < 16 \
+ && defined _GL_WORK_AROUND_LLVM_BUG_59792)
+ /* Compile with -D_GL_WORK_AROUND_LLVM_BUG_59792 to work around
+ that rare LLVM bug, though you may get many false-alarm warnings. */
+# define _Noreturn
+# elif ((!defined __cplusplus || defined __clang__) \
+ && (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \
+ || (!defined __STRICT_ANSI__ \
+ && (_GL_GNUC_PREREQ (4, 7) \
+ || (defined __apple_build_version__ \
+ ? 6000000 <= __apple_build_version__ \
+ : 3 < __clang_major__ + (5 <= __clang_minor__))))))
+ /* _Noreturn works as-is. */
+# elif _GL_GNUC_PREREQ (2, 8) || defined __clang__ || 0x5110 <= __SUNPRO_C
+# define _Noreturn __attribute__ ((__noreturn__))
+# elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0)
+# define _Noreturn __declspec (noreturn)
+# else
+# define _Noreturn
+# endif
+#endif
+
+
+/* Define if -D_POSIX_SOURCE is necessary. */
+#undef _POSIX_SOURCE
+
+/* Define if you have ISC 3.x or 4.x. */
+#undef _SYSV3
+
+/* For standard stat data types on VMS. */
+#undef _USE_STD_STAT
+
+/* Define to 1 if the system <stdint.h> predates C++11. */
+#undef __STDC_CONSTANT_MACROS
+
+/* Define to 1 if the system <stdint.h> predates C++11. */
+#undef __STDC_LIMIT_MACROS
+
+/* The _GL_ASYNC_SAFE marker should be attached to functions that are
+ signal handlers (for signals other than SIGABRT, SIGPIPE) or can be
+ invoked from such signal handlers. Such functions have some restrictions:
+ * All functions that it calls should be marked _GL_ASYNC_SAFE as well,
+ or should be listed as async-signal-safe in POSIX
+ <https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04>
+ section 2.4.3. Note that malloc(), sprintf(), and fwrite(), in
+ particular, are NOT async-signal-safe.
+ * All memory locations (variables and struct fields) that these functions
+ access must be marked 'volatile'. This holds for both read and write
+ accesses. Otherwise the compiler might optimize away stores to and
+ reads from such locations that occur in the program, depending on its
+ data flow analysis. For example, when the program contains a loop
+ that is intended to inspect a variable set from within a signal handler
+ while (!signal_occurred)
+ ;
+ the compiler is allowed to transform this into an endless loop if the
+ variable 'signal_occurred' is not declared 'volatile'.
+ Additionally, recall that:
+ * A signal handler should not modify errno (except if it is a handler
+ for a fatal signal and ends by raising the same signal again, thus
+ provoking the termination of the process). If it invokes a function
+ that may clobber errno, it needs to save and restore the value of
+ errno. */
+#define _GL_ASYNC_SAFE
+
+
+/* Attributes. */
+#if (defined __has_attribute \
+ && (!defined __clang_minor__ \
+ || (defined __apple_build_version__ \
+ ? 6000000 <= __apple_build_version__ \
+ : 5 <= __clang_major__)))
+# define _GL_HAS_ATTRIBUTE(attr) __has_attribute (__##attr##__)
+#else
+# define _GL_HAS_ATTRIBUTE(attr) _GL_ATTR_##attr
+# define _GL_ATTR_alloc_size _GL_GNUC_PREREQ (4, 3)
+# define _GL_ATTR_always_inline _GL_GNUC_PREREQ (3, 2)
+# define _GL_ATTR_artificial _GL_GNUC_PREREQ (4, 3)
+# define _GL_ATTR_cold _GL_GNUC_PREREQ (4, 3)
+# define _GL_ATTR_const _GL_GNUC_PREREQ (2, 95)
+# define _GL_ATTR_deprecated _GL_GNUC_PREREQ (3, 1)
+# define _GL_ATTR_diagnose_if 0
+# define _GL_ATTR_error _GL_GNUC_PREREQ (4, 3)
+# define _GL_ATTR_externally_visible _GL_GNUC_PREREQ (4, 1)
+# define _GL_ATTR_fallthrough _GL_GNUC_PREREQ (7, 0)
+# define _GL_ATTR_format _GL_GNUC_PREREQ (2, 7)
+# define _GL_ATTR_leaf _GL_GNUC_PREREQ (4, 6)
+# define _GL_ATTR_malloc _GL_GNUC_PREREQ (3, 0)
+# ifdef _ICC
+# define _GL_ATTR_may_alias 0
+# else
+# define _GL_ATTR_may_alias _GL_GNUC_PREREQ (3, 3)
+# endif
+# define _GL_ATTR_noinline _GL_GNUC_PREREQ (3, 1)
+# define _GL_ATTR_nonnull _GL_GNUC_PREREQ (3, 3)
+# define _GL_ATTR_nonstring _GL_GNUC_PREREQ (8, 0)
+# define _GL_ATTR_nothrow _GL_GNUC_PREREQ (3, 3)
+# define _GL_ATTR_packed _GL_GNUC_PREREQ (2, 7)
+# define _GL_ATTR_pure _GL_GNUC_PREREQ (2, 96)
+# define _GL_ATTR_returns_nonnull _GL_GNUC_PREREQ (4, 9)
+# define _GL_ATTR_sentinel _GL_GNUC_PREREQ (4, 0)
+# define _GL_ATTR_unused _GL_GNUC_PREREQ (2, 7)
+# define _GL_ATTR_warn_unused_result _GL_GNUC_PREREQ (3, 4)
+#endif
+
+/* Disable GCC -Wpedantic if using __has_c_attribute and this is not C23+. */
+#if (defined __has_c_attribute && _GL_GNUC_PREREQ (4, 6) \
+ && (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) <= 201710)
+# pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+
+
+/* _GL_ATTRIBUTE_ALLOC_SIZE ((N)) declares that the Nth argument of the function
+ is the size of the returned memory block.
+ _GL_ATTRIBUTE_ALLOC_SIZE ((M, N)) declares that the Mth argument multiplied
+ by the Nth argument of the function is the size of the returned memory block.
+ */
+/* Applies to: function, pointer to function, function types. */
+#ifndef _GL_ATTRIBUTE_ALLOC_SIZE
+# if _GL_HAS_ATTRIBUTE (alloc_size)
+# define _GL_ATTRIBUTE_ALLOC_SIZE(args) __attribute__ ((__alloc_size__ args))
+# else
+# define _GL_ATTRIBUTE_ALLOC_SIZE(args)
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_ALWAYS_INLINE tells that the compiler should always inline the
+ function and report an error if it cannot do so. */
+/* Applies to: function. */
+#ifndef _GL_ATTRIBUTE_ALWAYS_INLINE
+# if _GL_HAS_ATTRIBUTE (always_inline)
+# define _GL_ATTRIBUTE_ALWAYS_INLINE __attribute__ ((__always_inline__))
+# else
+# define _GL_ATTRIBUTE_ALWAYS_INLINE
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_ARTIFICIAL declares that the function is not important to show
+ in stack traces when debugging. The compiler should omit the function from
+ stack traces. */
+/* Applies to: function. */
+#ifndef _GL_ATTRIBUTE_ARTIFICIAL
+# if _GL_HAS_ATTRIBUTE (artificial)
+# define _GL_ATTRIBUTE_ARTIFICIAL __attribute__ ((__artificial__))
+# else
+# define _GL_ATTRIBUTE_ARTIFICIAL
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_COLD declares that the function is rarely executed. */
+/* Applies to: functions. */
+/* Avoid __attribute__ ((cold)) on MinGW; see thread starting at
+ <https://lists.gnu.org/r/emacs-devel/2019-04/msg01152.html>.
+ Also, Oracle Studio 12.6 requires 'cold' not '__cold__'. */
+#ifndef _GL_ATTRIBUTE_COLD
+# if _GL_HAS_ATTRIBUTE (cold) && !defined __MINGW32__
+# ifndef __SUNPRO_C
+# define _GL_ATTRIBUTE_COLD __attribute__ ((__cold__))
+# else
+# define _GL_ATTRIBUTE_COLD __attribute__ ((cold))
+# endif
+# else
+# define _GL_ATTRIBUTE_COLD
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_CONST declares that it is OK for a compiler to omit duplicate
+ calls to the function with the same arguments.
+ This attribute is safe for a function that neither depends on nor affects
+ observable state, and always returns exactly once - e.g., does not loop
+ forever, and does not call longjmp.
+ (This attribute is stricter than _GL_ATTRIBUTE_PURE.) */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_CONST
+# if _GL_HAS_ATTRIBUTE (const)
+# define _GL_ATTRIBUTE_CONST __attribute__ ((__const__))
+# else
+# define _GL_ATTRIBUTE_CONST
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_DEALLOC (F, I) declares that the function returns pointers
+ that can be freed by passing them as the Ith argument to the
+ function F.
+ _GL_ATTRIBUTE_DEALLOC_FREE declares that the function returns pointers that
+ can be freed via 'free'; it can be used only after declaring 'free'. */
+/* Applies to: functions. Cannot be used on inline functions. */
+#ifndef _GL_ATTRIBUTE_DEALLOC
+# if _GL_GNUC_PREREQ (11, 0)
+# define _GL_ATTRIBUTE_DEALLOC(f, i) __attribute__ ((__malloc__ (f, i)))
+# else
+# define _GL_ATTRIBUTE_DEALLOC(f, i)
+# endif
+#endif
+/* If gnulib's <string.h> or <wchar.h> has already defined this macro, continue
+ to use this earlier definition, since <stdlib.h> may not have been included
+ yet. */
+#ifndef _GL_ATTRIBUTE_DEALLOC_FREE
+# if defined __cplusplus && defined __GNUC__ && !defined __clang__
+/* Work around GCC bug <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108231> */
+# define _GL_ATTRIBUTE_DEALLOC_FREE \
+ _GL_ATTRIBUTE_DEALLOC ((void (*) (void *)) free, 1)
+# else
+# define _GL_ATTRIBUTE_DEALLOC_FREE \
+ _GL_ATTRIBUTE_DEALLOC (free, 1)
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_DEPRECATED: Declares that an entity is deprecated.
+ The compiler may warn if the entity is used. */
+/* Applies to:
+ - function, variable,
+ - struct, union, struct/union member,
+ - enumeration, enumeration item,
+ - typedef,
+ in C++ also: namespace, class, template specialization. */
+#ifndef _GL_ATTRIBUTE_DEPRECATED
+# ifdef __has_c_attribute
+# if __has_c_attribute (__deprecated__)
+# define _GL_ATTRIBUTE_DEPRECATED [[__deprecated__]]
+# endif
+# endif
+# if !defined _GL_ATTRIBUTE_DEPRECATED && _GL_HAS_ATTRIBUTE (deprecated)
+# define _GL_ATTRIBUTE_DEPRECATED __attribute__ ((__deprecated__))
+# endif
+# ifndef _GL_ATTRIBUTE_DEPRECATED
+# define _GL_ATTRIBUTE_DEPRECATED
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_ERROR(msg) requests an error if a function is called and
+ the function call is not optimized away.
+ _GL_ATTRIBUTE_WARNING(msg) requests a warning if a function is called and
+ the function call is not optimized away. */
+/* Applies to: functions. */
+#if !(defined _GL_ATTRIBUTE_ERROR && defined _GL_ATTRIBUTE_WARNING)
+# if _GL_HAS_ATTRIBUTE (error)
+# define _GL_ATTRIBUTE_ERROR(msg) __attribute__ ((__error__ (msg)))
+# define _GL_ATTRIBUTE_WARNING(msg) __attribute__ ((__warning__ (msg)))
+# elif _GL_HAS_ATTRIBUTE (diagnose_if)
+# define _GL_ATTRIBUTE_ERROR(msg) __attribute__ ((__diagnose_if__ (1, msg, "error")))
+# define _GL_ATTRIBUTE_WARNING(msg) __attribute__ ((__diagnose_if__ (1, msg, "warning")))
+# else
+# define _GL_ATTRIBUTE_ERROR(msg)
+# define _GL_ATTRIBUTE_WARNING(msg)
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_EXTERNALLY_VISIBLE declares that the entity should remain
+ visible to debuggers etc., even with '-fwhole-program'. */
+/* Applies to: functions, variables. */
+#ifndef _GL_ATTRIBUTE_EXTERNALLY_VISIBLE
+# if _GL_HAS_ATTRIBUTE (externally_visible)
+# define _GL_ATTRIBUTE_EXTERNALLY_VISIBLE __attribute__ ((externally_visible))
+# else
+# define _GL_ATTRIBUTE_EXTERNALLY_VISIBLE
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_FALLTHROUGH declares that it is not a programming mistake if
+ the control flow falls through to the immediately following 'case' or
+ 'default' label. The compiler should not warn in this case. */
+/* Applies to: Empty statement (;), inside a 'switch' statement. */
+/* Always expands to something. */
+#ifndef _GL_ATTRIBUTE_FALLTHROUGH
+# ifdef __has_c_attribute
+# if __has_c_attribute (__fallthrough__)
+# define _GL_ATTRIBUTE_FALLTHROUGH [[__fallthrough__]]
+# endif
+# endif
+# if !defined _GL_ATTRIBUTE_FALLTHROUGH && _GL_HAS_ATTRIBUTE (fallthrough)
+# define _GL_ATTRIBUTE_FALLTHROUGH __attribute__ ((__fallthrough__))
+# endif
+# ifndef _GL_ATTRIBUTE_FALLTHROUGH
+# define _GL_ATTRIBUTE_FALLTHROUGH ((void) 0)
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_FORMAT ((ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK))
+ declares that the STRING-INDEXth function argument is a format string of
+ style ARCHETYPE, which is one of:
+ printf, gnu_printf
+ scanf, gnu_scanf,
+ strftime, gnu_strftime,
+ strfmon,
+ or the same thing prefixed and suffixed with '__'.
+ If FIRST-TO-CHECK is not 0, arguments starting at FIRST-TO_CHECK
+ are suitable for the format string. */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_FORMAT
+# if _GL_HAS_ATTRIBUTE (format)
+# define _GL_ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
+# else
+# define _GL_ATTRIBUTE_FORMAT(spec)
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_LEAF declares that if the function is called from some other
+ compilation unit, it executes code from that unit only by return or by
+ exception handling. This declaration lets the compiler optimize that unit
+ more aggressively. */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_LEAF
+# if _GL_HAS_ATTRIBUTE (leaf)
+# define _GL_ATTRIBUTE_LEAF __attribute__ ((__leaf__))
+# else
+# define _GL_ATTRIBUTE_LEAF
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_MALLOC declares that the function returns a pointer to freshly
+ allocated memory. */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_MALLOC
+# if _GL_HAS_ATTRIBUTE (malloc)
+# define _GL_ATTRIBUTE_MALLOC __attribute__ ((__malloc__))
+# else
+# define _GL_ATTRIBUTE_MALLOC
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_MAY_ALIAS declares that pointers to the type may point to the
+ same storage as pointers to other types. Thus this declaration disables
+ strict aliasing optimization. */
+/* Applies to: types. */
+/* Oracle Studio 12.6 mishandles may_alias despite __has_attribute OK. */
+#ifndef _GL_ATTRIBUTE_MAY_ALIAS
+# if _GL_HAS_ATTRIBUTE (may_alias) && !defined __SUNPRO_C
+# define _GL_ATTRIBUTE_MAY_ALIAS __attribute__ ((__may_alias__))
+# else
+# define _GL_ATTRIBUTE_MAY_ALIAS
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_MAYBE_UNUSED declares that it is not a programming mistake if
+ the entity is not used. The compiler should not warn if the entity is not
+ used. */
+/* Applies to:
+ - function, variable,
+ - struct, union, struct/union member,
+ - enumeration, enumeration item,
+ - typedef,
+ in C++ also: class. */
+/* In C++ and C23, this is spelled [[__maybe_unused__]].
+ GCC's syntax is __attribute__ ((__unused__)).
+ clang supports both syntaxes. Except that with clang ≥ 6, < 10, in C++ mode,
+ __has_c_attribute (__maybe_unused__) yields true but the use of
+ [[__maybe_unused__]] nevertheless produces a warning. */
+#ifndef _GL_ATTRIBUTE_MAYBE_UNUSED
+# if defined __clang__ && defined __cplusplus
+# if __clang_major__ >= 10
+# define _GL_ATTRIBUTE_MAYBE_UNUSED [[__maybe_unused__]]
+# endif
+# elif defined __has_c_attribute
+# if __has_c_attribute (__maybe_unused__)
+# define _GL_ATTRIBUTE_MAYBE_UNUSED [[__maybe_unused__]]
+# endif
+# endif
+# ifndef _GL_ATTRIBUTE_MAYBE_UNUSED
+# define _GL_ATTRIBUTE_MAYBE_UNUSED _GL_ATTRIBUTE_UNUSED
+# endif
+#endif
+/* Alternative spelling of this macro, for convenience and for
+ compatibility with glibc/include/libc-symbols.h. */
+#define _GL_UNUSED _GL_ATTRIBUTE_MAYBE_UNUSED
+/* Earlier spellings of this macro. */
+#define _UNUSED_PARAMETER_ _GL_ATTRIBUTE_MAYBE_UNUSED
+
+/* _GL_ATTRIBUTE_NODISCARD declares that the caller of the function should not
+ discard the return value. The compiler may warn if the caller does not use
+ the return value, unless the caller uses something like ignore_value. */
+/* Applies to: function, enumeration, class. */
+#ifndef _GL_ATTRIBUTE_NODISCARD
+# if defined __clang__ && defined __cplusplus
+ /* With clang up to 15.0.6 (at least), in C++ mode, [[__nodiscard__]] produces
+ a warning.
+ The 1000 below means a yet unknown threshold. When clang++ version X
+ starts supporting [[__nodiscard__]] without warning about it, you can
+ replace the 1000 with X. */
+# if __clang_major__ >= 1000
+# define _GL_ATTRIBUTE_NODISCARD [[__nodiscard__]]
+# endif
+# elif defined __has_c_attribute
+# if __has_c_attribute (__nodiscard__)
+# define _GL_ATTRIBUTE_NODISCARD [[__nodiscard__]]
+# endif
+# endif
+# if !defined _GL_ATTRIBUTE_NODISCARD && _GL_HAS_ATTRIBUTE (warn_unused_result)
+# define _GL_ATTRIBUTE_NODISCARD __attribute__ ((__warn_unused_result__))
+# endif
+# ifndef _GL_ATTRIBUTE_NODISCARD
+# define _GL_ATTRIBUTE_NODISCARD
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_NOINLINE tells that the compiler should not inline the
+ function. */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_NOINLINE
+# if _GL_HAS_ATTRIBUTE (noinline)
+# define _GL_ATTRIBUTE_NOINLINE __attribute__ ((__noinline__))
+# else
+# define _GL_ATTRIBUTE_NOINLINE
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_NONNULL ((N1, N2,...)) declares that the arguments N1, N2,...
+ must not be NULL.
+ _GL_ATTRIBUTE_NONNULL () declares that all pointer arguments must not be
+ null. */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_NONNULL
+# if _GL_HAS_ATTRIBUTE (nonnull)
+# define _GL_ATTRIBUTE_NONNULL(args) __attribute__ ((__nonnull__ args))
+# else
+# define _GL_ATTRIBUTE_NONNULL(args)
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_NONSTRING declares that the contents of a character array is
+ not meant to be NUL-terminated. */
+/* Applies to: struct/union members and variables that are arrays of element
+ type '[[un]signed] char'. */
+#ifndef _GL_ATTRIBUTE_NONSTRING
+# if _GL_HAS_ATTRIBUTE (nonstring)
+# define _GL_ATTRIBUTE_NONSTRING __attribute__ ((__nonstring__))
+# else
+# define _GL_ATTRIBUTE_NONSTRING
+# endif
+#endif
+
+/* There is no _GL_ATTRIBUTE_NORETURN; use _Noreturn instead. */
+
+/* _GL_ATTRIBUTE_NOTHROW declares that the function does not throw exceptions.
+ */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_NOTHROW
+# if _GL_HAS_ATTRIBUTE (nothrow) && !defined __cplusplus
+# define _GL_ATTRIBUTE_NOTHROW __attribute__ ((__nothrow__))
+# else
+# define _GL_ATTRIBUTE_NOTHROW
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_PACKED declares:
+ For struct members: The member has the smallest possible alignment.
+ For struct, union, class: All members have the smallest possible alignment,
+ minimizing the memory required. */
+/* Applies to: struct members, struct, union,
+ in C++ also: class. */
+#ifndef _GL_ATTRIBUTE_PACKED
+# if _GL_HAS_ATTRIBUTE (packed)
+# define _GL_ATTRIBUTE_PACKED __attribute__ ((__packed__))
+# else
+# define _GL_ATTRIBUTE_PACKED
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_PURE declares that It is OK for a compiler to omit duplicate
+ calls to the function with the same arguments if observable state is not
+ changed between calls.
+ This attribute is safe for a function that does not affect
+ observable state, and always returns exactly once.
+ (This attribute is looser than _GL_ATTRIBUTE_CONST.) */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_PURE
+# if _GL_HAS_ATTRIBUTE (pure)
+# define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define _GL_ATTRIBUTE_PURE
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_RETURNS_NONNULL declares that the function's return value is
+ a non-NULL pointer. */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_RETURNS_NONNULL
+# if _GL_HAS_ATTRIBUTE (returns_nonnull)
+# define _GL_ATTRIBUTE_RETURNS_NONNULL __attribute__ ((__returns_nonnull__))
+# else
+# define _GL_ATTRIBUTE_RETURNS_NONNULL
+# endif
+#endif
+
+/* _GL_ATTRIBUTE_SENTINEL(pos) declares that the variadic function expects a
+ trailing NULL argument.
+ _GL_ATTRIBUTE_SENTINEL () - The last argument is NULL (requires C99).
+ _GL_ATTRIBUTE_SENTINEL ((N)) - The (N+1)st argument from the end is NULL. */
+/* Applies to: functions. */
+#ifndef _GL_ATTRIBUTE_SENTINEL
+# if _GL_HAS_ATTRIBUTE (sentinel)
+# define _GL_ATTRIBUTE_SENTINEL(pos) __attribute__ ((__sentinel__ pos))
+# else
+# define _GL_ATTRIBUTE_SENTINEL(pos)
+# endif
+#endif
+
+/* A helper macro. Don't use it directly. */
+#ifndef _GL_ATTRIBUTE_UNUSED
+# if _GL_HAS_ATTRIBUTE (unused)
+# define _GL_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define _GL_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+
+/* _GL_UNUSED_LABEL; declares that it is not a programming mistake if the
+ immediately preceding label is not used. The compiler should not warn
+ if the label is not used. */
+/* Applies to: label (both in C and C++). */
+/* Note that g++ < 4.5 does not support the '__attribute__ ((__unused__)) ;'
+ syntax. But clang does. */
+#ifndef _GL_UNUSED_LABEL
+# if !(defined __cplusplus && !_GL_GNUC_PREREQ (4, 5)) || defined __clang__
+# define _GL_UNUSED_LABEL _GL_ATTRIBUTE_UNUSED
+# else
+# define _GL_UNUSED_LABEL
+# endif
+#endif
+
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef __cplusplus
+#undef inline
+#endif
+
+/* Define to long or long long if <stdint.h> and <inttypes.h> don't define. */
+#undef intmax_t
+
+/* Work around a bug in Apple GCC 4.0.1 build 5465: In C99 mode, it supports
+ the ISO C 99 semantics of 'extern inline' (unlike the GNU C semantics of
+ earlier versions), but does not display it by setting __GNUC_STDC_INLINE__.
+ __APPLE__ && __MACH__ test for Mac OS X.
+ __APPLE_CC__ tests for the Apple compiler and its version.
+ __STDC_VERSION__ tests for the C99 mode. */
+#if defined __APPLE__ && defined __MACH__ && __APPLE_CC__ >= 5465 && !defined __cplusplus && __STDC_VERSION__ >= 199901L && !defined __GNUC_STDC_INLINE__
+# define __GNUC_STDC_INLINE__ 1
+#endif
+
+/* _GL_CMP (n1, n2) performs a three-valued comparison on n1 vs. n2, where
+ n1 and n2 are expressions without side effects, that evaluate to real
+ numbers (excluding NaN).
+ It returns
+ 1 if n1 > n2
+ 0 if n1 == n2
+ -1 if n1 < n2
+ The naïve code (n1 > n2 ? 1 : n1 < n2 ? -1 : 0) produces a conditional
+ jump with nearly all GCC versions up to GCC 10.
+ This variant (n1 < n2 ? -1 : n1 > n2) produces a conditional with many
+ GCC versions up to GCC 9.
+ The better code (n1 > n2) - (n1 < n2) from Hacker's Delight § 2-9
+ avoids conditional jumps in all GCC versions >= 3.4. */
+#define _GL_CMP(n1, n2) (((n1) > (n2)) - ((n1) < (n2)))
+
+
+/* Define to `int' if <sys/types.h> does not define. */
+#undef mode_t
+
+/* Define as a signed integer type capable of holding a process identifier. */
+#undef pid_t
+
+/* Define as the type of the result of subtracting two pointers, if the system
+ doesn't define it. */
+#undef ptrdiff_t
+
+/* Define to the equivalent of the C99 'restrict' keyword, or to
+ nothing if this is not supported. Do not define if restrict is
+ supported only directly. */
+#undef restrict
+/* Work around a bug in older versions of Sun C++, which did not
+ #define __restrict__ or support _Restrict or __restrict__
+ even though the corresponding Sun C compiler ended up with
+ "#define restrict _Restrict" or "#define restrict __restrict__"
+ in the previous line. This workaround can be removed once
+ we assume Oracle Developer Studio 12.5 (2016) or later. */
+#if defined __SUNPRO_CC && !defined __RESTRICT && !defined __restrict__
+# define _Restrict
+# define __restrict__
+#endif
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
+
+/* Define as a signed type of the same size as size_t. */
+#undef ssize_t
+
+/* Define uintmax_t to 'unsigned long' or 'unsigned long long' if <inttypes.h>
+ does not exist. */
+#undef uintmax_t
+
+
+ /* This definition is a duplicate of the one in unitypes.h.
+ It is here so that we can cope with an older version of unitypes.h
+ that does not contain this definition and that is pre-installed among
+ the public header files. */
+ # if defined __restrict \
+ || 2 < __GNUC__ + (95 <= __GNUC_MINOR__) \
+ || __clang_major__ >= 3
+ # define _UC_RESTRICT __restrict
+ # elif 199901L <= __STDC_VERSION__ || defined restrict
+ # define _UC_RESTRICT restrict
+ # else
+ # define _UC_RESTRICT
+ # endif
+
+
+#if (!defined HAVE_C_STATIC_ASSERT && !defined assert \
+ && (!defined __cplusplus \
+ || (__cpp_static_assert < 201411 \
+ && __GNUG__ < 6 && __clang_major__ < 6)))
+ #include <assert.h>
+ #undef/**/assert
+ /* Solaris 11.4 <assert.h> defines static_assert as a macro with 2 arguments.
+ We need it also to be invocable with a single argument. */
+ #if defined __sun && (__STDC_VERSION__ - 0 >= 201112L) && !defined __cplusplus
+ #undef/**/static_assert
+ #define static_assert _Static_assert
+ #endif
+#endif
diff --git a/src/include/cset.h b/src/include/cset.h
new file mode 100644
index 0000000..996e9a8
--- /dev/null
+++ b/src/include/cset.h
@@ -0,0 +1,74 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CC_LIMITS_H
+#include <limits.h>
+#else /* not HAVE_CC_LIMITS_H */
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 255
+#endif
+#endif /* not HAVE_CC_LIMITS_H */
+
+enum cset_builtin { CSET_BUILTIN };
+
+class cset {
+public:
+ cset();
+ cset(cset_builtin);
+ cset(const char *);
+ cset(const unsigned char *);
+ int operator()(unsigned char) const;
+
+ cset &operator|=(const cset &);
+ cset &operator|=(unsigned char);
+
+ friend class cset_init;
+private:
+ char v[UCHAR_MAX+1];
+ void clear();
+};
+
+inline int cset::operator()(unsigned char c) const
+{
+ return v[c];
+}
+
+inline cset &cset::operator|=(unsigned char c)
+{
+ v[c] = 1;
+ return *this;
+}
+
+extern cset csalpha;
+extern cset csupper;
+extern cset cslower;
+extern cset csdigit;
+extern cset csxdigit;
+extern cset csspace;
+extern cset cspunct;
+extern cset csalnum;
+extern cset csprint;
+extern cset csgraph;
+extern cset cscntrl;
+
+static class cset_init {
+ static int initialised;
+public:
+ cset_init();
+} _cset_init;
diff --git a/src/include/curtime.h b/src/include/curtime.h
new file mode 100644
index 0000000..5d3a24a
--- /dev/null
+++ b/src/include/curtime.h
@@ -0,0 +1,27 @@
+/* Copyright (C) 2015-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#ifndef LONG_FOR_TIME_T
+#include <time.h>
+#endif
+
+#ifdef LONG_FOR_TIME_T
+long
+#else
+time_t
+#endif
+current_time();
diff --git a/src/include/device.h b/src/include/device.h
new file mode 100644
index 0000000..6543292
--- /dev/null
+++ b/src/include/device.h
@@ -0,0 +1,25 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// The target device. Once initialized, the device doesn't change during
+// the entire program run. Sample devices are 'ps' (for Postscript), 'html'
+// (for HTML), and 'ascii', 'latin1', 'utf8' for TTY output.
+extern const char *device;
+
+// end of device.h
diff --git a/src/include/driver.h b/src/include/driver.h
new file mode 100644
index 0000000..965ac1a
--- /dev/null
+++ b/src/include/driver.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <errno.h>
+#include <math.h>
+#include "errarg.h"
+#include "error.h"
+#include "font.h"
+#include "printer.h"
+#include "geometry.h"
+
+void do_file(const char *);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/errarg.h b/src/include/errarg.h
new file mode 100644
index 0000000..67a5852
--- /dev/null
+++ b/src/include/errarg.h
@@ -0,0 +1,46 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class errarg {
+ enum { EMPTY, STRING, CHAR, INTEGER, UNSIGNED_INTEGER, DOUBLE } type;
+ union {
+ const char *s;
+ int n;
+ unsigned int u;
+ char c;
+ double d;
+ };
+ public:
+ errarg();
+ errarg(const char *);
+ errarg(char);
+ errarg(unsigned char);
+ errarg(int);
+ errarg(unsigned int);
+ errarg(double);
+ int empty() const;
+ void print() const;
+};
+
+extern errarg empty_errarg;
+
+extern void errprint(const char *,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
diff --git a/src/include/error.h b/src/include/error.h
new file mode 100644
index 0000000..77302f8
--- /dev/null
+++ b/src/include/error.h
@@ -0,0 +1,69 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+void fatal_with_file_and_line(const char *, int, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+void error_with_file_and_line(const char *, int, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+void warning_with_file_and_line(const char *, int, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+void debug_with_file_and_line(const char *, int, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+void fatal(const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+void error(const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+void warning(const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+void debug(const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+
+extern "C" const char *program_name;
+extern int current_lineno;
+extern const char *current_filename;
+extern const char *current_source_filename;
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/font.h b/src/include/font.h
new file mode 100644
index 0000000..68a82fa
--- /dev/null
+++ b/src/include/font.h
@@ -0,0 +1,343 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// A function of this type can be registered to define the semantics of
+// arbitrary commands in a font DESC file.
+typedef void (*FONT_COMMAND_HANDLER)(const char *, // command
+ const char *, // arg
+ const char *, // file
+ int); // lineno
+
+// A glyph is represented by a font-independent 'glyph *' pointer. The
+// functions name_to_glyph and number_to_glyph return such a pointer.
+//
+// There are two types of glyphs:
+//
+// - those with a name, and among these in particular:
+// 'charNNN' denoting a single 'char' in the input character set,
+// 'uXXXX' denoting a Unicode character,
+//
+// - those with a number, referring to the font-dependent glyph with
+// the given number.
+
+// The statically allocated information about a glyph.
+//
+// This is an abstract class; only its subclass 'charinfo' is
+// instantiated. 'charinfo' exists in two versions: one in
+// roff/troff/input.cpp for troff, and one in
+// libs/libgroff/nametoindex.cpp for the preprocessors and the
+// postprocessors.
+struct glyph {
+ int index; // A font-independent integer value.
+ int number; // Glyph number or -1.
+ friend class character_indexer;
+};
+
+#define UNDEFINED_GLYPH ((glyph *) 0)
+
+// The next three functions exist in two versions: one in
+// roff/troff/input.cpp for troff, and one in
+// libs/libgroff/nametoindex.cpp for the preprocessors and the
+// postprocessors.
+extern glyph *name_to_glyph(const char *); // Convert the glyph with
+ // the given name (arg1) to a 'glyph' object. This
+ // has the same semantics as the groff escape sequence
+ // \C'name'. If such a 'glyph' object does not yet
+ // exist, a new one is allocated.
+extern glyph *number_to_glyph(int); // Convert the font-dependent glyph
+ // with the given number (in the font) to a 'glyph'
+ // object. This has the same semantics as the groff
+ // escape sequence \N'number'. If such a 'glyph'
+ // object does not yet exist, a new one is allocated.
+extern const char *glyph_to_name(glyph *); // Convert the given
+ // glyph back to its name. Return null pointer
+ // if the glyph doesn't have a name.
+inline int glyph_to_number(glyph *); // Convert the given glyph back to
+ // its number. Return -1 if it does not designate
+ // a numbered character.
+inline int glyph_to_index(glyph *); // Return the unique index that is
+ // associated with the given glyph. It is >= 0.
+extern int glyph_to_unicode(glyph *); // Convert the given glyph to its
+ // Unicode codepoint. Return -1 if it does not
+ // designate a Unicode character.
+
+inline int glyph_to_number(glyph *g)
+{
+ return g->number;
+}
+
+inline int glyph_to_index(glyph *g)
+{
+ return g->index;
+}
+
+// Types used in non-public members of 'class font'.
+struct font_kern_list;
+struct font_char_metric;
+struct font_widths_cache;
+
+// A 'class font' instance represents the relevant information of a font of
+// the given device. This includes the set of glyphs represented by the
+// font, and metrics for each glyph.
+class font {
+public:
+ enum { // The valid argument values of 'has_ligature'.
+ LIG_ff = 1,
+ LIG_fi = 2,
+ LIG_fl = 4,
+ LIG_ffi = 8,
+ LIG_ffl = 16
+ };
+
+ virtual ~font(); // Destructor.
+ bool contains(glyph *); // This font contains the given glyph.
+ bool is_special(); // This font is searched for glyphs not defined
+ // in the current font. See section 'Special
+ // Fonts' in the groff Texinfo manual. Used by
+ // make_glyph_node().
+ int get_width(glyph *, int); // A rectangle represents the shape of the
+ // given glyph (arg1) at the given point size
+ // (arg2). Return the horizontal dimension of this
+ // rectangle.
+ int get_height(glyph *, int); // A rectangle represents the shape of the
+ // given glyph (arg1) at the given point size
+ // (arg2). Return the distance between the base
+ // line and the top of this rectangle.
+ // This is often also called the 'ascent' of the
+ // glyph. If the top is above the baseline, this
+ // value is positive.
+ int get_depth(glyph *, int); // A rectangle represents the shape of the
+ // given glyph (arg1) at the given point size
+ // (arg2). Return the distance between the base
+ // line and the bottom of this rectangle.
+ // This is often also called the 'descent' of the
+ // glyph. If the bottom is below the baseline,
+ // this value is positive.
+ int get_space_width(int); // Return the normal width of a space at the
+ // given point size.
+ int get_character_type(glyph *); // Return a bit mask describing the
+ // shape of the given glyph. Bit 0 is set if the
+ // character has a descender. Bit 1 is set if the
+ // character has a tall glyph. See groff manual,
+ // description of \w and the 'ct' register.
+ int get_kern(glyph *, glyph *, int); // Return the kerning between the
+ // given glyphs (arg1 and arg2), both at the given
+ // point size (arg3).
+ int get_skew(glyph *, int, int); // A rectangle represents the shape
+ // of the given glyph (arg1) at the given point size
+ // (arg2). For slanted fonts like Times-Italic, the
+ // optical vertical axis is naturally slanted. The
+ // natural slant value (measured in degrees;
+ // positive values mean aslant to the right) is
+ // specified in the font's description file (see
+ // member variable SLANT below). In addition to
+ // this, any font can be artificially slanted. This
+ // artificial slant value (arg3, measured in
+ // degrees; positive values mean a slant to the
+ // right) is specified with the \S escape.
+ //
+ // Return the skew value which is the horizontal
+ // distance between the upper left corner of the
+ // glyph box and the upper left corner of the glyph
+ // box thought to be slanted by the sum of the
+ // natural and artificial slant. It basically means
+ // how much an accent must be shifted horizontally
+ // to put it on the optical axis of the glyph.
+ bool has_ligature(int); // This font has the given ligature type
+ // (one of LIG_ff, LIG_fi, ...).
+ int get_italic_correction(glyph *, int); // If the given glyph (arg1)
+ // at the given point size (arg2) is followed by an
+ // unslanted glyph, some horizontal white space may
+ // need to be inserted in between. See the groff
+ // manual, description of \/. Return the amount
+ // (width) of this white space.
+ int get_left_italic_correction(glyph *, int); // If the given glyph (arg1)
+ // at the given point size (arg2) is preceded by an
+ // unslanted roman glyph, some horizontal white
+ // space may need to be inserted in between. See
+ // the groff manual, description of \,. Return the
+ // amount (width) of this white space.
+ int get_subscript_correction(glyph *, int); // If the given glyph (arg1)
+ // at the given point size (arg2)is followed by a
+ // subscript glyph, the horizontal position may need
+ // to be advanced by some (possibly negative)
+ // amount. See groff manual, description of \w and
+ // the 'ssc' register. Return this amount.
+ void set_zoom(int); // Set the font's zoom factor * 1000. Must be a
+ // non-negative value.
+ int get_zoom(); // Return the font's zoom factor * 1000.
+ int get_code(glyph *); // Return the code point in the physical
+ // font of the given glyph.
+ const char *get_special_device_encoding(glyph *); // Return
+ // special device-dependent information about
+ // the given glyph. Return null pointer if
+ // there is no special information.
+ const char *get_name(); // Return the name of this font.
+ const char *get_internal_name(); // Return the 'internalname'
+ // attribute of this font or null pointer if it
+ // has none.
+ const char *get_image_generator(); // Return the 'image_generator'
+ // attribute of this font or null pointer if it
+ // has none.
+ static bool scan_papersize(const char *, const char **,
+ double *, double *); // Parse the
+ // 'papersize' directive in the DESC file name
+ // given in arg1. Update arg2 with the name
+ // of the paper format and arg3 and arg4 with
+ // its length and width, respectively. Return
+ // whether paper size was successfully set.
+ static font *load_font(const char *, bool = false); // Load the font
+ // description file with the given name (arg1)
+ // and return a pointer to a 'font' object. If
+ // arg2 is true, only the part of the font
+ // description file before the 'charset' and
+ // 'kernpairs' sections is loaded. Return null
+ // pointer in case of failure.
+ static void command_line_font_dir(const char *); // Prepend given
+ // path (arg1) to the list of directories in which
+ // to look up fonts.
+ static FILE *open_file(const char *, char **); // Open
+ // a font file with the given name (arg1),
+ // searching along the current font path. If
+ // arg2 points to a string pointer, set it to
+ // the found file name (this depends on the
+ // device also). Return the opened file. If
+ // not found, arg2 is unchanged, and a null
+ // pointer is returned.
+
+ // Open the DESC file (depending on the device) and initialize some
+ // static variables with info from there.
+ static const char *load_desc();
+ static FONT_COMMAND_HANDLER
+ set_unknown_desc_command_handler(FONT_COMMAND_HANDLER); // Register
+ // a function which defines the semantics of
+ // arbitrary commands in the font DESC file.
+ // Now the variables from the DESC file, shared by all fonts.
+ static int res; // The 'res' attribute given in the DESC file.
+ static int hor; // The 'hor' attribute given in the DESC file.
+ static int vert; // The 'vert' attribute given in the DESC file.
+ static int unitwidth; // The 'unitwidth' attribute given in the DESC file.
+ static int paperwidth; // The 'paperwidth' attribute given in the
+ // DESC file, or derived from the 'papersize'
+ // attribute given in the DESC file.
+ static int paperlength; // The 'paperlength' attribute given in the
+ // DESC file, or derived from the 'papersize'
+ // attribute given in the DESC file.
+ static const char *papersize;
+ static int biggestfont; // The 'biggestfont' attribute given in the
+ // DESC file.
+ static int spare2;
+ static int sizescale; // The 'sizescale' attribute given in the DESC file.
+ static bool has_tcommand; // DESC file has 'tcommand' directive.
+ static bool use_unscaled_charwidths; // DESC file has
+ // 'unscaled_charwidths' directive.
+ static bool pass_filenames; // DESC file has 'pass_filenames'
+ // directive.
+ static bool use_charnames_in_special; // DESC file has
+ // 'use_charnames_in_special' directive.
+ static bool is_unicode; // DESC file has the 'unicode' directive.
+ static const char *image_generator; // The 'image_generator' attribute
+ // given in the DESC file.
+ static const char **font_name_table; // The 'fonts' attribute given
+ // in the DESC file, as a null
+ // pointer-terminated array of strings.
+ static const char **style_table; // The 'styles' attribute given
+ // in the DESC file, as a null
+ // pointer-terminated array of strings.
+ static const char *family; // The 'family' attribute given in the DESC
+ // file.
+ static int *sizes; // The 'sizes' attribute given in the DESC file, as
+ // an array of intervals of the form { lower1,
+ // upper1, ... lowerN, upperN, 0 }.
+
+private:
+ unsigned ligatures; // Bit mask of available ligatures. Used by
+ // has_ligature().
+ font_kern_list **kern_hash_table; // Hash table of kerning pairs.
+ // Used by get_kern().
+ int space_width; // The normal width of a space. Used by
+ // get_space_width().
+ bool special; // See public is_special() above.
+ char *name; // The name of this font. Used by get_name().
+ char *internalname; // The 'internalname' attribute of this font, or
+ // a null pointer. Used by get_internal_name().
+ double slant; // The natural slant angle (in degrees) of this font.
+ int zoom; // The font's magnification, multiplied by 1000.
+ // Used by scale(). A zero value means 'no zoom'.
+ int *ch_index; // Conversion table from font-independent character
+ // indices to indices for this particular font.
+ int nindices;
+ font_char_metric *ch; // Metrics information for every character in this
+ // font (if !is_unicode) or for just some characters
+ // (if is_unicode). The indices of this array are
+ // font-specific, found as values in ch_index[].
+ int ch_used;
+ int ch_size;
+ font_widths_cache *widths_cache; // A cache of scaled character
+ // widths. Used by the get_width() function.
+
+ static FONT_COMMAND_HANDLER unknown_desc_command_handler; // A
+ // function defining the semantics of arbitrary
+ // commands in the DESC file.
+ enum { KERN_HASH_TABLE_SIZE = 503 }; // Size of the hash table of kerning
+ // pairs.
+
+ // These methods add new characters to the ch_index[] and ch[] arrays.
+ void add_entry(glyph *, // glyph
+ const font_char_metric &); // metric
+ void copy_entry(glyph *, // new_glyph
+ glyph *); // old_glyph
+ void alloc_ch_index(int); // index
+ void extend_ch();
+ void compact();
+
+ void add_kern(glyph *, glyph *, int); // Add to the kerning table a
+ // kerning amount (arg3) between two given glyphs
+ // (arg1 and arg2).
+ static int hash_kern(glyph *, glyph *); // Return a hash code for
+ // the pair of glyphs (arg1 and arg2).
+
+ /* Returns w * pointsize / unitwidth, rounded to the nearest integer. */
+ int scale(int w, int pointsize);
+ static bool unit_scale(double *, char); // Convert value in arg1 from
+ // the given unit (arg2; possible values are
+ // 'i', 'c', 'p', and 'P' as documented in the
+ // info file of groff, section 'Measurements')
+ // to inches. Store result in arg1 and return
+ // whether conversion was successful.
+ virtual void handle_unknown_font_command(const char *, // command
+ const char *, // arg
+ const char *, // file
+ int); // lineno
+
+protected:
+ font(const char *); // Initialize a font with the given name.
+
+ // Load the font description file with the name in member variable
+ // `name` into this object. If arg1 is true, only the part of the
+ // font description file before the 'charset' and 'kernpairs' sections
+ // is loaded. Return success/failure status of load.
+ bool load(bool = false);
+};
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/geometry.h b/src/include/geometry.h
new file mode 100644
index 0000000..9f87f1b
--- /dev/null
+++ b/src/include/geometry.h
@@ -0,0 +1,26 @@
+// -*- C++ -*-
+/* Copyright (C) 2001-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley <gaius@glam.ac.uk>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int adjust_arc_center(const int *, double *);
+void check_output_arc_limits(int x, int y,
+ int xv1, int yv1,
+ int xv2, int yv2,
+ double c0, double c1,
+ int *minx, int *maxx,
+ int *miny, int *maxy);
diff --git a/src/include/getopt.h b/src/include/getopt.h
new file mode 100644
index 0000000..3d81e6d
--- /dev/null
+++ b/src/include/getopt.h
@@ -0,0 +1,226 @@
+/* Declarations for getopt.
+ Copyright (C) 1989-2020 Free Software Foundation, Inc.
+
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _GETOPT_H
+
+#ifndef __need_getopt
+# define _GETOPT_H 1
+#endif
+
+/* Standalone applications should #define __GETOPT_PREFIX to an
+ identifier that prefixes the external functions and variables
+ defined in this header. When this happens, include the
+ headers that might declare getopt so that they will not cause
+ confusion if included after this file. Then systematically rename
+ identifiers so that they do not collide with the system functions
+ and variables. Renaming avoids problems with some compilers and
+ linkers. */
+#if defined __GETOPT_PREFIX && !defined __need_getopt
+# include <stdlib.h>
+# include <stdio.h>
+# if HAVE_UNISTD_H
+# include <unistd.h>
+# endif
+# undef __need_getopt
+# undef getopt
+# undef getopt_long
+# undef getopt_long_only
+# undef optarg
+# undef opterr
+# undef optind
+# undef optopt
+# define __GETOPT_CONCAT(x, y) x ## y
+# define __GETOPT_XCONCAT(x, y) __GETOPT_CONCAT (x, y)
+# define __GETOPT_ID(y) __GETOPT_XCONCAT (__GETOPT_PREFIX, y)
+# define getopt __GETOPT_ID (getopt)
+# define getopt_long __GETOPT_ID (getopt_long)
+# define getopt_long_only __GETOPT_ID (getopt_long_only)
+# define optarg __GETOPT_ID (optarg)
+# define opterr __GETOPT_ID (opterr)
+# define optind __GETOPT_ID (optind)
+# define optopt __GETOPT_ID (optopt)
+#endif
+
+/* Standalone applications get correct prototypes for getopt_long and
+ getopt_long_only; they declare "char **argv". libc uses prototypes
+ with "char *const *argv" that are incorrect because getopt_long and
+ getopt_long_only can permute argv; this is required for backward
+ compatibility (e.g., for LSB 2.0.1).
+
+ This used to be '#if defined __GETOPT_PREFIX && !defined __need_getopt',
+ but it caused redefinition warnings if both unistd.h and getopt.h were
+ included, since unistd.h includes getopt.h having previously defined
+ __need_getopt.
+
+ The only place where __getopt_argv_const is used is in definitions
+ of getopt_long and getopt_long_only below, but these are visible
+ only if __need_getopt is not defined, so it is quite safe to rewrite
+ the conditional as follows:
+*/
+#if !defined __need_getopt
+# if defined __GETOPT_PREFIX
+# define __getopt_argv_const /* empty */
+# else
+# define __getopt_argv_const const
+# endif
+#endif
+
+/* If __GNU_LIBRARY__ is not already defined, either we are being used
+ standalone, or this is the first header included in the source file.
+ If we are being used with glibc, we need to include <features.h>, but
+ that does not exist if we are standalone. So: if __GNU_LIBRARY__ is
+ not defined, include <ctype.h>, which will pull in <features.h> for us
+ if it's from glibc. (Why ctype.h? It's guaranteed to exist and it
+ doesn't flood the namespace with stuff the way some other headers do.) */
+#if !defined __GNU_LIBRARY__
+# include <ctype.h>
+#endif
+
+#ifndef __THROW
+# ifndef __GNUC_PREREQ
+# define __GNUC_PREREQ(maj, min) (0)
+# endif
+# if defined __cplusplus && __GNUC_PREREQ (2,8)
+# define __THROW throw ()
+# else
+# define __THROW
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from 'getopt' to the caller.
+ When 'getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when 'ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to 'getopt'.
+
+ On entry to 'getopt', zero means this is the first call; initialize.
+
+ When 'getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, 'optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message 'getopt' prints
+ for unrecognized options. */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int optopt;
+
+#ifndef __need_getopt
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of 'struct option' terminated by an element containing a name which is
+ zero.
+
+ The field 'has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field 'flag' is not NULL, it points to a variable that is set
+ to the value given in the field 'val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an 'int' to
+ a compiled-in constant, such as set a value from 'optarg', set the
+ option's 'flag' field to zero and its 'val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero 'flag' field, 'getopt'
+ returns the contents of the 'val' field. */
+
+struct option
+{
+ const char *name;
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the 'has_arg' field of 'struct option'. */
+
+# define no_argument 0
+# define required_argument 1
+# define optional_argument 2
+#endif /* need getopt */
+
+
+/* Get definitions and prototypes for functions to process the
+ arguments in ARGV (ARGC of them, minus the program name) for
+ options given in OPTS.
+
+ Return the option character from OPTS just read. Return -1 when
+ there are no more options. For unrecognized options, or options
+ missing arguments, 'optopt' is set to the option letter, and '?' is
+ returned.
+
+ The OPTS string is a list of characters which are recognized option
+ letters, optionally followed by colons, specifying that that letter
+ takes an argument, to be placed in 'optarg'.
+
+ If a letter in OPTS is followed by two colons, its argument is
+ optional. This behavior is specific to the GNU 'getopt'.
+
+ The argument '--' causes premature termination of argument
+ scanning, explicitly telling 'getopt' that there are no more
+ options.
+
+ If OPTS begins with '--', then non-option arguments are treated as
+ arguments to the option '\0'. This behavior is specific to the GNU
+ 'getopt'. */
+
+extern int getopt (int ___argc, char *const *___argv, const char *__shortopts)
+ __THROW;
+
+#ifndef __need_getopt
+extern int getopt_long (int ___argc, char *__getopt_argv_const *___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind)
+ __THROW;
+extern int getopt_long_only (int ___argc, char *__getopt_argv_const *___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind)
+ __THROW;
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Make sure we later can get all the definitions and declarations. */
+#undef __need_getopt
+
+#endif /* getopt.h */
diff --git a/src/include/getopt_int.h b/src/include/getopt_int.h
new file mode 100644
index 0000000..fa6be44
--- /dev/null
+++ b/src/include/getopt_int.h
@@ -0,0 +1,130 @@
+/* Internal declarations for getopt.
+ Copyright (C) 1989-2020 Free Software Foundation, Inc.
+
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _GETOPT_INT_H
+#define _GETOPT_INT_H 1
+
+extern int _getopt_internal (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ int __long_only, int __posixly_correct);
+
+
+/* Reentrant versions which can handle parsing multiple argument
+ vectors at the same time. */
+
+/* Data type for reentrant functions. */
+struct _getopt_data
+{
+ /* These have exactly the same meaning as the corresponding global
+ variables, except that they are used for the reentrant
+ versions of getopt. */
+ int optind;
+ int opterr;
+ int optopt;
+ char *optarg;
+
+ /* Internal members. */
+
+ /* True if the internal members have been initialized. */
+ int __initialized;
+
+ /* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+ char *__nextchar;
+
+ /* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using '+' as the first character
+ of the list of option characters, or by calling getopt.
+
+ PERMUTE is the default. We permute the contents of ARGV as we
+ scan, so that eventually all the non-options are at the end.
+ This allows options to be given in any order, even with programs
+ that were not written to expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were
+ written to expect options and other ARGV-elements in any order
+ and that care about the ordering of the two. We describe each
+ non-option ARGV-element as if it were the argument of an option
+ with character code 1. Using '-' as the first character of the
+ list of option characters selects this mode of operation.
+
+ The special argument '--' forces an end of option-scanning regardless
+ of the value of 'ordering'. In the case of RETURN_IN_ORDER, only
+ '--' can cause 'getopt' to return -1 with 'optind' != ARGC. */
+
+ enum
+ {
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+ } __ordering;
+
+ /* If the POSIXLY_CORRECT environment variable is set
+ or getopt was called. */
+ int __posixly_correct;
+
+
+ /* Handle permutation of arguments. */
+
+ /* Describe the part of ARGV that contains non-options that have
+ been skipped. 'first_nonopt' is the index in ARGV of the first
+ of them; 'last_nonopt' is the index after the last of them. */
+
+ int __first_nonopt;
+ int __last_nonopt;
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ int __nonoption_flags_max_len;
+ int __nonoption_flags_len;
+# endif
+};
+
+/* The initializer is necessary to set OPTIND and OPTERR to their
+ default values and to clear the initialization flag. */
+#define _GETOPT_DATA_INITIALIZER { 1, 1 }
+
+extern int _getopt_internal_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ int __long_only, int __posixly_correct,
+ struct _getopt_data *__data);
+
+extern int _getopt_long_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ struct _getopt_data *__data);
+
+extern int _getopt_long_only_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts,
+ int *__longind,
+ struct _getopt_data *__data);
+
+#endif /* getopt_int.h */
diff --git a/src/include/gettext.h b/src/include/gettext.h
new file mode 100644
index 0000000..8a22212
--- /dev/null
+++ b/src/include/gettext.h
@@ -0,0 +1,22 @@
+/* -*- C -*- */
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This is a dummy header file to make getopt compile without gettext
+ support. */
+
+#define gettext(s) s
diff --git a/src/include/html-strings.h b/src/include/html-strings.h
new file mode 100644
index 0000000..eaf551b
--- /dev/null
+++ b/src/include/html-strings.h
@@ -0,0 +1,26 @@
+// -*- C++ -*-
+/* Copyright (C) 2001-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk).
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+ * defines the image tags issued by the pre-processors (tbl, pic, eqn)
+ * and later detected by pre-html.cpp
+ */
+
+#define HTML_IMAGE_INLINE_BEGIN "\\O[HTML-IMAGE-INLINE-BEGIN]"
+#define HTML_IMAGE_INLINE_END "\\O[HTML-IMAGE-INLINE-END]"
diff --git a/src/include/htmlhint.h b/src/include/htmlhint.h
new file mode 100644
index 0000000..c12a8b9
--- /dev/null
+++ b/src/include/htmlhint.h
@@ -0,0 +1,36 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley <gaius@glam.ac.uk>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTMLINDICATE_H
+#define HTMLINDICATE_H
+
+/*
+ * html_begin_suppress - suppresses output for the html device
+ * and resets the min/max registers for -Tps.
+ * Only called for inline images (such as eqn).
+ *
+ */
+extern void html_begin_suppress();
+
+/*
+ * html_end_suppress - end the suppression of output.
+ */
+extern void html_end_suppress();
+
+#endif
diff --git a/src/include/include.am b/src/include/include.am
new file mode 100644
index 0000000..ca32d6d
--- /dev/null
+++ b/src/include/include.am
@@ -0,0 +1,47 @@
+# Automake rules for 'include'
+#
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/gpl-2.0.html>.
+#
+########################################################################
+
+nodist_noinst_HEADERS = defs.h
+CLEANFILES += defs.h
+defs.h: config.status
+ @$(SHELL) $(top_srcdir)/gendef.sh defs.h \
+ "PROG_PREFIX=\"$(g)\"" \
+ "DEVICE=\"$(DEVICE)\"" \
+ "INSTALLPATH=\"$(prefix)\"" \
+ "BINPATH=\"$(bindir)\"" \
+ "FONTPATH=\"$(fontpath)\"" \
+ "MACROPATH=\"$(tmacpath)\"" \
+ "INDEX_SUFFIX=\"$(indexext)\"" \
+ "COMMON_WORDS_FILE=\"$(common_words_file)\"" \
+ "DEFAULT_INDEX_DIR=\"$(indexdir)\"" \
+ "DEFAULT_INDEX_NAME=\"$(indexname)\"" \
+ "DEFAULT_INDEX=\"$(indexdir)/$(indexname)\""
+
+dist-hook: dist_include
+dist_include:
+ chmod u+w $(distdir)/src/include
+ cp -f $(top_srcdir)/src/include/*.h $(distdir)/src/include
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/include/index.h b/src/include/index.h
new file mode 100644
index 0000000..a7d3201
--- /dev/null
+++ b/src/include/index.h
@@ -0,0 +1,41 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define INDEX_MAGIC 0x23021964
+#define INDEX_VERSION 1
+
+struct index_header {
+ int magic;
+ int version;
+ int tags_size;
+ int table_size;
+ int lists_size;
+ int strings_size;
+ int truncate;
+ int shortest;
+ int common;
+};
+
+struct tag {
+ int filename_index;
+ int start;
+ int length;
+};
+
+unsigned hash(const char *s, int len);
diff --git a/src/include/itable.h b/src/include/itable.h
new file mode 100644
index 0000000..c97db55
--- /dev/null
+++ b/src/include/itable.h
@@ -0,0 +1,193 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+// 'class ITABLE(T)' is the type of a hash table mapping an integer (int >= 0)
+// to an object of type T.
+//
+// 'struct IASSOC(T)' is the type of a association (pair) between an integer
+// (int >= 0) and an object of type T.
+//
+// 'class ITABLE_ITERATOR(T)' is the type of an iterator iterating through a
+// 'class ITABLE(T)'.
+//
+// Nowadays one would use templates for this; this code predates the addition
+// of templates to C++.
+#define ITABLE(T) T ## _itable
+#define IASSOC(T) T ## _iassoc
+#define ITABLE_ITERATOR(T) T ## _itable_iterator
+
+// ptable.h declares this too
+#ifndef NEXT_PTABLE_SIZE_DEFINED
+# define NEXT_PTABLE_SIZE_DEFINED
+extern unsigned next_ptable_size(unsigned); // Return the first suitable
+ // hash table size greater than the given
+ // value.
+#endif
+
+// Declare the types 'class ITABLE(T)', 'struct IASSOC(T)', and 'class
+// ITABLE_ITERATOR(T)' for the type 'T'.
+#define declare_itable(T) \
+ \
+struct IASSOC(T) { \
+ int key; \
+ T *val; \
+ IASSOC(T)(); \
+}; \
+ \
+class ITABLE(T); \
+ \
+class ITABLE_ITERATOR(T) { \
+ ITABLE(T) *p; \
+ unsigned i; \
+public: \
+ ITABLE_ITERATOR(T)(ITABLE(T) *); /* Initialize an iterator running \
+ through the given table. */ \
+ int next(int *, T **); /* Fetch the next pair, store the key \
+ and value in arg1 and arg2, \
+ respectively, and return 1. If \
+ there is no more pair in the \
+ table, return 0. */ \
+}; \
+ \
+class ITABLE(T) { \
+ IASSOC(T) *v; \
+ unsigned size; \
+ unsigned used; \
+ enum { \
+ FULL_NUM = 2, \
+ FULL_DEN = 3, \
+ INITIAL_SIZE = 17 \
+ }; \
+public: \
+ ITABLE(T)(); /* Create an empty table. */ \
+ ~ITABLE(T)(); /* Delete a table, including its \
+ values. */ \
+ void define(int, T *); /* Define the value (arg2) for a key \
+ (arg1). */ \
+ T *lookup(int); /* Return a pointer to the value of \
+ the given key, if found in the \
+ table, or NULL otherwise. */ \
+ friend class ITABLE_ITERATOR(T); \
+};
+
+
+// Values must be allocated by the caller (always using new[], not new)
+// and are freed by ITABLE.
+
+// Define the implementations of the members of the types 'class ITABLE(T)',
+// 'struct IASSOC(T)', 'class ITABLE_ITERATOR(T)' for the type 'T'.
+#define implement_itable(T) \
+ \
+IASSOC(T)::IASSOC(T)() \
+: key(-1), val(0) \
+{ \
+} \
+ \
+ITABLE(T)::ITABLE(T)() \
+{ \
+ v = new IASSOC(T)[size = INITIAL_SIZE]; \
+ used = 0; \
+} \
+ \
+ITABLE(T)::~ITABLE(T)() \
+{ \
+ for (unsigned i = 0; i < size; i++) \
+ delete[] v[i].val; \
+ delete[] v; \
+} \
+ \
+void ITABLE(T)::define(int key, T *val) \
+{ \
+ assert(key >= 0); \
+ unsigned int h = (unsigned int)(key); \
+ unsigned n; \
+ for (n = unsigned(h % size); \
+ v[n].key >= 0; \
+ n = (n == 0 ? size - 1 : n - 1)) \
+ if (v[n].key == key) { \
+ delete[] v[n].val; \
+ v[n].val = val; \
+ return; \
+ } \
+ if (val == 0) \
+ return; \
+ if (used*FULL_DEN >= size*FULL_NUM) { \
+ IASSOC(T) *oldv = v; \
+ unsigned old_size = size; \
+ size = next_ptable_size(size); \
+ v = new IASSOC(T)[size]; \
+ for (unsigned i = 0; i < old_size; i++) \
+ if (oldv[i].key >= 0) { \
+ if (oldv[i].val != 0) { \
+ unsigned j; \
+ for (j = (unsigned int)(oldv[i].key) % size; \
+ v[j].key >= 0; \
+ j = (j == 0 ? size - 1 : j - 1)) \
+ ; \
+ v[j].key = oldv[i].key; \
+ v[j].val = oldv[i].val; \
+ } \
+ } \
+ for (n = unsigned(h % size); \
+ v[n].key >= 0; \
+ n = (n == 0 ? size - 1 : n - 1)) \
+ ; \
+ delete[] oldv; \
+ } \
+ v[n].key = key; \
+ v[n].val = val; \
+ used++; \
+} \
+ \
+T *ITABLE(T)::lookup(int key) \
+{ \
+ assert(key >= 0); \
+ for (unsigned n = (unsigned int)key % size; \
+ v[n].key >= 0; \
+ n = (n == 0 ? size - 1 : n - 1)) \
+ if (v[n].key == key) \
+ return v[n].val; \
+ return 0; \
+} \
+ \
+ITABLE_ITERATOR(T)::ITABLE_ITERATOR(T)(ITABLE(T) *t) \
+: p(t), i(0) \
+{ \
+} \
+ \
+int ITABLE_ITERATOR(T)::next(int *keyp, T **valp) \
+{ \
+ unsigned size = p->size; \
+ IASSOC(T) *v = p->v; \
+ for (; i < size; i++) \
+ if (v[i].key >= 0) { \
+ *keyp = v[i].key; \
+ *valp = v[i].val; \
+ i++; \
+ return 1; \
+ } \
+ return 0; \
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/lf.h b/src/include/lf.h
new file mode 100644
index 0000000..dc85a84
--- /dev/null
+++ b/src/include/lf.h
@@ -0,0 +1,21 @@
+// -*- C++ -*-
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int interpret_lf_args(const char *p);
+void normalize_for_lf (string &fn);
diff --git a/src/include/lib.h b/src/include/lib.h
new file mode 100644
index 0000000..6b1d854
--- /dev/null
+++ b/src/include/lib.h
@@ -0,0 +1,161 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef GROFF_LIB_H
+#define GROFF_LIB_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if defined(__INTERIX) && !defined(_ALL_SOURCE)
+#define _ALL_SOURCE
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifndef HAVE_STRERROR
+ char *strerror(int);
+#endif
+ const char *i_to_a(int);
+ const char *ui_to_a(unsigned int);
+ const char *if_to_a(int, int);
+#ifdef __cplusplus
+}
+#endif
+
+#define __GETOPT_PREFIX groff_
+#include <getopt.h>
+
+#ifdef HAVE_SETLOCALE
+#include <locale.h>
+#define getlocale(category) setlocale(category, NULL)
+#else /* !HAVE_SETLOCALE */
+#define LC_ALL 0
+#define LC_CTYPE 0
+#define setlocale(category, locale) (void)(category, locale)
+#define getlocale(category) ((void)(category), (char *)"C")
+#endif /* !HAVE_SETLOCALE */
+
+#include <stdbool.h>
+
+char *strsave(const char *s);
+bool is_prime(unsigned);
+double groff_hypot(double, double);
+
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* LynxOS 4.0.0 doesn't declare vfprintf() */
+#ifdef NEED_DECLARATION_VFPRINTF
+ int vfprintf(FILE *, const char *, va_list);
+#endif
+
+#ifndef HAVE_MKSTEMP
+/* since mkstemp() is defined as a real C++ function if taken from
+ groff's mkstemp.cpp we need a declaration */
+int mkstemp(char *tmpl);
+#endif /* HAVE_MKSTEMP */
+
+int mksdir(char *tmpl);
+
+#ifdef __cplusplus
+ FILE *xtmpfile(char **namep = 0,
+ const char *postfix_long = 0,
+ const char *postfix_short = 0,
+ int do_unlink = 1);
+ char *xtmptemplate(const char *postfix_long,
+ const char *postfix_short);
+#endif
+
+#ifdef NEED_DECLARATION_POPEN
+ FILE *popen(const char *, const char *);
+#endif /* NEED_DECLARATION_POPEN */
+
+#ifdef NEED_DECLARATION_PCLOSE
+ int pclose (FILE *);
+#endif /* NEED_DECLARATION_PCLOSE */
+
+ size_t file_name_max(const char *fname);
+ size_t path_name_max(void);
+
+ extern char invalid_char_table[];
+
+ inline bool is_invalid_input_char(int c)
+ {
+ return (c >= 0 && invalid_char_table[c]);
+ }
+
+#ifdef HAVE_STRCASECMP
+#ifdef NEED_DECLARATION_STRCASECMP
+// Ultrix4.3's string.h fails to declare this.
+ int strcasecmp(const char *, const char *); }
+#endif /* NEED_DECLARATION_STRCASECMP */
+#else /* !HAVE_STRCASECMP */
+ int strcasecmp(const char *, const char *);
+#endif /* HAVE_STRCASECMP */
+
+#if !defined(_AIX) && !defined(sinix) && !defined(__sinix__)
+#ifdef HAVE_STRNCASECMP
+#ifdef NEED_DECLARATION_STRNCASECMP
+// SunOS's string.h fails to declare this.
+ int strncasecmp(const char *, const char *, int);
+#endif /* NEED_DECLARATION_STRNCASECMP */
+#else /* !HAVE_STRNCASECMP */
+ int strncasecmp(const char *, const char *, size_t);
+#endif /* HAVE_STRNCASECMP */
+#endif /* !_AIX && !sinix && !__sinix__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef HAVE_CC_LIMITS_H
+#include <limits.h>
+#else /* !HAVE_CC_LIMITS_H */
+#define INT_MAX 2147483647
+#endif /* !HAVE_CC_LIMITS_H */
+
+/* Maximum number of digits in decimal representations of `int` types
+ not including a leading minus sign. */
+#define INT_DIGITS 19 /* enough for 64 bit integer */
+#define UINT_DIGITS 20
+
+#ifdef PI
+#undef PI
+#endif
+
+static const double PI = 3.14159265358979323846;
+
+#endif /* GROFF_LIB_H */
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/localcharset.h b/src/include/localcharset.h
new file mode 100644
index 0000000..bbf694e
--- /dev/null
+++ b/src/include/localcharset.h
@@ -0,0 +1,40 @@
+/* Determine a canonical name for the current locale's character encoding.
+ Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ This file is part of the GNU CHARSET Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _LOCALCHARSET_H
+#define _LOCALCHARSET_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Determine the current locale's character encoding, and canonicalize it
+ into one of the canonical names listed in config.charset.
+ The result must not be freed; it is statically allocated.
+ If the canonical name cannot be determined, the result is a non-canonical
+ name. */
+extern const char * locale_charset (void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _LOCALCHARSET_H */
diff --git a/src/include/macropath.h b/src/include/macropath.h
new file mode 100644
index 0000000..26c1baf
--- /dev/null
+++ b/src/include/macropath.h
@@ -0,0 +1,22 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+extern search_path macro_path;
+extern search_path safer_macro_path;
+extern search_path config_macro_path;
diff --git a/src/include/nonposix.h b/src/include/nonposix.h
new file mode 100644
index 0000000..6a61009
--- /dev/null
+++ b/src/include/nonposix.h
@@ -0,0 +1,230 @@
+/* -*- C -*- */
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Eli Zaretskii (eliz@is.elta.co.il)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This header file compartmentalize all idiosyncrasies of non-Posix
+ systems, such as MS-DOS, MS-Windows, etc. It should be loaded after
+ the system headers like stdio.h to protect against warnings and error
+ messages w.r.t. redefining macros. */
+
+#if defined _MSC_VER
+# ifndef _WIN32
+# define _WIN32
+# endif
+#endif
+
+#if defined(__MSDOS__) || defined(__EMX__) \
+ || (defined(_WIN32) && !defined(_UWIN) && !defined(__CYGWIN__))
+
+/* Binary I/O nuisances. */
+# include <fcntl.h>
+# include <io.h>
+# ifdef HAVE_UNISTD_H
+# include <unistd.h>
+# endif
+# ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+# endif
+# ifdef HAVE_DIRECT_H
+# include <direct.h>
+# endif
+# ifdef HAVE_PROCESS_H
+# include <process.h>
+# endif
+# if defined(_MSC_VER) || defined(__MINGW32__)
+# define POPEN_RT "rt"
+# define POPEN_WT "wt"
+# define popen(c,m) _popen(c,m)
+# define pclose(p) _pclose(p)
+# define pipe(pfd) _pipe((pfd),0,_O_BINARY|_O_NOINHERIT)
+# define mkdir(p,m) _mkdir(p)
+# define setmode(f,m) _setmode(f,m)
+# define WAIT(s,p,m) _cwait(s,p,m)
+# define creat(p,m) _creat(p,m)
+# define read(f,b,s) _read(f,b,s)
+# define write(f,b,s) _write(f,b,s)
+# define dup(f) _dup(f)
+# define dup2(f1,f2) _dup2(f1,f2)
+# define close(f) _close(f)
+# define isatty(f) _isatty(f)
+# define access(p,m) _access(p,m)
+# endif
+# define SET_BINARY(f) do {if (!isatty(f)) setmode(f,O_BINARY);} while(0)
+# define FOPEN_RB "rb"
+# define FOPEN_WB "wb"
+# define FOPEN_RWB "wb+"
+# ifndef O_BINARY
+# ifdef _O_BINARY
+# define O_BINARY (_O_BINARY)
+# endif
+# endif
+
+/* The system shell. Groff assumes a Unixy shell, but non-Posix
+ systems don't have standard places where it lives, and might not
+ have it installed to begin with. We want to give them some leeway. */
+# ifdef __EMX__
+# define getcwd(b,s) _getcwd2(b,s)
+# else
+# define BSHELL (system_shell_name())
+# define BSHELL_DASH_C (system_shell_dash_c())
+# define IS_BSHELL(s) (is_system_shell(s))
+# endif
+
+/* The separator for directories in PATH and other environment
+ variables. */
+# define PATH_SEP ";"
+# define PATH_SEP_CHAR ';'
+
+/* Characters that separate directories in a path name. */
+# define DIR_SEPS "/\\:"
+
+/* How to tell if the argument is an absolute file name. */
+# define IS_ABSOLUTE(f) \
+ ((f)[0] == '/' || (f)[0] == '\\' || (f)[0] && (f)[1] == ':')
+
+/* The executable extension. */
+# define EXE_EXT ".exe"
+
+/* Possible executable extensions. */
+# define PATH_EXT ".com;.exe;.bat;.cmd"
+
+/* The system null device. */
+# define NULL_DEV "NUL"
+
+/* The default place to create temporary files. */
+# ifndef P_tmpdir
+# ifdef _P_tmpdir
+# define P_tmpdir _P_tmpdir
+# else
+# define P_tmpdir "c:/temp"
+# endif
+# endif
+
+/* Prototypes. */
+# ifdef __cplusplus
+ extern "C" {
+# endif
+ char * system_shell_name(void);
+ const char * system_shell_dash_c(void);
+ int is_system_shell(const char *);
+# ifdef __cplusplus
+ }
+# endif
+
+#endif
+
+#if defined(_WIN32) && !defined(_UWIN) && !defined(__CYGWIN__)
+/* Win32 implementations which use the Microsoft runtime library
+ * are prone to hanging when a pipe reader quits with unread data in the pipe.
+ * 'gtroff' avoids this, by invoking 'FLUSH_INPUT_PIPE()', defined as ... */
+# define FLUSH_INPUT_PIPE(fd) \
+ do if (!isatty(fd)) \
+ { \
+ char drain[BUFSIZ]; \
+ while (read(fd, drain, sizeof(drain)) > 0) \
+ ; \
+ } while (0)
+
+/* The Microsoft runtime library also has a broken argument passing mechanism,
+ * which may result in improper grouping of arguments passed to a child process
+ * by the 'spawn()' family of functions. In 'groff', only the 'spawnvp()'
+ * function is affected; we work around this defect, by substituting a
+ * wrapper function in place of 'spawnvp()' calls. */
+
+# ifdef __cplusplus
+ extern "C" {
+# endif
+ int spawnvp_wrapper(int, char *, char **);
+# ifdef __cplusplus
+ }
+# endif
+# ifndef SPAWN_FUNCTION_WRAPPERS
+# undef spawnvp
+# define spawnvp spawnvp_wrapper
+# undef _spawnvp
+# define _spawnvp spawnvp
+# endif /* SPAWN_FUNCTION_WRAPPERS */
+
+#else
+/* Other implementations do not suffer from Microsoft runtime bugs,
+ * but 'gtroff' requires a dummy definition for FLUSH_INPUT_PIPE() */
+# define FLUSH_INPUT_PIPE(fd) do {} while(0)
+#endif
+
+/* Defaults, for Posix systems. */
+
+#ifndef SET_BINARY
+# define SET_BINARY(f) do {} while(0)
+#endif
+#ifndef FOPEN_RB
+# define FOPEN_RB "r"
+#endif
+#ifndef FOPEN_WB
+# define FOPEN_WB "w"
+#endif
+#ifndef FOPEN_RWB
+# define FOPEN_RWB "w+"
+#endif
+#ifndef POPEN_RT
+# define POPEN_RT "r"
+#endif
+#ifndef POPEN_WT
+# define POPEN_WT "w"
+#endif
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+#ifndef BSHELL
+# define BSHELL "/bin/sh"
+#endif
+#ifndef BSHELL_DASH_C
+# define BSHELL_DASH_C "-c"
+#endif
+#ifndef IS_BSHELL
+# define IS_BSHELL(s) ((s) && strcmp(s,BSHELL) == 0)
+#endif
+#ifndef PATH_SEP
+# define PATH_SEP ":"
+# define PATH_SEP_CHAR ':'
+#endif
+#ifndef DIR_SEPS
+# define DIR_SEPS "/"
+#endif
+#ifndef IS_ABSOLUTE
+# define IS_ABSOLUTE(f) ((f)[0] == '/')
+#endif
+#ifndef EXE_EXT
+# define EXE_EXT ""
+#endif
+#ifndef PATH_EXT
+# define PATH_EXT ""
+#endif
+#ifndef NULL_DEV
+# define NULL_DEV "/dev/null"
+#endif
+#ifndef GS_NAME
+# define GS_NAME "gs"
+#endif
+#ifndef WAIT
+# define WAIT(s,p,m) wait(s)
+#endif
+#ifndef _WAIT_CHILD
+# define _WAIT_CHILD 0
+#endif
diff --git a/src/include/paper.h b/src/include/paper.h
new file mode 100644
index 0000000..ed789c4
--- /dev/null
+++ b/src/include/paper.h
@@ -0,0 +1,36 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct paper {
+ char *name;
+ double length; // in PS points
+ double width; // in PS points
+};
+
+// global constructor
+static class papersize_init {
+ static int initialised;
+public:
+ papersize_init();
+} _papersize_init;
+
+// A0-A7, B0-B7, C0-C7, D0-D7, 8 American paper sizes, 1 special size */
+#define NUM_PAPERSIZES 4*8 + 8 + 1
+
+extern paper papersizes[];
diff --git a/src/include/posix.h b/src/include/posix.h
new file mode 100644
index 0000000..81c2d22
--- /dev/null
+++ b/src/include/posix.h
@@ -0,0 +1,66 @@
+// -*- C++ -*-
+/* Copyright (C) 1992-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CC_OSFCN_H
+#include <osfcn.h>
+#else
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#endif
+
+#ifndef S_IRUSR
+#define S_IRUSR 0400
+#endif
+
+#ifndef S_IRGRP
+#define S_IRGRP 0040
+#endif
+
+#ifndef S_IROTH
+#define S_IROTH 0004
+#endif
+
+#ifndef S_IWUSR
+#define S_IWUSR 0200
+#endif
+
+#ifndef S_IXUSR
+#define S_IXUSR 0100
+#endif
+
+#ifndef S_ISREG
+#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+
+#ifndef O_RDONLY
+#define O_RDONLY 0
+#endif
+
+#ifndef F_OK
+#define F_OK 0
+#endif
+
+#ifndef HAVE_ISATTY
+#define isatty(n) (1)
+#endif
diff --git a/src/include/printer.h b/src/include/printer.h
new file mode 100644
index 0000000..7b6a7ba
--- /dev/null
+++ b/src/include/printer.h
@@ -0,0 +1,97 @@
+// -*- C++ -*-
+
+// <groff_src_dir>/src/include/printer.h
+
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+
+ Written by James Clark (jjc@jclark.com)
+
+ This file is part of groff.
+
+ groff is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ groff is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Description
+
+ The class 'printer' performs the postprocessing. Each
+ postprocessor only needs to implement a derived class of 'printer' and
+ a suitable function 'make_printer' for the device-dependent tasks.
+ Then the methods of class 'printer' are called automatically by
+ 'do_file()' in 'input.cpp'.
+*/
+
+#include "color.h"
+
+struct environment {
+ int fontno;
+ int size;
+ int hpos;
+ int vpos;
+ int height;
+ int slant;
+ color *col;
+ color *fill;
+};
+
+class font;
+
+struct font_pointer_list {
+ font *p;
+ font_pointer_list *next;
+
+ font_pointer_list(font *, font_pointer_list *);
+};
+
+class printer {
+public:
+ printer();
+ virtual ~printer();
+ void load_font(int, const char *);
+ void set_ascii_char(unsigned char, const environment *, int * = 0);
+ void set_special_char(const char *, const environment *, int * = 0);
+ virtual void set_numbered_char(int, const environment *, int * = 0);
+ glyph *set_char_and_width(const char *, const environment *,
+ int *, font **);
+ font *get_font_from_index(int);
+ virtual void draw(int, int *, int, const environment *);
+ // perform change of line color (text, outline) in the print-out
+ virtual void change_color(const environment * const);
+ // perform change of fill color in the print-out
+ virtual void change_fill_color(const environment * const);
+ virtual void begin_page(int) = 0;
+ virtual void end_page(int) = 0;
+ virtual font *make_font(const char *);
+ virtual void end_of_line();
+ virtual void special(char *, const environment *, char = 'p');
+ virtual void devtag(char *, const environment *, char = 'p');
+
+protected:
+ font_pointer_list *font_list;
+ font **font_table;
+ int nfonts;
+
+ // information about named characters
+ int is_char_named;
+ int is_named_set;
+ char named_command;
+ const char *named_char_s;
+ int named_char_n;
+
+private:
+ font *find_font(const char *);
+ virtual void set_char(glyph *, font *, const environment *, int,
+ const char *) = 0;
+};
+
+printer *make_printer();
diff --git a/src/include/ptable.h b/src/include/ptable.h
new file mode 100644
index 0000000..fa79293
--- /dev/null
+++ b/src/include/ptable.h
@@ -0,0 +1,233 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <string.h>
+
+// 'class PTABLE(T)' is the type of a hash table mapping a string
+// (const char *) to an object of type T.
+//
+// 'struct PASSOC(T)' is the type of a association (pair) between a
+// string (const char *) and an object of type T.
+//
+// 'class PTABLE_ITERATOR(T)' is the type of an iterator iterating through a
+// 'class PTABLE(T)'.
+//
+// Nowadays one would use templates for this; this code predates the addition
+// of templates to C++.
+#define PTABLE(T) T ## _ptable
+#define PASSOC(T) T ## _passoc
+#define PTABLE_ITERATOR(T) T ## _ptable_iterator
+
+// itable.h declares this too
+#ifndef NEXT_PTABLE_SIZE_DEFINED
+# define NEXT_PTABLE_SIZE_DEFINED
+extern unsigned next_ptable_size(unsigned); // Return the first suitable
+ // hash table size greater than the given
+ // value.
+#endif
+
+extern unsigned long hash_string(const char *); // Return a hash code of the
+ // given string. The hash function is
+ // platform dependent. */
+
+// Declare the types 'class PTABLE(T)', 'struct PASSOC(T)', and 'class
+// PTABLE_ITERATOR(T)' for the type 'T'.
+#define declare_ptable(T) \
+ \
+struct PASSOC(T) { \
+ char *key; \
+ T *val; \
+ PASSOC(T)(); \
+}; \
+ \
+class PTABLE(T); \
+ \
+class PTABLE_ITERATOR(T) { \
+ PTABLE(T) *p; \
+ unsigned i; \
+public: \
+ PTABLE_ITERATOR(T)(PTABLE(T) *); /* Initialize an iterator running \
+ through the given table. */ \
+ int next(const char **, T **); /* Fetch the next pair, store the key \
+ and value in arg1 and arg2, \
+ respectively, and return 1. If \
+ there is no more pair in the \
+ table, return 0. */ \
+}; \
+ \
+class PTABLE(T) { \
+ PASSOC(T) *v; \
+ unsigned size; \
+ unsigned used; \
+ enum { \
+ FULL_NUM = 1, \
+ FULL_DEN = 4, \
+ INITIAL_SIZE = 17 \
+ }; \
+public: \
+ PTABLE(T)(); /* Create an empty table. */ \
+ ~PTABLE(T)(); /* Delete a table, including its \
+ values. */ \
+ const char *define(const char *, T *);/* Define the value (arg2) for a key \
+ (arg1). Return the copy in the \
+ table of the key (arg1), or \
+ possibly NULL if the value (arg2) \
+ is NULL. */ \
+ T *lookup(const char *); /* Return a pointer to the value of \
+ the given key, if found in the \
+ table, or NULL otherwise. */ \
+ T *lookupassoc(const char **); /* Return a pointer to the value of \
+ the given key, passed by reference,\
+ and replace the key argument with \
+ the copy found in the table, if \
+ the key is found in the table. \
+ Return NULL otherwise. */ \
+ friend class PTABLE_ITERATOR(T); \
+};
+
+
+// Keys (which are strings) are allocated and freed by PTABLE.
+// Values must be allocated by the caller (always using new[], not new)
+// and are freed by PTABLE.
+
+// Define the implementations of the members of the types 'class PTABLE(T)',
+// 'struct PASSOC(T)', 'class PTABLE_ITERATOR(T)' for the type 'T'.
+#define implement_ptable(T) \
+ \
+PASSOC(T)::PASSOC(T)() \
+: key(0), val(0) \
+{ \
+} \
+ \
+PTABLE(T)::PTABLE(T)() \
+{ \
+ v = new PASSOC(T)[size = INITIAL_SIZE]; \
+ used = 0; \
+} \
+ \
+PTABLE(T)::~PTABLE(T)() \
+{ \
+ for (unsigned i = 0; i < size; i++) { \
+ free(v[i].key); \
+ /* XXX leak, because we don't know whether */ \
+ /* 'free', 'delete', or 'delete[]' should be used */ \
+ /* delete[] v[i].val; */ \
+ } \
+ delete[] v; \
+} \
+ \
+const char *PTABLE(T)::define(const char *key, T *val) \
+{ \
+ assert(key != 0); \
+ unsigned long h = hash_string(key); \
+ unsigned n; \
+ for (n = unsigned(h % size); \
+ v[n].key != 0; \
+ n = (n == 0 ? size - 1 : n - 1)) \
+ if (strcmp(v[n].key, key) == 0) { \
+ /* XXX leak, because we don't know whether */ \
+ /* 'free', 'delete', or 'delete[]' should be used */ \
+ /* delete[] v[n].val; */ \
+ v[n].val = val; \
+ return v[n].key; \
+ } \
+ if (val == 0) \
+ return 0; \
+ if (used*FULL_DEN >= size*FULL_NUM) { \
+ PASSOC(T) *oldv = v; \
+ unsigned old_size = size; \
+ size = next_ptable_size(size); \
+ v = new PASSOC(T)[size]; \
+ for (unsigned i = 0; i < old_size; i++) \
+ if (oldv[i].key != 0) { \
+ if (oldv[i].val == 0) \
+ free(oldv[i].key); \
+ else { \
+ unsigned j; \
+ for (j = unsigned(hash_string(oldv[i].key) % size); \
+ v[j].key != 0; \
+ j = (j == 0 ? size - 1 : j - 1)) \
+ ; \
+ v[j].key = oldv[i].key; \
+ v[j].val = oldv[i].val; \
+ } \
+ } \
+ for (n = unsigned(h % size); \
+ v[n].key != 0; \
+ n = (n == 0 ? size - 1 : n - 1)) \
+ ; \
+ delete[] oldv; \
+ } \
+ char *temp = (char*)malloc(strlen(key)+1); \
+ strcpy(temp, key); \
+ v[n].key = temp; \
+ v[n].val = val; \
+ used++; \
+ return temp; \
+} \
+ \
+T *PTABLE(T)::lookup(const char *key) \
+{ \
+ assert(key != 0); \
+ for (unsigned n = unsigned(hash_string(key) % size); \
+ v[n].key != 0; \
+ n = (n == 0 ? size - 1 : n - 1)) \
+ if (strcmp(v[n].key, key) == 0) \
+ return v[n].val; \
+ return 0; \
+} \
+ \
+T *PTABLE(T)::lookupassoc(const char **keyptr) \
+{ \
+ const char *key = *keyptr; \
+ assert(key != 0); \
+ for (unsigned n = unsigned(hash_string(key) % size); \
+ v[n].key != 0; \
+ n = (n == 0 ? size - 1 : n - 1)) \
+ if (strcmp(v[n].key, key) == 0) { \
+ *keyptr = v[n].key; \
+ return v[n].val; \
+ } \
+ return 0; \
+} \
+ \
+PTABLE_ITERATOR(T)::PTABLE_ITERATOR(T)(PTABLE(T) *t) \
+: p(t), i(0) \
+{ \
+} \
+ \
+int PTABLE_ITERATOR(T)::next(const char **keyp, T **valp) \
+{ \
+ unsigned size = p->size; \
+ PASSOC(T) *v = p->v; \
+ for (; i < size; i++) \
+ if (v[i].key != 0) { \
+ *keyp = v[i].key; \
+ *valp = v[i].val; \
+ i++; \
+ return 1; \
+ } \
+ return 0; \
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/refid.h b/src/include/refid.h
new file mode 100644
index 0000000..9bf3a40
--- /dev/null
+++ b/src/include/refid.h
@@ -0,0 +1,34 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class reference_id {
+ int filename_id;
+ int pos;
+public:
+ reference_id() : filename_id(-1) { }
+ reference_id(int fid, int off) : filename_id(fid), pos(off) { }
+ unsigned hash() const { return (filename_id << 4) + pos; }
+ int is_null() const { return filename_id < 0; }
+ friend inline int operator==(const reference_id &, const reference_id &);
+};
+
+inline int operator==(const reference_id &r1, const reference_id &r2)
+{
+ return r1.filename_id == r2.filename_id && r1.pos == r2.pos;
+}
diff --git a/src/include/relocate.h b/src/include/relocate.h
new file mode 100644
index 0000000..851e2cc
--- /dev/null
+++ b/src/include/relocate.h
@@ -0,0 +1,37 @@
+/* -*- C -*- */
+/* Provide relocation for macro and font files.
+
+ Copyright (C) 2005-2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef __cplusplus
+extern char *curr_prefix;
+extern size_t curr_prefix_len;
+
+void set_current_prefix ();
+char *xdirname (char *s);
+char *searchpath (const char *name, const char *pathp);
+#endif
+
+/* This function has C linkage. */
+extern
+#ifdef __cplusplus
+"C"
+#endif
+char *relocatep (const char *path);
+
+#ifdef __cplusplus
+char *relocate (const char *path);
+#endif
diff --git a/src/include/search.h b/src/include/search.h
new file mode 100644
index 0000000..eb9a039
--- /dev/null
+++ b/src/include/search.h
@@ -0,0 +1,100 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class search_item;
+class search_item_iterator;
+
+class search_list {
+public:
+ search_list();
+ ~search_list();
+ void add_file(const char *fn, int silent = 0);
+ int nfiles() const;
+private:
+ search_item *list;
+ int niterators;
+ int next_fid;
+ friend class search_list_iterator;
+};
+
+class bmpattern;
+
+class linear_searcher {
+ const char *ignore_fields;
+ int truncate_len;
+ bmpattern **keys;
+ int nkeys;
+ const char *search_and_check(const bmpattern *key, const char *buf,
+ const char *bufend, const char **start = 0)
+ const;
+ int check_match(const char *buf, const char *bufend, const char *match,
+ int matchlen, const char **cont, const char **start)
+ const;
+public:
+ linear_searcher(const char *query, int query_len,
+ const char *ign, int trunc);
+ ~linear_searcher();
+ int search(const char *buf, const char *bufend,
+ const char **startp, int *lengthp) const;
+};
+
+class search_list_iterator {
+ search_list *list;
+ search_item *ptr;
+ search_item_iterator *iter;
+ char *query;
+ linear_searcher searcher;
+public:
+ search_list_iterator(search_list *, const char *query);
+ ~search_list_iterator();
+ int next(const char **, int *, reference_id * = 0);
+};
+
+class search_item {
+protected:
+ char *name;
+ int filename_id;
+public:
+ search_item *next;
+ search_item(const char *nm, int fid);
+ virtual search_item_iterator *make_search_item_iterator(const char *) = 0;
+ virtual ~search_item();
+ int is_named(const char *) const;
+ virtual int next_filename_id() const;
+};
+
+class search_item_iterator {
+ char shut_g_plus_plus_up;
+public:
+ virtual ~search_item_iterator();
+ virtual int next(const linear_searcher &, const char **ptr, int *lenp,
+ reference_id *) = 0;
+};
+
+search_item *make_index_search_item(const char *filename, int fid);
+search_item *make_linear_search_item(int fd, const char *filename, int fid);
+
+extern int linear_truncate_len;
+extern const char *linear_ignore_fields;
+extern bool do_verify;
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/searchpath.h b/src/include/searchpath.h
new file mode 100644
index 0000000..2dda4e8
--- /dev/null
+++ b/src/include/searchpath.h
@@ -0,0 +1,30 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class search_path {
+ char *dirs;
+ unsigned init_len;
+public:
+ search_path(const char *envvar, const char *standard,
+ int add_home, int add_current);
+ ~search_path();
+ void command_line_dir(const char *);
+ FILE *open_file(const char *, char **);
+ FILE *open_file_cautious(const char *, char ** = 0, const char * = 0);
+};
diff --git a/src/include/stringclass.h b/src/include/stringclass.h
new file mode 100644
index 0000000..ca0bf85
--- /dev/null
+++ b/src/include/stringclass.h
@@ -0,0 +1,194 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+// Ensure that the first declaration of functions that are later
+// declared as inline declares them as inline.
+
+class string;
+
+inline string operator+(const string &, const string &);
+inline string operator+(const string &, const char *);
+inline string operator+(const char *, const string &);
+inline string operator+(const string &, char);
+inline string operator+(char, const string &);
+inline int operator==(const string &, const string &);
+inline int operator!=(const string &, const string &);
+
+class string {
+public:
+ string();
+ string(const string &);
+ string(const char *);
+ string(const char *, int);
+ string(char);
+
+ ~string();
+
+ string &operator=(const string &);
+ string &operator=(const char *);
+ string &operator=(char);
+
+ string &operator+=(const string &);
+ string &operator+=(const char *);
+ string &operator+=(char);
+ void append(const char *, int);
+
+ int length() const;
+ int empty() const;
+ int operator*() const;
+
+ string substring(int i, int n) const;
+
+ char &operator[](int);
+ char operator[](int) const;
+
+ void set_length(int i);
+ const char *contents() const;
+ int search(char) const;
+ char *extract() const;
+ void remove_spaces();
+ void clear();
+ void move(string &);
+
+ friend string operator+(const string &, const string &);
+ friend string operator+(const string &, const char *);
+ friend string operator+(const char *, const string &);
+ friend string operator+(const string &, char);
+ friend string operator+(char, const string &);
+
+ friend int operator==(const string &, const string &);
+ friend int operator!=(const string &, const string &);
+ friend int operator<=(const string &, const string &);
+ friend int operator<(const string &, const string &);
+ friend int operator>=(const string &, const string &);
+ friend int operator>(const string &, const string &);
+
+private:
+ char *ptr;
+ int len;
+ int sz;
+
+ string(const char *, int, const char *, int); // for use by operator+
+ void grow1();
+};
+
+
+inline char &string::operator[](int i)
+{
+ assert(i >= 0 && i < len);
+ return ptr[i];
+}
+
+inline char string::operator[](int i) const
+{
+ assert(i >= 0 && i < len);
+ return ptr[i];
+}
+
+inline int string::length() const
+{
+ return len;
+}
+
+inline int string::empty() const
+{
+ return len == 0;
+}
+
+inline int string::operator*() const
+{
+ return len;
+}
+
+inline const char *string::contents() const
+{
+ return ptr;
+}
+
+inline string operator+(const string &s1, const string &s2)
+{
+ return string(s1.ptr, s1.len, s2.ptr, s2.len);
+}
+
+inline string operator+(const string &s1, const char *s2)
+{
+#ifdef __GNUG__
+ if (s2 == 0)
+ return s1;
+ else
+ return string(s1.ptr, s1.len, s2, strlen(s2));
+#else
+ return s2 == 0 ? s1 : string(s1.ptr, s1.len, s2, strlen(s2));
+#endif
+}
+
+inline string operator+(const char *s1, const string &s2)
+{
+#ifdef __GNUG__
+ if (s1 == 0)
+ return s2;
+ else
+ return string(s1, strlen(s1), s2.ptr, s2.len);
+#else
+ return s1 == 0 ? s2 : string(s1, strlen(s1), s2.ptr, s2.len);
+#endif
+}
+
+inline string operator+(const string &s, char c)
+{
+ return string(s.ptr, s.len, &c, 1);
+}
+
+inline string operator+(char c, const string &s)
+{
+ return string(&c, 1, s.ptr, s.len);
+}
+
+inline int operator==(const string &s1, const string &s2)
+{
+ return (s1.len == s2.len
+ && (s1.len == 0 || memcmp(s1.ptr, s2.ptr, s1.len) == 0));
+}
+
+inline int operator!=(const string &s1, const string &s2)
+{
+ return (s1.len != s2.len
+ || (s1.len != 0 && memcmp(s1.ptr, s2.ptr, s1.len) != 0));
+}
+
+inline string string::substring(int i, int n) const
+{
+ assert(i >= 0 && i + n <= len);
+ return string(ptr + i, n);
+}
+
+inline string &string::operator+=(char c)
+{
+ if (len >= sz)
+ grow1();
+ ptr[len++] = c;
+ return *this;
+}
+
+void put_string(const string &, FILE *);
+
+string as_string(int);
diff --git a/src/include/symbol.h b/src/include/symbol.h
new file mode 100644
index 0000000..047255d
--- /dev/null
+++ b/src/include/symbol.h
@@ -0,0 +1,88 @@
+/* Copyright (C) 1989-2023 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stdint.h> // uintptr_t
+
+#define DONT_STORE 1
+#define MUST_ALREADY_EXIST 2
+
+class symbol {
+ static const char **table;
+ static int table_used;
+ static int table_size;
+ static char *block;
+ static int block_size;
+ const char *s;
+public:
+ symbol(const char *p, int how = 0);
+ symbol();
+ uintptr_t hash() const;
+ int operator ==(symbol) const;
+ int operator !=(symbol) const;
+ const char *contents() const;
+ int is_null() const;
+ int is_empty() const;
+};
+
+
+extern const symbol NULL_SYMBOL;
+extern const symbol EMPTY_SYMBOL;
+
+inline symbol::symbol() : s(0)
+{
+}
+
+inline int symbol::operator==(symbol p) const
+{
+ return s == p.s;
+}
+
+inline int symbol::operator!=(symbol p) const
+{
+ return s != p.s;
+}
+
+inline uintptr_t symbol::hash() const
+{
+ return reinterpret_cast<uintptr_t>(s);
+}
+
+inline const char *symbol::contents() const
+{
+ return s;
+}
+
+inline int symbol::is_null() const
+{
+ return s == 0;
+}
+
+inline int symbol::is_empty() const
+{
+ return s != 0 && *s == 0;
+}
+
+symbol concat(symbol, symbol);
+
+extern symbol default_symbol;
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/include/unicode.h b/src/include/unicode.h
new file mode 100644
index 0000000..670864b
--- /dev/null
+++ b/src/include/unicode.h
@@ -0,0 +1,63 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg <wl@gnu.org>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// Convert a groff glyph name to a string containing an underscore-separated
+// list of Unicode code points. For example,
+//
+// '-' -> '2010'
+// ',c' -> '00E7'
+// 'fl' -> '0066_006C'
+//
+// Return NULL if there is no equivalent.
+const char *glyph_name_to_unicode(const char *);
+
+// Convert a string containing an underscore-separated list of Unicode code
+// points to a groff glyph name. For example,
+//
+// '2010' -> 'hy'
+// '0066_006C' -> 'fl'
+//
+// Return NULL if there is no equivalent.
+const char *unicode_to_glyph_name(const char *);
+
+// Convert a string containing a precomposed Unicode character to a string
+// containing an underscore-separated list of Unicode code points,
+// representing its canonical decomposition. Also perform compatibility
+// equivalent replacement. For example,
+//
+// '1F3A' -> '0399_0313_0300'
+// 'FA6A' -> '983B'
+//
+// Return NULL if there is no equivalent.
+const char *decompose_unicode(const char *);
+
+// Test whether the given string denotes a Unicode character. It must
+// be of the form 'uNNNN', obeying the following rules.
+//
+// - 'NNNN' must consist of at least 4 hexadecimal digits in upper case.
+// - If there are more than 4 hexadecimal digits, the leading one must not
+// be zero,
+// - 'NNNN' must denote a valid Unicode code point (U+0000..U+10FFFF,
+// excluding surrogate code points.
+//
+// Return a pointer to 'NNNN' (skipping the leading 'u' character) in case
+// of success, NULL otherwise.
+const char *check_unicode_name(const char *);
+
+// end of unicode.h
diff --git a/src/libs/libbib/common.cpp b/src/libs/libbib/common.cpp
new file mode 100644
index 0000000..05accd8
--- /dev/null
+++ b/src/libs/libbib/common.cpp
@@ -0,0 +1,37 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+unsigned hash(const char *s, int len)
+{
+#if 0
+ unsigned h = 0, g;
+ while (*s != '\0') {
+ h <<= 4;
+ h += *s++;
+ if ((g = h & 0xf0000000) != 0) {
+ h ^= g >> 24;
+ h ^= g;
+ }
+ }
+#endif
+ unsigned h = 0;
+ while (--len >= 0)
+ h = *s++ + 65587*h;
+ return h;
+}
+
diff --git a/src/libs/libbib/index.cpp b/src/libs/libbib/index.cpp
new file mode 100644
index 0000000..aebcbbf
--- /dev/null
+++ b/src/libs/libbib/index.cpp
@@ -0,0 +1,688 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "posix.h"
+#include "cset.h"
+#include "cmap.h"
+#include "errarg.h"
+#include "error.h"
+
+#include "refid.h"
+#include "search.h"
+#include "index.h"
+#include "defs.h"
+
+#include "nonposix.h"
+
+// Interface to mmap.
+extern "C" {
+ void *mapread(int fd, int len);
+ int unmap(void *, int len);
+}
+
+#if 0
+const
+#endif
+int minus_one = -1;
+
+bool do_verify = false;
+
+struct word_list;
+
+class index_search_item : public search_item {
+ search_item *out_of_date_files;
+ index_header header;
+ char *buffer;
+ void *map_addr;
+ int map_len;
+ tag *tags;
+ int *table;
+ int *lists;
+ char *pool;
+ char *key_buffer;
+ char *filename_buffer;
+ int filename_buflen;
+ char **common_words_table;
+ int common_words_table_size;
+ const char *ignore_fields;
+ time_t mtime;
+
+ const char *get_invalidity_reason();
+ const int *search1(const char **pp, const char *end);
+ const int *search(const char *ptr, int length, int **temp_listp);
+ const char *munge_filename(const char *);
+ void read_common_words_file();
+ void add_out_of_date_file(int fd, const char *filename, int fid);
+public:
+ index_search_item(const char *, int);
+ ~index_search_item();
+ const char *check_header(index_header *, unsigned);
+ bool load(int fd);
+ search_item_iterator *make_search_item_iterator(const char *);
+ bool is_valid();
+ void check_files();
+ int next_filename_id() const;
+ friend class index_search_item_iterator;
+};
+
+class index_search_item_iterator : public search_item_iterator {
+ index_search_item *indx;
+ search_item_iterator *out_of_date_files_iter;
+ search_item *next_out_of_date_file;
+ const int *found_list;
+ int *temp_list;
+ char *buf;
+ int buflen;
+ linear_searcher searcher;
+ char *query;
+ int get_tag(int tagno, const linear_searcher &, const char **, int *,
+ reference_id *);
+public:
+ index_search_item_iterator(index_search_item *, const char *);
+ ~index_search_item_iterator();
+ int next(const linear_searcher &, const char **, int *, reference_id *);
+};
+
+
+index_search_item::index_search_item(const char *filename, int fid)
+: search_item(filename, fid), out_of_date_files(0), buffer(0), map_addr(0),
+ map_len(0), key_buffer(0), filename_buffer(0), filename_buflen(0),
+ common_words_table(0)
+{
+}
+
+index_search_item::~index_search_item()
+{
+ if (buffer)
+ free(buffer);
+ if (map_addr) {
+ if (unmap(map_addr, map_len) < 0)
+ error("unmap: %1", strerror(errno));
+ }
+ while (out_of_date_files) {
+ search_item *tem = out_of_date_files;
+ out_of_date_files = out_of_date_files->next;
+ delete tem;
+ }
+ delete[] filename_buffer;
+ delete[] key_buffer;
+ if (common_words_table) {
+ for (int i = 0; i < common_words_table_size; i++)
+ delete[] common_words_table[i];
+ delete[] common_words_table;
+ }
+}
+
+class file_closer {
+ int *fdp;
+public:
+ file_closer(int &fd) : fdp(&fd) { }
+ ~file_closer() { close(*fdp); }
+};
+
+// Tell the compiler that a variable is intentionally unused.
+inline void unused(void *) { }
+
+// Validate the data reported in the header so that we don't overread on
+// the heap in the load() member function. Return null pointer if no
+// problems are detected.
+const char *index_search_item::check_header(index_header *file_header,
+ unsigned file_size)
+{
+ if (file_header->tags_size < 0)
+ return "tag list length negative";
+ if (file_header->lists_size < 0)
+ return "reference list length negative";
+ // The table and string pool sizes will not be zero, even in an empty
+ // index.
+ if (file_header->table_size < 1)
+ return "table size nonpositive";
+ if (file_header->strings_size < 1)
+ return "string pool size nonpositive";
+ size_t sz = (file_header->tags_size * sizeof(tag)
+ + file_header->lists_size * sizeof(int)
+ + file_header->table_size * sizeof(int)
+ + file_header->strings_size
+ + sizeof(file_header));
+ if (sz != file_size)
+ return("size mismatch between header and data");
+ unsigned size_remaining = file_size;
+ unsigned chunk_size = file_header->tags_size * sizeof(tag);
+ if (chunk_size > size_remaining)
+ return "claimed tag list length exceeds file size";
+ size_remaining -= chunk_size;
+ chunk_size = file_header->lists_size * sizeof(int);
+ if (chunk_size > size_remaining)
+ return "claimed reference list length exceeds file size";
+ size_remaining -= chunk_size;
+ chunk_size = file_header->table_size * sizeof(int);
+ if (chunk_size > size_remaining)
+ return "claimed table size exceeds file size";
+ size_remaining -= chunk_size;
+ chunk_size = file_header->strings_size;
+ if (chunk_size > size_remaining)
+ return "claimed string pool size exceeds file size";
+ return 0;
+}
+
+bool index_search_item::load(int fd)
+{
+ file_closer fd_closer(fd); // close fd on return
+ unused(&fd_closer);
+ struct stat sb;
+ if (fstat(fd, &sb) < 0) {
+ error("can't fstat index '%1': %2", name, strerror(errno));
+ return false;
+ }
+ if (!S_ISREG(sb.st_mode)) {
+ error("index '%1' is not a regular file", name);
+ return false;
+ }
+ mtime = sb.st_mtime;
+ unsigned size = unsigned(sb.st_size); // widening conversion
+ if (size == 0) {
+ error("index '%1' is an empty file", name);
+ return false;
+ }
+ char *addr;
+ map_addr = mapread(fd, size);
+ if (map_addr) {
+ addr = (char *)map_addr;
+ map_len = size;
+ }
+ else {
+ addr = buffer = (char *)malloc(size);
+ if (buffer == 0) {
+ error("can't allocate memory to process index '%1'", name);
+ return false;
+ }
+ char *ptr = buffer;
+ int bytes_to_read = size;
+ while (bytes_to_read > 0) {
+ int nread = read(fd, ptr, bytes_to_read);
+ if (nread == 0) {
+ error("unexpected end-of-file while reading index '%1'", name);
+ return false;
+ }
+ if (nread < 0) {
+ error("read error on index '%1': %2", name, strerror(errno));
+ return false;
+ }
+ bytes_to_read -= nread;
+ ptr += nread;
+ }
+ }
+ header = *(index_header *)addr;
+ if (header.magic != INDEX_MAGIC) {
+ error("'%1' is not an index file: wrong magic number", name);
+ return false;
+ }
+ if (header.version != INDEX_VERSION) {
+ error("version number in index '%1' is wrong: was %2, should be %3",
+ name, header.version, INDEX_VERSION);
+ return false;
+ }
+ const char *problem = check_header(&header, size);
+ if (problem != 0) {
+ if (do_verify)
+ error("corrupt header in index file '%1': %2", name, problem);
+ else
+ error("corrupt header in index file '%1'", name);
+ return false;
+ }
+ tags = (tag *)(addr + sizeof(header));
+ lists = (int *)(tags + header.tags_size);
+ table = (int *)(lists + header.lists_size);
+ pool = (char *)(table + header.table_size);
+ ignore_fields = strchr(strchr(pool, '\0') + 1, '\0') + 1;
+ key_buffer = new char[header.truncate];
+ read_common_words_file();
+ return true;
+}
+
+const char *index_search_item::get_invalidity_reason()
+{
+ if (tags == 0)
+ return "not loaded";
+ if ((header.lists_size > 0) && (lists[header.lists_size - 1] >= 0))
+ return "last list element not negative";
+ int i;
+ for (i = 0; i < header.table_size; i++) {
+ int li = table[i];
+ if (li >= header.lists_size)
+ return "bad list index";
+ if (li >= 0) {
+ for (int *ptr = lists + li; *ptr >= 0; ptr++) {
+ if (*ptr >= header.tags_size)
+ return "bad tag index";
+ if (*ptr >= ptr[1] && ptr[1] >= 0)
+ return "list not ordered";
+ }
+ }
+ }
+ for (i = 0; i < header.tags_size; i++) {
+ if (tags[i].filename_index >= header.strings_size)
+ return "bad index in tags";
+ if (tags[i].length < 0)
+ return "bad length in tags";
+ if (tags[i].start < 0)
+ return "bad start in tags";
+ }
+ if (pool[header.strings_size - 1] != '\0')
+ return "last character in string pool is not null";
+ return 0;
+}
+
+bool index_search_item::is_valid()
+{
+ const char *reason = get_invalidity_reason();
+ if (!reason)
+ return true;
+ error("'%1' is bad: %2", name, reason);
+ return false;
+}
+
+int index_search_item::next_filename_id() const
+{
+ return filename_id + header.strings_size + 1;
+}
+
+search_item_iterator *index_search_item::make_search_item_iterator(
+ const char *query)
+{
+ return new index_search_item_iterator(this, query);
+}
+
+search_item *make_index_search_item(const char *filename, int fid)
+{
+ char *index_filename = new char[strlen(filename) + sizeof(INDEX_SUFFIX)];
+ strcpy(index_filename, filename);
+ strcat(index_filename, INDEX_SUFFIX);
+ int fd = open(index_filename, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return 0;
+ index_search_item *item = new index_search_item(index_filename, fid);
+ delete[] index_filename;
+ if (!item->load(fd)) {
+ close(fd);
+ delete item;
+ return 0;
+ }
+ else if (do_verify && !item->is_valid()) {
+ delete item;
+ return 0;
+ }
+ else {
+ item->check_files();
+ return item;
+ }
+}
+
+
+index_search_item_iterator::index_search_item_iterator(index_search_item *ind,
+ const char *q)
+: indx(ind), out_of_date_files_iter(0), next_out_of_date_file(0), temp_list(0),
+ buf(0), buflen(0),
+ searcher(q, strlen(q), ind->ignore_fields, ind->header.truncate),
+ query(strsave(q))
+{
+ found_list = indx->search(q, strlen(q), &temp_list);
+ if (!found_list) {
+ found_list = &minus_one;
+ warning("all keys would have been discarded in constructing index '%1'",
+ indx->name);
+ }
+}
+
+index_search_item_iterator::~index_search_item_iterator()
+{
+ delete[] temp_list;
+ delete[] buf;
+ delete[] query;
+ delete out_of_date_files_iter;
+}
+
+int index_search_item_iterator::next(const linear_searcher &,
+ const char **pp, int *lenp,
+ reference_id *ridp)
+{
+ if (found_list) {
+ for (;;) {
+ int tagno = *found_list;
+ if (tagno == -1)
+ break;
+ found_list++;
+ if (get_tag(tagno, searcher, pp, lenp, ridp))
+ return 1;
+ }
+ found_list = 0;
+ next_out_of_date_file = indx->out_of_date_files;
+ }
+ while (next_out_of_date_file) {
+ if (out_of_date_files_iter == 0)
+ out_of_date_files_iter
+ = next_out_of_date_file->make_search_item_iterator(query);
+ if (out_of_date_files_iter->next(searcher, pp, lenp, ridp))
+ return 1;
+ delete out_of_date_files_iter;
+ out_of_date_files_iter = 0;
+ next_out_of_date_file = next_out_of_date_file->next;
+ }
+ return 0;
+}
+
+int index_search_item_iterator::get_tag(int tagno,
+ const linear_searcher &searchr,
+ const char **pp, int *lenp,
+ reference_id *ridp)
+{
+ if (tagno < 0 || tagno >= indx->header.tags_size) {
+ error("bad tag number");
+ return 0;
+ }
+ tag *tp = indx->tags + tagno;
+ const char *filename = indx->munge_filename(indx->pool + tp->filename_index);
+ int fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ error("can't open '%1': %2", filename, strerror(errno));
+ return 0;
+ }
+ struct stat sb;
+ if (fstat(fd, &sb) < 0) {
+ error("can't fstat: %1", strerror(errno));
+ close(fd);
+ return 0;
+ }
+ time_t mtime = sb.st_mtime;
+ if (mtime > indx->mtime) {
+ indx->add_out_of_date_file(fd, filename,
+ indx->filename_id + tp->filename_index);
+ return 0;
+ }
+ int res = 0;
+ FILE *fp = fdopen(fd, FOPEN_RB);
+ if (!fp) {
+ error("fdopen failed");
+ close(fd);
+ return 0;
+ }
+ if (tp->start != 0 && fseek(fp, long(tp->start), 0) < 0)
+ error("can't seek on '%1': %2", filename, strerror(errno));
+ else {
+ int length = tp->length;
+ int err = 0;
+ if (length == 0) {
+ if (fstat(fileno(fp), &sb) < 0) {
+ error("can't stat '%1': %2", filename, strerror(errno));
+ err = 1;
+ }
+ else if (!S_ISREG(sb.st_mode)) {
+ error("'%1' is not a regular file", filename);
+ err = 1;
+ }
+ else
+ length = int(sb.st_size);
+ }
+ if (!err) {
+ if (length + 2 > buflen) {
+ delete[] buf;
+ buflen = length + 2;
+ buf = new char[buflen];
+ }
+ if (fread(buf + 1, 1, length, fp) != (size_t)length)
+ error("fread on '%1' failed: %2", filename, strerror(errno));
+ else {
+ buf[0] = '\n';
+ // Remove the CR characters from CRLF pairs.
+ int sidx = 1, didx = 1;
+ for ( ; sidx < length + 1; sidx++, didx++)
+ {
+ if (buf[sidx] == '\r')
+ {
+ if (buf[++sidx] != '\n')
+ buf[didx++] = '\r';
+ else
+ length--;
+ }
+ if (sidx != didx)
+ buf[didx] = buf[sidx];
+ }
+ buf[length + 1] = '\n';
+ res = searchr.search(buf + 1, buf + 2 + length, pp, lenp);
+ if (res && ridp)
+ *ridp = reference_id(indx->filename_id + tp->filename_index,
+ tp->start);
+ }
+ }
+ }
+ fclose(fp);
+ return res;
+}
+
+const char *index_search_item::munge_filename(const char *filename)
+{
+ if (IS_ABSOLUTE(filename))
+ return filename;
+ const char *cwd = pool;
+ int need_slash = (cwd[0] != 0
+ && strchr(DIR_SEPS, strchr(cwd, '\0')[-1]) == 0);
+ int len = strlen(cwd) + strlen(filename) + need_slash + 1;
+ if (len > filename_buflen) {
+ delete[] filename_buffer;
+ filename_buflen = len;
+ filename_buffer = new char[len];
+ }
+ strcpy(filename_buffer, cwd);
+ if (need_slash)
+ strcat(filename_buffer, "/");
+ strcat(filename_buffer, filename);
+ return filename_buffer;
+}
+
+const int *index_search_item::search1(const char **pp, const char *end)
+{
+ while (*pp < end && !csalnum(**pp))
+ *pp += 1;
+ if (*pp >= end)
+ return 0;
+ const char *start = *pp;
+ while (*pp < end && csalnum(**pp))
+ *pp += 1;
+ int len = *pp - start;
+ if (len < header.shortest)
+ return 0;
+ if (len > header.truncate)
+ len = header.truncate;
+ int is_number = 1;
+ for (int i = 0; i < len; i++)
+ if (csdigit(start[i]))
+ key_buffer[i] = start[i];
+ else {
+ key_buffer[i] = cmlower(start[i]);
+ is_number = 0;
+ }
+ if (is_number && !(len == 4 && start[0] == '1' && start[1] == '9'))
+ return 0;
+ unsigned hc = hash(key_buffer, len);
+ if (common_words_table) {
+ for (int h = hc % common_words_table_size;
+ common_words_table[h];
+ --h) {
+ if (strlen(common_words_table[h]) == (size_t)len
+ && memcmp(common_words_table[h], key_buffer, len) == 0)
+ return 0;
+ if (h == 0)
+ h = common_words_table_size;
+ }
+ }
+ int li = table[int(hc % header.table_size)];
+ return li < 0 ? &minus_one : lists + li;
+}
+
+static void merge(int *result, const int *s1, const int *s2)
+{
+ for (; *s1 >= 0; s1++) {
+ while (*s2 >= 0 && *s2 < *s1)
+ s2++;
+ if (*s2 == *s1)
+ *result++ = *s2;
+ }
+ *result++ = -1;
+}
+
+const int *index_search_item::search(const char *ptr, int length,
+ int **temp_listp)
+{
+ const char *end = ptr + length;
+ if (*temp_listp) {
+ delete[] *temp_listp;
+ *temp_listp = 0;
+ }
+ const int *first_list = 0;
+ while (ptr < end && (first_list = search1(&ptr, end)) == 0)
+ ;
+ if (!first_list)
+ return 0;
+ if (*first_list < 0)
+ return first_list;
+ const int *second_list = 0;
+ while (ptr < end && (second_list = search1(&ptr, end)) == 0)
+ ;
+ if (!second_list)
+ return first_list;
+ if (*second_list < 0)
+ return second_list;
+ const int *p;
+ for (p = first_list; *p >= 0; p++)
+ ;
+ int len = p - first_list;
+ for (p = second_list; *p >= 0; p++)
+ ;
+ if (p - second_list < len)
+ len = p - second_list;
+ int *matches = new int[len + 1];
+ merge(matches, first_list, second_list);
+ while (ptr < end) {
+ const int *list = search1(&ptr, end);
+ if (list != 0) {
+ if (*list < 0) {
+ delete[] matches;
+ return list;
+ }
+ merge(matches, matches, list);
+ if (*matches < 0) {
+ delete[] matches;
+ return &minus_one;
+ }
+ }
+ }
+ *temp_listp = matches;
+ return matches;
+}
+
+void index_search_item::read_common_words_file()
+{
+ if (header.common <= 0)
+ return;
+ const char *common_words_file = munge_filename(strchr(pool, '\0') + 1);
+ errno = 0;
+ FILE *fp = fopen(common_words_file, "r");
+ if (!fp) {
+ error("can't open '%1': %2", common_words_file, strerror(errno));
+ return;
+ }
+ common_words_table_size = 2*header.common + 1;
+ while (!is_prime(common_words_table_size))
+ common_words_table_size += 2;
+ common_words_table = new char *[common_words_table_size];
+ for (int i = 0; i < common_words_table_size; i++)
+ common_words_table[i] = 0;
+ int count = 0;
+ int key_len = 0;
+ for (;;) {
+ int c = getc(fp);
+ while (c != EOF && !csalnum(c))
+ c = getc(fp);
+ if (c == EOF)
+ break;
+ do {
+ if (key_len < header.truncate)
+ key_buffer[key_len++] = cmlower(c);
+ c = getc(fp);
+ } while (c != EOF && csalnum(c));
+ if (key_len >= header.shortest) {
+ int h = hash(key_buffer, key_len) % common_words_table_size;
+ while (common_words_table[h]) {
+ if (h == 0)
+ h = common_words_table_size;
+ --h;
+ }
+ common_words_table[h] = new char[key_len + 1];
+ memcpy(common_words_table[h], key_buffer, key_len);
+ common_words_table[h][key_len] = '\0';
+ }
+ if (++count >= header.common)
+ break;
+ key_len = 0;
+ if (c == EOF)
+ break;
+ }
+ fclose(fp);
+}
+
+void index_search_item::add_out_of_date_file(int fd, const char *filename,
+ int fid)
+{
+ search_item **pp;
+ for (pp = &out_of_date_files; *pp; pp = &(*pp)->next)
+ if ((*pp)->is_named(filename))
+ return;
+ *pp = make_linear_search_item(fd, filename, fid);
+ warning("'%1' modified since index '%2' created", filename, name);
+}
+
+void index_search_item::check_files()
+{
+ const char *pool_end = pool + header.strings_size;
+ for (const char *ptr = strchr(ignore_fields, '\0') + 1;
+ ptr < pool_end;
+ ptr = strchr(ptr, '\0') + 1) {
+ const char *path = munge_filename(ptr);
+ struct stat sb;
+ if (stat(path, &sb) < 0)
+ error("can't stat '%1': %2", path, strerror(errno));
+ else if (sb.st_mtime > mtime) {
+ int fd = open(path, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ error("can't open '%1': %2", path, strerror(errno));
+ else
+ add_out_of_date_file(fd, path, filename_id + (ptr - pool));
+ }
+ }
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libbib/libbib.am b/src/libs/libbib/libbib.am
new file mode 100644
index 0000000..00e1d3b
--- /dev/null
+++ b/src/libs/libbib/libbib.am
@@ -0,0 +1,35 @@
+# Automake rules for 'libbib'
+#
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/gpl-2.0.html>.
+#
+########################################################################
+
+noinst_LIBRARIES += libbib.a
+libbib_a_SOURCES = \
+ src/libs/libbib/common.cpp \
+ src/libs/libbib/index.cpp \
+ src/libs/libbib/linear.cpp \
+ src/libs/libbib/search.cpp \
+ src/libs/libbib/map.c
+src/libs/libbib/index.$(OBJEXT): defs.h
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/libs/libbib/linear.cpp b/src/libs/libbib/linear.cpp
new file mode 100644
index 0000000..22e11d8
--- /dev/null
+++ b/src/libs/libbib/linear.cpp
@@ -0,0 +1,506 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "posix.h"
+#include "errarg.h"
+#include "error.h"
+#include "cset.h"
+#include "cmap.h"
+#include "nonposix.h"
+
+#include "refid.h"
+#include "search.h"
+
+class file_buffer {
+ char *buffer;
+ char *bufend;
+public:
+ file_buffer();
+ ~file_buffer();
+ int load(int fd, const char *filename);
+ const char *get_start() const;
+ const char *get_end() const;
+};
+
+typedef unsigned char uchar;
+
+static uchar map[256];
+static uchar inv_map[256][3];
+
+struct map_init {
+ map_init();
+};
+
+static map_init the_map_init;
+
+map_init::map_init()
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ map[i] = csalnum(i) ? cmlower(i) : '\0';
+ for (i = 0; i < 256; i++) {
+ if (cslower(i)) {
+ inv_map[i][0] = i;
+ inv_map[i][1] = cmupper(i);
+ inv_map[i][2] = '\0';
+ }
+ else if (csdigit(i)) {
+ inv_map[i][0] = i;
+ inv_map[i][1] = 0;
+ }
+ else
+ inv_map[i][0] = '\0';
+ }
+}
+
+
+class bmpattern {
+ char *pat;
+ int len;
+ int delta[256];
+public:
+ bmpattern(const char *pattern, int pattern_length);
+ ~bmpattern();
+ const char *search(const char *p, const char *end) const;
+ int length() const;
+};
+
+bmpattern::bmpattern(const char *pattern, int pattern_length)
+: len(pattern_length)
+{
+ pat = new char[len];
+ int i;
+ for (i = 0; i < len; i++)
+ pat[i] = map[uchar(pattern[i])];
+ for (i = 0; i < 256; i++)
+ delta[i] = len;
+ for (i = 0; i < len; i++)
+ for (const unsigned char *inv = inv_map[uchar(pat[i])]; *inv; inv++)
+ delta[*inv] = len - i - 1;
+}
+
+const char *bmpattern::search(const char *buf, const char *end) const
+{
+ int buflen = end - buf;
+ if (len > buflen)
+ return 0;
+ const char *strend;
+ if (buflen > len*4)
+ strend = end - len*4;
+ else
+ strend = buf;
+ const char *k = buf + len - 1;
+ const int *del = delta;
+ const char *pattern = pat;
+ for (;;) {
+ while (k < strend) {
+ int t = del[uchar(*k)];
+ if (!t)
+ break;
+ k += t;
+ k += del[uchar(*k)];
+ k += del[uchar(*k)];
+ }
+ while (k < end && del[uchar(*k)] != 0)
+ k++;
+ if (k == end)
+ break;
+ int j = len - 1;
+ const char *s = k;
+ for (;;) {
+ if (j == 0)
+ return s;
+ if (map[uchar(*--s)] != uchar(pattern[--j]))
+ break;
+ }
+ k++;
+ }
+ return 0;
+}
+
+bmpattern::~bmpattern()
+{
+ delete[] pat;
+}
+
+inline int bmpattern::length() const
+{
+ return len;
+}
+
+
+static const char *find_end(const char *bufend, const char *p);
+
+const char *linear_searcher::search_and_check(const bmpattern *key,
+ const char *buf, const char *bufend, const char **start) const
+{
+ assert(buf[-1] == '\n');
+ assert(bufend[-1] == '\n');
+ const char *ptr = buf;
+ for (;;) {
+ const char *found = key->search(ptr, bufend);
+ if (!found)
+ break;
+ if (check_match(buf, bufend, found, key->length(), &ptr, start))
+ return found;
+ }
+ return 0;
+}
+
+static const char *skip_field(const char *end, const char *p)
+{
+ for (;;)
+ if (*p++ == '\n') {
+ if (p == end || *p == '%')
+ break;
+ const char *q;
+ for (q = p; *q == ' ' || *q == '\t'; q++)
+ ;
+ if (*q == '\n')
+ break;
+ p = q + 1;
+ }
+ return p;
+}
+
+static const char *find_end(const char *bufend, const char *p)
+{
+ for (;;)
+ if (*p++ == '\n') {
+ if (p == bufend)
+ break;
+ const char *q;
+ for (q = p; *q == ' ' || *q == '\t'; q++)
+ ;
+ if (*q == '\n')
+ break;
+ p = q + 1;
+ }
+ return p;
+}
+
+
+int linear_searcher::check_match(const char *buf, const char *bufend,
+ const char *match, int matchlen,
+ const char **cont, const char **start) const
+{
+ *cont = match + 1;
+ // The user is required to supply only the first truncate_len characters
+ // of the key. If truncate_len <= 0, he must supply all the key.
+ if ((truncate_len <= 0 || matchlen < truncate_len)
+ && map[uchar(match[matchlen])] != '\0')
+ return 0;
+
+ // The character before the match must not be an alphanumeric
+ // character (unless the alphanumeric character follows one or two
+ // percent characters at the beginning of the line), nor must it be
+ // a percent character at the beginning of a line, nor a percent
+ // character following a percent character at the beginning of a
+ // line.
+
+ switch (match - buf) {
+ case 0:
+ break;
+ case 1:
+ if (match[-1] == '%' || map[uchar(match[-1])] != '\0')
+ return 0;
+ break;
+ case 2:
+ if (map[uchar(match[-1])] != '\0' && match[-2] != '%')
+ return 0;
+ if (match[-1] == '%'
+ && (match[-2] == '\n' || match[-2] == '%'))
+ return 0;
+ break;
+ default:
+ if (map[uchar(match[-1])] != '\0'
+ && !(match[-2] == '%'
+ && (match[-3] == '\n'
+ || (match[-3] == '%' && match[-4] == '\n'))))
+ return 0;
+ if (match[-1] == '%'
+ && (match[-2] == '\n'
+ || (match[-2] == '%' && match[-3] == '\n')))
+ return 0;
+ }
+
+ const char *p = match;
+ int had_percent = 0;
+ for (;;) {
+ if (*p == '\n') {
+ if (!had_percent && p[1] == '%') {
+ if (p[2] != '\0' && strchr(ignore_fields, p[2]) != 0) {
+ *cont = skip_field(bufend, match + matchlen);
+ return 0;
+ }
+ if (!start)
+ break;
+ had_percent = 1;
+ }
+ if (p <= buf) {
+ if (start)
+ *start = p + 1;
+ return 1;
+ }
+ const char *q;
+ for (q = p - 1; *q == ' ' || *q == '\t'; q--)
+ ;
+ if (*q == '\n') {
+ if (start)
+ *start = p + 1;
+ break;
+ }
+ p = q;
+ }
+ p--;
+ }
+ return 1;
+}
+
+file_buffer::file_buffer()
+: buffer(0), bufend(0)
+{
+}
+
+file_buffer::~file_buffer()
+{
+ delete[] buffer;
+}
+
+const char *file_buffer::get_start() const
+{
+ return buffer ? buffer + 4 : 0;
+}
+
+const char *file_buffer::get_end() const
+{
+ return bufend;
+}
+
+int file_buffer::load(int fd, const char *filename)
+{
+ struct stat sb;
+ if (fstat(fd, &sb) < 0)
+ error("can't fstat '%1': %2", filename, strerror(errno));
+ else if (!S_ISREG(sb.st_mode))
+ error("'%1' is not a regular file", filename);
+ else {
+ // We need one character extra at the beginning for an additional newline
+ // used as a sentinel. We get 4 instead so that the read buffer will be
+ // word-aligned. This seems to make the read slightly faster. We also
+ // need one character at the end also for an additional newline used as a
+ // sentinel.
+ int size = int(sb.st_size);
+ buffer = new char[size + 4 + 1];
+ int nread = read(fd, buffer + 4, size);
+ if (nread < 0)
+ error("error reading '%1': %2", filename, strerror(errno));
+ else if (nread != size)
+ error("size of '%1' decreased", filename);
+ else {
+ char c;
+ nread = read(fd, &c, 1);
+ if (nread != 0)
+ error("size of '%1' increased", filename);
+ else if (memchr(buffer + 4, '\0', size < 1024 ? size : 1024) != 0)
+ error("database '%1' is a binary file", filename);
+ else {
+ close(fd);
+ buffer[3] = '\n';
+ int sidx = 4, didx = 4;
+ for ( ; sidx < size + 4; sidx++, didx++)
+ {
+ if (buffer[sidx] == '\r')
+ {
+ if (buffer[++sidx] != '\n')
+ buffer[didx++] = '\r';
+ else
+ size--;
+ }
+ if (sidx != didx)
+ buffer[didx] = buffer[sidx];
+ }
+ bufend = buffer + 4 + size;
+ if (bufend[-1] != '\n')
+ *bufend++ = '\n';
+ return 1;
+ }
+ }
+ delete[] buffer;
+ buffer = 0;
+ }
+ close(fd);
+ return 0;
+}
+
+linear_searcher::linear_searcher(const char *query, int query_len,
+ const char *ign, int trunc)
+: ignore_fields(ign), truncate_len(trunc), keys(0), nkeys(0)
+{
+ const char *query_end = query + query_len;
+ int nk = 0;
+ const char *p;
+ for (p = query; p < query_end; p++)
+ if (map[uchar(*p)] != '\0'
+ && (p[1] == '\0' || map[uchar(p[1])] == '\0'))
+ nk++;
+ if (nk == 0)
+ return;
+ keys = new bmpattern*[nk];
+ p = query;
+ for (;;) {
+ while (p < query_end && map[uchar(*p)] == '\0')
+ p++;
+ if (p == query_end)
+ break;
+ const char *start = p;
+ while (p < query_end && map[uchar(*p)] != '\0')
+ p++;
+ keys[nkeys++] = new bmpattern(start, p - start);
+ }
+ assert(nkeys <= nk);
+ if (nkeys == 0) {
+ delete[] keys;
+ keys = 0;
+ }
+}
+
+linear_searcher::~linear_searcher()
+{
+ for (int i = 0; i < nkeys; i++)
+ delete keys[i];
+ delete[] keys;
+}
+
+int linear_searcher::search(const char *buffer, const char *bufend,
+ const char **startp, int *lengthp) const
+{
+ assert(bufend - buffer > 0);
+ assert(buffer[-1] == '\n');
+ assert(bufend[-1] == '\n');
+ if (nkeys == 0)
+ return 0;
+ for (;;) {
+ const char *refstart;
+ const char *found = search_and_check(keys[0], buffer, bufend, &refstart);
+ if (!found)
+ break;
+ const char *refend = find_end(bufend, found + keys[0]->length());
+ int i;
+ for (i = 1; i < nkeys; i++)
+ if (!search_and_check(keys[i], refstart, refend))
+ break;
+ if (i >= nkeys) {
+ *startp = refstart;
+ *lengthp = refend - refstart;
+ return 1;
+ }
+ buffer = refend;
+ }
+ return 0;
+}
+
+class linear_search_item : public search_item {
+ file_buffer fbuf;
+public:
+ linear_search_item(const char *filename, int fid);
+ ~linear_search_item();
+ int load(int fd);
+ search_item_iterator *make_search_item_iterator(const char *);
+ friend class linear_search_item_iterator;
+};
+
+class linear_search_item_iterator : public search_item_iterator {
+ linear_search_item *lsi;
+ int pos;
+public:
+ linear_search_item_iterator(linear_search_item *, const char *query);
+ ~linear_search_item_iterator();
+ int next(const linear_searcher &, const char **ptr, int *lenp,
+ reference_id *ridp);
+};
+
+search_item *make_linear_search_item(int fd, const char *filename, int fid)
+{
+ linear_search_item *item = new linear_search_item(filename, fid);
+ if (!item->load(fd)) {
+ delete item;
+ return 0;
+ }
+ else
+ return item;
+}
+
+linear_search_item::linear_search_item(const char *filename, int fid)
+: search_item(filename, fid)
+{
+}
+
+linear_search_item::~linear_search_item()
+{
+}
+
+int linear_search_item::load(int fd)
+{
+ return fbuf.load(fd, name);
+}
+
+search_item_iterator *linear_search_item::make_search_item_iterator(
+ const char *query)
+{
+ return new linear_search_item_iterator(this, query);
+}
+
+linear_search_item_iterator::linear_search_item_iterator(
+ linear_search_item *p, const char *)
+: lsi(p), pos(0)
+{
+}
+
+linear_search_item_iterator::~linear_search_item_iterator()
+{
+}
+
+int linear_search_item_iterator::next(const linear_searcher &searcher,
+ const char **startp, int *lengthp,
+ reference_id *ridp)
+{
+ const char *bufstart = lsi->fbuf.get_start();
+ const char *bufend = lsi->fbuf.get_end();
+ const char *ptr = bufstart + pos;
+ if (ptr < bufend && searcher.search(ptr, bufend, startp, lengthp)) {
+ pos = *startp + *lengthp - bufstart;
+ if (ridp)
+ *ridp = reference_id(lsi->filename_id, *startp - bufstart);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libbib/map.c b/src/libs/libbib/map.c
new file mode 100644
index 0000000..9eae72b
--- /dev/null
+++ b/src/libs/libbib/map.c
@@ -0,0 +1,86 @@
+/* Copyright (C) 1989- 2014 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#ifdef HAVE_MMAP
+
+#include <sys/types.h>
+#include <sys/mman.h>
+
+/* The Net-2 man pages says that a MAP_FILE flag is required. */
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Prototypes */
+char *mapread(int, int);
+int unmap(char *, int);
+
+char *mapread(int fd, int nbytes)
+{
+ char *p = (char *)mmap((void *)0, (size_t)nbytes, PROT_READ,
+ MAP_FILE|MAP_PRIVATE, fd, (off_t)0);
+ if (p == MAP_FAILED)
+ return 0;
+ /* mmap() shouldn't return 0 since MAP_FIXED wasn't specified. */
+ if (p == 0)
+ abort();
+ return p;
+}
+
+int unmap(char *p, int len)
+{
+ return munmap((void *)p, len);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#else /* not HAVE_MMAP */
+
+#include <errno.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char *mapread(int fd, int nbytes)
+{
+ errno = ENODEV;
+ return 0;
+}
+
+int unmap(char *p, int len)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* not HAVE_MMAP */
diff --git a/src/libs/libbib/search.cpp b/src/libs/libbib/search.cpp
new file mode 100644
index 0000000..10867b6
--- /dev/null
+++ b/src/libs/libbib/search.cpp
@@ -0,0 +1,136 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "posix.h"
+#include "errarg.h"
+#include "error.h"
+#include "nonposix.h"
+
+#include "refid.h"
+#include "search.h"
+
+int linear_truncate_len = 6;
+const char *linear_ignore_fields = "XYZ";
+
+search_list::search_list()
+: list(0), niterators(0), next_fid(1)
+{
+}
+
+search_list::~search_list()
+{
+ assert(niterators == 0);
+ while (list) {
+ search_item *tem = list->next;
+ delete list;
+ list = tem;
+ }
+}
+
+void search_list::add_file(const char *filename, int silent)
+{
+ search_item *p = make_index_search_item(filename, next_fid);
+ if (!p) {
+ int fd = open(filename, O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ if (!silent)
+ error("can't open '%1': %2", filename, strerror(errno));
+ }
+ else
+ p = make_linear_search_item(fd, filename, next_fid);
+ }
+ if (p) {
+ search_item **pp;
+ for (pp = &list; *pp; pp = &(*pp)->next)
+ ;
+ *pp = p;
+ next_fid = p->next_filename_id();
+ }
+}
+
+int search_list::nfiles() const
+{
+ int n = 0;
+ for (search_item *ptr = list; ptr; ptr = ptr->next)
+ n++;
+ return n;
+}
+
+search_list_iterator::search_list_iterator(search_list *p, const char *q)
+: list(p), ptr(p->list), iter(0), query(strsave(q)),
+ searcher(q, strlen(q), linear_ignore_fields, linear_truncate_len)
+{
+ list->niterators += 1;
+}
+
+search_list_iterator::~search_list_iterator()
+{
+ list->niterators -= 1;
+ delete[] query;
+ delete iter;
+}
+
+int search_list_iterator::next(const char **pp, int *lenp, reference_id *ridp)
+{
+ while (ptr) {
+ if (iter == 0)
+ iter = ptr->make_search_item_iterator(query);
+ if (iter->next(searcher, pp, lenp, ridp))
+ return 1;
+ delete iter;
+ iter = 0;
+ ptr = ptr->next;
+ }
+ return 0;
+}
+
+search_item::search_item(const char *nm, int fid)
+: name(strsave(nm)), filename_id(fid), next(0)
+{
+}
+
+search_item::~search_item()
+{
+ delete[] name;
+}
+
+int search_item::is_named(const char *nm) const
+{
+ return strcmp(name, nm) == 0;
+}
+
+int search_item::next_filename_id() const
+{
+ return filename_id + 1;
+}
+
+search_item_iterator::~search_item_iterator()
+{
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libdriver/input.cpp b/src/libs/libdriver/input.cpp
new file mode 100644
index 0000000..821b526
--- /dev/null
+++ b/src/libs/libdriver/input.cpp
@@ -0,0 +1,1841 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+
+ Written by James Clark (jjc@jclark.com)
+ Major rewrite 2001 by Bernd Warken <groff-bernd.warken-72@web.de>
+
+ This file is part of groff, the GNU roff text processing system.
+
+ groff is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ groff is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Description
+
+ This file implements the parser for the intermediate groff output,
+ see groff_out(5), and does the printout for the given device.
+
+ All parsed information is processed within the function do_file().
+ A device postprocessor just needs to fill in the methods for the class
+ 'printer' (or rather a derived class) without having to worry about
+ the syntax of the intermediate output format. Consequently, the
+ programming of groff postprocessors is similar to the development of
+ device drivers.
+
+ The prototyping for this file is done in driver.h (and error.h).
+*/
+
+/* Changes of the 2001 rewrite of this file.
+
+ The interface to the outside and the handling of the global
+ variables was not changed, but internally many necessary changes
+ were performed.
+
+ The main aim for this rewrite is to provide a first step towards
+ making groff fully compatible with classical troff without pain.
+
+ Bugs fixed
+ - Unknown subcommands of 'D' and 'x' are now ignored like in the
+ classical case, but a warning is issued. This was also
+ implemented for the other commands.
+ - A warning is emitted if 'x stop' is missing.
+ - 'DC' and 'DE' commands didn't position to the right end after
+ drawing (now they do), see discussion below.
+ - So far, 'x stop' was ignored. Now it terminates the processing
+ of the current intermediate output file like the classical troff.
+ - The command 'c' didn't check correctly on white-space.
+ - The environment stack wasn't suitable for the color extensions
+ (replaced by a class).
+ - The old groff parser could only handle a prologue with the first
+ 3 lines having a fixed structure, while classical troff specified
+ the sequence of the first 3 commands without further
+ restrictions. Now the parser is smart about additional
+ white space, comments, and empty lines in the prologue.
+ - The old parser allowed space characters only as syntactical
+ separators, while classical troff had tab characters as well.
+ Now any sequence of tabs and/or spaces is a syntactical
+ separator between commands and/or arguments.
+ - Range checks for numbers implemented.
+
+ New and improved features
+ - The color commands 'm' and 'DF' are added.
+ - The old color command 'Df' is now converted and delegated to 'DFg'.
+ - The command 'F' is implemented as 'use intended file name'. It
+ checks whether its argument agrees with the file name used so far,
+ otherwise a warning is issued. Then the new name is remembered
+ and used for the following error messages.
+ - For the positioning after drawing commands, an alternative, easier
+ scheme is provided, but not yet activated; it can be chosen by
+ undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
+ It extends the rule of the classical troff output language in a
+ logical way instead of the rather strange actual positioning.
+ For details, see the discussion below.
+ - For the 'D' commands that only set the environment, the calling of
+ pr->send_draw() was removed because this doesn't make sense for
+ the 'DF' commands; the (changed) environment is sent with the
+ next command anyway.
+ - Error handling was clearly separated into warnings and fatal.
+ - The error behavior on additional arguments for 'D' and 'x'
+ commands with a fixed number of arguments was changed from being
+ ignored (former groff) to issue a warning and ignore (now), see
+ skip_line_x(). No fatal was chosen because both string and
+ integer arguments can occur.
+ - The gtroff program issues a trailing dummy integer argument for
+ some drawing commands with an odd number of arguments to make the
+ number of arguments even, e.g. the DC and Dt commands; this is
+ honored now.
+ - All D commands with a variable number of args expect an even
+ number of trailing integer arguments, so fatal on error was
+ implemented.
+ - Disable environment stack and the commands '{' and '}' by making
+ them conditional on macro USE_ENV_STACK; actually, this is
+ undefined by default. There isn't any known application for these
+ features.
+
+ Cosmetics
+ - Nested 'switch' commands are avoided by using more functions.
+ Dangerous 'fall-through's avoided.
+ - Commands and functions are sorted alphabetically (where possible).
+ - Dynamic arrays/buffers are now implemented as container classes.
+ - Some functions had an ugly return structure; this has been
+ streamlined by using classes.
+ - Use standard C math functions for number handling, so getting rid
+ of differences to '0'.
+ - The macro 'IntArg' has been created for an easier transition
+ to guaranteed 32 bits integers ('int' is enough for GNU, while
+ ANSI only guarantees 'long int' to have a length of 32 bits).
+ - The many usages of type 'int' are differentiated by using 'Char',
+ 'bool', and 'IntArg' where appropriate.
+ - To ease the calls of the local utility functions, the parser
+ variables 'current_file', 'npages', and 'current_env'
+ (formerly env) were made global to the file (formerly they were
+ local to the do_file() function)
+ - Various comments were added.
+
+ TODO
+ - Get rid of the stupid drawing positioning.
+ - Can the 'Dt' command be completely handled by setting environment
+ within do_file() instead of sending to pr?
+ - Integer arguments must be >= 32 bits, use conditional #define.
+ - Add scaling facility for classical device independence and
+ non-groff devices. Classical troff output had a quasi device
+ independence by scaling the intermediate output to the resolution
+ of the postprocessor device if different from the one specified
+ with 'x T', groff have not. So implement full quasi device
+ independence, including the mapping of the strange classical
+ devices to the postprocessor device (seems to be reasonably
+ easy).
+ - The external, global pointer variables are not optimally handled.
+ - The global variables 'current_filename',
+ 'current_source_filename', and 'current_lineno' are only used for
+ error reporting. So implement a static class 'Error'
+ ('::' calls).
+ - The global 'device' is the name used during the formatting
+ process; there should be a new variable for the device name used
+ during the postprocessing.
+ - Implement the B-spline drawing 'D~' for all graphical devices.
+ - Make 'environment' a class with an overflow check for its members
+ and a delete method to get rid of delete_current_env().
+ - Implement the 'EnvStack' to use 'new' instead of 'malloc'.
+ - The class definitions of this document could go into a new file.
+ - The comments in this section should go to a 'Changelog' or some
+ 'README' file in this directory.
+*/
+
+/*
+ Discussion of the positioning by drawing commands
+
+ There was some confusion about the positioning of the graphical
+ pointer at the printout after having executed a 'D' command.
+ The classical troff manual of Ossanna & Kernighan specified,
+
+ 'The position after a graphical object has been drawn is
+ at its end; for circles and ellipses, the "end" is at the
+ right side.'
+
+ From this, it follows that
+ - all open figures (args, splines, and lines) should position at their
+ final point.
+ - all circles and ellipses should position at their right-most point
+ (as if 2 halves had been drawn).
+ - all closed figures apart from circles and ellipses shouldn't change
+ the position because they return to their origin.
+ - all setting commands should not change position because they do not
+ draw any graphical object.
+
+ In the case of the open figures, this means that the horizontal
+ displacement is the sum of all odd arguments and the vertical offset
+ the sum of all even arguments, called the alternate arguments sum
+ displacement in the following.
+
+ Unfortunately, groff did not implement this simple rule. The former
+ documentation in groff_out(5) differed from the source code, and
+ neither of them is compatible with the classical rule.
+
+ The former groff_out(5) specified to use the alternative arguments
+ sum displacement for calculating the drawing positioning of
+ non-classical commands, including the 'Dt' command (setting-only)
+ and closed polygons. Applying this to the new groff color commands
+ will lead to disaster. For their arguments can take large values (>
+ 65000). On low resolution devices, the displacement of such large
+ values will corrupt the display or kill the printer. So the
+ nonsense specification has come to a natural end anyway.
+
+ The groff source code, however, had no positioning for the
+ setting-only commands (esp. 'Dt'), the right-end positioning for
+ outlined circles and ellipses, and the alternative argument sum
+ displacement for all other commands (including filled circles and
+ ellipses).
+
+ The reason why no one seems to have suffered from this mayhem so
+ far is that the graphical objects are usually generated by
+ preprocessors like pic that do not depend on the automatic
+ positioning. When using the low level '\D' escape sequences or 'D'
+ output commands, the strange positionings can be circumvented by
+ absolute positionings or by tricks like '\Z'.
+
+ So doing an exorcism on the strange, incompatible displacements might
+ not harm any existing documents, but will make the usage of the
+ graphical escape sequences and commands natural.
+
+ That's why the rewrite of this file returned to the reasonable,
+ classical specification with its clear end-of-drawing rule that is
+ suitable for all cases. But a macro STUPID_DRAWING_POSITIONING is
+ provided for testing the funny former behavior.
+
+ The new rule implies the following behavior.
+ - Setting commands ('Dt', 'Df', 'DF') and polygons ('Dp' and 'DP')
+ do not change position now.
+ - Filled circles and ellipses ('DC' and 'DE') position at their
+ most right point (outlined ones 'Dc' and 'De' did this anyway).
+ - As before, all open graphical objects position to their final
+ drawing point (alternate sum of the command arguments).
+
+*/
+
+#ifndef STUPID_DRAWING_POSITIONING
+// uncomment next line if all non-classical D commands shall position
+// to the strange alternate sum of args displacement
+#define STUPID_DRAWING_POSITIONING
+#endif
+
+// Decide whether the commands '{' and '}' for different environments
+// should be used.
+#undef USE_ENV_STACK
+
+#include "driver.h"
+#include "device.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <math.h>
+
+
+/**********************************************************************
+ local types
+ **********************************************************************/
+
+// integer type used in the fields of struct environment (see printer.h)
+typedef int EnvInt;
+
+// integer arguments of groff_out commands, must be >= 32 bits
+typedef int IntArg;
+
+// color components of groff_out color commands, must be >= 32 bits
+typedef unsigned int ColorArg;
+
+// Array for IntArg values.
+class IntArray {
+ size_t num_allocated;
+ size_t num_stored;
+ IntArg *data;
+public:
+ IntArray(void);
+ IntArray(const size_t);
+ ~IntArray(void);
+ IntArg operator[](const size_t i) const
+ {
+ if (i >= num_stored)
+ fatal("index out of range");
+ return (IntArg) data[i];
+ }
+ void append(IntArg);
+ IntArg *get_data(void) const { return (IntArg *)data; }
+ size_t len(void) const { return num_stored; }
+};
+
+// Characters read from the input queue.
+class Char {
+ int data;
+public:
+ Char(void) : data('\0') {}
+ Char(const int c) : data(c) {}
+ bool operator==(char c) const { return (data == c) ? true : false; }
+ bool operator==(int c) const { return (data == c) ? true : false; }
+ bool operator==(const Char c) const
+ { return (data == c.data) ? true : false; }
+ bool operator!=(char c) const { return !(*this == c); }
+ bool operator!=(int c) const { return !(*this == c); }
+ bool operator!=(const Char c) const { return !(*this == c); }
+ operator int() const { return (int) data; }
+ operator unsigned char() const { return (unsigned char) data; }
+ operator char() const { return (char) data; }
+};
+
+// Buffer for string arguments (Char, not char).
+class StringBuf {
+ size_t num_allocated;
+ size_t num_stored;
+ Char *data; // not terminated by '\0'
+public:
+ StringBuf(void); // allocate without storing
+ ~StringBuf(void);
+ void append(const Char); // append character to 'data'
+ char *make_string(void); // return new copy of 'data' with '\0'
+ bool is_empty(void) { // true if none stored
+ return (num_stored > 0) ? false : true;
+ }
+ void reset(void); // set 'num_stored' to 0
+};
+
+#ifdef USE_ENV_STACK
+class EnvStack {
+ environment **data;
+ size_t num_allocated;
+ size_t num_stored;
+public:
+ EnvStack(void);
+ ~EnvStack(void);
+ environment *pop(void);
+ void push(environment *e);
+};
+#endif // USE_ENV_STACK
+
+
+/**********************************************************************
+ external variables
+ **********************************************************************/
+
+// exported as extern by error.h (called from driver.h)
+// needed for error messages (see ../libgroff/error.cpp)
+const char *current_filename = 0; // printable name of the current file
+ // printable name of current source file
+const char *current_source_filename = 0;
+int current_lineno = 0; // current line number of printout
+
+// exported as extern by device.h;
+const char *device = 0; // cancel former init with literal
+
+printer *pr;
+
+// Note:
+//
+// We rely on an implementation of the 'new' operator which aborts
+// gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
+
+
+/**********************************************************************
+ static local variables
+ **********************************************************************/
+
+FILE *current_file = 0; // current input stream for parser
+
+// npages: number of pages processed so far (including current page),
+// _not_ the page number in the printout (can be set with 'p').
+int npages = 0;
+
+const ColorArg
+COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
+
+const IntArg
+INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
+
+// parser environment, created and deleted by each run of do_file()
+environment *current_env = 0;
+
+#ifdef USE_ENV_STACK
+const size_t
+envp_size = sizeof(environment *);
+#endif // USE_ENV_STACK
+
+
+/**********************************************************************
+ function declarations
+ **********************************************************************/
+
+// utility functions
+ColorArg color_from_Df_command(IntArg);
+ // transform old color into new
+void delete_current_env(void); // delete global var current_env
+void fatal_command(char); // abort for invalid command
+inline Char get_char(void); // read next character from input stream
+ColorArg get_color_arg(void); // read in argument for new color cmds
+IntArray *get_D_fixed_args(const size_t);
+ // read in fixed number of integer
+ // arguments
+IntArray *get_D_fixed_args_odd_dummy(const size_t);
+ // read in a fixed number of integer
+ // arguments plus optional dummy
+IntArray *get_D_variable_args(void);
+ // variable, even number of int args
+char *get_extended_arg(void); // argument for 'x X' (several lines)
+IntArg get_integer_arg(void); // read in next integer argument
+IntArray *get_possibly_integer_args();
+ // 0 or more integer arguments
+char *get_string_arg(void); // read in next string arg, ended by WS
+inline bool is_space_or_tab(const Char);
+ // test on space/tab char
+Char next_arg_begin(void); // skip white space on current line
+Char next_command(void); // go to next command, evt. diff. line
+inline bool odd(const int); // test if integer is odd
+void position_to_end_of_args(const IntArray * const);
+ // positioning after drawing
+void remember_filename(const char *);
+ // set global current_filename
+void remember_source_filename(const char *);
+ // set global current_source_filename
+void send_draw(const Char, const IntArray * const);
+ // call pr->draw
+void skip_line(void); // unconditionally skip to next line
+bool skip_line_checked(void); // skip line, false if args are left
+void skip_line_fatal(void); // skip line, fatal if args are left
+void skip_line_warn(void); // skip line, warn if args are left
+void skip_line_D(void); // skip line in D commands
+void skip_line_x(void); // skip line in x commands
+void skip_to_end_of_line(void); // skip to the end of the current line
+inline void unget_char(const Char);
+ // restore character onto input
+
+// parser subcommands
+void parse_color_command(color *);
+ // color sub(sub)commands m and DF
+void parse_D_command(void); // graphical subcommands
+bool parse_x_command(void); // device controller subcommands
+
+
+/**********************************************************************
+ class methods
+ **********************************************************************/
+
+#ifdef USE_ENV_STACK
+EnvStack::EnvStack(void)
+{
+ num_allocated = 4;
+ // allocate pointer to array of num_allocated pointers to environment
+ data = (environment **)malloc(envp_size * num_allocated);
+ if (data == 0)
+ fatal("could not allocate environment data");
+ num_stored = 0;
+}
+
+EnvStack::~EnvStack(void)
+{
+ for (size_t i = 0; i < num_stored; i++)
+ delete data[i];
+ free(data);
+}
+
+// return top element from stack and decrease stack pointer
+//
+// the calling function must take care of properly deleting the result
+environment *
+EnvStack::pop(void)
+{
+ num_stored--;
+ environment *result = data[num_stored];
+ data[num_stored] = 0;
+ return result;
+}
+
+// copy argument and push this onto the stack
+void
+EnvStack::push(environment *e)
+{
+ environment *e_copy = new environment;
+ if (num_stored >= num_allocated) {
+ environment **old_data = data;
+ num_allocated *= 2;
+ data = (environment **)malloc(envp_size * num_allocated);
+ if (data == 0)
+ fatal("could not allocate data");
+ for (size_t i = 0; i < num_stored; i++)
+ data[i] = old_data[i];
+ free(old_data);
+ }
+ e_copy->col = new color;
+ e_copy->fill = new color;
+ *e_copy->col = *e->col;
+ *e_copy->fill = *e->fill;
+ e_copy->fontno = e->fontno;
+ e_copy->height = e->height;
+ e_copy->hpos = e->hpos;
+ e_copy->size = e->size;
+ e_copy->slant = e->slant;
+ e_copy->vpos = e->vpos;
+ data[num_stored] = e_copy;
+ num_stored++;
+}
+#endif // USE_ENV_STACK
+
+IntArray::IntArray(void)
+{
+ num_allocated = 4;
+ data = new IntArg[num_allocated];
+ num_stored = 0;
+}
+
+IntArray::IntArray(const size_t n)
+{
+ if (n <= 0)
+ fatal("number of integers to be allocated must be > 0");
+ num_allocated = n;
+ data = new IntArg[num_allocated];
+ num_stored = 0;
+}
+
+IntArray::~IntArray(void)
+{
+ delete[] data;
+}
+
+void
+IntArray::append(IntArg x)
+{
+ if (num_stored >= num_allocated) {
+ IntArg *old_data = data;
+ num_allocated *= 2;
+ data = new IntArg[num_allocated];
+ for (size_t i = 0; i < num_stored; i++)
+ data[i] = old_data[i];
+ delete[] old_data;
+ }
+ data[num_stored] = x;
+ num_stored++;
+}
+
+StringBuf::StringBuf(void)
+{
+ num_stored = 0;
+ num_allocated = 128;
+ data = new Char[num_allocated];
+}
+
+StringBuf::~StringBuf(void)
+{
+ delete[] data;
+}
+
+void
+StringBuf::append(const Char c)
+{
+ if (num_stored >= num_allocated) {
+ Char *old_data = data;
+ num_allocated *= 2;
+ data = new Char[num_allocated];
+ for (size_t i = 0; i < num_stored; i++)
+ data[i] = old_data[i];
+ delete[] old_data;
+ }
+ data[num_stored] = c;
+ num_stored++;
+}
+
+char *
+StringBuf::make_string(void)
+{
+ char *result = new char[num_stored + 1];
+ for (size_t i = 0; i < num_stored; i++)
+ result[i] = (char) data[i];
+ result[num_stored] = '\0';
+ return result;
+}
+
+void
+StringBuf::reset(void)
+{
+ num_stored = 0;
+}
+
+/**********************************************************************
+ utility functions
+ **********************************************************************/
+
+//////////////////////////////////////////////////////////////////////
+/* color_from_Df_command:
+ Process the gray shade setting command Df.
+
+ Transform Df style color into DF style color.
+ Df color: 0-1000, 0 is white
+ DF color: 0-65536, 0 is black
+
+ The Df command is obsoleted by command DFg, but kept for
+ compatibility.
+*/
+ColorArg
+color_from_Df_command(IntArg Df_gray)
+{
+ return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
+}
+
+//////////////////////////////////////////////////////////////////////
+/* delete_current_env():
+ Delete global variable current_env and its pointer members.
+
+ This should be a class method of environment.
+*/
+void delete_current_env(void)
+{
+ delete current_env->col;
+ delete current_env->fill;
+ delete current_env;
+ current_env = 0;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* fatal_command():
+ Emit error message about invalid command and abort.
+*/
+void
+fatal_command(char command)
+{
+ fatal("'%1' command invalid before first 'p' command", command);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_char():
+ Retrieve the next character from the input queue.
+
+ Return: The retrieved character (incl. EOF), converted to Char.
+*/
+inline Char
+get_char(void)
+{
+ return (Char) getc(current_file);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_color_arg():
+ Retrieve an argument suitable for the color commands m and DF.
+
+ Return: The retrieved color argument.
+*/
+ColorArg
+get_color_arg(void)
+{
+ IntArg x = get_integer_arg();
+ if (x < 0 || x > (IntArg)COLORARG_MAX) {
+ error("color component argument out of range");
+ x = 0;
+ }
+ return (ColorArg) x;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_D_fixed_args():
+ Get a fixed number of integer arguments for D commands.
+
+ Fatal if wrong number of arguments.
+ Too many arguments on the line raise a warning.
+ A line skip is done.
+
+ number: In-parameter, the number of arguments to be retrieved.
+ ignore: In-parameter, ignore next argument -- GNU troff always emits
+ pairs of parameters for 'D' extensions added by groff.
+ Default is 'false'.
+
+ Return: New IntArray containing the arguments.
+*/
+IntArray *
+get_D_fixed_args(const size_t number)
+{
+ if (number <= 0)
+ fatal("requested number of arguments must be > 0");
+ IntArray *args = new IntArray(number);
+ for (size_t i = 0; i < number; i++)
+ args->append(get_integer_arg());
+ skip_line_D();
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_D_fixed_args_odd_dummy():
+ Get a fixed number of integer arguments for D commands and optionally
+ ignore a dummy integer argument if the requested number is odd.
+
+ The gtroff program adds a dummy argument to some commands to get
+ an even number of arguments.
+ Error if the number of arguments differs from the scheme above.
+ A line skip is done.
+
+ number: In-parameter, the number of arguments to be retrieved.
+
+ Return: New IntArray containing the arguments.
+*/
+IntArray *
+get_D_fixed_args_odd_dummy(const size_t number)
+{
+ if (number <= 0)
+ fatal("requested number of arguments must be > 0");
+ IntArray *args = new IntArray(number);
+ for (size_t i = 0; i < number; i++)
+ args->append(get_integer_arg());
+ if (odd(number)) {
+ IntArray *a = get_possibly_integer_args();
+ if (a->len() > 1)
+ error("too many arguments");
+ delete a;
+ }
+ skip_line_D();
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_D_variable_args():
+ Get a variable even number of integer arguments for D commands.
+
+ Get as many integer arguments as possible from the rest of the
+ current line.
+ - The arguments are separated by an arbitrary sequence of space or
+ tab characters.
+ - A comment, a newline, or EOF indicates the end of processing.
+ - Error on non-digit characters different from these.
+ - A final line skip is performed (except for EOF).
+
+ Return: New IntArray of the retrieved arguments.
+*/
+IntArray *
+get_D_variable_args()
+{
+ IntArray *args = get_possibly_integer_args();
+ size_t n = args->len();
+ if (n <= 0)
+ error("no arguments found");
+ if (odd(n))
+ error("even number of arguments expected");
+ skip_line_D();
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_extended_arg():
+ Retrieve extended arg for 'x X' command.
+
+ - Skip leading spaces and tabs, error on EOL or newline.
+ - Return everything before the next NL or EOF ('#' is not a comment);
+ as long as the following line starts with '+' this is returned
+ as well, with the '+' replaced by a newline.
+ - Final line skip is always performed.
+
+ Return: Allocated (new) string of retrieved text argument.
+*/
+char *
+get_extended_arg(void)
+{
+ StringBuf buf = StringBuf();
+ Char c = next_arg_begin();
+ while ((int) c != EOF) {
+ if ((int) c == '\n') {
+ current_lineno++;
+ c = get_char();
+ if ((int) c == '+')
+ buf.append((Char) '\n');
+ else {
+ unget_char(c); // first character of next line
+ break;
+ }
+ }
+ else
+ buf.append(c);
+ c = get_char();
+ }
+ return buf.make_string();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_integer_arg(): Retrieve integer argument.
+
+ Skip leading spaces and tabs, collect an optional '-' and all
+ following decimal digits (at least one) up to the next non-digit,
+ which is restored onto the input queue.
+
+ Fatal error on all other situations.
+
+ Return: Retrieved integer.
+*/
+IntArg
+get_integer_arg(void)
+{
+ StringBuf buf = StringBuf();
+ Char c = next_arg_begin();
+ if ((int) c == '-') {
+ buf.append(c);
+ c = get_char();
+ }
+ if (!isdigit((int) c))
+ fatal("integer argument expected");
+ while (isdigit((int) c)) {
+ buf.append(c);
+ c = get_char();
+ }
+ // c is not a digit
+ unget_char(c);
+ char *s = buf.make_string();
+ errno = 0;
+ long int number = strtol(s, 0, 10);
+ if (errno != 0
+ || number > INTARG_MAX || number < -INTARG_MAX) {
+ error("integer argument too large");
+ number = 0;
+ }
+ delete[] s;
+ return (IntArg) number;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_possibly_integer_args():
+ Parse the rest of the input line as a list of integer arguments.
+
+ Get as many integer arguments as possible from the rest of the
+ current line, even none.
+ - The arguments are separated by an arbitrary sequence of space or
+ tab characters.
+ - A comment, a newline, or EOF indicates the end of processing.
+ - Error on non-digit characters different from these.
+ - No line skip is performed.
+
+ Return: New IntArray of the retrieved arguments.
+*/
+IntArray *
+get_possibly_integer_args()
+{
+ bool done = false;
+ StringBuf buf = StringBuf();
+ Char c = get_char();
+ IntArray *args = new IntArray();
+ while (!done) {
+ buf.reset();
+ while (is_space_or_tab(c))
+ c = get_char();
+ if (c == '-') {
+ Char c1 = get_char();
+ if (isdigit((int) c1)) {
+ buf.append(c);
+ c = c1;
+ }
+ else
+ unget_char(c1);
+ }
+ while (isdigit((int) c)) {
+ buf.append(c);
+ c = get_char();
+ }
+ if (!buf.is_empty()) {
+ char *s = buf.make_string();
+ errno = 0;
+ long int x = strtol(s, 0, 10);
+ if (errno
+ || x > INTARG_MAX || x < -INTARG_MAX) {
+ error("invalid integer argument, set to 0");
+ x = 0;
+ }
+ args->append((IntArg) x);
+ delete[] s;
+ }
+ // Here, c is not a digit.
+ // Terminate on comment, end of line, or end of file, while
+ // space or tab indicate continuation; otherwise error.
+ switch((int) c) {
+ case '#':
+ skip_to_end_of_line();
+ done = true;
+ break;
+ case '\n':
+ done = true;
+ unget_char(c);
+ break;
+ case EOF:
+ done = true;
+ break;
+ case ' ':
+ case '\t':
+ break;
+ default:
+ error("integer argument expected");
+ done = true;
+ break;
+ }
+ }
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_string_arg():
+ Retrieve string arg.
+
+ - Skip leading spaces and tabs; error on EOL or newline.
+ - Return all following characters before the next space, tab,
+ newline, or EOF character (in-word '#' is not a comment character).
+ - The terminating space, tab, newline, or EOF character is restored
+ onto the input queue, so no line skip.
+
+ Return: Retrieved string as char *, allocated by 'new'.
+*/
+char *
+get_string_arg(void)
+{
+ StringBuf buf = StringBuf();
+ Char c = next_arg_begin();
+ while (!is_space_or_tab(c)
+ && c != Char('\n') && c != Char(EOF)) {
+ buf.append(c);
+ c = get_char();
+ }
+ unget_char(c); // restore white space
+ return buf.make_string();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* is_space_or_tab():
+ Test a character if it is a space or tab.
+
+ c: In-parameter, character to be tested.
+
+ Return: True, if c is a space or tab character, false otherwise.
+*/
+inline bool
+is_space_or_tab(const Char c)
+{
+ return (c == Char(' ') || c == Char('\t')) ? true : false;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* next_arg_begin():
+ Return first character of next argument.
+
+ Skip space and tab characters; error on newline or EOF.
+
+ Return: The first character different from these (including '#').
+*/
+Char
+next_arg_begin(void)
+{
+ Char c;
+ while (1) {
+ c = get_char();
+ switch ((int) c) {
+ case ' ':
+ case '\t':
+ break;
+ case '\n':
+ case EOF:
+ error("missing argument");
+ return c;
+ default: // first essential character
+ return c;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* next_command():
+ Find the first character of the next command.
+
+ Skip spaces, tabs, comments (introduced by #), and newlines.
+
+ Return: The first character different from these (including EOF).
+*/
+Char
+next_command(void)
+{
+ Char c;
+ while (1) {
+ c = get_char();
+ switch ((int) c) {
+ case ' ':
+ case '\t':
+ break;
+ case '\n':
+ current_lineno++;
+ break;
+ case '#': // comment
+ skip_line();
+ break;
+ default: // EOF or first essential character
+ return c;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* odd():
+ Test whether argument is an odd number.
+
+ n: In-parameter, the integer to be tested.
+
+ Return: True if odd, false otherwise.
+*/
+inline bool
+odd(const int n)
+{
+ return ((n & 1) == 1) ? true : false;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* position_to_end_of_args():
+ Move graphical pointer to end of drawn figure.
+
+ This is used by the D commands that draw open geometrical figures.
+ The algorithm simply sums up all horizontal displacements (arguments
+ with even number) for the horizontal component. Similarly, the
+ vertical component is the sum of the odd arguments.
+
+ args: In-parameter, the arguments of a former drawing command.
+*/
+void
+position_to_end_of_args(const IntArray * const args)
+{
+ size_t i;
+ const size_t n = args->len();
+ for (i = 0; i < n; i += 2)
+ current_env->hpos += (*args)[i];
+ for (i = 1; i < n; i += 2)
+ current_env->vpos += (*args)[i];
+}
+
+//////////////////////////////////////////////////////////////////////
+/* remember_filename():
+ Set global variable current_filename.
+
+ The actual filename is stored in current_filename. This is used by
+ the postprocessors, expecting the name "<standard input>" for stdin.
+
+ filename: In-out-parameter; is changed to the new value also.
+*/
+void
+remember_filename(const char *filename)
+{
+ char *fname;
+ if (strcmp(filename, "-") == 0)
+ fname = (char *)"<standard input>";
+ else
+ fname = (char *)filename;
+ size_t len = strlen(fname) + 1;
+ if (current_filename != 0)
+ free((char *)current_filename);
+ current_filename = (const char *)malloc(len);
+ if (current_filename == 0)
+ fatal("can't malloc space for filename");
+ strncpy((char *)current_filename, (char *)fname, len);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* remember_source_filename():
+ Set global variable current_source_filename.
+
+ The actual filename is stored in current_filename. This is used by
+ the postprocessors, expecting the name "<standard input>" for stdin.
+
+ filename: In-out-parameter; is changed to the new value also.
+*/
+void
+remember_source_filename(const char *filename)
+{
+ char *fname;
+ if (strcmp(filename, "-") == 0)
+ fname = (char *)"<standard input>";
+ else
+ fname = (char *)filename;
+ size_t len = strlen(fname) + 1;
+ if (current_source_filename != 0)
+ free((char *)current_source_filename);
+ current_source_filename = (const char *)malloc(len);
+ if (current_source_filename == 0)
+ fatal("can't malloc space for filename");
+ strncpy((char *)current_source_filename, (char *)fname, len);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* send_draw():
+ Call draw method of printer class.
+
+ subcmd: Letter of actual D subcommand.
+ args: Array of integer arguments of actual D subcommand.
+*/
+void
+send_draw(const Char subcmd, const IntArray * const args)
+{
+ EnvInt n = (EnvInt) args->len();
+ pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line():
+ Go to next line within the input queue.
+
+ Skip the rest of the current line, including the newline character.
+ The global variable current_lineno is adjusted.
+ No errors are raised.
+*/
+void
+skip_line(void)
+{
+ Char c = get_char();
+ while (1) {
+ if (c == '\n') {
+ current_lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ c = get_char();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_checked ():
+ Check that there aren't any arguments left on the rest of the line,
+ then skip line.
+
+ Spaces, tabs, and a comment are allowed before newline or EOF.
+ All other characters raise an error.
+*/
+bool
+skip_line_checked(void)
+{
+ bool ok = true;
+ Char c = get_char();
+ while (is_space_or_tab(c))
+ c = get_char();
+ switch((int) c) {
+ case '#': // comment
+ skip_line();
+ break;
+ case '\n':
+ current_lineno++;
+ break;
+ case EOF:
+ break;
+ default:
+ ok = false;
+ skip_line();
+ break;
+ }
+ return ok;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_fatal ():
+ Fatal error if arguments left, otherwise skip line.
+
+ Spaces, tabs, and a comment are allowed before newline or EOF.
+ All other characters trigger the error.
+*/
+void
+skip_line_fatal(void)
+{
+ bool ok = skip_line_checked();
+ if (!ok) {
+ current_lineno--;
+ error("too many arguments");
+ current_lineno++;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_warn ():
+ Skip line, but warn if arguments are left on actual line.
+
+ Spaces, tabs, and a comment are allowed before newline or EOF.
+ All other characters raise a warning
+*/
+void
+skip_line_warn(void)
+{
+ bool ok = skip_line_checked();
+ if (!ok) {
+ current_lineno--;
+ warning("too many arguments on current line");
+ current_lineno++;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_D ():
+ Skip line in 'D' commands.
+
+ Decide whether in case of an additional argument a fatal error is
+ raised (the documented classical behavior), only a warning is
+ issued, or the line is just skipped (former groff behavior).
+ Actually decided for the warning.
+*/
+void
+skip_line_D(void)
+{
+ skip_line_warn();
+ // or: skip_line_fatal();
+ // or: skip_line();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_x ():
+ Skip line in 'x' commands.
+
+ Decide whether in case of an additional argument a fatal error is
+ raised (the documented classical behavior), only a warning is
+ issued, or the line is just skipped (former groff behavior).
+ Actually decided for the warning.
+*/
+void
+skip_line_x(void)
+{
+ skip_line_warn();
+ // or: skip_line_fatal();
+ // or: skip_line();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_to_end_of_line():
+ Go to the end of the current line.
+
+ Skip the rest of the current line, excluding the newline character.
+ The global variable current_lineno is not changed.
+ No errors are raised.
+*/
+void
+skip_to_end_of_line(void)
+{
+ Char c = get_char();
+ while (1) {
+ if (c == '\n') {
+ unget_char(c);
+ return;
+ }
+ if (c == EOF)
+ return;
+ c = get_char();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* unget_char(c):
+ Restore character c onto input queue.
+
+ Write a character back onto the input stream.
+ EOF is gracefully handled.
+
+ c: In-parameter; character to be pushed onto the input queue.
+*/
+inline void
+unget_char(const Char c)
+{
+ if (c != EOF) {
+ int ch = (int) c;
+ if (ungetc(ch, current_file) == EOF)
+ fatal("could not unget character");
+ }
+}
+
+
+/**********************************************************************
+ parser subcommands
+ **********************************************************************/
+
+//////////////////////////////////////////////////////////////////////
+/* parse_color_command:
+ Process the commands m and DF, but not Df.
+
+ col: In-out-parameter; the color object to be set, must have
+ been initialized before.
+*/
+void
+parse_color_command(color *col)
+{
+ ColorArg gray = 0;
+ ColorArg red = 0, green = 0, blue = 0;
+ ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
+ Char subcmd = next_arg_begin();
+ switch((int) subcmd) {
+ case 'c': // DFc or mc: CMY
+ cyan = get_color_arg();
+ magenta = get_color_arg();
+ yellow = get_color_arg();
+ col->set_cmy(cyan, magenta, yellow);
+ break;
+ case 'd': // DFd or md: set default color
+ col->set_default();
+ break;
+ case 'g': // DFg or mg: gray
+ gray = get_color_arg();
+ col->set_gray(gray);
+ break;
+ case 'k': // DFk or mk: CMYK
+ cyan = get_color_arg();
+ magenta = get_color_arg();
+ yellow = get_color_arg();
+ black = get_color_arg();
+ col->set_cmyk(cyan, magenta, yellow, black);
+ break;
+ case 'r': // DFr or mr: RGB
+ red = get_color_arg();
+ green = get_color_arg();
+ blue = get_color_arg();
+ col->set_rgb(red, green, blue);
+ break;
+ default:
+ error("invalid color scheme '%1'", (int) subcmd);
+ break;
+ } // end of color subcommands
+}
+
+//////////////////////////////////////////////////////////////////////
+/* parse_D_command():
+ Parse the subcommands of graphical command D.
+
+ This is the part of the do_file() parser that scans the graphical
+ subcommands.
+ - Error on lacking or wrong arguments.
+ - Warning on too many arguments.
+ - Line is always skipped.
+*/
+void
+parse_D_command()
+{
+ Char subcmd = next_arg_begin();
+ switch((int) subcmd) {
+ case '~': // D~: draw B-spline
+ // actually, this isn't available for some postprocessors
+ // fall through
+ default: // unknown options are passed to device
+ {
+ IntArray *args = get_D_variable_args();
+ send_draw(subcmd, args);
+ position_to_end_of_args(args);
+ delete args;
+ break;
+ }
+ case 'a': // Da: draw arc
+ {
+ IntArray *args = get_D_fixed_args(4);
+ send_draw(subcmd, args);
+ position_to_end_of_args(args);
+ delete args;
+ break;
+ }
+ case 'c': // Dc: draw circle line
+ {
+ IntArray *args = get_D_fixed_args(1);
+ send_draw(subcmd, args);
+ // move to right end
+ current_env->hpos += (*args)[0];
+ delete args;
+ break;
+ }
+ case 'C': // DC: draw solid circle
+ {
+ IntArray *args = get_D_fixed_args_odd_dummy(1);
+ send_draw(subcmd, args);
+ // move to right end
+ current_env->hpos += (*args)[0];
+ delete args;
+ break;
+ }
+ case 'e': // De: draw ellipse line
+ case 'E': // DE: draw solid ellipse
+ {
+ IntArray *args = get_D_fixed_args(2);
+ send_draw(subcmd, args);
+ // move to right end
+ current_env->hpos += (*args)[0];
+ delete args;
+ break;
+ }
+ case 'f': // Df: set fill gray; obsoleted by DFg
+ {
+ IntArg arg = get_integer_arg();
+ if ((arg >= 0) && (arg <= 1000)) {
+ // convert arg and treat it like DFg
+ ColorArg gray = color_from_Df_command(arg);
+ current_env->fill->set_gray(gray);
+ }
+ else {
+ // set fill color to the same value as the current outline color
+ delete current_env->fill;
+ current_env->fill = new color(current_env->col);
+ }
+ pr->change_fill_color(current_env);
+ // skip unused 'vertical' component (\D'...' always emits pairs)
+ (void) get_integer_arg();
+# ifdef STUPID_DRAWING_POSITIONING
+ current_env->hpos += arg;
+# endif
+ skip_line_x();
+ break;
+ }
+ case 'F': // DF: set fill color, several formats
+ parse_color_command(current_env->fill);
+ pr->change_fill_color(current_env);
+ // no positioning (setting-only command)
+ skip_line_x();
+ break;
+ case 'l': // Dl: draw line
+ {
+ IntArray *args = get_D_fixed_args(2);
+ send_draw(subcmd, args);
+ position_to_end_of_args(args);
+ delete args;
+ break;
+ }
+ case 'p': // Dp: draw closed polygon line
+ case 'P': // DP: draw solid closed polygon
+ {
+ IntArray *args = get_D_variable_args();
+ send_draw(subcmd, args);
+# ifdef STUPID_DRAWING_POSITIONING
+ // final args positioning
+ position_to_end_of_args(args);
+# endif
+ delete args;
+ break;
+ }
+ case 't': // Dt: set line thickness
+ {
+ IntArray *args = get_D_fixed_args_odd_dummy(1);
+ send_draw(subcmd, args);
+# ifdef STUPID_DRAWING_POSITIONING
+ // final args positioning
+ position_to_end_of_args(args);
+# endif
+ delete args;
+ break;
+ }
+ } // end of D subcommands
+}
+
+//////////////////////////////////////////////////////////////////////
+/* parse_x_command():
+ Parse subcommands of the device control command x.
+
+ This is the part of the do_file() parser that scans the device
+ controlling commands.
+ - Error on duplicate prologue commands.
+ - Error on wrong or lacking arguments.
+ - Warning on too many arguments.
+ - Line is always skipped.
+
+ Globals:
+ - current_env: is set by many subcommands.
+ - npages: page counting variable
+
+ Return: boolean in the meaning of 'stopped'
+ - true if parsing should be stopped ('x stop').
+ - false if parsing should continue.
+*/
+bool
+parse_x_command(void)
+{
+ bool stopped = false;
+ char *subcmd_str = get_string_arg();
+ char subcmd = subcmd_str[0];
+ switch (subcmd) {
+ case 'f': // x font: mount font
+ {
+ IntArg n = get_integer_arg();
+ char *name = get_string_arg();
+ pr->load_font(n, name);
+ delete[] name;
+ skip_line_x();
+ break;
+ }
+ case 'F': // x Filename: set filename for errors
+ {
+ char *str_arg = get_extended_arg();
+ if (str_arg == 0)
+ warning("empty argument for 'x F' command");
+ else {
+ remember_source_filename(str_arg);
+ delete[] str_arg;
+ }
+ break;
+ }
+ case 'H': // x Height: set character height
+ current_env->height = get_integer_arg();
+ if (current_env->height == current_env->size)
+ current_env->height = 0;
+ skip_line_x();
+ break;
+ case 'i': // x init: initialize device
+ error("duplicate 'x init' command");
+ skip_line_x();
+ break;
+ case 'p': // x pause: pause device
+ skip_line_x();
+ break;
+ case 'r': // x res: set resolution
+ error("duplicate 'x res' command");
+ skip_line_x();
+ break;
+ case 's': // x stop: stop device
+ stopped = true;
+ skip_line_x();
+ break;
+ case 'S': // x Slant: set slant
+ current_env->slant = get_integer_arg();
+ skip_line_x();
+ break;
+ case 't': // x trailer: generate trailer info
+ skip_line_x();
+ break;
+ case 'T': // x Typesetter: set typesetter
+ error("duplicate 'x T' command");
+ skip_line();
+ break;
+ case 'u': // x underline: from .cu
+ {
+ char *str_arg = get_string_arg();
+ pr->special(str_arg, current_env, 'u');
+ delete[] str_arg;
+ skip_line_x();
+ break;
+ }
+ case 'X': // x X: send uninterpretedly to device
+ {
+ char *str_arg = get_extended_arg(); // includes line skip
+ if (npages <= 0)
+ error("'x X' command invalid before first 'p' command");
+ else if (str_arg && (strncmp(str_arg, "devtag:",
+ strlen("devtag:")) == 0))
+ pr->devtag(str_arg, current_env);
+ else
+ pr->special(str_arg, current_env);
+ delete[] str_arg;
+ break;
+ }
+ default: // ignore unknown x commands, but warn
+ warning("unknown command 'x %1'", subcmd);
+ skip_line();
+ }
+ delete[] subcmd_str;
+ return stopped;
+}
+
+
+/**********************************************************************
+ exported part (by driver.h)
+ **********************************************************************/
+
+////////////////////////////////////////////////////////////////////////
+/* do_file():
+ Parse and postprocess groff intermediate output.
+
+ filename: "-" for standard input, normal file name otherwise
+*/
+void
+do_file(const char *filename)
+{
+ Char command;
+ bool stopped = false; // terminating condition
+
+#ifdef USE_ENV_STACK
+ EnvStack env_stack = EnvStack();
+#endif // USE_ENV_STACK
+
+ // setup of global variables
+ npages = 0;
+ current_lineno = 1;
+ // 'pr' is initialized after the prologue.
+ // 'device' is set by the 1st prologue command.
+
+ if (filename[0] == '-' && filename[1] == '\0')
+ current_file = stdin;
+ else {
+ errno = 0;
+ current_file = fopen(filename, "r");
+ if (errno != 0 || current_file == 0) {
+ error("can't open file '%1'", filename);
+ return;
+ }
+ }
+ remember_filename(filename);
+
+ if (current_env != 0)
+ delete_current_env();
+ current_env = new environment;
+ current_env->col = new color;
+ current_env->fill = new color;
+ current_env->fontno = -1;
+ current_env->height = 0;
+ current_env->hpos = -1;
+ current_env->slant = 0;
+ current_env->size = 0;
+ current_env->vpos = -1;
+
+ // parsing of prologue (first 3 commands)
+ {
+ char *str_arg;
+ IntArg int_arg;
+
+ // 1st command 'x T'
+ command = next_command();
+ if ((int) command == EOF)
+ return;
+ if ((int) command != 'x')
+ fatal("the first command must be 'x T'");
+ str_arg = get_string_arg();
+ if (str_arg[0] != 'T')
+ fatal("the first command must be 'x T'");
+ delete[] str_arg;
+ char *tmp_dev = get_string_arg();
+ if (pr == 0) { // note: 'pr' initialized after prologue
+ device = tmp_dev;
+ if (0 /* nullptr */ == font::load_desc())
+ fatal("cannot load description of '%1' device", tmp_dev);
+ }
+ else {
+ if (device == 0 || strcmp(device, tmp_dev) != 0)
+ fatal("all files must use the same device");
+ delete[] tmp_dev;
+ }
+ skip_line_x(); // ignore further arguments
+ current_env->size = 10 * font::sizescale;
+
+ // 2nd command 'x res'
+ command = next_command();
+ if ((int) command != 'x')
+ fatal("the second command must be 'x res'");
+ str_arg = get_string_arg();
+ if (str_arg[0] != 'r')
+ fatal("the second command must be 'x res'");
+ delete[] str_arg;
+ int_arg = get_integer_arg();
+ EnvInt font_res = font::res;
+ if (int_arg != font_res)
+ fatal("resolution does not match");
+ int_arg = get_integer_arg();
+ if (int_arg != font::hor)
+ fatal("minimum horizontal motion does not match");
+ int_arg = get_integer_arg();
+ if (int_arg != font::vert)
+ fatal("minimum vertical motion does not match");
+ skip_line_x(); // ignore further arguments
+
+ // 3rd command 'x init'
+ command = next_command();
+ if (command != 'x')
+ fatal("the third command must be 'x init'");
+ str_arg = get_string_arg();
+ if (str_arg[0] != 'i')
+ fatal("the third command must be 'x init'");
+ delete[] str_arg;
+ skip_line_x();
+ }
+
+ // parsing of body
+ if (pr == 0)
+ pr = make_printer();
+ while (!stopped) {
+ command = next_command();
+ if (command == EOF)
+ break;
+ // spaces, tabs, comments, and newlines are skipped here
+ switch ((int) command) {
+ case '#': // #: comment, ignore up to end of line
+ skip_line();
+ break;
+#ifdef USE_ENV_STACK
+ case '{': // {: start a new environment (a copy)
+ env_stack.push(current_env);
+ break;
+ case '}': // }: pop previous env from stack
+ delete_current_env();
+ current_env = env_stack.pop();
+ break;
+#endif // USE_ENV_STACK
+ case '0': // ddc: obsolete jump and print command
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ { // expect 2 digits and a character
+ char s[3];
+ Char c = next_arg_begin();
+ if (npages <= 0)
+ fatal_command(command);
+ if (!isdigit((int) c)) {
+ error("digit expected");
+ c = 0;
+ }
+ s[0] = (char) command;
+ s[1] = (char) c;
+ s[2] = '\0';
+ errno = 0;
+ long int x = strtol(s, 0, 10);
+ if (errno != 0)
+ error("couldn't convert 2 digits");
+ EnvInt hor_pos = (EnvInt) x;
+ current_env->hpos += hor_pos;
+ c = next_arg_begin();
+ if ((int) c == '\n' || (int) c == EOF)
+ error("character argument expected");
+ else
+ pr->set_ascii_char((unsigned char) c, current_env);
+ break;
+ }
+ case 'c': // c: print ascii char without moving
+ {
+ if (npages <= 0)
+ fatal_command(command);
+ Char c = next_arg_begin();
+ if (c == '\n' || c == EOF)
+ error("missing argument to 'c' command");
+ else
+ pr->set_ascii_char((unsigned char) c, current_env);
+ break;
+ }
+ case 'C': // C: print named special character
+ {
+ if (npages <= 0)
+ fatal_command(command);
+ char *str_arg = get_string_arg();
+ pr->set_special_char(str_arg, current_env);
+ delete[] str_arg;
+ break;
+ }
+ case 'D': // drawing commands
+ if (npages <= 0)
+ fatal_command(command);
+ parse_D_command();
+ break;
+ case 'f': // f: set font to number
+ current_env->fontno = get_integer_arg();
+ break;
+ case 'F': // F: obsolete, replaced by 'x F'
+ {
+ char *str_arg = get_extended_arg();
+ remember_source_filename(str_arg);
+ delete[] str_arg;
+ break;
+ }
+ case 'h': // h: relative horizontal move
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->hpos += (EnvInt) get_integer_arg();
+ break;
+ case 'H': // H: absolute horizontal positioning
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->hpos = (EnvInt) get_integer_arg();
+ break;
+ case 'm': // m: glyph color
+ parse_color_command(current_env->col);
+ pr->change_color(current_env);
+ break;
+ case 'n': // n: print end of line
+ // ignore two arguments (historically)
+ if (npages <= 0)
+ fatal_command(command);
+ pr->end_of_line();
+ (void) get_integer_arg();
+ (void) get_integer_arg();
+ break;
+ case 'N': // N: print char with given int code
+ if (npages <= 0)
+ fatal_command(command);
+ pr->set_numbered_char(get_integer_arg(), current_env);
+ break;
+ case 'p': // p: start new page with given number
+ if (npages > 0)
+ pr->end_page(current_env->vpos);
+ npages++; // increment # of processed pages
+ pr->begin_page(get_integer_arg());
+ current_env->vpos = 0;
+ break;
+ case 's': // s: set point size
+ current_env->size = get_integer_arg();
+ if (current_env->height == current_env->size)
+ current_env->height = 0;
+ break;
+ case 't': // t: print a text word
+ {
+ char c;
+ if (npages <= 0)
+ fatal_command(command);
+ char *str_arg = get_string_arg();
+ size_t i = 0;
+ while ((c = str_arg[i++]) != '\0') {
+ EnvInt w;
+ pr->set_ascii_char((unsigned char) c, current_env, &w);
+ current_env->hpos += w;
+ }
+ delete[] str_arg;
+ break;
+ }
+ case 'u': // u: print spaced word
+ {
+ char c;
+ if (npages <= 0)
+ fatal_command(command);
+ EnvInt kern = (EnvInt) get_integer_arg();
+ char *str_arg = get_string_arg();
+ size_t i = 0;
+ while ((c = str_arg[i++]) != '\0') {
+ EnvInt w;
+ pr->set_ascii_char((unsigned char) c, current_env, &w);
+ current_env->hpos += w + kern;
+ }
+ delete[] str_arg;
+ break;
+ }
+ case 'v': // v: relative vertical move
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->vpos += (EnvInt) get_integer_arg();
+ break;
+ case 'V': // V: absolute vertical positioning
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->vpos = (EnvInt) get_integer_arg();
+ break;
+ case 'w': // w: inform about paddable space
+ break;
+ case 'x': // device controlling commands
+ stopped = parse_x_command();
+ break;
+ default:
+ warning("unrecognized command '%1'", (unsigned char) command);
+ skip_line();
+ break;
+ } // end of switch
+ } // end of while
+
+ // end of file reached
+ if (npages > 0)
+ pr->end_page(current_env->vpos);
+ delete pr;
+ pr = 0;
+ fclose(current_file);
+ // If 'stopped' is not 'true' here then there wasn't any 'x stop'.
+ if (!stopped)
+ warning("no final 'x stop' command");
+ delete_current_env();
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libdriver/libdriver.am b/src/libs/libdriver/libdriver.am
new file mode 100644
index 0000000..51475d8
--- /dev/null
+++ b/src/libs/libdriver/libdriver.am
@@ -0,0 +1,31 @@
+# Automake rules for 'libdriver'
+#
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/gpl-2.0.html>.
+#
+########################################################################
+
+noinst_LIBRARIES += libdriver.a
+libdriver_a_SOURCES = \
+ src/libs/libdriver/input.cpp \
+ src/libs/libdriver/printer.cpp
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/libs/libdriver/printer.cpp b/src/libs/libdriver/printer.cpp
new file mode 100644
index 0000000..f89b2e9
--- /dev/null
+++ b/src/libs/libdriver/printer.cpp
@@ -0,0 +1,268 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+ This file is part of groff.
+
+ groff is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ groff is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+
+#include "driver.h"
+
+/* If we are sending output to an onscreen pager (as is the normal case
+ when reading man pages), then we may get an error state on the output
+ stream, if the user does not read all the way to the end.
+
+ We normally expect to catch this, and clean up the error context,
+ when the pager exits, because we should get, and handle, a SIGPIPE.
+
+ However ...
+*/
+
+#if (defined(_MSC_VER) || defined(_WIN32)) \
+ && !defined(__CYGWIN__) && !defined(_UWIN)
+
+ /* Native MS-Windows doesn't know about SIGPIPE, so we cannot detect
+ the early exit from the pager, and therefore, cannot clean up the
+ error context; thus we use the following static function to
+ identify this particular error context, and so suppress unwanted
+ diagnostics.
+ */
+
+ static int
+ check_for_output_error (FILE* stream)
+ {
+ /* First, clean up any prior error context on the output stream */
+ if (ferror (stream))
+ clearerr (stream);
+ /* Clear errno, in case clearerr() and fflush() don't */
+ errno = 0;
+ /* Flush the output stream, so we can capture any error context,
+ other than the specific case we wish to suppress.
+
+ Microsoft doesn't document it, but the error code for the
+ specific context we are trying to suppress seems to be EINVAL --
+ a strange choice, since it is not normally associated with
+ fflush(); of course, it *should* be EPIPE, but this *definitely*
+ is not used, and *is* so documented.
+ */
+ return ((fflush(stream) < 0) && (errno != EINVAL));
+ }
+
+#else
+
+ /* For other systems, we simply assume that *any* output error context
+ is to be reported.
+ */
+# define check_for_output_error(stream) ferror(stream) || fflush(stream) < 0
+
+#endif
+
+
+font_pointer_list::font_pointer_list(font *f, font_pointer_list *fp)
+: p(f), next(fp)
+{
+}
+
+printer::printer()
+: font_list(0), font_table(0), nfonts(0)
+{
+}
+
+printer::~printer()
+{
+ delete[] font_table;
+ while (font_list) {
+ font_pointer_list *tem = font_list;
+ font_list = font_list->next;
+ delete tem->p;
+ delete tem;
+ }
+ if (check_for_output_error(stdout))
+ fatal("output error");
+}
+
+void printer::load_font(int n, const char *nm)
+{
+ assert(n >= 0);
+ if (n >= nfonts) {
+ if (nfonts == 0) {
+ nfonts = 10;
+ if (nfonts <= n)
+ nfonts = n + 1;
+ font_table = new font *[nfonts];
+ for (int i = 0; i < nfonts; i++)
+ font_table[i] = 0;
+ }
+ else {
+ font **old_font_table = font_table;
+ int old_nfonts = nfonts;
+ nfonts *= 2;
+ if (n >= nfonts)
+ nfonts = n + 1;
+ font_table = new font *[nfonts];
+ int i;
+ for (i = 0; i < old_nfonts; i++)
+ font_table[i] = old_font_table[i];
+ for (i = old_nfonts; i < nfonts; i++)
+ font_table[i] = 0;
+ delete[] old_font_table;
+ }
+ }
+ font *f = find_font(nm);
+ font_table[n] = f;
+}
+
+font *printer::find_font(const char *nm)
+{
+ for (font_pointer_list *p = font_list; p; p = p->next)
+ if (strcmp(p->p->get_name(), nm) == 0)
+ return p->p;
+ font *f = make_font(nm);
+ if (0 /* nullptr */ == f)
+ fatal("cannot find font '%1'", nm);
+ font_list = new font_pointer_list(f, font_list);
+ return f;
+}
+
+font *printer::make_font(const char *nm)
+{
+ return font::load_font(nm);
+}
+
+void printer::end_of_line()
+{
+}
+
+void printer::special(char *, const environment *, char)
+{
+}
+
+void printer::devtag(char *, const environment *, char)
+{
+}
+
+void printer::draw(int, int *, int, const environment *)
+{
+}
+
+void printer::change_color(const environment * const)
+{
+}
+
+void printer::change_fill_color(const environment * const)
+{
+}
+
+void printer::set_ascii_char(unsigned char c, const environment *env,
+ int *widthp)
+{
+ char buf[2];
+ int w;
+ font *f;
+
+ buf[0] = c;
+ buf[1] = '\0';
+
+ glyph *g = set_char_and_width(buf, env, &w, &f);
+
+ if (g != UNDEFINED_GLYPH) {
+ set_char(g, f, env, w, 0);
+ if (widthp)
+ *widthp = w;
+ }
+}
+
+void printer::set_special_char(const char *nm, const environment *env,
+ int *widthp)
+{
+ font *f;
+ int w;
+ glyph *g = set_char_and_width(nm, env, &w, &f);
+ if (g != UNDEFINED_GLYPH) {
+ set_char(g, f, env, w, nm);
+ if (widthp)
+ *widthp = w;
+ }
+}
+
+glyph *printer::set_char_and_width(const char *nm,
+ const environment *env, int *widthp,
+ font **f)
+{
+ glyph *g = name_to_glyph(nm);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return UNDEFINED_GLYPH;
+ }
+ *f = font_table[fn];
+ if (*f == 0) {
+ error("no font mounted at position %1", fn);
+ return UNDEFINED_GLYPH;
+ }
+ if (!(*f)->contains(g)) {
+ if (nm[0] != '\0' && nm[1] == '\0')
+ error("font '%1' does not contain ordinary character '%2'",
+ (*f)->get_name(), nm[0]);
+ else
+ error("font '%1' does not contain special character '%2'",
+ (*f)->get_name(), nm);
+ return UNDEFINED_GLYPH;
+ }
+ int w = (*f)->get_width(g, env->size);
+ if (widthp)
+ *widthp = w;
+ return g;
+}
+
+void printer::set_numbered_char(int num, const environment *env, int
+ *widthp)
+{
+ glyph *g = number_to_glyph(num);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return;
+ }
+ font *f = font_table[fn];
+ if (f == 0) {
+ error("no font mounted at position %1", fn);
+ return;
+ }
+ if (!f->contains(g)) {
+ error("font '%1' does not contain numbered character %2",
+ f->get_name(), num);
+ return;
+ }
+ int w = f->get_width(g, env->size);
+ if (widthp)
+ *widthp = w;
+ set_char(g, f, env, w, 0);
+}
+
+font *printer::get_font_from_index(int fontno)
+{
+ if ((fontno >= 0) && (fontno < nfonts))
+ return font_table[fontno];
+ else
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/assert.cpp b/src/libs/libgroff/assert.cpp
new file mode 100644
index 0000000..9da3d46
--- /dev/null
+++ b/src/libs/libgroff/assert.cpp
@@ -0,0 +1,38 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+extern "C" const char *program_name;
+
+void assertion_failed(int lineno, const char *filename,
+ const char *function, const char *msg)
+{
+ if (program_name != 0)
+ fprintf(stderr, "%s: ", program_name);
+ fprintf(stderr, "%s:%d: %s(): assertion failed: '%s'\n", filename,
+ lineno, function, msg);
+ fflush(stderr);
+ abort();
+}
diff --git a/src/libs/libgroff/change_lf.cpp b/src/libs/libgroff/change_lf.cpp
new file mode 100644
index 0000000..eb98766
--- /dev/null
+++ b/src/libs/libgroff/change_lf.cpp
@@ -0,0 +1,37 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <string.h>
+
+extern char *strsave(const char *);
+
+extern const char *current_filename;
+extern int current_lineno;
+
+void change_filename(const char *f)
+{
+ if (current_filename != 0 && strcmp(current_filename, f) == 0)
+ return;
+ current_filename = strsave(f);
+}
+
+void change_lineno(int ln)
+{
+ current_lineno = ln;
+}
diff --git a/src/libs/libgroff/cmap.cpp b/src/libs/libgroff/cmap.cpp
new file mode 100644
index 0000000..3f2c0c3
--- /dev/null
+++ b/src/libs/libgroff/cmap.cpp
@@ -0,0 +1,56 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <ctype.h>
+#include "cmap.h"
+
+cmap cmlower(CMAP_BUILTIN);
+cmap cmupper(CMAP_BUILTIN);
+
+#ifdef isascii
+#define ISASCII(c) isascii(c)
+#else
+#define ISASCII(c) (1)
+#endif
+
+cmap::cmap()
+{
+ unsigned char *p = v;
+ for (int i = 0; i <= UCHAR_MAX; i++)
+ p[i] = i;
+}
+
+cmap::cmap(cmap_builtin)
+{
+ // these are initialised by cmap_init::cmap_init()
+}
+
+int cmap_init::initialised = 0;
+
+cmap_init::cmap_init()
+{
+ if (initialised)
+ return;
+ initialised = 1;
+ for (int i = 0; i <= UCHAR_MAX; i++) {
+ cmupper.v[i] = ISASCII(i) && islower(i) ? toupper(i) : i;
+ cmlower.v[i] = ISASCII(i) && isupper(i) ? tolower(i) : i;
+ }
+}
diff --git a/src/libs/libgroff/color.cpp b/src/libs/libgroff/color.cpp
new file mode 100644
index 0000000..388c2ee
--- /dev/null
+++ b/src/libs/libgroff/color.cpp
@@ -0,0 +1,404 @@
+/* Copyright (C) 2001-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley <gaius@glam.ac.uk>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "color.h"
+#include "cset.h"
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "errarg.h"
+#include "error.h"
+
+static inline unsigned int
+min(const unsigned int a, const unsigned int b)
+{
+ if (a < b)
+ return a;
+ else
+ return b;
+}
+
+
+color::color(const color * const c)
+{
+ nm = c->nm;
+ scheme = c->scheme;
+ components[0] = c->components[0];
+ components[1] = c->components[1];
+ components[2] = c->components[2];
+ components[3] = c->components[3];
+}
+
+color::~color()
+{
+}
+
+int color::operator==(const color & c) const
+{
+ if (scheme != c.scheme)
+ return 0;
+ switch (scheme) {
+ case DEFAULT:
+ break;
+ case RGB:
+ if (Red != c.Red || Green != c.Green || Blue != c.Blue)
+ return 0;
+ break;
+ case CMYK:
+ if (Cyan != c.Cyan || Magenta != c.Magenta
+ || Yellow != c.Yellow || Black != c.Black)
+ return 0;
+ break;
+ case GRAY:
+ if (Gray != c.Gray)
+ return 0;
+ break;
+ case CMY:
+ if (Cyan != c.Cyan || Magenta != c.Magenta || Yellow != c.Yellow)
+ return 0;
+ break;
+ }
+ return 1;
+}
+
+int color::operator!=(const color & c) const
+{
+ return !(*this == c);
+}
+
+color_scheme color::get_components(unsigned int *c) const
+{
+#if 0
+ if (sizeof (c) < sizeof (unsigned int) * 4)
+ fatal("argument is not big enough to store 4 color components");
+#endif
+ c[0] = components[0];
+ c[1] = components[1];
+ c[2] = components[2];
+ c[3] = components[3];
+ return scheme;
+}
+
+void color::set_default()
+{
+ scheme = DEFAULT;
+}
+
+// (0, 0, 0) is black
+
+void color::set_rgb(const unsigned int r, const unsigned int g,
+ const unsigned int b)
+{
+ scheme = RGB;
+ Red = min(MAX_COLOR_VAL, r);
+ Green = min(MAX_COLOR_VAL, g);
+ Blue = min(MAX_COLOR_VAL, b);
+}
+
+// (0, 0, 0) is white
+
+void color::set_cmy(const unsigned int c, const unsigned int m,
+ const unsigned int y)
+{
+ scheme = CMY;
+ Cyan = min(MAX_COLOR_VAL, c);
+ Magenta = min(MAX_COLOR_VAL, m);
+ Yellow = min(MAX_COLOR_VAL, y);
+}
+
+// (0, 0, 0, 0) is white
+
+void color::set_cmyk(const unsigned int c, const unsigned int m,
+ const unsigned int y, const unsigned int k)
+{
+ scheme = CMYK;
+ Cyan = min(MAX_COLOR_VAL, c);
+ Magenta = min(MAX_COLOR_VAL, m);
+ Yellow = min(MAX_COLOR_VAL, y);
+ Black = min(MAX_COLOR_VAL, k);
+}
+
+// (0) is black
+
+void color::set_gray(const unsigned int g)
+{
+ scheme = GRAY;
+ Gray = min(MAX_COLOR_VAL, g);
+}
+
+/*
+ * atoh - computes the decimal value of a hexadecimal number string.
+ * 'length' characters of 's' are read. Returns 1 if successful.
+ */
+
+static int atoh(unsigned int *result,
+ const char * const s, const size_t length)
+{
+ size_t i = 0;
+ unsigned int val = 0;
+ while ((i < length) && csxdigit(s[i])) {
+ if (csdigit(s[i]))
+ val = val*0x10 + (s[i]-'0');
+ else if (csupper(s[i]))
+ val = val*0x10 + (s[i]-'A') + 10;
+ else
+ val = val*0x10 + (s[i]-'a') + 10;
+ i++;
+ }
+ if (i != length)
+ return 0;
+ *result = val;
+ return 1;
+}
+
+/*
+ * read_encoding - set color from a hexadecimal color string.
+ *
+ * Use color scheme 'cs' to parse 'n' color components from string 's'.
+ * Returns 1 if successful.
+ */
+
+int color::read_encoding(const color_scheme cs, const char * const s,
+ const size_t n)
+{
+ size_t hex_length = 2;
+ scheme = cs;
+ char *p = (char *) s;
+ p++;
+ if (*p == '#') {
+ hex_length = 4;
+ p++;
+ }
+ for (size_t i = 0; i < n; i++) {
+ if (!atoh(&(components[i]), p, hex_length))
+ return 0;
+ if (hex_length == 2)
+ components[i] *= 0x101; // scale up -- 0xff should become 0xffff
+ p += hex_length;
+ }
+ return 1;
+}
+
+int color::read_rgb(const char * const s)
+{
+ return read_encoding(RGB, s, 3);
+}
+
+int color::read_cmy(const char * const s)
+{
+ return read_encoding(CMY, s, 3);
+}
+
+int color::read_cmyk(const char * const s)
+{
+ return read_encoding(CMYK, s, 4);
+}
+
+int color::read_gray(const char * const s)
+{
+ return read_encoding(GRAY, s, 1);
+}
+
+void
+color::get_rgb(unsigned int *r, unsigned int *g, unsigned int *b) const
+{
+ switch (scheme) {
+ case RGB:
+ *r = Red;
+ *g = Green;
+ *b = Blue;
+ break;
+ case CMY:
+ *r = MAX_COLOR_VAL - Cyan;
+ *g = MAX_COLOR_VAL - Magenta;
+ *b = MAX_COLOR_VAL - Yellow;
+ break;
+ case CMYK:
+ *r = MAX_COLOR_VAL
+ - min(MAX_COLOR_VAL,
+ Cyan * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
+ *g = MAX_COLOR_VAL
+ - min(MAX_COLOR_VAL,
+ Magenta * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
+ *b = MAX_COLOR_VAL
+ - min(MAX_COLOR_VAL,
+ Yellow * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
+ break;
+ case GRAY:
+ *r = *g = *b = Gray;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
+
+void
+color::get_cmy(unsigned int *c, unsigned int *m, unsigned int *y) const
+{
+ switch (scheme) {
+ case RGB:
+ *c = MAX_COLOR_VAL - Red;
+ *m = MAX_COLOR_VAL - Green;
+ *y = MAX_COLOR_VAL - Blue;
+ break;
+ case CMY:
+ *c = Cyan;
+ *m = Magenta;
+ *y = Yellow;
+ break;
+ case CMYK:
+ *c = min(MAX_COLOR_VAL,
+ Cyan * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
+ *m = min(MAX_COLOR_VAL,
+ Magenta * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
+ *y = min(MAX_COLOR_VAL,
+ Yellow * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
+ break;
+ case GRAY:
+ *c = *m = *y = MAX_COLOR_VAL - Gray;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
+
+void color::get_cmyk(unsigned int *c, unsigned int *m,
+ unsigned int *y, unsigned int *k) const
+{
+ switch (scheme) {
+ case RGB:
+ *k = min(MAX_COLOR_VAL - Red,
+ min(MAX_COLOR_VAL - Green, MAX_COLOR_VAL - Blue));
+ if (MAX_COLOR_VAL == *k) {
+ *c = MAX_COLOR_VAL;
+ *m = MAX_COLOR_VAL;
+ *y = MAX_COLOR_VAL;
+ }
+ else {
+ *c = (MAX_COLOR_VAL * (MAX_COLOR_VAL - Red - *k))
+ / (MAX_COLOR_VAL - *k);
+ *m = (MAX_COLOR_VAL * (MAX_COLOR_VAL - Green - *k))
+ / (MAX_COLOR_VAL - *k);
+ *y = (MAX_COLOR_VAL * (MAX_COLOR_VAL - Blue - *k))
+ / (MAX_COLOR_VAL - *k);
+ }
+ break;
+ case CMY:
+ *k = min(Cyan, min(Magenta, Yellow));
+ if (MAX_COLOR_VAL == *k) {
+ *c = MAX_COLOR_VAL;
+ *m = MAX_COLOR_VAL;
+ *y = MAX_COLOR_VAL;
+ }
+ else {
+ *c = (MAX_COLOR_VAL * (Cyan - *k)) / (MAX_COLOR_VAL - *k);
+ *m = (MAX_COLOR_VAL * (Magenta - *k)) / (MAX_COLOR_VAL - *k);
+ *y = (MAX_COLOR_VAL * (Yellow - *k)) / (MAX_COLOR_VAL - *k);
+ }
+ break;
+ case CMYK:
+ *c = Cyan;
+ *m = Magenta;
+ *y = Yellow;
+ *k = Black;
+ break;
+ case GRAY:
+ *c = *m = *y = 0;
+ *k = MAX_COLOR_VAL - Gray;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
+
+// we use '0.222r + 0.707g + 0.071b' (this is the ITU standard)
+// as an approximation for gray
+
+void color::get_gray(unsigned int *g) const
+{
+ switch (scheme) {
+ case RGB:
+ *g = (222*Red + 707*Green + 71*Blue) / 1000;
+ break;
+ case CMY:
+ *g = MAX_COLOR_VAL - (222*Cyan + 707*Magenta + 71*Yellow) / 1000;
+ break;
+ case CMYK:
+ *g = (MAX_COLOR_VAL - (222*Cyan + 707*Magenta + 71*Yellow) / 1000)
+ * (MAX_COLOR_VAL - Black);
+ break;
+ case GRAY:
+ *g = Gray;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
+
+char *color::print_color()
+{
+ char *s = new char[30];
+ switch (scheme) {
+ case DEFAULT:
+ sprintf(s, "default");
+ break;
+ case RGB:
+ sprintf(s, "rgb %.2ff %.2ff %.2ff",
+ double(Red) / double(MAX_COLOR_VAL),
+ double(Green) / double(MAX_COLOR_VAL),
+ double(Blue) / double(MAX_COLOR_VAL));
+ break;
+ case CMY:
+ sprintf(s, "cmy %.2ff %.2ff %.2ff",
+ double(Cyan) / double(MAX_COLOR_VAL),
+ double(Magenta) / double(MAX_COLOR_VAL),
+ double(Yellow) / double(MAX_COLOR_VAL));
+ break;
+ case CMYK:
+ sprintf(s, "cmyk %.2ff %.2ff %.2ff %.2ff",
+ double(Cyan) / double(MAX_COLOR_VAL),
+ double(Magenta) / double(MAX_COLOR_VAL),
+ double(Yellow) / double(MAX_COLOR_VAL),
+ double(Black) / double(MAX_COLOR_VAL));
+ break;
+ case GRAY:
+ sprintf(s, "gray %.2ff",
+ double(Gray) / double(MAX_COLOR_VAL));
+ break;
+ }
+ return s;
+}
+
+color default_color;
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/config.charset b/src/libs/libgroff/config.charset
new file mode 100755
index 0000000..8fca485
--- /dev/null
+++ b/src/libs/libgroff/config.charset
@@ -0,0 +1,684 @@
+#! /bin/sh
+# Output a system dependent table of character encoding aliases.
+#
+# Copyright (C) 2000-2020 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# The table consists of lines of the form
+# ALIAS CANONICAL
+#
+# ALIAS is the (system dependent) result of "nl_langinfo (CODESET)".
+# ALIAS is compared in a case sensitive way.
+#
+# CANONICAL is the GNU canonical name for this character encoding.
+# It must be an encoding supported by libiconv. Support by GNU libc is
+# also desirable. CANONICAL is case insensitive. Usually an upper case
+# MIME charset name is preferred.
+# The current list of GNU canonical charset names is as follows.
+#
+# name MIME? used by which systems
+# (darwin = Mac OS X, woe32 = native Windows)
+#
+# ASCII, ANSI_X3.4-1968 glibc solaris freebsd netbsd darwin cygwin
+# ISO-8859-1 Y glibc aix hpux irix osf solaris freebsd netbsd openbsd darwin cygwin
+# ISO-8859-2 Y glibc aix hpux irix osf solaris freebsd netbsd openbsd darwin cygwin
+# ISO-8859-3 Y glibc solaris cygwin
+# ISO-8859-4 Y osf solaris freebsd netbsd openbsd darwin
+# ISO-8859-5 Y glibc aix hpux irix osf solaris freebsd netbsd openbsd darwin cygwin
+# ISO-8859-6 Y glibc aix hpux solaris cygwin
+# ISO-8859-7 Y glibc aix hpux irix osf solaris netbsd openbsd darwin cygwin
+# ISO-8859-8 Y glibc aix hpux osf solaris cygwin
+# ISO-8859-9 Y glibc aix hpux irix osf solaris darwin cygwin
+# ISO-8859-13 glibc netbsd openbsd darwin cygwin
+# ISO-8859-14 glibc cygwin
+# ISO-8859-15 glibc aix osf solaris freebsd netbsd openbsd darwin cygwin
+# KOI8-R Y glibc solaris freebsd netbsd openbsd darwin
+# KOI8-U Y glibc freebsd netbsd openbsd darwin cygwin
+# KOI8-T glibc
+# CP437 dos
+# CP775 dos
+# CP850 aix osf dos
+# CP852 dos
+# CP855 dos
+# CP856 aix
+# CP857 dos
+# CP861 dos
+# CP862 dos
+# CP864 dos
+# CP865 dos
+# CP866 freebsd netbsd openbsd darwin dos
+# CP869 dos
+# CP874 woe32 dos
+# CP922 aix
+# CP932 aix cygwin woe32 dos
+# CP943 aix
+# CP949 osf darwin woe32 dos
+# CP950 woe32 dos
+# CP1046 aix
+# CP1124 aix
+# CP1125 dos
+# CP1129 aix
+# CP1131 darwin
+# CP1250 woe32
+# CP1251 glibc solaris netbsd openbsd darwin cygwin woe32
+# CP1252 aix woe32
+# CP1253 woe32
+# CP1254 woe32
+# CP1255 glibc woe32
+# CP1256 woe32
+# CP1257 woe32
+# GB2312 Y glibc aix hpux irix solaris freebsd netbsd darwin
+# EUC-JP Y glibc aix hpux irix osf solaris freebsd netbsd darwin
+# EUC-KR Y glibc aix hpux irix osf solaris freebsd netbsd darwin cygwin
+# EUC-TW glibc aix hpux irix osf solaris netbsd
+# BIG5 Y glibc aix hpux osf solaris freebsd netbsd darwin cygwin
+# BIG5-HKSCS glibc solaris darwin
+# GBK glibc aix osf solaris darwin cygwin woe32 dos
+# GB18030 glibc solaris netbsd darwin
+# SHIFT_JIS Y hpux osf solaris freebsd netbsd darwin
+# JOHAB glibc solaris woe32
+# TIS-620 glibc aix hpux osf solaris cygwin
+# VISCII Y glibc
+# TCVN5712-1 glibc
+# ARMSCII-8 glibc darwin
+# GEORGIAN-PS glibc cygwin
+# PT154 glibc
+# HP-ROMAN8 hpux
+# HP-ARABIC8 hpux
+# HP-GREEK8 hpux
+# HP-HEBREW8 hpux
+# HP-TURKISH8 hpux
+# HP-KANA8 hpux
+# DEC-KANJI osf
+# DEC-HANYU osf
+# UTF-8 Y glibc aix hpux osf solaris netbsd darwin cygwin
+#
+# Note: Names which are not marked as being a MIME name should not be used in
+# Internet protocols for information interchange (mail, news, etc.).
+#
+# Note: ASCII and ANSI_X3.4-1968 are synonymous canonical names. Applications
+# must understand both names and treat them as equivalent.
+#
+# The first argument passed to this file is the canonical host specification,
+# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or
+# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+
+host="$1"
+os=`echo "$host" | sed -e 's/^[^-]*-[^-]*-\(.*\)$/\1/'`
+echo "# This file contains a table of character encoding aliases,"
+echo "# suitable for operating system '${os}'."
+echo "# It was automatically generated from config.charset."
+# List of references, updated during installation:
+echo "# Packages using this file: "
+case "$os" in
+ linux-gnulibc1*)
+ # Linux libc5 doesn't have nl_langinfo(CODESET); therefore
+ # localcharset.c falls back to using the full locale name
+ # from the environment variables.
+ echo "C ASCII"
+ echo "POSIX ASCII"
+ for l in af af_ZA ca ca_ES da da_DK de de_AT de_BE de_CH de_DE de_LU \
+ en en_AU en_BW en_CA en_DK en_GB en_IE en_NZ en_US en_ZA \
+ en_ZW es es_AR es_BO es_CL es_CO es_DO es_EC es_ES es_GT \
+ es_HN es_MX es_PA es_PE es_PY es_SV es_US es_UY es_VE et \
+ et_EE eu eu_ES fi fi_FI fo fo_FO fr fr_BE fr_CA fr_CH fr_FR \
+ fr_LU ga ga_IE gl gl_ES id id_ID in in_ID is is_IS it it_CH \
+ it_IT kl kl_GL nl nl_BE nl_NL no no_NO pt pt_BR pt_PT sv \
+ sv_FI sv_SE; do
+ echo "$l ISO-8859-1"
+ echo "$l.iso-8859-1 ISO-8859-1"
+ echo "$l.iso-8859-15 ISO-8859-15"
+ echo "$l.iso-8859-15@euro ISO-8859-15"
+ echo "$l@euro ISO-8859-15"
+ echo "$l.cp-437 CP437"
+ echo "$l.cp-850 CP850"
+ echo "$l.cp-1252 CP1252"
+ echo "$l.cp-1252@euro CP1252"
+ #echo "$l.atari-st ATARI-ST" # not a commonly used encoding
+ echo "$l.utf-8 UTF-8"
+ echo "$l.utf-8@euro UTF-8"
+ done
+ for l in cs cs_CZ hr hr_HR hu hu_HU pl pl_PL ro ro_RO sk sk_SK sl \
+ sl_SI sr sr_CS sr_YU; do
+ echo "$l ISO-8859-2"
+ echo "$l.iso-8859-2 ISO-8859-2"
+ echo "$l.cp-852 CP852"
+ echo "$l.cp-1250 CP1250"
+ echo "$l.utf-8 UTF-8"
+ done
+ for l in mk mk_MK ru ru_RU; do
+ echo "$l ISO-8859-5"
+ echo "$l.iso-8859-5 ISO-8859-5"
+ echo "$l.koi8-r KOI8-R"
+ echo "$l.cp-866 CP866"
+ echo "$l.cp-1251 CP1251"
+ echo "$l.utf-8 UTF-8"
+ done
+ for l in ar ar_SA; do
+ echo "$l ISO-8859-6"
+ echo "$l.iso-8859-6 ISO-8859-6"
+ echo "$l.cp-864 CP864"
+ #echo "$l.cp-868 CP868" # not a commonly used encoding
+ echo "$l.cp-1256 CP1256"
+ echo "$l.utf-8 UTF-8"
+ done
+ for l in el el_GR gr gr_GR; do
+ echo "$l ISO-8859-7"
+ echo "$l.iso-8859-7 ISO-8859-7"
+ echo "$l.cp-869 CP869"
+ echo "$l.cp-1253 CP1253"
+ echo "$l.cp-1253@euro CP1253"
+ echo "$l.utf-8 UTF-8"
+ echo "$l.utf-8@euro UTF-8"
+ done
+ for l in he he_IL iw iw_IL; do
+ echo "$l ISO-8859-8"
+ echo "$l.iso-8859-8 ISO-8859-8"
+ echo "$l.cp-862 CP862"
+ echo "$l.cp-1255 CP1255"
+ echo "$l.utf-8 UTF-8"
+ done
+ for l in tr tr_TR; do
+ echo "$l ISO-8859-9"
+ echo "$l.iso-8859-9 ISO-8859-9"
+ echo "$l.cp-857 CP857"
+ echo "$l.cp-1254 CP1254"
+ echo "$l.utf-8 UTF-8"
+ done
+ for l in lt lt_LT lv lv_LV; do
+ #echo "$l BALTIC" # not a commonly used encoding, wrong encoding name
+ echo "$l ISO-8859-13"
+ done
+ for l in ru_UA uk uk_UA; do
+ echo "$l KOI8-U"
+ done
+ for l in zh zh_CN; do
+ #echo "$l GB_2312-80" # not a commonly used encoding, wrong encoding name
+ echo "$l GB2312"
+ done
+ for l in ja ja_JP ja_JP.EUC; do
+ echo "$l EUC-JP"
+ done
+ for l in ko ko_KR; do
+ echo "$l EUC-KR"
+ done
+ for l in th th_TH; do
+ echo "$l TIS-620"
+ done
+ for l in fa fa_IR; do
+ #echo "$l ISIRI-3342" # a broken encoding
+ echo "$l.utf-8 UTF-8"
+ done
+ ;;
+ linux* | *-gnu*)
+ # With glibc-2.1 or newer, we don't need any canonicalization,
+ # because glibc has iconv and both glibc and libiconv support all
+ # GNU canonical names directly. Therefore, the Makefile does not
+ # need to install the alias file at all.
+ # The following applies only to glibc-2.0.x and older libcs.
+ echo "ISO_646.IRV:1983 ASCII"
+ ;;
+ aix*)
+ echo "ISO8859-1 ISO-8859-1"
+ echo "ISO8859-2 ISO-8859-2"
+ echo "ISO8859-5 ISO-8859-5"
+ echo "ISO8859-6 ISO-8859-6"
+ echo "ISO8859-7 ISO-8859-7"
+ echo "ISO8859-8 ISO-8859-8"
+ echo "ISO8859-9 ISO-8859-9"
+ echo "ISO8859-15 ISO-8859-15"
+ echo "IBM-850 CP850"
+ echo "IBM-856 CP856"
+ echo "IBM-921 ISO-8859-13"
+ echo "IBM-922 CP922"
+ echo "IBM-932 CP932"
+ echo "IBM-943 CP943"
+ echo "IBM-1046 CP1046"
+ echo "IBM-1124 CP1124"
+ echo "IBM-1129 CP1129"
+ echo "IBM-1252 CP1252"
+ echo "IBM-eucCN GB2312"
+ echo "IBM-eucJP EUC-JP"
+ echo "IBM-eucKR EUC-KR"
+ echo "IBM-eucTW EUC-TW"
+ echo "big5 BIG5"
+ echo "GBK GBK"
+ echo "TIS-620 TIS-620"
+ echo "UTF-8 UTF-8"
+ ;;
+ hpux*)
+ echo "iso88591 ISO-8859-1"
+ echo "iso88592 ISO-8859-2"
+ echo "iso88595 ISO-8859-5"
+ echo "iso88596 ISO-8859-6"
+ echo "iso88597 ISO-8859-7"
+ echo "iso88598 ISO-8859-8"
+ echo "iso88599 ISO-8859-9"
+ echo "iso885915 ISO-8859-15"
+ echo "roman8 HP-ROMAN8"
+ echo "arabic8 HP-ARABIC8"
+ echo "greek8 HP-GREEK8"
+ echo "hebrew8 HP-HEBREW8"
+ echo "turkish8 HP-TURKISH8"
+ echo "kana8 HP-KANA8"
+ echo "tis620 TIS-620"
+ echo "big5 BIG5"
+ echo "eucJP EUC-JP"
+ echo "eucKR EUC-KR"
+ echo "eucTW EUC-TW"
+ echo "hp15CN GB2312"
+ #echo "ccdc ?" # what is this?
+ echo "SJIS SHIFT_JIS"
+ echo "utf8 UTF-8"
+ ;;
+ irix*)
+ echo "ISO8859-1 ISO-8859-1"
+ echo "ISO8859-2 ISO-8859-2"
+ echo "ISO8859-5 ISO-8859-5"
+ echo "ISO8859-7 ISO-8859-7"
+ echo "ISO8859-9 ISO-8859-9"
+ echo "eucCN GB2312"
+ echo "eucJP EUC-JP"
+ echo "eucKR EUC-KR"
+ echo "eucTW EUC-TW"
+ ;;
+ osf*)
+ echo "ISO8859-1 ISO-8859-1"
+ echo "ISO8859-2 ISO-8859-2"
+ echo "ISO8859-4 ISO-8859-4"
+ echo "ISO8859-5 ISO-8859-5"
+ echo "ISO8859-7 ISO-8859-7"
+ echo "ISO8859-8 ISO-8859-8"
+ echo "ISO8859-9 ISO-8859-9"
+ echo "ISO8859-15 ISO-8859-15"
+ echo "cp850 CP850"
+ echo "big5 BIG5"
+ echo "dechanyu DEC-HANYU"
+ echo "dechanzi GB2312"
+ echo "deckanji DEC-KANJI"
+ echo "deckorean EUC-KR"
+ echo "eucJP EUC-JP"
+ echo "eucKR EUC-KR"
+ echo "eucTW EUC-TW"
+ echo "GBK GBK"
+ echo "KSC5601 CP949"
+ echo "sdeckanji EUC-JP"
+ echo "SJIS SHIFT_JIS"
+ echo "TACTIS TIS-620"
+ echo "UTF-8 UTF-8"
+ ;;
+ solaris*)
+ echo "646 ASCII"
+ echo "ISO8859-1 ISO-8859-1"
+ echo "ISO8859-2 ISO-8859-2"
+ echo "ISO8859-3 ISO-8859-3"
+ echo "ISO8859-4 ISO-8859-4"
+ echo "ISO8859-5 ISO-8859-5"
+ echo "ISO8859-6 ISO-8859-6"
+ echo "ISO8859-7 ISO-8859-7"
+ echo "ISO8859-8 ISO-8859-8"
+ echo "ISO8859-9 ISO-8859-9"
+ echo "ISO8859-15 ISO-8859-15"
+ echo "koi8-r KOI8-R"
+ echo "ansi-1251 CP1251"
+ echo "BIG5 BIG5"
+ echo "Big5-HKSCS BIG5-HKSCS"
+ echo "gb2312 GB2312"
+ echo "GBK GBK"
+ echo "GB18030 GB18030"
+ echo "cns11643 EUC-TW"
+ echo "5601 EUC-KR"
+ echo "ko_KR.johap92 JOHAB"
+ echo "eucJP EUC-JP"
+ echo "PCK SHIFT_JIS"
+ echo "TIS620.2533 TIS-620"
+ #echo "sun_eu_greek ?" # what is this?
+ echo "UTF-8 UTF-8"
+ ;;
+ freebsd* | os2*)
+ # FreeBSD 4.2 doesn't have nl_langinfo(CODESET); therefore
+ # localcharset.c falls back to using the full locale name
+ # from the environment variables.
+ # Likewise for OS/2. OS/2 has XFree86 just like FreeBSD. Just
+ # reuse FreeBSD's locale data for OS/2.
+ echo "C ASCII"
+ echo "US-ASCII ASCII"
+ for l in la_LN lt_LN; do
+ echo "$l.ASCII ASCII"
+ done
+ for l in da_DK de_AT de_CH de_DE en_AU en_CA en_GB en_US es_ES \
+ fi_FI fr_BE fr_CA fr_CH fr_FR is_IS it_CH it_IT la_LN \
+ lt_LN nl_BE nl_NL no_NO pt_PT sv_SE; do
+ echo "$l.ISO_8859-1 ISO-8859-1"
+ echo "$l.DIS_8859-15 ISO-8859-15"
+ done
+ for l in cs_CZ hr_HR hu_HU la_LN lt_LN pl_PL sl_SI; do
+ echo "$l.ISO_8859-2 ISO-8859-2"
+ done
+ for l in la_LN lt_LT; do
+ echo "$l.ISO_8859-4 ISO-8859-4"
+ done
+ for l in ru_RU ru_SU; do
+ echo "$l.KOI8-R KOI8-R"
+ echo "$l.ISO_8859-5 ISO-8859-5"
+ echo "$l.CP866 CP866"
+ done
+ echo "uk_UA.KOI8-U KOI8-U"
+ echo "zh_TW.BIG5 BIG5"
+ echo "zh_TW.Big5 BIG5"
+ echo "zh_CN.EUC GB2312"
+ echo "ja_JP.EUC EUC-JP"
+ echo "ja_JP.SJIS SHIFT_JIS"
+ echo "ja_JP.Shift_JIS SHIFT_JIS"
+ echo "ko_KR.EUC EUC-KR"
+ ;;
+ netbsd*)
+ echo "646 ASCII"
+ echo "ISO8859-1 ISO-8859-1"
+ echo "ISO8859-2 ISO-8859-2"
+ echo "ISO8859-4 ISO-8859-4"
+ echo "ISO8859-5 ISO-8859-5"
+ echo "ISO8859-7 ISO-8859-7"
+ echo "ISO8859-13 ISO-8859-13"
+ echo "ISO8859-15 ISO-8859-15"
+ echo "eucCN GB2312"
+ echo "eucJP EUC-JP"
+ echo "eucKR EUC-KR"
+ echo "eucTW EUC-TW"
+ echo "BIG5 BIG5"
+ echo "SJIS SHIFT_JIS"
+ ;;
+ openbsd*)
+ echo "646 ASCII"
+ echo "ISO8859-1 ISO-8859-1"
+ echo "ISO8859-2 ISO-8859-2"
+ echo "ISO8859-4 ISO-8859-4"
+ echo "ISO8859-5 ISO-8859-5"
+ echo "ISO8859-7 ISO-8859-7"
+ echo "ISO8859-13 ISO-8859-13"
+ echo "ISO8859-15 ISO-8859-15"
+ ;;
+ darwin[56]*)
+ # Darwin 6.8 doesn't have nl_langinfo(CODESET); therefore
+ # localcharset.c falls back to using the full locale name
+ # from the environment variables.
+ echo "C ASCII"
+ for l in en_AU en_CA en_GB en_US la_LN; do
+ echo "$l.US-ASCII ASCII"
+ done
+ for l in da_DK de_AT de_CH de_DE en_AU en_CA en_GB en_US es_ES \
+ fi_FI fr_BE fr_CA fr_CH fr_FR is_IS it_CH it_IT nl_BE \
+ nl_NL no_NO pt_PT sv_SE; do
+ echo "$l ISO-8859-1"
+ echo "$l.ISO8859-1 ISO-8859-1"
+ echo "$l.ISO8859-15 ISO-8859-15"
+ done
+ for l in la_LN; do
+ echo "$l.ISO8859-1 ISO-8859-1"
+ echo "$l.ISO8859-15 ISO-8859-15"
+ done
+ for l in cs_CZ hr_HR hu_HU la_LN pl_PL sl_SI; do
+ echo "$l.ISO8859-2 ISO-8859-2"
+ done
+ for l in la_LN lt_LT; do
+ echo "$l.ISO8859-4 ISO-8859-4"
+ done
+ for l in ru_RU; do
+ echo "$l.KOI8-R KOI8-R"
+ echo "$l.ISO8859-5 ISO-8859-5"
+ echo "$l.CP866 CP866"
+ done
+ for l in bg_BG; do
+ echo "$l.CP1251 CP1251"
+ done
+ echo "uk_UA.KOI8-U KOI8-U"
+ echo "zh_TW.BIG5 BIG5"
+ echo "zh_TW.Big5 BIG5"
+ echo "zh_CN.EUC GB2312"
+ echo "ja_JP.EUC EUC-JP"
+ echo "ja_JP.SJIS SHIFT_JIS"
+ echo "ko_KR.EUC EUC-KR"
+ ;;
+ darwin*)
+ # Darwin 7.5 has nl_langinfo(CODESET), but sometimes its value is
+ # useless:
+ # - It returns the empty string when LANG is set to a locale of the
+ # form ll_CC, although ll_CC/LC_CTYPE is a symlink to an UTF-8
+ # LC_CTYPE file.
+ # - The environment variables LANG, LC_CTYPE, LC_ALL are not set by
+ # the system; nl_langinfo(CODESET) returns "US-ASCII" in this case.
+ # - The documentation says:
+ # "... all code that calls BSD system routines should ensure
+ # that the const *char parameters of these routines are in UTF-8
+ # encoding. All BSD system functions expect their string
+ # parameters to be in UTF-8 encoding and nothing else."
+ # It also says
+ # "An additional caveat is that string parameters for files,
+ # paths, and other file-system entities must be in canonical
+ # UTF-8. In a canonical UTF-8 Unicode string, all decomposable
+ # characters are decomposed ..."
+ # but this is not true: You can pass non-decomposed UTF-8 strings
+ # to file system functions, and it is the OS which will convert
+ # them to decomposed UTF-8 before accessing the file system.
+ # - The Apple Terminal application displays UTF-8 by default.
+ # - However, other applications are free to use different encodings:
+ # - xterm uses ISO-8859-1 by default.
+ # - TextEdit uses MacRoman by default.
+ # We prefer UTF-8 over decomposed UTF-8-MAC because one should
+ # minimize the use of decomposed Unicode. Unfortunately, through the
+ # Darwin file system, decomposed UTF-8 strings are leaked into user
+ # space nevertheless.
+ # Then there are also the locales with encodings other than US-ASCII
+ # and UTF-8. These locales can be occasionally useful to users (e.g.
+ # when grepping through ISO-8859-1 encoded text files), when all their
+ # file names are in US-ASCII.
+ echo "ISO8859-1 ISO-8859-1"
+ echo "ISO8859-2 ISO-8859-2"
+ echo "ISO8859-4 ISO-8859-4"
+ echo "ISO8859-5 ISO-8859-5"
+ echo "ISO8859-7 ISO-8859-7"
+ echo "ISO8859-9 ISO-8859-9"
+ echo "ISO8859-13 ISO-8859-13"
+ echo "ISO8859-15 ISO-8859-15"
+ echo "KOI8-R KOI8-R"
+ echo "KOI8-U KOI8-U"
+ echo "CP866 CP866"
+ echo "CP949 CP949"
+ echo "CP1131 CP1131"
+ echo "CP1251 CP1251"
+ echo "eucCN GB2312"
+ echo "GB2312 GB2312"
+ echo "eucJP EUC-JP"
+ echo "eucKR EUC-KR"
+ echo "Big5 BIG5"
+ echo "Big5HKSCS BIG5-HKSCS"
+ echo "GBK GBK"
+ echo "GB18030 GB18030"
+ echo "SJIS SHIFT_JIS"
+ echo "ARMSCII-8 ARMSCII-8"
+ echo "PT154 PT154"
+ #echo "ISCII-DEV ?"
+ echo "* UTF-8"
+ ;;
+ beos* | haiku*)
+ # BeOS and Haiku have a single locale, and it has UTF-8 encoding.
+ echo "* UTF-8"
+ ;;
+ msdosdjgpp*)
+ # DJGPP 2.03 doesn't have nl_langinfo(CODESET); therefore
+ # localcharset.c falls back to using the full locale name
+ # from the environment variables.
+ echo "#"
+ echo "# The encodings given here may not all be correct."
+ echo "# If you find that the encoding given for your language and"
+ echo "# country is not the one your DOS machine actually uses, just"
+ echo "# correct it in this file, and send a mail to"
+ echo "# Juan Manuel Guerrero <juan.guerrero@gmx.de>"
+ echo "# and Bruno Haible <bruno@clisp.org>."
+ echo "#"
+ echo "C ASCII"
+ # ISO-8859-1 languages
+ echo "ca CP850"
+ echo "ca_ES CP850"
+ echo "da CP865" # not CP850 ??
+ echo "da_DK CP865" # not CP850 ??
+ echo "de CP850"
+ echo "de_AT CP850"
+ echo "de_CH CP850"
+ echo "de_DE CP850"
+ echo "en CP850"
+ echo "en_AU CP850" # not CP437 ??
+ echo "en_CA CP850"
+ echo "en_GB CP850"
+ echo "en_NZ CP437"
+ echo "en_US CP437"
+ echo "en_ZA CP850" # not CP437 ??
+ echo "es CP850"
+ echo "es_AR CP850"
+ echo "es_BO CP850"
+ echo "es_CL CP850"
+ echo "es_CO CP850"
+ echo "es_CR CP850"
+ echo "es_CU CP850"
+ echo "es_DO CP850"
+ echo "es_EC CP850"
+ echo "es_ES CP850"
+ echo "es_GT CP850"
+ echo "es_HN CP850"
+ echo "es_MX CP850"
+ echo "es_NI CP850"
+ echo "es_PA CP850"
+ echo "es_PY CP850"
+ echo "es_PE CP850"
+ echo "es_SV CP850"
+ echo "es_UY CP850"
+ echo "es_VE CP850"
+ echo "et CP850"
+ echo "et_EE CP850"
+ echo "eu CP850"
+ echo "eu_ES CP850"
+ echo "fi CP850"
+ echo "fi_FI CP850"
+ echo "fr CP850"
+ echo "fr_BE CP850"
+ echo "fr_CA CP850"
+ echo "fr_CH CP850"
+ echo "fr_FR CP850"
+ echo "ga CP850"
+ echo "ga_IE CP850"
+ echo "gd CP850"
+ echo "gd_GB CP850"
+ echo "gl CP850"
+ echo "gl_ES CP850"
+ echo "id CP850" # not CP437 ??
+ echo "id_ID CP850" # not CP437 ??
+ echo "is CP861" # not CP850 ??
+ echo "is_IS CP861" # not CP850 ??
+ echo "it CP850"
+ echo "it_CH CP850"
+ echo "it_IT CP850"
+ echo "lt CP775"
+ echo "lt_LT CP775"
+ echo "lv CP775"
+ echo "lv_LV CP775"
+ echo "nb CP865" # not CP850 ??
+ echo "nb_NO CP865" # not CP850 ??
+ echo "nl CP850"
+ echo "nl_BE CP850"
+ echo "nl_NL CP850"
+ echo "nn CP865" # not CP850 ??
+ echo "nn_NO CP865" # not CP850 ??
+ echo "no CP865" # not CP850 ??
+ echo "no_NO CP865" # not CP850 ??
+ echo "pt CP850"
+ echo "pt_BR CP850"
+ echo "pt_PT CP850"
+ echo "sv CP850"
+ echo "sv_SE CP850"
+ # ISO-8859-2 languages
+ echo "cs CP852"
+ echo "cs_CZ CP852"
+ echo "hr CP852"
+ echo "hr_HR CP852"
+ echo "hu CP852"
+ echo "hu_HU CP852"
+ echo "pl CP852"
+ echo "pl_PL CP852"
+ echo "ro CP852"
+ echo "ro_RO CP852"
+ echo "sk CP852"
+ echo "sk_SK CP852"
+ echo "sl CP852"
+ echo "sl_SI CP852"
+ echo "sq CP852"
+ echo "sq_AL CP852"
+ echo "sr CP852" # CP852 or CP866 or CP855 ??
+ echo "sr_CS CP852" # CP852 or CP866 or CP855 ??
+ echo "sr_YU CP852" # CP852 or CP866 or CP855 ??
+ # ISO-8859-3 languages
+ echo "mt CP850"
+ echo "mt_MT CP850"
+ # ISO-8859-5 languages
+ echo "be CP866"
+ echo "be_BE CP866"
+ echo "bg CP866" # not CP855 ??
+ echo "bg_BG CP866" # not CP855 ??
+ echo "mk CP866" # not CP855 ??
+ echo "mk_MK CP866" # not CP855 ??
+ echo "ru CP866"
+ echo "ru_RU CP866"
+ echo "uk CP1125"
+ echo "uk_UA CP1125"
+ # ISO-8859-6 languages
+ echo "ar CP864"
+ echo "ar_AE CP864"
+ echo "ar_DZ CP864"
+ echo "ar_EG CP864"
+ echo "ar_IQ CP864"
+ echo "ar_IR CP864"
+ echo "ar_JO CP864"
+ echo "ar_KW CP864"
+ echo "ar_MA CP864"
+ echo "ar_OM CP864"
+ echo "ar_QA CP864"
+ echo "ar_SA CP864"
+ echo "ar_SY CP864"
+ # ISO-8859-7 languages
+ echo "el CP869"
+ echo "el_GR CP869"
+ # ISO-8859-8 languages
+ echo "he CP862"
+ echo "he_IL CP862"
+ # ISO-8859-9 languages
+ echo "tr CP857"
+ echo "tr_TR CP857"
+ # Japanese
+ echo "ja CP932"
+ echo "ja_JP CP932"
+ # Chinese
+ echo "zh_CN GBK"
+ echo "zh_TW CP950" # not CP938 ??
+ # Korean
+ echo "kr CP949" # not CP934 ??
+ echo "kr_KR CP949" # not CP934 ??
+ # Thai
+ echo "th CP874"
+ echo "th_TH CP874"
+ # Other
+ echo "eo CP850"
+ echo "eo_EO CP850"
+ ;;
+esac
diff --git a/src/libs/libgroff/cset.cpp b/src/libs/libgroff/cset.cpp
new file mode 100644
index 0000000..6702237
--- /dev/null
+++ b/src/libs/libgroff/cset.cpp
@@ -0,0 +1,104 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <ctype.h>
+
+#include "lib.h"
+#include "cset.h"
+
+cset csalpha(CSET_BUILTIN);
+cset csupper(CSET_BUILTIN);
+cset cslower(CSET_BUILTIN);
+cset csdigit(CSET_BUILTIN);
+cset csxdigit(CSET_BUILTIN);
+cset csspace(CSET_BUILTIN);
+cset cspunct(CSET_BUILTIN);
+cset csalnum(CSET_BUILTIN);
+cset csprint(CSET_BUILTIN);
+cset csgraph(CSET_BUILTIN);
+cset cscntrl(CSET_BUILTIN);
+
+#ifdef isascii
+#define ISASCII(c) isascii(c)
+#else
+#define ISASCII(c) (1)
+#endif
+
+void cset::clear()
+{
+ char *p = v;
+ for (int i = 0; i <= UCHAR_MAX; i++)
+ p[i] = 0;
+}
+
+cset::cset()
+{
+ clear();
+}
+
+cset::cset(const char *s)
+{
+ clear();
+ while (*s)
+ v[(unsigned char)*s++] = 1;
+}
+
+cset::cset(const unsigned char *s)
+{
+ clear();
+ while (*s)
+ v[*s++] = 1;
+}
+
+cset::cset(cset_builtin)
+{
+ // these are initialised by cset_init::cset_init()
+}
+
+cset &cset::operator|=(const cset &cs)
+{
+ for (int i = 0; i <= UCHAR_MAX; i++)
+ if (cs.v[i])
+ v[i] = 1;
+ return *this;
+}
+
+
+int cset_init::initialised = 0;
+
+cset_init::cset_init()
+{
+ if (initialised)
+ return;
+ initialised = 1;
+ for (int i = 0; i <= UCHAR_MAX; i++) {
+ csalpha.v[i] = ISASCII(i) && isalpha(i);
+ csupper.v[i] = ISASCII(i) && isupper(i);
+ cslower.v[i] = ISASCII(i) && islower(i);
+ csdigit.v[i] = ISASCII(i) && isdigit(i);
+ csxdigit.v[i] = ISASCII(i) && isxdigit(i);
+ csspace.v[i] = ISASCII(i) && isspace(i);
+ cspunct.v[i] = ISASCII(i) && ispunct(i);
+ csalnum.v[i] = ISASCII(i) && isalnum(i);
+ csprint.v[i] = ISASCII(i) && isprint(i);
+ csgraph.v[i] = ISASCII(i) && isgraph(i);
+ cscntrl.v[i] = ISASCII(i) && iscntrl(i);
+ }
+}
diff --git a/src/libs/libgroff/curtime.cpp b/src/libs/libgroff/curtime.cpp
new file mode 100644
index 0000000..34dbc5c
--- /dev/null
+++ b/src/libs/libgroff/curtime.cpp
@@ -0,0 +1,55 @@
+/* Copyright (C) 2015-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "errarg.h"
+#include "error.h"
+
+#ifdef LONG_FOR_TIME_T
+long
+#else
+time_t
+#endif
+current_time()
+{
+ char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
+
+ if (source_date_epoch) {
+ errno = 0;
+ char *endptr;
+ long epoch = strtol(source_date_epoch, &endptr, 10);
+
+ if ((errno == ERANGE && (epoch == LONG_MAX || epoch == LONG_MIN)) ||
+ (errno != 0 && epoch == 0))
+ fatal("$SOURCE_DATE_EPOCH: strtol: %1", strerror(errno));
+ if (endptr == source_date_epoch)
+ fatal("$SOURCE_DATE_EPOCH: no digits found: '%1'", endptr);
+ if (*endptr != '\0')
+ fatal("$SOURCE_DATE_EPOCH: trailing garbage: '%1'", endptr);
+ return epoch;
+ } else
+ return time(0);
+}
diff --git a/src/libs/libgroff/device.cpp b/src/libs/libgroff/device.cpp
new file mode 100644
index 0000000..6caeb8e
--- /dev/null
+++ b/src/libs/libgroff/device.cpp
@@ -0,0 +1,39 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include "device.h"
+#include "defs.h"
+
+const char *device = DEVICE;
+
+struct device_init {
+ device_init();
+} _device_init;
+
+device_init::device_init()
+{
+ char *tem = getenv("GROFF_TYPESETTER");
+ if (tem && tem[0])
+ device = tem;
+}
diff --git a/src/libs/libgroff/errarg.cpp b/src/libs/libgroff/errarg.cpp
new file mode 100644
index 0000000..99fc0fa
--- /dev/null
+++ b/src/libs/libgroff/errarg.cpp
@@ -0,0 +1,136 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "errarg.h"
+
+errarg::errarg(const char *p) : type(STRING)
+{
+ s = p ? p : "(null)";
+}
+
+errarg::errarg() : type(EMPTY)
+{
+}
+
+errarg::errarg(int nn) : type(INTEGER)
+{
+ n = nn;
+}
+
+errarg::errarg(unsigned int uu) : type(UNSIGNED_INTEGER)
+{
+ u = uu;
+}
+
+errarg::errarg(char cc) : type(CHAR)
+{
+ c = cc;
+}
+
+errarg::errarg(unsigned char cc) : type(CHAR)
+{
+ c = cc;
+}
+
+errarg::errarg(double dd) : type(DOUBLE)
+{
+ d = dd;
+}
+
+int errarg::empty() const
+{
+ return type == EMPTY;
+}
+
+extern "C" {
+ const char *i_to_a(int);
+ const char *ui_to_a(unsigned int);
+}
+
+void errarg::print() const
+{
+ switch (type) {
+ case INTEGER:
+ fputs(i_to_a(n), stderr);
+ break;
+ case UNSIGNED_INTEGER:
+ fputs(ui_to_a(u), stderr);
+ break;
+ case CHAR:
+ putc(c, stderr);
+ break;
+ case STRING:
+ fputs(s, stderr);
+ break;
+ case DOUBLE:
+ fprintf(stderr, "%g", d);
+ break;
+ case EMPTY:
+ break;
+ }
+}
+
+errarg empty_errarg;
+
+void errprint(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ assert(format != 0);
+ char c;
+ while ((c = *format++) != '\0') {
+ if (c == '%') {
+ c = *format++;
+ switch(c) {
+ case '%':
+ fputc('%', stderr);
+ break;
+ case '1':
+ assert(!arg1.empty());
+ arg1.print();
+ break;
+ case '2':
+ assert(!arg2.empty());
+ arg2.print();
+ break;
+ case '3':
+ assert(!arg3.empty());
+ arg3.print();
+ break;
+ default:
+ assert(0 == "unsupported argument conversion (not in [%123])");
+ }
+ }
+ else
+ putc(c, stderr);
+ }
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/error.cpp b/src/libs/libgroff/error.cpp
new file mode 100644
index 0000000..b1a4e61
--- /dev/null
+++ b/src/libs/libgroff/error.cpp
@@ -0,0 +1,185 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "errarg.h"
+#include "error.h"
+
+extern void fatal_error_exit();
+
+enum error_type { DEBUG, WARNING, ERROR, FATAL };
+
+static void do_error_with_file_and_line(const char *filename,
+ const char *source_filename,
+ int lineno,
+ error_type type,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ bool need_space = false;
+ if (program_name != 0 /* nullptr */) {
+ fputs(program_name, stderr);
+ fputc(':', stderr);
+ need_space = true;
+ }
+ if (filename != 0 /* nullptr */) {
+ if (strcmp(filename, "-") == 0)
+ filename = "<standard input>";
+ fputs(filename, stderr);
+ if (source_filename != 0 /* nullptr */) {
+ fputs(":(", stderr);
+ fputs(source_filename, stderr);
+ fputc(')', stderr);
+ }
+ if (lineno > 0) {
+ fputc(':', stderr);
+ errprint("%1", lineno);
+ }
+ fputc(':', stderr);
+ need_space = true;
+ }
+ if (need_space)
+ fputc(' ', stderr);
+ switch (type) {
+ case FATAL:
+ fputs("fatal error", stderr);
+ break;
+ case ERROR:
+ fputs("error", stderr);
+ break;
+ case WARNING:
+ fputs("warning", stderr);
+ break;
+ case DEBUG:
+ fputs("debug", stderr);
+ break;
+ }
+ fputs(": ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+ if (type == FATAL)
+ fatal_error_exit();
+}
+
+static void do_error(error_type type,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error_with_file_and_line(current_filename, current_source_filename,
+ current_lineno, type, format, arg1, arg2,
+ arg3);
+}
+
+void debug(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(DEBUG, format, arg1, arg2, arg3);
+}
+
+void error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(ERROR, format, arg1, arg2, arg3);
+}
+
+void warning(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(WARNING, format, arg1, arg2, arg3);
+}
+
+void fatal(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(FATAL, format, arg1, arg2, arg3);
+}
+
+// Use the functions below when it's more costly to save and restore the
+// globals current_filename, current_source_filename, and current_lineno
+// than to specify additional arguments. For instance, a function that
+// would need to temporarily change their values and has multiple return
+// paths might prefer these to the simpler variants above.
+
+void debug_with_file_and_line(const char *filename,
+ int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error_with_file_and_line(filename, 0 /* nullptr */, lineno,
+ DEBUG, format, arg1, arg2, arg3);
+}
+
+void error_with_file_and_line(const char *filename,
+ int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error_with_file_and_line(filename, 0 /* nullptr */, lineno,
+ ERROR, format, arg1, arg2, arg3);
+}
+
+void warning_with_file_and_line(const char *filename,
+ int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error_with_file_and_line(filename, 0 /* nullptr */, lineno,
+ WARNING, format, arg1, arg2, arg3);
+}
+
+void fatal_with_file_and_line(const char *filename,
+ int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error_with_file_and_line(filename, 0 /* nullptr */, lineno,
+ FATAL, format, arg1, arg2, arg3);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/fatal.cpp b/src/libs/libgroff/fatal.cpp
new file mode 100644
index 0000000..1cbf3ef
--- /dev/null
+++ b/src/libs/libgroff/fatal.cpp
@@ -0,0 +1,30 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#define FATAL_ERROR_EXIT_CODE 3
+
+void fatal_error_exit()
+{
+ exit(FATAL_ERROR_EXIT_CODE);
+}
diff --git a/src/libs/libgroff/filename.cpp b/src/libs/libgroff/filename.cpp
new file mode 100644
index 0000000..ded9738
--- /dev/null
+++ b/src/libs/libgroff/filename.cpp
@@ -0,0 +1,31 @@
+/* Copyright (C) 2014-2022 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// This global stores the name of the input file being processed by
+// troff, an output driver, or other program.
+const char *current_filename = 0 /* nullptr */;
+
+// This global stores the name of the troff input file corresponding to
+// the part of a device-independent troff output being processed; it is
+// used only by output drivers.
+const char *current_source_filename = 0 /* nullptr */;
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/fmod.c b/src/libs/libgroff/fmod.c
new file mode 100644
index 0000000..45278f9
--- /dev/null
+++ b/src/libs/libgroff/fmod.c
@@ -0,0 +1,27 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <math.h>
+
+double fmod(x, y)
+ double x, y;
+{
+ double quot = x/y;
+ return x - (quot < 0.0 ? ceil(quot) : floor(quot)) * y;
+}
diff --git a/src/libs/libgroff/font.cpp b/src/libs/libgroff/font.cpp
new file mode 100644
index 0000000..c1af12c
--- /dev/null
+++ b/src/libs/libgroff/font.cpp
@@ -0,0 +1,1321 @@
+/* Copyright (C) 1989-2021 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "cset.h"
+#include "font.h"
+#include "unicode.h"
+#include "paper.h"
+
+const char *const WS = " \t\n\r";
+
+struct font_char_metric {
+ char type;
+ int code;
+ int width;
+ int height;
+ int depth;
+ int pre_math_space;
+ int italic_correction;
+ int subscript_correction;
+ char *special_device_coding;
+};
+
+struct font_kern_list {
+ glyph *glyph1;
+ glyph *glyph2;
+ int amount;
+ font_kern_list *next;
+
+ font_kern_list(glyph *, glyph *, int, font_kern_list * = 0);
+};
+
+struct font_widths_cache {
+ font_widths_cache *next;
+ int point_size;
+ int *width;
+
+ font_widths_cache(int, int, font_widths_cache * = 0);
+ ~font_widths_cache();
+};
+
+/* text_file */
+
+struct text_file {
+ FILE *fp;
+ char *path;
+ int lineno;
+ int linebufsize;
+ bool recognize_comments;
+ bool silent;
+ char *buf;
+ text_file(FILE *fp, char *p);
+ ~text_file();
+ bool next_line();
+ void error(const char *format,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+ void fatal(const char *format,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+};
+
+text_file::text_file(FILE *p, char *s) : fp(p), path(s), lineno(0),
+ linebufsize(128), recognize_comments(true), silent(false), buf(0)
+{
+}
+
+text_file::~text_file()
+{
+ delete[] buf;
+ free(path);
+ if (fp)
+ fclose(fp);
+}
+
+bool text_file::next_line()
+{
+ if (fp == 0)
+ return false;
+ if (buf == 0)
+ buf = new char[linebufsize];
+ for (;;) {
+ lineno++;
+ int length = 0;
+ for (;;) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ error("invalid input character code %1", int(c));
+ else {
+ if (length + 1 >= linebufsize) {
+ char *old_buf = buf;
+ buf = new char[linebufsize * 2];
+ memcpy(buf, old_buf, linebufsize);
+ delete[] old_buf;
+ linebufsize *= 2;
+ }
+ buf[length++] = c;
+ if (c == '\n')
+ break;
+ }
+ }
+ if (length == 0)
+ break;
+ buf[length] = '\0';
+ char *ptr = buf;
+ while (csspace(*ptr))
+ ptr++;
+ if (*ptr != 0 && (!recognize_comments || *ptr != '#'))
+ return true;
+ }
+ return false;
+}
+
+void text_file::error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (!silent)
+ error_with_file_and_line(path, lineno, format, arg1, arg2, arg3);
+}
+
+void text_file::fatal(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (!silent)
+ fatal_with_file_and_line(path, lineno, format, arg1, arg2, arg3);
+}
+
+int glyph_to_unicode(glyph *g)
+{
+ const char *nm = glyph_to_name(g);
+ if (nm != 0) {
+ // ASCII character?
+ if (nm[0] == 'c' && nm[1] == 'h' && nm[2] == 'a' && nm[3] == 'r'
+ && (nm[4] >= '0' && nm[4] <= '9')) {
+ int n = (nm[4] - '0');
+ if (nm[5] == '\0')
+ return n;
+ if (n > 0 && (nm[5] >= '0' && nm[5] <= '9')) {
+ n = 10*n + (nm[5] - '0');
+ if (nm[6] == '\0')
+ return n;
+ if (nm[6] >= '0' && nm[6] <= '9') {
+ n = 10*n + (nm[6] - '0');
+ if (nm[7] == '\0' && n < 128)
+ return n;
+ }
+ }
+ }
+ // Unicode character?
+ if (check_unicode_name(nm)) {
+ char *ignore;
+ return (int)strtol(nm + 1, &ignore, 16);
+ }
+ // If 'nm' is a single letter 'x', the glyph name is '\x'.
+ char buf[] = { '\\', '\0', '\0' };
+ if (nm[1] == '\0') {
+ buf[1] = nm[0];
+ nm = buf;
+ }
+ // groff glyphs that map to Unicode?
+ const char *unicode = glyph_name_to_unicode(nm);
+ if (unicode != 0 && strchr(unicode, '_') == 0) {
+ char *ignore;
+ return (int)strtol(unicode, &ignore, 16);
+ }
+ }
+ return -1;
+}
+
+/* font functions */
+
+font::font(const char *s) : ligatures(0), kern_hash_table(0),
+ space_width(0), special(false), internalname(0), slant(0.0), zoom(0),
+ ch_index(0), nindices(0), ch(0), ch_used(0), ch_size(0),
+ widths_cache(0)
+{
+ name = new char[strlen(s) + 1];
+ strcpy(name, s);
+}
+
+font::~font()
+{
+ for (int i = 0; i < ch_used; i++)
+ if (ch[i].special_device_coding)
+ delete[] ch[i].special_device_coding;
+ delete[] ch;
+ delete[] ch_index;
+ if (kern_hash_table) {
+ for (int i = 0; i < KERN_HASH_TABLE_SIZE; i++) {
+ font_kern_list *kerns = kern_hash_table[i];
+ while (kerns) {
+ font_kern_list *tem = kerns;
+ kerns = kerns->next;
+ delete tem;
+ }
+ }
+ delete[] kern_hash_table;
+ }
+ delete[] name;
+ delete[] internalname;
+ while (widths_cache) {
+ font_widths_cache *tem = widths_cache;
+ widths_cache = widths_cache->next;
+ delete tem;
+ }
+}
+
+static int scale_round(int n, int x, int y)
+{
+ assert(x >= 0 && y > 0);
+ int y2 = y/2;
+ if (x == 0)
+ return 0;
+ if (n >= 0) {
+ if (n <= (INT_MAX - y2) / x)
+ return (n * x + y2) / y;
+ return int(n * double(x) / double(y) + .5);
+ }
+ else {
+ if (-(unsigned)n <= (-(unsigned)INT_MIN - y2) / x)
+ return (n * x - y2) / y;
+ return int(n * double(x) / double(y) - .5);
+ }
+}
+
+static int scale_round(int n, int x, int y, int z)
+{
+ assert(x >= 0 && y > 0 && z > 0);
+ if (x == 0)
+ return 0;
+ if (n >= 0)
+ return int((n * double(x) / double(y)) * (double(z) / 1000.0) + .5);
+ else
+ return int((n * double(x) / double(y)) * (double(z) / 1000.0) - .5);
+}
+
+inline int font::scale(int w, int sz)
+{
+ if (zoom)
+ return scale_round(w, sz, unitwidth, zoom);
+ else
+ return sz == unitwidth ? w : scale_round(w, sz, unitwidth);
+}
+
+// Returns whether scaling by arguments was successful. Used for paper
+// size conversions.
+bool font::unit_scale(double *value, char unit)
+{
+ // Paper sizes are handled in inches.
+ double divisor = 0;
+ switch (unit) {
+ case 'i':
+ divisor = 1;
+ break;
+ case 'p':
+ divisor = 72;
+ break;
+ case 'P':
+ divisor = 6;
+ break;
+ case 'c':
+ divisor = 2.54;
+ break;
+ default:
+ assert(0 == "unit not in [cipP]");
+ break;
+ }
+ if (divisor) {
+ *value /= divisor;
+ return true;
+ }
+ return false;
+}
+
+int font::get_skew(glyph *g, int point_size, int sl)
+{
+ int h = get_height(g, point_size);
+ return int(h * tan((slant + sl) * PI / 180.0) + .5);
+}
+
+bool font::contains(glyph *g)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ // Explicitly enumerated glyph?
+ if (idx < nindices && ch_index[idx] >= 0)
+ return true;
+ if (is_unicode) {
+ // Unicode font
+ // ASCII or Unicode character, or groff glyph name that maps to Unicode?
+ if (glyph_to_unicode(g) >= 0)
+ return true;
+ // Numbered character?
+ if (glyph_to_number(g) >= 0)
+ return true;
+ }
+ return false;
+}
+
+bool font::is_special()
+{
+ return special;
+}
+
+font_widths_cache::font_widths_cache(int ps, int ch_size,
+ font_widths_cache *p)
+: next(p), point_size(ps)
+{
+ width = new int[ch_size];
+ for (int i = 0; i < ch_size; i++)
+ width[i] = -1;
+}
+
+font_widths_cache::~font_widths_cache()
+{
+ delete[] width;
+}
+
+int font::get_width(glyph *g, int point_size)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ int real_size;
+ if (zoom == 0) // 0 means "don't zoom"
+ real_size = point_size;
+ else
+ {
+ if (point_size <= (INT_MAX - 500) / zoom)
+ real_size = (point_size * zoom + 500) / 1000;
+ else
+ real_size = int(point_size * double(zoom) / 1000.0 + .5);
+ }
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ int i = ch_index[idx];
+ if (real_size == unitwidth || font::use_unscaled_charwidths)
+ return ch[i].width;
+
+ if (!widths_cache)
+ widths_cache = new font_widths_cache(real_size, ch_size);
+ else if (widths_cache->point_size != real_size) {
+ font_widths_cache **p;
+ for (p = &widths_cache; *p; p = &(*p)->next)
+ if ((*p)->point_size == real_size)
+ break;
+ if (*p) {
+ font_widths_cache *tem = *p;
+ *p = (*p)->next;
+ tem->next = widths_cache;
+ widths_cache = tem;
+ }
+ else
+ widths_cache = new font_widths_cache(real_size, ch_size,
+ widths_cache);
+ }
+ int &w = widths_cache->width[i];
+ if (w < 0)
+ w = scale(ch[i].width, point_size);
+ return w;
+ }
+ if (is_unicode) {
+ // Unicode font
+ int width = 24; // XXX: Add a request to override this.
+ int w = wcwidth(get_code(g));
+ if (w > 1)
+ width *= w;
+ if (real_size == unitwidth || font::use_unscaled_charwidths)
+ return width;
+ else
+ return scale(width, point_size);
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+int font::get_height(glyph *g, int point_size)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return scale(ch[ch_index[idx]].height, point_size);
+ }
+ if (is_unicode) {
+ // Unicode font
+ return 0;
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+int font::get_depth(glyph *g, int point_size)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return scale(ch[ch_index[idx]].depth, point_size);
+ }
+ if (is_unicode) {
+ // Unicode font
+ return 0;
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+int font::get_italic_correction(glyph *g, int point_size)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return scale(ch[ch_index[idx]].italic_correction, point_size);
+ }
+ if (is_unicode) {
+ // Unicode font
+ return 0;
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+int font::get_left_italic_correction(glyph *g, int point_size)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return scale(ch[ch_index[idx]].pre_math_space, point_size);
+ }
+ if (is_unicode) {
+ // Unicode font
+ return 0;
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+int font::get_subscript_correction(glyph *g, int point_size)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return scale(ch[ch_index[idx]].subscript_correction, point_size);
+ }
+ if (is_unicode) {
+ // Unicode font
+ return 0;
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+void font::set_zoom(int factor)
+{
+ assert(factor >= 0);
+ if (factor == 1000)
+ zoom = 0;
+ else
+ zoom = factor;
+}
+
+int font::get_zoom()
+{
+ return zoom;
+}
+
+int font::get_space_width(int point_size)
+{
+ return scale(space_width, point_size);
+}
+
+font_kern_list::font_kern_list(glyph *g1, glyph *g2, int n, font_kern_list *p)
+: glyph1(g1), glyph2(g2), amount(n), next(p)
+{
+}
+
+inline int font::hash_kern(glyph *g1, glyph *g2)
+{
+ int n = ((glyph_to_index(g1) << 10) + glyph_to_index(g2))
+ % KERN_HASH_TABLE_SIZE;
+ return n < 0 ? -n : n;
+}
+
+void font::add_kern(glyph *g1, glyph *g2, int amount)
+{
+ if (!kern_hash_table) {
+ kern_hash_table = new font_kern_list *[int(KERN_HASH_TABLE_SIZE)];
+ for (int i = 0; i < KERN_HASH_TABLE_SIZE; i++)
+ kern_hash_table[i] = 0;
+ }
+ font_kern_list **p = kern_hash_table + hash_kern(g1, g2);
+ *p = new font_kern_list(g1, g2, amount, *p);
+}
+
+int font::get_kern(glyph *g1, glyph *g2, int point_size)
+{
+ if (kern_hash_table) {
+ for (font_kern_list *p = kern_hash_table[hash_kern(g1, g2)]; p;
+ p = p->next)
+ if (g1 == p->glyph1 && g2 == p->glyph2)
+ return scale(p->amount, point_size);
+ }
+ return 0;
+}
+
+bool font::has_ligature(int mask)
+{
+ return (bool) (mask & ligatures);
+}
+
+int font::get_character_type(glyph *g)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return ch[ch_index[idx]].type;
+ }
+ if (is_unicode) {
+ // Unicode font
+ return 0;
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+int font::get_code(glyph *g)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return ch[ch_index[idx]].code;
+ }
+ if (is_unicode) {
+ // Unicode font
+ // ASCII or Unicode character, or groff glyph name that maps to Unicode?
+ int uni = glyph_to_unicode(g);
+ if (uni >= 0)
+ return uni;
+ // Numbered character?
+ int n = glyph_to_number(g);
+ if (n >= 0)
+ return n;
+ }
+ // The caller must check 'contains(g)' before calling get_code(g).
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+const char *font::get_name()
+{
+ return name;
+}
+
+const char *font::get_internal_name()
+{
+ return internalname;
+}
+
+const char *font::get_special_device_encoding(glyph *g)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx < nindices && ch_index[idx] >= 0) {
+ // Explicitly enumerated glyph
+ return ch[ch_index[idx]].special_device_coding;
+ }
+ if (is_unicode) {
+ // Unicode font
+ return 0;
+ }
+ assert(0 == "glyph is not indexed and device lacks Unicode support");
+ abort(); // -Wreturn-type
+}
+
+const char *font::get_image_generator()
+{
+ return image_generator;
+}
+
+void font::alloc_ch_index(int idx)
+{
+ if (nindices == 0) {
+ nindices = 128;
+ if (idx >= nindices)
+ nindices = idx + 10;
+ ch_index = new int[nindices];
+ for (int i = 0; i < nindices; i++)
+ ch_index[i] = -1;
+ }
+ else {
+ int old_nindices = nindices;
+ nindices *= 2;
+ if (idx >= nindices)
+ nindices = idx + 10;
+ int *old_ch_index = ch_index;
+ ch_index = new int[nindices];
+ memcpy(ch_index, old_ch_index, sizeof(int) * old_nindices);
+ for (int i = old_nindices; i < nindices; i++)
+ ch_index[i] = -1;
+ delete[] old_ch_index;
+ }
+}
+
+void font::extend_ch()
+{
+ if (ch == 0)
+ ch = new font_char_metric[ch_size = 16];
+ else {
+ int old_ch_size = ch_size;
+ ch_size *= 2;
+ font_char_metric *old_ch = ch;
+ ch = new font_char_metric[ch_size];
+ memcpy(ch, old_ch, old_ch_size * sizeof(font_char_metric));
+ delete[] old_ch;
+ }
+}
+
+void font::compact()
+{
+ int i;
+ for (i = nindices - 1; i >= 0; i--)
+ if (ch_index[i] >= 0)
+ break;
+ i++;
+ if (i < nindices) {
+ int *old_ch_index = ch_index;
+ ch_index = new int[i];
+ memcpy(ch_index, old_ch_index, i*sizeof(int));
+ delete[] old_ch_index;
+ nindices = i;
+ }
+ if (ch_used < ch_size) {
+ font_char_metric *old_ch = ch;
+ ch = new font_char_metric[ch_used];
+ memcpy(ch, old_ch, ch_used*sizeof(font_char_metric));
+ delete[] old_ch;
+ ch_size = ch_used;
+ }
+}
+
+void font::add_entry(glyph *g, const font_char_metric &metric)
+{
+ int idx = glyph_to_index(g);
+ assert(idx >= 0);
+ if (idx >= nindices)
+ alloc_ch_index(idx);
+ assert(idx < nindices);
+ if (ch_used + 1 >= ch_size)
+ extend_ch();
+ assert(ch_used + 1 < ch_size);
+ ch_index[idx] = ch_used;
+ ch[ch_used++] = metric;
+}
+
+void font::copy_entry(glyph *new_glyph, glyph *old_glyph)
+{
+ int new_index = glyph_to_index(new_glyph);
+ int old_index = glyph_to_index(old_glyph);
+ assert(new_index >= 0 && old_index >= 0 && old_index < nindices);
+ if (new_index >= nindices)
+ alloc_ch_index(new_index);
+ ch_index[new_index] = ch_index[old_index];
+}
+
+font *font::load_font(const char *s, bool load_header_only)
+{
+ font *f = new font(s);
+ if (!f->load(load_header_only)) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+static char *trim_arg(char *p)
+{
+ if (0 == p)
+ return 0;
+ while (csspace(*p))
+ p++;
+ char *q = strchr(p, '\0');
+ while (q > p && csspace(q[-1]))
+ q--;
+ *q = '\0';
+ return p;
+}
+
+bool font::scan_papersize(const char *p, const char **size,
+ double *length, double *width)
+{
+ double l, w;
+ char lu[2], wu[2];
+ const char *pp = p;
+ bool attempt_file_open = true;
+ char line[255];
+again:
+ if (csdigit(*pp)) {
+ if (sscanf(pp, "%lf%1[ipPc],%lf%1[ipPc]", &l, lu, &w, wu) == 4
+ && l > 0 && w > 0
+ && unit_scale(&l, lu[0]) && unit_scale(&w, wu[0])) {
+ if (length)
+ *length = l;
+ if (width)
+ *width = w;
+ if (size)
+ *size = "custom";
+ return true;
+ }
+ }
+ else {
+ int i;
+ for (i = 0; i < NUM_PAPERSIZES; i++)
+ if (strcasecmp(papersizes[i].name, pp) == 0) {
+ if (length)
+ *length = papersizes[i].length;
+ if (width)
+ *width = papersizes[i].width;
+ if (size)
+ *size = papersizes[i].name;
+ return true;
+ }
+ if (attempt_file_open) {
+ FILE *fp = fopen(p, "r");
+ if (fp != 0) {
+ if (fgets(line, 254, fp)) {
+ // Don't recurse on file names.
+ attempt_file_open = false;
+ char *linep = strchr(line, '\0');
+ // skip final newline, if any
+ if (*(--linep) == '\n')
+ *linep = '\0';
+ pp = line;
+ }
+ fclose(fp);
+ goto again;
+ }
+ }
+ }
+ return false;
+}
+
+bool font::load(bool load_header_only)
+{
+ FILE *fp;
+ char *path;
+ if ((fp = open_file(name, &path)) == 0)
+ return false;
+ text_file t(fp, path);
+ t.silent = load_header_only;
+ char *p = 0;
+ bool saw_name_directive = false;
+ while (t.next_line()) {
+ p = strtok(t.buf, WS);
+ if (strcmp(p, "name") == 0) {
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("'name' directive requires an argument");
+ return false;
+ }
+ if (strcmp(p, name) != 0) {
+ t.error("font description file name '%1' does not match 'name'"
+ " argument '%2'", name, p);
+ return false;
+ }
+ saw_name_directive = true;
+ }
+ else if (strcmp(p, "spacewidth") == 0) {
+ p = strtok(0, WS);
+ int n;
+ if (0 == p) {
+ t.error("missing argument to 'spacewidth' directive");
+ return false;
+ }
+ if (sscanf(p, "%d", &n) != 1) {
+ t.error("invalid argument '%1' to 'spacewidth' directive", p);
+ return false;
+ }
+ if (n <= 0) {
+ t.error("'spacewidth' argument '%1' out of range", n);
+ return false;
+ }
+ space_width = n;
+ }
+ else if (strcmp(p, "slant") == 0) {
+ p = strtok(0, WS);
+ double n;
+ if (0 == p) {
+ t.error("missing argument to 'slant' directive");
+ return false;
+ }
+ if (sscanf(p, "%lf", &n) != 1) {
+ t.error("invalid argument '%1' to 'slant' directive", p);
+ return false;
+ }
+ if (n >= 90.0 || n <= -90.0) {
+ t.error("'slant' directive argument '%1' out of range", n);
+ return false;
+ }
+ slant = n;
+ }
+ else if (strcmp(p, "ligatures") == 0) {
+ for (;;) {
+ p = strtok(0, WS);
+ if (0 == p || strcmp(p, "0") == 0)
+ break;
+ if (strcmp(p, "ff") == 0)
+ ligatures |= LIG_ff;
+ else if (strcmp(p, "fi") == 0)
+ ligatures |= LIG_fi;
+ else if (strcmp(p, "fl") == 0)
+ ligatures |= LIG_fl;
+ else if (strcmp(p, "ffi") == 0)
+ ligatures |= LIG_ffi;
+ else if (strcmp(p, "ffl") == 0)
+ ligatures |= LIG_ffl;
+ else {
+ t.error("unrecognized ligature '%1'", p);
+ return false;
+ }
+ }
+ }
+ else if (strcmp(p, "internalname") == 0) {
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("missing argument to 'internalname' directive");
+ return false;
+ }
+ internalname = new char[strlen(p) + 1];
+ strcpy(internalname, p);
+ }
+ else if (strcmp(p, "special") == 0) {
+ special = true;
+ }
+ else if (strcmp(p, "kernpairs") != 0 && strcmp(p, "charset") != 0) {
+ char *directive = p;
+ p = strtok(0, "\n");
+ handle_unknown_font_command(directive, trim_arg(p), t.path,
+ t.lineno);
+ }
+ else
+ break;
+ }
+ bool saw_charset_directive = false;
+ char *directive = p;
+ t.recognize_comments = false;
+ while (directive) {
+ if (strcmp(directive, "kernpairs") == 0) {
+ if (load_header_only)
+ return true;
+ for (;;) {
+ if (!t.next_line()) {
+ directive = 0;
+ break;
+ }
+ char *c1 = strtok(t.buf, WS);
+ if (0 == c1)
+ continue;
+ char *c2 = strtok(0, WS);
+ if (0 == c2) {
+ directive = c1;
+ break;
+ }
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("missing kern amount for kerning pair '%1 %2'", c1,
+ c2);
+ return false;
+ }
+ int n;
+ if (sscanf(p, "%d", &n) != 1) {
+ t.error("invalid kern amount '%1' for kerning pair '%2 %3'",
+ p, c1, c2);
+ return false;
+ }
+ glyph *g1 = name_to_glyph(c1);
+ glyph *g2 = name_to_glyph(c2);
+ add_kern(g1, g2, n);
+ }
+ }
+ else if (strcmp(directive, "charset") == 0) {
+ if (load_header_only)
+ return true;
+ saw_charset_directive = true;
+ glyph *last_glyph = 0;
+ for (;;) {
+ if (!t.next_line()) {
+ directive = 0;
+ break;
+ }
+ char *nm = strtok(t.buf, WS);
+ assert(nm != 0);
+ p = strtok(0, WS);
+ if (0 == p) {
+ directive = nm;
+ break;
+ }
+ if (p[0] == '"') {
+ if (last_glyph == 0) {
+ t.error("the first entry ('%1') in 'charset' subsection"
+ " cannot be an alias", nm);
+ return false;
+ }
+ if (strcmp(nm, "---") == 0) {
+ t.error("an unnamed character ('---') cannot be an alias");
+ return false;
+ }
+ glyph *g = name_to_glyph(nm);
+ copy_entry(g, last_glyph);
+ }
+ else {
+ font_char_metric metric;
+ metric.height = 0;
+ metric.depth = 0;
+ metric.pre_math_space = 0;
+ metric.italic_correction = 0;
+ metric.subscript_correction = 0;
+ int nparms = sscanf(p, "%d,%d,%d,%d,%d,%d",
+ &metric.width, &metric.height,
+ &metric.depth,
+ &metric.italic_correction,
+ &metric.pre_math_space,
+ &metric.subscript_correction);
+ if (nparms < 1) {
+ t.error("missing or invalid width for glyph '%1'", nm);
+ return false;
+ }
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("missing character type for '%1'", nm);
+ return false;
+ }
+ int type;
+ if (sscanf(p, "%d", &type) != 1) {
+ t.error("invalid character type for '%1'", nm);
+ return false;
+ }
+ if (type < 0 || type > 255) {
+ t.error("character type '%1' out of range for '%2'", type,
+ nm);
+ return false;
+ }
+ metric.type = type;
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("missing code for '%1'", nm);
+ return false;
+ }
+ char *ptr;
+ metric.code = (int)strtol(p, &ptr, 0);
+ if (metric.code == 0 && ptr == p) {
+ t.error("invalid code '%1' for character '%2'", p, nm);
+ return false;
+ }
+ if (is_unicode) {
+ int w = wcwidth(metric.code);
+ if (w > 1)
+ metric.width *= w;
+ }
+ p = strtok(0, WS);
+ if ((0 == p) || (strcmp(p, "--") == 0)) {
+ metric.special_device_coding = 0;
+ }
+ else {
+ char *nam = new char[strlen(p) + 1];
+ strcpy(nam, p);
+ metric.special_device_coding = nam;
+ }
+ if (strcmp(nm, "---") == 0) {
+ last_glyph = number_to_glyph(metric.code);
+ add_entry(last_glyph, metric);
+ }
+ else {
+ last_glyph = name_to_glyph(nm);
+ add_entry(last_glyph, metric);
+ copy_entry(number_to_glyph(metric.code), last_glyph);
+ }
+ }
+ }
+ if (0 == last_glyph) {
+ t.error("no glyphs defined in font description");
+ return false;
+ }
+ }
+ else {
+ t.error("unrecognized font description directive '%1' (missing"
+ " 'kernpairs' or 'charset'?)", directive);
+ return false;
+ }
+ }
+ compact();
+ t.lineno = 0;
+ if (!saw_name_directive) {
+ t.error("font description 'name' directive missing");
+ return false;
+ }
+ if (!is_unicode && !saw_charset_directive) {
+ t.error("font description 'charset' subsection missing");
+ return false;
+ }
+ if (space_width == 0) {
+ t.error("font description 'spacewidth' directive missing");
+ // _Don't_ return false; compute a typical one for Western glyphs.
+ if (zoom)
+ space_width = scale_round(unitwidth, res, 72 * 3 * sizescale,
+ zoom);
+ else
+ space_width = scale_round(unitwidth, res, 72 * 3 * sizescale);
+ }
+ return true;
+}
+
+static struct {
+ const char *numeric_directive;
+ int *ptr;
+} table[] = {
+ { "res", &font::res },
+ { "hor", &font::hor },
+ { "vert", &font::vert },
+ { "unitwidth", &font::unitwidth },
+ { "paperwidth", &font::paperwidth },
+ { "paperlength", &font::paperlength },
+ { "spare1", &font::biggestfont },
+ { "biggestfont", &font::biggestfont },
+ { "spare2", &font::spare2 },
+ { "sizescale", &font::sizescale },
+ };
+
+// Return file specification of DESC file for selected output device if
+// it can be located and is valid, and a null pointer otherwise.
+const char *font::load_desc()
+{
+ int nfonts = 0;
+ FILE *fp;
+ char *path;
+ if ((fp = open_file("DESC", &path)) == 0)
+ return 0 /* nullptr */;
+ text_file t(fp, path);
+ while (t.next_line()) {
+ char *p = strtok(t.buf, WS);
+ assert(p != 0);
+ bool numeric_directive_found = false;
+ unsigned int idx;
+ for (idx = 0; !numeric_directive_found
+ && idx < sizeof(table) / sizeof(table[0]); idx++)
+ if (strcmp(table[idx].numeric_directive, p) == 0)
+ numeric_directive_found = true;
+ if (numeric_directive_found) {
+ char *q = strtok(0, WS);
+ if (0 == q) {
+ t.error("missing value for directive '%1'", p);
+ return 0 /* nullptr */;
+ }
+ int val;
+ if (sscanf(q, "%d", &val) != 1) {
+ t.error("'%1' directive given invalid number '%2'", p, q);
+ return 0 /* nullptr */;
+ }
+ if ((strcmp(p, "res") == 0
+ || strcmp(p, "hor") == 0
+ || strcmp(p, "vert") == 0
+ || strcmp(p, "unitwidth") == 0
+ || strcmp(p, "paperwidth") == 0
+ || strcmp(p, "paperlength") == 0
+ || strcmp(p, "sizescale") == 0)
+ && val < 1) {
+ t.error("expected argument to '%1' directive to be a"
+ " positive number, got '%2'", p, val);
+ return 0 /* nullptr */;
+ }
+ *(table[idx-1].ptr) = val;
+ }
+ else if (strcmp("family", p) == 0) {
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("'family' directive requires an argument");
+ return 0 /* nullptr */;
+ }
+ char *tem = new char[strlen(p)+1];
+ strcpy(tem, p);
+ family = tem;
+ }
+ else if (strcmp("fonts", p) == 0) {
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("'fonts' directive requires arguments");
+ return 0 /* nullptr */;
+ }
+ if (sscanf(p, "%d", &nfonts) != 1 || nfonts <= 0) {
+ t.error("expected first argument to 'fonts' directive to be a"
+ " non-negative number, got '%1'", p);
+ return 0 /* nullptr */;
+ }
+ font_name_table = (const char **)new char *[nfonts+1];
+ for (int i = 0; i < nfonts; i++) {
+ p = strtok(0, WS);
+ while (0 == p) {
+ if (!t.next_line()) {
+ t.error("unexpected end of file while reading font list");
+ return 0 /* nullptr */;
+ }
+ p = strtok(t.buf, WS);
+ }
+ char *temp = new char[strlen(p)+1];
+ strcpy(temp, p);
+ font_name_table[i] = temp;
+ }
+ p = strtok(0, WS);
+ if (p != 0) {
+ t.error("font count does not match declared number of fonts"
+ " ('%1')", nfonts);
+ return 0 /* nullptr */;
+ }
+ font_name_table[nfonts] = 0;
+ }
+ else if (strcmp("papersize", p) == 0) {
+ if (0 == res) {
+ t.error("'res' directive must precede 'papersize' in device"
+ " description file");
+ return 0 /* nullptr */;
+ }
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("'papersize' directive requires an argument");
+ return 0 /* nullptr */;
+ }
+ bool found_paper = false;
+ char *savedp = strdup(p);
+ if (0 == savedp)
+ t.fatal("memory allocation failure while processing 'papersize'"
+ " directive");
+ while (p) {
+ double unscaled_paperwidth, unscaled_paperlength;
+ if (scan_papersize(p, &papersize, &unscaled_paperlength,
+ &unscaled_paperwidth)) {
+ paperwidth = int(unscaled_paperwidth * res + 0.5);
+ paperlength = int(unscaled_paperlength * res + 0.5);
+ found_paper = true;
+ break;
+ }
+ p = strtok(0, WS);
+ }
+ assert(savedp != 0);
+ if (!found_paper) {
+ t.error("unable to determine a paper format from '%1'", savedp);
+ free(savedp);
+ return 0 /* nullptr */;
+ }
+ free(savedp);
+ }
+ else if (strcmp("unscaled_charwidths", p) == 0)
+ use_unscaled_charwidths = true;
+ else if (strcmp("pass_filenames", p) == 0)
+ pass_filenames = true;
+ else if (strcmp("sizes", p) == 0) {
+ int n = 16;
+ sizes = new int[n];
+ int i = 0;
+ for (;;) {
+ p = strtok(0, WS);
+ while (0 == p) {
+ if (!t.next_line()) {
+ t.error("list of sizes must be terminated by '0'");
+ return 0 /* nullptr */;
+ }
+ p = strtok(t.buf, WS);
+ }
+ int lower, upper;
+ switch (sscanf(p, "%d-%d", &lower, &upper)) {
+ case 1:
+ upper = lower;
+ // fall through
+ case 2:
+ if (lower <= upper && lower >= 0)
+ break;
+ // fall through
+ default:
+ t.error("invalid size range '%1'", p);
+ return 0 /* nullptr */;
+ }
+ if (i + 2 > n) {
+ int *old_sizes = sizes;
+ sizes = new int[n*2];
+ memcpy(sizes, old_sizes, n*sizeof(int));
+ n *= 2;
+ delete[] old_sizes;
+ }
+ sizes[i++] = lower;
+ if (lower == 0)
+ break;
+ sizes[i++] = upper;
+ }
+ if (i == 1) {
+ t.error("must have some sizes");
+ return 0 /* nullptr */;
+ }
+ }
+ else if (strcmp("styles", p) == 0) {
+ int style_table_size = 5;
+ style_table = (const char **)new char *[style_table_size];
+ int j;
+ for (j = 0; j < style_table_size; j++)
+ style_table[j] = 0;
+ int i = 0;
+ for (;;) {
+ p = strtok(0, WS);
+ if (0 == p)
+ break;
+ // leave room for terminating 0
+ if (i + 1 >= style_table_size) {
+ const char **old_style_table = style_table;
+ style_table_size *= 2;
+ style_table = (const char **)new char*[style_table_size];
+ for (j = 0; j < i; j++)
+ style_table[j] = old_style_table[j];
+ for (; j < style_table_size; j++)
+ style_table[j] = 0;
+ delete[] old_style_table;
+ }
+ char *tem = new char[strlen(p) + 1];
+ strcpy(tem, p);
+ style_table[i++] = tem;
+ }
+ }
+ else if (strcmp("tcommand", p) == 0)
+ has_tcommand = true;
+ else if (strcmp("use_charnames_in_special", p) == 0)
+ use_charnames_in_special = true;
+ else if (strcmp("unicode", p) == 0)
+ is_unicode = true;
+ else if (strcmp("image_generator", p) == 0) {
+ p = strtok(0, WS);
+ if (0 == p) {
+ t.error("'image_generator' directive requires an argument");
+ return 0 /* nullptr */;
+ }
+ image_generator = strsave(p);
+ }
+ else if (strcmp("charset", p) == 0)
+ break;
+ else if (unknown_desc_command_handler) {
+ char *directive = p;
+ p = strtok(0, "\n");
+ (*unknown_desc_command_handler)(directive, trim_arg(p), t.path,
+ t.lineno);
+ }
+ }
+ t.lineno = 0;
+ if (res == 0) {
+ t.error("device description file missing 'res' directive");
+ return 0 /* nullptr */;
+ }
+ if (unitwidth == 0) {
+ t.error("device description file missing 'unitwidth' directive");
+ return 0 /* nullptr */;
+ }
+ if (font_name_table == 0) {
+ t.error("device description file missing 'fonts' directive");
+ return 0 /* nullptr */;
+ }
+ if (sizes == 0) {
+ t.error("device description file missing 'sizes' directive");
+ return 0 /* nullptr */;
+ }
+ return path;
+}
+
+void font::handle_unknown_font_command(const char *, const char *,
+ const char *, int)
+{
+}
+
+FONT_COMMAND_HANDLER
+font::set_unknown_desc_command_handler(FONT_COMMAND_HANDLER func)
+{
+ FONT_COMMAND_HANDLER prev = unknown_desc_command_handler;
+ unknown_desc_command_handler = func;
+ return prev;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/fontfile.cpp b/src/libs/libgroff/fontfile.cpp
new file mode 100644
index 0000000..8987971
--- /dev/null
+++ b/src/libs/libgroff/fontfile.cpp
@@ -0,0 +1,80 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include "font.h"
+#include "searchpath.h"
+#include "device.h"
+#include "defs.h"
+
+const char *const FONT_ENV_VAR = "GROFF_FONT_PATH";
+
+static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0);
+
+int font::res = 0;
+int font::hor = 1;
+int font::vert = 1;
+int font::unitwidth = 0;
+int font::paperwidth = 0;
+int font::paperlength = 0;
+const char *font::papersize = 0;
+int font::biggestfont = 0;
+int font::spare2 = 0;
+int font::sizescale = 1;
+bool font::has_tcommand = false;
+bool font::pass_filenames = false;
+bool font::use_unscaled_charwidths = false;
+bool font::use_charnames_in_special = false;
+bool font::is_unicode = false;
+const char *font::image_generator = 0;
+const char **font::font_name_table = 0;
+int *font::sizes = 0;
+const char *font::family = 0;
+const char **font::style_table = 0;
+FONT_COMMAND_HANDLER font::unknown_desc_command_handler = 0;
+
+void font::command_line_font_dir(const char *dir)
+{
+ font_path.command_line_dir(dir);
+}
+
+FILE *font::open_file(const char *nm, char **pathp)
+{
+ FILE *fp = 0 /* nullptr */;
+ // Do not traverse user-specified directories; Savannah #61424.
+ if (0 /* nullptr */ == strchr(nm, '/')) {
+ // Allocate enough for nm + device + 'dev' '/' '\0'.
+ int expected_size = strlen(nm) + strlen(device) + 5;
+ char *filename = new char[expected_size];
+ const int actual_size = sprintf(filename, "dev%s/%s", device, nm);
+ expected_size--; // sprintf() doesn't count the null terminator.
+ if (actual_size == expected_size)
+ fp = font_path.open_file(filename, pathp);
+ delete[] filename;
+ }
+ return fp;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/geometry.cpp b/src/libs/libgroff/geometry.cpp
new file mode 100644
index 0000000..c4665c4
--- /dev/null
+++ b/src/libs/libgroff/geometry.cpp
@@ -0,0 +1,180 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley <gaius@glam.ac.uk>
+ using adjust_arc_center() from printer.cpp, written by James Clark.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#undef MAX
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+#undef MIN
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+
+// This utility function adjusts the specified center of the
+// arc so that it is equidistant between the specified start
+// and end points. (p[0], p[1]) is a vector from the current
+// point to the center; (p[2], p[3]) is a vector from the
+// center to the end point. If the center can be adjusted,
+// a vector from the current point to the adjusted center is
+// stored in c[0], c[1] and 1 is returned. Otherwise 0 is
+// returned.
+
+#if 1
+int adjust_arc_center(const int *p, double *c)
+{
+ // We move the center along a line parallel to the line between
+ // the specified start point and end point so that the center
+ // is equidistant between the start and end point.
+ // It can be proved (using Lagrange multipliers) that this will
+ // give the point nearest to the specified center that is equidistant
+ // between the start and end point.
+
+ double x = p[0] + p[2]; // (x, y) is the end point
+ double y = p[1] + p[3];
+ double n = x*x + y*y;
+ if (n != 0) {
+ c[0]= double(p[0]);
+ c[1] = double(p[1]);
+ double k = .5 - (c[0]*x + c[1]*y)/n;
+ c[0] += k*x;
+ c[1] += k*y;
+ return 1;
+ }
+ else
+ return 0;
+}
+#else
+int printer::adjust_arc_center(const int *p, double *c)
+{
+ int x = p[0] + p[2]; // (x, y) is the end point
+ int y = p[1] + p[3];
+ // Start at the current point; go in the direction of the specified
+ // center point until we reach a point that is equidistant between
+ // the specified starting point and the specified end point. Place
+ // the center of the arc there.
+ double n = p[0]*double(x) + p[1]*double(y);
+ if (n > 0) {
+ double k = (double(x)*x + double(y)*y)/(2.0*n);
+ // (cx, cy) is our chosen center
+ c[0] = k*p[0];
+ c[1] = k*p[1];
+ return 1;
+ }
+ else {
+ // We would never reach such a point. So instead start at the
+ // specified end point of the arc. Go towards the specified
+ // center point until we reach a point that is equidistant between
+ // the specified start point and specified end point. Place
+ // the center of the arc there.
+ n = p[2]*double(x) + p[3]*double(y);
+ if (n > 0) {
+ double k = 1 - (double(x)*x + double(y)*y)/(2.0*n);
+ // (c[0], c[1]) is our chosen center
+ c[0] = p[0] + k*p[2];
+ c[1] = p[1] + k*p[3];
+ return 1;
+ }
+ else
+ return 0;
+ }
+}
+#endif
+
+
+/*
+ * check_output_arc_limits - works out the smallest box that will encompass
+ * an arc defined by an origin (x, y) and two
+ * vectors (p0, p1) and (p2, p3).
+ * (x1, y1) -> start of arc
+ * (x1, y1) + (xv1, yv1) -> center of circle
+ * (x1, y1) + (xv1, yv1) + (xv2, yv2) -> end of arc
+ *
+ * Works out in which quadrant the arc starts and
+ * stops, and from this it determines the x, y
+ * max/min limits. The arc is drawn clockwise.
+ */
+
+void check_output_arc_limits(int x_1, int y_1,
+ int xv_1, int yv_1,
+ int xv_2, int yv_2,
+ double c_0, double c_1,
+ int *minx, int *maxx,
+ int *miny, int *maxy)
+{
+ int radius = (int)sqrt(c_0 * c_0 + c_1 * c_1);
+ // clockwise direction
+ int xcenter = x_1 + xv_1;
+ int ycenter = y_1 + yv_1;
+ int xend = xcenter + xv_2;
+ int yend = ycenter + yv_2;
+ // for convenience, transform to counterclockwise direction,
+ // centered at the origin
+ int xs = xend - xcenter;
+ int ys = yend - ycenter;
+ int xe = x_1 - xcenter;
+ int ye = y_1 - ycenter;
+ *minx = *maxx = xs;
+ *miny = *maxy = ys;
+ if (xe > *maxx)
+ *maxx = xe;
+ else if (xe < *minx)
+ *minx = xe;
+ if (ye > *maxy)
+ *maxy = ye;
+ else if (ye < *miny)
+ *miny = ye;
+ int qs, qe; // quadrants 0..3
+ if (xs >= 0)
+ qs = (ys >= 0) ? 0 : 3;
+ else
+ qs = (ys >= 0) ? 1 : 2;
+ if (xe >= 0)
+ qe = (ye >= 0) ? 0 : 3;
+ else
+ qe = (ye >= 0) ? 1 : 2;
+ // make qs always smaller than qe
+ if ((qs > qe)
+ || ((qs == qe) && (double(xs) * ye < double(xe) * ys)))
+ qe += 4;
+ for (int i = qs; i < qe; i++)
+ switch (i % 4) {
+ case 0:
+ *maxy = radius;
+ break;
+ case 1:
+ *minx = -radius;
+ break;
+ case 2:
+ *miny = -radius;
+ break;
+ case 3:
+ *maxx = radius;
+ break;
+ }
+ *minx += xcenter;
+ *maxx += xcenter;
+ *miny += ycenter;
+ *maxy += ycenter;
+}
diff --git a/src/libs/libgroff/getcwd.c b/src/libs/libgroff/getcwd.c
new file mode 100644
index 0000000..dd8b578
--- /dev/null
+++ b/src/libs/libgroff/getcwd.c
@@ -0,0 +1,54 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Partial emulation of getcwd in terms of getwd. */
+
+#include <config.h>
+#include <sys/param.h>
+#include <string.h>
+#include <errno.h>
+
+char *getwd();
+
+char *getcwd(buf, size)
+ char *buf;
+ int size; /* POSIX says this should be size_t */
+{
+ if (size <= 0) {
+ errno = EINVAL;
+ return 0;
+ }
+ else {
+ char mybuf[MAXPATHLEN];
+ int saved_errno = errno;
+
+ errno = 0;
+ if (!getwd(mybuf)) {
+ if (errno == 0)
+ ; /* what to do? */
+ return 0;
+ }
+ errno = saved_errno;
+ if (strlen(mybuf) + 1 > size) {
+ errno = ERANGE;
+ return 0;
+ }
+ strcpy(buf, mybuf);
+ return buf;
+ }
+}
diff --git a/src/libs/libgroff/getopt.c b/src/libs/libgroff/getopt.c
new file mode 100644
index 0000000..6efa529
--- /dev/null
+++ b/src/libs/libgroff/getopt.c
@@ -0,0 +1,1241 @@
+/* Getopt for GNU.
+ NOTE: getopt is now part of the C library, so if you don't know what
+ "Keep this file name-space clean" means, talk to drepper@gnu.org
+ before changing it!
+ Copyright (C) 1987-2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+# define _NO_PROTO
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+# include <stdlib.h>
+# include <unistd.h>
+#endif /* GNU C library. */
+
+#include <string.h>
+
+#ifdef VMS
+# include <unixlib.h>
+#endif
+
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "gettext.h"
+# define _(msgid) gettext (msgid)
+#endif
+
+#if defined _LIBC && defined USE_IN_LIBIO
+# include <wchar.h>
+#endif
+
+#ifndef attribute_hidden
+# define attribute_hidden
+#endif
+
+/* Unlike standard Unix 'getopt', functions like 'getopt_long'
+ let the user intersperse the options with the other arguments.
+
+ As 'getopt_long' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Using 'getopt' or setting the environment variable POSIXLY_CORRECT
+ disables permutation.
+ Then the application's behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt.h"
+#include "getopt_int.h"
+
+/* For communication from 'getopt' to the caller.
+ When 'getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when 'ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to 'getopt'.
+
+ On entry to 'getopt', zero means this is the first call; initialize.
+
+ When 'getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, 'optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Keep a global copy of all internal members of getopt_data. */
+
+static struct _getopt_data getopt_data;
+
+
+#ifndef __GNU_LIBRARY__
+
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+
+#ifndef getenv
+extern char *getenv ();
+#endif
+
+#endif /* not __GNU_LIBRARY__ */
+
+#ifdef _LIBC
+/* Stored original parameters.
+ XXX This is no good solution. We should rather copy the args so
+ that we can compare them later. But we must not use malloc(3). */
+extern int __libc_argc;
+extern char **__libc_argv;
+
+/* Bash 2.0 gives us an environment variable containing flags
+ indicating ARGV elements that should not be considered arguments. */
+
+# ifdef USE_NONOPTION_FLAGS
+/* Defined in getopt_init.c */
+extern char *__getopt_nonoption_flags;
+# endif
+
+# ifdef USE_NONOPTION_FLAGS
+# define SWAP_FLAGS(ch1, ch2) \
+ if (d->__nonoption_flags_len > 0) \
+ { \
+ char __tmp = __getopt_nonoption_flags[ch1]; \
+ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
+ __getopt_nonoption_flags[ch2] = __tmp; \
+ }
+# else
+# define SWAP_FLAGS(ch1, ch2)
+# endif
+#else /* !_LIBC */
+# define SWAP_FLAGS(ch1, ch2)
+#endif /* _LIBC */
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ 'first_nonopt' and 'last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (char **argv, struct _getopt_data *d)
+{
+ int bottom = d->__first_nonopt;
+ int middle = d->__last_nonopt;
+ int top = d->optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ /* First make sure the handling of the '__getopt_nonoption_flags'
+ string can work normally. Our top argument must be in the range
+ of the string. */
+ if (d->__nonoption_flags_len > 0 && top >= d->__nonoption_flags_max_len)
+ {
+ /* We must extend the array. The user plays games with us and
+ presents new arguments. */
+ char *new_str = malloc (top + 1);
+ if (new_str == NULL)
+ d->__nonoption_flags_len = d->__nonoption_flags_max_len = 0;
+ else
+ {
+ memset (__mempcpy (new_str, __getopt_nonoption_flags,
+ d->__nonoption_flags_max_len),
+ '\0', top + 1 - d->__nonoption_flags_max_len);
+ d->__nonoption_flags_max_len = top + 1;
+ __getopt_nonoption_flags = new_str;
+ }
+ }
+#endif
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS (bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ d->__first_nonopt += (d->optind - d->__last_nonopt);
+ d->__last_nonopt = d->optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+static const char *
+_getopt_initialize (__attribute__((__unused__)) int argc,
+ __attribute__((__unused__)) char **argv,
+ const char *optstring, int posixly_correct,
+ struct _getopt_data *d)
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ d->__first_nonopt = d->__last_nonopt = d->optind;
+
+ d->__nextchar = NULL;
+
+ d->__posixly_correct = posixly_correct || !!getenv ("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ d->__ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ d->__ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (d->__posixly_correct)
+ d->__ordering = REQUIRE_ORDER;
+ else
+ d->__ordering = PERMUTE;
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ if (!d->__posixly_correct
+ && argc == __libc_argc && argv == __libc_argv)
+ {
+ if (d->__nonoption_flags_max_len == 0)
+ {
+ if (__getopt_nonoption_flags == NULL
+ || __getopt_nonoption_flags[0] == '\0')
+ d->__nonoption_flags_max_len = -1;
+ else
+ {
+ const char *orig_str = __getopt_nonoption_flags;
+ int len = d->__nonoption_flags_max_len = strlen (orig_str);
+ if (d->__nonoption_flags_max_len < argc)
+ d->__nonoption_flags_max_len = argc;
+ __getopt_nonoption_flags =
+ (char *) malloc (d->__nonoption_flags_max_len);
+ if (__getopt_nonoption_flags == NULL)
+ d->__nonoption_flags_max_len = -1;
+ else
+ memset (__mempcpy (__getopt_nonoption_flags, orig_str, len),
+ '\0', d->__nonoption_flags_max_len - len);
+ }
+ }
+ d->__nonoption_flags_len = d->__nonoption_flags_max_len;
+ }
+ else
+ d->__nonoption_flags_len = 0;
+#endif
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If 'getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If 'getopt' finds another option character, it returns that character,
+ updating 'optind' and 'nextchar' so that the next call to 'getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, 'getopt' returns -1.
+ Then 'optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set 'opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in 'optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in 'optarg', otherwise 'optarg' is set to zero.
+
+ If OPTSTRING starts with '-' or '+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with '--' instead of '-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a '=', or else the in next ARGV-element.
+ When 'getopt' finds a long-named option, it returns 0 if that option's
+ 'flag' field is nonzero, the value of the option's 'val' field
+ if the 'flag' field is zero.
+
+ LONGOPTS is a vector of 'struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options.
+
+ If POSIXLY_CORRECT is nonzero, behave as if the POSIXLY_CORRECT
+ environment variable were set. */
+
+int
+_getopt_internal_r (int argc, char **argv, const char *optstring,
+ const struct option *longopts, int *longind,
+ int long_only, int posixly_correct, struct _getopt_data *d)
+{
+ int print_errors = d->opterr;
+ if (optstring[0] == ':')
+ print_errors = 0;
+
+ if (argc < 1)
+ return -1;
+
+ d->optarg = NULL;
+
+ if (d->optind == 0 || !d->__initialized)
+ {
+ if (d->optind == 0)
+ d->optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize (argc, argv, optstring,
+ posixly_correct, d);
+ d->__initialized = 1;
+ }
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0' \
+ || (d->optind < d->__nonoption_flags_len \
+ && __getopt_nonoption_flags[d->optind] == '1'))
+#else
+# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')
+#endif
+
+ if (d->__nextchar == NULL || *d->__nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (d->__last_nonopt > d->optind)
+ d->__last_nonopt = d->optind;
+ if (d->__first_nonopt > d->optind)
+ d->__first_nonopt = d->optind;
+
+ if (d->__ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ exchange ((char **) argv, d);
+ else if (d->__last_nonopt != d->optind)
+ d->__first_nonopt = d->optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (d->optind < argc && NONOPTION_P)
+ d->optind++;
+ d->__last_nonopt = d->optind;
+ }
+
+ /* The special ARGV-element '--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (d->optind != argc && !strcmp (argv[d->optind], "--"))
+ {
+ d->optind++;
+
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ exchange ((char **) argv, d);
+ else if (d->__first_nonopt == d->__last_nonopt)
+ d->__first_nonopt = d->optind;
+ d->__last_nonopt = argc;
+
+ d->optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (d->optind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (d->__first_nonopt != d->__last_nonopt)
+ d->optind = d->__first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (d->__ordering == REQUIRE_ORDER)
+ return -1;
+ d->optarg = argv[d->optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ d->__nextchar = (argv[d->optind] + 1
+ + (longopts != NULL && argv[d->optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[d->optind][1] == '-'
+ || (long_only && (argv[d->optind][2]
+ || !strchr (optstring, argv[d->optind][1])))))
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar))
+ {
+ if ((unsigned int) (nameend - d->__nextchar)
+ == (unsigned int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else if (long_only
+ || pfound->has_arg != p->has_arg
+ || pfound->flag != p->flag
+ || pfound->val != p->val)
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("%s: option '%s' is ambiguous\n"),
+ argv[0], argv[d->optind]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("%s: option '%s' is ambiguous\n"),
+ argv[0], argv[d->optind]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optind++;
+ d->optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ d->optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ d->optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+ int n;
+#endif
+
+ if (argv[d->optind - 1][1] == '-')
+ {
+ /* --option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("\
+%s: option '--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#else
+ fprintf (stderr, _("\
+%s: option '--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#endif
+ }
+ else
+ {
+ /* +option or -option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("\
+%s: option '%c%s' doesn't allow an argument\n"),
+ argv[0], argv[d->optind - 1][0],
+ pfound->name);
+#else
+ fprintf (stderr, _("\
+%s: option '%c%s' doesn't allow an argument\n"),
+ argv[0], argv[d->optind - 1][0],
+ pfound->name);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (n >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#endif
+ }
+
+ d->__nextchar += strlen (d->__nextchar);
+
+ d->optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (d->optind < argc)
+ d->optarg = argv[d->optind++];
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option '%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr,
+ _("%s: option '%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[d->optind][1] == '-'
+ || strchr (optstring, *d->__nextchar) == NULL)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+ int n;
+#endif
+
+ if (argv[d->optind][1] == '-')
+ {
+ /* --option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: unrecognized option '--%s'\n"),
+ argv[0], d->__nextchar);
+#else
+ fprintf (stderr, _("%s: unrecognized option '--%s'\n"),
+ argv[0], d->__nextchar);
+#endif
+ }
+ else
+ {
+ /* +option or -option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: unrecognized option '%c%s'\n"),
+ argv[0], argv[d->optind][0], d->__nextchar);
+#else
+ fprintf (stderr, _("%s: unrecognized option '%c%s'\n"),
+ argv[0], argv[d->optind][0], d->__nextchar);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (n >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#endif
+ }
+ d->__nextchar = (char *) "";
+ d->optind++;
+ d->optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *d->__nextchar++;
+ char *temp = strchr (optstring, c);
+
+ /* Increment 'optind' when we start to process its last character. */
+ if (*d->__nextchar == '\0')
+ ++d->optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+ int n;
+#endif
+
+ if (d->__posixly_correct)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: illegal option -- %c\n"),
+ argv[0], c);
+#else
+ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c);
+#endif
+ }
+ else
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: invalid option -- %c\n"),
+ argv[0], c);
+#else
+ fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (n >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#endif
+ }
+ d->optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ d->optind++;
+ }
+ else if (d->optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+#endif
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented 'd->optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ d->optarg = argv[d->optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '=';
+ nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar))
+ {
+ if ((unsigned int) (nameend - d->__nextchar) == strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("%s: option '-W %s' is ambiguous\n"),
+ argv[0], argv[d->optind]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("%s: option '-W %s' is ambiguous\n"),
+ argv[0], argv[d->optind]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ d->optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option '-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("\
+%s: option '-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#endif
+ }
+
+ d->__nextchar += strlen (d->__nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (d->optind < argc)
+ d->optarg = argv[d->optind++];
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option '%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr,
+ _("%s: option '%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ d->__nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ d->optind++;
+ }
+ else
+ d->optarg = NULL;
+ d->__nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ d->optind++;
+ }
+ else if (d->optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option requires an argument -- %c\n"),
+ argv[0], c) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ if (_IO_fwide (stderr, 0) > 0)
+ __fwprintf (stderr, L"%s", buf);
+ else
+ fputs (buf, stderr);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+#endif
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented 'optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ d->optarg = argv[d->optind++];
+ d->__nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+_getopt_internal (int argc, char **argv, const char *optstring,
+ const struct option *longopts, int *longind,
+ int long_only, int posixly_correct)
+{
+ int result;
+
+ getopt_data.optind = optind;
+ getopt_data.opterr = opterr;
+
+ result = _getopt_internal_r (argc, argv, optstring, longopts, longind,
+ long_only, posixly_correct, &getopt_data);
+
+ optind = getopt_data.optind;
+ optarg = getopt_data.optarg;
+ optopt = getopt_data.optopt;
+
+ return result;
+}
+
+/* glibc gets a LSB-compliant getopt.
+ Standalone applications get a POSIX-compliant getopt. */
+#if _LIBC
+enum { POSIXLY_CORRECT = 0 };
+#else
+enum { POSIXLY_CORRECT = 1 };
+#endif
+
+int
+getopt (int argc, char *const *argv, const char *optstring)
+{
+ return _getopt_internal (argc, (char **) argv, optstring, NULL, NULL, 0,
+ POSIXLY_CORRECT);
+}
+
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of 'getopt'. */
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+
+ c = getopt (argc, argv, "abc:d:0123456789");
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/libs/libgroff/getopt1.c b/src/libs/libgroff/getopt1.c
new file mode 100644
index 0000000..374b3ce
--- /dev/null
+++ b/src/libs/libgroff/getopt1.c
@@ -0,0 +1,172 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+ Copyright (C) 1987-2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef _LIBC
+# include <getopt.h>
+#else
+# include "getopt.h"
+#endif
+#include "getopt_int.h"
+
+#include <stdio.h>
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (int argc, char *__getopt_argv_const *argv, const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, (char **) argv, options, long_options,
+ opt_index, 0, 0);
+}
+
+int
+_getopt_long_r (int argc, char **argv, const char *options,
+ const struct option *long_options, int *opt_index,
+ struct _getopt_data *d)
+{
+ return _getopt_internal_r (argc, argv, options, long_options, opt_index,
+ 0, 0, d);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+getopt_long_only (int argc, char *__getopt_argv_const *argv,
+ const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, (char **) argv, options, long_options,
+ opt_index, 1, 0);
+}
+
+int
+_getopt_long_only_r (int argc, char **argv, const char *options,
+ const struct option *long_options, int *opt_index,
+ struct _getopt_data *d)
+{
+ return _getopt_internal_r (argc, argv, options, long_options, opt_index,
+ 1, 0, d);
+}
+
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ static struct option long_options[] =
+ {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long (argc, argv, "abc:d:0123456789",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", optarg);
+ break;
+
+ case 'd':
+ printf ("option d with value '%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/libs/libgroff/glyphuni.cpp b/src/libs/libgroff/glyphuni.cpp
new file mode 100644
index 0000000..0d5a217
--- /dev/null
+++ b/src/libs/libgroff/glyphuni.cpp
@@ -0,0 +1,523 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg <wl@gnu.org>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "stringclass.h"
+#include "ptable.h"
+
+#include "unicode.h"
+
+struct glyph_to_unicode {
+ char *value;
+};
+
+declare_ptable(glyph_to_unicode)
+implement_ptable(glyph_to_unicode)
+
+PTABLE(glyph_to_unicode) glyph_to_unicode_table;
+
+// The entries commented out in the table below can't be used in glyph
+// names.
+
+struct S {
+ const char *key;
+ const char *value;
+} glyph_to_unicode_list[] = {
+ { "!", "0021" },
+ { "\"", "0022" },
+ { "dq", "0022" },
+ { "#", "0023" },
+ { "sh", "0023" },
+ { "$", "0024" },
+ { "Do", "0024" },
+ { "%", "0025" },
+ { "&", "0026" },
+ { "aq", "0027" },
+ { "(", "0028" },
+ { ")", "0029" },
+ { "*", "002A" },
+ { "+", "002B" },
+ { "pl", "002B" },
+ { ",", "002C" },
+ { ".", "002E" },
+ { "/", "002F" },
+ { "sl", "002F" },
+ { "0", "0030" },
+ { "1", "0031" },
+ { "2", "0032" },
+ { "3", "0033" },
+ { "4", "0034" },
+ { "5", "0035" },
+ { "6", "0036" },
+ { "7", "0037" },
+ { "8", "0038" },
+ { "9", "0039" },
+ { ":", "003A" },
+ { ";", "003B" },
+ { "<", "003C" },
+ { "=", "003D" },
+ { "eq", "003D" },
+ { ">", "003E" },
+ { "?", "003F" },
+ { "@", "0040" },
+ { "at", "0040" },
+ { "A", "0041" },
+ { "B", "0042" },
+ { "C", "0043" },
+ { "D", "0044" },
+ { "E", "0045" },
+ { "F", "0046" },
+ { "G", "0047" },
+ { "H", "0048" },
+ { "I", "0049" },
+ { "J", "004A" },
+ { "K", "004B" },
+ { "L", "004C" },
+ { "M", "004D" },
+ { "N", "004E" },
+ { "O", "004F" },
+ { "P", "0050" },
+ { "Q", "0051" },
+ { "R", "0052" },
+ { "S", "0053" },
+ { "T", "0054" },
+ { "U", "0055" },
+ { "V", "0056" },
+ { "W", "0057" },
+ { "X", "0058" },
+ { "Y", "0059" },
+ { "Z", "005A" },
+//{ "[", "005B" },
+ { "lB", "005B" },
+//{ "\\", "005C" },
+ { "rs", "005C" },
+//{ "]", "005D" },
+ { "rB", "005D" },
+ { "a^", "005E" },
+ { "^", "005E" },
+ { "ha", "005E" },
+ { "_", "005F" },
+ { "ru", "005F" },
+ { "ul", "005F" },
+ { "ga", "0060" },
+ { "a", "0061" },
+ { "b", "0062" },
+ { "c", "0063" },
+ { "d", "0064" },
+ { "e", "0065" },
+ { "f", "0066" },
+ { "ff", "0066_0066" },
+ { "Fi", "0066_0066_0069" },
+ { "Fl", "0066_0066_006C" },
+ { "fi", "0066_0069" },
+ { "fl", "0066_006C" },
+ { "g", "0067" },
+ { "h", "0068" },
+ { "i", "0069" },
+ { "j", "006A" },
+ { "k", "006B" },
+ { "l", "006C" },
+ { "m", "006D" },
+ { "n", "006E" },
+ { "o", "006F" },
+ { "p", "0070" },
+ { "q", "0071" },
+ { "r", "0072" },
+ { "s", "0073" },
+ { "t", "0074" },
+ { "u", "0075" },
+ { "v", "0076" },
+ { "w", "0077" },
+ { "x", "0078" },
+ { "y", "0079" },
+ { "z", "007A" },
+ { "lC", "007B" },
+ { "{", "007B" },
+ { "ba", "007C" },
+ { "or", "007C" },
+ { "|", "007C" },
+ { "rC", "007D" },
+ { "}", "007D" },
+ { "a~", "007E" },
+ { "~", "007E" },
+ { "ti", "007E" },
+ { "r!", "00A1" },
+ { "ct", "00A2" },
+ { "Po", "00A3" },
+ { "Cs", "00A4" },
+ { "Ye", "00A5" },
+ { "bb", "00A6" },
+ { "sc", "00A7" },
+ { "ad", "00A8" },
+ { "co", "00A9" },
+ { "Of", "00AA" },
+ { "Fo", "00AB" },
+ { "no", "00AC" },
+ { "tno", "00AC" },
+ // The soft hyphen U+00AD is meaningful only in the input file,
+ // not in the output.
+ { "rg", "00AE" },
+ { "a-", "00AF" },
+ { "de", "00B0" },
+ { "+-", "00B1" },
+ { "t+-", "00B1" },
+ { "S2", "00B2" },
+ { "S3", "00B3" },
+ { "aa", "00B4" },
+ { "mc", "00B5" },
+ { "ps", "00B6" },
+ { "pc", "00B7" },
+ { "ac", "00B8" },
+ { "S1", "00B9" },
+ { "Om", "00BA" },
+ { "Fc", "00BB" },
+ { "14", "00BC" },
+ { "12", "00BD" },
+ { "34", "00BE" },
+ { "r?", "00BF" },
+ { "`A", "00C0" },
+ { "'A", "00C1" },
+ { "^A", "00C2" },
+ { "~A", "00C3" },
+ { ":A", "00C4" },
+ { "oA", "00C5" },
+ { "AE", "00C6" },
+ { ",C", "00C7" },
+ { "`E", "00C8" },
+ { "'E", "00C9" },
+ { "^E", "00CA" },
+ { ":E", "00CB" },
+ { "`I", "00CC" },
+ { "'I", "00CD" },
+ { "^I", "00CE" },
+ { ":I", "00CF" },
+ { "-D", "00D0" },
+ { "~N", "00D1" },
+ { "`O", "00D2" },
+ { "'O", "00D3" },
+ { "^O", "00D4" },
+ { "~O", "00D5" },
+ { ":O", "00D6" },
+ { "mu", "00D7" },
+ { "tmu", "00D7" },
+ { "/O", "00D8" },
+ { "`U", "00D9" },
+ { "'U", "00DA" },
+ { "^U", "00DB" },
+ { ":U", "00DC" },
+ { "'Y", "00DD" },
+ { "TP", "00DE" },
+ { "ss", "00DF" },
+ { "`a", "00E0" },
+ { "'a", "00E1" },
+ { "^a", "00E2" },
+ { "~a", "00E3" },
+ { ":a", "00E4" },
+ { "oa", "00E5" },
+ { "ae", "00E6" },
+ { ",c", "00E7" },
+ { "`e", "00E8" },
+ { "'e", "00E9" },
+ { "^e", "00EA" },
+ { ":e", "00EB" },
+ { "`i", "00EC" },
+ { "'i", "00ED" },
+ { "^i", "00EE" },
+ { ":i", "00EF" },
+ { "Sd", "00F0" },
+ { "~n", "00F1" },
+ { "`o", "00F2" },
+ { "'o", "00F3" },
+ { "^o", "00F4" },
+ { "~o", "00F5" },
+ { ":o", "00F6" },
+ { "di", "00F7" },
+ { "tdi", "00F7" },
+ { "/o", "00F8" },
+ { "`u", "00F9" },
+ { "'u", "00FA" },
+ { "^u", "00FB" },
+ { ":u", "00FC" },
+ { "'y", "00FD" },
+ { "Tp", "00FE" },
+ { ":y", "00FF" },
+ { "'C", "0106" },
+ { "'c", "0107" },
+ { ".i", "0131" },
+ { "IJ", "0132" },
+ { "ij", "0133" },
+ { "/L", "0141" },
+ { "/l", "0142" },
+ { "OE", "0152" },
+ { "oe", "0153" },
+ { "vS", "0160" },
+ { "vs", "0161" },
+ { ":Y", "0178" },
+ { "vZ", "017D" },
+ { "vz", "017E" },
+ { "Fn", "0192" },
+ { ".j", "0237" },
+ { "ah", "02C7" },
+ { "ab", "02D8" },
+ { "a.", "02D9" },
+ { "ao", "02DA" },
+ { "ho", "02DB" },
+ { "a\"", "02DD" },
+ { "*A", "0391" },
+ { "*B", "0392" },
+ { "*G", "0393" },
+ { "*D", "0394" },
+ { "*E", "0395" },
+ { "*Z", "0396" },
+ { "*Y", "0397" },
+ { "*H", "0398" },
+ { "*I", "0399" },
+ { "*K", "039A" },
+ { "*L", "039B" },
+ { "*M", "039C" },
+ { "*N", "039D" },
+ { "*C", "039E" },
+ { "*O", "039F" },
+ { "*P", "03A0" },
+ { "*R", "03A1" },
+ { "*S", "03A3" },
+ { "*T", "03A4" },
+ { "*U", "03A5" },
+ { "*F", "03A6" },
+ { "*X", "03A7" },
+ { "*Q", "03A8" },
+ { "*W", "03A9" },
+ { "*a", "03B1" },
+ { "*b", "03B2" },
+ { "*g", "03B3" },
+ { "*d", "03B4" },
+ { "*e", "03B5" },
+ { "*z", "03B6" },
+ { "*y", "03B7" },
+ { "*h", "03B8" },
+ { "*i", "03B9" },
+ { "*k", "03BA" },
+ { "*l", "03BB" },
+ { "*m", "03BC" },
+ { "*n", "03BD" },
+ { "*c", "03BE" },
+ { "*o", "03BF" },
+ { "*p", "03C0" },
+ { "*r", "03C1" },
+ { "ts", "03C2" },
+ { "*s", "03C3" },
+ { "*t", "03C4" },
+ { "*u", "03C5" },
+ // the curly phi variant
+ { "+f", "03C6" },
+ { "*x", "03C7" },
+ { "*q", "03C8" },
+ { "*w", "03C9" },
+ { "+h", "03D1" },
+ // the stroked phi variant
+ { "*f", "03D5" },
+ { "+p", "03D6" },
+ { "+e", "03F5" },
+ // '-' and 'hy' denote a HYPHEN, usually a glyph with a smaller width than
+ // the MINUS sign. Users who are viewing broken man pages that assume
+ // that '-' denotes a U+002D character can either fix the broken man pages
+ // or apply the workaround described in the PROBLEMS file.
+ { "-", "2010" },
+ { "hy", "2010" },
+ { "en", "2013" },
+ { "em", "2014" },
+ { "`", "2018" },
+ { "oq", "2018" },
+ { "'", "2019" },
+ { "cq", "2019" },
+ { "bq", "201A" },
+ { "lq", "201C" },
+ { "rq", "201D" },
+ { "Bq", "201E" },
+ { "dg", "2020" },
+ { "dd", "2021" },
+ { "bu", "2022" },
+ { "%0", "2030" },
+ { "fm", "2032" },
+ { "sd", "2033" },
+ { "fo", "2039" },
+ { "fc", "203A" },
+ { "rn", "203E" },
+ { "f/", "2044" },
+ { "eu", "20AC" },
+ { "Eu", "20AC" },
+ { "-h", "210F" },
+ { "hbar", "210F" },
+ { "Im", "2111" },
+ { "wp", "2118" },
+ { "Re", "211C" },
+ { "tm", "2122" },
+ { "Ah", "2135" },
+ { "18", "215B" },
+ { "38", "215C" },
+ { "58", "215D" },
+ { "78", "215E" },
+ { "<-", "2190" },
+ { "ua", "2191" },
+ { "->", "2192" },
+ { "da", "2193" },
+ { "<>", "2194" },
+ { "va", "2195" },
+ { "CR", "21B5" },
+ { "lA", "21D0" },
+ { "uA", "21D1" },
+ { "rA", "21D2" },
+ { "dA", "21D3" },
+ { "hA", "21D4" },
+ { "vA", "21D5" },
+ { "fa", "2200" },
+ { "pd", "2202" },
+ { "te", "2203" },
+ { "es", "2205" },
+ { "gr", "2207" },
+ { "mo", "2208" },
+ { "nm", "2209" },
+ { "st", "220B" },
+ { "product", "220F" },
+ { "coproduct", "2210" },
+ { "sum", "2211" },
+ // 'mi' and '\-' represent a MINUS sign. But it is used in many man pages
+ // to denote the U+002D character that introduces a command-line option.
+ // For devices that support copy&paste, such as devhtml and devutf8, the
+ // user can apply the workaround described in the PROBLEMS file.
+ { "\\-", "2212" },
+ { "mi", "2212" },
+ { "-+", "2213" },
+ { "**", "2217" },
+ { "sqrt", "221A" },
+ { "sr", "221A" },
+ { "pt", "221D" },
+ { "if", "221E" },
+ { "/_", "2220" },
+ { "AN", "2227" },
+ { "OR", "2228" },
+ { "ca", "2229" },
+ { "cu", "222A" },
+ { "is", "222B" },
+ { "integral", "222B" },
+ { "tf", "2234" },
+ { "3d", "2234" },
+ { "ap", "223C" },
+ { "|=", "2243" },
+ { "=~", "2245" },
+ { "~~", "2248" },
+ { "~=", "2248" },
+ { "!=", "2260" },
+ { "==", "2261" },
+ { "ne", "2262" },
+ { "<=", "2264" },
+ { ">=", "2265" },
+ { "<<", "226A" },
+ { ">>", "226B" },
+ { "sb", "2282" },
+ { "sp", "2283" },
+ { "nb", "2284" },
+ { "nc", "2285" },
+ { "ib", "2286" },
+ { "ip", "2287" },
+ { "c+", "2295" },
+ { "c*", "2297" },
+ { "pp", "22A5" },
+ { "md", "22C5" },
+ { "lc", "2308" },
+ { "rc", "2309" },
+ { "lf", "230A" },
+ { "rf", "230B" },
+ { "parenlefttp", "239B" },
+ { "parenleftex", "239C" },
+ { "parenleftbt", "239D" },
+ { "parenrighttp", "239E" },
+ { "parenrightex", "239F" },
+ { "parenrightbt", "23A0" },
+ { "bracketlefttp", "23A1" },
+ { "bracketleftex", "23A2" },
+ { "bracketleftbt", "23A3" },
+ { "bracketrighttp", "23A4" },
+ { "bracketrightex", "23A5" },
+ { "bracketrightbt", "23A6" },
+ { "lt", "23A7" },
+ { "bracelefttp", "23A7" },
+ { "lk", "23A8" },
+ { "braceleftmid", "23A8" },
+ { "lb", "23A9" },
+ { "braceleftbt", "23A9" },
+ { "bv", "23AA" },
+ { "braceex", "23AA" },
+ { "braceleftex", "23AA" },
+ { "bracerightex", "23AA" },
+ { "rt", "23AB" },
+ { "bracerighttp", "23AB" },
+ { "rk", "23AC" },
+ { "bracerightmid", "23AC" },
+ { "rb", "23AD" },
+ { "bracerightbt", "23AD" },
+ { "an", "23AF" },
+ { "br", "2502" },
+ { "sq", "25A1" },
+ { "lz", "25CA" },
+ { "ci", "25CB" },
+ { "lh", "261C" },
+ { "rh", "261E" },
+ { "SP", "2660" },
+ { "CL", "2663" },
+ { "HE", "2665" },
+ { "DI", "2666" },
+ { "OK", "2713" },
+ // The 'left angle bracket' and 'right angle bracket' could be mapped to
+ // either U+2329,U+232A or U+3008,U+3009 or U+27E8,U+27E9. But the first
+ // and second possibility are double-width characters (see Unicode's
+ // 'DerivedEastAsianWidth.txt' file) and are therefore not suitable for
+ // general use, whereas the third possibility is single-width.
+ //
+ // The devhtml device overrides this mapping, because
+ //
+ // http://www.w3.org/TR/html401/sgml/entities.html
+ //
+ // says that in HTML, '&lang;' and '&rang;' are U+2329,U+232A,
+ // respectively.
+ { "la", "27E8" },
+ { "ra", "27E9" },
+};
+
+// global constructor
+static struct glyph_to_unicode_init {
+ glyph_to_unicode_init();
+} _glyph_to_unicode_init;
+
+glyph_to_unicode_init::glyph_to_unicode_init()
+{
+ for (unsigned int i = 0;
+ i < sizeof(glyph_to_unicode_list)/sizeof(glyph_to_unicode_list[0]);
+ i++) {
+ glyph_to_unicode *gtu = new glyph_to_unicode[1];
+ gtu->value = (char *)glyph_to_unicode_list[i].value;
+ glyph_to_unicode_table.define(glyph_to_unicode_list[i].key, gtu);
+ }
+}
+
+const char *glyph_name_to_unicode(const char *s)
+{
+ glyph_to_unicode *result = glyph_to_unicode_table.lookup(s);
+ return result ? result->value : 0;
+}
diff --git a/src/libs/libgroff/htmlhint.cpp b/src/libs/libgroff/htmlhint.cpp
new file mode 100644
index 0000000..8ebb84e
--- /dev/null
+++ b/src/libs/libgroff/htmlhint.cpp
@@ -0,0 +1,60 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "nonposix.h"
+#include "stringclass.h"
+#include "html-strings.h"
+
+/*
+ * This file contains a very simple set of routines which might
+ * be shared by preprocessors. It allows a preprocessor to indicate
+ * when an inline image should be created.
+ * This string is intercepted by pre-grohtml and substituted for
+ * the image name and suppression escapes.
+ *
+ * pre-html runs troff twice, once with -Thtml (or -Txhtml) and once
+ * with -Tps. 'troff -Thtml' (and 'troff -Txhtml') emits a
+ * <src='image'.png> tag and the postscript device driver works out
+ * the min/max limits of the graphic region. These region limits are
+ * read by pre-html and an image is generated via
+ *
+ * troff -Tps -> gs -> png
+ */
+
+/*
+ * html_begin_suppress - emit a start of image tag which will be seen
+ * by pre-html.
+ */
+void html_begin_suppress()
+{
+ put_string(HTML_IMAGE_INLINE_BEGIN, stdout);
+}
+
+/*
+ * html_end_suppress - emit an end of image tag which will be seen
+ * by pre-html.
+ */
+void html_end_suppress()
+{
+ put_string(HTML_IMAGE_INLINE_END, stdout);
+}
diff --git a/src/libs/libgroff/hypot.cpp b/src/libs/libgroff/hypot.cpp
new file mode 100644
index 0000000..fa0977d
--- /dev/null
+++ b/src/libs/libgroff/hypot.cpp
@@ -0,0 +1,34 @@
+/* Copyright (C) 2005-2020 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+double groff_hypot(double x, double y)
+{
+ double result = hypot(x, y);
+
+#ifdef __INTERIX
+ /* hypot() on Interix is broken */
+ if (isnan(result) && !isnan(x) && !isnan(y))
+ return 0.0;
+#endif
+
+ return result;
+}
diff --git a/src/libs/libgroff/iftoa.c b/src/libs/libgroff/iftoa.c
new file mode 100644
index 0000000..87aec58
--- /dev/null
+++ b/src/libs/libgroff/iftoa.c
@@ -0,0 +1,76 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char *if_to_a(int i, int decimal_point)
+{
+ static char buf[INT_DIGITS + 3]; // INT_DIGITS + '-', '.', '\0'
+ char *p = buf + INT_DIGITS + 2;
+ int point = 0;
+ buf[INT_DIGITS + 2] = '\0';
+ /* assert(decimal_point <= INT_DIGITS); */
+ if (i >= 0) {
+ do {
+ *--p = '0' + (i % 10);
+ i /= 10;
+ if (++point == decimal_point)
+ *--p = '.';
+ } while (i != 0 || point < decimal_point);
+ }
+ else { /* i < 0 */
+ do {
+ *--p = '0' - (i % 10);
+ i /= 10;
+ if (++point == decimal_point)
+ *--p = '.';
+ } while (i != 0 || point < decimal_point);
+ *--p = '-';
+ }
+ if (decimal_point > 0) {
+ char *q;
+ /* there must be a dot, so this will terminate */
+ for (q = buf + INT_DIGITS + 2; q[-1] == '0'; --q)
+ ;
+ if (q[-1] == '.') {
+ if (q - 1 == p) {
+ q[-1] = '0';
+ q[0] = '\0';
+ }
+ else
+ q[-1] = '\0';
+ }
+ else
+ *q = '\0';
+ }
+ return p;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/invalid.cpp b/src/libs/libgroff/invalid.cpp
new file mode 100644
index 0000000..59e4619
--- /dev/null
+++ b/src/libs/libgroff/invalid.cpp
@@ -0,0 +1,59 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+// Table of invalid input characters.
+
+char invalid_char_table[256]= {
+#ifndef IS_EBCDIC_HOST
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+#else
+ 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1,
+ 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+#endif
+};
diff --git a/src/libs/libgroff/itoa.c b/src/libs/libgroff/itoa.c
new file mode 100644
index 0000000..a573b2c
--- /dev/null
+++ b/src/libs/libgroff/itoa.c
@@ -0,0 +1,67 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char *i_to_a(int i)
+{
+ /* Room for INT_DIGITS digits, - and '\0' */
+ static char buf[INT_DIGITS + 2];
+ char *p = buf + INT_DIGITS + 1; /* points to terminating '\0' */
+ if (i >= 0) {
+ do {
+ *--p = '0' + (i % 10);
+ i /= 10;
+ } while (i != 0);
+ return p;
+ }
+ else { /* i < 0 */
+ do {
+ *--p = '0' - (i % 10);
+ i /= 10;
+ } while (i != 0);
+ *--p = '-';
+ }
+ return p;
+}
+
+const char *ui_to_a(unsigned int i)
+{
+ /* Room for UINT_DIGITS digits and '\0' */
+ static char buf[UINT_DIGITS + 1];
+ char *p = buf + UINT_DIGITS; /* points to terminating '\0' */
+ do {
+ *--p = '0' + (i % 10);
+ i /= 10;
+ } while (i != 0);
+ return p;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/lf.cpp b/src/libs/libgroff/lf.cpp
new file mode 100644
index 0000000..9c255fb
--- /dev/null
+++ b/src/libs/libgroff/lf.cpp
@@ -0,0 +1,78 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "cset.h"
+#include "stringclass.h"
+#include "lf.h"
+
+#include <ctype.h>
+
+extern void change_filename(const char *);
+extern void change_lineno(int);
+
+int interpret_lf_args(const char *p)
+{
+ while (*p == ' ')
+ p++;
+ if (!csdigit(*p))
+ return 0;
+ int ln = 0;
+ do {
+ ln *= 10;
+ ln += *p++ - '0';
+ } while (csdigit(*p));
+ if (*p != ' ' && *p != '\n' && *p != '\0')
+ return 0;
+ while (*p == ' ')
+ p++;
+ if (*p == '\0' || *p == '\n') {
+ change_lineno(ln);
+ return 1;
+ }
+ const char *q;
+ for (q = p;
+ *q != '\0' && *q != ' ' && *q != '\n' && *q != '\\';
+ q++)
+ ;
+ string tem(p, q - p);
+ while (*q == ' ')
+ q++;
+ if (*q != '\n' && *q != '\0')
+ return 0;
+ tem += '\0';
+ change_filename(tem.contents());
+ change_lineno(ln);
+ return 1;
+}
+
+#if defined(__MSDOS__) || (defined(_WIN32) && !defined(__CYGWIN__))
+void normalize_for_lf (string &fn)
+{
+ int fnlen = fn.length();
+ for (int i = 0; i < fnlen; i++) {
+ if (fn[i] == '\\')
+ fn[i] = '/';
+ }
+}
+#else
+void normalize_for_lf (string &)
+{
+}
+#endif
diff --git a/src/libs/libgroff/libgroff.am b/src/libs/libgroff/libgroff.am
new file mode 100644
index 0000000..be6d9e2
--- /dev/null
+++ b/src/libs/libgroff/libgroff.am
@@ -0,0 +1,182 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+libgroff_srcdir = $(top_srcdir)/src/libs/libgroff
+noinst_LIBRARIES += libgroff.a
+libgroff_a_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -D__GETOPT_PREFIX=groff_ \
+ -DENABLE_RELOCATABLE=1 \
+ -DLIBDIR=\"$(libdir)\"
+
+# Build from OBJS
+libgroff_a_SOURCES = \
+ src/libs/libgroff/assert.cpp \
+ src/libs/libgroff/change_lf.cpp \
+ src/libs/libgroff/cmap.cpp \
+ src/libs/libgroff/color.cpp \
+ src/libs/libgroff/cset.cpp\
+ src/libs/libgroff/curtime.cpp \
+ src/libs/libgroff/device.cpp \
+ src/libs/libgroff/errarg.cpp \
+ src/libs/libgroff/error.cpp \
+ src/libs/libgroff/fatal.cpp \
+ src/libs/libgroff/filename.cpp \
+ src/libs/libgroff/font.cpp \
+ src/libs/libgroff/fontfile.cpp \
+ src/libs/libgroff/geometry.cpp \
+ src/libs/libgroff/getopt.c \
+ src/libs/libgroff/getopt1.c \
+ src/libs/libgroff/glyphuni.cpp \
+ src/libs/libgroff/htmlhint.cpp \
+ src/libs/libgroff/hypot.cpp \
+ src/libs/libgroff/iftoa.c \
+ src/libs/libgroff/invalid.cpp \
+ src/libs/libgroff/itoa.c \
+ src/libs/libgroff/lf.cpp \
+ src/libs/libgroff/lineno.cpp \
+ src/libs/libgroff/localcharset.c \
+ src/libs/libgroff/macropath.cpp \
+ src/libs/libgroff/matherr.c \
+ src/libs/libgroff/maxfilename.cpp \
+ src/libs/libgroff/maxpathname.cpp \
+ src/libs/libgroff/mksdir.cpp \
+ src/libs/libgroff/nametoindex.cpp \
+ src/libs/libgroff/paper.cpp \
+ src/libs/libgroff/prime.cpp \
+ src/libs/libgroff/progname.c \
+ src/libs/libgroff/ptable.cpp \
+ src/libs/libgroff/quotearg.c \
+ src/libs/libgroff/relocate.cpp \
+ src/libs/libgroff/searchpath.cpp \
+ src/libs/libgroff/spawnvp.c \
+ src/libs/libgroff/string.cpp \
+ src/libs/libgroff/strsave.cpp \
+ src/libs/libgroff/symbol.cpp \
+ src/libs/libgroff/tmpfile.cpp \
+ src/libs/libgroff/tmpname.cpp \
+ src/libs/libgroff/unicode.cpp \
+ src/libs/libgroff/uniglyph.cpp \
+ src/libs/libgroff/uniuni.cpp \
+ src/libs/libgroff/relocatable.h
+if USE_GROFF_ALLOCATOR
+libgroff_a_SOURCES += \
+ src/libs/libgroff/new.cpp
+endif
+nodist_libgroff_a_SOURCES = src/libs/libgroff/version.cpp
+
+# TODO: these .c files could be removed (use gnulib instead).
+EXTRA_DIST += \
+ src/libs/libgroff/mkstemp.cpp \
+ src/libs/libgroff/fmod.c \
+ src/libs/libgroff/getcwd.c \
+ src/libs/libgroff/putenv.c \
+ src/libs/libgroff/strcasecmp.c \
+ src/libs/libgroff/strerror.c \
+ src/libs/libgroff/strncasecmp.c \
+ src/libs/libgroff/strtol.c \
+ src/libs/libgroff/config.charset \
+ src/libs/libgroff/ref-add.sin \
+ src/libs/libgroff/ref-del.sin \
+ src/libs/libgroff/make-uniuni
+
+CLEANFILES += \
+ src/libs/libgroff/version.cpp \
+ charset.alias \
+ ref-add.sed \
+ ref-del.sed
+
+# .o files have a 'libgroff_a-' prefix because we set
+# libgroff_a_CPPFLAGS.
+src/libs/libgroff/libgroff_a-device.$(OBJEXT): defs.h
+src/libs/libgroff/libgroff_a-fontfile.$(OBJEXT): defs.h
+src/libs/libgroff/libgroff_a-macropath.$(OBJEXT): defs.h
+src/libs/libgroff/libgroff_a-relocate.$(OBJEXT): defs.h
+
+src/libs/libgroff/version.cpp: $(top_srcdir)/.version
+ $(AM_V_at)printf 'const char *version_string = "%s.%s";\n' \
+ $(MAJOR_VERSION) $(MINOR_VERSION) > $@.tmp
+ $(AM_V_at)printf 'const char *revision_string = "%s";\n' \
+ $(REVISION) >> $@.tmp
+ $(AM_V_at)printf \
+ 'extern "C" {\nconst char *Version_string = "%s";\n}\n' \
+ $(VERSION) >> $@.tmp
+ $(AM_V_GEN)mv $@.tmp $@
+
+# Data for localcharset.c. Taken from libiconv/libcharset.
+
+LIBGROFF_PACKAGE = groff
+
+all: charset.alias ref-add.sed ref-del.sed
+
+charset.alias: $(libgroff_srcdir)/config.charset
+ $(AM_V_GEN)$(SHELL) $(libgroff_srcdir)/config.charset \
+ '$(HOST)' > t-$@ \
+ && mv t-$@ $@
+
+ref-add.sed: $(libgroff_srcdir)/ref-add.sin
+ $(AM_V_GEN)sed -e '/^#/d' \
+ -e 's/@''PACKAGE''@/$(LIBGROFF_PACKAGE)/g' \
+ $(libgroff_srcdir)/ref-add.sin > t-$@ \
+ && mv t-$@ $@
+
+ref-del.sed: $(libgroff_srcdir)/ref-del.sin
+ $(AM_V_GEN)sed -e '/^#/d' \
+ -e 's/@''PACKAGE''@/$(LIBGROFF_PACKAGE)/g' \
+ $(libgroff_srcdir)/ref-del.sin > t-$@ \
+ && mv t-$@ $@
+
+install-data-local: install_charset_data
+install_charset_data:
+ -test $(GLIBC21) != no || $(mkinstalldirs) $(DESTDIR)$(libdir)
+ if test -f $(DESTDIR)$(libdir)/charset.alias; then \
+ sed -f ref-add.sed $(DESTDIR)$(libdir)/charset.alias \
+ > $(DESTDIR)$(libdir)/t-charset.alias; \
+ $(INSTALL_DATA) $(DESTDIR)$(libdir)/t-charset.alias \
+ $(DESTDIR)$(libdir)/charset.alias; \
+ rm -f $(DESTDIR)$(libdir)/t-charset.alias; \
+ else \
+ if test $(GLIBC21) = no; then \
+ sed -f ref-add.sed charset.alias \
+ > $(DESTDIR)$(libdir)/t-charset.alias; \
+ $(INSTALL_DATA) $(DESTDIR)$(libdir)/t-charset.alias \
+ $(DESTDIR)$(libdir)/charset.alias; \
+ rm -f $(DESTDIR)$(libdir)/t-charset.alias; \
+ fi; \
+ fi
+
+uninstall-local: uninstall_charset_data
+uninstall_charset_data:
+ -if test -f $(DESTDIR)$(libdir)/charset.alias; then \
+ sed -f ref-del.sed $(DESTDIR)$(libdir)/charset.alias \
+ > $(DESTDIR)$(libdir)/t-charset.alias; \
+ if grep '^# Packages using this file: $$' \
+ $(DESTDIR)$(libdir)/t-charset.alias > /dev/null; then \
+ rm -f $(DESTDIR)$(libdir)/charset.alias; \
+ else \
+ $(INSTALL_DATA) $(DESTDIR)$(libdir)/t-charset.alias \
+ $(DESTDIR)$(libdir)/charset.alias; \
+ fi; \
+ rm -f $(DESTDIR)$(libdir)/t-charset.alias; \
+ fi
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/libs/libgroff/lineno.cpp b/src/libs/libgroff/lineno.cpp
new file mode 100644
index 0000000..6e356c7
--- /dev/null
+++ b/src/libs/libgroff/lineno.cpp
@@ -0,0 +1,20 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+// This global stores the line number of the input file being
+// processed by troff, an output driver, or other program.
+int current_lineno = 0;
diff --git a/src/libs/libgroff/localcharset.c b/src/libs/libgroff/localcharset.c
new file mode 100644
index 0000000..1bc07a4
--- /dev/null
+++ b/src/libs/libgroff/localcharset.c
@@ -0,0 +1,579 @@
+/* Determine a canonical name for the current locale's character encoding.
+
+ Copyright (C) 2000-2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <bruno@clisp.org>. */
+
+#include <config.h>
+
+/* Specification. */
+#include "localcharset.h"
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#if defined __APPLE__ && defined __MACH__ && HAVE_LANGINFO_CODESET
+# define DARWIN7 /* Darwin 7 or newer, i.e. Mac OS X 10.3 or newer */
+#endif
+
+#if defined _WIN32 || defined __WIN32__
+# define WINDOWS_NATIVE
+# include <locale.h>
+#endif
+
+#if defined __EMX__
+/* Assume EMX program runs on OS/2, even if compiled under DOS. */
+# ifndef OS2
+# define OS2
+# endif
+#endif
+
+#if !defined WINDOWS_NATIVE
+# include <unistd.h>
+# if HAVE_LANGINFO_CODESET
+# include <langinfo.h>
+# else
+# if 0 /* see comment below */
+# include <locale.h>
+# endif
+# endif
+# ifdef __CYGWIN__
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# endif
+#elif defined WINDOWS_NATIVE
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif
+#if defined OS2
+# define INCL_DOS
+# include <os2.h>
+#endif
+
+/* For MB_CUR_MAX_L */
+#if defined DARWIN7
+# include <xlocale.h>
+#endif
+
+#if ENABLE_RELOCATABLE
+# include "relocatable.h"
+#else
+# define relocate(pathname) (pathname)
+#endif
+
+/* Get LIBDIR. */
+#ifndef LIBDIR
+# include "configmake.h"
+#endif
+
+/* Define O_NOFOLLOW to 0 on platforms where it does not exist. */
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+#if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
+ /* Native Windows, Cygwin, OS/2, DOS */
+# define ISSLASH(C) ((C) == '/' || (C) == '\\')
+#endif
+
+#ifndef DIRECTORY_SEPARATOR
+# define DIRECTORY_SEPARATOR '/'
+#endif
+
+#ifndef ISSLASH
+# define ISSLASH(C) ((C) == DIRECTORY_SEPARATOR)
+#endif
+
+#if HAVE_DECL_GETC_UNLOCKED
+# undef getc
+# define getc getc_unlocked
+#endif
+
+/* The following static variable is declared 'volatile' to avoid a
+ possible multithread problem in the function get_charset_aliases. If we
+ are running in a threaded environment, and if two threads initialize
+ 'charset_aliases' simultaneously, both will produce the same value,
+ and everything will be ok if the two assignments to 'charset_aliases'
+ are atomic. But I don't know what will happen if the two assignments mix. */
+#if __STDC__ != 1
+# define volatile /* empty */
+#endif
+/* Pointer to the contents of the charset.alias file, if it has already been
+ read, else NULL. Its format is:
+ ALIAS_1 '\0' CANONICAL_1 '\0' ... ALIAS_n '\0' CANONICAL_n '\0' '\0' */
+static const char * volatile charset_aliases;
+
+/* Return a pointer to the contents of the charset.alias file. */
+static const char *
+get_charset_aliases (void)
+{
+ const char *cp;
+
+ cp = charset_aliases;
+ if (cp == NULL)
+ {
+#if !(defined DARWIN7 || defined VMS || defined WINDOWS_NATIVE || defined __CYGWIN__)
+ const char *dir;
+ const char *base = "charset.alias";
+ char *file_name;
+
+ /* Make it possible to override the charset.alias location. This is
+ necessary for running the testsuite before "make install". */
+ dir = getenv ("CHARSETALIASDIR");
+ if (dir == NULL || dir[0] == '\0')
+ dir = relocate (LIBDIR);
+
+ /* Concatenate dir and base into freshly allocated file_name. */
+ {
+ size_t dir_len = strlen (dir);
+ size_t base_len = strlen (base);
+ int add_slash = (dir_len > 0 && !ISSLASH (dir[dir_len - 1]));
+ file_name = (char *) malloc (dir_len + add_slash + base_len + 1);
+ if (file_name != NULL)
+ {
+ memcpy (file_name, dir, dir_len);
+ if (add_slash)
+ file_name[dir_len] = DIRECTORY_SEPARATOR;
+ memcpy (file_name + dir_len + add_slash, base, base_len + 1);
+ }
+ }
+
+ if (file_name == NULL)
+ /* Out of memory. Treat the file as empty. */
+ cp = "";
+ else
+ {
+ int fd;
+
+ /* Open the file. Reject symbolic links on platforms that support
+ O_NOFOLLOW. This is a security feature. Without it, an attacker
+ could retrieve parts of the contents (namely, the tail of the
+ first line that starts with "* ") of an arbitrary file by placing
+ a symbolic link to that file under the name "charset.alias" in
+ some writable directory and defining the environment variable
+ CHARSETALIASDIR to point to that directory. */
+ fd = open (file_name,
+ O_RDONLY | (HAVE_WORKING_O_NOFOLLOW ? O_NOFOLLOW : 0));
+ if (fd < 0)
+ /* File not found. Treat it as empty. */
+ cp = "";
+ else
+ {
+ FILE *fp;
+
+ fp = fdopen (fd, "r");
+ if (fp == NULL)
+ {
+ /* Out of memory. Treat the file as empty. */
+ close (fd);
+ cp = "";
+ }
+ else
+ {
+ /* Parse the file's contents. */
+ char *res_ptr = NULL;
+ size_t res_size = 0;
+
+ for (;;)
+ {
+ int c;
+ char buf1[50+1];
+ char buf2[50+1];
+ size_t l1, l2;
+ char *old_res_ptr;
+
+ c = getc (fp);
+ if (c == EOF)
+ break;
+ if (c == '\n' || c == ' ' || c == '\t')
+ continue;
+ if (c == '#')
+ {
+ /* Skip comment, to end of line. */
+ do
+ c = getc (fp);
+ while (!(c == EOF || c == '\n'));
+ if (c == EOF)
+ break;
+ continue;
+ }
+ ungetc (c, fp);
+ if (fscanf (fp, "%50s %50s", buf1, buf2) < 2)
+ break;
+ l1 = strlen (buf1);
+ l2 = strlen (buf2);
+ old_res_ptr = res_ptr;
+ if (res_size == 0)
+ {
+ res_size = l1 + 1 + l2 + 1;
+ res_ptr = (char *) malloc (res_size + 1);
+ }
+ else
+ {
+ res_size += l1 + 1 + l2 + 1;
+ res_ptr = (char *) realloc (res_ptr, res_size + 1);
+ }
+ if (res_ptr == NULL)
+ {
+ /* Out of memory. */
+ res_size = 0;
+ free (old_res_ptr);
+ break;
+ }
+ strcpy (res_ptr + res_size - (l2 + 1) - (l1 + 1), buf1);
+ strcpy (res_ptr + res_size - (l2 + 1), buf2);
+ }
+ fclose (fp);
+ if (res_size == 0)
+ cp = "";
+ else
+ {
+ *(res_ptr + res_size) = '\0';
+ cp = res_ptr;
+ }
+ }
+ }
+
+ free (file_name);
+ }
+
+#else
+
+# if defined DARWIN7
+ /* To avoid the trouble of installing a file that is shared by many
+ GNU packages -- many packaging systems have problems with this --,
+ simply inline the aliases here. */
+ cp = "ISO8859-1" "\0" "ISO-8859-1" "\0"
+ "ISO8859-2" "\0" "ISO-8859-2" "\0"
+ "ISO8859-4" "\0" "ISO-8859-4" "\0"
+ "ISO8859-5" "\0" "ISO-8859-5" "\0"
+ "ISO8859-7" "\0" "ISO-8859-7" "\0"
+ "ISO8859-9" "\0" "ISO-8859-9" "\0"
+ "ISO8859-13" "\0" "ISO-8859-13" "\0"
+ "ISO8859-15" "\0" "ISO-8859-15" "\0"
+ "KOI8-R" "\0" "KOI8-R" "\0"
+ "KOI8-U" "\0" "KOI8-U" "\0"
+ "CP866" "\0" "CP866" "\0"
+ "CP949" "\0" "CP949" "\0"
+ "CP1131" "\0" "CP1131" "\0"
+ "CP1251" "\0" "CP1251" "\0"
+ "eucCN" "\0" "GB2312" "\0"
+ "GB2312" "\0" "GB2312" "\0"
+ "eucJP" "\0" "EUC-JP" "\0"
+ "eucKR" "\0" "EUC-KR" "\0"
+ "Big5" "\0" "BIG5" "\0"
+ "Big5HKSCS" "\0" "BIG5-HKSCS" "\0"
+ "GBK" "\0" "GBK" "\0"
+ "GB18030" "\0" "GB18030" "\0"
+ "SJIS" "\0" "SHIFT_JIS" "\0"
+ "ARMSCII-8" "\0" "ARMSCII-8" "\0"
+ "PT154" "\0" "PT154" "\0"
+ /*"ISCII-DEV" "\0" "?" "\0"*/
+ "*" "\0" "UTF-8" "\0";
+# endif
+
+# if defined VMS
+ /* To avoid the troubles of an extra file charset.alias_vms in the
+ sources of many GNU packages, simply inline the aliases here. */
+ /* The list of encodings is taken from the OpenVMS 7.3-1 documentation
+ "Compaq C Run-Time Library Reference Manual for OpenVMS systems"
+ section 10.7 "Handling Different Character Sets". */
+ cp = "ISO8859-1" "\0" "ISO-8859-1" "\0"
+ "ISO8859-2" "\0" "ISO-8859-2" "\0"
+ "ISO8859-5" "\0" "ISO-8859-5" "\0"
+ "ISO8859-7" "\0" "ISO-8859-7" "\0"
+ "ISO8859-8" "\0" "ISO-8859-8" "\0"
+ "ISO8859-9" "\0" "ISO-8859-9" "\0"
+ /* Japanese */
+ "eucJP" "\0" "EUC-JP" "\0"
+ "SJIS" "\0" "SHIFT_JIS" "\0"
+ "DECKANJI" "\0" "DEC-KANJI" "\0"
+ "SDECKANJI" "\0" "EUC-JP" "\0"
+ /* Chinese */
+ "eucTW" "\0" "EUC-TW" "\0"
+ "DECHANYU" "\0" "DEC-HANYU" "\0"
+ "DECHANZI" "\0" "GB2312" "\0"
+ /* Korean */
+ "DECKOREAN" "\0" "EUC-KR" "\0";
+# endif
+
+# if defined WINDOWS_NATIVE || defined __CYGWIN__
+ /* To avoid the troubles of installing a separate file in the same
+ directory as the DLL and of retrieving the DLL's directory at
+ runtime, simply inline the aliases here. */
+
+ cp = "CP936" "\0" "GBK" "\0"
+ "CP1361" "\0" "JOHAB" "\0"
+ "CP20127" "\0" "ASCII" "\0"
+ "CP20866" "\0" "KOI8-R" "\0"
+ "CP20936" "\0" "GB2312" "\0"
+ "CP21866" "\0" "KOI8-RU" "\0"
+ "CP28591" "\0" "ISO-8859-1" "\0"
+ "CP28592" "\0" "ISO-8859-2" "\0"
+ "CP28593" "\0" "ISO-8859-3" "\0"
+ "CP28594" "\0" "ISO-8859-4" "\0"
+ "CP28595" "\0" "ISO-8859-5" "\0"
+ "CP28596" "\0" "ISO-8859-6" "\0"
+ "CP28597" "\0" "ISO-8859-7" "\0"
+ "CP28598" "\0" "ISO-8859-8" "\0"
+ "CP28599" "\0" "ISO-8859-9" "\0"
+ "CP28605" "\0" "ISO-8859-15" "\0"
+ "CP38598" "\0" "ISO-8859-8" "\0"
+ "CP51932" "\0" "EUC-JP" "\0"
+ "CP51936" "\0" "GB2312" "\0"
+ "CP51949" "\0" "EUC-KR" "\0"
+ "CP51950" "\0" "EUC-TW" "\0"
+ "CP54936" "\0" "GB18030" "\0"
+ "CP65001" "\0" "UTF-8" "\0";
+# endif
+#endif
+
+ charset_aliases = cp;
+ }
+
+ return cp;
+}
+
+/* Determine the current locale's character encoding, and canonicalize it
+ into one of the canonical names listed in config.charset.
+ The result must not be freed; it is statically allocated.
+ If the canonical name cannot be determined, the result is a non-canonical
+ name. */
+
+#ifdef STATIC
+STATIC
+#endif
+const char *
+locale_charset (void)
+{
+ const char *codeset;
+ const char *aliases;
+
+#if !(defined WINDOWS_NATIVE || defined OS2)
+
+# if HAVE_LANGINFO_CODESET
+
+ /* Most systems support nl_langinfo (CODESET) nowadays. */
+ codeset = nl_langinfo (CODESET);
+
+# ifdef __CYGWIN__
+ /* Cygwin < 1.7 does not have locales. nl_langinfo (CODESET) always
+ returns "US-ASCII". Return the suffix of the locale name from the
+ environment variables (if present) or the codepage as a number. */
+ if (codeset != NULL && strcmp (codeset, "US-ASCII") == 0)
+ {
+ const char *locale;
+ static char buf[2 + 10 + 1];
+
+ locale = getenv ("LC_ALL");
+ if (locale == NULL || locale[0] == '\0')
+ {
+ locale = getenv ("LC_CTYPE");
+ if (locale == NULL || locale[0] == '\0')
+ locale = getenv ("LANG");
+ }
+ if (locale != NULL && locale[0] != '\0')
+ {
+ /* If the locale name contains an encoding after the dot, return
+ it. */
+ const char *dot = strchr (locale, '.');
+
+ if (dot != NULL)
+ {
+ const char *modifier;
+
+ dot++;
+ /* Look for the possible @... trailer and remove it, if any. */
+ modifier = strchr (dot, '@');
+ if (modifier == NULL)
+ return dot;
+ if (modifier - dot < sizeof (buf))
+ {
+ memcpy (buf, dot, modifier - dot);
+ buf [modifier - dot] = '\0';
+ return buf;
+ }
+ }
+ }
+
+ /* The Windows API has a function returning the locale's codepage as a
+ number: GetACP(). This encoding is used by Cygwin, unless the user
+ has set the environment variable CYGWIN=codepage:oem (which very few
+ people do).
+ Output directed to console windows needs to be converted (to
+ GetOEMCP() if the console is using a raster font, or to
+ GetConsoleOutputCP() if it is using a TrueType font). Cygwin does
+ this conversion transparently (see winsup/cygwin/fhandler_console.cc),
+ converting to GetConsoleOutputCP(). This leads to correct results,
+ except when SetConsoleOutputCP has been called and a raster font is
+ in use. */
+ sprintf (buf, "CP%u", GetACP ());
+ codeset = buf;
+ }
+# endif
+
+# else
+
+ /* On old systems which lack it, use setlocale or getenv. */
+ const char *locale = NULL;
+
+ /* But most old systems don't have a complete set of locales. Some
+ (like SunOS 4 or DJGPP) have only the C locale. Therefore we don't
+ use setlocale here; it would return "C" when it doesn't support the
+ locale name the user has set. */
+# if 0
+ locale = setlocale (LC_CTYPE, NULL);
+# endif
+ if (locale == NULL || locale[0] == '\0')
+ {
+ locale = getenv ("LC_ALL");
+ if (locale == NULL || locale[0] == '\0')
+ {
+ locale = getenv ("LC_CTYPE");
+ if (locale == NULL || locale[0] == '\0')
+ locale = getenv ("LANG");
+ }
+ }
+
+ /* On some old systems, one used to set locale = "iso8859_1". On others,
+ you set it to "language_COUNTRY.charset". In any case, we resolve it
+ through the charset.alias file. */
+ codeset = locale;
+
+# endif
+
+#elif defined WINDOWS_NATIVE
+
+ static char buf[2 + 10 + 1];
+
+ /* The Windows API has a function returning the locale's codepage as
+ a number, but the value doesn't change according to what the
+ 'setlocale' call specified. So we use it as a last resort, in
+ case the string returned by 'setlocale' doesn't specify the
+ codepage. */
+ char *current_locale = setlocale (LC_ALL, NULL);
+ char *pdot;
+
+ /* If they set different locales for different categories,
+ 'setlocale' will return a semi-colon separated list of locale
+ values. To make sure we use the correct one, we choose LC_CTYPE. */
+ if (strchr (current_locale, ';'))
+ current_locale = setlocale (LC_CTYPE, NULL);
+
+ pdot = strrchr (current_locale, '.');
+ if (pdot)
+ sprintf (buf, "CP%s", pdot + 1);
+ else
+ {
+ /* The Windows API has a function returning the locale's codepage as a
+ number: GetACP().
+ When the output goes to a console window, it needs to be provided in
+ GetOEMCP() encoding if the console is using a raster font, or in
+ GetConsoleOutputCP() encoding if it is using a TrueType font.
+ But in GUI programs and for output sent to files and pipes, GetACP()
+ encoding is the best bet. */
+ sprintf (buf, "CP%u", GetACP ());
+ }
+ codeset = buf;
+
+#elif defined OS2
+
+ const char *locale;
+ static char buf[2 + 10 + 1];
+ ULONG cp[3];
+ ULONG cplen;
+
+ /* Allow user to override the codeset, as set in the operating system,
+ with standard language environment variables. */
+ locale = getenv ("LC_ALL");
+ if (locale == NULL || locale[0] == '\0')
+ {
+ locale = getenv ("LC_CTYPE");
+ if (locale == NULL || locale[0] == '\0')
+ locale = getenv ("LANG");
+ }
+ if (locale != NULL && locale[0] != '\0')
+ {
+ /* If the locale name contains an encoding after the dot, return it. */
+ const char *dot = strchr (locale, '.');
+
+ if (dot != NULL)
+ {
+ const char *modifier;
+
+ dot++;
+ /* Look for the possible @... trailer and remove it, if any. */
+ modifier = strchr (dot, '@');
+ if (modifier == NULL)
+ return dot;
+ if (modifier - dot < sizeof (buf))
+ {
+ memcpy (buf, dot, modifier - dot);
+ buf [modifier - dot] = '\0';
+ return buf;
+ }
+ }
+
+ /* Resolve through the charset.alias file. */
+ codeset = locale;
+ }
+ else
+ {
+ /* OS/2 has a function returning the locale's codepage as a number. */
+ if (DosQueryCp (sizeof (cp), cp, &cplen))
+ codeset = "";
+ else
+ {
+ sprintf (buf, "CP%u", cp[0]);
+ codeset = buf;
+ }
+ }
+
+#endif
+
+ if (codeset == NULL)
+ /* The canonical name cannot be determined. */
+ codeset = "";
+
+ /* Resolve alias. */
+ for (aliases = get_charset_aliases ();
+ *aliases != '\0';
+ aliases += strlen (aliases) + 1, aliases += strlen (aliases) + 1)
+ if (strcmp (codeset, aliases) == 0
+ || (aliases[0] == '*' && aliases[1] == '\0'))
+ {
+ codeset = aliases + strlen (aliases) + 1;
+ break;
+ }
+
+ /* Don't return an empty string. GNU libc and GNU libiconv interpret
+ the empty string as denoting "the locale's character encoding",
+ thus GNU libiconv would call this function a second time. */
+ if (codeset[0] == '\0')
+ codeset = "ASCII";
+
+#ifdef DARWIN7
+ /* Mac OS X sets MB_CUR_MAX to 1 when LC_ALL=C, and "UTF-8"
+ (the default codeset) does not work when MB_CUR_MAX is 1. */
+ if (strcmp (codeset, "UTF-8") == 0 && MB_CUR_MAX_L (uselocale (NULL)) <= 1)
+ codeset = "ASCII";
+#endif
+
+ return codeset;
+}
diff --git a/src/libs/libgroff/macropath.cpp b/src/libs/libgroff/macropath.cpp
new file mode 100644
index 0000000..a9c9b35
--- /dev/null
+++ b/src/libs/libgroff/macropath.cpp
@@ -0,0 +1,29 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "searchpath.h"
+#include "macropath.h"
+#include "defs.h"
+
+#define MACROPATH_ENVVAR "GROFF_TMAC_PATH"
+
+search_path macro_path(MACROPATH_ENVVAR, MACROPATH, 1, 1);
+search_path safer_macro_path(MACROPATH_ENVVAR, MACROPATH, 1, 0);
+search_path config_macro_path(MACROPATH_ENVVAR, MACROPATH, 0, 0);
diff --git a/src/libs/libgroff/make-uniuni b/src/libs/libgroff/make-uniuni
new file mode 100755
index 0000000..386eacd
--- /dev/null
+++ b/src/libs/libgroff/make-uniuni
@@ -0,0 +1,162 @@
+#! /bin/sh
+#
+# make-uniuni -- script for creating the file uniuni.cpp
+#
+# Copyright (C) 2005-2020 Free Software Foundation, Inc.
+# Written by Werner Lemberg <wl@gnu.org>
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# usage:
+#
+# make-uniuni <version-string> < UnicodeData.txt > uniuni.cpp
+#
+# 'UnicodeData.txt' is the central database file from the Unicode standard.
+# Unfortunately, it doesn't contain a version number which must be thus
+# provided manually as a parameter to the filter.
+#
+# This program needs a C preprocessor.
+#
+
+CPP=cpp
+
+prog="$0"
+
+if test $# -ne 1; then
+ echo "usage: $0 <version-string> < UnicodeData.txt > uniuni.cpp"
+ exit 1
+fi
+
+version_string="$1"
+
+# Remove ranges and control characters,
+# then extract the decomposition field,
+# then remove lines without decomposition,
+# then remove all compatibility decompositions.
+sed -e '/^[^;]*;</d' \
+| sed -e 's/;[^;]*;[^;]*;[^;]*;[^;]*;\([^;]*\);.*$/;\1/' \
+| sed -e '/^[^;]*;$/d' \
+| sed -e '/^[^;]*;</d' > $$1
+
+# Prepare input for running cpp.
+cat $$1 \
+| sed -e 's/^\([^;]*\);/#define \1 /' \
+ -e 's/ / u/g' > $$2
+cat $$1 \
+| sed -e 's/^\([^;]*\);.*$/\1 u\1/' >> $$2
+
+# Run C preprocessor to recursively decompose.
+$CPP $$2 $$3
+
+# Convert it back to original format.
+cat $$3 \
+| sed -e '/#/d' \
+ -e '/^$/d' \
+ -e 's/ \+/ /g' \
+ -e 's/ *$//' \
+ -e 's/u//g' \
+ -e 's/^\([^ ]*\) /\1;/' > $$4
+
+# Write preamble.
+cat <<END
+// -*- C++ -*-
+/* Copyright (C) 2002-2014 Free Software Foundation, Inc.
+ Written by Werner Lemberg <wl@gnu.org>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// This code has been algorithmically derived from the file
+// UnicodeData.txt, version $version_string, available from unicode.org,
+// on `date '+%Y-%m-%d'`.
+
+#include "lib.h"
+#include "stringclass.h"
+#include "ptable.h"
+
+#include "unicode.h"
+
+struct unicode_decompose {
+ char *value;
+};
+
+declare_ptable(unicode_decompose)
+implement_ptable(unicode_decompose)
+
+PTABLE(unicode_decompose) unicode_decompose_table;
+
+// the first digit in the composite string gives the number of composites
+
+struct S {
+ const char *key;
+ const char *value;
+} unicode_decompose_list[] = {
+END
+
+# Emit Unicode data.
+cat $$4 \
+| sed -e 's/ /_/g' \
+ -e 's/\(.*\);\(.*_.*_.*_.*\)$/ { "\1", "4\2" },/' \
+ -e 's/\(.*\);\(.*_.*_.*\)$/ { "\1", "3\2" },/' \
+ -e 's/\(.*\);\(.*_.*\)$/ { "\1", "2\2" },/' \
+ -e 's/\(.*\);\(.*\)$/ { "\1", "1\2" },/'
+
+# Write postamble.
+cat <<END
+};
+
+// global constructor
+
+static struct unicode_decompose_init {
+ unicode_decompose_init();
+} _unicode_decompose_init;
+
+unicode_decompose_init::unicode_decompose_init()
+{
+ for (unsigned int i = 0;
+ i < sizeof(unicode_decompose_list)/sizeof(unicode_decompose_list[0]);
+ i++) {
+ unicode_decompose *dec = new unicode_decompose[1];
+ dec->value = (char *)unicode_decompose_list[i].value;
+ unicode_decompose_table.define(unicode_decompose_list[i].key, dec);
+ }
+}
+
+const char *decompose_unicode(const char *s)
+{
+ unicode_decompose *result = unicode_decompose_table.lookup(s);
+ return result ? result->value : 0;
+}
+END
+
+
+# Remove temporary files.
+rm $$1 $$2 $$3 $$4
+
+# EOF
diff --git a/src/libs/libgroff/matherr.c b/src/libs/libgroff/matherr.c
new file mode 100644
index 0000000..a1adbf8
--- /dev/null
+++ b/src/libs/libgroff/matherr.c
@@ -0,0 +1,48 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <errno.h>
+
+#ifdef HAVE_STRUCT_EXCEPTION
+#ifdef TLOSS
+
+int matherr(exc)
+struct exception *exc;
+{
+ switch (exc->type) {
+ case SING:
+ case DOMAIN:
+ errno = EDOM;
+ break;
+ case OVERFLOW:
+ case UNDERFLOW:
+ case TLOSS:
+ case PLOSS:
+ errno = ERANGE;
+ break;
+ }
+ return 1;
+}
+
+#endif /* TLOSS */
+#endif /* HAVE_STRUCT_EXCEPTION */
diff --git a/src/libs/libgroff/maxfilename.cpp b/src/libs/libgroff/maxfilename.cpp
new file mode 100644
index 0000000..5e1defe
--- /dev/null
+++ b/src/libs/libgroff/maxfilename.cpp
@@ -0,0 +1,75 @@
+// -*- C++ -*-
+/* Copyright (C) 1992-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* file_name_max(dir) does the same as pathconf(dir, _PC_NAME_MAX) */
+
+#include "lib.h"
+
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+
+#ifdef _POSIX_VERSION
+
+size_t file_name_max(const char *fname)
+{
+ return pathconf(fname, _PC_NAME_MAX);
+}
+
+#else /* not _POSIX_VERSION */
+
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#else /* not HAVE_DIRENT_H */
+#ifdef HAVE_SYS_DIR_H
+#include <sys/dir.h>
+#endif /* HAVE_SYS_DIR_H */
+#endif /* not HAVE_DIRENT_H */
+
+#ifndef NAME_MAX
+#ifdef MAXNAMLEN
+#define NAME_MAX MAXNAMLEN
+#endif
+#endif
+
+#ifndef NAME_MAX
+#ifdef MAXNAMELEN
+#define NAME_MAX MAXNAMELEN
+#endif
+#endif
+
+#ifndef NAME_MAX
+#include <stdio.h>
+#ifdef FILENAME_MAX
+#define NAME_MAX FILENAME_MAX
+#endif
+#endif
+
+#ifndef NAME_MAX
+#define NAME_MAX 14
+#endif
+
+size_t file_name_max(const char *)
+{
+ return NAME_MAX;
+}
+
+#endif /* not _POSIX_VERSION */
diff --git a/src/libs/libgroff/maxpathname.cpp b/src/libs/libgroff/maxpathname.cpp
new file mode 100644
index 0000000..8041eeb
--- /dev/null
+++ b/src/libs/libgroff/maxpathname.cpp
@@ -0,0 +1,70 @@
+// -*- C++ -*-
+/* Copyright (C) 2005-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* path_name_max(dir) does the same as pathconf(dir, _PC_PATH_MAX) */
+
+#include "lib.h"
+
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+
+#ifdef _POSIX_VERSION
+
+size_t path_name_max()
+{
+ return pathconf("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf("/",_PC_PATH_MAX);
+}
+
+#else /* not _POSIX_VERSION */
+
+#include <stdlib.h>
+
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+#else /* not HAVE_DIRENT_H */
+# ifdef HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif /* HAVE_SYS_DIR_H */
+#endif /* not HAVE_DIRENT_H */
+
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else /* !MAXPATHLEN */
+# ifdef MAX_PATH
+# define PATH_MAX MAX_PATH
+# else /* !MAX_PATH */
+# ifdef _MAX_PATH
+# define PATH_MAX _MAX_PATH
+# else /* !_MAX_PATH */
+# define PATH_MAX 255
+# endif /* !_MAX_PATH */
+# endif /* !MAX_PATH */
+# endif /* !MAXPATHLEN */
+#endif /* !PATH_MAX */
+
+size_t path_name_max()
+{
+ return PATH_MAX;
+}
+
+#endif /* not _POSIX_VERSION */
diff --git a/src/libs/libgroff/mksdir.cpp b/src/libs/libgroff/mksdir.cpp
new file mode 100644
index 0000000..5fa6d28
--- /dev/null
+++ b/src/libs/libgroff/mksdir.cpp
@@ -0,0 +1,33 @@
+/* Copyright (C) 2001-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* This file is heavily based on the file mkstemp.c which is part of the
+ fileutils package. */
+
+
+extern int gen_tempname(char *, int = 0);
+
+/* Generate a unique temporary directory name from TEMPLATE.
+ The last six characters of TEMPLATE must be "XXXXXX";
+ they are replaced with a string that makes the filename unique.
+ Then open the directory and return a fd. */
+int mksdir(char *tmpl)
+{
+ return gen_tempname(tmpl, 1);
+}
diff --git a/src/libs/libgroff/mkstemp.cpp b/src/libs/libgroff/mkstemp.cpp
new file mode 100644
index 0000000..089a10a
--- /dev/null
+++ b/src/libs/libgroff/mkstemp.cpp
@@ -0,0 +1,33 @@
+/* Copyright (C) 2001-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* This file is heavily based on the file mkstemp.c which is part of the
+ fileutils package. */
+
+
+extern int gen_tempname(char *, int);
+
+/* Generate a unique temporary file name from TEMPLATE.
+ The last six characters of TEMPLATE must be "XXXXXX";
+ they are replaced with a string that makes the filename unique.
+ Then open the file and return a fd. */
+int mkstemp(char *tmpl)
+{
+ return gen_tempname(tmpl, 0);
+}
diff --git a/src/libs/libgroff/nametoindex.cpp b/src/libs/libgroff/nametoindex.cpp
new file mode 100644
index 0000000..095a432
--- /dev/null
+++ b/src/libs/libgroff/nametoindex.cpp
@@ -0,0 +1,167 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "font.h"
+#include "ptable.h"
+#include "itable.h"
+
+// Every glyphinfo is actually a charinfo.
+class charinfo : glyph {
+public:
+ const char *name; // The glyph name, or a null pointer.
+ friend class character_indexer;
+};
+
+// PTABLE(charinfo) is a hash table mapping 'const char *' to 'charinfo *'.
+declare_ptable(charinfo)
+implement_ptable(charinfo)
+
+// ITABLE(charinfo) is a hash table mapping 'int >= 0' to 'charinfo *'.
+declare_itable(charinfo)
+implement_itable(charinfo)
+
+// This class is as a registry storing all named and numbered glyphs known
+// so far, and assigns a unique index to each glyph.
+class character_indexer {
+public:
+ character_indexer();
+ ~character_indexer();
+ // --------------------- Lookup or creation of a glyph.
+ glyph *ascii_char_glyph(unsigned char);
+ glyph *named_char_glyph(const char *);
+ glyph *numbered_char_glyph(int);
+private:
+ int next_index; // Number of glyphs already allocated.
+ PTABLE(charinfo) table; // Table mapping name to glyph.
+ glyph *ascii_glyph[256]; // Shorthand table for looking up "charNNN"
+ // glyphs.
+ ITABLE(charinfo) ntable; // Table mapping number to glyph.
+ enum { NSMALL = 256 };
+ glyph *small_number_glyph[NSMALL]; // Shorthand table for looking up
+ // numbered glyphs with small numbers.
+};
+
+character_indexer::character_indexer()
+: next_index(0)
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ ascii_glyph[i] = UNDEFINED_GLYPH;
+ for (i = 0; i < NSMALL; i++)
+ small_number_glyph[i] = UNDEFINED_GLYPH;
+}
+
+character_indexer::~character_indexer()
+{
+}
+
+glyph *character_indexer::ascii_char_glyph(unsigned char c)
+{
+ if (ascii_glyph[c] == UNDEFINED_GLYPH) {
+ char buf[4+3+1];
+ memcpy(buf, "char", 4);
+ strcpy(buf + 4, i_to_a(c));
+ charinfo *ci = new charinfo;
+ ci->index = next_index++;
+ ci->number = -1;
+ ci->name = strsave(buf);
+ ascii_glyph[c] = ci;
+ }
+ return ascii_glyph[c];
+}
+
+inline glyph *character_indexer::named_char_glyph(const char *s)
+{
+ // Glyphs with name 'charNNN' are only stored in ascii_glyph[], not
+ // in the table. Therefore treat them specially here.
+ if (s[0] == 'c' && s[1] == 'h' && s[2] == 'a' && s[3] == 'r') {
+ char *val;
+ long n = strtol(s + 4, &val, 10);
+ if (val != s + 4 && *val == '\0' && n >= 0 && n < 256)
+ return ascii_char_glyph((unsigned char)n);
+ }
+ charinfo *ci = table.lookupassoc(&s);
+ if (0 == ci) {
+ ci = new charinfo[1];
+ ci->index = next_index++;
+ ci->number = -1;
+ ci->name = table.define(s, ci);
+ }
+ return ci;
+}
+
+inline glyph *character_indexer::numbered_char_glyph(int n)
+{
+ if (n >= 0 && n < NSMALL) {
+ if (small_number_glyph[n] == UNDEFINED_GLYPH) {
+ charinfo *ci = new charinfo;
+ ci->index = next_index++;
+ ci->number = n;
+ ci->name = 0;
+ small_number_glyph[n] = ci;
+ }
+ return small_number_glyph[n];
+ }
+ charinfo *ci = ntable.lookup(n);
+ if (0 == ci) {
+ ci = new charinfo[1];
+ ci->index = next_index++;
+ ci->number = n;
+ ci->name = 0;
+ ntable.define(n, ci);
+ }
+ return ci;
+}
+
+static character_indexer indexer;
+
+glyph *number_to_glyph(int n)
+{
+ return indexer.numbered_char_glyph(n);
+}
+
+// troff overrides this function with its own version.
+
+glyph *name_to_glyph(const char *s)
+{
+ assert(s != 0 && s[0] != '\0' && s[0] != ' ');
+ if (s[1] == '\0')
+ // \200 and char128 are synonyms
+ return indexer.ascii_char_glyph(s[0]);
+ return indexer.named_char_glyph(s);
+}
+
+const char *glyph_to_name(glyph *g)
+{
+ charinfo *ci = (charinfo *)g; // Every glyph is actually a charinfo.
+ return ci->name;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/new.cpp b/src/libs/libgroff/new.cpp
new file mode 100644
index 0000000..2af976c
--- /dev/null
+++ b/src/libs/libgroff/new.cpp
@@ -0,0 +1,75 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "posix.h"
+#include "nonposix.h"
+
+extern "C" const char *program_name;
+
+static void ewrite(const char *s)
+{
+ write(2, s, strlen(s));
+}
+
+void *operator new(size_t size)
+{
+ // Avoid relying on the behaviour of malloc(0).
+ if (size == 0)
+ size++;
+ char *p = (char *)malloc(unsigned(size));
+ if (p == 0) {
+ if (program_name) {
+ ewrite(program_name);
+ ewrite(": ");
+ }
+ ewrite("out of memory\n");
+ _exit(-1);
+ }
+ return p;
+}
+
+void operator delete(void *p) throw()
+{
+ if (p)
+ free(p);
+}
+
+void operator delete(void *p,
+ __attribute__((__unused__)) long unsigned int size)
+{
+ // It's ugly to duplicate the code from delete(void *) above, but if
+ // we don't, g++ 6.3 can't figure out we're calling through it to
+ // free().
+ //
+ // In function 'void operator delete(void*, long unsigned int)':
+ // warning: deleting 'void*' is undefined [-Wdelete-incomplete]
+ //delete p;
+ if (p)
+ free(p);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/paper.cpp b/src/libs/libgroff/paper.cpp
new file mode 100644
index 0000000..842f369
--- /dev/null
+++ b/src/libs/libgroff/paper.cpp
@@ -0,0 +1,82 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "paper.h"
+
+paper papersizes[NUM_PAPERSIZES];
+
+// length and width in mm
+static void add_iso_paper(char series, int offset,
+ int start_length, int start_width)
+{
+ int length = start_length;
+ int width = start_width;
+ for (int i = 0; i < 8; i++)
+ {
+ char *p = new char[3];
+ p[0] = series;
+ p[1] = '0' + i;
+ p[2] = '\0';
+ papersizes[offset + i].name = p;
+ // convert mm to inch
+ papersizes[offset + i].length = (double)length / 25.4;
+ papersizes[offset + i].width = (double)width / 25.4;
+ // after division by two, values must be rounded down to the next
+ // integer (as specified by ISO)
+ int tmp = length;
+ length = width;
+ width = tmp / 2;
+ }
+}
+
+// length and width in inch
+static void add_american_paper(const char *name, int idx,
+ double length, double width )
+{
+ char *p = new char[strlen(name) + 1];
+ strcpy(p, name);
+ papersizes[idx].name = p;
+ papersizes[idx].length = length;
+ papersizes[idx].width = width;
+}
+
+int papersize_init::initialised = 0;
+
+papersize_init::papersize_init()
+{
+ if (initialised)
+ return;
+ initialised = 1;
+ add_iso_paper('a', 0, 1189, 841);
+ add_iso_paper('b', 8, 1414, 1000);
+ add_iso_paper('c', 16, 1297, 917);
+ add_iso_paper('d', 24, 1090, 771);
+ add_american_paper("letter", 32, 11, 8.5);
+ add_american_paper("legal", 33, 14, 8.5);
+ add_american_paper("tabloid", 34, 17, 11);
+ add_american_paper("ledger", 35, 11, 17);
+ add_american_paper("statement", 36, 8.5, 5.5);
+ add_american_paper("executive", 37, 10, 7.5);
+ // the next three entries are for grolj4
+ add_american_paper("com10", 38, 9.5, 4.125);
+ add_american_paper("monarch", 39, 7.5, 3.875);
+ // this is an ISO format, but it easier to use add_american_paper
+ add_american_paper("dl", 40, 220/25.4, 110/25.4);
+}
diff --git a/src/libs/libgroff/prime.cpp b/src/libs/libgroff/prime.cpp
new file mode 100644
index 0000000..5ae068d
--- /dev/null
+++ b/src/libs/libgroff/prime.cpp
@@ -0,0 +1,55 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <math.h>
+
+bool is_prime(unsigned n)
+{
+ assert(n > 1);
+ if (n <= 3)
+ return true;
+ if (!(n & 1))
+ return false;
+ if (n % 3 == 0)
+ return false;
+ unsigned lim = unsigned(sqrt((double)n));
+ unsigned d = 5;
+ for (;;) {
+ if (d > lim)
+ break;
+ if (n % d == 0)
+ return false;
+ d += 2;
+ if (d > lim)
+ break;
+ if (n % d == 0)
+ return false;
+ d += 4;
+ }
+ return true;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/progname.c b/src/libs/libgroff/progname.c
new file mode 100644
index 0000000..f4320c1
--- /dev/null
+++ b/src/libs/libgroff/progname.c
@@ -0,0 +1,18 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+const char *program_name = 0;
diff --git a/src/libs/libgroff/ptable.cpp b/src/libs/libgroff/ptable.cpp
new file mode 100644
index 0000000..52d09f8
--- /dev/null
+++ b/src/libs/libgroff/ptable.cpp
@@ -0,0 +1,57 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "ptable.h"
+#include "errarg.h"
+#include "error.h"
+
+unsigned long hash_string(const char *s)
+{
+ // This is the mythical Aho-Hopcroft-Ullman hash function.
+ // TODO: Improve. See http://www.haible.de/bruno/hashfunc.html
+ assert(s != 0);
+ unsigned long h = 0, g;
+ while (*s != 0) {
+ h <<= 4;
+ h += *s++;
+ if ((g = h & 0xf0000000) != 0) {
+ h ^= g >> 24;
+ h ^= g;
+ }
+ }
+ return h;
+}
+
+static const unsigned table_sizes[] = {
+ 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009,
+ 80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009,
+ 16000057, 32000011, 64000031, 128000003, 0
+};
+
+unsigned next_ptable_size(unsigned n)
+{
+ const unsigned *p;
+ for (p = table_sizes; *p <= n; p++)
+ if (*p == 0)
+ fatal("cannot expand table");
+ return *p;
+}
+
+// end of ptable.cpp
diff --git a/src/libs/libgroff/putenv.c b/src/libs/libgroff/putenv.c
new file mode 100644
index 0000000..692a126
--- /dev/null
+++ b/src/libs/libgroff/putenv.c
@@ -0,0 +1,97 @@
+/* Copyright (C) 1991-2020 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Hacked slightly by jjc@jclark.com for groff. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#ifdef __STDC__
+#include <stddef.h>
+typedef void *PTR;
+typedef size_t SIZE_T;
+#else /* not __STDC__ */
+typedef char *PTR;
+typedef int SIZE_T;
+#endif /* not __STDC__ */
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#else /* not HAVE_STDLIB_H */
+PTR malloc();
+#endif /* not HAVE_STDLIB_H */
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+extern char **environ;
+
+/* Put STRING, which is of the form 'NAME=VALUE', in the environment. */
+
+int putenv(const char *string)
+{
+ char *name_end = strchr(string, '=');
+ SIZE_T size;
+ char **ep;
+
+ if (name_end == NULL)
+ {
+ /* Remove the variable from the environment. */
+ size = strlen(string);
+ for (ep = environ; *ep != NULL; ++ep)
+ if (!strncmp(*ep, string, size) && (*ep)[size] == '=')
+ {
+ while (ep[1] != NULL)
+ {
+ ep[0] = ep[1];
+ ++ep;
+ }
+ *ep = NULL;
+ return 0;
+ }
+ }
+
+ size = 0;
+ for (ep = environ; *ep != NULL; ++ep)
+ if (!strncmp(*ep, string, name_end - string)
+ && (*ep)[name_end - string] == '=')
+ break;
+ else
+ ++size;
+
+ if (*ep == NULL)
+ {
+ static char **last_environ = NULL;
+ char **new_environ = (char **) malloc((size + 2) * sizeof(char *));
+ if (new_environ == NULL)
+ return -1;
+ (void) memcpy((PTR) new_environ, (PTR) environ, size * sizeof(char *));
+ new_environ[size] = (char *) string;
+ new_environ[size + 1] = NULL;
+ if (last_environ != NULL)
+ free((PTR) last_environ);
+ last_environ = new_environ;
+ environ = new_environ;
+ }
+ else
+ *ep = (char *) string;
+
+ return 0;
+}
diff --git a/src/libs/libgroff/quotearg.c b/src/libs/libgroff/quotearg.c
new file mode 100644
index 0000000..75eaca8
--- /dev/null
+++ b/src/libs/libgroff/quotearg.c
@@ -0,0 +1,213 @@
+/* Copyright (C) 2004-2020 Free Software Foundation, Inc.
+ Written by: Jeff Conrad (jeff_conrad@msn.com)
+ and Keith Marshall (keith.d.marshall@ntlworld.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+
+/* Define the default mechanism, and messages, for error reporting
+ * (user may substitute a preferred alternative, by defining his own
+ * implementation of the macros REPORT_ERROR, QUOTE_ARG_MALLOC_FAILED
+ * and QUOTE_ARG_REALLOC_FAILED, in the header file 'nonposix.h').
+ */
+
+#include "nonposix.h"
+
+#ifndef REPORT_ERROR
+# define REPORT_ERROR(WHY) fprintf(stderr, "%s:%s\n", program_name, WHY)
+#endif
+#ifndef QUOTE_ARG_MALLOC_ERROR
+# define QUOTE_ARG_MALLOC_ERROR "malloc: Buffer allocation failed"
+#endif
+#ifndef QUOTE_ARG_REALLOC_ERROR
+# define QUOTE_ARG_REALLOC_ERROR "realloc: Buffer resize failed"
+#endif
+
+extern char *program_name; /* main program must define this */
+
+/* Prototypes */
+char *quote_arg(char *);
+void purge_quoted_args(char **);
+
+#undef FALSE
+#undef TRUE
+#define FALSE 0
+#define TRUE 1
+
+static int
+needs_quoting(const char *string)
+{
+ /* Scan 'string' to see whether it needs quoting for MSVC 'spawn'/'exec'
+ * (i.e., whether it contains whitespace or embedded quotes).
+ */
+
+ if (string == NULL) /* ignore NULL strings */
+ return FALSE;
+
+ if (*string == '\0') /* explicit arguments of zero length */
+ return TRUE; /* need quoting, so they aren't discarded */
+
+ while (*string) {
+ /* Scan non-NULL strings, up to '\0' terminator,
+ * returning 'TRUE' if quote or white space found.
+ */
+
+ if (*string == '"' || isspace(*string))
+ return TRUE;
+
+ /* otherwise, continue scanning to end of string */
+
+ ++string;
+ }
+
+ /* Fall through, if no quotes or white space found,
+ * in which case, return 'FALSE'.
+ */
+
+ return FALSE;
+}
+
+char *
+quote_arg(char *string)
+{
+ /* Enclose arguments in double quotes so that the parsing done in the
+ * MSVC runtime startup code doesn't split them at whitespace. Escape
+ * embedded double quotes so that they emerge intact from the parsing.
+ */
+
+ int backslashes;
+ char *quoted, *p, *q;
+
+ if (needs_quoting(string)) {
+ /* Need to create a quoted copy of 'string';
+ * maximum buffer space needed is twice the original length,
+ * plus two enclosing quotes and one '\0' terminator.
+ */
+
+ if ((quoted = (char *)malloc(2 * strlen(string) + 3)) == NULL) {
+ /* Couldn't get a buffer for the quoted string,
+ * so complain, and bail out gracefully.
+ */
+
+ REPORT_ERROR(QUOTE_ARG_MALLOC_ERROR);
+ exit(1);
+ }
+
+ /* Ok to proceed:
+ * insert the opening quote, then copy the source string,
+ * adding escapes as required.
+ */
+
+ *quoted = '"';
+ for (backslashes = 0, p = string, q = quoted; *p; p++) {
+ if (*p == '\\') {
+ /* Just count backslashes when we find them.
+ * We will copy them out later, when we know if the count
+ * needs to be adjusted, to escape an embedded quote.
+ */
+
+ ++backslashes;
+ }
+ else if (*p == '"') {
+ /* This embedded quote character must be escaped,
+ * but first double up any immediately preceding backslashes,
+ * with one extra, as the escape character.
+ */
+
+ for (backslashes += backslashes + 1; backslashes; backslashes--)
+ *++q = '\\';
+
+ /* and now, add the quote character itself */
+
+ *++q = '"';
+ }
+ else {
+ /* Any other character is simply copied,
+ * but first, if we have any pending backslashes,
+ * we must now insert them, without any count adjustment.
+ */
+
+ while (backslashes) {
+ *++q = '\\';
+ --backslashes;
+ }
+
+ /* and then, copy the current character */
+
+ *++q = *p;
+ }
+ }
+
+ /* At end of argument:
+ * If any backslashes remain to be copied out, append them now,
+ * doubling the actual count to protect against reduction by MSVC,
+ * as a consequence of the immediately following closing quote.
+ */
+
+ for (backslashes += backslashes; backslashes; backslashes--)
+ *++q = '\\';
+
+ /* Finally,
+ * add the closing quote, terminate the quoted string,
+ * and adjust its size to what was actually required,
+ * ready for return.
+ */
+
+ *++q = '"';
+ *++q = '\0';
+ if ((string = (char *)realloc(quoted, strlen(quoted) + 1)) == NULL) {
+ /* but bail out gracefully, on error */
+
+ REPORT_ERROR(QUOTE_ARG_REALLOC_ERROR);
+ exit(1);
+ }
+ }
+
+ /* 'string' now refers to the argument,
+ * quoted and escaped, as required.
+ */
+
+ return string;
+}
+
+void
+purge_quoted_args(char **argv)
+{
+ /* To avoid memory leaks,
+ * free all memory previously allocated by 'quoted_arg()',
+ * within the scope of the referring argument vector, 'argv'.
+ */
+
+ if (argv)
+ while (*argv) {
+ /* Any argument beginning with a double quote
+ * SHOULD have been allocated by 'quoted_arg()'.
+ */
+
+ if (**argv == '"')
+ free( *argv ); /* so free its allocation */
+ ++argv; /* and continue to the next argument */
+ }
+}
+
+/* quotearg.c: end of file */
diff --git a/src/libs/libgroff/ref-add.sin b/src/libs/libgroff/ref-add.sin
new file mode 100644
index 0000000..50aa7d1
--- /dev/null
+++ b/src/libs/libgroff/ref-add.sin
@@ -0,0 +1,29 @@
+# Add this package to a list of references stored in a text file.
+#
+# Copyright (C) 2000-2020 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# Written by Bruno Haible <haible@clisp.cons.org>.
+#
+/^# Packages using this file: / {
+ s/# Packages using this file://
+ ta
+ :a
+ s/ @PACKAGE@ / @PACKAGE@ /
+ tb
+ s/ $/ @PACKAGE@ /
+ :b
+ s/^/# Packages using this file:/
+}
diff --git a/src/libs/libgroff/ref-del.sin b/src/libs/libgroff/ref-del.sin
new file mode 100644
index 0000000..1601469
--- /dev/null
+++ b/src/libs/libgroff/ref-del.sin
@@ -0,0 +1,24 @@
+# Remove this package from a list of references stored in a text file.
+#
+# Copyright (C) 2000-2020 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# Written by Bruno Haible <haible@clisp.cons.org>.
+#
+/^# Packages using this file: / {
+ s/# Packages using this file://
+ s/ @PACKAGE@ / /
+ s/^/# Packages using this file:/
+}
diff --git a/src/libs/libgroff/relocatable.h b/src/libs/libgroff/relocatable.h
new file mode 100644
index 0000000..f152194
--- /dev/null
+++ b/src/libs/libgroff/relocatable.h
@@ -0,0 +1,19 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#include "relocate.h"
+#define relocate(path) relocatep(path)
diff --git a/src/libs/libgroff/relocate.cpp b/src/libs/libgroff/relocate.cpp
new file mode 100644
index 0000000..32a0e2e
--- /dev/null
+++ b/src/libs/libgroff/relocate.cpp
@@ -0,0 +1,244 @@
+/* Provide relocation for macro and font files.
+ Copyright (C) 2005-2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// Made after relocation code in kpathsea and gettext.
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "defs.h"
+#include "posix.h"
+#include "nonposix.h"
+#include "relocate.h"
+
+#if defined _WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif
+
+#define INSTALLPATHLEN (sizeof(INSTALLPATH) - 1)
+#ifndef DEBUG
+# define DEBUG 0
+#endif
+
+extern "C" const char *program_name;
+
+// The prefix (parent directory) corresponding to the binary.
+char *curr_prefix = 0;
+size_t curr_prefix_len = 0;
+
+// Return the directory part of a filename, or '.' if no path separators.
+char *xdirname(char *s)
+{
+ static const char dot[] = ".";
+ if (!s)
+ return 0;
+ // DIR_SEPS[] are possible directory separator characters, see nonposix.h.
+ // We want the rightmost separator of all possible ones.
+ // Example: d:/foo\\bar.
+ char *p = strrchr(s, DIR_SEPS[0]);
+ const char *sep = &DIR_SEPS[1];
+ while (*sep) {
+ char *p1 = strrchr(s, *sep);
+ if (p1 && (!p || p1 > p))
+ p = p1;
+ sep++;
+ }
+ if (p)
+ *p = '\0';
+ else
+ s = (char *)dot;
+ return s;
+}
+
+// Return the full path of NAME along the path PATHP.
+// Adapted from search_path::open_file in searchpath.cpp.
+char *searchpath(const char *name, const char *pathp)
+{
+ char *path;
+ if (!name || !*name)
+ return 0;
+#if DEBUG
+ fprintf(stderr, "searchpath: pathp: '%s'\n", pathp);
+ fprintf(stderr, "searchpath: trying '%s'\n", name);
+#endif
+ // Try first NAME as such; success if NAME is an absolute filename,
+ // or if NAME is found in the current directory.
+ if (!access (name, F_OK)) {
+ path = new char[path_name_max()];
+#ifdef _WIN32
+ path = _fullpath(path, name, path_name_max());
+#else
+ path = realpath(name, path);
+#endif
+#if DEBUG
+ fprintf(stderr, "searchpath: found '%s'\n", path);
+#endif
+ return path;
+ }
+ // Secondly, try the current directory.
+ // Now search along PATHP.
+ size_t namelen = strlen(name);
+ char *p = (char *)pathp;
+ for (;;) {
+ char *end = strchr(p, PATH_SEP_CHAR);
+ if (!end)
+ end = strchr(p, '\0');
+ int need_slash = end > p && strchr(DIR_SEPS, end[-1]) == 0;
+ path = new char[end - p + need_slash + namelen + 1];
+ memcpy(path, p, end - p);
+ if (need_slash)
+ path[end - p] = '/';
+ strcpy(path + (end - p) + need_slash, name);
+#if DEBUG
+ fprintf(stderr, "searchpath: trying '%s'\n", path);
+#endif
+ if (!access(path, F_OK)) {
+#if DEBUG
+ fprintf(stderr, "searchpath: found '%s'\n", name);
+#endif
+ return path;
+ }
+ delete[] path;
+ if (*end == '\0')
+ break;
+ p = end + 1;
+ }
+ return 0;
+}
+
+// Search NAME along PATHP with the elements of PATHEXT in turn added.
+char *searchpathext(const char *name, const char *pathext, const char *pathp)
+{
+ char *found = 0;
+ char *tmpathext = strsave(pathext); // strtok modifies this string,
+ // so make a copy
+ char *ext = strtok(tmpathext, PATH_SEP);
+ while (ext) {
+ char *namex = new char[strlen(name) + strlen(ext) + 1];
+ strcpy(namex, name);
+ strcat(namex, ext);
+ found = searchpath(namex, pathp);
+ delete[] namex;
+ if (found)
+ break;
+ ext = strtok(0, PATH_SEP);
+ }
+ delete[] tmpathext;
+ return found;
+}
+
+// Convert an MS path to a POSIX path.
+char *msw2posixpath(char *path)
+{
+ char *s = path;
+ while (*s) {
+ if (*s == '\\')
+ *s = '/';
+ s++;
+ }
+ return path;
+}
+
+// Compute the current prefix.
+void set_current_prefix()
+{
+ // Obtain the full path of the current binary;
+ // using GetModuleFileName on MS-Windows,
+ // and searching along PATH on other systems.
+#ifdef _WIN32
+ char *pathextstr;
+ curr_prefix = new char[path_name_max()];
+ int len = GetModuleFileName(0, curr_prefix, path_name_max());
+ if (len)
+ len = GetShortPathName(curr_prefix, curr_prefix, path_name_max());
+# if DEBUG
+ fprintf(stderr, "curr_prefix: %s\n", curr_prefix);
+# endif /* DEBUG */
+ if (!curr_prefix && !strchr(program_name, '.')) { // try with extensions
+ pathextstr = strsave(getenv("PATHEXT"));
+ if (!pathextstr)
+ pathextstr = strsave(PATH_EXT);
+ curr_prefix = searchpathext(program_name, pathextstr, getenv("PATH"));
+ delete[] pathextstr;
+ }
+#else /* !_WIN32 */
+ curr_prefix = searchpath(program_name, getenv("PATH"));
+ if (!curr_prefix)
+ return;
+#endif /* !_WIN32 */
+ msw2posixpath(curr_prefix);
+#if DEBUG
+ fprintf(stderr, "curr_prefix: %s\n", curr_prefix);
+#endif
+ curr_prefix = xdirname(curr_prefix); // directory of executable
+ curr_prefix = xdirname(curr_prefix); // parent directory of executable
+ curr_prefix_len = strlen(curr_prefix);
+#if DEBUG
+ fprintf(stderr, "curr_prefix: %s\n", curr_prefix);
+ fprintf(stderr, "curr_prefix_len: %d\n", curr_prefix_len);
+#endif
+}
+
+// Strip the installation prefix and replace it
+// with the current installation prefix; return the relocated path.
+char *relocatep(const char *path)
+{
+#if DEBUG
+ fprintf(stderr, "relocatep: path = %s\n", path);
+ fprintf(stderr, "relocatep: INSTALLPATH = %s\n", INSTALLPATH);
+ fprintf(stderr, "relocatep: INSTALLPATHLEN = %d\n", INSTALLPATHLEN);
+#endif
+ if (!curr_prefix)
+ set_current_prefix();
+ if (strncmp(INSTALLPATH, path, INSTALLPATHLEN))
+ return strsave(path);
+ char *relative_path = (char *)path + INSTALLPATHLEN;
+ size_t relative_path_len = strlen(relative_path);
+ char *relocated_path = (char *)malloc(curr_prefix_len
+ + relative_path_len + 1);
+ assert(0 != curr_prefix);
+ strcpy(relocated_path, curr_prefix);
+ strcat(relocated_path, relative_path);
+#if DEBUG
+ fprintf(stderr, "relocated_path: %s\n", relocated_path);
+#endif /* DEBUG */
+ return relocated_path;
+}
+
+// Return the original pathname if it exists;
+// otherwise return the relocated path.
+char *relocate(const char *path)
+{
+ char *p;
+ if (access(path, F_OK))
+ p = relocatep(path);
+ else
+ p = strsave(path);
+#if DEBUG
+ fprintf (stderr, "relocate: %s\n", p);
+#endif
+ return p;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/searchpath.cpp b/src/libs/libgroff/searchpath.cpp
new file mode 100644
index 0000000..6062e8d
--- /dev/null
+++ b/src/libs/libgroff/searchpath.cpp
@@ -0,0 +1,215 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "searchpath.h"
+#include "nonposix.h"
+
+#ifdef _WIN32
+# include "relocate.h"
+#else
+# define relocate(path) strsave(path)
+#endif
+
+search_path::search_path(const char *envvar, const char *standard,
+ int add_home, int add_current)
+{
+ char *home = 0;
+ if (add_home)
+ home = getenv("HOME");
+ char *e = 0;
+ if (envvar)
+ e = getenv(envvar);
+ dirs = new char[((e && *e) ? strlen(e) + 1 : 0)
+ + (add_current ? 1 + 1 : 0)
+ + ((home && *home) ? strlen(home) + 1 : 0)
+ + ((standard && *standard) ? strlen(standard) : 0)
+ + 1];
+ *dirs = '\0';
+ if (e && *e) {
+ strcat(dirs, e);
+ strcat(dirs, PATH_SEP);
+ }
+ if (add_current) {
+ strcat(dirs, ".");
+ strcat(dirs, PATH_SEP);
+ }
+ if (home && *home) {
+ strcat(dirs, home);
+ strcat(dirs, PATH_SEP);
+ }
+ if (standard && *standard)
+ strcat(dirs, standard);
+ init_len = strlen(dirs);
+}
+
+search_path::~search_path()
+{
+ // dirs is always allocated
+ delete[] dirs;
+}
+
+void search_path::command_line_dir(const char *s)
+{
+ char *old = dirs;
+ unsigned old_len = strlen(old);
+ unsigned slen = strlen(s);
+ dirs = new char[old_len + 1 + slen + 1];
+ memcpy(dirs, old, old_len - init_len);
+ char *p = dirs;
+ p += old_len - init_len;
+ if (init_len == 0)
+ *p++ = PATH_SEP_CHAR;
+ memcpy(p, s, slen);
+ p += slen;
+ if (init_len > 0) {
+ *p++ = PATH_SEP_CHAR;
+ memcpy(p, old + old_len - init_len, init_len);
+ p += init_len;
+ }
+ *p++ = '\0';
+ delete[] old;
+}
+
+FILE *search_path::open_file(const char *name, char **pathp)
+{
+ assert(name != 0);
+ if (IS_ABSOLUTE(name) || *dirs == '\0') {
+ FILE *fp = fopen(name, "r");
+ if (fp) {
+ if (pathp)
+ *pathp = strsave(name);
+ return fp;
+ }
+ else
+ return 0;
+ }
+ unsigned namelen = strlen(name);
+ char *p = dirs;
+ for (;;) {
+ char *end = strchr(p, PATH_SEP_CHAR);
+ if (!end)
+ end = strchr(p, '\0');
+ int need_slash = end > p && strchr(DIR_SEPS, end[-1]) == 0;
+ char *origpath = new char[(end - p) + need_slash + namelen + 1];
+ memcpy(origpath, p, end - p);
+ if (need_slash)
+ origpath[end - p] = '/';
+ strcpy(origpath + (end - p) + need_slash, name);
+#if 0
+ fprintf(stderr, "origpath '%s'\n", origpath);
+#endif
+ char *path = relocate(origpath);
+ delete[] origpath;
+#if 0
+ fprintf(stderr, "trying '%s'\n", path);
+#endif
+ FILE *fp = fopen(path, "r");
+ int err = errno;
+ if (fp) {
+ if (pathp)
+ *pathp = path;
+ else {
+ free(path);
+ errno = err;
+ }
+ return fp;
+ }
+ free(path);
+ errno = err;
+ if (*end == '\0')
+ break;
+ p = end + 1;
+ }
+ return 0;
+}
+
+FILE *search_path::open_file_cautious(const char *name, char **pathp,
+ const char *mode)
+{
+ if (!mode)
+ mode = "r";
+ bool reading = (strchr(mode, 'r') != 0);
+ if (name == 0 || strcmp(name, "-") == 0) {
+ if (pathp)
+ *pathp = strsave(reading ? "stdin" : "stdout");
+ return (reading ? stdin : stdout);
+ }
+ if (!reading || IS_ABSOLUTE(name) || *dirs == '\0') {
+ FILE *fp = fopen(name, mode);
+ if (fp) {
+ if (pathp)
+ *pathp = strsave(name);
+ return fp;
+ }
+ else
+ return 0;
+ }
+ unsigned namelen = strlen(name);
+ char *p = dirs;
+ for (;;) {
+ char *end = strchr(p, PATH_SEP_CHAR);
+ if (!end)
+ end = strchr(p, '\0');
+ int need_slash = end > p && strchr(DIR_SEPS, end[-1]) == 0;
+ char *origpath = new char[(end - p) + need_slash + namelen + 1];
+ memcpy(origpath, p, end - p);
+ if (need_slash)
+ origpath[end - p] = '/';
+ strcpy(origpath + (end - p) + need_slash, name);
+#if 0
+ fprintf(stderr, "origpath '%s'\n", origpath);
+#endif
+ char *path = relocate(origpath);
+ delete[] origpath;
+#if 0
+ fprintf(stderr, "trying '%s'\n", path);
+#endif
+ FILE *fp = fopen(path, mode);
+ int err = errno;
+ if (fp) {
+ if (pathp)
+ *pathp = path;
+ else {
+ free(path);
+ errno = err;
+ }
+ return fp;
+ }
+ free(path);
+ errno = err;
+ if (err != ENOENT)
+ return 0;
+ if (*end == '\0')
+ break;
+ p = end + 1;
+ }
+ errno = ENOENT;
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/spawnvp.c b/src/libs/libgroff/spawnvp.c
new file mode 100644
index 0000000..1fffa2b
--- /dev/null
+++ b/src/libs/libgroff/spawnvp.c
@@ -0,0 +1,120 @@
+/* Copyright (C) 2004-2020 Free Software Foundation, Inc.
+ Written by: Keith Marshall (keith.d.marshall@ntlworld.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef HAVE_PROCESS_H
+# include <process.h>
+#endif
+
+#if defined(__MSDOS__) \
+ || (defined(_WIN32) && !defined(_UWIN) && !defined(__CYGWIN__)) \
+ || defined(__EMX__)
+
+#define SPAWN_FUNCTION_WRAPPERS 1
+
+/* Define the default mechanism, and messages, for error reporting
+ * (user may substitute a preferred alternative, by defining his own
+ * implementation of the macros REPORT_ERROR and ARGV_MALLOC_ERROR,
+ * in the header file 'nonposix.h').
+ */
+
+#include "nonposix.h"
+
+#ifndef REPORT_ERROR
+# define REPORT_ERROR(WHY) fprintf(stderr, "%s:%s\n", program_name, WHY)
+#endif
+#ifndef ARGV_MALLOC_ERROR
+# define ARGV_MALLOC_ERROR "malloc: Allocation for 'argv' failed"
+#endif
+
+extern char *program_name;
+
+extern char *quote_arg(char *string);
+extern void purge_quoted_args(char **argv);
+
+int
+spawnvp_wrapper(int mode, char *path, char **argv)
+{
+ /* Invoke the system 'spawnvp' service
+ * enclosing the passed arguments in double quotes, as required,
+ * so that the (broken) default parsing in the MSVC runtime doesn't
+ * split them at whitespace. */
+
+ char **quoted_argv; /* used to build a quoted local copy of 'argv' */
+
+ int i; /* used as an index into 'argv' or 'quoted_argv' */
+ int status = -1; /* initialise return code, in case we fail */
+ int argc = 0; /* initialise argument count; may be none */
+
+ /* First count the number of arguments
+ * which are actually present in the passed 'argv'. */
+
+ if (argv)
+ for (quoted_argv = argv; *quoted_argv; ++argc, ++quoted_argv)
+ ;
+
+ /* If we do not now have an argument count,
+ * then we must fall through and fail. */
+
+ if (argc) {
+ /* We do have at least one argument:
+ * We will use a copy of the 'argv', in which to do the quoting,
+ * so we must allocate space for it. */
+
+ if ((quoted_argv = (char **)malloc(++argc * sizeof(char **))) == NULL) {
+ /* If we didn't get enough space,
+ * then complain, and bail out gracefully. */
+
+ REPORT_ERROR(ARGV_MALLOC_ERROR);
+ exit(1);
+ }
+
+ /* Now copy the passed 'argv' into our new vector,
+ * quoting its contents as required. */
+
+ for (i = 0; i < argc; i++)
+ quoted_argv[i] = quote_arg(argv[i]);
+
+ /* Invoke the MSVC 'spawnvp' service
+ * passing our now appropriately quoted copy of 'argv'. */
+
+ status = spawnvp(mode, path, quoted_argv);
+
+ /* Clean up our memory allocations
+ * for the quoted copy of 'argv', which is no longer required. */
+
+ purge_quoted_args(quoted_argv);
+ free(quoted_argv);
+ }
+
+ /* Finally,
+ * return the status code returned by 'spawnvp',
+ * or a failure code if we fell through. */
+
+ return status;
+}
+
+#endif /* __MSDOS__ || _WIN32 */
+
+/* spawnvp.c: end of file */
diff --git a/src/libs/libgroff/strcasecmp.c b/src/libs/libgroff/strcasecmp.c
new file mode 100644
index 0000000..22c0997
--- /dev/null
+++ b/src/libs/libgroff/strcasecmp.c
@@ -0,0 +1,65 @@
+/* strcasecmp.c -- case insensitive string comparator
+ Copyright (C) 1998-2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef LENGTH_LIMIT
+# define STRXCASECMP_FUNCTION strncasecmp
+# define STRXCASECMP_DECLARE_N , size_t n
+# define LENGTH_LIMIT_EXPR(Expr) Expr
+#else
+# define STRXCASECMP_FUNCTION strcasecmp
+# define STRXCASECMP_DECLARE_N /* empty */
+# define LENGTH_LIMIT_EXPR(Expr) 0
+#endif
+
+#include <stddef.h>
+#include <ctype.h>
+
+#define TOLOWER(Ch) (isupper (Ch) ? tolower (Ch) : (Ch))
+
+/* Compare {{no more than N characters of }}strings S1 and S2,
+ ignoring case, returning less than, equal to or
+ greater than zero if S1 is lexicographically less
+ than, equal to or greater than S2. */
+
+int
+STRXCASECMP_FUNCTION (const char *s1, const char *s2 STRXCASECMP_DECLARE_N)
+{
+ register const unsigned char *p1 = (const unsigned char *) s1;
+ register const unsigned char *p2 = (const unsigned char *) s2;
+ unsigned char c1, c2;
+
+ if (p1 == p2 || LENGTH_LIMIT_EXPR (n == 0))
+ return 0;
+
+ do
+ {
+ c1 = TOLOWER (*p1);
+ c2 = TOLOWER (*p2);
+
+ if (LENGTH_LIMIT_EXPR (--n == 0) || c1 == '\0')
+ break;
+
+ ++p1;
+ ++p2;
+ }
+ while (c1 == c2);
+
+ return c1 - c2;
+}
diff --git a/src/libs/libgroff/strerror.c b/src/libs/libgroff/strerror.c
new file mode 100644
index 0000000..a8887e5
--- /dev/null
+++ b/src/libs/libgroff/strerror.c
@@ -0,0 +1,46 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h> /* for MinGW */
+
+#define INT_DIGITS 19 /* enough for 64 bit integer */
+
+#ifndef HAVE_SYS_NERR
+extern int sys_nerr;
+#endif
+#ifndef HAVE_SYS_ERRLIST
+extern char *sys_errlist[];
+#endif
+
+char *strerror(n)
+ int n;
+{
+ static char buf[sizeof("Error ") + 1 + INT_DIGITS];
+ if (n >= 0 && n < sys_nerr && sys_errlist[n] != 0)
+ return sys_errlist[n];
+ else {
+ sprintf(buf, "Error %d", n);
+ return buf;
+ }
+}
diff --git a/src/libs/libgroff/string.cpp b/src/libs/libgroff/string.cpp
new file mode 100644
index 0000000..33c0565
--- /dev/null
+++ b/src/libs/libgroff/string.cpp
@@ -0,0 +1,354 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "lib.h"
+
+#include "stringclass.h"
+
+static char *salloc(int len, int *sizep);
+static void sfree(char *ptr, int size);
+static char *sfree_alloc(char *ptr, int size, int len, int *sizep);
+static char *srealloc(char *ptr, int size, int oldlen, int newlen, int *sizep);
+
+static char *salloc(int len, int *sizep)
+{
+ if (len == 0) {
+ *sizep = 0;
+ return 0;
+ }
+ else
+ return new char[*sizep = len*2];
+}
+
+static void sfree(char *ptr, int)
+{
+ delete[] ptr;
+}
+
+static char *sfree_alloc(char *ptr, int oldsz, int len, int *sizep)
+{
+ if (oldsz >= len) {
+ *sizep = oldsz;
+ return ptr;
+ }
+ delete[] ptr;
+ if (len == 0) {
+ *sizep = 0;
+ return 0;
+ }
+ else
+ return new char[*sizep = len*2];
+}
+
+static char *srealloc(char *ptr, int oldsz, int oldlen, int newlen, int *sizep)
+{
+ if (oldsz >= newlen) {
+ *sizep = oldsz;
+ return ptr;
+ }
+ if (newlen == 0) {
+ delete[] ptr;
+ *sizep = 0;
+ return 0;
+ }
+ else {
+ char *p = new char[*sizep = newlen*2];
+ if (oldlen < newlen && oldlen != 0)
+ memcpy(p, ptr, oldlen);
+ delete[] ptr;
+ return p;
+ }
+}
+
+string::string() : ptr(0), len(0), sz(0)
+{
+}
+
+string::string(const char *p, int n) : len(n)
+{
+ assert(n >= 0);
+ ptr = salloc(n, &sz);
+ if (n != 0)
+ memcpy(ptr, p, n);
+}
+
+string::string(const char *p)
+{
+ if (p == 0) {
+ len = 0;
+ ptr = 0;
+ sz = 0;
+ }
+ else {
+ len = strlen(p);
+ ptr = salloc(len, &sz);
+ if (len != 0)
+ memcpy(ptr, p, len);
+ }
+}
+
+string::string(char c) : len(1)
+{
+ ptr = salloc(1, &sz);
+ *ptr = c;
+}
+
+string::string(const string &s) : len(s.len)
+{
+ ptr = salloc(len, &sz);
+ if (len != 0)
+ memcpy(ptr, s.ptr, len);
+}
+
+string::~string()
+{
+ sfree(ptr, sz);
+}
+
+string &string::operator=(const string &s)
+{
+ ptr = sfree_alloc(ptr, sz, s.len, &sz);
+ len = s.len;
+ if (len != 0)
+ memcpy(ptr, s.ptr, len);
+ return *this;
+}
+
+string &string::operator=(const char *p)
+{
+ if (p == 0) {
+ sfree(ptr, len);
+ len = 0;
+ ptr = 0;
+ sz = 0;
+ }
+ else {
+ int slen = strlen(p);
+ ptr = sfree_alloc(ptr, sz, slen, &sz);
+ len = slen;
+ if (len != 0)
+ memcpy(ptr, p, len);
+ }
+ return *this;
+}
+
+string &string::operator=(char c)
+{
+ ptr = sfree_alloc(ptr, sz, 1, &sz);
+ len = 1;
+ *ptr = c;
+ return *this;
+}
+
+void string::move(string &s)
+{
+ sfree(ptr, sz);
+ ptr = s.ptr;
+ len = s.len;
+ sz = s.sz;
+ s.ptr = 0;
+ s.len = 0;
+ s.sz = 0;
+}
+
+void string::grow1()
+{
+ ptr = srealloc(ptr, sz, len, len + 1, &sz);
+}
+
+string &string::operator+=(const char *p)
+{
+ if (p != 0) {
+ int n = strlen(p);
+ int newlen = len + n;
+ if (newlen > sz)
+ ptr = srealloc(ptr, sz, len, newlen, &sz);
+ memcpy(ptr + len, p, n);
+ len = newlen;
+ }
+ return *this;
+}
+
+string &string::operator+=(const string &s)
+{
+ if (s.len != 0) {
+ int newlen = len + s.len;
+ if (newlen > sz)
+ ptr = srealloc(ptr, sz, len, newlen, &sz);
+ memcpy(ptr + len, s.ptr, s.len);
+ len = newlen;
+ }
+ return *this;
+}
+
+void string::append(const char *p, int n)
+{
+ if (n > 0) {
+ int newlen = len + n;
+ if (newlen > sz)
+ ptr = srealloc(ptr, sz, len, newlen, &sz);
+ memcpy(ptr + len, p, n);
+ len = newlen;
+ }
+}
+
+string::string(const char *s1, int n1, const char *s2, int n2)
+{
+ assert(n1 >= 0 && n2 >= 0);
+ len = n1 + n2;
+ if (len == 0) {
+ sz = 0;
+ ptr = 0;
+ }
+ else {
+ ptr = salloc(len, &sz);
+ if (n1 == 0)
+ memcpy(ptr, s2, n2);
+ else {
+ memcpy(ptr, s1, n1);
+ if (n2 != 0)
+ memcpy(ptr + n1, s2, n2);
+ }
+ }
+}
+
+int operator<=(const string &s1, const string &s2)
+{
+ return (s1.len <= s2.len
+ ? s1.len == 0 || memcmp(s1.ptr, s2.ptr, s1.len) <= 0
+ : s2.len != 0 && memcmp(s1.ptr, s2.ptr, s2.len) < 0);
+}
+
+int operator<(const string &s1, const string &s2)
+{
+ return (s1.len < s2.len
+ ? s1.len == 0 || memcmp(s1.ptr, s2.ptr, s1.len) <= 0
+ : s2.len != 0 && memcmp(s1.ptr, s2.ptr, s2.len) < 0);
+}
+
+int operator>=(const string &s1, const string &s2)
+{
+ return (s1.len >= s2.len
+ ? s2.len == 0 || memcmp(s1.ptr, s2.ptr, s2.len) >= 0
+ : s1.len != 0 && memcmp(s1.ptr, s2.ptr, s1.len) > 0);
+}
+
+int operator>(const string &s1, const string &s2)
+{
+ return (s1.len > s2.len
+ ? s2.len == 0 || memcmp(s1.ptr, s2.ptr, s2.len) >= 0
+ : s1.len != 0 && memcmp(s1.ptr, s2.ptr, s1.len) > 0);
+}
+
+void string::set_length(int i)
+{
+ assert(i >= 0);
+ if (i > sz)
+ ptr = srealloc(ptr, sz, len, i, &sz);
+ len = i;
+}
+
+void string::clear()
+{
+ len = 0;
+}
+
+int string::search(char c) const
+{
+ char *p = ptr ? (char *)memchr(ptr, c, len) : 0;
+ return p ? p - ptr : -1;
+}
+
+// we silently strip nuls
+
+char *string::extract() const
+{
+ char *p = ptr;
+ int n = len;
+ int nnuls = 0;
+ int i;
+ for (i = 0; i < n; i++)
+ if (p[i] == '\0')
+ nnuls++;
+ char *q =(char*)malloc(n + 1 - nnuls);
+ if (q != 0 /* nullptr */) {
+ char *r = q;
+ for (i = 0; i < n; i++)
+ if (p[i] != '\0')
+ *r++ = p[i];
+ *r = '\0';
+ }
+ return q;
+}
+
+void string::remove_spaces()
+{
+ int l = len - 1;
+ while (l >= 0 && ptr[l] == ' ')
+ l--;
+ char *p = ptr;
+ if (l > 0)
+ while (*p == ' ') {
+ p++;
+ l--;
+ }
+ if (len - 1 != l) {
+ if (l >= 0) {
+ len = l + 1;
+ char *tmp = new char[sz];
+ memcpy(tmp, p, len);
+ delete[] ptr;
+ ptr = tmp;
+ }
+ else {
+ len = 0;
+ if (ptr) {
+ delete[] ptr;
+ ptr = 0;
+ sz = 0;
+ }
+ }
+ }
+}
+
+void put_string(const string &s, FILE *fp)
+{
+ int len = s.length();
+ const char *ptr = s.contents();
+ for (int i = 0; i < len; i++)
+ putc(ptr[i], fp);
+}
+
+string as_string(int i)
+{
+ static char buf[INT_DIGITS + 2];
+ sprintf(buf, "%d", i);
+ return string(buf);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/strncasecmp.c b/src/libs/libgroff/strncasecmp.c
new file mode 100644
index 0000000..ec8aae2
--- /dev/null
+++ b/src/libs/libgroff/strncasecmp.c
@@ -0,0 +1,19 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#define LENGTH_LIMIT
+#include "strcasecmp.c"
diff --git a/src/libs/libgroff/strsave.cpp b/src/libs/libgroff/strsave.cpp
new file mode 100644
index 0000000..95a529b
--- /dev/null
+++ b/src/libs/libgroff/strsave.cpp
@@ -0,0 +1,40 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+char *strsave(const char *s)
+{
+ if (s == 0)
+ return 0;
+ char *p = (char*)malloc(strlen(s) + 1);
+ if (p != 0)
+ strcpy(p, s);
+ return p;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libgroff/strtol.c b/src/libs/libgroff/strtol.c
new file mode 100644
index 0000000..1820732
--- /dev/null
+++ b/src/libs/libgroff/strtol.c
@@ -0,0 +1,131 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#ifndef LONG_MAX
+#define LONG_MAX 2147483647
+#endif
+
+#ifndef LONG_MIN
+#define LONG_MIN (-LONG_MAX-1)
+#endif
+
+#ifdef isascii
+#define ISASCII(c) isascii(c)
+#else
+#define ISASCII(c) (1)
+#endif
+
+long strtol(str, ptr, base)
+ char *str, **ptr;
+ int base;
+{
+ char *start = str;
+ int neg = 0;
+ long val;
+ char *p;
+ static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+ while (ISASCII((unsigned char)*str) && isspace((unsigned char)*str))
+ str++;
+
+ if (*str == '-') {
+ neg = 1;
+ str++;
+ }
+ if (base == 0) {
+ if (*str == '0') {
+ if (str[1] == 'x' || str[1] == 'X') {
+ str += 2;
+ base = 16;
+ }
+ else
+ base = 8;
+ }
+ else
+ base = 10;
+ }
+ if (base < 2 || base > 36)
+ base = 10;
+ else if (base == 16 && *str == '0' && (str[1] == 'x' || str[1] == 'X'))
+ str += 2;
+
+ p = strchr(digits, (ISASCII((unsigned char)*str)
+ && isupper((unsigned char)*str)
+ ? tolower((unsigned char)*str)
+ : *str));
+ if (p == 0 || (val = (p - digits)) >= base) {
+ if (base == 16 && str > start && (str[-1] == 'x' || str[-1] == 'X')) {
+ if (ptr)
+ *ptr = str - 1;
+ }
+ else {
+ if (ptr)
+ *ptr = start;
+ errno = ERANGE;
+ }
+ return 0;
+ }
+ if (neg)
+ val = -val;
+
+ while (*++str != '\0') {
+ int n;
+
+ p = strchr(digits, (ISASCII((unsigned char)*str)
+ && isupper((unsigned char)*str)
+ ? tolower((unsigned char)*str) : *str));
+ if (p == 0)
+ break;
+ n = p - digits;
+ if (n >= base)
+ break;
+ if (neg) {
+ if (-(unsigned long)val > (-(unsigned long)LONG_MIN - n)/base) {
+ val = LONG_MIN;
+ errno = ERANGE;
+ }
+ else
+ val = val*base - n;
+ }
+ else {
+ if (val > (LONG_MAX - n)/base) {
+ val = LONG_MAX;
+ errno = ERANGE;
+ }
+ else
+ val = val*base + n;
+ }
+ }
+
+ if (ptr)
+ *ptr = str;
+
+ return val;
+}
diff --git a/src/libs/libgroff/symbol.cpp b/src/libs/libgroff/symbol.cpp
new file mode 100644
index 0000000..4f50627
--- /dev/null
+++ b/src/libs/libgroff/symbol.cpp
@@ -0,0 +1,157 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include "errarg.h"
+#include "error.h"
+#include "symbol.h"
+
+const char **symbol::table = 0;
+int symbol::table_used = 0;
+int symbol::table_size = 0;
+char *symbol::block = 0;
+int symbol::block_size = 0;
+
+const symbol NULL_SYMBOL;
+const symbol EMPTY_SYMBOL("");
+
+#ifdef BLOCK_SIZE
+#undef BLOCK_SIZE
+#endif
+
+const int BLOCK_SIZE = 1024;
+// the table will increase in size as necessary
+// the size will be chosen from the following array
+// add some more if you want
+static const unsigned int table_sizes[] = {
+ 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009, 80021,
+ 160001, 500009, 1000003, 1500007, 2000003, 0
+};
+const double FULL_MAX = 0.3; // don't let the table get more than this full
+
+static unsigned int hash_string(const char *p)
+{
+ // compute a hash code; this assumes 32-bit unsigned ints
+ // see p436 of Compilers by Aho, Sethi & Ullman
+ // give special treatment to two-character names
+ unsigned int hc = 0, g;
+ if (*p != 0) {
+ hc = *p++;
+ if (*p != 0) {
+ hc <<= 7;
+ hc += *p++;
+ for (; *p != 0; p++) {
+ hc <<= 4;
+ hc += *p;
+ if ((g = (hc & 0xf0000000)) == 0) {
+ hc ^= g >> 24;
+ hc ^= g;
+ }
+ }
+ }
+ }
+ return hc;
+}
+
+// Tell compiler that a variable is intentionally unused.
+inline void unused(void *) { }
+
+symbol::symbol(const char *p, int how)
+{
+ if (p == 0) {
+ s = 0;
+ return;
+ }
+ if (*p == 0) {
+ s = "";
+ return;
+ }
+ if (table == 0) {
+ table_size = table_sizes[0];
+ table = (const char **)new char*[table_size];
+ for (int i = 0; i < table_size; i++)
+ table[i] = 0;
+ table_used = 0;
+ }
+ unsigned int hc = hash_string(p);
+ const char **pp;
+ for (pp = table + hc % table_size;
+ *pp != 0;
+ (pp == table ? pp = table + table_size - 1 : --pp))
+ if (strcmp(p, *pp) == 0) {
+ s = *pp;
+ return;
+ }
+ if (how == MUST_ALREADY_EXIST) {
+ s = 0;
+ return;
+ }
+ if (table_used >= table_size - 1 || table_used >= table_size*FULL_MAX) {
+ const char **old_table = table;
+ unsigned int old_table_size = table_size;
+ int i;
+ for (i = 1; table_sizes[i] <= old_table_size; i++)
+ if (table_sizes[i] == 0)
+ fatal("too many symbols");
+ table_size = table_sizes[i];
+ table_used = 0;
+ table = (const char **)new char*[table_size];
+ for (i = 0; i < table_size; i++)
+ table[i] = 0;
+ for (pp = old_table + old_table_size - 1;
+ pp >= old_table;
+ --pp) {
+ symbol temp(*pp, 1); /* insert it into the new table */
+ unused(&temp);
+ }
+ delete[] old_table;
+ for (pp = table + hc % table_size;
+ *pp != 0;
+ (pp == table ? pp = table + table_size - 1 : --pp))
+ ;
+ }
+ ++table_used;
+ if (how == DONT_STORE) {
+ s = *pp = p;
+ }
+ else {
+ int len = strlen(p)+1;
+ if (block == 0 || block_size < len) {
+ block_size = len > BLOCK_SIZE ? len : BLOCK_SIZE;
+ block = new char [block_size];
+ }
+ (void)strcpy(block, p);
+ s = *pp = block;
+ block += len;
+ block_size -= len;
+ }
+}
+
+symbol concat(symbol s1, symbol s2)
+{
+ char *buf = new char [strlen(s1.contents()) + strlen(s2.contents()) + 1];
+ strcpy(buf, s1.contents());
+ strcat(buf, s2.contents());
+ symbol res(buf);
+ delete[] buf;
+ return res;
+}
+
+symbol default_symbol("default");
diff --git a/src/libs/libgroff/tmpfile.cpp b/src/libs/libgroff/tmpfile.cpp
new file mode 100644
index 0000000..5e807ae
--- /dev/null
+++ b/src/libs/libgroff/tmpfile.cpp
@@ -0,0 +1,188 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "posix.h"
+#include "errarg.h"
+#include "error.h"
+#include "nonposix.h"
+
+// If this is set, create temporary files there
+#define GROFF_TMPDIR_ENVVAR "GROFF_TMPDIR"
+// otherwise if this is set, create temporary files there
+#define TMPDIR_ENVVAR "TMPDIR"
+// otherwise, on MS-DOS or MS-Windows ...
+#if defined(__MSDOS__) || defined(_WIN32)
+// if either of these is set, create temporary files there
+// (giving priority to WIN32_TMPDIR_ENVVAR)
+#define WIN32_TMPDIR_ENVVAR "TMP"
+#define MSDOS_TMPDIR_ENVVAR "TEMP"
+#endif
+// otherwise if P_tmpdir is defined, create temporary files there
+#ifdef P_tmpdir
+# define DEFAULT_TMPDIR P_tmpdir
+#else
+// otherwise create temporary files here.
+# define DEFAULT_TMPDIR "/tmp"
+#endif
+// Use this as the prefix for temporary filenames.
+#define TMPFILE_PREFIX_SHORT ""
+#define TMPFILE_PREFIX_LONG "groff"
+
+char *tmpfile_prefix;
+size_t tmpfile_prefix_len;
+int use_short_postfix = 0;
+
+struct temp_init {
+ temp_init();
+ ~temp_init();
+} _temp_init;
+
+temp_init::temp_init()
+{
+ // First, choose a location for creating temporary files...
+ const char *tem;
+ // using the first match for any of the environment specs in listed order.
+ if (
+ (tem = getenv(GROFF_TMPDIR_ENVVAR)) == 0
+ && (tem = getenv(TMPDIR_ENVVAR)) == 0
+#if defined(__MSDOS__) || defined(_WIN32)
+ // If we didn't find a match for either of the above
+ // (which are preferred, regardless of the host operating system),
+ // and we are hosted on either MS-Windows or MS-DOS,
+ // then try the Microsoft conventions.
+ && (tem = getenv(WIN32_TMPDIR_ENVVAR)) == 0
+ && (tem = getenv(MSDOS_TMPDIR_ENVVAR)) == 0
+#endif
+ )
+ // If we didn't find an environment spec fall back to this default.
+ tem = DEFAULT_TMPDIR;
+ size_t tem_len = strlen(tem);
+ const char *tem_end = tem + tem_len - 1;
+ int need_slash = (strchr(DIR_SEPS, *tem_end) == 0) ? 1 : 0;
+ char *tem2 = new char[tem_len + need_slash + 1];
+ strcpy(tem2, tem);
+ if (need_slash)
+ strcat(tem2, "/");
+ const char *tem3 = TMPFILE_PREFIX_LONG;
+ if (file_name_max(tem2) <= 14) {
+ tem3 = TMPFILE_PREFIX_SHORT;
+ use_short_postfix = 1;
+ }
+ tmpfile_prefix_len = tem_len + need_slash + strlen(tem3);
+ tmpfile_prefix = new char[tmpfile_prefix_len + 1];
+ strcpy(tmpfile_prefix, tem2);
+ strcat(tmpfile_prefix, tem3);
+ delete[] tem2;
+}
+
+temp_init::~temp_init()
+{
+ delete[] tmpfile_prefix;
+}
+
+/*
+ * Generate a temporary name template with a postfix
+ * immediately after the TMPFILE_PREFIX.
+ * It uses the groff preferences for a temporary directory.
+ * Note that no file name is either created or opened,
+ * only the *template* is returned.
+ */
+
+char *xtmptemplate(const char *postfix_long, const char *postfix_short)
+{
+ const char *postfix = use_short_postfix ? postfix_short : postfix_long;
+ int postlen = 0;
+ if (postfix)
+ postlen = strlen(postfix);
+ char *templ = new char[tmpfile_prefix_len + postlen + 6 + 1];
+ strcpy(templ, tmpfile_prefix);
+ if (postlen > 0)
+ strcat(templ, postfix);
+ strcat(templ, "XXXXXX");
+ return templ;
+}
+
+// The trick with unlinking the temporary file while it is still in
+// use is not portable, it will fail on MS-DOS and most MS-Windows
+// filesystems. So it cannot be used on non-Posix systems.
+// Instead, we maintain a list of files to be deleted on exit.
+// This should be portable to all platforms.
+
+struct xtmpfile_list {
+ char *fname;
+ xtmpfile_list *next;
+ xtmpfile_list(char *fn) : fname(fn), next(0) {}
+};
+
+xtmpfile_list *xtmpfiles_to_delete = 0;
+
+struct xtmpfile_list_init {
+ ~xtmpfile_list_init();
+} _xtmpfile_list_init;
+
+xtmpfile_list_init::~xtmpfile_list_init()
+{
+ xtmpfile_list *x = xtmpfiles_to_delete;
+ while (x != 0) {
+ if (unlink(x->fname) < 0)
+ error("cannot unlink '%1': %2", x->fname, strerror(errno));
+ xtmpfile_list *tmp = x;
+ x = x->next;
+ delete[] tmp->fname;
+ delete tmp;
+ }
+}
+
+static void add_tmp_file(const char *name)
+{
+ char *s = new char[strlen(name)+1];
+ strcpy(s, name);
+ xtmpfile_list *x = new xtmpfile_list(s);
+ x->next = xtmpfiles_to_delete;
+ xtmpfiles_to_delete = x;
+}
+
+// Open a temporary file and with fatal error on failure.
+
+FILE *xtmpfile(char **namep,
+ const char *postfix_long, const char *postfix_short,
+ int do_unlink)
+{
+ char *templ = xtmptemplate(postfix_long, postfix_short);
+ errno = 0;
+ int fd = mkstemp(templ);
+ if (fd < 0)
+ fatal("cannot create temporary file: %1", strerror(errno));
+ errno = 0;
+ FILE *fp = fdopen(fd, FOPEN_RWB); // many callers of xtmpfile use binary I/O
+ if (!fp)
+ fatal("fdopen: %1", strerror(errno));
+ if (do_unlink)
+ add_tmp_file(templ);
+ if (namep)
+ *namep = templ;
+ else
+ delete[] templ;
+ return fp;
+}
diff --git a/src/libs/libgroff/tmpname.cpp b/src/libs/libgroff/tmpname.cpp
new file mode 100644
index 0000000..69dc9a4
--- /dev/null
+++ b/src/libs/libgroff/tmpname.cpp
@@ -0,0 +1,117 @@
+/* Copyright (C) 2001-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* This file is heavily based on the function __gen_tempname() in the
+ file tempname.c which is part of the fileutils package. */
+
+
+#include "lib.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+
+#include "posix.h"
+#include "nonposix.h"
+
+#ifndef TMP_MAX
+# define TMP_MAX 238328
+#endif
+
+#if HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#ifdef HAVE_GETTIMEOFDAY
+#ifdef NEED_DECLARATION_GETTIMEOFDAY
+extern "C" {
+ int gettimeofday(struct timeval *, void *);
+}
+#endif
+#endif
+
+#if HAVE_CC_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+/* Use the widest available unsigned type if uint64_t is not
+ available. The algorithm below extracts a number less than 62**6
+ (approximately 2**35.725) from uint64_t, so ancient hosts where
+ uintmax_t is only 32 bits lose about 3.725 bits of randomness,
+ which is better than not having mkstemp at all. */
+#if !defined UINT64_MAX && !defined uint64_t
+# define uint64_t uintmax_t
+#endif
+
+/* These are the characters used in temporary filenames. */
+static const char letters[] =
+"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+int gen_tempname(char *tmpl, int dir)
+{
+ static uint64_t value;
+
+ size_t len = strlen(tmpl);
+ if (len < 6 || strcmp(&tmpl[len - 6], "XXXXXX"))
+ return -1; /* EINVAL */
+
+ /* This is where the Xs start. */
+ char *XXXXXX = &tmpl[len - 6];
+
+ /* Get some more or less random data. */
+#if HAVE_GETTIMEOFDAY
+ timeval tv;
+ gettimeofday(&tv, 0);
+ uint64_t random_time_bits = ((uint64_t)tv.tv_usec << 16) ^ tv.tv_sec;
+#else
+ uint64_t random_time_bits = time(0);
+#endif
+ value += random_time_bits ^ getpid();
+
+ for (int count = 0; count < TMP_MAX; value += 7777, ++count) {
+ uint64_t v = value;
+
+ /* Fill in the random bits. */
+ XXXXXX[0] = letters[v % 62];
+ v /= 62;
+ XXXXXX[1] = letters[v % 62];
+ v /= 62;
+ XXXXXX[2] = letters[v % 62];
+ v /= 62;
+ XXXXXX[3] = letters[v % 62];
+ v /= 62;
+ XXXXXX[4] = letters[v % 62];
+ v /= 62;
+ XXXXXX[5] = letters[v % 62];
+
+ int fd = dir ? mkdir(tmpl, S_IRUSR | S_IWUSR | S_IXUSR)
+ : open(tmpl,
+ O_RDWR | O_CREAT | O_EXCL | O_BINARY,
+ S_IRUSR | S_IWUSR);
+
+ if (fd >= 0)
+ return fd;
+ else if (errno != EEXIST)
+ return -1;
+ }
+
+ /* We got out of the loop because we ran out of combinations to try. */
+ return -1; /* EEXIST */
+}
diff --git a/src/libs/libgroff/unicode.cpp b/src/libs/libgroff/unicode.cpp
new file mode 100644
index 0000000..29e80c7
--- /dev/null
+++ b/src/libs/libgroff/unicode.cpp
@@ -0,0 +1,65 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg <wl@gnu.org>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "cset.h"
+#include "stringclass.h"
+
+#include "unicode.h"
+
+const char *check_unicode_name(const char *u)
+{
+ if (*u != 'u')
+ return 0;
+ const char *p = ++u;
+ for (;;) {
+ int val = 0;
+ const char *start = p;
+ for (;;) {
+ // only uppercase hex digits allowed
+ if (!csxdigit(*p))
+ return 0;
+ if (csdigit(*p))
+ val = val*0x10 + (*p-'0');
+ else if (csupper(*p))
+ val = val*0x10 + (*p-'A'+10);
+ else
+ return 0;
+ // biggest Unicode value is U+10FFFF
+ if (val > 0x10FFFF)
+ return 0;
+ p++;
+ if (*p == '\0' || *p == '_')
+ break;
+ }
+ // surrogates not allowed
+ if ((val >= 0xD800 && val <= 0xDBFF) || (val >= 0xDC00 && val <= 0xDFFF))
+ return 0;
+ if (val > 0xFFFF) {
+ if (*start == '0') // no leading zeros allowed if > 0xFFFF
+ return 0;
+ }
+ else if (p - start != 4) // otherwise, check for exactly 4 hex digits
+ return 0;
+ if (*p == '\0')
+ break;
+ p++;
+ }
+ return u;
+}
diff --git a/src/libs/libgroff/uniglyph.cpp b/src/libs/libgroff/uniglyph.cpp
new file mode 100644
index 0000000..bab2bc4
--- /dev/null
+++ b/src/libs/libgroff/uniglyph.cpp
@@ -0,0 +1,497 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg <wl@gnu.org>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "stringclass.h"
+#include "ptable.h"
+
+#include "unicode.h"
+
+struct unicode_to_glyph {
+ char *value;
+};
+
+declare_ptable(unicode_to_glyph)
+implement_ptable(unicode_to_glyph)
+
+PTABLE(unicode_to_glyph) unicode_to_glyph_table;
+
+struct S {
+ const char *key;
+ const char *value;
+} unicode_to_glyph_list[] = {
+ { "0021", "!" },
+//{ "0022", "\"" },
+ { "0022", "dq" },
+//{ "0023", "#" },
+ { "0023", "sh" },
+//{ "0024", "$" },
+ { "0024", "Do" },
+ { "0025", "%" },
+ { "0026", "&" },
+ { "0027", "aq" },
+ { "0028", "(" },
+ { "0029", ")" },
+ { "002A", "*" },
+//{ "002B", "+" },
+ { "002B", "pl" },
+ { "002C", "," },
+ { "002E", "." },
+//{ "002F", "/" },
+ { "002F", "sl" },
+ { "0030", "0" },
+ { "0031", "1" },
+ { "0032", "2" },
+ { "0033", "3" },
+ { "0034", "4" },
+ { "0035", "5" },
+ { "0036", "6" },
+ { "0037", "7" },
+ { "0038", "8" },
+ { "0039", "9" },
+ { "003A", ":" },
+ { "003B", ";" },
+ { "003C", "<" },
+//{ "003D", "=" },
+ { "003D", "eq" },
+ { "003D_0338", "!=" },
+ { "003E", ">" },
+ { "003F", "?" },
+//{ "0040", "@" },
+ { "0040", "at" },
+ { "0041", "A" },
+ { "0041_0300", "`A" },
+ { "0041_0301", "'A" },
+ { "0041_0302", "^A" },
+ { "0041_0303", "~A" },
+ { "0041_0308", ":A" },
+ { "0041_030A", "oA" },
+ { "0042", "B" },
+ { "0043", "C" },
+ { "0043_0301", "'C" },
+ { "0043_0327", ",C" },
+ { "0044", "D" },
+ { "0045", "E" },
+ { "0045_0300", "`E" },
+ { "0045_0301", "'E" },
+ { "0045_0302", "^E" },
+ { "0045_0308", ":E" },
+ { "0046", "F" },
+ { "0047", "G" },
+ { "0048", "H" },
+ { "0049", "I" },
+ { "0049_0300", "`I" },
+ { "0049_0301", "'I" },
+ { "0049_0302", "^I" },
+ { "0049_0308", ":I" },
+ { "004A", "J" },
+ { "004B", "K" },
+ { "004C", "L" },
+ { "004D", "M" },
+ { "004E", "N" },
+ { "004E_0303", "~N" },
+ { "004F", "O" },
+ { "004F_0300", "`O" },
+ { "004F_0301", "'O" },
+ { "004F_0302", "^O" },
+ { "004F_0303", "~O" },
+ { "004F_0308", ":O" },
+ { "0050", "P" },
+ { "0051", "Q" },
+ { "0052", "R" },
+ { "0053", "S" },
+ { "0053_030C", "vS" },
+ { "0054", "T" },
+ { "0055", "U" },
+ { "0055_0300", "`U" },
+ { "0055_0301", "'U" },
+ { "0055_0302", "^U" },
+ { "0055_0308", ":U" },
+ { "0056", "V" },
+ { "0057", "W" },
+ { "0058", "X" },
+ { "0059", "Y" },
+ { "0059_0301", "'Y" },
+ { "0059_0308", ":Y" },
+ { "005A", "Z" },
+ { "005A_030C", "vZ" },
+ { "005B", "lB" },
+//{ "005B", "[" },
+ { "005C", "rs" },
+//{ "005C", "\\" },
+ { "005D", "rB" },
+//{ "005D", "]" },
+//{ "005E", "^" },
+//{ "005E", "a^" },
+ { "005E", "ha" },
+//{ "005F", "_" },
+//{ "005F", "ru" },
+ { "005F", "ul" },
+ { "0060", "ga" },
+ { "0061", "a" },
+ { "0061_0300", "`a" },
+ { "0061_0301", "'a" },
+ { "0061_0302", "^a" },
+ { "0061_0303", "~a" },
+ { "0061_0308", ":a" },
+ { "0061_030A", "oa" },
+ { "0062", "b" },
+ { "0063", "c" },
+ { "0063_0301", "'c" },
+ { "0063_0327", ",c" },
+ { "0064", "d" },
+ { "0065", "e" },
+ { "0065_0300", "`e" },
+ { "0065_0301", "'e" },
+ { "0065_0302", "^e" },
+ { "0065_0308", ":e" },
+ { "0066", "f" },
+ { "0066_0066", "ff" },
+ { "0066_0066_0069", "Fi" },
+ { "0066_0066_006C", "Fl" },
+ { "0066_0069", "fi" },
+ { "0066_006C", "fl" },
+ { "0067", "g" },
+ { "0068", "h" },
+ { "0069", "i" },
+ { "0069_0300", "`i" },
+ { "0069_0301", "'i" },
+ { "0069_0302", "^i" },
+ { "0069_0308", ":i" },
+ { "006A", "j" },
+ { "006B", "k" },
+ { "006C", "l" },
+ { "006D", "m" },
+ { "006E", "n" },
+ { "006E_0303", "~n" },
+ { "006F", "o" },
+ { "006F_0300", "`o" },
+ { "006F_0301", "'o" },
+ { "006F_0302", "^o" },
+ { "006F_0303", "~o" },
+ { "006F_0308", ":o" },
+ { "0070", "p" },
+ { "0071", "q" },
+ { "0072", "r" },
+ { "0073", "s" },
+ { "0073_030C", "vs" },
+ { "0074", "t" },
+ { "0075", "u" },
+ { "0075_0300", "`u" },
+ { "0075_0301", "'u" },
+ { "0075_0302", "^u" },
+ { "0075_0308", ":u" },
+ { "0076", "v" },
+ { "0077", "w" },
+ { "0078", "x" },
+ { "0079", "y" },
+ { "0079_0301", "'y" },
+ { "0079_0308", ":y" },
+ { "007A", "z" },
+ { "007A_030C", "vz" },
+ { "007B", "lC" },
+//{ "007B", "{" },
+ { "007C", "ba" },
+//{ "007C", "or" },
+//{ "007C", "|" },
+ { "007D", "rC" },
+//{ "007D", "}" },
+//{ "007E", "a~" },
+ { "007E", "ti" },
+//{ "007E", "~" },
+ { "00A1", "r!" },
+ { "00A2", "ct" },
+ { "00A3", "Po" },
+ { "00A4", "Cs" },
+ { "00A5", "Ye" },
+ { "00A6", "bb" },
+ { "00A7", "sc" },
+ { "00A8", "ad" },
+ { "00A9", "co" },
+ { "00AA", "Of" },
+ { "00AB", "Fo" },
+ { "00AC", "no" },
+//{ "00AC", "tno" },
+ // In groff, U+00AD is an input character only; it is not mapped to
+ // a glyph but to '\%'.
+ { "00AE", "rg" },
+ { "00AF", "a-" },
+ { "00B0", "de" },
+ { "00B1", "+-" },
+//{ "00B1", "t+-" },
+ { "00B2", "S2" },
+ { "00B3", "S3" },
+ { "00B4", "aa" },
+ { "00B5", "mc" },
+ { "00B6", "ps" },
+ { "00B7", "pc" },
+ { "00B8", "ac" },
+ { "00B9", "S1" },
+ { "00BA", "Om" },
+ { "00BB", "Fc" },
+ { "00BC", "14" },
+ { "00BD", "12" },
+ { "00BE", "34" },
+ { "00BF", "r?" },
+ { "00C6", "AE" },
+ { "00D0", "-D" },
+ { "00D7", "mu" },
+//{ "00D7", "tmu" },
+ { "00D8", "/O" },
+ { "00DE", "TP" },
+ { "00DF", "ss" },
+ { "00E6", "ae" },
+ { "00F0", "Sd" },
+ { "00F7", "di" },
+//{ "00F7", "tdi" },
+ { "00F8", "/o" },
+ { "00FE", "Tp" },
+ { "0131", ".i" },
+ { "0132", "IJ" },
+ { "0133", "ij" },
+ { "0141", "/L" },
+ { "0142", "/l" },
+ { "0152", "OE" },
+ { "0153", "oe" },
+ { "0192", "Fn" },
+ { "0237", ".j" },
+ { "02C7", "ah" },
+ { "02D8", "ab" },
+ { "02D9", "a." },
+ { "02DA", "ao" },
+ { "02DB", "ho" },
+ { "02DD", "a\"" },
+ { "0391", "*A" },
+ { "0392", "*B" },
+ { "0393", "*G" },
+ { "0394", "*D" },
+ { "0395", "*E" },
+ { "0396", "*Z" },
+ { "0397", "*Y" },
+ { "0398", "*H" },
+ { "0399", "*I" },
+ { "039A", "*K" },
+ { "039B", "*L" },
+ { "039C", "*M" },
+ { "039D", "*N" },
+ { "039E", "*C" },
+ { "039F", "*O" },
+ { "03A0", "*P" },
+ { "03A1", "*R" },
+ { "03A3", "*S" },
+ { "03A4", "*T" },
+ { "03A5", "*U" },
+ { "03A6", "*F" },
+ { "03A7", "*X" },
+ { "03A8", "*Q" },
+ { "03A9", "*W" },
+ { "03B1", "*a" },
+ { "03B2", "*b" },
+ { "03B3", "*g" },
+ { "03B4", "*d" },
+ { "03B5", "*e" },
+ { "03B6", "*z" },
+ { "03B7", "*y" },
+ { "03B8", "*h" },
+ { "03B9", "*i" },
+ { "03BA", "*k" },
+ { "03BB", "*l" },
+ { "03BC", "*m" },
+ { "03BD", "*n" },
+ { "03BE", "*c" },
+ { "03BF", "*o" },
+ { "03C0", "*p" },
+ { "03C1", "*r" },
+ { "03C2", "ts" },
+ { "03C3", "*s" },
+ { "03C4", "*t" },
+ { "03C5", "*u" },
+ { "03C6", "+f" },
+ { "03C7", "*x" },
+ { "03C8", "*q" },
+ { "03C9", "*w" },
+ { "03D1", "+h" },
+ { "03D5", "*f" },
+ { "03D6", "+p" },
+ { "03F5", "+e" },
+//{ "2010", "-" },
+ { "2010", "hy" },
+ { "2013", "en" },
+ { "2014", "em" },
+//{ "2018", "`" },
+ { "2018", "oq" },
+//{ "2019", "'" },
+ { "2019", "cq" },
+ { "201A", "bq" },
+ { "201C", "lq" },
+ { "201D", "rq" },
+ { "201E", "Bq" },
+ { "2020", "dg" },
+ { "2021", "dd" },
+ { "2022", "bu" },
+ { "2030", "%0" },
+ { "2032", "fm" },
+ { "2033", "sd" },
+ { "2039", "fo" },
+ { "203A", "fc" },
+ { "203E", "rn" },
+ { "2044", "f/" },
+ { "20AC", "Eu" },
+//{ "20AC", "eu" },
+ { "210F", "-h" },
+//{ "210F", "hbar" },
+ { "2111", "Im" },
+ { "2118", "wp" },
+ { "211C", "Re" },
+ { "2122", "tm" },
+ { "2135", "Ah" },
+ { "215B", "18" },
+ { "215C", "38" },
+ { "215D", "58" },
+ { "215E", "78" },
+ { "2190", "<-" },
+ { "2191", "ua" },
+ { "2192", "->" },
+ { "2193", "da" },
+ { "2194", "<>" },
+ { "2195", "va" },
+ { "21B5", "CR" },
+ { "21D0", "lA" },
+ { "21D1", "uA" },
+ { "21D2", "rA" },
+ { "21D3", "dA" },
+ { "21D4", "hA" },
+ { "21D5", "vA" },
+ { "2200", "fa" },
+ { "2202", "pd" },
+ { "2203", "te" },
+ { "2205", "es" },
+ { "2207", "gr" },
+ { "2208", "mo" },
+ { "2208_0338", "nm" },
+ { "220B", "st" },
+ { "220F", "product" },
+ { "2210", "coproduct" },
+ { "2211", "sum" },
+ { "2212", "mi" },
+//{ "2212", "\\-" },
+ { "2213", "-+" },
+ { "2217", "**" },
+ { "221A", "sr" },
+ { "221D", "pt" },
+ { "221E", "if" },
+ { "2220", "/_" },
+ { "2227", "AN" },
+ { "2228", "OR" },
+ { "2229", "ca" },
+ { "222A", "cu" },
+ { "222B", "is" },
+//{ "222B", "integral" },
+//{ "2234", "3d" },
+ { "2234", "tf" },
+ { "223C", "ap" },
+ { "2243", "|=" },
+ { "2245", "=~" },
+//{ "2248", "~=" },
+ { "2248", "~~" },
+ { "2261", "==" },
+ { "2261_0338", "ne" },
+ { "2264", "<=" },
+ { "2265", ">=" },
+ { "226A", ">>" },
+ { "226B", "<<" },
+ { "2282", "sb" },
+ { "2282_0338", "nb" },
+ { "2283", "sp" },
+ { "2283_0338", "nc" },
+ { "2286", "ib" },
+ { "2287", "ip" },
+ { "2295", "c+" },
+ { "2297", "c*" },
+ { "22A5", "pp" },
+ { "22C5", "md" },
+ { "2308", "lc" },
+ { "2309", "rc" },
+ { "230A", "lf" },
+ { "230B", "rf" },
+ { "239B", "parenlefttp" },
+ { "239C", "parenleftex" },
+ { "239D", "parenleftbt" },
+ { "239E", "parenrighttp" },
+ { "239F", "parenrightex" },
+ { "23A0", "parenrightbt" },
+//{ "23A1", "bracketlefttp" },
+ { "23A2", "bracketleftex" },
+//{ "23A3", "bracketleftbt" },
+//{ "23A4", "bracketrighttp" },
+ { "23A5", "bracketrightex" },
+//{ "23A6", "bracketrightbt" },
+ { "23A7", "lt" },
+//{ "23A7", "bracelefttp" },
+ { "23A8", "lk" },
+//{ "23A8", "braceleftmid" },
+ { "23A9", "lb" },
+//{ "23A9", "braceleftbt" },
+ { "23AA", "bv" },
+//{ "23AA", "braceex" },
+//{ "23AA", "braceleftex" },
+//{ "23AA", "bracerightex" },
+ { "23AB", "rt" },
+//{ "23AB", "bracerighttp" },
+ { "23AC", "rk" },
+//{ "23AC", "bracerightmid" },
+ { "23AD", "rb" },
+//{ "23AD", "bracerightbt" },
+ { "23AF", "an" },
+ { "2502", "br" },
+ { "25A1", "sq" },
+ { "25CA", "lz" },
+ { "25CB", "ci" },
+ { "261C", "lh" },
+ { "261E", "rh" },
+ { "2660", "SP" },
+ { "2663", "CL" },
+ { "2665", "HE" },
+ { "2666", "DI" },
+ { "2713", "OK" },
+ { "27E8", "la" },
+ { "27E9", "ra" },
+};
+
+// global constructor
+static struct unicode_to_glyph_init {
+ unicode_to_glyph_init();
+} _unicode_to_glyph_init;
+
+unicode_to_glyph_init::unicode_to_glyph_init()
+{
+ for (unsigned int i = 0;
+ i < sizeof(unicode_to_glyph_list)/sizeof(unicode_to_glyph_list[0]);
+ i++) {
+ unicode_to_glyph *utg = new unicode_to_glyph[1];
+ utg->value = (char *)unicode_to_glyph_list[i].value;
+ unicode_to_glyph_table.define(unicode_to_glyph_list[i].key, utg);
+ }
+}
+
+const char *unicode_to_glyph_name(const char *s)
+{
+ unicode_to_glyph *result = unicode_to_glyph_table.lookup(s);
+ return result ? result->value : 0;
+}
diff --git a/src/libs/libgroff/uniuni.cpp b/src/libs/libgroff/uniuni.cpp
new file mode 100644
index 0000000..e3b2708
--- /dev/null
+++ b/src/libs/libgroff/uniuni.cpp
@@ -0,0 +1,2128 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg <wl@gnu.org>
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// This code has been algorithmically derived from the file
+// UnicodeData.txt, version 7.0.0, available from unicode.org,
+// on 2014-12-16.
+
+#include "lib.h"
+#include "stringclass.h"
+#include "ptable.h"
+
+#include "unicode.h"
+
+struct unicode_decompose {
+ char *value;
+};
+
+declare_ptable(unicode_decompose)
+implement_ptable(unicode_decompose)
+
+PTABLE(unicode_decompose) unicode_decompose_table;
+
+// the first digit in the composite string gives the number of composites
+
+struct S {
+ const char *key;
+ const char *value;
+} unicode_decompose_list[] = {
+ { "00C0", "20041_0300" },
+ { "00C1", "20041_0301" },
+ { "00C2", "20041_0302" },
+ { "00C3", "20041_0303" },
+ { "00C4", "20041_0308" },
+ { "00C5", "20041_030A" },
+ { "00C7", "20043_0327" },
+ { "00C8", "20045_0300" },
+ { "00C9", "20045_0301" },
+ { "00CA", "20045_0302" },
+ { "00CB", "20045_0308" },
+ { "00CC", "20049_0300" },
+ { "00CD", "20049_0301" },
+ { "00CE", "20049_0302" },
+ { "00CF", "20049_0308" },
+ { "00D1", "2004E_0303" },
+ { "00D2", "2004F_0300" },
+ { "00D3", "2004F_0301" },
+ { "00D4", "2004F_0302" },
+ { "00D5", "2004F_0303" },
+ { "00D6", "2004F_0308" },
+ { "00D9", "20055_0300" },
+ { "00DA", "20055_0301" },
+ { "00DB", "20055_0302" },
+ { "00DC", "20055_0308" },
+ { "00DD", "20059_0301" },
+ { "00E0", "20061_0300" },
+ { "00E1", "20061_0301" },
+ { "00E2", "20061_0302" },
+ { "00E3", "20061_0303" },
+ { "00E4", "20061_0308" },
+ { "00E5", "20061_030A" },
+ { "00E7", "20063_0327" },
+ { "00E8", "20065_0300" },
+ { "00E9", "20065_0301" },
+ { "00EA", "20065_0302" },
+ { "00EB", "20065_0308" },
+ { "00EC", "20069_0300" },
+ { "00ED", "20069_0301" },
+ { "00EE", "20069_0302" },
+ { "00EF", "20069_0308" },
+ { "00F1", "2006E_0303" },
+ { "00F2", "2006F_0300" },
+ { "00F3", "2006F_0301" },
+ { "00F4", "2006F_0302" },
+ { "00F5", "2006F_0303" },
+ { "00F6", "2006F_0308" },
+ { "00F9", "20075_0300" },
+ { "00FA", "20075_0301" },
+ { "00FB", "20075_0302" },
+ { "00FC", "20075_0308" },
+ { "00FD", "20079_0301" },
+ { "00FF", "20079_0308" },
+ { "0100", "20041_0304" },
+ { "0101", "20061_0304" },
+ { "0102", "20041_0306" },
+ { "0103", "20061_0306" },
+ { "0104", "20041_0328" },
+ { "0105", "20061_0328" },
+ { "0106", "20043_0301" },
+ { "0107", "20063_0301" },
+ { "0108", "20043_0302" },
+ { "0109", "20063_0302" },
+ { "010A", "20043_0307" },
+ { "010B", "20063_0307" },
+ { "010C", "20043_030C" },
+ { "010D", "20063_030C" },
+ { "010E", "20044_030C" },
+ { "010F", "20064_030C" },
+ { "0112", "20045_0304" },
+ { "0113", "20065_0304" },
+ { "0114", "20045_0306" },
+ { "0115", "20065_0306" },
+ { "0116", "20045_0307" },
+ { "0117", "20065_0307" },
+ { "0118", "20045_0328" },
+ { "0119", "20065_0328" },
+ { "011A", "20045_030C" },
+ { "011B", "20065_030C" },
+ { "011C", "20047_0302" },
+ { "011D", "20067_0302" },
+ { "011E", "20047_0306" },
+ { "011F", "20067_0306" },
+ { "0120", "20047_0307" },
+ { "0121", "20067_0307" },
+ { "0122", "20047_0327" },
+ { "0123", "20067_0327" },
+ { "0124", "20048_0302" },
+ { "0125", "20068_0302" },
+ { "0128", "20049_0303" },
+ { "0129", "20069_0303" },
+ { "012A", "20049_0304" },
+ { "012B", "20069_0304" },
+ { "012C", "20049_0306" },
+ { "012D", "20069_0306" },
+ { "012E", "20049_0328" },
+ { "012F", "20069_0328" },
+ { "0130", "20049_0307" },
+ { "0134", "2004A_0302" },
+ { "0135", "2006A_0302" },
+ { "0136", "2004B_0327" },
+ { "0137", "2006B_0327" },
+ { "0139", "2004C_0301" },
+ { "013A", "2006C_0301" },
+ { "013B", "2004C_0327" },
+ { "013C", "2006C_0327" },
+ { "013D", "2004C_030C" },
+ { "013E", "2006C_030C" },
+ { "0143", "2004E_0301" },
+ { "0144", "2006E_0301" },
+ { "0145", "2004E_0327" },
+ { "0146", "2006E_0327" },
+ { "0147", "2004E_030C" },
+ { "0148", "2006E_030C" },
+ { "014C", "2004F_0304" },
+ { "014D", "2006F_0304" },
+ { "014E", "2004F_0306" },
+ { "014F", "2006F_0306" },
+ { "0150", "2004F_030B" },
+ { "0151", "2006F_030B" },
+ { "0154", "20052_0301" },
+ { "0155", "20072_0301" },
+ { "0156", "20052_0327" },
+ { "0157", "20072_0327" },
+ { "0158", "20052_030C" },
+ { "0159", "20072_030C" },
+ { "015A", "20053_0301" },
+ { "015B", "20073_0301" },
+ { "015C", "20053_0302" },
+ { "015D", "20073_0302" },
+ { "015E", "20053_0327" },
+ { "015F", "20073_0327" },
+ { "0160", "20053_030C" },
+ { "0161", "20073_030C" },
+ { "0162", "20054_0327" },
+ { "0163", "20074_0327" },
+ { "0164", "20054_030C" },
+ { "0165", "20074_030C" },
+ { "0168", "20055_0303" },
+ { "0169", "20075_0303" },
+ { "016A", "20055_0304" },
+ { "016B", "20075_0304" },
+ { "016C", "20055_0306" },
+ { "016D", "20075_0306" },
+ { "016E", "20055_030A" },
+ { "016F", "20075_030A" },
+ { "0170", "20055_030B" },
+ { "0171", "20075_030B" },
+ { "0172", "20055_0328" },
+ { "0173", "20075_0328" },
+ { "0174", "20057_0302" },
+ { "0175", "20077_0302" },
+ { "0176", "20059_0302" },
+ { "0177", "20079_0302" },
+ { "0178", "20059_0308" },
+ { "0179", "2005A_0301" },
+ { "017A", "2007A_0301" },
+ { "017B", "2005A_0307" },
+ { "017C", "2007A_0307" },
+ { "017D", "2005A_030C" },
+ { "017E", "2007A_030C" },
+ { "01A0", "2004F_031B" },
+ { "01A1", "2006F_031B" },
+ { "01AF", "20055_031B" },
+ { "01B0", "20075_031B" },
+ { "01CD", "20041_030C" },
+ { "01CE", "20061_030C" },
+ { "01CF", "20049_030C" },
+ { "01D0", "20069_030C" },
+ { "01D1", "2004F_030C" },
+ { "01D2", "2006F_030C" },
+ { "01D3", "20055_030C" },
+ { "01D4", "20075_030C" },
+ { "01D5", "30055_0308_0304" },
+ { "01D6", "30075_0308_0304" },
+ { "01D7", "30055_0308_0301" },
+ { "01D8", "30075_0308_0301" },
+ { "01D9", "30055_0308_030C" },
+ { "01DA", "30075_0308_030C" },
+ { "01DB", "30055_0308_0300" },
+ { "01DC", "30075_0308_0300" },
+ { "01DE", "30041_0308_0304" },
+ { "01DF", "30061_0308_0304" },
+ { "01E0", "30041_0307_0304" },
+ { "01E1", "30061_0307_0304" },
+ { "01E2", "200C6_0304" },
+ { "01E3", "200E6_0304" },
+ { "01E6", "20047_030C" },
+ { "01E7", "20067_030C" },
+ { "01E8", "2004B_030C" },
+ { "01E9", "2006B_030C" },
+ { "01EA", "2004F_0328" },
+ { "01EB", "2006F_0328" },
+ { "01EC", "3004F_0328_0304" },
+ { "01ED", "3006F_0328_0304" },
+ { "01EE", "201B7_030C" },
+ { "01EF", "20292_030C" },
+ { "01F0", "2006A_030C" },
+ { "01F4", "20047_0301" },
+ { "01F5", "20067_0301" },
+ { "01F8", "2004E_0300" },
+ { "01F9", "2006E_0300" },
+ { "01FA", "30041_030A_0301" },
+ { "01FB", "30061_030A_0301" },
+ { "01FC", "200C6_0301" },
+ { "01FD", "200E6_0301" },
+ { "01FE", "200D8_0301" },
+ { "01FF", "200F8_0301" },
+ { "0200", "20041_030F" },
+ { "0201", "20061_030F" },
+ { "0202", "20041_0311" },
+ { "0203", "20061_0311" },
+ { "0204", "20045_030F" },
+ { "0205", "20065_030F" },
+ { "0206", "20045_0311" },
+ { "0207", "20065_0311" },
+ { "0208", "20049_030F" },
+ { "0209", "20069_030F" },
+ { "020A", "20049_0311" },
+ { "020B", "20069_0311" },
+ { "020C", "2004F_030F" },
+ { "020D", "2006F_030F" },
+ { "020E", "2004F_0311" },
+ { "020F", "2006F_0311" },
+ { "0210", "20052_030F" },
+ { "0211", "20072_030F" },
+ { "0212", "20052_0311" },
+ { "0213", "20072_0311" },
+ { "0214", "20055_030F" },
+ { "0215", "20075_030F" },
+ { "0216", "20055_0311" },
+ { "0217", "20075_0311" },
+ { "0218", "20053_0326" },
+ { "0219", "20073_0326" },
+ { "021A", "20054_0326" },
+ { "021B", "20074_0326" },
+ { "021E", "20048_030C" },
+ { "021F", "20068_030C" },
+ { "0226", "20041_0307" },
+ { "0227", "20061_0307" },
+ { "0228", "20045_0327" },
+ { "0229", "20065_0327" },
+ { "022A", "3004F_0308_0304" },
+ { "022B", "3006F_0308_0304" },
+ { "022C", "3004F_0303_0304" },
+ { "022D", "3006F_0303_0304" },
+ { "022E", "2004F_0307" },
+ { "022F", "2006F_0307" },
+ { "0230", "3004F_0307_0304" },
+ { "0231", "3006F_0307_0304" },
+ { "0232", "20059_0304" },
+ { "0233", "20079_0304" },
+ { "0340", "10300" },
+ { "0341", "10301" },
+ { "0343", "10313" },
+ { "0344", "20308_0301" },
+ { "0374", "102B9" },
+ { "037E", "1003B" },
+ { "0385", "200A8_0301" },
+ { "0386", "20391_0301" },
+ { "0387", "100B7" },
+ { "0388", "20395_0301" },
+ { "0389", "20397_0301" },
+ { "038A", "20399_0301" },
+ { "038C", "2039F_0301" },
+ { "038E", "203A5_0301" },
+ { "038F", "203A9_0301" },
+ { "0390", "303B9_0308_0301" },
+ { "03AA", "20399_0308" },
+ { "03AB", "203A5_0308" },
+ { "03AC", "203B1_0301" },
+ { "03AD", "203B5_0301" },
+ { "03AE", "203B7_0301" },
+ { "03AF", "203B9_0301" },
+ { "03B0", "303C5_0308_0301" },
+ { "03CA", "203B9_0308" },
+ { "03CB", "203C5_0308" },
+ { "03CC", "203BF_0301" },
+ { "03CD", "203C5_0301" },
+ { "03CE", "203C9_0301" },
+ { "03D3", "203D2_0301" },
+ { "03D4", "203D2_0308" },
+ { "0400", "20415_0300" },
+ { "0401", "20415_0308" },
+ { "0403", "20413_0301" },
+ { "0407", "20406_0308" },
+ { "040C", "2041A_0301" },
+ { "040D", "20418_0300" },
+ { "040E", "20423_0306" },
+ { "0419", "20418_0306" },
+ { "0439", "20438_0306" },
+ { "0450", "20435_0300" },
+ { "0451", "20435_0308" },
+ { "0453", "20433_0301" },
+ { "0457", "20456_0308" },
+ { "045C", "2043A_0301" },
+ { "045D", "20438_0300" },
+ { "045E", "20443_0306" },
+ { "0476", "20474_030F" },
+ { "0477", "20475_030F" },
+ { "04C1", "20416_0306" },
+ { "04C2", "20436_0306" },
+ { "04D0", "20410_0306" },
+ { "04D1", "20430_0306" },
+ { "04D2", "20410_0308" },
+ { "04D3", "20430_0308" },
+ { "04D6", "20415_0306" },
+ { "04D7", "20435_0306" },
+ { "04DA", "204D8_0308" },
+ { "04DB", "204D9_0308" },
+ { "04DC", "20416_0308" },
+ { "04DD", "20436_0308" },
+ { "04DE", "20417_0308" },
+ { "04DF", "20437_0308" },
+ { "04E2", "20418_0304" },
+ { "04E3", "20438_0304" },
+ { "04E4", "20418_0308" },
+ { "04E5", "20438_0308" },
+ { "04E6", "2041E_0308" },
+ { "04E7", "2043E_0308" },
+ { "04EA", "204E8_0308" },
+ { "04EB", "204E9_0308" },
+ { "04EC", "2042D_0308" },
+ { "04ED", "2044D_0308" },
+ { "04EE", "20423_0304" },
+ { "04EF", "20443_0304" },
+ { "04F0", "20423_0308" },
+ { "04F1", "20443_0308" },
+ { "04F2", "20423_030B" },
+ { "04F3", "20443_030B" },
+ { "04F4", "20427_0308" },
+ { "04F5", "20447_0308" },
+ { "04F8", "2042B_0308" },
+ { "04F9", "2044B_0308" },
+ { "0622", "20627_0653" },
+ { "0623", "20627_0654" },
+ { "0624", "20648_0654" },
+ { "0625", "20627_0655" },
+ { "0626", "2064A_0654" },
+ { "06C0", "206D5_0654" },
+ { "06C2", "206C1_0654" },
+ { "06D3", "206D2_0654" },
+ { "0929", "20928_093C" },
+ { "0931", "20930_093C" },
+ { "0934", "20933_093C" },
+ { "0958", "20915_093C" },
+ { "0959", "20916_093C" },
+ { "095A", "20917_093C" },
+ { "095B", "2091C_093C" },
+ { "095C", "20921_093C" },
+ { "095D", "20922_093C" },
+ { "095E", "2092B_093C" },
+ { "095F", "2092F_093C" },
+ { "09CB", "209C7_09BE" },
+ { "09CC", "209C7_09D7" },
+ { "09DC", "209A1_09BC" },
+ { "09DD", "209A2_09BC" },
+ { "09DF", "209AF_09BC" },
+ { "0A33", "20A32_0A3C" },
+ { "0A36", "20A38_0A3C" },
+ { "0A59", "20A16_0A3C" },
+ { "0A5A", "20A17_0A3C" },
+ { "0A5B", "20A1C_0A3C" },
+ { "0A5E", "20A2B_0A3C" },
+ { "0B48", "20B47_0B56" },
+ { "0B4B", "20B47_0B3E" },
+ { "0B4C", "20B47_0B57" },
+ { "0B5C", "20B21_0B3C" },
+ { "0B5D", "20B22_0B3C" },
+ { "0B94", "20B92_0BD7" },
+ { "0BCA", "20BC6_0BBE" },
+ { "0BCB", "20BC7_0BBE" },
+ { "0BCC", "20BC6_0BD7" },
+ { "0C48", "20C46_0C56" },
+ { "0CC0", "20CBF_0CD5" },
+ { "0CC7", "20CC6_0CD5" },
+ { "0CC8", "20CC6_0CD6" },
+ { "0CCA", "20CC6_0CC2" },
+ { "0CCB", "30CC6_0CC2_0CD5" },
+ { "0D4A", "20D46_0D3E" },
+ { "0D4B", "20D47_0D3E" },
+ { "0D4C", "20D46_0D57" },
+ { "0DDA", "20DD9_0DCA" },
+ { "0DDC", "20DD9_0DCF" },
+ { "0DDD", "30DD9_0DCF_0DCA" },
+ { "0DDE", "20DD9_0DDF" },
+ { "0F43", "20F42_0FB7" },
+ { "0F4D", "20F4C_0FB7" },
+ { "0F52", "20F51_0FB7" },
+ { "0F57", "20F56_0FB7" },
+ { "0F5C", "20F5B_0FB7" },
+ { "0F69", "20F40_0FB5" },
+ { "0F73", "20F71_0F72" },
+ { "0F75", "20F71_0F74" },
+ { "0F76", "20FB2_0F80" },
+ { "0F78", "20FB3_0F80" },
+ { "0F81", "20F71_0F80" },
+ { "0F93", "20F92_0FB7" },
+ { "0F9D", "20F9C_0FB7" },
+ { "0FA2", "20FA1_0FB7" },
+ { "0FA7", "20FA6_0FB7" },
+ { "0FAC", "20FAB_0FB7" },
+ { "0FB9", "20F90_0FB5" },
+ { "1026", "21025_102E" },
+ { "1B06", "21B05_1B35" },
+ { "1B08", "21B07_1B35" },
+ { "1B0A", "21B09_1B35" },
+ { "1B0C", "21B0B_1B35" },
+ { "1B0E", "21B0D_1B35" },
+ { "1B12", "21B11_1B35" },
+ { "1B3B", "21B3A_1B35" },
+ { "1B3D", "21B3C_1B35" },
+ { "1B40", "21B3E_1B35" },
+ { "1B41", "21B3F_1B35" },
+ { "1B43", "21B42_1B35" },
+ { "1E00", "20041_0325" },
+ { "1E01", "20061_0325" },
+ { "1E02", "20042_0307" },
+ { "1E03", "20062_0307" },
+ { "1E04", "20042_0323" },
+ { "1E05", "20062_0323" },
+ { "1E06", "20042_0331" },
+ { "1E07", "20062_0331" },
+ { "1E08", "30043_0327_0301" },
+ { "1E09", "30063_0327_0301" },
+ { "1E0A", "20044_0307" },
+ { "1E0B", "20064_0307" },
+ { "1E0C", "20044_0323" },
+ { "1E0D", "20064_0323" },
+ { "1E0E", "20044_0331" },
+ { "1E0F", "20064_0331" },
+ { "1E10", "20044_0327" },
+ { "1E11", "20064_0327" },
+ { "1E12", "20044_032D" },
+ { "1E13", "20064_032D" },
+ { "1E14", "30045_0304_0300" },
+ { "1E15", "30065_0304_0300" },
+ { "1E16", "30045_0304_0301" },
+ { "1E17", "30065_0304_0301" },
+ { "1E18", "20045_032D" },
+ { "1E19", "20065_032D" },
+ { "1E1A", "20045_0330" },
+ { "1E1B", "20065_0330" },
+ { "1E1C", "30045_0327_0306" },
+ { "1E1D", "30065_0327_0306" },
+ { "1E1E", "20046_0307" },
+ { "1E1F", "20066_0307" },
+ { "1E20", "20047_0304" },
+ { "1E21", "20067_0304" },
+ { "1E22", "20048_0307" },
+ { "1E23", "20068_0307" },
+ { "1E24", "20048_0323" },
+ { "1E25", "20068_0323" },
+ { "1E26", "20048_0308" },
+ { "1E27", "20068_0308" },
+ { "1E28", "20048_0327" },
+ { "1E29", "20068_0327" },
+ { "1E2A", "20048_032E" },
+ { "1E2B", "20068_032E" },
+ { "1E2C", "20049_0330" },
+ { "1E2D", "20069_0330" },
+ { "1E2E", "30049_0308_0301" },
+ { "1E2F", "30069_0308_0301" },
+ { "1E30", "2004B_0301" },
+ { "1E31", "2006B_0301" },
+ { "1E32", "2004B_0323" },
+ { "1E33", "2006B_0323" },
+ { "1E34", "2004B_0331" },
+ { "1E35", "2006B_0331" },
+ { "1E36", "2004C_0323" },
+ { "1E37", "2006C_0323" },
+ { "1E38", "3004C_0323_0304" },
+ { "1E39", "3006C_0323_0304" },
+ { "1E3A", "2004C_0331" },
+ { "1E3B", "2006C_0331" },
+ { "1E3C", "2004C_032D" },
+ { "1E3D", "2006C_032D" },
+ { "1E3E", "2004D_0301" },
+ { "1E3F", "2006D_0301" },
+ { "1E40", "2004D_0307" },
+ { "1E41", "2006D_0307" },
+ { "1E42", "2004D_0323" },
+ { "1E43", "2006D_0323" },
+ { "1E44", "2004E_0307" },
+ { "1E45", "2006E_0307" },
+ { "1E46", "2004E_0323" },
+ { "1E47", "2006E_0323" },
+ { "1E48", "2004E_0331" },
+ { "1E49", "2006E_0331" },
+ { "1E4A", "2004E_032D" },
+ { "1E4B", "2006E_032D" },
+ { "1E4C", "3004F_0303_0301" },
+ { "1E4D", "3006F_0303_0301" },
+ { "1E4E", "3004F_0303_0308" },
+ { "1E4F", "3006F_0303_0308" },
+ { "1E50", "3004F_0304_0300" },
+ { "1E51", "3006F_0304_0300" },
+ { "1E52", "3004F_0304_0301" },
+ { "1E53", "3006F_0304_0301" },
+ { "1E54", "20050_0301" },
+ { "1E55", "20070_0301" },
+ { "1E56", "20050_0307" },
+ { "1E57", "20070_0307" },
+ { "1E58", "20052_0307" },
+ { "1E59", "20072_0307" },
+ { "1E5A", "20052_0323" },
+ { "1E5B", "20072_0323" },
+ { "1E5C", "30052_0323_0304" },
+ { "1E5D", "30072_0323_0304" },
+ { "1E5E", "20052_0331" },
+ { "1E5F", "20072_0331" },
+ { "1E60", "20053_0307" },
+ { "1E61", "20073_0307" },
+ { "1E62", "20053_0323" },
+ { "1E63", "20073_0323" },
+ { "1E64", "30053_0301_0307" },
+ { "1E65", "30073_0301_0307" },
+ { "1E66", "30053_030C_0307" },
+ { "1E67", "30073_030C_0307" },
+ { "1E68", "30053_0323_0307" },
+ { "1E69", "30073_0323_0307" },
+ { "1E6A", "20054_0307" },
+ { "1E6B", "20074_0307" },
+ { "1E6C", "20054_0323" },
+ { "1E6D", "20074_0323" },
+ { "1E6E", "20054_0331" },
+ { "1E6F", "20074_0331" },
+ { "1E70", "20054_032D" },
+ { "1E71", "20074_032D" },
+ { "1E72", "20055_0324" },
+ { "1E73", "20075_0324" },
+ { "1E74", "20055_0330" },
+ { "1E75", "20075_0330" },
+ { "1E76", "20055_032D" },
+ { "1E77", "20075_032D" },
+ { "1E78", "30055_0303_0301" },
+ { "1E79", "30075_0303_0301" },
+ { "1E7A", "30055_0304_0308" },
+ { "1E7B", "30075_0304_0308" },
+ { "1E7C", "20056_0303" },
+ { "1E7D", "20076_0303" },
+ { "1E7E", "20056_0323" },
+ { "1E7F", "20076_0323" },
+ { "1E80", "20057_0300" },
+ { "1E81", "20077_0300" },
+ { "1E82", "20057_0301" },
+ { "1E83", "20077_0301" },
+ { "1E84", "20057_0308" },
+ { "1E85", "20077_0308" },
+ { "1E86", "20057_0307" },
+ { "1E87", "20077_0307" },
+ { "1E88", "20057_0323" },
+ { "1E89", "20077_0323" },
+ { "1E8A", "20058_0307" },
+ { "1E8B", "20078_0307" },
+ { "1E8C", "20058_0308" },
+ { "1E8D", "20078_0308" },
+ { "1E8E", "20059_0307" },
+ { "1E8F", "20079_0307" },
+ { "1E90", "2005A_0302" },
+ { "1E91", "2007A_0302" },
+ { "1E92", "2005A_0323" },
+ { "1E93", "2007A_0323" },
+ { "1E94", "2005A_0331" },
+ { "1E95", "2007A_0331" },
+ { "1E96", "20068_0331" },
+ { "1E97", "20074_0308" },
+ { "1E98", "20077_030A" },
+ { "1E99", "20079_030A" },
+ { "1E9B", "2017F_0307" },
+ { "1EA0", "20041_0323" },
+ { "1EA1", "20061_0323" },
+ { "1EA2", "20041_0309" },
+ { "1EA3", "20061_0309" },
+ { "1EA4", "30041_0302_0301" },
+ { "1EA5", "30061_0302_0301" },
+ { "1EA6", "30041_0302_0300" },
+ { "1EA7", "30061_0302_0300" },
+ { "1EA8", "30041_0302_0309" },
+ { "1EA9", "30061_0302_0309" },
+ { "1EAA", "30041_0302_0303" },
+ { "1EAB", "30061_0302_0303" },
+ { "1EAC", "30041_0323_0302" },
+ { "1EAD", "30061_0323_0302" },
+ { "1EAE", "30041_0306_0301" },
+ { "1EAF", "30061_0306_0301" },
+ { "1EB0", "30041_0306_0300" },
+ { "1EB1", "30061_0306_0300" },
+ { "1EB2", "30041_0306_0309" },
+ { "1EB3", "30061_0306_0309" },
+ { "1EB4", "30041_0306_0303" },
+ { "1EB5", "30061_0306_0303" },
+ { "1EB6", "30041_0323_0306" },
+ { "1EB7", "30061_0323_0306" },
+ { "1EB8", "20045_0323" },
+ { "1EB9", "20065_0323" },
+ { "1EBA", "20045_0309" },
+ { "1EBB", "20065_0309" },
+ { "1EBC", "20045_0303" },
+ { "1EBD", "20065_0303" },
+ { "1EBE", "30045_0302_0301" },
+ { "1EBF", "30065_0302_0301" },
+ { "1EC0", "30045_0302_0300" },
+ { "1EC1", "30065_0302_0300" },
+ { "1EC2", "30045_0302_0309" },
+ { "1EC3", "30065_0302_0309" },
+ { "1EC4", "30045_0302_0303" },
+ { "1EC5", "30065_0302_0303" },
+ { "1EC6", "30045_0323_0302" },
+ { "1EC7", "30065_0323_0302" },
+ { "1EC8", "20049_0309" },
+ { "1EC9", "20069_0309" },
+ { "1ECA", "20049_0323" },
+ { "1ECB", "20069_0323" },
+ { "1ECC", "2004F_0323" },
+ { "1ECD", "2006F_0323" },
+ { "1ECE", "2004F_0309" },
+ { "1ECF", "2006F_0309" },
+ { "1ED0", "3004F_0302_0301" },
+ { "1ED1", "3006F_0302_0301" },
+ { "1ED2", "3004F_0302_0300" },
+ { "1ED3", "3006F_0302_0300" },
+ { "1ED4", "3004F_0302_0309" },
+ { "1ED5", "3006F_0302_0309" },
+ { "1ED6", "3004F_0302_0303" },
+ { "1ED7", "3006F_0302_0303" },
+ { "1ED8", "3004F_0323_0302" },
+ { "1ED9", "3006F_0323_0302" },
+ { "1EDA", "3004F_031B_0301" },
+ { "1EDB", "3006F_031B_0301" },
+ { "1EDC", "3004F_031B_0300" },
+ { "1EDD", "3006F_031B_0300" },
+ { "1EDE", "3004F_031B_0309" },
+ { "1EDF", "3006F_031B_0309" },
+ { "1EE0", "3004F_031B_0303" },
+ { "1EE1", "3006F_031B_0303" },
+ { "1EE2", "3004F_031B_0323" },
+ { "1EE3", "3006F_031B_0323" },
+ { "1EE4", "20055_0323" },
+ { "1EE5", "20075_0323" },
+ { "1EE6", "20055_0309" },
+ { "1EE7", "20075_0309" },
+ { "1EE8", "30055_031B_0301" },
+ { "1EE9", "30075_031B_0301" },
+ { "1EEA", "30055_031B_0300" },
+ { "1EEB", "30075_031B_0300" },
+ { "1EEC", "30055_031B_0309" },
+ { "1EED", "30075_031B_0309" },
+ { "1EEE", "30055_031B_0303" },
+ { "1EEF", "30075_031B_0303" },
+ { "1EF0", "30055_031B_0323" },
+ { "1EF1", "30075_031B_0323" },
+ { "1EF2", "20059_0300" },
+ { "1EF3", "20079_0300" },
+ { "1EF4", "20059_0323" },
+ { "1EF5", "20079_0323" },
+ { "1EF6", "20059_0309" },
+ { "1EF7", "20079_0309" },
+ { "1EF8", "20059_0303" },
+ { "1EF9", "20079_0303" },
+ { "1F00", "203B1_0313" },
+ { "1F01", "203B1_0314" },
+ { "1F02", "303B1_0313_0300" },
+ { "1F03", "303B1_0314_0300" },
+ { "1F04", "303B1_0313_0301" },
+ { "1F05", "303B1_0314_0301" },
+ { "1F06", "303B1_0313_0342" },
+ { "1F07", "303B1_0314_0342" },
+ { "1F08", "20391_0313" },
+ { "1F09", "20391_0314" },
+ { "1F0A", "30391_0313_0300" },
+ { "1F0B", "30391_0314_0300" },
+ { "1F0C", "30391_0313_0301" },
+ { "1F0D", "30391_0314_0301" },
+ { "1F0E", "30391_0313_0342" },
+ { "1F0F", "30391_0314_0342" },
+ { "1F10", "203B5_0313" },
+ { "1F11", "203B5_0314" },
+ { "1F12", "303B5_0313_0300" },
+ { "1F13", "303B5_0314_0300" },
+ { "1F14", "303B5_0313_0301" },
+ { "1F15", "303B5_0314_0301" },
+ { "1F18", "20395_0313" },
+ { "1F19", "20395_0314" },
+ { "1F1A", "30395_0313_0300" },
+ { "1F1B", "30395_0314_0300" },
+ { "1F1C", "30395_0313_0301" },
+ { "1F1D", "30395_0314_0301" },
+ { "1F20", "203B7_0313" },
+ { "1F21", "203B7_0314" },
+ { "1F22", "303B7_0313_0300" },
+ { "1F23", "303B7_0314_0300" },
+ { "1F24", "303B7_0313_0301" },
+ { "1F25", "303B7_0314_0301" },
+ { "1F26", "303B7_0313_0342" },
+ { "1F27", "303B7_0314_0342" },
+ { "1F28", "20397_0313" },
+ { "1F29", "20397_0314" },
+ { "1F2A", "30397_0313_0300" },
+ { "1F2B", "30397_0314_0300" },
+ { "1F2C", "30397_0313_0301" },
+ { "1F2D", "30397_0314_0301" },
+ { "1F2E", "30397_0313_0342" },
+ { "1F2F", "30397_0314_0342" },
+ { "1F30", "203B9_0313" },
+ { "1F31", "203B9_0314" },
+ { "1F32", "303B9_0313_0300" },
+ { "1F33", "303B9_0314_0300" },
+ { "1F34", "303B9_0313_0301" },
+ { "1F35", "303B9_0314_0301" },
+ { "1F36", "303B9_0313_0342" },
+ { "1F37", "303B9_0314_0342" },
+ { "1F38", "20399_0313" },
+ { "1F39", "20399_0314" },
+ { "1F3A", "30399_0313_0300" },
+ { "1F3B", "30399_0314_0300" },
+ { "1F3C", "30399_0313_0301" },
+ { "1F3D", "30399_0314_0301" },
+ { "1F3E", "30399_0313_0342" },
+ { "1F3F", "30399_0314_0342" },
+ { "1F40", "203BF_0313" },
+ { "1F41", "203BF_0314" },
+ { "1F42", "303BF_0313_0300" },
+ { "1F43", "303BF_0314_0300" },
+ { "1F44", "303BF_0313_0301" },
+ { "1F45", "303BF_0314_0301" },
+ { "1F48", "2039F_0313" },
+ { "1F49", "2039F_0314" },
+ { "1F4A", "3039F_0313_0300" },
+ { "1F4B", "3039F_0314_0300" },
+ { "1F4C", "3039F_0313_0301" },
+ { "1F4D", "3039F_0314_0301" },
+ { "1F50", "203C5_0313" },
+ { "1F51", "203C5_0314" },
+ { "1F52", "303C5_0313_0300" },
+ { "1F53", "303C5_0314_0300" },
+ { "1F54", "303C5_0313_0301" },
+ { "1F55", "303C5_0314_0301" },
+ { "1F56", "303C5_0313_0342" },
+ { "1F57", "303C5_0314_0342" },
+ { "1F59", "203A5_0314" },
+ { "1F5B", "303A5_0314_0300" },
+ { "1F5D", "303A5_0314_0301" },
+ { "1F5F", "303A5_0314_0342" },
+ { "1F60", "203C9_0313" },
+ { "1F61", "203C9_0314" },
+ { "1F62", "303C9_0313_0300" },
+ { "1F63", "303C9_0314_0300" },
+ { "1F64", "303C9_0313_0301" },
+ { "1F65", "303C9_0314_0301" },
+ { "1F66", "303C9_0313_0342" },
+ { "1F67", "303C9_0314_0342" },
+ { "1F68", "203A9_0313" },
+ { "1F69", "203A9_0314" },
+ { "1F6A", "303A9_0313_0300" },
+ { "1F6B", "303A9_0314_0300" },
+ { "1F6C", "303A9_0313_0301" },
+ { "1F6D", "303A9_0314_0301" },
+ { "1F6E", "303A9_0313_0342" },
+ { "1F6F", "303A9_0314_0342" },
+ { "1F70", "203B1_0300" },
+ { "1F71", "203B1_0301" },
+ { "1F72", "203B5_0300" },
+ { "1F73", "203B5_0301" },
+ { "1F74", "203B7_0300" },
+ { "1F75", "203B7_0301" },
+ { "1F76", "203B9_0300" },
+ { "1F77", "203B9_0301" },
+ { "1F78", "203BF_0300" },
+ { "1F79", "203BF_0301" },
+ { "1F7A", "203C5_0300" },
+ { "1F7B", "203C5_0301" },
+ { "1F7C", "203C9_0300" },
+ { "1F7D", "203C9_0301" },
+ { "1F80", "303B1_0313_0345" },
+ { "1F81", "303B1_0314_0345" },
+ { "1F82", "403B1_0313_0300_0345" },
+ { "1F83", "403B1_0314_0300_0345" },
+ { "1F84", "403B1_0313_0301_0345" },
+ { "1F85", "403B1_0314_0301_0345" },
+ { "1F86", "403B1_0313_0342_0345" },
+ { "1F87", "403B1_0314_0342_0345" },
+ { "1F88", "30391_0313_0345" },
+ { "1F89", "30391_0314_0345" },
+ { "1F8A", "40391_0313_0300_0345" },
+ { "1F8B", "40391_0314_0300_0345" },
+ { "1F8C", "40391_0313_0301_0345" },
+ { "1F8D", "40391_0314_0301_0345" },
+ { "1F8E", "40391_0313_0342_0345" },
+ { "1F8F", "40391_0314_0342_0345" },
+ { "1F90", "303B7_0313_0345" },
+ { "1F91", "303B7_0314_0345" },
+ { "1F92", "403B7_0313_0300_0345" },
+ { "1F93", "403B7_0314_0300_0345" },
+ { "1F94", "403B7_0313_0301_0345" },
+ { "1F95", "403B7_0314_0301_0345" },
+ { "1F96", "403B7_0313_0342_0345" },
+ { "1F97", "403B7_0314_0342_0345" },
+ { "1F98", "30397_0313_0345" },
+ { "1F99", "30397_0314_0345" },
+ { "1F9A", "40397_0313_0300_0345" },
+ { "1F9B", "40397_0314_0300_0345" },
+ { "1F9C", "40397_0313_0301_0345" },
+ { "1F9D", "40397_0314_0301_0345" },
+ { "1F9E", "40397_0313_0342_0345" },
+ { "1F9F", "40397_0314_0342_0345" },
+ { "1FA0", "303C9_0313_0345" },
+ { "1FA1", "303C9_0314_0345" },
+ { "1FA2", "403C9_0313_0300_0345" },
+ { "1FA3", "403C9_0314_0300_0345" },
+ { "1FA4", "403C9_0313_0301_0345" },
+ { "1FA5", "403C9_0314_0301_0345" },
+ { "1FA6", "403C9_0313_0342_0345" },
+ { "1FA7", "403C9_0314_0342_0345" },
+ { "1FA8", "303A9_0313_0345" },
+ { "1FA9", "303A9_0314_0345" },
+ { "1FAA", "403A9_0313_0300_0345" },
+ { "1FAB", "403A9_0314_0300_0345" },
+ { "1FAC", "403A9_0313_0301_0345" },
+ { "1FAD", "403A9_0314_0301_0345" },
+ { "1FAE", "403A9_0313_0342_0345" },
+ { "1FAF", "403A9_0314_0342_0345" },
+ { "1FB0", "203B1_0306" },
+ { "1FB1", "203B1_0304" },
+ { "1FB2", "303B1_0300_0345" },
+ { "1FB3", "203B1_0345" },
+ { "1FB4", "303B1_0301_0345" },
+ { "1FB6", "203B1_0342" },
+ { "1FB7", "303B1_0342_0345" },
+ { "1FB8", "20391_0306" },
+ { "1FB9", "20391_0304" },
+ { "1FBA", "20391_0300" },
+ { "1FBB", "20391_0301" },
+ { "1FBC", "20391_0345" },
+ { "1FBE", "103B9" },
+ { "1FC1", "200A8_0342" },
+ { "1FC2", "303B7_0300_0345" },
+ { "1FC3", "203B7_0345" },
+ { "1FC4", "303B7_0301_0345" },
+ { "1FC6", "203B7_0342" },
+ { "1FC7", "303B7_0342_0345" },
+ { "1FC8", "20395_0300" },
+ { "1FC9", "20395_0301" },
+ { "1FCA", "20397_0300" },
+ { "1FCB", "20397_0301" },
+ { "1FCC", "20397_0345" },
+ { "1FCD", "21FBF_0300" },
+ { "1FCE", "21FBF_0301" },
+ { "1FCF", "21FBF_0342" },
+ { "1FD0", "203B9_0306" },
+ { "1FD1", "203B9_0304" },
+ { "1FD2", "303B9_0308_0300" },
+ { "1FD3", "303B9_0308_0301" },
+ { "1FD6", "203B9_0342" },
+ { "1FD7", "303B9_0308_0342" },
+ { "1FD8", "20399_0306" },
+ { "1FD9", "20399_0304" },
+ { "1FDA", "20399_0300" },
+ { "1FDB", "20399_0301" },
+ { "1FDD", "21FFE_0300" },
+ { "1FDE", "21FFE_0301" },
+ { "1FDF", "21FFE_0342" },
+ { "1FE0", "203C5_0306" },
+ { "1FE1", "203C5_0304" },
+ { "1FE2", "303C5_0308_0300" },
+ { "1FE3", "303C5_0308_0301" },
+ { "1FE4", "203C1_0313" },
+ { "1FE5", "203C1_0314" },
+ { "1FE6", "203C5_0342" },
+ { "1FE7", "303C5_0308_0342" },
+ { "1FE8", "203A5_0306" },
+ { "1FE9", "203A5_0304" },
+ { "1FEA", "203A5_0300" },
+ { "1FEB", "203A5_0301" },
+ { "1FEC", "203A1_0314" },
+ { "1FED", "200A8_0300" },
+ { "1FEE", "200A8_0301" },
+ { "1FEF", "10060" },
+ { "1FF2", "303C9_0300_0345" },
+ { "1FF3", "203C9_0345" },
+ { "1FF4", "303C9_0301_0345" },
+ { "1FF6", "203C9_0342" },
+ { "1FF7", "303C9_0342_0345" },
+ { "1FF8", "2039F_0300" },
+ { "1FF9", "2039F_0301" },
+ { "1FFA", "203A9_0300" },
+ { "1FFB", "203A9_0301" },
+ { "1FFC", "203A9_0345" },
+ { "1FFD", "100B4" },
+ { "2000", "12002" },
+ { "2001", "12003" },
+ { "2126", "103A9" },
+ { "212A", "1004B" },
+ { "212B", "20041_030A" },
+ { "219A", "22190_0338" },
+ { "219B", "22192_0338" },
+ { "21AE", "22194_0338" },
+ { "21CD", "221D0_0338" },
+ { "21CE", "221D4_0338" },
+ { "21CF", "221D2_0338" },
+ { "2204", "22203_0338" },
+ { "2209", "22208_0338" },
+ { "220C", "2220B_0338" },
+ { "2224", "22223_0338" },
+ { "2226", "22225_0338" },
+ { "2241", "2223C_0338" },
+ { "2244", "22243_0338" },
+ { "2247", "22245_0338" },
+ { "2249", "22248_0338" },
+ { "2260", "2003D_0338" },
+ { "2262", "22261_0338" },
+ { "226D", "2224D_0338" },
+ { "226E", "2003C_0338" },
+ { "226F", "2003E_0338" },
+ { "2270", "22264_0338" },
+ { "2271", "22265_0338" },
+ { "2274", "22272_0338" },
+ { "2275", "22273_0338" },
+ { "2278", "22276_0338" },
+ { "2279", "22277_0338" },
+ { "2280", "2227A_0338" },
+ { "2281", "2227B_0338" },
+ { "2284", "22282_0338" },
+ { "2285", "22283_0338" },
+ { "2288", "22286_0338" },
+ { "2289", "22287_0338" },
+ { "22AC", "222A2_0338" },
+ { "22AD", "222A8_0338" },
+ { "22AE", "222A9_0338" },
+ { "22AF", "222AB_0338" },
+ { "22E0", "2227C_0338" },
+ { "22E1", "2227D_0338" },
+ { "22E2", "22291_0338" },
+ { "22E3", "22292_0338" },
+ { "22EA", "222B2_0338" },
+ { "22EB", "222B3_0338" },
+ { "22EC", "222B4_0338" },
+ { "22ED", "222B5_0338" },
+ { "2329", "13008" },
+ { "232A", "13009" },
+ { "2ADC", "22ADD_0338" },
+ { "304C", "2304B_3099" },
+ { "304E", "2304D_3099" },
+ { "3050", "2304F_3099" },
+ { "3052", "23051_3099" },
+ { "3054", "23053_3099" },
+ { "3056", "23055_3099" },
+ { "3058", "23057_3099" },
+ { "305A", "23059_3099" },
+ { "305C", "2305B_3099" },
+ { "305E", "2305D_3099" },
+ { "3060", "2305F_3099" },
+ { "3062", "23061_3099" },
+ { "3065", "23064_3099" },
+ { "3067", "23066_3099" },
+ { "3069", "23068_3099" },
+ { "3070", "2306F_3099" },
+ { "3071", "2306F_309A" },
+ { "3073", "23072_3099" },
+ { "3074", "23072_309A" },
+ { "3076", "23075_3099" },
+ { "3077", "23075_309A" },
+ { "3079", "23078_3099" },
+ { "307A", "23078_309A" },
+ { "307C", "2307B_3099" },
+ { "307D", "2307B_309A" },
+ { "3094", "23046_3099" },
+ { "309E", "2309D_3099" },
+ { "30AC", "230AB_3099" },
+ { "30AE", "230AD_3099" },
+ { "30B0", "230AF_3099" },
+ { "30B2", "230B1_3099" },
+ { "30B4", "230B3_3099" },
+ { "30B6", "230B5_3099" },
+ { "30B8", "230B7_3099" },
+ { "30BA", "230B9_3099" },
+ { "30BC", "230BB_3099" },
+ { "30BE", "230BD_3099" },
+ { "30C0", "230BF_3099" },
+ { "30C2", "230C1_3099" },
+ { "30C5", "230C4_3099" },
+ { "30C7", "230C6_3099" },
+ { "30C9", "230C8_3099" },
+ { "30D0", "230CF_3099" },
+ { "30D1", "230CF_309A" },
+ { "30D3", "230D2_3099" },
+ { "30D4", "230D2_309A" },
+ { "30D6", "230D5_3099" },
+ { "30D7", "230D5_309A" },
+ { "30D9", "230D8_3099" },
+ { "30DA", "230D8_309A" },
+ { "30DC", "230DB_3099" },
+ { "30DD", "230DB_309A" },
+ { "30F4", "230A6_3099" },
+ { "30F7", "230EF_3099" },
+ { "30F8", "230F0_3099" },
+ { "30F9", "230F1_3099" },
+ { "30FA", "230F2_3099" },
+ { "30FE", "230FD_3099" },
+ { "F900", "18C48" },
+ { "F901", "166F4" },
+ { "F902", "18ECA" },
+ { "F903", "18CC8" },
+ { "F904", "16ED1" },
+ { "F905", "14E32" },
+ { "F906", "153E5" },
+ { "F907", "19F9C" },
+ { "F908", "19F9C" },
+ { "F909", "15951" },
+ { "F90A", "191D1" },
+ { "F90B", "15587" },
+ { "F90C", "15948" },
+ { "F90D", "161F6" },
+ { "F90E", "17669" },
+ { "F90F", "17F85" },
+ { "F910", "1863F" },
+ { "F911", "187BA" },
+ { "F912", "188F8" },
+ { "F913", "1908F" },
+ { "F914", "16A02" },
+ { "F915", "16D1B" },
+ { "F916", "170D9" },
+ { "F917", "173DE" },
+ { "F918", "1843D" },
+ { "F919", "1916A" },
+ { "F91A", "199F1" },
+ { "F91B", "14E82" },
+ { "F91C", "15375" },
+ { "F91D", "16B04" },
+ { "F91E", "1721B" },
+ { "F91F", "1862D" },
+ { "F920", "19E1E" },
+ { "F921", "15D50" },
+ { "F922", "16FEB" },
+ { "F923", "185CD" },
+ { "F924", "18964" },
+ { "F925", "162C9" },
+ { "F926", "181D8" },
+ { "F927", "1881F" },
+ { "F928", "15ECA" },
+ { "F929", "16717" },
+ { "F92A", "16D6A" },
+ { "F92B", "172FC" },
+ { "F92C", "190CE" },
+ { "F92D", "14F86" },
+ { "F92E", "151B7" },
+ { "F92F", "152DE" },
+ { "F930", "164C4" },
+ { "F931", "16AD3" },
+ { "F932", "17210" },
+ { "F933", "176E7" },
+ { "F934", "18001" },
+ { "F935", "18606" },
+ { "F936", "1865C" },
+ { "F937", "18DEF" },
+ { "F938", "19732" },
+ { "F939", "19B6F" },
+ { "F93A", "19DFA" },
+ { "F93B", "1788C" },
+ { "F93C", "1797F" },
+ { "F93D", "17DA0" },
+ { "F93E", "183C9" },
+ { "F93F", "19304" },
+ { "F940", "19E7F" },
+ { "F941", "18AD6" },
+ { "F942", "158DF" },
+ { "F943", "15F04" },
+ { "F944", "17C60" },
+ { "F945", "1807E" },
+ { "F946", "17262" },
+ { "F947", "178CA" },
+ { "F948", "18CC2" },
+ { "F949", "196F7" },
+ { "F94A", "158D8" },
+ { "F94B", "15C62" },
+ { "F94C", "16A13" },
+ { "F94D", "16DDA" },
+ { "F94E", "16F0F" },
+ { "F94F", "17D2F" },
+ { "F950", "17E37" },
+ { "F951", "1964B" },
+ { "F952", "152D2" },
+ { "F953", "1808B" },
+ { "F954", "151DC" },
+ { "F955", "151CC" },
+ { "F956", "17A1C" },
+ { "F957", "17DBE" },
+ { "F958", "183F1" },
+ { "F959", "19675" },
+ { "F95A", "18B80" },
+ { "F95B", "162CF" },
+ { "F95C", "16A02" },
+ { "F95D", "18AFE" },
+ { "F95E", "14E39" },
+ { "F95F", "15BE7" },
+ { "F960", "16012" },
+ { "F961", "17387" },
+ { "F962", "17570" },
+ { "F963", "15317" },
+ { "F964", "178FB" },
+ { "F965", "14FBF" },
+ { "F966", "15FA9" },
+ { "F967", "14E0D" },
+ { "F968", "16CCC" },
+ { "F969", "16578" },
+ { "F96A", "17D22" },
+ { "F96B", "153C3" },
+ { "F96C", "1585E" },
+ { "F96D", "17701" },
+ { "F96E", "18449" },
+ { "F96F", "18AAA" },
+ { "F970", "16BBA" },
+ { "F971", "18FB0" },
+ { "F972", "16C88" },
+ { "F973", "162FE" },
+ { "F974", "182E5" },
+ { "F975", "163A0" },
+ { "F976", "17565" },
+ { "F977", "14EAE" },
+ { "F978", "15169" },
+ { "F979", "151C9" },
+ { "F97A", "16881" },
+ { "F97B", "17CE7" },
+ { "F97C", "1826F" },
+ { "F97D", "18AD2" },
+ { "F97E", "191CF" },
+ { "F97F", "152F5" },
+ { "F980", "15442" },
+ { "F981", "15973" },
+ { "F982", "15EEC" },
+ { "F983", "165C5" },
+ { "F984", "16FFE" },
+ { "F985", "1792A" },
+ { "F986", "195AD" },
+ { "F987", "19A6A" },
+ { "F988", "19E97" },
+ { "F989", "19ECE" },
+ { "F98A", "1529B" },
+ { "F98B", "166C6" },
+ { "F98C", "16B77" },
+ { "F98D", "18F62" },
+ { "F98E", "15E74" },
+ { "F98F", "16190" },
+ { "F990", "16200" },
+ { "F991", "1649A" },
+ { "F992", "16F23" },
+ { "F993", "17149" },
+ { "F994", "17489" },
+ { "F995", "179CA" },
+ { "F996", "17DF4" },
+ { "F997", "1806F" },
+ { "F998", "18F26" },
+ { "F999", "184EE" },
+ { "F99A", "19023" },
+ { "F99B", "1934A" },
+ { "F99C", "15217" },
+ { "F99D", "152A3" },
+ { "F99E", "154BD" },
+ { "F99F", "170C8" },
+ { "F9A0", "188C2" },
+ { "F9A1", "18AAA" },
+ { "F9A2", "15EC9" },
+ { "F9A3", "15FF5" },
+ { "F9A4", "1637B" },
+ { "F9A5", "16BAE" },
+ { "F9A6", "17C3E" },
+ { "F9A7", "17375" },
+ { "F9A8", "14EE4" },
+ { "F9A9", "156F9" },
+ { "F9AA", "15BE7" },
+ { "F9AB", "15DBA" },
+ { "F9AC", "1601C" },
+ { "F9AD", "173B2" },
+ { "F9AE", "17469" },
+ { "F9AF", "17F9A" },
+ { "F9B0", "18046" },
+ { "F9B1", "19234" },
+ { "F9B2", "196F6" },
+ { "F9B3", "19748" },
+ { "F9B4", "19818" },
+ { "F9B5", "14F8B" },
+ { "F9B6", "179AE" },
+ { "F9B7", "191B4" },
+ { "F9B8", "196B8" },
+ { "F9B9", "160E1" },
+ { "F9BA", "14E86" },
+ { "F9BB", "150DA" },
+ { "F9BC", "15BEE" },
+ { "F9BD", "15C3F" },
+ { "F9BE", "16599" },
+ { "F9BF", "16A02" },
+ { "F9C0", "171CE" },
+ { "F9C1", "17642" },
+ { "F9C2", "184FC" },
+ { "F9C3", "1907C" },
+ { "F9C4", "19F8D" },
+ { "F9C5", "16688" },
+ { "F9C6", "1962E" },
+ { "F9C7", "15289" },
+ { "F9C8", "1677B" },
+ { "F9C9", "167F3" },
+ { "F9CA", "16D41" },
+ { "F9CB", "16E9C" },
+ { "F9CC", "17409" },
+ { "F9CD", "17559" },
+ { "F9CE", "1786B" },
+ { "F9CF", "17D10" },
+ { "F9D0", "1985E" },
+ { "F9D1", "1516D" },
+ { "F9D2", "1622E" },
+ { "F9D3", "19678" },
+ { "F9D4", "1502B" },
+ { "F9D5", "15D19" },
+ { "F9D6", "16DEA" },
+ { "F9D7", "18F2A" },
+ { "F9D8", "15F8B" },
+ { "F9D9", "16144" },
+ { "F9DA", "16817" },
+ { "F9DB", "17387" },
+ { "F9DC", "19686" },
+ { "F9DD", "15229" },
+ { "F9DE", "1540F" },
+ { "F9DF", "15C65" },
+ { "F9E0", "16613" },
+ { "F9E1", "1674E" },
+ { "F9E2", "168A8" },
+ { "F9E3", "16CE5" },
+ { "F9E4", "17406" },
+ { "F9E5", "175E2" },
+ { "F9E6", "17F79" },
+ { "F9E7", "188CF" },
+ { "F9E8", "188E1" },
+ { "F9E9", "191CC" },
+ { "F9EA", "196E2" },
+ { "F9EB", "1533F" },
+ { "F9EC", "16EBA" },
+ { "F9ED", "1541D" },
+ { "F9EE", "171D0" },
+ { "F9EF", "17498" },
+ { "F9F0", "185FA" },
+ { "F9F1", "196A3" },
+ { "F9F2", "19C57" },
+ { "F9F3", "19E9F" },
+ { "F9F4", "16797" },
+ { "F9F5", "16DCB" },
+ { "F9F6", "181E8" },
+ { "F9F7", "17ACB" },
+ { "F9F8", "17B20" },
+ { "F9F9", "17C92" },
+ { "F9FA", "172C0" },
+ { "F9FB", "17099" },
+ { "F9FC", "18B58" },
+ { "F9FD", "14EC0" },
+ { "F9FE", "18336" },
+ { "F9FF", "1523A" },
+ { "FA00", "15207" },
+ { "FA01", "15EA6" },
+ { "FA02", "162D3" },
+ { "FA03", "17CD6" },
+ { "FA04", "15B85" },
+ { "FA05", "16D1E" },
+ { "FA06", "166B4" },
+ { "FA07", "18F3B" },
+ { "FA08", "1884C" },
+ { "FA09", "1964D" },
+ { "FA0A", "1898B" },
+ { "FA0B", "15ED3" },
+ { "FA0C", "15140" },
+ { "FA0D", "155C0" },
+ { "FA10", "1585A" },
+ { "FA12", "16674" },
+ { "FA15", "151DE" },
+ { "FA16", "1732A" },
+ { "FA17", "176CA" },
+ { "FA18", "1793C" },
+ { "FA19", "1795E" },
+ { "FA1A", "17965" },
+ { "FA1B", "1798F" },
+ { "FA1C", "19756" },
+ { "FA1D", "17CBE" },
+ { "FA1E", "17FBD" },
+ { "FA20", "18612" },
+ { "FA22", "18AF8" },
+ { "FA25", "19038" },
+ { "FA26", "190FD" },
+ { "FA2A", "198EF" },
+ { "FA2B", "198FC" },
+ { "FA2C", "19928" },
+ { "FA2D", "19DB4" },
+ { "FA2E", "190DE" },
+ { "FA2F", "196B7" },
+ { "FA30", "14FAE" },
+ { "FA31", "150E7" },
+ { "FA32", "1514D" },
+ { "FA33", "152C9" },
+ { "FA34", "152E4" },
+ { "FA35", "15351" },
+ { "FA36", "1559D" },
+ { "FA37", "15606" },
+ { "FA38", "15668" },
+ { "FA39", "15840" },
+ { "FA3A", "158A8" },
+ { "FA3B", "15C64" },
+ { "FA3C", "15C6E" },
+ { "FA3D", "16094" },
+ { "FA3E", "16168" },
+ { "FA3F", "1618E" },
+ { "FA40", "161F2" },
+ { "FA41", "1654F" },
+ { "FA42", "165E2" },
+ { "FA43", "16691" },
+ { "FA44", "16885" },
+ { "FA45", "16D77" },
+ { "FA46", "16E1A" },
+ { "FA47", "16F22" },
+ { "FA48", "1716E" },
+ { "FA49", "1722B" },
+ { "FA4A", "17422" },
+ { "FA4B", "17891" },
+ { "FA4C", "1793E" },
+ { "FA4D", "17949" },
+ { "FA4E", "17948" },
+ { "FA4F", "17950" },
+ { "FA50", "17956" },
+ { "FA51", "1795D" },
+ { "FA52", "1798D" },
+ { "FA53", "1798E" },
+ { "FA54", "17A40" },
+ { "FA55", "17A81" },
+ { "FA56", "17BC0" },
+ { "FA57", "17DF4" },
+ { "FA58", "17E09" },
+ { "FA59", "17E41" },
+ { "FA5A", "17F72" },
+ { "FA5B", "18005" },
+ { "FA5C", "181ED" },
+ { "FA5D", "18279" },
+ { "FA5E", "18279" },
+ { "FA5F", "18457" },
+ { "FA60", "18910" },
+ { "FA61", "18996" },
+ { "FA62", "18B01" },
+ { "FA63", "18B39" },
+ { "FA64", "18CD3" },
+ { "FA65", "18D08" },
+ { "FA66", "18FB6" },
+ { "FA67", "19038" },
+ { "FA68", "196E3" },
+ { "FA69", "197FF" },
+ { "FA6A", "1983B" },
+ { "FA6B", "16075" },
+ { "FA6C", "1242EE" },
+ { "FA6D", "18218" },
+ { "FA70", "14E26" },
+ { "FA71", "151B5" },
+ { "FA72", "15168" },
+ { "FA73", "14F80" },
+ { "FA74", "15145" },
+ { "FA75", "15180" },
+ { "FA76", "152C7" },
+ { "FA77", "152FA" },
+ { "FA78", "1559D" },
+ { "FA79", "15555" },
+ { "FA7A", "15599" },
+ { "FA7B", "155E2" },
+ { "FA7C", "1585A" },
+ { "FA7D", "158B3" },
+ { "FA7E", "15944" },
+ { "FA7F", "15954" },
+ { "FA80", "15A62" },
+ { "FA81", "15B28" },
+ { "FA82", "15ED2" },
+ { "FA83", "15ED9" },
+ { "FA84", "15F69" },
+ { "FA85", "15FAD" },
+ { "FA86", "160D8" },
+ { "FA87", "1614E" },
+ { "FA88", "16108" },
+ { "FA89", "1618E" },
+ { "FA8A", "16160" },
+ { "FA8B", "161F2" },
+ { "FA8C", "16234" },
+ { "FA8D", "163C4" },
+ { "FA8E", "1641C" },
+ { "FA8F", "16452" },
+ { "FA90", "16556" },
+ { "FA91", "16674" },
+ { "FA92", "16717" },
+ { "FA93", "1671B" },
+ { "FA94", "16756" },
+ { "FA95", "16B79" },
+ { "FA96", "16BBA" },
+ { "FA97", "16D41" },
+ { "FA98", "16EDB" },
+ { "FA99", "16ECB" },
+ { "FA9A", "16F22" },
+ { "FA9B", "1701E" },
+ { "FA9C", "1716E" },
+ { "FA9D", "177A7" },
+ { "FA9E", "17235" },
+ { "FA9F", "172AF" },
+ { "FAA0", "1732A" },
+ { "FAA1", "17471" },
+ { "FAA2", "17506" },
+ { "FAA3", "1753B" },
+ { "FAA4", "1761D" },
+ { "FAA5", "1761F" },
+ { "FAA6", "176CA" },
+ { "FAA7", "176DB" },
+ { "FAA8", "176F4" },
+ { "FAA9", "1774A" },
+ { "FAAA", "17740" },
+ { "FAAB", "178CC" },
+ { "FAAC", "17AB1" },
+ { "FAAD", "17BC0" },
+ { "FAAE", "17C7B" },
+ { "FAAF", "17D5B" },
+ { "FAB0", "17DF4" },
+ { "FAB1", "17F3E" },
+ { "FAB2", "18005" },
+ { "FAB3", "18352" },
+ { "FAB4", "183EF" },
+ { "FAB5", "18779" },
+ { "FAB6", "18941" },
+ { "FAB7", "18986" },
+ { "FAB8", "18996" },
+ { "FAB9", "18ABF" },
+ { "FABA", "18AF8" },
+ { "FABB", "18ACB" },
+ { "FABC", "18B01" },
+ { "FABD", "18AFE" },
+ { "FABE", "18AED" },
+ { "FABF", "18B39" },
+ { "FAC0", "18B8A" },
+ { "FAC1", "18D08" },
+ { "FAC2", "18F38" },
+ { "FAC3", "19072" },
+ { "FAC4", "19199" },
+ { "FAC5", "19276" },
+ { "FAC6", "1967C" },
+ { "FAC7", "196E3" },
+ { "FAC8", "19756" },
+ { "FAC9", "197DB" },
+ { "FACA", "197FF" },
+ { "FACB", "1980B" },
+ { "FACC", "1983B" },
+ { "FACD", "19B12" },
+ { "FACE", "19F9C" },
+ { "FACF", "12284A" },
+ { "FAD0", "122844" },
+ { "FAD1", "1233D5" },
+ { "FAD2", "13B9D" },
+ { "FAD3", "14018" },
+ { "FAD4", "14039" },
+ { "FAD5", "125249" },
+ { "FAD6", "125CD0" },
+ { "FAD7", "127ED3" },
+ { "FAD8", "19F43" },
+ { "FAD9", "19F8E" },
+ { "FB1D", "205D9_05B4" },
+ { "FB1F", "205F2_05B7" },
+ { "FB2A", "205E9_05C1" },
+ { "FB2B", "205E9_05C2" },
+ { "FB2C", "305E9_05BC_05C1" },
+ { "FB2D", "305E9_05BC_05C2" },
+ { "FB2E", "205D0_05B7" },
+ { "FB2F", "205D0_05B8" },
+ { "FB30", "205D0_05BC" },
+ { "FB31", "205D1_05BC" },
+ { "FB32", "205D2_05BC" },
+ { "FB33", "205D3_05BC" },
+ { "FB34", "205D4_05BC" },
+ { "FB35", "205D5_05BC" },
+ { "FB36", "205D6_05BC" },
+ { "FB38", "205D8_05BC" },
+ { "FB39", "205D9_05BC" },
+ { "FB3A", "205DA_05BC" },
+ { "FB3B", "205DB_05BC" },
+ { "FB3C", "205DC_05BC" },
+ { "FB3E", "205DE_05BC" },
+ { "FB40", "205E0_05BC" },
+ { "FB41", "205E1_05BC" },
+ { "FB43", "205E3_05BC" },
+ { "FB44", "205E4_05BC" },
+ { "FB46", "205E6_05BC" },
+ { "FB47", "205E7_05BC" },
+ { "FB48", "205E8_05BC" },
+ { "FB49", "205E9_05BC" },
+ { "FB4A", "205EA_05BC" },
+ { "FB4B", "205D5_05B9" },
+ { "FB4C", "205D1_05BF" },
+ { "FB4D", "205DB_05BF" },
+ { "FB4E", "205E4_05BF" },
+ { "1109A", "211099_110BA" },
+ { "1109C", "21109B_110BA" },
+ { "110AB", "2110A5_110BA" },
+ { "1112E", "211131_11127" },
+ { "1112F", "211132_11127" },
+ { "1134B", "211347_1133E" },
+ { "1134C", "211347_11357" },
+ { "114BB", "2114B9_114BA" },
+ { "114BC", "2114B9_114B0" },
+ { "114BE", "2114B9_114BD" },
+ { "115BA", "2115B8_115AF" },
+ { "115BB", "2115B9_115AF" },
+ { "1D15E", "21D157_1D165" },
+ { "1D15F", "21D158_1D165" },
+ { "1D160", "31D158_1D165_1D16E" },
+ { "1D161", "31D158_1D165_1D16F" },
+ { "1D162", "31D158_1D165_1D170" },
+ { "1D163", "31D158_1D165_1D171" },
+ { "1D164", "31D158_1D165_1D172" },
+ { "1D1BB", "21D1B9_1D165" },
+ { "1D1BC", "21D1BA_1D165" },
+ { "1D1BD", "31D1B9_1D165_1D16E" },
+ { "1D1BE", "31D1BA_1D165_1D16E" },
+ { "1D1BF", "31D1B9_1D165_1D16F" },
+ { "1D1C0", "31D1BA_1D165_1D16F" },
+ { "2F800", "14E3D" },
+ { "2F801", "14E38" },
+ { "2F802", "14E41" },
+ { "2F803", "120122" },
+ { "2F804", "14F60" },
+ { "2F805", "14FAE" },
+ { "2F806", "14FBB" },
+ { "2F807", "15002" },
+ { "2F808", "1507A" },
+ { "2F809", "15099" },
+ { "2F80A", "150E7" },
+ { "2F80B", "150CF" },
+ { "2F80C", "1349E" },
+ { "2F80D", "12063A" },
+ { "2F80E", "1514D" },
+ { "2F80F", "15154" },
+ { "2F810", "15164" },
+ { "2F811", "15177" },
+ { "2F812", "12051C" },
+ { "2F813", "134B9" },
+ { "2F814", "15167" },
+ { "2F815", "1518D" },
+ { "2F816", "12054B" },
+ { "2F817", "15197" },
+ { "2F818", "151A4" },
+ { "2F819", "14ECC" },
+ { "2F81A", "151AC" },
+ { "2F81B", "151B5" },
+ { "2F81C", "1291DF" },
+ { "2F81D", "151F5" },
+ { "2F81E", "15203" },
+ { "2F81F", "134DF" },
+ { "2F820", "1523B" },
+ { "2F821", "15246" },
+ { "2F822", "15272" },
+ { "2F823", "15277" },
+ { "2F824", "13515" },
+ { "2F825", "152C7" },
+ { "2F826", "152C9" },
+ { "2F827", "152E4" },
+ { "2F828", "152FA" },
+ { "2F829", "15305" },
+ { "2F82A", "15306" },
+ { "2F82B", "15317" },
+ { "2F82C", "15349" },
+ { "2F82D", "15351" },
+ { "2F82E", "1535A" },
+ { "2F82F", "15373" },
+ { "2F830", "1537D" },
+ { "2F831", "1537F" },
+ { "2F832", "1537F" },
+ { "2F833", "1537F" },
+ { "2F834", "120A2C" },
+ { "2F835", "17070" },
+ { "2F836", "153CA" },
+ { "2F837", "153DF" },
+ { "2F838", "120B63" },
+ { "2F839", "153EB" },
+ { "2F83A", "153F1" },
+ { "2F83B", "15406" },
+ { "2F83C", "1549E" },
+ { "2F83D", "15438" },
+ { "2F83E", "15448" },
+ { "2F83F", "15468" },
+ { "2F840", "154A2" },
+ { "2F841", "154F6" },
+ { "2F842", "15510" },
+ { "2F843", "15553" },
+ { "2F844", "15563" },
+ { "2F845", "15584" },
+ { "2F846", "15584" },
+ { "2F847", "15599" },
+ { "2F848", "155AB" },
+ { "2F849", "155B3" },
+ { "2F84A", "155C2" },
+ { "2F84B", "15716" },
+ { "2F84C", "15606" },
+ { "2F84D", "15717" },
+ { "2F84E", "15651" },
+ { "2F84F", "15674" },
+ { "2F850", "15207" },
+ { "2F851", "158EE" },
+ { "2F852", "157CE" },
+ { "2F853", "157F4" },
+ { "2F854", "1580D" },
+ { "2F855", "1578B" },
+ { "2F856", "15832" },
+ { "2F857", "15831" },
+ { "2F858", "158AC" },
+ { "2F859", "1214E4" },
+ { "2F85A", "158F2" },
+ { "2F85B", "158F7" },
+ { "2F85C", "15906" },
+ { "2F85D", "1591A" },
+ { "2F85E", "15922" },
+ { "2F85F", "15962" },
+ { "2F860", "1216A8" },
+ { "2F861", "1216EA" },
+ { "2F862", "159EC" },
+ { "2F863", "15A1B" },
+ { "2F864", "15A27" },
+ { "2F865", "159D8" },
+ { "2F866", "15A66" },
+ { "2F867", "136EE" },
+ { "2F868", "136FC" },
+ { "2F869", "15B08" },
+ { "2F86A", "15B3E" },
+ { "2F86B", "15B3E" },
+ { "2F86C", "1219C8" },
+ { "2F86D", "15BC3" },
+ { "2F86E", "15BD8" },
+ { "2F86F", "15BE7" },
+ { "2F870", "15BF3" },
+ { "2F871", "121B18" },
+ { "2F872", "15BFF" },
+ { "2F873", "15C06" },
+ { "2F874", "15F53" },
+ { "2F875", "15C22" },
+ { "2F876", "13781" },
+ { "2F877", "15C60" },
+ { "2F878", "15C6E" },
+ { "2F879", "15CC0" },
+ { "2F87A", "15C8D" },
+ { "2F87B", "121DE4" },
+ { "2F87C", "15D43" },
+ { "2F87D", "121DE6" },
+ { "2F87E", "15D6E" },
+ { "2F87F", "15D6B" },
+ { "2F880", "15D7C" },
+ { "2F881", "15DE1" },
+ { "2F882", "15DE2" },
+ { "2F883", "1382F" },
+ { "2F884", "15DFD" },
+ { "2F885", "15E28" },
+ { "2F886", "15E3D" },
+ { "2F887", "15E69" },
+ { "2F888", "13862" },
+ { "2F889", "122183" },
+ { "2F88A", "1387C" },
+ { "2F88B", "15EB0" },
+ { "2F88C", "15EB3" },
+ { "2F88D", "15EB6" },
+ { "2F88E", "15ECA" },
+ { "2F88F", "12A392" },
+ { "2F890", "15EFE" },
+ { "2F891", "122331" },
+ { "2F892", "122331" },
+ { "2F893", "18201" },
+ { "2F894", "15F22" },
+ { "2F895", "15F22" },
+ { "2F896", "138C7" },
+ { "2F897", "1232B8" },
+ { "2F898", "1261DA" },
+ { "2F899", "15F62" },
+ { "2F89A", "15F6B" },
+ { "2F89B", "138E3" },
+ { "2F89C", "15F9A" },
+ { "2F89D", "15FCD" },
+ { "2F89E", "15FD7" },
+ { "2F89F", "15FF9" },
+ { "2F8A0", "16081" },
+ { "2F8A1", "1393A" },
+ { "2F8A2", "1391C" },
+ { "2F8A3", "16094" },
+ { "2F8A4", "1226D4" },
+ { "2F8A5", "160C7" },
+ { "2F8A6", "16148" },
+ { "2F8A7", "1614C" },
+ { "2F8A8", "1614E" },
+ { "2F8A9", "1614C" },
+ { "2F8AA", "1617A" },
+ { "2F8AB", "1618E" },
+ { "2F8AC", "161B2" },
+ { "2F8AD", "161A4" },
+ { "2F8AE", "161AF" },
+ { "2F8AF", "161DE" },
+ { "2F8B0", "161F2" },
+ { "2F8B1", "161F6" },
+ { "2F8B2", "16210" },
+ { "2F8B3", "1621B" },
+ { "2F8B4", "1625D" },
+ { "2F8B5", "162B1" },
+ { "2F8B6", "162D4" },
+ { "2F8B7", "16350" },
+ { "2F8B8", "122B0C" },
+ { "2F8B9", "1633D" },
+ { "2F8BA", "162FC" },
+ { "2F8BB", "16368" },
+ { "2F8BC", "16383" },
+ { "2F8BD", "163E4" },
+ { "2F8BE", "122BF1" },
+ { "2F8BF", "16422" },
+ { "2F8C0", "163C5" },
+ { "2F8C1", "163A9" },
+ { "2F8C2", "13A2E" },
+ { "2F8C3", "16469" },
+ { "2F8C4", "1647E" },
+ { "2F8C5", "1649D" },
+ { "2F8C6", "16477" },
+ { "2F8C7", "13A6C" },
+ { "2F8C8", "1654F" },
+ { "2F8C9", "1656C" },
+ { "2F8CA", "12300A" },
+ { "2F8CB", "165E3" },
+ { "2F8CC", "166F8" },
+ { "2F8CD", "16649" },
+ { "2F8CE", "13B19" },
+ { "2F8CF", "16691" },
+ { "2F8D0", "13B08" },
+ { "2F8D1", "13AE4" },
+ { "2F8D2", "15192" },
+ { "2F8D3", "15195" },
+ { "2F8D4", "16700" },
+ { "2F8D5", "1669C" },
+ { "2F8D6", "180AD" },
+ { "2F8D7", "143D9" },
+ { "2F8D8", "16717" },
+ { "2F8D9", "1671B" },
+ { "2F8DA", "16721" },
+ { "2F8DB", "1675E" },
+ { "2F8DC", "16753" },
+ { "2F8DD", "1233C3" },
+ { "2F8DE", "13B49" },
+ { "2F8DF", "167FA" },
+ { "2F8E0", "16785" },
+ { "2F8E1", "16852" },
+ { "2F8E2", "16885" },
+ { "2F8E3", "12346D" },
+ { "2F8E4", "1688E" },
+ { "2F8E5", "1681F" },
+ { "2F8E6", "16914" },
+ { "2F8E7", "13B9D" },
+ { "2F8E8", "16942" },
+ { "2F8E9", "169A3" },
+ { "2F8EA", "169EA" },
+ { "2F8EB", "16AA8" },
+ { "2F8EC", "1236A3" },
+ { "2F8ED", "16ADB" },
+ { "2F8EE", "13C18" },
+ { "2F8EF", "16B21" },
+ { "2F8F0", "1238A7" },
+ { "2F8F1", "16B54" },
+ { "2F8F2", "13C4E" },
+ { "2F8F3", "16B72" },
+ { "2F8F4", "16B9F" },
+ { "2F8F5", "16BBA" },
+ { "2F8F6", "16BBB" },
+ { "2F8F7", "123A8D" },
+ { "2F8F8", "121D0B" },
+ { "2F8F9", "123AFA" },
+ { "2F8FA", "16C4E" },
+ { "2F8FB", "123CBC" },
+ { "2F8FC", "16CBF" },
+ { "2F8FD", "16CCD" },
+ { "2F8FE", "16C67" },
+ { "2F8FF", "16D16" },
+ { "2F900", "16D3E" },
+ { "2F901", "16D77" },
+ { "2F902", "16D41" },
+ { "2F903", "16D69" },
+ { "2F904", "16D78" },
+ { "2F905", "16D85" },
+ { "2F906", "123D1E" },
+ { "2F907", "16D34" },
+ { "2F908", "16E2F" },
+ { "2F909", "16E6E" },
+ { "2F90A", "13D33" },
+ { "2F90B", "16ECB" },
+ { "2F90C", "16EC7" },
+ { "2F90D", "123ED1" },
+ { "2F90E", "16DF9" },
+ { "2F90F", "16F6E" },
+ { "2F910", "123F5E" },
+ { "2F911", "123F8E" },
+ { "2F912", "16FC6" },
+ { "2F913", "17039" },
+ { "2F914", "1701E" },
+ { "2F915", "1701B" },
+ { "2F916", "13D96" },
+ { "2F917", "1704A" },
+ { "2F918", "1707D" },
+ { "2F919", "17077" },
+ { "2F91A", "170AD" },
+ { "2F91B", "120525" },
+ { "2F91C", "17145" },
+ { "2F91D", "124263" },
+ { "2F91E", "1719C" },
+ { "2F91F", "1243AB" },
+ { "2F920", "17228" },
+ { "2F921", "17235" },
+ { "2F922", "17250" },
+ { "2F923", "124608" },
+ { "2F924", "17280" },
+ { "2F925", "17295" },
+ { "2F926", "124735" },
+ { "2F927", "124814" },
+ { "2F928", "1737A" },
+ { "2F929", "1738B" },
+ { "2F92A", "13EAC" },
+ { "2F92B", "173A5" },
+ { "2F92C", "13EB8" },
+ { "2F92D", "13EB8" },
+ { "2F92E", "17447" },
+ { "2F92F", "1745C" },
+ { "2F930", "17471" },
+ { "2F931", "17485" },
+ { "2F932", "174CA" },
+ { "2F933", "13F1B" },
+ { "2F934", "17524" },
+ { "2F935", "124C36" },
+ { "2F936", "1753E" },
+ { "2F937", "124C92" },
+ { "2F938", "17570" },
+ { "2F939", "12219F" },
+ { "2F93A", "17610" },
+ { "2F93B", "124FA1" },
+ { "2F93C", "124FB8" },
+ { "2F93D", "125044" },
+ { "2F93E", "13FFC" },
+ { "2F93F", "14008" },
+ { "2F940", "176F4" },
+ { "2F941", "1250F3" },
+ { "2F942", "1250F2" },
+ { "2F943", "125119" },
+ { "2F944", "125133" },
+ { "2F945", "1771E" },
+ { "2F946", "1771F" },
+ { "2F947", "1771F" },
+ { "2F948", "1774A" },
+ { "2F949", "14039" },
+ { "2F94A", "1778B" },
+ { "2F94B", "14046" },
+ { "2F94C", "14096" },
+ { "2F94D", "12541D" },
+ { "2F94E", "1784E" },
+ { "2F94F", "1788C" },
+ { "2F950", "178CC" },
+ { "2F951", "140E3" },
+ { "2F952", "125626" },
+ { "2F953", "17956" },
+ { "2F954", "12569A" },
+ { "2F955", "1256C5" },
+ { "2F956", "1798F" },
+ { "2F957", "179EB" },
+ { "2F958", "1412F" },
+ { "2F959", "17A40" },
+ { "2F95A", "17A4A" },
+ { "2F95B", "17A4F" },
+ { "2F95C", "12597C" },
+ { "2F95D", "125AA7" },
+ { "2F95E", "125AA7" },
+ { "2F95F", "17AEE" },
+ { "2F960", "14202" },
+ { "2F961", "125BAB" },
+ { "2F962", "17BC6" },
+ { "2F963", "17BC9" },
+ { "2F964", "14227" },
+ { "2F965", "125C80" },
+ { "2F966", "17CD2" },
+ { "2F967", "142A0" },
+ { "2F968", "17CE8" },
+ { "2F969", "17CE3" },
+ { "2F96A", "17D00" },
+ { "2F96B", "125F86" },
+ { "2F96C", "17D63" },
+ { "2F96D", "14301" },
+ { "2F96E", "17DC7" },
+ { "2F96F", "17E02" },
+ { "2F970", "17E45" },
+ { "2F971", "14334" },
+ { "2F972", "126228" },
+ { "2F973", "126247" },
+ { "2F974", "14359" },
+ { "2F975", "1262D9" },
+ { "2F976", "17F7A" },
+ { "2F977", "12633E" },
+ { "2F978", "17F95" },
+ { "2F979", "17FFA" },
+ { "2F97A", "18005" },
+ { "2F97B", "1264DA" },
+ { "2F97C", "126523" },
+ { "2F97D", "18060" },
+ { "2F97E", "1265A8" },
+ { "2F97F", "18070" },
+ { "2F980", "12335F" },
+ { "2F981", "143D5" },
+ { "2F982", "180B2" },
+ { "2F983", "18103" },
+ { "2F984", "1440B" },
+ { "2F985", "1813E" },
+ { "2F986", "15AB5" },
+ { "2F987", "1267A7" },
+ { "2F988", "1267B5" },
+ { "2F989", "123393" },
+ { "2F98A", "12339C" },
+ { "2F98B", "18201" },
+ { "2F98C", "18204" },
+ { "2F98D", "18F9E" },
+ { "2F98E", "1446B" },
+ { "2F98F", "18291" },
+ { "2F990", "1828B" },
+ { "2F991", "1829D" },
+ { "2F992", "152B3" },
+ { "2F993", "182B1" },
+ { "2F994", "182B3" },
+ { "2F995", "182BD" },
+ { "2F996", "182E6" },
+ { "2F997", "126B3C" },
+ { "2F998", "182E5" },
+ { "2F999", "1831D" },
+ { "2F99A", "18363" },
+ { "2F99B", "183AD" },
+ { "2F99C", "18323" },
+ { "2F99D", "183BD" },
+ { "2F99E", "183E7" },
+ { "2F99F", "18457" },
+ { "2F9A0", "18353" },
+ { "2F9A1", "183CA" },
+ { "2F9A2", "183CC" },
+ { "2F9A3", "183DC" },
+ { "2F9A4", "126C36" },
+ { "2F9A5", "126D6B" },
+ { "2F9A6", "126CD5" },
+ { "2F9A7", "1452B" },
+ { "2F9A8", "184F1" },
+ { "2F9A9", "184F3" },
+ { "2F9AA", "18516" },
+ { "2F9AB", "1273CA" },
+ { "2F9AC", "18564" },
+ { "2F9AD", "126F2C" },
+ { "2F9AE", "1455D" },
+ { "2F9AF", "14561" },
+ { "2F9B0", "126FB1" },
+ { "2F9B1", "1270D2" },
+ { "2F9B2", "1456B" },
+ { "2F9B3", "18650" },
+ { "2F9B4", "1865C" },
+ { "2F9B5", "18667" },
+ { "2F9B6", "18669" },
+ { "2F9B7", "186A9" },
+ { "2F9B8", "18688" },
+ { "2F9B9", "1870E" },
+ { "2F9BA", "186E2" },
+ { "2F9BB", "18779" },
+ { "2F9BC", "18728" },
+ { "2F9BD", "1876B" },
+ { "2F9BE", "18786" },
+ { "2F9BF", "145D7" },
+ { "2F9C0", "187E1" },
+ { "2F9C1", "18801" },
+ { "2F9C2", "145F9" },
+ { "2F9C3", "18860" },
+ { "2F9C4", "18863" },
+ { "2F9C5", "127667" },
+ { "2F9C6", "188D7" },
+ { "2F9C7", "188DE" },
+ { "2F9C8", "14635" },
+ { "2F9C9", "188FA" },
+ { "2F9CA", "134BB" },
+ { "2F9CB", "1278AE" },
+ { "2F9CC", "127966" },
+ { "2F9CD", "146BE" },
+ { "2F9CE", "146C7" },
+ { "2F9CF", "18AA0" },
+ { "2F9D0", "18AED" },
+ { "2F9D1", "18B8A" },
+ { "2F9D2", "18C55" },
+ { "2F9D3", "127CA8" },
+ { "2F9D4", "18CAB" },
+ { "2F9D5", "18CC1" },
+ { "2F9D6", "18D1B" },
+ { "2F9D7", "18D77" },
+ { "2F9D8", "127F2F" },
+ { "2F9D9", "120804" },
+ { "2F9DA", "18DCB" },
+ { "2F9DB", "18DBC" },
+ { "2F9DC", "18DF0" },
+ { "2F9DD", "1208DE" },
+ { "2F9DE", "18ED4" },
+ { "2F9DF", "18F38" },
+ { "2F9E0", "1285D2" },
+ { "2F9E1", "1285ED" },
+ { "2F9E2", "19094" },
+ { "2F9E3", "190F1" },
+ { "2F9E4", "19111" },
+ { "2F9E5", "12872E" },
+ { "2F9E6", "1911B" },
+ { "2F9E7", "19238" },
+ { "2F9E8", "192D7" },
+ { "2F9E9", "192D8" },
+ { "2F9EA", "1927C" },
+ { "2F9EB", "193F9" },
+ { "2F9EC", "19415" },
+ { "2F9ED", "128BFA" },
+ { "2F9EE", "1958B" },
+ { "2F9EF", "14995" },
+ { "2F9F0", "195B7" },
+ { "2F9F1", "128D77" },
+ { "2F9F2", "149E6" },
+ { "2F9F3", "196C3" },
+ { "2F9F4", "15DB2" },
+ { "2F9F5", "19723" },
+ { "2F9F6", "129145" },
+ { "2F9F7", "12921A" },
+ { "2F9F8", "14A6E" },
+ { "2F9F9", "14A76" },
+ { "2F9FA", "197E0" },
+ { "2F9FB", "12940A" },
+ { "2F9FC", "14AB2" },
+ { "2F9FD", "129496" },
+ { "2F9FE", "1980B" },
+ { "2F9FF", "1980B" },
+ { "2FA00", "19829" },
+ { "2FA01", "1295B6" },
+ { "2FA02", "198E2" },
+ { "2FA03", "14B33" },
+ { "2FA04", "19929" },
+ { "2FA05", "199A7" },
+ { "2FA06", "199C2" },
+ { "2FA07", "199FE" },
+ { "2FA08", "14BCE" },
+ { "2FA09", "129B30" },
+ { "2FA0A", "19B12" },
+ { "2FA0B", "19C40" },
+ { "2FA0C", "19CFD" },
+ { "2FA0D", "14CCE" },
+ { "2FA0E", "14CED" },
+ { "2FA0F", "19D67" },
+ { "2FA10", "12A0CE" },
+ { "2FA11", "14CF8" },
+ { "2FA12", "12A105" },
+ { "2FA13", "12A20E" },
+ { "2FA14", "12A291" },
+ { "2FA15", "19EBB" },
+ { "2FA16", "14D56" },
+ { "2FA17", "19EF9" },
+ { "2FA18", "19EFE" },
+ { "2FA19", "19F05" },
+ { "2FA1A", "19F0F" },
+ { "2FA1B", "19F16" },
+ { "2FA1C", "19F3B" },
+ { "2FA1D", "12A600" },
+};
+
+// global constructor
+
+static struct unicode_decompose_init {
+ unicode_decompose_init();
+} _unicode_decompose_init;
+
+unicode_decompose_init::unicode_decompose_init()
+{
+ for (unsigned int i = 0;
+ i < sizeof(unicode_decompose_list)/sizeof(unicode_decompose_list[0]);
+ i++) {
+ unicode_decompose *dec = new unicode_decompose[1];
+ dec->value = (char *)unicode_decompose_list[i].value;
+ unicode_decompose_table.define(unicode_decompose_list[i].key, dec);
+ }
+}
+
+const char *decompose_unicode(const char *s)
+{
+ unicode_decompose *result = unicode_decompose_table.lookup(s);
+ return result ? result->value : 0;
+}
diff --git a/src/libs/libxutil/DviChar.c b/src/libs/libxutil/DviChar.c
new file mode 100644
index 0000000..fe086fc
--- /dev/null
+++ b/src/libs/libxutil/DviChar.c
@@ -0,0 +1,679 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+/*
+ * DviChar.c
+ *
+ * Map DVI (ditroff output) character names to
+ * font indexes and back
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include "DviChar.h"
+
+extern char *xmalloc(int);
+
+#define allocHash() ((DviCharNameHash *) xmalloc (sizeof (DviCharNameHash)))
+
+struct map_list {
+ struct map_list *next;
+ DviCharNameMap *map;
+};
+
+static struct map_list *world;
+
+static int standard_maps_loaded = 0;
+static void load_standard_maps (void);
+static int hash_name (const char *);
+static void dispose_hash(DviCharNameMap *);
+static void compute_hash(DviCharNameMap *);
+
+DviCharNameMap *
+DviFindMap (char *encoding)
+{
+ struct map_list *m;
+
+ if (!standard_maps_loaded)
+ load_standard_maps ();
+ for (m = world; m; m=m->next)
+ if (!strcmp (m->map->encoding, encoding))
+ return m->map;
+ return 0;
+}
+
+void
+DviRegisterMap (DviCharNameMap *map)
+{
+ struct map_list *m;
+
+ if (!standard_maps_loaded)
+ load_standard_maps ();
+ for (m = world; m; m = m->next)
+ if (!strcmp (m->map->encoding, map->encoding))
+ break;
+ if (!m) {
+ m = (struct map_list *) xmalloc (sizeof *m);
+ m->next = world;
+ world = m;
+ }
+ dispose_hash (map);
+ m->map = map;
+ compute_hash (map);
+}
+
+static void
+dispose_hash (DviCharNameMap *map)
+{
+ DviCharNameHash **buckets;
+ DviCharNameHash *h, *next;
+ int i;
+
+ buckets = map->buckets;
+ for (i = 0; i < DVI_HASH_SIZE; i++) {
+ for (h = buckets[i]; h; h=next) {
+ next = h->next;
+ free (h);
+ }
+ }
+}
+
+static int
+hash_name (const char *name)
+{
+ int i = 0;
+
+ while (*name)
+ i = (i << 1) ^ *name++;
+ if (i < 0)
+ i = -i;
+ return i;
+}
+
+static void
+compute_hash (DviCharNameMap *map)
+{
+ DviCharNameHash **buckets;
+ int c, s, i;
+ DviCharNameHash *h;
+
+ buckets = map->buckets;
+ for (i = 0; i < DVI_HASH_SIZE; i++)
+ buckets[i] = 0;
+ for (c = 0; c < DVI_MAP_SIZE; c++)
+ for (s = 0; s < DVI_MAX_SYNONYMS; s++) {
+ if (!map->dvi_names[c][s])
+ break;
+ i = hash_name (map->dvi_names[c][s]) % DVI_HASH_SIZE;
+ h = allocHash ();
+ h->next = buckets[i];
+ buckets[i] = h;
+ h->name = map->dvi_names[c][s];
+ h->position = c;
+ }
+
+}
+
+int
+DviCharIndex (DviCharNameMap *map, const char *name)
+{
+ int i;
+ DviCharNameHash *h;
+
+ i = hash_name (name) % DVI_HASH_SIZE;
+ for (h = map->buckets[i]; h; h=h->next)
+ if (!strcmp (h->name, name))
+ return h->position;
+ return -1;
+}
+
+static DviCharNameMap ISO8859_1_map = {
+ "iso8859-1",
+ 0,
+{
+{ 0, /* 0 */},
+{ 0, /* 1 */},
+{ 0, /* 2 */},
+{ 0, /* 3 */},
+{ 0, /* 4 */},
+{ 0, /* 5 */},
+{ 0, /* 6 */},
+{ 0, /* 7 */},
+{ 0, /* 8 */},
+{ 0, /* 9 */},
+{ 0, /* 10 */},
+{ 0, /* 11 */},
+{ 0, /* 12 */},
+{ 0, /* 13 */},
+{ 0, /* 14 */},
+{ 0, /* 15 */},
+{ 0, /* 16 */},
+{ 0, /* 17 */},
+{ 0, /* 18 */},
+{ 0, /* 19 */},
+{ 0, /* 20 */},
+{ 0, /* 21 */},
+{ 0, /* 22 */},
+{ 0, /* 23 */},
+{ 0, /* 24 */},
+{ 0, /* 25 */},
+{ 0, /* 26 */},
+{ 0, /* 27 */},
+{ 0, /* 28 */},
+{ 0, /* 29 */},
+{ 0, /* 30 */},
+{ 0, /* 31 */},
+{ 0, /* 32 */},
+{ "!", /* 33 */},
+{ "\"", "dq", /* 34 */},
+{ "#", "sh", /* 35 */},
+{ "$", "Do", /* 36 */},
+{ "%", /* 37 */},
+{ "&", /* 38 */},
+{ "'", "aq", "cq", "oq", /* 39 */},
+{ "(", /* 40 */},
+{ ")", /* 41 */},
+{ "*", /* 42 */},
+{ "+", /* 43 */},
+{ ",", /* 44 */},
+{ "\\-", /* 45 */},
+{ ".", /* 46 */},
+{ "/", "sl", /* 47 */},
+{ "0", /* 48 */},
+{ "1", /* 49 */},
+{ "2", /* 50 */},
+{ "3", /* 51 */},
+{ "4", /* 52 */},
+{ "5", /* 53 */},
+{ "6", /* 54 */},
+{ "7", /* 55 */},
+{ "8", /* 56 */},
+{ "9", /* 57 */},
+{ ":", /* 58 */},
+{ ";", /* 59 */},
+{ "<", /* 60 */},
+{ "=", /* 61 */},
+{ ">", /* 62 */},
+{ "?", /* 63 */},
+{ "@", "at", /* 64 */},
+{ "A", /* 65 */},
+{ "B", /* 66 */},
+{ "C", /* 67 */},
+{ "D", /* 68 */},
+{ "E", /* 69 */},
+{ "F", /* 70 */},
+{ "G", /* 71 */},
+{ "H", /* 72 */},
+{ "I", /* 73 */},
+{ "J", /* 74 */},
+{ "K", /* 75 */},
+{ "L", /* 76 */},
+{ "M", /* 77 */},
+{ "N", /* 78 */},
+{ "O", /* 79 */},
+{ "P", /* 80 */},
+{ "Q", /* 81 */},
+{ "R", /* 82 */},
+{ "S", /* 83 */},
+{ "T", /* 84 */},
+{ "U", /* 85 */},
+{ "V", /* 86 */},
+{ "W", /* 87 */},
+{ "X", /* 88 */},
+{ "Y", /* 89 */},
+{ "Z", /* 90 */},
+{ "[", "lB", /* 91 */},
+{ "\\", "rs", /* 92 */},
+{ "]", "rB", /* 93 */},
+{ "^", "a^", "ha", /* 94 */},
+{ "_", /* 95 */},
+{ "`", "ga", /* 96 */},
+{ "a", /* 97 */},
+{ "b", /* 98 */},
+{ "c", /* 99 */},
+{ "d", /* 100 */},
+{ "e", /* 101 */},
+{ "f", /* 102 */},
+{ "g", /* 103 */},
+{ "h", /* 104 */},
+{ "i", /* 105 */},
+{ "j", /* 106 */},
+{ "k", /* 107 */},
+{ "l", /* 108 */},
+{ "m", /* 109 */},
+{ "n", /* 110 */},
+{ "o", /* 111 */},
+{ "p", /* 112 */},
+{ "q", /* 113 */},
+{ "r", /* 114 */},
+{ "s", /* 115 */},
+{ "t", /* 116 */},
+{ "u", /* 117 */},
+{ "v", /* 118 */},
+{ "w", /* 119 */},
+{ "x", /* 120 */},
+{ "y", /* 121 */},
+{ "z", /* 122 */},
+{ "{", "lC", /* 123 */},
+{ "|", "ba", /* 124 */},
+{ "}", "rC", /* 125 */},
+{ "~", "a~", "ti", /* 126 */},
+{ 0, /* 127 */},
+{ 0, /* 128 */},
+{ 0, /* 129 */},
+{ 0, /* 130 */},
+{ 0, /* 131 */},
+{ 0, /* 132 */},
+{ 0, /* 133 */},
+{ 0, /* 134 */},
+{ 0, /* 135 */},
+{ 0, /* 136 */},
+{ 0, /* 137 */},
+{ 0, /* 138 */},
+{ 0, /* 139 */},
+{ 0, /* 140 */},
+{ 0, /* 141 */},
+{ 0, /* 142 */},
+{ 0, /* 143 */},
+{ 0, /* 144 */},
+{ 0, /* 145 */},
+{ 0, /* 146 */},
+{ 0, /* 147 */},
+{ 0, /* 148 */},
+{ 0, /* 149 */},
+{ 0, /* 150 */},
+{ 0, /* 151 */},
+{ 0, /* 152 */},
+{ 0, /* 153 */},
+{ 0, /* 154 */},
+{ 0, /* 155 */},
+{ 0, /* 156 */},
+{ 0, /* 157 */},
+{ 0, /* 158 */},
+{ 0, /* 159 */},
+{ 0, /* 160 */},
+{ "r!", /* 161 */},
+{ "ct", /* 162 */},
+{ "Po", /* 163 */},
+{ "Cs", /* 164 */},
+{ "Ye", /* 165 */},
+{ "bb", /* 166 */},
+{ "sc", /* 167 */},
+{ "ad", /* 168 */},
+{ "co", /* 169 */},
+{ "Of", /* 170 */},
+{ "Fo", /* 171 */},
+{ "tno", /* 172 */},
+{ "-", "hy", /* 173 */},
+{ "rg", /* 174 */},
+{ "a-", /* 175 */},
+{ "de", /* 176 */},
+{ "t+-", /* 177 */},
+{ "S2", /* 178 */},
+{ "S3", /* 179 */},
+{ "aa", /* 180 */},
+{ "mc", /* 181 */},
+{ "ps", /* 182 */},
+{ "pc", /* 183 */},
+{ "ac", /* 184 */},
+{ "S1", /* 185 */},
+{ "Om", /* 186 */},
+{ "Fc", /* 187 */},
+{ "14", /* 188 */},
+{ "12", /* 189 */},
+{ "34", /* 190 */},
+{ "r?", /* 191 */},
+{ "`A", /* 192 */},
+{ "'A", /* 193 */},
+{ "^A", /* 194 */},
+{ "~A", /* 195 */},
+{ ":A", /* 196 */},
+{ "oA", /* 197 */},
+{ "AE", /* 198 */},
+{ ",C", /* 199 */},
+{ "`E", /* 200 */},
+{ "'E", /* 201 */},
+{ "^E", /* 202 */},
+{ ":E", /* 203 */},
+{ "`I", /* 204 */},
+{ "'I", /* 205 */},
+{ "^I", /* 206 */},
+{ ":I", /* 207 */},
+{ "-D", /* 208 */},
+{ "~N", /* 209 */},
+{ "`O", /* 210 */},
+{ "'O", /* 211 */},
+{ "^O", /* 212 */},
+{ "~O", /* 213 */},
+{ ":O", /* 214 */},
+{ "tmu", /* 215 */},
+{ "/O", /* 216 */},
+{ "`U", /* 217 */},
+{ "'U", /* 218 */},
+{ "^U", /* 219 */},
+{ ":U", /* 220 */},
+{ "'Y", /* 221 */},
+{ "TP", /* 222 */},
+{ "ss", /* 223 */},
+{ "`a", /* 224 */},
+{ "'a", /* 225 */},
+{ "^a", /* 226 */},
+{ "~a", /* 227 */},
+{ ":a", /* 228 */},
+{ "oa", /* 229 */},
+{ "ae", /* 230 */},
+{ ",c", /* 231 */},
+{ "`e", /* 232 */},
+{ "'e", /* 233 */},
+{ "^e", /* 234 */},
+{ ":e", /* 235 */},
+{ "`i", /* 236 */},
+{ "'i", /* 237 */},
+{ "^i", /* 238 */},
+{ ":i", /* 239 */},
+{ "Sd", /* 240 */},
+{ "~n", /* 241 */},
+{ "`o", /* 242 */},
+{ "'o", /* 243 */},
+{ "^o", /* 244 */},
+{ "~o", /* 245 */},
+{ ":o", /* 246 */},
+{ "tdi", /* 247 */},
+{ "/o", /* 248 */},
+{ "`u", /* 249 */},
+{ "'u", /* 250 */},
+{ "^u", /* 251 */},
+{ ":u", /* 252 */},
+{ "'y", /* 253 */},
+{ "Tp", /* 254 */},
+{ ":y", /* 255 */},
+},
+ {0} /* buckets */};
+
+static DviCharNameMap Adobe_Symbol_map = {
+ "adobe-fontspecific",
+ 1,
+{
+{ 0, /* 0 */},
+{ 0, /* 1 */},
+{ 0, /* 2 */},
+{ 0, /* 3 */},
+{ 0, /* 4 */},
+{ 0, /* 5 */},
+{ 0, /* 6 */},
+{ 0, /* 7 */},
+{ 0, /* 8 */},
+{ 0, /* 9 */},
+{ 0, /* 10 */},
+{ 0, /* 11 */},
+{ 0, /* 12 */},
+{ 0, /* 13 */},
+{ 0, /* 14 */},
+{ 0, /* 15 */},
+{ 0, /* 16 */},
+{ 0, /* 17 */},
+{ 0, /* 18 */},
+{ 0, /* 19 */},
+{ 0, /* 20 */},
+{ 0, /* 21 */},
+{ 0, /* 22 */},
+{ 0, /* 23 */},
+{ 0, /* 24 */},
+{ 0, /* 25 */},
+{ 0, /* 26 */},
+{ 0, /* 27 */},
+{ 0, /* 28 */},
+{ 0, /* 29 */},
+{ 0, /* 30 */},
+{ 0, /* 31 */},
+{ 0, /* 32 */},
+{ "!", /* 33 */},
+{ "fa", /* 34 */},
+{ "#", "sh", /* 35 */},
+{ "te", /* 36 */},
+{ "%", /* 37 */},
+{ "&", /* 38 */},
+{ "st", /* 39 */},
+{ "(", /* 40 */},
+{ ")", /* 41 */},
+{ "**", /* 42 */},
+{ "+", "pl", /* 43 */},
+{ ",", /* 44 */},
+{ "\\-", "mi", /* 45 */},
+{ ".", /* 46 */},
+{ "/", "sl", /* 47 */},
+{ "0", /* 48 */},
+{ "1", /* 49 */},
+{ "2", /* 50 */},
+{ "3", /* 51 */},
+{ "4", /* 52 */},
+{ "5", /* 53 */},
+{ "6", /* 54 */},
+{ "7", /* 55 */},
+{ "8", /* 56 */},
+{ "9", /* 57 */},
+{ ":", /* 58 */},
+{ ";", /* 59 */},
+{ "<", /* 60 */},
+{ "=", "eq", /* 61 */},
+{ ">", /* 62 */},
+{ "?", /* 63 */},
+{ "=~", /* 64 */},
+{ "*A", /* 65 */},
+{ "*B", /* 66 */},
+{ "*X", /* 67 */},
+{ "*D", /* 68 */},
+{ "*E", /* 69 */},
+{ "*F", /* 70 */},
+{ "*G", /* 71 */},
+{ "*Y", /* 72 */},
+{ "*I", /* 73 */},
+{ "+h", /* 74 */},
+{ "*K", /* 75 */},
+{ "*L", /* 76 */},
+{ "*M", /* 77 */},
+{ "*N", /* 78 */},
+{ "*O", /* 79 */},
+{ "*P", /* 80 */},
+{ "*H", /* 81 */},
+{ "*R", /* 82 */},
+{ "*S", /* 83 */},
+{ "*T", /* 84 */},
+{ 0, /* 85 */},
+{ "ts", /* 86 */},
+{ "*W", /* 87 */},
+{ "*C", /* 88 */},
+{ "*Q", /* 89 */},
+{ "*Z", /* 90 */},
+{ "[", "lB", /* 91 */},
+{ "tf", "3d", /* 92 */},
+{ "]", "rB", /* 93 */},
+{ "pp", /* 94 */},
+{ "_", /* 95 */},
+{ "radicalex", /* 96 */},
+{ "*a", /* 97 */},
+{ "*b", /* 98 */},
+{ "*x", /* 99 */},
+{ "*d", /* 100 */},
+{ "*e", /* 101 */},
+{ "*f", /* 102 */},
+{ "*g", /* 103 */},
+{ "*y", /* 104 */},
+{ "*i", /* 105 */},
+{ "+f", /* 106 */},
+{ "*k", /* 107 */},
+{ "*l", /* 108 */},
+{ "*m", /* 109 */},
+{ "*n", /* 110 */},
+{ "*o", /* 111 */},
+{ "*p", /* 112 */},
+{ "*h", /* 113 */},
+{ "*r", /* 114 */},
+{ "*s", /* 115 */},
+{ "*t", /* 116 */},
+{ "*u", /* 117 */},
+{ "+p", /* 118 */},
+{ "*w", /* 119 */},
+{ "*c", /* 120 */},
+{ "*q", /* 121 */},
+{ "*z", /* 122 */},
+{ "lC", "{", /* 123 */},
+{ "ba", "|", /* 124 */},
+{ "rC", "}", /* 125 */},
+{ "ap", /* 126 */},
+{ 0, /* 127 */},
+{ 0, /* 128 */},
+{ 0, /* 129 */},
+{ 0, /* 130 */},
+{ 0, /* 131 */},
+{ 0, /* 132 */},
+{ 0, /* 133 */},
+{ 0, /* 134 */},
+{ 0, /* 135 */},
+{ 0, /* 136 */},
+{ 0, /* 137 */},
+{ 0, /* 138 */},
+{ 0, /* 139 */},
+{ 0, /* 140 */},
+{ 0, /* 141 */},
+{ 0, /* 142 */},
+{ 0, /* 143 */},
+{ 0, /* 144 */},
+{ 0, /* 145 */},
+{ 0, /* 146 */},
+{ 0, /* 147 */},
+{ 0, /* 148 */},
+{ 0, /* 149 */},
+{ 0, /* 150 */},
+{ 0, /* 151 */},
+{ 0, /* 152 */},
+{ 0, /* 153 */},
+{ 0, /* 154 */},
+{ 0, /* 155 */},
+{ 0, /* 156 */},
+{ 0, /* 157 */},
+{ 0, /* 158 */},
+{ 0, /* 159 */},
+{ 0, /* 160 */},
+{ "*U", /* 161 */},
+{ "fm", /* 162 */},
+{ "<=", /* 163 */},
+{ "f/", /* 164 */},
+{ "if", /* 165 */},
+{ "Fn", /* 166 */},
+{ "CL", /* 167 */},
+{ "DI", /* 168 */},
+{ "HE", /* 169 */},
+{ "SP", /* 170 */},
+{ "<>", /* 171 */},
+{ "<-", /* 172 */},
+{ "ua", "arrowverttp", /* 173 */},
+{ "->", /* 174 */},
+{ "da", "arrowvertbt", /* 175 */},
+{ "de", /* 176 */},
+{ "+-", /* 177 */},
+{ "sd", /* 178 */},
+{ ">=", /* 179 */},
+{ "mu", /* 180 */},
+{ "pt", /* 181 */},
+{ "pd", /* 182 */},
+{ "bu", /* 183 */},
+{ "di", /* 184 */},
+{ "!=", /* 185 */},
+{ "==", /* 186 */},
+{ "~=", "~~", /* 187 */},
+{ 0, /* 188 */},
+{ "arrowvertex", /* 189 */},
+{ "an", /* 190 */},
+{ "CR", /* 191 */},
+{ "Ah", /* 192 */},
+{ "Im", /* 193 */},
+{ "Re", /* 194 */},
+{ "wp", /* 195 */},
+{ "c*", /* 196 */},
+{ "c+", /* 197 */},
+{ "es", /* 198 */},
+{ "ca", /* 199 */},
+{ "cu", /* 200 */},
+{ "sp", /* 201 */},
+{ "ip", /* 202 */},
+{ "nb", /* 203 */},
+{ "sb", /* 204 */},
+{ "ib", /* 205 */},
+{ "mo", /* 206 */},
+{ "nm", /* 207 */},
+{ "/_", /* 208 */},
+{ "gr", /* 209 */},
+{ "rg", /* 210 */},
+{ "co", /* 211 */},
+{ "tm", /* 212 */},
+{ 0, /* 213 */},
+{ "sr", "sqrt", /* 214 */},
+{ "md", /* 215 */},
+{ "no", /* 216 */},
+{ "AN", /* 217 */},
+{ "OR", /* 218 */},
+{ "hA", /* 219 */},
+{ "lA", /* 220 */},
+{ "uA", /* 221 */},
+{ "rA", /* 222 */},
+{ "dA", /* 223 */},
+{ "lz", /* 224 */},
+{ "la", /* 225 */},
+{ 0, /* 226 */},
+{ 0, /* 227 */},
+{ 0, /* 228 */},
+{ 0, /* 229 */},
+{ "parenlefttp", /* 230 */},
+{ "parenleftex", /* 231 */},
+{ "parenleftbt", /* 232 */},
+{ "bracketlefttp", "lc", /* 233 */},
+{ "bracketleftex", /* 234 */},
+{ "bracketleftbt", "lf", /* 235 */},
+{ "bracelefttp", "lt", /* 236 */},
+{ "braceleftmid", "lk", /* 237 */},
+{ "braceleftbt", "lb", /* 238 */},
+{ "bracerightex", "braceleftex", "braceex", "bv", /* 239 */},
+{ 0, /* 240 */},
+{ "ra", /* 241 */},
+{ "is", "integral", /* 242 */},
+{ 0, /* 243 */},
+{ 0, /* 244 */},
+{ 0, /* 245 */},
+{ "parenrighttp", /* 246 */},
+{ "parenrightex", /* 247 */},
+{ "parenrightbt", /* 248 */},
+{ "bracketrighttp", "rc", /* 249 */},
+{ "bracketrightex", /* 250 */},
+{ "bracketrightbt", "rf", /* 251 */},
+{ "bracerighttp", "rt", /* 252 */},
+{ "bracerightmid", "rk", /* 253 */},
+{ "bracerightbt", "rb", /* 254 */},
+{ 0, /* 255 */},
+},
+ {0} /* buckets */};
+
+
+static void
+load_standard_maps (void)
+{
+ standard_maps_loaded = 1;
+ DviRegisterMap (&ISO8859_1_map);
+ DviRegisterMap (&Adobe_Symbol_map);
+}
diff --git a/src/libs/libxutil/XFontName.c b/src/libs/libxutil/XFontName.c
new file mode 100644
index 0000000..81ccdaa
--- /dev/null
+++ b/src/libs/libxutil/XFontName.c
@@ -0,0 +1,260 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+/*
+ * XFontName.c
+ *
+ * build/parse X Font name strings
+ */
+
+#include <config.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xos.h>
+#include "XFontName.h"
+#include <ctype.h>
+
+static char *
+extractStringField (char *name, char *buffer, int size,
+ unsigned int *attrp, unsigned int bit)
+{
+ char *buf = buffer;
+
+ if (!*name)
+ return 0;
+ while (*name && *name != '-' && size > 0) {
+ *buf++ = *name++;
+ --size;
+ }
+ if (size <= 0)
+ return 0;
+ *buf = '\0';
+ if (buffer[0] != '*' || buffer[1] != '\0')
+ *attrp |= bit;
+ if (*name == '-')
+ return name+1;
+ return name;
+}
+
+static char *
+extractUnsignedField (char *name, unsigned int *result,
+ unsigned int *attrp, unsigned int bit)
+{
+ char buf[256];
+ char *c;
+ unsigned int i;
+
+ name = extractStringField (name, buf, sizeof (buf), attrp, bit);
+ if (!name)
+ return 0;
+ if (!(*attrp & bit))
+ return name;
+ i = 0;
+ for (c = buf; *c; c++) {
+ if (!isdigit (*c))
+ return 0;
+ i = i * 10 + (*c - '0');
+ }
+ *result = i;
+ return name;
+}
+
+Bool
+XParseFontName (XFontNameString fontNameString, XFontName *fontName,
+ unsigned int *fontNameAttributes)
+{
+ char *name = fontNameString;
+ XFontName temp;
+ unsigned int attributes = 0;
+
+#define GetString(field,bit)\
+ if (!(name = extractStringField \
+ (name, temp.field, sizeof (temp.field),\
+ &attributes, bit))) \
+ return False;
+
+#define GetUnsigned(field,bit)\
+ if (!(name = extractUnsignedField \
+ (name, &temp.field, \
+ &attributes, bit))) \
+ return False;
+
+ GetString (Registry, FontNameRegistry)
+ GetString (Foundry, FontNameFoundry)
+ GetString (FamilyName, FontNameFamilyName)
+ GetString (WeightName, FontNameWeightName)
+ GetString (Slant, FontNameSlant)
+ GetString (SetwidthName, FontNameSetwidthName)
+ GetString (AddStyleName, FontNameAddStyleName)
+ GetUnsigned (PixelSize, FontNamePixelSize)
+ GetUnsigned (PointSize, FontNamePointSize)
+ GetUnsigned (ResolutionX, FontNameResolutionX)
+ GetUnsigned (ResolutionY, FontNameResolutionY)
+ GetString (Spacing, FontNameSpacing)
+ GetUnsigned (AverageWidth, FontNameAverageWidth)
+ GetString (CharSetRegistry, FontNameCharSetRegistry)
+ if (!*name) {
+ temp.CharSetEncoding[0] = '\0';
+ attributes |= FontNameCharSetEncoding;
+ } else {
+ GetString (CharSetEncoding, FontNameCharSetEncoding)
+ }
+ *fontName = temp;
+ *fontNameAttributes = attributes;
+ return True;
+}
+
+static char *
+utoa (unsigned int u, char *s, int size)
+{
+ char *t;
+
+ t = s + size;
+ *--t = '\0';
+ do
+ *--t = (u % 10) + '0';
+ while (u /= 10);
+ return t;
+}
+
+Bool
+XFormatFontName (XFontName *fontName, unsigned int fontNameAttributes,
+ XFontNameString fontNameString)
+{
+ char tmp[256];
+ char *name = tmp, *f;
+ int left = sizeof (tmp) - 1;
+ char number[32];
+
+#define PutString(field, bit)\
+ f = (fontNameAttributes & bit) ? \
+ fontName->field \
+ : (char *)"*"; \
+ if ((left -= strlen (f)) < 0) \
+ return False; \
+ while (*f) \
+ if ((*name++ = *f++) == '-') \
+ return False;
+#define PutHyphen()\
+ if (--left < 0) \
+ return False; \
+ *name++ = '-';
+
+#define PutUnsigned(field, bit) \
+ f = (fontNameAttributes & bit) ? \
+ utoa (fontName->field, number, sizeof (number)) \
+ : (char *)"*"; \
+ if ((left -= strlen (f)) < 0) \
+ return False; \
+ while (*f) \
+ *name++ = *f++;
+
+ PutString (Registry, FontNameRegistry)
+ PutHyphen ();
+ PutString (Foundry, FontNameFoundry)
+ PutHyphen ();
+ PutString (FamilyName, FontNameFamilyName)
+ PutHyphen ();
+ PutString (WeightName, FontNameWeightName)
+ PutHyphen ();
+ PutString (Slant, FontNameSlant)
+ PutHyphen ();
+ PutString (SetwidthName, FontNameSetwidthName)
+ PutHyphen ();
+ PutString (AddStyleName, FontNameAddStyleName)
+ PutHyphen ();
+ PutUnsigned (PixelSize, FontNamePixelSize)
+ PutHyphen ();
+ PutUnsigned (PointSize, FontNamePointSize)
+ PutHyphen ();
+ PutUnsigned (ResolutionX, FontNameResolutionX)
+ PutHyphen ();
+ PutUnsigned (ResolutionY, FontNameResolutionY)
+ PutHyphen ();
+ PutString (Spacing, FontNameSpacing)
+ PutHyphen ();
+ PutUnsigned (AverageWidth, FontNameAverageWidth)
+ PutHyphen ();
+ PutString (CharSetRegistry, FontNameCharSetRegistry)
+ PutHyphen ();
+ PutString (CharSetEncoding, FontNameCharSetEncoding)
+ *name = '\0';
+ strcpy (fontNameString, tmp);
+ return True;
+}
+
+Bool
+XCompareFontName (XFontName *name1, XFontName *name2,
+ unsigned int fontNameAttributes)
+{
+#define CompareString(field,bit) \
+ if (fontNameAttributes & bit) \
+ if (strcmp (name1->field, name2->field)) \
+ return False;
+
+#define CompareUnsigned(field,bit) \
+ if (fontNameAttributes & bit) \
+ if (name1->field != name2->field) \
+ return False;
+
+ CompareString (Registry, FontNameRegistry)
+ CompareString (Foundry, FontNameFoundry)
+ CompareString (FamilyName, FontNameFamilyName)
+ CompareString (WeightName, FontNameWeightName)
+ CompareString (Slant, FontNameSlant)
+ CompareString (SetwidthName, FontNameSetwidthName)
+ CompareString (AddStyleName, FontNameAddStyleName)
+ CompareUnsigned (PixelSize, FontNamePixelSize)
+ CompareUnsigned (PointSize, FontNamePointSize)
+ CompareUnsigned (ResolutionX, FontNameResolutionX)
+ CompareUnsigned (ResolutionY, FontNameResolutionY)
+ CompareString (Spacing, FontNameSpacing)
+ CompareUnsigned (AverageWidth, FontNameAverageWidth)
+ CompareString (CharSetRegistry, FontNameCharSetRegistry)
+ CompareString (CharSetEncoding, FontNameCharSetEncoding)
+ return True;
+}
+
+Bool
+XCopyFontName (XFontName *name1, XFontName *name2,
+ unsigned int fontNameAttributes)
+{
+#define CopyString(field,bit) \
+ if (fontNameAttributes & bit) \
+ strcpy (name2->field, name1->field);
+
+#define CopyUnsigned(field,bit) \
+ if (fontNameAttributes & bit) \
+ name2->field = name1->field;
+
+ CopyString (Registry, FontNameRegistry)
+ CopyString (Foundry, FontNameFoundry)
+ CopyString (FamilyName, FontNameFamilyName)
+ CopyString (WeightName, FontNameWeightName)
+ CopyString (Slant, FontNameSlant)
+ CopyString (SetwidthName, FontNameSetwidthName)
+ CopyString (AddStyleName, FontNameAddStyleName)
+ CopyUnsigned (PixelSize, FontNamePixelSize)
+ CopyUnsigned (PointSize, FontNamePointSize)
+ CopyUnsigned (ResolutionX, FontNameResolutionX)
+ CopyUnsigned (ResolutionY, FontNameResolutionY)
+ CopyString (Spacing, FontNameSpacing)
+ CopyUnsigned (AverageWidth, FontNameAverageWidth)
+ CopyString (CharSetRegistry, FontNameCharSetRegistry)
+ CopyString (CharSetEncoding, FontNameCharSetEncoding)
+ return True;
+}
diff --git a/src/libs/libxutil/libxutil.am b/src/libs/libxutil/libxutil.am
new file mode 100644
index 0000000..5963530
--- /dev/null
+++ b/src/libs/libxutil/libxutil.am
@@ -0,0 +1,35 @@
+# Automake rules for 'libxutil'
+#
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/gpl-2.0.html>.
+#
+########################################################################
+
+if !WITHOUT_X11
+noinst_LIBRARIES += libxutil.a
+libxutil_a_CPPFLAGS = $(AM_CPPFLAGS) $(X_CFLAGS)
+libxutil_a_SOURCES = \
+ src/libs/libxutil/DviChar.c \
+ src/libs/libxutil/XFontName.c \
+ src/libs/libxutil/xmalloc.c
+endif
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/libs/libxutil/xmalloc.c b/src/libs/libxutil/xmalloc.c
new file mode 100644
index 0000000..0852c62
--- /dev/null
+++ b/src/libs/libxutil/xmalloc.c
@@ -0,0 +1,28 @@
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+The GNU General Public License version 2 (GPL2) is available in the
+internet at <http://www.gnu.org/licenses/gpl-2.0.txt>. */
+
+#include <config.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+
+char *xmalloc(int n);
+
+char *xmalloc(int n)
+{
+ return XtMalloc(n);
+}
diff --git a/src/preproc/eqn/TODO b/src/preproc/eqn/TODO
new file mode 100644
index 0000000..2a22183
--- /dev/null
+++ b/src/preproc/eqn/TODO
@@ -0,0 +1,49 @@
+Use the same size increases for sum prod int as eqn does.
+
+Perhaps chartype should be renamed.
+
+TeX makes {sub,super}script on a single character with an accent
+into an accent onto the (character with the script). Should we do this?
+
+Implement mark and lineups within scripts, matrices and piles, and accents.
+(Why would this be useful?)
+
+Perhaps push hmotions down through lists to avoid upsetting spacing
+adjustments.
+
+Possibly generate .lf commands during compute_metrics phase.
+
+Consider whether there should be extra space at the side of piles.
+
+Provide scriptstyle displaystyle etc.
+
+Provide a nicer matrix syntax, e.g.,
+matrix ccc {
+a then b then c above
+e then f then g above
+h then i then k
+}
+
+Perhaps generate syntax error messages using the style of gpic.
+
+Wide accents.
+
+More use of \Z.
+
+Extensible square roots.
+
+Vphantom
+
+Smash.
+
+Provide a variant of vec that extends over the length of the accentee.
+
+Support vertical arrow delimiters.
+
+Make the following work:
+.EQ
+delim @@
+.EN
+.EQ @<-@
+some equation
+.EN
diff --git a/src/preproc/eqn/box.cpp b/src/preproc/eqn/box.cpp
new file mode 100644
index 0000000..5f6343e
--- /dev/null
+++ b/src/preproc/eqn/box.cpp
@@ -0,0 +1,651 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+const char *current_roman_font;
+
+char *gfont = 0;
+char *grfont = 0;
+char *gbfont = 0;
+int gsize = 0;
+
+int script_size_reduction = -1; // negative means reduce by a percentage
+
+int positive_space = -1;
+int negative_space = -1;
+
+int minimum_size = 5;
+
+int fat_offset = 4;
+int body_height = 85;
+int body_depth = 35;
+
+int over_hang = 0;
+int accent_width = 31;
+int delimiter_factor = 900;
+int delimiter_shortfall = 50;
+
+int null_delimiter_space = 12;
+int script_space = 5;
+int thin_space = 17;
+int medium_space = 22;
+int thick_space = 28;
+
+int num1 = 70;
+int num2 = 40;
+// we don't use num3, because we don't have \atop
+int denom1 = 70;
+int denom2 = 36;
+int axis_height = 26; // in 100ths of an em
+int sup1 = 42;
+int sup2 = 37;
+int sup3 = 28;
+int default_rule_thickness = 4;
+int sub1 = 20;
+int sub2 = 23;
+int sup_drop = 38;
+int sub_drop = 5;
+int x_height = 45;
+int big_op_spacing1 = 11;
+int big_op_spacing2 = 17;
+int big_op_spacing3 = 20;
+int big_op_spacing4 = 60;
+int big_op_spacing5 = 10;
+
+// These are for piles and matrices.
+
+int baseline_sep = 140; // = num1 + denom1
+int shift_down = 26; // = axis_height
+int column_sep = 100; // = em space
+int matrix_side_sep = 17; // = thin space
+
+int nroff = 0; // should we grok ndefine or tdefine?
+
+struct S {
+ const char *name;
+ int *ptr;
+} param_table[] = {
+ { "fat_offset", &fat_offset },
+ { "over_hang", &over_hang },
+ { "accent_width", &accent_width },
+ { "delimiter_factor", &delimiter_factor },
+ { "delimiter_shortfall", &delimiter_shortfall },
+ { "null_delimiter_space", &null_delimiter_space },
+ { "script_space", &script_space },
+ { "thin_space", &thin_space },
+ { "medium_space", &medium_space },
+ { "thick_space", &thick_space },
+ { "num1", &num1 },
+ { "num2", &num2 },
+ { "denom1", &denom1 },
+ { "denom2", &denom2 },
+ { "axis_height", &axis_height },
+ { "sup1", &sup1 },
+ { "sup2", &sup2 },
+ { "sup3", &sup3 },
+ { "default_rule_thickness", &default_rule_thickness },
+ { "sub1", &sub1 },
+ { "sub2", &sub2 },
+ { "sup_drop", &sup_drop },
+ { "sub_drop", &sub_drop },
+ { "x_height", &x_height },
+ { "big_op_spacing1", &big_op_spacing1 },
+ { "big_op_spacing2", &big_op_spacing2 },
+ { "big_op_spacing3", &big_op_spacing3 },
+ { "big_op_spacing4", &big_op_spacing4 },
+ { "big_op_spacing5", &big_op_spacing5 },
+ { "minimum_size", &minimum_size },
+ { "baseline_sep", &baseline_sep },
+ { "shift_down", &shift_down },
+ { "column_sep", &column_sep },
+ { "matrix_side_sep", &matrix_side_sep },
+ { "draw_lines", &draw_flag },
+ { "body_height", &body_height },
+ { "body_depth", &body_depth },
+ { "nroff", &nroff },
+ { 0, 0 }
+};
+
+void set_param(const char *name, int value)
+{
+ for (int i = 0; param_table[i].name != 0; i++)
+ if (strcmp(param_table[i].name, name) == 0) {
+ *param_table[i].ptr = value;
+ return;
+ }
+ error("unrecognised parameter '%1'", name);
+}
+
+int script_style(int style)
+{
+ return style > SCRIPT_STYLE ? style - 2 : style;
+}
+
+int cramped_style(int style)
+{
+ return (style & 1) ? style - 1 : style;
+}
+
+void set_space(int n)
+{
+ if (n < 0)
+ negative_space = -n;
+ else
+ positive_space = n;
+}
+
+// Return 0 if the specified size is bad.
+// The caller is responsible for giving the error message.
+
+int set_gsize(const char *s)
+{
+ const char *p = (*s == '+' || *s == '-') ? s + 1 : s;
+ char *end;
+ long n = strtol(p, &end, 10);
+ if (n <= 0 || *end != '\0' || n > INT_MAX)
+ return 0;
+ if (p > s) {
+ if (!gsize)
+ gsize = 10;
+ if (*s == '+') {
+ if (gsize > INT_MAX - n)
+ return 0;
+ gsize += int(n);
+ }
+ else {
+ if (gsize - n <= 0)
+ return 0;
+ gsize -= int(n);
+ }
+ }
+ else
+ gsize = int(n);
+ return 1;
+}
+
+void set_script_reduction(int n)
+{
+ script_size_reduction = n;
+}
+
+const char *get_gfont()
+{
+ return gfont ? gfont : "I";
+}
+
+const char *get_grfont()
+{
+ return grfont ? grfont : "R";
+}
+
+const char *get_gbfont()
+{
+ return gbfont ? gbfont : "B";
+}
+
+void set_gfont(const char *s)
+{
+ delete[] gfont;
+ gfont = strsave(s);
+}
+
+void set_grfont(const char *s)
+{
+ delete[] grfont;
+ grfont = strsave(s);
+}
+
+void set_gbfont(const char *s)
+{
+ delete[] gbfont;
+ gbfont = strsave(s);
+}
+
+// this must be precisely 2 characters in length
+#define COMPATIBLE_REG "0C"
+
+void start_string()
+{
+ if (output_format == troff) {
+ printf(".nr " COMPATIBLE_REG " \\n(.C\n");
+ printf(".cp 0\n");
+ printf(".ds " LINE_STRING "\n");
+ }
+}
+
+void output_string()
+{
+ if (output_format == troff)
+ printf("\\*(" LINE_STRING "\n");
+ else if (output_format == mathml && !xhtml)
+ putchar('\n');
+}
+
+void restore_compatibility()
+{
+ if (output_format == troff)
+ printf(".cp \\n(" COMPATIBLE_REG "\n");
+}
+
+void do_text(const char *s)
+{
+ if (output_format == troff) {
+ printf(".eo\n");
+ printf(".as " LINE_STRING " \"%s\n", s);
+ printf(".ec\n");
+ }
+ else if (output_format == mathml) {
+ fputs(s, stdout);
+ if (xhtml && strlen(s) > 0)
+ printf("\n");
+ }
+}
+
+void set_minimum_size(int n)
+{
+ minimum_size = n;
+}
+
+void set_script_size()
+{
+ if (minimum_size < 0)
+ minimum_size = 0;
+ if (script_size_reduction >= 0)
+ printf(".ps \\n[.s]-%d>?%d\n", script_size_reduction, minimum_size);
+ else
+ printf(".ps (u;\\n[.ps]*7+5/10>?%dz)\n", minimum_size);
+}
+
+int box::next_uid = 0;
+
+box::box() : spacing_type(ORDINARY_TYPE), uid(next_uid++)
+{
+}
+
+box::~box()
+{
+}
+
+void box::top_level()
+{
+ box *b = this;
+ if (output_format == troff) {
+ // debug_print();
+ // putc('\n', stderr);
+ printf(".nr " SAVED_FONT_REG " \\n[.f]\n");
+ printf(".ft\n");
+ printf(".nr " SAVED_PREV_FONT_REG " \\n[.f]\n");
+ printf(".ft %s\n", get_gfont());
+ printf(".nr " SAVED_SIZE_REG " \\n[.ps]\n");
+ if (gsize > 0) {
+ char buf[INT_DIGITS + 1];
+ sprintf(buf, "%d", gsize);
+ b = new size_box(strsave(buf), b);
+ }
+ current_roman_font = get_grfont();
+ // This catches tabs used within \Z (which aren't allowed).
+ b->check_tabs(0);
+ int r = b->compute_metrics(DISPLAY_STYLE);
+ printf(".ft \\n[" SAVED_PREV_FONT_REG "]\n");
+ printf(".ft \\n[" SAVED_FONT_REG "]\n");
+ printf(".nr " MARK_OR_LINEUP_FLAG_REG " %d\n", r);
+ if (r == FOUND_MARK) {
+ printf(".nr " SAVED_MARK_REG " \\n[" MARK_REG "]\n");
+ printf(".nr " MARK_WIDTH_REG " 0\\n[" WIDTH_FORMAT "]\n", b->uid);
+ }
+ else if (r == FOUND_LINEUP)
+ printf(".if r" SAVED_MARK_REG " .as1 " LINE_STRING " \\h'\\n["
+ SAVED_MARK_REG "]u-\\n[" MARK_REG "]u'\n");
+ else
+ assert(r == FOUND_NOTHING);
+ // If we use \R directly, the space will prevent it working in a
+ // macro argument; so we hide it in a string instead.
+ printf(".ds " SAVE_FONT_STRING " "
+ "\\R'" SAVED_INLINE_FONT_REG " \\En[.f]'"
+ "\\fP"
+ "\\R'" SAVED_INLINE_PREV_FONT_REG " \\En[.f]'"
+ "\\R'" SAVED_INLINE_SIZE_REG " \\En[.ps]'"
+ "\\s0"
+ "\\R'" SAVED_INLINE_PREV_SIZE_REG " \\En[.ps]'"
+ "\n"
+ ".ds " RESTORE_FONT_STRING " "
+ "\\f[\\En[" SAVED_INLINE_PREV_FONT_REG "]]"
+ "\\f[\\En[" SAVED_INLINE_FONT_REG "]]"
+ "\\s'\\En[" SAVED_INLINE_PREV_SIZE_REG "]u'"
+ "\\s'\\En[" SAVED_INLINE_SIZE_REG "]u'"
+ "\n");
+ printf(".as1 " LINE_STRING " \\&\\E*[" SAVE_FONT_STRING "]");
+ printf("\\f[%s]", get_gfont());
+ printf("\\s'\\En[" SAVED_SIZE_REG "]u'");
+ current_roman_font = get_grfont();
+ b->output();
+ printf("\\E*[" RESTORE_FONT_STRING "]\n");
+ if (r == FOUND_LINEUP)
+ printf(".if r" SAVED_MARK_REG " .as1 " LINE_STRING " \\h'\\n["
+ MARK_WIDTH_REG "]u-\\n[" SAVED_MARK_REG "]u-(\\n["
+ WIDTH_FORMAT "]u-\\n[" MARK_REG "]u)'\n",
+ b->uid);
+ b->extra_space();
+ if (!inline_flag)
+ printf(".ne \\n[" HEIGHT_FORMAT "]u-%dM>?0+(\\n["
+ DEPTH_FORMAT "]u-%dM>?0)\n",
+ b->uid, body_height, b->uid, body_depth);
+ }
+ else if (output_format == mathml) {
+ if (xhtml)
+ printf(".MATHML ");
+ printf("<math>");
+ b->output();
+ printf("</math>");
+ }
+ delete b;
+ next_uid = 0;
+}
+
+// gpic defines this register so as to make geqn not produce '\x's
+#define EQN_NO_EXTRA_SPACE_REG "0x"
+
+void box::extra_space()
+{
+ printf(".if !r" EQN_NO_EXTRA_SPACE_REG " "
+ ".nr " EQN_NO_EXTRA_SPACE_REG " 0\n");
+ if (positive_space >= 0 || negative_space >= 0) {
+ if (positive_space > 0)
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".as1 " LINE_STRING " \\x'-%dM'\n", positive_space);
+ if (negative_space > 0)
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".as1 " LINE_STRING " \\x'%dM'\n", negative_space);
+ positive_space = negative_space = -1;
+ }
+ else {
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".if \\n[" HEIGHT_FORMAT "]>%dM .as1 " LINE_STRING
+ " \\x'-(\\n[" HEIGHT_FORMAT
+ "]u-%dM)'\n",
+ uid, body_height, uid, body_height);
+ printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
+ ".if \\n[" DEPTH_FORMAT "]>%dM .as1 " LINE_STRING
+ " \\x'\\n[" DEPTH_FORMAT
+ "]u-%dM'\n",
+ uid, body_depth, uid, body_depth);
+ }
+}
+
+int box::compute_metrics(int)
+{
+ printf(".nr " WIDTH_FORMAT " 0\n", uid);
+ printf(".nr " HEIGHT_FORMAT " 0\n", uid);
+ printf(".nr " DEPTH_FORMAT " 0\n", uid);
+ return FOUND_NOTHING;
+}
+
+void box::compute_subscript_kern()
+{
+ printf(".nr " SUB_KERN_FORMAT " 0\n", uid);
+}
+
+void box::compute_skew()
+{
+ printf(".nr " SKEW_FORMAT " 0\n", uid);
+}
+
+void box::output()
+{
+}
+
+void box::check_tabs(int)
+{
+}
+
+int box::is_char()
+{
+ return 0;
+}
+
+int box::left_is_italic()
+{
+ return 0;
+}
+
+int box::right_is_italic()
+{
+ return 0;
+}
+
+void box::hint(unsigned)
+{
+}
+
+void box::handle_char_type(int, int)
+{
+}
+
+
+box_list::box_list(box *pp)
+{
+ p = new box*[10];
+ for (int i = 0; i < 10; i++)
+ p[i] = 0;
+ maxlen = 10;
+ len = 1;
+ p[0] = pp;
+}
+
+void box_list::append(box *pp)
+{
+ if (len + 1 > maxlen) {
+ box **oldp = p;
+ maxlen *= 2;
+ p = new box*[maxlen];
+ memcpy(p, oldp, sizeof(box*)*len);
+ delete[] oldp;
+ }
+ p[len++] = pp;
+}
+
+box_list::~box_list()
+{
+ for (int i = 0; i < len; i++)
+ delete p[i];
+ delete[] p;
+}
+
+void box_list::list_check_tabs(int level)
+{
+ for (int i = 0; i < len; i++)
+ p[i]->check_tabs(level);
+}
+
+
+pointer_box::pointer_box(box *pp) : p(pp)
+{
+ spacing_type = p->spacing_type;
+}
+
+pointer_box::~pointer_box()
+{
+ delete p;
+}
+
+int pointer_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void pointer_box::compute_subscript_kern()
+{
+ p->compute_subscript_kern();
+ printf(".nr " SUB_KERN_FORMAT " \\n[" SUB_KERN_FORMAT "]\n", uid, p->uid);
+}
+
+void pointer_box::compute_skew()
+{
+ p->compute_skew();
+ printf(".nr " SKEW_FORMAT " 0\\n[" SKEW_FORMAT "]\n",
+ uid, p->uid);
+}
+
+void pointer_box::check_tabs(int level)
+{
+ p->check_tabs(level);
+}
+
+int simple_box::compute_metrics(int)
+{
+ printf(".nr " WIDTH_FORMAT " 0\\w" DELIMITER_CHAR, uid);
+ output();
+ printf(DELIMITER_CHAR "\n");
+ printf(".nr " HEIGHT_FORMAT " 0>?\\n[rst]\n", uid);
+ printf(".nr " DEPTH_FORMAT " 0-\\n[rsb]>?0\n", uid);
+ printf(".nr " SUB_KERN_FORMAT " 0-\\n[ssc]>?0\n", uid);
+ printf(".nr " SKEW_FORMAT " 0\\n[skw]\n", uid);
+ return FOUND_NOTHING;
+}
+
+void simple_box::compute_subscript_kern()
+{
+ // do nothing, we already computed it in do_metrics
+}
+
+void simple_box::compute_skew()
+{
+ // do nothing, we already computed it in do_metrics
+}
+
+int box::is_simple()
+{
+ return 0;
+}
+
+int simple_box::is_simple()
+{
+ return 1;
+}
+
+quoted_text_box::quoted_text_box(char *s) : text(s)
+{
+}
+
+quoted_text_box::~quoted_text_box()
+{
+ free(text);
+}
+
+void quoted_text_box::output()
+{
+ if (text) {
+ if (output_format == troff)
+ fputs(text, stdout);
+ else if (output_format == mathml) {
+ fputs("<mtext>", stdout);
+ fputs(text, stdout);
+ fputs("</mtext>", stdout);
+ }
+ }
+}
+
+tab_box::tab_box() : disabled(0)
+{
+}
+
+// We treat a tab_box as having width 0 for width computations.
+
+void tab_box::output()
+{
+ if (!disabled)
+ printf("\\t");
+}
+
+void tab_box::check_tabs(int level)
+{
+ if (level > 0) {
+ error("tabs allowed only at outermost level");
+ disabled = 1;
+ }
+}
+
+space_box::space_box()
+{
+ spacing_type = SUPPRESS_TYPE;
+}
+
+void space_box::output()
+{
+ if (output_format == troff)
+ printf("\\h'%dM'", thick_space);
+ else if (output_format == mathml)
+ // &ThickSpace; doesn't display right under Firefox 1.5.
+ printf("<mtext>&ensp;</mtext>");
+}
+
+half_space_box::half_space_box()
+{
+ spacing_type = SUPPRESS_TYPE;
+}
+
+void half_space_box::output()
+{
+ if (output_format == troff)
+ printf("\\h'%dM'", thin_space);
+ else if (output_format == mathml)
+ printf("<mtext>&ThinSpace;</mtext>");
+}
+
+void box_list::list_debug_print(const char *sep)
+{
+ p[0]->debug_print();
+ for (int i = 1; i < len; i++) {
+ fprintf(stderr, "%s", sep);
+ p[i]->debug_print();
+ }
+}
+
+void quoted_text_box::debug_print()
+{
+ fprintf(stderr, "\"%s\"", (text ? text : ""));
+}
+
+void half_space_box::debug_print()
+{
+ fprintf(stderr, "^");
+}
+
+void space_box::debug_print()
+{
+ fprintf(stderr, "~");
+}
+
+void tab_box::debug_print()
+{
+ fprintf(stderr, "<tab>");
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/box.h b/src/preproc/eqn/box.h
new file mode 100644
index 0000000..981b464
--- /dev/null
+++ b/src/preproc/eqn/box.h
@@ -0,0 +1,278 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class list_box;
+
+class box {
+private:
+ static int next_uid;
+public:
+ int spacing_type;
+ const int uid;
+ box();
+ virtual void debug_print() = 0;
+ virtual ~box();
+ void top_level();
+ virtual int compute_metrics(int);
+ virtual void compute_subscript_kern();
+ virtual void compute_skew();
+ virtual void output();
+ void extra_space();
+ virtual list_box *to_list_box();
+ virtual int is_simple();
+ virtual int is_char();
+ virtual int left_is_italic();
+ virtual int right_is_italic();
+ virtual void handle_char_type(int, int);
+ enum { FOUND_NOTHING = 0, FOUND_MARK = 1, FOUND_LINEUP = 2 };
+ void set_spacing_type(char *type);
+ virtual void hint(unsigned);
+ virtual void check_tabs(int);
+};
+
+class box_list {
+private:
+ int maxlen;
+public:
+ box **p;
+ int len;
+
+ box_list(box *);
+ ~box_list();
+ void append(box *);
+ void list_check_tabs(int);
+ void list_debug_print(const char *sep);
+ friend class list_box;
+};
+
+// declarations to avoid friend name injection problems
+box *make_script_box(box *, box *, box *);
+box *make_mark_box(box *);
+box *make_lineup_box(box *);
+
+class list_box : public box {
+ int is_script;
+ box_list list;
+ int sty;
+public:
+ list_box(box *);
+ void debug_print();
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void output();
+ void check_tabs(int);
+ void append(box *);
+ list_box *to_list_box();
+ void handle_char_type(int, int);
+ void compute_sublist_width(int n);
+ friend box *make_script_box(box *, box *, box *);
+ friend box *make_mark_box(box *);
+ friend box *make_lineup_box(box *);
+};
+
+enum alignment { LEFT_ALIGN, RIGHT_ALIGN, CENTER_ALIGN };
+
+class column : public box_list {
+ alignment align;
+ int space;
+public:
+ column(box *);
+ void set_alignment(alignment);
+ void set_space(int);
+ void debug_print(const char *);
+
+ friend class matrix_box;
+ friend class pile_box;
+};
+
+class pile_box : public box {
+ column col;
+public:
+ pile_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+ void set_alignment(alignment a) { col.set_alignment(a); }
+ void set_space(int n) { col.set_space(n); }
+ void append(box *p) { col.append(p); }
+};
+
+class matrix_box : public box {
+private:
+ int len;
+ int maxlen;
+ column **p;
+public:
+ matrix_box(column *);
+ ~matrix_box();
+ void append(column *);
+ int compute_metrics(int);
+ void output();
+ void check_tabs(int);
+ void debug_print();
+};
+
+class pointer_box : public box {
+protected:
+ box *p;
+public:
+ pointer_box(box *);
+ ~pointer_box();
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void compute_skew();
+ void debug_print() = 0;
+ void check_tabs(int);
+};
+
+class vcenter_box : public pointer_box {
+public:
+ vcenter_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class simple_box : public box {
+public:
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void compute_skew();
+ void output() = 0;
+ void debug_print() = 0;
+ int is_simple();
+};
+
+class quoted_text_box : public simple_box {
+ char *text;
+public:
+ quoted_text_box(char *);
+ ~quoted_text_box();
+ void debug_print();
+ void output();
+};
+
+class half_space_box : public simple_box {
+public:
+ half_space_box();
+ void output();
+ void debug_print();
+};
+
+class space_box : public simple_box {
+public:
+ space_box();
+ void output();
+ void debug_print();
+};
+
+class tab_box : public box {
+ int disabled;
+public:
+ tab_box();
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+class size_box : public pointer_box {
+private:
+ char *size;
+public:
+ size_box(char *, box *);
+ ~size_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class font_box : public pointer_box {
+private:
+ char *f;
+public:
+ font_box(char *, box *);
+ ~font_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class fat_box : public pointer_box {
+public:
+ fat_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class vmotion_box : public pointer_box {
+private:
+ int n; // up is >= 0
+public:
+ vmotion_box(int, box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+class hmotion_box : public pointer_box {
+ int n;
+public:
+ hmotion_box(int, box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+box *split_text(char *);
+box *make_delim_box(char *, box *, char *);
+box *make_sqrt_box(box *);
+box *make_prime_box(box *);
+box *make_over_box(box *, box *);
+box *make_small_over_box(box *, box *);
+box *make_limit_box(box *, box *, box *);
+box *make_accent_box(box *, box *);
+box *make_uaccent_box(box *, box *);
+box *make_overline_box(box *);
+box *make_underline_box(box *);
+box *make_special_box(char *, box *);
+
+void set_space(int);
+int set_gsize(const char *);
+void set_gfont(const char *);
+void set_grfont(const char *);
+void set_gbfont(const char *);
+const char *get_gfont();
+const char *get_grfont();
+const char *get_gbfont();
+void start_string();
+void output_string();
+void do_text(const char *);
+void restore_compatibility();
+void set_script_reduction(int n);
+void set_minimum_size(int n);
+void set_param(const char *name, int value);
+
+void set_char_type(const char *type, char *ch);
+
+void init_char_table();
+void init_extensible();
+void define_extensible(const char *name, const char *ext, const char *top = 0,
+ const char *mid = 0, const char *bot = 0);
diff --git a/src/preproc/eqn/delim.cpp b/src/preproc/eqn/delim.cpp
new file mode 100644
index 0000000..01557c5
--- /dev/null
+++ b/src/preproc/eqn/delim.cpp
@@ -0,0 +1,418 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+enum left_or_right_t { LEFT_DELIM = 01, RIGHT_DELIM = 02 };
+
+// Small must be none-zero and must exist in each device.
+// Small will be put in the roman font, others are assumed to be
+// on the special font (so no font change will be necessary.)
+
+struct delimiter {
+ const char *name;
+ int flags;
+ const char *small;
+ const char *chain_format;
+ const char *ext;
+ const char *top;
+ const char *mid;
+ const char *bot;
+} delim_table[] = {
+ {
+ "(", LEFT_DELIM|RIGHT_DELIM, "(", "\\[parenleft%s]",
+ "\\[parenleftex]",
+ "\\[parenlefttp]",
+ 0,
+ "\\[parenleftbt]",
+ },
+ {
+ ")", LEFT_DELIM|RIGHT_DELIM, ")", "\\[parenright%s]",
+ "\\[parenrightex]",
+ "\\[parenrighttp]",
+ 0,
+ "\\[parenrightbt]",
+ },
+ {
+ "[", LEFT_DELIM|RIGHT_DELIM, "[", "\\[bracketleft%s]",
+ "\\[bracketleftex]",
+ "\\[bracketlefttp]",
+ 0,
+ "\\[bracketleftbt]",
+ },
+ {
+ "]", LEFT_DELIM|RIGHT_DELIM, "]", "\\[bracketright%s]",
+ "\\[bracketrightex]",
+ "\\[bracketrighttp]",
+ 0,
+ "\\[bracketrightbt]",
+ },
+ {
+ "{", LEFT_DELIM|RIGHT_DELIM, "{", "\\[braceleft%s]",
+ "\\[braceleftex]",
+ "\\[bracelefttp]",
+ "\\[braceleftmid]",
+ "\\[braceleftbt]",
+ },
+ {
+ "}", LEFT_DELIM|RIGHT_DELIM, "}", "\\[braceright%s]",
+ "\\[bracerightex]",
+ "\\[bracerighttp]",
+ "\\[bracerightmid]",
+ "\\[bracerightbt]",
+ },
+ {
+ "|", LEFT_DELIM|RIGHT_DELIM, "|", "\\[bar%s]",
+ "\\[barex]",
+ 0,
+ 0,
+ 0,
+ },
+ {
+ "floor", LEFT_DELIM, "\\(lf", "\\[floorleft%s]",
+ "\\[bracketleftex]",
+ 0,
+ 0,
+ "\\[bracketleftbt]",
+ },
+ {
+ "floor", RIGHT_DELIM, "\\(rf", "\\[floorright%s]",
+ "\\[bracketrightex]",
+ 0,
+ 0,
+ "\\[bracketrightbt]",
+ },
+ {
+ "ceiling", LEFT_DELIM, "\\(lc", "\\[ceilingleft%s]",
+ "\\[bracketleftex]",
+ "\\[bracketlefttp]",
+ 0,
+ 0,
+ },
+ {
+ "ceiling", RIGHT_DELIM, "\\(rc", "\\[ceilingright%s]",
+ "\\[bracketrightex]",
+ "\\[bracketrighttp]",
+ 0,
+ 0,
+ },
+ {
+ "||", LEFT_DELIM|RIGHT_DELIM, "|", "\\[bar%s]",
+ "\\[bardblex]",
+ 0,
+ 0,
+ 0,
+ },
+ {
+ "<", LEFT_DELIM|RIGHT_DELIM, "\\(la", "\\[angleleft%s]",
+ 0,
+ 0,
+ 0,
+ 0,
+ },
+ {
+ ">", LEFT_DELIM|RIGHT_DELIM, "\\(ra", "\\[angleright%s]",
+ 0,
+ 0,
+ 0,
+ 0,
+ },
+ {
+ "uparrow", LEFT_DELIM|RIGHT_DELIM, "\\(ua", "\\[arrowup%s]",
+ "\\[arrowvertex]",
+ "\\[arrowverttp]",
+ 0,
+ 0,
+ },
+ {
+ "downarrow", LEFT_DELIM|RIGHT_DELIM, "\\(da", "\\[arrowdown%s]",
+ "\\[arrowvertex]",
+ 0,
+ 0,
+ "\\[arrowvertbt]",
+ },
+ {
+ "updownarrow", LEFT_DELIM|RIGHT_DELIM, "\\(va", "\\[arrowupdown%s]",
+ "\\[arrowvertex]",
+ "\\[arrowverttp]",
+ 0,
+ "\\[arrowvertbt]",
+ },
+};
+
+const int DELIM_TABLE_SIZE = int(sizeof(delim_table)/sizeof(delim_table[0]));
+
+class delim_box : public box {
+private:
+ char *left;
+ char *right;
+ box *p;
+public:
+ delim_box(char *, box *, char *);
+ ~delim_box();
+ int compute_metrics(int);
+ void output();
+ void check_tabs(int);
+ void debug_print();
+};
+
+box *make_delim_box(char *l, box *pp, char *r)
+{
+ if (l != 0 && *l == '\0') {
+ delete[] l;
+ l = 0;
+ }
+ if (r != 0 && *r == '\0') {
+ delete[] r;
+ r = 0;
+ }
+ return new delim_box(l, pp, r);
+}
+
+delim_box::delim_box(char *l, box *pp, char *r)
+: left(l), right(r), p(pp)
+{
+}
+
+delim_box::~delim_box()
+{
+ delete[] left;
+ delete[] right;
+ delete p;
+}
+
+static void build_extensible(const char *ext, const char *top, const char *mid,
+ const char *bot)
+{
+ assert(ext != 0);
+ printf(".nr " DELIM_WIDTH_REG " 0\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ ext);
+ printf(".nr " EXT_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " EXT_DEPTH_REG " 0-\\n[rsb]\n");
+ if (top) {
+ printf(".nr " DELIM_WIDTH_REG " 0\\n[" DELIM_WIDTH_REG "]"
+ ">?\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ top);
+ printf(".nr " TOP_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " TOP_DEPTH_REG " 0-\\n[rsb]\n");
+ }
+ if (mid) {
+ printf(".nr " DELIM_WIDTH_REG " 0\\n[" DELIM_WIDTH_REG "]"
+ ">?\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ mid);
+ printf(".nr " MID_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " MID_DEPTH_REG " 0-\\n[rsb]\n");
+ }
+ if (bot) {
+ printf(".nr " DELIM_WIDTH_REG " 0\\n[" DELIM_WIDTH_REG "]"
+ ">?\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n",
+ bot);
+ printf(".nr " BOT_HEIGHT_REG " 0\\n[rst]\n");
+ printf(".nr " BOT_DEPTH_REG " 0-\\n[rsb]\n");
+ }
+ printf(".nr " TOTAL_HEIGHT_REG " 0");
+ if (top)
+ printf("+\\n[" TOP_HEIGHT_REG "]+\\n[" TOP_DEPTH_REG "]");
+ if (bot)
+ printf("+\\n[" BOT_HEIGHT_REG "]+\\n[" BOT_DEPTH_REG "]");
+ if (mid)
+ printf("+\\n[" MID_HEIGHT_REG "]+\\n[" MID_DEPTH_REG "]");
+ printf("\n");
+ // determine how many extensible characters we need
+ printf(".nr " TEMP_REG " \\n[" DELTA_REG "]-\\n[" TOTAL_HEIGHT_REG "]");
+ if (mid)
+ printf("/2");
+ printf(">?0+\\n[" EXT_HEIGHT_REG "]+\\n[" EXT_DEPTH_REG "]-1/(\\n["
+ EXT_HEIGHT_REG "]+\\n[" EXT_DEPTH_REG "])\n");
+
+ printf(".nr " TOTAL_HEIGHT_REG " +(\\n[" EXT_HEIGHT_REG "]+\\n["
+ EXT_DEPTH_REG "]*\\n[" TEMP_REG "]");
+ if (mid)
+ printf("*2");
+ printf(")\n");
+ printf(".ds " DELIM_STRING " \\Z" DELIMITER_CHAR
+ "\\v'-%dM-(\\n[" TOTAL_HEIGHT_REG "]u/2u)'\n",
+ axis_height);
+ if (top)
+ printf(".as " DELIM_STRING " \\v'\\n[" TOP_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" TOP_DEPTH_REG "]u'\n",
+ top);
+
+ // this macro appends $2 copies of $3 to string $1
+ printf(".de " REPEAT_APPEND_STRING_MACRO "\n"
+ ".if \\\\$2 \\{.as \\\\$1 \"\\\\$3\n"
+ "." REPEAT_APPEND_STRING_MACRO " \\\\$1 \\\\$2-1 \"\\\\$3\"\n"
+ ".\\}\n"
+ "..\n");
+
+ printf("." REPEAT_APPEND_STRING_MACRO " " DELIM_STRING " \\n[" TEMP_REG "] "
+ "\\v'\\n[" EXT_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" EXT_DEPTH_REG "]u'\n",
+ ext);
+
+ if (mid) {
+ printf(".as " DELIM_STRING " \\v'\\n[" MID_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" MID_DEPTH_REG "]u'\n",
+ mid);
+ printf("." REPEAT_APPEND_STRING_MACRO " " DELIM_STRING
+ " \\n[" TEMP_REG "] "
+ "\\v'\\n[" EXT_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" EXT_DEPTH_REG "]u'\n",
+ ext);
+ }
+ if (bot)
+ printf(".as " DELIM_STRING " \\v'\\n[" BOT_HEIGHT_REG "]u'"
+ "\\Z" DELIMITER_CHAR "%s" DELIMITER_CHAR
+ "\\v'\\n[" BOT_DEPTH_REG "]u'\n",
+ bot);
+ printf(".as " DELIM_STRING " " DELIMITER_CHAR "\n");
+}
+
+static void define_extensible_string(char *delim, int uid,
+ left_or_right_t left_or_right)
+{
+ printf(".ds " DELIM_STRING "\n");
+ delimiter *d = delim_table;
+ int delim_len = strlen(delim);
+ int i;
+ for (i = 0; i < DELIM_TABLE_SIZE; i++, d++)
+ if (strncmp(delim, d->name, delim_len) == 0
+ && (left_or_right & d->flags) != 0)
+ break;
+ if (i >= DELIM_TABLE_SIZE) {
+ error("there is no '%1' delimiter", delim);
+ printf(".nr " DELIM_WIDTH_REG " 0\n");
+ return;
+ }
+
+ printf(".nr " DELIM_WIDTH_REG " 0\\w" DELIMITER_CHAR "\\f[%s]%s\\fP" DELIMITER_CHAR "\n"
+ ".ds " DELIM_STRING " \\Z" DELIMITER_CHAR
+ "\\v'\\n[rsb]u+\\n[rst]u/2u-%dM'\\f[%s]%s\\fP" DELIMITER_CHAR "\n"
+ ".nr " TOTAL_HEIGHT_REG " \\n[rst]-\\n[rsb]\n"
+ ".if \\n[" TOTAL_HEIGHT_REG "]<\\n[" DELTA_REG "] "
+ "\\{",
+ current_roman_font, d->small, axis_height,
+ current_roman_font, d->small);
+
+ char buf[256];
+// The format string in the sprintf below comes from a struct
+// initializer above, and is not subject to external influence.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ sprintf(buf, d->chain_format, "\\\\n[" INDEX_REG "]");
+#pragma GCC diagnostic pop
+ printf(".nr " INDEX_REG " 0\n"
+ ".de " TEMP_MACRO "\n"
+ ".ie c%s \\{\\\n"
+ ".nr " DELIM_WIDTH_REG " 0\\w" DELIMITER_CHAR "%s" DELIMITER_CHAR "\n"
+ ".ds " DELIM_STRING " \\Z" DELIMITER_CHAR
+ "\\v'\\\\n[rsb]u+\\\\n[rst]u/2u-%dM'%s" DELIMITER_CHAR "\n"
+ ".nr " TOTAL_HEIGHT_REG " \\\\n[rst]-\\\\n[rsb]\n"
+ ".if \\\\n[" TOTAL_HEIGHT_REG "]<\\n[" DELTA_REG "] "
+ "\\{.nr " INDEX_REG " +1\n"
+ "." TEMP_MACRO "\n"
+ ".\\}\\}\n"
+ ".el .nr " INDEX_REG " 0-1\n"
+ "..\n"
+ "." TEMP_MACRO "\n",
+ buf, buf, axis_height, buf);
+ if (d->ext) {
+ printf(".if \\n[" INDEX_REG "]<0 \\{.if c%s \\{\\\n", d->ext);
+ build_extensible(d->ext, d->top, d->mid, d->bot);
+ printf(".\\}\\}\n");
+ }
+ printf(".\\}\n");
+ printf(".as " DELIM_STRING " \\h'\\n[" DELIM_WIDTH_REG "]u'\n");
+ printf(".nr " WIDTH_FORMAT " +\\n[" DELIM_WIDTH_REG "]\n", uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?(\\n[" TOTAL_HEIGHT_REG "]/2+%dM)\n",
+ uid, uid, axis_height);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ ">?(\\n[" TOTAL_HEIGHT_REG "]/2-%dM)\n",
+ uid, uid, axis_height);
+}
+
+int delim_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DELTA_REG " \\n[" HEIGHT_FORMAT "]-%dM"
+ ">?(\\n[" DEPTH_FORMAT "]+%dM)\n",
+ p->uid, axis_height, p->uid, axis_height);
+ printf(".nr " DELTA_REG " 0\\n[" DELTA_REG "]*%d/500"
+ ">?(\\n[" DELTA_REG "]*2-%dM)\n",
+ delimiter_factor, delimiter_shortfall);
+ if (left) {
+ define_extensible_string(left, uid, LEFT_DELIM);
+ printf(".rn " DELIM_STRING " " LEFT_DELIM_STRING_FORMAT "\n",
+ uid);
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" DELIM_WIDTH_REG "]\n");
+ }
+ if (right) {
+ define_extensible_string(right, uid, RIGHT_DELIM);
+ printf(".rn " DELIM_STRING " " RIGHT_DELIM_STRING_FORMAT "\n",
+ uid);
+ }
+ return r;
+}
+
+void delim_box::output()
+{
+ if (output_format == troff) {
+ if (left)
+ printf("\\*[" LEFT_DELIM_STRING_FORMAT "]", uid);
+ p->output();
+ if (right)
+ printf("\\*[" RIGHT_DELIM_STRING_FORMAT "]", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<mrow><mo>%s</mo>", left);
+ p->output();
+ printf("<mo>%s</mo></mrow>", right);
+ }
+}
+
+void delim_box::check_tabs(int level)
+{
+ p->check_tabs(level);
+}
+
+void delim_box::debug_print()
+{
+ fprintf(stderr, "left \"%s\" { ", left ? left : "");
+ p->debug_print();
+ fprintf(stderr, " }");
+ if (right)
+ fprintf(stderr, " right \"%s\"", right);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/eqn.1.man b/src/preproc/eqn/eqn.1.man
new file mode 100644
index 0000000..9c3d43c
--- /dev/null
+++ b/src/preproc/eqn/eqn.1.man
@@ -0,0 +1,2240 @@
+'\" et
+.TH @g@eqn @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@eqn \- format mathematics (equations) for
+.I groff
+or MathML
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2023 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_eqn_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.ie \n(.V<\n(.v \
+. ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el \
+. ds tx TeX
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@eqn
+.RB [ \-CNrR ]
+.RB [ \- d
+.IR xy ]
+.RB [ \-f
+.IR F ]
+.RB [ \-m
+.IR n ]
+.RB [ \-M
+.IR dir ]
+.RB [ \-p
+.IR n ]
+.RB [ \-s
+.IR n ]
+.RB [ \-T
+.IR dev ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@eqn
+.B \-\-help
+.YS
+.
+.
+.SY @g@eqn
+.B \-v
+.
+.SY @g@eqn
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU implementation of
+.I eqn \" GNU
+is part of the
+.MR groff @MAN7EXT@
+document formatting system.
+.
+.I @g@eqn
+is a
+.MR @g@troff @MAN1EXT@
+preprocessor that translates expressions in its own language,
+embedded in
+.MR roff @MAN7EXT@
+input files,
+into mathematical notation typeset by
+.MR @g@troff @MAN1EXT@ .
+.
+It copies each
+.IR file 's
+contents to the standard output stream,
+translating each
+.I equation
+between lines starting with
+.B .EQ
+and
+.BR .EN ,
+or within a pair of user-specified delimiters.
+.
+Normally,
+.I @g@eqn
+is not executed directly by the user,
+but invoked by specifying the
+.B \-e
+option to
+.MR groff @MAN1EXT@ .
+.
+While GNU
+.IR eqn 's \" GNU
+input syntax is highly compatible with AT&T
+.IR eqn , \" AT&T
+the output
+.I @g@eqn
+produces cannot be processed by AT&T
+.IR troff ; \" AT&T
+GNU
+.I troff \" GNU
+(or a
+.I troff \" generic
+implementing relevant GNU extensions)
+must be used.
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I @g@eqn
+reads the standard input stream.
+.
+.
+.P
+Unless the
+.B \-R
+option is used,
+.I @g@eqn
+searches for the file
+.I eqnrc
+in the directories given with the
+.B \-M
+option first,
+then in
+.if !'@COMPATIBILITY_WRAPPERS@'no' .IR @SYSTEMMACRODIR@ ,
+.IR @LOCALMACRODIR@ ,
+and finally in the standard macro directory
+.IR @MACRODIR@ .
+.
+If it exists and is readable,
+.I @g@eqn
+processes it before any input files.
+.
+.
+.P
+This man page primarily discusses the differences between GNU
+.I eqn \" GNU
+and AT&T
+.IR eqn .\" AT&T
+.
+Most of the new features of the GNU
+.I eqn \" GNU
+input language are based on \*[tx].
+.
+There are some references to the differences between \*[tx] and GNU
+.I eqn \" GNU
+below;
+these may safely be ignored if you do not know \*[tx].
+.
+.
+.P
+Three points are worth special note. \" good, bad, and different
+.
+.
+.IP \[bu] 2n
+GNU
+.I eqn \" GNU
+emits Presentation MathML output when invoked with the
+.RB \[lq] "\-T\~MathML" \[rq]
+option.
+.
+.
+.IP \[bu]
+GNU
+.I eqn \" GNU
+does not support terminal devices well,
+though it may suffice for simple inputs.
+.
+.
+.IP \[bu]
+GNU
+.I eqn
+sets the input token
+.RB \[lq] .\|.\|.\& \[rq]
+as an ellipsis on the text baseline,
+not the three centered dots of AT&T
+.IR eqn . \" AT&T
+.
+Set an ellipsis on the math axis with the GNU extension macro
+.BR cdots .
+.
+.
+.\" ====================================================================
+.SS "Anatomy of an equation"
+.\" ====================================================================
+.
+.I eqn
+input consists of tokens.
+.
+Consider a form of Newton's second law of motion.
+.
+The input
+.
+.
+.P
+.RS
+.EX
+\&.EQ
+F =
+m a
+\&.EN
+.EE
+.RE
+.
+.
+.P
+becomes
+.EQ
+F =
+m a.
+.EN
+.
+Each of
+.BR F ,
+.BR = ,
+.BR m ,
+and
+.B a
+is a token.
+.
+.
+Spaces and newlines are interchangeable;
+they separate tokens but do not break lines or produce space in
+the output.
+.
+.
+.P
+The following input characters not only separate tokens,
+but manage their grouping and spacing as well.
+.
+.
+.TP
+.B "{ }"
+Braces perform grouping.
+.
+Whereas
+.RB \[lq] "e sup a b" \[rq]
+expresses
+.ie n .RI \[lq]( e "\~to the\~" a )\~times\~ b \[rq],
+.el \{\
+.EQ
+e sup a b ,
+.EN
+.\}
+.RB \[lq] "e sup { a b }" \[rq]
+means
+.ie n .RI \[lq] e "\~to the\~(" a \~times\~ b )\[rq].
+.el \{\
+.EQ
+e sup { a b } .
+.EN
+.\}
+.
+When immediately preceded by a
+.RB \[lq] left \[rq]
+or
+.RB \[lq] right \[rq]
+primitive,
+a brace loses its special meaning.
+.
+.
+.TP
+.B "\[ha] \[ti]
+are the
+.I "half space"
+and
+.I "full space,"
+respectively.
+.
+Use them to tune the appearance of the output.
+.
+.
+.P
+Tab and leader characters separate tokens as well as advancing the
+drawing position to the next tab stop,
+but are seldom used in
+.I eqn
+input.
+.
+When they occur,
+they must appear at the outermost lexical scope.
+.
+This roughly means that they can't appear within braces that are
+necessary to disambiguate the input;
+.I @g@eqn
+will diagnose an error in this event.
+.
+(See subsection \[lq]Macros\[rq] below for additional token separation
+rules.)
+.
+.
+.P
+Other tokens are primitives,
+macros,
+an argument to either of the foregoing,
+or components of an equation.
+.
+.
+.br
+.ne 4v
+.P
+.I Primitives
+are fundamental keywords of the
+.I eqn
+language.
+.
+They can configure an aspect of the preprocessor's state,
+as when setting a \[lq]global\[rq] font selection or type size
+.RB ( gfont
+and
+.BR gsize ),
+or declaring or deleting macros
+.RB \%(\[lq] define \[rq]
+and
+.BR undef );
+these are termed
+.I commands.
+.
+Other primitives perform formatting operations on the tokens after them
+(as with
+.BR fat ,
+.BR over ,
+.BR sqrt ,
+or
+.BR up ).
+.
+.
+.P
+Equation
+.I components
+include mathematical variables,
+constants,
+numeric literals,
+and operators.
+.
+.I @g@eqn
+remaps some input character sequences to
+.I groff
+special character escape sequences for economy in equation entry and to
+ensure that glyphs from an unstyled font are used;
+see
+.MR groff_char @MAN7EXT@ .
+.
+.
+.P
+.RS
+.TS
+tab(@);
+Lf(CR) Lf(CR) Lw(1i) Lf(CR) Lf(CR).
++@\[rs][pl]@\&@\[aq]@\[rs][fm]
+-@\[rs][mi]@\&@<=@\[rs][<=]
+\&=@\[rs][eq]@\&@>=@\[rs][>=]
+.TE
+.RE
+.
+.
+.P
+.I Macros
+permit primitives,
+components,
+and other macros to be collected and referred to by a single token.
+.
+Predefined macros make convenient the preparation of
+.I eqn
+input in a form resembling its spoken expression;
+for example,
+consider
+.BR cos ,
+.BR hat ,
+.BR inf ,
+and
+.BR lim .
+.
+.
+.\" ====================================================================
+.SS "Spacing and typeface"
+.\" ====================================================================
+.
+GNU
+.I eqn
+imputes types to the components of an equation,
+adjusting the spacing between them accordingly.
+.
+Recognized types are as follows;
+most affect spacing only,
+whereas the
+.RB \%\[lq] letter \[rq]
+subtype of
+.RB \%\[lq] ordinary \[rq]
+also assigns a style.
+.
+.
+.RS 2n \" we need quite a bit of horizontal space for this table
+.P
+.TS
+Lf(CR) Lx
+Af(CR) Lx
+Af(CR) Lx
+Lf(CR) Lx.
+ordinary T{
+character such as \[lq]1\[rq],
+\[lq]a\[rq],
+or
+\[lq]!\&\[rq]
+T}
+letter character to be italicized by default
+digit \f[I]n/a\f[]
+operator T{
+large operator such as
+.ds Su \[lq]\s+5\[*S]\s0\[rq]
+.if \n(.g .if !c\[*S] .ds Su the summation operator
+\*[Su]
+.rm Su
+T}
+binary binary operator such as \[lq]\[pl]\[rq]
+relation relational operator such as \[lq]=\[rq]
+opening opening bracket such as \[lq](\[rq]
+closing closing bracket such as \[lq])\[rq]
+punctuation punctuation character such as \[lq],\[rq]
+inner sub-formula contained within brackets
+suppress component to which automatic spacing is not applied
+.TE
+.RE
+.
+.
+.P
+Two primitives apply types to equation components.
+.
+.
+.TP
+.BI type\~ "t e"
+Apply
+.RI type\~ t
+to
+.RI expression\~ e .
+.
+.
+.TP
+.BI chartype\~ "t text"
+Assign each character in (unquoted)
+.I text
+.RI type\~ t ,
+persistently.
+.
+.
+.P
+.I @g@eqn \" GNU
+sets up spacings and styles as if by the following commands.
+.
+.P
+.RS
+.TS
+tab(@);
+Lf(CR)1 Lf(CR).
+chartype \[dq]letter\[dq]@abcdefghiklmnopqrstuvwxyz
+chartype \[dq]letter\[dq]@ABCDEFGHIKLMNOPQRSTUVWXYZ
+chartype \[dq]letter\[dq]@\[rs][*a]\[rs][*b]\[rs][*g]\[rs][*d]\[rs][*e]\
+\[rs][*z]
+chartype \[dq]letter\[dq]@\[rs][*y]\[rs][*h]\[rs][*i]\[rs][*k]\[rs][*l]\
+\[rs][*m]
+chartype \[dq]letter\[dq]@\[rs][*n]\[rs][*c]\[rs][*o]\[rs][*p]\[rs][*r]\
+\[rs][*s]
+chartype \[dq]letter\[dq]@\[rs][*t]\[rs][*u]\[rs][*f]\[rs][*x]\[rs][*q]\
+\[rs][*w]
+chartype \[dq]binary\[dq]@*\[rs][pl]\[rs][mi]
+chartype \[dq]relation\[dq]@<>\[rs][eq]\[rs][<=]\[rs][>=]
+chartype \[dq]opening\[dq]@{([
+chartype \[dq]closing\[dq]@})]
+chartype \[dq]punctuation\[dq]@,;:.
+chartype \[dq]suppress\[dq]@\[ha]\[ti]
+.TE
+.RE
+.
+.
+.P
+.I @g@eqn
+assigns all other ordinary and special
+.I roff
+characters,
+including numerals 0\[en]9,
+the
+.RB \%\[lq] ordinary \[rq]
+type.
+.
+(The
+.RB \[lq] digit \[rq]
+type is not used,
+but is available for customization.)
+.\" XXX: How would you actually customize it, though? There doesn't
+.\" seem to be a means of replacing the font associated with a type.
+.\" Is the "digit" type just cruft?
+.
+In keeping with common practice in mathematical typesetting,
+lowercase,
+but not uppercase,
+Greek letters are assigned the
+.RB \%\[lq] letter \[rq]
+type to style them in italics.
+.
+The macros for producing ellipses,
+.RB \[lq] .\|.\|. \[rq],
+.BR cdots ,
+and
+.BR ldots ,
+use the
+.RB \%\[lq] inner \[rq]
+type.
+.
+.
+.\" ====================================================================
+.SS Primitives
+.\" ====================================================================
+.
+.I @g@eqn
+supports without alteration the AT&T
+.I eqn \" AT&T
+primitives
+.BR above ,
+.BR back ,
+.BR bar ,
+.BR bold ,
+.BR \%define ,
+.BR down ,
+.BR fat ,
+.BR font ,
+.BR from ,
+.BR fwd ,
+.BR gfont ,
+.BR gsize ,
+.BR italic ,
+.BR left ,
+.BR lineup ,
+.BR mark ,
+.BR \%matrix ,
+.BR \%ndefine ,
+.BR over ,
+.BR right ,
+.BR roman ,
+.BR size ,
+.BR sqrt ,
+.BR sub ,
+.BR sup ,
+.BR \%tdefine ,
+.BR to ,
+.BR \%under ,
+and
+.BR up .
+.
+.
+.\" ====================================================================
+.SS "New primitives"
+.\" ====================================================================
+.
+The GNU extension primitives
+.RB \[lq] type \[rq]
+and
+.B \%chartype
+are discussed in subsection \[lq]Spacing and typeface\[rq] above;
+.RB \[lq] set \[rq]
+in subsection \[lq]Customization\[rq] below;
+and
+.B grfont
+and
+.B gbfont
+in subsection \[lq]Fonts\[rq] below.
+.
+In the following synopses,
+.I X
+can be any character not appearing in the parameter thus bracketed.
+.
+.
+.TP
+.IB e1 \~accent\~ e2
+Set
+.I e2
+as an accent over
+.IR e1 .
+.
+.I e2
+is assumed to be at the appropriate height for a lowercase letter
+without an ascender;
+.I @g@ eqn
+vertically shifts it depending on
+.IR e1 's
+height.
+.
+For example,
+.B hat
+is defined as follows.
+.
+.
+.RS
+.IP
+.EX
+accent { "\[ha]" }
+.EE
+.RE
+.
+.
+.IP
+.BR dotdot ,
+.BR dot ,
+.BR tilde ,
+.BR vec ,
+and
+.B dyad
+are also defined using the
+.B \%accent
+primitive.
+.
+.
+.TP
+.BI big\~ e
+Enlarge the expression
+.IR e ;
+semantics like those of CSS \[lq]large\[rq] are intended.
+.
+In
+.I @g@troff
+output,
+the type size is increased by\~5 scaled points.
+.
+MathML output emits the following.
+.
+.
+.RS
+.IP
+.EX
+<mstyle \%mathsize=\[aq]big\[aq]>
+.EE
+.RE
+.
+.
+.TP
+.BI copy\~ file
+.TQ
+.BI include\~ file
+Interpolate the contents of
+.IR file ,
+omitting lines
+beginning with
+.B .EQ
+or
+.BR .EN .
+.
+If a relative path name,
+.I file
+is sought relative to the current working directory.
+.
+.
+.TP
+.BI ifdef\~ "name X anything X"
+If
+.I name
+is defined as a primitive or macro,
+interpret
+.IR anything .
+.
+.
+.TP
+.BI nosplit\~ text
+As
+.RI \[dq] text \[dq],
+but since
+.I text
+is not quoted it is subject to macro expansion;
+it is not split up and the spacing between characters not adjusted per
+subsection \[lq]Spacing and typeface\[rq] above.
+.
+.
+.TP
+.IB e\~ opprime
+As
+.BR prime ,
+but set the prime symbol as an operator
+.RI on\~ e .
+.
+In the input
+.RB \[lq] "A opprime sub 1" \[rq],
+the\~\[lq]1\[rq] is tucked under the prime as a subscript to
+the\~\[lq]A\[rq]
+(as is conventional in mathematical typesetting),
+whereas when
+.B prime
+is used,
+the\~\[lq]1\[rq] is a subscript to the prime character.
+.
+The precedence of
+.B \%opprime
+is the same as that of
+.B bar
+and
+.RB \%\[lq] under \[rq],
+and higher than that of other primitives except
+.B \%accent
+and
+.BR uaccent .
+.
+In unquoted text,
+a neutral apostrophe
+.RB ( \[aq] )
+that is not the first character on the input line is treated like
+.BR \%opprime .
+.
+.
+.TP
+.BI sdefine\~ "name X anything X"
+As
+.RB \%\[lq] define \[rq],
+but
+.I name
+is not recognized as a macro if called with arguments.
+.
+.
+.TP
+.IB e1 \~smallover\~ e2
+As
+.BR over ,
+but reduces the type size of
+.I e1
+and
+.IR e2 ,
+and puts less vertical space between
+.I e1
+and
+.I e2
+and the fraction bar.
+.
+The
+.B over
+primitive corresponds to the \*[tx]
+.B \[rs]over
+primitive in displayed equation styles;
+.B smallover
+corresponds to
+.B \[rs]over
+in non-display (\[lq]inline\[rq]) styles.
+.
+.
+.br
+.ne 5v
+.TP
+.BI space\~ n
+Set extra vertical spacing around the equation,
+replacing the default values,
+where
+.IR n \~is
+an integer in hundredths of an em.
+.
+If positive,
+.IR n \~increases
+vertical spacing before the equation;
+if negative,
+it does so after the equation.
+.
+This primitive provides an interface to
+.IR groff 's
+.B \[rs]x
+escape sequence,
+but with the opposite sign convention.
+.
+It has no effect if the equation is part of a
+.MR @g@pic @MAN1EXT@
+picture.
+.
+.
+.TP
+.BI special\~ "troff-macro e"
+Construct an object by calling
+.I troff-macro
+.RI on\~ e .
+.
+The
+.I troff \" generic
+string
+.B 0s
+contains the
+.I eqn \" generic
+output
+.RI for\~ e ,
+and the registers
+.BR 0w ,
+.BR 0h ,
+.BR 0d ,
+.BR 0skern ,
+and
+.B 0skew
+the width,
+height,
+depth,
+subscript kern,
+and skew
+.RI of\~ e ,
+respectively.
+.
+(The
+.I subscript kern
+of an object indicates how much a subscript on that object should be
+\[lq]tucked in\[rq],
+or placed to the left relative to a non-subscripted glyph of the same
+size.
+.
+The
+.I skew
+of an object is how far to the right of the center of the object an
+accent over it should be placed.)
+.
+The macro must modify
+.B 0s
+so that it outputs the desired result,
+returns the drawing position to the text baseline at the beginning of
+.IR e ,
+and updates the foregoing registers to correspond to the new dimensions
+of the result.
+.
+.
+.IP
+Suppose you want a construct that \[lq]cancels\[rq] an expression by
+drawing a diagonal line through it.
+.
+.
+.br
+.ne 11v
+.RS
+.IP
+.EX
+\&.de Ca
+\&. ds 0s \[rs]
+\[rs]Z\[aq]\[rs]\[rs]*(0s\[aq]\[rs]
+\[rs]v\[aq]\[rs]\[rs]n(0du\[aq]\[rs]
+\[rs]D\[aq]l \[rs]\[rs]n(0wu \-\[rs]\[rs]n(0hu\-\[rs]\
+\[rs]n(0du\[aq]\[rs]
+\[rs]v\[aq]\[rs]\[rs]n(0hu\[aq]
+\&..
+\&.EQ
+special Ca "x \[rs][mi] 3 \[rs][pl] x" \[ti] 3
+\&.EN
+.EE
+.RE
+.
+.
+.IP
+We use the
+.B \[rs][mi]
+and
+.B \[rs][pl]
+special characters instead of + and \-
+because they are part of the argument to a
+.I @g@troff
+macro,
+so
+.I @g@eqn
+does not transform them to mathematical glyphs for us.
+.
+Here's a more complicated construct that draws a box around an
+expression;
+the bottom of the box rests on the text baseline.
+.
+We define the
+.I eqn \" generic
+macro
+.B box
+to wrap the call of the
+.I @g@troff
+macro
+.BR Bx .
+.
+.
+.br
+.ne 17v
+.RS
+.IP
+.EX
+\&.de Bx
+\&.ds 0s \[rs]
+\[rs]Z\[aq]\[rs]\[rs]h\[aq]1n\[aq]\[rs]\[rs]*[0s]\[aq]\[rs]
+\[rs]v\[aq]\[rs]\[rs]n(0du+1n\[aq]\[rs]
+\[rs]D\[aq]l \[rs]\[rs]n(0wu+2n 0\[aq]\[rs]
+\[rs]D\[aq]l 0 \-\[rs]\[rs]n(0hu\-\[rs]\[rs]n(0du\-2n\[aq]\[rs]
+\[rs]D\[aq]l \-\[rs]\[rs]n(0wu\-2n 0\[aq]\[rs]
+\[rs]D\[aq]l 0 \[rs]\[rs]n(0hu+\[rs]\[rs]n(0du+2n\[aq]\[rs]
+\[rs]h\[aq]\[rs]\[rs]n(0wu+2n\[aq]
+\&.nr 0w +2n
+\&.nr 0d +1n
+\&.nr 0h +1n
+\&..
+\&.EQ
+define box \[aq] special Bx $1 \[aq]
+box(foo) \[ti] "bar"
+\&.EN
+.EE
+.RE
+.
+.
+.TP
+.BI "split \[dq]" text \[dq]
+As
+.IR text ,
+but since
+.I text
+is quoted,
+it is not subject to macro expansion;
+it is split up and the spacing between characters adjusted per
+subsection \[lq]Spacing and typeface\[rq] above.
+.
+.
+.TP
+.IB e1 \~uaccent\~ e2
+Set
+.I e2
+as an accent under
+.IR e1 .
+.
+.I e2
+is assumed to be at the appropriate height for a letter without a
+descender;
+.I @g@ eqn
+vertically shifts it depending on whether
+.I e1
+has a descender.
+.
+.B utilde
+is predefined using
+.B uaccent
+as a tilde accent below the baseline.
+.
+.
+.TP
+.BI undef\~ name
+Remove definition of macro or primitive
+.IR name ,
+making it undefined.
+.
+.
+.TP
+.BI vcenter\~ e
+Vertically center
+.I e
+about the
+.IR "math axis" ,
+a horizontal line upon which fraction bars and characters such as
+\[lq]\[pl]\[rq] and \[lq]\[mi]\[rq] are aligned.
+.
+MathML already behaves this way,
+so
+.I @g@eqn
+ignores this primitive when producing that output format.
+.
+The built-in
+.B sum
+macro is defined as if by the following.
+.
+.RS
+.IP
+.EX
+define sum ! { type "operator" vcenter size +5 \[rs](*S } !
+.EE
+.RE
+.
+.
+.br
+.ne 8v
+.\" ====================================================================
+.SS "Extended primitives"
+.\" ====================================================================
+.
+GNU
+.I eqn \" GNU
+extends the syntax of some AT&T
+.I eqn \" AT&T
+primitives,
+introducing one deliberate incompatibility.
+.
+.
+.TP
+.B "delim on"
+.I @g@eqn
+recognizes an
+.RB \[lq] on \[rq]
+argument to the
+.B \%delim
+primitive specially,
+restoring any delimiters previously disabled with
+.RB \%\[lq] "delim off" \[rq].
+.
+If delimiters haven't been specified,
+neither command has effect.
+.
+Few
+.I eqn \" generic
+documents are expected to use \[lq]o\[rq] and \[lq]n\[rq] as left and
+right delimiters,
+respectively.
+.
+If yours does,
+consider swapping them,
+or select others.
+.
+.
+.TP
+.BI col\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI ccol\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI lcol\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI rcol\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI pile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI cpile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI lpile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+.TQ
+.BI rpile\~ n\~\c
+.BR {\~ .\|.\|.\& \~}
+The integer
+.RI value\~ n
+(in hundredths of an em)
+increases the vertical spacing between rows,
+using
+.IR groff 's
+.B \[rs]x
+escape sequence
+(the value has no effect in MathML mode).
+.
+Negative values are accepted but have no effect.
+.
+If more than one
+.I n
+occurs in a matrix or pile,
+the largest is used.
+.
+.
+.\" ====================================================================
+.SS Customization
+.\" ====================================================================
+.
+When
+.I @g@eqn
+generates
+.I @g@troff
+input,
+the appearance of equations is controlled by a large number of
+parameters.
+.
+They have no effect when generating MathML,
+which delegates typesetting to a MathML rendering engine.
+.
+Configure these parameters with the
+.B set
+primitive.
+.
+.
+.TP
+.BI set\~ "p n"
+assigns
+.RI parameter\~ p
+the integer
+.RI value\~ n ;
+.IR n \~is
+interpreted in units of hundredths of an em unless otherwise stated.
+.
+For example,
+.
+.
+.RS
+.IP
+.EX
+set x_height 45
+.EE
+.RE
+.
+.
+.IP
+says that
+.I @g@eqn
+should assume that the font's x-height is 0.45\~ems.
+.
+.
+.RS
+.P
+Available parameters are as follows;
+defaults are shown in parentheses.
+.
+We intend these descriptions to be expository rather than rigorous.
+.
+.
+.TP 17n
+.B minimum_size
+sets a floor for the type size
+(in scaled points)
+at which equations are set
+.RB ( 5 ).
+.
+.
+.TP
+.B fat_offset
+The
+.B fat
+primitive emboldens an equation by overprinting two copies of the
+equation horizontally offset by this amount
+.RB ( 4 ).
+.
+In MathML mode,
+components to which
+.B \%fat_offset
+applies instead use the following.
+.
+.RS
+.RS
+.EX
+<mstyle mathvariant=\[aq]double\-struck\[aq]>
+.EE
+.RE
+.RE
+.
+.
+.TP
+.B over_hang
+A fraction bar is longer by twice this amount than
+the maximum of the widths of the numerator and denominator;
+in other words,
+it overhangs the numerator and denominator by at least this amount
+.RB ( 0 ).
+.
+.
+.TP
+.B accent_width
+When
+.B bar
+or
+.B \%under
+is applied to a single character,
+the line is this long
+.RB ( 31 ).
+.
+Normally,
+.B bar
+or
+.B \%under
+produces a line whose length is the width of the object to which it
+applies;
+in the case of a single character,
+this tends to produce a line that looks too long.
+.
+.
+.TP
+.B delimiter_factor
+Extensible delimiters produced with the
+.B left
+and
+.B right
+primitives have a combined height and depth of at least this many
+thousandths of twice the maximum amount by which the sub-equation that
+the delimiters enclose extends away from the axis
+.RB ( 900 ).
+.
+.
+.TP
+.B delimiter_shortfall
+Extensible delimiters produced with the
+.B left
+and
+.B right
+primitives have a combined height and depth not less than the
+difference of twice the maximum amount by which the sub-equation that
+the delimiters enclose extends away from the axis and this amount
+.RB ( 50 ).
+.
+.
+.TP
+.B null_delimiter_space
+This much horizontal space is inserted on each side of a fraction
+.RB ( 12 ).
+.
+.
+.TP
+.B script_space
+The width of subscripts and superscripts is increased by this amount
+.RB ( 5 ).
+.
+.
+.TP
+.B thin_space
+This amount of space is automatically inserted after punctuation
+characters.
+.
+It also configures the width of the space produced by the
+.B \[ha]
+token
+.RB ( 17 ).
+.
+.
+.TP
+.B medium_space
+This amount of space is automatically inserted on either side of
+binary operators
+.RB ( 22 ).
+.
+.
+.TP
+.B thick_space
+This amount of space is automatically inserted on either side of
+relations.
+.
+It also configures the width of the space produced by the
+.B \[ti]
+token
+.RB ( 28 ).
+.
+.
+.TP
+.B x_height
+The height of lowercase letters without ascenders such as \[lq]x\[rq]
+.RB ( 45 ).
+.
+.
+.TP
+.B axis_height
+The height above the baseline of the center of characters such as
+\[lq]\[pl]\[rq] and \[lq]\[mi]\[rq]
+.RB ( 26 ).
+.
+It is important that this value is correct for the font
+you are using.
+.
+.
+.TP
+.B default_rule_thickness
+This should be set to the thickness of the
+.B \[rs][ru]
+character,
+or the thickness of horizontal lines produced with the
+.B \[rs]D
+escape sequence
+.RB ( 4 ).
+.
+.
+.TP
+.B num1
+The
+.B over
+primitive shifts up the numerator by at least this amount
+.RB ( 70 ).
+.
+.
+.TP
+.B num2
+The
+.B smallover
+primitive shifts up the numerator by at least this amount
+.RB ( 36 ).
+.
+.
+.TP
+.B denom1
+The
+.B over
+primitive shifts down the denominator by at least this amount
+.RB ( 70 ).
+.
+.
+.TP
+.B denom2
+The
+.B smallover
+primitive shifts down the denominator by at least this amount
+.RB ( 36 ).
+.
+.
+.TP
+.B sup1
+Normally superscripts are shifted up by at least this amount
+.RB ( 42 ).
+.
+.
+.TP
+.B sup2
+Superscripts within superscripts or upper limits
+or numerators of
+.B smallover
+fractions are shifted up by at least this amount
+.RB ( 37 ).
+.
+Conventionally,
+this is less than
+.BR sup1 .
+.
+.
+.TP
+.B sup3
+Superscripts within denominators or square roots
+or subscripts or lower limits are shifted up by at least
+this amount
+.RB ( 28 ).
+.
+Conventionally,
+this is less than
+.BR sup2 .
+.
+.
+.TP
+.B sub1
+Subscripts are normally shifted down by at least this amount
+.RB ( 20 ).
+.
+.
+.TP
+.B sub2
+When there is both a subscript and a superscript,
+the subscript is shifted down by at least this amount
+.RB ( 23 ).
+.
+.
+.TP
+.B sup_drop
+The baseline of a superscript is no more than this much below the top of
+the object on which the superscript is set
+.RB ( 38 ).
+.
+.
+.TP
+.B sub_drop
+The baseline of a subscript is at least this much below the bottom of
+the object on which the subscript is set
+.RB ( 5 ).
+.
+.
+.TP
+.B big_op_spacing1
+The baseline of an upper limit is at least this much above the top of
+the object on which the limit is set
+.RB ( 11 ).
+.
+.
+.TP
+.B big_op_spacing2
+The baseline of a lower limit is at least this much below the bottom
+of the object on which the limit is set
+.RB ( 17 ).
+.
+.
+.TP
+.B big_op_spacing3
+The bottom of an upper limit is at least this much above the top of
+the object on which the limit is set
+.RB ( 20 ).
+.
+.
+.TP
+.B big_op_spacing4
+The top of a lower limit is at least this much below the bottom of the
+object on which the limit is set
+.RB ( 60 ).
+.
+.
+.TP
+.B big_op_spacing5
+This much vertical space is added above and below limits
+.RB ( 10 ).
+.
+.
+.TP
+.B baseline_sep
+The baselines of the rows in a pile or matrix are normally this far
+apart
+.RB ( 140 ).
+.
+Usually equal to the sum of
+.B num1
+and
+.BR denom1 .
+.
+.
+.TP
+.B shift_down
+The midpoint between the top baseline and the bottom baseline in a
+matrix or pile is shifted down by this much from the axis
+.RB ( 26 ).
+.
+Usually equal to
+.BR axis_height .
+.
+.
+.TP
+.B column_sep
+This much space is added between columns in a matrix
+.RB ( 100 ).
+.
+.
+.TP
+.B matrix_side_sep
+This much space is added at each side of a matrix
+.RB ( 17 ).
+.
+.
+.br
+.ne 4v
+.TP
+.B draw_lines
+If non-zero,
+.I @g@eqn
+draws lines using the
+.I troff \" generic
+.B \[rs]D
+escape sequence,
+rather than the
+.B \[rs]l
+escape sequence and the
+.B \[rs][ru]
+special character.
+.
+The
+.I eqnrc
+file sets the default:
+.BR 1 \~on
+.BR ps ,
+.BR html ,
+and the X11 devices,
+.RB otherwise\~ 0 .
+.
+.
+.TP
+.B body_height
+is the presumed height of an equation above the text baseline;
+.I @g@eqn
+adds any excess as extra pre-vertical line spacing with
+.IR troff 's\" generic
+.B \[rs]x
+escape sequence
+.RB ( 85 ).
+.
+.
+.TP
+.B body_depth
+is the presumed depth of an equation below the text baseline;
+.I @g@eqn
+adds any excess as extra post-vertical line spacing with
+.IR troff 's\" generic
+.B \[rs]x
+escape sequence
+.RB ( 35 ).
+.
+.
+.TP
+.B nroff
+If non-zero,
+then
+.B \%ndefine
+behaves like
+.B \%define
+and
+.B \%tdefine
+is ignored,
+otherwise
+.B \%tdefine
+behaves like
+.B \%define
+and
+.B \%ndefine
+is ignored.
+.
+The
+.I eqnrc
+file sets the default:
+.BR 1 \~on
+.BR ascii ,
+.BR latin1 ,
+.BR utf8 ,
+and
+.B cp1047
+devices,
+.RB otherwise\~ 0 .
+.RE
+.
+.
+.\" ====================================================================
+.SS Macros
+.\" ====================================================================
+.
+In GNU
+.IR eqn , \" GNU
+macros can take arguments.
+.
+A word defined by any of the
+.BR \%define ,
+.BR \%ndefine ,
+or
+.B \%tdefine
+primitives followed immediately by a left parenthesis is treated as a
+.I "parameterized macro call:"
+subsequent tokens up to a matching right parenthesis are treated as
+comma-separated arguments.
+.
+In this context only,
+commas and parentheses also serve as token separators.
+.
+A macro argument is not terminated by a comma inside parentheses nested
+within it.
+.
+In a macro definition,
+.BI $ n\c
+,
+where
+.I n
+is between 1 and\~9 inclusive,
+is replaced by the
+.IR n th
+argument;
+if there are fewer than
+.IR n \~arguments,
+it is replaced by nothing.
+.
+.
+.\" ====================================================================
+.SS "Predefined macros"
+.\" ====================================================================
+.
+GNU
+.I eqn \" GNU
+supports the predefined macros offered by AT&T
+.IR eqn : \" AT&T
+.BR and ,
+.BR \%approx ,
+.BR arc ,
+.BR cos ,
+.BR cosh ,
+.BR del ,
+.BR det ,
+.BR dot ,
+.BR \%dotdot ,
+.BR dyad ,
+.BR exp ,
+.BR for ,
+.BR grad ,
+.BR half ,
+.BR hat ,
+.BR if ,
+.BR \%inter ,
+.BR Im ,
+.BR inf ,
+.BR int ,
+.BR lim ,
+.BR ln ,
+.BR log ,
+.BR max ,
+.BR min ,
+.BR \%nothing ,
+.BR \%partial ,
+.BR prime ,
+.BR prod ,
+.BR Re ,
+.BR sin ,
+.BR sinh ,
+.BR sum ,
+.BR tan ,
+.BR tanh ,
+.BR tilde ,
+.BR times ,
+.BR union ,
+.BR vec ,
+.BR == ,
+.BR != ,
+.BR += ,
+.BR \-> ,
+.BR <\- ,
+.BR << ,
+.BR >> ,
+and
+.RB \[lq] .\|.\|. \[rq].
+.
+The lowercase classical Greek letters are available as
+.BR \%alpha ,
+.BR beta ,
+.BR chi ,
+.BR delta ,
+.BR \%epsilon ,
+.BR eta ,
+.BR gamma ,
+.BR iota ,
+.BR kappa ,
+.BR lambda ,
+.BR mu ,
+.BR nu ,
+.BR omega ,
+.BR \%omicron ,
+.BR phi ,
+.BR pi ,
+.BR psi ,
+.BR rho ,
+.BR sigma ,
+.BR tau ,
+.BR theta ,
+.BR \%upsilon ,
+.BR xi ,
+and
+.BR zeta .
+.
+Spell them with an initial capital letter
+.RB \%( Alpha )
+or in full capitals
+.RB \%( ALPHA )
+to obtain uppercase forms.
+.
+.
+.P
+GNU
+.I eqn \" GNU
+further defines the macros
+.BR cdot ,
+.BR cdots ,
+and
+.B utilde
+(all discussed above),
+.BR \%dollar ,
+which sets a dollar sign,
+and
+.BR ldots ,
+which sets an ellipsis on the text baseline.
+.
+.
+.\" ====================================================================
+.SS Fonts
+.\" ====================================================================
+.
+.I @g@eqn
+uses up to three typefaces to set an equation:
+italic (oblique),
+roman (upright),
+and bold.
+.
+Assign each a
+.I groff
+typeface with the primitives
+.BR gfont ,
+.BR \%grfont ,
+and
+.B \%gbfont.
+.
+The defaults are the styles
+.BR I ,
+.BR R ,
+and
+.B B
+(applied to the current font family).
+.
+The
+.B \%chartype
+primitive
+(see above)
+sets a character's type,
+which determines the face used to set it.
+.
+The
+.RB \%\[lq] letter \[rq]
+type is set in italics;
+others are set in roman.
+.
+Use the
+.B bold
+primitive to select an (upright) bold style.
+.
+.
+.TP
+.BI gbfont\~ f
+.RI Select\~ f
+as the bold font.
+.
+This is a GNU extension.
+.
+.
+.TP
+.BI gfont\~ f
+.RI Select\~ f
+as the italic font.
+.
+.
+.TP
+.BI grfont\~ f
+.RI Select\~ f
+as the roman font.
+.
+This is a GNU extension.
+.
+.
+.br
+.ne 4v
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-C
+Recognize
+.B .EQ
+and
+.B .EN
+even when followed by a character other than space or newline.
+.
+.
+.TP
+.BI \-d\~ xy
+Specify delimiters
+.I x
+for left
+.RI and\~ y
+for right ends
+of equations not bracketed by
+.BR .EQ / .EN .
+.
+.I x
+and
+.I y
+need not be distinct.
+.
+Any
+.RB \%\[lq] delim
+.IR xy \[rq]
+statements in the source file override this option.
+.
+.
+.TP
+.BI \-f\~ F
+is equivalent to
+.RB \[lq] gfont
+.IR F \[rq].
+.
+.
+.TP
+.BI \-m\~ n
+is equivalent to
+.RB \[lq] "set \%minimum_size"
+.IR n \[rq].
+.
+.
+.TP
+.BI \-M\~ dir
+Search
+.I dir
+for
+.I eqnrc
+before those listed in section \[lq]Description\[rq] above.
+.
+.
+.TP
+.B \-N
+Prohibit newlines within delimiters.
+.
+This option allows
+.I @g@eqn
+to recover better from missing closing delimiters.
+.
+.
+.TP
+.BI \-p\~ n
+Set sub- and superscripts
+.IR n \~points
+smaller than the surrounding text.
+.
+This option is deprecated.
+.
+.I @g@eqn
+normally sets sub- and superscripts at 70% of the type size of the
+surrounding text.
+.
+.
+.TP
+.B \-r
+Reduce the type size of subscripts at most once relative to the base
+type size for the equation.
+.
+.
+.TP
+.B \-R
+Don't load
+.IR eqnrc .
+.
+.
+.TP
+.BI \-s\~ n
+is equivalent to
+.RB \[lq] gsize
+.IR n \[rq].
+.
+This option is deprecated.
+.
+.
+.TP
+.BI \-T\~ dev
+Prepare output for the device
+.IR dev .
+.
+In most cases,
+the effect of this is to define a macro
+.I dev
+with a value
+.RB of\~ 1 ;
+.I eqnrc
+uses this to provide definitions appropriate for the device.
+.
+However,
+if the specified driver is \[lq]MathML\[rq],
+the output is MathML markup rather than
+.I @g@troff
+input,
+and
+.I eqnrc
+is not loaded at all.
+.
+The default output device is
+.BR @DEVICE@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @MACRODIR@/\:\%eqnrc
+Initialization file.
+.
+.
+.\" ====================================================================
+.SH "MathML mode limitations"
+.\" ====================================================================
+.
+MathML is designed on the assumption that it cannot know the exact
+physical characteristics of the media and devices on which it will
+be rendered.
+.
+It does not support control of motions and sizes to the same
+degree
+.I @g@troff
+does.
+.
+.
+.IP \[bu] 2n
+.I @g@eqn
+customization parameters have no effect on generated MathML.
+.
+.
+.IP \[bu]
+The
+.BR \%special ,
+.BR up ,
+.BR down ,
+.BR fwd ,
+and
+.B back
+primitives cannot be implemented,
+and yield a MathML \%\[lq]<merror>\[rq] message instead.
+.
+.
+.IP \[bu]
+The
+.B vcenter
+primitive is silently ignored,
+as centering on the math axis is the MathML default.
+.
+.
+.IP \[bu]
+Characters that
+.I @g@eqn
+sets extra large in
+.I troff \" mode
+mode\[em]notably the integral sign\[em]may appear too small and need to
+have their \[lq]<mstyle>\[rq] wrappers adjusted by hand.
+.
+.
+.P
+As in its
+.I troff \" mode
+mode,
+.I @g@eqn
+in MathML mode leaves the
+.B .EQ
+and
+.B .EN
+tokens in place,
+but emits nothing corresponding to
+.B \%delim
+delimiters.
+.
+They can,
+however,
+be recognized as character sequences that begin with \[lq]<math>\[rq],
+end with \[lq]</math>\[rq],
+and do not cross line boundaries.
+.
+.
+.\" ====================================================================
+.SH Caveats
+.\" ====================================================================
+.
+Tokens must be double-quoted in
+.I eqn \" generic
+input if they are not to be recognized as names of macros or primitives,
+or if they are to be interpreted by
+.IR troff . \" generic
+.
+In particular,
+short ones,
+like
+.RB \[lq] pi \[rq]
+and
+.RB \[lq] PI \[rq],
+can collide with
+.I troff \" generic
+identifiers.
+.
+For instance,
+the
+.I eqn \" generic
+command
+.RB \%\[lq]\^ "gfont PI" \^\[rq]
+does not select
+.IR groff 's
+Palatino italic font for the global italic face;
+you must use
+.RB \%\[lq]\^ "gfont \[dq]PI\[dq]" \^\[rq]
+instead.
+.
+.
+.P
+Delimited equations are set at the type size current at the beginning of
+the input line,
+not necessarily that immediately preceding the opening delimiter.
+.
+.
+.P
+Unlike \*[tx],
+.I eqn \" generic
+does not inherently distinguish displayed and inline equation styles;
+see the
+.B smallover
+primitive above.
+.
+However,
+macro packages frequently define
+.B EQ
+and
+.B EN
+macros such that the equation within is displayed.
+.
+These macros may accept arguments permitting the equation to be labeled
+or captioned;
+see the package's documentation.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+.I eqn \" generic
+abuses terminology\[em]its
+\[lq]equations\[rq]
+can be inequalities,
+bare expressions,
+or unintelligible gibberish.
+.
+But there's no changing it now.
+.
+.
+.P
+In
+.I nroff \" mode
+mode,
+lowercase Greek letters are rendered in roman instead of italic style.
+.
+.
+.P
+In MathML mode,
+the
+.B mark
+and
+.B lineup
+features don't work.
+.
+These could,
+in theory,
+be implemented with \%\[lq]<maligngroup>\[rq] elements.
+.
+.
+.P
+In MathML mode,
+each digit of a numeric literal gets a separate \[lq]<mn>\:</mn>\[rq]
+pair,
+and decimal points are tagged with \[lq]<mo>\:</mo>\[rq].
+.
+This is allowed by the specification,
+but inefficient.
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+We first illustrate
+.I @g@eqn
+usage with a trigonometric identity.
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+sin ( alpha + beta ) = sin alpha cos beta + cos alpha sin beta
+\&.EN
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+sin ( alpha + beta ) = sin alpha cos beta + cos alpha sin beta
+.EN
+.RE
+.\}
+.RE
+.
+.
+.P
+It can be convenient to set up delimiters if mathematical content will
+appear frequently in running text.
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+delim $$
+\&.EN
+.
+Having cached a table of logarithms,
+the property $ln ( x y ) = ln x + ln y$ sped calculations.
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+delim $$
+.EN
+.
+Having cached a table of logarithms,
+the property $ln ( x y ) = ln x + ln y$ sped calculations.
+.
+.\" We _must_ shut delimiters back off when serially processing man
+.\" pages, or subsequent documents cannot safely use those characters.
+.EQ
+delim off
+.EN
+.RE
+.\}
+.RE
+.
+.
+.P
+The quadratic formula illustrates use of fractions and radicals,
+and affords an opportunity to use the full space token
+.BR \[ti] .
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+x = { \- b \[ti] \[rs][+\-] \[ti] sqrt { b sup 2 \- 4 a c } } \
+over { 2 a }
+\&.EN
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+x = { - b ~ \[+-] ~ sqrt { b sup 2 - 4 a c } } over { 2 a }
+.EN
+.RE
+.\}
+.RE
+.
+.
+.P
+Alternatively,
+we could define the plus-minus sign as a binary operator.
+.
+Automatic spacing puts 0.06\~em less space on either side of the
+plus-minus than \[ti] does,
+this being the difference between the widths of the
+.B medium_space
+parameter used by binary operators and that of the full space.
+.
+Independently,
+we can define a macro \[lq]frac\[rq] for setting fractions.
+.
+.
+.RS
+.P
+.EX
+\&.EQ
+chartype "binary" \[rs][+\-]
+define frac ! { $1 } over { $2 } !
+x = frac(\- b \[rs][+\-] sqrt { b sup 2 \- 4 a c }, 2 a)
+\&.EN
+.EE
+.if t \{\
+.
+.
+.P
+.RS
+.EQ
+chartype "binary" \[+-]
+define frac ! { $1 } over { $2 } !
+x = frac(- b \[+-] sqrt { b sup 2 - 4 a c }, 2 a)
+.EN
+.RE
+.\}
+.RE
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Typesetting Mathematics\[em]User's Guide\[rq]
+(2nd edition),
+by Brian W.\& Kernighan
+and Lorinda L.\& Cherry,
+1978,
+AT&T Bell Laboratories Computing Science Technical Report No.\& 17.
+.
+.
+.P
+.IR The\~\*[tx]book ,
+by Donald E.\& Knuth,
+1984,
+Addison-Wesley Professional.
+.
+Appendix\~G
+discusses many of the parameters from section \[lq]Customization\[rq]
+above in greater detail.
+.
+.
+.P
+.MR groff_char @MAN7EXT@ ,
+particularly subsections \[lq]Logical symbols\[rq],
+\[lq]Mathematical symbols\[rq],
+and \[lq]Greek glyphs\[rq],
+documents a variety of special character escape sequences useful in
+mathematical typesetting.
+.
+.
+.P
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR @g@pic @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm tx
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_eqn_1_man_C]
+.do rr *groff_eqn_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" tab-width: 12
+.\" End:
+.\" vim: set filetype=groff tabstop=12 textwidth=72:
diff --git a/src/preproc/eqn/eqn.am b/src/preproc/eqn/eqn.am
new file mode 100644
index 0000000..e32bfb5
--- /dev/null
+++ b/src/preproc/eqn/eqn.am
@@ -0,0 +1,79 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += eqn
+prefixexecbin_SCRIPTS += neqn
+eqn_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I $(top_srcdir)/src/preproc/eqn \
+ -I $(top_builddir)/src/preproc/eqn
+eqn_LDADD = $(LIBM) libgroff.a lib/libgnu.a
+eqn_SOURCES = \
+ src/preproc/eqn/main.cpp \
+ src/preproc/eqn/lex.cpp \
+ src/preproc/eqn/box.cpp \
+ src/preproc/eqn/limit.cpp \
+ src/preproc/eqn/list.cpp \
+ src/preproc/eqn/over.cpp \
+ src/preproc/eqn/text.cpp \
+ src/preproc/eqn/script.cpp \
+ src/preproc/eqn/mark.cpp \
+ src/preproc/eqn/other.cpp \
+ src/preproc/eqn/delim.cpp \
+ src/preproc/eqn/sqrt.cpp \
+ src/preproc/eqn/pile.cpp \
+ src/preproc/eqn/special.cpp \
+ src/preproc/eqn/eqn.ypp \
+ src/preproc/eqn/box.h \
+ src/preproc/eqn/pbox.h \
+ src/preproc/eqn/eqn.h
+
+PREFIXMAN1 += src/preproc/eqn/eqn.1 src/preproc/eqn/neqn.1
+EXTRA_DIST += \
+ src/preproc/eqn/TODO \
+ src/preproc/eqn/eqn.1.man \
+ src/preproc/eqn/neqn.1.man \
+ src/preproc/eqn/neqn.sh
+
+# Since eqn_CPPFLAGS was set, all .o files have an 'eqn-' prefix.
+src/preproc/eqn/eqn-lex.$(OBJEXT): src/preproc/eqn/eqn.hpp
+
+MAINTAINERCLEANFILES += \
+ src/preproc/eqn/eqn.hpp \
+ src/preproc/eqn/eqn.cpp \
+ src/preproc/eqn/eqn.output
+
+neqn: $(top_srcdir)/src/preproc/eqn/neqn.sh $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)sed -e 's/[@]g[@]/$(g)/g' \
+ -f $(SH_DEPS_SED_SCRIPT) \
+ -e $(SH_SCRIPT_SED_CMD) \
+ $(top_srcdir)/src/preproc/eqn/neqn.sh \
+ >$@.tmp \
+ && chmod +x $@.tmp \
+ && mv $@.tmp $@
+
+eqn_TESTS = \
+ src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh
+TESTS += $(eqn_TESTS)
+EXTRA_DIST += $(eqn_TESTS)
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/eqn/eqn.cpp b/src/preproc/eqn/eqn.cpp
new file mode 100644
index 0000000..6756b9e
--- /dev/null
+++ b/src/preproc/eqn/eqn.cpp
@@ -0,0 +1,2112 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30802
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.8.2"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* First part of user prologue. */
+#line 18 "../src/preproc/eqn/eqn.ypp"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "lib.h"
+#include "box.h"
+extern int non_empty_flag;
+int yylex();
+void yyerror(const char *);
+
+#line 87 "src/preproc/eqn/eqn.cpp"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Use api.header.include to #include this header
+ instead of duplicating it here. */
+#ifndef YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ OVER = 258, /* OVER */
+ SMALLOVER = 259, /* SMALLOVER */
+ SQRT = 260, /* SQRT */
+ SUB = 261, /* SUB */
+ SUP = 262, /* SUP */
+ LPILE = 263, /* LPILE */
+ RPILE = 264, /* RPILE */
+ CPILE = 265, /* CPILE */
+ PILE = 266, /* PILE */
+ LEFT = 267, /* LEFT */
+ RIGHT = 268, /* RIGHT */
+ TO = 269, /* TO */
+ FROM = 270, /* FROM */
+ SIZE = 271, /* SIZE */
+ FONT = 272, /* FONT */
+ ROMAN = 273, /* ROMAN */
+ BOLD = 274, /* BOLD */
+ ITALIC = 275, /* ITALIC */
+ FAT = 276, /* FAT */
+ ACCENT = 277, /* ACCENT */
+ BAR = 278, /* BAR */
+ UNDER = 279, /* UNDER */
+ ABOVE = 280, /* ABOVE */
+ TEXT = 281, /* TEXT */
+ QUOTED_TEXT = 282, /* QUOTED_TEXT */
+ FWD = 283, /* FWD */
+ BACK = 284, /* BACK */
+ DOWN = 285, /* DOWN */
+ UP = 286, /* UP */
+ MATRIX = 287, /* MATRIX */
+ COL = 288, /* COL */
+ LCOL = 289, /* LCOL */
+ RCOL = 290, /* RCOL */
+ CCOL = 291, /* CCOL */
+ MARK = 292, /* MARK */
+ LINEUP = 293, /* LINEUP */
+ TYPE = 294, /* TYPE */
+ VCENTER = 295, /* VCENTER */
+ PRIME = 296, /* PRIME */
+ SPLIT = 297, /* SPLIT */
+ NOSPLIT = 298, /* NOSPLIT */
+ UACCENT = 299, /* UACCENT */
+ SPECIAL = 300, /* SPECIAL */
+ SPACE = 301, /* SPACE */
+ GFONT = 302, /* GFONT */
+ GSIZE = 303, /* GSIZE */
+ DEFINE = 304, /* DEFINE */
+ NDEFINE = 305, /* NDEFINE */
+ TDEFINE = 306, /* TDEFINE */
+ SDEFINE = 307, /* SDEFINE */
+ UNDEF = 308, /* UNDEF */
+ IFDEF = 309, /* IFDEF */
+ INCLUDE = 310, /* INCLUDE */
+ DELIM = 311, /* DELIM */
+ CHARTYPE = 312, /* CHARTYPE */
+ SET = 313, /* SET */
+ GRFONT = 314, /* GRFONT */
+ GBFONT = 315 /* GBFONT */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define OVER 258
+#define SMALLOVER 259
+#define SQRT 260
+#define SUB 261
+#define SUP 262
+#define LPILE 263
+#define RPILE 264
+#define CPILE 265
+#define PILE 266
+#define LEFT 267
+#define RIGHT 268
+#define TO 269
+#define FROM 270
+#define SIZE 271
+#define FONT 272
+#define ROMAN 273
+#define BOLD 274
+#define ITALIC 275
+#define FAT 276
+#define ACCENT 277
+#define BAR 278
+#define UNDER 279
+#define ABOVE 280
+#define TEXT 281
+#define QUOTED_TEXT 282
+#define FWD 283
+#define BACK 284
+#define DOWN 285
+#define UP 286
+#define MATRIX 287
+#define COL 288
+#define LCOL 289
+#define RCOL 290
+#define CCOL 291
+#define MARK 292
+#define LINEUP 293
+#define TYPE 294
+#define VCENTER 295
+#define PRIME 296
+#define SPLIT 297
+#define NOSPLIT 298
+#define UACCENT 299
+#define SPECIAL 300
+#define SPACE 301
+#define GFONT 302
+#define GSIZE 303
+#define DEFINE 304
+#define NDEFINE 305
+#define TDEFINE 306
+#define SDEFINE 307
+#define UNDEF 308
+#define IFDEF 309
+#define INCLUDE 310
+#define DELIM 311
+#define CHARTYPE 312
+#define SET 313
+#define GRFONT 314
+#define GBFONT 315
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 34 "../src/preproc/eqn/eqn.ypp"
+
+ char *str;
+ box *b;
+ pile_box *pb;
+ matrix_box *mb;
+ int n;
+ column *col;
+
+#line 269 "src/preproc/eqn/eqn.cpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED */
+/* Symbol kind. */
+enum yysymbol_kind_t
+{
+ YYSYMBOL_YYEMPTY = -2,
+ YYSYMBOL_YYEOF = 0, /* "end of file" */
+ YYSYMBOL_YYerror = 1, /* error */
+ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */
+ YYSYMBOL_OVER = 3, /* OVER */
+ YYSYMBOL_SMALLOVER = 4, /* SMALLOVER */
+ YYSYMBOL_SQRT = 5, /* SQRT */
+ YYSYMBOL_SUB = 6, /* SUB */
+ YYSYMBOL_SUP = 7, /* SUP */
+ YYSYMBOL_LPILE = 8, /* LPILE */
+ YYSYMBOL_RPILE = 9, /* RPILE */
+ YYSYMBOL_CPILE = 10, /* CPILE */
+ YYSYMBOL_PILE = 11, /* PILE */
+ YYSYMBOL_LEFT = 12, /* LEFT */
+ YYSYMBOL_RIGHT = 13, /* RIGHT */
+ YYSYMBOL_TO = 14, /* TO */
+ YYSYMBOL_FROM = 15, /* FROM */
+ YYSYMBOL_SIZE = 16, /* SIZE */
+ YYSYMBOL_FONT = 17, /* FONT */
+ YYSYMBOL_ROMAN = 18, /* ROMAN */
+ YYSYMBOL_BOLD = 19, /* BOLD */
+ YYSYMBOL_ITALIC = 20, /* ITALIC */
+ YYSYMBOL_FAT = 21, /* FAT */
+ YYSYMBOL_ACCENT = 22, /* ACCENT */
+ YYSYMBOL_BAR = 23, /* BAR */
+ YYSYMBOL_UNDER = 24, /* UNDER */
+ YYSYMBOL_ABOVE = 25, /* ABOVE */
+ YYSYMBOL_TEXT = 26, /* TEXT */
+ YYSYMBOL_QUOTED_TEXT = 27, /* QUOTED_TEXT */
+ YYSYMBOL_FWD = 28, /* FWD */
+ YYSYMBOL_BACK = 29, /* BACK */
+ YYSYMBOL_DOWN = 30, /* DOWN */
+ YYSYMBOL_UP = 31, /* UP */
+ YYSYMBOL_MATRIX = 32, /* MATRIX */
+ YYSYMBOL_COL = 33, /* COL */
+ YYSYMBOL_LCOL = 34, /* LCOL */
+ YYSYMBOL_RCOL = 35, /* RCOL */
+ YYSYMBOL_CCOL = 36, /* CCOL */
+ YYSYMBOL_MARK = 37, /* MARK */
+ YYSYMBOL_LINEUP = 38, /* LINEUP */
+ YYSYMBOL_TYPE = 39, /* TYPE */
+ YYSYMBOL_VCENTER = 40, /* VCENTER */
+ YYSYMBOL_PRIME = 41, /* PRIME */
+ YYSYMBOL_SPLIT = 42, /* SPLIT */
+ YYSYMBOL_NOSPLIT = 43, /* NOSPLIT */
+ YYSYMBOL_UACCENT = 44, /* UACCENT */
+ YYSYMBOL_SPECIAL = 45, /* SPECIAL */
+ YYSYMBOL_SPACE = 46, /* SPACE */
+ YYSYMBOL_GFONT = 47, /* GFONT */
+ YYSYMBOL_GSIZE = 48, /* GSIZE */
+ YYSYMBOL_DEFINE = 49, /* DEFINE */
+ YYSYMBOL_NDEFINE = 50, /* NDEFINE */
+ YYSYMBOL_TDEFINE = 51, /* TDEFINE */
+ YYSYMBOL_SDEFINE = 52, /* SDEFINE */
+ YYSYMBOL_UNDEF = 53, /* UNDEF */
+ YYSYMBOL_IFDEF = 54, /* IFDEF */
+ YYSYMBOL_INCLUDE = 55, /* INCLUDE */
+ YYSYMBOL_DELIM = 56, /* DELIM */
+ YYSYMBOL_CHARTYPE = 57, /* CHARTYPE */
+ YYSYMBOL_SET = 58, /* SET */
+ YYSYMBOL_GRFONT = 59, /* GRFONT */
+ YYSYMBOL_GBFONT = 60, /* GBFONT */
+ YYSYMBOL_61_ = 61, /* '^' */
+ YYSYMBOL_62_ = 62, /* '~' */
+ YYSYMBOL_63_t_ = 63, /* '\t' */
+ YYSYMBOL_64_ = 64, /* '{' */
+ YYSYMBOL_65_ = 65, /* '}' */
+ YYSYMBOL_YYACCEPT = 66, /* $accept */
+ YYSYMBOL_top = 67, /* top */
+ YYSYMBOL_equation = 68, /* equation */
+ YYSYMBOL_mark = 69, /* mark */
+ YYSYMBOL_from_to = 70, /* from_to */
+ YYSYMBOL_sqrt_over = 71, /* sqrt_over */
+ YYSYMBOL_script = 72, /* script */
+ YYSYMBOL_nonsup = 73, /* nonsup */
+ YYSYMBOL_simple = 74, /* simple */
+ YYSYMBOL_number = 75, /* number */
+ YYSYMBOL_pile_element_list = 76, /* pile_element_list */
+ YYSYMBOL_pile_arg = 77, /* pile_arg */
+ YYSYMBOL_column_list = 78, /* column_list */
+ YYSYMBOL_column_element_list = 79, /* column_element_list */
+ YYSYMBOL_column_arg = 80, /* column_arg */
+ YYSYMBOL_column = 81, /* column */
+ YYSYMBOL_text = 82, /* text */
+ YYSYMBOL_delim = 83 /* delim */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_uint8 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if !defined yyoverflow
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* !defined yyoverflow */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yy_state_t yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 72
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 379
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 66
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 18
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 75
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 142
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 315
+
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ (0 <= (YYX) && (YYX) <= YYMAXUTOK \
+ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \
+ : YYSYMBOL_YYUNDEF)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_int8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 63,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 61, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 64, 2, 65, 62, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60
+};
+
+#if YYDEBUG
+/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_int16 yyrline[] =
+{
+ 0, 125, 125, 127, 132, 134, 145, 147, 149, 154,
+ 156, 158, 160, 162, 167, 169, 171, 173, 178, 180,
+ 185, 187, 189, 194, 196, 198, 200, 202, 204, 206,
+ 208, 210, 212, 214, 216, 218, 220, 222, 224, 226,
+ 228, 230, 232, 234, 236, 238, 240, 242, 244, 246,
+ 248, 250, 252, 254, 256, 258, 263, 273, 275, 280,
+ 282, 287, 289, 294, 296, 301, 303, 308, 310, 312,
+ 314, 318, 320, 325, 327, 329
+};
+#endif
+
+/** Accessing symbol of state STATE. */
+#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State])
+
+#if YYDEBUG || 0
+/* The user-facing name of the symbol whose (internal) number is
+ YYSYMBOL. No bounds checking. */
+static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
+
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "\"end of file\"", "error", "\"invalid token\"", "OVER", "SMALLOVER",
+ "SQRT", "SUB", "SUP", "LPILE", "RPILE", "CPILE", "PILE", "LEFT", "RIGHT",
+ "TO", "FROM", "SIZE", "FONT", "ROMAN", "BOLD", "ITALIC", "FAT", "ACCENT",
+ "BAR", "UNDER", "ABOVE", "TEXT", "QUOTED_TEXT", "FWD", "BACK", "DOWN",
+ "UP", "MATRIX", "COL", "LCOL", "RCOL", "CCOL", "MARK", "LINEUP", "TYPE",
+ "VCENTER", "PRIME", "SPLIT", "NOSPLIT", "UACCENT", "SPECIAL", "SPACE",
+ "GFONT", "GSIZE", "DEFINE", "NDEFINE", "TDEFINE", "SDEFINE", "UNDEF",
+ "IFDEF", "INCLUDE", "DELIM", "CHARTYPE", "SET", "GRFONT", "GBFONT",
+ "'^'", "'~'", "'\\t'", "'{'", "'}'", "$accept", "top", "equation",
+ "mark", "from_to", "sqrt_over", "script", "nonsup", "simple", "number",
+ "pile_element_list", "pile_arg", "column_list", "column_element_list",
+ "column_arg", "column", "text", "delim", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#define YYPACT_NINF (-76)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-1)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int16 yypact[] =
+{
+ 230, 269, 6, 6, 6, 6, 2, 14, 14, 308,
+ 308, 308, 308, -76, -76, 14, 14, 14, 14, -50,
+ 230, 230, 14, 308, 4, 23, 14, -76, -76, -76,
+ 230, 24, 230, -76, -76, 70, -76, -76, 20, -76,
+ -76, -76, 230, -44, -76, -76, -76, -76, -76, -76,
+ -76, -76, 230, 308, 308, 57, 57, 57, 57, 308,
+ 308, 308, 308, 3, -76, -76, 308, 57, -76, -76,
+ 308, 130, -76, -76, 269, 269, 269, 269, 308, 308,
+ 308, -76, -76, -76, 308, 230, -12, 230, 191, 57,
+ 57, 57, 57, 57, 57, 8, 8, 8, 8, 12,
+ -76, 57, 57, -76, -76, -76, -76, 79, -76, 335,
+ -76, -76, -76, 230, -76, -6, 2, 230, 28, -76,
+ -76, -76, -76, -76, -76, 269, 269, 308, 230, -76,
+ -76, 230, -3, 230, -76, -76, -76, 230, -76, -2,
+ 230, -76
+};
+
+/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_int8 yydefact[] =
+{
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 23, 24, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 27, 28, 29,
+ 0, 0, 3, 4, 6, 9, 14, 18, 20, 15,
+ 71, 72, 0, 0, 32, 56, 33, 34, 31, 74,
+ 75, 73, 0, 0, 0, 43, 44, 45, 46, 0,
+ 0, 0, 0, 0, 7, 8, 0, 54, 25, 26,
+ 0, 0, 1, 5, 0, 0, 0, 0, 0, 0,
+ 0, 38, 39, 40, 0, 57, 0, 0, 37, 48,
+ 47, 49, 50, 52, 51, 0, 0, 0, 0, 0,
+ 61, 53, 55, 30, 16, 17, 10, 11, 21, 20,
+ 19, 41, 42, 0, 59, 0, 0, 0, 0, 67,
+ 68, 69, 70, 35, 62, 0, 0, 0, 58, 60,
+ 36, 63, 0, 0, 12, 13, 22, 0, 65, 0,
+ 64, 66
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -76, -76, 0, -17, -75, 1, -67, -13, 46, -7,
+ 9, 13, -76, -47, 22, -4, -1, -29
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_uint8 yydefgoto[] =
+{
+ 0, 31, 85, 33, 34, 35, 36, 37, 38, 43,
+ 86, 44, 99, 132, 119, 100, 45, 52
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_uint8 yytable[] =
+{
+ 32, 106, 39, 64, 65, 51, 53, 54, 59, 60,
+ 61, 62, 110, 113, 63, 73, 46, 47, 48, 113,
+ 87, 66, 137, 137, 72, 70, 78, 79, 40, 41,
+ 71, 68, 40, 41, 40, 41, 95, 96, 97, 98,
+ 40, 41, 80, 81, 82, 95, 96, 97, 98, 69,
+ 134, 135, 88, 114, 73, 55, 56, 57, 58, 129,
+ 136, 83, 138, 141, 84, 108, 49, 50, 73, 67,
+ 42, 73, 117, 74, 75, 104, 105, 123, 107, 80,
+ 81, 82, 74, 75, 76, 77, 139, 130, 118, 118,
+ 118, 118, 133, 125, 126, 124, 115, 0, 83, 89,
+ 90, 84, 0, 0, 0, 91, 92, 93, 94, 0,
+ 0, 73, 101, 128, 73, 51, 102, 131, 120, 121,
+ 122, 0, 0, 73, 109, 0, 111, 0, 0, 0,
+ 112, 0, 0, 131, 0, 1, 0, 140, 2, 3,
+ 4, 5, 6, 0, 0, 0, 7, 8, 9, 10,
+ 11, 12, 0, 0, 0, 0, 13, 14, 15, 16,
+ 17, 18, 19, 0, 0, 0, 0, 20, 21, 22,
+ 23, 0, 24, 25, 0, 26, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 27, 28, 29, 30, 103, 1, 0, 0, 2,
+ 3, 4, 5, 6, 116, 0, 0, 7, 8, 9,
+ 10, 11, 12, 0, 0, 0, 0, 13, 14, 15,
+ 16, 17, 18, 19, 0, 0, 0, 0, 20, 21,
+ 22, 23, 0, 24, 25, 1, 26, 0, 2, 3,
+ 4, 5, 6, 0, 0, 0, 7, 8, 9, 10,
+ 11, 12, 27, 28, 29, 30, 13, 14, 15, 16,
+ 17, 18, 19, 0, 0, 0, 0, 20, 21, 22,
+ 23, 0, 24, 25, 1, 26, 0, 2, 3, 4,
+ 5, 6, 0, 0, 0, 7, 8, 9, 10, 11,
+ 12, 27, 28, 29, 30, 13, 14, 15, 16, 17,
+ 18, 19, 0, 0, 0, 0, 0, 0, 22, 23,
+ 0, 24, 25, 0, 26, 0, 2, 3, 4, 5,
+ 6, 0, 0, 0, 7, 8, 9, 10, 11, 12,
+ 27, 28, 29, 30, 13, 14, 15, 16, 17, 18,
+ 19, 78, 127, 0, 0, 0, 0, 22, 23, 0,
+ 24, 25, 0, 26, 0, 0, 0, 80, 81, 82,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 27,
+ 28, 29, 30, 0, 0, 0, 83, 0, 0, 84
+};
+
+static const yytype_int16 yycheck[] =
+{
+ 0, 76, 1, 20, 21, 6, 7, 8, 15, 16,
+ 17, 18, 79, 25, 64, 32, 3, 4, 5, 25,
+ 64, 22, 25, 25, 0, 26, 6, 7, 26, 27,
+ 30, 27, 26, 27, 26, 27, 33, 34, 35, 36,
+ 26, 27, 22, 23, 24, 33, 34, 35, 36, 26,
+ 125, 126, 52, 65, 71, 9, 10, 11, 12, 65,
+ 127, 41, 65, 65, 44, 78, 64, 65, 85, 23,
+ 64, 88, 64, 3, 4, 74, 75, 65, 77, 22,
+ 23, 24, 3, 4, 14, 15, 133, 116, 95, 96,
+ 97, 98, 64, 14, 15, 99, 87, -1, 41, 53,
+ 54, 44, -1, -1, -1, 59, 60, 61, 62, -1,
+ -1, 128, 66, 113, 131, 116, 70, 117, 96, 97,
+ 98, -1, -1, 140, 78, -1, 80, -1, -1, -1,
+ 84, -1, -1, 133, -1, 5, -1, 137, 8, 9,
+ 10, 11, 12, -1, -1, -1, 16, 17, 18, 19,
+ 20, 21, -1, -1, -1, -1, 26, 27, 28, 29,
+ 30, 31, 32, -1, -1, -1, -1, 37, 38, 39,
+ 40, -1, 42, 43, -1, 45, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 61, 62, 63, 64, 65, 5, -1, -1, 8,
+ 9, 10, 11, 12, 13, -1, -1, 16, 17, 18,
+ 19, 20, 21, -1, -1, -1, -1, 26, 27, 28,
+ 29, 30, 31, 32, -1, -1, -1, -1, 37, 38,
+ 39, 40, -1, 42, 43, 5, 45, -1, 8, 9,
+ 10, 11, 12, -1, -1, -1, 16, 17, 18, 19,
+ 20, 21, 61, 62, 63, 64, 26, 27, 28, 29,
+ 30, 31, 32, -1, -1, -1, -1, 37, 38, 39,
+ 40, -1, 42, 43, 5, 45, -1, 8, 9, 10,
+ 11, 12, -1, -1, -1, 16, 17, 18, 19, 20,
+ 21, 61, 62, 63, 64, 26, 27, 28, 29, 30,
+ 31, 32, -1, -1, -1, -1, -1, -1, 39, 40,
+ -1, 42, 43, -1, 45, -1, 8, 9, 10, 11,
+ 12, -1, -1, -1, 16, 17, 18, 19, 20, 21,
+ 61, 62, 63, 64, 26, 27, 28, 29, 30, 31,
+ 32, 6, 7, -1, -1, -1, -1, 39, 40, -1,
+ 42, 43, -1, 45, -1, -1, -1, 22, 23, 24,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, 61,
+ 62, 63, 64, -1, -1, -1, 41, -1, -1, 44
+};
+
+/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ state STATE-NUM. */
+static const yytype_int8 yystos[] =
+{
+ 0, 5, 8, 9, 10, 11, 12, 16, 17, 18,
+ 19, 20, 21, 26, 27, 28, 29, 30, 31, 32,
+ 37, 38, 39, 40, 42, 43, 45, 61, 62, 63,
+ 64, 67, 68, 69, 70, 71, 72, 73, 74, 71,
+ 26, 27, 64, 75, 77, 82, 77, 77, 77, 64,
+ 65, 82, 83, 82, 82, 74, 74, 74, 74, 75,
+ 75, 75, 75, 64, 69, 69, 82, 74, 27, 26,
+ 82, 68, 0, 69, 3, 4, 14, 15, 6, 7,
+ 22, 23, 24, 41, 44, 68, 76, 64, 68, 74,
+ 74, 74, 74, 74, 74, 33, 34, 35, 36, 78,
+ 81, 74, 74, 65, 71, 71, 70, 71, 73, 74,
+ 72, 74, 74, 25, 65, 76, 13, 64, 75, 80,
+ 80, 80, 80, 65, 81, 14, 15, 7, 68, 65,
+ 83, 68, 79, 64, 70, 70, 72, 25, 65, 79,
+ 68, 65
+};
+
+/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr1[] =
+{
+ 0, 66, 67, 67, 68, 68, 69, 69, 69, 70,
+ 70, 70, 70, 70, 71, 71, 71, 71, 72, 72,
+ 73, 73, 73, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 75, 76, 76, 77,
+ 77, 78, 78, 79, 79, 80, 80, 81, 81, 81,
+ 81, 82, 82, 83, 83, 83
+};
+
+/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 0, 1, 1, 2, 1, 2, 2, 1,
+ 3, 3, 5, 5, 1, 2, 3, 3, 1, 3,
+ 1, 3, 5, 1, 1, 2, 2, 1, 1, 1,
+ 3, 2, 2, 2, 2, 4, 5, 3, 2, 2,
+ 2, 3, 3, 2, 2, 2, 2, 3, 3, 3,
+ 3, 3, 3, 3, 2, 3, 1, 1, 3, 3,
+ 4, 1, 2, 1, 3, 3, 4, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYNOMEM goto yyexhaustedlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (!yyvaluep)
+ return;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ yy_symbol_value_print (yyo, yykind, yyvaluep);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)]);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep)
+{
+ YY_USE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/* Lookahead token kind. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ int yyn;
+ /* The return value of yyparse. */
+ int yyresult;
+ /* Lookahead symbol kind. */
+ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yysetstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ YY_IGNORE_USELESS_CAST_BEGIN
+ *yyssp = YY_CAST (yy_state_t, yystate);
+ YY_IGNORE_USELESS_CAST_END
+ YY_STACK_PRINT (yyss, yyssp);
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ YYNOMEM;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYPTRDIFF_T yysize = yyssp - yyss + 1;
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ yy_state_t *yyss1 = yyss;
+ YYSTYPE *yyvs1 = yyvs;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * YYSIZEOF (*yyssp),
+ &yyvs1, yysize * YYSIZEOF (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ YYNOMEM;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yy_state_t *yyss1 = yyss;
+ union yyalloc *yyptr =
+ YY_CAST (union yyalloc *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize))));
+ if (! yyptr)
+ YYNOMEM;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YY_IGNORE_USELESS_CAST_BEGIN
+ YYDPRINTF ((stderr, "Stack size increased to %ld\n",
+ YY_CAST (long, yystacksize)));
+ YY_IGNORE_USELESS_CAST_END
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token\n"));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = YYEOF;
+ yytoken = YYSYMBOL_YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else if (yychar == YYerror)
+ {
+ /* The scanner already issued an error message, process directly
+ to error recovery. But do not keep the error token as
+ lookahead, it is too special and may lead us to an endless
+ loop in error recovery. */
+ yychar = YYUNDEF;
+ yytoken = YYSYMBOL_YYerror;
+ goto yyerrlab1;
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 3: /* top: equation */
+#line 128 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].b)->top_level(); non_empty_flag = 1; }
+#line 1472 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 4: /* equation: mark */
+#line 133 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1478 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 5: /* equation: equation mark */
+#line 135 "../src/preproc/eqn/eqn.ypp"
+ {
+ list_box *lb = (yyvsp[-1].b)->to_list_box();
+ if (!lb)
+ lb = new list_box((yyvsp[-1].b));
+ lb->append((yyvsp[0].b));
+ (yyval.b) = lb;
+ }
+#line 1490 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 6: /* mark: from_to */
+#line 146 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1496 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 7: /* mark: MARK mark */
+#line 148 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_mark_box((yyvsp[0].b)); }
+#line 1502 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 8: /* mark: LINEUP mark */
+#line 150 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_lineup_box((yyvsp[0].b)); }
+#line 1508 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 9: /* from_to: sqrt_over */
+#line 155 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1514 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 10: /* from_to: sqrt_over TO from_to */
+#line 157 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-2].b), 0, (yyvsp[0].b)); }
+#line 1520 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 11: /* from_to: sqrt_over FROM sqrt_over */
+#line 159 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-2].b), (yyvsp[0].b), 0); }
+#line 1526 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 12: /* from_to: sqrt_over FROM sqrt_over TO from_to */
+#line 161 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-4].b), (yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1532 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 13: /* from_to: sqrt_over FROM sqrt_over FROM from_to */
+#line 163 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_limit_box((yyvsp[-4].b), make_limit_box((yyvsp[-2].b), (yyvsp[0].b), 0), 0); }
+#line 1538 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 14: /* sqrt_over: script */
+#line 168 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1544 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 15: /* sqrt_over: SQRT sqrt_over */
+#line 170 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_sqrt_box((yyvsp[0].b)); }
+#line 1550 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 16: /* sqrt_over: sqrt_over OVER sqrt_over */
+#line 172 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_over_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1556 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 17: /* sqrt_over: sqrt_over SMALLOVER sqrt_over */
+#line 174 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_small_over_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1562 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 18: /* script: nonsup */
+#line 179 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1568 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 19: /* script: simple SUP script */
+#line 181 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_script_box((yyvsp[-2].b), 0, (yyvsp[0].b)); }
+#line 1574 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 20: /* nonsup: simple */
+#line 186 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[0].b); }
+#line 1580 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 21: /* nonsup: simple SUB nonsup */
+#line 188 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_script_box((yyvsp[-2].b), (yyvsp[0].b), 0); }
+#line 1586 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 22: /* nonsup: simple SUB simple SUP script */
+#line 190 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_script_box((yyvsp[-4].b), (yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1592 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 23: /* simple: TEXT */
+#line 195 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = split_text((yyvsp[0].str)); }
+#line 1598 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 24: /* simple: QUOTED_TEXT */
+#line 197 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new quoted_text_box((yyvsp[0].str)); }
+#line 1604 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 25: /* simple: SPLIT QUOTED_TEXT */
+#line 199 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = split_text((yyvsp[0].str)); }
+#line 1610 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 26: /* simple: NOSPLIT TEXT */
+#line 201 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new quoted_text_box((yyvsp[0].str)); }
+#line 1616 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 27: /* simple: '^' */
+#line 203 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new half_space_box; }
+#line 1622 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 28: /* simple: '~' */
+#line 205 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new space_box; }
+#line 1628 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 29: /* simple: '\t' */
+#line 207 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new tab_box; }
+#line 1634 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 30: /* simple: '{' equation '}' */
+#line 209 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[-1].b); }
+#line 1640 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 31: /* simple: PILE pile_arg */
+#line 211 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(CENTER_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1646 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 32: /* simple: LPILE pile_arg */
+#line 213 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(LEFT_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1652 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 33: /* simple: RPILE pile_arg */
+#line 215 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(RIGHT_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1658 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 34: /* simple: CPILE pile_arg */
+#line 217 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].pb)->set_alignment(CENTER_ALIGN); (yyval.b) = (yyvsp[0].pb); }
+#line 1664 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 35: /* simple: MATRIX '{' column_list '}' */
+#line 219 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = (yyvsp[-1].mb); }
+#line 1670 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 36: /* simple: LEFT delim equation RIGHT delim */
+#line 221 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_delim_box((yyvsp[-3].str), (yyvsp[-2].b), (yyvsp[0].str)); }
+#line 1676 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 37: /* simple: LEFT delim equation */
+#line 223 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_delim_box((yyvsp[-1].str), (yyvsp[0].b), 0); }
+#line 1682 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 38: /* simple: simple BAR */
+#line 225 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_overline_box((yyvsp[-1].b)); }
+#line 1688 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 39: /* simple: simple UNDER */
+#line 227 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_underline_box((yyvsp[-1].b)); }
+#line 1694 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 40: /* simple: simple PRIME */
+#line 229 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_prime_box((yyvsp[-1].b)); }
+#line 1700 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 41: /* simple: simple ACCENT simple */
+#line 231 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_accent_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1706 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 42: /* simple: simple UACCENT simple */
+#line 233 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_uaccent_box((yyvsp[-2].b), (yyvsp[0].b)); }
+#line 1712 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 43: /* simple: ROMAN simple */
+#line 235 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box(strsave(get_grfont()), (yyvsp[0].b)); }
+#line 1718 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 44: /* simple: BOLD simple */
+#line 237 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box(strsave(get_gbfont()), (yyvsp[0].b)); }
+#line 1724 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 45: /* simple: ITALIC simple */
+#line 239 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box(strsave(get_gfont()), (yyvsp[0].b)); }
+#line 1730 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 46: /* simple: FAT simple */
+#line 241 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new fat_box((yyvsp[0].b)); }
+#line 1736 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 47: /* simple: FONT text simple */
+#line 243 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new font_box((yyvsp[-1].str), (yyvsp[0].b)); }
+#line 1742 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 48: /* simple: SIZE text simple */
+#line 245 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new size_box((yyvsp[-1].str), (yyvsp[0].b)); }
+#line 1748 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 49: /* simple: FWD number simple */
+#line 247 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new hmotion_box((yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1754 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 50: /* simple: BACK number simple */
+#line 249 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new hmotion_box(-(yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1760 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 51: /* simple: UP number simple */
+#line 251 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new vmotion_box((yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1766 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 52: /* simple: DOWN number simple */
+#line 253 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new vmotion_box(-(yyvsp[-1].n), (yyvsp[0].b)); }
+#line 1772 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 53: /* simple: TYPE text simple */
+#line 255 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].b)->set_spacing_type((yyvsp[-1].str)); (yyval.b) = (yyvsp[0].b); }
+#line 1778 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 54: /* simple: VCENTER simple */
+#line 257 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = new vcenter_box((yyvsp[0].b)); }
+#line 1784 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 55: /* simple: SPECIAL text simple */
+#line 259 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.b) = make_special_box((yyvsp[-1].str), (yyvsp[0].b)); }
+#line 1790 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 56: /* number: text */
+#line 264 "../src/preproc/eqn/eqn.ypp"
+ {
+ int n;
+ if (sscanf((yyvsp[0].str), "%d", &n) == 1)
+ (yyval.n) = n;
+ delete[] (yyvsp[0].str);
+ }
+#line 1801 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 57: /* pile_element_list: equation */
+#line 274 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.pb) = new pile_box((yyvsp[0].b)); }
+#line 1807 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 58: /* pile_element_list: pile_element_list ABOVE equation */
+#line 276 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-2].pb)->append((yyvsp[0].b)); (yyval.pb) = (yyvsp[-2].pb); }
+#line 1813 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 59: /* pile_arg: '{' pile_element_list '}' */
+#line 281 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.pb) = (yyvsp[-1].pb); }
+#line 1819 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 60: /* pile_arg: number '{' pile_element_list '}' */
+#line 283 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-1].pb)->set_space((yyvsp[-3].n)); (yyval.pb) = (yyvsp[-1].pb); }
+#line 1825 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 61: /* column_list: column */
+#line 288 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.mb) = new matrix_box((yyvsp[0].col)); }
+#line 1831 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 62: /* column_list: column_list column */
+#line 290 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-1].mb)->append((yyvsp[0].col)); (yyval.mb) = (yyvsp[-1].mb); }
+#line 1837 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 63: /* column_element_list: equation */
+#line 295 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.col) = new column((yyvsp[0].b)); }
+#line 1843 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 64: /* column_element_list: column_element_list ABOVE equation */
+#line 297 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-2].col)->append((yyvsp[0].b)); (yyval.col) = (yyvsp[-2].col); }
+#line 1849 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 65: /* column_arg: '{' column_element_list '}' */
+#line 302 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.col) = (yyvsp[-1].col); }
+#line 1855 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 66: /* column_arg: number '{' column_element_list '}' */
+#line 304 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[-1].col)->set_space((yyvsp[-3].n)); (yyval.col) = (yyvsp[-1].col); }
+#line 1861 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 67: /* column: COL column_arg */
+#line 309 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(CENTER_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1867 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 68: /* column: LCOL column_arg */
+#line 311 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(LEFT_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1873 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 69: /* column: RCOL column_arg */
+#line 313 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(RIGHT_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1879 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 70: /* column: CCOL column_arg */
+#line 315 "../src/preproc/eqn/eqn.ypp"
+ { (yyvsp[0].col)->set_alignment(CENTER_ALIGN); (yyval.col) = (yyvsp[0].col); }
+#line 1885 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 71: /* text: TEXT */
+#line 319 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1891 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 72: /* text: QUOTED_TEXT */
+#line 321 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1897 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 73: /* delim: text */
+#line 326 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = (yyvsp[0].str); }
+#line 1903 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 74: /* delim: '{' */
+#line 328 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = strsave("{"); }
+#line 1909 "src/preproc/eqn/eqn.cpp"
+ break;
+
+ case 75: /* delim: '}' */
+#line 330 "../src/preproc/eqn/eqn.ypp"
+ { (yyval.str) = strsave("}"); }
+#line 1915 "src/preproc/eqn/eqn.cpp"
+ break;
+
+
+#line 1919 "src/preproc/eqn/eqn.cpp"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ yyerror (YY_("syntax error"));
+ }
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+ ++yynerrs;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ /* Pop stack until we find a state that shifts the error token. */
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYSYMBOL_YYerror;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturnlab;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturnlab;
+
+
+/*-----------------------------------------------------------.
+| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. |
+`-----------------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturnlab;
+
+
+/*----------------------------------------------------------.
+| yyreturnlab -- parsing is finished, clean up and return. |
+`----------------------------------------------------------*/
+yyreturnlab:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ YY_ACCESSING_SYMBOL (+*yyssp), yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+
+ return yyresult;
+}
+
+#line 333 "../src/preproc/eqn/eqn.ypp"
+
diff --git a/src/preproc/eqn/eqn.h b/src/preproc/eqn/eqn.h
new file mode 100644
index 0000000..a4143cb
--- /dev/null
+++ b/src/preproc/eqn/eqn.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include "cset.h"
+#include "errarg.h"
+#include "error.h"
+
+#include "box.h"
+
+typedef enum {troff, mathml} eqnmode_t;
+
+extern char start_delim;
+extern char end_delim;
+extern int non_empty_flag;
+extern int inline_flag;
+extern int draw_flag;
+extern int one_size_reduction_flag;
+extern int compatible_flag;
+extern int nroff;
+extern eqnmode_t output_format;
+extern int xhtml;
+
+void init_lex(const char *str, const char *filename, int lineno);
+void lex_error(const char *message,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+
+void init_table(const char *device);
+
+// prefix for all registers, strings, macros
+#define PREFIX "0"
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/eqn.hpp b/src/preproc/eqn/eqn.hpp
new file mode 100644
index 0000000..de02d7c
--- /dev/null
+++ b/src/preproc/eqn/eqn.hpp
@@ -0,0 +1,210 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison interface for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+#ifndef YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ OVER = 258, /* OVER */
+ SMALLOVER = 259, /* SMALLOVER */
+ SQRT = 260, /* SQRT */
+ SUB = 261, /* SUB */
+ SUP = 262, /* SUP */
+ LPILE = 263, /* LPILE */
+ RPILE = 264, /* RPILE */
+ CPILE = 265, /* CPILE */
+ PILE = 266, /* PILE */
+ LEFT = 267, /* LEFT */
+ RIGHT = 268, /* RIGHT */
+ TO = 269, /* TO */
+ FROM = 270, /* FROM */
+ SIZE = 271, /* SIZE */
+ FONT = 272, /* FONT */
+ ROMAN = 273, /* ROMAN */
+ BOLD = 274, /* BOLD */
+ ITALIC = 275, /* ITALIC */
+ FAT = 276, /* FAT */
+ ACCENT = 277, /* ACCENT */
+ BAR = 278, /* BAR */
+ UNDER = 279, /* UNDER */
+ ABOVE = 280, /* ABOVE */
+ TEXT = 281, /* TEXT */
+ QUOTED_TEXT = 282, /* QUOTED_TEXT */
+ FWD = 283, /* FWD */
+ BACK = 284, /* BACK */
+ DOWN = 285, /* DOWN */
+ UP = 286, /* UP */
+ MATRIX = 287, /* MATRIX */
+ COL = 288, /* COL */
+ LCOL = 289, /* LCOL */
+ RCOL = 290, /* RCOL */
+ CCOL = 291, /* CCOL */
+ MARK = 292, /* MARK */
+ LINEUP = 293, /* LINEUP */
+ TYPE = 294, /* TYPE */
+ VCENTER = 295, /* VCENTER */
+ PRIME = 296, /* PRIME */
+ SPLIT = 297, /* SPLIT */
+ NOSPLIT = 298, /* NOSPLIT */
+ UACCENT = 299, /* UACCENT */
+ SPECIAL = 300, /* SPECIAL */
+ SPACE = 301, /* SPACE */
+ GFONT = 302, /* GFONT */
+ GSIZE = 303, /* GSIZE */
+ DEFINE = 304, /* DEFINE */
+ NDEFINE = 305, /* NDEFINE */
+ TDEFINE = 306, /* TDEFINE */
+ SDEFINE = 307, /* SDEFINE */
+ UNDEF = 308, /* UNDEF */
+ IFDEF = 309, /* IFDEF */
+ INCLUDE = 310, /* INCLUDE */
+ DELIM = 311, /* DELIM */
+ CHARTYPE = 312, /* CHARTYPE */
+ SET = 313, /* SET */
+ GRFONT = 314, /* GRFONT */
+ GBFONT = 315 /* GBFONT */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define OVER 258
+#define SMALLOVER 259
+#define SQRT 260
+#define SUB 261
+#define SUP 262
+#define LPILE 263
+#define RPILE 264
+#define CPILE 265
+#define PILE 266
+#define LEFT 267
+#define RIGHT 268
+#define TO 269
+#define FROM 270
+#define SIZE 271
+#define FONT 272
+#define ROMAN 273
+#define BOLD 274
+#define ITALIC 275
+#define FAT 276
+#define ACCENT 277
+#define BAR 278
+#define UNDER 279
+#define ABOVE 280
+#define TEXT 281
+#define QUOTED_TEXT 282
+#define FWD 283
+#define BACK 284
+#define DOWN 285
+#define UP 286
+#define MATRIX 287
+#define COL 288
+#define LCOL 289
+#define RCOL 290
+#define CCOL 291
+#define MARK 292
+#define LINEUP 293
+#define TYPE 294
+#define VCENTER 295
+#define PRIME 296
+#define SPLIT 297
+#define NOSPLIT 298
+#define UACCENT 299
+#define SPECIAL 300
+#define SPACE 301
+#define GFONT 302
+#define GSIZE 303
+#define DEFINE 304
+#define NDEFINE 305
+#define TDEFINE 306
+#define SDEFINE 307
+#define UNDEF 308
+#define IFDEF 309
+#define INCLUDE 310
+#define DELIM 311
+#define CHARTYPE 312
+#define SET 313
+#define GRFONT 314
+#define GBFONT 315
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 34 "../src/preproc/eqn/eqn.ypp"
+
+ char *str;
+ box *b;
+ pile_box *pb;
+ matrix_box *mb;
+ int n;
+ column *col;
+
+#line 196 "src/preproc/eqn/eqn.hpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_EQN_EQN_HPP_INCLUDED */
diff --git a/src/preproc/eqn/eqn.ypp b/src/preproc/eqn/eqn.ypp
new file mode 100644
index 0000000..a22ad59
--- /dev/null
+++ b/src/preproc/eqn/eqn.ypp
@@ -0,0 +1,333 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+%{
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "lib.h"
+#include "box.h"
+extern int non_empty_flag;
+int yylex();
+void yyerror(const char *);
+%}
+
+%union {
+ char *str;
+ box *b;
+ pile_box *pb;
+ matrix_box *mb;
+ int n;
+ column *col;
+}
+
+%token OVER
+%token SMALLOVER
+%token SQRT
+%token SUB
+%token SUP
+%token LPILE
+%token RPILE
+%token CPILE
+%token PILE
+%token LEFT
+%token RIGHT
+%token TO
+%token FROM
+%token SIZE
+%token FONT
+%token ROMAN
+%token BOLD
+%token ITALIC
+%token FAT
+%token ACCENT
+%token BAR
+%token UNDER
+%token ABOVE
+%token <str> TEXT
+%token <str> QUOTED_TEXT
+%token FWD
+%token BACK
+%token DOWN
+%token UP
+%token MATRIX
+%token COL
+%token LCOL
+%token RCOL
+%token CCOL
+%token MARK
+%token LINEUP
+%token TYPE
+%token VCENTER
+%token PRIME
+%token SPLIT
+%token NOSPLIT
+%token UACCENT
+%token SPECIAL
+
+/* these are handled in the lexer */
+%token SPACE
+%token GFONT
+%token GSIZE
+%token DEFINE
+%token NDEFINE
+%token TDEFINE
+%token SDEFINE
+%token UNDEF
+%token IFDEF
+%token INCLUDE
+%token DELIM
+%token CHARTYPE
+%token SET
+%token GRFONT
+%token GBFONT
+
+/* The original eqn manual says that 'left' is right associative. It's lying.
+Consider 'left ( ~ left ( ~ right ) right )'. */
+
+%right LEFT
+%left RIGHT
+%right LPILE RPILE CPILE PILE TEXT QUOTED_TEXT MATRIX MARK LINEUP '^' '~' '\t' '{' SPLIT NOSPLIT
+%right FROM TO
+%left SQRT OVER SMALLOVER
+%right SUB SUP
+%right ROMAN BOLD ITALIC FAT FONT SIZE FWD BACK DOWN UP TYPE VCENTER SPECIAL
+%right BAR UNDER PRIME
+%left ACCENT UACCENT
+
+%type <b> mark from_to sqrt_over script simple equation nonsup
+%type <n> number
+%type <str> text delim
+%type <pb> pile_element_list pile_arg
+%type <mb> column_list
+%type <col> column column_arg column_element_list
+
+%%
+top:
+ /* empty */
+ | equation
+ { $1->top_level(); non_empty_flag = 1; }
+ ;
+
+equation:
+ mark
+ { $$ = $1; }
+ | equation mark
+ {
+ list_box *lb = $1->to_list_box();
+ if (!lb)
+ lb = new list_box($1);
+ lb->append($2);
+ $$ = lb;
+ }
+ ;
+
+mark:
+ from_to
+ { $$ = $1; }
+ | MARK mark
+ { $$ = make_mark_box($2); }
+ | LINEUP mark
+ { $$ = make_lineup_box($2); }
+ ;
+
+from_to:
+ sqrt_over %prec FROM
+ { $$ = $1; }
+ | sqrt_over TO from_to
+ { $$ = make_limit_box($1, 0, $3); }
+ | sqrt_over FROM sqrt_over
+ { $$ = make_limit_box($1, $3, 0); }
+ | sqrt_over FROM sqrt_over TO from_to
+ { $$ = make_limit_box($1, $3, $5); }
+ | sqrt_over FROM sqrt_over FROM from_to
+ { $$ = make_limit_box($1, make_limit_box($3, $5, 0), 0); }
+ ;
+
+sqrt_over:
+ script
+ { $$ = $1; }
+ | SQRT sqrt_over
+ { $$ = make_sqrt_box($2); }
+ | sqrt_over OVER sqrt_over
+ { $$ = make_over_box($1, $3); }
+ | sqrt_over SMALLOVER sqrt_over
+ { $$ = make_small_over_box($1, $3); }
+ ;
+
+script:
+ nonsup
+ { $$ = $1; }
+ | simple SUP script
+ { $$ = make_script_box($1, 0, $3); }
+ ;
+
+nonsup:
+ simple %prec SUP
+ { $$ = $1; }
+ | simple SUB nonsup
+ { $$ = make_script_box($1, $3, 0); }
+ | simple SUB simple SUP script
+ { $$ = make_script_box($1, $3, $5); }
+ ;
+
+simple:
+ TEXT
+ { $$ = split_text($1); }
+ | QUOTED_TEXT
+ { $$ = new quoted_text_box($1); }
+ | SPLIT QUOTED_TEXT
+ { $$ = split_text($2); }
+ | NOSPLIT TEXT
+ { $$ = new quoted_text_box($2); }
+ | '^'
+ { $$ = new half_space_box; }
+ | '~'
+ { $$ = new space_box; }
+ | '\t'
+ { $$ = new tab_box; }
+ | '{' equation '}'
+ { $$ = $2; }
+ | PILE pile_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ | LPILE pile_arg
+ { $2->set_alignment(LEFT_ALIGN); $$ = $2; }
+ | RPILE pile_arg
+ { $2->set_alignment(RIGHT_ALIGN); $$ = $2; }
+ | CPILE pile_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ | MATRIX '{' column_list '}'
+ { $$ = $3; }
+ | LEFT delim equation RIGHT delim
+ { $$ = make_delim_box($2, $3, $5); }
+ | LEFT delim equation
+ { $$ = make_delim_box($2, $3, 0); }
+ | simple BAR
+ { $$ = make_overline_box($1); }
+ | simple UNDER
+ { $$ = make_underline_box($1); }
+ | simple PRIME
+ { $$ = make_prime_box($1); }
+ | simple ACCENT simple
+ { $$ = make_accent_box($1, $3); }
+ | simple UACCENT simple
+ { $$ = make_uaccent_box($1, $3); }
+ | ROMAN simple
+ { $$ = new font_box(strsave(get_grfont()), $2); }
+ | BOLD simple
+ { $$ = new font_box(strsave(get_gbfont()), $2); }
+ | ITALIC simple
+ { $$ = new font_box(strsave(get_gfont()), $2); }
+ | FAT simple
+ { $$ = new fat_box($2); }
+ | FONT text simple
+ { $$ = new font_box($2, $3); }
+ | SIZE text simple
+ { $$ = new size_box($2, $3); }
+ | FWD number simple
+ { $$ = new hmotion_box($2, $3); }
+ | BACK number simple
+ { $$ = new hmotion_box(-$2, $3); }
+ | UP number simple
+ { $$ = new vmotion_box($2, $3); }
+ | DOWN number simple
+ { $$ = new vmotion_box(-$2, $3); }
+ | TYPE text simple
+ { $3->set_spacing_type($2); $$ = $3; }
+ | VCENTER simple
+ { $$ = new vcenter_box($2); }
+ | SPECIAL text simple
+ { $$ = make_special_box($2, $3); }
+ ;
+
+number:
+ text
+ {
+ int n;
+ if (sscanf($1, "%d", &n) == 1)
+ $$ = n;
+ delete[] $1;
+ }
+ ;
+
+pile_element_list:
+ equation
+ { $$ = new pile_box($1); }
+ | pile_element_list ABOVE equation
+ { $1->append($3); $$ = $1; }
+ ;
+
+pile_arg:
+ '{' pile_element_list '}'
+ { $$ = $2; }
+ | number '{' pile_element_list '}'
+ { $3->set_space($1); $$ = $3; }
+ ;
+
+column_list:
+ column
+ { $$ = new matrix_box($1); }
+ | column_list column
+ { $1->append($2); $$ = $1; }
+ ;
+
+column_element_list:
+ equation
+ { $$ = new column($1); }
+ | column_element_list ABOVE equation
+ { $1->append($3); $$ = $1; }
+ ;
+
+column_arg:
+ '{' column_element_list '}'
+ { $$ = $2; }
+ | number '{' column_element_list '}'
+ { $3->set_space($1); $$ = $3; }
+ ;
+
+column:
+ COL column_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ | LCOL column_arg
+ { $2->set_alignment(LEFT_ALIGN); $$ = $2; }
+ | RCOL column_arg
+ { $2->set_alignment(RIGHT_ALIGN); $$ = $2; }
+ | CCOL column_arg
+ { $2->set_alignment(CENTER_ALIGN); $$ = $2; }
+ ;
+
+text: TEXT
+ { $$ = $1; }
+ | QUOTED_TEXT
+ { $$ = $1; }
+ ;
+
+delim:
+ text
+ { $$ = $1; }
+ | '{'
+ { $$ = strsave("{"); }
+ | '}'
+ { $$ = strsave("}"); }
+ ;
+
+%%
diff --git a/src/preproc/eqn/lex.cpp b/src/preproc/eqn/lex.cpp
new file mode 100644
index 0000000..e38a486
--- /dev/null
+++ b/src/preproc/eqn/lex.cpp
@@ -0,0 +1,1236 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "eqn.hpp"
+#include "stringclass.h"
+#include "ptable.h"
+
+
+// declarations to avoid friend name injection problems
+int get_char();
+int peek_char();
+int get_location(char **, int *);
+
+struct definition {
+ char is_macro;
+ char is_simple;
+ union {
+ int tok;
+ char *contents;
+ };
+ definition();
+ ~definition();
+};
+
+definition::definition() : is_macro(1), is_simple(0)
+{
+ contents = 0;
+}
+
+definition::~definition()
+{
+ if (is_macro)
+ free(contents);
+}
+
+declare_ptable(definition)
+implement_ptable(definition)
+
+PTABLE(definition) macro_table;
+
+static struct {
+ const char *name;
+ int token;
+} token_table[] = {
+ { "over", OVER },
+ { "smallover", SMALLOVER },
+ { "sqrt", SQRT },
+ { "sub", SUB },
+ { "sup", SUP },
+ { "lpile", LPILE },
+ { "rpile", RPILE },
+ { "cpile", CPILE },
+ { "pile", PILE },
+ { "left", LEFT },
+ { "right", RIGHT },
+ { "to", TO },
+ { "from", FROM },
+ { "size", SIZE },
+ { "font", FONT },
+ { "roman", ROMAN },
+ { "bold", BOLD },
+ { "italic", ITALIC },
+ { "fat", FAT },
+ { "bar", BAR },
+ { "under", UNDER },
+ { "accent", ACCENT },
+ { "uaccent", UACCENT },
+ { "above", ABOVE },
+ { "fwd", FWD },
+ { "back", BACK },
+ { "down", DOWN },
+ { "up", UP },
+ { "matrix", MATRIX },
+ { "col", COL },
+ { "lcol", LCOL },
+ { "rcol", RCOL },
+ { "ccol", CCOL },
+ { "mark", MARK },
+ { "lineup", LINEUP },
+ { "space", SPACE },
+ { "gfont", GFONT },
+ { "gsize", GSIZE },
+ { "define", DEFINE },
+ { "sdefine", SDEFINE },
+ { "ndefine", NDEFINE },
+ { "tdefine", TDEFINE },
+ { "undef", UNDEF },
+ { "ifdef", IFDEF },
+ { "include", INCLUDE },
+ { "copy", INCLUDE },
+ { "delim", DELIM },
+ { "chartype", CHARTYPE },
+ { "type", TYPE },
+ { "vcenter", VCENTER },
+ { "set", SET },
+ { "opprime", PRIME },
+ { "grfont", GRFONT },
+ { "gbfont", GBFONT },
+ { "split", SPLIT },
+ { "nosplit", NOSPLIT },
+ { "special", SPECIAL },
+};
+
+struct builtin_def {
+ const char *name;
+ const char *def;
+};
+
+static struct builtin_def common_defs[] = {
+ { "ALPHA", "\\(*A" },
+ { "BETA", "\\(*B" },
+ { "CHI", "\\(*X" },
+ { "DELTA", "\\(*D" },
+ { "EPSILON", "\\(*E" },
+ { "ETA", "\\(*Y" },
+ { "GAMMA", "\\(*G" },
+ { "IOTA", "\\(*I" },
+ { "KAPPA", "\\(*K" },
+ { "LAMBDA", "\\(*L" },
+ { "MU", "\\(*M" },
+ { "NU", "\\(*N" },
+ { "OMEGA", "\\(*W" },
+ { "OMICRON", "\\(*O" },
+ { "PHI", "\\(*F" },
+ { "PI", "\\(*P" },
+ { "PSI", "\\(*Q" },
+ { "RHO", "\\(*R" },
+ { "SIGMA", "\\(*S" },
+ { "TAU", "\\(*T" },
+ { "THETA", "\\(*H" },
+ { "UPSILON", "\\(*U" },
+ { "XI", "\\(*C" },
+ { "ZETA", "\\(*Z" },
+ { "Alpha", "\\(*A" },
+ { "Beta", "\\(*B" },
+ { "Chi", "\\(*X" },
+ { "Delta", "\\(*D" },
+ { "Epsilon", "\\(*E" },
+ { "Eta", "\\(*Y" },
+ { "Gamma", "\\(*G" },
+ { "Iota", "\\(*I" },
+ { "Kappa", "\\(*K" },
+ { "Lambda", "\\(*L" },
+ { "Mu", "\\(*M" },
+ { "Nu", "\\(*N" },
+ { "Omega", "\\(*W" },
+ { "Omicron", "\\(*O" },
+ { "Phi", "\\(*F" },
+ { "Pi", "\\(*P" },
+ { "Psi", "\\(*Q" },
+ { "Rho", "\\(*R" },
+ { "Sigma", "\\(*S" },
+ { "Tau", "\\(*T" },
+ { "Theta", "\\(*H" },
+ { "Upsilon", "\\(*U" },
+ { "Xi", "\\(*C" },
+ { "Zeta", "\\(*Z" },
+ { "alpha", "\\(*a" },
+ { "beta", "\\(*b" },
+ { "chi", "\\(*x" },
+ { "delta", "\\(*d" },
+ { "epsilon", "\\(*e" },
+ { "eta", "\\(*y" },
+ { "gamma", "\\(*g" },
+ { "iota", "\\(*i" },
+ { "kappa", "\\(*k" },
+ { "lambda", "\\(*l" },
+ { "mu", "\\(*m" },
+ { "nu", "\\(*n" },
+ { "omega", "\\(*w" },
+ { "omicron", "\\(*o" },
+ { "phi", "\\(*f" },
+ { "pi", "\\(*p" },
+ { "psi", "\\(*q" },
+ { "rho", "\\(*r" },
+ { "sigma", "\\(*s" },
+ { "tau", "\\(*t" },
+ { "theta", "\\(*h" },
+ { "upsilon", "\\(*u" },
+ { "xi", "\\(*c" },
+ { "zeta", "\\(*z" },
+ { "max", "{type \"operator\" roman \"max\"}" },
+ { "min", "{type \"operator\" roman \"min\"}" },
+ { "lim", "{type \"operator\" roman \"lim\"}" },
+ { "sin", "{type \"operator\" roman \"sin\"}" },
+ { "cos", "{type \"operator\" roman \"cos\"}" },
+ { "tan", "{type \"operator\" roman \"tan\"}" },
+ { "sinh", "{type \"operator\" roman \"sinh\"}" },
+ { "cosh", "{type \"operator\" roman \"cosh\"}" },
+ { "tanh", "{type \"operator\" roman \"tanh\"}" },
+ { "arc", "{type \"operator\" roman \"arc\"}" },
+ { "log", "{type \"operator\" roman \"log\"}" },
+ { "ln", "{type \"operator\" roman \"ln\"}" },
+ { "exp", "{type \"operator\" roman \"exp\"}" },
+ { "Re", "{type \"operator\" roman \"Re\"}" },
+ { "Im", "{type \"operator\" roman \"Im\"}" },
+ { "det", "{type \"operator\" roman \"det\"}" },
+ { "and", "{roman \"and\"}" },
+ { "if", "{roman \"if\"}" },
+ { "for", "{roman \"for\"}" },
+ { "times", "type \"binary\" \\(mu" },
+ { "ldots", "type \"inner\" { . . . }" },
+ { "inf", "\\(if" },
+ { "partial", "\\(pd" },
+ { "nothing", "\"\"" },
+ { "half", "{1 smallover 2}" },
+ { "hat_def", "roman \"^\"" },
+ { "hat", "accent { hat_def }" },
+ { "tilde_def", "\"~\"" },
+ { "tilde", "accent { tilde_def }" },
+ { "==", "type \"relation\" \\(==" },
+ { "!=", "type \"relation\" \\(!=" },
+ { "+-", "type \"binary\" \\(+-" },
+ { "->", "type \"relation\" \\(->" },
+ { "<-", "type \"relation\" \\(<-" },
+ { "<<", "type \"relation\" \\(<<" },
+ { ">>", "type \"relation\" \\(>>" },
+ { "prime", "'" },
+ { "approx", "type \"relation\" \"\\(~=\"" },
+ { "grad", "\\(gr" },
+ { "del", "\\(gr" },
+ { "cdot", "type \"binary\" \\(md" },
+ { "cdots", "type \"inner\" { \\(md \\(md \\(md }" },
+ { "dollar", "$" },
+};
+
+/* composite definitions that require troff size and motion operators */
+static struct builtin_def troff_defs[] = {
+ { "sum", "{type \"operator\" vcenter size +5 \\(*S}" },
+ { "prod", "{type \"operator\" vcenter size +5 \\(*P}" },
+ { "int", "{type \"operator\" vcenter size +8 \\(is}" },
+ { "union", "{type \"operator\" vcenter size +5 \\(cu}" },
+ { "inter", "{type \"operator\" vcenter size +5 \\(ca}" },
+ { "dot_def", "up 52 back 15 \".\"" },
+ { "dot", "accent { dot_def }" },
+ { "dotdot_def", "up 52 back 25 \"..\"" },
+ { "dotdot", "accent { dotdot_def }" },
+ { "utilde_def", "down 75 \"~\"" },
+ { "utilde", "uaccent { utilde_def }" },
+ { "vec_def", "up 52 size -5 \\(->" },
+ { "vec", "accent { vec_def }" },
+ { "dyad_def", "up 52 size -5 { \\(<> }" },
+ { "dyad", "accent { dyad_def }" },
+ { "...", "type \"inner\" { . . . }" },
+};
+
+/* equivalent definitions for MathML mode */
+static struct builtin_def mathml_defs[] = {
+ { "sum", "{type \"operator\" size big \\(*S}" },
+ { "prod", "{type \"operator\" size big \\(*P}" },
+ { "int", "{type \"operator\" size big \\(is}" },
+ { "union", "{type \"operator\" size big \\(cu}" },
+ { "inter", "{type \"operator\" size big \\(ca}" },
+ { "dot", "accent { \".\" }" },
+ { "dotdot", "accent { \"..\" }" },
+ { "utilde", "uaccent { \"~\" }" },
+ { "vec", "accent { \\(-> }" },
+ { "dyad", "accent { \\(<> }" },
+ { "...", "type \"inner\" { . . . }" },
+};
+
+void init_table(const char *device)
+{
+ unsigned int i;
+ for (i = 0; i < sizeof(token_table)/sizeof(token_table[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 0;
+ def->tok = token_table[i].token;
+ macro_table.define(token_table[i].name, def);
+ }
+ for (i = 0; i < sizeof(common_defs)/sizeof(common_defs[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave(common_defs[i].def);
+ def->is_simple = 1;
+ macro_table.define(common_defs[i].name, def);
+ }
+ if (output_format == troff) {
+ for (i = 0; i < sizeof(troff_defs)/sizeof(troff_defs[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave(troff_defs[i].def);
+ def->is_simple = 1;
+ macro_table.define(troff_defs[i].name, def);
+ }
+ }
+ else if (output_format == mathml) {
+ for (i = 0; i < sizeof(mathml_defs)/sizeof(mathml_defs[0]); i++) {
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave(mathml_defs[i].def);
+ def->is_simple = 1;
+ macro_table.define(mathml_defs[i].name, def);
+ }
+ }
+ definition *def = new definition[1];
+ def->is_macro = 1;
+ def->contents = strsave("1");
+ macro_table.define(device, def);
+}
+
+class input {
+ input *next;
+public:
+ input(input *p);
+ virtual ~input();
+ virtual int get() = 0;
+ virtual int peek() = 0;
+ virtual int get_location(char **, int *);
+
+ friend int get_char();
+ friend int peek_char();
+ friend int get_location(char **, int *);
+ friend void init_lex(const char *str, const char *filename, int lineno);
+};
+
+class file_input : public input {
+ FILE *fp;
+ char *filename;
+ int lineno;
+ string line;
+ const char *ptr;
+ int read_line();
+public:
+ file_input(FILE *, const char *, input *);
+ ~file_input();
+ int get();
+ int peek();
+ int get_location(char **, int *);
+};
+
+
+class macro_input : public input {
+ char *s;
+ char *p;
+public:
+ macro_input(const char *, input *);
+ ~macro_input();
+ int get();
+ int peek();
+};
+
+class top_input : public macro_input {
+ char *filename;
+ int lineno;
+ public:
+ top_input(const char *, const char *, int, input *);
+ ~top_input();
+ int get();
+ int get_location(char **, int *);
+};
+
+class argument_macro_input: public input {
+ char *s;
+ char *p;
+ char *ap;
+ int argc;
+ char *argv[9];
+public:
+ argument_macro_input(const char *, int, char **, input *);
+ ~argument_macro_input();
+ int get();
+ int peek();
+};
+
+input::input(input *x) : next(x)
+{
+}
+
+input::~input()
+{
+}
+
+int input::get_location(char **, int *)
+{
+ return 0;
+}
+
+file_input::file_input(FILE *f, const char *fn, input *p)
+: input(p), lineno(0), ptr("")
+{
+ fp = f;
+ filename = strsave(fn);
+}
+
+file_input::~file_input()
+{
+ if (fclose(fp) < 0)
+ fatal("unable to close '%1': %2", filename, strerror(errno));
+ delete[] filename;
+}
+
+int file_input::read_line()
+{
+ for (;;) {
+ line.clear();
+ lineno++;
+ for (;;) {
+ int c = getc(fp);
+ if (c == '\r') {
+ c = getc(fp);
+ if (c != '\n')
+ lex_error("invalid input character code %1", '\r');
+ }
+ if (c == EOF)
+ break;
+ else if (is_invalid_input_char(c))
+ lex_error("invalid input character code %1", c);
+ else {
+ line += char(c);
+ if (c == '\n')
+ break;
+ }
+ }
+ if (line.length() == 0)
+ return 0;
+ if (!(line.length() >= 3 && line[0] == '.' && line[1] == 'E'
+ && (line[2] == 'Q' || line[2] == 'N')
+ && (line.length() == 3 || line[3] == ' ' || line[3] == '\n'
+ || compatible_flag))) {
+ line += '\0';
+ ptr = line.contents();
+ return 1;
+ }
+ }
+}
+
+int file_input::get()
+{
+ if (*ptr != '\0' || read_line())
+ return *ptr++ & 0377;
+ else
+ return EOF;
+}
+
+int file_input::peek()
+{
+ if (*ptr != '\0' || read_line())
+ return *ptr;
+ else
+ return EOF;
+}
+
+int file_input::get_location(char **fnp, int *lnp)
+{
+ *fnp = filename;
+ *lnp = lineno;
+ return 1;
+}
+
+macro_input::macro_input(const char *str, input *x) : input(x)
+{
+ p = s = strsave(str);
+}
+
+macro_input::~macro_input()
+{
+ free(s);
+}
+
+int macro_input::get()
+{
+ if (p == 0 || *p == '\0')
+ return EOF;
+ else
+ return *p++ & 0377;
+}
+
+int macro_input::peek()
+{
+ if (p == 0 || *p == '\0')
+ return EOF;
+ else
+ return *p & 0377;
+}
+
+top_input::top_input(const char *str, const char *fn, int ln, input *x)
+: macro_input(str, x), lineno(ln)
+{
+ filename = strsave(fn);
+}
+
+top_input::~top_input()
+{
+ free(filename);
+}
+
+int top_input::get()
+{
+ int c = macro_input::get();
+ if (c == '\n')
+ lineno++;
+ return c;
+}
+
+int top_input::get_location(char **fnp, int *lnp)
+{
+ *fnp = filename;
+ *lnp = lineno;
+ return 1;
+}
+
+// Character representing $1. Must be invalid input character.
+#define ARG1 14
+
+argument_macro_input::argument_macro_input(const char *body, int ac,
+ char **av, input *x)
+: input(x), ap(0), argc(ac)
+{
+ int i;
+ for (i = 0; i < argc; i++)
+ argv[i] = av[i];
+ p = s = strsave(body);
+ int j = 0;
+ for (i = 0; s[i] != '\0'; i++)
+ if (s[i] == '$' && s[i+1] >= '0' && s[i+1] <= '9') {
+ if (s[i+1] != '0')
+ s[j++] = ARG1 + s[++i] - '1';
+ }
+ else
+ s[j++] = s[i];
+ s[j] = '\0';
+}
+
+
+argument_macro_input::~argument_macro_input()
+{
+ for (int i = 0; i < argc; i++)
+ delete[] argv[i];
+ delete[] s;
+}
+
+int argument_macro_input::get()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return *ap++ & 0377;
+ ap = 0;
+ }
+ if (p == 0)
+ return EOF;
+ while (*p >= ARG1 && *p <= ARG1 + 8) {
+ int i = *p++ - ARG1;
+ if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
+ ap = argv[i];
+ return *ap++ & 0377;
+ }
+ }
+ if (*p == '\0')
+ return EOF;
+ return *p++ & 0377;
+}
+
+int argument_macro_input::peek()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return *ap & 0377;
+ ap = 0;
+ }
+ if (p == 0)
+ return EOF;
+ while (*p >= ARG1 && *p <= ARG1 + 8) {
+ int i = *p++ - ARG1;
+ if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
+ ap = argv[i];
+ return *ap & 0377;
+ }
+ }
+ if (*p == '\0')
+ return EOF;
+ return *p & 0377;
+}
+
+static input *current_input = 0;
+
+/* we insert a newline between input from different levels */
+
+int get_char()
+{
+ if (current_input == 0)
+ return EOF;
+ else {
+ int c = current_input->get();
+ if (c != EOF)
+ return c;
+ else {
+ input *tem = current_input;
+ current_input = current_input->next;
+ delete tem;
+ return '\n';
+ }
+ }
+}
+
+int peek_char()
+{
+ if (current_input == 0)
+ return EOF;
+ else {
+ int c = current_input->peek();
+ if (c != EOF)
+ return c;
+ else
+ return '\n';
+ }
+}
+
+int get_location(char **fnp, int *lnp)
+{
+ for (input *p = current_input; p; p = p->next)
+ if (p->get_location(fnp, lnp))
+ return 1;
+ return 0;
+}
+
+string token_buffer;
+const int NCONTEXT = 4;
+string context_ring[NCONTEXT];
+int context_index = 0;
+
+void flush_context()
+{
+ for (int i = 0; i < NCONTEXT; i++)
+ context_ring[i] = "";
+ context_index = 0;
+}
+
+void show_context()
+{
+ int i = context_index;
+ fputs(" context is\n\t", stderr);
+ for (;;) {
+ int j = (i + 1) % NCONTEXT;
+ if (j == context_index) {
+ fputs(">>> ", stderr);
+ put_string(context_ring[i], stderr);
+ fputs(" <<<", stderr);
+ break;
+ }
+ else if (context_ring[i].length() > 0) {
+ put_string(context_ring[i], stderr);
+ putc(' ', stderr);
+ }
+ i = j;
+ }
+ putc('\n', stderr);
+}
+
+void add_context(const string &s)
+{
+ context_ring[context_index] = s;
+ context_index = (context_index + 1) % NCONTEXT;
+}
+
+void add_context(char c)
+{
+ context_ring[context_index] = c;
+ context_index = (context_index + 1) % NCONTEXT;
+}
+
+void add_quoted_context(const string &s)
+{
+ string &r = context_ring[context_index];
+ r = '"';
+ for (int i = 0; i < s.length(); i++)
+ if (s[i] == '"')
+ r += "\\\"";
+ else
+ r += s[i];
+ r += '"';
+ context_index = (context_index + 1) % NCONTEXT;
+}
+
+void init_lex(const char *str, const char *filename, int lineno)
+{
+ while (current_input != 0) {
+ input *tem = current_input;
+ current_input = current_input->next;
+ delete tem;
+ }
+ current_input = new top_input(str, filename, lineno, 0);
+ flush_context();
+}
+
+
+void get_delimited_text()
+{
+ char *filename, *last_seen_filename;
+ int lineno;
+ int got_location = get_location(&filename, &lineno);
+ // `filename` gets invalidated if we iterate off the end of the file.
+ last_seen_filename = strdup(filename);
+ int start = get_char();
+ while (start == ' ' || start == '\t' || start == '\n')
+ start = get_char();
+ token_buffer.clear();
+ if (start == EOF) {
+ current_lineno = 0;
+ if (got_location)
+ error_with_file_and_line(last_seen_filename, lineno,
+ "end of input while defining macro");
+ else
+ error("end of input while defining macro");
+ free(last_seen_filename);
+ return;
+ }
+ for (;;) {
+ int c = get_char();
+ if (c == EOF) {
+ current_lineno = 0;
+ if (got_location)
+ error_with_file_and_line(last_seen_filename, lineno,
+ "end of input while defining macro");
+ else
+ error("end of input while defining macro");
+ add_context(start + token_buffer);
+ free(last_seen_filename);
+ return;
+ }
+ if (c == start)
+ break;
+ token_buffer += char(c);
+ }
+ add_context(start + token_buffer + start);
+ free(last_seen_filename);
+}
+
+void interpolate_macro_with_args(const char *body)
+{
+ char *argv[9];
+ int argc = 0;
+ int i;
+ for (i = 0; i < 9; i++)
+ argv[i] = 0;
+ int level = 0;
+ int c;
+ do {
+ token_buffer.clear();
+ for (;;) {
+ c = get_char();
+ if (c == EOF) {
+ lex_error("end of input while scanning macro arguments");
+ break;
+ }
+ if (level == 0 && (c == ',' || c == ')')) {
+ if (token_buffer.length() > 0) {
+ token_buffer += '\0';
+ argv[argc] = strsave(token_buffer.contents());
+ }
+ // for 'foo()', argc = 0
+ if (argc > 0 || c != ')' || i > 0)
+ argc++;
+ break;
+ }
+ token_buffer += char(c);
+ if (c == '(')
+ level++;
+ else if (c == ')')
+ level--;
+ }
+ } while (c != ')' && c != EOF);
+ current_input = new argument_macro_input(body, argc, argv, current_input);
+}
+
+/* If lookup flag is non-zero the token will be looked up to see
+if it is macro. If it's 1, it will looked up to see if it's a token.
+*/
+
+int get_token(int lookup_flag = 0)
+{
+ for (;;) {
+ int c = get_char();
+ while (c == ' ' || c == '\n')
+ c = get_char();
+ switch (c) {
+ case EOF:
+ {
+ add_context("end of input");
+ }
+ return 0;
+ case '"':
+ {
+ int quoted = 0;
+ token_buffer.clear();
+ for (;;) {
+ c = get_char();
+ if (c == EOF) {
+ lex_error("missing \"");
+ break;
+ }
+ else if (c == '\n') {
+ lex_error("newline before end of quoted text");
+ break;
+ }
+ else if (c == '"') {
+ if (!quoted)
+ break;
+ token_buffer[token_buffer.length() - 1] = '"';
+ quoted = 0;
+ }
+ else {
+ token_buffer += c;
+ quoted = quoted ? 0 : c == '\\';
+ }
+ }
+ }
+ add_quoted_context(token_buffer);
+ return QUOTED_TEXT;
+ case '{':
+ case '}':
+ case '^':
+ case '~':
+ case '\t':
+ add_context(c);
+ return c;
+ default:
+ {
+ int break_flag = 0;
+ int quoted = 0;
+ token_buffer.clear();
+ if (c == '\\')
+ quoted = 1;
+ else
+ token_buffer += c;
+ int done = 0;
+ while (!done) {
+ c = peek_char();
+ if (!quoted && lookup_flag != 0 && c == '(') {
+ token_buffer += '\0';
+ definition *def = macro_table.lookup(token_buffer.contents());
+ if (def && def->is_macro && !def->is_simple) {
+ (void)get_char(); // skip initial '('
+ interpolate_macro_with_args(def->contents);
+ break_flag = 1;
+ break;
+ }
+ token_buffer.set_length(token_buffer.length() - 1);
+ }
+ if (quoted) {
+ quoted = 0;
+ switch (c) {
+ case EOF:
+ lex_error("'\\' ignored at end of equation");
+ done = 1;
+ break;
+ case '\n':
+ lex_error("'\\' ignored because followed by newline");
+ done = 1;
+ break;
+ case '\t':
+ lex_error("'\\' ignored because followed by tab");
+ done = 1;
+ break;
+ case '"':
+ (void)get_char();
+ token_buffer += '"';
+ break;
+ default:
+ (void)get_char();
+ token_buffer += '\\';
+ token_buffer += c;
+ break;
+ }
+ }
+ else {
+ switch (c) {
+ case EOF:
+ case '{':
+ case '}':
+ case '^':
+ case '~':
+ case '"':
+ case ' ':
+ case '\t':
+ case '\n':
+ done = 1;
+ break;
+ case '\\':
+ (void)get_char();
+ quoted = 1;
+ break;
+ default:
+ (void)get_char();
+ token_buffer += char(c);
+ break;
+ }
+ }
+ }
+ if (break_flag || token_buffer.length() == 0)
+ break;
+ if (lookup_flag != 0) {
+ token_buffer += '\0';
+ definition *def = macro_table.lookup(token_buffer.contents());
+ token_buffer.set_length(token_buffer.length() - 1);
+ if (def) {
+ if (def->is_macro) {
+ current_input = new macro_input(def->contents, current_input);
+ break;
+ }
+ else if (lookup_flag == 1) {
+ add_context(token_buffer);
+ return def->tok;
+ }
+ }
+ }
+ add_context(token_buffer);
+ return TEXT;
+ }
+ }
+ }
+}
+
+void do_include()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad filename for include");
+ return;
+ }
+ token_buffer += '\0';
+ const char *filename = token_buffer.contents();
+ errno = 0;
+ FILE *fp = fopen(filename, "r");
+ if (fp == 0) {
+ lex_error("can't open included file '%1'", filename);
+ return;
+ }
+ current_input = new file_input(fp, filename, current_input);
+}
+
+void ignore_definition()
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad definition");
+ return;
+ }
+ get_delimited_text();
+}
+
+void do_definition(int is_simple)
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad definition");
+ return;
+ }
+ token_buffer += '\0';
+ const char *name = token_buffer.contents();
+ definition *def = macro_table.lookup(name);
+ if (def == 0) {
+ def = new definition[1];
+ macro_table.define(name, def);
+ }
+ else if (def->is_macro) {
+ free(def->contents);
+ }
+ get_delimited_text();
+ token_buffer += '\0';
+ def->is_macro = 1;
+ def->contents = strsave(token_buffer.contents());
+ def->is_simple = is_simple;
+}
+
+void do_undef()
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad undef command");
+ return;
+ }
+ token_buffer += '\0';
+ macro_table.define(token_buffer.contents(), 0);
+}
+
+void do_gsize()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to gsize command");
+ return;
+ }
+ token_buffer += '\0';
+ if (!set_gsize(token_buffer.contents()))
+ lex_error("invalid size '%1'", token_buffer.contents());
+}
+
+void do_gfont()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to gfont command");
+ return;
+ }
+ token_buffer += '\0';
+ set_gfont(token_buffer.contents());
+}
+
+void do_grfont()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to grfont command");
+ return;
+ }
+ token_buffer += '\0';
+ set_grfont(token_buffer.contents());
+}
+
+void do_gbfont()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to gbfont command");
+ return;
+ }
+ token_buffer += '\0';
+ set_gbfont(token_buffer.contents());
+}
+
+void do_space()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad argument to space command");
+ return;
+ }
+ token_buffer += '\0';
+ char *ptr;
+ long n = strtol(token_buffer.contents(), &ptr, 10);
+ if (n == 0 && ptr == token_buffer.contents())
+ lex_error("bad argument '%1' to space command", token_buffer.contents());
+ else
+ set_space(int(n));
+}
+
+void do_ifdef()
+{
+ int t = get_token();
+ if (t != TEXT) {
+ lex_error("bad ifdef");
+ return;
+ }
+ token_buffer += '\0';
+ definition *def = macro_table.lookup(token_buffer.contents());
+ int result = def && def->is_macro && !def->is_simple;
+ get_delimited_text();
+ if (result) {
+ token_buffer += '\0';
+ current_input = new macro_input(token_buffer.contents(), current_input);
+ }
+}
+
+char start_delim_saved = '\0';
+char end_delim_saved = '\0';
+
+void do_delim()
+{
+ int c = get_char();
+ while (c == ' ' || c == '\n')
+ c = get_char();
+ int d;
+ if (c == EOF || (d = get_char()) == EOF)
+ lex_error("end of file while reading argument to 'delim'");
+ else {
+ if (c == 'o' && d == 'f' && peek_char() == 'f') {
+ (void)get_char();
+ start_delim_saved = start_delim;
+ end_delim_saved = end_delim;
+ start_delim = end_delim = '\0';
+ }
+ else if (c == 'o' && d == 'n') {
+ start_delim = start_delim_saved;
+ end_delim = end_delim_saved;
+ }
+ else {
+ start_delim = c;
+ end_delim = d;
+ }
+ }
+}
+
+void do_chartype()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad chartype");
+ return;
+ }
+ token_buffer += '\0';
+ string type = token_buffer;
+ t = get_token();
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad chartype");
+ return;
+ }
+ token_buffer += '\0';
+ set_char_type(type.contents(), strsave(token_buffer.contents()));
+}
+
+void do_set()
+{
+ int t = get_token(2);
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad set");
+ return;
+ }
+ token_buffer += '\0';
+ string param = token_buffer;
+ t = get_token();
+ if (t != TEXT && t != QUOTED_TEXT) {
+ lex_error("bad set");
+ return;
+ }
+ token_buffer += '\0';
+ int n;
+ if (sscanf(&token_buffer[0], "%d", &n) != 1) {
+ lex_error("bad number '%1'", token_buffer.contents());
+ return;
+ }
+ set_param(param.contents(), n);
+}
+
+int yylex()
+{
+ for (;;) {
+ int tk = get_token(1);
+ switch(tk) {
+ case UNDEF:
+ do_undef();
+ break;
+ case SDEFINE:
+ do_definition(1);
+ break;
+ case DEFINE:
+ do_definition(0);
+ break;
+ case TDEFINE:
+ if (!nroff)
+ do_definition(0);
+ else
+ ignore_definition();
+ break;
+ case NDEFINE:
+ if (nroff)
+ do_definition(0);
+ else
+ ignore_definition();
+ break;
+ case GSIZE:
+ do_gsize();
+ break;
+ case GFONT:
+ do_gfont();
+ break;
+ case GRFONT:
+ do_grfont();
+ break;
+ case GBFONT:
+ do_gbfont();
+ break;
+ case SPACE:
+ do_space();
+ break;
+ case INCLUDE:
+ do_include();
+ break;
+ case IFDEF:
+ do_ifdef();
+ break;
+ case DELIM:
+ do_delim();
+ break;
+ case CHARTYPE:
+ do_chartype();
+ break;
+ case SET:
+ do_set();
+ break;
+ case QUOTED_TEXT:
+ case TEXT:
+ token_buffer += '\0';
+ yylval.str = strsave(token_buffer.contents());
+ // fall through
+ default:
+ return tk;
+ }
+ }
+}
+
+void lex_error(const char *message,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ char *filename;
+ int lineno;
+ if (!get_location(&filename, &lineno))
+ error(message, arg1, arg2, arg3);
+ else
+ error_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
+}
+
+void yyerror(const char *s)
+{
+ char *filename;
+ int lineno;
+ if (!get_location(&filename, &lineno))
+ error(s);
+ else
+ error_with_file_and_line(filename, lineno, s);
+ show_context();
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/limit.cpp b/src/preproc/eqn/limit.cpp
new file mode 100644
index 0000000..bf64a04
--- /dev/null
+++ b/src/preproc/eqn/limit.cpp
@@ -0,0 +1,217 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "pbox.h"
+
+class limit_box : public box {
+private:
+ box *p;
+ box *from;
+ box *to;
+public:
+ limit_box(box *, box *, box *);
+ ~limit_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+box *make_limit_box(box *pp, box *qq, box *rr)
+{
+ return new limit_box(pp, qq, rr);
+}
+
+limit_box::limit_box(box *pp, box *qq, box *rr)
+: p(pp), from(qq), to(rr)
+{
+ spacing_type = p->spacing_type;
+}
+
+limit_box::~limit_box()
+{
+ delete p;
+ delete from;
+ delete to;
+}
+
+int limit_box::compute_metrics(int style)
+{
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ if (!(style <= SCRIPT_STYLE && one_size_reduction_flag))
+ set_script_size();
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ int res = 0;
+ int mark_uid = -1;
+ if (from != 0) {
+ res = from->compute_metrics(cramped_style(script_style(style)));
+ if (res)
+ mark_uid = from->uid;
+ }
+ if (to != 0) {
+ int r = to->compute_metrics(script_style(style));
+ if (res && r)
+ error("multiple marks and lineups");
+ else {
+ mark_uid = to->uid;
+ res = r;
+ }
+ }
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ int r = p->compute_metrics(style);
+ p->compute_subscript_kern();
+ if (res && r)
+ error("multiple marks and lineups");
+ else {
+ mark_uid = p->uid;
+ res = r;
+ }
+ printf(".nr " LEFT_WIDTH_FORMAT " "
+ "0\\n[" WIDTH_FORMAT "]",
+ uid, p->uid);
+ if (from != 0)
+ printf(">?(\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, from->uid);
+ if (to != 0)
+ printf(">?(-\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, to->uid);
+ printf("/2\n");
+ printf(".nr " WIDTH_FORMAT " "
+ "0\\n[" WIDTH_FORMAT "]",
+ uid, p->uid);
+ if (from != 0)
+ printf(">?(-\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, from->uid);
+ if (to != 0)
+ printf(">?(\\n[" SUB_KERN_FORMAT "]+\\n[" WIDTH_FORMAT "])",
+ p->uid, to->uid);
+ printf("/2+\\n[" LEFT_WIDTH_FORMAT "]\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]", uid, p->uid);
+ if (to != 0)
+ printf(">?\\n[" WIDTH_FORMAT "]", to->uid);
+ if (from != 0)
+ printf(">?\\n[" WIDTH_FORMAT "]", from->uid);
+ printf("\n");
+ if (res)
+ printf(".nr " MARK_REG " +(\\n[" LEFT_WIDTH_FORMAT "]"
+ "-(\\n[" WIDTH_FORMAT "]/2))\n",
+ uid, mark_uid);
+ if (to != 0) {
+ printf(".nr " SUP_RAISE_FORMAT " %dM+\\n[" DEPTH_FORMAT
+ "]>?%dM+\\n[" HEIGHT_FORMAT "]\n",
+ uid, big_op_spacing1, to->uid, big_op_spacing3, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" SUP_RAISE_FORMAT "]+\\n["
+ HEIGHT_FORMAT "]+%dM\n",
+ uid, uid, to->uid, big_op_spacing5);
+ }
+ else
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ if (from != 0) {
+ printf(".nr " SUB_LOWER_FORMAT " %dM+\\n[" HEIGHT_FORMAT
+ "]>?%dM+\\n[" DEPTH_FORMAT "]\n",
+ uid, big_op_spacing2, from->uid, big_op_spacing4, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" SUB_LOWER_FORMAT "]+\\n["
+ DEPTH_FORMAT "]+%dM\n",
+ uid, uid, from->uid, big_op_spacing5);
+ }
+ else
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return res;
+}
+
+void limit_box::output()
+{
+ if (output_format == troff) {
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ if (to != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u"
+ "+(-\\n[" WIDTH_FORMAT "]u+\\n[" SUB_KERN_FORMAT "]u/2u)'",
+ uid, to->uid, p->uid);
+ to->output();
+ printf(DELIMITER_CHAR);
+ }
+ if (from != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'\\n[" SUB_LOWER_FORMAT "]u'", uid);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u"
+ "+(-\\n[" SUB_KERN_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid, from->uid);
+ from->output();
+ printf(DELIMITER_CHAR);
+ }
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u"
+ "-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ if (from != 0 && to != 0) {
+ printf("<munderover>");
+ p->output();
+ from->output();
+ to->output();
+ printf("</munderover>");
+ }
+ else if (from != 0) {
+ printf("<munder>");
+ p->output();
+ from->output();
+ printf("</munder>");
+ }
+ else if (to != 0) {
+ printf("<mover>");
+ p->output();
+ to->output();
+ printf("</mover>");
+ }
+ }
+}
+
+void limit_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " }");
+ if (from) {
+ fprintf(stderr, " from { ");
+ from->debug_print();
+ fprintf(stderr, " }");
+ }
+ if (to) {
+ fprintf(stderr, " to { ");
+ to->debug_print();
+ fprintf(stderr, " }");
+ }
+}
+
+void limit_box::check_tabs(int level)
+{
+ if (to)
+ to->check_tabs(level + 1);
+ if (from)
+ from->check_tabs(level + 1);
+ p->check_tabs(level + 1);
+}
diff --git a/src/preproc/eqn/list.cpp b/src/preproc/eqn/list.cpp
new file mode 100644
index 0000000..52644c5
--- /dev/null
+++ b/src/preproc/eqn/list.cpp
@@ -0,0 +1,241 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "pbox.h"
+
+list_box *box::to_list_box()
+{
+ return 0;
+}
+
+list_box *list_box::to_list_box()
+{
+ return this;
+}
+
+void list_box::append(box *pp)
+{
+ list_box *q = pp->to_list_box();
+ if (q == 0)
+ list.append(pp);
+ else {
+ for (int i = 0; i < q->list.len; i++) {
+ list.append(q->list.p[i]);
+ q->list.p[i] = 0;
+ }
+ q->list.len = 0;
+ delete q;
+ }
+}
+
+list_box::list_box(box *pp) : list(pp), sty(-1)
+{
+ list_box *q = pp->to_list_box();
+ if (q != 0) {
+ // flatten it
+ list.p[0] = q->list.p[0];
+ for (int i = 1; i < q->list.len; i++) {
+ list.append(q->list.p[i]);
+ q->list.p[i] = 0;
+ }
+ q->list.len = 0;
+ delete q;
+ }
+}
+
+static int compute_spacing(int is_script, int left, int right)
+{
+ if (left == SUPPRESS_TYPE || right == SUPPRESS_TYPE)
+ return 0;
+ if (left == PUNCTUATION_TYPE)
+ return is_script ? 0 : thin_space;
+ if (left == OPENING_TYPE || right == CLOSING_TYPE)
+ return 0;
+ if (right == BINARY_TYPE || left == BINARY_TYPE)
+ return is_script ? 0 : medium_space;
+ if (right == RELATION_TYPE) {
+ if (left == RELATION_TYPE)
+ return 0;
+ else
+ return is_script ? 0 : thick_space;
+ }
+ if (left == RELATION_TYPE)
+ return is_script ? 0 : thick_space;
+ if (right == OPERATOR_TYPE)
+ return thin_space;
+ if (left == INNER_TYPE || right == INNER_TYPE)
+ return is_script ? 0 : thin_space;
+ if (left == OPERATOR_TYPE && right == ORDINARY_TYPE)
+ return thin_space;
+ return 0;
+}
+
+int list_box::compute_metrics(int style)
+{
+ sty = style;
+ int i;
+ for (i = 0; i < list.len; i++) {
+ int t = list.p[i]->spacing_type;
+ // 5
+ if (t == BINARY_TYPE) {
+ int prevt;
+ if (i == 0
+ || (prevt = list.p[i-1]->spacing_type) == BINARY_TYPE
+ || prevt == OPERATOR_TYPE
+ || prevt == RELATION_TYPE
+ || prevt == OPENING_TYPE
+ || prevt == SUPPRESS_TYPE
+ || prevt == PUNCTUATION_TYPE)
+ list.p[i]->spacing_type = ORDINARY_TYPE;
+ }
+ // 7
+ else if ((t == RELATION_TYPE || t == CLOSING_TYPE
+ || t == PUNCTUATION_TYPE)
+ && i > 0 && list.p[i-1]->spacing_type == BINARY_TYPE)
+ list.p[i-1]->spacing_type = ORDINARY_TYPE;
+ }
+ for (i = 0; i < list.len; i++) {
+ unsigned flags = 0;
+ if (i - 1 >= 0 && list.p[i - 1]->right_is_italic())
+ flags |= HINT_PREV_IS_ITALIC;
+ if (i + 1 < list.len && list.p[i + 1]->left_is_italic())
+ flags |= HINT_NEXT_IS_ITALIC;
+ if (flags)
+ list.p[i]->hint(flags);
+ }
+ is_script = (style <= SCRIPT_STYLE);
+ int total_spacing = 0;
+ for (i = 1; i < list.len; i++)
+ total_spacing += compute_spacing(is_script, list.p[i-1]->spacing_type,
+ list.p[i]->spacing_type);
+ int res = 0;
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple()) {
+ int r = list.p[i]->compute_metrics(style);
+ if (r) {
+ if (res)
+ error("multiple marks and lineups");
+ else {
+ compute_sublist_width(i);
+ printf(".nr " MARK_REG " +\\n[" TEMP_REG"]\n");
+ res = r;
+ }
+ }
+ }
+ printf(".nr " WIDTH_FORMAT " %dM", uid, total_spacing);
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple())
+ printf("+\\n[" WIDTH_FORMAT "]", list.p[i]->uid);
+ printf("\n");
+ printf(".nr " HEIGHT_FORMAT " 0", uid);
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple())
+ printf(">?\\n[" HEIGHT_FORMAT "]", list.p[i]->uid);
+ printf("\n");
+ printf(".nr " DEPTH_FORMAT " 0", uid);
+ for (i = 0; i < list.len; i++)
+ if (!list.p[i]->is_simple())
+ printf(">?\\n[" DEPTH_FORMAT "]", list.p[i]->uid);
+ printf("\n");
+ int have_simple = 0;
+ for (i = 0; i < list.len && !have_simple; i++)
+ have_simple = list.p[i]->is_simple();
+ if (have_simple) {
+ printf(".nr " WIDTH_FORMAT " +\\w" DELIMITER_CHAR, uid);
+ for (i = 0; i < list.len; i++)
+ if (list.p[i]->is_simple())
+ list.p[i]->output();
+ printf(DELIMITER_CHAR "\n");
+ printf(".nr " HEIGHT_FORMAT " \\n[rst]>?\\n[" HEIGHT_FORMAT "]\n",
+ uid, uid);
+ printf(".nr " DEPTH_FORMAT " 0-\\n[rsb]>?\\n[" DEPTH_FORMAT "]\n",
+ uid, uid);
+ }
+ return res;
+}
+
+void list_box::compute_sublist_width(int n)
+{
+ int total_spacing = 0;
+ int i;
+ for (i = 1; i < n + 1 && i < list.len; i++)
+ total_spacing += compute_spacing(is_script, list.p[i-1]->spacing_type,
+ list.p[i]->spacing_type);
+ printf(".nr " TEMP_REG " %dM", total_spacing);
+ for (i = 0; i < n; i++)
+ if (!list.p[i]->is_simple())
+ printf("+\\n[" WIDTH_FORMAT "]", list.p[i]->uid);
+ int have_simple = 0;
+ for (i = 0; i < n && !have_simple; i++)
+ have_simple = list.p[i]->is_simple();
+ if (have_simple) {
+ printf("+\\w" DELIMITER_CHAR);
+ for (i = 0; i < n; i++)
+ if (list.p[i]->is_simple())
+ list.p[i]->output();
+ printf(DELIMITER_CHAR);
+ }
+ printf("\n");
+}
+
+void list_box::compute_subscript_kern()
+{
+ // We can only call compute_subscript_kern if we have called
+ // compute_metrics first.
+ if (list.p[list.len-1]->is_simple())
+ list.p[list.len-1]->compute_metrics(sty);
+ list.p[list.len-1]->compute_subscript_kern();
+ printf(".nr " SUB_KERN_FORMAT " \\n[" SUB_KERN_FORMAT "]\n",
+ uid, list.p[list.len-1]->uid);
+}
+
+void list_box::output()
+{
+ if (output_format == mathml)
+ printf("<mrow>");
+ for (int i = 0; i < list.len; i++) {
+ if (output_format == troff && i > 0) {
+ int n = compute_spacing(is_script,
+ list.p[i-1]->spacing_type,
+ list.p[i]->spacing_type);
+ if (n > 0)
+ printf("\\h'%dM'", n);
+ }
+ list.p[i]->output();
+ }
+ if (output_format == mathml)
+ printf("</mrow>");
+}
+
+void list_box::handle_char_type(int st, int ft)
+{
+ for (int i = 0; i < list.len; i++)
+ list.p[i]->handle_char_type(st, ft);
+}
+
+void list_box::debug_print()
+{
+ list.list_debug_print(" ");
+}
+
+void list_box::check_tabs(int level)
+{
+ list.list_check_tabs(level);
+}
diff --git a/src/preproc/eqn/main.cpp b/src/preproc/eqn/main.cpp
new file mode 100644
index 0000000..81d5184
--- /dev/null
+++ b/src/preproc/eqn/main.cpp
@@ -0,0 +1,485 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "stringclass.h"
+#include "device.h"
+#include "searchpath.h"
+#include "macropath.h"
+#include "htmlhint.h"
+#include "pbox.h"
+#include "ctype.h"
+#include "lf.h"
+
+#define STARTUP_FILE "eqnrc"
+
+extern int yyparse();
+extern "C" const char *Version_string;
+
+static char *delim_search (char *, int);
+static int inline_equation (FILE *, string &, string &);
+
+char start_delim = '\0';
+char end_delim = '\0';
+int non_empty_flag;
+int inline_flag;
+int draw_flag = 0;
+int one_size_reduction_flag = 0;
+int compatible_flag = 0;
+int no_newline_in_delim_flag = 0;
+int html = 0;
+int xhtml = 0;
+eqnmode_t output_format;
+
+static const char *input_char_description(int c)
+{
+ switch (c) {
+ case '\001':
+ return "a leader character";
+ case '\n':
+ return "a newline character";
+ case '\b':
+ return "a backspace character";
+ case '\t':
+ return "a tab character";
+ case ' ':
+ return "a space character";
+ case '\177':
+ return "a delete character";
+ }
+ size_t bufsz = sizeof "character code " + INT_DIGITS + 1;
+ // repeat expression; no VLAs in ISO C++
+ static char buf[sizeof "character code " + INT_DIGITS + 1];
+ (void) memset(buf, 0, bufsz);
+ if (csprint(c)) {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ return buf;
+ }
+ (void) sprintf(buf, "character code %d", c);
+ return buf;
+}
+
+static bool read_line(FILE *fp, string *p)
+{
+ p->clear();
+ int c = -1;
+ while ((c = getc(fp)) != EOF) {
+ if (!is_invalid_input_char(c))
+ *p += char(c);
+ else
+ error("invalid input (%1)", input_char_description(c));
+ if (c == '\n')
+ break;
+ }
+ return (p->length() > 0);
+}
+
+void do_file(FILE *fp, const char *filename)
+{
+ string linebuf;
+ string str;
+ string fn(filename);
+ fn += '\0';
+ normalize_for_lf(fn);
+ current_filename = fn.contents();
+ if (output_format == troff)
+ printf(".lf 1 %s\n", current_filename);
+ current_lineno = 1;
+ while (read_line(fp, &linebuf)) {
+ if (linebuf.length() >= 4
+ && linebuf[0] == '.' && linebuf[1] == 'l' && linebuf[2] == 'f'
+ && (linebuf[3] == ' ' || linebuf[3] == '\n' || compatible_flag))
+ {
+ put_string(linebuf, stdout);
+ linebuf += '\0';
+ // In GNU roff, `lf` assigns the number of the _next_ line.
+ if (interpret_lf_args(linebuf.contents() + 3))
+ current_lineno--;
+ }
+ else if (linebuf.length() >= 4
+ && linebuf[0] == '.'
+ && linebuf[1] == 'E'
+ && linebuf[2] == 'Q'
+ && (linebuf[3] == ' ' || linebuf[3] == '\n'
+ || compatible_flag)) {
+ put_string(linebuf, stdout);
+ int start_lineno = current_lineno + 1;
+ str.clear();
+ for (;;) {
+ if (!read_line(fp, &linebuf)) {
+ current_lineno = 0; // suppress report of line number
+ fatal("end of file before .EN");
+ }
+ if (linebuf.length() >= 3
+ && linebuf[0] == '.'
+ && linebuf[1] == 'E') {
+ if (linebuf[2] == 'N'
+ && (linebuf.length() == 3 || linebuf[3] == ' '
+ || linebuf[3] == '\n' || compatible_flag))
+ break;
+ else if (linebuf[2] == 'Q' && linebuf.length() > 3
+ && (linebuf[3] == ' ' || linebuf[3] == '\n'
+ || compatible_flag)) {
+ current_lineno++; // We just read another line.
+ fatal("equations cannot be nested (.EQ within .EQ)");
+ }
+ }
+ str += linebuf;
+ }
+ str += '\0';
+ start_string();
+ init_lex(str.contents(), current_filename, start_lineno);
+ non_empty_flag = 0;
+ inline_flag = 0;
+ yyparse();
+ restore_compatibility();
+ if (non_empty_flag) {
+ if (output_format == mathml)
+ putchar('\n');
+ else {
+ current_lineno++;
+ printf(".lf %d\n", current_lineno);
+ output_string();
+ }
+ }
+ if (output_format == troff) {
+ current_lineno++;
+ printf(".lf %d\n", current_lineno);
+ }
+ put_string(linebuf, stdout);
+ }
+ else if (start_delim != '\0' && linebuf.search(start_delim) >= 0
+ && inline_equation(fp, linebuf, str))
+ ;
+ else
+ put_string(linebuf, stdout);
+ current_lineno++;
+ }
+ current_filename = 0;
+ current_lineno = 0;
+}
+
+// Handle an inline equation. Return 1 if it was an inline equation,
+// otherwise.
+static int inline_equation(FILE *fp, string &linebuf, string &str)
+{
+ linebuf += '\0';
+ char *ptr = &linebuf[0];
+ char *start = delim_search(ptr, start_delim);
+ if (!start) {
+ // It wasn't a delimiter after all.
+ linebuf.set_length(linebuf.length() - 1); // strip the '\0'
+ return 0;
+ }
+ start_string();
+ inline_flag = 1;
+ for (;;) {
+ if (no_newline_in_delim_flag && strchr(start + 1, end_delim) == 0) {
+ error("unterminated inline equation; started with %1,"
+ " expecting %2", input_char_description(start_delim),
+ input_char_description(end_delim));
+ char *nl = strchr(start + 1, '\n');
+ if (nl != 0)
+ *nl = '\0';
+ do_text(ptr);
+ break;
+ }
+ int start_lineno = current_lineno;
+ *start = '\0';
+ do_text(ptr);
+ ptr = start + 1;
+ str.clear();
+ for (;;) {
+ char *end = strchr(ptr, end_delim);
+ if (end != 0) {
+ *end = '\0';
+ str += ptr;
+ ptr = end + 1;
+ break;
+ }
+ str += ptr;
+ if (!read_line(fp, &linebuf))
+ fatal("unterminated inline equation; started with %1,"
+ " expecting %2", input_char_description(start_delim),
+ input_char_description(end_delim));
+ linebuf += '\0';
+ ptr = &linebuf[0];
+ }
+ str += '\0';
+ if (output_format == troff && html) {
+ printf(".as1 %s ", LINE_STRING);
+ html_begin_suppress();
+ printf("\n");
+ }
+ init_lex(str.contents(), current_filename, start_lineno);
+ yyparse();
+ if (output_format == troff && html) {
+ printf(".as1 %s ", LINE_STRING);
+ html_end_suppress();
+ printf("\n");
+ }
+ if (output_format == mathml)
+ printf("\n");
+ if (xhtml) {
+ /* skip leading spaces */
+ while ((*ptr != '\0') && (*ptr == ' '))
+ ptr++;
+ }
+ start = delim_search(ptr, start_delim);
+ if (start == 0) {
+ char *nl = strchr(ptr, '\n');
+ if (nl != 0)
+ *nl = '\0';
+ do_text(ptr);
+ break;
+ }
+ }
+ restore_compatibility();
+ if (output_format == troff)
+ printf(".lf %d\n", current_lineno);
+ output_string();
+ if (output_format == troff)
+ printf(".lf %d\n", current_lineno + 1);
+ return 1;
+}
+
+/* Search for delim. Skip over number register and string names etc. */
+
+static char *delim_search(char *ptr, int delim)
+{
+ while (*ptr) {
+ if (*ptr == delim)
+ return ptr;
+ if (*ptr++ == '\\') {
+ switch (*ptr) {
+ case 'n':
+ case '*':
+ case 'f':
+ case 'g':
+ case 'k':
+ switch (*++ptr) {
+ case '\0':
+ case '\\':
+ break;
+ case '(':
+ if (*++ptr != '\\' && *ptr != '\0'
+ && *++ptr != '\\' && *ptr != '\0')
+ ptr++;
+ break;
+ case '[':
+ while (*++ptr != '\0')
+ if (*ptr == ']') {
+ ptr++;
+ break;
+ }
+ break;
+ default:
+ ptr++;
+ break;
+ }
+ break;
+ case '\\':
+ case '\0':
+ break;
+ default:
+ ptr++;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+void usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-CNrR] [-d xy] [-f font] [-m n] [-M dir] [-p n] [-s n]"
+ " [-T name] [file ...]\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int opt;
+ int load_startup_file = 1;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "CNrRd:f:m:M:p:s:T:v",
+ long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'C':
+ compatible_flag = 1;
+ break;
+ case 'R': // don't load eqnrc
+ load_startup_file = 0;
+ break;
+ case 'M':
+ config_macro_path.command_line_dir(optarg);
+ break;
+ case 'v':
+ printf("GNU eqn (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'd':
+ if (optarg[0] == '\0' || optarg[1] == '\0')
+ error("'-d' option requires a two-character argument");
+ else if (is_invalid_input_char(optarg[0]))
+ error("invalid delimiter (%1) in '-d' option argument",
+ input_char_description(optarg[0]));
+ else if (is_invalid_input_char(optarg[1]))
+ error("invalid delimiter (%1) in '-d' option argument",
+ input_char_description(optarg[1]));
+ else {
+ start_delim = optarg[0];
+ end_delim = optarg[1];
+ }
+ break;
+ case 'f':
+ set_gfont(optarg);
+ break;
+ case 'T':
+ device = optarg;
+ if (strcmp(device, "ps:html") == 0) {
+ device = "ps";
+ html = 1;
+ }
+ else if (strcmp(device, "MathML") == 0) {
+ output_format = mathml;
+ load_startup_file = 0;
+ }
+ else if (strcmp(device, "mathml:xhtml") == 0) {
+ device = "MathML";
+ output_format = mathml;
+ load_startup_file = 0;
+ xhtml = 1;
+ }
+ break;
+ case 's':
+ if (set_gsize(optarg))
+ warning("option '-s' is deprecated");
+ else
+ error("invalid size '%1' in '-s' option argument ", optarg);
+ break;
+ case 'p':
+ {
+ int n;
+ if (sscanf(optarg, "%d", &n) == 1) {
+ warning("option '-p' is deprecated");
+ set_script_reduction(n);
+ }
+ else
+ error("invalid size '%1' in '-p' option argument ", optarg);
+ }
+ break;
+ case 'm':
+ {
+ int n;
+ if (sscanf(optarg, "%d", &n) == 1)
+ set_minimum_size(n);
+ else
+ error("invalid size '%1' in '-n' option argument", optarg);
+ }
+ break;
+ case 'r':
+ one_size_reduction_flag = 1;
+ break;
+ case 'N':
+ no_newline_in_delim_flag = 1;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ init_table(device);
+ init_char_table();
+ printf(".do if !dEQ .ds EQ\n"
+ ".do if !dEN .ds EN\n");
+ if (output_format == troff) {
+ printf(".if !'\\*(.T'%s' "
+ ".if !'\\*(.T'html' " // the html device uses '-Tps' to render
+ // equations as images
+ ".tm warning: %s should have been given a '-T\\*(.T' option\n",
+ device, program_name);
+ printf(".if '\\*(.T'html' "
+ ".if !'%s'ps' "
+ ".tm warning: %s should have been given a '-Tps' option\n",
+ device, program_name);
+ printf(".if '\\*(.T'html' "
+ ".if !'%s'ps' "
+ ".tm warning: (it is advisable to invoke groff via: groff -Thtml -e)\n",
+ device);
+ }
+ if (load_startup_file) {
+ char *path;
+ FILE *fp = config_macro_path.open_file(STARTUP_FILE, &path);
+ if (fp) {
+ do_file(fp, path);
+ if (fclose(fp) < 0)
+ fatal("unable to close '%1': %2", STARTUP_FILE,
+ strerror(errno));
+ free(path);
+ }
+ }
+ if (optind >= argc)
+ do_file(stdin, "-");
+ else
+ for (int i = optind; i < argc; i++)
+ if (strcmp(argv[i], "-") == 0)
+ do_file(stdin, "-");
+ else {
+ errno = 0;
+ FILE *fp = fopen(argv[i], "r");
+ if (!fp)
+ fatal("unable to open '%1': %2", argv[i], strerror(errno));
+ else {
+ do_file(fp, argv[i]);
+ if (fclose(fp) < 0)
+ fatal("unable to close '%1': %2", argv[i], strerror(errno));
+ }
+ }
+ if (ferror(stdout))
+ fatal("standard output stream is in an error state");
+ if (fflush(stdout) < 0)
+ fatal("unable to flush standard output stream: %1",
+ strerror(errno));
+ exit(EXIT_SUCCESS);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/mark.cpp b/src/preproc/eqn/mark.cpp
new file mode 100644
index 0000000..9427b10
--- /dev/null
+++ b/src/preproc/eqn/mark.cpp
@@ -0,0 +1,120 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "pbox.h"
+
+class mark_box : public pointer_box {
+public:
+ mark_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+// we push down marks so that they don't interfere with spacing
+
+box *make_mark_box(box *p)
+{
+ list_box *b = p->to_list_box();
+ if (b != 0) {
+ b->list.p[0] = make_mark_box(b->list.p[0]);
+ return b;
+ }
+ else
+ return new mark_box(p);
+}
+
+mark_box::mark_box(box *pp) : pointer_box(pp)
+{
+}
+
+void mark_box::output()
+{
+ p->output();
+}
+
+int mark_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ if (res)
+ error("multiple marks and lineups");
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " MARK_REG " 0\n");
+ return FOUND_MARK;
+}
+
+void mark_box::debug_print()
+{
+ fprintf(stderr, "mark { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+
+class lineup_box : public pointer_box {
+public:
+ lineup_box(box *);
+ void output();
+ int compute_metrics(int style);
+ void debug_print();
+};
+
+// we push down lineups so that they don't interfere with spacing
+
+box *make_lineup_box(box *p)
+{
+ list_box *b = p->to_list_box();
+ if (b != 0) {
+ b->list.p[0] = make_lineup_box(b->list.p[0]);
+ return b;
+ }
+ else
+ return new lineup_box(p);
+}
+
+lineup_box::lineup_box(box *pp) : pointer_box(pp)
+{
+}
+
+void lineup_box::output()
+{
+ p->output();
+}
+
+int lineup_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ if (res)
+ error("multiple marks and lineups");
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " MARK_REG " 0\n");
+ return FOUND_LINEUP;
+}
+
+void lineup_box::debug_print()
+{
+ fprintf(stderr, "lineup { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
diff --git a/src/preproc/eqn/neqn.1.man b/src/preproc/eqn/neqn.1.man
new file mode 100644
index 0000000..758a150
--- /dev/null
+++ b/src/preproc/eqn/neqn.1.man
@@ -0,0 +1,92 @@
+.TH @g@neqn @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@neqn \- format equations for character-cell terminal output
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2001-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_neqn_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@neqn
+.RI [ @g@eqn-argument \~.\|.\|.]
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I @g@neqn
+invokes the
+.MR @g@eqn @MAN1EXT@
+command with the
+.B ascii
+output device.
+.
+.
+.LP
+.I @g@eqn
+does not support low-resolution,
+typewriter-like devices,
+although it may work adequately for very simple input.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR @g@eqn @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_neqn_1_man_C]
+.do rr *groff_neqn_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=nroff textwidth=72:
diff --git a/src/preproc/eqn/neqn.sh b/src/preproc/eqn/neqn.sh
new file mode 100644
index 0000000..fb8d616
--- /dev/null
+++ b/src/preproc/eqn/neqn.sh
@@ -0,0 +1,26 @@
+#! /bin/sh
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 2 of the License (GPL2).
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# The GPL2 license text is available in the internet at
+# <http://www.gnu.org/licenses/gpl-2.0.txt>.
+
+# Provision of this shell script should not be taken to imply that use of
+# GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported.
+
+@GROFF_BIN_PATH_SETUP@
+PATH="$GROFF_RUNTIME$PATH"
+export PATH
+exec @g@eqn -Tascii ${1+"$@"}
+
+# eof
diff --git a/src/preproc/eqn/other.cpp b/src/preproc/eqn/other.cpp
new file mode 100644
index 0000000..1ddc8fb
--- /dev/null
+++ b/src/preproc/eqn/other.cpp
@@ -0,0 +1,706 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+class accent_box : public pointer_box {
+private:
+ box *ab;
+public:
+ accent_box(box *, box *);
+ ~accent_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+box *make_accent_box(box *p, box *q)
+{
+ return new accent_box(p, q);
+}
+
+accent_box::accent_box(box *pp, box *qq) : pointer_box(pp), ab(qq)
+{
+}
+
+accent_box::~accent_box()
+{
+ delete ab;
+}
+
+#if 0
+int accent_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ p->compute_skew();
+ ab->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" HEIGHT_FORMAT "]-%dM>?0\n",
+ uid, p->uid, x_height);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+\\n["
+ SUP_RAISE_FORMAT "]\n",
+ uid, ab->uid, uid);
+ return r;
+}
+
+void accent_box::output()
+{
+ if (output_format == troff) {
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u+\\n["
+ SKEW_FORMAT "]u'",
+ p->uid, ab->uid, p->uid);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ ab->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", ab->uid);
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\h'-(\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u+\\n["
+ SKEW_FORMAT "]u)'",
+ p->uid, ab->uid, p->uid);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<mover accent='true'>");
+ p->output();
+ ab->output();
+ printf("</mover>")
+ }
+}
+#endif
+
+/* This version copes with the possibility of an accent's being wider
+than its accentee. LEFT_WIDTH_FORMAT gives the distance from the
+left edge of the resulting box to the middle of the accentee's box.*/
+
+int accent_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ p->compute_skew();
+ ab->compute_metrics(style);
+ printf(".nr " LEFT_WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2-\\n[" SKEW_FORMAT "])\n",
+ uid, p->uid, ab->uid, p->uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2+\\n[" SKEW_FORMAT "])"
+ "+\\n[" LEFT_WIDTH_FORMAT "]\n",
+ uid, p->uid, ab->uid, p->uid, uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" HEIGHT_FORMAT "]-%dM>?0\n",
+ uid, p->uid, x_height);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+\\n["
+ SUP_RAISE_FORMAT "]\n",
+ uid, ab->uid, uid);
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" LEFT_WIDTH_FORMAT "]"
+ "-(\\n[" WIDTH_FORMAT "]/2)'\n",
+ uid, p->uid);
+ return r;
+}
+
+void accent_box::output()
+{
+ if (output_format == troff) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u+\\n[" SKEW_FORMAT "]u"
+ "-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid, ab->uid);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ ab->output();
+ printf(DELIMITER_CHAR);
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<mover accent='true'>");
+ p->output();
+ ab->output();
+ printf("</mover>");
+ }
+}
+
+void accent_box::check_tabs(int level)
+{
+ ab->check_tabs(level + 1);
+ p->check_tabs(level + 1);
+}
+
+void accent_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } accent { ");
+ ab->debug_print();
+ fprintf(stderr, " }");
+}
+
+class overline_char_box : public simple_box {
+public:
+ overline_char_box();
+ void output();
+ void debug_print();
+};
+
+overline_char_box::overline_char_box()
+{
+}
+
+void overline_char_box::output()
+{
+ if (output_format == troff) {
+ printf("\\v'-%dM/2u-%dM'", 7*default_rule_thickness, x_height);
+ printf((draw_flag ? "\\D'l%dM 0'" : "\\l'%dM\\&\\(ru'"),
+ accent_width);
+ printf("\\v'%dM/2u+%dM'", 7*default_rule_thickness, x_height);
+ }
+ else if (output_format == mathml)
+ printf("<mo>&macr;</mo>");
+}
+
+void overline_char_box::debug_print()
+{
+ fprintf(stderr, "<overline char>");
+}
+
+class overline_box : public pointer_box {
+public:
+ overline_box(box *);
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+};
+
+box *make_overline_box(box *p)
+{
+ if (p->is_char())
+ return new accent_box(p, new overline_char_box);
+ else
+ return new overline_box(p);
+}
+
+overline_box::overline_box(box *pp) : pointer_box(pp)
+{
+}
+
+int overline_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(cramped_style(style));
+ // 9
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+%dM\n",
+ uid, p->uid, default_rule_thickness*5);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void overline_box::output()
+{
+ if (output_format == troff) {
+ // 9
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'-\\n[" HEIGHT_FORMAT "]u-(%dM/2u)'",
+ p->uid, 7*default_rule_thickness);
+ if (draw_flag)
+ printf("\\D'l\\n[" WIDTH_FORMAT "]u 0'", p->uid);
+ else
+ printf("\\l'\\n[" WIDTH_FORMAT "]u\\&\\(ru'", p->uid);
+ printf(DELIMITER_CHAR);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<mover accent='false'>");
+ p->output();
+ printf("<mo>&macr;</mo></mover>");
+ }
+}
+
+void overline_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } bar");
+}
+
+class uaccent_box : public pointer_box {
+ box *ab;
+public:
+ uaccent_box(box *, box *);
+ ~uaccent_box();
+ int compute_metrics(int);
+ void output();
+ void compute_subscript_kern();
+ void check_tabs(int);
+ void debug_print();
+};
+
+box *make_uaccent_box(box *p, box *q)
+{
+ return new uaccent_box(p, q);
+}
+
+uaccent_box::uaccent_box(box *pp, box *qq)
+: pointer_box(pp), ab(qq)
+{
+}
+
+uaccent_box::~uaccent_box()
+{
+ delete ab;
+}
+
+int uaccent_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ ab->compute_metrics(style);
+ printf(".nr " LEFT_WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2)\n",
+ uid, p->uid, ab->uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]/2"
+ ">?(\\n[" WIDTH_FORMAT "]/2)"
+ "+\\n[" LEFT_WIDTH_FORMAT "]\n",
+ uid, p->uid, ab->uid, uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ "+\\n[" DEPTH_FORMAT "]\n",
+ uid, p->uid, ab->uid);
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" LEFT_WIDTH_FORMAT "]"
+ "-(\\n[" WIDTH_FORMAT "]/2)'\n",
+ uid, p->uid);
+ return r;
+}
+
+void uaccent_box::output()
+{
+ if (output_format == troff) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, ab->uid);
+ printf("\\v'\\n[" DEPTH_FORMAT "]u'", p->uid);
+ ab->output();
+ printf(DELIMITER_CHAR);
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" LEFT_WIDTH_FORMAT "]u-(\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, p->uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<munder accent='true'>");
+ p->output();
+ ab->output();
+ printf("</munder>");
+ }
+}
+
+void uaccent_box::check_tabs(int level)
+{
+ ab->check_tabs(level + 1);
+ p->check_tabs(level + 1);
+}
+
+void uaccent_box::compute_subscript_kern()
+{
+ box::compute_subscript_kern(); // want 0 subscript kern
+}
+
+void uaccent_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } uaccent { ");
+ ab->debug_print();
+ fprintf(stderr, " }");
+}
+
+class underline_char_box : public simple_box {
+public:
+ underline_char_box();
+ void output();
+ void debug_print();
+};
+
+underline_char_box::underline_char_box()
+{
+}
+
+void underline_char_box::output()
+{
+ if (output_format == troff) {
+ printf("\\v'%dM/2u'", 7*default_rule_thickness);
+ printf((draw_flag ? "\\D'l%dM 0'" : "\\l'%dM\\&\\(ru'"),
+ accent_width);
+ printf("\\v'-%dM/2u'", 7*default_rule_thickness);
+ }
+ else if (output_format == mathml)
+ printf("<mo>&lowbar;</mo>");
+}
+
+void underline_char_box::debug_print()
+{
+ fprintf(stderr, "<underline char>");
+}
+
+
+class underline_box : public pointer_box {
+public:
+ underline_box(box *);
+ int compute_metrics(int);
+ void output();
+ void compute_subscript_kern();
+ void debug_print();
+};
+
+box *make_underline_box(box *p)
+{
+ if (p->is_char())
+ return new uaccent_box(p, new underline_char_box);
+ else
+ return new underline_box(p);
+}
+
+underline_box::underline_box(box *pp) : pointer_box(pp)
+{
+}
+
+int underline_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ // 10
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]+%dM\n",
+ uid, p->uid, default_rule_thickness*5);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void underline_box::output()
+{
+ if (output_format == troff) {
+ // 10
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'\\n[" DEPTH_FORMAT "]u+(%dM/2u)'",
+ p->uid, 7*default_rule_thickness);
+ if (draw_flag)
+ printf("\\D'l\\n[" WIDTH_FORMAT "]u 0'", p->uid);
+ else
+ printf("\\l'\\n[" WIDTH_FORMAT "]u\\&\\(ru'", p->uid);
+ printf(DELIMITER_CHAR);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<munder accent='true'>");
+ p->output();
+ printf("<mo>&macr;</mo></munder>");
+ }
+}
+
+// we want an underline box to have 0 subscript kern
+
+void underline_box::compute_subscript_kern()
+{
+ box::compute_subscript_kern();
+}
+
+void underline_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " } under");
+}
+
+size_box::size_box(char *s, box *pp) : pointer_box(pp), size(s)
+{
+}
+
+int size_box::compute_metrics(int style)
+{
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ printf(".ps %s\n", size);
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ int r = p->compute_metrics(style);
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void size_box::output()
+{
+ if (output_format == troff) {
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ p->output();
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<mstyle mathsize='%s'>", size);
+ p->output();
+ printf("</mstyle>");
+ }
+}
+
+size_box::~size_box()
+{
+ free(size);
+}
+
+void size_box::debug_print()
+{
+ fprintf(stderr, "size %s { ", size);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+
+font_box::font_box(char *s, box *pp) : pointer_box(pp), f(s)
+{
+}
+
+font_box::~font_box()
+{
+ free(f);
+}
+
+int font_box::compute_metrics(int style)
+{
+ const char *old_roman_font = current_roman_font;
+ current_roman_font = f;
+ printf(".nr " FONT_FORMAT " \\n[.f]\n", uid);
+ printf(".ft %s\n", f);
+ int r = p->compute_metrics(style);
+ current_roman_font = old_roman_font;
+ printf(".ft \\n[" FONT_FORMAT "]\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void font_box::output()
+{
+ if (output_format == troff) {
+ printf("\\f[%s]", f);
+ const char *old_roman_font = current_roman_font;
+ current_roman_font = f;
+ p->output();
+ current_roman_font = old_roman_font;
+ printf("\\f[\\n[" FONT_FORMAT "]]", uid);
+ }
+ else if (output_format == mathml) {
+ const char *mlfont = f;
+ // bold and italic are already in MathML; translate eqn roman here
+ switch (f[0]) {
+ case 'I':
+ case 'i':
+ mlfont = "italic";
+ break;
+ case 'B':
+ case 'b':
+ mlfont = "bold";
+ break;
+ case 'R':
+ case 'r':
+ default:
+ mlfont = "normal";
+ break;
+ }
+ printf("<mstyle mathvariant='%s'>", mlfont);
+ p->output();
+ printf("</mstyle>");
+ }
+}
+
+void font_box::debug_print()
+{
+ fprintf(stderr, "font %s { ", f);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+fat_box::fat_box(box *pp) : pointer_box(pp)
+{
+}
+
+int fat_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]+%dM\n",
+ uid, p->uid, fat_offset);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ return r;
+}
+
+void fat_box::output()
+{
+ if (output_format == troff) {
+ p->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", p->uid);
+ printf("\\h'%dM'", fat_offset);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<mstyle mathvariant='double-struck'>");
+ p->output();
+ printf("</mstyle>");
+ }
+}
+
+
+void fat_box::debug_print()
+{
+ fprintf(stderr, "fat { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+
+vmotion_box::vmotion_box(int i, box *pp) : pointer_box(pp), n(i)
+{
+}
+
+int vmotion_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ if (n > 0) {
+ printf(".nr " HEIGHT_FORMAT " %dM+\\n[" HEIGHT_FORMAT "]\n",
+ uid, n, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ }
+ else {
+ printf(".nr " DEPTH_FORMAT " %dM+\\n[" DEPTH_FORMAT "]>?0\n",
+ uid, -n, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n",
+ uid, p->uid);
+ }
+ return r;
+}
+
+void vmotion_box::output()
+{
+ if (output_format == troff) {
+ printf("\\v'%dM'", -n);
+ p->output();
+ printf("\\v'%dM'", n);
+ }
+ else if (output_format == mathml) {
+ printf("<merror>eqn vertical motion cannot be expressed "
+ "in MathML</merror>");
+ p->output();
+ }
+}
+
+void vmotion_box::debug_print()
+{
+ if (n >= 0)
+ fprintf(stderr, "up %d { ", n);
+ else
+ fprintf(stderr, "down %d { ", -n);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+hmotion_box::hmotion_box(int i, box *pp) : pointer_box(pp), n(i)
+{
+}
+
+int hmotion_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]+%dM\n",
+ uid, p->uid, n);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ if (r)
+ printf(".nr " MARK_REG " +%dM\n", n);
+ return r;
+}
+
+void hmotion_box::output()
+{
+ if (output_format == troff) {
+ printf("\\h'%dM'", n);
+ p->output();
+ }
+ else if (output_format == mathml) {
+ printf("<merror>eqn horizontal motion cannot be expressed "
+ "in MathML</merror>");
+ p->output();
+ }
+}
+
+void hmotion_box::debug_print()
+{
+ if (n >= 0)
+ fprintf(stderr, "fwd %d { ", n);
+ else
+ fprintf(stderr, "back %d { ", -n);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+vcenter_box::vcenter_box(box *pp) : pointer_box(pp)
+{
+}
+
+int vcenter_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" DEPTH_FORMAT "]-\\n["
+ HEIGHT_FORMAT "]/2+%dM\n",
+ uid, p->uid, p->uid, axis_height);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]+\\n["
+ SUP_RAISE_FORMAT "]>?0\n", uid, p->uid, uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]-\\n["
+ SUP_RAISE_FORMAT "]>?0\n", uid, p->uid, uid);
+
+ return r;
+}
+
+void vcenter_box::output()
+{
+ if (output_format == troff)
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ p->output();
+ if (output_format == troff)
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+}
+
+void vcenter_box::debug_print()
+{
+ fprintf(stderr, "vcenter { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
diff --git a/src/preproc/eqn/over.cpp b/src/preproc/eqn/over.cpp
new file mode 100644
index 0000000..6a9cd9b
--- /dev/null
+++ b/src/preproc/eqn/over.cpp
@@ -0,0 +1,204 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "pbox.h"
+
+class over_box : public box {
+private:
+ int reduce_size;
+ box *num;
+ box *den;
+public:
+ over_box(int small, box *, box *);
+ ~over_box();
+ void debug_print();
+ int compute_metrics(int);
+ void output();
+ void check_tabs(int);
+};
+
+box *make_over_box(box *pp, box *qq)
+{
+ return new over_box(0, pp, qq);
+}
+
+box *make_small_over_box(box *pp, box *qq)
+{
+ return new over_box(1, pp, qq);
+}
+
+over_box::over_box(int is_small, box *pp, box *qq)
+: reduce_size(is_small), num(pp), den(qq)
+{
+ spacing_type = INNER_TYPE;
+}
+
+over_box::~over_box()
+{
+ delete num;
+ delete den;
+}
+
+int over_box::compute_metrics(int style)
+{
+ if (reduce_size) {
+ style = script_style(style);
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ set_script_size();
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ }
+ int mark_uid = 0;
+ int res = num->compute_metrics(style);
+ if (res)
+ mark_uid = num->uid;
+ int r = den->compute_metrics(cramped_style(style));
+ if (r && res)
+ error("multiple marks and lineups");
+ else {
+ mark_uid = den->uid;
+ res = r;
+ }
+ if (reduce_size)
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ printf(".nr " WIDTH_FORMAT " (\\n[" WIDTH_FORMAT "]>?\\n[" WIDTH_FORMAT "]",
+ uid, num->uid, den->uid);
+ // allow for \(ru being wider than both the numerator and denominator
+ if (!draw_flag)
+ fputs(">?\\w" DELIMITER_CHAR "\\(ru" DELIMITER_CHAR, stdout);
+ printf(")+%dM\n", null_delimiter_space*2 + over_hang*2);
+ // 15b
+ printf(".nr " SUP_RAISE_FORMAT " %dM\n",
+ uid, (reduce_size ? num2 : num1));
+ printf(".nr " SUB_LOWER_FORMAT " %dM\n",
+ uid, (reduce_size ? denom2 : denom1));
+
+ // 15d
+ printf(".nr " SUP_RAISE_FORMAT " +(\\n[" DEPTH_FORMAT
+ "]-\\n[" SUP_RAISE_FORMAT "]+%dM+(%dM/2)+%dM)>?0\n",
+ uid, num->uid, uid, axis_height, default_rule_thickness,
+ default_rule_thickness*(reduce_size ? 1 : 3));
+ printf(".nr " SUB_LOWER_FORMAT " +(\\n[" HEIGHT_FORMAT
+ "]-\\n[" SUB_LOWER_FORMAT "]-%dM+(%dM/2)+%dM)>?0\n",
+ uid, den->uid, uid, axis_height, default_rule_thickness,
+ default_rule_thickness*(reduce_size ? 1 : 3));
+
+
+ printf(".nr " HEIGHT_FORMAT " \\n[" SUP_RAISE_FORMAT "]+\\n["
+ HEIGHT_FORMAT "]\n",
+ uid, uid, num->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" SUB_LOWER_FORMAT "]+\\n["
+ DEPTH_FORMAT "]\n",
+ uid, uid, den->uid);
+ if (res)
+ printf(".nr " MARK_REG " +(\\n[" WIDTH_FORMAT "]-\\n["
+ WIDTH_FORMAT "]/2)\n", uid, mark_uid);
+ return res;
+}
+
+#define USE_Z
+
+void over_box::output()
+{
+ if (output_format == troff) {
+ if (reduce_size)
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ #ifdef USE_Z
+ printf("\\Z" DELIMITER_CHAR);
+ #endif
+ // move up to the numerator baseline
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ // move across so that it's centered
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, num->uid);
+
+ // print the numerator
+ num->output();
+
+ #ifdef USE_Z
+ printf(DELIMITER_CHAR);
+ #else
+ // back again
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", num->uid);
+ printf("\\h'-(\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, num->uid);
+ // down again
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ #endif
+ #ifdef USE_Z
+ printf("\\Z" DELIMITER_CHAR);
+ #endif
+ // move down to the denominator baseline
+ printf("\\v'\\n[" SUB_LOWER_FORMAT "]u'", uid);
+
+ // move across so that it's centered
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, den->uid);
+
+ // print the denominator
+ den->output();
+
+ #ifdef USE_Z
+ printf(DELIMITER_CHAR);
+ #else
+ // back again
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", den->uid);
+ printf("\\h'-(\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u)'",
+ uid, den->uid);
+ // up again
+ printf("\\v'-\\n[" SUB_LOWER_FORMAT "]u'", uid);
+ #endif
+ if (reduce_size)
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ // draw the line
+ printf("\\h'%dM'", null_delimiter_space);
+ printf("\\v'-%dM'", axis_height);
+ fputs(draw_flag ? "\\D'l" : "\\l'", stdout);
+ printf("\\n[" WIDTH_FORMAT "]u-%dM",
+ uid, 2*null_delimiter_space);
+ fputs(draw_flag ? " 0'" : "\\&\\(ru'", stdout);
+ printf("\\v'%dM'", axis_height);
+ printf("\\h'%dM'", null_delimiter_space);
+ }
+ else if (output_format == mathml) {
+ // FIXME: passing a displaystyle attribute doesn't validate.
+ printf("<mfrac>");
+ num->output();
+ den->output();
+ printf("</mfrac>");
+ }
+}
+
+void over_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ num->debug_print();
+ if (reduce_size)
+ fprintf(stderr, " } smallover { ");
+ else
+ fprintf(stderr, " } over { ");
+ den->debug_print();
+ fprintf(stderr, " }");
+}
+
+void over_box::check_tabs(int level)
+{
+ num->check_tabs(level + 1);
+ den->check_tabs(level + 1);
+}
diff --git a/src/preproc/eqn/pbox.h b/src/preproc/eqn/pbox.h
new file mode 100644
index 0000000..b185419
--- /dev/null
+++ b/src/preproc/eqn/pbox.h
@@ -0,0 +1,140 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+extern int fat_offset;
+
+extern int over_hang;
+extern int accent_width;
+
+extern int delimiter_factor;
+extern int delimiter_shortfall;
+
+extern int null_delimiter_space;
+extern int script_space;
+extern int thin_space;
+extern int medium_space;
+extern int thick_space;
+
+extern int num1;
+extern int num2;
+// we don't use num3, because we don't have \atop
+extern int denom1;
+extern int denom2;
+extern int axis_height;
+extern int sup1;
+extern int sup2;
+extern int sup3;
+extern int default_rule_thickness;
+extern int sub1;
+extern int sub2;
+extern int sup_drop;
+extern int sub_drop;
+extern int x_height;
+extern int big_op_spacing1;
+extern int big_op_spacing2;
+extern int big_op_spacing3;
+extern int big_op_spacing4;
+extern int big_op_spacing5;
+
+extern int baseline_sep;
+extern int shift_down;
+extern int column_sep;
+extern int matrix_side_sep;
+
+// ms.eqn relies on this!
+
+#define LINE_STRING "10"
+#define MARK_OR_LINEUP_FLAG_REG "MK"
+
+#define WIDTH_FORMAT PREFIX "w%d"
+#define HEIGHT_FORMAT PREFIX "h%d"
+#define DEPTH_FORMAT PREFIX "d%d"
+#define TOTAL_FORMAT PREFIX "t%d"
+#define SIZE_FORMAT PREFIX "z%d"
+#define SMALL_SIZE_FORMAT PREFIX "Z%d"
+#define SUP_RAISE_FORMAT PREFIX "p%d"
+#define SUB_LOWER_FORMAT PREFIX "b%d"
+#define SUB_KERN_FORMAT PREFIX "k%d"
+#define FONT_FORMAT PREFIX "f%d"
+#define SKEW_FORMAT PREFIX "s%d"
+#define LEFT_WIDTH_FORMAT PREFIX "lw%d"
+#define LEFT_DELIM_STRING_FORMAT PREFIX "l%d"
+#define RIGHT_DELIM_STRING_FORMAT PREFIX "r%d"
+#define SQRT_STRING_FORMAT PREFIX "sqr%d"
+#define SQRT_WIDTH_FORMAT PREFIX "sq%d"
+#define BASELINE_SEP_FORMAT PREFIX "bs%d"
+// this needs two parameters, the uid and the column index
+#define COLUMN_WIDTH_FORMAT PREFIX "cw%d,%d"
+
+#define BAR_STRING PREFIX "sqb"
+#define TEMP_REG PREFIX "temp"
+#define MARK_REG PREFIX "mark"
+#define MARK_WIDTH_REG PREFIX "mwidth"
+#define SAVED_MARK_REG PREFIX "smark"
+#define MAX_SIZE_REG PREFIX "mxsz"
+#define REPEAT_APPEND_STRING_MACRO PREFIX "ras"
+#define TOP_HEIGHT_REG PREFIX "th"
+#define TOP_DEPTH_REG PREFIX "td"
+#define MID_HEIGHT_REG PREFIX "mh"
+#define MID_DEPTH_REG PREFIX "md"
+#define BOT_HEIGHT_REG PREFIX "bh"
+#define BOT_DEPTH_REG PREFIX "bd"
+#define EXT_HEIGHT_REG PREFIX "eh"
+#define EXT_DEPTH_REG PREFIX "ed"
+#define TOTAL_HEIGHT_REG PREFIX "tot"
+#define DELTA_REG PREFIX "delta"
+#define DELIM_STRING PREFIX "delim"
+#define DELIM_WIDTH_REG PREFIX "dwidth"
+#define SAVED_FONT_REG PREFIX "sfont"
+#define SAVED_PREV_FONT_REG PREFIX "spfont"
+#define SAVED_INLINE_FONT_REG PREFIX "sifont"
+#define SAVED_INLINE_PREV_FONT_REG PREFIX "sipfont"
+#define SAVED_SIZE_REG PREFIX "ssize"
+#define SAVED_INLINE_SIZE_REG PREFIX "sisize"
+#define SAVED_INLINE_PREV_SIZE_REG PREFIX "sipsize"
+#define SAVE_FONT_STRING PREFIX "sfont"
+#define RESTORE_FONT_STRING PREFIX "rfont"
+#define INDEX_REG PREFIX "i"
+#define TEMP_MACRO PREFIX "tempmac"
+
+#define DELIMITER_CHAR "\\(EQ"
+
+const int CRAMPED_SCRIPT_STYLE = 0;
+const int SCRIPT_STYLE = 1;
+const int CRAMPED_DISPLAY_STYLE = 2;
+const int DISPLAY_STYLE = 3;
+
+extern int script_style(int);
+extern int cramped_style(int);
+
+const int ORDINARY_TYPE = 0;
+const int OPERATOR_TYPE = 1;
+const int BINARY_TYPE = 2;
+const int RELATION_TYPE = 3;
+const int OPENING_TYPE = 4;
+const int CLOSING_TYPE = 5;
+const int PUNCTUATION_TYPE = 6;
+const int INNER_TYPE = 7;
+const int SUPPRESS_TYPE = 8;
+
+void set_script_size();
+
+enum { HINT_PREV_IS_ITALIC = 01, HINT_NEXT_IS_ITALIC = 02 };
+
+extern const char *current_roman_font;
diff --git a/src/preproc/eqn/pile.cpp b/src/preproc/eqn/pile.cpp
new file mode 100644
index 0000000..fcc2e92
--- /dev/null
+++ b/src/preproc/eqn/pile.cpp
@@ -0,0 +1,354 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// piles and matrices
+
+#include <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+// SUP_RAISE_FORMAT gives the first baseline
+// BASELINE_SEP_FORMAT gives the separation between baselines
+
+int pile_box::compute_metrics(int style)
+{
+ int i;
+ for (i = 0; i < col.len; i++)
+ col.p[i]->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0", uid);
+ for (i = 0; i < col.len; i++)
+ printf(">?\\n[" WIDTH_FORMAT "]", col.p[i]->uid);
+ printf("\n");
+ printf(".nr " BASELINE_SEP_FORMAT " %dM",
+ uid, baseline_sep+col.space);
+ for (i = 1; i < col.len; i++)
+ printf(">?(\\n[" DEPTH_FORMAT "]+\\n[" HEIGHT_FORMAT "]+%dM)",
+ col.p[i-1]->uid, col.p[i]->uid, default_rule_thickness*5);
+ // round it so that it's a multiple of the vertical motion quantum
+ printf("+(\\n(.V/2)/\\n(.V*\\n(.V\n");
+
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d/2"
+ "+%dM\n",
+ uid, uid, col.len-1, axis_height - shift_down);
+ printf(".nr " HEIGHT_FORMAT " \\n[" SUP_RAISE_FORMAT "]+\\n["
+ HEIGHT_FORMAT "]\n",
+ uid, uid, col.p[0]->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d+\\n["
+ DEPTH_FORMAT "]-\\n[" SUP_RAISE_FORMAT "]\n",
+ uid, uid, col.len-1, col.p[col.len-1]->uid, uid);
+ return FOUND_NOTHING;
+}
+
+void pile_box::output()
+{
+ if (output_format == troff) {
+ int i;
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ for (i = 0; i < col.len; i++) {
+ switch (col.align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, col.p[i]->uid);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ uid, col.p[i]->uid);
+ break;
+ default:
+ assert(0);
+ }
+ col.p[i]->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", col.p[i]->uid);
+ switch (col.align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ col.p[i]->uid, uid);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ col.p[i]->uid, uid);
+ break;
+ default:
+ assert(0);
+ }
+ if (i != col.len - 1)
+ printf("\\v'\\n[" BASELINE_SEP_FORMAT "]u'", uid);
+ }
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\v'-(%du*\\n[" BASELINE_SEP_FORMAT "]u)'", col.len - 1, uid);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ const char *av;
+ switch (col.align) {
+ case LEFT_ALIGN:
+ av = "left";
+ break;
+ case RIGHT_ALIGN:
+ av = "right";
+ break;
+ case CENTER_ALIGN:
+ av = "center";
+ break;
+ default:
+ assert(0);
+ }
+ printf("<mtable columnalign='%s'>", av);
+ for (int i = 0; i < col.len; i++) {
+ printf("<mtr><mtd>");
+ col.p[i]->output();
+ printf("</mtd></mtr>");
+ }
+ printf("</mtable>");
+ }
+}
+
+pile_box::pile_box(box *pp) : col(pp)
+{
+}
+
+void pile_box::check_tabs(int level)
+{
+ col.list_check_tabs(level);
+}
+
+void pile_box::debug_print()
+{
+ col.debug_print("pile");
+}
+
+int matrix_box::compute_metrics(int style)
+{
+ int i, j;
+ int max_len = 0;
+ int space = 0;
+ for (i = 0; i < len; i++) {
+ for (j = 0; j < p[i]->len; j++)
+ p[i]->p[j]->compute_metrics(style);
+ if (p[i]->len > max_len)
+ max_len = p[i]->len;
+ if (p[i]->space > space)
+ space = p[i]->space;
+ }
+ for (i = 0; i < len; i++) {
+ printf(".nr " COLUMN_WIDTH_FORMAT " 0", uid, i);
+ for (j = 0; j < p[i]->len; j++)
+ printf(">?\\n[" WIDTH_FORMAT "]", p[i]->p[j]->uid);
+ printf("\n");
+ }
+ printf(".nr " WIDTH_FORMAT " %dM",
+ uid, column_sep*(len-1)+2*matrix_side_sep);
+ for (i = 0; i < len; i++)
+ printf("+\\n[" COLUMN_WIDTH_FORMAT "]", uid, i);
+ printf("\n");
+ printf(".nr " BASELINE_SEP_FORMAT " %dM",
+ uid, baseline_sep+space);
+ for (i = 0; i < len; i++)
+ for (j = 1; j < p[i]->len; j++)
+ printf(">?(\\n[" DEPTH_FORMAT "]+\\n[" HEIGHT_FORMAT "]+%dM)",
+ p[i]->p[j-1]->uid, p[i]->p[j]->uid, default_rule_thickness*5);
+ // round it so that it's a multiple of the vertical motion quantum
+ printf("+(\\n(.V/2)/\\n(.V*\\n(.V\n");
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d/2"
+ "+%dM\n",
+ uid, uid, max_len-1, axis_height - shift_down);
+ printf(".nr " HEIGHT_FORMAT " 0\\n[" SUP_RAISE_FORMAT "]+(0",
+ uid, uid);
+ for (i = 0; i < len; i++)
+ printf(">?\\n[" HEIGHT_FORMAT "]", p[i]->p[0]->uid);
+ printf(")>?0\n");
+ printf(".nr " DEPTH_FORMAT " \\n[" BASELINE_SEP_FORMAT "]*%d-\\n["
+ SUP_RAISE_FORMAT "]+(0",
+ uid, uid, max_len-1, uid);
+ for (i = 0; i < len; i++)
+ if (p[i]->len == max_len)
+ printf(">?\\n[" DEPTH_FORMAT "]", p[i]->p[max_len-1]->uid);
+ printf(")>?0\n");
+ return FOUND_NOTHING;
+}
+
+void matrix_box::output()
+{
+ if (output_format == troff) {
+ printf("\\h'%dM'", matrix_side_sep);
+ for (int i = 0; i < len; i++) {
+ int j;
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ for (j = 0; j < p[i]->len; j++) {
+ switch (p[i]->align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" COLUMN_WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u/2u'",
+ uid, i, p[i]->p[j]->uid);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" COLUMN_WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ uid, i, p[i]->p[j]->uid);
+ break;
+ default:
+ assert(0);
+ }
+ p[i]->p[j]->output();
+ printf("\\h'-\\n[" WIDTH_FORMAT "]u'", p[i]->p[j]->uid);
+ switch (p[i]->align) {
+ case LEFT_ALIGN:
+ break;
+ case CENTER_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" COLUMN_WIDTH_FORMAT "]u/2u'",
+ p[i]->p[j]->uid, uid, i);
+ break;
+ case RIGHT_ALIGN:
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" COLUMN_WIDTH_FORMAT "]u'",
+ p[i]->p[j]->uid, uid, i);
+ break;
+ default:
+ assert(0);
+ }
+ if (j != p[i]->len - 1)
+ printf("\\v'\\n[" BASELINE_SEP_FORMAT "]u'", uid);
+ }
+ printf("\\v'\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\v'-(%du*\\n[" BASELINE_SEP_FORMAT "]u)'", p[i]->len - 1, uid);
+ printf("\\h'\\n[" COLUMN_WIDTH_FORMAT "]u'", uid, i);
+ if (i != len - 1)
+ printf("\\h'%dM'", column_sep);
+ }
+ printf("\\h'%dM'", matrix_side_sep);
+ }
+ else if (output_format == mathml) {
+ int n = p[0]->len; // Each column must have the same number of rows in it
+ printf("<mtable>");
+ for (int i = 0; i < n; i++) {
+ printf("<mtr>");
+ for (int j = 0; j < len; j++) {
+ const char *av;
+ switch (p[j]->align) {
+ case LEFT_ALIGN:
+ av = "left";
+ break;
+ case RIGHT_ALIGN:
+ av = "right";
+ break;
+ case CENTER_ALIGN:
+ av = "center";
+ break;
+ default:
+ assert(0);
+ }
+ printf("<mtd columnalign='%s'>", av);
+ p[j]->p[i]->output();
+ printf("</mtd>");
+ }
+ printf("</mtr>");
+ }
+ printf("</mtable>");
+ }
+}
+
+matrix_box::matrix_box(column *pp)
+{
+ p = new column*[10];
+ for (int i = 0; i < 10; i++)
+ p[i] = 0;
+ maxlen = 10;
+ len = 1;
+ p[0] = pp;
+}
+
+matrix_box::~matrix_box()
+{
+ for (int i = 0; i < len; i++)
+ delete p[i];
+ delete[] p;
+}
+
+void matrix_box::append(column *pp)
+{
+ if (len + 1 > maxlen) {
+ column **oldp = p;
+ maxlen *= 2;
+ p = new column*[maxlen];
+ memcpy(p, oldp, sizeof(column*)*len);
+ delete[] oldp;
+ }
+ p[len++] = pp;
+}
+
+void matrix_box::check_tabs(int level)
+{
+ for (int i = 0; i < len; i++)
+ p[i]->list_check_tabs(level);
+}
+
+void matrix_box::debug_print()
+{
+ fprintf(stderr, "matrix { ");
+ p[0]->debug_print("col");
+ for (int i = 1; i < len; i++) {
+ fprintf(stderr, " ");
+ p[i]->debug_print("col");
+ }
+ fprintf(stderr, " }");
+}
+
+column::column(box *pp) : box_list(pp), align(CENTER_ALIGN), space(0)
+{
+}
+
+void column::set_alignment(alignment a)
+{
+ align = a;
+}
+
+void column::set_space(int n)
+{
+ space = n;
+}
+
+void column::debug_print(const char *s)
+{
+ char c = '\0'; // shut up -Wall
+ switch (align) {
+ case LEFT_ALIGN:
+ c = 'l';
+ break;
+ case RIGHT_ALIGN:
+ c = 'r';
+ break;
+ case CENTER_ALIGN:
+ c = 'c';
+ break;
+ default:
+ assert(0);
+ }
+ fprintf(stderr, "%c%s %d { ", c, s, space);
+ list_debug_print(" above ");
+ fprintf(stderr, " }");
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/script.cpp b/src/preproc/eqn/script.cpp
new file mode 100644
index 0000000..f485eb9
--- /dev/null
+++ b/src/preproc/eqn/script.cpp
@@ -0,0 +1,250 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+#include "eqn.h"
+#include "pbox.h"
+
+class script_box : public pointer_box {
+private:
+ box *sub;
+ box *sup;
+public:
+ script_box(box *, box *, box *);
+ ~script_box();
+ int compute_metrics(int);
+ void output();
+ void debug_print();
+ int left_is_italic();
+ void hint(unsigned);
+ void check_tabs(int);
+};
+
+/* The idea is that the script should attach to the rightmost box
+of a list. For example, given '2x sup 3', the superscript should
+attach to 'x' rather than '2x'. */
+
+box *make_script_box(box *nuc, box *sub, box *sup)
+{
+ list_box *b = nuc->to_list_box();
+ if (b != 0) {
+ b->list.p[b->list.len-1] = make_script_box(b->list.p[b->list.len - 1],
+ sub,
+ sup);
+ return b;
+ }
+ else
+ return new script_box(nuc, sub, sup);
+}
+
+script_box::script_box(box *pp, box *qq, box *rr)
+: pointer_box(pp), sub(qq), sup(rr)
+{
+}
+
+script_box::~script_box()
+{
+ delete sub;
+ delete sup;
+}
+
+int script_box::left_is_italic()
+{
+ return p->left_is_italic();
+}
+
+int script_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ p->compute_subscript_kern();
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ if (!(style <= SCRIPT_STYLE && one_size_reduction_flag))
+ set_script_size();
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ if (sub != 0)
+ sub->compute_metrics(cramped_style(script_style(style)));
+ if (sup != 0)
+ sup->compute_metrics(script_style(style));
+ // 18a
+ if (p->is_char()) {
+ printf(".nr " SUP_RAISE_FORMAT " 0\n", uid);
+ printf(".nr " SUB_LOWER_FORMAT " 0\n", uid);
+ }
+ else {
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" HEIGHT_FORMAT "]-%dM>?0\n",
+ uid, p->uid, sup_drop);
+ printf(".nr " SUB_LOWER_FORMAT " \\n[" DEPTH_FORMAT "]+%dM\n",
+ uid, p->uid, sub_drop);
+ }
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ if (sup == 0) {
+ assert(sub != 0);
+ // 18b
+ printf(".nr " SUB_LOWER_FORMAT " \\n[" SUB_LOWER_FORMAT "]>?%dM>?(\\n["
+ HEIGHT_FORMAT "]-(%dM*4/5))\n",
+ uid, uid, sub1, sub->uid, x_height);
+ }
+ else {
+ // sup != 0
+ // 18c
+ int pos;
+ if (style == DISPLAY_STYLE)
+ pos = sup1;
+ else if (style & 1) // not cramped
+ pos = sup2;
+ else
+ pos = sup3;
+ printf(".nr " SUP_RAISE_FORMAT " \\n[" SUP_RAISE_FORMAT
+ "]>?%dM>?(\\n[" DEPTH_FORMAT "]+(%dM/4))\n",
+ uid, uid, pos, sup->uid, x_height);
+ // 18d
+ if (sub != 0) {
+ printf(".nr " SUB_LOWER_FORMAT " \\n[" SUB_LOWER_FORMAT "]>?%dM\n",
+ uid, uid, sub2);
+ // 18e
+ printf(".nr " TEMP_REG " \\n[" DEPTH_FORMAT "]-\\n["
+ SUP_RAISE_FORMAT "]+\\n[" HEIGHT_FORMAT "]-\\n["
+ SUB_LOWER_FORMAT "]+(4*%dM)\n",
+ sup->uid, uid, sub->uid, uid, default_rule_thickness);
+ printf(".if \\n[" TEMP_REG "] \\{");
+ printf(".nr " SUB_LOWER_FORMAT " +\\n[" TEMP_REG "]\n", uid);
+ printf(".nr " TEMP_REG " (%dM*4/5)-\\n[" SUP_RAISE_FORMAT
+ "]+\\n[" DEPTH_FORMAT "]>?0\n",
+ x_height, uid, sup->uid);
+ printf(".nr " SUP_RAISE_FORMAT " +\\n[" TEMP_REG "]\n", uid);
+ printf(".nr " SUB_LOWER_FORMAT " -\\n[" TEMP_REG "]\n", uid);
+ printf(".\\}\n");
+ }
+ }
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]", uid, p->uid);
+ if (sub != 0 && sup != 0)
+ printf("+((\\n[" WIDTH_FORMAT "]-\\n[" SUB_KERN_FORMAT "]>?\\n["
+ WIDTH_FORMAT "])+%dM)>?0\n",
+ sub->uid, p->uid, sup->uid, script_space);
+ else if (sub != 0)
+ printf("+(\\n[" WIDTH_FORMAT "]-\\n[" SUB_KERN_FORMAT "]+%dM)>?0\n",
+ sub->uid, p->uid, script_space);
+ else if (sup != 0)
+ printf("+(\\n[" WIDTH_FORMAT "]+%dM)>?0\n", sup->uid, script_space);
+ else
+ printf("\n");
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]",
+ uid, p->uid);
+ if (sup != 0)
+ printf(">?(\\n[" SUP_RAISE_FORMAT "]+\\n[" HEIGHT_FORMAT "])",
+ uid, sup->uid);
+ if (sub != 0)
+ printf(">?(-\\n[" SUB_LOWER_FORMAT "]+\\n[" HEIGHT_FORMAT "])",
+ uid, sub->uid);
+ printf("\n");
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]",
+ uid, p->uid);
+ if (sub != 0)
+ printf(">?(\\n[" SUB_LOWER_FORMAT "]+\\n[" DEPTH_FORMAT "])",
+ uid, sub->uid);
+ if (sup != 0)
+ printf(">?(-\\n[" SUP_RAISE_FORMAT "]+\\n[" DEPTH_FORMAT "])",
+ uid, sup->uid);
+ printf("\n");
+ return res;
+}
+
+void script_box::output()
+{
+ if (output_format == troff) {
+ p->output();
+ if (sup != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ sup->output();
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf(DELIMITER_CHAR);
+ }
+ if (sub != 0) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\v'\\n[" SUB_LOWER_FORMAT "]u'", uid);
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ printf("\\h'-\\n[" SUB_KERN_FORMAT "]u'", p->uid);
+ sub->output();
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf(DELIMITER_CHAR);
+ }
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u'",
+ uid, p->uid);
+ }
+ else if (output_format == mathml) {
+ if (sup != 0 && sub != 0) {
+ printf("<msubsup>");
+ p->output();
+ sub->output();
+ sup->output();
+ printf("</msubsup>");
+ }
+ else if (sup != 0) {
+ printf("<msup>");
+ p->output();
+ sup->output();
+ printf("</msup>");
+ }
+ else if (sub != 0) {
+ printf("<msub>");
+ p->output();
+ sub->output();
+ printf("</msub>");
+ }
+ }
+}
+
+void script_box::hint(unsigned flags)
+{
+ p->hint(flags & ~HINT_NEXT_IS_ITALIC);
+}
+
+void script_box::debug_print()
+{
+ fprintf(stderr, "{ ");
+ p->debug_print();
+ fprintf(stderr, " }");
+ if (sub) {
+ fprintf(stderr, " sub { ");
+ sub->debug_print();
+ fprintf(stderr, " }");
+ }
+ if (sup) {
+ fprintf(stderr, " sup { ");
+ sup->debug_print();
+ fprintf(stderr, " }");
+ }
+}
+
+void script_box::check_tabs(int level)
+{
+ if (sup)
+ sup->check_tabs(level + 1);
+ if (sub)
+ sub->check_tabs(level + 1);
+ p->check_tabs(level);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/eqn/special.cpp b/src/preproc/eqn/special.cpp
new file mode 100644
index 0000000..8ad8238
--- /dev/null
+++ b/src/preproc/eqn/special.cpp
@@ -0,0 +1,117 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "pbox.h"
+
+#define STRING_FORMAT PREFIX "str%d"
+
+#define SPECIAL_STRING "0s"
+#define SPECIAL_WIDTH_REG "0w"
+#define SPECIAL_HEIGHT_REG "0h"
+#define SPECIAL_DEPTH_REG "0d"
+#define SPECIAL_SUB_KERN_REG "0skern"
+#define SPECIAL_SKEW_REG "0skew"
+
+/*
+For example:
+
+.de Cl
+.ds 0s \Z'\\*[0s]'\v'\\n(0du'\D'l \\n(0wu -\\n(0hu-\\n(0du'\v'\\n(0hu'
+..
+.EQ
+define cancel 'special Cl'
+.EN
+*/
+
+
+class special_box : public pointer_box {
+ char *macro_name;
+public:
+ special_box(char *, box *);
+ ~special_box();
+ int compute_metrics(int);
+ void compute_subscript_kern();
+ void compute_skew();
+ void output();
+ void debug_print();
+};
+
+box *make_special_box(char *s, box *p)
+{
+ return new special_box(s, p);
+}
+
+special_box::special_box(char *s, box *pp) : pointer_box(pp), macro_name(s)
+{
+}
+
+special_box::~special_box()
+{
+ delete[] macro_name;
+}
+
+int special_box::compute_metrics(int style)
+{
+ int r = p->compute_metrics(style);
+ p->compute_subscript_kern();
+ p->compute_skew();
+ printf(".ds " SPECIAL_STRING " \"");
+ p->output();
+ printf("\n");
+ printf(".nr " SPECIAL_WIDTH_REG " 0\\n[" WIDTH_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_HEIGHT_REG " \\n[" HEIGHT_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_DEPTH_REG " \\n[" DEPTH_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_SUB_KERN_REG " \\n[" SUB_KERN_FORMAT "]\n", p->uid);
+ printf(".nr " SPECIAL_SKEW_REG " 0\\n[" SKEW_FORMAT "]\n", p->uid);
+ printf(".%s\n", macro_name);
+ printf(".rn " SPECIAL_STRING " " STRING_FORMAT "\n", uid);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" SPECIAL_WIDTH_REG "]\n", uid);
+ printf(".nr " HEIGHT_FORMAT " 0>?\\n[" SPECIAL_HEIGHT_REG "]\n", uid);
+ printf(".nr " DEPTH_FORMAT " 0>?\\n[" SPECIAL_DEPTH_REG "]\n", uid);
+ printf(".nr " SUB_KERN_FORMAT " 0>?\\n[" SPECIAL_SUB_KERN_REG "]\n", uid);
+ printf(".nr " SKEW_FORMAT " 0\\n[" SPECIAL_SKEW_REG "]\n", uid);
+ // User will have to change MARK_REG if appropriate.
+ return r;
+}
+
+void special_box::compute_subscript_kern()
+{
+ // Already computed in compute_metrics(), so do nothing.
+}
+
+void special_box::compute_skew()
+{
+ // Already computed in compute_metrics(), so do nothing.
+}
+
+void special_box::output()
+{
+ if (output_format == troff)
+ printf("\\*[" STRING_FORMAT "]", uid);
+ else if (output_format == mathml)
+ printf("<merror>eqn specials cannot be expressed in MathML</merror>");
+}
+
+void special_box::debug_print()
+{
+ fprintf(stderr, "special %s { ", macro_name);
+ p->debug_print();
+ fprintf(stderr, " }");
+}
diff --git a/src/preproc/eqn/sqrt.cpp b/src/preproc/eqn/sqrt.cpp
new file mode 100644
index 0000000..e8d7c77
--- /dev/null
+++ b/src/preproc/eqn/sqrt.cpp
@@ -0,0 +1,186 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "eqn.h"
+#include "pbox.h"
+
+
+class sqrt_box : public pointer_box {
+public:
+ sqrt_box(box *);
+ int compute_metrics(int style);
+ void output();
+ void debug_print();
+ void check_tabs(int);
+};
+
+box *make_sqrt_box(box *pp)
+{
+ return new sqrt_box(pp);
+}
+
+sqrt_box::sqrt_box(box *pp) : pointer_box(pp)
+{
+}
+
+#define SQRT_CHAR "\\[sqrt]"
+#define RADICAL_EXTENSION_CHAR "\\[sqrtex]"
+
+#define SQRT_CHAIN "\\[sqrt\\\\n[" INDEX_REG "]]"
+#define BAR_CHAIN "\\[sqrtex\\\\n[" INDEX_REG "]]"
+
+int sqrt_box::compute_metrics(int style)
+{
+ // 11
+ int r = p->compute_metrics(cramped_style(style));
+ printf(".nr " TEMP_REG " \\n[" HEIGHT_FORMAT "]+\\n[" DEPTH_FORMAT
+ "]+%dM+(%dM/4)\n",
+ p->uid, p->uid, default_rule_thickness,
+ (style > SCRIPT_STYLE ? x_height : default_rule_thickness));
+ printf(".nr " SIZE_FORMAT " \\n[.ps]\n", uid);
+ printf(".ds " SQRT_STRING_FORMAT " " SQRT_CHAR "\n", uid);
+ printf(".ds " BAR_STRING " " RADICAL_EXTENSION_CHAR "\n");
+ printf(".nr " SQRT_WIDTH_FORMAT
+ " 0\\w" DELIMITER_CHAR SQRT_CHAR DELIMITER_CHAR "\n",
+ uid);
+ printf(".if \\n[rst]-\\n[rsb]-%dM<\\n[" TEMP_REG "] \\{",
+ default_rule_thickness);
+
+ printf(".nr " INDEX_REG " 0\n"
+ ".de " TEMP_MACRO "\n"
+ ".ie c" SQRT_CHAIN " \\{"
+ ".ds " SQRT_STRING_FORMAT " " SQRT_CHAIN "\n"
+ ".ie c" BAR_CHAIN " .ds " BAR_STRING " " BAR_CHAIN "\n"
+ ".el .ds " BAR_STRING " " RADICAL_EXTENSION_CHAR "\n"
+ ".nr " SQRT_WIDTH_FORMAT
+ " 0\\w" DELIMITER_CHAR SQRT_CHAIN DELIMITER_CHAR "\n"
+ ".if \\\\n[rst]-\\\\n[rsb]-%dM<\\n[" TEMP_REG "] \\{"
+ ".nr " INDEX_REG " +1\n"
+ "." TEMP_MACRO "\n"
+ ".\\}\\}\n"
+ ".el .nr " INDEX_REG " 0-1\n"
+ "..\n"
+ "." TEMP_MACRO "\n",
+ uid, uid, default_rule_thickness);
+
+ printf(".if \\n[" INDEX_REG "]<0 \\{");
+
+ // Determine the maximum point size
+ printf(".ps 1000\n");
+ printf(".nr " MAX_SIZE_REG " \\n[.ps]\n");
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ // We define a macro that will increase the current point size
+ // until we get a radical sign that's tall enough or we reach
+ // the maximum point size.
+ printf(".de " TEMP_MACRO "\n"
+ ".nr " SQRT_WIDTH_FORMAT
+ " 0\\w" DELIMITER_CHAR "\\*[" SQRT_STRING_FORMAT "]" DELIMITER_CHAR "\n"
+ ".if \\\\n[rst]"
+ "&(\\\\n[rst]-\\\\n[rsb]-%dM<\\n[" TEMP_REG "])"
+ "&(\\\\n[.ps]<\\n[" MAX_SIZE_REG "]) \\{"
+ ".ps +1\n"
+ "." TEMP_MACRO "\n"
+ ".\\}\n"
+ "..\n"
+ "." TEMP_MACRO "\n",
+ uid, uid, default_rule_thickness);
+
+ printf(".\\}\\}\n");
+
+ printf(".nr " SMALL_SIZE_FORMAT " \\n[.ps]\n", uid);
+ // set TEMP_REG to the amount by which the radical sign is too big
+ printf(".nr " TEMP_REG " \\n[rst]-\\n[rsb]-%dM-\\n[" TEMP_REG "]\n",
+ default_rule_thickness);
+ // If TEMP_REG is negative, the bottom of the radical sign should
+ // be -TEMP_REG above the bottom of p. If it's positive, the bottom
+ // of the radical sign should be TEMP_REG/2 below the bottom of p.
+ // This calculates the amount by which the baseline of the radical
+ // should be raised.
+ printf(".nr " SUP_RAISE_FORMAT " (-\\n[" TEMP_REG "]>?(-\\n[" TEMP_REG "]/2))"
+ "-\\n[rsb]-\\n[" DEPTH_FORMAT "]\n", uid, p->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?(\\n[" SUP_RAISE_FORMAT "]+\\n[rst])\n",
+ uid, p->uid, uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ ">?(-\\n[" SUP_RAISE_FORMAT "]-\\n[rsb])\n",
+ uid, p->uid, uid);
+ // Do this last, so we don't lose height and depth information on
+ // the radical sign.
+ // Remember that the width of the bar might be greater than the width of p.
+
+ printf(".nr " TEMP_REG " "
+ "\\n[" WIDTH_FORMAT "]"
+ ">?\\w" DELIMITER_CHAR "\\*[" BAR_STRING "]" DELIMITER_CHAR "\n",
+ p->uid);
+ printf(".as " SQRT_STRING_FORMAT " "
+ "\\l'\\n[" TEMP_REG "]u\\&\\*[" BAR_STRING "]'\n",
+ uid);
+ printf(".nr " WIDTH_FORMAT " \\n[" TEMP_REG "]"
+ "+\\n[" SQRT_WIDTH_FORMAT "]\n",
+ uid, uid);
+
+ if (r)
+ printf(".nr " MARK_REG " +\\n[" SQRT_WIDTH_FORMAT "]\n", uid);
+ // the top of the bar might be higher than the top of the radical sign
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?(\\n[" SUP_RAISE_FORMAT "]+\\n[rst])\n",
+ uid, p->uid, uid);
+ // put a bit of extra space above the bar
+ printf(".nr " HEIGHT_FORMAT " +%dM\n", uid, default_rule_thickness);
+ printf(".ps \\n[" SIZE_FORMAT "]u\n", uid);
+ return r;
+}
+
+void sqrt_box::output()
+{
+ if (output_format == troff) {
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\s[\\n[" SMALL_SIZE_FORMAT "]u]", uid);
+ printf("\\v'-\\n[" SUP_RAISE_FORMAT "]u'", uid);
+ printf("\\*[" SQRT_STRING_FORMAT "]", uid);
+ printf("\\s[\\n[" SIZE_FORMAT "]u]", uid);
+ printf(DELIMITER_CHAR);
+
+ printf("\\Z" DELIMITER_CHAR);
+ printf("\\h'\\n[" WIDTH_FORMAT "]u-\\n[" WIDTH_FORMAT "]u"
+ "+\\n[" SQRT_WIDTH_FORMAT "]u/2u'",
+ uid, p->uid, uid);
+ p->output();
+ printf(DELIMITER_CHAR);
+
+ printf("\\h'\\n[" WIDTH_FORMAT "]u'", uid);
+ }
+ else if (output_format == mathml) {
+ printf("<msqrt>");
+ p->output();
+ printf("</msqrt>");
+ }
+}
+
+void sqrt_box::debug_print()
+{
+ fprintf(stderr, "sqrt { ");
+ p->debug_print();
+ fprintf(stderr, " }");
+}
+
+void sqrt_box::check_tabs(int level)
+{
+ p->check_tabs(level + 1);
+}
diff --git a/src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh b/src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh
new file mode 100755
index 0000000..feb78c1
--- /dev/null
+++ b/src/preproc/eqn/tests/diagnostics-report-correct-line-numbers.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+eqn="${abs_top_builddir:-.}/eqn"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$error"
+ fail=yes
+}
+
+# Verify that correct input file line numbers are reported.
+
+echo "checking for absent line number in early EOF diagnostic" >&2
+input='.EQ'
+error=$(printf "%s\n" "$input" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+: fatal' || wail
+
+echo "checking for correct line number in nested 'EQ' diagnostic" >&2
+input='.EQ
+.EQ'
+error=$(printf "%s\n" "$input" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+:2: fatal' || wail
+
+echo "checking for correct line number in invalid input character" \
+ "diagnostic" >&2
+error=$(printf ".EQ\nx\n.EN\n\200\n" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+:4: error' || wail
+
+echo "checking for correct line number in invalid input character" \
+ "diagnostic when 'lf' request used beforehand" >&2
+error=$(printf ".EQ\nx\n.EN\n.lf 99\n\200\n" | "$eqn" 2>&1 > /dev/null)
+echo "$error" | grep -Eq '^[^:]+:[^:]+:99: error' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/eqn/text.cpp b/src/preproc/eqn/text.cpp
new file mode 100644
index 0000000..272f3fe
--- /dev/null
+++ b/src/preproc/eqn/text.cpp
@@ -0,0 +1,957 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdlib.h>
+#include "eqn.h"
+#include "pbox.h"
+#include "ptable.h"
+
+struct map {
+ const char *from;
+ const char *to;
+};
+
+struct map entity_table[] = {
+ // Classic troff special characters
+ {"%", "&shy;"}, // ISOnum
+ {"'", "&acute;"}, // ISOdia
+ {"!=", "&ne;"}, // ISOtech
+ {"**", "&lowast;"}, // ISOtech
+ {"*a", "&alpha;"}, // ISOgrk3
+ {"*A", "A"},
+ {"*b", "&beta;"}, // ISOgrk3
+ {"*B", "B"},
+ {"*d", "&delta;"}, // ISOgrk3
+ {"*D", "&Delta;"}, // ISOgrk3
+ {"*e", "&epsilon;"}, // ISOgrk3
+ {"*E", "E"},
+ {"*f", "&phi;"}, // ISOgrk3
+ {"*F", "&Phi;"}, // ISOgrk3
+ {"*g", "&gamma;"}, // ISOgrk3
+ {"*G", "&Gamma;"}, // ISOgrk3
+ {"*h", "&theta;"}, // ISOgrk3
+ {"*H", "&Theta;"}, // ISOgrk3
+ {"*i", "&iota;"}, // ISOgrk3
+ {"*I", "I"},
+ {"*k", "&kappa;"}, // ISOgrk3
+ {"*K", "K;"},
+ {"*l", "&lambda;"}, // ISOgrk3
+ {"*L", "&Lambda;"}, // ISOgrk3
+ {"*m", "&mu;"}, // ISOgrk3
+ {"*M", "M"},
+ {"*n", "&nu;"}, // ISOgrk3
+ {"*N", "N"},
+ {"*o", "o"},
+ {"*O", "O"},
+ {"*p", "&pi;"}, // ISOgrk3
+ {"*P", "&Pi;"}, // ISOgrk3
+ {"*q", "&psi;"}, // ISOgrk3
+ {"*Q", "&PSI;"}, // ISOgrk3
+ {"*r", "&rho;"}, // ISOgrk3
+ {"*R", "R"},
+ {"*s", "&sigma;"}, // ISOgrk3
+ {"*S", "&Sigma;"}, // ISOgrk3
+ {"*t", "&tau;"}, // ISOgrk3
+ {"*T", "&Tau;"}, // ISOgrk3
+ {"*u", "&upsilon;"}, // ISOgrk3
+ {"*U", "&Upsilon;"}, // ISOgrk3
+ {"*w", "&omega;"}, // ISOgrk3
+ {"*W", "&Omega;"}, // ISOgrk3
+ {"*x", "&chi;"}, // ISOgrk3
+ {"*X", "&Chi;"}, // ISOgrk3
+ {"*y", "&eta;"}, // ISOgrk3
+ {"*Y", "&Eta;"}, // ISOgrk3
+ {"*z", "&zeta;"}, // ISOgrk3
+ {"*Z", "&Zeta;"}, // ISOgrk3
+ {"+-", "&plusmn;"}, // ISOnum
+ {"->", "&rarr;"}, // ISOnum
+ {"12", "&frac12;"}, // ISOnum
+ {"14", "&frac14;"}, // ISOnum
+ {"34", "&frac34;"}, // ISOnum
+ {"<-", "&larr;"}, // ISOnum
+ {"==", "&equiv;"}, // ISOtech
+ {"Fi", "&ffilig;"}, // ISOpub
+ {"Fl", "&ffllig;"}, // ISOpub
+ {"aa", "&acute;"}, // ISOdia
+ {"ap", "&sim;"}, // ISOtech
+ {"bl", "&phonexb;"}, // ISOpub
+ {"br", "&boxv;"}, // ISObox
+ {"bs", "&phone;"}, // ISOpub (for the Bell logo)
+ {"bu", "&bull;"}, // ISOpub
+ {"bv", "&verbar;"}, // ISOnum
+ {"ca", "&cap;"}, // ISOtech
+ {"ci", "&cir;"}, // ISOpub
+ {"co", "&copy;"}, // ISOnum
+ {"ct", "&cent;"}, // ISOnum
+ {"cu", "&cup;"}, // ISOtech
+ {"da", "&darr;"}, // ISOnum
+ {"de", "&deg;"}, // ISOnum
+ {"dg", "&dagger;"}, // ISOpub
+ {"dd", "&Dagger;"}, // ISOpub
+ {"di", "&divide;"}, // ISOnum
+ {"em", "&mdash;"}, // ISOpub
+ {"eq", "&equals;"}, // ISOnum
+ {"es", "&empty;"}, // ISOamso
+ {"ff", "&fflig;"}, // ISOpub
+ {"fi", "&filig;"}, // ISOpub
+ {"fl", "&fllig;"}, // ISOpub
+ {"fm", "&prime;"}, // ISOtech
+ {"ge", "&ge;"}, // ISOtech
+ {"gr", "&nabla;"}, // ISOtech
+ {"hy", "&hyphen;"}, // ISOnum
+ {"ib", "&sube;"}, // ISOtech
+ {"if", "&infin;"}, // ISOtech
+ {"ip", "&supe;"}, // ISOtech
+ {"is", "&int;"}, // ISOtech
+ {"le", "&le;"}, // ISOtech
+ // Some pile characters go here
+ {"mi", "&minus;"}, // ISOtech
+ {"mo", "&isin;"}, // ISOtech
+ {"mu", "&times;"}, // ISOnum
+ {"no", "&not;"}, // ISOnum
+ {"or", "&verbar;"}, // ISOnum
+ {"pl", "&plus;"}, // ISOnum
+ {"pt", "&prop;"}, // ISOtech
+ {"rg", "&trade;"}, // ISOnum
+ // More pile characters go here
+ {"rn", "&macr;"}, // ISOdia
+ {"ru", "&lowbar;"}, // ISOnum
+ {"sb", "&sub;"}, // ISOtech
+ {"sc", "&sect;"}, // ISOnum
+ {"sl", "/"},
+ {"sp", "&sup;"}, // ISOtech
+ {"sq", "&squf;"}, // ISOpub
+ {"sr", "&radic;"}, // ISOtech
+ {"ts", "&sigmav;"}, // ISOgrk3
+ {"ua", "&uarr;"}, // ISOnum
+ {"ul", "_"},
+ {"~=", "&cong;"}, // ISOtech
+ // Extended specials supported by groff; see groff_char(7).
+ // These are listed in the order they occur on that man page.
+ {"-D", "&ETH;"}, // ISOlat: Icelandic uppercase eth
+ {"Sd", "&eth;"}, // ISOlat1: Icelandic lowercase eth
+ {"TP", "&THORN;"}, // ISOlat1: Icelandic uppercase thorn
+ {"Tp", "&thorn;"}, // ISOlat1: Icelandic lowercase thorn
+ {"ss", "&szlig;"}, // ISOlat1
+ // Ligatures
+ // ff, fi, fl, ffi, ffl from old troff go here
+ {"AE", "&AElig;"}, // ISOlat1
+ {"ae", "&aelig;"}, // ISOlat1
+ {"OE", "&OElig;"}, // ISOlat2
+ {"oe", "&oelig;"}, // ISOlat2
+ {"IJ", "&ijlig;"}, // ISOlat2: Dutch IJ ligature
+ {"ij", "&IJlig;"}, // ISOlat2: Dutch ij ligature
+ {".i", "&inodot;"}, // ISOlat2,ISOamso
+ {".j", "&jnodot;"}, // ISOamso (undocumented but in 1.19)
+ // Accented characters
+ {"'A", "&Aacute;"}, // ISOlat1
+ {"'C", "&Cacute;"}, // ISOlat2
+ {"'E", "&Eacute;"}, // ISOlat1
+ {"'I", "&Iacute;"}, // ISOlat1
+ {"'O", "&Oacute;"}, // ISOlat1
+ {"'U", "&Uacute;"}, // ISOlat1
+ {"'Y", "&Yacute;"}, // ISOlat1
+ {"'a", "&aacute;"}, // ISOlat1
+ {"'c", "&cacute;"}, // ISOlat2
+ {"'e", "&eacute;"}, // ISOlat1
+ {"'i", "&iacute;"}, // ISOlat1
+ {"'o", "&oacute;"}, // ISOlat1
+ {"'u", "&uacute;"}, // ISOlat1
+ {"'y", "&yacute;"}, // ISOlat1
+ {":A", "&Auml;"}, // ISOlat1
+ {":E", "&Euml;"}, // ISOlat1
+ {":I", "&Iuml;"}, // ISOlat1
+ {":O", "&Ouml;"}, // ISOlat1
+ {":U", "&Uuml;"}, // ISOlat1
+ {":Y", "&Yuml;"}, // ISOlat2
+ {":a", "&auml;"}, // ISOlat1
+ {":e", "&euml;"}, // ISOlat1
+ {":i", "&iuml;"}, // ISOlat1
+ {":o", "&ouml;"}, // ISOlat1
+ {":u", "&uuml;"}, // ISOlat1
+ {":y", "&yuml;"}, // ISOlat1
+ {"^A", "&Acirc;"}, // ISOlat1
+ {"^E", "&Ecirc;"}, // ISOlat1
+ {"^I", "&Icirc;"}, // ISOlat1
+ {"^O", "&Ocirc;"}, // ISOlat1
+ {"^U", "&Ucirc;"}, // ISOlat1
+ {"^a", "&acirc;"}, // ISOlat1
+ {"^e", "&ecirc;"}, // ISOlat1
+ {"^i", "&icirc;"}, // ISOlat1
+ {"^o", "&ocirc;"}, // ISOlat1
+ {"^u", "&ucirc;"}, // ISOlat1
+ {"`A", "&Agrave;"}, // ISOlat1
+ {"`E", "&Egrave;"}, // ISOlat1
+ {"`I", "&Igrave;"}, // ISOlat1
+ {"`O", "&Ograve;"}, // ISOlat1
+ {"`U", "&Ugrave;"}, // ISOlat1
+ {"`a", "&agrave;"}, // ISOlat1
+ {"`e", "&egrave;"}, // ISOlat1
+ {"`i", "&igrave;"}, // ISOlat1
+ {"`o", "&ograve;"}, // ISOlat1
+ {"`u", "&ugrave;"}, // ISOlat1
+ {"~A", "&Atilde;"}, // ISOlat1
+ {"~N", "&Ntilde;"}, // ISOlat1
+ {"~O", "&Otilde;"}, // ISOlat1
+ {"~a", "&atilde;"}, // ISOlat1
+ {"~n", "&ntilde;"}, // ISOlat1
+ {"~o", "&otilde;"}, // ISOlat1
+ {"vS", "&Scaron;"}, // ISOlat2
+ {"vs", "&scaron;"}, // ISOlat2
+ {"vZ", "&Zcaron;"}, // ISOlat2
+ {"vz", "&zcaron;"}, // ISOlat2
+ {",C", "&Ccedil;"}, // ISOlat1
+ {",c", "&ccedil;"}, // ISOlat1
+ {"/L", "&Lstrok;"}, // ISOlat2: Polish L with a slash
+ {"/l", "&lstrok;"}, // ISOlat2: Polish l with a slash
+ {"/O", "&Oslash;"}, // ISOlat1
+ {"/o", "&oslash;"}, // ISOlat1
+ {"oA", "&Aring;"}, // ISOlat1
+ {"oa", "&aring;"}, // ISOlat1
+ // Accents
+ {"a\"","&dblac;"}, // ISOdia: double acute accent (Hungarian umlaut)
+ {"a-", "&macr;"}, // ISOdia: macron or bar accent
+ {"a.", "&dot;"}, // ISOdia: dot above
+ {"a^", "&circ;"}, // ISOdia: circumflex accent
+ {"aa", "&acute;"}, // ISOdia: acute accent
+ {"ga", "&grave;"}, // ISOdia: grave accent
+ {"ab", "&breve;"}, // ISOdia: breve accent
+ {"ac", "&cedil;"}, // ISOdia: cedilla accent
+ {"ad", "&uml;"}, // ISOdia: umlaut or dieresis
+ {"ah", "&caron;"}, // ISOdia: caron (aka hacek accent)
+ {"ao", "&ring;"}, // ISOdia: ring or circle accent
+ {"a~", "&tilde;"}, // ISOdia: tilde accent
+ {"ho", "&ogon;"}, // ISOdia: hook or ogonek accent
+ {"ha", "^"}, // ASCII circumflex, hat, caret
+ {"ti", "~"}, // ASCII tilde, large tilde
+ // Quotes
+ {"Bq", "&lsquor;"}, // ISOpub: low double comma quote
+ {"bq", "&ldquor;"}, // ISOpub: low single comma quote
+ {"lq", "&ldquo;"}, // ISOnum
+ {"rq", "&rdquo;"}, // ISOpub
+ {"oq", "&lsquo;"}, // ISOnum: single open quote
+ {"cq", "&rsquo;"}, // ISOnum: single closing quote (ASCII 39)
+ {"aq", "&zerosp;'"}, // apostrophe quote
+ {"dq", "\""}, // double quote (ASCII 34)
+ {"Fo", "&laquo;"}, // ISOnum
+ {"Fc", "&raquo;"}, // ISOnum
+ //{"fo", "&fo;"},
+ //{"fc", "&fc;"},
+ // Punctuation
+ {"r!", "&iexcl;"}, // ISOnum
+ {"r?", "&iquest;"}, // ISOnum
+ // Old troff \(em goes here
+ {"en", "&ndash;"}, // ISOpub: en dash
+ // Old troff \(hy goes here
+ // Brackets
+ {"lB", "&lsqb;"}, // ISOnum: left (square) bracket
+ {"rB", "&rsqb;"}, // ISOnum: right (square) bracket
+ {"lC", "&lcub;"}, // ISOnum: left (curly) brace
+ {"rC", "&rcub;"}, // ISOnum: right (curly) brace
+ {"la", "&lang;"}, // ISOtech: left angle bracket
+ {"ra", "&rang;"}, // ISOtech: right angle bracket
+ // Old troff \(bv goes here
+ // Bracket-pile characters could go here.
+ // Arrows
+ // Old troff \(<- and \(-> go here
+ {"<>", "&harr;"}, // ISOamsa
+ {"da", "&darr;"}, // ISOnum
+ {"ua", "&uarr;"}, // ISOnum
+ {"lA", "&lArr;"}, // ISOtech
+ {"rA", "&rArr;"}, // ISOtech
+ {"hA", "&iff;"}, // ISOtech: horizontal double-headed arrow
+ {"dA", "&dArr;"}, // ISOamsa
+ {"uA", "&uArr;"}, // ISOamsa
+ {"vA", "&vArr;"}, // ISOamsa: vertical double-headed double arrow
+ //{"an", "&an;"},
+ // Lines
+ {"-h", "&planck;"}, // ISOamso: h-bar (Planck's constant)
+ // Old troff \(or goes here
+ {"ba", "&verbar;"}, // ISOnum
+ // Old troff \(br, \{u, \(ul, \(bv go here
+ {"bb", "&brvbar;"}, // ISOnum
+ {"sl", "/"},
+ {"rs", "&bsol;"}, // ISOnum
+ // Text markers
+ // Old troff \(ci, \(bu, \(dd, \(dg go here
+ {"lz", "&loz;"}, // ISOpub
+ // Old troff sq goes here
+ {"ps", "&para;"}, // ISOnum: paragraph or pilcrow sign
+ {"sc", "&sect;"}, // ISOnum (in old troff)
+ // Old troff \(lh, \{h go here
+ {"at", "&commat;"}, // ISOnum
+ {"sh", "&num;"}, // ISOnum
+ //{"CR", "&CR;"},
+ {"OK", "&check;"}, // ISOpub
+ // Legalize
+ // Old troff \(co, \{g go here
+ {"tm", "&trade;"}, // ISOnum
+ // Currency symbols
+ {"Do", "&dollar;"}, // ISOnum
+ {"ct", "&cent;"}, // ISOnum
+ {"eu", "&euro;"},
+ {"Eu", "&euro;"},
+ {"Ye", "&yen;"}, // ISOnum
+ {"Po", "&pound;"}, // ISOnum
+ {"Cs", "&curren;"}, // ISOnum: currency sign
+ {"Fn", "&fnof"}, // ISOtech
+ // Units
+ // Old troff de goes here
+ {"%0", "&permil;"}, // ISOtech: per thousand, per mille sign
+ // Old troff \(fm goes here
+ {"sd", "&Prime;"}, // ISOtech
+ {"mc", "&micro;"}, // ISOnum
+ {"Of", "&ordf;"}, // ISOnum
+ {"Om", "&ordm;"}, // ISOnum
+ // Logical symbols
+ {"AN", "&and;"}, // ISOtech
+ {"OR", "&or;"}, // ISOtech
+ // Old troff \(no goes here
+ {"te", "&exist;"}, // ISOtech: there exists, existential quantifier
+ {"fa", "&forall;"}, // ISOtech: for all, universal quantifier
+ {"st", "&bepsi"}, // ISOamsr: such that
+ {"3d", "&there4;"}, // ISOtech
+ {"tf", "&there4;"}, // ISOtech
+ // Mathematical symbols
+ // Old troff "12", "14", "34" goes here
+ {"S1", "&sup1;"}, // ISOnum
+ {"S2", "&sup2;"}, // ISOnum
+ {"S3", "&sup3;"}, // ISOnum
+ // Old troff \(pl", \-, \(+- go here
+ {"t+-", "&plusmn;"}, // ISOnum
+ {"-+", "&mnplus;"}, // ISOtech
+ {"pc", "&middot;"}, // ISOnum
+ {"md", "&middot;"}, // ISOnum
+ // Old troff \(mu goes here
+ {"tmu", "&times;"}, // ISOnum
+ {"c*", "&otimes;"}, // ISOamsb: multiply sign in a circle
+ {"c+", "&oplus;"}, // ISOamsb: plus sign in a circle
+ // Old troff \(di goes here
+ {"tdi", "&divide;"}, // ISOnum
+ {"f/", "&horbar;"}, // ISOnum: horizintal bar for fractions
+ // Old troff \(** goes here
+ {"<=", "&le;"}, // ISOtech
+ {">=", "&ge;"}, // ISOtech
+ {"<<", "&Lt;"}, // ISOamsr
+ {">>", "&Gt;"}, // ISOamsr
+ {"!=", "&ne;"}, // ISOtech
+ // Old troff \(eq and \(== go here
+ {"=~", "&cong;"}, // ISOamsr
+ // Old troff \(ap goes here
+ {"~~", "&ap;"}, // ISOtech
+ // This appears to be an error in the groff table.
+ // It clashes with the Bell Labs use of ~= for a congruence sign
+ // {"~=", "&ap;"}, // ISOamsr
+ // Old troff \(pt, \(es, \(mo go here
+ {"nm", "&notin;"}, // ISOtech
+ {"nb", "&nsub;"}, // ISOamsr
+ {"nc", "&nsup;"}, // ISOamsn
+ {"ne", "&nequiv;"}, // ISOamsn
+ // Old troff \(sb, \(sp, \(ib, \(ip, \(ca, \(cu go here
+ {"/_", "&ang;"}, // ISOamso
+ {"pp", "&perp;"}, // ISOtech
+ // Old troff \(is goes here
+ {"sum", "&sum;"}, // ISOamsb
+ {"product", "&prod;"}, // ISOamsb
+ {"gr", "&nabla;"}, // ISOtech
+ // Old troff \(sr. \{n, \(if go here
+ {"Ah", "&aleph;"}, // ISOtech
+ {"Im", "&image;"}, // ISOamso: Fraktur I, imaginary
+ {"Re", "&real;"}, // ISOamso: Fraktur R, real
+ {"wp", "&weierp;"}, // ISOamso
+ {"pd", "&part;"}, // ISOtech: partial differentiation sign
+ // Their table duplicates the Greek letters here.
+ // We list only the variant forms here, mapping them into
+ // the ISO Greek 4 variants (which may or may not be correct :-()
+ {"+f", "&b.phiv;"}, // ISOgrk4: variant phi
+ {"+h", "&b.thetas;"}, // ISOgrk4: variant theta
+ {"+p", "&b.omega;"}, // ISOgrk4: variant pi, looking like omega
+ // Card symbols
+ {"CL", "&clubs;"}, // ISOpub: club suit
+ {"SP", "&spades;"}, // ISOpub: spade suit
+ {"HE", "&hearts;"}, // ISOpub: heart suit
+ {"DI", "&diams;"}, // ISOpub: diamond suit
+};
+
+const char *special_to_entity(const char *sp)
+{
+ struct map *mp;
+ for (mp = entity_table;
+ mp < entity_table + sizeof(entity_table)/sizeof(entity_table[0]);
+ mp++) {
+ if (strcmp(mp->from, sp) == 0)
+ return mp->to;
+ }
+ return NULL;
+}
+
+class char_box : public simple_box {
+ unsigned char c;
+ char next_is_italic;
+ char prev_is_italic;
+public:
+ char_box(unsigned char);
+ void debug_print();
+ void output();
+ int is_char();
+ int left_is_italic();
+ int right_is_italic();
+ void hint(unsigned);
+ void handle_char_type(int, int);
+};
+
+class special_char_box : public simple_box {
+ char *s;
+public:
+ special_char_box(const char *);
+ ~special_char_box();
+ void output();
+ void debug_print();
+ int is_char();
+ void handle_char_type(int, int);
+};
+
+enum spacing_type {
+ s_ordinary,
+ s_operator,
+ s_binary,
+ s_relation,
+ s_opening,
+ s_closing,
+ s_punctuation,
+ s_inner,
+ s_suppress
+};
+
+const char *spacing_type_table[] = {
+ "ordinary",
+ "operator",
+ "binary",
+ "relation",
+ "opening",
+ "closing",
+ "punctuation",
+ "inner",
+ "suppress",
+ 0,
+};
+
+const int DIGIT_TYPE = 0;
+const int LETTER_TYPE = 1;
+
+const char *font_type_table[] = {
+ "digit",
+ "letter",
+ 0,
+};
+
+struct char_info {
+ int spacing_type;
+ int font_type;
+ char_info();
+};
+
+char_info::char_info()
+: spacing_type(ORDINARY_TYPE), font_type(DIGIT_TYPE)
+{
+}
+
+static char_info char_table[256];
+
+declare_ptable(char_info)
+implement_ptable(char_info)
+
+PTABLE(char_info) special_char_table;
+
+static int get_special_char_spacing_type(const char *ch)
+{
+ char_info *p = special_char_table.lookup(ch);
+ return p ? p->spacing_type : ORDINARY_TYPE;
+}
+
+static int get_special_char_font_type(const char *ch)
+{
+ char_info *p = special_char_table.lookup(ch);
+ return p ? p->font_type : DIGIT_TYPE;
+}
+
+static void set_special_char_type(const char *ch, int st, int ft)
+{
+ char_info *p = special_char_table.lookup(ch);
+ if (!p) {
+ p = new char_info[1];
+ special_char_table.define(ch, p);
+ }
+ if (st >= 0)
+ p->spacing_type = st;
+ if (ft >= 0)
+ p->font_type = ft;
+}
+
+void init_char_table()
+{
+ set_special_char_type("pl", s_binary, -1);
+ set_special_char_type("mi", s_binary, -1);
+ set_special_char_type("eq", s_relation, -1);
+ set_special_char_type("<=", s_relation, -1);
+ set_special_char_type(">=", s_relation, -1);
+ char_table['}'].spacing_type = s_closing;
+ char_table[')'].spacing_type = s_closing;
+ char_table[']'].spacing_type = s_closing;
+ char_table['{'].spacing_type = s_opening;
+ char_table['('].spacing_type = s_opening;
+ char_table['['].spacing_type = s_opening;
+ char_table[','].spacing_type = s_punctuation;
+ char_table[';'].spacing_type = s_punctuation;
+ char_table[':'].spacing_type = s_punctuation;
+ char_table['.'].spacing_type = s_punctuation;
+ char_table['>'].spacing_type = s_relation;
+ char_table['<'].spacing_type = s_relation;
+ char_table['*'].spacing_type = s_binary;
+ for (int i = 0; i < 256; i++)
+ if (csalpha(i))
+ char_table[i].font_type = LETTER_TYPE;
+}
+
+static int lookup_spacing_type(const char *type)
+{
+ for (int i = 0; spacing_type_table[i] != 0; i++)
+ if (strcmp(spacing_type_table[i], type) == 0)
+ return i;
+ return -1;
+}
+
+static int lookup_font_type(const char *type)
+{
+ for (int i = 0; font_type_table[i] != 0; i++)
+ if (strcmp(font_type_table[i], type) == 0)
+ return i;
+ return -1;
+}
+
+void box::set_spacing_type(char *type)
+{
+ int t = lookup_spacing_type(type);
+ if (t < 0)
+ error("unrecognised type '%1'", type);
+ else
+ spacing_type = t;
+ free(type);
+}
+
+char_box::char_box(unsigned char cc)
+: c(cc), next_is_italic(0), prev_is_italic(0)
+{
+ spacing_type = char_table[c].spacing_type;
+}
+
+void char_box::hint(unsigned flags)
+{
+ if (flags & HINT_PREV_IS_ITALIC)
+ prev_is_italic = 1;
+ if (flags & HINT_NEXT_IS_ITALIC)
+ next_is_italic = 1;
+}
+
+void char_box::output()
+{
+ if (output_format == troff) {
+ int font_type = char_table[c].font_type;
+ if (font_type != LETTER_TYPE)
+ printf("\\f[%s]", current_roman_font);
+ if (!prev_is_italic)
+ fputs("\\,", stdout);
+ if (c == '\\')
+ fputs("\\e", stdout);
+ else
+ putchar(c);
+ if (!next_is_italic)
+ fputs("\\/", stdout);
+ else
+ fputs("\\&", stdout); // suppress ligaturing and kerning
+ if (font_type != LETTER_TYPE)
+ fputs("\\fP", stdout);
+ }
+ else if (output_format == mathml) {
+ if (isdigit(c))
+ printf("<mn>");
+ else if (char_table[c].spacing_type)
+ printf("<mo>");
+ else
+ printf("<mi>");
+ if (c == '<')
+ printf("&lt;");
+ else if (c == '>')
+ printf("&gt;");
+ else if (c == '&')
+ printf("&amp;");
+ else
+ putchar(c);
+ if (isdigit(c))
+ printf("</mn>");
+ else if (char_table[c].spacing_type)
+ printf("</mo>");
+ else
+ printf("</mi>");
+ }
+}
+
+int char_box::left_is_italic()
+{
+ int font_type = char_table[c].font_type;
+ return font_type == LETTER_TYPE;
+}
+
+int char_box::right_is_italic()
+{
+ int font_type = char_table[c].font_type;
+ return font_type == LETTER_TYPE;
+}
+
+int char_box::is_char()
+{
+ return 1;
+}
+
+void char_box::debug_print()
+{
+ if (c == '\\') {
+ putc('\\', stderr);
+ putc('\\', stderr);
+ }
+ else
+ putc(c, stderr);
+}
+
+special_char_box::special_char_box(const char *t)
+{
+ s = strsave(t);
+ spacing_type = get_special_char_spacing_type(s);
+}
+
+special_char_box::~special_char_box()
+{
+ free(s);
+}
+
+void special_char_box::output()
+{
+ if (output_format == troff) {
+ int font_type = get_special_char_font_type(s);
+ if (font_type != LETTER_TYPE)
+ printf("\\f[%s]", current_roman_font);
+ printf("\\,\\[%s]\\/", s);
+ if (font_type != LETTER_TYPE)
+ printf("\\fP");
+ }
+ else if (output_format == mathml) {
+ const char *entity = special_to_entity(s);
+ if (entity != NULL)
+ printf("<mo>%s</mo>", entity);
+ else
+ printf("<merror>unknown eqn/troff special char %s</merror>", s);
+ }
+}
+
+int special_char_box::is_char()
+{
+ return 1;
+}
+
+void special_char_box::debug_print()
+{
+ fprintf(stderr, "\\[%s]", s);
+}
+
+
+void char_box::handle_char_type(int st, int ft)
+{
+ if (st >= 0)
+ char_table[c].spacing_type = st;
+ if (ft >= 0)
+ char_table[c].font_type = ft;
+}
+
+void special_char_box::handle_char_type(int st, int ft)
+{
+ set_special_char_type(s, st, ft);
+}
+
+void set_char_type(const char *type, char *ch)
+{
+ assert(ch != 0);
+ int st = lookup_spacing_type(type);
+ int ft = lookup_font_type(type);
+ if (st < 0 && ft < 0) {
+ error("bad character type '%1'", type);
+ delete[] ch;
+ return;
+ }
+ box *b = split_text(ch);
+ b->handle_char_type(st, ft);
+ delete b;
+}
+
+/* We give primes special treatment so that in "x' sub 2", the "2"
+will be tucked under the prime */
+
+class prime_box : public pointer_box {
+ box *pb;
+public:
+ prime_box(box *);
+ ~prime_box();
+ int compute_metrics(int style);
+ void output();
+ void compute_subscript_kern();
+ void debug_print();
+ void handle_char_type(int, int);
+};
+
+box *make_prime_box(box *pp)
+{
+ return new prime_box(pp);
+}
+
+prime_box::prime_box(box *pp) : pointer_box(pp)
+{
+ pb = new special_char_box("fm");
+}
+
+prime_box::~prime_box()
+{
+ delete pb;
+}
+
+int prime_box::compute_metrics(int style)
+{
+ int res = p->compute_metrics(style);
+ pb->compute_metrics(style);
+ printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]"
+ "+\\n[" WIDTH_FORMAT "]\n",
+ uid, p->uid, pb->uid);
+ printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
+ ">?\\n[" HEIGHT_FORMAT "]\n",
+ uid, p->uid, pb->uid);
+ printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
+ ">?\\n[" DEPTH_FORMAT "]\n",
+ uid, p->uid, pb->uid);
+ return res;
+}
+
+void prime_box::compute_subscript_kern()
+{
+ p->compute_subscript_kern();
+ printf(".nr " SUB_KERN_FORMAT " 0\\n[" WIDTH_FORMAT "]"
+ "+\\n[" SUB_KERN_FORMAT "]>?0\n",
+ uid, pb->uid, p->uid);
+}
+
+void prime_box::output()
+{
+ p->output();
+ pb->output();
+}
+
+void prime_box::handle_char_type(int st, int ft)
+{
+ p->handle_char_type(st, ft);
+ pb->handle_char_type(st, ft);
+}
+
+void prime_box::debug_print()
+{
+ p->debug_print();
+ putc('\'', stderr);
+}
+
+box *split_text(char *text)
+{
+ list_box *lb = 0;
+ box *fb = 0;
+ char *s = text;
+ while (*s != '\0') {
+ char c = *s++;
+ box *b = 0;
+ switch (c) {
+ case '+':
+ b = new special_char_box("pl");
+ break;
+ case '-':
+ b = new special_char_box("mi");
+ break;
+ case '=':
+ b = new special_char_box("eq");
+ break;
+ case '\'':
+ b = new special_char_box("fm");
+ break;
+ case '<':
+ if (*s == '=') {
+ b = new special_char_box("<=");
+ s++;
+ break;
+ }
+ goto normal_char;
+ case '>':
+ if (*s == '=') {
+ b = new special_char_box(">=");
+ s++;
+ break;
+ }
+ goto normal_char;
+ case '\\':
+ if (*s == '\0') {
+ lex_error("bad escape");
+ break;
+ }
+ c = *s++;
+ switch (c) {
+ case '(':
+ {
+ char buf[3];
+ if (*s != '\0') {
+ buf[0] = *s++;
+ if (*s != '\0') {
+ buf[1] = *s++;
+ buf[2] = '\0';
+ b = new special_char_box(buf);
+ }
+ else {
+ lex_error("bad escape");
+ }
+ }
+ else {
+ lex_error("bad escape");
+ }
+ }
+ break;
+ case '[':
+ {
+ char *ch = s;
+ while (*s != ']' && *s != '\0')
+ s++;
+ if (*s == '\0')
+ lex_error("bad escape");
+ else {
+ *s++ = '\0';
+ b = new special_char_box(ch);
+ }
+ }
+ break;
+ case 'f':
+ case 'g':
+ case 'k':
+ case 'n':
+ case '*':
+ {
+ char *escape_start = s - 2;
+ switch (*s) {
+ case '(':
+ if (*++s != '\0')
+ ++s;
+ break;
+ case '[':
+ for (++s; *s != '\0' && *s != ']'; s++)
+ ;
+ break;
+ }
+ if (*s == '\0')
+ lex_error("bad escape");
+ else {
+ ++s;
+ char *buf = new char[s - escape_start + 1];
+ memcpy(buf, escape_start, s - escape_start);
+ buf[s - escape_start] = '\0';
+ b = new quoted_text_box(buf);
+ }
+ }
+ break;
+ case '-':
+ case '_':
+ {
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ b = new special_char_box(buf);
+ }
+ break;
+ case '`':
+ b = new special_char_box("ga");
+ break;
+ case '\'':
+ b = new special_char_box("aa");
+ break;
+ case 'e':
+ case '\\':
+ b = new char_box('\\');
+ break;
+ case '^':
+ case '|':
+ case '0':
+ {
+ char buf[3];
+ buf[0] = '\\';
+ buf[1] = c;
+ buf[2] = '\0';
+ b = new quoted_text_box(strsave(buf));
+ break;
+ }
+ default:
+ lex_error("unquoted escape");
+ b = new quoted_text_box(strsave(s - 2));
+ s = strchr(s, '\0');
+ break;
+ }
+ break;
+ default:
+ normal_char:
+ b = new char_box(c);
+ break;
+ }
+ while (*s == '\'') {
+ if (b == 0)
+ b = new quoted_text_box(0);
+ b = new prime_box(b);
+ s++;
+ }
+ if (b != 0) {
+ if (lb != 0)
+ lb->append(b);
+ else if (fb != 0) {
+ lb = new list_box(fb);
+ lb->append(b);
+ }
+ else
+ fb = b;
+ }
+ }
+ free(text);
+ if (lb != 0)
+ return lb;
+ else if (fb != 0)
+ return fb;
+ else
+ return new quoted_text_box(0);
+}
+
diff --git a/src/preproc/grn/README b/src/preproc/grn/README
new file mode 100644
index 0000000..73273f7
--- /dev/null
+++ b/src/preproc/grn/README
@@ -0,0 +1,68 @@
+This is grn from the Berkeley ditroff distribution. It has no
+AT&T code and is therefore freely distributable.
+
+Tim Theisen <tim@cs.wisc.edu>
+
+=====================================================================
+
+This is the modified code for the groff. It uses the different
+devxxx format that is ascii rather than binary as in the
+Berkeley distribution. Since groff does not have the \Ds option
+for line drawing (dotted, dashed, etc.), this version includes
+the routines for drawing curves and arcs, so it does not use the
+\D~, \Da nor \Dc. Although also included in here is a routine
+for drawing the optional gremlin style curves, it is not used
+because the gremlin editor uses the conventional spline
+algorithm. The Berkeley grn has the choice of different
+stipples. Here, only different shades of gray will be painted
+depending on the gremlin file. It is possible to upgrade this at
+a later time. (Daniel Senderowicz <daniel@synchrods.com> 12/28/99)
+
+=====================================================================
+
+Gremlin produces three types of curves: B-Splines, interpolated
+curves and Bezier. As the original Berkeley grn, now groff grn
+will honor B-Splines and interpolated curves. Bezier curves will
+be printed as B-Splines. (Daniel Senderowicz <daniel@synchrods.com>
+10/04/02)
+
+=====================================================================
+
+It has been further modified by Werner Lemberg <wl@gnu.org> to fit
+better into the groff package.
+
+ . Replaced Makefile with Makefile.sub.
+
+ . Removed dev.h since it is unused.
+
+ . Renamed grn.1 to grn.man; this man page has been extensively
+ revised.
+
+ . Used error() and fatal() from libgroff for all source files.
+
+ . Renamed *.c to *.cpp; updates as needed for C++ (prototypes, proper
+ casts, standard header files etc). Heavy formatting.
+
+ . main.cpp:
+
+ Using groff's default values instead of DEVDIR, DEFAULTDEV,
+ PRINTER, TYPESETTER, and GREMLIB.
+
+ 'res' is now an integer.
+
+ Added '-C' command flag (for compatibility mode) as with other
+ preprocessors.
+
+ Added '-F' and '-v' option (similar to troff).
+
+ Renamed '-L' option to '-M' for consistence.
+
+ Removed '-P' option.
+
+ Using font::load_desc() for scanning DESC files.
+
+ Removed SYSV-specific code.
+
+ Using macro_path.open_file() for getting gremlin graphic files.
+
+ Added usage().
diff --git a/src/preproc/grn/gprint.h b/src/preproc/grn/gprint.h
new file mode 100644
index 0000000..772d79a
--- /dev/null
+++ b/src/preproc/grn/gprint.h
@@ -0,0 +1,90 @@
+/* Last non-groff version: gprint.h 1.1 84/10/08
+ *
+ * This file contains standard definitions used by the gprint program.
+ */
+
+#include <stdio.h>
+#include <math.h>
+
+
+#define xorn(x,y) (x)
+ /* was 512 */
+#define yorn(x,y) (511 - (y)) /* switch direction for */
+ /* y-coordinates */
+
+#define STYLES 6
+#define SIZES 4
+#define FONTS 4
+#define SOLID -1
+#define DOTTED 004 /* 014 */
+#define DASHED 020 /* 034 */
+#define DOTDASHED 024 /* 054 */
+#define LONGDASHED 074
+
+#define DEFTHICK -1 /* default thickness */
+#define DEFSTYLE SOLID /* default line style */
+
+#define TRUE 1
+#define FALSE 0
+
+#define nullelt -1
+#define nullpt -1
+#define nullun NULL
+
+#define BOTLEFT 0
+#define BOTRIGHT 1
+#define CENTCENT 2
+#define VECTOR 3
+#define ARC 4
+#define CURVE 5
+#define POLYGON 6
+#define BSPLINE 7
+#define BEZIER 8
+#define TOPLEFT 10
+#define TOPCENT 11
+#define TOPRIGHT 12
+#define CENTLEFT 13
+#define CENTRIGHT 14
+#define BOTCENT 15
+#define TEXT(t) ( (t <= CENTCENT) || (t >= TOPLEFT) )
+
+/* WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING
+ * The above (TEXT) test is dependent on the relative values of the
+ * constants and will have to change if these values change or if new
+ * commands are added with value greater than BOTCENT
+ */
+
+#define NUSER 4
+#define NFONTS 4
+#define NBRUSHES 6
+#define NSIZES 4
+#define NJUSTS 9
+#define NSTIPPLES 16
+
+#define ADD 1
+#define DELETE 2
+#define MOD 3
+
+typedef struct point {
+ double x, y;
+ struct point *nextpt;
+} POINT;
+
+typedef struct elmt {
+ int type, brushf, size, textlength;
+ char *textpt;
+ POINT *ptlist;
+ struct elmt *nextelt, *setnext;
+} ELT;
+
+#define DBNextElt(elt) (elt->nextelt)
+#define DBNextofSet(elt) (elt->setnext)
+#define DBNullelt(elt) (elt == NULL)
+#define Nullpoint(pt) ((pt) == (POINT *) NULL)
+#define PTNextPoint(pt) (pt->nextpt)
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/grn/grn.1.man b/src/preproc/grn/grn.1.man
new file mode 100644
index 0000000..cbc15ae
--- /dev/null
+++ b/src/preproc/grn/grn.1.man
@@ -0,0 +1,978 @@
+'\" t
+.TH @g@grn @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@grn \- embed Gremlin images in
+.I groff
+documents
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2000-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grn_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@grn
+.RB [ \-C ]
+.RB [ \-T\~\c
+.IR dev ]
+.RB [ \-M\~\c
+.IR dir ]
+.RB [ \-F\~\c
+.IR dir ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@grn
+.B \-?
+.
+.SY @g@grn
+.B \-\-help
+.YS
+.
+.
+.SY @g@grn
+.B \-v
+.
+.SY @g@grn
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I @g@grn
+is a preprocessor for including
+.I gremlin
+pictures in
+.MR @g@troff @MAN1EXT@
+input.
+.
+.I @g@grn
+writes to standard output,
+processing only input lines between two that start with
+.B .GS
+and
+.BR .GE .
+.
+Those lines must contain
+.I @g@grn
+commands
+(see below).
+.
+These macros request a
+.I gremlin
+file;
+the picture in that file is converted and placed in the
+.I @g@troff
+input stream.
+.
+.B .GS
+may be called with a
+.BR C ,
+.BR L ,
+or
+.B R
+argument to center,
+left-,
+or right-justify the whole
+.I gremlin
+picture
+(the default is to center).
+.
+If no
+.I file
+is mentioned,
+the standard input is read.
+.
+At the end of the picture,
+the position on the page is the bottom of the
+.I gremlin
+picture.
+.
+If the
+.I @g@grn
+entry is ended with
+.B .GF
+instead of
+.BR .GE ,
+the position is left at the top of the picture.
+.
+.
+.PP
+Currently only the
+.I me
+macro package has support for
+.BR .GS ,
+.BR .GE ,
+and
+.BR .GF .
+.
+.
+.PP
+.I @g@grn
+produces drawing escape sequences that use
+.IR groff 's
+color scheme extension
+.RB ( \[rs]D\[aq]F \~.\|.\|.\& \[aq] ),
+and thus may not work with other
+.IR troff s.
+.
+.
+.\" ====================================================================
+.SS "\f[I]grn\f[] commands"
+.\" ====================================================================
+.
+Each input line between
+.B .GS
+and
+.B .GE
+may have one
+.I @g@grn
+command.
+.
+Commands consist of one or two strings separated by white space,
+the first string being the command and the second its operand.
+.
+Commands may be upper- or lowercase and abbreviated down to one
+character.
+.
+.
+.PP
+Commands that affect a picture's environment
+(those listed before
+.RB \%\[lq] default \[rq],
+see below)
+are only in effect for the current picture:
+the environment is reinitialized to the defaults at the start of the
+next picture.
+.
+The commands are as follows.
+.
+.
+.TP
+.BI 1\~ N
+.TQ
+.BI 2\~ N
+.TQ
+.BI 3\~ N
+.TQ
+.BI 4\~ N
+.
+Set
+.IR gremlin 's
+text size number 1
+(2,
+3,
+or 4)
+to
+.I N
+points.
+.
+The default is 12
+(16,
+24,
+and 36,
+respectively).
+.
+.
+.TP
+.BI roman\~ f
+.TQ
+.BI italics\~ f
+.TQ
+.BI bold\~ f
+.TQ
+.BI special\~ f
+Set the roman
+(italics,
+bold,
+or special)
+font to
+.IR @g@troff 's
+font
+.I f
+(either a name or number).
+.
+The default is R
+(I,
+B,
+and S,
+respectively).
+.
+.
+.TP
+.BI l\~ f
+.TQ
+.BI stipple\~ f
+Set the stipple font to
+.IR @g@troff 's
+stipple font
+.I f
+(name or number).
+.
+The command
+.B \%stipple
+may be abbreviated down as far as
+.RB \[lq] st \[rq]
+(to avoid confusion with
+.RB \%\[lq] special \[rq]).
+.
+There is
+.I no
+default for stipples
+(unless one is set by the
+.RB \%\[lq] default \[rq]
+command),
+and it is invalid to include a
+.I gremlin
+picture with polygons without specifying a stipple font.
+.
+.
+.TP
+.BI x\~ N
+.TQ
+.BI scale\~ N
+Magnify the picture
+(in addition to any default magnification)
+by
+.IR N ,
+a floating-point number larger than zero.
+.
+The command
+.B scale
+may be abbreviated down to
+.RB \[lq] sc \[rq].
+.
+.
+.TP
+.BI narrow\~ N
+.TQ
+.BI medium\~ N
+.TQ
+.BI thick\~ N
+.
+Set the thickness of
+.IR gremlin 's
+narrow
+(medium and thick,
+respectively)
+lines to
+.I N
+times 0.15pt
+(this value can be changed at compile time).
+.
+The default is 1.0
+(3.0 and 5.0,
+respectively),
+which corresponds to 0.15pt
+(0.45pt and 0.75pt,
+respectively).
+.
+A thickness value of zero selects the smallest available line thickness.
+.
+Negative values cause the line thickness to be proportional to the
+current point size.
+.
+.
+.TP
+.BR pointscale\~ [ off | on ]
+Scale text to match the picture.
+.
+Gremlin text is usually printed in the point size specified with the
+commands
+.BR 1 ,
+.BR 2 ,
+.BR 3 ,
+.RB or\~ 4 ,
+regardless of any scaling factors in the picture.
+.
+Setting
+.B pointscale
+will cause the point sizes to scale with the picture
+(within
+.IR @g@troff 's
+limitations,
+of course).
+.
+An operand of anything but
+.B off
+will turn text scaling on.
+.
+.
+.TP
+.B default
+Reset the picture environment defaults to the settings in the current
+picture.
+.
+This is meant to be used as a global parameter setting mechanism at
+the beginning of the
+.I @g@troff
+input file,
+but can be used at any time to reset the default settings.
+.
+.
+.TP
+.BI width\~ N
+Force the picture to be
+.I N
+inches wide.
+.
+This overrides any scaling factors present in the same picture.
+.
+.RB \[lq] "width 0" \[rq]
+is ignored.
+.
+.
+.TP
+.BI height\~ N
+Force the picture to be
+.I N
+inches high,
+overriding other scaling factors.
+.
+If both
+.B width
+and
+.B height
+are specified,
+the tighter constraint will determine the scale of the picture.
+.
+.B height
+and
+.B width
+commands are not saved with a
+.RB \%\[lq] default \[rq]
+command.
+.
+They will,
+however,
+affect point size scaling if that option is set.
+.
+.
+.TP
+.BI file\~ name
+Get picture from
+.I gremlin
+file
+.I name
+located the current directory
+(or in the library directory;
+see the
+.B \-M
+option above).
+.
+If multiple
+.B file
+commands are given,
+the last one controls.
+.
+If
+.I name
+doesn't exist,
+an error message is reported and processing continues from the
+.B .GE
+line.
+.
+.
+.\" ====================================================================
+.SS "Usage with \f[I]groff\f[]"
+.\" ====================================================================
+.
+Since
+.I @g@grn
+is a preprocessor,
+it has no access to elements of formatter state,
+such as
+indentation,
+line length,
+type size,
+or
+register values.
+.
+Consequently,
+no
+.I @g@troff
+input can be placed between the
+.B .GS
+and
+.B .GE
+macros.
+.
+However,
+.I gremlin
+text elements are subsequently processed by
+.IR @g@troff ,
+so anything valid in a single line of
+.I @g@troff
+input is valid in a line of
+.I gremlin
+text
+(barring the dot control character \[lq].\[rq] at the beginning of a
+line).
+.
+Thus,
+it is possible to have equations within a
+.I gremlin
+figure by including in the
+.I gremlin
+file
+.I eqn \" language
+expressions enclosed by previously defined delimiters
+(e.g.,
+\[lq]$$\[rq]).
+.
+.
+.PP
+When using
+.I @g@grn
+along with other preprocessors,
+it is best to run
+.MR @g@tbl @MAN1EXT@
+before
+.IR @g@grn ,
+.MR @g@pic @MAN1EXT@ ,
+and/or
+.I ideal \" no GNU version yet
+to avoid overworking
+.IR @g@tbl .
+.
+.MR @g@eqn @MAN1EXT@
+should always be run last.
+.
+.MR groff @MAN1EXT@
+will automatically run preprocessors in the correct order.
+.
+.
+.PP
+A picture is considered an entity,
+but that doesn't stop
+.I @g@troff
+from trying to break it up if it falls off the end of a page.
+.
+Placing the picture between \[lq]keeps\[rq] in the
+.I me
+macros will ensure proper placement.
+.
+.
+.PP
+.I @g@grn
+uses
+.IR @g@troff 's
+registers
+.B g1
+through
+.B g9
+and sets registers
+.B g1
+and
+.B g2
+to the width and height of the
+.I gremlin
+figure
+(in device units)
+before entering the
+.B .GS
+macro
+(this is for those who want to rewrite these macros).
+.
+.
+.\" ====================================================================
+.SS "Gremlin file format"
+.\" ====================================================================
+.
+There exist two distinct
+.I gremlin
+file formats:
+the original format for AED graphic terminals,
+and the Sun or X11 version.
+.
+An extension used by the Sun/X11 version allowing reference points with
+negative coordinates is
+.I not
+compatible with the AED version.
+.
+As long as a
+.I gremlin
+file does not contain negative coordinates,
+either format will be read correctly by either version of
+.I gremlin
+or
+.IR @g@grn .
+.
+The other difference in
+Sun/X11 format is the use of names for picture objects
+(e.g.,
+.BR POLYGON ,
+.BR CURVE )
+instead of numbers.
+.
+Files representing the same picture are shown below.
+.
+.
+.PP
+.ie t .ne 18v
+.el .ne 19v
+.TS
+center, tab(@);
+l lw(0.1i) l.
+sungremlinfile@@gremlinfile
+0 240.00 128.00@@0 240.00 128.00
+CENTCENT@@2
+240.00 128.00@@240.00 128.00
+185.00 120.00@@185.00 120.00
+240.00 120.00@@240.00 120.00
+296.00 120.00@@296.00 120.00
+*@@\-1.00 \-1.00
+2 3@@2 3
+10 A Triangle@@10 A Triangle
+POLYGON@@6
+224.00 416.00@@224.00 416.00
+96.00 160.00@@96.00 160.00
+384.00 160.00@@384.00 160.00
+*@@\-1.00 \-1.00
+5 1@@5 1
+0@@0
+\-1@@\-1
+.TE
+.
+.
+.IP \[bu] 2n
+The first line of each
+.I gremlin
+file contains either the string
+.RB \[lq] gremlinfile \[rq]
+(AED)
+or
+.RB \[lq] sungremlinfile \[rq]
+(Sun/X11).
+.
+.
+.IP \[bu]
+The second line of the file contains an orientation and
+.I x
+and
+.I y
+values for a positioning point,
+separated by spaces.
+.
+The orientation,
+either
+.B 0
+or
+.BR 1 ,
+is ignored by the Sun/X11 version.
+.
+.B 0
+means that
+.I gremlin
+will display things in horizontal format
+(a drawing area wider than it is tall,
+with a menu across the top).
+.
+.B 1
+means that
+.I gremlin
+will display things in vertical format
+(a drawing area taller than it is wide,
+with a menu on the left side).
+.
+.I x
+and
+.I y
+are floating-point values giving a positioning point to be used when
+this file is read into another file.
+.
+The stuff on this line really isn't all that important;
+a value of
+.RB \[lq] "1 0.00 0.00" \[rq]
+is suggested.
+.
+.
+.IP \[bu]
+The rest of the file consists of zero or more element specifications.
+.
+After the last element specification is a line containing the string
+.RB \[lq] \-1 \[rq].
+.
+.
+.IP \[bu]
+Lines longer than 127 characters are truncated to that length.
+.
+.
+.\" ====================================================================
+.SS "Element specifications"
+.\" ====================================================================
+.
+.IP \[bu] 2n
+The first line of each element contains a single decimal number giving
+the type of the element (AED) or its name (Sun/X11).
+.
+.
+.IP
+.ie t .ne 18v
+.el .ne 19v
+.TS
+center, tab(@);
+css
+ccc
+nBlBl.
+\f[I]gremlin\f[] File Format: Object Type Specification
+_
+AED Number@Sun/X11 Name@Description
+0@BOTLEFT@bottom-left-justified text
+1@BOTRIGHT@bottom-right-justified text
+2@CENTCENT@center-justified text
+3@VECTOR@vector
+4@ARC@arc
+5@CURVE@curve
+6@POLYGON@polygon
+7@BSPLINE@b-spline
+8@BEZIER@B\['e]zier
+10@TOPLEFT@top-left-justified text
+11@TOPCENT@top-center-justified text
+12@TOPRIGHT@top-right-justified text
+13@CENTLEFT@left-center-justified text
+14@CENTRIGHT@right-center-justified text
+15@BOTCENT@bottom-center-justified text
+.TE
+.
+.
+.IP \[bu]
+After the object type comes a variable number of lines,
+each specifying a point used to display the element.
+.
+Each line contains an x-coordinate and a y-coordinate in floating-point
+format,
+separated by spaces.
+.
+The list of points is terminated by a line containing the string
+.RB \[lq] "\-1.0 \-1.0" \[rq]
+(AED) or a single asterisk,
+.RB \[lq] * \[rq]
+(Sun/X11).
+.
+.
+.IP \[bu]
+After the points comes a line containing two decimal values,
+giving the brush and size for the element.
+.
+The brush determines the style in which things are drawn.
+.
+For vectors,
+arcs,
+and curves there are six valid brush values.
+.
+.
+.IP
+.TS
+center, tab(@);
+nB l.
+1@thin dotted lines
+2@thin dot-dashed lines
+3@thick solid lines
+4@thin dashed lines
+5@thin solid lines
+6@medium solid lines
+.TE
+.
+.
+.IP
+For polygons,
+one more value,
+0,
+is valid.
+.
+It specifies a polygon with an invisible border.
+.
+For text,
+the brush selects a font as follows.
+.
+.
+.IP
+.TS
+center, tab(@);
+nB l.
+1@roman (R font in \f[I]@g@troff\f[])
+2@italics (I font in \f[I]@g@troff\f[])
+3@bold (B font in \f[I]@g@troff\f[])
+4@special (S font in \f[I]@g@troff\f[])
+.TE
+.
+.
+.IP
+If you're using
+.I @g@grn
+to run your pictures through
+.IR groff ,
+the font is really just a starting font.
+.
+The text string can contain formatting sequences like
+\[lq]\[rs]fI\[rq]
+or
+\[lq]\[rs]d\[rq]
+which may change the font
+(as well as do many other things).
+.
+For text,
+the size field is a decimal value between 1 and 4.
+.
+It selects the size of the font in which the text will be drawn.
+.
+For polygons,
+this size field is interpreted as a stipple number to fill the polygon
+with.
+.
+The number is used to index into a stipple font at print time.
+.
+.
+.IP \[bu]
+The last line of each element contains a decimal number and a string of
+characters,
+separated by a single space.
+.
+The number is a count of the number of characters in the string.
+.
+This information is used only for text elements,
+and contains the text string.
+.
+There can be spaces inside the text.
+.
+For arcs,
+curves,
+and vectors,
+the character count is zero
+.RB ( 0 ),
+followed by exactly one space before the newline.
+.
+.
+.\" ====================================================================
+.SS Coordinates
+.\" ====================================================================
+.
+.I gremlin
+was designed for AED terminals,
+and its coordinates reflect the AED coordinate space.
+.
+For vertical pictures,
+.IR x \~values
+range 116 to 511,
+and
+.IR y \~values
+from 0 to 483.
+.
+For horizontal pictures,
+.IR x \~values
+range from 0 to 511,
+and
+.IR y \~values
+from 0 to 367.
+.
+Although you needn't absolutely stick to this range,
+you'll get better results if you at least stay in this vicinity.
+.
+Also,
+point lists are terminated by a point of
+(\-1,
+\-1),
+so you shouldn't ever use negative coordinates.
+.
+.I gremlin
+writes out coordinates using the
+.MR printf 3
+format \[lq]%f1.2\[rq];
+it's probably a good idea to use the same format if you want to modify
+the
+.I @g@grn
+code.
+.
+.
+.\" ====================================================================
+.SS "Sun/X11 coordinates"
+.\" ====================================================================
+.
+There is no restriction on the range of coordinates used to create
+objects in the Sun/X11 version of
+.IR gremlin .
+.
+However,
+files with negative coordinates
+.I will
+cause problems if displayed on the AED.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-?\&
+and
+.B \-\-help
+display a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-C
+Recognize
+.B .GS
+and
+.B .GE
+(and
+.BR .GF )
+even when followed by a character other than space or newline.
+.
+.
+.TP
+.BI \-F\~ dir
+Search
+.I dir
+for subdirectories
+.IR dev name
+.RI ( name
+is the name of the output driver)
+for the
+.I DESC
+file before the default font directories
+.IR @LOCALFONTDIR@ ,
+.IR @FONTDIR@ ,
+and
+.IR @LEGACYFONTDIR@ .
+.
+.
+.TP
+.BI \-M\~ dir
+Prepend
+.I dir
+to the search path for
+.I gremlin
+files.
+.
+The default search path is the current directory,
+the home directory,
+.if !'@COMPATIBILITY_WRAPPERS@'no' .IR @SYSTEMMACRODIR@ ,
+.IR @LOCALMACRODIR@ ,
+and
+.IR @MACRODIR@ ,
+in that order.
+.\".
+.\".
+.\".TP
+.\".B \-s
+.\"This switch causes the picture to be traversed twice:
+.\"The first time,
+.\"only the interiors of filled polygons
+.\"(as borderless polygons)
+.\"are printed.
+.\".
+.\"The second time,
+.\"the outline is printed as a series of line segments.
+.\".
+.\"This way,
+.\"postprocessors that overwrite rather than merge picture elements
+.\"(such as PostScript)
+.\"can still have text and graphics on a shaded background.
+.
+.
+.TP
+.BI \-T\~ dev
+Prepare device output using output driver
+.IR dev .
+.
+The default is
+.BR @DEVICE@ .
+.
+See
+.MR groff @MAN1EXT@
+for a list of valid devices.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.IR @FONTDIR@/\:\%dev name /\:DESC
+describes the output device
+.IR name .
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+David Slattengren and Barry Roitblat wrote the original Berkeley
+.IR grn .
+.
+Daniel Senderowicz and Werner Lemberg modified it for
+.IR groff .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR gremlin 1 ,
+.MR groff @MAN1EXT@ ,
+.MR @g@pic @MAN1EXT@ ,
+.MR ideal 1
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grn_1_man_C]
+.do rr *groff_grn_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/preproc/grn/grn.am b/src/preproc/grn/grn.am
new file mode 100644
index 0000000..f45fa24
--- /dev/null
+++ b/src/preproc/grn/grn.am
@@ -0,0 +1,35 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += grn
+grn_SOURCES = \
+ src/preproc/grn/hdb.cpp \
+ src/preproc/grn/hpoint.cpp \
+ src/preproc/grn/hgraph.cpp \
+ src/preproc/grn/main.cpp \
+ src/preproc/grn/gprint.h
+src/preproc/grn/main.$(OBJEXT): defs.h
+grn_LDADD = libgroff.a lib/libgnu.a $(LIBM)
+PREFIXMAN1 += src/preproc/grn/grn.1
+EXTRA_DIST += src/preproc/grn/README src/preproc/grn/grn.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/grn/hdb.cpp b/src/preproc/grn/hdb.cpp
new file mode 100644
index 0000000..9ba3eaa
--- /dev/null
+++ b/src/preproc/grn/hdb.cpp
@@ -0,0 +1,441 @@
+ /* Last non-groff version: hdb.c 1.8 (Berkeley) 84/10/20
+ *
+ * Copyright -C- 1982 Barry S. Roitblat
+ *
+ * This file contains database routines for the hard copy programs of
+ * the gremlin picture editor.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include "gprint.h"
+#include <string.h>
+#include <ctype.h>
+
+#include "errarg.h"
+#include "error.h"
+
+#define MAXSTRING 128
+#define MAXSTRING_S "127"
+
+/* imports from main.cpp */
+
+extern char gremlinfile[]; /* name of file currently reading */
+extern int SUNFILE; /* TRUE if SUN gremlin file */
+extern int compatibility_flag; /* TRUE if in compatibility mode */
+extern void *grnmalloc(size_t size, const char *what);
+extern void savebounds(double x, double y);
+
+/* imports from hpoint.cpp */
+
+extern POINT *PTInit();
+extern POINT *PTMakePoint(double x, double y, POINT ** pplist);
+
+
+int DBGetType(char *s);
+
+static long lineno = 0; /* line number of gremlin file */
+
+/*
+ * This routine returns a pointer to an initialized database element
+ * which would be the only element in an empty list.
+ */
+ELT *
+DBInit()
+{
+ return ((ELT *) NULL);
+} /* end DBInit */
+
+
+/*
+ * This routine creates a new element with the specified attributes and
+ * links it into database.
+ */
+ELT *
+DBCreateElt(int type,
+ POINT * pointlist,
+ int brush,
+ int size,
+ char *text,
+ ELT **db)
+{
+ ELT *temp = 0;
+
+ temp = (ELT *) grnmalloc(sizeof(ELT), "picture element");
+ temp->nextelt = *db;
+ temp->type = type;
+ temp->ptlist = pointlist;
+ temp->brushf = brush;
+ temp->size = size;
+ temp->textpt = text;
+ *db = temp;
+ return (temp);
+} /* end CreateElt */
+
+
+/*
+ * This routine reads the specified file into a database and returns a
+ * pointer to that database.
+ */
+ELT *
+DBRead(FILE *file)
+{
+ int i;
+ int done; /* flag for input exhausted */
+ double nx; /* x holder so x is not set before orienting */
+ int type; /* element type */
+ ELT *elist; /* pointer to the file's elements */
+ POINT *plist; /* pointer for reading in points */
+ char string[MAXSTRING], *txt;
+ double x, y; /* x and y are read in point coords */
+ int len, brush, size;
+ int lastpoint;
+
+ SUNFILE = FALSE;
+ elist = DBInit();
+ int nitems = fscanf(file, "%" MAXSTRING_S "s%*[^\n]\n", string);
+ if (nitems != 1) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "malformed input; giving up on this"
+ " picture");
+ return (elist);
+ }
+ lineno++;
+ if (strcmp(string, "gremlinfile")) {
+ if (strcmp(string, "sungremlinfile")) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "not a gremlin file; giving up on this"
+ " picture");
+ return (elist);
+ }
+ SUNFILE = TRUE;
+ }
+
+ nitems = fscanf(file, "%d%lf%lf\n", &size, &x, &y);
+ if (nitems != 3) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "malformed input; giving up on this"
+ " picture");
+ return (elist);
+ }
+ lineno++;
+ /* ignore orientation and file positioning point */
+
+ done = FALSE;
+ while (!done) {
+ /* if (fscanf(file,"%" MAXSTRING_S "s\n", string) == EOF) */
+ /* I changed the scanf format because the element */
+ /* can have two words (e.g. CURVE SPLINE) */
+ if (fscanf(file, "\n%"
+ MAXSTRING_S
+ "[^\n]%*[^\n]\n", string) == EOF) {
+ lineno++;
+ error_with_file_and_line(gremlinfile, lineno, "error in format;"
+ " giving up on this picture");
+ return (elist);
+ }
+ lineno++;
+
+ type = DBGetType(string); /* interpret element type */
+ if (type < 0) { /* no more data */
+ done = TRUE;
+ } else {
+ /* always one point */
+#ifdef UW_FASTSCAN
+ (void) xscanf(file, &x, &y);
+#else
+ nitems = fscanf(file, "%lf%lf\n", &x, &y);
+ if (nitems != 2) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "malformed input; giving up on this"
+ " picture");
+ return (elist);
+ }
+ lineno++;
+#endif /* UW_FASTSCAN */
+ plist = PTInit(); /* NULL point list */
+
+ /*
+ * Files created on the SUN have point lists terminated by a line
+ * containing only an asterisk ('*'). Files created on the AED
+ * have point lists terminated by the coordinate pair (-1.00
+ * -1.00).
+ */
+ if (TEXT(type)) { /* read only first point for TEXT elements */
+ nx = xorn(x, y);
+ y = yorn(x, y);
+ (void) PTMakePoint(nx, y, &plist);
+ savebounds(nx, y);
+
+#ifdef UW_FASTSCAN
+ while (xscanf(file, &x, &y));
+#else
+ lastpoint = FALSE;
+ do {
+ char *cp = fgets(string, MAXSTRING, file);
+ if (0 /* nullptr */ == cp) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "premature end-of-file or error"
+ " reading input; giving up on this"
+ " picture");
+ return(elist);
+ }
+ lineno++;
+ if (string[0] == '*') { /* SUN gremlin file */
+ lastpoint = TRUE;
+ } else {
+ if (!sscanf(string, "%lf%lf", &x, &y)) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "expected coordinate pair, got"
+ " '%1'; giving up on this"
+ " picture", string);
+ return(elist);
+ }
+ if ((x == -1.00 && y == -1.00) && (!SUNFILE))
+ lastpoint = TRUE;
+ else {
+ if (compatibility_flag)
+ savebounds(xorn(x, y), yorn(x, y));
+ }
+ }
+ } while (!lastpoint);
+#endif /* UW_FASTSCAN */
+ } else { /* not TEXT element */
+#ifdef UW_FASTSCAN
+ do {
+ nx = xorn(x, y);
+ y = yorn(x, y);
+ (void) PTMakePoint(nx, y, &plist);
+ savebounds(nx, y);
+ } while (xscanf(file, &x, &y));
+#else
+ lastpoint = FALSE;
+ while (!lastpoint) {
+ nx = xorn(x, y);
+ y = yorn(x, y);
+ (void) PTMakePoint(nx, y, &plist);
+ savebounds(nx, y);
+
+ char *cp = fgets(string, MAXSTRING, file);
+ if (0 /* nullptr */ == cp) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "premature end-of-file or error"
+ " reading input; giving up on this"
+ " picture");
+ return(elist);
+ }
+ lineno++;
+ if (string[0] == '*') { /* SUN gremlin file */
+ lastpoint = TRUE;
+ } else {
+ (void) sscanf(string, "%lf%lf", &x, &y);
+ if ((x == -1.00 && y == -1.00) && (!SUNFILE))
+ lastpoint = TRUE;
+ }
+ }
+#endif /* UW_FASTSCAN */
+ }
+ nitems = fscanf(file, "%d%d\n", &brush, &size);
+ if (nitems != 2) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "malformed input; giving up on this"
+ " picture");
+ return (elist);
+ }
+ lineno++;
+ nitems = fscanf(file, "%d", &len); /* text length */
+ if (nitems != 1) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "malformed input; giving up on this"
+ " picture");
+ return (elist);
+ }
+ (void) getc(file); /* eat blank */
+ lineno++; /* advance line counter early */
+ if (len < 0) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "length claimed for text is nonsense:"
+ " '%1'; giving up on this picture",
+ len);
+ return (elist);
+ }
+ txt = (char *) grnmalloc((unsigned) len + 1, "element text");
+ for (i = 0; i < len; ++i) { /* read text */
+ int c = getc(file);
+ if (c == EOF)
+ break;
+ txt[i] = c;
+ }
+ if (feof(file)) {
+ error_with_file_and_line(gremlinfile, lineno,
+ "end of file while reading text of"
+ " length %1; giving up on this"
+ " picture", len);
+ return (elist);
+ }
+ txt[len] = '\0';
+ (void) DBCreateElt(type, plist, brush, size, txt, &elist);
+ } /* end else */
+ } /* end while not done */ ;
+ return (elist);
+} /* end DBRead */
+
+
+/*
+ * Interpret element type in string s.
+ * Old file format consisted of integer element types.
+ * New file format has literal names for element types.
+ */
+int
+DBGetType(char *s)
+{
+ if (isdigit(s[0]) || (s[0] == '-')) /* old element format or EOF */
+ return (atoi(s));
+
+ switch (s[0]) {
+ case 'P':
+ return (POLYGON);
+ case 'V':
+ return (VECTOR);
+ case 'A':
+ return (ARC);
+ case 'C':
+ if (s[1] == 'U') {
+ if (s[5] == '\n')
+ return (CURVE);
+ switch (s[7]) {
+ case 'S':
+ return(BSPLINE);
+ case 'E':
+ warning_with_file_and_line(gremlinfile, lineno,
+ "using B-spline for Bezier curve");
+ return(BSPLINE);
+ default:
+ return(CURVE);
+ }
+ }
+ switch (s[4]) {
+ case 'L':
+ return (CENTLEFT);
+ case 'C':
+ return (CENTCENT);
+ case 'R':
+ return (CENTRIGHT);
+ default:
+ error_with_file_and_line(gremlinfile, lineno,
+ "unknown element type '%1'", s);
+ return -1;
+ }
+ case 'B':
+ switch (s[3]) {
+ case 'L':
+ return (BOTLEFT);
+ case 'C':
+ return (BOTCENT);
+ case 'R':
+ return (BOTRIGHT);
+ default:
+ error_with_file_and_line(gremlinfile, lineno,
+ "unknown element type '%1'", s);
+ return -1;
+ }
+ case 'T':
+ switch (s[3]) {
+ case 'L':
+ return (TOPLEFT);
+ case 'C':
+ return (TOPCENT);
+ case 'R':
+ return (TOPRIGHT);
+ default:
+ error_with_file_and_line(gremlinfile, lineno,
+ "unknown element type '%1'", s);
+ return -1;
+ }
+ default:
+ error_with_file_and_line(gremlinfile, lineno,
+ "unknown element type '%1'", s);
+ return -1;
+ }
+}
+
+#ifdef UW_FASTSCAN
+/*
+ * Optimization hack added by solomon@crys.wisc.edu, 12/2/86.
+ * A huge fraction of the time was spent reading floating point numbers
+ * from the input file, but the numbers always have the format 'ddd.dd'.
+ * Thus the following special-purpose version of fscanf.
+ *
+ * xscanf(f,xp,yp) does roughly what fscanf(f,"%f%f",xp,yp) does except:
+ * -the next piece of input must be of the form
+ * <space>* <digit>*'.'<digit>* <space>* <digit>*'.'<digit>*
+ * -xscanf eats the character following the second number
+ * -xscanf returns 0 for "end-of-data" indication, 1 otherwise, where
+ * end-of-data is signalled by a '*' [in which case the rest of the
+ * line is gobbled], or by '-1.00 -1.00' [but only if !SUNFILE].
+ */
+int
+xscanf(FILE *f,
+ double *xp,
+ double *yp)
+{
+ int c, i, j, m, frac;
+ int iscale = 1, jscale = 1; /* x = i/scale, y=j/jscale */
+
+ while ((c = getc(f)) == ' ');
+ if (c == '*') {
+ while ((c = getc(f)) != '\n');
+ return 0;
+ }
+ i = m = frac = 0;
+ while (isdigit(c) || c == '.' || c == '-') {
+ if (c == '-') {
+ m++;
+ c = getc(f);
+ continue;
+ }
+ if (c == '.')
+ frac = 1;
+ else {
+ if (frac)
+ iscale *= 10;
+ i = 10 * i + c - '0';
+ }
+ c = getc(f);
+ }
+ if (m)
+ i = -i;
+ *xp = (double) i / (double) iscale;
+
+ while ((c = getc(f)) == ' ');
+ j = m = frac = 0;
+ while (isdigit(c) || c == '.' || c == '-') {
+ if (c == '-') {
+ m++;
+ c = getc(f);
+ continue;
+ }
+ if (c == '.')
+ frac = 1;
+ else {
+ if (frac)
+ jscale *= 10;
+ j = 10 * j + c - '0';
+ }
+ c = getc(f);
+ }
+ if (m)
+ j = -j;
+ *yp = (double) j / (double) jscale;
+ return (SUNFILE || i != -iscale || j != -jscale);
+}
+#endif /* UW_FASTSCAN */
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/grn/hgraph.cpp b/src/preproc/grn/hgraph.cpp
new file mode 100644
index 0000000..9ed81a4
--- /dev/null
+++ b/src/preproc/grn/hgraph.cpp
@@ -0,0 +1,1060 @@
+/* Last non-groff version: hgraph.c 1.14 (Berkeley) 84/11/27
+ *
+ * This file contains the graphics routines for converting gremlin
+ * pictures to troff input.
+ */
+
+#include "lib.h"
+
+#include "gprint.h"
+
+#define MAXVECT 40
+#define MAXPOINTS 200
+#define LINELENGTH 1
+#define PointsPerInterval 64
+#define pi 3.14159265358979324
+#define twopi (2.0 * pi)
+#define len(a, b) groff_hypot((double)(b.x-a.x), \
+ (double)(b.y-a.y))
+
+
+extern int dotshifter; /* for the length of dotted curves */
+
+extern int style[]; /* line and character styles */
+extern double thick[];
+extern char *tfont[];
+extern int tsize[];
+extern int stipple_index[]; /* stipple font idx for stipples 0-16 */
+extern char *stipple; /* stipple type (cf or ug) */
+
+
+extern double troffscale; /* imports from main.c */
+extern double linethickness;
+extern int linmod;
+extern int lastx;
+extern int lasty;
+extern int lastyline;
+extern int ytop;
+extern int ybottom;
+extern int xleft;
+extern int xright;
+extern enum E {
+ OUTLINE, FILL, BOTH
+} polyfill;
+
+extern double adj1;
+extern double adj2;
+extern double adj3;
+extern double adj4;
+extern int res;
+
+void HGSetFont(int font, int size);
+void HGPutText(int justify, POINT pnt, char *string);
+void HGSetBrush(int mode);
+void tmove2(int px, int py);
+void doarc(POINT cp, POINT sp, int angle);
+void tmove(POINT * ptr);
+void cr();
+void drawwig(POINT * ptr, int type);
+void HGtline(int x1, int y1);
+void deltax(double x);
+void deltay(double y);
+void HGArc(int cx, int cy, int px, int py, int angle);
+void picurve(int *x, int *y, int npts);
+void HGCurve(int *x, int *y, int numpoints);
+void Parameterize(int x[], int y[], double h[], int n);
+void PeriodicSpline(double h[], int z[],
+ double dz[], double d2z[], double d3z[],
+ int npoints);
+void NaturalEndSpline(double h[], int z[],
+ double dz[], double d2z[], double d3z[],
+ int npoints);
+
+
+
+/*--------------------------------------------------------------------*
+ | Routine: HGPrintElt (element_pointer, baseline)
+ |
+ | Results: Examines a picture element and calls the appropriate
+ | routine(s) to print them according to their type. After
+ | the picture is drawn, current position is (lastx,lasty).
+ *--------------------------------------------------------------------*/
+
+void
+HGPrintElt(ELT *element,
+ int /* baseline */)
+{
+ POINT *p1;
+ POINT *p2;
+ int length;
+ int graylevel;
+
+ if (!DBNullelt(element) && !Nullpoint((p1 = element->ptlist))) {
+ /* p1 always has first point */
+ if (TEXT(element->type)) {
+ HGSetFont(element->brushf, element->size);
+ switch (element->size) {
+ case 1:
+ p1->y += adj1;
+ break;
+ case 2:
+ p1->y += adj2;
+ break;
+ case 3:
+ p1->y += adj3;
+ break;
+ case 4:
+ p1->y += adj4;
+ break;
+ default:
+ break;
+ }
+ HGPutText(element->type, *p1, element->textpt);
+ } else {
+ if (element->brushf) /* if there is a brush, the */
+ HGSetBrush(element->brushf); /* graphics need it set */
+
+ switch (element->type) {
+
+ case ARC:
+ p2 = PTNextPoint(p1);
+ tmove(p2);
+ doarc(*p1, *p2, element->size);
+ cr();
+ break;
+
+ case CURVE:
+ length = 0; /* keep track of line length */
+ drawwig(p1, CURVE);
+ cr();
+ break;
+
+ case BSPLINE:
+ length = 0; /* keep track of line length */
+ drawwig(p1, BSPLINE);
+ cr();
+ break;
+
+ case VECTOR:
+ length = 0; /* keep track of line length so */
+ tmove(p1); /* single lines don't get long */
+ while (!Nullpoint((p1 = PTNextPoint(p1)))) {
+ HGtline((int) (p1->x * troffscale),
+ (int) (p1->y * troffscale));
+ if (length++ > LINELENGTH) {
+ length = 0;
+ printf("\\\n");
+ }
+ } /* end while */
+ cr();
+ break;
+
+ case POLYGON:
+ {
+ /* brushf = style of outline; size = color of fill:
+ * on first pass (polyfill=FILL), do the interior using 'P'
+ * unless size=0
+ * on second pass (polyfill=OUTLINE), do the outline using a
+ * series of vectors. It might make more sense to use \D'p
+ * ...', but there is no uniform way to specify a 'fill
+ * character' that prints as 'no fill' on all output
+ * devices (and stipple fonts).
+ * If polyfill=BOTH, just use the \D'p ...' command.
+ */
+ double firstx = p1->x;
+ double firsty = p1->y;
+
+ length = 0; /* keep track of line length so */
+ /* single lines don't get long */
+
+ if (polyfill == FILL || polyfill == BOTH) {
+ /* do the interior */
+ char command = (polyfill == BOTH && element->brushf)
+ ? 'p' : 'P';
+
+ /* include outline, if there is one and */
+ /* the -p flag was set */
+
+ /* switch based on what gremlin gives */
+ switch (element->size) {
+ case 1:
+ graylevel = 1;
+ break;
+ case 3:
+ graylevel = 2;
+ break;
+ case 12:
+ graylevel = 3;
+ break;
+ case 14:
+ graylevel = 4;
+ break;
+ case 16:
+ graylevel = 5;
+ break;
+ case 19:
+ graylevel = 6;
+ break;
+ case 21:
+ graylevel = 7;
+ break;
+ case 23:
+ graylevel = 8;
+ break;
+ default: /* who's giving something else? */
+ graylevel = NSTIPPLES;
+ break;
+ }
+ /* int graylevel = element->size; */
+
+ if (graylevel < 0)
+ break;
+ if (graylevel > NSTIPPLES)
+ graylevel = NSTIPPLES;
+ printf("\\D'Fg %.3f'",
+ double(1000 - stipple_index[graylevel]) / 1000.0);
+ cr();
+ tmove(p1);
+ printf("\\D'%c", command);
+
+ while (!Nullpoint((PTNextPoint(p1)))) {
+ p1 = PTNextPoint(p1);
+ deltax((double) p1->x);
+ deltay((double) p1->y);
+ if (length++ > LINELENGTH) {
+ length = 0;
+ printf("\\\n");
+ }
+ } /* end while */
+
+ /* close polygon if not done so by user */
+ if ((firstx != p1->x) || (firsty != p1->y)) {
+ deltax((double) firstx);
+ deltay((double) firsty);
+ }
+ putchar('\'');
+ cr();
+ break;
+ }
+ /* else polyfill == OUTLINE; only draw the outline */
+ if (!(element->brushf))
+ break;
+ length = 0; /* keep track of line length */
+ tmove(p1);
+
+ while (!Nullpoint((PTNextPoint(p1)))) {
+ p1 = PTNextPoint(p1);
+ HGtline((int) (p1->x * troffscale),
+ (int) (p1->y * troffscale));
+ if (length++ > LINELENGTH) {
+ length = 0;
+ printf("\\\n");
+ }
+ } /* end while */
+
+ /* close polygon if not done so by user */
+ if ((firstx != p1->x) || (firsty != p1->y)) {
+ HGtline((int) (firstx * troffscale),
+ (int) (firsty * troffscale));
+ }
+ cr();
+ break;
+ } /* end case POLYGON */
+ } /* end switch */
+ } /* end else Text */
+ } /* end if */
+} /* end PrintElt */
+
+
+/*---------------------------------------------------------------------*
+ | Routine: HGPutText (justification, position_point, string)
+ |
+ | Results: Given the justification, a point to position with, and a
+ | string to put, HGPutText first sends the string into a
+ | diversion, moves to the positioning point, then outputs
+ | local vertical and horizontal motions as needed to
+ | justify the text. After all motions are done, the
+ | diversion is printed out.
+ *--------------------------------------------------------------------*/
+
+void
+HGPutText(int justify,
+ POINT pnt,
+ char *string)
+{
+ int savelasty = lasty; /* vertical motion for text is to be */
+ /* ignored. Save current y here */
+
+ printf(".nr g8 \\n(.d\n"); /* save current vertical position. */
+ printf(".ds g9 \""); /* define string containing the text. */
+ while (*string) { /* put out the string */
+ if (*string == '\\' &&
+ *(string + 1) == '\\') { /* one character at a */
+ printf("\\\\\\"); /* time replacing // */
+ string++; /* by //// to prevent */
+ } /* interpretation at */
+ printf("%c", *(string++)); /* printout time */
+ }
+ printf("\n");
+
+ tmove(&pnt); /* move to positioning point */
+
+ switch (justify) {
+ /* local vertical motions--the numbers here are used to be
+ somewhat compatible with gprint */
+ case CENTLEFT:
+ case CENTCENT:
+ case CENTRIGHT:
+ printf("\\v'0.85n'"); /* down half */
+ break;
+
+ case TOPLEFT:
+ case TOPCENT:
+ case TOPRIGHT:
+ printf("\\v'1.7n'"); /* down whole */
+ }
+
+ switch (justify) {
+ /* local horizontal motions */
+ case BOTCENT:
+ case CENTCENT:
+ case TOPCENT:
+ printf("\\h'-\\w'\\*(g9'u/2u'"); /* back half */
+ break;
+
+ case BOTRIGHT:
+ case CENTRIGHT:
+ case TOPRIGHT:
+ printf("\\h'-\\w'\\*(g9'u'"); /* back whole */
+ }
+
+ printf("\\&\\*(g9\n"); /* now print the text. */
+ printf(".sp |\\n(g8u\n"); /* restore vertical position */
+ lasty = savelasty; /* vertical position restored to */
+ lastx = xleft; /* where it was before text, also */
+ /* horizontal is at left */
+} /* end HGPutText */
+
+
+/*--------------------------------------------------------------------*
+ | Routine: doarc (center_point, start_point, angle)
+ |
+ | Results: Produces either drawarc command or a drawcircle command
+ | depending on the angle needed to draw through.
+ *--------------------------------------------------------------------*/
+
+void
+doarc(POINT cp,
+ POINT sp,
+ int angle)
+{
+ if (angle) /* arc with angle */
+ HGArc((int) (cp.x * troffscale), (int) (cp.y * troffscale),
+ (int) (sp.x * troffscale), (int) (sp.y * troffscale), angle);
+ else /* a full circle (angle == 0) */
+ HGArc((int) (cp.x * troffscale), (int) (cp.y * troffscale),
+ (int) (sp.x * troffscale), (int) (sp.y * troffscale), 0);
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: HGSetFont (font_number, Point_size)
+ |
+ | Results: ALWAYS outputs a .ft and .ps directive to troff. This
+ | is done because someone may change stuff inside a text
+ | string. Changes thickness back to default thickness.
+ | Default thickness depends on font and point size.
+ *--------------------------------------------------------------------*/
+
+void
+HGSetFont(int font,
+ int size)
+{
+ printf(".ft %s\n"
+ ".ps %d\n", tfont[font - 1], tsize[size - 1]);
+ linethickness = DEFTHICK;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: HGSetBrush (line_mode)
+ |
+ | Results: Generates the troff commands to set up the line width
+ | and style of subsequent lines. Does nothing if no
+ | change is needed.
+ |
+ | Side Efct: Sets 'linmode' and 'linethickness'.
+ *--------------------------------------------------------------------*/
+
+void
+HGSetBrush(int mode)
+{
+ int printed = 0;
+
+ if (linmod != style[--mode]) {
+ /* Groff doesn't understand \Ds, so we take it out */
+ /* printf ("\\D's %du'", linmod = style[mode]); */
+ linmod = style[mode];
+ printed = 1;
+ }
+ if (linethickness != thick[mode]) {
+ linethickness = thick[mode];
+ printf("\\h'-%.2fp'\\D't %.2fp'", linethickness, linethickness);
+ printed = 1;
+ }
+ if (printed)
+ cr();
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: deltax (x_destination)
+ |
+ | Results: Scales and outputs a number for delta x (with a leading
+ | space) given 'lastx' and x_destination.
+ |
+ | Side Efct: Resets 'lastx' to x_destination.
+ *--------------------------------------------------------------------*/
+
+void
+deltax(double x)
+{
+ int ix = (int) (x * troffscale);
+
+ printf(" %du", ix - lastx);
+ lastx = ix;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: deltay (y_destination)
+ |
+ | Results: Scales and outputs a number for delta y (with a leading
+ | space) given 'lastyline' and y_destination.
+ |
+ | Side Efct: Resets 'lastyline' to y_destination. Since 'line'
+ | vertical motions don't affect 'page' ones, 'lasty' isn't
+ | updated.
+ *--------------------------------------------------------------------*/
+
+void
+deltay(double y)
+{
+ int iy = (int) (y * troffscale);
+
+ printf(" %du", iy - lastyline);
+ lastyline = iy;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: tmove2 (px, py)
+ |
+ | Results: Produces horizontal and vertical moves for troff given
+ | the pair of points to move to and knowing the current
+ | position. Also puts out a horizontal move to start the
+ | line. This is a variation without the .sp command.
+ *--------------------------------------------------------------------*/
+
+void
+tmove2(int px,
+ int py)
+{
+ int dx;
+ int dy;
+
+ if ((dy = py - lasty)) {
+ printf("\\v'%du'", dy);
+ }
+ lastyline = lasty = py; /* lasty is always set to current */
+ if ((dx = px - lastx)) {
+ printf("\\h'%du'", dx);
+ lastx = px;
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: tmove (point_pointer)
+ |
+ | Results: Produces horizontal and vertical moves for troff given
+ | the pointer of a point to move to and knowing the
+ | current position. Also puts out a horizontal move to
+ | start the line.
+ *--------------------------------------------------------------------*/
+
+void
+tmove(POINT * ptr)
+{
+ int ix = (int) (ptr->x * troffscale);
+ int iy = (int) (ptr->y * troffscale);
+ int dx;
+ int dy;
+
+ if ((dy = iy - lasty)) {
+ printf(".sp %du\n", dy);
+ }
+ lastyline = lasty = iy; /* lasty is always set to current */
+ if ((dx = ix - lastx)) {
+ printf("\\h'%du'", dx);
+ lastx = ix;
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: cr ( )
+ |
+ | Results: Ends off an input line. '.sp -1' is also added to
+ | counteract the vertical move done at the end of text
+ | lines.
+ |
+ | Side Efct: Sets 'lastx' to 'xleft' for troff's return to left
+ | margin.
+ *--------------------------------------------------------------------*/
+
+void
+cr()
+{
+ printf("\n.sp -1\n");
+ lastx = xleft;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: line ( )
+ |
+ | Results: Draws a single solid line to (x,y).
+ *--------------------------------------------------------------------*/
+
+void
+line(int px,
+ int py)
+{
+ printf("\\D'l");
+ printf(" %du", px - lastx);
+ printf(" %du'", py - lastyline);
+ lastx = px;
+ lastyline = lasty = py;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: drawwig (ptr, type)
+ |
+ | Results: The point sequence found in the structure pointed by ptr
+ | is placed in integer arrays for further manipulation by
+ | the existing routing. With the corresponding type
+ | parameter, either picurve or HGCurve are called.
+ *--------------------------------------------------------------------*/
+
+void
+drawwig(POINT * ptr,
+ int type)
+{
+ int npts; /* point list index */
+ int x[MAXPOINTS], y[MAXPOINTS]; /* point list */
+
+ for (npts = 1; !Nullpoint(ptr); ptr = PTNextPoint(ptr), npts++) {
+ x[npts] = (int) (ptr->x * troffscale);
+ y[npts] = (int) (ptr->y * troffscale);
+ }
+ if (--npts) {
+ if (type == CURVE) /* Use the 2 different types of curves */
+ HGCurve(&x[0], &y[0], npts);
+ else
+ picurve(&x[0], &y[0], npts);
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: HGArc (xcenter, ycenter, xstart, ystart, angle)
+ |
+ | Results: This routine plots an arc centered about (cx, cy)
+ | counter-clockwise starting from the point (px, py)
+ | through 'angle' degrees. If angle is 0, a full circle
+ | is drawn. It does so by creating a draw-path around the
+ | arc whose density of points depends on the size of the
+ | arc.
+ *--------------------------------------------------------------------*/
+
+void
+HGArc(int cx,
+ int cy,
+ int px,
+ int py,
+ int angle)
+{
+ double xs, ys, resolution, fullcircle;
+ int m;
+ int mask;
+ int extent;
+ int nx;
+ int ny;
+ int length;
+ double epsilon;
+
+ xs = px - cx;
+ ys = py - cy;
+
+ length = 0;
+
+ resolution = (1.0 + groff_hypot(xs, ys) / res) * PointsPerInterval;
+ /* mask = (1 << (int) log10(resolution + 1.0)) - 1; */
+ (void) frexp(resolution, &m); /* more elegant than log10 */
+ for (mask = 1; mask < m; mask = mask << 1);
+ mask -= 1;
+
+ epsilon = 1.0 / resolution;
+ fullcircle = (2.0 * pi) * resolution;
+ if (angle == 0)
+ extent = (int) fullcircle;
+ else
+ extent = (int) (angle * fullcircle / 360.0);
+
+ HGtline(px, py);
+ while (--extent >= 0) {
+ xs += epsilon * ys;
+ nx = cx + (int) (xs + 0.5);
+ ys -= epsilon * xs;
+ ny = cy + (int) (ys + 0.5);
+ if (!(extent & mask)) {
+ HGtline(nx, ny); /* put out a point on circle */
+ if (length++ > LINELENGTH) {
+ length = 0;
+ printf("\\\n");
+ }
+ }
+ } /* end for */
+} /* end HGArc */
+
+
+/*--------------------------------------------------------------------*
+ | Routine: picurve (xpoints, ypoints, num_of_points)
+ |
+ | Results: Draws a curve delimited by (not through) the line
+ | segments traced by (xpoints, ypoints) point list. This
+ | is the 'Pic'-style curve.
+ *--------------------------------------------------------------------*/
+
+void
+picurve(int *x,
+ int *y,
+ int npts)
+{
+ int nseg; /* effective resolution for each curve */
+ int xp; /* current point (and temporary) */
+ int yp;
+ int pxp, pyp; /* previous point (to make lines from) */
+ int i; /* inner curve segment traverser */
+ int length = 0;
+ double w; /* position factor */
+ double t1, t2, t3; /* calculation temps */
+
+ if (x[1] == x[npts] && y[1] == y[npts]) {
+ x[0] = x[npts - 1]; /* if the lines' ends meet, make */
+ y[0] = y[npts - 1]; /* sure the curve meets */
+ x[npts + 1] = x[2];
+ y[npts + 1] = y[2];
+ } else { /* otherwise, make the ends of the */
+ x[0] = x[1]; /* curve touch the ending points of */
+ y[0] = y[1]; /* the line segments */
+ x[npts + 1] = x[npts];
+ y[npts + 1] = y[npts];
+ }
+
+ pxp = (x[0] + x[1]) / 2; /* make the last point pointers */
+ pyp = (y[0] + y[1]) / 2; /* point to the start of the 1st line */
+ tmove2(pxp, pyp);
+
+ for (; npts--; x++, y++) { /* traverse the line segments */
+ xp = x[0] - x[1];
+ yp = y[0] - y[1];
+ nseg = (int) groff_hypot((double) xp, (double) yp);
+ xp = x[1] - x[2];
+ yp = y[1] - y[2];
+ /* 'nseg' is the number of line */
+ /* segments that will be drawn for */
+ /* each curve segment. */
+ nseg = (int) ((double) (nseg + (int) groff_hypot((double) xp,
+ (double) yp)) /
+ res * PointsPerInterval);
+
+ for (i = 1; i < nseg; i++) {
+ w = (double) i / (double) nseg;
+ t1 = w * w;
+ t3 = t1 + 1.0 - (w + w);
+ t2 = 2.0 - (t3 + t1);
+ xp = (((int) (t1 * x[2] + t2 * x[1] + t3 * x[0])) + 1) / 2;
+ yp = (((int) (t1 * y[2] + t2 * y[1] + t3 * y[0])) + 1) / 2;
+
+ HGtline(xp, yp);
+ if (length++ > LINELENGTH) {
+ length = 0;
+ printf("\\\n");
+ }
+ }
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: HGCurve(xpoints, ypoints, num_points)
+ |
+ | Results: This routine generates a smooth curve through a set of
+ | points. The method used is the parametric spline curve
+ | on unit knot mesh described in 'Spline Curve Techniques'
+ | by Patrick Baudelaire, Robert Flegal, and Robert Sproull
+ | -- Xerox Parc.
+ *--------------------------------------------------------------------*/
+
+void
+HGCurve(int *x,
+ int *y,
+ int numpoints)
+{
+ double h[MAXPOINTS], dx[MAXPOINTS], dy[MAXPOINTS];
+ double d2x[MAXPOINTS], d2y[MAXPOINTS], d3x[MAXPOINTS], d3y[MAXPOINTS];
+ double t, t2, t3;
+ int j;
+ int k;
+ int nx;
+ int ny;
+ int lx, ly;
+ int length = 0;
+
+ lx = x[1];
+ ly = y[1];
+ tmove2(lx, ly);
+
+ /*
+ * Solve for derivatives of the curve at each point separately for x
+ * and y (parametric).
+ */
+ Parameterize(x, y, h, numpoints);
+
+ /* closed curve */
+ if ((x[1] == x[numpoints]) && (y[1] == y[numpoints])) {
+ PeriodicSpline(h, x, dx, d2x, d3x, numpoints);
+ PeriodicSpline(h, y, dy, d2y, d3y, numpoints);
+ } else {
+ NaturalEndSpline(h, x, dx, d2x, d3x, numpoints);
+ NaturalEndSpline(h, y, dy, d2y, d3y, numpoints);
+ }
+
+ /*
+ * Generate the curve using the above information and
+ * PointsPerInterval vectors between each specified knot.
+ */
+
+ for (j = 1; j < numpoints; ++j) {
+ if ((x[j] == x[j + 1]) && (y[j] == y[j + 1]))
+ continue;
+ for (k = 0; k <= PointsPerInterval; ++k) {
+ t = (double) k *h[j] / (double) PointsPerInterval;
+ t2 = t * t;
+ t3 = t * t * t;
+ nx = x[j] + (int) (t * dx[j] + t2 * d2x[j] / 2 + t3 * d3x[j] / 6);
+ ny = y[j] + (int) (t * dy[j] + t2 * d2y[j] / 2 + t3 * d3y[j] / 6);
+ HGtline(nx, ny);
+ if (length++ > LINELENGTH) {
+ length = 0;
+ printf("\\\n");
+ }
+ } /* end for k */
+ } /* end for j */
+} /* end HGCurve */
+
+
+/*--------------------------------------------------------------------*
+ | Routine: Parameterize (xpoints, ypoints, hparams, num_points)
+ |
+ | Results: This routine calculates parametric values for use in
+ | calculating curves. The parametric values are returned
+ | in the array h. The values are an approximation of
+ | cumulative arc lengths of the curve (uses cord length).
+ | For additional information, see paper cited below.
+ *--------------------------------------------------------------------*/
+
+void
+Parameterize(int x[],
+ int y[],
+ double h[],
+ int n)
+{
+ int dx;
+ int dy;
+ int i;
+ int j;
+ double u[MAXPOINTS];
+
+ for (i = 1; i <= n; ++i) {
+ u[i] = 0;
+ for (j = 1; j < i; j++) {
+ dx = x[j + 1] - x[j];
+ dy = y[j + 1] - y[j];
+ /* Here was overflowing, so I changed it. */
+ /* u[i] += sqrt ((double) (dx * dx + dy * dy)); */
+ u[i] += groff_hypot((double) dx, (double) dy);
+ }
+ }
+ for (i = 1; i < n; ++i)
+ h[i] = u[i + 1] - u[i];
+} /* end Parameterize */
+
+
+/*--------------------------------------------------------------------*
+ | Routine: PeriodicSpline (h, z, dz, d2z, d3z, npoints)
+ |
+ | Results: This routine solves for the cubic polynomial to fit a
+ | spline curve to the points specified by the list of
+ | values. The curve generated is periodic. The
+ | algorithms for this curve are from the 'Spline Curve
+ | Techniques' paper cited above.
+ *--------------------------------------------------------------------*/
+
+void
+PeriodicSpline(double h[], /* parameterization */
+ int z[], /* point list */
+ double dz[], /* to return the 1st derivative */
+ double d2z[], /* 2nd derivative */
+ double d3z[], /* 3rd derivative */
+ int npoints) /* number of valid points */
+{
+ double d[MAXPOINTS];
+ double deltaz[MAXPOINTS], a[MAXPOINTS], b[MAXPOINTS];
+ double c[MAXPOINTS], r[MAXPOINTS], s[MAXPOINTS];
+ int i;
+
+ /* step 1 */
+ for (i = 1; i < npoints; ++i) {
+ deltaz[i] = h[i] ? ((double) (z[i + 1] - z[i])) / h[i] : 0;
+ }
+ h[0] = h[npoints - 1];
+ deltaz[0] = deltaz[npoints - 1];
+
+ /* step 2 */
+ for (i = 1; i < npoints - 1; ++i) {
+ d[i] = deltaz[i + 1] - deltaz[i];
+ }
+ d[0] = deltaz[1] - deltaz[0];
+
+ /* step 3a */
+ a[1] = 2 * (h[0] + h[1]);
+ b[1] = d[0];
+ c[1] = h[0];
+ for (i = 2; i < npoints - 1; ++i) {
+ a[i] = 2 * (h[i - 1] + h[i]) -
+ pow((double) h[i - 1], (double) 2.0) / a[i - 1];
+ b[i] = d[i - 1] - h[i - 1] * b[i - 1] / a[i - 1];
+ c[i] = -h[i - 1] * c[i - 1] / a[i - 1];
+ }
+
+ /* step 3b */
+ r[npoints - 1] = 1;
+ s[npoints - 1] = 0;
+ for (i = npoints - 2; i > 0; --i) {
+ r[i] = -(h[i] * r[i + 1] + c[i]) / a[i];
+ s[i] = (6 * b[i] - h[i] * s[i + 1]) / a[i];
+ }
+
+ /* step 4 */
+ d2z[npoints - 1] = (6 * d[npoints - 2] - h[0] * s[1]
+ - h[npoints - 1] * s[npoints - 2])
+ / (h[0] * r[1] + h[npoints - 1] * r[npoints - 2]
+ + 2 * (h[npoints - 2] + h[0]));
+ for (i = 1; i < npoints - 1; ++i) {
+ d2z[i] = r[i] * d2z[npoints - 1] + s[i];
+ }
+ d2z[npoints] = d2z[1];
+
+ /* step 5 */
+ for (i = 1; i < npoints; ++i) {
+ dz[i] = deltaz[i] - h[i] * (2 * d2z[i] + d2z[i + 1]) / 6;
+ d3z[i] = h[i] ? (d2z[i + 1] - d2z[i]) / h[i] : 0;
+ }
+} /* end PeriodicSpline */
+
+
+/*--------------------------------------------------------------------
+ | Routine: NaturalEndSpline (h, z, dz, d2z, d3z, npoints)
+ |
+ | Results: This routine solves for the cubic polynomial to fit a
+ | spline curve the points specified by the list of values.
+ | The algorithms for this curve are from the 'Spline Curve
+ | Techniques' paper cited above.
+ *--------------------------------------------------------------------*/
+
+void
+NaturalEndSpline(double h[], /* parameterization */
+ int z[], /* Point list */
+ double dz[], /* to return the 1st derivative */
+ double d2z[], /* 2nd derivative */
+ double d3z[], /* 3rd derivative */
+ int npoints) /* number of valid points */
+{
+ double d[MAXPOINTS];
+ double deltaz[MAXPOINTS], a[MAXPOINTS], b[MAXPOINTS];
+ int i;
+
+ /* step 1 */
+ for (i = 1; i < npoints; ++i) {
+ deltaz[i] = h[i] ? ((double) (z[i + 1] - z[i])) / h[i] : 0;
+ }
+ deltaz[0] = deltaz[npoints - 1];
+
+ /* step 2 */
+ for (i = 1; i < npoints - 1; ++i) {
+ d[i] = deltaz[i + 1] - deltaz[i];
+ }
+ d[0] = deltaz[1] - deltaz[0];
+
+ /* step 3 */
+ a[0] = 2 * (h[2] + h[1]);
+ b[0] = d[1];
+ for (i = 1; i < npoints - 2; ++i) {
+ a[i] = 2 * (h[i + 1] + h[i + 2]) -
+ pow((double) h[i + 1], (double) 2.0) / a[i - 1];
+ b[i] = d[i + 1] - h[i + 1] * b[i - 1] / a[i - 1];
+ }
+
+ /* step 4 */
+ d2z[npoints] = d2z[1] = 0;
+ for (i = npoints - 1; i > 1; --i) {
+ d2z[i] = (6 * b[i - 2] - h[i] * d2z[i + 1]) / a[i - 2];
+ }
+
+ /* step 5 */
+ for (i = 1; i < npoints; ++i) {
+ dz[i] = deltaz[i] - h[i] * (2 * d2z[i] + d2z[i + 1]) / 6;
+ d3z[i] = h[i] ? (d2z[i + 1] - d2z[i]) / h[i] : 0;
+ }
+} /* end NaturalEndSpline */
+
+
+/*--------------------------------------------------------------------*
+ | Routine: change (x_position, y_position, visible_flag)
+ |
+ | Results: As HGtline passes from the invisible to visible (or vice
+ | versa) portion of a line, change is called to either
+ | draw the line, or initialize the beginning of the next
+ | one. Change calls line to draw segments if visible_flag
+ | is set (which means we're leaving a visible area).
+ *--------------------------------------------------------------------*/
+
+void
+change(int x,
+ int y,
+ int vis)
+{
+ static int length = 0;
+
+ if (vis) { /* leaving a visible area, draw it. */
+ line(x, y);
+ if (length++ > LINELENGTH) {
+ length = 0;
+ printf("\\\n");
+ }
+ } else { /* otherwise entering one; remember */
+ /* beginning */
+ tmove2(x, y);
+ }
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: HGtline (xstart, ystart, xend, yend)
+ |
+ | Results: Draws a line from current position to (x1,y1) using
+ | line(x1, y1) to place individual segments of dotted or
+ | dashed lines.
+ *--------------------------------------------------------------------*/
+
+void
+HGtline(int x_1,
+ int y_1)
+{
+ int x_0 = lastx;
+ int y_0 = lasty;
+ int dx;
+ int dy;
+ int oldcoord;
+ int res1;
+ int visible;
+ int res2;
+ int xinc;
+ int yinc;
+ int dotcounter;
+
+ if (linmod == SOLID) {
+ line(x_1, y_1);
+ return;
+ }
+
+ /* for handling different resolutions */
+ dotcounter = linmod << dotshifter;
+
+ xinc = 1;
+ yinc = 1;
+ if ((dx = x_1 - x_0) < 0) {
+ xinc = -xinc;
+ dx = -dx;
+ }
+ if ((dy = y_1 - y_0) < 0) {
+ yinc = -yinc;
+ dy = -dy;
+ }
+ res1 = 0;
+ res2 = 0;
+ visible = 0;
+ if (dx >= dy) {
+ oldcoord = y_0;
+ while (x_0 != x_1) {
+ if ((x_0 & dotcounter) && !visible) {
+ change(x_0, y_0, 0);
+ visible = 1;
+ } else if (visible && !(x_0 & dotcounter)) {
+ change(x_0 - xinc, oldcoord, 1);
+ visible = 0;
+ }
+ if (res1 > res2) {
+ oldcoord = y_0;
+ res2 += dx - res1;
+ res1 = 0;
+ y_0 += yinc;
+ }
+ res1 += dy;
+ x_0 += xinc;
+ }
+ } else {
+ oldcoord = x_0;
+ while (y_0 != y_1) {
+ if ((y_0 & dotcounter) && !visible) {
+ change(x_0, y_0, 0);
+ visible = 1;
+ } else if (visible && !(y_0 & dotcounter)) {
+ change(oldcoord, y_0 - yinc, 1);
+ visible = 0;
+ }
+ if (res1 > res2) {
+ oldcoord = x_0;
+ res2 += dy - res1;
+ res1 = 0;
+ x_0 += xinc;
+ }
+ res1 += dx;
+ y_0 += yinc;
+ }
+ }
+ if (visible)
+ change(x_1, y_1, 1);
+ else
+ change(x_1, y_1, 0);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/grn/hpoint.cpp b/src/preproc/grn/hpoint.cpp
new file mode 100644
index 0000000..5ef0c0a
--- /dev/null
+++ b/src/preproc/grn/hpoint.cpp
@@ -0,0 +1,59 @@
+/* Last non-groff version: hpoint.c 1.1 84/10/08 */
+
+/*
+ * This file contains routines for manipulating the point data
+ * structures for the gremlin picture editor.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include "gprint.h"
+
+/* imports from main.cpp */
+extern void *grnmalloc(size_t size, const char *what);
+
+/*
+ * Return pointer to empty point list.
+ */
+POINT *
+PTInit()
+{
+ return ((POINT *) NULL);
+}
+
+
+/*
+ * This routine creates a new point with coordinates x and y and links
+ * it into the point list.
+ */
+POINT *
+PTMakePoint(double x,
+ double y,
+ POINT **pplist)
+{
+ POINT *pt;
+
+ if (Nullpoint(pt = *pplist)) { /* empty list */
+ *pplist = (POINT *) grnmalloc(sizeof(POINT), "initial point");
+ pt = *pplist;
+ } else {
+ while (!Nullpoint(pt->nextpt))
+ pt = pt->nextpt;
+ pt->nextpt = (POINT *) grnmalloc(sizeof(POINT), "subsequent point");
+ pt = pt->nextpt;
+ }
+
+ pt->x = x;
+ pt->y = y;
+ pt->nextpt = PTInit();
+ return (pt);
+} /* end PTMakePoint */
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/grn/main.cpp b/src/preproc/grn/main.cpp
new file mode 100644
index 0000000..6d6d586
--- /dev/null
+++ b/src/preproc/grn/main.cpp
@@ -0,0 +1,977 @@
+/* Last non-groff version: main.c 1.23 (Berkeley) 85/08/05
+ *
+ * Adapted to GNU troff by Daniel Senderowicz 99/12/29.
+ *
+ * Further refinements by Werner Lemberg 00/02/20.
+ *
+ *
+ * This file contains the main and file system dependent routines for
+ * processing gremlin files into troff input. The program watches input
+ * go by to standard output, only interpreting things between .GS and
+ * .GE lines. Default values (font, size, scale, thickness) may be
+ * overridden with a 'default' command and are further overridden by
+ * commands in the input.
+ *
+ * Inside the GS and GE, commands are accepted to reconfigure the
+ * picture. At most one command may reside on each line, and each
+ * command is followed by a parameter separated by white space. The
+ * commands are as follows, and may be abbreviated down to one character
+ * (with exception of 'scale' and 'stipple' down to "sc" and "st") and
+ * may be upper or lower case.
+ *
+ * default - Make all settings in the current
+ * .GS/.GE the global defaults.
+ * Height, width and file are NOT
+ * saved.
+ * 1, 2, 3, 4 - Set size 1, 2, 3, or 4 (followed
+ * by an integer point size).
+ * roman, italics, bold, special - Set gremlin's fonts to any other
+ * troff font (1 or 2 characters).
+ * stipple, l - Use a stipple font for polygons.
+ * Arg is troff font name. No
+ * default. Can use only one stipple
+ * font per picture. (See below for
+ * stipple font index.)
+ * scale, x - Scale is IN ADDITION to the global
+ * scale factor from the default.
+ * pointscale - Turn on scaling point sizes to
+ * match 'scale' commands. (Optional
+ * operand 'off' to turn it off.)
+ * narrow, medium, thick - Set widths of lines.
+ * file - Set the file name to read the
+ * gremlin picture from. If the file
+ * isn't in the current directory,
+ * the gremlin library is tried.
+ * width, height - These two commands override any
+ * scaling factor that is in effect,
+ * and forces the picture to fit into
+ * either the height or width
+ * specified, whichever makes the
+ * picture smaller. The operand for
+ * these two commands is a
+ * floating-point number in units of
+ * inches.
+ * l<nn> (integer <nn>) - Set association between stipple
+ * <nn> and a stipple 'character'.
+ * <nn> must be in the range 0 to
+ * NSTIPPLES (16) inclusive. The
+ * integer operand is an index in the
+ * stipple font selected. Valid cf
+ * (cifplot) indices are 1-32
+ * (although 24 is not defined),
+ * valid ug (unigrafix) indices are
+ * 1-14, and valid gs (gray scale)
+ * indices are 0-16. Nonetheless,
+ * any number between 0 and 255 is
+ * accepted since new stipple fonts
+ * may be added. An integer operand
+ * is required.
+ *
+ * Troff number registers used: g1 through g9. g1 is the width of the
+ * picture, and g2 is the height. g3, and g4, save information, g8 and
+ * g9 are used for text processing and g5-g7 are reserved.
+ */
+
+
+#include "lib.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h> // errno
+#include "gprint.h"
+
+#include "device.h"
+#include "font.h"
+#include "searchpath.h"
+#include "macropath.h"
+
+#include "errarg.h"
+#include "error.h"
+#include "defs.h"
+
+extern "C" const char *Version_string;
+
+/* database imports */
+
+extern void HGPrintElt(ELT *element, int baseline);
+extern ELT *DBInit();
+extern ELT *DBRead(FILE *file);
+extern POINT *PTInit();
+extern POINT *PTMakePoint(double x, double y, POINT **pplist);
+
+#define INIT_FILE_SIZE 50 /* Initial sz of file array from cmd line. */
+#define FILE_SIZE_INCR 50 /* Amount to increase array of files by. */
+
+#define SUN_SCALEFACTOR 0.70
+
+/* #define DEFSTIPPLE "gs" */
+#define DEFSTIPPLE "cf"
+/*
+ * This grn implementation emits '.st' requests to control stipple
+ * effects, but groff does not (currently) support any such request.
+ *
+ * This hack disables the emission of such requests, without destroying
+ * the infrastructure necessary to support the feature in the future; to
+ * enable the emission of '.st' requests, at a future date when groff
+ * can support them, simply rewrite the following #define as:
+ *
+ * #define USE_ST_REQUEST stipple
+ *
+ * with accompanying comment: "emit '.st' requests as required".
+ */
+#define USE_ST_REQUEST 0 /* never emit '.st' requests */
+
+#define MAXINLINE 100 /* input line length */
+
+#define SCREENtoINCH 0.02 /* scaling factor, screen to inches */
+
+#define BIG 999999999999.0 /* unwieldy large floating number */
+
+
+/* static char sccsid[] = "@(#) (Berkeley) 8/5/85, 12/28/99"; */
+
+int res; /* the printer's resolution goes here */
+
+int dotshifter; /* for the length of dotted curves */
+
+double linethickness; /* brush styles */
+int linmod;
+int lastx; /* point registers for printing */
+int lasty; /* elements */
+int lastyline; /* A line's vertical position is NOT */
+ /* the same after that line is over, */
+ /* so for a line of drawing commands, */
+ /* vertical spacing is kept in */
+ /* lastyline. */
+
+/* These are the default fonts, sizes, line styles, */
+/* and thicknesses. They can be modified from a */
+/* 'default' command and are reset each time the */
+/* start of a picture (.GS) is found. */
+
+const char *deffont[] =
+{"R", "I", "B", "S"};
+int defsize[] =
+{10, 16, 24, 36};
+/* #define BASE_THICKNESS 1.0 */
+#define BASE_THICKNESS 0.15
+double defthick[STYLES] =
+{1 * BASE_THICKNESS,
+ 1 * BASE_THICKNESS,
+ 5 * BASE_THICKNESS,
+ 1 * BASE_THICKNESS,
+ 1 * BASE_THICKNESS,
+ 3 * BASE_THICKNESS};
+
+/* int cf_stipple_index[NSTIPPLES + 1] = */
+/* {0, 1, 3, 12, 14, 16, 19, 21, 23}; */
+/* a logarithmic scale looks better than a linear one for gray shades */
+/* */
+/* int other_stipple_index[NSTIPPLES + 1] = */
+/* {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; */
+
+int cf_stipple_index[NSTIPPLES + 1] =
+{0, 18, 32, 56, 100, 178, 316, 562, 1000}; /* only 1-8 used */
+int other_stipple_index[NSTIPPLES + 1] =
+{0, 62, 125, 187, 250, 312, 375, 437, 500,
+ 562, 625, 687, 750, 812, 875, 937, 1000};
+
+/* int *defstipple_index = other_stipple_index; */
+int *defstipple_index = cf_stipple_index;
+
+int style[STYLES] =
+{DOTTED, DOTDASHED, SOLID, DASHED, SOLID, SOLID};
+double scale = 1.0; /* no scaling, default */
+int defpoint = 0; /* flag for point size scaling */
+char *defstipple = (char *) 0;
+enum E {
+ OUTLINE, FILL, BOTH
+} polyfill;
+
+/* flag to control filling of polygons */
+
+double adj1 = 0.0;
+double adj2 = 0.0;
+double adj3 = 0.0;
+double adj4 = 0.0;
+
+double thick[STYLES]; /* thicknesses set by defaults, then */
+ /* by commands */
+char *tfont[FONTS]; /* fonts originally set to deffont */
+ /* values, then optionally changed by */
+int tsize[SIZES]; /* commands inside grn */
+int stipple_index[NSTIPPLES + 1]; /* stipple font file indices */
+char *stipple;
+
+double xscale; /* scaling factor from individual pictures */
+double troffscale; /* scaling factor at output time */
+
+double width; /* user-request maximum width for picture */
+ /* (in inches) */
+double height; /* user-request height */
+int pointscale; /* flag for point size scaling */
+int setdefault; /* flag for a .GS/.GE to remember all */
+ /* settings */
+int sflag; /* -s flag: sort order (do polyfill first) */
+
+double toppoint; /* remember the picture */
+double bottompoint; /* bounds in these variables */
+double leftpoint;
+double rightpoint;
+
+int ytop; /* these are integer versions of the above */
+int ybottom; /* so not to convert each time they're used */
+int xleft;
+int xright;
+
+static int linenum = 0; /* line number of troff input file */
+char inputline[MAXINLINE]; /* spot to filter through the file */
+char *c1 = inputline; /* c1, c2, and c3 will be used to */
+char *c2 = inputline + 1; /* hunt for lines that begin with */
+char *c3 = inputline + 2; /* '.GS' by looking individually */
+char *c4 = inputline + 3; /* needed for compatibility mode */
+char GScommand[MAXINLINE]; /* put user's '.GS' command line here */
+char gremlinfile[MAXINLINE]; /* filename to use for a picture */
+int SUNFILE = FALSE; /* TRUE if SUN gremlin file */
+int compatibility_flag = FALSE; /* TRUE if in compatibility mode */
+
+
+void getres();
+int doinput(FILE *fp);
+void conv(FILE *fp, int baseline);
+void savestate();
+int has_polygon(ELT *elist);
+void interpret(char *line);
+
+void *
+grnmalloc(size_t size,
+ const char *what)
+{
+ void *ptr = 0;
+ ptr = malloc(size);
+ if (!ptr) {
+ fatal("memory allocation failed for %1: %2", what, strerror(errno));
+ }
+ return ptr;
+}
+
+void
+usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-Cs] [-M dir] [-F dir] [-T dev] [file ...]\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+
+/* Add a new file entry in the array, expanding array if needs be. */
+
+char **
+add_file(char **file,
+ char *new_file,
+ int *count,
+ int *cur_size)
+{
+ if (*count >= *cur_size) {
+ *cur_size += FILE_SIZE_INCR;
+ file = (char **) realloc(file, *cur_size * sizeof(char *));
+ if (file == NULL) {
+ fatal("unable to extend file array");
+ }
+ }
+ file[*count] = new_file;
+ *count += 1;
+
+ return file;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: main (argument_count, argument_pointer)
+ |
+ | Results: Parses the command line, accumulating input file names,
+ | then reads the inputs, passing it directly to output
+ | until a '.GS' line is read. Main then passes control to
+ | 'conv' to do the gremlin file conversions.
+ *--------------------------------------------------------------------*/
+
+int
+main(int argc,
+ char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+ program_name = argv[0];
+ FILE *fp;
+ int k;
+ char c;
+ int gfil = 0;
+ char **file = NULL;
+ int file_cur_size = INIT_FILE_SIZE;
+ char *operand(int *argcp, char ***argvp);
+
+ file = (char **) grnmalloc(file_cur_size * sizeof(char *),
+ "file array");
+ while (--argc) {
+ if (**++argv != '-')
+ file = add_file(file, *argv, &gfil, &file_cur_size);
+ else
+ switch (c = (*argv)[1]) {
+
+ case 0:
+ file = add_file(file, NULL, &gfil, &file_cur_size);
+ break;
+
+ case 'C': /* compatibility mode */
+ compatibility_flag = TRUE;
+ break;
+
+ case 'F': /* font path to find DESC */
+ font::command_line_font_dir(operand(&argc, &argv));
+ break;
+
+ case 'T': /* final output typesetter name */
+ device = operand(&argc, &argv);
+ break;
+
+ case 'M': /* set library directory */
+ macro_path.command_line_dir(operand(&argc, &argv));
+ break;
+
+ case 's': /* preserve order of elements */
+ sflag = 1;
+ break;
+
+ case '-':
+ if (strcmp(*argv,"--version")==0) {
+ case 'v':
+ printf("GNU grn (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ if (strcmp(*argv,"--help")==0) {
+ case '?':
+ usage(stdout);
+ exit(0);
+ break;
+ }
+ // fallthrough
+ default:
+ error("unknown switch: %1", c);
+ usage(stderr);
+ exit(1);
+ }
+ }
+
+ getres(); /* set the resolution for an output device */
+
+ if (gfil == 0) { /* no filename, use standard input */
+ file[0] = NULL;
+ gfil++;
+ }
+
+ for (k = 0; k < gfil; k++) {
+ if (file[k] != NULL) {
+ if ((fp = fopen(file[k], "r")) == NULL)
+ fatal("can't open %1", file[k]);
+ } else
+ fp = stdin;
+
+ while (doinput(fp)) {
+ if (*c1 == '.' && *c2 == 'G' && *c3 == 'S') {
+ if (compatibility_flag ||
+ *c4 == '\n' || *c4 == ' ' || *c4 == '\0')
+ conv(fp, linenum);
+ else
+ fputs(inputline, stdout);
+ } else
+ fputs(inputline, stdout);
+ }
+ }
+
+ return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: char * operand (& argc, & argv)
+ |
+ | Results: Returns address of the operand given with a command-line
+ | option. It uses either '-Xoperand' or '-X operand',
+ | whichever is present. The program is terminated if no
+ | option is present.
+ |
+ | Side Efct: argc and argv are updated as necessary.
+ *--------------------------------------------------------------------*/
+
+char *
+operand(int *argcp,
+ char ***argvp)
+{
+ if ((**argvp)[2])
+ return (**argvp + 2); /* operand immediately follows */
+ if ((--*argcp) <= 0) { /* no operand */
+ error("command-line option operand missing.");
+ exit(8);
+ }
+ return (*(++(*argvp))); /* operand is next word */
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: getres ()
+ |
+ | Results: Sets 'res' to the resolution of the output device.
+ *--------------------------------------------------------------------*/
+
+void
+getres()
+{
+ int linepiece;
+
+ if (0 /* nullptr */ == font::load_desc())
+ fatal("cannot load 'DESC' description file for device '%1'",
+ device);
+
+ res = font::res;
+
+ /* Correct the brush thicknesses based on res */
+ /* if (res >= 256) {
+ defthick[0] = res >> 8;
+ defthick[1] = res >> 8;
+ defthick[2] = res >> 4;
+ defthick[3] = res >> 8;
+ defthick[4] = res >> 8;
+ defthick[5] = res >> 6;
+ } */
+
+ linepiece = res >> 9;
+ for (dotshifter = 0; linepiece; dotshifter++)
+ linepiece = linepiece >> 1;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: int doinput (file_pointer)
+ |
+ | Results: A line of input is read into 'inputline'.
+ |
+ | Side Efct: "linenum" is incremented.
+ |
+ | Bugs: Lines longer than MAXINLINE are NOT checked, except for
+ | updating 'linenum'.
+ *--------------------------------------------------------------------*/
+
+int
+doinput(FILE *fp)
+{
+ if (fgets(inputline, MAXINLINE, fp) == NULL)
+ return 0;
+ if (strchr(inputline, '\n')) /* ++ only if it's a complete line */
+ linenum++;
+ return 1;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: initpic ( )
+ |
+ | Results: Sets all parameters to the normal defaults, possibly
+ | overridden by a setdefault command. Initialize the
+ | picture variables, and output the startup commands to
+ | troff to begin the picture.
+ *--------------------------------------------------------------------*/
+
+void
+initpic()
+{
+ int i;
+
+ for (i = 0; i < STYLES; i++) { /* line thickness defaults */
+ thick[i] = defthick[i];
+ }
+ for (i = 0; i < FONTS; i++) { /* font name defaults */
+ tfont[i] = (char *)deffont[i];
+ }
+ for (i = 0; i < SIZES; i++) { /* font size defaults */
+ tsize[i] = defsize[i];
+ }
+ for (i = 0; i <= NSTIPPLES; i++) { /* stipple font file default */
+ /* indices */
+ stipple_index[i] = defstipple_index[i];
+ }
+ stipple = defstipple;
+
+ gremlinfile[0] = 0; /* filename is 'null' */
+ setdefault = 0; /* not the default settings (yet) */
+
+ toppoint = BIG; /* set the picture bounds out */
+ bottompoint = -BIG; /* of range so they'll be set */
+ leftpoint = BIG; /* by 'savebounds' on input */
+ rightpoint = -BIG;
+
+ pointscale = defpoint;/* flag for scaling point sizes default */
+ xscale = scale; /* default scale of individual pictures */
+ width = 0.0; /* size specifications input by user */
+ height = 0.0;
+
+ linethickness = DEFTHICK; /* brush styles */
+ linmod = DEFSTYLE;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: conv (file_pointer, starting_line)
+ |
+ | Results: At this point, we just passed a '.GS' line in the input
+ | file. conv reads the input and calls 'interpret' to
+ | process commands, gathering up information until a '.GE'
+ | line is found. It then calls 'HGPrint' to do the
+ | translation of the gremlin file to troff commands.
+ *--------------------------------------------------------------------*/
+
+void
+conv(FILE *fp,
+ int baseline)
+{
+ FILE *gfp = NULL; /* input file pointer */
+ int done = 0; /* flag to remember if finished */
+ ELT *e; /* current element pointer */
+ ELT *PICTURE; /* whole picture data base pointer */
+ double temp; /* temporary calculating area */
+ /* POINT ptr; */ /* coordinates of a point to pass to 'mov' */
+ /* routine */
+ int flyback; /* flag 'want to end up at the top of the */
+ /* picture?' */
+ int compat; /* test character after .GE or .GF */
+
+
+ initpic(); /* set defaults, ranges, etc. */
+ strcpy(GScommand, inputline); /* save '.GS' line for later */
+
+ do {
+ done = !doinput(fp); /* test for EOF */
+ flyback = (*c3 == 'F'); /* and .GE or .GF */
+ compat = (compatibility_flag ||
+ *c4 == '\n' || *c4 == ' ' || *c4 == '\0');
+ done |= (*c1 == '.' && *c2 == 'G' && (*c3 == 'E' || flyback) &&
+ compat);
+
+ if (done) {
+ if (setdefault)
+ savestate();
+
+ if (!gremlinfile[0]) {
+ if (!setdefault)
+ error("no picture file name at line %1", baseline);
+ return;
+ }
+ char *path;
+ gfp = macro_path.open_file(gremlinfile, &path);
+ if (0 /* nullptr */ == gfp) {
+ error("cannot open picture file '%1'", gremlinfile);
+ return;
+ }
+ PICTURE = DBRead(gfp); /* read picture file */
+ fclose(gfp);
+ free(path);
+ if (DBNullelt(PICTURE))
+ return; /* If a request is made to make the */
+ /* picture fit into a specific area, */
+ /* set the scale to do that. */
+
+ if (stipple == (char *) NULL) /* if user forgot stipple */
+ if (has_polygon(PICTURE)) /* and picture has a polygon */
+ stipple = (char *)DEFSTIPPLE; /* then set the default */
+
+ if ((temp = bottompoint - toppoint) < 0.1)
+ temp = 0.1;
+ temp = (height != 0.0) ? height / (temp * SCREENtoINCH) : BIG;
+ if ((troffscale = rightpoint - leftpoint) < 0.1)
+ troffscale = 0.1;
+ troffscale = (width != 0.0) ?
+ width / (troffscale * SCREENtoINCH) : BIG;
+ if (temp == BIG && troffscale == BIG)
+ troffscale = xscale;
+ else {
+ if (temp < troffscale)
+ troffscale = temp;
+ } /* here, troffscale is the */
+ /* picture's scaling factor */
+ if (pointscale) {
+ int i; /* do point scaling here, when */
+ /* scale is known, before output */
+ for (i = 0; i < SIZES; i++)
+ tsize[i] = (int) (troffscale * (double) tsize[i] + 0.5);
+ }
+
+ /* change to device units */
+ troffscale *= SCREENtoINCH * res; /* from screen units */
+
+ /* Calculate integer versions of the picture limits. */
+ ytop = (int) (toppoint * troffscale);
+ ybottom = (int) (bottompoint * troffscale);
+ xleft = (int) (leftpoint * troffscale);
+ xright = (int) (rightpoint * troffscale);
+
+ /* save stuff in number registers, */
+ /* register g1 = picture width and */
+ /* register g2 = picture height, */
+ /* set vertical spacing, no fill, */
+ /* and break (to make sure picture */
+ /* starts on left), and put out the */
+ /* user's '.GS' line. */
+ printf(".br\n"
+ ".nr g1 %du\n"
+ ".nr g2 %du\n"
+ "%s"
+ ".nr g3 \\n(.f\n"
+ ".nr g4 \\n(.s\n"
+ "\\0\n"
+ ".sp -1\n",
+ xright - xleft, ybottom - ytop, GScommand);
+
+ if (USE_ST_REQUEST) /* stipple requested for this picture */
+ printf(".st %s\n", stipple);
+ lastx = xleft; /* note where we are (upper left */
+ lastyline = lasty = ytop; /* corner of the picture) */
+
+ /* Just dump everything in the order it appears.
+ *
+ * If -s command-line option, traverse picture twice: First time,
+ * print only the interiors of filled polygons (as borderless
+ * polygons). Second time, print the outline as series of line
+ * segments. This way, postprocessors that overwrite rather than
+ * merge picture elements (such as Postscript) can still have text
+ * and graphics on a shaded background.
+ */
+ /* if (sflag) */
+ if (!sflag) { /* changing the default for filled polygons */
+ e = PICTURE;
+ polyfill = FILL;
+ while (!DBNullelt(e)) {
+ printf(".mk\n");
+ if (e->type == POLYGON)
+ HGPrintElt(e, baseline);
+ printf(".rt\n");
+ lastx = xleft;
+ lastyline = lasty = ytop;
+ e = DBNextElt(e);
+ }
+ }
+ e = PICTURE;
+
+ /* polyfill = !sflag ? BOTH : OUTLINE; */
+ polyfill = sflag ? BOTH : OUTLINE; /* changing default */
+ while (!DBNullelt(e)) {
+ printf(".mk\n");
+ HGPrintElt(e, baseline);
+ printf(".rt\n");
+ lastx = xleft;
+ lastyline = lasty = ytop;
+ e = DBNextElt(e);
+ }
+
+ /* decide where to end picture */
+
+ /* I [Senderowicz?] changed everything here. I always use the */
+ /* combination .mk and .rt, so once finished I just space down */
+ /* height of the picture that is \n(g2u. */
+ if (flyback) { /* end picture at upper left */
+ /* ptr.x = leftpoint;
+ ptr.y = toppoint; */
+ } else { /* end picture at lower left */
+ /* ptr.x = leftpoint;
+ ptr.y = bottompoint; */
+ printf(".sp \\n(g2u\n");
+ }
+
+ /* tmove(&ptr); */ /* restore default line parameters */
+
+ /* Restore everything to the way it was before the .GS, then */
+ /* put out the '.GE' line from user */
+
+ /* printf("\\D't %du'\\D's %du'\n", DEFTHICK, DEFSTYLE); */
+ /* groff doesn't understand the \Ds command */
+
+ printf("\\D't %du'\n", DEFTHICK);
+ if (flyback) /* make sure we end up at top of */
+ printf(".sp -1\n"); /* picture if 'flying back' */
+ if (USE_ST_REQUEST) /* restore stipple to previous */
+ printf(".st\n");
+ printf(".br\n"
+ ".ft \\n(g3\n"
+ ".ps \\n(g4\n"
+ "%s", inputline);
+ } else
+ interpret(inputline); /* take commands from the input file */
+ } while (!done);
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: savestate ( )
+ |
+ | Results: All the current scaling/font size/font name/thickness/
+ | pointscale settings are made the defaults. Scaled
+ | point sizes are NOT saved. The scaling is done each
+ | time a new picture is started.
+ |
+ | Side Efct: scale, and def* are modified.
+ *--------------------------------------------------------------------*/
+
+void
+savestate()
+{
+ int i;
+
+ for (i = 0; i < STYLES; i++) /* line thickness defaults */
+ defthick[i] = thick[i];
+ for (i = 0; i < FONTS; i++) /* font name defaults */
+ deffont[i] = tfont[i];
+ for (i = 0; i < SIZES; i++) /* font size defaults */
+ defsize[i] = tsize[i];
+ /* stipple font file default indices */
+ for (i = 0; i <= NSTIPPLES; i++)
+ defstipple_index[i] = stipple_index[i];
+
+ defstipple = stipple; /* if stipple has been set, it's remembered */
+ scale *= xscale; /* default scale of individual pictures */
+ defpoint = pointscale;/* flag to scale point sizes from x factors */
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: savebounds (x_coordinate, y_coordinate)
+ |
+ | Results: Keeps track of the maximum and minimum extent of a
+ | picture in the global variables: left-, right-, top- and
+ | bottompoint. 'savebounds' assumes that the points have
+ | been oriented to the correct direction. No scaling has
+ | taken place, though.
+ *--------------------------------------------------------------------*/
+
+void
+savebounds(double x,
+ double y)
+{
+ if (x < leftpoint)
+ leftpoint = x;
+ if (x > rightpoint)
+ rightpoint = x;
+ if (y < toppoint)
+ toppoint = y;
+ if (y > bottompoint)
+ bottompoint = y;
+}
+
+
+/*--------------------------------------------------------------------*
+ | Routine: interpret (character_string)
+ |
+ | Results: Commands are taken from the input string and performed.
+ | Commands are separated by newlines, and are of the
+ | format:
+ | string1 string2
+ | where string1 is the command, string2 the argument.
+ |
+ | Side Efct: Font and size strings, plus the gremlin file name and
+ | the width and height variables are set by this routine.
+ *--------------------------------------------------------------------*/
+
+void
+interpret(char *line)
+{
+ char str1[MAXINLINE];
+ char str2[MAXINLINE];
+ char *chr;
+ int i;
+ double par;
+
+ str2[0] = '\0';
+ sscanf(line, "%80s%80s", &str1[0], &str2[0]);
+ for (chr = &str1[0]; *chr; chr++) /* convert command to */
+ if (isupper(*chr))
+ *chr = tolower(*chr); /* lower case */
+
+ switch (str1[0]) {
+
+ case '1':
+ case '2': /* font sizes */
+ case '3':
+ case '4':
+ i = atoi(str2);
+ if (i > 0 && i < 1000)
+ tsize[str1[0] - '1'] = i;
+ else
+ error("bad font size value at line %1", linenum);
+ break;
+
+ case 'r': /* roman */
+ if (str2[0] < '0')
+ goto nofont;
+ tfont[0] = (char *) grnmalloc(strlen(str2) + 1, "roman command");
+ strcpy(tfont[0], str2);
+ break;
+
+ case 'i': /* italics */
+ if (str2[0] < '0')
+ goto nofont;
+ tfont[1] = (char *) grnmalloc(strlen(str2) + 1, "italics command");
+ strcpy(tfont[1], str2);
+ break;
+
+ case 'b': /* bold */
+ if (str2[0] < '0')
+ goto nofont;
+ tfont[2] = (char *) grnmalloc(strlen(str2) + 1, "bold command");
+ strcpy(tfont[2], str2);
+ break;
+
+ case 's': /* special */
+ if (str1[1] == 'c')
+ goto scalecommand; /* or scale */
+
+ if (str2[0] < '0') {
+ nofont:
+ error("no font name specified in line %1", linenum);
+ break;
+ }
+ if (str1[1] == 't')
+ goto stipplecommand; /* or stipple */
+
+ tfont[3] = (char *) grnmalloc(strlen(str2) + 1, "special command");
+ strcpy(tfont[3], str2);
+ break;
+
+ case 'l': /* l */
+ if (isdigit(str1[1])) { /* set stipple index */
+ int idx = atoi(str1 + 1), val;
+
+ if (idx < 0 || idx > NSTIPPLES) {
+ error("bad stipple number %1 at line %2", idx, linenum);
+ break;
+ }
+ if (!defstipple_index)
+ defstipple_index = other_stipple_index;
+ val = atoi(str2);
+ if (val >= 0 && val < 256)
+ stipple_index[idx] = val;
+ else
+ error("bad stipple index value at line %1", linenum);
+ break;
+ }
+
+ stipplecommand: /* set stipple name */
+ stipple = (char *) grnmalloc(strlen(str2) + 1, "stipple command");
+ strcpy(stipple, str2);
+ /* if it's a 'known' font (currently only 'cf'), set indices */
+ if (strcmp(stipple, "cf") == 0)
+ defstipple_index = cf_stipple_index;
+ else
+ defstipple_index = other_stipple_index;
+ for (i = 0; i <= NSTIPPLES; i++)
+ stipple_index[i] = defstipple_index[i];
+ break;
+
+ case 'a': /* text adjust */
+ par = atof(str2);
+ switch (str1[1]) {
+ case '1':
+ adj1 = par;
+ break;
+ case '2':
+ adj2 = par;
+ break;
+ case '3':
+ adj3 = par;
+ break;
+ case '4':
+ adj4 = par;
+ break;
+ default:
+ error("bad adjust command at line %1", linenum);
+ break;
+ }
+ break;
+
+ case 't': /* thick */
+ thick[2] = defthick[0] * atof(str2);
+ break;
+
+ case 'm': /* medium */
+ thick[5] = defthick[0] * atof(str2);
+ break;
+
+ case 'n': /* narrow */
+ thick[0] = thick[1] = thick[3] = thick[4] =
+ defthick[0] * atof(str2);
+ break;
+
+ case 'x': /* x */
+ scalecommand: /* scale */
+ par = atof(str2);
+ if (par > 0.0)
+ xscale *= par;
+ else
+ error("invalid scale value on line %1", linenum);
+ break;
+
+ case 'f': /* file */
+ strcpy(gremlinfile, str2);
+ break;
+
+ case 'w': /* width */
+ width = atof(str2);
+ if (width < 0.0)
+ width = -width;
+ break;
+
+ case 'h': /* height */
+ height = atof(str2);
+ if (height < 0.0)
+ height = -height;
+ break;
+
+ case 'd': /* defaults */
+ setdefault = 1;
+ break;
+
+ case 'p': /* pointscale */
+ if (strcmp("off", str2))
+ pointscale = 1;
+ else
+ pointscale = 0;
+ break;
+
+ default:
+ error("unknown command '%1' on line %2", str1, linenum);
+ exit(8);
+ break;
+ };
+}
+
+
+/*
+ * return TRUE if picture contains a polygon
+ * otherwise FALSE
+ */
+
+int
+has_polygon(ELT *elist)
+{
+ while (!DBNullelt(elist)) {
+ if (elist->type == POLYGON)
+ return (1);
+ elist = DBNextElt(elist);
+ }
+
+ return (0);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/html/html.am b/src/preproc/html/html.am
new file mode 100644
index 0000000..ac5ca9d
--- /dev/null
+++ b/src/preproc/html/html.am
@@ -0,0 +1,32 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += pre-grohtml
+pre_grohtml_LDADD = libgroff.a lib/libgnu.a $(LIBM)
+pre_grohtml_SOURCES = \
+ src/preproc/html/pre-html.cpp \
+ src/preproc/html/pushback.cpp \
+ src/preproc/html/pre-html.h \
+ src/preproc/html/pushback.h
+src/preproc/html/pre-html.$(OBJEXT): defs.h
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/html/pre-html.cpp b/src/preproc/html/pre-html.cpp
new file mode 100644
index 0000000..fecfb05
--- /dev/null
+++ b/src/preproc/html/pre-html.cpp
@@ -0,0 +1,1819 @@
+/* Copyright (C) 2000-2021 Free Software Foundation, Inc.
+ * Written by Gaius Mulley (gaius@glam.ac.uk).
+ *
+ * This file is part of groff.
+ *
+ * groff is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * groff is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with groff; see the file COPYING. If not, write to the Free
+ * Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#define PREHTMLC
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "posix.h"
+#include "defs.h"
+#include "searchpath.h"
+#include "paper.h"
+#include "device.h"
+#include "font.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef _POSIX_VERSION
+# include <sys/wait.h>
+# define PID_T pid_t
+#else /* not _POSIX_VERSION */
+# define PID_T int
+#endif /* not _POSIX_VERSION */
+
+#include <stdarg.h>
+
+#include "nonposix.h"
+
+#if 0
+# define DEBUGGING
+#endif
+
+/* Establish some definitions to facilitate discrimination between
+ differing runtime environments. */
+
+#undef MAY_FORK_CHILD_PROCESS
+#undef MAY_SPAWN_ASYNCHRONOUS_CHILD
+
+#if defined(__MSDOS__) || defined(_WIN32)
+
+// Most MS-DOS and Win32 environments will be missing the 'fork'
+// capability (some, like Cygwin, have it, but it is better avoided).
+
+# define MAY_FORK_CHILD_PROCESS 0
+
+// On these systems, we use 'spawn...', instead of 'fork' ... 'exec...'.
+# include <process.h> // for 'spawn...'
+# include <fcntl.h> // for attributes of pipes
+
+# if defined(__CYGWIN__) || defined(_UWIN) || defined(_WIN32)
+
+// These Win32 implementations allow parent and 'spawn...'ed child to
+// multitask asynchronously.
+
+# define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
+
+# else
+
+// Others may adopt MS-DOS behaviour where parent must sleep,
+// from 'spawn...' until child terminates.
+
+# define MAY_SPAWN_ASYNCHRONOUS_CHILD 0
+
+# endif /* not defined __CYGWIN__, _UWIN, or _WIN32 */
+
+# if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
+/* When we are building a DEBUGGING version we need to tell pre-grohtml
+ where to put intermediate files (the DEBUGGING version will preserve
+ these on exit).
+
+ On a Unix host, we might simply use '/tmp', but MS-DOS and Win32 will
+ probably not have this on all disk drives, so default to using
+ 'c:/temp' instead. (Note that user may choose to override this by
+ supplying a definition such as
+
+ -DDEBUG_FILE_DIR=d:/path/to/debug/files
+
+ in the CPPFLAGS to 'make'.) */
+
+# define DEBUG_FILE_DIR c:/temp
+# endif
+
+#else /* not __MSDOS__ or _WIN32 */
+
+// For non-Microsoft environments assume Unix conventions,
+// so 'fork' is required and child processes are asynchronous.
+# define MAY_FORK_CHILD_PROCESS 1
+# define MAY_SPAWN_ASYNCHRONOUS_CHILD 1
+
+# if defined(DEBUGGING) && !defined(DEBUG_FILE_DIR)
+/* For a DEBUGGING version, on the Unix host, we can also usually rely
+ on being able to use '/tmp' for temporary file storage. (Note that,
+ as in the __MSDOS__ or _WIN32 case above, the user may override this
+ by defining
+
+ -DDEBUG_FILE_DIR=/path/to/debug/files
+
+ in the CPPFLAGS.) */
+
+# define DEBUG_FILE_DIR /tmp
+# endif
+
+#endif /* not __MSDOS__ or _WIN32 */
+
+#ifdef DEBUGGING
+// For a DEBUGGING version, we need some additional macros,
+// to direct the captured debugging mode output to appropriately named
+// files in the specified DEBUG_FILE_DIR.
+
+# define DEBUG_TEXT(text) #text
+# define DEBUG_NAME(text) DEBUG_TEXT(text)
+# define DEBUG_FILE(name) DEBUG_NAME(DEBUG_FILE_DIR) "/" name
+#endif
+
+extern "C" const char *Version_string;
+
+#include "pre-html.h"
+#include "pushback.h"
+#include "html-strings.h"
+
+#define DEFAULT_LINE_LENGTH 7 // inches wide
+#define DEFAULT_IMAGE_RES 100 // number of pixels per inch resolution
+#define IMAGE_BORDER_PIXELS 0
+#define INLINE_LEADER_CHAR '\\'
+
+// Don't use colour names here! Otherwise there is a dependency on
+// a file called 'rgb.txt' which maps names to colours.
+#define TRANSPARENT "-background rgb:f/f/f -transparent rgb:f/f/f"
+#define MIN_ALPHA_BITS 0
+#define MAX_ALPHA_BITS 4
+
+#define PAGE_TEMPLATE_SHORT "pg"
+#define PAGE_TEMPLATE_LONG "-page-"
+#define PS_TEMPLATE_SHORT "ps"
+#define PS_TEMPLATE_LONG "-ps-"
+#define REGION_TEMPLATE_SHORT "rg"
+#define REGION_TEMPLATE_LONG "-regions-"
+
+typedef enum {
+ CENTERED, LEFT, RIGHT, INLINE
+} IMAGE_ALIGNMENT;
+
+typedef enum {xhtml, html4} html_dialect;
+
+static int postscriptRes = -1; // PostScript resolution,
+ // dots per inch
+static int stdoutfd = 1; // output file descriptor -
+ // normally 1 but might move
+ // -1 means closed
+static char *psFileName = 0 /* nullptr */; // PostScript file name
+static char *psPageName = 0 /* nullptr */; // name of file
+ // containing current
+ // PostScript page
+static char *regionFileName = 0 /* nullptr */; // name of file
+ // containing all image
+ // regions
+static char *imagePageName = 0 /* nullptr */; // name of bitmap image
+ // file containing
+ // current page
+static const char *image_device = "pnmraw";
+static int image_res = DEFAULT_IMAGE_RES;
+static int vertical_offset = 0;
+static char *image_template = 0 /* nullptr */; // image file name
+ // template
+static char *macroset_template= 0 /* nullptr */; // image file
+ // name template
+ // passed to
+ // troff by -D
+static int troff_arg = 0; // troff arg index
+static char *image_dir = 0 /* nullptr */; // user-specified image
+ // directory
+static int textAlphaBits = MAX_ALPHA_BITS;
+static int graphicAlphaBits = MAX_ALPHA_BITS;
+static char *antiAlias = 0 /* nullptr */; // anti-alias arguments
+ // to be passed to gs
+static bool want_progress_report = false; // display page numbers
+ // as they are processed
+static int currentPageNo = -1; // current image page number
+#if defined(DEBUGGING)
+static bool debugging = false;
+static char *troffFileName = 0 /* nullptr */; // pre-html output sent
+ // to troff -Tps
+static char *htmlFileName = 0 /* nullptr */; // pre-html output sent
+ // to troff -Thtml
+#endif
+static bool need_eqn = false; // must we preprocess via eqn?
+
+static char *linebuf = 0 /* nullptr */; // for scanning devps/DESC
+static int linebufsize = 0;
+static const char *image_gen = 0 /* nullptr */; // the 'gs' program
+
+static const char devhtml_desc[] = "devhtml/DESC";
+static const char devps_desc[] = "devps/DESC";
+
+const char *const FONT_ENV_VAR = "GROFF_FONT_PATH";
+static search_path font_path(FONT_ENV_VAR, FONTPATH, 0, 0);
+static html_dialect dialect = html4;
+
+
+/*
+ * Images are generated via PostScript, gs, and the pnm utilities.
+ */
+#define IMAGE_DEVICE "-Tps"
+
+
+/*
+ * sys_fatal - Write a fatal error message.
+ * Taken from src/roff/groff/pipeline.c.
+ */
+
+void sys_fatal(const char *s)
+{
+ fatal("%1: %2", s, strerror(errno));
+}
+
+/*
+ * get_line - Copy a line (w/o newline) from a file to the
+ * global line buffer.
+ */
+
+int get_line(FILE *f)
+{
+ if (f == 0)
+ return 0;
+ if (linebuf == 0) {
+ linebuf = new char[128];
+ linebufsize = 128;
+ }
+ int i = 0;
+ // skip leading whitespace
+ for (;;) {
+ int c = getc(f);
+ if (c == EOF)
+ return 0;
+ if (c != ' ' && c != '\t') {
+ ungetc(c, f);
+ break;
+ }
+ }
+ for (;;) {
+ int c = getc(f);
+ if (c == EOF)
+ break;
+ if (i + 1 >= linebufsize) {
+ char *old_linebuf = linebuf;
+ linebuf = new char[linebufsize * 2];
+ memcpy(linebuf, old_linebuf, linebufsize);
+ delete[] old_linebuf;
+ linebufsize *= 2;
+ }
+ linebuf[i++] = c;
+ if (c == '\n') {
+ i--;
+ break;
+ }
+ }
+ linebuf[i] = '\0';
+ return 1;
+}
+
+/*
+ * get_resolution - Return the PostScript device resolution.
+ */
+
+static unsigned int get_resolution(void)
+{
+ char *pathp;
+ FILE *f;
+ unsigned int res = 0;
+ f = font_path.open_file(devps_desc, &pathp);
+ if (0 == f)
+ fatal("cannot open file '%1'", devps_desc);
+ free(pathp);
+ // XXX: We should break out of this loop if we hit a "charset" line.
+ // "This line and everything following it in the file are ignored."
+ // (groff_font(5))
+ while (get_line(f))
+ (void) sscanf(linebuf, "res %u", &res);
+ fclose(f);
+ return res;
+}
+
+
+/*
+ * get_image_generator - Return the declared program from the HTML
+ * device description.
+ */
+
+static char *get_image_generator(void)
+{
+ char *pathp;
+ FILE *f;
+ char *generator = 0;
+ const char keyword[] = "image_generator";
+ const size_t keyword_len = strlen(keyword);
+ f = font_path.open_file(devhtml_desc, &pathp);
+ if (0 == f)
+ fatal("cannot open file '%1'", devhtml_desc);
+ free(pathp);
+ // XXX: We should break out of this loop if we hit a "charset" line.
+ // "This line and everything following it in the file are ignored."
+ // (groff_font(5))
+ while (get_line(f)) {
+ char *cursor = linebuf;
+ size_t limit = strlen(linebuf);
+ char *end = linebuf + limit;
+ if (0 == (strncmp(linebuf, keyword, keyword_len))) {
+ cursor += keyword_len;
+ // At least one space or tab is required.
+ if(!(' ' == *cursor) || ('\t' == *cursor))
+ continue;
+ cursor++;
+ while((cursor < end) && ((' ' == *cursor) || ('\t' == *cursor)))
+ cursor++;
+ if (cursor == end)
+ continue;
+ generator = cursor;
+ }
+ }
+ fclose(f);
+ return generator;
+}
+
+/*
+ * html_system - A wrapper for system().
+ */
+
+void html_system(const char *s, int redirect_stdout)
+{
+#if defined(DEBUGGING)
+ if (debugging) {
+ fprintf(stderr, "executing: ");
+ fwrite(s, sizeof(char), strlen(s), stderr);
+ fflush(stderr);
+ }
+#endif
+ {
+ int saved_stdout = dup(1);
+ int fdnull = open(NULL_DEV, O_WRONLY|O_BINARY, 0666);
+ if (redirect_stdout && saved_stdout > 1 && fdnull > 1)
+ dup2(fdnull, 1);
+ if (fdnull >= 0)
+ close(fdnull);
+ int status = system(s);
+ if (redirect_stdout)
+ dup2(saved_stdout, 1);
+ if (status == -1)
+ fprintf(stderr, "Calling '%s' failed\n", s);
+ else if (status)
+ fprintf(stderr, "Calling '%s' returned status %d\n", s, status);
+ close(saved_stdout);
+ }
+}
+
+/*
+ * make_string - Create a string via `malloc()`, place the variadic
+ * arguments as formatted by `fmt` into it, and return
+ * it. Adapted from Linux man-pages' printf(3) example.
+ * We never return a null pointer, instead treating
+ * failure as invariably fatal.
+ */
+
+char *make_string(const char *fmt, ...)
+{
+ size_t size = 0;
+ char *p = 0 /* nullptr */;
+ va_list ap;
+ va_start(ap, fmt);
+ int n = vsnprintf(p, size, fmt, ap);
+ va_end(ap);
+ if (n < 0)
+ sys_fatal("vsnprintf");
+ size = static_cast<size_t>(n) + 1 /* '\0' */;
+ p = static_cast<char *>(malloc(size));
+ if (0 /* nullptr */ == p)
+ sys_fatal("vsnprintf");
+ va_start(ap, fmt);
+ n = vsnprintf(p, size, fmt, ap);
+ va_end(ap);
+ if (n < 0)
+ sys_fatal("vsnprintf");
+ assert(p != 0 /* nullptr */);
+ return p;
+}
+
+/*
+ * classes and methods for retaining ascii text
+ */
+
+struct char_block {
+ enum { SIZE = 256 };
+ char buffer[SIZE];
+ int used;
+ char_block *next;
+
+ char_block();
+};
+
+char_block::char_block()
+: used(0), next(0)
+{
+ for (int i = 0; i < SIZE; i++)
+ buffer[i] = 0;
+}
+
+class char_buffer {
+public:
+ char_buffer();
+ ~char_buffer();
+ void read_file(FILE *fp);
+ int do_html(int argc, char *argv[]);
+ int do_image(int argc, char *argv[]);
+ void emit_troff_output(int device_format_selector);
+ void write_upto_newline(char_block **t, int *i, int is_html);
+ bool can_see(char_block **t, int *i, const char *string);
+ void skip_until_newline(char_block **t, int *i);
+private:
+ char_block *head;
+ char_block *tail;
+ int run_output_filter(int device_format_selector, int argc,
+ char *argv[]);
+};
+
+char_buffer::char_buffer()
+: head(0), tail(0)
+{
+}
+
+char_buffer::~char_buffer()
+{
+ while (head != 0 /* nullptr */) {
+ char_block *temp = head;
+ head = head->next;
+ delete temp;
+ }
+}
+
+/*
+ * read_file - Read file `fp` into char_blocks.
+ */
+
+void char_buffer::read_file(FILE *fp)
+{
+ int n;
+ while (!feof(fp)) {
+ if (0 /* nullptr */ == tail) {
+ tail = new char_block;
+ head = tail;
+ }
+ else {
+ if (tail->used == char_block::SIZE) {
+ tail->next = new char_block;
+ tail = tail->next;
+ }
+ }
+ // We now have a tail ready for the next `SIZE` bytes of the file.
+ n = fread(tail->buffer, sizeof(char), char_block::SIZE-tail->used,
+ fp);
+ if ((n < 0) || ((0 == n) && !feof(fp)))
+ sys_fatal("fread");
+ tail->used += n * sizeof(char);
+ }
+}
+
+/*
+ * writeNbytes - Write n bytes to stdout.
+ */
+
+static void writeNbytes(const char *s, int l)
+{
+ int n = 0;
+ int r;
+
+ while (n < l) {
+ r = write(stdoutfd, s, l - n);
+ if (r < 0)
+ sys_fatal("write");
+ n += r;
+ s += r;
+ }
+}
+
+/*
+ * writeString - Write a string to stdout.
+ */
+
+static void writeString(const char *s)
+{
+ writeNbytes(s, strlen(s));
+}
+
+/*
+ * makeFileName - Create the image filename template
+ * and the macroset image template.
+ */
+
+static void makeFileName(void)
+{
+ if ((image_dir != 0 /* nullptr */)
+ && (strchr(image_dir, '%') != 0 /* nullptr */))
+ fatal("'%%' is prohibited within the image directory name");
+ if ((image_template != 0 /* nullptr */)
+ && (strchr(image_template, '%') != 0 /* nullptr */))
+ fatal("'%%' is prohibited within the image template");
+ if (0 /* nullptr */ == image_dir)
+ image_dir = (char *)"";
+ else if (strlen(image_dir) > 0
+ && image_dir[strlen(image_dir) - 1] != '/')
+ image_dir = make_string("%s/", image_dir);
+ if (0 /* nullptr */ == image_template)
+ macroset_template = make_string("%sgrohtml-%d-", image_dir,
+ int(getpid()));
+ else
+ macroset_template = make_string("%s%s-", image_dir,
+ image_template);
+ size_t mtlen = strlen(macroset_template);
+ image_template = (char *)malloc(strlen("%d") + mtlen + 1);
+ if (0 /* nullptr */ == image_template)
+ sys_fatal("malloc");
+ char *s = strcpy(image_template, macroset_template);
+ s += mtlen;
+ // Keep this format string synced with troff:suppress_node::tprint().
+ strcpy(s, "%d");
+}
+
+/*
+ * setupAntiAlias - Set up the antialias string, used when we call gs.
+ */
+
+static void setupAntiAlias(void)
+{
+ if (textAlphaBits == 0 && graphicAlphaBits == 0)
+ antiAlias = make_string(" ");
+ else if (textAlphaBits == 0)
+ antiAlias = make_string("-dGraphicsAlphaBits=%d ",
+ graphicAlphaBits);
+ else if (graphicAlphaBits == 0)
+ antiAlias = make_string("-dTextAlphaBits=%d ", textAlphaBits);
+ else
+ antiAlias = make_string("-dTextAlphaBits=%d"
+ " -dGraphicsAlphaBits=%d ", textAlphaBits,
+ graphicAlphaBits);
+}
+
+/*
+ * checkImageDir - Check whether the image directory is available.
+ */
+
+static void checkImageDir(void)
+{
+ if (image_dir != 0 /* nullptr */ && strcmp(image_dir, "") != 0)
+ if (!(mkdir(image_dir, 0777) == 0 || errno == EEXIST))
+ fatal("cannot create directory '%1': %2", image_dir,
+ strerror(errno));
+}
+
+/*
+ * write_end_image - End the image. Write out the image extents if we
+ * are using -Tps.
+ */
+
+static void write_end_image(int is_html)
+{
+ /*
+ * if we are producing html then these
+ * emit image name and enable output
+ * else
+ * we are producing images
+ * in which case these generate image
+ * boundaries
+ */
+ writeString("\\O[4]\\O[2]");
+ if (is_html)
+ writeString("\\O[1]");
+ else
+ writeString("\\O[0]");
+}
+
+/*
+ * write_start_image - Write troff code which will:
+ *
+ * (i) disable html output for the following image
+ * (ii) reset the max/min x/y registers during
+ * Postscript Rendering.
+ */
+
+static void write_start_image(IMAGE_ALIGNMENT pos, int is_html)
+{
+ writeString("\\O[5");
+ switch (pos) {
+ case INLINE:
+ writeString("i");
+ break;
+ case LEFT:
+ writeString("l");
+ break;
+ case RIGHT:
+ writeString("r");
+ break;
+ case CENTERED:
+ default:
+ writeString("c");
+ break;
+ }
+ writeString(image_template);
+ writeString(".png]");
+ if (is_html)
+ writeString("\\O[0]\\O[3]");
+ else
+ // reset min/max registers
+ writeString("\\O[1]\\O[3]");
+}
+
+/*
+ * write_upto_newline - Write the contents of the buffer until a
+ * newline is seen. Check for
+ * HTML_IMAGE_INLINE_BEGIN and
+ * HTML_IMAGE_INLINE_END; process them if they are
+ * present.
+ */
+
+void char_buffer::write_upto_newline(char_block **t, int *i,
+ int is_html)
+{
+ int j = *i;
+
+ if (*t) {
+ while (j < (*t)->used
+ && (*t)->buffer[j] != '\n'
+ && (*t)->buffer[j] != INLINE_LEADER_CHAR)
+ j++;
+ if (j < (*t)->used
+ && (*t)->buffer[j] == '\n')
+ j++;
+ writeNbytes((*t)->buffer + (*i), j - (*i));
+ if (j < char_block::SIZE && (*t)->buffer[j] == INLINE_LEADER_CHAR) {
+ if (can_see(t, &j, HTML_IMAGE_INLINE_BEGIN))
+ write_start_image(INLINE, is_html);
+ else if (can_see(t, &j, HTML_IMAGE_INLINE_END))
+ write_end_image(is_html);
+ else {
+ if (j < (*t)->used) {
+ *i = j;
+ j++;
+ writeNbytes((*t)->buffer + (*i), j - (*i));
+ }
+ }
+ }
+ if (j == (*t)->used) {
+ *i = 0;
+ *t = (*t)->next;
+ if (*t && (*t)->buffer[j - 1] != '\n')
+ write_upto_newline(t, i, is_html);
+ }
+ else
+ // newline was seen
+ *i = j;
+ }
+}
+
+/*
+ * can_see - Return true if we can see string in t->buffer[i] onwards.
+ */
+
+bool char_buffer::can_see(char_block **t, int *i, const char *str)
+{
+ int j = 0;
+ int l = strlen(str);
+ int k = *i;
+ char_block *s = *t;
+
+ while (s) {
+ while (k < s->used && j < l && s->buffer[k] == str[j]) {
+ j++;
+ k++;
+ }
+ if (j == l) {
+ *i = k;
+ *t = s;
+ return true;
+ }
+ else if (k < s->used && s->buffer[k] != str[j])
+ return false;
+ s = s->next;
+ k = 0;
+ }
+ return false;
+}
+
+/*
+ * skip_until_newline - Skip all characters until a newline is seen.
+ * The newline is not consumed.
+ */
+
+void char_buffer::skip_until_newline(char_block **t, int *i)
+{
+ int j = *i;
+
+ if (*t) {
+ while (j < (*t)->used && (*t)->buffer[j] != '\n')
+ j++;
+ if (j == (*t)->used) {
+ *i = 0;
+ *t = (*t)->next;
+ skip_until_newline(t, i);
+ }
+ else
+ // newline was seen
+ *i = j;
+ }
+}
+
+#define DEVICE_FORMAT(filter) (filter == HTML_OUTPUT_FILTER)
+#define HTML_OUTPUT_FILTER 0
+#define IMAGE_OUTPUT_FILTER 1
+#define OUTPUT_STREAM(name) creat((name), S_IWUSR | S_IRUSR)
+#define PS_OUTPUT_STREAM OUTPUT_STREAM(psFileName)
+#define REGION_OUTPUT_STREAM OUTPUT_STREAM(regionFileName)
+
+/*
+ * emit_troff_output - Write formatted buffer content to the troff
+ * post-processor data pipeline.
+ */
+
+void char_buffer::emit_troff_output(int device_format_selector)
+{
+ // Handle output for BOTH html and image device formats
+ // if 'device_format_selector' is passed as
+ //
+ // HTML_FORMAT(HTML_OUTPUT_FILTER)
+ // Buffer data is written to the output stream
+ // with template image names translated to actual image names.
+ //
+ // HTML_FORMAT(IMAGE_OUTPUT_FILTER)
+ // Buffer data is written to the output stream
+ // with no translation, for image file creation in the
+ // post-processor.
+
+ int idx = 0;
+ char_block *element = head;
+
+ while (element != 0 /* nullptr */)
+ write_upto_newline(&element, &idx, device_format_selector);
+
+#if 0
+ if (close(stdoutfd) < 0)
+ sys_fatal ("close");
+
+ // now we grab fd=1 so that the next pipe cannot use fd=1
+ if (stdoutfd == 1) {
+ if (dup(2) != stdoutfd)
+ sys_fatal ("dup failed to use fd=1");
+ }
+#endif /* 0 */
+}
+
+/*
+ * The image class remembers the position of all images in the
+ * PostScript file and assigns names for each image.
+ */
+
+struct imageItem {
+ imageItem *next;
+ int X1;
+ int Y1;
+ int X2;
+ int Y2;
+ char *imageName;
+ int resolution;
+ int maxx;
+ int pageNo;
+
+ imageItem(int x1, int y1, int x2, int y2,
+ int page, int res, int max_width, char *name);
+ ~imageItem();
+};
+
+/*
+ * imageItem - Constructor.
+ */
+
+imageItem::imageItem(int x1, int y1, int x2, int y2,
+ int page, int res, int max_width, char *name)
+{
+ X1 = x1;
+ Y1 = y1;
+ X2 = x2;
+ Y2 = y2;
+ pageNo = page;
+ resolution = res;
+ maxx = max_width;
+ imageName = name;
+ next = 0 /* nullptr */;
+}
+
+/*
+ * imageItem - Destructor.
+ */
+
+imageItem::~imageItem()
+{
+ if (imageName)
+ free(imageName);
+}
+
+/*
+ * imageList - A class containing a list of imageItems.
+ */
+
+class imageList {
+private:
+ imageItem *head;
+ imageItem *tail;
+ int count;
+public:
+ imageList();
+ ~imageList();
+ void add(int x1, int y1, int x2, int y2,
+ int page, int res, int maxx, char *name);
+ void createImages(void);
+ int createPage(int pageno);
+ void createImage(imageItem *i);
+ int getMaxX(int pageno);
+};
+
+/*
+ * imageList - Constructor.
+ */
+
+imageList::imageList()
+: head(0), tail(0), count(0)
+{
+}
+
+/*
+ * imageList - Destructor.
+ */
+
+imageList::~imageList()
+{
+ while (head != 0 /* nullptr */) {
+ imageItem *i = head;
+ head = head->next;
+ delete i;
+ }
+}
+
+/*
+ * createPage - Create image of page `pageno` from PostScript file.
+ */
+
+int imageList::createPage(int pageno)
+{
+ char *s;
+
+ if (currentPageNo == pageno)
+ return 0;
+
+ if (currentPageNo >= 1) {
+ /*
+ * We need to unlink the files which change each time a new page is
+ * processed. The final unlink is done by xtmpfile when
+ * pre-grohtml exits.
+ */
+ unlink(imagePageName);
+ unlink(psPageName);
+ }
+
+ if (want_progress_report) {
+ fprintf(stderr, "[%d] ", pageno);
+ fflush(stderr);
+ }
+
+#if defined(DEBUGGING)
+ if (debugging)
+ fprintf(stderr, "creating page %d\n", pageno);
+#endif
+
+ s = make_string("psselect -q -p%d %s %s\n",
+ pageno, psFileName, psPageName);
+ html_system(s, 1);
+ assert(strlen(image_gen) > 0);
+ s = make_string("echo showpage | "
+ "%s%s -q -dBATCH -dSAFER "
+ "-dDEVICEHEIGHTPOINTS=792 "
+ "-dDEVICEWIDTHPOINTS=%d -dFIXEDMEDIA=true "
+ "-sDEVICE=%s -r%d %s "
+ "-sOutputFile=%s %s -\n",
+ image_gen,
+ EXE_EXT,
+ (getMaxX(pageno) * image_res) / postscriptRes,
+ image_device,
+ image_res,
+ antiAlias,
+ imagePageName,
+ psPageName);
+ html_system(s, 1);
+ free(s);
+ currentPageNo = pageno;
+ return 0;
+}
+
+/*
+ * min - Return the minimum of two numbers.
+ */
+
+int min(int x, int y)
+{
+ if (x < y)
+ return x;
+ else
+ return y;
+}
+
+/*
+ * max - Return the maximum of two numbers.
+ */
+
+int max(int x, int y)
+{
+ if (x > y)
+ return x;
+ else
+ return y;
+}
+
+/*
+ * getMaxX - Return the largest right-hand position for any image
+ * on `pageno`.
+ */
+
+int imageList::getMaxX(int pageno)
+{
+ imageItem *h = head;
+ int x = postscriptRes * DEFAULT_LINE_LENGTH;
+
+ while (h != 0 /* nullptr */) {
+ if (h->pageNo == pageno)
+ x = max(h->X2, x);
+ h = h->next;
+ }
+ return x;
+}
+
+/*
+ * createImage - Generate a minimal PNG file from the set of page
+ * images.
+ */
+
+void imageList::createImage(imageItem *i)
+{
+ if (i->X1 != -1) {
+ char *s;
+ int x1 = max(min(i->X1, i->X2) * image_res / postscriptRes
+ - IMAGE_BORDER_PIXELS,
+ 0);
+ int y1 = max(image_res * vertical_offset / 72
+ + min(i->Y1, i->Y2) * image_res / postscriptRes
+ - IMAGE_BORDER_PIXELS,
+ 0);
+ int x2 = max(i->X1, i->X2) * image_res / postscriptRes
+ + IMAGE_BORDER_PIXELS;
+ int y2 = image_res * vertical_offset / 72
+ + max(i->Y1, i->Y2) * image_res / postscriptRes
+ + 1 + IMAGE_BORDER_PIXELS;
+ if (createPage(i->pageNo) == 0) {
+ s = make_string("pnmcut%s %d %d %d %d < %s "
+ "| pnmcrop%s -quiet | pnmtopng%s -quiet %s"
+ "> %s\n",
+ EXE_EXT,
+ x1, y1, x2 - x1 + 1, y2 - y1 + 1,
+ imagePageName,
+ EXE_EXT,
+ EXE_EXT,
+ TRANSPARENT,
+ i->imageName);
+ html_system(s, 0);
+ free(s);
+ }
+ else {
+ fprintf(stderr, "failed to generate image of page %d\n",
+ i->pageNo);
+ fflush(stderr);
+ }
+#if defined(DEBUGGING)
+ }
+ else {
+ if (debugging) {
+ fprintf(stderr, "ignoring image as x1 coord is -1\n");
+ fflush(stderr);
+ }
+#endif
+ }
+}
+
+/*
+ * add - Add an image description to the imageList.
+ */
+
+void imageList::add(int x1, int y1, int x2, int y2,
+ int page, int res, int maxx, char *name)
+{
+ imageItem *i = new imageItem(x1, y1, x2, y2, page, res, maxx, name);
+
+ if (0 /* nullptr */ == head) {
+ head = i;
+ tail = i;
+ }
+ else {
+ tail->next = i;
+ tail = i;
+ }
+}
+
+/*
+ * createImages - For each image descriptor on the imageList,
+ * create the actual image.
+ */
+
+void imageList::createImages(void)
+{
+ imageItem *h = head;
+
+ while (h != 0 /* nullptr */) {
+ createImage(h);
+ h = h->next;
+ }
+}
+
+static imageList listOfImages; // list of images defined by region file
+
+/*
+ * generateImages - Parse the region file and generate images from the
+ * PostScript file. The region file contains the
+ * x1,y1--x2,y2 extents of each image.
+ */
+
+static void generateImages(char *region_file_name)
+{
+ pushBackBuffer *f=new pushBackBuffer(region_file_name);
+
+ while (f->putPB(f->getPB()) != eof) {
+ if (f->isString("grohtml-info:page")) {
+ int page = f->readInt();
+ int x1 = f->readInt();
+ int y1 = f->readInt();
+ int x2 = f->readInt();
+ int y2 = f->readInt();
+ int maxx = f->readInt();
+ char *name = f->readString();
+ int res = postscriptRes;
+ listOfImages.add(x1, y1, x2, y2, page, res, maxx, name);
+ while (f->putPB(f->getPB()) != '\n'
+ && f->putPB(f->getPB()) != eof)
+ (void)f->getPB();
+ if (f->putPB(f->getPB()) == '\n')
+ (void)f->getPB();
+ }
+ else {
+ /* Write any error messages out to the user. */
+ fputc(f->getPB(), stderr);
+ }
+ }
+ fflush(stderr);
+
+ listOfImages.createImages();
+ if (want_progress_report) {
+ fprintf(stderr, "done\n");
+ fflush(stderr);
+ }
+ delete f;
+}
+
+/*
+ * set_redirection - Redirect file descriptor `was` to file descriptor
+ * `willbe`.
+ */
+
+static void set_redirection(int was, int willbe)
+{
+ // Nothing to do if 'was' and 'willbe' already have same handle.
+ if (was != willbe) {
+ // Otherwise attempt the specified redirection.
+ if (dup2(willbe, was) < 0) {
+ // Redirection failed, so issue diagnostic and bail out.
+ fprintf(stderr, "failed to replace fd=%d with %d\n", was, willbe);
+ if (willbe == STDOUT_FILENO)
+ fprintf(stderr,
+ "likely that stdout should be opened before %d\n", was);
+ sys_fatal("dup2");
+ }
+
+ // When redirection has been successfully completed assume redundant
+ // handle 'willbe' is no longer required, so close it.
+ if (close(willbe) < 0)
+ // Issue diagnostic if 'close' fails.
+ sys_fatal("close");
+ }
+}
+
+/*
+ * save_and_redirect - Duplicate file descriptor for `was` on file
+ * descriptor `willbe`.
+ */
+
+static int save_and_redirect(int was, int willbe)
+{
+ if (was == willbe)
+ // No redirection specified; silently bail out.
+ return (was);
+
+ // Proceeding with redirection so first save and verify our duplicate
+ // handle for 'was'.
+ int saved = dup(was);
+ if (saved < 0) {
+ fprintf(stderr, "unable to get duplicate handle for %d\n", was);
+ sys_fatal("dup");
+ }
+
+ // Duplicate handle safely established so complete redirection.
+ set_redirection(was, willbe);
+
+ // Finally return the saved duplicate descriptor for the original
+ // 'was' descriptor.
+ return saved;
+}
+
+/*
+ * alterDeviceTo - If toImage is set
+ * the argument list is altered to include
+ * IMAGE_DEVICE; we invoke groff rather than troff.
+ * Else
+ * set -Thtml and groff.
+ */
+
+static void alterDeviceTo(int argc, char *argv[], int toImage)
+{
+ int i = 0;
+
+ if (toImage) {
+ while (i < argc) {
+ if ((strcmp(argv[i], "-Thtml") == 0) ||
+ (strcmp(argv[i], "-Txhtml") == 0))
+ argv[i] = (char *)IMAGE_DEVICE;
+ i++;
+ }
+ argv[troff_arg] = (char *)"groff"; /* rather than troff */
+ }
+ else {
+ while (i < argc) {
+ if (strcmp(argv[i], IMAGE_DEVICE) == 0) {
+ if (dialect == xhtml)
+ argv[i] = (char *)"-Txhtml";
+ else
+ argv[i] = (char *)"-Thtml";
+ }
+ i++;
+ }
+ argv[troff_arg] = (char *)"groff"; /* use groff -Z */
+ }
+}
+
+/*
+ * addArg - Append newarg onto the command list for groff.
+ */
+
+char **addArg(int argc, char *argv[], char *newarg)
+{
+ char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
+ int i = 0;
+
+ if (0 /* nullptr */ == new_argv)
+ sys_fatal("malloc");
+
+ if (argc > 0) {
+ new_argv[i] = argv[i];
+ i++;
+ }
+ new_argv[i] = newarg;
+ while (i < argc) {
+ new_argv[i + 1] = argv[i];
+ i++;
+ }
+ argc++;
+ new_argv[argc] = 0 /* nullptr */;
+ return new_argv;
+}
+
+/*
+ * addRegDef - Append a defined register or string onto the command
+ * list for troff.
+ */
+
+char **addRegDef(int argc, char *argv[], const char *numReg)
+{
+ char **new_argv = (char **)malloc((argc + 2) * sizeof(char *));
+ int i = 0;
+
+ if (0 /* nullptr */ == new_argv)
+ sys_fatal("malloc");
+
+ while (i < argc) {
+ new_argv[i] = argv[i];
+ i++;
+ }
+ new_argv[argc] = strsave(numReg);
+ argc++;
+ new_argv[argc] = 0 /* nullptr */;
+ return new_argv;
+}
+
+/*
+ * dump_args - Display the argument list.
+ */
+
+void dump_args(int argc, char *argv[])
+{
+ fprintf(stderr, " %d arguments:", argc);
+ for (int i = 0; i < argc; i++)
+ fprintf(stderr, " %s", argv[i]);
+ fprintf(stderr, "\n");
+}
+
+/*
+ * print_args - Print arguments as if issued on the command line.
+ */
+
+#if defined(DEBUGGING)
+
+void print_args(int argc, char *argv[])
+{
+ if (debugging) {
+ fprintf(stderr, "executing: ");
+ for (int i = 0; i < argc; i++)
+ fprintf(stderr, "%s ", argv[i]);
+ fprintf(stderr, "\n");
+ }
+}
+
+#else
+
+void print_args(int, char **)
+{
+}
+
+#endif
+
+int char_buffer::run_output_filter(int filter, int argc, char **argv)
+{
+ int pipedes[2];
+ PID_T child_pid;
+ int wstatus;
+
+ print_args(argc, argv);
+ if (pipe(pipedes) < 0)
+ sys_fatal("pipe");
+
+#if MAY_FORK_CHILD_PROCESS
+ // This is the Unix process model. To invoke our post-processor,
+ // we must 'fork' the current process.
+
+ if ((child_pid = fork()) < 0)
+ sys_fatal("fork");
+
+ else if (child_pid == 0) {
+ // This is the child process. We redirect its input file descriptor
+ // to read data emerging from our pipe. There is no point in
+ // saving, since we won't be able to restore later!
+
+ set_redirection(STDIN_FILENO, pipedes[0]);
+
+ // The parent process will be writing this data; release the child's
+ // writeable handle on the pipe since we have no use for it.
+
+ if (close(pipedes[1]) < 0)
+ sys_fatal("close");
+
+ // The IMAGE_OUTPUT_FILTER needs special output redirection...
+
+ if (filter == IMAGE_OUTPUT_FILTER) {
+ // ...with BOTH 'stdout' AND 'stderr' diverted to files, the
+ // latter so that `generateImages()` can scrape "grohtml-info"
+ // from it.
+
+ set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
+ set_redirection(STDERR_FILENO, REGION_OUTPUT_STREAM);
+ }
+
+ // Now we are ready to launch the output filter.
+
+ execvp(argv[0], argv); // does not return unless it fails
+ fatal("cannot execute '%1': %2", argv[0], strerror(errno));
+ }
+
+ else {
+ // This is the parent process. We write data to the filter pipeline
+ // where the child will read it. We have no need to read from the
+ // input side ourselves, so close it.
+
+ if (close(pipedes[0]) < 0)
+ sys_fatal("close");
+
+ // Now redirect the standard output file descriptor to the inlet end
+ // of the pipe, and push the formatted data to the filter.
+
+ pipedes[1] = save_and_redirect(STDOUT_FILENO, pipedes[1]);
+ emit_troff_output(DEVICE_FORMAT(filter));
+
+ // After emitting all the data we close our connection to the inlet
+ // end of the pipe so the child process will detect end of data.
+
+ set_redirection(STDOUT_FILENO, pipedes[1]);
+
+ // Finally, we must wait for the child process to complete.
+
+ if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid)
+ sys_fatal("wait");
+ }
+
+#elif MAY_SPAWN_ASYNCHRONOUS_CHILD
+
+ // We do not have `fork` (or we prefer not to use it), but
+ // asynchronous processes are allowed, passing data through pipes.
+ // This should be okay for most Win32 systems and is preferred to
+ // `fork` for starting child processes under Cygwin.
+
+ // Before we start the post-processor we bind its inherited standard
+ // input file descriptor to the readable end of our pipe, saving our
+ // own standard input file descriptor in `pipedes[0]`.
+
+ pipedes[0] = save_and_redirect(STDIN_FILENO, pipedes[0]);
+
+ // For the Win32 model,
+ // we need special provision for saving BOTH 'stdout' and 'stderr'.
+
+ int saved_stdout = dup(STDOUT_FILENO);
+ int saved_stderr = STDERR_FILENO;
+
+ // The IMAGE_OUTPUT_FILTER needs special output redirection...
+
+ if (filter == IMAGE_OUTPUT_FILTER) {
+ // with BOTH 'stdout' AND 'stderr' diverted to files while saving a
+ // duplicate handle for 'stderr'.
+
+ set_redirection(STDOUT_FILENO, PS_OUTPUT_STREAM);
+ saved_stderr = save_and_redirect(STDERR_FILENO,
+ REGION_OUTPUT_STREAM);
+ }
+
+ // Use an asynchronous spawn request to start the post-processor.
+
+ if ((child_pid = spawnvp(_P_NOWAIT, argv[0], argv)) < 0) {
+ fatal("cannot spawn %1: %2", argv[0], strerror(errno));
+ }
+
+ // Once the post-processor has been started we revert our 'stdin'
+ // to its original saved source, which also closes the readable handle
+ // for the pipe.
+
+ set_redirection(STDIN_FILENO, pipedes[0]);
+
+ // if we redirected 'stderr', for use by the image post-processor,
+ // then we also need to reinstate its original assignment.
+
+ if (filter == IMAGE_OUTPUT_FILTER)
+ set_redirection(STDERR_FILENO, saved_stderr);
+
+ // Now we redirect the standard output to the inlet end of the pipe,
+ // and push out the appropriately formatted data to the filter.
+
+ set_redirection(STDOUT_FILENO, pipedes[1]);
+ emit_troff_output(DEVICE_FORMAT(filter));
+
+ // After emitting all the data we close our connection to the inlet
+ // end of the pipe so the child process will detect end of data.
+
+ set_redirection(STDOUT_FILENO, saved_stdout);
+
+ // And finally, we must wait for the child process to complete.
+
+ if (WAIT(&wstatus, child_pid, _WAIT_CHILD) != child_pid)
+ sys_fatal("wait");
+
+#else /* can't do asynchronous pipes! */
+
+ // TODO: code to support an MS-DOS style process model should go here
+ fatal("output filtering not supported on this platform");
+
+#endif /* MAY_FORK_CHILD_PROCESS or MAY_SPAWN_ASYNCHRONOUS_CHILD */
+
+ return wstatus;
+}
+
+/*
+ * do_html - Set the troff number htmlflip and
+ * write out the buffer to troff -Thtml.
+ */
+
+int char_buffer::do_html(int argc, char *argv[])
+{
+ string s;
+
+ alterDeviceTo(argc, argv, 0);
+ argv += troff_arg; // skip all arguments up to groff
+ argc -= troff_arg;
+ argv = addArg(argc, argv, (char *)"-Z");
+ argc++;
+
+ s = (char *)"-dwww-image-template=";
+ s += macroset_template; // Do not combine these statements,
+ // otherwise they will not work.
+ s += '\0'; // The trailing '\0' is ignored.
+ argv = addRegDef(argc, argv, s.contents());
+ argc++;
+
+ if (dialect == xhtml) {
+ argv = addRegDef(argc, argv, "-rxhtml=1");
+ argc++;
+ if (need_eqn) {
+ argv = addRegDef(argc, argv, "-e");
+ argc++;
+ }
+ }
+
+#if defined(DEBUGGING)
+# define HTML_DEBUG_STREAM OUTPUT_STREAM(htmlFileName)
+ // slight security risk: only enabled if defined(DEBUGGING)
+ if (debugging) {
+ int saved_stdout = save_and_redirect(STDOUT_FILENO,
+ HTML_DEBUG_STREAM);
+ emit_troff_output(DEVICE_FORMAT(HTML_OUTPUT_FILTER));
+ set_redirection(STDOUT_FILENO, saved_stdout);
+ }
+#endif
+
+ return run_output_filter(HTML_OUTPUT_FILTER, argc, argv);
+}
+
+/*
+ * do_image - Write out the buffer to troff -Tps.
+ */
+
+int char_buffer::do_image(int argc, char *argv[])
+{
+ string s;
+
+ alterDeviceTo(argc, argv, 1);
+ argv += troff_arg; // skip all arguments up to troff/groff
+ argc -= troff_arg;
+ argv = addRegDef(argc, argv, "-rps4html=1");
+ argc++;
+
+ s = "-dwww-image-template=";
+ s += macroset_template;
+ s += '\0';
+ argv = addRegDef(argc, argv, s.contents());
+ argc++;
+
+ // Override local settings and produce a letter-size PostScript page
+ // file.
+ argv = addRegDef(argc, argv, "-P-pletter");
+ argc++;
+
+ if (dialect == xhtml) {
+ if (need_eqn) {
+ argv = addRegDef(argc, argv, "-rxhtml=1");
+ argc++;
+ }
+ argv = addRegDef(argc, argv, "-e");
+ argc++;
+ }
+
+#if defined(DEBUGGING)
+# define IMAGE_DEBUG_STREAM OUTPUT_STREAM(troffFileName)
+ // slight security risk: only enabled if defined(DEBUGGING)
+ if (debugging) {
+ int saved_stdout = save_and_redirect(STDOUT_FILENO,
+ IMAGE_DEBUG_STREAM);
+ emit_troff_output(DEVICE_FORMAT(IMAGE_OUTPUT_FILTER));
+ set_redirection(STDOUT_FILENO, saved_stdout);
+ }
+#endif
+
+ return run_output_filter(IMAGE_OUTPUT_FILTER, argc, argv);
+}
+
+static char_buffer inputFile;
+
+/*
+ * usage - Emit usage message.
+ */
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-epV] [-a anti-aliasing-text-bits] [-D image-directory]"
+" [-F font-directory] [-g anti-aliasing-graphics-bits] [-i resolution]"
+" [-I image-stem] [-o image-vertical-offset] [-x html-dialect]"
+" troff-command troff-argument ...\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"Prepare a troff(1) document for HTML formatting.\n"
+"\n"
+"This program is not intended to be executed standalone; it is\n"
+"normally part of a groff pipeline. If your need to run it manually\n"
+"(e.g., for debugging purposes), give the 'groff' program the\n"
+"command-line option '-V' to inspect the arguments with which\n",
+ stream);
+ fprintf(stream,
+"'%s' is called. See the grohtml(1) manual page.\n",
+ program_name);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+/*
+ * scanArguments - Scan for all arguments including -P-i, -P-o, -P-D,
+ * and -P-I. Return the argument index of the first
+ * non-option.
+ */
+
+static int scanArguments(int argc, char **argv)
+{
+ const char *cmdprefix = getenv("GROFF_COMMAND_PREFIX");
+ if (!cmdprefix)
+ cmdprefix = PROG_PREFIX;
+ size_t pfxlen = strlen(cmdprefix);
+ char *troff_name = new char[pfxlen + strlen("troff") + 1];
+ char *s = strcpy(troff_name, cmdprefix);
+ s += pfxlen;
+ strcpy(s, "troff");
+ int c, i;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { 0 /* nullptr */, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv,
+ "+a:bCdD:eF:g:Ghi:I:j:lno:prs:S:vVx:y", long_options,
+ 0 /* nullptr */))
+ != EOF)
+ switch(c) {
+ case 'a':
+ textAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
+ MAX_ALPHA_BITS);
+ if (textAlphaBits == 3)
+ fatal("cannot use 3 bits of antialiasing information");
+ break;
+ case 'b':
+ // handled by post-grohtml (set background color to white)
+ break;
+ case 'C':
+ // handled by post-grohtml (don't write Creator HTML comment)
+ break;
+ case 'd':
+#if defined(DEBUGGING)
+ debugging = true;
+#endif
+ break;
+ case 'D':
+ image_dir = optarg;
+ break;
+ case 'e':
+ need_eqn = true;
+ break;
+ case 'F':
+ font_path.command_line_dir(optarg);
+ break;
+ case 'g':
+ graphicAlphaBits = min(max(MIN_ALPHA_BITS, atoi(optarg)),
+ MAX_ALPHA_BITS);
+ if (graphicAlphaBits == 3)
+ fatal("cannot use 3 bits of antialiasing information");
+ break;
+ case 'G':
+ // handled by post-grohtml (don't write CreationDate HTML comment)
+ break;
+ case 'h':
+ // handled by post-grohtml (write headings with font size changes)
+ break;
+ case 'i':
+ image_res = atoi(optarg);
+ break;
+ case 'I':
+ image_template = optarg;
+ break;
+ case 'j':
+ // handled by post-grohtml (set job name for multiple file output)
+ break;
+ case 'l':
+ // handled by post-grohtml (no automatic section links)
+ break;
+ case 'n':
+ // handled by post-grohtml (generate simple heading anchors)
+ break;
+ case 'o':
+ vertical_offset = atoi(optarg);
+ break;
+ case 'p':
+ want_progress_report = true;
+ break;
+ case 'r':
+ // handled by post-grohtml (no header and footer lines)
+ break;
+ case 's':
+ // handled by post-grohtml (use font size n as the HTML base size)
+ break;
+ case 'S':
+ // handled by post-grohtml (set file split level)
+ break;
+ case 'v':
+ printf("GNU pre-grohtml (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ case 'V':
+ // handled by post-grohtml (create validator button)
+ break;
+ case 'x':
+ // html dialect
+ if (strcmp(optarg, "x") == 0)
+ dialect = xhtml;
+ else if (strcmp(optarg, "4") == 0)
+ dialect = html4;
+ else
+ warning("unsupported HTML dialect: '%1'", optarg);
+ break;
+ case 'y':
+ // handled by post-grohtml (create groff signature)
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ break;
+ }
+
+ i = optind;
+ while (i < argc) {
+ if (strcmp(argv[i], troff_name) == 0)
+ troff_arg = i;
+ else if (argv[i][0] != '-')
+ return i;
+ i++;
+ }
+ delete[] troff_name;
+
+ return argc;
+}
+
+/*
+ * makeTempFiles - Name the temporary files.
+ */
+
+static void makeTempFiles(void)
+{
+#if defined(DEBUGGING)
+ psFileName = DEBUG_FILE("prehtml-ps");
+ regionFileName = DEBUG_FILE("prehtml-region");
+ imagePageName = DEBUG_FILE("prehtml-page");
+ psPageName = DEBUG_FILE("prehtml-psn");
+ troffFileName = DEBUG_FILE("prehtml-troff");
+ htmlFileName = DEBUG_FILE("prehtml-html");
+#else /* not DEBUGGING */
+ FILE *f;
+
+ // psPageName contains a single page of PostScript.
+ f = xtmpfile(&psPageName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT, true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+
+ // imagePageName contains a bitmap image of a single PostScript page.
+ f = xtmpfile(&imagePageName, PAGE_TEMPLATE_LONG, PAGE_TEMPLATE_SHORT,
+ true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+
+ // psFileName contains a PostScript file of the complete document.
+ f = xtmpfile(&psFileName, PS_TEMPLATE_LONG, PS_TEMPLATE_SHORT, true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+
+ // regionFileName contains a list of the images and their boxed
+ // coordinates.
+ f = xtmpfile(&regionFileName,
+ REGION_TEMPLATE_LONG, REGION_TEMPLATE_SHORT, true);
+ if (0 /* nullptr */ == f)
+ sys_fatal("xtmpfile");
+ fclose(f);
+#endif /* not DEBUGGING */
+}
+
+static bool do_file(const char *filename)
+{
+ FILE *fp;
+
+ current_filename = filename;
+ if (strcmp(filename, "-") == 0)
+ fp = stdin;
+ else {
+ fp = fopen(filename, "r");
+ if (0 /* nullptr*/ == fp) {
+ error("unable to open '%1': %2", filename, strerror(errno));
+ return false;
+ }
+ }
+ inputFile.read_file(fp);
+ if (fp != stdin)
+ if (fclose(fp) != 0)
+ sys_fatal("fclose");
+ current_filename = 0 /* nullptr */;
+ return true;
+}
+
+static void cleanup(void)
+{
+ free(const_cast<char *>(image_gen));
+}
+
+int main(int argc, char **argv)
+{
+#ifdef CAPTURE_MODE
+ fprintf(stderr, "%s: invoked with %d arguments ...\n", argv[0], argc);
+ for (int i = 0; i < argc; i++)
+ fprintf(stderr, "%2d: %s\n", i, argv[i]);
+ FILE *dump = fopen(DEBUG_FILE("pre-html-data"), "wb");
+ if (dump != 0 /* nullptr */) {
+ while((int ch = fgetc(stdin)) >= 0)
+ fputc(ch, dump);
+ fclose(dump);
+ }
+ exit(EXIT_FAILURE);
+#endif /* CAPTURE_MODE */
+ program_name = argv[0];
+ if (atexit(&cleanup) != 0)
+ sys_fatal("atexit");
+ int operand_index = scanArguments(argc, argv);
+ image_gen = strsave(get_image_generator());
+ if (0 == image_gen)
+ fatal("'image_generator' directive not found in file '%1'",
+ devhtml_desc);
+ postscriptRes = get_resolution();
+ if (postscriptRes < 1) // TODO: what's a more sane minimum value?
+ fatal("'res' directive missing or invalid in file '%1'",
+ devps_desc);
+ setupAntiAlias();
+ checkImageDir();
+ makeFileName();
+ bool have_file_operand = false;
+ while (operand_index < argc) {
+ if (argv[operand_index][0] != '-') {
+ if(!do_file(argv[operand_index]))
+ exit(EXIT_FAILURE);
+ have_file_operand = true;
+ }
+ operand_index++;
+ }
+
+ if (!have_file_operand)
+ do_file("-");
+ makeTempFiles();
+ int wstatus = inputFile.do_image(argc, argv);
+ if (wstatus == 0) {
+ generateImages(regionFileName);
+ wstatus = inputFile.do_html(argc, argv);
+ }
+ else
+ if (WEXITSTATUS(wstatus) != 0)
+ // XXX: This is a crappy suggestion. See Savannah #62673.
+ fatal("'%1' exited with status %2; re-run with a different output"
+ " driver to see diagnostic messages", argv[0],
+ WEXITSTATUS(wstatus));
+ exit(EXIT_SUCCESS);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/html/pre-html.h b/src/preproc/html/pre-html.h
new file mode 100644
index 0000000..f257854
--- /dev/null
+++ b/src/preproc/html/pre-html.h
@@ -0,0 +1,38 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ * Written by Gaius Mulley (gaius@glam.ac.uk).
+ *
+ * This file is part of groff.
+ *
+ * groff is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * groff is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with groff; see the file COPYING. If not, write to the Free Software
+ * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * defines functions implemented within pre-html.cpp
+ */
+
+#if !defined(PREHTMLH)
+# define PREHTMLH
+# if defined(PREHTMLC)
+# define EXTERN
+# else
+# define EXTERN extern
+# endif
+
+
+extern void sys_fatal (const char *s);
+
+#undef EXTERN
+#endif
diff --git a/src/preproc/html/pushback.cpp b/src/preproc/html/pushback.cpp
new file mode 100644
index 0000000..100ac0b
--- /dev/null
+++ b/src/preproc/html/pushback.cpp
@@ -0,0 +1,336 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk).
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <signal.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "posix.h"
+#include "nonposix.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "pushback.h"
+#include "pre-html.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+# define ERROR(X) (void)(fprintf(stderr, "%s:%d error %s\n", __FILE__, __LINE__, X) && \
+ (fflush(stderr)) && localexit(1))
+
+
+#define MAXPUSHBACKSTACK 4096 /* maximum number of character that can be pushed back */
+
+
+/*
+ * constructor for pushBackBuffer
+ */
+
+pushBackBuffer::pushBackBuffer (char *filename)
+{
+ charStack = (char *)malloc(MAXPUSHBACKSTACK);
+ if (charStack == 0) {
+ sys_fatal("malloc");
+ }
+ stackPtr = 0; /* index to push back stack */
+ verbose = 0;
+ eofFound = FALSE;
+ lineNo = 1;
+ if (strcmp(filename, "") != 0) {
+ stdIn = dup(0);
+ if (stdIn < 0) {
+ sys_fatal("dup stdin");
+ }
+ close(0);
+ if (open(filename, O_RDONLY) != 0) {
+ sys_fatal("when trying to open file");
+ } else {
+ fileName = filename;
+ }
+ }
+}
+
+pushBackBuffer::~pushBackBuffer ()
+{
+ if (charStack != 0) {
+ free(charStack);
+ }
+ close(0);
+ /* restore stdin in file descriptor 0 */
+ if (dup(stdIn) < 0) {
+ sys_fatal("restore stdin");
+ }
+ close(stdIn);
+}
+
+/*
+ * localexit - wraps exit with a return code to aid the ERROR macro.
+ */
+
+int localexit (int i)
+{
+ exit(i);
+ return( 1 );
+}
+
+/*
+ * getPB - returns a character, possibly a pushed back character.
+ */
+
+char pushBackBuffer::getPB (void)
+{
+ if (stackPtr>0) {
+ stackPtr--;
+ return( charStack[stackPtr] );
+ } else {
+ char ch;
+
+ if (read(0, &ch, 1) == 1) {
+ if (verbose) {
+ printf("%c", ch);
+ }
+ if (ch == '\n') {
+ lineNo++;
+ }
+ return( ch );
+ } else {
+ eofFound = TRUE;
+ return( eof );
+ }
+ }
+}
+
+/*
+ * putPB - pushes a character onto the push back stack.
+ * The same character is returned.
+ */
+
+char pushBackBuffer::putPB (char ch)
+{
+ if (stackPtr<MAXPUSHBACKSTACK) {
+ charStack[stackPtr] = ch ;
+ stackPtr++;
+ } else {
+ ERROR("max push back stack exceeded, increase MAXPUSHBACKSTACK constant");
+ }
+ return( ch );
+}
+
+/*
+ * isWhite - returns TRUE if a white character is found. This character is NOT consumed.
+ */
+
+static int isWhite (char ch)
+{
+ return( (ch==' ') || (ch == '\t') || (ch == '\n') );
+}
+
+/*
+ * skipToNewline - skips characters until a newline is seen.
+ */
+
+void pushBackBuffer::skipToNewline (void)
+{
+ while ((putPB(getPB()) != '\n') && (! eofFound)) {
+ getPB();
+ }
+}
+
+/*
+ * skipUntilToken - skips until a token is seen
+ */
+
+void pushBackBuffer::skipUntilToken (void)
+{
+ char ch;
+
+ while ((isWhite(putPB(getPB())) || (putPB(getPB()) == '#')) && (! eofFound)) {
+ ch = getPB();
+ if (ch == '#') {
+ skipToNewline();
+ }
+ }
+}
+
+/*
+ * isString - returns TRUE if the string, s, matches the pushed back string.
+ * if TRUE is returned then this string is consumed, otherwise it is
+ * left alone.
+ */
+
+int pushBackBuffer::isString (const char *s)
+{
+ int length=strlen(s);
+ int i=0;
+
+ while ((i<length) && (putPB(getPB())==s[i])) {
+ if (getPB() != s[i]) {
+ ERROR("assert failed");
+ }
+ i++;
+ }
+ if (i==length) {
+ return( TRUE );
+ } else {
+ i--;
+ while (i>=0) {
+ if (putPB(s[i]) != s[i]) {
+ ERROR("assert failed");
+ }
+ i--;
+ }
+ }
+ return( FALSE );
+}
+
+/*
+ * isDigit - returns TRUE if the character, ch, is a digit.
+ */
+
+static int isDigit (char ch)
+{
+ return( ((ch>='0') && (ch<='9')) );
+}
+
+/*
+ * isHexDigit - returns TRUE if the character, ch, is a hex digit.
+ */
+
+#if 0
+static int isHexDigit (char ch)
+{
+ return( (isDigit(ch)) || ((ch>='a') && (ch<='f')) );
+}
+#endif
+
+/*
+ * readInt - returns an integer from the input stream.
+ */
+
+int pushBackBuffer::readInt (void)
+{
+ int c =0;
+ int i =0;
+ int s =1;
+ char ch=getPB();
+
+ while (isWhite(ch)) {
+ ch=getPB();
+ }
+ // now read integer
+
+ if (ch == '-') {
+ s = -1;
+ ch = getPB();
+ }
+ while (isDigit(ch)) {
+ i *= 10;
+ if ((ch>='0') && (ch<='9')) {
+ i += (int)(ch-'0');
+ }
+ ch = getPB();
+ c++;
+ }
+ if (ch != putPB(ch)) {
+ ERROR("assert failed");
+ }
+ return( i*s );
+}
+
+/*
+ * convertToFloat - converts integers, a and b into a.b
+ */
+
+static double convertToFloat (int a, int b)
+{
+ int c=10;
+ double f;
+
+ while (b>c) {
+ c *= 10;
+ }
+ f = ((double)a) + (((double)b)/((double)c));
+ return( f );
+}
+
+/*
+ * readNumber - returns a float representing the word just read.
+ */
+
+double pushBackBuffer::readNumber (void)
+{
+ int i;
+ char ch;
+
+ i = readInt();
+ if ((ch = getPB()) == '.') {
+ return convertToFloat(i, readInt());
+ }
+ putPB(ch);
+ return (double)i;
+}
+
+/*
+ * readString - reads a string terminated by white space
+ * and returns a malloced area of memory containing
+ * a copy of the characters.
+ */
+
+char *pushBackBuffer::readString (void)
+{
+ char buffer[MAXPUSHBACKSTACK];
+ char *str = 0;
+ int i=0;
+ char ch=getPB();
+
+ while (isWhite(ch)) {
+ ch=getPB();
+ }
+ while ((i < MAXPUSHBACKSTACK) && (! isWhite(ch)) && (! eofFound)) {
+ buffer[i] = ch;
+ i++;
+ ch = getPB();
+ }
+ if (i < MAXPUSHBACKSTACK) {
+ buffer[i] = (char)0;
+ str = (char *)malloc(strlen(buffer)+1);
+ strcpy(str, buffer);
+ }
+ return( str );
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/html/pushback.h b/src/preproc/html/pushback.h
new file mode 100644
index 0000000..263a6c5
--- /dev/null
+++ b/src/preproc/html/pushback.h
@@ -0,0 +1,52 @@
+// -*- C -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk).
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#define eof (char)-1
+
+
+/*
+ * defines the class and methods implemented within pushback.cpp
+ */
+
+class pushBackBuffer
+{
+ private:
+ char *charStack;
+ int stackPtr; /* index to push back stack */
+ int verbose;
+ int eofFound;
+ char *fileName;
+ int lineNo;
+ int stdIn;
+
+ public:
+ pushBackBuffer (char *);
+ ~ pushBackBuffer ();
+ char getPB (void);
+ char putPB (char ch);
+ void skipUntilToken (void);
+ void skipToNewline (void);
+ double readNumber (void);
+ int readInt (void);
+ char *readString (void);
+ int isString (const char *string);
+};
+
+
diff --git a/src/preproc/pic/TODO b/src/preproc/pic/TODO
new file mode 100644
index 0000000..53ca282
--- /dev/null
+++ b/src/preproc/pic/TODO
@@ -0,0 +1,35 @@
+In troff mode, dotted and dashed splines.
+
+Make DELIMITED have type lstr; this would allow us to give better
+error messages for problems within the body of for and if constructs.
+
+In troff mode without -x, fake \D't' with .ps commands.
+
+Perhaps an option to set command char.
+
+Add an output class for dumb line printers. It wouldn't be pretty but
+it would be better than nothing. Integrate it with texinfo. Useful
+for groff -Tascii as well.
+
+Option to allow better positioning of arrowheads on arcs.
+
+Perhaps add PostScript output mode.
+
+Change the interface to the output class so that output devices have
+the opportunity to handle arrowheads themselves.
+
+Consider whether the line thickness should scale.
+
+Consider whether the test in a for loop should be fuzzy (as it
+apparently is in grap).
+
+Possibly change fillval so that zero is black.
+
+Provide a way of getting text blocks (positioned with '.in' rather
+than \h), into pic. Should be possible to use block of diverted text
+in pic. Possibly something similar to T{ and T} in tbl.
+
+Option to provide macro backtraces.
+
+Have a path that is searched by 'copy' statement. Set by environment
+variable or command-line option.
diff --git a/src/preproc/pic/common.cpp b/src/preproc/pic/common.cpp
new file mode 100644
index 0000000..6a4a93e
--- /dev/null
+++ b/src/preproc/pic/common.cpp
@@ -0,0 +1,647 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pic.h"
+#include "common.h"
+
+// output a dashed circle as a series of arcs
+
+void common_output::dashed_circle(const position &cent, double rad,
+ const line_type &lt)
+{
+ assert(lt.type == line_type::dashed);
+ line_type slt = lt;
+ slt.type = line_type::solid;
+ double dash_angle = lt.dash_width/rad;
+ int ndashes;
+ double gap_angle;
+ if (dash_angle >= M_PI/4.0) {
+ if (dash_angle < M_PI/2.0) {
+ gap_angle = M_PI/2.0 - dash_angle;
+ ndashes = 4;
+ }
+ else if (dash_angle < M_PI) {
+ gap_angle = M_PI - dash_angle;
+ ndashes = 2;
+ }
+ else {
+ circle(cent, rad, slt, -1.0);
+ return;
+ }
+ }
+ else {
+ ndashes = 4*int(ceil(M_PI/(4.0*dash_angle)));
+ gap_angle = (M_PI*2.0)/ndashes - dash_angle;
+ }
+ for (int i = 0; i < ndashes; i++) {
+ double start_angle = i*(dash_angle+gap_angle) - dash_angle/2.0;
+ solid_arc(cent, rad, start_angle, start_angle + dash_angle, lt);
+ }
+}
+
+// output a dotted circle as a series of dots
+
+void common_output::dotted_circle(const position &cent, double rad,
+ const line_type &lt)
+{
+ assert(lt.type == line_type::dotted);
+ double gap_angle = lt.dash_width/rad;
+ int ndots;
+ if (gap_angle >= M_PI/2.0) {
+ // always have at least 2 dots
+ gap_angle = M_PI;
+ ndots = 2;
+ }
+ else {
+ ndots = 4*int(M_PI/(2.0*gap_angle));
+ gap_angle = (M_PI*2.0)/ndots;
+ }
+ double ang = 0.0;
+ for (int i = 0; i < ndots; i++, ang += gap_angle)
+ dot(cent + position(cos(ang), sin(ang))*rad, lt);
+}
+
+// recursive function for dash drawing, used by dashed_ellipse
+
+void common_output::ellipse_arc(const position &cent,
+ const position &z0, const position &z1,
+ const distance &dim, const line_type &lt)
+{
+ assert(lt.type == line_type::solid);
+ assert(dim.x != 0 && dim.y != 0);
+ double eps = 0.0001;
+ position zml = (z0 + z1) / 2;
+ // apply affine transformation (from ellipse to circle) to compute angle
+ // of new position, then invert transformation to get exact position
+ double psi = atan2(zml.y / dim.y, zml.x / dim.x);
+ position zm = position(dim.x * cos(psi), dim.y * sin(psi));
+ // to approximate the ellipse arc with one or more circle arcs, we
+ // first compute the radius of curvature in zm
+ double a_2 = dim.x * dim.x;
+ double a_4 = a_2 * a_2;
+ double b_2 = dim.y * dim.y;
+ double b_4 = b_2 * b_2;
+ double e_2 = a_2 - b_2;
+ double temp = a_4 * zm.y * zm.y + b_4 * zm.x * zm.x;
+ double rho = sqrt(temp / a_4 / b_4 * temp / a_4 / b_4 * temp);
+ // compute center of curvature circle
+ position M = position(e_2 * zm.x / a_2 * zm.x / a_2 * zm.x,
+ -e_2 * zm.y / b_2 * zm.y / b_2 * zm.y);
+ // compute distance between circle and ellipse arc at start and end
+ double phi0 = atan2(z0.y - M.y, z0.x - M.x);
+ double phi1 = atan2(z1.y - M.y, z1.x - M.x);
+ position M0 = position(rho * cos(phi0), rho * sin(phi0)) + M;
+ position M1 = position(rho * cos(phi1), rho * sin(phi1)) + M;
+ double dist0 = hypot(z0 - M0) / sqrt(z0 * z0);
+ double dist1 = hypot(z1 - M1) / sqrt(z1 * z1);
+ if (dist0 < eps && dist1 < eps)
+ solid_arc(M + cent, rho, phi0, phi1, lt);
+ else {
+ ellipse_arc(cent, z0, zm, dim, lt);
+ ellipse_arc(cent, zm, z1, dim, lt);
+ }
+}
+
+// output a dashed ellipse as a series of arcs
+
+void common_output::dashed_ellipse(const position &cent, const distance &dim,
+ const line_type &lt)
+{
+ assert(lt.type == line_type::dashed);
+ double dim_x = dim.x / 2;
+ double dim_y = dim.y / 2;
+ line_type slt = lt;
+ slt.type = line_type::solid;
+ double dw = lt.dash_width;
+ // we use an approximation to compute the ellipse length (found in:
+ // Bronstein, Semendjajew, Taschenbuch der Mathematik)
+ double lambda = (dim.x - dim.y) / (dim.x + dim.y);
+ double le = M_PI / 2 * (dim.x + dim.y)
+ * ((64 - 3 * lambda * lambda * lambda * lambda )
+ / (64 - 16 * lambda * lambda));
+ // for symmetry we make nmax a multiple of 8
+ int nmax = 8 * int(le / dw / 8 + 0.5);
+ if (nmax < 8) {
+ nmax = 8;
+ dw = le / 8;
+ }
+ int ndash = nmax / 2;
+ double gapwidth = (le - dw * ndash) / ndash;
+ double l = 0;
+ position z = position(dim_x, 0);
+ position zdot = z;
+ int j = 0;
+ int jmax = int(10 / lt.dash_width);
+ for (int i = 0; i <= nmax; i++) {
+ position zold = z;
+ position zpre = zdot;
+ double ld = (int(i / 2) + 0.5) * dw + int((i + 1) / 2) * gapwidth;
+ double lold = 0;
+ double dl = 1;
+ // find next position for fixed arc length
+ while (l < ld) {
+ j++;
+ lold = l;
+ zold = z;
+ double phi = j * 2 * M_PI / jmax;
+ z = position(dim_x * cos(phi), dim_y * sin(phi));
+ dl = hypot(z - zold);
+ l += dl;
+ }
+ // interpolate linearly between the last two points,
+ // using the length difference as the scaling factor
+ double delta = (ld - lold) / dl;
+ zdot = zold + (z - zold) * delta;
+ // compute angle of new position on the affine circle
+ // and use it to get the exact value on the ellipse
+ double psi = atan2(zdot.y / dim_y, zdot.x / dim_x);
+ zdot = position(dim_x * cos(psi), dim_y * sin(psi));
+ if ((i % 2 == 0) && (i > 1))
+ ellipse_arc(cent, zpre, zdot, dim / 2, slt);
+ }
+}
+
+// output a dotted ellipse as a series of dots
+
+void common_output::dotted_ellipse(const position &cent, const distance &dim,
+ const line_type &lt)
+{
+ assert(lt.type == line_type::dotted);
+ double dim_x = dim.x / 2;
+ double dim_y = dim.y / 2;
+ line_type slt = lt;
+ slt.type = line_type::solid;
+ // we use an approximation to compute the ellipse length (found in:
+ // Bronstein, Semendjajew, Taschenbuch der Mathematik)
+ double lambda = (dim.x - dim.y) / (dim.x + dim.y);
+ double le = M_PI / 2 * (dim.x + dim.y)
+ * ((64 - 3 * lambda * lambda * lambda * lambda )
+ / (64 - 16 * lambda * lambda));
+ // for symmetry we make nmax a multiple of 4
+ int ndots = 4 * int(le / lt.dash_width / 4 + 0.5);
+ if (ndots < 4)
+ ndots = 4;
+ double l = 0;
+ position z = position(dim_x, 0);
+ int j = 0;
+ int jmax = int(10 / lt.dash_width);
+ for (int i = 1; i <= ndots; i++) {
+ position zold = z;
+ double lold = l;
+ double ld = i * le / ndots;
+ double dl = 1;
+ // find next position for fixed arc length
+ while (l < ld) {
+ j++;
+ lold = l;
+ zold = z;
+ double phi = j * 2 * M_PI / jmax;
+ z = position(dim_x * cos(phi), dim_y * sin(phi));
+ dl = hypot(z - zold);
+ l += dl;
+ }
+ // interpolate linearly between the last two points,
+ // using the length difference as the scaling factor
+ double delta = (ld - lold) / dl;
+ position zdot = zold + (z - zold) * delta;
+ // compute angle of new position on the affine circle
+ // and use it to get the exact value on the ellipse
+ double psi = atan2(zdot.y / dim_y, zdot.x / dim_x);
+ zdot = position(dim_x * cos(psi), dim_y * sin(psi));
+ dot(cent + zdot, slt);
+ }
+}
+
+// return non-zero iff we can compute a center
+
+int compute_arc_center(const position &start, const position &cent,
+ const position &end, position *result)
+{
+ // This finds the point along the vector from start to cent that
+ // is equidistant between start and end.
+ distance c = cent - start;
+ distance e = end - start;
+ double n = c*e;
+ if (n == 0.0)
+ return 0;
+ *result = start + c*((e*e)/(2.0*n));
+ return 1;
+}
+
+// output a dashed arc as a series of arcs
+
+void common_output::dashed_arc(const position &start, const position &cent,
+ const position &end, const line_type &lt)
+{
+ assert(lt.type == line_type::dashed);
+ position c;
+ if (!compute_arc_center(start, cent, end, &c)) {
+ line(start, &end, 1, lt);
+ return;
+ }
+ distance start_offset = start - c;
+ distance end_offset = end - c;
+ double start_angle = atan2(start_offset.y, start_offset.x);
+ double end_angle = atan2(end_offset.y, end_offset.x);
+ double rad = hypot(c - start);
+ double dash_angle = lt.dash_width/rad;
+ double total_angle = end_angle - start_angle;
+ while (total_angle < 0)
+ total_angle += M_PI + M_PI;
+ if (total_angle <= dash_angle*2.0) {
+ solid_arc(cent, rad, start_angle, end_angle, lt);
+ return;
+ }
+ int ndashes = int((total_angle - dash_angle)/(dash_angle*2.0) + .5);
+ double dash_and_gap_angle = (total_angle - dash_angle)/ndashes;
+ for (int i = 0; i <= ndashes; i++)
+ solid_arc(cent, rad, start_angle + i*dash_and_gap_angle,
+ start_angle + i*dash_and_gap_angle + dash_angle, lt);
+}
+
+// output a dotted arc as a series of dots
+
+void common_output::dotted_arc(const position &start, const position &cent,
+ const position &end, const line_type &lt)
+{
+ assert(lt.type == line_type::dotted);
+ position c;
+ if (!compute_arc_center(start, cent, end, &c)) {
+ line(start, &end, 1, lt);
+ return;
+ }
+ distance start_offset = start - c;
+ distance end_offset = end - c;
+ double start_angle = atan2(start_offset.y, start_offset.x);
+ double total_angle = atan2(end_offset.y, end_offset.x) - start_angle;
+ while (total_angle < 0)
+ total_angle += M_PI + M_PI;
+ double rad = hypot(c - start);
+ int ndots = int(total_angle/(lt.dash_width/rad) + .5);
+ if (ndots == 0)
+ dot(start, lt);
+ else {
+ for (int i = 0; i <= ndots; i++) {
+ double a = start_angle + (total_angle*i)/ndots;
+ dot(cent + position(cos(a), sin(a))*rad, lt);
+ }
+ }
+}
+
+void common_output::solid_arc(const position &cent, double rad,
+ double start_angle, double end_angle,
+ const line_type &lt)
+{
+ line_type slt = lt;
+ slt.type = line_type::solid;
+ arc(cent + position(cos(start_angle), sin(start_angle))*rad,
+ cent,
+ cent + position(cos(end_angle), sin(end_angle))*rad,
+ slt);
+}
+
+
+void common_output::rounded_box(const position &cent, const distance &dim,
+ double rad, const line_type &lt,
+ double fill, char *color_fill)
+{
+ if (fill >= 0.0 || color_fill)
+ filled_rounded_box(cent, dim, rad, fill);
+ switch (lt.type) {
+ case line_type::invisible:
+ break;
+ case line_type::dashed:
+ dashed_rounded_box(cent, dim, rad, lt);
+ break;
+ case line_type::dotted:
+ dotted_rounded_box(cent, dim, rad, lt);
+ break;
+ case line_type::solid:
+ solid_rounded_box(cent, dim, rad, lt);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+
+void common_output::dashed_rounded_box(const position &cent,
+ const distance &dim, double rad,
+ const line_type &lt)
+{
+ line_type slt = lt;
+ slt.type = line_type::solid;
+
+ double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad;
+ int n_hor_dashes = int(hor_length/(lt.dash_width*2.0) + .5);
+ double hor_gap_width = (n_hor_dashes != 0
+ ? hor_length/n_hor_dashes - lt.dash_width
+ : 0.0);
+
+ double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad;
+ int n_vert_dashes = int(vert_length/(lt.dash_width*2.0) + .5);
+ double vert_gap_width = (n_vert_dashes != 0
+ ? vert_length/n_vert_dashes - lt.dash_width
+ : 0.0);
+ // Note that each corner arc has to be split into two for dashing,
+ // because one part is dashed using vert_gap_width, and the other
+ // using hor_gap_width.
+ double offset = lt.dash_width/2.0;
+ dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
+ -M_PI/4.0, 0, slt, lt.dash_width, vert_gap_width, &offset);
+ dash_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad),
+ cent + position(dim.x/2.0, dim.y/2.0 - rad),
+ slt, lt.dash_width, vert_gap_width, &offset);
+ dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
+ 0, M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset);
+
+ offset = lt.dash_width/2.0;
+ dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
+ M_PI/4.0, M_PI/2, slt, lt.dash_width, hor_gap_width, &offset);
+ dash_line(cent + position(dim.x/2.0 - rad, dim.y/2.0),
+ cent + position(-dim.x/2.0 + rad, dim.y/2.0),
+ slt, lt.dash_width, hor_gap_width, &offset);
+ dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
+ M_PI/2, 3*M_PI/4.0, slt, lt.dash_width, hor_gap_width, &offset);
+
+ offset = lt.dash_width/2.0;
+ dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
+ 3.0*M_PI/4.0, M_PI, slt, lt.dash_width, vert_gap_width, &offset);
+ dash_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad),
+ cent + position(-dim.x/2.0, -dim.y/2.0 + rad),
+ slt, lt.dash_width, vert_gap_width, &offset);
+ dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
+ M_PI, 5.0*M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset);
+
+ offset = lt.dash_width/2.0;
+ dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
+ 5*M_PI/4.0, 3*M_PI/2.0, slt, lt.dash_width, hor_gap_width, &offset);
+ dash_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0),
+ cent + position(dim.x/2.0 - rad, -dim.y/2.0),
+ slt, lt.dash_width, hor_gap_width, &offset);
+ dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
+ 3*M_PI/2, 7*M_PI/4, slt, lt.dash_width, hor_gap_width, &offset);
+}
+
+// Used by dashed_rounded_box.
+
+void common_output::dash_arc(const position &cent, double rad,
+ double start_angle, double end_angle,
+ const line_type &lt,
+ double dash_width, double gap_width,
+ double *offsetp)
+{
+ double length = (end_angle - start_angle)*rad;
+ double pos = 0.0;
+ for (;;) {
+ if (*offsetp >= dash_width) {
+ double rem = dash_width + gap_width - *offsetp;
+ if (pos + rem > length) {
+ *offsetp += length - pos;
+ break;
+ }
+ else {
+ pos += rem;
+ *offsetp = 0.0;
+ }
+ }
+ else {
+ double rem = dash_width - *offsetp;
+ if (pos + rem > length) {
+ solid_arc(cent, rad, start_angle + pos/rad, end_angle, lt);
+ *offsetp += length - pos;
+ break;
+ }
+ else {
+ solid_arc(cent, rad, start_angle + pos/rad,
+ start_angle + (pos + rem)/rad, lt);
+ pos += rem;
+ *offsetp = dash_width;
+ }
+ }
+ }
+}
+
+// Used by dashed_rounded_box.
+
+void common_output::dash_line(const position &start, const position &end,
+ const line_type &lt,
+ double dash_width, double gap_width,
+ double *offsetp)
+{
+ distance dist = end - start;
+ double length = hypot(dist);
+ if (length == 0.0)
+ return;
+ double pos = 0.0;
+ for (;;) {
+ if (*offsetp >= dash_width) {
+ double rem = dash_width + gap_width - *offsetp;
+ if (pos + rem > length) {
+ *offsetp += length - pos;
+ break;
+ }
+ else {
+ pos += rem;
+ *offsetp = 0.0;
+ }
+ }
+ else {
+ double rem = dash_width - *offsetp;
+ if (pos + rem > length) {
+ line(start + dist*(pos/length), &end, 1, lt);
+ *offsetp += length - pos;
+ break;
+ }
+ else {
+ position p(start + dist*((pos + rem)/length));
+ line(start + dist*(pos/length), &p, 1, lt);
+ pos += rem;
+ *offsetp = dash_width;
+ }
+ }
+ }
+}
+
+void common_output::dotted_rounded_box(const position &cent,
+ const distance &dim, double rad,
+ const line_type &lt)
+{
+ line_type slt = lt;
+ slt.type = line_type::solid;
+
+ double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad;
+ int n_hor_dots = int(hor_length/lt.dash_width + .5);
+ double hor_gap_width = (n_hor_dots != 0
+ ? hor_length/n_hor_dots
+ : lt.dash_width);
+
+ double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad;
+ int n_vert_dots = int(vert_length/lt.dash_width + .5);
+ double vert_gap_width = (n_vert_dots != 0
+ ? vert_length/n_vert_dots
+ : lt.dash_width);
+ double epsilon = lt.dash_width/(rad*100.0);
+
+ double offset = 0.0;
+ dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
+ -M_PI/4.0, 0, slt, vert_gap_width, &offset);
+ dot_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad),
+ cent + position(dim.x/2.0, dim.y/2.0 - rad),
+ slt, vert_gap_width, &offset);
+ dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
+ 0, M_PI/4.0 - epsilon, slt, vert_gap_width, &offset);
+
+ offset = 0.0;
+ dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
+ M_PI/4.0, M_PI/2, slt, hor_gap_width, &offset);
+ dot_line(cent + position(dim.x/2.0 - rad, dim.y/2.0),
+ cent + position(-dim.x/2.0 + rad, dim.y/2.0),
+ slt, hor_gap_width, &offset);
+ dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
+ M_PI/2, 3*M_PI/4.0 - epsilon, slt, hor_gap_width, &offset);
+
+ offset = 0.0;
+ dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
+ 3.0*M_PI/4.0, M_PI, slt, vert_gap_width, &offset);
+ dot_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad),
+ cent + position(-dim.x/2.0, -dim.y/2.0 + rad),
+ slt, vert_gap_width, &offset);
+ dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
+ M_PI, 5.0*M_PI/4.0 - epsilon, slt, vert_gap_width, &offset);
+
+ offset = 0.0;
+ dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
+ 5*M_PI/4.0, 3*M_PI/2.0, slt, hor_gap_width, &offset);
+ dot_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0),
+ cent + position(dim.x/2.0 - rad, -dim.y/2.0),
+ slt, hor_gap_width, &offset);
+ dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
+ 3*M_PI/2, 7*M_PI/4 - epsilon, slt, hor_gap_width, &offset);
+}
+
+// Used by dotted_rounded_box.
+
+void common_output::dot_arc(const position &cent, double rad,
+ double start_angle, double end_angle,
+ const line_type &lt, double gap_width,
+ double *offsetp)
+{
+ double length = (end_angle - start_angle)*rad;
+ double pos = 0.0;
+ for (;;) {
+ if (*offsetp == 0.0) {
+ double ang = start_angle + pos/rad;
+ dot(cent + position(cos(ang), sin(ang))*rad, lt);
+ }
+ double rem = gap_width - *offsetp;
+ if (pos + rem > length) {
+ *offsetp += length - pos;
+ break;
+ }
+ else {
+ pos += rem;
+ *offsetp = 0.0;
+ }
+ }
+}
+
+// Used by dotted_rounded_box.
+
+void common_output::dot_line(const position &start, const position &end,
+ const line_type &lt, double gap_width,
+ double *offsetp)
+{
+ distance dist = end - start;
+ double length = hypot(dist);
+ if (length == 0.0)
+ return;
+ double pos = 0.0;
+ for (;;) {
+ if (*offsetp == 0.0)
+ dot(start + dist*(pos/length), lt);
+ double rem = gap_width - *offsetp;
+ if (pos + rem > length) {
+ *offsetp += length - pos;
+ break;
+ }
+ else {
+ pos += rem;
+ *offsetp = 0.0;
+ }
+ }
+}
+
+void common_output::solid_rounded_box(const position &cent,
+ const distance &dim, double rad,
+ const line_type &lt)
+{
+ position tem = cent - dim/2.0;
+ arc(tem + position(0.0, rad),
+ tem + position(rad, rad),
+ tem + position(rad, 0.0),
+ lt);
+ tem = cent + position(-dim.x/2.0, dim.y/2.0);
+ arc(tem + position(rad, 0.0),
+ tem + position(rad, -rad),
+ tem + position(0.0, -rad),
+ lt);
+ tem = cent + dim/2.0;
+ arc(tem + position(0.0, -rad),
+ tem + position(-rad, -rad),
+ tem + position(-rad, 0.0),
+ lt);
+ tem = cent + position(dim.x/2.0, -dim.y/2.0);
+ arc(tem + position(-rad, 0.0),
+ tem + position(-rad, rad),
+ tem + position(0.0, rad),
+ lt);
+ position end;
+ end = cent + position(-dim.x/2.0, dim.y/2.0 - rad);
+ line(cent - dim/2.0 + position(0.0, rad), &end, 1, lt);
+ end = cent + position(dim.x/2.0 - rad, dim.y/2.0);
+ line(cent + position(-dim.x/2.0 + rad, dim.y/2.0), &end, 1, lt);
+ end = cent + position(dim.x/2.0, -dim.y/2.0 + rad);
+ line(cent + position(dim.x/2.0, dim.y/2.0 - rad), &end, 1, lt);
+ end = cent + position(-dim.x/2.0 + rad, -dim.y/2.0);
+ line(cent + position(dim.x/2.0 - rad, -dim.y/2.0), &end, 1, lt);
+}
+
+void common_output::filled_rounded_box(const position &cent,
+ const distance &dim, double rad,
+ double fill)
+{
+ line_type ilt;
+ ilt.type = line_type::invisible;
+ circle(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, ilt, fill);
+ circle(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, ilt, fill);
+ circle(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, ilt, fill);
+ circle(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, ilt, fill);
+ position vec[4];
+ vec[0] = cent + position(dim.x/2.0, dim.y/2.0 - rad);
+ vec[1] = cent + position(-dim.x/2.0, dim.y/2.0 - rad);
+ vec[2] = cent + position(-dim.x/2.0, -dim.y/2.0 + rad);
+ vec[3] = cent + position(dim.x/2.0, -dim.y/2.0 + rad);
+ polygon(vec, 4, ilt, fill);
+ vec[0] = cent + position(dim.x/2.0 - rad, dim.y/2.0);
+ vec[1] = cent + position(-dim.x/2.0 + rad, dim.y/2.0);
+ vec[2] = cent + position(-dim.x/2.0 + rad, -dim.y/2.0);
+ vec[3] = cent + position(dim.x/2.0 - rad, -dim.y/2.0);
+ polygon(vec, 4, ilt, fill);
+}
diff --git a/src/preproc/pic/common.h b/src/preproc/pic/common.h
new file mode 100644
index 0000000..a2d5f70
--- /dev/null
+++ b/src/preproc/pic/common.h
@@ -0,0 +1,79 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class common_output : public output {
+private:
+ void dash_line(const position &start, const position &end,
+ const line_type &lt, double dash_width, double gap_width,
+ double *offsetp);
+ void dash_arc(const position &cent, double rad,
+ double start_angle, double end_angle, const line_type &lt,
+ double dash_width, double gap_width, double *offsetp);
+ void dot_line(const position &start, const position &end,
+ const line_type &lt, double gap_width, double *offsetp);
+ void dot_arc(const position &cent, double rad,
+ double start_angle, double end_angle, const line_type &lt,
+ double gap_width, double *offsetp);
+protected:
+ virtual void dot(const position &, const line_type &) = 0;
+ void ellipse_arc(const position &, const position &,
+ const position &, const distance &,
+ const line_type &);
+ void dashed_circle(const position &, double rad, const line_type &);
+ void dotted_circle(const position &, double rad, const line_type &);
+ void dashed_ellipse(const position &, const distance &, const line_type &);
+ void dotted_ellipse(const position &, const distance &, const line_type &);
+ void dashed_arc(const position &, const position &, const position &,
+ const line_type &);
+ void dotted_arc(const position &, const position &, const position &,
+ const line_type &);
+ virtual void solid_arc(const position &cent, double rad, double start_angle,
+ double end_angle, const line_type &lt);
+ void dashed_rounded_box(const position &, const distance &, double,
+ const line_type &);
+ void dotted_rounded_box(const position &, const distance &, double,
+ const line_type &);
+ void solid_rounded_box(const position &, const distance &, double,
+ const line_type &);
+ void filled_rounded_box(const position &, const distance &, double,
+ double);
+public:
+ void start_picture(double sc, const position &ll, const position &ur) = 0;
+ void finish_picture() = 0;
+ void circle(const position &, double rad, const line_type &, double) = 0;
+ void text(const position &, text_piece *, int, double) = 0;
+ void line(const position &, const position *, int n, const line_type &) = 0;
+ void polygon(const position *, int n, const line_type &, double) = 0;
+ void spline(const position &, const position *, int n,
+ const line_type &) = 0;
+ void arc(const position &, const position &, const position &,
+ const line_type &) = 0;
+ void ellipse(const position &, const distance &,
+ const line_type &, double) = 0;
+ void rounded_box(const position &, const distance &, double,
+ const line_type &, double, char *);
+ void set_color(char *, char *) = 0;
+ void reset_color() = 0;
+ char *get_last_filled() = 0;
+ char *get_outline_color() = 0;
+};
+
+int compute_arc_center(const position &start, const position &cent,
+ const position &end, position *result);
+
diff --git a/src/preproc/pic/lex.cpp b/src/preproc/pic/lex.cpp
new file mode 100644
index 0000000..e59801b
--- /dev/null
+++ b/src/preproc/pic/lex.cpp
@@ -0,0 +1,2039 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pic.h"
+#include "ptable.h"
+#include "object.h"
+#include "pic.hpp"
+
+declare_ptable(char)
+implement_ptable(char)
+
+PTABLE(char) macro_table;
+
+// First character of the range representing $1-$<MAX_ARG>.
+// All of them must be invalid input characters.
+#ifndef IS_EBCDIC_HOST
+#define ARG1 0x80
+#define MAX_ARG 32
+#else
+#define ARG1 0x30
+#define MAX_ARG 16
+#endif
+
+class macro_input : public input {
+ char *s;
+ char *p;
+public:
+ macro_input(const char *);
+ ~macro_input();
+ int get();
+ int peek();
+};
+
+class argument_macro_input : public input {
+ char *s;
+ char *p;
+ char *ap;
+ int argc;
+ char *argv[MAX_ARG];
+public:
+ argument_macro_input(const char *, int, char **);
+ ~argument_macro_input();
+ int get();
+ int peek();
+};
+
+input::input() : next(0)
+{
+}
+
+input::~input()
+{
+}
+
+int input::get_location(const char **, int *)
+{
+ return 0;
+}
+
+file_input::file_input(FILE *f, const char *fn)
+: fp(f), filename(fn), lineno(0), ptr("")
+{
+}
+
+file_input::~file_input()
+{
+ fclose(fp);
+}
+
+int file_input::read_line()
+{
+ for (;;) {
+ line.clear();
+ lineno++;
+ for (;;) {
+ int c = getc(fp);
+ if (c == '\r') {
+ c = getc(fp);
+ if (c != '\n')
+ lex_error("invalid input character code %1", '\r');
+ }
+ if (c == EOF)
+ break;
+ else if (is_invalid_input_char(c))
+ lex_error("invalid input character code %1", c);
+ else {
+ line += char(c);
+ if (c == '\n')
+ break;
+ }
+ }
+ if (line.length() == 0)
+ return 0;
+ if (!(line.length() >= 3 && line[0] == '.' && line[1] == 'P'
+ && (line[2] == 'S' || line[2] == 'E' || line[2] == 'F')
+ && (line.length() == 3 || line[3] == ' ' || line[3] == '\n'
+ || compatible_flag))) {
+ line += '\0';
+ ptr = line.contents();
+ return 1;
+ }
+ }
+}
+
+int file_input::get()
+{
+ if (*ptr != '\0' || read_line())
+ return (unsigned char)*ptr++;
+ else
+ return EOF;
+}
+
+int file_input::peek()
+{
+ if (*ptr != '\0' || read_line())
+ return (unsigned char)*ptr;
+ else
+ return EOF;
+}
+
+int file_input::get_location(const char **fnp, int *lnp)
+{
+ *fnp = filename;
+ *lnp = lineno;
+ return 1;
+}
+
+macro_input::macro_input(const char *str)
+{
+ p = s = strsave(str);
+}
+
+macro_input::~macro_input()
+{
+ free(s);
+}
+
+int macro_input::get()
+{
+ if (p == 0 || *p == '\0')
+ return EOF;
+ else
+ return (unsigned char)*p++;
+}
+
+int macro_input::peek()
+{
+ if (p == 0 || *p == '\0')
+ return EOF;
+ else
+ return (unsigned char)*p;
+}
+
+char *process_body(const char *body)
+{
+ char *s = strsave(body);
+ int j = 0;
+ for (int i = 0; s[i] != '\0'; i++)
+ if (s[i] == '$' && csdigit(s[i + 1])) {
+ int n = 0;
+ int start = i;
+ i++;
+ while (csdigit(s[i]))
+ if (n > MAX_ARG)
+ i++;
+ else
+ n = 10 * n + s[i++] - '0';
+ if (n > MAX_ARG) {
+ string arg;
+ for (int k = start; k < i; k++)
+ arg += s[k];
+ lex_error("invalid macro argument number %1", arg.contents());
+ }
+ else if (n > 0)
+ s[j++] = ARG1 + n - 1;
+ i--;
+ }
+ else
+ s[j++] = s[i];
+ s[j] = '\0';
+ return s;
+}
+
+argument_macro_input::argument_macro_input(const char *body, int ac, char **av)
+: ap(0), argc(ac)
+{
+ for (int i = 0; i < argc; i++)
+ argv[i] = av[i];
+ p = s = process_body(body);
+}
+
+argument_macro_input::~argument_macro_input()
+{
+ for (int i = 0; i < argc; i++)
+ free(argv[i]);
+ free(s);
+}
+
+int argument_macro_input::get()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return (unsigned char)*ap++;
+ ap = 0;
+ }
+ if (p == 0)
+ return EOF;
+ while ((unsigned char)*p >= ARG1
+ && (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
+ int i = (unsigned char)*p++ - ARG1;
+ if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
+ ap = argv[i];
+ return (unsigned char)*ap++;
+ }
+ }
+ if (*p == '\0')
+ return EOF;
+ return (unsigned char)*p++;
+}
+
+int argument_macro_input::peek()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return (unsigned char)*ap;
+ ap = 0;
+ }
+ if (p == 0)
+ return EOF;
+ while ((unsigned char)*p >= ARG1
+ && (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
+ int i = (unsigned char)*p++ - ARG1;
+ if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
+ ap = argv[i];
+ return (unsigned char)*ap;
+ }
+ }
+ if (*p == '\0')
+ return EOF;
+ return (unsigned char)*p;
+}
+
+class input_stack {
+ static input *current_input;
+ static int bol_flag;
+public:
+ static void push(input *);
+ static void clear();
+ static int get_char();
+ static int peek_char();
+ static int get_location(const char **fnp, int *lnp);
+ static void push_back(unsigned char c, int was_bol = 0);
+ static int bol();
+};
+
+input *input_stack::current_input = 0;
+int input_stack::bol_flag = 0;
+
+inline int input_stack::bol()
+{
+ return bol_flag;
+}
+
+void input_stack::clear()
+{
+ while (current_input != 0) {
+ input *tem = current_input;
+ current_input = current_input->next;
+ delete tem;
+ }
+ bol_flag = 1;
+}
+
+void input_stack::push(input *in)
+{
+ in->next = current_input;
+ current_input = in;
+}
+
+void lex_init(input *top)
+{
+ input_stack::clear();
+ input_stack::push(top);
+}
+
+void lex_cleanup()
+{
+ while (input_stack::get_char() != EOF)
+ ;
+}
+
+int input_stack::get_char()
+{
+ while (current_input != 0) {
+ int c = current_input->get();
+ if (c != EOF) {
+ bol_flag = c == '\n';
+ return c;
+ }
+ // don't pop the top-level input off the stack
+ if (current_input->next == 0)
+ return EOF;
+ input *tem = current_input;
+ current_input = current_input->next;
+ delete tem;
+ }
+ return EOF;
+}
+
+int input_stack::peek_char()
+{
+ while (current_input != 0) {
+ int c = current_input->peek();
+ if (c != EOF)
+ return c;
+ if (current_input->next == 0)
+ return EOF;
+ input *tem = current_input;
+ current_input = current_input->next;
+ delete tem;
+ }
+ return EOF;
+}
+
+class char_input : public input {
+ int c;
+public:
+ char_input(int);
+ int get();
+ int peek();
+};
+
+char_input::char_input(int n) : c((unsigned char)n)
+{
+}
+
+int char_input::get()
+{
+ int n = c;
+ c = EOF;
+ return n;
+}
+
+int char_input::peek()
+{
+ return c;
+}
+
+void input_stack::push_back(unsigned char c, int was_bol)
+{
+ push(new char_input(c));
+ bol_flag = was_bol;
+}
+
+int input_stack::get_location(const char **fnp, int *lnp)
+{
+ for (input *p = current_input; p; p = p->next)
+ if (p->get_location(fnp, lnp))
+ return 1;
+ return 0;
+}
+
+string context_buffer;
+
+string token_buffer;
+double token_double;
+int token_int;
+
+void interpolate_macro_with_args(const char *body)
+{
+ char *argv[MAX_ARG];
+ int argc = 0;
+ int ignore = 0;
+ int i;
+ for (i = 0; i < MAX_ARG; i++)
+ argv[i] = 0;
+ int level = 0;
+ int c;
+ enum { NORMAL, IN_STRING, IN_STRING_QUOTED } state = NORMAL;
+ do {
+ token_buffer.clear();
+ for (;;) {
+ c = input_stack::get_char();
+ if (c == EOF) {
+ lex_error("end of input while scanning macro arguments");
+ break;
+ }
+ if (state == NORMAL && level == 0 && (c == ',' || c == ')')) {
+ if (token_buffer.length() > 0) {
+ token_buffer += '\0';
+ if (!ignore) {
+ if (argc == MAX_ARG) {
+ lex_warning("only %1 macro arguments supported", MAX_ARG);
+ ignore = 1;
+ }
+ else
+ argv[argc] = strsave(token_buffer.contents());
+ }
+ }
+ // for 'foo()', argc = 0
+ if (argc > 0 || c != ')' || i > 0)
+ if (!ignore)
+ argc++;
+ break;
+ }
+ token_buffer += char(c);
+ switch (state) {
+ case NORMAL:
+ if (c == '"')
+ state = IN_STRING;
+ else if (c == '(')
+ level++;
+ else if (c == ')')
+ level--;
+ break;
+ case IN_STRING:
+ if (c == '"')
+ state = NORMAL;
+ else if (c == '\\')
+ state = IN_STRING_QUOTED;
+ break;
+ case IN_STRING_QUOTED:
+ state = IN_STRING;
+ break;
+ }
+ }
+ } while (c != ')' && c != EOF);
+ input_stack::push(new argument_macro_input(body, argc, argv));
+}
+
+static int docmp(const char *s1, int n1, const char *s2, int n2)
+{
+ if (n1 < n2) {
+ int r = memcmp(s1, s2, n1);
+ return r ? r : -1;
+ }
+ else if (n1 > n2) {
+ int r = memcmp(s1, s2, n2);
+ return r ? r : 1;
+ }
+ else
+ return memcmp(s1, s2, n1);
+}
+
+int lookup_keyword(const char *str, int len)
+{
+ static struct keyword {
+ const char *name;
+ int token;
+ } table[] = {
+ { "Here", HERE },
+ { "above", ABOVE },
+ { "aligned", ALIGNED },
+ { "and", AND },
+ { "arc", ARC },
+ { "arrow", ARROW },
+ { "at", AT },
+ { "atan2", ATAN2 },
+ { "below", BELOW },
+ { "between", BETWEEN },
+ { "bottom", BOTTOM },
+ { "box", BOX },
+ { "by", BY },
+ { "ccw", CCW },
+ { "center", CENTER },
+ { "chop", CHOP },
+ { "circle", CIRCLE },
+ { "color", COLORED },
+ { "colored", COLORED },
+ { "colour", COLORED },
+ { "coloured", COLORED },
+ { "command", COMMAND },
+ { "copy", COPY },
+ { "cos", COS },
+ { "cw", CW },
+ { "dashed", DASHED },
+ { "define", DEFINE },
+ { "diam", DIAMETER },
+ { "diameter", DIAMETER },
+ { "do", DO },
+ { "dotted", DOTTED },
+ { "down", DOWN },
+ { "east", EAST },
+ { "ellipse", ELLIPSE },
+ { "else", ELSE },
+ { "end", END },
+ { "exp", EXP },
+ { "figname", FIGNAME },
+ { "fill", FILL },
+ { "filled", FILL },
+ { "for", FOR },
+ { "from", FROM },
+ { "height", HEIGHT },
+ { "ht", HEIGHT },
+ { "if", IF },
+ { "int", INT },
+ { "invis", INVISIBLE },
+ { "invisible", INVISIBLE },
+ { "last", LAST },
+ { "left", LEFT },
+ { "line", LINE },
+ { "ljust", LJUST },
+ { "log", LOG },
+ { "lower", LOWER },
+ { "max", K_MAX },
+ { "min", K_MIN },
+ { "move", MOVE },
+ { "north", NORTH },
+ { "of", OF },
+ { "outline", OUTLINED },
+ { "outlined", OUTLINED },
+ { "plot", PLOT },
+ { "print", PRINT },
+ { "rad", RADIUS },
+ { "radius", RADIUS },
+ { "rand", RAND },
+ { "reset", RESET },
+ { "right", RIGHT },
+ { "rjust", RJUST },
+ { "same", SAME },
+ { "sh", SH },
+ { "shaded", SHADED },
+ { "sin", SIN },
+ { "solid", SOLID },
+ { "south", SOUTH },
+ { "spline", SPLINE },
+ { "sprintf", SPRINTF },
+ { "sqrt", SQRT },
+ { "srand", SRAND },
+ { "start", START },
+ { "the", THE },
+ { "then", THEN },
+ { "thick", THICKNESS },
+ { "thickness", THICKNESS },
+ { "thru", THRU },
+ { "to", TO },
+ { "top", TOP },
+ { "undef", UNDEF },
+ { "until", UNTIL },
+ { "up", UP },
+ { "upper", UPPER },
+ { "way", WAY },
+ { "west", WEST },
+ { "wid", WIDTH },
+ { "width", WIDTH },
+ { "with", WITH },
+ { "xslanted", XSLANTED },
+ { "yslanted", YSLANTED },
+ };
+
+ const keyword *start = table;
+ const keyword *end = table + sizeof(table)/sizeof(table[0]);
+ while (start < end) {
+ // start <= target < end
+ const keyword *mid = start + (end - start)/2;
+
+ int cmp = docmp(str, len, mid->name, strlen(mid->name));
+ if (cmp == 0)
+ return mid->token;
+ if (cmp < 0)
+ end = mid;
+ else
+ start = mid + 1;
+ }
+ return 0;
+}
+
+int get_token_after_dot(int c)
+{
+ // get_token deals with the case where c is a digit
+ switch (c) {
+ case 'h':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ context_buffer = ".ht";
+ return DOT_HT;
+ }
+ else if (c == 'e') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'i') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'g') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'h') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ context_buffer = ".height";
+ return DOT_HT;
+ }
+ input_stack::push_back('h');
+ }
+ input_stack::push_back('g');
+ }
+ input_stack::push_back('i');
+ }
+ input_stack::push_back('e');
+ }
+ input_stack::push_back('h');
+ return '.';
+ case 'x':
+ input_stack::get_char();
+ context_buffer = ".x";
+ return DOT_X;
+ case 'y':
+ input_stack::get_char();
+ context_buffer = ".y";
+ return DOT_Y;
+ case 'c':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'e') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'n') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'e') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'r') {
+ input_stack::get_char();
+ context_buffer = ".center";
+ return DOT_C;
+ }
+ input_stack::push_back('e');
+ }
+ input_stack::push_back('t');
+ }
+ input_stack::push_back('n');
+ }
+ input_stack::push_back('e');
+ }
+ context_buffer = ".c";
+ return DOT_C;
+ case 'n':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'e') {
+ input_stack::get_char();
+ context_buffer = ".ne";
+ return DOT_NE;
+ }
+ else if (c == 'w') {
+ input_stack::get_char();
+ context_buffer = ".nw";
+ return DOT_NW;
+ }
+ else {
+ context_buffer = ".n";
+ return DOT_N;
+ }
+ break;
+ case 'e':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'n') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'd') {
+ input_stack::get_char();
+ context_buffer = ".end";
+ return DOT_END;
+ }
+ input_stack::push_back('n');
+ context_buffer = ".e";
+ return DOT_E;
+ }
+ context_buffer = ".e";
+ return DOT_E;
+ case 'w':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'i') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'd') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'h') {
+ input_stack::get_char();
+ context_buffer = ".width";
+ return DOT_WID;
+ }
+ input_stack::push_back('t');
+ }
+ context_buffer = ".wid";
+ return DOT_WID;
+ }
+ input_stack::push_back('i');
+ }
+ context_buffer = ".w";
+ return DOT_W;
+ case 's':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'e') {
+ input_stack::get_char();
+ context_buffer = ".se";
+ return DOT_SE;
+ }
+ else if (c == 'w') {
+ input_stack::get_char();
+ context_buffer = ".sw";
+ return DOT_SW;
+ }
+ else {
+ if (c == 't') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'a') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'r') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ context_buffer = ".start";
+ return DOT_START;
+ }
+ input_stack::push_back('r');
+ }
+ input_stack::push_back('a');
+ }
+ input_stack::push_back('t');
+ }
+ context_buffer = ".s";
+ return DOT_S;
+ }
+ break;
+ case 't':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'o') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'p') {
+ input_stack::get_char();
+ context_buffer = ".top";
+ return DOT_N;
+ }
+ input_stack::push_back('o');
+ }
+ context_buffer = ".t";
+ return DOT_N;
+ case 'l':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'e') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'f') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ context_buffer = ".left";
+ return DOT_W;
+ }
+ input_stack::push_back('f');
+ }
+ input_stack::push_back('e');
+ }
+ context_buffer = ".l";
+ return DOT_W;
+ case 'r':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'a') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'd') {
+ input_stack::get_char();
+ context_buffer = ".rad";
+ return DOT_RAD;
+ }
+ input_stack::push_back('a');
+ }
+ else if (c == 'i') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'g') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'h') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ context_buffer = ".right";
+ return DOT_E;
+ }
+ input_stack::push_back('h');
+ }
+ input_stack::push_back('g');
+ }
+ input_stack::push_back('i');
+ }
+ context_buffer = ".r";
+ return DOT_E;
+ case 'b':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'o') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'o') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'm') {
+ input_stack::get_char();
+ context_buffer = ".bottom";
+ return DOT_S;
+ }
+ input_stack::push_back('o');
+ }
+ input_stack::push_back('t');
+ }
+ context_buffer = ".bot";
+ return DOT_S;
+ }
+ input_stack::push_back('o');
+ }
+ context_buffer = ".b";
+ return DOT_S;
+ default:
+ context_buffer = '.';
+ return '.';
+ }
+}
+
+int get_token(int lookup_flag)
+{
+ context_buffer.clear();
+ for (;;) {
+ int n = 0;
+ int bol = input_stack::bol();
+ int c = input_stack::get_char();
+ if (bol && c == command_char) {
+ token_buffer.clear();
+ token_buffer += c;
+ // the newline is not part of the token
+ for (;;) {
+ c = input_stack::peek_char();
+ if (c == EOF || c == '\n')
+ break;
+ input_stack::get_char();
+ token_buffer += char(c);
+ }
+ context_buffer = token_buffer;
+ return COMMAND_LINE;
+ }
+ switch (c) {
+ case EOF:
+ return EOF;
+ case ' ':
+ case '\t':
+ break;
+ case '\\':
+ {
+ int d = input_stack::peek_char();
+ if (d != '\n') {
+ context_buffer = '\\';
+ return '\\';
+ }
+ input_stack::get_char();
+ break;
+ }
+ case '#':
+ do {
+ c = input_stack::get_char();
+ } while (c != '\n' && c != EOF);
+ if (c == '\n')
+ context_buffer = '\n';
+ return c;
+ case '"':
+ context_buffer = '"';
+ token_buffer.clear();
+ for (;;) {
+ c = input_stack::get_char();
+ if (c == '\\') {
+ context_buffer += '\\';
+ c = input_stack::peek_char();
+ if (c == '"') {
+ input_stack::get_char();
+ token_buffer += '"';
+ context_buffer += '"';
+ }
+ else
+ token_buffer += '\\';
+ }
+ else if (c == '\n') {
+ error("newline in string");
+ break;
+ }
+ else if (c == EOF) {
+ error("missing '\"'");
+ break;
+ }
+ else if (c == '"') {
+ context_buffer += '"';
+ break;
+ }
+ else {
+ context_buffer += char(c);
+ token_buffer += char(c);
+ }
+ }
+ return TEXT;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ int overflow = 0;
+ n = 0;
+ for (;;) {
+ if (n > (INT_MAX - 9)/10) {
+ overflow = 1;
+ break;
+ }
+ n *= 10;
+ n += c - '0';
+ context_buffer += char(c);
+ c = input_stack::peek_char();
+ if (c == EOF || !csdigit(c))
+ break;
+ c = input_stack::get_char();
+ }
+ token_double = n;
+ if (overflow) {
+ for (;;) {
+ token_double *= 10.0;
+ token_double += c - '0';
+ context_buffer += char(c);
+ c = input_stack::peek_char();
+ if (c == EOF || !csdigit(c))
+ break;
+ c = input_stack::get_char();
+ }
+ // if somebody asks for 1000000000000th, we will silently
+ // give them INT_MAXth
+ double temp = token_double; // work around gas 1.34/sparc bug
+ if (token_double > INT_MAX)
+ n = INT_MAX;
+ else
+ n = int(temp);
+ }
+ }
+ switch (c) {
+ case 'i':
+ case 'I':
+ context_buffer += char(c);
+ input_stack::get_char();
+ return NUMBER;
+ case '.':
+ {
+ context_buffer += '.';
+ input_stack::get_char();
+ got_dot:
+ double factor = 1.0;
+ for (;;) {
+ c = input_stack::peek_char();
+ if (c == EOF || !csdigit(c))
+ break;
+ input_stack::get_char();
+ context_buffer += char(c);
+ factor /= 10.0;
+ if (c != '0')
+ token_double += factor*(c - '0');
+ }
+ if (c != 'e' && c != 'E') {
+ if (c == 'i' || c == 'I') {
+ context_buffer += char(c);
+ input_stack::get_char();
+ }
+ return NUMBER;
+ }
+ }
+ // fall through
+ case 'e':
+ case 'E':
+ {
+ int echar = c;
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ int sign = '+';
+ if (c == '+' || c == '-') {
+ sign = c;
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == EOF || !csdigit(c)) {
+ input_stack::push_back(sign);
+ input_stack::push_back(echar);
+ return NUMBER;
+ }
+ context_buffer += char(echar);
+ context_buffer += char(sign);
+ }
+ else {
+ if (c == EOF || !csdigit(c)) {
+ input_stack::push_back(echar);
+ return NUMBER;
+ }
+ context_buffer += char(echar);
+ }
+ input_stack::get_char();
+ context_buffer += char(c);
+ n = c - '0';
+ for (;;) {
+ c = input_stack::peek_char();
+ if (c == EOF || !csdigit(c))
+ break;
+ input_stack::get_char();
+ context_buffer += char(c);
+ n = n*10 + (c - '0');
+ }
+ if (sign == '-')
+ n = -n;
+ if (c == 'i' || c == 'I') {
+ context_buffer += char(c);
+ input_stack::get_char();
+ }
+ token_double *= pow(10.0, n);
+ return NUMBER;
+ }
+ case 'n':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'd') {
+ input_stack::get_char();
+ token_int = n;
+ context_buffer += "nd";
+ return ORDINAL;
+ }
+ input_stack::push_back('n');
+ return NUMBER;
+ case 'r':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'd') {
+ input_stack::get_char();
+ token_int = n;
+ context_buffer += "rd";
+ return ORDINAL;
+ }
+ input_stack::push_back('r');
+ return NUMBER;
+ case 't':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'h') {
+ input_stack::get_char();
+ token_int = n;
+ context_buffer += "th";
+ return ORDINAL;
+ }
+ input_stack::push_back('t');
+ return NUMBER;
+ case 's':
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ token_int = n;
+ context_buffer += "st";
+ return ORDINAL;
+ }
+ input_stack::push_back('s');
+ return NUMBER;
+ default:
+ return NUMBER;
+ }
+ break;
+ case '\'':
+ {
+ c = input_stack::peek_char();
+ if (c == 't') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == 'h') {
+ input_stack::get_char();
+ context_buffer = "'th";
+ return TH;
+ }
+ else
+ input_stack::push_back('t');
+ }
+ context_buffer = "'";
+ return '\'';
+ }
+ case '.':
+ {
+ c = input_stack::peek_char();
+ if (c != EOF && csdigit(c)) {
+ n = 0;
+ token_double = 0.0;
+ context_buffer = '.';
+ goto got_dot;
+ }
+ return get_token_after_dot(c);
+ }
+ case '<':
+ c = input_stack::peek_char();
+ if (c == '-') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ if (c == '>') {
+ input_stack::get_char();
+ context_buffer = "<->";
+ return DOUBLE_ARROW_HEAD;
+ }
+ context_buffer = "<-";
+ return LEFT_ARROW_HEAD;
+ }
+ else if (c == '=') {
+ input_stack::get_char();
+ context_buffer = "<=";
+ return LESSEQUAL;
+ }
+ context_buffer = "<";
+ return '<';
+ case '-':
+ c = input_stack::peek_char();
+ if (c == '>') {
+ input_stack::get_char();
+ context_buffer = "->";
+ return RIGHT_ARROW_HEAD;
+ }
+ context_buffer = "-";
+ return '-';
+ case '!':
+ c = input_stack::peek_char();
+ if (c == '=') {
+ input_stack::get_char();
+ context_buffer = "!=";
+ return NOTEQUAL;
+ }
+ context_buffer = "!";
+ return '!';
+ case '>':
+ c = input_stack::peek_char();
+ if (c == '=') {
+ input_stack::get_char();
+ context_buffer = ">=";
+ return GREATEREQUAL;
+ }
+ context_buffer = ">";
+ return '>';
+ case '=':
+ c = input_stack::peek_char();
+ if (c == '=') {
+ input_stack::get_char();
+ context_buffer = "==";
+ return EQUALEQUAL;
+ }
+ context_buffer = "=";
+ return '=';
+ case '&':
+ c = input_stack::peek_char();
+ if (c == '&') {
+ input_stack::get_char();
+ context_buffer = "&&";
+ return ANDAND;
+ }
+ context_buffer = "&";
+ return '&';
+ case '|':
+ c = input_stack::peek_char();
+ if (c == '|') {
+ input_stack::get_char();
+ context_buffer = "||";
+ return OROR;
+ }
+ context_buffer = "|";
+ return '|';
+ default:
+ if (c != EOF && csalpha(c)) {
+ token_buffer.clear();
+ token_buffer = c;
+ for (;;) {
+ c = input_stack::peek_char();
+ if (c == EOF || (!csalnum(c) && c != '_'))
+ break;
+ input_stack::get_char();
+ token_buffer += char(c);
+ }
+ int tok = lookup_keyword(token_buffer.contents(),
+ token_buffer.length());
+ if (tok != 0) {
+ context_buffer = token_buffer;
+ return tok;
+ }
+ char *def = 0;
+ if (lookup_flag) {
+ token_buffer += '\0';
+ def = macro_table.lookup(token_buffer.contents());
+ token_buffer.set_length(token_buffer.length() - 1);
+ if (def) {
+ if (c == '(') {
+ input_stack::get_char();
+ interpolate_macro_with_args(def);
+ }
+ else
+ input_stack::push(new macro_input(def));
+ }
+ }
+ if (!def) {
+ context_buffer = token_buffer;
+ if (csupper(token_buffer[0]))
+ return LABEL;
+ else
+ return VARIABLE;
+ }
+ }
+ else {
+ context_buffer = char(c);
+ return (unsigned char)c;
+ }
+ break;
+ }
+ }
+}
+
+int get_delimited()
+{
+ token_buffer.clear();
+ int c = input_stack::get_char();
+ while (c == ' ' || c == '\t' || c == '\n')
+ c = input_stack::get_char();
+ if (c == EOF) {
+ lex_error("missing delimiter");
+ return 0;
+ }
+ context_buffer = char(c);
+ int had_newline = 0;
+ int start = c;
+ int level = 0;
+ enum { NORMAL, IN_STRING, IN_STRING_QUOTED, DELIM_END } state = NORMAL;
+ for (;;) {
+ c = input_stack::get_char();
+ if (c == EOF) {
+ lex_error("missing closing delimiter");
+ return 0;
+ }
+ if (c == '\n')
+ had_newline = 1;
+ else if (!had_newline)
+ context_buffer += char(c);
+ switch (state) {
+ case NORMAL:
+ if (start == '{') {
+ if (c == '{') {
+ level++;
+ break;
+ }
+ if (c == '}') {
+ if (--level < 0)
+ state = DELIM_END;
+ break;
+ }
+ }
+ else {
+ if (c == start) {
+ state = DELIM_END;
+ break;
+ }
+ }
+ if (c == '"')
+ state = IN_STRING;
+ break;
+ case IN_STRING_QUOTED:
+ if (c == '\n')
+ state = NORMAL;
+ else
+ state = IN_STRING;
+ break;
+ case IN_STRING:
+ if (c == '"' || c == '\n')
+ state = NORMAL;
+ else if (c == '\\')
+ state = IN_STRING_QUOTED;
+ break;
+ case DELIM_END:
+ // This case it just to shut cfront 2.0 up.
+ default:
+ assert(0);
+ }
+ if (state == DELIM_END)
+ break;
+ token_buffer += c;
+ }
+ return 1;
+}
+
+void do_define()
+{
+ int t = get_token(0); // do not expand what we are defining
+ if (t != VARIABLE && t != LABEL) {
+ lex_error("can only define variable or placename");
+ return;
+ }
+ token_buffer += '\0';
+ string nm = token_buffer;
+ const char *name = nm.contents();
+ if (!get_delimited())
+ return;
+ token_buffer += '\0';
+ macro_table.define(name, strsave(token_buffer.contents()));
+}
+
+void do_undef()
+{
+ int t = get_token(0); // do not expand what we are undefining
+ if (t != VARIABLE && t != LABEL) {
+ lex_error("can only define variable or placename");
+ return;
+ }
+ token_buffer += '\0';
+ macro_table.define(token_buffer.contents(), 0);
+}
+
+
+class for_input : public input {
+ char *var;
+ char *body;
+ double from;
+ double to;
+ int by_is_multiplicative;
+ double by;
+ const char *p;
+ int done_newline;
+public:
+ for_input(char *, double, double, int, double, char *);
+ ~for_input();
+ int get();
+ int peek();
+};
+
+for_input::for_input(char *vr, double f, double t,
+ int bim, double b, char *bd)
+: var(vr), body(bd), from(f), to(t), by_is_multiplicative(bim), by(b),
+ p(body), done_newline(0)
+{
+}
+
+for_input::~for_input()
+{
+ free(var);
+ free(body);
+}
+
+int for_input::get()
+{
+ if (p == 0)
+ return EOF;
+ for (;;) {
+ if (*p != '\0')
+ return (unsigned char)*p++;
+ if (!done_newline) {
+ done_newline = 1;
+ return '\n';
+ }
+ double val;
+ if (!lookup_variable(var, &val)) {
+ lex_error("body of 'for' terminated enclosing block");
+ return EOF;
+ }
+ if (by_is_multiplicative)
+ val *= by;
+ else
+ val += by;
+ define_variable(var, val);
+ if ((from <= to && val > to)
+ || (from >= to && val < to)) {
+ p = 0;
+ return EOF;
+ }
+ p = body;
+ done_newline = 0;
+ }
+}
+
+int for_input::peek()
+{
+ if (p == 0)
+ return EOF;
+ if (*p != '\0')
+ return (unsigned char)*p;
+ if (!done_newline)
+ return '\n';
+ double val;
+ if (!lookup_variable(var, &val))
+ return EOF;
+ if (by_is_multiplicative) {
+ if (val * by > to)
+ return EOF;
+ }
+ else {
+ if ((from <= to && val + by > to)
+ || (from >= to && val + by < to))
+ return EOF;
+ }
+ if (*body == '\0')
+ return EOF;
+ return (unsigned char)*body;
+}
+
+void do_for(char *var, double from, double to, int by_is_multiplicative,
+ double by, char *body)
+{
+ define_variable(var, from);
+ if ((by_is_multiplicative && by <= 0)
+ || (by > 0 && from > to)
+ || (by < 0 && from < to))
+ return;
+ input_stack::push(new for_input(var, from, to,
+ by_is_multiplicative, by, body));
+}
+
+
+void do_copy(const char *filename)
+{
+ errno = 0;
+ FILE *fp = fopen(filename, "r");
+ if (fp == 0) {
+ lex_error("can't open '%1': %2", filename, strerror(errno));
+ return;
+ }
+ input_stack::push(new file_input(fp, filename));
+}
+
+class copy_thru_input : public input {
+ int done;
+ char *body;
+ char *until;
+ const char *p;
+ const char *ap;
+ int argv[MAX_ARG];
+ int argc;
+ string line;
+ int get_line();
+ virtual int inget() = 0;
+public:
+ copy_thru_input(const char *b, const char *u);
+ ~copy_thru_input();
+ int get();
+ int peek();
+};
+
+class copy_file_thru_input : public copy_thru_input {
+ input *in;
+public:
+ copy_file_thru_input(input *, const char *b, const char *u);
+ ~copy_file_thru_input();
+ int inget();
+};
+
+copy_file_thru_input::copy_file_thru_input(input *i, const char *b,
+ const char *u)
+: copy_thru_input(b, u), in(i)
+{
+}
+
+copy_file_thru_input::~copy_file_thru_input()
+{
+ delete in;
+}
+
+int copy_file_thru_input::inget()
+{
+ if (!in)
+ return EOF;
+ else
+ return in->get();
+}
+
+class copy_rest_thru_input : public copy_thru_input {
+public:
+ copy_rest_thru_input(const char *, const char *u);
+ int inget();
+};
+
+copy_rest_thru_input::copy_rest_thru_input(const char *b, const char *u)
+: copy_thru_input(b, u)
+{
+}
+
+int copy_rest_thru_input::inget()
+{
+ while (next != 0) {
+ int c = next->get();
+ if (c != EOF)
+ return c;
+ if (next->next == 0)
+ return EOF;
+ input *tem = next;
+ next = next->next;
+ delete tem;
+ }
+ return EOF;
+
+}
+
+copy_thru_input::copy_thru_input(const char *b, const char *u)
+: done(0)
+{
+ ap = 0;
+ body = process_body(b);
+ p = 0;
+ until = strsave(u);
+}
+
+
+copy_thru_input::~copy_thru_input()
+{
+ delete[] body;
+ delete[] until;
+}
+
+int copy_thru_input::get()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return (unsigned char)*ap++;
+ ap = 0;
+ }
+ for (;;) {
+ if (p == 0) {
+ if (!get_line())
+ break;
+ p = body;
+ }
+ if (*p == '\0') {
+ p = 0;
+ return '\n';
+ }
+ while ((unsigned char)*p >= ARG1
+ && (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
+ int i = (unsigned char)*p++ - ARG1;
+ if (i < argc && line[argv[i]] != '\0') {
+ ap = line.contents() + argv[i];
+ return (unsigned char)*ap++;
+ }
+ }
+ if (*p != '\0')
+ return (unsigned char)*p++;
+ }
+ return EOF;
+}
+
+int copy_thru_input::peek()
+{
+ if (ap) {
+ if (*ap != '\0')
+ return (unsigned char)*ap;
+ ap = 0;
+ }
+ for (;;) {
+ if (p == 0) {
+ if (!get_line())
+ break;
+ p = body;
+ }
+ if (*p == '\0')
+ return '\n';
+ while ((unsigned char)*p >= ARG1
+ && (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
+ int i = (unsigned char)*p++ - ARG1;
+ if (i < argc && line[argv[i]] != '\0') {
+ ap = line.contents() + argv[i];
+ return (unsigned char)*ap;
+ }
+ }
+ if (*p != '\0')
+ return (unsigned char)*p;
+ }
+ return EOF;
+}
+
+int copy_thru_input::get_line()
+{
+ if (done)
+ return 0;
+ line.clear();
+ argc = 0;
+ int c = inget();
+ for (;;) {
+ while (c == ' ')
+ c = inget();
+ if (c == EOF || c == '\n')
+ break;
+ if (argc == MAX_ARG) {
+ do {
+ c = inget();
+ } while (c != '\n' && c != EOF);
+ break;
+ }
+ argv[argc++] = line.length();
+ do {
+ line += char(c);
+ c = inget();
+ } while (c != ' ' && c != '\n');
+ line += '\0';
+ }
+ if (until != 0 && argc > 0 && strcmp(&line[argv[0]], until) == 0) {
+ done = 1;
+ return 0;
+ }
+ return argc > 0 || c == '\n';
+}
+
+class simple_file_input : public input {
+ const char *filename;
+ int lineno;
+ FILE *fp;
+public:
+ simple_file_input(FILE *, const char *);
+ ~simple_file_input();
+ int get();
+ int peek();
+ int get_location(const char **, int *);
+};
+
+simple_file_input::simple_file_input(FILE *p, const char *s)
+: filename(s), lineno(1), fp(p)
+{
+}
+
+simple_file_input::~simple_file_input()
+{
+ // don't delete the filename
+ fclose(fp);
+}
+
+int simple_file_input::get()
+{
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ error("invalid input character code %1", c);
+ c = getc(fp);
+ }
+ if (c == '\n')
+ lineno++;
+ return c;
+}
+
+int simple_file_input::peek()
+{
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ error("invalid input character code %1", c);
+ c = getc(fp);
+ }
+ if (c != EOF)
+ ungetc(c, fp);
+ return c;
+}
+
+int simple_file_input::get_location(const char **fnp, int *lnp)
+{
+ *fnp = filename;
+ *lnp = lineno;
+ return 1;
+}
+
+
+void copy_file_thru(const char *filename, const char *body, const char *until)
+{
+ errno = 0;
+ FILE *fp = fopen(filename, "r");
+ if (fp == 0) {
+ lex_error("can't open '%1': %2", filename, strerror(errno));
+ return;
+ }
+ input *in = new copy_file_thru_input(new simple_file_input(fp, filename),
+ body, until);
+ input_stack::push(in);
+}
+
+void copy_rest_thru(const char *body, const char *until)
+{
+ input_stack::push(new copy_rest_thru_input(body, until));
+}
+
+void push_body(const char *s)
+{
+ input_stack::push(new char_input('\n'));
+ input_stack::push(new macro_input(s));
+}
+
+int delim_flag = 0;
+
+char *get_thru_arg()
+{
+ int c = input_stack::peek_char();
+ while (c == ' ') {
+ input_stack::get_char();
+ c = input_stack::peek_char();
+ }
+ if (c != EOF && csalpha(c)) {
+ // looks like a macro
+ input_stack::get_char();
+ token_buffer = c;
+ for (;;) {
+ c = input_stack::peek_char();
+ if (c == EOF || (!csalnum(c) && c != '_'))
+ break;
+ input_stack::get_char();
+ token_buffer += char(c);
+ }
+ context_buffer = token_buffer;
+ token_buffer += '\0';
+ char *def = macro_table.lookup(token_buffer.contents());
+ if (def)
+ return strsave(def);
+ // I guess it wasn't a macro after all; so push the macro name back.
+ // -2 because we added a '\0'
+ for (int i = token_buffer.length() - 2; i >= 0; i--)
+ input_stack::push_back(token_buffer[i]);
+ }
+ if (get_delimited()) {
+ token_buffer += '\0';
+ return strsave(token_buffer.contents());
+ }
+ else
+ return 0;
+}
+
+int lookahead_token = -1;
+string old_context_buffer;
+
+void do_lookahead()
+{
+ if (lookahead_token == -1) {
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ }
+}
+
+int yylex()
+{
+ if (delim_flag) {
+ assert(lookahead_token == -1);
+ if (delim_flag == 2) {
+ if ((yylval.str = get_thru_arg()) != 0)
+ return DELIMITED;
+ else
+ return 0;
+ }
+ else {
+ if (get_delimited()) {
+ token_buffer += '\0';
+ yylval.str = strsave(token_buffer.contents());
+ return DELIMITED;
+ }
+ else
+ return 0;
+ }
+ }
+ for (;;) {
+ int t;
+ if (lookahead_token >= 0) {
+ t = lookahead_token;
+ lookahead_token = -1;
+ }
+ else
+ t = get_token(1);
+ switch (t) {
+ case '\n':
+ return ';';
+ case EOF:
+ return 0;
+ case DEFINE:
+ do_define();
+ break;
+ case UNDEF:
+ do_undef();
+ break;
+ case ORDINAL:
+ yylval.n = token_int;
+ return t;
+ case NUMBER:
+ yylval.x = token_double;
+ return t;
+ case COMMAND_LINE:
+ case TEXT:
+ token_buffer += '\0';
+ if (!input_stack::get_location(&yylval.lstr.filename,
+ &yylval.lstr.lineno)) {
+ yylval.lstr.filename = 0;
+ yylval.lstr.lineno = -1;
+ }
+ yylval.lstr.str = strsave(token_buffer.contents());
+ return t;
+ case LABEL:
+ case VARIABLE:
+ token_buffer += '\0';
+ yylval.str = strsave(token_buffer.contents());
+ return t;
+ case LEFT:
+ // change LEFT to LEFT_CORNER when followed by OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token == OF)
+ return LEFT_CORNER;
+ else
+ return t;
+ case RIGHT:
+ // change RIGHT to RIGHT_CORNER when followed by OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token == OF)
+ return RIGHT_CORNER;
+ else
+ return t;
+ case UPPER:
+ // recognise UPPER only before LEFT or RIGHT
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != LEFT && lookahead_token != RIGHT) {
+ yylval.str = strsave("upper");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case LOWER:
+ // recognise LOWER only before LEFT or RIGHT
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != LEFT && lookahead_token != RIGHT) {
+ yylval.str = strsave("lower");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case NORTH:
+ // recognise NORTH only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("north");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case SOUTH:
+ // recognise SOUTH only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("south");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case EAST:
+ // recognise EAST only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("east");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case WEST:
+ // recognise WEST only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("west");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case TOP:
+ // recognise TOP only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("top");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case BOTTOM:
+ // recognise BOTTOM only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("bottom");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case CENTER:
+ // recognise CENTER only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("center");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case START:
+ // recognise START only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("start");
+ return VARIABLE;
+ }
+ else
+ return t;
+ case END:
+ // recognise END only before OF
+ old_context_buffer = context_buffer;
+ lookahead_token = get_token(1);
+ if (lookahead_token != OF) {
+ yylval.str = strsave("end");
+ return VARIABLE;
+ }
+ else
+ return t;
+ default:
+ return t;
+ }
+ }
+}
+
+void lex_error(const char *message,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ const char *filename;
+ int lineno;
+ if (!input_stack::get_location(&filename, &lineno))
+ error(message, arg1, arg2, arg3);
+ else
+ error_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
+}
+
+void lex_warning(const char *message,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ const char *filename;
+ int lineno;
+ if (!input_stack::get_location(&filename, &lineno))
+ warning(message, arg1, arg2, arg3);
+ else
+ warning_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
+}
+
+void yyerror(const char *s)
+{
+ const char *filename;
+ int lineno;
+ const char *context = 0;
+ if (lookahead_token == -1) {
+ if (context_buffer.length() > 0) {
+ context_buffer += '\0';
+ context = context_buffer.contents();
+ }
+ }
+ else {
+ if (old_context_buffer.length() > 0) {
+ old_context_buffer += '\0';
+ context = old_context_buffer.contents();
+ }
+ }
+ if (!input_stack::get_location(&filename, &lineno)) {
+ if (context) {
+ if (context[0] == '\n' && context[1] == '\0')
+ error("%1 before newline", s);
+ else
+ error("%1 before '%2'", s, context);
+ }
+ else
+ error("%1 at end of picture", s);
+ }
+ else {
+ if (context) {
+ if (context[0] == '\n' && context[1] == '\0')
+ error_with_file_and_line(filename, lineno, "%1 before newline", s);
+ else
+ error_with_file_and_line(filename, lineno, "%1 before '%2'",
+ s, context);
+ }
+ else
+ error_with_file_and_line(filename, lineno, "%1 at end of picture", s);
+ }
+}
+
diff --git a/src/preproc/pic/main.cpp b/src/preproc/pic/main.cpp
new file mode 100644
index 0000000..f7e194f
--- /dev/null
+++ b/src/preproc/pic/main.cpp
@@ -0,0 +1,664 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pic.h"
+
+extern int yyparse();
+extern "C" const char *Version_string;
+
+output *out;
+char *graphname; // the picture box name in TeX mode
+
+bool want_flyback = false;
+// groff pic supports '.PY' to work around mm package stepping on 'PF'.
+bool want_alternate_flyback = false;
+int zero_length_line_flag = 0;
+// Non-zero means we're using a groff driver.
+int driver_extension_flag = 1;
+int compatible_flag = 0;
+int safer_flag = 1;
+int command_char = '.'; // the character that introduces lines
+ // that should be passed through transparently
+static int lf_flag = 1; // non-zero if we should attempt to understand
+ // lines beginning with '.lf'
+
+// Non-zero means a parse error was encountered.
+static int had_parse_error = 0;
+
+void do_file(const char *filename);
+
+class top_input : public input {
+ FILE *fp;
+ int bol;
+ int eof;
+ int push_back[3];
+ int start_lineno;
+public:
+ top_input(FILE *);
+ int get();
+ int peek();
+ int get_location(const char **, int *);
+};
+
+top_input::top_input(FILE *p) : fp(p), bol(1), eof(0)
+{
+ push_back[0] = push_back[1] = push_back[2] = EOF;
+ start_lineno = current_lineno;
+}
+
+int top_input::get()
+{
+ if (eof)
+ return EOF;
+ if (push_back[2] != EOF) {
+ int c = push_back[2];
+ push_back[2] = EOF;
+ return c;
+ }
+ else if (push_back[1] != EOF) {
+ int c = push_back[1];
+ push_back[1] = EOF;
+ return c;
+ }
+ else if (push_back[0] != EOF) {
+ int c = push_back[0];
+ push_back[0] = EOF;
+ return c;
+ }
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ error("invalid input character code %1", int(c));
+ c = getc(fp);
+ bol = 0;
+ }
+ if (bol && c == '.') {
+ c = getc(fp);
+ if (c == 'P') {
+ c = getc(fp);
+ if (c == 'E' || c == 'F' || c == 'Y') {
+ int d = getc(fp);
+ if (d != EOF)
+ ungetc(d, fp);
+ if (d == EOF || d == ' ' || d == '\n' || compatible_flag) {
+ eof = 1;
+ want_flyback = (c == 'F');
+ want_alternate_flyback = (c == 'Y');
+ return EOF;
+ }
+ push_back[0] = c;
+ push_back[1] = 'P';
+ return '.';
+ }
+ if (c == 'S') {
+ c = getc(fp);
+ if (c != EOF)
+ ungetc(c, fp);
+ if (c == EOF || c == ' ' || c == '\n' || compatible_flag) {
+ error("nested .PS");
+ eof = 1;
+ return EOF;
+ }
+ push_back[0] = 'S';
+ push_back[1] = 'P';
+ return '.';
+ }
+ if (c != EOF)
+ ungetc(c, fp);
+ push_back[0] = 'P';
+ return '.';
+ }
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ return '.';
+ }
+ }
+ if (c == '\n') {
+ bol = 1;
+ current_lineno++;
+ return '\n';
+ }
+ bol = 0;
+ if (c == EOF) {
+ eof = 1;
+ error("end of file before .PE, .PF, or .PY");
+ error_with_file_and_line(current_filename, start_lineno - 1,
+ ".PS was here");
+ }
+ return c;
+}
+
+int top_input::peek()
+{
+ if (eof)
+ return EOF;
+ if (push_back[2] != EOF)
+ return push_back[2];
+ if (push_back[1] != EOF)
+ return push_back[1];
+ if (push_back[0] != EOF)
+ return push_back[0];
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ error("invalid input character code %1", int(c));
+ c = getc(fp);
+ bol = 0;
+ }
+ if (bol && c == '.') {
+ c = getc(fp);
+ if (c == 'P') {
+ c = getc(fp);
+ if (c == 'E' || c == 'F' || c == 'Y') {
+ int d = getc(fp);
+ if (d != EOF)
+ ungetc(d, fp);
+ if (d == EOF || d == ' ' || d == '\n' || compatible_flag) {
+ eof = 1;
+ want_flyback = (c == 'F');
+ want_alternate_flyback = (c == 'Y');
+ return EOF;
+ }
+ push_back[0] = c;
+ push_back[1] = 'P';
+ push_back[2] = '.';
+ return '.';
+ }
+ if (c == 'S') {
+ c = getc(fp);
+ if (c != EOF)
+ ungetc(c, fp);
+ if (c == EOF || c == ' ' || c == '\n' || compatible_flag) {
+ error("nested .PS");
+ eof = 1;
+ return EOF;
+ }
+ push_back[0] = 'S';
+ push_back[1] = 'P';
+ push_back[2] = '.';
+ return '.';
+ }
+ if (c != EOF)
+ ungetc(c, fp);
+ push_back[0] = 'P';
+ push_back[1] = '.';
+ return '.';
+ }
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ push_back[0] = '.';
+ return '.';
+ }
+ }
+ if (c != EOF)
+ ungetc(c, fp);
+ if (c == '\n')
+ return '\n';
+ return c;
+}
+
+int top_input::get_location(const char **filenamep, int *linenop)
+{
+ *filenamep = current_filename;
+ *linenop = current_lineno;
+ return 1;
+}
+
+void do_picture(FILE *fp)
+{
+ want_flyback = false;
+ int c;
+ if (!graphname)
+ free(graphname);
+ graphname = strsave("graph"); // default picture name in TeX mode
+ while ((c = getc(fp)) == ' ')
+ ;
+ if (c == '<') {
+ string filename;
+ while ((c = getc(fp)) == ' ')
+ ;
+ while (c != EOF && c != ' ' && c != '\n') {
+ filename += char(c);
+ c = getc(fp);
+ }
+ if (c == ' ') {
+ do {
+ c = getc(fp);
+ } while (c != EOF && c != '\n');
+ }
+ if (c == '\n')
+ current_lineno++;
+ if (filename.length() == 0)
+ error("missing filename after '<'");
+ else {
+ filename += '\0';
+ const char *old_filename = current_filename;
+ int old_lineno = current_lineno;
+ // filenames must be permanent
+ do_file(strsave(filename.contents()));
+ current_filename = old_filename;
+ current_lineno = old_lineno;
+ }
+ out->set_location(current_filename, current_lineno);
+ }
+ else {
+ out->set_location(current_filename, current_lineno);
+ string start_line;
+ while (c != EOF) {
+ if (c == '\n') {
+ current_lineno++;
+ break;
+ }
+ start_line += c;
+ c = getc(fp);
+ }
+ if (c == EOF)
+ return;
+ start_line += '\0';
+ double wid, ht;
+ switch (sscanf(&start_line[0], "%lf %lf", &wid, &ht)) {
+ case 1:
+ ht = 0.0;
+ break;
+ case 2:
+ break;
+ default:
+ ht = wid = 0.0;
+ break;
+ }
+ out->set_desired_width_height(wid, ht);
+ out->set_args(start_line.contents());
+ lex_init(new top_input(fp));
+ if (yyparse()) {
+ had_parse_error = 1;
+ lex_error("giving up on this picture");
+ }
+ parse_cleanup();
+ lex_cleanup();
+
+ // skip the rest of the .PE/.PF/.PY line
+ while ((c = getc(fp)) != EOF && c != '\n')
+ ;
+ if (c == '\n')
+ current_lineno++;
+ out->set_location(current_filename, current_lineno);
+ }
+}
+
+void do_file(const char *filename)
+{
+ FILE *fp;
+ if (strcmp(filename, "-") == 0)
+ fp = stdin;
+ else {
+ errno = 0;
+ fp = fopen(filename, "r");
+ if (fp == 0) {
+ delete out;
+ fatal("can't open '%1': %2", filename, strerror(errno));
+ }
+ }
+ string fn(filename);
+ fn += '\0';
+ normalize_for_lf(fn);
+ current_filename = fn.contents();
+ out->set_location(current_filename, 1);
+ current_lineno = 1;
+ enum { START, MIDDLE, HAD_DOT, HAD_P, HAD_PS, HAD_l, HAD_lf } state = START;
+ for (;;) {
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ error("invalid input character code %1", int(c));
+ c = getc(fp);
+ }
+ if (c == EOF)
+ break;
+ switch (state) {
+ case START:
+ if (c == '.')
+ state = HAD_DOT;
+ else {
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case MIDDLE:
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ break;
+ case HAD_DOT:
+ if (c == 'P')
+ state = HAD_P;
+ else if (lf_flag && c == 'l')
+ state = HAD_l;
+ else {
+ putchar('.');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_P:
+ if (c == 'S')
+ state = HAD_PS;
+ else {
+ putchar('.');
+ putchar('P');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_PS:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ ungetc(c, fp);
+ do_picture(fp);
+ state = START;
+ }
+ else {
+ fputs(".PS", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ case HAD_l:
+ if (c == 'f')
+ state = HAD_lf;
+ else {
+ putchar('.');
+ putchar('l');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_lf:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ string line;
+ while (c != EOF) {
+ line += c;
+ if (c == '\n') {
+ current_lineno++;
+ break;
+ }
+ c = getc(fp);
+ }
+ line += '\0';
+ interpret_lf_args(line.contents());
+ printf(".lf%s", line.contents());
+ state = START;
+ }
+ else {
+ fputs(".lf", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ default:
+ assert(0);
+ }
+ }
+ switch (state) {
+ case START:
+ break;
+ case MIDDLE:
+ putchar('\n');
+ break;
+ case HAD_DOT:
+ fputs(".\n", stdout);
+ break;
+ case HAD_P:
+ fputs(".P\n", stdout);
+ break;
+ case HAD_PS:
+ fputs(".PS\n", stdout);
+ break;
+ case HAD_l:
+ fputs(".l\n", stdout);
+ break;
+ case HAD_lf:
+ fputs(".lf\n", stdout);
+ break;
+ }
+ if (fp != stdin)
+ fclose(fp);
+}
+
+#ifdef FIG_SUPPORT
+void do_whole_file(const char *filename)
+{
+ // Do not set current_filename.
+ FILE *fp;
+ if (strcmp(filename, "-") == 0)
+ fp = stdin;
+ else {
+ errno = 0;
+ fp = fopen(filename, "r");
+ if (fp == 0)
+ fatal("can't open '%1': %2", filename, strerror(errno));
+ }
+ lex_init(new file_input(fp, filename));
+ if (yyparse())
+ had_parse_error = 1;
+ parse_cleanup();
+ lex_cleanup();
+}
+#endif
+
+void usage(FILE *stream)
+{
+ fprintf(stream, "usage: %s [-CnSU] [file ...]\n", program_name);
+#ifdef TEX_SUPPORT
+ fprintf(stream, "usage: %s -t [-cCSUz] [file ...]\n", program_name);
+#endif
+#ifdef FIG_SUPPORT
+ fprintf(stream, "usage: %s -f [-v] [file]\n", program_name);
+#endif
+ fprintf(stream, "usage: %s {-v | --version}\n", program_name);
+ fprintf(stream, "usage: %s --help\n", program_name);
+}
+
+#if defined(__MSDOS__) || defined(__EMX__)
+static char *fix_program_name(char *arg, char *dflt)
+{
+ if (!arg)
+ return dflt;
+ char *prog = strchr(arg, '\0');
+ for (;;) {
+ if (prog == arg)
+ break;
+ --prog;
+ if (strchr("\\/:", *prog)) {
+ prog++;
+ break;
+ }
+ }
+ char *ext = strchr(prog, '.');
+ if (ext)
+ *ext = '\0';
+ for (char *p = prog; *p; p++)
+ if ('A' <= *p && *p <= 'Z')
+ *p = 'a' + (*p - 'A');
+ return prog;
+}
+#endif /* __MSDOS__ || __EMX__ */
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+#if defined(__MSDOS__) || defined(__EMX__)
+ argv[0] = fix_program_name(argv[0], "pic");
+#endif /* __MSDOS__ || __EMX__ */
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int opt;
+#ifdef TEX_SUPPORT
+ int tex_flag = 0;
+ int tpic_flag = 0;
+#endif
+#ifdef FIG_SUPPORT
+ int whole_file_flag = 0;
+ int fig_flag = 0;
+#endif
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "T:CDSUtcvnxzpf", long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'C':
+ compatible_flag = 1;
+ break;
+ case 'D':
+ case 'T':
+ break;
+ case 'S':
+ safer_flag = 1;
+ break;
+ case 'U':
+ safer_flag = 0;
+ break;
+ case 'f':
+#ifdef FIG_SUPPORT
+ whole_file_flag++;
+ fig_flag++;
+#else
+ fatal("fig support not included");
+#endif
+ break;
+ case 'n':
+ driver_extension_flag = 0;
+ break;
+ case 'p':
+ case 'x':
+ warning("-%1 option is obsolete", char(opt));
+ break;
+ case 't':
+#ifdef TEX_SUPPORT
+ tex_flag++;
+#else
+ fatal("TeX support not included");
+#endif
+ break;
+ case 'c':
+#ifdef TEX_SUPPORT
+ tpic_flag++;
+#else
+ fatal("TeX support not included");
+#endif
+ break;
+ case 'v':
+ {
+ printf("GNU pic (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case 'z':
+ // zero length lines will be printed as dots
+ zero_length_line_flag++;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ parse_init();
+#ifdef TEX_SUPPORT
+ if (tpic_flag) {
+ out = make_tpic_output();
+ lf_flag = 0;
+ }
+ else if (tex_flag) {
+ out = make_tex_output();
+ command_char = '\\';
+ lf_flag = 0;
+ }
+ else
+#endif
+#ifdef FIG_SUPPORT
+ if (fig_flag)
+ out = make_fig_output();
+ else
+#endif
+ {
+ out = make_troff_output();
+ printf(".do if !dPS .ds PS\n"
+ ".do if !dPE .ds PE\n"
+ ".do if !dPF .ds PF\n"
+ ".do if !dPY .ds PY\n");
+ }
+#ifdef FIG_SUPPORT
+ if (whole_file_flag) {
+ if (optind >= argc)
+ do_whole_file("-");
+ else if (argc - optind > 1) {
+ usage(stderr);
+ exit(1);
+ } else
+ do_whole_file(argv[optind]);
+ }
+ else {
+#endif
+ if (optind >= argc)
+ do_file("-");
+ else
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+#ifdef FIG_SUPPORT
+ }
+#endif
+ delete out;
+ if (ferror(stdout) || fflush(stdout) < 0)
+ fatal("output error");
+ return had_parse_error;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/pic/object.cpp b/src/preproc/pic/object.cpp
new file mode 100644
index 0000000..e207fb1
--- /dev/null
+++ b/src/preproc/pic/object.cpp
@@ -0,0 +1,2079 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pic.h"
+#include "ptable.h"
+#include "object.h"
+
+void print_object_list(object *);
+
+line_type::line_type()
+: type(solid), thickness(1.0)
+{
+}
+
+output::output() : args(0), desired_height(0.0), desired_width(0.0)
+{
+}
+
+output::~output()
+{
+ delete[] args;
+}
+
+void output::set_desired_width_height(double wid, double ht)
+{
+ desired_width = wid;
+ desired_height = ht;
+}
+
+void output::set_args(const char *s)
+{
+ delete[] args;
+ if (s == 0 || *s == '\0')
+ args = 0;
+ else
+ args = strsave(s);
+}
+
+int output::supports_filled_polygons()
+{
+ return 0;
+}
+
+void output::begin_block(const position &, const position &)
+{
+}
+
+void output::end_block()
+{
+}
+
+double output::compute_scale(double sc, const position &ll, const position &ur)
+{
+ distance dim = ur - ll;
+ if (desired_width != 0.0 || desired_height != 0.0) {
+ sc = 0.0;
+ if (desired_width != 0.0) {
+ if (dim.x == 0.0)
+ error("width specified for picture with zero width");
+ else
+ sc = dim.x/desired_width;
+ }
+ if (desired_height != 0.0) {
+ if (dim.y == 0.0)
+ error("height specified for picture with zero height");
+ else {
+ double tem = dim.y/desired_height;
+ if (tem > sc)
+ sc = tem;
+ }
+ }
+ return sc == 0.0 ? 1.0 : sc;
+ }
+ else {
+ if (sc <= 0.0)
+ sc = 1.0;
+ distance sdim = dim/sc;
+ double max_width = 0.0;
+ lookup_variable("maxpswid", &max_width);
+ double max_height = 0.0;
+ lookup_variable("maxpsht", &max_height);
+ if ((max_width > 0.0 && sdim.x > max_width)
+ || (max_height > 0.0 && sdim.y > max_height)) {
+ double xscale = dim.x/max_width;
+ double yscale = dim.y/max_height;
+ return xscale > yscale ? xscale : yscale;
+ }
+ else
+ return sc;
+ }
+}
+
+position::position(const place &pl)
+{
+ if (pl.obj != 0) {
+ // Use two statements to work around bug in SGI C++.
+ object *tem = pl.obj;
+ *this = tem->origin();
+ }
+ else {
+ x = pl.x;
+ y = pl.y;
+ }
+}
+
+position::position() : x(0.0), y(0.0)
+{
+}
+
+position::position(double a, double b) : x(a), y(b)
+{
+}
+
+
+int operator==(const position &a, const position &b)
+{
+ return a.x == b.x && a.y == b.y;
+}
+
+int operator!=(const position &a, const position &b)
+{
+ return a.x != b.x || a.y != b.y;
+}
+
+position &position::operator+=(const position &a)
+{
+ x += a.x;
+ y += a.y;
+ return *this;
+}
+
+position &position::operator-=(const position &a)
+{
+ x -= a.x;
+ y -= a.y;
+ return *this;
+}
+
+position &position::operator*=(double a)
+{
+ x *= a;
+ y *= a;
+ return *this;
+}
+
+position &position::operator/=(double a)
+{
+ x /= a;
+ y /= a;
+ return *this;
+}
+
+position operator-(const position &a)
+{
+ return position(-a.x, -a.y);
+}
+
+position operator+(const position &a, const position &b)
+{
+ return position(a.x + b.x, a.y + b.y);
+}
+
+position operator-(const position &a, const position &b)
+{
+ return position(a.x - b.x, a.y - b.y);
+}
+
+position operator/(const position &a, double n)
+{
+ return position(a.x/n, a.y/n);
+}
+
+position operator*(const position &a, double n)
+{
+ return position(a.x*n, a.y*n);
+}
+
+// dot product
+
+double operator*(const position &a, const position &b)
+{
+ return a.x*b.x + a.y*b.y;
+}
+
+double hypot(const position &a)
+{
+ return groff_hypot(a.x, a.y);
+}
+
+struct arrow_head_type {
+ double height;
+ double width;
+ int solid;
+};
+
+void draw_arrow(const position &pos, const distance &dir,
+ const arrow_head_type &aht, const line_type &lt,
+ char *outline_color_for_fill)
+{
+ double hyp = hypot(dir);
+ if (hyp == 0.0) {
+ error("cannot draw arrow on object with zero length");
+ return;
+ }
+ position base = -dir;
+ base *= aht.height/hyp;
+ position n(dir.y, -dir.x);
+ n *= aht.width/(hyp*2.0);
+ line_type slt = lt;
+ slt.type = line_type::solid;
+ if (aht.solid && out->supports_filled_polygons()) {
+ position v[3];
+ v[0] = pos;
+ v[1] = pos + base + n;
+ v[2] = pos + base - n;
+ // fill with outline color
+ out->set_color(outline_color_for_fill, outline_color_for_fill);
+ // make stroke thin to avoid arrow sticking
+ slt.thickness = 0.1;
+ out->polygon(v, 3, slt, 1);
+ }
+ else {
+ // use two line segments to avoid arrow sticking
+ out->line(pos + base - n, &pos, 1, slt);
+ out->line(pos + base + n, &pos, 1, slt);
+ }
+}
+
+object::object() : prev(0), next(0)
+{
+}
+
+object::~object()
+{
+}
+
+void object::move_by(const position &)
+{
+}
+
+void object::print()
+{
+}
+
+void object::print_text()
+{
+}
+
+int object::blank()
+{
+ return 0;
+}
+
+struct bounding_box {
+ int blank;
+ position ll;
+ position ur;
+
+ bounding_box();
+ void encompass(const position &);
+};
+
+bounding_box::bounding_box()
+: blank(1)
+{
+}
+
+void bounding_box::encompass(const position &pos)
+{
+ if (blank) {
+ ll = pos;
+ ur = pos;
+ blank = 0;
+ }
+ else {
+ if (pos.x < ll.x)
+ ll.x = pos.x;
+ if (pos.y < ll.y)
+ ll.y = pos.y;
+ if (pos.x > ur.x)
+ ur.x = pos.x;
+ if (pos.y > ur.y)
+ ur.y = pos.y;
+ }
+}
+
+void object::update_bounding_box(bounding_box *)
+{
+}
+
+position object::origin()
+{
+ return position(0.0,0.0);
+}
+
+position object::north()
+{
+ return origin();
+}
+
+position object::south()
+{
+ return origin();
+}
+
+position object::east()
+{
+ return origin();
+}
+
+position object::west()
+{
+ return origin();
+}
+
+position object::north_east()
+{
+ return origin();
+}
+
+position object::north_west()
+{
+ return origin();
+}
+
+position object::south_east()
+{
+ return origin();
+}
+
+position object::south_west()
+{
+ return origin();
+}
+
+position object::start()
+{
+ return origin();
+}
+
+position object::end()
+{
+ return origin();
+}
+
+position object::center()
+{
+ return origin();
+}
+
+double object::width()
+{
+ return 0.0;
+}
+
+double object::radius()
+{
+ return 0.0;
+}
+
+double object::height()
+{
+ return 0.0;
+}
+
+place *object::find_label(const char *)
+{
+ return 0;
+}
+
+segment::segment(const position &a, int n, segment *p)
+: is_absolute(n), pos(a), next(p)
+{
+}
+
+text_item::text_item(char *t, const char *fn, int ln)
+: next(0), text(t), filename(fn), lineno(ln)
+{
+ adj.h = CENTER_ADJUST;
+ adj.v = NONE_ADJUST;
+}
+
+text_item::~text_item()
+{
+ delete[] text;
+}
+
+object_spec::object_spec(object_type t) : type(t)
+{
+ flags = 0;
+ tbl = 0;
+ segment_list = 0;
+ segment_width = segment_height = 0.0;
+ segment_is_absolute = 0;
+ text = 0;
+ shaded = 0;
+ xslanted = 0;
+ yslanted = 0;
+ outlined = 0;
+ with = 0;
+ dir = RIGHT_DIRECTION;
+}
+
+object_spec::~object_spec()
+{
+ delete tbl;
+ while (segment_list != 0) {
+ segment *tem = segment_list;
+ segment_list = segment_list->next;
+ delete tem;
+ }
+ object *p = oblist.head;
+ while (p != 0) {
+ object *tem = p;
+ p = p->next;
+ delete tem;
+ }
+ while (text != 0) {
+ text_item *tem = text;
+ text = text->next;
+ delete tem;
+ }
+ delete with;
+ delete[] shaded;
+ delete[] outlined;
+}
+
+class command_object : public object {
+ char *s;
+ const char *filename;
+ int lineno;
+public:
+ command_object(char *, const char *, int);
+ ~command_object();
+ object_type type() { return OTHER_OBJECT; }
+ void print();
+};
+
+command_object::command_object(char *p, const char *fn, int ln)
+: s(p), filename(fn), lineno(ln)
+{
+}
+
+command_object::~command_object()
+{
+ delete[] s;
+}
+
+void command_object::print()
+{
+ out->command(s, filename, lineno);
+}
+
+object *make_command_object(char *s, const char *fn, int ln)
+{
+ return new command_object(s, fn, ln);
+}
+
+class mark_object : public object {
+public:
+ mark_object();
+ object_type type();
+};
+
+object *make_mark_object()
+{
+ return new mark_object();
+}
+
+mark_object::mark_object()
+{
+}
+
+object_type mark_object::type()
+{
+ return MARK_OBJECT;
+}
+
+object_list::object_list() : head(0), tail(0)
+{
+}
+
+void object_list::append(object *obj)
+{
+ if (tail == 0) {
+ obj->next = obj->prev = 0;
+ head = tail = obj;
+ }
+ else {
+ obj->prev = tail;
+ obj->next = 0;
+ tail->next = obj;
+ tail = obj;
+ }
+}
+
+void object_list::wrap_up_block(object_list *ol)
+{
+ object *p;
+ for (p = tail; p && p->type() != MARK_OBJECT; p = p->prev)
+ ;
+ assert(p != 0);
+ ol->head = p->next;
+ if (ol->head) {
+ ol->tail = tail;
+ ol->head->prev = 0;
+ }
+ else
+ ol->tail = 0;
+ tail = p->prev;
+ if (tail)
+ tail->next = 0;
+ else
+ head = 0;
+ delete p;
+}
+
+text_piece::text_piece()
+: text(0), filename(0), lineno(-1)
+{
+ adj.h = CENTER_ADJUST;
+ adj.v = NONE_ADJUST;
+}
+
+text_piece::~text_piece()
+{
+ free(text);
+}
+
+class graphic_object : public object {
+ int ntext;
+ text_piece *text;
+ int aligned;
+protected:
+ line_type lt;
+ char *outline_color;
+ char *color_fill;
+public:
+ graphic_object();
+ ~graphic_object();
+ object_type type() = 0;
+ void print_text();
+ void add_text(text_item *, int);
+ void set_dotted(double);
+ void set_dashed(double);
+ void set_thickness(double);
+ void set_invisible();
+ void set_outline_color(char *);
+ char *get_outline_color();
+ virtual void set_fill(double);
+ virtual void set_xslanted(double);
+ virtual void set_yslanted(double);
+ virtual void set_fill_color(char *);
+};
+
+graphic_object::graphic_object()
+: ntext(0), text(0), aligned(0), outline_color(0), color_fill(0)
+{
+}
+
+void graphic_object::set_dotted(double wid)
+{
+ lt.type = line_type::dotted;
+ lt.dash_width = wid;
+}
+
+void graphic_object::set_dashed(double wid)
+{
+ lt.type = line_type::dashed;
+ lt.dash_width = wid;
+}
+
+void graphic_object::set_thickness(double th)
+{
+ lt.thickness = th;
+}
+
+void graphic_object::set_fill(double)
+{
+}
+
+void graphic_object::set_xslanted(double)
+{
+}
+
+void graphic_object::set_yslanted(double)
+{
+}
+
+void graphic_object::set_fill_color(char *c)
+{
+ color_fill = strsave(c);
+}
+
+void graphic_object::set_outline_color(char *c)
+{
+ outline_color = strsave(c);
+}
+
+char *graphic_object::get_outline_color()
+{
+ return outline_color;
+}
+
+void graphic_object::set_invisible()
+{
+ lt.type = line_type::invisible;
+}
+
+void graphic_object::add_text(text_item *t, int a)
+{
+ aligned = a;
+ int len = 0;
+ text_item *p;
+ for (p = t; p; p = p->next)
+ len++;
+ if (len == 0)
+ text = 0;
+ else {
+ text = new text_piece[len];
+ for (p = t, len = 0; p; p = p->next, len++) {
+ text[len].text = p->text;
+ p->text = 0;
+ text[len].adj = p->adj;
+ text[len].filename = p->filename;
+ text[len].lineno = p->lineno;
+ }
+ }
+ ntext = len;
+}
+
+void graphic_object::print_text()
+{
+ double angle = 0.0;
+ if (aligned) {
+ position d(end() - start());
+ if (d.x != 0.0 || d.y != 0.0)
+ angle = atan2(d.y, d.x);
+ }
+ if (text != 0) {
+ out->set_color(color_fill, get_outline_color());
+ out->text(center(), text, ntext, angle);
+ out->reset_color();
+ }
+}
+
+graphic_object::~graphic_object()
+{
+ if (text)
+ delete[] text;
+}
+
+class rectangle_object : public graphic_object {
+protected:
+ position cent;
+ position dim;
+public:
+ rectangle_object(const position &);
+ double width() { return dim.x; }
+ double height() { return dim.y; }
+ position origin() { return cent; }
+ position center() { return cent; }
+ position north() { return position(cent.x, cent.y + dim.y/2.0); }
+ position south() { return position(cent.x, cent.y - dim.y/2.0); }
+ position east() { return position(cent.x + dim.x/2.0, cent.y); }
+ position west() { return position(cent.x - dim.x/2.0, cent.y); }
+ position north_east() { return position(cent.x + dim.x/2.0, cent.y + dim.y/2.0); }
+ position north_west() { return position(cent.x - dim.x/2.0, cent.y + dim.y/2.0); }
+ position south_east() { return position(cent.x + dim.x/2.0, cent.y - dim.y/2.0); }
+ position south_west() { return position(cent.x - dim.x/2.0, cent.y - dim.y/2.0); }
+ object_type type() = 0;
+ void update_bounding_box(bounding_box *);
+ void move_by(const position &);
+};
+
+rectangle_object::rectangle_object(const position &d)
+: dim(d)
+{
+}
+
+void rectangle_object::update_bounding_box(bounding_box *p)
+{
+ p->encompass(cent - dim/2.0);
+ p->encompass(cent + dim/2.0);
+}
+
+void rectangle_object::move_by(const position &a)
+{
+ cent += a;
+}
+
+class closed_object : public rectangle_object {
+public:
+ closed_object(const position &);
+ object_type type() = 0;
+ void set_fill(double);
+ void set_xslanted(double);
+ void set_yslanted(double);
+ void set_fill_color(char *fill);
+protected:
+ double fill; // < 0 if not filled
+ double xslanted; // !=0 if x is slanted
+ double yslanted; // !=0 if y is slanted
+ char *color_fill; // = 0 if not colored
+};
+
+closed_object::closed_object(const position &pos)
+: rectangle_object(pos), fill(-1.0), xslanted(0), yslanted(0), color_fill(0)
+{
+}
+
+void closed_object::set_fill(double f)
+{
+ assert(f >= 0.0);
+ fill = f;
+}
+
+/* accept positive and negative values */
+void closed_object::set_xslanted(double s)
+{
+ //assert(s >= 0.0);
+ xslanted = s;
+}
+/* accept positive and negative values */
+void closed_object::set_yslanted(double s)
+{
+ //assert(s >= 0.0);
+ yslanted = s;
+}
+
+void closed_object::set_fill_color(char *f)
+{
+ color_fill = strsave(f);
+}
+
+class box_object : public closed_object {
+ double xrad;
+ double yrad;
+public:
+ box_object(const position &, double);
+ object_type type() { return BOX_OBJECT; }
+ void print();
+ position north_east();
+ position north_west();
+ position south_east();
+ position south_west();
+};
+
+box_object::box_object(const position &pos, double r)
+: closed_object(pos), xrad(dim.x > 0 ? r : -r), yrad(dim.y > 0 ? r : -r)
+{
+}
+
+const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2;
+
+position box_object::north_east()
+{
+ return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
+ cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
+}
+
+position box_object::north_west()
+{
+ return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
+ cent.y + dim.y/2.0 - CHOP_FACTOR*yrad);
+}
+
+position box_object::south_east()
+{
+ return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad,
+ cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
+}
+
+position box_object::south_west()
+{
+ return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad,
+ cent.y - dim.y/2.0 + CHOP_FACTOR*yrad);
+}
+
+void box_object::print()
+{
+ if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
+ return;
+ out->set_color(color_fill, graphic_object::get_outline_color());
+ if (xrad == 0.0) {
+ distance dim2 = dim/2.0;
+ position vec[4];
+ /* error("x slanted %1", xslanted); */
+ /* error("y slanted %1", yslanted); */
+ vec[0] = cent + position(dim2.x, -(dim2.y - yslanted)); /* lr */
+ vec[1] = cent + position(dim2.x + xslanted, dim2.y + yslanted); /* ur */
+ vec[2] = cent + position(-(dim2.x - xslanted), dim2.y); /* ul */
+ vec[3] = cent + position(-(dim2.x), -dim2.y); /* ll */
+ out->polygon(vec, 4, lt, fill);
+ }
+ else {
+ distance abs_dim(fabs(dim.x), fabs(dim.y));
+ out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill, color_fill);
+ }
+ out->reset_color();
+}
+
+graphic_object *object_spec::make_box(position *curpos, direction *dirp)
+{
+ static double last_box_height;
+ static double last_box_width;
+ static double last_box_radius;
+ static int have_last_box = 0;
+ if (!(flags & HAS_HEIGHT)) {
+ if ((flags & IS_SAME) && have_last_box)
+ height = last_box_height;
+ else
+ lookup_variable("boxht", &height);
+ }
+ if (!(flags & HAS_WIDTH)) {
+ if ((flags & IS_SAME) && have_last_box)
+ width = last_box_width;
+ else
+ lookup_variable("boxwid", &width);
+ }
+ if (!(flags & HAS_RADIUS)) {
+ if ((flags & IS_SAME) && have_last_box)
+ radius = last_box_radius;
+ else
+ lookup_variable("boxrad", &radius);
+ }
+ last_box_width = width;
+ last_box_height = height;
+ last_box_radius = radius;
+ have_last_box = 1;
+ radius = fabs(radius);
+ if (radius*2.0 > fabs(width))
+ radius = fabs(width/2.0);
+ if (radius*2.0 > fabs(height))
+ radius = fabs(height/2.0);
+ box_object *p = new box_object(position(width, height), radius);
+ if (!position_rectangle(p, curpos, dirp)) {
+ delete p;
+ p = 0;
+ }
+ return p;
+}
+
+// return non-zero for success
+
+int object_spec::position_rectangle(rectangle_object *p,
+ position *curpos, direction *dirp)
+{
+ position pos;
+ dir = *dirp; // ignore any direction in attribute list
+ position motion;
+ switch (dir) {
+ case UP_DIRECTION:
+ motion.y = p->height()/2.0;
+ break;
+ case DOWN_DIRECTION:
+ motion.y = -p->height()/2.0;
+ break;
+ case LEFT_DIRECTION:
+ motion.x = -p->width()/2.0;
+ break;
+ case RIGHT_DIRECTION:
+ motion.x = p->width()/2.0;
+ break;
+ default:
+ assert(0);
+ }
+ if (flags & HAS_AT) {
+ pos = at;
+ if (flags & HAS_WITH) {
+ place offset;
+ place here;
+ here.obj = p;
+ if (!with->follow(here, &offset))
+ return 0;
+ pos -= offset;
+ }
+ }
+ else {
+ pos = *curpos;
+ pos += motion;
+ }
+ p->move_by(pos);
+ pos += motion;
+ *curpos = pos;
+ return 1;
+}
+
+class block_object : public rectangle_object {
+ object_list oblist;
+ PTABLE(place) *tbl;
+public:
+ block_object(const position &, const object_list &ol, PTABLE(place) *t);
+ ~block_object();
+ place *find_label(const char *);
+ object_type type();
+ void move_by(const position &);
+ void print();
+};
+
+block_object::block_object(const position &d, const object_list &ol,
+ PTABLE(place) *t)
+: rectangle_object(d), oblist(ol), tbl(t)
+{
+}
+
+block_object::~block_object()
+{
+ delete tbl;
+ object *p = oblist.head;
+ while (p != 0) {
+ object *tem = p;
+ p = p->next;
+ delete tem;
+ }
+}
+
+void block_object::print()
+{
+ out->begin_block(south_west(), north_east());
+ print_object_list(oblist.head);
+ out->end_block();
+}
+
+static void adjust_objectless_places(PTABLE(place) *tbl, const position &a)
+{
+ // Adjust all the labels that aren't attached to objects.
+ PTABLE_ITERATOR(place) iter(tbl);
+ const char *key;
+ place *pl;
+ while (iter.next(&key, &pl))
+ if (key && csupper(key[0]) && pl->obj == 0) {
+ pl->x += a.x;
+ pl->y += a.y;
+ }
+}
+
+void block_object::move_by(const position &a)
+{
+ cent += a;
+ for (object *p = oblist.head; p; p = p->next)
+ p->move_by(a);
+ adjust_objectless_places(tbl, a);
+}
+
+
+place *block_object::find_label(const char *name)
+{
+ return tbl->lookup(name);
+}
+
+object_type block_object::type()
+{
+ return BLOCK_OBJECT;
+}
+
+graphic_object *object_spec::make_block(position *curpos, direction *dirp)
+{
+ bounding_box bb;
+ for (object *p = oblist.head; p; p = p->next)
+ p->update_bounding_box(&bb);
+ position dim;
+ if (!bb.blank) {
+ position m = -(bb.ll + bb.ur)/2.0;
+ for (object *p = oblist.head; p; p = p->next)
+ p->move_by(m);
+ adjust_objectless_places(tbl, m);
+ dim = bb.ur - bb.ll;
+ }
+ if (flags & HAS_WIDTH)
+ dim.x = width;
+ if (flags & HAS_HEIGHT)
+ dim.y = height;
+ block_object *block = new block_object(dim, oblist, tbl);
+ if (!position_rectangle(block, curpos, dirp)) {
+ delete block;
+ block = 0;
+ }
+ tbl = 0;
+ oblist.head = oblist.tail = 0;
+ return block;
+}
+
+class text_object : public rectangle_object {
+public:
+ text_object(const position &);
+ object_type type() { return TEXT_OBJECT; }
+};
+
+text_object::text_object(const position &d)
+: rectangle_object(d)
+{
+}
+
+graphic_object *object_spec::make_text(position *curpos, direction *dirp)
+{
+ if (!(flags & HAS_HEIGHT)) {
+ lookup_variable("textht", &height);
+ int nitems = 0;
+ for (text_item *t = text; t; t = t->next)
+ nitems++;
+ height *= nitems;
+ }
+ if (!(flags & HAS_WIDTH))
+ lookup_variable("textwid", &width);
+ text_object *p = new text_object(position(width, height));
+ if (!position_rectangle(p, curpos, dirp)) {
+ delete p;
+ p = 0;
+ }
+ return p;
+}
+
+
+class ellipse_object : public closed_object {
+public:
+ ellipse_object(const position &);
+ position north_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
+ cent.y + dim.y/(M_SQRT2*2.0)); }
+ position north_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
+ cent.y + dim.y/(M_SQRT2*2.0)); }
+ position south_east() { return position(cent.x + dim.x/(M_SQRT2*2.0),
+ cent.y - dim.y/(M_SQRT2*2.0)); }
+ position south_west() { return position(cent.x - dim.x/(M_SQRT2*2.0),
+ cent.y - dim.y/(M_SQRT2*2.0)); }
+ double radius() { return dim.x/2.0; }
+ object_type type() { return ELLIPSE_OBJECT; }
+ void print();
+};
+
+ellipse_object::ellipse_object(const position &d)
+: closed_object(d)
+{
+}
+
+void ellipse_object::print()
+{
+ if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
+ return;
+ out->set_color(color_fill, graphic_object::get_outline_color());
+ out->ellipse(cent, dim, lt, fill);
+ out->reset_color();
+}
+
+graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp)
+{
+ static double last_ellipse_height;
+ static double last_ellipse_width;
+ static int have_last_ellipse = 0;
+ if (!(flags & HAS_HEIGHT)) {
+ if ((flags & IS_SAME) && have_last_ellipse)
+ height = last_ellipse_height;
+ else
+ lookup_variable("ellipseht", &height);
+ }
+ if (!(flags & HAS_WIDTH)) {
+ if ((flags & IS_SAME) && have_last_ellipse)
+ width = last_ellipse_width;
+ else
+ lookup_variable("ellipsewid", &width);
+ }
+ last_ellipse_width = width;
+ last_ellipse_height = height;
+ have_last_ellipse = 1;
+ ellipse_object *p = new ellipse_object(position(width, height));
+ if (!position_rectangle(p, curpos, dirp)) {
+ delete p;
+ return 0;
+ }
+ return p;
+}
+
+class circle_object : public ellipse_object {
+public:
+ circle_object(double);
+ object_type type() { return CIRCLE_OBJECT; }
+ void print();
+};
+
+circle_object::circle_object(double diam)
+: ellipse_object(position(diam, diam))
+{
+}
+
+void circle_object::print()
+{
+ if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0)
+ return;
+ out->set_color(color_fill, graphic_object::get_outline_color());
+ out->circle(cent, dim.x/2.0, lt, fill);
+ out->reset_color();
+}
+
+graphic_object *object_spec::make_circle(position *curpos, direction *dirp)
+{
+ static double last_circle_radius;
+ static int have_last_circle = 0;
+ if (!(flags & HAS_RADIUS)) {
+ if ((flags & IS_SAME) && have_last_circle)
+ radius = last_circle_radius;
+ else
+ lookup_variable("circlerad", &radius);
+ }
+ last_circle_radius = radius;
+ have_last_circle = 1;
+ circle_object *p = new circle_object(radius*2.0);
+ if (!position_rectangle(p, curpos, dirp)) {
+ delete p;
+ return 0;
+ }
+ return p;
+}
+
+class move_object : public graphic_object {
+ position strt;
+ position en;
+public:
+ move_object(const position &s, const position &e);
+ position origin() { return en; }
+ object_type type() { return MOVE_OBJECT; }
+ void update_bounding_box(bounding_box *);
+ void move_by(const position &);
+};
+
+move_object::move_object(const position &s, const position &e)
+: strt(s), en(e)
+{
+}
+
+void move_object::update_bounding_box(bounding_box *p)
+{
+ p->encompass(strt);
+ p->encompass(en);
+}
+
+void move_object::move_by(const position &a)
+{
+ strt += a;
+ en += a;
+}
+
+graphic_object *object_spec::make_move(position *curpos, direction *dirp)
+{
+ static position last_move;
+ static int have_last_move = 0;
+ *dirp = dir;
+ // No need to look at at since 'at' attribute sets 'from' attribute.
+ position startpos = (flags & HAS_FROM) ? from : *curpos;
+ if (!(flags & HAS_SEGMENT)) {
+ if ((flags & IS_SAME) && have_last_move)
+ segment_pos = last_move;
+ else {
+ switch (dir) {
+ case UP_DIRECTION:
+ segment_pos.y = segment_height;
+ break;
+ case DOWN_DIRECTION:
+ segment_pos.y = -segment_height;
+ break;
+ case LEFT_DIRECTION:
+ segment_pos.x = -segment_width;
+ break;
+ case RIGHT_DIRECTION:
+ segment_pos.x = segment_width;
+ break;
+ default:
+ assert(0);
+ }
+ }
+ }
+ segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
+ // Reverse the segment_list so that it's in forward order.
+ segment *old = segment_list;
+ segment_list = 0;
+ while (old != 0) {
+ segment *tem = old->next;
+ old->next = segment_list;
+ segment_list = old;
+ old = tem;
+ }
+ // Compute the end position.
+ position endpos = startpos;
+ for (segment *s = segment_list; s; s = s->next)
+ if (s->is_absolute)
+ endpos = s->pos;
+ else
+ endpos += s->pos;
+ have_last_move = 1;
+ last_move = endpos - startpos;
+ move_object *p = new move_object(startpos, endpos);
+ *curpos = endpos;
+ return p;
+}
+
+class linear_object : public graphic_object {
+protected:
+ char arrow_at_start;
+ char arrow_at_end;
+ arrow_head_type aht;
+ position strt;
+ position en;
+public:
+ linear_object(const position &s, const position &e);
+ position start() { return strt; }
+ position end() { return en; }
+ void move_by(const position &);
+ void update_bounding_box(bounding_box *) = 0;
+ object_type type() = 0;
+ void add_arrows(int at_start, int at_end, const arrow_head_type &);
+};
+
+class line_object : public linear_object {
+protected:
+ position *v;
+ int n;
+public:
+ line_object(const position &s, const position &e, position *, int);
+ ~line_object();
+ position origin() { return strt; }
+ position center() { return (strt + en)/2.0; }
+ position north() { return (en.y - strt.y) > 0 ? en : strt; }
+ position south() { return (en.y - strt.y) < 0 ? en : strt; }
+ position east() { return (en.x - strt.x) > 0 ? en : strt; }
+ position west() { return (en.x - strt.x) < 0 ? en : strt; }
+ object_type type() { return LINE_OBJECT; }
+ void update_bounding_box(bounding_box *);
+ void print();
+ void move_by(const position &);
+};
+
+class arrow_object : public line_object {
+public:
+ arrow_object(const position &, const position &, position *, int);
+ object_type type() { return ARROW_OBJECT; }
+};
+
+class spline_object : public line_object {
+public:
+ spline_object(const position &, const position &, position *, int);
+ object_type type() { return SPLINE_OBJECT; }
+ void print();
+ void update_bounding_box(bounding_box *);
+};
+
+linear_object::linear_object(const position &s, const position &e)
+: arrow_at_start(0), arrow_at_end(0), strt(s), en(e)
+{
+}
+
+void linear_object::move_by(const position &a)
+{
+ strt += a;
+ en += a;
+}
+
+void linear_object::add_arrows(int at_start, int at_end,
+ const arrow_head_type &a)
+{
+ arrow_at_start = at_start;
+ arrow_at_end = at_end;
+ aht = a;
+}
+
+line_object::line_object(const position &s, const position &e,
+ position *p, int i)
+: linear_object(s, e), v(p), n(i)
+{
+}
+
+void line_object::print()
+{
+ if (lt.type == line_type::invisible)
+ return;
+ out->set_color(0, graphic_object::get_outline_color());
+ // shorten line length to avoid arrow sticking.
+ position sp = strt;
+ if (arrow_at_start) {
+ position base = v[0] - strt;
+ double hyp = hypot(base);
+ if (hyp == 0.0) {
+ error("cannot draw arrow on object with zero length");
+ return;
+ }
+ if (aht.solid && out->supports_filled_polygons()) {
+ base *= aht.height / hyp;
+ draw_arrow(strt, strt - v[0], aht, lt,
+ graphic_object::get_outline_color());
+ sp = strt + base;
+ } else {
+ base *= fabs(lt.thickness) / hyp / 72 / 4;
+ sp = strt + base;
+ draw_arrow(sp, sp - v[0], aht, lt,
+ graphic_object::get_outline_color());
+ }
+ }
+ if (arrow_at_end) {
+ position base = v[n-1] - (n > 1 ? v[n-2] : strt);
+ double hyp = hypot(base);
+ if (hyp == 0.0) {
+ error("cannot draw arrow on object with zero length");
+ return;
+ }
+ if (aht.solid && out->supports_filled_polygons()) {
+ base *= aht.height / hyp;
+ draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
+ graphic_object::get_outline_color());
+ v[n-1] = en - base;
+ } else {
+ base *= fabs(lt.thickness) / hyp / 72 / 4;
+ v[n-1] = en - base;
+ draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
+ graphic_object::get_outline_color());
+ }
+ }
+ out->line(sp, v, n, lt);
+ out->reset_color();
+}
+
+void line_object::update_bounding_box(bounding_box *p)
+{
+ p->encompass(strt);
+ for (int i = 0; i < n; i++)
+ p->encompass(v[i]);
+}
+
+void line_object::move_by(const position &pos)
+{
+ linear_object::move_by(pos);
+ for (int i = 0; i < n; i++)
+ v[i] += pos;
+}
+
+void spline_object::update_bounding_box(bounding_box *p)
+{
+ p->encompass(strt);
+ p->encompass(en);
+ /*
+
+ If
+
+ p1 = q1/2 + q2/2
+ p2 = q1/6 + q2*5/6
+ p3 = q2*5/6 + q3/6
+ p4 = q2/2 + q3/2
+ [ the points for the Bezier cubic ]
+
+ and
+
+ t = .5
+
+ then
+
+ (1-t)^3*p1 + 3*t*(t - 1)^2*p2 + 3*t^2*(1-t)*p3 + t^3*p4
+ [ the equation for the Bezier cubic ]
+
+ = .125*q1 + .75*q2 + .125*q3
+
+ */
+ for (int i = 1; i < n; i++)
+ p->encompass((i == 1 ? strt : v[i-2])*.125 + v[i-1]*.75 + v[i]*.125);
+}
+
+arrow_object::arrow_object(const position &s, const position &e,
+ position *p, int i)
+: line_object(s, e, p, i)
+{
+}
+
+spline_object::spline_object(const position &s, const position &e,
+ position *p, int i)
+: line_object(s, e, p, i)
+{
+}
+
+void spline_object::print()
+{
+ if (lt.type == line_type::invisible)
+ return;
+ out->set_color(0, graphic_object::get_outline_color());
+ // shorten line length for spline to avoid arrow sticking
+ position sp = strt;
+ if (arrow_at_start) {
+ position base = v[0] - strt;
+ double hyp = hypot(base);
+ if (hyp == 0.0) {
+ error("cannot draw arrow on object with zero length");
+ return;
+ }
+ if (aht.solid && out->supports_filled_polygons()) {
+ base *= aht.height / hyp;
+ draw_arrow(strt, strt - v[0], aht, lt,
+ graphic_object::get_outline_color());
+ sp = strt + base*0.1; // to reserve spline shape
+ } else {
+ base *= fabs(lt.thickness) / hyp / 72 / 4;
+ sp = strt + base;
+ draw_arrow(sp, sp - v[0], aht, lt,
+ graphic_object::get_outline_color());
+ }
+ }
+ if (arrow_at_end) {
+ position base = v[n-1] - (n > 1 ? v[n-2] : strt);
+ double hyp = hypot(base);
+ if (hyp == 0.0) {
+ error("cannot draw arrow on object with zero length");
+ return;
+ }
+ if (aht.solid && out->supports_filled_polygons()) {
+ base *= aht.height / hyp;
+ draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
+ graphic_object::get_outline_color());
+ v[n-1] = en - base*0.1; // to reserve spline shape
+ } else {
+ base *= fabs(lt.thickness) / hyp / 72 / 4;
+ v[n-1] = en - base;
+ draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt,
+ graphic_object::get_outline_color());
+ }
+ }
+ out->spline(sp, v, n, lt);
+ out->reset_color();
+}
+
+line_object::~line_object()
+{
+ delete[] v;
+}
+
+linear_object *object_spec::make_line(position *curpos, direction *dirp)
+{
+ static position last_line;
+ static int have_last_line = 0;
+ *dirp = dir;
+ // We handle 'at' only in conjunction with 'with', otherwise it is
+ // the same as the 'from' attribute.
+ position startpos;
+ if ((flags & HAS_AT) && (flags & HAS_WITH))
+ // handled later -- we need the end position
+ startpos = *curpos;
+ else if (flags & HAS_FROM)
+ startpos = from;
+ else
+ startpos = *curpos;
+ if (!(flags & HAS_SEGMENT)) {
+ if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT)
+ && have_last_line)
+ segment_pos = last_line;
+ else
+ switch (dir) {
+ case UP_DIRECTION:
+ segment_pos.y = segment_height;
+ break;
+ case DOWN_DIRECTION:
+ segment_pos.y = -segment_height;
+ break;
+ case LEFT_DIRECTION:
+ segment_pos.x = -segment_width;
+ break;
+ case RIGHT_DIRECTION:
+ segment_pos.x = segment_width;
+ break;
+ default:
+ assert(0);
+ }
+ }
+ segment_list = new segment(segment_pos, segment_is_absolute, segment_list);
+ // reverse the segment_list so that it's in forward order
+ segment *old = segment_list;
+ segment_list = 0;
+ while (old != 0) {
+ segment *tem = old->next;
+ old->next = segment_list;
+ segment_list = old;
+ old = tem;
+ }
+ // Absolutise all movements
+ position endpos = startpos;
+ int nsegments = 0;
+ segment *s;
+ for (s = segment_list; s; s = s->next, nsegments++)
+ if (s->is_absolute)
+ endpos = s->pos;
+ else {
+ endpos += s->pos;
+ s->pos = endpos;
+ s->is_absolute = 1; // to avoid confusion
+ }
+ if ((flags & HAS_AT) && (flags & HAS_WITH)) {
+ // 'tmpobj' works for arrows and splines too -- we only need positions
+ line_object tmpobj(startpos, endpos, 0, 0);
+ position pos = at;
+ place offset;
+ place here;
+ here.obj = &tmpobj;
+ if (!with->follow(here, &offset))
+ return 0;
+ pos -= offset;
+ for (s = segment_list; s; s = s->next)
+ s->pos += pos;
+ startpos += pos;
+ endpos += pos;
+ }
+ // handle chop
+ line_object *p = 0;
+ position *v = new position[nsegments];
+ int i = 0;
+ for (s = segment_list; s; s = s->next, i++)
+ v[i] = s->pos;
+ if (flags & IS_DEFAULT_CHOPPED) {
+ lookup_variable("circlerad", &start_chop);
+ end_chop = start_chop;
+ flags |= IS_CHOPPED;
+ }
+ if (flags & IS_CHOPPED) {
+ position start_chop_vec, end_chop_vec;
+ if (start_chop != 0.0) {
+ start_chop_vec = v[0] - startpos;
+ start_chop_vec *= start_chop / hypot(start_chop_vec);
+ }
+ if (end_chop != 0.0) {
+ end_chop_vec = (v[nsegments - 1]
+ - (nsegments > 1 ? v[nsegments - 2] : startpos));
+ end_chop_vec *= end_chop / hypot(end_chop_vec);
+ }
+ startpos += start_chop_vec;
+ v[nsegments - 1] -= end_chop_vec;
+ endpos -= end_chop_vec;
+ }
+ switch (type) {
+ case SPLINE_OBJECT:
+ p = new spline_object(startpos, endpos, v, nsegments);
+ break;
+ case ARROW_OBJECT:
+ p = new arrow_object(startpos, endpos, v, nsegments);
+ break;
+ case LINE_OBJECT:
+ p = new line_object(startpos, endpos, v, nsegments);
+ break;
+ default:
+ assert(0);
+ }
+ have_last_line = 1;
+ last_line = endpos - startpos;
+ *curpos = endpos;
+ return p;
+}
+
+class arc_object : public linear_object {
+ int clockwise;
+ position cent;
+ double rad;
+public:
+ arc_object(int, const position &, const position &, const position &);
+ position origin() { return cent; }
+ position center() { return cent; }
+ double radius() { return rad; }
+ position north();
+ position south();
+ position east();
+ position west();
+ position north_east();
+ position north_west();
+ position south_east();
+ position south_west();
+ void update_bounding_box(bounding_box *);
+ object_type type() { return ARC_OBJECT; }
+ void print();
+ void move_by(const position &pos);
+};
+
+arc_object::arc_object(int cw, const position &s, const position &e,
+ const position &c)
+: linear_object(s, e), clockwise(cw), cent(c)
+{
+ rad = hypot(c - s);
+}
+
+void arc_object::move_by(const position &pos)
+{
+ linear_object::move_by(pos);
+ cent += pos;
+}
+
+// we get arc corners from the corresponding circle
+
+position arc_object::north()
+{
+ position result(cent);
+ result.y += rad;
+ return result;
+}
+
+position arc_object::south()
+{
+ position result(cent);
+ result.y -= rad;
+ return result;
+}
+
+position arc_object::east()
+{
+ position result(cent);
+ result.x += rad;
+ return result;
+}
+
+position arc_object::west()
+{
+ position result(cent);
+ result.x -= rad;
+ return result;
+}
+
+position arc_object::north_east()
+{
+ position result(cent);
+ result.x += rad/M_SQRT2;
+ result.y += rad/M_SQRT2;
+ return result;
+}
+
+position arc_object::north_west()
+{
+ position result(cent);
+ result.x -= rad/M_SQRT2;
+ result.y += rad/M_SQRT2;
+ return result;
+}
+
+position arc_object::south_east()
+{
+ position result(cent);
+ result.x += rad/M_SQRT2;
+ result.y -= rad/M_SQRT2;
+ return result;
+}
+
+position arc_object::south_west()
+{
+ position result(cent);
+ result.x -= rad/M_SQRT2;
+ result.y -= rad/M_SQRT2;
+ return result;
+}
+
+
+void arc_object::print()
+{
+ if (lt.type == line_type::invisible)
+ return;
+ out->set_color(0, graphic_object::get_outline_color());
+ // handle arrow direction; make shorter line for arc
+ position sp, ep, b;
+ if (clockwise) {
+ sp = en;
+ ep = strt;
+ } else {
+ sp = strt;
+ ep = en;
+ }
+ if (arrow_at_start) {
+ double theta = aht.height / rad;
+ if (clockwise)
+ theta = - theta;
+ b = strt - cent;
+ b = position(b.x*cos(theta) - b.y*sin(theta),
+ b.x*sin(theta) + b.y*cos(theta)) + cent;
+ if (clockwise)
+ ep = b;
+ else
+ sp = b;
+ if (aht.solid && out->supports_filled_polygons()) {
+ draw_arrow(strt, strt - b, aht, lt,
+ graphic_object::get_outline_color());
+ } else {
+ position v = b;
+ theta = fabs(lt.thickness) / 72 / 4 / rad;
+ if (clockwise)
+ theta = - theta;
+ b = strt - cent;
+ b = position(b.x*cos(theta) - b.y*sin(theta),
+ b.x*sin(theta) + b.y*cos(theta)) + cent;
+ draw_arrow(b, b - v, aht, lt,
+ graphic_object::get_outline_color());
+ out->line(b, &v, 1, lt);
+ }
+ }
+ if (arrow_at_end) {
+ double theta = aht.height / rad;
+ if (!clockwise)
+ theta = - theta;
+ b = en - cent;
+ b = position(b.x*cos(theta) - b.y*sin(theta),
+ b.x*sin(theta) + b.y*cos(theta)) + cent;
+ if (clockwise)
+ sp = b;
+ else
+ ep = b;
+ if (aht.solid && out->supports_filled_polygons()) {
+ draw_arrow(en, en - b, aht, lt,
+ graphic_object::get_outline_color());
+ } else {
+ position v = b;
+ theta = fabs(lt.thickness) / 72 / 4 / rad;
+ if (!clockwise)
+ theta = - theta;
+ b = en - cent;
+ b = position(b.x*cos(theta) - b.y*sin(theta),
+ b.x*sin(theta) + b.y*cos(theta)) + cent;
+ draw_arrow(b, b - v, aht, lt,
+ graphic_object::get_outline_color());
+ out->line(b, &v, 1, lt);
+ }
+ }
+ out->arc(sp, cent, ep, lt);
+ out->reset_color();
+}
+
+inline double max(double a, double b)
+{
+ return a > b ? a : b;
+}
+
+void arc_object::update_bounding_box(bounding_box *p)
+{
+ p->encompass(strt);
+ p->encompass(en);
+ position start_offset = strt - cent;
+ if (start_offset.x == 0.0 && start_offset.y == 0.0)
+ return;
+ position end_offset = en - cent;
+ if (end_offset.x == 0.0 && end_offset.y == 0.0)
+ return;
+ double start_quad = atan2(start_offset.y, start_offset.x)/(M_PI/2.0);
+ double end_quad = atan2(end_offset.y, end_offset.x)/(M_PI/2.0);
+ if (clockwise) {
+ double temp = start_quad;
+ start_quad = end_quad;
+ end_quad = temp;
+ }
+ if (start_quad < 0.0)
+ start_quad += 4.0;
+ while (end_quad <= start_quad)
+ end_quad += 4.0;
+ double r = max(hypot(start_offset), hypot(end_offset));
+ for (int q = int(start_quad) + 1; q < end_quad; q++) {
+ position offset;
+ switch (q % 4) {
+ case 0:
+ offset.x = r;
+ break;
+ case 1:
+ offset.y = r;
+ break;
+ case 2:
+ offset.x = -r;
+ break;
+ case 3:
+ offset.y = -r;
+ break;
+ }
+ p->encompass(cent + offset);
+ }
+}
+
+// We ignore the with attribute. The at attribute always refers to the center.
+
+linear_object *object_spec::make_arc(position *curpos, direction *dirp)
+{
+ *dirp = dir;
+ int cw = (flags & IS_CLOCKWISE) != 0;
+ // compute the start
+ position startpos;
+ if (flags & HAS_FROM)
+ startpos = from;
+ else
+ startpos = *curpos;
+ if (!(flags & HAS_RADIUS))
+ lookup_variable("arcrad", &radius);
+ // compute the end
+ position endpos;
+ if (flags & HAS_TO)
+ endpos = to;
+ else {
+ position m(radius, radius);
+ // Adjust the signs.
+ if (cw) {
+ if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
+ m.x = -m.x;
+ if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION)
+ m.y = -m.y;
+ *dirp = direction((dir + 3) % 4);
+ }
+ else {
+ if (dir == UP_DIRECTION || dir == LEFT_DIRECTION)
+ m.x = -m.x;
+ if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION)
+ m.y = -m.y;
+ *dirp = direction((dir + 1) % 4);
+ }
+ endpos = startpos + m;
+ }
+ // compute the center
+ position centerpos;
+ if (flags & HAS_AT)
+ centerpos = at;
+ else if (startpos == endpos)
+ centerpos = startpos;
+ else {
+ position h = (endpos - startpos)/2.0;
+ double d = hypot(h);
+ if (radius <= 0)
+ radius = .25;
+ // make the radius big enough
+ if (radius < d)
+ radius = d;
+ double alpha = acos(d/radius);
+ double theta = atan2(h.y, h.x);
+ if (cw)
+ theta -= alpha;
+ else
+ theta += alpha;
+ centerpos = position(cos(theta), sin(theta))*radius + startpos;
+ }
+ arc_object *p = new arc_object(cw, startpos, endpos, centerpos);
+ *curpos = endpos;
+ return p;
+}
+
+graphic_object *object_spec::make_linear(position *curpos, direction *dirp)
+{
+ linear_object *obj;
+ if (type == ARC_OBJECT)
+ obj = make_arc(curpos, dirp);
+ else
+ obj = make_line(curpos, dirp);
+ if (type == ARROW_OBJECT
+ && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD)) == 0)
+ flags |= HAS_RIGHT_ARROW_HEAD;
+ if (obj && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD))) {
+ arrow_head_type a;
+ int at_start = (flags & HAS_LEFT_ARROW_HEAD) != 0;
+ int at_end = (flags & HAS_RIGHT_ARROW_HEAD) != 0;
+ if (flags & HAS_HEIGHT)
+ a.height = height;
+ else
+ lookup_variable("arrowht", &a.height);
+ if (flags & HAS_WIDTH)
+ a.width = width;
+ else
+ lookup_variable("arrowwid", &a.width);
+ double solid;
+ lookup_variable("arrowhead", &solid);
+ a.solid = solid != 0.0;
+ obj->add_arrows(at_start, at_end, a);
+ }
+ return obj;
+}
+
+object *object_spec::make_object(position *curpos, direction *dirp)
+{
+ graphic_object *obj = 0;
+ switch (type) {
+ case BLOCK_OBJECT:
+ obj = make_block(curpos, dirp);
+ break;
+ case BOX_OBJECT:
+ obj = make_box(curpos, dirp);
+ break;
+ case TEXT_OBJECT:
+ obj = make_text(curpos, dirp);
+ break;
+ case ELLIPSE_OBJECT:
+ obj = make_ellipse(curpos, dirp);
+ break;
+ case CIRCLE_OBJECT:
+ obj = make_circle(curpos, dirp);
+ break;
+ case MOVE_OBJECT:
+ obj = make_move(curpos, dirp);
+ break;
+ case ARC_OBJECT:
+ case LINE_OBJECT:
+ case SPLINE_OBJECT:
+ case ARROW_OBJECT:
+ obj = make_linear(curpos, dirp);
+ break;
+ case MARK_OBJECT:
+ case OTHER_OBJECT:
+ default:
+ assert(0);
+ break;
+ }
+ if (obj) {
+ if (flags & IS_INVISIBLE)
+ obj->set_invisible();
+ if (text != 0)
+ obj->add_text(text, (flags & IS_ALIGNED) != 0);
+ if (flags & IS_DOTTED)
+ obj->set_dotted(dash_width);
+ else if (flags & IS_DASHED)
+ obj->set_dashed(dash_width);
+ double th;
+ if (flags & HAS_THICKNESS)
+ th = thickness;
+ else
+ lookup_variable("linethick", &th);
+ obj->set_thickness(th);
+ if (flags & IS_OUTLINED)
+ obj->set_outline_color(outlined);
+ if (flags & IS_XSLANTED)
+ obj->set_xslanted(xslanted);
+ if (flags & IS_YSLANTED)
+ obj->set_yslanted(yslanted);
+ if (flags & (IS_DEFAULT_FILLED | IS_FILLED)) {
+ if (flags & IS_SHADED)
+ obj->set_fill_color(shaded);
+ else {
+ if (flags & IS_DEFAULT_FILLED)
+ lookup_variable("fillval", &fill);
+ if (fill < 0.0)
+ error("bad fill value %1", fill);
+ else
+ obj->set_fill(fill);
+ }
+ }
+ }
+ return obj;
+}
+
+struct string_list {
+ string_list *next;
+ char *str;
+ string_list(char *);
+ ~string_list();
+};
+
+string_list::string_list(char *s)
+: next(0), str(s)
+{
+}
+
+string_list::~string_list()
+{
+ free(str);
+}
+
+/* A path is used to hold the argument to the 'with' attribute. For
+ example, '.nw' or '.A.s' or '.A'. The major operation on a path is to
+ take a place and follow the path through the place to place within the
+ place. Note that '.A.B.C.sw' will work.
+
+ For compatibility with DWB pic, 'with' accepts positions also (this
+ is incorrectly documented in CSTR 116). */
+
+path::path(corner c)
+: crn(c), label_list(0), ypath(0), is_position(0)
+{
+}
+
+path::path(position p)
+: crn(0), label_list(0), ypath(0), is_position(1)
+{
+ pos.x = p.x;
+ pos.y = p.y;
+}
+
+path::path(char *l, corner c)
+: crn(c), ypath(0), is_position(0)
+{
+ label_list = new string_list(l);
+}
+
+path::~path()
+{
+ while (label_list) {
+ string_list *tem = label_list;
+ label_list = label_list->next;
+ delete tem;
+ }
+ delete ypath;
+}
+
+void path::append(corner c)
+{
+ assert(crn == 0);
+ crn = c;
+}
+
+void path::append(char *s)
+{
+ string_list **p;
+ for (p = &label_list; *p; p = &(*p)->next)
+ ;
+ *p = new string_list(s);
+}
+
+void path::set_ypath(path *p)
+{
+ ypath = p;
+}
+
+// return non-zero for success
+
+int path::follow(const place &pl, place *result) const
+{
+ if (is_position) {
+ result->x = pos.x;
+ result->y = pos.y;
+ result->obj = 0;
+ return 1;
+ }
+ const place *p = &pl;
+ for (string_list *lb = label_list; lb; lb = lb->next)
+ if (p->obj == 0 || (p = p->obj->find_label(lb->str)) == 0) {
+ lex_error("object does not contain a place '%1'", lb->str);
+ return 0;
+ }
+ if (crn == 0 || p->obj == 0)
+ *result = *p;
+ else {
+ position ps = ((p->obj)->*(crn))();
+ result->x = ps.x;
+ result->y = ps.y;
+ result->obj = 0;
+ }
+ if (ypath) {
+ place tem;
+ if (!ypath->follow(pl, &tem))
+ return 0;
+ result->y = tem.y;
+ if (result->obj != tem.obj)
+ result->obj = 0;
+ }
+ return 1;
+}
+
+void print_object_list(object *p)
+{
+ for (; p; p = p->next) {
+ p->print();
+ p->print_text();
+ }
+}
+
+void print_picture(object *obj)
+{
+ bounding_box bb;
+ for (object *p = obj; p; p = p->next)
+ p->update_bounding_box(&bb);
+ double scale;
+ lookup_variable("scale", &scale);
+ out->start_picture(scale, bb.ll, bb.ur);
+ print_object_list(obj);
+ out->finish_picture();
+}
+
diff --git a/src/preproc/pic/object.h b/src/preproc/pic/object.h
new file mode 100644
index 0000000..e39b6a6
--- /dev/null
+++ b/src/preproc/pic/object.h
@@ -0,0 +1,227 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct place;
+
+enum object_type {
+ OTHER_OBJECT,
+ BOX_OBJECT,
+ CIRCLE_OBJECT,
+ ELLIPSE_OBJECT,
+ ARC_OBJECT,
+ SPLINE_OBJECT,
+ LINE_OBJECT,
+ ARROW_OBJECT,
+ MOVE_OBJECT,
+ TEXT_OBJECT,
+ BLOCK_OBJECT,
+ MARK_OBJECT
+ };
+
+struct bounding_box;
+
+struct object {
+ object *prev;
+ object *next;
+ object();
+ virtual ~object();
+ virtual position origin();
+ virtual double width();
+ virtual double radius();
+ virtual double height();
+ virtual position north();
+ virtual position south();
+ virtual position east();
+ virtual position west();
+ virtual position north_east();
+ virtual position north_west();
+ virtual position south_east();
+ virtual position south_west();
+ virtual position start();
+ virtual position end();
+ virtual position center();
+ virtual place *find_label(const char *);
+ virtual void move_by(const position &);
+ virtual int blank();
+ virtual void update_bounding_box(bounding_box *);
+ virtual object_type type() = 0;
+ virtual void print();
+ virtual void print_text();
+};
+
+typedef position (object::*corner)();
+
+struct place {
+ object *obj;
+ double x, y;
+};
+
+struct string_list;
+
+class path {
+ position pos;
+ corner crn;
+ string_list *label_list;
+ path *ypath;
+ int is_position;
+public:
+ path(corner = 0);
+ path(position);
+ path(char *, corner = 0);
+ ~path();
+ void append(corner);
+ void append(char *);
+ void set_ypath(path *);
+ int follow(const place &, place *) const;
+};
+
+struct object_list {
+ object *head;
+ object *tail;
+ object_list();
+ void append(object *);
+ void wrap_up_block(object_list *);
+};
+
+declare_ptable(place)
+
+// these go counterclockwise
+enum direction {
+ RIGHT_DIRECTION,
+ UP_DIRECTION,
+ LEFT_DIRECTION,
+ DOWN_DIRECTION
+ };
+
+struct graphics_state {
+ double x, y;
+ direction dir;
+};
+
+struct saved_state : public graphics_state {
+ saved_state *prev;
+ PTABLE(place) *tbl;
+};
+
+
+struct text_item {
+ text_item *next;
+ char *text;
+ adjustment adj;
+ const char *filename;
+ int lineno;
+
+ text_item(char *, const char *, int);
+ ~text_item();
+};
+
+const unsigned long IS_DOTTED = 01;
+const unsigned long IS_DASHED = 02;
+const unsigned long IS_CLOCKWISE = 04;
+const unsigned long IS_INVISIBLE = 020;
+const unsigned long HAS_LEFT_ARROW_HEAD = 040;
+const unsigned long HAS_RIGHT_ARROW_HEAD = 0100;
+const unsigned long HAS_SEGMENT = 0200;
+const unsigned long IS_SAME = 0400;
+const unsigned long HAS_FROM = 01000;
+const unsigned long HAS_AT = 02000;
+const unsigned long HAS_WITH = 04000;
+const unsigned long HAS_HEIGHT = 010000;
+const unsigned long HAS_WIDTH = 020000;
+const unsigned long HAS_RADIUS = 040000;
+const unsigned long HAS_TO = 0100000;
+const unsigned long IS_CHOPPED = 0200000;
+const unsigned long IS_DEFAULT_CHOPPED = 0400000;
+const unsigned long HAS_THICKNESS = 01000000;
+const unsigned long IS_FILLED = 02000000;
+const unsigned long IS_DEFAULT_FILLED = 04000000;
+const unsigned long IS_ALIGNED = 010000000;
+const unsigned long IS_SHADED = 020000000;
+const unsigned long IS_OUTLINED = 040000000;
+const unsigned long IS_XSLANTED = 0100000000;
+const unsigned long IS_YSLANTED = 0200000000;
+
+struct segment {
+ int is_absolute;
+ position pos;
+ segment *next;
+ segment(const position &, int, segment *);
+};
+
+class rectangle_object;
+class graphic_object;
+class linear_object;
+
+struct object_spec {
+ unsigned long flags;
+ object_type type;
+ object_list oblist;
+ PTABLE(place) *tbl;
+ double dash_width;
+ position from;
+ position to;
+ position at;
+ position by;
+ path *with;
+ text_item *text;
+ double height;
+ double radius;
+ double width;
+ double segment_width;
+ double segment_height;
+ double start_chop;
+ double end_chop;
+ double thickness;
+ double fill;
+ double xslanted;
+ double yslanted;
+ char *shaded;
+ char *outlined;
+ direction dir;
+ segment *segment_list;
+ position segment_pos;
+ int segment_is_absolute;
+
+ object_spec(object_type);
+ ~object_spec();
+ object *make_object(position *, direction *);
+ graphic_object *make_box(position *, direction *);
+ graphic_object *make_block(position *, direction *);
+ graphic_object *make_text(position *, direction *);
+ graphic_object *make_ellipse(position *, direction *);
+ graphic_object *make_circle(position *, direction *);
+ linear_object *make_line(position *, direction *);
+ linear_object *make_arc(position *, direction *);
+ graphic_object *make_linear(position *, direction *);
+ graphic_object *make_move(position *, direction *);
+ int position_rectangle(rectangle_object *p, position *curpos,
+ direction *dirp);
+};
+
+
+object *make_object(object_spec *, position *, direction *);
+
+object *make_mark_object();
+object *make_command_object(char *, const char *, int);
+
+int lookup_variable(const char *name, double *val);
+void define_variable(const char *name, double val);
+
+void print_picture(object *);
+
diff --git a/src/preproc/pic/output.h b/src/preproc/pic/output.h
new file mode 100644
index 0000000..f01e0a1
--- /dev/null
+++ b/src/preproc/pic/output.h
@@ -0,0 +1,82 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct line_type {
+ enum { invisible, solid, dotted, dashed } type;
+ double dash_width;
+ double thickness; // the thickness is in points
+
+ line_type();
+};
+
+
+class output {
+protected:
+ char *args;
+ double desired_height; // zero if no height specified
+ double desired_width; // zero if no depth specified
+ double compute_scale(double, const position &, const position &);
+public:
+ output();
+ virtual ~output();
+ void set_desired_width_height(double wid, double ht);
+ void set_args(const char *);
+ virtual void start_picture(double sc, const position &ll, const position &ur) = 0;
+ virtual void finish_picture() = 0;
+ virtual void circle(const position &, double rad,
+ const line_type &, double) = 0;
+ virtual void text(const position &, text_piece *, int, double) = 0;
+ virtual void line(const position &, const position *, int n,
+ const line_type &) = 0;
+ virtual void polygon(const position *, int n,
+ const line_type &, double) = 0;
+ virtual void spline(const position &, const position *, int n,
+ const line_type &) = 0;
+ virtual void arc(const position &, const position &, const position &,
+ const line_type &) = 0;
+ virtual void ellipse(const position &, const distance &,
+ const line_type &, double) = 0;
+ virtual void rounded_box(const position &, const distance &, double,
+ const line_type &, double, char *) = 0;
+ virtual void command(const char *, const char *, int) = 0;
+ virtual void set_location(const char *, int) {}
+ virtual void set_color(char *, char *) = 0;
+ virtual void reset_color() = 0;
+ virtual char *get_last_filled() = 0;
+ virtual char *get_outline_color() = 0;
+ virtual int supports_filled_polygons();
+ virtual void begin_block(const position &ll, const position &ur);
+ virtual void end_block();
+};
+
+extern output *out;
+
+/* #define FIG_SUPPORT 1 */
+#define TEX_SUPPORT 1
+
+output *make_troff_output();
+
+#ifdef TEX_SUPPORT
+output *make_tex_output();
+output *make_tpic_output();
+#endif /* TEX_SUPPORT */
+
+#ifdef FIG_SUPPORT
+output *make_fig_output();
+#endif /* FIG_SUPPORT */
diff --git a/src/preproc/pic/pic.1.man b/src/preproc/pic/pic.1.man
new file mode 100644
index 0000000..727cbc3
--- /dev/null
+++ b/src/preproc/pic/pic.1.man
@@ -0,0 +1,1569 @@
+.TH @g@pic @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@pic \- compile pictures for
+.I troff
+or TeX
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_pic_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.\" Definitions
+.\" ====================================================================
+.
+.ie t \{\
+. ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+. ds lx L\h'-0.36m'\v'-0.22v'\s-2A\s0\h'-0.15m'\v'0.22v'\*[tx]
+.\}
+.el \{\
+. ds tx TeX
+. ds lx LaTeX
+.\}
+.
+.ie \n(.g .ds ic \/
+.el .ds ic \^
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@pic
+.RB [ \-CnSU ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@pic
+.B \-t
+.RB [ \-cCSUz ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@pic
+.B \-\-help
+.YS
+.
+.
+.SY @g@pic
+.B \-v
+.
+.SY @g@pic
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU implementation of
+.I pic \" generic
+is part of the
+.MR groff @MAN1EXT@
+document formatting system.
+.
+.I @g@pic
+is a
+.MR @g@troff @MAN1EXT@
+preprocessor that translates descriptions of diagrammatic pictures
+embedded in
+.MR roff @MAN7EXT@
+or \*[tx] input files into the language understood by \*[tx] or
+.IR @g@troff .
+.
+It copies the contents of each
+.I file
+to the standard output stream,
+except that lines between
+.B .PS
+and any of
+.BR .PE ,
+.BR .PF ,
+or
+.B .PY
+are interpreted as picture descriptions in the
+.I pic
+language.
+.
+End a
+.I @g@pic
+picture with
+.B .PE
+to leave the drawing position at the bottom of the picture,
+and with
+.B .PF
+or
+.B .PY
+to leave it at the top.
+.
+Normally,
+.I @g@pic
+is not executed directly by the user,
+but invoked by specifying the
+.B \-p
+option to
+.MR groff @MAN1EXT@ .
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+the standard input stream is read.
+.
+.
+.P
+It is the user's responsibility to provide appropriate definitions
+of the
+.BR PS ,
+.BR PE ,
+and one or both of the
+.B PF
+and
+.B PY
+macros.
+.
+When a macro package does not supply these,
+obtain simple definitions with the
+.I groff
+option
+.BR \-mpic ;
+these will center each picture.
+.
+.
+.P
+GNU
+.I pic \" GNU
+supports
+.B PY
+as a synonym of
+.B PF
+to work around a name space collision with the
+.I mm
+macro package,
+which defines
+.B PF
+as a page footer management macro.
+.
+Use
+.B PF
+preferentially unless a similar problem faces your document.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-c
+Be more compatible with
+.IR tpic ;
+implies
+.BR \-t .
+.
+Lines beginning with
+.B \[rs]
+are not passed through transparently.
+.
+Lines beginning with
+.B .\&
+are passed through with the initial
+.B .\&
+changed to
+.BR \[rs] .
+.
+A line beginning with
+.B .ps
+is given special treatment:
+it takes an optional integer argument specifying the line thickness
+(pen size)
+in milliinches;
+a missing argument restores the previous line thickness;
+the default line thickness is 8\~milliinches.
+.
+The line thickness thus specified takes effect only when a
+non-negative line thickness has not been specified by use of the
+.B \%thickness
+attribute or by setting the
+.B \%linethick
+variable.
+.
+.
+.TP
+.B \-C
+Recognize
+.BR .PS ,
+.BR .PE ,
+.BR .PF ,
+and
+.B .PY
+even when followed by a character other than space or newline.
+.
+.
+.TP
+.B \-n
+Don't use
+.I groff
+extensions to the
+.I troff \" generic
+drawing commands.
+.
+Specify this option if a postprocessor you're using doesn't support
+these extensions,
+described in
+.MR groff_out @MAN5EXT@ .
+.
+This option also causes
+.I @g@pic
+not to use zero-length lines to draw dots in
+.I troff \" generic
+mode.
+.
+.
+.TP
+.B \-S
+Operate in
+.I safer mode;
+.B sh
+commands are ignored.
+.
+This mode,
+enabled by default,
+can be useful when operating on untrustworthy input.
+.
+.
+.TP
+.B \-t
+Produce \*[tx] output.
+.
+.
+.TP
+.B \-U
+Operate in
+.I unsafe mode;
+.B sh
+commands are interpreted.
+.
+.
+.TP
+.B \-z
+In \*[tx] mode,
+draw dots using zero-length lines.
+.
+.
+.P
+The following options supported by other versions of
+.I pic \" generic
+are ignored.
+.
+.
+.TP
+.B \-D
+Draw all lines using the \[rs]D escape sequence.
+GNU
+.I pic \" GNU
+always does this.
+.
+.TP
+.BI \-T\~ dev
+Generate output for the
+.I troff \" generic
+device
+.IR dev .
+.
+This is unnecessary because the
+.I troff \" generic
+output generated by
+GNU
+.I pic \" GNU
+is device-independent.
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+This section primarily discusses the differences between GNU
+.I pic \" GNU
+and the Eighth Edition Research Unix version of AT&T
+.I pic \" AT&T
+(1985).
+.
+Many of these differences also apply to later versions of AT&T
+.IR pic .
+.
+.
+.\" ====================================================================
+.SS "\*[tx] mode"
+.\" ====================================================================
+.
+\*[tx]-compatible output is produced when the
+.B \-t
+option is specified.
+.
+You must use a \*[tx] driver that supports
+.I tpic
+version 2 specials.
+.
+.RI ( tpic
+was a fork of AT&T
+.I pic \" AT&T
+by Tim Morgan of the University of California at Irvine that diverged
+from its source around 1984.
+.
+It is best known today for lending its name to a group of
+.B \[rs]special
+commands it produced for \*[tx].)
+.\" http://ftp.cs.stanford.edu/tex/texhax/texhax90.019
+.
+.
+.P
+Lines beginning with
+.B \[rs]
+are passed through transparently;
+a
+.B %
+is added to the end of the line to avoid unwanted spaces.
+.
+You can safely use this feature to change fonts or the value of
+.BR \[rs]baselineskip .
+.
+Anything else may well produce undesirable results;
+use at your own risk.
+.
+By default,
+lines beginning with a dot are not treated specially\[em]but see the
+.B \-c
+option.
+.
+.
+.P
+In \*[tx] mode,
+.I @g@pic
+will define a vbox called
+.B \[rs]graph
+for each picture.
+.
+Use GNU
+.IR pic 's \" GNU
+.B figname
+command to change the name of the vbox.
+.
+You must print that vbox yourself using the command
+.
+.RS
+.EX
+\[rs]centerline{\[rs]box\[rs]graph}
+.EE
+.RE
+.
+for instance.
+.
+Since the vbox has a height of zero
+(it is defined with
+.BR \[rs]vtop )
+this will produce slightly more vertical space above the picture than
+below it;
+.
+.RS
+.EX
+\[rs]centerline{\[rs]raise 1em\[rs]box\[rs]graph}
+.EE
+.RE
+.
+would avoid this.
+.
+To give the vbox a positive height and a depth of zero
+(as used by \*[lx]'s
+.IR \%graphics.sty ,
+for example)
+define the following macro in your document.
+.
+.RS
+.EX
+\[rs]def\[rs]gpicbox#1{%
+ \[rs]vbox{\[rs]unvbox\[rs]csname #1\[rs]endcsname\[rs]kern 0pt}}
+.EE
+.RE
+.
+You can then simply say
+.B \[rs]gpicbox{graph}
+instead of
+.BR \[rs]box\[rs]graph .
+.
+.
+.\" ====================================================================
+.SS Commands
+.\" ====================================================================
+.
+Several commands new to GNU
+.I pic \" GNU
+accept delimiters,
+shown in their synopses as braces
+.BR "{ }" .
+.
+Nesting of braces is supported.
+.
+Any other characters
+(except a space,
+tab,
+or newline)
+.\" XXX even crazy control characters, ugh--src/preproc/pic/lex.cpp:1266
+may be used as alternative delimiters,
+in which case the members of a given pair must be identical.
+.
+Strings are recognized within delimiters of either kind;
+they may contain the delimiter character or unbalanced braces.
+.
+.
+.TP
+\fBfor\fR \fIvariable\fR \fB=\fR \fIexpr1\fR \fBto\fR \fIexpr2\fR \
+[\fBby\fR [\fB*\fR]\,\fIexpr3\/\fR] \fBdo\fR \fIX\fR \fIbody\fR \fIX\fR
+Set
+.I variable
+to
+.IR expr1 .
+.
+While the value of
+.I variable
+is less than or equal to
+.IR expr2 ,
+do
+.I body
+and increment
+.I variable
+by
+.IR expr3 ;
+if
+.B by
+is not given,
+increment
+.I variable
+by 1.
+.
+If
+.I expr3
+is prefixed by
+.B *
+then
+.I variable
+will instead be multiplied by
+.IR expr3 .
+.
+The value of
+.I expr3
+can be negative for the additive case;
+.I variable
+is then tested whether it is greater than or equal to
+.IR expr2 .
+.
+For the multiplicative case,
+.I expr3
+must be greater than zero.
+.
+If the constraints aren't met,
+the loop isn't executed.
+.
+.I X
+can be any character not occurring in
+.IR body .
+.
+.TP
+\fBif\fR \fIexpr\fR \fBthen\fR \fIX\fR \fIif-true\fR \fIX\fR \
+[\fBelse\fR \fIY\fR \fIif-false\fR \fIY\fR]
+Evaluate
+.IR expr ;
+if it is non-zero then do
+.IR if-true ,
+otherwise do
+.IR if-false .
+.
+.I X
+can be any character not occurring in
+.IR if-true .
+.
+.I Y
+can be any character not occurring in
+.IR if-false .
+.
+.TP
+.BI print\~ arg\c
+\~.\|.\|.
+Concatenate and write arguments to the standard error stream followed by
+a newline.
+.
+Each
+.I arg
+must be an expression,
+a position,
+or text.
+.
+This is useful for debugging.
+.
+.TP
+.BI command\~ arg\c
+\~.\|.\|.
+.\" Move right margin to indentation since we must indent more later.
+.RS
+Concatenate arguments
+and pass them as a line to
+.I troff \" generic
+or \*[tx].
+.
+Each
+.I arg
+must be an expression,
+a position,
+or text.
+.
+.B command
+allows the values of
+.I pic
+variables to be passed to the formatter.
+.
+For example,
+.
+.RS
+.EX
+\&.PS
+x = 14
+command ".ds string x is " x "."
+\&.PE
+\[rs]*[string]
+.EE
+.RE
+.
+produces
+.
+.RS
+.EX
+x is 14.
+.EE
+.RE
+when formatted with
+.IR troff . \" generic
+.RE
+.
+.
+.TP
+\fBsh\fR \fIX\fR \fIcommand\fR \fIX\fR
+Pass
+.I command
+to a shell.
+.
+.
+.TP
+\fBcopy\fR \fB"\,\fIfilename\/\fB"\fR
+Include
+.I filename
+at this point in the file.
+.
+.
+.TP
+.BR copy\~ [ \[dq]\c
+.IB filename \[dq]\c
+.RB ]\~ thru\~\c
+.IR "X body X" \~\c \" space in roman: we must use 2-font macro with \c
+.RB [ until\~ \[dq]\c
+.IB word \[dq]\c
+]
+.TQ
+.BR copy\~ [ \[dq]\c
+.IB filename \[dq]\c
+.RB ]\~ thru\~\c
+.IR macro \~\c \" space roman because we must use 2-font macro with \c
+.RB [ until\~ \[dq]\c
+.IB word \[dq]\c
+]
+.\" Move right margin to indentation since we must indent more later.
+.RS
+This construct does
+.I body
+once for each line of
+.IR filename ;
+the line is split into blank-delimited words,
+and occurrences of
+.BI $ i
+in
+.IR body ,
+for
+.I i
+between 1 and 9,
+are replaced by the
+.IR i -th
+word of the line.
+.
+If
+.I filename
+is not given,
+lines are taken from the current input up to
+.BR .PE .
+.
+If an
+.B until
+clause is specified,
+lines will be read only until a line the first word of which is
+.IR word ;
+that line will then be discarded.
+.
+.I X
+can be any character not occurring in
+.IR body .
+.
+For example,
+.
+.RS \" now move further
+.EX
+\&.PS
+copy thru % circle at ($1,$2) % until "END"
+1 2
+3 4
+5 6
+END
+box
+\&.PE
+.EE
+.RE
+.
+and
+.
+.RS
+.EX
+\&.PS
+circle at (1,2)
+circle at (3,4)
+circle at (5,6)
+box
+\&.PE
+.EE
+.RE
+.
+are equivalent.
+.
+The commands to be performed for each line can also be taken from a
+macro defined earlier by giving the name of the macro as the argument to
+.BR thru .
+.
+The argument after
+.B thru
+is looked up as a macro name first;
+if not defined,
+its first character is interpreted as a delimiter.
+.RE
+.
+.
+.TP
+.B reset
+.TQ
+.BI reset\~ pvar1\c
+.RB [ , ]\~\c
+.IR pvar2 \~.\|.\|.
+Reset predefined variables
+.IR pvar1 ,
+.I pvar2
+\&.\|.\|.\& to their default values;
+if no arguments are given,
+reset all predefined variables to their default values.
+.
+Variable names may be separated by commas,
+spaces,
+or both.
+.
+Assigning a value to
+.B scale
+also causes all predefined variables that control dimensions to be reset
+to their default values times the new value of
+.BR scale .
+.
+.
+.TP
+\fBplot\fR \fIexpr\fR [\fB"\,\fItext\*(ic\fB"\fR]
+This is a text object which is constructed by using
+.I text
+as a format string for sprintf
+with an argument of
+.IR expr .
+.
+If
+.I text
+is omitted a format string of
+.B \[dq]%g\[dq]
+is used.
+.
+Attributes can be specified in the same way as for a normal text
+object.
+Be very careful that you specify an appropriate format string;
+.I @g@pic
+does only very limited checking of the string.
+.
+This is deprecated in favour of
+.BR sprintf .
+.
+.TP
+.IB var \~:=\~ expr
+.RS
+This syntax resembles variable assignment with
+.B =
+except that
+.I var
+must already be defined,
+and
+.I expr
+will be assigned to
+.I var
+without creating a variable local to the current block.
+.
+(By contrast,
+.B =
+defines
+.I var
+in the current block if it is not already defined there,
+and then changes the value in the current block only.)
+.
+For example,
+.
+.RS
+.EX
+.B .PS
+.B x = 3
+.B y = 3
+.B [
+.B x := 5
+.B y = 5
+.B ]
+.B print x " " y
+.B .PE
+.EE
+.RE
+.
+writes
+.
+.RS
+.EX
+5 3
+.EE
+.RE
+.
+to the standard error stream.
+.RE
+.
+.
+.\" ====================================================================
+.SS Expressions
+.\" ====================================================================
+.
+The syntax for expressions has been significantly extended.
+.
+.
+.P
+.IB x\ \[ha]\ y
+(exponentiation)
+.br
+.BI sin( x )
+.br
+.BI cos( x )
+.br
+.BI atan2( y , \ x )
+.br
+.BI log( x )
+(base 10)
+.br
+.BI exp( x )
+(base 10, i.e.\&
+.ie t 10\v'-.4m'\fIx\*(ic\fR\v'.4m')
+.el 10\[ha]\fIx\fR)
+.br
+.BI sqrt( x )
+.br
+.BI int( x )
+.br
+.B rand()
+(return a random number between 0 and 1)
+.br
+.BI rand( x )
+(return a random number between 1 and
+.IR x ;
+deprecated)
+.br
+.BI srand( x )
+(set the random number seed)
+.br
+.BI max( e1 , \ e2 )
+.br
+.BI min( e1 , \ e2 )
+.br
+.BI ! e
+.br
+\fIe1\fB && \fIe2\fR
+.br
+\fIe1\fB || \fIe2\fR
+.br
+\fIe1\fB == \fIe2\fR
+.br
+\fIe1\fB != \fIe2\fR
+.br
+\fIe1\fB >= \fIe2\fR
+.br
+\fIe1\fB > \fIe2\fR
+.br
+\fIe1\fB <= \fIe2\fR
+.br
+\fIe1\fB < \fIe2\fR
+.br
+\fB"\,\fIstr1\*(ic\fB" == "\,\fIstr2\*(ic\fB"\fR
+.br
+\fB"\,\fIstr1\*(ic\fB" != "\,\fIstr2\*(ic\fB"\fR
+.br
+.
+.
+.LP
+String comparison expressions must be parenthesised in some contexts
+to avoid ambiguity.
+.
+.
+.\" ====================================================================
+.SS "Other changes"
+.\" ====================================================================
+.
+A bare expression,
+.IR expr ,
+is acceptable as an attribute;
+it is equivalent to
+.IR dir\ expr ,
+where
+.I dir
+is the current direction.
+.
+For example
+.LP
+.RS
+.B line 2i
+.RE
+.LP
+means draw a line 2\ inches long in the current direction.
+.
+The \[oq]i\[cq]
+(or \[oq]I\[cq])
+character is ignored;
+to use another measurement unit,
+set the
+.I scale
+variable to an appropriate value.
+.
+.
+.LP
+The maximum width and height of the picture are taken from the variables
+.B maxpswid
+and
+.BR maxpsht .
+.
+Initially,
+these have values 8.5 and 11.
+.
+.
+.LP
+Scientific notation is allowed for numbers.
+For example
+.
+.
+.RS
+.LP
+.B
+x = 5e\-2
+.RE
+.
+.
+.LP
+Text attributes can be compounded.
+.
+For example,
+.
+.RS
+.LP
+.B
+"foo" above ljust
+.RE
+.
+.
+.LP
+is valid.
+.
+.
+.LP
+There is no limit to the depth to which blocks can be examined.
+.
+For example,
+.RS
+.LP
+.EX
+[A: [B: [C: box ]]] with .A.B.C.sw at 1,2
+circle at last [\^].A.B.C
+.EE
+.RE
+.
+.
+.LP
+is acceptable.
+.
+.
+.LP
+Arcs now have compass points determined by the circle of which the arc
+is a part.
+.
+.
+.LP
+Circles,
+ellipses,
+and arcs can be dotted or dashed.
+.
+In \*[tx] mode splines can be dotted or dashed also.
+.
+.
+.LP
+Boxes can have rounded corners.
+.
+The
+.B rad
+attribute specifies the radius of the quarter-circles at each corner.
+If no
+.B rad
+or
+.B diam
+attribute is given,
+a radius of
+.B boxrad
+is used.
+.
+Initially,
+.B boxrad
+has a value of\ 0.
+.
+A box with rounded corners can be dotted or dashed.
+.
+.
+.LP
+Boxes can have slanted sides.
+.
+This effectively changes the shape of a box from a rectangle to an
+arbitrary parallelogram.
+.
+The
+.B xslanted
+and
+.B yslanted
+attributes specify the x and y\~offset of the box's upper right
+corner from its default position.
+.
+.
+.LP
+The
+.B .PS
+line can have a second argument specifying a maximum height for
+the picture.
+.
+If the width of zero is specified the width will be ignored in computing
+the scaling factor for the picture.
+.
+GNU
+.I pic \" GNU
+will always scale a picture by the same amount vertically as well as
+horizontally.
+.
+This is different from DWB 2.0
+.I pic \" foreign
+which may scale a picture by a different amount vertically than
+horizontally if a height is specified.
+.
+.
+.LP
+Each text object has an invisible box associated with it.
+.
+The compass points of a text object are determined by this box.
+.
+The implicit motion associated with the object is also determined
+by this box.
+.
+The dimensions of this box are taken from the width and height
+attributes;
+if the width attribute is not supplied then the width will be taken to
+be
+.BR textwid ;
+if the height attribute is not supplied then the height will be taken to
+be the number of text strings associated with the object times
+.BR textht .
+.
+Initially,
+.B textwid
+and
+.B textht
+have a value of 0.
+.
+.
+.LP
+In
+(almost all)
+places where a quoted text string can be used,
+an expression of the form
+.
+.
+.IP
+.BI sprintf(\[dq] format \[dq],\~ arg ,\fR\~.\|.\|.\fB)
+.
+.
+.LP
+can also be used;
+this will produce the arguments formatted according to
+.IR format ,
+which should be a string as described in
+.MR printf 3
+appropriate for the number of arguments supplied.
+.
+Only the modifiers
+.RB \[lq] # \[rq],
+.RB \[lq] \- \[rq],
+.RB \[lq] + \[rq],
+and \[lq]\~\[rq] [space]),
+a minimum field width,
+an optional precision,
+and the conversion specifiers
+.BR %e ,
+.BR %E ,
+.BR %f ,
+.BR %g ,
+.BR %G ,
+and
+.B %%
+are supported.
+.
+.
+.LP
+The thickness of the lines used to draw objects is controlled by the
+.B linethick
+variable.
+.
+This gives the thickness of lines in points.
+.
+A negative value means use the default thickness:
+in \*[tx] output mode,
+this means use a thickness of 8 milliinches;
+in \*[tx] output mode with the
+.B \-c
+option,
+this means use the line thickness specified by
+.B .ps
+lines;
+in
+.I troff
+output mode,
+this means use a thickness proportional to the pointsize.
+.
+A zero value means draw the thinnest possible line supported by
+the output device.
+.
+Initially,
+it has a value of \-1.
+.
+There is also a
+.BR thick [ ness ]
+attribute.
+.
+For example,
+.
+.
+.RS
+.LP
+.B circle thickness 1.5
+.RE
+.
+.
+.LP
+would draw a circle using a line with a thickness of 1.5 points.
+.
+The thickness of lines is not affected by the
+value of the
+.B scale
+variable,
+nor by the width or height given in the
+.B .PS
+line.
+.
+.
+.LP
+Boxes
+(including boxes with rounded corners or slanted sides),
+circles and ellipses can be filled by giving them an attribute of
+.BR fill [ ed ].
+.
+This takes an optional argument of an expression with a value between
+0 and 1;
+0 will fill it with white,
+1 with black,
+values in between with a proportionally gray shade.
+.
+A value greater than 1 can also be used:
+this means fill with the
+shade of gray that is currently being used for text and lines.
+.
+Normally this will be black,
+but output devices may provide a mechanism for changing this.
+.
+Without an argument,
+then the value of the variable
+.B \%fillval
+will be used.
+.
+Initially,
+this has a value of 0.5.
+.
+The invisible attribute does not affect the filling of objects.
+.
+Any text associated with a filled object will be added after the object
+has been filled,
+so that the text will not be obscured by the filling.
+.
+.
+.P
+Additional modifiers are available to draw colored objects:
+.BR \%outline [ d ]
+sets the color of the outline,
+.B shaded
+the fill color,
+and
+.BR colo [ u ] r [ ed ]
+sets both.
+.
+All expect a subsequent string argument specifying the color.
+.
+.RS
+.EX
+circle shaded \[dq]green\[dq] outline \[dq]black\[dq]
+.EE
+.RE
+.
+Color is not yet supported in \*[tx] mode.
+.
+Device macro files like
+.I ps.tmac
+declare color names;
+you can define additional ones with the
+.B \%defcolor
+request
+(see
+.MR groff @MAN7EXT@ ).
+.
+.
+.LP
+To change the name of the vbox in \*[tx] mode,
+set the pseudo-variable
+.B \%figname
+(which is actually a specially parsed command)
+within a picture.
+.
+Example:
+.RS
+.LP
+.B .PS
+.br
+.B figname = foobar;
+.br
+.B ...
+.br
+.B .PE
+.RE
+.
+.
+.LP
+The picture is then available in the box
+.BR \[rs]foobar .
+.
+.
+.LP
+.I @g@pic
+assumes that at the beginning of a picture both glyph and fill color are
+set to the default value.
+.
+.
+.LP
+Arrow heads will be drawn as solid triangles if the variable
+.B arrowhead
+is non-zero and either \*[tx] mode is enabled or the
+.B \-n
+option has not been given.
+.
+Initially,
+.B arrowhead
+has a value of\ 1.
+.
+Solid arrow heads are always filled with the current outline color.
+.
+.
+.LP
+The
+.I troff
+output of
+.I @g@pic
+is device-independent.
+.
+The
+.B \-T
+option is therefore redundant.
+.
+All numbers are taken to be in inches;
+numbers are never interpreted to be in
+.I troff
+machine units.
+.
+.
+.LP
+Objects can have an
+.B aligned
+attribute.
+.
+This will only work if the postprocessor is
+.MR grops @MAN1EXT@
+or
+.MR gropdf @MAN1EXT@ .
+.
+Any text associated with an object having the
+.B aligned
+attribute will be rotated about the center of the object
+so that it is aligned in the direction from the start point
+to the end point of the object.
+.
+This attribute will have no effect on objects whose start and end points
+are coincident.
+.
+.
+.LP
+In places where
+.IB n th
+is allowed,
+.BI \[aq] expr \[aq]th
+is also allowed.
+.
+.RB \[lq] \[aq]th \[lq]
+is a single token:
+no space is allowed between the apostrophe and the
+.RB \[lq] th \[rq].
+.
+.
+For example,
+.IP
+.EX
+for i = 1 to 4 do {
+ line from \[aq]i\[aq]th box.nw to \[aq]i+1\[aq]th box.se
+}
+.EE
+.
+.
+.\" ====================================================================
+.SH Conversion
+.\" ====================================================================
+.
+To obtain a stand-alone picture from a
+.I @g@pic
+file,
+enclose your
+.I pic \" language
+code with
+.B .PS
+and
+.B .PE
+requests;
+.I roff
+configuration commands may be added at the beginning of the file,
+but no
+.I roff
+text.
+.
+.
+.LP
+It is necessary to feed this file into
+.I groff
+without adding any page information,
+so you must check which
+.B .PS
+and
+.B .PE
+requests are actually called.
+.
+For example,
+the
+.I mm
+macro package adds a page number,
+which is very annoying.
+.
+At the moment,
+calling standard
+.I groff
+without any macro package works.
+.
+Alternatively,
+you can define your own requests,
+e.g.,
+to do nothing:
+.
+.
+.LP
+.RS
+.EX
+\&.de PS
+\&..
+\&.de PE
+\&..
+.EE
+.RE
+.
+.
+.LP
+.I groff
+itself does not provide direct conversion into other graphics file
+formats.
+.
+But there are lots of possibilities if you first transform your
+picture into PostScript\*R format using the
+.I groff
+option
+.BR \-Tps .
+.
+Since this
+.IR ps -file
+lacks BoundingBox information it is not very useful by itself, but it
+may be fed into other conversion programs, usually named
+.BI ps2 other
+or
+.BI psto other
+or the like.
+.
+Moreover,
+the PostScript interpreter Ghostscript
+.RI ( gs (1))
+has built-in graphics conversion devices that are called with the option
+.
+.
+.LP
+.RS
+.BI "gs \-sDEVICE=" <devname>
+.RE
+.
+.
+.LP
+Call
+.
+.
+.LP
+.RS
+.B gs \-\-help
+.RE
+.
+.
+.LP
+for a list of the available devices.
+.
+.
+.LP
+An alternative may be to use the
+.B \-Tpdf
+option to convert your picture directly into
+.B PDF
+format.
+.
+The MediaBox of the file produced can be controlled by passing a
+.B \-P\-p
+papersize to
+.IR groff .
+.
+.
+.br
+.ne 3v
+.P
+As the Encapsulated PostScript File Format
+.B EPS
+is getting more and more important,
+and the conversion wasn't regarded trivial in the past you might be
+interested to know that there is a conversion tool named
+.I ps2eps
+which does the right job.
+.
+It is much better than the tool
+.I ps2epsi
+packaged with
+.IR gs .
+.
+.
+.LP
+For bitmapped graphic formats,
+you should use
+.IR pstopnm ;
+the resulting (intermediate)
+.MR pnm 5
+file can be then converted to virtually any graphics format using the
+tools of the
+.B netpbm
+package.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @MACRODIR@/pic.tmac
+offers simple definitions of the
+.BR PS ,
+.BR PE ,
+.BR PF ,
+and
+.B PY
+macros.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+Characters that are invalid as input to GNU
+.I troff \" GNU
+(see the
+.I groff
+Texinfo manual or
+.MR groff_char @MAN7EXT@
+for a list)
+are rejected even in \*[tx] mode.
+.
+.
+.LP
+The interpretation of
+.B \%fillval
+is incompatible with the
+.I pic \" AT&T
+in Tenth Edition Research Unix,
+which interprets 0 as black and 1 as white.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.TP
+.I @DOCDIR@/\:pic\:.ps
+\[lq]Making Pictures with GNU pic\[rq],
+by Eric S.\& Raymond.
+.
+This file,
+together with its source,
+.IR pic.ms ,
+is part of the
+.I groff
+distribution.
+.
+.
+.P
+\[lq]PIC\[em]A Graphics Language for Typesetting: User Manual\[rq],
+by Brian W.\& Kernighan,
+1984
+(revised 1991),
+AT&T Bell Laboratories Computing Science Technical Report No.\& 116
+.
+.
+.P
+.I ps2eps
+is available from CTAN mirrors, e.g.,
+.UR ftp://\:ftp\:.dante\:.de/\:tex\-archive/\:support/\:ps2eps/
+.UE
+.
+.
+.LP
+W.\& Richard Stevens,
+.UR http://\:www\:.kohala\:.com/\:start/\:troff/\:pic2html\:.html
+.I Turning PIC into HTML
+.UE
+.
+.
+.LP
+W.\& Richard Stevens,
+.UR http://\:www\:.kohala\:.com/\:start/\:troff/\:pic\:.examples\:.ps
+.IR "Examples of " pic " Macros"
+.UE
+.
+.
+.LP
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR tex 1 ,
+.MR gs 1 ,
+.MR ps2eps 1 ,
+.MR pstopnm 1 ,
+.MR ps2epsi 1 ,
+.MR pnm 5
+.
+.
+.\" Clean up.
+.rm tx
+.rm lx
+.rm ic
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_pic_1_man_C]
+.do rr *groff_pic_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/preproc/pic/pic.am b/src/preproc/pic/pic.am
new file mode 100644
index 0000000..fae1372
--- /dev/null
+++ b/src/preproc/pic/pic.am
@@ -0,0 +1,56 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += pic
+pic_CPPFLAGS = $(AM_CPPFLAGS) \
+ -I $(top_srcdir)/src/preproc/pic \
+ -I $(top_builddir)/src/preproc/pic
+pic_LDADD = libgroff.a $(LIBM) lib/libgnu.a
+pic_SOURCES = \
+ src/preproc/pic/pic.ypp \
+ src/preproc/pic/lex.cpp \
+ src/preproc/pic/main.cpp \
+ src/preproc/pic/object.cpp \
+ src/preproc/pic/common.cpp \
+ src/preproc/pic/troff.cpp \
+ src/preproc/pic/tex.cpp \
+ src/preproc/pic/pic.h \
+ src/preproc/pic/position.h \
+ src/preproc/pic/text.h \
+ src/preproc/pic/common.h \
+ src/preproc/pic/output.h \
+ src/preproc/pic/object.h
+
+PREFIXMAN1 += src/preproc/pic/pic.1
+EXTRA_DIST += \
+ src/preproc/pic/TODO \
+ src/preproc/pic/pic.1.man
+
+# Since pic_CPPFLAGS was set, all .o files have a 'pic-' prefix.
+src/preproc/pic/pic-lex.$(OBJEXT): src/preproc/pic/pic.hpp
+
+MAINTAINERCLEANFILES += \
+ src/preproc/pic/pic.cpp \
+ src/preproc/pic/pic.hpp \
+ src/preproc/pic/pic.output
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/pic/pic.cpp b/src/preproc/pic/pic.cpp
new file mode 100644
index 0000000..14b15e4
--- /dev/null
+++ b/src/preproc/pic/pic.cpp
@@ -0,0 +1,5080 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30802
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.8.2"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* First part of user prologue. */
+#line 19 "../src/preproc/pic/pic.ypp"
+
+#include "pic.h"
+#include "ptable.h"
+#include "object.h"
+
+extern int delim_flag;
+extern void copy_rest_thru(const char *, const char *);
+extern void copy_file_thru(const char *, const char *, const char *);
+extern void push_body(const char *);
+extern void do_for(char *var, double from, double to,
+ int by_is_multiplicative, double by, char *body);
+extern void do_lookahead();
+
+/* Maximum number of characters produced by printf("%g") */
+#define GDIGITS 14
+
+int yylex();
+void yyerror(const char *);
+
+void reset(const char *nm);
+void reset_all();
+
+place *lookup_label(const char *);
+void define_label(const char *label, const place *pl);
+
+direction current_direction;
+position current_position;
+
+implement_ptable(place)
+
+PTABLE(place) top_table;
+
+PTABLE(place) *current_table = &top_table;
+saved_state *current_saved_state = 0;
+
+object_list olist;
+
+const char *ordinal_postfix(int n);
+const char *object_type_name(object_type type);
+char *format_number(const char *fmt, double n);
+char *do_sprintf(const char *fmt, const double *v, int nv);
+
+
+#line 115 "src/preproc/pic/pic.cpp"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Use api.header.include to #include this header
+ instead of duplicating it here. */
+#ifndef YY_YY_SRC_PREPROC_PIC_PIC_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_PIC_PIC_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ LABEL = 258, /* LABEL */
+ VARIABLE = 259, /* VARIABLE */
+ NUMBER = 260, /* NUMBER */
+ TEXT = 261, /* TEXT */
+ COMMAND_LINE = 262, /* COMMAND_LINE */
+ DELIMITED = 263, /* DELIMITED */
+ ORDINAL = 264, /* ORDINAL */
+ TH = 265, /* TH */
+ LEFT_ARROW_HEAD = 266, /* LEFT_ARROW_HEAD */
+ RIGHT_ARROW_HEAD = 267, /* RIGHT_ARROW_HEAD */
+ DOUBLE_ARROW_HEAD = 268, /* DOUBLE_ARROW_HEAD */
+ LAST = 269, /* LAST */
+ BOX = 270, /* BOX */
+ CIRCLE = 271, /* CIRCLE */
+ ELLIPSE = 272, /* ELLIPSE */
+ ARC = 273, /* ARC */
+ LINE = 274, /* LINE */
+ ARROW = 275, /* ARROW */
+ MOVE = 276, /* MOVE */
+ SPLINE = 277, /* SPLINE */
+ HEIGHT = 278, /* HEIGHT */
+ RADIUS = 279, /* RADIUS */
+ FIGNAME = 280, /* FIGNAME */
+ WIDTH = 281, /* WIDTH */
+ DIAMETER = 282, /* DIAMETER */
+ UP = 283, /* UP */
+ DOWN = 284, /* DOWN */
+ RIGHT = 285, /* RIGHT */
+ LEFT = 286, /* LEFT */
+ FROM = 287, /* FROM */
+ TO = 288, /* TO */
+ AT = 289, /* AT */
+ WITH = 290, /* WITH */
+ BY = 291, /* BY */
+ THEN = 292, /* THEN */
+ SOLID = 293, /* SOLID */
+ DOTTED = 294, /* DOTTED */
+ DASHED = 295, /* DASHED */
+ CHOP = 296, /* CHOP */
+ SAME = 297, /* SAME */
+ INVISIBLE = 298, /* INVISIBLE */
+ LJUST = 299, /* LJUST */
+ RJUST = 300, /* RJUST */
+ ABOVE = 301, /* ABOVE */
+ BELOW = 302, /* BELOW */
+ OF = 303, /* OF */
+ THE = 304, /* THE */
+ WAY = 305, /* WAY */
+ BETWEEN = 306, /* BETWEEN */
+ AND = 307, /* AND */
+ HERE = 308, /* HERE */
+ DOT_N = 309, /* DOT_N */
+ DOT_E = 310, /* DOT_E */
+ DOT_W = 311, /* DOT_W */
+ DOT_S = 312, /* DOT_S */
+ DOT_NE = 313, /* DOT_NE */
+ DOT_SE = 314, /* DOT_SE */
+ DOT_NW = 315, /* DOT_NW */
+ DOT_SW = 316, /* DOT_SW */
+ DOT_C = 317, /* DOT_C */
+ DOT_START = 318, /* DOT_START */
+ DOT_END = 319, /* DOT_END */
+ DOT_X = 320, /* DOT_X */
+ DOT_Y = 321, /* DOT_Y */
+ DOT_HT = 322, /* DOT_HT */
+ DOT_WID = 323, /* DOT_WID */
+ DOT_RAD = 324, /* DOT_RAD */
+ SIN = 325, /* SIN */
+ COS = 326, /* COS */
+ ATAN2 = 327, /* ATAN2 */
+ LOG = 328, /* LOG */
+ EXP = 329, /* EXP */
+ SQRT = 330, /* SQRT */
+ K_MAX = 331, /* K_MAX */
+ K_MIN = 332, /* K_MIN */
+ INT = 333, /* INT */
+ RAND = 334, /* RAND */
+ SRAND = 335, /* SRAND */
+ COPY = 336, /* COPY */
+ THRU = 337, /* THRU */
+ TOP = 338, /* TOP */
+ BOTTOM = 339, /* BOTTOM */
+ UPPER = 340, /* UPPER */
+ LOWER = 341, /* LOWER */
+ SH = 342, /* SH */
+ PRINT = 343, /* PRINT */
+ CW = 344, /* CW */
+ CCW = 345, /* CCW */
+ FOR = 346, /* FOR */
+ DO = 347, /* DO */
+ IF = 348, /* IF */
+ ELSE = 349, /* ELSE */
+ ANDAND = 350, /* ANDAND */
+ OROR = 351, /* OROR */
+ NOTEQUAL = 352, /* NOTEQUAL */
+ EQUALEQUAL = 353, /* EQUALEQUAL */
+ LESSEQUAL = 354, /* LESSEQUAL */
+ GREATEREQUAL = 355, /* GREATEREQUAL */
+ LEFT_CORNER = 356, /* LEFT_CORNER */
+ RIGHT_CORNER = 357, /* RIGHT_CORNER */
+ NORTH = 358, /* NORTH */
+ SOUTH = 359, /* SOUTH */
+ EAST = 360, /* EAST */
+ WEST = 361, /* WEST */
+ CENTER = 362, /* CENTER */
+ END = 363, /* END */
+ START = 364, /* START */
+ RESET = 365, /* RESET */
+ UNTIL = 366, /* UNTIL */
+ PLOT = 367, /* PLOT */
+ THICKNESS = 368, /* THICKNESS */
+ FILL = 369, /* FILL */
+ COLORED = 370, /* COLORED */
+ OUTLINED = 371, /* OUTLINED */
+ SHADED = 372, /* SHADED */
+ XSLANTED = 373, /* XSLANTED */
+ YSLANTED = 374, /* YSLANTED */
+ ALIGNED = 375, /* ALIGNED */
+ SPRINTF = 376, /* SPRINTF */
+ COMMAND = 377, /* COMMAND */
+ DEFINE = 378, /* DEFINE */
+ UNDEF = 379 /* UNDEF */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define LABEL 258
+#define VARIABLE 259
+#define NUMBER 260
+#define TEXT 261
+#define COMMAND_LINE 262
+#define DELIMITED 263
+#define ORDINAL 264
+#define TH 265
+#define LEFT_ARROW_HEAD 266
+#define RIGHT_ARROW_HEAD 267
+#define DOUBLE_ARROW_HEAD 268
+#define LAST 269
+#define BOX 270
+#define CIRCLE 271
+#define ELLIPSE 272
+#define ARC 273
+#define LINE 274
+#define ARROW 275
+#define MOVE 276
+#define SPLINE 277
+#define HEIGHT 278
+#define RADIUS 279
+#define FIGNAME 280
+#define WIDTH 281
+#define DIAMETER 282
+#define UP 283
+#define DOWN 284
+#define RIGHT 285
+#define LEFT 286
+#define FROM 287
+#define TO 288
+#define AT 289
+#define WITH 290
+#define BY 291
+#define THEN 292
+#define SOLID 293
+#define DOTTED 294
+#define DASHED 295
+#define CHOP 296
+#define SAME 297
+#define INVISIBLE 298
+#define LJUST 299
+#define RJUST 300
+#define ABOVE 301
+#define BELOW 302
+#define OF 303
+#define THE 304
+#define WAY 305
+#define BETWEEN 306
+#define AND 307
+#define HERE 308
+#define DOT_N 309
+#define DOT_E 310
+#define DOT_W 311
+#define DOT_S 312
+#define DOT_NE 313
+#define DOT_SE 314
+#define DOT_NW 315
+#define DOT_SW 316
+#define DOT_C 317
+#define DOT_START 318
+#define DOT_END 319
+#define DOT_X 320
+#define DOT_Y 321
+#define DOT_HT 322
+#define DOT_WID 323
+#define DOT_RAD 324
+#define SIN 325
+#define COS 326
+#define ATAN2 327
+#define LOG 328
+#define EXP 329
+#define SQRT 330
+#define K_MAX 331
+#define K_MIN 332
+#define INT 333
+#define RAND 334
+#define SRAND 335
+#define COPY 336
+#define THRU 337
+#define TOP 338
+#define BOTTOM 339
+#define UPPER 340
+#define LOWER 341
+#define SH 342
+#define PRINT 343
+#define CW 344
+#define CCW 345
+#define FOR 346
+#define DO 347
+#define IF 348
+#define ELSE 349
+#define ANDAND 350
+#define OROR 351
+#define NOTEQUAL 352
+#define EQUALEQUAL 353
+#define LESSEQUAL 354
+#define GREATEREQUAL 355
+#define LEFT_CORNER 356
+#define RIGHT_CORNER 357
+#define NORTH 358
+#define SOUTH 359
+#define EAST 360
+#define WEST 361
+#define CENTER 362
+#define END 363
+#define START 364
+#define RESET 365
+#define UNTIL 366
+#define PLOT 367
+#define THICKNESS 368
+#define FILL 369
+#define COLORED 370
+#define OUTLINED 371
+#define SHADED 372
+#define XSLANTED 373
+#define YSLANTED 374
+#define ALIGNED 375
+#define SPRINTF 376
+#define COMMAND 377
+#define DEFINE 378
+#define UNDEF 379
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 65 "../src/preproc/pic/pic.ypp"
+
+ char *str;
+ int n;
+ double x;
+ struct { double x, y; } pair;
+ struct { double x; char *body; } if_data;
+ struct { char *str; const char *filename; int lineno; } lstr;
+ struct { double *v; int nv; int maxv; } dv;
+ struct { double val; int is_multiplicative; } by;
+ place pl;
+ object *obj;
+ corner crn;
+ path *pth;
+ object_spec *spec;
+ saved_state *pstate;
+ graphics_state state;
+ object_type obtype;
+
+#line 435 "src/preproc/pic/pic.cpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_PIC_PIC_HPP_INCLUDED */
+/* Symbol kind. */
+enum yysymbol_kind_t
+{
+ YYSYMBOL_YYEMPTY = -2,
+ YYSYMBOL_YYEOF = 0, /* "end of file" */
+ YYSYMBOL_YYerror = 1, /* error */
+ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */
+ YYSYMBOL_LABEL = 3, /* LABEL */
+ YYSYMBOL_VARIABLE = 4, /* VARIABLE */
+ YYSYMBOL_NUMBER = 5, /* NUMBER */
+ YYSYMBOL_TEXT = 6, /* TEXT */
+ YYSYMBOL_COMMAND_LINE = 7, /* COMMAND_LINE */
+ YYSYMBOL_DELIMITED = 8, /* DELIMITED */
+ YYSYMBOL_ORDINAL = 9, /* ORDINAL */
+ YYSYMBOL_TH = 10, /* TH */
+ YYSYMBOL_LEFT_ARROW_HEAD = 11, /* LEFT_ARROW_HEAD */
+ YYSYMBOL_RIGHT_ARROW_HEAD = 12, /* RIGHT_ARROW_HEAD */
+ YYSYMBOL_DOUBLE_ARROW_HEAD = 13, /* DOUBLE_ARROW_HEAD */
+ YYSYMBOL_LAST = 14, /* LAST */
+ YYSYMBOL_BOX = 15, /* BOX */
+ YYSYMBOL_CIRCLE = 16, /* CIRCLE */
+ YYSYMBOL_ELLIPSE = 17, /* ELLIPSE */
+ YYSYMBOL_ARC = 18, /* ARC */
+ YYSYMBOL_LINE = 19, /* LINE */
+ YYSYMBOL_ARROW = 20, /* ARROW */
+ YYSYMBOL_MOVE = 21, /* MOVE */
+ YYSYMBOL_SPLINE = 22, /* SPLINE */
+ YYSYMBOL_HEIGHT = 23, /* HEIGHT */
+ YYSYMBOL_RADIUS = 24, /* RADIUS */
+ YYSYMBOL_FIGNAME = 25, /* FIGNAME */
+ YYSYMBOL_WIDTH = 26, /* WIDTH */
+ YYSYMBOL_DIAMETER = 27, /* DIAMETER */
+ YYSYMBOL_UP = 28, /* UP */
+ YYSYMBOL_DOWN = 29, /* DOWN */
+ YYSYMBOL_RIGHT = 30, /* RIGHT */
+ YYSYMBOL_LEFT = 31, /* LEFT */
+ YYSYMBOL_FROM = 32, /* FROM */
+ YYSYMBOL_TO = 33, /* TO */
+ YYSYMBOL_AT = 34, /* AT */
+ YYSYMBOL_WITH = 35, /* WITH */
+ YYSYMBOL_BY = 36, /* BY */
+ YYSYMBOL_THEN = 37, /* THEN */
+ YYSYMBOL_SOLID = 38, /* SOLID */
+ YYSYMBOL_DOTTED = 39, /* DOTTED */
+ YYSYMBOL_DASHED = 40, /* DASHED */
+ YYSYMBOL_CHOP = 41, /* CHOP */
+ YYSYMBOL_SAME = 42, /* SAME */
+ YYSYMBOL_INVISIBLE = 43, /* INVISIBLE */
+ YYSYMBOL_LJUST = 44, /* LJUST */
+ YYSYMBOL_RJUST = 45, /* RJUST */
+ YYSYMBOL_ABOVE = 46, /* ABOVE */
+ YYSYMBOL_BELOW = 47, /* BELOW */
+ YYSYMBOL_OF = 48, /* OF */
+ YYSYMBOL_THE = 49, /* THE */
+ YYSYMBOL_WAY = 50, /* WAY */
+ YYSYMBOL_BETWEEN = 51, /* BETWEEN */
+ YYSYMBOL_AND = 52, /* AND */
+ YYSYMBOL_HERE = 53, /* HERE */
+ YYSYMBOL_DOT_N = 54, /* DOT_N */
+ YYSYMBOL_DOT_E = 55, /* DOT_E */
+ YYSYMBOL_DOT_W = 56, /* DOT_W */
+ YYSYMBOL_DOT_S = 57, /* DOT_S */
+ YYSYMBOL_DOT_NE = 58, /* DOT_NE */
+ YYSYMBOL_DOT_SE = 59, /* DOT_SE */
+ YYSYMBOL_DOT_NW = 60, /* DOT_NW */
+ YYSYMBOL_DOT_SW = 61, /* DOT_SW */
+ YYSYMBOL_DOT_C = 62, /* DOT_C */
+ YYSYMBOL_DOT_START = 63, /* DOT_START */
+ YYSYMBOL_DOT_END = 64, /* DOT_END */
+ YYSYMBOL_DOT_X = 65, /* DOT_X */
+ YYSYMBOL_DOT_Y = 66, /* DOT_Y */
+ YYSYMBOL_DOT_HT = 67, /* DOT_HT */
+ YYSYMBOL_DOT_WID = 68, /* DOT_WID */
+ YYSYMBOL_DOT_RAD = 69, /* DOT_RAD */
+ YYSYMBOL_SIN = 70, /* SIN */
+ YYSYMBOL_COS = 71, /* COS */
+ YYSYMBOL_ATAN2 = 72, /* ATAN2 */
+ YYSYMBOL_LOG = 73, /* LOG */
+ YYSYMBOL_EXP = 74, /* EXP */
+ YYSYMBOL_SQRT = 75, /* SQRT */
+ YYSYMBOL_K_MAX = 76, /* K_MAX */
+ YYSYMBOL_K_MIN = 77, /* K_MIN */
+ YYSYMBOL_INT = 78, /* INT */
+ YYSYMBOL_RAND = 79, /* RAND */
+ YYSYMBOL_SRAND = 80, /* SRAND */
+ YYSYMBOL_COPY = 81, /* COPY */
+ YYSYMBOL_THRU = 82, /* THRU */
+ YYSYMBOL_TOP = 83, /* TOP */
+ YYSYMBOL_BOTTOM = 84, /* BOTTOM */
+ YYSYMBOL_UPPER = 85, /* UPPER */
+ YYSYMBOL_LOWER = 86, /* LOWER */
+ YYSYMBOL_SH = 87, /* SH */
+ YYSYMBOL_PRINT = 88, /* PRINT */
+ YYSYMBOL_CW = 89, /* CW */
+ YYSYMBOL_CCW = 90, /* CCW */
+ YYSYMBOL_FOR = 91, /* FOR */
+ YYSYMBOL_DO = 92, /* DO */
+ YYSYMBOL_IF = 93, /* IF */
+ YYSYMBOL_ELSE = 94, /* ELSE */
+ YYSYMBOL_ANDAND = 95, /* ANDAND */
+ YYSYMBOL_OROR = 96, /* OROR */
+ YYSYMBOL_NOTEQUAL = 97, /* NOTEQUAL */
+ YYSYMBOL_EQUALEQUAL = 98, /* EQUALEQUAL */
+ YYSYMBOL_LESSEQUAL = 99, /* LESSEQUAL */
+ YYSYMBOL_GREATEREQUAL = 100, /* GREATEREQUAL */
+ YYSYMBOL_LEFT_CORNER = 101, /* LEFT_CORNER */
+ YYSYMBOL_RIGHT_CORNER = 102, /* RIGHT_CORNER */
+ YYSYMBOL_NORTH = 103, /* NORTH */
+ YYSYMBOL_SOUTH = 104, /* SOUTH */
+ YYSYMBOL_EAST = 105, /* EAST */
+ YYSYMBOL_WEST = 106, /* WEST */
+ YYSYMBOL_CENTER = 107, /* CENTER */
+ YYSYMBOL_END = 108, /* END */
+ YYSYMBOL_START = 109, /* START */
+ YYSYMBOL_RESET = 110, /* RESET */
+ YYSYMBOL_UNTIL = 111, /* UNTIL */
+ YYSYMBOL_PLOT = 112, /* PLOT */
+ YYSYMBOL_THICKNESS = 113, /* THICKNESS */
+ YYSYMBOL_FILL = 114, /* FILL */
+ YYSYMBOL_COLORED = 115, /* COLORED */
+ YYSYMBOL_OUTLINED = 116, /* OUTLINED */
+ YYSYMBOL_SHADED = 117, /* SHADED */
+ YYSYMBOL_XSLANTED = 118, /* XSLANTED */
+ YYSYMBOL_YSLANTED = 119, /* YSLANTED */
+ YYSYMBOL_ALIGNED = 120, /* ALIGNED */
+ YYSYMBOL_SPRINTF = 121, /* SPRINTF */
+ YYSYMBOL_COMMAND = 122, /* COMMAND */
+ YYSYMBOL_DEFINE = 123, /* DEFINE */
+ YYSYMBOL_UNDEF = 124, /* UNDEF */
+ YYSYMBOL_125_ = 125, /* '.' */
+ YYSYMBOL_126_ = 126, /* '(' */
+ YYSYMBOL_127_ = 127, /* '`' */
+ YYSYMBOL_128_ = 128, /* '[' */
+ YYSYMBOL_129_ = 129, /* ',' */
+ YYSYMBOL_130_ = 130, /* '<' */
+ YYSYMBOL_131_ = 131, /* '>' */
+ YYSYMBOL_132_ = 132, /* '+' */
+ YYSYMBOL_133_ = 133, /* '-' */
+ YYSYMBOL_134_ = 134, /* '*' */
+ YYSYMBOL_135_ = 135, /* '/' */
+ YYSYMBOL_136_ = 136, /* '%' */
+ YYSYMBOL_137_ = 137, /* '!' */
+ YYSYMBOL_138_ = 138, /* '^' */
+ YYSYMBOL_139_ = 139, /* ';' */
+ YYSYMBOL_140_ = 140, /* '=' */
+ YYSYMBOL_141_ = 141, /* ':' */
+ YYSYMBOL_142_ = 142, /* '{' */
+ YYSYMBOL_143_ = 143, /* '}' */
+ YYSYMBOL_144_ = 144, /* ']' */
+ YYSYMBOL_145_ = 145, /* ')' */
+ YYSYMBOL_YYACCEPT = 146, /* $accept */
+ YYSYMBOL_top = 147, /* top */
+ YYSYMBOL_element_list = 148, /* element_list */
+ YYSYMBOL_middle_element_list = 149, /* middle_element_list */
+ YYSYMBOL_optional_separator = 150, /* optional_separator */
+ YYSYMBOL_separator = 151, /* separator */
+ YYSYMBOL_placeless_element = 152, /* placeless_element */
+ YYSYMBOL_153_1 = 153, /* $@1 */
+ YYSYMBOL_154_2 = 154, /* $@2 */
+ YYSYMBOL_155_3 = 155, /* $@3 */
+ YYSYMBOL_156_4 = 156, /* $@4 */
+ YYSYMBOL_157_5 = 157, /* $@5 */
+ YYSYMBOL_158_6 = 158, /* $@6 */
+ YYSYMBOL_159_7 = 159, /* $@7 */
+ YYSYMBOL_macro_name = 160, /* macro_name */
+ YYSYMBOL_reset_variables = 161, /* reset_variables */
+ YYSYMBOL_print_args = 162, /* print_args */
+ YYSYMBOL_print_arg = 163, /* print_arg */
+ YYSYMBOL_simple_if = 164, /* simple_if */
+ YYSYMBOL_165_8 = 165, /* $@8 */
+ YYSYMBOL_until = 166, /* until */
+ YYSYMBOL_any_expr = 167, /* any_expr */
+ YYSYMBOL_text_expr = 168, /* text_expr */
+ YYSYMBOL_optional_by = 169, /* optional_by */
+ YYSYMBOL_element = 170, /* element */
+ YYSYMBOL_171_9 = 171, /* @9 */
+ YYSYMBOL_172_10 = 172, /* $@10 */
+ YYSYMBOL_optional_element = 173, /* optional_element */
+ YYSYMBOL_object_spec = 174, /* object_spec */
+ YYSYMBOL_175_11 = 175, /* @11 */
+ YYSYMBOL_text = 176, /* text */
+ YYSYMBOL_sprintf_args = 177, /* sprintf_args */
+ YYSYMBOL_position = 178, /* position */
+ YYSYMBOL_position_not_place = 179, /* position_not_place */
+ YYSYMBOL_between = 180, /* between */
+ YYSYMBOL_expr_pair = 181, /* expr_pair */
+ YYSYMBOL_place = 182, /* place */
+ YYSYMBOL_label = 183, /* label */
+ YYSYMBOL_ordinal = 184, /* ordinal */
+ YYSYMBOL_optional_ordinal_last = 185, /* optional_ordinal_last */
+ YYSYMBOL_nth_primitive = 186, /* nth_primitive */
+ YYSYMBOL_object_type = 187, /* object_type */
+ YYSYMBOL_label_path = 188, /* label_path */
+ YYSYMBOL_relative_path = 189, /* relative_path */
+ YYSYMBOL_path = 190, /* path */
+ YYSYMBOL_corner = 191, /* corner */
+ YYSYMBOL_expr = 192, /* expr */
+ YYSYMBOL_expr_lower_than = 193, /* expr_lower_than */
+ YYSYMBOL_expr_not_lower_than = 194 /* expr_not_lower_than */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_int16 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if !defined yyoverflow
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* !defined yyoverflow */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yy_state_t yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 6
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 2438
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 146
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 49
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 260
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 454
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 379
+
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ (0 <= (YYX) && (YYX) <= YYMAXUTOK \
+ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \
+ : YYSYMBOL_YYUNDEF)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_uint8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 137, 2, 2, 2, 136, 2, 2,
+ 126, 145, 134, 132, 129, 133, 125, 135, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 141, 139,
+ 130, 140, 131, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 128, 2, 144, 138, 2, 127, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 142, 2, 143, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
+ 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
+ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
+ 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
+ 115, 116, 117, 118, 119, 120, 121, 122, 123, 124
+};
+
+#if YYDEBUG
+/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_int16 yyrline[] =
+{
+ 0, 275, 275, 276, 285, 290, 292, 296, 298, 302,
+ 303, 307, 315, 320, 332, 334, 336, 338, 340, 345,
+ 350, 357, 356, 372, 380, 382, 379, 393, 395, 392,
+ 405, 404, 413, 422, 421, 435, 436, 441, 442, 446,
+ 451, 456, 464, 466, 485, 492, 494, 505, 504, 516,
+ 517, 522, 524, 529, 535, 541, 543, 545, 547, 549,
+ 551, 553, 560, 564, 569, 577, 591, 597, 605, 612,
+ 618, 611, 627, 637, 638, 643, 645, 647, 649, 654,
+ 661, 668, 675, 682, 687, 694, 702, 701, 728, 734,
+ 740, 746, 752, 771, 778, 785, 792, 799, 806, 813,
+ 820, 827, 834, 849, 861, 867, 876, 883, 908, 912,
+ 918, 924, 930, 936, 941, 947, 953, 959, 966, 975,
+ 982, 998, 1015, 1020, 1025, 1030, 1035, 1040, 1045, 1050,
+ 1058, 1068, 1078, 1088, 1098, 1104, 1112, 1114, 1126, 1131,
+ 1161, 1163, 1169, 1178, 1180, 1185, 1190, 1195, 1200, 1205,
+ 1210, 1216, 1221, 1229, 1230, 1234, 1239, 1245, 1247, 1253,
+ 1259, 1265, 1274, 1284, 1286, 1295, 1297, 1305, 1307, 1312,
+ 1327, 1345, 1347, 1349, 1351, 1353, 1355, 1357, 1359, 1361,
+ 1366, 1368, 1376, 1380, 1382, 1390, 1392, 1398, 1404, 1410,
+ 1416, 1425, 1427, 1429, 1431, 1433, 1435, 1437, 1439, 1441,
+ 1443, 1445, 1447, 1449, 1451, 1453, 1455, 1457, 1459, 1461,
+ 1463, 1465, 1467, 1469, 1471, 1473, 1475, 1477, 1479, 1481,
+ 1483, 1485, 1487, 1492, 1494, 1499, 1504, 1512, 1514, 1521,
+ 1528, 1535, 1542, 1549, 1551, 1553, 1555, 1563, 1571, 1584,
+ 1586, 1588, 1597, 1606, 1619, 1628, 1637, 1646, 1648, 1650,
+ 1652, 1659, 1665, 1670, 1672, 1674, 1676, 1678, 1680, 1682,
+ 1684
+};
+#endif
+
+/** Accessing symbol of state STATE. */
+#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State])
+
+#if YYDEBUG || 0
+/* The user-facing name of the symbol whose (internal) number is
+ YYSYMBOL. No bounds checking. */
+static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
+
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "\"end of file\"", "error", "\"invalid token\"", "LABEL", "VARIABLE",
+ "NUMBER", "TEXT", "COMMAND_LINE", "DELIMITED", "ORDINAL", "TH",
+ "LEFT_ARROW_HEAD", "RIGHT_ARROW_HEAD", "DOUBLE_ARROW_HEAD", "LAST",
+ "BOX", "CIRCLE", "ELLIPSE", "ARC", "LINE", "ARROW", "MOVE", "SPLINE",
+ "HEIGHT", "RADIUS", "FIGNAME", "WIDTH", "DIAMETER", "UP", "DOWN",
+ "RIGHT", "LEFT", "FROM", "TO", "AT", "WITH", "BY", "THEN", "SOLID",
+ "DOTTED", "DASHED", "CHOP", "SAME", "INVISIBLE", "LJUST", "RJUST",
+ "ABOVE", "BELOW", "OF", "THE", "WAY", "BETWEEN", "AND", "HERE", "DOT_N",
+ "DOT_E", "DOT_W", "DOT_S", "DOT_NE", "DOT_SE", "DOT_NW", "DOT_SW",
+ "DOT_C", "DOT_START", "DOT_END", "DOT_X", "DOT_Y", "DOT_HT", "DOT_WID",
+ "DOT_RAD", "SIN", "COS", "ATAN2", "LOG", "EXP", "SQRT", "K_MAX", "K_MIN",
+ "INT", "RAND", "SRAND", "COPY", "THRU", "TOP", "BOTTOM", "UPPER",
+ "LOWER", "SH", "PRINT", "CW", "CCW", "FOR", "DO", "IF", "ELSE", "ANDAND",
+ "OROR", "NOTEQUAL", "EQUALEQUAL", "LESSEQUAL", "GREATEREQUAL",
+ "LEFT_CORNER", "RIGHT_CORNER", "NORTH", "SOUTH", "EAST", "WEST",
+ "CENTER", "END", "START", "RESET", "UNTIL", "PLOT", "THICKNESS", "FILL",
+ "COLORED", "OUTLINED", "SHADED", "XSLANTED", "YSLANTED", "ALIGNED",
+ "SPRINTF", "COMMAND", "DEFINE", "UNDEF", "'.'", "'('", "'`'", "'['",
+ "','", "'<'", "'>'", "'+'", "'-'", "'*'", "'/'", "'%'", "'!'", "'^'",
+ "';'", "'='", "':'", "'{'", "'}'", "']'", "')'", "$accept", "top",
+ "element_list", "middle_element_list", "optional_separator", "separator",
+ "placeless_element", "$@1", "$@2", "$@3", "$@4", "$@5", "$@6", "$@7",
+ "macro_name", "reset_variables", "print_args", "print_arg", "simple_if",
+ "$@8", "until", "any_expr", "text_expr", "optional_by", "element", "@9",
+ "$@10", "optional_element", "object_spec", "@11", "text", "sprintf_args",
+ "position", "position_not_place", "between", "expr_pair", "place",
+ "label", "ordinal", "optional_ordinal_last", "nth_primitive",
+ "object_type", "label_path", "relative_path", "path", "corner", "expr",
+ "expr_lower_than", "expr_not_lower_than", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#define YYPACT_NINF (-240)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-206)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int16 yypact[] =
+{
+ -114, -240, 20, -240, 757, -107, -240, -98, -123, -240,
+ -240, -240, -240, -240, -240, -240, -240, -240, -240, -106,
+ -240, -240, -240, -240, 9, -240, 1087, 46, 1172, 49,
+ 1597, -70, 1087, -240, -240, -114, -240, 3, -33, -240,
+ 877, -240, -240, -114, 1172, -60, 36, -14, -240, 74,
+ -240, -240, -240, -240, -240, -240, -240, -240, -240, -240,
+ -240, -240, -240, -240, -240, -240, -240, -240, -240, -34,
+ -18, 8, 38, 47, 51, 65, 101, 102, 112, 122,
+ -240, -240, 21, 150, -240, -240, -240, -240, -240, -240,
+ -240, -240, -240, 1257, 1172, 1597, 1597, 1087, -240, -240,
+ -43, -240, -240, 357, 2242, 59, 258, -240, 10, 2147,
+ -240, 1, 6, 1172, 1172, 145, -1, 2, 357, 2273,
+ -240, -240, 220, 249, 1087, -114, -114, -240, 721, -240,
+ 252, -240, -240, -240, -240, 1597, 1597, 1597, 1597, 2024,
+ 2024, 1853, 1939, 1682, 1682, 1682, 1427, 1767, -240, -240,
+ 2024, 2024, 2024, -240, -240, -240, -240, -240, -240, -240,
+ -240, 1597, 2024, 23, 23, 23, 1597, 1597, -240, -240,
+ 2282, 593, -240, 1172, -240, -240, -240, -240, 250, -240,
+ 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 458,
+ 1172, -240, -240, -240, -240, -240, -240, -240, -240, 121,
+ 107, 123, 256, 2157, 137, 261, 134, 134, -240, 1767,
+ 1767, -240, -240, -240, -240, -240, 276, -240, -240, -240,
+ -240, -240, -240, -240, -240, -240, -240, 138, -240, -240,
+ 24, 156, 235, -240, 1597, 1597, 1597, 1597, 1597, 1597,
+ 1597, 1597, 1597, 1597, 1597, 1597, 1597, 1597, 1597, 1682,
+ 1682, 1597, -240, 134, -240, 1172, 1172, 23, 23, 1172,
+ 1172, -240, -240, 143, 757, 153, -240, -240, 280, 2282,
+ 2282, 2282, 2282, 2282, 2282, 2282, 2282, -43, 2147, -43,
+ -43, 2253, 275, 275, 295, 1002, -43, 2081, -240, -240,
+ 10, 1342, -240, 694, 2282, 2282, 2282, 2282, 2282, -240,
+ -240, -240, 2282, 2282, -98, -123, 16, 28, -240, -43,
+ 56, 302, -240, 291, -240, 155, 160, 172, 161, 164,
+ 167, 184, 185, 181, -240, 186, 188, -240, 1682, 1767,
+ 1767, -240, -240, 1682, 1682, -240, -240, -240, -240, -240,
+ 156, 279, 314, 2291, 440, 440, 413, 413, 2282, 413,
+ 413, -72, -72, 134, 134, 134, 134, -49, 117, 343,
+ 322, -240, 314, 239, 2300, -240, -240, -240, 314, 239,
+ 2300, -119, -240, -240, -240, -240, -240, 2116, 2116, -240,
+ 206, 333, -240, 123, 2131, -240, 228, -240, -240, 1172,
+ -240, -240, -240, 1172, 1172, -240, -240, -240, -110, 195,
+ 197, -47, 128, 292, 1682, 1682, 1597, -240, 1597, -240,
+ 757, -240, -240, 2116, -240, 228, 338, -240, 200, 202,
+ 212, -240, -240, -240, 1682, 1682, -240, -43, -27, 360,
+ 2282, -240, -240, 214, -240, -240, -240, -240, -240, -73,
+ 30, -240, 1512, 268, -240, -240, 216, 1597, 2282, -240,
+ -240, 2282, 354, -240
+};
+
+/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_int16 yydefact[] =
+{
+ 7, 9, 0, 3, 2, 8, 1, 0, 0, 136,
+ 18, 75, 76, 77, 78, 79, 80, 81, 82, 0,
+ 14, 15, 17, 16, 0, 21, 0, 0, 0, 36,
+ 0, 0, 0, 86, 69, 7, 72, 35, 32, 5,
+ 65, 83, 10, 7, 0, 0, 0, 23, 27, 0,
+ 162, 226, 227, 165, 167, 205, 204, 161, 191, 192,
+ 193, 194, 195, 196, 197, 198, 199, 200, 201, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 202, 203, 0, 0, 210, 211, 216, 217, 218, 219,
+ 220, 222, 221, 0, 0, 0, 0, 20, 42, 45,
+ 46, 140, 143, 141, 157, 0, 0, 163, 0, 44,
+ 223, 224, 0, 0, 0, 0, 52, 0, 0, 51,
+ 224, 39, 84, 0, 19, 7, 7, 4, 8, 40,
+ 0, 33, 124, 125, 126, 0, 0, 0, 0, 93,
+ 95, 97, 99, 0, 0, 0, 0, 0, 107, 108,
+ 109, 111, 120, 122, 123, 130, 131, 132, 133, 127,
+ 128, 0, 113, 0, 0, 0, 0, 0, 135, 129,
+ 92, 0, 12, 0, 38, 37, 11, 24, 0, 22,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 208, 206, 212, 214, 209, 207, 213, 215, 0,
+ 0, 143, 141, 51, 224, 0, 239, 260, 43, 0,
+ 0, 228, 229, 230, 231, 232, 0, 158, 179, 168,
+ 171, 172, 173, 174, 175, 176, 177, 0, 169, 170,
+ 0, 159, 0, 153, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 61, 260, 47, 0, 0, 0, 0, 0,
+ 0, 85, 138, 0, 0, 0, 6, 41, 0, 88,
+ 89, 90, 91, 94, 96, 98, 100, 101, 0, 102,
+ 103, 162, 165, 167, 0, 0, 105, 183, 185, 104,
+ 182, 0, 106, 0, 110, 112, 121, 134, 114, 118,
+ 119, 117, 115, 116, 162, 226, 205, 204, 66, 0,
+ 67, 68, 13, 0, 28, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 251, 0, 0, 240, 0, 0,
+ 0, 156, 142, 0, 0, 166, 144, 146, 164, 178,
+ 160, 0, 258, 259, 257, 256, 253, 255, 155, 225,
+ 254, 233, 234, 235, 236, 237, 238, 0, 0, 0,
+ 0, 55, 56, 58, 59, 54, 53, 57, 258, 60,
+ 259, 0, 87, 70, 34, 190, 182, 0, 0, 180,
+ 0, 0, 184, 0, 51, 25, 49, 241, 242, 0,
+ 244, 245, 246, 0, 0, 249, 250, 252, 0, 144,
+ 146, 0, 0, 0, 0, 0, 0, 48, 0, 137,
+ 73, 189, 188, 0, 181, 49, 0, 29, 0, 0,
+ 0, 148, 145, 147, 0, 0, 154, 149, 0, 62,
+ 139, 74, 71, 0, 26, 50, 243, 247, 248, 149,
+ 0, 151, 0, 0, 186, 150, 151, 0, 63, 30,
+ 152, 64, 0, 31
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const yytype_int16 yypgoto[] =
+{
+ -240, -240, 17, -240, 12, 329, -240, -240, -240, -240,
+ -240, -240, -240, -240, -240, -240, 334, -76, -240, -240,
+ -42, 13, -103, -240, -127, -240, -240, -240, -240, -240,
+ 5, -240, 99, 194, 169, -44, 4, -100, -240, -240,
+ -240, -104, -240, -239, -240, -50, -26, -240, 61
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int16 yydefgoto[] =
+{
+ 0, 2, 3, 35, 264, 5, 36, 49, 313, 415,
+ 178, 386, 452, 268, 176, 37, 97, 98, 38, 360,
+ 417, 199, 116, 443, 39, 126, 410, 432, 40, 125,
+ 117, 371, 100, 101, 249, 102, 118, 104, 105, 106,
+ 107, 228, 287, 288, 289, 108, 119, 110, 120
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_int16 yytable[] =
+{
+ 109, 266, 229, 404, 122, 424, 109, 129, 231, 41,
+ 408, 252, 4, 50, 170, 47, -17, 44, 45, 53,
+ 6, 208, 209, 210, 54, 1, 409, 50, -16, 9,
+ 103, 99, 42, 53, 46, 421, 103, 99, 54, 174,
+ 175, 115, 375, 43, 308, 169, 380, 127, 208, 201,
+ 112, 191, 192, 121, 217, 171, 123, 172, 230, 209,
+ 210, 131, 245, 246, 247, 218, 248, 203, 177, 206,
+ 207, 109, 445, 219, 220, 221, 222, 223, 224, 225,
+ 173, 226, 179, 209, 210, 209, 210, 111, 253, 209,
+ 210, 48, 180, 111, 255, 256, 290, 202, 109, 257,
+ 258, 103, 99, 292, 441, 209, 210, 205, 181, 269,
+ 270, 271, 272, 273, 274, 275, 276, 278, 278, 278,
+ 278, 293, 193, 194, 294, 295, 296, 261, 103, 99,
+ 340, 250, 130, 41, 182, 297, 298, 94, 411, 412,
+ 302, 303, 263, 265, 31, 278, 251, 103, 103, 103,
+ 103, 94, 361, 363, 204, -17, 367, 369, 111, -17,
+ -17, 446, 209, 210, 183, 336, 337, -16, 299, 300,
+ 301, -16, -16, 184, 433, 311, 41, 185, 377, 378,
+ 195, 196, 254, 293, 293, 111, 312, 227, -140, -140,
+ 231, 186, 200, 315, 316, 317, 318, 319, 320, 321,
+ 322, 323, 325, 326, 111, 111, 111, 111, 342, 343,
+ 344, 345, 346, 347, 348, 349, 350, 351, 352, 353,
+ 354, 355, 356, 278, 278, 359, 9, 187, 188, 362,
+ 364, 376, 111, 368, 370, 290, 328, 382, 189, 329,
+ 330, 201, 277, 279, 280, 286, 405, 383, 190, 209,
+ 210, 197, 198, 103, 103, 262, 267, 425, 314, 203,
+ 209, 210, 365, 366, 218, 384, 327, 334, 331, 41,
+ 309, 335, 248, 220, 221, 222, 223, 224, 225, 338,
+ 226, 216, 339, 431, 341, 399, 400, 372, 374, 202,
+ 220, 221, 222, 223, 224, 225, 373, 226, 379, 385,
+ 387, 389, 278, 293, 293, 388, 390, 278, 278, 391,
+ 111, 111, 392, 393, 394, 234, 235, 236, 237, 238,
+ 239, 211, 212, 213, 214, 215, 395, 376, 376, 403,
+ 407, 396, 103, 397, 255, 413, 414, 103, 103, 416,
+ 422, 31, 423, 426, 435, 436, 204, 437, 357, 358,
+ 241, 242, 243, 244, 245, 246, 247, 438, 248, 444,
+ 449, 450, 453, 376, 128, 310, 124, 211, 212, 213,
+ 214, 215, 333, 434, 0, 0, 406, 0, 278, 278,
+ 429, 0, 430, 0, 200, 0, 227, 0, 0, 111,
+ 0, 0, 0, 0, 111, 111, 442, 0, 278, 278,
+ 0, 332, 418, 227, 0, 0, 419, 420, 103, 103,
+ 0, 236, 237, 238, 239, 41, 448, 0, 0, 0,
+ 0, 451, 211, 212, 213, 214, 215, 398, 103, 103,
+ 0, 0, 401, 402, -141, -141, 0, 0, 234, 235,
+ 236, 237, 238, 239, 241, 242, 243, 244, 245, 246,
+ 247, 0, 248, 0, 0, 234, 235, 236, 237, 238,
+ 239, 50, 51, 52, 9, 111, 111, 53, 0, 0,
+ 0, 0, 54, 241, 242, 243, 244, 245, 246, 247,
+ 0, 248, 0, 0, 0, 111, 111, 0, 55, 56,
+ 241, 242, 243, 244, 245, 246, 247, 0, 248, 0,
+ 0, 0, 0, 427, 428, 0, 0, 0, 0, 0,
+ 0, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 439, 440, 0, 0, 0, 69, 70,
+ 71, 72, 73, 74, 75, 76, 77, 78, 79, 238,
+ 239, 80, 81, 82, 83, 243, 244, 245, 246, 247,
+ 0, 248, 0, 0, 0, 0, 0, 0, 0, 84,
+ 85, 86, 87, 88, 89, 90, 91, 92, 0, 0,
+ 241, 242, 243, 244, 245, 246, 247, 0, 248, 31,
+ 0, 0, 0, 0, 113, 94, 0, 0, 0, 0,
+ 0, 95, 0, 0, 0, 114, 304, 305, 52, 9,
+ 10, 0, 53, 324, 0, 0, 0, 54, 11, 12,
+ 13, 14, 15, 16, 17, 18, 0, 0, 19, 0,
+ 0, 20, 21, 306, 307, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 57, 58, 59, 60,
+ 61, 62, 63, 64, 65, 66, 67, 68, 0, 0,
+ 0, 0, 0, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 24, 0, 80, 81, 82, 83,
+ 25, 26, 0, 0, 27, 0, 28, 0, 0, 0,
+ 0, 0, 0, 0, 84, 85, 86, 87, 88, 89,
+ 90, 91, 92, 29, 0, 30, 0, 0, 0, 0,
+ 0, 0, 0, 0, 31, 32, 0, 0, 0, 93,
+ 94, 33, 0, 0, 7, 8, 95, 9, 10, 0,
+ 96, 0, 0, 0, 0, 34, 11, 12, 13, 14,
+ 15, 16, 17, 18, 0, 0, 19, 0, 0, 20,
+ 21, 22, 23, 0, 0, 0, 0, 0, 0, 0,
+ 7, 8, 0, 9, 10, 0, 0, 0, 0, 0,
+ 0, 0, 11, 12, 13, 14, 15, 16, 17, 18,
+ 0, 0, 19, 0, 0, 20, 21, 22, 23, 234,
+ 235, 236, 237, 238, 239, 0, 0, 0, 0, 0,
+ 0, 0, 24, 0, 0, 0, 0, 0, 25, 26,
+ 0, 0, 27, 0, 28, 0, 0, 0, 0, 0,
+ 0, 0, 0, 240, 241, 242, 243, 244, 245, 246,
+ 247, 29, 248, 30, 0, 0, 0, 0, 24, 0,
+ 0, 0, 31, 32, 25, 26, 0, 0, 27, 33,
+ 28, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 42, 0, 0, 34, 0, 0, 0, 29, 0, 30,
+ 0, 0, 0, 0, 0, 0, 0, 0, 31, 32,
+ 50, 51, 52, 9, 0, 33, 53, 0, 132, 133,
+ 134, 54, 0, 0, 0, 0, 0, 0, 0, 34,
+ 135, 136, 0, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
+ 154, 155, 156, 157, 158, 0, 0, 0, 0, 0,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
+ 67, 68, 0, 0, 0, 0, 0, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 0, 0,
+ 80, 81, 82, 83, 0, 0, 159, 160, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 84, 85,
+ 86, 87, 88, 89, 90, 91, 92, 0, 0, 0,
+ 161, 162, 163, 164, 165, 166, 167, 168, 31, 0,
+ 0, 0, 0, 113, 94, 50, 51, 52, 9, 0,
+ 95, 53, 0, 0, 96, 0, 54, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 55, 56, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 66, 67, 68, 0, 0, 0,
+ 0, 0, 69, 70, 71, 72, 73, 74, 75, 76,
+ 77, 78, 79, 0, 0, 80, 81, 82, 83, 0,
+ 50, 51, 52, 9, 0, 0, 53, 0, 0, 0,
+ 0, 54, 0, 84, 85, 86, 87, 88, 89, 90,
+ 91, 92, 0, 0, 0, 0, 0, 55, 56, 0,
+ 0, 0, 0, 31, 0, 0, 0, 284, 93, 94,
+ 0, 0, 0, 0, 0, 95, 0, 0, 0, 114,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
+ 67, 68, 0, 0, 0, 0, 0, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 0, 0,
+ 80, 81, 82, 83, 0, 50, 51, 52, 9, 0,
+ 0, 53, 0, 0, 0, 0, 54, 0, 84, 85,
+ 86, 87, 88, 89, 90, 91, 92, 0, 0, 0,
+ 0, 0, 55, 56, 0, 0, 0, 0, 31, 0,
+ 0, 0, 0, 93, 94, 0, 0, 0, 0, 0,
+ 95, 0, 0, 0, 96, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 66, 67, 68, 0, 0, 0,
+ 0, 0, 69, 70, 71, 72, 73, 74, 75, 76,
+ 77, 78, 79, 0, 0, 80, 81, 82, 83, 0,
+ 50, 51, 52, 9, 0, 0, 53, 0, 0, 0,
+ 0, 54, 0, 84, 85, 86, 87, 88, 89, 90,
+ 91, 92, 0, 0, 0, 0, 0, 55, 56, 0,
+ 0, 0, 0, 31, 0, 0, 0, 0, 113, 94,
+ 0, 0, 0, 0, 0, 95, 0, 0, 0, 114,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
+ 67, 68, 0, 0, 0, 0, 0, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 0, 0,
+ 80, 81, 82, 83, 0, 50, 51, 52, 9, 0,
+ 0, 53, 0, 0, 0, 0, 54, 0, 84, 85,
+ 86, 87, 88, 89, 90, 91, 92, 0, 0, 0,
+ 0, 0, 55, 56, 0, 0, 0, 0, 31, 0,
+ 0, 0, 0, 93, 94, 0, 0, 0, 0, 0,
+ 95, 0, 0, 0, 114, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 66, 67, 68, 0, 0, 0,
+ 0, 0, 69, 70, 71, 72, 73, 74, 75, 76,
+ 77, 78, 79, 0, 0, 80, 81, 82, 83, 0,
+ 281, 51, 52, 0, 0, 0, 282, 0, 0, 0,
+ 0, 283, 0, 84, 85, 86, 87, 88, 89, 90,
+ 91, 92, 0, 0, 0, 0, 0, 55, 56, 0,
+ 0, 0, 0, 31, 0, 0, 0, 0, 291, 94,
+ 0, 0, 0, 0, 0, 95, 0, 0, 0, 114,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
+ 67, 68, 0, 0, 0, 0, 0, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 0, 0,
+ 80, 81, 82, 83, 0, 50, 51, 52, 0, 0,
+ 0, 53, 0, 0, 0, 0, 54, 0, 84, 85,
+ 86, 87, 88, 89, 90, 91, 92, 0, 0, 0,
+ 0, 0, 55, 56, 0, 0, 0, 0, 0, 0,
+ 0, 0, 284, 285, 94, 0, 0, 0, 0, 0,
+ 95, 0, 0, 0, 96, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 66, 67, 68, 0, 0, 0,
+ 0, 0, 69, 70, 71, 72, 73, 74, 75, 76,
+ 77, 78, 79, 0, 0, 80, 81, 82, 83, 0,
+ 50, 51, 52, 0, 0, 0, 53, 0, 0, 0,
+ 0, 54, 0, 84, 85, 86, 87, 88, 89, 90,
+ 91, 92, 0, 0, 0, 0, 0, 55, 56, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 113, 94,
+ 0, 0, 0, 0, 0, 95, 447, 0, 0, 96,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
+ 67, 68, 0, 0, 0, 0, 0, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 0, 0,
+ 80, 81, 82, 83, 0, 50, 51, 52, 0, 0,
+ 0, 53, 0, 0, 0, 0, 54, 0, 84, 85,
+ 86, 87, 88, 89, 90, 91, 92, 0, 0, 0,
+ 0, 0, 55, 56, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 113, 94, 0, 0, 0, 0, 0,
+ 95, 0, 0, 0, 96, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 66, 67, 68, 0, 0, 0,
+ 0, 0, 69, 70, 71, 72, 73, 74, 75, 76,
+ 77, 78, 79, 0, 0, 80, 81, 82, 83, 0,
+ 50, 51, 52, 0, 0, 0, 53, 0, 0, 0,
+ 0, 54, 0, 84, 85, 86, 87, 88, 89, 90,
+ 91, 92, 0, 0, 0, 0, 0, 55, 56, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 93, 94,
+ 0, 0, 0, 0, 0, 95, 0, 0, 0, 96,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
+ 67, 68, 0, 0, 0, 0, 0, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 0, 0,
+ 80, 81, 82, 83, 0, 0, 50, 51, 52, 0,
+ 0, 0, 53, 0, 0, 0, 0, 54, 84, 85,
+ 86, 87, 88, 89, 90, 91, 92, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 291, 94, 0, 0, 0, 0, 0,
+ 95, -205, 0, 0, 96, 0, 57, 58, 59, 60,
+ 61, 62, 63, 64, 65, 66, 67, 68, 0, 0,
+ 0, 0, 0, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 0, 0, 80, 81, 82, 83,
+ 0, 0, 50, 51, 52, 0, 0, 0, 53, 0,
+ 0, 0, 0, 54, 84, 85, 86, 87, 88, 89,
+ 90, 91, 92, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 113,
+ 94, 0, 0, 0, 0, 0, 95, -204, 0, 0,
+ 96, 0, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 0, 0, 0, 0, 0, 69,
+ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 0, 0, 80, 81, 82, 83, 0, 50, 51, 52,
+ 0, 0, 0, 53, 0, 0, 0, 0, 54, 0,
+ 84, 85, 86, 87, 88, 89, 90, 91, 92, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 113, 94, 0, 0, 0,
+ 0, 0, 95, 0, 0, 0, 96, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 0,
+ 0, 0, 0, 0, 69, 70, 71, 72, 73, 74,
+ 75, 76, 77, 78, 79, 0, 0, 80, 81, 82,
+ 83, 55, 56, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 84, 85, 86, 87, 88,
+ 89, 90, 91, 92, 0, 58, 59, 60, 61, 62,
+ 63, 64, 65, 66, 67, 68, 55, 56, 0, 0,
+ 113, 94, 0, 0, 0, 0, 0, 95, 0, 0,
+ 0, 96, 0, 0, 80, 81, 82, 83, 0, 0,
+ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
+ 68, 0, 84, 85, 86, 87, 88, 89, 90, 91,
+ 92, 0, 0, 0, 0, 232, 0, 0, 233, 80,
+ 81, 82, 83, 0, 0, 232, 381, 0, 233, 0,
+ 0, 0, 0, 0, 0, 0, 0, 84, 85, 86,
+ 87, 88, 89, 90, 91, 92, 259, 260, 236, 237,
+ 238, 239, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 284, 234, 235, 236, 237, 238, 239, 0, 0,
+ 0, 0, 259, 260, 236, 237, 238, 239, 0, 0,
+ 240, 241, 242, 243, 244, 245, 246, 247, 0, 248,
+ 0, 0, 0, 0, 0, 0, 240, 241, 242, 243,
+ 244, 245, 246, 247, 0, 248, 240, 241, 242, 243,
+ 244, 245, 246, 247, 0, 248, 58, 59, 60, 61,
+ 62, 63, 64, 65, 66, 67, 68, 58, 59, 60,
+ 61, 62, 63, 64, 65, 66, 67, 68, 0, 0,
+ 0, 0, 0, 0, 0, 80, 81, 82, 83, 0,
+ 0, 0, 0, 0, 0, 0, 80, 81, 82, 83,
+ 0, 0, 0, 84, 85, 86, 87, 88, 89, 90,
+ 91, 92, 0, 0, 84, 85, 86, 87, 88, 89,
+ 90, 91, 92, 0, 0, 0, 0, 216, 259, 260,
+ 236, 237, 238, 239, 0, 0, 0, 234, 235, 236,
+ 237, 238, 239, 0, 0, 0, 234, 0, 236, 237,
+ 238, 239, 0, 0, 0, 259, 0, 236, 237, 238,
+ 239, 0, 0, 241, 242, 243, 244, 245, 246, 247,
+ 0, 248, 241, 242, 243, 244, 245, 246, 247, 0,
+ 248, 241, 242, 243, 244, 245, 246, 247, 0, 248,
+ 241, 242, 243, 244, 245, 246, 247, 0, 248
+};
+
+static const yytype_int16 yycheck[] =
+{
+ 26, 128, 106, 52, 30, 52, 32, 4, 108, 4,
+ 129, 114, 0, 3, 40, 6, 0, 140, 141, 9,
+ 0, 97, 132, 133, 14, 139, 145, 3, 0, 6,
+ 26, 26, 139, 9, 140, 145, 32, 32, 14, 3,
+ 4, 28, 281, 141, 171, 40, 285, 35, 124, 93,
+ 4, 30, 31, 4, 104, 43, 126, 44, 48, 132,
+ 133, 94, 134, 135, 136, 6, 138, 93, 82, 95,
+ 96, 97, 145, 14, 15, 16, 17, 18, 19, 20,
+ 140, 22, 8, 132, 133, 132, 133, 26, 114, 132,
+ 133, 82, 126, 32, 95, 96, 146, 93, 124, 97,
+ 98, 97, 97, 147, 131, 132, 133, 94, 126, 135,
+ 136, 137, 138, 139, 140, 141, 142, 143, 144, 145,
+ 146, 147, 101, 102, 150, 151, 152, 122, 124, 124,
+ 230, 130, 129, 128, 126, 161, 162, 127, 377, 378,
+ 166, 167, 125, 126, 121, 171, 140, 143, 144, 145,
+ 146, 127, 255, 256, 93, 139, 259, 260, 97, 143,
+ 144, 131, 132, 133, 126, 209, 210, 139, 163, 164,
+ 165, 143, 144, 126, 413, 171, 171, 126, 282, 283,
+ 30, 31, 37, 209, 210, 124, 173, 128, 132, 133,
+ 290, 126, 93, 180, 181, 182, 183, 184, 185, 186,
+ 187, 188, 189, 190, 143, 144, 145, 146, 234, 235,
+ 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
+ 246, 247, 248, 249, 250, 251, 6, 126, 126, 255,
+ 256, 281, 171, 259, 260, 285, 129, 287, 126, 132,
+ 133, 285, 143, 144, 145, 146, 129, 291, 126, 132,
+ 133, 101, 102, 249, 250, 6, 4, 129, 8, 285,
+ 132, 133, 257, 258, 6, 291, 145, 130, 145, 264,
+ 171, 10, 138, 15, 16, 17, 18, 19, 20, 3,
+ 22, 125, 144, 410, 49, 329, 330, 144, 8, 285,
+ 15, 16, 17, 18, 19, 20, 143, 22, 3, 8,
+ 145, 129, 328, 329, 330, 145, 145, 333, 334, 145,
+ 249, 250, 145, 129, 129, 95, 96, 97, 98, 99,
+ 100, 65, 66, 67, 68, 69, 145, 377, 378, 50,
+ 8, 145, 328, 145, 95, 129, 3, 333, 334, 111,
+ 145, 121, 145, 51, 6, 145, 285, 145, 249, 250,
+ 130, 131, 132, 133, 134, 135, 136, 145, 138, 145,
+ 92, 145, 8, 413, 35, 171, 32, 65, 66, 67,
+ 68, 69, 203, 415, -1, -1, 33, -1, 404, 405,
+ 406, -1, 408, -1, 285, -1, 128, -1, -1, 328,
+ -1, -1, -1, -1, 333, 334, 36, -1, 424, 425,
+ -1, 145, 389, 128, -1, -1, 393, 394, 404, 405,
+ -1, 97, 98, 99, 100, 410, 442, -1, -1, -1,
+ -1, 447, 65, 66, 67, 68, 69, 328, 424, 425,
+ -1, -1, 333, 334, 132, 133, -1, -1, 95, 96,
+ 97, 98, 99, 100, 130, 131, 132, 133, 134, 135,
+ 136, -1, 138, -1, -1, 95, 96, 97, 98, 99,
+ 100, 3, 4, 5, 6, 404, 405, 9, -1, -1,
+ -1, -1, 14, 130, 131, 132, 133, 134, 135, 136,
+ -1, 138, -1, -1, -1, 424, 425, -1, 30, 31,
+ 130, 131, 132, 133, 134, 135, 136, -1, 138, -1,
+ -1, -1, -1, 404, 405, -1, -1, -1, -1, -1,
+ -1, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+ 62, 63, 64, 424, 425, -1, -1, -1, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 80, 99,
+ 100, 83, 84, 85, 86, 132, 133, 134, 135, 136,
+ -1, 138, -1, -1, -1, -1, -1, -1, -1, 101,
+ 102, 103, 104, 105, 106, 107, 108, 109, -1, -1,
+ 130, 131, 132, 133, 134, 135, 136, -1, 138, 121,
+ -1, -1, -1, -1, 126, 127, -1, -1, -1, -1,
+ -1, 133, -1, -1, -1, 137, 3, 4, 5, 6,
+ 7, -1, 9, 145, -1, -1, -1, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, -1, -1, 25, -1,
+ -1, 28, 29, 30, 31, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, -1, -1,
+ -1, -1, -1, 70, 71, 72, 73, 74, 75, 76,
+ 77, 78, 79, 80, 81, -1, 83, 84, 85, 86,
+ 87, 88, -1, -1, 91, -1, 93, -1, -1, -1,
+ -1, -1, -1, -1, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, -1, 112, -1, -1, -1, -1,
+ -1, -1, -1, -1, 121, 122, -1, -1, -1, 126,
+ 127, 128, -1, -1, 3, 4, 133, 6, 7, -1,
+ 137, -1, -1, -1, -1, 142, 15, 16, 17, 18,
+ 19, 20, 21, 22, -1, -1, 25, -1, -1, 28,
+ 29, 30, 31, -1, -1, -1, -1, -1, -1, -1,
+ 3, 4, -1, 6, 7, -1, -1, -1, -1, -1,
+ -1, -1, 15, 16, 17, 18, 19, 20, 21, 22,
+ -1, -1, 25, -1, -1, 28, 29, 30, 31, 95,
+ 96, 97, 98, 99, 100, -1, -1, -1, -1, -1,
+ -1, -1, 81, -1, -1, -1, -1, -1, 87, 88,
+ -1, -1, 91, -1, 93, -1, -1, -1, -1, -1,
+ -1, -1, -1, 129, 130, 131, 132, 133, 134, 135,
+ 136, 110, 138, 112, -1, -1, -1, -1, 81, -1,
+ -1, -1, 121, 122, 87, 88, -1, -1, 91, 128,
+ 93, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 139, -1, -1, 142, -1, -1, -1, 110, -1, 112,
+ -1, -1, -1, -1, -1, -1, -1, -1, 121, 122,
+ 3, 4, 5, 6, -1, 128, 9, -1, 11, 12,
+ 13, 14, -1, -1, -1, -1, -1, -1, -1, 142,
+ 23, 24, -1, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, -1, -1, -1, -1, -1,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, -1, -1, -1, -1, -1, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80, -1, -1,
+ 83, 84, 85, 86, -1, -1, 89, 90, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, -1, -1, -1,
+ 113, 114, 115, 116, 117, 118, 119, 120, 121, -1,
+ -1, -1, -1, 126, 127, 3, 4, 5, 6, -1,
+ 133, 9, -1, -1, 137, -1, 14, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 30, 31, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, -1, -1, -1,
+ -1, -1, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, -1, -1, 83, 84, 85, 86, -1,
+ 3, 4, 5, 6, -1, -1, 9, -1, -1, -1,
+ -1, 14, -1, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, -1, -1, -1, -1, -1, 30, 31, -1,
+ -1, -1, -1, 121, -1, -1, -1, 125, 126, 127,
+ -1, -1, -1, -1, -1, 133, -1, -1, -1, 137,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, -1, -1, -1, -1, -1, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80, -1, -1,
+ 83, 84, 85, 86, -1, 3, 4, 5, 6, -1,
+ -1, 9, -1, -1, -1, -1, 14, -1, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, -1, -1, -1,
+ -1, -1, 30, 31, -1, -1, -1, -1, 121, -1,
+ -1, -1, -1, 126, 127, -1, -1, -1, -1, -1,
+ 133, -1, -1, -1, 137, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, -1, -1, -1,
+ -1, -1, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, -1, -1, 83, 84, 85, 86, -1,
+ 3, 4, 5, 6, -1, -1, 9, -1, -1, -1,
+ -1, 14, -1, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, -1, -1, -1, -1, -1, 30, 31, -1,
+ -1, -1, -1, 121, -1, -1, -1, -1, 126, 127,
+ -1, -1, -1, -1, -1, 133, -1, -1, -1, 137,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, -1, -1, -1, -1, -1, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80, -1, -1,
+ 83, 84, 85, 86, -1, 3, 4, 5, 6, -1,
+ -1, 9, -1, -1, -1, -1, 14, -1, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, -1, -1, -1,
+ -1, -1, 30, 31, -1, -1, -1, -1, 121, -1,
+ -1, -1, -1, 126, 127, -1, -1, -1, -1, -1,
+ 133, -1, -1, -1, 137, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, -1, -1, -1,
+ -1, -1, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, -1, -1, 83, 84, 85, 86, -1,
+ 3, 4, 5, -1, -1, -1, 9, -1, -1, -1,
+ -1, 14, -1, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, -1, -1, -1, -1, -1, 30, 31, -1,
+ -1, -1, -1, 121, -1, -1, -1, -1, 126, 127,
+ -1, -1, -1, -1, -1, 133, -1, -1, -1, 137,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, -1, -1, -1, -1, -1, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80, -1, -1,
+ 83, 84, 85, 86, -1, 3, 4, 5, -1, -1,
+ -1, 9, -1, -1, -1, -1, 14, -1, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, -1, -1, -1,
+ -1, -1, 30, 31, -1, -1, -1, -1, -1, -1,
+ -1, -1, 125, 126, 127, -1, -1, -1, -1, -1,
+ 133, -1, -1, -1, 137, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, -1, -1, -1,
+ -1, -1, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, -1, -1, 83, 84, 85, 86, -1,
+ 3, 4, 5, -1, -1, -1, 9, -1, -1, -1,
+ -1, 14, -1, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, -1, -1, -1, -1, -1, 30, 31, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, 126, 127,
+ -1, -1, -1, -1, -1, 133, 134, -1, -1, 137,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, -1, -1, -1, -1, -1, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80, -1, -1,
+ 83, 84, 85, 86, -1, 3, 4, 5, -1, -1,
+ -1, 9, -1, -1, -1, -1, 14, -1, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, -1, -1, -1,
+ -1, -1, 30, 31, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 126, 127, -1, -1, -1, -1, -1,
+ 133, -1, -1, -1, 137, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, -1, -1, -1,
+ -1, -1, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, -1, -1, 83, 84, 85, 86, -1,
+ 3, 4, 5, -1, -1, -1, 9, -1, -1, -1,
+ -1, 14, -1, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, -1, -1, -1, -1, -1, 30, 31, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, 126, 127,
+ -1, -1, -1, -1, -1, 133, -1, -1, -1, 137,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, -1, -1, -1, -1, -1, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80, -1, -1,
+ 83, 84, 85, 86, -1, -1, 3, 4, 5, -1,
+ -1, -1, 9, -1, -1, -1, -1, 14, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 126, 127, -1, -1, -1, -1, -1,
+ 133, 48, -1, -1, 137, -1, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, -1, -1,
+ -1, -1, -1, 70, 71, 72, 73, 74, 75, 76,
+ 77, 78, 79, 80, -1, -1, 83, 84, 85, 86,
+ -1, -1, 3, 4, 5, -1, -1, -1, 9, -1,
+ -1, -1, -1, 14, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, 126,
+ 127, -1, -1, -1, -1, -1, 133, 48, -1, -1,
+ 137, -1, 53, 54, 55, 56, 57, 58, 59, 60,
+ 61, 62, 63, 64, -1, -1, -1, -1, -1, 70,
+ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ -1, -1, 83, 84, 85, 86, -1, 3, 4, 5,
+ -1, -1, -1, 9, -1, -1, -1, -1, 14, -1,
+ 101, 102, 103, 104, 105, 106, 107, 108, 109, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 126, 127, -1, -1, -1,
+ -1, -1, 133, -1, -1, -1, 137, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63, 64, -1,
+ -1, -1, -1, -1, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, -1, -1, 83, 84, 85,
+ 86, 30, 31, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 101, 102, 103, 104, 105,
+ 106, 107, 108, 109, -1, 54, 55, 56, 57, 58,
+ 59, 60, 61, 62, 63, 64, 30, 31, -1, -1,
+ 126, 127, -1, -1, -1, -1, -1, 133, -1, -1,
+ -1, 137, -1, -1, 83, 84, 85, 86, -1, -1,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, -1, 101, 102, 103, 104, 105, 106, 107, 108,
+ 109, -1, -1, -1, -1, 48, -1, -1, 51, 83,
+ 84, 85, 86, -1, -1, 48, 125, -1, 51, -1,
+ -1, -1, -1, -1, -1, -1, -1, 101, 102, 103,
+ 104, 105, 106, 107, 108, 109, 95, 96, 97, 98,
+ 99, 100, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 125, 95, 96, 97, 98, 99, 100, -1, -1,
+ -1, -1, 95, 96, 97, 98, 99, 100, -1, -1,
+ 129, 130, 131, 132, 133, 134, 135, 136, -1, 138,
+ -1, -1, -1, -1, -1, -1, 129, 130, 131, 132,
+ 133, 134, 135, 136, -1, 138, 129, 130, 131, 132,
+ 133, 134, 135, 136, -1, 138, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, -1, -1,
+ -1, -1, -1, -1, -1, 83, 84, 85, 86, -1,
+ -1, -1, -1, -1, -1, -1, 83, 84, 85, 86,
+ -1, -1, -1, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, -1, -1, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, -1, -1, -1, -1, 125, 95, 96,
+ 97, 98, 99, 100, -1, -1, -1, 95, 96, 97,
+ 98, 99, 100, -1, -1, -1, 95, -1, 97, 98,
+ 99, 100, -1, -1, -1, 95, -1, 97, 98, 99,
+ 100, -1, -1, 130, 131, 132, 133, 134, 135, 136,
+ -1, 138, 130, 131, 132, 133, 134, 135, 136, -1,
+ 138, 130, 131, 132, 133, 134, 135, 136, -1, 138,
+ 130, 131, 132, 133, 134, 135, 136, -1, 138
+};
+
+/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ state STATE-NUM. */
+static const yytype_uint8 yystos[] =
+{
+ 0, 139, 147, 148, 150, 151, 0, 3, 4, 6,
+ 7, 15, 16, 17, 18, 19, 20, 21, 22, 25,
+ 28, 29, 30, 31, 81, 87, 88, 91, 93, 110,
+ 112, 121, 122, 128, 142, 149, 152, 161, 164, 170,
+ 174, 176, 139, 141, 140, 141, 140, 6, 82, 153,
+ 3, 4, 5, 9, 14, 30, 31, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63, 64, 70,
+ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ 83, 84, 85, 86, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 126, 127, 133, 137, 162, 163, 176,
+ 178, 179, 181, 182, 183, 184, 185, 186, 191, 192,
+ 193, 194, 4, 126, 137, 167, 168, 176, 182, 192,
+ 194, 4, 192, 126, 162, 175, 171, 150, 151, 4,
+ 129, 94, 11, 12, 13, 23, 24, 26, 27, 28,
+ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 89,
+ 90, 113, 114, 115, 116, 117, 118, 119, 120, 176,
+ 192, 150, 167, 140, 3, 4, 160, 82, 156, 8,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 30, 31, 101, 102, 30, 31, 101, 102, 167,
+ 178, 181, 182, 192, 194, 167, 192, 192, 163, 132,
+ 133, 65, 66, 67, 68, 69, 125, 191, 6, 14,
+ 15, 16, 17, 18, 19, 20, 22, 128, 187, 187,
+ 48, 183, 48, 51, 95, 96, 97, 98, 99, 100,
+ 129, 130, 131, 132, 133, 134, 135, 136, 138, 180,
+ 130, 140, 168, 192, 37, 95, 96, 97, 98, 95,
+ 96, 176, 6, 148, 150, 148, 170, 4, 159, 192,
+ 192, 192, 192, 192, 192, 192, 192, 178, 192, 178,
+ 178, 3, 9, 14, 125, 126, 178, 188, 189, 190,
+ 191, 126, 181, 192, 192, 192, 192, 192, 192, 176,
+ 176, 176, 192, 192, 3, 4, 30, 31, 170, 178,
+ 179, 182, 167, 154, 8, 167, 167, 167, 167, 167,
+ 167, 167, 167, 167, 145, 167, 167, 145, 129, 132,
+ 133, 145, 145, 180, 130, 10, 181, 181, 3, 144,
+ 183, 49, 192, 192, 192, 192, 192, 192, 192, 192,
+ 192, 192, 192, 192, 192, 192, 192, 178, 178, 192,
+ 165, 168, 192, 168, 192, 176, 176, 168, 192, 168,
+ 192, 177, 144, 143, 8, 189, 191, 187, 187, 3,
+ 189, 125, 191, 181, 192, 8, 157, 145, 145, 129,
+ 145, 145, 145, 129, 129, 145, 145, 145, 178, 181,
+ 181, 178, 178, 50, 52, 129, 33, 8, 129, 145,
+ 172, 189, 189, 129, 3, 155, 111, 166, 167, 167,
+ 167, 145, 145, 145, 52, 129, 51, 178, 178, 192,
+ 192, 170, 173, 189, 166, 6, 145, 145, 145, 178,
+ 178, 131, 36, 169, 145, 145, 131, 134, 192, 92,
+ 145, 192, 158, 8
+};
+
+/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */
+static const yytype_uint8 yyr1[] =
+{
+ 0, 146, 147, 147, 148, 149, 149, 150, 150, 151,
+ 151, 152, 152, 152, 152, 152, 152, 152, 152, 152,
+ 152, 153, 152, 152, 154, 155, 152, 156, 157, 152,
+ 158, 152, 152, 159, 152, 152, 152, 160, 160, 161,
+ 161, 161, 162, 162, 163, 163, 163, 165, 164, 166,
+ 166, 167, 167, 168, 168, 168, 168, 168, 168, 168,
+ 168, 168, 169, 169, 169, 170, 170, 170, 170, 171,
+ 172, 170, 170, 173, 173, 174, 174, 174, 174, 174,
+ 174, 174, 174, 174, 174, 174, 175, 174, 174, 174,
+ 174, 174, 174, 174, 174, 174, 174, 174, 174, 174,
+ 174, 174, 174, 174, 174, 174, 174, 174, 174, 174,
+ 174, 174, 174, 174, 174, 174, 174, 174, 174, 174,
+ 174, 174, 174, 174, 174, 174, 174, 174, 174, 174,
+ 174, 174, 174, 174, 174, 174, 176, 176, 177, 177,
+ 178, 178, 178, 179, 179, 179, 179, 179, 179, 179,
+ 179, 179, 179, 180, 180, 181, 181, 182, 182, 182,
+ 182, 182, 183, 183, 183, 184, 184, 185, 185, 186,
+ 186, 187, 187, 187, 187, 187, 187, 187, 187, 187,
+ 188, 188, 189, 189, 189, 190, 190, 190, 190, 190,
+ 190, 191, 191, 191, 191, 191, 191, 191, 191, 191,
+ 191, 191, 191, 191, 191, 191, 191, 191, 191, 191,
+ 191, 191, 191, 191, 191, 191, 191, 191, 191, 191,
+ 191, 191, 191, 192, 192, 193, 194, 194, 194, 194,
+ 194, 194, 194, 194, 194, 194, 194, 194, 194, 194,
+ 194, 194, 194, 194, 194, 194, 194, 194, 194, 194,
+ 194, 194, 194, 194, 194, 194, 194, 194, 194, 194,
+ 194
+};
+
+/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 1, 1, 3, 1, 3, 0, 1, 1,
+ 2, 3, 3, 4, 1, 1, 1, 1, 1, 2,
+ 2, 0, 3, 2, 0, 0, 7, 0, 0, 6,
+ 0, 10, 1, 0, 4, 1, 1, 1, 1, 2,
+ 2, 3, 1, 2, 1, 1, 1, 0, 5, 0,
+ 2, 1, 1, 3, 3, 3, 3, 3, 3, 3,
+ 3, 2, 0, 2, 3, 1, 4, 4, 4, 0,
+ 0, 6, 1, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 2, 3, 0, 4, 3, 3,
+ 3, 3, 2, 2, 3, 2, 3, 2, 3, 2,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2,
+ 3, 2, 3, 2, 3, 3, 3, 3, 3, 3,
+ 2, 3, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 3, 2, 1, 5, 0, 3,
+ 1, 1, 3, 1, 3, 5, 3, 5, 5, 5,
+ 7, 6, 8, 1, 4, 3, 3, 1, 2, 2,
+ 3, 1, 1, 1, 3, 1, 3, 1, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,
+ 2, 3, 1, 1, 2, 1, 5, 4, 3, 3,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
+ 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 3, 1, 1, 2, 2,
+ 2, 2, 2, 3, 3, 3, 3, 3, 3, 2,
+ 3, 4, 4, 6, 4, 4, 4, 6, 6, 4,
+ 4, 3, 4, 3, 3, 3, 3, 3, 3, 3,
+ 2
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYNOMEM goto yyexhaustedlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (!yyvaluep)
+ return;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ yy_symbol_value_print (yyo, yykind, yyvaluep);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)]);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep)
+{
+ YY_USE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/* Lookahead token kind. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ int yyn;
+ /* The return value of yyparse. */
+ int yyresult;
+ /* Lookahead symbol kind. */
+ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yysetstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ YY_IGNORE_USELESS_CAST_BEGIN
+ *yyssp = YY_CAST (yy_state_t, yystate);
+ YY_IGNORE_USELESS_CAST_END
+ YY_STACK_PRINT (yyss, yyssp);
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ YYNOMEM;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYPTRDIFF_T yysize = yyssp - yyss + 1;
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ yy_state_t *yyss1 = yyss;
+ YYSTYPE *yyvs1 = yyvs;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * YYSIZEOF (*yyssp),
+ &yyvs1, yysize * YYSIZEOF (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ YYNOMEM;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yy_state_t *yyss1 = yyss;
+ union yyalloc *yyptr =
+ YY_CAST (union yyalloc *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize))));
+ if (! yyptr)
+ YYNOMEM;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YY_IGNORE_USELESS_CAST_BEGIN
+ YYDPRINTF ((stderr, "Stack size increased to %ld\n",
+ YY_CAST (long, yystacksize)));
+ YY_IGNORE_USELESS_CAST_END
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token\n"));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = YYEOF;
+ yytoken = YYSYMBOL_YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else if (yychar == YYerror)
+ {
+ /* The scanner already issued an error message, process directly
+ to error recovery. But do not keep the error token as
+ lookahead, it is too special and may lead us to an endless
+ loop in error recovery. */
+ yychar = YYUNDEF;
+ yytoken = YYSYMBOL_YYerror;
+ goto yyerrlab1;
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 3: /* top: element_list */
+#line 277 "../src/preproc/pic/pic.ypp"
+ {
+ if (olist.head)
+ print_picture(olist.head);
+ }
+#line 2343 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 4: /* element_list: optional_separator middle_element_list optional_separator */
+#line 286 "../src/preproc/pic/pic.ypp"
+ { (yyval.pl) = (yyvsp[-1].pl); }
+#line 2349 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 5: /* middle_element_list: element */
+#line 291 "../src/preproc/pic/pic.ypp"
+ { (yyval.pl) = (yyvsp[0].pl); }
+#line 2355 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 6: /* middle_element_list: middle_element_list separator element */
+#line 293 "../src/preproc/pic/pic.ypp"
+ { (yyval.pl) = (yyvsp[-2].pl); }
+#line 2361 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 11: /* placeless_element: FIGNAME '=' macro_name */
+#line 308 "../src/preproc/pic/pic.ypp"
+ {
+ delete[] graphname;
+ graphname = new char[strlen((yyvsp[0].str)) + 1];
+ strcpy(graphname, (yyvsp[0].str));
+ delete[] (yyvsp[0].str);
+ }
+#line 2372 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 12: /* placeless_element: VARIABLE '=' any_expr */
+#line 316 "../src/preproc/pic/pic.ypp"
+ {
+ define_variable((yyvsp[-2].str), (yyvsp[0].x));
+ free((yyvsp[-2].str));
+ }
+#line 2381 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 13: /* placeless_element: VARIABLE ':' '=' any_expr */
+#line 321 "../src/preproc/pic/pic.ypp"
+ {
+ place *p = lookup_label((yyvsp[-3].str));
+ if (!p) {
+ lex_error("variable '%1' not defined", (yyvsp[-3].str));
+ YYABORT;
+ }
+ p->obj = 0;
+ p->x = (yyvsp[0].x);
+ p->y = 0.0;
+ free((yyvsp[-3].str));
+ }
+#line 2397 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 14: /* placeless_element: UP */
+#line 333 "../src/preproc/pic/pic.ypp"
+ { current_direction = UP_DIRECTION; }
+#line 2403 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 15: /* placeless_element: DOWN */
+#line 335 "../src/preproc/pic/pic.ypp"
+ { current_direction = DOWN_DIRECTION; }
+#line 2409 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 16: /* placeless_element: LEFT */
+#line 337 "../src/preproc/pic/pic.ypp"
+ { current_direction = LEFT_DIRECTION; }
+#line 2415 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 17: /* placeless_element: RIGHT */
+#line 339 "../src/preproc/pic/pic.ypp"
+ { current_direction = RIGHT_DIRECTION; }
+#line 2421 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 18: /* placeless_element: COMMAND_LINE */
+#line 341 "../src/preproc/pic/pic.ypp"
+ {
+ olist.append(make_command_object((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename,
+ (yyvsp[0].lstr).lineno));
+ }
+#line 2430 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 19: /* placeless_element: COMMAND print_args */
+#line 346 "../src/preproc/pic/pic.ypp"
+ {
+ olist.append(make_command_object((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename,
+ (yyvsp[0].lstr).lineno));
+ }
+#line 2439 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 20: /* placeless_element: PRINT print_args */
+#line 351 "../src/preproc/pic/pic.ypp"
+ {
+ fprintf(stderr, "%s\n", (yyvsp[0].lstr).str);
+ delete[] (yyvsp[0].lstr).str;
+ fflush(stderr);
+ }
+#line 2449 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 21: /* $@1: %empty */
+#line 357 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 1; }
+#line 2455 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 22: /* placeless_element: SH $@1 DELIMITED */
+#line 359 "../src/preproc/pic/pic.ypp"
+ {
+ delim_flag = 0;
+ if (safer_flag)
+ lex_error("unsafe to run command '%1'; ignoring",
+ (yyvsp[0].str));
+ else {
+ int retval = system((yyvsp[0].str));
+ if (retval < 0)
+ lex_error("error running command '%1': system()"
+ " returned %2", (yyvsp[0].str), retval);
+ }
+ delete[] (yyvsp[0].str);
+ }
+#line 2473 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 23: /* placeless_element: COPY TEXT */
+#line 373 "../src/preproc/pic/pic.ypp"
+ {
+ if (yychar < 0)
+ do_lookahead();
+ do_copy((yyvsp[0].lstr).str);
+ // do not delete the filename
+ }
+#line 2484 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 24: /* $@2: %empty */
+#line 380 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 2; }
+#line 2490 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 25: /* $@3: %empty */
+#line 382 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 0; }
+#line 2496 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 26: /* placeless_element: COPY TEXT THRU $@2 DELIMITED $@3 until */
+#line 384 "../src/preproc/pic/pic.ypp"
+ {
+ if (yychar < 0)
+ do_lookahead();
+ copy_file_thru((yyvsp[-5].lstr).str, (yyvsp[-2].str), (yyvsp[0].str));
+ // do not delete the filename
+ delete[] (yyvsp[-2].str);
+ delete[] (yyvsp[0].str);
+ }
+#line 2509 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 27: /* $@4: %empty */
+#line 393 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 2; }
+#line 2515 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 28: /* $@5: %empty */
+#line 395 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 0; }
+#line 2521 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 29: /* placeless_element: COPY THRU $@4 DELIMITED $@5 until */
+#line 397 "../src/preproc/pic/pic.ypp"
+ {
+ if (yychar < 0)
+ do_lookahead();
+ copy_rest_thru((yyvsp[-2].str), (yyvsp[0].str));
+ delete[] (yyvsp[-2].str);
+ delete[] (yyvsp[0].str);
+ }
+#line 2533 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 30: /* $@6: %empty */
+#line 405 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 1; }
+#line 2539 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 31: /* placeless_element: FOR VARIABLE '=' expr TO expr optional_by DO $@6 DELIMITED */
+#line 407 "../src/preproc/pic/pic.ypp"
+ {
+ delim_flag = 0;
+ if (yychar < 0)
+ do_lookahead();
+ do_for((yyvsp[-8].str), (yyvsp[-6].x), (yyvsp[-4].x), (yyvsp[-3].by).is_multiplicative, (yyvsp[-3].by).val, (yyvsp[0].str));
+ }
+#line 2550 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 32: /* placeless_element: simple_if */
+#line 414 "../src/preproc/pic/pic.ypp"
+ {
+ if (yychar < 0)
+ do_lookahead();
+ if ((yyvsp[0].if_data).x != 0.0)
+ push_body((yyvsp[0].if_data).body);
+ delete[] (yyvsp[0].if_data).body;
+ }
+#line 2562 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 33: /* $@7: %empty */
+#line 422 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 1; }
+#line 2568 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 34: /* placeless_element: simple_if ELSE $@7 DELIMITED */
+#line 424 "../src/preproc/pic/pic.ypp"
+ {
+ delim_flag = 0;
+ if (yychar < 0)
+ do_lookahead();
+ if ((yyvsp[-3].if_data).x != 0.0)
+ push_body((yyvsp[-3].if_data).body);
+ else
+ push_body((yyvsp[0].str));
+ free((yyvsp[-3].if_data).body);
+ free((yyvsp[0].str));
+ }
+#line 2584 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 36: /* placeless_element: RESET */
+#line 437 "../src/preproc/pic/pic.ypp"
+ { define_variable("scale", 1.0); }
+#line 2590 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 39: /* reset_variables: RESET VARIABLE */
+#line 447 "../src/preproc/pic/pic.ypp"
+ {
+ reset((yyvsp[0].str));
+ delete[] (yyvsp[0].str);
+ }
+#line 2599 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 40: /* reset_variables: reset_variables VARIABLE */
+#line 452 "../src/preproc/pic/pic.ypp"
+ {
+ reset((yyvsp[0].str));
+ delete[] (yyvsp[0].str);
+ }
+#line 2608 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 41: /* reset_variables: reset_variables ',' VARIABLE */
+#line 457 "../src/preproc/pic/pic.ypp"
+ {
+ reset((yyvsp[0].str));
+ delete[] (yyvsp[0].str);
+ }
+#line 2617 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 42: /* print_args: print_arg */
+#line 465 "../src/preproc/pic/pic.ypp"
+ { (yyval.lstr) = (yyvsp[0].lstr); }
+#line 2623 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 43: /* print_args: print_args print_arg */
+#line 467 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.lstr).str = new char[strlen((yyvsp[-1].lstr).str) + strlen((yyvsp[0].lstr).str) + 1];
+ strcpy((yyval.lstr).str, (yyvsp[-1].lstr).str);
+ strcat((yyval.lstr).str, (yyvsp[0].lstr).str);
+ delete[] (yyvsp[-1].lstr).str;
+ delete[] (yyvsp[0].lstr).str;
+ if ((yyvsp[-1].lstr).filename) {
+ (yyval.lstr).filename = (yyvsp[-1].lstr).filename;
+ (yyval.lstr).lineno = (yyvsp[-1].lstr).lineno;
+ }
+ else if ((yyvsp[0].lstr).filename) {
+ (yyval.lstr).filename = (yyvsp[0].lstr).filename;
+ (yyval.lstr).lineno = (yyvsp[0].lstr).lineno;
+ }
+ }
+#line 2643 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 44: /* print_arg: expr */
+#line 486 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.lstr).str = new char[GDIGITS + 1];
+ sprintf((yyval.lstr).str, "%g", (yyvsp[0].x));
+ (yyval.lstr).filename = 0;
+ (yyval.lstr).lineno = 0;
+ }
+#line 2654 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 45: /* print_arg: text */
+#line 493 "../src/preproc/pic/pic.ypp"
+ { (yyval.lstr) = (yyvsp[0].lstr); }
+#line 2660 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 46: /* print_arg: position */
+#line 495 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.lstr).str = new char[GDIGITS + 2 + GDIGITS + 1];
+ sprintf((yyval.lstr).str, "%g, %g", (yyvsp[0].pair).x, (yyvsp[0].pair).y);
+ (yyval.lstr).filename = 0;
+ (yyval.lstr).lineno = 0;
+ }
+#line 2671 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 47: /* $@8: %empty */
+#line 505 "../src/preproc/pic/pic.ypp"
+ { delim_flag = 1; }
+#line 2677 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 48: /* simple_if: IF any_expr THEN $@8 DELIMITED */
+#line 507 "../src/preproc/pic/pic.ypp"
+ {
+ delim_flag = 0;
+ (yyval.if_data).x = (yyvsp[-3].x);
+ (yyval.if_data).body = (yyvsp[0].str);
+ }
+#line 2687 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 49: /* until: %empty */
+#line 516 "../src/preproc/pic/pic.ypp"
+ { (yyval.str) = 0; }
+#line 2693 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 50: /* until: UNTIL TEXT */
+#line 518 "../src/preproc/pic/pic.ypp"
+ { (yyval.str) = (yyvsp[0].lstr).str; }
+#line 2699 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 51: /* any_expr: expr */
+#line 523 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[0].x); }
+#line 2705 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 52: /* any_expr: text_expr */
+#line 525 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[0].x); }
+#line 2711 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 53: /* text_expr: text EQUALEQUAL text */
+#line 530 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.x) = strcmp((yyvsp[-2].lstr).str, (yyvsp[0].lstr).str) == 0;
+ delete[] (yyvsp[-2].lstr).str;
+ delete[] (yyvsp[0].lstr).str;
+ }
+#line 2721 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 54: /* text_expr: text NOTEQUAL text */
+#line 536 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.x) = strcmp((yyvsp[-2].lstr).str, (yyvsp[0].lstr).str) != 0;
+ delete[] (yyvsp[-2].lstr).str;
+ delete[] (yyvsp[0].lstr).str;
+ }
+#line 2731 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 55: /* text_expr: text_expr ANDAND text_expr */
+#line 542 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); }
+#line 2737 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 56: /* text_expr: text_expr ANDAND expr */
+#line 544 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); }
+#line 2743 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 57: /* text_expr: expr ANDAND text_expr */
+#line 546 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); }
+#line 2749 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 58: /* text_expr: text_expr OROR text_expr */
+#line 548 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); }
+#line 2755 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 59: /* text_expr: text_expr OROR expr */
+#line 550 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); }
+#line 2761 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 60: /* text_expr: expr OROR text_expr */
+#line 552 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); }
+#line 2767 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 61: /* text_expr: '!' text_expr */
+#line 554 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[0].x) == 0.0); }
+#line 2773 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 62: /* optional_by: %empty */
+#line 560 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.by).val = 1.0;
+ (yyval.by).is_multiplicative = 0;
+ }
+#line 2782 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 63: /* optional_by: BY expr */
+#line 565 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.by).val = (yyvsp[0].x);
+ (yyval.by).is_multiplicative = 0;
+ }
+#line 2791 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 64: /* optional_by: BY '*' expr */
+#line 570 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.by).val = (yyvsp[0].x);
+ (yyval.by).is_multiplicative = 1;
+ }
+#line 2800 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 65: /* element: object_spec */
+#line 578 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pl).obj = (yyvsp[0].spec)->make_object(&current_position,
+ &current_direction);
+ if ((yyval.pl).obj == 0)
+ YYABORT;
+ delete (yyvsp[0].spec);
+ if ((yyval.pl).obj)
+ olist.append((yyval.pl).obj);
+ else {
+ (yyval.pl).x = current_position.x;
+ (yyval.pl).y = current_position.y;
+ }
+ }
+#line 2818 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 66: /* element: LABEL ':' optional_separator element */
+#line 592 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pl) = (yyvsp[0].pl);
+ define_label((yyvsp[-3].str), & (yyval.pl));
+ free((yyvsp[-3].str));
+ }
+#line 2828 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 67: /* element: LABEL ':' optional_separator position_not_place */
+#line 598 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pl).obj = 0;
+ (yyval.pl).x = (yyvsp[0].pair).x;
+ (yyval.pl).y = (yyvsp[0].pair).y;
+ define_label((yyvsp[-3].str), & (yyval.pl));
+ free((yyvsp[-3].str));
+ }
+#line 2840 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 68: /* element: LABEL ':' optional_separator place */
+#line 606 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pl) = (yyvsp[0].pl);
+ define_label((yyvsp[-3].str), & (yyval.pl));
+ free((yyvsp[-3].str));
+ }
+#line 2850 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 69: /* @9: %empty */
+#line 612 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.state).x = current_position.x;
+ (yyval.state).y = current_position.y;
+ (yyval.state).dir = current_direction;
+ }
+#line 2860 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 70: /* $@10: %empty */
+#line 618 "../src/preproc/pic/pic.ypp"
+ {
+ current_position.x = (yyvsp[-2].state).x;
+ current_position.y = (yyvsp[-2].state).y;
+ current_direction = (yyvsp[-2].state).dir;
+ }
+#line 2870 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 71: /* element: '{' @9 element_list '}' $@10 optional_element */
+#line 624 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pl) = (yyvsp[-3].pl);
+ }
+#line 2878 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 72: /* element: placeless_element */
+#line 628 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pl).obj = 0;
+ (yyval.pl).x = current_position.x;
+ (yyval.pl).y = current_position.y;
+ }
+#line 2888 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 73: /* optional_element: %empty */
+#line 637 "../src/preproc/pic/pic.ypp"
+ {}
+#line 2894 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 74: /* optional_element: element */
+#line 639 "../src/preproc/pic/pic.ypp"
+ {}
+#line 2900 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 75: /* object_spec: BOX */
+#line 644 "../src/preproc/pic/pic.ypp"
+ { (yyval.spec) = new object_spec(BOX_OBJECT); }
+#line 2906 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 76: /* object_spec: CIRCLE */
+#line 646 "../src/preproc/pic/pic.ypp"
+ { (yyval.spec) = new object_spec(CIRCLE_OBJECT); }
+#line 2912 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 77: /* object_spec: ELLIPSE */
+#line 648 "../src/preproc/pic/pic.ypp"
+ { (yyval.spec) = new object_spec(ELLIPSE_OBJECT); }
+#line 2918 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 78: /* object_spec: ARC */
+#line 650 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = new object_spec(ARC_OBJECT);
+ (yyval.spec)->dir = current_direction;
+ }
+#line 2927 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 79: /* object_spec: LINE */
+#line 655 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = new object_spec(LINE_OBJECT);
+ lookup_variable("lineht", & (yyval.spec)->segment_height);
+ lookup_variable("linewid", & (yyval.spec)->segment_width);
+ (yyval.spec)->dir = current_direction;
+ }
+#line 2938 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 80: /* object_spec: ARROW */
+#line 662 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = new object_spec(ARROW_OBJECT);
+ lookup_variable("lineht", & (yyval.spec)->segment_height);
+ lookup_variable("linewid", & (yyval.spec)->segment_width);
+ (yyval.spec)->dir = current_direction;
+ }
+#line 2949 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 81: /* object_spec: MOVE */
+#line 669 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = new object_spec(MOVE_OBJECT);
+ lookup_variable("moveht", & (yyval.spec)->segment_height);
+ lookup_variable("movewid", & (yyval.spec)->segment_width);
+ (yyval.spec)->dir = current_direction;
+ }
+#line 2960 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 82: /* object_spec: SPLINE */
+#line 676 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = new object_spec(SPLINE_OBJECT);
+ lookup_variable("lineht", & (yyval.spec)->segment_height);
+ lookup_variable("linewid", & (yyval.spec)->segment_width);
+ (yyval.spec)->dir = current_direction;
+ }
+#line 2971 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 83: /* object_spec: text */
+#line 683 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = new object_spec(TEXT_OBJECT);
+ (yyval.spec)->text = new text_item((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename, (yyvsp[0].lstr).lineno);
+ }
+#line 2980 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 84: /* object_spec: PLOT expr */
+#line 688 "../src/preproc/pic/pic.ypp"
+ {
+ lex_warning("'plot' is deprecated; use 'sprintf'"
+ " instead");
+ (yyval.spec) = new object_spec(TEXT_OBJECT);
+ (yyval.spec)->text = new text_item(format_number(0, (yyvsp[0].x)), 0, -1);
+ }
+#line 2991 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 85: /* object_spec: PLOT expr text */
+#line 695 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = new object_spec(TEXT_OBJECT);
+ (yyval.spec)->text = new text_item(format_number((yyvsp[0].lstr).str, (yyvsp[-1].x)),
+ (yyvsp[0].lstr).filename, (yyvsp[0].lstr).lineno);
+ delete[] (yyvsp[0].lstr).str;
+ }
+#line 3002 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 86: /* @11: %empty */
+#line 702 "../src/preproc/pic/pic.ypp"
+ {
+ saved_state *p = new saved_state;
+ (yyval.pstate) = p;
+ p->x = current_position.x;
+ p->y = current_position.y;
+ p->dir = current_direction;
+ p->tbl = current_table;
+ p->prev = current_saved_state;
+ current_position.x = 0.0;
+ current_position.y = 0.0;
+ current_table = new PTABLE(place);
+ current_saved_state = p;
+ olist.append(make_mark_object());
+ }
+#line 3021 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 87: /* object_spec: '[' @11 element_list ']' */
+#line 717 "../src/preproc/pic/pic.ypp"
+ {
+ current_position.x = (yyvsp[-2].pstate)->x;
+ current_position.y = (yyvsp[-2].pstate)->y;
+ current_direction = (yyvsp[-2].pstate)->dir;
+ (yyval.spec) = new object_spec(BLOCK_OBJECT);
+ olist.wrap_up_block(& (yyval.spec)->oblist);
+ (yyval.spec)->tbl = current_table;
+ current_table = (yyvsp[-2].pstate)->tbl;
+ current_saved_state = (yyvsp[-2].pstate)->prev;
+ delete (yyvsp[-2].pstate);
+ }
+#line 3037 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 88: /* object_spec: object_spec HEIGHT expr */
+#line 729 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->height = (yyvsp[0].x);
+ (yyval.spec)->flags |= HAS_HEIGHT;
+ }
+#line 3047 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 89: /* object_spec: object_spec RADIUS expr */
+#line 735 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->radius = (yyvsp[0].x);
+ (yyval.spec)->flags |= HAS_RADIUS;
+ }
+#line 3057 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 90: /* object_spec: object_spec WIDTH expr */
+#line 741 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->width = (yyvsp[0].x);
+ (yyval.spec)->flags |= HAS_WIDTH;
+ }
+#line 3067 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 91: /* object_spec: object_spec DIAMETER expr */
+#line 747 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->radius = (yyvsp[0].x)/2.0;
+ (yyval.spec)->flags |= HAS_RADIUS;
+ }
+#line 3077 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 92: /* object_spec: object_spec expr */
+#line 753 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ switch ((yyval.spec)->dir) {
+ case UP_DIRECTION:
+ (yyval.spec)->segment_pos.y += (yyvsp[0].x);
+ break;
+ case DOWN_DIRECTION:
+ (yyval.spec)->segment_pos.y -= (yyvsp[0].x);
+ break;
+ case RIGHT_DIRECTION:
+ (yyval.spec)->segment_pos.x += (yyvsp[0].x);
+ break;
+ case LEFT_DIRECTION:
+ (yyval.spec)->segment_pos.x -= (yyvsp[0].x);
+ break;
+ }
+ }
+#line 3100 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 93: /* object_spec: object_spec UP */
+#line 772 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->dir = UP_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.y += (yyval.spec)->segment_height;
+ }
+#line 3111 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 94: /* object_spec: object_spec UP expr */
+#line 779 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->dir = UP_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.y += (yyvsp[0].x);
+ }
+#line 3122 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 95: /* object_spec: object_spec DOWN */
+#line 786 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->dir = DOWN_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.y -= (yyval.spec)->segment_height;
+ }
+#line 3133 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 96: /* object_spec: object_spec DOWN expr */
+#line 793 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->dir = DOWN_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.y -= (yyvsp[0].x);
+ }
+#line 3144 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 97: /* object_spec: object_spec RIGHT */
+#line 800 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->dir = RIGHT_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.x += (yyval.spec)->segment_width;
+ }
+#line 3155 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 98: /* object_spec: object_spec RIGHT expr */
+#line 807 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->dir = RIGHT_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.x += (yyvsp[0].x);
+ }
+#line 3166 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 99: /* object_spec: object_spec LEFT */
+#line 814 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->dir = LEFT_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.x -= (yyval.spec)->segment_width;
+ }
+#line 3177 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 100: /* object_spec: object_spec LEFT expr */
+#line 821 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->dir = LEFT_DIRECTION;
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.x -= (yyvsp[0].x);
+ }
+#line 3188 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 101: /* object_spec: object_spec FROM position */
+#line 828 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= HAS_FROM;
+ (yyval.spec)->from.x = (yyvsp[0].pair).x;
+ (yyval.spec)->from.y = (yyvsp[0].pair).y;
+ }
+#line 3199 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 102: /* object_spec: object_spec TO position */
+#line 835 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ if ((yyval.spec)->flags & HAS_SEGMENT)
+ (yyval.spec)->segment_list = new segment((yyval.spec)->segment_pos,
+ (yyval.spec)->segment_is_absolute,
+ (yyval.spec)->segment_list);
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.x = (yyvsp[0].pair).x;
+ (yyval.spec)->segment_pos.y = (yyvsp[0].pair).y;
+ (yyval.spec)->segment_is_absolute = 1;
+ (yyval.spec)->flags |= HAS_TO;
+ (yyval.spec)->to.x = (yyvsp[0].pair).x;
+ (yyval.spec)->to.y = (yyvsp[0].pair).y;
+ }
+#line 3218 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 103: /* object_spec: object_spec AT position */
+#line 850 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= HAS_AT;
+ (yyval.spec)->at.x = (yyvsp[0].pair).x;
+ (yyval.spec)->at.y = (yyvsp[0].pair).y;
+ if ((yyval.spec)->type != ARC_OBJECT) {
+ (yyval.spec)->flags |= HAS_FROM;
+ (yyval.spec)->from.x = (yyvsp[0].pair).x;
+ (yyval.spec)->from.y = (yyvsp[0].pair).y;
+ }
+ }
+#line 3234 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 104: /* object_spec: object_spec WITH path */
+#line 862 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= HAS_WITH;
+ (yyval.spec)->with = (yyvsp[0].pth);
+ }
+#line 3244 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 105: /* object_spec: object_spec WITH position */
+#line 868 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= HAS_WITH;
+ position pos;
+ pos.x = (yyvsp[0].pair).x;
+ pos.y = (yyvsp[0].pair).y;
+ (yyval.spec)->with = new path(pos);
+ }
+#line 3257 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 106: /* object_spec: object_spec BY expr_pair */
+#line 877 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= HAS_SEGMENT;
+ (yyval.spec)->segment_pos.x += (yyvsp[0].pair).x;
+ (yyval.spec)->segment_pos.y += (yyvsp[0].pair).y;
+ }
+#line 3268 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 107: /* object_spec: object_spec THEN */
+#line 884 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ if (!((yyval.spec)->flags & HAS_SEGMENT))
+ switch ((yyval.spec)->dir) {
+ case UP_DIRECTION:
+ (yyval.spec)->segment_pos.y += (yyval.spec)->segment_width;
+ break;
+ case DOWN_DIRECTION:
+ (yyval.spec)->segment_pos.y -= (yyval.spec)->segment_width;
+ break;
+ case RIGHT_DIRECTION:
+ (yyval.spec)->segment_pos.x += (yyval.spec)->segment_width;
+ break;
+ case LEFT_DIRECTION:
+ (yyval.spec)->segment_pos.x -= (yyval.spec)->segment_width;
+ break;
+ }
+ (yyval.spec)->segment_list = new segment((yyval.spec)->segment_pos,
+ (yyval.spec)->segment_is_absolute,
+ (yyval.spec)->segment_list);
+ (yyval.spec)->flags &= ~HAS_SEGMENT;
+ (yyval.spec)->segment_pos.x = (yyval.spec)->segment_pos.y = 0.0;
+ (yyval.spec)->segment_is_absolute = 0;
+ }
+#line 3297 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 108: /* object_spec: object_spec SOLID */
+#line 909 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec); // nothing
+ }
+#line 3305 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 109: /* object_spec: object_spec DOTTED */
+#line 913 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= IS_DOTTED;
+ lookup_variable("dashwid", & (yyval.spec)->dash_width);
+ }
+#line 3315 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 110: /* object_spec: object_spec DOTTED expr */
+#line 919 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= IS_DOTTED;
+ (yyval.spec)->dash_width = (yyvsp[0].x);
+ }
+#line 3325 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 111: /* object_spec: object_spec DASHED */
+#line 925 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= IS_DASHED;
+ lookup_variable("dashwid", & (yyval.spec)->dash_width);
+ }
+#line 3335 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 112: /* object_spec: object_spec DASHED expr */
+#line 931 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= IS_DASHED;
+ (yyval.spec)->dash_width = (yyvsp[0].x);
+ }
+#line 3345 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 113: /* object_spec: object_spec FILL */
+#line 937 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= IS_DEFAULT_FILLED;
+ }
+#line 3354 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 114: /* object_spec: object_spec FILL expr */
+#line 942 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= IS_FILLED;
+ (yyval.spec)->fill = (yyvsp[0].x);
+ }
+#line 3364 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 115: /* object_spec: object_spec XSLANTED expr */
+#line 948 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= IS_XSLANTED;
+ (yyval.spec)->xslanted = (yyvsp[0].x);
+ }
+#line 3374 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 116: /* object_spec: object_spec YSLANTED expr */
+#line 954 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= IS_YSLANTED;
+ (yyval.spec)->yslanted = (yyvsp[0].x);
+ }
+#line 3384 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 117: /* object_spec: object_spec SHADED text */
+#line 960 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= (IS_SHADED | IS_FILLED);
+ (yyval.spec)->shaded = new char[strlen((yyvsp[0].lstr).str)+1];
+ strcpy((yyval.spec)->shaded, (yyvsp[0].lstr).str);
+ }
+#line 3395 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 118: /* object_spec: object_spec COLORED text */
+#line 967 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= (IS_SHADED | IS_OUTLINED | IS_FILLED);
+ (yyval.spec)->shaded = new char[strlen((yyvsp[0].lstr).str)+1];
+ strcpy((yyval.spec)->shaded, (yyvsp[0].lstr).str);
+ (yyval.spec)->outlined = new char[strlen((yyvsp[0].lstr).str)+1];
+ strcpy((yyval.spec)->outlined, (yyvsp[0].lstr).str);
+ }
+#line 3408 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 119: /* object_spec: object_spec OUTLINED text */
+#line 976 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= IS_OUTLINED;
+ (yyval.spec)->outlined = new char[strlen((yyvsp[0].lstr).str)+1];
+ strcpy((yyval.spec)->outlined, (yyvsp[0].lstr).str);
+ }
+#line 3419 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 120: /* object_spec: object_spec CHOP */
+#line 983 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ // line chop chop means line chop 0 chop 0
+ if ((yyval.spec)->flags & IS_DEFAULT_CHOPPED) {
+ (yyval.spec)->flags |= IS_CHOPPED;
+ (yyval.spec)->flags &= ~IS_DEFAULT_CHOPPED;
+ (yyval.spec)->start_chop = (yyval.spec)->end_chop = 0.0;
+ }
+ else if ((yyval.spec)->flags & IS_CHOPPED) {
+ (yyval.spec)->end_chop = 0.0;
+ }
+ else {
+ (yyval.spec)->flags |= IS_DEFAULT_CHOPPED;
+ }
+ }
+#line 3439 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 121: /* object_spec: object_spec CHOP expr */
+#line 999 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ if ((yyval.spec)->flags & IS_DEFAULT_CHOPPED) {
+ (yyval.spec)->flags |= IS_CHOPPED;
+ (yyval.spec)->flags &= ~IS_DEFAULT_CHOPPED;
+ (yyval.spec)->start_chop = 0.0;
+ (yyval.spec)->end_chop = (yyvsp[0].x);
+ }
+ else if ((yyval.spec)->flags & IS_CHOPPED) {
+ (yyval.spec)->end_chop = (yyvsp[0].x);
+ }
+ else {
+ (yyval.spec)->start_chop = (yyval.spec)->end_chop = (yyvsp[0].x);
+ (yyval.spec)->flags |= IS_CHOPPED;
+ }
+ }
+#line 3460 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 122: /* object_spec: object_spec SAME */
+#line 1016 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= IS_SAME;
+ }
+#line 3469 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 123: /* object_spec: object_spec INVISIBLE */
+#line 1021 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= IS_INVISIBLE;
+ }
+#line 3478 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 124: /* object_spec: object_spec LEFT_ARROW_HEAD */
+#line 1026 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= HAS_LEFT_ARROW_HEAD;
+ }
+#line 3487 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 125: /* object_spec: object_spec RIGHT_ARROW_HEAD */
+#line 1031 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= HAS_RIGHT_ARROW_HEAD;
+ }
+#line 3496 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 126: /* object_spec: object_spec DOUBLE_ARROW_HEAD */
+#line 1036 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD);
+ }
+#line 3505 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 127: /* object_spec: object_spec CW */
+#line 1041 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= IS_CLOCKWISE;
+ }
+#line 3514 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 128: /* object_spec: object_spec CCW */
+#line 1046 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags &= ~IS_CLOCKWISE;
+ }
+#line 3523 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 129: /* object_spec: object_spec text */
+#line 1051 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ text_item **p;
+ for (p = & (yyval.spec)->text; *p; p = &(*p)->next)
+ ;
+ *p = new text_item((yyvsp[0].lstr).str, (yyvsp[0].lstr).filename, (yyvsp[0].lstr).lineno);
+ }
+#line 3535 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 130: /* object_spec: object_spec LJUST */
+#line 1059 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ if ((yyval.spec)->text) {
+ text_item *p;
+ for (p = (yyval.spec)->text; p->next; p = p->next)
+ ;
+ p->adj.h = LEFT_ADJUST;
+ }
+ }
+#line 3549 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 131: /* object_spec: object_spec RJUST */
+#line 1069 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ if ((yyval.spec)->text) {
+ text_item *p;
+ for (p = (yyval.spec)->text; p->next; p = p->next)
+ ;
+ p->adj.h = RIGHT_ADJUST;
+ }
+ }
+#line 3563 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 132: /* object_spec: object_spec ABOVE */
+#line 1079 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ if ((yyval.spec)->text) {
+ text_item *p;
+ for (p = (yyval.spec)->text; p->next; p = p->next)
+ ;
+ p->adj.v = ABOVE_ADJUST;
+ }
+ }
+#line 3577 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 133: /* object_spec: object_spec BELOW */
+#line 1089 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ if ((yyval.spec)->text) {
+ text_item *p;
+ for (p = (yyval.spec)->text; p->next; p = p->next)
+ ;
+ p->adj.v = BELOW_ADJUST;
+ }
+ }
+#line 3591 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 134: /* object_spec: object_spec THICKNESS expr */
+#line 1099 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-2].spec);
+ (yyval.spec)->flags |= HAS_THICKNESS;
+ (yyval.spec)->thickness = (yyvsp[0].x);
+ }
+#line 3601 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 135: /* object_spec: object_spec ALIGNED */
+#line 1105 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.spec) = (yyvsp[-1].spec);
+ (yyval.spec)->flags |= IS_ALIGNED;
+ }
+#line 3610 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 136: /* text: TEXT */
+#line 1113 "../src/preproc/pic/pic.ypp"
+ { (yyval.lstr) = (yyvsp[0].lstr); }
+#line 3616 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 137: /* text: SPRINTF '(' TEXT sprintf_args ')' */
+#line 1115 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.lstr).filename = (yyvsp[-2].lstr).filename;
+ (yyval.lstr).lineno = (yyvsp[-2].lstr).lineno;
+ (yyval.lstr).str = do_sprintf((yyvsp[-2].lstr).str, (yyvsp[-1].dv).v, (yyvsp[-1].dv).nv);
+ delete[] (yyvsp[-1].dv).v;
+ free((yyvsp[-2].lstr).str);
+ }
+#line 3628 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 138: /* sprintf_args: %empty */
+#line 1126 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.dv).v = 0;
+ (yyval.dv).nv = 0;
+ (yyval.dv).maxv = 0;
+ }
+#line 3638 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 139: /* sprintf_args: sprintf_args ',' expr */
+#line 1132 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.dv) = (yyvsp[-2].dv);
+ if ((yyval.dv).nv >= (yyval.dv).maxv) {
+ if ((yyval.dv).nv == 0) {
+ (yyval.dv).v = new double[4];
+ (yyval.dv).maxv = 4;
+ }
+ else {
+ double *oldv = (yyval.dv).v;
+ (yyval.dv).maxv *= 2;
+#if 0
+ (yyval.dv).v = new double[(yyval.dv).maxv];
+ memcpy((yyval.dv).v, oldv, (yyval.dv).nv*sizeof(double));
+#else
+ // workaround for bug in Compaq C++ V6.5-033
+ // for Compaq Tru64 UNIX V5.1A (Rev. 1885)
+ double *foo = new double[(yyval.dv).maxv];
+ memcpy(foo, oldv, (yyval.dv).nv*sizeof(double));
+ (yyval.dv).v = foo;
+#endif
+ delete[] oldv;
+ }
+ }
+ (yyval.dv).v[(yyval.dv).nv] = (yyvsp[0].x);
+ (yyval.dv).nv += 1;
+ }
+#line 3669 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 140: /* position: position_not_place */
+#line 1162 "../src/preproc/pic/pic.ypp"
+ { (yyval.pair) = (yyvsp[0].pair); }
+#line 3675 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 141: /* position: place */
+#line 1164 "../src/preproc/pic/pic.ypp"
+ {
+ position pos = (yyvsp[0].pl);
+ (yyval.pair).x = pos.x;
+ (yyval.pair).y = pos.y;
+ }
+#line 3685 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 142: /* position: '(' place ')' */
+#line 1170 "../src/preproc/pic/pic.ypp"
+ {
+ position pos = (yyvsp[-1].pl);
+ (yyval.pair).x = pos.x;
+ (yyval.pair).y = pos.y;
+ }
+#line 3695 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 143: /* position_not_place: expr_pair */
+#line 1179 "../src/preproc/pic/pic.ypp"
+ { (yyval.pair) = (yyvsp[0].pair); }
+#line 3701 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 144: /* position_not_place: position '+' expr_pair */
+#line 1181 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (yyvsp[-2].pair).x + (yyvsp[0].pair).x;
+ (yyval.pair).y = (yyvsp[-2].pair).y + (yyvsp[0].pair).y;
+ }
+#line 3710 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 145: /* position_not_place: '(' position '+' expr_pair ')' */
+#line 1186 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (yyvsp[-3].pair).x + (yyvsp[-1].pair).x;
+ (yyval.pair).y = (yyvsp[-3].pair).y + (yyvsp[-1].pair).y;
+ }
+#line 3719 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 146: /* position_not_place: position '-' expr_pair */
+#line 1191 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (yyvsp[-2].pair).x - (yyvsp[0].pair).x;
+ (yyval.pair).y = (yyvsp[-2].pair).y - (yyvsp[0].pair).y;
+ }
+#line 3728 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 147: /* position_not_place: '(' position '-' expr_pair ')' */
+#line 1196 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (yyvsp[-3].pair).x - (yyvsp[-1].pair).x;
+ (yyval.pair).y = (yyvsp[-3].pair).y - (yyvsp[-1].pair).y;
+ }
+#line 3737 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 148: /* position_not_place: '(' position ',' position ')' */
+#line 1201 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (yyvsp[-3].pair).x;
+ (yyval.pair).y = (yyvsp[-1].pair).y;
+ }
+#line 3746 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 149: /* position_not_place: expr between position AND position */
+#line 1206 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (1.0 - (yyvsp[-4].x))*(yyvsp[-2].pair).x + (yyvsp[-4].x)*(yyvsp[0].pair).x;
+ (yyval.pair).y = (1.0 - (yyvsp[-4].x))*(yyvsp[-2].pair).y + (yyvsp[-4].x)*(yyvsp[0].pair).y;
+ }
+#line 3755 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 150: /* position_not_place: '(' expr between position AND position ')' */
+#line 1211 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).x + (yyvsp[-5].x)*(yyvsp[-1].pair).x;
+ (yyval.pair).y = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).y + (yyvsp[-5].x)*(yyvsp[-1].pair).y;
+ }
+#line 3764 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 151: /* position_not_place: expr_not_lower_than '<' position ',' position '>' */
+#line 1217 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).x + (yyvsp[-5].x)*(yyvsp[-1].pair).x;
+ (yyval.pair).y = (1.0 - (yyvsp[-5].x))*(yyvsp[-3].pair).y + (yyvsp[-5].x)*(yyvsp[-1].pair).y;
+ }
+#line 3773 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 152: /* position_not_place: '(' expr_not_lower_than '<' position ',' position '>' ')' */
+#line 1222 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (1.0 - (yyvsp[-6].x))*(yyvsp[-4].pair).x + (yyvsp[-6].x)*(yyvsp[-2].pair).x;
+ (yyval.pair).y = (1.0 - (yyvsp[-6].x))*(yyvsp[-4].pair).y + (yyvsp[-6].x)*(yyvsp[-2].pair).y;
+ }
+#line 3782 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 155: /* expr_pair: expr ',' expr */
+#line 1235 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pair).x = (yyvsp[-2].x);
+ (yyval.pair).y = (yyvsp[0].x);
+ }
+#line 3791 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 156: /* expr_pair: '(' expr_pair ')' */
+#line 1240 "../src/preproc/pic/pic.ypp"
+ { (yyval.pair) = (yyvsp[-1].pair); }
+#line 3797 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 157: /* place: label */
+#line 1246 "../src/preproc/pic/pic.ypp"
+ { (yyval.pl) = (yyvsp[0].pl); }
+#line 3803 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 158: /* place: label corner */
+#line 1248 "../src/preproc/pic/pic.ypp"
+ {
+ path pth((yyvsp[0].crn));
+ if (!pth.follow((yyvsp[-1].pl), & (yyval.pl)))
+ YYABORT;
+ }
+#line 3813 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 159: /* place: corner label */
+#line 1254 "../src/preproc/pic/pic.ypp"
+ {
+ path pth((yyvsp[-1].crn));
+ if (!pth.follow((yyvsp[0].pl), & (yyval.pl)))
+ YYABORT;
+ }
+#line 3823 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 160: /* place: corner OF label */
+#line 1260 "../src/preproc/pic/pic.ypp"
+ {
+ path pth((yyvsp[-2].crn));
+ if (!pth.follow((yyvsp[0].pl), & (yyval.pl)))
+ YYABORT;
+ }
+#line 3833 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 161: /* place: HERE */
+#line 1266 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pl).x = current_position.x;
+ (yyval.pl).y = current_position.y;
+ (yyval.pl).obj = 0;
+ }
+#line 3843 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 162: /* label: LABEL */
+#line 1275 "../src/preproc/pic/pic.ypp"
+ {
+ place *p = lookup_label((yyvsp[0].str));
+ if (!p) {
+ lex_error("there is no place '%1'", (yyvsp[0].str));
+ YYABORT;
+ }
+ (yyval.pl) = *p;
+ free((yyvsp[0].str));
+ }
+#line 3857 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 163: /* label: nth_primitive */
+#line 1285 "../src/preproc/pic/pic.ypp"
+ { (yyval.pl).obj = (yyvsp[0].obj); }
+#line 3863 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 164: /* label: label '.' LABEL */
+#line 1287 "../src/preproc/pic/pic.ypp"
+ {
+ path pth((yyvsp[0].str));
+ if (!pth.follow((yyvsp[-2].pl), & (yyval.pl)))
+ YYABORT;
+ }
+#line 3873 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 165: /* ordinal: ORDINAL */
+#line 1296 "../src/preproc/pic/pic.ypp"
+ { (yyval.n) = (yyvsp[0].n); }
+#line 3879 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 166: /* ordinal: '`' any_expr TH */
+#line 1298 "../src/preproc/pic/pic.ypp"
+ {
+ // XXX Check for overflow (and non-integers?).
+ (yyval.n) = (int)(yyvsp[-1].x);
+ }
+#line 3888 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 167: /* optional_ordinal_last: LAST */
+#line 1306 "../src/preproc/pic/pic.ypp"
+ { (yyval.n) = 1; }
+#line 3894 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 168: /* optional_ordinal_last: ordinal LAST */
+#line 1308 "../src/preproc/pic/pic.ypp"
+ { (yyval.n) = (yyvsp[-1].n); }
+#line 3900 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 169: /* nth_primitive: ordinal object_type */
+#line 1313 "../src/preproc/pic/pic.ypp"
+ {
+ int count = 0;
+ object *p;
+ for (p = olist.head; p != 0; p = p->next)
+ if (p->type() == (yyvsp[0].obtype) && ++count == (yyvsp[-1].n)) {
+ (yyval.obj) = p;
+ break;
+ }
+ if (p == 0) {
+ lex_error("there is no %1%2 %3", (yyvsp[-1].n), ordinal_postfix((yyvsp[-1].n)),
+ object_type_name((yyvsp[0].obtype)));
+ YYABORT;
+ }
+ }
+#line 3919 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 170: /* nth_primitive: optional_ordinal_last object_type */
+#line 1328 "../src/preproc/pic/pic.ypp"
+ {
+ int count = 0;
+ object *p;
+ for (p = olist.tail; p != 0; p = p->prev)
+ if (p->type() == (yyvsp[0].obtype) && ++count == (yyvsp[-1].n)) {
+ (yyval.obj) = p;
+ break;
+ }
+ if (p == 0) {
+ lex_error("there is no %1%2 last %3", (yyvsp[-1].n),
+ ordinal_postfix((yyvsp[-1].n)), object_type_name((yyvsp[0].obtype)));
+ YYABORT;
+ }
+ }
+#line 3938 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 171: /* object_type: BOX */
+#line 1346 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = BOX_OBJECT; }
+#line 3944 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 172: /* object_type: CIRCLE */
+#line 1348 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = CIRCLE_OBJECT; }
+#line 3950 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 173: /* object_type: ELLIPSE */
+#line 1350 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = ELLIPSE_OBJECT; }
+#line 3956 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 174: /* object_type: ARC */
+#line 1352 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = ARC_OBJECT; }
+#line 3962 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 175: /* object_type: LINE */
+#line 1354 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = LINE_OBJECT; }
+#line 3968 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 176: /* object_type: ARROW */
+#line 1356 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = ARROW_OBJECT; }
+#line 3974 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 177: /* object_type: SPLINE */
+#line 1358 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = SPLINE_OBJECT; }
+#line 3980 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 178: /* object_type: '[' ']' */
+#line 1360 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = BLOCK_OBJECT; }
+#line 3986 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 179: /* object_type: TEXT */
+#line 1362 "../src/preproc/pic/pic.ypp"
+ { (yyval.obtype) = TEXT_OBJECT; }
+#line 3992 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 180: /* label_path: '.' LABEL */
+#line 1367 "../src/preproc/pic/pic.ypp"
+ { (yyval.pth) = new path((yyvsp[0].str)); }
+#line 3998 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 181: /* label_path: label_path '.' LABEL */
+#line 1369 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pth) = (yyvsp[-2].pth);
+ (yyval.pth)->append((yyvsp[0].str));
+ }
+#line 4007 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 182: /* relative_path: corner */
+#line 1377 "../src/preproc/pic/pic.ypp"
+ { (yyval.pth) = new path((yyvsp[0].crn)); }
+#line 4013 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 183: /* relative_path: label_path */
+#line 1381 "../src/preproc/pic/pic.ypp"
+ { (yyval.pth) = (yyvsp[0].pth); }
+#line 4019 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 184: /* relative_path: label_path corner */
+#line 1383 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pth) = (yyvsp[-1].pth);
+ (yyval.pth)->append((yyvsp[0].crn));
+ }
+#line 4028 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 185: /* path: relative_path */
+#line 1391 "../src/preproc/pic/pic.ypp"
+ { (yyval.pth) = (yyvsp[0].pth); }
+#line 4034 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 186: /* path: '(' relative_path ',' relative_path ')' */
+#line 1393 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.pth) = (yyvsp[-3].pth);
+ (yyval.pth)->set_ypath((yyvsp[-1].pth));
+ }
+#line 4043 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 187: /* path: ORDINAL LAST object_type relative_path */
+#line 1399 "../src/preproc/pic/pic.ypp"
+ {
+ lex_warning("'%1%2 last %3' in 'with' argument ignored",
+ (yyvsp[-3].n), ordinal_postfix((yyvsp[-3].n)), object_type_name((yyvsp[-1].obtype)));
+ (yyval.pth) = (yyvsp[0].pth);
+ }
+#line 4053 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 188: /* path: LAST object_type relative_path */
+#line 1405 "../src/preproc/pic/pic.ypp"
+ {
+ lex_warning("'last %1' in 'with' argument ignored",
+ object_type_name((yyvsp[-1].obtype)));
+ (yyval.pth) = (yyvsp[0].pth);
+ }
+#line 4063 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 189: /* path: ORDINAL object_type relative_path */
+#line 1411 "../src/preproc/pic/pic.ypp"
+ {
+ lex_warning("'%1%2 %3' in 'with' argument ignored",
+ (yyvsp[-2].n), ordinal_postfix((yyvsp[-2].n)), object_type_name((yyvsp[-1].obtype)));
+ (yyval.pth) = (yyvsp[0].pth);
+ }
+#line 4073 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 190: /* path: LABEL relative_path */
+#line 1417 "../src/preproc/pic/pic.ypp"
+ {
+ lex_warning("initial '%1' in 'with' argument ignored", (yyvsp[-1].str));
+ delete[] (yyvsp[-1].str);
+ (yyval.pth) = (yyvsp[0].pth);
+ }
+#line 4083 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 191: /* corner: DOT_N */
+#line 1426 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north; }
+#line 4089 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 192: /* corner: DOT_E */
+#line 1428 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::east; }
+#line 4095 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 193: /* corner: DOT_W */
+#line 1430 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::west; }
+#line 4101 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 194: /* corner: DOT_S */
+#line 1432 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south; }
+#line 4107 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 195: /* corner: DOT_NE */
+#line 1434 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north_east; }
+#line 4113 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 196: /* corner: DOT_SE */
+#line 1436 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object:: south_east; }
+#line 4119 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 197: /* corner: DOT_NW */
+#line 1438 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north_west; }
+#line 4125 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 198: /* corner: DOT_SW */
+#line 1440 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south_west; }
+#line 4131 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 199: /* corner: DOT_C */
+#line 1442 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::center; }
+#line 4137 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 200: /* corner: DOT_START */
+#line 1444 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::start; }
+#line 4143 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 201: /* corner: DOT_END */
+#line 1446 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::end; }
+#line 4149 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 202: /* corner: TOP */
+#line 1448 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north; }
+#line 4155 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 203: /* corner: BOTTOM */
+#line 1450 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south; }
+#line 4161 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 204: /* corner: LEFT */
+#line 1452 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::west; }
+#line 4167 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 205: /* corner: RIGHT */
+#line 1454 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::east; }
+#line 4173 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 206: /* corner: UPPER LEFT */
+#line 1456 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north_west; }
+#line 4179 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 207: /* corner: LOWER LEFT */
+#line 1458 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south_west; }
+#line 4185 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 208: /* corner: UPPER RIGHT */
+#line 1460 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north_east; }
+#line 4191 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 209: /* corner: LOWER RIGHT */
+#line 1462 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south_east; }
+#line 4197 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 210: /* corner: LEFT_CORNER */
+#line 1464 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::west; }
+#line 4203 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 211: /* corner: RIGHT_CORNER */
+#line 1466 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::east; }
+#line 4209 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 212: /* corner: UPPER LEFT_CORNER */
+#line 1468 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north_west; }
+#line 4215 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 213: /* corner: LOWER LEFT_CORNER */
+#line 1470 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south_west; }
+#line 4221 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 214: /* corner: UPPER RIGHT_CORNER */
+#line 1472 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north_east; }
+#line 4227 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 215: /* corner: LOWER RIGHT_CORNER */
+#line 1474 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south_east; }
+#line 4233 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 216: /* corner: NORTH */
+#line 1476 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::north; }
+#line 4239 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 217: /* corner: SOUTH */
+#line 1478 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::south; }
+#line 4245 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 218: /* corner: EAST */
+#line 1480 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::east; }
+#line 4251 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 219: /* corner: WEST */
+#line 1482 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::west; }
+#line 4257 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 220: /* corner: CENTER */
+#line 1484 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::center; }
+#line 4263 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 221: /* corner: START */
+#line 1486 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::start; }
+#line 4269 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 222: /* corner: END */
+#line 1488 "../src/preproc/pic/pic.ypp"
+ { (yyval.crn) = &object::end; }
+#line 4275 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 223: /* expr: expr_lower_than */
+#line 1493 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[0].x); }
+#line 4281 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 224: /* expr: expr_not_lower_than */
+#line 1495 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[0].x); }
+#line 4287 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 225: /* expr_lower_than: expr '<' expr */
+#line 1500 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) < (yyvsp[0].x)); }
+#line 4293 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 226: /* expr_not_lower_than: VARIABLE */
+#line 1505 "../src/preproc/pic/pic.ypp"
+ {
+ if (!lookup_variable((yyvsp[0].str), & (yyval.x))) {
+ lex_error("there is no variable '%1'", (yyvsp[0].str));
+ YYABORT;
+ }
+ free((yyvsp[0].str));
+ }
+#line 4305 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 227: /* expr_not_lower_than: NUMBER */
+#line 1513 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[0].x); }
+#line 4311 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 228: /* expr_not_lower_than: place DOT_X */
+#line 1515 "../src/preproc/pic/pic.ypp"
+ {
+ if ((yyvsp[-1].pl).obj != 0)
+ (yyval.x) = (yyvsp[-1].pl).obj->origin().x;
+ else
+ (yyval.x) = (yyvsp[-1].pl).x;
+ }
+#line 4322 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 229: /* expr_not_lower_than: place DOT_Y */
+#line 1522 "../src/preproc/pic/pic.ypp"
+ {
+ if ((yyvsp[-1].pl).obj != 0)
+ (yyval.x) = (yyvsp[-1].pl).obj->origin().y;
+ else
+ (yyval.x) = (yyvsp[-1].pl).y;
+ }
+#line 4333 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 230: /* expr_not_lower_than: place DOT_HT */
+#line 1529 "../src/preproc/pic/pic.ypp"
+ {
+ if ((yyvsp[-1].pl).obj != 0)
+ (yyval.x) = (yyvsp[-1].pl).obj->height();
+ else
+ (yyval.x) = 0.0;
+ }
+#line 4344 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 231: /* expr_not_lower_than: place DOT_WID */
+#line 1536 "../src/preproc/pic/pic.ypp"
+ {
+ if ((yyvsp[-1].pl).obj != 0)
+ (yyval.x) = (yyvsp[-1].pl).obj->width();
+ else
+ (yyval.x) = 0.0;
+ }
+#line 4355 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 232: /* expr_not_lower_than: place DOT_RAD */
+#line 1543 "../src/preproc/pic/pic.ypp"
+ {
+ if ((yyvsp[-1].pl).obj != 0)
+ (yyval.x) = (yyvsp[-1].pl).obj->radius();
+ else
+ (yyval.x) = 0.0;
+ }
+#line 4366 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 233: /* expr_not_lower_than: expr '+' expr */
+#line 1550 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[-2].x) + (yyvsp[0].x); }
+#line 4372 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 234: /* expr_not_lower_than: expr '-' expr */
+#line 1552 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[-2].x) - (yyvsp[0].x); }
+#line 4378 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 235: /* expr_not_lower_than: expr '*' expr */
+#line 1554 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[-2].x) * (yyvsp[0].x); }
+#line 4384 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 236: /* expr_not_lower_than: expr '/' expr */
+#line 1556 "../src/preproc/pic/pic.ypp"
+ {
+ if ((yyvsp[0].x) == 0.0) {
+ lex_error("division by zero");
+ YYABORT;
+ }
+ (yyval.x) = (yyvsp[-2].x)/(yyvsp[0].x);
+ }
+#line 4396 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 237: /* expr_not_lower_than: expr '%' expr */
+#line 1564 "../src/preproc/pic/pic.ypp"
+ {
+ if ((yyvsp[0].x) == 0.0) {
+ lex_error("modulus by zero");
+ YYABORT;
+ }
+ (yyval.x) = fmod((yyvsp[-2].x), (yyvsp[0].x));
+ }
+#line 4408 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 238: /* expr_not_lower_than: expr '^' expr */
+#line 1572 "../src/preproc/pic/pic.ypp"
+ {
+ errno = 0;
+ (yyval.x) = pow((yyvsp[-2].x), (yyvsp[0].x));
+ if (errno == EDOM) {
+ lex_error("arguments to '^' operator out of domain");
+ YYABORT;
+ }
+ if (errno == ERANGE) {
+ lex_error("result of '^' operator out of range");
+ YYABORT;
+ }
+ }
+#line 4425 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 239: /* expr_not_lower_than: '-' expr */
+#line 1585 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = -(yyvsp[0].x); }
+#line 4431 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 240: /* expr_not_lower_than: '(' any_expr ')' */
+#line 1587 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[-1].x); }
+#line 4437 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 241: /* expr_not_lower_than: SIN '(' any_expr ')' */
+#line 1589 "../src/preproc/pic/pic.ypp"
+ {
+ errno = 0;
+ (yyval.x) = sin((yyvsp[-1].x));
+ if (errno == ERANGE) {
+ lex_error("sin result out of range");
+ YYABORT;
+ }
+ }
+#line 4450 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 242: /* expr_not_lower_than: COS '(' any_expr ')' */
+#line 1598 "../src/preproc/pic/pic.ypp"
+ {
+ errno = 0;
+ (yyval.x) = cos((yyvsp[-1].x));
+ if (errno == ERANGE) {
+ lex_error("cos result out of range");
+ YYABORT;
+ }
+ }
+#line 4463 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 243: /* expr_not_lower_than: ATAN2 '(' any_expr ',' any_expr ')' */
+#line 1607 "../src/preproc/pic/pic.ypp"
+ {
+ errno = 0;
+ (yyval.x) = atan2((yyvsp[-3].x), (yyvsp[-1].x));
+ if (errno == EDOM) {
+ lex_error("atan2 argument out of domain");
+ YYABORT;
+ }
+ if (errno == ERANGE) {
+ lex_error("atan2 result out of range");
+ YYABORT;
+ }
+ }
+#line 4480 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 244: /* expr_not_lower_than: LOG '(' any_expr ')' */
+#line 1620 "../src/preproc/pic/pic.ypp"
+ {
+ errno = 0;
+ (yyval.x) = log10((yyvsp[-1].x));
+ if (errno == ERANGE) {
+ lex_error("log result out of range");
+ YYABORT;
+ }
+ }
+#line 4493 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 245: /* expr_not_lower_than: EXP '(' any_expr ')' */
+#line 1629 "../src/preproc/pic/pic.ypp"
+ {
+ errno = 0;
+ (yyval.x) = pow(10.0, (yyvsp[-1].x));
+ if (errno == ERANGE) {
+ lex_error("exp result out of range");
+ YYABORT;
+ }
+ }
+#line 4506 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 246: /* expr_not_lower_than: SQRT '(' any_expr ')' */
+#line 1638 "../src/preproc/pic/pic.ypp"
+ {
+ errno = 0;
+ (yyval.x) = sqrt((yyvsp[-1].x));
+ if (errno == EDOM) {
+ lex_error("sqrt argument out of domain");
+ YYABORT;
+ }
+ }
+#line 4519 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 247: /* expr_not_lower_than: K_MAX '(' any_expr ',' any_expr ')' */
+#line 1647 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[-3].x) > (yyvsp[-1].x) ? (yyvsp[-3].x) : (yyvsp[-1].x); }
+#line 4525 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 248: /* expr_not_lower_than: K_MIN '(' any_expr ',' any_expr ')' */
+#line 1649 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[-3].x) < (yyvsp[-1].x) ? (yyvsp[-3].x) : (yyvsp[-1].x); }
+#line 4531 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 249: /* expr_not_lower_than: INT '(' any_expr ')' */
+#line 1651 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = (yyvsp[-1].x) < 0 ? -floor(-(yyvsp[-1].x)) : floor((yyvsp[-1].x)); }
+#line 4537 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 250: /* expr_not_lower_than: RAND '(' any_expr ')' */
+#line 1653 "../src/preproc/pic/pic.ypp"
+ {
+ lex_error("use of 'rand' with an argument is"
+ " deprecated; shift and scale 'rand()' with"
+ " arithmetic instead");
+ (yyval.x) = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*(yyvsp[-1].x));
+ }
+#line 4548 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 251: /* expr_not_lower_than: RAND '(' ')' */
+#line 1660 "../src/preproc/pic/pic.ypp"
+ {
+ /* return a random number in the range [0,1) */
+ /* portable, but not very random */
+ (yyval.x) = (rand() & 0x7fff) / double(0x8000);
+ }
+#line 4558 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 252: /* expr_not_lower_than: SRAND '(' any_expr ')' */
+#line 1666 "../src/preproc/pic/pic.ypp"
+ {
+ (yyval.x) = 0;
+ srand((unsigned int)(yyvsp[-1].x));
+ }
+#line 4567 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 253: /* expr_not_lower_than: expr LESSEQUAL expr */
+#line 1671 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) <= (yyvsp[0].x)); }
+#line 4573 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 254: /* expr_not_lower_than: expr '>' expr */
+#line 1673 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) > (yyvsp[0].x)); }
+#line 4579 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 255: /* expr_not_lower_than: expr GREATEREQUAL expr */
+#line 1675 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) >= (yyvsp[0].x)); }
+#line 4585 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 256: /* expr_not_lower_than: expr EQUALEQUAL expr */
+#line 1677 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) == (yyvsp[0].x)); }
+#line 4591 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 257: /* expr_not_lower_than: expr NOTEQUAL expr */
+#line 1679 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != (yyvsp[0].x)); }
+#line 4597 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 258: /* expr_not_lower_than: expr ANDAND expr */
+#line 1681 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 && (yyvsp[0].x) != 0.0); }
+#line 4603 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 259: /* expr_not_lower_than: expr OROR expr */
+#line 1683 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[-2].x) != 0.0 || (yyvsp[0].x) != 0.0); }
+#line 4609 "src/preproc/pic/pic.cpp"
+ break;
+
+ case 260: /* expr_not_lower_than: '!' expr */
+#line 1685 "../src/preproc/pic/pic.ypp"
+ { (yyval.x) = ((yyvsp[0].x) == 0.0); }
+#line 4615 "src/preproc/pic/pic.cpp"
+ break;
+
+
+#line 4619 "src/preproc/pic/pic.cpp"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ yyerror (YY_("syntax error"));
+ }
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+ ++yynerrs;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ /* Pop stack until we find a state that shifts the error token. */
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYSYMBOL_YYerror;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturnlab;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturnlab;
+
+
+/*-----------------------------------------------------------.
+| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. |
+`-----------------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturnlab;
+
+
+/*----------------------------------------------------------.
+| yyreturnlab -- parsing is finished, clean up and return. |
+`----------------------------------------------------------*/
+yyreturnlab:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ YY_ACCESSING_SYMBOL (+*yyssp), yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+
+ return yyresult;
+}
+
+#line 1689 "../src/preproc/pic/pic.ypp"
+
+
+/* bison defines const to be empty unless __STDC__ is defined, which it
+isn't under cfront */
+
+#ifdef const
+#undef const
+#endif
+
+static struct {
+ const char *name;
+ double val;
+ int scaled; // non-zero if val should be multiplied by scale
+} defaults_table[] = {
+ { "arcrad", .25, 1 },
+ { "arrowht", .1, 1 },
+ { "arrowwid", .05, 1 },
+ { "circlerad", .25, 1 },
+ { "boxht", .5, 1 },
+ { "boxwid", .75, 1 },
+ { "boxrad", 0.0, 1 },
+ { "dashwid", .05, 1 },
+ { "ellipseht", .5, 1 },
+ { "ellipsewid", .75, 1 },
+ { "moveht", .5, 1 },
+ { "movewid", .5, 1 },
+ { "lineht", .5, 1 },
+ { "linewid", .5, 1 },
+ { "textht", 0.0, 1 },
+ { "textwid", 0.0, 1 },
+ { "scale", 1.0, 0 },
+ { "linethick", -1.0, 0 }, // in points
+ { "fillval", .5, 0 },
+ { "arrowhead", 1.0, 0 },
+ { "maxpswid", 8.5, 0 },
+ { "maxpsht", 11.0, 0 },
+};
+
+place *lookup_label(const char *label)
+{
+ saved_state *state = current_saved_state;
+ PTABLE(place) *tbl = current_table;
+ for (;;) {
+ place *pl = tbl->lookup(label);
+ if (pl)
+ return pl;
+ if (!state)
+ return 0;
+ tbl = state->tbl;
+ state = state->prev;
+ }
+}
+
+void define_label(const char *label, const place *pl)
+{
+ place *p = new place[1];
+ *p = *pl;
+ current_table->define(label, p);
+}
+
+int lookup_variable(const char *name, double *val)
+{
+ place *pl = lookup_label(name);
+ if (pl) {
+ *val = pl->x;
+ return 1;
+ }
+ return 0;
+}
+
+void define_variable(const char *name, double val)
+{
+ place *p = new place[1];
+ p->obj = 0;
+ p->x = val;
+ p->y = 0.0;
+ current_table->define(name, p);
+ if (strcmp(name, "scale") == 0) {
+ // When the scale changes, reset all scaled predefined variables to
+ // their default values.
+ for (unsigned int i = 0;
+ i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
+ if (defaults_table[i].scaled)
+ define_variable(defaults_table[i].name, val*defaults_table[i].val);
+ }
+}
+
+// called once only (not once per parse)
+
+void parse_init()
+{
+ current_direction = RIGHT_DIRECTION;
+ current_position.x = 0.0;
+ current_position.y = 0.0;
+ // This resets everything to its default value.
+ reset_all();
+}
+
+void reset(const char *nm)
+{
+ for (unsigned int i = 0;
+ i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
+ if (strcmp(nm, defaults_table[i].name) == 0) {
+ double val = defaults_table[i].val;
+ if (defaults_table[i].scaled) {
+ double scale;
+ lookup_variable("scale", &scale);
+ val *= scale;
+ }
+ define_variable(defaults_table[i].name, val);
+ return;
+ }
+ lex_error("'%1' is not a predefined variable", nm);
+}
+
+void reset_all()
+{
+ // We only have to explicitly reset the predefined variables that
+ // aren't scaled because 'scale' is not scaled, and changing the
+ // value of 'scale' will reset all the predefined variables that
+ // are scaled.
+ for (unsigned int i = 0;
+ i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
+ if (!defaults_table[i].scaled)
+ define_variable(defaults_table[i].name, defaults_table[i].val);
+}
+
+// called after each parse
+
+void parse_cleanup()
+{
+ while (current_saved_state != 0) {
+ delete current_table;
+ current_table = current_saved_state->tbl;
+ saved_state *tem = current_saved_state;
+ current_saved_state = current_saved_state->prev;
+ delete tem;
+ }
+ assert(current_table == &top_table);
+ PTABLE_ITERATOR(place) iter(current_table);
+ const char *key;
+ place *pl;
+ while (iter.next(&key, &pl))
+ if (pl->obj != 0) {
+ position pos = pl->obj->origin();
+ pl->obj = 0;
+ pl->x = pos.x;
+ pl->y = pos.y;
+ }
+ while (olist.head != 0) {
+ object *tem = olist.head;
+ olist.head = olist.head->next;
+ delete tem;
+ }
+ olist.tail = 0;
+ current_direction = RIGHT_DIRECTION;
+ current_position.x = 0.0;
+ current_position.y = 0.0;
+}
+
+const char *ordinal_postfix(int n)
+{
+ if (n < 10 || n > 20)
+ switch (n % 10) {
+ case 1:
+ return "st";
+ case 2:
+ return "nd";
+ case 3:
+ return "rd";
+ }
+ return "th";
+}
+
+const char *object_type_name(object_type type)
+{
+ switch (type) {
+ case BOX_OBJECT:
+ return "box";
+ case CIRCLE_OBJECT:
+ return "circle";
+ case ELLIPSE_OBJECT:
+ return "ellipse";
+ case ARC_OBJECT:
+ return "arc";
+ case SPLINE_OBJECT:
+ return "spline";
+ case LINE_OBJECT:
+ return "line";
+ case ARROW_OBJECT:
+ return "arrow";
+ case MOVE_OBJECT:
+ return "move";
+ case TEXT_OBJECT:
+ return "\"\"";
+ case BLOCK_OBJECT:
+ return "[]";
+ case OTHER_OBJECT:
+ case MARK_OBJECT:
+ default:
+ break;
+ }
+ return "object";
+}
+
+static char sprintf_buf[1024];
+
+char *format_number(const char *fmt, double n)
+{
+ if (0 /* nullptr */ == fmt)
+ fmt = "%g";
+ return do_sprintf(fmt, &n, 1);
+}
+
+char *do_sprintf(const char *fmt, const double *v, int nv)
+{
+ // Define valid conversion specifiers and modifiers.
+ static const char spcs[] = "eEfgG%";
+ static const char mods[] = "#-+ 0123456789.";
+ string result;
+ int i = 0;
+ string one_format;
+ while (*fmt) {
+ if ('%' == *fmt) {
+ one_format += *fmt++;
+ for (; *fmt != '\0' && strchr(mods, *fmt) != 0; fmt++)
+ one_format += *fmt;
+ if ('\0' == *fmt || strchr(spcs, *fmt) == 0) {
+ lex_error("invalid sprintf conversion specifier '%1'", *fmt);
+ result += one_format;
+ result += fmt;
+ break;
+ }
+ if ('%' == *fmt) {
+ fmt++;
+ snprintf(sprintf_buf, sizeof(sprintf_buf), "%%");
+ }
+ else {
+ if (i >= nv) {
+ lex_error("too few arguments to sprintf");
+ result += one_format;
+ result += fmt;
+ break;
+ }
+ one_format += *fmt++;
+ one_format += '\0';
+// We validated the format string above. Most conversion specifiers are
+// rejected, including `n`.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ snprintf(sprintf_buf, sizeof(sprintf_buf),
+ one_format.contents(), v[i++]);
+#pragma GCC diagnostic pop
+ }
+ one_format.clear();
+ result += sprintf_buf;
+ }
+ else
+ result += *fmt++;
+ }
+ result += '\0';
+ return strsave(result.contents());
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/pic/pic.h b/src/preproc/pic/pic.h
new file mode 100644
index 0000000..1a99498
--- /dev/null
+++ b/src/preproc/pic/pic.h
@@ -0,0 +1,122 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <math.h>
+#include <stdlib.h>
+
+#ifdef NEED_DECLARATION_RAND
+#undef rand
+extern "C" {
+ int rand();
+}
+#endif /* NEED_DECLARATION_RAND */
+
+#ifdef NEED_DECLARATION_SRAND
+#undef srand
+extern "C" {
+#ifdef RET_TYPE_SRAND_IS_VOID
+ void srand(unsigned int);
+#else
+ int srand(unsigned int);
+#endif
+}
+#endif /* NEED_DECLARATION_SRAND */
+
+#ifndef HAVE_FMOD
+extern "C" {
+ double fmod(double, double);
+}
+#endif
+
+#include "cset.h"
+#include "stringclass.h"
+#include "lf.h"
+#include "errarg.h"
+#include "error.h"
+#include "position.h"
+#include "text.h"
+#include "output.h"
+
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880
+#endif
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+class input {
+ input *next;
+public:
+ input();
+ virtual ~input();
+ virtual int get() = 0;
+ virtual int peek() = 0;
+ virtual int get_location(const char **, int *);
+ friend class input_stack;
+ friend class copy_rest_thru_input;
+};
+
+class file_input : public input {
+ FILE *fp;
+ const char *filename;
+ int lineno;
+ string line;
+ const char *ptr;
+ int read_line();
+public:
+ file_input(FILE *, const char *);
+ ~file_input();
+ int get();
+ int peek();
+ int get_location(const char **, int *);
+};
+
+void lex_init(input *);
+int get_location(char **, int *);
+
+void do_copy(const char *file);
+void parse_init();
+void parse_cleanup();
+
+void lex_error(const char *message,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+
+void lex_warning(const char *message,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+
+void lex_cleanup();
+
+extern bool want_flyback;
+extern bool want_alternate_flyback;
+extern int command_char;
+// zero_length_line_flag is non-zero if zero-length lines are drawn
+// as dots by the output device
+extern int zero_length_line_flag;
+extern int driver_extension_flag;
+extern int compatible_flag;
+extern int safer_flag;
+extern char *graphname;
diff --git a/src/preproc/pic/pic.hpp b/src/preproc/pic/pic.hpp
new file mode 100644
index 0000000..c162496
--- /dev/null
+++ b/src/preproc/pic/pic.hpp
@@ -0,0 +1,348 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison interface for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+#ifndef YY_YY_SRC_PREPROC_PIC_PIC_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_PIC_PIC_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ LABEL = 258, /* LABEL */
+ VARIABLE = 259, /* VARIABLE */
+ NUMBER = 260, /* NUMBER */
+ TEXT = 261, /* TEXT */
+ COMMAND_LINE = 262, /* COMMAND_LINE */
+ DELIMITED = 263, /* DELIMITED */
+ ORDINAL = 264, /* ORDINAL */
+ TH = 265, /* TH */
+ LEFT_ARROW_HEAD = 266, /* LEFT_ARROW_HEAD */
+ RIGHT_ARROW_HEAD = 267, /* RIGHT_ARROW_HEAD */
+ DOUBLE_ARROW_HEAD = 268, /* DOUBLE_ARROW_HEAD */
+ LAST = 269, /* LAST */
+ BOX = 270, /* BOX */
+ CIRCLE = 271, /* CIRCLE */
+ ELLIPSE = 272, /* ELLIPSE */
+ ARC = 273, /* ARC */
+ LINE = 274, /* LINE */
+ ARROW = 275, /* ARROW */
+ MOVE = 276, /* MOVE */
+ SPLINE = 277, /* SPLINE */
+ HEIGHT = 278, /* HEIGHT */
+ RADIUS = 279, /* RADIUS */
+ FIGNAME = 280, /* FIGNAME */
+ WIDTH = 281, /* WIDTH */
+ DIAMETER = 282, /* DIAMETER */
+ UP = 283, /* UP */
+ DOWN = 284, /* DOWN */
+ RIGHT = 285, /* RIGHT */
+ LEFT = 286, /* LEFT */
+ FROM = 287, /* FROM */
+ TO = 288, /* TO */
+ AT = 289, /* AT */
+ WITH = 290, /* WITH */
+ BY = 291, /* BY */
+ THEN = 292, /* THEN */
+ SOLID = 293, /* SOLID */
+ DOTTED = 294, /* DOTTED */
+ DASHED = 295, /* DASHED */
+ CHOP = 296, /* CHOP */
+ SAME = 297, /* SAME */
+ INVISIBLE = 298, /* INVISIBLE */
+ LJUST = 299, /* LJUST */
+ RJUST = 300, /* RJUST */
+ ABOVE = 301, /* ABOVE */
+ BELOW = 302, /* BELOW */
+ OF = 303, /* OF */
+ THE = 304, /* THE */
+ WAY = 305, /* WAY */
+ BETWEEN = 306, /* BETWEEN */
+ AND = 307, /* AND */
+ HERE = 308, /* HERE */
+ DOT_N = 309, /* DOT_N */
+ DOT_E = 310, /* DOT_E */
+ DOT_W = 311, /* DOT_W */
+ DOT_S = 312, /* DOT_S */
+ DOT_NE = 313, /* DOT_NE */
+ DOT_SE = 314, /* DOT_SE */
+ DOT_NW = 315, /* DOT_NW */
+ DOT_SW = 316, /* DOT_SW */
+ DOT_C = 317, /* DOT_C */
+ DOT_START = 318, /* DOT_START */
+ DOT_END = 319, /* DOT_END */
+ DOT_X = 320, /* DOT_X */
+ DOT_Y = 321, /* DOT_Y */
+ DOT_HT = 322, /* DOT_HT */
+ DOT_WID = 323, /* DOT_WID */
+ DOT_RAD = 324, /* DOT_RAD */
+ SIN = 325, /* SIN */
+ COS = 326, /* COS */
+ ATAN2 = 327, /* ATAN2 */
+ LOG = 328, /* LOG */
+ EXP = 329, /* EXP */
+ SQRT = 330, /* SQRT */
+ K_MAX = 331, /* K_MAX */
+ K_MIN = 332, /* K_MIN */
+ INT = 333, /* INT */
+ RAND = 334, /* RAND */
+ SRAND = 335, /* SRAND */
+ COPY = 336, /* COPY */
+ THRU = 337, /* THRU */
+ TOP = 338, /* TOP */
+ BOTTOM = 339, /* BOTTOM */
+ UPPER = 340, /* UPPER */
+ LOWER = 341, /* LOWER */
+ SH = 342, /* SH */
+ PRINT = 343, /* PRINT */
+ CW = 344, /* CW */
+ CCW = 345, /* CCW */
+ FOR = 346, /* FOR */
+ DO = 347, /* DO */
+ IF = 348, /* IF */
+ ELSE = 349, /* ELSE */
+ ANDAND = 350, /* ANDAND */
+ OROR = 351, /* OROR */
+ NOTEQUAL = 352, /* NOTEQUAL */
+ EQUALEQUAL = 353, /* EQUALEQUAL */
+ LESSEQUAL = 354, /* LESSEQUAL */
+ GREATEREQUAL = 355, /* GREATEREQUAL */
+ LEFT_CORNER = 356, /* LEFT_CORNER */
+ RIGHT_CORNER = 357, /* RIGHT_CORNER */
+ NORTH = 358, /* NORTH */
+ SOUTH = 359, /* SOUTH */
+ EAST = 360, /* EAST */
+ WEST = 361, /* WEST */
+ CENTER = 362, /* CENTER */
+ END = 363, /* END */
+ START = 364, /* START */
+ RESET = 365, /* RESET */
+ UNTIL = 366, /* UNTIL */
+ PLOT = 367, /* PLOT */
+ THICKNESS = 368, /* THICKNESS */
+ FILL = 369, /* FILL */
+ COLORED = 370, /* COLORED */
+ OUTLINED = 371, /* OUTLINED */
+ SHADED = 372, /* SHADED */
+ XSLANTED = 373, /* XSLANTED */
+ YSLANTED = 374, /* YSLANTED */
+ ALIGNED = 375, /* ALIGNED */
+ SPRINTF = 376, /* SPRINTF */
+ COMMAND = 377, /* COMMAND */
+ DEFINE = 378, /* DEFINE */
+ UNDEF = 379 /* UNDEF */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define LABEL 258
+#define VARIABLE 259
+#define NUMBER 260
+#define TEXT 261
+#define COMMAND_LINE 262
+#define DELIMITED 263
+#define ORDINAL 264
+#define TH 265
+#define LEFT_ARROW_HEAD 266
+#define RIGHT_ARROW_HEAD 267
+#define DOUBLE_ARROW_HEAD 268
+#define LAST 269
+#define BOX 270
+#define CIRCLE 271
+#define ELLIPSE 272
+#define ARC 273
+#define LINE 274
+#define ARROW 275
+#define MOVE 276
+#define SPLINE 277
+#define HEIGHT 278
+#define RADIUS 279
+#define FIGNAME 280
+#define WIDTH 281
+#define DIAMETER 282
+#define UP 283
+#define DOWN 284
+#define RIGHT 285
+#define LEFT 286
+#define FROM 287
+#define TO 288
+#define AT 289
+#define WITH 290
+#define BY 291
+#define THEN 292
+#define SOLID 293
+#define DOTTED 294
+#define DASHED 295
+#define CHOP 296
+#define SAME 297
+#define INVISIBLE 298
+#define LJUST 299
+#define RJUST 300
+#define ABOVE 301
+#define BELOW 302
+#define OF 303
+#define THE 304
+#define WAY 305
+#define BETWEEN 306
+#define AND 307
+#define HERE 308
+#define DOT_N 309
+#define DOT_E 310
+#define DOT_W 311
+#define DOT_S 312
+#define DOT_NE 313
+#define DOT_SE 314
+#define DOT_NW 315
+#define DOT_SW 316
+#define DOT_C 317
+#define DOT_START 318
+#define DOT_END 319
+#define DOT_X 320
+#define DOT_Y 321
+#define DOT_HT 322
+#define DOT_WID 323
+#define DOT_RAD 324
+#define SIN 325
+#define COS 326
+#define ATAN2 327
+#define LOG 328
+#define EXP 329
+#define SQRT 330
+#define K_MAX 331
+#define K_MIN 332
+#define INT 333
+#define RAND 334
+#define SRAND 335
+#define COPY 336
+#define THRU 337
+#define TOP 338
+#define BOTTOM 339
+#define UPPER 340
+#define LOWER 341
+#define SH 342
+#define PRINT 343
+#define CW 344
+#define CCW 345
+#define FOR 346
+#define DO 347
+#define IF 348
+#define ELSE 349
+#define ANDAND 350
+#define OROR 351
+#define NOTEQUAL 352
+#define EQUALEQUAL 353
+#define LESSEQUAL 354
+#define GREATEREQUAL 355
+#define LEFT_CORNER 356
+#define RIGHT_CORNER 357
+#define NORTH 358
+#define SOUTH 359
+#define EAST 360
+#define WEST 361
+#define CENTER 362
+#define END 363
+#define START 364
+#define RESET 365
+#define UNTIL 366
+#define PLOT 367
+#define THICKNESS 368
+#define FILL 369
+#define COLORED 370
+#define OUTLINED 371
+#define SHADED 372
+#define XSLANTED 373
+#define YSLANTED 374
+#define ALIGNED 375
+#define SPRINTF 376
+#define COMMAND 377
+#define DEFINE 378
+#define UNDEF 379
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 65 "../src/preproc/pic/pic.ypp"
+
+ char *str;
+ int n;
+ double x;
+ struct { double x, y; } pair;
+ struct { double x; char *body; } if_data;
+ struct { char *str; const char *filename; int lineno; } lstr;
+ struct { double *v; int nv; int maxv; } dv;
+ struct { double val; int is_multiplicative; } by;
+ place pl;
+ object *obj;
+ corner crn;
+ path *pth;
+ object_spec *spec;
+ saved_state *pstate;
+ graphics_state state;
+ object_type obtype;
+
+#line 334 "src/preproc/pic/pic.hpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_PIC_PIC_HPP_INCLUDED */
diff --git a/src/preproc/pic/pic.ypp b/src/preproc/pic/pic.ypp
new file mode 100644
index 0000000..b2fa6dc
--- /dev/null
+++ b/src/preproc/pic/pic.ypp
@@ -0,0 +1,1957 @@
+/* Copyright (C) 1989-2022 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+%{
+#include "pic.h"
+#include "ptable.h"
+#include "object.h"
+
+extern int delim_flag;
+extern void copy_rest_thru(const char *, const char *);
+extern void copy_file_thru(const char *, const char *, const char *);
+extern void push_body(const char *);
+extern void do_for(char *var, double from, double to,
+ int by_is_multiplicative, double by, char *body);
+extern void do_lookahead();
+
+/* Maximum number of characters produced by printf("%g") */
+#define GDIGITS 14
+
+int yylex();
+void yyerror(const char *);
+
+void reset(const char *nm);
+void reset_all();
+
+place *lookup_label(const char *);
+void define_label(const char *label, const place *pl);
+
+direction current_direction;
+position current_position;
+
+implement_ptable(place)
+
+PTABLE(place) top_table;
+
+PTABLE(place) *current_table = &top_table;
+saved_state *current_saved_state = 0;
+
+object_list olist;
+
+const char *ordinal_postfix(int n);
+const char *object_type_name(object_type type);
+char *format_number(const char *fmt, double n);
+char *do_sprintf(const char *fmt, const double *v, int nv);
+
+%}
+
+%expect 2
+
+%union {
+ char *str;
+ int n;
+ double x;
+ struct { double x, y; } pair;
+ struct { double x; char *body; } if_data;
+ struct { char *str; const char *filename; int lineno; } lstr;
+ struct { double *v; int nv; int maxv; } dv;
+ struct { double val; int is_multiplicative; } by;
+ place pl;
+ object *obj;
+ corner crn;
+ path *pth;
+ object_spec *spec;
+ saved_state *pstate;
+ graphics_state state;
+ object_type obtype;
+}
+
+%token <str> LABEL
+%token <str> VARIABLE
+%token <x> NUMBER
+%token <lstr> TEXT
+%token <lstr> COMMAND_LINE
+%token <str> DELIMITED
+%token <n> ORDINAL
+%token TH
+%token LEFT_ARROW_HEAD
+%token RIGHT_ARROW_HEAD
+%token DOUBLE_ARROW_HEAD
+%token LAST
+%token BOX
+%token CIRCLE
+%token ELLIPSE
+%token ARC
+%token LINE
+%token ARROW
+%token MOVE
+%token SPLINE
+%token HEIGHT
+%token RADIUS
+%token FIGNAME
+%token WIDTH
+%token DIAMETER
+%token UP
+%token DOWN
+%token RIGHT
+%token LEFT
+%token FROM
+%token TO
+%token AT
+%token WITH
+%token BY
+%token THEN
+%token SOLID
+%token DOTTED
+%token DASHED
+%token CHOP
+%token SAME
+%token INVISIBLE
+%token LJUST
+%token RJUST
+%token ABOVE
+%token BELOW
+%token OF
+%token THE
+%token WAY
+%token BETWEEN
+%token AND
+%token HERE
+%token DOT_N
+%token DOT_E
+%token DOT_W
+%token DOT_S
+%token DOT_NE
+%token DOT_SE
+%token DOT_NW
+%token DOT_SW
+%token DOT_C
+%token DOT_START
+%token DOT_END
+%token DOT_X
+%token DOT_Y
+%token DOT_HT
+%token DOT_WID
+%token DOT_RAD
+%token SIN
+%token COS
+%token ATAN2
+%token LOG
+%token EXP
+%token SQRT
+%token K_MAX
+%token K_MIN
+%token INT
+%token RAND
+%token SRAND
+%token COPY
+%token THRU
+%token TOP
+%token BOTTOM
+%token UPPER
+%token LOWER
+%token SH
+%token PRINT
+%token CW
+%token CCW
+%token FOR
+%token DO
+%token IF
+%token ELSE
+%token ANDAND
+%token OROR
+%token NOTEQUAL
+%token EQUALEQUAL
+%token LESSEQUAL
+%token GREATEREQUAL
+%token LEFT_CORNER
+%token RIGHT_CORNER
+%token NORTH
+%token SOUTH
+%token EAST
+%token WEST
+%token CENTER
+%token END
+%token START
+%token RESET
+%token UNTIL
+%token PLOT
+%token THICKNESS
+%token FILL
+%token COLORED
+%token OUTLINED
+%token SHADED
+%token XSLANTED
+%token YSLANTED
+%token ALIGNED
+%token SPRINTF
+%token COMMAND
+
+%token DEFINE
+%token UNDEF
+
+%left '.'
+
+/* this ensures that plot 17 "%g" parses as (plot 17 "%g") */
+%left PLOT
+%left TEXT SPRINTF
+
+/* give text adjustments higher precedence than TEXT, so that
+box "foo" above ljust == box ("foo" above ljust)
+*/
+
+%left LJUST RJUST ABOVE BELOW
+
+%left LEFT RIGHT
+/* Give attributes that take an optional expression a higher
+precedence than left and right, so that, e.g., 'line chop left'
+parses properly. */
+%left CHOP SOLID DASHED DOTTED UP DOWN FILL COLORED OUTLINED
+%left XSLANTED YSLANTED
+%left LABEL
+
+%left VARIABLE NUMBER '(' SIN COS ATAN2 LOG EXP SQRT K_MAX K_MIN INT RAND SRAND LAST
+%left ORDINAL HERE '`'
+
+%left BOX CIRCLE ELLIPSE ARC LINE ARROW SPLINE '['
+
+/* these need to be lower than '-' */
+%left HEIGHT RADIUS WIDTH DIAMETER FROM TO AT THICKNESS
+
+/* these must have higher precedence than CHOP so that 'label %prec CHOP'
+works */
+%left DOT_N DOT_E DOT_W DOT_S DOT_NE DOT_SE DOT_NW DOT_SW DOT_C
+%left DOT_START DOT_END TOP BOTTOM LEFT_CORNER RIGHT_CORNER
+%left UPPER LOWER NORTH SOUTH EAST WEST CENTER START END
+
+%left ','
+%left OROR
+%left ANDAND
+%left EQUALEQUAL NOTEQUAL
+%left '<' '>' LESSEQUAL GREATEREQUAL
+
+%left BETWEEN OF
+%left AND
+
+%left '+' '-'
+%left '*' '/' '%'
+%right '!'
+%right '^'
+
+%type <x> expr expr_lower_than expr_not_lower_than any_expr text_expr
+%type <by> optional_by
+%type <pair> expr_pair position_not_place
+%type <if_data> simple_if
+%type <obj> nth_primitive
+%type <crn> corner
+%type <pth> path label_path relative_path
+%type <pl> place label element element_list middle_element_list
+%type <spec> object_spec
+%type <pair> position
+%type <obtype> object_type
+%type <n> optional_ordinal_last ordinal
+%type <str> macro_name until
+%type <dv> sprintf_args
+%type <lstr> text print_args print_arg
+
+%%
+
+top:
+ optional_separator
+ | element_list
+ {
+ if (olist.head)
+ print_picture(olist.head);
+ }
+ ;
+
+
+element_list:
+ optional_separator middle_element_list optional_separator
+ { $$ = $2; }
+ ;
+
+middle_element_list:
+ element
+ { $$ = $1; }
+ | middle_element_list separator element
+ { $$ = $1; }
+ ;
+
+optional_separator:
+ /* empty */
+ | separator
+ ;
+
+separator:
+ ';'
+ | separator ';'
+ ;
+
+placeless_element:
+ FIGNAME '=' macro_name
+ {
+ delete[] graphname;
+ graphname = new char[strlen($3) + 1];
+ strcpy(graphname, $3);
+ delete[] $3;
+ }
+ |
+ VARIABLE '=' any_expr
+ {
+ define_variable($1, $3);
+ free($1);
+ }
+ | VARIABLE ':' '=' any_expr
+ {
+ place *p = lookup_label($1);
+ if (!p) {
+ lex_error("variable '%1' not defined", $1);
+ YYABORT;
+ }
+ p->obj = 0;
+ p->x = $4;
+ p->y = 0.0;
+ free($1);
+ }
+ | UP
+ { current_direction = UP_DIRECTION; }
+ | DOWN
+ { current_direction = DOWN_DIRECTION; }
+ | LEFT
+ { current_direction = LEFT_DIRECTION; }
+ | RIGHT
+ { current_direction = RIGHT_DIRECTION; }
+ | COMMAND_LINE
+ {
+ olist.append(make_command_object($1.str, $1.filename,
+ $1.lineno));
+ }
+ | COMMAND print_args
+ {
+ olist.append(make_command_object($2.str, $2.filename,
+ $2.lineno));
+ }
+ | PRINT print_args
+ {
+ fprintf(stderr, "%s\n", $2.str);
+ delete[] $2.str;
+ fflush(stderr);
+ }
+ | SH
+ { delim_flag = 1; }
+ DELIMITED
+ {
+ delim_flag = 0;
+ if (safer_flag)
+ lex_error("unsafe to run command '%1'; ignoring",
+ $3);
+ else {
+ int retval = system($3);
+ if (retval < 0)
+ lex_error("error running command '%1': system()"
+ " returned %2", $3, retval);
+ }
+ delete[] $3;
+ }
+ | COPY TEXT
+ {
+ if (yychar < 0)
+ do_lookahead();
+ do_copy($2.str);
+ // do not delete the filename
+ }
+ | COPY TEXT THRU
+ { delim_flag = 2; }
+ DELIMITED
+ { delim_flag = 0; }
+ until
+ {
+ if (yychar < 0)
+ do_lookahead();
+ copy_file_thru($2.str, $5, $7);
+ // do not delete the filename
+ delete[] $5;
+ delete[] $7;
+ }
+ | COPY THRU
+ { delim_flag = 2; }
+ DELIMITED
+ { delim_flag = 0; }
+ until
+ {
+ if (yychar < 0)
+ do_lookahead();
+ copy_rest_thru($4, $6);
+ delete[] $4;
+ delete[] $6;
+ }
+ | FOR VARIABLE '=' expr TO expr optional_by DO
+ { delim_flag = 1; }
+ DELIMITED
+ {
+ delim_flag = 0;
+ if (yychar < 0)
+ do_lookahead();
+ do_for($2, $4, $6, $7.is_multiplicative, $7.val, $10);
+ }
+ | simple_if
+ {
+ if (yychar < 0)
+ do_lookahead();
+ if ($1.x != 0.0)
+ push_body($1.body);
+ delete[] $1.body;
+ }
+ | simple_if ELSE
+ { delim_flag = 1; }
+ DELIMITED
+ {
+ delim_flag = 0;
+ if (yychar < 0)
+ do_lookahead();
+ if ($1.x != 0.0)
+ push_body($1.body);
+ else
+ push_body($4);
+ free($1.body);
+ free($4);
+ }
+ | reset_variables
+ | RESET
+ { define_variable("scale", 1.0); }
+ ;
+
+macro_name:
+ VARIABLE
+ | LABEL
+ ;
+
+reset_variables:
+ RESET VARIABLE
+ {
+ reset($2);
+ delete[] $2;
+ }
+ | reset_variables VARIABLE
+ {
+ reset($2);
+ delete[] $2;
+ }
+ | reset_variables ',' VARIABLE
+ {
+ reset($3);
+ delete[] $3;
+ }
+ ;
+
+print_args:
+ print_arg
+ { $$ = $1; }
+ | print_args print_arg
+ {
+ $$.str = new char[strlen($1.str) + strlen($2.str) + 1];
+ strcpy($$.str, $1.str);
+ strcat($$.str, $2.str);
+ delete[] $1.str;
+ delete[] $2.str;
+ if ($1.filename) {
+ $$.filename = $1.filename;
+ $$.lineno = $1.lineno;
+ }
+ else if ($2.filename) {
+ $$.filename = $2.filename;
+ $$.lineno = $2.lineno;
+ }
+ }
+ ;
+
+print_arg:
+ expr %prec ','
+ {
+ $$.str = new char[GDIGITS + 1];
+ sprintf($$.str, "%g", $1);
+ $$.filename = 0;
+ $$.lineno = 0;
+ }
+ | text
+ { $$ = $1; }
+ | position %prec ','
+ {
+ $$.str = new char[GDIGITS + 2 + GDIGITS + 1];
+ sprintf($$.str, "%g, %g", $1.x, $1.y);
+ $$.filename = 0;
+ $$.lineno = 0;
+ }
+ ;
+
+simple_if:
+ IF any_expr THEN
+ { delim_flag = 1; }
+ DELIMITED
+ {
+ delim_flag = 0;
+ $$.x = $2;
+ $$.body = $5;
+ }
+ ;
+
+until:
+ /* empty */
+ { $$ = 0; }
+ | UNTIL TEXT
+ { $$ = $2.str; }
+ ;
+
+any_expr:
+ expr
+ { $$ = $1; }
+ | text_expr
+ { $$ = $1; }
+ ;
+
+text_expr:
+ text EQUALEQUAL text
+ {
+ $$ = strcmp($1.str, $3.str) == 0;
+ delete[] $1.str;
+ delete[] $3.str;
+ }
+ | text NOTEQUAL text
+ {
+ $$ = strcmp($1.str, $3.str) != 0;
+ delete[] $1.str;
+ delete[] $3.str;
+ }
+ | text_expr ANDAND text_expr
+ { $$ = ($1 != 0.0 && $3 != 0.0); }
+ | text_expr ANDAND expr
+ { $$ = ($1 != 0.0 && $3 != 0.0); }
+ | expr ANDAND text_expr
+ { $$ = ($1 != 0.0 && $3 != 0.0); }
+ | text_expr OROR text_expr
+ { $$ = ($1 != 0.0 || $3 != 0.0); }
+ | text_expr OROR expr
+ { $$ = ($1 != 0.0 || $3 != 0.0); }
+ | expr OROR text_expr
+ { $$ = ($1 != 0.0 || $3 != 0.0); }
+ | '!' text_expr
+ { $$ = ($2 == 0.0); }
+ ;
+
+
+optional_by:
+ /* empty */
+ {
+ $$.val = 1.0;
+ $$.is_multiplicative = 0;
+ }
+ | BY expr
+ {
+ $$.val = $2;
+ $$.is_multiplicative = 0;
+ }
+ | BY '*' expr
+ {
+ $$.val = $3;
+ $$.is_multiplicative = 1;
+ }
+ ;
+
+element:
+ object_spec
+ {
+ $$.obj = $1->make_object(&current_position,
+ &current_direction);
+ if ($$.obj == 0)
+ YYABORT;
+ delete $1;
+ if ($$.obj)
+ olist.append($$.obj);
+ else {
+ $$.x = current_position.x;
+ $$.y = current_position.y;
+ }
+ }
+ | LABEL ':' optional_separator element
+ {
+ $$ = $4;
+ define_label($1, & $$);
+ free($1);
+ }
+ | LABEL ':' optional_separator position_not_place
+ {
+ $$.obj = 0;
+ $$.x = $4.x;
+ $$.y = $4.y;
+ define_label($1, & $$);
+ free($1);
+ }
+ | LABEL ':' optional_separator place
+ {
+ $$ = $4;
+ define_label($1, & $$);
+ free($1);
+ }
+ | '{'
+ {
+ $<state>$.x = current_position.x;
+ $<state>$.y = current_position.y;
+ $<state>$.dir = current_direction;
+ }
+ element_list '}'
+ {
+ current_position.x = $<state>2.x;
+ current_position.y = $<state>2.y;
+ current_direction = $<state>2.dir;
+ }
+ optional_element
+ {
+ $$ = $3;
+ }
+ | placeless_element
+ {
+ $$.obj = 0;
+ $$.x = current_position.x;
+ $$.y = current_position.y;
+ }
+ ;
+
+optional_element:
+ /* empty */
+ {}
+ | element
+ {}
+ ;
+
+object_spec:
+ BOX
+ { $$ = new object_spec(BOX_OBJECT); }
+ | CIRCLE
+ { $$ = new object_spec(CIRCLE_OBJECT); }
+ | ELLIPSE
+ { $$ = new object_spec(ELLIPSE_OBJECT); }
+ | ARC
+ {
+ $$ = new object_spec(ARC_OBJECT);
+ $$->dir = current_direction;
+ }
+ | LINE
+ {
+ $$ = new object_spec(LINE_OBJECT);
+ lookup_variable("lineht", & $$->segment_height);
+ lookup_variable("linewid", & $$->segment_width);
+ $$->dir = current_direction;
+ }
+ | ARROW
+ {
+ $$ = new object_spec(ARROW_OBJECT);
+ lookup_variable("lineht", & $$->segment_height);
+ lookup_variable("linewid", & $$->segment_width);
+ $$->dir = current_direction;
+ }
+ | MOVE
+ {
+ $$ = new object_spec(MOVE_OBJECT);
+ lookup_variable("moveht", & $$->segment_height);
+ lookup_variable("movewid", & $$->segment_width);
+ $$->dir = current_direction;
+ }
+ | SPLINE
+ {
+ $$ = new object_spec(SPLINE_OBJECT);
+ lookup_variable("lineht", & $$->segment_height);
+ lookup_variable("linewid", & $$->segment_width);
+ $$->dir = current_direction;
+ }
+ | text %prec TEXT
+ {
+ $$ = new object_spec(TEXT_OBJECT);
+ $$->text = new text_item($1.str, $1.filename, $1.lineno);
+ }
+ | PLOT expr
+ {
+ lex_warning("'plot' is deprecated; use 'sprintf'"
+ " instead");
+ $$ = new object_spec(TEXT_OBJECT);
+ $$->text = new text_item(format_number(0, $2), 0, -1);
+ }
+ | PLOT expr text
+ {
+ $$ = new object_spec(TEXT_OBJECT);
+ $$->text = new text_item(format_number($3.str, $2),
+ $3.filename, $3.lineno);
+ delete[] $3.str;
+ }
+ | '['
+ {
+ saved_state *p = new saved_state;
+ $<pstate>$ = p;
+ p->x = current_position.x;
+ p->y = current_position.y;
+ p->dir = current_direction;
+ p->tbl = current_table;
+ p->prev = current_saved_state;
+ current_position.x = 0.0;
+ current_position.y = 0.0;
+ current_table = new PTABLE(place);
+ current_saved_state = p;
+ olist.append(make_mark_object());
+ }
+ element_list ']'
+ {
+ current_position.x = $<pstate>2->x;
+ current_position.y = $<pstate>2->y;
+ current_direction = $<pstate>2->dir;
+ $$ = new object_spec(BLOCK_OBJECT);
+ olist.wrap_up_block(& $$->oblist);
+ $$->tbl = current_table;
+ current_table = $<pstate>2->tbl;
+ current_saved_state = $<pstate>2->prev;
+ delete $<pstate>2;
+ }
+ | object_spec HEIGHT expr
+ {
+ $$ = $1;
+ $$->height = $3;
+ $$->flags |= HAS_HEIGHT;
+ }
+ | object_spec RADIUS expr
+ {
+ $$ = $1;
+ $$->radius = $3;
+ $$->flags |= HAS_RADIUS;
+ }
+ | object_spec WIDTH expr
+ {
+ $$ = $1;
+ $$->width = $3;
+ $$->flags |= HAS_WIDTH;
+ }
+ | object_spec DIAMETER expr
+ {
+ $$ = $1;
+ $$->radius = $3/2.0;
+ $$->flags |= HAS_RADIUS;
+ }
+ | object_spec expr %prec HEIGHT
+ {
+ $$ = $1;
+ $$->flags |= HAS_SEGMENT;
+ switch ($$->dir) {
+ case UP_DIRECTION:
+ $$->segment_pos.y += $2;
+ break;
+ case DOWN_DIRECTION:
+ $$->segment_pos.y -= $2;
+ break;
+ case RIGHT_DIRECTION:
+ $$->segment_pos.x += $2;
+ break;
+ case LEFT_DIRECTION:
+ $$->segment_pos.x -= $2;
+ break;
+ }
+ }
+ | object_spec UP
+ {
+ $$ = $1;
+ $$->dir = UP_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.y += $$->segment_height;
+ }
+ | object_spec UP expr
+ {
+ $$ = $1;
+ $$->dir = UP_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.y += $3;
+ }
+ | object_spec DOWN
+ {
+ $$ = $1;
+ $$->dir = DOWN_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.y -= $$->segment_height;
+ }
+ | object_spec DOWN expr
+ {
+ $$ = $1;
+ $$->dir = DOWN_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.y -= $3;
+ }
+ | object_spec RIGHT
+ {
+ $$ = $1;
+ $$->dir = RIGHT_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.x += $$->segment_width;
+ }
+ | object_spec RIGHT expr
+ {
+ $$ = $1;
+ $$->dir = RIGHT_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.x += $3;
+ }
+ | object_spec LEFT
+ {
+ $$ = $1;
+ $$->dir = LEFT_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.x -= $$->segment_width;
+ }
+ | object_spec LEFT expr
+ {
+ $$ = $1;
+ $$->dir = LEFT_DIRECTION;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.x -= $3;
+ }
+ | object_spec FROM position
+ {
+ $$ = $1;
+ $$->flags |= HAS_FROM;
+ $$->from.x = $3.x;
+ $$->from.y = $3.y;
+ }
+ | object_spec TO position
+ {
+ $$ = $1;
+ if ($$->flags & HAS_SEGMENT)
+ $$->segment_list = new segment($$->segment_pos,
+ $$->segment_is_absolute,
+ $$->segment_list);
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.x = $3.x;
+ $$->segment_pos.y = $3.y;
+ $$->segment_is_absolute = 1;
+ $$->flags |= HAS_TO;
+ $$->to.x = $3.x;
+ $$->to.y = $3.y;
+ }
+ | object_spec AT position
+ {
+ $$ = $1;
+ $$->flags |= HAS_AT;
+ $$->at.x = $3.x;
+ $$->at.y = $3.y;
+ if ($$->type != ARC_OBJECT) {
+ $$->flags |= HAS_FROM;
+ $$->from.x = $3.x;
+ $$->from.y = $3.y;
+ }
+ }
+ | object_spec WITH path
+ {
+ $$ = $1;
+ $$->flags |= HAS_WITH;
+ $$->with = $3;
+ }
+ | object_spec WITH position %prec ','
+ {
+ $$ = $1;
+ $$->flags |= HAS_WITH;
+ position pos;
+ pos.x = $3.x;
+ pos.y = $3.y;
+ $$->with = new path(pos);
+ }
+ | object_spec BY expr_pair
+ {
+ $$ = $1;
+ $$->flags |= HAS_SEGMENT;
+ $$->segment_pos.x += $3.x;
+ $$->segment_pos.y += $3.y;
+ }
+ | object_spec THEN
+ {
+ $$ = $1;
+ if (!($$->flags & HAS_SEGMENT))
+ switch ($$->dir) {
+ case UP_DIRECTION:
+ $$->segment_pos.y += $$->segment_width;
+ break;
+ case DOWN_DIRECTION:
+ $$->segment_pos.y -= $$->segment_width;
+ break;
+ case RIGHT_DIRECTION:
+ $$->segment_pos.x += $$->segment_width;
+ break;
+ case LEFT_DIRECTION:
+ $$->segment_pos.x -= $$->segment_width;
+ break;
+ }
+ $$->segment_list = new segment($$->segment_pos,
+ $$->segment_is_absolute,
+ $$->segment_list);
+ $$->flags &= ~HAS_SEGMENT;
+ $$->segment_pos.x = $$->segment_pos.y = 0.0;
+ $$->segment_is_absolute = 0;
+ }
+ | object_spec SOLID
+ {
+ $$ = $1; // nothing
+ }
+ | object_spec DOTTED
+ {
+ $$ = $1;
+ $$->flags |= IS_DOTTED;
+ lookup_variable("dashwid", & $$->dash_width);
+ }
+ | object_spec DOTTED expr
+ {
+ $$ = $1;
+ $$->flags |= IS_DOTTED;
+ $$->dash_width = $3;
+ }
+ | object_spec DASHED
+ {
+ $$ = $1;
+ $$->flags |= IS_DASHED;
+ lookup_variable("dashwid", & $$->dash_width);
+ }
+ | object_spec DASHED expr
+ {
+ $$ = $1;
+ $$->flags |= IS_DASHED;
+ $$->dash_width = $3;
+ }
+ | object_spec FILL
+ {
+ $$ = $1;
+ $$->flags |= IS_DEFAULT_FILLED;
+ }
+ | object_spec FILL expr
+ {
+ $$ = $1;
+ $$->flags |= IS_FILLED;
+ $$->fill = $3;
+ }
+ | object_spec XSLANTED expr
+ {
+ $$ = $1;
+ $$->flags |= IS_XSLANTED;
+ $$->xslanted = $3;
+ }
+ | object_spec YSLANTED expr
+ {
+ $$ = $1;
+ $$->flags |= IS_YSLANTED;
+ $$->yslanted = $3;
+ }
+ | object_spec SHADED text
+ {
+ $$ = $1;
+ $$->flags |= (IS_SHADED | IS_FILLED);
+ $$->shaded = new char[strlen($3.str)+1];
+ strcpy($$->shaded, $3.str);
+ }
+ | object_spec COLORED text
+ {
+ $$ = $1;
+ $$->flags |= (IS_SHADED | IS_OUTLINED | IS_FILLED);
+ $$->shaded = new char[strlen($3.str)+1];
+ strcpy($$->shaded, $3.str);
+ $$->outlined = new char[strlen($3.str)+1];
+ strcpy($$->outlined, $3.str);
+ }
+ | object_spec OUTLINED text
+ {
+ $$ = $1;
+ $$->flags |= IS_OUTLINED;
+ $$->outlined = new char[strlen($3.str)+1];
+ strcpy($$->outlined, $3.str);
+ }
+ | object_spec CHOP
+ {
+ $$ = $1;
+ // line chop chop means line chop 0 chop 0
+ if ($$->flags & IS_DEFAULT_CHOPPED) {
+ $$->flags |= IS_CHOPPED;
+ $$->flags &= ~IS_DEFAULT_CHOPPED;
+ $$->start_chop = $$->end_chop = 0.0;
+ }
+ else if ($$->flags & IS_CHOPPED) {
+ $$->end_chop = 0.0;
+ }
+ else {
+ $$->flags |= IS_DEFAULT_CHOPPED;
+ }
+ }
+ | object_spec CHOP expr
+ {
+ $$ = $1;
+ if ($$->flags & IS_DEFAULT_CHOPPED) {
+ $$->flags |= IS_CHOPPED;
+ $$->flags &= ~IS_DEFAULT_CHOPPED;
+ $$->start_chop = 0.0;
+ $$->end_chop = $3;
+ }
+ else if ($$->flags & IS_CHOPPED) {
+ $$->end_chop = $3;
+ }
+ else {
+ $$->start_chop = $$->end_chop = $3;
+ $$->flags |= IS_CHOPPED;
+ }
+ }
+ | object_spec SAME
+ {
+ $$ = $1;
+ $$->flags |= IS_SAME;
+ }
+ | object_spec INVISIBLE
+ {
+ $$ = $1;
+ $$->flags |= IS_INVISIBLE;
+ }
+ | object_spec LEFT_ARROW_HEAD
+ {
+ $$ = $1;
+ $$->flags |= HAS_LEFT_ARROW_HEAD;
+ }
+ | object_spec RIGHT_ARROW_HEAD
+ {
+ $$ = $1;
+ $$->flags |= HAS_RIGHT_ARROW_HEAD;
+ }
+ | object_spec DOUBLE_ARROW_HEAD
+ {
+ $$ = $1;
+ $$->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD);
+ }
+ | object_spec CW
+ {
+ $$ = $1;
+ $$->flags |= IS_CLOCKWISE;
+ }
+ | object_spec CCW
+ {
+ $$ = $1;
+ $$->flags &= ~IS_CLOCKWISE;
+ }
+ | object_spec text %prec TEXT
+ {
+ $$ = $1;
+ text_item **p;
+ for (p = & $$->text; *p; p = &(*p)->next)
+ ;
+ *p = new text_item($2.str, $2.filename, $2.lineno);
+ }
+ | object_spec LJUST
+ {
+ $$ = $1;
+ if ($$->text) {
+ text_item *p;
+ for (p = $$->text; p->next; p = p->next)
+ ;
+ p->adj.h = LEFT_ADJUST;
+ }
+ }
+ | object_spec RJUST
+ {
+ $$ = $1;
+ if ($$->text) {
+ text_item *p;
+ for (p = $$->text; p->next; p = p->next)
+ ;
+ p->adj.h = RIGHT_ADJUST;
+ }
+ }
+ | object_spec ABOVE
+ {
+ $$ = $1;
+ if ($$->text) {
+ text_item *p;
+ for (p = $$->text; p->next; p = p->next)
+ ;
+ p->adj.v = ABOVE_ADJUST;
+ }
+ }
+ | object_spec BELOW
+ {
+ $$ = $1;
+ if ($$->text) {
+ text_item *p;
+ for (p = $$->text; p->next; p = p->next)
+ ;
+ p->adj.v = BELOW_ADJUST;
+ }
+ }
+ | object_spec THICKNESS expr
+ {
+ $$ = $1;
+ $$->flags |= HAS_THICKNESS;
+ $$->thickness = $3;
+ }
+ | object_spec ALIGNED
+ {
+ $$ = $1;
+ $$->flags |= IS_ALIGNED;
+ }
+ ;
+
+text:
+ TEXT
+ { $$ = $1; }
+ | SPRINTF '(' TEXT sprintf_args ')'
+ {
+ $$.filename = $3.filename;
+ $$.lineno = $3.lineno;
+ $$.str = do_sprintf($3.str, $4.v, $4.nv);
+ delete[] $4.v;
+ free($3.str);
+ }
+ ;
+
+sprintf_args:
+ /* empty */
+ {
+ $$.v = 0;
+ $$.nv = 0;
+ $$.maxv = 0;
+ }
+ | sprintf_args ',' expr
+ {
+ $$ = $1;
+ if ($$.nv >= $$.maxv) {
+ if ($$.nv == 0) {
+ $$.v = new double[4];
+ $$.maxv = 4;
+ }
+ else {
+ double *oldv = $$.v;
+ $$.maxv *= 2;
+#if 0
+ $$.v = new double[$$.maxv];
+ memcpy($$.v, oldv, $$.nv*sizeof(double));
+#else
+ // workaround for bug in Compaq C++ V6.5-033
+ // for Compaq Tru64 UNIX V5.1A (Rev. 1885)
+ double *foo = new double[$$.maxv];
+ memcpy(foo, oldv, $$.nv*sizeof(double));
+ $$.v = foo;
+#endif
+ delete[] oldv;
+ }
+ }
+ $$.v[$$.nv] = $3;
+ $$.nv += 1;
+ }
+ ;
+
+position:
+ position_not_place
+ { $$ = $1; }
+ | place
+ {
+ position pos = $1;
+ $$.x = pos.x;
+ $$.y = pos.y;
+ }
+ | '(' place ')'
+ {
+ position pos = $2;
+ $$.x = pos.x;
+ $$.y = pos.y;
+ }
+ ;
+
+position_not_place:
+ expr_pair
+ { $$ = $1; }
+ | position '+' expr_pair
+ {
+ $$.x = $1.x + $3.x;
+ $$.y = $1.y + $3.y;
+ }
+ | '(' position '+' expr_pair ')'
+ {
+ $$.x = $2.x + $4.x;
+ $$.y = $2.y + $4.y;
+ }
+ | position '-' expr_pair
+ {
+ $$.x = $1.x - $3.x;
+ $$.y = $1.y - $3.y;
+ }
+ | '(' position '-' expr_pair ')'
+ {
+ $$.x = $2.x - $4.x;
+ $$.y = $2.y - $4.y;
+ }
+ | '(' position ',' position ')'
+ {
+ $$.x = $2.x;
+ $$.y = $4.y;
+ }
+ | expr between position AND position
+ {
+ $$.x = (1.0 - $1)*$3.x + $1*$5.x;
+ $$.y = (1.0 - $1)*$3.y + $1*$5.y;
+ }
+ | '(' expr between position AND position ')'
+ {
+ $$.x = (1.0 - $2)*$4.x + $2*$6.x;
+ $$.y = (1.0 - $2)*$4.y + $2*$6.y;
+ }
+ /* the next two rules cause harmless shift/reduce warnings */
+ | expr_not_lower_than '<' position ',' position '>'
+ {
+ $$.x = (1.0 - $1)*$3.x + $1*$5.x;
+ $$.y = (1.0 - $1)*$3.y + $1*$5.y;
+ }
+ | '(' expr_not_lower_than '<' position ',' position '>' ')'
+ {
+ $$.x = (1.0 - $2)*$4.x + $2*$6.x;
+ $$.y = (1.0 - $2)*$4.y + $2*$6.y;
+ }
+ ;
+
+between:
+ BETWEEN
+ | OF THE WAY BETWEEN
+ ;
+
+expr_pair:
+ expr ',' expr
+ {
+ $$.x = $1;
+ $$.y = $3;
+ }
+ | '(' expr_pair ')'
+ { $$ = $2; }
+ ;
+
+place:
+ /* line at A left == line (at A) left */
+ label %prec CHOP
+ { $$ = $1; }
+ | label corner
+ {
+ path pth($2);
+ if (!pth.follow($1, & $$))
+ YYABORT;
+ }
+ | corner label
+ {
+ path pth($1);
+ if (!pth.follow($2, & $$))
+ YYABORT;
+ }
+ | corner OF label
+ {
+ path pth($1);
+ if (!pth.follow($3, & $$))
+ YYABORT;
+ }
+ | HERE
+ {
+ $$.x = current_position.x;
+ $$.y = current_position.y;
+ $$.obj = 0;
+ }
+ ;
+
+label:
+ LABEL
+ {
+ place *p = lookup_label($1);
+ if (!p) {
+ lex_error("there is no place '%1'", $1);
+ YYABORT;
+ }
+ $$ = *p;
+ free($1);
+ }
+ | nth_primitive
+ { $$.obj = $1; }
+ | label '.' LABEL
+ {
+ path pth($3);
+ if (!pth.follow($1, & $$))
+ YYABORT;
+ }
+ ;
+
+ordinal:
+ ORDINAL
+ { $$ = $1; }
+ | '`' any_expr TH
+ {
+ // XXX Check for overflow (and non-integers?).
+ $$ = (int)$2;
+ }
+ ;
+
+optional_ordinal_last:
+ LAST
+ { $$ = 1; }
+ | ordinal LAST
+ { $$ = $1; }
+ ;
+
+nth_primitive:
+ ordinal object_type
+ {
+ int count = 0;
+ object *p;
+ for (p = olist.head; p != 0; p = p->next)
+ if (p->type() == $2 && ++count == $1) {
+ $$ = p;
+ break;
+ }
+ if (p == 0) {
+ lex_error("there is no %1%2 %3", $1, ordinal_postfix($1),
+ object_type_name($2));
+ YYABORT;
+ }
+ }
+ | optional_ordinal_last object_type
+ {
+ int count = 0;
+ object *p;
+ for (p = olist.tail; p != 0; p = p->prev)
+ if (p->type() == $2 && ++count == $1) {
+ $$ = p;
+ break;
+ }
+ if (p == 0) {
+ lex_error("there is no %1%2 last %3", $1,
+ ordinal_postfix($1), object_type_name($2));
+ YYABORT;
+ }
+ }
+ ;
+
+object_type:
+ BOX
+ { $$ = BOX_OBJECT; }
+ | CIRCLE
+ { $$ = CIRCLE_OBJECT; }
+ | ELLIPSE
+ { $$ = ELLIPSE_OBJECT; }
+ | ARC
+ { $$ = ARC_OBJECT; }
+ | LINE
+ { $$ = LINE_OBJECT; }
+ | ARROW
+ { $$ = ARROW_OBJECT; }
+ | SPLINE
+ { $$ = SPLINE_OBJECT; }
+ | '[' ']'
+ { $$ = BLOCK_OBJECT; }
+ | TEXT
+ { $$ = TEXT_OBJECT; }
+ ;
+
+label_path:
+ '.' LABEL
+ { $$ = new path($2); }
+ | label_path '.' LABEL
+ {
+ $$ = $1;
+ $$->append($3);
+ }
+ ;
+
+relative_path:
+ corner %prec CHOP
+ { $$ = new path($1); }
+ /* give this a lower precedence than LEFT and RIGHT so that
+ [A: box] with .A left == [A: box] with (.A left) */
+ | label_path %prec TEXT
+ { $$ = $1; }
+ | label_path corner
+ {
+ $$ = $1;
+ $$->append($2);
+ }
+ ;
+
+path:
+ relative_path
+ { $$ = $1; }
+ | '(' relative_path ',' relative_path ')'
+ {
+ $$ = $2;
+ $$->set_ypath($4);
+ }
+ /* The rest of these rules are a compatibility sop. */
+ | ORDINAL LAST object_type relative_path
+ {
+ lex_warning("'%1%2 last %3' in 'with' argument ignored",
+ $1, ordinal_postfix($1), object_type_name($3));
+ $$ = $4;
+ }
+ | LAST object_type relative_path
+ {
+ lex_warning("'last %1' in 'with' argument ignored",
+ object_type_name($2));
+ $$ = $3;
+ }
+ | ORDINAL object_type relative_path
+ {
+ lex_warning("'%1%2 %3' in 'with' argument ignored",
+ $1, ordinal_postfix($1), object_type_name($2));
+ $$ = $3;
+ }
+ | LABEL relative_path
+ {
+ lex_warning("initial '%1' in 'with' argument ignored", $1);
+ delete[] $1;
+ $$ = $2;
+ }
+ ;
+
+corner:
+ DOT_N
+ { $$ = &object::north; }
+ | DOT_E
+ { $$ = &object::east; }
+ | DOT_W
+ { $$ = &object::west; }
+ | DOT_S
+ { $$ = &object::south; }
+ | DOT_NE
+ { $$ = &object::north_east; }
+ | DOT_SE
+ { $$ = &object:: south_east; }
+ | DOT_NW
+ { $$ = &object::north_west; }
+ | DOT_SW
+ { $$ = &object::south_west; }
+ | DOT_C
+ { $$ = &object::center; }
+ | DOT_START
+ { $$ = &object::start; }
+ | DOT_END
+ { $$ = &object::end; }
+ | TOP
+ { $$ = &object::north; }
+ | BOTTOM
+ { $$ = &object::south; }
+ | LEFT
+ { $$ = &object::west; }
+ | RIGHT
+ { $$ = &object::east; }
+ | UPPER LEFT
+ { $$ = &object::north_west; }
+ | LOWER LEFT
+ { $$ = &object::south_west; }
+ | UPPER RIGHT
+ { $$ = &object::north_east; }
+ | LOWER RIGHT
+ { $$ = &object::south_east; }
+ | LEFT_CORNER
+ { $$ = &object::west; }
+ | RIGHT_CORNER
+ { $$ = &object::east; }
+ | UPPER LEFT_CORNER
+ { $$ = &object::north_west; }
+ | LOWER LEFT_CORNER
+ { $$ = &object::south_west; }
+ | UPPER RIGHT_CORNER
+ { $$ = &object::north_east; }
+ | LOWER RIGHT_CORNER
+ { $$ = &object::south_east; }
+ | NORTH
+ { $$ = &object::north; }
+ | SOUTH
+ { $$ = &object::south; }
+ | EAST
+ { $$ = &object::east; }
+ | WEST
+ { $$ = &object::west; }
+ | CENTER
+ { $$ = &object::center; }
+ | START
+ { $$ = &object::start; }
+ | END
+ { $$ = &object::end; }
+ ;
+
+expr:
+ expr_lower_than
+ { $$ = $1; }
+ | expr_not_lower_than
+ { $$ = $1; }
+ ;
+
+expr_lower_than:
+ expr '<' expr
+ { $$ = ($1 < $3); }
+ ;
+
+expr_not_lower_than:
+ VARIABLE
+ {
+ if (!lookup_variable($1, & $$)) {
+ lex_error("there is no variable '%1'", $1);
+ YYABORT;
+ }
+ free($1);
+ }
+ | NUMBER
+ { $$ = $1; }
+ | place DOT_X
+ {
+ if ($1.obj != 0)
+ $$ = $1.obj->origin().x;
+ else
+ $$ = $1.x;
+ }
+ | place DOT_Y
+ {
+ if ($1.obj != 0)
+ $$ = $1.obj->origin().y;
+ else
+ $$ = $1.y;
+ }
+ | place DOT_HT
+ {
+ if ($1.obj != 0)
+ $$ = $1.obj->height();
+ else
+ $$ = 0.0;
+ }
+ | place DOT_WID
+ {
+ if ($1.obj != 0)
+ $$ = $1.obj->width();
+ else
+ $$ = 0.0;
+ }
+ | place DOT_RAD
+ {
+ if ($1.obj != 0)
+ $$ = $1.obj->radius();
+ else
+ $$ = 0.0;
+ }
+ | expr '+' expr
+ { $$ = $1 + $3; }
+ | expr '-' expr
+ { $$ = $1 - $3; }
+ | expr '*' expr
+ { $$ = $1 * $3; }
+ | expr '/' expr
+ {
+ if ($3 == 0.0) {
+ lex_error("division by zero");
+ YYABORT;
+ }
+ $$ = $1/$3;
+ }
+ | expr '%' expr
+ {
+ if ($3 == 0.0) {
+ lex_error("modulus by zero");
+ YYABORT;
+ }
+ $$ = fmod($1, $3);
+ }
+ | expr '^' expr
+ {
+ errno = 0;
+ $$ = pow($1, $3);
+ if (errno == EDOM) {
+ lex_error("arguments to '^' operator out of domain");
+ YYABORT;
+ }
+ if (errno == ERANGE) {
+ lex_error("result of '^' operator out of range");
+ YYABORT;
+ }
+ }
+ | '-' expr %prec '!'
+ { $$ = -$2; }
+ | '(' any_expr ')'
+ { $$ = $2; }
+ | SIN '(' any_expr ')'
+ {
+ errno = 0;
+ $$ = sin($3);
+ if (errno == ERANGE) {
+ lex_error("sin result out of range");
+ YYABORT;
+ }
+ }
+ | COS '(' any_expr ')'
+ {
+ errno = 0;
+ $$ = cos($3);
+ if (errno == ERANGE) {
+ lex_error("cos result out of range");
+ YYABORT;
+ }
+ }
+ | ATAN2 '(' any_expr ',' any_expr ')'
+ {
+ errno = 0;
+ $$ = atan2($3, $5);
+ if (errno == EDOM) {
+ lex_error("atan2 argument out of domain");
+ YYABORT;
+ }
+ if (errno == ERANGE) {
+ lex_error("atan2 result out of range");
+ YYABORT;
+ }
+ }
+ | LOG '(' any_expr ')'
+ {
+ errno = 0;
+ $$ = log10($3);
+ if (errno == ERANGE) {
+ lex_error("log result out of range");
+ YYABORT;
+ }
+ }
+ | EXP '(' any_expr ')'
+ {
+ errno = 0;
+ $$ = pow(10.0, $3);
+ if (errno == ERANGE) {
+ lex_error("exp result out of range");
+ YYABORT;
+ }
+ }
+ | SQRT '(' any_expr ')'
+ {
+ errno = 0;
+ $$ = sqrt($3);
+ if (errno == EDOM) {
+ lex_error("sqrt argument out of domain");
+ YYABORT;
+ }
+ }
+ | K_MAX '(' any_expr ',' any_expr ')'
+ { $$ = $3 > $5 ? $3 : $5; }
+ | K_MIN '(' any_expr ',' any_expr ')'
+ { $$ = $3 < $5 ? $3 : $5; }
+ | INT '(' any_expr ')'
+ { $$ = $3 < 0 ? -floor(-$3) : floor($3); }
+ | RAND '(' any_expr ')'
+ {
+ lex_error("use of 'rand' with an argument is"
+ " deprecated; shift and scale 'rand()' with"
+ " arithmetic instead");
+ $$ = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*$3);
+ }
+ | RAND '(' ')'
+ {
+ /* return a random number in the range [0,1) */
+ /* portable, but not very random */
+ $$ = (rand() & 0x7fff) / double(0x8000);
+ }
+ | SRAND '(' any_expr ')'
+ {
+ $$ = 0;
+ srand((unsigned int)$3);
+ }
+ | expr LESSEQUAL expr
+ { $$ = ($1 <= $3); }
+ | expr '>' expr
+ { $$ = ($1 > $3); }
+ | expr GREATEREQUAL expr
+ { $$ = ($1 >= $3); }
+ | expr EQUALEQUAL expr
+ { $$ = ($1 == $3); }
+ | expr NOTEQUAL expr
+ { $$ = ($1 != $3); }
+ | expr ANDAND expr
+ { $$ = ($1 != 0.0 && $3 != 0.0); }
+ | expr OROR expr
+ { $$ = ($1 != 0.0 || $3 != 0.0); }
+ | '!' expr
+ { $$ = ($2 == 0.0); }
+
+ ;
+
+%%
+
+/* bison defines const to be empty unless __STDC__ is defined, which it
+isn't under cfront */
+
+#ifdef const
+#undef const
+#endif
+
+static struct {
+ const char *name;
+ double val;
+ int scaled; // non-zero if val should be multiplied by scale
+} defaults_table[] = {
+ { "arcrad", .25, 1 },
+ { "arrowht", .1, 1 },
+ { "arrowwid", .05, 1 },
+ { "circlerad", .25, 1 },
+ { "boxht", .5, 1 },
+ { "boxwid", .75, 1 },
+ { "boxrad", 0.0, 1 },
+ { "dashwid", .05, 1 },
+ { "ellipseht", .5, 1 },
+ { "ellipsewid", .75, 1 },
+ { "moveht", .5, 1 },
+ { "movewid", .5, 1 },
+ { "lineht", .5, 1 },
+ { "linewid", .5, 1 },
+ { "textht", 0.0, 1 },
+ { "textwid", 0.0, 1 },
+ { "scale", 1.0, 0 },
+ { "linethick", -1.0, 0 }, // in points
+ { "fillval", .5, 0 },
+ { "arrowhead", 1.0, 0 },
+ { "maxpswid", 8.5, 0 },
+ { "maxpsht", 11.0, 0 },
+};
+
+place *lookup_label(const char *label)
+{
+ saved_state *state = current_saved_state;
+ PTABLE(place) *tbl = current_table;
+ for (;;) {
+ place *pl = tbl->lookup(label);
+ if (pl)
+ return pl;
+ if (!state)
+ return 0;
+ tbl = state->tbl;
+ state = state->prev;
+ }
+}
+
+void define_label(const char *label, const place *pl)
+{
+ place *p = new place[1];
+ *p = *pl;
+ current_table->define(label, p);
+}
+
+int lookup_variable(const char *name, double *val)
+{
+ place *pl = lookup_label(name);
+ if (pl) {
+ *val = pl->x;
+ return 1;
+ }
+ return 0;
+}
+
+void define_variable(const char *name, double val)
+{
+ place *p = new place[1];
+ p->obj = 0;
+ p->x = val;
+ p->y = 0.0;
+ current_table->define(name, p);
+ if (strcmp(name, "scale") == 0) {
+ // When the scale changes, reset all scaled predefined variables to
+ // their default values.
+ for (unsigned int i = 0;
+ i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
+ if (defaults_table[i].scaled)
+ define_variable(defaults_table[i].name, val*defaults_table[i].val);
+ }
+}
+
+// called once only (not once per parse)
+
+void parse_init()
+{
+ current_direction = RIGHT_DIRECTION;
+ current_position.x = 0.0;
+ current_position.y = 0.0;
+ // This resets everything to its default value.
+ reset_all();
+}
+
+void reset(const char *nm)
+{
+ for (unsigned int i = 0;
+ i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
+ if (strcmp(nm, defaults_table[i].name) == 0) {
+ double val = defaults_table[i].val;
+ if (defaults_table[i].scaled) {
+ double scale;
+ lookup_variable("scale", &scale);
+ val *= scale;
+ }
+ define_variable(defaults_table[i].name, val);
+ return;
+ }
+ lex_error("'%1' is not a predefined variable", nm);
+}
+
+void reset_all()
+{
+ // We only have to explicitly reset the predefined variables that
+ // aren't scaled because 'scale' is not scaled, and changing the
+ // value of 'scale' will reset all the predefined variables that
+ // are scaled.
+ for (unsigned int i = 0;
+ i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
+ if (!defaults_table[i].scaled)
+ define_variable(defaults_table[i].name, defaults_table[i].val);
+}
+
+// called after each parse
+
+void parse_cleanup()
+{
+ while (current_saved_state != 0) {
+ delete current_table;
+ current_table = current_saved_state->tbl;
+ saved_state *tem = current_saved_state;
+ current_saved_state = current_saved_state->prev;
+ delete tem;
+ }
+ assert(current_table == &top_table);
+ PTABLE_ITERATOR(place) iter(current_table);
+ const char *key;
+ place *pl;
+ while (iter.next(&key, &pl))
+ if (pl->obj != 0) {
+ position pos = pl->obj->origin();
+ pl->obj = 0;
+ pl->x = pos.x;
+ pl->y = pos.y;
+ }
+ while (olist.head != 0) {
+ object *tem = olist.head;
+ olist.head = olist.head->next;
+ delete tem;
+ }
+ olist.tail = 0;
+ current_direction = RIGHT_DIRECTION;
+ current_position.x = 0.0;
+ current_position.y = 0.0;
+}
+
+const char *ordinal_postfix(int n)
+{
+ if (n < 10 || n > 20)
+ switch (n % 10) {
+ case 1:
+ return "st";
+ case 2:
+ return "nd";
+ case 3:
+ return "rd";
+ }
+ return "th";
+}
+
+const char *object_type_name(object_type type)
+{
+ switch (type) {
+ case BOX_OBJECT:
+ return "box";
+ case CIRCLE_OBJECT:
+ return "circle";
+ case ELLIPSE_OBJECT:
+ return "ellipse";
+ case ARC_OBJECT:
+ return "arc";
+ case SPLINE_OBJECT:
+ return "spline";
+ case LINE_OBJECT:
+ return "line";
+ case ARROW_OBJECT:
+ return "arrow";
+ case MOVE_OBJECT:
+ return "move";
+ case TEXT_OBJECT:
+ return "\"\"";
+ case BLOCK_OBJECT:
+ return "[]";
+ case OTHER_OBJECT:
+ case MARK_OBJECT:
+ default:
+ break;
+ }
+ return "object";
+}
+
+static char sprintf_buf[1024];
+
+char *format_number(const char *fmt, double n)
+{
+ if (0 /* nullptr */ == fmt)
+ fmt = "%g";
+ return do_sprintf(fmt, &n, 1);
+}
+
+char *do_sprintf(const char *fmt, const double *v, int nv)
+{
+ // Define valid conversion specifiers and modifiers.
+ static const char spcs[] = "eEfgG%";
+ static const char mods[] = "#-+ 0123456789.";
+ string result;
+ int i = 0;
+ string one_format;
+ while (*fmt) {
+ if ('%' == *fmt) {
+ one_format += *fmt++;
+ for (; *fmt != '\0' && strchr(mods, *fmt) != 0; fmt++)
+ one_format += *fmt;
+ if ('\0' == *fmt || strchr(spcs, *fmt) == 0) {
+ lex_error("invalid sprintf conversion specifier '%1'", *fmt);
+ result += one_format;
+ result += fmt;
+ break;
+ }
+ if ('%' == *fmt) {
+ fmt++;
+ snprintf(sprintf_buf, sizeof(sprintf_buf), "%%");
+ }
+ else {
+ if (i >= nv) {
+ lex_error("too few arguments to sprintf");
+ result += one_format;
+ result += fmt;
+ break;
+ }
+ one_format += *fmt++;
+ one_format += '\0';
+// We validated the format string above. Most conversion specifiers are
+// rejected, including `n`.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ snprintf(sprintf_buf, sizeof(sprintf_buf),
+ one_format.contents(), v[i++]);
+#pragma GCC diagnostic pop
+ }
+ one_format.clear();
+ result += sprintf_buf;
+ }
+ else
+ result += *fmt++;
+ }
+ result += '\0';
+ return strsave(result.contents());
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/pic/position.h b/src/preproc/pic/position.h
new file mode 100644
index 0000000..fb32737
--- /dev/null
+++ b/src/preproc/pic/position.h
@@ -0,0 +1,46 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct place;
+struct position {
+ double x;
+ double y;
+ position(double, double );
+ position();
+ position(const place &);
+ position &operator+=(const position &);
+ position &operator-=(const position &);
+ position &operator*=(double);
+ position &operator/=(double);
+};
+
+position operator-(const position &);
+position operator+(const position &, const position &);
+position operator-(const position &, const position &);
+position operator/(const position &, double);
+position operator*(const position &, double);
+// dot product
+double operator*(const position &, const position &);
+int operator==(const position &, const position &);
+int operator!=(const position &, const position &);
+
+double hypot(const position &a);
+
+typedef position distance;
+
diff --git a/src/preproc/pic/tex.cpp b/src/preproc/pic/tex.cpp
new file mode 100644
index 0000000..c6071af
--- /dev/null
+++ b/src/preproc/pic/tex.cpp
@@ -0,0 +1,458 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pic.h"
+
+#ifdef TEX_SUPPORT
+
+#include "common.h"
+
+class tex_output : public common_output {
+public:
+ tex_output();
+ ~tex_output();
+ void start_picture(double, const position &ll, const position &ur);
+ void finish_picture();
+ void text(const position &, text_piece *, int, double);
+ void line(const position &, const position *, int n,
+ const line_type &);
+ void polygon(const position *, int n,
+ const line_type &, double);
+ void spline(const position &, const position *, int n,
+ const line_type &);
+ void arc(const position &, const position &, const position &,
+ const line_type &);
+ void circle(const position &, double rad, const line_type &, double);
+ void ellipse(const position &, const distance &, const line_type &, double);
+ void command(const char *, const char *, int);
+ void set_color(char *, char *);
+ void reset_color();
+ char *get_last_filled();
+ char *get_outline_color();
+ int supports_filled_polygons();
+private:
+ position upper_left;
+ double height;
+ double width;
+ double scale;
+ double pen_size;
+
+ void point(const position &);
+ void dot(const position &, const line_type &);
+ void solid_arc(const position &cent, double rad, double start_angle,
+ double end_angle, const line_type &lt);
+ position transform(const position &);
+protected:
+ virtual void set_pen_size(double ps);
+};
+
+// convert inches to milliinches
+
+inline int milliinches(double x)
+{
+ return int(x*1000.0 + .5);
+}
+
+inline position tex_output::transform(const position &pos)
+{
+ return position((pos.x - upper_left.x)/scale,
+ (upper_left.y - pos.y)/scale);
+}
+
+output *make_tex_output()
+{
+ return new tex_output;
+}
+
+tex_output::tex_output()
+{
+}
+
+tex_output::~tex_output()
+{
+}
+
+const int DEFAULT_PEN_SIZE = 8;
+
+void tex_output::set_pen_size(double ps)
+{
+ if (ps < 0.0)
+ ps = -1.0;
+ if (ps != pen_size) {
+ pen_size = ps;
+ printf(" \\special{pn %d}%%\n",
+ ps < 0.0 ? DEFAULT_PEN_SIZE : int(ps*(1000.0/72.0) + .5));
+ }
+}
+
+void tex_output::start_picture(double sc, const position &ll,
+ const position &ur)
+{
+ upper_left.x = ll.x;
+ upper_left.y = ur.y;
+ scale = compute_scale(sc, ll, ur);
+ height = (ur.y - ll.y)/scale;
+ width = (ur.x - ll.x)/scale;
+ /* The point of \vskip 0pt is to ensure that the vtop gets
+ a height of 0 rather than the height of the hbox; this
+ might be non-zero if text from text attributes lies outside pic's
+ idea of the bounding box of the picture. */
+ /* \newbox and \newdimen are defined with \outer in plain.tex and can't
+ be used directly in an \if clause. */
+ printf("\\expandafter\\ifx\\csname %s\\endcsname\\relax\n"
+ " \\csname newbox\\expandafter\\endcsname\\csname %s\\endcsname\n"
+ "\\fi\n"
+ "\\ifx\\graphtemp\\undefined\n"
+ " \\csname newdimen\\endcsname\\graphtemp\n"
+ "\\fi\n"
+ "\\expandafter\\setbox\\csname %s\\endcsname\n"
+ " =\\vtop{\\vskip 0pt\\hbox{%%\n",
+ graphname, graphname, graphname);
+ pen_size = -2.0;
+}
+
+void tex_output::finish_picture()
+{
+ printf(" \\hbox{\\vrule depth%.3fin width0pt height 0pt}%%\n"
+ " \\kern %.3fin\n"
+ " }%%\n"
+ "}%%\n",
+ height, width);
+}
+
+void tex_output::text(const position &center, text_piece *v, int n, double)
+{
+ position c = transform(center);
+ for (int i = 0; i < n; i++)
+ if (v[i].text != 0 && *v[i].text != '\0') {
+ int j = 2*i - n + 1;
+ if (v[i].adj.v == ABOVE_ADJUST)
+ j--;
+ else if (v[i].adj.v == BELOW_ADJUST)
+ j++;
+ if (j == 0) {
+ printf(" \\graphtemp=.5ex\n"
+ " \\advance\\graphtemp by %.3fin\n", c.y);
+ }
+ else {
+ printf(" \\graphtemp=\\baselineskip\n"
+ " \\multiply\\graphtemp by %d\n"
+ " \\divide\\graphtemp by 2\n"
+ " \\advance\\graphtemp by .5ex\n"
+ " \\advance\\graphtemp by %.3fin\n",
+ j, c.y);
+ }
+ printf(" \\rlap{\\kern %.3fin\\lower\\graphtemp", c.x);
+ fputs("\\hbox to 0pt{", stdout);
+ if (v[i].adj.h != LEFT_ADJUST)
+ fputs("\\hss ", stdout);
+ fputs(v[i].text, stdout);
+ if (v[i].adj.h != RIGHT_ADJUST)
+ fputs("\\hss", stdout);
+ fputs("}}%\n", stdout);
+ }
+}
+
+void tex_output::point(const position &pos)
+{
+ position p = transform(pos);
+ printf(" \\special{pa %d %d}%%\n", milliinches(p.x), milliinches(p.y));
+}
+
+void tex_output::line(const position &start, const position *v, int n,
+ const line_type &lt)
+{
+ set_pen_size(lt.thickness);
+ point(start);
+ for (int i = 0; i < n; i++)
+ point(v[i]);
+ fputs(" \\special{", stdout);
+ switch(lt.type) {
+ case line_type::invisible:
+ fputs("ip", stdout);
+ break;
+ case line_type::solid:
+ fputs("fp", stdout);
+ break;
+ case line_type::dotted:
+ printf("dt %.3f", lt.dash_width/scale);
+ break;
+ case line_type::dashed:
+ printf("da %.3f", lt.dash_width/scale);
+ break;
+ }
+ fputs("}%\n", stdout);
+}
+
+void tex_output::polygon(const position *v, int n,
+ const line_type &lt, double fill)
+{
+ if (fill >= 0.0) {
+ if (fill > 1.0)
+ fill = 1.0;
+ printf(" \\special{sh %.3f}%%\n", fill);
+ }
+ line(v[n-1], v, n, lt);
+}
+
+void tex_output::spline(const position &start, const position *v, int n,
+ const line_type &lt)
+{
+ if (lt.type == line_type::invisible)
+ return;
+ set_pen_size(lt.thickness);
+ point(start);
+ for (int i = 0; i < n; i++)
+ point(v[i]);
+ fputs(" \\special{sp", stdout);
+ switch(lt.type) {
+ case line_type::solid:
+ break;
+ case line_type::dotted:
+ printf(" %.3f", -lt.dash_width/scale);
+ break;
+ case line_type::dashed:
+ printf(" %.3f", lt.dash_width/scale);
+ break;
+ case line_type::invisible:
+ assert(0);
+ }
+ fputs("}%\n", stdout);
+}
+
+void tex_output::solid_arc(const position &cent, double rad,
+ double start_angle, double end_angle,
+ const line_type &lt)
+{
+ set_pen_size(lt.thickness);
+ position c = transform(cent);
+ printf(" \\special{ar %d %d %d %d %f %f}%%\n",
+ milliinches(c.x),
+ milliinches(c.y),
+ milliinches(rad/scale),
+ milliinches(rad/scale),
+ -end_angle,
+ (-end_angle > -start_angle) ? (double)M_PI * 2 - start_angle
+ : -start_angle);
+}
+
+void tex_output::arc(const position &start, const position &cent,
+ const position &end, const line_type &lt)
+{
+ switch (lt.type) {
+ case line_type::invisible:
+ break;
+ case line_type::dashed:
+ dashed_arc(start, cent, end, lt);
+ break;
+ case line_type::dotted:
+ dotted_arc(start, cent, end, lt);
+ break;
+ case line_type::solid:
+ {
+ position c;
+ if (!compute_arc_center(start, cent, end, &c)) {
+ line(start, &end, 1, lt);
+ break;
+ }
+ solid_arc(c,
+ hypot(cent - start),
+ atan2(start.y - c.y, start.x - c.x),
+ atan2(end.y - c.y, end.x - c.x),
+ lt);
+ break;
+ }
+ }
+}
+
+void tex_output::circle(const position &cent, double rad,
+ const line_type &lt, double fill)
+{
+ if (fill >= 0.0 && lt.type != line_type::solid) {
+ if (fill > 1.0)
+ fill = 1.0;
+ line_type ilt;
+ ilt.type = line_type::invisible;
+ ellipse(cent, position(rad*2.0, rad*2.0), ilt, fill);
+ }
+ switch (lt.type) {
+ case line_type::dashed:
+ dashed_circle(cent, rad, lt);
+ break;
+ case line_type::invisible:
+ break;
+ case line_type::solid:
+ ellipse(cent, position(rad*2.0,rad*2.0), lt, fill);
+ break;
+ case line_type::dotted:
+ dotted_circle(cent, rad, lt);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+void tex_output::ellipse(const position &cent, const distance &dim,
+ const line_type &lt, double fill)
+{
+ if (lt.type == line_type::invisible) {
+ if (fill < 0.0)
+ return;
+ }
+ else
+ set_pen_size(lt.thickness);
+ if (fill >= 0.0) {
+ if (fill > 1.0)
+ fill = 1.0;
+ printf(" \\special{sh %.3f}%%\n", fill);
+ }
+ position c = transform(cent);
+ switch (lt.type) {
+ case line_type::solid:
+ case line_type::invisible:
+ printf(" \\special{%s %d %d %d %d 0 6.28319}%%\n",
+ (lt.type == line_type::invisible ? "ia" : "ar"),
+ milliinches(c.x),
+ milliinches(c.y),
+ milliinches(dim.x/(2.0*scale)),
+ milliinches(dim.y/(2.0*scale)));
+ break;
+ case line_type::dashed:
+ dashed_ellipse(cent, dim / scale, lt);
+ break;
+ case line_type::dotted:
+ dotted_ellipse(cent, dim / scale, lt);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+void tex_output::command(const char *s, const char *, int)
+{
+ fputs(s, stdout);
+ putchar('%'); // avoid unwanted spaces
+ putchar('\n');
+}
+
+int tex_output::supports_filled_polygons()
+{
+ return 1;
+}
+
+void tex_output::dot(const position &pos, const line_type &lt)
+{
+ if (zero_length_line_flag) {
+ line_type slt = lt;
+ slt.type = line_type::solid;
+ line(pos, &pos, 1, slt);
+ }
+ else {
+ int dot_rad = int(lt.thickness*(1000.0/(72.0*2)) + .5);
+ if (dot_rad == 0)
+ dot_rad = 1;
+ position p = transform(pos);
+ printf(" \\special{sh 1}%%\n"
+ " \\special{ia %d %d %d %d 0 6.28319}%%\n",
+ milliinches(p.x), milliinches(p.y), dot_rad, dot_rad);
+ }
+}
+
+void tex_output::set_color(char *, char *)
+{
+ /* not implemented yet */
+}
+
+void tex_output::reset_color()
+{
+ /* not implemented yet */
+}
+
+char *tex_output::get_last_filled()
+{
+ /* not implemented yet */
+ return NULL;
+}
+
+char *tex_output::get_outline_color()
+{
+ /* not implemented yet */
+ return NULL;
+}
+
+class tpic_output : public tex_output {
+public:
+ tpic_output();
+ void command(const char *, const char *, int);
+private:
+ void set_pen_size(double ps);
+ int default_pen_size;
+ int prev_default_pen_size;
+};
+
+tpic_output::tpic_output()
+: default_pen_size(DEFAULT_PEN_SIZE), prev_default_pen_size(DEFAULT_PEN_SIZE)
+{
+}
+
+void tpic_output::command(const char *s, const char *filename, int lineno)
+{
+ assert(s[0] == '.');
+ if (s[1] == 'p' && s[2] == 's' && (s[3] == '\0' || !csalpha(s[3]))) {
+ const char *p = s + 3;
+ while (csspace(*p))
+ p++;
+ if (*p == '\0') {
+ int temp = default_pen_size;
+ default_pen_size = prev_default_pen_size;
+ prev_default_pen_size = temp;
+ }
+ else {
+ char *ptr;
+ int temp = (int)strtol(p, &ptr, 10);
+ if (temp == 0 && ptr == p)
+ error_with_file_and_line(filename, lineno,
+ "argument to '.ps' not an integer");
+ else if (temp < 0)
+ error_with_file_and_line(filename, lineno,
+ "negative pen size");
+ else {
+ prev_default_pen_size = default_pen_size;
+ default_pen_size = temp;
+ }
+ }
+ }
+ else
+ printf("\\%s%%\n", s + 1);
+}
+
+void tpic_output::set_pen_size(double ps)
+{
+ if (ps < 0.0)
+ printf(" \\special{pn %d}%%\n", default_pen_size);
+ else
+ tex_output::set_pen_size(ps);
+}
+
+output *make_tpic_output()
+{
+ return new tpic_output;
+}
+
+#endif
diff --git a/src/preproc/pic/text.h b/src/preproc/pic/text.h
new file mode 100644
index 0000000..9b9353f
--- /dev/null
+++ b/src/preproc/pic/text.h
@@ -0,0 +1,46 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+enum hadjustment {
+ CENTER_ADJUST,
+ LEFT_ADJUST,
+ RIGHT_ADJUST
+ };
+
+enum vadjustment {
+ NONE_ADJUST,
+ ABOVE_ADJUST,
+ BELOW_ADJUST
+ };
+
+struct adjustment {
+ hadjustment h;
+ vadjustment v;
+};
+
+struct text_piece {
+ char *text;
+ adjustment adj;
+ const char *filename;
+ int lineno;
+
+ text_piece();
+ ~text_piece();
+};
diff --git a/src/preproc/pic/troff.cpp b/src/preproc/pic/troff.cpp
new file mode 100644
index 0000000..3dc87a7
--- /dev/null
+++ b/src/preproc/pic/troff.cpp
@@ -0,0 +1,579 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pic.h"
+#include "common.h"
+
+
+const double RELATIVE_THICKNESS = -1.0;
+const double BAD_THICKNESS = -2.0;
+
+class simple_output : public common_output {
+ virtual void simple_line(const position &, const position &) = 0;
+ virtual void simple_spline(const position &, const position *, int n) = 0;
+ virtual void simple_arc(const position &, const position &,
+ const position &) = 0;
+ virtual void simple_circle(int, const position &, double rad) = 0;
+ virtual void simple_ellipse(int, const position &, const distance &) = 0;
+ virtual void simple_polygon(int, const position *, int) = 0;
+ virtual void line_thickness(double) = 0;
+ virtual void set_fill(double) = 0;
+ virtual void set_color(char *, char *) = 0;
+ virtual void reset_color() = 0;
+ virtual char *get_last_filled() = 0;
+ void dot(const position &, const line_type &) = 0;
+public:
+ void start_picture(double sc, const position &ll, const position &ur) = 0;
+ void finish_picture() = 0;
+ void text(const position &, text_piece *, int, double) = 0;
+ void line(const position &, const position *, int n,
+ const line_type &);
+ void polygon(const position *, int n,
+ const line_type &, double);
+ void spline(const position &, const position *, int n,
+ const line_type &);
+ void arc(const position &, const position &, const position &,
+ const line_type &);
+ void circle(const position &, double rad, const line_type &, double);
+ void ellipse(const position &, const distance &, const line_type &, double);
+ int supports_filled_polygons();
+};
+
+int simple_output::supports_filled_polygons()
+{
+ return driver_extension_flag != 0;
+}
+
+void simple_output::arc(const position &start, const position &cent,
+ const position &end, const line_type &lt)
+{
+ switch (lt.type) {
+ case line_type::solid:
+ line_thickness(lt.thickness);
+ simple_arc(start, cent, end);
+ break;
+ case line_type::invisible:
+ break;
+ case line_type::dashed:
+ dashed_arc(start, cent, end, lt);
+ break;
+ case line_type::dotted:
+ dotted_arc(start, cent, end, lt);
+ break;
+ }
+}
+
+void simple_output::line(const position &start, const position *v, int n,
+ const line_type &lt)
+{
+ position pos = start;
+ line_thickness(lt.thickness);
+ for (int i = 0; i < n; i++) {
+ switch (lt.type) {
+ case line_type::solid:
+ simple_line(pos, v[i]);
+ break;
+ case line_type::dotted:
+ {
+ distance vec(v[i] - pos);
+ double dist = hypot(vec);
+ int ndots = int(dist/lt.dash_width + .5);
+ if (ndots == 0)
+ dot(pos, lt);
+ else {
+ vec /= double(ndots);
+ for (int j = 0; j <= ndots; j++)
+ dot(pos + vec*j, lt);
+ }
+ }
+ break;
+ case line_type::dashed:
+ {
+ distance vec(v[i] - pos);
+ double dist = hypot(vec);
+ if (dist <= lt.dash_width*2.0)
+ simple_line(pos, v[i]);
+ else {
+ int ndashes = int((dist - lt.dash_width)/(lt.dash_width*2.0) + .5);
+ distance dash_vec = vec*(lt.dash_width/dist);
+ double dash_gap = (dist - lt.dash_width)/ndashes;
+ distance dash_gap_vec = vec*(dash_gap/dist);
+ for (int j = 0; j <= ndashes; j++) {
+ position s(pos + dash_gap_vec*j);
+ simple_line(s, s + dash_vec);
+ }
+ }
+ }
+ break;
+ case line_type::invisible:
+ break;
+ default:
+ assert(0);
+ }
+ pos = v[i];
+ }
+}
+
+void simple_output::spline(const position &start, const position *v, int n,
+ const line_type &lt)
+{
+ line_thickness(lt.thickness);
+ simple_spline(start, v, n);
+}
+
+void simple_output::polygon(const position *v, int n,
+ const line_type &lt, double fill)
+{
+ if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
+ if (get_last_filled() == 0)
+ set_fill(fill);
+ simple_polygon(1, v, n);
+ }
+ if (lt.type == line_type::solid && driver_extension_flag) {
+ line_thickness(lt.thickness);
+ simple_polygon(0, v, n);
+ }
+ else if (lt.type != line_type::invisible) {
+ line_thickness(lt.thickness);
+ line(v[n - 1], v, n, lt);
+ }
+}
+
+void simple_output::circle(const position &cent, double rad,
+ const line_type &lt, double fill)
+{
+ if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
+ if (get_last_filled() == 0)
+ set_fill(fill);
+ simple_circle(1, cent, rad);
+ }
+ line_thickness(lt.thickness);
+ switch (lt.type) {
+ case line_type::invisible:
+ break;
+ case line_type::dashed:
+ dashed_circle(cent, rad, lt);
+ break;
+ case line_type::dotted:
+ dotted_circle(cent, rad, lt);
+ break;
+ case line_type::solid:
+ simple_circle(0, cent, rad);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+void simple_output::ellipse(const position &cent, const distance &dim,
+ const line_type &lt, double fill)
+{
+ if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
+ if (get_last_filled() == 0)
+ set_fill(fill);
+ simple_ellipse(1, cent, dim);
+ }
+ if (lt.type != line_type::invisible)
+ line_thickness(lt.thickness);
+ switch (lt.type) {
+ case line_type::invisible:
+ break;
+ case line_type::dotted:
+ dotted_ellipse(cent, dim, lt);
+ break;
+ case line_type::dashed:
+ dashed_ellipse(cent, dim, lt);
+ break;
+ case line_type::solid:
+ simple_ellipse(0, cent, dim);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+class troff_output : public simple_output {
+ const char *last_filename;
+ position upper_left;
+ double height;
+ double scale;
+ double last_line_thickness;
+ double last_fill;
+ char *last_filled; // color
+ char *last_outlined; // color
+public:
+ troff_output();
+ ~troff_output();
+ void start_picture(double, const position &ll, const position &ur);
+ void finish_picture();
+ void text(const position &, text_piece *, int, double);
+ void dot(const position &, const line_type &);
+ void command(const char *, const char *, int);
+ void set_location(const char *, int);
+ void simple_line(const position &, const position &);
+ void simple_spline(const position &, const position *, int n);
+ void simple_arc(const position &, const position &, const position &);
+ void simple_circle(int, const position &, double rad);
+ void simple_ellipse(int, const position &, const distance &);
+ void simple_polygon(int, const position *, int);
+ void line_thickness(double p);
+ void set_fill(double);
+ void set_color(char *, char *);
+ void reset_color();
+ char *get_last_filled();
+ char *get_outline_color();
+ position transform(const position &);
+};
+
+output *make_troff_output()
+{
+ return new troff_output;
+}
+
+troff_output::troff_output()
+: last_filename(0), last_line_thickness(BAD_THICKNESS),
+ last_fill(-1.0), last_filled(0), last_outlined(0)
+{
+}
+
+troff_output::~troff_output()
+{
+ free((char *)last_filename);
+}
+
+inline position troff_output::transform(const position &pos)
+{
+ return position((pos.x - upper_left.x)/scale,
+ (upper_left.y - pos.y)/scale);
+}
+
+#define FILL_REG "00"
+
+// If this register > 0, then pic will generate \X'ps: ...' commands
+// if the aligned attribute is used.
+#define GROPS_REG "0p"
+
+// If this register is defined, geqn won't produce '\x's.
+#define EQN_NO_EXTRA_SPACE_REG "0x"
+
+void troff_output::start_picture(double sc,
+ const position &ll, const position &ur)
+{
+ upper_left.x = ll.x;
+ upper_left.y = ur.y;
+ scale = compute_scale(sc, ll, ur);
+ height = (ur.y - ll.y)/scale;
+ double width = (ur.x - ll.x)/scale;
+ printf(".PS %.3fi %.3fi", height, width);
+ if (args)
+ printf(" %s\n", args);
+ else
+ putchar('\n');
+ printf(".\\\" %g %g %g %g\n", ll.x, ll.y, ur.x, ur.y);
+ printf(".\\\" %.3fi %.3fi %.3fi %.3fi\n", 0.0, height, width, 0.0);
+ printf(".nr " FILL_REG " \\n(.u\n.nf\n");
+ printf(".nr " EQN_NO_EXTRA_SPACE_REG " 1\n");
+ // This guarantees that if the picture is used in a diversion it will
+ // have the right width.
+ printf("\\h'%.3fi'\n.sp -1\n", width);
+}
+
+void troff_output::finish_picture()
+{
+ line_thickness(BAD_THICKNESS);
+ last_fill = -1.0; // force it to be reset for each picture
+ reset_color();
+ if (!(want_flyback || want_alternate_flyback))
+ printf(".sp %.3fi+1\n", height);
+ printf(".if \\n(" FILL_REG " .fi\n");
+ printf(".br\n");
+ printf(".nr " EQN_NO_EXTRA_SPACE_REG " 0\n");
+ // this is a little gross
+ set_location(current_filename, current_lineno);
+ if (want_flyback)
+ fputs(".PF\n", stdout);
+ else if (want_alternate_flyback)
+ fputs(".PY\n", stdout);
+ else
+ fputs(".PE\n", stdout);
+}
+
+void troff_output::command(const char *s,
+ const char *filename, int lineno)
+{
+ if (filename != 0)
+ set_location(filename, lineno);
+ fputs(s, stdout);
+ putchar('\n');
+}
+
+void troff_output::simple_circle(int filled, const position &cent, double rad)
+{
+ position c = transform(cent);
+ printf("\\h'%.3fi'"
+ "\\v'%.3fi'"
+ "\\D'%c %.3fi'"
+ "\n.sp -1\n",
+ c.x - rad/scale,
+ c.y,
+ (filled ? 'C' : 'c'),
+ rad*2.0/scale);
+}
+
+void troff_output::simple_ellipse(int filled, const position &cent,
+ const distance &dim)
+{
+ position c = transform(cent);
+ printf("\\h'%.3fi'"
+ "\\v'%.3fi'"
+ "\\D'%c %.3fi %.3fi'"
+ "\n.sp -1\n",
+ c.x - dim.x/(2.0*scale),
+ c.y,
+ (filled ? 'E' : 'e'),
+ dim.x/scale, dim.y/scale);
+}
+
+void troff_output::simple_arc(const position &start, const distance &cent,
+ const distance &end)
+{
+ position s = transform(start);
+ position c = transform(cent);
+ distance cv = c - s;
+ distance ev = transform(end) - c;
+ printf("\\h'%.3fi'"
+ "\\v'%.3fi'"
+ "\\D'a %.3fi %.3fi %.3fi %.3fi'"
+ "\n.sp -1\n",
+ s.x, s.y, cv.x, cv.y, ev.x, ev.y);
+}
+
+void troff_output::simple_line(const position &start, const position &end)
+{
+ position s = transform(start);
+ distance ev = transform(end) - s;
+ printf("\\h'%.3fi'"
+ "\\v'%.3fi'"
+ "\\D'l %.3fi %.3fi'"
+ "\n.sp -1\n",
+ s.x, s.y, ev.x, ev.y);
+}
+
+void troff_output::simple_spline(const position &start,
+ const position *v, int n)
+{
+ position pos = transform(start);
+ printf("\\h'%.3fi'"
+ "\\v'%.3fi'",
+ pos.x, pos.y);
+ fputs("\\D'~ ", stdout);
+ for (int i = 0; i < n; i++) {
+ position temp = transform(v[i]);
+ distance d = temp - pos;
+ pos = temp;
+ if (i != 0)
+ putchar(' ');
+ printf("%.3fi %.3fi", d.x, d.y);
+ }
+ printf("'\n.sp -1\n");
+}
+
+// a solid polygon
+
+void troff_output::simple_polygon(int filled, const position *v, int n)
+{
+ position pos = transform(v[0]);
+ printf("\\h'%.3fi'"
+ "\\v'%.3fi'",
+ pos.x, pos.y);
+ printf("\\D'%c ", (filled ? 'P' : 'p'));
+ for (int i = 1; i < n; i++) {
+ position temp = transform(v[i]);
+ distance d = temp - pos;
+ pos = temp;
+ if (i != 1)
+ putchar(' ');
+ printf("%.3fi %.3fi", d.x, d.y);
+ }
+ printf("'\n.sp -1\n");
+}
+
+const double TEXT_AXIS = 0.22; // in ems
+
+static const char *choose_delimiter(const char *text)
+{
+ if (strchr(text, '\'') == 0)
+ return "'";
+ else
+ return "\\(ts";
+}
+
+void troff_output::text(const position &center, text_piece *v, int n,
+ double ang)
+{
+ line_thickness(BAD_THICKNESS); // text might use lines (e.g., in equations)
+ int rotate_flag = 0;
+ if (driver_extension_flag && ang != 0.0) {
+ rotate_flag = 1;
+ position c = transform(center);
+ printf(".if \\n(" GROPS_REG " \\{\\\n"
+ "\\h'%.3fi'"
+ "\\v'%.3fi'"
+ "\\X'ps: exec gsave currentpoint 2 copy translate %.4f rotate neg exch neg exch translate'"
+ "\n.sp -1\n"
+ ".\\}\n",
+ c.x, c.y, -ang*180.0/M_PI);
+ }
+ for (int i = 0; i < n; i++)
+ if (v[i].text != 0 && *v[i].text != '\0') {
+ position c = transform(center);
+ if (v[i].filename != 0)
+ set_location(v[i].filename, v[i].lineno);
+ printf("\\h'%.3fi", c.x);
+ const char *delim = choose_delimiter(v[i].text);
+ if (v[i].adj.h == RIGHT_ADJUST)
+ printf("-\\w%s%s%su", delim, v[i].text, delim);
+ else if (v[i].adj.h != LEFT_ADJUST)
+ printf("-(\\w%s%s%su/2u)", delim, v[i].text, delim);
+ putchar('\'');
+ printf("\\v'%.3fi-(%dv/2u)+%dv+%.2fm",
+ c.y,
+ n - 1,
+ i,
+ TEXT_AXIS);
+ if (v[i].adj.v == ABOVE_ADJUST)
+ printf("-.5v");
+ else if (v[i].adj.v == BELOW_ADJUST)
+ printf("+.5v");
+ putchar('\'');
+ fputs(v[i].text, stdout);
+ fputs("\n.sp -1\n", stdout);
+ }
+ if (rotate_flag)
+ printf(".if \\n(" GROPS_REG " \\{\\\n"
+ "\\X'ps: exec grestore'\n.sp -1\n"
+ ".\\}\n");
+}
+
+void troff_output::line_thickness(double p)
+{
+ if (p < 0.0)
+ p = RELATIVE_THICKNESS;
+ if (driver_extension_flag && p != last_line_thickness) {
+ printf("\\D't %.3fp'\\h'%.3fp'\n.sp -1\n", p, -p);
+ last_line_thickness = p;
+ }
+}
+
+void troff_output::set_fill(double f)
+{
+ if (driver_extension_flag && f != last_fill) {
+ // \D'Fg ...' emits a node only in compatibility mode,
+ // thus we add a dummy node
+ printf("\\&\\D'Fg %.3f'\n.sp -1\n", 1.0 - f);
+ last_fill = f;
+ }
+ if (last_filled) {
+ free(last_filled);
+ last_filled = 0;
+ printf(".fcolor\n");
+ }
+}
+
+void troff_output::set_color(char *color_fill, char *color_outlined)
+{
+ if (driver_extension_flag) {
+ if (last_filled || last_outlined) {
+ reset_color();
+ }
+ // .gcolor and .fcolor emit a node in compatibility mode only,
+ // but that won't work anyway
+ if (color_fill) {
+ printf(".fcolor %s\n", color_fill);
+ last_filled = strsave(color_fill);
+ }
+ if (color_outlined) {
+ printf(".gcolor %s\n", color_outlined);
+ last_outlined = strsave(color_outlined);
+ }
+ }
+}
+
+void troff_output::reset_color()
+{
+ if (driver_extension_flag) {
+ if (last_filled) {
+ printf(".fcolor\n");
+ free(last_filled);
+ last_filled = 0;
+ }
+ if (last_outlined) {
+ printf(".gcolor\n");
+ free(last_outlined);
+ last_outlined = 0;
+ }
+ }
+}
+
+char *troff_output::get_last_filled()
+{
+ return last_filled;
+}
+
+char *troff_output::get_outline_color()
+{
+ return last_outlined;
+}
+
+const double DOT_AXIS = .044;
+
+void troff_output::dot(const position &cent, const line_type &lt)
+{
+ if (driver_extension_flag) {
+ line_thickness(lt.thickness);
+ simple_line(cent, cent);
+ }
+ else {
+ position c = transform(cent);
+ printf("\\h'%.3fi-(\\w'.'u/2u)'"
+ "\\v'%.3fi+%.2fm'"
+ ".\n.sp -1\n",
+ c.x,
+ c.y,
+ DOT_AXIS);
+ }
+}
+
+void troff_output::set_location(const char *s, int n)
+{
+ if (last_filename != 0 && strcmp(s, last_filename) == 0)
+ printf(".lf %d\n", n);
+ else {
+ printf(".lf %d %s\n", n, s);
+ char *lfn = strdup(s);
+ if (0 == lfn)
+ fatal("memory allocation failure while copying file name");
+ last_filename = lfn;
+ }
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/preconv/preconv.1.man b/src/preproc/preconv/preconv.1.man
new file mode 100644
index 0000000..1535bae
--- /dev/null
+++ b/src/preproc/preconv/preconv.1.man
@@ -0,0 +1,559 @@
+.TH preconv @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+preconv \- prepare files for typesetting with
+.I groff
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2006-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_preconv_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY preconv
+.RB [ \-dr ]
+.RB [ \-D\~\c
+.IR fallback-encoding ]
+.RB [ \-e\~\c
+.IR encoding ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY preconv
+.B \-h
+.
+.SY preconv
+.B \-\-help
+.YS
+.
+.
+.SY preconv
+.B \-v
+.
+.SY preconv
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I preconv
+reads each
+.IR file ,
+converts its encoded characters to a form
+.MR @g@troff @MAN1EXT@
+can interpret,
+and sends the result to the standard output stream.
+.
+Currently,
+this means that code points in the range 0\[en]127
+(in US-ASCII,
+ISO\~8859,
+or Unicode)
+remain as-is and the remainder are converted to the
+.I groff
+special character form
+.RB \[lq] \[rs][\c
+.BI u XXXX ]\c
+\[rq],
+where
+.I XXXX
+is a hexadecimal number of four to six digits corresponding to a Unicode
+code point.
+.
+By default,
+.I preconv
+also inserts a
+.I roff
+.B .lf
+request at the beginning of each
+.IR file ,
+identifying it for the benefit of later processing
+(including diagnostic messages);
+the
+.B \-r
+option suppresses this behavior.
+.
+.
+.PP
+In typical usage scenarios,
+.I preconv
+need not be run directly;
+instead it should be invoked with the
+.B \-k
+or
+.B \-K
+options of
+.IR groff .
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+the standard input stream is read.
+.
+.
+.PP
+.I preconv
+tries to find the input encoding with the following algorithm,
+stopping at the first success.
+.
+.
+.IP 1. 4n
+If the input encoding has been explicitly specified with option
+.BR \-e ,
+use it.
+.
+.
+.IP 2.
+If the input starts with a Unicode Byte Order Mark,
+determine the encoding as UTF-8,
+UTF-16,
+or UTF-32 accordingly.
+.
+.
+.IP 3.
+If the input stream is seekable,
+check the first and second input lines for a recognized GNU\~Emacs
+file-local variable identifying the character encoding,
+here referred to as the \[lq]coding tag\[rq] for brevity.
+.
+If found,
+use it.
+.
+.
+.IP 4.
+If the input stream is seekable,
+and if the
+.I uchardet
+library is available on the system,
+use it to try to infer the encoding of the file.
+.
+.
+.IP 5.
+If the
+.B \-D
+option specifies an encoding,
+use it.
+.
+.
+.IP 6.
+Use the encoding specified by the current locale
+.RI ( LC_CTYPE ),
+unless the locale is
+\[lq]C\[rq],
+\[lq]POSIX\[rq],
+or empty,
+in which case assume Latin-1
+(ISO\~8859-1).
+.
+.
+.PP
+The coding tag and
+.I uchardet
+methods in the above procedure rely upon a seekable input stream;
+when
+.I preconv
+reads from a pipe,
+the stream is not seekable,
+and these detection methods are skipped.
+.
+If character encoding detection of your input files is unreliable,
+arrange for one of the other methods to succeed by using
+.IR preconv 's
+.B \-D
+or
+.B \-e
+options,
+or by configuring your locale appropriately.
+.
+.I groff
+also supports a
+.I \%GROFF_ENCODING
+environment variable,
+which can be overridden by its
+.B \-K
+option.
+.
+Valid values for
+(or parameters to)
+all of these are enumerated in the lists of recognized coding tags in
+the next subsection,
+and are further influenced by
+.I iconv
+library support.
+.
+.
+.\" ====================================================================
+.SS "Coding tags"
+.\" ====================================================================
+.
+Text editors that support more than a single character encoding need
+tags within the input files to mark the file's encoding.
+.
+While it is possible to guess the right input encoding with the help of
+heuristics that are reliable for a preponderance of natural language
+texts,
+they are not absolutely reliable.
+.
+Heuristics can fail on inputs that are too short or don't represent a
+natural language.
+.
+.
+.PP
+Consequently,
+.I preconv
+supports the coding tag convention used by GNU\~Emacs
+(with some restrictions).
+.
+This notation appears in specially marked regions of an input file
+designated for \[lq]file-local variables\[rq].
+.
+.
+.PP
+.I preconv
+interprets the following syntax if it occurs in a
+.I roff
+comment
+in the first or second line of the input file.
+.
+Both \[lq]\[rs]"\[rq] and \[lq]\[rs]#\[rq] comment forms are recognized,
+but the control
+(or no-break control)
+character must be the default and must begin the line.
+.
+Similarly,
+the escape character must be the default.
+.
+.
+.RS
+.EX
+.B \-*\- \c
+.RB [.\|.\|. ; ]\~\c
+.B coding: \c
+.I encoding\c
+.RB [ ;\~ .\|.\|.\&]\~\c
+.B \-*\-
+.EE
+.RE
+.
+.
+.PP
+The only variable
+.I preconv
+interprets is \[lq]coding\[rq],
+which can take the values listed below.
+.
+.
+.PP
+The following list comprises all MIME \[lq]charset\[rq] parameter values
+recognized,
+case-insensitively,
+by
+.IR preconv .
+.
+.RS
+\%big5,
+\%cp1047,
+\%euc\-jp,
+\%euc\-kr,
+\%gb2312,
+\%iso\-8859\-1,
+\%iso\-8859\-2,
+\%iso\-8859\-5,
+\%iso\-8859\-7,
+\%iso\-8859\-9,
+\%iso\-8859\-13,
+\%iso\-8859\-15,
+\%koi8\-r,
+\%us\-ascii,
+\%utf\-8,
+\%utf\-16,
+\%utf\-16be,
+\%utf\-16le
+.RE
+.
+.
+.PP
+In addition,
+the following list of other coding tags is recognized,
+each of which is mapped to an appropriate value from the list above.
+.
+.RS
+\%ascii,
+\%chinese\-big5,
+\%chinese\-euc,
+\%chinese\-iso\-8bit,
+\%cn\-big5,
+\%cn\-gb,
+\%cn\-gb\-2312,
+\%cp878,
+\%csascii,
+\%csisolatin1,
+\%cyrillic\-iso\-8bit,
+\%cyrillic\-koi8,
+\%euc\-china,
+\%euc\-cn,
+\%euc\-japan,
+\%euc\-japan\-1990,
+\%euc\-korea,
+\%greek\-iso\-8bit,
+\%iso\-10646/utf8,
+\%iso\-10646/utf\-8,
+\%iso\-latin\-1,
+\%iso\-latin\-2,
+\%iso\-latin\-5,
+\%iso\-latin\-7,
+\%iso\-latin\-9,
+\%japanese\-euc,
+\%japanese\-iso\-8bit,
+\%jis8,
+\%koi8,
+\%korean\-euc,
+\%korean\-iso\-8bit,
+\%latin\-0,
+\%latin1,
+\%latin\-1,
+\%latin\-2,
+\%latin\-5,
+\%latin\-7,
+\%latin\-9,
+\%mule\-utf\-8,
+\%mule\-utf\-16,
+\%mule\-utf\-16be,
+\%mule\-utf\-16\-be,
+\%mule\-utf\-16be\-with\-signature,
+\%mule\-utf\-16le,
+\%mule\-utf\-16\-le,
+\%mule\-utf\-16le\-with\-signature,
+\%utf8,
+\%utf\-16\-be,
+\%utf\-16\-be\-with\-signature,
+\%utf\-16be\-with\-signature,
+\%utf\-16\-le,
+\%utf\-16\-le\-with\-signature,
+\%utf\-16le\-with\-signature
+.RE
+.
+.
+.PP
+Trailing
+\[lq]\-dos\[rq],
+\[lq]\-unix\[rq],
+and
+\[lq]\-mac\[rq]
+suffixes on coding tags
+(which indicate the end-of-line convention used in the file)
+are disregarded for the purpose of comparison with the above tags.
+.
+.
+.\" ====================================================================
+.SS "\f[I]iconv\f[] support"
+.\" ====================================================================
+.
+While
+.I preconv
+recognizes all of the coding tags listed above,
+it is capable on its own of interpreting only three encodings:
+Latin-1,
+code page 1047,
+and UTF-8.
+.
+If
+.I iconv
+support is configured at compile time and available at run time,
+all others are passed to
+.I iconv
+library functions,
+which may recognize many additional encoding strings.
+.
+The command
+.RB \[lq] preconv\~\-v \[rq]
+discloses whether
+.I iconv
+support is configured.
+.
+.
+.PP
+The use of
+.I iconv
+means that characters in the input that encode invalid code points for
+that encoding may be dropped from the output stream or mapped to the
+Unicode replacement character
+(U+FFFD).
+.
+Compare the following examples using the input \[lq]caf\['e]\[rq]
+(note the \[lq]e\[rq] with an acute accent),
+which due to its short length challenges inference of the encoding used.
+.
+.RS
+.EX
+printf \[aq]caf\[rs]351\[rs]n\[aq] | LC_ALL=en_US.UTF\-8 preconv
+printf \[aq]caf\[rs]351\[rs]n\[aq] | preconv \-e us\-ascii
+printf \[aq]caf\[rs]351\[rs]n\[aq] | preconv \-e latin\-1
+.EE
+.RE
+.
+The fate of the accented \[lq]e\[rq] differs in each case.
+.
+In the first,
+.I uchardet
+fails to detect an encoding
+(though the library on your system may behave differently)
+and
+.I preconv
+falls back to the locale settings,
+where octal 351 starts an incomplete UTF-8 sequence and results in the
+Unicode replacement character.
+.
+In the second,
+it is not a representable character in the declared input encoding of
+US-ASCII and is discarded by
+.IR iconv .
+.
+In the last,
+it is correctly detected and mapped.
+.
+.
+.\" ====================================================================
+.SS Limitations
+.\" ====================================================================
+.
+.I preconv
+cannot perform any transformation on input that it cannot see.
+.
+Examples include files that are interpolated by preprocessors that run
+subsequently,
+including
+.MR @g@soelim @MAN1EXT@ ;
+files included by
+.I @g@troff
+itself through
+.RB \[lq] so \[rq]
+and similar requests;
+and string definitions passed to
+.I @g@troff
+through its
+.B \-d
+command-line option.
+.
+.
+.P
+.I preconv
+assumes that its input uses the default escape character,
+a backslash
+.BR \[rs] ,
+and writes special character escape sequences accordingly.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-d
+Emit debugging messages to the standard error stream.
+.
+.
+.TP
+.BI \-D\~ fallback-encoding
+Report
+.I fallback-encoding
+if all detection methods fail.
+.
+.
+.TP
+.BI \-e\~ encoding
+Skip detection and assume
+.IR encoding ;
+see
+.IR groff 's
+.B \-K
+option.
+.
+.
+.TP
+.B \-r
+Write files \[lq]raw\[rq];
+do not add
+.B .lf
+requests.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@ ,
+.MR iconv 3 ,
+.MR locale 7
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_preconv_1_man_C]
+.do rr *groff_preconv_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/preproc/preconv/preconv.am b/src/preproc/preconv/preconv.am
new file mode 100644
index 0000000..199ff66
--- /dev/null
+++ b/src/preproc/preconv/preconv.am
@@ -0,0 +1,37 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += preconv
+preconv_LDADD = libgroff.a $(LIBM) $(LIBICONV) $(UCHARDET_LIBS) \
+ lib/libgnu.a
+preconv_SOURCES = src/preproc/preconv/preconv.cpp
+preconv_CPPFLAGS = $(AM_CPPFLAGS) $(UCHARDET_CFLAGS)
+man1_MANS += src/preproc/preconv/preconv.1
+EXTRA_DIST += src/preproc/preconv/preconv.1.man
+
+preconv_TESTS = \
+ src/preproc/preconv/tests/do-not-seek-the-unseekable.sh \
+ src/preproc/preconv/tests/smoke-test.sh
+TESTS += $(preconv_TESTS)
+EXTRA_DIST += $(preconv_TESTS)
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/preconv/preconv.cpp b/src/preproc/preconv/preconv.cpp
new file mode 100644
index 0000000..d403425
--- /dev/null
+++ b/src/preproc/preconv/preconv.cpp
@@ -0,0 +1,1318 @@
+/* Copyright (C) 2005-2020 Free Software Foundation, Inc.
+ Written by Werner Lemberg (wl@gnu.org)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+#ifdef HAVE_UCHARDET
+#include <uchardet/uchardet.h>
+#endif
+
+#include "errarg.h"
+#include "error.h"
+#include "localcharset.h"
+#include "nonposix.h"
+#include "stringclass.h"
+#include "lf.h"
+
+#include <locale.h>
+
+#if HAVE_ICONV
+# include <iconv.h>
+# ifdef WORDS_BIGENDIAN
+# define UNICODE "UTF-32BE"
+# else
+# define UNICODE "UTF-32LE"
+# endif
+#endif
+
+#define MAX_VAR_LEN 100
+
+extern "C" const char *Version_string;
+
+char fallback_encoding[MAX_VAR_LEN];
+char user_encoding[MAX_VAR_LEN];
+char encoding_string[MAX_VAR_LEN];
+bool is_debugging = false;
+int raw_flag = 0;
+
+struct conversion {
+ const char *from;
+ const char *to;
+};
+
+// The official list of MIME tags can be found at
+//
+// http://www.iana.org/assignments/character-sets
+//
+// For encodings which don't have a MIME tag we use GNU iconv's encoding
+// names (which also work with the portable GNU libiconv package). They
+// are marked with '*'.
+//
+// Encodings specific to XEmacs and Emacs are marked as such; no mark means
+// that they are used by both Emacs and XEmacs.
+//
+// Encodings marked with '--' are special to Emacs, XEmacs, or other
+// applications and shouldn't be used for data exchange.
+//
+// 'Not covered' means that the encoding can be handled neither by GNU iconv
+// nor by libiconv, or just one of them has support for it.
+//
+// A special case is VIQR encoding: Despite of having a MIME tag it is
+// missing in both libiconv 1.10 and iconv (coming with GNU libc 2.3.6).
+//
+// Finally, we add all aliases of GNU iconv for 'ascii', 'latin1', and
+// 'utf8' to catch those encoding names before iconv is called.
+//
+// Note that most entries are commented out -- only a small, (rather)
+// reliable and stable subset of encodings is recognized (for coding tags)
+// which are still in greater use today (January 2006). Most notably, all
+// Windows-specific encodings are not selected because they lack stability:
+// Microsoft has changed the mappings instead of creating new versions.
+//
+// Please contact the groff list if you find the selection inadequate.
+
+static const conversion
+emacs_to_mime[] = {
+ {"ascii", "US-ASCII"}, // Emacs
+ {"big5", "Big5"},
+ {"chinese-big5", "Big5"}, // Emacs
+ {"chinese-euc", "GB2312"}, // XEmacs
+ {"chinese-iso-8bit", "GB2312"}, // Emacs
+ {"cn-big5", "Big5"},
+ {"cn-gb", "GB2312"}, // Emacs
+ {"cn-gb-2312", "GB2312"},
+ {"cp878", "KOI8-R"}, // Emacs
+ {"cp1047", "CP1047"}, // EBCDIC
+ {"csascii", "US-ASCII"}, // alias
+ {"csisolatin1", "ISO-8859-1"}, // alias
+ {"cyrillic-iso-8bit", "ISO-8859-5"}, // Emacs
+ {"cyrillic-koi8", "KOI8-R"}, // not KOI8!, Emacs
+ {"euc-china", "GB2312"}, // Emacs
+ {"euc-cn", "GB2312"}, // Emacs
+ {"euc-japan", "EUC-JP"},
+ {"euc-japan-1990", "EUC-JP"}, // Emacs
+ {"euc-jp", "EUC-JP"},
+ {"euc-korea", "EUC-KR"},
+ {"euc-kr", "EUC-KR"},
+ {"gb2312", "GB2312"},
+ {"greek-iso-8bit", "ISO-8859-7"},
+ {"iso-10646/utf8", "UTF-8"}, // alias
+ {"iso-10646/utf-8", "UTF-8"}, // alias
+ {"iso-8859-1", "ISO-8859-1"},
+ {"iso-8859-13", "ISO-8859-13"}, // Emacs
+ {"iso-8859-15", "ISO-8859-15"},
+ {"iso-8859-2", "ISO-8859-2"},
+ {"iso-8859-5", "ISO-8859-5"},
+ {"iso-8859-7", "ISO-8859-7"},
+ {"iso-8859-9", "ISO-8859-9"},
+ {"iso-latin-1", "ISO-8859-1"},
+ {"iso-latin-2", "ISO-8859-2"}, // Emacs
+ {"iso-latin-5", "ISO-8859-9"}, // Emacs
+ {"iso-latin-7", "ISO-8859-13"}, // Emacs
+ {"iso-latin-9", "ISO-8859-15"}, // Emacs
+ {"japanese-iso-8bit", "EUC-JP"}, // Emacs
+ {"japanese-euc", "EUC-JP"}, // XEmacs
+ {"jis8", "EUC-JP"}, // XEmacs
+ {"koi8", "KOI8-R"}, // not KOI8!, Emacs
+ {"koi8-r", "KOI8-R"},
+ {"korean-euc", "EUC-KR"}, // XEmacs
+ {"korean-iso-8bit", "EUC-KR"}, // Emacs
+ {"latin1", "ISO-8859-1"}, // alias
+ {"latin-0", "ISO-8859-15"}, // Emacs
+ {"latin-1", "ISO-8859-1"}, // Emacs
+ {"latin-2", "ISO-8859-2"}, // Emacs
+ {"latin-5", "ISO-8859-9"}, // Emacs
+ {"latin-7", "ISO-8859-13"}, // Emacs
+ {"latin-9", "ISO-8859-15"}, // Emacs
+ {"mule-utf-16", "UTF-16"}, // Emacs
+ {"mule-utf-16be", "UTF-16BE"}, // Emacs
+ {"mule-utf-16-be", "UTF-16BE"}, // Emacs
+ {"mule-utf-16be-with-signature", "UTF-16"}, // Emacs, not UTF-16BE
+ {"mule-utf-16le", "UTF-16LE"}, // Emacs
+ {"mule-utf-16-le", "UTF-16LE"}, // Emacs
+ {"mule-utf-16le-with-signature", "UTF-16"}, // Emacs, not UTF-16LE
+ {"mule-utf-8", "UTF-8"}, // Emacs
+ {"us-ascii", "US-ASCII"}, // Emacs
+ {"utf8", "UTF-8"}, // alias
+ {"utf-16", "UTF-16"}, // Emacs
+ {"utf-16be", "UTF-16BE"}, // Emacs
+ {"utf-16-be", "UTF-16BE"}, // Emacs
+ {"utf-16be-with-signature", "UTF-16"}, // Emacs, not UTF-16BE
+ {"utf-16-be-with-signature", "UTF-16"}, // Emacs, not UTF-16BE
+ {"utf-16le", "UTF-16LE"}, // Emacs
+ {"utf-16-le", "UTF-16LE"}, // Emacs
+ {"utf-16le-with-signature", "UTF-16"}, // Emacs, not UTF-16LE
+ {"utf-16-le-with-signature", "UTF-16"}, // Emacs, not UTF-16LE
+ {"utf-8", "UTF-8"}, // Emacs
+
+// {"alternativnyj", ""}, // ?
+// {"arabic-iso-8bit", "ISO-8859-6"}, // Emacs
+// {"binary", ""}, // --
+// {"chinese-hz", "HZ-GB-2312"}, // Emacs
+// {"chinese-iso-7bit", "ISO-2022-CN"}, // Emacs
+// {"chinese-iso-8bit-with-esc", ""}, // --
+// {"compound-text", ""}, // --
+// {"compound-text-with-extension", ""}, // --
+// {"cp1125", "cp1125"}, // *
+// {"cp1250", "windows-1250"},// Emacs
+// {"cp1251", "windows-1251"},// Emacs
+// {"cp1252", "windows-1252"},// Emacs
+// {"cp1253", "windows-1253"},// Emacs
+// {"cp1254", "windows-1254"},// Emacs
+// {"cp1255", "windows-1255"},// Emacs
+// {"cp1256", "windows-1256"},// Emacs
+// {"cp1257", "windows-1257"},// Emacs
+// {"cp1258", "windows-1258"},// Emacs
+// {"cp437", "cp437"}, // Emacs
+// {"cp720", ""}, // not covered
+// {"cp737", "cp737"}, // *, Emacs
+// {"cp775", "cp775"}, // Emacs
+// {"cp850", "cp850"}, // Emacs
+// {"cp851", "cp851"}, // Emacs
+// {"cp852", "cp852"}, // Emacs
+// {"cp855", "cp855"}, // Emacs
+// {"cp857", "cp857"}, // Emacs
+// {"cp860", "cp860"}, // Emacs
+// {"cp861", "cp861"}, // Emacs
+// {"cp862", "cp862"}, // Emacs
+// {"cp863", "cp863"}, // Emacs
+// {"cp864", "cp864"}, // Emacs
+// {"cp865", "cp865"}, // Emacs
+// {"cp866", "cp866"}, // Emacs
+// {"cp866u", "cp1125"}, // *, Emacs
+// {"cp869", "cp869"}, // Emacs
+// {"cp874", "cp874"}, // *, Emacs
+// {"cp932", "cp932"}, // *, Emacs
+// {"cp936", "cp936"}, // Emacs
+// {"cp949", "cp949"}, // *, Emacs
+// {"cp950", "cp950"}, // *, Emacs
+// {"ctext", ""}, // --
+// {"ctext-no-compositions", ""}, // --
+// {"ctext-with-extensions", ""}, // --
+// {"cyrillic-alternativnyj", ""}, // ?, Emacs
+// {"cyrillic-iso-8bit-with-esc", ""}, // --
+// {"cyrillic-koi8-t", "KOI8-T"}, // *, Emacs
+// {"devanagari", ""}, // not covered
+// {"dos", ""}, // --
+// {"emacs-mule", ""}, // --
+// {"euc-jisx0213", "EUC-JISX0213"},// *, XEmacs?
+// {"euc-jisx0213-with-esc", ""}, // XEmacs?
+// {"euc-taiwan", "EUC-TW"}, // *, Emacs
+// {"euc-tw", "EUC-TW"}, // *, Emacs
+// {"georgian-ps", "GEORGIAN-PS"}, // *, Emacs
+// {"greek-iso-8bit-with-esc", ""}, // --
+// {"hebrew-iso-8bit", "ISO-8859-8"}, // Emacs
+// {"hebrew-iso-8bit-with-esc", ""}, // --
+// {"hz", "HZ-GB-2312"},
+// {"hz-gb-2312", "HZ-GB-2312"},
+// {"in-is13194", ""}, // not covered
+// {"in-is13194-devanagari", ""}, // not covered
+// {"in-is13194-with-esc", ""}, // --
+// {"iso-2022-7", ""}, // XEmacs?
+// {"iso-2022-7bit", ""}, // --
+// {"iso-2022-7bit-lock", ""}, // --
+// {"iso-2022-7bit-lock-ss2", ""}, // --
+// {"iso-2022-7bit-ss2", ""}, // --
+// {"iso-2022-8", ""}, // XEmacs?
+// {"iso-2022-8bit", ""}, // XEmacs?
+// {"iso-2022-8bit-lock", ""}, // XEmacs?
+// {"iso-2022-8bit-lock-ss2", ""}, // XEmacs?
+// {"iso-2022-8bit-ss2", ""}, // --
+// {"iso-2022-cjk", ""}, // --
+// {"iso-2022-cn", "ISO-2022-CN"}, // Emacs
+// {"iso-2022-cn-ext", "ISO-2022-CN-EXT"},// Emacs
+// {"iso-2022-int-1", ""}, // --
+// {"iso-2022-jp", "ISO-2022-JP"},
+// {"iso-2022-jp-1978-irv", "ISO-2022-JP"},
+// {"iso-2022-jp-2", "ISO-2022-JP-2"},
+// {"iso-2022-jp-3", "ISO-2022-JP-3"},// *, XEmacs?
+// {"iso-2022-jp-3-compatible", ""}, // XEmacs?
+// {"iso-2022-jp-3-strict", "ISO-2022-JP-3"},// *, XEmacs?
+// {"iso-2022-kr", "ISO-2022-KR"},
+// {"iso-2022-lock", ""}, // XEmacs?
+// {"iso-8859-10", "ISO-8859-10"}, // Emacs
+// {"iso-8859-11", "ISO-8859-11"}, // *, Emacs
+// {"iso-8859-14", "ISO-8859-14"}, // Emacs
+// {"iso-8859-16", "ISO-8859-16"},
+// {"iso-8859-3", "ISO-8859-3"},
+// {"iso-8859-4", "ISO-8859-4"},
+// {"iso-8859-6", "ISO-8859-6"},
+// {"iso-8859-8", "ISO-8859-8"},
+// {"iso-8859-8-e", "ISO-8859-8"},
+// {"iso-8859-8-i", "ISO-8859-8"}, // Emacs
+// {"iso-latin-10", "ISO-8859-16"}, // Emacs
+// {"iso-latin-1-with-esc", ""}, // --
+// {"iso-latin-2-with-esc", ""}, // --
+// {"iso-latin-3", "ISO-8859-3"}, // Emacs
+// {"iso-latin-3-with-esc", ""}, // --
+// {"iso-latin-4", "ISO-8859-4"}, // Emacs
+// {"iso-latin-4-with-esc", ""}, // --
+// {"iso-latin-5-with-esc", ""}, // --
+// {"iso-latin-6", "ISO-8859-10"}, // Emacs
+// {"iso-latin-8", "ISO-8859-14"}, // Emacs
+// {"iso-safe", ""}, // --
+// {"japanese-iso-7bit-1978-irv", "ISO-2022-JP"}, // Emacs
+// {"japanese-iso-8bit-with-esc", ""}, // --
+// {"japanese-shift-jis", "Shift_JIS"}, // Emacs
+// {"japanese-shift-jisx0213", ""}, // XEmacs?
+// {"jis7", "ISO-2022-JP"}, // Xemacs
+// {"junet", "ISO-2022-JP"},
+// {"koi8-t", "KOI8-T"}, // *, Emacs
+// {"koi8-u", "KOI8-U"}, // Emacs
+// {"korean-iso-7bit-lock", "ISO-2022-KR"},
+// {"korean-iso-8bit-with-esc", ""}, // --
+// {"lao", ""}, // not covered
+// {"lao-with-esc", ""}, // --
+// {"latin-10", "ISO-8859-16"}, // Emacs
+// {"latin-3", "ISO-8859-3"}, // Emacs
+// {"latin-4", "ISO-8859-4"}, // Emacs
+// {"latin-6", "ISO-8859-10"}, // Emacs
+// {"latin-8", "ISO-8859-14"}, // Emacs
+// {"mac", ""}, // --
+// {"mac-roman", "MACINTOSH"}, // Emacs
+// {"mik", ""}, // not covered
+// {"next", "NEXTSTEP"}, // *, Emacs
+// {"no-conversion", ""}, // --
+// {"old-jis", "ISO-2022-JP"},
+// {"pt154", "PT154"}, // Emacs
+// {"raw-text", ""}, // --
+// {"ruscii", "cp1125"}, // *, Emacs
+// {"shift-jis", "Shift_JIS"}, // XEmacs
+// {"shift_jis", "Shift_JIS"},
+// {"shift_jisx0213", "Shift_JISX0213"},// *, XEmacs?
+// {"sjis", "Shift_JIS"}, // Emacs
+// {"tcvn", "TCVN"}, // *, Emacs
+// {"tcvn-5712", "TCVN"}, // *, Emacs
+// {"thai-tis620", "TIS-620"},
+// {"thai-tis620-with-esc", ""}, // --
+// {"th-tis620", "TIS-620"},
+// {"tibetan", ""}, // not covered
+// {"tibetan-iso-8bit", ""}, // not covered
+// {"tibetan-iso-8bit-with-esc", ""}, // --
+// {"tis-620", "TIS-620"},
+// {"tis620", "TIS-620"},
+// {"undecided", ""}, // --
+// {"unix", ""}, // --
+// {"utf-7", "UTF-7"}, // Emacs
+// {"utf-7-safe", ""}, // XEmacs?
+// {"utf-8-ws", "UTF-8"}, // XEmacs?
+// {"vietnamese-tcvn", "TCVN"}, // *, Emacs
+// {"vietnamese-viqr", "VIQR"}, // not covered
+// {"vietnamese-viscii", "VISCII"},
+// {"vietnamese-vscii", ""}, // not covered
+// {"viqr", "VIQR"}, // not covered
+// {"viscii", "VISCII"},
+// {"vscii", ""}, // not covered
+// {"windows-037", ""}, // not covered
+// {"windows-10000", ""}, // not covered
+// {"windows-10001", ""}, // not covered
+// {"windows-10006", ""}, // not covered
+// {"windows-10007", ""}, // not covered
+// {"windows-10029", ""}, // not covered
+// {"windows-10079", ""}, // not covered
+// {"windows-10081", ""}, // not covered
+// {"windows-1026", ""}, // not covered
+// {"windows-1200", ""}, // not covered
+// {"windows-1250", "windows-1250"},
+// {"windows-1251", "windows-1251"},
+// {"windows-1252", "windows-1252"},
+// {"windows-1253", "windows-1253"},
+// {"windows-1254", "windows-1254"},
+// {"windows-1255", "windows-1255"},
+// {"windows-1256", "windows-1256"},
+// {"windows-1257", "windows-1257"},
+// {"windows-1258", "windows-1258"},
+// {"windows-1361", "cp1361"}, // *, XEmacs
+// {"windows-437", "cp437"}, // XEmacs
+// {"windows-500", ""}, // not covered
+// {"windows-708", ""}, // not covered
+// {"windows-709", ""}, // not covered
+// {"windows-710", ""}, // not covered
+// {"windows-720", ""}, // not covered
+// {"windows-737", "cp737"}, // *, XEmacs
+// {"windows-775", "cp775"}, // XEmacs
+// {"windows-850", "cp850"}, // XEmacs
+// {"windows-852", "cp852"}, // XEmacs
+// {"windows-855", "cp855"}, // XEmacs
+// {"windows-857", "cp857"}, // XEmacs
+// {"windows-860", "cp860"}, // XEmacs
+// {"windows-861", "cp861"}, // XEmacs
+// {"windows-862", "cp862"}, // XEmacs
+// {"windows-863", "cp863"}, // XEmacs
+// {"windows-864", "cp864"}, // XEmacs
+// {"windows-865", "cp865"}, // XEmacs
+// {"windows-866", "cp866"}, // XEmacs
+// {"windows-869", "cp869"}, // XEmacs
+// {"windows-874", "cp874"}, // XEmacs
+// {"windows-875", ""}, // not covered
+// {"windows-932", "cp932"}, // *, XEmacs
+// {"windows-936", "cp936"}, // XEmacs
+// {"windows-949", "cp949"}, // *, XEmacs
+// {"windows-950", "cp950"}, // *, XEmacs
+// {"x-ctext", ""}, // --
+// {"x-ctext-with-extensions", ""}, // --
+
+ {NULL, NULL},
+};
+
+// ---------------------------------------------------------
+// Convert encoding name from emacs to mime.
+// ---------------------------------------------------------
+char *
+emacs2mime(char *emacs_enc)
+{
+ int emacs_enc_len = strlen(emacs_enc);
+ if (emacs_enc_len > 4
+ && !strcasecmp(emacs_enc + emacs_enc_len - 4, "-dos"))
+ emacs_enc[emacs_enc_len - 4] = 0;
+ if (emacs_enc_len > 4
+ && !strcasecmp(emacs_enc + emacs_enc_len - 4, "-mac"))
+ emacs_enc[emacs_enc_len - 4] = 0;
+ if (emacs_enc_len > 5
+ && !strcasecmp(emacs_enc + emacs_enc_len - 5, "-unix"))
+ emacs_enc[emacs_enc_len - 5] = 0;
+ for (const conversion *table = emacs_to_mime; table->from; table++)
+ if (!strcasecmp(emacs_enc, table->from))
+ return (char *)table->to;
+ return emacs_enc;
+}
+
+// ---------------------------------------------------------
+// Print out Unicode entity if value is greater than 0x7F.
+// ---------------------------------------------------------
+inline void
+unicode_entity(int u)
+{
+ if (u < 0x80)
+ putchar(u);
+ else {
+ // Handle no-break space and soft hyphen specially--they are input
+ // characters only, not glyphs. See groff_char(7).
+ if (u == 0xA0) {
+ putchar('\\');
+ putchar('~');
+ }
+ else if (u == 0xAD) {
+ putchar('\\');
+ putchar('%');
+ }
+ else
+ printf("\\[u%04X]", u);
+ }
+}
+
+// ---------------------------------------------------------
+// Conversion functions. All functions take 'data', which
+// normally holds the first two lines, and a file pointer.
+// ---------------------------------------------------------
+
+// Conversion from ISO-8859-1 (aka Latin-1) to Unicode.
+void
+conversion_latin1(FILE *fp, const string &data)
+{
+ int len = data.length();
+ const unsigned char *ptr = (const unsigned char *)data.contents();
+ for (int i = 0; i < len; i++)
+ unicode_entity(ptr[i]);
+ int c = -1;
+ while ((c = getc(fp)) != EOF)
+ unicode_entity(c);
+}
+
+// A future version of groff shall support UTF-8 natively.
+// In this case, the UTF-8 stuff here in this file will be
+// moved to the troff program.
+
+struct utf8 {
+ FILE *fp;
+ unsigned char s[6];
+ enum {
+ FIRST = 0,
+ SECOND,
+ THIRD,
+ FOURTH,
+ FIFTH,
+ SIXTH
+ } byte;
+ int expected_byte_count;
+ bool emit_invalid_utf8_warning;
+ bool emit_incomplete_utf8_warning;
+ utf8(FILE *);
+ ~utf8();
+ void add(unsigned char);
+ void invalid();
+ void incomplete();
+};
+
+utf8::utf8(FILE *f) : fp(f), byte(FIRST), expected_byte_count(1),
+ emit_invalid_utf8_warning(true),
+ emit_incomplete_utf8_warning(true)
+{
+ // empty
+}
+
+utf8::~utf8()
+{
+ if (byte != FIRST)
+ incomplete();
+}
+
+inline void
+utf8::add(unsigned char c)
+{
+ s[byte] = c;
+ if (byte == FIRST) {
+ if (c < 0x80)
+ unicode_entity(c);
+ else if (c < 0xC0)
+ invalid();
+ else if (c < 0xE0) {
+ expected_byte_count = 2;
+ byte = SECOND;
+ }
+ else if (c < 0xF0) {
+ expected_byte_count = 3;
+ byte = SECOND;
+ }
+ else if (c < 0xF8) {
+ expected_byte_count = 4;
+ byte = SECOND;
+ }
+ else if (c < 0xFC) {
+ expected_byte_count = 5;
+ byte = SECOND;
+ }
+ else if (c < 0xFE) {
+ expected_byte_count = 6;
+ byte = SECOND;
+ }
+ else
+ invalid();
+ return;
+ }
+ if (c < 0x80 || c > 0xBF) {
+ incomplete();
+ add(c);
+ return;
+ }
+ switch (byte) {
+ case FIRST:
+ // can't happen
+ break;
+ case SECOND:
+ if (expected_byte_count == 2) {
+ if (s[0] < 0xC2)
+ invalid();
+ else
+ unicode_entity(((s[0] & 0x1F) << 6)
+ | (s[1] ^ 0x80));
+ byte = FIRST;
+ }
+ else
+ byte = THIRD;
+ break;
+ case THIRD:
+ if (expected_byte_count == 3) {
+ if (!(s[0] >= 0xE1 || s[1] >= 0xA0))
+ invalid();
+ else
+ unicode_entity(((s[0] & 0x1F) << 12)
+ | ((s[1] ^ 0x80) << 6)
+ | (s[2] ^ 0x80));
+ byte = FIRST;
+ }
+ else
+ byte = FOURTH;
+ break;
+ case FOURTH:
+ // We reject everything greater than 0x10FFFF.
+ if (expected_byte_count == 4) {
+ if (!((s[0] >= 0xF1 || s[1] >= 0x90)
+ && (s[0] < 0xF4 || (s[0] == 0xF4 && s[1] < 0x90))))
+ invalid();
+ else
+ unicode_entity(((s[0] & 0x07) << 18)
+ | ((s[1] ^ 0x80) << 12)
+ | ((s[2] ^ 0x80) << 6)
+ | (s[3] ^ 0x80));
+ byte = FIRST;
+ }
+ else
+ byte = FIFTH;
+ break;
+ case FIFTH:
+ if (expected_byte_count == 5) {
+ invalid();
+ byte = FIRST;
+ }
+ else
+ byte = SIXTH;
+ break;
+ case SIXTH:
+ invalid();
+ byte = FIRST;
+ break;
+ }
+}
+
+// We use fprintf(stderr) instead of libgroff's debug() because we need
+// to output longs, and libgroff's errprint() doesn't support that.
+
+void
+utf8::invalid()
+{
+ if (is_debugging && emit_invalid_utf8_warning) {
+ fprintf(stderr, " invalid UTF-8 sequence(s) in input stream:"
+ " replacing each such sequence with 0xFFFD\n");
+ emit_invalid_utf8_warning = false;
+ }
+ unicode_entity(0xFFFD);
+ byte = FIRST;
+}
+
+void
+utf8::incomplete()
+{
+ if (is_debugging && emit_incomplete_utf8_warning) {
+ fprintf(stderr, " incomplete UTF-8 sequence(s) in input stream:"
+ " replacing each such sequence with 0xFFFD\n");
+ emit_incomplete_utf8_warning = false;
+ }
+ unicode_entity(0xFFFD);
+ byte = FIRST;
+}
+
+// Conversion from UTF-8 to Unicode.
+void
+conversion_utf8(FILE *fp, const string &data)
+{
+ utf8 u(fp);
+ int len = data.length();
+ const unsigned char *ptr = (const unsigned char *)data.contents();
+ for (int i = 0; i < len; i++)
+ u.add(ptr[i]);
+ int c = -1;
+ while ((c = getc(fp)) != EOF)
+ u.add(c);
+ return;
+}
+
+// Conversion from cp1047 (EBCDIC) to UTF-8.
+void
+conversion_cp1047(FILE *fp, const string &data)
+{
+ static unsigned char cp1047[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x9C, 0x09, 0x86, 0x7F, // 0x00
+ 0x97, 0x8D, 0x8E, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x9D, 0x85, 0x08, 0x87, // 0x10
+ 0x18, 0x19, 0x92, 0x8F, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x0A, 0x17, 0x1B, // 0x20
+ 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x05, 0x06, 0x07,
+ 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, // 0x30
+ 0x98, 0x99, 0x9A, 0x9B, 0x14, 0x15, 0x9E, 0x1A,
+ 0x20, 0xA0, 0xE2, 0xE4, 0xE0, 0xE1, 0xE3, 0xE5, // 0x40
+ 0xE7, 0xF1, 0xA2, 0x2E, 0x3C, 0x28, 0x2B, 0x7C,
+ 0x26, 0xE9, 0xEA, 0xEB, 0xE8, 0xED, 0xEE, 0xEF, // 0x50
+ 0xEC, 0xDF, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E,
+ 0x2D, 0x2F, 0xC2, 0xC4, 0xC0, 0xC1, 0xC3, 0xC5, // 0x60
+ 0xC7, 0xD1, 0xA6, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
+ 0xF8, 0xC9, 0xCA, 0xCB, 0xC8, 0xCD, 0xCE, 0xCF, // 0x70
+ 0xCC, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
+ 0xD8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // 0x80
+ 0x68, 0x69, 0xAB, 0xBB, 0xF0, 0xFD, 0xFE, 0xB1,
+ 0xB0, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, // 0x90
+ 0x71, 0x72, 0xAA, 0xBA, 0xE6, 0xB8, 0xC6, 0xA4,
+ 0xB5, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, // 0xA0
+ 0x79, 0x7A, 0xA1, 0xBF, 0xD0, 0x5B, 0xDE, 0xAE,
+ 0xAC, 0xA3, 0xA5, 0xB7, 0xA9, 0xA7, 0xB6, 0xBC, // 0xB0
+ 0xBD, 0xBE, 0xDD, 0xA8, 0xAF, 0x5D, 0xB4, 0xD7,
+ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // 0xC0
+ 0x48, 0x49, 0xAD, 0xF4, 0xF6, 0xF2, 0xF3, 0xF5,
+ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, // 0xD0
+ 0x51, 0x52, 0xB9, 0xFB, 0xFC, 0xF9, 0xFA, 0xFF,
+ 0x5C, 0xF7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, // 0xE0
+ 0x59, 0x5A, 0xB2, 0xD4, 0xD6, 0xD2, 0xD3, 0xD5,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0xF0
+ 0x38, 0x39, 0xB3, 0xDB, 0xDC, 0xD9, 0xDA, 0x9F,
+ };
+ int len = data.length();
+ const unsigned char *ptr = (const unsigned char *)data.contents();
+ for (int i = 0; i < len; i++)
+ unicode_entity(cp1047[ptr[i]]);
+ int c = -1;
+ while ((c = getc(fp)) != EOF)
+ unicode_entity(cp1047[c]);
+}
+
+// Locale-sensible conversion.
+#if HAVE_ICONV
+void
+conversion_iconv(FILE *fp, const string &data, char *enc)
+{
+ iconv_t handle = iconv_open(UNICODE, enc);
+ if (handle == (iconv_t)-1) {
+ if (errno == EINVAL) {
+ error("encoding system '%1' not supported by iconv()", enc);
+ return;
+ }
+ fatal("iconv_open failed");
+ }
+ char inbuf[BUFSIZ];
+ int outbuf[BUFSIZ];
+ char *outptr = (char *)outbuf;
+ size_t outbytes_left = BUFSIZ * sizeof (int);
+ // Handle 'data'.
+ char *inptr = (char *)data.contents();
+ size_t inbytes_left = data.length();
+ char *limit;
+ while (inbytes_left > 0) {
+ size_t status = iconv(handle,
+ (ICONV_CONST char **)&inptr, &inbytes_left,
+ &outptr, &outbytes_left);
+ if (status == (size_t)-1) {
+ if (errno == EILSEQ) {
+ // Invalid byte sequence. XXX
+ inptr++;
+ inbytes_left--;
+ }
+ else if (errno == E2BIG) {
+ // Output buffer is full.
+ limit = (char *)outbuf + BUFSIZ * sizeof (int) - outbytes_left;
+ for (int *ptr = outbuf; (char *)ptr < limit; ptr++)
+ unicode_entity(*ptr);
+ memmove(outbuf, outptr, outbytes_left);
+ outptr = (char *)outbuf + outbytes_left;
+ outbytes_left = BUFSIZ * sizeof (int) - outbytes_left;
+ }
+ else if (errno == EINVAL) {
+ // 'data' ends with partial input sequence.
+ memcpy(inbuf, inptr, inbytes_left);
+ break;
+ }
+ }
+ }
+ // Handle 'fp' and switch to 'inbuf'.
+ size_t read_bytes;
+ char *read_start = inbuf + inbytes_left;
+ while ((read_bytes = fread(read_start, 1, BUFSIZ - inbytes_left, fp)) > 0) {
+ inptr = inbuf;
+ inbytes_left += read_bytes;
+ while (inbytes_left > 0) {
+ size_t status = iconv(handle,
+ (ICONV_CONST char **)&inptr, &inbytes_left,
+ &outptr, &outbytes_left);
+ if (status == (size_t)-1) {
+ if (errno == EILSEQ) {
+ // Invalid byte sequence. XXX
+ inptr++;
+ inbytes_left--;
+ }
+ else if (errno == E2BIG) {
+ // Output buffer is full.
+ limit = (char *)outbuf + BUFSIZ * sizeof (int) - outbytes_left;
+ for (int *ptr = outbuf; (char *)ptr < limit; ptr++)
+ unicode_entity(*ptr);
+ memmove(outbuf, outptr, outbytes_left);
+ outptr = (char *)outbuf + outbytes_left;
+ outbytes_left = BUFSIZ * sizeof (int) - outbytes_left;
+ }
+ else if (errno == EINVAL) {
+ // 'inbuf' ends with partial input sequence.
+ memmove(inbuf, inptr, inbytes_left);
+ break;
+ }
+ }
+ }
+ read_start = inbuf + inbytes_left;
+ }
+ iconv_close(handle);
+ // XXX use ferror?
+ limit = (char *)outbuf + BUFSIZ * sizeof (int) - outbytes_left;
+ for (int *ptr = outbuf; (char *)ptr < limit; ptr++)
+ unicode_entity(*ptr);
+}
+#endif /* HAVE_ICONV */
+
+// ---------------------------------------------------------
+// Handle Byte Order Mark.
+//
+// Since we have a chicken-and-egg problem it's necessary
+// to handle the BOM manually if it is in the data stream.
+// As documented in the Unicode book it is very unlikely
+// that any normal text file (regardless of the encoding)
+// starts with the bytes which represent a BOM.
+//
+// Return the BOM in string 'BOM'; 'data' then starts with
+// the byte after the BOM. This function reads (at most)
+// four bytes from the data stream.
+//
+// Return encoding if a BOM is found, NULL otherwise.
+// ---------------------------------------------------------
+const char *
+get_BOM(FILE *fp, string &BOM, string &data)
+{
+ // The BOM is U+FEFF. We have thus the following possible
+ // representations.
+ //
+ // UTF-8: 0xEFBBBF
+ // UTF-16: 0xFEFF or 0xFFFE
+ // UTF-32: 0x0000FEFF or 0xFFFE0000
+ static struct {
+ int len;
+ const char *str;
+ const char *name;
+ } BOM_table[] = {
+ {4, "\x00\x00\xFE\xFF", "UTF-32"},
+ {4, "\xFF\xFE\x00\x00", "UTF-32"},
+ {3, "\xEF\xBB\xBF", "UTF-8"},
+ {2, "\xFE\xFF", "UTF-16"},
+ {2, "\xFF\xFE", "UTF-16"},
+ };
+ const int BOM_table_len = sizeof (BOM_table) / sizeof (BOM_table[0]);
+ char BOM_string[4];
+ const char *retval = NULL;
+ int len;
+ for (len = 0; len < 4; len++) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ BOM_string[len] = char(c);
+ }
+ int i;
+ for (i = 0; i < BOM_table_len; i++) {
+ if (BOM_table[i].len <= len
+ && memcmp(BOM_string, BOM_table[i].str, BOM_table[i].len) == 0)
+ break;
+ }
+ int j = 0;
+ if (i < BOM_table_len) {
+ for (; j < BOM_table[i].len; j++)
+ BOM += BOM_string[j];
+ retval = BOM_table[i].name;
+ }
+ for (; j < len; j++)
+ data += BOM_string[j];
+ return retval;
+}
+
+// ---------------------------------------------------------
+// Get first two lines from input stream.
+//
+// Return string (allocated with 'new') without zero bytes
+// or NULL in case no coding tag can occur in the data
+// (which is stored unmodified in 'data').
+// ---------------------------------------------------------
+char *
+get_tag_lines(FILE *fp, string &data)
+{
+ int newline_count = 0;
+ int c, prev = -1;
+ // Handle CR, LF, and CRLF as line separators.
+ for (int i = 0; i < data.length(); i++) {
+ c = data[i];
+ if (c == '\n' || c == '\r')
+ newline_count++;
+ if (c == '\n' && prev == '\r')
+ newline_count--;
+ prev = c;
+ }
+ if (newline_count > 1)
+ return NULL;
+ bool emit_warning = true;
+ for (int lines = newline_count; lines < 2; lines++) {
+ while ((c = getc(fp)) != EOF) {
+ if (c == '\0' && is_debugging && emit_warning) {
+ warning("null byte(s) found in input stream:"
+ " search for coding tag might return false result");
+ emit_warning = false;
+ }
+ data += char(c);
+ if (c == '\n' || c == '\r')
+ break;
+ }
+ // Handle CR, LF, and CRLF as line separators.
+ if (c == '\r') {
+ c = getc(fp);
+ if (c != EOF && c != '\n')
+ ungetc(c, fp);
+ else
+ data += char(c);
+ }
+ }
+ return data.extract();
+}
+
+// ---------------------------------------------------------
+// Check whether C string starts with a comment.
+//
+// Return 1 if true, 0 otherwise.
+// ---------------------------------------------------------
+int
+is_comment_line(char *s)
+{
+ if (!s || !*s)
+ return 0;
+ if (*s == '.' || *s == '\'')
+ {
+ s++;
+ while (*s == ' ' || *s == '\t')
+ s++;
+ if (*s && *s == '\\')
+ {
+ s++;
+ if (*s == '"' || *s == '#')
+ return 1;
+ }
+ }
+ else if (*s == '\\')
+ {
+ s++;
+ if (*s == '#')
+ return 1;
+ }
+ return 0;
+}
+
+// ---------------------------------------------------------
+// Get a value/variable pair from a local variables list
+// in a C string which look like this:
+//
+// <variable1>: <value1>; <variable2>: <value2>; ...
+//
+// Leading and trailing blanks are ignored. There might be
+// more than one blank after ':' and ';'.
+//
+// Return position of next value/variable pair or NULL if
+// at end of data.
+// ---------------------------------------------------------
+char *
+get_variable_value_pair(char *d1, char **variable, char **value)
+{
+ static char var[MAX_VAR_LEN], val[MAX_VAR_LEN];
+ *variable = var;
+ *value = val;
+ while (*d1 == ' ' || *d1 == '\t')
+ d1++;
+ // Get variable.
+ int l = 0;
+ while (l < MAX_VAR_LEN - 1 && *d1 && !strchr(";: \t", *d1))
+ var[l++] = *(d1++);
+ var[l] = 0;
+ // Skip everything until ':', ';', or end of data.
+ while (*d1 && *d1 != ':' && *d1 != ';')
+ d1++;
+ val[0] = 0;
+ if (!*d1)
+ return NULL;
+ if (*d1 == ';')
+ return d1 + 1;
+ d1++;
+ while (*d1 == ' ' || *d1 == '\t')
+ d1++;
+ // Get value.
+ l = 0;
+ while (l < MAX_VAR_LEN - 1 && *d1 && !strchr("; \t", *d1))
+ val[l++] = *(d1++);
+ val[l] = 0;
+ // Skip everything until ';' or end of data.
+ while (*d1 && *d1 != ';')
+ d1++;
+ if (*d1 == ';')
+ return d1 + 1;
+ return NULL;
+}
+
+// ---------------------------------------------------------
+// Check coding tag in the read buffer.
+//
+// We search for the following line:
+//
+// <comment> ... -*-<local variables list>-*-
+//
+// ('...' might be anything).
+//
+// <comment> can be one of the following syntax forms at the
+// beginning of the line:
+//
+// .\" .\# '\" '\# \#
+//
+// There can be whitespace after the leading '.' or "'".
+//
+// The local variables list must occur within the first
+// comment block at the very beginning of the data stream.
+//
+// Within the <local variables list>, we search for
+//
+// coding: <value>
+//
+// which specifies the coding system used for the data
+// stream.
+//
+// Return <value> if found, NULL otherwise.
+//
+// Note that null bytes in the data are skipped before applying
+// the algorithm. This should work even with files encoded as
+// UTF-16 or UTF-32 (or its siblings) in most cases.
+// ---------------------------------------------------------
+char *
+check_coding_tag(FILE *fp, string &data)
+{
+ char *inbuf = get_tag_lines(fp, data);
+ char *lineend;
+ for (char *p = inbuf; is_comment_line(p); p = lineend + 1) {
+ if ((lineend = strchr(p, '\n')) == NULL)
+ break;
+ *lineend = 0; // switch temporarily to '\0'
+ char *d1 = strstr(p, "-*-");
+ char *d2 = 0;
+ if (d1)
+ d2 = strstr(d1 + 3, "-*-");
+ *lineend = '\n'; // restore newline
+ if (!d1 || !d2)
+ continue;
+ *d2 = 0; // switch temporarily to '\0'
+ d1 += 3;
+ while (d1) {
+ char *variable, *value;
+ d1 = get_variable_value_pair(d1, &variable, &value);
+ if (!strcasecmp(variable, "coding")) {
+ *d2 = '-'; // restore '-'
+ free(inbuf);
+ return value;
+ }
+ }
+ *d2 = '-'; // restore '-'
+ }
+ free(inbuf);
+ return NULL;
+}
+
+char *
+detect_file_encoding(FILE *fp)
+{
+#ifdef HAVE_UCHARDET
+ uchardet_t ud = NULL;
+ struct stat stat_buf;
+ size_t len, read_bytes;
+ char *data = NULL;
+ int res, current_position;
+ const char *charset;
+ char *ret = NULL;
+
+ current_position = ftell(fp);
+ /* Due to BOM and tag detection, we are not at the beginning of the
+ file. */
+ rewind(fp);
+ if (fstat(fileno(fp), &stat_buf) != 0) {
+ error("fstat: %1", strerror(errno));
+ goto end;
+ }
+ len = stat_buf.st_size;
+ if (is_debugging)
+ fprintf(stderr, " len: %lu\n", (unsigned long)len);
+ if (len == 0)
+ goto end;
+ data = (char *)calloc(len, 1);
+ read_bytes = fread(data, 1, len, fp);
+ if (read_bytes == 0) {
+ error("fread: %1", strerror(errno));
+ goto end;
+ }
+ /* We rewind back to the original position */
+ if (fseek(fp, current_position, SEEK_SET) != 0) {
+ fatal("fseek: %1", strerror(errno));
+ goto end;
+ }
+ ud = uchardet_new();
+ res = uchardet_handle_data(ud, data, len);
+ if (res != 0) {
+ debug(" uchardet_handle_data: error %1\n", res);
+ goto end;
+ }
+ if (is_debugging)
+ fprintf(stderr, " uchardet read: %lu bytes\n",
+ (unsigned long)read_bytes);
+ uchardet_data_end(ud);
+ charset = uchardet_get_charset(ud);
+ if (is_debugging) {
+ if (charset)
+ fprintf(stderr, " charset: %s\n", charset);
+ else
+ fprintf(stderr, " charset is NULL\n");
+ }
+ /* uchardet 0.0.1 could return an empty string instead of NULL */
+ if (charset && *charset) {
+ ret = (char *)malloc(strlen(charset) + 1);
+ strcpy(ret, charset);
+ }
+
+end:
+ if (ud)
+ uchardet_delete(ud);
+ if (data)
+ free(data);
+
+ return ret;
+#else /* not HAVE_UCHARDET */
+ return NULL;
+#endif /* not HAVE_UCHARDET */
+}
+
+// ---------------------------------------------------------
+// Handle an input file. If `filename` is "-", read the
+// standard input stream.
+//
+// Return 1 on success, 0 otherwise.
+// ---------------------------------------------------------
+int
+do_file(const char *filename)
+{
+ FILE *fp;
+ string BOM, data;
+ bool is_seekable = false;
+ string reported_filename;
+
+ // TODO: Consider moving some of this into a `quoted_file_name`
+ // function in libgroff.
+ if (strcmp(filename, "-") == 0) {
+ fp = stdin;
+ reported_filename = string("<standard input>");
+ }
+ else {
+ fp = fopen(filename, FOPEN_RB);
+ reported_filename = "'" + string(filename) + "'";
+ }
+ if (!fp) {
+ error("can't open %1: %2", reported_filename.contents(),
+ strerror(errno));
+ return 0;
+ }
+ if (is_debugging)
+ fprintf(stderr, "processing %s\n", reported_filename.contents());
+ if (fseek(fp, 0L, SEEK_SET) == 0)
+ is_seekable = true;
+ else {
+ SET_BINARY(fileno(fp));
+ if (is_debugging)
+ fprintf(stderr, " stream is not seekable: %s\n",
+ strerror(errno));
+ }
+ const char *BOM_encoding = get_BOM(fp, BOM, data);
+ // Determine the encoding.
+ char *encoding;
+ int must_free_encoding = 0;
+ if (user_encoding[0]) {
+ if (is_debugging) {
+ fprintf(stderr, " user-specified encoding '%s', "
+ "no search for coding tag\n",
+ user_encoding);
+ if (BOM_encoding && strcmp(BOM_encoding, user_encoding))
+ fprintf(stderr, " but BOM in data stream implies encoding '%s'!\n",
+ BOM_encoding);
+ }
+ encoding = (char *)user_encoding;
+ }
+ else if (BOM_encoding) {
+ if (is_debugging)
+ fprintf(stderr, " found BOM, no search for coding tag\n");
+ encoding = (char *)BOM_encoding;
+ }
+ else {
+ // 'check_coding_tag' returns a pointer to a static array (or NULL).
+ char *file_encoding = check_coding_tag(fp, data);
+ if (!file_encoding) {
+ if (is_debugging)
+ fprintf(stderr, " no coding tag\n");
+ if (is_seekable)
+ file_encoding = detect_file_encoding(fp);
+ if (!file_encoding) {
+ if (is_debugging)
+ fprintf(stderr, " could not detect encoding with uchardet\n");
+ file_encoding = fallback_encoding;
+ }
+ else
+ must_free_encoding = 1;
+ }
+ else
+ if (is_debugging)
+ fprintf(stderr, " coding tag: '%s'\n", file_encoding);
+ encoding = file_encoding;
+ }
+ strncpy(encoding_string, encoding, MAX_VAR_LEN - 1);
+ encoding_string[MAX_VAR_LEN - 1] = 0;
+ if (must_free_encoding)
+ free(encoding);
+ encoding = encoding_string;
+ // Translate from MIME & Emacs encoding names to locale encoding names.
+ encoding = emacs2mime(encoding_string);
+ if (encoding[0] == '\0') {
+ error("encoding '%1' not supported, not a portable encoding",
+ encoding_string);
+ return 0;
+ }
+ if (is_debugging)
+ fprintf(stderr, " encoding used: '%s'\n", encoding);
+ if (!raw_flag) {
+ string fn(filename);
+ fn += '\0';
+ normalize_for_lf(fn);
+ printf(".lf 1 %s\n", fn.contents());
+ }
+ int success = 1;
+ // Call converter (converters write to stdout).
+ if (!strcasecmp(encoding, "ISO-8859-1"))
+ conversion_latin1(fp, BOM + data);
+ else if (!strcasecmp(encoding, "UTF-8"))
+ conversion_utf8(fp, data);
+ else if (!strcasecmp(encoding, "cp1047"))
+ conversion_cp1047(fp, BOM + data);
+ else {
+#if HAVE_ICONV
+ conversion_iconv(fp, BOM + data, encoding);
+#else
+ error("encoding system '%1' not supported", encoding);
+ success = 0;
+#endif /* HAVE_ICONV */
+ }
+ if (fp != stdin)
+ fclose(fp);
+ return success;
+}
+
+// ---------------------------------------------------------
+// Print usage.
+// ---------------------------------------------------------
+void
+usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-dr] [-D fallback-encoding] [-e encoding] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fprintf(stream,
+"\n"
+"Read each file, convert its encoded characters to a form GNU"
+" troff(1)\n"
+"can interpret, and send the result to the standard output stream.\n"
+"The default fallback encoding is '%s'. See the preconv(1) manual"
+" page.\n",
+ fallback_encoding);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+// ---------------------------------------------------------
+// Main routine.
+// ---------------------------------------------------------
+int
+main(int argc, char **argv)
+{
+ program_name = argv[0];
+ // Determine the fallback encoding. This must be done before
+ // getopt() is called since the usage message shows the fallback
+ // encoding.
+ setlocale(LC_ALL, "");
+ char *locale = getlocale(LC_CTYPE);
+ if (!locale || !strcmp(locale, "C") || !strcmp(locale, "POSIX"))
+ strcpy(fallback_encoding, "latin1");
+ else {
+ strncpy(fallback_encoding, locale_charset(), MAX_VAR_LEN - 1);
+ fallback_encoding[MAX_VAR_LEN - 1] = 0;
+ }
+
+ program_name = argv[0];
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ // Parse the command-line options.
+ while ((opt = getopt_long(argc, argv,
+ "dD:e:hrv", long_options, NULL)) != EOF)
+ switch (opt) {
+ case 'v':
+ printf("GNU preconv (groff) version %s %s iconv support and %s uchardet support\n",
+ Version_string,
+#ifdef HAVE_ICONV
+ "with",
+#else
+ "without",
+#endif /* HAVE_ICONV */
+#ifdef HAVE_UCHARDET
+ "with"
+#else
+ "without"
+#endif /* HAVE_UCHARDET */
+ );
+ exit(0);
+ break;
+ case 'd':
+ is_debugging = true;
+ break;
+ case 'e':
+ if (optarg) {
+ strncpy(user_encoding, optarg, MAX_VAR_LEN - 1);
+ user_encoding[MAX_VAR_LEN - 1] = 0;
+ }
+ else
+ user_encoding[0] = 0;
+ break;
+ case 'D':
+ if (optarg) {
+ strncpy(fallback_encoding, optarg, MAX_VAR_LEN - 1);
+ fallback_encoding[MAX_VAR_LEN - 1] = 0;
+ }
+ break;
+ case 'r':
+ raw_flag = 1;
+ break;
+ case 'h':
+ usage(stdout);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ int nbad = 0;
+ if (is_debugging)
+ fprintf(stderr, "fallback encoding: '%s'\n", fallback_encoding);
+ if (optind >= argc)
+ nbad += !do_file("-");
+ else
+ for (int i = optind; i < argc; i++)
+ nbad += !do_file(argv[i]);
+ if (ferror(stdout) || fflush(stdout) < 0)
+ fatal("output error");
+ return nbad != 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/preconv/tests/do-not-seek-the-unseekable.sh b/src/preproc/preconv/tests/do-not-seek-the-unseekable.sh
new file mode 100755
index 0000000..2b1142d
--- /dev/null
+++ b/src/preproc/preconv/tests/do-not-seek-the-unseekable.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+set -e
+
+preconv="${abs_top_builddir:-.}/preconv"
+
+fail=
+
+wail () {
+ echo FAILED >&2
+ fail=YES
+}
+
+# Scrape debugging output to see if we're skipping unseekable streams.
+# This is fragile, but we don't want to lock the language of diagnostic
+# messages (especially debugging ones). If this test fails, check the
+# text of the command's debugging output for a mismatch before
+# investigating deeper problems.
+
+echo "testing seekability of file operand '-'" >&2
+output=$(printf '' | "$preconv" -d - 2>&1)
+echo "$output" | grep -q "stream is not seekable" || wail
+
+# /dev/stdin might not exist in a chroot. Or, if it's not (a symbolic
+# link to) a character special device, the next test will not be valid,
+# as when using GNU Make's `-j` option.
+#
+# Similarly, we must have a controlling terminal.
+test -z "$fail"
+echo "skipping if /dev/stdin is not a character device" >&2
+test -c /dev/stdin || exit 77 # skip
+echo "skipping if there is no controlling terminal" >&2
+test "$(tty)" != "not a tty" || exit 77 # skip
+
+echo "testing seekability of standard input stream" >&2
+output=$(printf '' | "$preconv" -d /dev/stdin 2>&1)
+echo "$output" | grep -q "stream is not seekable" || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/preconv/tests/smoke-test.sh b/src/preproc/preconv/tests/smoke-test.sh
new file mode 100755
index 0000000..4131416
--- /dev/null
+++ b/src/preproc/preconv/tests/smoke-test.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Ensure a predictable character encoding.
+export LC_ALL=C
+
+set -e
+
+preconv="${abs_top_builddir:-.}/preconv"
+
+echo "testing -e flag override of BOM detection" >&2
+printf '\376\377\0\100\0\n' \
+ | "$preconv" -d -e euc-kr 2>&1 > /dev/null \
+ | grep -q "no search for coding tag"
+
+echo "testing detection of UTF-32BE BOM" >&2
+printf '\0\0\376\377\0\0\0\100\0\0\0\n' \
+ | "$preconv" -d 2>&1 > /dev/null \
+ | grep -q "found BOM"
+
+echo "testing detection of UTF-32LE BOM" >&2
+printf '\377\376\0\0\100\0\0\0\n\0\0\0' \
+ | "$preconv" -d 2>&1 > /dev/null \
+ | grep -q "found BOM"
+
+echo "testing detection of UTF-16BE BOM" >&2
+printf '\376\377\0\100\0\n' \
+ | "$preconv" -d 2>&1 > /dev/null \
+ | grep -q "found BOM"
+
+echo "testing detection of UTF-16LE BOM" >&2
+printf '\377\376\100\0\n\0' \
+ | "$preconv" -d 2>&1 > /dev/null \
+ | grep -q "found BOM"
+
+echo "testing detection of UTF-8 BOM" >&2
+printf '\357\273\277@\n' \
+ | "$preconv" -d 2>&1 > /dev/null \
+ | grep -q "found BOM"
+
+# We do not find a coding tag on piped input because it isn't seekable.
+echo "testing detection of Emacs coding tag in piped input" >&2
+printf '.\\" -*- coding: euc-kr; -*-\\n' \
+ | "$preconv" -d 2>&1 >/dev/null \
+ | grep -q "no coding tag"
+
+# We need uchardet to work to get past this point.
+echo "testing uchardet detection of encoding" >&2
+"$preconv" -v | grep -q 'with uchardet support' || exit 77
+
+# Instead of using temporary files, which in all fastidiousness means
+# cleaning them up even if we're interrupted, which in turn means
+# setting up signal handlers, we use files in the build tree.
+
+doc=contrib/mm/groff_mmse.7
+echo "testing uchardet detection on Latin-1 document $doc" >&2
+"$preconv" -d -D us-ascii 2>&1 >/dev/null $doc \
+ | grep -q 'charset: ISO-8859-1'
+
+# uchardet can't seek on a pipe either.
+echo "testing uchardet detection on pipe (expect fallback to -D)" >&2
+printf 'Eat at the caf\351.\n' \
+ | "$preconv" -d -D euc-kr 2>&1 > /dev/null \
+ | grep -q "encoding used: 'EUC-KR'"
+
+# Fall back to the locale. preconv assumes Latin-1 for C instead of
+# US-ASCII.
+echo "testing fallback to locale setting in environment" >&2
+printf 'Eat at the caf\351.\n' \
+ | "$preconv" -d 2>&1 > /dev/null \
+ | grep -q "encoding used: 'ISO-8859-1'"
diff --git a/src/preproc/refer/TODO b/src/preproc/refer/TODO
new file mode 100644
index 0000000..36f4508
--- /dev/null
+++ b/src/preproc/refer/TODO
@@ -0,0 +1,124 @@
+inline references
+
+Some sort of macro/subroutine that can cover several references.
+
+move-punctuation should ignore multiple punctuation characters.
+
+Make the index files machine independent.
+
+Allow search keys to be negated (with !) to indicate that the
+reference should not contain the key. Ignore negated keys during
+indexed searching.
+
+Provide an option with lkbib and lookbib that prints the location
+(filename, position) of each reference. Need to map filename_id's
+back to filenames.
+
+Rename join-authors to join-fields. Have a separate label-join-fields
+command used by @ and #.
+
+Have some sort of quantifier: e.g., $.n#A means execute '$.n' for each
+instance of an A field, setting $ to that field, and then join the
+results using the join-authors command.
+
+no-text-in-bracket command which says not to allow post_text and
+pre_text when the [] flags has been given. Useful for superscripted
+footnotes.
+
+Make it possible to translate - to \(en in page ranges.
+
+Trim eign a bit.
+
+In indexed searching discard all numeric keys except dates.
+
+Allow '\ ' to separate article from first word.
+
+%also
+
+Option automatically to supply [] flags in every reference.
+
+See if we can avoid requiring a comma before jr. and so on
+in find_last_name().
+
+Cache sortified authors in authors string during tentative evaluation of
+label specification.
+
+Possibly don't allow * and % expressions in the first part of ?:, | or
+& expressions.
+
+Handle better the case where <> occurs inside functions and in the
+first operand of ~. Or perhaps implement <> using some magic character
+in the string.
+
+Should special treatment be given to lines beginning with . in
+references? (Unix refer seems to treat them like '%').
+
+Add global flag to control whether all files should be stat-ed after
+loading, and whether they should be stat-ed before each search.
+Perhaps make this dependent on the number of files there are.
+
+Option to truncate keys to truncate_len in linear searching.
+
+Allow multiple -f options in indxbib.
+
+In indxbib, possibly store common words rather than common words
+filename. In this case store only words that are actually present in
+the file.
+
+Perhaps we should put out an obnoxious copyright message when lookbib
+starts up.
+
+Provide an option that writes a file containing just the references
+actually used. Useful if you want to distribute a document.
+
+Have a magic token such that
+%A <sort stuff><magic token><print stuff>
+will print as though it were
+%A <print stuff>
+but sort as though it were
+%A <sort stuff>
+Do we need this if we can specify author alternatives for sorting?
+No, provided we have separate alternatives for @.
+
+In consider_authors when last names are ambiguous we might be able to
+use just the first name and not Jr. bit. Or we might be able to
+abbreviate the author.
+
+It ought to be possible to specify an alternative field to sort on
+instead of date. (ie if there's a field giving the type of document --
+these references should sort after any years)
+
+Provide a way to execute a command using a command-line option.
+
+Option to set the label-spec as a command-line option (-L).
+
+Command to specify which fields can occur multiple times:
+multiple AE
+
+Command to specify how various fields sort:
+aort-as-name A
+sort-as-date D
+sort-as-title T
+sort-as-other O
+
+Command to specify which fields are author fields:
+# if we don't have A use field Q
+author-fields AQ
+
+Commands to set properties of tokens.
+sortify-token \(ae ae
+uppercase-token \[ae] \[AE]
+
+Command to set the names of months:
+months january february march april may ...
+
+Perhaps provide some sort of macro capability:
+# perhaps a macro capability
+defmacro foo
+annotation-field $1
+endef
+
+Command to control strings used in capitalization
+capitalize-start \s+2
+capitalize-end \s-2
+(perhaps make these arguments to the capitalize command.)
diff --git a/src/preproc/refer/command.cpp b/src/preproc/refer/command.cpp
new file mode 100644
index 0000000..b49e2be
--- /dev/null
+++ b/src/preproc/refer/command.cpp
@@ -0,0 +1,814 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "refer.h"
+#include "refid.h"
+#include "search.h"
+#include "command.h"
+
+cset cs_field_name = csalpha;
+
+class input_item {
+ input_item *next;
+ char *filename;
+ int first_lineno;
+ string buffer;
+ const char *ptr;
+ const char *end;
+public:
+ input_item(string &, const char *, int = 1);
+ ~input_item();
+ int get_char();
+ int peek_char();
+ void skip_char();
+ void get_location(const char **, int *);
+
+ friend class input_stack;
+};
+
+input_item::input_item(string &s, const char *fn, int ln)
+: filename(strsave(fn)), first_lineno(ln)
+{
+ buffer.move(s);
+ ptr = buffer.contents();
+ end = ptr + buffer.length();
+}
+
+input_item::~input_item()
+{
+ delete[] filename;
+}
+
+inline int input_item::peek_char()
+{
+ if (ptr >= end)
+ return EOF;
+ else
+ return (unsigned char)*ptr;
+}
+
+inline int input_item::get_char()
+{
+ if (ptr >= end)
+ return EOF;
+ else
+ return (unsigned char)*ptr++;
+}
+
+inline void input_item::skip_char()
+{
+ ptr++;
+}
+
+void input_item::get_location(const char **filenamep, int *linenop)
+{
+ *filenamep = filename;
+ if (ptr == buffer.contents())
+ *linenop = first_lineno;
+ else {
+ int ln = first_lineno;
+ const char *e = ptr - 1;
+ for (const char *p = buffer.contents(); p < e; p++)
+ if (*p == '\n')
+ ln++;
+ ln--; // Back up to identify line number _before_ last seen newline.
+ *linenop = ln;
+ }
+ return;
+}
+
+class input_stack {
+ static input_item *top;
+public:
+ static void init();
+ static int get_char();
+ static int peek_char();
+ static void skip_char() { top->skip_char(); }
+ static void push_file(const char *);
+ static void push_string(string &, const char *, int);
+ static void error(const char *format,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+};
+
+input_item *input_stack::top = 0;
+
+void input_stack::init()
+{
+ while (top) {
+ input_item *tem = top;
+ top = top->next;
+ delete tem;
+ }
+}
+
+int input_stack::get_char()
+{
+ while (top) {
+ int c = top->get_char();
+ if (c >= 0)
+ return c;
+ input_item *tem = top;
+ top = top->next;
+ delete tem;
+ }
+ return -1;
+}
+
+int input_stack::peek_char()
+{
+ while (top) {
+ int c = top->peek_char();
+ if (c >= 0)
+ return c;
+ input_item *tem = top;
+ top = top->next;
+ delete tem;
+ }
+ return -1;
+}
+
+void input_stack::push_file(const char *fn)
+{
+ FILE *fp;
+ if (strcmp(fn, "-") == 0) {
+ fp = stdin;
+ fn = "<standard input>";
+ }
+ else {
+ errno = 0;
+ fp = fopen(fn, "r");
+ if (fp == 0) {
+ error("can't open '%1': %2", fn, strerror(errno));
+ return;
+ }
+ }
+ string buf;
+ bool is_at_beginning_of_line = true;
+ int lineno = 1;
+ for (;;) {
+ int c = getc(fp);
+ if (is_at_beginning_of_line && c == '.') {
+ // replace lines beginning with .R1 or .R2 with a blank line
+ c = getc(fp);
+ if (c == 'R') {
+ c = getc(fp);
+ if (c == '1' || c == '2') {
+ int cc = c;
+ c = getc(fp);
+ if (compatible_flag || c == ' ' || c == '\n' || c == EOF) {
+ while (c != '\n' && c != EOF)
+ c = getc(fp);
+ }
+ else {
+ buf += '.';
+ buf += 'R';
+ buf += cc;
+ }
+ }
+ else {
+ buf += '.';
+ buf += 'R';
+ }
+ }
+ else
+ buf += '.';
+ }
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ error_with_file_and_line(fn, lineno,
+ "invalid input character code %1", c);
+ else {
+ buf += c;
+ if (c == '\n') {
+ is_at_beginning_of_line = true;
+ lineno++;
+ }
+ else
+ is_at_beginning_of_line = false;
+ }
+ }
+ if (fp != stdin)
+ fclose(fp);
+ if (buf.length() > 0 && buf[buf.length() - 1] != '\n')
+ buf += '\n';
+ input_item *it = new input_item(buf, fn);
+ it->next = top;
+ top = it;
+}
+
+void input_stack::push_string(string &s, const char *filename, int lineno)
+{
+ input_item *it = new input_item(s, filename, lineno);
+ it->next = top;
+ top = it;
+}
+
+void input_stack::error(const char *format, const errarg &arg1,
+ const errarg &arg2, const errarg &arg3)
+{
+ const char *filename;
+ int lineno;
+ for (input_item *it = top; it; it = it->next) {
+ it->get_location(&filename, &lineno);
+ error_with_file_and_line(filename, lineno, format, arg1, arg2, arg3);
+ return;
+ }
+ ::error(format, arg1, arg2, arg3);
+}
+
+void command_error(const char *format, const errarg &arg1,
+ const errarg &arg2, const errarg &arg3)
+{
+ input_stack::error(format, arg1, arg2, arg3);
+}
+
+// # not recognized in ""
+// \<newline> is recognized in ""
+// # does not conceal newline
+// if missing closing quote, word extends to end of line
+// no special treatment of \ other than before newline
+// \<newline> not recognized after #
+// ; allowed as alternative to newline
+// ; not recognized in ""
+// don't clear word_buffer; just append on
+// return -1 for EOF, 0 for newline, 1 for word
+
+int get_word(string &word_buffer)
+{
+ int c = input_stack::get_char();
+ for (;;) {
+ if (c == '#') {
+ do {
+ c = input_stack::get_char();
+ } while (c != '\n' && c != EOF);
+ break;
+ }
+ if (c == '\\' && input_stack::peek_char() == '\n')
+ input_stack::skip_char();
+ else if (c != ' ' && c != '\t')
+ break;
+ c = input_stack::get_char();
+ }
+ if (c == EOF)
+ return -1;
+ if (c == '\n' || c == ';')
+ return 0;
+ if (c == '"') {
+ for (;;) {
+ c = input_stack::peek_char();
+ if (c == EOF || c == '\n')
+ break;
+ input_stack::skip_char();
+ if (c == '"') {
+ int d = input_stack::peek_char();
+ if (d == '"')
+ input_stack::skip_char();
+ else
+ break;
+ }
+ else if (c == '\\') {
+ int d = input_stack::peek_char();
+ if (d == '\n')
+ input_stack::skip_char();
+ else
+ word_buffer += '\\';
+ }
+ else
+ word_buffer += c;
+ }
+ return 1;
+ }
+ word_buffer += c;
+ for (;;) {
+ c = input_stack::peek_char();
+ if (c == ' ' || c == '\t' || c == '\n' || c == '#' || c == ';')
+ break;
+ input_stack::skip_char();
+ if (c == '\\') {
+ int d = input_stack::peek_char();
+ if (d == '\n')
+ input_stack::skip_char();
+ else
+ word_buffer += '\\';
+ }
+ else
+ word_buffer += c;
+ }
+ return 1;
+}
+
+union argument {
+ const char *s;
+ int n;
+};
+
+// This is for debugging.
+
+static void echo_command(int argc, argument *argv)
+{
+ for (int i = 0; i < argc; i++)
+ fprintf(stderr, "%s\n", argv[i].s);
+}
+
+static void include_command(int argc, argument *argv)
+{
+ assert(argc == 1);
+ input_stack::push_file(argv[0].s);
+}
+
+static void capitalize_command(int argc, argument *argv)
+{
+ if (argc > 0)
+ capitalize_fields = argv[0].s;
+ else
+ capitalize_fields.clear();
+}
+
+static void accumulate_command(int, argument *)
+{
+ accumulate = 1;
+}
+
+static void no_accumulate_command(int, argument *)
+{
+ accumulate = 0;
+}
+
+static void move_punctuation_command(int, argument *)
+{
+ move_punctuation = 1;
+}
+
+static void no_move_punctuation_command(int, argument *)
+{
+ move_punctuation = 0;
+}
+
+static void sort_command(int argc, argument *argv)
+{
+ if (argc == 0)
+ sort_fields = "AD";
+ else
+ sort_fields = argv[0].s;
+ accumulate = 1;
+}
+
+static void no_sort_command(int, argument *)
+{
+ sort_fields.clear();
+}
+
+static void articles_command(int argc, argument *argv)
+{
+ articles.clear();
+ int i;
+ for (i = 0; i < argc; i++) {
+ articles += argv[i].s;
+ articles += '\0';
+ }
+ int len = articles.length();
+ for (i = 0; i < len; i++)
+ articles[i] = cmlower(articles[i]);
+}
+
+static void database_command(int argc, argument *argv)
+{
+ for (int i = 0; i < argc; i++)
+ database_list.add_file(argv[i].s);
+}
+
+static void default_database_command(int, argument *)
+{
+ search_default = 1;
+}
+
+static void no_default_database_command(int, argument *)
+{
+ search_default = 0;
+}
+
+static void bibliography_command(int argc, argument *argv)
+{
+ have_bibliography = 1;
+ const char *saved_filename = current_filename;
+ int saved_lineno = current_lineno;
+ int saved_label_in_text = label_in_text;
+ label_in_text = 0;
+ if (!accumulate)
+ fputs(".]<\n", stdout);
+ for (int i = 0; i < argc; i++)
+ do_bib(argv[i].s);
+ if (accumulate)
+ output_references();
+ else
+ fputs(".]>\n", stdout);
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+ label_in_text = saved_label_in_text;
+}
+
+static void annotate_command(int argc, argument *argv)
+{
+ if (argc > 0)
+ annotation_field = argv[0].s[0];
+ else
+ annotation_field = 'X';
+ if (argc == 2)
+ annotation_macro = argv[1].s;
+ else
+ annotation_macro = "AP";
+}
+
+static void no_annotate_command(int, argument *)
+{
+ annotation_macro.clear();
+ annotation_field = -1;
+}
+
+static void reverse_command(int, argument *argv)
+{
+ reverse_fields = argv[0].s;
+}
+
+static void no_reverse_command(int, argument *)
+{
+ reverse_fields.clear();
+}
+
+static void abbreviate_command(int argc, argument *argv)
+{
+ abbreviate_fields = argv[0].s;
+ period_before_initial = argc > 1 ? argv[1].s : ". ";
+ period_before_last_name = argc > 2 ? argv[2].s : ". ";
+ period_before_other = argc > 3 ? argv[3].s : ". ";
+ period_before_hyphen = argc > 4 ? argv[4].s : ".";
+}
+
+static void no_abbreviate_command(int, argument *)
+{
+ abbreviate_fields.clear();
+}
+
+string search_ignore_fields;
+
+static void search_ignore_command(int argc, argument *argv)
+{
+ if (argc > 0)
+ search_ignore_fields = argv[0].s;
+ else
+ search_ignore_fields = "XYZ";
+ search_ignore_fields += '\0';
+ linear_ignore_fields = search_ignore_fields.contents();
+}
+
+static void no_search_ignore_command(int, argument *)
+{
+ linear_ignore_fields = "";
+}
+
+static void search_truncate_command(int argc, argument *argv)
+{
+ if (argc > 0)
+ linear_truncate_len = argv[0].n;
+ else
+ linear_truncate_len = 6;
+}
+
+static void no_search_truncate_command(int, argument *)
+{
+ linear_truncate_len = -1;
+}
+
+static void discard_command(int argc, argument *argv)
+{
+ if (argc == 0)
+ discard_fields = "XYZ";
+ else
+ discard_fields = argv[0].s;
+ accumulate = 1;
+}
+
+static void no_discard_command(int, argument *)
+{
+ discard_fields.clear();
+}
+
+static void label_command(int, argument *argv)
+{
+ set_label_spec(argv[0].s);
+}
+
+static void abbreviate_label_ranges_command(int argc, argument *argv)
+{
+ abbreviate_label_ranges = 1;
+ label_range_indicator = argc > 0 ? argv[0].s : "-";
+}
+
+static void no_abbreviate_label_ranges_command(int, argument *)
+{
+ abbreviate_label_ranges = 0;
+}
+
+static void label_in_reference_command(int, argument *)
+{
+ label_in_reference = 1;
+}
+
+static void no_label_in_reference_command(int, argument *)
+{
+ label_in_reference = 0;
+}
+
+static void label_in_text_command(int, argument *)
+{
+ label_in_text = 1;
+}
+
+static void no_label_in_text_command(int, argument *)
+{
+ label_in_text = 0;
+}
+
+static void sort_adjacent_labels_command(int, argument *)
+{
+ sort_adjacent_labels = 1;
+}
+
+static void no_sort_adjacent_labels_command(int, argument *)
+{
+ sort_adjacent_labels = 0;
+}
+
+static void date_as_label_command(int argc, argument *argv)
+{
+ if (set_date_label_spec(argc > 0 ? argv[0].s : "D%a*"))
+ date_as_label = 1;
+}
+
+static void no_date_as_label_command(int, argument *)
+{
+ date_as_label = 0;
+}
+
+static void short_label_command(int, argument *argv)
+{
+ if (set_short_label_spec(argv[0].s))
+ short_label_flag = 1;
+}
+
+static void no_short_label_command(int, argument *)
+{
+ short_label_flag = 0;
+}
+
+static void compatible_command(int, argument *)
+{
+ compatible_flag = 1;
+}
+
+static void no_compatible_command(int, argument *)
+{
+ compatible_flag = 0;
+}
+
+static void join_authors_command(int argc, argument *argv)
+{
+ join_authors_exactly_two = argv[0].s;
+ join_authors_default = argc > 1 ? argv[1].s : argv[0].s;
+ join_authors_last_two = argc == 3 ? argv[2].s : argv[0].s;
+}
+
+static void bracket_label_command(int, argument *argv)
+{
+ pre_label = argv[0].s;
+ post_label = argv[1].s;
+ sep_label = argv[2].s;
+}
+
+static void separate_label_second_parts_command(int, argument *argv)
+{
+ separate_label_second_parts = argv[0].s;
+}
+
+static void et_al_command(int argc, argument *argv)
+{
+ et_al = argv[0].s;
+ et_al_min_elide = argv[1].n;
+ if (et_al_min_elide < 1)
+ et_al_min_elide = 1;
+ et_al_min_total = argc >= 3 ? argv[2].n : 0;
+}
+
+static void no_et_al_command(int, argument *)
+{
+ et_al.clear();
+ et_al_min_elide = 0;
+}
+
+typedef void (*command_t)(int, argument *);
+
+/* arg_types is a string describing the numbers and types of arguments.
+s means a string, i means an integer, f is a list of fields, F is
+a single field,
+? means that the previous argument is optional, * means that the
+previous argument can occur any number of times. */
+
+struct S {
+ const char *name;
+ command_t func;
+ const char *arg_types;
+} command_table[] = {
+ { "include", include_command, "s" },
+ { "echo", echo_command, "s*" },
+ { "capitalize", capitalize_command, "f?" },
+ { "accumulate", accumulate_command, "" },
+ { "no-accumulate", no_accumulate_command, "" },
+ { "move-punctuation", move_punctuation_command, "" },
+ { "no-move-punctuation", no_move_punctuation_command, "" },
+ { "sort", sort_command, "s?" },
+ { "no-sort", no_sort_command, "" },
+ { "articles", articles_command, "s*" },
+ { "database", database_command, "ss*" },
+ { "default-database", default_database_command, "" },
+ { "no-default-database", no_default_database_command, "" },
+ { "bibliography", bibliography_command, "ss*" },
+ { "annotate", annotate_command, "F?s?" },
+ { "no-annotate", no_annotate_command, "" },
+ { "reverse", reverse_command, "s" },
+ { "no-reverse", no_reverse_command, "" },
+ { "abbreviate", abbreviate_command, "ss?s?s?s?" },
+ { "no-abbreviate", no_abbreviate_command, "" },
+ { "search-ignore", search_ignore_command, "f?" },
+ { "no-search-ignore", no_search_ignore_command, "" },
+ { "search-truncate", search_truncate_command, "i?" },
+ { "no-search-truncate", no_search_truncate_command, "" },
+ { "discard", discard_command, "f?" },
+ { "no-discard", no_discard_command, "" },
+ { "label", label_command, "s" },
+ { "abbreviate-label-ranges", abbreviate_label_ranges_command, "s?" },
+ { "no-abbreviate-label-ranges", no_abbreviate_label_ranges_command, "" },
+ { "label-in-reference", label_in_reference_command, "" },
+ { "no-label-in-reference", no_label_in_reference_command, "" },
+ { "label-in-text", label_in_text_command, "" },
+ { "no-label-in-text", no_label_in_text_command, "" },
+ { "sort-adjacent-labels", sort_adjacent_labels_command, "" },
+ { "no-sort-adjacent-labels", no_sort_adjacent_labels_command, "" },
+ { "date-as-label", date_as_label_command, "s?" },
+ { "no-date-as-label", no_date_as_label_command, "" },
+ { "short-label", short_label_command, "s" },
+ { "no-short-label", no_short_label_command, "" },
+ { "compatible", compatible_command, "" },
+ { "no-compatible", no_compatible_command, "" },
+ { "join-authors", join_authors_command, "sss?" },
+ { "bracket-label", bracket_label_command, "sss" },
+ { "separate-label-second-parts", separate_label_second_parts_command, "s" },
+ { "et-al", et_al_command, "sii?" },
+ { "no-et-al", no_et_al_command, "" },
+};
+
+static int check_args(const char *types, const char *name,
+ int argc, argument *argv)
+{
+ int argno = 0;
+ while (*types) {
+ if (argc == 0) {
+ if (types[1] == '?')
+ break;
+ else if (types[1] == '*') {
+ assert(types[2] == '\0');
+ break;
+ }
+ else {
+ input_stack::error("missing argument for command '%1'", name);
+ return 0;
+ }
+ }
+ switch (*types) {
+ case 's':
+ break;
+ case 'i':
+ {
+ char *ptr;
+ long n = strtol(argv->s, &ptr, 10);
+ if ((n == 0 && ptr == argv->s)
+ || *ptr != '\0') {
+ input_stack::error("argument %1 for command '%2' must be an integer",
+ argno + 1, name);
+ return 0;
+ }
+ argv->n = (int)n;
+ break;
+ }
+ case 'f':
+ {
+ for (const char *ptr = argv->s; *ptr != '\0'; ptr++)
+ if (!cs_field_name(*ptr)) {
+ input_stack::error("argument %1 for command '%2' must be a list of fields",
+ argno + 1, name);
+ return 0;
+ }
+ break;
+ }
+ case 'F':
+ if (argv->s[0] == '\0' || argv->s[1] != '\0'
+ || !cs_field_name(argv->s[0])) {
+ input_stack::error("argument %1 for command '%2' must be a field name",
+ argno + 1, name);
+ return 0;
+ }
+ break;
+ default:
+ assert(0);
+ }
+ if (types[1] == '?')
+ types += 2;
+ else if (types[1] != '*')
+ types += 1;
+ --argc;
+ ++argv;
+ ++argno;
+ }
+ if (argc > 0) {
+ input_stack::error("too many arguments for command '%1'", name);
+ return 0;
+ }
+ return 1;
+}
+
+static void execute_command(const char *name, int argc, argument *argv)
+{
+ for (unsigned int i = 0;
+ i < sizeof(command_table)/sizeof(command_table[0]); i++)
+ if (strcmp(name, command_table[i].name) == 0) {
+ if (check_args(command_table[i].arg_types, name, argc, argv))
+ (*command_table[i].func)(argc, argv);
+ return;
+ }
+ input_stack::error("unknown command '%1'", name);
+}
+
+static void command_loop()
+{
+ string command;
+ for (;;) {
+ command.clear();
+ int res = get_word(command);
+ if (res != 1) {
+ if (res == 0)
+ continue;
+ break;
+ }
+ int argc = 0;
+ command += '\0';
+ while ((res = get_word(command)) == 1) {
+ argc++;
+ command += '\0';
+ }
+ argument *argv = new argument[argc];
+ const char *ptr = command.contents();
+ for (int i = 0; i < argc; i++)
+ argv[i].s = ptr = strchr(ptr, '\0') + 1;
+ execute_command(command.contents(), argc, argv);
+ delete[] argv;
+ if (res == -1)
+ break;
+ }
+}
+
+void process_commands(string &s, const char *file, int lineno)
+{
+ const char *saved_filename = current_filename;
+ int saved_lineno = current_lineno;
+ input_stack::init();
+ current_filename = file;
+ // Report diagnostics with respect to line _before_ last newline seen.
+ current_lineno = lineno - 1;
+ input_stack::push_string(s, file, lineno);
+ command_loop();
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/refer/command.h b/src/preproc/refer/command.h
new file mode 100644
index 0000000..db850f4
--- /dev/null
+++ b/src/preproc/refer/command.h
@@ -0,0 +1,35 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+void process_commands(string &s, const char *file, int lineno);
+
+extern int have_bibliography;
+extern int accumulate;
+extern int move_punctuation;
+extern int search_default;
+extern search_list database_list;
+extern int label_in_text;
+extern int label_in_reference;
+extern int sort_adjacent_labels;
+extern string pre_label;
+extern string post_label;
+extern string sep_label;
+
+extern void do_bib(const char *);
+extern void output_references();
diff --git a/src/preproc/refer/label.cpp b/src/preproc/refer/label.cpp
new file mode 100644
index 0000000..f8c1645
--- /dev/null
+++ b/src/preproc/refer/label.cpp
@@ -0,0 +1,2607 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30802
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.8.2"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* First part of user prologue. */
+#line 19 "../src/preproc/refer/label.ypp"
+
+
+#include "refer.h"
+#include "refid.h"
+#include "ref.h"
+#include "token.h"
+
+int yylex();
+void yyerror(const char *);
+
+static const char *format_serial(char c, int n);
+
+struct label_info {
+ int start;
+ int length;
+ int count;
+ int total;
+ label_info(const string &);
+};
+
+label_info *lookup_label(const string &label);
+
+struct expression {
+ enum {
+ // Does the tentative label depend on the reference?
+ CONTAINS_VARIABLE = 01,
+ CONTAINS_STAR = 02,
+ CONTAINS_FORMAT = 04,
+ CONTAINS_AT = 010
+ };
+ virtual ~expression() { }
+ virtual void evaluate(int, const reference &, string &,
+ substring_position &) = 0;
+ virtual unsigned analyze() { return 0; }
+};
+
+class at_expr : public expression {
+public:
+ at_expr() { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return CONTAINS_VARIABLE|CONTAINS_AT; }
+};
+
+class format_expr : public expression {
+ char type;
+ int width;
+ int first_number;
+public:
+ format_expr(char c, int w = 0, int f = 1)
+ : type(c), width(w), first_number(f) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return CONTAINS_FORMAT; }
+};
+
+class field_expr : public expression {
+ int number;
+ char name;
+public:
+ field_expr(char nm, int num) : number(num), name(nm) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return CONTAINS_VARIABLE; }
+};
+
+class literal_expr : public expression {
+ string s;
+public:
+ literal_expr(const char *ptr, int len) : s(ptr, len) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class unary_expr : public expression {
+protected:
+ expression *expr;
+public:
+ unary_expr(expression *e) : expr(e) { }
+ ~unary_expr() { delete expr; }
+ void evaluate(int, const reference &, string &, substring_position &) = 0;
+ unsigned analyze() { return expr ? expr->analyze() : 0; }
+};
+
+// This caches the analysis of an expression.
+
+class analyzed_expr : public unary_expr {
+ unsigned flags;
+public:
+ analyzed_expr(expression *);
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return flags; }
+};
+
+class star_expr : public unary_expr {
+public:
+ star_expr(expression *e) : unary_expr(e) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() {
+ return ((expr ? (expr->analyze() & ~CONTAINS_VARIABLE) : 0)
+ | CONTAINS_STAR);
+ }
+};
+
+typedef void map_func(const char *, const char *, string &);
+
+class map_expr : public unary_expr {
+ map_func *func;
+public:
+ map_expr(expression *e, map_func *f) : unary_expr(e), func(f) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+typedef const char *extractor_func(const char *, const char *, const char **);
+
+class extractor_expr : public unary_expr {
+ int part;
+ extractor_func *func;
+public:
+ enum { BEFORE = +1, MATCH = 0, AFTER = -1 };
+ extractor_expr(expression *e, extractor_func *f, int pt)
+ : unary_expr(e), part(pt), func(f) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class truncate_expr : public unary_expr {
+ int n;
+public:
+ truncate_expr(expression *e, int i) : unary_expr(e), n(i) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class separator_expr : public unary_expr {
+public:
+ separator_expr(expression *e) : unary_expr(e) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class binary_expr : public expression {
+protected:
+ expression *expr1;
+ expression *expr2;
+public:
+ binary_expr(expression *e1, expression *e2) : expr1(e1), expr2(e2) { }
+ ~binary_expr() { delete expr1; delete expr2; }
+ void evaluate(int, const reference &, string &, substring_position &) = 0;
+ unsigned analyze() {
+ return (expr1 ? expr1->analyze() : 0) | (expr2 ? expr2->analyze() : 0);
+ }
+};
+
+class alternative_expr : public binary_expr {
+public:
+ alternative_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class list_expr : public binary_expr {
+public:
+ list_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class substitute_expr : public binary_expr {
+public:
+ substitute_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class ternary_expr : public expression {
+protected:
+ expression *expr1;
+ expression *expr2;
+ expression *expr3;
+public:
+ ternary_expr(expression *e1, expression *e2, expression *e3)
+ : expr1(e1), expr2(e2), expr3(e3) { }
+ ~ternary_expr() { delete expr1; delete expr2; delete expr3; }
+ void evaluate(int, const reference &, string &, substring_position &) = 0;
+ unsigned analyze() {
+ return ((expr1 ? expr1->analyze() : 0)
+ | (expr2 ? expr2->analyze() : 0)
+ | (expr3 ? expr3->analyze() : 0));
+ }
+};
+
+class conditional_expr : public ternary_expr {
+public:
+ conditional_expr(expression *e1, expression *e2, expression *e3)
+ : ternary_expr(e1, e2, e3) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+static expression *parsed_label = 0;
+static expression *parsed_date_label = 0;
+static expression *parsed_short_label = 0;
+
+static expression *parse_result;
+
+string literals;
+
+
+#line 270 "src/preproc/refer/label.cpp"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Use api.header.include to #include this header
+ instead of duplicating it here. */
+#ifndef YY_YY_SRC_PREPROC_REFER_LABEL_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_REFER_LABEL_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ TOKEN_LETTER = 258, /* TOKEN_LETTER */
+ TOKEN_LITERAL = 259, /* TOKEN_LITERAL */
+ TOKEN_DIGIT = 260 /* TOKEN_DIGIT */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define TOKEN_LETTER 258
+#define TOKEN_LITERAL 259
+#define TOKEN_DIGIT 260
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 218 "../src/preproc/refer/label.ypp"
+
+ int num;
+ expression *expr;
+ struct { int ndigits; int val; } dig;
+ struct { int start; int len; } str;
+
+#line 340 "src/preproc/refer/label.cpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_REFER_LABEL_HPP_INCLUDED */
+/* Symbol kind. */
+enum yysymbol_kind_t
+{
+ YYSYMBOL_YYEMPTY = -2,
+ YYSYMBOL_YYEOF = 0, /* "end of file" */
+ YYSYMBOL_YYerror = 1, /* error */
+ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */
+ YYSYMBOL_TOKEN_LETTER = 3, /* TOKEN_LETTER */
+ YYSYMBOL_TOKEN_LITERAL = 4, /* TOKEN_LITERAL */
+ YYSYMBOL_TOKEN_DIGIT = 5, /* TOKEN_DIGIT */
+ YYSYMBOL_6_ = 6, /* '?' */
+ YYSYMBOL_7_ = 7, /* ':' */
+ YYSYMBOL_8_ = 8, /* '|' */
+ YYSYMBOL_9_ = 9, /* '&' */
+ YYSYMBOL_10_ = 10, /* '~' */
+ YYSYMBOL_11_ = 11, /* '@' */
+ YYSYMBOL_12_ = 12, /* '%' */
+ YYSYMBOL_13_ = 13, /* '.' */
+ YYSYMBOL_14_ = 14, /* '+' */
+ YYSYMBOL_15_ = 15, /* '-' */
+ YYSYMBOL_16_ = 16, /* '*' */
+ YYSYMBOL_17_ = 17, /* '(' */
+ YYSYMBOL_18_ = 18, /* ')' */
+ YYSYMBOL_19_ = 19, /* '<' */
+ YYSYMBOL_20_ = 20, /* '>' */
+ YYSYMBOL_YYACCEPT = 21, /* $accept */
+ YYSYMBOL_expr = 22, /* expr */
+ YYSYMBOL_conditional = 23, /* conditional */
+ YYSYMBOL_optional_conditional = 24, /* optional_conditional */
+ YYSYMBOL_alternative = 25, /* alternative */
+ YYSYMBOL_list = 26, /* list */
+ YYSYMBOL_substitute = 27, /* substitute */
+ YYSYMBOL_string = 28, /* string */
+ YYSYMBOL_optional_number = 29, /* optional_number */
+ YYSYMBOL_number = 30, /* number */
+ YYSYMBOL_digits = 31, /* digits */
+ YYSYMBOL_flag = 32 /* flag */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_int8 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if !defined yyoverflow
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* !defined yyoverflow */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yy_state_t yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 21
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 39
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 21
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 12
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 34
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 49
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 260
+
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ (0 <= (YYX) && (YYX) <= YYMAXUTOK \
+ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \
+ : YYSYMBOL_YYUNDEF)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_int8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 12, 9, 2,
+ 17, 18, 16, 14, 2, 15, 13, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 7, 2,
+ 19, 2, 20, 6, 11, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 8, 2, 10, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5
+};
+
+#if YYDEBUG
+/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_int16 yyrline[] =
+{
+ 0, 246, 246, 251, 253, 259, 260, 265, 267, 269,
+ 274, 276, 281, 283, 288, 290, 295, 297, 299, 315,
+ 319, 350, 352, 354, 356, 358, 364, 365, 370, 372,
+ 377, 379, 386, 387, 389
+};
+#endif
+
+/** Accessing symbol of state STATE. */
+#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State])
+
+#if YYDEBUG || 0
+/* The user-facing name of the symbol whose (internal) number is
+ YYSYMBOL. No bounds checking. */
+static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
+
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "\"end of file\"", "error", "\"invalid token\"", "TOKEN_LETTER",
+ "TOKEN_LITERAL", "TOKEN_DIGIT", "'?'", "':'", "'|'", "'&'", "'~'", "'@'",
+ "'%'", "'.'", "'+'", "'-'", "'*'", "'('", "')'", "'<'", "'>'", "$accept",
+ "expr", "conditional", "optional_conditional", "alternative", "list",
+ "substitute", "string", "optional_number", "number", "digits", "flag", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#define YYPACT_NINF (-26)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-1)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int8 yypact[] =
+{
+ 2, 11, -26, -26, 12, 2, 2, 24, -26, -26,
+ 21, 2, 18, -6, -26, 26, -26, -26, 27, 15,
+ 14, -26, 2, 2, 2, 18, 2, -3, 11, 11,
+ -26, -26, -26, -26, -26, 28, 2, 2, -6, -26,
+ -26, 33, 26, 26, 2, 11, -26, -26, 26
+};
+
+/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_int8 yydefact[] =
+{
+ 5, 16, 15, 14, 0, 5, 5, 0, 6, 2,
+ 3, 7, 10, 12, 28, 17, 18, 30, 19, 0,
+ 0, 1, 5, 0, 0, 11, 0, 32, 0, 0,
+ 23, 29, 31, 24, 25, 0, 8, 9, 13, 33,
+ 34, 0, 21, 22, 0, 26, 4, 20, 27
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -26, -26, -7, -4, -26, -1, -11, 13, -26, -25,
+ -26, -26
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ 0, 7, 8, 9, 10, 11, 12, 13, 47, 15,
+ 18, 41
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_int8 yytable[] =
+{
+ 25, 19, 20, 42, 43, 1, 2, 27, 28, 29,
+ 30, 39, 40, 3, 4, 16, 14, 17, 35, 5,
+ 48, 6, 36, 37, 21, 25, 25, 22, 26, 23,
+ 24, 31, 32, 33, 34, 44, 45, 46, 0, 38
+};
+
+static const yytype_int8 yycheck[] =
+{
+ 11, 5, 6, 28, 29, 3, 4, 13, 14, 15,
+ 16, 14, 15, 11, 12, 3, 5, 5, 22, 17,
+ 45, 19, 23, 24, 0, 36, 37, 6, 10, 8,
+ 9, 5, 5, 18, 20, 7, 3, 44, -1, 26
+};
+
+/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ state STATE-NUM. */
+static const yytype_int8 yystos[] =
+{
+ 0, 3, 4, 11, 12, 17, 19, 22, 23, 24,
+ 25, 26, 27, 28, 5, 30, 3, 5, 31, 24,
+ 24, 0, 6, 8, 9, 27, 10, 13, 14, 15,
+ 16, 5, 5, 18, 20, 24, 26, 26, 28, 14,
+ 15, 32, 30, 30, 7, 3, 23, 29, 30
+};
+
+/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr1[] =
+{
+ 0, 21, 22, 23, 23, 24, 24, 25, 25, 25,
+ 26, 26, 27, 27, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 29, 29, 30, 30,
+ 31, 31, 32, 32, 32
+};
+
+/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 1, 1, 5, 0, 1, 1, 3, 3,
+ 1, 2, 1, 3, 1, 1, 1, 2, 2, 2,
+ 5, 3, 3, 2, 3, 3, 0, 1, 1, 2,
+ 1, 2, 0, 1, 1
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYNOMEM goto yyexhaustedlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (!yyvaluep)
+ return;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ yy_symbol_value_print (yyo, yykind, yyvaluep);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)]);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep)
+{
+ YY_USE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/* Lookahead token kind. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ int yyn;
+ /* The return value of yyparse. */
+ int yyresult;
+ /* Lookahead symbol kind. */
+ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yysetstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ YY_IGNORE_USELESS_CAST_BEGIN
+ *yyssp = YY_CAST (yy_state_t, yystate);
+ YY_IGNORE_USELESS_CAST_END
+ YY_STACK_PRINT (yyss, yyssp);
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ YYNOMEM;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYPTRDIFF_T yysize = yyssp - yyss + 1;
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ yy_state_t *yyss1 = yyss;
+ YYSTYPE *yyvs1 = yyvs;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * YYSIZEOF (*yyssp),
+ &yyvs1, yysize * YYSIZEOF (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ YYNOMEM;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yy_state_t *yyss1 = yyss;
+ union yyalloc *yyptr =
+ YY_CAST (union yyalloc *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize))));
+ if (! yyptr)
+ YYNOMEM;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YY_IGNORE_USELESS_CAST_BEGIN
+ YYDPRINTF ((stderr, "Stack size increased to %ld\n",
+ YY_CAST (long, yystacksize)));
+ YY_IGNORE_USELESS_CAST_END
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token\n"));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = YYEOF;
+ yytoken = YYSYMBOL_YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else if (yychar == YYerror)
+ {
+ /* The scanner already issued an error message, process directly
+ to error recovery. But do not keep the error token as
+ lookahead, it is too special and may lead us to an endless
+ loop in error recovery. */
+ yychar = YYUNDEF;
+ yytoken = YYSYMBOL_YYerror;
+ goto yyerrlab1;
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 2: /* expr: optional_conditional */
+#line 247 "../src/preproc/refer/label.ypp"
+ { parse_result = ((yyvsp[0].expr) ? new analyzed_expr((yyvsp[0].expr)) : 0); }
+#line 1370 "src/preproc/refer/label.cpp"
+ break;
+
+ case 3: /* conditional: alternative */
+#line 252 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = (yyvsp[0].expr); }
+#line 1376 "src/preproc/refer/label.cpp"
+ break;
+
+ case 4: /* conditional: alternative '?' optional_conditional ':' conditional */
+#line 254 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new conditional_expr((yyvsp[-4].expr), (yyvsp[-2].expr), (yyvsp[0].expr)); }
+#line 1382 "src/preproc/refer/label.cpp"
+ break;
+
+ case 5: /* optional_conditional: %empty */
+#line 259 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = 0; }
+#line 1388 "src/preproc/refer/label.cpp"
+ break;
+
+ case 6: /* optional_conditional: conditional */
+#line 261 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = (yyvsp[0].expr); }
+#line 1394 "src/preproc/refer/label.cpp"
+ break;
+
+ case 7: /* alternative: list */
+#line 266 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = (yyvsp[0].expr); }
+#line 1400 "src/preproc/refer/label.cpp"
+ break;
+
+ case 8: /* alternative: alternative '|' list */
+#line 268 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new alternative_expr((yyvsp[-2].expr), (yyvsp[0].expr)); }
+#line 1406 "src/preproc/refer/label.cpp"
+ break;
+
+ case 9: /* alternative: alternative '&' list */
+#line 270 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new conditional_expr((yyvsp[-2].expr), (yyvsp[0].expr), 0); }
+#line 1412 "src/preproc/refer/label.cpp"
+ break;
+
+ case 10: /* list: substitute */
+#line 275 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = (yyvsp[0].expr); }
+#line 1418 "src/preproc/refer/label.cpp"
+ break;
+
+ case 11: /* list: list substitute */
+#line 277 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new list_expr((yyvsp[-1].expr), (yyvsp[0].expr)); }
+#line 1424 "src/preproc/refer/label.cpp"
+ break;
+
+ case 12: /* substitute: string */
+#line 282 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = (yyvsp[0].expr); }
+#line 1430 "src/preproc/refer/label.cpp"
+ break;
+
+ case 13: /* substitute: substitute '~' string */
+#line 284 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new substitute_expr((yyvsp[-2].expr), (yyvsp[0].expr)); }
+#line 1436 "src/preproc/refer/label.cpp"
+ break;
+
+ case 14: /* string: '@' */
+#line 289 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new at_expr; }
+#line 1442 "src/preproc/refer/label.cpp"
+ break;
+
+ case 15: /* string: TOKEN_LITERAL */
+#line 291 "../src/preproc/refer/label.ypp"
+ {
+ (yyval.expr) = new literal_expr(literals.contents() + (yyvsp[0].str).start,
+ (yyvsp[0].str).len);
+ }
+#line 1451 "src/preproc/refer/label.cpp"
+ break;
+
+ case 16: /* string: TOKEN_LETTER */
+#line 296 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new field_expr((yyvsp[0].num), 0); }
+#line 1457 "src/preproc/refer/label.cpp"
+ break;
+
+ case 17: /* string: TOKEN_LETTER number */
+#line 298 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new field_expr((yyvsp[-1].num), (yyvsp[0].num) - 1); }
+#line 1463 "src/preproc/refer/label.cpp"
+ break;
+
+ case 18: /* string: '%' TOKEN_LETTER */
+#line 300 "../src/preproc/refer/label.ypp"
+ {
+ switch ((yyvsp[0].num)) {
+ case 'I':
+ case 'i':
+ case 'A':
+ case 'a':
+ (yyval.expr) = new format_expr((yyvsp[0].num));
+ break;
+ default:
+ command_error("unrecognized format '%1'", char((yyvsp[0].num)));
+ (yyval.expr) = new format_expr('a');
+ break;
+ }
+ }
+#line 1482 "src/preproc/refer/label.cpp"
+ break;
+
+ case 19: /* string: '%' digits */
+#line 316 "../src/preproc/refer/label.ypp"
+ {
+ (yyval.expr) = new format_expr('0', (yyvsp[0].dig).ndigits, (yyvsp[0].dig).val);
+ }
+#line 1490 "src/preproc/refer/label.cpp"
+ break;
+
+ case 20: /* string: string '.' flag TOKEN_LETTER optional_number */
+#line 320 "../src/preproc/refer/label.ypp"
+ {
+ switch ((yyvsp[-1].num)) {
+ case 'l':
+ (yyval.expr) = new map_expr((yyvsp[-4].expr), lowercase);
+ break;
+ case 'u':
+ (yyval.expr) = new map_expr((yyvsp[-4].expr), uppercase);
+ break;
+ case 'c':
+ (yyval.expr) = new map_expr((yyvsp[-4].expr), capitalize);
+ break;
+ case 'r':
+ (yyval.expr) = new map_expr((yyvsp[-4].expr), reverse_name);
+ break;
+ case 'a':
+ (yyval.expr) = new map_expr((yyvsp[-4].expr), abbreviate_name);
+ break;
+ case 'y':
+ (yyval.expr) = new extractor_expr((yyvsp[-4].expr), find_year, (yyvsp[-2].num));
+ break;
+ case 'n':
+ (yyval.expr) = new extractor_expr((yyvsp[-4].expr), find_last_name, (yyvsp[-2].num));
+ break;
+ default:
+ (yyval.expr) = (yyvsp[-4].expr);
+ command_error("unknown function '%1'", char((yyvsp[-1].num)));
+ break;
+ }
+ }
+#line 1524 "src/preproc/refer/label.cpp"
+ break;
+
+ case 21: /* string: string '+' number */
+#line 351 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new truncate_expr((yyvsp[-2].expr), (yyvsp[0].num)); }
+#line 1530 "src/preproc/refer/label.cpp"
+ break;
+
+ case 22: /* string: string '-' number */
+#line 353 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new truncate_expr((yyvsp[-2].expr), -(yyvsp[0].num)); }
+#line 1536 "src/preproc/refer/label.cpp"
+ break;
+
+ case 23: /* string: string '*' */
+#line 355 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new star_expr((yyvsp[-1].expr)); }
+#line 1542 "src/preproc/refer/label.cpp"
+ break;
+
+ case 24: /* string: '(' optional_conditional ')' */
+#line 357 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = (yyvsp[-1].expr); }
+#line 1548 "src/preproc/refer/label.cpp"
+ break;
+
+ case 25: /* string: '<' optional_conditional '>' */
+#line 359 "../src/preproc/refer/label.ypp"
+ { (yyval.expr) = new separator_expr((yyvsp[-1].expr)); }
+#line 1554 "src/preproc/refer/label.cpp"
+ break;
+
+ case 26: /* optional_number: %empty */
+#line 364 "../src/preproc/refer/label.ypp"
+ { (yyval.num) = -1; }
+#line 1560 "src/preproc/refer/label.cpp"
+ break;
+
+ case 27: /* optional_number: number */
+#line 366 "../src/preproc/refer/label.ypp"
+ { (yyval.num) = (yyvsp[0].num); }
+#line 1566 "src/preproc/refer/label.cpp"
+ break;
+
+ case 28: /* number: TOKEN_DIGIT */
+#line 371 "../src/preproc/refer/label.ypp"
+ { (yyval.num) = (yyvsp[0].num); }
+#line 1572 "src/preproc/refer/label.cpp"
+ break;
+
+ case 29: /* number: number TOKEN_DIGIT */
+#line 373 "../src/preproc/refer/label.ypp"
+ { (yyval.num) = (yyvsp[-1].num)*10 + (yyvsp[0].num); }
+#line 1578 "src/preproc/refer/label.cpp"
+ break;
+
+ case 30: /* digits: TOKEN_DIGIT */
+#line 378 "../src/preproc/refer/label.ypp"
+ { (yyval.dig).ndigits = 1; (yyval.dig).val = (yyvsp[0].num); }
+#line 1584 "src/preproc/refer/label.cpp"
+ break;
+
+ case 31: /* digits: digits TOKEN_DIGIT */
+#line 380 "../src/preproc/refer/label.ypp"
+ { (yyval.dig).ndigits = (yyvsp[-1].dig).ndigits + 1; (yyval.dig).val = (yyvsp[-1].dig).val*10 + (yyvsp[0].num); }
+#line 1590 "src/preproc/refer/label.cpp"
+ break;
+
+ case 32: /* flag: %empty */
+#line 386 "../src/preproc/refer/label.ypp"
+ { (yyval.num) = 0; }
+#line 1596 "src/preproc/refer/label.cpp"
+ break;
+
+ case 33: /* flag: '+' */
+#line 388 "../src/preproc/refer/label.ypp"
+ { (yyval.num) = 1; }
+#line 1602 "src/preproc/refer/label.cpp"
+ break;
+
+ case 34: /* flag: '-' */
+#line 390 "../src/preproc/refer/label.ypp"
+ { (yyval.num) = -1; }
+#line 1608 "src/preproc/refer/label.cpp"
+ break;
+
+
+#line 1612 "src/preproc/refer/label.cpp"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ yyerror (YY_("syntax error"));
+ }
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+ ++yynerrs;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ /* Pop stack until we find a state that shifts the error token. */
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYSYMBOL_YYerror;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturnlab;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturnlab;
+
+
+/*-----------------------------------------------------------.
+| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. |
+`-----------------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturnlab;
+
+
+/*----------------------------------------------------------.
+| yyreturnlab -- parsing is finished, clean up and return. |
+`----------------------------------------------------------*/
+yyreturnlab:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ YY_ACCESSING_SYMBOL (+*yyssp), yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+
+ return yyresult;
+}
+
+#line 393 "../src/preproc/refer/label.ypp"
+
+
+/* bison defines const to be empty unless __STDC__ is defined, which it
+isn't under cfront */
+
+#ifdef const
+#undef const
+#endif
+
+const char *spec_ptr;
+const char *spec_end;
+const char *spec_cur;
+
+static char uppercase_array[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z',
+};
+
+static char lowercase_array[] = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z',
+};
+
+int yylex()
+{
+ while (spec_ptr < spec_end && csspace(*spec_ptr))
+ spec_ptr++;
+ spec_cur = spec_ptr;
+ if (spec_ptr >= spec_end)
+ return 0;
+ unsigned char c = *spec_ptr++;
+ if (csalpha(c)) {
+ yylval.num = c;
+ return TOKEN_LETTER;
+ }
+ if (csdigit(c)) {
+ yylval.num = c - '0';
+ return TOKEN_DIGIT;
+ }
+ if (c == '\'') {
+ yylval.str.start = literals.length();
+ for (; spec_ptr < spec_end; spec_ptr++) {
+ if (*spec_ptr == '\'') {
+ if (++spec_ptr < spec_end && *spec_ptr == '\'')
+ literals += '\'';
+ else {
+ yylval.str.len = literals.length() - yylval.str.start;
+ return TOKEN_LITERAL;
+ }
+ }
+ else
+ literals += *spec_ptr;
+ }
+ yylval.str.len = literals.length() - yylval.str.start;
+ return TOKEN_LITERAL;
+ }
+ return c;
+}
+
+int set_label_spec(const char *label_spec)
+{
+ spec_cur = spec_ptr = label_spec;
+ spec_end = strchr(label_spec, '\0');
+ literals.clear();
+ if (yyparse())
+ return 0;
+ delete parsed_label;
+ parsed_label = parse_result;
+ return 1;
+}
+
+int set_date_label_spec(const char *label_spec)
+{
+ spec_cur = spec_ptr = label_spec;
+ spec_end = strchr(label_spec, '\0');
+ literals.clear();
+ if (yyparse())
+ return 0;
+ delete parsed_date_label;
+ parsed_date_label = parse_result;
+ return 1;
+}
+
+int set_short_label_spec(const char *label_spec)
+{
+ spec_cur = spec_ptr = label_spec;
+ spec_end = strchr(label_spec, '\0');
+ literals.clear();
+ if (yyparse())
+ return 0;
+ delete parsed_short_label;
+ parsed_short_label = parse_result;
+ return 1;
+}
+
+void yyerror(const char *message)
+{
+ if (spec_cur < spec_end)
+ command_error("label specification %1 before '%2'", message, spec_cur);
+ else
+ command_error("label specification %1 at end of string",
+ message, spec_cur);
+}
+
+void at_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (tentative)
+ ref.canonicalize_authors(result);
+ else {
+ const char *end, *start = ref.get_authors(&end);
+ if (start)
+ result.append(start, end - start);
+ }
+}
+
+void format_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (tentative)
+ return;
+ const label_info *lp = ref.get_label_ptr();
+ int num = lp == 0 ? ref.get_number() : lp->count;
+ if (type != '0')
+ result += format_serial(type, num + 1);
+ else {
+ const char *ptr = i_to_a(num + first_number);
+ int pad = width - strlen(ptr);
+ while (--pad >= 0)
+ result += '0';
+ result += ptr;
+ }
+}
+
+static const char *format_serial(char c, int n)
+{
+ assert(n > 0);
+ static char buf[128]; // more than enough.
+ switch (c) {
+ case 'i':
+ case 'I':
+ {
+ char *p = buf;
+ // troff uses z and w to represent 10000 and 5000 in Roman
+ // numerals; I can find no historical basis for this usage
+ const char *s = c == 'i' ? "zwmdclxvi" : "ZWMDCLXVI";
+ if (n >= 40000)
+ return i_to_a(n);
+ while (n >= 10000) {
+ *p++ = s[0];
+ n -= 10000;
+ }
+ for (int i = 1000; i > 0; i /= 10, s += 2) {
+ int m = n/i;
+ n -= m*i;
+ switch (m) {
+ case 3:
+ *p++ = s[2];
+ /* falls through */
+ case 2:
+ *p++ = s[2];
+ /* falls through */
+ case 1:
+ *p++ = s[2];
+ break;
+ case 4:
+ *p++ = s[2];
+ *p++ = s[1];
+ break;
+ case 8:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 7:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 6:
+ *p++ = s[1];
+ *p++ = s[2];
+ break;
+ case 5:
+ *p++ = s[1];
+ break;
+ case 9:
+ *p++ = s[2];
+ *p++ = s[0];
+ }
+ }
+ *p = 0;
+ break;
+ }
+ case 'a':
+ case 'A':
+ {
+ char *p = buf;
+ // this is derived from troff/reg.c
+ while (n > 0) {
+ int d = n % 26;
+ if (d == 0)
+ d = 26;
+ n -= d;
+ n /= 26;
+ *p++ = c == 'a' ? lowercase_array[d - 1] :
+ uppercase_array[d - 1];
+ }
+ *p-- = 0;
+ // Reverse it.
+ char *q = buf;
+ while (q < p) {
+ char temp = *q;
+ *q = *p;
+ *p = temp;
+ --p;
+ ++q;
+ }
+ break;
+ }
+ default:
+ assert(0);
+ }
+ return buf;
+}
+
+void field_expr::evaluate(int, const reference &ref,
+ string &result, substring_position &)
+{
+ const char *end;
+ const char *start = ref.get_field(name, &end);
+ if (start) {
+ start = nth_field(number, start, &end);
+ if (start)
+ result.append(start, end - start);
+ }
+}
+
+void literal_expr::evaluate(int, const reference &,
+ string &result, substring_position &)
+{
+ result += s;
+}
+
+analyzed_expr::analyzed_expr(expression *e)
+: unary_expr(e), flags(e ? e->analyze() : 0)
+{
+}
+
+void analyzed_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ if (expr)
+ expr->evaluate(tentative, ref, result, pos);
+}
+
+void star_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ const label_info *lp = ref.get_label_ptr();
+ if (!tentative
+ && (lp == 0 || lp->total > 1)
+ && expr)
+ expr->evaluate(tentative, ref, result, pos);
+}
+
+void separator_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ int start_length = result.length();
+ int is_first = pos.start < 0;
+ if (expr)
+ expr->evaluate(tentative, ref, result, pos);
+ if (is_first) {
+ pos.start = start_length;
+ pos.length = result.length() - start_length;
+ }
+}
+
+void map_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (expr) {
+ string temp;
+ substring_position temp_pos;
+ expr->evaluate(tentative, ref, temp, temp_pos);
+ (*func)(temp.contents(), temp.contents() + temp.length(), result);
+ }
+}
+
+void extractor_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (expr) {
+ string temp;
+ substring_position temp_pos;
+ expr->evaluate(tentative, ref, temp, temp_pos);
+ const char *end, *start = (*func)(temp.contents(),
+ temp.contents() + temp.length(),
+ &end);
+ switch (part) {
+ case BEFORE:
+ if (start)
+ result.append(temp.contents(), start - temp.contents());
+ else
+ result += temp;
+ break;
+ case MATCH:
+ if (start)
+ result.append(start, end - start);
+ break;
+ case AFTER:
+ if (start)
+ result.append(end, temp.contents() + temp.length() - end);
+ break;
+ default:
+ assert(0);
+ }
+ }
+}
+
+static void first_part(int len, const char *ptr, const char *end,
+ string &result)
+{
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ const token_info *ti = lookup_token(token_start, ptr);
+ int counts = ti->sortify_non_empty(token_start, ptr);
+ if (counts && --len < 0)
+ break;
+ if (counts || ti->is_accent())
+ result.append(token_start, ptr - token_start);
+ }
+}
+
+static void last_part(int len, const char *ptr, const char *end,
+ string &result)
+{
+ const char *start = ptr;
+ int count = 0;
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ const token_info *ti = lookup_token(token_start, ptr);
+ if (ti->sortify_non_empty(token_start, ptr))
+ count++;
+ }
+ ptr = start;
+ int skip = count - len;
+ if (skip > 0) {
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ assert(0);
+ const token_info *ti = lookup_token(token_start, ptr);
+ if (ti->sortify_non_empty(token_start, ptr) && --skip < 0) {
+ ptr = token_start;
+ break;
+ }
+ }
+ }
+ first_part(len, ptr, end, result);
+}
+
+void truncate_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (expr) {
+ string temp;
+ substring_position temp_pos;
+ expr->evaluate(tentative, ref, temp, temp_pos);
+ const char *start = temp.contents();
+ const char *end = start + temp.length();
+ if (n > 0)
+ first_part(n, start, end, result);
+ else if (n < 0)
+ last_part(-n, start, end, result);
+ }
+}
+
+void alternative_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ int start_length = result.length();
+ if (expr1)
+ expr1->evaluate(tentative, ref, result, pos);
+ if (result.length() == start_length && expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+}
+
+void list_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ if (expr1)
+ expr1->evaluate(tentative, ref, result, pos);
+ if (expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+}
+
+void substitute_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ int start_length = result.length();
+ if (expr1)
+ expr1->evaluate(tentative, ref, result, pos);
+ if (result.length() > start_length && result[result.length() - 1] == '-') {
+ // ought to see if pos covers the -
+ result.set_length(result.length() - 1);
+ if (expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+ }
+}
+
+void conditional_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ string temp;
+ substring_position temp_pos;
+ if (expr1)
+ expr1->evaluate(tentative, ref, temp, temp_pos);
+ if (temp.length() > 0) {
+ if (expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+ }
+ else {
+ if (expr3)
+ expr3->evaluate(tentative, ref, result, pos);
+ }
+}
+
+void reference::pre_compute_label()
+{
+ if (parsed_label != 0
+ && (parsed_label->analyze() & expression::CONTAINS_VARIABLE)) {
+ label.clear();
+ substring_position temp_pos;
+ parsed_label->evaluate(1, *this, label, temp_pos);
+ label_ptr = lookup_label(label);
+ }
+}
+
+void reference::compute_label()
+{
+ label.clear();
+ if (parsed_label)
+ parsed_label->evaluate(0, *this, label, separator_pos);
+ if (short_label_flag && parsed_short_label)
+ parsed_short_label->evaluate(0, *this, short_label, short_separator_pos);
+ if (date_as_label) {
+ string new_date;
+ if (parsed_date_label) {
+ substring_position temp_pos;
+ parsed_date_label->evaluate(0, *this, new_date, temp_pos);
+ }
+ set_date(new_date);
+ }
+ if (label_ptr)
+ label_ptr->count += 1;
+}
+
+void reference::immediate_compute_label()
+{
+ if (label_ptr)
+ label_ptr->total = 2; // force use of disambiguator
+ compute_label();
+}
+
+int reference::merge_labels(reference **v, int n, label_type type,
+ string &result)
+{
+ if (abbreviate_label_ranges)
+ return merge_labels_by_number(v, n, type, result);
+ else
+ return merge_labels_by_parts(v, n, type, result);
+}
+
+int reference::merge_labels_by_number(reference **v, int n, label_type type,
+ string &result)
+{
+ if (n <= 1)
+ return 0;
+ int num = get_number();
+ // Only merge three or more labels.
+ if (v[0]->get_number() != num + 1
+ || v[1]->get_number() != num + 2)
+ return 0;
+ int i;
+ for (i = 2; i < n; i++)
+ if (v[i]->get_number() != num + i + 1)
+ break;
+ result = get_label(type);
+ result += label_range_indicator;
+ result += v[i - 1]->get_label(type);
+ return i;
+}
+
+const substring_position &reference::get_separator_pos(label_type type) const
+{
+ if (type == SHORT_LABEL && short_label_flag)
+ return short_separator_pos;
+ else
+ return separator_pos;
+}
+
+const string &reference::get_label(label_type type) const
+{
+ if (type == SHORT_LABEL && short_label_flag)
+ return short_label;
+ else
+ return label;
+}
+
+int reference::merge_labels_by_parts(reference **v, int n, label_type type,
+ string &result)
+{
+ if (n <= 0)
+ return 0;
+ const string &lb = get_label(type);
+ const substring_position &sp = get_separator_pos(type);
+ if (sp.start < 0
+ || sp.start != v[0]->get_separator_pos(type).start
+ || memcmp(lb.contents(), v[0]->get_label(type).contents(),
+ sp.start) != 0)
+ return 0;
+ result = lb;
+ int i = 0;
+ do {
+ result += separate_label_second_parts;
+ const substring_position &s = v[i]->get_separator_pos(type);
+ int sep_end_pos = s.start + s.length;
+ result.append(v[i]->get_label(type).contents() + sep_end_pos,
+ v[i]->get_label(type).length() - sep_end_pos);
+ } while (++i < n
+ && sp.start == v[i]->get_separator_pos(type).start
+ && memcmp(lb.contents(), v[i]->get_label(type).contents(),
+ sp.start) == 0);
+ return i;
+}
+
+string label_pool;
+
+label_info::label_info(const string &s)
+: start(label_pool.length()), length(s.length()), count(0), total(1)
+{
+ label_pool += s;
+}
+
+static label_info **label_table = 0;
+static int label_table_size = 0;
+static int label_table_used = 0;
+
+label_info *lookup_label(const string &label)
+{
+ if (label_table == 0) {
+ label_table = new label_info *[17];
+ label_table_size = 17;
+ for (int i = 0; i < 17; i++)
+ label_table[i] = 0;
+ }
+ unsigned h = hash_string(label.contents(), label.length()) % label_table_size;
+ label_info **ptr;
+ for (ptr = label_table + h;
+ *ptr != 0;
+ (ptr == label_table)
+ ? (ptr = label_table + label_table_size - 1)
+ : ptr--)
+ if ((*ptr)->length == label.length()
+ && memcmp(label_pool.contents() + (*ptr)->start, label.contents(),
+ label.length()) == 0) {
+ (*ptr)->total += 1;
+ return *ptr;
+ }
+ label_info *result = *ptr = new label_info(label);
+ if (++label_table_used * 2 > label_table_size) {
+ // Rehash the table.
+ label_info **old_table = label_table;
+ int old_size = label_table_size;
+ label_table_size = next_size(label_table_size);
+ label_table = new label_info *[label_table_size];
+ int i;
+ for (i = 0; i < label_table_size; i++)
+ label_table[i] = 0;
+ for (i = 0; i < old_size; i++)
+ if (old_table[i]) {
+ h = hash_string(label_pool.contents() + old_table[i]->start,
+ old_table[i]->length);
+ label_info **p;
+ for (p = label_table + (h % label_table_size);
+ *p != 0;
+ (p == label_table)
+ ? (p = label_table + label_table_size - 1)
+ : --p)
+ ;
+ *p = old_table[i];
+ }
+ delete[] old_table;
+ }
+ return result;
+}
+
+void clear_labels()
+{
+ for (int i = 0; i < label_table_size; i++) {
+ delete label_table[i];
+ label_table[i] = 0;
+ }
+ label_table_used = 0;
+ label_pool.clear();
+}
+
+static void consider_authors(reference **start, reference **end, int i);
+
+void compute_labels(reference **v, int n)
+{
+ if (parsed_label
+ && (parsed_label->analyze() & expression::CONTAINS_AT)
+ && sort_fields.length() >= 2
+ && sort_fields[0] == 'A'
+ && sort_fields[1] == '+')
+ consider_authors(v, v + n, 0);
+ for (int i = 0; i < n; i++)
+ v[i]->compute_label();
+}
+
+
+/* A reference with a list of authors <A0,A1,...,AN> _needs_ author i
+where 0 <= i <= N if there exists a reference with a list of authors
+<B0,B1,...,BM> such that <A0,A1,...,AN> != <B0,B1,...,BM> and M >= i
+and Aj = Bj for 0 <= j < i. In this case if we can't say "A0,
+A1,...,A(i-1) et al" because this would match both <A0,A1,...,AN> and
+<B0,B1,...,BM>. If a reference needs author i we only have to call
+need_author(j) for some j >= i such that the reference also needs
+author j. */
+
+/* This function handles 2 tasks:
+determine which authors are needed (cannot be elided with et al.);
+determine which authors can have only last names in the labels.
+
+References >= start and < end have the same first i author names.
+Also they're sorted by A+. */
+
+static void consider_authors(reference **start, reference **end, int i)
+{
+ if (start >= end)
+ return;
+ reference **p = start;
+ if (i >= (*p)->get_nauthors()) {
+ for (++p; p < end && i >= (*p)->get_nauthors(); p++)
+ ;
+ if (p < end && i > 0) {
+ // If we have an author list <A B C> and an author list <A B C D>,
+ // then both lists need C.
+ for (reference **q = start; q < end; q++)
+ (*q)->need_author(i - 1);
+ }
+ start = p;
+ }
+ while (p < end) {
+ reference **last_name_start = p;
+ reference **name_start = p;
+ for (++p;
+ p < end && i < (*p)->get_nauthors()
+ && same_author_last_name(**last_name_start, **p, i);
+ p++) {
+ if (!same_author_name(**name_start, **p, i)) {
+ consider_authors(name_start, p, i + 1);
+ name_start = p;
+ }
+ }
+ consider_authors(name_start, p, i + 1);
+ if (last_name_start == name_start) {
+ for (reference **q = last_name_start; q < p; q++)
+ (*q)->set_last_name_unambiguous(i);
+ }
+ // If we have an author list <A B C D> and <A B C E>, then the lists
+ // need author D and E respectively.
+ if (name_start > start || p < end) {
+ for (reference **q = last_name_start; q < p; q++)
+ (*q)->need_author(i);
+ }
+ }
+}
+
+int same_author_last_name(const reference &r1, const reference &r2, int n)
+{
+ const char *ae1;
+ const char *as1 = r1.get_sort_field(0, n, 0, &ae1);
+ const char *ae2;
+ const char *as2 = r2.get_sort_field(0, n, 0, &ae2);
+ if (!as1 && !as2) return 1; // they are the same
+ if (!as1 || !as2) return 0;
+ return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0;
+}
+
+int same_author_name(const reference &r1, const reference &r2, int n)
+{
+ const char *ae1;
+ const char *as1 = r1.get_sort_field(0, n, -1, &ae1);
+ const char *ae2;
+ const char *as2 = r2.get_sort_field(0, n, -1, &ae2);
+ if (!as1 && !as2) return 1; // they are the same
+ if (!as1 || !as2) return 0;
+ return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0;
+}
+
+
+void int_set::set(int i)
+{
+ assert(i >= 0);
+ int bytei = i >> 3;
+ if (bytei >= v.length()) {
+ int old_length = v.length();
+ v.set_length(bytei + 1);
+ for (int j = old_length; j <= bytei; j++)
+ v[j] = 0;
+ }
+ v[bytei] |= 1 << (i & 7);
+}
+
+int int_set::get(int i) const
+{
+ assert(i >= 0);
+ int bytei = i >> 3;
+ return bytei >= v.length() ? 0 : (v[bytei] & (1 << (i & 7))) != 0;
+}
+
+void reference::set_last_name_unambiguous(int i)
+{
+ last_name_unambiguous.set(i);
+}
+
+void reference::need_author(int n)
+{
+ if (n > last_needed_author)
+ last_needed_author = n;
+}
+
+const char *reference::get_authors(const char **end) const
+{
+ if (!computed_authors) {
+ ((reference *)this)->computed_authors = 1;
+ string &result = ((reference *)this)->authors;
+ int na = get_nauthors();
+ result.clear();
+ for (int i = 0; i < na; i++) {
+ if (last_name_unambiguous.get(i)) {
+ const char *e, *start = get_author_last_name(i, &e);
+ assert(start != 0);
+ result.append(start, e - start);
+ }
+ else {
+ const char *e, *start = get_author(i, &e);
+ assert(start != 0);
+ result.append(start, e - start);
+ }
+ if (i == last_needed_author
+ && et_al.length() > 0
+ && et_al_min_elide > 0
+ && last_needed_author + et_al_min_elide < na
+ && na >= et_al_min_total) {
+ result += et_al;
+ break;
+ }
+ if (i < na - 1) {
+ if (na == 2)
+ result += join_authors_exactly_two;
+ else if (i < na - 2)
+ result += join_authors_default;
+ else
+ result += join_authors_last_two;
+ }
+ }
+ }
+ const char *start = authors.contents();
+ *end = start + authors.length();
+ return start;
+}
+
+int reference::get_nauthors() const
+{
+ if (nauthors < 0) {
+ const char *dummy;
+ int na;
+ for (na = 0; get_author(na, &dummy) != 0; na++)
+ ;
+ ((reference *)this)->nauthors = na;
+ }
+ return nauthors;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/refer/label.hpp b/src/preproc/refer/label.hpp
new file mode 100644
index 0000000..9f79fd2
--- /dev/null
+++ b/src/preproc/refer/label.hpp
@@ -0,0 +1,98 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison interface for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+#ifndef YY_YY_SRC_PREPROC_REFER_LABEL_HPP_INCLUDED
+# define YY_YY_SRC_PREPROC_REFER_LABEL_HPP_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ TOKEN_LETTER = 258, /* TOKEN_LETTER */
+ TOKEN_LITERAL = 259, /* TOKEN_LITERAL */
+ TOKEN_DIGIT = 260 /* TOKEN_DIGIT */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define TOKEN_LETTER 258
+#define TOKEN_LITERAL 259
+#define TOKEN_DIGIT 260
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 218 "../src/preproc/refer/label.ypp"
+
+ int num;
+ expression *expr;
+ struct { int ndigits; int val; } dig;
+ struct { int start; int len; } str;
+
+#line 84 "src/preproc/refer/label.hpp"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SRC_PREPROC_REFER_LABEL_HPP_INCLUDED */
diff --git a/src/preproc/refer/label.ypp b/src/preproc/refer/label.ypp
new file mode 100644
index 0000000..f5210d5
--- /dev/null
+++ b/src/preproc/refer/label.ypp
@@ -0,0 +1,1195 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+%{
+
+#include "refer.h"
+#include "refid.h"
+#include "ref.h"
+#include "token.h"
+
+int yylex();
+void yyerror(const char *);
+
+static const char *format_serial(char c, int n);
+
+struct label_info {
+ int start;
+ int length;
+ int count;
+ int total;
+ label_info(const string &);
+};
+
+label_info *lookup_label(const string &label);
+
+struct expression {
+ enum {
+ // Does the tentative label depend on the reference?
+ CONTAINS_VARIABLE = 01,
+ CONTAINS_STAR = 02,
+ CONTAINS_FORMAT = 04,
+ CONTAINS_AT = 010
+ };
+ virtual ~expression() { }
+ virtual void evaluate(int, const reference &, string &,
+ substring_position &) = 0;
+ virtual unsigned analyze() { return 0; }
+};
+
+class at_expr : public expression {
+public:
+ at_expr() { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return CONTAINS_VARIABLE|CONTAINS_AT; }
+};
+
+class format_expr : public expression {
+ char type;
+ int width;
+ int first_number;
+public:
+ format_expr(char c, int w = 0, int f = 1)
+ : type(c), width(w), first_number(f) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return CONTAINS_FORMAT; }
+};
+
+class field_expr : public expression {
+ int number;
+ char name;
+public:
+ field_expr(char nm, int num) : number(num), name(nm) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return CONTAINS_VARIABLE; }
+};
+
+class literal_expr : public expression {
+ string s;
+public:
+ literal_expr(const char *ptr, int len) : s(ptr, len) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class unary_expr : public expression {
+protected:
+ expression *expr;
+public:
+ unary_expr(expression *e) : expr(e) { }
+ ~unary_expr() { delete expr; }
+ void evaluate(int, const reference &, string &, substring_position &) = 0;
+ unsigned analyze() { return expr ? expr->analyze() : 0; }
+};
+
+// This caches the analysis of an expression.
+
+class analyzed_expr : public unary_expr {
+ unsigned flags;
+public:
+ analyzed_expr(expression *);
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() { return flags; }
+};
+
+class star_expr : public unary_expr {
+public:
+ star_expr(expression *e) : unary_expr(e) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+ unsigned analyze() {
+ return ((expr ? (expr->analyze() & ~CONTAINS_VARIABLE) : 0)
+ | CONTAINS_STAR);
+ }
+};
+
+typedef void map_func(const char *, const char *, string &);
+
+class map_expr : public unary_expr {
+ map_func *func;
+public:
+ map_expr(expression *e, map_func *f) : unary_expr(e), func(f) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+typedef const char *extractor_func(const char *, const char *, const char **);
+
+class extractor_expr : public unary_expr {
+ int part;
+ extractor_func *func;
+public:
+ enum { BEFORE = +1, MATCH = 0, AFTER = -1 };
+ extractor_expr(expression *e, extractor_func *f, int pt)
+ : unary_expr(e), part(pt), func(f) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class truncate_expr : public unary_expr {
+ int n;
+public:
+ truncate_expr(expression *e, int i) : unary_expr(e), n(i) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class separator_expr : public unary_expr {
+public:
+ separator_expr(expression *e) : unary_expr(e) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class binary_expr : public expression {
+protected:
+ expression *expr1;
+ expression *expr2;
+public:
+ binary_expr(expression *e1, expression *e2) : expr1(e1), expr2(e2) { }
+ ~binary_expr() { delete expr1; delete expr2; }
+ void evaluate(int, const reference &, string &, substring_position &) = 0;
+ unsigned analyze() {
+ return (expr1 ? expr1->analyze() : 0) | (expr2 ? expr2->analyze() : 0);
+ }
+};
+
+class alternative_expr : public binary_expr {
+public:
+ alternative_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class list_expr : public binary_expr {
+public:
+ list_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class substitute_expr : public binary_expr {
+public:
+ substitute_expr(expression *e1, expression *e2) : binary_expr(e1, e2) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+class ternary_expr : public expression {
+protected:
+ expression *expr1;
+ expression *expr2;
+ expression *expr3;
+public:
+ ternary_expr(expression *e1, expression *e2, expression *e3)
+ : expr1(e1), expr2(e2), expr3(e3) { }
+ ~ternary_expr() { delete expr1; delete expr2; delete expr3; }
+ void evaluate(int, const reference &, string &, substring_position &) = 0;
+ unsigned analyze() {
+ return ((expr1 ? expr1->analyze() : 0)
+ | (expr2 ? expr2->analyze() : 0)
+ | (expr3 ? expr3->analyze() : 0));
+ }
+};
+
+class conditional_expr : public ternary_expr {
+public:
+ conditional_expr(expression *e1, expression *e2, expression *e3)
+ : ternary_expr(e1, e2, e3) { }
+ void evaluate(int, const reference &, string &, substring_position &);
+};
+
+static expression *parsed_label = 0;
+static expression *parsed_date_label = 0;
+static expression *parsed_short_label = 0;
+
+static expression *parse_result;
+
+string literals;
+
+%}
+
+%union {
+ int num;
+ expression *expr;
+ struct { int ndigits; int val; } dig;
+ struct { int start; int len; } str;
+}
+
+/* uppercase or lowercase letter */
+%token <num> TOKEN_LETTER
+/* literal characters */
+%token <str> TOKEN_LITERAL
+/* digit */
+%token <num> TOKEN_DIGIT
+
+%type <expr> conditional
+%type <expr> alternative
+%type <expr> list
+%type <expr> string
+%type <expr> substitute
+%type <expr> optional_conditional
+%type <num> number
+%type <dig> digits
+%type <num> optional_number
+%type <num> flag
+
+%%
+
+expr:
+ optional_conditional
+ { parse_result = ($1 ? new analyzed_expr($1) : 0); }
+ ;
+
+conditional:
+ alternative
+ { $$ = $1; }
+ | alternative '?' optional_conditional ':' conditional
+ { $$ = new conditional_expr($1, $3, $5); }
+ ;
+
+optional_conditional:
+ /* empty */
+ { $$ = 0; }
+ | conditional
+ { $$ = $1; }
+ ;
+
+alternative:
+ list
+ { $$ = $1; }
+ | alternative '|' list
+ { $$ = new alternative_expr($1, $3); }
+ | alternative '&' list
+ { $$ = new conditional_expr($1, $3, 0); }
+ ;
+
+list:
+ substitute
+ { $$ = $1; }
+ | list substitute
+ { $$ = new list_expr($1, $2); }
+ ;
+
+substitute:
+ string
+ { $$ = $1; }
+ | substitute '~' string
+ { $$ = new substitute_expr($1, $3); }
+ ;
+
+string:
+ '@'
+ { $$ = new at_expr; }
+ | TOKEN_LITERAL
+ {
+ $$ = new literal_expr(literals.contents() + $1.start,
+ $1.len);
+ }
+ | TOKEN_LETTER
+ { $$ = new field_expr($1, 0); }
+ | TOKEN_LETTER number
+ { $$ = new field_expr($1, $2 - 1); }
+ | '%' TOKEN_LETTER
+ {
+ switch ($2) {
+ case 'I':
+ case 'i':
+ case 'A':
+ case 'a':
+ $$ = new format_expr($2);
+ break;
+ default:
+ command_error("unrecognized format '%1'", char($2));
+ $$ = new format_expr('a');
+ break;
+ }
+ }
+
+ | '%' digits
+ {
+ $$ = new format_expr('0', $2.ndigits, $2.val);
+ }
+ | string '.' flag TOKEN_LETTER optional_number
+ {
+ switch ($4) {
+ case 'l':
+ $$ = new map_expr($1, lowercase);
+ break;
+ case 'u':
+ $$ = new map_expr($1, uppercase);
+ break;
+ case 'c':
+ $$ = new map_expr($1, capitalize);
+ break;
+ case 'r':
+ $$ = new map_expr($1, reverse_name);
+ break;
+ case 'a':
+ $$ = new map_expr($1, abbreviate_name);
+ break;
+ case 'y':
+ $$ = new extractor_expr($1, find_year, $3);
+ break;
+ case 'n':
+ $$ = new extractor_expr($1, find_last_name, $3);
+ break;
+ default:
+ $$ = $1;
+ command_error("unknown function '%1'", char($4));
+ break;
+ }
+ }
+
+ | string '+' number
+ { $$ = new truncate_expr($1, $3); }
+ | string '-' number
+ { $$ = new truncate_expr($1, -$3); }
+ | string '*'
+ { $$ = new star_expr($1); }
+ | '(' optional_conditional ')'
+ { $$ = $2; }
+ | '<' optional_conditional '>'
+ { $$ = new separator_expr($2); }
+ ;
+
+optional_number:
+ /* empty */
+ { $$ = -1; }
+ | number
+ { $$ = $1; }
+ ;
+
+number:
+ TOKEN_DIGIT
+ { $$ = $1; }
+ | number TOKEN_DIGIT
+ { $$ = $1*10 + $2; }
+ ;
+
+digits:
+ TOKEN_DIGIT
+ { $$.ndigits = 1; $$.val = $1; }
+ | digits TOKEN_DIGIT
+ { $$.ndigits = $1.ndigits + 1; $$.val = $1.val*10 + $2; }
+ ;
+
+
+flag:
+ /* empty */
+ { $$ = 0; }
+ | '+'
+ { $$ = 1; }
+ | '-'
+ { $$ = -1; }
+ ;
+
+%%
+
+/* bison defines const to be empty unless __STDC__ is defined, which it
+isn't under cfront */
+
+#ifdef const
+#undef const
+#endif
+
+const char *spec_ptr;
+const char *spec_end;
+const char *spec_cur;
+
+static char uppercase_array[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z',
+};
+
+static char lowercase_array[] = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z',
+};
+
+int yylex()
+{
+ while (spec_ptr < spec_end && csspace(*spec_ptr))
+ spec_ptr++;
+ spec_cur = spec_ptr;
+ if (spec_ptr >= spec_end)
+ return 0;
+ unsigned char c = *spec_ptr++;
+ if (csalpha(c)) {
+ yylval.num = c;
+ return TOKEN_LETTER;
+ }
+ if (csdigit(c)) {
+ yylval.num = c - '0';
+ return TOKEN_DIGIT;
+ }
+ if (c == '\'') {
+ yylval.str.start = literals.length();
+ for (; spec_ptr < spec_end; spec_ptr++) {
+ if (*spec_ptr == '\'') {
+ if (++spec_ptr < spec_end && *spec_ptr == '\'')
+ literals += '\'';
+ else {
+ yylval.str.len = literals.length() - yylval.str.start;
+ return TOKEN_LITERAL;
+ }
+ }
+ else
+ literals += *spec_ptr;
+ }
+ yylval.str.len = literals.length() - yylval.str.start;
+ return TOKEN_LITERAL;
+ }
+ return c;
+}
+
+int set_label_spec(const char *label_spec)
+{
+ spec_cur = spec_ptr = label_spec;
+ spec_end = strchr(label_spec, '\0');
+ literals.clear();
+ if (yyparse())
+ return 0;
+ delete parsed_label;
+ parsed_label = parse_result;
+ return 1;
+}
+
+int set_date_label_spec(const char *label_spec)
+{
+ spec_cur = spec_ptr = label_spec;
+ spec_end = strchr(label_spec, '\0');
+ literals.clear();
+ if (yyparse())
+ return 0;
+ delete parsed_date_label;
+ parsed_date_label = parse_result;
+ return 1;
+}
+
+int set_short_label_spec(const char *label_spec)
+{
+ spec_cur = spec_ptr = label_spec;
+ spec_end = strchr(label_spec, '\0');
+ literals.clear();
+ if (yyparse())
+ return 0;
+ delete parsed_short_label;
+ parsed_short_label = parse_result;
+ return 1;
+}
+
+void yyerror(const char *message)
+{
+ if (spec_cur < spec_end)
+ command_error("label specification %1 before '%2'", message, spec_cur);
+ else
+ command_error("label specification %1 at end of string",
+ message, spec_cur);
+}
+
+void at_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (tentative)
+ ref.canonicalize_authors(result);
+ else {
+ const char *end, *start = ref.get_authors(&end);
+ if (start)
+ result.append(start, end - start);
+ }
+}
+
+void format_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (tentative)
+ return;
+ const label_info *lp = ref.get_label_ptr();
+ int num = lp == 0 ? ref.get_number() : lp->count;
+ if (type != '0')
+ result += format_serial(type, num + 1);
+ else {
+ const char *ptr = i_to_a(num + first_number);
+ int pad = width - strlen(ptr);
+ while (--pad >= 0)
+ result += '0';
+ result += ptr;
+ }
+}
+
+static const char *format_serial(char c, int n)
+{
+ assert(n > 0);
+ static char buf[128]; // more than enough.
+ switch (c) {
+ case 'i':
+ case 'I':
+ {
+ char *p = buf;
+ // troff uses z and w to represent 10000 and 5000 in Roman
+ // numerals; I can find no historical basis for this usage
+ const char *s = c == 'i' ? "zwmdclxvi" : "ZWMDCLXVI";
+ if (n >= 40000)
+ return i_to_a(n);
+ while (n >= 10000) {
+ *p++ = s[0];
+ n -= 10000;
+ }
+ for (int i = 1000; i > 0; i /= 10, s += 2) {
+ int m = n/i;
+ n -= m*i;
+ switch (m) {
+ case 3:
+ *p++ = s[2];
+ /* falls through */
+ case 2:
+ *p++ = s[2];
+ /* falls through */
+ case 1:
+ *p++ = s[2];
+ break;
+ case 4:
+ *p++ = s[2];
+ *p++ = s[1];
+ break;
+ case 8:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 7:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 6:
+ *p++ = s[1];
+ *p++ = s[2];
+ break;
+ case 5:
+ *p++ = s[1];
+ break;
+ case 9:
+ *p++ = s[2];
+ *p++ = s[0];
+ }
+ }
+ *p = 0;
+ break;
+ }
+ case 'a':
+ case 'A':
+ {
+ char *p = buf;
+ // this is derived from troff/reg.c
+ while (n > 0) {
+ int d = n % 26;
+ if (d == 0)
+ d = 26;
+ n -= d;
+ n /= 26;
+ *p++ = c == 'a' ? lowercase_array[d - 1] :
+ uppercase_array[d - 1];
+ }
+ *p-- = 0;
+ // Reverse it.
+ char *q = buf;
+ while (q < p) {
+ char temp = *q;
+ *q = *p;
+ *p = temp;
+ --p;
+ ++q;
+ }
+ break;
+ }
+ default:
+ assert(0);
+ }
+ return buf;
+}
+
+void field_expr::evaluate(int, const reference &ref,
+ string &result, substring_position &)
+{
+ const char *end;
+ const char *start = ref.get_field(name, &end);
+ if (start) {
+ start = nth_field(number, start, &end);
+ if (start)
+ result.append(start, end - start);
+ }
+}
+
+void literal_expr::evaluate(int, const reference &,
+ string &result, substring_position &)
+{
+ result += s;
+}
+
+analyzed_expr::analyzed_expr(expression *e)
+: unary_expr(e), flags(e ? e->analyze() : 0)
+{
+}
+
+void analyzed_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ if (expr)
+ expr->evaluate(tentative, ref, result, pos);
+}
+
+void star_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ const label_info *lp = ref.get_label_ptr();
+ if (!tentative
+ && (lp == 0 || lp->total > 1)
+ && expr)
+ expr->evaluate(tentative, ref, result, pos);
+}
+
+void separator_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ int start_length = result.length();
+ int is_first = pos.start < 0;
+ if (expr)
+ expr->evaluate(tentative, ref, result, pos);
+ if (is_first) {
+ pos.start = start_length;
+ pos.length = result.length() - start_length;
+ }
+}
+
+void map_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (expr) {
+ string temp;
+ substring_position temp_pos;
+ expr->evaluate(tentative, ref, temp, temp_pos);
+ (*func)(temp.contents(), temp.contents() + temp.length(), result);
+ }
+}
+
+void extractor_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (expr) {
+ string temp;
+ substring_position temp_pos;
+ expr->evaluate(tentative, ref, temp, temp_pos);
+ const char *end, *start = (*func)(temp.contents(),
+ temp.contents() + temp.length(),
+ &end);
+ switch (part) {
+ case BEFORE:
+ if (start)
+ result.append(temp.contents(), start - temp.contents());
+ else
+ result += temp;
+ break;
+ case MATCH:
+ if (start)
+ result.append(start, end - start);
+ break;
+ case AFTER:
+ if (start)
+ result.append(end, temp.contents() + temp.length() - end);
+ break;
+ default:
+ assert(0);
+ }
+ }
+}
+
+static void first_part(int len, const char *ptr, const char *end,
+ string &result)
+{
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ const token_info *ti = lookup_token(token_start, ptr);
+ int counts = ti->sortify_non_empty(token_start, ptr);
+ if (counts && --len < 0)
+ break;
+ if (counts || ti->is_accent())
+ result.append(token_start, ptr - token_start);
+ }
+}
+
+static void last_part(int len, const char *ptr, const char *end,
+ string &result)
+{
+ const char *start = ptr;
+ int count = 0;
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ const token_info *ti = lookup_token(token_start, ptr);
+ if (ti->sortify_non_empty(token_start, ptr))
+ count++;
+ }
+ ptr = start;
+ int skip = count - len;
+ if (skip > 0) {
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ assert(0);
+ const token_info *ti = lookup_token(token_start, ptr);
+ if (ti->sortify_non_empty(token_start, ptr) && --skip < 0) {
+ ptr = token_start;
+ break;
+ }
+ }
+ }
+ first_part(len, ptr, end, result);
+}
+
+void truncate_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &)
+{
+ if (expr) {
+ string temp;
+ substring_position temp_pos;
+ expr->evaluate(tentative, ref, temp, temp_pos);
+ const char *start = temp.contents();
+ const char *end = start + temp.length();
+ if (n > 0)
+ first_part(n, start, end, result);
+ else if (n < 0)
+ last_part(-n, start, end, result);
+ }
+}
+
+void alternative_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ int start_length = result.length();
+ if (expr1)
+ expr1->evaluate(tentative, ref, result, pos);
+ if (result.length() == start_length && expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+}
+
+void list_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ if (expr1)
+ expr1->evaluate(tentative, ref, result, pos);
+ if (expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+}
+
+void substitute_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ int start_length = result.length();
+ if (expr1)
+ expr1->evaluate(tentative, ref, result, pos);
+ if (result.length() > start_length && result[result.length() - 1] == '-') {
+ // ought to see if pos covers the -
+ result.set_length(result.length() - 1);
+ if (expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+ }
+}
+
+void conditional_expr::evaluate(int tentative, const reference &ref,
+ string &result, substring_position &pos)
+{
+ string temp;
+ substring_position temp_pos;
+ if (expr1)
+ expr1->evaluate(tentative, ref, temp, temp_pos);
+ if (temp.length() > 0) {
+ if (expr2)
+ expr2->evaluate(tentative, ref, result, pos);
+ }
+ else {
+ if (expr3)
+ expr3->evaluate(tentative, ref, result, pos);
+ }
+}
+
+void reference::pre_compute_label()
+{
+ if (parsed_label != 0
+ && (parsed_label->analyze() & expression::CONTAINS_VARIABLE)) {
+ label.clear();
+ substring_position temp_pos;
+ parsed_label->evaluate(1, *this, label, temp_pos);
+ label_ptr = lookup_label(label);
+ }
+}
+
+void reference::compute_label()
+{
+ label.clear();
+ if (parsed_label)
+ parsed_label->evaluate(0, *this, label, separator_pos);
+ if (short_label_flag && parsed_short_label)
+ parsed_short_label->evaluate(0, *this, short_label, short_separator_pos);
+ if (date_as_label) {
+ string new_date;
+ if (parsed_date_label) {
+ substring_position temp_pos;
+ parsed_date_label->evaluate(0, *this, new_date, temp_pos);
+ }
+ set_date(new_date);
+ }
+ if (label_ptr)
+ label_ptr->count += 1;
+}
+
+void reference::immediate_compute_label()
+{
+ if (label_ptr)
+ label_ptr->total = 2; // force use of disambiguator
+ compute_label();
+}
+
+int reference::merge_labels(reference **v, int n, label_type type,
+ string &result)
+{
+ if (abbreviate_label_ranges)
+ return merge_labels_by_number(v, n, type, result);
+ else
+ return merge_labels_by_parts(v, n, type, result);
+}
+
+int reference::merge_labels_by_number(reference **v, int n, label_type type,
+ string &result)
+{
+ if (n <= 1)
+ return 0;
+ int num = get_number();
+ // Only merge three or more labels.
+ if (v[0]->get_number() != num + 1
+ || v[1]->get_number() != num + 2)
+ return 0;
+ int i;
+ for (i = 2; i < n; i++)
+ if (v[i]->get_number() != num + i + 1)
+ break;
+ result = get_label(type);
+ result += label_range_indicator;
+ result += v[i - 1]->get_label(type);
+ return i;
+}
+
+const substring_position &reference::get_separator_pos(label_type type) const
+{
+ if (type == SHORT_LABEL && short_label_flag)
+ return short_separator_pos;
+ else
+ return separator_pos;
+}
+
+const string &reference::get_label(label_type type) const
+{
+ if (type == SHORT_LABEL && short_label_flag)
+ return short_label;
+ else
+ return label;
+}
+
+int reference::merge_labels_by_parts(reference **v, int n, label_type type,
+ string &result)
+{
+ if (n <= 0)
+ return 0;
+ const string &lb = get_label(type);
+ const substring_position &sp = get_separator_pos(type);
+ if (sp.start < 0
+ || sp.start != v[0]->get_separator_pos(type).start
+ || memcmp(lb.contents(), v[0]->get_label(type).contents(),
+ sp.start) != 0)
+ return 0;
+ result = lb;
+ int i = 0;
+ do {
+ result += separate_label_second_parts;
+ const substring_position &s = v[i]->get_separator_pos(type);
+ int sep_end_pos = s.start + s.length;
+ result.append(v[i]->get_label(type).contents() + sep_end_pos,
+ v[i]->get_label(type).length() - sep_end_pos);
+ } while (++i < n
+ && sp.start == v[i]->get_separator_pos(type).start
+ && memcmp(lb.contents(), v[i]->get_label(type).contents(),
+ sp.start) == 0);
+ return i;
+}
+
+string label_pool;
+
+label_info::label_info(const string &s)
+: start(label_pool.length()), length(s.length()), count(0), total(1)
+{
+ label_pool += s;
+}
+
+static label_info **label_table = 0;
+static int label_table_size = 0;
+static int label_table_used = 0;
+
+label_info *lookup_label(const string &label)
+{
+ if (label_table == 0) {
+ label_table = new label_info *[17];
+ label_table_size = 17;
+ for (int i = 0; i < 17; i++)
+ label_table[i] = 0;
+ }
+ unsigned h = hash_string(label.contents(), label.length()) % label_table_size;
+ label_info **ptr;
+ for (ptr = label_table + h;
+ *ptr != 0;
+ (ptr == label_table)
+ ? (ptr = label_table + label_table_size - 1)
+ : ptr--)
+ if ((*ptr)->length == label.length()
+ && memcmp(label_pool.contents() + (*ptr)->start, label.contents(),
+ label.length()) == 0) {
+ (*ptr)->total += 1;
+ return *ptr;
+ }
+ label_info *result = *ptr = new label_info(label);
+ if (++label_table_used * 2 > label_table_size) {
+ // Rehash the table.
+ label_info **old_table = label_table;
+ int old_size = label_table_size;
+ label_table_size = next_size(label_table_size);
+ label_table = new label_info *[label_table_size];
+ int i;
+ for (i = 0; i < label_table_size; i++)
+ label_table[i] = 0;
+ for (i = 0; i < old_size; i++)
+ if (old_table[i]) {
+ h = hash_string(label_pool.contents() + old_table[i]->start,
+ old_table[i]->length);
+ label_info **p;
+ for (p = label_table + (h % label_table_size);
+ *p != 0;
+ (p == label_table)
+ ? (p = label_table + label_table_size - 1)
+ : --p)
+ ;
+ *p = old_table[i];
+ }
+ delete[] old_table;
+ }
+ return result;
+}
+
+void clear_labels()
+{
+ for (int i = 0; i < label_table_size; i++) {
+ delete label_table[i];
+ label_table[i] = 0;
+ }
+ label_table_used = 0;
+ label_pool.clear();
+}
+
+static void consider_authors(reference **start, reference **end, int i);
+
+void compute_labels(reference **v, int n)
+{
+ if (parsed_label
+ && (parsed_label->analyze() & expression::CONTAINS_AT)
+ && sort_fields.length() >= 2
+ && sort_fields[0] == 'A'
+ && sort_fields[1] == '+')
+ consider_authors(v, v + n, 0);
+ for (int i = 0; i < n; i++)
+ v[i]->compute_label();
+}
+
+
+/* A reference with a list of authors <A0,A1,...,AN> _needs_ author i
+where 0 <= i <= N if there exists a reference with a list of authors
+<B0,B1,...,BM> such that <A0,A1,...,AN> != <B0,B1,...,BM> and M >= i
+and Aj = Bj for 0 <= j < i. In this case if we can't say "A0,
+A1,...,A(i-1) et al" because this would match both <A0,A1,...,AN> and
+<B0,B1,...,BM>. If a reference needs author i we only have to call
+need_author(j) for some j >= i such that the reference also needs
+author j. */
+
+/* This function handles 2 tasks:
+determine which authors are needed (cannot be elided with et al.);
+determine which authors can have only last names in the labels.
+
+References >= start and < end have the same first i author names.
+Also they're sorted by A+. */
+
+static void consider_authors(reference **start, reference **end, int i)
+{
+ if (start >= end)
+ return;
+ reference **p = start;
+ if (i >= (*p)->get_nauthors()) {
+ for (++p; p < end && i >= (*p)->get_nauthors(); p++)
+ ;
+ if (p < end && i > 0) {
+ // If we have an author list <A B C> and an author list <A B C D>,
+ // then both lists need C.
+ for (reference **q = start; q < end; q++)
+ (*q)->need_author(i - 1);
+ }
+ start = p;
+ }
+ while (p < end) {
+ reference **last_name_start = p;
+ reference **name_start = p;
+ for (++p;
+ p < end && i < (*p)->get_nauthors()
+ && same_author_last_name(**last_name_start, **p, i);
+ p++) {
+ if (!same_author_name(**name_start, **p, i)) {
+ consider_authors(name_start, p, i + 1);
+ name_start = p;
+ }
+ }
+ consider_authors(name_start, p, i + 1);
+ if (last_name_start == name_start) {
+ for (reference **q = last_name_start; q < p; q++)
+ (*q)->set_last_name_unambiguous(i);
+ }
+ // If we have an author list <A B C D> and <A B C E>, then the lists
+ // need author D and E respectively.
+ if (name_start > start || p < end) {
+ for (reference **q = last_name_start; q < p; q++)
+ (*q)->need_author(i);
+ }
+ }
+}
+
+int same_author_last_name(const reference &r1, const reference &r2, int n)
+{
+ const char *ae1;
+ const char *as1 = r1.get_sort_field(0, n, 0, &ae1);
+ const char *ae2;
+ const char *as2 = r2.get_sort_field(0, n, 0, &ae2);
+ if (!as1 && !as2) return 1; // they are the same
+ if (!as1 || !as2) return 0;
+ return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0;
+}
+
+int same_author_name(const reference &r1, const reference &r2, int n)
+{
+ const char *ae1;
+ const char *as1 = r1.get_sort_field(0, n, -1, &ae1);
+ const char *ae2;
+ const char *as2 = r2.get_sort_field(0, n, -1, &ae2);
+ if (!as1 && !as2) return 1; // they are the same
+ if (!as1 || !as2) return 0;
+ return ae1 - as1 == ae2 - as2 && memcmp(as1, as2, ae1 - as1) == 0;
+}
+
+
+void int_set::set(int i)
+{
+ assert(i >= 0);
+ int bytei = i >> 3;
+ if (bytei >= v.length()) {
+ int old_length = v.length();
+ v.set_length(bytei + 1);
+ for (int j = old_length; j <= bytei; j++)
+ v[j] = 0;
+ }
+ v[bytei] |= 1 << (i & 7);
+}
+
+int int_set::get(int i) const
+{
+ assert(i >= 0);
+ int bytei = i >> 3;
+ return bytei >= v.length() ? 0 : (v[bytei] & (1 << (i & 7))) != 0;
+}
+
+void reference::set_last_name_unambiguous(int i)
+{
+ last_name_unambiguous.set(i);
+}
+
+void reference::need_author(int n)
+{
+ if (n > last_needed_author)
+ last_needed_author = n;
+}
+
+const char *reference::get_authors(const char **end) const
+{
+ if (!computed_authors) {
+ ((reference *)this)->computed_authors = 1;
+ string &result = ((reference *)this)->authors;
+ int na = get_nauthors();
+ result.clear();
+ for (int i = 0; i < na; i++) {
+ if (last_name_unambiguous.get(i)) {
+ const char *e, *start = get_author_last_name(i, &e);
+ assert(start != 0);
+ result.append(start, e - start);
+ }
+ else {
+ const char *e, *start = get_author(i, &e);
+ assert(start != 0);
+ result.append(start, e - start);
+ }
+ if (i == last_needed_author
+ && et_al.length() > 0
+ && et_al_min_elide > 0
+ && last_needed_author + et_al_min_elide < na
+ && na >= et_al_min_total) {
+ result += et_al;
+ break;
+ }
+ if (i < na - 1) {
+ if (na == 2)
+ result += join_authors_exactly_two;
+ else if (i < na - 2)
+ result += join_authors_default;
+ else
+ result += join_authors_last_two;
+ }
+ }
+ }
+ const char *start = authors.contents();
+ *end = start + authors.length();
+ return start;
+}
+
+int reference::get_nauthors() const
+{
+ if (nauthors < 0) {
+ const char *dummy;
+ int na;
+ for (na = 0; get_author(na, &dummy) != 0; na++)
+ ;
+ ((reference *)this)->nauthors = na;
+ }
+ return nauthors;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/refer/ref.cpp b/src/preproc/refer/ref.cpp
new file mode 100644
index 0000000..9e1b5e7
--- /dev/null
+++ b/src/preproc/refer/ref.cpp
@@ -0,0 +1,1161 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "refer.h"
+#include "refid.h"
+#include "ref.h"
+#include "token.h"
+
+static const char *find_day(const char *, const char *, const char **);
+static int find_month(const char *start, const char *end);
+static void abbreviate_names(string &);
+
+#define DEFAULT_ARTICLES "the\000a\000an"
+
+string articles(DEFAULT_ARTICLES, sizeof(DEFAULT_ARTICLES));
+
+// Multiple occurrences of fields are separated by FIELD_SEPARATOR.
+const char FIELD_SEPARATOR = '\0';
+
+const char MULTI_FIELD_NAMES[] = "AE";
+const char *AUTHOR_FIELDS = "AQ";
+
+enum { OTHER, JOURNAL_ARTICLE, BOOK, ARTICLE_IN_BOOK, TECH_REPORT, BELL_TM };
+
+const char *reference_types[] = {
+ "other",
+ "journal-article",
+ "book",
+ "article-in-book",
+ "tech-report",
+ "bell-tm",
+};
+
+static string temp_fields[256];
+
+reference::reference(const char *start, int len, reference_id *ridp)
+: h(0), merged(0), no(-1), field(0), nfields(0), label_ptr(0),
+ computed_authors(0), last_needed_author(-1), nauthors(-1)
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ field_index[i] = NULL_FIELD_INDEX;
+ if (ridp)
+ rid = *ridp;
+ if (start == 0)
+ return;
+ if (len <= 0)
+ return;
+ const char *end = start + len;
+ const char *ptr = start;
+ assert(*ptr == '%');
+ while (ptr < end) {
+ if (ptr + 1 < end && ptr[1] != '\0'
+ && ((ptr[1] != '%' && ptr[1] == annotation_field)
+ || (ptr + 2 < end && ptr[1] == '%' && ptr[2] != '\0'
+ && discard_fields.search(ptr[2]) < 0))) {
+ if (ptr[1] == '%')
+ ptr++;
+ string &f = temp_fields[(unsigned char)ptr[1]];
+ ptr += 2;
+ while (ptr < end && csspace(*ptr))
+ ptr++;
+ for (;;) {
+ for (;;) {
+ if (ptr >= end) {
+ f += '\n';
+ break;
+ }
+ f += *ptr;
+ if (*ptr++ == '\n')
+ break;
+ }
+ if (ptr >= end || *ptr == '%')
+ break;
+ }
+ }
+ else if (ptr + 1 < end && ptr[1] != '\0' && ptr[1] != '%'
+ && discard_fields.search(ptr[1]) < 0) {
+ string &f = temp_fields[(unsigned char)ptr[1]];
+ if (f.length() > 0) {
+ if (strchr(MULTI_FIELD_NAMES, ptr[1]) != 0)
+ f += FIELD_SEPARATOR;
+ else
+ f.clear();
+ }
+ ptr += 2;
+ if (ptr < end) {
+ if (*ptr == ' ')
+ ptr++;
+ for (;;) {
+ const char *p = ptr;
+ while (ptr < end && *ptr != '\n')
+ ptr++;
+ // strip trailing white space
+ const char *q = ptr;
+ while (q > p && q[-1] != '\n' && csspace(q[-1]))
+ q--;
+ while (p < q)
+ f += *p++;
+ if (ptr >= end)
+ break;
+ ptr++;
+ if (ptr >= end)
+ break;
+ if (*ptr == '%')
+ break;
+ f += ' ';
+ }
+ }
+ }
+ else {
+ // skip this field
+ for (;;) {
+ while (ptr < end && *ptr++ != '\n')
+ ;
+ if (ptr >= end || *ptr == '%')
+ break;
+ }
+ }
+ }
+ for (i = 0; i < 256; i++)
+ if (temp_fields[i].length() > 0)
+ nfields++;
+ field = new string[nfields];
+ int j = 0;
+ for (i = 0; i < 256; i++)
+ if (temp_fields[i].length() > 0) {
+ field[j].move(temp_fields[i]);
+ if (abbreviate_fields.search(i) >= 0)
+ abbreviate_names(field[j]);
+ field_index[i] = j;
+ j++;
+ }
+}
+
+reference::~reference()
+{
+ if (nfields > 0)
+ delete[] field;
+}
+
+// ref is the inline, this is the database ref
+
+void reference::merge(reference &ref)
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (field_index[i] != NULL_FIELD_INDEX)
+ temp_fields[i].move(field[field_index[i]]);
+ for (i = 0; i < 256; i++)
+ if (ref.field_index[i] != NULL_FIELD_INDEX)
+ temp_fields[i].move(ref.field[ref.field_index[i]]);
+ for (i = 0; i < 256; i++)
+ field_index[i] = NULL_FIELD_INDEX;
+ int old_nfields = nfields;
+ nfields = 0;
+ for (i = 0; i < 256; i++)
+ if (temp_fields[i].length() > 0)
+ nfields++;
+ if (nfields != old_nfields) {
+ if (old_nfields > 0)
+ delete[] field;
+ field = new string[nfields];
+ }
+ int j = 0;
+ for (i = 0; i < 256; i++)
+ if (temp_fields[i].length() > 0) {
+ field[j].move(temp_fields[i]);
+ field_index[i] = j;
+ j++;
+ }
+ merged = 1;
+}
+
+void reference::insert_field(unsigned char c, string &s)
+{
+ assert(s.length() > 0);
+ if (field_index[c] != NULL_FIELD_INDEX) {
+ field[field_index[c]].move(s);
+ return;
+ }
+ assert(field_index[c] == NULL_FIELD_INDEX);
+ string *old_field = field;
+ field = new string[nfields + 1];
+ int pos = 0;
+ int i;
+ for (i = 0; i < int(c); i++)
+ if (field_index[i] != NULL_FIELD_INDEX)
+ pos++;
+ for (i = 0; i < pos; i++)
+ field[i].move(old_field[i]);
+ field[pos].move(s);
+ for (i = pos; i < nfields; i++)
+ field[i + 1].move(old_field[i]);
+ if (nfields > 0)
+ delete[] old_field;
+ nfields++;
+ field_index[c] = pos;
+ for (i = c + 1; i < 256; i++)
+ if (field_index[i] != NULL_FIELD_INDEX)
+ field_index[i] += 1;
+}
+
+void reference::delete_field(unsigned char c)
+{
+ if (field_index[c] == NULL_FIELD_INDEX)
+ return;
+ string *old_field = field;
+ field = new string[nfields - 1];
+ int i;
+ for (i = 0; i < int(field_index[c]); i++)
+ field[i].move(old_field[i]);
+ for (i = field_index[c]; i < nfields - 1; i++)
+ field[i].move(old_field[i + 1]);
+ if (nfields > 0)
+ delete[] old_field;
+ nfields--;
+ field_index[c] = NULL_FIELD_INDEX;
+ for (i = c + 1; i < 256; i++)
+ if (field_index[i] != NULL_FIELD_INDEX)
+ field_index[i] -= 1;
+}
+
+void reference::compute_hash_code()
+{
+ if (!rid.is_null())
+ h = rid.hash();
+ else {
+ h = 0;
+ for (int i = 0; i < nfields; i++)
+ if (field[i].length() > 0) {
+ h <<= 4;
+ h ^= hash_string(field[i].contents(), field[i].length());
+ }
+ }
+}
+
+void reference::set_number(int n)
+{
+ no = n;
+}
+
+const char SORT_SEP = '\001';
+const char SORT_SUB_SEP = '\002';
+const char SORT_SUB_SUB_SEP = '\003';
+
+// sep specifies additional word separators
+
+void sortify_words(const char *s, const char *end, const char *sep,
+ string &result)
+{
+ int non_empty = 0;
+ int need_separator = 0;
+ for (;;) {
+ const char *token_start = s;
+ if (!get_token(&s, end))
+ break;
+ if ((s - token_start == 1
+ && (*token_start == ' '
+ || *token_start == '\n'
+ || (sep && *token_start != '\0'
+ && strchr(sep, *token_start) != 0)))
+ || (s - token_start == 2
+ && token_start[0] == '\\' && token_start[1] == ' ')) {
+ if (non_empty)
+ need_separator = 1;
+ }
+ else {
+ const token_info *ti = lookup_token(token_start, s);
+ if (ti->sortify_non_empty(token_start, s)) {
+ if (need_separator) {
+ result += ' ';
+ need_separator = 0;
+ }
+ ti->sortify(token_start, s, result);
+ non_empty = 1;
+ }
+ }
+ }
+}
+
+void sortify_word(const char *s, const char *end, string &result)
+{
+ for (;;) {
+ const char *token_start = s;
+ if (!get_token(&s, end))
+ break;
+ const token_info *ti = lookup_token(token_start, s);
+ ti->sortify(token_start, s, result);
+ }
+}
+
+void sortify_other(const char *s, int len, string &key)
+{
+ sortify_words(s, s + len, 0, key);
+}
+
+void sortify_title(const char *s, int len, string &key)
+{
+ const char *end = s + len;
+ for (; s < end && (*s == ' ' || *s == '\n'); s++)
+ ;
+ const char *ptr = s;
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ if (ptr - token_start == 1
+ && (*token_start == ' ' || *token_start == '\n'))
+ break;
+ }
+ if (ptr < end) {
+ unsigned int first_word_len = ptr - s - 1;
+ const char *ae = articles.contents() + articles.length();
+ for (const char *a = articles.contents();
+ a < ae;
+ a = strchr(a, '\0') + 1)
+ if (first_word_len == strlen(a)) {
+ unsigned int j;
+ for (j = 0; j < first_word_len; j++)
+ if (a[j] != cmlower(s[j]))
+ break;
+ if (j >= first_word_len) {
+ s = ptr;
+ for (; s < end && (*s == ' ' || *s == '\n'); s++)
+ ;
+ break;
+ }
+ }
+ }
+ sortify_words(s, end, 0, key);
+}
+
+void sortify_name(const char *s, int len, string &key)
+{
+ const char *last_name_end;
+ const char *last_name = find_last_name(s, s + len, &last_name_end);
+ sortify_word(last_name, last_name_end, key);
+ key += SORT_SUB_SUB_SEP;
+ if (last_name > s)
+ sortify_words(s, last_name, ".", key);
+ key += SORT_SUB_SUB_SEP;
+ if (last_name_end < s + len)
+ sortify_words(last_name_end, s + len, ".,", key);
+}
+
+void sortify_date(const char *s, int len, string &key)
+{
+ const char *year_end;
+ const char *year_start = find_year(s, s + len, &year_end);
+ if (!year_start) {
+ // Things without years are often 'forthcoming', so it makes sense
+ // that they sort after things with explicit years.
+ key += 'A';
+ sortify_words(s, s + len, 0, key);
+ return;
+ }
+ int n = year_end - year_start;
+ while (n < 4) {
+ key += '0';
+ n++;
+ }
+ while (year_start < year_end)
+ key += *year_start++;
+ int m = find_month(s, s + len);
+ if (m < 0)
+ return;
+ key += 'A' + m;
+ const char *day_end;
+ const char *day_start = find_day(s, s + len, &day_end);
+ if (!day_start)
+ return;
+ if (day_end - day_start == 1)
+ key += '0';
+ while (day_start < day_end)
+ key += *day_start++;
+}
+
+// SORT_{SUB,SUB_SUB}_SEP can creep in from use of @ in label specification.
+
+void sortify_label(const char *s, int len, string &key)
+{
+ const char *end = s + len;
+ for (;;) {
+ const char *ptr;
+ for (ptr = s;
+ ptr < end && *ptr != SORT_SUB_SEP && *ptr != SORT_SUB_SUB_SEP;
+ ptr++)
+ ;
+ if (ptr > s)
+ sortify_words(s, ptr, 0, key);
+ s = ptr;
+ if (s >= end)
+ break;
+ key += *s++;
+ }
+}
+
+void reference::compute_sort_key()
+{
+ if (sort_fields.length() == 0)
+ return;
+ sort_fields += '\0';
+ const char *sf = sort_fields.contents();
+ int first_time = 1;
+ while (*sf != '\0') {
+ if (!first_time)
+ sort_key += SORT_SEP;
+ first_time = 0;
+ char f = *sf++;
+ int n = 1;
+ if (*sf == '+') {
+ n = INT_MAX;
+ sf++;
+ }
+ else if (csdigit(*sf)) {
+ char *ptr;
+ long l = strtol(sf, &ptr, 10);
+ if (l == 0 && ptr == sf)
+ ;
+ else {
+ sf = ptr;
+ if (l < 0) {
+ n = 1;
+ }
+ else {
+ n = int(l);
+ }
+ }
+ }
+ if (f == '.')
+ sortify_label(label.contents(), label.length(), sort_key);
+ else if (f == AUTHOR_FIELDS[0])
+ sortify_authors(n, sort_key);
+ else
+ sortify_field(f, n, sort_key);
+ }
+ sort_fields.set_length(sort_fields.length() - 1);
+}
+
+void reference::sortify_authors(int n, string &result) const
+{
+ for (const char *p = AUTHOR_FIELDS; *p != '\0'; p++)
+ if (contains_field(*p)) {
+ sortify_field(*p, n, result);
+ return;
+ }
+ sortify_field(AUTHOR_FIELDS[0], n, result);
+}
+
+void reference::canonicalize_authors(string &result) const
+{
+ int len = result.length();
+ sortify_authors(INT_MAX, result);
+ if (result.length() > len)
+ result += SORT_SUB_SEP;
+}
+
+void reference::sortify_field(unsigned char f, int n, string &result) const
+{
+ typedef void (*sortify_t)(const char *, int, string &);
+ sortify_t sortifier = sortify_other;
+ switch (f) {
+ case 'A':
+ case 'E':
+ sortifier = sortify_name;
+ break;
+ case 'D':
+ sortifier = sortify_date;
+ break;
+ case 'B':
+ case 'J':
+ case 'T':
+ sortifier = sortify_title;
+ break;
+ }
+ int fi = field_index[(unsigned char)f];
+ if (fi != NULL_FIELD_INDEX) {
+ string &str = field[fi];
+ const char *start = str.contents();
+ const char *end = start + str.length();
+ for (int i = 0; i < n && start < end; i++) {
+ const char *p = start;
+ while (start < end && *start != FIELD_SEPARATOR)
+ start++;
+ if (i > 0)
+ result += SORT_SUB_SEP;
+ (*sortifier)(p, start - p, result);
+ if (start < end)
+ start++;
+ }
+ }
+}
+
+int compare_reference(const reference &r1, const reference &r2)
+{
+ assert(r1.no >= 0);
+ assert(r2.no >= 0);
+ const char *s1 = r1.sort_key.contents();
+ int n1 = r1.sort_key.length();
+ const char *s2 = r2.sort_key.contents();
+ int n2 = r2.sort_key.length();
+ for (; n1 > 0 && n2 > 0; --n1, --n2, ++s1, ++s2)
+ if (*s1 != *s2)
+ return (int)(unsigned char)*s1 - (int)(unsigned char)*s2;
+ if (n2 > 0)
+ return -1;
+ if (n1 > 0)
+ return 1;
+ return r1.no - r2.no;
+}
+
+int same_reference(const reference &r1, const reference &r2)
+{
+ if (!r1.rid.is_null() && r1.rid == r2.rid)
+ return 1;
+ if (r1.h != r2.h)
+ return 0;
+ if (r1.nfields != r2.nfields)
+ return 0;
+ int i = 0;
+ for (i = 0; i < 256; i++)
+ if (r1.field_index != r2.field_index)
+ return 0;
+ for (i = 0; i < r1.nfields; i++)
+ if (r1.field[i] != r2.field[i])
+ return 0;
+ return 1;
+}
+
+const char *find_last_name(const char *start, const char *end,
+ const char **endp)
+{
+ const char *ptr = start;
+ const char *last_word = start;
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ if (ptr - token_start == 1) {
+ if (*token_start == ',') {
+ *endp = token_start;
+ return last_word;
+ }
+ else if (*token_start == ' ' || *token_start == '\n') {
+ if (ptr < end && *ptr != ' ' && *ptr != '\n')
+ last_word = ptr;
+ }
+ }
+ }
+ *endp = end;
+ return last_word;
+}
+
+void abbreviate_name(const char *ptr, const char *end, string &result)
+{
+ const char *last_name_end;
+ const char *last_name_start = find_last_name(ptr, end, &last_name_end);
+ int need_period = 0;
+ for (;;) {
+ const char *token_start = ptr;
+ if (!get_token(&ptr, last_name_start))
+ break;
+ const token_info *ti = lookup_token(token_start, ptr);
+ if (need_period) {
+ if ((ptr - token_start == 1 && *token_start == ' ')
+ || (ptr - token_start == 2 && token_start[0] == '\\'
+ && token_start[1] == ' '))
+ continue;
+ if (ti->is_upper())
+ result += period_before_initial;
+ else
+ result += period_before_other;
+ need_period = 0;
+ }
+ result.append(token_start, ptr - token_start);
+ if (ti->is_upper()) {
+ const char *lower_ptr = ptr;
+ int first_token = 1;
+ for (;;) {
+ token_start = ptr;
+ if (!get_token(&ptr, last_name_start))
+ break;
+ if ((ptr - token_start == 1 && *token_start == ' ')
+ || (ptr - token_start == 2 && token_start[0] == '\\'
+ && token_start[1] == ' '))
+ break;
+ ti = lookup_token(token_start, ptr);
+ if (ti->is_hyphen()) {
+ const char *ptr1 = ptr;
+ if (get_token(&ptr1, last_name_start)) {
+ ti = lookup_token(ptr, ptr1);
+ if (ti->is_upper()) {
+ result += period_before_hyphen;
+ result.append(token_start, ptr1 - token_start);
+ ptr = ptr1;
+ }
+ }
+ }
+ else if (ti->is_upper()) {
+ // MacDougal -> MacD.
+ result.append(lower_ptr, ptr - lower_ptr);
+ lower_ptr = ptr;
+ first_token = 1;
+ }
+ else if (first_token && ti->is_accent()) {
+ result.append(token_start, ptr - token_start);
+ lower_ptr = ptr;
+ }
+ first_token = 0;
+ }
+ need_period = 1;
+ }
+ }
+ if (need_period)
+ result += period_before_last_name;
+ result.append(last_name_start, end - last_name_start);
+}
+
+static void abbreviate_names(string &result)
+{
+ string str;
+ str.move(result);
+ const char *ptr = str.contents();
+ const char *end = ptr + str.length();
+ while (ptr < end) {
+ const char *name_end = (char *)memchr(ptr, FIELD_SEPARATOR, end - ptr);
+ if (name_end == 0)
+ name_end = end;
+ abbreviate_name(ptr, name_end, result);
+ if (name_end >= end)
+ break;
+ ptr = name_end + 1;
+ result += FIELD_SEPARATOR;
+ }
+}
+
+void reverse_name(const char *ptr, const char *name_end, string &result)
+{
+ const char *last_name_end;
+ const char *last_name_start = find_last_name(ptr, name_end, &last_name_end);
+ result.append(last_name_start, last_name_end - last_name_start);
+ while (last_name_start > ptr
+ && (last_name_start[-1] == ' ' || last_name_start[-1] == '\n'))
+ last_name_start--;
+ if (last_name_start > ptr) {
+ result += ", ";
+ result.append(ptr, last_name_start - ptr);
+ }
+ if (last_name_end < name_end)
+ result.append(last_name_end, name_end - last_name_end);
+}
+
+void reverse_names(string &result, int n)
+{
+ if (n <= 0)
+ return;
+ string str;
+ str.move(result);
+ const char *ptr = str.contents();
+ const char *end = ptr + str.length();
+ while (ptr < end) {
+ if (--n < 0) {
+ result.append(ptr, end - ptr);
+ break;
+ }
+ const char *name_end = (char *)memchr(ptr, FIELD_SEPARATOR, end - ptr);
+ if (name_end == 0)
+ name_end = end;
+ reverse_name(ptr, name_end, result);
+ if (name_end >= end)
+ break;
+ ptr = name_end + 1;
+ result += FIELD_SEPARATOR;
+ }
+}
+
+// Return number of field separators.
+
+int join_fields(string &f)
+{
+ const char *ptr = f.contents();
+ int len = f.length();
+ int nfield_seps = 0;
+ int j;
+ for (j = 0; j < len; j++)
+ if (ptr[j] == FIELD_SEPARATOR)
+ nfield_seps++;
+ if (nfield_seps == 0)
+ return 0;
+ string temp;
+ int field_seps_left = nfield_seps;
+ for (j = 0; j < len; j++) {
+ if (ptr[j] == FIELD_SEPARATOR) {
+ if (nfield_seps == 1)
+ temp += join_authors_exactly_two;
+ else if (--field_seps_left == 0)
+ temp += join_authors_last_two;
+ else
+ temp += join_authors_default;
+ }
+ else
+ temp += ptr[j];
+ }
+ f = temp;
+ return nfield_seps;
+}
+
+void uppercase(const char *start, const char *end, string &result)
+{
+ for (;;) {
+ const char *token_start = start;
+ if (!get_token(&start, end))
+ break;
+ const token_info *ti = lookup_token(token_start, start);
+ ti->upper_case(token_start, start, result);
+ }
+}
+
+void lowercase(const char *start, const char *end, string &result)
+{
+ for (;;) {
+ const char *token_start = start;
+ if (!get_token(&start, end))
+ break;
+ const token_info *ti = lookup_token(token_start, start);
+ ti->lower_case(token_start, start, result);
+ }
+}
+
+void capitalize(const char *ptr, const char *end, string &result)
+{
+ int in_small_point_size = 0;
+ for (;;) {
+ const char *start = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ const token_info *ti = lookup_token(start, ptr);
+ const char *char_end = ptr;
+ int is_lower = ti->is_lower();
+ if ((is_lower || ti->is_upper()) && get_token(&ptr, end)) {
+ const token_info *ti2 = lookup_token(char_end, ptr);
+ if (!ti2->is_accent())
+ ptr = char_end;
+ }
+ if (is_lower) {
+ if (!in_small_point_size) {
+ result += "\\s-2";
+ in_small_point_size = 1;
+ }
+ ti->upper_case(start, char_end, result);
+ result.append(char_end, ptr - char_end);
+ }
+ else {
+ if (in_small_point_size) {
+ result += "\\s+2";
+ in_small_point_size = 0;
+ }
+ result.append(start, ptr - start);
+ }
+ }
+ if (in_small_point_size)
+ result += "\\s+2";
+}
+
+void capitalize_field(string &str)
+{
+ string temp;
+ capitalize(str.contents(), str.contents() + str.length(), temp);
+ str.move(temp);
+}
+
+int is_terminated(const char *ptr, const char *end)
+{
+ const char *last_token = end;
+ for (;;) {
+ const char *p = ptr;
+ if (!get_token(&ptr, end))
+ break;
+ last_token = p;
+ }
+ return end - last_token == 1
+ && (*last_token == '.' || *last_token == '!' || *last_token == '?');
+}
+
+void reference::output(FILE *fp)
+{
+ fputs(".]-\n", fp);
+ for (int i = 0; i < 256; i++)
+ if (field_index[i] != NULL_FIELD_INDEX && i != annotation_field) {
+ string &f = field[field_index[i]];
+ if (!csdigit(i)) {
+ int j = reverse_fields.search(i);
+ if (j >= 0) {
+ int n;
+ int len = reverse_fields.length();
+ if (++j < len && csdigit(reverse_fields[j])) {
+ n = reverse_fields[j] - '0';
+ for (++j; j < len && csdigit(reverse_fields[j]); j++)
+ // should check for overflow
+ n = n*10 + reverse_fields[j] - '0';
+ }
+ else
+ n = INT_MAX;
+ reverse_names(f, n);
+ }
+ }
+ int is_multiple = join_fields(f) > 0;
+ if (capitalize_fields.search(i) >= 0)
+ capitalize_field(f);
+ if (memchr(f.contents(), '\n', f.length()) == 0) {
+ fprintf(fp, ".ds [%c ", i);
+ if (f[0] == ' ' || f[0] == '\\' || f[0] == '"')
+ putc('"', fp);
+ put_string(f, fp);
+ putc('\n', fp);
+ }
+ else {
+ fprintf(fp, ".de [%c\n", i);
+ put_string(f, fp);
+ fputs("..\n", fp);
+ }
+ if (i == 'P') {
+ int multiple_pages = 0;
+ const char *s = f.contents();
+ const char *end = f.contents() + f.length();
+ for (;;) {
+ const char *token_start = s;
+ if (!get_token(&s, end))
+ break;
+ const token_info *ti = lookup_token(token_start, s);
+ if (ti->is_hyphen() || ti->is_range_sep()) {
+ multiple_pages = 1;
+ break;
+ }
+ }
+ fprintf(fp, ".nr [P %d\n", multiple_pages);
+ }
+ else if (i == 'E')
+ fprintf(fp, ".nr [E %d\n", is_multiple);
+ }
+ for (const char *p = "TAO"; *p; p++) {
+ int fi = field_index[(unsigned char)*p];
+ if (fi != NULL_FIELD_INDEX) {
+ string &f = field[fi];
+ fprintf(fp, ".nr [%c %d\n", *p,
+ is_terminated(f.contents(), f.contents() + f.length()));
+ }
+ }
+ int t = classify();
+ fprintf(fp, ".][ %d %s\n", t, reference_types[t]);
+ if (annotation_macro.length() > 0 && annotation_field >= 0
+ && field_index[annotation_field] != NULL_FIELD_INDEX) {
+ putc('.', fp);
+ put_string(annotation_macro, fp);
+ putc('\n', fp);
+ put_string(field[field_index[annotation_field]], fp);
+ }
+}
+
+void reference::print_sort_key_comment(FILE *fp)
+{
+ fputs(".\\\"", fp);
+ put_string(sort_key, fp);
+ putc('\n', fp);
+}
+
+const char *find_year(const char *start, const char *end, const char **endp)
+{
+ for (;;) {
+ while (start < end && !csdigit(*start))
+ start++;
+ const char *ptr = start;
+ if (start == end)
+ break;
+ while (ptr < end && csdigit(*ptr))
+ ptr++;
+ if (ptr - start == 4 || ptr - start == 3
+ || (ptr - start == 2
+ && (start[0] >= '4' || (start[0] == '3' && start[1] >= '2')))) {
+ *endp = ptr;
+ return start;
+ }
+ start = ptr;
+ }
+ return 0;
+}
+
+static const char *find_day(const char *start, const char *end,
+ const char **endp)
+{
+ for (;;) {
+ while (start < end && !csdigit(*start))
+ start++;
+ const char *ptr = start;
+ if (start == end)
+ break;
+ while (ptr < end && csdigit(*ptr))
+ ptr++;
+ if ((ptr - start == 1 && start[0] != '0')
+ || (ptr - start == 2 &&
+ (start[0] == '1'
+ || start[0] == '2'
+ || (start[0] == '3' && start[1] <= '1')
+ || (start[0] == '0' && start[1] != '0')))) {
+ *endp = ptr;
+ return start;
+ }
+ start = ptr;
+ }
+ return 0;
+}
+
+static int find_month(const char *start, const char *end)
+{
+ static const char *months[] = {
+ "january",
+ "february",
+ "march",
+ "april",
+ "may",
+ "june",
+ "july",
+ "august",
+ "september",
+ "october",
+ "november",
+ "december",
+ };
+ for (;;) {
+ while (start < end && !csalpha(*start))
+ start++;
+ const char *ptr = start;
+ if (start == end)
+ break;
+ while (ptr < end && csalpha(*ptr))
+ ptr++;
+ if (ptr - start >= 3) {
+ for (unsigned int i = 0; i < sizeof(months)/sizeof(months[0]); i++) {
+ const char *q = months[i];
+ const char *p = start;
+ for (; p < ptr; p++, q++)
+ if (cmlower(*p) != *q)
+ break;
+ if (p >= ptr)
+ return i;
+ }
+ }
+ start = ptr;
+ }
+ return -1;
+}
+
+int reference::contains_field(char c) const
+{
+ return field_index[(unsigned char)c] != NULL_FIELD_INDEX;
+}
+
+int reference::classify()
+{
+ if (contains_field('J'))
+ return JOURNAL_ARTICLE;
+ if (contains_field('B'))
+ return ARTICLE_IN_BOOK;
+ if (contains_field('G'))
+ return TECH_REPORT;
+ if (contains_field('R'))
+ return TECH_REPORT;
+ if (contains_field('I'))
+ return BOOK;
+ if (contains_field('M'))
+ return BELL_TM;
+ return OTHER;
+}
+
+const char *reference::get_year(const char **endp) const
+{
+ if (field_index['D'] != NULL_FIELD_INDEX) {
+ string &date = field[field_index['D']];
+ const char *start = date.contents();
+ const char *end = start + date.length();
+ return find_year(start, end, endp);
+ }
+ else
+ return 0;
+}
+
+const char *reference::get_field(unsigned char c, const char **endp) const
+{
+ if (field_index[c] != NULL_FIELD_INDEX) {
+ string &f = field[field_index[c]];
+ const char *start = f.contents();
+ *endp = start + f.length();
+ return start;
+ }
+ else
+ return 0;
+}
+
+const char *reference::get_date(const char **endp) const
+{
+ return get_field('D', endp);
+}
+
+const char *nth_field(int i, const char *start, const char **endp)
+{
+ while (--i >= 0) {
+ start = (char *)memchr(start, FIELD_SEPARATOR, *endp - start);
+ if (!start)
+ return 0;
+ start++;
+ }
+ const char *e = (char *)memchr(start, FIELD_SEPARATOR, *endp - start);
+ if (e)
+ *endp = e;
+ return start;
+}
+
+const char *reference::get_author(int i, const char **endp) const
+{
+ for (const char *f = AUTHOR_FIELDS; *f != '\0'; f++) {
+ const char *start = get_field(*f, endp);
+ if (start) {
+ if (strchr(MULTI_FIELD_NAMES, *f) != 0)
+ return nth_field(i, start, endp);
+ else if (i == 0)
+ return start;
+ else
+ return 0;
+ }
+ }
+ return 0;
+}
+
+const char *reference::get_author_last_name(int i, const char **endp) const
+{
+ for (const char *f = AUTHOR_FIELDS; *f != '\0'; f++) {
+ const char *start = get_field(*f, endp);
+ if (start) {
+ if (strchr(MULTI_FIELD_NAMES, *f) != 0) {
+ start = nth_field(i, start, endp);
+ if (!start)
+ return 0;
+ }
+ if (*f == 'A')
+ return find_last_name(start, *endp, endp);
+ else
+ return start;
+ }
+ }
+ return 0;
+}
+
+void reference::set_date(string &d)
+{
+ if (d.length() == 0)
+ delete_field('D');
+ else
+ insert_field('D', d);
+}
+
+int same_year(const reference &r1, const reference &r2)
+{
+ const char *ye1;
+ const char *ys1 = r1.get_year(&ye1);
+ const char *ye2;
+ const char *ys2 = r2.get_year(&ye2);
+ if (ys1 == 0) {
+ if (ys2 == 0)
+ return same_date(r1, r2);
+ else
+ return 0;
+ }
+ else if (ys2 == 0)
+ return 0;
+ else if (ye1 - ys1 != ye2 - ys2)
+ return 0;
+ else
+ return memcmp(ys1, ys2, ye1 - ys1) == 0;
+}
+
+int same_date(const reference &r1, const reference &r2)
+{
+ const char *e1;
+ const char *s1 = r1.get_date(&e1);
+ const char *e2;
+ const char *s2 = r2.get_date(&e2);
+ if (s1 == 0)
+ return s2 == 0;
+ else if (s2 == 0)
+ return 0;
+ else if (e1 - s1 != e2 - s2)
+ return 0;
+ else
+ return memcmp(s1, s2, e1 - s1) == 0;
+}
+
+const char *reference::get_sort_field(int i, int si, int ssi,
+ const char **endp) const
+{
+ const char *start = sort_key.contents();
+ const char *end = start + sort_key.length();
+ if (i < 0) {
+ *endp = end;
+ return start;
+ }
+ while (--i >= 0) {
+ start = (char *)memchr(start, SORT_SEP, end - start);
+ if (!start)
+ return 0;
+ start++;
+ }
+ const char *e = (char *)memchr(start, SORT_SEP, end - start);
+ if (e)
+ end = e;
+ if (si < 0) {
+ *endp = end;
+ return start;
+ }
+ while (--si >= 0) {
+ start = (char *)memchr(start, SORT_SUB_SEP, end - start);
+ if (!start)
+ return 0;
+ start++;
+ }
+ e = (char *)memchr(start, SORT_SUB_SEP, end - start);
+ if (e)
+ end = e;
+ if (ssi < 0) {
+ *endp = end;
+ return start;
+ }
+ while (--ssi >= 0) {
+ start = (char *)memchr(start, SORT_SUB_SUB_SEP, end - start);
+ if (!start)
+ return 0;
+ start++;
+ }
+ e = (char *)memchr(start, SORT_SUB_SUB_SEP, end - start);
+ if (e)
+ end = e;
+ *endp = end;
+ return start;
+}
+
diff --git a/src/preproc/refer/ref.h b/src/preproc/refer/ref.h
new file mode 100644
index 0000000..1205a28
--- /dev/null
+++ b/src/preproc/refer/ref.h
@@ -0,0 +1,127 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// declarations to avoid friend name injection problems
+int compare_reference(const reference &, const reference &);
+int same_reference(const reference &, const reference &);
+int same_year(const reference &, const reference &);
+int same_date(const reference &, const reference &);
+int same_author_last_name(const reference &, const reference &, int);
+int same_author_name(const reference &, const reference &, int);
+
+struct label_info;
+
+enum label_type { NORMAL_LABEL, SHORT_LABEL };
+const int N_LABEL_TYPES = 2;
+
+struct substring_position {
+ int start;
+ int length;
+ substring_position() : start(-1) { }
+};
+
+class int_set {
+ string v;
+public:
+ int_set() { }
+ void set(int i);
+ int get(int i) const;
+};
+
+class reference {
+private:
+ unsigned h;
+ reference_id rid;
+ int merged;
+ string sort_key;
+ int no;
+ string *field;
+ int nfields;
+ unsigned char field_index[256];
+ enum { NULL_FIELD_INDEX = 255 };
+ string label;
+ substring_position separator_pos;
+ string short_label;
+ substring_position short_separator_pos;
+ label_info *label_ptr;
+ string authors;
+ int computed_authors;
+ int last_needed_author;
+ int nauthors;
+ int_set last_name_unambiguous;
+
+ int contains_field(char) const;
+ void insert_field(unsigned char, string &s);
+ void delete_field(unsigned char);
+ void set_date(string &);
+ const char *get_sort_field(int i, int si, int ssi, const char **endp) const;
+ int merge_labels_by_parts(reference **, int, label_type, string &);
+ int merge_labels_by_number(reference **, int, label_type, string &);
+public:
+ reference(const char * = 0, int = -1, reference_id * = 0);
+ ~reference();
+ void output(FILE *);
+ void print_sort_key_comment(FILE *);
+ void set_number(int);
+ int get_number() const { return no; }
+ unsigned hash() const { return h; }
+ const string &get_label(label_type type) const;
+ const substring_position &get_separator_pos(label_type) const;
+ int is_merged() const { return merged; }
+ void compute_sort_key();
+ void compute_hash_code();
+ void pre_compute_label();
+ void compute_label();
+ void immediate_compute_label();
+ int classify();
+ void merge(reference &);
+ int merge_labels(reference **, int, label_type, string &);
+ int get_nauthors() const;
+ void need_author(int);
+ void set_last_name_unambiguous(int);
+ void sortify_authors(int, string &) const;
+ void canonicalize_authors(string &) const;
+ void sortify_field(unsigned char, int, string &) const;
+ const char *get_author(int, const char **) const;
+ const char *get_author_last_name(int, const char **) const;
+ const char *get_date(const char **) const;
+ const char *get_year(const char **) const;
+ const char *get_field(unsigned char, const char **) const;
+ const label_info *get_label_ptr() const { return label_ptr; }
+ const char *get_authors(const char **) const;
+ // for sorting
+ friend int compare_reference(const reference &r1, const reference &r2);
+ // for merging
+ friend int same_reference(const reference &, const reference &);
+ friend int same_year(const reference &, const reference &);
+ friend int same_date(const reference &, const reference &);
+ friend int same_author_last_name(const reference &, const reference &, int);
+ friend int same_author_name(const reference &, const reference &, int);
+};
+
+const char *find_year(const char *, const char *, const char **);
+const char *find_last_name(const char *, const char *, const char **);
+
+const char *nth_field(int i, const char *start, const char **endp);
+
+void capitalize(const char *ptr, const char *end, string &result);
+void reverse_name(const char *ptr, const char *end, string &result);
+void uppercase(const char *ptr, const char *end, string &result);
+void lowercase(const char *ptr, const char *end, string &result);
+void abbreviate_name(const char *ptr, const char *end, string &result);
diff --git a/src/preproc/refer/refer.1.man b/src/preproc/refer/refer.1.man
new file mode 100644
index 0000000..210afe7
--- /dev/null
+++ b/src/preproc/refer/refer.1.man
@@ -0,0 +1,2020 @@
+.TH @g@refer @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@refer \- process bibliographic references for
+.I groff
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_refer_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@refer
+.RB [ \-bCenPRS ]
+.RB [ \-a\~\c
+.IR n ]
+.RB [ \-B
+.IB field . macro\c
+]
+.RB [ \-c\~\c
+.IR fields ]
+.RB [ \-f\~\c
+.IR n ]
+.RB [ \-i\~\c
+.IR fields ]
+.RB [ \-k\~\c
+.IR field ]
+.RB [ \-l\~\c
+.IR range-expression ]
+.RB [ \-p\~\c
+.IR database-file ]
+.RB [ \-s\~\c
+.IR fields ]
+.RB [ \-t\~\c
+.IR n ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@refer
+.B \-\-help
+.YS
+.
+.
+.SY @g@refer
+.B \-v
+.
+.SY @g@refer
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU implementation of
+.I \%refer \" generic
+is part of the
+.MR groff @MAN1EXT@
+document formatting system.
+.
+.I @g@refer
+is a
+.MR @g@troff @MAN1EXT@
+preprocessor that prepares bibilographic citations by looking up
+keywords specified in a
+.MR roff @MAN7EXT@
+input document,
+obviating the need to type such annotations,
+and permitting the citation style in formatted output to be altered
+independently and systematically.
+.
+It copies the contents of each
+.I file
+to the standard output stream,
+except that it interprets lines between
+.B .[
+and
+.B .]\&
+as citations to be translated into
+.I groff
+input,
+and lines between
+.B .R1
+and
+.B .R2
+as instructions regarding how citations are to be processed.
+.
+Normally,
+.I @g@refer
+is not executed directly by the user,
+but invoked by specifying the
+.B \-R
+option to
+.MR groff @MAN1EXT@ .
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+the standard input stream is read.
+.
+.
+.LP
+Each citation specifies a reference.
+.
+The citation can specify a reference that is contained in a
+bibliographic database by giving a set of keywords that only that
+reference contains.
+.
+Alternatively it can specify a reference by supplying a database record
+in the citation.
+.
+A combination of these alternatives is also possible.
+.
+.
+.LP
+For each citation,
+.I @g@refer
+can produce a mark in the text.
+.
+This mark consists of some label which can be separated from the text
+and from other labels in various ways.
+.
+For each reference it also outputs
+.MR groff @MAN7EXT@
+language commands that can be used by a macro package to produce a
+formatted reference for each citation.
+.
+The output of
+.I @g@refer
+must therefore be processed using a suitable macro package,
+such as
+.\" .IR man ,
+.IR me ,
+.IR mm ,
+.IR mom ,
+or
+.IR ms .
+.
+The commands to format a citation's reference can be output immediately
+after the citation,
+or the references may be accumulated,
+and the commands output at some later point.
+.
+If the references are accumulated,
+then multiple citations of the same reference will produce a single
+formatted reference.
+.
+.
+.LP
+The interpretation of lines between
+.B .R1
+and
+.B .R2
+as prepreocessor commands is a feature of GNU
+.IR \%refer . \" GNU
+.
+Documents making use of this feature can still be processed by AT&T
+.I \%refer \" AT&T
+just by adding the lines
+.
+.RS
+.EX
+\&.de R1
+\&.ig R2
+\&..
+.EE
+.RE
+.
+to the beginning of the document.
+.
+This will cause
+.MR @g@troff @MAN1EXT@
+to ignore everything between
+.B .R1
+and
+.BR .R2 .
+.
+The effect of some commands can also be achieved by options.
+.
+These options are supported mainly for compatibility with AT&T
+.IR \%refer . \" AT&T
+.
+It is usually more convenient to use commands.
+.
+.
+.LP
+.I @g@refer
+generates
+.B .lf
+requests so that file names and line numbers in messages produced by
+commands that read
+.I @g@refer
+output will be correct;
+it also interprets lines beginning with
+.B .lf
+so that file names and line numbers in the messages and
+.B .lf
+lines that it produces will be accurate even if the input has been
+preprocessed by a command such as
+.MR @g@soelim @MAN1EXT@ .
+.
+.
+.\" ====================================================================
+.SS "Bibliographic databases"
+.\" ====================================================================
+.
+The bibliographic database is a text file consisting of records
+separated by one or more blank lines.
+.
+Within each record fields start with a
+.B %
+at the beginning of a line.
+.
+Each field has a one character name that immediately follows the
+.BR % .
+It is best to use only upper and lower case letters for the names
+of fields.
+.
+The name of the field should be followed by exactly one space,
+and then by the contents of the field.
+.
+Empty fields are ignored.
+.
+The conventional meaning of each field is as follows:
+.
+.
+.TP
+.B %A
+The name of an author.
+.
+If the name contains a suffix such as \[lq]Jr.\&\[rq],
+it should be separated from the last name by a comma.
+.
+There can be multiple occurrences of the
+.B %A
+field.
+.
+The order is significant.
+.
+It is a good idea always to supply an
+.B %A
+field or a
+.B %Q
+field.
+.
+.
+.TP
+.B %B
+For an article that is part of a book,
+the title of the book.
+.
+.
+.TP
+.B %C
+The place (city) of publication.
+.
+.
+.TP
+.B %D
+The date of publication.
+.
+The year should be specified in full.
+.
+If the month is specified,
+the name rather than the number of the month should be used,
+but only the first three letters are required.
+.
+It is a good idea always to supply a
+.B %D
+field;
+if the date is unknown,
+a value such as
+.B in press
+or
+.B unknown
+can be used.
+.
+.
+.TP
+.B %E
+For an article that is part of a book,
+the name of an editor of the book.
+.
+Where the work has editors and no authors,
+the names of the editors should be given as
+.B %A
+fields and
+.RB \[lq] ,\~(ed.)\& \[rq]
+or
+.RB \[lq] ,\~(eds.)\& \[rq]
+should be appended to the last author.
+.
+.
+.TP
+.B %G
+U.S. government ordering number.
+.
+.
+.TP
+.B %I
+The publisher (issuer).
+.
+.
+.TP
+.B %J
+For an article in a journal,
+the name of the journal.
+.
+.
+.TP
+.B %K
+Keywords to be used for searching.
+.
+.
+.TP
+.B %L
+Label.
+.
+.
+.TP
+.B %N
+Journal issue number.
+.
+.
+.TP
+.B %O
+Other information.
+.
+This is usually printed at the end of the reference.
+.
+.
+.TP
+.B %P
+Page number.
+.
+A range of pages can be specified as
+.IB m \- \c
+.IR n .
+.
+.
+.TP
+.B %Q
+The name of the author,
+if the author is not a person.
+.
+This will only be used if there are no
+.B %A
+fields.
+.
+There can only be one
+.B %Q
+field.
+.
+.
+.TP
+.B %R
+Technical report number.
+.
+.
+.TP
+.B %S
+Series name.
+.
+.
+.TP
+.B %T
+Title.
+.
+For an article in a book or journal,
+this should be the title of the article.
+.
+.
+.TP
+.B %V
+Volume number of the journal or book.
+.
+.
+.TP
+.B %X
+Annotation.
+.
+.
+.LP
+For all fields except
+.B %A
+and
+.BR %E ,
+if there is more than one occurrence of a particular field in a record,
+only the last such field will be used.
+.
+.
+.P
+If accent strings are used,
+they should follow the character to be accented.
+.
+This means that an
+.I ms
+document must call the
+.B .AM
+macro when it initializes.
+.
+Accent strings should not be quoted:
+use one
+.B \e
+rather than two.
+.
+Accent strings are an obsolescent feature of the
+.I me
+and
+.I ms
+macro packages;
+modern documents should use
+.I groff
+special character escape sequences instead;
+see
+.MR groff_char @MAN7EXT@ .
+.
+.
+.\" ====================================================================
+.SS Citations
+.\" ====================================================================
+.
+Citations have a characteristic format.
+.
+.RS
+.EX
+.BI .[ opening-text
+.I flags keywords
+.I fields
+.BI .] closing-text
+.EE
+.RE
+.
+.
+.LP
+The
+.IR opening-text ,
+.IR closing-text ,
+and
+.I flags
+components are optional.
+.
+Only one of the
+.I keywords
+and
+.I fields
+components need be specified.
+.
+.
+.LP
+The
+.I keywords
+component says to search the bibliographic databases for a reference
+that contains all the words in
+.IR keywords .
+.
+It is an error if more than one reference is found.
+.
+.
+.LP
+The
+.I fields
+components specifies additional fields to replace or supplement those
+specified in the reference.
+.
+When references are being accumulated and the
+.I keywords
+component is non-empty,
+then additional fields should be specified only on the first occasion
+that a particular reference is cited,
+and will apply to all citations of that reference.
+.
+.
+.br
+.ne 2v
+.LP
+The
+.I opening-text
+and
+.I closing-text
+components specify strings to be used to bracket the label instead of
+those in the
+.B \%bracket\-label
+command.
+.
+If either of these components is non-empty,
+the strings specified in the
+.B \%bracket\-label
+command will not be used;
+this behavior can be altered using the
+.B [
+and
+.B ]
+flags.
+.
+Leading and trailing spaces are significant for these components.
+.
+.
+.LP
+The
+.I flags
+component is a list of non-alphanumeric characters each of which
+modifies the treatment of this particular citation.
+.
+AT&T
+.I \%refer \" AT&T
+will treat these flags as part of the keywords and so will ignore them
+since they are non-alphanumeric.
+.
+The following flags are currently recognized.
+.
+.
+.TP
+.B #
+Use the label specified by the
+.B \%short\-label
+command,
+instead of that specified by the
+.B \%label
+command.
+.
+If no short label has been specified,
+the normal label will be used.
+.
+Typically the short label is used with author-date labels and consists
+of only the date and possibly a disambiguating letter;
+the
+.RB \[lq] # \[rq]
+is supposed to be suggestive of a numeric type of label.
+.
+.
+.TP
+.B [
+Precede
+.I opening-text
+with the first string specified in the
+.B \%bracket\-label
+command.
+.
+.
+.TP
+.B ]
+Follow
+.I closing-text
+with the second string specified in the
+.B \%bracket\-label
+command.
+.
+.
+.LP
+An advantage of using the
+.B [
+and
+.B ]
+flags rather than including the brackets in
+.I opening-text
+and
+.I closing-text
+is that
+.
+you can change the style of bracket used in the document just by
+changing the
+.B \%bracket\-label
+command.
+.
+Another is that sorting and merging of citations will not necessarily be
+inhibited if the flags are used.
+.
+.
+.LP
+If a label is to be inserted into the text,
+it will be attached to the line preceding the
+.B .[
+line.
+.
+If there is no such line,
+then an extra line will be inserted before the
+.B .[
+line and a warning will be given.
+.
+.
+.LP
+There is no special notation for making a citation to multiple
+references.
+.
+Just use a sequence of citations,
+one for each reference.
+.
+Don't put anything between the citations.
+.
+The labels for all the citations will be attached to the line preceding
+the first citation.
+.
+The labels may also be sorted or merged.
+.
+See the description of the
+.B <>
+label expression,
+and of the
+.B \%sort\-adjacent\-labels
+and
+.B \%abbreviate\-label\-ranges
+commands.
+.
+A label will not be merged if its citation has a non-empty
+.I opening-text
+or
+.IR closing-text .
+.
+However,
+the labels for a citation using the
+.B ]
+flag and without any
+.I closing-text
+immediately followed by a citation using the
+.B [
+flag and without any
+.I opening-text
+may be sorted and merged
+even though the first citation's
+.I opening-text
+or the second citation's
+.I closing-text
+is non-empty.
+.
+(If you wish to prevent this,
+use the dummy character escape sequence
+.B \[rs]&
+as the first citation's
+.IR closing-text .)
+.
+.
+.\" ====================================================================
+.SS Commands
+.\" ====================================================================
+.
+Commands are contained between lines starting with
+.B .R1
+and
+.BR .R2 .
+.
+Recognition of these lines can be prevented by the
+.B \-R
+option.
+.
+When a
+.B .R1
+line is recognized any accumulated references are flushed out.
+.
+Neither
+.B .R1
+nor
+.B .R2
+lines,
+nor anything between them,
+is output.
+.
+.
+.P
+Commands are separated by newlines or semicolons.
+.
+A number sign
+.RB ( # )
+introduces a comment that extends to the end of the line,
+but does not conceal the newline.
+.
+Each command is broken up into words.
+.
+Words are separated by spaces or tabs.
+.
+A word that begins with a (neutral) double quote
+.RB ( \[dq] )
+extends to the next double quote that is not followed by another double
+quote.
+.
+If there is no such double quote,
+the word extends to the end of the line.
+.
+Pairs of double quotes in a word beginning with a double quote collapse
+to one double quote.
+.
+Neither a number sign nor a semicolon is recognized inside double
+quotes.
+.
+A line can be continued by ending it with a backslash
+.RB \[lq] \[rs] \[rq];
+this works everywhere except after a number sign.
+.
+.
+.LP
+.ds n \fR*\fP\"
+Each command
+.I name
+that is marked with \*n has an associated negative command
+.BI no\- name
+that undoes the effect of
+.IR name .
+.
+For example,
+the
+.B no\-sort
+command specifies that references should not be sorted.
+.
+The negative commands take no arguments.
+.
+.
+.LP
+In the following description each argument must be a single word;
+.I field
+is used for a single upper or lower case letter naming a field;
+.I fields
+is used for a sequence of such letters;
+.I m
+and
+.I n
+are used for a non-negative numbers;
+.I string
+is used for an arbitrary string;
+.I file
+is used for the name of a file.
+.
+.
+.TP
+.BI abbreviate\*n\~ fields\~string1\~string2\~string3\~string4
+Abbreviate the first names of
+.IR fields .
+.
+An initial letter will be separated from another initial letter by
+.IR string1 ,
+from the last name by
+.IR string2 ,
+and from anything else
+(such as \[lq]von\[rq] or \[lq]de\[rq])
+by
+.IR string3 .
+.
+These default to a period followed by a space.
+.
+In a hyphenated first name,
+the initial of the first part of the name will be separated from the
+hyphen by
+.IR string4 ;
+this defaults to a period.
+.
+No attempt is made to handle any ambiguities that might
+result from abbreviation.
+.
+Names are abbreviated before sorting and before label construction.
+.
+.
+.TP
+.BI abbreviate\-label\-ranges\*n\~ string
+.
+Three or more adjacent labels that refer to consecutive references
+will be abbreviated to a label consisting of the first label,
+followed by
+.IR string ,
+followed by the last label.
+.
+This is mainly useful with numeric labels.
+.
+If
+.I string
+is omitted,
+it defaults to
+.RB \[lq] \- \[rq].
+.
+.
+.TP
+.B accumulate\*n
+Accumulate references instead of writing out each reference
+as it is encountered.
+.
+Accumulated references will be written out whenever a reference
+of the form
+.
+.RS
+.RS
+.EX
+.B .[
+.B $LIST$
+.B .]
+.EE
+.RE
+.
+is encountered,
+after all input files have been processed,
+and whenever a
+.B .R1
+line is recognized.
+.RE
+.
+.
+.TP
+.BI annotate\*n\~ "field string"
+.I field
+is an annotation;
+print it at the end of the reference as a paragraph preceded by the line
+.
+.RS
+.IP
+.BI . string
+.
+.
+.LP
+If
+.I string
+is omitted,
+it will default to
+.BR AP ;
+if
+.I field
+is also omitted it will default to
+.BR X .
+.
+Only one field can be an annotation.
+.RE
+.
+.
+.TP
+.BI articles\~ string\~\c
+\&.\|.\|.
+Each
+.I string
+is a definite or indefinite article,
+and should be ignored at the beginning of
+.B T
+fields when sorting.
+.
+Initially,
+\[lq]a\[rq],
+\[lq]an\[rq],
+and
+\[lq]the\[rq] are recognized as articles.
+.
+.
+.TP
+.BI bibliography\~ file\~\c
+\&.\|.\|.
+.
+Write out all the references contained in each bibliographic database
+.IR file .
+.
+This command should come last in an
+.BR .R1 / .R2
+block.
+.
+.
+.TP
+.BI bracket\-label\~ "string1 string2 string3"
+In the text,
+bracket each label with
+.I string1
+and
+.IR string2 .
+.
+An occurrence of
+.I string2
+immediately followed by
+.I string1
+will be turned into
+.IR string3 .
+.
+The default behavior is as follows.
+.
+.RS \" RS twice to get inboard of the tagged paragraph indentation.
+.RS
+.EX
+.B bracket\-label \e*([. \e*(.] \[dq], \[dq]
+.EE
+.RE
+.RE
+.
+.
+.TP
+.BI capitalize\~ fields
+Convert
+.I fields
+to caps and small caps.
+.
+.
+.TP
+.B compatible\*n
+Recognize
+.B .R1
+and
+.B .R2
+even when followed by a character other than space or newline.
+.
+.
+.TP
+.BI database\~ file\~\c
+\&.\|.\|.
+Search each bibliographic database
+.IR file .
+.
+For each
+.IR file ,
+if an index
+.RI file @INDEX_SUFFIX@
+created by
+.MR @g@indxbib @MAN1EXT@
+exists,
+then it will be searched instead;
+each index can cover multiple databases.
+.
+.
+.TP
+.BI date\-as\-label\*n\~ string
+.I string
+is a label expression that specifies a string with which to replace the
+.B D
+field after constructing the label.
+.
+See subsection \[lq]Label expressions\[rq] below for a description of
+label expressions.
+.
+This command is useful if you do not want explicit labels in the
+reference list,
+but instead want to handle any necessary disambiguation by qualifying
+the date in some way.
+.
+The label used in the text would typically be some combination of the
+author and date.
+.
+In most cases you should also use the
+.B \%no\-label\-in\-reference
+command.
+.
+For example,
+.
+.RS \" RS twice to get inboard of the tagged paragraph indentation.
+.RS
+.EX
+.B date\-as\-label D.+yD.y%a*D.\-y
+.EE
+.RE
+.
+would attach a disambiguating letter to the year part of the
+.B D
+field in the reference.
+.RE
+.
+.
+.TP
+.B default\-database\*n
+The default database should be searched.
+.
+This is the default behavior,
+so the negative version of this command is more useful.
+.
+.I @g@refer
+determines whether the default database should be searched
+on the first occasion that it needs to do a search.
+.
+Thus a
+.B \%no\-default\-database
+command must be given before then,
+in order to be effective.
+.
+.
+.TP
+.BI discard\*n\~ fields
+When the reference is read,
+.I fields
+should be discarded;
+no string definitions for
+.I fields
+will be output.
+.
+Initially,
+.I fields
+are
+.BR XYZ .
+.
+.
+.TP
+.BI et\-al\*n\~ "string m n"
+Control use of
+.B et al.\&
+in the evaluation of
+.B @
+expressions in label expressions.
+.
+If the number of authors needed to make the author sequence unambiguous
+is
+.I u
+and the total number of authors is
+.I t
+then the last
+.IR t \|\-\| u
+authors will be replaced by
+.I string
+provided that
+.IR t \|\-\| u
+is not less than
+.I m
+and
+.I t
+is not less than
+.IR n .
+.
+The default behavior is as follows.
+.
+.RS \" RS twice to get inboard of the tagged paragraph indentation.
+.RS
+.EX
+.B et\-al \[dq] et al\[dq] 2 3
+.EE
+.RE
+.
+Note the absence of a dot from the end of the abbreviation,
+which is arguably not correct.
+.
+.RI ( "Et al" [.]
+is short for
+.IR "et alli" ,
+as
+.I etc.\&
+is short for
+.IR "et cetera".)
+.RE
+.
+.
+.TP
+.BI include\~ file
+Include
+.I file
+and interpret the contents as commands.
+.
+.
+.TP
+.BI join\-authors\~ "string1 string2 string3"
+Join multiple authors together with
+.IR string s.
+.
+When there are exactly two authors,
+they will be joined with
+.IR string1 .
+.
+When there are more than two authors,
+all but the last two will be joined with
+.IR string2 ,
+and the last two authors will be joined with
+.IR string3 .
+.
+If
+.I string3
+is omitted,
+it will default to
+.IR string1 ;
+if
+.I string2
+is also omitted it will also default to
+.IR string1 .
+.
+For example,
+.
+.RS
+.RS
+.EX
+join\-authors \[dq] and \[dq] \[dq], \[dq] \[dq], and \[dq]
+.EE
+.RE
+.
+will restore the default method for joining authors.
+.RE
+.
+.
+.TP
+.B label\-in\-reference\*n
+When outputting the reference,
+define the string
+.B [F
+to be the reference's label.
+.
+This is the default behavior,
+so the negative version of this command is more useful.
+.
+.
+.TP
+.B label\-in\-text\*n
+For each reference output a label in the text.
+.
+The label will be separated from the surrounding text as described in
+the
+.B \%bracket\-label
+command.
+.
+This is the default behavior,
+so the negative version of this command is more useful.
+.
+.
+.TP
+.BI label\~ string
+.I string
+is a label expression describing how to label each reference.
+.
+.
+.TP
+.BI separate\-label\-second\-parts\~ string
+When merging two-part labels,
+separate the second part of the second label from the first label with
+.IR string .
+.
+See the description of the
+.B <>
+label expression.
+.
+.
+.TP
+.B move\-punctuation\*n
+In the text,
+move any punctuation at the end of line past the label.
+.
+It is usually a good idea to give this command unless you are using
+superscripted numbers as labels.
+.
+.
+.TP
+.BI reverse\*n\~ string
+Reverse the fields whose names
+are in
+.IR string .
+.
+Each field name can be followed by a number which says how many such
+fields should be reversed.
+.
+If no number is given for a field,
+all such fields will be reversed.
+.
+.
+.TP
+.BI search\-ignore\*n\~ fields
+While searching for keys in databases for which no index exists,
+ignore the contents of
+.IR fields .
+.
+Initially,
+fields
+.B XYZ
+are ignored.
+.
+.
+.TP
+.BI search\-truncate\*n\~ n
+Only require the first
+.I n
+characters of keys to be given.
+.
+In effect when searching for a given key words in the database are
+truncated to the maximum of
+.I n
+and the length of the key.
+.
+Initially,
+.I n
+is\~6.
+.
+.
+.TP
+.BI short\-label\*n\~ string
+.I string
+is a label expression that specifies an alternative
+(usually shorter)
+style of label.
+.
+This is used when the
+.B #
+flag is given in the citation.
+.
+When using author-date style labels,
+the identity of the author or authors is sometimes clear from the
+context,
+and so it may be desirable to omit the author or authors from the label.
+.
+The
+.B \%short\-label
+command will typically be used to specify a label containing just
+a date and possibly a disambiguating letter.
+.
+.
+.TP
+.BI sort\*n\~ string
+Sort references according to
+.IR string .
+.
+References will automatically be accumulated.
+.
+.I string
+should be a list of field names,
+each followed by a number,
+indicating how many fields with the name should be used for sorting.
+.
+.RB \[lq] + \[rq]
+can be used to indicate that all the fields with the name should be
+used.
+.
+Also
+.B .\&
+can be used to indicate the references should be sorted using the
+(tentative) label.
+.
+(Subsection \[lq]Label expressions\[rq] below describes the concept of a
+tentative label.)
+.
+.
+.TP
+.B sort\-adjacent\-labels\*n
+Sort labels that are adjacent in the text according to their position
+in the reference list.
+.
+This command should usually be given if the
+.B \%abbreviate\-label\-ranges
+command has been given,
+or if the label expression contains a
+.B <>
+expression.
+.
+This will have no effect unless references are being accumulated.
+.
+.
+.\" ====================================================================
+.SS "Label expressions"
+.\" ====================================================================
+.
+Label expressions can be evaluated both normally and tentatively.
+.
+The result of normal evaluation is used for output.
+.
+The result of tentative evaluation,
+called the
+.IR "tentative label" ,
+is used to gather the information that normal evaluation needs to
+disambiguate the label.
+.
+Label expressions specified by the
+.B \%date\-as\-label
+and
+.B \%short\-label
+commands are not evaluated tentatively.
+.
+Normal and tentative evaluation are the same for all types of expression
+other than
+.BR @ ,
+.BR * ,
+and
+.B %
+expressions.
+.
+The description below applies to normal evaluation,
+except where otherwise specified.
+.
+.
+.TP
+.I field
+.TQ
+.I field\~n
+The
+.IR n -th
+part of
+.IR field .
+.
+If
+.I n
+is omitted,
+it defaults to\~1.
+.
+.
+.TP
+.BI \[aq] string \[aq]
+The characters in
+.I string
+literally.
+.
+.
+.TP
+.B @
+All the authors joined as specified by the
+.B \%join\-authors
+command.
+.
+The whole of each author's name will be used.
+.
+However,
+if the references are sorted by author
+(that is,
+the sort specification starts with
+.RB \[lq] A+ \[rq]),
+then authors' last names will be used instead,
+provided that this does not introduce ambiguity,
+and also an initial subsequence of the authors may be used instead of
+all the authors,
+again provided that this does not introduce ambiguity.
+.
+The use of only the last name for the
+.IR i -th
+author of some reference
+is considered to be ambiguous if
+there is some other reference,
+such that the first
+.IR i \|\-\|1
+authors of the references are the same,
+the
+.IR i -th
+authors are not the same,
+but the
+.IR i -th
+authors last names are the same.
+.
+A proper initial subsequence of the sequence of authors for some
+reference is considered to be ambiguous if there is a reference with
+some other sequence of authors which also has that subsequence as a
+proper initial subsequence.
+.
+When an initial subsequence of authors is used,
+the remaining authors are replaced by the string specified by the
+.B \%et\-al
+command;
+this command may also specify additional requirements that must be
+met before an initial subsequence can be used.
+.
+.B @
+tentatively evaluates to a canonical representation of the authors,
+such that authors that compare equally for sorting purpose will have
+the same representation.
+.
+.
+.TP
+.BI % n
+.TQ
+.B %a
+.TQ
+.B %A
+.TQ
+.B %i
+.TQ
+.B %I
+The serial number of the reference formatted according to the
+character following the
+.BR % .
+The serial number of a reference is\~1 plus the number of earlier
+references with same tentative label as this reference.
+.
+These expressions tentatively evaluate to an empty string.
+.
+.TP
+.IB expr *
+If there is another reference with the same tentative label as this
+reference,
+then
+.IR expr ,
+otherwise an empty string.
+.
+It tentatively evaluates to an empty string.
+.
+.
+.TP
+.IB expr + n
+.TQ
+.IB expr \- n
+The first
+.RB ( + )
+or last
+.RB ( \- )
+.I n
+upper or lower case letters or digits of
+.IR expr .
+.
+.I roff
+special characters
+(such as
+.BR \e(\[aq]a )
+count as a single letter.
+.
+Accent strings are retained but do not count towards the total.
+.
+.
+.TP
+.IB expr .l
+.I expr
+converted to lowercase.
+.
+.
+.TP
+.IB expr .u
+.I expr
+converted to uppercase.
+.
+.
+.TP
+.IB expr .c
+.I expr
+converted to caps and small caps.
+.
+.
+.TP
+.IB expr .r
+.I expr
+reversed so that the last name is first.
+.
+.
+.TP
+.IB expr .a
+.I expr
+with first names abbreviated.
+.
+Fields specified in the
+.B \%abbreviate
+command are abbreviated before any labels are evaluated.
+.
+Thus
+.B .a
+is useful only when you want a field to be abbreviated in a label
+but not in a reference.
+.
+.
+.TP
+.IB expr .y
+The year part of
+.IR expr .
+.
+.
+.TP
+.IB expr .+y
+The part of
+.I expr
+before the year,
+or the whole of
+.I expr
+if it does not contain a year.
+.
+.
+.TP
+.IB expr .\-y
+The part of
+.I expr
+after the year,
+or an empty string if
+.I expr
+does not contain a year.
+.
+.
+.TP
+.IB expr .n
+The last name part of
+.IR expr .
+.
+.
+.TP
+.IB expr1 \[ti] expr2
+.I expr1
+except that if the last character of
+.I expr1
+is
+.B \-
+then it will be replaced by
+.IR expr2 .
+.
+.
+.TP
+.I expr1 expr2
+The concatenation of
+.I expr1
+and
+.IR expr2 .
+.
+.
+.TP
+.IB expr1 | expr2
+If
+.I expr1
+is non-empty then
+.I expr1
+otherwise
+.IR expr2 .
+.
+.
+.TP
+.IB expr1 & expr2
+If
+.I expr1
+is non-empty
+then
+.I expr2
+otherwise an empty string.
+.
+.
+.TP
+.IB expr1 ? expr2 : expr3
+If
+.I expr1
+is non-empty
+then
+.I expr2
+otherwise
+.IR expr3 .
+.
+.
+.TP
+.BI < expr >
+The label is in two parts,
+which are separated by
+.IR expr .
+.
+Two adjacent two-part labels which have the same first part will be
+merged by appending the second part of the second label onto the first
+label separated by the string specified in the
+.B \%separate\-label\-second\-parts
+command
+(initially,
+a comma followed by a space);
+the resulting label will also be a two-part label with the same first
+part as before merging,
+and so additional labels can be merged into it.
+.
+It is permissible for the first part to be empty;
+this may be desirable for expressions used in the
+.B \%short\-label
+command.
+.
+.
+.TP
+.BI ( expr )
+The same as
+.IR expr .
+.
+Used for grouping.
+.
+.
+.LP
+The above expressions are listed in order of precedence
+(highest first);
+.B &
+and
+.B |
+have the same precedence.
+.
+.
+.\" ====================================================================
+.SS "Macro interface"
+.\" ====================================================================
+.
+Each reference starts with a call to the macro
+.BR ]\- .
+.
+The string
+.B [F
+will be defined to be the label for this reference,
+unless the
+.B \%no\-label\-in\-reference
+command has been given.
+.
+There then follows a series of string definitions,
+one for each field:
+string
+.BI [ X
+corresponds to field
+.IR X .
+.
+The register
+.B [P
+is set to\~1 if the
+.B P
+field contains a range of pages.
+.
+The
+.BR [T ,
+.B [A
+and
+.B [O
+registers are set to\~1 according as the
+.BR T ,
+.B A
+and
+.B O
+fields end with any of
+.B .?!\&
+(an end-of-sentence character).
+.
+The
+.B [E
+register will be set to\~1 if the
+.B [E
+string contains more than one name.
+.
+The reference is followed by a call to the
+.B ][
+macro.
+.
+The first argument to this macro gives a number representing
+the type of the reference.
+.
+If a reference contains a
+.B J
+field,
+it will be classified as type\~1,
+otherwise if it contains a
+.B B
+field,
+it will be type\~3,
+otherwise if it contains a
+.B G
+or
+.B R
+field it will be type\~4,
+otherwise if it contains an
+.B I
+field it will be type\~2,
+otherwise it will be type\~0.
+.
+The second argument is a symbolic name for the type:
+.BR other ,
+.BR \%journal\-article ,
+.BR book ,
+.BR \%article\-in\-book ,
+or
+.BR \%tech\-report .
+.
+Groups of references that have been accumulated or are produced by the
+.B \%bibliography
+command are preceded by a call to the
+.B ]<
+macro and followed by a call to the
+.B ]>
+macro.
+.
+.
+.br
+.ne 4v
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-R
+Don't recognize lines beginning with
+.BR .R1 / .R2 .
+.
+.
+.P
+Other options are equivalent to
+.I @g@refer
+commands.
+.
+.
+.TP 16n
+.BI \-a\~ n
+.B reverse
+.BI A n
+.
+.
+.TP
+.B \-b
+.B "\%no\-label\-in\-text; \%no\-label\-in\-reference"
+.
+.
+.TP
+.B \-B
+See below.
+.
+.
+.TP
+.BI \-c\~ fields
+.B capitalize
+.I fields
+.
+.
+.TP
+.B \-C
+.B compatible
+.
+.
+.TP
+.B \-e
+.B accumulate
+.
+.
+.TP
+.BI \-f\~ n
+.B \%label
+.BI % n
+.
+.
+.TP
+.BI \-i\~ fields
+.B search\-ignore
+.I fields
+.
+.
+.TP
+.B \-k
+.B \%label
+.B L\[ti]%a
+.
+.
+.TP
+.BI \-k\~ field
+.B \%label
+.IB field \[ti]%a
+.
+.
+.TP
+.B \-l
+.B \%label
+.B A.nD.y%a
+.
+.
+.TP
+.BI \-l\~ m
+.B \%label
+.BI A.n+ m D.y%a
+.
+.
+.TP
+.BI \-l\~, n
+.B \%label
+.BI A.nD.y\- n %a
+.
+.
+.TP
+.BI \-l\~ m , n
+.B \%label
+.BI A.n+ m D.y\- n %a
+.
+.
+.TP
+.B \-n
+.B \%no\-default\-database
+.
+.
+.TP
+.BI \-p\~ db-file
+.B database
+.I db-file
+.
+.
+.TP
+.B \-P
+.B move\-punctuation
+.
+.
+.TP
+.BI \-s\~ spec
+.B sort
+.I spec
+.
+.
+.TP
+.B \-S
+.B \%label \[dq](A.n|Q) \[aq], \[aq] (D.y|D)\[dq]; \
+\%bracket-\%label \[dq]\~(\[dq]\~)\~\[dq];\~\[dq]
+.
+.
+.TP
+.BI \-t\~ n
+.B search\-truncate
+.I n
+.
+.
+.P
+The
+.B B
+option has command equivalents with the addition that the file names
+specified on the command line are processed as if they were arguments to
+the
+.B \%bibliography
+command instead of in the normal way.
+.
+.
+.TP 16n
+.B \-B
+.B "annotate X AP; \%no\-label\-in\-reference"
+.
+.
+.TP
+.BI \-B\~ field . macro
+.B annotate
+.I field
+.IB macro ;
+.B \%no\-label\-in\-reference
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I REFER
+If set,
+overrides the default database.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @DEFAULT_INDEX@
+Default database.
+.
+.
+.TP
+.RI file @INDEX_SUFFIX@
+Index files.
+.
+.
+.TP
+.I @MACRODIR@/\:refer\:.tmac
+defines macros and strings facilitating integration with macro packages
+that wish to support
+.IR @g@refer .
+.
+.
+.LP
+.I @g@refer
+uses temporary files.
+.
+See the
+.MR groff @MAN1EXT@
+man page for details of where such files are created.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+In label expressions,
+.B <>
+expressions are ignored inside
+.BI . char
+expressions.
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+We can illustrate the operation of
+.I @g@refer
+with a sample bibliographic database containing one entry and a simple
+.I roff
+document to cite that entry.
+.
+.
+.P
+.RS
+.EX
+$ \c
+.B cat > my\-db\-file
+.B %A Daniel P.\[rs]& Friedman
+.B %A Matthias Felleisen
+.B %C Cambridge, Massachusetts
+.B %D 1996
+.B %I The MIT Press
+.B %T The Little Schemer, Fourth Edition
+$ \c
+.B refer -p my\-db\-file
+.B Read the book
+.B .[
+.B friedman
+.B .]
+.B on your summer vacation.
+.I <Control+D>
+\&.lf 1 \-
+Read the book\[rs]*([.1\[rs]*(.]
+\&.ds [F 1
+\&.]\-
+\&.ds [A Daniel P. Friedman and Matthias Felleisen
+\&.ds [C Cambridge, Massachusetts
+\&.ds [D 1996
+\&.ds [I The MIT Press
+\&.ds [T The Little Schemer, Fourth Edition
+\&.nr [T 0
+\&.nr [A 0
+\&.][ 2 book
+\&.lf 5 \-
+on your summer vacation.
+.EE
+.RE
+.
+.
+.P
+The foregoing shows us that
+.I @g@refer
+(a) produces a label \[lq]1\[rq];
+(b) brackets that label with interpolations of the
+.RB \[lq] [. \[rq]
+and
+.RB \[lq] .] \[rq]
+strings;
+(c) calls a macro
+.RB \[lq] ]\- \[rq];
+(d) defines strings and registers containing the label and bibliographic
+data for the reference;
+(e) calls a macro
+.RB \[lq] ][ \[rq];
+and (f) uses the
+.B lf
+request to restore the line numbers of the original input.
+.
+As discussed in subsection \[lq]Macro interface\[rq] above,
+it is up to the document or a macro package to employ and format this
+information usefully.
+.
+Let us see how we might turn
+.MR groff_ms @MAN7EXT@
+to this task.
+.
+.
+.P
+.RS
+.EX
+$ \c
+.B REFER=my\-db\-file groff \-R \-ms
+.B .LP
+.B Read the book
+.B .[
+.B friedman
+.B .]
+.B on your summer vacation.
+.B Commentary is available.\[rs]*{*\[rs]*}
+.B .FS \[rs]*{*\[rs]*}
+.B Space reserved for penetrating insight.
+.B .FE
+.EE
+.RE
+.
+.
+.LP
+.IR ms 's
+automatic footnote numbering mechanism is not aware of
+.IR @g@refer 's
+label numbering,
+so we have manually specified a (superscripted) symbolic footnote for
+our non-bibliographic aside.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Some Applications of Inverted Indexes on the Unix System\[rq],
+by M.\& E.\& Lesk,
+1978,
+AT&T Bell Laboratories Computing Science Technical Report No.\& 69.
+.
+.
+.LP
+.MR @g@indxbib @MAN1EXT@ ,
+.MR @g@lookbib @MAN1EXT@ ,
+.MR lkbib @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_refer_1_man_C]
+.do rr *groff_refer_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/preproc/refer/refer.am b/src/preproc/refer/refer.am
new file mode 100644
index 0000000..273f334
--- /dev/null
+++ b/src/preproc/refer/refer.am
@@ -0,0 +1,61 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += refer
+refer_CPPFLAGS = $(AM_CPPFLAGS) -I $(top_srcdir)/src/preproc/refer
+refer_LDADD = libbib.a libgroff.a $(LIBM) lib/libgnu.a
+refer_SOURCES = \
+ src/preproc/refer/command.cpp \
+ src/preproc/refer/ref.cpp \
+ src/preproc/refer/refer.cpp \
+ src/preproc/refer/token.cpp \
+ src/preproc/refer/label.ypp \
+ src/preproc/refer/refer.h \
+ src/preproc/refer/ref.h \
+ src/preproc/refer/token.h \
+ src/preproc/refer/command.h
+
+PREFIXMAN1 += src/preproc/refer/refer.1
+EXTRA_DIST += \
+ src/preproc/refer/TODO \
+ src/preproc/refer/refer.1.man
+
+# Since refer_CPPFLAGS was set, all .o files have a 'refer-' prefix.
+src/preproc/refer/refer-command.$(OBJEXT): defs.h
+src/preproc/refer/refer-ref.$(OBJEXT): defs.h
+src/preproc/refer/refer-refer.$(OBJEXT): defs.h
+src/preproc/refer/refer-token.$(OBJEXT): defs.h
+src/preproc/refer/refer-label.$(OBJEXT): defs.h
+
+MAINTAINERCLEANFILES += \
+ src/preproc/refer/label.cpp \
+ src/preproc/refer/label.hpp \
+ src/preproc/refer/label.output
+
+refer_TESTS = \
+ src/preproc/refer/tests/report-correct-line-numbers.sh
+TESTS += $(refer_TESTS)
+EXTRA_DIST += \
+ $(refer_TESTS) \
+ src/preproc/refer/tests/artifacts/62124.bib
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/refer/refer.cpp b/src/preproc/refer/refer.cpp
new file mode 100644
index 0000000..a5c291e
--- /dev/null
+++ b/src/preproc/refer/refer.cpp
@@ -0,0 +1,1267 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "refer.h"
+#include "refid.h"
+#include "ref.h"
+#include "token.h"
+#include "search.h"
+#include "command.h"
+
+extern "C" const char *Version_string;
+
+const char PRE_LABEL_MARKER = '\013';
+const char POST_LABEL_MARKER = '\014';
+const char LABEL_MARKER = '\015'; // label_type is added on
+
+#define FORCE_LEFT_BRACKET 04
+#define FORCE_RIGHT_BRACKET 010
+
+static FILE *outfp = stdout;
+
+string capitalize_fields;
+string reverse_fields;
+string abbreviate_fields;
+string period_before_last_name = ". ";
+string period_before_initial = ".";
+string period_before_hyphen = "";
+string period_before_other = ". ";
+string sort_fields;
+int annotation_field = -1;
+string annotation_macro;
+string discard_fields = "XYZ";
+string pre_label = "\\*([.";
+string post_label = "\\*(.]";
+string sep_label = ", ";
+int have_bibliography = 0;
+int accumulate = 0;
+int move_punctuation = 0;
+int abbreviate_label_ranges = 0;
+string label_range_indicator;
+int label_in_text = 1;
+int label_in_reference = 1;
+int date_as_label = 0;
+int sort_adjacent_labels = 0;
+// Join exactly two authors with this.
+string join_authors_exactly_two = " and ";
+// When there are more than two authors join the last two with this.
+string join_authors_last_two = ", and ";
+// Otherwise join authors with this.
+string join_authors_default = ", ";
+string separate_label_second_parts = ", ";
+// Use this string to represent that there are other authors.
+string et_al = " et al";
+// Use et al only if it can replace at least this many authors.
+int et_al_min_elide = 2;
+// Use et al only if the total number of authors is at least this.
+int et_al_min_total = 3;
+
+
+int compatible_flag = 0;
+
+int short_label_flag = 0;
+
+static bool recognize_R1_R2 = true;
+
+search_list database_list;
+int search_default = 1;
+static int default_database_loaded = 0;
+
+static reference **citation = 0;
+static int ncitations = 0;
+static int citation_max = 0;
+
+static reference **reference_hash_table = 0;
+static int hash_table_size;
+static int nreferences = 0;
+
+static int need_syncing = 0;
+string pending_line;
+string pending_lf_lines;
+
+static void output_pending_line();
+static unsigned immediately_handle_reference(const string &);
+static void immediately_output_references();
+static unsigned store_reference(const string &);
+static void divert_to_temporary_file();
+static reference *make_reference(const string &, unsigned *);
+static void usage(FILE *stream);
+static void do_file(const char *);
+static void split_punct(string &line, string &punct);
+static void output_citation_group(reference **v, int n, label_type,
+ FILE *fp);
+static void possibly_load_default_database();
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ outfp = stdout;
+ int finished_options = 0;
+ int bib_flag = 0;
+ int done_spec = 0;
+
+ // TODO: Migrate to getopt_long; see, e.g., src/preproc/eqn/main.cpp.
+ for (--argc, ++argv;
+ !finished_options && argc > 0 && argv[0][0] == '-'
+ && argv[0][1] != '\0';
+ argv++, argc--) {
+ const char *opt = argv[0] + 1;
+ while (opt != 0 && *opt != '\0') {
+ switch (*opt) {
+ case 'C':
+ compatible_flag = 1;
+ opt++;
+ break;
+ case 'B':
+ bib_flag = 1;
+ label_in_reference = 0;
+ label_in_text = 0;
+ ++opt;
+ if (*opt == '\0') {
+ annotation_field = 'X';
+ annotation_macro = "AP";
+ }
+ else if (csalnum(opt[0]) && opt[1] == '.' && opt[2] != '\0') {
+ annotation_field = opt[0];
+ annotation_macro = opt + 2;
+ }
+ opt = 0;
+ break;
+ case 'P':
+ move_punctuation = 1;
+ opt++;
+ break;
+ case 'R':
+ recognize_R1_R2 = false;
+ opt++;
+ break;
+ case 'S':
+ // Not a very useful spec.
+ set_label_spec("(A.n|Q)', '(D.y|D)");
+ done_spec = 1;
+ pre_label = " (";
+ post_label = ")";
+ sep_label = "; ";
+ opt++;
+ break;
+ case 'V':
+ do_verify = true;
+ opt++;
+ break;
+ case 'f':
+ {
+ const char *num = 0;
+ if (*++opt == '\0') {
+ if (argc > 1) {
+ num = *++argv;
+ --argc;
+ }
+ else {
+ error("'f' option requires an argument");
+ usage(stderr);
+ exit(1);
+ }
+ }
+ else {
+ num = opt;
+ opt = 0;
+ }
+ const char *ptr;
+ for (ptr = num; *ptr; ptr++)
+ if (!csdigit(*ptr)) {
+ error("invalid character '%1' in argument to 'f' option",
+ *ptr);
+ break;
+ }
+ if (*ptr == '\0') {
+ string spec;
+ spec = '%';
+ spec += num;
+ spec += '\0';
+ set_label_spec(spec.contents());
+ done_spec = 1;
+ }
+ break;
+ }
+ case 'b':
+ label_in_text = 0;
+ label_in_reference = 0;
+ opt++;
+ break;
+ case 'e':
+ accumulate = 1;
+ opt++;
+ break;
+ case 'c':
+ capitalize_fields = ++opt;
+ opt = 0;
+ break;
+ case 'k':
+ {
+ char buf[5];
+ if (csalpha(*++opt))
+ buf[0] = *opt++;
+ else {
+ if (*opt != '\0')
+ error("invalid field name '%1' in argument to 'k' option",
+ *opt++);
+ buf[0] = 'L';
+ }
+ buf[1] = '~';
+ buf[2] = '%';
+ buf[3] = 'a';
+ buf[4] = '\0';
+ set_label_spec(buf);
+ done_spec = 1;
+ }
+ break;
+ case 'a':
+ {
+ const char *ptr;
+ for (ptr = ++opt; *ptr; ptr++)
+ if (!csdigit(*ptr)) {
+ error("'a' option argument must be an integer");
+ break;
+ }
+ if (*ptr == '\0') {
+ reverse_fields = 'A';
+ reverse_fields += opt;
+ }
+ opt = 0;
+ }
+ break;
+ case 'i':
+ linear_ignore_fields = ++opt;
+ opt = 0;
+ break;
+ case 'l':
+ {
+ char buf[INT_DIGITS*2 + 11]; // A.n+2D.y-3%a
+ strcpy(buf, "A.n");
+ if (*++opt != '\0' && *opt != ',') {
+ char *ptr;
+ long n = strtol(opt, &ptr, 10);
+ if (n == 0 && ptr == opt) {
+ error("invalid integer '%1' in 'l' option argument", opt);
+ opt = 0;
+ break;
+ }
+ if (n < 0)
+ n = 0;
+ opt = ptr;
+ sprintf(strchr(buf, '\0'), "+%ld", n);
+ }
+ strcat(buf, "D.y");
+ if (*opt == ',')
+ opt++;
+ if (*opt != '\0') {
+ char *ptr;
+ long n = strtol(opt, &ptr, 10);
+ if (n == 0 && ptr == opt) {
+ error("invalid integer '%1' in 'l' option argument", opt);
+ opt = 0;
+ break;
+ }
+ if (n < 0)
+ n = 0;
+ sprintf(strchr(buf, '\0'), "-%ld", n);
+ opt = ptr;
+ if (*opt != '\0')
+ error("argument to 'l' option not of form 'm,n'");
+ }
+ strcat(buf, "%a");
+ if (!set_label_spec(buf))
+ assert(0 == "set_label_spec() failed");
+ done_spec = 1;
+ }
+ break;
+ case 'n':
+ search_default = 0;
+ opt++;
+ break;
+ case 'p':
+ {
+ const char *filename = 0;
+ if (*++opt == '\0') {
+ if (argc > 1) {
+ filename = *++argv;
+ argc--;
+ }
+ else {
+ error("option 'p' requires an argument");
+ usage(stderr);
+ exit(1);
+ }
+ }
+ else {
+ filename = opt;
+ opt = 0;
+ }
+ database_list.add_file(filename);
+ }
+ break;
+ case 's':
+ if (*++opt == '\0')
+ sort_fields = "AD";
+ else {
+ sort_fields = opt;
+ opt = 0;
+ }
+ accumulate = 1;
+ break;
+ case 't':
+ {
+ char *ptr;
+ long n = strtol(opt, &ptr, 10);
+ if (n == 0 && ptr == opt) {
+ error("invalid integer '%1' in 't' option argument", opt);
+ opt = 0;
+ break;
+ }
+ if (n < 1)
+ n = 1;
+ linear_truncate_len = int(n);
+ opt = ptr;
+ break;
+ }
+ case '-':
+ if (opt[1] == '\0') {
+ finished_options = 1;
+ opt++;
+ break;
+ }
+ if (strcmp(opt, "-version") == 0) {
+ case 'v':
+ printf("GNU refer (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ if (strcmp(opt, "-help") == 0) {
+ usage(stdout);
+ exit(0);
+ break;
+ }
+ // fall through
+ default:
+ error("unrecognized option '%1'", opt);
+ usage(stderr);
+ exit(1);
+ break;
+ }
+ }
+ }
+ if (!done_spec)
+ set_label_spec("%1");
+ if (argc <= 0) {
+ if (bib_flag)
+ do_bib("-");
+ else
+ do_file("-");
+ }
+ else {
+ for (int i = 0; i < argc; i++) {
+ if (bib_flag)
+ do_bib(argv[i]);
+ else
+ do_file(argv[i]);
+ }
+ }
+ if (accumulate)
+ output_references();
+ if (fflush(stdout) < 0)
+ fatal("output error: %1", strerror(errno));
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-bCenPRS] [-aN] [-cXYZ] [-fN] [-iXYZ] [-kX] [-lM,N]"
+" [-p db-file] [-sXYZ] [-tN] [-Bl.m] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+static void possibly_load_default_database()
+{
+ if (search_default && !default_database_loaded) {
+ char *filename = getenv("REFER");
+ if (filename)
+ database_list.add_file(filename);
+ else
+ database_list.add_file(DEFAULT_INDEX, 1);
+ default_database_loaded = 1;
+ }
+}
+
+static bool is_list(const string &str)
+{
+ const char *start = str.contents();
+ const char *end = start + str.length();
+ while (end > start && csspace(end[-1]))
+ end--;
+ while (start < end && csspace(*start))
+ start++;
+ return end - start == 6 && memcmp(start, "$LIST$", 6) == 0;
+}
+
+static void do_file(const char *filename)
+{
+ FILE *fp;
+ if (strcmp(filename, "-") == 0) {
+ fp = stdin;
+ }
+ else {
+ errno = 0;
+ fp = fopen(filename, "r");
+ if (fp == 0) {
+ error("can't open '%1': %2", filename, strerror(errno));
+ return;
+ }
+ }
+ string fn(filename);
+ fn += '\0';
+ normalize_for_lf(fn);
+ current_filename = fn.contents();
+ fprintf(outfp, ".lf 1 %s\n", current_filename);
+ current_lineno = 1;
+ string line;
+ for (;;) {
+ line.clear();
+ for (;;) {
+ int c = getc(fp);
+ if (EOF == c) {
+ if (line.length() > 0)
+ line += '\n';
+ break;
+ }
+ if (is_invalid_input_char(c))
+ error("invalid input character code %1", c);
+ else {
+ line += c;
+ if ('\n' == c)
+ break;
+ }
+ }
+ int len = line.length();
+ if (len == 0)
+ break;
+ current_lineno++;
+ if (len >= 2 && line[0] == '.' && line[1] == '[') {
+ int start_lineno = current_lineno;
+ bool at_start_of_line = true;
+ string str;
+ string post;
+ string pre(line.contents() + 2, line.length() - 3);
+ for (;;) {
+ int c = getc(fp);
+ if (EOF == c) {
+ error_with_file_and_line(current_filename, start_lineno,
+ "missing '.]' line");
+ break;
+ }
+ if (at_start_of_line)
+ current_lineno++;
+ if (at_start_of_line && '.' == c) {
+ int d = getc(fp);
+ if (d == ']') {
+ while ((d = getc(fp)) != '\n' && d != EOF) {
+ if (is_invalid_input_char(d))
+ error("invalid input character code %1", d);
+ else
+ post += d;
+ }
+ break;
+ }
+ if (d != EOF)
+ ungetc(d, fp);
+ }
+ if (is_invalid_input_char(c))
+ error("invalid input character code %1", c);
+ else
+ str += c;
+ at_start_of_line = ('\n' == c);
+ }
+ if (is_list(str)) {
+ output_pending_line();
+ if (accumulate)
+ output_references();
+ else
+ error("found '$LIST$' but not accumulating references");
+ }
+ else {
+ unsigned flags = (accumulate
+ ? store_reference(str)
+ : immediately_handle_reference(str));
+ if (label_in_text) {
+ if (accumulate && outfp == stdout)
+ divert_to_temporary_file();
+ if (pending_line.length() == 0) {
+ warning("can't attach citation to previous line");
+ }
+ else
+ pending_line.set_length(pending_line.length() - 1);
+ string punct;
+ if (move_punctuation)
+ split_punct(pending_line, punct);
+ int have_text = pre.length() > 0 || post.length() > 0;
+ label_type lt = label_type(flags & ~(FORCE_LEFT_BRACKET
+ |FORCE_RIGHT_BRACKET));
+ if ((flags & FORCE_LEFT_BRACKET) || !have_text)
+ pending_line += PRE_LABEL_MARKER;
+ pending_line += pre;
+ char lm = LABEL_MARKER + (int)lt;
+ pending_line += lm;
+ pending_line += post;
+ if ((flags & FORCE_RIGHT_BRACKET) || !have_text)
+ pending_line += POST_LABEL_MARKER;
+ pending_line += punct;
+ pending_line += '\n';
+ }
+ }
+ need_syncing = 1;
+ }
+ else if (len >= 4
+ && '.' == line[0] && 'l' == line[1] && 'f' == line[2]
+ && (compatible_flag || '\n' == line[3] || ' ' == line[3]))
+ {
+ pending_lf_lines += line;
+ line += '\0';
+ if (interpret_lf_args(line.contents() + 3))
+ current_lineno--;
+ }
+ else if (recognize_R1_R2
+ && len >= 4
+ && '.' == line[0] && 'R' == line[1] && '1' == line[2]
+ && (compatible_flag || '\n' == line[3] || ' ' == line[3]))
+ {
+ line.clear();
+ int start_lineno = current_lineno;
+ bool at_start_of_line = true;
+ for (;;) {
+ int c = getc(fp);
+ if (c != EOF && at_start_of_line)
+ current_lineno++;
+ if (at_start_of_line && '.' == c) {
+ c = getc(fp);
+ if ('R' == c) {
+ c = getc(fp);
+ if ('2' == c) {
+ c = getc(fp);
+ if (compatible_flag || ' ' == c || '\n' == c || EOF == c)
+ {
+ while (c != EOF && c != '\n')
+ c = getc(fp);
+ break;
+ }
+ else {
+ line += '.';
+ line += 'R';
+ line += '2';
+ }
+ }
+ else {
+ line += '.';
+ line += 'R';
+ }
+ }
+ else
+ line += '.';
+ }
+ if (EOF == c) {
+ error_with_file_and_line(current_filename, start_lineno,
+ "missing '.R2' line");
+ break;
+ }
+ if (is_invalid_input_char(c))
+ error_with_file_and_line(current_filename, start_lineno,
+ "invalid input character code %1",
+ c);
+ else {
+ line += c;
+ at_start_of_line = ('\n' == c);
+ }
+ }
+ output_pending_line();
+ if (accumulate)
+ output_references();
+ else
+ nreferences = 0;
+ process_commands(line, current_filename, start_lineno + 1);
+ need_syncing = 1;
+ }
+ else {
+ output_pending_line();
+ pending_line = line;
+ }
+ }
+ need_syncing = 0;
+ output_pending_line();
+ if (fp != stdin)
+ fclose(fp);
+}
+
+class label_processing_state {
+ enum {
+ NORMAL,
+ PENDING_LABEL,
+ PENDING_LABEL_POST,
+ PENDING_LABEL_POST_PRE,
+ PENDING_POST
+ } state;
+ label_type type; // type of pending labels
+ int count; // number of pending labels
+ reference **rptr; // pointer to next reference
+ int rcount; // number of references left
+ FILE *fp;
+ int handle_pending(int c);
+public:
+ label_processing_state(reference **, int, FILE *);
+ ~label_processing_state();
+ void process(int c);
+};
+
+static void output_pending_line()
+{
+ if (label_in_text && !accumulate && ncitations > 0) {
+ label_processing_state state(citation, ncitations, outfp);
+ int len = pending_line.length();
+ for (int i = 0; i < len; i++)
+ state.process((unsigned char)(pending_line[i]));
+ }
+ else
+ put_string(pending_line, outfp);
+ pending_line.clear();
+ if (pending_lf_lines.length() > 0) {
+ put_string(pending_lf_lines, outfp);
+ pending_lf_lines.clear();
+ }
+ if (!accumulate)
+ immediately_output_references();
+ if (need_syncing) {
+ fprintf(outfp, ".lf %d %s\n", current_lineno, current_filename);
+ need_syncing = 0;
+ }
+}
+
+static void split_punct(string &line, string &punct)
+{
+ const char *start = line.contents();
+ const char *end = start + line.length();
+ const char *ptr = start;
+ const char *last_token_start = 0;
+ for (;;) {
+ if (ptr >= end)
+ break;
+ last_token_start = ptr;
+ if (*ptr == PRE_LABEL_MARKER || *ptr == POST_LABEL_MARKER
+ || (*ptr >= LABEL_MARKER
+ && *ptr < LABEL_MARKER + N_LABEL_TYPES))
+ ptr++;
+ else if (!get_token(&ptr, end))
+ break;
+ }
+ if (last_token_start) {
+ const token_info *ti = lookup_token(last_token_start, end);
+ if (ti->is_punct()) {
+ punct.append(last_token_start, end - last_token_start);
+ line.set_length(last_token_start - start);
+ }
+ }
+}
+
+static void divert_to_temporary_file()
+{
+ outfp = xtmpfile();
+}
+
+static void store_citation(reference *ref)
+{
+ if (ncitations >= citation_max) {
+ if (citation == 0)
+ citation = new reference*[citation_max = 100];
+ else {
+ reference **old_citation = citation;
+ citation_max *= 2;
+ citation = new reference *[citation_max];
+ memcpy(citation, old_citation, ncitations*sizeof(reference *));
+ delete[] old_citation;
+ }
+ }
+ citation[ncitations++] = ref;
+}
+
+static unsigned store_reference(const string &str)
+{
+ if (reference_hash_table == 0) {
+ reference_hash_table = new reference *[17];
+ hash_table_size = 17;
+ for (int i = 0; i < hash_table_size; i++)
+ reference_hash_table[i] = 0;
+ }
+ unsigned flags;
+ reference *ref = make_reference(str, &flags);
+ ref->compute_hash_code();
+ unsigned h = ref->hash();
+ reference **ptr;
+ for (ptr = reference_hash_table + (h % hash_table_size);
+ *ptr != 0;
+ ((ptr == reference_hash_table)
+ ? (ptr = reference_hash_table + hash_table_size - 1)
+ : --ptr))
+ if (same_reference(**ptr, *ref))
+ break;
+ if (*ptr != 0) {
+ if (ref->is_merged())
+ warning("fields ignored because reference already used");
+ delete ref;
+ ref = *ptr;
+ }
+ else {
+ *ptr = ref;
+ ref->set_number(nreferences);
+ nreferences++;
+ ref->pre_compute_label();
+ ref->compute_sort_key();
+ if (nreferences*2 >= hash_table_size) {
+ // Rehash it.
+ reference **old_table = reference_hash_table;
+ int old_size = hash_table_size;
+ hash_table_size = next_size(hash_table_size);
+ reference_hash_table = new reference*[hash_table_size];
+ int i;
+ for (i = 0; i < hash_table_size; i++)
+ reference_hash_table[i] = 0;
+ for (i = 0; i < old_size; i++)
+ if (old_table[i]) {
+ reference **p;
+ for (p = (reference_hash_table
+ + (old_table[i]->hash() % hash_table_size));
+ *p;
+ ((p == reference_hash_table)
+ ? (p = reference_hash_table + hash_table_size - 1)
+ : --p))
+ ;
+ *p = old_table[i];
+ }
+ delete[] old_table;
+ }
+ }
+ if (label_in_text)
+ store_citation(ref);
+ return flags;
+}
+
+unsigned immediately_handle_reference(const string &str)
+{
+ unsigned flags;
+ reference *ref = make_reference(str, &flags);
+ ref->set_number(nreferences);
+ if (label_in_text || label_in_reference) {
+ ref->pre_compute_label();
+ ref->immediate_compute_label();
+ }
+ nreferences++;
+ store_citation(ref);
+ return flags;
+}
+
+static void immediately_output_references()
+{
+ for (int i = 0; i < ncitations; i++) {
+ reference *ref = citation[i];
+ if (label_in_reference) {
+ fputs(".ds [F ", outfp);
+ const string &label = ref->get_label(NORMAL_LABEL);
+ if (label.length() > 0
+ && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
+ putc('"', outfp);
+ put_string(label, outfp);
+ putc('\n', outfp);
+ }
+ ref->output(outfp);
+ delete ref;
+ }
+ ncitations = 0;
+}
+
+static void output_citation_group(reference **v, int n, label_type type,
+ FILE *fp)
+{
+ if (sort_adjacent_labels) {
+ // Do an insertion sort. Usually n will be very small.
+ for (int i = 1; i < n; i++) {
+ int num = v[i]->get_number();
+ reference *temp = v[i];
+ int j;
+ for (j = i - 1; j >= 0 && v[j]->get_number() > num; j--)
+ v[j + 1] = v[j];
+ v[j + 1] = temp;
+ }
+ }
+ // This messes up if !accumulate.
+ if (accumulate && n > 1) {
+ // remove duplicates
+ int j = 1;
+ for (int i = 1; i < n; i++)
+ if (v[i]->get_label(type) != v[i - 1]->get_label(type))
+ v[j++] = v[i];
+ n = j;
+ }
+ string merged_label;
+ for (int i = 0; i < n; i++) {
+ int nmerged = v[i]->merge_labels(v + i + 1, n - i - 1, type,
+ merged_label);
+ if (nmerged > 0) {
+ put_string(merged_label, fp);
+ i += nmerged;
+ }
+ else
+ put_string(v[i]->get_label(type), fp);
+ if (i < n - 1)
+ put_string(sep_label, fp);
+ }
+}
+
+
+label_processing_state::label_processing_state(reference **p, int n,
+ FILE *f)
+: state(NORMAL), count(0), rptr(p), rcount(n), fp(f)
+{
+}
+
+label_processing_state::~label_processing_state()
+{
+ int handled = handle_pending(EOF);
+ assert(!handled);
+ assert(rcount == 0);
+}
+
+int label_processing_state::handle_pending(int c)
+{
+ switch (state) {
+ case NORMAL:
+ break;
+ case PENDING_LABEL:
+ if (POST_LABEL_MARKER == c) {
+ state = PENDING_LABEL_POST;
+ return 1;
+ }
+ else {
+ output_citation_group(rptr, count, type, fp);
+ rptr += count ;
+ rcount -= count;
+ state = NORMAL;
+ }
+ break;
+ case PENDING_LABEL_POST:
+ if (PRE_LABEL_MARKER == c) {
+ state = PENDING_LABEL_POST_PRE;
+ return 1;
+ }
+ else {
+ output_citation_group(rptr, count, type, fp);
+ rptr += count;
+ rcount -= count;
+ put_string(post_label, fp);
+ state = NORMAL;
+ }
+ break;
+ case PENDING_LABEL_POST_PRE:
+ if (c >= LABEL_MARKER
+ && c < LABEL_MARKER + N_LABEL_TYPES
+ && c - LABEL_MARKER == type) {
+ count += 1;
+ state = PENDING_LABEL;
+ return 1;
+ }
+ else {
+ output_citation_group(rptr, count, type, fp);
+ rptr += count;
+ rcount -= count;
+ put_string(sep_label, fp);
+ state = NORMAL;
+ }
+ break;
+ case PENDING_POST:
+ if (PRE_LABEL_MARKER == c) {
+ put_string(sep_label, fp);
+ state = NORMAL;
+ return 1;
+ }
+ else {
+ put_string(post_label, fp);
+ state = NORMAL;
+ }
+ break;
+ }
+ return 0;
+}
+
+void label_processing_state::process(int c)
+{
+ if (handle_pending(c))
+ return;
+ assert(state == NORMAL);
+ switch (c) {
+ case PRE_LABEL_MARKER:
+ put_string(pre_label, fp);
+ state = NORMAL;
+ break;
+ case POST_LABEL_MARKER:
+ state = PENDING_POST;
+ break;
+ case LABEL_MARKER:
+ case LABEL_MARKER + 1:
+ count = 1;
+ state = PENDING_LABEL;
+ type = label_type(c - LABEL_MARKER);
+ break;
+ default:
+ state = NORMAL;
+ putc(c, fp);
+ break;
+ }
+}
+
+extern "C" {
+
+int rcompare(const void *p1, const void *p2)
+{
+ return compare_reference(**(reference **)p1, **(reference **)p2);
+}
+
+}
+
+void output_references()
+{
+ assert(accumulate);
+ if (!hash_table_size) {
+ if (have_bibliography)
+ error("nothing to reference (probably 'bibliography' before"
+ " 'sort')");
+ accumulate = 0;
+ nreferences = 0;
+ return;
+ }
+ if (nreferences > 0) {
+ int j = 0;
+ int i;
+ for (i = 0; i < hash_table_size; i++)
+ if (reference_hash_table[i] != 0)
+ reference_hash_table[j++] = reference_hash_table[i];
+ assert(j == nreferences);
+ for (; j < hash_table_size; j++)
+ reference_hash_table[j] = 0;
+ qsort(reference_hash_table, nreferences, sizeof(reference*),
+ rcompare);
+ for (i = 0; i < nreferences; i++)
+ reference_hash_table[i]->set_number(i);
+ compute_labels(reference_hash_table, nreferences);
+ }
+ if (outfp != stdout) {
+ rewind(outfp);
+ {
+ label_processing_state state(citation, ncitations, stdout);
+ int c;
+ while ((c = getc(outfp)) != EOF)
+ state.process(c);
+ }
+ ncitations = 0;
+ fclose(outfp);
+ outfp = stdout;
+ }
+ if (nreferences > 0) {
+ fputs(".]<\n", outfp);
+ for (int i = 0; i < nreferences; i++) {
+ if (sort_fields.length() > 0)
+ reference_hash_table[i]->print_sort_key_comment(outfp);
+ if (label_in_reference) {
+ fputs(".ds [F ", outfp);
+ const string &label
+ = reference_hash_table[i]->get_label(NORMAL_LABEL);
+ if (label.length() > 0
+ && (label[0] == ' ' || label[0] == '\\' || label[0] == '"'))
+ putc('"', outfp);
+ put_string(label, outfp);
+ putc('\n', outfp);
+ }
+ reference_hash_table[i]->output(outfp);
+ delete reference_hash_table[i];
+ reference_hash_table[i] = 0;
+ }
+ fputs(".]>\n", outfp);
+ nreferences = 0;
+ }
+ clear_labels();
+}
+
+static reference *find_reference(const char *query, int query_len)
+{
+ // This is so that error messages look better.
+ while (query_len > 0 && csspace(query[query_len - 1]))
+ query_len--;
+ string str;
+ for (int i = 0; i < query_len; i++)
+ str += query[i] == '\n' ? ' ' : query[i];
+ str += '\0';
+ possibly_load_default_database();
+ search_list_iterator iter(&database_list, str.contents());
+ reference_id rid;
+ const char *start;
+ int len;
+ if (!iter.next(&start, &len, &rid)) {
+ error("no matches for '%1'", str.contents());
+ return 0;
+ }
+ const char *end = start + len;
+ while (start < end) {
+ if (*start == '%')
+ break;
+ while (start < end && *start++ != '\n')
+ ;
+ }
+ if (start >= end) {
+ error("found a reference for '%1' but it didn't contain any fields",
+ str.contents());
+ return 0;
+ }
+ reference *result = new reference(start, end - start, &rid);
+ if (iter.next(&start, &len, &rid))
+ warning("multiple matches for '%1'", str.contents());
+ return result;
+}
+
+static reference *make_reference(const string &str, unsigned *flagsp)
+{
+ const char *start = str.contents();
+ const char *end = start + str.length();
+ const char *ptr = start;
+ while (ptr < end) {
+ if (*ptr == '%')
+ break;
+ while (ptr < end && *ptr++ != '\n')
+ ;
+ }
+ *flagsp = 0;
+ for (; start < ptr; start++) {
+ if (*start == '#')
+ *flagsp = (SHORT_LABEL | (*flagsp & (FORCE_RIGHT_BRACKET
+ | FORCE_LEFT_BRACKET)));
+ else if (*start == '[')
+ *flagsp |= FORCE_LEFT_BRACKET;
+ else if (*start == ']')
+ *flagsp |= FORCE_RIGHT_BRACKET;
+ else if (!csspace(*start))
+ break;
+ }
+ if (start >= end) {
+ error("empty reference");
+ return new reference;
+ }
+ reference *database_ref = 0;
+ if (start < ptr)
+ database_ref = find_reference(start, ptr - start);
+ reference *inline_ref = 0;
+ if (ptr < end)
+ inline_ref = new reference(ptr, end - ptr);
+ if (inline_ref) {
+ if (database_ref) {
+ database_ref->merge(*inline_ref);
+ delete inline_ref;
+ return database_ref;
+ }
+ else
+ return inline_ref;
+ }
+ else if (database_ref)
+ return database_ref;
+ else
+ return new reference;
+}
+
+static void do_ref(const string &str)
+{
+ if (accumulate)
+ (void)store_reference(str);
+ else {
+ (void)immediately_handle_reference(str);
+ immediately_output_references();
+ }
+}
+
+static void trim_blanks(string &str)
+{
+ const char *start = str.contents();
+ const char *end = start + str.length();
+ while (end > start && end[-1] != '\n' && csspace(end[-1]))
+ --end;
+ str.set_length(end - start);
+}
+
+void do_bib(const char *filename)
+{
+ FILE *fp;
+ if (strcmp(filename, "-") == 0)
+ fp = stdin;
+ else {
+ errno = 0;
+ fp = fopen(filename, "r");
+ if (fp == 0) {
+ error("can't open '%1': %2", filename, strerror(errno));
+ return;
+ }
+ current_filename = filename;
+ }
+ current_lineno = 1;
+ enum {
+ START, MIDDLE, BODY, BODY_START, BODY_BLANK, BODY_DOT
+ } state = START;
+ string body;
+ for (;;) {
+ int c = getc(fp);
+ if (EOF == c)
+ break;
+ if (is_invalid_input_char(c)) {
+ error("invalid input character code %1", c);
+ continue;
+ }
+ switch (state) {
+ case START:
+ if ('%' == c) {
+ body = c;
+ state = BODY;
+ }
+ else if (c != '\n')
+ state = MIDDLE;
+ break;
+ case MIDDLE:
+ if ('\n' == c)
+ state = START;
+ break;
+ case BODY:
+ body += c;
+ if ('\n' == c)
+ state = BODY_START;
+ break;
+ case BODY_START:
+ if ('\n' == c) {
+ do_ref(body);
+ state = START;
+ }
+ else if ('.' == c)
+ state = BODY_DOT;
+ else if (csspace(c)) {
+ state = BODY_BLANK;
+ body += c;
+ }
+ else {
+ body += c;
+ state = BODY;
+ }
+ break;
+ case BODY_BLANK:
+ if ('\n' == c) {
+ trim_blanks(body);
+ do_ref(body);
+ state = START;
+ }
+ else if (csspace(c))
+ body += c;
+ else {
+ body += c;
+ state = BODY;
+ }
+ break;
+ case BODY_DOT:
+ if (']' == c) {
+ do_ref(body);
+ state = MIDDLE;
+ }
+ else {
+ body += '.';
+ body += c;
+ state = ('\n' == c) ? BODY_START : BODY;
+ }
+ break;
+ default:
+ assert(0 == "unhandled case while parsing bibliography file");
+ }
+ if ('\n' == c)
+ current_lineno++;
+ }
+ switch (state) {
+ case START:
+ case MIDDLE:
+ break;
+ case BODY:
+ body += '\n';
+ do_ref(body);
+ break;
+ case BODY_DOT:
+ case BODY_START:
+ do_ref(body);
+ break;
+ case BODY_BLANK:
+ trim_blanks(body);
+ do_ref(body);
+ break;
+ }
+ fclose(fp);
+}
+
+// from the Dragon Book
+
+unsigned hash_string(const char *s, int len)
+{
+ const char *end = s + len;
+ unsigned h = 0, g;
+ while (s < end) {
+ h <<= 4;
+ h += *s++;
+ if ((g = h & 0xf0000000) != 0) {
+ h ^= g >> 24;
+ h ^= g;
+ }
+ }
+ return h;
+}
+
+int next_size(int n)
+{
+ static const int table_sizes[] = {
+ 101, 503, 1009, 2003, 3001, 4001, 5003, 10007, 20011, 40009,
+ 80021, 160001, 500009, 1000003, 2000003, 4000037, 8000009,
+ 16000057, 32000011, 64000031, 128000003, 0
+ };
+
+ const int *p;
+ for (p = table_sizes; *p <= n && *p != 0; p++)
+ ;
+ assert(*p != 0);
+ return *p;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/refer/refer.h b/src/preproc/refer/refer.h
new file mode 100644
index 0000000..3ebff27
--- /dev/null
+++ b/src/preproc/refer/refer.h
@@ -0,0 +1,81 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "cmap.h"
+#include "lf.h"
+
+#include "defs.h"
+
+unsigned hash_string(const char *, int);
+int next_size(int);
+
+extern string capitalize_fields;
+extern string reverse_fields;
+extern string abbreviate_fields;
+extern string period_before_last_name;
+extern string period_before_initial;
+extern string period_before_hyphen;
+extern string period_before_other;
+extern string sort_fields;
+extern int annotation_field;
+extern string annotation_macro;
+extern string discard_fields;
+extern string articles;
+extern int abbreviate_label_ranges;
+extern string label_range_indicator;
+extern int date_as_label;
+extern string join_authors_exactly_two;
+extern string join_authors_last_two;
+extern string join_authors_default;
+extern string separate_label_second_parts;
+extern string et_al;
+extern int et_al_min_elide;
+extern int et_al_min_total;
+
+extern int compatible_flag;
+
+extern int set_label_spec(const char *);
+extern int set_date_label_spec(const char *);
+extern int set_short_label_spec(const char *);
+
+extern int short_label_flag;
+
+void clear_labels();
+void command_error(const char *,
+ const errarg &arg1 = empty_errarg,
+ const errarg &arg2 = empty_errarg,
+ const errarg &arg3 = empty_errarg);
+
+class reference;
+
+void compute_labels(reference **, int);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/refer/tests/artifacts/62124.bib b/src/preproc/refer/tests/artifacts/62124.bib
new file mode 100644
index 0000000..1093837
--- /dev/null
+++ b/src/preproc/refer/tests/artifacts/62124.bib
@@ -0,0 +1,4 @@
+%A ÂIrritablé, X.
+%T ˆUniversit\*'e de Grenoble. ‰Cours donn\*'es aux Houches.
+%Z ˆMon dieu, Consiel !‰
+%Z NOTE: This file is deliberately not valid UTF-8. Try Latin-1.
diff --git a/src/preproc/refer/tests/report-correct-line-numbers.sh b/src/preproc/refer/tests/report-correct-line-numbers.sh
new file mode 100755
index 0000000..19cae53
--- /dev/null
+++ b/src/preproc/refer/tests/report-correct-line-numbers.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+refer="${abs_top_builddir:-.}/refer"
+
+fail=
+
+wail () {
+ echo FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #62124. Ensure correct line numbers in
+# diagnostics on bibliography files.
+
+# Locate directory containing our test artifacts.
+artifact_dir=
+
+for buildroot in . .. ../..
+do
+ d=$buildroot/src/preproc/refer/tests/artifacts
+ if [ -d "$d" ]
+ then
+ artifact_dir=$d
+ break
+ fi
+done
+
+# If we can't find it, we can't test.
+test -z "$artifact_dir" && exit 77 # skip
+
+input=".
+.R1
+bibliography $artifact_dir/62124.bib
+cattywumpus
+.R2
+.
+.R1
+bibliography $artifact_dir/62124.bib
+cattywumpus
+.R2"
+
+# We want standard error _only_.
+output=$(echo "$input" | "$refer" -e -p "$artifact_dir"/62124.bib \
+ 2>&1 >/dev/null)
+
+# We should get every complaint about the bibliography twice because it
+# is dumped twice; the line numbers should not change because they're
+# problems with the bibliography file, not the input file.
+
+# We're pattern-matching diagnostic output here, which is a delicate
+# thing to do. If a test failure occurs, ensure the diagnostic message
+# text hasn't changed before assuming a deeper logic problem.
+
+echo "checking line number of invalid character on bibliography line 1"
+count=$(echo "$output" | grep -c "refer:.*/62124.bib:1:.*code 129")
+test $count -eq 2 || wail
+
+echo "checking line number of first invalid character on bibliography" \
+ "line 2"
+count=$(echo "$output" | grep -c "refer:.*/62124.bib:2:.*code 136")
+test $count -eq 2 || wail
+
+echo "checking line number of second invalid character on" \
+ "bibliography line 2"
+count=$(echo "$output" | grep -c "refer:.*/62124.bib:2:.*code 137")
+test $count -eq 2 || wail
+
+echo "checking line number of first invalid character on" \
+ "bibliography line 3"
+count=$(echo "$output" | grep -c "refer:.*/62124.bib:3:.*code 136")
+test $count -eq 2 || wail
+
+echo "checking line number of second invalid character on" \
+ "bibliography line 3"
+count=$(echo "$output" | grep -c "refer:.*/62124.bib:3:.*code 137")
+test $count -eq 2 || wail
+
+# Problems with the input file should also be accurately located.
+
+echo "checking line number of invalid refer(1) command on input line 4"
+echo "$output"
+echo "$output" | grep -q "refer:.*:4:.*unknown command" || wail
+
+echo "checking line number of invalid refer(1) command on input line 9"
+echo "$output"
+echo "$output" | grep -q "refer:.*:9:.*unknown command" || wail
+
+# Regression-test Savannah #62391.
+
+output=$(printf '\0201\n' | "$refer" 2>&1 >/dev/null)
+
+echo "checking line number of invalid input character on input line 1"
+echo "$output" | grep -q "refer:.*:1:.*invalid input character" \
+ || wail
+
+output=$(printf '.R1\nbogus \0200\n.R2\n' | "$refer" 2>&1 >/dev/null)
+
+echo "checking line number of invalid input character after refer(1)" \
+ "command on input line 2"
+echo "$output" | grep -q "refer:.*:2:.*invalid input character" \
+ || wail
+
+output=$(printf '.R1\ndatabase nonexistent.bib\n.R2\n' | "$refer" 2>&1 \
+ >/dev/null)
+
+echo "checking line number of attempt to load nonexistent database"
+echo "$output" | grep -q "refer:.*:2:.*can't open 'nonexistent\.bib':" \
+ || wail
+
+output=$(printf '.R1\ninclude nonexistent.bib\n.R2\n' | "$refer" 2>&1 \
+ >/dev/null)
+
+echo "checking line number of attempt to load nonexistent inclusion"
+echo "$output" | grep -q "refer:.*:2:.*can't open 'nonexistent\.bib':" \
+ || wail
+test -z "$fail" || exit 1
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/refer/token.cpp b/src/preproc/refer/token.cpp
new file mode 100644
index 0000000..e643cbd
--- /dev/null
+++ b/src/preproc/refer/token.cpp
@@ -0,0 +1,377 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "refer.h"
+#include "token.h"
+
+#define TOKEN_TABLE_SIZE 1009
+// I believe in Icelandic thorn sorts after z.
+#define THORN_SORT_KEY "{"
+
+struct token_table_entry {
+ const char *tok;
+ token_info ti;
+ token_table_entry();
+};
+
+token_table_entry token_table[TOKEN_TABLE_SIZE];
+int ntokens = 0;
+
+static void skip_name(const char **ptr, const char *end)
+{
+ if (*ptr < end) {
+ switch (*(*ptr)++) {
+ case '(':
+ if (*ptr < end) {
+ *ptr += 1;
+ if (*ptr < end)
+ *ptr += 1;
+ }
+ break;
+ case '[':
+ while (*ptr < end)
+ if (*(*ptr)++ == ']')
+ break;
+ break;
+ }
+ }
+}
+
+int get_token(const char **ptr, const char *end)
+{
+ if (*ptr >= end)
+ return 0;
+ char c = *(*ptr)++;
+ if (c == '\\' && *ptr < end) {
+ switch (**ptr) {
+ default:
+ *ptr += 1;
+ break;
+ case '(':
+ case '[':
+ skip_name(ptr, end);
+ break;
+ case '*':
+ case 'f':
+ *ptr += 1;
+ skip_name(ptr, end);
+ break;
+ }
+ }
+ return 1;
+}
+
+token_info::token_info()
+: type(TOKEN_OTHER), sort_key(0), other_case(0)
+{
+}
+
+void token_info::set(token_type t, const char *sk, const char *oc)
+{
+ assert(oc == 0 || t == TOKEN_UPPER || t == TOKEN_LOWER);
+ type = t;
+ sort_key = sk;
+ other_case = oc;
+}
+
+void token_info::sortify(const char *start, const char *end, string &result)
+ const
+{
+ if (sort_key)
+ result += sort_key;
+ else if (type == TOKEN_UPPER || type == TOKEN_LOWER) {
+ for (; start < end; start++)
+ if (csalpha(*start))
+ result += cmlower(*start);
+ }
+}
+
+int token_info::sortify_non_empty(const char *start, const char *end) const
+{
+ if (sort_key)
+ return *sort_key != '\0';
+ if (type != TOKEN_UPPER && type != TOKEN_LOWER)
+ return 0;
+ for (; start < end; start++)
+ if (csalpha(*start))
+ return 1;
+ return 0;
+}
+
+
+void token_info::lower_case(const char *start, const char *end,
+ string &result) const
+{
+ if (type != TOKEN_UPPER) {
+ while (start < end)
+ result += *start++;
+ }
+ else if (other_case)
+ result += other_case;
+ else {
+ while (start < end)
+ result += cmlower(*start++);
+ }
+}
+
+void token_info::upper_case(const char *start, const char *end,
+ string &result) const
+{
+ if (type != TOKEN_LOWER) {
+ while (start < end)
+ result += *start++;
+ }
+ else if (other_case)
+ result += other_case;
+ else {
+ while (start < end)
+ result += cmupper(*start++);
+ }
+}
+
+token_table_entry::token_table_entry()
+: tok(0)
+{
+}
+
+static void store_token(const char *tok, token_type typ,
+ const char *sk = 0, const char *oc = 0)
+{
+ unsigned n = hash_string(tok, strlen(tok)) % TOKEN_TABLE_SIZE;
+ for (;;) {
+ if (token_table[n].tok == 0) {
+ if (++ntokens == TOKEN_TABLE_SIZE)
+ assert(0);
+ token_table[n].tok = tok;
+ break;
+ }
+ if (strcmp(tok, token_table[n].tok) == 0)
+ break;
+ if (n == 0)
+ n = TOKEN_TABLE_SIZE - 1;
+ else
+ --n;
+ }
+ token_table[n].ti.set(typ, sk, oc);
+}
+
+
+token_info default_token_info;
+
+const token_info *lookup_token(const char *start, const char *end)
+{
+ unsigned n = hash_string(start, end - start) % TOKEN_TABLE_SIZE;
+ for (;;) {
+ if (token_table[n].tok == 0)
+ break;
+ if (strlen(token_table[n].tok) == size_t(end - start)
+ && memcmp(token_table[n].tok, start, end - start) == 0)
+ return &(token_table[n].ti);
+ if (n == 0)
+ n = TOKEN_TABLE_SIZE - 1;
+ else
+ --n;
+ }
+ return &default_token_info;
+}
+
+static void init_ascii()
+{
+ const char *p;
+ for (p = "abcdefghijklmnopqrstuvwxyz"; *p; p++) {
+ char buf[2];
+ buf[0] = *p;
+ buf[1] = '\0';
+ store_token(strsave(buf), TOKEN_LOWER);
+ buf[0] = cmupper(buf[0]);
+ store_token(strsave(buf), TOKEN_UPPER);
+ }
+ for (p = "0123456789"; *p; p++) {
+ char buf[2];
+ buf[0] = *p;
+ buf[1] = '\0';
+ const char *s = strsave(buf);
+ store_token(s, TOKEN_OTHER, s);
+ }
+ for (p = ".,:;?!"; *p; p++) {
+ char buf[2];
+ buf[0] = *p;
+ buf[1] = '\0';
+ store_token(strsave(buf), TOKEN_PUNCT);
+ }
+ store_token("-", TOKEN_HYPHEN);
+}
+
+static void store_letter(const char *lower, const char *upper,
+ const char *sort_key = 0)
+{
+ store_token(lower, TOKEN_LOWER, sort_key, upper);
+ store_token(upper, TOKEN_UPPER, sort_key, lower);
+}
+
+static void init_letter(unsigned char uc_code, unsigned char lc_code,
+ const char *sort_key)
+{
+ char lbuf[2];
+ lbuf[0] = lc_code;
+ lbuf[1] = 0;
+ char ubuf[2];
+ ubuf[0] = uc_code;
+ ubuf[1] = 0;
+ store_letter(strsave(lbuf), strsave(ubuf), sort_key);
+}
+
+static void init_latin1()
+{
+ init_letter(0xc0, 0xe0, "a");
+ init_letter(0xc1, 0xe1, "a");
+ init_letter(0xc2, 0xe2, "a");
+ init_letter(0xc3, 0xe3, "a");
+ init_letter(0xc4, 0xe4, "a");
+ init_letter(0xc5, 0xe5, "a");
+ init_letter(0xc6, 0xe6, "ae");
+ init_letter(0xc7, 0xe7, "c");
+ init_letter(0xc8, 0xe8, "e");
+ init_letter(0xc9, 0xe9, "e");
+ init_letter(0xca, 0xea, "e");
+ init_letter(0xcb, 0xeb, "e");
+ init_letter(0xcc, 0xec, "i");
+ init_letter(0xcd, 0xed, "i");
+ init_letter(0xce, 0xee, "i");
+ init_letter(0xcf, 0xef, "i");
+
+ init_letter(0xd0, 0xf0, "d");
+ init_letter(0xd1, 0xf1, "n");
+ init_letter(0xd2, 0xf2, "o");
+ init_letter(0xd3, 0xf3, "o");
+ init_letter(0xd4, 0xf4, "o");
+ init_letter(0xd5, 0xf5, "o");
+ init_letter(0xd6, 0xf6, "o");
+ init_letter(0xd8, 0xf8, "o");
+ init_letter(0xd9, 0xf9, "u");
+ init_letter(0xda, 0xfa, "u");
+ init_letter(0xdb, 0xfb, "u");
+ init_letter(0xdc, 0xfc, "u");
+ init_letter(0xdd, 0xfd, "y");
+ init_letter(0xde, 0xfe, THORN_SORT_KEY);
+
+ store_token("\337", TOKEN_LOWER, "ss", "SS");
+ store_token("\377", TOKEN_LOWER, "y", "Y");
+}
+
+static void init_two_char_letter(char l1, char l2, char u1, char u2,
+ const char *sk = 0)
+{
+ char buf[6];
+ buf[0] = '\\';
+ buf[1] = '(';
+ buf[2] = l1;
+ buf[3] = l2;
+ buf[4] = '\0';
+ const char *p = strsave(buf);
+ buf[2] = u1;
+ buf[3] = u2;
+ store_letter(p, strsave(buf), sk);
+ buf[1] = '[';
+ buf[4] = ']';
+ buf[5] = '\0';
+ p = strsave(buf);
+ buf[2] = l1;
+ buf[3] = l2;
+ store_letter(strsave(buf), p, sk);
+
+}
+
+static void init_special_chars()
+{
+ const char *p;
+ for (p = "':^`~"; *p; p++)
+ for (const char *q = "aeiouy"; *q; q++) {
+ // Use a variable to work around bug in gcc 2.0
+ char c = cmupper(*q);
+ init_two_char_letter(*p, *q, *p, c);
+ }
+ for (p = "/l/o~n,coeaeij"; *p; p += 2) {
+ // Use variables to work around bug in gcc 2.0
+ char c0 = cmupper(p[0]);
+ char c1 = cmupper(p[1]);
+ init_two_char_letter(p[0], p[1], c0, c1);
+ }
+ init_two_char_letter('v', 's', 'v', 'S', "s");
+ init_two_char_letter('v', 'z', 'v', 'Z', "z");
+ init_two_char_letter('o', 'a', 'o', 'A', "a");
+ init_two_char_letter('T', 'p', 'T', 'P', THORN_SORT_KEY);
+ init_two_char_letter('-', 'd', '-', 'D');
+
+ store_token("\\(ss", TOKEN_LOWER, 0, "SS");
+ store_token("\\[ss]", TOKEN_LOWER, 0, "SS");
+
+ store_token("\\(Sd", TOKEN_LOWER, "d", "\\(-D");
+ store_token("\\[Sd]", TOKEN_LOWER, "d", "\\[-D]");
+ store_token("\\(hy", TOKEN_HYPHEN);
+ store_token("\\[hy]", TOKEN_HYPHEN);
+ store_token("\\(en", TOKEN_RANGE_SEP);
+ store_token("\\[en]", TOKEN_RANGE_SEP);
+}
+
+static void init_strings()
+{
+ char buf[6];
+ buf[0] = '\\';
+ buf[1] = '*';
+ for (const char *p = "'`^^,:~v_o./;"; *p; p++) {
+ buf[2] = *p;
+ buf[3] = '\0';
+ store_token(strsave(buf), TOKEN_ACCENT);
+ buf[2] = '[';
+ buf[3] = *p;
+ buf[4] = ']';
+ buf[5] = '\0';
+ store_token(strsave(buf), TOKEN_ACCENT);
+ }
+
+ // -ms special letters
+ store_letter("\\*(th", "\\*(Th", THORN_SORT_KEY);
+ store_letter("\\*[th]", "\\*[Th]", THORN_SORT_KEY);
+ store_letter("\\*(d-", "\\*(D-");
+ store_letter("\\*[d-]", "\\*[D-]");
+ store_letter("\\*(ae", "\\*(Ae", "ae");
+ store_letter("\\*[ae]", "\\*[Ae]", "ae");
+ store_letter("\\*(oe", "\\*(Oe", "oe");
+ store_letter("\\*[oe]", "\\*[Oe]", "oe");
+
+ store_token("\\*3", TOKEN_LOWER, "y", "Y");
+ store_token("\\*8", TOKEN_LOWER, "ss", "SS");
+ store_token("\\*q", TOKEN_LOWER, "o", "O");
+}
+
+struct token_initer {
+ token_initer();
+};
+
+static token_initer the_token_initer;
+
+token_initer::token_initer()
+{
+ init_ascii();
+ init_latin1();
+ init_special_chars();
+ init_strings();
+ default_token_info.set(TOKEN_OTHER);
+}
diff --git a/src/preproc/refer/token.h b/src/preproc/refer/token.h
new file mode 100644
index 0000000..9cd688c
--- /dev/null
+++ b/src/preproc/refer/token.h
@@ -0,0 +1,87 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+enum token_type {
+ TOKEN_OTHER,
+ TOKEN_UPPER,
+ TOKEN_LOWER,
+ TOKEN_ACCENT,
+ TOKEN_PUNCT,
+ TOKEN_HYPHEN,
+ TOKEN_RANGE_SEP
+};
+
+class token_info {
+private:
+ token_type type;
+ const char *sort_key;
+ const char *other_case;
+public:
+ token_info();
+ void set(token_type, const char *sk = 0, const char *oc = 0);
+ void lower_case(const char *start, const char *end, string &result) const;
+ void upper_case(const char *start, const char *end, string &result) const;
+ void sortify(const char *start, const char *end, string &result) const;
+ int sortify_non_empty(const char *start, const char *end) const;
+ int is_upper() const;
+ int is_lower() const;
+ int is_accent() const;
+ int is_other() const;
+ int is_punct() const;
+ int is_hyphen() const;
+ int is_range_sep() const;
+};
+
+inline int token_info::is_upper() const
+{
+ return type == TOKEN_UPPER;
+}
+
+inline int token_info::is_lower() const
+{
+ return type == TOKEN_LOWER;
+}
+
+inline int token_info::is_accent() const
+{
+ return type == TOKEN_ACCENT;
+}
+
+inline int token_info::is_other() const
+{
+ return type == TOKEN_OTHER;
+}
+
+inline int token_info::is_punct() const
+{
+ return type == TOKEN_PUNCT;
+}
+
+inline int token_info::is_hyphen() const
+{
+ return type == TOKEN_HYPHEN;
+}
+
+inline int token_info::is_range_sep() const
+{
+ return type == TOKEN_RANGE_SEP;
+}
+
+int get_token(const char **ptr, const char *end);
+const token_info *lookup_token(const char *start, const char *end);
diff --git a/src/preproc/soelim/TODO b/src/preproc/soelim/TODO
new file mode 100644
index 0000000..f2a3924
--- /dev/null
+++ b/src/preproc/soelim/TODO
@@ -0,0 +1 @@
+Understand .pso.
diff --git a/src/preproc/soelim/soelim.1.man b/src/preproc/soelim/soelim.1.man
new file mode 100644
index 0000000..4a1c042
--- /dev/null
+++ b/src/preproc/soelim/soelim.1.man
@@ -0,0 +1,456 @@
+'\" p
+.TH @g@soelim @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@soelim \- recursively interpolate source requests in
+.I roff
+or other text files
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_soelim_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.\" Man pages are seldom preprocessed with pic(1).
+.mso pic.tmac
+.
+.
+.\" ====================================================================
+.\" Definitions
+.\" ====================================================================
+.
+.ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el .ds tx TeX
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@soelim
+.RB [ \-Crt ]
+.RB [ \-I
+.IR dir ]
+.RI [ input-file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@soelim
+.B \-\-help
+.YS
+.
+.
+.SY @g@soelim
+.B \-v
+.
+.SY @g@soelim
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+GNU
+.I soelim \" GNU
+is a preprocessor for the
+.MR groff @MAN7EXT@
+document formatting system.
+.
+.I @g@soelim
+works as a filter to eliminate source requests in
+.MR roff @MAN7EXT@
+input files;
+that is,
+it replaces lines of the form
+.RB \[lq] .so
+.IR included-file \[rq]
+within each text
+.I input-file
+with the contents of
+.IR included-file ,
+recursively.
+.
+By default,
+it writes
+.B lf
+requests as well to record the name and line number of each
+.I input-file
+and
+.IR included-file ,
+so that any diagnostics produced by later processing can be accurately
+traced to the original input.
+.
+Options allow this information to be suppressed
+.RB ( \-r )
+or supplied in \*[tx] comments instead
+.RB ( \-t ).
+.
+In the absence of
+.I input-file
+arguments,
+.I @g@soelim
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.PP
+If the name of a
+.I macro-file
+contains a backslash,
+use
+.B \[rs]\[rs]
+or
+.B \[rs]e
+to embed it.
+.
+To embed a space,
+write
+.RB \[lq] \[rs]\~ \[rq]
+(backslash followed by a space).
+.
+Any other escape sequence in
+.IR macro-file ,
+including
+.RB \[lq] \[rs][rs] \[rq],
+prevents
+.I @g@soelim
+from replacing the source request.
+.
+.
+.PP
+The dot must be at the beginning of a line and must be followed by
+.RB \[lq] so \[rq]
+without intervening spaces or tabs for
+.I @g@soelim
+to handle it.
+.
+This convention allows source requests to be \[lq]protected\[rq] from
+processing by
+.IR @g@soelim ,
+for instance as part of macro definitions or
+.RB \[lq] if \[rq]
+requests.
+.
+.
+.PP
+There must also be at least one space between
+.RB \[lq] so \[rq]
+and its
+.I macro-file
+argument.
+.
+The
+.B \-C
+option overrides this requirement.
+.
+.
+.PP
+The foregoing is the limit of
+.IR @g@soelim 's
+understanding of the
+.I roff
+language;
+it does not,
+for example,
+replace the input line
+.
+.RS
+.EX
+\&.if 1 .so otherfile
+.EE
+.RE
+.
+with the contents of
+.IR otherfile .
+.
+With its
+.B \-r
+option,
+therefore,
+.I @g@soelim
+can be used to process text files in general,
+to flatten a tree of input documents.
+.
+.
+.PP
+.I soelim \" generic
+was designed to handle situations where the target of a
+.I roff \" generic
+source request requires a preprocessor such as
+.MR @g@eqn @MAN1EXT@ ,
+.MR @g@pic @MAN1EXT@ ,
+.MR @g@refer @MAN1EXT@ ,
+or
+.MR @g@tbl @MAN1EXT@ .
+.
+The usual processing sequence of
+.MR groff @MAN1EXT@
+is as follows.
+.
+.\" Does this groff installation use a command prefix? In installed
+.\" pages, this comparison will not look like it needs to be dynamically
+.\" decided.
+.\"
+.\" This is done so that the box sizes (in the pic(1) diagram) and arrow
+.\" alignments (in the text alternative) can remain fixed.
+.if !'@g@'\%' \{\
+In the diagrams below,
+the traditional names for
+.I soelim
+and
+.I troff
+are used;
+on this system,
+the GNU versions are installed as
+.I @g@soelim
+and
+.IR @g@troff .
+.\}
+.
+.
+.PP
+.ie t \{\
+.PS
+.ps 10
+.vs 12
+box invisible width 0.5 height 0.4 "input" "file";
+move to last box .bottom;
+down;
+arrow 0.3;
+box invisible width 0.8 height 0.2 "preprocessor";
+move to last box .right
+right;
+arrow 0.3;
+A: box invisible width 0.35 height 0.2 "troff";
+move to last box .top;
+up;
+move 0.3;
+box invisible width 0.6 height 0.4 "sourced" "file";
+line <- up 0.3 from A.top;
+move to A.right;
+right;
+arrow 0.3;
+box invisible width 0.85 height 0.2 "postprocessor";
+move to last box .bottom;
+down;
+arrow 0.3;
+box invisible width 0.5 height 0.4 "output" "file"
+.ps
+.vs
+.PE
+.\}
+.el \{\
+.EX
+ input sourced
+ file file
+ \[bv] \[bv]
+ \[da] \[da]
+ preprocessor \[an]\[->] troff \[an]\[->] postprocessor
+ \[bv]
+ \[da]
+ output
+ file
+.EE
+.\}
+.PP
+That is,
+files sourced with
+.RB \[lq] so \[rq]
+are normally read
+.I only
+by the formatter,
+.IR @g@troff .
+.
+.I @g@soelim
+is
+.I not
+required for
+.I @g@troff
+to source files.
+.
+.
+.PP
+If a file to be sourced should also be preprocessed,
+it must already be read
+.I before
+the input file passes through the preprocessor.
+.
+.IR @g@soelim ,
+normally invoked via
+.IR groff 's
+.B \-s
+option,
+handles this.
+.
+.
+.PP
+.ie t \{\
+.PS
+.ps 10
+.vs 12
+box invisible width 0.5 height 0.4 "input" "file";
+move to last box .bottom;
+down;
+arrow 0.3;
+A: box invisible width 0.5 height 0.2 "soelim";
+line <- 0.3;
+box invisible width 0.5 height 0.4 "sourced" "file";
+move to A.right;
+right;
+arrow 0.3;
+box invisible width 0.8 height 0.2 "preprocessor";
+arrow 0.3;
+box invisible width 0.35 height 0.2 "troff";
+arrow 0.3
+box invisible width 0.85 height 0.2 "postprocessor";
+move to last box .bottom;
+down;
+arrow 0.3;
+box invisible width 0.5 height 0.4 "output" "file"
+.ps
+.vs
+.PE
+.\}
+.el \{\
+.EX
+ input
+ file
+ \[bv]
+ \[da]
+ soelim \[an]\[->] preprocessor \[an]\[->] troff \[an]\[->] \
+postprocessor
+ \[ua] \[bv]
+ \[bv] \[da]
+ sourced output
+ file file
+.EE
+.\}
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-C
+Recognize an input line starting with
+.B .so
+even if a character other than a space or newline follows.
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+path for
+.I input-
+and
+.I included-files.
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.
+.TP
+.B \-r
+Write files \[lq]raw\[rq];
+do not add
+.B lf
+requests.
+.
+.
+.TP
+.B \-t
+Emit \*[tx] comment lines starting with
+.RB \[lq] % \[rq]
+indicating the current file and line number,
+rather than
+.B lf
+requests for the same purpose.
+.
+.
+.PP
+If both
+.B \-r
+and
+.B \-t
+are given,
+the last one specified controls.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@
+.
+.
+.\" Clean up.
+.rm tx
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_soelim_1_man_C]
+.do rr *groff_soelim_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/preproc/soelim/soelim.am b/src/preproc/soelim/soelim.am
new file mode 100644
index 0000000..1aa7941
--- /dev/null
+++ b/src/preproc/soelim/soelim.am
@@ -0,0 +1,31 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += soelim
+soelim_LDADD = libgroff.a $(LIBM) lib/libgnu.a
+soelim_SOURCES = src/preproc/soelim/soelim.cpp
+PREFIXMAN1 += src/preproc/soelim/soelim.1
+EXTRA_DIST += \
+ src/preproc/soelim/TODO \
+ src/preproc/soelim/soelim.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/soelim/soelim.cpp b/src/preproc/soelim/soelim.cpp
new file mode 100644
index 0000000..bafc5cd
--- /dev/null
+++ b/src/preproc/soelim/soelim.cpp
@@ -0,0 +1,315 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "nonposix.h"
+#include "searchpath.h"
+#include "lf.h"
+
+// The include search path initially contains only the current directory.
+static search_path include_search_path(0, 0, 0, 1);
+
+int compatible_flag = 0;
+int raw_flag = 0;
+int tex_flag = 0;
+
+extern "C" const char *Version_string;
+
+int do_file(const char *);
+
+
+void usage(FILE *stream)
+{
+ fprintf(stream, "usage: %s [-Crt] [-I dir] [file ...]\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "CI:rtv", long_options, NULL)) != EOF)
+ switch (opt) {
+ case 'v':
+ printf("GNU soelim (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'C':
+ compatible_flag = 1;
+ break;
+ case 'I':
+ include_search_path.command_line_dir(optarg);
+ break;
+ case 'r':
+ raw_flag = 1;
+ break;
+ case 't':
+ tex_flag = 1;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ int nbad = 0;
+ if (optind >= argc)
+ nbad += !do_file("-");
+ else
+ for (int i = optind; i < argc; i++)
+ nbad += !do_file(argv[i]);
+ if (ferror(stdout) || fflush(stdout) < 0)
+ fatal("output error");
+ return nbad != 0;
+}
+
+void set_location()
+{
+ if (!raw_flag) {
+ if (!tex_flag)
+ printf(".lf %d %s\n", current_lineno, current_filename);
+ else
+ printf("%% file %s, line %d\n", current_filename, current_lineno);
+ }
+}
+
+void do_so(const char *line)
+{
+ const char *p = line;
+ while (*p == ' ')
+ p++;
+ string filename;
+ int success = 1;
+ for (const char *q = p;
+ success && *q != '\0' && *q != '\n' && *q != ' ';
+ q++)
+ if (*q == '\\') {
+ switch (*++q) {
+ case 'e':
+ case '\\':
+ filename += '\\';
+ break;
+ case ' ':
+ filename += ' ';
+ break;
+ default:
+ success = 0;
+ break;
+ }
+ }
+ else
+ filename += char(*q);
+ if (success && filename.length() > 0) {
+ filename += '\0';
+ const char *fn = current_filename;
+ int ln = current_lineno;
+ current_lineno--;
+ if (do_file(filename.contents())) {
+ current_filename = fn;
+ current_lineno = ln;
+ set_location();
+ return;
+ }
+ current_lineno++;
+ }
+ fputs(".so", stdout);
+ fputs(line, stdout);
+}
+
+int do_file(const char *filename)
+{
+ char *file_name_in_path = 0;
+ FILE *fp = include_search_path.open_file_cautious(filename,
+ &file_name_in_path);
+ int err = errno;
+ string whole_filename(file_name_in_path ? file_name_in_path : filename);
+ whole_filename += '\0';
+ free(file_name_in_path);
+ if (fp == 0) {
+ error("can't open '%1': %2", whole_filename.contents(), strerror(err));
+ return 0;
+ }
+ normalize_for_lf(whole_filename);
+ current_filename = whole_filename.contents();
+ current_lineno = 1;
+ set_location();
+ enum { START, MIDDLE, HAD_DOT, HAD_s, HAD_so, HAD_l, HAD_lf } state = START;
+ for (;;) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ switch (state) {
+ case START:
+ if (c == '.')
+ state = HAD_DOT;
+ else {
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case MIDDLE:
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ break;
+ case HAD_DOT:
+ if (c == 's')
+ state = HAD_s;
+ else if (c == 'l')
+ state = HAD_l;
+ else {
+ putchar('.');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_s:
+ if (c == 'o')
+ state = HAD_so;
+ else {
+ putchar('.');
+ putchar('s');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_so:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ string line;
+ for (; c != EOF && c != '\n'; c = getc(fp))
+ line += c;
+ current_lineno++;
+ line += '\n';
+ line += '\0';
+ do_so(line.contents());
+ state = START;
+ }
+ else {
+ fputs(".so", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ case HAD_l:
+ if (c == 'f')
+ state = HAD_lf;
+ else {
+ putchar('.');
+ putchar('l');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_lf:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ string line;
+ for (; c != EOF && c != '\n'; c = getc(fp))
+ line += c;
+ current_lineno++;
+ line += '\n';
+ line += '\0';
+ interpret_lf_args(line.contents());
+ printf(".lf%s", line.contents());
+ state = START;
+ }
+ else {
+ fputs(".lf", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ default:
+ assert(0);
+ }
+ }
+ switch (state) {
+ case HAD_DOT:
+ fputs(".\n", stdout);
+ break;
+ case HAD_l:
+ fputs(".l\n", stdout);
+ break;
+ case HAD_s:
+ fputs(".s\n", stdout);
+ break;
+ case HAD_lf:
+ fputs(".lf\n", stdout);
+ break;
+ case HAD_so:
+ fputs(".so\n", stdout);
+ break;
+ case MIDDLE:
+ putc('\n', stdout);
+ break;
+ case START:
+ break;
+ }
+ if (fp != stdin)
+ fclose(fp);
+ current_filename = 0;
+ return 1;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/tbl/main.cpp b/src/preproc/tbl/main.cpp
new file mode 100644
index 0000000..db105c2
--- /dev/null
+++ b/src/preproc/tbl/main.cpp
@@ -0,0 +1,1692 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "table.h"
+
+#define MAX_POINT_SIZE 99
+#define MAX_VERTICAL_SPACING 72
+
+extern "C" const char *Version_string;
+
+int compatible_flag = 0;
+
+class table_input {
+ FILE *fp;
+ enum { START, MIDDLE,
+ REREAD_T, REREAD_TE, REREAD_E,
+ LEADER_1, LEADER_2, LEADER_3, LEADER_4,
+ END, ERROR } state;
+ string unget_stack;
+public:
+ table_input(FILE *);
+ int get();
+ int ended() { return unget_stack.empty() && state == END; }
+ void unget(char);
+};
+
+table_input::table_input(FILE *p)
+: fp(p), state(START)
+{
+}
+
+void table_input::unget(char c)
+{
+ assert(c != '\0');
+ unget_stack += c;
+ if (c == '\n')
+ current_lineno--;
+}
+
+int table_input::get()
+{
+ int len = unget_stack.length();
+ if (len != 0) {
+ unsigned char c = unget_stack[len - 1];
+ unget_stack.set_length(len - 1);
+ if (c == '\n')
+ current_lineno++;
+ return c;
+ }
+ int c;
+ for (;;) {
+ switch (state) {
+ case START:
+ if ((c = getc(fp)) == '.') {
+ if ((c = getc(fp)) == 'T') {
+ if ((c = getc(fp)) == 'E') {
+ if (compatible_flag) {
+ state = END;
+ return EOF;
+ }
+ else {
+ c = getc(fp);
+ if (c != EOF)
+ ungetc(c, fp);
+ if (c == EOF || c == ' ' || c == '\n') {
+ state = END;
+ return EOF;
+ }
+ state = REREAD_TE;
+ return '.';
+ }
+ }
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ state = REREAD_T;
+ return '.';
+ }
+ }
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ state = MIDDLE;
+ return '.';
+ }
+ }
+ else if (c == EOF) {
+ state = ERROR;
+ return EOF;
+ }
+ else {
+ if (c == '\n')
+ current_lineno++;
+ else {
+ state = MIDDLE;
+ if (c == '\0') {
+ error("invalid input character code 0");
+ break;
+ }
+ }
+ return c;
+ }
+ break;
+ case MIDDLE:
+ // handle line continuation and uninterpreted leader character
+ if ((c = getc(fp)) == '\\') {
+ c = getc(fp);
+ if (c == '\n') {
+ current_lineno++;
+ c = getc(fp);
+ }
+ else if (c == 'a' && compatible_flag) {
+ state = LEADER_1;
+ return '\\';
+ }
+ else {
+ if (c != EOF)
+ ungetc(c, fp);
+ c = '\\';
+ }
+ }
+ if (c == EOF) {
+ state = ERROR;
+ return EOF;
+ }
+ else {
+ if (c == '\n') {
+ state = START;
+ current_lineno++;
+ }
+ else if (c == '\0') {
+ error("invalid input character code 0");
+ break;
+ }
+ return c;
+ }
+ case REREAD_T:
+ state = MIDDLE;
+ return 'T';
+ case REREAD_TE:
+ state = REREAD_E;
+ return 'T';
+ case REREAD_E:
+ state = MIDDLE;
+ return 'E';
+ case LEADER_1:
+ state = LEADER_2;
+ return '*';
+ case LEADER_2:
+ state = LEADER_3;
+ return '(';
+ case LEADER_3:
+ state = LEADER_4;
+ return PREFIX_CHAR;
+ case LEADER_4:
+ state = MIDDLE;
+ return LEADER_CHAR;
+ case END:
+ case ERROR:
+ return EOF;
+ }
+ }
+}
+
+void process_input_file(FILE *);
+void process_table(table_input &in);
+
+void process_input_file(FILE *fp)
+{
+ enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
+ state = START;
+ int c;
+ while ((c = getc(fp)) != EOF)
+ switch (state) {
+ case START:
+ if (c == '.')
+ state = HAD_DOT;
+ else {
+ if (c == '\n')
+ current_lineno++;
+ else
+ state = MIDDLE;
+ putchar(c);
+ }
+ break;
+ case MIDDLE:
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ putchar(c);
+ break;
+ case HAD_DOT:
+ if (c == 'T')
+ state = HAD_T;
+ else if (c == 'l')
+ state = HAD_l;
+ else {
+ putchar('.');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_T:
+ if (c == 'S')
+ state = HAD_TS;
+ else {
+ putchar('.');
+ putchar('T');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_TS:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ putchar('.');
+ putchar('T');
+ putchar('S');
+ while (c != '\n') {
+ if (c == EOF) {
+ error("end of file at beginning of table");
+ return;
+ }
+ putchar(c);
+ c = getc(fp);
+ }
+ putchar('\n');
+ current_lineno++;
+ {
+ table_input input(fp);
+ process_table(input);
+ set_troff_location(current_filename, current_lineno);
+ if (input.ended()) {
+ fputs(".TE", stdout);
+ while ((c = getc(fp)) != '\n') {
+ if (c == EOF) {
+ putchar('\n');
+ return;
+ }
+ putchar(c);
+ }
+ putchar('\n');
+ current_lineno++;
+ }
+ }
+ state = START;
+ }
+ else {
+ fputs(".TS", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ case HAD_l:
+ if (c == 'f')
+ state = HAD_lf;
+ else {
+ putchar('.');
+ putchar('l');
+ putchar(c);
+ if (c == '\n') {
+ current_lineno++;
+ state = START;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case HAD_lf:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ string line;
+ while (c != EOF) {
+ line += c;
+ if (c == '\n') {
+ current_lineno++;
+ break;
+ }
+ c = getc(fp);
+ }
+ line += '\0';
+ interpret_lf_args(line.contents());
+ printf(".lf%s", line.contents());
+ state = START;
+ }
+ else {
+ fputs(".lf", stdout);
+ putchar(c);
+ state = MIDDLE;
+ }
+ break;
+ default:
+ assert(0 == "invalid `state` in switch");
+ }
+ switch(state) {
+ case START:
+ break;
+ case MIDDLE:
+ putchar('\n');
+ break;
+ case HAD_DOT:
+ fputs(".\n", stdout);
+ break;
+ case HAD_l:
+ fputs(".l\n", stdout);
+ break;
+ case HAD_T:
+ fputs(".T\n", stdout);
+ break;
+ case HAD_lf:
+ fputs(".lf\n", stdout);
+ break;
+ case HAD_TS:
+ fputs(".TS\n", stdout);
+ break;
+ }
+ if (fp != stdin)
+ fclose(fp);
+}
+
+struct options {
+ unsigned flags;
+ int linesize;
+ char delim[2];
+ char tab_char;
+ char decimal_point_char;
+
+ options();
+};
+
+options::options()
+: flags(0), linesize(0), tab_char('\t'), decimal_point_char('.')
+{
+ delim[0] = delim[1] = '\0';
+}
+
+// Return non-zero if p and q are the same ignoring case.
+
+int strieq(const char *p, const char *q)
+{
+ for (; cmlower(*p) == cmlower(*q); p++, q++)
+ if (*p == '\0')
+ return 1;
+ return 0;
+}
+
+// Handle region options. Return a null pointer if we should give up on
+// this table.
+options *process_options(table_input &in)
+{
+ options *opt = new options;
+ string line;
+ int level = 0;
+ for (;;) {
+ int c = in.get();
+ if (c == EOF) {
+ int i = line.length();
+ while (--i >= 0)
+ in.unget(line[i]);
+ return opt;
+ }
+ if (c == '\n') {
+ in.unget(c);
+ int i = line.length();
+ while (--i >= 0)
+ in.unget(line[i]);
+ return opt;
+ }
+ else if (c == '(')
+ level++;
+ else if (c == ')')
+ level--;
+ else if (c == ';' && 0 == level) {
+ line += '\0';
+ break;
+ }
+ line += c;
+ }
+ if (line.empty())
+ return opt;
+ char *p = &line[0];
+ for (;;) {
+ while (!csalpha(*p) && *p != '\0')
+ p++;
+ if (*p == '\0')
+ break;
+ char *q = p;
+ while (csalpha(*q))
+ q++;
+ char *arg = 0;
+ if (*q != '(' && *q != '\0')
+ *q++ = '\0';
+ while (csspace(*q))
+ q++;
+ if (*q == '(') {
+ *q++ = '\0';
+ arg = q;
+ while (*q != ')' && *q != '\0')
+ q++;
+ if (*q == '\0')
+ error("'%1' region option argument missing closing parenthesis",
+ arg);
+ else
+ *q++ = '\0';
+ }
+ if (*p == '\0') {
+ if (arg)
+ error("'%1' region option argument cannot be empty", arg);
+ }
+ else if (strieq(p, "tab")) {
+ if (!arg)
+ error("'tab' region option requires argument in parentheses");
+ else {
+ if (arg[0] == '\0' || arg[1] != '\0')
+ error("'tab' region option argument must be a single"
+ " character");
+ else
+ opt->tab_char = arg[0];
+ }
+ }
+ else if (strieq(p, "linesize")) {
+ if (!arg)
+ error("'linesize' region option requires argument in"
+ " parentheses");
+ else {
+ if (sscanf(arg, "%d", &opt->linesize) != 1)
+ error("invalid argument to 'linesize' region option: '%1'",
+ arg);
+ else if (opt->linesize <= 0) {
+ error("'linesize' region option argument must be positive");
+ opt->linesize = 0;
+ }
+ }
+ }
+ else if (strieq(p, "delim")) {
+ if (!arg)
+ error("'delim' region option requires argument in parentheses");
+ else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
+ error("argument to 'delim' option must be two characters");
+ else {
+ opt->delim[0] = arg[0];
+ opt->delim[1] = arg[1];
+ }
+ }
+ else if (strieq(p, "center") || strieq(p, "centre")) {
+ if (arg)
+ error("'center' region option does not take an argument");
+ opt->flags |= table::CENTER;
+ }
+ else if (strieq(p, "expand")) {
+ if (arg)
+ error("'expand' region option does not take an argument");
+ opt->flags |= table::EXPAND;
+ opt->flags |= table::GAP_EXPAND;
+ }
+ else if (strieq(p, "box") || strieq(p, "frame")) {
+ if (arg)
+ error("'box' region option does not take an argument");
+ opt->flags |= table::BOX;
+ }
+ else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
+ if (arg)
+ error("'doublebox' region option does not take an argument");
+ opt->flags |= table::DOUBLEBOX;
+ }
+ else if (strieq(p, "allbox")) {
+ if (arg)
+ error("'allbox' region option does not take an argument");
+ opt->flags |= table::ALLBOX;
+ }
+ else if (strieq(p, "nokeep")) {
+ if (arg)
+ error("'nokeep' region option does not take an argument");
+ opt->flags |= table::NOKEEP;
+ }
+ else if (strieq(p, "nospaces")) {
+ if (arg)
+ error("'nospaces' region option does not take an argument");
+ opt->flags |= table::NOSPACES;
+ }
+ else if (strieq(p, "nowarn")) {
+ if (arg)
+ error("'nowarn' region option does not take an argument");
+ opt->flags |= table::NOWARN;
+ }
+ else if (strieq(p, "decimalpoint")) {
+ if (!arg)
+ error("'decimalpoint' region option requires argument in"
+ " parentheses");
+ else {
+ if (arg[0] == '\0' || arg[1] != '\0')
+ error("'decimalpoint' region option argument must be a single"
+ " character");
+ else
+ opt->decimal_point_char = arg[0];
+ }
+ }
+ else if (strieq(p, "experimental")) {
+ opt->flags |= table::EXPERIMENTAL;
+ }
+ else {
+ error("unrecognized region option '%1'", p);
+ // delete opt;
+ // return 0;
+ }
+ p = q;
+ }
+ return opt;
+}
+
+entry_modifier::entry_modifier()
+: vertical_alignment(CENTER), zero_width(0), stagger(0)
+{
+ vertical_spacing.inc = vertical_spacing.val = 0;
+ point_size.inc = point_size.val = 0;
+}
+
+entry_modifier::~entry_modifier()
+{
+}
+
+entry_format::entry_format() : type(FORMAT_LEFT)
+{
+}
+
+entry_format::entry_format(format_type t) : type(t)
+{
+}
+
+void entry_format::debug_print() const
+{
+ switch (type) {
+ case FORMAT_LEFT:
+ putc('l', stderr);
+ break;
+ case FORMAT_CENTER:
+ putc('c', stderr);
+ break;
+ case FORMAT_RIGHT:
+ putc('r', stderr);
+ break;
+ case FORMAT_NUMERIC:
+ putc('n', stderr);
+ break;
+ case FORMAT_ALPHABETIC:
+ putc('a', stderr);
+ break;
+ case FORMAT_SPAN:
+ putc('s', stderr);
+ break;
+ case FORMAT_VSPAN:
+ putc('^', stderr);
+ break;
+ case FORMAT_HLINE:
+ putc('_', stderr);
+ break;
+ case FORMAT_DOUBLE_HLINE:
+ putc('=', stderr);
+ break;
+ default:
+ assert(0 == "invalid column classifier in switch");
+ break;
+ }
+ if (point_size.val != 0) {
+ putc('p', stderr);
+ if (point_size.inc > 0)
+ putc('+', stderr);
+ else if (point_size.inc < 0)
+ putc('-', stderr);
+ fprintf(stderr, "%d ", point_size.val);
+ }
+ if (vertical_spacing.val != 0) {
+ putc('v', stderr);
+ if (vertical_spacing.inc > 0)
+ putc('+', stderr);
+ else if (vertical_spacing.inc < 0)
+ putc('-', stderr);
+ fprintf(stderr, "%d ", vertical_spacing.val);
+ }
+ if (!font.empty()) {
+ putc('f', stderr);
+ put_string(font, stderr);
+ putc(' ', stderr);
+ }
+ if (!macro.empty()) {
+ putc('m', stderr);
+ put_string(macro, stderr);
+ putc(' ', stderr);
+ }
+ switch (vertical_alignment) {
+ case entry_modifier::CENTER:
+ break;
+ case entry_modifier::TOP:
+ putc('t', stderr);
+ break;
+ case entry_modifier::BOTTOM:
+ putc('d', stderr);
+ break;
+ }
+ if (zero_width)
+ putc('z', stderr);
+ if (stagger)
+ putc('u', stderr);
+}
+
+struct format {
+ int nrows;
+ int ncolumns;
+ int *separation;
+ string *width;
+ char *equal;
+ char *expand;
+ entry_format **entry;
+ char **vline;
+
+ format(int nr, int nc);
+ ~format();
+ void add_rows(int n);
+};
+
+format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
+{
+ int i;
+ separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
+ for (i = 0; i < ncolumns-1; i++)
+ separation[i] = -1;
+ width = new string[ncolumns];
+ equal = new char[ncolumns];
+ expand = new char[ncolumns];
+ for (i = 0; i < ncolumns; i++) {
+ equal[i] = 0;
+ expand[i] = 0;
+ }
+ entry = new entry_format *[nrows];
+ for (i = 0; i < nrows; i++)
+ entry[i] = new entry_format[ncolumns];
+ vline = new char*[nrows];
+ for (i = 0; i < nrows; i++) {
+ vline[i] = new char[ncolumns+1];
+ for (int j = 0; j < ncolumns+1; j++)
+ vline[i][j] = 0;
+ }
+}
+
+void format::add_rows(int n)
+{
+ int i;
+ char **old_vline = vline;
+ vline = new char*[nrows + n];
+ for (i = 0; i < nrows; i++)
+ vline[i] = old_vline[i];
+ delete[] old_vline;
+ for (i = 0; i < n; i++) {
+ vline[nrows + i] = new char[ncolumns + 1];
+ for (int j = 0; j < ncolumns + 1; j++)
+ vline[nrows + i][j] = 0;
+ }
+ entry_format **old_entry = entry;
+ entry = new entry_format *[nrows + n];
+ for (i = 0; i < nrows; i++)
+ entry[i] = old_entry[i];
+ delete[] old_entry;
+ for (i = 0; i < n; i++)
+ entry[nrows + i] = new entry_format[ncolumns];
+ nrows += n;
+}
+
+format::~format()
+{
+ delete[] separation;
+ delete[] width;
+ delete[] equal;
+ delete[] expand;
+ for (int i = 0; i < nrows; i++) {
+ delete[] vline[i];
+ delete[] entry[i];
+ }
+ delete[] vline;
+ delete[] entry;
+}
+
+struct input_entry_format : public entry_format {
+ input_entry_format *next;
+ string width;
+ int separation;
+ int vline;
+ int vline_count;
+ bool is_last_column;
+ bool is_equal_width;
+ int expand;
+ input_entry_format(format_type, input_entry_format * = 0);
+ ~input_entry_format();
+ void debug_print();
+};
+
+input_entry_format::input_entry_format(format_type t, input_entry_format *p)
+: entry_format(t), next(p)
+{
+ separation = -1;
+ is_last_column = false;
+ vline = 0;
+ vline_count = 0;
+ is_equal_width = false;
+ expand = 0;
+}
+
+input_entry_format::~input_entry_format()
+{
+}
+
+void free_input_entry_format_list(input_entry_format *list)
+{
+ while (list) {
+ input_entry_format *tem = list;
+ list = list->next;
+ delete tem;
+ }
+}
+
+void input_entry_format::debug_print()
+{
+ int i;
+ for (i = 0; i < vline_count; i++)
+ putc('|', stderr);
+ entry_format::debug_print();
+ if (!width.empty()) {
+ putc('w', stderr);
+ putc('(', stderr);
+ put_string(width, stderr);
+ putc(')', stderr);
+ }
+ if (is_equal_width)
+ putc('e', stderr);
+ if (expand)
+ putc('x', stderr);
+ if (separation >= 0)
+ fprintf(stderr, "%d", separation);
+ for (i = 0; i < vline; i++)
+ putc('|', stderr);
+ if (is_last_column)
+ putc(',', stderr);
+}
+
+// Interpret a table format specification, like "CC,LR.". Return null
+// pointer if we should give up on this table. If this is a
+// continuation format line, `current_format` will be the current format
+// line.
+format *process_format(table_input &in, options *opt,
+ format *current_format = 0)
+{
+ input_entry_format *list = 0 /* nullptr */;
+ bool have_expand = false;
+ bool is_first_row = true;
+ int c = in.get();
+ for (;;) {
+ int vline_count = 0;
+ bool got_format = false;
+ bool got_period = false;
+ format_type t = FORMAT_LEFT;
+ for (;;) {
+ if (c == EOF) {
+ error("end of input while processing table format"
+ " specification");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ switch (c) {
+ case 'n':
+ case 'N':
+ t = FORMAT_NUMERIC;
+ got_format = true;
+ break;
+ case 'a':
+ case 'A':
+ got_format = true;
+ t = FORMAT_ALPHABETIC;
+ break;
+ case 'c':
+ case 'C':
+ got_format = true;
+ t = FORMAT_CENTER;
+ break;
+ case 'l':
+ case 'L':
+ got_format = true;
+ t = FORMAT_LEFT;
+ break;
+ case 'r':
+ case 'R':
+ got_format = true;
+ t = FORMAT_RIGHT;
+ break;
+ case 's':
+ case 'S':
+ got_format = true;
+ t = FORMAT_SPAN;
+ break;
+ case '^':
+ got_format = true;
+ t = FORMAT_VSPAN;
+ break;
+ case '_':
+ case '-': // tbl also accepts this
+ got_format = true;
+ t = FORMAT_HLINE;
+ if (is_first_row)
+ opt->flags |= table::HAS_TOP_HLINE;
+ break;
+ case '=':
+ got_format = true;
+ t = FORMAT_DOUBLE_HLINE;
+ break;
+ case '.':
+ got_period = true;
+ break;
+ case '|':
+ // leading vertical line in row
+ opt->flags |= table::HAS_TOP_VLINE;
+ vline_count++;
+ // list->vline_count is updated later
+ break;
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+ default:
+ if (c == opt->tab_char)
+ break;
+ error("invalid column classifier '%1'", char(c));
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ if (got_period)
+ break;
+ c = in.get();
+ if (got_format)
+ break;
+ }
+ if (got_period)
+ break;
+ list = new input_entry_format(t, list);
+ if (vline_count > 2) {
+ vline_count = 2;
+ error("more than 2 vertical lines at beginning of row description");
+ }
+ list->vline_count = vline_count;
+ // Now handle modifiers.
+ vline_count = 0;
+ bool is_valid_modifier_sequence = true;
+ do {
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ int w = 0;
+ do {
+ w = w*10 + (c - '0');
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ list->separation = w;
+ }
+ break;
+ case 'B':
+ case 'b':
+ c = in.get();
+ list->font = "B";
+ break;
+ case 'd':
+ case 'D':
+ c = in.get();
+ list->vertical_alignment = entry_modifier::BOTTOM;
+ break;
+ case 'e':
+ case 'E':
+ c = in.get();
+ list->is_equal_width = true;
+ // 'e' and 'x' are mutually exclusive
+ list->expand = 0;
+ break;
+ case 'f':
+ case 'F':
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c == EOF) {
+ error("'f' column modifier missing font name or mounting"
+ " position");
+ break;
+ }
+ if (c == '(') {
+ for (;;) {
+ c = in.get();
+ if (c == EOF || c == ' ' || c == '\t') {
+ error("'f' column modifier missing closing parenthesis");
+ break;
+ }
+ if (c == ')') {
+ c = in.get();
+ break;
+ }
+ list->font += char(c);
+ }
+ }
+ else {
+ list->font = c;
+ char cc = c;
+ c = in.get();
+ if (!csdigit(cc)
+ && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
+ list->font += char(c);
+ c = in.get();
+ }
+ }
+ break;
+ case 'I':
+ case 'i':
+ c = in.get();
+ list->font = "I";
+ break;
+ case 'm':
+ case 'M':
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c == EOF) {
+ error("'m' column modifier missing macro name");
+ break;
+ }
+ if (c == '(') {
+ for (;;) {
+ c = in.get();
+ if (c == EOF || c == ' ' || c == '\t') {
+ error("'m' column modifier missing closing parenthesis");
+ break;
+ }
+ if (c == ')') {
+ c = in.get();
+ break;
+ }
+ list->macro += char(c);
+ }
+ }
+ else {
+ list->macro = c;
+ char cc = c;
+ c = in.get();
+ if (!csdigit(cc)
+ && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
+ list->macro += char(c);
+ c = in.get();
+ }
+ }
+ break;
+ case 'p':
+ case 'P':
+ {
+ inc_number &ps = list->point_size;
+ ps.val = 0;
+ ps.inc = 0;
+ c = in.get();
+ if (c == '+' || c == '-') {
+ ps.inc = (c == '+' ? 1 : -1);
+ c = in.get();
+ }
+ if (c == EOF || !csdigit(c)) {
+ warning("'p' column modifier must be followed by"
+ " (optionally signed) integer; ignoring");
+ ps.inc = 0;
+ }
+ else {
+ do {
+ ps.val *= 10;
+ ps.val += c - '0';
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ if (ps.val > MAX_POINT_SIZE || ps.val < -MAX_POINT_SIZE) {
+ warning("'p' column modifier argument magnitude of %1"
+ " points out of range (> %2); ignoring", ps.val,
+ MAX_POINT_SIZE);
+ ps.val = 0;
+ ps.inc = 0;
+ }
+ break;
+ }
+ case 't':
+ case 'T':
+ c = in.get();
+ list->vertical_alignment = entry_modifier::TOP;
+ break;
+ case 'u':
+ case 'U':
+ c = in.get();
+ list->stagger = 1;
+ break;
+ case 'v':
+ case 'V':
+ {
+ inc_number &vs = list->vertical_spacing;
+ vs.val = 0;
+ vs.inc = 0;
+ c = in.get();
+ if (c == '+' || c == '-') {
+ vs.inc = (c == '+' ? 1 : -1);
+ c = in.get();
+ }
+ if (c == EOF || !csdigit(c)) {
+ warning("'v' column modifier must be followed by"
+ " (optionally signed) integer; ignoring");
+ vs.inc = 0;
+ }
+ else {
+ do {
+ vs.val *= 10;
+ vs.val += c - '0';
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ if (vs.val > MAX_VERTICAL_SPACING
+ || vs.val < -MAX_VERTICAL_SPACING) {
+ warning("'v' column modifier argument magnitude of %1"
+ " points out of range (> %2); ignoring", vs.val,
+ MAX_VERTICAL_SPACING);
+ vs.val = 0;
+ vs.inc = 0;
+ }
+ break;
+ }
+ case 'w':
+ case 'W':
+ c = in.get();
+ while (c == ' ' || c == '\t')
+ c = in.get();
+ if (c == '(') {
+ list->width = "";
+ c = in.get();
+ while (c != ')') {
+ if (c == EOF || c == '\n') {
+ error("'w' column modifier missing closing parenthesis");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ list->width += c;
+ c = in.get();
+ }
+ c = in.get();
+ }
+ else {
+ if (c == '+' || c == '-') {
+ list->width = char(c);
+ c = in.get();
+ }
+ else
+ list->width = "";
+ if (c == EOF || !csdigit(c))
+ error("invalid argument to 'w' modifier");
+ else {
+ do {
+ list->width += char(c);
+ c = in.get();
+ } while (c != EOF && csdigit(c));
+ }
+ }
+ // 'w' and 'x' are mutually exclusive
+ list->expand = 0;
+ break;
+ case 'x':
+ case 'X':
+ c = in.get();
+ list->expand = 1;
+ // 'x' and 'e' are mutually exclusive
+ list->is_equal_width = false;
+ // 'x' and 'w' are mutually exclusive
+ list->width = "";
+ break;
+ case 'z':
+ case 'Z':
+ c = in.get();
+ list->zero_width = 1;
+ break;
+ case '|':
+ if (is_first_row)
+ opt->flags |= table::HAS_TOP_VLINE;
+ c = in.get();
+ vline_count++;
+ break;
+ case ' ':
+ case '\t':
+ c = in.get();
+ break;
+ default:
+ if (c == opt->tab_char)
+ c = in.get();
+ else
+ is_valid_modifier_sequence = false;
+ break;
+ }
+ } while (is_valid_modifier_sequence);
+ if (vline_count > 2) {
+ vline_count = 2;
+ error("more than 2 vertical lines after column descriptor");
+ }
+ list->vline += vline_count;
+ if (c == '\n' || c == ',') {
+ vline_count = 0;
+ is_first_row = false;
+ c = in.get();
+ list->is_last_column = true;
+ }
+ }
+ if (c == '.') {
+ do {
+ c = in.get();
+ } while (c == ' ' || c == '\t');
+ if (c != '\n') {
+ error("'.' is not the last character of the table format");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ }
+ if (!list) {
+ error("table format specification is empty");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ list->is_last_column = true;
+ // now reverse the list so that the first row is at the beginning
+ input_entry_format *rev = 0;
+ while (list != 0) {
+ input_entry_format *tem = list->next;
+ list->next = rev;
+ rev = list;
+ list = tem;
+ }
+ list = rev;
+ input_entry_format *tem;
+
+#if 0
+ for (tem = list; tem; tem = tem->next)
+ tem->debug_print();
+ putc('\n', stderr);
+#endif
+ // compute number of columns and rows
+ int ncolumns = 0;
+ int nrows = 0;
+ int col = 0;
+ for (tem = list; tem; tem = tem->next) {
+ if (tem->is_last_column) {
+ if (col >= ncolumns)
+ ncolumns = col + 1;
+ col = 0;
+ nrows++;
+ }
+ else
+ col++;
+ }
+ int row;
+ format *f;
+ if (current_format) {
+ if (ncolumns > current_format->ncolumns) {
+ error("cannot increase the number of columns in a continued format");
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ return 0 /* nullptr */;
+ }
+ f = current_format;
+ row = f->nrows;
+ f->add_rows(nrows);
+ }
+ else {
+ f = new format(nrows, ncolumns);
+ row = 0;
+ }
+ col = 0;
+ for (tem = list; tem; tem = tem->next) {
+ f->entry[row][col] = *tem;
+ if (col < ncolumns - 1) {
+ // use the greatest separation
+ if (tem->separation > f->separation[col]) {
+ if (current_format)
+ error("cannot change column separation in continued format");
+ else
+ f->separation[col] = tem->separation;
+ }
+ }
+ else if (tem->separation >= 0)
+ error("column separation specified for last column");
+ if (tem->is_equal_width && !f->equal[col]) {
+ if (current_format)
+ error("cannot change which columns are equal in continued format");
+ else
+ f->equal[col] = 1;
+ }
+ if (tem->expand && !f->expand[col]) {
+ if (current_format)
+ error("cannot change which columns are expanded in continued format");
+ else {
+ f->expand[col] = 1;
+ have_expand = true;
+ }
+ }
+ if (!tem->width.empty()) {
+ // use the last width
+ if (!f->width[col].empty() && f->width[col] != tem->width)
+ error("multiple widths for column %1", col + 1);
+ f->width[col] = tem->width;
+ }
+ if (tem->vline_count)
+ f->vline[row][col] = tem->vline_count;
+ f->vline[row][col + 1] = tem->vline;
+ if (tem->is_last_column) {
+ row++;
+ col = 0;
+ }
+ else
+ col++;
+ }
+ free_input_entry_format_list(list);
+ list = 0 /* nullptr */;
+ for (col = 0; col < ncolumns; col++) {
+ entry_format *e = f->entry[f->nrows - 1] + col;
+ if (e->type != FORMAT_HLINE
+ && e->type != FORMAT_DOUBLE_HLINE
+ && e->type != FORMAT_SPAN)
+ break;
+ }
+ if (col >= ncolumns) {
+ error("last row of format is all lines");
+ delete f;
+ return 0 /* nullptr */;
+ }
+ if (have_expand && (opt->flags & table::EXPAND)) {
+ error("'x' column modifier encountered; ignoring region option"
+ " 'expand'");
+ opt->flags &= ~table::EXPAND;
+ }
+ return f;
+}
+
+table *process_data(table_input &in, format *f, options *opt)
+{
+ char tab_char = opt->tab_char;
+ int ncolumns = f->ncolumns;
+ int current_row = 0;
+ int format_index = 0;
+ bool give_up = false;
+ enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
+ table *tbl = new table(ncolumns, opt->flags, opt->linesize,
+ opt->decimal_point_char);
+ if (opt->delim[0] != '\0')
+ tbl->set_delim(opt->delim[0], opt->delim[1]);
+ for (;;) {
+ // first determine what type of line this is
+ int c = in.get();
+ if (c == EOF)
+ break;
+ if (c == '.') {
+ int d = in.get();
+ if (d != EOF && csdigit(d)) {
+ in.unget(d);
+ type = DATA_INPUT_LINE;
+ }
+ else {
+ in.unget(d);
+ type = TROFF_INPUT_LINE;
+ }
+ }
+ else if (c == '_' || c == '=') {
+ int d = in.get();
+ if (d == '\n') {
+ if (c == '_')
+ type = SINGLE_HLINE;
+ else
+ type = DOUBLE_HLINE;
+ if (0 == current_row)
+ tbl->flags |= table::HAS_TOP_HLINE;
+ }
+ else {
+ in.unget(d);
+ type = DATA_INPUT_LINE;
+ }
+ }
+ else {
+ type = DATA_INPUT_LINE;
+ }
+ switch (type) {
+ case DATA_INPUT_LINE:
+ {
+ string input_entry;
+ if (format_index >= f->nrows)
+ format_index = f->nrows - 1;
+ // A format row that is all lines doesn't use up a data line.
+ while (format_index < f->nrows - 1) {
+ int cnt;
+ for (cnt = 0; cnt < ncolumns; cnt++) {
+ entry_format *e = f->entry[format_index] + cnt;
+ if (e->type != FORMAT_HLINE
+ && e->type != FORMAT_DOUBLE_HLINE
+ // Unfortunately tbl treats a span as needing data.
+ // && e->type != FORMAT_SPAN
+ )
+ break;
+ }
+ if (cnt < ncolumns)
+ break;
+ for (cnt = 0; cnt < ncolumns; cnt++)
+ tbl->add_entry(current_row, cnt, input_entry,
+ f->entry[format_index] + cnt, current_filename,
+ current_lineno);
+ tbl->add_vlines(current_row, f->vline[format_index]);
+ format_index++;
+ current_row++;
+ }
+ entry_format *line_format = f->entry[format_index];
+ int col = 0;
+ bool seen_row_comment = false;
+ for (;;) {
+ if (c == tab_char || c == '\n') {
+ int ln = current_lineno;
+ if (c == '\n')
+ --ln;
+ if ((opt->flags & table::NOSPACES))
+ input_entry.remove_spaces();
+ while (col < ncolumns
+ && line_format[col].type == FORMAT_SPAN) {
+ tbl->add_entry(current_row, col, "", &line_format[col],
+ current_filename, ln);
+ col++;
+ }
+ if (c == '\n' && input_entry.length() == 2
+ && input_entry[0] == 'T' && input_entry[1] == '{') {
+ input_entry = "";
+ ln++;
+ enum {
+ START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
+ GOT_l, GOT_lf, END
+ } state = START;
+ while (state != END) {
+ c = in.get();
+ if (c == EOF)
+ break;
+ switch (state) {
+ case START:
+ if (c == 'T')
+ state = GOT_T;
+ else if (c == '.')
+ state = GOT_DOT;
+ else {
+ input_entry += c;
+ if (c != '\n')
+ state = MIDDLE;
+ }
+ break;
+ case GOT_T:
+ if (c == '}')
+ state = GOT_RIGHT_BRACE;
+ else {
+ input_entry += 'T';
+ input_entry += c;
+ state = c == '\n' ? START : MIDDLE;
+ }
+ break;
+ case GOT_DOT:
+ if (c == 'l')
+ state = GOT_l;
+ else {
+ input_entry += '.';
+ input_entry += c;
+ state = c == '\n' ? START : MIDDLE;
+ }
+ break;
+ case GOT_l:
+ if (c == 'f')
+ state = GOT_lf;
+ else {
+ input_entry += ".l";
+ input_entry += c;
+ state = c == '\n' ? START : MIDDLE;
+ }
+ break;
+ case GOT_lf:
+ if (c == ' ' || c == '\n' || compatible_flag) {
+ string args;
+ input_entry += ".lf";
+ while (c != EOF) {
+ args += c;
+ if (c == '\n')
+ break;
+ c = in.get();
+ }
+ args += '\0';
+ interpret_lf_args(args.contents());
+ // remove the '\0'
+ args.set_length(args.length() - 1);
+ input_entry += args;
+ state = START;
+ }
+ else {
+ input_entry += ".lf";
+ input_entry += c;
+ state = MIDDLE;
+ }
+ break;
+ case GOT_RIGHT_BRACE:
+ if ((opt->flags & table::NOSPACES)) {
+ while (c == ' ')
+ c = in.get();
+ if (c == EOF)
+ break;
+ }
+ if (c == '\n' || c == tab_char)
+ state = END;
+ else {
+ input_entry += 'T';
+ input_entry += '}';
+ input_entry += c;
+ state = MIDDLE;
+ }
+ break;
+ case MIDDLE:
+ if (c == '\n')
+ state = START;
+ input_entry += c;
+ break;
+ case END:
+ default:
+ assert(0 == "invalid `state` in switch");
+ }
+ }
+ if (c == EOF) {
+ error("end of data in middle of text block");
+ give_up = true;
+ break;
+ }
+ }
+ if (col >= ncolumns) {
+ if (!input_entry.empty()) {
+ if (input_entry.length() >= 2
+ && input_entry[0] == '\\'
+ && input_entry[1] == '"')
+ seen_row_comment = true;
+ else if (!seen_row_comment) {
+ if (c == '\n')
+ in.unget(c);
+ input_entry += '\0';
+ error("excess table entry '%1' discarded",
+ input_entry.contents());
+ if (c == '\n')
+ (void)in.get();
+ }
+ }
+ }
+ else
+ tbl->add_entry(current_row, col, input_entry,
+ &line_format[col], current_filename, ln);
+ col++;
+ if (c == '\n')
+ break;
+ input_entry = "";
+ }
+ else
+ input_entry += c;
+ c = in.get();
+ if (c == EOF)
+ break;
+ }
+ if (give_up)
+ break;
+ input_entry = "";
+ for (; col < ncolumns; col++)
+ tbl->add_entry(current_row, col, input_entry, &line_format[col],
+ current_filename, current_lineno - 1);
+ tbl->add_vlines(current_row, f->vline[format_index]);
+ current_row++;
+ format_index++;
+ }
+ break;
+ case TROFF_INPUT_LINE:
+ {
+ string line;
+ int ln = current_lineno;
+ for (;;) {
+ line += c;
+ if (c == '\n')
+ break;
+ c = in.get();
+ if (c == EOF) {
+ break;
+ }
+ }
+ tbl->add_text_line(current_row, line, current_filename, ln);
+ if (line.length() >= 4
+ && line[0] == '.' && line[1] == 'T' && line[2] == '&') {
+ format *newf = process_format(in, opt, f);
+ if (newf == 0)
+ give_up = true;
+ else
+ f = newf;
+ }
+ if (line.length() >= 3
+ && line[0] == '.' && line[1] == 'l' && line[2] == 'f') {
+ line += '\0';
+ interpret_lf_args(line.contents() + 3);
+ }
+ }
+ break;
+ case SINGLE_HLINE:
+ tbl->add_single_hline(current_row);
+ break;
+ case DOUBLE_HLINE:
+ tbl->add_double_hline(current_row);
+ break;
+ default:
+ assert(0 == "invalid `type` in switch");
+ }
+ if (give_up)
+ break;
+ }
+ if (!give_up && current_row == 0) {
+ error("no real data");
+ give_up = true;
+ }
+ if (give_up) {
+ delete tbl;
+ return 0;
+ }
+ // Do this here rather than at the beginning in case continued formats
+ // change it.
+ int i;
+ for (i = 0; i < ncolumns - 1; i++)
+ if (f->separation[i] >= 0)
+ tbl->set_column_separation(i, f->separation[i]);
+ for (i = 0; i < ncolumns; i++)
+ if (!f->width[i].empty())
+ tbl->set_minimum_width(i, f->width[i]);
+ for (i = 0; i < ncolumns; i++)
+ if (f->equal[i])
+ tbl->set_equal_column(i);
+ for (i = 0; i < ncolumns; i++)
+ if (f->expand[i])
+ tbl->set_expand_column(i);
+ return tbl;
+}
+
+void process_table(table_input &in)
+{
+ options *opt = 0 /* nullptr */;
+ format *fmt = 0 /* nullptr */;
+ table *tbl = 0 /* nullptr */;
+ if ((opt = process_options(in)) != 0 /* nullptr */
+ && (fmt = process_format(in, opt)) != 0 /* nullptr */
+ && (tbl = process_data(in, fmt, opt)) != 0 /* nullptr */) {
+ tbl->print();
+ delete tbl;
+ }
+ else {
+ error("giving up on this table region");
+ while (in.get() != EOF)
+ ;
+ }
+ delete opt;
+ delete fmt;
+ if (!in.ended())
+ error("premature end of file");
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-C] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "vC", long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'C':
+ compatible_flag = 1;
+ break;
+ case 'v':
+ {
+ printf("GNU tbl (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ }
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ printf(".if !\\n(.g .ab GNU tbl requires groff extensions; aborting\n"
+ ".do if !dTS .ds TS\n"
+ ".do if !dT& .ds T&\n"
+ ".do if !dTE .ds TE\n");
+ if (argc > optind) {
+ for (int i = optind; i < argc; i++)
+ if (argv[i][0] == '-' && argv[i][1] == '\0') {
+ current_filename = "-";
+ current_lineno = 1;
+ printf(".lf 1 -\n");
+ process_input_file(stdin);
+ }
+ else {
+ errno = 0;
+ FILE *fp = fopen(argv[i], "r");
+ if (fp == 0) {
+ current_filename = 0 /* nullptr */;
+ fatal("can't open '%1': %2", argv[i], strerror(errno));
+ }
+ else {
+ current_lineno = 1;
+ string fn(argv[i]);
+ fn += '\0';
+ normalize_for_lf(fn);
+ current_filename = fn.contents();
+ printf(".lf 1 %s\n", current_filename);
+ process_input_file(fp);
+ }
+ }
+ }
+ else {
+ current_filename = "-";
+ current_lineno = 1;
+ printf(".lf 1 -\n");
+ process_input_file(stdin);
+ }
+ if (ferror(stdout) || fflush(stdout) < 0)
+ fatal("output error");
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/tbl/table.cpp b/src/preproc/tbl/table.cpp
new file mode 100644
index 0000000..c391c90
--- /dev/null
+++ b/src/preproc/tbl/table.cpp
@@ -0,0 +1,3161 @@
+/* Copyright (C) 1989-2023 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "table.h"
+
+#define BAR_HEIGHT ".25m"
+#define DOUBLE_LINE_SEP "2p"
+#define HALF_DOUBLE_LINE_SEP "1p"
+#define LINE_SEP "2p"
+#define BODY_DEPTH ".25m"
+
+const int DEFAULT_COLUMN_SEPARATION = 3;
+
+#define DELIMITER_CHAR "\\[tbl]"
+#define SEPARATION_FACTOR_REG PREFIX "sep"
+#define LEFTOVER_FACTOR_REG PREFIX "leftover"
+#define BOTTOM_REG PREFIX "bot"
+#define RESET_MACRO_NAME PREFIX "init"
+#define LINESIZE_REG PREFIX "lps"
+#define TOP_REG PREFIX "top"
+#define CURRENT_ROW_REG PREFIX "crow"
+#define LAST_PASSED_ROW_REG PREFIX "passed"
+#define TRANSPARENT_STRING_NAME PREFIX "trans"
+#define QUOTE_STRING_NAME PREFIX "quote"
+#define SECTION_DIVERSION_NAME PREFIX "section"
+#define SECTION_DIVERSION_FLAG_REG PREFIX "sflag"
+#define SAVED_VERTICAL_POS_REG PREFIX "vert"
+#define NEED_BOTTOM_RULE_REG PREFIX "brule"
+#define USE_KEEPS_REG PREFIX "usekeeps"
+#define KEEP_MACRO_NAME PREFIX "keep"
+#define RELEASE_MACRO_NAME PREFIX "release"
+#define SAVED_FONT_REG PREFIX "fnt"
+#define SAVED_SIZE_REG PREFIX "sz"
+#define SAVED_FILL_REG PREFIX "fll"
+#define SAVED_INDENT_REG PREFIX "ind"
+#define SAVED_CENTER_REG PREFIX "cent"
+#define SAVED_TABS_NAME PREFIX "tabs"
+#define SAVED_INTER_WORD_SPACE_SIZE PREFIX "ss"
+#define SAVED_INTER_SENTENCE_SPACE_SIZE PREFIX "sss"
+#define TABLE_DIVERSION_NAME PREFIX "table"
+#define TABLE_DIVERSION_FLAG_REG PREFIX "tflag"
+#define TABLE_KEEP_MACRO_NAME PREFIX "tkeep"
+#define TABLE_RELEASE_MACRO_NAME PREFIX "trelease"
+#define NEEDED_REG PREFIX "needed"
+#define REPEATED_MARK_MACRO PREFIX "rmk"
+#define REPEATED_VPT_MACRO PREFIX "rvpt"
+#define SUPPRESS_BOTTOM_REG PREFIX "supbot"
+#define SAVED_DN_REG PREFIX "dn"
+#define SAVED_HYPHENATION_MODE_REG PREFIX "hyphmode"
+#define SAVED_HYPHENATION_LANG_NAME PREFIX "hyphlang"
+#define SAVED_HYPHENATION_MAX_LINES_REG PREFIX "hyphmaxlines"
+#define SAVED_HYPHENATION_MARGIN_REG PREFIX "hyphmargin"
+#define SAVED_HYPHENATION_SPACE_REG PREFIX "hyphspace"
+#define SAVED_NUMBERING_LINENO PREFIX "linenumber"
+#define SAVED_NUMBERING_SUPPRESSION_COUNT PREFIX "linenumbersuppresscnt"
+#define STARTING_PAGE_REG PREFIX "starting-page"
+#define IS_BOXED_REG PREFIX "is-boxed"
+#define PREVIOUS_PAGE_REG PREFIX "previous-page"
+
+// this must be one character
+#define COMPATIBLE_REG PREFIX "c"
+
+// for use with `ig` requests embedded inside macro definitions
+#define NOP_NAME PREFIX "nop"
+
+#define AVAILABLE_WIDTH_REG PREFIX "available-width"
+#define EXPAND_REG PREFIX "expansion-amount"
+
+#define LEADER_REG PREFIX LEADER
+
+#define BLOCK_WIDTH_PREFIX PREFIX "tbw"
+#define BLOCK_DIVERSION_PREFIX PREFIX "tbd"
+#define BLOCK_HEIGHT_PREFIX PREFIX "tbh"
+#define SPAN_WIDTH_PREFIX PREFIX "w"
+#define SPAN_LEFT_NUMERIC_WIDTH_PREFIX PREFIX "lnw"
+#define SPAN_RIGHT_NUMERIC_WIDTH_PREFIX PREFIX "rnw"
+#define SPAN_ALPHABETIC_WIDTH_PREFIX PREFIX "aw"
+#define COLUMN_SEPARATION_PREFIX PREFIX "cs"
+#define ROW_START_PREFIX PREFIX "rs"
+#define COLUMN_START_PREFIX PREFIX "cl"
+#define COLUMN_END_PREFIX PREFIX "ce"
+#define COLUMN_DIVIDE_PREFIX PREFIX "cd"
+#define ROW_TOP_PREFIX PREFIX "rt"
+
+string block_width_reg(int, int);
+string block_diversion_name(int, int);
+string block_height_reg(int, int);
+string span_width_reg(int, int);
+string span_left_numeric_width_reg(int, int);
+string span_right_numeric_width_reg(int, int);
+string span_alphabetic_width_reg(int, int);
+string column_separation_reg(int);
+string row_start_reg(int);
+string column_start_reg(int);
+string column_end_reg(int);
+string column_divide_reg(int);
+string row_top_reg(int);
+
+void set_inline_modifier(const entry_modifier *);
+void restore_inline_modifier(const entry_modifier *);
+void set_modifier(const entry_modifier *);
+int find_decimal_point(const char *, char, const char *);
+
+string an_empty_string;
+int location_force_filename = 0;
+
+void printfs(const char *,
+ const string &arg1 = an_empty_string,
+ const string &arg2 = an_empty_string,
+ const string &arg3 = an_empty_string,
+ const string &arg4 = an_empty_string,
+ const string &arg5 = an_empty_string);
+
+void prints(const string &);
+
+inline void prints(char c)
+{
+ putchar(c);
+}
+
+inline void prints(const char *s)
+{
+ fputs(s, stdout);
+}
+
+void prints(const string &s)
+{
+ if (!s.empty())
+ fwrite(s.contents(), 1, s.length(), stdout);
+}
+
+struct horizontal_span {
+ horizontal_span *next;
+ int start_col;
+ int end_col;
+ horizontal_span(int, int, horizontal_span *);
+};
+
+class single_line_entry;
+class double_line_entry;
+class simple_entry;
+
+class table_entry {
+friend class table;
+ table_entry *next;
+ int input_lineno;
+ const char *input_filename;
+protected:
+ int start_row;
+ int end_row;
+ int start_col;
+ int end_col;
+ const table *parent;
+ const entry_modifier *mod;
+public:
+ void set_location();
+ table_entry(const table *, const entry_modifier *);
+ virtual ~table_entry();
+ virtual int divert(int, const string *, int *, int);
+ virtual void do_width();
+ virtual void do_depth();
+ virtual void print() = 0;
+ virtual void position_vertically() = 0;
+ virtual single_line_entry *to_single_line_entry();
+ virtual double_line_entry *to_double_line_entry();
+ virtual simple_entry *to_simple_entry();
+ virtual int line_type();
+ virtual void note_double_vrule_on_right(int);
+ virtual void note_double_vrule_on_left(int);
+};
+
+class simple_entry : public table_entry {
+public:
+ simple_entry(const table *, const entry_modifier *);
+ void print();
+ void position_vertically();
+ simple_entry *to_simple_entry();
+ virtual void add_tab();
+ virtual void simple_print(int);
+};
+
+class empty_entry : public simple_entry {
+public:
+ empty_entry(const table *, const entry_modifier *);
+ int line_type();
+};
+
+class text_entry : public simple_entry {
+protected:
+ char *contents;
+ void print_contents();
+public:
+ text_entry(const table *, const entry_modifier *, char *);
+ ~text_entry();
+};
+
+void text_entry::print_contents()
+{
+ set_inline_modifier(mod);
+ prints(contents);
+ restore_inline_modifier(mod);
+}
+
+class repeated_char_entry : public text_entry {
+public:
+ repeated_char_entry(const table *, const entry_modifier *, char *);
+ void simple_print(int);
+};
+
+class simple_text_entry : public text_entry {
+public:
+ simple_text_entry(const table *, const entry_modifier *, char *);
+ void do_width();
+};
+
+class left_text_entry : public simple_text_entry {
+public:
+ left_text_entry(const table *, const entry_modifier *, char *);
+ void simple_print(int);
+ void add_tab();
+};
+
+class right_text_entry : public simple_text_entry {
+public:
+ right_text_entry(const table *, const entry_modifier *, char *);
+ void simple_print(int);
+ void add_tab();
+};
+
+class center_text_entry : public simple_text_entry {
+public:
+ center_text_entry(const table *, const entry_modifier *, char *);
+ void simple_print(int);
+ void add_tab();
+};
+
+class numeric_text_entry : public text_entry {
+ int dot_pos;
+public:
+ numeric_text_entry(const table *, const entry_modifier *, char *, int);
+ void do_width();
+ void simple_print(int);
+};
+
+class alphabetic_text_entry : public text_entry {
+public:
+ alphabetic_text_entry(const table *, const entry_modifier *, char *);
+ void do_width();
+ void simple_print(int);
+ void add_tab();
+};
+
+class line_entry : public simple_entry {
+protected:
+ char double_vrule_on_right;
+ char double_vrule_on_left;
+public:
+ line_entry(const table *, const entry_modifier *);
+ void note_double_vrule_on_right(int);
+ void note_double_vrule_on_left(int);
+ void simple_print(int) = 0;
+};
+
+class single_line_entry : public line_entry {
+public:
+ single_line_entry(const table *, const entry_modifier *);
+ void simple_print(int);
+ single_line_entry *to_single_line_entry();
+ int line_type();
+};
+
+class double_line_entry : public line_entry {
+public:
+ double_line_entry(const table *, const entry_modifier *);
+ void simple_print(int);
+ double_line_entry *to_double_line_entry();
+ int line_type();
+};
+
+class short_line_entry : public simple_entry {
+public:
+ short_line_entry(const table *, const entry_modifier *);
+ void simple_print(int);
+ int line_type();
+};
+
+class short_double_line_entry : public simple_entry {
+public:
+ short_double_line_entry(const table *, const entry_modifier *);
+ void simple_print(int);
+ int line_type();
+};
+
+class block_entry : public table_entry {
+ char *contents;
+protected:
+ void do_divert(int, int, const string *, int *, int);
+public:
+ block_entry(const table *, const entry_modifier *, char *);
+ ~block_entry();
+ int divert(int, const string *, int *, int);
+ void do_depth();
+ void position_vertically();
+ void print() = 0;
+};
+
+class left_block_entry : public block_entry {
+public:
+ left_block_entry(const table *, const entry_modifier *, char *);
+ void print();
+};
+
+class right_block_entry : public block_entry {
+public:
+ right_block_entry(const table *, const entry_modifier *, char *);
+ void print();
+};
+
+class center_block_entry : public block_entry {
+public:
+ center_block_entry(const table *, const entry_modifier *, char *);
+ void print();
+};
+
+class alphabetic_block_entry : public block_entry {
+public:
+ alphabetic_block_entry(const table *, const entry_modifier *, char *);
+ void print();
+ int divert(int, const string *, int *, int);
+};
+
+table_entry::table_entry(const table *p, const entry_modifier *m)
+: next(0), input_lineno(-1), input_filename(0),
+ start_row(-1), end_row(-1), start_col(-1), end_col(-1), parent(p), mod(m)
+{
+}
+
+table_entry::~table_entry()
+{
+}
+
+int table_entry::divert(int, const string *, int *, int)
+{
+ return 0;
+}
+
+void table_entry::do_width()
+{
+}
+
+single_line_entry *table_entry::to_single_line_entry()
+{
+ return 0;
+}
+
+double_line_entry *table_entry::to_double_line_entry()
+{
+ return 0;
+}
+
+simple_entry *table_entry::to_simple_entry()
+{
+ return 0;
+}
+
+void table_entry::do_depth()
+{
+}
+
+void table_entry::set_location()
+{
+ set_troff_location(input_filename, input_lineno);
+}
+
+int table_entry::line_type()
+{
+ return -1;
+}
+
+void table_entry::note_double_vrule_on_right(int)
+{
+}
+
+void table_entry::note_double_vrule_on_left(int)
+{
+}
+
+simple_entry::simple_entry(const table *p, const entry_modifier *m)
+: table_entry(p, m)
+{
+}
+
+void simple_entry::add_tab()
+{
+ // do nothing
+}
+
+void simple_entry::simple_print(int)
+{
+ // do nothing
+}
+
+void simple_entry::position_vertically()
+{
+ if (start_row != end_row)
+ switch (mod->vertical_alignment) {
+ case entry_modifier::TOP:
+ printfs(".sp |\\n[%1]u\n", row_start_reg(start_row));
+ break;
+ case entry_modifier::CENTER:
+ // Perform the motion in two stages so that the center is rounded
+ // vertically upwards even if net vertical motion is upwards.
+ printfs(".sp |\\n[%1]u\n", row_start_reg(start_row));
+ printfs(".sp \\n[" BOTTOM_REG "]u-\\n[%1]u-1v/2u\n",
+ row_start_reg(start_row));
+ break;
+ case entry_modifier::BOTTOM:
+ printfs(".sp |\\n[%1]u+\\n[" BOTTOM_REG "]u-\\n[%1]u-1v\n",
+ row_start_reg(start_row));
+ break;
+ default:
+ assert(0 == "simple entry vertical position modifier not TOP,"
+ " CENTER, or BOTTOM");
+ }
+}
+
+void simple_entry::print()
+{
+ prints(".ta");
+ add_tab();
+ prints('\n');
+ set_location();
+ prints("\\&");
+ simple_print(0);
+ prints('\n');
+}
+
+simple_entry *simple_entry::to_simple_entry()
+{
+ return this;
+}
+
+empty_entry::empty_entry(const table *p, const entry_modifier *m)
+: simple_entry(p, m)
+{
+}
+
+int empty_entry::line_type()
+{
+ return 0;
+}
+
+text_entry::text_entry(const table *p, const entry_modifier *m, char *s)
+: simple_entry(p, m), contents(s)
+{
+}
+
+text_entry::~text_entry()
+{
+ free(contents);
+}
+
+repeated_char_entry::repeated_char_entry(const table *p,
+ const entry_modifier *m, char *s)
+: text_entry(p, m, s)
+{
+}
+
+void repeated_char_entry::simple_print(int)
+{
+ printfs("\\h'|\\n[%1]u'", column_start_reg(start_col));
+ set_inline_modifier(mod);
+ printfs("\\l" DELIMITER_CHAR "\\n[%1]u\\&",
+ span_width_reg(start_col, end_col));
+ prints(contents);
+ prints(DELIMITER_CHAR);
+ restore_inline_modifier(mod);
+}
+
+simple_text_entry::simple_text_entry(const table *p,
+ const entry_modifier *m, char *s)
+: text_entry(p, m, s)
+{
+}
+
+void simple_text_entry::do_width()
+{
+ set_location();
+ printfs(".nr %1 \\n[%1]>?\\w" DELIMITER_CHAR,
+ span_width_reg(start_col, end_col));
+ print_contents();
+ prints(DELIMITER_CHAR "\n");
+}
+
+left_text_entry::left_text_entry(const table *p,
+ const entry_modifier *m, char *s)
+: simple_text_entry(p, m, s)
+{
+}
+
+void left_text_entry::simple_print(int)
+{
+ printfs("\\h'|\\n[%1]u'", column_start_reg(start_col));
+ print_contents();
+}
+
+// The only point of this is to make '\a' "work" as in Unix tbl. Grrr.
+
+void left_text_entry::add_tab()
+{
+ printfs(" \\n[%1]u", column_end_reg(end_col));
+}
+
+right_text_entry::right_text_entry(const table *p,
+ const entry_modifier *m, char *s)
+: simple_text_entry(p, m, s)
+{
+}
+
+void right_text_entry::simple_print(int)
+{
+ printfs("\\h'|\\n[%1]u'", column_start_reg(start_col));
+ prints("\002\003");
+ print_contents();
+ prints("\002");
+}
+
+void right_text_entry::add_tab()
+{
+ printfs(" \\n[%1]u", column_end_reg(end_col));
+}
+
+center_text_entry::center_text_entry(const table *p,
+ const entry_modifier *m, char *s)
+: simple_text_entry(p, m, s)
+{
+}
+
+void center_text_entry::simple_print(int)
+{
+ printfs("\\h'|\\n[%1]u'", column_start_reg(start_col));
+ prints("\002\003");
+ print_contents();
+ prints("\003\002");
+}
+
+void center_text_entry::add_tab()
+{
+ printfs(" \\n[%1]u", column_end_reg(end_col));
+}
+
+numeric_text_entry::numeric_text_entry(const table *p,
+ const entry_modifier *m,
+ char *s, int pos)
+: text_entry(p, m, s), dot_pos(pos)
+{
+}
+
+void numeric_text_entry::do_width()
+{
+ if (dot_pos != 0) {
+ set_location();
+ printfs(".nr %1 0\\w" DELIMITER_CHAR,
+ block_width_reg(start_row, start_col));
+ set_inline_modifier(mod);
+ for (int i = 0; i < dot_pos; i++)
+ prints(contents[i]);
+ restore_inline_modifier(mod);
+ prints(DELIMITER_CHAR "\n");
+ printfs(".nr %1 \\n[%1]>?\\n[%2]\n",
+ span_left_numeric_width_reg(start_col, end_col),
+ block_width_reg(start_row, start_col));
+ }
+ else
+ printfs(".nr %1 0\n", block_width_reg(start_row, start_col));
+ if (contents[dot_pos] != '\0') {
+ set_location();
+ printfs(".nr %1 \\n[%1]>?\\w" DELIMITER_CHAR,
+ span_right_numeric_width_reg(start_col, end_col));
+ set_inline_modifier(mod);
+ prints(contents + dot_pos);
+ restore_inline_modifier(mod);
+ prints(DELIMITER_CHAR "\n");
+ }
+}
+
+void numeric_text_entry::simple_print(int)
+{
+ printfs("\\h'|(\\n[%1]u-\\n[%2]u-\\n[%3]u/2u+\\n[%2]u+\\n[%4]u-\\n[%5]u)'",
+ span_width_reg(start_col, end_col),
+ span_left_numeric_width_reg(start_col, end_col),
+ span_right_numeric_width_reg(start_col, end_col),
+ column_start_reg(start_col),
+ block_width_reg(start_row, start_col));
+ print_contents();
+}
+
+alphabetic_text_entry::alphabetic_text_entry(const table *p,
+ const entry_modifier *m,
+ char *s)
+: text_entry(p, m, s)
+{
+}
+
+void alphabetic_text_entry::do_width()
+{
+ set_location();
+ printfs(".nr %1 \\n[%1]>?\\w" DELIMITER_CHAR,
+ span_alphabetic_width_reg(start_col, end_col));
+ print_contents();
+ prints(DELIMITER_CHAR "\n");
+}
+
+void alphabetic_text_entry::simple_print(int)
+{
+ printfs("\\h'|\\n[%1]u'", column_start_reg(start_col));
+ printfs("\\h'\\n[%1]u-\\n[%2]u/2u'",
+ span_width_reg(start_col, end_col),
+ span_alphabetic_width_reg(start_col, end_col));
+ print_contents();
+}
+
+// The only point of this is to make '\a' "work" as in Unix tbl. Grrr.
+
+void alphabetic_text_entry::add_tab()
+{
+ printfs(" \\n[%1]u", column_end_reg(end_col));
+}
+
+block_entry::block_entry(const table *p, const entry_modifier *m, char *s)
+: table_entry(p, m), contents(s)
+{
+}
+
+block_entry::~block_entry()
+{
+ delete[] contents;
+}
+
+void block_entry::position_vertically()
+{
+ if (start_row != end_row)
+ switch(mod->vertical_alignment) {
+ case entry_modifier::TOP:
+ printfs(".sp |\\n[%1]u\n", row_start_reg(start_row));
+ break;
+ case entry_modifier::CENTER:
+ // Perform the motion in two stages so that the center is rounded
+ // vertically upwards even if net vertical motion is upwards.
+ printfs(".sp |\\n[%1]u\n", row_start_reg(start_row));
+ printfs(".sp \\n[" BOTTOM_REG "]u-\\n[%1]u-\\n[%2]u/2u\n",
+ row_start_reg(start_row),
+ block_height_reg(start_row, start_col));
+ break;
+ case entry_modifier::BOTTOM:
+ printfs(".sp |\\n[%1]u+\\n[" BOTTOM_REG "]u-\\n[%1]u-\\n[%2]u\n",
+ row_start_reg(start_row),
+ block_height_reg(start_row, start_col));
+ break;
+ default:
+ assert(0 == "block entry vertical position modifier not TOP,"
+ " CENTER, or BOTTOM");
+ }
+ if (mod->stagger)
+ prints(".sp -.5v\n");
+}
+
+int block_entry::divert(int ncols, const string *mw, int *sep, int do_expand)
+{
+ do_divert(0, ncols, mw, sep, do_expand);
+ return 1;
+}
+
+void block_entry::do_divert(int alphabetic, int ncols, const string *mw,
+ int *sep, int do_expand)
+{
+ int i;
+ for (i = start_col; i <= end_col; i++)
+ if (parent->expand[i])
+ break;
+ if (i > end_col) {
+ if (do_expand)
+ return;
+ }
+ else {
+ if (!do_expand)
+ return;
+ }
+ printfs(".di %1\n", block_diversion_name(start_row, start_col));
+ prints(".if \\n[" SAVED_FILL_REG "] .fi\n"
+ ".in 0\n");
+ prints(".ll ");
+ for (i = start_col; i <= end_col; i++)
+ if (mw[i].empty() && !parent->expand[i])
+ break;
+ if (i > end_col) {
+ // Every column spanned by this entry has a minimum width.
+ for (int j = start_col; j <= end_col; j++) {
+ if (j > start_col) {
+ if (sep)
+ printfs("+%1n", as_string(sep[j - 1]));
+ prints('+');
+ }
+ if (parent->expand[j])
+ prints("\\n[" EXPAND_REG "]u");
+ else
+ printfs("(n;%1)", mw[j]);
+ }
+ printfs(">?\\n[%1]u", span_width_reg(start_col, end_col));
+ }
+ else
+ // Assign each column with a block entry 1/(n+1) of the line
+ // width, where n is the column count.
+ printfs("(u;\\n[%1]>?(\\n[.l]*%2/%3))",
+ span_width_reg(start_col, end_col),
+ as_string(end_col - start_col + 1),
+ as_string(ncols + 1));
+ if (alphabetic)
+ prints("-2n");
+ prints("\n");
+ prints(".ss \\n[" SAVED_INTER_WORD_SPACE_SIZE "]"
+ " \\n[" SAVED_INTER_SENTENCE_SPACE_SIZE "]\n");
+ prints(".cp \\n(" COMPATIBLE_REG "\n");
+ set_modifier(mod);
+ set_location();
+ prints(contents);
+ prints(".br\n.di\n.cp 0\n");
+ if (!mod->zero_width) {
+ if (alphabetic) {
+ printfs(".nr %1 \\n[%1]>?(\\n[dl]+2n)\n",
+ span_width_reg(start_col, end_col));
+ printfs(".nr %1 \\n[%1]>?\\n[dl]\n",
+ span_alphabetic_width_reg(start_col, end_col));
+ }
+ else
+ printfs(".nr %1 \\n[%1]>?\\n[dl]\n",
+ span_width_reg(start_col, end_col));
+ }
+ printfs(".nr %1 \\n[dn]\n", block_height_reg(start_row, start_col));
+ printfs(".nr %1 \\n[dl]\n", block_width_reg(start_row, start_col));
+ prints("." RESET_MACRO_NAME "\n"
+ ".in \\n[" SAVED_INDENT_REG "]u\n"
+ ".nf\n");
+ // the block might have contained .lf commands
+ location_force_filename = 1;
+}
+
+void block_entry::do_depth()
+{
+ printfs(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?(\\n[%1]+\\n[%2])\n",
+ row_start_reg(start_row),
+ block_height_reg(start_row, start_col));
+}
+
+left_block_entry::left_block_entry(const table *p,
+ const entry_modifier *m, char *s)
+: block_entry(p, m, s)
+{
+}
+
+void left_block_entry::print()
+{
+ printfs(".in +\\n[%1]u\n", column_start_reg(start_col));
+ printfs(".%1\n", block_diversion_name(start_row, start_col));
+ prints(".in\n");
+}
+
+right_block_entry::right_block_entry(const table *p,
+ const entry_modifier *m, char *s)
+: block_entry(p, m, s)
+{
+}
+
+void right_block_entry::print()
+{
+ printfs(".in +\\n[%1]u+\\n[%2]u-\\n[%3]u\n",
+ column_start_reg(start_col),
+ span_width_reg(start_col, end_col),
+ block_width_reg(start_row, start_col));
+ printfs(".%1\n", block_diversion_name(start_row, start_col));
+ prints(".in\n");
+}
+
+center_block_entry::center_block_entry(const table *p,
+ const entry_modifier *m, char *s)
+: block_entry(p, m, s)
+{
+}
+
+void center_block_entry::print()
+{
+ printfs(".in +\\n[%1]u+(\\n[%2]u-\\n[%3]u/2u)\n",
+ column_start_reg(start_col),
+ span_width_reg(start_col, end_col),
+ block_width_reg(start_row, start_col));
+ printfs(".%1\n", block_diversion_name(start_row, start_col));
+ prints(".in\n");
+}
+
+alphabetic_block_entry::alphabetic_block_entry(const table *p,
+ const entry_modifier *m,
+ char *s)
+: block_entry(p, m, s)
+{
+}
+
+int alphabetic_block_entry::divert(int ncols, const string *mw, int *sep,
+ int do_expand)
+{
+ do_divert(1, ncols, mw, sep, do_expand);
+ return 1;
+}
+
+void alphabetic_block_entry::print()
+{
+ printfs(".in +\\n[%1]u+(\\n[%2]u-\\n[%3]u/2u)\n",
+ column_start_reg(start_col),
+ span_width_reg(start_col, end_col),
+ span_alphabetic_width_reg(start_col, end_col));
+ printfs(".%1\n", block_diversion_name(start_row, start_col));
+ prints(".in\n");
+}
+
+line_entry::line_entry(const table *p, const entry_modifier *m)
+: simple_entry(p, m), double_vrule_on_right(0), double_vrule_on_left(0)
+{
+}
+
+void line_entry::note_double_vrule_on_right(int is_corner)
+{
+ double_vrule_on_right = is_corner ? 1 : 2;
+}
+
+void line_entry::note_double_vrule_on_left(int is_corner)
+{
+ double_vrule_on_left = is_corner ? 1 : 2;
+}
+
+single_line_entry::single_line_entry(const table *p, const entry_modifier *m)
+: line_entry(p, m)
+{
+}
+
+int single_line_entry::line_type()
+{
+ return 1;
+}
+
+void single_line_entry::simple_print(int dont_move)
+{
+ printfs("\\h'|\\n[%1]u",
+ column_divide_reg(start_col));
+ if (double_vrule_on_left) {
+ prints(double_vrule_on_left == 1 ? "-" : "+");
+ prints(HALF_DOUBLE_LINE_SEP);
+ }
+ prints("'");
+ if (!dont_move)
+ prints("\\v'-" BAR_HEIGHT "'");
+ printfs("\\s[\\n[" LINESIZE_REG "]]" "\\D'l |\\n[%1]u",
+ column_divide_reg(end_col+1));
+ if (double_vrule_on_right) {
+ prints(double_vrule_on_left == 1 ? "+" : "-");
+ prints(HALF_DOUBLE_LINE_SEP);
+ }
+ prints("0'\\s0");
+ if (!dont_move)
+ prints("\\v'" BAR_HEIGHT "'");
+}
+
+single_line_entry *single_line_entry::to_single_line_entry()
+{
+ return this;
+}
+
+double_line_entry::double_line_entry(const table *p,
+ const entry_modifier *m)
+: line_entry(p, m)
+{
+}
+
+int double_line_entry::line_type()
+{
+ return 2;
+}
+
+void double_line_entry::simple_print(int dont_move)
+{
+ if (!dont_move)
+ prints("\\v'-" BAR_HEIGHT "'");
+ printfs("\\h'|\\n[%1]u",
+ column_divide_reg(start_col));
+ if (double_vrule_on_left) {
+ prints(double_vrule_on_left == 1 ? "-" : "+");
+ prints(HALF_DOUBLE_LINE_SEP);
+ }
+ prints("'");
+ printfs("\\v'-" HALF_DOUBLE_LINE_SEP "'"
+ "\\s[\\n[" LINESIZE_REG "]]"
+ "\\D'l |\\n[%1]u",
+ column_divide_reg(end_col+1));
+ if (double_vrule_on_right)
+ prints("-" HALF_DOUBLE_LINE_SEP);
+ prints(" 0'");
+ printfs("\\v'" DOUBLE_LINE_SEP "'"
+ "\\D'l |\\n[%1]u",
+ column_divide_reg(start_col));
+ if (double_vrule_on_right) {
+ prints(double_vrule_on_left == 1 ? "+" : "-");
+ prints(HALF_DOUBLE_LINE_SEP);
+ }
+ prints(" 0'");
+ prints("\\s0"
+ "\\v'-" HALF_DOUBLE_LINE_SEP "'");
+ if (!dont_move)
+ prints("\\v'" BAR_HEIGHT "'");
+}
+
+double_line_entry *double_line_entry::to_double_line_entry()
+{
+ return this;
+}
+
+short_line_entry::short_line_entry(const table *p, const entry_modifier *m)
+: simple_entry(p, m)
+{
+}
+
+int short_line_entry::line_type()
+{
+ return 1;
+}
+
+void short_line_entry::simple_print(int dont_move)
+{
+ if (mod->stagger)
+ prints("\\v'-.5v'");
+ if (!dont_move)
+ prints("\\v'-" BAR_HEIGHT "'");
+ printfs("\\h'|\\n[%1]u'", column_start_reg(start_col));
+ printfs("\\s[\\n[" LINESIZE_REG "]]"
+ "\\D'l \\n[%1]u 0'"
+ "\\s0",
+ span_width_reg(start_col, end_col));
+ if (!dont_move)
+ prints("\\v'" BAR_HEIGHT "'");
+ if (mod->stagger)
+ prints("\\v'.5v'");
+}
+
+short_double_line_entry::short_double_line_entry(const table *p,
+ const entry_modifier *m)
+: simple_entry(p, m)
+{
+}
+
+int short_double_line_entry::line_type()
+{
+ return 2;
+}
+
+void short_double_line_entry::simple_print(int dont_move)
+{
+ if (mod->stagger)
+ prints("\\v'-.5v'");
+ if (!dont_move)
+ prints("\\v'-" BAR_HEIGHT "'");
+ printfs("\\h'|\\n[%2]u'"
+ "\\v'-" HALF_DOUBLE_LINE_SEP "'"
+ "\\s[\\n[" LINESIZE_REG "]]"
+ "\\D'l \\n[%1]u 0'"
+ "\\v'" DOUBLE_LINE_SEP "'"
+ "\\D'l |\\n[%2]u 0'"
+ "\\s0"
+ "\\v'-" HALF_DOUBLE_LINE_SEP "'",
+ span_width_reg(start_col, end_col),
+ column_start_reg(start_col));
+ if (!dont_move)
+ prints("\\v'" BAR_HEIGHT "'");
+ if (mod->stagger)
+ prints("\\v'.5v'");
+}
+
+void set_modifier(const entry_modifier *m)
+{
+ if (!m->font.empty())
+ printfs(".ft %1\n", m->font);
+ if (m->point_size.val != 0) {
+ prints(".ps ");
+ if (m->point_size.inc > 0)
+ prints('+');
+ else if (m->point_size.inc < 0)
+ prints('-');
+ printfs("%1\n", as_string(m->point_size.val));
+ }
+ if (m->vertical_spacing.val != 0) {
+ prints(".vs ");
+ if (m->vertical_spacing.inc > 0)
+ prints('+');
+ else if (m->vertical_spacing.inc < 0)
+ prints('-');
+ printfs("%1\n", as_string(m->vertical_spacing.val));
+ }
+ if (!m->macro.empty())
+ printfs(".%1\n", m->macro);
+}
+
+void set_inline_modifier(const entry_modifier *m)
+{
+ if (!m->font.empty())
+ printfs("\\f[%1]", m->font);
+ if (m->point_size.val != 0) {
+ prints("\\s[");
+ if (m->point_size.inc > 0)
+ prints('+');
+ else if (m->point_size.inc < 0)
+ prints('-');
+ printfs("%1]", as_string(m->point_size.val));
+ }
+ if (m->stagger)
+ prints("\\v'-.5v'");
+}
+
+void restore_inline_modifier(const entry_modifier *m)
+{
+ if (!m->font.empty())
+ prints("\\f[\\n[" SAVED_FONT_REG "]]");
+ if (m->point_size.val != 0)
+ prints("\\s[\\n[" SAVED_SIZE_REG "]]");
+ if (m->stagger)
+ prints("\\v'.5v'");
+}
+
+struct stuff {
+ stuff *next;
+ int row; // occurs before row 'row'
+ char printed; // has it been printed?
+
+ stuff(int);
+ virtual void print(table *) = 0;
+ virtual ~stuff();
+ virtual int is_single_line() { return 0; };
+ virtual int is_double_line() { return 0; };
+};
+
+stuff::stuff(int r) : next(0), row(r), printed(0)
+{
+}
+
+stuff::~stuff()
+{
+}
+
+struct text_stuff : public stuff {
+ string contents;
+ const char *filename;
+ int lineno;
+
+ text_stuff(const string &, int, const char *, int);
+ ~text_stuff();
+ void print(table *);
+};
+
+text_stuff::text_stuff(const string &s, int r, const char *fn, int ln)
+: stuff(r), contents(s), filename(fn), lineno(ln)
+{
+}
+
+text_stuff::~text_stuff()
+{
+}
+
+void text_stuff::print(table *)
+{
+ printed = 1;
+ prints(".cp \\n(" COMPATIBLE_REG "\n");
+ set_troff_location(filename, lineno);
+ prints(contents);
+ prints(".cp 0\n");
+ location_force_filename = 1; // it might have been a .lf command
+}
+
+struct single_hline_stuff : public stuff {
+ single_hline_stuff(int);
+ void print(table *);
+ int is_single_line();
+};
+
+single_hline_stuff::single_hline_stuff(int r) : stuff(r)
+{
+}
+
+void single_hline_stuff::print(table *tbl)
+{
+ printed = 1;
+ tbl->print_single_hline(row);
+}
+
+int single_hline_stuff::is_single_line()
+{
+ return 1;
+}
+
+struct double_hline_stuff : stuff {
+ double_hline_stuff(int);
+ void print(table *);
+ int is_double_line();
+};
+
+double_hline_stuff::double_hline_stuff(int r) : stuff(r)
+{
+}
+
+void double_hline_stuff::print(table *tbl)
+{
+ printed = 1;
+ tbl->print_double_hline(row);
+}
+
+int double_hline_stuff::is_double_line()
+{
+ return 1;
+}
+
+struct vertical_rule {
+ vertical_rule *next;
+ int start_row;
+ int end_row;
+ int col;
+ char is_double;
+ string top_adjust;
+ string bot_adjust;
+
+ vertical_rule(int, int, int, int, vertical_rule *);
+ ~vertical_rule();
+ void contribute_to_bottom_macro(table *);
+ void print();
+};
+
+vertical_rule::vertical_rule(int sr, int er, int c, int dbl,
+ vertical_rule *p)
+: next(p), start_row(sr), end_row(er), col(c), is_double(dbl)
+{
+}
+
+vertical_rule::~vertical_rule()
+{
+}
+
+void vertical_rule::contribute_to_bottom_macro(table *tbl)
+{
+ printfs(".if \\n[" CURRENT_ROW_REG "]>=%1",
+ as_string(start_row));
+ if (end_row != tbl->get_nrows() - 1)
+ printfs("&(\\n[" CURRENT_ROW_REG "]<%1)",
+ as_string(end_row));
+ prints(" \\{\\\n");
+ printfs(". if %1<=\\n[" LAST_PASSED_ROW_REG "] .nr %2 \\n[#T]\n",
+ as_string(start_row),
+ row_top_reg(start_row));
+ const char *offset_table[3];
+ if (is_double) {
+ offset_table[0] = "-" HALF_DOUBLE_LINE_SEP;
+ offset_table[1] = "+" HALF_DOUBLE_LINE_SEP;
+ offset_table[2] = 0;
+ }
+ else {
+ offset_table[0] = "";
+ offset_table[1] = 0;
+ }
+ for (const char **offsetp = offset_table; *offsetp; offsetp++) {
+ prints(". sp -1\n"
+ "\\v'" BODY_DEPTH);
+ if (!bot_adjust.empty())
+ printfs("+%1", bot_adjust);
+ prints("'");
+ printfs("\\h'\\n[%1]u%3'\\s[\\n[" LINESIZE_REG "]]\\D'l 0 |\\n[%2]u-1v",
+ column_divide_reg(col),
+ row_top_reg(start_row),
+ *offsetp);
+ if (!bot_adjust.empty())
+ printfs("-(%1)", bot_adjust);
+ // don't perform the top adjustment if the top is actually #T
+ if (!top_adjust.empty())
+ printfs("+((%1)*(%2>\\n[" LAST_PASSED_ROW_REG "]))",
+ top_adjust,
+ as_string(start_row));
+ prints("'\\s0\n");
+ }
+ prints(".\\}\n");
+}
+
+void vertical_rule::print()
+{
+ printfs("\\*[" TRANSPARENT_STRING_NAME "]"
+ ".if %1<=\\*[" QUOTE_STRING_NAME "]\\n[" LAST_PASSED_ROW_REG "] "
+ ".nr %2 \\*[" QUOTE_STRING_NAME "]\\n[#T]\n",
+ as_string(start_row),
+ row_top_reg(start_row));
+ const char *offset_table[3];
+ if (is_double) {
+ offset_table[0] = "-" HALF_DOUBLE_LINE_SEP;
+ offset_table[1] = "+" HALF_DOUBLE_LINE_SEP;
+ offset_table[2] = 0;
+ }
+ else {
+ offset_table[0] = "";
+ offset_table[1] = 0;
+ }
+ for (const char **offsetp = offset_table; *offsetp; offsetp++) {
+ prints("\\*[" TRANSPARENT_STRING_NAME "].sp -1\n"
+ "\\*[" TRANSPARENT_STRING_NAME "]\\v'" BODY_DEPTH);
+ if (!bot_adjust.empty())
+ printfs("+%1", bot_adjust);
+ prints("'");
+ printfs("\\h'\\n[%1]u%3'"
+ "\\s[\\n[" LINESIZE_REG "]]"
+ "\\D'l 0 |\\*[" QUOTE_STRING_NAME "]\\n[%2]u-1v",
+ column_divide_reg(col),
+ row_top_reg(start_row),
+ *offsetp);
+ if (!bot_adjust.empty())
+ printfs("-(%1)", bot_adjust);
+ // don't perform the top adjustment if the top is actually #T
+ if (!top_adjust.empty())
+ printfs("+((%1)*(%2>\\*[" QUOTE_STRING_NAME "]\\n["
+ LAST_PASSED_ROW_REG "]))",
+ top_adjust,
+ as_string(start_row));
+ prints("'"
+ "\\s0\n");
+ }
+}
+
+table::table(int nc, unsigned f, int ls, char dpc)
+: nrows(0), ncolumns(nc), linesize(ls), decimal_point_char(dpc),
+ vrule_list(0), stuff_list(0), span_list(0),
+ entry_list(0), entry_list_tailp(&entry_list), entry(0),
+ vline(0), row_is_all_lines(0), left_separation(0),
+ right_separation(0), total_separation(0), allocated_rows(0), flags(f)
+{
+ minimum_width = new string[ncolumns];
+ column_separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
+ equal = new char[ncolumns];
+ expand = new char[ncolumns];
+ int i;
+ for (i = 0; i < ncolumns; i++) {
+ equal[i] = 0;
+ expand[i] = 0;
+ }
+ for (i = 0; i < ncolumns - 1; i++)
+ column_separation[i] = DEFAULT_COLUMN_SEPARATION;
+ delim[0] = delim[1] = '\0';
+}
+
+table::~table()
+{
+ for (int i = 0; i < nrows; i++) {
+ delete[] entry[i];
+ delete[] vline[i];
+ }
+ delete[] entry;
+ delete[] vline;
+ while (entry_list) {
+ table_entry *tem = entry_list;
+ entry_list = entry_list->next;
+ delete tem;
+ }
+ delete[] minimum_width;
+ delete[] column_separation;
+ delete[] equal;
+ delete[] expand;
+ while (stuff_list) {
+ stuff *tem = stuff_list;
+ stuff_list = stuff_list->next;
+ delete tem;
+ }
+ while (vrule_list) {
+ vertical_rule *tem = vrule_list;
+ vrule_list = vrule_list->next;
+ delete tem;
+ }
+ delete[] row_is_all_lines;
+ while (span_list) {
+ horizontal_span *tem = span_list;
+ span_list = span_list->next;
+ delete tem;
+ }
+}
+
+void table::set_delim(char c1, char c2)
+{
+ delim[0] = c1;
+ delim[1] = c2;
+}
+
+void table::set_minimum_width(int c, const string &w)
+{
+ assert(c >= 0 && c < ncolumns);
+ minimum_width[c] = w;
+}
+
+void table::set_column_separation(int c, int n)
+{
+ assert(c >= 0 && c < ncolumns - 1);
+ column_separation[c] = n;
+}
+
+void table::set_equal_column(int c)
+{
+ assert(c >= 0 && c < ncolumns);
+ equal[c] = 1;
+}
+
+void table::set_expand_column(int c)
+{
+ assert(c >= 0 && c < ncolumns);
+ expand[c] = 1;
+}
+
+void table::add_stuff(stuff *p)
+{
+ stuff **pp;
+ for (pp = &stuff_list; *pp; pp = &(*pp)->next)
+ ;
+ *pp = p;
+}
+
+void table::add_text_line(int r, const string &s, const char *filename,
+ int lineno)
+{
+ add_stuff(new text_stuff(s, r, filename, lineno));
+}
+
+void table::add_single_hline(int r)
+{
+ add_stuff(new single_hline_stuff(r));
+}
+
+void table::add_double_hline(int r)
+{
+ add_stuff(new double_hline_stuff(r));
+}
+
+void table::allocate(int r)
+{
+ if (r >= nrows) {
+ typedef table_entry **PPtable_entry; // work around g++ 1.36.1 bug
+ if (r >= allocated_rows) {
+ if (allocated_rows == 0) {
+ allocated_rows = 16;
+ if (allocated_rows <= r)
+ allocated_rows = r + 1;
+ entry = new PPtable_entry[allocated_rows];
+ vline = new char*[allocated_rows];
+ }
+ else {
+ table_entry ***old_entry = entry;
+ int old_allocated_rows = allocated_rows;
+ allocated_rows *= 2;
+ if (allocated_rows <= r)
+ allocated_rows = r + 1;
+ entry = new PPtable_entry[allocated_rows];
+ memcpy(entry, old_entry, sizeof(table_entry**)*old_allocated_rows);
+ delete[] old_entry;
+ char **old_vline = vline;
+ vline = new char*[allocated_rows];
+ memcpy(vline, old_vline, sizeof(char*)*old_allocated_rows);
+ delete[] old_vline;
+ }
+ }
+ assert(allocated_rows > r);
+ while (nrows <= r) {
+ entry[nrows] = new table_entry*[ncolumns];
+ int i;
+ for (i = 0; i < ncolumns; i++)
+ entry[nrows][i] = 0;
+ vline[nrows] = new char[ncolumns+1];
+ for (i = 0; i < ncolumns+1; i++)
+ vline[nrows][i] = 0;
+ nrows++;
+ }
+ }
+}
+
+void table::do_hspan(int r, int c)
+{
+ assert(r >= 0 && c >= 0 && r < nrows && c < ncolumns);
+ if (c == 0) {
+ error("first column cannot be horizontally spanned");
+ return;
+ }
+ table_entry *e = entry[r][c];
+ if (e) {
+ assert(e->start_row <= r && r <= e->end_row
+ && e->start_col <= c && c <= e->end_col
+ && e->end_row - e->start_row > 0
+ && e->end_col - e->start_col > 0);
+ return;
+ }
+ e = entry[r][c-1];
+ // e can be 0 if we had an empty entry or an error
+ if (e == 0)
+ return;
+ if (e->start_row != r) {
+ /*
+ l l
+ ^ s */
+ error("impossible horizontal span at row %1, column %2", r + 1,
+ c + 1);
+ }
+ else {
+ e->end_col = c;
+ entry[r][c] = e;
+ }
+}
+
+void table::do_vspan(int r, int c)
+{
+ assert(r >= 0 && c >= 0 && r < nrows && c < ncolumns);
+ if (0 == r) {
+ error("first row cannot be vertically spanned");
+ return;
+ }
+ table_entry *e = entry[r][c];
+ if (e) {
+ assert(e->start_row <= r);
+ assert(r <= e->end_row);
+ assert(e->start_col <= c);
+ assert(c <= e->end_col);
+ assert((e->end_row - e->start_row) > 0);
+ assert((e->end_col - e->start_col) > 0);
+ return;
+ }
+ e = entry[r-1][c];
+ // e can be a null pointer if we had an empty entry or an error
+ if (0 == e)
+ return;
+ if (e->start_col != c) {
+ /* l s
+ l ^ */
+ error("impossible vertical span at row %1, column %2", r + 1,
+ c + 1);
+ }
+ else {
+ for (int i = c; i <= e->end_col; i++) {
+ assert(entry[r][i] == 0);
+ entry[r][i] = e;
+ }
+ e->end_row = r;
+ }
+}
+
+int find_decimal_point(const char *s, char decimal_point_char,
+ const char *delim)
+{
+ if (s == 0 || *s == '\0')
+ return -1;
+ const char *p;
+ int in_delim = 0; // is p within eqn delimiters?
+ // tbl recognises \& even within eqn delimiters; I don't
+ for (p = s; *p; p++)
+ if (in_delim) {
+ if (*p == delim[1])
+ in_delim = 0;
+ }
+ else if (*p == delim[0])
+ in_delim = 1;
+ else if (p[0] == '\\' && p[1] == '&')
+ return p - s;
+ int possible_pos = -1;
+ in_delim = 0;
+ for (p = s; *p; p++)
+ if (in_delim) {
+ if (*p == delim[1])
+ in_delim = 0;
+ }
+ else if (*p == delim[0])
+ in_delim = 1;
+ else if (p[0] == decimal_point_char && csdigit(p[1]))
+ possible_pos = p - s;
+ if (possible_pos >= 0)
+ return possible_pos;
+ in_delim = 0;
+ for (p = s; *p; p++)
+ if (in_delim) {
+ if (*p == delim[1])
+ in_delim = 0;
+ }
+ else if (*p == delim[0])
+ in_delim = 1;
+ else if (csdigit(*p))
+ possible_pos = p + 1 - s;
+ return possible_pos;
+}
+
+void table::add_entry(int r, int c, const string &str,
+ const entry_format *f, const char *fn, int ln)
+{
+ allocate(r);
+ table_entry *e = 0;
+ char *s = str.extract();
+ if (str.search('\n') >= 0) {
+ bool was_changed = false;
+ for (int i = 0; s[i] != '\0'; i++)
+ if ((i > 0) && (s[(i - 1)] == '\\') && (s[i] == 'R')) {
+ s[i] = '&';
+ was_changed = true;
+ }
+ if (was_changed)
+ error_with_file_and_line(fn, ln, "repeating a glyph with '\\R'"
+ " is not allowed in a text block");
+ }
+ if (str == "\\_") {
+ e = new short_line_entry(this, f);
+ }
+ else if (str == "\\=") {
+ e = new short_double_line_entry(this, f);
+ }
+ else if (str == "_") {
+ single_line_entry *lefte;
+ if (c > 0 && entry[r][c-1] != 0 &&
+ (lefte = entry[r][c-1]->to_single_line_entry()) != 0
+ && lefte->start_row == r
+ && lefte->mod->stagger == f->stagger) {
+ lefte->end_col = c;
+ entry[r][c] = lefte;
+ }
+ else
+ e = new single_line_entry(this, f);
+ }
+ else if (str == "=") {
+ double_line_entry *lefte;
+ if (c > 0 && entry[r][c-1] != 0 &&
+ (lefte = entry[r][c-1]->to_double_line_entry()) != 0
+ && lefte->start_row == r
+ && lefte->mod->stagger == f->stagger) {
+ lefte->end_col = c;
+ entry[r][c] = lefte;
+ }
+ else
+ e = new double_line_entry(this, f);
+ }
+ else if (str == "\\^") {
+ if (r == 0) {
+ error("first row cannot contain a vertical span entry '\\^'");
+ e = new empty_entry(this, f);
+ }
+ else
+ do_vspan(r, c);
+ }
+ else {
+ int is_block = str.search('\n') >= 0;
+ switch (f->type) {
+ case FORMAT_SPAN:
+ assert(str.empty());
+ do_hspan(r, c);
+ break;
+ case FORMAT_LEFT:
+ if (!str.empty()) {
+ if (is_block)
+ e = new left_block_entry(this, f, s);
+ else
+ e = new left_text_entry(this, f, s);
+ }
+ else
+ e = new empty_entry(this, f);
+ break;
+ case FORMAT_CENTER:
+ if (!str.empty()) {
+ if (is_block)
+ e = new center_block_entry(this, f, s);
+ else
+ e = new center_text_entry(this, f, s);
+ }
+ else
+ e = new empty_entry(this, f);
+ break;
+ case FORMAT_RIGHT:
+ if (!str.empty()) {
+ if (is_block)
+ e = new right_block_entry(this, f, s);
+ else
+ e = new right_text_entry(this, f, s);
+ }
+ else
+ e = new empty_entry(this, f);
+ break;
+ case FORMAT_NUMERIC:
+ if (!str.empty()) {
+ if (is_block) {
+ error_with_file_and_line(fn, ln, "can't have numeric text block");
+ e = new left_block_entry(this, f, s);
+ }
+ else {
+ int pos = find_decimal_point(s, decimal_point_char, delim);
+ if (pos < 0)
+ e = new center_text_entry(this, f, s);
+ else
+ e = new numeric_text_entry(this, f, s, pos);
+ }
+ }
+ else
+ e = new empty_entry(this, f);
+ break;
+ case FORMAT_ALPHABETIC:
+ if (!str.empty()) {
+ if (is_block)
+ e = new alphabetic_block_entry(this, f, s);
+ else
+ e = new alphabetic_text_entry(this, f, s);
+ }
+ else
+ e = new empty_entry(this, f);
+ break;
+ case FORMAT_VSPAN:
+ do_vspan(r, c);
+ break;
+ case FORMAT_HLINE:
+ if ((str.length() != 0) && (str != "\\&"))
+ error_with_file_and_line(fn, ln,
+ "ignoring non-empty data entry using"
+ " '_' column classifier");
+ e = new single_line_entry(this, f);
+ break;
+ case FORMAT_DOUBLE_HLINE:
+ if ((str.length() != 0) && (str != "\\&"))
+ error_with_file_and_line(fn, ln,
+ "ignoring non-empty data entry using"
+ " '=' column classifier");
+ e = new double_line_entry(this, f);
+ break;
+ default:
+ assert(0 == "table column format not in FORMAT_{SPAN,LEFT,CENTER,"
+ "RIGHT,NUMERIC,ALPHABETIC,VSPAN,HLINE,DOUBLE_HLINE}");
+ }
+ }
+ if (e) {
+ table_entry *preve = entry[r][c];
+ if (preve) {
+ /* c s
+ ^ l */
+ error_with_file_and_line(fn, ln, "row %1, column %2 already"
+ " spanned",
+ r + 1, c + 1);
+ delete e;
+ }
+ else {
+ e->input_lineno = ln;
+ e->input_filename = fn;
+ e->start_row = e->end_row = r;
+ e->start_col = e->end_col = c;
+ *entry_list_tailp = e;
+ entry_list_tailp = &e->next;
+ entry[r][c] = e;
+ }
+ }
+}
+
+// add vertical lines for row r
+
+void table::add_vlines(int r, const char *v)
+{
+ allocate(r);
+ bool lwarned = false;
+ bool twarned = false;
+ for (int i = 0; i < ncolumns+1; i++) {
+ assert(v[i] < 3);
+ if (v[i] && (flags & (BOX | ALLBOX | DOUBLEBOX)) && (i == 0)
+ && (!lwarned)) {
+ error("ignoring vertical line at leading edge of boxed table");
+ lwarned = true;
+ }
+ else if (v[i] && (flags & (BOX | ALLBOX | DOUBLEBOX))
+ && (i == ncolumns) && (!twarned)) {
+ error("ignoring vertical line at trailing edge of boxed table");
+ twarned = true;
+ }
+ else
+ vline[r][i] = v[i];
+ }
+}
+
+void table::check()
+{
+ table_entry *p = entry_list;
+ int i, j;
+ while (p) {
+ for (i = p->start_row; i <= p->end_row; i++)
+ for (j = p->start_col; j <= p->end_col; j++)
+ assert(entry[i][j] == p);
+ p = p->next;
+ }
+}
+
+void table::print()
+{
+ location_force_filename = 1;
+ check();
+ init_output();
+ determine_row_type();
+ compute_widths();
+ if (!(flags & CENTER))
+ prints(".if \\n[" SAVED_CENTER_REG "] \\{\\\n");
+ prints(". in +(u;\\n[.l]-\\n[.i]-\\n[TW]/2>?-\\n[.i])\n"
+ ". nr " SAVED_INDENT_REG " \\n[.i]\n");
+ if (!(flags & CENTER))
+ prints(".\\}\n");
+ build_vrule_list();
+ define_bottom_macro();
+ do_top();
+ for (int i = 0; i < nrows; i++)
+ do_row(i);
+ do_bottom();
+}
+
+void table::determine_row_type()
+{
+ row_is_all_lines = new char[nrows];
+ for (int i = 0; i < nrows; i++) {
+ bool had_single = false;
+ bool had_double = false;
+ bool had_non_line = false;
+ for (int c = 0; c < ncolumns; c++) {
+ table_entry *e = entry[i][c];
+ if (e != 0) {
+ if (e->start_row == e->end_row) {
+ int t = e->line_type();
+ switch (t) {
+ case -1:
+ had_non_line = true;
+ break;
+ case 0:
+ // empty
+ break;
+ case 1:
+ had_single = true;
+ break;
+ case 2:
+ had_double = true;
+ break;
+ default:
+ assert(0 == "table entry line type not in {-1, 0, 1, 2}");
+ }
+ if (had_non_line)
+ break;
+ }
+ c = e->end_col;
+ }
+ }
+ if (had_non_line)
+ row_is_all_lines[i] = 0;
+ else if (had_double)
+ row_is_all_lines[i] = 2;
+ else if (had_single)
+ row_is_all_lines[i] = 1;
+ else
+ row_is_all_lines[i] = 0;
+ }
+}
+
+int table::count_expand_columns()
+{
+ int count = 0;
+ for (int i = 0; i < ncolumns; i++)
+ if (expand[i])
+ count++;
+ return count;
+}
+
+void table::init_output()
+{
+ prints(".\\\" initialize output\n");
+ prints(".nr " COMPATIBLE_REG " \\n(.C\n"
+ ".cp 0\n");
+ if (linesize > 0)
+ printfs(".nr " LINESIZE_REG " %1\n", as_string(linesize));
+ else
+ prints(".nr " LINESIZE_REG " \\n[.s]\n");
+ if (!(flags & CENTER))
+ prints(".nr " SAVED_CENTER_REG " \\n[.ce]\n");
+ if (compatible_flag)
+ prints(".ds " LEADER_REG " \\a\n");
+ if (!(flags & NOKEEP))
+ prints(".if !r " USE_KEEPS_REG " .nr " USE_KEEPS_REG " 1\n");
+ prints(".de " RESET_MACRO_NAME "\n"
+ ". ft \\n[.f]\n"
+ ". ps \\n[.s]\n"
+ ". vs \\n[.v]u\n"
+ ". in \\n[.i]u\n"
+ ". ll \\n[.l]u\n"
+ ". ls \\n[.L]\n"
+ ". hy \\\\n[" SAVED_HYPHENATION_MODE_REG "]\n"
+ ". hla \\\\*[" SAVED_HYPHENATION_LANG_NAME "]\n"
+ ". hlm \\\\n[" SAVED_HYPHENATION_MAX_LINES_REG "]\n"
+ ". hym \\\\n[" SAVED_HYPHENATION_MARGIN_REG "]\n"
+ ". hys \\\\n[" SAVED_HYPHENATION_SPACE_REG "]\n"
+ ". ad \\n[.j]\n"
+ ". ie \\n[.u] .fi\n"
+ ". el .nf\n"
+ ". ce \\n[.ce]\n"
+ ". ta \\\\*[" SAVED_TABS_NAME "]\n"
+ ". ss \\\\n[" SAVED_INTER_WORD_SPACE_SIZE "]"
+ " \\\\n[" SAVED_INTER_SENTENCE_SPACE_SIZE "]\n"
+ "..\n"
+ ".nr " SAVED_INDENT_REG " \\n[.i]\n"
+ ".nr " SAVED_FONT_REG " \\n[.f]\n"
+ ".nr " SAVED_SIZE_REG " \\n[.s]\n"
+ ".nr " SAVED_FILL_REG " \\n[.u]\n"
+ ".ds " SAVED_TABS_NAME " \\n[.tabs]\n"
+ ".nr " SAVED_INTER_WORD_SPACE_SIZE " \\n[.ss]\n"
+ ".nr " SAVED_INTER_SENTENCE_SPACE_SIZE " \\n[.sss]\n"
+ ".nr " SAVED_HYPHENATION_MODE_REG " \\n[.hy]\n"
+ ".ds " SAVED_HYPHENATION_LANG_NAME " \\n[.hla]\n"
+ ".nr " SAVED_HYPHENATION_MAX_LINES_REG " (\\n[.hlm])\n"
+ ".nr " SAVED_HYPHENATION_MARGIN_REG " \\n[.hym]\n"
+ ".nr " SAVED_HYPHENATION_SPACE_REG " \\n[.hys]\n"
+ ".nr T. 0\n"
+ ".nr " CURRENT_ROW_REG " 0-1\n"
+ ".nr " LAST_PASSED_ROW_REG " 0-1\n"
+ ".nr " SECTION_DIVERSION_FLAG_REG " 0\n"
+ ".ds " TRANSPARENT_STRING_NAME "\n"
+ ".ds " QUOTE_STRING_NAME "\n"
+ ".nr " NEED_BOTTOM_RULE_REG " 1\n"
+ ".nr " SUPPRESS_BOTTOM_REG " 0\n"
+ ".eo\n"
+ ".de " REPEATED_MARK_MACRO "\n"
+ ". mk \\$1\n"
+ ". if !'\\n(.z'' \\!." REPEATED_MARK_MACRO " \"\\$1\"\n"
+ "..\n"
+ ".de " REPEATED_VPT_MACRO "\n"
+ ". vpt \\$1\n"
+ ". if !'\\n(.z'' \\!." REPEATED_VPT_MACRO " \"\\$1\"\n"
+ "..\n");
+ if (!(flags & NOKEEP)) {
+ prints(".de " KEEP_MACRO_NAME "\n"
+ ". if '\\n[.z]'' \\{\\\n"
+ ". ds " QUOTE_STRING_NAME " \\\\\n"
+ ". ds " TRANSPARENT_STRING_NAME " \\!\n"
+ ". di " SECTION_DIVERSION_NAME "\n"
+ ". nr " SECTION_DIVERSION_FLAG_REG " 1\n"
+ ". in 0\n"
+ ". \\}\n"
+ "..\n"
+ // Protect '#' in macro name from being interpreted by eqn.
+ ".ig\n"
+ ".EQ\n"
+ "delim off\n"
+ ".EN\n"
+ "..\n"
+ ".de " RELEASE_MACRO_NAME "\n"
+ ". if \\n[" SECTION_DIVERSION_FLAG_REG "] \\{\\\n"
+ ". di\n"
+ ". in \\n[" SAVED_INDENT_REG "]u\n"
+ ". nr " SAVED_DN_REG " \\n[dn]\n"
+ ". ds " QUOTE_STRING_NAME "\n"
+ ". ds " TRANSPARENT_STRING_NAME "\n"
+ ". nr " SECTION_DIVERSION_FLAG_REG " 0\n"
+ ". if \\n[.t]<=\\n[dn] \\{\\\n"
+ ". nr T. 1\n"
+ ". T#\n"
+ ". nr " SUPPRESS_BOTTOM_REG " 1\n"
+ ". sp \\n[.t]u\n"
+ ". nr " SUPPRESS_BOTTOM_REG " 0\n"
+ ". mk #T\n"
+ ". \\}\n");
+ if (!(flags & NOWARN)) {
+ prints(". if \\n[.t]<=\\n[" SAVED_DN_REG "] \\{\\\n");
+ // eqn(1) delimiters have already been switched off.
+ entry_list->set_location();
+ // Since we turn off traps, troff won't go into an infinite loop
+ // when we output the table row; it will just flow off the bottom
+ // of the page.
+ prints(". tmc \\n[.F]:\\n[.c]: warning:\n"
+ ". tm1 \" table row does not fit on page \\n%\n");
+ prints(". \\}\n");
+ }
+ prints(". nf\n"
+ ". ls 1\n"
+ ". " SECTION_DIVERSION_NAME "\n"
+ ". ls\n"
+ ". rm " SECTION_DIVERSION_NAME "\n"
+ ". \\}\n"
+ "..\n"
+ ".ig\n"
+ ".EQ\n"
+ "delim on\n"
+ ".EN\n"
+ "..\n"
+ ".nr " TABLE_DIVERSION_FLAG_REG " 0\n"
+ ".de " TABLE_KEEP_MACRO_NAME "\n"
+ ". if '\\n[.z]'' \\{\\\n"
+ ". di " TABLE_DIVERSION_NAME "\n"
+ ". nr " TABLE_DIVERSION_FLAG_REG " 1\n"
+ ". \\}\n"
+ "..\n"
+ ".de " TABLE_RELEASE_MACRO_NAME "\n"
+ ". if \\n[" TABLE_DIVERSION_FLAG_REG "] \\{\\\n"
+ ". br\n"
+ ". di\n"
+ ". nr " SAVED_DN_REG " \\n[dn]\n"
+ ". ne \\n[dn]u+\\n[.V]u\n"
+ ". ie \\n[.t]<=\\n[" SAVED_DN_REG "] \\{\\\n");
+ // Protect characters in diagnostic message (especially :, [, ])
+ // from being interpreted by eqn.
+ prints(". ds " NOP_NAME " \\\" empty\n");
+ prints(". ig " NOP_NAME "\n"
+ ".EQ\n"
+ "delim off\n"
+ ".EN\n"
+ ". " NOP_NAME "\n");
+ entry_list->set_location();
+ prints(". nr " PREVIOUS_PAGE_REG " (\\n% - 1)\n"
+ ". tmc \\n[.F]:\\n[.c]: error:\n"
+ ". tmc \" boxed table does not fit on page"
+ " \\n[" PREVIOUS_PAGE_REG "];\n"
+ ". tm1 \" use .TS H/.TH with a supporting macro package"
+ "\n"
+ ". rr " PREVIOUS_PAGE_REG "\n");
+ prints(". ig " NOP_NAME "\n"
+ ".EQ\n"
+ "delim on\n"
+ ".EN\n"
+ ". " NOP_NAME "\n");
+ prints(". \\}\n"
+ ". el \\{\\\n"
+ ". in 0\n"
+ ". ls 1\n"
+ ". nf\n"
+ ". " TABLE_DIVERSION_NAME "\n"
+ ". \\}\n"
+ ". rm " TABLE_DIVERSION_NAME "\n"
+ ". \\}\n"
+ "..\n");
+ }
+ prints(".ec\n"
+ ".ce 0\n");
+ prints(".nr " SAVED_NUMBERING_LINENO " \\n[ln]\n"
+ ".nr ln 0\n"
+ ".nr " SAVED_NUMBERING_SUPPRESSION_COUNT " \\n[.nn]\n"
+ ".nn 2147483647\n"); // 2^31-1; inelegant but effective
+ prints(".nf\n");
+}
+
+string block_width_reg(int r, int c)
+{
+ static char name[sizeof(BLOCK_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS];
+ sprintf(name, BLOCK_WIDTH_PREFIX "%d,%d", r, c);
+ return string(name);
+}
+
+string block_diversion_name(int r, int c)
+{
+ static char name[sizeof(BLOCK_DIVERSION_PREFIX)+INT_DIGITS+1+INT_DIGITS];
+ sprintf(name, BLOCK_DIVERSION_PREFIX "%d,%d", r, c);
+ return string(name);
+}
+
+string block_height_reg(int r, int c)
+{
+ static char name[sizeof(BLOCK_HEIGHT_PREFIX)+INT_DIGITS+1+INT_DIGITS];
+ sprintf(name, BLOCK_HEIGHT_PREFIX "%d,%d", r, c);
+ return string(name);
+}
+
+string span_width_reg(int start_col, int end_col)
+{
+ static char name[sizeof(SPAN_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS];
+ sprintf(name, SPAN_WIDTH_PREFIX "%d", start_col);
+ if (end_col != start_col)
+ sprintf(strchr(name, '\0'), ",%d", end_col);
+ return string(name);
+}
+
+string span_left_numeric_width_reg(int start_col, int end_col)
+{
+ static char name[sizeof(SPAN_LEFT_NUMERIC_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS];
+ sprintf(name, SPAN_LEFT_NUMERIC_WIDTH_PREFIX "%d", start_col);
+ if (end_col != start_col)
+ sprintf(strchr(name, '\0'), ",%d", end_col);
+ return string(name);
+}
+
+string span_right_numeric_width_reg(int start_col, int end_col)
+{
+ static char name[sizeof(SPAN_RIGHT_NUMERIC_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS];
+ sprintf(name, SPAN_RIGHT_NUMERIC_WIDTH_PREFIX "%d", start_col);
+ if (end_col != start_col)
+ sprintf(strchr(name, '\0'), ",%d", end_col);
+ return string(name);
+}
+
+string span_alphabetic_width_reg(int start_col, int end_col)
+{
+ static char name[sizeof(SPAN_ALPHABETIC_WIDTH_PREFIX)+INT_DIGITS+1+INT_DIGITS];
+ sprintf(name, SPAN_ALPHABETIC_WIDTH_PREFIX "%d", start_col);
+ if (end_col != start_col)
+ sprintf(strchr(name, '\0'), ",%d", end_col);
+ return string(name);
+}
+
+string column_separation_reg(int col)
+{
+ static char name[sizeof(COLUMN_SEPARATION_PREFIX)+INT_DIGITS];
+ sprintf(name, COLUMN_SEPARATION_PREFIX "%d", col);
+ return string(name);
+}
+
+string row_start_reg(int row)
+{
+ static char name[sizeof(ROW_START_PREFIX)+INT_DIGITS];
+ sprintf(name, ROW_START_PREFIX "%d", row);
+ return string(name);
+}
+
+string column_start_reg(int col)
+{
+ static char name[sizeof(COLUMN_START_PREFIX)+INT_DIGITS];
+ sprintf(name, COLUMN_START_PREFIX "%d", col);
+ return string(name);
+}
+
+string column_end_reg(int col)
+{
+ static char name[sizeof(COLUMN_END_PREFIX)+INT_DIGITS];
+ sprintf(name, COLUMN_END_PREFIX "%d", col);
+ return string(name);
+}
+
+string column_divide_reg(int col)
+{
+ static char name[sizeof(COLUMN_DIVIDE_PREFIX)+INT_DIGITS];
+ sprintf(name, COLUMN_DIVIDE_PREFIX "%d", col);
+ return string(name);
+}
+
+string row_top_reg(int row)
+{
+ static char name[sizeof(ROW_TOP_PREFIX)+INT_DIGITS];
+ sprintf(name, ROW_TOP_PREFIX "%d", row);
+ return string(name);
+}
+
+void init_span_reg(int start_col, int end_col)
+{
+ printfs(".nr %1 \\n(.H\n.nr %2 0\n.nr %3 0\n.nr %4 0\n",
+ span_width_reg(start_col, end_col),
+ span_alphabetic_width_reg(start_col, end_col),
+ span_left_numeric_width_reg(start_col, end_col),
+ span_right_numeric_width_reg(start_col, end_col));
+}
+
+void compute_span_width(int start_col, int end_col)
+{
+ printfs(".nr %1 \\n[%1]>?(\\n[%2]+\\n[%3])\n"
+ ".if \\n[%4] .nr %1 \\n[%1]>?(\\n[%4]+2n)\n",
+ span_width_reg(start_col, end_col),
+ span_left_numeric_width_reg(start_col, end_col),
+ span_right_numeric_width_reg(start_col, end_col),
+ span_alphabetic_width_reg(start_col, end_col));
+}
+
+// Increase the widths of columns so that the width of any spanning
+// entry is not greater than the sum of the widths of the columns that
+// it spans. Ensure that the widths of columns remain equal.
+
+void table::divide_span(int start_col, int end_col)
+{
+ assert(end_col > start_col);
+ printfs(".nr " NEEDED_REG " \\n[%1]-(\\n[%2]",
+ span_width_reg(start_col, end_col),
+ span_width_reg(start_col, start_col));
+ int i;
+ for (i = start_col + 1; i <= end_col; i++) {
+ // The column separation may shrink with the expand option.
+ if (!(flags & EXPAND))
+ printfs("+%1n", as_string(column_separation[i - 1]));
+ printfs("+\\n[%1]", span_width_reg(i, i));
+ }
+ prints(")\n");
+ printfs(".nr " NEEDED_REG " \\n[" NEEDED_REG "]/%1\n",
+ as_string(end_col - start_col + 1));
+ prints(".if \\n[" NEEDED_REG "] \\{\\\n");
+ for (i = start_col; i <= end_col; i++)
+ printfs(". nr %1 +\\n[" NEEDED_REG "]\n",
+ span_width_reg(i, i));
+ int equal_flag = 0;
+ for (i = start_col; i <= end_col && !equal_flag; i++)
+ if (equal[i] || expand[i])
+ equal_flag = 1;
+ if (equal_flag) {
+ for (i = 0; i < ncolumns; i++)
+ if (i < start_col || i > end_col)
+ printfs(". nr %1 +\\n[" NEEDED_REG "]\n",
+ span_width_reg(i, i));
+ }
+ prints(".\\}\n");
+}
+
+void table::sum_columns(int start_col, int end_col, int do_expand)
+{
+ assert(end_col > start_col);
+ int i;
+ for (i = start_col; i <= end_col; i++)
+ if (expand[i])
+ break;
+ if (i > end_col) {
+ if (do_expand)
+ return;
+ }
+ else {
+ if (!do_expand)
+ return;
+ }
+ printfs(".nr %1 \\n[%2]",
+ span_width_reg(start_col, end_col),
+ span_width_reg(start_col, start_col));
+ for (i = start_col + 1; i <= end_col; i++)
+ printfs("+(%1*\\n[" SEPARATION_FACTOR_REG "])+\\n[%2]",
+ as_string(column_separation[i - 1]),
+ span_width_reg(i, i));
+ prints('\n');
+}
+
+horizontal_span::horizontal_span(int sc, int ec, horizontal_span *p)
+: next(p), start_col(sc), end_col(ec)
+{
+}
+
+void table::build_span_list()
+{
+ span_list = 0;
+ table_entry *p = entry_list;
+ while (p) {
+ if (p->end_col != p->start_col) {
+ horizontal_span *q;
+ for (q = span_list; q; q = q->next)
+ if (q->start_col == p->start_col
+ && q->end_col == p->end_col)
+ break;
+ if (!q)
+ span_list = new horizontal_span(p->start_col, p->end_col, span_list);
+ }
+ p = p->next;
+ }
+ // Now sort span_list primarily by order of end_row, and secondarily
+ // by reverse order of start_row. This ensures that if we divide
+ // spans using the order in span_list, we will get reasonable results.
+ horizontal_span *unsorted = span_list;
+ span_list = 0;
+ while (unsorted) {
+ horizontal_span **pp;
+ for (pp = &span_list; *pp; pp = &(*pp)->next)
+ if (unsorted->end_col < (*pp)->end_col
+ || (unsorted->end_col == (*pp)->end_col
+ && (unsorted->start_col > (*pp)->start_col)))
+ break;
+ horizontal_span *tem = unsorted->next;
+ unsorted->next = *pp;
+ *pp = unsorted;
+ unsorted = tem;
+ }
+}
+
+void table::compute_overall_width()
+{
+ prints(".\\\" compute overall width\n");
+ if (!(flags & GAP_EXPAND)) {
+ if (left_separation)
+ printfs(".if n .ll -%1n\n", as_string(left_separation));
+ if (right_separation)
+ printfs(".if n .ll -%1n\n", as_string(right_separation));
+ }
+ // Compute the amount of horizontal space available for expansion,
+ // measuring every column _including_ those eligible for expansion.
+ // This is the minimum required to set the table without compression.
+ prints(".nr " EXPAND_REG " 0\n");
+ prints(".nr " AVAILABLE_WIDTH_REG " \\n[.l]-\\n[.i]");
+ for (int i = 0; i < ncolumns; i++)
+ printfs("-\\n[%1]", span_width_reg(i, i));
+ if (total_separation)
+ printfs("-%1n", as_string(total_separation));
+ prints("\n");
+ // If the "expand" region option was given, a different warning will
+ // be issued later (if "nowarn" was not also specified).
+ if ((!(flags & NOWARN)) && (!(flags & EXPAND))) {
+ prints(".if \\n[" AVAILABLE_WIDTH_REG "]<0 \\{\\\n");
+ // Protect characters in diagnostic message (especially :, [, ])
+ // from being interpreted by eqn.
+ prints(". ig\n"
+ ".EQ\n"
+ "delim off\n"
+ ".EN\n"
+ ". .\n");
+ entry_list->set_location();
+ prints(". tmc \\n[.F]:\\n[.c]: warning:\n"
+ ". tm1 \" table wider than line length minus indentation"
+ "\n");
+ prints(". ig\n"
+ ".EQ\n"
+ "delim on\n"
+ ".EN\n"
+ ". .\n");
+ prints(". nr " AVAILABLE_WIDTH_REG " 0\n");
+ prints(".\\}\n");
+ }
+ // Now do a similar computation, this time omitting columns that
+ // _aren't_ undergoing expansion. The difference is the amount of
+ // space we have to distribute among the expanded columns.
+ bool do_expansion = false;
+ for (int i = 0; i < ncolumns; i++)
+ if (expand[i]) {
+ do_expansion = true;
+ break;
+ }
+ if (do_expansion) {
+ prints(".if \\n[" AVAILABLE_WIDTH_REG "] \\\n");
+ prints(". nr " EXPAND_REG " \\n[.l]-\\n[.i]");
+ for (int i = 0; i < ncolumns; i++)
+ if (!expand[i])
+ printfs("-\\n[%1]", span_width_reg(i, i));
+ if (total_separation)
+ printfs("-%1n", as_string(total_separation));
+ prints("\n");
+ int colcount = count_expand_columns();
+ if (colcount > 1)
+ printfs(".nr " EXPAND_REG " \\n[" EXPAND_REG "]/%1\n",
+ as_string(colcount));
+ for (int i = 0; i < ncolumns; i++)
+ if (expand[i])
+ printfs(".nr %1 \\n[%1]>?\\n[" EXPAND_REG "]\n",
+ span_width_reg(i, i));
+ }
+}
+
+void table::compute_total_separation()
+{
+ if (flags & (ALLBOX | BOX | DOUBLEBOX))
+ left_separation = right_separation = 1;
+ else {
+ for (int r = 0; r < nrows; r++) {
+ if (vline[r][0] > 0)
+ left_separation = 1;
+ if (vline[r][ncolumns] > 0)
+ right_separation = 1;
+ }
+ }
+ total_separation = left_separation + right_separation;
+ for (int c = 0; c < ncolumns - 1; c++)
+ total_separation += column_separation[c];
+}
+
+void table::compute_separation_factor()
+{
+ prints(".\\\" compute column separation factor\n");
+ // Don't let the separation factor be negative.
+ prints(".nr " SEPARATION_FACTOR_REG " \\n[.l]-\\n[.i]");
+ for (int i = 0; i < ncolumns; i++)
+ printfs("-\\n[%1]", span_width_reg(i, i));
+ printfs("/%1\n", as_string(total_separation));
+ // Store the remainder for use in compute_column_positions().
+ if (flags & GAP_EXPAND) {
+ prints(".if n \\\n");
+ prints(". nr " LEFTOVER_FACTOR_REG " \\n[.l]-\\n[.i]");
+ for (int i = 0; i < ncolumns; i++)
+ printfs("-\\n[%1]", span_width_reg(i, i));
+ printfs("%%%1\n", as_string(total_separation));
+ }
+ prints(".ie \\n[" SEPARATION_FACTOR_REG "]<=0 \\{\\\n");
+ if (!(flags & NOWARN)) {
+ // Protect characters in diagnostic message (especially :, [, ])
+ // from being interpreted by eqn.
+ prints(".ig\n"
+ ".EQ\n"
+ "delim off\n"
+ ".EN\n"
+ "..\n");
+ entry_list->set_location();
+ prints(".tmc \\n[.F]:\\n[.c]: warning:\n"
+ ".tm1 \" table column separation reduced to zero\n"
+ ".nr " SEPARATION_FACTOR_REG " 0\n");
+ }
+ prints(".\\}\n"
+ ".el .if \\n[" SEPARATION_FACTOR_REG "]<1n \\{\\\n");
+ if (!(flags & NOWARN)) {
+ entry_list->set_location();
+ prints(".tmc \\n[.F]:\\n[.c]: warning:\n"
+ ".tm1 \" table column separation reduced to fit line"
+ " length\n");
+ prints(".ig\n"
+ ".EQ\n"
+ "delim on\n"
+ ".EN\n"
+ "..\n");
+ }
+ prints(".\\}\n");
+}
+
+void table::compute_column_positions()
+{
+ prints(".\\\" compute column positions\n");
+ printfs(".nr %1 0\n", column_divide_reg(0));
+ printfs(".nr %1 %2n\n", column_start_reg(0),
+ as_string(left_separation));
+ // In nroff mode, compensate for width of vertical rule.
+ if (left_separation)
+ printfs(".if n .nr %1 +1n\n", column_start_reg(0));
+ int i;
+ for (i = 1;; i++) {
+ printfs(".nr %1 \\n[%2]+\\n[%3]\n",
+ column_end_reg(i-1),
+ column_start_reg(i-1),
+ span_width_reg(i-1, i-1));
+ if (i >= ncolumns)
+ break;
+ printfs(".nr %1 \\n[%2]+(%3*\\n[" SEPARATION_FACTOR_REG "])\n",
+ column_start_reg(i),
+ column_end_reg(i-1),
+ as_string(column_separation[i-1]));
+ // If we have leftover expansion room in a table using the "expand"
+ // region option, put it prior to the last column so that the table
+ // looks as if expanded to the available line length.
+ if ((ncolumns > 2) && (flags & GAP_EXPAND) && (i == (ncolumns - 1)))
+ printfs(".if n .if \\n[" LEFTOVER_FACTOR_REG "] .nr %1 +(1n>?\\n["
+ LEFTOVER_FACTOR_REG "])\n",
+ column_start_reg(i));
+ printfs(".nr %1 \\n[%2]+\\n[%3]/2\n",
+ column_divide_reg(i),
+ column_end_reg(i-1),
+ column_start_reg(i));
+ }
+ printfs(".nr %1 \\n[%2]+%3n\n",
+ column_divide_reg(ncolumns),
+ column_end_reg(i-1),
+ as_string(right_separation));
+ printfs(".nr TW \\n[%1]\n",
+ column_divide_reg(ncolumns));
+ if (flags & DOUBLEBOX) {
+ printfs(".nr %1 +" DOUBLE_LINE_SEP "\n", column_divide_reg(0));
+ printfs(".nr %1 -" DOUBLE_LINE_SEP "\n", column_divide_reg(ncolumns));
+ }
+}
+
+void table::make_columns_equal()
+{
+ int first = -1; // index of first equal column
+ int i;
+ for (i = 0; i < ncolumns; i++)
+ if (equal[i]) {
+ if (first < 0) {
+ printfs(".nr %1 \\n[%1]", span_width_reg(i, i));
+ first = i;
+ }
+ else
+ printfs(">?\\n[%1]", span_width_reg(i, i));
+ }
+ if (first >= 0) {
+ prints('\n');
+ for (i = first + 1; i < ncolumns; i++)
+ if (equal[i])
+ printfs(".nr %1 \\n[%2]\n",
+ span_width_reg(i, i),
+ span_width_reg(first, first));
+ }
+}
+
+void table::compute_widths()
+{
+ prints(".\\\" compute column widths\n");
+ build_span_list();
+ int i;
+ horizontal_span *p;
+ // These values get refined later.
+ prints(".nr " SEPARATION_FACTOR_REG " 1n\n");
+ for (i = 0; i < ncolumns; i++) {
+ init_span_reg(i, i);
+ if (!minimum_width[i].empty())
+ printfs(".nr %1 (n;%2)\n", span_width_reg(i, i), minimum_width[i]);
+ }
+ for (p = span_list; p; p = p->next)
+ init_span_reg(p->start_col, p->end_col);
+ // Compute all field widths except for blocks.
+ table_entry *q;
+ for (q = entry_list; q; q = q->next)
+ if (!q->mod->zero_width)
+ q->do_width();
+ // Compute all span widths, not handling blocks yet.
+ for (i = 0; i < ncolumns; i++)
+ compute_span_width(i, i);
+ for (p = span_list; p; p = p->next)
+ compute_span_width(p->start_col, p->end_col);
+ // Making columns equal normally increases the width of some columns.
+ make_columns_equal();
+ // Note that divide_span keeps equal width columns equal.
+ // This function might increase the width of some columns, too.
+ for (p = span_list; p; p = p->next)
+ divide_span(p->start_col, p->end_col);
+ compute_total_separation();
+ for (p = span_list; p; p = p->next)
+ sum_columns(p->start_col, p->end_col, 0);
+ // Now handle unexpanded blocks.
+ bool had_spanning_block = false;
+ bool had_equal_block = false;
+ for (q = entry_list; q; q = q->next)
+ if (q->divert(ncolumns, minimum_width,
+ (flags & EXPAND) ? column_separation : 0, 0)) {
+ if (q->end_col > q->start_col)
+ had_spanning_block = true;
+ for (i = q->start_col; i <= q->end_col && !had_equal_block; i++)
+ if (equal[i])
+ had_equal_block = true;
+ }
+ // Adjust widths.
+ if (had_equal_block)
+ make_columns_equal();
+ if (had_spanning_block)
+ for (p = span_list; p; p = p->next)
+ divide_span(p->start_col, p->end_col);
+ compute_overall_width();
+ if ((flags & EXPAND) && total_separation != 0) {
+ compute_separation_factor();
+ for (p = span_list; p; p = p->next)
+ sum_columns(p->start_col, p->end_col, 0);
+ }
+ else {
+ // Handle expanded blocks.
+ for (p = span_list; p; p = p->next)
+ sum_columns(p->start_col, p->end_col, 1);
+ for (q = entry_list; q; q = q->next)
+ if (q->divert(ncolumns, minimum_width, 0, 1)) {
+ if (q->end_col > q->start_col)
+ had_spanning_block = true;
+ }
+ // Adjust widths again.
+ if (had_spanning_block)
+ for (p = span_list; p; p = p->next)
+ divide_span(p->start_col, p->end_col);
+ }
+ compute_column_positions();
+}
+
+void table::print_single_hline(int r)
+{
+ prints(".vs " LINE_SEP ">?\\n[.V]u\n"
+ ".ls 1\n"
+ "\\v'" BODY_DEPTH "'"
+ "\\s[\\n[" LINESIZE_REG "]]");
+ if (r > nrows - 1)
+ prints("\\D'l |\\n[TW]u 0'");
+ else {
+ int start_col = 0;
+ for (;;) {
+ while (start_col < ncolumns
+ && entry[r][start_col] != 0
+ && entry[r][start_col]->start_row != r)
+ start_col++;
+ int end_col;
+ for (end_col = start_col;
+ end_col < ncolumns
+ && (entry[r][end_col] == 0
+ || entry[r][end_col]->start_row == r);
+ end_col++)
+ ;
+ if (end_col <= start_col)
+ break;
+ printfs("\\h'|\\n[%1]u",
+ column_divide_reg(start_col));
+ if ((r > 0 && vline[r-1][start_col] == 2)
+ || (r < nrows && vline[r][start_col] == 2))
+ prints("-" HALF_DOUBLE_LINE_SEP);
+ prints("'");
+ printfs("\\D'l |\\n[%1]u",
+ column_divide_reg(end_col));
+ if ((r > 0 && vline[r-1][end_col] == 2)
+ || (r < nrows && vline[r][end_col] == 2))
+ prints("+" HALF_DOUBLE_LINE_SEP);
+ prints(" 0'");
+ start_col = end_col;
+ }
+ }
+ prints("\\s0\n");
+ prints(".ls\n"
+ ".vs\n");
+}
+
+void table::print_double_hline(int r)
+{
+ prints(".vs " LINE_SEP "+" DOUBLE_LINE_SEP
+ ">?\\n[.V]u\n"
+ ".ls 1\n"
+ "\\v'" BODY_DEPTH "'"
+ "\\s[\\n[" LINESIZE_REG "]]");
+ if (r > nrows - 1)
+ prints("\\v'-" DOUBLE_LINE_SEP "'"
+ "\\D'l |\\n[TW]u 0'"
+ "\\v'" DOUBLE_LINE_SEP "'"
+ "\\h'|0'"
+ "\\D'l |\\n[TW]u 0'");
+ else {
+ int start_col = 0;
+ for (;;) {
+ while (start_col < ncolumns
+ && entry[r][start_col] != 0
+ && entry[r][start_col]->start_row != r)
+ start_col++;
+ int end_col;
+ for (end_col = start_col;
+ end_col < ncolumns
+ && (entry[r][end_col] == 0
+ || entry[r][end_col]->start_row == r);
+ end_col++)
+ ;
+ if (end_col <= start_col)
+ break;
+ const char *left_adjust = 0;
+ if ((r > 0 && vline[r-1][start_col] == 2)
+ || (r < nrows && vline[r][start_col] == 2))
+ left_adjust = "-" HALF_DOUBLE_LINE_SEP;
+ const char *right_adjust = 0;
+ if ((r > 0 && vline[r-1][end_col] == 2)
+ || (r < nrows && vline[r][end_col] == 2))
+ right_adjust = "+" HALF_DOUBLE_LINE_SEP;
+ printfs("\\v'-" DOUBLE_LINE_SEP "'"
+ "\\h'|\\n[%1]u",
+ column_divide_reg(start_col));
+ if (left_adjust)
+ prints(left_adjust);
+ prints("'");
+ printfs("\\D'l |\\n[%1]u",
+ column_divide_reg(end_col));
+ if (right_adjust)
+ prints(right_adjust);
+ prints(" 0'");
+ printfs("\\v'" DOUBLE_LINE_SEP "'"
+ "\\h'|\\n[%1]u",
+ column_divide_reg(start_col));
+ if (left_adjust)
+ prints(left_adjust);
+ prints("'");
+ printfs("\\D'l |\\n[%1]u",
+ column_divide_reg(end_col));
+ if (right_adjust)
+ prints(right_adjust);
+ prints(" 0'");
+ start_col = end_col;
+ }
+ }
+ prints("\\s0\n"
+ ".ls\n"
+ ".vs\n");
+}
+
+void table::compute_vrule_top_adjust(int start_row, int col, string &result)
+{
+ if (row_is_all_lines[start_row] && start_row < nrows - 1) {
+ if (row_is_all_lines[start_row] == 2)
+ result = LINE_SEP ">?\\n[.V]u" "+" DOUBLE_LINE_SEP;
+ else
+ result = LINE_SEP ">?\\n[.V]u";
+ start_row++;
+ }
+ else {
+ result = "";
+ if (start_row == 0)
+ return;
+ for (stuff *p = stuff_list; p && p->row <= start_row; p = p->next)
+ if (p->row == start_row
+ && (p->is_single_line() || p->is_double_line()))
+ return;
+ }
+ int left = 0;
+ if (col > 0) {
+ table_entry *e = entry[start_row-1][col-1];
+ if (e && e->start_row == e->end_row) {
+ if (e->to_double_line_entry() != 0)
+ left = 2;
+ else if (e->to_single_line_entry() != 0)
+ left = 1;
+ }
+ }
+ int right = 0;
+ if (col < ncolumns) {
+ table_entry *e = entry[start_row-1][col];
+ if (e && e->start_row == e->end_row) {
+ if (e->to_double_line_entry() != 0)
+ right = 2;
+ else if (e->to_single_line_entry() != 0)
+ right = 1;
+ }
+ }
+ if (row_is_all_lines[start_row-1] == 0) {
+ if (left > 0 || right > 0) {
+ result += "-" BODY_DEPTH "-" BAR_HEIGHT;
+ if ((left == 2 && right != 2) || (right == 2 && left != 2))
+ result += "-" HALF_DOUBLE_LINE_SEP;
+ else if (left == 2 && right == 2)
+ result += "+" HALF_DOUBLE_LINE_SEP;
+ }
+ }
+ else if (row_is_all_lines[start_row-1] == 2) {
+ if ((left == 2 && right != 2) || (right == 2 && left != 2))
+ result += "-" DOUBLE_LINE_SEP;
+ else if (left == 1 || right == 1)
+ result += "-" HALF_DOUBLE_LINE_SEP;
+ }
+}
+
+void table::compute_vrule_bot_adjust(int end_row, int col, string &result)
+{
+ if (row_is_all_lines[end_row] && end_row > 0) {
+ end_row--;
+ result = "";
+ }
+ else {
+ stuff *p;
+ for (p = stuff_list; p && p->row < end_row + 1; p = p->next)
+ ;
+ if (p && p->row == end_row + 1 && p->is_double_line()) {
+ result = "-" DOUBLE_LINE_SEP;
+ return;
+ }
+ if ((p != 0 && p->row == end_row + 1)
+ || end_row == nrows - 1) {
+ result = "";
+ return;
+ }
+ if (row_is_all_lines[end_row+1] == 1)
+ result = LINE_SEP;
+ else if (row_is_all_lines[end_row+1] == 2)
+ result = LINE_SEP "+" DOUBLE_LINE_SEP;
+ else
+ result = "";
+ }
+ int left = 0;
+ if (col > 0) {
+ table_entry *e = entry[end_row+1][col-1];
+ if (e && e->start_row == e->end_row) {
+ if (e->to_double_line_entry() != 0)
+ left = 2;
+ else if (e->to_single_line_entry() != 0)
+ left = 1;
+ }
+ }
+ int right = 0;
+ if (col < ncolumns) {
+ table_entry *e = entry[end_row+1][col];
+ if (e && e->start_row == e->end_row) {
+ if (e->to_double_line_entry() != 0)
+ right = 2;
+ else if (e->to_single_line_entry() != 0)
+ right = 1;
+ }
+ }
+ if (row_is_all_lines[end_row+1] == 0) {
+ if (left > 0 || right > 0) {
+ result = "1v-" BODY_DEPTH "-" BAR_HEIGHT;
+ if ((left == 2 && right != 2) || (right == 2 && left != 2))
+ result += "+" HALF_DOUBLE_LINE_SEP;
+ else if (left == 2 && right == 2)
+ result += "-" HALF_DOUBLE_LINE_SEP;
+ }
+ }
+ else if (row_is_all_lines[end_row+1] == 2) {
+ if (left == 2 && right == 2)
+ result += "-" DOUBLE_LINE_SEP;
+ else if (left != 2 && right != 2 && (left == 1 || right == 1))
+ result += "-" HALF_DOUBLE_LINE_SEP;
+ }
+}
+
+void table::add_vertical_rule(int start_row, int end_row,
+ int col, int is_double)
+{
+ vrule_list = new vertical_rule(start_row, end_row, col, is_double,
+ vrule_list);
+ compute_vrule_top_adjust(start_row, col, vrule_list->top_adjust);
+ compute_vrule_bot_adjust(end_row, col, vrule_list->bot_adjust);
+}
+
+void table::build_vrule_list()
+{
+ int col;
+ if (flags & ALLBOX) {
+ for (col = 1; col < ncolumns; col++) {
+ int start_row = 0;
+ for (;;) {
+ while (start_row < nrows && vline_spanned(start_row, col))
+ start_row++;
+ if (start_row >= nrows)
+ break;
+ int end_row = start_row;
+ while (end_row < nrows && !vline_spanned(end_row, col))
+ end_row++;
+ end_row--;
+ add_vertical_rule(start_row, end_row, col, 0);
+ start_row = end_row + 1;
+ }
+ }
+ }
+ if (flags & (BOX | ALLBOX | DOUBLEBOX)) {
+ add_vertical_rule(0, nrows - 1, 0, 0);
+ add_vertical_rule(0, nrows - 1, ncolumns, 0);
+ }
+ for (int end_row = 0; end_row < nrows; end_row++)
+ for (col = 0; col < ncolumns+1; col++)
+ if (vline[end_row][col] > 0
+ && !vline_spanned(end_row, col)
+ && (end_row == nrows - 1
+ || vline[end_row+1][col] != vline[end_row][col]
+ || vline_spanned(end_row+1, col))) {
+ int start_row;
+ for (start_row = end_row - 1;
+ start_row >= 0
+ && vline[start_row][col] == vline[end_row][col]
+ && !vline_spanned(start_row, col);
+ start_row--)
+ ;
+ start_row++;
+ add_vertical_rule(start_row, end_row, col, vline[end_row][col] > 1);
+ }
+ for (vertical_rule *p = vrule_list; p; p = p->next)
+ if (p->is_double)
+ for (int r = p->start_row; r <= p->end_row; r++) {
+ if (p->col > 0 && entry[r][p->col-1] != 0
+ && entry[r][p->col-1]->end_col == p->col-1) {
+ int is_corner = r == p->start_row || r == p->end_row;
+ entry[r][p->col-1]->note_double_vrule_on_right(is_corner);
+ }
+ if (p->col < ncolumns && entry[r][p->col] != 0
+ && entry[r][p->col]->start_col == p->col) {
+ int is_corner = r == p->start_row || r == p->end_row;
+ entry[r][p->col]->note_double_vrule_on_left(is_corner);
+ }
+ }
+}
+
+void table::define_bottom_macro()
+{
+ prints(".\\\" define bottom macro\n");
+ prints(".eo\n"
+ // protect # in macro name against eqn
+ ".ig\n"
+ ".EQ\n"
+ "delim off\n"
+ ".EN\n"
+ "..\n"
+ ".de T#\n"
+ ". if !\\n[" SUPPRESS_BOTTOM_REG "] \\{\\\n"
+ ". " REPEATED_VPT_MACRO " 0\n"
+ ". mk " SAVED_VERTICAL_POS_REG "\n");
+ if (flags & (BOX | ALLBOX | DOUBLEBOX)) {
+ prints(". if \\n[T.]&\\n[" NEED_BOTTOM_RULE_REG "] \\{\\\n");
+ print_single_hline(0);
+ prints(". \\}\n");
+ }
+ prints(". ls 1\n");
+ for (vertical_rule *p = vrule_list; p; p = p->next)
+ p->contribute_to_bottom_macro(this);
+ if (flags & DOUBLEBOX)
+ prints(". if \\n[T.] \\{\\\n"
+ ". vs " DOUBLE_LINE_SEP ">?\\n[.V]u\n"
+ "\\v'" BODY_DEPTH "'\\s[\\n[" LINESIZE_REG "]]"
+ "\\D'l \\n[TW]u 0'\\s0\n"
+ ". vs\n"
+ ". \\}\n"
+ ". if \\n[" LAST_PASSED_ROW_REG "]>=0 "
+ ".nr " TOP_REG " \\n[#T]-" DOUBLE_LINE_SEP "\n"
+ ". sp -1\n"
+ "\\v'" BODY_DEPTH "'\\s[\\n[" LINESIZE_REG "]]"
+ "\\D'l 0 |\\n[" TOP_REG "]u-1v'\\s0\n"
+ ". sp -1\n"
+ "\\v'" BODY_DEPTH "'\\h'|\\n[TW]u'\\s[\\n[" LINESIZE_REG "]]"
+ "\\D'l 0 |\\n[" TOP_REG "]u-1v'\\s0\n");
+ prints(". ls\n");
+ prints(". nr " LAST_PASSED_ROW_REG " \\n[" CURRENT_ROW_REG "]\n"
+ ". sp |\\n[" SAVED_VERTICAL_POS_REG "]u\n"
+ ". " REPEATED_VPT_MACRO " 1\n");
+ if ((flags & NOKEEP) && (flags & (BOX | DOUBLEBOX | ALLBOX)))
+ prints(". if (\\n% > \\n[" STARTING_PAGE_REG "]) \\{\\\n"
+ ". tmc \\n[.F]:\\n[.c]: warning:\n"
+ ". tmc \" boxed, unkept table does not fit on page\n"
+ ". tm1 \" \\n[" STARTING_PAGE_REG "]\n"
+ ". \\}\n");
+ prints(". \\}\n"
+ "..\n"
+ ".ig\n"
+ ".EQ\n"
+ "delim on\n"
+ ".EN\n"
+ "..\n"
+ ".ec\n");
+}
+
+// is the vertical line before column c in row r horizontally spanned?
+
+int table::vline_spanned(int r, int c)
+{
+ assert(r >= 0 && r < nrows && c >= 0 && c < ncolumns + 1);
+ return (c != 0 && c != ncolumns && entry[r][c] != 0
+ && entry[r][c]->start_col != c
+ // horizontally spanning lines don't count
+ && entry[r][c]->to_double_line_entry() == 0
+ && entry[r][c]->to_single_line_entry() == 0);
+}
+
+int table::row_begins_section(int r)
+{
+ assert(r >= 0 && r < nrows);
+ for (int i = 0; i < ncolumns; i++)
+ if (entry[r][i] && entry[r][i]->start_row != r)
+ return 0;
+ return 1;
+}
+
+int table::row_ends_section(int r)
+{
+ assert(r >= 0 && r < nrows);
+ for (int i = 0; i < ncolumns; i++)
+ if (entry[r][i] && entry[r][i]->end_row != r)
+ return 0;
+ return 1;
+}
+
+void table::do_row(int r)
+{
+ printfs(".\\\" do row %1\n", i_to_a(r));
+ if (!(flags & NOKEEP) && row_begins_section(r))
+ prints(".if \\n[" USE_KEEPS_REG "] ." KEEP_MACRO_NAME "\n");
+ bool had_line = false;
+ stuff *p;
+ for (p = stuff_list; p && p->row < r; p = p->next)
+ ;
+ for (stuff *p1 = p; p1 && p1->row == r; p1 = p1->next)
+ if (!p1->printed && (p1->is_single_line() || p1->is_double_line())) {
+ had_line = true;
+ break;
+ }
+ if (!had_line && !row_is_all_lines[r])
+ printfs("." REPEATED_MARK_MACRO " %1\n", row_top_reg(r));
+ had_line = false;
+ for (; p && p->row == r; p = p->next)
+ if (!p->printed) {
+ p->print(this);
+ if (!had_line && (p->is_single_line() || p->is_double_line())) {
+ printfs("." REPEATED_MARK_MACRO " %1\n", row_top_reg(r));
+ had_line = true;
+ }
+ }
+ // change the row *after* printing the stuff list (which might contain .TH)
+ printfs("\\*[" TRANSPARENT_STRING_NAME "].nr " CURRENT_ROW_REG " %1\n",
+ as_string(r));
+ if (!had_line && row_is_all_lines[r])
+ printfs("." REPEATED_MARK_MACRO " %1\n", row_top_reg(r));
+ // we might have had a .TH, for example, since we last tried
+ if (!(flags & NOKEEP) && row_begins_section(r))
+ prints(".if \\n[" USE_KEEPS_REG "] ." KEEP_MACRO_NAME "\n");
+ printfs(".mk %1\n", row_start_reg(r));
+ prints(".mk " BOTTOM_REG "\n"
+ "." REPEATED_VPT_MACRO " 0\n");
+ int c;
+ int row_is_blank = 1;
+ int first_start_row = r;
+ for (c = 0; c < ncolumns; c++) {
+ table_entry *e = entry[r][c];
+ if (e) {
+ if (e->end_row == r) {
+ e->do_depth();
+ if (e->start_row < first_start_row)
+ first_start_row = e->start_row;
+ row_is_blank = 0;
+ }
+ c = e->end_col;
+ }
+ }
+ if (row_is_blank)
+ prints(".nr " BOTTOM_REG " +1v\n");
+ if (row_is_all_lines[r]) {
+ prints(".vs " LINE_SEP);
+ if (row_is_all_lines[r] == 2)
+ prints("+" DOUBLE_LINE_SEP);
+ prints(">?\\n[.V]u\n.ls 1\n");
+ prints("\\&");
+ prints("\\v'" BODY_DEPTH);
+ if (row_is_all_lines[r] == 2)
+ prints("-" HALF_DOUBLE_LINE_SEP);
+ prints("'");
+ for (c = 0; c < ncolumns; c++) {
+ table_entry *e = entry[r][c];
+ if (e) {
+ if (e->end_row == e->start_row)
+ e->to_simple_entry()->simple_print(1);
+ c = e->end_col;
+ }
+ }
+ prints("\n");
+ prints(".ls\n"
+ ".vs\n");
+ prints(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?\\n[.d]\n");
+ printfs(".sp |\\n[%1]u\n", row_start_reg(r));
+ }
+ for (int i = row_is_all_lines[r] ? r - 1 : r;
+ i >= first_start_row;
+ i--) {
+ simple_entry *first = 0;
+ for (c = 0; c < ncolumns; c++) {
+ table_entry *e = entry[r][c];
+ if (e) {
+ if (e->end_row == r && e->start_row == i) {
+ simple_entry *simple = e->to_simple_entry();
+ if (simple) {
+ if (!first) {
+ prints(".ta");
+ first = simple;
+ }
+ simple->add_tab();
+ }
+ }
+ c = e->end_col;
+ }
+ }
+ if (first) {
+ prints('\n');
+ first->position_vertically();
+ first->set_location();
+ prints("\\&");
+ first->simple_print(0);
+ for (c = first->end_col + 1; c < ncolumns; c++) {
+ table_entry *e = entry[r][c];
+ if (e) {
+ if (e->end_row == r && e->start_row == i) {
+ simple_entry *simple = e->to_simple_entry();
+ if (simple) {
+ if (e->end_row != e->start_row) {
+ prints('\n');
+ simple->position_vertically();
+ prints("\\&");
+ }
+ simple->simple_print(0);
+ }
+ }
+ c = e->end_col;
+ }
+ }
+ prints('\n');
+ prints(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?\\n[.d]\n");
+ printfs(".sp |\\n[%1]u\n", row_start_reg(r));
+ }
+ }
+ for (c = 0; c < ncolumns; c++) {
+ table_entry *e = entry[r][c];
+ if (e) {
+ if (e->end_row == r && e->to_simple_entry() == 0) {
+ e->position_vertically();
+ e->print();
+ prints(".nr " BOTTOM_REG " \\n[" BOTTOM_REG "]>?\\n[.d]\n");
+ printfs(".sp |\\n[%1]u\n", row_start_reg(r));
+ }
+ c = e->end_col;
+ }
+ }
+ prints("." REPEATED_VPT_MACRO " 1\n"
+ ".sp |\\n[" BOTTOM_REG "]u\n"
+ "\\*[" TRANSPARENT_STRING_NAME "].nr " NEED_BOTTOM_RULE_REG " 1\n");
+ if (r != nrows - 1 && (flags & ALLBOX)) {
+ print_single_hline(r + 1);
+ prints("\\*[" TRANSPARENT_STRING_NAME "].nr " NEED_BOTTOM_RULE_REG " 0\n");
+ }
+ if (r != nrows - 1) {
+ if (p && p->row == r + 1
+ && (p->is_single_line() || p->is_double_line())) {
+ p->print(this);
+ prints("\\*[" TRANSPARENT_STRING_NAME "].nr " NEED_BOTTOM_RULE_REG
+ " 0\n");
+ }
+ int printed_one = 0;
+ for (vertical_rule *vr = vrule_list; vr; vr = vr->next)
+ if (vr->end_row == r) {
+ if (!printed_one) {
+ prints("." REPEATED_VPT_MACRO " 0\n");
+ printed_one = 1;
+ }
+ vr->print();
+ }
+ if (printed_one)
+ prints("." REPEATED_VPT_MACRO " 1\n");
+ if (!(flags & NOKEEP) && row_ends_section(r))
+ prints(".if \\n[" USE_KEEPS_REG "] ." RELEASE_MACRO_NAME "\n");
+ }
+}
+
+void table::do_top()
+{
+ prints(".\\\" do top\n");
+ prints(".ss \\n[" SAVED_INTER_WORD_SPACE_SIZE "]\n");
+ prints(".fc \002\003\n");
+ if (flags & (BOX | DOUBLEBOX | ALLBOX))
+ prints(".nr " IS_BOXED_REG " 1\n");
+ else
+ prints(".nr " IS_BOXED_REG " 0\n");
+ if (!(flags & NOKEEP) && (flags & (BOX | DOUBLEBOX | ALLBOX)))
+ prints("." TABLE_KEEP_MACRO_NAME "\n");
+ if (flags & DOUBLEBOX) {
+ prints(".ls 1\n"
+ ".vs " LINE_SEP ">?\\n[.V]u\n"
+ "\\v'" BODY_DEPTH "'\\s[\\n[" LINESIZE_REG "]]\\D'l \\n[TW]u 0'\\s0\n"
+ ".vs\n"
+ "." REPEATED_MARK_MACRO " " TOP_REG "\n"
+ ".vs " DOUBLE_LINE_SEP ">?\\n[.V]u\n");
+ printfs("\\v'" BODY_DEPTH "'"
+ "\\s[\\n[" LINESIZE_REG "]]"
+ "\\h'\\n[%1]u'"
+ "\\D'l |\\n[%2]u 0'"
+ "\\s0"
+ "\n",
+ column_divide_reg(0),
+ column_divide_reg(ncolumns));
+ prints(".ls\n"
+ ".vs\n");
+ }
+ else if (flags & (ALLBOX | BOX))
+ print_single_hline(0);
+ // On terminal devices, a vertical rule on the first row of the table
+ // will stick out 1v above it if it the table is unboxed or lacks a
+ // horizontal rule on the first row. This is necessary for grotty's
+ // rule intersection detection. We must make room for it so that the
+ // vertical rule is not drawn above the top of the page.
+ else if ((flags & HAS_TOP_VLINE) && !(flags & HAS_TOP_HLINE))
+ prints(".if n .sp\n");
+ prints(".nr " STARTING_PAGE_REG " \\n%\n");
+ //printfs(".mk %1\n", row_top_reg(0));
+}
+
+void table::do_bottom()
+{
+ prints(".\\\" do bottom\n");
+ // print stuff after last row
+ for (stuff *p = stuff_list; p; p = p->next)
+ if (p->row > nrows - 1)
+ p->print(this);
+ if (!(flags & NOKEEP))
+ prints(".if \\n[" USE_KEEPS_REG "] ." RELEASE_MACRO_NAME "\n");
+ printfs(".mk %1\n", row_top_reg(nrows));
+ prints(".nr " NEED_BOTTOM_RULE_REG " 1\n"
+ ".nr T. 1\n"
+ // protect # in macro name against eqn
+ ".ig\n"
+ ".EQ\n"
+ "delim off\n"
+ ".EN\n"
+ "..\n"
+ ".T#\n"
+ ".ig\n"
+ ".EQ\n"
+ "delim on\n"
+ ".EN\n"
+ "..\n");
+ if (!(flags & NOKEEP) && (flags & (BOX | DOUBLEBOX | ALLBOX)))
+ prints("." TABLE_RELEASE_MACRO_NAME "\n");
+ if (flags & DOUBLEBOX)
+ prints(".sp " DOUBLE_LINE_SEP "\n");
+ // Horizontal box lines take up an entire row on nroff devices (maybe
+ // a half-row if we ever support [emulators of] devices like the
+ // Teletype Model 37 with half-line motions).
+ if (flags & (BOX | DOUBLEBOX | ALLBOX))
+ prints(".if n .sp\n");
+ // Space again for the doublebox option, until we can draw that more
+ // attractively; see Savannah #43637.
+ if (flags & DOUBLEBOX)
+ prints(".if n .sp\n");
+ prints("." RESET_MACRO_NAME "\n"
+ ".nn \\n[" SAVED_NUMBERING_SUPPRESSION_COUNT "]\n"
+ ".ie \\n[" SAVED_NUMBERING_LINENO "] "
+ ".nm \\n[" SAVED_NUMBERING_LINENO "]\n"
+ ".el .nm\n"
+ ".fc\n"
+ ".cp \\n(" COMPATIBLE_REG "\n");
+}
+
+int table::get_nrows()
+{
+ return nrows;
+}
+
+const char *last_filename = 0;
+
+void set_troff_location(const char *fn, int ln)
+{
+ if (!location_force_filename && last_filename != 0
+ && strcmp(fn, last_filename) == 0)
+ printfs(".lf %1\n", as_string(ln));
+ else {
+ string filename(fn);
+ filename += '\0';
+ normalize_for_lf(filename);
+ printfs(".lf %1 %2\n", as_string(ln), filename.contents());
+ last_filename = fn;
+ location_force_filename = 0;
+ }
+}
+
+void printfs(const char *s, const string &arg1, const string &arg2,
+ const string &arg3, const string &arg4, const string &arg5)
+{
+ if (s) {
+ char c;
+ while ((c = *s++) != '\0') {
+ if (c == '%') {
+ switch (*s++) {
+ case '1':
+ prints(arg1);
+ break;
+ case '2':
+ prints(arg2);
+ break;
+ case '3':
+ prints(arg3);
+ break;
+ case '4':
+ prints(arg4);
+ break;
+ case '5':
+ prints(arg5);
+ break;
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ break;
+ case '%':
+ prints('%');
+ break;
+ default:
+ assert(0 == "printfs format character not in [1-9%]");
+ }
+ }
+ else
+ prints(c);
+ }
+ }
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/tbl/table.h b/src/preproc/tbl/table.h
new file mode 100644
index 0000000..62346fa
--- /dev/null
+++ b/src/preproc/tbl/table.h
@@ -0,0 +1,179 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "cset.h"
+#include "cmap.h"
+#include "stringclass.h"
+#include "errarg.h"
+#include "error.h"
+#include "lf.h"
+
+// PREFIX and PREFIX_CHAR must be the same.
+#define PREFIX "3"
+#define PREFIX_CHAR '3'
+
+// LEADER and LEADER_CHAR must be the same.
+#define LEADER "a"
+#define LEADER_CHAR 'a'
+
+struct inc_number {
+ short inc;
+ short val;
+};
+
+struct entry_modifier {
+ inc_number point_size;
+ inc_number vertical_spacing;
+ string font;
+ string macro;
+ enum { CENTER, TOP, BOTTOM } vertical_alignment;
+ char zero_width;
+ char stagger;
+
+ entry_modifier();
+ ~entry_modifier();
+};
+
+enum format_type {
+ FORMAT_LEFT,
+ FORMAT_CENTER,
+ FORMAT_RIGHT,
+ FORMAT_NUMERIC,
+ FORMAT_ALPHABETIC,
+ FORMAT_SPAN,
+ FORMAT_VSPAN,
+ FORMAT_HLINE,
+ FORMAT_DOUBLE_HLINE
+};
+
+struct entry_format : public entry_modifier {
+ format_type type;
+
+ entry_format(format_type);
+ entry_format();
+ void debug_print() const;
+};
+
+class table_entry;
+struct horizontal_span;
+struct stuff;
+struct vertical_rule;
+
+class table {
+ int nrows;
+ int ncolumns;
+ int linesize;
+ char delim[2];
+ char decimal_point_char;
+ vertical_rule *vrule_list;
+ stuff *stuff_list;
+ horizontal_span *span_list;
+ table_entry *entry_list;
+ table_entry **entry_list_tailp;
+ table_entry ***entry;
+ char **vline;
+ char *row_is_all_lines;
+ string *minimum_width;
+ int *column_separation;
+ char *equal;
+ int left_separation; // from a vertical rule or box border, in ens
+ int right_separation; // from a vertical rule or box border, in ens
+ int total_separation;
+ int allocated_rows;
+ void build_span_list();
+ void compute_overall_width();
+ void do_hspan(int r, int c);
+ void do_vspan(int r, int c);
+ void allocate(int r);
+ void compute_widths();
+ void divide_span(int, int);
+ void sum_columns(int, int, int);
+ void compute_total_separation();
+ void compute_separation_factor();
+ void compute_column_positions();
+ void do_row(int);
+ void init_output();
+ void add_stuff(stuff *);
+ void do_top();
+ void do_bottom();
+ void do_vertical_rules();
+ void build_vrule_list();
+ void add_vertical_rule(int, int, int, int);
+ void define_bottom_macro();
+ int vline_spanned(int r, int c);
+ int row_begins_section(int);
+ int row_ends_section(int);
+ void make_columns_equal();
+ void compute_vrule_top_adjust(int, int, string &);
+ void compute_vrule_bot_adjust(int, int, string &);
+ void determine_row_type();
+ int count_expand_columns();
+public:
+ unsigned flags;
+ enum {
+ CENTER = 0x00000001,
+ EXPAND = 0x00000002,
+ BOX = 0x00000004,
+ ALLBOX = 0x00000008,
+ DOUBLEBOX = 0x00000010,
+ NOKEEP = 0x00000020,
+ NOSPACES = 0x00000040,
+ NOWARN = 0x00000080,
+ // The next few properties help manage nroff mode output.
+ HAS_TOP_VLINE = 0x00000100,
+ HAS_TOP_HLINE = 0x00000200,
+ GAP_EXPAND = 0x00000400,
+ EXPERIMENTAL = 0x80000000 // undocumented
+ };
+ char *expand;
+ table(int nc, unsigned flags, int linesize, char decimal_point_char);
+ ~table();
+
+ void add_text_line(int r, const string &, const char *, int);
+ void add_single_hline(int r);
+ void add_double_hline(int r);
+ void add_entry(int r, int c, const string &, const entry_format *,
+ const char *, int lineno);
+ void add_vlines(int r, const char *);
+ void check();
+ void print();
+ void set_minimum_width(int c, const string &w);
+ void set_column_separation(int c, int n);
+ void set_equal_column(int c);
+ void set_expand_column(int c);
+ void set_delim(char c1, char c2);
+ void print_single_hline(int r);
+ void print_double_hline(int r);
+ int get_nrows();
+};
+
+void set_troff_location(const char *, int);
+
+extern int compatible_flag;
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/preproc/tbl/tbl.1.man b/src/preproc/tbl/tbl.1.man
new file mode 100644
index 0000000..4c9a98a
--- /dev/null
+++ b/src/preproc/tbl/tbl.1.man
@@ -0,0 +1,2018 @@
+'\" t
+.TH @g@tbl @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@tbl \- prepare tables for
+.I groff
+documents
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2023 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_tbl_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@tbl
+.RB [ \-C ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@tbl
+.B \-\-help
+.YS
+.
+.
+.SY @g@tbl
+.B \-v
+.
+.SY @g@tbl
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU implementation of
+.I tbl \" generic
+is part of the
+.MR groff @MAN1EXT@
+document formatting system.
+.
+.I @g@tbl
+is a
+.MR @g@troff @MAN1EXT@
+preprocessor that translates descriptions of tables embedded in
+.MR roff @MAN7EXT@
+input files into the language understood by
+.IR @g@troff .
+.
+It copies the contents of each
+.I file
+to the standard output stream,
+except that lines between
+.B .TS
+and
+.B .TE
+are interpreted as table descriptions.
+.
+While GNU
+.IR tbl 's \" GNU
+input syntax is highly compatible with AT&T
+.IR tbl , \" AT&T
+the output GNU
+.I tbl \" GNU
+produces cannot be processed by AT&T
+.IR troff ; \" AT&T
+GNU
+.I troff \" GNU
+(or a
+.I troff \" generic
+implementing any GNU extensions employed)
+must be used.
+.
+Normally,
+.I @g@tbl
+is not executed directly by the user,
+but invoked by specifying the
+.B \-t
+option to
+.MR groff @MAN1EXT@ .
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I @g@tbl
+reads the standard input stream.
+.
+.
+.\" ====================================================================
+.SS Overview
+.\" ====================================================================
+.
+.I @g@tbl
+expects to find table descriptions between input lines that begin with
+.B .TS
+(table start)
+and
+.B .TE
+(table end).
+.
+Each such
+.I table region
+encloses one or more table descriptions.
+.
+Within a table region,
+table descriptions beyond the first must each be preceded
+by an input line beginning with
+.BR .T& .
+.
+This mechanism does not start a new table region;
+all table descriptions are treated as part of their
+.BR .TS / .TE
+enclosure,
+even if they are boxed or have column headings that repeat on subsequent
+pages
+(see below).
+.
+.
+.P
+(Experienced
+.I roff
+users should observe that
+.I @g@tbl
+is not a
+.I roff
+language interpreter:
+the default control character must be used,
+and no spaces or tabs are permitted between the control character and
+the macro name.
+.
+These
+.I @g@tbl
+input tokens remain as-is in the output,
+where they become ordinary macro calls.
+.
+Macro packages often define
+.BR TS ,
+.BR T& ,
+and
+.B TE
+macros to handle issues of table placement on the page.
+.
+.I @g@tbl
+produces
+.I groff
+code to define these macros as empty if their definitions do not exist
+when the formatter encounters a table region.)
+.
+.
+.P
+Each table region may begin with
+.I region options,
+and must contain one or more
+.I table definitions;
+each table definition contains a
+.I format specification
+followed by one or more input lines (rows) of
+.I entries.
+.
+These entries comprise the
+.I table data.
+.
+.
+.
+.\" ====================================================================
+.SS "Region options"
+.\" ====================================================================
+.
+The line immediately following the
+.B .TS
+token may specify region options,
+keywords that influence the interpretation or rendering of the region as
+a whole or all table entries within it indiscriminately.
+.
+They must be separated by commas,
+spaces,
+or tabs.
+.
+Those that require a parenthesized argument permit spaces and tabs
+between the option's name and the opening parenthesis.
+.
+Options accumulate and cannot be unset within a region once declared;
+if an option that takes a parameter is repeated,
+the last occurrence controls.
+.
+If present,
+the set of region options must be terminated with a semicolon
+.RB ( ; ).
+.
+.
+.P
+Any of the
+.BR allbox ,
+.BR box ,
+.BR doublebox ,
+.BR frame ,
+and
+.B doubleframe
+region options makes a table \[lq]boxed\[rq] for the purpose of later
+discussion.
+.
+.
+.TP
+.B allbox
+Enclose each table entry in a box;
+implies
+.BR box .
+.
+.
+.TP
+.B box
+Enclose the entire table region in a box.
+.
+As a GNU extension,
+the alternative option name
+.B frame
+is also recognized.
+.
+.
+.TP
+.B center
+Center the table region with respect to the current indentation and line
+length;
+the default is to left-align it.
+.
+As a GNU extension,
+the alternative option name
+.B centre
+is also recognized.
+.
+.
+.TP
+.BI decimalpoint( c )
+Recognize character
+.I c
+as the decimal separator in columns using the
+.B N
+(numeric) classifier
+(see subsection \[lq]Column classifiers\[rq] below).
+.
+This is a GNU extension.
+.
+.
+.TP
+.BI delim( xy )
+Recognize characters
+.I x
+.RI and\~ y
+as start and end delimiters,
+respectively,
+for
+.MR @g@eqn @MAN1EXT@
+input,
+and ignore input between them.
+.
+.I x
+.RI and\~ y
+need not be distinct.
+.
+.
+.TP
+.B doublebox
+Enclose the entire table region in a double box;
+implies
+.BR box .
+.
+As a GNU extension,
+the alternative option name
+.B \%doubleframe
+is also recognized.
+.
+.
+.TP
+.B expand
+Spread the table horizontally to fill the available space
+(line length minus indentation)
+by increasing column separation.
+.
+Ordinarily,
+a table is made only as wide as necessary to accommodate the widths of
+its entries and its column separations
+(whether specified or default).
+.
+When
+.B expand
+applies to a table that exceeds the available horizontal space,
+column separation is reduced as far as necessary
+(even to zero).
+.
+.I @g@tbl
+produces
+.I groff
+input that issues a diagnostic if such compression occurs.
+.
+The column modifier
+.B x
+(see below)
+overrides this option.
+.
+.
+.TP
+.BI linesize( n )
+Draw lines or rules
+(e.g.,
+from
+.BR box )
+with a thickness of
+.IR n \~points.
+.
+The default is the current type size when the region begins.
+.
+This option is ignored on terminal devices.
+.
+.
+.TP
+.B nokeep
+Don't use
+.I roff
+diversions to manage page breaks.
+.
+Normally,
+.I @g@tbl
+employs them to avoid breaking a page within a table row.
+.
+This usage can sometimes interact badly with macro packages' own use of
+diversions\[em]when footnotes,
+for example,
+are employed.
+.
+This is a GNU extension.
+.
+.
+.TP
+.B nospaces
+Ignore leading and trailing spaces in table entries.
+.
+This is a GNU extension.
+.
+.
+.TP
+.B nowarn
+Suppress diagnostic messages produced at document formatting time when
+the line or page lengths are inadequate to contain a table row.
+.
+This is a GNU extension.
+.
+.
+.\" TODO: How about "right"? (and "left" for symmetry)
+.TP
+.BI tab( c )
+Use the character
+.I c
+instead of a tab to separate entries in a row of table data.
+.
+.
+.\" ====================================================================
+.SS "Table format specification"
+.\" ====================================================================
+.
+The table format specification is mandatory:
+it determines the number of columns in the table and directs how the
+entries within it are to be typeset.
+.
+The format specification is a series of column
+.I descriptors.
+.
+Each descriptor encodes a
+.I classifier
+followed by zero or more
+.I modifiers.
+.
+Classifiers are letters
+(recognized case-insensitively)
+or punctuation symbols;
+modifiers consist of or begin with letters or numerals.
+.
+Spaces,
+tabs,
+newlines,
+and commas separate descriptors.
+.
+Newlines and commas are special;
+they apply the descriptors following them to a subsequent row of the
+table.
+.
+(This enables column headings to be centered or emboldened while the
+table entries for the data are not,
+for instance.)
+.
+We term the resulting group of column descriptors a
+.I row definition.
+.
+Within a row definition,
+separation between column descriptors
+(by spaces or tabs)
+is often optional;
+only some modifiers,
+described below,
+make separation necessary.
+.
+.
+.P
+Each column descriptor begins with a mandatory
+.I classifier,
+a character that selects from one of several arrangements.
+.
+Some determine the positioning of table entries within a rectangular
+cell:
+centered,
+left-aligned,
+numeric
+(aligned to a configurable decimal separator),
+and so on.
+.
+Others perform special operations like drawing lines or spanning entries
+from adjacent cells in the table.
+.
+Except for
+.RB \[lq] | \[rq],
+any classifier can be followed by one or more
+.I modifiers;
+some of these accept an argument,
+which in GNU
+.I tbl \" GNU
+can be parenthesized.
+.\" AT&T tbl allowed parentheses only after 'w'.
+.\" TODO: Accept parentheses after 'p' and 'v'.
+.
+Modifiers select fonts,
+set the type size,
+.\"define the column width,
+.\"adjust inter-column spacing, \" slack text for window/orphan control
+and perform other tasks described below.
+.
+.
+.P
+The format specification can occupy multiple input lines,
+but must conclude with a dot
+.RB \[lq] .\& \[rq]
+followed by a newline.
+.
+Each row definition is applied in turn to one row of the table.
+.
+The last row definition is applied to rows of table data in excess of
+the row definitions.
+.
+.
+.P
+For clarity in this document's examples,
+we shall write classifiers in uppercase and modifiers in lowercase.
+.
+Thus,
+.RB \[lq] CbCb,LR.\& \[rq]
+defines two rows of two columns.
+.
+The first row's entries are centered and boldfaced;
+the second and any further rows' first and second columns are left- and
+right-aligned,
+respectively.
+.
+.\" slack text for window/orphan control
+.\"If more rows of entries are added to the table data,
+.\"they reuse the row definition
+.\".RB \[lq] LR \[rq].
+.
+.
+.P
+The row definition with the most column descriptors determines the
+number of columns in the table;
+any row definition with fewer is implicitly extended on the right-hand
+side with
+.B L
+classifiers as many times as necessary to make the table rectangular.
+.
+.
+.\" ====================================================================
+.SS "Column classifiers"
+.\" ====================================================================
+.
+The
+.BR L ,
+.BR R ,
+and
+.B C
+classifiers are the easiest to understand and use.
+.
+.
+.TP
+.BR A ,\~ a
+Center longest entry in this column,
+left-align remaining entries in the column with respect to the centered
+entry,
+then indent all entries by one en.
+.
+Such \[lq]alphabetic\[rq] entries
+(hence the name of the classifier)
+can be used in the same column as
+.BR L -classified
+entries,
+as in
+.RB \[lq] LL,AR.\& \[rq].
+.
+The
+.B A
+entries are often termed \[lq]sub-columns\[rq] due to their indentation.
+.
+.
+.TP
+.BR C ,\~ c
+Center entry within the column.
+.
+.
+.TP
+.BR L ,\~ l
+Left-align entry within the column.
+.
+.
+.TP
+.BR N ,\~ n
+Numerically align entry in the column.
+.
+.I @g@tbl
+aligns columns of numbers vertically at the units place.
+.
+If multiple decimal separators are adjacent to a digit,
+it uses the rightmost one for vertical alignment.
+.
+If there is no decimal separator,
+the rightmost digit is used for vertical alignment;
+otherwise,
+.I @g@tbl
+centers the entry within the column.
+.
+The
+.I roff
+dummy character
+.B \[rs]&
+in an entry marks the glyph preceding it
+(if any)
+as the units place;
+if multiple instances occur in the data,
+the leftmost is used for alignment.
+.
+.
+.IP
+If
+.BR N -classified
+entries share a column with
+.B L
+or
+.BR R \~entries,
+.I @g@tbl
+centers the widest
+.BR N \~entry
+with respect to the widest
+.B L
+or
+.BR R \~entry,
+preserving the alignment of
+.BR N \~entries
+with respect to each other.
+.
+.
+.IP
+The appearance of
+.I @g@eqn
+equations
+within
+.BR N -classified
+columns
+can be troublesome due to the foregoing textual scan for a decimal
+separator.
+.
+Use the
+.B \%delim
+region option to make
+.I @g@tbl
+ignore the data within
+.I eqn
+delimiters for that purpose.
+.
+.
+.TP
+.BR R ,\~ r
+Right-align entry within the column.
+.
+.
+.TP
+.BR S ,\~ s
+Span previous entry on the left into this column.
+.
+.
+.TP
+.B \[ha]
+Span entry in the same column from the previous row into this row.
+.
+.
+.TP
+.BR _ ,\~ \-
+Replace table entry with a horizontal rule.
+.
+An empty table entry is expected to correspond to this classifier;
+if data are found there,
+.I @g@tbl
+issues a diagnostic message.
+.
+.
+.TP
+.B =
+Replace table entry with a double horizontal rule.
+.
+An empty table entry is expected to correspond to this classifier;
+if data are found there,
+.I @g@tbl
+issues a diagnostic message.
+.
+.
+.TP
+.B |
+Place a vertical rule (line) on the corresponding row of the table
+(if two of these are adjacent,
+a double vertical rule).
+.
+This classifier does not contribute to the column count and no table
+entries correspond to it.
+.
+A
+.B |
+to the left of the first column descriptor or to the right of the last
+one produces a vertical rule at the edge of the table;
+these are redundant
+(and ignored)
+in boxed tables.
+.
+.
+.P
+To change the table format within a
+.I @g@tbl
+region,
+use the
+.B .T&
+token at the start of a line.
+.
+It is followed by a format specification and table data,
+but
+.I not
+region options.
+.
+The quantity of columns in a new table format thus introduced cannot
+increase relative to the previous table format;
+in that case,
+you must end the table region and start another.
+.
+If that will not serve because the region uses box options or the
+columns align in an undesirable manner,
+you must design the initial table format specification to include the
+maximum quantity of columns required,
+and use the
+.B S
+horizontal spanning classifier where necessary to achieve the desired
+columnar alignment.
+.
+.
+.P
+Attempting to horizontally span in the first column or vertically span
+on the first row is an error.
+.
+Non-rectangular span areas are also not supported.
+.
+.
+.\" ====================================================================
+.SS "Column modifiers"
+.\" ====================================================================
+.
+Any number of modifiers can follow a column classifier.
+.
+Arguments to modifiers,
+where accepted,
+are case-sensitive.
+.
+If the same modifier is applied to a column specifier more than once,
+or if conflicting modifiers are applied,
+only the last occurrence has effect.
+.
+The
+.RB modifier\~ x
+is mutually exclusive with
+.B e
+.RB and\~ w ,
+but
+.B e
+is not mutually exclusive
+.RB with\~ w ;
+if these are used in combination,
+.BR x \~unsets
+both
+.B e
+.RB and\~ w ,
+while either
+.B e
+or
+.B w
+.RB overrides\~ x .
+.
+.
+.br
+.ne 4v \" Keep next two tagged paragraphs together.
+.TP
+.BR b ,\~ B
+Typeset entry in boldface,
+abbreviating
+.BR f(B) .
+.
+.
+.TP
+.BR d ,\~ D
+Align a vertically spanned table entry to the bottom
+(\[lq]down\[rq]),
+instead of the center,
+of its range.
+.
+This is a GNU extension.
+.
+.
+.TP
+.BR e ,\~ E
+Equalize the widths of columns with this modifier.
+.
+The column with the largest width controls.
+.
+This modifier sets the default line length used in a text block.
+.
+.
+.TP
+.BR f ,\~ F
+Select the typeface for the table entry.
+.
+This modifier must be followed by a font or style name
+(one or two characters not starting with a digit),
+font mounting position
+(a single digit),
+or a name or mounting position of any length in parentheses.
+.
+The last form is a GNU extension.
+.
+(The parameter corresponds to that accepted by the
+.I troff \" generic
+.B ft
+request.)
+.
+A one-character argument not in parentheses must be separated by one or
+more spaces or tabs from what follows.
+.
+.
+.TP
+.BR i ,\~ I
+Typeset entry in an oblique or italic face,
+abbreviating
+.BR f(I) .
+.
+.
+.TP
+.BR m ,\~ M
+Call a
+.I groff
+macro before typesetting a text block
+(see subsection \[lq]Text blocks\[rq] below).
+.
+This is a GNU extension.
+.
+This modifier must be followed by a macro name of one or two characters
+or a name of any length in parentheses.
+.
+A one-character macro name not in parentheses must be separated by one
+or more spaces or tabs from what follows.
+.
+The named macro must be defined before the table region containing this
+column modifier is encountered.
+.
+The macro should contain only simple
+.I groff
+requests to change text formatting,
+like adjustment or hyphenation.
+.
+The macro is called
+.I after
+the column modifiers
+.BR b ,
+.BR f ,
+.BR i ,
+.BR p ,
+and
+.B v
+take effect;
+it can thus override other column modifiers.
+.
+.
+.TP
+.BR p ,\~ P
+Set the type size for the table entry.
+.
+This modifier must be followed by an
+.RI integer\~ n
+with an optional leading sign.
+.
+If unsigned,
+the type size is set to
+.IR n \~scaled
+points.
+.
+Otherwise,
+the type size is incremented or decremented per the sign by
+.IR n \~scaled
+points.
+.
+The use of a signed multi-digit number is a GNU extension.
+.
+(The parameter corresponds to that accepted by the
+.I troff \" generic
+.B ps
+request.)
+.
+If a type size modifier is followed by a column separation modifier
+(see below),
+they must be separated by at least one space or tab.
+.\" TODO: Allow parentheses so scaling units and fractional values can
+.\" be used?
+.
+.
+.TP
+.BR t ,\~ T
+Align a vertically spanned table entry to the top,
+instead of the center,
+of its range.
+.
+.
+.TP
+.BR u ,\~ U
+Move the column up one half-line,
+\[lq]staggering\[rq] the rows.
+.
+This is a GNU extension.
+.
+.
+.TP
+.BR v ,\~ V
+Set the vertical spacing to be used in a text block.
+.
+This modifier must be followed by an
+.RI integer\~ n
+with an optional leading sign.
+.
+If unsigned,
+the vertical spacing is set to
+.IR n\~ points.
+.
+Otherwise,
+the vertical spacing is incremented or decremented per the sign by
+.IR n\~ points.
+.
+The use of a signed multi-digit number is a GNU extension.
+.
+(This parameter corresponds to that accepted by the
+.I troff \" generic
+.B vs
+request.)
+.
+If a vertical spacing modifier is followed by a column separation
+modifier
+(see below),
+they must be separated by at least one space or tab.
+.\" TODO: Allow parentheses so scaling units and fractional values can
+.\" be used?
+.
+.
+.TP
+.BR w ,\~ W
+Set the column's minimum width.
+.
+This modifier must be followed by a number,
+which is either a unitless integer,
+or a
+.I roff
+horizontal measurement in parentheses.
+.
+Parentheses are required if the width is to be followed immediately by
+an explicit column separation
+(alternatively,
+follow the width with one or more spaces or tabs).
+.
+If no unit is specified,
+ens are assumed.
+.
+This modifier sets the default line length used in a text block.
+.
+.
+.TP
+.BR x ,\~ X
+Expand the column.
+.
+After computing the column widths,
+distribute any remaining line length evenly over all columns bearing
+this modifier.
+.
+Applying the
+.BR x \~modifier
+to more than one column is a GNU extension.
+.\" 'x' wasn't documented at all in Lesk 1979.
+.
+This modifier sets the default line length used in a text block.
+.
+.
+.TP
+.BR z ,\~ Z
+Ignore the table entries corresponding to this column for width
+calculation purposes;
+that is,
+compute the column's width using only the information in its descriptor.
+.
+.
+.TP
+.I n
+A numeric suffix on a column descriptor sets the separation distance
+(in ens)
+from the succeeding column;
+the default separation is
+.BR 3n .
+.
+This separation is
+proportionally multiplied if the
+.B expand
+region option is in effect;
+in the case of tables wider than the output line length,
+this separation might be zero.
+.
+A negative separation cannot be specified.
+.
+A separation amount after the last column in a row is nonsensical and
+provokes a diagnostic from
+.IR @g@tbl .
+.
+.
+.\" ====================================================================
+.SS "Table data"
+.\" ====================================================================
+.
+The table data come after the format specification.
+.
+Each input line corresponds to a table row,
+except that a backslash at the end of a line of table data continues an
+entry on the next input line.
+.
+(Text blocks,
+discussed below,
+also spread table entries across multiple input lines.)
+.
+Table entries within a row are separated in the input by a tab character
+by default;
+see the
+.B tab
+region option above.
+.
+Excess entries in a row of table data
+(those that have no corresponding column descriptor,
+not even an implicit one arising from rectangularization of the table)
+are discarded with a diagnostic message.
+.
+.I roff
+control lines are accepted between rows of table data and within text
+blocks.
+.
+If you wish to visibly mark an empty table entry in the document source,
+populate it with the
+.B \[rs]&
+.I roff
+dummy character.
+.
+The table data are interrupted by a line consisting of the
+.B .T&
+input token,
+and conclude with the line
+.BR .TE .
+.
+.
+.P
+Ordinarily,
+a table entry is typeset rigidly.
+.
+It is not filled,
+broken,
+hyphenated,
+adjusted,
+or populated with additional inter-sentence space.
+.
+.I @g@tbl
+instructs the formatter to measure each table entry as it occurs in the
+input,
+updating the width required by its corresponding column.
+.
+If the
+.B z
+modifier applies to the column,
+this measurement is ignored;
+if
+.B w
+applies and its argument is larger than this width,
+that argument is used instead.
+.
+In contrast to conventional
+.I roff
+input
+(within a paragraph,
+say),
+changes to text formatting,
+such as font selection or vertical spacing,
+do not persist between entries.
+.
+.
+.P
+Several forms of table entry are interpreted specially.
+.
+.
+.IP \[bu] 2n
+If a table row contains only an underscore or equals sign
+.RB ( _
+or
+.BR = ),
+a single or double horizontal rule (line),
+respectively,
+is drawn across the table at that point.
+.
+.
+.IP \[bu] 2n
+A table entry containing only
+.B _
+or
+.B =
+on an otherwise populated row is replaced by a single or double
+horizontal rule,
+respectively,
+joining its
+neighbors.
+.
+.
+.IP \[bu] 2n
+Prefixing a lone underscore or equals sign with a backslash also has
+meaning.
+.
+If a table entry consists only of
+.B \[rs]_
+or
+.B \[rs]=
+on an otherwise populated row,
+it is replaced by a single or double horizontal rule,
+respectively,
+that does
+.I not
+(quite) join its neighbors.
+.
+.
+.IP \[bu]
+A table entry consisting of
+.BI \[rs]R x\c
+,
+where
+.IR x \~is
+any
+.I roff
+ordinary or special character,
+is replaced by enough repetitions of the glyph corresponding
+.RI to\~ x
+to fill the column,
+albeit without joining its neighbors.
+.\" TODO: Bad things happen if there's garbage in the entry after 'x',
+.\" which can be a *roff special character escape sequence, so
+.\" validation is not trivial.
+.
+.
+.IP \[bu]
+On any row but the first,
+a table entry of
+.B \[rs]\[ha]
+causes the entry above it to span down into the current one.
+.
+.
+.P
+On occasion,
+these special tokens may be required as literal table data.
+.
+To use either
+.B _
+or
+.B =
+literally and alone in an entry,
+prefix or suffix it with the
+.I roff
+dummy character
+.BR \[rs]& .
+.
+To express
+.BR \[rs]_ ,
+.BR \[rs]= ,
+or
+.BR \[rs]R ,
+use a
+.I roff
+escape sequence to interpolate the backslash
+.RB ( \[rs]e
+or
+.BR \[rs][rs] ).
+.
+A reliable way to emplace the
+.B \[rs]\[ha]
+glyph sequence within a table entry is to use a pair of
+.I groff
+special character escape sequences
+.RB ( \[rs][rs]\[rs][ha] ).
+.
+.
+.P
+Rows of table entries can be interleaved with
+.I groff
+control lines;
+these do not count as table data.
+.
+On such lines the default control character
+.RB ( .\& )
+must be used
+(and not changed);
+the no-break control character is not recognized.
+.
+To start the first table entry in a row with a dot,
+precede it with the
+.I roff
+dummy character
+.BR \[rs]& .
+.
+.
+.\" ====================================================================
+.SS "Text blocks"
+.\" ====================================================================
+.
+An ordinary table entry's contents can make a column,
+and therefore the table,
+excessively wide;
+the table then exceeds the line length of the page,
+and becomes ugly or is exposed to truncation by the output device.
+.
+When a table entry requires more conventional typesetting,
+breaking across more than one output line
+(and thereby increasing the height of its row),
+it can be placed within a
+.I text block.
+.
+.
+.P
+.I @g@tbl
+interprets a table entry beginning with
+.RB \[lq] T{ \[rq]
+at the end of an input line not as table data,
+but as a token starting a text block.
+.
+Similarly,
+.RB \[lq] T} \[rq]
+at the start of an input line ends a text block;
+it must also end the table entry.
+.
+Text block tokens can share an input line with other table data
+(preceding
+.B T{
+and following
+.BR T} ).
+.
+Input lines between these tokens are formatted in a diversion by
+.IR troff . \" generic
+.
+Text blocks cannot be nested.
+.
+Multiple text blocks can occur in a table row.
+.
+.
+.P
+Text blocks are formatted as was the text prior to the table,
+modified by applicable column descriptors.
+.
+Specifically,
+the classifiers
+.BR A ,
+.BR C ,
+.BR L ,
+.BR N ,
+.BR R ,
+and
+.B S
+determine a text block's
+.I alignment
+within its cell,
+but not its
+.I adjustment.
+.
+Add
+.B na
+or
+.B ad
+requests to the beginning of a text block to alter its adjustment
+distinctly from other text in the document.
+.
+As with other table entries,
+when a text block ends,
+any alterations to formatting parameters are discarded.
+.
+They do not affect subsequent table entries,
+not even other text blocks.
+.
+.
+.P
+.ne 2v
+If
+.B w
+or
+.B x
+modifiers are not specified for
+.I all
+columns of a text block's span,
+the default length of the text block
+(more precisely,
+the line length used to process the text block diversion)
+is computed as
+.IR L \[tmu] C /( N +1),
+.\" ...and rounded to the horizontal motion quantum of the output device
+where
+.I L
+is the current line length,
+.I C
+the number of columns spanned by the text block,
+and
+.I N
+the number of columns in the table.
+.
+If necessary,
+you can also control a text block's width by including an
+.B ll
+(line length)
+request in it prior to any text to be formatted.
+.
+Because a diversion is used to format the text block,
+its height and width are subsequently available in the registers
+.B dn
+and
+.BR dl ,
+respectively.
+.
+.
+.\" ====================================================================
+.SS \f[I]roff\f[] interface
+.\" ====================================================================
+.
+The register
+.B TW
+stores the width of the table region in basic units;
+it can't be used within the region itself,
+but is defined before the
+.B .TE
+token is output so that a
+.I groff
+macro named
+.B TE
+can make use of it.
+.
+.B T.\&
+is a Boolean-valued register indicating whether the bottom of the table
+is being processed.
+.
+The
+.B #T
+register marks the top of the table.
+.
+Avoid using these names for any other purpose.
+.
+.
+.P
+.I @g@tbl
+also defines a macro
+.B T#
+to produce the bottom and side lines of a boxed table.
+.
+While
+.I @g@tbl
+itself arranges for the output to include a call of this macro at the
+end of such a table,
+it can also be used by macro packages to create boxes for multi-page
+tables by calling it from a page footer macro that is itself called by
+a trap planted near the bottom of the page.
+.
+See section \[lq]Limitations\[rq] below for more on multi-page tables.
+.
+.
+.P
+GNU
+.I tbl \" GNU
+.\" AT&T tbl used all kinds of registers; many began with "3".
+internally employs register,
+string,
+macro,
+and diversion names beginning with the
+.RB numeral\~ 3 .
+.
+A document to be preprocessed with GNU
+.I tbl \" GNU
+should not use any such identifiers.
+.\" XXX: Why are they not named starting with "gtbl*" or something? GNU
+.\" tbl turns AT&T troff compatibility mode off anyway.
+.
+.
+.\" ====================================================================
+.SS "Interaction with \f[I]@g@eqn\f[]"
+.\" ====================================================================
+.
+.I @g@tbl
+should always be called before
+.MR @g@eqn @MAN1EXT@ .
+.
+(\c
+.MR groff @MAN1EXT@
+automatically arranges preprocessors in the correct order.)
+.
+Don't call the
+.B EQ
+and
+.B EN
+macros within tables;
+instead,
+set up delimiters in your
+.I eqn \" generic
+input and use the
+.B \%delim
+region option so that
+.I @g@tbl
+will recognize them.
+.
+.
+.br
+.ne 5v \" Keep enough space for heading, intro sentence, and first item.
+.\" ====================================================================
+.SS "GNU \f[I]tbl\f[] enhancements"
+.\" ====================================================================
+.
+In addition to extensions noted above,
+GNU
+.I tbl \" GNU
+removes constraints endured by users of AT&T
+.IR tbl .\" AT&T
+.
+.
+.IP \[bu] 2n
+Region options can be specified in any lettercase.
+.
+.
+.IP \[bu]
+There is no limit on the number of columns in a table,
+regardless of their classification,
+nor any limit on the number of text blocks.
+.
+.
+.IP \[bu]
+All table rows are considered when deciding column widths,
+not just those occurring in the first 200 input lines of a region.
+.
+Similarly,
+table continuation
+.RB ( .T& )
+tokens are recognized outside a region's first 200 input lines.
+.
+.
+.IP \[bu]
+Numeric and alphabetic entries may appear in the same column.
+.
+.
+.IP \[bu]
+Numeric and alphabetic entries may span horizontally.
+.
+.
+.\" ====================================================================
+.SS "Using GNU \f[I]tbl\f[] within macros"
+.\" ====================================================================
+.
+You can embed a table region inside a macro definition.
+.
+However,
+since
+.I @g@tbl
+writes its own macro definitions at the beginning of each table region,
+it is necessary to call end macros instead of ending macro definitions
+with
+.RB \[lq] ..\& \[rq].
+.\" XXX: Why don't we fix that by ending all of tbl's own macro
+.\" definitions with a call to a macro in its own reserved name space?
+.
+Additionally,
+the escape character must be disabled. \" XXX: Why?
+.
+.
+.P
+Not all
+.I @g@tbl
+features can be exercised from such macros because
+.I @g@tbl
+is a
+.I roff
+preprocessor:
+it sees the input earlier than
+.I @g@troff
+does.
+.
+For example,
+vertically aligning decimal separators fails if the numbers containing
+them occur as macro or string parameters;
+the alignment is performed by
+.I @g@tbl
+itself,
+which sees only
+.BR \[rs]$1 ,
+.BR \[rs]$2 ,
+and so on,
+and therefore can't recognize a decimal separator that only appears
+later when
+.I @g@troff
+interpolates a macro or string definition.
+.
+.
+.\" XXX: The following is a general caveat about preprocessors; move it.
+.P
+Using
+.I @g@tbl
+macros within conditional input
+(that is,
+contingent upon an
+.BR if ,
+.BR ie ,
+.BR el ,
+or
+.B while
+request)
+can result in misleading line numbers in subsequent diagnostics.
+.
+.I @g@tbl
+unconditionally injects its output into the source document,
+but the conditional branch containing it may not be taken,
+and if it is not,
+the
+.B lf
+requests that
+.I @g@tbl
+injects to restore the source line number cannot take effect.
+.
+Consider copying the input line counter register
+.B c.\&
+and restoring its value at a convenient location after applicable
+arithmetic.
+.
+.
+.br
+.ne 5v
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-C
+Enable AT&T compatibility mode:
+recognize
+.B .TS
+and
+.B .TE
+even when followed by a character other than space or newline.
+.
+Furthermore,
+interpret the uninterpreted leader escape sequence
+.BR \[rs]a .
+.
+.
+.\" ====================================================================
+.SH Limitations
+.\" ====================================================================
+.
+Multi-page tables,
+if boxed and/or if you want their column headings repeated after page
+breaks,
+require support at the time the document is formatted.
+.
+A convention for such support has arisen in macro packages such as
+.IR ms ,
+.IR mm ,
+and
+.IR me .
+.
+To use it,
+follow the
+.B .TS
+token with a space and then
+.RB \[lq] H \[rq];
+this will be interpreted by the formatter
+as a
+.B TS
+macro call with an
+.B H
+argument.
+.
+Then,
+within the table data,
+call the
+.B TH
+macro;
+this informs the macro package where the headings end.
+.
+If your table has no such heading rows,
+or you do not desire their repetition,
+call
+.B TH
+immediately after the table format specification.
+.
+If a multi-page table is boxed or has repeating column headings,
+do not enclose it with keep/release macros,
+or divert it in any other way.
+.
+Further,
+the
+.B bp
+request will not cause a page break in a
+.RB \[lq] "TS H" \[rq]
+table.
+.
+Define a macro to wrap
+.BR bp :
+invoke it normally if there is no current diversion.
+.
+Otherwise,
+pass the macro call to the enclosing diversion using the transparent
+line escape sequence
+.BR \[rs]!\& ;
+this will \[lq]bubble up\[rq] the page break to the output device.
+.
+See section \[lq]Examples\[rq] below for a demonstration.
+.
+.
+.P
+Double horizontal rules are not supported by
+.MR grotty @MAN1EXT@ ;
+single rules are used instead.
+.
+.I \%grotty
+also ignores half-line motions,
+so the
+.B u
+column modifier has no effect.
+.
+On terminal devices
+.RI (\[lq] nroff\~ mode\[rq]),
+horizontal rules and box borders occupy a full vee of space;
+this amount is doubled for
+.B doublebox
+tables.
+.
+Tables using these features thus require more vertical space in
+.I nroff
+mode than in
+.I troff
+mode:
+write
+.B ne
+requests accordingly.
+.
+Vertical rules between columns are drawn in the space between columns in
+.I nroff
+mode;
+using double vertical rules and/or reducing the column separation below
+the default can make them ugly or overstrike them with table data.
+.
+.
+.P
+A text block within a table must be able to fit on one page.
+.
+.
+.P
+Using
+.B \[rs]a
+to put leaders in table entries does not work
+in GNU
+.IR tbl , \" GNU
+except in compatibility mode.
+.
+This is correct behavior:
+.B \[rs]a
+is an
+.I uninterpreted
+leader.
+.
+You can still use the
+.I roff
+leader character (Control+A) or define a string to use
+.B \[rs]a
+as it was designed:
+to be interpreted only in copy mode.
+.
+.
+.RS
+.P
+.EX
+\&.ds a \[rs]a
+\&.TS
+\&box center tab(;);
+\&Lw(2i)0 L.
+\&Population\[rs]*a;6,327,119
+\&.TE
+.EE
+.RE
+.
+.
+.\" We use a real leader to avoid defining a string in a man page.
+.P
+.TS
+box center tab(;);
+Lw(2i)0 L.
+Population;6,327,119
+.TE
+.
+.
+.P
+A leading and/or trailing
+.B |
+in a format specification,
+such as
+.RB \[lq] |LCR|.\& \[rq],
+produces an en space between the vertical rules and the content of the
+adjacent columns.
+.
+If no such space is desired
+(so that the rule abuts the content),
+you can introduce \[lq]dummy\[rq] columns with zero separation and empty
+corresponding table entries before and/or after.
+.
+.
+.RS
+.P
+.EX
+\&.TS
+\&center tab(#);
+\&R0|L C R0|L.
+_
+\&#levulose#glucose#dextrose#
+_
+\&.TE
+.EE
+.RE
+.
+.
+.P
+These dummy columns have zero width and are therefore invisible;
+unfortunately they usually don't work as intended on terminal devices.
+.
+.
+.if t \{\
+.TS
+center tab(#);
+R0|L C R0|L.
+_
+#levulose#glucose#dextrose#
+_
+.TE
+.\}
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+It can be easier to acquire the language of
+.I tbl \" generic
+through examples than formal description,
+especially at first.
+.
+.
+.\" Note: This example is nearly at the column limit (78n) for nroff
+.\" output. Recast with care.
+.RS
+.P
+.EX
+\&.TS
+box center tab(#);
+Cb Cb
+L L.
+Ability#Application
+Strength#crushes a tomato
+Dexterity#dodges a thrown tomato
+Constitution#eats a month-old tomato without becoming ill
+Intelligence#knows that a tomato is a fruit
+Wisdom#chooses \[rs]f[I]not\[rs]f[] to put tomato in a fruit salad
+Charisma#sells obligate carnivores tomato-based fruit salads
+\&.TE
+.EE
+.RE
+.
+.
+.P
+.TS
+box center tab(#);
+Cb Cb
+L L.
+Ability#Application
+Strength#crushes a tomato
+Dexterity#dodges a thrown tomato
+Constitution#eats a month-old tomato without becoming ill
+Intelligence#knows that a tomato is a fruit
+Wisdom#chooses \f[I]not\f[] to put tomato in a fruit salad
+Charisma#sells obligate carnivores tomato-based fruit salads
+.TE
+.
+.
+.P
+The
+.B A
+and
+.B N
+column classifiers can be easier to grasp in visual rendering than in
+description.
+.
+.
+.RS
+.P
+.EX
+\&.TS
+center tab(;);
+CbS,LN,AN.
+Daily energy intake (in MJ)
+Macronutrients
+\&.\[rs]" assume 3 significant figures of precision
+Carbohydrates;4.5
+Fats;2.25
+Protein;3
+\&.T&
+LN,AN.
+Mineral
+Pu\-239;14.6
+_
+\&.T&
+LN.
+Total;\[rs][ti]24.4
+\&.TE
+.EE
+.RE
+.
+.
+.RS
+.P
+.TS
+center tab(;);
+CbS,LN,AN.
+Daily energy intake (in MJ)
+.\" assume 3 significant figures of precision
+Macronutrients
+Carbohydrates;4.5
+Fats;2.25
+Protein;3
+.T&
+LN,AN.
+Mineral
+Pu-239;14.6
+_
+.T&
+LN.
+Total;\[ti]24.4
+.TE
+.RE
+.
+.
+.br
+.ne 12v
+.P
+Next,
+we'll lightly adapt a compact presentation of spanning,
+vertical alignment,
+and zero-width column modifiers from the
+.I mandoc
+reference for its
+.I tbl \" generic
+interpreter.
+.
+It rewards close study.
+.
+.
+.RS
+.P
+.EX
+\&.TS
+box center tab(:);
+Lz S | Rt
+Ld| Cb| \[ha]
+\[ha] | Rz S.
+left:r
+l:center:
+:right
+\&.TE
+.EE
+.RE
+.
+.
+.RS
+.P
+.TS
+box center tab(:);
+Lz S | Rt
+Ld| Cb| ^
+^ | Rz S.
+left:r
+l:center:
+:right
+.TE
+.RE
+.
+.
+.P
+.ne 2v
+Row staggering is not visually achievable on terminal devices,
+but a table using it can remain comprehensible nonetheless.
+.
+.
+.RS
+.P
+.EX
+\&.TS
+center tab(|);
+Cf(BI) Cf(BI) Cf(B), C C Cu.
+n|n\[rs]f[B]\[rs][tmu]\[rs]f[]n|difference
+1|1
+2|4|3
+3|9|5
+4|16|7
+5|25|9
+6|36|11
+\&.TE
+.EE
+.RE
+.
+.
+.RS
+.P
+.TS
+center tab(|);
+Cf(BI) Cf(BI) Cf(B), C C Cu.
+n|n\f[B]\[tmu]\f[]n|difference
+1|1
+2|4|3
+3|9|5
+4|16|7
+5|25|9
+6|36|11
+.TE
+.RE
+.
+.
+.P
+Some
+.I @g@tbl
+features cannot be illustrated in the limited environment of a portable
+man page.
+.
+.
+.\" TODO: Find a better example than this.
+.\".P
+.\"As noted above,
+.\"we can embed a table region in a
+.\".I groff
+.\"macro definition.
+.\".
+.\".IR @g@tbl ,
+.\"however,
+.\"cannot know what will result from any macro argument interpolations,
+.\"so we might confine such interpolations to one column of the table and
+.\"apply the
+.\".B x
+.\"modifier to it.
+.\".
+.\".
+.\".RS
+.\".P
+.\".EX
+.\"\&.de END
+.\"\&..
+.\"\&.eo
+.\"\&.de MYTABLE END
+.\"\&.TS
+.\"\&allbox tab(;);
+.\"\&C Lx.
+.\"\&This is table \[rs]$1.;\[rs]$2
+.\"\&.TE
+.\"\&.END
+.\"\&.ec
+.\"\&.MYTABLE 1 alpha
+.\"\&.MYTABLE 2 beta
+.\"\&.MYTABLE 3 "gamma delta"
+.\".EE
+.\".RE
+.\"
+.\"
+.P
+We can define a macro outside of a
+.I tbl \" generic
+region that we can call from within it to cause a page break inside a
+multi-page boxed table.
+.
+You can choose a different name;
+be sure to change both occurrences of \[lq]BP\[rq].
+.
+.
+.RS
+.P
+.ne 4v
+.EX
+\&.de BP
+\&.\& ie \[aq]\[rs]\[rs]n(.z\[aq]\[aq] \&.bp \[rs]\[rs]$1
+\&.\& el \[rs]!.BP \[rs]\[rs]$1
+\&..
+.EE
+.RE
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Tbl\[em]A Program to Format Tables\[rq],
+by M.\& E.\& Lesk,
+1976
+(revised 16 January 1979),
+AT&T Bell Laboratories Computing Science Technical Report No.\& 49.
+.
+.
+.P
+The spanning example above was taken from
+.UR https://man.openbsd.org/tbl.7
+.IR mandoc 's
+man page for its
+.I tbl \" mandoc
+implementation
+.UE .
+.
+.
+.P
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_tbl_1_man_C]
+.do rr *groff_tbl_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/preproc/tbl/tbl.am b/src/preproc/tbl/tbl.am
new file mode 100644
index 0000000..d4b0edb
--- /dev/null
+++ b/src/preproc/tbl/tbl.am
@@ -0,0 +1,54 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += tbl
+tbl_LDADD = libgroff.a $(LIBM) lib/libgnu.a
+tbl_SOURCES = \
+ src/preproc/tbl/main.cpp \
+ src/preproc/tbl/table.cpp \
+ src/preproc/tbl/table.h
+PREFIXMAN1 += src/preproc/tbl/tbl.1
+EXTRA_DIST += src/preproc/tbl/tbl.1.man
+
+tbl_TESTS = \
+ src/preproc/tbl/tests/boxes-and-vertical-rules.sh \
+ src/preproc/tbl/tests/check-horizontal-line-length.sh \
+ src/preproc/tbl/tests/check-line-intersections.sh \
+ src/preproc/tbl/tests/check-vertical-line-length.sh \
+ src/preproc/tbl/tests/cooperate-with-nm-request.sh \
+ src/preproc/tbl/tests/count-continued-input-lines.sh \
+ src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh \
+ src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh \
+ src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh \
+ src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh \
+ src/preproc/tbl/tests/expand-region-option-works.sh \
+ src/preproc/tbl/tests/format-time-diagnostics-work.sh \
+ src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh \
+ src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh \
+ src/preproc/tbl/tests/save-and-restore-line-numbering.sh \
+ src/preproc/tbl/tests/save-and-restore-tab-stops.sh \
+ src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh \
+ src/preproc/tbl/tests/x-column-modifier-works.sh
+TESTS += $(tbl_TESTS)
+EXTRA_DIST += $(tbl_TESTS)
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/preproc/tbl/tests/boxes-and-vertical-rules.sh b/src/preproc/tbl/tests/boxes-and-vertical-rules.sh
new file mode 100755
index 0000000..0471188
--- /dev/null
+++ b/src/preproc/tbl/tests/boxes-and-vertical-rules.sh
@@ -0,0 +1,174 @@
+#!/bin/sh
+#
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Test behavior of unexpanded tables using boxes and/or vertical rules.
+
+# Case 1: "naked" table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table without vertical rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '2p' \
+ | grep -qx 'abcdef abcdef abcdef abcdef abcdef' || wail
+
+# Case 2: left-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with left-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef abcdef abcdef' || wail
+
+# Case 3: right-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with right-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx 'abcdef abcdef abcdef abcdef abcdef |' || wail
+
+# Case 4: vertical rule on both ends
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef abcdef abcdef |' || wail
+
+# Case 5: vertical rule on both ends and interior rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| L L L | L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both edge and interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef | abcdef abcdef |' || wail
+
+# Case 6: boxed table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table without interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef abcdef abcdef |' || wail
+
+# Case 7: boxed table with interior vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+L L L | L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table with interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef | abcdef abcdef |' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/check-horizontal-line-length.sh b/src/preproc/tbl/tests/check-horizontal-line-length.sh
new file mode 100755
index 0000000..3d5e2a2
--- /dev/null
+++ b/src/preproc/tbl/tests/check-horizontal-line-length.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$output"
+ fail=yes
+}
+
+# GNU tbl draws horizontal lines 1n wider than they need to be on nroff
+# devices to enable them to cross a vertical line on the right-hand
+# side.
+
+input='.ll 10n
+.TS
+L.
+_
+1234567890
+.TE
+.pl \n(nlu
+'
+
+echo "checking length of plain horizontal rule" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | grep -Eqx -- '-{11}' || wail
+
+input='.ll 12n
+.TS
+| L |.
+_
+1234567890
+_
+.TE
+.pl \n(nlu
+'
+
+echo "checking intersection of vertical and horizontal rules" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | sed -n '1p' | grep -Eqx '\+-{12}\+' || wail
+echo "$output" | sed -n '3p' | grep -Eqx '\+-{12}\+' || wail
+
+input='.ll 12n
+.TS
+box;
+L.
+1234567890
+.TE
+.pl \n(nlu
+'
+
+echo "checking width of boxed table" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | sed -n '1p' | grep -Eqx '\+-{12}\+' || wail
+echo "$output" | sed -n '3p' | grep -Eqx '\+-{12}\+' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/check-line-intersections.sh b/src/preproc/tbl/tests/check-line-intersections.sh
new file mode 100755
index 0000000..2012beb
--- /dev/null
+++ b/src/preproc/tbl/tests/check-line-intersections.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$output"
+ fail=yes
+}
+
+input='.TS
+allbox tab(@);
+L L L.
+a@b@c
+d@e@f
+g@h@i
+.TE
+'
+
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output"
+
+for l in 1 3 5 7
+do
+ echo "checking intersections on line $l"
+ echo "$output" | sed -n ${l}p | grep -Fqx '+---+---+---+' || wail
+done
+
+# TODO: Check `-Tutf8` output for correct crossing glyph identities.
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/check-vertical-line-length.sh b/src/preproc/tbl/tests/check-vertical-line-length.sh
new file mode 100755
index 0000000..1aafd09
--- /dev/null
+++ b/src/preproc/tbl/tests/check-vertical-line-length.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$output"
+ fail=yes
+}
+
+# GNU tbl draws vertical lines 1v taller than they need to be on nroff
+# devices to enable them to cross a potential horizontal line in the
+# table.
+
+input='.ll 12n
+.TS
+| L |.
+_
+1234567890
+.TE
+.pl \n(nlu
+'
+
+echo "checking length of plain vertical rule" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | sed -n '2p' | grep -Fqx -- '| 1234567890 |' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/cooperate-with-nm-request.sh b/src/preproc/tbl/tests/cooperate-with-nm-request.sh
new file mode 100755
index 0000000..cfbd750
--- /dev/null
+++ b/src/preproc/tbl/tests/cooperate-with-nm-request.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Regression-test Savannah #59812.
+#
+# A nonzero value of \n[ln] should not cause spurious numbering of table
+# rows.
+
+DOC='\
+.nf
+foo
+.nm 1
+bar
+.nm
+baz
+.TS
+l.
+qux
+.TE
+'
+
+OUTPUT=$(printf "%s" "$DOC" | "$groff" -Tascii -t)
+
+echo "$OUTPUT" | grep -Fqx qux
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/count-continued-input-lines.sh b/src/preproc/tbl/tests/count-continued-input-lines.sh
new file mode 100755
index 0000000..4682788
--- /dev/null
+++ b/src/preproc/tbl/tests/count-continued-input-lines.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Regression-test Savannah #62191.
+#
+# Line continuation in a row of table data should not make the input
+# line counter inaccurate.
+
+input='.this-is-line-1
+.TS
+L.
+foo\
+bar\
+baz\
+qux
+.TE
+.this-is-line-9'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -t -ww -z 2>&1)
+echo "$output" | grep -q '^troff.*:9:.*this-is-line-9'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh b/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh
new file mode 100755
index 0000000..c4698f1
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh
@@ -0,0 +1,225 @@
+#!/bin/sh
+#
+# Copyright (C) 2022-2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+grotty="${abs_top_builddir:-.}/grotty"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #63449.
+#
+# In nroff mode, a table at the top of the page (i.e., one starting at
+# the first possible output line, with no vertical margin) that has
+# vertical rules should not overdraw the page top and provoke a warning
+# from grotty about "character(s) above [the] first line [being]
+# discarded".
+
+# Case 1: No horizontal rules; vertical rule at leading column.
+input='.TS
+| L.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (1)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that a lone vertical rule starts the first output line"
+echo "$output" | sed -n '1p' | grep -Fqx '|' || wail
+
+# Case 2: No horizontal rules; vertical rule between columns.
+input='.TS
+tab(@);
+L | L.
+foo@bar
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (2)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that a lone vertical rule ends the first output line"
+echo "$output" | sed -n '1p' | grep -Eqx ' +\|' || wail
+
+# Case 3: No horizontal rules; vertical rule at trailing column.
+input='.TS
+L |.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (3)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that a lone vertical rule ends the first output line"
+echo "$output" | sed -n '1p' | grep -Eqx ' +\|' || wail
+
+# Case 4: Vertical rule with horizontal rule in row description.
+input='.TS
+_
+L |.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (4)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that intersection is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '+' || wail
+
+# Case 5: Vertical rule with horizontal rule as first table datum.
+input='.TS
+L |.
+_
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (5)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that intersection is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '+' || wail
+
+# Case 6: Horizontal rule as non-first row description with vertical
+# rule.
+input='.TS
+L,_,L |.
+foo
+bar
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (6)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that table data begin on first output line"
+echo "$output" | sed -n '1p' | grep -q 'foo' || wail
+
+# Also ensure that no collateral damage arises in related cases.
+
+# Case 7: Horizontal rule as first table datum with no vertical rule.
+input='.TS
+L.
+_
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (7)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that horizontal rule is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '^---' || wail
+
+# Case 8: Horizontal rule as last table datum with no vertical rule.
+input='.TS
+L.
+foo
+_
+.TE
+.ec @
+.pl @n(nlu'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (8)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that horizontal rule is placed on the last output line"
+echo "$output" | sed -n '$p' | grep -q '^---' || wail
+
+# Case 9: Horizontal rule in row description with no vertical rule.
+input='.TS
+_
+L.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (9)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that intersection is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '^---' || wail
+
+# Case 10: Horizontal rule as non-first row description with no vertical
+# rule.
+input='.TS
+L,_,L.
+foo
+bar
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (10)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that table data begin on first output line"
+echo "$output" | sed -n '1p' | grep -q 'foo' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh b/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh
new file mode 100755
index 0000000..4f63eed
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ fail=yes
+}
+
+# Regression-test Savannah #49390.
+
+input='foo
+.TS
+box;
+L.
+bar
+.TE
+baz
+.pl \n(nlu
+'
+
+echo "checking for post-table text non-overlap of (single) box border"
+output=$(printf "%s" "$input" | "$groff" -t -Tascii)
+echo "$output" | grep -q baz || wail
+
+input='foo
+.TS
+doublebox;
+L.
+bar
+.TE
+baz
+.pl \n(nlu
+'
+
+echo "checking for post-table text non-overlap of double box border"
+output=$(printf "%s" "$input" | "$groff" -t -Tascii)
+echo "$output" | grep -q baz || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh b/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh
new file mode 100755
index 0000000..1d672ec
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+tbl="${abs_top_builddir:-.}/tbl"
+
+# Regression-test Savannah #61417.
+#
+# Don't segfault because we tried to span down from an invalid span that
+# tbl neglected to replace with an empty table entry.
+
+test -f core && exit 77 # skip
+
+input=$(cat <<EOF
+.TS
+l.
+\^
+\^
+.TE
+EOF
+)
+output=$(printf "%s" "$input" | "$tbl")
+! test -f core
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh b/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh
new file mode 100755
index 0000000..30bd9f6
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ fail=yes
+}
+
+# Regression-test Savannah #62366.
+#
+# Do not SEGV when a text block begins with a repeating glyph token, and
+# do not malformat the output if it ends with one.
+
+test -f core && exit 77 # skip
+
+input='.TS
+L.
+T{
+\Ra
+T}
+.TE
+.TS
+L.
+T{
+foo
+\Ra
+T}
+.TE
+.TS
+L.
+T{
+foo
+\Ra
+bar
+T}
+.TE'
+
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii -P-cbou)
+
+echo "checking that tbl doesn't segfault" >&2
+test -f core && wail
+
+echo "checking text block starting with repeating glyph" >&2
+echo "$output" | sed -n 1p | grep -qx 'a' || wail
+
+echo "checking text block ending with repeating glyph" >&2
+echo "$output" | sed -n 2p | grep -qx 'foo a' || wail
+
+echo "checking text block containing repeating glyph" >&2
+echo "$output" | sed -n 3p | grep -qx 'foo a bar' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/expand-region-option-works.sh b/src/preproc/tbl/tests/expand-region-option-works.sh
new file mode 100755
index 0000000..97da39d
--- /dev/null
+++ b/src/preproc/tbl/tests/expand-region-option-works.sh
@@ -0,0 +1,173 @@
+#!/bin/sh
+#
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Ensure that the "expand" region option expands to the line length.
+
+# Case 1: "naked" table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table without vertical rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '2p' \
+ | grep -Eqx '(abcdef {8,9}){4}abcdef' || wail
+
+# Case 2: left-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+| L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with left-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {8}abcdef {7}abcdef {8}abcdef {9}abcdef' \
+ || wail
+
+# Case 3: right-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with right-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx 'abcdef {8}abcdef {7}abcdef {8}abcdef {9}abcdef \|' \
+ || wail
+
+# Case 4: vertical rule on both ends
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+| L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {7}abcdef {7}abcdef {8}abcdef {8}abcdef \|' \
+ || wail
+
+# Case 5: vertical rule on both ends and interior rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+| L L L | L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both edge and interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {7}abcdef {7}abcdef {4}\| {3}abcdef {8}abcdef \|' || wail
+
+# Case 6: boxed table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box expand tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table without interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {7}abcdef {7}abcdef {8}abcdef {8}abcdef \|' \
+ || wail
+
+# Case 7: boxed table with interior vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box expand tab(@);
+L L L | L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table with interior rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {7}abcdef {7}abcdef {4}\| {3}abcdef {8}abcdef \|' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/format-time-diagnostics-work.sh b/src/preproc/tbl/tests/format-time-diagnostics-work.sh
new file mode 100755
index 0000000..9d422bd
--- /dev/null
+++ b/src/preproc/tbl/tests/format-time-diagnostics-work.sh
@@ -0,0 +1,268 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Ensure we get diagnostics when we expect to, and not when we don't.
+#
+# Do NOT pattern-match the text of the diagnostic messages; those should
+# be left flexible. (Some day they might even be localized.)
+
+# As of this writing, there are 5 distinct format-time diagnostic
+# messages that tbl writes roff code to generate, one of which can be
+# produced two different ways.
+
+# Diagnostic #1: a row overruns the page bottom
+input='.pl 2v
+.TS
+;
+L.
+T{
+.nf
+1
+2
+3
+T}
+.TE
+'
+
+echo "checking for diagnostic when row with text box overruns page" \
+ "bottom"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when row with text" \
+ "box overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+echo "checking 'nokeep' suppression of diagnostic when row with text" \
+ "box overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# The other way to get "diagnostic #1" is to have a row that is too
+# tall _without_ involving a text block, for instance by having a font
+# or vertical spacing that is too high.
+input='.pl 2v
+.vs 3v
+.TS
+;
+L.
+1
+.TE
+'
+
+echo "checking for diagnostic when row with large vertical spacing" \
+ "overruns page bottom"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when row with large" \
+ "vertical spacing overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+echo "checking 'nokeep' suppression of diagnostic when row with large" \
+ "vertical spacing overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Diagnostic #2: a boxed table won't fit on a page
+
+input='.pl 2v
+.vs 3v
+.TS
+box;
+L.
+1
+.TE
+'
+
+echo "checking for diagnostic when boxed table won't fit on page"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# The above is an error, so the "nowarn" region option won't shut it up.
+#
+# However, "nokeep" does--but arguably shouldn't. See
+# <https://savannah.gnu.org/bugs/?61878>. If that gets fixed, we should
+# test that we still get a diagnostic even with the option given.
+
+# Diagnostic #3: unexpanded columns overrun the line length
+#
+# Case 1: no 'x' column modifiers used
+
+input='.pl 2v
+.ll 10n
+.TS
+;
+L.
+12345678901
+.TE
+'
+
+echo "checking for diagnostic when unexpanded columns overrun line" \
+ "length (1)"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when unexpanded" \
+ "columns overrun line length (1)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when unexpanded" \
+ "columns overrun line length (1)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# Case 2: 'x' column modifier used
+#
+# This worked as a "get out of jail (warning) free" card in groff 1.22.4
+# and earlier; i.e., it incorrectly suppressed the warning. See
+# Savannah #61854.
+
+input='.pl 2v
+.ll 10n
+.TS
+;
+Lx.
+12345678901
+.TE
+'
+
+echo "checking for diagnostic when unexpanded columns overrun line" \
+ "length (2)"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when unexpanded" \
+ "columns overrun line length (2)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when unexpanded" \
+ "columns overrun line length (2)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# Diagnostic #4: expanded table gets all column separation squashed out
+
+input='.pl 3v
+.ll 10n
+.TS
+tab(;) expand;
+L L.
+abcde;fghij
+.TE
+'
+
+echo "checking for diagnostic when region-expanded table has column" \
+ "separation eliminated"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when" \
+ "region-expanded table has column separation eliminated"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when" \
+ "region-expanded table has column separation eliminated"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# Diagnostic #5: expanded table gets column separation reduced
+
+input='.pl 3v
+.ll 10n
+.TS
+tab(;) expand;
+L L.
+abcd;efgh
+.TE
+'
+
+echo "checking for diagnostic when region-expanded table has column" \
+ "separation reduced"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when" \
+ "region-expanded table has column separation reduced"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when" \
+ "region-expanded table has column separation reduced"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh b/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh
new file mode 100755
index 0000000..e368b31
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Regression-test Savannah #59971.
+#
+# Hyphenation needs to be restored between (and after) text blocks just
+# as adjustment is.
+
+EXAMPLE='.nr LL 78n
+.hw a-bc-def-ghij-klmno-pqrstu-vwxyz
+.LP
+Here is a table with hyphenation disabled in its text block.
+.
+.TS
+l lx.
+foo T{
+.nh
+abcdefghijklmnopqrstuvwxyz
+abcdefghijklmnopqrstuvwxyz
+abcdefghijklmnopqrstuvwxyz
+T}
+.TE
+.
+Let us see if hyphenation is enabled again as it should be.
+abcdefghijklmnopqrstuvwxyz'
+
+OUTPUT=$(printf "%s\n" "$EXAMPLE" | "$groff" -Tascii -P-cbou -t -ms)
+
+echo "$OUTPUT"
+
+echo "testing whether hyphenation disabled in table text block" >&2
+! echo "$OUTPUT" | grep '^foo' | grep -- '-$'
+
+echo "testing whether hyphenation enabled after table" >&2
+echo "$OUTPUT" | grep -qx 'Let us see.*lmno-'
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh b/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh
new file mode 100755
index 0000000..e9a06d8
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #61909.
+#
+# Inter-sentence space should not be applied to the content of ordinary
+# table entries. They are set "rigidly" (tbl(1)), also without filling,
+# adjustment, hyphenation or breaking. If you want those things, use a
+# text block.
+
+input='.ss 12 120
+Before one.
+Before two.
+.TS
+L.
+.\" two spaces
+Foo. Bar.
+.\" four spaces
+Baz. Qux.
+.\" two spaces
+T{
+Ack. Nak.
+T}
+.TE
+After one.
+After two.
+'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t)
+echo "$output"
+
+echo "checking that inter-sentence space is altered too early"
+echo "$output" \
+ | grep -Fqx 'Before one. Before two.' || wail # 11 spaces
+
+echo "checking that inter-sentence space is not applied to ordinary" \
+ "table entries (1)"
+echo "$output" | grep -Fqx 'Foo. Bar.' || wail # 2 spaces
+
+echo "checking that inter-sentence space is not applied to ordinary" \
+ "table entries (2)"
+echo "$output" | grep -Fqx 'Baz. Qux.' || wail # 4 spaces
+
+echo "checking that inter-sentence space is applied to text blocks"
+echo "$output" | grep -Fqx 'Ack. Nak.' || wail # 11 spaces
+
+echo "checking that inter-sentence space is restored after table"
+echo "$output" \
+ | grep -Fqx 'After one. After two.' || wail # 11 spaces
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-line-numbering.sh b/src/preproc/tbl/tests/save-and-restore-line-numbering.sh
new file mode 100755
index 0000000..592b43a
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-line-numbering.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #60140.
+#
+# Line numbering needs to be suspended within a table and restored
+# afterward. Historical implementations handled line numbering in
+# tables badly when text blocks were used.
+
+input='.nm 1
+Here is a line of output.
+Sic transit adispicing meatballs.
+We pad it out with more content to ensure that the line breaks.
+.TS
+L.
+This is my table.
+There are many like it but this one is mine.
+T{
+Ut enim ad minima veniam,
+quis nostrum exercitationem ullam corporis suscipitlaboriosam,
+nisi ut aliquid ex ea commodi consequatur?
+T}
+.TE
+What is the line number now?'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t)
+echo "$output"
+
+echo "testing that line numbering is suppressed in table" >&2
+echo "$output" | grep -Fqx 'This is my table.' || wail
+
+echo "testing that line numbering is restored after table" >&2
+echo "$output" | grep -Eq '3 +What is the line number now\?' || wail
+
+input='.nf
+.nm 1
+test of line numbering suppression
+five
+four
+.nn 3
+three
+.TS
+L.
+I am a table.
+I have two rows.
+.TE
+two
+one
+numbering returns here'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t)
+echo "$output"
+
+echo "testing that suppressed numbering is restored correctly" >&2
+echo "$output" | grep -Eq '4 +numbering returns here' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-tab-stops.sh b/src/preproc/tbl/tests/save-and-restore-tab-stops.sh
new file mode 100755
index 0000000..b98922a
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-tab-stops.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #42978.
+#
+# When tbl changes the tab stops, it needs to restore them.
+#
+# Based on an example by Bjarni Igni Gislason.
+
+EXAMPLE='.TH tbl\-tabs\-test 1 2020-10-20 "groff test suite"
+.SH Name
+tbl\-tabs\-test \- see if tbl messes up the tab stops
+.SH Description
+Do not use tabs in man pages outside of
+.BR .TS / .TE
+regions.
+.PP
+But if you do.\|.\|.
+.PP
+.TS
+l l l.
+table entries long enough to change the tab stops
+.TE
+.PP
+.EX
+#!/bin/sh
+case $#
+1)
+ if foo
+ then
+ bar
+ else
+ if baz
+ then
+ qux
+ fi
+ fi
+;;
+esac
+.EE'
+
+OUTPUT=$(printf "%s\n" "$EXAMPLE" | "$groff" -Tascii -P-cbou -t -man)
+FAIL=
+
+if ! echo "$OUTPUT" | grep -Eq '^ {12}if foo$'
+then
+ FAIL=yes
+ echo "first tab stop is wrong" >&2
+fi
+
+if ! echo "$OUTPUT" | grep -Eq '^ {17}bar$'
+then
+ FAIL=yes
+ echo "second tab stop is wrong" >&2
+fi
+
+if ! echo "$OUTPUT" | grep -Eq '^ {22}qux$'
+then
+ FAIL=yes
+ echo "third tab stop is wrong" >&2
+fi
+
+test -z "$FAIL"
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh b/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh
new file mode 100755
index 0000000..621a752
--- /dev/null
+++ b/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #61878.
+#
+# A boxed, unkept table that overruns the page bottom will produce ugly
+# output; it looks especially bizarre in nroff mode.
+#
+# We set the page length to 2v to force a problem (any boxed table in
+# nroff mode needs 3 vees minimum), and put a page break at the start to
+# catch an incorrectly initialized starting page number for the table.
+
+input='.pl 2v
+.bp
+.TS
+box nokeep;
+L.
+Z
+.TE'
+
+output=$(printf "%s" "$input" | "$groff" -t -Tascii 2>/dev/null)
+error=$(printf "%s" "$input" | "$groff" -t -Tascii 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that a diagnostic message is produced"
+echo "$error" | grep -q 'warning: boxed.*page 2$' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/x-column-modifier-works.sh b/src/preproc/tbl/tests/x-column-modifier-works.sh
new file mode 100755
index 0000000..da9b890
--- /dev/null
+++ b/src/preproc/tbl/tests/x-column-modifier-works.sh
@@ -0,0 +1,172 @@
+#!/bin/sh
+#
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Ensure that the "x" column modifier causes table expansion to the line
+# length.
+
+# Case 1: "naked" table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+Lx L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table without vertical rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '2p' \
+ | grep -Eqx 'abcdef {25}(abcdef {3}){3}abcdef' || wail
+
+# Case 2: left-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| Lx L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with left-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {23}(abcdef {3}){3}abcdef' || wail
+
+# Case 3: right-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+Lx L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with right-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx 'abcdef {23}(abcdef {3}){3}abcdef \|' || wail
+
+# Case 4: vertical rule on both ends
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| Lx L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {21}(abcdef {3}){3}abcdef \|' || wail
+
+# Case 5: vertical rule on both ends and interior rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| Lx L L | L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both edge and interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {21}abcdef {3}abcdef \| abcdef {3}abcdef \|' \
+ || wail
+
+# Case 6: boxed table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+Lx L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table without interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {21}(abcdef {3}){3}abcdef \|' || wail
+
+# Case 7: boxed table with interior vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+Lx L L | L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table with interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {21}abcdef {3}abcdef \| abcdef {3}abcdef \|' \
+ || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/roff/groff/groff.1.man b/src/roff/groff/groff.1.man
new file mode 100644
index 0000000..c551326
--- /dev/null
+++ b/src/roff/groff/groff.1.man
@@ -0,0 +1,2403 @@
+.TH groff @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+groff \- front end to the GNU
+.I roff
+document formatting system
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2022 Free Software Foundation, Inc.
+.\"
+.\" This file is part of groff, the GNU roff type-setting system.
+.\"
+.\" Permission is granted to copy, distribute and/or modify this
+.\" document under the terms of the GNU Free Documentation License,
+.\" Version 1.3 or any later version published by the Free Software
+.\" Foundation; with no Invariant Sections, with no Front-Cover Texts,
+.\" and with no Back-Cover Texts.
+.\"
+.\" A copy of the Free Documentation License is included as a file
+.\" called FDL in the main directory of the groff source package.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_groff_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.\" Define a string for the TeX logo.
+.ie t .ds TeX T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el .ds TeX TeX
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY groff
+.RB [ \-abcCeEgGijklNpRsStUVXzZ ]
+.RB [ \-d\~\c
+.IR ctext ]
+.RB [ \-d\~\c
+.IB string =\c
+.IR text ]
+.RB [ \-D\~\c
+.IR fallback-encoding ]
+.RB [ \-f\~\c
+.IR font-family ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-K\~\c
+.IR input-encoding ]
+.RB [ \-L\~\c
+.IR spooler-argument ]
+.RB [ \-m\~\c
+.IR macro-package ]
+.RB [ \-M\~\c
+.IR macro-directory ]
+.RB [ \-n\~\c
+.IR page-number ]
+.RB [ \-o\~\c
+.IR page-list ]
+.RB [ \-P\~\c
+.IR postprocessor-argument ]
+.RB [ \-r\~\c
+.IR cnumeric-expression ]
+.RB [ \-r\~\c
+.IB register =\c
+.IR numeric-expression ]
+.RB [ \-T\~\c
+.IR output-device ]
+.RB [ \-w\~\c
+.IR warning-category ]
+.RB [ \-W\~\c
+.IR warning-category ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY groff
+.B \-h
+.
+.SY groff
+.B \-\-help
+.YS
+.
+.
+.SY groff
+.B \-v
+.RI [ option\~ .\|.\|.\&]
+.RI [ file\~ .\|.\|.]
+.
+.SY groff
+.B \-\-version
+.RI [ option\~ .\|.\|.\&]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I groff
+is the primary front end to the GNU
+.I roff
+document formatting system.
+.
+.\" BEGIN Keep parallel with groff.texi node "What Is groff?".
+.\" This language is slightly expanded from that in the "ANNOUNCE" file
+.\" and on the groff home page.
+GNU
+.I roff
+is a typesetting system that reads plain text input files that include
+formatting commands to produce output in PostScript,
+PDF,
+HTML,
+DVI,
+or other formats,
+or for display to a terminal.
+.
+Formatting commands can be low-level typesetting primitives,
+macros from a supplied package,
+or user-defined macros.
+.
+All three approaches can be combined.
+.
+If no
+.I file
+operands are specified,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I groff
+reads the standard input stream.
+.
+.
+.P
+A reimplementation and extension of the typesetter from AT&T Unix,
+.I groff
+is present on most POSIX systems owing to its long association with Unix
+manuals
+(including man pages).
+.
+It and its predecessor are notable for their production of several
+best-selling software engineering texts.
+.
+.I groff
+is capable of producing typographically sophisticated documents while
+consuming minimal system resources.
+.\" END Keep parallel with groff.texi node "What Is groff?".
+.
+.
+.P
+The
+.I groff
+command orchestrates the execution of preprocessors,
+the transformation of input documents into a device-independent page
+description language,
+and the production of output from that language.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message and exit.
+.
+.
+.P
+Because
+.I groff
+is intended to subsume most users' direct invocations of the
+.MR @g@troff @MAN1EXT@
+formatter,
+the two programs share a set of options.
+.
+However,
+.I groff
+has some options that
+.I @g@troff
+does not share,
+and others which
+.I groff
+interprets differently.
+.
+At the same time,
+not all valid
+.I @g@troff
+options can be given to
+.IR groff .
+.
+.
+.\" ====================================================================
+.SS "\f[I]groff\f[]-specific options"
+.\" ====================================================================
+.
+The following options either do not exist in
+GNU
+.I troff \" GNU
+or are interpreted differently by
+.IR groff .
+.
+.
+.TP
+.BI \-D\~ enc
+Set fallback input encoding used by
+.MR preconv @MAN1EXT@
+to
+.IR enc ;
+implies
+.BR \-k .
+.
+.
+.TP
+.B \-e
+Run
+.MR @g@eqn @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.B \-g
+Run
+.MR @g@grn @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.B \-G
+Run
+.MR grap 1
+preprocessor;
+implies
+.BR \-p .
+.
+.
+.TP
+.BI \-I\~ dir
+Works as
+.IR @g@troff 's
+option
+(see below),
+but also implies
+.B \-g
+and
+.BR \-s .
+.
+It is passed to
+.MR @g@soelim @MAN1EXT@
+and the output driver,
+and
+.I @g@grn
+is passed an
+.B \-M
+option with
+.I dir
+as its argument.
+.
+.
+.TP
+.B \-j
+Run
+.MR @g@chem @MAN1EXT@
+preprocessor;
+implies
+.BR \-p .
+.
+.
+.TP
+.B \-k
+Run
+.MR preconv @MAN1EXT@
+preprocessor.
+.
+Refer to its man page for its behavior if neither of
+.IR groff 's
+.B \-K
+or
+.B \-D
+options is also specified.
+.
+.
+.TP
+.BI \-K\~ enc
+Set input encoding used by
+.MR preconv @MAN1EXT@
+to
+.IR enc ;
+implies
+.BR \-k .
+.
+.
+.TP
+.B \-l
+Send the output to a spooler program for printing.
+.
+The
+.RB \[lq] print \[rq]
+directive in the device description file
+specifies the default command to be used;
+see
+.MR groff_font @MAN5EXT@ .
+.
+If no such directive is present for the output device,
+.ie '@PSPRINT@'' \{\
+this option is ignored.
+.\}
+.el \{\
+output is piped to
+.MR @PSPRINT@ 1 .
+.\}
+.
+See options
+.B \-L
+and
+.BR \-X .
+.
+.
+.TP
+.BI \-L\~ arg
+Pass
+.I arg
+to the print spooler program.
+.
+If multiple
+.IR arg s
+are required,
+pass each with a separate
+.B \-L
+option.
+.
+.I groff
+does not prefix an option dash to
+.I arg
+before passing it to the spooler program.
+.
+.
+.TP
+.B \-M
+Works as
+.IR @g@troff 's
+option
+(see below),
+but is also passed to
+.MR @g@eqn @MAN1EXT@ ,
+.MR grap @MAN1EXT@ ,
+and
+.MR @g@grn @MAN1EXT@ .
+.
+.
+.TP
+.B \-N
+Prohibit newlines between
+.I eqn \" language
+delimiters:
+pass
+.B \-N
+to
+.MR @g@eqn @MAN1EXT@ .
+.
+.
+.TP
+.B \-p
+Run
+.MR @g@pic @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.BI \-P\~ arg
+Pass
+.I arg
+to the postprocessor.
+.
+If multiple
+.IR arg s
+are required,
+pass each with a separate
+.B \-P
+option.
+.
+.I groff
+does not prefix an option dash to
+.I arg
+before passing it to the postprocessor.
+.
+.
+.TP
+.B \-R
+Run
+.MR @g@refer @MAN1EXT@
+preprocessor.
+.
+No mechanism is provided for passing arguments to
+.I @g@refer
+because most
+.I @g@refer
+options have equivalent language elements that can be specified within
+the document.
+.
+.
+.TP
+.B \-s
+Run
+.MR @g@soelim @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.B \-S
+Operate in \[lq]safer\[rq] mode;
+see
+.B \-U
+below for its opposite.
+.
+For security reasons,
+safer mode is enabled by default.
+.
+.
+.TP
+.B \-t
+Run
+.MR @g@tbl @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.BI \-T\~ dev
+Direct
+.I @g@troff
+to format the input for the output device
+.IR dev .
+.
+.I groff
+then calls an output driver to convert
+.IR @g@troff 's
+output to a form appropriate for
+.IR dev ;
+see subsection \[lq]Output devices\[rq] below.
+.
+.
+.TP
+.B \-U
+Operate in unsafe mode:
+pass the
+.B \-U
+option to
+.I @g@pic
+and
+.IR @g@troff .
+.
+.
+.TP
+.B \-v
+.TQ
+.B \-\-version
+Write version information for
+.I groff
+and all programs run by it to the standard output stream;
+that is,
+the given command line is processed in the usual way,
+passing
+.B \-v
+to the formatter and any pre- or postprocessors invoked.
+.
+.
+.TP
+.B \-V
+Output the pipeline that
+.I groff
+would run to the standard output stream,
+but do not execute it.
+.
+If given more than once,
+.I groff
+both writes and runs the pipeline.
+.
+.
+.TP
+.B \-X
+Use
+.MR gxditview @MAN1EXT@
+instead of the usual postprocessor to (pre)view a document on an X11
+display.
+.
+Combining this option with
+.B \-Tps
+uses the font metrics of the PostScript device,
+whereas the
+.B \-TX75
+and
+.B \-TX100
+options use the metrics of X11 fonts.
+.
+.
+.TP
+.B \-Z
+Disable postprocessing.
+.
+.I @g@troff
+output will appear on the standard output stream
+(unless suppressed with
+.BR \-z );
+see
+.MR groff_out @MAN5EXT@
+for a description of this format.
+.
+.
+.\" ====================================================================
+.SS "Transparent options"
+.\" ====================================================================
+.
+The following options are passed as-is to the formatter program
+.MR @g@troff @MAN1EXT@
+and described in more detail in its man page.
+.
+.
+.TP
+.B \-a
+Generate a plain text approximation of the typeset output.
+.
+.
+.TP
+.B \-b
+Write a backtrace to the standard error stream on each error or warning.
+.
+.
+.TP
+.B \-c
+Start with color output disabled.
+.
+.
+.TP
+.B \-C
+Enable AT&T
+.I troff \" AT&T
+compatibility mode;
+implies
+.BR \-c .
+.
+.
+.TP
+.BI \-d\~ cs
+.TQ
+.BI \-d\~ name = string
+Define string.
+.
+.
+.TP
+.B \-E
+Inhibit
+.I @g@troff
+error messages;
+implies
+.BR \-Ww .
+.
+.
+.TP
+.BI \-f\~ fam
+Set default font family.
+.
+.
+.TP
+.BI \-F\~ dir
+Search in directory
+.I dir
+for the selected output device's directory of device and font
+description files.
+.
+.
+.TP
+.B \-i
+Process standard input after the specified input files.
+.
+.
+.TP
+.BI \-I\~ dir
+Search
+.I dir
+for input files.
+.
+.
+.TP
+.BI \-m\~ name
+Process
+.RI name .tmac
+before input files.
+.
+.
+.TP
+.BI \-M\~ dir
+Search directory
+.I dir
+for macro files.
+.
+.
+.TP
+.BI \-n\~ num
+Number the first page
+.IR num .
+.
+.
+.TP
+.BI \-o\~ list
+Output only pages in
+.IR list .
+.
+.
+.TP
+.BI \-r\~ cnumeric-expression
+.TQ
+.BI \-r\~ register = numeric-expression
+Define register.
+.
+.
+.TP
+.BI \-w\~ name
+.TQ
+.BI \-W\~ name
+Enable
+.RB ( \-w )
+or inhibit
+.RB ( \-W )
+emission of warnings in category
+.IR name .
+.
+.
+.TP
+.B \-z
+Suppress formatted device-independent output of
+.IR @g@troff .
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+The architecture of the GNU
+.I roff
+system
+follows that of other device-independent
+.I roff
+implementations,
+comprising preprocessors,
+macro packages,
+output drivers
+(or \[lq]postprocessors\[rq]),
+a suite of utilities,
+and the formatter
+.I @g@troff
+at its heart.
+.
+See
+.MR roff @MAN7EXT@
+for a survey of how a
+.I roff
+system works.
+.
+.
+.P
+The front end programs available in the GNU
+.I roff
+system make it easier to use than traditional
+.IR roff s
+that required the construction of pipelines or use of temporary files to
+carry a source document from maintainable form to device-ready output.
+.
+The discussion below summarizes the constituent parts of the GNU
+.I roff
+system.
+.
+It complements
+.MR roff @MAN7EXT@
+with
+.IR groff -specific
+information.
+.
+.
+.\" ====================================================================
+.SS "Getting started"
+.\" ====================================================================
+.
+Those who prefer to learn by experimenting or are desirous of rapid
+feedback from the system may wish to start with a \[lq]Hello,
+world!\&\[rq] document.
+.
+.
+.P
+.EX
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tascii \
+| sed \[aq]/\[ha]$/d\[aq]
+Hello, world!
+.EE
+.
+.
+.P
+We used a
+.I sed
+command only to eliminate the 65 blank lines that would otherwise flood
+the terminal screen.
+.
+.RI ( roff
+systems were developed in the days of paper-based terminals with 66
+lines to a page.)
+.
+.
+.P
+Today's users may prefer output to a UTF-8-capable terminal.
+.
+.
+.P
+.EX
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tutf8 \
+| sed \[aq]/\[ha]$/d\[aq]
+.EE
+.
+.
+.P
+Producing PDF,
+HTML,
+or \*[TeX]'s DVI is also straightforward.
+.
+The hard part may be selecting a viewer program for the output.
+.
+.
+.P
+.EX
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tpdf > hello.pdf
+$ \c
+.B evince hello.pdf
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Thtml > hello.html
+$ \c
+.B firefox hello.html
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tdvi > hello.dvi
+$ \c
+.B xdvi hello.html
+.EE
+.
+.
+.\" ====================================================================
+.SS "Using \f[I]groff\f[] as a REPL"
+.\" ====================================================================
+.
+Those with a programmer's bent may be pleased to know that they can use
+.I groff
+in a read-evaluate-print loop (REPL).
+.
+Doing so can be handy to verify one's understanding of the formatter's
+behavior and/or the syntax it accepts.
+.
+Turning on all warnings with
+.B \-ww
+can aid this goal.
+.
+.
+.P
+.EX
+$ \c
+.B groff \-ww \-Tutf8
+.B \[rs]# This is a comment. Let\[aq]s define a register.
+.B .nr a 1
+.B \[rs]# Do integer arithmetic with operators evaluated left-to-right.
+.B .nr b \[rs]n[a]+5/2
+.ne 2v
+.B \[rs]# Let\[aq]s get the result on the standard error stream.
+.B .tm \[rs]n[b]
+3
+.B \[rs]# Now we\[aq]ll define a string.
+.B .ds name Leslie\[rs]" This is another form of comment.
+.B .nr b (\[rs]n[a] + (7/2))
+.B \[rs]# Center the next two text input lines.
+.B .ce 2
+.B Hi, \[rs]*[name].
+.B Your secret number is \[rs]n[b].
+.B \[rs]# We will see that the division rounded toward zero.
+.B It is
+.B \[rs]# Here\[aq]s an if-else control structure.
+.B .ie (\[rs]n[b] % 2) odd.
+.B .el even.
+.B \[rs]# This trick sets the page length to the current vertical
+.B \[rs]# position, so that blank lines don\[aq]t spew when we\[aq]re \
+done.
+.B .pl \[rs]n[nl]u
+.I <Control-D>
+ Hi, Leslie.
+ Your secret number is 4.
+It is even.
+.EE
+.
+.
+.\" ====================================================================
+.SS "Paper format"
+.\" ====================================================================
+.
+In GNU
+.IR roff ,
+the page dimensions for the formatter
+.I @g@troff
+and for output devices are handled separately.
+.
+In the formatter,
+requests are used to set the page length
+.RB ( .pl ),
+page offset
+(or left margin,
+.BR .po ),
+and line length
+.RB ( .ll ).
+.
+The right margin is not explicitly configured;
+the combination of page offset and line length provides the information
+necessary to derive it.
+.
+The
+.I papersize
+macro package,
+automatically loaded by
+.IR @g@troff ,
+provides an interface for configuring page dimensions by convenient
+names,
+like \[lq]letter\[rq] or
+\[lq]A4\[rq];
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+The formatter's default in this installation is
+.RB \[lq] @PAGE@ \[rq].
+.
+.
+.P
+It is up to each macro package to respect the page dimensions configured
+in this way.
+.
+Some offer alternative mechanisms.
+.
+.
+.P
+For each output device,
+the size of the output medium can be set in its
+.I DESC
+file.
+.
+Most output drivers also recognize a command-line option
+.B \-p
+to override the default dimensions and an option
+.B \-l
+to use landscape orientation.
+.
+See
+.MR groff_font @MAN5EXT@
+for a description of the
+.B papersize
+directive,
+which takes an argument of the same form as
+.BR \-p .
+.
+The output driver's man page,
+such as
+.MR grops @MAN1EXT@ ,
+may also be helpful.
+.
+.I groff
+uses the command-line option
+.B \-P
+to pass options to output devices;
+for example,
+use the following for PostScript output on A4 paper in landscape
+orientation.
+.
+.
+.IP
+.EX
+groff \-Tps \-dpaper=a4l \-P\-pa4 \-P\-l \-ms foo.ms > foo.ps
+.EE
+.
+.
+.\" ====================================================================
+.SS "Front end"
+.\" ====================================================================
+.
+The
+.I groff
+program is a wrapper around the
+.MR @g@troff @MAN1EXT@
+program.
+.
+It allows one to specify preprocessors via command-line options and
+automatically runs the appropriate postprocessor for the selected
+output device.
+.
+Doing so,
+the manual construction of pipelines or management of temporary files
+required of users of traditional
+.MR roff @MAN7EXT@
+systems can be avoided.
+.
+Use the
+.MR grog @MAN1EXT@
+program to infer an appropriate
+.I groff
+command line to format a document.
+.
+.
+.\" ====================================================================
+.SS Language
+.\" ====================================================================
+.
+Input to a
+.I roff
+system is in plain text interleaved with control lines and escape
+sequences.
+.
+The combination constitutes a document in one of a family of languages
+we also call
+.IR roff ;
+see
+.MR roff @MAN7EXT@
+for background.
+.
+An overview of GNU
+.I roff
+language syntax and features,
+including lists of all supported escape sequences,
+requests,
+and predefined registers,
+can be found in
+.MR groff @MAN7EXT@ .
+.
+GNU
+.I roff
+extensions to the AT&T
+.I troff
+language,
+a common subset of
+.I roff
+dialects extant today,
+are detailed in
+.MR groff_diff @MAN7EXT@ .
+.
+.
+.\" ====================================================================
+.SS Preprocessors
+.\" ====================================================================
+.
+A preprocessor interprets a domain-specific language that produces
+.I roff
+language output.
+.
+Frequently,
+such input is confined to sections or regions of a
+.I roff
+input file
+(bracketed with macro calls specific to each preprocessor),
+which it replaces.
+.
+Preprocessors therefore often interpret a subset of
+.I roff
+syntax along with their own language.
+.
+GNU
+.I roff
+provides reimplementations of most preprocessors familiar to users of
+AT&T
+.IR troff ; \" AT&T
+these routinely have extended features and/or require GNU
+.I troff \" GNU
+to format their output.
+.
+.
+.br
+.ne 10v
+.P
+.RS
+.TS
+tab($);
+Li Lx.
+@g@tbl$lays out tables;
+@g@eqn$typesets mathematics;
+@g@pic$draws diagrams;
+@g@refer$processes bibliographic references;
+@g@soelim$preprocesses \[lq]sourced\[rq] input files;
+@g@grn$T{
+renders
+.MR gremlin 1
+diagrams;
+T}
+@g@chem$T{
+draws chemical structural formul\[ae]
+using
+.IR pic ; \" generic
+T}
+gperl$T{
+populates
+.I groff
+registers and strings using
+.MR perl 1 ;
+T}
+glilypond$T{
+embeds
+.I LilyPond
+sheet music;
+and
+T}
+gpinyin$T{
+eases Mandarin Chinese input using Hanyu Pinyin.
+T}
+.TE
+.RE
+.
+.
+.P
+A preprocessor unique to GNU
+.I roff
+is
+.MR preconv @MAN1EXT@ ,
+which converts various input encodings to something GNU
+.I troff \" GNU
+can understand.
+.
+When used,
+it is run before any other preprocessors.
+.
+.
+.P
+Most preprocessors enclose content between a pair of characteristic
+tokens.
+.
+Such a token must occur at the beginning of an input line and use the
+dot control character.
+.
+Spaces and tabs must not follow the control character or precede the
+end of the input line.
+.
+Deviating from these rules defeats a token's recognition by the
+preprocessor.
+.
+Tokens are generally preserved in preprocessor output and interpreted as
+macro calls subsequently by
+.IR @g@troff .
+.
+The
+.I @g@ideal
+preprocessor is not yet available in
+.IR groff .
+.
+.
+.P
+.TS
+box, center, tab (^);
+c | c | c
+CfCR | CfCR | CfCR.
+preprocessor^starting token^ending token
+=
+@g@chem^.cstart^.cend
+@g@eqn^.EQ^.EN
+grap^.G1^.G2
+@g@grn^.GS^.GE
+.\" Keep the .IF line below the @g@ideal line.
+@g@ideal^.IS^.IE
+^^.IF
+.\" Keep the .PF line below the @g@pic line.
+@g@pic^.PS^.PE
+^^.PF
+^^.PY
+@g@refer^.R1^.R2
+@g@tbl^.TS^.TE
+_
+glilypond^.lilypond start^.lilypond stop
+gperl^.Perl start^.Perl stop
+gpinyin^.pinyin start^.pinyin stop
+.TE
+.
+.
+.\" ====================================================================
+.SS "Macro packages"
+.\" ====================================================================
+.
+Macro files are
+.I roff
+input files designed to produce no output themselves but instead ease
+the preparation of other
+.I roff
+documents.
+.
+When a macro file is installed at a standard location and suitable for
+use by a general audience,
+it is termed a
+.IR "macro package" .
+.
+.
+.P
+Macro packages can be loaded prior to any
+.I roff
+input documents with the
+.BR \-m \~option.
+.
+The GNU
+.I roff
+system implements most well-known macro packages for AT&T
+.I troff \" AT&T
+.\" exceptions: mpm, mv
+in a compatible way and extends them.
+.
+These have one- or two-letter names arising from intense practices of
+naming economy in early Unix culture,
+a laconic approach that led to many of the packages being identified in
+general usage with the
+.I nroff
+and
+.I troff
+option letter used to invoke them,
+sometimes to punning effect,
+as with \[lq]man\[rq]
+(short for \[lq]manual\[rq]),
+and even with the option dash,
+as in the case of the
+.I s
+package,
+much better known as
+.I ms
+or even
+.IR \-ms .
+.
+.
+.P
+Macro packages serve a variety of purposes.
+.
+Some are \[lq]full-service\[rq] packages,
+adopting responsibility for page layout among other fundamental tasks,
+and defining their own lexicon of macros for document composition;
+each such package stands alone and a given document can use at most one.
+.
+.
+.TP
+.I an
+is used to compose man pages in the format originating in Version\~7
+Unix (1979);
+see
+.MR groff_man @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-man .
+.
+.
+.TP
+.I doc
+is used to compose man pages in the format originating in 4.3BSD-Reno
+(1990);
+see
+.MR groff_mdoc @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-mdoc .
+.
+.
+.TP
+.I e
+is the Berkeley general-purpose macro suite,
+developed as an alternative to AT&T's
+.IR s ;
+see
+.MR groff_me @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-me .
+.
+.
+.TP
+.I m
+implements the format used by the
+second-generation AT&T macro suite for general documents,
+a successor to
+.IR s ;
+see
+.MR groff_mm @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-mm .
+.
+.
+.TP
+.I om
+(invariably called \[lq]mom\[rq])
+is a modern package written by Peter Schaffter specifically for GNU
+.IR roff .
+.
+Consult the
+.UR file://\:@HTMLDOCDIR@/\:mom/\:toc\:.html
+.I mom
+HTML manual
+.UE
+for extensive documentation.
+.
+She\[em]for
+.I mom
+takes the female pronoun\[em]can be specified on the command line as
+.BR \-mom .
+.
+.
+.TP
+.I s
+is the original AT&T general-purpose document format;
+see
+.MR groff_ms @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-ms .
+.
+.
+.P
+Others are supplemental.
+.
+For instance,
+.
+.I \%andoc
+is a wrapper package specific to GNU
+.I roff
+that recognizes whether a document uses
+.I man
+or
+.I mdoc
+format and loads the corresponding macro package.
+.
+It can be specified on the command line as
+.BR \%\-mandoc .
+.
+A
+.MR man 1
+librarian program \" such as man-db, since 2001
+may use this macro file to delegate loading of the correct macro
+package;
+it is thus unnecessary for
+.I man
+itself to scan the contents of a document to decide the issue.
+.
+.
+.P
+Many macro files augment the function of the full-service packages,
+or of
+.I roff
+documents that do not employ such a package\[em]the latter are sometimes
+characterized as \[lq]raw\[rq].
+.
+These auxiliary packages are described,
+along with
+details of macro file naming and placement,
+in
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS Formatters
+.\" ====================================================================
+.
+The formatter,
+the program that interprets
+.I roff
+language input,
+is
+.MR @g@troff @MAN1EXT@ .
+.
+It provides the features of the AT&T
+.I troff \" AT&T
+and
+.I nroff \" AT&T
+programs as well as many extensions.
+.
+The command-line option
+.B \-C
+switches
+.I @g@troff
+into
+.IR "compatibility mode" ,
+which tries to emulate AT&T
+.I troff \" AT&T
+as closely as is practical to enable the formatting of documents written
+for the older system.
+.
+.
+.P
+A shell script,
+.MR @g@nroff @MAN1EXT@ ,
+emulates the behavior of AT&T
+.IR nroff . \" AT&T
+.
+It attempts to correctly encode the output based on the locale,
+relieving the user of the need to specify an output device with the
+.B \-T
+option and is therefore convenient for use with terminal output devices,
+described in the next subsection.
+.
+.
+.P
+GNU
+.I troff \" GNU
+generates output in a device-independent,
+but not device-agnostic,
+page description language detailed in
+.MR groff_out @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS "Output devices"
+.\" ====================================================================
+.
+.I @g@troff
+output is formatted for a particular
+.IR "output device" ,
+typically specified by the
+.B \-T
+option to the formatter or a front end.
+.
+If neither this option nor the
+.I \%GROFF_TYPESETTER
+environment variable is used,
+the default output device is
+.BR @DEVICE@ .
+.
+An output device may be any of the following.
+.
+.
+.TP 9n \" to fit "X100\-12" even on troff devices
+.B ascii
+for terminals using the ISO 646 1991:IRV character set and encoding,
+also known as US-ASCII.
+.
+.
+.TP
+.B cp1047
+for terminals using the IBM code page 1047 character set and encoding.
+.
+.
+.TP
+.B dvi
+for TeX DVI format.
+.
+.
+.TP
+.B html
+.TQ
+.B xhtml
+for HTML and XHTML output,
+respectively.
+.
+.
+.TP
+.B latin1
+for terminals using the ISO Latin-1
+(ISO 8859-1)
+character set and encoding.
+.
+.
+.TP
+.B lbp
+for Canon CaPSL printers
+(LBP-4 and LBP-8 series laser printers).
+.
+.
+.TP
+.B lj4
+for HP LaserJet4-compatible
+(or other PCL5-compatible)
+printers.
+.
+.
+.TP
+.B pdf
+for PDF output.
+.
+.
+.TP
+.B ps
+for PostScript output.
+.
+.
+.TP
+.B utf8
+for terminals using the ISO 10646 (\[lq]Unicode\[rq]) character set in
+UTF-8 encoding.
+.
+.
+.TP
+.B X75
+for previewing with
+.I \%gxditview
+using
+75 dpi resolution and a
+10-point base type size.
+.
+.
+.TP
+.B X75\-12
+for previewing with
+.I \%gxditview
+using
+75 dpi resolution and a
+12-point base type size.
+.
+.
+.TP
+.B X100
+for previewing with
+.I \%gxditview
+using
+100 dpi resolution and a
+10-point base type size.
+.
+.
+.TP
+.B X100\-12
+for previewing with
+.I \%gxditview
+using
+100 dpi resolution
+and a
+12-point base type size.
+.
+.
+.\" ====================================================================
+.SS Postprocessors
+.\" ====================================================================
+.
+Any program that interprets the output of
+GNU
+.I troff \" GNU
+is a
+postprocessor.
+.
+The postprocessors provided by GNU
+.I roff
+are
+.IR "output drivers" ,
+which prepare a document for viewing or printing.
+.
+Postprocessors for other purposes,
+such as page resequencing or statistical measurement of a document,
+are conceivable.
+.
+.
+.P
+An output driver supports one or more output devices,
+each with its own device description file.
+.
+A device determines its postprocessor with the
+.B postpro
+directive in its device description file;
+see
+.MR groff_font @MAN5EXT@ .
+.
+The
+.B \-X
+option overrides this selection,
+causing
+.I \%gxditview
+to serve as the output driver.
+.
+.
+.TP
+.MR grodvi @MAN1EXT@
+provides
+.BR dvi .
+.
+.
+.TP
+.MR grohtml @MAN1EXT@
+provides
+.B html
+and
+.BR xhtml .
+.
+.
+.TP
+.MR grolbp @MAN1EXT@
+provides
+.BR lbp .
+.
+.
+.TP
+.MR grolj4 @MAN1EXT@
+provides
+.BR lj4 .
+.
+.
+.TP
+.MR gropdf @MAN1EXT@
+provides
+.BR pdf .
+.
+.
+.TP
+.MR grops @MAN1EXT@
+provides
+.BR ps .
+.
+.
+.TP
+.MR grotty @MAN1EXT@
+provides
+.BR ascii ,
+.BR cp1047 ,
+.BR latin1 ,
+and
+.BR utf8 .
+.
+.
+.TP
+.MR gxditview @MAN1EXT@
+provides
+.BR X75 ,
+.BR X75\-12 ,
+.BR X100 ,
+and
+.BR X100\-12 ,
+and additionally can preview
+.BR ps .
+.
+.
+.\" ====================================================================
+.SS Utilities
+.\" ====================================================================
+.
+GNU
+.I roff
+includes a suite of utilities.
+.
+.
+.TP
+.MR gdiffmk @MAN1EXT@
+marks differences between a pair of
+.I roff
+input files.
+.
+.
+.TP
+.MR grog @MAN1EXT@
+infers the
+.I groff
+command a document requires.
+.
+.
+.P
+Several utilities prepare descriptions of fonts,
+enabling the formatter to use them when producing output for a given
+device.
+.
+.
+.TP
+.MR addftinfo @MAN1EXT@
+adds information to AT&T
+.I troff \" AT&T
+font description files to enable their use with
+GNU
+.IR troff .\" GNU
+.
+.
+.TP
+.MR afmtodit @MAN1EXT@
+creates font description files for PostScript Type\~1 fonts.
+.
+.
+.TP
+.MR pfbtops @MAN1EXT@
+translates a PostScript Type\~1 font in PFB
+(Printer Font Binary)
+format to PFA
+(Printer Font ASCII),
+which can then be interpreted by
+.IR \%afmtodit .
+.
+.
+.TP
+.MR hpftodit @MAN1EXT@
+creates font description files for the HP LaserJet\~4 family of
+printers.
+.
+.
+.TP
+.MR tfmtodit @MAN1EXT@
+creates font description files for the TeX DVI device.
+.
+.
+.TP
+.MR xtotroff @MAN1EXT@
+creates font description files for X Window System core fonts.
+.
+.
+.P
+A trio of tools transform material constructed using
+.I roff
+preprocessor languages into graphical image files.
+.
+.
+.TP
+.MR eqn2graph @MAN1EXT@
+converts an
+.I eqn
+equation into a cropped image.
+.
+.
+.TP
+.MR grap2graph @MAN1EXT@
+converts a
+.I grap
+diagram into a cropped image.
+.
+.
+.TP
+.MR pic2graph @MAN1EXT@
+converts a
+.I pic
+diagram into a cropped image.
+.
+.
+.P
+Another set of programs works with the bibliographic data files used
+by the
+.MR refer @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.MR @g@indxbib @MAN1EXT@
+makes inverted indices for bibliographic databases,
+speeding lookup operations on them.
+.
+.
+.TP
+.MR lkbib @MAN1EXT@
+searches the databases.
+.
+.
+.TP
+.MR @g@lookbib @MAN1EXT@
+interactively searches
+the databases.
+.
+.
+.\" ====================================================================
+.SH "Exit status"
+.\" ====================================================================
+.
+.I groff
+exits with a failure status if there was a problem parsing its arguments
+and a successful status if either of the options
+.B \-h
+or
+.B \-\-help
+was specified.
+.
+Otherwise,
+.I groff
+runs a pipeline to process its input;
+if all commands within the pipeline exit successfully,
+.I groff
+does likewise.
+.
+If not,
+.IR groff 's
+exit status encodes a summary of problems encountered,
+setting bit\~0 if a command exited with a failure status,
+bit\~1 if a command was terminated with a signal,
+and bit\~2 if a command could not be executed.
+.
+(Thus,
+if all three misfortunes befell one's pipeline,
+.I groff
+would exit with status 2\[ha]0 + 2\[ha]1 + 2\[ha]2 = 1+2+4 = 7.)
+.
+To troubleshoot pipeline problems,
+you may wish to re-run the
+.I groff
+command with the
+.B \-V
+option and break the reported pipeline down into separate stages,
+inspecting the exit status of and diagnostic messages emitted by each
+command.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+Normally,
+the path separator in environment variables ending with
+.I PATH
+is the colon;
+this may vary depending on the operating system.
+.
+For example,
+Windows uses a semicolon instead.
+.
+.
+.TP
+.I GROFF_BIN_PATH
+This search path,
+followed by
+.IR PATH ,
+is used to locate commands executed by
+.IR groff .
+.
+If it is not set,
+the installation directory of the GNU
+.I roff
+executables,
+.IR @BINDIR@ ,
+is searched before
+.IR PATH .
+.
+.
+.TP
+.I GROFF_COMMAND_PREFIX
+GNU
+.I roff
+can be configured at compile time to apply a prefix to the names of the
+programs it provides that had a counterpart in AT&T
+.IR troff , \" AT&T
+so that name collisions are avoided at run time.
+.
+The default prefix is empty.
+.
+.
+.IP
+When used,
+this prefix is conventionally the letter \[lq]g\[rq].
+.
+For example,
+GNU
+.I troff \" GNU
+would be installed as
+.IR gtroff .
+.
+Besides
+.IR troff , \" GNU
+the prefix applies to
+the formatter
+.IR nroff ; \" GNU
+the preprocessors
+.IR eqn , \" generic
+.IR grn , \" generic
+.IR pic , \" generic
+.IR \%refer , \" generic
+.IR tbl , \" generic
+and
+.IR \%soelim ; \" generic
+and the utilities
+.I \%indxbib \" generic
+and
+.IR \%lookbib . \" generic
+.
+.
+.TP
+.I GROFF_ENCODING
+The value of this variable is passed to the
+.IR preconv (@MAN1EXT@)
+preprocessor's
+.B \-e
+option to select the character encoding of input files.
+.
+This variable's existence implies
+the
+.I groff
+option
+.BR \-k .
+.
+If set but empty,
+.I groff
+calls
+.I preconv
+without an
+.B \-e
+option.
+.
+.IR groff 's
+.B \-K
+option overrides
+.IR \%GROFF_ENCODING .
+.
+.
+.TP
+.I GROFF_FONT_PATH
+Seek the selected output device's directory of device and font
+description files in this list of directories.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROFF_TMAC_PATH
+Seek macro files in this list of directories.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.TP
+.I GROFF_TMPDIR
+Create temporary files in this directory.
+.
+If not set,
+but the environment variable
+.I \%TMPDIR
+is set,
+temporary files are created there instead.
+.
+On Windows systems,
+if neither of the foregoing are set,
+the environment variables
+.I TMP
+and
+.I TEMP
+(in that order)
+are checked also.
+.
+Otherwise,
+temporary files are created in
+.IR /tmp .
+.
+The
+.MR @g@refer @MAN1EXT@ ,
+.MR grohtml @MAN1EXT@ ,
+and
+.MR grops @MAN1EXT@
+commands use temporary files.
+.
+.
+.TP
+.I GROFF_TYPESETTER
+Set the default output device.
+.
+If empty or not set,
+.B @DEVICE@
+is used.
+.
+The
+.B \-T
+option overrides
+.IR \%GROFF_TYPESETTER .
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A time stamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation time stamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR localtime 3
+when the formatter starts up and stored in registers usable by documents
+and macro packages.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+.I roff
+systems are best known for formatting man pages.
+.
+Once a
+.MR man 1
+librarian program has located a man page,
+it may execute a
+.I groff
+command much like the following.
+.
+.RS
+.EX
+groff \-t \-man \-Tutf8 /usr/share/man/man1/groff.1
+.EE
+.RE
+.
+The librarian will also pipe the output through a pager,
+which might not interpret the SGR terminal escape sequences
+.I groff
+emits for boldface,
+underlining,
+or italics;
+see section \[lq]Limitations\[rq] below.
+.
+.
+.P
+To process a
+.I roff
+input file using the preprocessors
+.I @g@tbl
+and
+.I @g@pic
+and the
+.I me
+macro package in the way to which AT&T
+.I troff \" AT&T
+users were accustomed,
+one would type
+(or script)
+a pipeline.
+.
+.
+.IP
+.EX
+@g@pic foo.me | @g@tbl | @g@troff \-me \-Tutf8 | grotty
+.EE
+.
+.
+.P
+Using
+.IR groff ,
+this pipe can be shortened to an equivalent command.
+.
+.IP
+.EX
+groff \-p \-t \-me \-T utf8 foo.me
+.EE
+.
+.
+.P
+An even easier way to do this is to use
+.MR grog @MAN1EXT@
+to guess the preprocessor and macro options and execute the result by
+using the command substitution feature of the shell.
+.
+.IP
+.EX
+$(grog \-Tutf8 foo.me)
+.EE
+.
+.
+.P
+Each command-line option to a postprocessor must be specified with any
+required leading dashes
+.RB \[lq] \- \[rq]
+.\" No GNU roff postprocessor uses long options for anything except
+.\" --help or --version.
+.\"or
+.\".RB \[lq] \-\- \[rq]
+because
+.I groff
+passes the arguments as-is to the postprocessor;
+this permits arbitrary arguments to be transmitted.
+.
+For example,
+to pass a title to the
+.I gxditview
+postprocessor,
+the shell commands
+.
+.RS
+.EX
+groff \-X \-P \-title \-P \[aq]trial run\[aq] mydoc.t
+.EE
+.RE
+.
+and
+.
+.RS
+.EX
+groff \-X \-Z mydoc.t | gxditview \-title \[aq]trial run\[aq] \-
+.EE
+.RE
+.
+are equivalent.
+.
+.
+.\" ====================================================================
+.SH Limitations
+.\" ====================================================================
+.
+When paging output for the
+.BR ascii ,
+.BR cp1047 ,
+.BR latin1 ,
+and
+.B utf8
+devices,
+programs like
+.MR more 1
+and
+.MR less 1
+may require command-line options to correctly handle some terminal
+escape sequences;
+see
+.MR grotty @MAN1EXT@ .
+.
+.
+.P
+On EBCDIC hosts such as OS/390 Unix,
+the output devices
+.B ascii
+and
+.B latin1
+aren't available.
+.
+Conversely,
+the output device
+.B cp1047
+is not available on systems based on the ISO\~646 or ISO\~8859 character
+encoding standards.
+.
+.
+.\" ====================================================================
+.SH "Installation directories"
+.\" ====================================================================
+.
+GNU
+.I roff
+installs files in varying locations depending on its compile-time
+configuration.
+.
+On this installation,
+the following locations are used.
+.
+.
+.if !'@APPDEFDIR@'' \{\
+.TP
+.I @APPDEFDIR@
+Application defaults directory for
+.MR gxditview @MAN1EXT@ .
+.\}
+.
+.
+.TP
+.I @BINDIR@
+Directory containing
+.IR groff 's
+executable commands.
+.
+.
+.TP
+.I @COMMON_WORDS_FILE@
+List of common words for
+.MR indxbib @MAN1EXT@ .
+.
+.
+.TP
+.I @DATASUBDIR@
+Directory for data files.
+.
+.
+.TP
+.I @DEFAULT_INDEX@
+Default index for
+.MR lkbib @MAN1EXT@
+and
+.MR refer @MAN1EXT@ .
+.
+.
+.TP
+.I @DOCDIR@
+Documentation directory.
+.
+.
+.TP
+.I @EXAMPLEDIR@
+Example directory.
+.
+.
+.TP
+.I @FONTDIR@
+Font directory.
+.
+.
+.TP
+.I @HTMLDOCDIR@
+HTML documentation directory.
+.
+.
+.TP
+.I @LEGACYFONTDIR@
+Legacy font directory.
+.
+.
+.TP
+.I @LOCALFONTDIR@
+Local font directory.
+.
+.
+.TP
+.I @LOCALMACRODIR@
+Local macro package
+.RI ( tmac
+file) directory.
+.
+.
+.TP
+.I @MACRODIR@
+Macro package
+.RI ( tmac
+file) directory.
+.
+.
+.TP
+.I @OLDFONTDIR@
+Font directory for compatibility with old versions of
+.IR groff ;
+see
+.MR grops @MAN1EXT@ .
+.
+.
+.TP
+.I @PDFDOCDIR@
+PDF documentation directory.
+.
+.
+.if !'@COMPATIBILITY_WRAPPERS@'no' \{\
+.TP
+.I @SYSTEMMACRODIR@
+System macro package
+.RI ( tmac
+file) directory.
+.\}
+.
+.
+.\" ====================================================================
+.SS "\f[I]groff\f[] macro directory"
+.\" ====================================================================
+.
+Most macro files supplied with GNU
+.I roff
+are stored in
+.I @MACRODIR@
+for the installation corresponding to this document.
+.
+As a rule,
+multiple directories are searched for macro files;
+see
+.MR @g@troff @MAN1EXT@ .
+.
+For a catalog of macro files GNU
+.I roff
+provides,
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS "\f[I]groff\f[] device and font description directory"
+.\" ====================================================================
+.
+Device and font description files supplied with GNU
+.I roff
+are stored in
+.I @FONTDIR@
+for the installation corresponding to this document.
+.
+As a rule,
+multiple directories are searched for device and font description files;
+see
+.MR @g@troff @MAN1EXT@ .
+.
+For the formats of these files,
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Availability
+.\" ====================================================================
+.
+Obtain links to
+.I groff
+releases for download,
+its source repository,
+discussion mailing lists,
+a support ticket tracker,
+and further information from the
+.UR http://\:www\:.gnu\:.org/\:software/\:groff
+.I groff
+page of the GNU website
+.UE .
+.
+.
+.P
+A free implementation of the
+.I grap
+preprocessor,
+written by
+.MT faber@\:lunabase\:.org
+Ted Faber
+.ME ,
+can be found at the
+.UR http://\:www\:.lunabase\:.org/\:\[ti]faber/\:Vault/\:software/\
+\:grap/
+.I grap
+website
+.UE .
+.
+.I groff
+supports only this
+.IR grap .
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I groff
+(both the front-end command and the overall system)
+was primarily written by
+.MT jjc@\:jclark\:.com
+James Clark
+.ME .
+.
+Contributors to this document include Clark,
+Trent A.\& Fisher,
+.MT wl@gnu.org
+Werner Lemberg
+.ME ,
+.MT groff\-bernd.warken\-72@\:web\:.de
+Bernd Warken
+.ME ,
+and
+.MT g.branden\:.robinson@\:gmail\:.com
+G.\& Branden Robinson
+.ME .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.IR "Groff: The GNU Implementation of troff" ,
+by Trent A.\& Fisher and Werner Lemberg,
+is the primary
+.I groff
+manual.
+.
+You can browse it interactively with \[lq]info groff\[rq].
+.
+.
+.\" groff ships 59 man pages generated from 58 source files. The
+.\" numbered comments refer to their sorting order in the source tree,
+.\" so that it is easier to tell that we've enumerated all of them.
+.TP
+Introduction, \c
+history, \c
+and further reading:
+.MR roff @MAN7EXT@ \" #23
+.
+.
+.TP
+.RI "Viewer for\~" groff "\~(and AT&T device-independent\~" troff \
+)\~documents:
+.MR gxditview @MAN1EXT@ \" #33
+.
+.
+.TP
+Preprocessors:
+.MR @g@chem @MAN1EXT@ , \" #1
+.MR @g@eqn @MAN1EXT@ , \" #34
+.MR @g@neqn @MAN1EXT@ , \" #35
+.MR glilypond @MAN1EXT@ , \" #4
+.MR @g@grn @MAN1EXT@ , \" #36
+.MR preconv @MAN1EXT@ , \" #38
+.MR gperl @MAN1EXT@ , \" #5
+.MR @g@pic @MAN1EXT@ , \" #37
+.MR gpinyin @MAN1EXT@ , \" #6
+.MR @g@refer @MAN1EXT@ , \" #39
+.MR @g@soelim @MAN1EXT@ , \" #40
+.MR @g@tbl @MAN1EXT@ \" #41
+.
+.
+.TP
+Macro packages and package-specific utilities:
+.MR groff_hdtbl @MAN7EXT@ , \" #9
+.MR groff_man @MAN7EXT@ , \" #55a
+.MR groff_man_style @MAN7EXT@ , \" #55b
+.MR groff_mdoc @MAN7EXT@ , \" #56
+.MR groff_me @MAN7EXT@ , \" #57
+.MR groff_mm @MAN7EXT@ , \" # 10
+.MR groff_mmse @MAN7EXT@ , \" # 11
+.MR mmroff @MAN1EXT@ , \" #12
+.MR groff_mom @MAN7EXT@ , \" #13
+.MR pdfmom @MAN1EXT@ , \" #30
+.MR groff_ms @MAN7EXT@ , \" #58
+.MR groff_rfc1345 @MAN7EXT@ , \" 16
+.MR groff_trace @MAN7EXT@ , \" #59
+.MR groff_www @MAN7EXT@ \" #60
+.
+.
+.TP
+Bibliographic database management tools:
+.MR @g@indxbib @MAN1EXT@ , \" #49
+.MR lkbib @MAN1EXT@ , \" #50
+.MR @g@lookbib @MAN1EXT@ \" #51
+.
+.
+.TP
+Language, \c
+conventions, \c
+and GNU extensions:
+.MR groff @MAN7EXT@ , \" #17
+.MR groff_char @MAN7EXT@ , \" #18
+.MR groff_diff @MAN7EXT@ , \" #19
+.MR groff_font @MAN5EXT@ , \" #20
+.MR groff_tmac @MAN5EXT@ \" #22
+.
+.
+.TP
+Intermediate output language:
+.MR groff_out @MAN5EXT@ \" #21
+.
+.
+.TP
+Formatter program:
+.MR @g@troff @MAN1EXT@ \" #45
+.
+.
+.TP
+Formatter wrappers:
+.\".MR groff @MAN1EXT@ , \" 42 -- this page
+.MR @g@nroff @MAN1EXT@ , \" #44
+.MR pdfroff @MAN1EXT@ \" #14
+.
+.
+.TP
+Postprocessors for output devices:
+.MR grodvi @MAN1EXT@ , \" #24
+.MR grohtml @MAN1EXT@ , \" #25
+.MR grolbp @MAN1EXT@ , \" #26
+.MR grolj4 @MAN1EXT@ , \" #27
+.MR gropdf @MAN1EXT@ , \" #29
+.MR grops @MAN1EXT@ , \" #31
+.MR grotty @MAN1EXT@ \" #32
+.
+.
+.TP
+Font support utilities:
+.MR addftinfo @MAN1EXT@ , \" #46
+.MR afmtodit @MAN1EXT@ , \" #47
+.MR hpftodit @MAN1EXT@ , \" #48
+.MR pfbtops @MAN1EXT@ , \" #52
+.MR tfmtodit @MAN1EXT@ , \" #53
+.MR xtotroff @MAN1EXT@ \" #54
+.
+.
+.TP
+Graphics conversion utilities:
+.MR eqn2graph @MAN1EXT@ , \" #2
+.MR grap2graph @MAN1EXT@ , \" #7
+.MR pic2graph @MAN1EXT@ \" #15
+.
+.
+.TP
+Difference-marking utility:
+.MR gdiffmk @MAN1EXT@ \" #3
+.
+.
+.TP
+\[lq]groff guess\[rq] utility:
+.MR grog @MAN1EXT@ \" #43
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_groff_1_man_C]
+.do rr *groff_groff_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/roff/groff/groff.am b/src/roff/groff/groff.am
new file mode 100644
index 0000000..8937d4c
--- /dev/null
+++ b/src/roff/groff/groff.am
@@ -0,0 +1,87 @@
+# Copyright (C) 1993-2020 Free Software Foundation, Inc.
+#
+# Original Makefile.sub rewritten by
+# Bernd Warken <groff-bernd.warken-72@web.de>
+# and Werner LEMBERG <wl@gnu.org>
+#
+# Automake migration by
+# Bertrand Garrigues <bertrand.garrigues@laposte.net>
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += groff
+groff_LDADD = \
+ libgroff.a \
+ lib/libgnu.a \
+ $(LIBM)
+groff_SOURCES = \
+ src/roff/groff/groff.cpp \
+ src/roff/groff/pipeline.c \
+ src/roff/groff/pipeline.h
+src/roff/groff/groff.$(OBJEXT): defs.h
+man1_MANS += src/roff/groff/groff.1
+EXTRA_DIST += src/roff/groff/groff.1.man
+
+groff_TESTS = \
+ src/roff/groff/tests/ab_works.sh \
+ src/roff/groff/tests/adjustment_works.sh \
+ src/roff/groff/tests/break_zero-length_output_line_sanely.sh \
+ src/roff/groff/tests/device_control_escapes_express_basic_latin.sh \
+ src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh \
+ src/roff/groff/tests/dot-cp_register_works.sh \
+ src/roff/groff/tests/dot-nm_register_works.sh \
+ src/roff/groff/tests/dot-nn_register_works.sh \
+ src/roff/groff/tests/evc_produces_no_output_if_invalid.sh \
+ src/roff/groff/tests/fp_should_not_traverse_directories.sh \
+ src/roff/groff/tests/handle_special_input_code_points.sh \
+ src/roff/groff/tests/html_works_with_grn_and_eqn.sh \
+ src/roff/groff/tests/initialization_is_quiet.sh \
+ src/roff/groff/tests/localization_works.sh \
+ src/roff/groff/tests/msoquiet_works.sh \
+ src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh \
+ src/roff/groff/tests/output_driver_C_and_G_options_work.sh \
+ src/roff/groff/tests/recognize_end_of_sentence.sh \
+ src/roff/groff/tests/regression_savannah_56555.sh \
+ src/roff/groff/tests/regression_savannah_58153.sh \
+ src/roff/groff/tests/regression_savannah_58162.sh \
+ src/roff/groff/tests/regression_savannah_58337.sh \
+ src/roff/groff/tests/regression_savannah_59202.sh \
+ src/roff/groff/tests/smoke-test_html_device.sh \
+ src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh \
+ src/roff/groff/tests/soquiet_works.sh \
+ src/roff/groff/tests/string_case_xform_errors.sh \
+ src/roff/groff/tests/string_case_xform_requests.sh \
+ src/roff/groff/tests/string_case_xform_unicode_escape.sh \
+ src/roff/groff/tests/substring_works.sh \
+ src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh
+TESTS += $(groff_TESTS)
+EXTRA_DIST += $(groff_TESTS)
+
+# required test artifacts
+EXTRA_DIST += \
+ src/roff/groff/tests/artifacts/HONEYPOT \
+ src/roff/groff/tests/artifacts/devascii/README
+
+groff_XFAIL_TESTS = \
+ src/roff/groff/tests/string_case_xform_unicode_escape.sh
+XFAIL_TESTS += $(groff_XFAIL_TESTS)
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/roff/groff/groff.cpp b/src/roff/groff/groff.cpp
new file mode 100644
index 0000000..4eb7329
--- /dev/null
+++ b/src/roff/groff/groff.cpp
@@ -0,0 +1,866 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// A front end for groff.
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "font.h"
+#include "device.h"
+#include "pipeline.h"
+#include "nonposix.h"
+#include "relocate.h"
+#include "defs.h"
+
+#define GXDITVIEW "gxditview"
+
+// troff will be passed an argument of -rXREG=1 if the -X option is
+// specified
+#define XREG ".X"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+// The number of commands must be in sync with MAX_COMMANDS in
+// pipeline.h.
+
+// grap, chem, and ideal must come before pic;
+// tbl must come before eqn
+const int PRECONV_INDEX = 0;
+const int SOELIM_INDEX = PRECONV_INDEX + 1;
+const int REFER_INDEX = SOELIM_INDEX + 1;
+const int GRAP_INDEX = REFER_INDEX + 1;
+const int CHEM_INDEX = GRAP_INDEX + 1;
+const int IDEAL_INDEX = CHEM_INDEX + 1;
+const int PIC_INDEX = IDEAL_INDEX + 1;
+const int TBL_INDEX = PIC_INDEX + 1;
+const int GRN_INDEX = TBL_INDEX + 1;
+const int EQN_INDEX = GRN_INDEX + 1;
+const int TROFF_INDEX = EQN_INDEX + 1;
+const int POST_INDEX = TROFF_INDEX + 1;
+const int SPOOL_INDEX = POST_INDEX + 1;
+
+const int NCOMMANDS = SPOOL_INDEX + 1;
+
+class possible_command {
+ char *name;
+ string args;
+ char **argv;
+
+ void build_argv();
+public:
+ possible_command();
+ ~possible_command();
+ void clear_name();
+ void set_name(const char *);
+ void set_name(const char *, const char *);
+ const char *get_name();
+ void append_arg(const char *, const char * = 0 /* nullptr */);
+ void insert_arg(const char *);
+ void insert_args(string s);
+ void clear_args();
+ char **get_argv();
+ void print(int is_last, FILE *fp);
+};
+
+extern "C" const char *Version_string;
+
+int lflag = 0;
+char *spooler = 0 /* nullptr */;
+char *postdriver = 0 /* nullptr */;
+char *predriver = 0 /* nullptr */;
+bool need_postdriver = true;
+char *saved_path = 0 /* nullptr */;
+char *groff_bin_path = 0 /* nullptr */;
+char *groff_font_path = 0 /* nullptr */;
+
+possible_command commands[NCOMMANDS];
+
+int run_commands(int no_pipe);
+void print_commands(FILE *);
+void append_arg_to_string(const char *arg, string &str);
+void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+const char *xbasename(const char *);
+
+void usage(FILE *stream);
+
+static char *xstrdup(const char *s) {
+ if (0 /* nullptr */ == s)
+ return const_cast<char *>(s);
+ char *str = strdup(s);
+ if (0 /* nullptr */ == str)
+ fatal("unable to copy string: %1", strerror(errno));
+ return str;
+}
+
+static void xputenv(const char *s) {
+ if (putenv(const_cast<char *>(s)) != 0)
+ fatal("unable to write to environment: %1", strerror(errno));
+ return;
+}
+
+static void xexit(int status) {
+ free(spooler);
+ free(predriver);
+ free(postdriver);
+ free(saved_path);
+ free(groff_bin_path);
+ free(groff_font_path);
+ exit(status);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ assert(NCOMMANDS <= MAX_COMMANDS);
+ string Pargs, Largs, Fargs;
+ int Kflag = 0;
+ int vflag = 0;
+ int Vflag = 0;
+ int zflag = 0;
+ int iflag = 0;
+ int Xflag = 0;
+ int oflag = 0;
+ int safer_flag = 1;
+ int is_xhtml = 0;
+ int eflag = 0;
+ int need_pic = 0;
+ int opt;
+ const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
+ const char *encoding = getenv("GROFF_ENCODING");
+ if (!command_prefix)
+ command_prefix = PROG_PREFIX;
+ commands[TROFF_INDEX].set_name(command_prefix, "troff");
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(
+ argc, argv,
+ "abcCd:D:eEf:F:gGhiI:jJkK:lL:m:M:"
+ "n:No:pP:r:RsStT:UvVw:W:XzZ",
+ long_options, NULL))
+ != EOF) {
+ char buf[3];
+ buf[0] = '-';
+ buf[1] = opt;
+ buf[2] = '\0';
+ switch (opt) {
+ case 'i':
+ iflag = 1;
+ break;
+ case 'I':
+ commands[GRN_INDEX].set_name(command_prefix, "grn");
+ commands[GRN_INDEX].append_arg("-M", optarg);
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ commands[SOELIM_INDEX].append_arg(buf, optarg);
+ // .psbb may need to search for files
+ commands[TROFF_INDEX].append_arg(buf, optarg);
+ // \X'ps:import' may need to search for files
+ Pargs += buf;
+ Pargs += optarg;
+ Pargs += '\0';
+ break;
+ case 'D':
+ commands[PRECONV_INDEX].set_name("preconv");
+ commands[PRECONV_INDEX].append_arg("-D", optarg);
+ break;
+ case 'K':
+ commands[PRECONV_INDEX].append_arg("-e", optarg);
+ Kflag = 1;
+ // fall through
+ case 'k':
+ commands[PRECONV_INDEX].set_name("preconv");
+ break;
+ case 't':
+ commands[TBL_INDEX].set_name(command_prefix, "tbl");
+ break;
+ case 'J':
+ // commands[IDEAL_INDEX].set_name(command_prefix, "gideal");
+ // need_pic = 1;
+ break;
+ case 'j':
+ commands[CHEM_INDEX].set_name(command_prefix, "chem");
+ need_pic = 1;
+ break;
+ case 'p':
+ commands[PIC_INDEX].set_name(command_prefix, "pic");
+ break;
+ case 'g':
+ commands[GRN_INDEX].set_name(command_prefix, "grn");
+ break;
+ case 'G':
+ commands[GRAP_INDEX].set_name(command_prefix, "grap");
+ need_pic = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ commands[EQN_INDEX].set_name(command_prefix, "eqn");
+ break;
+ case 's':
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ break;
+ case 'R':
+ commands[REFER_INDEX].set_name(command_prefix, "refer");
+ break;
+ case 'z':
+ case 'a':
+ commands[TROFF_INDEX].append_arg(buf);
+ // fall through
+ case 'Z':
+ zflag++;
+ need_postdriver = false;
+ break;
+ case 'l':
+ lflag++;
+ break;
+ case 'V':
+ Vflag++;
+ break;
+ case 'v':
+ vflag = 1;
+ printf("GNU groff version %s\n", Version_string);
+ printf(
+ "Copyright (C) 2022 Free Software Foundation, Inc.\n"
+ "GNU groff comes with ABSOLUTELY NO WARRANTY.\n"
+ "You may redistribute copies of groff and its subprograms\n"
+ "under the terms of the GNU General Public License.\n"
+ "For more information about these matters, see the file\n"
+ "named COPYING.\n");
+ printf("\ncalled subprograms:\n\n");
+ fflush(stdout);
+ // Pass -v to all possible subprograms
+ commands[PRECONV_INDEX].append_arg(buf);
+ commands[CHEM_INDEX].append_arg(buf);
+ commands[IDEAL_INDEX].append_arg(buf);
+ commands[POST_INDEX].append_arg(buf);
+ // fall through
+ case 'C':
+ commands[SOELIM_INDEX].append_arg(buf);
+ commands[REFER_INDEX].append_arg(buf);
+ commands[PIC_INDEX].append_arg(buf);
+ commands[GRAP_INDEX].append_arg(buf);
+ commands[TBL_INDEX].append_arg(buf);
+ commands[GRN_INDEX].append_arg(buf);
+ commands[EQN_INDEX].append_arg(buf);
+ commands[TROFF_INDEX].append_arg(buf);
+ break;
+ case 'N':
+ commands[EQN_INDEX].append_arg(buf);
+ break;
+ case 'h':
+ usage(stdout);
+ break;
+ case 'E':
+ case 'b':
+ commands[TROFF_INDEX].append_arg(buf);
+ break;
+ case 'c':
+ commands[TROFF_INDEX].append_arg(buf);
+ break;
+ case 'S':
+ safer_flag = 1;
+ break;
+ case 'U':
+ safer_flag = 0;
+ break;
+ case 'T':
+ if (strcmp(optarg, "xhtml") == 0) {
+ // force soelim to aid the html preprocessor
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ Pargs += "-x";
+ Pargs += '\0';
+ Pargs += 'x';
+ Pargs += '\0';
+ is_xhtml = 1;
+ device = "html";
+ break;
+ }
+ if (strcmp(optarg, "html") == 0)
+ // force soelim to aid the html preprocessor
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ if (strcmp(optarg, "Xps") == 0) {
+ warning("-TXps option is obsolete: use -X -Tps instead");
+ device = "ps";
+ Xflag++;
+ }
+ else
+ device = optarg;
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ if (Fargs.length() > 0) {
+ Fargs += PATH_SEP_CHAR;
+ Fargs += optarg;
+ }
+ else
+ Fargs = optarg;
+ break;
+ case 'o':
+ oflag = 1;
+ // fall through
+ case 'f':
+ case 'm':
+ case 'r':
+ case 'd':
+ case 'n':
+ case 'w':
+ case 'W':
+ commands[TROFF_INDEX].append_arg(buf, optarg);
+ break;
+ case 'M':
+ commands[EQN_INDEX].append_arg(buf, optarg);
+ commands[GRAP_INDEX].append_arg(buf, optarg);
+ commands[GRN_INDEX].append_arg(buf, optarg);
+ commands[TROFF_INDEX].append_arg(buf, optarg);
+ break;
+ case 'P':
+ Pargs += optarg;
+ Pargs += '\0';
+ break;
+ case 'L':
+ append_arg_to_string(optarg, Largs);
+ break;
+ case 'X':
+ Xflag++;
+ need_postdriver = false;
+ break;
+ case '?':
+ usage(stderr);
+ xexit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "no case to handle option character");
+ break;
+ }
+ }
+ if (need_pic)
+ commands[PIC_INDEX].set_name(command_prefix, "pic");
+ if (encoding) {
+ commands[PRECONV_INDEX].set_name("preconv");
+ if (!Kflag && *encoding)
+ commands[PRECONV_INDEX].append_arg("-e", encoding);
+ }
+ if (!safer_flag) {
+ commands[TROFF_INDEX].insert_arg("-U");
+ commands[PIC_INDEX].append_arg("-U");
+ }
+ font::set_unknown_desc_command_handler(handle_unknown_desc_command);
+ const char *desc = font::load_desc();
+ if (0 /* nullptr */ == desc)
+ fatal("cannot load 'DESC' description file for device '%1'",
+ device);
+ if (need_postdriver && (0 /* nullptr */ == postdriver))
+ fatal_with_file_and_line(desc, 0, "device description file missing"
+ " 'postpro' directive");
+ if (predriver && !zflag) {
+ commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
+ commands[TROFF_INDEX].set_name(predriver);
+ // pass the device arguments to the predrivers as well
+ commands[TROFF_INDEX].insert_args(Pargs);
+ if (eflag && is_xhtml)
+ commands[TROFF_INDEX].insert_arg("-e");
+ if (vflag)
+ commands[TROFF_INDEX].insert_arg("-v");
+ }
+ const char *real_driver = 0 /* nullptr */;
+ if (Xflag) {
+ real_driver = postdriver;
+ postdriver = xstrdup(GXDITVIEW); // so we can free() it in xexit()
+ commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
+ }
+ if (postdriver)
+ commands[POST_INDEX].set_name(postdriver);
+ int gxditview_flag = postdriver
+ && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
+ if (gxditview_flag && argc - optind == 1) {
+ commands[POST_INDEX].append_arg("-title");
+ commands[POST_INDEX].append_arg(argv[optind]);
+ commands[POST_INDEX].append_arg("-xrm");
+ commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
+ string filename_string("|");
+ append_arg_to_string(argv[0], filename_string);
+ append_arg_to_string("-Z", filename_string);
+ for (int i = 1; i < argc; i++)
+ append_arg_to_string(argv[i], filename_string);
+ filename_string += '\0';
+ commands[POST_INDEX].append_arg("-filename");
+ commands[POST_INDEX].append_arg(filename_string.contents());
+ }
+ if (gxditview_flag && Xflag) {
+ string print_string(real_driver);
+ if (spooler) {
+ print_string += " | ";
+ print_string += spooler;
+ print_string += Largs;
+ }
+ print_string += '\0';
+ commands[POST_INDEX].append_arg("-printCommand");
+ commands[POST_INDEX].append_arg(print_string.contents());
+ }
+ const char *p = Pargs.contents();
+ const char *end = p + Pargs.length();
+ while (p < end) {
+ commands[POST_INDEX].append_arg(p);
+ p = strchr(p, '\0') + 1;
+ }
+ if (gxditview_flag)
+ commands[POST_INDEX].append_arg("-");
+ if (lflag && !vflag && !Xflag && spooler) {
+ commands[SPOOL_INDEX].set_name(BSHELL);
+ commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
+ Largs += '\0';
+ Largs = spooler + Largs;
+ commands[SPOOL_INDEX].append_arg(Largs.contents());
+ }
+ if (zflag) {
+ commands[POST_INDEX].set_name(0 /* nullptr */);
+ commands[SPOOL_INDEX].set_name(0 /* nullptr */);
+ }
+ commands[TROFF_INDEX].append_arg("-T", device);
+ if (strcmp(device, "html") == 0) {
+ if (is_xhtml) {
+ if (oflag)
+ fatal("'-o' option is invalid with device 'xhtml'");
+ if (zflag)
+ commands[EQN_INDEX].append_arg("-Tmathml:xhtml");
+ else if (eflag)
+ commands[EQN_INDEX].clear_name();
+ }
+ else {
+ if (oflag)
+ fatal("'-o' option is invalid with device 'html'");
+ // html renders equations as images via ps
+ commands[EQN_INDEX].append_arg("-Tps:html");
+ }
+ }
+ else
+ commands[EQN_INDEX].append_arg("-T", device);
+
+ commands[GRN_INDEX].append_arg("-T", device);
+
+ int first_index;
+ for (first_index = 0; first_index < TROFF_INDEX; first_index++)
+ if (commands[first_index].get_name() != 0 /* nullptr */)
+ break;
+ if (optind < argc) {
+ if (argv[optind][0] == '-' && argv[optind][1] != '\0')
+ commands[first_index].append_arg("--");
+ for (int i = optind; i < argc; i++)
+ commands[first_index].append_arg(argv[i]);
+ if (iflag)
+ commands[first_index].append_arg("-");
+ }
+ if (Fargs.length() > 0) {
+ string e = "GROFF_FONT_PATH";
+ e += '=';
+ e += Fargs;
+ char *fontpath = getenv("GROFF_FONT_PATH");
+ if (fontpath && *fontpath) {
+ e += PATH_SEP_CHAR;
+ e += fontpath;
+ }
+ e += '\0';
+ groff_font_path = xstrdup(e.contents());
+ xputenv(groff_font_path);
+ }
+ {
+ // we save the original path in GROFF_PATH__ and put it into the
+ // environment -- troff will pick it up later.
+ char *path = getenv("PATH");
+ string g = "GROFF_PATH__";
+ g += '=';
+ if (path && *path)
+ g += path;
+ g += '\0';
+ saved_path = xstrdup(g.contents());
+ xputenv(saved_path);
+ char *binpath = getenv("GROFF_BIN_PATH");
+ string f = "PATH";
+ f += '=';
+ if (binpath && *binpath)
+ f += binpath;
+ else {
+ binpath = relocatep(BINPATH);
+ f += binpath;
+ }
+ if (path && *path) {
+ f += PATH_SEP_CHAR;
+ f += path;
+ }
+ f += '\0';
+ groff_bin_path = xstrdup(f.contents());
+ xputenv(groff_bin_path);
+ }
+ if (Vflag)
+ print_commands(Vflag == 1 ? stdout : stderr);
+ if (Vflag == 1)
+ xexit(EXIT_SUCCESS);
+ xexit(run_commands(vflag));
+}
+
+const char *xbasename(const char *s)
+{
+ if (!s)
+ return 0 /* nullptr */;
+ // DIR_SEPS[] are possible directory separator characters; see
+ // nonposix.h. We want the rightmost separator of all possible ones.
+ // Example: d:/foo\\bar.
+ const char *p = strrchr(s, DIR_SEPS[0]), *p1;
+ const char *sep = &DIR_SEPS[1];
+
+ while (*sep)
+ {
+ p1 = strrchr(s, *sep);
+ if (p1 && (!p || p1 > p))
+ p = p1;
+ sep++;
+ }
+ return p ? p + 1 : s;
+}
+
+void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "print") == 0) {
+ if (arg == 0 /* nullptr */)
+ error_with_file_and_line(filename, lineno, "'print' directive"
+ " requires an argument");
+ else
+ spooler = xstrdup(arg);
+ return;
+ }
+ if (strcmp(command, "prepro") == 0) {
+ if (arg == 0 /* nullptr */)
+ error("'prepro' directive requires an argument");
+ else {
+ for (const char *p = arg; *p; p++)
+ if (csspace(*p)) {
+ error_with_file_and_line(filename, lineno, "invalid 'prepro'"
+ " directive argument '%1': program"
+ " name required", arg);
+ }
+ predriver = xstrdup(arg);
+ }
+ return;
+ }
+ if (strcmp(command, "postpro") == 0) {
+ if (arg == 0 /* nullptr */)
+ error_with_file_and_line(filename, lineno, "'postpro' directive"
+ " requires an argument");
+ else {
+ for (const char *p = arg; *p; p++)
+ if (csspace(*p)) {
+ error_with_file_and_line(filename, lineno, "invalid 'postpro'"
+ " directive argument '%1': program"
+ " name required", arg);
+ return;
+ }
+ postdriver = xstrdup(arg);
+ }
+ return;
+ }
+}
+
+void print_commands(FILE *fp)
+{
+ int last;
+ for (last = SPOOL_INDEX; last >= 0; last--)
+ if (commands[last].get_name() != 0 /* nullptr */)
+ break;
+ for (int i = 0; i <= last; i++)
+ if (commands[i].get_name() != 0 /* nullptr */)
+ commands[i].print(i == last, fp);
+}
+
+// Run the commands. Return the code with which to exit.
+
+int run_commands(int no_pipe)
+{
+ char **v[NCOMMANDS]; // vector of argv arrays to pipe together
+ int ncommands = 0;
+ for (int i = 0; i < NCOMMANDS; i++)
+ if (commands[i].get_name() != 0 /* nullptr */)
+ v[ncommands++] = commands[i].get_argv();
+ return run_pipeline(ncommands, v, no_pipe);
+}
+
+possible_command::possible_command()
+: name(0), argv(0)
+{
+}
+
+possible_command::~possible_command()
+{
+ free(name);
+ delete[] argv;
+}
+
+void possible_command::set_name(const char *s)
+{
+ free(name);
+ name = xstrdup(s);
+}
+
+void possible_command::clear_name()
+{
+ delete[] name;
+ delete[] argv;
+ name = NULL;
+ argv = NULL;
+}
+
+void possible_command::set_name(const char *s1, const char *s2)
+{
+ free(name);
+ name = (char*)malloc(strlen(s1) + strlen(s2) + 1);
+ strcpy(name, s1);
+ strcat(name, s2);
+}
+
+const char *possible_command::get_name()
+{
+ return name;
+}
+
+void possible_command::clear_args()
+{
+ args.clear();
+}
+
+void possible_command::append_arg(const char *s, const char *t)
+{
+ args += s;
+ if (t)
+ args += t;
+ args += '\0';
+}
+
+void possible_command::insert_arg(const char *s)
+{
+ string str(s);
+ str += '\0';
+ str += args;
+ args = str;
+}
+
+void possible_command::insert_args(string s)
+{
+ const char *p = s.contents();
+ const char *end = p + s.length();
+ int l = 0;
+ if (p >= end)
+ return;
+ // find the total number of arguments in our string
+ do {
+ l++;
+ p = strchr(p, '\0') + 1;
+ } while (p < end);
+ // now insert each argument preserving the order
+ for (int i = l - 1; i >= 0; i--) {
+ p = s.contents();
+ for (int j = 0; j < i; j++)
+ p = strchr(p, '\0') + 1;
+ insert_arg(p);
+ }
+}
+
+void possible_command::build_argv()
+{
+ if (argv)
+ return;
+ // Count the number of arguments.
+ int len = args.length();
+ int argc = 1;
+ char *p = 0 /* nullptr */;
+ if (len > 0) {
+ p = &args[0];
+ for (int i = 0; i < len; i++)
+ if (p[i] == '\0')
+ argc++;
+ }
+ // Build an argument vector.
+ argv = new char *[argc + 1];
+ argv[0] = name;
+ for (int i = 1; i < argc; i++) {
+ argv[i] = p;
+ p = strchr(p, '\0') + 1;
+ }
+ argv[argc] = 0 /* nullptr */;
+}
+
+void possible_command::print(int is_last, FILE *fp)
+{
+ build_argv();
+ if (IS_BSHELL(argv[0])
+ && argv[1] != 0 /* nullptr */
+ && strcmp(argv[1], BSHELL_DASH_C) == 0
+ && argv[2] != 0 /* nullptr */ && argv[3] == 0 /* nullptr */)
+ fputs(argv[2], fp);
+ else {
+ fputs(argv[0], fp);
+ string str;
+ for (int i = 1; argv[i] != 0 /* nullptr */; i++) {
+ str.clear();
+ append_arg_to_string(argv[i], str);
+ put_string(str, fp);
+ }
+ }
+ if (is_last)
+ putc('\n', fp);
+ else
+ fputs(" | ", fp);
+}
+
+void append_arg_to_string(const char *arg, string &str)
+{
+ str += ' ';
+ int needs_quoting = 0;
+ // Native Windows programs don't support '..' style of quoting, so
+ // always behave as if ARG included the single quote character.
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ int contains_single_quote = 1;
+#else
+ int contains_single_quote = 0;
+#endif
+ const char*p;
+ for (p = arg; *p != '\0'; p++)
+ switch (*p) {
+ case ';':
+ case '&':
+ case '(':
+ case ')':
+ case '|':
+ case '^':
+ case '<':
+ case '>':
+ case '\n':
+ case ' ':
+ case '\t':
+ case '\\':
+ case '"':
+ case '$':
+ case '?':
+ case '*':
+ needs_quoting = 1;
+ break;
+ case '\'':
+ contains_single_quote = 1;
+ break;
+ }
+ if (contains_single_quote || arg[0] == '\0') {
+ str += '"';
+ for (p = arg; *p != '\0'; p++)
+ switch (*p) {
+#if !(defined(_WIN32) && !defined(__CYGWIN__))
+ case '"':
+ case '\\':
+ case '$':
+ str += '\\';
+#else
+ case '"':
+ case '\\':
+ if (*p == '"' || (*p == '\\' && p[1] == '"'))
+ str += '\\';
+#endif
+ // fall through
+ default:
+ str += *p;
+ break;
+ }
+ str += '"';
+ }
+ else if (needs_quoting) {
+ str += '\'';
+ str += arg;
+ str += '\'';
+ }
+ else
+ str += arg;
+}
+
+char **possible_command::get_argv()
+{
+ build_argv();
+ return argv;
+}
+
+void usage(FILE *stream)
+{
+ // Add `J` to the cluster if we ever get ideal(1) support.
+ fprintf(stream,
+"usage: %s [-abcCeEgGijklNpRsStUVXzZ] [-d ctext] [-d string=text]"
+" [-D fallback-encoding] [-f font-family] [-F font-directory]"
+" [-I inclusion-directory] [-K input-encoding] [-L spooler-argument]"
+" [-m macro-package] [-M macro-directory] [-n page-number]"
+" [-o page-list] [-P postprocessor-argument] [-r cnumeric-expression]"
+" [-r register=numeric-expression] [-T output-device]"
+" [-w warning-category] [-W warning-category]"
+" [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"groff (GNU roff) is a typesetting system that reads plain text input\n"
+"files that include formatting commands to produce output in\n"
+"PostScript, PDF, HTML, or DVI formats or for display to a terminal.\n"
+"See the groff(1) manual page.\n",
+ stream);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+extern "C" {
+
+void c_error(const char *format, const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ error(format, arg1, arg2, arg3);
+}
+
+void c_fatal(const char *format, const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ fatal(format, arg1, arg2, arg3);
+}
+
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/groff/pipeline.c b/src/roff/groff/pipeline.c
new file mode 100644
index 0000000..defafc2
--- /dev/null
+++ b/src/roff/groff/pipeline.c
@@ -0,0 +1,589 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_STRERROR
+#include <string.h>
+#else
+extern char *strerror();
+#endif
+
+#ifdef _POSIX_VERSION
+
+#include <sys/wait.h>
+#define PID_T pid_t
+
+#else /* not _POSIX_VERSION */
+
+/* traditional Unix */
+
+#define WIFEXITED(s) (((s) & 0377) == 0)
+#define WIFSTOPPED(s) (((s) & 0377) == 0177)
+#define WIFSIGNALED(s) (((s) & 0377) != 0 && (((s) & 0377) != 0177))
+#define WEXITSTATUS(s) (((s) >> 8) & 0377)
+#define WTERMSIG(s) ((s) & 0177)
+#define WSTOPSIG(s) (((s) >> 8) & 0377)
+
+#ifndef WCOREFLAG
+#define WCOREFLAG 0200
+#endif
+
+#define PID_T int
+
+#endif /* not _POSIX_VERSION */
+
+/* SVR4 uses WCOREFLG; Net 2 uses WCOREFLAG. */
+#ifndef WCOREFLAG
+#ifdef WCOREFLG
+#define WCOREFLAG WCOREFLG
+#endif /* WCOREFLG */
+#endif /* not WCOREFLAG */
+
+#ifndef WCOREDUMP
+#ifdef WCOREFLAG
+#define WCOREDUMP(s) ((s) & WCOREFLAG)
+#else /* not WCOREFLAG */
+#define WCOREDUMP(s) (0)
+#endif /* WCOREFLAG */
+#endif /* not WCOREDUMP */
+
+#include "pipeline.h"
+
+/* Prototype */
+int run_pipeline(int, char ***, int);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void c_error(const char *, const char *, const char *,
+ const char *);
+extern void c_fatal(const char *, const char *, const char *,
+ const char *);
+extern const char *i_to_a(int); /* from libgroff */
+
+#ifdef __cplusplus
+}
+#endif
+
+static void sys_fatal(const char *);
+static const char *xstrsignal(int);
+
+
+#if defined(__MSDOS__) \
+ || (defined(_WIN32) && !defined(_UWIN) && !defined(__CYGWIN__)) \
+ || defined(__EMX__)
+
+#include <process.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "nonposix.h"
+
+static const char *sh = "sh";
+static const char *cmd = "cmd";
+static const char *command = "command";
+
+extern int strcasecmp(const char *, const char *);
+
+char *sbasename(const char *path)
+{
+ char *base;
+ const char *p1, *p2;
+
+ p1 = path;
+ if ((p2 = strrchr(p1, '\\'))
+ || (p2 = strrchr(p1, '/'))
+ || (p2 = strrchr(p1, ':')))
+ p1 = p2 + 1;
+ if ((p2 = strrchr(p1, '.'))
+ && ((strcasecmp(p2, ".exe") == 0)
+ || (strcasecmp(p2, ".com") == 0)))
+ ;
+ else
+ p2 = p1 + strlen(p1);
+
+ base = malloc((size_t)(p2 - p1));
+ strncpy(base, p1, p2 - p1);
+ *(base + (p2 - p1)) = '\0';
+
+ return(base);
+}
+
+/* Get the name of the system shell */
+char *system_shell_name(void)
+{
+ const char *shell_name;
+
+ /*
+ Use a Unixy shell if it's installed. Use SHELL if set; otherwise,
+ let spawnlp try to find sh; if that fails, use COMSPEC if set; if
+ not, try cmd.exe; if that fails, default to command.com.
+ */
+
+ if ((shell_name = getenv("SHELL")) != NULL)
+ ;
+ else if (spawnlp(_P_WAIT, sh, sh, "-c", ":", NULL) == 0)
+ shell_name = sh;
+ else if ((shell_name = getenv("COMSPEC")) != NULL)
+ ;
+ else if (spawnlp(_P_WAIT, cmd, cmd, "/c", ";", NULL) == 0)
+ shell_name = cmd;
+ else
+ shell_name = command;
+
+ return sbasename(shell_name);
+}
+
+const char *system_shell_dash_c(void)
+{
+ char *shell_name;
+ const char *dash_c;
+
+ shell_name = system_shell_name();
+
+ /* Assume that if the shell name ends in 'sh', it's Unixy */
+ if (strcasecmp(shell_name + strlen(shell_name) - strlen("sh"), "sh") == 0)
+ dash_c = "-c";
+ else
+ dash_c = "/c";
+
+ free(shell_name);
+ return dash_c;
+}
+
+int is_system_shell(const char *prog)
+{
+ int result;
+ char *this_prog, *system_shell;
+
+ if (!prog) /* paranoia */
+ return 0;
+
+ this_prog = sbasename(prog);
+ system_shell = system_shell_name();
+
+ result = strcasecmp(this_prog, system_shell) == 0;
+
+ free(this_prog);
+ free(system_shell);
+
+ return result;
+}
+
+#ifdef _WIN32
+
+/*
+ Windows 32 doesn't have fork(), so we need to start asynchronous child
+ processes with spawn() rather than exec(). If there is more than one
+ command, i.e., a pipeline, the parent must set up each child's I/O
+ redirection prior to the spawn. The original stdout must be restored
+ before spawning the last process in the pipeline, and the original
+ stdin must be restored in the parent after spawning the last process
+ and before waiting for any of the children.
+*/
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int i;
+ int last_input = 0; /* pacify some compilers */
+ int save_stdin = 0;
+ int save_stdout = 0;
+ int ret = 0;
+ char err_str[BUFSIZ];
+ PID_T pids[MAX_COMMANDS];
+
+ for (i = 0; i < ncommands; i++) {
+ int pdes[2];
+ PID_T pid;
+
+ /* If no_pipe is set, just run the commands in sequence
+ to show the version numbers */
+ if (ncommands > 1 && !no_pipe) {
+ /* last command doesn't need a new pipe */
+ if (i < ncommands - 1) {
+ if (pipe(pdes) < 0) {
+ sprintf(err_str, "%s: pipe", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ }
+ /* 1st command; writer */
+ if (i == 0) {
+ /* save stdin */
+ if ((save_stdin = dup(STDIN_FILENO)) < 0)
+ sys_fatal("dup stdin");
+ /* save stdout */
+ if ((save_stdout = dup(STDOUT_FILENO)) < 0)
+ sys_fatal("dup stdout");
+
+ /* connect stdout to write end of pipe */
+ if (dup2(pdes[1], STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(pdes[1]) < 0) {
+ sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /*
+ Save the read end of the pipe so that it can be connected to
+ stdin of the next program in the pipeline during the next
+ pass through the loop.
+ */
+ last_input = pdes[0];
+ }
+ /* reader and writer */
+ else if (i < ncommands - 1) {
+ /* connect stdin to read end of last pipe */
+ if (dup2(last_input, STDIN_FILENO) < 0) {
+ sprintf(err_str, " %s: dup2(stdin)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(last_input) < 0) {
+ sprintf(err_str, "%s: close(last_input)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* connect stdout to write end of new pipe */
+ if (dup2(pdes[1], STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(pdes[1]) < 0) {
+ sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ last_input = pdes[0];
+ }
+ /* last command; reader */
+ else {
+ /* connect stdin to read end of last pipe */
+ if (dup2(last_input, STDIN_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdin)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(last_input) < 0) {
+ sprintf(err_str, "%s: close(last_input)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* restore original stdout */
+ if (dup2(save_stdout, STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(save_stdout))", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* close stdout copy */
+ if (close(save_stdout) < 0) {
+ sprintf(err_str, "%s: close(save_stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ }
+ }
+ if ((pid = spawnvp(_P_NOWAIT, commands[i][0], commands[i])) < 0) {
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ _exit(EXEC_FAILED_EXIT_STATUS);
+ }
+ pids[i] = pid;
+ }
+
+ if (ncommands > 1 && !no_pipe) {
+ /* restore original stdin if it was redirected */
+ if (dup2(save_stdin, STDIN_FILENO) < 0) {
+ sprintf(err_str, "dup2(save_stdin))");
+ sys_fatal(err_str);
+ }
+ /* close stdin copy */
+ if (close(save_stdin) < 0) {
+ sprintf(err_str, "close(save_stdin)");
+ sys_fatal(err_str);
+ }
+ }
+
+ for (i = 0; i < ncommands; i++) {
+ int status;
+ PID_T pid;
+
+ pid = pids[i];
+ if ((pid = WAIT(&status, pid, _WAIT_CHILD)) < 0) {
+ sprintf(err_str, "%s: wait", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ else if (status != 0)
+ ret |= 1;
+ }
+ return ret;
+}
+
+#else /* not _WIN32 */
+
+/* MS-DOS doesn't have 'fork', so we need to simulate the pipe by
+ running the programs in sequence with standard streams redirected to
+ and from temporary files.
+*/
+
+
+/* A signal handler that just records that a signal has happened. */
+static int child_interrupted;
+
+static RETSIGTYPE signal_catcher(int signo)
+{
+ child_interrupted++;
+}
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int save_stdin = dup(0);
+ int save_stdout = dup(1);
+ char *tmpfiles[2];
+ int infile = 0;
+ int outfile = 1;
+ int i, f, ret = 0;
+
+ /* Choose names for a pair of temporary files to implement the pipeline.
+ Microsoft's 'tempnam' uses the directory specified by 'getenv("TMP")'
+ if it exists; in case it doesn't, try the GROFF alternatives, or
+ 'getenv("TEMP")' as last resort -- at least one of these had better
+ be set, since Microsoft's default has a high probability of failure. */
+ char *tmpdir;
+ if ((tmpdir = getenv("GROFF_TMPDIR")) == NULL
+ && (tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = getenv("TEMP");
+
+ /* Don't use 'tmpnam' here: Microsoft's implementation yields unusable
+ file names if current directory is on network share with read-only
+ root. */
+ tmpfiles[0] = tempnam(tmpdir, NULL);
+ tmpfiles[1] = tempnam(tmpdir, NULL);
+
+ for (i = 0; i < ncommands; i++) {
+ int exit_status;
+ RETSIGTYPE (*prev_handler)(int);
+
+ if (i && !no_pipe) {
+ /* redirect stdin from temp file */
+ f = open(tmpfiles[infile], O_RDONLY|O_BINARY, 0666);
+ if (f < 0)
+ sys_fatal("open stdin");
+ if (dup2(f, 0) < 0)
+ sys_fatal("dup2 stdin");
+ if (close(f) < 0)
+ sys_fatal("close stdin");
+ }
+ if ((i < ncommands - 1) && !no_pipe) {
+ /* redirect stdout to temp file */
+ f = open(tmpfiles[outfile], O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
+ if (f < 0)
+ sys_fatal("open stdout");
+ if (dup2(f, 1) < 0)
+ sys_fatal("dup2 stdout");
+ if (close(f) < 0)
+ sys_fatal("close stdout");
+ }
+ else if (dup2(save_stdout, 1) < 0)
+ sys_fatal("restore stdout");
+
+ /* run the program */
+ child_interrupted = 0;
+ prev_handler = signal(SIGINT, signal_catcher);
+ exit_status = spawnvp(P_WAIT, commands[i][0], commands[i]);
+ signal(SIGINT, prev_handler);
+ if (child_interrupted) {
+ c_error("%1: Interrupted", commands[i][0], (char *)0, (char *)0);
+ ret |= 2;
+ }
+ else if (exit_status < 0) {
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ ret |= 4;
+ }
+ if (exit_status != 0)
+ ret |= 1;
+ /* There's no sense to continue with the pipe if one of the
+ programs has ended abnormally, is there? */
+ if (ret != 0)
+ break;
+ /* swap temp files: make output of this program be input for the next */
+ infile = 1 - infile;
+ outfile = 1 - outfile;
+ }
+ if (dup2(save_stdin, 0) < 0)
+ sys_fatal("restore stdin");
+ unlink(tmpfiles[0]);
+ unlink(tmpfiles[1]);
+ return ret;
+}
+
+#endif /* not _WIN32 */
+
+#else /* not __MSDOS__, not _WIN32 */
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int i;
+ int last_input = 0;
+ PID_T pids[MAX_COMMANDS];
+ int ret = 0;
+ int proc_count = ncommands;
+
+ for (i = 0; i < ncommands; i++) {
+ int pdes[2];
+ PID_T pid;
+
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (pipe(pdes) < 0)
+ sys_fatal("pipe");
+ }
+ pid = fork();
+ if (pid < 0)
+ sys_fatal("fork");
+ if (pid == 0) {
+ /* child */
+ if (last_input != 0) {
+ if (close(0) < 0)
+ sys_fatal("close");
+ if (dup(last_input) < 0)
+ sys_fatal("dup");
+ if (close(last_input) < 0)
+ sys_fatal("close");
+ }
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (close(1) < 0)
+ sys_fatal("close");
+ if (dup(pdes[1]) < 0)
+ sys_fatal("dup");
+ if (close(pdes[1]) < 0)
+ sys_fatal("close");
+ if (close(pdes[0]))
+ sys_fatal("close");
+ }
+ execvp(commands[i][0], commands[i]);
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ _exit(EXEC_FAILED_EXIT_STATUS);
+ }
+ /* in the parent */
+ if (last_input != 0) {
+ if (close(last_input) < 0)
+ sys_fatal("close");
+ }
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (close(pdes[1]) < 0)
+ sys_fatal("close");
+ last_input = pdes[0];
+ }
+ pids[i] = pid;
+ }
+ while (proc_count > 0) {
+ int status;
+ PID_T pid = wait(&status);
+
+ if (pid < 0)
+ sys_fatal("wait");
+ for (i = 0; i < ncommands; i++)
+ if (pids[i] == pid) {
+ pids[i] = -1;
+ --proc_count;
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+#ifdef SIGPIPE
+ if (sig == SIGPIPE) {
+ if (i == ncommands - 1) {
+ /* This works around a problem that occurred when using the
+ rerasterize action in gxditview. What seemed to be
+ happening (on SunOS 4.1.1) was that pclose() closed the
+ pipe and waited for groff, gtroff got a SIGPIPE, but
+ gpic blocked writing to gtroff, and so groff blocked
+ waiting for gpic and gxditview blocked waiting for
+ groff. I don't understand why gpic wasn't getting a
+ SIGPIPE. */
+ int j;
+
+ for (j = 0; j < ncommands; j++)
+ if (pids[j] > 0)
+ (void)kill(pids[j], SIGPIPE);
+ }
+ }
+ else
+#endif /* SIGPIPE */
+ {
+ c_error("%1: %2%3",
+ commands[i][0],
+ xstrsignal(sig),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+ ret |= 2;
+ }
+ }
+ else if (WIFEXITED(status)) {
+ int exit_status = WEXITSTATUS(status);
+
+ if (exit_status == EXEC_FAILED_EXIT_STATUS)
+ ret |= 4;
+ else if (exit_status != 0)
+ ret |= 1;
+ }
+ else
+ c_error("unexpected status %1", i_to_a(status), (char *)0,
+ (char *)0);
+ break;
+ }
+ }
+ return ret;
+}
+
+#endif /* not __MSDOS__, not _WIN32 */
+
+static void sys_fatal(const char *s)
+{
+ c_fatal("%1: %2", s, strerror(errno), (char *)0);
+}
+
+static const char *xstrsignal(int n)
+{
+ static char buf[sizeof("Signal ") + 1 + sizeof(int) * 3];
+
+#ifdef NSIG
+#if HAVE_DECL_STRSIGNAL
+ if (n >= 0 && n < NSIG && strsignal(n) != 0)
+ return strsignal(n);
+#else
+#if HAVE_DECL_SYS_SIGLIST
+ if (n >= 0 && n < NSIG && sys_siglist[n] != 0)
+ return sys_siglist[n];
+#endif /* HAVE_DECL_SYS_SIGLIST */
+#endif /* HAVE_DECL_STRSIGNAL */
+#endif /* NSIG */
+ sprintf(buf, "Signal %d", n);
+ return buf;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/groff/pipeline.h b/src/roff/groff/pipeline.h
new file mode 100644
index 0000000..17c02a6
--- /dev/null
+++ b/src/roff/groff/pipeline.h
@@ -0,0 +1,30 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef __cplusplus
+extern "C" {
+ int run_pipeline(int, char ***, int);
+}
+#endif
+
+/* run_pipeline can handle at most this many commands,
+ see the const numbers in groff.cpp */
+#define MAX_COMMANDS 15
+
+/* Children exit with this status if execvp fails. */
+#define EXEC_FAILED_EXIT_STATUS 0xff
diff --git a/src/roff/groff/tests/ab_works.sh b/src/roff/groff/tests/ab_works.sh
new file mode 100755
index 0000000..b353837
--- /dev/null
+++ b/src/roff/groff/tests/ab_works.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (C) 2021-2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Verify exit status and regression-test Savannah #60782.
+#
+# We don't test the X11 devices because groff launches an X client,
+# which has to be killed. Using "-z" to avoid this masks the bug.
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+for d in ascii cp1047 dvi html latin1 lbp lj4 pdf ps utf8
+do
+ echo "verifying exit status of .ab request using $d device" >&2
+ printf '.ab\n' | "$groff" -Z -T$d
+ test $? -eq 1 || exit 1
+done
+
+echo "verifying empty output of .ab request with no arguments" >&2
+OUT=$(printf '.ab\n' | "$groff" -Z -Tascii 2>&1)
+test "$OUT" = "" || exit 1
+
+echo "verifying that arguments to .ab request go to stderr" >&2
+OUT=$(printf '.ab foo\n' | "$groff" -Z -Tascii 2>&1 > /dev/null)
+test "$OUT" = "foo" || exit 1
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/adjustment_works.sh b/src/roff/groff/tests/adjustment_works.sh
new file mode 100755
index 0000000..e4cd65d
--- /dev/null
+++ b/src/roff/groff/tests/adjustment_works.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='.pl 1v
+.ll 9n
+foo bar\p
+.na
+foo bar\p
+.ad l
+foo bar\p
+.na
+foo bar\p
+.ad b
+foo bar\p
+.na
+foo bar\p
+.ad c
+foo bar\p
+.na
+foo bar\p
+.ad r
+foo bar\p
+.na
+foo bar\p
+.ad
+foo bar\p
+.ad b
+.ad 100
+foo bar\p'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii)
+B='foo bar' # 3 spaces
+L='foo bar' # left or off
+C=' foo bar' # trailing space truncated
+R=' foo bar' # 2 leading spaces
+
+echo "verifying default adjustment mode 'b'" >&2
+echo "$OUTPUT" | sed -n '1p' | grep -Fqx "$B"
+
+echo "verifying that .na works" >&2
+echo "$OUTPUT" | sed -n '2p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'l'" >&2
+echo "$OUTPUT" | sed -n '3p' | grep -Fqx "$L"
+
+echo "verifying that .na works after '.ad l'" >&2
+echo "$OUTPUT" | sed -n '4p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'b'" >&2
+echo "$OUTPUT" | sed -n '5p' | grep -Fqx "$B"
+
+echo "verifying that .na works after '.ad b'" >&2
+echo "$OUTPUT" | sed -n '6p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'c'" >&2
+echo "$OUTPUT" | sed -n '7p' | grep -Fqx "$C"
+
+echo "verifying that .na works after '.ad c'" >&2
+echo "$OUTPUT" | sed -n '8p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'r'" >&2
+echo "$OUTPUT" | sed -n '9p' | grep -Fqx "$R"
+
+echo "verifying that .na works after '.ad r'" >&2
+echo "$OUTPUT" | sed -n '10p' | grep -Fqx "$L"
+
+echo "verifying that '.ad' restores previous adjustment mode" >&2
+echo "$OUTPUT" | sed -n '11p' | grep -Fqx "$R"
+
+echo "verifying that out-of-range adjustment mode 100 is ignored" >&2
+echo "$OUTPUT" | sed -n '12p' | grep -Fqx "$B"
diff --git a/src/roff/groff/tests/artifacts/HONEYPOT b/src/roff/groff/tests/artifacts/HONEYPOT
new file mode 100644
index 0000000..51a57dd
--- /dev/null
+++ b/src/roff/groff/tests/artifacts/HONEYPOT
@@ -0,0 +1,15 @@
+name HONEYPOT
+spacewidth 24
+charset
+a 0 0 77
+b 0 0 79
+d 0 0 76
+e 0 0 82
+i 0 0 105
+l 0 0 65
+m 0 0 109
+o 0 0 86
+r 0 0 73
+s 0 0 115
+w 0 0 69
+y 0 0 121
diff --git a/src/roff/groff/tests/artifacts/devascii/README b/src/roff/groff/tests/artifacts/devascii/README
new file mode 100644
index 0000000..d89ba03
--- /dev/null
+++ b/src/roff/groff/tests/artifacts/devascii/README
@@ -0,0 +1 @@
+This directory is intentionally empty (apart from this file).
diff --git a/src/roff/groff/tests/break_zero-length_output_line_sanely.sh b/src/roff/groff/tests/break_zero-length_output_line_sanely.sh
new file mode 100755
index 0000000..8d6deb0
--- /dev/null
+++ b/src/roff/groff/tests/break_zero-length_output_line_sanely.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Do not core dump when attempting to distribute a space amount of zero
+# if someone sets the line length to zero. See Savannah #61089.
+# Reproducer courtesy of John Gardner.
+
+INPUT='.de _
+. na
+. nh
+. ll 0
+. di A
+\&\\$1
+. di
+. br
+..
+._ " XYZ"
+.A
+'
+
+OUTPUT=$(printf "%s" "$INPUT" | "$groff" -Tascii)
+echo "$OUTPUT" | grep -qx XYZ
diff --git a/src/roff/groff/tests/device_control_escapes_express_basic_latin.sh b/src/roff/groff/tests/device_control_escapes_express_basic_latin.sh
new file mode 100755
index 0000000..6418913
--- /dev/null
+++ b/src/roff/groff/tests/device_control_escapes_express_basic_latin.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+# Confirm translation of a groff special character escape sequence to a
+# basic Latin character when used in a device control escape sequence.
+#
+# $1 is the special character escape _without_ the leading backslash.
+# $2 is the expected output character _shell-quoted as necessary_.
+# $3 is a human-readable glyph description for the test log.
+# $4 is the groff -T device name under test.
+check_char () {
+ sc=$1
+ output=$2
+ description=$3
+ device=$4
+ printf 'checking conversion of \\%s to %s (%s) on device %s' \
+ "$sc" "$output" "$description" "$device" >&2
+ if ! printf '\\X#\\%s %s#\n' "$sc" "$desc" | "$groff" -T$device -Z \
+ | grep -Fqx 'x X '$output' '
+ then
+ printf '...FAILED' >&2
+ fail=yes
+ fi
+ printf '\n' >&2
+}
+
+for device in utf8 html
+do
+ check_char - - "minus sign" $device
+ check_char '[aq]' "'" "neutral apostrophe" $device
+ check_char '[dq]' '"' "double quote" $device
+ check_char '[ga]' '`' "grave accent" $device
+ check_char '[ha]' ^ "caret/hat" $device
+ check_char '[rs]' '\' "reverse solidus/backslash" $device
+ check_char '[ti]' '~' "tilde" $device
+done
+
+test -z "$fail" || exit 1
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh b/src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh
new file mode 100755
index 0000000..4f4a12a
--- /dev/null
+++ b/src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='ン
+ã‚ã‚AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+'
+
+echo "$DOC" | "$groff" -D utf8 -Tutf8 -mja
diff --git a/src/roff/groff/tests/dot-cp_register_works.sh b/src/roff/groff/tests/dot-cp_register_works.sh
new file mode 100755
index 0000000..ffbb402
--- /dev/null
+++ b/src/roff/groff/tests/dot-cp_register_works.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+DOC='.pl 1v
+A
+.do if 1 \n[.cp] \" Get initial compatibility state (depends on -C).
+B
+.do if 1 \n[.cp] \" Did observing the state change it?
+.cp 1
+C
+.do if 1 \n[.cp] \" Saved compatibility state should be 1 now.
+.cp 0
+D
+.do if 1 \n[.cp] \" Verify 1->0 transition.
+.cp 1
+E
+.do if 1 \n[.cp] \" Verify 0->1 transition.
+.cp 0
+F
+.if !\n[.C] \n[.cp] \" Outside of .do context, should return -1.
+'
+
+set -e
+
+printf "%s" "$DOC" | "$groff" -Tascii \
+ | grep -x "A 0 B 0 C 1 D 0 E 1 F -1"
+
+printf "%s" "$DOC" | "$groff" -C -Tascii \
+ | grep -x "A 1 B 1 C 1 D 0 E 1 F -1"
diff --git a/src/roff/groff/tests/dot-nm_register_works.sh b/src/roff/groff/tests/dot-nm_register_works.sh
new file mode 100755
index 0000000..809bbd3
--- /dev/null
+++ b/src/roff/groff/tests/dot-nm_register_works.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+DOC='\
+.nf
+foo (\n[.nm])
+.nm 1
+bar (\n[.nm])
+.nn
+baz (\n[.nm])
+.nm
+qux (\n[.nm])
+.fi
+'
+
+set -e
+
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx 'foo (0)'
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx ' 1 bar (1)'
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx 'baz (1)'
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx 'qux (0)'
diff --git a/src/roff/groff/tests/dot-nn_register_works.sh b/src/roff/groff/tests/dot-nn_register_works.sh
new file mode 100755
index 0000000..0998cb0
--- /dev/null
+++ b/src/roff/groff/tests/dot-nn_register_works.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Unit test .nn register.
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+input='.ec @
+.de is-numbered
+. nop This line
+. ie (@@n[.nm] & (1-@@n[.nn])) IS
+. el ISN'"'"'T
+. nop numbered.
+. br
+..
+Test line numbering.
+.is-numbered
+.nm 1
+.nn 2
+.is-numbered
+.is-numbered
+.is-numbered
+.nm
+.is-numbered
+.pl @n[nl]u'
+
+# Apply line numbers to the output externally for easy grepping.
+output=$(echo "$input" | $groff -Tascii | nl)
+echo "$output"
+
+echo "verifying that line 1 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+1[[:space:]]+Test line numbering\." || wail
+
+echo "verifying that line 2 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+2[[:space:]]+This line ISN'T" || wail
+
+echo "verifying that line 3 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+3[[:space:]]+This line ISN'T" || wail
+
+echo "verifying that line 4 is numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+4[[:space:]]+1 +This line IS numbered" || wail
+
+echo "verifying that line 5 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+5[[:space:]]+This line ISN'T" || wail
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/evc_produces_no_output_if_invalid.sh b/src/roff/groff/tests/evc_produces_no_output_if_invalid.sh
new file mode 100755
index 0000000..47b0d1d
--- /dev/null
+++ b/src/roff/groff/tests/evc_produces_no_output_if_invalid.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #60913.
+
+test -z "$(printf '.evc foo bar\n' | "$groff")"
diff --git a/src/roff/groff/tests/fp_should_not_traverse_directories.sh b/src/roff/groff/tests/fp_should_not_traverse_directories.sh
new file mode 100755
index 0000000..f60f42f
--- /dev/null
+++ b/src/roff/groff/tests/fp_should_not_traverse_directories.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #61424.
+#
+# The `fp` request should not be able to access font description files
+# outside of the device and font description search path (configurable
+# with the -F option and GROFF_FONT_PATH environment variable).
+#
+# An absolute file name _won't_ work: it gets dev\*[.T]/ stuck on the
+# front of it by libgroff.
+#
+# Locate directory containing our test artifacts.
+artifact_dir=
+base=src/roff/groff/tests
+device=artifacts
+
+for buildroot in . .. ../..
+do
+ d=$buildroot/$base/$device
+ if [ -d "$d" ]
+ then
+ artifact_dir=$d
+ break
+ fi
+done
+
+# If we can't find it, we can't test.
+test -z "$artifact_dir" && exit 77 # skip
+
+input='.fp 5 ../HONEYPOT
+.ft 5
+word
+.fp 5 HONEYPOT ../HONEYPOT
+.ft HONEYPOT
+.br
+my word is able
+.pl \n[nl]u'
+
+output=$(printf "%s" "$input" | "$groff" -b -ww -F "$artifact_dir" \
+ -Tascii)
+echo "$output" | grep -Fx word
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/roff/groff/tests/handle_special_input_code_points.sh b/src/roff/groff/tests/handle_special_input_code_points.sh
new file mode 100755
index 0000000..c996150
--- /dev/null
+++ b/src/roff/groff/tests/handle_special_input_code_points.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #58962.
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+input='.if " "\~" .tm input no-break space matches \\~
+.if "­"\%" .tm input soft hyphen matches \\%'
+
+fail=
+
+wail () {
+ echo "...FAILED"
+ fail=yes
+}
+
+output=$(printf "%s\n" "$input" | "$groff" -Z 2>&1)
+echo "$output"
+
+printf "checking that input no-break space is mapped to \\~\n"
+echo "$output" | grep -qx 'input no-break space matches \\~' || wail
+
+printf "checking that input soft hyphen is mapped to \\%%\n"
+echo "$output" | grep -qx 'input soft hyphen matches \\%' || wail
+
+test -z "$fail"
diff --git a/src/roff/groff/tests/html_works_with_grn_and_eqn.sh b/src/roff/groff/tests/html_works_with_grn_and_eqn.sh
new file mode 100755
index 0000000..e070944
--- /dev/null
+++ b/src/roff/groff/tests/html_works_with_grn_and_eqn.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Keep this list of programs in sync with GROFF_CHECK_GROHTML_PROGRAMS
+# in m4/groff.m4.
+for cmd in pnmcrop pnmcut pnmtopng pnmtops psselect
+do
+ if ! command -v $cmd >/dev/null
+ then
+ echo "cannot locate '$cmd' command; skipping test" >&2
+ exit 77 # skip
+ fi
+done
+
+# Commit c71b4ef4aa provoked an infinite loop in post-grohtml with these
+# preprocessors.
+
+input='.EQ
+gsize 12
+delim $$
+.EN
+.pp
+.pp
+The faster clocks are $ PN $'
+
+output=$("$groff" -b -ww -Thtml -eg -me "$input")
+test -n "$output"
diff --git a/src/roff/groff/tests/initialization_is_quiet.sh b/src/roff/groff/tests/initialization_is_quiet.sh
new file mode 100755
index 0000000..38ac81f
--- /dev/null
+++ b/src/roff/groff/tests/initialization_is_quiet.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (C) 2021-2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #60874.
+#
+# groff should start up in any supported locale, in compatibility mode
+# or not, without producing diagnostics.
+
+# Keep preconv from being run.
+#
+# The "unset" in Solaris /usr/xpg4/bin/sh can actually fail.
+if ! unset GROFF_ENCODING
+then
+ echo "unable to clear environment; skipping" >&2
+ exit 77
+fi
+
+for compat in "" " -C"
+do
+ for locale in cs de en fr it ja sv zh
+ do
+ echo testing \"-m $locale$compat\" >&2
+ output=$("$groff" -ww -m $locale$compat -a </dev/null 2>/dev/null)
+ error=$("$groff" -ww -m $locale$compat -z </dev/null 2>&1)
+ test -n "$error" && echo "$error"
+ test -n "$output" && echo "$output"
+ test -n "$error$output" && wail
+ done
+done
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=4 tabstop=4 textwidth=72:
diff --git a/src/roff/groff/tests/localization_works.sh b/src/roff/groff/tests/localization_works.sh
new file mode 100755
index 0000000..0585259
--- /dev/null
+++ b/src/roff/groff/tests/localization_works.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='\*[locale]'
+
+echo "testing default localization (English)" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii)
+echo "$OUTPUT" | grep -qx english
+
+echo "testing Czech localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m cs)
+echo "$OUTPUT" | grep -qx czech
+
+echo "testing German localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m de)
+echo "$OUTPUT" | grep -qx german
+
+echo "testing English localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m en)
+echo "$OUTPUT" | grep -qx english
+
+echo "testing French localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m fr)
+echo "$OUTPUT" | grep -qx french
+
+echo "testing Italian localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m it)
+echo "$OUTPUT" | grep -qx italian
+
+echo "testing Japanese localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m ja)
+echo "$OUTPUT" | grep -qx japanese
+
+echo "testing Swedish localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m sv)
+echo "$OUTPUT" | grep -qx swedish
+
+echo "testing Chinese localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m zh)
+echo "$OUTPUT" | grep -qx chinese
diff --git a/src/roff/groff/tests/msoquiet_works.sh b/src/roff/groff/tests/msoquiet_works.sh
new file mode 100755
index 0000000..80c085a
--- /dev/null
+++ b/src/roff/groff/tests/msoquiet_works.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+DOC='.msoquiet nonexistent'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii 2>&1)
+echo "$OUTPUT"
+
+echo "testing that .msoquiet of nonexistent file produces no warning" \
+ >&2
+test -z "$OUTPUT"
diff --git a/src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh b/src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh
new file mode 100755
index 0000000..b2c17bc
--- /dev/null
+++ b/src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# This test fails with some versions of GNU Bash, such as 3.2; the here
+# document nested within a command substitution confuses it.
+#
+# https://lists.gnu.org/archive/html/bug-bash/2017-02/msg00024.html
+
+expected="' = '"
+actual=$(printf '.pl 1v\n\\[oq] = '"'"'\n' | "$groff" -Tlatin1)
+test "$actual" = "$expected"
diff --git a/src/roff/groff/tests/output_driver_C_and_G_options_work.sh b/src/roff/groff/tests/output_driver_C_and_G_options_work.sh
new file mode 100755
index 0000000..2bb1cc3
--- /dev/null
+++ b/src/roff/groff/tests/output_driver_C_and_G_options_work.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Feed groff empty input documents and verify that expected comments
+# emerge from the output drivers.
+
+# Expect Creator: and CreationDate: comments.
+echo "testing presence of Creator: comment in HTML output" >&2
+echo | "$groff" -Thtml | grep -Fq '<!-- Creator:'
+
+echo "testing presence of CreationDate: comment in HTML output" >&2
+echo | "$groff" -Thtml | grep -Fq '<!-- CreationDate:'
+
+# Make sure the options are recognized so we can distinguish a match
+# failure. We can't use -Z or -z because they keep the output driver
+# from running at all.
+for OPT in -C -G
+do
+ if ! echo | "$groff" -Thtml -P$OPT > /dev/null
+ then
+ echo "option $OPT not recognized!" >&2
+ exit 2
+ fi
+done
+
+# Now shut them off.
+echo "testing absence of Creator: comment in HTML output" >&2
+! echo | "$groff" -Thtml -P-G | grep -Fq '<!-- Creator:'
+
+echo "testing absence of CreationDate: comment in HTML output" >&2
+! echo | "$groff" -Thtml -P-C | grep -Fq '<!-- CreationDate:'
diff --git a/src/roff/groff/tests/recognize_end_of_sentence.sh b/src/roff/groff/tests/recognize_end_of_sentence.sh
new file mode 100755
index 0000000..aa1e4dc
--- /dev/null
+++ b/src/roff/groff/tests/recognize_end_of_sentence.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Verify that the characters trailing the period are all transparent for
+# purposes of end-of-sentence recognition. We use UTF-8 so we won't get
+# warnings about \[dg] and \[dd] missing from other encodings.
+#
+# Also confirm that we get _two_ spaces after the end of a sentence.
+
+"$groff" -Tutf8 <<EOF | grep -qE 'Eat\.[^ ]+ Drink\.'
+.pl 1v
+Eat."')]*\[dg]\[dd]\[rq]\[cq]
+Drink.
+EOF
diff --git a/src/roff/groff/tests/regression_savannah_56555.sh b/src/roff/groff/tests/regression_savannah_56555.sh
new file mode 100755
index 0000000..3d13f16
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_56555.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Check for segfault if we try to write a glyph before setting up.
+# Savannah #56555.
+"$groff" >/dev/null <<EOF
+\!ta
+EOF
diff --git a/src/roff/groff/tests/regression_savannah_58153.sh b/src/roff/groff/tests/regression_savannah_58153.sh
new file mode 100755
index 0000000..25771fd
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_58153.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (C) 2020-2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Ensure that we get backtrace output across file and pipe boundaries.
+# Savannah #58153.
+OUT=$("$groff" -b -ww -U 2>&1 >/dev/null <<EOF
+.ec @
+.pso printf '@s[-20]'
+EOF
+)
+
+set -e
+
+printf "%s\n" "$OUT" | grep -qw 'backtrace: pipe'
+printf "%s\n" "$OUT" | grep -qw 'backtrace: file'
diff --git a/src/roff/groff/tests/regression_savannah_58162.sh b/src/roff/groff/tests/regression_savannah_58162.sh
new file mode 100755
index 0000000..d744e99
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_58162.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Compatibility mode should not get shut off by macro file inclusion.
+printf '.ds FOO FAIL\n\\*[FOO]' | "$groff" -C -Tutf8 | grep -Fx 'FOO]'
diff --git a/src/roff/groff/tests/regression_savannah_58337.sh b/src/roff/groff/tests/regression_savannah_58337.sh
new file mode 100755
index 0000000..7928651
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_58337.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# groff should ignore negative inter-word and inter-sentence space
+# sizes. And certainly not fail an assertion. Savannah #58337.
+"$groff" -Tascii <<EOF | grep -Fqx 'A B. C.'
+.pl 1v
+.ss -1 -1
+A B.
+C.
+EOF
diff --git a/src/roff/groff/tests/regression_savannah_59202.sh b/src/roff/groff/tests/regression_savannah_59202.sh
new file mode 100755
index 0000000..6d8bdc1
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_59202.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# troff should not segfault when its standard output is closed.
+# Savannah #59202.
+
+# If a core file already exists, it should be dealt with; skip test.
+test -e core && exit 77
+echo | "$groff" >&-
+! test -e core
diff --git a/src/roff/groff/tests/smoke-test_html_device.sh b/src/roff/groff/tests/smoke-test_html_device.sh
new file mode 100755
index 0000000..877fc28
--- /dev/null
+++ b/src/roff/groff/tests/smoke-test_html_device.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Copyright (C) 2020, 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Keep this list of programs in sync with GROFF_CHECK_GROHTML_PROGRAMS
+# in m4/groff.m4.
+for cmd in pnmcrop pnmcut pnmtopng pnmtops psselect
+do
+ if ! command -v $cmd >/dev/null
+ then
+ echo "cannot locate '$cmd' command; skipping test" >&2
+ exit 77 # skip
+ fi
+done
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=yes
+}
+
+cleanup () {
+ rm -f grohtml-[0-9]*-[12].png
+ trap - HUP INT QUIT TERM
+}
+
+trap 'trap "" HUP INT QUIT TERM; cleanup; kill -s INT $$' \
+ HUP INT QUIT TERM
+
+input='.TS
+L.
+foobar
+.TE'
+
+# Inline images are named grohtml-$$-<image sequence number>.png.
+
+echo "checking production of inline image for tbl(1) table" >&2
+output=$(echo "$input" | "$groff" -t -Thtml)
+echo "$output" | grep -q '<img src="grohtml-[0-9]\+-1.png"' || wail
+
+input='.EQ
+x sup 2 + y sup 2 = z sup 2
+.EN'
+
+echo "checking production of inline image for eqn(1) equation" >&2
+output=$(echo "$input" | "$groff" -e -Thtml)
+echo "$output" | grep -q '<img src="grohtml-[0-9]\+-2.png"' || wail
+
+cleanup
+
+# We can't run remaining tests if the environment doesn't support UTF-8.
+test "$(locale charmap)" = UTF-8 || exit 77 # skip
+
+# Check two forms of character transformation.
+#
+# dash's built-in printf doesn't support \x or \u escapes, so likely
+# other shells don't either, and expecting one that does to be in the
+# $PATH seems optimistic. So use UTF-8 octal bytes directly.
+echo "checking -k -Thtml" >&2
+printf '\303\241' | "$groff" -k -Thtml | grep -qx '<p>&aacute;</p>' \
+ || wail
+
+# We test compatibility-mode HTML output somewhat differently since
+# preconv only emits groffish \[uXXXX] escapes for non-ASCII codepoints.
+echo "checking -C -k -Thtml" >&2
+printf "\('a" | "$groff" -C -k -Thtml | grep -qx '<p>&aacute;</p>' \
+ || wail
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh b/src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh
new file mode 100755
index 0000000..96be055
--- /dev/null
+++ b/src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ fail=yes
+}
+
+# Regression-test Savannah #63011.
+#
+# A handful of escape sequences bizarrely accept newlines as argument
+# delimiters. Don't throw diagnostics if they are used.
+
+input="\A
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'A' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'A' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww)
+test "$output" = "1 D" || wail
+
+input=".sp
+\b
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'b' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'b' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww)
+echo "$output" | grep -Fqx "B D" || wail
+
+input="\o
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'o' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'o' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww \
+ | LC_ALL=C od -t c)
+# 7 spaces between C and D.
+printf "%s\n" "$output" \
+ | grep -Eqx '0000000 +A +\\b +B +\\b +C D +\\n *' || wail
+
+input="\w
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'w' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'w' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww)
+test "$output" = "72 D" || wail
+
+input="\X
+tty: link http://example.com
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'X' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'X' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -P -c)
+test "$output" = ' D' || wail
+
+input="\Z
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'Z' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+# This looks really weird but is consistent. A newline used as a
+# delimiter still gets interpreted as an input line ending. What we see
+# here is: 'ABC' is formatted, the drawing position is reset to the
+# beginning of the line, a word space (from filling, overstriking 'A')
+# goes on the output, followed by 'D', so it appears as 'ADC'.
+#
+# `printf '\\Z@ABC@\nD\n'` produces the same output.
+echo "checking correct handling of newline delimiter to 'Z' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww \
+ | LC_ALL=C od -t c)
+printf "%s\n" "$output" | grep -Eqx '0000000 +A +B +\\b +D +C +\\n *' \
+ || wail
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/soquiet_works.sh b/src/roff/groff/tests/soquiet_works.sh
new file mode 100755
index 0000000..e2ea286
--- /dev/null
+++ b/src/roff/groff/tests/soquiet_works.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+DOC='.soquiet nonexistent'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii 2>&1)
+echo "$OUTPUT"
+
+echo "testing that .soquiet of nonexistent file produces no error" >&2
+test -z "$OUTPUT"
diff --git a/src/roff/groff/tests/string_case_xform_errors.sh b/src/roff/groff/tests/string_case_xform_errors.sh
new file mode 100755
index 0000000..a16d763
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_errors.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+expected="troff:<standard input>:1: error: cannot apply string case transformation to a request ('br')"
+
+actual=$("$groff" -Tutf8 2>&1 <<EOF
+.stringdown br
+EOF
+)
+
+echo "$actual" | grep -qx "$expected"
diff --git a/src/roff/groff/tests/string_case_xform_requests.sh b/src/roff/groff/tests/string_case_xform_requests.sh
new file mode 100755
index 0000000..a9b8fa6
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_requests.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+input=".pl 1v
+.ds resume R\\['e]sum\\['e]\\\"
+\\*[resume]
+.stringdown resume
+\\*[resume]
+.stringup resume
+\\*[resume]"
+expected="Résumé résumé RÉSUMÉ"
+actual=$(echo "$input" | "$groff" -Tutf8)
+test "$actual" = "$expected"
diff --git a/src/roff/groff/tests/string_case_xform_unicode_escape.sh b/src/roff/groff/tests/string_case_xform_unicode_escape.sh
new file mode 100755
index 0000000..529e0c3
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_unicode_escape.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# The following is what we expect in the future when we support Unicode
+# case transformations.
+expected="attaché ATTACHÉ"
+
+# For now, we expect problems like this:
+# troff: backtrace: '<standard input>':4: string 'attache'
+# troff: backtrace: file '<standard input>':5
+# troff: <standard input>:5: warning: can't find special character
+# 'U0065_0301'
+
+actual=$("$groff" -Tutf8 2>&1 <<EOF
+.pl 1v
+.ds attache attach\\[u0065_0301]\\\"
+\\*[attache]
+.stringup attache
+\\*[attache]
+EOF
+)
+
+echo "$actual" | grep -Fqx "$expected"
diff --git a/src/roff/groff/tests/substring_works.sh b/src/roff/groff/tests/substring_works.sh
new file mode 100755
index 0000000..a57d579
--- /dev/null
+++ b/src/roff/groff/tests/substring_works.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# This test is based on a contribution by Jim Avera; see
+# <https://savannah.gnu.org/bugs/?60802>.
+
+"$groff" -z -ww <<'EOF'
+.
+.nr debug 1
+.de debugmsg
+. if \\n[debug] .tm \\$*
+..
+.de errormsg
+. tm ERROR: \\$*
+. nr nerrors (\\n[nerrors]+1)
+..
+.nr nerrors 0
+.
+.\" .substring xx n1 [n2]
+.\" Replace contents of string named xx with the substring bounded by
+.\" zero-based indices indices n1 and n2. Negative indices count
+.\" backwards from the end of the string. If omitted, n2 is `-1`.
+.\"
+.\" If n1 > n2, n1 and n2 are swapped. If n1 equals or exceeds the
+.\" string length, it is set to `-1`.
+.\"
+.\" NOT YET IMPLEMENTED:
+.\" If n1 > n2, or if n1 equals or exceeds the string length, then
+.\" any contents of xx are replaced with the empty string.
+.\"
+.de jtest \" input_string n1 n2 expected_result
+. if \\n[.$]<4 .ab jtest: expected at least 4 arguments, got \\n[.$]
+. ds t*input "\\$1
+. ds t*n1 \\$2
+. ds t*n2 \\$3
+. ds t*expected "\\$4
+. shift 4
+. ds t*comment "\\$*
+.
+. ds t*str "\\*[t*input]
+. ie '\\*[t*n2]'' .substring t*str \\*[t*n1]
+. el .substring t*str \\*[t*n1] \\*[t*n2]
+. ie '\\*[t*str]'\\*[t*expected]' \{\
+. debugmsg .substring '\\*[t*input]' \\*[t*n1] \\*[t*n2] -> \
+'\\*[t*str]' (OK) \\*[t*comment]
+. \}
+. el \{\
+. errormsg .substring '\\*[t*input]' \\*[t*n1] \\*[t*n2] yielded \
+'\\*[t*str]', EXPECTED '\\*[t*expected]' \\*[t*comment]
+. \}
+..
+.
+.debugmsg --- Pick a single character from non-empty ---
+.jtest "abc" 0 0 "a"
+.jtest "abc" 1 1 "b"
+.jtest "abc" 2 2 "c"
+.
+.debugmsg --- Pick multiple characters from non-empty ---
+.jtest "abcd" 0 1 "ab"
+.jtest "abcd" 1 1 "b"
+.jtest "abcd" 0 3 "abcd"
+.jtest "abcd" 0 -1 "abcd"
+.jtest "abcd" 0 "" "abcd"
+.jtest "abcd" 1 3 "bcd"
+.jtest "abcd" 2 3 "cd"
+.jtest "abcd" 3 3 "d"
+.
+.debugmsg --- Omit n2 with non-empty input and non-empty result ---
+.jtest "abc" 0 "" "abc"
+.jtest "abc" 1 "" "bc"
+.jtest "abc" 2 "" "c"
+.jtest "a" 0 "" "a"
+.
+.\"debugmsg --- Specify empty substring with n2==(n1-1) ---
+.\"jtest "abcd" 3 2 ""
+.\"jtest "abcd" 2 1 ""
+.\"jtest "abcd" 1 0 ""
+.debugmsg --- Pick multiple characters from non-empty using inverted \
+range ---
+.jtest "abcd" 3 2 "cd"
+.jtest "abcd" 2 1 "bc"
+.jtest "abcd" 1 0 "ab"
+.
+.\"debugmsg --- Specify empty substring with n1==length and n2 omitted ---
+.\"jtest "abcd" 4 "" ""
+.\"jtest "abc" 3 "" ""
+.\"jtest "ab" 2 "" ""
+.\"jtest "a" 1 "" ""
+.\"jtest "" 0 "" ""
+.debugmsg --- Pick single character using out-of-bounds start index \
+(unless string empty) ---
+.jtest "abcd" 4 "" "d"
+.jtest "abc" 3 "" "c"
+.jtest "ab" 2 "" "b"
+.jtest "a" 1 "" "a"
+.jtest "" 0 "" ""
+.jtest "" 0 -1 ""
+.jtest "" 0 -2 ""
+.
+.if \n[nerrors] .ab Aborting, got \n[nerrors] errors.
+EOF
diff --git a/src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh b/src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh
new file mode 100755
index 0000000..0d1cacd
--- /dev/null
+++ b/src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+export GROFF_TYPESETTER=
+
+# The vertical space is so that the 36-point 'A' won't be truncated by
+# the top of the page. That could be confusing and misleading to anyone
+# who ever has to troubleshoot this test case.
+DOC=".vs 10v
+\s36A"
+
+set -e
+
+# Verify that the idiosyncratic behavior of \sN is supported in
+# compatibility mode...
+echo "testing \s36A in compatibility mode (36-point 'A')" >&2
+echo "$DOC" | "$groff" -C -Z | grep -qx 's36000'
+
+# ...and not in regular mode.
+echo "testing \s36A in non-compatibility mode (3-point '6A')" >&2
+echo "$DOC" | "$groff" -Z | grep -qx 's3000'
+
+# Check that we get a diagnostic when relying on the ambiguous form.
+echo "testing for diagnostic on \s36 in compatibility mode" >&2
+echo "$DOC" | "$groff" -C -Z 2>&1 >/dev/null \
+ | grep -q 'ambiguous type size in escape sequence'
diff --git a/src/roff/nroff/nroff.1.man b/src/roff/nroff/nroff.1.man
new file mode 100644
index 0000000..cdefee1
--- /dev/null
+++ b/src/roff/nroff/nroff.1.man
@@ -0,0 +1,358 @@
+.TH @g@nroff @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@nroff \- format documents with
+.I groff
+for TTY (terminal) devices
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_nroff_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@nroff
+.RB [ \-bcCEhikpRStUVz ]
+.RB [ \-d\~\c
+.IR ctext ]
+.RB [ \-d\~\c
+.IB string =\c
+.IR text ]
+.RB [ \-K\~\c
+.IR fallback-encoding ]
+.RB [ \-m\~\c
+.IR macro-package ]
+.RB [ \-M\~\c
+.IR macro-directory ]
+.RB [ \-n\~\c
+.IR page-number ]
+.RB [ \-o\~\c
+.IR page-list ]
+.RB [ \-P\~\c
+.IR postprocessor-argument ]
+.RB [ \-r\~\c
+.IR cnumeric-expression ]
+.RB [ \-r\~\c
+.IB register =\c
+.IR numeric-expression ]
+.RB [ \-T\~\c
+.IR output-device ]
+.RB [ \-w\~\c
+.IR warning-category ]
+.RB [ \-W\~\c
+.IR warning-category ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@nroff
+.B \-\-help
+.YS
+.
+.
+.SY @g@nroff
+.B \-v
+.
+.SY @g@nroff
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I @g@nroff
+formats documents written in the
+.MR groff @MAN7EXT@
+language for typewriter-like devices such as terminal emulators.
+.
+GNU
+.I nroff \" GNU
+emulates the AT&T
+.I nroff \" AT&T
+command using
+.MR groff @MAN1EXT@ .
+.
+.I @g@nroff
+generates output via
+.MR grotty @MAN1EXT@ ,
+.IR groff 's
+terminal output driver,
+which needs to know the character encoding scheme used by the device.
+.
+Consequently,
+acceptable arguments to the
+.B \-T
+option are
+.BR ascii ,
+.BR latin1 ,
+.BR utf8 ,
+and
+.BR cp1047 ;
+any others are ignored.
+.
+If neither the
+.I \%GROFF_TYPESETTER
+environment variable nor the
+.B \-T
+command-line option
+(which overrides the environment variable)
+specifies a (valid) device,
+.I @g@nroff
+consults the locale to select an appropriate output device.
+.
+It first tries the
+.MR locale 1
+program,
+then checks several locale-related environment variables;
+see section \[lq]Environment\[rq] below.
+.
+If all of the foregoing fail,
+.B \-Tascii
+is implied.
+.
+.
+.P
+The
+.BR \-b ,
+.BR \-c ,
+.BR \-C ,
+.BR \-d ,
+.BR \-E ,
+.BR \-i ,
+.BR \-m ,
+.BR \-M ,
+.BR \-n ,
+.BR \-o ,
+.BR \-r ,
+.BR \-U ,
+.BR \-w ,
+.BR \-W ,
+and
+.B \-z
+options have the effects described in
+.MR @g@troff @MAN1EXT@ .
+.
+.B \-c
+and
+.B \-h
+imply
+.RB \[lq] \-P\-c \[rq]
+and
+.RB \[lq] \-P\-h \[rq],
+respectively;
+.B \-c
+is also interpreted directly by
+.IR @g@troff .
+.
+In addition,
+this implementation ignores the AT&T
+.I nroff \" AT&T
+options
+.BR \-e ,
+.BR \-q ,
+and
+.B \-s
+(which are not implemented in
+.IR groff ).
+.
+The options
+.BR \-k ,
+.BR \-K ,
+.BR \-p ,
+.BR \-P ,
+.BR \-R ,
+.BR \-t ,
+and
+.B \-S
+are documented in
+.MR groff @MAN1EXT@ .
+.
+.B \-V
+causes
+.I @g@nroff
+to display the constructed
+.I groff
+command on the standard output stream,
+but does not execute it.
+.
+.B \-v
+and
+.B \-\-version
+show version information about
+.I @g@nroff
+and the programs it runs,
+while
+.B \-\-help
+displays a usage message;
+all exit afterward.
+.
+.
+.\" ====================================================================
+.SH "Exit status"
+.\" ====================================================================
+.
+.I @g@nroff
+exits with error
+.RB status\~ 2
+if there was a problem parsing its arguments,
+with
+.RB status\~ 0
+if any of the options
+.BR \-V ,
+.BR \-v ,
+.BR \-\-version ,
+or
+.B \-\-help
+were specified,
+and with the status of
+.I groff
+otherwise.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+Normally,
+the path separator in environment variables ending with
+.I PATH
+is the colon;
+this may vary depending on the operating system.
+.
+For example,
+Windows uses a semicolon instead.
+.
+.
+.TP
+.I GROFF_BIN_PATH
+is a colon-separated list of directories in which to search for the
+.I groff
+executable before searching in
+.IR PATH .
+.
+If unset,
+.I @BINDIR@
+is used.
+.
+.
+.TP
+.I GROFF_TYPESETTER
+specifies the default output device for
+.IR groff .
+.
+.
+.TP
+.I LC_ALL
+.TQ
+.I LC_CTYPE
+.TQ
+.I LANG
+.TQ
+.I LESSCHARSET
+are pattern-matched in this order for contents matching standard
+character encodings supported by
+.I groff
+in the event no
+.B \-T
+option is given and
+.I \%GROFF_TYPESETTER
+is unset,
+or the values specified are invalid.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @MACRODIR@/\:\%tty\-char\:.tmac
+defines fallback definitions of
+.I roff
+special characters.
+.
+These definitions more poorly optically approximate typeset output
+than those of
+.I tty.tmac
+in favor of communicating semantic information.
+.
+.I nroff
+loads it automatically.
+.
+.
+.\" ====================================================================
+.SH Notes
+.\" ====================================================================
+.
+Pager programs like
+.MR more 1
+and
+.MR less 1
+may require command-line options to correctly handle some output
+sequences;
+see
+.MR grotty @MAN1EXT@ .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR grotty @MAN1EXT@ ,
+.MR locale 1 ,
+.MR roff @MAN7EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_nroff_1_man_C]
+.do rr *groff_nroff_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/roff/nroff/nroff.am b/src/roff/nroff/nroff.am
new file mode 100644
index 0000000..82738d9
--- /dev/null
+++ b/src/roff/nroff/nroff.am
@@ -0,0 +1,44 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_SCRIPTS += nroff
+nroff_srcdir = $(top_srcdir)/src/roff/nroff
+PREFIXMAN1 += src/roff/nroff/nroff.1
+EXTRA_DIST += \
+ src/roff/nroff/nroff.1.man \
+ src/roff/nroff/nroff.sh
+
+nroff_TESTS = \
+ src/roff/nroff/tests/verbose_option_works.sh
+TESTS += $(nroff_TESTS)
+EXTRA_DIST += $(nroff_TESTS)
+
+nroff: $(nroff_srcdir)/nroff.sh $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)rm -f $@ \
+ && sed -f $(SH_DEPS_SED_SCRIPT) \
+ -e $(SH_SCRIPT_SED_CMD) \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ $(nroff_srcdir)/nroff.sh \
+ >$@ \
+ && chmod +x $@
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/roff/nroff/nroff.sh b/src/roff/nroff/nroff.sh
new file mode 100644
index 0000000..77f5c06
--- /dev/null
+++ b/src/roff/nroff/nroff.sh
@@ -0,0 +1,204 @@
+#! /bin/sh
+# Emulate nroff with groff.
+#
+# Copyright (C) 1992-2021 Free Software Foundation, Inc.
+#
+# Written by James Clark, Werner Lemberg, and G. Branden Robinson.
+#
+# This file is part of 'groff'.
+#
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License (GPL) as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prog="$0"
+
+T=
+Topt=
+opts=
+dry_run=
+is_option_argument_pending=
+
+usage="usage: $prog [-bcCEhikpRStUVz] [-d ctext] [-d string=text] \
+[-K fallback-encoding] [-m macro-package] [-M macro-directory] \
+[-n page-number] [-o page-list] [-P postprocessor-argument] \
+[-r cnumeric-expression] [-r register=numeric-expression] \
+[-T output-device] [-w warning-category] [-W warning-category] \
+[file ...]
+usage: $prog {-v | --version}
+usage: $prog --help"
+
+for arg
+do
+ if [ -n "$is_option_argument_pending" ]
+ then
+ is_option_argument_pending=
+ opts="$opts $arg"
+ shift
+ continue
+ fi
+
+ case $arg in
+ -c)
+ opts="$opts $arg -P-c" ;;
+ -h)
+ opts="$opts -P-h" ;;
+ -[eq] | -s*)
+ # ignore these options
+ ;;
+ -[dKmMnoPrTwW])
+ is_option_argument_pending=yes
+ opts="$opts $arg" ;;
+ -[bCEikpRStUz] | -[dKMmrnoPwW]*)
+ opts="$opts $arg" ;;
+ -T*)
+ Topt=$arg ;;
+ -u*)
+ # -u is for Solaris compatibility and not otherwise documented.
+ #
+ # Solaris 2.2 through at least Solaris 9 'man' invokes
+ # 'nroff -u0 ... | col -x'. Ignore the -u0, since 'less' and
+ # 'more' can use the emboldening info. But disable SGR, since
+ # Solaris 'col' mishandles it.
+ opts="$opts -P-c" ;;
+ -V)
+ dry_run=yes ;;
+ -v | --version)
+ echo "GNU nroff (groff) version @VERSION@"
+ opts="$opts $arg" ;;
+ --help)
+ echo "$usage"
+ exit 0 ;;
+ --)
+ shift
+ break ;;
+ -)
+ break ;;
+ -*)
+ echo "$prog: usage error: invalid option '$arg'" >&2
+ echo "$usage" >&2
+ exit 2 ;;
+ *)
+ break ;;
+ esac
+ shift
+done
+
+if [ -n "$is_option_argument_pending" ]
+then
+ echo "$prog: usage error: option '$arg' requires an argument" >&2
+ exit 2
+fi
+
+# Determine the -T option. Was a valid one specified?
+case "$Topt" in
+ -Tascii | -Tlatin1 | -Tutf8 | -Tcp1047)
+ T=$Topt ;;
+esac
+
+# -T option absent or invalid; try environment.
+if [ -z "$T" ]
+then
+ Tenv=-T$GROFF_TYPESETTER
+ case "$Tenv" in
+ -Tascii | -Tlatin1 | -Tutf8 | -Tcp1047)
+ T=$Tenv ;;
+ esac
+fi
+
+# Finally, infer a -T option from the locale. Try 'locale charmap'
+# first because it is the most reliable, then look at environment
+# variables.
+if [ -z "$T" ]
+then
+ # The separate `exec` is to work around a ~2004 bug in Cygwin sh.exe.
+ case "`exec 2>/dev/null ; locale charmap`" in
+ UTF-8)
+ Tloc=utf8 ;;
+ ISO-8859-1 | ISO-8859-15)
+ Tloc=latin1 ;;
+ IBM-1047)
+ Tloc=cp1047 ;;
+ *)
+ # Some old shells don't support ${FOO:-bar} expansion syntax. We
+ # should switch to it when it is safe to abandon support for them.
+ case "${LC_ALL-${LC_CTYPE-${LANG}}}" in
+ *.UTF-8)
+ Tloc=utf8 ;;
+ iso_8859_1 | *.ISO-8859-1 | *.ISO8859-1 | \
+ iso_8859_15 | *.ISO-8859-15 | *.ISO8859-15)
+ Tloc=latin1 ;;
+ *.IBM-1047)
+ Tloc=cp1047 ;;
+ *)
+ case "$LESSCHARSET" in
+ utf-8)
+ Tloc=utf8 ;;
+ latin1)
+ Tloc=latin1 ;;
+ cp1047)
+ Tloc=cp1047 ;;
+ *)
+ Tloc=ascii ;;
+ esac ;;
+ esac ;;
+ esac
+ T=-T$Tloc
+fi
+
+# Load nroff-style character definitions too.
+opts="-mtty-char$opts"
+
+# Set up the 'GROFF_BIN_PATH' variable to be exported in the current
+# 'GROFF_RUNTIME' environment.
+@GROFF_BIN_PATH_SETUP@
+export GROFF_BIN_PATH
+
+# Let our test harness redirect us. See LC_ALL comment above.
+groff=${GROFF_TEST_GROFF-groff}
+
+# Note 1: It would be nice to apply the DRY ("Don't Repeat Yourself")
+# principle here and store the entire command string to be executed into
+# a variable, and then either display it or execute it. For example:
+#
+# cmd="PATH=... groff ... $@"
+# ...
+# printf "%s\n" "$cmd"
+# ...
+# eval $cmd
+#
+# Unfortunately, the shell is a nightmarish hellscape of quoting issues.
+# Naïve attempts to solve the problem fail when arguments to nroff
+# contain embedded whitespace or shell metacharacters. The solution
+# below works with those, but there is insufficient quoting in -V (dry
+# run) mode, such that you can't copy-and-paste the output of 'nroff -V'
+# if you pass it a filename like foo"bar (with the embedded quotation
+# mark) and expect it to run without further quoting.
+#
+# If POSIX adopts Bash's ${var@Q} or an equivalent, this issue can be
+# revisited.
+#
+# Note 2: The construction '${1+"@$"}' preserves the absence of
+# arguments in old shells; see "Shell Substitutions" in the GNU Autoconf
+# manual. We don't want 'nroff' to become 'groff ... ""' if $# equals
+# zero.
+if [ -n "$dry_run" ]
+then
+ echo PATH="$GROFF_RUNTIME$PATH" $groff $T $opts ${1+"$@"}
+else
+ PATH="$GROFF_RUNTIME$PATH" $groff $T $opts ${1+"$@"}
+fi
+
+# Local Variables:
+# fill-column: 72
+# End:
+# vim: set autoindent expandtab shiftwidth=2 softtabstop=2 textwidth=72:
diff --git a/src/roff/nroff/tests/verbose_option_works.sh b/src/roff/nroff/tests/verbose_option_works.sh
new file mode 100755
index 0000000..85ca8f0
--- /dev/null
+++ b/src/roff/nroff/tests/verbose_option_works.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Ensure a predictable character encoding.
+export LC_ALL=C
+export LESSCHARSET=
+export GROFF_TYPESETTER=
+
+set -e
+
+export GROFF_TEST_GROFF=${abs_top_builddir:-.}/test-groff
+
+# The $PATH used by an installed nroff at runtime does not match what
+# we're trying to test, which should be using the groff and runtime
+# support from the build tree. Therefore the $PATH that nroff -V
+# reports will _always_ be wrong for test purposes. Skip over it.
+#
+# If the build environment has a directory in the $PATH matching
+# "test-groff " (with the trailing space), failure may result if sed
+# doesn't match greedily. POSIX says it should.
+sedexpr='s/^PATH=.*test-groff /test-groff /'
+PATH=${abs_top_builddir:-.}:$PATH
+
+nroff_ver=$(nroff -v | awk 'NR == 1 {print $NF}')
+groff_ver=$(nroff -v | awk 'NR == 2 {print $NF}')
+
+echo nroff: $nroff_ver >&2
+echo groff: $groff_ver >&2
+test "$nroff_ver" = "$groff_ver"
+
+echo "testing 'nroff -V'" >&2
+nroff -V | sed "$sedexpr" | grep -x "test-groff -Tascii -mtty-char"
+
+echo "testing 'nroff -V 1'" >&2
+nroff -V 1 | sed "$sedexpr" | grep -x "test-groff -Tascii -mtty-char 1"
+
+echo "testing 'nroff -V \"1a 1b\"'" >&2
+nroff -V \"1a 1b\" | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char \"1a 1b\""
+
+echo "testing 'nroff -V \"1a 1b\" 2'" >&2
+nroff -V \"1a 1b\" 2 | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char \"1a 1b\" 2"
+
+echo "testing 'nroff -V 1a\\\"1b 2'" >&2
+nroff -V 1a\"1b 2 | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char 1a\"1b 2"
+
+echo "testing 'nroff -V -d FOO=BAR 1'" >&2
+nroff -V -d FOO=BAR 1 | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char -d FOO=BAR 1"
diff --git a/src/roff/troff/TODO b/src/roff/troff/TODO
new file mode 100644
index 0000000..533167a
--- /dev/null
+++ b/src/roff/troff/TODO
@@ -0,0 +1,125 @@
+A line prefix request to make e.g. French quotation possible:
+
+ He said: >> blablablabla
+ >> blablabla blabla bla
+ >> blabla blabla bla bla
+ >> bla bla bla blablabla
+ >> blabla. <<
+
+Give a more helpful error message when the indent is set to a value
+greater than the line-length.
+
+Tracing. This is a pain to implement because requests are responsible
+for reading their own arguments.
+
+Possibly implement -s option (stop every N pages). This functionality
+would be more appropriate in a postprocessor.
+
+Line breaking should be smarter. In particular, it should be possible
+to shrink spaces. Also avoid having a line that's been shrunk a lot
+next to a line that's been stretched a lot. The difficulty is to
+design a mechanism that allows the user complete control over the
+decision of where to break the line.
+
+Provide a mechanism to control the shape of the rag in non-justified
+text.
+
+Add a discretionary break escape sequence. \='...'...'...' like TeX.
+
+Think about kerning between characters and spaces. (Need to implement
+get_breakpoints and split methods for kern_pair_node class.)
+
+In troff, if .L > 1 when a diversion is reread in no-fill mode, then
+extra line-spacing is added on. Groff at the moment treats line-spacing
+like vertical spacing and doesn't do this.
+
+Suppose \(ch comes from a special font S, and that the current font is
+R. Suppose that R contains a hyphen character and that S does not.
+Suppose that the current font is R. Suppose that \(ch is in a word
+and has a non-zero hyphen-type. Then we ought to be able to hyphenate,
+but we won't be able to because we will look for the hyphen only in
+font S and not in font R.
+
+Perhaps the current interpolation depth should be accessible in a number
+register.
+
+Should \w deal with a newline like \X?
+
+Have another look at uses of token::delimiter. Perhaps we need to
+distinguish the case where we want to see if a token could start a
+number, from the case where we want to see if it could occur somewhere
+in a number expression.
+
+Provide a facility like copy thru in pic.
+
+Fancier implementation of font families which doesn't group fonts into
+families purely on the basis of their names.
+
+In the DESC file make the number of fonts optional if they are all on
+one line.
+
+Number register to give the diversion level.
+
+Time various alternative implementations of scale (both in font.c and
+number.c). On a sparc it's faster to always do it in floating point.
+
+Devise a more compact representation for the hyphenation patterns trie.
+
+Have a per-environment parameter to increase letter-spacing.
+
+Request to set character height.
+
+Request to set character slant.
+
+Support non-uniformly scalable fonts. Perhaps associate a suffix with
+a particular range of sizes. E.g.,
+ sizesuffix .display 14-512
+Then is you ask for R at pointsize 16, groff will first look for
+R.display and then R. Probably necessary to be able to specify a
+separate unitwidth for each sizesuffix (e.g., uuu for X).
+
+Make it possible to suppress hyphenation on a word-by-word basis.
+(Perhaps store hyphenation flags in tfont.)
+
+Possibly allow multiple simultaneous input line traps.
+
+Unpaddable, breakable space escape sequence.
+
+Support hanging punctuation.
+
+In justified text, if the last line of a paragraph is only a little
+bit short it might be desirable to justify the line. Allow the user
+control over this.
+
+The pm request could print where the macro was defined. Also could
+optionally print the contents of a macro.
+
+Provide some way to round numbers to multiples of the current
+horizontal or vertical motion quantum.
+
+Better string-processing support (search).
+
+Generalized ligatures.
+
+Request to remove an environment. (Maintain a count of the references
+to the environment from the environment table, environment dictionary
+or environment stack.)
+
+Perhaps in the nr request a leading '-' should only be recognized as a
+decrement when it's at the same interpolation depth as the request.
+
+Don't ever change a charinfo. Create new variants instead and chain
+them together.
+
+Unix troff appears to read the first character of a request name in
+copy mode. Should we do the same?
+
+Number register giving name of end macro.
+
+More thorough range checking.
+
+Provide syntax for octal and hexadecimal numeric constants. Perhaps
+o#100 and x#7f as per Scheme. Or perhaps PostScript 16#7f. Ambiguity
+between whether 'c' is treated as digit or scaling indicator.
+
+Local variables.
diff --git a/src/roff/troff/charinfo.h b/src/roff/troff/charinfo.h
new file mode 100644
index 0000000..0a7a481
--- /dev/null
+++ b/src/roff/troff/charinfo.h
@@ -0,0 +1,301 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <vector>
+#include <utility>
+
+extern int class_flag; // set if there was a call to '.class'
+extern void get_flags();
+
+class macro;
+
+class charinfo : glyph {
+ static int next_index;
+ charinfo *translation;
+ macro *mac;
+ unsigned char special_translation;
+ unsigned char hyphenation_code;
+ unsigned int flags;
+ unsigned char ascii_code;
+ unsigned char asciify_code;
+ char not_found;
+ char transparent_translate; // non-zero means translation applies
+ // to transparent throughput
+ char translate_input; // non-zero means that asciify_code is
+ // active for .asciify (set by .trin)
+ char_mode mode;
+ // Unicode character classes
+ std::vector<std::pair<int, int> > ranges;
+ std::vector<charinfo *> nested_classes;
+public:
+ enum { // Values for the flags bitmask. See groff
+ // manual, description of the '.cflags' request.
+ ENDS_SENTENCE = 0x01,
+ BREAK_BEFORE = 0x02,
+ BREAK_AFTER = 0x04,
+ OVERLAPS_HORIZONTALLY = 0x08,
+ OVERLAPS_VERTICALLY = 0x10,
+ TRANSPARENT = 0x20,
+ IGNORE_HCODES = 0x40,
+ DONT_BREAK_BEFORE = 0x80,
+ DONT_BREAK_AFTER = 0x100,
+ INTER_CHAR_SPACE = 0x200
+ };
+ enum {
+ TRANSLATE_NONE,
+ TRANSLATE_SPACE,
+ TRANSLATE_DUMMY,
+ TRANSLATE_STRETCHABLE_SPACE,
+ TRANSLATE_HYPHEN_INDICATOR
+ };
+ symbol nm;
+ charinfo(symbol);
+ glyph *as_glyph();
+ int ends_sentence();
+ int overlaps_vertically();
+ int overlaps_horizontally();
+ int can_break_before();
+ int can_break_after();
+ int transparent();
+ int ignore_hcodes();
+ int prohibit_break_before();
+ int prohibit_break_after();
+ int inter_char_space();
+ unsigned char get_hyphenation_code();
+ unsigned char get_ascii_code();
+ unsigned char get_asciify_code();
+ int get_unicode_code();
+ void set_hyphenation_code(unsigned char);
+ void set_ascii_code(unsigned char);
+ void set_asciify_code(unsigned char);
+ void set_translation_input();
+ int get_translation_input();
+ charinfo *get_translation(int = 0);
+ void set_translation(charinfo *, int, int);
+ void get_flags();
+ void set_flags(unsigned int);
+ void set_special_translation(int, int);
+ int get_special_translation(int = 0);
+ macro *set_macro(macro *);
+ macro *setx_macro(macro *, char_mode);
+ macro *get_macro();
+ int first_time_not_found();
+ void set_number(int);
+ int get_number();
+ int numbered();
+ int is_normal();
+ int is_fallback();
+ int is_special();
+ symbol *get_symbol();
+ void add_to_class(int);
+ void add_to_class(int, int);
+ void add_to_class(charinfo *);
+ bool is_class();
+ bool contains(int, bool = false);
+ bool contains(symbol, bool = false);
+ bool contains(charinfo *, bool = false);
+};
+
+charinfo *get_charinfo(symbol);
+extern charinfo *charset_table[];
+charinfo *get_charinfo_by_number(int);
+
+inline int charinfo::overlaps_horizontally()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & OVERLAPS_HORIZONTALLY;
+}
+
+inline int charinfo::overlaps_vertically()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & OVERLAPS_VERTICALLY;
+}
+
+inline int charinfo::can_break_before()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & BREAK_BEFORE;
+}
+
+inline int charinfo::can_break_after()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & BREAK_AFTER;
+}
+
+inline int charinfo::ends_sentence()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & ENDS_SENTENCE;
+}
+
+inline int charinfo::transparent()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & TRANSPARENT;
+}
+
+inline int charinfo::ignore_hcodes()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & IGNORE_HCODES;
+}
+
+inline int charinfo::prohibit_break_before()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & DONT_BREAK_BEFORE;
+}
+
+inline int charinfo::prohibit_break_after()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & DONT_BREAK_AFTER;
+}
+
+inline int charinfo::inter_char_space()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & INTER_CHAR_SPACE;
+}
+
+inline int charinfo::numbered()
+{
+ return number >= 0;
+}
+
+inline int charinfo::is_normal()
+{
+ return mode == CHAR_NORMAL;
+}
+
+inline int charinfo::is_fallback()
+{
+ return mode == CHAR_FALLBACK;
+}
+
+inline int charinfo::is_special()
+{
+ return mode == CHAR_SPECIAL;
+}
+
+inline charinfo *charinfo::get_translation(int transparent_throughput)
+{
+ return (transparent_throughput && !transparent_translate
+ ? 0
+ : translation);
+}
+
+inline unsigned char charinfo::get_hyphenation_code()
+{
+ return hyphenation_code;
+}
+
+inline unsigned char charinfo::get_ascii_code()
+{
+ return ascii_code;
+}
+
+inline unsigned char charinfo::get_asciify_code()
+{
+ return (translate_input ? asciify_code : 0);
+}
+
+inline void charinfo::set_flags(unsigned int c)
+{
+ flags = c;
+}
+
+inline glyph *charinfo::as_glyph()
+{
+ return this;
+}
+
+inline void charinfo::set_translation_input()
+{
+ translate_input = 1;
+}
+
+inline int charinfo::get_translation_input()
+{
+ return translate_input;
+}
+
+inline int charinfo::get_special_translation(int transparent_throughput)
+{
+ return (transparent_throughput && !transparent_translate
+ ? int(TRANSLATE_NONE)
+ : special_translation);
+}
+
+inline macro *charinfo::get_macro()
+{
+ return mac;
+}
+
+inline int charinfo::first_time_not_found()
+{
+ if (not_found)
+ return 0;
+ else {
+ not_found = 1;
+ return 1;
+ }
+}
+
+inline symbol *charinfo::get_symbol()
+{
+ return &nm;
+}
+
+inline void charinfo::add_to_class(int c)
+{
+ class_flag = 1;
+ // TODO ranges cumbersome for single characters?
+ ranges.push_back(std::pair<int, int>(c, c));
+}
+
+inline void charinfo::add_to_class(int lo,
+ int hi)
+{
+ class_flag = 1;
+ ranges.push_back(std::pair<int, int>(lo, hi));
+}
+
+inline void charinfo::add_to_class(charinfo *ci)
+{
+ class_flag = 1;
+ nested_classes.push_back(ci);
+}
+
+inline bool charinfo::is_class()
+{
+ return (!ranges.empty() || !nested_classes.empty());
+}
diff --git a/src/roff/troff/column.cpp b/src/roff/troff/column.cpp
new file mode 100644
index 0000000..55563ba
--- /dev/null
+++ b/src/roff/troff/column.cpp
@@ -0,0 +1,731 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef COLUMN
+
+#include "troff.h"
+#include "symbol.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "stringclass.h"
+
+void output_file::vjustify(vunits, symbol)
+{
+ // do nothing
+}
+
+struct justification_spec;
+struct output_line;
+
+class column : public output_file {
+private:
+ output_file *out;
+ vunits bottom;
+ output_line *col;
+ output_line **tail;
+ void add_output_line(output_line *);
+ void begin_page(int pageno, vunits page_length);
+ void flush();
+ void print_line(hunits, vunits, node *, vunits, vunits);
+ void vjustify(vunits, symbol);
+ void transparent_char(unsigned char c);
+ void copy_file(hunits, vunits, const char *);
+ int is_printing();
+ void check_bottom();
+public:
+ column();
+ ~column();
+ void start();
+ void output();
+ void justify(const justification_spec &);
+ void trim();
+ void reset();
+ vunits get_bottom();
+ vunits get_last_extra_space();
+ int is_active() { return out != 0; }
+};
+
+column *the_column = 0;
+
+struct transparent_output_line;
+struct vjustify_output_line;
+
+class output_line {
+ output_line *next;
+public:
+ output_line();
+ virtual ~output_line();
+ virtual void output(output_file *, vunits);
+ virtual transparent_output_line *as_transparent_output_line();
+ virtual vjustify_output_line *as_vjustify_output_line();
+ virtual vunits distance();
+ virtual vunits height();
+ virtual void reset();
+ virtual vunits extra_space(); // post line
+ friend class column;
+ friend class justification_spec;
+};
+
+class position_output_line : public output_line {
+ vunits dist;
+public:
+ position_output_line(vunits);
+ vunits distance();
+};
+
+class node_output_line : public position_output_line {
+ node *nd;
+ hunits page_offset;
+ vunits before;
+ vunits after;
+public:
+ node_output_line(vunits, node *, hunits, vunits, vunits);
+ ~node_output_line();
+ void output(output_file *, vunits);
+ vunits height();
+ vunits extra_space();
+};
+
+class vjustify_output_line : public position_output_line {
+ vunits current;
+ symbol typ;
+public:
+ vjustify_output_line(vunits dist, symbol);
+ vunits height();
+ vjustify_output_line *as_vjustify_output_line();
+ void vary(vunits amount);
+ void reset();
+ symbol type();
+};
+
+inline symbol vjustify_output_line::type()
+{
+ return typ;
+}
+
+class copy_file_output_line : public position_output_line {
+ symbol filename;
+ hunits hpos;
+public:
+ copy_file_output_line(vunits, const char *, hunits);
+ void output(output_file *, vunits);
+};
+
+class transparent_output_line : public output_line {
+ string buf;
+public:
+ transparent_output_line();
+ void output(output_file *, vunits);
+ void append_char(unsigned char c);
+ transparent_output_line *as_transparent_output_line();
+};
+
+output_line::output_line() : next(0)
+{
+}
+
+output_line::~output_line()
+{
+}
+
+void output_line::reset()
+{
+}
+
+transparent_output_line *output_line::as_transparent_output_line()
+{
+ return 0;
+}
+
+vjustify_output_line *output_line::as_vjustify_output_line()
+{
+ return 0;
+}
+
+void output_line::output(output_file *, vunits)
+{
+}
+
+vunits output_line::distance()
+{
+ return V0;
+}
+
+vunits output_line::height()
+{
+ return V0;
+}
+
+vunits output_line::extra_space()
+{
+ return V0;
+}
+
+position_output_line::position_output_line(vunits d)
+: dist(d)
+{
+}
+
+vunits position_output_line::distance()
+{
+ return dist;
+}
+
+node_output_line::node_output_line(vunits d, node *n, hunits po, vunits b, vunits a)
+: position_output_line(d), nd(n), page_offset(po), before(b), after(a)
+{
+}
+
+node_output_line::~node_output_line()
+{
+ delete_node_list(nd);
+}
+
+void node_output_line::output(output_file *out, vunits pos)
+{
+ out->print_line(page_offset, pos, nd, before, after);
+ nd = 0;
+}
+
+vunits node_output_line::height()
+{
+ return after;
+}
+
+vunits node_output_line::extra_space()
+{
+ return after;
+}
+
+vjustify_output_line::vjustify_output_line(vunits d, symbol t)
+: position_output_line(d), typ(t)
+{
+}
+
+void vjustify_output_line::reset()
+{
+ current = V0;
+}
+
+vunits vjustify_output_line::height()
+{
+ return current;
+}
+
+vjustify_output_line *vjustify_output_line::as_vjustify_output_line()
+{
+ return this;
+}
+
+inline void vjustify_output_line::vary(vunits amount)
+{
+ current += amount;
+}
+
+transparent_output_line::transparent_output_line()
+{
+}
+
+transparent_output_line *transparent_output_line::as_transparent_output_line()
+{
+ return this;
+}
+
+void transparent_output_line::append_char(unsigned char c)
+{
+ assert(c != 0);
+ buf += c;
+}
+
+void transparent_output_line::output(output_file *out, vunits)
+{
+ int len = buf.length();
+ for (int i = 0; i < len; i++)
+ out->transparent_char(buf[i]);
+}
+
+copy_file_output_line::copy_file_output_line(vunits d, const char *f, hunits h)
+: position_output_line(d), hpos(h), filename(f)
+{
+}
+
+void copy_file_output_line::output(output_file *out, vunits pos)
+{
+ out->copy_file(hpos, pos, filename.contents());
+}
+
+column::column()
+: bottom(V0), col(0), tail(&col), out(0)
+{
+}
+
+column::~column()
+{
+ assert(out != 0);
+ error("automatically outputting column before exiting");
+ output();
+ delete the_output;
+}
+
+void column::start()
+{
+ assert(out == 0);
+ if (!the_output)
+ init_output();
+ assert(the_output != 0);
+ out = the_output;
+ the_output = this;
+}
+
+void column::begin_page(int pageno, vunits page_length)
+{
+ assert(out != 0);
+ if (col) {
+ error("automatically outputting column before beginning next page");
+ output();
+ the_output->begin_page(pageno, page_length);
+ }
+ else
+ out->begin_page(pageno, page_length);
+
+}
+
+void column::flush()
+{
+ assert(out != 0);
+ out->flush();
+}
+
+int column::is_printing()
+{
+ assert(out != 0);
+ return out->is_printing();
+}
+
+vunits column::get_bottom()
+{
+ return bottom;
+}
+
+void column::add_output_line(output_line *ln)
+{
+ *tail = ln;
+ bottom += ln->distance();
+ bottom += ln->height();
+ ln->next = 0;
+ tail = &(*tail)->next;
+}
+
+void column::print_line(hunits page_offset, vunits pos, node *nd,
+ vunits before, vunits after)
+{
+ assert(out != 0);
+ add_output_line(new node_output_line(pos - bottom, nd, page_offset, before, after));
+}
+
+void column::vjustify(vunits pos, symbol typ)
+{
+ assert(out != 0);
+ add_output_line(new vjustify_output_line(pos - bottom, typ));
+}
+
+void column::transparent_char(unsigned char c)
+{
+ assert(out != 0);
+ transparent_output_line *tl = 0;
+ if (*tail)
+ tl = (*tail)->as_transparent_output_line();
+ if (!tl) {
+ tl = new transparent_output_line;
+ add_output_line(tl);
+ }
+ tl->append_char(c);
+}
+
+void column::copy_file(hunits page_offset, vunits pos, const char *filename)
+{
+ assert(out != 0);
+ add_output_line(new copy_file_output_line(pos - bottom, filename, page_offset));
+}
+
+void column::trim()
+{
+ output_line **spp = 0;
+ for (output_line **pp = &col; *pp; pp = &(*pp)->next)
+ if ((*pp)->as_vjustify_output_line() == 0)
+ spp = 0;
+ else if (!spp)
+ spp = pp;
+ if (spp) {
+ output_line *ln = *spp;
+ *spp = 0;
+ tail = spp;
+ while (ln) {
+ output_line *tem = ln->next;
+ bottom -= ln->distance();
+ bottom -= ln->height();
+ delete ln;
+ ln = tem;
+ }
+ }
+}
+
+void column::reset()
+{
+ bottom = V0;
+ for (output_line *ln = col; ln; ln = ln->next) {
+ bottom += ln->distance();
+ ln->reset();
+ bottom += ln->height();
+ }
+}
+
+void column::check_bottom()
+{
+ vunits b;
+ for (output_line *ln = col; ln; ln = ln->next) {
+ b += ln->distance();
+ b += ln->height();
+ }
+ assert(b == bottom);
+}
+
+void column::output()
+{
+ assert(out != 0);
+ vunits vpos(V0);
+ output_line *ln = col;
+ while (ln) {
+ vpos += ln->distance();
+ ln->output(out, vpos);
+ vpos += ln->height();
+ output_line *tem = ln->next;
+ delete ln;
+ ln = tem;
+ }
+ tail = &col;
+ bottom = V0;
+ col = 0;
+ the_output = out;
+ out = 0;
+}
+
+vunits column::get_last_extra_space()
+{
+ if (!col)
+ return V0;
+ for (output_line *p = col; p->next; p = p->next)
+ ;
+ return p->extra_space();
+}
+
+class justification_spec {
+ vunits height;
+ symbol *type;
+ vunits *amount;
+ int n;
+ int maxn;
+public:
+ justification_spec(vunits);
+ ~justification_spec();
+ void append(symbol t, vunits v);
+ void justify(output_line *, vunits *bottomp) const;
+};
+
+justification_spec::justification_spec(vunits h)
+: height(h), n(0), maxn(10)
+{
+ type = new symbol[maxn];
+ amount = new vunits[maxn];
+}
+
+justification_spec::~justification_spec()
+{
+ delete[] type;
+ delete[] amount;
+}
+
+void justification_spec::append(symbol t, vunits v)
+{
+ if (v <= V0) {
+ if (v < V0)
+ warning(WARN_RANGE,
+ "maximum space for vertical justification must not be negative");
+ else
+ warning(WARN_RANGE,
+ "maximum space for vertical justification must not be zero");
+ return;
+ }
+ if (n >= maxn) {
+ maxn *= 2;
+ symbol *old_type = type;
+ type = new symbol[maxn];
+ int i;
+ for (i = 0; i < n; i++)
+ type[i] = old_type[i];
+ delete[] old_type;
+ vunits *old_amount = amount;
+ amount = new vunits[maxn];
+ for (i = 0; i < n; i++)
+ amount[i] = old_amount[i];
+ delete[] old_amount;
+ }
+ assert(n < maxn);
+ type[n] = t;
+ amount[n] = v;
+ n++;
+}
+
+void justification_spec::justify(output_line *col, vunits *bottomp) const
+{
+ if (*bottomp >= height)
+ return;
+ vunits total;
+ output_line *p;
+ for (p = col; p; p = p->next) {
+ vjustify_output_line *sp = p->as_vjustify_output_line();
+ if (sp) {
+ symbol t = sp->type();
+ for (int i = 0; i < n; i++) {
+ if (t == type[i])
+ total += amount[i];
+ }
+ }
+ }
+ vunits gap = height - *bottomp;
+ for (p = col; p; p = p->next) {
+ vjustify_output_line *sp = p->as_vjustify_output_line();
+ if (sp) {
+ symbol t = sp->type();
+ for (int i = 0; i < n; i++) {
+ if (t == type[i]) {
+ if (total <= gap) {
+ sp->vary(amount[i]);
+ gap -= amount[i];
+ }
+ else {
+ // gap < total
+ vunits v = scale(amount[i], gap, total);
+ sp->vary(v);
+ gap -= v;
+ }
+ total -= amount[i];
+ }
+ }
+ }
+ }
+ assert(total == V0);
+ *bottomp = height - gap;
+}
+
+void column::justify(const justification_spec &js)
+{
+ check_bottom();
+ js.justify(col, &bottom);
+ check_bottom();
+}
+
+void column_justify()
+{
+ vunits height;
+ if (!the_column->is_active())
+ error("can't justify column - column not active");
+ else if (get_vunits(&height, 'v')) {
+ justification_spec js(height);
+ symbol nm = get_long_name(true /* required */);
+ if (!nm.is_null()) {
+ vunits v;
+ if (get_vunits(&v, 'v')) {
+ js.append(nm, v);
+ int err = 0;
+ while (has_arg()) {
+ nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ err = 1;
+ break;
+ }
+ if (!get_vunits(&v, 'v')) {
+ err = 1;
+ break;
+ }
+ js.append(nm, v);
+ }
+ if (!err)
+ the_column->justify(js);
+ }
+ }
+ }
+ skip_line();
+}
+
+void column_start()
+{
+ if (the_column->is_active())
+ error("can't start column - column already active");
+ else
+ the_column->start();
+ skip_line();
+}
+
+void column_output()
+{
+ if (!the_column->is_active())
+ error("can't output column - column not active");
+ else
+ the_column->output();
+ skip_line();
+}
+
+void column_trim()
+{
+ if (!the_column->is_active())
+ error("can't trim column - column not active");
+ else
+ the_column->trim();
+ skip_line();
+}
+
+void column_reset()
+{
+ if (!the_column->is_active())
+ error("can't reset column - column not active");
+ else
+ the_column->reset();
+ skip_line();
+}
+
+class column_bottom_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *column_bottom_reg::get_string()
+{
+ return i_to_a(the_column->get_bottom().to_units());
+}
+
+class column_extra_space_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *column_extra_space_reg::get_string()
+{
+ return i_to_a(the_column->get_last_extra_space().to_units());
+}
+
+class column_active_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *column_active_reg::get_string()
+{
+ return the_column->is_active() ? "1" : "0";
+}
+
+static int no_vjustify_mode = 0;
+
+class vjustify_node : public node {
+ symbol typ;
+public:
+ vjustify_node(symbol);
+ int reread(int *);
+ const char *type();
+ int same(node *);
+ node *copy();
+};
+
+vjustify_node::vjustify_node(symbol t)
+: typ(t)
+{
+}
+
+node *vjustify_node::copy()
+{
+ return new vjustify_node(typ, div_nest_level);
+}
+
+const char *vjustify_node::type()
+{
+ return "vjustify_node";
+}
+
+int vjustify_node::same(node *nd)
+{
+ return typ == ((vjustify_node *)nd)->typ;
+}
+
+int vjustify_node::reread(int *bolp)
+{
+ curdiv->vjustify(typ);
+ *bolp = 1;
+ return 1;
+}
+
+void macro_diversion::vjustify(symbol type)
+{
+ if (!no_vjustify_mode)
+ mac->append(new vjustify_node(type));
+}
+
+void top_level_diversion::vjustify(symbol type)
+{
+ if (no_space_mode || no_vjustify_mode)
+ return;
+ assert(first_page_begun); // I'm not sure about this.
+ the_output->vjustify(vertical_position, type);
+}
+
+void no_vjustify()
+{
+ skip_line();
+ no_vjustify_mode = 1;
+}
+
+void restore_vjustify()
+{
+ skip_line();
+ no_vjustify_mode = 0;
+}
+
+void init_column_requests()
+{
+ the_column = new column;
+ init_request("cols", column_start);
+ init_request("colo", column_output);
+ init_request("colj", column_justify);
+ init_request("colr", column_reset);
+ init_request("colt", column_trim);
+ init_request("nvj", no_vjustify);
+ init_request("rvj", restore_vjustify);
+ register_dictionary.define(".colb", new column_bottom_reg);
+ register_dictionary.define(".colx", new column_extra_space_reg);
+ register_dictionary.define(".cola", new column_active_reg);
+ register_dictionary.define(".nvj",
+ new readonly_register(&no_vjustify_mode));
+}
+
+#endif /* COLUMN */
diff --git a/src/roff/troff/dictionary.cpp b/src/roff/troff/dictionary.cpp
new file mode 100644
index 0000000..08afbd3
--- /dev/null
+++ b/src/roff/troff/dictionary.cpp
@@ -0,0 +1,209 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include "troff.h"
+#include "dictionary.h"
+
+// is 'p' a good size for a hash table
+
+static int is_good_size(unsigned int p)
+{
+ const unsigned int SMALL = 10;
+ unsigned int i;
+ for (i = 2; i <= p/2; i++)
+ if (p % i == 0)
+ return 0;
+ for (i = 0x100; i != 0; i <<= 8)
+ if (i % p <= SMALL || i % p > p - SMALL)
+ return 0;
+ return 1;
+}
+
+dictionary::dictionary(int n) : size(n), used(0), threshold(0.5), factor(1.5)
+{
+ table = new association[n];
+}
+
+// see Knuth, Sorting and Searching, p518, Algorithm L
+// we can't use double-hashing because we want a remove function
+
+void *dictionary::lookup(symbol s, void *v)
+{
+ int i;
+ for (i = int(s.hash() % size);
+ table[i].v != 0;
+ i == 0 ? i = size - 1: --i)
+ if (s == table[i].s) {
+ if (v != 0) {
+ void *temp = table[i].v;
+ table[i].v = v;
+ return temp;
+ }
+ else
+ return table[i].v;
+ }
+ if (v == 0)
+ return 0;
+ ++used;
+ table[i].v = v;
+ table[i].s = s;
+ if ((double)used/(double)size >= threshold || used + 1 >= size) {
+ int old_size = size;
+ size = int(size*factor);
+ while (!is_good_size(size))
+ ++size;
+ association *old_table = table;
+ table = new association[size];
+ used = 0;
+ for (i = 0; i < old_size; i++)
+ if (old_table[i].v != 0)
+ (void)lookup(old_table[i].s, old_table[i].v);
+ delete[] old_table;
+ }
+ return 0;
+}
+
+void *dictionary::lookup(const char *p)
+{
+ symbol s(p, MUST_ALREADY_EXIST);
+ if (s.is_null())
+ return 0;
+ else
+ return lookup(s);
+}
+
+// see Knuth, Sorting and Searching, p527, Algorithm R
+
+void *dictionary::remove(symbol s)
+{
+ // this relies on the fact that we are using linear probing
+ int i;
+ for (i = int(s.hash() % size);
+ table[i].v != 0 && s != table[i].s;
+ i == 0 ? i = size - 1: --i)
+ ;
+ void *p = table[i].v;
+ while (table[i].v != 0) {
+ table[i].v = 0;
+ int j = i;
+ int r;
+ do {
+ --i;
+ if (i < 0)
+ i = size - 1;
+ if (table[i].v == 0)
+ break;
+ r = int(table[i].s.hash() % size);
+ } while ((i <= r && r < j) || (r < j && j < i) || (j < i && i <= r));
+ table[j] = table[i];
+ }
+ if (p != 0)
+ --used;
+ return p;
+}
+
+dictionary_iterator::dictionary_iterator(dictionary &d) : dict(&d), i(0)
+{
+}
+
+int dictionary_iterator::get(symbol *sp, void **vp)
+{
+ for (; i < dict->size; i++)
+ if (dict->table[i].v) {
+ *sp = dict->table[i].s;
+ *vp = dict->table[i].v;
+ i++;
+ return 1;
+ }
+ return 0;
+}
+
+object_dictionary_iterator::object_dictionary_iterator(object_dictionary &od)
+: di(od.d)
+{
+}
+
+object::object() : rcount(0)
+{
+}
+
+object::~object()
+{
+}
+
+void object::add_reference()
+{
+ rcount += 1;
+}
+
+void object::remove_reference()
+{
+ if (--rcount == 0)
+ delete this;
+}
+
+object_dictionary::object_dictionary(int n) : d(n)
+{
+}
+
+object *object_dictionary::lookup(symbol nm)
+{
+ return (object *)d.lookup(nm);
+}
+
+void object_dictionary::define(symbol nm, object *obj)
+{
+ obj->add_reference();
+ obj = (object *)d.lookup(nm, obj);
+ if (obj)
+ obj->remove_reference();
+}
+
+void object_dictionary::rename(symbol oldnm, symbol newnm)
+{
+ object *obj = (object *)d.remove(oldnm);
+ if (obj) {
+ obj = (object *)d.lookup(newnm, obj);
+ if (obj)
+ obj->remove_reference();
+ }
+}
+
+void object_dictionary::remove(symbol nm)
+{
+ object *obj = (object *)d.remove(nm);
+ if (obj)
+ obj->remove_reference();
+}
+
+// Return non-zero if oldnm was defined.
+
+int object_dictionary::alias(symbol newnm, symbol oldnm)
+{
+ object *obj = (object *)d.lookup(oldnm);
+ if (obj) {
+ obj->add_reference();
+ obj = (object *)d.lookup(newnm, obj);
+ if (obj)
+ obj->remove_reference();
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/roff/troff/dictionary.h b/src/roff/troff/dictionary.h
new file mode 100644
index 0000000..71c4f3f
--- /dev/null
+++ b/src/roff/troff/dictionary.h
@@ -0,0 +1,91 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+
+// there is no distinction between name with no value and name with NULL value
+// null names are not permitted (they will be ignored).
+
+struct association {
+ symbol s;
+ void *v;
+ association() : v(0) {}
+};
+
+class dictionary;
+
+class dictionary_iterator {
+ dictionary *dict;
+ int i;
+public:
+ dictionary_iterator(dictionary &);
+ int get(symbol *, void **);
+};
+
+class dictionary {
+ int size;
+ int used;
+ double threshold;
+ double factor;
+ association *table;
+ void rehash(int);
+public:
+ dictionary(int);
+ void *lookup(symbol s, void *v=0); // returns value associated with key
+ void *lookup(const char *);
+ // if second parameter not NULL, value will be replaced
+ void *remove(symbol);
+ friend class dictionary_iterator;
+};
+
+class object {
+ int rcount;
+ public:
+ object();
+ virtual ~object();
+ void add_reference();
+ void remove_reference();
+};
+
+class object_dictionary;
+
+class object_dictionary_iterator {
+ dictionary_iterator di;
+public:
+ object_dictionary_iterator(object_dictionary &);
+ int get(symbol *, object **);
+};
+
+class object_dictionary {
+ dictionary d;
+public:
+ object_dictionary(int);
+ object *lookup(symbol nm);
+ void define(symbol nm, object *obj);
+ void rename(symbol oldnm, symbol newnm);
+ void remove(symbol nm);
+ int alias(symbol newnm, symbol oldnm);
+ friend class object_dictionary_iterator;
+};
+
+
+inline int object_dictionary_iterator::get(symbol *sp, object **op)
+{
+ return di.get(sp, (void **)op);
+}
diff --git a/src/roff/troff/div.cpp b/src/roff/troff/div.cpp
new file mode 100644
index 0000000..d195baf
--- /dev/null
+++ b/src/roff/troff/div.cpp
@@ -0,0 +1,1212 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+// diversions
+
+#include "troff.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+
+#include "nonposix.h"
+
+bool is_exit_underway = false;
+bool is_eoi_macro_finished = false;
+bool seen_last_page_ejector = false;
+static bool began_page_in_eoi_macro = false;
+int last_page_number = 0; // if > 0, the number of the last page
+ // specified with -o
+
+static int last_post_line_extra_space = 0; // needed for \n(.a
+static int nl_reg_contents = -1;
+static int dl_reg_contents = 0;
+static int dn_reg_contents = 0;
+static int vertical_position_traps_flag = 1;
+static vunits truncated_space;
+static vunits needed_space;
+
+diversion::diversion(symbol s)
+: prev(0), nm(s), vertical_position(V0), high_water_mark(V0),
+ any_chars_added(0), no_space_mode(0), needs_push(0), saved_seen_break(0),
+ saved_seen_space(0), saved_seen_eol(0), saved_suppress_next_eol(0),
+ marked_place(V0)
+{
+}
+
+struct vertical_size {
+ vunits pre_extra, post_extra, pre, post;
+ vertical_size(vunits vs, vunits post_vs);
+};
+
+vertical_size::vertical_size(vunits vs, vunits post_vs)
+: pre_extra(V0), post_extra(V0), pre(vs), post(post_vs)
+{
+}
+
+void node::set_vertical_size(vertical_size *)
+{
+}
+
+void extra_size_node::set_vertical_size(vertical_size *v)
+{
+ if (n < V0) {
+ if (-n > v->pre_extra)
+ v->pre_extra = -n;
+ }
+ else if (n > v->post_extra)
+ v->post_extra = n;
+}
+
+void vertical_size_node::set_vertical_size(vertical_size *v)
+{
+ if (n < V0)
+ v->pre = -n;
+ else
+ v->post = n;
+}
+
+top_level_diversion *topdiv;
+
+diversion *curdiv;
+
+void do_divert(int append, int boxing)
+{
+ tok.skip();
+ symbol nm = get_name();
+ if (nm.is_null()) {
+ if (curdiv->prev) {
+ curenv->seen_break = curdiv->saved_seen_break;
+ curenv->seen_space = curdiv->saved_seen_space;
+ curenv->seen_eol = curdiv->saved_seen_eol;
+ curenv->suppress_next_eol = curdiv->saved_suppress_next_eol;
+ if (boxing) {
+ curenv->line = curdiv->saved_line;
+ curenv->width_total = curdiv->saved_width_total;
+ curenv->space_total = curdiv->saved_space_total;
+ curenv->saved_indent = curdiv->saved_saved_indent;
+ curenv->target_text_length = curdiv->saved_target_text_length;
+ curenv->prev_line_interrupted = curdiv->saved_prev_line_interrupted;
+ }
+ diversion *temp = curdiv;
+ curdiv = curdiv->prev;
+ delete temp;
+ }
+ else
+ warning(WARN_DI, "diversion stack underflow");
+ }
+ else {
+ macro_diversion *md = new macro_diversion(nm, append);
+ md->prev = curdiv;
+ curdiv = md;
+ curdiv->saved_seen_break = curenv->seen_break;
+ curdiv->saved_seen_space = curenv->seen_space;
+ curdiv->saved_seen_eol = curenv->seen_eol;
+ curdiv->saved_suppress_next_eol = curenv->suppress_next_eol;
+ curenv->seen_break = 0;
+ curenv->seen_space = 0;
+ curenv->seen_eol = 0;
+ if (boxing) {
+ curdiv->saved_line = curenv->line;
+ curdiv->saved_width_total = curenv->width_total;
+ curdiv->saved_space_total = curenv->space_total;
+ curdiv->saved_saved_indent = curenv->saved_indent;
+ curdiv->saved_target_text_length = curenv->target_text_length;
+ curdiv->saved_prev_line_interrupted = curenv->prev_line_interrupted;
+ curenv->line = 0;
+ curenv->start_line();
+ }
+ }
+ skip_line();
+}
+
+void divert()
+{
+ do_divert(0, 0);
+}
+
+void divert_append()
+{
+ do_divert(1, 0);
+}
+
+void box()
+{
+ do_divert(0, 1);
+}
+
+void box_append()
+{
+ do_divert(1, 1);
+}
+
+void diversion::need(vunits n)
+{
+ vunits d = distance_to_next_trap();
+ if (d < n) {
+ truncated_space = -d;
+ needed_space = n;
+ space(d, 1);
+ }
+}
+
+macro_diversion::macro_diversion(symbol s, int append)
+: diversion(s), max_width(H0)
+{
+#if 0
+ if (append) {
+ /* We don't allow recursive appends, e.g.:
+
+ .da a
+ .a
+ .di
+
+ This causes an infinite loop in troff anyway.
+ This is because the user could do
+
+ .as a foo
+
+ in the diversion, and this would mess things up royally,
+ since there would be two things appending to the same
+ macro_header.
+ To make it work, we would have to copy the _contents_
+ of the macro into which we were diverting; this doesn't
+ strike me as worthwhile.
+ However,
+
+ .di a
+ .a
+ .a
+ .di
+
+ will work and will make 'a' contain two copies of what it contained
+ before; in troff, 'a' would contain nothing. */
+ request_or_macro *rm
+ = (request_or_macro *)request_dictionary.remove(s);
+ if (!rm || (mac = rm->to_macro()) == 0)
+ mac = new macro;
+ }
+ else
+ mac = new macro;
+#endif
+ // We can now catch the situation described above by comparing
+ // the length of the charlist in the macro_header with the length
+ // stored in the macro. When we detect this, we copy the contents.
+ mac = new macro(1);
+ if (append) {
+ request_or_macro *rm
+ = (request_or_macro *)request_dictionary.lookup(s);
+ if (rm) {
+ macro *m = rm->to_macro();
+ if (m)
+ *mac = *m;
+ }
+ }
+}
+
+macro_diversion::~macro_diversion()
+{
+ request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
+ macro *m = rm ? rm->to_macro() : 0;
+ if (m) {
+ *m = *mac;
+ delete mac;
+ }
+ else
+ request_dictionary.define(nm, mac);
+ mac = 0;
+ dl_reg_contents = max_width.to_units();
+ dn_reg_contents = vertical_position.to_units();
+}
+
+vunits macro_diversion::distance_to_next_trap()
+{
+ if (!diversion_trap.is_null() && diversion_trap_pos > vertical_position)
+ return diversion_trap_pos - vertical_position;
+ else
+ // Subtract vresolution so that vunits::vunits does not overflow.
+ return vunits(INT_MAX - vresolution);
+}
+
+void macro_diversion::transparent_output(unsigned char c)
+{
+ mac->append(c);
+}
+
+void macro_diversion::transparent_output(node *n)
+{
+ mac->append(n);
+}
+
+void macro_diversion::output(node *nd, int retain_size,
+ vunits vs, vunits post_vs, hunits width)
+{
+ no_space_mode = 0;
+ vertical_size v(vs, post_vs);
+ while (nd != 0) {
+ nd->set_vertical_size(&v);
+ node *temp = nd;
+ nd = nd->next;
+ if (temp->interpret(mac))
+ delete temp;
+ else {
+#if 1
+ temp->freeze_space();
+#endif
+ mac->append(temp);
+ }
+ }
+ last_post_line_extra_space = v.post_extra.to_units();
+ if (!retain_size) {
+ v.pre = vs;
+ v.post = post_vs;
+ }
+ if (width > max_width)
+ max_width = width;
+ vunits x = v.pre + v.pre_extra + v.post + v.post_extra;
+ if (vertical_position_traps_flag
+ && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
+ && diversion_trap_pos <= vertical_position + x) {
+ vunits trunc = vertical_position + x - diversion_trap_pos;
+ if (trunc > v.post)
+ trunc = v.post;
+ v.post -= trunc;
+ x -= trunc;
+ truncated_space = trunc;
+ spring_trap(diversion_trap);
+ }
+ mac->append(new vertical_size_node(-v.pre));
+ mac->append(new vertical_size_node(v.post));
+ mac->append('\n');
+ vertical_position += x;
+ if (vertical_position - v.post > high_water_mark)
+ high_water_mark = vertical_position - v.post;
+}
+
+void macro_diversion::space(vunits n, int)
+{
+ if (vertical_position_traps_flag
+ && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
+ && diversion_trap_pos <= vertical_position + n) {
+ truncated_space = vertical_position + n - diversion_trap_pos;
+ n = diversion_trap_pos - vertical_position;
+ spring_trap(diversion_trap);
+ }
+ else if (n + vertical_position < V0)
+ n = -vertical_position;
+ mac->append(new diverted_space_node(n));
+ vertical_position += n;
+}
+
+void macro_diversion::copy_file(const char *filename)
+{
+ mac->append(new diverted_copy_file_node(filename));
+}
+
+top_level_diversion::top_level_diversion()
+: page_number(0), page_count(0), last_page_count(-1),
+ page_length(units_per_inch*11),
+ prev_page_offset(units_per_inch), page_offset(units_per_inch),
+ page_trap_list(0), have_next_page_number(0),
+ ejecting_page(0), before_first_page(1)
+{
+}
+
+// find the next trap after pos
+
+trap *top_level_diversion::find_next_trap(vunits *next_trap_pos)
+{
+ trap *next_trap = 0;
+ for (trap *pt = page_trap_list; pt != 0; pt = pt->next)
+ if (!pt->nm.is_null()) {
+ if (pt->position >= V0) {
+ if (pt->position > vertical_position
+ && pt->position < page_length
+ && (next_trap == 0 || pt->position < *next_trap_pos)) {
+ next_trap = pt;
+ *next_trap_pos = pt->position;
+ }
+ }
+ else {
+ vunits pos = pt->position;
+ pos += page_length;
+ if (pos > 0
+ && pos > vertical_position
+ && (next_trap == 0 || pos < *next_trap_pos)) {
+ next_trap = pt;
+ *next_trap_pos = pos;
+ }
+ }
+ }
+ return next_trap;
+}
+
+vunits top_level_diversion::distance_to_next_trap()
+{
+ vunits d;
+ if (!find_next_trap(&d))
+ return page_length - vertical_position;
+ else
+ return d - vertical_position;
+}
+
+void top_level_diversion::output(node *nd, int retain_size,
+ vunits vs, vunits post_vs, hunits width)
+{
+ no_space_mode = 0;
+ vunits next_trap_pos;
+ trap *next_trap = find_next_trap(&next_trap_pos);
+ if (before_first_page && begin_page())
+ fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
+ vertical_size v(vs, post_vs);
+ for (node *tem = nd; tem != 0; tem = tem->next)
+ tem->set_vertical_size(&v);
+ last_post_line_extra_space = v.post_extra.to_units();
+ if (!retain_size) {
+ v.pre = vs;
+ v.post = post_vs;
+ }
+ vertical_position += v.pre;
+ vertical_position += v.pre_extra;
+ the_output->print_line(page_offset, vertical_position, nd,
+ v.pre + v.pre_extra, v.post_extra, width);
+ vertical_position += v.post_extra;
+ if (vertical_position > high_water_mark)
+ high_water_mark = vertical_position;
+ if (vertical_position_traps_flag && vertical_position >= page_length)
+ begin_page();
+ else if (vertical_position_traps_flag
+ && next_trap != 0 && vertical_position >= next_trap_pos) {
+ nl_reg_contents = vertical_position.to_units();
+ truncated_space = v.post;
+ spring_trap(next_trap->nm);
+ }
+ else if (v.post > V0) {
+ vertical_position += v.post;
+ if (vertical_position_traps_flag
+ && next_trap != 0 && vertical_position >= next_trap_pos) {
+ truncated_space = vertical_position - next_trap_pos;
+ vertical_position = next_trap_pos;
+ nl_reg_contents = vertical_position.to_units();
+ spring_trap(next_trap->nm);
+ }
+ else if (vertical_position_traps_flag && vertical_position >= page_length)
+ begin_page();
+ else
+ nl_reg_contents = vertical_position.to_units();
+ }
+ else
+ nl_reg_contents = vertical_position.to_units();
+}
+
+void top_level_diversion::transparent_output(unsigned char c)
+{
+ if (before_first_page && begin_page())
+ // This can only happen with the .output request.
+ fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
+ const char *s = asciify(c);
+ while (*s)
+ the_output->transparent_char(*s++);
+}
+
+void top_level_diversion::transparent_output(node * /*n*/)
+{
+ if (getenv("GROFF_ENABLE_TRANSPARENCY_WARNINGS") != 0 /* nullptr */)
+ error("can't transparently output node at top level");
+}
+
+void top_level_diversion::copy_file(const char *filename)
+{
+ if (before_first_page && begin_page())
+ fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
+ the_output->copy_file(page_offset, vertical_position, filename);
+}
+
+void top_level_diversion::space(vunits n, int forced)
+{
+ if (no_space_mode) {
+ if (!forced)
+ return;
+ else
+ no_space_mode = 0;
+ }
+ if (before_first_page) {
+ begin_page(n);
+ return;
+ }
+ vunits next_trap_pos;
+ trap *next_trap = find_next_trap(&next_trap_pos);
+ vunits y = vertical_position + n;
+ if (curenv->get_vertical_spacing().to_units())
+ curenv->seen_space += n.to_units()
+ / curenv->get_vertical_spacing().to_units();
+ if (vertical_position_traps_flag && next_trap != 0 && y >= next_trap_pos) {
+ vertical_position = next_trap_pos;
+ nl_reg_contents = vertical_position.to_units();
+ truncated_space = y - vertical_position;
+ spring_trap(next_trap->nm);
+ }
+ else if (y < V0) {
+ vertical_position = V0;
+ nl_reg_contents = vertical_position.to_units();
+ }
+ else if (vertical_position_traps_flag && y >= page_length && n >= V0)
+ begin_page(y - page_length);
+ else {
+ vertical_position = y;
+ nl_reg_contents = vertical_position.to_units();
+ }
+}
+
+trap::trap(symbol s, vunits n, trap *p)
+: next(p), position(n), nm(s)
+{
+}
+
+void top_level_diversion::add_trap(symbol nam, vunits pos)
+{
+ trap *first_free_slot = 0;
+ trap **p;
+ for (p = &page_trap_list; *p; p = &(*p)->next) {
+ if ((*p)->nm.is_null()) {
+ if (first_free_slot == 0)
+ first_free_slot = *p;
+ }
+ else if ((*p)->position == pos) {
+ (*p)->nm = nam;
+ return;
+ }
+ }
+ if (first_free_slot) {
+ first_free_slot->nm = nam;
+ first_free_slot->position = pos;
+ }
+ else
+ *p = new trap(nam, pos, 0);
+}
+
+void top_level_diversion::remove_trap(symbol nam)
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->nm == nam) {
+ p->nm = NULL_SYMBOL;
+ return;
+ }
+}
+
+void top_level_diversion::remove_trap_at(vunits pos)
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->position == pos) {
+ p->nm = NULL_SYMBOL;
+ return;
+ }
+}
+
+void top_level_diversion::change_trap(symbol nam, vunits pos)
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->nm == nam) {
+ p->position = pos;
+ return;
+ }
+ warning(WARN_MAC, "cannot move unplanted trap macro '%1'",
+ nam.contents());
+}
+
+void top_level_diversion::print_traps()
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->nm.is_null())
+ fprintf(stderr, " empty\n");
+ else
+ fprintf(stderr, "%s\t%d\n", p->nm.contents(), p->position.to_units());
+ fflush(stderr);
+}
+
+void end_diversions()
+{
+ while (curdiv != topdiv) {
+ error("automatically ending diversion '%1' on exit",
+ curdiv->nm.contents());
+ diversion *tem = curdiv;
+ curdiv = curdiv->prev;
+ delete tem;
+ }
+}
+
+void cleanup_and_exit(int exit_code)
+{
+ if (the_output) {
+ the_output->trailer(topdiv->get_page_length());
+ // If we're already dying, don't call the_output's destructor. See
+ // node.cpp:real_output_file::~real_output_file().
+ if (!the_output->is_dying)
+ delete the_output;
+ }
+ FLUSH_INPUT_PIPE(STDIN_FILENO);
+ exit(exit_code);
+}
+
+// Returns non-zero if it sprung a top-of-page trap.
+// The optional parameter is for the .trunc register.
+int top_level_diversion::begin_page(vunits n)
+{
+ if (is_exit_underway) {
+ if (page_count == last_page_count
+ ? curenv->is_empty()
+ : (is_eoi_macro_finished && (seen_last_page_ejector
+ || began_page_in_eoi_macro)))
+ cleanup_and_exit(EXIT_SUCCESS);
+ if (!is_eoi_macro_finished)
+ began_page_in_eoi_macro = true;
+ }
+ if (last_page_number > 0 && page_number == last_page_number)
+ cleanup_and_exit(EXIT_SUCCESS);
+ if (!the_output)
+ init_output();
+ ++page_count;
+ if (have_next_page_number) {
+ page_number = next_page_number;
+ have_next_page_number = 0;
+ }
+ else if (before_first_page == 1)
+ page_number = 1;
+ else
+ page_number++;
+ // spring the top of page trap if there is one
+ vunits next_trap_pos;
+ vertical_position = -vresolution;
+ trap *next_trap = find_next_trap(&next_trap_pos);
+ vertical_position = V0;
+ high_water_mark = V0;
+ ejecting_page = 0;
+ // If before_first_page was 2, then the top of page transition was undone
+ // using eg .nr nl 0-1. See nl_reg::set_value.
+ if (before_first_page != 2)
+ the_output->begin_page(page_number, page_length);
+ before_first_page = 0;
+ nl_reg_contents = vertical_position.to_units();
+ if (vertical_position_traps_flag && next_trap != 0 && next_trap_pos == V0) {
+ truncated_space = n;
+ spring_trap(next_trap->nm);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void continue_page_eject()
+{
+ if (topdiv->get_ejecting()) {
+ if (curdiv != topdiv)
+ error("can't continue page ejection because of current diversion");
+ else if (!vertical_position_traps_flag)
+ error("can't continue page ejection because vertical position traps disabled");
+ else {
+ push_page_ejector();
+ topdiv->space(topdiv->get_page_length(), 1);
+ }
+ }
+}
+
+void top_level_diversion::set_next_page_number(int n)
+{
+ next_page_number= n;
+ have_next_page_number = 1;
+}
+
+int top_level_diversion::get_next_page_number()
+{
+ return have_next_page_number ? next_page_number : page_number + 1;
+}
+
+void top_level_diversion::set_page_length(vunits n)
+{
+ page_length = n;
+}
+
+diversion::~diversion()
+{
+}
+
+void page_offset()
+{
+ hunits n;
+ // The troff manual says that the default scaling indicator is v,
+ // but it is in fact m: v wouldn't make sense for a horizontally
+ // oriented request.
+ if (!has_arg() || !get_hunits(&n, 'm', topdiv->page_offset))
+ n = topdiv->prev_page_offset;
+ topdiv->prev_page_offset = topdiv->page_offset;
+ topdiv->page_offset = n;
+ topdiv->modified_tag.incl(MTSM_PO);
+ skip_line();
+}
+
+void page_length()
+{
+ vunits n;
+ if (has_arg() && get_vunits(&n, 'v', topdiv->get_page_length()))
+ topdiv->set_page_length(n);
+ else
+ topdiv->set_page_length(11*units_per_inch);
+ skip_line();
+}
+
+void when_request()
+{
+ vunits n;
+ if (get_vunits(&n, 'v')) {
+ symbol s = get_name();
+ if (s.is_null())
+ topdiv->remove_trap_at(n);
+ else
+ topdiv->add_trap(s, n);
+ }
+ skip_line();
+}
+
+void begin_page()
+{
+ int got_arg = 0;
+ int n = 0; /* pacify compiler */
+ if (has_arg() && get_integer(&n, topdiv->get_page_number()))
+ got_arg = 1;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (curdiv == topdiv) {
+ if (topdiv->before_first_page) {
+ if (!break_flag) {
+ if (got_arg)
+ topdiv->set_next_page_number(n);
+ if (got_arg || !topdiv->no_space_mode)
+ topdiv->begin_page();
+ }
+ else if (topdiv->no_space_mode && !got_arg)
+ topdiv->begin_page();
+ else {
+ /* Given this
+
+ .wh 0 x
+ .de x
+ .tm \\n%
+ ..
+ .bp 3
+
+ troff prints
+
+ 1
+ 3
+
+ This code makes groff do the same. */
+
+ push_page_ejector();
+ topdiv->begin_page();
+ if (got_arg)
+ topdiv->set_next_page_number(n);
+ topdiv->set_ejecting();
+ }
+ }
+ else {
+ push_page_ejector();
+ if (break_flag)
+ curenv->do_break();
+ if (got_arg)
+ topdiv->set_next_page_number(n);
+ if (!(topdiv->no_space_mode && !got_arg))
+ topdiv->set_ejecting();
+ }
+ }
+ tok.next();
+}
+
+void no_space()
+{
+ curdiv->no_space_mode = 1;
+ skip_line();
+}
+
+void restore_spacing()
+{
+ curdiv->no_space_mode = 0;
+ skip_line();
+}
+
+/* It is necessary to generate a break before reading the argument,
+because otherwise arguments using | will be wrong. But if we just
+generate a break as usual, then the line forced out may spring a trap
+and thus push a macro onto the input stack before we have had a chance
+to read the argument to the sp request. We resolve this dilemma by
+setting, before generating the break, a flag which will postpone the
+actual pushing of the macro associated with the trap sprung by the
+outputting of the line forced out by the break till after we have read
+the argument to the request. If the break did cause a trap to be
+sprung, then we don't actually do the space. */
+
+void space_request()
+{
+ postpone_traps();
+ if (break_flag)
+ curenv->do_break();
+ vunits n;
+ if (!has_arg() || !get_vunits(&n, 'v'))
+ n = curenv->get_vertical_spacing();
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (!unpostpone_traps() && !curdiv->no_space_mode)
+ curdiv->space(n);
+ else
+ // The line might have had line spacing that was truncated.
+ truncated_space += n;
+
+ tok.next();
+}
+
+void blank_line()
+{
+ curenv->do_break();
+ if (!trap_sprung_flag && !curdiv->no_space_mode)
+ curdiv->space(curenv->get_vertical_spacing());
+ else
+ truncated_space += curenv->get_vertical_spacing();
+}
+
+/* need_space might spring a trap and so we must be careful that the
+BEGIN_TRAP token is not skipped over. */
+
+void need_space()
+{
+ vunits n;
+ if (!has_arg() || !get_vunits(&n, 'v'))
+ n = curenv->get_vertical_spacing();
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ curdiv->need(n);
+ tok.next();
+}
+
+void page_number()
+{
+ int n;
+
+ // the ps4html register is set if we are using -Tps
+ // to generate images for html
+ // XXX: Yuck! Get rid of this; macro packages already test the
+ // register before invoking .pn.
+ reg *r = (reg *)register_dictionary.lookup("ps4html");
+ if (r == NULL)
+ if (has_arg() && get_integer(&n, topdiv->get_page_number()))
+ topdiv->set_next_page_number(n);
+ skip_line();
+}
+
+vunits saved_space;
+
+void save_vertical_space()
+{
+ vunits x;
+ if (!has_arg() || !get_vunits(&x, 'v'))
+ x = curenv->get_vertical_spacing();
+ if (curdiv->distance_to_next_trap() > x)
+ curdiv->space(x, 1);
+ else
+ saved_space = x;
+ skip_line();
+}
+
+void output_saved_vertical_space()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (saved_space > V0)
+ curdiv->space(saved_space, 1);
+ saved_space = V0;
+ tok.next();
+}
+
+void flush_output()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (the_output)
+ the_output->flush();
+ tok.next();
+}
+
+void macro_diversion::set_diversion_trap(symbol s, vunits n)
+{
+ diversion_trap = s;
+ diversion_trap_pos = n;
+}
+
+void macro_diversion::clear_diversion_trap()
+{
+ diversion_trap = NULL_SYMBOL;
+}
+
+void top_level_diversion::set_diversion_trap(symbol, vunits)
+{
+ error("can't set diversion trap when no current diversion");
+}
+
+void top_level_diversion::clear_diversion_trap()
+{
+ error("can't clear diversion trap when no current diversion");
+}
+
+void diversion_trap()
+{
+ vunits n;
+ if (has_arg() && get_vunits(&n, 'v')) {
+ symbol s = get_name();
+ if (!s.is_null())
+ curdiv->set_diversion_trap(s, n);
+ else
+ curdiv->clear_diversion_trap();
+ }
+ else
+ curdiv->clear_diversion_trap();
+ skip_line();
+}
+
+void change_trap()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ vunits x;
+ if (has_arg() && get_vunits(&x, 'v'))
+ topdiv->change_trap(s, x);
+ else
+ topdiv->remove_trap(s);
+ }
+ skip_line();
+}
+
+void print_traps()
+{
+ topdiv->print_traps();
+ skip_line();
+}
+
+void mark()
+{
+ symbol s = get_name();
+ if (s.is_null())
+ curdiv->marked_place = curdiv->get_vertical_position();
+ else if (curdiv == topdiv)
+ set_number_reg(s, nl_reg_contents);
+ else
+ set_number_reg(s, curdiv->get_vertical_position().to_units());
+ skip_line();
+}
+
+// This is truly bizarre. It is documented in the SQ manual.
+
+void return_request()
+{
+ vunits dist = curdiv->marked_place - curdiv->get_vertical_position();
+ if (has_arg()) {
+ if (tok.ch() == '-') {
+ tok.next();
+ vunits x;
+ if (get_vunits(&x, 'v'))
+ dist = -x;
+ }
+ else {
+ vunits x;
+ if (get_vunits(&x, 'v'))
+ dist = x >= V0 ? x - curdiv->get_vertical_position() : V0;
+ }
+ }
+ if (dist < V0)
+ curdiv->space(dist);
+ skip_line();
+}
+
+void vertical_position_traps()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ vertical_position_traps_flag = (n != 0);
+ else
+ vertical_position_traps_flag = 1;
+ skip_line();
+}
+
+class page_offset_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool page_offset_reg::get_value(units *res)
+{
+ *res = topdiv->get_page_offset().to_units();
+ return true;
+}
+
+const char *page_offset_reg::get_string()
+{
+ return i_to_a(topdiv->get_page_offset().to_units());
+}
+
+class page_length_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool page_length_reg::get_value(units *res)
+{
+ *res = topdiv->get_page_length().to_units();
+ return true;
+}
+
+const char *page_length_reg::get_string()
+{
+ return i_to_a(topdiv->get_page_length().to_units());
+}
+
+class vertical_position_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool vertical_position_reg::get_value(units *res)
+{
+ if (curdiv == topdiv && topdiv->before_first_page)
+ *res = -1;
+ else
+ *res = curdiv->get_vertical_position().to_units();
+ return true;
+}
+
+const char *vertical_position_reg::get_string()
+{
+ if (curdiv == topdiv && topdiv->before_first_page)
+ return "-1";
+ else
+ return i_to_a(curdiv->get_vertical_position().to_units());
+}
+
+class high_water_mark_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool high_water_mark_reg::get_value(units *res)
+{
+ *res = curdiv->get_high_water_mark().to_units();
+ return true;
+}
+
+const char *high_water_mark_reg::get_string()
+{
+ return i_to_a(curdiv->get_high_water_mark().to_units());
+}
+
+class distance_to_next_trap_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool distance_to_next_trap_reg::get_value(units *res)
+{
+ *res = curdiv->distance_to_next_trap().to_units();
+ return true;
+}
+
+const char *distance_to_next_trap_reg::get_string()
+{
+ return i_to_a(curdiv->distance_to_next_trap().to_units());
+}
+
+class diversion_name_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *diversion_name_reg::get_string()
+{
+ return curdiv->get_diversion_name();
+}
+
+class page_number_reg : public general_reg {
+public:
+ page_number_reg();
+ bool get_value(units *);
+ void set_value(units);
+};
+
+page_number_reg::page_number_reg()
+{
+}
+
+void page_number_reg::set_value(units n)
+{
+ topdiv->set_page_number(n);
+}
+
+bool page_number_reg::get_value(units *res)
+{
+ *res = topdiv->get_page_number();
+ return true;
+}
+
+class next_page_number_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *next_page_number_reg::get_string()
+{
+ return i_to_a(topdiv->get_next_page_number());
+}
+
+class page_ejecting_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *page_ejecting_reg::get_string()
+{
+ return i_to_a(topdiv->get_ejecting());
+}
+
+class constant_vunits_reg : public reg {
+ vunits *p;
+public:
+ constant_vunits_reg(vunits *);
+ const char *get_string();
+};
+
+constant_vunits_reg::constant_vunits_reg(vunits *q) : p(q)
+{
+}
+
+const char *constant_vunits_reg::get_string()
+{
+ return i_to_a(p->to_units());
+}
+
+class nl_reg : public variable_reg {
+public:
+ nl_reg();
+ void set_value(units);
+};
+
+nl_reg::nl_reg() : variable_reg(&nl_reg_contents)
+{
+}
+
+void nl_reg::set_value(units n)
+{
+ variable_reg::set_value(n);
+ // Setting nl to a negative value when the vertical position in
+ // the top-level diversion is 0 undoes the top of page transition,
+ // so that the header macro will be called as if the top of page
+ // transition hasn't happened. This is used by Larry Wall's
+ // wrapman program. Setting before_first_page to 2 rather than 1,
+ // tells top_level_diversion::begin_page not to call
+ // output_file::begin_page again.
+ if (n < 0 && topdiv->get_vertical_position() == V0)
+ topdiv->before_first_page = 2;
+}
+
+class no_space_mode_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool no_space_mode_reg::get_value(units *val)
+{
+ *val = curdiv->no_space_mode;
+ return true;
+}
+
+const char *no_space_mode_reg::get_string()
+{
+ return curdiv->no_space_mode ? "1" : "0";
+}
+
+void init_div_requests()
+{
+ init_request("box", box);
+ init_request("boxa", box_append);
+ init_request("bp", begin_page);
+ init_request("ch", change_trap);
+ init_request("da", divert_append);
+ init_request("di", divert);
+ init_request("dt", diversion_trap);
+ init_request("fl", flush_output);
+ init_request("mk", mark);
+ init_request("ne", need_space);
+ init_request("ns", no_space);
+ init_request("os", output_saved_vertical_space);
+ init_request("pl", page_length);
+ init_request("pn", page_number);
+ init_request("po", page_offset);
+ init_request("ptr", print_traps);
+ init_request("rs", restore_spacing);
+ init_request("rt", return_request);
+ init_request("sp", space_request);
+ init_request("sv", save_vertical_space);
+ init_request("vpt", vertical_position_traps);
+ init_request("wh", when_request);
+ register_dictionary.define(".a",
+ new readonly_register(&last_post_line_extra_space));
+ register_dictionary.define(".d", new vertical_position_reg);
+ register_dictionary.define(".h", new high_water_mark_reg);
+ register_dictionary.define(".ne",
+ new constant_vunits_reg(&needed_space));
+ register_dictionary.define(".ns", new no_space_mode_reg);
+ register_dictionary.define(".o", new page_offset_reg);
+ register_dictionary.define(".p", new page_length_reg);
+ register_dictionary.define(".pe", new page_ejecting_reg);
+ register_dictionary.define(".pn", new next_page_number_reg);
+ register_dictionary.define(".t", new distance_to_next_trap_reg);
+ register_dictionary.define(".trunc",
+ new constant_vunits_reg(&truncated_space));
+ register_dictionary.define(".vpt",
+ new readonly_register(&vertical_position_traps_flag));
+ register_dictionary.define(".z", new diversion_name_reg);
+ register_dictionary.define("dl", new variable_reg(&dl_reg_contents));
+ register_dictionary.define("dn", new variable_reg(&dn_reg_contents));
+ register_dictionary.define("nl", new nl_reg);
+ register_dictionary.define("%", new page_number_reg);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/div.h b/src/roff/troff/div.h
new file mode 100644
index 0000000..15c7807
--- /dev/null
+++ b/src/roff/troff/div.h
@@ -0,0 +1,175 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+void do_divert(int append, int boxing);
+void end_diversions();
+void page_offset();
+
+class diversion {
+ friend void do_divert(int append, int boxing);
+ friend void end_diversions();
+ diversion *prev;
+ node *saved_line;
+ hunits saved_width_total;
+ int saved_space_total;
+ hunits saved_saved_indent;
+ hunits saved_target_text_length;
+ int saved_prev_line_interrupted;
+protected:
+ symbol nm;
+ vunits vertical_position;
+ vunits high_water_mark;
+public:
+ int any_chars_added;
+ int no_space_mode;
+ int needs_push;
+ int saved_seen_break;
+ int saved_seen_space;
+ int saved_seen_eol;
+ int saved_suppress_next_eol;
+ state_set modified_tag;
+ vunits marked_place;
+ diversion(symbol s = NULL_SYMBOL);
+ virtual ~diversion();
+ virtual void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width) = 0;
+ virtual void transparent_output(unsigned char) = 0;
+ virtual void transparent_output(node *) = 0;
+ virtual void space(vunits distance, int forced = 0) = 0;
+#ifdef COLUMN
+ virtual void vjustify(symbol) = 0;
+#endif /* COLUMN */
+ vunits get_vertical_position() { return vertical_position; }
+ vunits get_high_water_mark() { return high_water_mark; }
+ virtual vunits distance_to_next_trap() = 0;
+ void need(vunits);
+ const char *get_diversion_name() { return nm.contents(); }
+ virtual void set_diversion_trap(symbol, vunits) = 0;
+ virtual void clear_diversion_trap() = 0;
+ virtual void copy_file(const char *filename) = 0;
+ virtual int is_diversion() = 0;
+};
+
+class macro;
+
+class macro_diversion : public diversion {
+ macro *mac;
+ hunits max_width;
+ symbol diversion_trap;
+ vunits diversion_trap_pos;
+public:
+ macro_diversion(symbol, int);
+ ~macro_diversion();
+ void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width);
+ void transparent_output(unsigned char);
+ void transparent_output(node *);
+ void space(vunits distance, int forced = 0);
+#ifdef COLUMN
+ void vjustify(symbol);
+#endif /* COLUMN */
+ vunits distance_to_next_trap();
+ void set_diversion_trap(symbol, vunits);
+ void clear_diversion_trap();
+ void copy_file(const char *filename);
+ int is_diversion() { return 1; }
+};
+
+struct trap {
+ trap *next;
+ vunits position;
+ symbol nm;
+ trap(symbol, vunits, trap *);
+};
+
+class output_file;
+
+class top_level_diversion : public diversion {
+ int page_number;
+ int page_count;
+ int last_page_count;
+ vunits page_length;
+ hunits prev_page_offset;
+ hunits page_offset;
+ trap *page_trap_list;
+ trap *find_next_trap(vunits *);
+ int have_next_page_number;
+ int next_page_number;
+ int ejecting_page; // Is the current page being ejected?
+public:
+ int before_first_page;
+ top_level_diversion();
+ void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width);
+ void transparent_output(unsigned char);
+ void transparent_output(node *);
+ void space(vunits distance, int forced = 0);
+#ifdef COLUMN
+ void vjustify(symbol);
+#endif /* COLUMN */
+ hunits get_page_offset() { return page_offset; }
+ vunits get_page_length() { return page_length; }
+ vunits distance_to_next_trap();
+ void add_trap(symbol nm, vunits pos);
+ void change_trap(symbol nm, vunits pos);
+ void remove_trap(symbol);
+ void remove_trap_at(vunits pos);
+ void print_traps();
+ int get_page_count() { return page_count; }
+ int get_page_number() { return page_number; }
+ int get_next_page_number();
+ void set_page_number(int n) { page_number = n; }
+ int begin_page(vunits = V0);
+ void set_next_page_number(int);
+ void set_page_length(vunits);
+ void copy_file(const char *filename);
+ int get_ejecting() { return ejecting_page; }
+ void set_ejecting() { ejecting_page = 1; }
+ friend void page_offset();
+ void set_diversion_trap(symbol, vunits);
+ void clear_diversion_trap();
+ void set_last_page() { last_page_count = page_count; }
+ int is_diversion() { return 0; }
+};
+
+extern top_level_diversion *topdiv;
+extern diversion *curdiv;
+
+extern bool is_exit_underway;
+extern bool is_eoi_macro_finished;
+extern bool seen_last_page_ejector;
+extern int last_page_number;
+
+void spring_trap(symbol); // implemented by input.c
+extern int trap_sprung_flag;
+void postpone_traps();
+int unpostpone_traps();
+
+void push_page_ejector();
+void continue_page_eject();
+void handle_first_page_transition();
+void blank_line();
+void begin_page();
+
+extern void cleanup_and_exit(int);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/env.cpp b/src/roff/troff/env.cpp
new file mode 100644
index 0000000..9f00284
--- /dev/null
+++ b/src/roff/troff/env.cpp
@@ -0,0 +1,4138 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "troff.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "font.h"
+#include "charinfo.h"
+#include "macropath.h"
+#include "input.h"
+#include <math.h>
+
+symbol default_family("T");
+
+enum { ADJUST_LEFT = 0,
+ ADJUST_BOTH = 1,
+ ADJUST_CENTER = 3,
+ ADJUST_RIGHT = 5,
+ ADJUST_MAX = 5
+};
+
+enum {
+ // Not all combinations are valid; see hyphenate_request() below.
+ HYPHEN_NONE = 0,
+ HYPHEN_DEFAULT = 1,
+ HYPHEN_NOT_LAST_LINE = 2,
+ HYPHEN_NOT_LAST_CHARS = 4,
+ HYPHEN_NOT_FIRST_CHARS = 8,
+ HYPHEN_LAST_CHAR = 16,
+ HYPHEN_FIRST_CHAR = 32,
+ HYPHEN_MAX = 63
+};
+
+struct env_list_node {
+ environment *env;
+ env_list_node *next;
+ env_list_node(environment *e, env_list_node *p) : env(e), next(p) {}
+};
+
+env_list_node *env_stack;
+dictionary env_dictionary(10);
+environment *curenv;
+static int next_line_number = 0;
+extern int suppress_push;
+extern statem *get_diversion_state();
+
+charinfo *field_delimiter_char;
+charinfo *padding_indicator_char;
+
+int translate_space_to_dummy = 0;
+
+class pending_output_line {
+ node *nd;
+ int no_fill;
+ int was_centered;
+ vunits vs;
+ vunits post_vs;
+ hunits width;
+#ifdef WIDOW_CONTROL
+ int last_line; // Is it the last line of the paragraph?
+#endif /* WIDOW_CONTROL */
+public:
+ pending_output_line *next;
+
+ pending_output_line(node *, int, vunits, vunits, hunits, int,
+ pending_output_line * = 0);
+ ~pending_output_line();
+ int output();
+
+#ifdef WIDOW_CONTROL
+ friend void environment::mark_last_line();
+ friend void environment::output(node *, int, vunits, vunits, hunits, int);
+#endif /* WIDOW_CONTROL */
+};
+
+pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
+ hunits w, int ce,
+ pending_output_line *p)
+: nd(n), no_fill(nf), was_centered(ce), vs(v), post_vs(pv), width(w),
+#ifdef WIDOW_CONTROL
+ last_line(0),
+#endif /* WIDOW_CONTROL */
+ next(p)
+{
+}
+
+pending_output_line::~pending_output_line()
+{
+ delete_node_list(nd);
+}
+
+int pending_output_line::output()
+{
+ if (trap_sprung_flag)
+ return 0;
+#ifdef WIDOW_CONTROL
+ if (next && next->last_line && !no_fill) {
+ curdiv->need(vs + post_vs + vunits(vresolution));
+ if (trap_sprung_flag) {
+ next->last_line = 0; // Try to avoid infinite loops.
+ return 0;
+ }
+ }
+#endif
+ curenv->construct_format_state(nd, was_centered, !no_fill);
+ curdiv->output(nd, no_fill, vs, post_vs, width);
+ nd = 0;
+ return 1;
+}
+
+void environment::output(node *nd, int no_fill_flag,
+ vunits vs, vunits post_vs,
+ hunits width, int was_centered)
+{
+#ifdef WIDOW_CONTROL
+ while (pending_lines) {
+ if (widow_control && !pending_lines->no_fill && !pending_lines->next)
+ break;
+ if (!pending_lines->output())
+ break;
+ pending_output_line *tem = pending_lines;
+ pending_lines = pending_lines->next;
+ delete tem;
+ }
+#else /* WIDOW_CONTROL */
+ output_pending_lines();
+#endif /* WIDOW_CONTROL */
+ if (!trap_sprung_flag && !pending_lines
+#ifdef WIDOW_CONTROL
+ && (!widow_control || no_fill_flag)
+#endif /* WIDOW_CONTROL */
+ ) {
+ curenv->construct_format_state(nd, was_centered, !no_fill_flag);
+ curdiv->output(nd, no_fill_flag, vs, post_vs, width);
+ } else {
+ pending_output_line **p;
+ for (p = &pending_lines; *p; p = &(*p)->next)
+ ;
+ *p = new pending_output_line(nd, no_fill_flag, vs, post_vs, width,
+ was_centered);
+ }
+}
+
+// a line from .tl goes at the head of the queue
+
+void environment::output_title(node *nd, int no_fill_flag,
+ vunits vs, vunits post_vs,
+ hunits width)
+{
+ if (!trap_sprung_flag)
+ curdiv->output(nd, no_fill_flag, vs, post_vs, width);
+ else
+ pending_lines = new pending_output_line(nd, no_fill_flag, vs, post_vs,
+ width, 0, pending_lines);
+}
+
+void environment::output_pending_lines()
+{
+ while (pending_lines && pending_lines->output()) {
+ pending_output_line *tem = pending_lines;
+ pending_lines = pending_lines->next;
+ delete tem;
+ }
+}
+
+#ifdef WIDOW_CONTROL
+
+void environment::mark_last_line()
+{
+ if (!widow_control || !pending_lines)
+ return;
+ pending_output_line *p;
+ for (p = pending_lines; p->next; p = p->next)
+ ;
+ if (!p->no_fill)
+ p->last_line = 1;
+}
+
+void widow_control_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->widow_control = n != 0;
+ else
+ curenv->widow_control = 1;
+ skip_line();
+}
+
+#endif /* WIDOW_CONTROL */
+
+/* font_size functions */
+
+size_range *font_size::size_table = 0;
+int font_size::nranges = 0;
+
+extern "C" {
+
+int compare_ranges(const void *p1, const void *p2)
+{
+ return ((size_range *)p1)->min - ((size_range *)p2)->min;
+}
+
+}
+
+void font_size::init_size_table(int *sizes)
+{
+ nranges = 0;
+ while (sizes[nranges*2] != 0)
+ nranges++;
+ assert(nranges > 0);
+ size_table = new size_range[nranges];
+ for (int i = 0; i < nranges; i++) {
+ size_table[i].min = sizes[i*2];
+ size_table[i].max = sizes[i*2 + 1];
+ }
+ qsort(size_table, nranges, sizeof(size_range), compare_ranges);
+}
+
+font_size::font_size(int sp)
+{
+ for (int i = 0; i < nranges; i++) {
+ if (sp < size_table[i].min) {
+ if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
+ p = size_table[i - 1].max;
+ else
+ p = size_table[i].min;
+ return;
+ }
+ if (sp <= size_table[i].max) {
+ p = sp;
+ return;
+ }
+ }
+ p = size_table[nranges - 1].max;
+}
+
+int font_size::to_units()
+{
+ return scale(p, units_per_inch, sizescale*72);
+}
+
+// we can't do this in a static constructor because various dictionaries
+// have to get initialized first
+
+static symbol default_environment_name("0");
+
+void init_environments()
+{
+ curenv = new environment(default_environment_name);
+ (void)env_dictionary.lookup(default_environment_name, curenv);
+}
+
+void tab_character()
+{
+ curenv->tab_char = get_optional_char();
+ skip_line();
+}
+
+void leader_character()
+{
+ curenv->leader_char = get_optional_char();
+ skip_line();
+}
+
+void environment::add_char(charinfo *ci)
+{
+ int s;
+ node *gc_np = 0;
+ if (interrupted)
+ ;
+ // don't allow fields in dummy environments
+ else if (ci == field_delimiter_char && !dummy) {
+ if (current_field)
+ wrap_up_field();
+ else
+ start_field();
+ }
+ else if (current_field && ci == padding_indicator_char)
+ add_padding();
+ else if (current_tab) {
+ if (tab_contents == 0)
+ tab_contents = new line_start_node;
+ if (ci != hyphen_indicator_char)
+ tab_contents = tab_contents->add_char(ci, this, &tab_width, &s, &gc_np);
+ else
+ tab_contents = tab_contents->add_discretionary_hyphen();
+ }
+ else {
+ if (line == 0)
+ start_line();
+#if 0
+ fprintf(stderr, "current line is\n");
+ line->debug_node_list();
+#endif
+ if (ci != hyphen_indicator_char)
+ line = line->add_char(ci, this, &width_total, &space_total, &gc_np);
+ else
+ line = line->add_discretionary_hyphen();
+ }
+#if 0
+ fprintf(stderr, "now after we have added character the line is\n");
+ line->debug_node_list();
+#endif
+ if ((!suppress_push) && gc_np) {
+ if (gc_np && (gc_np->state == 0)) {
+ gc_np->state = construct_state(0);
+ gc_np->push_state = get_diversion_state();
+ }
+ else if (line && (line->state == 0)) {
+ line->state = construct_state(0);
+ line->push_state = get_diversion_state();
+ }
+ }
+#if 0
+ fprintf(stderr, "now we have possibly added the state the line is\n");
+ line->debug_node_list();
+#endif
+}
+
+node *environment::make_char_node(charinfo *ci)
+{
+ return make_node(ci, this);
+}
+
+void environment::add_node(node *n)
+{
+ if (n == 0)
+ return;
+ if (!suppress_push) {
+ if (n->is_special && n->state == NULL)
+ n->state = construct_state(0);
+ n->push_state = get_diversion_state();
+ }
+
+ if (current_tab || current_field)
+ n->freeze_space();
+ if (interrupted) {
+ delete n;
+ }
+ else if (current_tab) {
+ n->next = tab_contents;
+ tab_contents = n;
+ tab_width += n->width();
+ }
+ else {
+ if (line == 0) {
+ if (discarding && n->discardable()) {
+ // XXX possibly: input_line_start -= n->width();
+ delete n;
+ return;
+ }
+ start_line();
+ }
+ width_total += n->width();
+ space_total += n->nspaces();
+ n->next = line;
+ line = n;
+ construct_new_line_state(line);
+ }
+}
+
+void environment::add_hyphen_indicator()
+{
+ if (current_tab || interrupted || current_field
+ || hyphen_indicator_char != 0)
+ return;
+ if (line == 0)
+ start_line();
+ line = line->add_discretionary_hyphen();
+}
+
+int environment::get_hyphenation_flags()
+{
+ return hyphenation_flags;
+}
+
+int environment::get_hyphen_line_max()
+{
+ return hyphen_line_max;
+}
+
+int environment::get_hyphen_line_count()
+{
+ return hyphen_line_count;
+}
+
+int environment::get_center_lines()
+{
+ return center_lines;
+}
+
+int environment::get_right_justify_lines()
+{
+ return right_justify_lines;
+}
+
+int environment::get_no_number_count()
+{
+ return no_number_count;
+}
+
+void environment::add_italic_correction()
+{
+ if (current_tab) {
+ if (tab_contents)
+ tab_contents = tab_contents->add_italic_correction(&tab_width);
+ }
+ else if (line)
+ line = line->add_italic_correction(&width_total);
+}
+
+void environment::space_newline()
+{
+ assert(!current_tab && !current_field);
+ if (interrupted)
+ return;
+ hunits x = H0;
+ hunits sw = env_space_width(this);
+ hunits ssw = env_sentence_space_width(this);
+ if (!translate_space_to_dummy) {
+ x = sw;
+ if (node_list_ends_sentence(line) == 1)
+ x += ssw;
+ }
+ width_list *w = new width_list(sw, ssw);
+ if (node_list_ends_sentence(line) == 1)
+ w->next = new width_list(sw, ssw);
+ if (line != 0 && line->merge_space(x, sw, ssw)) {
+ width_total += x;
+ return;
+ }
+ add_node(new word_space_node(x, get_fill_color(), w));
+ possibly_break_line(0, spread_flag);
+ spread_flag = 0;
+}
+
+void environment::space()
+{
+ space(env_space_width(this), env_sentence_space_width(this));
+}
+
+void environment::space(hunits space_width, hunits sentence_space_width)
+{
+ if (interrupted)
+ return;
+ if (current_field && padding_indicator_char == 0) {
+ add_padding();
+ return;
+ }
+ hunits x = translate_space_to_dummy ? H0 : space_width;
+ node *p = current_tab ? tab_contents : line;
+ hunits *tp = current_tab ? &tab_width : &width_total;
+ if (p && p->nspaces() == 1 && p->width() == x
+ && node_list_ends_sentence(p->next) == 1) {
+ hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
+ if (p->merge_space(xx, space_width, sentence_space_width)) {
+ *tp += xx;
+ return;
+ }
+ }
+ if (p && p->merge_space(x, space_width, sentence_space_width)) {
+ *tp += x;
+ return;
+ }
+ add_node(new word_space_node(x,
+ get_fill_color(),
+ new width_list(space_width,
+ sentence_space_width)));
+ possibly_break_line(0, spread_flag);
+ spread_flag = 0;
+}
+
+static node *do_underline_special(bool do_underline_spaces)
+{
+ macro m;
+ m.append_str("x u ");
+ m.append(do_underline_spaces ? '1' : '0');
+ return new special_node(m, 1);
+}
+
+bool environment::set_font(symbol nm)
+{
+ if (interrupted) {
+ warning(WARN_FONT, "ignoring font selection on interrupted line");
+ return true; // "no operation" is successful
+ }
+ if (nm == symbol("P") || nm.is_empty()) {
+ if (family->make_definite(prev_fontno) < 0)
+ return false;
+ int tem = fontno;
+ fontno = prev_fontno;
+ prev_fontno = tem;
+ }
+ else {
+ prev_fontno = fontno;
+ int n = symbol_fontno(nm);
+ if (n < 0) {
+ n = next_available_font_position();
+ if (!mount_font(n, nm))
+ return false;
+ }
+ if (family->make_definite(n) < 0)
+ return false;
+ fontno = n;
+ }
+ if (underline_spaces && fontno != prev_fontno) {
+ if (fontno == get_underline_fontno())
+ add_node(do_underline_special(true));
+ if (prev_fontno == get_underline_fontno())
+ add_node(do_underline_special(false));
+ }
+ return true;
+}
+
+bool environment::set_font(int n)
+{
+ if (interrupted)
+ return false;
+ if (is_good_fontno(n)) {
+ prev_fontno = fontno;
+ fontno = n;
+ }
+ else {
+ warning(WARN_FONT, "no font mounted at position %1", n);
+ return false;
+ }
+ return true;
+}
+
+void environment::set_family(symbol fam)
+{
+ if (interrupted)
+ return;
+ if (fam.is_null() || fam.is_empty()) {
+ int previous_mounting_position = prev_family->make_definite(fontno);
+ assert(previous_mounting_position >= 0);
+ if (previous_mounting_position < 0)
+ return;
+ font_family *tem = family;
+ family = prev_family;
+ prev_family = tem;
+ }
+ else {
+ font_family *f = lookup_family(fam);
+ // If the family isn't already in the dictionary, looking it up will
+ // create an entry for it. That doesn't mean that it will be
+ // resolvable to a real font when combined with a style name.
+ assert((f != 0 /* nullptr */) &&
+ (0 != "font family dictionary lookup"));
+ if (0 /* nullptr */ == f)
+ return;
+ if (f->make_definite(fontno) < 0) {
+ error("no font family named '%1' exists", fam.contents());
+ return;
+ }
+ prev_family = family;
+ family = f;
+ }
+}
+
+void environment::set_size(int n)
+{
+ if (interrupted)
+ return;
+ if (n == 0) {
+ font_size temp = prev_size;
+ prev_size = size;
+ size = temp;
+ int temp2 = prev_requested_size;
+ prev_requested_size = requested_size;
+ requested_size = temp2;
+ }
+ else {
+ prev_size = size;
+ size = font_size(n);
+ prev_requested_size = requested_size;
+ requested_size = n;
+ }
+}
+
+void environment::set_char_height(int n)
+{
+ if (interrupted)
+ return;
+ if (n == requested_size || n <= 0)
+ char_height = 0;
+ else
+ char_height = n;
+}
+
+void environment::set_char_slant(int n)
+{
+ if (interrupted)
+ return;
+ char_slant = n;
+}
+
+color *environment::get_prev_glyph_color()
+{
+ return prev_glyph_color;
+}
+
+color *environment::get_glyph_color()
+{
+ return glyph_color;
+}
+
+color *environment::get_prev_fill_color()
+{
+ return prev_fill_color;
+}
+
+color *environment::get_fill_color()
+{
+ return fill_color;
+}
+
+void environment::set_glyph_color(color *c)
+{
+ if (interrupted)
+ return;
+ curenv->prev_glyph_color = curenv->glyph_color;
+ curenv->glyph_color = c;
+}
+
+void environment::set_fill_color(color *c)
+{
+ if (interrupted)
+ return;
+ curenv->prev_fill_color = curenv->fill_color;
+ curenv->fill_color = c;
+}
+
+environment::environment(symbol nm)
+: dummy(0),
+ prev_line_length((units_per_inch*13)/2),
+ line_length((units_per_inch*13)/2),
+ prev_title_length((units_per_inch*13)/2),
+ title_length((units_per_inch*13)/2),
+ prev_size(sizescale*10),
+ size(sizescale*10),
+ requested_size(sizescale*10),
+ prev_requested_size(sizescale*10),
+ char_height(0),
+ char_slant(0),
+ space_size(12),
+ sentence_space_size(12),
+ adjust_mode(ADJUST_BOTH),
+ fill(1),
+ interrupted(0),
+ prev_line_interrupted(0),
+ center_lines(0),
+ right_justify_lines(0),
+ prev_vertical_spacing(points_to_units(12)),
+ vertical_spacing(points_to_units(12)),
+ prev_post_vertical_spacing(0),
+ post_vertical_spacing(0),
+ prev_line_spacing(1),
+ line_spacing(1),
+ prev_indent(0),
+ indent(0),
+ temporary_indent(0),
+ have_temporary_indent(0),
+ underline_lines(0),
+ underline_spaces(0),
+ input_trap_count(0),
+ continued_input_trap(0),
+ line(0),
+ prev_text_length(0),
+ width_total(0),
+ space_total(0),
+ input_line_start(0),
+ line_tabs(0),
+ current_tab(TAB_NONE),
+ leader_node(0),
+ tab_char(0),
+ leader_char(charset_table['.']),
+ current_field(0),
+ discarding(0),
+ spread_flag(0),
+ margin_character_flags(0),
+ margin_character_node(0),
+ margin_character_distance(points_to_units(10)),
+ numbering_nodes(0),
+ number_text_separation(1),
+ line_number_indent(0),
+ line_number_multiple(1),
+ no_number_count(0),
+ hyphenation_flags(1),
+ hyphen_line_count(0),
+ hyphen_line_max(-1),
+ hyphenation_space(H0),
+ hyphenation_margin(H0),
+ composite(0),
+ pending_lines(0),
+#ifdef WIDOW_CONTROL
+ widow_control(0),
+#endif /* WIDOW_CONTROL */
+ glyph_color(&default_color),
+ prev_glyph_color(&default_color),
+ fill_color(&default_color),
+ prev_fill_color(&default_color),
+ seen_space(0),
+ seen_eol(0),
+ suppress_next_eol(0),
+ seen_break(0),
+ tabs(units_per_inch/2, TAB_LEFT),
+ name(nm),
+ control_char('.'),
+ no_break_control_char('\''),
+ hyphen_indicator_char(0)
+{
+ prev_family = family = lookup_family(default_family);
+ prev_fontno = fontno = 1;
+ if (!is_good_fontno(1))
+ fatal("font mounted at position 1 is not valid");
+ if (family->make_definite(1) < 0)
+ fatal("invalid default font family '%1'",
+ default_family.contents());
+ prev_fontno = fontno;
+}
+
+environment::environment(const environment *e)
+: dummy(1),
+ prev_line_length(e->prev_line_length),
+ line_length(e->line_length),
+ prev_title_length(e->prev_title_length),
+ title_length(e->title_length),
+ prev_size(e->prev_size),
+ size(e->size),
+ requested_size(e->requested_size),
+ prev_requested_size(e->prev_requested_size),
+ char_height(e->char_height),
+ char_slant(e->char_slant),
+ prev_fontno(e->prev_fontno),
+ fontno(e->fontno),
+ prev_family(e->prev_family),
+ family(e->family),
+ space_size(e->space_size),
+ sentence_space_size(e->sentence_space_size),
+ adjust_mode(e->adjust_mode),
+ fill(e->fill),
+ interrupted(0),
+ prev_line_interrupted(0),
+ center_lines(0),
+ right_justify_lines(0),
+ prev_vertical_spacing(e->prev_vertical_spacing),
+ vertical_spacing(e->vertical_spacing),
+ prev_post_vertical_spacing(e->prev_post_vertical_spacing),
+ post_vertical_spacing(e->post_vertical_spacing),
+ prev_line_spacing(e->prev_line_spacing),
+ line_spacing(e->line_spacing),
+ prev_indent(e->prev_indent),
+ indent(e->indent),
+ temporary_indent(0),
+ have_temporary_indent(0),
+ underline_lines(0),
+ underline_spaces(0),
+ input_trap_count(0),
+ continued_input_trap(0),
+ line(0),
+ prev_text_length(e->prev_text_length),
+ width_total(0),
+ space_total(0),
+ input_line_start(0),
+ line_tabs(e->line_tabs),
+ current_tab(TAB_NONE),
+ leader_node(0),
+ tab_char(e->tab_char),
+ leader_char(e->leader_char),
+ current_field(0),
+ discarding(0),
+ spread_flag(0),
+ margin_character_flags(e->margin_character_flags),
+ margin_character_node(e->margin_character_node),
+ margin_character_distance(e->margin_character_distance),
+ numbering_nodes(0),
+ number_text_separation(e->number_text_separation),
+ line_number_indent(e->line_number_indent),
+ line_number_multiple(e->line_number_multiple),
+ no_number_count(e->no_number_count),
+ hyphenation_flags(e->hyphenation_flags),
+ hyphen_line_count(0),
+ hyphen_line_max(e->hyphen_line_max),
+ hyphenation_space(e->hyphenation_space),
+ hyphenation_margin(e->hyphenation_margin),
+ composite(0),
+ pending_lines(0),
+#ifdef WIDOW_CONTROL
+ widow_control(e->widow_control),
+#endif /* WIDOW_CONTROL */
+ glyph_color(e->glyph_color),
+ prev_glyph_color(e->prev_glyph_color),
+ fill_color(e->fill_color),
+ prev_fill_color(e->prev_fill_color),
+ seen_space(e->seen_space),
+ seen_eol(e->seen_eol),
+ suppress_next_eol(e->suppress_next_eol),
+ seen_break(e->seen_break),
+ tabs(e->tabs),
+ name(e->name), // so that, e.g., '.if "\n[.ev]"0"' works
+ control_char(e->control_char),
+ no_break_control_char(e->no_break_control_char),
+ hyphen_indicator_char(e->hyphen_indicator_char)
+{
+}
+
+void environment::copy(const environment *e)
+{
+ prev_line_length = e->prev_line_length;
+ line_length = e->line_length;
+ prev_title_length = e->prev_title_length;
+ title_length = e->title_length;
+ prev_size = e->prev_size;
+ size = e->size;
+ prev_requested_size = e->prev_requested_size;
+ requested_size = e->requested_size;
+ char_height = e->char_height;
+ char_slant = e->char_slant;
+ space_size = e->space_size;
+ sentence_space_size = e->sentence_space_size;
+ adjust_mode = e->adjust_mode;
+ fill = e->fill;
+ interrupted = 0;
+ prev_line_interrupted = 0;
+ center_lines = 0;
+ right_justify_lines = 0;
+ prev_vertical_spacing = e->prev_vertical_spacing;
+ vertical_spacing = e->vertical_spacing;
+ prev_post_vertical_spacing = e->prev_post_vertical_spacing,
+ post_vertical_spacing = e->post_vertical_spacing,
+ prev_line_spacing = e->prev_line_spacing;
+ line_spacing = e->line_spacing;
+ prev_indent = e->prev_indent;
+ indent = e->indent;
+ have_temporary_indent = 0;
+ temporary_indent = 0;
+ underline_lines = 0;
+ underline_spaces = 0;
+ input_trap_count = 0;
+ continued_input_trap = 0;
+ prev_text_length = e->prev_text_length;
+ width_total = 0;
+ space_total = 0;
+ input_line_start = 0;
+ control_char = e->control_char;
+ no_break_control_char = e->no_break_control_char;
+ hyphen_indicator_char = e->hyphen_indicator_char;
+ spread_flag = 0;
+ line = 0;
+ pending_lines = 0;
+ discarding = 0;
+ tabs = e->tabs;
+ line_tabs = e->line_tabs;
+ current_tab = TAB_NONE;
+ current_field = 0;
+ margin_character_flags = e->margin_character_flags;
+ if (e->margin_character_node)
+ margin_character_node = e->margin_character_node->copy();
+ margin_character_distance = e->margin_character_distance;
+ numbering_nodes = 0;
+ number_text_separation = e->number_text_separation;
+ line_number_multiple = e->line_number_multiple;
+ line_number_indent = e->line_number_indent;
+ no_number_count = e->no_number_count;
+ tab_char = e->tab_char;
+ leader_char = e->leader_char;
+ hyphenation_flags = e->hyphenation_flags;
+ fontno = e->fontno;
+ prev_fontno = e->prev_fontno;
+ dummy = e->dummy;
+ family = e->family;
+ prev_family = e->prev_family;
+ leader_node = 0;
+#ifdef WIDOW_CONTROL
+ widow_control = e->widow_control;
+#endif /* WIDOW_CONTROL */
+ hyphen_line_max = e->hyphen_line_max;
+ hyphen_line_count = 0;
+ hyphenation_space = e->hyphenation_space;
+ hyphenation_margin = e->hyphenation_margin;
+ composite = 0;
+ glyph_color= e->glyph_color;
+ prev_glyph_color = e->prev_glyph_color;
+ fill_color = e->fill_color;
+ prev_fill_color = e->prev_fill_color;
+}
+
+environment::~environment()
+{
+ delete leader_node;
+ delete_node_list(line);
+ delete_node_list(numbering_nodes);
+}
+
+hunits environment::get_input_line_position()
+{
+ hunits n;
+ if (line == 0)
+ n = -input_line_start;
+ else
+ n = width_total - input_line_start;
+ if (current_tab)
+ n += tab_width;
+ return n;
+}
+
+void environment::set_input_line_position(hunits n)
+{
+ input_line_start = line == 0 ? -n : width_total - n;
+ if (current_tab)
+ input_line_start += tab_width;
+}
+
+hunits environment::get_line_length()
+{
+ return line_length;
+}
+
+hunits environment::get_saved_line_length()
+{
+ if (line)
+ return target_text_length + saved_indent;
+ else
+ return line_length;
+}
+
+vunits environment::get_vertical_spacing()
+{
+ return vertical_spacing;
+}
+
+vunits environment::get_post_vertical_spacing()
+{
+ return post_vertical_spacing;
+}
+
+int environment::get_line_spacing()
+{
+ return line_spacing;
+}
+
+vunits environment::total_post_vertical_spacing()
+{
+ vunits tem(post_vertical_spacing);
+ if (line_spacing > 1)
+ tem += (line_spacing - 1)*vertical_spacing;
+ return tem;
+}
+
+int environment::get_bold()
+{
+ return get_bold_fontno(fontno);
+}
+
+hunits environment::get_digit_width()
+{
+ return env_digit_width(this);
+}
+
+int environment::get_adjust_mode()
+{
+ return adjust_mode;
+}
+
+int environment::get_fill()
+{
+ return fill;
+}
+
+hunits environment::get_indent()
+{
+ return indent;
+}
+
+hunits environment::get_saved_indent()
+{
+ if (line)
+ return saved_indent;
+ else if (have_temporary_indent)
+ return temporary_indent;
+ else
+ return indent;
+}
+
+hunits environment::get_temporary_indent()
+{
+ return temporary_indent;
+}
+
+hunits environment::get_title_length()
+{
+ return title_length;
+}
+
+node *environment::get_prev_char()
+{
+ for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
+ node *last = n->last_char_node();
+ if (last)
+ return last;
+ }
+ return 0;
+}
+
+hunits environment::get_prev_char_width()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return H0;
+ return last->width();
+}
+
+hunits environment::get_prev_char_skew()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return H0;
+ return last->skew();
+}
+
+vunits environment::get_prev_char_height()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return V0;
+ vunits min, max;
+ last->vertical_extent(&min, &max);
+ return -min;
+}
+
+vunits environment::get_prev_char_depth()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return V0;
+ vunits min, max;
+ last->vertical_extent(&min, &max);
+ return max;
+}
+
+hunits environment::get_text_length()
+{
+ hunits n = line == 0 ? H0 : width_total;
+ if (current_tab)
+ n += tab_width;
+ return n;
+}
+
+hunits environment::get_prev_text_length()
+{
+ return prev_text_length;
+}
+
+
+static int sb_reg_contents = 0;
+static int st_reg_contents = 0;
+static int ct_reg_contents = 0;
+static int rsb_reg_contents = 0;
+static int rst_reg_contents = 0;
+static int skw_reg_contents = 0;
+static int ssc_reg_contents = 0;
+
+void environment::width_registers()
+{
+ // this is used to implement \w; it sets the st, sb, ct registers
+ vunits min = 0, max = 0, cur = 0;
+ int character_type = 0;
+ ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
+ skw_reg_contents = line ? line->skew().to_units() : 0;
+ line = reverse_node_list(line);
+ vunits real_min = V0;
+ vunits real_max = V0;
+ vunits v1, v2;
+ for (node *tem = line; tem; tem = tem->next) {
+ tem->vertical_extent(&v1, &v2);
+ v1 += cur;
+ if (v1 < real_min)
+ real_min = v1;
+ v2 += cur;
+ if (v2 > real_max)
+ real_max = v2;
+ if ((cur += tem->vertical_width()) < min)
+ min = cur;
+ else if (cur > max)
+ max = cur;
+ character_type |= tem->character_type();
+ }
+ line = reverse_node_list(line);
+ st_reg_contents = -min.to_units();
+ sb_reg_contents = -max.to_units();
+ rst_reg_contents = -real_min.to_units();
+ rsb_reg_contents = -real_max.to_units();
+ ct_reg_contents = character_type;
+}
+
+node *environment::extract_output_line()
+{
+ if (current_tab)
+ wrap_up_tab();
+ node *n = line;
+ line = 0;
+ return n;
+}
+
+/* environment related requests */
+
+void environment_switch()
+{
+ if (curenv->is_dummy()) {
+ error("cannot switch out of dummy environment");
+ }
+ else {
+ symbol nm = get_long_name();
+ if (nm.is_null()) {
+ if (env_stack == 0)
+ error("environment stack underflow");
+ else {
+ int seen_space = curenv->seen_space;
+ int seen_eol = curenv->seen_eol;
+ int suppress_next_eol = curenv->suppress_next_eol;
+ curenv = env_stack->env;
+ curenv->seen_space = seen_space;
+ curenv->seen_eol = seen_eol;
+ curenv->suppress_next_eol = suppress_next_eol;
+ env_list_node *tem = env_stack;
+ env_stack = env_stack->next;
+ delete tem;
+ }
+ }
+ else {
+ environment *e = (environment *)env_dictionary.lookup(nm);
+ if (!e) {
+ e = new environment(nm);
+ (void)env_dictionary.lookup(nm, e);
+ }
+ env_stack = new env_list_node(curenv, env_stack);
+ curenv = e;
+ }
+ }
+ skip_line();
+}
+
+void environment_copy()
+{
+ environment *e=0;
+ tok.skip();
+ symbol nm = get_long_name();
+ if (nm.is_null()) {
+ error("no environment specified to copy from");
+ }
+ else {
+ e = (environment *)env_dictionary.lookup(nm);
+ if (e)
+ curenv->copy(e);
+ else
+ error("cannot copy from nonexistent environment '%1'",
+ nm.contents());
+ }
+ skip_line();
+}
+
+void fill_color_change()
+{
+ symbol s = get_name();
+ if (s.is_null())
+ curenv->set_fill_color(curenv->get_prev_fill_color());
+ else
+ do_fill_color(s);
+ skip_line();
+}
+
+void glyph_color_change()
+{
+ symbol s = get_name();
+ if (s.is_null())
+ curenv->set_glyph_color(curenv->get_prev_glyph_color());
+ else
+ do_glyph_color(s);
+ skip_line();
+}
+
+static symbol P_symbol("P");
+
+void font_change()
+{
+ symbol s = get_name();
+ bool is_number = true;
+ if (s.is_null() || s == P_symbol) {
+ s = P_symbol;
+ is_number = false;
+ }
+ else {
+ for (const char *p = s.contents(); p != 0 && *p != 0; p++)
+ if (!csdigit(*p)) {
+ is_number = false;
+ break;
+ }
+ }
+ // environment::set_font warns if a bogus mounting position is
+ // requested. We must warn here if a bogus font name is selected.
+ if (is_number)
+ (void) curenv->set_font(atoi(s.contents()));
+ else
+ if (!curenv->set_font(s))
+ warning(WARN_FONT, "cannot select font '%1'", s.contents());
+ skip_line();
+}
+
+void family_change()
+{
+ symbol s = get_name();
+ curenv->set_family(s);
+ skip_line();
+}
+
+void point_size()
+{
+ int n;
+ if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
+ if (n <= 0)
+ n = 1;
+ curenv->set_size(n);
+ }
+ else
+ curenv->set_size(0);
+ skip_line();
+}
+
+void override_sizes()
+{
+ int n = 16;
+ int *sizes = new int[n];
+ int i = 0;
+ char *buf = read_string();
+ if (!buf)
+ return;
+ char *p = strtok(buf, " \t");
+ for (;;) {
+ if (!p)
+ break;
+ int lower, upper;
+ switch (sscanf(p, "%d-%d", &lower, &upper)) {
+ case 1:
+ upper = lower;
+ // fall through
+ case 2:
+ if (lower <= upper && lower >= 0)
+ break;
+ // fall through
+ default:
+ warning(WARN_RANGE, "bad size range '%1'", p);
+ return;
+ }
+ if (i + 2 > n) {
+ int *old_sizes = sizes;
+ sizes = new int[n*2];
+ memcpy(sizes, old_sizes, n*sizeof(int));
+ n *= 2;
+ delete[] old_sizes;
+ }
+ sizes[i++] = lower;
+ if (lower == 0)
+ break;
+ sizes[i++] = upper;
+ p = strtok(0, " \t");
+ }
+ font_size::init_size_table(sizes);
+}
+
+void space_size()
+{
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ warning(WARN_RANGE, "negative word space size ignored: '%1'", n);
+ else
+ curenv->space_size = n;
+ if (has_arg() && get_integer(&n))
+ if (n < 0)
+ warning(WARN_RANGE, "negative sentence space size ignored: "
+ "'%1'", n);
+ else
+ curenv->sentence_space_size = n;
+ else
+ curenv->sentence_space_size = curenv->space_size;
+ }
+ skip_line();
+}
+
+void fill()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->fill = 1;
+ tok.next();
+}
+
+void no_fill()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->fill = 0;
+ curenv->suppress_next_eol = 1;
+ tok.next();
+}
+
+void center()
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ else if (n < 0)
+ n = 0;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->right_justify_lines = 0;
+ curenv->center_lines = n;
+ curdiv->modified_tag.incl(MTSM_CE);
+ tok.next();
+}
+
+void right_justify()
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ else if (n < 0)
+ n = 0;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->center_lines = 0;
+ curenv->right_justify_lines = n;
+ curdiv->modified_tag.incl(MTSM_RJ);
+ tok.next();
+}
+
+void line_length()
+{
+ hunits temp;
+ hunits minimum_length = font::hor;
+ if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
+ if (temp < minimum_length) {
+ warning(WARN_RANGE, "invalid line length %1u rounded to device"
+ " horizontal motion quantum", temp.to_units());
+ temp = minimum_length;
+ }
+ }
+ else
+ temp = curenv->prev_line_length;
+ curenv->prev_line_length = curenv->line_length;
+ curenv->line_length = temp;
+ curdiv->modified_tag.incl(MTSM_LL);
+ skip_line();
+}
+
+void title_length()
+{
+ hunits temp;
+ hunits minimum_length = font::hor;
+ if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
+ if (temp < minimum_length) {
+ warning(WARN_RANGE, "invalid title length %1u rounded to device"
+ " horizontal motion quantum", temp.to_units());
+ temp = minimum_length;
+ }
+ }
+ else
+ temp = curenv->prev_title_length;
+ curenv->prev_title_length = curenv->title_length;
+ curenv->title_length = temp;
+ skip_line();
+}
+
+void vertical_spacing()
+{
+ vunits temp;
+ if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
+ if (temp < V0) {
+ warning(WARN_RANGE, "vertical spacing must not be negative");
+ temp = vresolution;
+ }
+ }
+ else
+ temp = curenv->prev_vertical_spacing;
+ curenv->prev_vertical_spacing = curenv->vertical_spacing;
+ curenv->vertical_spacing = temp;
+ skip_line();
+}
+
+void post_vertical_spacing()
+{
+ vunits temp;
+ if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
+ if (temp < V0) {
+ warning(WARN_RANGE,
+ "post vertical spacing must be greater than or equal to 0");
+ temp = V0;
+ }
+ }
+ else
+ temp = curenv->prev_post_vertical_spacing;
+ curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
+ curenv->post_vertical_spacing = temp;
+ skip_line();
+}
+
+void line_spacing()
+{
+ int temp;
+ if (has_arg() && get_integer(&temp)) {
+ if (temp < 1) {
+ warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
+ temp = 1;
+ }
+ }
+ else
+ temp = curenv->prev_line_spacing;
+ curenv->prev_line_spacing = curenv->line_spacing;
+ curenv->line_spacing = temp;
+ skip_line();
+}
+
+void indent()
+{
+ hunits temp;
+ if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
+ if (temp < H0) {
+ warning(WARN_RANGE, "indent cannot be negative");
+ temp = H0;
+ }
+ }
+ else
+ temp = curenv->prev_indent;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->have_temporary_indent = 0;
+ curenv->prev_indent = curenv->indent;
+ curenv->indent = temp;
+ curdiv->modified_tag.incl(MTSM_IN);
+ tok.next();
+}
+
+void temporary_indent()
+{
+ int err = 0;
+ hunits temp;
+ if (!get_hunits(&temp, 'm', curenv->get_indent()))
+ err = 1;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (temp < H0) {
+ warning(WARN_RANGE, "total indent cannot be negative");
+ temp = H0;
+ }
+ if (!err) {
+ curenv->temporary_indent = temp;
+ curenv->have_temporary_indent = 1;
+ curdiv->modified_tag.incl(MTSM_TI);
+ }
+ tok.next();
+}
+
+void do_underline(int underline_spaces)
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ if (n <= 0) {
+ if (curenv->underline_lines > 0) {
+ curenv->prev_fontno = curenv->fontno;
+ curenv->fontno = curenv->pre_underline_fontno;
+ if (underline_spaces) {
+ curenv->underline_spaces = 0;
+ curenv->add_node(do_underline_special(false));
+ }
+ }
+ curenv->underline_lines = 0;
+ }
+ else {
+ curenv->underline_lines = n;
+ curenv->pre_underline_fontno = curenv->fontno;
+ curenv->fontno = get_underline_fontno();
+ if (underline_spaces) {
+ curenv->underline_spaces = 1;
+ curenv->add_node(do_underline_special(true));
+ }
+ }
+ skip_line();
+}
+
+void continuous_underline()
+{
+ do_underline(1);
+}
+
+void underline()
+{
+ do_underline(0);
+}
+
+void control_char()
+{
+ curenv->control_char = '.';
+ if (has_arg()) {
+ if (tok.ch() == 0)
+ error("bad control character");
+ else
+ curenv->control_char = tok.ch();
+ }
+ skip_line();
+}
+
+void no_break_control_char()
+{
+ curenv->no_break_control_char = '\'';
+ if (has_arg()) {
+ if (tok.ch() == 0)
+ error("bad control character");
+ else
+ curenv->no_break_control_char = tok.ch();
+ }
+ skip_line();
+}
+
+void margin_character()
+{
+ while (tok.is_space())
+ tok.next();
+ charinfo *ci = tok.get_char();
+ if (ci) {
+ // Call tok.next() only after making the node so that
+ // .mc \s+9\(br\s0 works.
+ node *nd = curenv->make_char_node(ci);
+ tok.next();
+ if (nd) {
+ delete curenv->margin_character_node;
+ curenv->margin_character_node = nd;
+ curenv->margin_character_flags = MARGIN_CHARACTER_ON
+ | MARGIN_CHARACTER_NEXT;
+ hunits d;
+ if (has_arg() && get_hunits(&d, 'm'))
+ curenv->margin_character_distance = d;
+ }
+ }
+ else {
+ check_missing_character();
+ curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
+ if (curenv->margin_character_flags == 0) {
+ delete curenv->margin_character_node;
+ curenv->margin_character_node = 0;
+ }
+ }
+ skip_line();
+}
+
+void number_lines()
+{
+ delete_node_list(curenv->numbering_nodes);
+ curenv->numbering_nodes = 0;
+ if (has_arg()) {
+ node *nd = 0;
+ for (int i = '9'; i >= '0'; i--) {
+ node *tem = make_node(charset_table[i], curenv);
+ if (!tem) {
+ skip_line();
+ return;
+ }
+ tem->next = nd;
+ nd = tem;
+ }
+ curenv->numbering_nodes = nd;
+ curenv->line_number_digit_width = env_digit_width(curenv);
+ int n;
+ if (!tok.usable_as_delimiter()) {
+ if (get_integer(&n, next_line_number)) {
+ next_line_number = n;
+ if (next_line_number < 0) {
+ warning(WARN_RANGE, "negative line number");
+ next_line_number = 0;
+ }
+ }
+ }
+ else
+ while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (has_arg()) {
+ if (!tok.usable_as_delimiter()) {
+ if (get_integer(&n)) {
+ if (n <= 0) {
+ warning(WARN_RANGE, "negative or zero line number multiple");
+ }
+ else
+ curenv->line_number_multiple = n;
+ }
+ }
+ else
+ while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (has_arg()) {
+ if (!tok.usable_as_delimiter()) {
+ if (get_integer(&n))
+ curenv->number_text_separation = n;
+ }
+ else
+ while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (has_arg() && !tok.usable_as_delimiter() && get_integer(&n))
+ curenv->line_number_indent = n;
+ }
+ }
+ }
+ skip_line();
+}
+
+void no_number()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->no_number_count = n > 0 ? n : 0;
+ else
+ curenv->no_number_count = 1;
+ skip_line();
+}
+
+void no_hyphenate()
+{
+ curenv->hyphenation_flags = 0;
+ skip_line();
+}
+
+void hyphenate_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n)) {
+ if (n < HYPHEN_NONE) {
+ warning(WARN_RANGE, "negative hyphenation flags ignored: %1", n);
+ } else if (n > HYPHEN_MAX) {
+ warning(WARN_RANGE, "unknown hyphenation flags ignored (maximum "
+ "%1): %2", HYPHEN_MAX, n);
+ } else if (((n & HYPHEN_DEFAULT) && (n & ~HYPHEN_DEFAULT))
+ || ((n & HYPHEN_FIRST_CHAR) && (n & HYPHEN_NOT_FIRST_CHARS))
+ || ((n & HYPHEN_LAST_CHAR) && (n & HYPHEN_NOT_LAST_CHARS)))
+ warning(WARN_SYNTAX, "contradictory hyphenation flags ignored: "
+ "%1", n);
+ else
+ curenv->hyphenation_flags = n;
+ }
+ else
+ curenv->hyphenation_flags = 1;
+ skip_line();
+}
+
+void hyphen_char()
+{
+ curenv->hyphen_indicator_char = get_optional_char();
+ skip_line();
+}
+
+void hyphen_line_max_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->hyphen_line_max = n;
+ else
+ curenv->hyphen_line_max = -1;
+ skip_line();
+}
+
+void environment::interrupt()
+{
+ if (!dummy) {
+ add_node(new transparent_dummy_node);
+ interrupted = 1;
+ }
+}
+
+void environment::newline()
+{
+ int was_centered = 0;
+ if (underline_lines > 0) {
+ if (--underline_lines == 0) {
+ prev_fontno = fontno;
+ fontno = pre_underline_fontno;
+ if (underline_spaces) {
+ underline_spaces = 0;
+ add_node(do_underline_special(false));
+ }
+ }
+ }
+ if (current_field)
+ wrap_up_field();
+ if (current_tab)
+ wrap_up_tab();
+ // strip trailing spaces
+ while (line != 0 && line->discardable()) {
+ width_total -= line->width();
+ space_total -= line->nspaces();
+ node *tem = line;
+ line = line->next;
+ delete tem;
+ }
+ node *to_be_output = 0;
+ hunits to_be_output_width;
+ prev_line_interrupted = 0;
+ if (dummy)
+ space_newline();
+ else if (interrupted) {
+ interrupted = 0;
+ // see environment::final_break
+ prev_line_interrupted = is_exit_underway ? 2 : 1;
+ }
+ else if (center_lines > 0) {
+ --center_lines;
+ hunits x = target_text_length - width_total;
+ if (x > H0)
+ saved_indent += x/2;
+ to_be_output = line;
+ was_centered = 1;
+ to_be_output_width = width_total;
+ line = 0;
+ }
+ else if (right_justify_lines > 0) {
+ --right_justify_lines;
+ hunits x = target_text_length - width_total;
+ if (x > H0)
+ saved_indent += x;
+ to_be_output = line;
+ to_be_output_width = width_total;
+ line = 0;
+ }
+ else if (fill)
+ space_newline();
+ else {
+ to_be_output = line;
+ to_be_output_width = width_total;
+ line = 0;
+ }
+ input_line_start = line == 0 ? H0 : width_total;
+ if (to_be_output) {
+ if (is_html && !fill) {
+ curdiv->modified_tag.incl(MTSM_EOL);
+ if (suppress_next_eol)
+ suppress_next_eol = 0;
+ else
+ seen_eol = 1;
+ }
+
+ output_line(to_be_output, to_be_output_width, was_centered);
+ hyphen_line_count = 0;
+ }
+ if (input_trap_count > 0) {
+ if (!(continued_input_trap && prev_line_interrupted))
+ if (--input_trap_count == 0)
+ spring_trap(input_trap);
+ }
+}
+
+void environment::output_line(node *n, hunits width, int was_centered)
+{
+ prev_text_length = width;
+ if (margin_character_flags) {
+ hunits d = line_length + margin_character_distance - saved_indent - width;
+ if (d > 0) {
+ n = new hmotion_node(d, get_fill_color(), n);
+ width += d;
+ }
+ margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
+ node *tem;
+ if (!margin_character_flags) {
+ tem = margin_character_node;
+ margin_character_node = 0;
+ }
+ else
+ tem = margin_character_node->copy();
+ tem->next = n;
+ n = tem;
+ width += tem->width();
+ }
+ node *nn = 0;
+ while (n != 0) {
+ node *tem = n->next;
+ n->next = nn;
+ nn = n;
+ n = tem;
+ }
+ if (!saved_indent.is_zero())
+ nn = new hmotion_node(saved_indent, get_fill_color(), nn);
+ width += saved_indent;
+ if (no_number_count > 0)
+ --no_number_count;
+ else if (numbering_nodes) {
+ hunits w = (line_number_digit_width
+ *(3+line_number_indent+number_text_separation));
+ if (next_line_number % line_number_multiple != 0)
+ nn = new hmotion_node(w, get_fill_color(), nn);
+ else {
+ hunits x = w;
+ nn = new hmotion_node(number_text_separation * line_number_digit_width,
+ get_fill_color(), nn);
+ x -= number_text_separation*line_number_digit_width;
+ char buf[30];
+ sprintf(buf, "%3d", next_line_number);
+ for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
+ node *gn = numbering_nodes;
+ for (int count = *p - '0'; count > 0; count--)
+ gn = gn->next;
+ gn = gn->copy();
+ x -= gn->width();
+ gn->next = nn;
+ nn = gn;
+ }
+ nn = new hmotion_node(x, get_fill_color(), nn);
+ }
+ width += w;
+ ++next_line_number;
+ }
+ output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width,
+ was_centered);
+}
+
+void environment::start_line()
+{
+ assert(line == 0);
+ discarding = 0;
+ line = new line_start_node;
+ if (have_temporary_indent) {
+ saved_indent = temporary_indent;
+ have_temporary_indent = 0;
+ }
+ else
+ saved_indent = indent;
+ target_text_length = line_length - saved_indent;
+ width_total = H0;
+ space_total = 0;
+}
+
+hunits environment::get_hyphenation_space()
+{
+ return hyphenation_space;
+}
+
+void hyphenation_space_request()
+{
+ hunits n;
+ if (get_hunits(&n, 'm')) {
+ if (n < H0) {
+ warning(WARN_RANGE, "hyphenation space cannot be negative");
+ n = H0;
+ }
+ curenv->hyphenation_space = n;
+ }
+ skip_line();
+}
+
+hunits environment::get_hyphenation_margin()
+{
+ return hyphenation_margin;
+}
+
+void hyphenation_margin_request()
+{
+ hunits n;
+ if (get_hunits(&n, 'm')) {
+ if (n < H0) {
+ warning(WARN_RANGE, "hyphenation margin cannot be negative");
+ n = H0;
+ }
+ curenv->hyphenation_margin = n;
+ }
+ skip_line();
+}
+
+breakpoint *environment::choose_breakpoint()
+{
+ hunits x = width_total;
+ int s = space_total;
+ node *n = line;
+ breakpoint *best_bp = 0; // the best breakpoint so far
+ int best_bp_fits = 0;
+ while (n != 0) {
+ x -= n->width();
+ s -= n->nspaces();
+ breakpoint *bp = n->get_breakpoints(x, s);
+ while (bp != 0) {
+ if (bp->width <= target_text_length) {
+ if (!bp->hyphenated) {
+ breakpoint *tem = bp->next;
+ bp->next = 0;
+ while (tem != 0) {
+ breakpoint *tem1 = tem;
+ tem = tem->next;
+ delete tem1;
+ }
+ if (best_bp_fits
+ // Decide whether to use the hyphenated breakpoint.
+ && (hyphen_line_max < 0
+ // Only choose the hyphenated breakpoint if it would not
+ // exceed the maximum number of consecutive hyphenated
+ // lines.
+ || hyphen_line_count + 1 <= hyphen_line_max)
+ && !(adjust_mode == ADJUST_BOTH
+ // Don't choose the hyphenated breakpoint if the line
+ // can be justified by adding no more than
+ // hyphenation_space to any word space.
+ ? (bp->nspaces > 0
+ && (((target_text_length - bp->width
+ + (bp->nspaces - 1)*hresolution)/bp->nspaces)
+ <= hyphenation_space))
+ // Don't choose the hyphenated breakpoint if the line
+ // is no more than hyphenation_margin short.
+ : target_text_length - bp->width <= hyphenation_margin)) {
+ delete bp;
+ return best_bp;
+ }
+ if (best_bp)
+ delete best_bp;
+ return bp;
+ }
+ else {
+ if ((adjust_mode == ADJUST_BOTH
+ ? hyphenation_space == H0
+ : hyphenation_margin == H0)
+ && (hyphen_line_max < 0
+ || hyphen_line_count + 1 <= hyphen_line_max)) {
+ // No need to consider a non-hyphenated breakpoint.
+ if (best_bp)
+ delete best_bp;
+ breakpoint *tem = bp->next;
+ bp->next = 0;
+ while (tem != 0) {
+ breakpoint *tem1 = tem;
+ tem = tem->next;
+ delete tem1;
+ }
+ return bp;
+ }
+ // It fits but it's hyphenated.
+ if (!best_bp_fits) {
+ if (best_bp)
+ delete best_bp;
+ best_bp = bp;
+ bp = bp->next;
+ best_bp_fits = 1;
+ }
+ else {
+ breakpoint *tem = bp;
+ bp = bp->next;
+ delete tem;
+ }
+ }
+ }
+ else {
+ if (best_bp)
+ delete best_bp;
+ best_bp = bp;
+ bp = bp->next;
+ }
+ }
+ n = n->next;
+ }
+ if (best_bp) {
+ if (!best_bp_fits)
+ output_warning(WARN_BREAK, "cannot break line");
+ return best_bp;
+ }
+ return 0;
+}
+
+void environment::hyphenate_line(int start_here)
+{
+ assert(line != 0);
+ hyphenation_type prev_type = line->get_hyphenation_type();
+ node **startp;
+ if (start_here)
+ startp = &line;
+ else
+ for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
+ hyphenation_type this_type = (*startp)->get_hyphenation_type();
+ if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
+ break;
+ prev_type = this_type;
+ }
+ if (*startp == 0)
+ return;
+ node *tem = *startp;
+ do {
+ tem = tem->next;
+ } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
+ int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
+ node *end = tem;
+ hyphen_list *sl = 0;
+ tem = *startp;
+ node *forward = 0;
+ int i = 0;
+ while (tem != end) {
+ sl = tem->get_hyphen_list(sl, &i);
+ node *tem1 = tem;
+ tem = tem->next;
+ tem1->next = forward;
+ forward = tem1;
+ }
+ if (!inhibit) {
+ // this is for characters like hyphen and emdash
+ int prev_code = 0;
+ for (hyphen_list *h = sl; h; h = h->next) {
+ h->breakable = (prev_code != 0
+ && h->next != 0
+ && h->next->hyphenation_code != 0);
+ prev_code = h->hyphenation_code;
+ }
+ }
+ if (hyphenation_flags != 0
+ && !inhibit
+ // this may not be right if we have extra space on this line
+ && !((hyphenation_flags & HYPHEN_NOT_LAST_LINE)
+ && (curdiv->distance_to_next_trap()
+ <= vertical_spacing + total_post_vertical_spacing()))
+ && i >= (4
+ - (hyphenation_flags & HYPHEN_FIRST_CHAR ? 1 : 0)
+ - (hyphenation_flags & HYPHEN_LAST_CHAR ? 1 : 0)
+ + (hyphenation_flags & HYPHEN_NOT_FIRST_CHARS ? 1 : 0)
+ + (hyphenation_flags & HYPHEN_NOT_LAST_CHARS ? 1 : 0)))
+ hyphenate(sl, hyphenation_flags);
+ while (forward != 0) {
+ node *tem1 = forward;
+ forward = forward->next;
+ tem1->next = 0;
+ tem = tem1->add_self(tem, &sl);
+ }
+ *startp = tem;
+}
+
+static node *node_list_reverse(node *n)
+{
+ node *res = 0;
+ while (n) {
+ node *tem = n;
+ n = n->next;
+ tem->next = res;
+ res = tem;
+ }
+ return res;
+}
+
+static void distribute_space(node *n, int nspaces, hunits desired_space,
+ bool force_reverse_node_list = false)
+{
+ if (desired_space.is_zero() || nspaces == 0)
+ return;
+ // Positive desired space is the typical case. Negative desired space
+ // is possible if we have overrun an unbreakable line. But we should
+ // not get here if there are no adjustable space nodes to adjust.
+ assert(nspaces > 0);
+ // Space cannot always be distributed evenly among all of the space
+ // nodes in the node list: there are limits to device resolution. We
+ // add space until we run out, which might happen before the end of
+ // the line. To achieve uniform typographical grayness and avoid
+ // rivers, we switch the end from which space is initially distributed
+ // with each line requiring it, unless compelled to reverse it. The
+ // node list's natural ordering is in the direction of text flow, so
+ // we distribute space initially from the left, unlike AT&T troff.
+ static bool do_reverse_node_list = false;
+ if (force_reverse_node_list || do_reverse_node_list)
+ n = node_list_reverse(n);
+ if (!force_reverse_node_list && spread_limit >= 0
+ && desired_space.to_units() > 0) {
+ hunits em = curenv->get_size();
+ double Ems = (double)desired_space.to_units() / nspaces
+ / (em.is_zero() ? hresolution : em.to_units());
+ if (Ems > spread_limit)
+ output_warning(WARN_BREAK, "spreading %1m per space", Ems);
+ }
+ for (node *tem = n; tem; tem = tem->next)
+ tem->spread_space(&nspaces, &desired_space);
+ if (force_reverse_node_list || do_reverse_node_list)
+ (void)node_list_reverse(n);
+ if (!force_reverse_node_list)
+ do_reverse_node_list = !do_reverse_node_list;
+}
+
+void environment::possibly_break_line(int start_here, int forced)
+{
+ int was_centered = center_lines > 0;
+ if (!fill || current_tab || current_field || dummy)
+ return;
+ while (line != 0
+ && (forced
+ // When a macro follows a paragraph in fill mode, the
+ // current line should not be empty.
+ || (width_total - line->width()) > target_text_length)) {
+ hyphenate_line(start_here);
+ breakpoint *bp = choose_breakpoint();
+ if (bp == 0)
+ // we'll find one eventually
+ return;
+ node *pre, *post;
+ node **ndp = &line;
+ while (*ndp != bp->nd)
+ ndp = &(*ndp)->next;
+ bp->nd->split(bp->index, &pre, &post);
+ *ndp = post;
+ hunits extra_space_width = H0;
+ switch(adjust_mode) {
+ case ADJUST_BOTH:
+ if (bp->nspaces != 0)
+ extra_space_width = target_text_length - bp->width;
+ else if (bp->width > 0 && target_text_length > 0
+ && target_text_length > bp->width)
+ output_warning(WARN_BREAK, "cannot adjust line");
+ break;
+ case ADJUST_CENTER:
+ saved_indent += (target_text_length - bp->width)/2;
+ was_centered = 1;
+ break;
+ case ADJUST_RIGHT:
+ saved_indent += target_text_length - bp->width;
+ break;
+ }
+ distribute_space(pre, bp->nspaces, extra_space_width);
+ hunits output_width = bp->width + extra_space_width;
+ // This should become an assert() when we can get reliable width
+ // data from CJK glyphs. See Savannah #44018.
+ if (output_width <= 0) {
+ double output_width_in_ems = output_width.to_units();
+ output_warning(WARN_BREAK, "line has non-positive width %1m",
+ output_width_in_ems);
+ return;
+ }
+ input_line_start -= output_width;
+ if (bp->hyphenated)
+ hyphen_line_count++;
+ else
+ hyphen_line_count = 0;
+ delete bp;
+ space_total = 0;
+ width_total = 0;
+ node *first_non_discardable = 0;
+ node *tem;
+ for (tem = line; tem != 0; tem = tem->next)
+ if (!tem->discardable())
+ first_non_discardable = tem;
+ node *to_be_discarded;
+ if (first_non_discardable) {
+ to_be_discarded = first_non_discardable->next;
+ first_non_discardable->next = 0;
+ for (tem = line; tem != 0; tem = tem->next) {
+ width_total += tem->width();
+ space_total += tem->nspaces();
+ }
+ discarding = 0;
+ }
+ else {
+ discarding = 1;
+ to_be_discarded = line;
+ line = 0;
+ }
+ // Do output_line() here so that line will be 0 iff the
+ // the environment will be empty.
+ output_line(pre, output_width, was_centered);
+ while (to_be_discarded != 0) {
+ tem = to_be_discarded;
+ to_be_discarded = to_be_discarded->next;
+ input_line_start -= tem->width();
+ delete tem;
+ }
+ if (line != 0) {
+ if (have_temporary_indent) {
+ saved_indent = temporary_indent;
+ have_temporary_indent = 0;
+ }
+ else
+ saved_indent = indent;
+ target_text_length = line_length - saved_indent;
+ }
+ }
+}
+
+/*
+Do the break at the end of input after the end macro (if any).
+
+Unix troff behaves as follows: if the last line is
+
+foo bar\c
+
+it will output foo on the current page, and bar on the next page;
+if the last line is
+
+foo\c
+
+or
+
+foo bar
+
+everything will be output on the current page. This behaviour must be
+considered a bug.
+
+The problem is that some macro packages rely on this. For example,
+the ATK macros have an end macro that emits \c if it needs to print a
+table of contents but doesn't do a 'bp in the end macro; instead the
+'bp is done in the bottom of page trap. This works with Unix troff,
+provided that the current environment is not empty at the end of the
+input file.
+
+The following will make macro packages that do that sort of thing work
+even if the current environment is empty at the end of the input file.
+If the last input line used \c and this line occurred in the end macro,
+then we'll force everything out on the current page, but we'll make
+sure that the environment isn't empty so that we won't exit at the
+bottom of this page.
+*/
+
+void environment::final_break()
+{
+ if (prev_line_interrupted == 2) {
+ do_break();
+ add_node(new transparent_dummy_node);
+ }
+ else
+ do_break();
+}
+
+node *environment::make_tag(const char *nm, int i)
+{
+ if (is_html) {
+ /*
+ * need to emit tag for post-grohtml
+ * but we check to see whether we can emit specials
+ */
+ if (curdiv == topdiv && topdiv->before_first_page)
+ topdiv->begin_page();
+
+ macro m;
+ m.append_str("devtag:");
+ for (const char *p = nm; *p; p++)
+ if (!is_invalid_input_char((unsigned char)*p))
+ m.append(*p);
+ m.append(' ');
+ m.append_int(i);
+ return new special_node(m);
+ }
+ return 0;
+}
+
+void environment::dump_troff_state()
+{
+#define SPACES " "
+ fprintf(stderr, SPACES "register 'in' = %d\n", curenv->indent.to_units());
+ if (curenv->have_temporary_indent)
+ fprintf(stderr, SPACES "register 'ti' = %d\n",
+ curenv->temporary_indent.to_units());
+ fprintf(stderr, SPACES "centered lines 'ce' = %d\n", curenv->center_lines);
+ fprintf(stderr, SPACES "register 'll' = %d\n",
+ curenv->line_length.to_units());
+ fprintf(stderr, SPACES "fill 'fi=1/nf=0' = %d\n", curenv->fill);
+ fprintf(stderr, SPACES "page offset 'po' = %d\n",
+ topdiv->get_page_offset().to_units());
+ fprintf(stderr, SPACES "seen_break = %d\n", curenv->seen_break);
+ fprintf(stderr, SPACES "seen_space = %d\n", curenv->seen_space);
+ fflush(stderr);
+#undef SPACES
+}
+
+statem *environment::construct_state(int only_eol)
+{
+ if (is_html) {
+ statem *s = new statem();
+ if (!only_eol) {
+ s->add_tag(MTSM_IN, indent);
+ s->add_tag(MTSM_LL, line_length);
+ s->add_tag(MTSM_PO, topdiv->get_page_offset().to_units());
+ s->add_tag(MTSM_RJ, right_justify_lines);
+ if (have_temporary_indent)
+ s->add_tag(MTSM_TI, temporary_indent);
+ s->add_tag_ta();
+ if (seen_break)
+ s->add_tag(MTSM_BR);
+ if (seen_space != 0)
+ s->add_tag(MTSM_SP, seen_space);
+ seen_break = 0;
+ seen_space = 0;
+ }
+ if (seen_eol) {
+ s->add_tag(MTSM_EOL);
+ s->add_tag(MTSM_CE, center_lines);
+ }
+ seen_eol = 0;
+ return s;
+ }
+ else
+ return NULL;
+}
+
+void environment::construct_format_state(node *n, int was_centered,
+ int filling)
+{
+ if (is_html) {
+ // find first glyph node which has a state.
+ while (n != 0 && n->state == 0)
+ n = n->next;
+ if (n == 0 || (n->state == 0))
+ return;
+ if (seen_space != 0)
+ n->state->add_tag(MTSM_SP, seen_space);
+ if (seen_eol && topdiv == curdiv)
+ n->state->add_tag(MTSM_EOL);
+ seen_space = 0;
+ seen_eol = 0;
+ if (was_centered)
+ n->state->add_tag(MTSM_CE, center_lines+1);
+ else
+ n->state->add_tag_if_unknown(MTSM_CE, 0);
+ n->state->add_tag_if_unknown(MTSM_FI, filling);
+ n = n->next;
+ while (n != 0) {
+ if (n->state != 0) {
+ n->state->sub_tag_ce();
+ n->state->add_tag_if_unknown(MTSM_FI, filling);
+ }
+ n = n->next;
+ }
+ }
+}
+
+void environment::construct_new_line_state(node *n)
+{
+ if (is_html) {
+ // find first glyph node which has a state.
+ while (n != 0 && n->state == 0)
+ n = n->next;
+ if (n == 0 || n->state == 0)
+ return;
+ if (seen_space != 0)
+ n->state->add_tag(MTSM_SP, seen_space);
+ if (seen_eol && topdiv == curdiv)
+ n->state->add_tag(MTSM_EOL);
+ seen_space = 0;
+ seen_eol = 0;
+ }
+}
+
+extern int global_diverted_space;
+
+void environment::do_break(int do_spread)
+{
+ int was_centered = 0;
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ topdiv->begin_page();
+ return;
+ }
+ if (current_tab)
+ wrap_up_tab();
+ if (line) {
+ // this is so that hyphenation works
+ if (line->nspaces() == 0) {
+ line = new space_node(H0, get_fill_color(), line);
+ space_total++;
+ }
+ possibly_break_line(0, do_spread);
+ }
+ while (line != 0 && line->discardable()) {
+ width_total -= line->width();
+ space_total -= line->nspaces();
+ node *tem = line;
+ line = line->next;
+ delete tem;
+ }
+ discarding = 0;
+ input_line_start = H0;
+ if (line != 0) {
+ if (fill) {
+ switch (adjust_mode) {
+ case ADJUST_CENTER:
+ saved_indent += (target_text_length - width_total)/2;
+ was_centered = 1;
+ break;
+ case ADJUST_RIGHT:
+ saved_indent += target_text_length - width_total;
+ break;
+ }
+ }
+ node *tem = line;
+ line = 0;
+ output_line(tem, width_total, was_centered);
+ hyphen_line_count = 0;
+ }
+ prev_line_interrupted = 0;
+#ifdef WIDOW_CONTROL
+ mark_last_line();
+ output_pending_lines();
+#endif /* WIDOW_CONTROL */
+ if (!global_diverted_space) {
+ curdiv->modified_tag.incl(MTSM_BR);
+ seen_break = 1;
+ }
+}
+
+int environment::is_empty()
+{
+ return !current_tab && line == 0 && pending_lines == 0;
+}
+
+void do_break_request(int spread)
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break(spread);
+ tok.next();
+}
+
+void break_request()
+{
+ do_break_request(0);
+}
+
+void break_spread_request()
+{
+ do_break_request(1);
+}
+
+void title()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_title();
+ return;
+ }
+ node *part[3];
+ hunits part_width[3];
+ part[0] = part[1] = part[2] = 0;
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ read_title_parts(part, part_width);
+ curenv = oldenv;
+ curenv->size = env.size;
+ curenv->prev_size = env.prev_size;
+ curenv->requested_size = env.requested_size;
+ curenv->prev_requested_size = env.prev_requested_size;
+ curenv->char_height = env.char_height;
+ curenv->char_slant = env.char_slant;
+ curenv->fontno = env.fontno;
+ curenv->prev_fontno = env.prev_fontno;
+ curenv->glyph_color = env.glyph_color;
+ curenv->prev_glyph_color = env.prev_glyph_color;
+ curenv->fill_color = env.fill_color;
+ curenv->prev_fill_color = env.prev_fill_color;
+ node *n = 0;
+ node *p = part[2];
+ while (p != 0) {
+ node *tem = p;
+ p = p->next;
+ tem->next = n;
+ n = tem;
+ }
+ hunits length_title(curenv->title_length);
+ hunits f = length_title - part_width[1];
+ hunits f2 = f/2;
+ n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
+ p = part[1];
+ while (p != 0) {
+ node *tem = p;
+ p = p->next;
+ tem->next = n;
+ n = tem;
+ }
+ n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
+ p = part[0];
+ while (p != 0) {
+ node *tem = p;
+ p = p->next;
+ tem->next = n;
+ n = tem;
+ }
+ curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
+ curenv->total_post_vertical_spacing(), length_title);
+ curenv->hyphen_line_count = 0;
+ tok.next();
+}
+
+void adjust()
+{
+ curenv->adjust_mode |= 1;
+ if (has_arg()) {
+ switch (tok.ch()) {
+ case 'l':
+ curenv->adjust_mode = ADJUST_LEFT;
+ break;
+ case 'r':
+ curenv->adjust_mode = ADJUST_RIGHT;
+ break;
+ case 'c':
+ curenv->adjust_mode = ADJUST_CENTER;
+ break;
+ case 'b':
+ case 'n':
+ curenv->adjust_mode = ADJUST_BOTH;
+ break;
+ default:
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ warning(WARN_RANGE, "negative adjustment mode");
+ else if (n > ADJUST_MAX)
+ warning(WARN_RANGE, "out-of-range adjustment mode ignored: "
+ "%1", n);
+ else
+ curenv->adjust_mode = n;
+ }
+ }
+ }
+ skip_line();
+}
+
+void no_adjust()
+{
+ curenv->adjust_mode &= ~1;
+ skip_line();
+}
+
+void do_input_trap(int continued)
+{
+ curenv->input_trap_count = 0;
+ if (continued)
+ curenv->continued_input_trap = 1;
+ else
+ curenv->continued_input_trap = 0;
+ int n;
+ if (has_arg() && get_integer(&n)) {
+ if (n <= 0)
+ warning(WARN_RANGE,
+ "number of lines for input trap must be greater than zero");
+ else {
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ curenv->input_trap_count = n;
+ curenv->input_trap = s;
+ }
+ }
+ }
+ skip_line();
+}
+
+void input_trap()
+{
+ do_input_trap(0);
+}
+
+void input_trap_continued()
+{
+ do_input_trap(1);
+}
+
+/* tabs */
+
+// must not be R or C or L or a legitimate part of a number expression
+const char TAB_REPEAT_CHAR = 'T';
+
+struct tab {
+ tab *next;
+ hunits pos;
+ tab_type type;
+ tab(hunits, tab_type);
+ enum { BLOCK = 1024 };
+};
+
+tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
+{
+}
+
+tab_stops::tab_stops(hunits distance, tab_type type)
+: initial_list(0)
+{
+ repeated_list = new tab(distance, type);
+}
+
+tab_stops::~tab_stops()
+{
+ clear();
+}
+
+tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
+{
+ hunits nextpos;
+
+ return distance_to_next_tab(curpos, distance, &nextpos);
+}
+
+tab_type tab_stops::distance_to_next_tab(hunits curpos,
+ hunits *distance,
+ hunits *nextpos)
+{
+ hunits lastpos = 0;
+ tab *tem;
+ for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
+ lastpos = tem->pos;
+ if (tem) {
+ *distance = tem->pos - curpos;
+ *nextpos = tem->pos;
+ return tem->type;
+ }
+ if (repeated_list == 0)
+ return TAB_NONE;
+ hunits base = lastpos;
+ for (;;) {
+ for (tem = repeated_list; tem && tem->pos + base <= curpos;
+ tem = tem->next)
+ lastpos = tem->pos;
+ if (tem) {
+ *distance = tem->pos + base - curpos;
+ *nextpos = tem->pos + base;
+ return tem->type;
+ }
+ if (lastpos < 0)
+ lastpos = 0;
+ base += lastpos;
+ }
+ return TAB_NONE;
+}
+
+const char *tab_stops::to_string()
+{
+ static char *buf = 0;
+ static int buf_size = 0;
+ // figure out a maximum on the amount of space we can need
+ int count = 0;
+ tab *p;
+ for (p = initial_list; p; p = p->next)
+ ++count;
+ for (p = repeated_list; p; p = p->next)
+ ++count;
+ // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
+ int need = count*12 + 3;
+ if (buf == 0 || need > buf_size) {
+ if (buf)
+ delete[] buf;
+ buf_size = need;
+ buf = new char[buf_size];
+ }
+ char *ptr = buf;
+ for (p = initial_list; p; p = p->next) {
+ strcpy(ptr, i_to_a(p->pos.to_units()));
+ ptr = strchr(ptr, '\0');
+ *ptr++ = 'u';
+ *ptr = '\0';
+ switch (p->type) {
+ case TAB_LEFT:
+ break;
+ case TAB_RIGHT:
+ *ptr++ = 'R';
+ break;
+ case TAB_CENTER:
+ *ptr++ = 'C';
+ break;
+ case TAB_NONE:
+ default:
+ assert(0);
+ }
+ }
+ if (repeated_list)
+ *ptr++ = TAB_REPEAT_CHAR;
+ for (p = repeated_list; p; p = p->next) {
+ strcpy(ptr, i_to_a(p->pos.to_units()));
+ ptr = strchr(ptr, '\0');
+ *ptr++ = 'u';
+ *ptr = '\0';
+ switch (p->type) {
+ case TAB_LEFT:
+ break;
+ case TAB_RIGHT:
+ *ptr++ = 'R';
+ break;
+ case TAB_CENTER:
+ *ptr++ = 'C';
+ break;
+ case TAB_NONE:
+ default:
+ assert(0);
+ }
+ }
+ *ptr++ = '\0';
+ return buf;
+}
+
+tab_stops::tab_stops() : initial_list(0), repeated_list(0)
+{
+}
+
+tab_stops::tab_stops(const tab_stops &ts)
+: initial_list(0), repeated_list(0)
+{
+ tab **p = &initial_list;
+ tab *t = ts.initial_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+ p = &repeated_list;
+ t = ts.repeated_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+}
+
+void tab_stops::clear()
+{
+ while (initial_list) {
+ tab *tem = initial_list;
+ initial_list = initial_list->next;
+ delete tem;
+ }
+ while (repeated_list) {
+ tab *tem = repeated_list;
+ repeated_list = repeated_list->next;
+ delete tem;
+ }
+}
+
+void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
+{
+ tab **p;
+ for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
+ ;
+ *p = new tab(pos, type);
+}
+
+
+void tab_stops::operator=(const tab_stops &ts)
+{
+ clear();
+ tab **p = &initial_list;
+ tab *t = ts.initial_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+ p = &repeated_list;
+ t = ts.repeated_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+}
+
+void set_tabs()
+{
+ hunits pos;
+ hunits prev_pos = 0;
+ bool is_first_stop = true;
+ bool is_repeating_stop = false;
+ tab_stops tabs;
+ while (has_arg()) {
+ if (tok.ch() == TAB_REPEAT_CHAR) {
+ tok.next();
+ is_repeating_stop = true;
+ prev_pos = 0;
+ }
+ if (!get_hunits(&pos, 'm', prev_pos))
+ break;
+ tab_type type = TAB_LEFT;
+ if (tok.ch() == 'C') {
+ tok.next();
+ type = TAB_CENTER;
+ }
+ else if (tok.ch() == 'R') {
+ tok.next();
+ type = TAB_RIGHT;
+ }
+ else if (tok.ch() == 'L') {
+ tok.next();
+ }
+ if (pos <= prev_pos && ((!is_first_stop) || is_repeating_stop))
+ warning(WARN_RANGE,
+ "positions of tab stops must be strictly increasing");
+ else {
+ tabs.add_tab(pos, type, is_repeating_stop);
+ prev_pos = pos;
+ is_first_stop = false;
+ }
+ }
+ curenv->tabs = tabs;
+ curdiv->modified_tag.incl(MTSM_TA);
+ skip_line();
+}
+
+const char *environment::get_tabs()
+{
+ return tabs.to_string();
+}
+
+tab_type environment::distance_to_next_tab(hunits *distance)
+{
+ return line_tabs
+ ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
+ : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
+}
+
+tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
+{
+ return line_tabs
+ ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
+ : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
+ leftpos);
+}
+
+void field_characters()
+{
+ field_delimiter_char = get_optional_char();
+ if (field_delimiter_char)
+ padding_indicator_char = get_optional_char();
+ else
+ padding_indicator_char = 0;
+ skip_line();
+}
+
+void line_tabs_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->line_tabs = n != 0;
+ else
+ curenv->line_tabs = 1;
+ skip_line();
+}
+
+int environment::get_line_tabs()
+{
+ return line_tabs;
+}
+
+void environment::wrap_up_tab()
+{
+ if (!current_tab)
+ return;
+ if (line == 0)
+ start_line();
+ hunits tab_amount;
+ switch (current_tab) {
+ case TAB_RIGHT:
+ tab_amount = tab_distance - tab_width;
+ line = make_tab_node(tab_amount, line);
+ break;
+ case TAB_CENTER:
+ tab_amount = tab_distance - tab_width/2;
+ line = make_tab_node(tab_amount, line);
+ break;
+ case TAB_NONE:
+ case TAB_LEFT:
+ default:
+ assert(0);
+ }
+ width_total += tab_amount;
+ width_total += tab_width;
+ if (current_field) {
+ if (tab_precedes_field) {
+ pre_field_width += tab_amount;
+ tab_precedes_field = 0;
+ }
+ field_distance -= tab_amount;
+ field_spaces += tab_field_spaces;
+ }
+ if (tab_contents != 0) {
+ node *tem;
+ for (tem = tab_contents; tem->next != 0; tem = tem->next)
+ ;
+ tem->next = line;
+ line = tab_contents;
+ }
+ tab_field_spaces = 0;
+ tab_contents = 0;
+ tab_width = H0;
+ tab_distance = H0;
+ current_tab = TAB_NONE;
+}
+
+node *environment::make_tab_node(hunits d, node *next)
+{
+ if (leader_node != 0 && d < 0) {
+ error("motion generated by leader cannot be negative");
+ delete leader_node;
+ leader_node = 0;
+ }
+ if (!leader_node)
+ return new hmotion_node(d, 1, 0, get_fill_color(), next);
+ node *n = new hline_node(d, leader_node, next);
+ leader_node = 0;
+ return n;
+}
+
+void environment::handle_tab(int is_leader)
+{
+ hunits d;
+ hunits absolute;
+ if (current_tab)
+ wrap_up_tab();
+ charinfo *ci = is_leader ? leader_char : tab_char;
+ delete leader_node;
+ leader_node = ci ? make_char_node(ci) : 0;
+ tab_type t = distance_to_next_tab(&d, &absolute);
+ switch (t) {
+ case TAB_NONE:
+ return;
+ case TAB_LEFT:
+ add_node(make_tag("tab L", absolute.to_units()));
+ add_node(make_tab_node(d));
+ return;
+ case TAB_RIGHT:
+ add_node(make_tag("tab R", absolute.to_units()));
+ break;
+ case TAB_CENTER:
+ add_node(make_tag("tab C", absolute.to_units()));
+ break;
+ default:
+ assert(0);
+ }
+ tab_width = 0;
+ tab_distance = d;
+ tab_contents = 0;
+ current_tab = t;
+ tab_field_spaces = 0;
+}
+
+void environment::start_field()
+{
+ assert(!current_field);
+ hunits d;
+ if (distance_to_next_tab(&d) != TAB_NONE) {
+ pre_field_width = get_text_length();
+ field_distance = d;
+ current_field = 1;
+ field_spaces = 0;
+ tab_field_spaces = 0;
+ for (node *p = line; p; p = p->next)
+ if (p->nspaces()) {
+ p->freeze_space();
+ space_total--;
+ }
+ tab_precedes_field = current_tab != TAB_NONE;
+ }
+ else
+ error("zero field width");
+}
+
+void environment::wrap_up_field()
+{
+ if (!current_tab && field_spaces == 0)
+ add_padding();
+ hunits padding = field_distance - (get_text_length() - pre_field_width);
+ if (current_tab && tab_field_spaces != 0) {
+ hunits tab_padding = scale(padding,
+ tab_field_spaces,
+ field_spaces + tab_field_spaces);
+ padding -= tab_padding;
+ distribute_space(tab_contents, tab_field_spaces, tab_padding,
+ true /* force reversal of node list */);
+ tab_field_spaces = 0;
+ tab_width += tab_padding;
+ }
+ if (field_spaces != 0) {
+ distribute_space(line, field_spaces, padding,
+ true /* force reversal of node list */);
+ width_total += padding;
+ if (current_tab) {
+ // the start of the tab has been moved to the right by padding, so
+ tab_distance -= padding;
+ if (tab_distance <= H0) {
+ // use the next tab stop instead
+ current_tab = tabs.distance_to_next_tab(get_input_line_position()
+ - tab_width,
+ &tab_distance);
+ if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
+ width_total += tab_width;
+ if (current_tab == TAB_LEFT) {
+ line = make_tab_node(tab_distance, line);
+ width_total += tab_distance;
+ current_tab = TAB_NONE;
+ }
+ if (tab_contents != 0) {
+ node *tem;
+ for (tem = tab_contents; tem->next != 0; tem = tem->next)
+ ;
+ tem->next = line;
+ line = tab_contents;
+ tab_contents = 0;
+ }
+ tab_width = H0;
+ tab_distance = H0;
+ }
+ }
+ }
+ }
+ current_field = 0;
+}
+
+void environment::add_padding()
+{
+ if (current_tab) {
+ tab_contents = new space_node(H0, get_fill_color(), tab_contents);
+ tab_field_spaces++;
+ }
+ else {
+ if (line == 0)
+ start_line();
+ line = new space_node(H0, get_fill_color(), line);
+ field_spaces++;
+ }
+}
+
+typedef int (environment::*INT_FUNCP)();
+typedef vunits (environment::*VUNITS_FUNCP)();
+typedef hunits (environment::*HUNITS_FUNCP)();
+typedef const char *(environment::*STRING_FUNCP)();
+
+class int_env_reg : public reg {
+ INT_FUNCP func;
+ public:
+ int_env_reg(INT_FUNCP);
+ const char *get_string();
+ bool get_value(units *val);
+};
+
+class vunits_env_reg : public reg {
+ VUNITS_FUNCP func;
+ public:
+ vunits_env_reg(VUNITS_FUNCP f);
+ const char *get_string();
+ bool get_value(units *val);
+};
+
+
+class hunits_env_reg : public reg {
+ HUNITS_FUNCP func;
+ public:
+ hunits_env_reg(HUNITS_FUNCP f);
+ const char *get_string();
+ bool get_value(units *val);
+};
+
+class string_env_reg : public reg {
+ STRING_FUNCP func;
+public:
+ string_env_reg(STRING_FUNCP);
+ const char *get_string();
+};
+
+int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
+{
+}
+
+bool int_env_reg::get_value(units *val)
+{
+ *val = (curenv->*func)();
+ return true;
+}
+
+const char *int_env_reg::get_string()
+{
+ return i_to_a((curenv->*func)());
+}
+
+vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
+{
+}
+
+bool vunits_env_reg::get_value(units *val)
+{
+ *val = (curenv->*func)().to_units();
+ return true;
+}
+
+const char *vunits_env_reg::get_string()
+{
+ return i_to_a((curenv->*func)().to_units());
+}
+
+hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
+{
+}
+
+bool hunits_env_reg::get_value(units *val)
+{
+ *val = (curenv->*func)().to_units();
+ return true;
+}
+
+const char *hunits_env_reg::get_string()
+{
+ return i_to_a((curenv->*func)().to_units());
+}
+
+string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
+{
+}
+
+const char *string_env_reg::get_string()
+{
+ return (curenv->*func)();
+}
+
+class horizontal_place_reg : public general_reg {
+public:
+ horizontal_place_reg();
+ bool get_value(units *);
+ void set_value(units);
+};
+
+horizontal_place_reg::horizontal_place_reg()
+{
+}
+
+bool horizontal_place_reg::get_value(units *res)
+{
+ *res = curenv->get_input_line_position().to_units();
+ return true;
+}
+
+void horizontal_place_reg::set_value(units n)
+{
+ curenv->set_input_line_position(hunits(n));
+}
+
+int environment::get_zoom()
+{
+ return env_get_zoom(this);
+}
+
+int environment::get_numbering_nodes()
+{
+ return (curenv->numbering_nodes ? 1 : 0);
+}
+
+const char *environment::get_font_family_string()
+{
+ return family->nm.contents();
+}
+
+const char *environment::get_glyph_color_string()
+{
+ return glyph_color->nm.contents();
+}
+
+const char *environment::get_fill_color_string()
+{
+ return fill_color->nm.contents();
+}
+
+const char *environment::get_font_name_string()
+{
+ symbol f = get_font_name(fontno, this);
+ return f.contents();
+}
+
+const char *environment::get_style_name_string()
+{
+ symbol f = get_style_name(fontno);
+ return f.contents();
+}
+
+const char *environment::get_name_string()
+{
+ return name.contents();
+}
+
+// Convert a quantity in scaled points to ascii decimal fraction.
+
+const char *sptoa(int sp)
+{
+ assert(sp > 0);
+ assert(sizescale > 0);
+ if (sizescale == 1)
+ return i_to_a(sp);
+ if (sp % sizescale == 0)
+ return i_to_a(sp/sizescale);
+ // See if 1/sizescale is exactly representable as a decimal fraction,
+ // ie its only prime factors are 2 and 5.
+ int n = sizescale;
+ int power2 = 0;
+ while ((n & 1) == 0) {
+ n >>= 1;
+ power2++;
+ }
+ int power5 = 0;
+ while ((n % 5) == 0) {
+ n /= 5;
+ power5++;
+ }
+ if (n == 1) {
+ int decimal_point = power5 > power2 ? power5 : power2;
+ if (decimal_point <= 10) {
+ int factor = 1;
+ int t;
+ for (t = decimal_point - power2; --t >= 0;)
+ factor *= 2;
+ for (t = decimal_point - power5; --t >= 0;)
+ factor *= 5;
+ if (factor == 1 || sp <= INT_MAX/factor)
+ return if_to_a(sp*factor, decimal_point);
+ }
+ }
+ double s = double(sp)/double(sizescale);
+ double factor = 10.0;
+ double val = s;
+ int decimal_point = 0;
+ do {
+ double v = ceil(s*factor);
+ if (v > INT_MAX)
+ break;
+ val = v;
+ factor *= 10.0;
+ } while (++decimal_point < 10);
+ return if_to_a(int(val), decimal_point);
+}
+
+const char *environment::get_point_size_string()
+{
+ return sptoa(curenv->get_point_size());
+}
+
+const char *environment::get_requested_point_size_string()
+{
+ return sptoa(curenv->get_requested_point_size());
+}
+
+void environment::print_env()
+{
+ // at the time of calling .pev, those values are always zero or
+ // meaningless:
+ //
+ // char_height, char_slant,
+ // interrupted
+ // current_tab, tab_width, tab_distance
+ // current_field, field_distance, pre_field_width, field_spaces,
+ // tab_field_spaces, tab_precedes_field
+ // composite
+ //
+ errprint(" previous line length: %1u\n", prev_line_length.to_units());
+ errprint(" line length: %1u\n", line_length.to_units());
+ errprint(" previous title length: %1u\n", prev_title_length.to_units());
+ errprint(" title length: %1u\n", title_length.to_units());
+ errprint(" previous size: %1p (%2s)\n",
+ prev_size.to_points(), prev_size.to_scaled_points());
+ errprint(" size: %1p (%2s)\n",
+ size.to_points(), size.to_scaled_points());
+ errprint(" previous requested size: %1s\n", prev_requested_size);
+ errprint(" requested size: %1s\n", requested_size);
+ errprint(" previous font number: %1\n", prev_fontno);
+ errprint(" font number: %1\n", fontno);
+ errprint(" previous family: '%1'\n", prev_family->nm.contents());
+ errprint(" family: '%1'\n", family->nm.contents());
+ errprint(" space size: %1/36 em\n", space_size);
+ errprint(" sentence space size: %1/36 em\n", sentence_space_size);
+ errprint(" previous line interrupted: %1\n",
+ prev_line_interrupted ? "yes" : "no");
+ errprint(" fill mode: %1\n", fill ? "on" : "off");
+ errprint(" adjust mode: %1\n",
+ adjust_mode == ADJUST_LEFT
+ ? "left"
+ : adjust_mode == ADJUST_BOTH
+ ? "both"
+ : adjust_mode == ADJUST_CENTER
+ ? "center"
+ : "right");
+ if (center_lines)
+ errprint(" lines to center: %1\n", center_lines);
+ if (right_justify_lines)
+ errprint(" lines to right justify: %1\n", right_justify_lines);
+ errprint(" previous vertical spacing: %1u\n",
+ prev_vertical_spacing.to_units());
+ errprint(" vertical spacing: %1u\n", vertical_spacing.to_units());
+ errprint(" previous post-vertical spacing: %1u\n",
+ prev_post_vertical_spacing.to_units());
+ errprint(" post-vertical spacing: %1u\n",
+ post_vertical_spacing.to_units());
+ errprint(" previous line spacing: %1\n", prev_line_spacing);
+ errprint(" line spacing: %1\n", line_spacing);
+ errprint(" previous indentation: %1u\n", prev_indent.to_units());
+ errprint(" indentation: %1u\n", indent.to_units());
+ errprint(" temporary indentation: %1u\n", temporary_indent.to_units());
+ errprint(" have temporary indentation: %1\n",
+ have_temporary_indent ? "yes" : "no");
+ errprint(" currently used indentation: %1u\n", saved_indent.to_units());
+ errprint(" target text length: %1u\n", target_text_length.to_units());
+ if (underline_lines) {
+ errprint(" lines to underline: %1\n", underline_lines);
+ errprint(" font number before underlining: %1\n", pre_underline_fontno);
+ errprint(" underline spaces: %1\n", underline_spaces ? "yes" : "no");
+ }
+ if (input_trap.contents()) {
+ errprint(" input trap macro: '%1'\n", input_trap.contents());
+ errprint(" input trap line counter: %1\n", input_trap_count);
+ errprint(" continued input trap: %1\n",
+ continued_input_trap ? "yes" : "no");
+ }
+ errprint(" previous text length: %1u\n", prev_text_length.to_units());
+ errprint(" total width: %1u\n", width_total.to_units());
+ errprint(" total number of spaces: %1\n", space_total);
+ errprint(" input line start: %1u\n", input_line_start.to_units());
+ errprint(" line tabs: %1\n", line_tabs ? "yes" : "no");
+ errprint(" discarding: %1\n", discarding ? "yes" : "no");
+ errprint(" spread flag set: %1\n", spread_flag ? "yes" : "no"); // \p
+ if (margin_character_node) {
+ errprint(" margin character flags: %1\n",
+ margin_character_flags == MARGIN_CHARACTER_ON
+ ? "on"
+ : margin_character_flags == MARGIN_CHARACTER_NEXT
+ ? "next"
+ : margin_character_flags == (MARGIN_CHARACTER_ON
+ | MARGIN_CHARACTER_NEXT)
+ ? "on, next"
+ : "none");
+ errprint(" margin character distance: %1u\n",
+ margin_character_distance.to_units());
+ }
+ if (numbering_nodes) {
+ errprint(" line number digit width: %1u\n",
+ line_number_digit_width.to_units());
+ errprint(" separation between number and text: %1 digit spaces\n",
+ number_text_separation);
+ errprint(" line number indentation: %1 digit spaces\n",
+ line_number_indent);
+ errprint(" print line numbers every %1line%1\n",
+ line_number_multiple > 1 ? i_to_a(line_number_multiple) : "",
+ line_number_multiple > 1 ? "s" : "");
+ errprint(" lines not to enumerate: %1\n", no_number_count);
+ }
+ string hf = hyphenation_flags ? "on" : "off";
+ if (hyphenation_flags & HYPHEN_NOT_LAST_LINE)
+ hf += ", not last line";
+ if (hyphenation_flags & HYPHEN_LAST_CHAR)
+ hf += ", last char";
+ if (hyphenation_flags & HYPHEN_NOT_LAST_CHARS)
+ hf += ", not last two chars";
+ if (hyphenation_flags & HYPHEN_FIRST_CHAR)
+ hf += ", first char";
+ if (hyphenation_flags & HYPHEN_NOT_FIRST_CHARS)
+ hf += ", not first two chars";
+ hf += '\0';
+ errprint(" hyphenation_flags: %1\n", hf.contents());
+ errprint(" number of consecutive hyphenated lines: %1\n",
+ hyphen_line_count);
+ errprint(" maximum number of consecutive hyphenated lines: %1\n",
+ hyphen_line_max);
+ errprint(" hyphenation space: %1u\n", hyphenation_space.to_units());
+ errprint(" hyphenation margin: %1u\n", hyphenation_margin.to_units());
+#ifdef WIDOW_CONTROL
+ errprint(" widow control: %1\n", widow_control ? "yes" : "no");
+#endif /* WIDOW_CONTROL */
+}
+
+void print_env()
+{
+ errprint("Current Environment:\n");
+ curenv->print_env();
+ dictionary_iterator iter(env_dictionary);
+ symbol s;
+ environment *e;
+ while (iter.get(&s, (void **)&e)) {
+ assert(!s.is_null());
+ errprint("Environment %1:\n", s.contents());
+ if (e != curenv)
+ e->print_env();
+ else
+ errprint(" current\n");
+ }
+ fflush(stderr);
+ skip_line();
+}
+
+#define init_int_env_reg(name, func) \
+ register_dictionary.define(name, new int_env_reg(&environment::func))
+
+#define init_vunits_env_reg(name, func) \
+ register_dictionary.define(name, new vunits_env_reg(&environment::func))
+
+#define init_hunits_env_reg(name, func) \
+ register_dictionary.define(name, new hunits_env_reg(&environment::func))
+
+#define init_string_env_reg(name, func) \
+ register_dictionary.define(name, new string_env_reg(&environment::func))
+
+void init_env_requests()
+{
+ init_request("ad", adjust);
+ init_request("br", break_request);
+ init_request("brp", break_spread_request);
+ init_request("c2", no_break_control_char);
+ init_request("cc", control_char);
+ init_request("ce", center);
+ init_request("cu", continuous_underline);
+ init_request("ev", environment_switch);
+ init_request("evc", environment_copy);
+ init_request("fam", family_change);
+ init_request("fc", field_characters);
+ init_request("fi", fill);
+ init_request("fcolor", fill_color_change);
+ init_request("ft", font_change);
+ init_request("gcolor", glyph_color_change);
+ init_request("hc", hyphen_char);
+ init_request("hlm", hyphen_line_max_request);
+ init_request("hy", hyphenate_request);
+ init_request("hym", hyphenation_margin_request);
+ init_request("hys", hyphenation_space_request);
+ init_request("in", indent);
+ init_request("it", input_trap);
+ init_request("itc", input_trap_continued);
+ init_request("lc", leader_character);
+ init_request("linetabs", line_tabs_request);
+ init_request("ll", line_length);
+ init_request("ls", line_spacing);
+ init_request("lt", title_length);
+ init_request("mc", margin_character);
+ init_request("na", no_adjust);
+ init_request("nf", no_fill);
+ init_request("nh", no_hyphenate);
+ init_request("nm", number_lines);
+ init_request("nn", no_number);
+ init_request("pev", print_env);
+ init_request("ps", point_size);
+ init_request("pvs", post_vertical_spacing);
+ init_request("rj", right_justify);
+ init_request("sizes", override_sizes);
+ init_request("ss", space_size);
+ init_request("ta", set_tabs);
+ init_request("ti", temporary_indent);
+ init_request("tc", tab_character);
+ init_request("tl", title);
+ init_request("ul", underline);
+ init_request("vs", vertical_spacing);
+#ifdef WIDOW_CONTROL
+ init_request("wdc", widow_control_request);
+#endif /* WIDOW_CONTROL */
+ init_int_env_reg(".b", get_bold);
+ init_vunits_env_reg(".cdp", get_prev_char_depth);
+ init_int_env_reg(".ce", get_center_lines);
+ init_vunits_env_reg(".cht", get_prev_char_height);
+ init_hunits_env_reg(".csk", get_prev_char_skew);
+ init_string_env_reg(".ev", get_name_string);
+ init_int_env_reg(".f", get_font);
+ init_string_env_reg(".fam", get_font_family_string);
+ init_string_env_reg(".fn", get_font_name_string);
+ init_int_env_reg(".height", get_char_height);
+ init_int_env_reg(".hlc", get_hyphen_line_count);
+ init_int_env_reg(".hlm", get_hyphen_line_max);
+ init_int_env_reg(".hy", get_hyphenation_flags);
+ init_hunits_env_reg(".hym", get_hyphenation_margin);
+ init_hunits_env_reg(".hys", get_hyphenation_space);
+ init_hunits_env_reg(".i", get_indent);
+ init_hunits_env_reg(".in", get_saved_indent);
+ init_int_env_reg(".int", get_prev_line_interrupted);
+ init_int_env_reg(".linetabs", get_line_tabs);
+ init_hunits_env_reg(".lt", get_title_length);
+ init_int_env_reg(".j", get_adjust_mode);
+ init_hunits_env_reg(".k", get_text_length);
+ init_int_env_reg(".L", get_line_spacing);
+ init_hunits_env_reg(".l", get_line_length);
+ init_hunits_env_reg(".ll", get_saved_line_length);
+ init_string_env_reg(".M", get_fill_color_string);
+ init_string_env_reg(".m", get_glyph_color_string);
+ init_hunits_env_reg(".n", get_prev_text_length);
+ init_int_env_reg(".nm", get_numbering_nodes);
+ init_int_env_reg(".nn", get_no_number_count);
+ init_int_env_reg(".ps", get_point_size);
+ init_int_env_reg(".psr", get_requested_point_size);
+ init_vunits_env_reg(".pvs", get_post_vertical_spacing);
+ init_int_env_reg(".rj", get_right_justify_lines);
+ init_string_env_reg(".s", get_point_size_string);
+ init_int_env_reg(".slant", get_char_slant);
+ init_int_env_reg(".ss", get_space_size);
+ init_int_env_reg(".sss", get_sentence_space_size);
+ init_string_env_reg(".sr", get_requested_point_size_string);
+ init_string_env_reg(".sty", get_style_name_string);
+ init_string_env_reg(".tabs", get_tabs);
+ init_int_env_reg(".u", get_fill);
+ init_vunits_env_reg(".v", get_vertical_spacing);
+ init_hunits_env_reg(".w", get_prev_char_width);
+ init_int_env_reg(".zoom", get_zoom);
+ register_dictionary.define("ct", new variable_reg(&ct_reg_contents));
+ register_dictionary.define("hp", new horizontal_place_reg);
+ register_dictionary.define("ln", new variable_reg(&next_line_number));
+ register_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
+ register_dictionary.define("rst", new variable_reg(&rst_reg_contents));
+ register_dictionary.define("sb", new variable_reg(&sb_reg_contents));
+ register_dictionary.define("skw", new variable_reg(&skw_reg_contents));
+ register_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
+ register_dictionary.define("st", new variable_reg(&st_reg_contents));
+}
+
+// Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
+
+struct trie_node;
+
+class trie {
+ trie_node *tp;
+ virtual void do_match(int len, void *val) = 0;
+ virtual void do_delete(void *) = 0;
+ void delete_trie_node(trie_node *);
+public:
+ trie() : tp(0) {}
+ virtual ~trie(); // virtual to shut up g++
+ void insert(const char *, int, void *);
+ // find calls do_match for each match it finds
+ void find(const char *pat, int patlen);
+ void clear();
+};
+
+class hyphen_trie : private trie {
+ int *h;
+ void do_match(int i, void *v);
+ void do_delete(void *v);
+ void insert_pattern(const char *pat, int patlen, int *num);
+ void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
+ int hpf_getc(FILE *f);
+public:
+ hyphen_trie() {}
+ ~hyphen_trie() {}
+ void hyphenate(const char *word, int len, int *hyphens);
+ void read_patterns_file(const char *name, int append, dictionary *ex);
+};
+
+struct hyphenation_language {
+ symbol name;
+ dictionary exceptions;
+ hyphen_trie patterns;
+ hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
+ ~hyphenation_language() { }
+};
+
+dictionary language_dictionary(5);
+hyphenation_language *current_language = 0;
+
+static void set_hyphenation_language()
+{
+ symbol nm = get_name(true /* required */);
+ if (!nm.is_null()) {
+ current_language = (hyphenation_language *)language_dictionary.lookup(nm);
+ if (!current_language) {
+ current_language = new hyphenation_language(nm);
+ (void)language_dictionary.lookup(nm, (void *)current_language);
+ }
+ }
+ skip_line();
+}
+
+const int WORD_MAX = 256; // we use unsigned char for offsets in
+ // hyphenation exceptions
+
+static void hyphen_word()
+{
+ if (!current_language) {
+ error("no current hyphenation language");
+ skip_line();
+ return;
+ }
+ char buf[WORD_MAX + 1];
+ unsigned char pos[WORD_MAX + 2];
+ for (;;) {
+ tok.skip();
+ if (tok.is_newline() || tok.is_eof())
+ break;
+ int i = 0;
+ int npos = 0;
+ while (i < WORD_MAX && !tok.is_space() && !tok.is_newline()
+ && !tok.is_eof()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_line();
+ return;
+ }
+ tok.next();
+ if (ci->get_ascii_code() == '-') {
+ if (i > 0 && (npos == 0 || pos[npos - 1] != i))
+ pos[npos++] = i;
+ }
+ else {
+ unsigned char c = ci->get_hyphenation_code();
+ if (c == 0)
+ break;
+ buf[i++] = c;
+ }
+ }
+ if (i > 0) {
+ pos[npos] = 0;
+ buf[i] = 0;
+ unsigned char *tem = new unsigned char[npos + 1];
+ memcpy(tem, pos, npos + 1);
+ tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
+ tem);
+ if (tem)
+ delete[] tem;
+ }
+ }
+ skip_line();
+}
+
+struct trie_node {
+ char c;
+ trie_node *down;
+ trie_node *right;
+ void *val;
+ trie_node(char, trie_node *);
+};
+
+trie_node::trie_node(char ch, trie_node *p)
+: c(ch), down(0), right(p), val(0)
+{
+}
+
+trie::~trie()
+{
+ clear();
+}
+
+void trie::clear()
+{
+ delete_trie_node(tp);
+ tp = 0;
+}
+
+
+void trie::delete_trie_node(trie_node *p)
+{
+ if (p) {
+ delete_trie_node(p->down);
+ delete_trie_node(p->right);
+ if (p->val)
+ do_delete(p->val);
+ delete p;
+ }
+}
+
+void trie::insert(const char *pat, int patlen, void *val)
+{
+ trie_node **p = &tp;
+ assert(patlen > 0 && pat != 0);
+ for (;;) {
+ while (*p != 0 && (*p)->c < pat[0])
+ p = &((*p)->right);
+ if (*p == 0 || (*p)->c != pat[0])
+ *p = new trie_node(pat[0], *p);
+ if (--patlen == 0) {
+ (*p)->val = val;
+ break;
+ }
+ ++pat;
+ p = &((*p)->down);
+ }
+}
+
+void trie::find(const char *pat, int patlen)
+{
+ trie_node *p = tp;
+ for (int i = 0; p != 0 && i < patlen; i++) {
+ while (p != 0 && p->c < pat[i])
+ p = p->right;
+ if (p != 0 && p->c == pat[i]) {
+ if (p->val != 0)
+ do_match(i+1, p->val);
+ p = p->down;
+ }
+ else
+ break;
+ }
+}
+
+struct operation {
+ operation *next;
+ short distance;
+ short num;
+ operation(int, int, operation *);
+};
+
+operation::operation(int i, int j, operation *op)
+: next(op), distance(j), num(i)
+{
+}
+
+void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
+{
+ operation *op = 0;
+ for (int i = 0; i < patlen+1; i++)
+ if (num[i] != 0)
+ op = new operation(num[i], patlen - i, op);
+ insert(pat, patlen, op);
+}
+
+void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
+ int patlen)
+{
+ char buf[WORD_MAX + 2];
+ unsigned char pos[WORD_MAX + 2];
+ int i = 0, j = 0;
+ int npos = 0;
+ while (j < patlen) {
+ unsigned char c = pat[j++];
+ if (c == '-') {
+ if (i > 0 && (npos == 0 || pos[npos - 1] != i))
+ pos[npos++] = i;
+ }
+ else if (c == ' ')
+ buf[i++] = ' ';
+ else
+ buf[i++] = hpf_code_table[c];
+ }
+ if (i > 0) {
+ pos[npos] = 0;
+ buf[i] = 0;
+ unsigned char *tem = new unsigned char[npos + 1];
+ memcpy(tem, pos, npos + 1);
+ tem = (unsigned char *)ex->lookup(symbol(buf), tem);
+ if (tem)
+ delete[] tem;
+ }
+}
+
+void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
+{
+ int j;
+ for (j = 0; j < len + 1; j++)
+ hyphens[j] = 0;
+ for (j = 0; j < len - 1; j++) {
+ h = hyphens + j;
+ find(word + j, len - j);
+ }
+}
+
+inline int max(int m, int n)
+{
+ return m > n ? m : n;
+}
+
+void hyphen_trie::do_match(int i, void *v)
+{
+ operation *op = (operation *)v;
+ while (op != 0) {
+ h[i - op->distance] = max(h[i - op->distance], op->num);
+ op = op->next;
+ }
+}
+
+void hyphen_trie::do_delete(void *v)
+{
+ operation *op = (operation *)v;
+ while (op) {
+ operation *tem = op;
+ op = tem->next;
+ delete tem;
+ }
+}
+
+/* We use very simple rules to parse TeX's hyphenation patterns.
+
+ . '%' starts a comment even if preceded by '\'.
+
+ . No support for digraphs and like '\$'.
+
+ . '^^xx' ('x' is 0-9 or a-f), and '^^x' (character code of 'x' in the
+ range 0-127) are recognized; other use of '^' causes an error.
+
+ . No macro expansion.
+
+ . We check for the expression '\patterns{...}' (possibly with
+ whitespace before and after the braces). Everything between the
+ braces is taken as hyphenation patterns. Consequently, '{' and '}'
+ are not allowed in patterns.
+
+ . Similarly, '\hyphenation{...}' gives a list of hyphenation
+ exceptions.
+
+ . '\endinput' is recognized also.
+
+ . For backwards compatibility, if '\patterns' is missing, the
+ whole file is treated as a list of hyphenation patterns (only
+ recognizing '%' as the start of a comment.
+
+*/
+
+int hyphen_trie::hpf_getc(FILE *f)
+{
+ int c = getc(f);
+ int c1;
+ int cc = 0;
+ if (c != '^')
+ return c;
+ c = getc(f);
+ if (c != '^')
+ goto fail;
+ c = getc(f);
+ c1 = getc(f);
+ if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
+ && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
+ if (c >= '0' && c <= '9')
+ c -= '0';
+ else
+ c = c - 'a' + 10;
+ if (c1 >= '0' && c1 <= '9')
+ c1 -= '0';
+ else
+ c1 = c1 - 'a' + 10;
+ cc = c * 16 + c1;
+ }
+ else {
+ ungetc(c1, f);
+ if (c >= 0 && c <= 63)
+ cc = c + 64;
+ else if (c >= 64 && c <= 127)
+ cc = c - 64;
+ else
+ goto fail;
+ }
+ return cc;
+fail:
+ error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
+ return c;
+}
+
+void hyphen_trie::read_patterns_file(const char *name, int append,
+ dictionary *ex)
+{
+ if (!append)
+ clear();
+ char buf[WORD_MAX + 1];
+ for (int i = 0; i < WORD_MAX + 1; i++)
+ buf[i] = 0;
+ int num[WORD_MAX + 1];
+ errno = 0;
+ char *path = 0;
+ FILE *fp = mac_path->open_file(name, &path);
+ if (fp == 0) {
+ error("can't find hyphenation patterns file '%1'", name);
+ return;
+ }
+ int c = hpf_getc(fp);
+ int have_patterns = 0; // we've seen \patterns
+ int final_pattern = 0; // 1 if we have a trailing closing brace
+ int have_hyphenation = 0; // we've seen \hyphenation
+ int final_hyphenation = 0; // 1 if we have a trailing closing brace
+ int have_keyword = 0; // we've seen either \patterns or \hyphenation
+ int traditional = 0; // don't handle \patterns
+ for (;;) {
+ for (;;) {
+ if (c == '%') { // skip comments
+ do {
+ c = getc(fp);
+ } while (c != EOF && c != '\n');
+ }
+ if (c == EOF || !csspace(c))
+ break;
+ c = hpf_getc(fp);
+ }
+ if (c == EOF) {
+ if (have_keyword || traditional) // we are done
+ break;
+ else { // rescan file in 'traditional' mode
+ rewind(fp);
+ traditional = 1;
+ c = hpf_getc(fp);
+ continue;
+ }
+ }
+ int i = 0;
+ num[0] = 0;
+ if (!(c == '{' || c == '}')) { // skip braces at line start
+ do { // scan patterns
+ if (csdigit(c))
+ num[i] = c - '0';
+ else {
+ buf[i++] = c;
+ num[i] = 0;
+ }
+ c = hpf_getc(fp);
+ } while (i < WORD_MAX && c != EOF && !csspace(c)
+ && c != '%' && c != '{' && c != '}');
+ }
+ if (!traditional) {
+ if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
+ while (csspace(c))
+ c = hpf_getc(fp);
+ if (c == '{') {
+ if (have_patterns || have_hyphenation)
+ error("\\patterns is not allowed inside of %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ else {
+ have_patterns = 1;
+ have_keyword = 1;
+ }
+ c = hpf_getc(fp);
+ continue;
+ }
+ }
+ else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
+ while (csspace(c))
+ c = hpf_getc(fp);
+ if (c == '{') {
+ if (have_patterns || have_hyphenation)
+ error("\\hyphenation is not allowed inside of %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ else {
+ have_hyphenation = 1;
+ have_keyword = 1;
+ }
+ c = hpf_getc(fp);
+ continue;
+ }
+ }
+ else if (strstr(buf, "\\endinput")) {
+ if (have_patterns || have_hyphenation)
+ error("found \\endinput inside of %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ break;
+ }
+ else if (c == '}') {
+ if (have_patterns) {
+ have_patterns = 0;
+ if (i > 0)
+ final_pattern = 1;
+ }
+ else if (have_hyphenation) {
+ have_hyphenation = 0;
+ if (i > 0)
+ final_hyphenation = 1;
+ }
+ c = hpf_getc(fp);
+ }
+ else if (c == '{') {
+ if (have_patterns || have_hyphenation)
+ error("'{' is not allowed within %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ c = hpf_getc(fp); // skipped if not starting \patterns
+ // or \hyphenation
+ }
+ }
+ else {
+ if (c == '{' || c == '}')
+ c = hpf_getc(fp);
+ }
+ if (i > 0) {
+ if (have_patterns || final_pattern || traditional) {
+ for (int j = 0; j < i; j++)
+ buf[j] = hpf_code_table[(unsigned char)buf[j]];
+ insert_pattern(buf, i, num);
+ final_pattern = 0;
+ }
+ else if (have_hyphenation || final_hyphenation) {
+ // hyphenation exceptions in a pattern file are subject to `.hy'
+ // restrictions; we mark such entries with a trailing space
+ buf[i++] = ' ';
+ insert_hyphenation(ex, buf, i);
+ final_hyphenation = 0;
+ }
+ }
+ }
+ fclose(fp);
+ free(path);
+ return;
+}
+
+void hyphenate(hyphen_list *h, unsigned flags)
+{
+ if (!current_language)
+ return;
+ while (h) {
+ while (h && h->hyphenation_code == 0)
+ h = h->next;
+ int len = 0;
+ char hbuf[WORD_MAX + 2];
+ char *buf = hbuf + 1;
+ hyphen_list *tem;
+ for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
+ if (tem->hyphenation_code != 0)
+ buf[len++] = tem->hyphenation_code;
+ else
+ break;
+ }
+ hyphen_list *nexth = tem;
+ if (len >= 2) {
+ // check `.hw' entries
+ buf[len] = 0;
+ unsigned char *pos
+ = (unsigned char *)current_language->exceptions.lookup(buf);
+ if (pos != 0) {
+ int j = 0;
+ int i = 1;
+ for (tem = h; tem != 0; tem = tem->next, i++)
+ if (pos[j] == i) {
+ tem->hyphen = 1;
+ j++;
+ }
+ }
+ else {
+ // check `\hyphenation' entries from pattern files;
+ // such entries are marked with a trailing space
+ buf[len] = ' ';
+ buf[len + 1] = 0;
+ pos = (unsigned char *)current_language->exceptions.lookup(buf);
+ if (pos != 0) {
+ int j = 0;
+ int i = 1;
+ tem = h;
+ if (pos[j] == i) {
+ if (flags & HYPHEN_FIRST_CHAR)
+ tem->hyphen = 1;
+ j++;
+ }
+ tem = tem->next;
+ i++;
+ if (pos[j] == i) {
+ if (!(flags & HYPHEN_NOT_FIRST_CHARS))
+ tem->hyphen = 1;
+ j++;
+ }
+ tem = tem->next;
+ i++;
+ if (!(flags & HYPHEN_LAST_CHAR))
+ --len;
+ if (flags & HYPHEN_NOT_LAST_CHARS)
+ --len;
+ for (; i < len && tem; tem = tem->next, i++)
+ if (pos[j] == i) {
+ tem->hyphen = 1;
+ j++;
+ }
+ }
+ else {
+ hbuf[0] = hbuf[len + 1] = '.';
+ int num[WORD_MAX + 3];
+ current_language->patterns.hyphenate(hbuf, len + 2, num);
+ // The position of a hyphenation point gets marked with an odd
+ // number. Example:
+ //
+ // hbuf: . h e l p f u l .
+ // num: 0 0 0 2 4 3 0 0 0 0
+ if (!(flags & HYPHEN_FIRST_CHAR))
+ num[2] = 0;
+ if (flags & HYPHEN_NOT_FIRST_CHARS)
+ num[3] = 0;
+ if (flags & HYPHEN_LAST_CHAR)
+ ++len;
+ if (flags & HYPHEN_NOT_LAST_CHARS)
+ --len;
+ int i;
+ for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
+ if (num[i] & 1)
+ tem->hyphen = 1;
+ }
+ }
+ }
+ h = nexth;
+ }
+}
+
+static void do_hyphenation_patterns_file(bool append)
+{
+ symbol name = get_long_name(true /* required */);
+ if (!name.is_null()) {
+ if (!current_language)
+ error("no current hyphenation language");
+ else
+ current_language->patterns.read_patterns_file(
+ name.contents(), append,
+ &current_language->exceptions);
+ }
+ skip_line();
+}
+
+static void hyphenation_patterns_file()
+{
+ do_hyphenation_patterns_file(false /* append */);
+}
+
+static void hyphenation_patterns_file_append()
+{
+ do_hyphenation_patterns_file(true /* append */);
+}
+
+class hyphenation_language_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *hyphenation_language_reg::get_string()
+{
+ return current_language ? current_language->name.contents() : "";
+}
+
+void init_hyphen_requests()
+{
+ init_request("hw", hyphen_word);
+ init_request("hla", set_hyphenation_language);
+ init_request("hpf", hyphenation_patterns_file);
+ init_request("hpfa", hyphenation_patterns_file_append);
+ register_dictionary.define(".hla", new hyphenation_language_reg);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/env.h b/src/roff/troff/env.h
new file mode 100644
index 0000000..f6c1d21
--- /dev/null
+++ b/src/roff/troff/env.h
@@ -0,0 +1,421 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class statem;
+
+struct size_range {
+ int min;
+ int max;
+};
+
+class font_size {
+ static size_range *size_table;
+ static int nranges;
+ int p;
+public:
+ font_size();
+ font_size(int points);
+ int to_points();
+ int to_scaled_points();
+ int to_units();
+ int operator==(font_size);
+ int operator!=(font_size);
+ static void init_size_table(int *sizes);
+};
+
+inline font_size::font_size() : p(0)
+{
+}
+
+inline int font_size::operator==(font_size fs)
+{
+ return p == fs.p;
+}
+
+inline int font_size::operator!=(font_size fs)
+{
+ return p != fs.p;
+}
+
+inline int font_size::to_scaled_points()
+{
+ return p;
+}
+
+inline int font_size::to_points()
+{
+ return p/sizescale;
+}
+
+class environment;
+
+hunits env_digit_width(environment *);
+hunits env_space_width(environment *);
+hunits env_sentence_space_width(environment *);
+hunits env_narrow_space_width(environment *);
+hunits env_half_narrow_space_width(environment *);
+int env_get_zoom(environment *);
+
+struct tab;
+
+enum tab_type { TAB_NONE, TAB_LEFT, TAB_CENTER, TAB_RIGHT };
+
+class tab_stops {
+ tab *initial_list;
+ tab *repeated_list;
+public:
+ tab_stops();
+ tab_stops(hunits distance, tab_type type);
+ tab_stops(const tab_stops &);
+ ~tab_stops();
+ void operator=(const tab_stops &);
+ tab_type distance_to_next_tab(hunits pos, hunits *distance);
+ tab_type distance_to_next_tab(hunits curpos, hunits *distance, hunits *leftpos);
+ void clear();
+ void add_tab(hunits pos, tab_type type, int repeated);
+ const char *to_string();
+};
+
+const unsigned MARGIN_CHARACTER_ON = 1;
+const unsigned MARGIN_CHARACTER_NEXT = 2;
+
+class charinfo;
+struct node;
+struct breakpoint;
+class font_family;
+class pending_output_line;
+
+// declarations to avoid friend name injection problems
+void title_length();
+void space_size();
+void fill();
+void no_fill();
+void adjust();
+void no_adjust();
+void center();
+void right_justify();
+void vertical_spacing();
+void post_vertical_spacing();
+void line_spacing();
+void line_length();
+void indent();
+void temporary_indent();
+void do_underline(int);
+void do_input_trap(int);
+void set_tabs();
+void margin_character();
+void no_number();
+void number_lines();
+void leader_character();
+void tab_character();
+void hyphenate_request();
+void no_hyphenate();
+void hyphen_line_max_request();
+void hyphenation_space_request();
+void hyphenation_margin_request();
+void line_width();
+#if 0
+void tabs_save();
+void tabs_restore();
+#endif
+void line_tabs_request();
+void title();
+#ifdef WIDOW_CONTROL
+void widow_control_request();
+#endif /* WIDOW_CONTROL */
+
+class environment {
+ int dummy; // dummy environment used for \w
+ hunits prev_line_length;
+ hunits line_length;
+ hunits prev_title_length;
+ hunits title_length;
+ font_size prev_size;
+ font_size size;
+ int requested_size;
+ int prev_requested_size;
+ int char_height;
+ int char_slant;
+ int prev_fontno;
+ int fontno;
+ font_family *prev_family;
+ font_family *family;
+ int space_size; // in 36ths of an em
+ int sentence_space_size; // same but for spaces at the end of sentences
+ int adjust_mode;
+ int fill;
+ int interrupted;
+ int prev_line_interrupted;
+ int center_lines;
+ int right_justify_lines;
+ vunits prev_vertical_spacing;
+ vunits vertical_spacing;
+ vunits prev_post_vertical_spacing;
+ vunits post_vertical_spacing;
+ int prev_line_spacing;
+ int line_spacing;
+ hunits prev_indent;
+ hunits indent;
+ hunits temporary_indent;
+ int have_temporary_indent;
+ hunits saved_indent;
+ hunits target_text_length;
+ int pre_underline_fontno;
+ int underline_lines;
+ int underline_spaces;
+ symbol input_trap;
+ int input_trap_count;
+ int continued_input_trap;
+ node *line; // in reverse order
+ hunits prev_text_length;
+ hunits width_total;
+ int space_total;
+ hunits input_line_start;
+ node *tab_contents;
+ hunits tab_width;
+ hunits tab_distance;
+ int line_tabs;
+ tab_type current_tab;
+ node *leader_node;
+ charinfo *tab_char;
+ charinfo *leader_char;
+ int current_field; // is there a current field?
+ hunits field_distance;
+ hunits pre_field_width;
+ int field_spaces;
+ int tab_field_spaces;
+ int tab_precedes_field;
+ int discarding;
+ int spread_flag; // set by \p
+ unsigned margin_character_flags;
+ node *margin_character_node;
+ hunits margin_character_distance;
+ node *numbering_nodes;
+ hunits line_number_digit_width;
+ int number_text_separation; // in digit spaces
+ int line_number_indent; // in digit spaces
+ int line_number_multiple;
+ int no_number_count;
+ unsigned hyphenation_flags;
+ int hyphen_line_count;
+ int hyphen_line_max;
+ hunits hyphenation_space;
+ hunits hyphenation_margin;
+ int composite; // used for construction of composite char?
+ pending_output_line *pending_lines;
+#ifdef WIDOW_CONTROL
+ int widow_control;
+#endif /* WIDOW_CONTROL */
+ color *glyph_color;
+ color *prev_glyph_color;
+ color *fill_color;
+ color *prev_fill_color;
+
+ tab_type distance_to_next_tab(hunits *);
+ tab_type distance_to_next_tab(hunits *distance, hunits *leftpos);
+ void start_line();
+ void output_line(node *, hunits, int);
+ void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width, int was_centered);
+ void output_title(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width);
+#ifdef WIDOW_CONTROL
+ void mark_last_line();
+#endif /* WIDOW_CONTROL */
+ breakpoint *choose_breakpoint();
+ void hyphenate_line(int start_here = 0);
+ void start_field();
+ void wrap_up_field();
+ void add_padding();
+ node *make_tab_node(hunits d, node *next = 0);
+ node *get_prev_char();
+public:
+ int seen_space;
+ int seen_eol;
+ int suppress_next_eol;
+ int seen_break;
+ tab_stops tabs;
+ const symbol name;
+ unsigned char control_char;
+ unsigned char no_break_control_char;
+ charinfo *hyphen_indicator_char;
+
+ environment(symbol);
+ environment(const environment *); // for temporary environment
+ ~environment();
+ statem *construct_state(int only_eol);
+ void print_env();
+ void copy(const environment *);
+ int is_dummy() { return dummy; }
+ int is_empty();
+ int is_composite() { return composite; }
+ void set_composite() { composite = 1; }
+ vunits get_vertical_spacing(); // .v
+ vunits get_post_vertical_spacing(); // .pvs
+ int get_line_spacing(); // .L
+ vunits total_post_vertical_spacing();
+ int get_point_size() { return size.to_scaled_points(); }
+ font_size get_font_size() { return size; }
+ int get_size() { return size.to_units(); }
+ int get_requested_point_size() { return requested_size; }
+ int get_char_height() { return char_height; }
+ int get_char_slant() { return char_slant; }
+ hunits get_digit_width();
+ int get_font() { return fontno; }; // .f
+ int get_zoom(); // .zoom
+ int get_numbering_nodes(); // .nm
+ font_family *get_family() { return family; }
+ int get_bold(); // .b
+ int get_adjust_mode(); // .j
+ int get_fill(); // .u
+ hunits get_indent(); // .i
+ hunits get_temporary_indent();
+ hunits get_line_length(); // .l
+ hunits get_saved_line_length(); // .ll
+ hunits get_saved_indent(); // .in
+ hunits get_title_length();
+ hunits get_prev_char_width(); // .w
+ hunits get_prev_char_skew();
+ vunits get_prev_char_height();
+ vunits get_prev_char_depth();
+ hunits get_text_length(); // .k
+ hunits get_prev_text_length(); // .n
+ hunits get_space_width() { return env_space_width(this); }
+ int get_space_size() { return space_size; } // in ems/36
+ int get_sentence_space_size() { return sentence_space_size; }
+ hunits get_narrow_space_width() { return env_narrow_space_width(this); }
+ hunits get_half_narrow_space_width()
+ { return env_half_narrow_space_width(this); }
+ hunits get_input_line_position();
+ const char *get_tabs();
+ int get_line_tabs();
+ int get_hyphenation_flags();
+ int get_hyphen_line_max();
+ int get_hyphen_line_count();
+ hunits get_hyphenation_space();
+ hunits get_hyphenation_margin();
+ int get_center_lines();
+ int get_right_justify_lines();
+ int get_no_number_count();
+ int get_prev_line_interrupted() { return prev_line_interrupted; }
+ color *get_fill_color();
+ color *get_glyph_color();
+ color *get_prev_glyph_color();
+ color *get_prev_fill_color();
+ void set_glyph_color(color *c);
+ void set_fill_color(color *c);
+ node *make_char_node(charinfo *);
+ node *extract_output_line();
+ void width_registers();
+ void wrap_up_tab();
+ bool set_font(int);
+ bool set_font(symbol);
+ void set_family(symbol);
+ void set_size(int);
+ void set_char_height(int);
+ void set_char_slant(int);
+ void set_input_line_position(hunits); // used by \n(hp
+ void interrupt();
+ void spread() { spread_flag = 1; }
+ void possibly_break_line(int start_here = 0, int forced = 0);
+ void do_break(int spread = 0); // .br
+ void final_break();
+ node *make_tag(const char *name, int i);
+ void newline();
+ void handle_tab(int is_leader = 0); // do a tab or leader
+ void add_node(node *);
+ void add_char(charinfo *);
+ void add_hyphen_indicator();
+ void add_italic_correction();
+ void space();
+ void space(hunits, hunits);
+ void space_newline();
+ const char *get_glyph_color_string();
+ const char *get_fill_color_string();
+ const char *get_font_family_string();
+ const char *get_font_name_string();
+ const char *get_style_name_string();
+ const char *get_name_string();
+ const char *get_point_size_string();
+ const char *get_requested_point_size_string();
+ void output_pending_lines();
+ void construct_format_state(node *n, int was_centered, int fill);
+ void construct_new_line_state(node *n);
+ void dump_troff_state();
+
+ friend void title_length();
+ friend void space_size();
+ friend void fill();
+ friend void no_fill();
+ friend void adjust();
+ friend void no_adjust();
+ friend void center();
+ friend void right_justify();
+ friend void vertical_spacing();
+ friend void post_vertical_spacing();
+ friend void line_spacing();
+ friend void line_length();
+ friend void indent();
+ friend void temporary_indent();
+ friend void do_underline(int);
+ friend void do_input_trap(int);
+ friend void set_tabs();
+ friend void margin_character();
+ friend void no_number();
+ friend void number_lines();
+ friend void leader_character();
+ friend void tab_character();
+ friend void hyphenate_request();
+ friend void no_hyphenate();
+ friend void hyphen_line_max_request();
+ friend void hyphenation_space_request();
+ friend void hyphenation_margin_request();
+ friend void line_width();
+#if 0
+ friend void tabs_save();
+ friend void tabs_restore();
+#endif
+ friend void line_tabs_request();
+ friend void title();
+#ifdef WIDOW_CONTROL
+ friend void widow_control_request();
+#endif /* WIDOW_CONTROL */
+
+ friend void do_divert(int append, int boxing);
+};
+
+extern environment *curenv;
+extern void pop_env();
+extern void push_env(int);
+
+void init_environments();
+void read_hyphen_file(const char *name);
+
+extern double spread_limit;
+
+extern int break_flag;
+extern symbol default_family;
+extern int translate_space_to_dummy;
+
+extern unsigned char hpf_code_table[];
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/hvunits.h b/src/roff/troff/hvunits.h
new file mode 100644
index 0000000..e47a0a0
--- /dev/null
+++ b/src/roff/troff/hvunits.h
@@ -0,0 +1,339 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+class vunits {
+ int n;
+public:
+ vunits();
+ vunits(units);
+ units to_units();
+ int is_zero();
+ vunits& operator+=(const vunits&);
+ vunits& operator-=(const vunits&);
+ friend inline vunits scale(vunits n, units x, units y); // scale n by x/y
+ friend inline vunits scale(vunits n, vunits x, vunits y);
+ friend inline vunits operator +(const vunits&, const vunits&);
+ friend inline vunits operator -(const vunits&, const vunits&);
+ friend inline vunits operator -(const vunits&);
+ friend inline int operator /(const vunits&, const vunits&);
+ friend inline vunits operator /(const vunits&, int);
+ friend inline vunits operator *(const vunits&, int);
+ friend inline vunits operator *(int, const vunits&);
+ friend inline int operator <(const vunits&, const vunits&);
+ friend inline int operator >(const vunits&, const vunits&);
+ friend inline int operator <=(const vunits&, const vunits&);
+ friend inline int operator >=(const vunits&, const vunits&);
+ friend inline int operator ==(const vunits&, const vunits&);
+ friend inline int operator !=(const vunits&, const vunits&);
+};
+
+extern const vunits V0;
+
+
+class hunits {
+ int n;
+public:
+ hunits();
+ hunits(units);
+ units to_units();
+ int is_zero();
+ hunits& operator+=(const hunits&);
+ hunits& operator-=(const hunits&);
+ friend inline hunits scale(hunits n, units x, units y); // scale n by x/y
+ friend inline hunits scale(hunits n, double x);
+ friend inline hunits operator +(const hunits&, const hunits&);
+ friend inline hunits operator -(const hunits&, const hunits&);
+ friend inline hunits operator -(const hunits&);
+ friend inline int operator /(const hunits&, const hunits&);
+ friend inline hunits operator /(const hunits&, int);
+ friend inline hunits operator *(const hunits&, int);
+ friend inline hunits operator *(int, const hunits&);
+ friend inline int operator <(const hunits&, const hunits&);
+ friend inline int operator >(const hunits&, const hunits&);
+ friend inline int operator <=(const hunits&, const hunits&);
+ friend inline int operator >=(const hunits&, const hunits&);
+ friend inline int operator ==(const hunits&, const hunits&);
+ friend inline int operator !=(const hunits&, const hunits&);
+};
+
+extern const hunits H0;
+
+extern int get_vunits(vunits *, unsigned char si);
+extern int get_hunits(hunits *, unsigned char si);
+extern int get_vunits(vunits *, unsigned char si, vunits prev_value);
+extern int get_hunits(hunits *, unsigned char si, hunits prev_value);
+
+inline vunits:: vunits() : n(0)
+{
+}
+
+inline units vunits::to_units()
+{
+ return n*vresolution;
+}
+
+inline int vunits::is_zero()
+{
+ return n == 0;
+}
+
+inline vunits operator +(const vunits & x, const vunits & y)
+{
+ vunits r;
+ r = x;
+ r.n += y.n;
+ return r;
+}
+
+inline vunits operator -(const vunits & x, const vunits & y)
+{
+ vunits r;
+ r = x;
+ r.n -= y.n;
+ return r;
+}
+
+inline vunits operator -(const vunits & x)
+{
+ vunits r;
+ r.n = -x.n;
+ return r;
+}
+
+inline int operator /(const vunits & x, const vunits & y)
+{
+ return x.n/y.n;
+}
+
+inline vunits operator /(const vunits & x, int n)
+{
+ vunits r;
+ r = x;
+ r.n /= n;
+ return r;
+}
+
+inline vunits operator *(const vunits & x, int n)
+{
+ vunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline vunits operator *(int n, const vunits & x)
+{
+ vunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline int operator <(const vunits & x, const vunits & y)
+{
+ return x.n < y.n;
+}
+
+inline int operator >(const vunits & x, const vunits & y)
+{
+ return x.n > y.n;
+}
+
+inline int operator <=(const vunits & x, const vunits & y)
+{
+ return x.n <= y.n;
+}
+
+inline int operator >=(const vunits & x, const vunits & y)
+{
+ return x.n >= y.n;
+}
+
+inline int operator ==(const vunits & x, const vunits & y)
+{
+ return x.n == y.n;
+}
+
+inline int operator !=(const vunits & x, const vunits & y)
+{
+ return x.n != y.n;
+}
+
+
+inline vunits& vunits::operator+=(const vunits & x)
+{
+ n += x.n;
+ return *this;
+}
+
+inline vunits& vunits::operator-=(const vunits & x)
+{
+ n -= x.n;
+ return *this;
+}
+
+inline hunits:: hunits() : n(0)
+{
+}
+
+inline units hunits::to_units()
+{
+ return n*hresolution;
+}
+
+inline int hunits::is_zero()
+{
+ return n == 0;
+}
+
+inline hunits operator +(const hunits & x, const hunits & y)
+{
+ hunits r;
+ r = x;
+ r.n += y.n;
+ return r;
+}
+
+inline hunits operator -(const hunits & x, const hunits & y)
+{
+ hunits r;
+ r = x;
+ r.n -= y.n;
+ return r;
+}
+
+inline hunits operator -(const hunits & x)
+{
+ hunits r;
+ r = x;
+ r.n = -x.n;
+ return r;
+}
+
+inline int operator /(const hunits & x, const hunits & y)
+{
+ return x.n/y.n;
+}
+
+inline hunits operator /(const hunits & x, int n)
+{
+ hunits r;
+ r = x;
+ r.n /= n;
+ return r;
+}
+
+inline hunits operator *(const hunits & x, int n)
+{
+ hunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline hunits operator *(int n, const hunits & x)
+{
+ hunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline int operator <(const hunits & x, const hunits & y)
+{
+ return x.n < y.n;
+}
+
+inline int operator >(const hunits & x, const hunits & y)
+{
+ return x.n > y.n;
+}
+
+inline int operator <=(const hunits & x, const hunits & y)
+{
+ return x.n <= y.n;
+}
+
+inline int operator >=(const hunits & x, const hunits & y)
+{
+ return x.n >= y.n;
+}
+
+inline int operator ==(const hunits & x, const hunits & y)
+{
+ return x.n == y.n;
+}
+
+inline int operator !=(const hunits & x, const hunits & y)
+{
+ return x.n != y.n;
+}
+
+
+inline hunits& hunits::operator+=(const hunits & x)
+{
+ n += x.n;
+ return *this;
+}
+
+inline hunits& hunits::operator-=(const hunits & x)
+{
+ n -= x.n;
+ return *this;
+}
+
+inline hunits scale(hunits n, units x, units y)
+{
+ hunits r;
+ r.n = scale(n.n, x, y);
+ return r;
+}
+
+inline vunits scale(vunits n, units x, units y)
+{
+ vunits r;
+ r.n = scale(n.n, x, y);
+ return r;
+}
+
+inline vunits scale(vunits n, vunits x, vunits y)
+{
+ vunits r;
+ r.n = scale(n.n, x.n, y.n);
+ return r;
+}
+
+inline hunits scale(hunits n, double x)
+{
+ hunits r;
+ r.n = int(n.n*x);
+ return r;
+}
+
+inline units scale(units n, double x)
+{
+ return int(n*x);
+}
+
+inline units points_to_units(units n)
+{
+ return scale(n, units_per_inch, 72);
+}
+
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
new file mode 100644
index 0000000..292ee73
--- /dev/null
+++ b/src/roff/troff/input.cpp
@@ -0,0 +1,9209 @@
+/* Copyright (C) 1989-2022 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "troff.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "font.h"
+#include "charinfo.h"
+#include "macropath.h"
+#include "input.h"
+#include "defs.h"
+#include "unicode.h"
+#include "curtime.h"
+
+// Needed for getpid() and isatty()
+#include "posix.h"
+
+#include "nonposix.h"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+#define MACRO_PREFIX "tmac."
+#define MACRO_POSTFIX ".tmac"
+#define INITIAL_STARTUP_FILE "troffrc"
+#define FINAL_STARTUP_FILE "troffrc-end"
+#define DEFAULT_INPUT_STACK_LIMIT 1000
+
+#ifndef DEFAULT_WARNING_MASK
+// warnings that are enabled by default
+#define DEFAULT_WARNING_MASK \
+ (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT|WARN_FILE)
+#endif
+
+// initial size of buffer for reading names; expanded as necessary
+#define ABUF_SIZE 16
+
+extern "C" const char *program_name;
+extern "C" const char *Version_string;
+
+#ifdef COLUMN
+void init_column_requests();
+#endif /* COLUMN */
+
+static node *read_draw_node();
+static void read_color_draw_node(token &);
+static void push_token(const token &);
+void copy_file();
+#ifdef COLUMN
+void vjustify();
+#endif /* COLUMN */
+void transparent_file();
+
+token tok;
+int break_flag = 0;
+int class_flag = 0;
+int color_flag = 1; // colors are on by default
+static int backtrace_flag = 0;
+#ifndef POPEN_MISSING
+char *pipe_command = 0;
+#endif
+charinfo *charset_table[256];
+unsigned char hpf_code_table[256];
+
+static int warning_mask = DEFAULT_WARNING_MASK;
+static int inhibit_errors = 0;
+static int ignoring = 0;
+
+static void enable_warning(const char *);
+static void disable_warning(const char *);
+
+static int escape_char = '\\';
+static symbol end_of_input_macro_name;
+static symbol blank_line_macro_name;
+static symbol leading_spaces_macro_name;
+static int compatible_flag = 0;
+static int do_old_compatible_flag = -1; // for .do request
+int ascii_output_flag = 0;
+int suppress_output_flag = 0;
+int is_html = 0;
+int begin_level = 0; // number of nested \O escapes
+
+int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
+ // \O[345], \R, \s, or \S has been processed
+ // in token::next()
+int old_have_input = 0; // value of have_input right before \n
+bool device_has_tcommand = false; // 't' output command supported
+int unsafe_flag = 0; // safer by default
+
+bool have_multiple_params = false; // e.g., \[e aa], \*[foo bar]
+
+double spread_limit = -3.0 - 1.0; // negative means deactivated
+
+double warn_scale;
+char warn_scaling_indicator;
+int debug_state = 0; // turns on debugging of the html troff state
+
+search_path *mac_path = &safer_macro_path;
+
+// Defaults to the current directory.
+search_path include_search_path(0, 0, 0, 1);
+
+static int get_copy(node**, bool = false, bool = false);
+static void copy_mode_error(const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
+static symbol read_escape_parameter(read_mode = NO_ARGS);
+static symbol read_long_escape_parameters(read_mode = NO_ARGS);
+static void interpolate_string(symbol);
+static void interpolate_string_with_args(symbol);
+static void interpolate_macro(symbol, bool = false);
+static void interpolate_number_format(symbol);
+static void interpolate_environment_variable(symbol);
+
+static symbol composite_glyph_name(symbol);
+static void interpolate_arg(symbol);
+static request_or_macro *lookup_request(symbol);
+static int get_delim_number(units *, unsigned char);
+static int get_delim_number(units *, unsigned char, units);
+static symbol do_get_long_name(bool, char);
+static int get_line_arg(units *res, unsigned char si, charinfo **cp);
+static bool read_size(int *);
+static symbol get_delim_name();
+static void init_registers();
+static void trapping_blank_line();
+
+class input_iterator;
+input_iterator *make_temp_iterator(const char *);
+const char *input_char_description(int);
+
+void process_input_stack();
+void chop_macro(); // declare to avoid friend name injection
+
+
+void set_escape_char()
+{
+ if (has_arg()) {
+ if (tok.ch() == 0) {
+ error("cannot select invalid escape character; using '\\'");
+ escape_char = '\\';
+ }
+ else
+ escape_char = tok.ch();
+ }
+ else
+ escape_char = '\\';
+ skip_line();
+}
+
+void escape_off()
+{
+ escape_char = 0;
+ skip_line();
+}
+
+static int saved_escape_char = '\\';
+
+void save_escape_char()
+{
+ saved_escape_char = escape_char;
+ skip_line();
+}
+
+void restore_escape_char()
+{
+ escape_char = saved_escape_char;
+ skip_line();
+}
+
+struct arg_list;
+
+class input_iterator {
+public:
+ input_iterator();
+ input_iterator(int is_div);
+ virtual ~input_iterator() {}
+ int get(node **);
+ friend class input_stack;
+ int is_diversion;
+ statem *diversion_state;
+protected:
+ const unsigned char *ptr;
+ const unsigned char *eptr;
+ input_iterator *next;
+private:
+ virtual int fill(node **);
+ virtual int peek();
+ virtual int has_args() { return 0; }
+ virtual int nargs() { return 0; }
+ virtual input_iterator *get_arg(int) { return 0; }
+ virtual arg_list *get_arg_list() { return 0; }
+ virtual symbol get_macro_name() { return NULL_SYMBOL; }
+ virtual int space_follows_arg(int) { return 0; }
+ virtual int get_break_flag() { return 0; }
+ virtual int get_location(int, const char **, int *) { return 0; }
+ virtual void backtrace() {}
+ virtual int set_location(const char *, int) { return 0; }
+ virtual int next_file(FILE *, const char *) { return 0; }
+ virtual void shift(int) {}
+ virtual int is_boundary() {return 0; }
+ virtual int is_file() { return 0; }
+ virtual int is_macro() { return 0; }
+ virtual void save_compatible_flag(int) {}
+ virtual int get_compatible_flag() { return 0; }
+};
+
+input_iterator::input_iterator()
+: is_diversion(0), ptr(0), eptr(0)
+{
+}
+
+input_iterator::input_iterator(int is_div)
+: is_diversion(is_div), ptr(0), eptr(0)
+{
+}
+
+int input_iterator::fill(node **)
+{
+ return EOF;
+}
+
+int input_iterator::peek()
+{
+ return EOF;
+}
+
+inline int input_iterator::get(node **p)
+{
+ return ptr < eptr ? *ptr++ : fill(p);
+}
+
+class input_boundary : public input_iterator {
+public:
+ int is_boundary() { return 1; }
+};
+
+class input_return_boundary : public input_iterator {
+public:
+ int is_boundary() { return 2; }
+};
+
+class file_iterator : public input_iterator {
+ FILE *fp;
+ int lineno;
+ const char *filename;
+ int popened;
+ int newline_flag;
+ int seen_escape;
+ enum { BUF_SIZE = 512 };
+ unsigned char buf[BUF_SIZE];
+ void close();
+public:
+ file_iterator(FILE *, const char *, int = 0);
+ ~file_iterator();
+ int fill(node **);
+ int peek();
+ int get_location(int, const char **, int *);
+ void backtrace();
+ int set_location(const char *, int);
+ int next_file(FILE *, const char *);
+ int is_file();
+};
+
+file_iterator::file_iterator(FILE *f, const char *fn, int po)
+: fp(f), lineno(1), filename(fn), popened(po),
+ newline_flag(0), seen_escape(0)
+{
+ if ((font::use_charnames_in_special) && (fn != 0)) {
+ if (!the_output)
+ init_output();
+ the_output->put_filename(fn, po);
+ }
+}
+
+file_iterator::~file_iterator()
+{
+ close();
+}
+
+void file_iterator::close()
+{
+ if (fp == stdin)
+ clearerr(stdin);
+#ifndef POPEN_MISSING
+ else if (popened)
+ pclose(fp);
+#endif /* not POPEN_MISSING */
+ else
+ fclose(fp);
+}
+
+int file_iterator::is_file()
+{
+ return 1;
+}
+
+int file_iterator::next_file(FILE *f, const char *s)
+{
+ close();
+ filename = s;
+ fp = f;
+ lineno = 1;
+ newline_flag = 0;
+ seen_escape = 0;
+ popened = 0;
+ ptr = 0;
+ eptr = 0;
+ return 1;
+}
+
+int file_iterator::fill(node **)
+{
+ if (newline_flag)
+ lineno++;
+ newline_flag = 0;
+ unsigned char *p = buf;
+ ptr = p;
+ unsigned char *e = p + BUF_SIZE;
+ while (p < e) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ *p++ = c;
+ if (c == '\n') {
+ seen_escape = 0;
+ newline_flag = 1;
+ break;
+ }
+ seen_escape = (c == '\\');
+ }
+ }
+ if (p > buf) {
+ eptr = p;
+ return *ptr++;
+ }
+ else {
+ eptr = p;
+ return EOF;
+ }
+}
+
+int file_iterator::peek()
+{
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ c = getc(fp);
+ }
+ if (c != EOF)
+ ungetc(c, fp);
+ return c;
+}
+
+int file_iterator::get_location(int /*allow_macro*/,
+ const char **filenamep, int *linenop)
+{
+ *linenop = lineno;
+ if (filename != 0 && strcmp(filename, "-") == 0)
+ *filenamep = "<standard input>";
+ else
+ *filenamep = filename;
+ return 1;
+}
+
+void file_iterator::backtrace()
+{
+ const char *f;
+ int n;
+ // Get side effect of filename rewrite if stdin.
+ (void) get_location(0, &f, &n);
+ if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ errprint("backtrace: %3 '%1':%2\n", f, n, popened ? "pipe" : "file");
+}
+
+int file_iterator::set_location(const char *f, int ln)
+{
+ if (f) {
+ filename = f;
+ if (!the_output)
+ init_output();
+ the_output->put_filename(f, 0);
+ }
+ lineno = ln;
+ return 1;
+}
+
+input_iterator nil_iterator;
+
+class input_stack {
+public:
+ static int get(node **);
+ static int peek();
+ static void push(input_iterator *);
+ static input_iterator *get_arg(int);
+ static arg_list *get_arg_list();
+ static symbol get_macro_name();
+ static int space_follows_arg(int);
+ static int get_break_flag();
+ static int nargs();
+ static int get_location(int, const char **, int *);
+ static int set_location(const char *, int);
+ static void backtrace();
+ static void next_file(FILE *, const char *);
+ static void end_file();
+ static void shift(int n);
+ static void add_boundary();
+ static void add_return_boundary();
+ static int is_return_boundary();
+ static void remove_boundary();
+ static int get_level();
+ static int get_div_level();
+ static void increase_level();
+ static void decrease_level();
+ static void clear();
+ static void pop_macro();
+ static void save_compatible_flag(int);
+ static int get_compatible_flag();
+ static statem *get_diversion_state();
+ static void check_end_diversion(input_iterator *t);
+ static int limit;
+ static int div_level;
+ static statem *diversion_state;
+private:
+ static input_iterator *top;
+ static int level;
+ static int finish_get(node **);
+ static int finish_peek();
+};
+
+input_iterator *input_stack::top = &nil_iterator;
+int input_stack::level = 0;
+int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
+int input_stack::div_level = 0;
+statem *input_stack::diversion_state = 0;
+int suppress_push=0;
+
+
+inline int input_stack::get_level()
+{
+ return level;
+}
+
+inline void input_stack::increase_level()
+{
+ level++;
+}
+
+inline void input_stack::decrease_level()
+{
+ level--;
+}
+
+inline int input_stack::get_div_level()
+{
+ return div_level;
+}
+
+inline int input_stack::get(node **np)
+{
+ int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
+ if (res == '\n') {
+ old_have_input = have_input;
+ have_input = 0;
+ }
+ return res;
+}
+
+int input_stack::finish_get(node **np)
+{
+ for (;;) {
+ int c = top->fill(np);
+ if (c != EOF || top->is_boundary())
+ return c;
+ if (top == &nil_iterator)
+ break;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+#if defined(DEBUGGING)
+ if (debug_state)
+ if (tem->is_diversion)
+ fprintf(stderr,
+ "in diversion level = %d\n", input_stack::get_div_level());
+#endif
+ top = top->next;
+ level--;
+ delete tem;
+ if (top->ptr < top->eptr)
+ return *top->ptr++;
+ }
+ assert(level == 0);
+ return EOF;
+}
+
+inline int input_stack::peek()
+{
+ return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
+}
+
+void input_stack::check_end_diversion(input_iterator *t)
+{
+ if (t->is_diversion) {
+ div_level--;
+ if (diversion_state)
+ delete diversion_state;
+ diversion_state = t->diversion_state;
+ }
+}
+
+int input_stack::finish_peek()
+{
+ for (;;) {
+ int c = top->peek();
+ if (c != EOF || top->is_boundary())
+ return c;
+ if (top == &nil_iterator)
+ break;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ if (top->ptr < top->eptr)
+ return *top->ptr;
+ }
+ assert(level == 0);
+ return EOF;
+}
+
+void input_stack::add_boundary()
+{
+ push(new input_boundary);
+}
+
+void input_stack::add_return_boundary()
+{
+ push(new input_return_boundary);
+}
+
+int input_stack::is_return_boundary()
+{
+ return top->is_boundary() == 2;
+}
+
+void input_stack::remove_boundary()
+{
+ assert(top->is_boundary());
+ input_iterator *temp = top->next;
+ check_end_diversion(top);
+
+ delete top;
+ top = temp;
+ level--;
+}
+
+void input_stack::push(input_iterator *in)
+{
+ if (in == 0)
+ return;
+ if (++level > limit && limit > 0)
+ fatal("input stack limit exceeded (probable infinite loop)");
+ in->next = top;
+ top = in;
+ if (top->is_diversion) {
+ div_level++;
+ in->diversion_state = diversion_state;
+ diversion_state = curenv->construct_state(0);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ curenv->dump_troff_state();
+ fflush(stderr);
+ }
+#endif
+ }
+#if defined(DEBUGGING)
+ if (debug_state)
+ if (top->is_diversion) {
+ fprintf(stderr,
+ "in diversion level = %d\n", input_stack::get_div_level());
+ fflush(stderr);
+ }
+#endif
+}
+
+statem *get_diversion_state()
+{
+ return input_stack::get_diversion_state();
+}
+
+statem *input_stack::get_diversion_state()
+{
+ if (0 == diversion_state)
+ return 0;
+ else
+ return new statem(diversion_state);
+}
+
+input_iterator *input_stack::get_arg(int i)
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_arg(i);
+ return 0;
+}
+
+arg_list *input_stack::get_arg_list()
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_arg_list();
+ return 0;
+}
+
+symbol input_stack::get_macro_name()
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_macro_name();
+ return NULL_SYMBOL;
+}
+
+int input_stack::space_follows_arg(int i)
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->space_follows_arg(i);
+ return 0;
+}
+
+int input_stack::get_break_flag()
+{
+ return top->get_break_flag();
+}
+
+void input_stack::shift(int n)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->has_args()) {
+ p->shift(n);
+ return;
+ }
+}
+
+int input_stack::nargs()
+{
+ for (input_iterator *p =top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->nargs();
+ return 0;
+}
+
+int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->get_location(allow_macro, filenamep, linenop))
+ return 1;
+ return 0;
+}
+
+void input_stack::backtrace()
+{
+ for (input_iterator *p = top; p; p = p->next)
+ p->backtrace();
+}
+
+int input_stack::set_location(const char *filename, int lineno)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->set_location(filename, lineno))
+ return 1;
+ return 0;
+}
+
+void input_stack::next_file(FILE *fp, const char *s)
+{
+ input_iterator **pp;
+ for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
+ if ((*pp)->next_file(fp, s))
+ return;
+ if (++level > limit && limit > 0)
+ fatal("input stack limit exceeded");
+ *pp = new file_iterator(fp, s);
+ (*pp)->next = &nil_iterator;
+}
+
+void input_stack::end_file()
+{
+ for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
+ if ((*pp)->is_file()) {
+ input_iterator *tem = *pp;
+ check_end_diversion(tem);
+ *pp = (*pp)->next;
+ delete tem;
+ level--;
+ return;
+ }
+}
+
+void input_stack::clear()
+{
+ int nboundaries = 0;
+ while (top != &nil_iterator) {
+ if (top->is_boundary())
+ nboundaries++;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ }
+ // Keep while_request happy.
+ for (; nboundaries > 0; --nboundaries)
+ add_return_boundary();
+}
+
+void input_stack::pop_macro()
+{
+ int nboundaries = 0;
+ int is_macro = 0;
+ do {
+ if (top->next == &nil_iterator)
+ break;
+ if (top->is_boundary())
+ nboundaries++;
+ is_macro = top->is_macro();
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ } while (!is_macro);
+ // Keep while_request happy.
+ for (; nboundaries > 0; --nboundaries)
+ add_return_boundary();
+}
+
+inline void input_stack::save_compatible_flag(int f)
+{
+ top->save_compatible_flag(f);
+}
+
+inline int input_stack::get_compatible_flag()
+{
+ return top->get_compatible_flag();
+}
+
+void backtrace_request()
+{
+ input_stack::backtrace();
+ fflush(stderr);
+ skip_line();
+}
+
+void next_file()
+{
+ symbol nm = get_long_name();
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (nm.is_null())
+ input_stack::end_file();
+ else {
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(nm.contents());
+ if (!fp)
+ error("can't open '%1': %2", nm.contents(), strerror(errno));
+ else
+ input_stack::next_file(fp, nm.contents());
+ }
+ tok.next();
+}
+
+void shift()
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ input_stack::shift(n);
+ skip_line();
+}
+
+static char get_char_for_escape_parameter(bool allow_space = false)
+{
+ int c = get_copy(0 /* nullptr */, false /* is defining */,
+ true /* handle \E */);
+ switch (c) {
+ case EOF:
+ copy_mode_error("end of input in escape sequence");
+ return '\0';
+ default:
+ if (!is_invalid_input_char(c))
+ break;
+ // fall through
+ case '\n':
+ if (c == '\n')
+ input_stack::push(make_temp_iterator("\n"));
+ // fall through
+ case ' ':
+ if (c == ' ' && allow_space)
+ break;
+ // fall through
+ case '\t':
+ case '\001':
+ case '\b':
+ copy_mode_error("%1 is not allowed in an escape sequence parameter",
+ input_char_description(c));
+ return '\0';
+ }
+ return c;
+}
+
+static symbol read_two_char_escape_parameter()
+{
+ char buf[3];
+ buf[0] = get_char_for_escape_parameter();
+ if (buf[0] != '\0') {
+ buf[1] = get_char_for_escape_parameter();
+ if (buf[1] == '\0')
+ buf[0] = 0;
+ else
+ buf[2] = 0;
+ }
+ return symbol(buf);
+}
+
+static symbol read_long_escape_parameters(read_mode mode)
+{
+ int start_level = input_stack::get_level();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ char c;
+ int have_char = 0;
+ for (;;) {
+ c = get_char_for_escape_parameter(have_char && mode == WITH_ARGS);
+ if (c == 0) {
+ if (buf != abuf)
+ delete[] buf;
+ return NULL_SYMBOL;
+ }
+ have_char = 1;
+ if (mode == WITH_ARGS && c == ' ')
+ break;
+ if (i + 2 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ if (c == ']' && input_stack::get_level() == start_level)
+ break;
+ buf[i++] = c;
+ }
+ buf[i] = 0;
+ if (c == ' ')
+ have_multiple_params = true;
+ if (buf == abuf) {
+ if (i == 0) {
+ if (mode != ALLOW_EMPTY)
+ copy_mode_error("empty escape name");
+ return EMPTY_SYMBOL;
+ }
+ return symbol(abuf);
+ }
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+static symbol read_escape_parameter(read_mode mode)
+{
+ char c = get_char_for_escape_parameter();
+ if (c == 0)
+ return NULL_SYMBOL;
+ if (c == '(')
+ return read_two_char_escape_parameter();
+ if (c == '[' && !compatible_flag)
+ return read_long_escape_parameters(mode);
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ return symbol(buf);
+}
+
+static symbol read_increment_and_escape_parameter(int *incp)
+{
+ char c = get_char_for_escape_parameter();
+ switch (c) {
+ case 0:
+ *incp = 0;
+ return NULL_SYMBOL;
+ case '(':
+ *incp = 0;
+ return read_two_char_escape_parameter();
+ case '+':
+ *incp = 1;
+ return read_escape_parameter();
+ case '-':
+ *incp = -1;
+ return read_escape_parameter();
+ case '[':
+ if (!compatible_flag) {
+ *incp = 0;
+ return read_long_escape_parameters();
+ }
+ break;
+ }
+ *incp = 0;
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ return symbol(buf);
+}
+
+static int get_copy(node **nd, bool is_defining, bool handle_escape_E)
+{
+ for (;;) {
+ int c = input_stack::get(nd);
+ if (c == PUSH_GROFF_MODE) {
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 0;
+ continue;
+ }
+ if (c == PUSH_COMP_MODE) {
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 1;
+ continue;
+ }
+ if (c == POP_GROFFCOMP_MODE) {
+ compatible_flag = input_stack::get_compatible_flag();
+ continue;
+ }
+ if (c == BEGIN_QUOTE) {
+ input_stack::increase_level();
+ continue;
+ }
+ if (c == END_QUOTE) {
+ input_stack::decrease_level();
+ continue;
+ }
+ if (c == DOUBLE_QUOTE)
+ continue;
+ if (c == ESCAPE_E && handle_escape_E)
+ c = escape_char;
+ if (c == ESCAPE_NEWLINE) {
+ if (is_defining)
+ return c;
+ do {
+ c = input_stack::get(nd);
+ } while (c == ESCAPE_NEWLINE);
+ }
+ if (c != escape_char || escape_char <= 0)
+ return c;
+ again:
+ c = input_stack::peek();
+ switch(c) {
+ case 0:
+ return escape_char;
+ case '"':
+ (void)input_stack::get(0);
+ while ((c = input_stack::get(0)) != '\n' && c != EOF)
+ ;
+ return c;
+ case '#': // Like \" but newline is ignored.
+ (void)input_stack::get(0);
+ while ((c = input_stack::get(0)) != '\n')
+ if (c == EOF)
+ return EOF;
+ break;
+ case '$':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_arg(s);
+ break;
+ }
+ case '*':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter(WITH_ARGS);
+ if (!(s.is_null() || s.is_empty())) {
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ interpolate_string_with_args(s);
+ }
+ else
+ interpolate_string(s);
+ }
+ break;
+ }
+ case 'a':
+ (void)input_stack::get(0);
+ return '\001';
+ case 'e':
+ (void)input_stack::get(0);
+ return ESCAPE_e;
+ case 'E':
+ (void)input_stack::get(0);
+ if (handle_escape_E)
+ goto again;
+ return ESCAPE_E;
+ case 'n':
+ {
+ (void)input_stack::get(0);
+ int inc;
+ symbol s = read_increment_and_escape_parameter(&inc);
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_reg(s, inc);
+ break;
+ }
+ case 'g':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_format(s);
+ break;
+ }
+ case 't':
+ (void)input_stack::get(0);
+ return '\t';
+ case 'V':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_environment_variable(s);
+ break;
+ }
+ case '\n':
+ (void)input_stack::get(0);
+ if (is_defining)
+ return ESCAPE_NEWLINE;
+ break;
+ case ' ':
+ (void)input_stack::get(0);
+ return ESCAPE_SPACE;
+ case '~':
+ (void)input_stack::get(0);
+ return ESCAPE_TILDE;
+ case ':':
+ (void)input_stack::get(0);
+ return ESCAPE_COLON;
+ case '|':
+ (void)input_stack::get(0);
+ return ESCAPE_BAR;
+ case '^':
+ (void)input_stack::get(0);
+ return ESCAPE_CIRCUMFLEX;
+ case '{':
+ (void)input_stack::get(0);
+ return ESCAPE_LEFT_BRACE;
+ case '}':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_BRACE;
+ case '`':
+ (void)input_stack::get(0);
+ return ESCAPE_LEFT_QUOTE;
+ case '\'':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_QUOTE;
+ case '-':
+ (void)input_stack::get(0);
+ return ESCAPE_HYPHEN;
+ case '_':
+ (void)input_stack::get(0);
+ return ESCAPE_UNDERSCORE;
+ case 'c':
+ (void)input_stack::get(0);
+ return ESCAPE_c;
+ case '!':
+ (void)input_stack::get(0);
+ return ESCAPE_BANG;
+ case '?':
+ (void)input_stack::get(0);
+ return ESCAPE_QUESTION;
+ case '&':
+ (void)input_stack::get(0);
+ return ESCAPE_AMPERSAND;
+ case ')':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_PARENTHESIS;
+ case '.':
+ (void)input_stack::get(0);
+ return c;
+ case '%':
+ (void)input_stack::get(0);
+ return ESCAPE_PERCENT;
+ default:
+ if (c == escape_char) {
+ (void)input_stack::get(0);
+ return c;
+ }
+ else
+ return escape_char;
+ }
+ }
+}
+
+class non_interpreted_char_node : public node {
+ unsigned char c;
+public:
+ non_interpreted_char_node(unsigned char);
+ node *copy();
+ int interpret(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+int non_interpreted_char_node::same(node *nd)
+{
+ return c == ((non_interpreted_char_node *)nd)->c;
+}
+
+const char *non_interpreted_char_node::type()
+{
+ return "non_interpreted_char_node";
+}
+
+int non_interpreted_char_node::force_tprint()
+{
+ return 0;
+}
+
+int non_interpreted_char_node::is_tag()
+{
+ return 0;
+}
+
+non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
+{
+ assert(n != 0);
+}
+
+node *non_interpreted_char_node::copy()
+{
+ return new non_interpreted_char_node(c);
+}
+
+int non_interpreted_char_node::interpret(macro *mac)
+{
+ mac->append(c);
+ return 1;
+}
+
+static void do_width();
+static node *do_non_interpreted();
+static node *do_special();
+static node *do_suppress(symbol nm);
+static void do_register();
+
+dictionary color_dictionary(501);
+
+static color *lookup_color(symbol nm)
+{
+ assert(!nm.is_null());
+ if (nm == default_symbol)
+ return &default_color;
+ color *c = (color *)color_dictionary.lookup(nm);
+ if (c == 0)
+ warning(WARN_COLOR, "color '%1' not defined", nm.contents());
+ return c;
+}
+
+void do_glyph_color(symbol nm)
+{
+ if (nm.is_null())
+ return;
+ if (nm.is_empty())
+ curenv->set_glyph_color(curenv->get_prev_glyph_color());
+ else {
+ color *tem = lookup_color(nm);
+ if (tem)
+ curenv->set_glyph_color(tem);
+ else
+ (void)color_dictionary.lookup(nm, new color(nm));
+ }
+}
+
+void do_fill_color(symbol nm)
+{
+ if (nm.is_null())
+ return;
+ if (nm.is_empty())
+ curenv->set_fill_color(curenv->get_prev_fill_color());
+ else {
+ color *tem = lookup_color(nm);
+ if (tem)
+ curenv->set_fill_color(tem);
+ else
+ (void)color_dictionary.lookup(nm, new color(nm));
+ }
+}
+
+static unsigned int get_color_element(const char *scheme, const char *col)
+{
+ units val;
+ if (!get_number(&val, 'f')) {
+ warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
+ tok.next();
+ return 0;
+ }
+ if (val < 0) {
+ warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
+ return 0;
+ }
+ if (val > color::MAX_COLOR_VAL+1) {
+ warning(WARN_RANGE, "%1 cannot be greater than 1", col);
+ // we change 0x10000 to 0xffff
+ return color::MAX_COLOR_VAL;
+ }
+ return (unsigned int)val;
+}
+
+static color *read_rgb(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing rgb color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_rgb(s)) {
+ warning(WARN_COLOR, "expecting rgb color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int r = get_color_element("rgb color", "red component");
+ unsigned int g = get_color_element("rgb color", "green component");
+ unsigned int b = get_color_element("rgb color", "blue component");
+ col->set_rgb(r, g, b);
+ }
+ return col;
+}
+
+static color *read_cmy(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing cmy color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_cmy(s)) {
+ warning(WARN_COLOR, "expecting cmy color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int c = get_color_element("cmy color", "cyan component");
+ unsigned int m = get_color_element("cmy color", "magenta component");
+ unsigned int y = get_color_element("cmy color", "yellow component");
+ col->set_cmy(c, m, y);
+ }
+ return col;
+}
+
+static color *read_cmyk(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing cmyk color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_cmyk(s)) {
+ warning(WARN_COLOR, "expecting cmyk color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int c = get_color_element("cmyk color", "cyan component");
+ unsigned int m = get_color_element("cmyk color", "magenta component");
+ unsigned int y = get_color_element("cmyk color", "yellow component");
+ unsigned int k = get_color_element("cmyk color", "black component");
+ col->set_cmyk(c, m, y, k);
+ }
+ return col;
+}
+
+static color *read_gray(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing gray value");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_gray(s)) {
+ warning(WARN_COLOR, "expecting gray definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator("\n"));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int g = get_color_element("gray", "gray value");
+ col->set_gray(g);
+ }
+ return col;
+}
+
+static void activate_color()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ color_flag = n != 0;
+ else
+ color_flag = 1;
+ skip_line();
+}
+
+static void define_color()
+{
+ symbol color_name = get_long_name(true /* required */);
+ if (color_name.is_null()) {
+ skip_line();
+ return;
+ }
+ if (color_name == default_symbol) {
+ warning(WARN_COLOR, "default color can't be redefined");
+ skip_line();
+ return;
+ }
+ symbol style = get_long_name(true /* required */);
+ if (style.is_null()) {
+ skip_line();
+ return;
+ }
+ color *col;
+ if (strcmp(style.contents(), "rgb") == 0)
+ col = read_rgb();
+ else if (strcmp(style.contents(), "cmyk") == 0)
+ col = read_cmyk();
+ else if (strcmp(style.contents(), "gray") == 0)
+ col = read_gray();
+ else if (strcmp(style.contents(), "grey") == 0)
+ col = read_gray();
+ else if (strcmp(style.contents(), "cmy") == 0)
+ col = read_cmy();
+ else {
+ warning(WARN_COLOR, "unknown color space '%1';"
+ " use 'rgb', 'cmyk', 'gray' or 'cmy'", style.contents());
+ skip_line();
+ return;
+ }
+ if (col) {
+ col->nm = color_name;
+ (void)color_dictionary.lookup(color_name, col);
+ }
+ skip_line();
+}
+
+node *do_overstrike()
+{
+ overstrike_node *on = new overstrike_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in overstrike"
+ " escape sequence (got %1)", tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (tok.is_horizontal_space())
+ on->overstrike(tok.nd->copy());
+ else if (tok.is_unstretchable_space())
+ {
+ node *n = new hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ on->overstrike(n);
+ }
+ else {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ node *n = curenv->make_char_node(ci);
+ if (n)
+ on->overstrike(n);
+ }
+ }
+ }
+ return on;
+}
+
+static node *do_bracket()
+{
+ bracket_node *bn = new bracket_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " bracket-building escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ node *n = curenv->make_char_node(ci);
+ if (n)
+ bn->bracket(n);
+ }
+ }
+ return bn;
+}
+
+static int do_name_test()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ bool got_bad_char = false;
+ bool got_some_char = false;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in identifier"
+ " validation escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (!tok.ch())
+ got_bad_char = true;
+ got_some_char = true;
+ }
+ return (got_some_char && !got_bad_char);
+}
+
+static int do_expr_test()
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return 0;
+ tok.next();
+ // disable all warning and error messages temporarily
+ int saved_warning_mask = warning_mask;
+ int saved_inhibit_errors = inhibit_errors;
+ warning_mask = 0;
+ inhibit_errors = 1;
+ int dummy;
+ int result = get_number_rigidly(&dummy, 'u');
+ warning_mask = saved_warning_mask;
+ inhibit_errors = saved_inhibit_errors;
+ if (tok == start && input_stack::get_level() == start_level)
+ return result;
+ // ignore everything up to the delimiter in case we aren't right there
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " expression test escape sequence (got %1)",
+ tok.description());
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start && input_stack::get_level() == start_level)
+ break;
+ }
+ return 0;
+}
+
+#if 0
+static node *do_zero_width()
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ error("missing closing delimiter");
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ tok.process();
+ }
+ curenv = oldenv;
+ node *rev = env.extract_output_line();
+ node *n = 0;
+ while (rev) {
+ node *tem = rev;
+ rev = rev->next;
+ tem->next = n;
+ n = tem;
+ }
+ return new zero_width_node(n);
+}
+
+#else
+
+// It's undesirable for \Z to change environments, because then
+// \n(.w won't work as expected.
+
+static node *do_zero_width()
+{
+ node *rev = new dummy_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " zero-width escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (!tok.add_to_zero_width_node_list(&rev))
+ error("invalid token in argument to escaped 'Z'");
+ }
+ node *n = 0;
+ while (rev) {
+ node *tem = rev;
+ rev = rev->next;
+ tem->next = n;
+ n = tem;
+ }
+ return new zero_width_node(n);
+}
+
+#endif
+
+token_node *node::get_token_node()
+{
+ return 0;
+}
+
+class token_node : public node {
+public:
+ token tk;
+ token_node(const token &t);
+ node *copy();
+ token_node *get_token_node();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+token_node::token_node(const token &t) : tk(t)
+{
+}
+
+node *token_node::copy()
+{
+ return new token_node(tk);
+}
+
+token_node *token_node::get_token_node()
+{
+ return this;
+}
+
+int token_node::same(node *nd)
+{
+ return tk == ((token_node *)nd)->tk;
+}
+
+const char *token_node::type()
+{
+ return "token_node";
+}
+
+int token_node::force_tprint()
+{
+ return 0;
+}
+
+int token_node::is_tag()
+{
+ return 0;
+}
+
+token::token() : nd(0), type(TOKEN_EMPTY)
+{
+}
+
+token::~token()
+{
+ delete nd;
+}
+
+token::token(const token &t)
+: nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
+{
+ // Use two statements to work around bug in SGI C++.
+ node *tem = t.nd;
+ nd = tem ? tem->copy() : 0;
+}
+
+void token::operator=(const token &t)
+{
+ delete nd;
+ nm = t.nm;
+ // Use two statements to work around bug in SGI C++.
+ node *tem = t.nd;
+ nd = tem ? tem->copy() : 0;
+ c = t.c;
+ val = t.val;
+ dim = t.dim;
+ type = t.type;
+}
+
+void token::skip()
+{
+ while (is_space())
+ next();
+}
+
+bool has_arg()
+{
+ while (tok.is_space())
+ tok.next();
+ return !tok.is_newline();
+}
+
+void token::make_space()
+{
+ type = TOKEN_SPACE;
+}
+
+void token::make_newline()
+{
+ type = TOKEN_NEWLINE;
+}
+
+void token::next()
+{
+ if (nd) {
+ delete nd;
+ nd = 0;
+ }
+ units x;
+ for (;;) {
+ node *n = 0;
+ int cc = input_stack::get(&n);
+ if (cc != escape_char || escape_char == 0) {
+ handle_ordinary_char:
+ switch(cc) {
+ case INPUT_NO_BREAK_SPACE:
+ type = TOKEN_STRETCHABLE_SPACE;
+ return;
+ case INPUT_SOFT_HYPHEN:
+ type = TOKEN_HYPHEN_INDICATOR;
+ return;
+ case PUSH_GROFF_MODE:
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 0;
+ continue;
+ case PUSH_COMP_MODE:
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 1;
+ continue;
+ case POP_GROFFCOMP_MODE:
+ compatible_flag = input_stack::get_compatible_flag();
+ continue;
+ case BEGIN_QUOTE:
+ input_stack::increase_level();
+ continue;
+ case END_QUOTE:
+ input_stack::decrease_level();
+ continue;
+ case DOUBLE_QUOTE:
+ continue;
+ case EOF:
+ type = TOKEN_EOF;
+ return;
+ case TRANSPARENT_FILE_REQUEST:
+ case TITLE_REQUEST:
+ case COPY_FILE_REQUEST:
+#ifdef COLUMN
+ case VJUSTIFY_REQUEST:
+#endif /* COLUMN */
+ type = TOKEN_REQUEST;
+ c = cc;
+ return;
+ case BEGIN_TRAP:
+ type = TOKEN_BEGIN_TRAP;
+ return;
+ case END_TRAP:
+ type = TOKEN_END_TRAP;
+ return;
+ case LAST_PAGE_EJECTOR:
+ seen_last_page_ejector = 1;
+ // fall through
+ case PAGE_EJECTOR:
+ type = TOKEN_PAGE_EJECTOR;
+ return;
+ case ESCAPE_PERCENT:
+ ESCAPE_PERCENT:
+ type = TOKEN_HYPHEN_INDICATOR;
+ return;
+ case ESCAPE_SPACE:
+ ESCAPE_SPACE:
+ type = TOKEN_UNSTRETCHABLE_SPACE;
+ return;
+ case ESCAPE_TILDE:
+ ESCAPE_TILDE:
+ type = TOKEN_STRETCHABLE_SPACE;
+ return;
+ case ESCAPE_COLON:
+ ESCAPE_COLON:
+ type = TOKEN_ZERO_WIDTH_BREAK;
+ return;
+ case ESCAPE_e:
+ ESCAPE_e:
+ type = TOKEN_ESCAPE;
+ return;
+ case ESCAPE_E:
+ goto handle_escape_char;
+ case ESCAPE_BAR:
+ ESCAPE_BAR:
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(curenv->get_narrow_space_width(),
+ curenv->get_fill_color());
+ return;
+ case ESCAPE_CIRCUMFLEX:
+ ESCAPE_CIRCUMFLEX:
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(curenv->get_half_narrow_space_width(),
+ curenv->get_fill_color());
+ return;
+ case ESCAPE_NEWLINE:
+ have_input = 0;
+ break;
+ case ESCAPE_LEFT_BRACE:
+ ESCAPE_LEFT_BRACE:
+ type = TOKEN_LEFT_BRACE;
+ return;
+ case ESCAPE_RIGHT_BRACE:
+ ESCAPE_RIGHT_BRACE:
+ type = TOKEN_RIGHT_BRACE;
+ return;
+ case ESCAPE_LEFT_QUOTE:
+ ESCAPE_LEFT_QUOTE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("ga");
+ return;
+ case ESCAPE_RIGHT_QUOTE:
+ ESCAPE_RIGHT_QUOTE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("aa");
+ return;
+ case ESCAPE_HYPHEN:
+ ESCAPE_HYPHEN:
+ type = TOKEN_SPECIAL;
+ nm = symbol("-");
+ return;
+ case ESCAPE_UNDERSCORE:
+ ESCAPE_UNDERSCORE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("ul");
+ return;
+ case ESCAPE_c:
+ ESCAPE_c:
+ type = TOKEN_INTERRUPT;
+ return;
+ case ESCAPE_BANG:
+ ESCAPE_BANG:
+ type = TOKEN_TRANSPARENT;
+ return;
+ case ESCAPE_QUESTION:
+ ESCAPE_QUESTION:
+ nd = do_non_interpreted();
+ if (nd) {
+ type = TOKEN_NODE;
+ return;
+ }
+ break;
+ case ESCAPE_AMPERSAND:
+ ESCAPE_AMPERSAND:
+ type = TOKEN_DUMMY;
+ return;
+ case ESCAPE_RIGHT_PARENTHESIS:
+ ESCAPE_RIGHT_PARENTHESIS:
+ type = TOKEN_TRANSPARENT_DUMMY;
+ return;
+ case '\b':
+ type = TOKEN_BACKSPACE;
+ return;
+ case ' ':
+ type = TOKEN_SPACE;
+ return;
+ case '\t':
+ type = TOKEN_TAB;
+ return;
+ case '\n':
+ type = TOKEN_NEWLINE;
+ return;
+ case '\001':
+ type = TOKEN_LEADER;
+ return;
+ case 0:
+ {
+ assert(n != 0);
+ token_node *tn = n->get_token_node();
+ if (tn) {
+ *this = tn->tk;
+ delete tn;
+ }
+ else {
+ nd = n;
+ type = TOKEN_NODE;
+ }
+ }
+ return;
+ default:
+ type = TOKEN_CHAR;
+ c = cc;
+ return;
+ }
+ }
+ else {
+ handle_escape_char:
+ cc = input_stack::get(&n);
+ switch(cc) {
+ case '(':
+ nm = read_two_char_escape_parameter();
+ type = TOKEN_SPECIAL;
+ return;
+ case EOF:
+ type = TOKEN_EOF;
+ error("end of input after escape character");
+ return;
+ case '`':
+ goto ESCAPE_LEFT_QUOTE;
+ case '\'':
+ goto ESCAPE_RIGHT_QUOTE;
+ case '-':
+ goto ESCAPE_HYPHEN;
+ case '_':
+ goto ESCAPE_UNDERSCORE;
+ case '%':
+ goto ESCAPE_PERCENT;
+ case ' ':
+ goto ESCAPE_SPACE;
+ case '0':
+ nd = new hmotion_node(curenv->get_digit_width(),
+ curenv->get_fill_color());
+ type = TOKEN_HORIZONTAL_SPACE;
+ return;
+ case '|':
+ goto ESCAPE_BAR;
+ case '^':
+ goto ESCAPE_CIRCUMFLEX;
+ case '/':
+ type = TOKEN_ITALIC_CORRECTION;
+ return;
+ case ',':
+ type = TOKEN_NODE;
+ nd = new left_italic_corrected_node;
+ return;
+ case '&':
+ goto ESCAPE_AMPERSAND;
+ case ')':
+ goto ESCAPE_RIGHT_PARENTHESIS;
+ case '!':
+ goto ESCAPE_BANG;
+ case '?':
+ goto ESCAPE_QUESTION;
+ case '~':
+ goto ESCAPE_TILDE;
+ case ':':
+ goto ESCAPE_COLON;
+ case '"':
+ while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
+ ;
+ if (cc == '\n')
+ type = TOKEN_NEWLINE;
+ else
+ type = TOKEN_EOF;
+ return;
+ case '#': // Like \" but newline is ignored.
+ while ((cc = input_stack::get(0)) != '\n')
+ if (cc == EOF) {
+ type = TOKEN_EOF;
+ return;
+ }
+ break;
+ case '$':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_arg(s);
+ break;
+ }
+ case '*':
+ {
+ symbol s = read_escape_parameter(WITH_ARGS);
+ if (!(s.is_null() || s.is_empty())) {
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ interpolate_string_with_args(s);
+ }
+ else
+ interpolate_string(s);
+ }
+ break;
+ }
+ case 'a':
+ nd = new non_interpreted_char_node('\001');
+ type = TOKEN_NODE;
+ return;
+ case 'A':
+ c = '0' + do_name_test();
+ type = TOKEN_CHAR;
+ return;
+ case 'b':
+ nd = do_bracket();
+ type = TOKEN_NODE;
+ return;
+ case 'B':
+ c = '0' + do_expr_test();
+ type = TOKEN_CHAR;
+ return;
+ case 'c':
+ goto ESCAPE_c;
+ case 'C':
+ nm = get_delim_name();
+ if (nm.is_null())
+ break;
+ type = TOKEN_SPECIAL;
+ return;
+ case 'd':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(curenv->get_size() / 2,
+ curenv->get_fill_color());
+ return;
+ case 'D':
+ nd = read_draw_node();
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'e':
+ goto ESCAPE_e;
+ case 'E':
+ goto handle_escape_char;
+ case 'f':
+ {
+ symbol s = read_escape_parameter(ALLOW_EMPTY);
+ if (s.is_null())
+ break;
+ const char *p;
+ for (p = s.contents(); *p != '\0'; p++)
+ if (!csdigit(*p))
+ break;
+ // environment::set_font warns if a bogus mounting position is
+ // requested. We must warn here if a bogus font name is
+ // selected.
+ if (*p != '\0' || s.is_empty()) {
+ if (!curenv->set_font(s))
+ warning(WARN_FONT, "cannot select font '%1'",
+ s.contents());
+ }
+ else
+ (void) curenv->set_font(atoi(s.contents()));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ }
+ case 'F':
+ {
+ symbol s = read_escape_parameter(ALLOW_EMPTY);
+ if (s.is_null())
+ break;
+ curenv->set_family(s);
+ have_input = 1;
+ break;
+ }
+ case 'g':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_format(s);
+ break;
+ }
+ case 'h':
+ if (!get_delim_number(&x, 'm'))
+ break;
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(x, curenv->get_fill_color());
+ return;
+ case 'H':
+ // don't take height increments relative to previous height if
+ // in compatibility mode
+ if (!compatible_flag && curenv->get_char_height()) {
+ if (get_delim_number(&x, 'z', curenv->get_char_height()))
+ curenv->set_char_height(x);
+ }
+ else {
+ if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
+ curenv->set_char_height(x);
+ }
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'k':
+ nm = read_escape_parameter();
+ if (nm.is_null() || nm.is_empty())
+ break;
+ type = TOKEN_MARK_INPUT;
+ return;
+ case 'l':
+ case 'L':
+ {
+ charinfo *s = 0;
+ if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
+ break;
+ if (s == 0)
+ s = get_charinfo(cc == 'l' ? "ru" : "br");
+ type = TOKEN_NODE;
+ node *char_node = curenv->make_char_node(s);
+ if (cc == 'l')
+ nd = new hline_node(x, char_node);
+ else
+ nd = new vline_node(x, char_node);
+ return;
+ }
+ case 'm':
+ do_glyph_color(read_escape_parameter(ALLOW_EMPTY));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'M':
+ do_fill_color(read_escape_parameter(ALLOW_EMPTY));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'n':
+ {
+ int inc;
+ symbol s = read_increment_and_escape_parameter(&inc);
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_reg(s, inc);
+ break;
+ }
+ case 'N':
+ if (!get_delim_number(&val, 0))
+ break;
+ if (val < 0) {
+ warning(WARN_CHAR, "invalid numbered character %1", val);
+ break;
+ }
+ type = TOKEN_NUMBERED_CHAR;
+ return;
+ case 'o':
+ nd = do_overstrike();
+ type = TOKEN_NODE;
+ return;
+ case 'O':
+ nd = do_suppress(read_escape_parameter());
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'p':
+ type = TOKEN_SPREAD;
+ return;
+ case 'r':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
+ return;
+ case 'R':
+ do_register();
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 's':
+ if (read_size(&x))
+ curenv->set_size(x);
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'S':
+ if (get_delim_number(&x, 0))
+ curenv->set_char_slant(x);
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 't':
+ type = TOKEN_NODE;
+ nd = new non_interpreted_char_node('\t');
+ return;
+ case 'u':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(-curenv->get_size() / 2,
+ curenv->get_fill_color());
+ return;
+ case 'v':
+ if (!get_delim_number(&x, 'v'))
+ break;
+ type = TOKEN_NODE;
+ nd = new vmotion_node(x, curenv->get_fill_color());
+ return;
+ case 'V':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_environment_variable(s);
+ break;
+ }
+ case 'w':
+ do_width();
+ break;
+ case 'x':
+ if (!get_delim_number(&x, 'v'))
+ break;
+ type = TOKEN_NODE;
+ nd = new extra_size_node(x);
+ return;
+ case 'X':
+ nd = do_special();
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'Y':
+ {
+ symbol s = read_escape_parameter();
+ if (s.is_null() || s.is_empty())
+ break;
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m) {
+ error("can't transparently throughput a request");
+ break;
+ }
+ nd = new special_node(*m);
+ type = TOKEN_NODE;
+ return;
+ }
+ case 'z':
+ {
+ next();
+ if (type == TOKEN_NODE || type == TOKEN_HORIZONTAL_SPACE)
+ nd = new zero_width_node(nd);
+ else {
+ charinfo *ci = get_char(true /* required */);
+ if (ci == 0)
+ break;
+ node *gn = curenv->make_char_node(ci);
+ if (gn == 0)
+ break;
+ nd = new zero_width_node(gn);
+ type = TOKEN_NODE;
+ }
+ return;
+ }
+ case 'Z':
+ nd = do_zero_width();
+ if (nd == 0)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case '{':
+ goto ESCAPE_LEFT_BRACE;
+ case '}':
+ goto ESCAPE_RIGHT_BRACE;
+ case '\n':
+ break;
+ case '[':
+ if (!compatible_flag) {
+ symbol s = read_long_escape_parameters(WITH_ARGS);
+ if (s.is_null() || s.is_empty())
+ break;
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ nm = composite_glyph_name(s);
+ }
+ else {
+ const char *gn = check_unicode_name(s.contents());
+ if (gn) {
+ const char *gn_decomposed = decompose_unicode(gn);
+ if (gn_decomposed)
+ gn = &gn_decomposed[1];
+ const char *groff_gn = unicode_to_glyph_name(gn);
+ if (groff_gn)
+ nm = symbol(groff_gn);
+ else {
+ char *buf = new char[strlen(gn) + 1 + 1];
+ strcpy(buf, "u");
+ strcat(buf, gn);
+ nm = symbol(buf);
+ delete[] buf;
+ }
+ }
+ else
+ nm = symbol(s.contents());
+ }
+ type = TOKEN_SPECIAL;
+ return;
+ }
+ goto handle_ordinary_char;
+ default:
+ if (cc != escape_char && cc != '.')
+ warning(WARN_ESCAPE, "escape character ignored before %1",
+ input_char_description(cc));
+ goto handle_ordinary_char;
+ }
+ }
+ }
+}
+
+int token::operator==(const token &t)
+{
+ if (type != t.type)
+ return 0;
+ switch(type) {
+ case TOKEN_CHAR:
+ return c == t.c;
+ case TOKEN_SPECIAL:
+ return nm == t.nm;
+ case TOKEN_NUMBERED_CHAR:
+ return val == t.val;
+ default:
+ return 1;
+ }
+}
+
+int token::operator!=(const token &t)
+{
+ return !(*this == t);
+}
+
+// is token a suitable delimiter (like ')?
+
+bool token::usable_as_delimiter(bool report_error)
+{
+ switch(type) {
+ case TOKEN_CHAR:
+ switch(c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '+':
+ case '-':
+ case '/':
+ case '*':
+ case '%':
+ case '<':
+ case '>':
+ case '=':
+ case '&':
+ case ':':
+ case '(':
+ case ')':
+ case '.':
+ if (report_error)
+ error("character '%1' is not allowed as a starting delimiter",
+ char(c));
+ return false;
+ default:
+ return true;
+ }
+ case TOKEN_NODE:
+ // the user doesn't know what a node is
+ if (report_error)
+ error("missing argument or invalid starting delimiter");
+ return false;
+ case TOKEN_SPACE:
+ case TOKEN_STRETCHABLE_SPACE:
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ case TOKEN_HORIZONTAL_SPACE:
+ case TOKEN_TAB:
+ case TOKEN_NEWLINE:
+ if (report_error)
+ error("%1 is not allowed as a starting delimiter", description());
+ return false;
+ default:
+ return true;
+ }
+}
+
+const char *token::description()
+{
+ static char buf[4];
+ switch (type) {
+ case TOKEN_BACKSPACE:
+ return "a backspace character";
+ case TOKEN_CHAR:
+ if (c == INPUT_DELETE)
+ return "a delete character";
+ else {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ buf[3] = '\0';
+ return buf;
+ }
+ case TOKEN_DUMMY:
+ return "an escaped '&'";
+ case TOKEN_ESCAPE:
+ return "an escaped 'e'";
+ case TOKEN_HYPHEN_INDICATOR:
+ return "an escaped '%'";
+ case TOKEN_INTERRUPT:
+ return "an escaped 'c'";
+ case TOKEN_ITALIC_CORRECTION:
+ return "an escaped '/'";
+ case TOKEN_LEADER:
+ return "a leader character";
+ case TOKEN_LEFT_BRACE:
+ return "an escaped '{'";
+ case TOKEN_MARK_INPUT:
+ return "an escaped 'k'";
+ case TOKEN_NEWLINE:
+ return "a newline";
+ case TOKEN_NODE:
+ return "a node";
+ case TOKEN_NUMBERED_CHAR:
+ return "an escaped 'N'";
+ case TOKEN_RIGHT_BRACE:
+ return "an escaped '}'";
+ case TOKEN_SPACE:
+ return "a space";
+ case TOKEN_SPECIAL:
+ return "a special character";
+ case TOKEN_SPREAD:
+ return "an escaped 'p'";
+ case TOKEN_STRETCHABLE_SPACE:
+ return "an escaped '~'";
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ return "an escaped ' '";
+ case TOKEN_HORIZONTAL_SPACE:
+ return "a horizontal motion";
+ case TOKEN_TAB:
+ return "a tab character";
+ case TOKEN_TRANSPARENT:
+ return "an escaped '!'";
+ case TOKEN_TRANSPARENT_DUMMY:
+ return "an escaped ')'";
+ case TOKEN_ZERO_WIDTH_BREAK:
+ return "an escaped ':'";
+ case TOKEN_EOF:
+ return "end of input";
+ default:
+ break;
+ }
+ return "a magic token";
+}
+
+void skip_line()
+{
+ while (!tok.is_newline())
+ if (tok.is_eof())
+ return;
+ else
+ tok.next();
+ tok.next();
+}
+
+void compatible()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ compatible_flag = n != 0;
+ else
+ compatible_flag = 1;
+ skip_line();
+}
+
+static void diagnose_missing_identifier(bool required)
+{
+ if (tok.is_newline() || tok.is_eof()) {
+ if (required)
+ warning(WARN_MISSING, "missing identifier");
+ }
+ else if (tok.is_right_brace() || tok.is_tab()) {
+ const char *start = tok.description();
+ do {
+ tok.next();
+ } while (tok.is_space() || tok.is_right_brace() || tok.is_tab());
+ if (!tok.is_newline() && !tok.is_eof())
+ error("%1 is not allowed before an argument", start);
+ else if (required)
+ warning(WARN_MISSING, "missing identifier");
+ }
+ else if (required)
+ error("expected identifier, got %1", tok.description());
+ else
+ error("expected identifier, got %1; treated as missing",
+ tok.description());
+}
+
+static void diagnose_invalid_identifier()
+{
+ if (!tok.is_newline() && !tok.is_eof() && !tok.is_space()
+ && !tok.is_tab() && !tok.is_right_brace()
+ // We don't want to give a warning for .el\{
+ && !tok.is_left_brace())
+ error("%1 is not allowed in an identifier", tok.description());
+}
+
+symbol get_name(bool required)
+{
+ if (compatible_flag) {
+ char buf[3];
+ tok.skip();
+ if ((buf[0] = tok.ch()) != 0) {
+ tok.next();
+ if ((buf[1] = tok.ch()) != 0) {
+ buf[2] = 0;
+ tok.make_space();
+ }
+ else
+ diagnose_invalid_identifier();
+ return symbol(buf);
+ }
+ else {
+ diagnose_missing_identifier(required);
+ return NULL_SYMBOL;
+ }
+ }
+ else
+ return get_long_name(required);
+}
+
+symbol get_long_name(bool required)
+{
+ return do_get_long_name(required, 0);
+}
+
+static symbol do_get_long_name(bool required, char end)
+{
+ while (tok.is_space())
+ tok.next();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ for (;;) {
+ // If end != 0 we normally have to append a null byte
+ if (i + 2 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
+ break;
+ i++;
+ tok.next();
+ }
+ if (i == 0) {
+ diagnose_missing_identifier(required);
+ return NULL_SYMBOL;
+ }
+ if (end && buf[i] == end)
+ buf[i+1] = '\0';
+ else
+ diagnose_invalid_identifier();
+ if (buf == abuf)
+ return symbol(buf);
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+void exit_troff()
+{
+ is_exit_underway = true;
+ topdiv->set_last_page();
+ if (!end_of_input_macro_name.is_null()) {
+ spring_trap(end_of_input_macro_name);
+ tok.next();
+ process_input_stack();
+ }
+ curenv->final_break();
+ tok.next();
+ process_input_stack();
+ end_diversions();
+ if (topdiv->get_page_length() > 0) {
+ is_eoi_macro_finished = true;
+ topdiv->set_ejecting();
+ static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
+ input_stack::push(make_temp_iterator((char *)buf));
+ topdiv->space(topdiv->get_page_length(), 1);
+ tok.next();
+ process_input_stack();
+ seen_last_page_ejector = 1; // should be set already
+ topdiv->set_ejecting();
+ push_page_ejector();
+ topdiv->space(topdiv->get_page_length(), 1);
+ tok.next();
+ process_input_stack();
+ }
+ cleanup_and_exit(EXIT_SUCCESS);
+}
+
+// This implements .ex. The input stack must be cleared before calling
+// exit_troff().
+
+void exit_request()
+{
+ input_stack::clear();
+ if (is_exit_underway)
+ tok.next();
+ else
+ exit_troff();
+}
+
+void return_macro_request()
+{
+ if (has_arg() && tok.ch())
+ input_stack::pop_macro();
+ input_stack::pop_macro();
+ tok.next();
+}
+
+void eoi_macro()
+{
+ end_of_input_macro_name = get_name();
+ skip_line();
+}
+
+void blank_line_macro()
+{
+ blank_line_macro_name = get_name();
+ skip_line();
+}
+
+void leading_spaces_macro()
+{
+ leading_spaces_macro_name = get_name();
+ skip_line();
+}
+
+static void trapping_blank_line()
+{
+ if (!blank_line_macro_name.is_null())
+ spring_trap(blank_line_macro_name);
+ else
+ blank_line();
+}
+
+void do_request()
+{
+ assert(do_old_compatible_flag == -1);
+ do_old_compatible_flag = compatible_flag;
+ compatible_flag = 0;
+ symbol nm = get_name();
+ if (nm.is_null())
+ skip_line();
+ else
+ interpolate_macro(nm, true /* don't want next token */);
+ compatible_flag = do_old_compatible_flag;
+ do_old_compatible_flag = -1;
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (m)
+ tok.next();
+}
+
+inline int possibly_handle_first_page_transition()
+{
+ if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
+ handle_first_page_transition();
+ return 1;
+ }
+ else
+ return 0;
+}
+
+static int transparent_translate(int cc)
+{
+ if (!is_invalid_input_char(cc)) {
+ charinfo *ci = charset_table[cc];
+ switch (ci->get_special_translation(1)) {
+ case charinfo::TRANSLATE_SPACE:
+ return ' ';
+ case charinfo::TRANSLATE_STRETCHABLE_SPACE:
+ return ESCAPE_TILDE;
+ case charinfo::TRANSLATE_DUMMY:
+ return ESCAPE_AMPERSAND;
+ case charinfo::TRANSLATE_HYPHEN_INDICATOR:
+ return ESCAPE_PERCENT;
+ }
+ // This is really ugly.
+ ci = ci->get_translation(1);
+ if (ci) {
+ int c = ci->get_ascii_code();
+ if (c != '\0')
+ return c;
+ if (getenv("GROFF_ENABLE_TRANSPARENCY_WARNINGS")
+ != 0 /* nullptr */)
+ error("can't translate %1 to special character '%2'"
+ " in transparent throughput",
+ input_char_description(cc),
+ ci->nm.contents());
+ }
+ }
+ return cc;
+}
+
+class int_stack {
+ struct int_stack_element {
+ int n;
+ int_stack_element *next;
+ } *top;
+public:
+ int_stack();
+ ~int_stack();
+ void push(int);
+ int is_empty();
+ int pop();
+};
+
+int_stack::int_stack()
+{
+ top = 0;
+}
+
+int_stack::~int_stack()
+{
+ while (top != 0) {
+ int_stack_element *temp = top;
+ top = top->next;
+ delete temp;
+ }
+}
+
+int int_stack::is_empty()
+{
+ return top == 0;
+}
+
+void int_stack::push(int n)
+{
+ int_stack_element *p = new int_stack_element;
+ p->next = top;
+ p->n = n;
+ top = p;
+}
+
+int int_stack::pop()
+{
+ assert(top != 0);
+ int_stack_element *p = top;
+ top = top->next;
+ int n = p->n;
+ delete p;
+ return n;
+}
+
+int node::reread(int *)
+{
+ return 0;
+}
+
+int global_diverted_space = 0;
+
+int diverted_space_node::reread(int *bolp)
+{
+ global_diverted_space = 1;
+ if (curenv->get_fill())
+ trapping_blank_line();
+ else
+ curdiv->space(n);
+ global_diverted_space = 0;
+ *bolp = 1;
+ return 1;
+}
+
+int diverted_copy_file_node::reread(int *bolp)
+{
+ curdiv->copy_file(filename.contents());
+ *bolp = 1;
+ return 1;
+}
+
+int word_space_node::reread(int *)
+{
+ if (unformat) {
+ for (width_list *w = orig_width; w; w = w->next)
+ curenv->space(w->width, w->sentence_width);
+ unformat = 0;
+ return 1;
+ }
+ return 0;
+}
+
+int unbreakable_space_node::reread(int *)
+{
+ return 0;
+}
+
+int hmotion_node::reread(int *)
+{
+ if (unformat && was_tab) {
+ curenv->handle_tab(0);
+ unformat = 0;
+ return 1;
+ }
+ return 0;
+}
+
+static int leading_spaces_number = 0;
+static int leading_spaces_space = 0;
+
+void process_input_stack()
+{
+ int_stack trap_bol_stack;
+ int bol = 1;
+ for (;;) {
+ int suppress_next = 0;
+ switch (tok.type) {
+ case token::TOKEN_CHAR:
+ {
+ unsigned char ch = tok.c;
+ if (bol && !have_input
+ && (ch == curenv->control_char
+ || ch == curenv->no_break_control_char)) {
+ break_flag = ch == curenv->control_char;
+ // skip tabs as well as spaces here
+ do {
+ tok.next();
+ } while (tok.is_white_space());
+ symbol nm = get_name();
+#if defined(DEBUGGING)
+ if (debug_state) {
+ if (! nm.is_null()) {
+ if (strcmp(nm.contents(), "test") == 0) {
+ fprintf(stderr, "found it!\n");
+ fflush(stderr);
+ }
+ fprintf(stderr, "interpreting [%s]", nm.contents());
+ if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
+ fprintf(stderr, " currently in diversion: %s",
+ curdiv->get_diversion_name());
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ }
+ }
+#endif
+ if (nm.is_null())
+ skip_line();
+ else {
+ interpolate_macro(nm);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
+ curenv->dump_troff_state();
+ }
+#endif
+ }
+ suppress_next = 1;
+ }
+ else {
+ if (possibly_handle_first_page_transition())
+ ;
+ else {
+ for (;;) {
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
+ }
+#endif
+ curenv->add_char(charset_table[ch]);
+ tok.next();
+ if (tok.type != token::TOKEN_CHAR)
+ break;
+ ch = tok.c;
+ }
+ suppress_next = 1;
+ bol = 0;
+ }
+ }
+ break;
+ }
+ case token::TOKEN_TRANSPARENT:
+ {
+ if (bol) {
+ if (possibly_handle_first_page_transition())
+ ;
+ else {
+ int cc;
+ do {
+ node *n;
+ cc = get_copy(&n);
+ if (cc != EOF) {
+ if (cc != '\0')
+ curdiv->transparent_output(transparent_translate(cc));
+ else
+ curdiv->transparent_output(n);
+ }
+ } while (cc != '\n' && cc != EOF);
+ if (cc == EOF)
+ curdiv->transparent_output('\n');
+ }
+ }
+ break;
+ }
+ case token::TOKEN_NEWLINE:
+ {
+ if (bol && !old_have_input
+ && !curenv->get_prev_line_interrupted())
+ trapping_blank_line();
+ else {
+ curenv->newline();
+ bol = 1;
+ }
+ break;
+ }
+ case token::TOKEN_REQUEST:
+ {
+ int request_code = tok.c;
+ tok.next();
+ switch (request_code) {
+ case TITLE_REQUEST:
+ title();
+ break;
+ case COPY_FILE_REQUEST:
+ copy_file();
+ break;
+ case TRANSPARENT_FILE_REQUEST:
+ transparent_file();
+ break;
+#ifdef COLUMN
+ case VJUSTIFY_REQUEST:
+ vjustify();
+ break;
+#endif /* COLUMN */
+ default:
+ assert(0);
+ break;
+ }
+ suppress_next = 1;
+ break;
+ }
+ case token::TOKEN_SPACE:
+ {
+ if (possibly_handle_first_page_transition())
+ ;
+ else if (bol && !curenv->get_prev_line_interrupted()) {
+ int nspaces = 0;
+ // save space_width now so that it isn't changed by \f or \s
+ // which we wouldn't notice here
+ hunits space_width = curenv->get_space_width();
+ do {
+ nspaces += tok.nspaces();
+ tok.next();
+ } while (tok.is_space());
+ if (tok.is_newline())
+ trapping_blank_line();
+ else {
+ push_token(tok);
+ leading_spaces_number = nspaces;
+ leading_spaces_space = space_width.to_units() * nspaces;
+ if (!leading_spaces_macro_name.is_null())
+ spring_trap(leading_spaces_macro_name);
+ else {
+ curenv->do_break();
+ curenv->add_node(new hmotion_node(space_width * nspaces,
+ curenv->get_fill_color()));
+ }
+ bol = 0;
+ }
+ }
+ else {
+ curenv->space();
+ bol = 0;
+ }
+ break;
+ }
+ case token::TOKEN_EOF:
+ return;
+ case token::TOKEN_NODE:
+ case token::TOKEN_HORIZONTAL_SPACE:
+ {
+ if (possibly_handle_first_page_transition())
+ ;
+ else if (tok.nd->reread(&bol)) {
+ delete tok.nd;
+ tok.nd = 0;
+ }
+ else {
+ curenv->add_node(tok.nd);
+ tok.nd = 0;
+ bol = 0;
+ curenv->possibly_break_line(1);
+ }
+ break;
+ }
+ case token::TOKEN_PAGE_EJECTOR:
+ {
+ continue_page_eject();
+ // I think we just want to preserve bol.
+ // bol = 1;
+ break;
+ }
+ case token::TOKEN_BEGIN_TRAP:
+ {
+ trap_bol_stack.push(bol);
+ bol = 1;
+ have_input = 0;
+ break;
+ }
+ case token::TOKEN_END_TRAP:
+ {
+ if (trap_bol_stack.is_empty())
+ error("spurious end trap token detected!");
+ else
+ bol = trap_bol_stack.pop();
+ have_input = 0;
+
+ /* I'm not totally happy about this. But I can't think of any other
+ way to do it. Doing an output_pending_lines() whenever a
+ TOKEN_END_TRAP is detected doesn't work: for example,
+
+ .wh -1i x
+ .de x
+ 'bp
+ ..
+ .wh -.5i y
+ .de y
+ .tl ''-%-''
+ ..
+ .br
+ .ll .5i
+ .sp |\n(.pu-1i-.5v
+ a\%very\%very\%long\%word
+
+ will print all but the first lines from the word immediately
+ after the footer, rather than on the next page. */
+
+ if (trap_bol_stack.is_empty())
+ curenv->output_pending_lines();
+ break;
+ }
+ default:
+ {
+ bol = 0;
+ tok.process();
+ break;
+ }
+ }
+ if (!suppress_next)
+ tok.next();
+ trap_sprung_flag = 0;
+ }
+}
+
+#ifdef WIDOW_CONTROL
+
+void flush_pending_lines()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ curenv->output_pending_lines();
+ tok.next();
+}
+
+#endif /* WIDOW_CONTROL */
+
+request_or_macro::request_or_macro()
+{
+}
+
+macro *request_or_macro::to_macro()
+{
+ return 0;
+}
+
+request::request(REQUEST_FUNCP pp) : p(pp)
+{
+}
+
+void request::invoke(symbol, bool)
+{
+ (*p)();
+}
+
+struct char_block {
+ enum { SIZE = 128 };
+ unsigned char s[SIZE];
+ char_block *next;
+ char_block();
+};
+
+char_block::char_block()
+: next(0)
+{
+}
+
+class char_list {
+public:
+ char_list();
+ ~char_list();
+ void append(unsigned char);
+ void set(unsigned char, int);
+ unsigned char get(int);
+ int length();
+private:
+ unsigned char *ptr;
+ int len;
+ char_block *head;
+ char_block *tail;
+ friend class macro_header;
+ friend class string_iterator;
+};
+
+char_list::char_list()
+: ptr(0), len(0), head(0), tail(0)
+{
+}
+
+char_list::~char_list()
+{
+ while (head != 0) {
+ char_block *tem = head;
+ head = head->next;
+ delete tem;
+ }
+}
+
+int char_list::length()
+{
+ return len;
+}
+
+void char_list::append(unsigned char c)
+{
+ if (tail == 0) {
+ head = tail = new char_block;
+ ptr = tail->s;
+ }
+ else {
+ if (ptr >= tail->s + char_block::SIZE) {
+ tail->next = new char_block;
+ tail = tail->next;
+ ptr = tail->s;
+ }
+ }
+ *ptr++ = c;
+ len++;
+}
+
+void char_list::set(unsigned char c, int offset)
+{
+ assert(len > offset);
+ // optimization for access at the end
+ int boundary = len - len % char_block::SIZE;
+ if (offset >= boundary) {
+ *(tail->s + offset - boundary) = c;
+ return;
+ }
+ char_block *tem = head;
+ int l = 0;
+ for (;;) {
+ l += char_block::SIZE;
+ if (l > offset) {
+ *(tem->s + offset % char_block::SIZE) = c;
+ return;
+ }
+ tem = tem->next;
+ }
+}
+
+unsigned char char_list::get(int offset)
+{
+ assert(len > offset);
+ // optimization for access at the end
+ int boundary = len - len % char_block::SIZE;
+ if (offset >= boundary)
+ return *(tail->s + offset - boundary);
+ char_block *tem = head;
+ int l = 0;
+ for (;;) {
+ l += char_block::SIZE;
+ if (l > offset)
+ return *(tem->s + offset % char_block::SIZE);
+ tem = tem->next;
+ }
+}
+
+class node_list {
+ node *head;
+ node *tail;
+public:
+ node_list();
+ ~node_list();
+ void append(node *);
+ int length();
+ node *extract();
+
+ friend class macro_header;
+ friend class string_iterator;
+};
+
+void node_list::append(node *n)
+{
+ if (head == 0) {
+ n->next = 0;
+ head = tail = n;
+ }
+ else {
+ n->next = 0;
+ tail = tail->next = n;
+ }
+}
+
+int node_list::length()
+{
+ int total = 0;
+ for (node *n = head; n != 0; n = n->next)
+ ++total;
+ return total;
+}
+
+node_list::node_list()
+{
+ head = tail = 0;
+}
+
+node *node_list::extract()
+{
+ node *temp = head;
+ head = tail = 0;
+ return temp;
+}
+
+node_list::~node_list()
+{
+ delete_node_list(head);
+}
+
+class macro_header {
+public:
+ int count;
+ char_list cl;
+ node_list nl;
+ macro_header() { count = 1; }
+ macro_header *copy(int);
+};
+
+macro::~macro()
+{
+ if (p != 0 && --(p->count) <= 0)
+ delete p;
+}
+
+macro::macro()
+: is_a_diversion(0), is_a_string(1)
+{
+ if (!input_stack::get_location(1, &filename, &lineno)) {
+ filename = 0;
+ lineno = 0;
+ }
+ len = 0;
+ empty_macro = 1;
+ p = 0;
+}
+
+macro::macro(const macro &m)
+: filename(m.filename), lineno(m.lineno), len(m.len),
+ empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion),
+ is_a_string(m.is_a_string), p(m.p)
+{
+ if (p != 0)
+ p->count++;
+}
+
+macro::macro(int is_div)
+: is_a_diversion(is_div)
+{
+ if (!input_stack::get_location(1, &filename, &lineno)) {
+ filename = 0;
+ lineno = 0;
+ }
+ len = 0;
+ empty_macro = 1;
+ is_a_string = 1;
+ p = 0;
+}
+
+int macro::is_diversion()
+{
+ return is_a_diversion;
+}
+
+int macro::is_string()
+{
+ return is_a_string;
+}
+
+void macro::clear_string_flag()
+{
+ is_a_string = 0;
+}
+
+macro &macro::operator=(const macro &m)
+{
+ // don't assign object
+ if (m.p != 0)
+ m.p->count++;
+ if (p != 0 && --(p->count) <= 0)
+ delete p;
+ p = m.p;
+ filename = m.filename;
+ lineno = m.lineno;
+ len = m.len;
+ empty_macro = m.empty_macro;
+ is_a_diversion = m.is_a_diversion;
+ is_a_string = m.is_a_string;
+ return *this;
+}
+
+void macro::append(unsigned char c)
+{
+ assert(c != 0);
+ if (p == 0)
+ p = new macro_header;
+ if (p->cl.length() != len) {
+ macro_header *tem = p->copy(len);
+ if (--(p->count) <= 0)
+ delete p;
+ p = tem;
+ }
+ p->cl.append(c);
+ ++len;
+ if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
+ empty_macro = 0;
+}
+
+void macro::set(unsigned char c, int offset)
+{
+ assert(p != 0);
+ assert(c != 0);
+ p->cl.set(c, offset);
+}
+
+unsigned char macro::get(int offset)
+{
+ assert(p != 0);
+ return p->cl.get(offset);
+}
+
+int macro::length()
+{
+ return len;
+}
+
+void macro::append_str(const char *s)
+{
+ int i = 0;
+
+ if (s) {
+ while (s[i] != (char)0) {
+ append(s[i]);
+ i++;
+ }
+ }
+}
+
+void macro::append(node *n)
+{
+ assert(n != 0);
+ if (p == 0)
+ p = new macro_header;
+ if (p->cl.length() != len) {
+ macro_header *tem = p->copy(len);
+ if (--(p->count) <= 0)
+ delete p;
+ p = tem;
+ }
+ p->cl.append(0);
+ p->nl.append(n);
+ ++len;
+ empty_macro = 0;
+}
+
+void macro::append_unsigned(unsigned int i)
+{
+ unsigned int j = i / 10;
+ if (j != 0)
+ append_unsigned(j);
+ append(((unsigned char)(((int)'0') + i % 10)));
+}
+
+void macro::append_int(int i)
+{
+ if (i < 0) {
+ append('-');
+ i = -i;
+ }
+ append_unsigned((unsigned int)i);
+}
+
+void macro::print_size()
+{
+ errprint("%1", len);
+}
+
+// make a copy of the first n bytes
+
+macro_header *macro_header::copy(int n)
+{
+ macro_header *p = new macro_header;
+ char_block *bp = cl.head;
+ unsigned char *ptr = bp->s;
+ node *nd = nl.head;
+ while (--n >= 0) {
+ if (ptr >= bp->s + char_block::SIZE) {
+ bp = bp->next;
+ ptr = bp->s;
+ }
+ unsigned char c = *ptr++;
+ p->cl.append(c);
+ if (c == 0) {
+ p->nl.append(nd->copy());
+ nd = nd->next;
+ }
+ }
+ return p;
+}
+
+void print_macros()
+{
+ object_dictionary_iterator iter(request_dictionary);
+ request_or_macro *rm;
+ symbol s;
+ while (iter.get(&s, (object **)&rm)) {
+ assert(!s.is_null());
+ macro *m = rm->to_macro();
+ if (m) {
+ errprint("%1\t", s.contents());
+ m->print_size();
+ errprint("\n");
+ }
+ }
+ fflush(stderr);
+ skip_line();
+}
+
+class string_iterator : public input_iterator {
+ macro mac;
+ const char *how_invoked;
+ int newline_flag;
+ int lineno;
+ char_block *bp;
+ int count; // of characters remaining
+ node *nd;
+ int saved_compatible_flag;
+ int with_break; // inherited from the caller
+protected:
+ symbol nm;
+ string_iterator();
+public:
+ string_iterator(const macro &, const char * = 0, symbol = NULL_SYMBOL);
+ int fill(node **);
+ int peek();
+ int get_location(int, const char **, int *);
+ void backtrace();
+ int get_break_flag() { return with_break; }
+ void save_compatible_flag(int f) { saved_compatible_flag = f; }
+ int get_compatible_flag() { return saved_compatible_flag; }
+ int is_diversion();
+};
+
+string_iterator::string_iterator(const macro &m, const char *p, symbol s)
+: input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
+ lineno(1), nm(s)
+{
+ count = mac.len;
+ if (count != 0) {
+ bp = mac.p->cl.head;
+ nd = mac.p->nl.head;
+ ptr = eptr = bp->s;
+ }
+ else {
+ bp = 0;
+ nd = 0;
+ ptr = eptr = 0;
+ }
+ with_break = input_stack::get_break_flag();
+}
+
+string_iterator::string_iterator()
+{
+ bp = 0;
+ nd = 0;
+ ptr = eptr = 0;
+ newline_flag = 0;
+ how_invoked = 0;
+ lineno = 1;
+ count = 0;
+ with_break = input_stack::get_break_flag();
+}
+
+int string_iterator::is_diversion()
+{
+ return mac.is_diversion();
+}
+
+int string_iterator::fill(node **np)
+{
+ if (newline_flag)
+ lineno++;
+ newline_flag = 0;
+ if (count <= 0)
+ return EOF;
+ const unsigned char *p = eptr;
+ if (p >= bp->s + char_block::SIZE) {
+ bp = bp->next;
+ p = bp->s;
+ }
+ if (*p == '\0') {
+ if (np) {
+ *np = nd->copy();
+ if (is_diversion())
+ (*np)->div_nest_level = input_stack::get_div_level();
+ else
+ (*np)->div_nest_level = 0;
+ }
+ nd = nd->next;
+ eptr = ptr = p + 1;
+ count--;
+ return 0;
+ }
+ const unsigned char *e = bp->s + char_block::SIZE;
+ if (e - p > count)
+ e = p + count;
+ ptr = p;
+ while (p < e) {
+ unsigned char c = *p;
+ if (c == '\n' || c == ESCAPE_NEWLINE) {
+ newline_flag = 1;
+ p++;
+ break;
+ }
+ if (c == '\0')
+ break;
+ p++;
+ }
+ eptr = p;
+ count -= p - ptr;
+ return *ptr++;
+}
+
+int string_iterator::peek()
+{
+ if (count <= 0)
+ return EOF;
+ const unsigned char *p = eptr;
+ if (p >= bp->s + char_block::SIZE) {
+ p = bp->next->s;
+ }
+ return *p;
+}
+
+int string_iterator::get_location(int allow_macro,
+ const char **filep, int *linep)
+{
+ if (!allow_macro)
+ return 0;
+ if (mac.filename == 0)
+ return 0;
+ *filep = mac.filename;
+ *linep = mac.lineno + lineno - 1;
+ return 1;
+}
+
+void string_iterator::backtrace()
+{
+ if (mac.filename) {
+ if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ errprint("backtrace: '%1':%2", mac.filename,
+ mac.lineno + lineno - 1);
+ if (how_invoked) {
+ if (!nm.is_null())
+ errprint(": %1 '%2'\n", how_invoked, nm.contents());
+ else
+ errprint(": %1\n", how_invoked);
+ }
+ else
+ errprint("\n");
+ }
+}
+
+class temp_iterator : public input_iterator {
+ unsigned char *base;
+ temp_iterator(const char *, int len);
+public:
+ ~temp_iterator();
+ friend input_iterator *make_temp_iterator(const char *);
+};
+
+#ifdef __GNUG__
+inline
+#endif
+temp_iterator::temp_iterator(const char *s, int len)
+{
+ base = new unsigned char[len];
+ if (len > 0)
+ memcpy(base, s, len);
+ ptr = base;
+ eptr = base + len;
+}
+
+temp_iterator::~temp_iterator()
+{
+ delete[] base;
+}
+
+
+input_iterator *make_temp_iterator(const char *s)
+{
+ if (s == 0)
+ return new temp_iterator(s, 0);
+ else {
+ int n = strlen(s);
+ return new temp_iterator(s, n);
+ }
+}
+
+// this is used when macros with arguments are interpolated
+
+struct arg_list {
+ macro mac;
+ int space_follows;
+ arg_list *next;
+ arg_list(const macro &, int);
+ arg_list(const arg_list *);
+ ~arg_list();
+};
+
+arg_list::arg_list(const macro &m, int s) : mac(m), space_follows(s), next(0)
+{
+}
+
+arg_list::arg_list(const arg_list *al)
+: next(0)
+{
+ mac = al->mac;
+ space_follows = al->space_follows;
+ arg_list **a = &next;
+ arg_list *p = al->next;
+ while (p) {
+ *a = new arg_list(p->mac, p->space_follows);
+ p = p->next;
+ a = &(*a)->next;
+ }
+}
+
+arg_list::~arg_list()
+{
+}
+
+class macro_iterator : public string_iterator {
+ arg_list *args;
+ int argc;
+ int with_break; // whether called as .foo or 'foo
+public:
+ macro_iterator(symbol, macro &, const char * = "macro", int = 0);
+ macro_iterator();
+ ~macro_iterator();
+ int has_args() { return 1; }
+ input_iterator *get_arg(int);
+ arg_list *get_arg_list();
+ symbol get_macro_name();
+ int space_follows_arg(int);
+ int get_break_flag() { return with_break; }
+ int nargs() { return argc; }
+ void add_arg(const macro &, int);
+ void shift(int);
+ int is_macro() { return 1; }
+ int is_diversion();
+};
+
+input_iterator *macro_iterator::get_arg(int i)
+{
+ if (i == 0)
+ return make_temp_iterator(nm.contents());
+ if (i > 0 && i <= argc) {
+ arg_list *p = args;
+ for (int j = 1; j < i; j++) {
+ assert(p != 0);
+ p = p->next;
+ }
+ return new string_iterator(p->mac);
+ }
+ else
+ return 0;
+}
+
+arg_list *macro_iterator::get_arg_list()
+{
+ return args;
+}
+
+symbol macro_iterator::get_macro_name()
+{
+ return nm;
+}
+
+int macro_iterator::space_follows_arg(int i)
+{
+ if (i > 0 && i <= argc) {
+ arg_list *p = args;
+ for (int j = 1; j < i; j++) {
+ assert(p != 0);
+ p = p->next;
+ }
+ return p->space_follows;
+ }
+ else
+ return 0;
+}
+
+void macro_iterator::add_arg(const macro &m, int s)
+{
+ arg_list **p;
+ for (p = &args; *p; p = &((*p)->next))
+ ;
+ *p = new arg_list(m, s);
+ ++argc;
+}
+
+void macro_iterator::shift(int n)
+{
+ while (n > 0 && argc > 0) {
+ arg_list *tem = args;
+ args = args->next;
+ delete tem;
+ --argc;
+ --n;
+ }
+}
+
+// This gets used by, e.g., .if '\?xxx\?''.
+
+int operator==(const macro &m1, const macro &m2)
+{
+ if (m1.len != m2.len)
+ return 0;
+ string_iterator iter1(m1);
+ string_iterator iter2(m2);
+ int n = m1.len;
+ while (--n >= 0) {
+ node *nd1 = 0;
+ int c1 = iter1.get(&nd1);
+ assert(c1 != EOF);
+ node *nd2 = 0;
+ int c2 = iter2.get(&nd2);
+ assert(c2 != EOF);
+ if (c1 != c2) {
+ if (c1 == 0)
+ delete nd1;
+ else if (c2 == 0)
+ delete nd2;
+ return 0;
+ }
+ if (c1 == 0) {
+ assert(nd1 != 0);
+ assert(nd2 != 0);
+ int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
+ delete nd1;
+ delete nd2;
+ if (!are_same)
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void interpolate_macro(symbol nm, bool do_not_want_next_token)
+{
+ request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
+ if (p == 0) {
+ int warned = 0;
+ const char *s = nm.contents();
+ if (strlen(s) > 2) {
+ request_or_macro *r;
+ char buf[3];
+ buf[0] = s[0];
+ buf[1] = s[1];
+ buf[2] = '\0';
+ r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
+ if (r) {
+ macro *m = r->to_macro();
+ if (!m || !m->empty())
+ warned = warning(WARN_SPACE,
+ "macro '%1' not defined "
+ "(possibly missing space after '%2')",
+ nm.contents(), buf);
+ }
+ }
+ if (!warned) {
+ warning(WARN_MAC, "macro '%1' not defined", nm.contents());
+ p = new macro;
+ request_dictionary.define(nm, p);
+ }
+ }
+ if (p)
+ p->invoke(nm, do_not_want_next_token);
+ else {
+ skip_line();
+ return;
+ }
+}
+
+static void decode_args(macro_iterator *mi)
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ node *n;
+ int c = get_copy(&n);
+ for (;;) {
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '\n' || c == EOF)
+ break;
+ macro arg;
+ int quote_input_level = 0;
+ int done_tab_warning = 0;
+ arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
+ // we store discarded double quotes for \$^
+ if (c == '"') {
+ arg.append(DOUBLE_QUOTE);
+ quote_input_level = input_stack::get_level();
+ c = get_copy(&n);
+ }
+ while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
+ if (quote_input_level > 0 && c == '"'
+ && (compatible_flag
+ || input_stack::get_level() == quote_input_level)) {
+ arg.append(DOUBLE_QUOTE);
+ c = get_copy(&n);
+ if (c == '"') {
+ arg.append(c);
+ c = get_copy(&n);
+ }
+ else
+ break;
+ }
+ else {
+ if (c == 0)
+ arg.append(n);
+ else {
+ if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
+ warning(WARN_TAB, "tab character in unquoted macro argument");
+ done_tab_warning = 1;
+ }
+ arg.append(c);
+ }
+ c = get_copy(&n);
+ }
+ }
+ arg.append(POP_GROFFCOMP_MODE);
+ mi->add_arg(arg, (c == ' '));
+ }
+ }
+}
+
+static void decode_string_args(macro_iterator *mi)
+{
+ node *n;
+ int c = get_copy(&n);
+ for (;;) {
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '\n' || c == EOF) {
+ error("missing ']'");
+ break;
+ }
+ if (c == ']')
+ break;
+ macro arg;
+ int quote_input_level = 0;
+ int done_tab_warning = 0;
+ if (c == '"') {
+ quote_input_level = input_stack::get_level();
+ c = get_copy(&n);
+ }
+ while (c != EOF && c != '\n'
+ && !(c == ']' && quote_input_level == 0)
+ && !(c == ' ' && quote_input_level == 0)) {
+ if (quote_input_level > 0 && c == '"'
+ && input_stack::get_level() == quote_input_level) {
+ c = get_copy(&n);
+ if (c == '"') {
+ arg.append(c);
+ c = get_copy(&n);
+ }
+ else
+ break;
+ }
+ else {
+ if (c == 0)
+ arg.append(n);
+ else {
+ if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
+ warning(WARN_TAB, "tab character in unquoted string argument");
+ done_tab_warning = 1;
+ }
+ arg.append(c);
+ }
+ c = get_copy(&n);
+ }
+ }
+ mi->add_arg(arg, (c == ' '));
+ }
+}
+
+void macro::invoke(symbol nm, bool do_not_want_next_token)
+{
+ macro_iterator *mi = new macro_iterator(nm, *this);
+ decode_args(mi);
+ input_stack::push(mi);
+ // we must delay tok.next() in case the function has been called by
+ // do_request to assure proper handling of compatible_flag
+ if (!do_not_want_next_token)
+ tok.next();
+}
+
+macro *macro::to_macro()
+{
+ return this;
+}
+
+int macro::empty()
+{
+ return empty_macro == 1;
+}
+
+macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called,
+ int init_args)
+: string_iterator(m, how_called, s), args(0), argc(0), with_break(break_flag)
+{
+ if (init_args) {
+ arg_list *al = input_stack::get_arg_list();
+ if (al) {
+ args = new arg_list(al);
+ argc = input_stack::nargs();
+ }
+ }
+}
+
+macro_iterator::macro_iterator() : args(0), argc(0), with_break(break_flag)
+{
+}
+
+macro_iterator::~macro_iterator()
+{
+ while (args != 0) {
+ arg_list *tem = args;
+ args = args->next;
+ delete tem;
+ }
+}
+
+dictionary composite_dictionary(17);
+
+void composite_request()
+{
+ symbol from = get_name(true /* required */);
+ if (!from.is_null()) {
+ const char *from_gn = glyph_name_to_unicode(from.contents());
+ if (!from_gn) {
+ from_gn = check_unicode_name(from.contents());
+ if (!from_gn) {
+ error("invalid composite glyph name '%1'", from.contents());
+ skip_line();
+ return;
+ }
+ }
+ const char *from_decomposed = decompose_unicode(from_gn);
+ if (from_decomposed)
+ from_gn = &from_decomposed[1];
+ symbol to = get_name(true /* required */);
+ if (to.is_null())
+ composite_dictionary.remove(symbol(from_gn));
+ else {
+ const char *to_gn = glyph_name_to_unicode(to.contents());
+ if (!to_gn) {
+ to_gn = check_unicode_name(to.contents());
+ if (!to_gn) {
+ error("invalid composite glyph name '%1'", to.contents());
+ skip_line();
+ return;
+ }
+ }
+ const char *to_decomposed = decompose_unicode(to_gn);
+ if (to_decomposed)
+ to_gn = &to_decomposed[1];
+ if (strcmp(from_gn, to_gn) == 0)
+ composite_dictionary.remove(symbol(from_gn));
+ else
+ (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
+ }
+ }
+ skip_line();
+}
+
+static symbol composite_glyph_name(symbol nm)
+{
+ macro_iterator *mi = new macro_iterator();
+ decode_string_args(mi);
+ input_stack::push(mi);
+ const char *gn = glyph_name_to_unicode(nm.contents());
+ if (!gn) {
+ gn = check_unicode_name(nm.contents());
+ if (!gn) {
+ error("invalid base glyph '%1' in composite glyph name", nm.contents());
+ return EMPTY_SYMBOL;
+ }
+ }
+ const char *gn_decomposed = decompose_unicode(gn);
+ string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
+ string gl;
+ int n = input_stack::nargs();
+ for (int i = 1; i <= n; i++) {
+ glyph_name += '_';
+ input_iterator *p = input_stack::get_arg(i);
+ gl.clear();
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ gl += c;
+ gl += '\0';
+ const char *u = glyph_name_to_unicode(gl.contents());
+ if (!u) {
+ u = check_unicode_name(gl.contents());
+ if (!u) {
+ error("invalid component '%1' in composite glyph name",
+ gl.contents());
+ return EMPTY_SYMBOL;
+ }
+ }
+ const char *decomposed = decompose_unicode(u);
+ if (decomposed)
+ u = &decomposed[1];
+ void *mapped_composite = composite_dictionary.lookup(symbol(u));
+ if (mapped_composite)
+ u = (const char *)mapped_composite;
+ glyph_name += u;
+ }
+ glyph_name += '\0';
+ const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
+ if (groff_gn)
+ return symbol(groff_gn);
+ gl.clear();
+ gl += 'u';
+ gl += glyph_name;
+ return symbol(gl.contents());
+}
+
+int trap_sprung_flag = 0;
+int postpone_traps_flag = 0;
+symbol postponed_trap;
+
+void spring_trap(symbol nm)
+{
+ assert(!nm.is_null());
+ trap_sprung_flag = 1;
+ if (postpone_traps_flag) {
+ postponed_trap = nm;
+ return;
+ }
+ static char buf[2] = { BEGIN_TRAP, '\0' };
+ static char buf2[2] = { END_TRAP, '\0' };
+ input_stack::push(make_temp_iterator(buf2));
+ request_or_macro *p = lookup_request(nm);
+ // We don't perform this validation at the time the trap is planted
+ // because a request name might be replaced by a macro by the time the
+ // trap springs.
+ macro *m = p->to_macro();
+ if (m)
+ input_stack::push(new macro_iterator(nm, *m, "trap-called macro"));
+ else
+ error("trap failed to spring: '%1' is a request", nm.contents());
+ input_stack::push(make_temp_iterator(buf));
+}
+
+void postpone_traps()
+{
+ postpone_traps_flag = 1;
+}
+
+int unpostpone_traps()
+{
+ postpone_traps_flag = 0;
+ if (!postponed_trap.is_null()) {
+ spring_trap(postponed_trap);
+ postponed_trap = NULL_SYMBOL;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void read_request()
+{
+ macro_iterator *mi = new macro_iterator;
+ int reading_from_terminal = isatty(fileno(stdin));
+ int had_prompt = 0;
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c = get_copy(0);
+ while (c == ' ')
+ c = get_copy(0);
+ while (c != EOF && c != '\n' && c != ' ') {
+ if (!is_invalid_input_char(c)) {
+ if (reading_from_terminal)
+ fputc(c, stderr);
+ had_prompt = 1;
+ }
+ c = get_copy(0);
+ }
+ if (c == ' ') {
+ tok.make_space();
+ decode_args(mi);
+ }
+ }
+ if (reading_from_terminal) {
+ fputc(had_prompt ? ':' : '\a', stderr);
+ fflush(stderr);
+ }
+ input_stack::push(mi);
+ macro mac;
+ int nl = 0;
+ int c;
+ while ((c = getchar()) != EOF) {
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ if (c == '\n') {
+ if (nl)
+ break;
+ else
+ nl = 1;
+ }
+ else
+ nl = 0;
+ mac.append(c);
+ }
+ }
+ if (reading_from_terminal)
+ clearerr(stdin);
+ input_stack::push(new string_iterator(mac));
+ tok.next();
+}
+
+enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
+enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
+enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
+
+void do_define_string(define_mode mode, comp_mode comp)
+{
+ symbol nm;
+ node *n = 0; // pacify compiler
+ int c;
+ nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad string definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ macro mac;
+ request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
+ macro *mm = rm ? rm->to_macro() : 0;
+ if (mode == DEFINE_APPEND && mm)
+ mac = *mm;
+ if (comp == COMP_DISABLE)
+ mac.append(PUSH_GROFF_MODE);
+ else if (comp == COMP_ENABLE)
+ mac.append(PUSH_COMP_MODE);
+ while (c != '\n' && c != EOF) {
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append((unsigned char)c);
+ c = get_copy(&n);
+ }
+ if (comp == COMP_DISABLE || comp == COMP_ENABLE)
+ mac.append(POP_GROFFCOMP_MODE);
+ if (!mm) {
+ mm = new macro;
+ request_dictionary.define(nm, mm);
+ }
+ *mm = mac;
+ tok.next();
+}
+
+void define_string()
+{
+ do_define_string(DEFINE_NORMAL,
+ compatible_flag ? COMP_ENABLE: COMP_IGNORE);
+}
+
+void define_nocomp_string()
+{
+ do_define_string(DEFINE_NORMAL, COMP_DISABLE);
+}
+
+void append_string()
+{
+ do_define_string(DEFINE_APPEND,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_nocomp_string()
+{
+ do_define_string(DEFINE_APPEND, COMP_DISABLE);
+}
+
+void do_define_character(char_mode mode, const char *font_name)
+{
+ node *n = 0; // pacify compiler
+ int c;
+ tok.skip();
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_line();
+ return;
+ }
+ if (font_name) {
+ string s(font_name);
+ s += ' ';
+ s += ci->nm.contents();
+ s += '\0';
+ ci = get_charinfo(symbol(s.contents()));
+ }
+ tok.next();
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad character definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ' || c == '\t')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ macro *m = new macro;
+ while (c != '\n' && c != EOF) {
+ if (c == 0)
+ m->append(n);
+ else
+ m->append((unsigned char)c);
+ c = get_copy(&n);
+ }
+ m = ci->setx_macro(m, mode);
+ if (m)
+ delete m;
+ tok.next();
+}
+
+void define_character()
+{
+ do_define_character(CHAR_NORMAL);
+}
+
+void define_fallback_character()
+{
+ do_define_character(CHAR_FALLBACK);
+}
+
+void define_special_character()
+{
+ do_define_character(CHAR_SPECIAL);
+}
+
+static void remove_character()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (!tok.is_space() && !tok.is_tab()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (!ci)
+ break;
+ macro *m = ci->set_macro(0);
+ if (m)
+ delete m;
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+static void interpolate_string(symbol nm)
+{
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot interpolate request '%1'", nm.contents());
+ else {
+ if (m->is_string()) {
+ string_iterator *si = new string_iterator(*m, "string", nm);
+ input_stack::push(si);
+ }
+ else {
+ // if a macro is called as a string, \$0 doesn't get changed
+ macro_iterator *mi = new macro_iterator(input_stack::get_macro_name(),
+ *m, "string", 1);
+ input_stack::push(mi);
+ }
+ }
+}
+
+static void interpolate_string_with_args(symbol nm)
+{
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot interpolate request '%1'", nm.contents());
+ else {
+ macro_iterator *mi = new macro_iterator(nm, *m);
+ decode_string_args(mi);
+ input_stack::push(mi);
+ }
+}
+
+static void interpolate_arg(symbol nm)
+{
+ const char *s = nm.contents();
+ if (!s || *s == '\0')
+ copy_mode_error("missing positional argument number");
+ else if (s[1] == 0 && csdigit(s[0]))
+ input_stack::push(input_stack::get_arg(s[0] - '0'));
+ else if (s[0] == '*' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ for (int i = 1; i <= limit; i++) {
+ input_iterator *p = input_stack::get_arg(i);
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ args += c;
+ if (i != limit)
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else if (s[0] == '@' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ for (int i = 1; i <= limit; i++) {
+ args += '"';
+ args += char(BEGIN_QUOTE);
+ input_iterator *p = input_stack::get_arg(i);
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ args += c;
+ args += char(END_QUOTE);
+ args += '"';
+ if (i != limit)
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else if (s[0] == '^' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ int c = input_stack::peek();
+ for (int i = 1; i <= limit; i++) {
+ input_iterator *p = input_stack::get_arg(i);
+ while ((c = p->get(0)) != EOF) {
+ if (c == DOUBLE_QUOTE)
+ c = '"';
+ args += c;
+ }
+ if (input_stack::space_follows_arg(i))
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else {
+ const char *p;
+ for (p = s; *p && csdigit(*p); p++)
+ ;
+ if (*p)
+ copy_mode_error("invalid positional argument number '%1'", s);
+ else
+ input_stack::push(input_stack::get_arg(atoi(s)));
+ }
+}
+
+void handle_first_page_transition()
+{
+ push_token(tok);
+ topdiv->begin_page();
+}
+
+// We push back a token by wrapping it up in a token_node, and
+// wrapping that up in a string_iterator.
+
+static void push_token(const token &t)
+{
+ macro m;
+ m.append(new token_node(t));
+ input_stack::push(new string_iterator(m));
+}
+
+void push_page_ejector()
+{
+ static char buf[2] = { PAGE_EJECTOR, '\0' };
+ input_stack::push(make_temp_iterator(buf));
+}
+
+void handle_initial_request(unsigned char code)
+{
+ char buf[2];
+ buf[0] = code;
+ buf[1] = '\0';
+ macro mac;
+ mac.append(new token_node(tok));
+ input_stack::push(new string_iterator(mac));
+ input_stack::push(make_temp_iterator(buf));
+ topdiv->begin_page();
+ tok.next();
+}
+
+void handle_initial_title()
+{
+ handle_initial_request(TITLE_REQUEST);
+}
+
+void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
+{
+ symbol nm, term, dot_symbol(".");
+ if (calling == CALLING_INDIRECT) {
+ symbol temp1 = get_name(true /* required */);
+ if (temp1.is_null()) {
+ skip_line();
+ return;
+ }
+ symbol temp2 = get_name();
+ input_stack::push(make_temp_iterator("\n"));
+ if (!temp2.is_null()) {
+ interpolate_string(temp2);
+ input_stack::push(make_temp_iterator(" "));
+ }
+ interpolate_string(temp1);
+ input_stack::push(make_temp_iterator(" "));
+ tok.next();
+ }
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ }
+ term = get_name(); // the request that terminates the definition
+ if (term.is_null())
+ term = dot_symbol;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ const char *start_filename;
+ int start_lineno;
+ int have_start_location = input_stack::get_location(0, &start_filename,
+ &start_lineno);
+ node *n;
+ // doing this here makes the line numbers come out right
+ int c = get_copy(&n, true /* is defining*/);
+ macro mac;
+ macro *mm = 0;
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ request_or_macro *rm =
+ (request_or_macro *)request_dictionary.lookup(nm);
+ if (rm)
+ mm = rm->to_macro();
+ if (mm && mode == DEFINE_APPEND)
+ mac = *mm;
+ }
+ int bol = 1;
+ if (comp == COMP_DISABLE)
+ mac.append(PUSH_GROFF_MODE);
+ else if (comp == COMP_ENABLE)
+ mac.append(PUSH_COMP_MODE);
+ for (;;) {
+ if (c == '\n')
+ mac.clear_string_flag();
+ while (c == ESCAPE_NEWLINE) {
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
+ mac.append(c);
+ c = get_copy(&n, true /* is defining */);
+ }
+ if (bol && c == '.') {
+ const char *s = term.contents();
+ int d = 0;
+ // see if it matches term
+ int i = 0;
+ if (s[0] != 0) {
+ while ((d = get_copy(&n)) == ' ' || d == '\t')
+ ;
+ if ((unsigned char)s[0] == d) {
+ for (i = 1; s[i] != 0; i++) {
+ d = get_copy(&n);
+ if ((unsigned char)s[i] != d)
+ break;
+ }
+ }
+ }
+ if (s[i] == 0
+ && ((i == 2 && compatible_flag)
+ || (d = get_copy(&n)) == ' '
+ || d == '\n')) { // we found it
+ if (d == '\n')
+ tok.make_newline();
+ else
+ tok.make_space();
+ if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
+ if (!mm) {
+ mm = new macro;
+ request_dictionary.define(nm, mm);
+ }
+ if (comp == COMP_DISABLE || comp == COMP_ENABLE)
+ mac.append(POP_GROFFCOMP_MODE);
+ *mm = mac;
+ }
+ if (term != dot_symbol) {
+ ignoring = 0;
+ interpolate_macro(term);
+ }
+ else
+ skip_line();
+ return;
+ }
+ if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
+ mac.append(c);
+ for (int j = 0; j < i; j++)
+ mac.append(s[j]);
+ }
+ c = d;
+ }
+ if (c == EOF) {
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ if (have_start_location)
+ error_with_file_and_line(start_filename, start_lineno,
+ "end of file while defining macro '%1'",
+ nm.contents());
+ else
+ error("end of file while defining macro '%1'", nm.contents());
+ }
+ else {
+ if (have_start_location)
+ error_with_file_and_line(start_filename, start_lineno,
+ "end of file while ignoring input lines");
+ else
+ error("end of file while ignoring input lines");
+ }
+ tok.next();
+ return;
+ }
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append(c);
+ }
+ bol = (c == '\n');
+ c = get_copy(&n, true /* is defining */);
+ }
+}
+
+void define_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void define_nocomp_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
+}
+
+void define_indirect_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void define_indirect_nocomp_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
+}
+
+void append_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_nocomp_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
+}
+
+void append_indirect_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_indirect_nocomp_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
+}
+
+void ignore()
+{
+ ignoring = 1;
+ do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
+ ignoring = 0;
+}
+
+void remove_macro()
+{
+ for (;;) {
+ symbol s = get_name();
+ if (s.is_null())
+ break;
+ request_dictionary.remove(s);
+ }
+ skip_line();
+}
+
+void rename_macro()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null())
+ request_dictionary.rename(s1, s2);
+ }
+ skip_line();
+}
+
+void alias_macro()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null()) {
+ if (!request_dictionary.alias(s1, s2))
+ warning(WARN_MAC, "macro '%1' not defined", s2.contents());
+ }
+ }
+ skip_line();
+}
+
+void chop_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot chop request");
+ else if (m->empty())
+ error("cannot chop empty macro");
+ else {
+ int have_restore = 0;
+ // we have to check for additional save/restore pairs which could be
+ // there due to empty am1 requests.
+ for (;;) {
+ if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
+ break;
+ have_restore = 1;
+ m->len -= 1;
+ if (m->get(m->len - 1) != PUSH_GROFF_MODE
+ && m->get(m->len - 1) != PUSH_COMP_MODE)
+ break;
+ have_restore = 0;
+ m->len -= 1;
+ if (m->len == 0)
+ break;
+ }
+ if (m->len == 0)
+ error("cannot chop empty macro");
+ else {
+ if (have_restore)
+ m->set(POP_GROFFCOMP_MODE, m->len - 1);
+ else
+ m->len -= 1;
+ }
+ }
+ }
+ skip_line();
+}
+
+enum case_xform_mode { STRING_UPCASE, STRING_DOWNCASE };
+
+// Case-transform each byte of the string argument's contents.
+void do_string_case_transform(case_xform_mode mode)
+{
+ assert((mode == STRING_DOWNCASE) || (mode == STRING_UPCASE));
+ symbol s = get_name(true /* required */);
+ if (s.is_null()) {
+ skip_line();
+ return;
+ }
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m) {
+ error("cannot apply string case transformation to a request ('%1')",
+ s.contents());
+ skip_line();
+ return;
+ }
+ string_iterator iter1(*m);
+ macro *mac = new macro;
+ int len = m->macro::length();
+ for (int l = 0; l < len; l++) {
+ int nc, c = iter1.get(0);
+ if (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ nc = c;
+ else if (c == EOF)
+ break;
+ else
+ if (mode == STRING_DOWNCASE)
+ nc = tolower(c);
+ else
+ nc = toupper(c);
+ mac->append(nc);
+ }
+ request_dictionary.define(s, mac);
+ tok.next();
+}
+
+// Uppercase-transform each byte of the string argument's contents.
+void stringdown_request() {
+ do_string_case_transform(STRING_DOWNCASE);
+}
+
+// Lowercase-transform each byte of the string argument's contents.
+void stringup_request() {
+ do_string_case_transform(STRING_UPCASE);
+}
+
+void substring_request()
+{
+ int start; // 0, 1, ..., n-1 or -1, -2, ...
+ symbol s = get_name(true /* required */);
+ if (!s.is_null() && get_integer(&start)) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot apply 'substring' on a request");
+ else {
+ int end = -1;
+ if (!has_arg() || get_integer(&end)) {
+ int real_length = 0; // 1, 2, ..., n
+ string_iterator iter1(*m);
+ for (int l = 0; l < m->len; l++) {
+ int c = iter1.get(0);
+ if (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ continue;
+ if (c == EOF)
+ break;
+ real_length++;
+ }
+ if (start < 0)
+ start += real_length;
+ if (end < 0)
+ end += real_length;
+ if (start > end) {
+ int tem = start;
+ start = end;
+ end = tem;
+ }
+ if (start >= real_length || end < 0) {
+ warning(WARN_RANGE,
+ "start and end index of substring out of range");
+ m->len = 0;
+ if (m->p) {
+ if (--(m->p->count) <= 0)
+ delete m->p;
+ m->p = 0;
+ }
+ skip_line();
+ return;
+ }
+ if (start < 0) {
+ warning(WARN_RANGE,
+ "start index of substring out of range, set to 0");
+ start = 0;
+ }
+ if (end >= real_length) {
+ warning(WARN_RANGE,
+ "end index of substring out of range, set to string length");
+ end = real_length - 1;
+ }
+ // now extract the substring
+ string_iterator iter(*m);
+ int i;
+ for (i = 0; i < start; i++) {
+ int c = iter.get(0);
+ while (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ c = iter.get(0);
+ if (c == EOF)
+ break;
+ }
+ macro mac;
+ for (; i <= end; i++) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ while (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ c = iter.get(0);
+ if (c == EOF)
+ break;
+ if (c == 0)
+ mac.append(nd);
+ else
+ mac.append((unsigned char)c);
+ }
+ *m = mac;
+ }
+ }
+ }
+ skip_line();
+}
+
+void length_request()
+{
+ symbol ret;
+ ret = get_name(true /* required */);
+ if (ret.is_null()) {
+ skip_line();
+ return;
+ }
+ int c;
+ node *n;
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad string definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ int len = 0;
+ while (c != '\n' && c != EOF) {
+ ++len;
+ c = get_copy(&n);
+ }
+ reg *r = (reg*)register_dictionary.lookup(ret);
+ if (r)
+ r->set_value(len);
+ else
+ set_number_reg(ret, len);
+ tok.next();
+}
+
+void asciify_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot asciify request");
+ else {
+ macro am;
+ string_iterator iter(*m);
+ for (;;) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ if (c == EOF)
+ break;
+ if (c != 0)
+ am.append(c);
+ else
+ nd->asciify(&am);
+ }
+ *m = am;
+ }
+ }
+ skip_line();
+}
+
+void unformat_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot unformat request");
+ else {
+ macro am;
+ string_iterator iter(*m);
+ for (;;) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ if (c == EOF)
+ break;
+ if (c != 0)
+ am.append(c);
+ else {
+ if (nd->set_unformat_flag())
+ am.append(nd);
+ }
+ }
+ *m = am;
+ }
+ }
+ skip_line();
+}
+
+static void interpolate_environment_variable(symbol nm)
+{
+ const char *s = getenv(nm.contents());
+ if (s && *s)
+ input_stack::push(make_temp_iterator(s));
+}
+
+void interpolate_number_reg(symbol nm, int inc)
+{
+ reg *r = lookup_number_reg(nm);
+ if (inc < 0)
+ r->decrement();
+ else if (inc > 0)
+ r->increment();
+ input_stack::push(make_temp_iterator(r->get_string()));
+}
+
+static void interpolate_number_format(symbol nm)
+{
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r)
+ input_stack::push(make_temp_iterator(r->get_format()));
+}
+
+static int get_delim_number(units *n, unsigned char si, int prev_value)
+{
+ token start;
+ start.next();
+ if (start.usable_as_delimiter(true /* report error */)) {
+ tok.next();
+ if (get_number(n, si, prev_value)) {
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int get_delim_number(units *n, unsigned char si)
+{
+ token start;
+ start.next();
+ if (start.usable_as_delimiter(true /* report error */)) {
+ tok.next();
+ if (get_number(n, si)) {
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int get_line_arg(units *n, unsigned char si, charinfo **cp)
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return 0;
+ tok.next();
+ if (get_number(n, si)) {
+ if (tok.is_dummy() || tok.is_transparent_dummy())
+ tok.next();
+ if (!(start == tok && input_stack::get_level() == start_level)) {
+ *cp = tok.get_char(true /* required */);
+ tok.next();
+ }
+ if (!(start == tok && input_stack::get_level() == start_level))
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ return 0;
+}
+
+static bool read_size(int *x)
+{
+ tok.next();
+ int c = tok.ch();
+ int inc = 0;
+ if (c == '-') {
+ inc = -1;
+ tok.next();
+ c = tok.ch();
+ }
+ else if (c == '+') {
+ inc = 1;
+ tok.next();
+ c = tok.ch();
+ }
+ int val = 0; // pacify compiler
+ bool contains_invalid_digit = false;
+ if (c == '(') {
+ tok.next();
+ c = tok.ch();
+ if (!inc) {
+ // allow an increment either before or after the left parenthesis
+ if (c == '-') {
+ inc = -1;
+ tok.next();
+ c = tok.ch();
+ }
+ else if (c == '+') {
+ inc = 1;
+ tok.next();
+ c = tok.ch();
+ }
+ }
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = c - '0';
+ tok.next();
+ c = tok.ch();
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = val*10 + (c - '0');
+ val *= sizescale;
+ }
+ }
+ }
+ else if (csdigit(c)) {
+ val = c - '0';
+ if (compatible_flag && !inc && c != '0' && c < '4') {
+ // Support legacy \sNN syntax.
+ tok.next();
+ c = tok.ch();
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = val*10 + (c - '0');
+ error("ambiguous type size in escape sequence; rewrite to use"
+ " '%1s(%2' or similar", static_cast<char>(escape_char),
+ val);
+ }
+ }
+ val *= sizescale;
+ }
+ else if (!tok.usable_as_delimiter(true /* report error */))
+ return false;
+ else {
+ token start(tok);
+ tok.next();
+ c = tok.ch();
+ if (!inc && (c == '-' || c == '+')) {
+ inc = c == '+' ? 1 : -1;
+ tok.next();
+ }
+ if (!get_number(&val, 'z'))
+ return false;
+ if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
+ if (start.ch() == '[')
+ error("missing ']' in type size escape sequence");
+ else
+ error("missing closing delimiter in type size escape sequence");
+ return false;
+ }
+ }
+ if (contains_invalid_digit) {
+ if (c)
+ error("expected valid digit in type size escape sequence, got %1",
+ input_char_description(c));
+ else
+ error("invalid digit in type size escape sequence");
+ return false;
+ }
+ else {
+ switch (inc) {
+ case 0:
+ if (val == 0) {
+ // special case -- point size 0 means "revert to previous size"
+ *x = 0;
+ return true;
+ }
+ *x = val;
+ break;
+ case 1:
+ *x = curenv->get_requested_point_size() + val;
+ break;
+ case -1:
+ *x = curenv->get_requested_point_size() - val;
+ break;
+ default:
+ assert(0);
+ }
+ if (*x <= 0) {
+ warning(WARN_RANGE,
+ "type size escape sequence results in non-positive size"
+ " %1u; setting it to 1u", *x);
+ *x = 1;
+ }
+ return true;
+ }
+}
+
+static symbol get_delim_name()
+{
+ token start;
+ start.next();
+ if (start.is_eof()) {
+ error("end of input at start of delimited name");
+ return NULL_SYMBOL;
+ }
+ if (start.is_newline()) {
+ error("can't delimit name with a newline");
+ return NULL_SYMBOL;
+ }
+ int start_level = input_stack::get_level();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ for (;;) {
+ if (i + 1 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ tok.next();
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if ((buf[i] = tok.ch()) == 0) {
+ error("missing delimiter (got %1)", tok.description());
+ if (buf != abuf)
+ delete[] buf;
+ return NULL_SYMBOL;
+ }
+ i++;
+ }
+ buf[i] = '\0';
+ if (buf == abuf) {
+ if (i == 0) {
+ error("empty delimited name");
+ return NULL_SYMBOL;
+ }
+ else
+ return symbol(buf);
+ }
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+// Implement \R
+
+static void do_register()
+{
+ token start;
+ start.next();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return;
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ return;
+ while (tok.is_space())
+ tok.next();
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ int prev_value;
+ if (!r || !r->get_value(&prev_value))
+ prev_value = 0;
+ int val;
+ if (!get_number(&val, 'u', prev_value))
+ return;
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ if (r)
+ r->set_value(val);
+ else
+ set_number_reg(nm, val);
+}
+
+// this implements the \w escape sequence
+
+static void do_width()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " width computation escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ tok.process();
+ }
+ env.wrap_up_tab();
+ units x = env.get_input_line_position().to_units();
+ input_stack::push(make_temp_iterator(i_to_a(x)));
+ env.width_registers();
+ curenv = oldenv;
+ have_input = 0;
+}
+
+charinfo *page_character;
+
+void set_page_character()
+{
+ page_character = get_optional_char();
+ skip_line();
+}
+
+static const symbol percent_symbol("%");
+
+void read_title_parts(node **part, hunits *part_width)
+{
+ tok.skip();
+ if (tok.is_newline() || tok.is_eof())
+ return;
+ token start(tok);
+ int start_level = input_stack::get_level();
+ tok.next();
+ for (int i = 0; i < 3; i++) {
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level)) {
+ tok.next();
+ break;
+ }
+ if (page_character != 0 && tok.get_char() == page_character)
+ interpolate_number_reg(percent_symbol, 0);
+ else
+ tok.process();
+ tok.next();
+ }
+ curenv->wrap_up_tab();
+ part_width[i] = curenv->get_input_line_position();
+ part[i] = curenv->extract_output_line();
+ }
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+}
+
+class non_interpreted_node : public node {
+ macro mac;
+public:
+ non_interpreted_node(const macro &);
+ int interpret(macro *);
+ node *copy();
+ int ends_sentence();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
+{
+}
+
+int non_interpreted_node::ends_sentence()
+{
+ return 2;
+}
+
+int non_interpreted_node::same(node *nd)
+{
+ return mac == ((non_interpreted_node *)nd)->mac;
+}
+
+const char *non_interpreted_node::type()
+{
+ return "non_interpreted_node";
+}
+
+int non_interpreted_node::force_tprint()
+{
+ return 0;
+}
+
+int non_interpreted_node::is_tag()
+{
+ return 0;
+}
+
+node *non_interpreted_node::copy()
+{
+ return new non_interpreted_node(mac);
+}
+
+int non_interpreted_node::interpret(macro *m)
+{
+ string_iterator si(mac);
+ node *n = 0; // pacify compiler
+ for (;;) {
+ int c = si.get(&n);
+ if (c == EOF)
+ break;
+ if (c == 0)
+ m->append(n);
+ else
+ m->append(c);
+ }
+ return 1;
+}
+
+static node *do_non_interpreted()
+{
+ node *n;
+ int c;
+ macro mac;
+ while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append(c);
+ if (c == EOF || c == '\n') {
+ error("unterminated transparent embedding escape sequence");
+ return 0;
+ }
+ return new non_interpreted_node(mac);
+}
+
+static void encode_char(macro *mac, char c)
+{
+ if (c == '\0') {
+ if (tok.is_stretchable_space()
+ || tok.is_unstretchable_space())
+ mac->append(' ');
+ else if (tok.is_special()) {
+ const char *sc;
+ if (font::use_charnames_in_special) {
+ charinfo *ci = tok.get_char(true /* required */);
+ sc = ci->get_symbol()->contents();
+ }
+ else
+ sc = tok.get_char()->get_symbol()->contents();
+ if (strcmp("-", sc) == 0)
+ mac->append('-');
+ else if (strcmp("aq", sc) == 0)
+ mac->append('\'');
+ else if (strcmp("dq", sc) == 0)
+ mac->append('"');
+ else if (strcmp("ga", sc) == 0)
+ mac->append('`');
+ else if (strcmp("ha", sc) == 0)
+ mac->append('^');
+ else if (strcmp("rs", sc) == 0)
+ mac->append('\\');
+ else if (strcmp("ti", sc) == 0)
+ mac->append('~');
+ else {
+ if (font::use_charnames_in_special) {
+ if (sc[0] != (char)0) {
+ mac->append('\\');
+ mac->append('[');
+ int i = 0;
+ while (sc[i] != (char)0) {
+ mac->append(sc[i]);
+ i++;
+ }
+ mac->append(']');
+ }
+ else
+ error("special character '%1' cannot be used within"
+ " device control escape sequence", sc);
+ }
+ }
+ }
+ else if (!(tok.is_hyphen_indicator()
+ || tok.is_dummy()
+ || tok.is_transparent_dummy()
+ || tok.is_zero_width_break()))
+ error("%1 is invalid within device control escape sequence",
+ tok.description());
+ }
+ else {
+ if ((font::use_charnames_in_special) && (c == '\\')) {
+ /*
+ * add escape escape sequence
+ */
+ mac->append(c);
+ }
+ mac->append(c);
+ }
+}
+
+static node *do_special()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ macro mac;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in device control"
+ " escape sequence (got %1)", tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ unsigned char c;
+ if (tok.is_space())
+ c = ' ';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (tok.is_leader())
+ c = '\001';
+ else if (tok.is_backspace())
+ c = '\b';
+ else
+ c = tok.ch();
+ encode_char(&mac, c);
+ }
+ return new special_node(mac);
+}
+
+void device_request()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ macro mac;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ mac.append(c);
+ curenv->add_node(new special_node(mac));
+ }
+ tok.next();
+}
+
+void device_macro_request()
+{
+ symbol s = get_name(true /* required */);
+ if (!(s.is_null() || s.is_empty())) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (m)
+ curenv->add_node(new special_node(*m));
+ else
+ error("can't transparently throughput a request");
+ }
+ skip_line();
+}
+
+void output_request()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ topdiv->transparent_output(c);
+ topdiv->transparent_output('\n');
+ }
+ tok.next();
+}
+
+extern int image_no; // from node.cpp
+
+static node *do_suppress(symbol nm)
+{
+ if (nm.is_null() || nm.is_empty()) {
+ error("output suppression escape sequence requires an argument");
+ return 0;
+ }
+ const char *s = nm.contents();
+ switch (*s) {
+ case '0':
+ if (begin_level == 0)
+ // suppress generation of glyphs
+ return new suppress_node(0, 0);
+ break;
+ case '1':
+ if (begin_level == 0)
+ // enable generation of glyphs
+ return new suppress_node(1, 0);
+ break;
+ case '2':
+ if (begin_level == 0)
+ return new suppress_node(1, 1);
+ break;
+ case '3':
+ have_input = 1;
+ begin_level++;
+ break;
+ case '4':
+ have_input = 1;
+ begin_level--;
+ break;
+ case '5':
+ {
+ s++; // move over '5'
+ char position = *s;
+ if (*s == (char)0) {
+ error("missing position and filename in output suppression"
+ " escape sequence");
+ return 0;
+ }
+ if (!(position == 'l'
+ || position == 'r'
+ || position == 'c'
+ || position == 'i')) {
+ error("expected position 'l', 'r', 'c', or 'i' in output"
+ " suppression escape sequence, got '%1'", position);
+ return 0;
+ }
+ s++; // onto image name
+ if (s == (char *)0) {
+ error("missing image name in output suppression escape"
+ " sequence");
+ return 0;
+ }
+ image_no++;
+ if (begin_level == 0)
+ return new suppress_node(symbol(s), position, image_no);
+ else
+ have_input = 1;
+ }
+ break;
+ default:
+ error("invalid argument '%1' to output suppression escape sequence",
+ *s);
+ }
+ return 0;
+}
+
+void special_node::tprint(troff_output_file *out)
+{
+ tprint_start(out);
+ string_iterator iter(mac);
+ for (;;) {
+ int c = iter.get(0);
+ if (c == EOF)
+ break;
+ for (const char *s = ::asciify(c); *s; s++)
+ tprint_char(out, *s);
+ }
+ tprint_end(out);
+}
+
+int get_file_line(const char **filename, int *lineno)
+{
+ return input_stack::get_location(0, filename, lineno);
+}
+
+void line_file()
+{
+ int n;
+ if (get_integer(&n)) {
+ const char *filename = 0;
+ if (has_arg()) {
+ symbol s = get_long_name();
+ filename = s.contents();
+ }
+ (void)input_stack::set_location(filename, n-1);
+ }
+ skip_line();
+}
+
+static int nroff_mode = 0;
+
+static void nroff_request()
+{
+ nroff_mode = 1;
+ skip_line();
+}
+
+static void troff_request()
+{
+ nroff_mode = 0;
+ skip_line();
+}
+
+static void skip_alternative()
+{
+ int level = 0;
+ // ensure that ".if 0\{" works as expected
+ if (tok.is_left_brace())
+ level++;
+ int c;
+ for (;;) {
+ c = input_stack::get(0);
+ if (c == EOF)
+ break;
+ if (c == ESCAPE_LEFT_BRACE)
+ ++level;
+ else if (c == ESCAPE_RIGHT_BRACE)
+ --level;
+ else if (c == escape_char && escape_char > 0)
+ switch(input_stack::get(0)) {
+ case '{':
+ ++level;
+ break;
+ case '}':
+ --level;
+ break;
+ case '"':
+ while ((c = input_stack::get(0)) != '\n' && c != EOF)
+ ;
+ }
+ /*
+ Note that the level can properly be < 0, e.g.
+
+ .if 1 \{\
+ .if 0 \{\
+ .\}\}
+
+ So don't give an error message in this case.
+ */
+ if (level <= 0 && c == '\n')
+ break;
+ }
+ tok.next();
+}
+
+static void begin_alternative()
+{
+ while (tok.is_space() || tok.is_left_brace())
+ tok.next();
+}
+
+void nop_request()
+{
+ while (tok.is_space())
+ tok.next();
+}
+
+static int_stack if_else_stack;
+
+int do_if_request()
+{
+ int invert = 0;
+ while (tok.is_space())
+ tok.next();
+ while (tok.ch() == '!') {
+ tok.next();
+ invert = !invert;
+ }
+ int result;
+ unsigned char c = tok.ch();
+ if (compatible_flag)
+ switch (c) {
+ case 'F':
+ case 'S':
+ case 'c':
+ case 'd':
+ case 'm':
+ case 'r':
+ warning(WARN_SYNTAX,
+ "conditional operator '%1' used in compatibility mode",
+ c);
+ break;
+ default:
+ break;
+ }
+ if (c == 't') {
+ tok.next();
+ result = !nroff_mode;
+ }
+ else if (c == 'n') {
+ tok.next();
+ result = nroff_mode;
+ }
+ else if (c == 'v') {
+ tok.next();
+ result = 0;
+ }
+ else if (c == 'o') {
+ result = (topdiv->get_page_number() & 1);
+ tok.next();
+ }
+ else if (c == 'e') {
+ result = !(topdiv->get_page_number() & 1);
+ tok.next();
+ }
+ else if (c == 'd' || c == 'r') {
+ tok.next();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = (c == 'd'
+ ? request_dictionary.lookup(nm) != 0
+ : register_dictionary.lookup(nm) != 0);
+ }
+ else if (c == 'm') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = (nm == default_symbol
+ || color_dictionary.lookup(nm) != 0);
+ }
+ else if (c == 'c') {
+ tok.next();
+ tok.skip();
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_alternative();
+ return 0;
+ }
+ result = character_exists(ci, curenv);
+ tok.next();
+ }
+ else if (c == 'F') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = check_font(curenv->get_family()->nm, nm);
+ }
+ else if (c == 'S') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = check_style(nm);
+ }
+ else if (tok.is_space())
+ result = 0;
+ else if (tok.usable_as_delimiter()) {
+ token delim = tok;
+ int delim_level = input_stack::get_level();
+ environment env1(curenv);
+ environment env2(curenv);
+ environment *oldenv = curenv;
+ curenv = &env1;
+ suppress_push = 1;
+ for (int i = 0; i < 2; i++) {
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in output"
+ " comparison operator (got %1)", tok.description());
+ tok.next();
+ curenv = oldenv;
+ return 0;
+ }
+ if (tok == delim
+ && (compatible_flag || input_stack::get_level() == delim_level))
+ break;
+ tok.process();
+ }
+ curenv = &env2;
+ }
+ node *n1 = env1.extract_output_line();
+ node *n2 = env2.extract_output_line();
+ result = same_node_list(n1, n2);
+ delete_node_list(n1);
+ delete_node_list(n2);
+ curenv = oldenv;
+ have_input = 0;
+ suppress_push = 0;
+ tok.next();
+ }
+ else {
+ units n;
+ if (!get_number(&n, 'u')) {
+ skip_alternative();
+ return 0;
+ }
+ else
+ result = n > 0;
+ }
+ if (invert)
+ result = !result;
+ if (result)
+ begin_alternative();
+ else
+ skip_alternative();
+ return result;
+}
+
+void if_else_request()
+{
+ if_else_stack.push(do_if_request());
+}
+
+void if_request()
+{
+ do_if_request();
+}
+
+void else_request()
+{
+ if (if_else_stack.is_empty()) {
+ warning(WARN_EL, "unbalanced 'el' request");
+ skip_alternative();
+ }
+ else {
+ if (if_else_stack.pop())
+ skip_alternative();
+ else
+ begin_alternative();
+ }
+}
+
+static int while_depth = 0;
+static int while_break_flag = 0;
+
+void while_request()
+{
+ macro mac;
+ int escaped = 0;
+ int level = 0;
+ mac.append(new token_node(tok));
+ for (;;) {
+ node *n = 0; // pacify compiler
+ int c = input_stack::get(&n);
+ if (c == EOF)
+ break;
+ if (c == 0) {
+ escaped = 0;
+ mac.append(n);
+ }
+ else if (escaped) {
+ if (c == '{')
+ level += 1;
+ else if (c == '}')
+ level -= 1;
+ escaped = 0;
+ mac.append(c);
+ }
+ else {
+ if (c == ESCAPE_LEFT_BRACE)
+ level += 1;
+ else if (c == ESCAPE_RIGHT_BRACE)
+ level -= 1;
+ else if (c == escape_char)
+ escaped = 1;
+ mac.append(c);
+ if (c == '\n' && level <= 0)
+ break;
+ }
+ }
+ if (level != 0)
+ error("unbalanced brace escape sequences");
+ else {
+ while_depth++;
+ input_stack::add_boundary();
+ for (;;) {
+ input_stack::push(new string_iterator(mac, "while loop"));
+ tok.next();
+ if (!do_if_request()) {
+ while (input_stack::get(0) != EOF)
+ ;
+ break;
+ }
+ process_input_stack();
+ if (while_break_flag || input_stack::is_return_boundary()) {
+ while_break_flag = 0;
+ break;
+ }
+ }
+ input_stack::remove_boundary();
+ while_depth--;
+ }
+ tok.next();
+}
+
+void while_break_request()
+{
+ if (!while_depth) {
+ error("no while loop");
+ skip_line();
+ }
+ else {
+ while_break_flag = 1;
+ while (input_stack::get(0) != EOF)
+ ;
+ tok.next();
+ }
+}
+
+void while_continue_request()
+{
+ if (!while_depth) {
+ error("no while loop");
+ skip_line();
+ }
+ else {
+ while (input_stack::get(0) != EOF)
+ ;
+ tok.next();
+ }
+}
+
+void do_source(bool quietly)
+{
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ skip_line();
+ else {
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(nm.contents());
+ if (fp)
+ input_stack::push(new file_iterator(fp, nm.contents()));
+ else
+ // Suppress diagnostic only if we're operating quietly and it's an
+ // expected problem.
+ if (!(quietly && (ENOENT == errno)))
+ error("can't open '%1': %2", nm.contents(), strerror(errno));
+ tok.next();
+ }
+}
+
+// .so
+
+void source()
+{
+ do_source(false /* not quietly*/ );
+}
+
+// .soquiet: like .so, but silently ignore files that can't be opened
+// due to their nonexistence
+
+void source_quietly()
+{
+ do_source(true /* quietly */ );
+}
+
+// like .so but use popen()
+
+void pipe_source()
+{
+ if (!unsafe_flag) {
+ error("'pso' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+#ifdef POPEN_MISSING
+ error("pipes not available on this system");
+ skip_line();
+#else /* not POPEN_MISSING */
+ if (tok.is_newline() || tok.is_eof())
+ error("missing command");
+ else {
+ int c;
+ while ((c = get_copy(0)) == ' ' || c == '\t')
+ ;
+ int buf_size = 24;
+ char *buf = new char[buf_size];
+ int buf_used = 0;
+ for (; c != '\n' && c != EOF; c = get_copy(0)) {
+ const char *s = asciify(c);
+ int slen = strlen(s);
+ if (buf_used + slen + 1> buf_size) {
+ char *old_buf = buf;
+ int old_buf_size = buf_size;
+ buf_size *= 2;
+ buf = new char[buf_size];
+ memcpy(buf, old_buf, old_buf_size);
+ delete[] old_buf;
+ }
+ strcpy(buf + buf_used, s);
+ buf_used += slen;
+ }
+ buf[buf_used] = '\0';
+ errno = 0;
+ FILE *fp = popen(buf, POPEN_RT);
+ if (fp)
+ input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
+ else
+ error("can't open pipe to process '%1': %2", buf, strerror(errno));
+ delete[] buf;
+ }
+ tok.next();
+#endif /* not POPEN_MISSING */
+ }
+}
+
+// .psbb
+//
+// Extract bounding box limits from PostScript file, and assign
+// them to the following four gtroff registers:--
+//
+static int llx_reg_contents = 0;
+static int lly_reg_contents = 0;
+static int urx_reg_contents = 0;
+static int ury_reg_contents = 0;
+
+// Manifest constants to specify the status of bounding box range
+// acquisition; (note that PSBB_RANGE_IS_BAD is also suitable for
+// assignment as a default ordinate property value).
+//
+#define PSBB_RANGE_IS_BAD 0
+#define PSBB_RANGE_IS_SET 1
+#define PSBB_RANGE_AT_END 2
+
+// Maximum input line length, for DSC conformance, and options to
+// control how it will be enforced; caller should select either of
+// DSC_LINE_MAX_IGNORED, to allow partial line collection spread
+// across multiple calls, or DSC_LINE_MAX_ENFORCE, to truncate
+// excess length lines at the DSC limit.
+//
+// Note that DSC_LINE_MAX_CHECKED is reserved for internal use by
+// ps_locator::get_line(), and should not be specified in any call;
+// also, handling of DSC_LINE_MAX_IGNORED, as a get_line() option,
+// is currently unimplemented.
+//
+#define DSC_LINE_MAX 255
+#define DSC_LINE_MAX_IGNORED -1
+#define DSC_LINE_MAX_ENFORCE 0
+#define DSC_LINE_MAX_CHECKED 1
+
+// Input characters to be considered as white space, when reading
+// PostScript file comments.
+//
+cset white_space("\n\r \t");
+
+// Class psbb_locator
+//
+// This locally declared and implemented class provides the methods
+// to be used for retrieval of bounding box properties from a specified
+// PostScript or PDF file.
+//
+class psbb_locator
+{
+ public:
+ // Only the class constructor is exposed publicly; instantiation of
+ // a class object will retrieve the requisite bounding box properties
+ // from the specified file, and assign them to gtroff registers.
+ //
+ psbb_locator(const char *);
+
+ private:
+ FILE *fp;
+ const char *filename;
+ char buf[2 + DSC_LINE_MAX];
+ int llx, lly, urx, ury;
+
+ // CRLF handling hook, for get_line() function.
+ //
+ int lastc;
+
+ // Private method functions facilitate implementation of the
+ // class constructor; none are used in any other context.
+ //
+ int get_line(int);
+ inline bool get_header_comment(void);
+ inline const char *context_args(const char *);
+ inline const char *context_args(const char *, const char *);
+ inline const char *bounding_box_args(void);
+ int parse_bounding_box(const char *);
+ inline void assign_registers(void);
+ inline int skip_to_trailer(void);
+};
+
+// psbb_locator class constructor.
+//
+psbb_locator::psbb_locator(const char *fname):
+filename(fname), llx(0), lly(0), urx(0), ury(0), lastc(EOF)
+{
+ // PS files might contain non-printable characters, such as ^Z
+ // and CRs not followed by an LF, so open them in binary mode.
+ //
+ fp = include_search_path.open_file_cautious(filename, 0, FOPEN_RB);
+ if (fp) {
+ // After successfully opening the file, acquire the first
+ // line, whence we may determine the file format...
+ //
+ if (get_line(DSC_LINE_MAX_ENFORCE) == 0)
+ //
+ // ...except in the case of an empty file, which we are
+ // unable to process further.
+ //
+ error("'%1' is empty", filename);
+
+# if 0
+ else if (context_args("%PDF-")) {
+ // TODO: PDF files specify a /MediaBox, as the equivalent
+ // of %%BoundingBox; we must implement a handler for this.
+ }
+# endif
+
+ else if (context_args("%!PS-Adobe-")) {
+ //
+ // PostScript files -- strictly, we expect EPS -- should
+ // specify a %%BoundingBox comment; locate it, initially
+ // expecting to find it in the comments header...
+ //
+ const char *context = NULL;
+ while ((context == NULL) && get_header_comment()) {
+ if ((context = bounding_box_args()) != NULL) {
+
+ // When the "%%BoundingBox" comment is found, it may simply
+ // specify the bounding box property values, or it may defer
+ // assignment to a similar trailer comment...
+ //
+ int status = parse_bounding_box(context);
+ if (status == PSBB_RANGE_AT_END) {
+ //
+ // ...in which case we must locate the trailer, and search
+ // for the appropriate specification within it.
+ //
+ if (skip_to_trailer() > 0) {
+ while ((context = bounding_box_args()) == NULL
+ && get_line(DSC_LINE_MAX_ENFORCE) > 0)
+ ;
+ if (context != NULL) {
+ //
+ // When we find a bounding box specification here...
+ //
+ if ((status = parse_bounding_box(context)) == PSBB_RANGE_AT_END)
+ //
+ // ...we must ensure it is not a further attempt to defer
+ // assignment to a trailer, (which we are already parsing).
+ //
+ error("'(atend)' is not allowed in trailer of '%1'",
+ filename);
+ }
+ }
+ else
+ // The trailer could not be found, so there is no context in
+ // which a trailing %%BoundingBox comment might be located.
+ //
+ context = NULL;
+ }
+ if (status == PSBB_RANGE_IS_BAD) {
+ //
+ // This arises when we found a %%BoundingBox comment, but
+ // we were unable to extract a valid set of range values from
+ // it; all we can do is diagnose this.
+ //
+ error("the arguments to the %%%%BoundingBox comment in '%1' are bad",
+ filename);
+ }
+ }
+ }
+ if (context == NULL)
+ //
+ // Conversely, this arises when no value specifying %%BoundingBox
+ // comment has been found, in any appropriate location...
+ //
+ error("%%%%BoundingBox comment not found in '%1'", filename);
+ }
+ else
+ // ...while this indicates that there was no appropriate file format
+ // identifier, on the first line of the input file.
+ //
+ error("'%1' does not conform to the Document Structuring Conventions",
+ filename);
+
+ // Regardless of success or failure of bounding box property acquisition,
+ // we did successfully open an input file, so we must now close it...
+ //
+ fclose(fp);
+ }
+ else
+ // ...but in this case, we did not successfully open any input file.
+ //
+ error("can't open '%1': %2", filename, strerror(errno));
+
+ // Irrespective of whether or not we were able to successfully acquire the
+ // bounding box properties, we ALWAYS update the associated gtroff registers.
+ //
+ assign_registers();
+}
+
+// psbb_locator::parse_bounding_box()
+//
+// Parse the argument to a %%BoundingBox comment, returning:
+// PSBB_RANGE_IS_SET if it contains four numbers,
+// PSBB_RANGE_AT_END if it contains "(atend)", or
+// PSBB_RANGE_IS_BAD otherwise.
+//
+int psbb_locator::parse_bounding_box(const char *context)
+{
+ // The Document Structuring Conventions say that the numbers
+ // should be integers.
+ //
+ int status = PSBB_RANGE_IS_SET;
+ if (sscanf(context, "%d %d %d %d", &llx, &lly, &urx, &ury) != 4) {
+ //
+ // Unfortunately some broken applications get this wrong;
+ // try to parse them as doubles instead...
+ //
+ double x1, x2, x3, x4;
+ if (sscanf(context, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
+ llx = (int)x1;
+ lly = (int)x2;
+ urx = (int)x3;
+ ury = (int)x4;
+ }
+ else {
+ // ...but if we can't parse four numbers, skip over any
+ // initial white space...
+ //
+ while (*context == '\x20' || *context == '\t')
+ context++;
+
+ // ...before checking for "(atend)", and setting the
+ // appropriate exit status accordingly.
+ //
+ status = (context_args("(atend)", context) == NULL)
+ ? llx = lly = urx = ury = PSBB_RANGE_IS_BAD
+ : PSBB_RANGE_AT_END;
+ }
+ }
+ return status;
+}
+
+// ps_locator::get_line()
+//
+// Collect an input record from a PostScript or PDF file.
+//
+// Inputs:
+// buf pointer to caller's input buffer.
+// fp FILE stream pointer, whence input is read.
+// filename name of input file, (for diagnostic use only).
+// dscopt DSC_LINE_MAX_ENFORCE or DSC_LINE_MAX_IGNORED.
+//
+// Returns the number of input characters stored into caller's
+// buffer, or zero at end of input stream.
+//
+// FIXME: Currently, get_line() always scans an entire line of
+// input, but returns only as much as will fit in caller's buffer;
+// the return value is always a positive integer, or zero, with no
+// way of indicating to caller, that there was more data than the
+// buffer could accommodate. A future enhancement could mitigate
+// this, returning a negative value in the event of truncation, or
+// even allowing for piecewise retrieval of excessively long lines
+// in successive reads; (this may be necessary to properly support
+// DSC_LINE_MAX_IGNORED, which is currently unimplemented).
+//
+int psbb_locator::get_line(int dscopt)
+{
+ int c, count = 0;
+ do {
+ // Collect input characters into caller's buffer, until we
+ // encounter a line terminator, or end of file...
+ //
+ while (((c = getc(fp)) != '\n') && (c != '\r') && (c != EOF)) {
+ if ((((lastc = c) < 0x1b) && !white_space(c)) || (c == 0x7f))
+ //
+ // ...rejecting any which may be designated as invalid.
+ //
+ error("invalid input character code %1 in '%2'", int(c), filename);
+
+ // On reading a valid input character, and when there is
+ // room in caller's buffer...
+ //
+ else if (count < DSC_LINE_MAX)
+ //
+ // ...store it.
+ //
+ buf[count++] = c;
+
+ // We have a valid input character, but it will not fit
+ // into caller's buffer; if enforcing DSC conformity...
+ //
+ else if (dscopt == DSC_LINE_MAX_ENFORCE) {
+ //
+ // ...diagnose and truncate.
+ //
+ dscopt = DSC_LINE_MAX_CHECKED;
+ error("PostScript file '%1' is non-conforming "
+ "because length of line exceeds 255", filename);
+ }
+ }
+ // Reading LF may be a special case: when it immediately
+ // follows a CR which terminated the preceding input line,
+ // we deem it to complete a CRLF terminator for the already
+ // collected preceding line; discard it, and restart input
+ // collection for the current line.
+ //
+ } while ((lastc == '\r') && ((lastc = c) == '\n'));
+
+ // For each collected input line, record its actual terminator,
+ // substitute our preferred LF terminator...
+ //
+ if (((lastc = c) != EOF) || (count > 0))
+ buf[count++] = '\n';
+
+ // ...and append the required C-string (NUL) terminator, before
+ // returning the actual count of input characters stored.
+ //
+ buf[count] = '\0';
+ return count;
+}
+
+// psbb_locator::context_args()
+//
+// Inputs:
+// tag literal text to be matched at start of input line
+//
+// Returns a pointer to the trailing substring of the current
+// input line, following an initial substring matching the "tag"
+// argument, or NULL if "tag" is not matched.
+//
+inline const char *psbb_locator::context_args(const char *tag)
+{
+ return context_args(tag, buf);
+}
+
+// psbb_locator::context_args()
+//
+// Overloaded variant of the preceding function, operating on
+// an alternative input buffer, (which may represent a terminal
+// substring of the psbb_locator's primary input line buffer).
+//
+// Inputs:
+// tag literal text to be matched at start of buffer
+// p pointer to text to be checked for "tag" match
+//
+// Returns a pointer to the trailing substring of the specified
+// text buffer, following an initial substring matching the "tag"
+// argument, or NULL if "tag" is not matched.
+//
+inline const char *psbb_locator::context_args(const char *tag, const char *p)
+{
+ size_t len = strlen(tag);
+ return (strncmp(tag, p, len) == 0) ? p + len : NULL;
+}
+
+// psbb_locator::bounding_box_args()
+//
+// Returns a pointer to the arguments string, within the current
+// input line, when this represents a PostScript "%%BoundingBox:"
+// comment, or NULL otherwise.
+//
+inline const char *psbb_locator::bounding_box_args(void)
+{
+ return context_args("%%BoundingBox:");
+}
+
+// psbb_locator::assign_registers()
+//
+// Copies the bounding box properties established within the
+// class object, to the associated gtroff registers.
+//
+inline void psbb_locator::assign_registers(void)
+{
+ llx_reg_contents = llx;
+ lly_reg_contents = lly;
+ urx_reg_contents = urx;
+ ury_reg_contents = ury;
+}
+
+// psbb_locator::get_header_comment()
+//
+// Fetch a line of PostScript input; return true if it complies with
+// the formatting requirements for header comments, and it is not an
+// "%%EndComments" line; otherwise return false.
+//
+inline bool psbb_locator::get_header_comment(void)
+{
+ return
+ // The first necessary requirement, for returning true,
+ // is that the input line is not empty, (i.e. not EOF).
+ //
+ get_line(DSC_LINE_MAX_ENFORCE) != 0
+
+ // In header comments, '%X' ('X' any printable character
+ // except whitespace) is also acceptable.
+ //
+ && (buf[0] == '%') && !white_space(buf[1])
+
+ // Finally, the input line must not say "%%EndComments".
+ //
+ && context_args("%%EndComments") == NULL;
+}
+
+// psbb_locator::skip_to_trailer()
+//
+// Reposition the PostScript input stream, such that the next get_line()
+// will retrieve the first line, if any, following a "%%Trailer" comment;
+// returns a positive integer value if the "%%Trailer" comment is found,
+// or zero if it is not.
+//
+inline int psbb_locator::skip_to_trailer(void)
+{
+ // Begin by considering a chunk of the input file starting 512 bytes
+ // before its end, and search it for a "%%Trailer" comment; if none is
+ // found, incrementally double the chunk size while it remains within
+ // a 32768L byte range, and search again...
+ //
+ for (ssize_t offset = 512L; offset > 0L; offset <<= 1) {
+ int status, failed;
+ if ((offset > 32768L) || ((failed = fseek(fp, -offset, SEEK_END)) != 0))
+ //
+ // ...ultimately resetting the offset to zero, and simply seeking
+ // to the start of the file, to terminate the cycle and do a "last
+ // ditch" search of the entire file, if any backward seek fails, or
+ // if we reach the arbitrary 32768L byte range limit.
+ //
+ failed = fseek(fp, offset = 0L, SEEK_SET);
+
+ // Following each successful seek...
+ //
+ if (!failed) {
+ //
+ // ...perform a search by reading lines from the input stream...
+ //
+ do { status = get_line(DSC_LINE_MAX_ENFORCE);
+ //
+ // ...until we either exhaust the available stream data, or
+ // we have located a "%%Trailer" comment line.
+ //
+ } while ((status != 0) && (context_args("%%Trailer") == NULL));
+ if (status > 0)
+ //
+ // We found the "%%Trailer" comment, so we may immediately
+ // return, with the stream positioned appropriately...
+ //
+ return status;
+ }
+ }
+ // ...otherwise, we report that no "%%Trailer" comment was found.
+ //
+ return 0;
+}
+
+// ps_bbox_request()
+//
+// Handle the .psbb request.
+//
+void ps_bbox_request()
+{
+ // Parse input line, to extract file name.
+ //
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ //
+ // No file name specified: ignore the entire request.
+ //
+ skip_line();
+ else {
+ // File name acquired: swallow the rest of the line.
+ //
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ errno = 0;
+
+ // Update {llx,lly,urx,ury}_reg_contents:
+ // declaring this class instance achieves this, as an
+ // intentional side effect of object construction.
+ //
+ psbb_locator do_ps_file(nm.contents());
+
+ // All done for .psbb; move on, to continue
+ // input stream processing.
+ //
+ tok.next();
+ }
+}
+
+const char *asciify(int c)
+{
+ static char buf[3];
+ buf[0] = escape_char == '\0' ? '\\' : escape_char;
+ buf[1] = buf[2] = '\0';
+ switch (c) {
+ case ESCAPE_QUESTION:
+ buf[1] = '?';
+ break;
+ case ESCAPE_AMPERSAND:
+ buf[1] = '&';
+ break;
+ case ESCAPE_RIGHT_PARENTHESIS:
+ buf[1] = ')';
+ break;
+ case ESCAPE_UNDERSCORE:
+ buf[1] = '_';
+ break;
+ case ESCAPE_BAR:
+ buf[1] = '|';
+ break;
+ case ESCAPE_CIRCUMFLEX:
+ buf[1] = '^';
+ break;
+ case ESCAPE_LEFT_BRACE:
+ buf[1] = '{';
+ break;
+ case ESCAPE_RIGHT_BRACE:
+ buf[1] = '}';
+ break;
+ case ESCAPE_LEFT_QUOTE:
+ buf[1] = '`';
+ break;
+ case ESCAPE_RIGHT_QUOTE:
+ buf[1] = '\'';
+ break;
+ case ESCAPE_HYPHEN:
+ buf[1] = '-';
+ break;
+ case ESCAPE_BANG:
+ buf[1] = '!';
+ break;
+ case ESCAPE_c:
+ buf[1] = 'c';
+ break;
+ case ESCAPE_e:
+ buf[1] = 'e';
+ break;
+ case ESCAPE_E:
+ buf[1] = 'E';
+ break;
+ case ESCAPE_PERCENT:
+ buf[1] = '%';
+ break;
+ case ESCAPE_SPACE:
+ buf[1] = ' ';
+ break;
+ case ESCAPE_TILDE:
+ buf[1] = '~';
+ break;
+ case ESCAPE_COLON:
+ buf[1] = ':';
+ break;
+ case PUSH_GROFF_MODE:
+ case PUSH_COMP_MODE:
+ case POP_GROFFCOMP_MODE:
+ buf[0] = '\0';
+ break;
+ default:
+ if (is_invalid_input_char(c))
+ buf[0] = '\0';
+ else
+ buf[0] = c;
+ break;
+ }
+ return buf;
+}
+
+const char *input_char_description(int c)
+{
+ switch (c) {
+ case '\n':
+ return "a newline character";
+ case '\b':
+ return "a backspace character";
+ case '\001':
+ return "a leader character";
+ case '\t':
+ return "a tab character";
+ case ' ':
+ return "a space character";
+ case '\0':
+ return "a node";
+ }
+ size_t bufsz = sizeof "magic character code " + INT_DIGITS + 1;
+ // repeat expression; no VLAs in ISO C++
+ static char buf[sizeof "magic character code " + INT_DIGITS + 1];
+ (void) memset(buf, 0, bufsz);
+ if (is_invalid_input_char(c)) {
+ const char *s = asciify(c);
+ if (*s) {
+ buf[0] = '\'';
+ strcpy(buf + 1, s);
+ strcat(buf, "'");
+ return buf;
+ }
+ sprintf(buf, "magic character code %d", c);
+ return buf;
+ }
+ if (csprint(c)) {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ return buf;
+ }
+ sprintf(buf, "character code %d", c);
+ return buf;
+}
+
+void tag()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ string s;
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ s = "x X ";
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ s += (char)c;
+ s += '\n';
+ curenv->add_node(new tag_node(s, 0));
+ }
+ tok.next();
+}
+
+void taga()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ string s;
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ s = "x X ";
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ s += (char)c;
+ s += '\n';
+ curenv->add_node(new tag_node(s, 1));
+ }
+ tok.next();
+}
+
+// .tm, .tm1, and .tmc
+
+void do_terminal(int newline, int string_like)
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (string_like && c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), stderr);
+ }
+ if (newline)
+ fputc('\n', stderr);
+ fflush(stderr);
+ tok.next();
+}
+
+void terminal()
+{
+ do_terminal(1, 0);
+}
+
+void terminal1()
+{
+ do_terminal(1, 1);
+}
+
+void terminal_continue()
+{
+ do_terminal(0, 1);
+}
+
+dictionary stream_dictionary(20);
+
+void do_open(int append)
+{
+ symbol stream = get_name(true /* required */);
+ if (!stream.is_null()) {
+ symbol filename = get_long_name(true /* required */);
+ if (!filename.is_null()) {
+ errno = 0;
+ FILE *fp = fopen(filename.contents(), append ? "a" : "w");
+ if (!fp) {
+ error("can't open '%1' for %2: %3",
+ filename.contents(),
+ append ? "appending" : "writing",
+ strerror(errno));
+ fp = (FILE *)stream_dictionary.remove(stream);
+ }
+ else
+ fp = (FILE *)stream_dictionary.lookup(stream, fp);
+ if (fp)
+ fclose(fp);
+ }
+ }
+ skip_line();
+}
+
+void open_request()
+{
+ if (!unsafe_flag) {
+ error("'open' request is not allowed in safer mode");
+ skip_line();
+ }
+ else
+ do_open(0);
+}
+
+void opena_request()
+{
+ if (!unsafe_flag) {
+ error("'opena' request is not allowed in safer mode");
+ skip_line();
+ }
+ else
+ do_open(1);
+}
+
+void close_request()
+{
+ symbol stream = get_name(true /* required */);
+ if (!stream.is_null()) {
+ FILE *fp = (FILE *)stream_dictionary.remove(stream);
+ if (!fp)
+ error("no stream named '%1'", stream.contents());
+ else
+ fclose(fp);
+ }
+ skip_line();
+}
+
+// .write and .writec
+
+void do_write_request(int newline)
+{
+ symbol stream = get_name(true /* required */);
+ if (stream.is_null()) {
+ skip_line();
+ return;
+ }
+ FILE *fp = (FILE *)stream_dictionary.lookup(stream);
+ if (!fp) {
+ error("no stream named '%1'", stream.contents());
+ skip_line();
+ return;
+ }
+ int c;
+ while ((c = get_copy(0)) == ' ')
+ ;
+ if (c == '"')
+ c = get_copy(0);
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), fp);
+ if (newline)
+ fputc('\n', fp);
+ fflush(fp);
+ tok.next();
+}
+
+void write_request()
+{
+ do_write_request(1);
+}
+
+void write_request_continue()
+{
+ do_write_request(0);
+}
+
+void write_macro_request()
+{
+ symbol stream = get_name(true /* required */);
+ if (stream.is_null()) {
+ skip_line();
+ return;
+ }
+ FILE *fp = (FILE *)stream_dictionary.lookup(stream);
+ if (!fp) {
+ error("no stream named '%1'", stream.contents());
+ skip_line();
+ return;
+ }
+ symbol s = get_name(true /* required */);
+ if (s.is_null()) {
+ skip_line();
+ return;
+ }
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot write request");
+ else {
+ string_iterator iter(*m);
+ for (;;) {
+ int c = iter.get(0);
+ if (c == EOF)
+ break;
+ fputs(asciify(c), fp);
+ }
+ fflush(fp);
+ }
+ skip_line();
+}
+
+void warnscale_request()
+{
+ if (has_arg()) {
+ char c = tok.ch();
+ if (c == 'u')
+ warn_scale = 1.0;
+ else if (c == 'i')
+ warn_scale = (double)units_per_inch;
+ else if (c == 'c')
+ warn_scale = (double)units_per_inch / 2.54;
+ else if (c == 'p')
+ warn_scale = (double)units_per_inch / 72.0;
+ else if (c == 'P')
+ warn_scale = (double)units_per_inch / 6.0;
+ else {
+ warning(WARN_SCALE,
+ "scaling unit '%1' invalid; using 'i' instead", c);
+ c = 'i';
+ }
+ warn_scaling_indicator = c;
+ }
+ skip_line();
+}
+
+void spreadwarn_request()
+{
+ hunits n;
+ if (has_arg() && get_hunits(&n, 'm')) {
+ if (n < 0)
+ n = 0;
+ hunits em = curenv->get_size();
+ spread_limit = (double)n.to_units()
+ / (em.is_zero() ? hresolution : em.to_units());
+ }
+ else
+ spread_limit = -spread_limit - 1; // no arg toggles on/off without
+ // changing value; we mirror at
+ // -0.5 to make zero a valid value
+ skip_line();
+}
+
+static void init_charset_table()
+{
+ char buf[16];
+ strcpy(buf, "char");
+ for (int i = 0; i < 256; i++) {
+ strcpy(buf + 4, i_to_a(i));
+ charset_table[i] = get_charinfo(symbol(buf));
+ charset_table[i]->set_ascii_code(i);
+ if (csalpha(i))
+ charset_table[i]->set_hyphenation_code(cmlower(i));
+ }
+ charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['-']->set_flags(charinfo::BREAK_AFTER);
+ charset_table['"']->set_flags(charinfo::TRANSPARENT);
+ charset_table['\'']->set_flags(charinfo::TRANSPARENT);
+ charset_table[')']->set_flags(charinfo::TRANSPARENT);
+ charset_table[']']->set_flags(charinfo::TRANSPARENT);
+ charset_table['*']->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("dd"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("cq"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
+ get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
+ get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
+ page_character = charset_table['%'];
+}
+
+static void init_hpf_code_table()
+{
+ for (int i = 0; i < 256; i++)
+ hpf_code_table[i] = cmlower(i);
+}
+
+static void do_translate(int translate_transparent, int translate_input)
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (tok.is_space()) {
+ // This is a really bizarre troff feature.
+ tok.next();
+ translate_space_to_dummy = tok.is_dummy();
+ if (tok.is_newline() || tok.is_eof())
+ break;
+ error("cannot translate space character; ignoring");
+ tok.next();
+ continue;
+ }
+ charinfo *ci1 = tok.get_char(true /* required */);
+ if (ci1 == 0)
+ break;
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
+ translate_transparent);
+ break;
+ }
+ if (tok.is_space())
+ ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
+ translate_transparent);
+ else if (tok.is_stretchable_space())
+ ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
+ translate_transparent);
+ else if (tok.is_dummy())
+ ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
+ translate_transparent);
+ else if (tok.is_hyphen_indicator())
+ ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
+ translate_transparent);
+ else {
+ charinfo *ci2 = tok.get_char(true /* required */);
+ if (ci2 == 0)
+ break;
+ if (ci1 == ci2)
+ ci1->set_translation(0, translate_transparent, translate_input);
+ else
+ ci1->set_translation(ci2, translate_transparent, translate_input);
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+void translate()
+{
+ do_translate(1, 0);
+}
+
+void translate_no_transparent()
+{
+ do_translate(0, 0);
+}
+
+void translate_input()
+{
+ do_translate(1, 1);
+}
+
+void char_flags()
+{
+ int flags;
+ if (get_integer(&flags))
+ while (has_arg()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ ci->set_flags(flags);
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+void hyphenation_code()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0)
+ break;
+ tok.next();
+ tok.skip();
+ unsigned char c = tok.ch();
+ if (c == 0) {
+ error("hyphenation code must be ordinary character");
+ break;
+ }
+ if (csdigit(c)) {
+ error("hyphenation code cannot be digit");
+ break;
+ }
+ ci->set_hyphenation_code(c);
+ if (ci->get_translation()
+ && ci->get_translation()->get_translation_input())
+ ci->get_translation()->set_hyphenation_code(c);
+ tok.next();
+ tok.skip();
+ }
+ skip_line();
+}
+
+void hyphenation_patterns_file_code()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ int n1, n2;
+ if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
+ if (!has_arg()) {
+ error("missing output hyphenation code");
+ break;
+ }
+ if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
+ hpf_code_table[n1] = n2;
+ tok.skip();
+ }
+ else {
+ error("output hyphenation code must be integer in the range 0..255");
+ break;
+ }
+ }
+ else {
+ error("input hyphenation code must be integer in the range 0..255");
+ break;
+ }
+ }
+ skip_line();
+}
+
+dictionary char_class_dictionary(501);
+
+void define_class()
+{
+ tok.skip();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ charinfo *ci = get_charinfo(nm);
+ charinfo *child1 = 0, *child2 = 0;
+ while (!tok.is_newline() && !tok.is_eof()) {
+ tok.skip();
+ if (child1 != 0 && tok.ch() == '-') {
+ tok.next();
+ child2 = tok.get_char(true /* required */);
+ if (!child2) {
+ warning(WARN_MISSING,
+ "missing end of character range in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ if (child1->is_class() || child2->is_class()) {
+ warning(WARN_SYNTAX,
+ "a nested character class is not allowed in a range"
+ " definition");
+ skip_line();
+ return;
+ }
+ int u1 = child1->get_unicode_code();
+ int u2 = child2->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid start value in character range");
+ skip_line();
+ return;
+ }
+ if (u2 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid end value in character range");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1, u2);
+ child1 = child2 = 0;
+ }
+ else if (child1 != 0) {
+ if (child1->is_class()) {
+ if (ci == child1) {
+ warning(WARN_SYNTAX, "invalid cyclic class nesting");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(child1);
+ }
+ else {
+ int u1 = child1->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid character value in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1);
+ }
+ child1 = 0;
+ }
+ child1 = tok.get_char(true /* required */);
+ tok.next();
+ if (!child1) {
+ if (!tok.is_newline())
+ skip_line();
+ break;
+ }
+ }
+ if (child1 != 0) {
+ if (child1->is_class()) {
+ if (ci == child1) {
+ warning(WARN_SYNTAX, "invalid cyclic class nesting");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(child1);
+ }
+ else {
+ int u1 = child1->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid character value in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1);
+ }
+ child1 = 0;
+ }
+ if (!ci->is_class()) {
+ warning(WARN_SYNTAX,
+ "empty class definition for '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ (void)char_class_dictionary.lookup(nm, ci);
+ skip_line();
+}
+
+charinfo *token::get_char(bool required)
+{
+ if (type == TOKEN_CHAR)
+ return charset_table[c];
+ if (type == TOKEN_SPECIAL)
+ return get_charinfo(nm);
+ if (type == TOKEN_NUMBERED_CHAR)
+ return get_charinfo_by_number(val);
+ if (type == TOKEN_ESCAPE) {
+ if (escape_char != 0)
+ return charset_table[escape_char];
+ else {
+ // XXX: Is this possible? token::add_to_zero_width_node_list()
+ // and token::process() don't add this token type if the escape
+ // character is null. If not, this should be an assert(). Also
+ // see escape_off().
+ error("escaped 'e' used while escape sequences disabled");
+ return 0;
+ }
+ }
+ if (required) {
+ if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
+ warning(WARN_MISSING, "missing ordinary or special character");
+ else
+ error("expected ordinary or special character, got %1",
+ description());
+ }
+ return 0;
+}
+
+charinfo *get_optional_char()
+{
+ while (tok.is_space())
+ tok.next();
+ charinfo *ci = tok.get_char();
+ if (!ci)
+ check_missing_character();
+ else
+ tok.next();
+ return ci;
+}
+
+void check_missing_character()
+{
+ if (!tok.is_newline() && !tok.is_eof() && !tok.is_right_brace()
+ && !tok.is_tab())
+ error("expected ordinary or special character, got %1; treated as"
+ " missing", tok.description());
+}
+
+// this is for \Z
+
+int token::add_to_zero_width_node_list(node **pp)
+{
+ hunits w;
+ int s;
+ node *n = 0;
+ switch (type) {
+ case TOKEN_CHAR:
+ *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
+ break;
+ case TOKEN_DUMMY:
+ n = new dummy_node;
+ break;
+ case TOKEN_ESCAPE:
+ if (escape_char != 0)
+ *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
+ break;
+ case TOKEN_HYPHEN_INDICATOR:
+ *pp = (*pp)->add_discretionary_hyphen();
+ break;
+ case TOKEN_ITALIC_CORRECTION:
+ *pp = (*pp)->add_italic_correction(&w);
+ break;
+ case TOKEN_LEFT_BRACE:
+ break;
+ case TOKEN_MARK_INPUT:
+ set_number_reg(nm, curenv->get_input_line_position().to_units());
+ break;
+ case TOKEN_NODE:
+ case TOKEN_HORIZONTAL_SPACE:
+ n = nd;
+ nd = 0;
+ break;
+ case TOKEN_NUMBERED_CHAR:
+ *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
+ break;
+ case TOKEN_RIGHT_BRACE:
+ break;
+ case TOKEN_SPACE:
+ n = new hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_SPECIAL:
+ *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
+ break;
+ case TOKEN_STRETCHABLE_SPACE:
+ n = new unbreakable_space_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ n = new space_char_hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_TRANSPARENT_DUMMY:
+ n = new transparent_dummy_node;
+ break;
+ case TOKEN_ZERO_WIDTH_BREAK:
+ n = new space_node(H0, curenv->get_fill_color());
+ n->freeze_space();
+ n->is_escape_colon();
+ break;
+ default:
+ return 0;
+ }
+ if (n) {
+ n->next = *pp;
+ *pp = n;
+ }
+ return 1;
+}
+
+void token::process()
+{
+ if (possibly_handle_first_page_transition())
+ return;
+ switch (type) {
+ case TOKEN_BACKSPACE:
+ curenv->add_node(new hmotion_node(-curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_CHAR:
+ curenv->add_char(charset_table[c]);
+ break;
+ case TOKEN_DUMMY:
+ curenv->add_node(new dummy_node);
+ break;
+ case TOKEN_EMPTY:
+ assert(0);
+ break;
+ case TOKEN_EOF:
+ assert(0);
+ break;
+ case TOKEN_ESCAPE:
+ if (escape_char != 0)
+ curenv->add_char(charset_table[escape_char]);
+ break;
+ case TOKEN_BEGIN_TRAP:
+ case TOKEN_END_TRAP:
+ case TOKEN_PAGE_EJECTOR:
+ // these are all handled in process_input_stack()
+ break;
+ case TOKEN_HYPHEN_INDICATOR:
+ curenv->add_hyphen_indicator();
+ break;
+ case TOKEN_INTERRUPT:
+ curenv->interrupt();
+ break;
+ case TOKEN_ITALIC_CORRECTION:
+ curenv->add_italic_correction();
+ break;
+ case TOKEN_LEADER:
+ curenv->handle_tab(1);
+ break;
+ case TOKEN_LEFT_BRACE:
+ break;
+ case TOKEN_MARK_INPUT:
+ set_number_reg(nm, curenv->get_input_line_position().to_units());
+ break;
+ case TOKEN_NEWLINE:
+ curenv->newline();
+ break;
+ case TOKEN_NODE:
+ case TOKEN_HORIZONTAL_SPACE:
+ curenv->add_node(nd);
+ nd = 0;
+ break;
+ case TOKEN_NUMBERED_CHAR:
+ curenv->add_char(get_charinfo_by_number(val));
+ break;
+ case TOKEN_REQUEST:
+ // handled in process_input_stack()
+ break;
+ case TOKEN_RIGHT_BRACE:
+ break;
+ case TOKEN_SPACE:
+ curenv->space();
+ break;
+ case TOKEN_SPECIAL:
+ curenv->add_char(get_charinfo(nm));
+ break;
+ case TOKEN_SPREAD:
+ curenv->spread();
+ break;
+ case TOKEN_STRETCHABLE_SPACE:
+ curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_TAB:
+ curenv->handle_tab(0);
+ break;
+ case TOKEN_TRANSPARENT:
+ break;
+ case TOKEN_TRANSPARENT_DUMMY:
+ curenv->add_node(new transparent_dummy_node);
+ break;
+ case TOKEN_ZERO_WIDTH_BREAK:
+ {
+ node *tmp = new space_node(H0, curenv->get_fill_color());
+ tmp->freeze_space();
+ tmp->is_escape_colon();
+ curenv->add_node(tmp);
+ break;
+ }
+ default:
+ assert(0);
+ }
+}
+
+class nargs_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *nargs_reg::get_string()
+{
+ return i_to_a(input_stack::nargs());
+}
+
+class lineno_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *lineno_reg::get_string()
+{
+ int line;
+ const char *file;
+ if (!input_stack::get_location(0, &file, &line))
+ line = 0;
+ return i_to_a(line);
+}
+
+class writable_lineno_reg : public general_reg {
+public:
+ writable_lineno_reg();
+ void set_value(units);
+ bool get_value(units *);
+};
+
+writable_lineno_reg::writable_lineno_reg()
+{
+}
+
+bool writable_lineno_reg::get_value(units *res)
+{
+ int line;
+ const char *file;
+ if (!input_stack::get_location(0, &file, &line))
+ return false;
+ *res = line;
+ return true;
+}
+
+void writable_lineno_reg::set_value(units n)
+{
+ input_stack::set_location(0, n);
+}
+
+class filename_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *filename_reg::get_string()
+{
+ int line;
+ const char *file;
+ if (input_stack::get_location(0, &file, &line))
+ return file;
+ else
+ return 0;
+}
+
+class break_flag_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *break_flag_reg::get_string()
+{
+ return i_to_a(input_stack::get_break_flag());
+}
+
+class readonly_text_register : public reg {
+ const char *s;
+public:
+ readonly_text_register(const char *);
+ const char *get_string();
+};
+
+readonly_text_register::readonly_text_register(const char *p) : s(p)
+{
+}
+
+const char *readonly_text_register::get_string()
+{
+ return s;
+}
+
+readonly_register::readonly_register(int *q) : p(q)
+{
+}
+
+const char *readonly_register::get_string()
+{
+ return i_to_a(*p);
+}
+
+void abort_request()
+{
+ int c;
+ if (tok.is_eof())
+ c = EOF;
+ else if (tok.is_newline())
+ c = '\n';
+ else {
+ while ((c = get_copy(0)) == ' ')
+ ;
+ }
+ if (!(c == EOF || c == '\n')) {
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), stderr);
+ fputc('\n', stderr);
+ }
+ fflush(stderr);
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+char *read_string()
+{
+ int len = 256;
+ char *s = new char[len];
+ int c;
+ while ((c = get_copy(0)) == ' ')
+ ;
+ int i = 0;
+ while (c != '\n' && c != EOF) {
+ if (!is_invalid_input_char(c)) {
+ if (i + 2 > len) {
+ char *tem = s;
+ s = new char[len*2];
+ memcpy(s, tem, len);
+ len *= 2;
+ delete[] tem;
+ }
+ s[i++] = c;
+ }
+ c = get_copy(0);
+ }
+ s[i] = '\0';
+ tok.next();
+ if (i == 0) {
+ delete[] s;
+ return 0;
+ }
+ return s;
+}
+
+void pipe_output()
+{
+ if (!unsafe_flag) {
+ error("'pi' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+#ifdef POPEN_MISSING
+ error("pipes not available on this system");
+ skip_line();
+#else /* not POPEN_MISSING */
+ if (the_output) {
+ error("can't pipe: output already started");
+ skip_line();
+ }
+ else {
+ char *pc;
+ if ((pc = read_string()) == 0)
+ error("can't pipe to empty command");
+ if (pipe_command) {
+ char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
+ strcpy(s, pipe_command);
+ strcat(s, "|");
+ strcat(s, pc);
+ delete[] pipe_command;
+ delete[] pc;
+ pipe_command = s;
+ }
+ else
+ pipe_command = pc;
+ }
+#endif /* not POPEN_MISSING */
+ }
+}
+
+static int system_status;
+
+void system_request()
+{
+ if (!unsafe_flag) {
+ error("'sy' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+ char *command = read_string();
+ if (!command)
+ error("empty command");
+ else {
+ system_status = system(command);
+ delete[] command;
+ }
+ }
+}
+
+void copy_file()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(COPY_FILE_REQUEST);
+ return;
+ }
+ symbol filename = get_long_name(true /* required */);
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (!filename.is_null())
+ curdiv->copy_file(filename.contents());
+ tok.next();
+}
+
+#ifdef COLUMN
+
+void vjustify()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(VJUSTIFY_REQUEST);
+ return;
+ }
+ symbol type = get_long_name(true /* required */);
+ if (!type.is_null())
+ curdiv->vjustify(type);
+ skip_line();
+}
+
+#endif /* COLUMN */
+
+void transparent_file()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(TRANSPARENT_FILE_REQUEST);
+ return;
+ }
+ symbol filename = get_long_name(true /* required */);
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (!filename.is_null()) {
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(filename.contents());
+ if (!fp)
+ error("can't open '%1': %2", filename.contents(), strerror(errno));
+ else {
+ int bol = 1;
+ for (;;) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ curdiv->transparent_output(c);
+ bol = c == '\n';
+ }
+ }
+ if (!bol)
+ curdiv->transparent_output('\n');
+ fclose(fp);
+ }
+ }
+ tok.next();
+}
+
+class page_range {
+ int first;
+ int last;
+public:
+ page_range *next;
+ page_range(int, int, page_range *);
+ int contains(int n);
+};
+
+page_range::page_range(int i, int j, page_range *p)
+: first(i), last(j), next(p)
+{
+}
+
+int page_range::contains(int n)
+{
+ return n >= first && (last <= 0 || n <= last);
+}
+
+page_range *output_page_list = 0;
+
+int in_output_page_list(int n)
+{
+ if (!output_page_list)
+ return 1;
+ for (page_range *p = output_page_list; p; p = p->next)
+ if (p->contains(n))
+ return 1;
+ return 0;
+}
+
+static void parse_output_page_list(char *p)
+{
+ for (;;) {
+ int i;
+ if (*p == '-')
+ i = 1;
+ else if (csdigit(*p)) {
+ i = 0;
+ do
+ i = i*10 + *p++ - '0';
+ while (csdigit(*p));
+ }
+ else
+ break;
+ int j;
+ if (*p == '-') {
+ p++;
+ j = 0;
+ if (csdigit(*p)) {
+ do
+ j = j*10 + *p++ - '0';
+ while (csdigit(*p));
+ }
+ }
+ else
+ j = i;
+ if (j == 0)
+ last_page_number = -1;
+ else if (last_page_number >= 0 && j > last_page_number)
+ last_page_number = j;
+ output_page_list = new page_range(i, j, output_page_list);
+ if (*p != ',')
+ break;
+ ++p;
+ }
+ if (*p != '\0') {
+ error("bad output page list");
+ output_page_list = 0;
+ }
+}
+
+static FILE *open_mac_file(const char *mac, char **path)
+{
+ // Try `mac`.tmac first, then tmac.`mac`. Expect ENOENT errors.
+ char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
+ strcpy(s1, mac);
+ strcat(s1, MACRO_POSTFIX);
+ FILE *fp = mac_path->open_file(s1, path);
+ if (!fp && ENOENT != errno)
+ error("can't open macro file '%1': %2", s1, strerror(errno));
+ delete[] s1;
+ if (!fp) {
+ char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
+ strcpy(s2, MACRO_PREFIX);
+ strcat(s2, mac);
+ fp = mac_path->open_file(s2, path);
+ if (!fp && ENOENT != errno)
+ error("can't open macro file '%1': %2", s2, strerror(errno));
+ delete[] s2;
+ }
+ return fp;
+}
+
+static void process_macro_file(const char *mac)
+{
+ char *path;
+ FILE *fp = open_mac_file(mac, &path);
+ if (!fp)
+ fatal("unable to open macro file for -m argument '%1'", mac);
+ const char *s = symbol(path).contents();
+ free(path);
+ input_stack::push(new file_iterator(fp, s));
+ tok.next();
+ process_input_stack();
+}
+
+static void process_startup_file(const char *filename)
+{
+ char *path;
+ search_path *orig_mac_path = mac_path;
+ mac_path = &config_macro_path;
+ FILE *fp = mac_path->open_file(filename, &path);
+ if (fp) {
+ input_stack::push(new file_iterator(fp, symbol(path).contents()));
+ free(path);
+ tok.next();
+ process_input_stack();
+ }
+ mac_path = orig_mac_path;
+}
+
+void do_macro_source(bool quietly)
+{
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ skip_line();
+ else {
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ char *path;
+ FILE *fp = mac_path->open_file(nm.contents(), &path);
+ // .mso cannot go through open_mac_file, which handles the -m option
+ // and expects only an identifier like "s" or "an", not a file name.
+ // We need to do it here manually: If we have tmac.FOOBAR, try
+ // FOOBAR.tmac and vice versa.
+ if (!fp) {
+ const char *fn = nm.contents();
+ size_t fnlen = strlen(fn);
+ if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
+ char *s = new char[fnlen + sizeof(MACRO_POSTFIX)];
+ strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
+ strcat(s, MACRO_POSTFIX);
+ fp = mac_path->open_file(s, &path);
+ delete[] s;
+ }
+ if (!fp) {
+ if (strncasecmp(fn + fnlen - sizeof(MACRO_POSTFIX) + 1,
+ MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
+ char *s = new char[fnlen + sizeof(MACRO_PREFIX)];
+ strcpy(s, MACRO_PREFIX);
+ strncat(s, fn, fnlen - sizeof(MACRO_POSTFIX) + 1);
+ fp = mac_path->open_file(s, &path);
+ delete[] s;
+ }
+ }
+ }
+ if (fp) {
+ input_stack::push(new file_iterator(fp, symbol(path).contents()));
+ free(path);
+ }
+ else
+ // Suppress diagnostic only if we're operating quietly and it's an
+ // expected problem.
+ if (!quietly && (ENOENT == errno))
+ warning(WARN_FILE, "can't open macro file '%1': %2",
+ nm.contents(), strerror(errno));
+ tok.next();
+ }
+}
+
+// .mso
+
+void macro_source()
+{
+ do_macro_source(false /* not quietly (if WARN_FILE enabled) */ );
+}
+
+// .msoquiet: like .mso, but silently ignore files that can't be opened
+// due to their nonexistence
+
+void macro_source_quietly()
+{
+ do_macro_source(true /* quietly */ );
+}
+
+static void process_input_file(const char *name)
+{
+ FILE *fp;
+ if (strcmp(name, "-") == 0) {
+ clearerr(stdin);
+ fp = stdin;
+ }
+ else {
+ errno = 0;
+ fp = include_search_path.open_file_cautious(name);
+ if (!fp)
+ fatal("can't open '%1': %2", name, strerror(errno));
+ }
+ input_stack::push(new file_iterator(fp, name));
+ tok.next();
+ process_input_stack();
+}
+
+// make sure the_input is empty before calling this
+
+static int evaluate_expression(const char *expr, units *res)
+{
+ input_stack::push(make_temp_iterator(expr));
+ tok.next();
+ int success = get_number(res, 'u');
+ while (input_stack::get(0) != EOF)
+ ;
+ return success;
+}
+
+static void do_register_assignment(const char *s)
+{
+ const char *p = strchr(s, '=');
+ if (!p) {
+ char buf[2];
+ buf[0] = s[0];
+ buf[1] = 0;
+ units n;
+ if (evaluate_expression(s + 1, &n))
+ set_number_reg(buf, n);
+ }
+ else {
+ char *buf = new char[p - s + 1];
+ memcpy(buf, s, p - s);
+ buf[p - s] = 0;
+ units n;
+ if (evaluate_expression(p + 1, &n))
+ set_number_reg(buf, n);
+ delete[] buf;
+ }
+}
+
+static void set_string(const char *name, const char *value)
+{
+ macro *m = new macro;
+ for (const char *p = value; *p; p++)
+ if (!is_invalid_input_char((unsigned char)*p))
+ m->append(*p);
+ request_dictionary.define(name, m);
+}
+
+static void do_string_assignment(const char *s)
+{
+ const char *p = strchr(s, '=');
+ if (!p) {
+ char buf[2];
+ buf[0] = s[0];
+ buf[1] = 0;
+ set_string(buf, s + 1);
+ }
+ else {
+ char *buf = new char[p - s + 1];
+ memcpy(buf, s, p - s);
+ buf[p - s] = 0;
+ set_string(buf, p + 1);
+ delete[] buf;
+ }
+}
+
+struct string_list {
+ const char *s;
+ string_list *next;
+ string_list(const char *ss) : s(ss), next(0) {}
+};
+
+#if 0
+static void prepend_string(const char *s, string_list **p)
+{
+ string_list *l = new string_list(s);
+ l->next = *p;
+ *p = l;
+}
+#endif
+
+static void add_string(const char *s, string_list **p)
+{
+ while (*p)
+ p = &((*p)->next);
+ *p = new string_list(s);
+}
+
+void usage(FILE *stream, const char *prog)
+{
+ fprintf(stream,
+"usage: %s [-abcCEiRUz] [-d ct] [-d string=text] [-f font-family]"
+" [-F font-directory] [-I inclusion-directory] [-m macro-package]"
+" [-M macro-directory] [-n page-number] [-o page-list]"
+" [-r cnumeric-expression] [-r register=numeric-expression]"
+" [-T output-device] [-w warning-category] [-W warning-category]"
+" [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ prog, prog, prog);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ string_list *macros = 0;
+ string_list *register_assignments = 0;
+ string_list *string_assignments = 0;
+ int iflag = 0;
+ int tflag = 0;
+ int fflag = 0;
+ int nflag = 0;
+ int no_rc = 0; // don't process troffrc and troffrc-end
+ int next_page_number = 0; // pacify compiler
+ opterr = 0;
+ hresolution = vresolution = 1;
+ // restore $PATH if called from groff
+ char* groff_path = getenv("GROFF_PATH__");
+ if (groff_path) {
+ string e = "PATH";
+ e += '=';
+ if (*groff_path)
+ e += groff_path;
+ e += '\0';
+ if (putenv(strsave(e.contents())))
+ fatal("putenv failed");
+ }
+ setlocale(LC_CTYPE, "");
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { 0, 0, 0, 0 }
+ };
+#if defined(DEBUGGING)
+#define DEBUG_OPTION "D"
+#else
+#define DEBUG_OPTION ""
+#endif
+ while ((c = getopt_long(argc, argv,
+ "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"
+ DEBUG_OPTION, long_options, 0))
+ != EOF)
+ switch(c) {
+ case 'v':
+ {
+ printf("GNU troff (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case 'I':
+ // Search path for .psbb files
+ // and most other non-system input files.
+ include_search_path.command_line_dir(optarg);
+ break;
+ case 'T':
+ device = optarg;
+ tflag = 1;
+ is_html = (strcmp(device, "html") == 0);
+ break;
+ case 'C':
+ compatible_flag = 1;
+ // fall through
+ case 'c':
+ color_flag = 0;
+ break;
+ case 'M':
+ macro_path.command_line_dir(optarg);
+ safer_macro_path.command_line_dir(optarg);
+ config_macro_path.command_line_dir(optarg);
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'm':
+ add_string(optarg, &macros);
+ break;
+ case 'E':
+ inhibit_errors = 1;
+ break;
+ case 'R':
+ no_rc = 1;
+ break;
+ case 'w':
+ enable_warning(optarg);
+ break;
+ case 'W':
+ disable_warning(optarg);
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'b':
+ backtrace_flag = 1;
+ break;
+ case 'a':
+ ascii_output_flag = 1;
+ break;
+ case 'z':
+ suppress_output_flag = 1;
+ break;
+ case 'n':
+ if (sscanf(optarg, "%d", &next_page_number) == 1)
+ nflag++;
+ else
+ error("bad page number");
+ break;
+ case 'o':
+ parse_output_page_list(optarg);
+ break;
+ case 'd':
+ if (*optarg == '\0')
+ error("'-d' requires non-empty argument");
+ else if (*optarg == '=')
+ error("malformed argument to '-d'; string name cannot be empty"
+ " or contain an equals sign");
+ else
+ add_string(optarg, &string_assignments);
+ break;
+ case 'r':
+ if (*optarg == '\0')
+ error("'-r' requires non-empty argument");
+ else if (*optarg == '=')
+ error("malformed argument to '-r'; register name cannot be"
+ " empty or contain an equals sign");
+ else
+ add_string(optarg, &register_assignments);
+ break;
+ case 'f':
+ default_family = symbol(optarg);
+ fflag = 1;
+ break;
+ case 'q':
+ case 's':
+ case 't':
+ // silently ignore these
+ break;
+ case 'U':
+ unsafe_flag = 1; // unsafe behaviour
+ break;
+#if defined(DEBUGGING)
+ case 'D':
+ debug_state = 1;
+ break;
+#endif
+ case CHAR_MAX + 1: // --help
+ usage(stdout, argv[0]);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr, argv[0]);
+ exit(1);
+ break; // never reached
+ default:
+ assert(0);
+ }
+ if (unsafe_flag)
+ mac_path = &macro_path;
+ set_string(".T", device);
+ init_charset_table();
+ init_hpf_code_table();
+ if (0 /* nullptr */ == font::load_desc())
+ fatal("cannot load 'DESC' description file for device '%1'",
+ device);
+ units_per_inch = font::res;
+ hresolution = font::hor;
+ vresolution = font::vert;
+ sizescale = font::sizescale;
+ device_has_tcommand = font::has_tcommand;
+ warn_scale = (double)units_per_inch;
+ warn_scaling_indicator = 'i';
+ if (!fflag && font::family != 0 && *font::family != '\0')
+ default_family = symbol(font::family);
+ font_size::init_size_table(font::sizes);
+ int i;
+ int j = 1;
+ if (font::style_table)
+ for (i = 0; font::style_table[i]; i++)
+ // Mounting a style can't actually fail due to a bad style name;
+ // that's not determined until the full font name is resolved.
+ // The DESC file also can't provoke a problem by requesting over a
+ // thousand slots in the style table.
+ if (!mount_style(j++, symbol(font::style_table[i])))
+ warning(WARN_FONT, "cannot mount style '%1' directed by 'DESC'"
+ " file for device '%2'", font::style_table[i], device);
+ for (i = 0; font::font_name_table[i]; i++, j++)
+ // In the DESC file, a font name of 0 (zero) means "leave this
+ // position empty".
+ if (strcmp(font::font_name_table[i], "0") != 0)
+ if (!mount_font(j, symbol(font::font_name_table[i])))
+ warning(WARN_FONT, "cannot mount font '%1' directed by 'DESC'"
+ " file for device '%2'", font::font_name_table[i],
+ device);
+ curdiv = topdiv = new top_level_diversion;
+ if (nflag)
+ topdiv->set_next_page_number(next_page_number);
+ init_input_requests();
+ init_env_requests();
+ init_div_requests();
+#ifdef COLUMN
+ init_column_requests();
+#endif /* COLUMN */
+ init_node_requests();
+ register_dictionary.define(".T", new readonly_text_register(tflag ? "1" : "0"));
+ init_registers();
+ init_reg_requests();
+ init_hyphen_requests();
+ init_environments();
+ while (string_assignments) {
+ do_string_assignment(string_assignments->s);
+ string_list *tem = string_assignments;
+ string_assignments = string_assignments->next;
+ delete tem;
+ }
+ while (register_assignments) {
+ do_register_assignment(register_assignments->s);
+ string_list *tem = register_assignments;
+ register_assignments = register_assignments->next;
+ delete tem;
+ }
+ if (!no_rc)
+ process_startup_file(INITIAL_STARTUP_FILE);
+ while (macros) {
+ process_macro_file(macros->s);
+ string_list *tem = macros;
+ macros = macros->next;
+ delete tem;
+ }
+ if (!no_rc)
+ process_startup_file(FINAL_STARTUP_FILE);
+ for (i = optind; i < argc; i++)
+ process_input_file(argv[i]);
+ if (optind >= argc || iflag)
+ process_input_file("-");
+ exit_troff();
+ return 0; // not reached
+}
+
+void warn_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n)) {
+ if (n & ~WARN_TOTAL) {
+ warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
+ n &= WARN_TOTAL;
+ }
+ warning_mask = n;
+ }
+ else
+ warning_mask = WARN_TOTAL;
+ skip_line();
+}
+
+static void init_registers()
+{
+#ifdef LONG_FOR_TIME_T
+ long
+#else /* not LONG_FOR_TIME_T */
+ time_t
+#endif /* not LONG_FOR_TIME_T */
+ t = current_time();
+ struct tm *tt = localtime(&t);
+ set_number_reg("seconds", int(tt->tm_sec));
+ set_number_reg("minutes", int(tt->tm_min));
+ set_number_reg("hours", int(tt->tm_hour));
+ set_number_reg("dw", int(tt->tm_wday + 1));
+ set_number_reg("dy", int(tt->tm_mday));
+ set_number_reg("mo", int(tt->tm_mon + 1));
+ set_number_reg("year", int(1900 + tt->tm_year));
+ set_number_reg("yr", int(tt->tm_year));
+ set_number_reg("$$", getpid());
+ register_dictionary.define(".A",
+ new readonly_text_register(ascii_output_flag
+ ? "1"
+ : "0"));
+}
+
+/*
+ * registers associated with \O
+ */
+
+static int output_reg_minx_contents = -1;
+static int output_reg_miny_contents = -1;
+static int output_reg_maxx_contents = -1;
+static int output_reg_maxy_contents = -1;
+
+void check_output_limits(int x, int y)
+{
+ if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
+ output_reg_minx_contents = x;
+ if (x > output_reg_maxx_contents)
+ output_reg_maxx_contents = x;
+ if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
+ output_reg_miny_contents = y;
+ if (y > output_reg_maxy_contents)
+ output_reg_maxy_contents = y;
+}
+
+void reset_output_registers()
+{
+ output_reg_minx_contents = -1;
+ output_reg_miny_contents = -1;
+ output_reg_maxx_contents = -1;
+ output_reg_maxy_contents = -1;
+}
+
+void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
+{
+ *minx = output_reg_minx_contents;
+ *miny = output_reg_miny_contents;
+ *maxx = output_reg_maxx_contents;
+ *maxy = output_reg_maxy_contents;
+}
+
+void init_input_requests()
+{
+ init_request("ab", abort_request);
+ init_request("als", alias_macro);
+ init_request("am", append_macro);
+ init_request("am1", append_nocomp_macro);
+ init_request("ami", append_indirect_macro);
+ init_request("ami1", append_indirect_nocomp_macro);
+ init_request("as", append_string);
+ init_request("as1", append_nocomp_string);
+ init_request("asciify", asciify_macro);
+ init_request("backtrace", backtrace_request);
+ init_request("blm", blank_line_macro);
+ init_request("break", while_break_request);
+ init_request("cf", copy_file);
+ init_request("cflags", char_flags);
+ init_request("char", define_character);
+ init_request("chop", chop_macro);
+ init_request("class", define_class);
+ init_request("close", close_request);
+ init_request("color", activate_color);
+ init_request("composite", composite_request);
+ init_request("continue", while_continue_request);
+ init_request("cp", compatible);
+ init_request("de", define_macro);
+ init_request("de1", define_nocomp_macro);
+ init_request("defcolor", define_color);
+ init_request("dei", define_indirect_macro);
+ init_request("dei1", define_indirect_nocomp_macro);
+ init_request("device", device_request);
+ init_request("devicem", device_macro_request);
+ init_request("do", do_request);
+ init_request("ds", define_string);
+ init_request("ds1", define_nocomp_string);
+ init_request("ec", set_escape_char);
+ init_request("ecr", restore_escape_char);
+ init_request("ecs", save_escape_char);
+ init_request("el", else_request);
+ init_request("em", eoi_macro);
+ init_request("eo", escape_off);
+ init_request("ex", exit_request);
+ init_request("fchar", define_fallback_character);
+#ifdef WIDOW_CONTROL
+ init_request("fpl", flush_pending_lines);
+#endif /* WIDOW_CONTROL */
+ init_request("hcode", hyphenation_code);
+ init_request("hpfcode", hyphenation_patterns_file_code);
+ init_request("ie", if_else_request);
+ init_request("if", if_request);
+ init_request("ig", ignore);
+ init_request("length", length_request);
+ init_request("lf", line_file);
+ init_request("lsm", leading_spaces_macro);
+ init_request("mso", macro_source);
+ init_request("msoquiet", macro_source_quietly);
+ init_request("nop", nop_request);
+ init_request("nroff", nroff_request);
+ init_request("nx", next_file);
+ init_request("open", open_request);
+ init_request("opena", opena_request);
+ init_request("output", output_request);
+ init_request("pc", set_page_character);
+ init_request("pi", pipe_output);
+ init_request("pm", print_macros);
+ init_request("psbb", ps_bbox_request);
+#ifndef POPEN_MISSING
+ init_request("pso", pipe_source);
+#endif /* not POPEN_MISSING */
+ init_request("rchar", remove_character);
+ init_request("rd", read_request);
+ init_request("return", return_macro_request);
+ init_request("rm", remove_macro);
+ init_request("rn", rename_macro);
+ init_request("schar", define_special_character);
+ init_request("shift", shift);
+ init_request("so", source);
+ init_request("soquiet", source_quietly);
+ init_request("spreadwarn", spreadwarn_request);
+ init_request("stringdown", stringdown_request);
+ init_request("stringup", stringup_request);
+ init_request("substring", substring_request);
+ init_request("sy", system_request);
+ init_request("tag", tag);
+ init_request("taga", taga);
+ init_request("tm", terminal);
+ init_request("tm1", terminal1);
+ init_request("tmc", terminal_continue);
+ init_request("tr", translate);
+ init_request("trf", transparent_file);
+ init_request("trin", translate_input);
+ init_request("trnt", translate_no_transparent);
+ init_request("troff", troff_request);
+ init_request("unformat", unformat_macro);
+#ifdef COLUMN
+ init_request("vj", vjustify);
+#endif /* COLUMN */
+ init_request("warn", warn_request);
+ init_request("warnscale", warnscale_request);
+ init_request("while", while_request);
+ init_request("write", write_request);
+ init_request("writec", write_request_continue);
+ init_request("writem", write_macro_request);
+ register_dictionary.define(".$", new nargs_reg);
+ register_dictionary.define(".br", new break_flag_reg);
+ register_dictionary.define(".C", new readonly_register(&compatible_flag));
+ register_dictionary.define(".cp", new readonly_register(&do_old_compatible_flag));
+ register_dictionary.define(".O", new variable_reg(&begin_level));
+ register_dictionary.define(".c", new lineno_reg);
+ register_dictionary.define(".color", new readonly_register(&color_flag));
+ register_dictionary.define(".F", new filename_reg);
+ register_dictionary.define(".g", new readonly_text_register("1"));
+ register_dictionary.define(".H", new readonly_register(&hresolution));
+ register_dictionary.define(".R", new readonly_text_register("10000"));
+ register_dictionary.define(".U", new readonly_register(&unsafe_flag));
+ register_dictionary.define(".V", new readonly_register(&vresolution));
+ register_dictionary.define(".warn", new readonly_register(&warning_mask));
+ extern const char *major_version;
+ register_dictionary.define(".x", new readonly_text_register(major_version));
+ extern const char *revision;
+ register_dictionary.define(".Y", new readonly_text_register(revision));
+ extern const char *minor_version;
+ register_dictionary.define(".y", new readonly_text_register(minor_version));
+ register_dictionary.define("c.", new writable_lineno_reg);
+ register_dictionary.define("llx", new variable_reg(&llx_reg_contents));
+ register_dictionary.define("lly", new variable_reg(&lly_reg_contents));
+ register_dictionary.define("lsn", new variable_reg(&leading_spaces_number));
+ register_dictionary.define("lss", new variable_reg(&leading_spaces_space));
+ register_dictionary.define("opmaxx",
+ new variable_reg(&output_reg_maxx_contents));
+ register_dictionary.define("opmaxy",
+ new variable_reg(&output_reg_maxy_contents));
+ register_dictionary.define("opminx",
+ new variable_reg(&output_reg_minx_contents));
+ register_dictionary.define("opminy",
+ new variable_reg(&output_reg_miny_contents));
+ register_dictionary.define("slimit",
+ new variable_reg(&input_stack::limit));
+ register_dictionary.define("systat", new variable_reg(&system_status));
+ register_dictionary.define("urx", new variable_reg(&urx_reg_contents));
+ register_dictionary.define("ury", new variable_reg(&ury_reg_contents));
+}
+
+object_dictionary request_dictionary(501);
+
+void init_request(const char *s, REQUEST_FUNCP f)
+{
+ request_dictionary.define(s, new request(f));
+}
+
+static request_or_macro *lookup_request(symbol nm)
+{
+ assert(!nm.is_null());
+ request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
+ if (p == 0) {
+ warning(WARN_MAC, "macro '%1' not defined", nm.contents());
+ p = new macro;
+ request_dictionary.define(nm, p);
+ }
+ return p;
+}
+
+node *charinfo_to_node_list(charinfo *ci, const environment *envp)
+{
+ // Don't interpret character definitions in compatible mode.
+ int old_compatible_flag = compatible_flag;
+ compatible_flag = 0;
+ int old_escape_char = escape_char;
+ escape_char = '\\';
+ macro *mac = ci->set_macro(0);
+ assert(mac != 0);
+ environment *oldenv = curenv;
+ environment env(envp);
+ curenv = &env;
+ curenv->set_composite();
+ token old_tok = tok;
+ input_stack::add_boundary();
+ string_iterator *si =
+ new string_iterator(*mac, "composite character", ci->nm);
+ input_stack::push(si);
+ // we don't use process_input_stack, because we don't want to recognise
+ // requests
+ for (;;) {
+ tok.next();
+ if (tok.is_eof())
+ break;
+ if (tok.is_newline()) {
+ error("composite character mustn't contain newline");
+ while (!tok.is_eof())
+ tok.next();
+ break;
+ }
+ else
+ tok.process();
+ }
+ node *n = curenv->extract_output_line();
+ input_stack::remove_boundary();
+ ci->set_macro(mac);
+ tok = old_tok;
+ curenv = oldenv;
+ compatible_flag = old_compatible_flag;
+ escape_char = old_escape_char;
+ have_input = 0;
+ return n;
+}
+
+static node *read_draw_node()
+{
+ token start;
+ start.next();
+ if (!start.usable_as_delimiter(true /* report error */)){
+ do {
+ tok.next();
+ } while (tok != start && !tok.is_newline() && !tok.is_eof());
+ }
+ else {
+ tok.next();
+ if (tok == start)
+ error("missing argument");
+ else {
+ unsigned char type = tok.ch();
+ if (type == 'F') {
+ read_color_draw_node(start);
+ return 0;
+ }
+ tok.next();
+ int maxpoints = 10;
+ hvpair *point = new hvpair[maxpoints];
+ int npoints = 0;
+ int no_last_v = 0;
+ bool err = false;
+ int i;
+ for (i = 0; tok != start; i++) {
+ if (i == maxpoints) {
+ hvpair *oldpoint = point;
+ point = new hvpair[maxpoints*2];
+ for (int j = 0; j < maxpoints; j++)
+ point[j] = oldpoint[j];
+ maxpoints *= 2;
+ delete[] oldpoint;
+ }
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in drawing"
+ " escape sequence (got %1)", tok.description());
+ err = true;
+ break;
+ }
+ if (!get_hunits(&point[i].h,
+ type == 'f' || type == 't' ? 'u' : 'm')) {
+ err = true;
+ break;
+ }
+ ++npoints;
+ tok.skip();
+ point[i].v = V0;
+ if (tok == start) {
+ no_last_v = 1;
+ break;
+ }
+ if (!get_vunits(&point[i].v, 'v')) {
+ err = false;
+ break;
+ }
+ tok.skip();
+ }
+ while (tok != start && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (!err) {
+ switch (type) {
+ case 'l':
+ if (npoints != 1 || no_last_v) {
+ error("two arguments needed for line");
+ npoints = 1;
+ }
+ break;
+ case 'c':
+ if (npoints != 1 || !no_last_v) {
+ error("one argument needed for circle");
+ npoints = 1;
+ point[0].v = V0;
+ }
+ break;
+ case 'e':
+ if (npoints != 1 || no_last_v) {
+ error("two arguments needed for ellipse");
+ npoints = 1;
+ }
+ break;
+ case 'a':
+ if (npoints != 2 || no_last_v) {
+ error("four arguments needed for arc");
+ npoints = 2;
+ }
+ break;
+ case '~':
+ if (no_last_v)
+ error("even number of arguments needed for spline");
+ break;
+ case 'f':
+ if (npoints != 1 || !no_last_v) {
+ error("one argument needed for gray shade");
+ npoints = 1;
+ point[0].v = V0;
+ }
+ default:
+ // silently pass it through
+ break;
+ }
+ draw_node *dn = new draw_node(type, point, npoints,
+ curenv->get_font_size(),
+ curenv->get_glyph_color(),
+ curenv->get_fill_color());
+ delete[] point;
+ return dn;
+ }
+ else {
+ delete[] point;
+ }
+ }
+ }
+ return 0;
+}
+
+static void read_color_draw_node(token &start)
+{
+ tok.next();
+ if (tok == start) {
+ error("missing color scheme");
+ return;
+ }
+ unsigned char scheme = tok.ch();
+ tok.next();
+ color *col = 0;
+ char end = start.ch();
+ switch (scheme) {
+ case 'c':
+ col = read_cmy(end);
+ break;
+ case 'd':
+ col = &default_color;
+ break;
+ case 'g':
+ col = read_gray(end);
+ break;
+ case 'k':
+ col = read_cmyk(end);
+ break;
+ case 'r':
+ col = read_rgb(end);
+ break;
+ }
+ if (col)
+ curenv->set_fill_color(col);
+ while (tok != start) {
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in color space"
+ " drawing escape sequence (got %1)", tok.description());
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ tok.next();
+ }
+ have_input = 1;
+}
+
+static struct {
+ const char *name;
+ int mask;
+} warning_table[] = {
+ { "char", WARN_CHAR },
+ { "range", WARN_RANGE },
+ { "break", WARN_BREAK },
+ { "delim", WARN_DELIM },
+ { "el", WARN_EL },
+ { "scale", WARN_SCALE },
+ { "number", WARN_NUMBER },
+ { "syntax", WARN_SYNTAX },
+ { "tab", WARN_TAB },
+ { "right-brace", WARN_RIGHT_BRACE },
+ { "missing", WARN_MISSING },
+ { "input", WARN_INPUT },
+ { "escape", WARN_ESCAPE },
+ { "space", WARN_SPACE },
+ { "font", WARN_FONT },
+ { "di", WARN_DI },
+ { "mac", WARN_MAC },
+ { "reg", WARN_REG },
+ { "ig", WARN_IG },
+ { "color", WARN_COLOR },
+ { "file", WARN_FILE },
+ { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
+ { "w", WARN_TOTAL },
+ { "default", DEFAULT_WARNING_MASK },
+};
+
+static int lookup_warning(const char *name)
+{
+ for (unsigned int i = 0;
+ i < sizeof(warning_table)/sizeof(warning_table[0]);
+ i++)
+ if (strcmp(name, warning_table[i].name) == 0)
+ return warning_table[i].mask;
+ return 0;
+}
+
+static void enable_warning(const char *name)
+{
+ int mask = lookup_warning(name);
+ if (mask)
+ warning_mask |= mask;
+ else
+ error("unrecognized warning category '%1'", name);
+}
+
+static void disable_warning(const char *name)
+{
+ int mask = lookup_warning(name);
+ if (mask)
+ warning_mask &= ~mask;
+ else
+ error("unrecognized warning category '%1'", name);
+}
+
+static void copy_mode_error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (ignoring) {
+ static const char prefix[] = "(in ignored input) ";
+ char *s = new char[sizeof(prefix) + strlen(format)];
+ strcpy(s, prefix);
+ strcat(s, format);
+ warning(WARN_IG, s, arg1, arg2, arg3);
+ delete[] s;
+ }
+ else
+ error(format, arg1, arg2, arg3);
+}
+
+enum error_type { DEBUG, WARNING, OUTPUT_WARNING, ERROR, FATAL };
+
+static void do_error(error_type type,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ const char *filename;
+ int lineno;
+ if (inhibit_errors && type < FATAL)
+ return;
+ if (backtrace_flag)
+ input_stack::backtrace();
+ if (!get_file_line(&filename, &lineno))
+ filename = 0;
+ if (filename) {
+ if (program_name)
+ errprint("%1:", program_name);
+ errprint("%1:%2: ", filename, lineno);
+ }
+ else if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ switch (type) {
+ case FATAL:
+ fputs("fatal error: ", stderr);
+ break;
+ case ERROR:
+ fputs("error: ", stderr);
+ break;
+ case WARNING:
+ fputs("warning: ", stderr);
+ break;
+ case DEBUG:
+ fputs("debug: ", stderr);
+ break;
+ case OUTPUT_WARNING:
+ double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
+ fprintf(stderr, "warning [p %d, %.1f%c",
+ topdiv->get_page_number(), fromtop, warn_scaling_indicator);
+ if (topdiv != curdiv) {
+ double fromtop1 = curdiv->get_vertical_position().to_units()
+ / warn_scale;
+ fprintf(stderr, ", div '%s', %.1f%c",
+ curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
+ }
+ fprintf(stderr, "]: ");
+ break;
+ }
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+ if (type == FATAL)
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+void debug(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(DEBUG, format, arg1, arg2, arg3);
+}
+
+int warning(warning_type t,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if ((t & warning_mask) != 0) {
+ do_error(WARNING, format, arg1, arg2, arg3);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int output_warning(warning_type t,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if ((t & warning_mask) != 0) {
+ do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(ERROR, format, arg1, arg2, arg3);
+}
+
+void fatal(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(FATAL, format, arg1, arg2, arg3);
+}
+
+void fatal_with_file_and_line(const char *filename, int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" fatal error: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+void error_with_file_and_line(const char *filename, int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" error: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+void debug_with_file_and_line(const char *filename,
+ int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" debug: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+dictionary charinfo_dictionary(501);
+
+charinfo *get_charinfo(symbol nm)
+{
+ void *p = charinfo_dictionary.lookup(nm);
+ if (p != 0)
+ return (charinfo *)p;
+ charinfo *cp = new charinfo(nm);
+ (void)charinfo_dictionary.lookup(nm, cp);
+ return cp;
+}
+
+int charinfo::next_index = 0;
+
+charinfo::charinfo(symbol s)
+: translation(0), mac(0), special_translation(TRANSLATE_NONE),
+ hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
+ not_found(0), transparent_translate(1), translate_input(0),
+ mode(CHAR_NORMAL), nm(s)
+{
+ index = next_index++;
+ number = -1;
+ get_flags();
+}
+
+int charinfo::get_unicode_code()
+{
+ if (ascii_code != '\0')
+ return ascii_code;
+ return glyph_to_unicode(this);
+}
+
+void charinfo::set_hyphenation_code(unsigned char c)
+{
+ hyphenation_code = c;
+}
+
+void charinfo::set_translation(charinfo *ci, int tt, int ti)
+{
+ translation = ci;
+ if (ci && ti) {
+ if (hyphenation_code != 0)
+ ci->set_hyphenation_code(hyphenation_code);
+ if (asciify_code != 0)
+ ci->set_asciify_code(asciify_code);
+ else if (ascii_code != 0)
+ ci->set_asciify_code(ascii_code);
+ ci->set_translation_input();
+ }
+ special_translation = TRANSLATE_NONE;
+ transparent_translate = tt;
+}
+
+// Recompute flags for all entries in the charinfo dictionary.
+void get_flags()
+{
+ dictionary_iterator iter(charinfo_dictionary);
+ charinfo *ci;
+ symbol s;
+ while (iter.get(&s, (void **)&ci)) {
+ assert(!s.is_null());
+ ci->get_flags();
+ }
+ class_flag = 0;
+}
+
+// Get the union of all flags affecting this charinfo.
+void charinfo::get_flags()
+{
+ dictionary_iterator iter(char_class_dictionary);
+ charinfo *ci;
+ symbol s;
+ while (iter.get(&s, (void **)&ci)) {
+ assert(!s.is_null());
+ if (ci->contains(get_unicode_code())) {
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "charinfo::get_flags %p %s %d\n",
+ (void *)ci, ci->nm.contents(), ci->flags);
+#endif
+ flags |= ci->flags;
+ }
+ }
+}
+
+void charinfo::set_special_translation(int c, int tt)
+{
+ special_translation = c;
+ translation = 0;
+ transparent_translate = tt;
+}
+
+void charinfo::set_ascii_code(unsigned char c)
+{
+ ascii_code = c;
+}
+
+void charinfo::set_asciify_code(unsigned char c)
+{
+ asciify_code = c;
+}
+
+macro *charinfo::set_macro(macro *m)
+{
+ macro *tem = mac;
+ mac = m;
+ return tem;
+}
+
+macro *charinfo::setx_macro(macro *m, char_mode cm)
+{
+ macro *tem = mac;
+ mac = m;
+ mode = cm;
+ return tem;
+}
+
+void charinfo::set_number(int n)
+{
+ assert(n >= 0);
+ number = n;
+}
+
+int charinfo::get_number()
+{
+ assert(number >= 0);
+ return number;
+}
+
+bool charinfo::contains(int c, bool already_called)
+{
+ if (already_called) {
+ warning(WARN_SYNTAX,
+ "cyclic nested class detected while processing character code %1",
+ c);
+ return false;
+ }
+ std::vector<std::pair<int, int> >::const_iterator ranges_iter;
+ ranges_iter = ranges.begin();
+ while (ranges_iter != ranges.end()) {
+ if (c >= ranges_iter->first && c <= ranges_iter->second) {
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "charinfo::contains(%d)\n", c);
+#endif
+ return true;
+ }
+ ++ranges_iter;
+ }
+
+ std::vector<charinfo *>::const_iterator nested_iter;
+ nested_iter = nested_classes.begin();
+ while (nested_iter != nested_classes.end()) {
+ if ((*nested_iter)->contains(c, true))
+ return true;
+ ++nested_iter;
+ }
+
+ return false;
+}
+
+bool charinfo::contains(symbol s, bool already_called)
+{
+ if (already_called) {
+ warning(WARN_SYNTAX,
+ "cyclic nested class detected while processing symbol %1",
+ s.contents());
+ return false;
+ }
+ const char *unicode = glyph_name_to_unicode(s.contents());
+ if (unicode != 0 && strchr(unicode, '_') == 0) {
+ char *ignore;
+ int c = (int)strtol(unicode, &ignore, 16);
+ return contains(c, true);
+ }
+ else
+ return false;
+}
+
+bool charinfo::contains(charinfo *, bool)
+{
+ // TODO
+ return false;
+}
+
+symbol UNNAMED_SYMBOL("---");
+
+// For numbered characters not between 0 and 255, we make a symbol out
+// of the number and store them in this dictionary.
+
+dictionary numbered_charinfo_dictionary(11);
+
+charinfo *get_charinfo_by_number(int n)
+{
+ static charinfo *number_table[256];
+
+ if (n >= 0 && n < 256) {
+ charinfo *ci = number_table[n];
+ if (!ci) {
+ ci = new charinfo(UNNAMED_SYMBOL);
+ ci->set_number(n);
+ number_table[n] = ci;
+ }
+ return ci;
+ }
+ else {
+ symbol ns(i_to_a(n));
+ charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
+ if (!ci) {
+ ci = new charinfo(UNNAMED_SYMBOL);
+ ci->set_number(n);
+ (void)numbered_charinfo_dictionary.lookup(ns, ci);
+ }
+ return ci;
+ }
+}
+
+// This overrides the same function from libgroff; while reading font
+// definition files it puts single-letter glyph names into
+// 'charset_table' and converts glyph names of the form '\x' ('x' a
+// single letter) into 'x'. Consequently, symbol("x") refers to glyph
+// name '\x', not 'x'.
+
+glyph *name_to_glyph(const char *nm)
+{
+ charinfo *ci;
+ if (nm[1] == 0)
+ ci = charset_table[nm[0] & 0xff];
+ else if (nm[0] == '\\' && nm[2] == 0)
+ ci = get_charinfo(symbol(nm + 1));
+ else
+ ci = get_charinfo(symbol(nm));
+ return ci->as_glyph();
+}
+
+glyph *number_to_glyph(int n)
+{
+ return get_charinfo_by_number(n)->as_glyph();
+}
+
+const char *glyph_to_name(glyph *g)
+{
+ charinfo *ci = (charinfo *)g; // Every glyph is actually a charinfo.
+ return (ci->nm != UNNAMED_SYMBOL ? ci->nm.contents() : 0);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/input.h b/src/roff/troff/input.h
new file mode 100644
index 0000000..e78124f
--- /dev/null
+++ b/src/roff/troff/input.h
@@ -0,0 +1,120 @@
+/* Copyright (C) 2001-2022 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* special character codes */
+
+#ifndef IS_EBCDIC_HOST
+
+const int ESCAPE_QUESTION = 015;
+const int BEGIN_TRAP = 016;
+const int END_TRAP = 017;
+const int PAGE_EJECTOR = 020;
+const int ESCAPE_NEWLINE = 021;
+const int ESCAPE_AMPERSAND = 022;
+const int ESCAPE_UNDERSCORE = 023;
+const int ESCAPE_BAR = 024;
+const int ESCAPE_CIRCUMFLEX = 025;
+const int ESCAPE_LEFT_BRACE = 026;
+const int ESCAPE_RIGHT_BRACE = 027;
+const int ESCAPE_LEFT_QUOTE = 030;
+const int ESCAPE_RIGHT_QUOTE = 031;
+const int ESCAPE_HYPHEN = 032;
+const int ESCAPE_BANG = 033;
+const int ESCAPE_c = 034;
+const int ESCAPE_e = 035;
+const int ESCAPE_PERCENT = 036;
+const int ESCAPE_SPACE = 037;
+
+const int INPUT_DELETE = 0177;
+
+const int TITLE_REQUEST = 0200;
+const int COPY_FILE_REQUEST = 0201;
+const int TRANSPARENT_FILE_REQUEST = 0202;
+#ifdef COLUMN
+const int VJUSTIFY_REQUEST = 0203;
+#endif /* COLUMN */
+const int ESCAPE_E = 0204;
+const int LAST_PAGE_EJECTOR = 0205;
+const int ESCAPE_RIGHT_PARENTHESIS = 0206;
+const int ESCAPE_TILDE = 0207;
+const int ESCAPE_COLON = 0210;
+const int PUSH_GROFF_MODE = 0211;
+const int PUSH_COMP_MODE = 0212;
+const int POP_GROFFCOMP_MODE = 0213;
+const int BEGIN_QUOTE = 0214;
+const int END_QUOTE = 0215;
+const int DOUBLE_QUOTE = 0216;
+const int INPUT_NO_BREAK_SPACE = 0240;
+const int INPUT_SOFT_HYPHEN= 0255;
+
+#else /* IS_EBCDIC_HOST */
+
+const int INPUT_DELETE = 007;
+
+const int ESCAPE_QUESTION = 010;
+const int BEGIN_TRAP = 011;
+const int END_TRAP = 013;
+const int PAGE_EJECTOR = 015;
+const int ESCAPE_NEWLINE = 016;
+const int ESCAPE_AMPERSAND = 017;
+const int ESCAPE_UNDERSCORE = 020;
+const int ESCAPE_BAR = 021;
+const int ESCAPE_CIRCUMFLEX = 022;
+const int ESCAPE_LEFT_BRACE = 023;
+const int ESCAPE_RIGHT_BRACE = 024;
+const int ESCAPE_LEFT_QUOTE = 027;
+const int ESCAPE_RIGHT_QUOTE = 030;
+const int ESCAPE_HYPHEN = 031;
+const int ESCAPE_BANG = 032;
+const int ESCAPE_c = 033;
+const int ESCAPE_e = 034;
+const int ESCAPE_PERCENT = 035;
+const int ESCAPE_SPACE = 036;
+
+const int TITLE_REQUEST = 060;
+const int COPY_FILE_REQUEST = 061;
+const int TRANSPARENT_FILE_REQUEST = 062;
+#ifdef COLUMN
+const int VJUSTIFY_REQUEST = 063;
+#endif /* COLUMN */
+const int ESCAPE_E = 064;
+const int LAST_PAGE_EJECTOR = 065;
+const int ESCAPE_RIGHT_PARENTHESIS = 066;
+const int ESCAPE_TILDE = 067;
+const int ESCAPE_COLON = 070;
+const int PUSH_GROFF_MODE = 071;
+const int PUSH_COMP_MODE = 072;
+const int POP_GROFFCOMP_MODE = 073;
+const int BEGIN_QUOTE = 074;
+const int END_QUOTE = 075;
+const int DOUBLE_QUOTE = 076;
+
+const int INPUT_NO_BREAK_SPACE = 0101;
+const int INPUT_SOFT_HYPHEN= 0312;
+
+#endif /* IS_EBCDIC_HOST */
+
+extern void do_glyph_color(symbol);
+extern void do_fill_color(symbol);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/mtsm.cpp b/src/roff/troff/mtsm.cpp
new file mode 100644
index 0000000..058b9b1
--- /dev/null
+++ b/src/roff/troff/mtsm.cpp
@@ -0,0 +1,651 @@
+/* Copyright (C) 2003-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// mtsm: minimum troff state machine
+
+extern int debug_state;
+
+#include "troff.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+
+#if defined(DEBUGGING)
+static int no_of_statems = 0;
+#endif
+
+int_value::int_value()
+: value(0), is_known(0)
+{
+}
+
+int_value::~int_value()
+{
+}
+
+void int_value::diff(FILE *fp, const char *s, int_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs(" ", fp);
+ fputs(i_to_a(compare.value), fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ if (debug_state)
+ fflush(fp);
+ }
+}
+
+void int_value::set(int v)
+{
+ is_known = 1;
+ value = v;
+}
+
+void int_value::unset()
+{
+ is_known = 0;
+}
+
+void int_value::set_if_unknown(int v)
+{
+ if (!is_known)
+ set(v);
+}
+
+int int_value::differs(int_value compare)
+{
+ return compare.is_known
+ && (!is_known || value != compare.value);
+}
+
+bool_value::bool_value()
+{
+}
+
+bool_value::~bool_value()
+{
+}
+
+void bool_value::diff(FILE *fp, const char *s, bool_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ if (debug_state)
+ fflush(fp);
+ }
+}
+
+units_value::units_value()
+{
+}
+
+units_value::~units_value()
+{
+}
+
+void units_value::diff(FILE *fp, const char *s, units_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs(" ", fp);
+ fputs(i_to_a(compare.value), fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ if (debug_state)
+ fflush(fp);
+ }
+}
+
+void units_value::set(hunits v)
+{
+ is_known = 1;
+ value = v.to_units();
+}
+
+int units_value::differs(units_value compare)
+{
+ return compare.is_known
+ && (!is_known || value != compare.value);
+}
+
+string_value::string_value()
+: value(string("")), is_known(0)
+{
+}
+
+string_value::~string_value()
+{
+}
+
+void string_value::diff(FILE *fp, const char *s, string_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs(" ", fp);
+ fputs(compare.value.contents(), fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ }
+}
+
+void string_value::set(string v)
+{
+ is_known = 1;
+ value = v;
+}
+
+void string_value::unset()
+{
+ is_known = 0;
+}
+
+int string_value::differs(string_value compare)
+{
+ return compare.is_known
+ && (!is_known || value != compare.value);
+}
+
+statem::statem()
+{
+#if defined(DEBUGGING)
+ issue_no = no_of_statems;
+ no_of_statems++;
+#endif
+}
+
+statem::statem(statem *copy)
+{
+ int i;
+ for (i = 0; i < LAST_BOOL; i++)
+ bool_values[i] = copy->bool_values[i];
+ for (i = 0; i < LAST_INT; i++)
+ int_values[i] = copy->int_values[i];
+ for (i = 0; i < LAST_UNITS; i++)
+ units_values[i] = copy->units_values[i];
+ for (i = 0; i < LAST_STRING; i++)
+ string_values[i] = copy->string_values[i];
+#if defined(DEBUGGING)
+ issue_no = copy->issue_no;
+#endif
+}
+
+statem::~statem()
+{
+}
+
+void statem::flush(FILE *fp, statem *compare)
+{
+ int_values[MTSM_FI].diff(fp, "devtag:.fi",
+ compare->int_values[MTSM_FI]);
+ int_values[MTSM_RJ].diff(fp, "devtag:.rj",
+ compare->int_values[MTSM_RJ]);
+ int_values[MTSM_SP].diff(fp, "devtag:.sp",
+ compare->int_values[MTSM_SP]);
+ units_values[MTSM_IN].diff(fp, "devtag:.in",
+ compare->units_values[MTSM_IN]);
+ units_values[MTSM_LL].diff(fp, "devtag:.ll",
+ compare->units_values[MTSM_LL]);
+ units_values[MTSM_PO].diff(fp, "devtag:.po",
+ compare->units_values[MTSM_PO]);
+ string_values[MTSM_TA].diff(fp, "devtag:.ta",
+ compare->string_values[MTSM_TA]);
+ units_values[MTSM_TI].diff(fp, "devtag:.ti",
+ compare->units_values[MTSM_TI]);
+ int_values[MTSM_CE].diff(fp, "devtag:.ce",
+ compare->int_values[MTSM_CE]);
+ bool_values[MTSM_EOL].diff(fp, "devtag:.eol",
+ compare->bool_values[MTSM_EOL]);
+ bool_values[MTSM_BR].diff(fp, "devtag:.br",
+ compare->bool_values[MTSM_BR]);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "compared state %d\n", compare->issue_no);
+ fflush(stderr);
+ }
+#endif
+}
+
+void statem::add_tag(int_value_state t, int v)
+{
+ int_values[t].set(v);
+}
+
+void statem::add_tag(units_value_state t, hunits v)
+{
+ units_values[t].set(v);
+}
+
+void statem::add_tag(bool_value_state t)
+{
+ bool_values[t].set(1);
+}
+
+void statem::add_tag(string_value_state t, string v)
+{
+ string_values[t].set(v);
+}
+
+void statem::add_tag_if_unknown(int_value_state t, int v)
+{
+ int_values[t].set_if_unknown(v);
+}
+
+void statem::sub_tag_ce()
+{
+ int_values[MTSM_CE].unset();
+}
+
+/*
+ * add_tag_ta - add the tab settings to the minimum troff state machine
+ */
+
+void statem::add_tag_ta()
+{
+ if (is_html) {
+ string s = string("");
+ hunits d, l;
+ enum tab_type t;
+ do {
+ t = curenv->tabs.distance_to_next_tab(l, &d);
+ l += d;
+ switch (t) {
+ case TAB_LEFT:
+ s += " L ";
+ s += as_string(l.to_units());
+ break;
+ case TAB_CENTER:
+ s += " C ";
+ s += as_string(l.to_units());
+ break;
+ case TAB_RIGHT:
+ s += " R ";
+ s += as_string(l.to_units());
+ break;
+ case TAB_NONE:
+ break;
+ }
+ } while (t != TAB_NONE && l < curenv->get_line_length());
+ s += '\0';
+ string_values[MTSM_TA].set(s);
+ }
+}
+
+void statem::update(statem *older, statem *newer, int_value_state t)
+{
+ if (newer->int_values[t].differs(older->int_values[t])
+ && !newer->int_values[t].is_known)
+ newer->int_values[t].set(older->int_values[t].value);
+}
+
+void statem::update(statem *older, statem *newer, units_value_state t)
+{
+ if (newer->units_values[t].differs(older->units_values[t])
+ && !newer->units_values[t].is_known)
+ newer->units_values[t].set(older->units_values[t].value);
+}
+
+void statem::update(statem *older, statem *newer, bool_value_state t)
+{
+ if (newer->bool_values[t].differs(older->bool_values[t])
+ && !newer->bool_values[t].is_known)
+ newer->bool_values[t].set(older->bool_values[t].value);
+}
+
+void statem::update(statem *older, statem *newer, string_value_state t)
+{
+ if (newer->string_values[t].differs(older->string_values[t])
+ && !newer->string_values[t].is_known)
+ newer->string_values[t].set(older->string_values[t].value);
+}
+
+void statem::merge(statem *newer, statem *older)
+{
+ if (newer == 0 || older == 0)
+ return;
+ update(older, newer, MTSM_EOL);
+ update(older, newer, MTSM_BR);
+ update(older, newer, MTSM_FI);
+ update(older, newer, MTSM_LL);
+ update(older, newer, MTSM_PO);
+ update(older, newer, MTSM_RJ);
+ update(older, newer, MTSM_SP);
+ update(older, newer, MTSM_TA);
+ update(older, newer, MTSM_TI);
+ update(older, newer, MTSM_CE);
+}
+
+stack::stack()
+: next(0), state(0)
+{
+}
+
+stack::stack(statem *s, stack *n)
+: next(n), state(s)
+{
+}
+
+stack::~stack()
+{
+ if (state)
+ delete state;
+ if (next)
+ delete next;
+}
+
+mtsm::mtsm()
+: sp(0)
+{
+ driver = new statem();
+}
+
+mtsm::~mtsm()
+{
+ delete driver;
+ if (sp)
+ delete sp;
+}
+
+/*
+ * push_state - push the current troff state and use 'n' as
+ * the new troff state.
+ */
+
+void mtsm::push_state(statem *n)
+{
+ if (is_html) {
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "--> state %d pushed\n", n->issue_no);
+ fflush(stderr);
+ }
+#endif
+ sp = new stack(n, sp);
+ }
+}
+
+void mtsm::pop_state()
+{
+ if (is_html) {
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "--> state popped\n");
+ fflush(stderr);
+ }
+#endif
+ if (sp == 0)
+ fatal("empty state machine stack");
+ sp->state = 0;
+ stack *t = sp;
+ sp = sp->next;
+ t->next = 0;
+ delete t;
+ }
+}
+
+/*
+ * inherit - scan the stack and collects inherited values.
+ */
+
+void mtsm::inherit(statem *s, int reset_bool)
+{
+ if (sp && sp->state) {
+ if (s->units_values[MTSM_IN].is_known
+ && sp->state->units_values[MTSM_IN].is_known)
+ s->units_values[MTSM_IN].value += sp->state->units_values[MTSM_IN].value;
+ s->update(sp->state, s, MTSM_FI);
+ s->update(sp->state, s, MTSM_LL);
+ s->update(sp->state, s, MTSM_PO);
+ s->update(sp->state, s, MTSM_RJ);
+ s->update(sp->state, s, MTSM_TA);
+ s->update(sp->state, s, MTSM_TI);
+ s->update(sp->state, s, MTSM_CE);
+ if (sp->state->bool_values[MTSM_BR].is_known
+ && sp->state->bool_values[MTSM_BR].value) {
+ if (reset_bool)
+ sp->state->bool_values[MTSM_BR].set(0);
+ s->bool_values[MTSM_BR].set(1);
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "inherited br from pushed state %d\n",
+ sp->state->issue_no);
+#endif
+ }
+ else if (s->bool_values[MTSM_BR].is_known
+ && s->bool_values[MTSM_BR].value)
+ if (! s->int_values[MTSM_CE].is_known)
+ s->bool_values[MTSM_BR].unset();
+ if (sp->state->bool_values[MTSM_EOL].is_known
+ && sp->state->bool_values[MTSM_EOL].value) {
+ if (reset_bool)
+ sp->state->bool_values[MTSM_EOL].set(0);
+ s->bool_values[MTSM_EOL].set(1);
+ }
+ }
+}
+
+void mtsm::flush(FILE *fp, statem *s, string tag_list)
+{
+ if (is_html && s) {
+ inherit(s, 1);
+ driver->flush(fp, s);
+ // Set rj, ce, ti to unknown if they were known and
+ // we have seen an eol or br. This ensures that these values
+ // are emitted during the next glyph (as they step from n..0
+ // at each newline).
+ if ((driver->bool_values[MTSM_EOL].is_known
+ && driver->bool_values[MTSM_EOL].value)
+ || (driver->bool_values[MTSM_BR].is_known
+ && driver->bool_values[MTSM_BR].value)) {
+ if (driver->units_values[MTSM_TI].is_known)
+ driver->units_values[MTSM_TI].is_known = 0;
+ if (driver->int_values[MTSM_RJ].is_known
+ && driver->int_values[MTSM_RJ].value > 0)
+ driver->int_values[MTSM_RJ].is_known = 0;
+ if (driver->int_values[MTSM_CE].is_known
+ && driver->int_values[MTSM_CE].value > 0)
+ driver->int_values[MTSM_CE].is_known = 0;
+ }
+ // reset the boolean values
+ driver->bool_values[MTSM_BR].set(0);
+ driver->bool_values[MTSM_EOL].set(0);
+ // reset space value
+ driver->int_values[MTSM_SP].set(0);
+ // lastly write out any direct tag entries
+ if (tag_list != string("")) {
+ string t = tag_list + '\0';
+ fputs(t.contents(), fp);
+ }
+ }
+}
+
+/*
+ * display_state - dump out a synopsis of the state to stderr.
+ */
+
+void statem::display_state()
+{
+ fprintf(stderr, " <state ");
+ if (bool_values[MTSM_BR].is_known) {
+ if (bool_values[MTSM_BR].value)
+ fprintf(stderr, "[br]");
+ else
+ fprintf(stderr, "[!br]");
+ }
+ if (bool_values[MTSM_EOL].is_known) {
+ if (bool_values[MTSM_EOL].value)
+ fprintf(stderr, "[eol]");
+ else
+ fprintf(stderr, "[!eol]");
+ }
+ if (int_values[MTSM_SP].is_known) {
+ if (int_values[MTSM_SP].value)
+ fprintf(stderr, "[sp %d]", int_values[MTSM_SP].value);
+ else
+ fprintf(stderr, "[!sp]");
+ }
+ fprintf(stderr, ">");
+ fflush(stderr);
+}
+
+int mtsm::has_changed(int_value_state t, statem *s)
+{
+ return driver->int_values[t].differs(s->int_values[t]);
+}
+
+int mtsm::has_changed(units_value_state t, statem *s)
+{
+ return driver->units_values[t].differs(s->units_values[t]);
+}
+
+int mtsm::has_changed(bool_value_state t, statem *s)
+{
+ return driver->bool_values[t].differs(s->bool_values[t]);
+}
+
+int mtsm::has_changed(string_value_state t, statem *s)
+{
+ return driver->string_values[t].differs(s->string_values[t]);
+}
+
+int mtsm::changed(statem *s)
+{
+ if (s == 0 || !is_html)
+ return 0;
+ s = new statem(s);
+ inherit(s, 0);
+ int result = has_changed(MTSM_EOL, s)
+ || has_changed(MTSM_BR, s)
+ || has_changed(MTSM_FI, s)
+ || has_changed(MTSM_IN, s)
+ || has_changed(MTSM_LL, s)
+ || has_changed(MTSM_PO, s)
+ || has_changed(MTSM_RJ, s)
+ || has_changed(MTSM_SP, s)
+ || has_changed(MTSM_TA, s)
+ || has_changed(MTSM_CE, s);
+ delete s;
+ return result;
+}
+
+void mtsm::add_tag(FILE *fp, string s)
+{
+ fflush(fp);
+ s += '\0';
+ fputs(s.contents(), fp);
+}
+
+/*
+ * state_set class
+ */
+
+state_set::state_set()
+: boolset(0), intset(0), unitsset(0), stringset(0)
+{
+}
+
+state_set::~state_set()
+{
+}
+
+void state_set::incl(bool_value_state b)
+{
+ boolset |= 1 << (int)b;
+}
+
+void state_set::incl(int_value_state i)
+{
+ intset |= 1 << (int)i;
+}
+
+void state_set::incl(units_value_state u)
+{
+ unitsset |= 1 << (int)u;
+}
+
+void state_set::incl(string_value_state s)
+{
+ stringset |= 1 << (int)s;
+}
+
+void state_set::excl(bool_value_state b)
+{
+ boolset &= ~(1 << (int)b);
+}
+
+void state_set::excl(int_value_state i)
+{
+ intset &= ~(1 << (int)i);
+}
+
+void state_set::excl(units_value_state u)
+{
+ unitsset &= ~(1 << (int)u);
+}
+
+void state_set::excl(string_value_state s)
+{
+ stringset &= ~(1 << (int)s);
+}
+
+int state_set::is_in(bool_value_state b)
+{
+ return (boolset & (1 << (int)b)) != 0;
+}
+
+int state_set::is_in(int_value_state i)
+{
+ return (intset & (1 << (int)i)) != 0;
+}
+
+int state_set::is_in(units_value_state u)
+{
+ return (unitsset & (1 << (int)u)) != 0;
+}
+
+int state_set::is_in(string_value_state s)
+{
+ return (stringset & (1 << (int)s)) != 0;
+}
+
+void state_set::add(units_value_state, int n)
+{
+ unitsset += n;
+}
+
+units state_set::val(units_value_state)
+{
+ return unitsset;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/mtsm.h b/src/roff/troff/mtsm.h
new file mode 100644
index 0000000..cfca73d
--- /dev/null
+++ b/src/roff/troff/mtsm.h
@@ -0,0 +1,165 @@
+// -*- C++ -*-
+/* Copyright (C) 2003-2020 Free Software Foundation, Inc.
+ *
+ * mtsm.h
+ *
+ * written by Gaius Mulley (gaius@glam.ac.uk)
+ *
+ * provides a minimal troff state machine which is necessary to
+ * emit meta tags for the post-grohtml device driver.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct int_value {
+ int value;
+ int is_known;
+ int_value();
+ ~int_value();
+ void diff(FILE *, const char *, int_value);
+ int differs(int_value);
+ void set(int);
+ void unset();
+ void set_if_unknown(int);
+};
+
+struct bool_value : public int_value {
+ bool_value();
+ ~bool_value();
+ void diff(FILE *, const char *, bool_value);
+};
+
+struct units_value : public int_value {
+ units_value();
+ ~units_value();
+ void diff(FILE *, const char *, units_value);
+ int differs(units_value);
+ void set(hunits);
+};
+
+struct string_value {
+ string value;
+ int is_known;
+ string_value();
+ ~string_value();
+ void diff(FILE *, const char *, string_value);
+ int differs(string_value);
+ void set(string);
+ void unset();
+};
+
+enum bool_value_state {
+ MTSM_EOL,
+ MTSM_BR,
+ LAST_BOOL
+};
+enum int_value_state {
+ MTSM_FI,
+ MTSM_RJ,
+ MTSM_CE,
+ MTSM_SP,
+ LAST_INT
+};
+enum units_value_state {
+ MTSM_IN,
+ MTSM_LL,
+ MTSM_PO,
+ MTSM_TI,
+ LAST_UNITS
+};
+enum string_value_state {
+ MTSM_TA,
+ LAST_STRING
+};
+
+struct statem {
+#if defined(DEBUGGING)
+ int issue_no;
+#endif
+ bool_value bool_values[LAST_BOOL];
+ int_value int_values[LAST_INT];
+ units_value units_values[LAST_UNITS];
+ string_value string_values[LAST_STRING];
+ statem();
+ statem(statem *);
+ ~statem();
+ void flush(FILE *, statem *);
+ int changed(statem *);
+ void merge(statem *, statem *);
+ void add_tag(int_value_state, int);
+ void add_tag(bool_value_state);
+ void add_tag(units_value_state, hunits);
+ void add_tag(string_value_state, string);
+ void sub_tag_ce();
+ void add_tag_if_unknown(int_value_state, int);
+ void add_tag_ta();
+ void display_state();
+ void update(statem *, statem *, int_value_state);
+ void update(statem *, statem *, bool_value_state);
+ void update(statem *, statem *, units_value_state);
+ void update(statem *, statem *, string_value_state);
+};
+
+struct stack {
+ stack *next;
+ statem *state;
+ stack();
+ stack(statem *, stack *);
+ ~stack();
+};
+
+class mtsm {
+ statem *driver;
+ stack *sp;
+ int has_changed(int_value_state, statem *);
+ int has_changed(bool_value_state, statem *);
+ int has_changed(units_value_state, statem *);
+ int has_changed(string_value_state, statem *);
+ void inherit(statem *, int);
+public:
+ mtsm();
+ ~mtsm();
+ void push_state(statem *);
+ void pop_state();
+ void flush(FILE *, statem *, string);
+ int changed(statem *);
+ void add_tag(FILE *, string);
+};
+
+class state_set {
+ int boolset;
+ int intset;
+ int unitsset;
+ int stringset;
+public:
+ state_set();
+ ~state_set();
+ void incl(bool_value_state);
+ void incl(int_value_state);
+ void incl(units_value_state);
+ void incl(string_value_state);
+ void excl(bool_value_state);
+ void excl(int_value_state);
+ void excl(units_value_state);
+ void excl(string_value_state);
+ int is_in(bool_value_state);
+ int is_in(int_value_state);
+ int is_in(units_value_state);
+ int is_in(string_value_state);
+ void add(units_value_state, int);
+ units val(units_value_state);
+};
diff --git a/src/roff/troff/node.cpp b/src/roff/troff/node.cpp
new file mode 100644
index 0000000..d17198d
--- /dev/null
+++ b/src/roff/troff/node.cpp
@@ -0,0 +1,6656 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+extern int debug_state;
+
+#include "troff.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "font.h"
+#include "charinfo.h"
+#include "input.h"
+#include "geometry.h"
+
+#include "nonposix.h"
+
+#ifdef _POSIX_VERSION
+
+#include <sys/wait.h>
+
+#else /* not _POSIX_VERSION */
+
+/* traditional Unix */
+
+#define WIFEXITED(s) (((s) & 0377) == 0)
+#define WEXITSTATUS(s) (((s) >> 8) & 0377)
+#define WTERMSIG(s) ((s) & 0177)
+#define WIFSTOPPED(s) (((s) & 0377) == 0177)
+#define WSTOPSIG(s) (((s) >> 8) & 0377)
+#define WIFSIGNALED(s) (((s) & 0377) != 0 && (((s) & 0377) != 0177))
+
+#endif /* not _POSIX_VERSION */
+
+// declarations to avoid friend name injections
+class tfont;
+class tfont_spec;
+tfont *make_tfont(tfont_spec &);
+
+
+/*
+ * how many boundaries of images have been written? Useful for
+ * debugging grohtml
+ */
+
+int image_no = 0;
+static int suppress_start_page = 0;
+
+#define STORE_WIDTH 1
+
+symbol HYPHEN_SYMBOL("hy");
+
+// Character used when a hyphen is inserted at a line break.
+static charinfo *soft_hyphen_char;
+
+enum constant_space_type {
+ CONSTANT_SPACE_NONE,
+ CONSTANT_SPACE_RELATIVE,
+ CONSTANT_SPACE_ABSOLUTE
+ };
+
+struct special_font_list {
+ int n;
+ special_font_list *next;
+};
+
+special_font_list *global_special_fonts;
+static int global_ligature_mode = 1;
+static int global_kern_mode = 1;
+
+class track_kerning_function {
+ int non_zero;
+ units min_size;
+ hunits min_amount;
+ units max_size;
+ hunits max_amount;
+public:
+ track_kerning_function();
+ track_kerning_function(units, hunits, units, hunits);
+ int operator==(const track_kerning_function &);
+ int operator!=(const track_kerning_function &);
+ hunits compute(int point_size);
+};
+
+struct font_lookup_info {
+ int position;
+ int requested_position;
+ char *requested_name;
+ font_lookup_info();
+};
+
+font_lookup_info::font_lookup_info() : position(-1),
+ requested_position(-1), requested_name(0)
+{
+}
+
+// embolden fontno when this is the current font
+
+struct conditional_bold {
+ conditional_bold *next;
+ int fontno;
+ hunits offset;
+ conditional_bold(int, hunits, conditional_bold * = 0);
+};
+
+class font_info {
+ tfont *last_tfont;
+ int number;
+ font_size last_size;
+ int last_height;
+ int last_slant;
+ symbol internal_name;
+ symbol external_name;
+ font *fm;
+ char is_bold;
+ hunits bold_offset;
+ track_kerning_function track_kern;
+ constant_space_type is_constant_spaced;
+ units constant_space;
+ int last_ligature_mode;
+ int last_kern_mode;
+ conditional_bold *cond_bold_list;
+ void flush();
+public:
+ special_font_list *sf;
+ font_info(symbol, int, symbol, font *);
+ int contains(charinfo *);
+ void set_bold(hunits);
+ void unbold();
+ void set_conditional_bold(int, hunits);
+ void conditional_unbold(int);
+ void set_track_kern(track_kerning_function &);
+ void set_constant_space(constant_space_type, units = 0);
+ int is_named(symbol);
+ symbol get_name();
+ tfont *get_tfont(font_size, int, int, int);
+ hunits get_space_width(font_size, int);
+ hunits get_narrow_space_width(font_size);
+ hunits get_half_narrow_space_width(font_size);
+ int get_bold(hunits *);
+ int is_special();
+ int is_style();
+ void set_zoom(int);
+ int get_zoom();
+ friend symbol get_font_name(int, environment *);
+ friend symbol get_style_name(int);
+};
+
+class tfont_spec {
+protected:
+ symbol name;
+ int input_position;
+ font *fm;
+ font_size size;
+ char is_bold;
+ char is_constant_spaced;
+ int ligature_mode;
+ int kern_mode;
+ hunits bold_offset;
+ hunits track_kern; // add this to the width
+ hunits constant_space_width;
+ int height;
+ int slant;
+public:
+ tfont_spec(symbol, int, font *, font_size, int, int);
+ tfont_spec(const tfont_spec &spec) { *this = spec; }
+ tfont_spec plain();
+ int operator==(const tfont_spec &);
+ friend tfont *font_info::get_tfont(font_size fs, int, int, int);
+};
+
+class tfont : public tfont_spec {
+ static tfont *tfont_list;
+ tfont *next;
+ tfont *plain_version;
+public:
+ tfont(tfont_spec &);
+ int contains(charinfo *);
+ hunits get_width(charinfo *c);
+ int get_bold(hunits *);
+ int get_constant_space(hunits *);
+ hunits get_track_kern();
+ tfont *get_plain();
+ font_size get_size();
+ int get_zoom();
+ symbol get_name();
+ charinfo *get_lig(charinfo *c1, charinfo *c2);
+ int get_kern(charinfo *c1, charinfo *c2, hunits *res);
+ int get_input_position();
+ int get_character_type(charinfo *);
+ int get_height();
+ int get_slant();
+ vunits get_char_height(charinfo *);
+ vunits get_char_depth(charinfo *);
+ hunits get_char_skew(charinfo *);
+ hunits get_italic_correction(charinfo *);
+ hunits get_left_italic_correction(charinfo *);
+ hunits get_subscript_correction(charinfo *);
+ friend tfont *make_tfont(tfont_spec &);
+};
+
+inline int env_definite_font(environment *env)
+{
+ return env->get_family()->make_definite(env->get_font());
+}
+
+/* font_info functions */
+
+static font_info **font_table = 0;
+static int font_table_size = 0;
+
+font_info::font_info(symbol nm, int n, symbol enm, font *f)
+: last_tfont(0), number(n), last_size(0),
+ internal_name(nm), external_name(enm), fm(f),
+ is_bold(0), is_constant_spaced(CONSTANT_SPACE_NONE), last_ligature_mode(1),
+ last_kern_mode(1), cond_bold_list(0), sf(0)
+{
+}
+
+inline int font_info::contains(charinfo *ci)
+{
+ return fm != 0 && fm->contains(ci->as_glyph());
+}
+
+inline int font_info::is_special()
+{
+ return fm != 0 && fm->is_special();
+}
+
+inline int font_info::is_style()
+{
+ return fm == 0;
+}
+
+void font_info::set_zoom(int zoom)
+{
+ assert(fm != 0);
+ fm->set_zoom(zoom);
+}
+
+inline int font_info::get_zoom()
+{
+ if (is_style())
+ return 0;
+ return fm->get_zoom();
+}
+
+tfont *make_tfont(tfont_spec &spec)
+{
+ for (tfont *p = tfont::tfont_list; p; p = p->next)
+ if (*p == spec)
+ return p;
+ return new tfont(spec);
+}
+
+int env_get_zoom(environment *env)
+{
+ int fontno = env->get_family()->make_definite(env->get_font());
+ return font_table[fontno]->get_zoom();
+}
+
+// this is the current_font, fontno is where we found the character,
+// presumably a special font
+
+tfont *font_info::get_tfont(font_size fs, int height, int slant, int fontno)
+{
+ if (last_tfont == 0 || fs != last_size
+ || height != last_height || slant != last_slant
+ || global_ligature_mode != last_ligature_mode
+ || global_kern_mode != last_kern_mode
+ || fontno != number) {
+ font_info *f = font_table[fontno];
+ tfont_spec spec(f->external_name, f->number, f->fm, fs, height, slant);
+ for (conditional_bold *p = cond_bold_list; p; p = p->next)
+ if (p->fontno == fontno) {
+ spec.is_bold = 1;
+ spec.bold_offset = p->offset;
+ break;
+ }
+ if (!spec.is_bold && is_bold) {
+ spec.is_bold = 1;
+ spec.bold_offset = bold_offset;
+ }
+ spec.track_kern = track_kern.compute(fs.to_scaled_points());
+ spec.ligature_mode = global_ligature_mode;
+ spec.kern_mode = global_kern_mode;
+ switch (is_constant_spaced) {
+ case CONSTANT_SPACE_NONE:
+ break;
+ case CONSTANT_SPACE_ABSOLUTE:
+ spec.is_constant_spaced = 1;
+ spec.constant_space_width = constant_space;
+ break;
+ case CONSTANT_SPACE_RELATIVE:
+ spec.is_constant_spaced = 1;
+ spec.constant_space_width
+ = scale(constant_space*fs.to_scaled_points(),
+ units_per_inch,
+ 36*72*sizescale);
+ break;
+ default:
+ assert(0);
+ }
+ if (fontno != number)
+ return make_tfont(spec);
+ // save font for comparison purposes
+ last_tfont = make_tfont(spec);
+ // save font related values not contained in tfont
+ last_size = fs;
+ last_height = height;
+ last_slant = slant;
+ last_ligature_mode = global_ligature_mode;
+ last_kern_mode = global_kern_mode;
+ }
+ return last_tfont;
+}
+
+int font_info::get_bold(hunits *res)
+{
+ if (is_bold) {
+ *res = bold_offset;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void font_info::unbold()
+{
+ if (is_bold) {
+ is_bold = 0;
+ flush();
+ }
+}
+
+void font_info::set_bold(hunits offset)
+{
+ if (!is_bold || offset != bold_offset) {
+ is_bold = 1;
+ bold_offset = offset;
+ flush();
+ }
+}
+
+void font_info::set_conditional_bold(int fontno, hunits offset)
+{
+ for (conditional_bold *p = cond_bold_list; p; p = p->next)
+ if (p->fontno == fontno) {
+ if (offset != p->offset) {
+ p->offset = offset;
+ flush();
+ }
+ return;
+ }
+ cond_bold_list = new conditional_bold(fontno, offset, cond_bold_list);
+}
+
+conditional_bold::conditional_bold(int f, hunits h, conditional_bold *x)
+: next(x), fontno(f), offset(h)
+{
+}
+
+void font_info::conditional_unbold(int fontno)
+{
+ for (conditional_bold **p = &cond_bold_list; *p; p = &(*p)->next)
+ if ((*p)->fontno == fontno) {
+ conditional_bold *tem = *p;
+ *p = (*p)->next;
+ delete tem;
+ flush();
+ return;
+ }
+}
+
+void font_info::set_constant_space(constant_space_type type, units x)
+{
+ if (type != is_constant_spaced
+ || (type != CONSTANT_SPACE_NONE && x != constant_space)) {
+ flush();
+ is_constant_spaced = type;
+ constant_space = x;
+ }
+}
+
+void font_info::set_track_kern(track_kerning_function &tk)
+{
+ if (track_kern != tk) {
+ track_kern = tk;
+ flush();
+ }
+}
+
+void font_info::flush()
+{
+ last_tfont = 0;
+}
+
+int font_info::is_named(symbol s)
+{
+ return internal_name == s;
+}
+
+symbol font_info::get_name()
+{
+ return internal_name;
+}
+
+symbol get_font_name(int fontno, environment *env)
+{
+ symbol f = font_table[fontno]->get_name();
+ if (font_table[fontno]->is_style()) {
+ return concat(env->get_family()->nm, f);
+ }
+ return f;
+}
+
+symbol get_style_name(int fontno)
+{
+ if (font_table[fontno]->is_style())
+ return font_table[fontno]->get_name();
+ else
+ return EMPTY_SYMBOL;
+}
+
+hunits font_info::get_space_width(font_size fs, int space_sz)
+{
+ if (is_constant_spaced == CONSTANT_SPACE_NONE)
+ return scale(hunits(fm->get_space_width(fs.to_scaled_points())),
+ space_sz, 12);
+ else if (is_constant_spaced == CONSTANT_SPACE_ABSOLUTE)
+ return constant_space;
+ else
+ return scale(constant_space*fs.to_scaled_points(),
+ units_per_inch, 36*72*sizescale);
+}
+
+hunits font_info::get_narrow_space_width(font_size fs)
+{
+ charinfo *ci = get_charinfo(symbol("|"));
+ if (fm->contains(ci->as_glyph()))
+ return hunits(fm->get_width(ci->as_glyph(), fs.to_scaled_points()));
+ else
+ return hunits(fs.to_units()/6);
+}
+
+hunits font_info::get_half_narrow_space_width(font_size fs)
+{
+ charinfo *ci = get_charinfo(symbol("^"));
+ if (fm->contains(ci->as_glyph()))
+ return hunits(fm->get_width(ci->as_glyph(), fs.to_scaled_points()));
+ else
+ return hunits(fs.to_units()/12);
+}
+
+/* tfont */
+
+tfont_spec::tfont_spec(symbol nm, int n, font *f,
+ font_size s, int h, int sl)
+: name(nm), input_position(n), fm(f), size(s),
+ is_bold(0), is_constant_spaced(0), ligature_mode(1), kern_mode(1),
+ height(h), slant(sl)
+{
+ if (height == size.to_scaled_points())
+ height = 0;
+}
+
+int tfont_spec::operator==(const tfont_spec &spec)
+{
+ if (fm == spec.fm
+ && size == spec.size
+ && input_position == spec.input_position
+ && name == spec.name
+ && height == spec.height
+ && slant == spec.slant
+ && (is_bold
+ ? (spec.is_bold && bold_offset == spec.bold_offset)
+ : !spec.is_bold)
+ && track_kern == spec.track_kern
+ && (is_constant_spaced
+ ? (spec.is_constant_spaced
+ && constant_space_width == spec.constant_space_width)
+ : !spec.is_constant_spaced)
+ && ligature_mode == spec.ligature_mode
+ && kern_mode == spec.kern_mode)
+ return 1;
+ else
+ return 0;
+}
+
+tfont_spec tfont_spec::plain()
+{
+ return tfont_spec(name, input_position, fm, size, height, slant);
+}
+
+hunits tfont::get_width(charinfo *c)
+{
+ if (is_constant_spaced)
+ return constant_space_width;
+ else if (is_bold)
+ return (hunits(fm->get_width(c->as_glyph(), size.to_scaled_points()))
+ + track_kern + bold_offset);
+ else
+ return (hunits(fm->get_width(c->as_glyph(), size.to_scaled_points()))
+ + track_kern);
+}
+
+vunits tfont::get_char_height(charinfo *c)
+{
+ vunits v = fm->get_height(c->as_glyph(), size.to_scaled_points());
+ if (height != 0 && height != size.to_scaled_points())
+ return scale(v, height, size.to_scaled_points());
+ else
+ return v;
+}
+
+vunits tfont::get_char_depth(charinfo *c)
+{
+ vunits v = fm->get_depth(c->as_glyph(), size.to_scaled_points());
+ if (height != 0 && height != size.to_scaled_points())
+ return scale(v, height, size.to_scaled_points());
+ else
+ return v;
+}
+
+hunits tfont::get_char_skew(charinfo *c)
+{
+ return hunits(fm->get_skew(c->as_glyph(), size.to_scaled_points(), slant));
+}
+
+hunits tfont::get_italic_correction(charinfo *c)
+{
+ return hunits(fm->get_italic_correction(c->as_glyph(), size.to_scaled_points()));
+}
+
+hunits tfont::get_left_italic_correction(charinfo *c)
+{
+ return hunits(fm->get_left_italic_correction(c->as_glyph(),
+ size.to_scaled_points()));
+}
+
+hunits tfont::get_subscript_correction(charinfo *c)
+{
+ return hunits(fm->get_subscript_correction(c->as_glyph(),
+ size.to_scaled_points()));
+}
+
+inline int tfont::get_input_position()
+{
+ return input_position;
+}
+
+inline int tfont::contains(charinfo *ci)
+{
+ return fm->contains(ci->as_glyph());
+}
+
+inline int tfont::get_character_type(charinfo *ci)
+{
+ return fm->get_character_type(ci->as_glyph());
+}
+
+inline int tfont::get_bold(hunits *res)
+{
+ if (is_bold) {
+ *res = bold_offset;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+inline int tfont::get_constant_space(hunits *res)
+{
+ if (is_constant_spaced) {
+ *res = constant_space_width;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+inline hunits tfont::get_track_kern()
+{
+ return track_kern;
+}
+
+inline tfont *tfont::get_plain()
+{
+ return plain_version;
+}
+
+inline font_size tfont::get_size()
+{
+ return size;
+}
+
+inline int tfont::get_zoom()
+{
+ return fm->get_zoom();
+}
+
+inline symbol tfont::get_name()
+{
+ return name;
+}
+
+inline int tfont::get_height()
+{
+ return height;
+}
+
+inline int tfont::get_slant()
+{
+ return slant;
+}
+
+symbol SYMBOL_ff("ff");
+symbol SYMBOL_fi("fi");
+symbol SYMBOL_fl("fl");
+symbol SYMBOL_Fi("Fi");
+symbol SYMBOL_Fl("Fl");
+
+charinfo *tfont::get_lig(charinfo *c1, charinfo *c2)
+{
+ if (ligature_mode == 0)
+ return 0;
+ charinfo *ci = 0;
+ if (c1->get_ascii_code() == 'f') {
+ switch (c2->get_ascii_code()) {
+ case 'f':
+ if (fm->has_ligature(font::LIG_ff))
+ ci = get_charinfo(SYMBOL_ff);
+ break;
+ case 'i':
+ if (fm->has_ligature(font::LIG_fi))
+ ci = get_charinfo(SYMBOL_fi);
+ break;
+ case 'l':
+ if (fm->has_ligature(font::LIG_fl))
+ ci = get_charinfo(SYMBOL_fl);
+ break;
+ }
+ }
+ else if (ligature_mode != 2 && c1->nm == SYMBOL_ff) {
+ switch (c2->get_ascii_code()) {
+ case 'i':
+ if (fm->has_ligature(font::LIG_ffi))
+ ci = get_charinfo(SYMBOL_Fi);
+ break;
+ case 'l':
+ if (fm->has_ligature(font::LIG_ffl))
+ ci = get_charinfo(SYMBOL_Fl);
+ break;
+ }
+ }
+ if (ci != 0 && fm->contains(ci->as_glyph()))
+ return ci;
+ return 0;
+}
+
+inline int tfont::get_kern(charinfo *c1, charinfo *c2, hunits *res)
+{
+ if (kern_mode == 0)
+ return 0;
+ else {
+ int n = fm->get_kern(c1->as_glyph(),
+ c2->as_glyph(),
+ size.to_scaled_points());
+ if (n) {
+ *res = hunits(n);
+ return 1;
+ }
+ else
+ return 0;
+ }
+}
+
+tfont *tfont::tfont_list = 0;
+
+tfont::tfont(tfont_spec &spec) : tfont_spec(spec)
+{
+ next = tfont_list;
+ tfont_list = this;
+ tfont_spec plain_spec = plain();
+ tfont *p;
+ for (p = tfont_list; p; p = p->next)
+ if (*p == plain_spec) {
+ plain_version = p;
+ break;
+ }
+ if (!p)
+ plain_version = new tfont(plain_spec);
+}
+
+/* output_file */
+
+class real_output_file : public output_file {
+#ifndef POPEN_MISSING
+ int piped;
+#endif
+ int printing; // decision via optional page list
+ int output_on; // \O[0] or \O[1] escape sequences
+ virtual void really_transparent_char(unsigned char) = 0;
+ virtual void really_print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits width) = 0;
+ virtual void really_begin_page(int pageno, vunits page_length) = 0;
+ virtual void really_copy_file(hunits x, vunits y, const char *filename);
+ virtual void really_put_filename(const char *, int);
+ virtual void really_on();
+ virtual void really_off();
+public:
+ FILE *fp;
+ real_output_file();
+ ~real_output_file();
+ void flush();
+ void transparent_char(unsigned char);
+ void print_line(hunits x, vunits y, node *n, vunits before, vunits after, hunits width);
+ void begin_page(int pageno, vunits page_length);
+ void put_filename(const char *, int);
+ void on();
+ void off();
+ int is_on();
+ int is_printing();
+ void copy_file(hunits x, vunits y, const char *filename);
+};
+
+class suppress_output_file : public real_output_file {
+public:
+ suppress_output_file();
+ void really_transparent_char(unsigned char);
+ void really_print_line(hunits x, vunits y, node *n, vunits, vunits, hunits width);
+ void really_begin_page(int pageno, vunits page_length);
+};
+
+class ascii_output_file : public real_output_file {
+public:
+ ascii_output_file();
+ void really_transparent_char(unsigned char);
+ void really_print_line(hunits x, vunits y, node *n, vunits, vunits, hunits width);
+ void really_begin_page(int pageno, vunits page_length);
+ void outc(unsigned char c);
+ void outs(const char *s);
+};
+
+void ascii_output_file::outc(unsigned char c)
+{
+ fputc(c, fp);
+}
+
+void ascii_output_file::outs(const char *s)
+{
+ fputc('<', fp);
+ if (s)
+ fputs(s, fp);
+ fputc('>', fp);
+}
+
+struct hvpair;
+
+class troff_output_file : public real_output_file {
+ units hpos;
+ units vpos;
+ units output_vpos;
+ units output_hpos;
+ int force_motion;
+ int current_size;
+ int current_slant;
+ int current_height;
+ tfont *current_tfont;
+ color *current_fill_color;
+ color *current_glyph_color;
+ int current_font_number;
+ symbol *font_position;
+ int nfont_positions;
+ enum { TBUF_SIZE = 256 };
+ char tbuf[TBUF_SIZE];
+ int tbuf_len;
+ int tbuf_kern;
+ int begun_page;
+ int cur_div_level;
+ string tag_list;
+ void do_motion();
+ void put(char c);
+ void put(unsigned char c);
+ void put(int i);
+ void put(unsigned int i);
+ void put(const char *s);
+ void set_font(tfont *tf);
+ void flush_tbuf();
+public:
+ troff_output_file();
+ ~troff_output_file();
+ void trailer(vunits page_length);
+ void put_char(charinfo *, tfont *, color *, color *);
+ void put_char_width(charinfo *, tfont *, color *, color *, hunits, hunits);
+ void right(hunits);
+ void down(vunits);
+ void moveto(hunits, vunits);
+ void start_special(tfont *, color *, color *, int = 0);
+ void start_special();
+ void special_char(unsigned char c);
+ void end_special();
+ void word_marker();
+ void really_transparent_char(unsigned char c);
+ void really_print_line(hunits x, vunits y, node *n, vunits before, vunits after, hunits width);
+ void really_begin_page(int pageno, vunits page_length);
+ void really_copy_file(hunits x, vunits y, const char *filename);
+ void really_put_filename(const char *, int);
+ void really_on();
+ void really_off();
+ void draw(char, hvpair *, int, font_size, color *, color *);
+ void determine_line_limits (char code, hvpair *point, int npoints);
+ void check_charinfo(tfont *tf, charinfo *ci);
+ void glyph_color(color *c);
+ void fill_color(color *c);
+ int get_hpos() { return hpos; }
+ int get_vpos() { return vpos; }
+ void add_to_tag_list(string s);
+ friend void space_char_hmotion_node::tprint(troff_output_file *);
+ friend void unbreakable_space_node::tprint(troff_output_file *);
+};
+
+static void put_string(const char *s, FILE *fp)
+{
+ for (; *s != '\0'; ++s)
+ putc(*s, fp);
+}
+
+inline void troff_output_file::put(char c)
+{
+ putc(c, fp);
+}
+
+inline void troff_output_file::put(unsigned char c)
+{
+ putc(c, fp);
+}
+
+inline void troff_output_file::put(const char *s)
+{
+ put_string(s, fp);
+}
+
+inline void troff_output_file::put(int i)
+{
+ put_string(i_to_a(i), fp);
+}
+
+inline void troff_output_file::put(unsigned int i)
+{
+ put_string(ui_to_a(i), fp);
+}
+
+void troff_output_file::start_special(tfont *tf, color *gcol, color *fcol,
+ int no_init_string)
+{
+ set_font(tf);
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ if (!no_init_string)
+ put("x X ");
+}
+
+void troff_output_file::start_special()
+{
+ flush_tbuf();
+ do_motion();
+ put("x X ");
+}
+
+void troff_output_file::special_char(unsigned char c)
+{
+ put(c);
+ if (c == '\n')
+ put('+');
+}
+
+void troff_output_file::end_special()
+{
+ put('\n');
+}
+
+inline void troff_output_file::moveto(hunits h, vunits v)
+{
+ hpos = h.to_units();
+ vpos = v.to_units();
+}
+
+void troff_output_file::really_print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits)
+{
+ moveto(x, y);
+ while (n != 0) {
+ // Check whether we should push the current troff state and use
+ // the state at the start of the invocation of this diversion.
+ if (n->div_nest_level > cur_div_level && n->push_state) {
+ state.push_state(n->push_state);
+ cur_div_level = n->div_nest_level;
+ }
+ // Has the current diversion level decreased? Then we must pop the
+ // troff state.
+ while (n->div_nest_level < cur_div_level) {
+ state.pop_state();
+ cur_div_level = n->div_nest_level;
+ }
+ // Now check whether the state has changed.
+ if ((is_on() || n->force_tprint())
+ && (state.changed(n->state) || n->is_tag() || n->is_special)) {
+ flush_tbuf();
+ do_motion();
+ force_motion = 1;
+ flush();
+ state.flush(fp, n->state, tag_list);
+ tag_list = string("");
+ flush();
+ }
+ n->tprint(this);
+ n = n->next;
+ }
+ flush_tbuf();
+ // This ensures that transparent throughput will have a more predictable
+ // position.
+ do_motion();
+ force_motion = 1;
+ hpos = 0;
+ put('n');
+ put(before.to_units());
+ put(' ');
+ put(after.to_units());
+ put('\n');
+}
+
+inline void troff_output_file::word_marker()
+{
+ flush_tbuf();
+ if (is_on())
+ put('w');
+}
+
+inline void troff_output_file::right(hunits n)
+{
+ hpos += n.to_units();
+}
+
+inline void troff_output_file::down(vunits n)
+{
+ vpos += n.to_units();
+}
+
+void troff_output_file::do_motion()
+{
+ if (force_motion) {
+ put('V');
+ put(vpos);
+ put('\n');
+ put('H');
+ put(hpos);
+ put('\n');
+ }
+ else {
+ if (hpos != output_hpos) {
+ units n = hpos - output_hpos;
+ if (n > 0 && n < hpos) {
+ put('h');
+ put(n);
+ }
+ else {
+ put('H');
+ put(hpos);
+ }
+ put('\n');
+ }
+ if (vpos != output_vpos) {
+ units n = vpos - output_vpos;
+ if (n > 0 && n < vpos) {
+ put('v');
+ put(n);
+ }
+ else {
+ put('V');
+ put(vpos);
+ }
+ put('\n');
+ }
+ }
+ output_vpos = vpos;
+ output_hpos = hpos;
+ force_motion = 0;
+}
+
+void troff_output_file::flush_tbuf()
+{
+ if (!is_on()) {
+ tbuf_len = 0;
+ return;
+ }
+
+ if (tbuf_len == 0)
+ return;
+ if (tbuf_kern == 0)
+ put('t');
+ else {
+ put('u');
+ put(tbuf_kern);
+ put(' ');
+ }
+ check_output_limits(hpos, vpos);
+ check_output_limits(hpos, vpos - current_size);
+
+ for (int i = 0; i < tbuf_len; i++)
+ put(tbuf[i]);
+ put('\n');
+ tbuf_len = 0;
+}
+
+void troff_output_file::check_charinfo(tfont *tf, charinfo *ci)
+{
+ if (!is_on())
+ return;
+
+ int height = tf->get_char_height(ci).to_units();
+ int width = tf->get_width(ci).to_units()
+ + tf->get_italic_correction(ci).to_units();
+ int depth = tf->get_char_depth(ci).to_units();
+ check_output_limits(output_hpos, output_vpos - height);
+ check_output_limits(output_hpos + width, output_vpos + depth);
+}
+
+void troff_output_file::put_char_width(charinfo *ci, tfont *tf,
+ color *gcol, color *fcol,
+ hunits w, hunits k)
+{
+ int kk = k.to_units();
+ if (!is_on()) {
+ flush_tbuf();
+ hpos += w.to_units() + kk;
+ return;
+ }
+ set_font(tf);
+ unsigned char c = ci->get_ascii_code();
+ if (c == '\0') {
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ check_charinfo(tf, ci);
+ if (ci->numbered()) {
+ put('N');
+ put(ci->get_number());
+ }
+ else {
+ put('C');
+ const char *s = ci->nm.contents();
+ if (s[1] == 0) {
+ put('\\');
+ put(s[0]);
+ }
+ else
+ put(s);
+ }
+ put('\n');
+ hpos += w.to_units() + kk;
+ }
+ else if (device_has_tcommand) {
+ if (tbuf_len > 0 && hpos == output_hpos && vpos == output_vpos
+ && (!gcol || gcol == current_glyph_color)
+ && (!fcol || fcol == current_fill_color)
+ && kk == tbuf_kern
+ && tbuf_len < TBUF_SIZE) {
+ check_charinfo(tf, ci);
+ tbuf[tbuf_len++] = c;
+ output_hpos += w.to_units() + kk;
+ hpos = output_hpos;
+ return;
+ }
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ check_charinfo(tf, ci);
+ tbuf[tbuf_len++] = c;
+ output_hpos += w.to_units() + kk;
+ tbuf_kern = kk;
+ hpos = output_hpos;
+ }
+ else {
+ // flush_tbuf();
+ int n = hpos - output_hpos;
+ check_charinfo(tf, ci);
+ // check_output_limits(output_hpos, output_vpos);
+ if (vpos == output_vpos
+ && (!gcol || gcol == current_glyph_color)
+ && (!fcol || fcol == current_fill_color)
+ && n > 0 && n < 100 && !force_motion) {
+ put(char(n/10 + '0'));
+ put(char(n%10 + '0'));
+ put(c);
+ output_hpos = hpos;
+ }
+ else {
+ glyph_color(gcol);
+ fill_color(fcol);
+ do_motion();
+ put('c');
+ put(c);
+ }
+ hpos += w.to_units() + kk;
+ }
+}
+
+void troff_output_file::put_char(charinfo *ci, tfont *tf,
+ color *gcol, color *fcol)
+{
+ flush_tbuf();
+ if (!is_on())
+ return;
+ set_font(tf);
+ unsigned char c = ci->get_ascii_code();
+ if (c == '\0') {
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ if (ci->numbered()) {
+ put('N');
+ put(ci->get_number());
+ }
+ else {
+ put('C');
+ const char *s = ci->nm.contents();
+ if (s[1] == 0) {
+ put('\\');
+ put(s[0]);
+ }
+ else
+ put(s);
+ }
+ put('\n');
+ }
+ else {
+ int n = hpos - output_hpos;
+ if (vpos == output_vpos
+ && (!gcol || gcol == current_glyph_color)
+ && (!fcol || fcol == current_fill_color)
+ && n > 0 && n < 100) {
+ put(char(n/10 + '0'));
+ put(char(n%10 + '0'));
+ put(c);
+ output_hpos = hpos;
+ }
+ else {
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ put('c');
+ put(c);
+ }
+ }
+}
+
+// set_font calls 'flush_tbuf' if necessary.
+
+void troff_output_file::set_font(tfont *tf)
+{
+ if (current_tfont == tf)
+ return;
+ flush_tbuf();
+ int n = tf->get_input_position();
+ symbol nm = tf->get_name();
+ if (n >= nfont_positions || font_position[n] != nm) {
+ put("x font ");
+ put(n);
+ put(' ');
+ put(nm.contents());
+ put('\n');
+ if (n >= nfont_positions) {
+ int old_nfont_positions = nfont_positions;
+ symbol *old_font_position = font_position;
+ nfont_positions *= 3;
+ nfont_positions /= 2;
+ if (nfont_positions <= n)
+ nfont_positions = n + 10;
+ font_position = new symbol[nfont_positions];
+ memcpy(font_position, old_font_position,
+ old_nfont_positions*sizeof(symbol));
+ delete[] old_font_position;
+ }
+ font_position[n] = nm;
+ }
+ if (current_font_number != n) {
+ put('f');
+ put(n);
+ put('\n');
+ current_font_number = n;
+ }
+ int zoom = tf->get_zoom();
+ int size;
+ if (zoom)
+ size = scale(tf->get_size().to_scaled_points(),
+ zoom, 1000);
+ else
+ size = tf->get_size().to_scaled_points();
+ if (current_size != size) {
+ put('s');
+ put(size);
+ put('\n');
+ current_size = size;
+ }
+ int slant = tf->get_slant();
+ if (current_slant != slant) {
+ put("x Slant ");
+ put(slant);
+ put('\n');
+ current_slant = slant;
+ }
+ int height = tf->get_height();
+ if (current_height != height) {
+ put("x Height ");
+ put(height == 0 ? current_size : height);
+ put('\n');
+ current_height = height;
+ }
+ current_tfont = tf;
+}
+
+// fill_color calls 'flush_tbuf' and 'do_motion' if necessary.
+
+void troff_output_file::fill_color(color *col)
+{
+ if (!col || current_fill_color == col)
+ return;
+ current_fill_color = col;
+ if (!color_flag)
+ return;
+ flush_tbuf();
+ do_motion();
+ put("DF");
+ unsigned int components[4];
+ color_scheme cs;
+ cs = col->get_components(components);
+ switch (cs) {
+ case DEFAULT:
+ put('d');
+ break;
+ case RGB:
+ put("r ");
+ put(Red);
+ put(' ');
+ put(Green);
+ put(' ');
+ put(Blue);
+ break;
+ case CMY:
+ put("c ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ break;
+ case CMYK:
+ put("k ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ put(' ');
+ put(Black);
+ break;
+ case GRAY:
+ put("g ");
+ put(Gray);
+ break;
+ }
+ put('\n');
+}
+
+// glyph_color calls 'flush_tbuf' and 'do_motion' if necessary.
+
+void troff_output_file::glyph_color(color *col)
+{
+ if (!col || current_glyph_color == col)
+ return;
+ current_glyph_color = col;
+ if (!color_flag)
+ return;
+ flush_tbuf();
+ // grotty doesn't like a color command if the vertical position is zero.
+ do_motion();
+ put("m");
+ unsigned int components[4];
+ color_scheme cs;
+ cs = col->get_components(components);
+ switch (cs) {
+ case DEFAULT:
+ put('d');
+ break;
+ case RGB:
+ put("r ");
+ put(Red);
+ put(' ');
+ put(Green);
+ put(' ');
+ put(Blue);
+ break;
+ case CMY:
+ put("c ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ break;
+ case CMYK:
+ put("k ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ put(' ');
+ put(Black);
+ break;
+ case GRAY:
+ put("g ");
+ put(Gray);
+ break;
+ }
+ put('\n');
+}
+
+void troff_output_file::add_to_tag_list(string s)
+{
+ if (tag_list == string(""))
+ tag_list = s;
+ else {
+ tag_list += string("\n");
+ tag_list += s;
+ }
+}
+
+// determine_line_limits - works out the smallest box which will contain
+// the entity, code, built from the point array.
+void troff_output_file::determine_line_limits(char code, hvpair *point,
+ int npoints)
+{
+ int i, x, y;
+
+ if (!is_on())
+ return;
+
+ switch (code) {
+ case 'c':
+ case 'C':
+ // only the h field is used when defining a circle
+ check_output_limits(output_hpos,
+ output_vpos - point[0].h.to_units()/2);
+ check_output_limits(output_hpos + point[0].h.to_units(),
+ output_vpos + point[0].h.to_units()/2);
+ break;
+ case 'E':
+ case 'e':
+ check_output_limits(output_hpos,
+ output_vpos - point[0].v.to_units()/2);
+ check_output_limits(output_hpos + point[0].h.to_units(),
+ output_vpos + point[0].v.to_units()/2);
+ break;
+ case 'P':
+ case 'p':
+ x = output_hpos;
+ y = output_vpos;
+ check_output_limits(x, y);
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ break;
+ case 't':
+ x = output_hpos;
+ y = output_vpos;
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ break;
+ case 'a':
+ double c[2];
+ int p[4];
+ int minx, miny, maxx, maxy;
+ x = output_hpos;
+ y = output_vpos;
+ p[0] = point[0].h.to_units();
+ p[1] = point[0].v.to_units();
+ p[2] = point[1].h.to_units();
+ p[3] = point[1].v.to_units();
+ if (adjust_arc_center(p, c)) {
+ check_output_arc_limits(x, y,
+ p[0], p[1], p[2], p[3],
+ c[0], c[1],
+ &minx, &maxx, &miny, &maxy);
+ check_output_limits(minx, miny);
+ check_output_limits(maxx, maxy);
+ break;
+ }
+ // fall through
+ case 'l':
+ x = output_hpos;
+ y = output_vpos;
+ check_output_limits(x, y);
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ break;
+ default:
+ x = output_hpos;
+ y = output_vpos;
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ }
+}
+
+void troff_output_file::draw(char code, hvpair *point, int npoints,
+ font_size fsize, color *gcol, color *fcol)
+{
+ int i;
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ if (is_on()) {
+ int size = fsize.to_scaled_points();
+ if (current_size != size) {
+ put('s');
+ put(size);
+ put('\n');
+ current_size = size;
+ current_tfont = 0;
+ }
+ put('D');
+ put(code);
+ if (code == 'c') {
+ put(' ');
+ put(point[0].h.to_units());
+ }
+ else
+ for (i = 0; i < npoints; i++) {
+ put(' ');
+ put(point[i].h.to_units());
+ put(' ');
+ put(point[i].v.to_units());
+ }
+ determine_line_limits(code, point, npoints);
+ }
+
+ for (i = 0; i < npoints; i++)
+ output_hpos += point[i].h.to_units();
+ hpos = output_hpos;
+ if (code != 'e') {
+ for (i = 0; i < npoints; i++)
+ output_vpos += point[i].v.to_units();
+ vpos = output_vpos;
+ }
+ if (is_on())
+ put('\n');
+}
+
+void troff_output_file::really_on()
+{
+ flush_tbuf();
+ force_motion = 1;
+ do_motion();
+}
+
+void troff_output_file::really_off()
+{
+ flush_tbuf();
+}
+
+void troff_output_file::really_put_filename(const char *filename, int po)
+{
+ flush_tbuf();
+ put("x F ");
+ if (po)
+ put("<");
+ put(filename);
+ if (po)
+ put(">");
+ put('\n');
+}
+
+void troff_output_file::really_begin_page(int pageno, vunits page_length)
+{
+ flush_tbuf();
+ if (begun_page) {
+ if (page_length > V0) {
+ put('V');
+ put(page_length.to_units());
+ put('\n');
+ }
+ }
+ else
+ begun_page = 1;
+ current_tfont = 0;
+ current_font_number = -1;
+ current_size = 0;
+ // current_height = 0;
+ // current_slant = 0;
+ hpos = 0;
+ vpos = 0;
+ output_hpos = 0;
+ output_vpos = 0;
+ force_motion = 1;
+ for (int i = 0; i < nfont_positions; i++)
+ font_position[i] = NULL_SYMBOL;
+ put('p');
+ put(pageno);
+ put('\n');
+}
+
+void troff_output_file::really_copy_file(hunits x, vunits y,
+ const char *filename)
+{
+ moveto(x, y);
+ flush_tbuf();
+ do_motion();
+ errno = 0;
+ FILE *ifp = include_search_path.open_file_cautious(filename);
+ if (ifp == 0)
+ error("can't open '%1': %2", filename, strerror(errno));
+ else {
+ int c;
+ while ((c = getc(ifp)) != EOF)
+ put(char(c));
+ fclose(ifp);
+ }
+ force_motion = 1;
+ current_size = 0;
+ current_tfont = 0;
+ current_font_number = -1;
+ for (int i = 0; i < nfont_positions; i++)
+ font_position[i] = NULL_SYMBOL;
+}
+
+void troff_output_file::really_transparent_char(unsigned char c)
+{
+ put(c);
+}
+
+troff_output_file::~troff_output_file()
+{
+ delete[] font_position;
+}
+
+void troff_output_file::trailer(vunits page_length)
+{
+ flush_tbuf();
+ if (page_length > V0) {
+ put("x trailer\n");
+ put('V');
+ put(page_length.to_units());
+ put('\n');
+ }
+ put("x stop\n");
+}
+
+troff_output_file::troff_output_file()
+: current_slant(0), current_height(0), current_fill_color(0),
+ current_glyph_color(0), nfont_positions(10), tbuf_len(0), begun_page(0),
+ cur_div_level(0)
+{
+ font_position = new symbol[nfont_positions];
+ put("x T ");
+ put(device);
+ put('\n');
+ put("x res ");
+ put(units_per_inch);
+ put(' ');
+ put(hresolution);
+ put(' ');
+ put(vresolution);
+ put('\n');
+ put("x init\n");
+}
+
+/* output_file */
+
+output_file *the_output = 0;
+
+output_file::output_file()
+{
+ is_dying = false;
+}
+
+output_file::~output_file()
+{
+}
+
+void output_file::trailer(vunits)
+{
+}
+
+void output_file::put_filename(const char *, int)
+{
+}
+
+void output_file::on()
+{
+}
+
+void output_file::off()
+{
+}
+
+real_output_file::real_output_file()
+: printing(0), output_on(1)
+{
+#ifndef POPEN_MISSING
+ if (pipe_command) {
+ if ((fp = popen(pipe_command, POPEN_WT)) != 0) {
+ piped = 1;
+ return;
+ }
+ error("pipe open failed: %1", strerror(errno));
+ }
+ piped = 0;
+#endif /* not POPEN_MISSING */
+ fp = stdout;
+}
+
+real_output_file::~real_output_file()
+{
+ if (!fp)
+ return;
+ // Prevent destructor from recursing; see div.cpp:cleanup_and_exit().
+ is_dying = true;
+ // To avoid looping, set fp to 0 before calling fatal().
+ if (ferror(fp)) {
+ fp = 0;
+ fatal("error on output file stream");
+ }
+ else if (fflush(fp) < 0) {
+ fp = 0;
+ fatal("unable to flush output file: %1", strerror(errno));
+ }
+#ifndef POPEN_MISSING
+ if (piped) {
+ int result = pclose(fp);
+ fp = 0;
+ if (result < 0)
+ fatal("unable to close pipe: %1", strerror(errno));
+ if (!WIFEXITED(result))
+ error("output process '%1' got fatal signal %2",
+ pipe_command,
+ WIFSIGNALED(result) ? WTERMSIG(result) : WSTOPSIG(result));
+ else {
+ int exit_status = WEXITSTATUS(result);
+ if (exit_status != 0)
+ error("output process '%1' exited with status %2",
+ pipe_command, exit_status);
+ }
+ }
+ else
+#endif /* not POPEN MISSING */
+ if (fclose(fp) < 0) {
+ fp = 0;
+ fatal("unable to close output file: %1", strerror(errno));
+ }
+}
+
+void real_output_file::flush()
+{
+ // To avoid looping, set fp to 0 before calling fatal().
+ if (fflush(fp) < 0) {
+ fp = 0;
+ fatal("unable to flush output file: %1", strerror(errno));
+ }
+}
+
+int real_output_file::is_printing()
+{
+ return printing;
+}
+
+void real_output_file::begin_page(int pageno, vunits page_length)
+{
+ printing = in_output_page_list(pageno);
+ if (printing)
+ really_begin_page(pageno, page_length);
+}
+
+void real_output_file::copy_file(hunits x, vunits y, const char *filename)
+{
+ if (printing && output_on)
+ really_copy_file(x, y, filename);
+ check_output_limits(x.to_units(), y.to_units());
+}
+
+void real_output_file::transparent_char(unsigned char c)
+{
+ if (printing && output_on)
+ really_transparent_char(c);
+}
+
+void real_output_file::print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits width)
+{
+ if (printing)
+ really_print_line(x, y, n, before, after, width);
+ delete_node_list(n);
+}
+
+void real_output_file::really_copy_file(hunits, vunits, const char *)
+{
+ // do nothing
+}
+
+void real_output_file::put_filename(const char *filename, int po)
+{
+ really_put_filename(filename, po);
+}
+
+void real_output_file::really_put_filename(const char *, int)
+{
+}
+
+void real_output_file::on()
+{
+ really_on();
+ if (output_on == 0)
+ output_on = 1;
+}
+
+void real_output_file::off()
+{
+ really_off();
+ output_on = 0;
+}
+
+int real_output_file::is_on()
+{
+ return output_on;
+}
+
+void real_output_file::really_on()
+{
+}
+
+void real_output_file::really_off()
+{
+}
+
+/* ascii_output_file */
+
+void ascii_output_file::really_transparent_char(unsigned char c)
+{
+ putc(c, fp);
+}
+
+void ascii_output_file::really_print_line(hunits, vunits, node *n,
+ vunits, vunits, hunits)
+{
+ while (n != 0) {
+ n->ascii_print(this);
+ n = n->next;
+ }
+ fputc('\n', fp);
+}
+
+void ascii_output_file::really_begin_page(int /*pageno*/, vunits /*page_length*/)
+{
+ fputs("<beginning of page>\n", fp);
+}
+
+ascii_output_file::ascii_output_file()
+{
+}
+
+/* suppress_output_file */
+
+suppress_output_file::suppress_output_file()
+{
+}
+
+void suppress_output_file::really_print_line(hunits, vunits, node *, vunits, vunits, hunits)
+{
+}
+
+void suppress_output_file::really_begin_page(int, vunits)
+{
+}
+
+void suppress_output_file::really_transparent_char(unsigned char)
+{
+}
+
+/* glyphs, ligatures, kerns, discretionary breaks */
+
+class charinfo_node : public node {
+protected:
+ charinfo *ci;
+public:
+ charinfo_node(charinfo *, statem *, int, node * = 0);
+ int ends_sentence();
+ int overlaps_vertically();
+ int overlaps_horizontally();
+};
+
+charinfo_node::charinfo_node(charinfo *c, statem *s, int pop, node *x)
+: node(x, s, pop), ci(c)
+{
+}
+
+int charinfo_node::ends_sentence()
+{
+ if (ci->ends_sentence())
+ return 1;
+ else if (ci->transparent())
+ return 2;
+ else
+ return 0;
+}
+
+int charinfo_node::overlaps_horizontally()
+{
+ return ci->overlaps_horizontally();
+}
+
+int charinfo_node::overlaps_vertically()
+{
+ return ci->overlaps_vertically();
+}
+
+class glyph_node : public charinfo_node {
+protected:
+ tfont *tf;
+ color *gcol;
+ color *fcol; /* this is needed for grotty */
+#ifdef STORE_WIDTH
+ hunits wid;
+ glyph_node(charinfo *, tfont *, color *, color *, hunits,
+ statem *, int, node * = 0);
+#endif
+public:
+ glyph_node(charinfo *, tfont *, color *, color *,
+ statem *, int, node * = 0);
+ ~glyph_node() {}
+ node *copy();
+ node *merge_glyph_node(glyph_node *);
+ node *merge_self(node *);
+ hunits width();
+ node *last_char_node();
+ units size();
+ void vertical_extent(vunits *, vunits *);
+ hunits subscript_correction();
+ hunits italic_correction();
+ hunits left_italic_correction();
+ hunits skew();
+ hyphenation_type get_hyphenation_type();
+ tfont *get_tfont();
+ color *get_glyph_color();
+ color *get_fill_color();
+ void tprint(troff_output_file *);
+ void zero_width_tprint(troff_output_file *);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_self(node *, hyphen_list **);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int character_type();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void debug_node();
+};
+
+class ligature_node : public glyph_node {
+ node *n1;
+ node *n2;
+#ifdef STORE_WIDTH
+ ligature_node(charinfo *, tfont *, color *, color *, hunits,
+ node *, node *, statem *, int, node * = 0);
+#endif
+public:
+ void *operator new(size_t);
+ void operator delete(void *);
+ ligature_node(charinfo *, tfont *, color *, color *,
+ node *, node *, statem *, int, node * = 0);
+ ~ligature_node();
+ node *copy();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class kern_pair_node : public node {
+ hunits amount;
+ node *n1;
+ node *n2;
+public:
+ kern_pair_node(hunits, node *, node *, statem *, int, node * = 0);
+ ~kern_pair_node();
+ node *copy();
+ node *merge_glyph_node(glyph_node *);
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_discretionary_hyphen();
+ hunits width();
+ node *last_char_node();
+ hunits italic_correction();
+ hunits subscript_correction();
+ void tprint(troff_output_file *);
+ hyphenation_type get_hyphenation_type();
+ int ends_sentence();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void vertical_extent(vunits *, vunits *);
+};
+
+class dbreak_node : public node {
+ node *none;
+ node *pre;
+ node *post;
+public:
+ dbreak_node(node *, node *, statem *, int, node * = 0);
+ ~dbreak_node();
+ node *copy();
+ node *merge_glyph_node(glyph_node *);
+ node *add_discretionary_hyphen();
+ hunits width();
+ node *last_char_node();
+ hunits italic_correction();
+ hunits subscript_correction();
+ void tprint(troff_output_file *);
+ breakpoint *get_breakpoints(hunits width, int ns, breakpoint *rest = 0,
+ int is_inner = 0);
+ int nbreaks();
+ int ends_sentence();
+ void split(int, node **, node **);
+ hyphenation_type get_hyphenation_type();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+void *ligature_node::operator new(size_t n)
+{
+ return new char[n];
+}
+
+void ligature_node::operator delete(void *p)
+{
+ delete[] (char *)p;
+}
+
+glyph_node::glyph_node(charinfo *c, tfont *t, color *gc, color *fc,
+ statem *s, int pop, node *x)
+: charinfo_node(c, s, pop, x), tf(t), gcol(gc), fcol(fc)
+{
+#ifdef STORE_WIDTH
+ wid = tf->get_width(ci);
+#endif
+}
+
+#ifdef STORE_WIDTH
+glyph_node::glyph_node(charinfo *c, tfont *t,
+ color *gc, color *fc, hunits w,
+ statem *s, int pop, node *x)
+: charinfo_node(c, s, pop, x), tf(t), gcol(gc), fcol(fc), wid(w)
+{
+}
+#endif
+
+node *glyph_node::copy()
+{
+#ifdef STORE_WIDTH
+ return new glyph_node(ci, tf, gcol, fcol, wid, state, div_nest_level);
+#else
+ return new glyph_node(ci, tf, gcol, fcol, state, div_nest_level);
+#endif
+}
+
+node *glyph_node::merge_self(node *nd)
+{
+ return nd->merge_glyph_node(this);
+}
+
+int glyph_node::character_type()
+{
+ return tf->get_character_type(ci);
+}
+
+node *glyph_node::add_self(node *n, hyphen_list **p)
+{
+ assert(ci->get_hyphenation_code() == (*p)->hyphenation_code);
+ next = 0;
+ node *nn;
+ if (n == 0 || (nn = n->merge_glyph_node(this)) == 0) {
+ next = n;
+ nn = this;
+ }
+ if ((*p)->hyphen)
+ nn = nn->add_discretionary_hyphen();
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return nn;
+}
+
+units glyph_node::size()
+{
+ return tf->get_size().to_units();
+}
+
+hyphen_list *glyph_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ (*count)++;
+ return new hyphen_list(ci->get_hyphenation_code(), tail);
+}
+
+tfont *node::get_tfont()
+{
+ return 0;
+}
+
+tfont *glyph_node::get_tfont()
+{
+ return tf;
+}
+
+color *node::get_glyph_color()
+{
+ return 0;
+}
+
+color *glyph_node::get_glyph_color()
+{
+ return gcol;
+}
+
+color *node::get_fill_color()
+{
+ return 0;
+}
+
+color *glyph_node::get_fill_color()
+{
+ return fcol;
+}
+
+node *node::merge_glyph_node(glyph_node *)
+{
+ return 0;
+}
+
+node *glyph_node::merge_glyph_node(glyph_node *gn)
+{
+ if (tf == gn->tf && gcol == gn->gcol && fcol == gn->fcol) {
+ charinfo *lig;
+ if ((lig = tf->get_lig(ci, gn->ci)) != 0) {
+ node *next1 = next;
+ next = 0;
+ return new ligature_node(lig, tf, gcol, fcol, this, gn, state,
+ gn->div_nest_level, next1);
+ }
+ hunits kern;
+ if (tf->get_kern(ci, gn->ci, &kern)) {
+ node *next1 = next;
+ next = 0;
+ return new kern_pair_node(kern, this, gn, state,
+ gn->div_nest_level, next1);
+ }
+ }
+ return 0;
+}
+
+#ifdef STORE_WIDTH
+inline
+#endif
+hunits glyph_node::width()
+{
+#ifdef STORE_WIDTH
+ return wid;
+#else
+ return tf->get_width(ci);
+#endif
+}
+
+node *glyph_node::last_char_node()
+{
+ return this;
+}
+
+void glyph_node::vertical_extent(vunits *min, vunits *max)
+{
+ *min = -tf->get_char_height(ci);
+ *max = tf->get_char_depth(ci);
+}
+
+hunits glyph_node::skew()
+{
+ return tf->get_char_skew(ci);
+}
+
+hunits glyph_node::subscript_correction()
+{
+ return tf->get_subscript_correction(ci);
+}
+
+hunits glyph_node::italic_correction()
+{
+ return tf->get_italic_correction(ci);
+}
+
+hunits glyph_node::left_italic_correction()
+{
+ return tf->get_left_italic_correction(ci);
+}
+
+hyphenation_type glyph_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+void glyph_node::ascii_print(ascii_output_file *ascii)
+{
+ unsigned char c = ci->get_ascii_code();
+ if (c != 0)
+ ascii->outc(c);
+ else
+ ascii->outs(ci->nm.contents());
+}
+
+void glyph_node::debug_node()
+{
+ unsigned char c = ci->get_ascii_code();
+ fprintf(stderr, "{ %s [", type());
+ if (c)
+ fprintf(stderr, "%c", c);
+ else
+ fprintf(stderr, "%s", ci->nm.contents());
+ if (push_state)
+ fprintf(stderr, " <push_state>");
+ if (state)
+ state->display_state();
+ fprintf(stderr, " nest level %d", div_nest_level);
+ fprintf(stderr, "]}\n");
+ fflush(stderr);
+}
+
+ligature_node::ligature_node(charinfo *c, tfont *t, color *gc, color *fc,
+ node *gn1, node *gn2, statem *s,
+ int pop, node *x)
+: glyph_node(c, t, gc, fc, s, pop, x), n1(gn1), n2(gn2)
+{
+}
+
+#ifdef STORE_WIDTH
+ligature_node::ligature_node(charinfo *c, tfont *t, color *gc, color *fc,
+ hunits w, node *gn1, node *gn2, statem *s,
+ int pop, node *x)
+: glyph_node(c, t, gc, fc, w, s, pop, x), n1(gn1), n2(gn2)
+{
+}
+#endif
+
+ligature_node::~ligature_node()
+{
+ delete n1;
+ delete n2;
+}
+
+node *ligature_node::copy()
+{
+#ifdef STORE_WIDTH
+ return new ligature_node(ci, tf, gcol, fcol, wid, n1->copy(), n2->copy(),
+ state, div_nest_level);
+#else
+ return new ligature_node(ci, tf, gcol, fcol, n1->copy(), n2->copy(),
+ state, div_nest_level);
+#endif
+}
+
+void ligature_node::ascii_print(ascii_output_file *ascii)
+{
+ n1->ascii_print(ascii);
+ n2->ascii_print(ascii);
+}
+
+hyphen_list *ligature_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ hyphen_list *hl = n2->get_hyphen_list(tail, count);
+ return n1->get_hyphen_list(hl, count);
+}
+
+node *ligature_node::add_self(node *n, hyphen_list **p)
+{
+ n = n1->add_self(n, p);
+ n = n2->add_self(n, p);
+ n1 = n2 = 0;
+ delete this;
+ return n;
+}
+
+kern_pair_node::kern_pair_node(hunits n, node *first, node *second,
+ statem* s, int pop, node *x)
+: node(x, s, pop), amount(n), n1(first), n2(second)
+{
+}
+
+dbreak_node::dbreak_node(node *n, node *p, statem *s, int pop, node *x)
+: node(x, s, pop), none(n), pre(p), post(0)
+{
+}
+
+node *dbreak_node::merge_glyph_node(glyph_node *gn)
+{
+ glyph_node *gn2 = (glyph_node *)gn->copy();
+ node *new_none = none ? none->merge_glyph_node(gn) : 0;
+ node *new_post = post ? post->merge_glyph_node(gn2) : 0;
+ if (new_none == 0 && new_post == 0) {
+ delete gn2;
+ return 0;
+ }
+ if (new_none != 0)
+ none = new_none;
+ else {
+ gn->next = none;
+ none = gn;
+ }
+ if (new_post != 0)
+ post = new_post;
+ else {
+ gn2->next = post;
+ post = gn2;
+ }
+ return this;
+}
+
+node *kern_pair_node::merge_glyph_node(glyph_node *gn)
+{
+ node *nd = n2->merge_glyph_node(gn);
+ if (nd == 0)
+ return 0;
+ n2 = nd;
+ nd = n2->merge_self(n1);
+ if (nd) {
+ nd->next = next;
+ n1 = 0;
+ n2 = 0;
+ delete this;
+ return nd;
+ }
+ return this;
+}
+
+hunits kern_pair_node::italic_correction()
+{
+ return n2->italic_correction();
+}
+
+hunits kern_pair_node::subscript_correction()
+{
+ return n2->subscript_correction();
+}
+
+void kern_pair_node::vertical_extent(vunits *min, vunits *max)
+{
+ n1->vertical_extent(min, max);
+ vunits min2, max2;
+ n2->vertical_extent(&min2, &max2);
+ if (min2 < *min)
+ *min = min2;
+ if (max2 > *max)
+ *max = max2;
+}
+
+node *kern_pair_node::add_discretionary_hyphen()
+{
+ tfont *tf = n1->get_tfont();
+ if (tf) {
+ if (tf->contains(soft_hyphen_char)) {
+ color *gcol = n2->get_glyph_color();
+ color *fcol = n2->get_fill_color();
+ node *next1 = next;
+ next = 0;
+ node *n = copy();
+ glyph_node *gn = new glyph_node(soft_hyphen_char, tf, gcol, fcol,
+ state, div_nest_level);
+ node *nn = n->merge_glyph_node(gn);
+ if (nn == 0) {
+ gn->next = n;
+ nn = gn;
+ }
+ return new dbreak_node(this, nn, state, div_nest_level, next1);
+ }
+ }
+ return this;
+}
+
+kern_pair_node::~kern_pair_node()
+{
+ if (n1 != 0)
+ delete n1;
+ if (n2 != 0)
+ delete n2;
+}
+
+dbreak_node::~dbreak_node()
+{
+ delete_node_list(pre);
+ delete_node_list(post);
+ delete_node_list(none);
+}
+
+node *kern_pair_node::copy()
+{
+ return new kern_pair_node(amount, n1->copy(), n2->copy(), state,
+ div_nest_level);
+}
+
+node *copy_node_list(node *n)
+{
+ node *p = 0;
+ while (n != 0) {
+ node *nn = n->copy();
+ nn->next = p;
+ p = nn;
+ n = n->next;
+ }
+ while (p != 0) {
+ node *pp = p->next;
+ p->next = n;
+ n = p;
+ p = pp;
+ }
+ return n;
+}
+
+void delete_node_list(node *n)
+{
+ while (n != 0) {
+ node *tem = n;
+ n = n->next;
+ delete tem;
+ }
+}
+
+node *dbreak_node::copy()
+{
+ dbreak_node *p = new dbreak_node(copy_node_list(none), copy_node_list(pre),
+ state, div_nest_level);
+ p->post = copy_node_list(post);
+ return p;
+}
+
+hyphen_list *node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return tail;
+}
+
+hyphen_list *kern_pair_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ hyphen_list *hl = n2->get_hyphen_list(tail, count);
+ return n1->get_hyphen_list(hl, count);
+}
+
+class hyphen_inhibitor_node : public node {
+public:
+ hyphen_inhibitor_node(node * = 0);
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hyphenation_type get_hyphenation_type();
+};
+
+hyphen_inhibitor_node::hyphen_inhibitor_node(node *nd) : node(nd)
+{
+}
+
+node *hyphen_inhibitor_node::copy()
+{
+ return new hyphen_inhibitor_node;
+}
+
+int hyphen_inhibitor_node::same(node *)
+{
+ return 1;
+}
+
+const char *hyphen_inhibitor_node::type()
+{
+ return "hyphen_inhibitor_node";
+}
+
+int hyphen_inhibitor_node::force_tprint()
+{
+ return 0;
+}
+
+int hyphen_inhibitor_node::is_tag()
+{
+ return 0;
+}
+
+hyphenation_type hyphen_inhibitor_node::get_hyphenation_type()
+{
+ return HYPHEN_INHIBIT;
+}
+
+/* add_discretionary_hyphen methods */
+
+node *dbreak_node::add_discretionary_hyphen()
+{
+ if (post)
+ post = post->add_discretionary_hyphen();
+ if (none)
+ none = none->add_discretionary_hyphen();
+ return this;
+}
+
+node *node::add_discretionary_hyphen()
+{
+ tfont *tf = get_tfont();
+ if (!tf)
+ return new hyphen_inhibitor_node(this);
+ if (tf->contains(soft_hyphen_char)) {
+ color *gcol = get_glyph_color();
+ color *fcol = get_fill_color();
+ node *next1 = next;
+ next = 0;
+ node *n = copy();
+ glyph_node *gn = new glyph_node(soft_hyphen_char, tf, gcol, fcol,
+ state, div_nest_level);
+ node *n1 = n->merge_glyph_node(gn);
+ if (n1 == 0) {
+ gn->next = n;
+ n1 = gn;
+ }
+ return new dbreak_node(this, n1, state, div_nest_level, next1);
+ }
+ return this;
+}
+
+node *node::merge_self(node *)
+{
+ return 0;
+}
+
+node *node::add_self(node *n, hyphen_list ** /*p*/)
+{
+ next = n;
+ return this;
+}
+
+node *kern_pair_node::add_self(node *n, hyphen_list **p)
+{
+ n = n1->add_self(n, p);
+ n = n2->add_self(n, p);
+ n1 = n2 = 0;
+ delete this;
+ return n;
+}
+
+hunits node::width()
+{
+ return H0;
+}
+
+node *node::last_char_node()
+{
+ return 0;
+}
+
+int node::force_tprint()
+{
+ return 0;
+}
+
+int node::is_tag()
+{
+ return 0;
+}
+
+int node::get_break_code()
+{
+ return 0;
+}
+
+hunits hmotion_node::width()
+{
+ return n;
+}
+
+units node::size()
+{
+ return points_to_units(10);
+}
+
+void node::debug_node()
+{
+ fprintf(stderr, "{ %s ", type());
+ if (push_state)
+ fprintf(stderr, " <push_state>");
+ if (state)
+ fprintf(stderr, " <state>");
+ fprintf(stderr, " nest level %d", div_nest_level);
+ fprintf(stderr, " }\n");
+ fflush(stderr);
+}
+
+void node::debug_node_list()
+{
+ node *n = next;
+
+ debug_node();
+ while (n != 0) {
+ n->debug_node();
+ n = n->next;
+ }
+}
+
+hunits kern_pair_node::width()
+{
+ return n1->width() + n2->width() + amount;
+}
+
+node *kern_pair_node::last_char_node()
+{
+ node *nd = n2->last_char_node();
+ if (nd)
+ return nd;
+ return n1->last_char_node();
+}
+
+hunits dbreak_node::width()
+{
+ hunits x = H0;
+ for (node *n = none; n != 0; n = n->next)
+ x += n->width();
+ return x;
+}
+
+node *dbreak_node::last_char_node()
+{
+ for (node *n = none; n; n = n->next) {
+ node *last_node = n->last_char_node();
+ if (last_node)
+ return last_node;
+ }
+ return 0;
+}
+
+hunits dbreak_node::italic_correction()
+{
+ return none ? none->italic_correction() : H0;
+}
+
+hunits dbreak_node::subscript_correction()
+{
+ return none ? none->subscript_correction() : H0;
+}
+
+class italic_corrected_node : public node {
+ node *n;
+ hunits x;
+public:
+ italic_corrected_node(node *, hunits, statem *, int, node * = 0);
+ ~italic_corrected_node();
+ node *copy();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ hunits width();
+ node *last_char_node();
+ void vertical_extent(vunits *, vunits *);
+ int ends_sentence();
+ int overlaps_horizontally();
+ int overlaps_vertically();
+ int same(node *);
+ hyphenation_type get_hyphenation_type();
+ tfont *get_tfont();
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ int character_type();
+ void tprint(troff_output_file *);
+ hunits subscript_correction();
+ hunits skew();
+ node *add_self(node *, hyphen_list **);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+node *node::add_italic_correction(hunits *wd)
+{
+ hunits ic = italic_correction();
+ if (ic.is_zero())
+ return this;
+ else {
+ node *next1 = next;
+ next = 0;
+ *wd += ic;
+ return new italic_corrected_node(this, ic, state, div_nest_level, next1);
+ }
+}
+
+italic_corrected_node::italic_corrected_node(node *nn, hunits xx, statem *s,
+ int pop, node *p)
+: node(p, s, pop), n(nn), x(xx)
+{
+ assert(n != 0);
+}
+
+italic_corrected_node::~italic_corrected_node()
+{
+ delete n;
+}
+
+node *italic_corrected_node::copy()
+{
+ return new italic_corrected_node(n->copy(), x, state, div_nest_level);
+}
+
+hunits italic_corrected_node::width()
+{
+ return n->width() + x;
+}
+
+void italic_corrected_node::vertical_extent(vunits *min, vunits *max)
+{
+ n->vertical_extent(min, max);
+}
+
+void italic_corrected_node::tprint(troff_output_file *out)
+{
+ n->tprint(out);
+ out->right(x);
+}
+
+hunits italic_corrected_node::skew()
+{
+ return n->skew() - x/2;
+}
+
+hunits italic_corrected_node::subscript_correction()
+{
+ return n->subscript_correction() - x;
+}
+
+void italic_corrected_node::ascii_print(ascii_output_file *out)
+{
+ n->ascii_print(out);
+}
+
+int italic_corrected_node::ends_sentence()
+{
+ return n->ends_sentence();
+}
+
+int italic_corrected_node::overlaps_horizontally()
+{
+ return n->overlaps_horizontally();
+}
+
+int italic_corrected_node::overlaps_vertically()
+{
+ return n->overlaps_vertically();
+}
+
+node *italic_corrected_node::last_char_node()
+{
+ return n->last_char_node();
+}
+
+tfont *italic_corrected_node::get_tfont()
+{
+ return n->get_tfont();
+}
+
+hyphenation_type italic_corrected_node::get_hyphenation_type()
+{
+ return n->get_hyphenation_type();
+}
+
+node *italic_corrected_node::add_self(node *nd, hyphen_list **p)
+{
+ nd = n->add_self(nd, p);
+ hunits not_interested;
+ nd = nd->add_italic_correction(&not_interested);
+ n = 0;
+ delete this;
+ return nd;
+}
+
+hyphen_list *italic_corrected_node::get_hyphen_list(hyphen_list *tail,
+ int *count)
+{
+ return n->get_hyphen_list(tail, count);
+}
+
+int italic_corrected_node::character_type()
+{
+ return n->character_type();
+}
+
+class break_char_node : public node {
+ node *ch;
+ char break_code;
+ char prev_break_code;
+ color *col;
+public:
+ break_char_node(node *, int, int, color *, node * = 0);
+ break_char_node(node *, int, int, color *, statem *, int, node * = 0);
+ ~break_char_node();
+ node *copy();
+ hunits width();
+ vunits vertical_width();
+ node *last_char_node();
+ int character_type();
+ int ends_sentence();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ void tprint(troff_output_file *);
+ void zero_width_tprint(troff_output_file *);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ hyphenation_type get_hyphenation_type();
+ int overlaps_vertically();
+ int overlaps_horizontally();
+ units size();
+ tfont *get_tfont();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int get_break_code();
+};
+
+break_char_node::break_char_node(node *n, int bc, int pbc, color *c, node *x)
+: node(x), ch(n), break_code(bc), prev_break_code(pbc), col(c)
+{
+}
+
+break_char_node::break_char_node(node *n, int bc, int pbc, color *c,
+ statem *s, int pop, node *x)
+: node(x, s, pop), ch(n), break_code(bc), prev_break_code(pbc), col(c)
+{
+}
+
+break_char_node::~break_char_node()
+{
+ delete ch;
+}
+
+node *break_char_node::copy()
+{
+ return new break_char_node(ch->copy(), break_code, prev_break_code,
+ col, state, div_nest_level);
+}
+
+hunits break_char_node::width()
+{
+ return ch->width();
+}
+
+vunits break_char_node::vertical_width()
+{
+ return ch->vertical_width();
+}
+
+node *break_char_node::last_char_node()
+{
+ return ch->last_char_node();
+}
+
+int break_char_node::character_type()
+{
+ return ch->character_type();
+}
+
+int break_char_node::ends_sentence()
+{
+ return ch->ends_sentence();
+}
+
+enum break_char_type {
+ CAN_BREAK_BEFORE = 0x01,
+ CAN_BREAK_AFTER = 0x02,
+ IGNORE_HCODES = 0x04,
+ PROHIBIT_BREAK_BEFORE = 0x08,
+ PROHIBIT_BREAK_AFTER = 0x10,
+ INTER_CHAR_SPACE = 0x20
+};
+
+node *break_char_node::add_self(node *n, hyphen_list **p)
+{
+ int have_space_node = 0;
+ assert((*p)->hyphenation_code == 0);
+ if (break_code & CAN_BREAK_BEFORE) {
+ if ((*p)->breakable || break_code & IGNORE_HCODES) {
+ n = new space_node(H0, col, n);
+ n->freeze_space();
+ have_space_node = 1;
+ }
+ }
+ if (!have_space_node) {
+ if (prev_break_code & INTER_CHAR_SPACE
+ || prev_break_code & PROHIBIT_BREAK_AFTER) {
+ if (break_code & PROHIBIT_BREAK_BEFORE)
+ // stretchable zero-width space not implemented yet
+ ;
+ else {
+ // breakable, stretchable zero-width space not implemented yet
+ n = new space_node(H0, col, n);
+ n->freeze_space();
+ }
+ }
+ }
+ next = n;
+ n = this;
+ if (break_code & CAN_BREAK_AFTER) {
+ if ((*p)->breakable || break_code & IGNORE_HCODES) {
+ n = new space_node(H0, col, n);
+ n->freeze_space();
+ }
+ }
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return n;
+}
+
+hyphen_list *break_char_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+hyphenation_type break_char_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+void break_char_node::ascii_print(ascii_output_file *ascii)
+{
+ ch->ascii_print(ascii);
+}
+
+int break_char_node::overlaps_vertically()
+{
+ return ch->overlaps_vertically();
+}
+
+int break_char_node::overlaps_horizontally()
+{
+ return ch->overlaps_horizontally();
+}
+
+units break_char_node::size()
+{
+ return ch->size();
+}
+
+tfont *break_char_node::get_tfont()
+{
+ return ch->get_tfont();
+}
+
+node *extra_size_node::copy()
+{
+ return new extra_size_node(n, state, div_nest_level);
+}
+
+extra_size_node::extra_size_node(vunits i, statem *s, int pop)
+: node(0, s, pop), n(i)
+{
+}
+
+extra_size_node::extra_size_node(vunits i)
+: n(i)
+{
+}
+
+node *vertical_size_node::copy()
+{
+ return new vertical_size_node(n, state, div_nest_level);
+}
+
+vertical_size_node::vertical_size_node(vunits i, statem *s, int pop)
+: node(0, s, pop), n(i)
+{
+}
+
+vertical_size_node::vertical_size_node(vunits i)
+: n(i)
+{
+}
+
+node *hmotion_node::copy()
+{
+ return new hmotion_node(n, was_tab, unformat, col, state, div_nest_level);
+}
+
+node *space_char_hmotion_node::copy()
+{
+ return new space_char_hmotion_node(n, col, state, div_nest_level);
+}
+
+vmotion_node::vmotion_node(vunits i, color *c)
+: n(i), col(c)
+{
+}
+
+vmotion_node::vmotion_node(vunits i, color *c, statem *s, int pop)
+: node(0, s, pop), n(i), col(c)
+{
+}
+
+node *vmotion_node::copy()
+{
+ return new vmotion_node(n, col, state, div_nest_level);
+}
+
+node *dummy_node::copy()
+{
+ return new dummy_node;
+}
+
+node *transparent_dummy_node::copy()
+{
+ return new transparent_dummy_node;
+}
+
+hline_node::~hline_node()
+{
+ if (n)
+ delete n;
+}
+
+hline_node::hline_node(hunits i, node *c, node *nxt)
+: node(nxt), x(i), n(c)
+{
+}
+
+hline_node::hline_node(hunits i, node *c, statem *s, int pop, node *nxt)
+: node(nxt, s, pop), x(i), n(c)
+{
+}
+
+node *hline_node::copy()
+{
+ return new hline_node(x, n ? n->copy() : 0, state, div_nest_level);
+}
+
+hunits hline_node::width()
+{
+ return x < H0 ? H0 : x;
+}
+
+vline_node::vline_node(vunits i, node *c, node *nxt)
+: node(nxt), x(i), n(c)
+{
+}
+
+vline_node::vline_node(vunits i, node *c, statem *s, int pop, node *nxt)
+: node(nxt, s, pop), x(i), n(c)
+{
+}
+
+vline_node::~vline_node()
+{
+ if (n)
+ delete n;
+}
+
+node *vline_node::copy()
+{
+ return new vline_node(x, n ? n->copy() : 0, state, div_nest_level);
+}
+
+hunits vline_node::width()
+{
+ return n == 0 ? H0 : n->width();
+}
+
+zero_width_node::zero_width_node(node *nd, statem *s, int pop)
+: node(0, s, pop), n(nd)
+{
+}
+
+zero_width_node::zero_width_node(node *nd)
+: n(nd)
+{
+}
+
+zero_width_node::~zero_width_node()
+{
+ delete_node_list(n);
+}
+
+node *zero_width_node::copy()
+{
+ return new zero_width_node(copy_node_list(n), state, div_nest_level);
+}
+
+int node_list_character_type(node *p)
+{
+ int t = 0;
+ for (; p; p = p->next)
+ t |= p->character_type();
+ return t;
+}
+
+int zero_width_node::character_type()
+{
+ return node_list_character_type(n);
+}
+
+void node_list_vertical_extent(node *p, vunits *min, vunits *max)
+{
+ *min = V0;
+ *max = V0;
+ vunits cur_vpos = V0;
+ vunits v1, v2;
+ for (; p; p = p->next) {
+ p->vertical_extent(&v1, &v2);
+ v1 += cur_vpos;
+ if (v1 < *min)
+ *min = v1;
+ v2 += cur_vpos;
+ if (v2 > *max)
+ *max = v2;
+ cur_vpos += p->vertical_width();
+ }
+}
+
+void zero_width_node::vertical_extent(vunits *min, vunits *max)
+{
+ node_list_vertical_extent(n, min, max);
+}
+
+overstrike_node::overstrike_node()
+: list(0), max_width(H0)
+{
+}
+
+overstrike_node::overstrike_node(statem *s, int pop)
+: node(0, s, pop), list(0), max_width(H0)
+{
+}
+
+overstrike_node::~overstrike_node()
+{
+ delete_node_list(list);
+}
+
+node *overstrike_node::copy()
+{
+ overstrike_node *on = new overstrike_node(state, div_nest_level);
+ for (node *tem = list; tem; tem = tem->next)
+ on->overstrike(tem->copy());
+ return on;
+}
+
+void overstrike_node::overstrike(node *n)
+{
+ if (n == 0)
+ return;
+ hunits w = n->width();
+ if (w > max_width)
+ max_width = w;
+ node **p;
+ for (p = &list; *p; p = &(*p)->next)
+ ;
+ n->next = 0;
+ *p = n;
+}
+
+hunits overstrike_node::width()
+{
+ return max_width;
+}
+
+bracket_node::bracket_node()
+: list(0), max_width(H0)
+{
+}
+
+bracket_node::bracket_node(statem *s, int pop)
+: node(0, s, pop), list(0), max_width(H0)
+{
+}
+
+bracket_node::~bracket_node()
+{
+ delete_node_list(list);
+}
+
+node *bracket_node::copy()
+{
+ bracket_node *on = new bracket_node(state, div_nest_level);
+ node *last_node = 0;
+ node *tem;
+ if (list)
+ list->last = 0;
+ for (tem = list; tem; tem = tem->next) {
+ if (tem->next)
+ tem->next->last = tem;
+ last_node = tem;
+ }
+ for (tem = last_node; tem; tem = tem->last)
+ on->bracket(tem->copy());
+ return on;
+}
+
+void bracket_node::bracket(node *n)
+{
+ if (n == 0)
+ return;
+ hunits w = n->width();
+ if (w > max_width)
+ max_width = w;
+ n->next = list;
+ list = n;
+}
+
+hunits bracket_node::width()
+{
+ return max_width;
+}
+
+int node::nspaces()
+{
+ return 0;
+}
+
+int node::merge_space(hunits, hunits, hunits)
+{
+ return 0;
+}
+
+
+space_node::space_node(hunits nn, color *c, node *p)
+: node(p, 0, 0), n(nn), set(0), was_escape_colon(0), col(c)
+{
+}
+
+space_node::space_node(hunits nn, int s, int flag, color *c, statem *st,
+ int pop, node *p)
+: node(p, st, pop), n(nn), set(s), was_escape_colon(flag), col(c)
+{
+}
+
+#if 0
+space_node::~space_node()
+{
+}
+#endif
+
+node *space_node::copy()
+{
+ return new space_node(n, set, was_escape_colon, col, state, div_nest_level);
+}
+
+int space_node::force_tprint()
+{
+ return 0;
+}
+
+int space_node::is_tag()
+{
+ return 0;
+}
+
+int space_node::nspaces()
+{
+ return set ? 0 : 1;
+}
+
+int space_node::merge_space(hunits h, hunits, hunits)
+{
+ n += h;
+ return 1;
+}
+
+hunits space_node::width()
+{
+ return n;
+}
+
+void node::spread_space(int*, hunits*)
+{
+}
+
+void space_node::spread_space(int *n_spaces, hunits *desired_space)
+{
+ if (!set) {
+ assert(*n_spaces > 0);
+ if (*n_spaces == 1) {
+ n += *desired_space;
+ *desired_space = H0;
+ }
+ else {
+ hunits extra = *desired_space / *n_spaces;
+ *desired_space -= extra;
+ n += extra;
+ }
+ *n_spaces -= 1;
+ set = 1;
+ }
+}
+
+void node::freeze_space()
+{
+}
+
+void space_node::freeze_space()
+{
+ set = 1;
+}
+
+void node::is_escape_colon()
+{
+}
+
+void space_node::is_escape_colon()
+{
+ was_escape_colon = 1;
+}
+
+diverted_space_node::diverted_space_node(vunits d, statem *s, int pop,
+ node *p)
+: node(p, s, pop), n(d)
+{
+}
+
+diverted_space_node::diverted_space_node(vunits d, node *p)
+: node(p), n(d)
+{
+}
+
+node *diverted_space_node::copy()
+{
+ return new diverted_space_node(n, state, div_nest_level);
+}
+
+diverted_copy_file_node::diverted_copy_file_node(symbol s, statem *st,
+ int pop, node *p)
+: node(p, st, pop), filename(s)
+{
+}
+
+diverted_copy_file_node::diverted_copy_file_node(symbol s, node *p)
+: node(p), filename(s)
+{
+}
+
+node *diverted_copy_file_node::copy()
+{
+ return new diverted_copy_file_node(filename, state, div_nest_level);
+}
+
+int node::ends_sentence()
+{
+ return 0;
+}
+
+int kern_pair_node::ends_sentence()
+{
+ switch (n2->ends_sentence()) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ break;
+ default:
+ assert(0);
+ }
+ return n1->ends_sentence();
+}
+
+int node_list_ends_sentence(node *n)
+{
+ for (; n != 0; n = n->next)
+ switch (n->ends_sentence()) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ break;
+ default:
+ assert(0);
+ }
+ return 2;
+}
+
+int dbreak_node::ends_sentence()
+{
+ return node_list_ends_sentence(none);
+}
+
+int node::overlaps_horizontally()
+{
+ return 0;
+}
+
+int node::overlaps_vertically()
+{
+ return 0;
+}
+
+int node::discardable()
+{
+ return 0;
+}
+
+int space_node::discardable()
+{
+ return set ? 0 : 1;
+}
+
+vunits node::vertical_width()
+{
+ return V0;
+}
+
+vunits vline_node::vertical_width()
+{
+ return x;
+}
+
+vunits vmotion_node::vertical_width()
+{
+ return n;
+}
+
+int node::set_unformat_flag()
+{
+ return 1;
+}
+
+int node::character_type()
+{
+ return 0;
+}
+
+hunits node::subscript_correction()
+{
+ return H0;
+}
+
+hunits node::italic_correction()
+{
+ return H0;
+}
+
+hunits node::left_italic_correction()
+{
+ return H0;
+}
+
+hunits node::skew()
+{
+ return H0;
+}
+
+/* vertical_extent methods */
+
+void node::vertical_extent(vunits *min, vunits *max)
+{
+ vunits v = vertical_width();
+ if (v < V0) {
+ *min = v;
+ *max = V0;
+ }
+ else {
+ *max = v;
+ *min = V0;
+ }
+}
+
+void vline_node::vertical_extent(vunits *min, vunits *max)
+{
+ if (n == 0)
+ node::vertical_extent(min, max);
+ else {
+ vunits cmin, cmax;
+ n->vertical_extent(&cmin, &cmax);
+ vunits h = n->size();
+ if (x < V0) {
+ if (-x < h) {
+ *min = x;
+ *max = V0;
+ }
+ else {
+ // we print the first character and then move up, so
+ *max = cmax;
+ // we print the last character and then move up h
+ *min = cmin + h;
+ if (*min > V0)
+ *min = V0;
+ *min += x;
+ }
+ }
+ else {
+ if (x < h) {
+ *max = x;
+ *min = V0;
+ }
+ else {
+ // we move down by h and then print the first character, so
+ *min = cmin + h;
+ if (*min > V0)
+ *min = V0;
+ *max = x + cmax;
+ }
+ }
+ }
+}
+
+/* ascii_print methods */
+
+static void ascii_print_reverse_node_list(ascii_output_file *ascii, node *n)
+{
+ if (n == 0)
+ return;
+ ascii_print_reverse_node_list(ascii, n->next);
+ n->ascii_print(ascii);
+}
+
+void dbreak_node::ascii_print(ascii_output_file *ascii)
+{
+ ascii_print_reverse_node_list(ascii, none);
+}
+
+void kern_pair_node::ascii_print(ascii_output_file *ascii)
+{
+ n1->ascii_print(ascii);
+ n2->ascii_print(ascii);
+}
+
+void node::ascii_print(ascii_output_file *)
+{
+}
+
+void space_node::ascii_print(ascii_output_file *ascii)
+{
+ if (!n.is_zero())
+ ascii->outc(' ');
+}
+
+void hmotion_node::ascii_print(ascii_output_file *ascii)
+{
+ // this is pretty arbitrary
+ if (n >= points_to_units(2))
+ ascii->outc(' ');
+}
+
+void space_char_hmotion_node::ascii_print(ascii_output_file *ascii)
+{
+ ascii->outc(' ');
+}
+
+/* asciify methods */
+
+void node::asciify(macro *m)
+{
+ m->append(this);
+}
+
+void glyph_node::asciify(macro *m)
+{
+ unsigned char c = ci->get_asciify_code();
+ if (c == 0)
+ c = ci->get_ascii_code();
+ if (c != 0) {
+ m->append(c);
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+void kern_pair_node::asciify(macro *m)
+{
+ n1->asciify(m);
+ n2->asciify(m);
+ n1 = n2 = 0;
+ delete this;
+}
+
+static void asciify_reverse_node_list(macro *m, node *n)
+{
+ if (n == 0)
+ return;
+ asciify_reverse_node_list(m, n->next);
+ n->asciify(m);
+}
+
+void dbreak_node::asciify(macro *m)
+{
+ asciify_reverse_node_list(m, none);
+ none = 0;
+ delete this;
+}
+
+void ligature_node::asciify(macro *m)
+{
+ n1->asciify(m);
+ n2->asciify(m);
+ n1 = n2 = 0;
+ delete this;
+}
+
+void break_char_node::asciify(macro *m)
+{
+ ch->asciify(m);
+ ch = 0;
+ delete this;
+}
+
+void italic_corrected_node::asciify(macro *m)
+{
+ n->asciify(m);
+ n = 0;
+ delete this;
+}
+
+void left_italic_corrected_node::asciify(macro *m)
+{
+ if (n) {
+ n->asciify(m);
+ n = 0;
+ }
+ delete this;
+}
+
+void hmotion_node::asciify(macro *m)
+{
+ if (was_tab) {
+ m->append('\t');
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+space_char_hmotion_node::space_char_hmotion_node(hunits i, color *c,
+ statem *s, int pop,
+ node *nxt)
+: hmotion_node(i, c, s, pop, nxt)
+{
+}
+
+space_char_hmotion_node::space_char_hmotion_node(hunits i, color *c,
+ node *nxt)
+: hmotion_node(i, c, 0, 0, nxt)
+{
+}
+
+void space_char_hmotion_node::asciify(macro *m)
+{
+ m->append(ESCAPE_SPACE);
+ delete this;
+}
+
+void space_node::asciify(macro *m)
+{
+ if (was_escape_colon) {
+ m->append(ESCAPE_COLON);
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+void word_space_node::asciify(macro *m)
+{
+ for (width_list *w = orig_width; w; w = w->next)
+ m->append(' ');
+ delete this;
+}
+
+void unbreakable_space_node::asciify(macro *m)
+{
+ m->append(ESCAPE_TILDE);
+ delete this;
+}
+
+void line_start_node::asciify(macro *)
+{
+ delete this;
+}
+
+void vertical_size_node::asciify(macro *)
+{
+ delete this;
+}
+
+breakpoint *node::get_breakpoints(hunits /*width*/, int /*nspaces*/,
+ breakpoint *rest, int /*is_inner*/)
+{
+ return rest;
+}
+
+int node::nbreaks()
+{
+ return 0;
+}
+
+breakpoint *space_node::get_breakpoints(hunits wd, int ns,
+ breakpoint *rest, int is_inner)
+{
+ if (next && next->discardable())
+ return rest;
+ breakpoint *bp = new breakpoint;
+ bp->next = rest;
+ bp->width = wd;
+ bp->nspaces = ns;
+ bp->hyphenated = 0;
+ if (is_inner) {
+ assert(rest != 0);
+ bp->index = rest->index + 1;
+ bp->nd = rest->nd;
+ }
+ else {
+ bp->nd = this;
+ bp->index = 0;
+ }
+ return bp;
+}
+
+int space_node::nbreaks()
+{
+ if (next && next->discardable())
+ return 0;
+ else
+ return 1;
+}
+
+static breakpoint *node_list_get_breakpoints(node *p, hunits *widthp,
+ int ns, breakpoint *rest)
+{
+ if (p != 0) {
+ rest = p->get_breakpoints(*widthp,
+ ns,
+ node_list_get_breakpoints(p->next, widthp, ns,
+ rest),
+ 1);
+ *widthp += p->width();
+ }
+ return rest;
+}
+
+breakpoint *dbreak_node::get_breakpoints(hunits wd, int ns,
+ breakpoint *rest, int is_inner)
+{
+ breakpoint *bp = new breakpoint;
+ bp->next = rest;
+ bp->width = wd;
+ for (node *tem = pre; tem != 0; tem = tem->next)
+ bp->width += tem->width();
+ bp->nspaces = ns;
+ bp->hyphenated = 1;
+ if (is_inner) {
+ assert(rest != 0);
+ bp->index = rest->index + 1;
+ bp->nd = rest->nd;
+ }
+ else {
+ bp->nd = this;
+ bp->index = 0;
+ }
+ return node_list_get_breakpoints(none, &wd, ns, bp);
+}
+
+int dbreak_node::nbreaks()
+{
+ int i = 1;
+ for (node *tem = none; tem != 0; tem = tem->next)
+ i += tem->nbreaks();
+ return i;
+}
+
+void node::split(int /*where*/, node ** /*prep*/, node ** /*postp*/)
+{
+ assert(0);
+}
+
+void space_node::split(int where, node **pre, node **post)
+{
+ assert(where == 0);
+ *pre = next;
+ *post = 0;
+ delete this;
+}
+
+static void node_list_split(node *p, int *wherep, node **prep, node **postp)
+{
+ if (p == 0)
+ return;
+ int nb = p->nbreaks();
+ node_list_split(p->next, wherep, prep, postp);
+ if (*wherep < 0) {
+ p->next = *postp;
+ *postp = p;
+ }
+ else if (*wherep < nb) {
+ p->next = *prep;
+ p->split(*wherep, prep, postp);
+ }
+ else {
+ p->next = *prep;
+ *prep = p;
+ }
+ *wherep -= nb;
+}
+
+void dbreak_node::split(int where, node **prep, node **postp)
+{
+ assert(where >= 0);
+ if (where == 0) {
+ *postp = post;
+ post = 0;
+ if (pre == 0)
+ *prep = next;
+ else {
+ node *tem;
+ for (tem = pre; tem->next != 0; tem = tem->next)
+ ;
+ tem->next = next;
+ *prep = pre;
+ }
+ pre = 0;
+ delete this;
+ }
+ else {
+ *prep = next;
+ where -= 1;
+ node_list_split(none, &where, prep, postp);
+ none = 0;
+ delete this;
+ }
+}
+
+hyphenation_type node::get_hyphenation_type()
+{
+ return HYPHEN_BOUNDARY;
+}
+
+hyphenation_type dbreak_node::get_hyphenation_type()
+{
+ return HYPHEN_INHIBIT;
+}
+
+hyphenation_type kern_pair_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type dummy_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type transparent_dummy_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type hmotion_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type space_char_hmotion_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type overstrike_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type space_node::get_hyphenation_type()
+{
+ if (was_escape_colon)
+ return HYPHEN_MIDDLE;
+ return HYPHEN_BOUNDARY;
+}
+
+hyphenation_type unbreakable_space_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+int node::interpret(macro *)
+{
+ return 0;
+}
+
+special_node::special_node(const macro &m, int n)
+: mac(m), no_init_string(n)
+{
+ font_size fs = curenv->get_font_size();
+ int char_height = curenv->get_char_height();
+ int char_slant = curenv->get_char_slant();
+ int fontno = env_definite_font(curenv);
+ tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, fontno);
+ if (curenv->is_composite())
+ tf = tf->get_plain();
+ gcol = curenv->get_glyph_color();
+ fcol = curenv->get_fill_color();
+ is_special = 1;
+}
+
+special_node::special_node(const macro &m, tfont *t,
+ color *gc, color *fc,
+ statem *s, int pop,
+ int n)
+: node(0, s, pop), mac(m), tf(t), gcol(gc), fcol(fc), no_init_string(n)
+{
+ is_special = 1;
+}
+
+int special_node::same(node *n)
+{
+ return mac == ((special_node *)n)->mac
+ && tf == ((special_node *)n)->tf
+ && gcol == ((special_node *)n)->gcol
+ && fcol == ((special_node *)n)->fcol
+ && no_init_string == ((special_node *)n)->no_init_string;
+}
+
+const char *special_node::type()
+{
+ return "special_node";
+}
+
+int special_node::ends_sentence()
+{
+ return 2;
+}
+
+int special_node::force_tprint()
+{
+ return 0;
+}
+
+int special_node::is_tag()
+{
+ return 0;
+}
+
+node *special_node::copy()
+{
+ return new special_node(mac, tf, gcol, fcol, state, div_nest_level,
+ no_init_string);
+}
+
+void special_node::tprint_start(troff_output_file *out)
+{
+ out->start_special(tf, gcol, fcol, no_init_string);
+}
+
+void special_node::tprint_char(troff_output_file *out, unsigned char c)
+{
+ out->special_char(c);
+}
+
+void special_node::tprint_end(troff_output_file *out)
+{
+ out->end_special();
+}
+
+tfont *special_node::get_tfont()
+{
+ return tf;
+}
+
+/* suppress_node */
+
+suppress_node::suppress_node(int on_or_off, int issue_limits)
+: is_on(on_or_off), emit_limits(issue_limits), filename(0), position(0),
+ image_id(0)
+{
+}
+
+suppress_node::suppress_node(symbol f, char p, int id)
+: is_on(2), emit_limits(0), filename(f), position(p), image_id(id)
+{
+ is_special = 1;
+}
+
+suppress_node::suppress_node(int issue_limits, int on_or_off,
+ symbol f, char p, int id,
+ statem *s, int pop)
+: node(0, s, pop), is_on(on_or_off), emit_limits(issue_limits), filename(f),
+ position(p), image_id(id)
+{
+}
+
+int suppress_node::same(node *n)
+{
+ return ((is_on == ((suppress_node *)n)->is_on)
+ && (emit_limits == ((suppress_node *)n)->emit_limits)
+ && (filename == ((suppress_node *)n)->filename)
+ && (position == ((suppress_node *)n)->position)
+ && (image_id == ((suppress_node *)n)->image_id));
+}
+
+const char *suppress_node::type()
+{
+ return "suppress_node";
+}
+
+node *suppress_node::copy()
+{
+ return new suppress_node(emit_limits, is_on, filename, position, image_id,
+ state, div_nest_level);
+}
+
+/* tag_node */
+
+tag_node::tag_node()
+: delayed(0)
+{
+ is_special = 1;
+}
+
+tag_node::tag_node(string s, int delay)
+: tag_string(s), delayed(delay)
+{
+ is_special = !delay;
+}
+
+tag_node::tag_node(string s, statem *st, int pop, int delay)
+: node(0, st, pop), tag_string(s), delayed(delay)
+{
+ is_special = !delay;
+}
+
+node *tag_node::copy()
+{
+ return new tag_node(tag_string, state, div_nest_level, delayed);
+}
+
+void tag_node::tprint(troff_output_file *out)
+{
+ if (delayed)
+ out->add_to_tag_list(tag_string);
+ else
+ out->state.add_tag(out->fp, tag_string);
+}
+
+int tag_node::same(node *nd)
+{
+ return tag_string == ((tag_node *)nd)->tag_string
+ && delayed == ((tag_node *)nd)->delayed;
+}
+
+const char *tag_node::type()
+{
+ return "tag_node";
+}
+
+int tag_node::force_tprint()
+{
+ return !delayed;
+}
+
+int tag_node::is_tag()
+{
+ return !delayed;
+}
+
+int tag_node::ends_sentence()
+{
+ return 2;
+}
+
+// Get contents of register `p` as integer.
+// Used only by suppress_node::tprint().
+static int get_register(const char *p)
+{
+ assert(p != 0 /* nullptr */);
+ reg *r = (reg *)register_dictionary.lookup(p);
+ assert(r != 0 /* nullptr */);
+ units value;
+ assert(r->get_value(&value));
+ return int(value);
+}
+
+// Get contents of register `p` as string.
+// Used only by suppress_node::tprint().
+static const char *get_string(const char *p)
+{
+ assert(p != 0 /* nullptr */);
+ reg *r = (reg *)register_dictionary.lookup(p);
+ assert(r != 0 /* nullptr */);
+ return r->get_string();
+}
+
+void suppress_node::put(troff_output_file *out, const char *s)
+{
+ int i = 0;
+ while (s[i] != (char)0) {
+ out->special_char(s[i]);
+ i++;
+ }
+}
+
+/*
+ * We need to remember the start of the image and its name (\O5). But
+ * we won't always need this information; for instance, \O2 is used to
+ * produce a bounding box with no associated image or position thereof.
+ */
+
+static char last_position = 0;
+static const char *image_filename = "";
+static size_t image_filename_len = 0;
+static int subimage_counter = 0;
+
+/*
+ * tprint - if (is_on == 2)
+ * remember current position (l, r, c, i) and filename
+ * else
+ * if (emit_limits)
+ * if (html)
+ * emit image tag
+ * else
+ * emit postscript bounds for image
+ * else
+ * if (suppress boolean differs from current state)
+ * alter state
+ * reset registers
+ * record current page
+ * set low water mark.
+ */
+
+void suppress_node::tprint(troff_output_file *out)
+{
+ int current_page = topdiv->get_page_number();
+ // Does the node have an associated position and file name?
+ if (is_on == 2) {
+ // Save them for future bounding box limits.
+ last_position = position;
+ image_filename = strsave(filename.contents());
+ image_filename_len = strlen(image_filename);
+ }
+ else { // is_on = 0 or 1
+ // Now check whether the suppress node requires us to issue limits.
+ if (emit_limits) {
+ const size_t namebuflen = 8192;
+ char name[namebuflen] = { '\0' };
+ // Jump through a flaming hoop to avoid a "format nonliteral"
+ // warning from blindly using sprintf...and avoid trouble from
+ // mischievous image stems.
+ //
+ // Keep this format string synced with pre-html:makeFileName().
+ const char format[] = "%d";
+ const size_t format_len = strlen(format);
+ const char *percent_position = strstr(image_filename, format);
+ if (percent_position) {
+ subimage_counter++;
+ assert(sizeof subimage_counter <= 8);
+ // A 64-bit signed int produces up to 19 decimal digits.
+ char *subimage_number = (char *)malloc(20); // 19 digits + \0
+ if (0 == subimage_number)
+ fatal("memory allocation failure");
+ // Replace the %d in the filename with this number.
+ size_t enough = image_filename_len + 19 - format_len;
+ char *new_name = (char *)malloc(enough);
+ if (0 == new_name)
+ fatal("memory allocation failure");
+ ptrdiff_t prefix_length = percent_position - image_filename;
+ strncpy(new_name, image_filename, prefix_length);
+ sprintf(subimage_number, "%d", subimage_counter);
+ size_t number_length = strlen(subimage_number);
+ strcpy(new_name + prefix_length, subimage_number);
+ // Skip over the format in the source string.
+ const char *suffix_src = image_filename + prefix_length
+ + format_len;
+ char *suffix_dst = new_name + prefix_length + number_length;
+ strcpy(suffix_dst, suffix_src);
+ // Ensure the new string fits with room for a terminal '\0'.
+ const size_t len = strlen(new_name);
+ if (len > (namebuflen - 1))
+ error("constructed file name in suppressed output escape"
+ " sequence is too long (>= %1 bytes); skipping image",
+ (int)namebuflen);
+ else
+ strncpy(name, new_name, (namebuflen - 1));
+ free(new_name);
+ free(subimage_number);
+ }
+ else {
+ if (image_filename_len > (namebuflen - 1))
+ error("file name in suppressed output escape sequence is too"
+ " long (>= %1 bytes); skipping image", (int)namebuflen);
+ else
+ strcpy(name, image_filename);
+ }
+ if (is_html) {
+ switch (last_position) {
+ case 'c':
+ out->start_special();
+ put(out, "devtag:.centered-image");
+ break;
+ case 'r':
+ out->start_special();
+ put(out, "devtag:.right-image");
+ break;
+ case 'l':
+ out->start_special();
+ put(out, "devtag:.left-image");
+ break;
+ case 'i':
+ ;
+ default:
+ ;
+ }
+ out->end_special();
+ out->start_special();
+ put(out, "devtag:.auto-image ");
+ put(out, name);
+ out->end_special();
+ }
+ else {
+ // postscript (or other device)
+ if (suppress_start_page > 0
+ && (current_page != suppress_start_page))
+ error("suppression limit registers span more than a page;"
+ " grohtml-info for image %1 will be wrong", image_no);
+ // if (topdiv->get_page_number() != suppress_start_page)
+ // fprintf(stderr, "end of image and topdiv page = %d and"
+ // " suppress_start_page = %d\n",
+ // topdiv->get_page_number(), suppress_start_page);
+
+ // `name` will contain a "%d" in which the image_no is placed.
+ fprintf(stderr,
+ "grohtml-info:page %d %d %d %d %d %d %s %d %d"
+ " %s:%s\n",
+ topdiv->get_page_number(),
+ get_register("opminx"), get_register("opminy"),
+ get_register("opmaxx"), get_register("opmaxy"),
+ // page offset + line length
+ get_register(".o") + get_register(".l"),
+ name, hresolution, vresolution, get_string(".F"),
+ get_string(".c"));
+ fflush(stderr);
+ }
+ }
+ else { // We are not emitting limits.
+ if (is_on) {
+ out->on();
+ reset_output_registers();
+ }
+ else
+ out->off();
+ suppress_start_page = current_page;
+ }
+ } // is_on
+}
+
+int suppress_node::force_tprint()
+{
+ return is_on;
+}
+
+int suppress_node::is_tag()
+{
+ return is_on;
+}
+
+hunits suppress_node::width()
+{
+ return H0;
+}
+
+/* composite_node */
+
+class composite_node : public charinfo_node {
+ node *n;
+ tfont *tf;
+public:
+ composite_node(node *, charinfo *, tfont *, statem *, int, node * = 0);
+ ~composite_node();
+ node *copy();
+ hunits width();
+ node *last_char_node();
+ units size();
+ void tprint(troff_output_file *);
+ hyphenation_type get_hyphenation_type();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_self(node *, hyphen_list **);
+ tfont *get_tfont();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void vertical_extent(vunits *, vunits *);
+ vunits vertical_width();
+};
+
+composite_node::composite_node(node *p, charinfo *c, tfont *t, statem *s,
+ int pop, node *x)
+: charinfo_node(c, s, pop, x), n(p), tf(t)
+{
+}
+
+composite_node::~composite_node()
+{
+ delete_node_list(n);
+}
+
+node *composite_node::copy()
+{
+ return new composite_node(copy_node_list(n), ci, tf, state, div_nest_level);
+}
+
+hunits composite_node::width()
+{
+ hunits x;
+ if (tf->get_constant_space(&x))
+ return x;
+ x = H0;
+ for (node *tem = n; tem; tem = tem->next)
+ x += tem->width();
+ hunits offset;
+ if (tf->get_bold(&offset))
+ x += offset;
+ x += tf->get_track_kern();
+ return x;
+}
+
+node *composite_node::last_char_node()
+{
+ return this;
+}
+
+vunits composite_node::vertical_width()
+{
+ vunits v = V0;
+ for (node *tem = n; tem; tem = tem->next)
+ v += tem->vertical_width();
+ return v;
+}
+
+units composite_node::size()
+{
+ return tf->get_size().to_units();
+}
+
+hyphenation_type composite_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+void composite_node::asciify(macro *m)
+{
+ unsigned char c = ci->get_asciify_code();
+ if (c == 0)
+ c = ci->get_ascii_code();
+ if (c != 0) {
+ m->append(c);
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+void composite_node::ascii_print(ascii_output_file *ascii)
+{
+ unsigned char c = ci->get_ascii_code();
+ if (c != 0)
+ ascii->outc(c);
+ else
+ ascii->outs(ci->nm.contents());
+
+}
+
+hyphen_list *composite_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ (*count)++;
+ return new hyphen_list(ci->get_hyphenation_code(), tail);
+}
+
+node *composite_node::add_self(node *nn, hyphen_list **p)
+{
+ assert(ci->get_hyphenation_code() == (*p)->hyphenation_code);
+ next = nn;
+ nn = this;
+ if ((*p)->hyphen)
+ nn = nn->add_discretionary_hyphen();
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return nn;
+}
+
+tfont *composite_node::get_tfont()
+{
+ return tf;
+}
+
+node *reverse_node_list(node *n)
+{
+ node *r = 0;
+ while (n) {
+ node *tem = n;
+ n = n->next;
+ tem->next = r;
+ r = tem;
+ }
+ return r;
+}
+
+void composite_node::vertical_extent(vunits *minimum, vunits *maximum)
+{
+ n = reverse_node_list(n);
+ node_list_vertical_extent(n, minimum, maximum);
+ n = reverse_node_list(n);
+}
+
+width_list::width_list(hunits w, hunits s)
+: width(w), sentence_width(s), next(0)
+{
+}
+
+width_list::width_list(width_list *w)
+: width(w->width), sentence_width(w->sentence_width), next(0)
+{
+}
+
+word_space_node::word_space_node(hunits d, color *c, width_list *w, node *x)
+: space_node(d, c, x), orig_width(w), unformat(0)
+{
+}
+
+word_space_node::word_space_node(hunits d, int s, color *c, width_list *w,
+ int flag, statem *st, int pop, node *x)
+: space_node(d, s, 0, c, st, pop, x), orig_width(w), unformat(flag)
+{
+}
+
+word_space_node::~word_space_node()
+{
+ width_list *w = orig_width;
+ while (w != 0) {
+ width_list *tmp = w;
+ w = w->next;
+ delete tmp;
+ }
+}
+
+node *word_space_node::copy()
+{
+ assert(orig_width != 0);
+ width_list *w_old_curr = orig_width;
+ width_list *w_new_curr = new width_list(w_old_curr);
+ width_list *w_new = w_new_curr;
+ w_old_curr = w_old_curr->next;
+ while (w_old_curr != 0) {
+ w_new_curr->next = new width_list(w_old_curr);
+ w_new_curr = w_new_curr->next;
+ w_old_curr = w_old_curr->next;
+ }
+ return new word_space_node(n, set, col, w_new, unformat, state,
+ div_nest_level);
+}
+
+int word_space_node::set_unformat_flag()
+{
+ unformat = 1;
+ return 1;
+}
+
+void word_space_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->word_marker();
+ out->right(n);
+}
+
+int word_space_node::merge_space(hunits h, hunits sw, hunits ssw)
+{
+ n += h;
+ assert(orig_width != 0);
+ width_list *w = orig_width;
+ for (; w->next; w = w->next)
+ ;
+ w->next = new width_list(sw, ssw);
+ return 1;
+}
+
+unbreakable_space_node::unbreakable_space_node(hunits d, color *c, node *x)
+: word_space_node(d, c, 0, x)
+{
+}
+
+unbreakable_space_node::unbreakable_space_node(hunits d, int s,
+ color *c, statem *st, int pop,
+ node *x)
+: word_space_node(d, s, c, 0, 0, st, pop, x)
+{
+}
+
+node *unbreakable_space_node::copy()
+{
+ return new unbreakable_space_node(n, set, col, state, div_nest_level);
+}
+
+int unbreakable_space_node::force_tprint()
+{
+ return 0;
+}
+
+int unbreakable_space_node::is_tag()
+{
+ return 0;
+}
+
+breakpoint *unbreakable_space_node::get_breakpoints(hunits, int,
+ breakpoint *rest, int)
+{
+ return rest;
+}
+
+int unbreakable_space_node::nbreaks()
+{
+ return 0;
+}
+
+void unbreakable_space_node::split(int, node **, node **)
+{
+ assert(0);
+}
+
+int unbreakable_space_node::merge_space(hunits, hunits, hunits)
+{
+ return 0;
+}
+
+hvpair::hvpair()
+{
+}
+
+draw_node::draw_node(char c, hvpair *p, int np, font_size s,
+ color *gc, color *fc)
+: npoints(np), sz(s), gcol(gc), fcol(fc), code(c)
+{
+ point = new hvpair[npoints];
+ for (int i = 0; i < npoints; i++)
+ point[i] = p[i];
+}
+
+draw_node::draw_node(char c, hvpair *p, int np, font_size s,
+ color *gc, color *fc, statem *st, int pop)
+: node(0, st, pop), npoints(np), sz(s), gcol(gc), fcol(fc), code(c)
+{
+ point = new hvpair[npoints];
+ for (int i = 0; i < npoints; i++)
+ point[i] = p[i];
+}
+
+int draw_node::same(node *n)
+{
+ draw_node *nd = (draw_node *)n;
+ if (code != nd->code || npoints != nd->npoints || sz != nd->sz
+ || gcol != nd->gcol || fcol != nd->fcol)
+ return 0;
+ for (int i = 0; i < npoints; i++)
+ if (point[i].h != nd->point[i].h || point[i].v != nd->point[i].v)
+ return 0;
+ return 1;
+}
+
+const char *draw_node::type()
+{
+ return "draw_node";
+}
+
+int draw_node::force_tprint()
+{
+ return 0;
+}
+
+int draw_node::is_tag()
+{
+ return 0;
+}
+
+draw_node::~draw_node()
+{
+ if (point)
+ delete[] point;
+}
+
+hunits draw_node::width()
+{
+ hunits x = H0;
+ for (int i = 0; i < npoints; i++)
+ x += point[i].h;
+ return x;
+}
+
+vunits draw_node::vertical_width()
+{
+ if (code == 'e')
+ return V0;
+ vunits x = V0;
+ for (int i = 0; i < npoints; i++)
+ x += point[i].v;
+ return x;
+}
+
+node *draw_node::copy()
+{
+ return new draw_node(code, point, npoints, sz, gcol, fcol, state,
+ div_nest_level);
+}
+
+void draw_node::tprint(troff_output_file *out)
+{
+ out->draw(code, point, npoints, sz, gcol, fcol);
+}
+
+/* tprint methods */
+
+void glyph_node::tprint(troff_output_file *out)
+{
+ tfont *ptf = tf->get_plain();
+ if (ptf == tf)
+ out->put_char_width(ci, ptf, gcol, fcol, width(), H0);
+ else {
+ hunits offset;
+ int bold = tf->get_bold(&offset);
+ hunits w = ptf->get_width(ci);
+ hunits k = H0;
+ hunits x;
+ int cs = tf->get_constant_space(&x);
+ if (cs) {
+ x -= w;
+ if (bold)
+ x -= offset;
+ hunits x2 = x/2;
+ out->right(x2);
+ k = x - x2;
+ }
+ else
+ k = tf->get_track_kern();
+ if (bold) {
+ out->put_char(ci, ptf, gcol, fcol);
+ out->right(offset);
+ }
+ out->put_char_width(ci, ptf, gcol, fcol, w, k);
+ }
+}
+
+void glyph_node::zero_width_tprint(troff_output_file *out)
+{
+ tfont *ptf = tf->get_plain();
+ hunits offset;
+ int bold = tf->get_bold(&offset);
+ hunits x;
+ int cs = tf->get_constant_space(&x);
+ if (cs) {
+ x -= ptf->get_width(ci);
+ if (bold)
+ x -= offset;
+ x = x/2;
+ out->right(x);
+ }
+ out->put_char(ci, ptf, gcol, fcol);
+ if (bold) {
+ out->right(offset);
+ out->put_char(ci, ptf, gcol, fcol);
+ out->right(-offset);
+ }
+ if (cs)
+ out->right(-x);
+}
+
+void break_char_node::tprint(troff_output_file *t)
+{
+ ch->tprint(t);
+}
+
+void break_char_node::zero_width_tprint(troff_output_file *t)
+{
+ ch->zero_width_tprint(t);
+}
+
+void hline_node::tprint(troff_output_file *out)
+{
+ if (x < H0) {
+ out->right(x);
+ x = -x;
+ }
+ if (n == 0) {
+ out->right(x);
+ return;
+ }
+ hunits w = n->width();
+ if (w <= H0) {
+ error("horizontal line drawing character must have positive width");
+ out->right(x);
+ return;
+ }
+ int i = int(x/w);
+ if (i == 0) {
+ hunits xx = x - w;
+ hunits xx2 = xx/2;
+ out->right(xx2);
+ if (out->is_on())
+ n->tprint(out);
+ out->right(xx - xx2);
+ }
+ else {
+ hunits rem = x - w*i;
+ if (rem > H0) {
+ if (n->overlaps_horizontally()) {
+ if (out->is_on())
+ n->tprint(out);
+ out->right(rem - w);
+ }
+ else
+ out->right(rem);
+ }
+ while (--i >= 0)
+ if (out->is_on())
+ n->tprint(out);
+ }
+}
+
+void vline_node::tprint(troff_output_file *out)
+{
+ if (n == 0) {
+ out->down(x);
+ return;
+ }
+ vunits h = n->size();
+ int overlaps = n->overlaps_vertically();
+ vunits y = x;
+ if (y < V0) {
+ y = -y;
+ int i = y / h;
+ vunits rem = y - i*h;
+ if (i == 0) {
+ out->right(n->width());
+ out->down(-rem);
+ }
+ else {
+ while (--i > 0) {
+ n->zero_width_tprint(out);
+ out->down(-h);
+ }
+ if (overlaps) {
+ n->zero_width_tprint(out);
+ out->down(-rem);
+ if (out->is_on())
+ n->tprint(out);
+ out->down(-h);
+ }
+ else {
+ if (out->is_on())
+ n->tprint(out);
+ out->down(-h - rem);
+ }
+ }
+ }
+ else {
+ int i = y / h;
+ vunits rem = y - i*h;
+ if (i == 0) {
+ out->down(rem);
+ out->right(n->width());
+ }
+ else {
+ out->down(h);
+ if (overlaps)
+ n->zero_width_tprint(out);
+ out->down(rem);
+ while (--i > 0) {
+ n->zero_width_tprint(out);
+ out->down(h);
+ }
+ if (out->is_on())
+ n->tprint(out);
+ }
+ }
+}
+
+void zero_width_node::tprint(troff_output_file *out)
+{
+ if (!n)
+ return;
+ if (!n->next) {
+ n->zero_width_tprint(out);
+ return;
+ }
+ int hpos = out->get_hpos();
+ int vpos = out->get_vpos();
+ node *tem = n;
+ while (tem) {
+ tem->tprint(out);
+ tem = tem->next;
+ }
+ out->moveto(hpos, vpos);
+}
+
+void overstrike_node::tprint(troff_output_file *out)
+{
+ hunits pos = H0;
+ for (node *tem = list; tem; tem = tem->next) {
+ hunits x = (max_width - tem->width())/2;
+ out->right(x - pos);
+ pos = x;
+ tem->zero_width_tprint(out);
+ }
+ out->right(max_width - pos);
+}
+
+void bracket_node::tprint(troff_output_file *out)
+{
+ if (list == 0)
+ return;
+ int npieces = 0;
+ node *tem;
+ for (tem = list; tem; tem = tem->next)
+ ++npieces;
+ vunits h = list->size();
+ vunits totalh = h*npieces;
+ vunits y = (totalh - h)/2;
+ out->down(y);
+ for (tem = list; tem; tem = tem->next) {
+ tem->zero_width_tprint(out);
+ out->down(-h);
+ }
+ out->right(max_width);
+ out->down(totalh - y);
+}
+
+void node::tprint(troff_output_file *)
+{
+}
+
+void node::zero_width_tprint(troff_output_file *out)
+{
+ int hpos = out->get_hpos();
+ int vpos = out->get_vpos();
+ tprint(out);
+ out->moveto(hpos, vpos);
+}
+
+void space_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->right(n);
+}
+
+void hmotion_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->right(n);
+}
+
+void space_char_hmotion_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ if (is_html) {
+ // we emit the space width as a negative glyph index
+ out->flush_tbuf();
+ out->do_motion();
+ out->put('N');
+ out->put(-n.to_units());
+ out->put('\n');
+ }
+ out->right(n);
+}
+
+void vmotion_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->down(n);
+}
+
+void kern_pair_node::tprint(troff_output_file *out)
+{
+ n1->tprint(out);
+ out->right(amount);
+ n2->tprint(out);
+}
+
+static void tprint_reverse_node_list(troff_output_file *out, node *n)
+{
+ if (n == 0)
+ return;
+ tprint_reverse_node_list(out, n->next);
+ n->tprint(out);
+}
+
+void dbreak_node::tprint(troff_output_file *out)
+{
+ tprint_reverse_node_list(out, none);
+}
+
+void composite_node::tprint(troff_output_file *out)
+{
+ hunits bold_offset;
+ int is_bold = tf->get_bold(&bold_offset);
+ hunits track_kern = tf->get_track_kern();
+ hunits constant_space;
+ int is_constant_spaced = tf->get_constant_space(&constant_space);
+ hunits x = H0;
+ if (is_constant_spaced) {
+ x = constant_space;
+ for (node *tem = n; tem; tem = tem->next)
+ x -= tem->width();
+ if (is_bold)
+ x -= bold_offset;
+ hunits x2 = x/2;
+ out->right(x2);
+ x -= x2;
+ }
+ if (is_bold) {
+ int hpos = out->get_hpos();
+ int vpos = out->get_vpos();
+ tprint_reverse_node_list(out, n);
+ out->moveto(hpos, vpos);
+ out->right(bold_offset);
+ }
+ tprint_reverse_node_list(out, n);
+ if (is_constant_spaced)
+ out->right(x);
+ else
+ out->right(track_kern);
+}
+
+static node *make_composite_node(charinfo *s, environment *env)
+{
+ int fontno = env_definite_font(env);
+ if (fontno < 0) {
+ error("cannot format composite glyph: no current font");
+ return 0;
+ }
+ assert(fontno < font_table_size && font_table[fontno] != 0);
+ node *n = charinfo_to_node_list(s, env);
+ font_size fs = env->get_font_size();
+ int char_height = env->get_char_height();
+ int char_slant = env->get_char_slant();
+ tfont *tf = font_table[fontno]->get_tfont(fs, char_height, char_slant,
+ fontno);
+ if (env->is_composite())
+ tf = tf->get_plain();
+ return new composite_node(n, s, tf, 0, 0, 0);
+}
+
+static node *make_glyph_node(charinfo *s, environment *env,
+ bool want_warnings = true)
+{
+ int fontno = env_definite_font(env);
+ if (fontno < 0) {
+ error("cannot format glyph: no current font");
+ return 0;
+ }
+ assert(fontno < font_table_size && font_table[fontno] != 0);
+ int fn = fontno;
+ int found = font_table[fontno]->contains(s);
+ if (!found) {
+ macro *mac = s->get_macro();
+ if (mac && s->is_fallback())
+ return make_composite_node(s, env);
+ if (s->numbered()) {
+ if (want_warnings)
+ warning(WARN_CHAR, "character code %1 not defined in current"
+ " font", s->get_number());
+ return 0;
+ }
+ special_font_list *sf = font_table[fontno]->sf;
+ while (sf != 0 && !found) {
+ fn = sf->n;
+ if (font_table[fn])
+ found = font_table[fn]->contains(s);
+ sf = sf->next;
+ }
+ if (!found) {
+ symbol f = font_table[fontno]->get_name();
+ string gl(f.contents());
+ gl += ' ';
+ gl += s->nm.contents();
+ gl += '\0';
+ charinfo *ci = get_charinfo(symbol(gl.contents()));
+ if (ci && ci->get_macro())
+ return make_composite_node(ci, env);
+ }
+ if (!found) {
+ sf = global_special_fonts;
+ while (sf != 0 && !found) {
+ fn = sf->n;
+ if (font_table[fn])
+ found = font_table[fn]->contains(s);
+ sf = sf->next;
+ }
+ }
+ if (!found)
+ if (mac && s->is_special())
+ return make_composite_node(s, env);
+ if (!found) {
+ for (fn = 0; fn < font_table_size; fn++)
+ if (font_table[fn]
+ && font_table[fn]->is_special()
+ && font_table[fn]->contains(s)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ if (want_warnings && s->first_time_not_found()) {
+ unsigned char input_code = s->get_ascii_code();
+ if (input_code != 0) {
+ if (csgraph(input_code))
+ warning(WARN_CHAR, "character '%1' not defined",
+ input_code);
+ else
+ warning(WARN_CHAR, "character with input code %1 not"
+ " defined", int(input_code));
+ }
+ else if (s->nm.contents()) {
+ const char *nm = s->nm.contents();
+ const char *backslash = (nm[1] == 0) ? "\\" : "";
+ warning(WARN_CHAR, "special character '%1%2' not defined",
+ backslash, nm);
+ }
+ }
+ return 0;
+ }
+ }
+ font_size fs = env->get_font_size();
+ int char_height = env->get_char_height();
+ int char_slant = env->get_char_slant();
+ tfont *tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, fn);
+ if (env->is_composite())
+ tf = tf->get_plain();
+ color *gcol = env->get_glyph_color();
+ color *fcol = env->get_fill_color();
+ return new glyph_node(s, tf, gcol, fcol, 0, 0);
+}
+
+node *make_node(charinfo *ci, environment *env)
+{
+ switch (ci->get_special_translation()) {
+ case charinfo::TRANSLATE_SPACE:
+ return new space_char_hmotion_node(env->get_space_width(),
+ env->get_fill_color());
+ case charinfo::TRANSLATE_STRETCHABLE_SPACE:
+ return new unbreakable_space_node(env->get_space_width(),
+ env->get_fill_color());
+ case charinfo::TRANSLATE_DUMMY:
+ return new dummy_node;
+ case charinfo::TRANSLATE_HYPHEN_INDICATOR:
+ error("translation to \\%% ignored in this context");
+ break;
+ }
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ macro *mac = ci->get_macro();
+ if (mac && ci->is_normal())
+ return make_composite_node(ci, env);
+ else
+ return make_glyph_node(ci, env);
+}
+
+bool character_exists(charinfo *ci, environment *env)
+{
+ if (ci->get_special_translation() != charinfo::TRANSLATE_NONE)
+ return true;
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ if (ci->get_macro())
+ return true;
+ node *nd = make_glyph_node(ci, env, false /* don't want warnings */);
+ if (nd) {
+ delete nd;
+ return true;
+ }
+ return false;
+}
+
+node *node::add_char(charinfo *ci, environment *env,
+ hunits *widthp, int *spacep, node **glyph_comp_np)
+{
+ node *res;
+ switch (ci->get_special_translation()) {
+ case charinfo::TRANSLATE_SPACE:
+ res = new space_char_hmotion_node(env->get_space_width(),
+ env->get_fill_color(), this);
+ *widthp += res->width();
+ return res;
+ case charinfo::TRANSLATE_STRETCHABLE_SPACE:
+ res = new unbreakable_space_node(env->get_space_width(),
+ env->get_fill_color(), this);
+ res->freeze_space();
+ *widthp += res->width();
+ *spacep += res->nspaces();
+ return res;
+ case charinfo::TRANSLATE_DUMMY:
+ return new dummy_node(this);
+ case charinfo::TRANSLATE_HYPHEN_INDICATOR:
+ return add_discretionary_hyphen();
+ }
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ macro *mac = ci->get_macro();
+ if (mac && ci->is_normal()) {
+ res = make_composite_node(ci, env);
+ if (res) {
+ res->next = this;
+ *widthp += res->width();
+ if (glyph_comp_np)
+ *glyph_comp_np = res;
+ }
+ else {
+ if (glyph_comp_np)
+ *glyph_comp_np = res;
+ return this;
+ }
+ }
+ else {
+ node *gn = make_glyph_node(ci, env);
+ if (gn == 0)
+ return this;
+ else {
+ hunits old_width = width();
+ node *p = gn->merge_self(this);
+ if (p == 0) {
+ *widthp += gn->width();
+ gn->next = this;
+ res = gn;
+ }
+ else {
+ *widthp += p->width() - old_width;
+ res = p;
+ }
+ if (glyph_comp_np)
+ *glyph_comp_np = res;
+ }
+ }
+ int break_code = 0;
+ if (ci->can_break_before())
+ break_code = CAN_BREAK_BEFORE;
+ if (ci->can_break_after())
+ break_code |= CAN_BREAK_AFTER;
+ if (ci->ignore_hcodes())
+ break_code |= IGNORE_HCODES;
+ if (ci->prohibit_break_before())
+ break_code = PROHIBIT_BREAK_BEFORE;
+ if (ci->prohibit_break_after())
+ break_code |= PROHIBIT_BREAK_AFTER;
+ if (ci->inter_char_space())
+ break_code |= INTER_CHAR_SPACE;
+ if (break_code) {
+ node *next1 = res->next;
+ res->next = 0;
+ res = new break_char_node(res, break_code, get_break_code(),
+ env->get_fill_color(), next1);
+ }
+ return res;
+}
+
+#ifdef __GNUG__
+inline
+#endif
+int same_node(node *n1, node *n2)
+{
+ if (n1 != 0) {
+ if (n2 != 0)
+ return n1->type() == n2->type() && n1->same(n2);
+ else
+ return 0;
+ }
+ else
+ return n2 == 0;
+}
+
+int same_node_list(node *n1, node *n2)
+{
+ while (n1 && n2) {
+ if (n1->type() != n2->type() || !n1->same(n2))
+ return 0;
+ n1 = n1->next;
+ n2 = n2->next;
+ }
+ return !n1 && !n2;
+}
+
+int extra_size_node::same(node *nd)
+{
+ return n == ((extra_size_node *)nd)->n;
+}
+
+const char *extra_size_node::type()
+{
+ return "extra_size_node";
+}
+
+int extra_size_node::force_tprint()
+{
+ return 0;
+}
+
+int extra_size_node::is_tag()
+{
+ return 0;
+}
+
+int vertical_size_node::same(node *nd)
+{
+ return n == ((vertical_size_node *)nd)->n;
+}
+
+const char *vertical_size_node::type()
+{
+ return "vertical_size_node";
+}
+
+int vertical_size_node::set_unformat_flag()
+{
+ return 0;
+}
+
+int vertical_size_node::force_tprint()
+{
+ return 0;
+}
+
+int vertical_size_node::is_tag()
+{
+ return 0;
+}
+
+int hmotion_node::same(node *nd)
+{
+ return n == ((hmotion_node *)nd)->n
+ && col == ((hmotion_node *)nd)->col;
+}
+
+const char *hmotion_node::type()
+{
+ return "hmotion_node";
+}
+
+int hmotion_node::set_unformat_flag()
+{
+ unformat = 1;
+ return 1;
+}
+
+int hmotion_node::force_tprint()
+{
+ return 0;
+}
+
+int hmotion_node::is_tag()
+{
+ return 0;
+}
+
+node *hmotion_node::add_self(node *nd, hyphen_list **p)
+{
+ next = nd;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *hmotion_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int space_char_hmotion_node::same(node *nd)
+{
+ return n == ((space_char_hmotion_node *)nd)->n
+ && col == ((space_char_hmotion_node *)nd)->col;
+}
+
+const char *space_char_hmotion_node::type()
+{
+ return "space_char_hmotion_node";
+}
+
+int space_char_hmotion_node::force_tprint()
+{
+ return 0;
+}
+
+int space_char_hmotion_node::is_tag()
+{
+ return 0;
+}
+
+node *space_char_hmotion_node::add_self(node *nd, hyphen_list **p)
+{
+ next = nd;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *space_char_hmotion_node::get_hyphen_list(hyphen_list *tail,
+ int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int vmotion_node::same(node *nd)
+{
+ return n == ((vmotion_node *)nd)->n
+ && col == ((vmotion_node *)nd)->col;
+}
+
+const char *vmotion_node::type()
+{
+ return "vmotion_node";
+}
+
+int vmotion_node::force_tprint()
+{
+ return 0;
+}
+
+int vmotion_node::is_tag()
+{
+ return 0;
+}
+
+int hline_node::same(node *nd)
+{
+ return x == ((hline_node *)nd)->x && same_node(n, ((hline_node *)nd)->n);
+}
+
+const char *hline_node::type()
+{
+ return "hline_node";
+}
+
+int hline_node::force_tprint()
+{
+ return 0;
+}
+
+int hline_node::is_tag()
+{
+ return 0;
+}
+
+int vline_node::same(node *nd)
+{
+ return x == ((vline_node *)nd)->x && same_node(n, ((vline_node *)nd)->n);
+}
+
+const char *vline_node::type()
+{
+ return "vline_node";
+}
+
+int vline_node::force_tprint()
+{
+ return 0;
+}
+
+int vline_node::is_tag()
+{
+ return 0;
+}
+
+int dummy_node::same(node * /*nd*/)
+{
+ return 1;
+}
+
+const char *dummy_node::type()
+{
+ return "dummy_node";
+}
+
+int dummy_node::force_tprint()
+{
+ return 0;
+}
+
+int dummy_node::is_tag()
+{
+ return 0;
+}
+
+int transparent_dummy_node::same(node * /*nd*/)
+{
+ return 1;
+}
+
+const char *transparent_dummy_node::type()
+{
+ return "transparent_dummy_node";
+}
+
+int transparent_dummy_node::force_tprint()
+{
+ return 0;
+}
+
+int transparent_dummy_node::is_tag()
+{
+ return 0;
+}
+
+int transparent_dummy_node::ends_sentence()
+{
+ return 2;
+}
+
+int zero_width_node::same(node *nd)
+{
+ return same_node_list(n, ((zero_width_node *)nd)->n);
+}
+
+const char *zero_width_node::type()
+{
+ return "zero_width_node";
+}
+
+int zero_width_node::force_tprint()
+{
+ return 0;
+}
+
+int zero_width_node::is_tag()
+{
+ return 0;
+}
+
+int italic_corrected_node::same(node *nd)
+{
+ return (x == ((italic_corrected_node *)nd)->x
+ && same_node(n, ((italic_corrected_node *)nd)->n));
+}
+
+const char *italic_corrected_node::type()
+{
+ return "italic_corrected_node";
+}
+
+int italic_corrected_node::force_tprint()
+{
+ return 0;
+}
+
+int italic_corrected_node::is_tag()
+{
+ return 0;
+}
+
+left_italic_corrected_node::left_italic_corrected_node(node *xx)
+: node(xx), n(0)
+{
+}
+
+left_italic_corrected_node::left_italic_corrected_node(statem *s, int pop,
+ node *xx)
+: node(xx, s, pop), n(0)
+{
+}
+
+left_italic_corrected_node::~left_italic_corrected_node()
+{
+ delete n;
+}
+
+node *left_italic_corrected_node::merge_glyph_node(glyph_node *gn)
+{
+ if (n == 0) {
+ hunits lic = gn->left_italic_correction();
+ if (!lic.is_zero()) {
+ x = lic;
+ n = gn;
+ return this;
+ }
+ }
+ else {
+ node *nd = n->merge_glyph_node(gn);
+ if (nd) {
+ n = nd;
+ x = n->left_italic_correction();
+ return this;
+ }
+ }
+ return 0;
+}
+
+node *left_italic_corrected_node::copy()
+{
+ left_italic_corrected_node *nd =
+ new left_italic_corrected_node(state, div_nest_level);
+ if (n) {
+ nd->n = n->copy();
+ nd->x = x;
+ }
+ return nd;
+}
+
+void left_italic_corrected_node::tprint(troff_output_file *out)
+{
+ if (n) {
+ out->right(x);
+ n->tprint(out);
+ }
+}
+
+const char *left_italic_corrected_node::type()
+{
+ return "left_italic_corrected_node";
+}
+
+int left_italic_corrected_node::force_tprint()
+{
+ return 0;
+}
+
+int left_italic_corrected_node::is_tag()
+{
+ return 0;
+}
+
+int left_italic_corrected_node::same(node *nd)
+{
+ return (x == ((left_italic_corrected_node *)nd)->x
+ && same_node(n, ((left_italic_corrected_node *)nd)->n));
+}
+
+void left_italic_corrected_node::ascii_print(ascii_output_file *out)
+{
+ if (n)
+ n->ascii_print(out);
+}
+
+hunits left_italic_corrected_node::width()
+{
+ return n ? n->width() + x : H0;
+}
+
+void left_italic_corrected_node::vertical_extent(vunits *minimum,
+ vunits *maximum)
+{
+ if (n)
+ n->vertical_extent(minimum, maximum);
+ else
+ node::vertical_extent(minimum, maximum);
+}
+
+hunits left_italic_corrected_node::skew()
+{
+ return n ? n->skew() + x/2 : H0;
+}
+
+hunits left_italic_corrected_node::subscript_correction()
+{
+ return n ? n->subscript_correction() : H0;
+}
+
+hunits left_italic_corrected_node::italic_correction()
+{
+ return n ? n->italic_correction() : H0;
+}
+
+int left_italic_corrected_node::ends_sentence()
+{
+ return n ? n->ends_sentence() : 0;
+}
+
+int left_italic_corrected_node::overlaps_horizontally()
+{
+ return n ? n->overlaps_horizontally() : 0;
+}
+
+int left_italic_corrected_node::overlaps_vertically()
+{
+ return n ? n->overlaps_vertically() : 0;
+}
+
+node *left_italic_corrected_node::last_char_node()
+{
+ return n ? n->last_char_node() : 0;
+}
+
+tfont *left_italic_corrected_node::get_tfont()
+{
+ return n ? n->get_tfont() : 0;
+}
+
+hyphenation_type left_italic_corrected_node::get_hyphenation_type()
+{
+ if (n)
+ return n->get_hyphenation_type();
+ else
+ return HYPHEN_MIDDLE;
+}
+
+hyphen_list *left_italic_corrected_node::get_hyphen_list(hyphen_list *tail,
+ int *count)
+{
+ return n ? n->get_hyphen_list(tail, count) : tail;
+}
+
+node *left_italic_corrected_node::add_self(node *nd, hyphen_list **p)
+{
+ if (n) {
+ nd = new left_italic_corrected_node(state, div_nest_level, nd);
+ nd = n->add_self(nd, p);
+ n = 0;
+ delete this;
+ return nd;
+ }
+ else {
+ next = nd;
+ return this;
+ }
+}
+
+int left_italic_corrected_node::character_type()
+{
+ return n ? n->character_type() : 0;
+}
+
+int overstrike_node::same(node *nd)
+{
+ return same_node_list(list, ((overstrike_node *)nd)->list);
+}
+
+const char *overstrike_node::type()
+{
+ return "overstrike_node";
+}
+
+int overstrike_node::force_tprint()
+{
+ return 0;
+}
+
+int overstrike_node::is_tag()
+{
+ return 0;
+}
+
+node *overstrike_node::add_self(node *n, hyphen_list **p)
+{
+ next = n;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *overstrike_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int bracket_node::same(node *nd)
+{
+ return same_node_list(list, ((bracket_node *)nd)->list);
+}
+
+const char *bracket_node::type()
+{
+ return "bracket_node";
+}
+
+int bracket_node::force_tprint()
+{
+ return 0;
+}
+
+int bracket_node::is_tag()
+{
+ return 0;
+}
+
+int composite_node::same(node *nd)
+{
+ return ci == ((composite_node *)nd)->ci
+ && same_node_list(n, ((composite_node *)nd)->n);
+}
+
+const char *composite_node::type()
+{
+ return "composite_node";
+}
+
+int composite_node::force_tprint()
+{
+ return 0;
+}
+
+int composite_node::is_tag()
+{
+ return 0;
+}
+
+int glyph_node::same(node *nd)
+{
+ return ci == ((glyph_node *)nd)->ci
+ && tf == ((glyph_node *)nd)->tf
+ && gcol == ((glyph_node *)nd)->gcol
+ && fcol == ((glyph_node *)nd)->fcol;
+}
+
+const char *glyph_node::type()
+{
+ return "glyph_node";
+}
+
+int glyph_node::force_tprint()
+{
+ return 0;
+}
+
+int glyph_node::is_tag()
+{
+ return 0;
+}
+
+int ligature_node::same(node *nd)
+{
+ return (same_node(n1, ((ligature_node *)nd)->n1)
+ && same_node(n2, ((ligature_node *)nd)->n2)
+ && glyph_node::same(nd));
+}
+
+const char *ligature_node::type()
+{
+ return "ligature_node";
+}
+
+int ligature_node::force_tprint()
+{
+ return 0;
+}
+
+int ligature_node::is_tag()
+{
+ return 0;
+}
+
+int kern_pair_node::same(node *nd)
+{
+ return (amount == ((kern_pair_node *)nd)->amount
+ && same_node(n1, ((kern_pair_node *)nd)->n1)
+ && same_node(n2, ((kern_pair_node *)nd)->n2));
+}
+
+const char *kern_pair_node::type()
+{
+ return "kern_pair_node";
+}
+
+int kern_pair_node::force_tprint()
+{
+ return 0;
+}
+
+int kern_pair_node::is_tag()
+{
+ return 0;
+}
+
+int dbreak_node::same(node *nd)
+{
+ return (same_node_list(none, ((dbreak_node *)nd)->none)
+ && same_node_list(pre, ((dbreak_node *)nd)->pre)
+ && same_node_list(post, ((dbreak_node *)nd)->post));
+}
+
+const char *dbreak_node::type()
+{
+ return "dbreak_node";
+}
+
+int dbreak_node::force_tprint()
+{
+ return 0;
+}
+
+int dbreak_node::is_tag()
+{
+ return 0;
+}
+
+int break_char_node::same(node *nd)
+{
+ return break_code == ((break_char_node *)nd)->break_code
+ && col == ((break_char_node *)nd)->col
+ && same_node(ch, ((break_char_node *)nd)->ch);
+}
+
+const char *break_char_node::type()
+{
+ return "break_char_node";
+}
+
+int break_char_node::force_tprint()
+{
+ return 0;
+}
+
+int break_char_node::is_tag()
+{
+ return 0;
+}
+
+int break_char_node::get_break_code()
+{
+ return break_code;
+}
+
+int line_start_node::same(node * /*nd*/)
+{
+ return 1;
+}
+
+const char *line_start_node::type()
+{
+ return "line_start_node";
+}
+
+int line_start_node::force_tprint()
+{
+ return 0;
+}
+
+int line_start_node::is_tag()
+{
+ return 0;
+}
+
+int space_node::same(node *nd)
+{
+ return n == ((space_node *)nd)->n
+ && set == ((space_node *)nd)->set
+ && col == ((space_node *)nd)->col;
+}
+
+const char *space_node::type()
+{
+ return "space_node";
+}
+
+int word_space_node::same(node *nd)
+{
+ return n == ((word_space_node *)nd)->n
+ && set == ((word_space_node *)nd)->set
+ && col == ((word_space_node *)nd)->col;
+}
+
+const char *word_space_node::type()
+{
+ return "word_space_node";
+}
+
+int word_space_node::force_tprint()
+{
+ return 0;
+}
+
+int word_space_node::is_tag()
+{
+ return 0;
+}
+
+void unbreakable_space_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ if (is_html) {
+ // we emit the space width as a negative glyph index
+ out->flush_tbuf();
+ out->do_motion();
+ out->put('N');
+ out->put(-n.to_units());
+ out->put('\n');
+ }
+ out->right(n);
+}
+
+int unbreakable_space_node::same(node *nd)
+{
+ return n == ((unbreakable_space_node *)nd)->n
+ && set == ((unbreakable_space_node *)nd)->set
+ && col == ((unbreakable_space_node *)nd)->col;
+}
+
+const char *unbreakable_space_node::type()
+{
+ return "unbreakable_space_node";
+}
+
+node *unbreakable_space_node::add_self(node *nd, hyphen_list **p)
+{
+ next = nd;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *unbreakable_space_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int diverted_space_node::same(node *nd)
+{
+ return n == ((diverted_space_node *)nd)->n;
+}
+
+const char *diverted_space_node::type()
+{
+ return "diverted_space_node";
+}
+
+int diverted_space_node::force_tprint()
+{
+ return 0;
+}
+
+int diverted_space_node::is_tag()
+{
+ return 0;
+}
+
+int diverted_copy_file_node::same(node *nd)
+{
+ return filename == ((diverted_copy_file_node *)nd)->filename;
+}
+
+const char *diverted_copy_file_node::type()
+{
+ return "diverted_copy_file_node";
+}
+
+int diverted_copy_file_node::force_tprint()
+{
+ return 0;
+}
+
+int diverted_copy_file_node::is_tag()
+{
+ return 0;
+}
+
+// Grow the font_table so that its size is > n.
+
+static void grow_font_table(int n)
+{
+ assert(n >= font_table_size);
+ font_info **old_font_table = font_table;
+ int old_font_table_size = font_table_size;
+ font_table_size = font_table_size ? (font_table_size*3)/2 : 10;
+ if (font_table_size <= n)
+ font_table_size = n + 10;
+ font_table = new font_info *[font_table_size];
+ if (old_font_table_size)
+ memcpy(font_table, old_font_table,
+ old_font_table_size*sizeof(font_info *));
+ delete[] old_font_table;
+ for (int i = old_font_table_size; i < font_table_size; i++)
+ font_table[i] = 0;
+}
+
+dictionary font_translation_dictionary(17);
+
+static symbol get_font_translation(symbol nm)
+{
+ void *p = font_translation_dictionary.lookup(nm);
+ return p ? symbol((char *)p) : nm;
+}
+
+dictionary font_dictionary(50);
+
+static bool mount_font_no_translate(int n, symbol name,
+ symbol external_name,
+ bool check_only = false)
+{
+ assert(n >= 0);
+ // We store the address of this char in `font_dictionary` to indicate
+ // that we've previously tried to mount the font and failed.
+ static char a_char;
+ font *fm = 0 /* nullptr */;
+ void *p = font_dictionary.lookup(external_name);
+ if (0 /* nullptr */ == p) {
+ fm = font::load_font(external_name.contents(), check_only);
+ if (check_only)
+ return fm != 0 /* nullptr */;
+ if (0 /* nullptr */ == fm) {
+ (void)font_dictionary.lookup(external_name, &a_char);
+ return false;
+ }
+ (void)font_dictionary.lookup(name, fm);
+ }
+ else if (p == &a_char) {
+ return false;
+ }
+ else
+ fm = (font*)p;
+ if (check_only)
+ return true;
+ if (n >= font_table_size) {
+ if (n - font_table_size > 1000) {
+ error("font position too much larger than first unused position");
+ return false;
+ }
+ grow_font_table(n);
+ }
+ else if (font_table[n] != 0 /* nullptr */)
+ delete font_table[n];
+ font_table[n] = new font_info(name, n, external_name, fm);
+ font_family::invalidate_fontno(n);
+ return true;
+}
+
+bool mount_font(int n, symbol name, symbol external_name)
+{
+ assert(n >= 0);
+ name = get_font_translation(name);
+ if (external_name.is_null())
+ external_name = name;
+ else
+ external_name = get_font_translation(external_name);
+ return mount_font_no_translate(n, name, external_name);
+}
+
+int check_font(symbol fam, symbol name)
+{
+ if (check_style(name))
+ name = concat(fam, name);
+ return mount_font_no_translate(0, name, name, true /* check only */);
+}
+
+int check_style(symbol s)
+{
+ int i = symbol_fontno(s);
+ return i < 0 ? 0 : font_table[i]->is_style();
+}
+
+bool mount_style(int n, symbol name)
+{
+ assert(n >= 0);
+ if (n >= font_table_size) {
+ if (n - font_table_size > 1000) {
+ error("font position too much larger than first unused position");
+ return false;
+ }
+ grow_font_table(n);
+ }
+ else if (font_table[n] != 0)
+ delete font_table[n];
+ font_table[n] = new font_info(get_font_translation(name), n,
+ NULL_SYMBOL, 0);
+ font_family::invalidate_fontno(n);
+ return true;
+}
+
+/* global functions */
+
+void font_translate()
+{
+ symbol from = get_name(true /* required */);
+ if (!from.is_null()) {
+ symbol to = get_name();
+ if (to.is_null() || from == to)
+ font_translation_dictionary.remove(from);
+ else
+ (void)font_translation_dictionary.lookup(from, (void *)to.contents());
+ }
+ skip_line();
+}
+
+void font_position()
+{
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ error("negative font position");
+ else {
+ symbol internal_name = get_name(true /* required */);
+ if (!internal_name.is_null()) {
+ symbol external_name = get_long_name();
+ if (!mount_font(n, internal_name, external_name)) {
+ string msg;
+ if (external_name != 0 /* nullptr */)
+ msg += string(" from file '") + external_name.contents()
+ + string("'");
+ msg += '\0';
+ error("cannot load font '%1'%2 for mounting",
+ internal_name.contents(), msg.contents());
+ }
+ }
+ }
+ }
+ skip_line();
+}
+
+font_family::font_family(symbol s)
+: map_size(10), nm(s)
+{
+ map = new int[map_size];
+ for (int i = 0; i < map_size; i++)
+ map[i] = -1;
+}
+
+font_family::~font_family()
+{
+ delete[] map;
+}
+
+// Resolve a requested font mounting position to a mounting position
+// usable by the output driver. (Positions 1 through 4 are typically
+// allocated to styles, and are not usable thus.) A return value of
+// `-1` indicates failure.
+int font_family::make_definite(int mounting_position)
+{
+ assert(mounting_position >= 0);
+ int pos = mounting_position;
+ if (pos < 0)
+ return -1;
+ if (pos < map_size && map[pos] >= 0)
+ return map[pos];
+ if (!(pos < font_table_size && font_table[pos] != 0))
+ return -1;
+ if (pos >= map_size) {
+ int old_map_size = map_size;
+ int *old_map = map;
+ map_size *= 3;
+ map_size /= 2;
+ if (pos >= map_size)
+ map_size = pos + 10;
+ map = new int[map_size];
+ memcpy(map, old_map, old_map_size * sizeof (int));
+ delete[] old_map;
+ for (int j = old_map_size; j < map_size; j++)
+ map[j] = -1;
+ }
+ if (!(font_table[pos]->is_style()))
+ return map[pos] = pos;
+ symbol sty = font_table[pos]->get_name();
+ symbol f = concat(nm, sty);
+ int n;
+ // Don't use symbol_fontno, because that might return a style and
+ // because we don't want to translate the name.
+ for (n = 0; n < font_table_size; n++)
+ if (font_table[n] != 0 && font_table[n]->is_named(f)
+ && !font_table[n]->is_style())
+ break;
+ if (n >= font_table_size) {
+ n = next_available_font_position();
+ if (!mount_font_no_translate(n, f, f))
+ return -1;
+ }
+ return map[pos] = n;
+}
+
+dictionary family_dictionary(5);
+
+font_family *lookup_family(symbol nm)
+{
+ font_family *f = (font_family *)family_dictionary.lookup(nm);
+ if (!f) {
+ f = new font_family(nm);
+ (void)family_dictionary.lookup(nm, f);
+ }
+ return f;
+}
+
+void font_family::invalidate_fontno(int n)
+{
+ assert(n >= 0 && n < font_table_size);
+ dictionary_iterator iter(family_dictionary);
+ symbol nam;
+ font_family *fam;
+ while (iter.get(&nam, (void **)&fam)) {
+ int mapsize = fam->map_size;
+ if (n < mapsize)
+ fam->map[n] = -1;
+ for (int i = 0; i < mapsize; i++)
+ if (fam->map[i] == n)
+ fam->map[i] = -1;
+ }
+}
+
+void style()
+{
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ error("negative font position");
+ else {
+ symbol internal_name = get_name(true /* required */);
+ if (!internal_name.is_null())
+ (void) mount_style(n, internal_name);
+ }
+ }
+ skip_line();
+}
+
+static void font_lookup_error(font_lookup_info& finfo,
+ const char *msg)
+{
+ if (finfo.requested_name)
+ error("cannot load font '%1' %2", finfo.requested_name, msg);
+ else
+ error("cannot load font at position %1 %2",
+ finfo.requested_position, msg);
+}
+
+// Read the next token and look it up as a font name or position number.
+// Return lookup success. Store, in the supplied struct argument, the
+// requested name or position, and the position actually resolved; -1
+// means not found (see `font_lookup_info` constructor).
+static bool has_font(font_lookup_info *finfo)
+{
+ int n;
+ tok.skip();
+ if (tok.usable_as_delimiter()) {
+ symbol s = get_name(true /* required */);
+ finfo->requested_name = (char *)s.contents();
+ if (!s.is_null()) {
+ n = symbol_fontno(s);
+ if (n < 0) {
+ n = next_available_font_position();
+ if (mount_font(n, s))
+ finfo->position = n;
+ }
+ finfo->position = curenv->get_family()->make_definite(n);
+ }
+ }
+ else if (get_integer(&n)) {
+ finfo->requested_position = n;
+ if (!(n < 0 || n >= font_table_size || font_table[n] == 0))
+ finfo->position = curenv->get_family()->make_definite(n);
+ }
+ return (finfo->position != -1);
+}
+
+static int underline_fontno = 2;
+
+void underline_font()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to make it the underline font");
+ else
+ underline_fontno = finfo.position;
+ skip_line();
+}
+
+int get_underline_fontno()
+{
+ return underline_fontno;
+}
+
+void define_font_special_character()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo)) {
+ font_lookup_error(finfo, "to define font-specific fallback glyph");
+ // Normally we skip the remainder of the line unconditionally at the
+ // end of a request-implementing function, but do_define_character()
+ // will eat the rest of it for us.
+ skip_line();
+ }
+ else {
+ symbol f = font_table[finfo.position]->get_name();
+ do_define_character(CHAR_FONT_SPECIAL, f.contents());
+ }
+}
+
+void remove_font_special_character()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to remove font-specific fallback glyph");
+ else {
+ symbol f = font_table[finfo.position]->get_name();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (!tok.is_space() && !tok.is_tab()) {
+ charinfo *s = tok.get_char(true /* required */);
+ string gl(f.contents());
+ gl += ' ';
+ gl += s->nm.contents();
+ gl += '\0';
+ charinfo *ci = get_charinfo(symbol(gl.contents()));
+ if (!ci)
+ break;
+ macro *m = ci->set_macro(0);
+ if (m)
+ delete m;
+ }
+ tok.next();
+ }
+ }
+ skip_line();
+}
+
+static void read_special_fonts(special_font_list **sp)
+{
+ special_font_list *s = *sp;
+ *sp = 0;
+ while (s != 0) {
+ special_font_list *tem = s;
+ s = s->next;
+ delete tem;
+ }
+ special_font_list **p = sp;
+ while (has_arg()) {
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to mark it as special");
+ else {
+ special_font_list *tem = new special_font_list;
+ tem->n = finfo.position;
+ tem->next = 0;
+ *p = tem;
+ p = &(tem->next);
+ }
+ }
+}
+
+void font_special_request()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to mark other fonts as special"
+ " contingently upon it"); // a mouthful :-/
+ else
+ read_special_fonts(&font_table[finfo.position]->sf);
+ skip_line();
+}
+
+void special_request()
+{
+ read_special_fonts(&global_special_fonts);
+ skip_line();
+}
+
+void font_zoom_request()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to set a zoom factor for it");
+ else {
+ int n = finfo.position;
+ if (font_table[n]->is_style())
+ warning(WARN_FONT, "can't set zoom factor for a style");
+ else {
+ int zoom;
+ if (has_arg() && get_integer(&zoom)) {
+ if (zoom < 0)
+ warning(WARN_FONT, "can't use negative zoom factor");
+ else
+ font_table[n]->set_zoom(zoom);
+ }
+ else
+ font_table[n]->set_zoom(0);
+ }
+ }
+ skip_line();
+}
+
+int next_available_font_position()
+{
+ int i;
+ for (i = 1; i < font_table_size && font_table[i] != 0; i++)
+ ;
+ return i;
+}
+
+int symbol_fontno(symbol s)
+{
+ s = get_font_translation(s);
+ for (int i = 0; i < font_table_size; i++)
+ if (font_table[i] != 0 && font_table[i]->is_named(s))
+ return i;
+ return -1;
+}
+
+int is_good_fontno(int n)
+{
+ return n >= 0 && n < font_table_size && font_table[n] != 0;
+}
+
+int get_bold_fontno(int n)
+{
+ if (n >= 0 && n < font_table_size && font_table[n] != 0) {
+ hunits offset;
+ if (font_table[n]->get_bold(&offset))
+ return offset.to_units() + 1;
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
+hunits env_digit_width(environment *env)
+{
+ node *n = make_glyph_node(charset_table['0'], env);
+ if (n) {
+ hunits x = n->width();
+ delete n;
+ return x;
+ }
+ else
+ return H0;
+}
+
+hunits env_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return scale(fs.to_units()/3, env->get_space_size(), 12);
+ else
+ return font_table[fn]->get_space_width(fs, env->get_space_size());
+}
+
+hunits env_sentence_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return scale(fs.to_units()/3, env->get_sentence_space_size(), 12);
+ else
+ return font_table[fn]->get_space_width(fs, env->get_sentence_space_size());
+}
+
+hunits env_half_narrow_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return 0;
+ else
+ return font_table[fn]->get_half_narrow_space_width(fs);
+}
+
+hunits env_narrow_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return 0;
+ else
+ return font_table[fn]->get_narrow_space_width(fs);
+}
+
+void bold_font()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "for emboldening");
+ else {
+ int n = finfo.position;
+ if (has_arg()) {
+ // This is a bit non-orthogonal, but faithful to CSTR #54. We can
+ // only conditionally embolden a font specified by name, not
+ // position, so ".bd S B 4" works but ".bd 5 3 4" does not. The
+ // latter bolds the font at position 5 unconditionally, and
+ // ignores the third argument.
+ if (tok.usable_as_delimiter()) {
+ font_lookup_info finfo2;
+ if (!has_font(&finfo2))
+ font_lookup_error(finfo2, "for conditional emboldening");
+ else {
+ int f = finfo2.position;
+ units offset;
+ if (has_arg() && get_number(&offset, 'u') && offset >= 1)
+ font_table[f]->set_conditional_bold(n, hunits(offset - 1));
+ else
+ font_table[f]->conditional_unbold(n);
+ }
+ }
+ else {
+ font_lookup_info finfo2;
+ if (!has_font(&finfo2))
+ font_lookup_error(finfo2, "for conditional emboldening");
+ units offset;
+ if (get_number(&offset, 'u') && offset >= 1)
+ font_table[n]->set_bold(hunits(offset - 1));
+ else
+ font_table[n]->unbold();
+ }
+ }
+ else
+ font_table[n]->unbold();
+ }
+ skip_line();
+}
+
+track_kerning_function::track_kerning_function() : non_zero(0)
+{
+}
+
+track_kerning_function::track_kerning_function(int min_s, hunits min_a,
+ int max_s, hunits max_a)
+: non_zero(1), min_size(min_s), min_amount(min_a), max_size(max_s),
+ max_amount(max_a)
+{
+}
+
+int track_kerning_function::operator==(const track_kerning_function &tk)
+{
+ if (non_zero)
+ return (tk.non_zero
+ && min_size == tk.min_size
+ && min_amount == tk.min_amount
+ && max_size == tk.max_size
+ && max_amount == tk.max_amount);
+ else
+ return !tk.non_zero;
+}
+
+int track_kerning_function::operator!=(const track_kerning_function &tk)
+{
+ if (non_zero)
+ return (!tk.non_zero
+ || min_size != tk.min_size
+ || min_amount != tk.min_amount
+ || max_size != tk.max_size
+ || max_amount != tk.max_amount);
+ else
+ return tk.non_zero;
+}
+
+hunits track_kerning_function::compute(int size)
+{
+ if (non_zero) {
+ if (max_size <= min_size)
+ return min_amount;
+ else if (size <= min_size)
+ return min_amount;
+ else if (size >= max_size)
+ return max_amount;
+ else
+ return (scale(max_amount, size - min_size, max_size - min_size)
+ + scale(min_amount, max_size - size, max_size - min_size));
+ }
+ else
+ return H0;
+}
+
+void track_kern()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "for track kerning");
+ else {
+ int n = finfo.position, min_s, max_s;
+ hunits min_a, max_a;
+ if (has_arg()
+ && get_number(&min_s, 'z')
+ && get_hunits(&min_a, 'p')
+ && get_number(&max_s, 'z')
+ && get_hunits(&max_a, 'p')) {
+ track_kerning_function tk(min_s, min_a, max_s, max_a);
+ font_table[n]->set_track_kern(tk);
+ }
+ else {
+ track_kerning_function tk;
+ font_table[n]->set_track_kern(tk);
+ }
+ }
+ skip_line();
+}
+
+void constant_space()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "for constant spacing");
+ else {
+ int n = finfo.position, x, y;
+ if (!has_arg() || !get_integer(&x))
+ font_table[n]->set_constant_space(CONSTANT_SPACE_NONE);
+ else {
+ if (!has_arg() || !get_number(&y, 'z'))
+ font_table[n]->set_constant_space(CONSTANT_SPACE_RELATIVE, x);
+ else
+ font_table[n]->set_constant_space(CONSTANT_SPACE_ABSOLUTE,
+ scale(y*x,
+ units_per_inch,
+ 36*72*sizescale));
+ }
+ }
+ skip_line();
+}
+
+void ligature()
+{
+ int lig;
+ if (has_arg() && get_integer(&lig) && lig >= 0 && lig <= 2)
+ global_ligature_mode = lig;
+ else
+ global_ligature_mode = 1;
+ skip_line();
+}
+
+void kern_request()
+{
+ int k;
+ if (has_arg() && get_integer(&k))
+ global_kern_mode = k != 0;
+ else
+ global_kern_mode = 1;
+ skip_line();
+}
+
+void set_soft_hyphen_char()
+{
+ soft_hyphen_char = get_optional_char();
+ if (!soft_hyphen_char)
+ soft_hyphen_char = get_charinfo(HYPHEN_SYMBOL);
+ skip_line();
+}
+
+void init_output()
+{
+ if (suppress_output_flag)
+ the_output = new suppress_output_file;
+ else if (ascii_output_flag)
+ the_output = new ascii_output_file;
+ else
+ the_output = new troff_output_file;
+}
+
+class next_available_font_position_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *next_available_font_position_reg::get_string()
+{
+ return i_to_a(next_available_font_position());
+}
+
+class printing_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *printing_reg::get_string()
+{
+ if (the_output)
+ return the_output->is_printing() ? "1" : "0";
+ else
+ return "0";
+}
+
+void init_node_requests()
+{
+ init_request("bd", bold_font);
+ init_request("cs", constant_space);
+ init_request("fp", font_position);
+ init_request("fschar", define_font_special_character);
+ init_request("fspecial", font_special_request);
+ init_request("fzoom", font_zoom_request);
+ init_request("ftr", font_translate);
+ init_request("kern", kern_request);
+ init_request("lg", ligature);
+ init_request("rfschar", remove_font_special_character);
+ init_request("shc", set_soft_hyphen_char);
+ init_request("special", special_request);
+ init_request("sty", style);
+ init_request("tkf", track_kern);
+ init_request("uf", underline_font);
+ register_dictionary.define(".fp", new next_available_font_position_reg);
+ register_dictionary.define(".kern",
+ new readonly_register(&global_kern_mode));
+ register_dictionary.define(".lg",
+ new readonly_register(&global_ligature_mode));
+ register_dictionary.define(".P", new printing_reg);
+ soft_hyphen_char = get_charinfo(HYPHEN_SYMBOL);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/node.h b/src/roff/troff/node.h
new file mode 100644
index 0000000..de82753
--- /dev/null
+++ b/src/roff/troff/node.h
@@ -0,0 +1,670 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct hyphen_list {
+ unsigned char hyphen;
+ unsigned char breakable;
+ unsigned char hyphenation_code;
+ hyphen_list *next;
+ hyphen_list(unsigned char code, hyphen_list *p = 0);
+};
+
+void hyphenate(hyphen_list *, unsigned);
+
+enum hyphenation_type { HYPHEN_MIDDLE, HYPHEN_BOUNDARY, HYPHEN_INHIBIT };
+
+class ascii_output_file;
+
+struct breakpoint;
+struct vertical_size;
+class charinfo;
+
+class macro;
+
+class troff_output_file;
+class tfont;
+class environment;
+
+class glyph_node;
+class diverted_space_node;
+class token_node;
+
+struct node {
+ node *next;
+ node *last;
+ statem *state;
+ statem *push_state;
+ int div_nest_level;
+ int is_special;
+ node();
+ node(node *);
+ node(node *, statem *, int);
+ node *add_char(charinfo *, environment *, hunits *, int *, node ** = 0);
+
+ virtual ~node();
+ virtual node *copy() = 0;
+ virtual int set_unformat_flag();
+ virtual int force_tprint() = 0;
+ virtual int is_tag() = 0;
+ virtual int get_break_code();
+ virtual hunits width();
+ virtual hunits subscript_correction();
+ virtual hunits italic_correction();
+ virtual hunits left_italic_correction();
+ virtual hunits skew();
+ virtual int nspaces();
+ virtual int merge_space(hunits, hunits, hunits);
+ virtual vunits vertical_width();
+ virtual node *last_char_node();
+ virtual void vertical_extent(vunits *, vunits *);
+ virtual int character_type();
+ virtual void set_vertical_size(vertical_size *);
+ virtual int ends_sentence();
+ virtual node *merge_self(node *);
+ virtual node *add_discretionary_hyphen();
+ virtual node *add_self(node *, hyphen_list **);
+ virtual hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ virtual void ascii_print(ascii_output_file *);
+ virtual void asciify(macro *);
+ virtual int discardable();
+ virtual void spread_space(int *, hunits *);
+ virtual void freeze_space();
+ virtual void is_escape_colon();
+ virtual breakpoint *get_breakpoints(hunits, int, breakpoint * = 0, int = 0);
+ virtual int nbreaks();
+ virtual void split(int, node **, node **);
+ virtual hyphenation_type get_hyphenation_type();
+ virtual int reread(int *);
+ virtual token_node *get_token_node();
+ virtual int overlaps_vertically();
+ virtual int overlaps_horizontally();
+ virtual units size();
+ virtual int interpret(macro *);
+
+ virtual node *merge_glyph_node(glyph_node *);
+ virtual tfont *get_tfont();
+ virtual color *get_glyph_color();
+ virtual color *get_fill_color();
+ virtual void tprint(troff_output_file *);
+ virtual void zero_width_tprint(troff_output_file *);
+
+ node *add_italic_correction(hunits *);
+
+ virtual int same(node *) = 0;
+ virtual const char *type() = 0;
+ virtual void debug_node();
+ virtual void debug_node_list();
+};
+
+inline node::node()
+: next(0), last(0), state(0), push_state(0), div_nest_level(0), is_special(0)
+{
+}
+
+inline node::node(node *n)
+: next(n), last(0), state(0), push_state(0), div_nest_level(0), is_special(0)
+{
+}
+
+inline node::node(node *n, statem *s, int divlevel)
+: next(n), last(0), push_state(0), div_nest_level(divlevel), is_special(0)
+{
+ if (s)
+ state = new statem(s);
+ else
+ state = 0;
+}
+
+inline node::~node()
+{
+ if (state != 0)
+ delete state;
+ if (push_state != 0)
+ delete push_state;
+}
+
+// 0 means it doesn't, 1 means it does, 2 means it's transparent
+
+int node_list_ends_sentence(node *);
+
+struct breakpoint {
+ breakpoint *next;
+ hunits width;
+ int nspaces;
+ node *nd;
+ int index;
+ char hyphenated;
+};
+
+class line_start_node : public node {
+public:
+ line_start_node() {}
+ node *copy() { return new line_start_node; }
+ int same(node *);
+ int force_tprint();
+ int is_tag();
+ const char *type();
+ void asciify(macro *);
+};
+
+class space_node : public node {
+private:
+protected:
+ hunits n;
+ char set;
+ char was_escape_colon;
+ color *col; /* for grotty */
+ space_node(hunits, int, int, color *, statem *, int, node * = 0);
+public:
+ space_node(hunits, color *, node * = 0);
+ node *copy();
+ int nspaces();
+ hunits width();
+ int discardable();
+ int merge_space(hunits, hunits, hunits);
+ void freeze_space();
+ void is_escape_colon();
+ void spread_space(int *, hunits *);
+ void tprint(troff_output_file *);
+ breakpoint *get_breakpoints(hunits, int, breakpoint * = 0, int = 0);
+ int nbreaks();
+ void split(int, node **, node **);
+ void ascii_print(ascii_output_file *);
+ int same(node *);
+ void asciify(macro *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hyphenation_type get_hyphenation_type();
+};
+
+struct width_list {
+ hunits width;
+ hunits sentence_width;
+ width_list *next;
+ width_list(hunits, hunits);
+ width_list(width_list *);
+};
+
+class word_space_node : public space_node {
+protected:
+ width_list *orig_width;
+ unsigned char unformat;
+ word_space_node(hunits, int, color *, width_list *, int, statem *, int,
+ node * = 0);
+public:
+ word_space_node(hunits, color *, width_list *, node * = 0);
+ ~word_space_node();
+ node *copy();
+ int reread(int *);
+ int set_unformat_flag();
+ void tprint(troff_output_file *);
+ int same(node *);
+ void asciify(macro *);
+ const char *type();
+ int merge_space(hunits, hunits, hunits);
+ int force_tprint();
+ int is_tag();
+};
+
+class unbreakable_space_node : public word_space_node {
+ unbreakable_space_node(hunits, int, color *, statem *, int, node * = 0);
+public:
+ unbreakable_space_node(hunits, color *, node * = 0);
+ node *copy();
+ int reread(int *);
+ void tprint(troff_output_file *);
+ int same(node *);
+ void asciify(macro *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ breakpoint *get_breakpoints(hunits, int, breakpoint * = 0, int = 0);
+ int nbreaks();
+ void split(int, node **, node **);
+ int merge_space(hunits, hunits, hunits);
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class diverted_space_node : public node {
+public:
+ vunits n;
+ diverted_space_node(vunits, node * = 0);
+ diverted_space_node(vunits, statem *, int, node * = 0);
+ node *copy();
+ int reread(int *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class diverted_copy_file_node : public node {
+ symbol filename;
+public:
+ vunits n;
+ diverted_copy_file_node(symbol, node * = 0);
+ diverted_copy_file_node(symbol, statem *, int, node * = 0);
+ node *copy();
+ int reread(int *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class extra_size_node : public node {
+ vunits n;
+public:
+ extra_size_node(vunits);
+ extra_size_node(vunits, statem *, int);
+ void set_vertical_size(vertical_size *);
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class vertical_size_node : public node {
+ vunits n;
+public:
+ vertical_size_node(vunits, statem *, int);
+ vertical_size_node(vunits);
+ void set_vertical_size(vertical_size *);
+ void asciify(macro *);
+ node *copy();
+ int set_unformat_flag();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class hmotion_node : public node {
+protected:
+ hunits n;
+ unsigned char was_tab;
+ unsigned char unformat;
+ color *col; /* for grotty */
+public:
+ hmotion_node(hunits i, color *c, node *nxt = 0)
+ : node(nxt), n(i), was_tab(0), unformat(0), col(c) {}
+ hmotion_node(hunits i, color *c, statem *s, int divlevel, node *nxt = 0)
+ : node(nxt, s, divlevel), n(i), was_tab(0), unformat(0), col(c) {}
+ hmotion_node(hunits i, int flag1, int flag2, color *c, statem *s,
+ int divlevel, node *nxt = 0)
+ : node(nxt, s, divlevel), n(i), was_tab(flag1), unformat(flag2),
+ col(c) {}
+ hmotion_node(hunits i, int flag1, int flag2, color *c, node *nxt = 0)
+ : node(nxt), n(i), was_tab(flag1), unformat(flag2), col(c) {}
+ node *copy();
+ int reread(int *);
+ int set_unformat_flag();
+ void asciify(macro *);
+ void tprint(troff_output_file *);
+ hunits width();
+ void ascii_print(ascii_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class space_char_hmotion_node : public hmotion_node {
+public:
+ space_char_hmotion_node(hunits, color *, node * = 0);
+ space_char_hmotion_node(hunits, color *, statem *, int, node * = 0);
+ node *copy();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class vmotion_node : public node {
+ vunits n;
+ color *col; /* for grotty */
+public:
+ vmotion_node(vunits, color *);
+ vmotion_node(vunits, color *, statem *, int);
+ void tprint(troff_output_file *);
+ node *copy();
+ vunits vertical_width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class hline_node : public node {
+ hunits x;
+ node *n;
+public:
+ hline_node(hunits, node *, node * = 0);
+ hline_node(hunits, node *, statem *, int, node * = 0);
+ ~hline_node();
+ node *copy();
+ hunits width();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class vline_node : public node {
+ vunits x;
+ node *n;
+public:
+ vline_node(vunits, node *, node * = 0);
+ vline_node(vunits, node *, statem *, int, node * = 0);
+ ~vline_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ hunits width();
+ vunits vertical_width();
+ void vertical_extent(vunits *, vunits *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class dummy_node : public node {
+public:
+ dummy_node(node *nd = 0) : node(nd) {}
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hyphenation_type get_hyphenation_type();
+};
+
+class transparent_dummy_node : public node {
+public:
+ transparent_dummy_node(node *nd = 0) : node(nd) {}
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int ends_sentence();
+ hyphenation_type get_hyphenation_type();
+};
+
+class zero_width_node : public node {
+ node *n;
+public:
+ zero_width_node(node *);
+ zero_width_node(node *, statem *, int);
+ ~zero_width_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void append(node *);
+ int character_type();
+ void vertical_extent(vunits *, vunits *);
+};
+
+class left_italic_corrected_node : public node {
+ node *n;
+ hunits x;
+public:
+ left_italic_corrected_node(node * = 0);
+ left_italic_corrected_node(statem *, int, node * = 0);
+ ~left_italic_corrected_node();
+ void tprint(troff_output_file *);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hunits width();
+ node *last_char_node();
+ void vertical_extent(vunits *, vunits *);
+ int ends_sentence();
+ int overlaps_horizontally();
+ int overlaps_vertically();
+ hyphenation_type get_hyphenation_type();
+ tfont *get_tfont();
+ int character_type();
+ hunits skew();
+ hunits italic_correction();
+ hunits subscript_correction();
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_self(node *, hyphen_list **);
+ node *merge_glyph_node(glyph_node *);
+};
+
+class overstrike_node : public node {
+ node *list;
+ hunits max_width;
+public:
+ overstrike_node();
+ overstrike_node(statem *, int);
+ ~overstrike_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ void overstrike(node *); // add another node to be overstruck
+ hunits width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class bracket_node : public node {
+ node *list;
+ hunits max_width;
+public:
+ bracket_node();
+ bracket_node(statem *, int);
+ ~bracket_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ void bracket(node *); // add another node to be overstruck
+ hunits width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class special_node : public node {
+ macro mac;
+ tfont *tf;
+ color *gcol;
+ color *fcol;
+ int no_init_string;
+ void tprint_start(troff_output_file *);
+ void tprint_char(troff_output_file *, unsigned char);
+ void tprint_end(troff_output_file *);
+public:
+ special_node(const macro &, int = 0);
+ special_node(const macro &, tfont *, color *, color *, statem *, int,
+ int = 0);
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int ends_sentence();
+ tfont *get_tfont();
+};
+
+class suppress_node : public node {
+ int is_on;
+ int emit_limits; // must we issue the extent of the area written out?
+ symbol filename;
+ char position;
+ int image_id;
+public:
+ suppress_node(int, int);
+ suppress_node(symbol, char, int);
+ suppress_node(int, int, symbol, char, int, statem *, int);
+ suppress_node(int, int, symbol, char, int);
+ node *copy();
+ void tprint(troff_output_file *);
+ hunits width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+private:
+ void put(troff_output_file *, const char *);
+};
+
+class tag_node : public node {
+public:
+ string tag_string;
+ int delayed;
+ tag_node();
+ tag_node(string, int);
+ tag_node(string, statem *, int, int);
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int ends_sentence();
+};
+
+struct hvpair {
+ hunits h;
+ vunits v;
+ hvpair();
+};
+
+class draw_node : public node {
+ int npoints;
+ font_size sz;
+ color *gcol;
+ color *fcol;
+ char code;
+ hvpair *point;
+public:
+ draw_node(char, hvpair *, int, font_size, color *, color *);
+ draw_node(char, hvpair *, int, font_size, color *, color *, statem *, int);
+ ~draw_node();
+ hunits width();
+ vunits vertical_width();
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class charinfo;
+node *make_node(charinfo *, environment *);
+bool character_exists(charinfo *, environment *);
+
+int same_node_list(node *, node *);
+node *reverse_node_list(node *);
+void delete_node_list(node *);
+node *copy_node_list(node *);
+
+int get_bold_fontno(int);
+
+inline hyphen_list::hyphen_list(unsigned char code, hyphen_list *p)
+: hyphen(0), breakable(0), hyphenation_code(code), next(p)
+{
+}
+
+extern void read_desc();
+extern bool mount_font(int, symbol, symbol = NULL_SYMBOL);
+extern int check_font(symbol, symbol);
+extern int check_style(symbol);
+extern bool mount_style(int, symbol);
+extern int is_good_fontno(int);
+extern int symbol_fontno(symbol);
+extern int next_available_font_position();
+extern void init_size_table(int *);
+extern int get_underline_fontno();
+
+class output_file {
+ char make_g_plus_plus_shut_up;
+public:
+ output_file();
+ bool is_dying;
+ virtual ~output_file();
+ virtual void trailer(vunits);
+ virtual void flush() = 0;
+ virtual void transparent_char(unsigned char) = 0;
+ virtual void print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits width) = 0;
+ virtual void begin_page(int pageno, vunits page_length) = 0;
+ virtual void copy_file(hunits x, vunits y, const char *filename) = 0;
+ virtual int is_printing() = 0;
+ virtual void put_filename(const char *, int);
+ virtual void on();
+ virtual void off();
+#ifdef COLUMN
+ virtual void vjustify(vunits, symbol);
+#endif /* COLUMN */
+ mtsm state;
+};
+
+#ifndef POPEN_MISSING
+extern char *pipe_command;
+#endif
+
+extern output_file *the_output;
+extern void init_output();
+int in_output_page_list(int);
+
+class font_family {
+ int *map;
+ int map_size;
+public:
+ const symbol nm;
+ font_family(symbol);
+ ~font_family();
+ int make_definite(int);
+ static void invalidate_fontno(int);
+};
+
+font_family *lookup_family(symbol);
+symbol get_font_name(int, environment *);
+symbol get_style_name(int);
+extern search_path include_search_path;
diff --git a/src/roff/troff/number.cpp b/src/roff/troff/number.cpp
new file mode 100644
index 0000000..348b557
--- /dev/null
+++ b/src/roff/troff/number.cpp
@@ -0,0 +1,712 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include "troff.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "token.h"
+#include "div.h"
+
+const vunits V0; // zero in vertical units
+const hunits H0; // zero in horizontal units
+
+int hresolution = 1;
+int vresolution = 1;
+int units_per_inch;
+int sizescale;
+
+static bool is_valid_expression(units *v, int scaling_unit,
+ bool is_parenthesized,
+ bool is_mandatory = false);
+static bool is_valid_expression_start();
+
+int get_vunits(vunits *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */)) {
+ *res = vunits(x);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int get_hunits(hunits *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */)) {
+ *res = hunits(x);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+// for \B
+
+int get_number_rigidly(units *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */,
+ true /* is_mandatory */)) {
+ *res = x;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int get_number(units *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */)) {
+ *res = x;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int get_integer(int *res)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, 0, false /* is_parenthesized */)) {
+ *res = x;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+enum incr_number_result { BAD, ABSOLUTE, INCREMENT, DECREMENT };
+
+static incr_number_result get_incr_number(units *res, unsigned char);
+
+int get_vunits(vunits *res, unsigned char si, vunits prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, si)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + v;
+ break;
+ case DECREMENT:
+ *res = prev_value - v;
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+int get_hunits(hunits *res, unsigned char si, hunits prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, si)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + v;
+ break;
+ case DECREMENT:
+ *res = prev_value - v;
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+int get_number(units *res, unsigned char si, units prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, si)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + v;
+ break;
+ case DECREMENT:
+ *res = prev_value - v;
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+int get_integer(int *res, int prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, 0)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + int(v);
+ break;
+ case DECREMENT:
+ *res = prev_value - int(v);
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+
+static incr_number_result get_incr_number(units *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return BAD;
+ incr_number_result result = ABSOLUTE;
+ if (tok.ch() == '+') {
+ tok.next();
+ result = INCREMENT;
+ }
+ else if (tok.ch() == '-') {
+ tok.next();
+ result = DECREMENT;
+ }
+ if (is_valid_expression(res, si, false /* is_parenthesized */))
+ return result;
+ else
+ return BAD;
+}
+
+static bool is_valid_expression_start()
+{
+ while (tok.is_space())
+ tok.next();
+ if (tok.is_newline()) {
+ warning(WARN_MISSING, "numeric expression missing");
+ return false;
+ }
+ if (tok.is_tab()) {
+ warning(WARN_TAB, "expected numeric expression, got %1",
+ tok.description());
+ return false;
+ }
+ if (tok.is_right_brace()) {
+ warning(WARN_RIGHT_BRACE, "expected numeric expression, got right"
+ "brace escape sequence");
+ return false;
+ }
+ return true;
+}
+
+enum { OP_LEQ = 'L', OP_GEQ = 'G', OP_MAX = 'X', OP_MIN = 'N' };
+
+#define SCALING_UNITS "icfPmnpuvMsz"
+
+static bool is_valid_term(units *v, int scaling_unit,
+ bool is_parenthesized, bool is_mandatory);
+
+static bool is_valid_expression(units *v, int scaling_unit,
+ bool is_parenthesized,
+ bool is_mandatory)
+{
+ int result = is_valid_term(v, scaling_unit, is_parenthesized,
+ is_mandatory);
+ while (result) {
+ if (is_parenthesized)
+ tok.skip();
+ int op = tok.ch();
+ switch (op) {
+ case '+':
+ case '-':
+ case '/':
+ case '*':
+ case '%':
+ case ':':
+ case '&':
+ tok.next();
+ break;
+ case '>':
+ tok.next();
+ if (tok.ch() == '=') {
+ tok.next();
+ op = OP_GEQ;
+ }
+ else if (tok.ch() == '?') {
+ tok.next();
+ op = OP_MAX;
+ }
+ break;
+ case '<':
+ tok.next();
+ if (tok.ch() == '=') {
+ tok.next();
+ op = OP_LEQ;
+ }
+ else if (tok.ch() == '?') {
+ tok.next();
+ op = OP_MIN;
+ }
+ break;
+ case '=':
+ tok.next();
+ if (tok.ch() == '=')
+ tok.next();
+ break;
+ default:
+ return result;
+ }
+ units v2;
+ if (!is_valid_term(&v2, scaling_unit, is_parenthesized,
+ is_mandatory))
+ return false;
+ int overflow = 0;
+ switch (op) {
+ case '<':
+ *v = *v < v2;
+ break;
+ case '>':
+ *v = *v > v2;
+ break;
+ case OP_LEQ:
+ *v = *v <= v2;
+ break;
+ case OP_GEQ:
+ *v = *v >= v2;
+ break;
+ case OP_MIN:
+ if (*v > v2)
+ *v = v2;
+ break;
+ case OP_MAX:
+ if (*v < v2)
+ *v = v2;
+ break;
+ case '=':
+ *v = *v == v2;
+ break;
+ case '&':
+ *v = *v > 0 && v2 > 0;
+ break;
+ case ':':
+ *v = *v > 0 || v2 > 0;
+ break;
+ case '+':
+ if (v2 < 0) {
+ if (*v < INT_MIN - v2)
+ overflow = 1;
+ }
+ else if (v2 > 0) {
+ if (*v > INT_MAX - v2)
+ overflow = 1;
+ }
+ if (overflow) {
+ error("addition overflow");
+ return false;
+ }
+ *v += v2;
+ break;
+ case '-':
+ if (v2 < 0) {
+ if (*v > INT_MAX + v2)
+ overflow = 1;
+ }
+ else if (v2 > 0) {
+ if (*v < INT_MIN + v2)
+ overflow = 1;
+ }
+ if (overflow) {
+ error("subtraction overflow");
+ return false;
+ }
+ *v -= v2;
+ break;
+ case '*':
+ if (v2 < 0) {
+ if (*v > 0) {
+ if ((unsigned)*v > -(unsigned)INT_MIN / -(unsigned)v2)
+ overflow = 1;
+ }
+ else if (-(unsigned)*v > INT_MAX / -(unsigned)v2)
+ overflow = 1;
+ }
+ else if (v2 > 0) {
+ if (*v > 0) {
+ if (*v > INT_MAX / v2)
+ overflow = 1;
+ }
+ else if (-(unsigned)*v > -(unsigned)INT_MIN / v2)
+ overflow = 1;
+ }
+ if (overflow) {
+ error("multiplication overflow");
+ return false;
+ }
+ *v *= v2;
+ break;
+ case '/':
+ if (v2 == 0) {
+ error("division by zero");
+ return false;
+ }
+ *v /= v2;
+ break;
+ case '%':
+ if (v2 == 0) {
+ error("modulus by zero");
+ return false;
+ }
+ *v %= v2;
+ break;
+ default:
+ assert(0 == "unhandled switch case while processing operator");
+ }
+ }
+ return result;
+}
+
+static bool is_valid_term(units *v, int scaling_unit,
+ bool is_parenthesized, bool is_mandatory)
+{
+ int negative = 0;
+ for (;;)
+ if (is_parenthesized && tok.is_space())
+ tok.next();
+ else if (tok.ch() == '+')
+ tok.next();
+ else if (tok.ch() == '-') {
+ tok.next();
+ negative = !negative;
+ }
+ else
+ break;
+ unsigned char c = tok.ch();
+ switch (c) {
+ case '|':
+ // | is not restricted to the outermost level
+ // tbl uses this
+ tok.next();
+ if (!is_valid_term(v, scaling_unit, is_parenthesized, is_mandatory))
+ return false;
+ int tem;
+ tem = (scaling_unit == 'v'
+ ? curdiv->get_vertical_position().to_units()
+ : curenv->get_input_line_position().to_units());
+ if (tem >= 0) {
+ if (*v < INT_MIN + tem) {
+ error("numeric overflow");
+ return false;
+ }
+ }
+ else {
+ if (*v > INT_MAX + tem) {
+ error("numeric overflow");
+ return false;
+ }
+ }
+ *v -= tem;
+ if (negative) {
+ if (*v == INT_MIN) {
+ error("numeric overflow");
+ return false;
+ }
+ *v = -*v;
+ }
+ return true;
+ case '(':
+ tok.next();
+ c = tok.ch();
+ if (c == ')') {
+ if (is_mandatory)
+ return false;
+ warning(WARN_SYNTAX, "empty parentheses");
+ tok.next();
+ *v = 0;
+ return true;
+ }
+ else if (c != 0 && strchr(SCALING_UNITS, c) != 0) {
+ tok.next();
+ if (tok.ch() == ';') {
+ tok.next();
+ scaling_unit = c;
+ }
+ else {
+ error("expected ';' after scaling unit, got %1",
+ tok.description());
+ return false;
+ }
+ }
+ else if (c == ';') {
+ scaling_unit = 0;
+ tok.next();
+ }
+ if (!is_valid_expression(v, scaling_unit,
+ true /* is_parenthesized */, is_mandatory))
+ return false;
+ tok.skip();
+ if (tok.ch() != ')') {
+ if (is_mandatory)
+ return false;
+ warning(WARN_SYNTAX, "expected ')', got %1", tok.description());
+ }
+ else
+ tok.next();
+ if (negative) {
+ if (*v == INT_MIN) {
+ error("numeric overflow");
+ return false;
+ }
+ *v = -*v;
+ }
+ return true;
+ case '.':
+ *v = 0;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ *v = 0;
+ do {
+ if (*v > INT_MAX/10) {
+ error("numeric overflow");
+ return false;
+ }
+ *v *= 10;
+ if (*v > INT_MAX - (int(c) - '0')) {
+ error("numeric overflow");
+ return false;
+ }
+ *v += c - '0';
+ tok.next();
+ c = tok.ch();
+ } while (csdigit(c));
+ break;
+ case '/':
+ case '*':
+ case '%':
+ case ':':
+ case '&':
+ case '>':
+ case '<':
+ case '=':
+ warning(WARN_SYNTAX, "empty left operand to '%1' operator", c);
+ *v = 0;
+ return is_mandatory ? false : true;
+ default:
+ warning(WARN_NUMBER, "expected numeric expression, got %1",
+ tok.description());
+ return false;
+ }
+ int divisor = 1;
+ if (tok.ch() == '.') {
+ tok.next();
+ for (;;) {
+ c = tok.ch();
+ if (!csdigit(c))
+ break;
+ // we may multiply the divisor by 254 later on
+ if (divisor <= INT_MAX/2540 && *v <= (INT_MAX - 9)/10) {
+ *v *= 10;
+ *v += c - '0';
+ divisor *= 10;
+ }
+ tok.next();
+ }
+ }
+ int si = scaling_unit;
+ int do_next = 0;
+ if ((c = tok.ch()) != 0 && strchr(SCALING_UNITS, c) != 0) {
+ switch (scaling_unit) {
+ case 0:
+ warning(WARN_SCALE, "scaling unit invalid in context");
+ break;
+ case 'z':
+ if (c != 'u' && c != 'z') {
+ warning(WARN_SCALE, "'%1' scaling unit invalid in context;"
+ " convert to 'z' or 'u'", c);
+ break;
+ }
+ si = c;
+ break;
+ case 'u':
+ si = c;
+ break;
+ default:
+ if (c == 'z') {
+ warning(WARN_SCALE, "'z' scaling unit invalid in context");
+ break;
+ }
+ si = c;
+ break;
+ }
+ // Don't do tok.next() here because the next token might be \s,
+ // which would affect the interpretation of m.
+ do_next = 1;
+ }
+ switch (si) {
+ case 'i':
+ *v = scale(*v, units_per_inch, divisor);
+ break;
+ case 'c':
+ *v = scale(*v, units_per_inch*100, divisor*254);
+ break;
+ case 0:
+ case 'u':
+ if (divisor != 1)
+ *v /= divisor;
+ break;
+ case 'f':
+ *v = scale(*v, 65536, divisor);
+ break;
+ case 'p':
+ *v = scale(*v, units_per_inch, divisor*72);
+ break;
+ case 'P':
+ *v = scale(*v, units_per_inch, divisor*6);
+ break;
+ case 'm':
+ {
+ // Convert to hunits so that with -Tascii 'm' behaves as in nroff.
+ hunits em = curenv->get_size();
+ *v = scale(*v, em.is_zero() ? hresolution : em.to_units(),
+ divisor);
+ }
+ break;
+ case 'M':
+ {
+ hunits em = curenv->get_size();
+ *v = scale(*v, em.is_zero() ? hresolution : em.to_units(),
+ (divisor * 100));
+ }
+ break;
+ case 'n':
+ {
+ // Convert to hunits so that with -Tascii 'n' behaves as in nroff.
+ hunits en = curenv->get_size() / 2;
+ *v = scale(*v, en.is_zero() ? hresolution : en.to_units(),
+ divisor);
+ }
+ break;
+ case 'v':
+ *v = scale(*v, curenv->get_vertical_spacing().to_units(), divisor);
+ break;
+ case 's':
+ while (divisor > INT_MAX/(sizescale*72)) {
+ divisor /= 10;
+ *v /= 10;
+ }
+ *v = scale(*v, units_per_inch, divisor*sizescale*72);
+ break;
+ case 'z':
+ *v = scale(*v, sizescale, divisor);
+ break;
+ default:
+ assert(0 == "unhandled switch case when processing scaling unit");
+ }
+ if (do_next)
+ tok.next();
+ if (negative) {
+ if (*v == INT_MIN) {
+ error("numeric overflow");
+ return false;
+ }
+ *v = -*v;
+ }
+ return true;
+}
+
+units scale(units n, units x, units y)
+{
+ assert(x >= 0 && y > 0);
+ if (x == 0)
+ return 0;
+ if (n >= 0) {
+ if (n <= INT_MAX/x)
+ return (n*x)/y;
+ }
+ else {
+ if (-(unsigned)n <= -(unsigned)INT_MIN/x)
+ return (n*x)/y;
+ }
+ double res = n*double(x)/double(y);
+ if (res > INT_MAX) {
+ error("numeric overflow");
+ return INT_MAX;
+ }
+ else if (res < INT_MIN) {
+ error("numeric overflow");
+ return INT_MIN;
+ }
+ return int(res);
+}
+
+vunits::vunits(units x)
+{
+ // Don't depend on rounding direction when dividing negative integers.
+ if (vresolution == 1)
+ n = x;
+ else
+ n = (x < 0
+ ? -((-x + (vresolution / 2) - 1) / vresolution)
+ : (x + (vresolution / 2) - 1) / vresolution);
+}
+
+hunits::hunits(units x)
+{
+ // Don't depend on rounding direction when dividing negative integers.
+ if (hresolution == 1)
+ n = x;
+ else
+ n = (x < 0
+ ? -((-x + (hresolution / 2) - 1) / hresolution)
+ : (x + (hresolution / 2) - 1) / hresolution);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/reg.cpp b/src/roff/troff/reg.cpp
new file mode 100644
index 0000000..6b7689d
--- /dev/null
+++ b/src/roff/troff/reg.cpp
@@ -0,0 +1,479 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "troff.h"
+#include "dictionary.h"
+#include "token.h"
+#include "request.h"
+#include "reg.h"
+
+object_dictionary register_dictionary(101);
+
+bool reg::get_value(units * /*d*/)
+{
+ return false;
+}
+
+void reg::increment()
+{
+ error("can't increment read-only register");
+}
+
+void reg::decrement()
+{
+ error("can't decrement read-only register");
+}
+
+void reg::set_increment(units /*n*/)
+{
+ error("can't automatically increment read-only register");
+}
+
+void reg::alter_format(char /*f*/, int /*w*/)
+{
+ error("can't assign format of read-only register");
+}
+
+const char *reg::get_format()
+{
+ return "0";
+}
+
+void reg::set_value(units /*n*/)
+{
+ error("can't write read-only register");
+}
+
+general_reg::general_reg() : format('1'), width(0), inc(0)
+{
+}
+
+static char uppercase_array[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z',
+};
+
+static char lowercase_array[] = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z',
+};
+
+static const char *number_value_to_ascii(int value, char format, int width)
+{
+ static char buf[128]; // must be at least 21
+ switch(format) {
+ case '1':
+ if (width <= 0)
+ return i_to_a(value);
+ else if (width > int(sizeof(buf) - 2))
+ sprintf(buf, "%.*d", int(sizeof(buf) - 2), int(value));
+ else
+ sprintf(buf, "%.*d", width, int(value));
+ break;
+ case 'i':
+ case 'I':
+ {
+ char *p = buf;
+ // troff uses z and w to represent 10000 and 5000 in Roman
+ // numerals; I can find no historical basis for this usage
+ const char *s = format == 'i' ? "zwmdclxvi" : "ZWMDCLXVI";
+ int n = int(value);
+ if (n >= 40000 || n <= -40000) {
+ error("magnitude of '%1' too big for i or I format", n);
+ return i_to_a(n);
+ }
+ if (n == 0) {
+ *p++ = '0';
+ *p = 0;
+ break;
+ }
+ if (n < 0) {
+ *p++ = '-';
+ n = -n;
+ }
+ while (n >= 10000) {
+ *p++ = s[0];
+ n -= 10000;
+ }
+ for (int i = 1000; i > 0; i /= 10, s += 2) {
+ int m = n/i;
+ n -= m*i;
+ switch (m) {
+ case 3:
+ *p++ = s[2];
+ /* falls through */
+ case 2:
+ *p++ = s[2];
+ /* falls through */
+ case 1:
+ *p++ = s[2];
+ break;
+ case 4:
+ *p++ = s[2];
+ *p++ = s[1];
+ break;
+ case 8:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 7:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 6:
+ *p++ = s[1];
+ *p++ = s[2];
+ break;
+ case 5:
+ *p++ = s[1];
+ break;
+ case 9:
+ *p++ = s[2];
+ *p++ = s[0];
+ }
+ }
+ *p = 0;
+ break;
+ }
+ case 'a':
+ case 'A':
+ {
+ int n = value;
+ char *p = buf;
+ if (n == 0) {
+ *p++ = '0';
+ *p = 0;
+ }
+ else {
+ if (n < 0) {
+ n = -n;
+ *p++ = '-';
+ }
+ // this is a bit tricky
+ while (n > 0) {
+ int d = n % 26;
+ if (d == 0)
+ d = 26;
+ n -= d;
+ n /= 26;
+ *p++ = format == 'a' ? lowercase_array[d - 1] :
+ uppercase_array[d - 1];
+ }
+ *p-- = 0;
+ char *q = buf[0] == '-' ? buf + 1 : buf;
+ while (q < p) {
+ char temp = *q;
+ *q = *p;
+ *p = temp;
+ --p;
+ ++q;
+ }
+ }
+ break;
+ }
+ default:
+ assert(0);
+ break;
+ }
+ return buf;
+}
+
+const char *general_reg::get_string()
+{
+ units n;
+ if (!get_value(&n))
+ return "";
+ return number_value_to_ascii(n, format, width);
+}
+
+
+void general_reg::increment()
+{
+ int n;
+ if (get_value(&n))
+ set_value(n + inc);
+}
+
+void general_reg::decrement()
+{
+ int n;
+ if (get_value(&n))
+ set_value(n - inc);
+}
+
+void general_reg::set_increment(units n)
+{
+ inc = n;
+}
+
+void general_reg::alter_format(char f, int w)
+{
+ format = f;
+ width = w;
+}
+
+static const char *number_format_to_ascii(char format, int width)
+{
+ static char buf[24];
+ if (format == '1') {
+ if (width > 0) {
+ int n = width;
+ if (n > int(sizeof(buf)) - 1)
+ n = int(sizeof(buf)) - 1;
+ sprintf(buf, "%.*d", n, 0);
+ return buf;
+ }
+ else
+ return "0";
+ }
+ else {
+ buf[0] = format;
+ buf[1] = '\0';
+ return buf;
+ }
+}
+
+const char *general_reg::get_format()
+{
+ return number_format_to_ascii(format, width);
+}
+
+class number_reg : public general_reg {
+ units value;
+public:
+ number_reg();
+ bool get_value(units *);
+ void set_value(units);
+};
+
+number_reg::number_reg() : value(0)
+{
+}
+
+bool number_reg::get_value(units *res)
+{
+ *res = value;
+ return true;
+}
+
+void number_reg::set_value(units n)
+{
+ value = n;
+}
+
+variable_reg::variable_reg(units *p) : ptr(p)
+{
+}
+
+void variable_reg::set_value(units n)
+{
+ *ptr = n;
+}
+
+bool variable_reg::get_value(units *res)
+{
+ *res = *ptr;
+ return true;
+}
+
+void define_number_reg()
+{
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ units v;
+ units prev_value;
+ if (!r || !r->get_value(&prev_value))
+ prev_value = 0;
+ if (get_number(&v, 'u', prev_value)) {
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ r->set_value(v);
+ if (tok.is_space() && has_arg() && get_number(&v, 'u'))
+ r->set_increment(v);
+ }
+ skip_line();
+}
+
+#if 0
+void inline_define_reg()
+{
+ token start;
+ start.next();
+ if (!start.delimiter(true /* report error */))
+ return;
+ tok.next();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null())
+ return;
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ units v;
+ units prev_value;
+ if (!r->get_value(&prev_value))
+ prev_value = 0;
+ if (get_number(&v, 'u', prev_value)) {
+ r->set_value(v);
+ if (start != tok) {
+ if (get_number(&v, 'u')) {
+ r->set_increment(v);
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ }
+ }
+ }
+}
+#endif
+
+void set_number_reg(symbol nm, units n)
+{
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ r->set_value(n);
+}
+
+reg *lookup_number_reg(symbol nm)
+{
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ warning(WARN_REG, "register '%1' not defined", nm.contents());
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ return r;
+}
+
+void alter_format()
+{
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ tok.skip();
+ char c = tok.ch();
+ if (csdigit(c)) {
+ int n = 0;
+ do {
+ ++n;
+ tok.next();
+ } while (csdigit(tok.ch()));
+ r->alter_format('1', n);
+ }
+ else if (c == 'i' || c == 'I' || c == 'a' || c == 'A')
+ r->alter_format(c);
+ else if (tok.is_newline() || tok.is_eof())
+ warning(WARN_MISSING, "missing register format");
+ else
+ if (!cscntrl(c))
+ error("invalid register format '%1'", c);
+ else
+ error("invalid register format (got %1)", tok.description());
+ skip_line();
+}
+
+void remove_reg()
+{
+ for (;;) {
+ symbol s = get_name();
+ if (s.is_null())
+ break;
+ register_dictionary.remove(s);
+ }
+ skip_line();
+}
+
+void alias_reg()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null()) {
+ if (!register_dictionary.alias(s1, s2))
+ warning(WARN_REG, "register '%1' not defined", s2.contents());
+ }
+ }
+ skip_line();
+}
+
+void rename_reg()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null())
+ register_dictionary.rename(s1, s2);
+ }
+ skip_line();
+}
+
+void print_number_regs()
+{
+ object_dictionary_iterator iter(register_dictionary);
+ reg *r;
+ symbol s;
+ while (iter.get(&s, (object **)&r)) {
+ assert(!s.is_null());
+ errprint("%1\t", s.contents());
+ const char *p = r->get_string();
+ if (p)
+ errprint(p);
+ errprint("\n");
+ }
+ fflush(stderr);
+ skip_line();
+}
+
+void init_reg_requests()
+{
+ init_request("rr", remove_reg);
+ init_request("nr", define_number_reg);
+ init_request("af", alter_format);
+ init_request("aln", alias_reg);
+ init_request("rnn", rename_reg);
+ init_request("pnr", print_number_regs);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/reg.h b/src/roff/troff/reg.h
new file mode 100644
index 0000000..132bcf1
--- /dev/null
+++ b/src/roff/troff/reg.h
@@ -0,0 +1,74 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+class reg : public object {
+public:
+ virtual const char *get_string() = 0;
+ virtual bool get_value(units *);
+ virtual void increment();
+ virtual void decrement();
+ virtual void set_increment(units);
+ virtual void alter_format(char f, int w = 0);
+ virtual const char *get_format();
+ virtual void set_value(units);
+};
+
+class readonly_register : public reg {
+ int *p;
+public:
+ readonly_register(int *);
+ const char *get_string();
+};
+
+class general_reg : public reg {
+ char format;
+ int width;
+ int inc;
+public:
+ general_reg();
+ const char *get_string();
+ void increment();
+ void decrement();
+ void alter_format(char f, int w = 0);
+ void set_increment(units);
+ const char *get_format();
+ void add_value(units);
+
+ void set_value(units) = 0;
+ bool get_value(units *) = 0;
+};
+
+class variable_reg : public general_reg {
+ units *ptr;
+public:
+ variable_reg(int *);
+ void set_value(units);
+ bool get_value(units *);
+};
+
+extern object_dictionary register_dictionary;
+extern void set_number_reg(symbol nm, units n);
+extern void check_output_limits(int x, int y);
+extern void reset_output_registers();
+
+reg *lookup_number_reg(symbol);
+#if 0
+void inline_define_reg();
+#endif
diff --git a/src/roff/troff/request.h b/src/roff/troff/request.h
new file mode 100644
index 0000000..0991e49
--- /dev/null
+++ b/src/roff/troff/request.h
@@ -0,0 +1,97 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+typedef void (*REQUEST_FUNCP)();
+
+class macro;
+
+class request_or_macro : public object {
+public:
+ request_or_macro();
+ virtual void invoke(symbol, bool) = 0;
+ virtual macro *to_macro();
+};
+
+class request : public request_or_macro {
+ REQUEST_FUNCP p;
+public:
+ void invoke(symbol, bool);
+ request(REQUEST_FUNCP);
+};
+
+void delete_request_or_macro(request_or_macro *);
+
+extern object_dictionary request_dictionary;
+
+class macro_header;
+struct node;
+
+class macro : public request_or_macro {
+ const char *filename; // where was it defined?
+ int lineno;
+ int len;
+ int empty_macro;
+ int is_a_diversion;
+ int is_a_string; // if it contains no newline
+public:
+ macro_header *p;
+ macro();
+ ~macro();
+ macro(const macro &);
+ macro(int);
+ macro &operator=(const macro &);
+ void append(unsigned char);
+ void append(node *);
+ void append_unsigned(unsigned int);
+ void append_int(int);
+ void append_str(const char *);
+ void set(unsigned char, int);
+ unsigned char get(int);
+ int length();
+ void invoke(symbol, bool);
+ macro *to_macro();
+ void print_size();
+ int empty();
+ int is_diversion();
+ int is_string();
+ void clear_string_flag();
+ friend class string_iterator;
+ friend void chop_macro();
+ friend void substring_request();
+ friend int operator==(const macro &, const macro &);
+};
+
+extern void init_input_requests();
+extern void init_markup_requests();
+extern void init_div_requests();
+extern void init_node_requests();
+extern void init_reg_requests();
+extern void init_env_requests();
+extern void init_hyphen_requests();
+extern void init_request(const char *, REQUEST_FUNCP);
+
+class charinfo;
+class environment;
+
+node *charinfo_to_node_list(charinfo *, const environment *);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/token.h b/src/roff/troff/token.h
new file mode 100644
index 0000000..bd76055
--- /dev/null
+++ b/src/roff/troff/token.h
@@ -0,0 +1,249 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+class charinfo;
+struct node;
+class vunits;
+
+class token {
+ symbol nm;
+ node *nd;
+ unsigned char c;
+ int val;
+ units dim;
+ enum token_type {
+ TOKEN_BACKSPACE,
+ TOKEN_BEGIN_TRAP,
+ TOKEN_CHAR, // a normal printing character
+ TOKEN_DUMMY, // \&
+ TOKEN_EMPTY, // this is the initial value
+ TOKEN_END_TRAP,
+ TOKEN_ESCAPE, // \e
+ TOKEN_HYPHEN_INDICATOR,
+ TOKEN_INTERRUPT, // \c
+ TOKEN_ITALIC_CORRECTION, // \/
+ TOKEN_LEADER, // ^A
+ TOKEN_LEFT_BRACE,
+ TOKEN_MARK_INPUT, // \k -- 'nm' is the name of the register
+ TOKEN_NEWLINE, // newline
+ TOKEN_NODE,
+ TOKEN_NUMBERED_CHAR,
+ TOKEN_PAGE_EJECTOR,
+ TOKEN_REQUEST,
+ TOKEN_RIGHT_BRACE,
+ TOKEN_SPACE, // ' ' -- ordinary space
+ TOKEN_SPECIAL, // a special character -- \' \` \- \(xx \[xxx]
+ TOKEN_SPREAD, // \p -- break and spread output line
+ TOKEN_STRETCHABLE_SPACE, // \~
+ TOKEN_UNSTRETCHABLE_SPACE, // '\ '
+ TOKEN_HORIZONTAL_SPACE, // \|, \^, \0, \h
+ TOKEN_TAB, // tab
+ TOKEN_TRANSPARENT, // \!
+ TOKEN_TRANSPARENT_DUMMY, // \)
+ TOKEN_ZERO_WIDTH_BREAK, // \:
+ TOKEN_EOF // end of file
+ } type;
+public:
+ token();
+ ~token();
+ token(const token &);
+ void operator=(const token &);
+ void next();
+ void process();
+ void skip();
+ int nspaces(); // 1 if space, 0 otherwise
+ bool is_eof();
+ bool is_space();
+ bool is_stretchable_space();
+ bool is_unstretchable_space();
+ bool is_horizontal_space();
+ bool is_white_space();
+ bool is_special();
+ bool is_newline();
+ bool is_tab();
+ bool is_leader();
+ bool is_backspace();
+ bool usable_as_delimiter(bool = false);
+ bool is_dummy();
+ bool is_transparent_dummy();
+ bool is_transparent();
+ bool is_left_brace();
+ bool is_right_brace();
+ bool is_page_ejector();
+ bool is_hyphen_indicator();
+ bool is_zero_width_break();
+ int operator==(const token &); // need this for delimiters, and for conditions
+ int operator!=(const token &); // ditto
+ unsigned char ch();
+ charinfo *get_char(bool = false);
+ int add_to_zero_width_node_list(node **);
+ void make_space();
+ void make_newline();
+ const char *description();
+
+ friend void process_input_stack();
+ friend node *do_overstrike();
+};
+
+extern token tok; // the current token
+
+extern symbol get_name(bool = false);
+extern symbol get_long_name(bool = false);
+extern charinfo *get_optional_char();
+extern char *read_string();
+extern void check_missing_character();
+extern void skip_line();
+extern void handle_initial_title();
+
+enum char_mode {
+ CHAR_NORMAL,
+ CHAR_FALLBACK,
+ CHAR_FONT_SPECIAL,
+ CHAR_SPECIAL
+};
+
+extern void do_define_character(char_mode, const char * = 0);
+
+class hunits;
+extern void read_title_parts(node **part, hunits *part_width);
+
+extern int get_number_rigidly(units *result, unsigned char si);
+
+extern int get_number(units *result, unsigned char si);
+extern int get_integer(int *result);
+
+extern int get_number(units *result, unsigned char si, units prev_value);
+extern int get_integer(int *result, int prev_value);
+
+void interpolate_number_reg(symbol, int);
+
+const char *asciify(int c);
+
+inline bool token::is_newline()
+{
+ return type == TOKEN_NEWLINE;
+}
+
+inline bool token::is_space()
+{
+ return type == TOKEN_SPACE;
+}
+
+inline bool token::is_stretchable_space()
+{
+ return type == TOKEN_STRETCHABLE_SPACE;
+}
+
+inline bool token::is_unstretchable_space()
+{
+ return type == TOKEN_UNSTRETCHABLE_SPACE;
+}
+
+inline bool token::is_horizontal_space()
+{
+ return type == TOKEN_HORIZONTAL_SPACE;
+}
+
+inline bool token::is_special()
+{
+ return type == TOKEN_SPECIAL;
+}
+
+inline int token::nspaces()
+{
+ return (int)(type == TOKEN_SPACE);
+}
+
+inline bool token::is_white_space()
+{
+ return type == TOKEN_SPACE || type == TOKEN_TAB;
+}
+
+inline bool token::is_transparent()
+{
+ return type == TOKEN_TRANSPARENT;
+}
+
+inline bool token::is_page_ejector()
+{
+ return type == TOKEN_PAGE_EJECTOR;
+}
+
+inline unsigned char token::ch()
+{
+ return type == TOKEN_CHAR ? c : 0;
+}
+
+inline bool token::is_eof()
+{
+ return type == TOKEN_EOF;
+}
+
+inline bool token::is_dummy()
+{
+ return type == TOKEN_DUMMY;
+}
+
+inline bool token::is_transparent_dummy()
+{
+ return type == TOKEN_TRANSPARENT_DUMMY;
+}
+
+inline bool token::is_left_brace()
+{
+ return type == TOKEN_LEFT_BRACE;
+}
+
+inline bool token::is_right_brace()
+{
+ return type == TOKEN_RIGHT_BRACE;
+}
+
+inline bool token::is_tab()
+{
+ return type == TOKEN_TAB;
+}
+
+inline bool token::is_leader()
+{
+ return type == TOKEN_LEADER;
+}
+
+inline bool token::is_backspace()
+{
+ return type == TOKEN_BACKSPACE;
+}
+
+inline bool token::is_hyphen_indicator()
+{
+ return type == TOKEN_HYPHEN_INDICATOR;
+}
+
+inline bool token::is_zero_width_break()
+{
+ return type == TOKEN_ZERO_WIDTH_BREAK;
+}
+
+bool has_arg();
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/troff.1.man b/src/roff/troff/troff.1.man
new file mode 100644
index 0000000..d83c4a5
--- /dev/null
+++ b/src/roff/troff/troff.1.man
@@ -0,0 +1,1047 @@
+'\" t
+.TH @g@troff @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@troff \- GNU
+.I roff
+typesetter and document formatter
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" This file is part of groff, the GNU roff type-setting system.
+.\"
+.\" Permission is granted to copy, distribute and/or modify this
+.\" document under the terms of the GNU Free Documentation License,
+.\" Version 1.3 or any later version published by the Free Software
+.\" Foundation; with no Invariant Sections, with no Front-Cover Texts,
+.\" and with no Back-Cover Texts.
+.\"
+.\" A copy of the Free Documentation License is included as a file
+.\" called FDL in the main directory of the groff source package.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_troff_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@troff
+.RB [ \-abcCEiRUz ]
+.RB [ \-d\~\c
+.IR ctext ]
+.RB [ \-d\~\c
+.IB string =\c
+.IR text ]
+.RB [ \-f\~\c
+.IR font-family ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-m\~\c
+.IR macro-package ]
+.RB [ \-M\~\c
+.IR macro-directory ]
+.RB [ \-n\~\c
+.IR page-number ]
+.RB [ \-o\~\c
+.IR page-list ]
+.RB [ \-r\~\c
+.IR cnumeric-expression ]
+.RB [ \-r\~\c
+.IB register =\c
+.IR numeric-expression ]
+.RB [ \-T\~\c
+.IR output-device ]
+.RB [ \-w\~\c
+.IR warning-category ]
+.RB [ \-W\~\c
+.IR warning-category ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@troff
+.B \-\-help
+.YS
+.
+.
+.SY @g@troff
+.B \-v
+.
+.SY @g@troff
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+GNU
+.I troff \" GNU
+transforms
+.MR groff @MAN7EXT@
+language input into the device-independent output format described in
+.MR groff_out @MAN5EXT@ ;
+.I @g@troff
+is thus the heart of the GNU
+.I roff
+document formatting system.
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+the standard input stream is read.
+.
+.
+.P
+GNU
+.I troff \" GNU
+is functionally compatible with the AT&T
+.I troff \" AT&T
+typesetter and features numerous extensions.
+.
+Many people prefer to use the
+.MR groff @MAN1EXT@
+command,
+a front end which also runs preprocessors and output drivers in the
+appropriate order and with appropriate options.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-a
+Generate a plain text approximation of the typeset output.
+.
+The read-only register
+.B .A
+is set to\~1.
+.
+This option produces a sort of abstract preview of the formatted output.
+.
+.
+.RS
+.IP \[bu] 2n
+Page breaks are marked by a phrase in angle brackets;
+for example,
+\[lq]<beginning of page>\[rq].
+.
+.
+.IP \[bu]
+Lines are broken where they would be in the formatted output.
+.
+.
+.IP \[bu]
+A horizontal motion of any size is represented as one space.
+.
+Adjacent horizontal motions are not combined.
+.
+Inter-sentence space nodes
+(those arising from the second argument to the
+.B .ss
+request)
+are not represented.
+.
+.
+.IP \[bu]
+Vertical motions are not represented.
+.
+.
+.IP \[bu]
+Special characters are rendered in angle brackets;
+for example,
+the default soft hyphen character appears as
+\[lq]<hy>\[rq].
+.RE
+.
+.
+.IP
+The above description should not be considered a specification;
+the details of
+.B \-a
+output are subject to change.
+.
+.
+.TP
+.B \-b
+Write a backtrace reporting the state of
+.IR @g@troff 's
+input parser to the standard error stream with each diagnostic message.
+.
+The line numbers given in the backtrace might not always be correct,
+because
+.IR @g@troff 's
+idea of line numbers can be confused by requests that append to
+.\" strings or (??? strings never contain newlines)
+macros.
+.
+.
+.TP
+.B \-c
+Start with color output disabled.
+.
+.
+.TP
+.B \-C
+Enable AT&T
+.I troff \" AT&T
+compatibility mode;
+implies
+.BR \-c .
+.
+See
+.MR groff_diff @MAN7EXT@ .
+.
+.
+.TP
+.BI \-d\~ ctext
+.TQ
+.BI \-d\~ string = text
+Define
+.I roff
+.RI string\~ c
+or
+.I string
+as
+.I text.
+.
+.IR c \~must
+be one character;
+.I string
+can be of arbitrary length.
+.
+Such string assignments happen before any macro file is loaded,
+including the startup file.
+.
+Due to
+.MR getopt_long 3
+limitations,
+.IR c\~ cannot
+be,
+and
+.I string
+cannot contain,
+an equals sign,
+even though that is a valid character in a
+.I roff
+identifier.
+.
+.
+.TP
+.B \-E
+Inhibit
+.I @g@troff
+error messages;
+implies
+.BR \-Ww .
+.
+This option does
+.I not
+suppress messages sent to the standard error stream by documents or
+macro packages using
+.B tm
+or related requests.
+.
+.
+.TP
+.BI \-f\~ fam
+Use
+.I fam
+as the default font family.
+.
+.
+.TP
+.BI \-F\~ dir
+Search in directory
+.I dir
+for the selected output device's directory of device and font
+description files.
+.
+See the description of
+.I GROFF_FONT_PATH
+in section \[lq]Environment\[rq] below for the default search locations
+and ordering.
+.
+.
+.TP
+.B \-i
+Read the standard input stream after all named input files have been
+processed.
+.
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+for files
+(those named on the command line;
+in
+.BR psbb ,
+.BR so ,
+and
+.B soquiet
+requests;
+and in
+.RB \[lq] "\[rs]X\[aq]ps: import\[aq]" \[rq],
+.RB \[lq] "\[rs]X\[aq]ps: file\[aq]" \[rq],
+and
+.RB \[lq] "\[rs]X\[aq]pdf: pdfpic\[aq]" \[rq]
+device control escape sequences).
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.B \-I
+works similarly to,
+and is named for,
+the \[lq]include\[rq]
+option of Unix C compilers.
+.
+.
+.TP
+.BI \-m\~ name
+Process the file
+.RI name .tmac
+prior to any input files.
+.
+If not found,
+.IR tmac. name
+is attempted.
+.
+.I name
+(in both arrangements)
+is presumed to be a macro file;
+see the description of
+.I GROFF_TMAC_PATH
+in section \[lq]Environment\[rq] below for the default search locations
+and ordering.
+.
+.
+.TP
+.BI \-M\~ dir
+Search directory
+.I dir
+for macro files.
+.
+See the description of
+.I GROFF_TMAC_PATH
+in section \[lq]Environment\[rq] below for the default search locations
+and ordering.
+.
+.
+.TP
+.BI \-n\~ num
+Begin numbering pages at
+.I num.
+.
+The default
+.RB is\~ 1 .
+.
+.
+.TP
+.BI \-o\~ list
+Output only pages in
+.I list,
+which is a comma-separated list of inclusive page ranges;
+.I n
+means page
+.I n,
+.IB m \- n
+means every page
+.RI between\~ m
+.RI and\~ n ,
+.BI \- n
+means every page up
+.RI to\~ n ,
+and
+.IB n \-
+means every page from
+.IR n \~on.
+.
+.I @g@troff
+stops processing and exits after formatting the last page enumerated in
+.I list.
+.
+.
+.TP
+.BI \-r\~ cnumeric-expression
+.TQ
+.BI \-r\~ register = numeric-expression
+Define
+.I roff
+.RI register\~ c
+or
+.I register
+as
+.I numeric-expression.
+.
+.IR c \~must
+be a one-character name;
+.I register
+can be of arbitrary length.
+.
+Such register assignments happen before any macro file is loaded,
+including the startup file.
+.
+Due to
+.MR getopt_long 3
+limitations,
+.IR c\~ cannot
+be,
+and
+.I register
+cannot contain,
+an equals sign,
+even though that is a valid character in a
+.I roff
+identifier.
+.
+.
+.TP
+.B \-R
+Don't load
+.I troffrc
+and
+.IR troffrc\-end .
+.
+.
+.TP
+.BI \-T\~ dev
+Prepare output for device
+.I dev.
+.
+The default is
+.BR @DEVICE@ ;
+see
+.MR groff @MAN1EXT@ .
+.
+.
+.TP
+.B \-U
+Operate in
+.I unsafe mode,
+enabling the
+.BR open ,
+.BR opena ,
+.BR pi ,
+.BR pso ,
+and
+.B sy
+requests,
+which are disabled by default because they allow an untrusted input
+document to write to arbitrary file names and run arbitrary commands.
+.
+This option also adds the current directory to the macro package search
+path;
+see the
+.B \-m
+and
+.B \-M
+options above.
+.
+.
+.TP
+.BI \-w\~ name
+.TQ
+.BI \-W\~ name
+Enable
+.RB ( \-w )
+or inhibit
+.RB ( \-W )
+warnings in category
+.I name.
+.
+See section \[lq]Warnings\[rq] below.
+.
+.
+.TP
+.B \-z
+Suppress formatted output.
+.
+.
+.\" ====================================================================
+.SH Warnings
+.\" ====================================================================
+.
+.\" BEGIN Keep parallel with groff.texi node "Warnings".
+.\" Caveat: the Texinfo manual sorts them by number, not name.
+Warning diagnostics emitted by
+.I @g@troff
+are divided into named,
+numbered categories.
+.
+The name associated with each warning category is used by the
+.B \-w
+and
+.B \-W
+options.
+.
+Each category is also assigned a power of two;
+the sum of enabled category codes is used by the
+.B warn
+request and the
+.B .warn
+register.
+.
+Warnings of each category are produced under the following
+circumstances.
+.
+.
+.P
+.TS
+tab(@), center, box;
+c c c | c c c
+r rI lB | r rI lB.
+Bit@Code@Category@Bit@Code@Category
+_
+0@1@char@10@1024@reg
+1@2@number@11@2048@tab
+2@4@break@12@4096@right\-brace
+3@8@delim@13@8192@missing
+4@16@el@14@16384@input
+5@32@scale@15@32768@escape
+6@64@range@16@65536@space
+7@128@syntax@17@131072@font
+8@256@di@18@262144@ig
+9@512@mac@19@524288@color
+@@@20@1048576@file
+.TE
+.
+.
+.P
+.nr x \w'\fBright\-brace'+1n+\w'00000'u
+.ta \nxuR
+.
+.
+.TP \nxu+3n
+.BR break "\t4"
+A filled output line could not be broken such that its length was less
+than the output line length
+.BR \[rs]n[.l] .
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR char "\t1"
+No mounted font defines a glyph for the requested character.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR color "\t524288"
+An undefined color name was selected,
+an attempt was made to define a color using an unrecognized color space,
+an invalid component in a color definition was encountered,
+or an attempt was made to redefine a default color.
+.
+.
+.TP
+.BR delim "\t8"
+The closing delimiter in an escape sequence was missing or mismatched.
+.
+.
+.TP
+.BR di "\t256"
+A
+.BR di ,
+.BR da ,
+.BR box ,
+or
+.B boxa
+request was invoked without an argument when there was no current
+diversion.
+.
+.
+.TP
+.BR el "\t16"
+The
+.B el
+request was encountered with no prior corresponding
+.B ie
+request.
+.
+.
+.TP
+.BR escape "\t32768"
+An unsupported escape sequence was encountered.
+.
+.
+.TP
+.BR file "\t1048576"
+An attempt was made to load a file that does not exist.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR font "\t131072"
+A non-existent font was selected,
+or the selection was ignored because a font selection escape sequence
+was used after the output line continuation escape sequence on an input
+line.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR ig "\t262144"
+An invalid escape sequence occurred in input ignored using the
+.B ig
+request.
+.
+This warning category diagnoses a condition that is an error when it
+occurs in non-ignored input.
+.
+.
+.TP
+.BR input "\t16384"
+An invalid character occurred on the input stream.
+.
+.
+.TP
+.BR mac "\t512"
+An undefined string,
+macro,
+or diversion was used.
+.
+When such an object is dereferenced,
+an empty one of that name is automatically created.
+.
+So,
+unless it is later deleted,
+at most one warning is given for each.
+.
+.
+.IP
+This warning is also emitted upon an attempt to move an unplanted trap
+macro.
+.
+In such cases,
+the unplanted macro is
+.I not
+dereferenced,
+so it is not created if it does not exist.
+.
+.
+.TP
+.BR missing "\t8192"
+A request was invoked with a mandatory argument absent.
+.
+.
+.TP
+.BR number "\t2"
+An invalid numeric expression was encountered.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR range "\t64"
+A numeric expression was out of range for its context.
+.
+.
+.TP
+.BR reg "\t1024"
+An undefined register was used.
+.
+When an undefined register is dereferenced,
+it is automatically defined with a value of\~0.
+.
+So,
+unless it is later deleted,
+at most one warning is given for each.
+.
+.
+.TP
+.BR right\-brace "\t4096"
+A right brace escape sequence
+.B \[rs]}
+was encountered where a number was expected.
+.
+.
+.TP
+.BR scale "\t32"
+A scaling unit inappropriate to its context was used in a numeric
+expression.
+.
+.
+.TP
+.BR space "\t65536"
+A space was missing between a request or macro and its argument.
+.
+This warning is produced when an undefined name longer than two
+characters is encountered and the first two characters of the name
+constitute a defined name.
+.
+No request is invoked,
+no macro called,
+and an empty macro is not defined.
+.
+This category is enabled by default.
+.
+It never occurs in compatibility mode.
+.
+.
+.TP
+.BR syntax "\t128"
+A self-contradictory hyphenation mode was requested;
+an empty or incomplete numeric expression was encountered;
+an operand to a numeric operator was missing;
+an attempt was made to define a recursive,
+empty,
+or nonsensical character class;
+or a
+.I groff
+extension conditional expression operator was used while in
+compatibility mode.
+.
+.
+.TP
+.BR tab "\t2048"
+A tab character was encountered where a number was expected,
+or appeared in an unquoted macro argument.
+.
+.
+.P
+Two warning names group other warning categories for convenience.
+.
+.
+.TP
+.B all
+All warning categories except
+.BR di ,
+.BR mac ,
+and
+.BR reg .
+.
+This shorthand is intended to produce all warnings that are useful with
+macro packages and documents written for AT&T
+.I troff \" AT&T
+and its descendants,
+which have less fastidious diagnostics than GNU
+.IR troff . \" GNU
+.
+.
+.TP
+.B w
+All warning categories.
+.
+Authors of documents and macro packages targeting
+.I groff
+are encouraged to use this setting.
+.\" END Keep parallel with groff.texi node "Warnings".
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.I GROFF_FONT_PATH
+and
+.I GROFF_TMAC_PATH
+each accept a search path of directories;
+that is,
+a list of directory names separated by the system's path component
+separator character.
+.
+On Unix systems,
+this character is a colon (:);
+on Windows systems,
+it is a semicolon (;).
+.
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+.I @g@troff
+will scan directories given as arguments to any specified
+.B \-F
+options before these,
+then in a site-specific directory
+.RI ( @LOCALFONTDIR@ ),
+a standard location
+.RI ( @FONTDIR@ ),
+and a compatibility directory
+.RI ( @LEGACYFONTDIR@ )
+after them.
+.
+.
+.TP
+.I GROFF_TMAC_PATH
+A list of directories in which to search for macro files.
+.
+.I @g@troff
+will scan directories given as arguments to any specified
+.B \-M
+options before these,
+then the current directory
+(only if in unsafe mode),
+the user's home directory,
+.if !'@COMPATIBILITY_WRAPPERS@'no' \{\
+a platform-specific directory
+.RI ( @SYSTEMMACRODIR@ ),
+.\}
+a site-specific directory
+.RI ( @LOCALMACRODIR@ ),
+and a standard location
+.RI ( @MACRODIR@ )
+after them.
+.
+.
+.TP
+.I GROFF_TYPESETTER
+Set the default output device.
+.
+If empty or not set,
+.B @DEVICE@
+is used.
+.
+The
+.B \-T
+option overrides
+.IR \%GROFF_TYPESETTER .
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR localtime 3
+when the formatter starts up and stored in registers usable by documents
+and macro packages.
+.
+.
+.TP
+.I TZ
+The timezone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @MACRODIR@/\:\%troffrc
+is an initialization macro file loaded before any macro packages
+specified with
+.B \-m
+options.
+.
+.
+.TP
+.I @MACRODIR@/\:\%troffrc\-end
+is an initialization macro file loaded after all macro packages
+specified with
+.B \-m
+options.
+.
+.
+.TP
+.IR @MACRODIR@/\: name \:.tmac
+are macro files distributed with
+.IR groff .
+.
+.
+.TP
+.IR @FONTDIR@/\:\%dev name /\:DESC
+describes the output device
+.IR name .
+.
+.
+.TP
+.IR @FONTDIR@/\:\%dev name / F
+describes the font
+.I F
+of device
+.I name.
+.
+.
+.P
+.I troffrc
+and
+.I troffrc\-end
+are sought neither in the current nor the home directory by default for
+security reasons,
+even if the
+.B \-U
+option is specified.
+.
+Use the
+.B \-M
+command-line option or the
+.I GROFF_TMAC_PATH
+environment variable to add these directories to the search path if
+necessary.
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+The GNU version of
+.I troff \" generic
+was originally written by James Clark;
+he also wrote the original version of this document,
+which was updated by
+.MT wl@\:gnu\:.org
+Werner Lemberg
+.ME ,
+.MT groff\-bernd\:.warken\-72@\:web\:.de
+Bernd Warken
+.ME ,
+and
+.MT g.branden\:.robinson@\:gmail\:.com
+G.\& Branden Robinson
+.ME .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.IR "Groff: The GNU Implementation of troff" ,
+by Trent A.\& Fisher and Werner Lemberg,
+is the primary
+.I groff
+manual.
+.
+You can browse it interactively with \[lq]info groff\[rq].
+.
+.
+.TP
+.MR groff @MAN1EXT@
+offers an overview of the GNU
+.I roff
+system
+and describes its front end executable.
+.
+.
+.TP
+.MR groff @MAN7EXT@
+details the
+.I groff
+language,
+including a short but complete reference of all predefined requests,
+registers,
+and escape sequences.
+.
+.
+.TP
+.MR groff_char @MAN7EXT@
+explains the syntax of
+.I groff
+special character escape sequences,
+and lists all special characters predefined by the language.
+.
+.
+.TP
+.MR groff_diff @MAN7EXT@
+enumerates the differences between
+AT&T device-independent
+.I troff \" AT&T
+and
+.IR groff .
+.
+.
+.TP
+.MR groff_font @MAN5EXT@
+covers the format of
+.I groff
+device and font description files.
+.
+.
+.TP
+.MR groff_out @MAN5EXT@
+describes the format of
+.IR @g@troff 's
+output.
+.
+.
+.TP
+.MR groff_tmac @MAN5EXT@
+includes information about macro files that ship with
+.IR groff .
+.
+.
+.TP
+.MR roff @MAN7EXT@
+supplies background on
+.I roff
+systems in general,
+including pointers to further related documentation.
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_troff_1_man_C]
+.do rr *groff_troff_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/roff/troff/troff.am b/src/roff/troff/troff.am
new file mode 100644
index 0000000..42ac7fa
--- /dev/null
+++ b/src/roff/troff/troff.am
@@ -0,0 +1,66 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += troff
+PREFIXMAN1 += src/roff/troff/troff.1
+EXTRA_DIST += \
+ src/roff/troff/column.cpp \
+ src/roff/troff/troff.1.man \
+ src/roff/troff/TODO
+troff_LDADD = libgroff.a lib/libgnu.a $(LIBM)
+troff_SOURCES = \
+ src/roff/troff/dictionary.cpp \
+ src/roff/troff/div.cpp \
+ src/roff/troff/env.cpp \
+ src/roff/troff/input.cpp \
+ src/roff/troff/mtsm.cpp \
+ src/roff/troff/node.cpp \
+ src/roff/troff/number.cpp \
+ src/roff/troff/reg.cpp \
+ src/roff/troff/env.h \
+ src/roff/troff/node.h \
+ src/roff/troff/troff.h \
+ src/roff/troff/div.h \
+ src/roff/troff/reg.h \
+ src/roff/troff/dictionary.h \
+ src/roff/troff/input.h \
+ src/roff/troff/mtsm.h \
+ src/roff/troff/token.h \
+ src/roff/troff/charinfo.h \
+ src/roff/troff/request.h \
+ src/roff/troff/hvunits.h
+
+nodist_troff_SOURCES = src/roff/troff/majorminor.cpp
+
+src/roff/troff/input.$(OBJEXT): defs.h
+CLEANFILES += src/roff/troff/majorminor.cpp
+
+src/roff/troff/majorminor.cpp: $(top_srcdir)/.version
+ $(AM_V_at)printf 'const char *major_version = "%s";\n' \
+ $(MAJOR_VERSION) > $@.tmp
+ $(AM_V_at)printf 'const char *minor_version = "%s";\n' \
+ $(MINOR_VERSION) >> $@.tmp
+ $(AM_V_at)printf 'const char *revision = "%s";\n' \
+ $(REVISION) >> $@.tmp
+ $(AM_V_GEN)mv $@.tmp $@
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/roff/troff/troff.h b/src/roff/troff/troff.h
new file mode 100644
index 0000000..8cef744
--- /dev/null
+++ b/src/roff/troff/troff.h
@@ -0,0 +1,97 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "color.h"
+#include "device.h"
+#include "searchpath.h"
+
+typedef int units;
+
+extern units scale(units n, units x, units y); // scale n by x/y
+
+extern units units_per_inch;
+
+extern int ascii_output_flag;
+extern int suppress_output_flag;
+extern int color_flag;
+extern int is_html;
+
+extern bool device_has_tcommand;
+extern int vresolution;
+extern int hresolution;
+extern int sizescale;
+
+extern search_path *mac_path;
+
+#include "cset.h"
+#include "cmap.h"
+#include "errarg.h"
+#include "error.h"
+
+enum warning_type {
+ WARN_CHAR = 01,
+ WARN_NUMBER = 02,
+ WARN_BREAK = 04,
+ WARN_DELIM = 010,
+ WARN_EL = 020,
+ WARN_SCALE = 040,
+ WARN_RANGE = 0100,
+ WARN_SYNTAX = 0200,
+ WARN_DI = 0400,
+ WARN_MAC = 01000,
+ WARN_REG = 02000,
+ WARN_TAB = 04000,
+ WARN_RIGHT_BRACE = 010000,
+ WARN_MISSING = 020000,
+ WARN_INPUT = 040000,
+ WARN_ESCAPE = 0100000,
+ WARN_SPACE = 0200000,
+ WARN_FONT = 0400000,
+ WARN_IG = 01000000,
+ WARN_COLOR = 02000000,
+ WARN_FILE = 04000000
+ // change WARN_TOTAL if you add more warning types
+};
+
+const int WARN_TOTAL = 07777777;
+
+int warning(warning_type, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+int output_warning(warning_type, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/addftinfo/addftinfo.1.man b/src/utils/addftinfo/addftinfo.1.man
new file mode 100644
index 0000000..a719136
--- /dev/null
+++ b/src/utils/addftinfo/addftinfo.1.man
@@ -0,0 +1,236 @@
+.TH addftinfo @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+addftinfo \- add font metrics to
+.I troff
+fonts for use with
+.I groff
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_addftinfo_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY addftinfo
+.RB [ \-asc\-height\~\c
+.IR n ]
+.RB [ \-body\-depth\~\c
+.IR n ]
+.RB [ \-body\-height\~\c
+.IR n ]
+.RB [ \-cap\-height\~\c
+.IR n ]
+.RB [ \-comma\-depth\~\c
+.IR n ]
+.RB [ \-desc\-depth\~\c
+.IR n ]
+.RB [ \-fig\-height\~\c
+.IR n ]
+.RB [ \-x\-height\~\c
+.IR n ]
+.I resolution
+.I unit-width
+.I font
+.YS
+.
+.
+.SY addftinfo
+.B \-\-help
+.YS
+.
+.
+.SY addftinfo
+.B \-v
+.
+.SY addftinfo
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I addftinfo
+reads an
+.RI AT&T \~troff
+font description file
+.IR font ,
+adds additional font metric information required by
+.\" We need the "GNU" below because the @g@ prefix might be empty.
+.RI GNU \~@g@troff (@MAN1EXT@),
+and writes the combined result to the standard output.
+.
+The information added is derived from the font's existing parameters and
+assumptions about traditional
+.I troff
+names for characters.
+.
+Among the font metrics added are the heights and depths of characters
+(how far each extends vertically above and below the baseline).
+.
+The
+.I resolution
+and
+.I unit-width
+arguments should be the same as the corresponding parameters in the
+.I DESC
+file.
+.
+.I font
+is the name of the file describing the font;
+if
+.I font
+ends with
+.RB \[lq] I \[rq],
+the font is assumed to be oblique
+(or italic).
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.P
+All other options change parameters that are used to derive the heights
+and depths.
+.
+Like the existing quantities in the font description file,
+each
+.RI value\~ n
+is in
+.I "scaled points,"
+.RI inches/ resolution
+for a font whose type size is
+.IR unit-width ;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI \-asc\-height \~n
+height of characters with ascenders,
+such as \[lq]b\[rq],
+\[lq]d\[rq],
+or \[lq]l\[rq]
+.
+.
+.TP
+.BI \-body\-depth \~n
+depth of characters such as parentheses
+.
+.
+.TP
+.BI \-body\-height \~n
+height of characters such as parentheses
+.
+.
+.TP
+.BI \-cap\-height \~n
+height of uppercase letters such as \[lq]A\[rq]
+.
+.
+.TP
+.BI \-comma\-depth \~n
+depth of a comma
+.
+.
+.TP
+.BI \-desc\-depth \~n
+depth of characters with descenders,
+such as \[lq]p\[rq],
+\[lq]q\[rq],
+or \[lq]y\[rq]
+.
+.
+.TP
+.B \-fig\-height
+height of figures (numerals)
+.
+.
+.TP
+.BI \-x\-height \~n
+height of lowercase letters without ascenders such as \[lq]x\[rq]
+.
+.
+.P
+.I addftinfo
+makes no attempt to use the specified parameters to infer unspecified
+parameters.
+.
+If a parameter is not specified,
+the default will be used.
+.
+The defaults are chosen to produce reasonable values for a Times font.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff_font @MAN5EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR groff_char @MAN7EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_addftinfo_1_man_C]
+.do rr *groff_addftinfo_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/addftinfo/addftinfo.am b/src/utils/addftinfo/addftinfo.am
new file mode 100644
index 0000000..dd51372
--- /dev/null
+++ b/src/utils/addftinfo/addftinfo.am
@@ -0,0 +1,35 @@
+# Automake rules for 'src utils addftinfo'
+#
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/gpl-2.0.html>.
+#
+########################################################################
+
+bin_PROGRAMS += addftinfo
+man1_MANS += src/utils/addftinfo/addftinfo.1
+EXTRA_DIST += src/utils/addftinfo/addftinfo.1.man
+addftinfo_LDADD = libgroff.a lib/libgnu.a
+addftinfo_SOURCES = \
+ src/utils/addftinfo/addftinfo.cpp \
+ src/utils/addftinfo/guess.cpp \
+ src/utils/addftinfo/guess.h
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/addftinfo/addftinfo.cpp b/src/utils/addftinfo/addftinfo.cpp
new file mode 100644
index 0000000..6f4facf
--- /dev/null
+++ b/src/utils/addftinfo/addftinfo.cpp
@@ -0,0 +1,237 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "guess.h"
+
+extern "C" const char *Version_string;
+
+static void usage(FILE *stream);
+static void usage();
+static void usage(const char *problem);
+static void version();
+static void convert_font(const font_params &, FILE *, FILE *);
+
+typedef int font_params::*param_t;
+
+static struct {
+ const char *name;
+ param_t par;
+} param_table[] = {
+ { "asc-height", &font_params::asc_height },
+ { "body-depth", &font_params::body_depth },
+ { "body-height", &font_params::body_height },
+ { "cap-height", &font_params::cap_height },
+ { "comma-depth", &font_params::comma_depth },
+ { "desc-depth", &font_params::desc_depth },
+ { "fig-height", &font_params::fig_height },
+ { "x-height", &font_params::x_height },
+};
+
+// These are all in thousandths of an em.
+// These values are correct for PostScript Times Roman.
+
+#define DEFAULT_X_HEIGHT 448
+#define DEFAULT_FIG_HEIGHT 676
+#define DEFAULT_ASC_HEIGHT 682
+#define DEFAULT_BODY_HEIGHT 676
+#define DEFAULT_CAP_HEIGHT 662
+#define DEFAULT_COMMA_DEPTH 143
+#define DEFAULT_DESC_DEPTH 217
+#define DEFAULT_BODY_DEPTH 177
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ int i;
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-v") || !strcmp(argv[i],"--version"))
+ version();
+ if (!strcmp(argv[i],"--help")) {
+ usage(stdout);
+ exit(0);
+ }
+ }
+ if (argc < 4)
+ usage("insufficient arguments");
+ /* The next couple of usage() calls cannot provide a meaningful
+ diagnostic because we don't know whether sscanf() failed on a
+ required parameter or an option. A refactor could fix this. */
+ int resolution;
+ if (sscanf(argv[argc-3], "%d", &resolution) != 1)
+ usage();
+ if (resolution <= 0)
+ fatal("resolution must be positive");
+ int unitwidth;
+ if (sscanf(argv[argc-2], "%d", &unitwidth) != 1)
+ usage();
+ if (unitwidth <= 0)
+ fatal("unit width must be positive");
+ font_params param;
+ const char *font = argv[argc-1];
+ param.italic = (font[0] != '\0' && strchr(font, '\0')[-1] == 'I');
+ param.em = (resolution*unitwidth)/72;
+ param.x_height = DEFAULT_X_HEIGHT;
+ param.fig_height = DEFAULT_FIG_HEIGHT;
+ param.asc_height = DEFAULT_ASC_HEIGHT;
+ param.body_height = DEFAULT_BODY_HEIGHT;
+ param.cap_height = DEFAULT_CAP_HEIGHT;
+ param.comma_depth = DEFAULT_COMMA_DEPTH;
+ param.desc_depth = DEFAULT_DESC_DEPTH;
+ param.body_depth = DEFAULT_BODY_DEPTH;
+ for (i = 1; i < argc && argv[i][0] == '-'; i++) {
+ if (argv[i][1] == '-' && argv[i][2] == '\0') {
+ i++;
+ break;
+ }
+ if (i + 1 >= argc)
+ usage("option requires argument");
+ size_t j;
+ for (j = 0;; j++) {
+ if (j >= sizeof(param_table)/sizeof(param_table[0]))
+ fatal("parameter '%1' not recognized", argv[i] + 1);
+ if (strcmp(param_table[j].name, argv[i] + 1) == 0)
+ break;
+ }
+ if (sscanf(argv[i+1], "%d", &(param.*(param_table[j].par))) != 1)
+ fatal("invalid option argument '%1'", argv[i+1]);
+ i++;
+ }
+ if (argc - i != 3)
+ usage("insufficient arguments");
+ errno = 0;
+ FILE *infp = fopen(font, "r");
+ if (infp == 0)
+ fatal("can't open '%1': %2", font, strerror(errno));
+ convert_font(param, infp, stdout);
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream, "usage: %s", program_name);
+ size_t len = sizeof(param_table)/sizeof(param_table[0]);
+ for (size_t i = 0; i < len; i++)
+ fprintf(stream, " [-%s n]", param_table[i].name);
+ fputs(" resolution unit-width font\n", stream);
+ fprintf(stream, "usage: %s {-v | --version}\n"
+ "usage: %s --help\n", program_name, program_name);
+}
+
+static void usage()
+{
+ usage(stderr);
+ exit(1);
+}
+
+static void usage(const char *problem)
+{
+ error("%1", problem);
+ usage();
+}
+
+static void version()
+{
+ printf("GNU addftinfo (groff) version %s\n", Version_string);
+ exit(0);
+}
+
+static int get_line(FILE *fp, string *p)
+{
+ int c;
+ p->clear();
+ while ((c = getc(fp)) != EOF) {
+ *p += char(c);
+ if (c == '\n')
+ break;
+ }
+ return p->length() > 0;
+}
+
+static void convert_font(const font_params &param, FILE *infp,
+ FILE *outfp)
+{
+ string s;
+ while (get_line(infp, &s)) {
+ put_string(s, outfp);
+ if (s.length() >= 8
+ && strncmp(&s[0], "charset", 7))
+ break;
+ }
+ while (get_line(infp, &s)) {
+ s += '\0';
+ string name;
+ const char *p = s.contents();
+ while (csspace(*p))
+ p++;
+ while (*p != '\0' && !csspace(*p))
+ name += *p++;
+ while (csspace(*p))
+ p++;
+ for (const char *q = s.contents(); q < p; q++)
+ putc(*q, outfp);
+ char *next;
+ char_metric metric;
+ metric.width = (int)strtol(p, &next, 10);
+ if (next != p) {
+ printf("%d", metric.width);
+ p = next;
+ metric.type = (int)strtol(p, &next, 10);
+ if (next != p) {
+ name += '\0';
+ guess(name.contents(), param, &metric);
+ if (metric.sk == 0) {
+ if (metric.left_ic == 0) {
+ if (metric.ic == 0) {
+ if (metric.depth == 0) {
+ if (metric.height != 0)
+ printf(",%d", metric.height);
+ }
+ else
+ printf(",%d,%d", metric.height, metric.depth);
+ }
+ else
+ printf(",%d,%d,%d", metric.height, metric.depth,
+ metric.ic);
+ }
+ else
+ printf(",%d,%d,%d,%d", metric.height, metric.depth,
+ metric.ic, metric.left_ic);
+ }
+ else
+ printf(",%d,%d,%d,%d,%d", metric.height, metric.depth,
+ metric.ic, metric.left_ic, metric.sk);
+ }
+ }
+ fputs(p, outfp);
+ }
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/addftinfo/guess.cpp b/src/utils/addftinfo/guess.cpp
new file mode 100644
index 0000000..08bbe05
--- /dev/null
+++ b/src/utils/addftinfo/guess.cpp
@@ -0,0 +1,489 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "guess.h"
+
+void guess(const char *s, const font_params &param, char_metric *metric)
+{
+ int &height = metric->height;
+ int &depth = metric->depth;
+
+ metric->ic = 0;
+ metric->left_ic = 0;
+ metric->sk = 0;
+ height = 0;
+ depth = 0;
+ if (s[0] == '\0' || (s[1] != '\0' && s[2] != '\0'))
+ goto do_default;
+#define HASH(c1, c2) (((unsigned char)(c1) << 8) | (unsigned char)(c2))
+ switch (HASH(s[0], s[1])) {
+ default:
+ do_default:
+ if (metric->type & 01)
+ depth = param.desc_depth;
+ if (metric->type & 02)
+ height = param.asc_height;
+ else
+ height = param.x_height;
+ break;
+ case HASH('\\', '|'):
+ case HASH('\\', '^'):
+ case HASH('\\', '&'):
+ // these have zero height and depth
+ break;
+ case HASH('f', 0):
+ height = param.asc_height;
+ if (param.italic)
+ depth = param.desc_depth;
+ break;
+ case HASH('a', 0):
+ case HASH('c', 0):
+ case HASH('e', 0):
+ case HASH('m', 0):
+ case HASH('n', 0):
+ case HASH('o', 0):
+ case HASH('r', 0):
+ case HASH('s', 0):
+ case HASH('u', 0):
+ case HASH('v', 0):
+ case HASH('w', 0):
+ case HASH('x', 0):
+ case HASH('z', 0):
+ height = param.x_height;
+ break;
+ case HASH('i', 0):
+ height = param.x_height;
+ break;
+ case HASH('b', 0):
+ case HASH('d', 0):
+ case HASH('h', 0):
+ case HASH('k', 0):
+ case HASH('l', 0):
+ case HASH('F', 'i'):
+ case HASH('F', 'l'):
+ case HASH('f', 'f'):
+ case HASH('f', 'i'):
+ case HASH('f', 'l'):
+ height = param.asc_height;
+ break;
+ case HASH('t', 0):
+ height = param.asc_height;
+ break;
+ case HASH('g', 0):
+ case HASH('p', 0):
+ case HASH('q', 0):
+ case HASH('y', 0):
+ height = param.x_height;
+ depth = param.desc_depth;
+ break;
+ case HASH('j', 0):
+ height = param.x_height;
+ depth = param.desc_depth;
+ break;
+ case HASH('A', 0):
+ case HASH('B', 0):
+ case HASH('C', 0):
+ case HASH('D', 0):
+ case HASH('E', 0):
+ case HASH('F', 0):
+ case HASH('G', 0):
+ case HASH('H', 0):
+ case HASH('I', 0):
+ case HASH('J', 0):
+ case HASH('K', 0):
+ case HASH('L', 0):
+ case HASH('M', 0):
+ case HASH('N', 0):
+ case HASH('O', 0):
+ case HASH('P', 0):
+ case HASH('Q', 0):
+ case HASH('R', 0):
+ case HASH('S', 0):
+ case HASH('T', 0):
+ case HASH('U', 0):
+ case HASH('V', 0):
+ case HASH('W', 0):
+ case HASH('X', 0):
+ case HASH('Y', 0):
+ case HASH('Z', 0):
+ height = param.cap_height;
+ break;
+ case HASH('*', 'A'):
+ case HASH('*', 'B'):
+ case HASH('*', 'C'):
+ case HASH('*', 'D'):
+ case HASH('*', 'E'):
+ case HASH('*', 'F'):
+ case HASH('*', 'G'):
+ case HASH('*', 'H'):
+ case HASH('*', 'I'):
+ case HASH('*', 'K'):
+ case HASH('*', 'L'):
+ case HASH('*', 'M'):
+ case HASH('*', 'N'):
+ case HASH('*', 'O'):
+ case HASH('*', 'P'):
+ case HASH('*', 'Q'):
+ case HASH('*', 'R'):
+ case HASH('*', 'S'):
+ case HASH('*', 'T'):
+ case HASH('*', 'U'):
+ case HASH('*', 'W'):
+ case HASH('*', 'X'):
+ case HASH('*', 'Y'):
+ case HASH('*', 'Z'):
+ height = param.cap_height;
+ break;
+ case HASH('0', 0):
+ case HASH('1', 0):
+ case HASH('2', 0):
+ case HASH('3', 0):
+ case HASH('4', 0):
+ case HASH('5', 0):
+ case HASH('6', 0):
+ case HASH('7', 0):
+ case HASH('8', 0):
+ case HASH('9', 0):
+ case HASH('1', '2'):
+ case HASH('1', '4'):
+ case HASH('3', '4'):
+ height = param.fig_height;
+ break;
+ case HASH('(', 0):
+ case HASH(')', 0):
+ case HASH('[', 0):
+ case HASH(']', 0):
+ case HASH('{', 0):
+ case HASH('}', 0):
+ height = param.body_height;
+ depth = param.body_depth;
+ break;
+ case HASH('i', 's'):
+ height = (param.em*3)/4;
+ depth = param.em/4;
+ break;
+ case HASH('*', 'a'):
+ case HASH('*', 'e'):
+ case HASH('*', 'i'):
+ case HASH('*', 'k'):
+ case HASH('*', 'n'):
+ case HASH('*', 'o'):
+ case HASH('*', 'p'):
+ case HASH('*', 's'):
+ case HASH('*', 't'):
+ case HASH('*', 'u'):
+ case HASH('*', 'w'):
+ height = param.x_height;
+ break;
+ case HASH('*', 'd'):
+ case HASH('*', 'l'):
+ height = param.asc_height;
+ break;
+ case HASH('*', 'g'):
+ case HASH('*', 'h'):
+ case HASH('*', 'm'):
+ case HASH('*', 'r'):
+ case HASH('*', 'x'):
+ case HASH('*', 'y'):
+ height = param.x_height;
+ depth = param.desc_depth;
+ break;
+ case HASH('*', 'b'):
+ case HASH('*', 'c'):
+ case HASH('*', 'f'):
+ case HASH('*', 'q'):
+ case HASH('*', 'z'):
+ height = param.asc_height;
+ depth = param.desc_depth;
+ break;
+ case HASH('t', 's'):
+ height = param.x_height;
+ depth = param.desc_depth;
+ break;
+ case HASH('!', 0):
+ case HASH('?', 0):
+ case HASH('"', 0):
+ case HASH('#', 0):
+ case HASH('$', 0):
+ case HASH('%', 0):
+ case HASH('&', 0):
+ case HASH('*', 0):
+ case HASH('+', 0):
+ height = param.asc_height;
+ break;
+ case HASH('`', 0):
+ case HASH('\'', 0):
+ height = param.asc_height;
+ break;
+ case HASH('~', 0):
+ case HASH('^', 0):
+ case HASH('a', 'a'):
+ case HASH('g', 'a'):
+ height = param.asc_height;
+ break;
+ case HASH('r', 'u'):
+ case HASH('.', 0):
+ break;
+ case HASH(',', 0):
+ depth = param.comma_depth;
+ break;
+ case HASH('m', 'i'):
+ case HASH('-', 0):
+ case HASH('h', 'y'):
+ case HASH('e', 'm'):
+ height = param.x_height;
+ break;
+ case HASH(':', 0):
+ height = param.x_height;
+ break;
+ case HASH(';', 0):
+ height = param.x_height;
+ depth = param.comma_depth;
+ break;
+ case HASH('=', 0):
+ case HASH('e', 'q'):
+ height = param.x_height;
+ break;
+ case HASH('<', 0):
+ case HASH('>', 0):
+ case HASH('>', '='):
+ case HASH('<', '='):
+ case HASH('@', 0):
+ case HASH('/', 0):
+ case HASH('|', 0):
+ case HASH('\\', 0):
+ height = param.asc_height;
+ break;
+ case HASH('_', 0):
+ case HASH('u', 'l'):
+ case HASH('\\', '_'):
+ depth = param.em/4;
+ break;
+ case HASH('r', 'n'):
+ height = (param.em*3)/4;
+ break;
+ case HASH('s', 'r'):
+ height = (param.em*3)/4;
+ depth = param.em/4;
+ break;
+ case HASH('b', 'u'):
+ case HASH('s', 'q'):
+ case HASH('d', 'e'):
+ case HASH('d', 'g'):
+ case HASH('f', 'm'):
+ case HASH('c', 't'):
+ case HASH('r', 'g'):
+ case HASH('c', 'o'):
+ case HASH('p', 'l'):
+ case HASH('*', '*'):
+ case HASH('s', 'c'):
+ case HASH('s', 'l'):
+ case HASH('=', '='):
+ case HASH('~', '='):
+ case HASH('a', 'p'):
+ case HASH('!', '='):
+ case HASH('-', '>'):
+ case HASH('<', '-'):
+ case HASH('u', 'a'):
+ case HASH('d', 'a'):
+ case HASH('m', 'u'):
+ case HASH('d', 'i'):
+ case HASH('+', '-'):
+ case HASH('c', 'u'):
+ case HASH('c', 'a'):
+ case HASH('s', 'b'):
+ case HASH('s', 'p'):
+ case HASH('i', 'b'):
+ case HASH('i', 'p'):
+ case HASH('i', 'f'):
+ case HASH('p', 'd'):
+ case HASH('g', 'r'):
+ case HASH('n', 'o'):
+ case HASH('p', 't'):
+ case HASH('e', 's'):
+ case HASH('m', 'o'):
+ case HASH('b', 'r'):
+ case HASH('d', 'd'):
+ case HASH('r', 'h'):
+ case HASH('l', 'h'):
+ case HASH('o', 'r'):
+ case HASH('c', 'i'):
+ height = param.asc_height;
+ break;
+ case HASH('l', 't'):
+ case HASH('l', 'b'):
+ case HASH('r', 't'):
+ case HASH('r', 'b'):
+ case HASH('l', 'k'):
+ case HASH('r', 'k'):
+ case HASH('b', 'v'):
+ case HASH('l', 'f'):
+ case HASH('r', 'f'):
+ case HASH('l', 'c'):
+ case HASH('r', 'c'):
+ height = (param.em*3)/4;
+ depth = param.em/4;
+ break;
+#if 0
+ case HASH('%', '0'):
+ case HASH('-', '+'):
+ case HASH('-', 'D'):
+ case HASH('-', 'd'):
+ case HASH('-', 'd'):
+ case HASH('-', 'h'):
+ case HASH('.', 'i'):
+ case HASH('.', 'j'):
+ case HASH('/', 'L'):
+ case HASH('/', 'O'):
+ case HASH('/', 'l'):
+ case HASH('/', 'o'):
+ case HASH('=', '~'):
+ case HASH('A', 'E'):
+ case HASH('A', 'h'):
+ case HASH('A', 'N'):
+ case HASH('C', 's'):
+ case HASH('D', 'o'):
+ case HASH('F', 'c'):
+ case HASH('F', 'o'):
+ case HASH('I', 'J'):
+ case HASH('I', 'm'):
+ case HASH('O', 'E'):
+ case HASH('O', 'f'):
+ case HASH('O', 'K'):
+ case HASH('O', 'm'):
+ case HASH('O', 'R'):
+ case HASH('P', 'o'):
+ case HASH('R', 'e'):
+ case HASH('S', '1'):
+ case HASH('S', '2'):
+ case HASH('S', '3'):
+ case HASH('T', 'P'):
+ case HASH('T', 'p'):
+ case HASH('Y', 'e'):
+ case HASH('\\', '-'):
+ case HASH('a', '"'):
+ case HASH('a', '-'):
+ case HASH('a', '.'):
+ case HASH('a', '^'):
+ case HASH('a', 'b'):
+ case HASH('a', 'c'):
+ case HASH('a', 'd'):
+ case HASH('a', 'e'):
+ case HASH('a', 'h'):
+ case HASH('a', 'o'):
+ case HASH('a', 't'):
+ case HASH('a', '~'):
+ case HASH('b', 'a'):
+ case HASH('b', 'b'):
+ case HASH('b', 's'):
+ case HASH('c', '*'):
+ case HASH('c', '+'):
+ case HASH('f', '/'):
+ case HASH('f', 'a'):
+ case HASH('f', 'c'):
+ case HASH('f', 'o'):
+ case HASH('h', 'a'):
+ case HASH('h', 'o'):
+ case HASH('i', 'j'):
+ case HASH('l', 'A'):
+ case HASH('l', 'B'):
+ case HASH('l', 'C'):
+ case HASH('m', 'd'):
+ case HASH('n', 'c'):
+ case HASH('n', 'e'):
+ case HASH('n', 'm'):
+ case HASH('o', 'A'):
+ case HASH('o', 'a'):
+ case HASH('o', 'e'):
+ case HASH('o', 'q'):
+ case HASH('p', 'l'):
+ case HASH('p', 'p'):
+ case HASH('p', 's'):
+ case HASH('r', '!'):
+ case HASH('r', '?'):
+ case HASH('r', 'A'):
+ case HASH('r', 'B'):
+ case HASH('r', 'C'):
+ case HASH('r', 's'):
+ case HASH('s', 'h'):
+ case HASH('s', 's'):
+ case HASH('t', 'e'):
+ case HASH('t', 'f'):
+ case HASH('t', 'i'):
+ case HASH('t', 'm'):
+ case HASH('~', '~'):
+ case HASH('v', 'S'):
+ case HASH('v', 'Z'):
+ case HASH('v', 's'):
+ case HASH('v', 'z'):
+ case HASH('^', 'A'):
+ case HASH('^', 'E'):
+ case HASH('^', 'I'):
+ case HASH('^', 'O'):
+ case HASH('^', 'U'):
+ case HASH('^', 'a'):
+ case HASH('^', 'e'):
+ case HASH('^', 'i'):
+ case HASH('^', 'o'):
+ case HASH('^', 'u'):
+ case HASH('`', 'A'):
+ case HASH('`', 'E'):
+ case HASH('`', 'I'):
+ case HASH('`', 'O'):
+ case HASH('`', 'U'):
+ case HASH('`', 'a'):
+ case HASH('`', 'e'):
+ case HASH('`', 'i'):
+ case HASH('`', 'o'):
+ case HASH('`', 'u'):
+ case HASH('~', 'A'):
+ case HASH('~', 'N'):
+ case HASH('~', 'O'):
+ case HASH('~', 'a'):
+ case HASH('~', 'n'):
+ case HASH('~', 'o'):
+ case HASH('\'', 'A'):
+ case HASH('\'', 'C'):
+ case HASH('\'', 'E'):
+ case HASH('\'', 'I'):
+ case HASH('\'', 'O'):
+ case HASH('\'', 'U'):
+ case HASH('\'', 'a'):
+ case HASH('\'', 'c'):
+ case HASH('\'', 'e'):
+ case HASH('\'', 'i'):
+ case HASH('\'', 'o'):
+ case HASH('\'', 'u')
+ case HASH(':', 'A'):
+ case HASH(':', 'E'):
+ case HASH(':', 'I'):
+ case HASH(':', 'O'):
+ case HASH(':', 'U'):
+ case HASH(':', 'Y'):
+ case HASH(':', 'a'):
+ case HASH(':', 'e'):
+ case HASH(':', 'i'):
+ case HASH(':', 'o'):
+ case HASH(':', 'u'):
+ case HASH(':', 'y'):
+ case HASH(',', 'C'):
+ case HASH(',', 'c'):
+#endif
+ }
+}
diff --git a/src/utils/addftinfo/guess.h b/src/utils/addftinfo/guess.h
new file mode 100644
index 0000000..d763fe0
--- /dev/null
+++ b/src/utils/addftinfo/guess.h
@@ -0,0 +1,43 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct font_params {
+ int italic;
+ int em;
+ int x_height;
+ int fig_height;
+ int cap_height;
+ int asc_height;
+ int body_height;
+ int comma_depth;
+ int desc_depth;
+ int body_depth;
+};
+
+struct char_metric {
+ int width;
+ int type;
+ int height;
+ int depth;
+ int ic;
+ int left_ic;
+ int sk;
+};
+
+void guess(const char *s, const font_params &param, char_metric *metric);
diff --git a/src/utils/afmtodit/afmtodit.1.man b/src/utils/afmtodit/afmtodit.1.man
new file mode 100644
index 0000000..7b0a39f
--- /dev/null
+++ b/src/utils/afmtodit/afmtodit.1.man
@@ -0,0 +1,635 @@
+.TH afmtodit @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+afmtodit \- adapt Adobe Font Metrics files for
+.I groff
+PostScript and PDF output
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_afmtodit_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY afmtodit
+.RB [ \-ckmnsx ]
+.RB [ \-a\~\c
+.IR slant ]
+.RB [ \-d\~\c
+.IR device-description-file ]
+.RB [ \-e\~\c
+.IR encoding-file ]
+.RB [ \-f\~\c
+.IR internal-name ]
+.RB [ \-i\~\c
+.IR italic-correction-factor ]
+.RB [ \-o\~\c
+.IR output-file ]
+.RB [ \-w\~\c
+.IR space-width ]
+.I afm-file
+.I map-file
+.I font-description-file
+.YS
+.
+.
+.SY afmtodit
+.B \-\-help
+.YS
+.
+.
+.SY afmtodit
+.B \-v
+.
+.SY afmtodit
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I \%afmtodit
+adapts an
+Adobe Font Metric
+file,
+.IR afm-file ,
+for use with the
+.B ps
+and
+.B pdf
+output devices of
+.MR @g@troff @MAN1EXT@ .
+.
+.I map-file
+associates a
+.I groff
+ordinary or special character name with a PostScript glyph name.
+.
+Output is written in
+.MR groff_font @MAN5EXT@
+format to
+.I font-description-file,
+a file named for the intended
+.I groff
+font name
+(but see the
+.B \-o
+option).
+.
+.
+.LP
+.I map-file
+should contain a sequence of lines of the form
+.
+.RS
+.EX
+.I ps-glyph groff-char
+.EE
+.RE
+.
+where
+.I ps-glyph
+is the PostScript glyph name and
+.I groff-char
+is a
+.I groff
+ordinary
+(if of unit length)
+or special
+(if longer)
+character identifier.
+.
+The same
+.I ps-glyph
+can occur multiple times in the file;
+each
+.I groff-char
+must occur at most once.
+.
+Lines starting with \[lq]#\[rq] and blank lines are ignored.
+.
+If the file isn't found in the current directory,
+it is sought in the
+.I devps/generate
+subdirectory of the default font directory.
+.
+.
+.LP
+If a PostScript glyph is not mentioned in
+.IR map-file ,
+and a
+.I groff
+character name can't be deduced using the Adobe Glyph List
+(AGL,
+built into
+.IR afmtodit ),
+then
+.I \%afmtodit
+puts the PostScript glyph into the
+.I groff
+font description file as an unnamed glyph which can only be accessed
+by the \[lq]\eN\[rq] escape sequence in a
+.I roff
+document.
+.
+In particular,
+this is true for glyph variants named in the form
+.RI \[lq] foo . bar \[rq];
+all glyph names containing one or more periods are mapped to unnamed
+entities.
+.
+Unless
+.B \-e
+is specified,
+the encoding defined in the AFM file
+(i.e.,
+entries
+with non-negative codes)
+is used.
+.
+Refer to section \[lq]Using Symbols\[rq] in
+.IR "Groff: The GNU Implementation of troff" ,
+the
+.I groff
+Texinfo manual,
+or
+.MR groff_char @MAN7EXT@ ,
+which describe how
+.I groff
+character identifiers are constructed.
+.
+.
+.LP
+Glyphs not encoded in the AFM file
+(i.e.,
+entries indexed as \[lq]\-1\[rq])
+are still available in
+.IR groff ;
+they get glyph index values greater than 255
+(or greater than the biggest code used in the AFM file in the unlikely
+case that it is greater than 255)
+in the
+.I groff
+font description file.
+.
+Unencoded glyph indices don't have a specific order;
+it is best to access them only via special character identifiers.
+.
+.
+.P
+If the font file proper
+(not just its metrics)
+is available,
+listing it in the files
+.I @FONTDIR@/\:\%devps/\:\%download
+and
+.I @FONTDIR@/\:\%devpdf/\:\%download
+enables it to be embedded in the output produced by
+.MR grops @MAN1EXT@
+and
+.MR gropdf @MAN1EXT@ ,
+respectively.
+.
+.
+.P
+If the
+.B \-i
+option is used,
+.I \%afmtodit
+automatically generates an italic correction,
+a left italic correction,
+and a subscript correction for each glyph
+(the significance of these is explained in
+.MR groff_font @MAN5EXT@ );
+they can be specified for individual glyphs by
+adding to the
+.I afm-file
+lines of the form:
+.
+.RS
+.EX
+.RI italicCorrection \~ps-glyph\~n
+.RI leftItalicCorrection \~ps-glyph\~n
+.RI subscriptCorrection \~ps-glyph\~n
+.EE
+.RE
+.
+where
+.I ps-glyph
+is the PostScript glyph name,
+and
+.I n
+is the desired value of the corresponding parameter in thousandths of an
+em.
+.
+Such parameters are normally needed only for italic
+(or oblique)
+fonts.
+.
+.
+.P
+The
+.B \-s
+option should be given if the font is \[lq]special\[rq],
+meaning that
+.I groff
+should search it whenever a glyph is not found in the current font.
+.
+In that case,
+.I font-description-file
+should be listed as an argument to the
+.B fonts
+directive in the output device's
+.I DESC
+file;
+if it is not special,
+there is no need to do so,
+since
+.MR @g@troff @MAN1EXT@
+will automatically mount it when it is first used.
+.
+.
+.br
+.ne 7v
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \%\-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-a\~ slant
+Use
+.I slant
+as the slant (\[lq]angle\[rq]) parameter in the font description file;
+this is used by
+.I groff
+in the positioning of accents.
+.
+By default
+.I \%afmtodit
+uses the negative of the
+.B \%ItalicAngle
+specified in the AFM file;
+with true italic fonts it is sometimes desirable to use a slant that is
+less than this.
+.
+If you find that an italic font places accents over base glyphs
+too far to the right,
+use
+.B \-a
+to give it a smaller slant.
+.
+.
+.TP
+.B \-c
+Include comments in the font description file identifying the PostScript
+font.
+.
+.
+.TP
+.BI \-d\~ device-description-file
+The device description file is
+.I desc-file
+rather than the default
+.IR DESC .
+.
+If not found in the current directory,
+the
+.I devps
+subdirectory of the default font directory is searched
+(this is true for both the default device description file and a file
+given with option
+.BR \-d ).
+.
+.
+.TP
+.BI \-e\~ encoding-file
+The PostScript font should be reencoded to use the encoding described
+in
+.IR enc-file .
+.
+The format of
+.I enc-file
+is described in
+.MR grops @MAN1EXT@ .
+.
+If not found in the current directory,
+the
+.I devps
+subdirectory of the default font directory is searched.
+.
+.
+.TP
+.BI \-f\~ internal-name
+The internal name of the
+.I groff
+font is set to
+.IR name .
+.
+.
+.TP
+.BI \-i\~ italic-correction-factor
+Generate an italic correction for each glyph so that its width plus its
+italic correction is equal to
+.I italic-correction-factor
+thousandths of an em
+plus the amount by which the right edge of the glyph's bounding box is
+to the right of its origin.
+.
+If this would result in a negative italic correction,
+use a zero italic correction instead.
+.
+.
+.IP
+Also generate a subscript correction equal to the
+product of the tangent of the slant of the font and
+four fifths of the x-height of the font.
+.
+If this would result in a subscript correction greater than the italic
+correction,
+use a subscript correction equal to the italic correction instead.
+.
+.
+.IP
+Also generate a left italic correction for each glyph equal to
+.I italic-correction-factor
+thousandths of an em
+plus the amount by which the left edge of the glyph's bounding box is to
+the left of its origin.
+.
+The left italic correction may be negative unless option
+.B \-m
+is given.
+.
+.
+.IP
+This option is normally needed only with italic
+(or oblique)
+fonts.
+.
+The font description files distributed with
+.I groff
+were created using an option of
+.B \-i50
+for italic fonts.
+.
+.
+.TP
+.BI \-o\~ output-file
+Write to
+.I output-file
+instead of
+.I font-description-file.
+.
+.
+.TP
+.B \-k
+Omit any kerning data from the
+.I groff
+font;
+use only for monospaced (constant-width) fonts.
+.
+.
+.TP
+.B \-m
+Prevent negative left italic correction values.
+.
+Font description files for roman styles distributed with
+.I groff
+were created with
+.RB \[lq] \-i0\~\-m \[rq]
+to improve spacing with
+.MR @g@eqn @MAN1EXT@ .
+.
+.
+.TP
+.B \-n
+Don't output a
+.B ligatures
+command for this font;
+use with monospaced (constant-width) fonts.
+.
+.
+.TP
+.B \-s
+Add the
+.B special
+directive to the font description file.
+.
+.
+.TP
+.BI \-w\~ space-width
+Use
+.I space-width
+as the with of inter-word spaces.
+.
+.
+.TP
+.B \-x
+Don't use the built-in Adobe Glyph List.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:DESC
+describes the
+.B ps
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devps/ F
+describes the font known
+.RI as\~ F
+on device
+.BR ps .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%download
+lists fonts available for embedding within the PostScript document
+(or download to the device).
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%generate/\:\%dingbats.map
+.TQ
+.I @FONTDIR@/\:\%devps/\:\%generate/\:\%dingbats\-reversed.map
+.TQ
+.I @FONTDIR@/\:\%devps/\:\%generate/\:\%slanted\-symbol.map
+.TQ
+.I @FONTDIR@/\:\%devps/\:\%generate/\:\%symbol.map
+.TQ
+.I @FONTDIR@/\:\%devps/\:\%generate/\:\%text.map
+map names in the Adobe Glyph List to
+.I groff
+special character identifiers for Zapf Dingbats
+.RB ( ZD ),
+reversed Zapf Dingbats
+.RB ( ZDR ),
+slanted symbol
+.RB ( SS ),
+symbol
+.RB ( S ),
+and text fonts,
+respectively.
+.
+These
+.IR map-file s
+are used to produce the font description files provided with
+.I groff
+for the
+.I \%grops
+output driver.
+.
+.
+.\" ====================================================================
+.SH Diagnostics
+.\" ====================================================================
+.
+.TP
+.RI "AGL name \[aq]" x "\[aq] already mapped to groff name \[aq]" y\c
+.RI "\[aq]; ignoring AGL name \[aq]uni" XXXX \[aq]
+You can disregard these if they're in the form shown,
+where the ignored AGL name contains four hexadecimal digits
+.IR XXXX .
+.
+The Adobe Glyph List (AGL) has its own names for glyphs;
+they are often
+different from
+.IR groff 's
+special character names.
+.
+.I \%afmtodit
+is constructing a mapping from
+.I groff
+special character names to AGL names;
+this can be a one-to-one or many-to-one mapping,
+but one-to-many will not work,
+so
+.I \%afmtodit
+discards the excess mappings.
+.
+For example,
+if
+.I x
+is
+.BR *D ,
+.I y
+is
+.BR \%Delta ,
+and
+.I z
+is
+.BR uni0394 ,
+.I \%afmtodit
+is telling you that the
+.I groff
+font description that it is writing cannot map the
+.I groff
+special character
+.B \[rs][*D]
+to AGL glyphs
+.B \%Delta
+and
+.B uni0394
+at the same time.
+.
+.
+.IP
+If you get a message like this but are unhappy with which mapping is
+ignored,
+a remedy is to craft an alternative
+.I map-file
+and re-run
+.I \%afmtodit
+using it.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.IR "Groff: The GNU Implementation of troff" ,
+by Trent A.\& Fisher and Werner Lemberg,
+is the primary
+.I groff
+manual.
+.
+Section \[lq]Using Symbols\[rq] may be of particular note.
+.
+You can browse it interactively with \[lq]info \[aq](groff)Using
+\%Symbols\[aq]\[rq].
+.
+.
+.LP
+.MR groff @MAN1EXT@ ,
+.MR gropdf @MAN1EXT@ ,
+.MR grops @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_afmtodit_1_man_C]
+.do rr *groff_afmtodit_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/afmtodit/afmtodit.am b/src/utils/afmtodit/afmtodit.am
new file mode 100644
index 0000000..fda095d
--- /dev/null
+++ b/src/utils/afmtodit/afmtodit.am
@@ -0,0 +1,56 @@
+# Automake rules for 'src utils afmtodit'
+#
+# Copyright (C) 2013-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+########################################################################
+
+afmtodit_srcdir = $(top_srcdir)/src/utils/afmtodit
+
+bin_SCRIPTS += afmtodit
+man1_MANS += src/utils/afmtodit/afmtodit.1
+EXTRA_DIST += \
+ src/utils/afmtodit/afmtodit.1.man \
+ src/utils/afmtodit/afmtodit.pl \
+ src/utils/afmtodit/afmtodit.tables \
+ src/utils/afmtodit/make-afmtodit-tables
+
+afmtodit: $(afmtodit_srcdir)/afmtodit.pl $(afmtodit_srcdir)/afmtodit.tables
+ $(AM_V_GEN)if test -n "$(PERL)"; then \
+ sed -e "s|[@]PERL[@]|$(PERL)|" \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ -e "s|[@]FONTDIR[@]|$(fontdir)|" \
+ -e "/[@]afmtodit.tables[@]/ r $(afmtodit_srcdir)/afmtodit.tables" \
+ -e "/[@]afmtodit.tables[@]/ d" \
+ $(afmtodit_srcdir)/afmtodit.pl \
+ >afmtodit; \
+ else \
+ sed -e "s|[@]VERSION[@]|$(VERSION)|" \
+ -e "s|[@]FONTDIR[@]|$(fontdir)|" \
+ -e "/[@]afmtodit.tables[@]/ r $(afmtodit_srcdir)/afmtodit.tables" \
+ -e "/[@]afmtodit.tables[@]/ d" \
+ $(afmtodit_srcdir)/afmtodit.pl \
+ >afmtodit; \
+ fi \
+ && chmod +x afmtodit
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/afmtodit/afmtodit.pl b/src/utils/afmtodit/afmtodit.pl
new file mode 100644
index 0000000..c6b67cc
--- /dev/null
+++ b/src/utils/afmtodit/afmtodit.pl
@@ -0,0 +1,645 @@
+#!@PERL@
+# Copyright (C) 1989-2020 Free Software Foundation, Inc.
+# Written by James Clark (jjc@jclark.com)
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use warnings;
+use strict;
+
+@afmtodit.tables@
+
+my $prog = $0;
+my $groff_sys_fontdir = "@FONTDIR@";
+my $want_help;
+my $space_width = 0;
+
+our ($opt_a, $opt_c, $opt_d, $opt_e, $opt_f, $opt_i, $opt_k,
+ $opt_m, $opt_n, $opt_o, $opt_s, $opt_v, $opt_x);
+
+use Getopt::Long qw(:config gnu_getopt);
+GetOptions( "a=s", "c", "d=s", "e=s", "f=s", "i=s", "k", "m", "n",
+ "o=s", "s", "v", "w=i" => \$space_width, "x", "version" => \$opt_v,
+ "help" => \$want_help
+);
+
+my $afmtodit_version = "GNU afmtodit (groff) version @VERSION@";
+
+if ($opt_v) {
+ print "$afmtodit_version\n";
+ exit 0;
+}
+
+sub croak {
+ my $msg = shift;
+ print STDERR "$prog: error: $msg";
+ exit(1);
+}
+
+sub usage {
+ my $stream = *STDOUT;
+ my $had_error = shift;
+ $stream = *STDERR if $had_error;
+ print $stream "usage: $prog [-ckmnsx] [-a slant]" .
+ " [-d device-description-file] [-e encoding-file]" .
+ " [-f internal-name] [-i italic-correction-factor]" .
+ " [-o output-file] [-w space-width] afm-file map-file" .
+ " font-description-file\n" .
+ "usage: $prog {-v | --version}\n" .
+ "usage: $prog --help\n";
+ unless ($had_error) {
+ print $stream "\n" .
+"Adapt an Adobe Font Metric file, afm-file, for use with the 'ps'\n" .
+"and 'pdf' output devices of groff(1). See the afmtodit(1) manual " .
+"page.\n";
+ }
+ my $status = 0;
+ $status = 2 if ($had_error);
+ exit($status);
+}
+
+&usage(0) if ($want_help);
+
+if ($#ARGV != 2) {
+ print STDERR "$prog: usage error: insufficient arguments\n";
+ &usage(1);
+}
+
+my $afm = $ARGV[0];
+my $map = $ARGV[1];
+my $fontfile = $ARGV[2];
+my $outfile = $opt_o || $fontfile;
+my $desc = $opt_d || "DESC";
+my $sys_map = $groff_sys_fontdir . "/devps/generate/" . $map;
+my $sys_desc = $groff_sys_fontdir . "/devps/" . $desc;
+
+# read the afm file
+
+my $psname;
+my ($notice, $version, $fullname, $familyname, @comments);
+my $italic_angle = 0;
+my (@kern1, @kern2, @kernx);
+my (%italic_correction, %left_italic_correction);
+my %subscript_correction;
+# my %ligs
+my %ligatures;
+my (@encoding, %in_encoding);
+my (%width, %height, %depth);
+my (%left_side_bearing, %right_side_bearing);
+
+open(AFM, $afm) || croak("unable to open '$ARGV[0]': $!\n");
+
+while (<AFM>) {
+ chomp;
+ s/\x0D$//;
+ my @field = split(' ');
+ next if $#field < 0;
+ if ($field[0] eq "FontName") {
+ $psname = $field[1];
+ if($opt_f) {
+ $psname = $opt_f;
+ }
+ }
+ elsif($field[0] eq "Notice") {
+ $notice = $_;
+ }
+ elsif($field[0] eq "Version") {
+ $version = $_;
+ }
+ elsif($field[0] eq "FullName") {
+ $fullname = $_;
+ }
+ elsif($field[0] eq "FamilyName") {
+ $familyname = $_;
+ }
+ elsif($field[0] eq "Comment") {
+ push(@comments, $_);
+ }
+ elsif($field[0] eq "ItalicAngle") {
+ $italic_angle = -$field[1];
+ }
+ elsif ($field[0] eq "KPX") {
+ if ($#field == 3) {
+ push(@kern1, $field[1]);
+ push(@kern2, $field[2]);
+ push(@kernx, $field[3]);
+ }
+ }
+ elsif ($field[0] eq "italicCorrection") {
+ $italic_correction{$field[1]} = $field[2];
+ }
+ elsif ($field[0] eq "leftItalicCorrection") {
+ $left_italic_correction{$field[1]} = $field[2];
+ }
+ elsif ($field[0] eq "subscriptCorrection") {
+ $subscript_correction{$field[1]} = $field[2];
+ }
+ elsif ($field[0] eq "StartCharMetrics") {
+ while (<AFM>) {
+ @field = split(' ');
+ next if $#field < 0;
+ last if ($field[0] eq "EndCharMetrics");
+ if ($field[0] eq "C") {
+ my $w;
+ my $wx = 0;
+ my $n = "";
+# %ligs = ();
+ my $lly = 0;
+ my $ury = 0;
+ my $llx = 0;
+ my $urx = 0;
+ my $c = $field[1];
+ my $i = 2;
+ while ($i <= $#field) {
+ if ($field[$i] eq "WX") {
+ $w = $field[$i + 1];
+ $i += 2;
+ }
+ elsif ($field[$i] eq "N") {
+ $n = $field[$i + 1];
+ $i += 2;
+ }
+ elsif ($field[$i] eq "B") {
+ $llx = $field[$i + 1];
+ $lly = $field[$i + 2];
+ $urx = $field[$i + 3];
+ $ury = $field[$i + 4];
+ $i += 5;
+ }
+# elsif ($field[$i] eq "L") {
+# $ligs{$field[$i + 2]} = $field[$i + 1];
+# $i += 3;
+# }
+ else {
+ while ($i <= $#field && $field[$i] ne ";") {
+ $i++;
+ }
+ $i++;
+ }
+ }
+ if (!$opt_e && $c != -1) {
+ $encoding[$c] = $n;
+ $in_encoding{$n} = 1;
+ }
+ $width{$n} = $w;
+ $height{$n} = $ury;
+ $depth{$n} = -$lly;
+ $left_side_bearing{$n} = -$llx;
+ $right_side_bearing{$n} = $urx - $w;
+# foreach my $lig (sort keys %ligs) {
+# $ligatures{$lig} = $n . " " . $ligs{$lig};
+# }
+ }
+ }
+ }
+}
+close(AFM);
+
+# read the DESC file
+
+my ($sizescale, $resolution, $unitwidth);
+$sizescale = 1;
+
+open(DESC, $desc) || open(DESC, $sys_desc) ||
+ croak("unable to open '$desc' or '$sys_desc': $!\n");
+while (<DESC>) {
+ next if /^#/;
+ chop;
+ my @field = split(' ');
+ next if $#field < 0;
+ last if $field[0] eq "charset";
+ if ($field[0] eq "res") {
+ $resolution = $field[1];
+ }
+ elsif ($field[0] eq "unitwidth") {
+ $unitwidth = $field[1];
+ }
+ elsif ($field[0] eq "sizescale") {
+ $sizescale = $field[1];
+ }
+}
+close(DESC);
+
+if ($opt_e) {
+ # read the encoding file
+
+ my $sys_opt_e = $groff_sys_fontdir . "/devps/" . $opt_e;
+ open(ENCODING, $opt_e) || open(ENCODING, $sys_opt_e) ||
+ croak("unable to open '$opt_e' or '$sys_opt_e': $!\n");
+ while (<ENCODING>) {
+ next if /^#/;
+ chop;
+ my @field = split(' ');
+ next if $#field < 0;
+ if ($#field == 1) {
+ if ($field[1] >= 0 && defined $width{$field[0]}) {
+ $encoding[$field[1]] = $field[0];
+ $in_encoding{$field[0]} = 1;
+ }
+ }
+ }
+ close(ENCODING);
+}
+
+# read the map file
+
+my (%nmap, %map);
+
+open(MAP, $map) || open(MAP, $sys_map) ||
+ croak("unable to open '$map' or '$sys_map': $!\n");
+while (<MAP>) {
+ next if /^#/;
+ chop;
+ my @field = split(' ');
+ next if $#field < 0;
+ if ($#field == 1) {
+ if ($field[1] eq "space") {
+ # The PostScript character "space" is automatically mapped
+ # to the groff character "space"; this is for grops.
+ warn "$prog: you are not allowed to map to " .
+ "the groff character 'space'";
+ }
+ elsif ($field[0] eq "space") {
+ warn "$prog: you are not allowed to map " .
+ "the PostScript character 'space'";
+ }
+ else {
+ $nmap{$field[0]} += 0;
+ $map{$field[0], $nmap{$field[0]}} = $field[1];
+ $nmap{$field[0]} += 1;
+
+ # There is more than one way to make a PS glyph name;
+ # let us try Unicode names with both 'uni' and 'u' prefixes.
+ my $utmp = $AGL_to_unicode{$field[0]};
+ if (defined $utmp && $utmp =~ /^[0-9A-F]{4}$/) {
+ foreach my $unicodepsname ("uni" . $utmp, "u" . $utmp) {
+ $nmap{$unicodepsname} += 0;
+ $map{$unicodepsname, $nmap{$unicodepsname}} = $field[1];
+ $nmap{$unicodepsname} += 1;
+ }
+ }
+ }
+ }
+}
+close(MAP);
+
+$italic_angle = $opt_a if $opt_a;
+
+
+if (!$opt_x) {
+ my %mapped;
+ my $i = ($#encoding > 256) ? ($#encoding + 1) : 256;
+ foreach my $ch (sort keys %width) {
+ # add unencoded characters
+ if (!$in_encoding{$ch}) {
+ $encoding[$i] = $ch;
+ $i++;
+ }
+ if ($nmap{$ch}) {
+ for (my $j = 0; $j < $nmap{$ch}; $j++) {
+ if (defined $mapped{$map{$ch, $j}}) {
+ print STDERR "$prog: AGL name"
+ . " '$mapped{$map{$ch, $j}}' already mapped to"
+ . " groff name '$map{$ch, $j}'; ignoring AGL"
+ . " name '$ch'\n";
+ }
+ else {
+ $mapped{$map{$ch, $j}} = $ch;
+ }
+ }
+ }
+ else {
+ my $u = ""; # the resulting groff glyph name
+ my $ucomp = ""; # Unicode string before decomposition
+ my $utmp = ""; # temporary value
+ my $component = "";
+ my $nv = 0;
+
+ # Step 1:
+ # Drop all characters from the glyph name starting with the
+ # first occurrence of a period (U+002E FULL STOP), if any.
+ # ?? We avoid mapping of glyphs with periods, since they are
+ # likely to be variant glyphs, leading to a 'many ps glyphs --
+ # one groff glyph' conflict.
+ #
+ # If multiple glyphs in the font represent the same character
+ # in the Unicode standard, as do 'A' and 'A.swash', for example,
+ # they can be differentiated by using the same base name with
+ # different suffixes. This suffix (the part of glyph name that
+ # follows the first period) does not participate in the
+ # computation of a character sequence. It can be used by font
+ # designers to indicate some characteristics of the glyph. The
+ # suffix may contain periods or any other permitted characters.
+ # Small cap A, for example, could be named 'uni0041.sc' or
+ # 'A.sc'.
+
+ next if $ch =~ /\./;
+
+ # Step 2:
+ # Split the remaining string into a sequence of components,
+ # using the underscore character (U+005F LOW LINE) as the
+ # delimiter.
+
+ while ($ch =~ /([^_]+)/g) {
+ $component = $1;
+
+ # Step 3:
+ # Map each component to a character string according to the
+ # procedure below:
+ #
+ # * If the component is in the Adobe Glyph List, then map
+ # it to the corresponding character in that list.
+
+ $utmp = $AGL_to_unicode{$component};
+ if ($utmp) {
+ $utmp = "U+" . $utmp;
+ }
+
+ # * Otherwise, if the component is of the form 'uni'
+ # (U+0075 U+006E U+0069) followed by a sequence of
+ # uppercase hexadecimal digits (0 .. 9, A .. F, i.e.,
+ # U+0030 .. U+0039, U+0041 .. U+0046), the length of
+ # that sequence is a multiple of four, and each group of
+ # four digits represents a number in the set {0x0000 ..
+ # 0xD7FF, 0xE000 .. 0xFFFF}, then interpret each such
+ # number as a Unicode scalar value and map the component
+ # to the string made of those scalar values.
+
+ elsif ($component =~ /^uni([0-9A-F]{4})+$/) {
+ while ($component =~ /([0-9A-F]{4})/g) {
+ $nv = hex("0x" . $1);
+ if ($nv <= 0xD7FF || $nv >= 0xE000) {
+ $utmp .= "U+" . $1;
+ }
+ else {
+ $utmp = "";
+ last;
+ }
+ }
+ }
+
+ # * Otherwise, if the component is of the form 'u' (U+0075)
+ # followed by a sequence of four to six uppercase
+ # hexadecimal digits {0 .. 9, A .. F} (U+0030 .. U+0039,
+ # U+0041 .. U+0046), and those digits represent a number
+ # in {0x0000 .. 0xD7FF, 0xE000 .. 0x10FFFF}, then
+ # interpret this number as a Unicode scalar value and map
+ # the component to the string made of this scalar value.
+
+ elsif ($component =~ /^u([0-9A-F]{4,6})$/) {
+ $nv = hex("0x" . $1);
+ if ($nv <= 0xD7FF || ($nv >= 0xE000 && $nv <= 0x10FFFF)) {
+ $utmp = "U+" . $1;
+ }
+ }
+
+ # Finally, concatenate those strings; the result is the
+ # character string to which the glyph name is mapped.
+
+ $ucomp .= $utmp if $utmp;
+ }
+
+ # Unicode decomposition
+ while ($ucomp =~ /([0-9A-F]{4,6})/g) {
+ $component = $1;
+ $utmp = $unicode_decomposed{$component};
+ $u .= "_" . ($utmp ? $utmp : $component);
+ }
+ $u =~ s/^_/u/;
+ if ($u) {
+ if (defined $mapped{$u}) {
+ warn "$prog: both $mapped{$u} and $ch map to $u";
+ }
+ else {
+ $mapped{$u} = $ch;
+ }
+ $nmap{$ch} += 1;
+ $map{$ch, "0"} = $u;
+ }
+ }
+ }
+}
+
+# Check explicitly for groff's standard ligatures -- many afm files don't
+# have proper 'L' entries.
+
+my %default_ligatures = (
+ "fi", "f i",
+ "fl", "f l",
+ "ff", "f f",
+ "ffi", "ff i",
+ "ffl", "ff l",
+);
+
+foreach my $lig (sort keys %default_ligatures) {
+ if (defined $width{$lig} && !defined $ligatures{$lig}) {
+ $ligatures{$lig} = $default_ligatures{$lig};
+ }
+}
+
+# print it all out
+
+open(FONT, ">$outfile") ||
+ croak("unable to open '$outfile' for writing: $!\n");
+select(FONT);
+
+print("# This file was generated with $afmtodit_version.\n");
+print("#\n");
+print("# $fullname\n") if defined $fullname;
+print("# $version\n") if defined $version;
+print("# $familyname\n") if defined $familyname;
+
+if ($opt_c) {
+ print("#\n");
+ if (defined $notice || @comments) {
+ print("# The original AFM file contains the following comments:\n");
+ print("#\n");
+ print("# $notice\n") if defined $notice;
+ foreach my $comment (@comments) {
+ print("# $comment\n");
+ }
+ }
+ else {
+ print("# The original AFM file contains no comments.\n");
+ }
+}
+
+print("\n");
+
+my $name = $fontfile;
+$name =~ s@.*/@@;
+
+my $sw = 0;
+$sw = conv($width{"space"}) if defined $width{"space"};
+$sw = $space_width if ($space_width);
+
+print("name $name\n");
+print("internalname $psname\n") if $psname;
+print("special\n") if $opt_s;
+printf("slant %g\n", $italic_angle) if $italic_angle != 0;
+printf("spacewidth %d\n", $sw) if $sw;
+
+if ($opt_e) {
+ my $e = $opt_e;
+ $e =~ s@.*/@@;
+ print("encoding $e\n");
+}
+
+if (!$opt_n && %ligatures) {
+ print("ligatures");
+ foreach my $lig (sort keys %ligatures) {
+ print(" $lig");
+ }
+ print(" 0\n");
+}
+
+if (!$opt_k && $#kern1 >= 0) {
+ print("\n");
+ print("kernpairs\n");
+
+ for (my $i = 0; $i <= $#kern1; $i++) {
+ my $c1 = $kern1[$i];
+ my $c2 = $kern2[$i];
+ if (defined $nmap{$c1} && $nmap{$c1} != 0
+ && defined $nmap{$c2} && $nmap{$c2} != 0) {
+ for (my $j = 0; $j < $nmap{$c1}; $j++) {
+ for (my $k = 0; $k < $nmap{$c2}; $k++) {
+ if ($kernx[$i] != 0) {
+ printf("%s %s %d\n",
+ $map{$c1, $j},
+ $map{$c2, $k},
+ conv($kernx[$i]));
+ }
+ }
+ }
+ }
+ }
+}
+
+my ($asc_boundary, $desc_boundary, $xheight, $slant);
+
+# characters not shorter than asc_boundary are considered to have ascenders
+
+$asc_boundary = 0;
+$asc_boundary = $height{"t"} if defined $height{"t"};
+$asc_boundary -= 1;
+
+# likewise for descenders
+
+$desc_boundary = 0;
+$desc_boundary = $depth{"g"} if defined $depth{"g"};
+$desc_boundary = $depth{"j"} if defined $depth{"g"} && $depth{"j"} < $desc_boundary;
+$desc_boundary = $depth{"p"} if defined $depth{"p"} && $depth{"p"} < $desc_boundary;
+$desc_boundary = $depth{"q"} if defined $depth{"q"} && $depth{"q"} < $desc_boundary;
+$desc_boundary = $depth{"y"} if defined $depth{"y"} && $depth{"y"} < $desc_boundary;
+$desc_boundary -= 1;
+
+if (defined $height{"x"}) {
+ $xheight = $height{"x"};
+}
+elsif (defined $height{"alpha"}) {
+ $xheight = $height{"alpha"};
+}
+else {
+ $xheight = 450;
+}
+
+$italic_angle = $italic_angle*3.14159265358979323846/180.0;
+$slant = sin($italic_angle)/cos($italic_angle);
+$slant = 0 if $slant < 0;
+
+print("\n");
+print("charset\n");
+for (my $i = 0; $i <= $#encoding; $i++) {
+ my $ch = $encoding[$i];
+ if (defined $ch && $ch ne "" && $ch ne "space") {
+ $map{$ch, "0"} = "---" if !defined $nmap{$ch} || $nmap{$ch} == 0;
+ my $type = 0;
+ my $h = $height{$ch};
+ $h = 0 if $h < 0;
+ my $d = $depth{$ch};
+ $d = 0 if $d < 0;
+ $type = 1 if $d >= $desc_boundary;
+ $type += 2 if $h >= $asc_boundary;
+ printf("%s\t%d", $map{$ch, "0"}, conv($width{$ch}));
+ my $italic_correction = 0;
+ my $left_math_fit = 0;
+ my $subscript_correction = 0;
+ if (defined $opt_i) {
+ $italic_correction = $right_side_bearing{$ch} + $opt_i;
+ $italic_correction = 0 if $italic_correction < 0;
+ $subscript_correction = $slant * $xheight * .8;
+ $subscript_correction = $italic_correction if
+ $subscript_correction > $italic_correction;
+ $left_math_fit = $left_side_bearing{$ch} + $opt_i;
+ if (defined $opt_m) {
+ $left_math_fit = 0 if $left_math_fit < 0;
+ }
+ }
+ if (defined $italic_correction{$ch}) {
+ $italic_correction = $italic_correction{$ch};
+ }
+ if (defined $left_italic_correction{$ch}) {
+ $left_math_fit = $left_italic_correction{$ch};
+ }
+ if (defined $subscript_correction{$ch}) {
+ $subscript_correction = $subscript_correction{$ch};
+ }
+ if ($subscript_correction != 0) {
+ printf(",%d,%d", conv($h), conv($d));
+ printf(",%d,%d,%d", conv($italic_correction),
+ conv($left_math_fit),
+ conv($subscript_correction));
+ }
+ elsif ($left_math_fit != 0) {
+ printf(",%d,%d", conv($h), conv($d));
+ printf(",%d,%d", conv($italic_correction),
+ conv($left_math_fit));
+ }
+ elsif ($italic_correction != 0) {
+ printf(",%d,%d", conv($h), conv($d));
+ printf(",%d", conv($italic_correction));
+ }
+ elsif ($d != 0) {
+ printf(",%d,%d", conv($h), conv($d));
+ }
+ else {
+ # always put the height in to stop groff guessing
+ printf(",%d", conv($h));
+ }
+ printf("\t%d", $type);
+ printf("\t%d\t%s\n", $i, $ch);
+ if (defined $nmap{$ch}) {
+ for (my $j = 1; $j < $nmap{$ch}; $j++) {
+ printf("%s\t\"\n", $map{$ch, $j});
+ }
+ }
+ }
+ if (defined $ch && $ch eq "space" && defined $width{"space"}) {
+ printf("space\t%d\t0\t%d\tspace\n", conv($width{"space"}), $i);
+ }
+}
+
+sub conv {
+ $_[0]*$unitwidth*$resolution/(72*1000*$sizescale) +
+ ($_[0] < 0 ? -.5 : .5);
+}
+
+# Local Variables:
+# fill-column: 72
+# mode: CPerl
+# End:
+# vim: set cindent noexpandtab shiftwidth=2 softtabstop=2 textwidth=72:
diff --git a/src/utils/afmtodit/afmtodit.tables b/src/utils/afmtodit/afmtodit.tables
new file mode 100644
index 0000000..16e3647
--- /dev/null
+++ b/src/utils/afmtodit/afmtodit.tables
@@ -0,0 +1,6163 @@
+# This table was algorithmically derived from the file 'UnicodeData.txt'
+# for Unicode 15.0.0, available from unicode.org,
+# on 2022-10-09.
+my %unicode_decomposed = (
+ "00C0", "0041_0300",
+ "00C1", "0041_0301",
+ "00C2", "0041_0302",
+ "00C3", "0041_0303",
+ "00C4", "0041_0308",
+ "00C5", "0041_030A",
+ "00C7", "0043_0327",
+ "00C8", "0045_0300",
+ "00C9", "0045_0301",
+ "00CA", "0045_0302",
+ "00CB", "0045_0308",
+ "00CC", "0049_0300",
+ "00CD", "0049_0301",
+ "00CE", "0049_0302",
+ "00CF", "0049_0308",
+ "00D1", "004E_0303",
+ "00D2", "004F_0300",
+ "00D3", "004F_0301",
+ "00D4", "004F_0302",
+ "00D5", "004F_0303",
+ "00D6", "004F_0308",
+ "00D9", "0055_0300",
+ "00DA", "0055_0301",
+ "00DB", "0055_0302",
+ "00DC", "0055_0308",
+ "00DD", "0059_0301",
+ "00E0", "0061_0300",
+ "00E1", "0061_0301",
+ "00E2", "0061_0302",
+ "00E3", "0061_0303",
+ "00E4", "0061_0308",
+ "00E5", "0061_030A",
+ "00E7", "0063_0327",
+ "00E8", "0065_0300",
+ "00E9", "0065_0301",
+ "00EA", "0065_0302",
+ "00EB", "0065_0308",
+ "00EC", "0069_0300",
+ "00ED", "0069_0301",
+ "00EE", "0069_0302",
+ "00EF", "0069_0308",
+ "00F1", "006E_0303",
+ "00F2", "006F_0300",
+ "00F3", "006F_0301",
+ "00F4", "006F_0302",
+ "00F5", "006F_0303",
+ "00F6", "006F_0308",
+ "00F9", "0075_0300",
+ "00FA", "0075_0301",
+ "00FB", "0075_0302",
+ "00FC", "0075_0308",
+ "00FD", "0079_0301",
+ "00FF", "0079_0308",
+ "0100", "0041_0304",
+ "0101", "0061_0304",
+ "0102", "0041_0306",
+ "0103", "0061_0306",
+ "0104", "0041_0328",
+ "0105", "0061_0328",
+ "0106", "0043_0301",
+ "0107", "0063_0301",
+ "0108", "0043_0302",
+ "0109", "0063_0302",
+ "010A", "0043_0307",
+ "010B", "0063_0307",
+ "010C", "0043_030C",
+ "010D", "0063_030C",
+ "010E", "0044_030C",
+ "010F", "0064_030C",
+ "0112", "0045_0304",
+ "0113", "0065_0304",
+ "0114", "0045_0306",
+ "0115", "0065_0306",
+ "0116", "0045_0307",
+ "0117", "0065_0307",
+ "0118", "0045_0328",
+ "0119", "0065_0328",
+ "011A", "0045_030C",
+ "011B", "0065_030C",
+ "011C", "0047_0302",
+ "011D", "0067_0302",
+ "011E", "0047_0306",
+ "011F", "0067_0306",
+ "0120", "0047_0307",
+ "0121", "0067_0307",
+ "0122", "0047_0327",
+ "0123", "0067_0327",
+ "0124", "0048_0302",
+ "0125", "0068_0302",
+ "0128", "0049_0303",
+ "0129", "0069_0303",
+ "012A", "0049_0304",
+ "012B", "0069_0304",
+ "012C", "0049_0306",
+ "012D", "0069_0306",
+ "012E", "0049_0328",
+ "012F", "0069_0328",
+ "0130", "0049_0307",
+ "0134", "004A_0302",
+ "0135", "006A_0302",
+ "0136", "004B_0327",
+ "0137", "006B_0327",
+ "0139", "004C_0301",
+ "013A", "006C_0301",
+ "013B", "004C_0327",
+ "013C", "006C_0327",
+ "013D", "004C_030C",
+ "013E", "006C_030C",
+ "0143", "004E_0301",
+ "0144", "006E_0301",
+ "0145", "004E_0327",
+ "0146", "006E_0327",
+ "0147", "004E_030C",
+ "0148", "006E_030C",
+ "014C", "004F_0304",
+ "014D", "006F_0304",
+ "014E", "004F_0306",
+ "014F", "006F_0306",
+ "0150", "004F_030B",
+ "0151", "006F_030B",
+ "0154", "0052_0301",
+ "0155", "0072_0301",
+ "0156", "0052_0327",
+ "0157", "0072_0327",
+ "0158", "0052_030C",
+ "0159", "0072_030C",
+ "015A", "0053_0301",
+ "015B", "0073_0301",
+ "015C", "0053_0302",
+ "015D", "0073_0302",
+ "015E", "0053_0327",
+ "015F", "0073_0327",
+ "0160", "0053_030C",
+ "0161", "0073_030C",
+ "0162", "0054_0327",
+ "0163", "0074_0327",
+ "0164", "0054_030C",
+ "0165", "0074_030C",
+ "0168", "0055_0303",
+ "0169", "0075_0303",
+ "016A", "0055_0304",
+ "016B", "0075_0304",
+ "016C", "0055_0306",
+ "016D", "0075_0306",
+ "016E", "0055_030A",
+ "016F", "0075_030A",
+ "0170", "0055_030B",
+ "0171", "0075_030B",
+ "0172", "0055_0328",
+ "0173", "0075_0328",
+ "0174", "0057_0302",
+ "0175", "0077_0302",
+ "0176", "0059_0302",
+ "0177", "0079_0302",
+ "0178", "0059_0308",
+ "0179", "005A_0301",
+ "017A", "007A_0301",
+ "017B", "005A_0307",
+ "017C", "007A_0307",
+ "017D", "005A_030C",
+ "017E", "007A_030C",
+ "01A0", "004F_031B",
+ "01A1", "006F_031B",
+ "01AF", "0055_031B",
+ "01B0", "0075_031B",
+ "01CD", "0041_030C",
+ "01CE", "0061_030C",
+ "01CF", "0049_030C",
+ "01D0", "0069_030C",
+ "01D1", "004F_030C",
+ "01D2", "006F_030C",
+ "01D3", "0055_030C",
+ "01D4", "0075_030C",
+ "01D5", "0055_0308_0304",
+ "01D6", "0075_0308_0304",
+ "01D7", "0055_0308_0301",
+ "01D8", "0075_0308_0301",
+ "01D9", "0055_0308_030C",
+ "01DA", "0075_0308_030C",
+ "01DB", "0055_0308_0300",
+ "01DC", "0075_0308_0300",
+ "01DE", "0041_0308_0304",
+ "01DF", "0061_0308_0304",
+ "01E0", "0041_0307_0304",
+ "01E1", "0061_0307_0304",
+ "01E2", "00C6_0304",
+ "01E3", "00E6_0304",
+ "01E6", "0047_030C",
+ "01E7", "0067_030C",
+ "01E8", "004B_030C",
+ "01E9", "006B_030C",
+ "01EA", "004F_0328",
+ "01EB", "006F_0328",
+ "01EC", "004F_0328_0304",
+ "01ED", "006F_0328_0304",
+ "01EE", "01B7_030C",
+ "01EF", "0292_030C",
+ "01F0", "006A_030C",
+ "01F4", "0047_0301",
+ "01F5", "0067_0301",
+ "01F8", "004E_0300",
+ "01F9", "006E_0300",
+ "01FA", "0041_030A_0301",
+ "01FB", "0061_030A_0301",
+ "01FC", "00C6_0301",
+ "01FD", "00E6_0301",
+ "01FE", "00D8_0301",
+ "01FF", "00F8_0301",
+ "0200", "0041_030F",
+ "0201", "0061_030F",
+ "0202", "0041_0311",
+ "0203", "0061_0311",
+ "0204", "0045_030F",
+ "0205", "0065_030F",
+ "0206", "0045_0311",
+ "0207", "0065_0311",
+ "0208", "0049_030F",
+ "0209", "0069_030F",
+ "020A", "0049_0311",
+ "020B", "0069_0311",
+ "020C", "004F_030F",
+ "020D", "006F_030F",
+ "020E", "004F_0311",
+ "020F", "006F_0311",
+ "0210", "0052_030F",
+ "0211", "0072_030F",
+ "0212", "0052_0311",
+ "0213", "0072_0311",
+ "0214", "0055_030F",
+ "0215", "0075_030F",
+ "0216", "0055_0311",
+ "0217", "0075_0311",
+ "0218", "0053_0326",
+ "0219", "0073_0326",
+ "021A", "0054_0326",
+ "021B", "0074_0326",
+ "021E", "0048_030C",
+ "021F", "0068_030C",
+ "0226", "0041_0307",
+ "0227", "0061_0307",
+ "0228", "0045_0327",
+ "0229", "0065_0327",
+ "022A", "004F_0308_0304",
+ "022B", "006F_0308_0304",
+ "022C", "004F_0303_0304",
+ "022D", "006F_0303_0304",
+ "022E", "004F_0307",
+ "022F", "006F_0307",
+ "0230", "004F_0307_0304",
+ "0231", "006F_0307_0304",
+ "0232", "0059_0304",
+ "0233", "0079_0304",
+ "0340", "0300",
+ "0341", "0301",
+ "0343", "0313",
+ "0344", "0308_0301",
+ "0374", "02B9",
+ "037E", "003B",
+ "0385", "00A8_0301",
+ "0386", "0391_0301",
+ "0387", "00B7",
+ "0388", "0395_0301",
+ "0389", "0397_0301",
+ "038A", "0399_0301",
+ "038C", "039F_0301",
+ "038E", "03A5_0301",
+ "038F", "03A9_0301",
+ "0390", "03B9_0308_0301",
+ "03AA", "0399_0308",
+ "03AB", "03A5_0308",
+ "03AC", "03B1_0301",
+ "03AD", "03B5_0301",
+ "03AE", "03B7_0301",
+ "03AF", "03B9_0301",
+ "03B0", "03C5_0308_0301",
+ "03CA", "03B9_0308",
+ "03CB", "03C5_0308",
+ "03CC", "03BF_0301",
+ "03CD", "03C5_0301",
+ "03CE", "03C9_0301",
+ "03D3", "03D2_0301",
+ "03D4", "03D2_0308",
+ "0400", "0415_0300",
+ "0401", "0415_0308",
+ "0403", "0413_0301",
+ "0407", "0406_0308",
+ "040C", "041A_0301",
+ "040D", "0418_0300",
+ "040E", "0423_0306",
+ "0419", "0418_0306",
+ "0439", "0438_0306",
+ "0450", "0435_0300",
+ "0451", "0435_0308",
+ "0453", "0433_0301",
+ "0457", "0456_0308",
+ "045C", "043A_0301",
+ "045D", "0438_0300",
+ "045E", "0443_0306",
+ "0476", "0474_030F",
+ "0477", "0475_030F",
+ "04C1", "0416_0306",
+ "04C2", "0436_0306",
+ "04D0", "0410_0306",
+ "04D1", "0430_0306",
+ "04D2", "0410_0308",
+ "04D3", "0430_0308",
+ "04D6", "0415_0306",
+ "04D7", "0435_0306",
+ "04DA", "04D8_0308",
+ "04DB", "04D9_0308",
+ "04DC", "0416_0308",
+ "04DD", "0436_0308",
+ "04DE", "0417_0308",
+ "04DF", "0437_0308",
+ "04E2", "0418_0304",
+ "04E3", "0438_0304",
+ "04E4", "0418_0308",
+ "04E5", "0438_0308",
+ "04E6", "041E_0308",
+ "04E7", "043E_0308",
+ "04EA", "04E8_0308",
+ "04EB", "04E9_0308",
+ "04EC", "042D_0308",
+ "04ED", "044D_0308",
+ "04EE", "0423_0304",
+ "04EF", "0443_0304",
+ "04F0", "0423_0308",
+ "04F1", "0443_0308",
+ "04F2", "0423_030B",
+ "04F3", "0443_030B",
+ "04F4", "0427_0308",
+ "04F5", "0447_0308",
+ "04F8", "042B_0308",
+ "04F9", "044B_0308",
+ "0622", "0627_0653",
+ "0623", "0627_0654",
+ "0624", "0648_0654",
+ "0625", "0627_0655",
+ "0626", "064A_0654",
+ "06C0", "06D5_0654",
+ "06C2", "06C1_0654",
+ "06D3", "06D2_0654",
+ "0929", "0928_093C",
+ "0931", "0930_093C",
+ "0934", "0933_093C",
+ "0958", "0915_093C",
+ "0959", "0916_093C",
+ "095A", "0917_093C",
+ "095B", "091C_093C",
+ "095C", "0921_093C",
+ "095D", "0922_093C",
+ "095E", "092B_093C",
+ "095F", "092F_093C",
+ "09CB", "09C7_09BE",
+ "09CC", "09C7_09D7",
+ "09DC", "09A1_09BC",
+ "09DD", "09A2_09BC",
+ "09DF", "09AF_09BC",
+ "0A33", "0A32_0A3C",
+ "0A36", "0A38_0A3C",
+ "0A59", "0A16_0A3C",
+ "0A5A", "0A17_0A3C",
+ "0A5B", "0A1C_0A3C",
+ "0A5E", "0A2B_0A3C",
+ "0B48", "0B47_0B56",
+ "0B4B", "0B47_0B3E",
+ "0B4C", "0B47_0B57",
+ "0B5C", "0B21_0B3C",
+ "0B5D", "0B22_0B3C",
+ "0B94", "0B92_0BD7",
+ "0BCA", "0BC6_0BBE",
+ "0BCB", "0BC7_0BBE",
+ "0BCC", "0BC6_0BD7",
+ "0C48", "0C46_0C56",
+ "0CC0", "0CBF_0CD5",
+ "0CC7", "0CC6_0CD5",
+ "0CC8", "0CC6_0CD6",
+ "0CCA", "0CC6_0CC2",
+ "0CCB", "0CC6_0CC2_0CD5",
+ "0D4A", "0D46_0D3E",
+ "0D4B", "0D47_0D3E",
+ "0D4C", "0D46_0D57",
+ "0DDA", "0DD9_0DCA",
+ "0DDC", "0DD9_0DCF",
+ "0DDD", "0DD9_0DCF_0DCA",
+ "0DDE", "0DD9_0DDF",
+ "0F43", "0F42_0FB7",
+ "0F4D", "0F4C_0FB7",
+ "0F52", "0F51_0FB7",
+ "0F57", "0F56_0FB7",
+ "0F5C", "0F5B_0FB7",
+ "0F69", "0F40_0FB5",
+ "0F73", "0F71_0F72",
+ "0F75", "0F71_0F74",
+ "0F76", "0FB2_0F80",
+ "0F78", "0FB3_0F80",
+ "0F81", "0F71_0F80",
+ "0F93", "0F92_0FB7",
+ "0F9D", "0F9C_0FB7",
+ "0FA2", "0FA1_0FB7",
+ "0FA7", "0FA6_0FB7",
+ "0FAC", "0FAB_0FB7",
+ "0FB9", "0F90_0FB5",
+ "1026", "1025_102E",
+ "1B06", "1B05_1B35",
+ "1B08", "1B07_1B35",
+ "1B0A", "1B09_1B35",
+ "1B0C", "1B0B_1B35",
+ "1B0E", "1B0D_1B35",
+ "1B12", "1B11_1B35",
+ "1B3B", "1B3A_1B35",
+ "1B3D", "1B3C_1B35",
+ "1B40", "1B3E_1B35",
+ "1B41", "1B3F_1B35",
+ "1B43", "1B42_1B35",
+ "1E00", "0041_0325",
+ "1E01", "0061_0325",
+ "1E02", "0042_0307",
+ "1E03", "0062_0307",
+ "1E04", "0042_0323",
+ "1E05", "0062_0323",
+ "1E06", "0042_0331",
+ "1E07", "0062_0331",
+ "1E08", "0043_0327_0301",
+ "1E09", "0063_0327_0301",
+ "1E0A", "0044_0307",
+ "1E0B", "0064_0307",
+ "1E0C", "0044_0323",
+ "1E0D", "0064_0323",
+ "1E0E", "0044_0331",
+ "1E0F", "0064_0331",
+ "1E10", "0044_0327",
+ "1E11", "0064_0327",
+ "1E12", "0044_032D",
+ "1E13", "0064_032D",
+ "1E14", "0045_0304_0300",
+ "1E15", "0065_0304_0300",
+ "1E16", "0045_0304_0301",
+ "1E17", "0065_0304_0301",
+ "1E18", "0045_032D",
+ "1E19", "0065_032D",
+ "1E1A", "0045_0330",
+ "1E1B", "0065_0330",
+ "1E1C", "0045_0327_0306",
+ "1E1D", "0065_0327_0306",
+ "1E1E", "0046_0307",
+ "1E1F", "0066_0307",
+ "1E20", "0047_0304",
+ "1E21", "0067_0304",
+ "1E22", "0048_0307",
+ "1E23", "0068_0307",
+ "1E24", "0048_0323",
+ "1E25", "0068_0323",
+ "1E26", "0048_0308",
+ "1E27", "0068_0308",
+ "1E28", "0048_0327",
+ "1E29", "0068_0327",
+ "1E2A", "0048_032E",
+ "1E2B", "0068_032E",
+ "1E2C", "0049_0330",
+ "1E2D", "0069_0330",
+ "1E2E", "0049_0308_0301",
+ "1E2F", "0069_0308_0301",
+ "1E30", "004B_0301",
+ "1E31", "006B_0301",
+ "1E32", "004B_0323",
+ "1E33", "006B_0323",
+ "1E34", "004B_0331",
+ "1E35", "006B_0331",
+ "1E36", "004C_0323",
+ "1E37", "006C_0323",
+ "1E38", "004C_0323_0304",
+ "1E39", "006C_0323_0304",
+ "1E3A", "004C_0331",
+ "1E3B", "006C_0331",
+ "1E3C", "004C_032D",
+ "1E3D", "006C_032D",
+ "1E3E", "004D_0301",
+ "1E3F", "006D_0301",
+ "1E40", "004D_0307",
+ "1E41", "006D_0307",
+ "1E42", "004D_0323",
+ "1E43", "006D_0323",
+ "1E44", "004E_0307",
+ "1E45", "006E_0307",
+ "1E46", "004E_0323",
+ "1E47", "006E_0323",
+ "1E48", "004E_0331",
+ "1E49", "006E_0331",
+ "1E4A", "004E_032D",
+ "1E4B", "006E_032D",
+ "1E4C", "004F_0303_0301",
+ "1E4D", "006F_0303_0301",
+ "1E4E", "004F_0303_0308",
+ "1E4F", "006F_0303_0308",
+ "1E50", "004F_0304_0300",
+ "1E51", "006F_0304_0300",
+ "1E52", "004F_0304_0301",
+ "1E53", "006F_0304_0301",
+ "1E54", "0050_0301",
+ "1E55", "0070_0301",
+ "1E56", "0050_0307",
+ "1E57", "0070_0307",
+ "1E58", "0052_0307",
+ "1E59", "0072_0307",
+ "1E5A", "0052_0323",
+ "1E5B", "0072_0323",
+ "1E5C", "0052_0323_0304",
+ "1E5D", "0072_0323_0304",
+ "1E5E", "0052_0331",
+ "1E5F", "0072_0331",
+ "1E60", "0053_0307",
+ "1E61", "0073_0307",
+ "1E62", "0053_0323",
+ "1E63", "0073_0323",
+ "1E64", "0053_0301_0307",
+ "1E65", "0073_0301_0307",
+ "1E66", "0053_030C_0307",
+ "1E67", "0073_030C_0307",
+ "1E68", "0053_0323_0307",
+ "1E69", "0073_0323_0307",
+ "1E6A", "0054_0307",
+ "1E6B", "0074_0307",
+ "1E6C", "0054_0323",
+ "1E6D", "0074_0323",
+ "1E6E", "0054_0331",
+ "1E6F", "0074_0331",
+ "1E70", "0054_032D",
+ "1E71", "0074_032D",
+ "1E72", "0055_0324",
+ "1E73", "0075_0324",
+ "1E74", "0055_0330",
+ "1E75", "0075_0330",
+ "1E76", "0055_032D",
+ "1E77", "0075_032D",
+ "1E78", "0055_0303_0301",
+ "1E79", "0075_0303_0301",
+ "1E7A", "0055_0304_0308",
+ "1E7B", "0075_0304_0308",
+ "1E7C", "0056_0303",
+ "1E7D", "0076_0303",
+ "1E7E", "0056_0323",
+ "1E7F", "0076_0323",
+ "1E80", "0057_0300",
+ "1E81", "0077_0300",
+ "1E82", "0057_0301",
+ "1E83", "0077_0301",
+ "1E84", "0057_0308",
+ "1E85", "0077_0308",
+ "1E86", "0057_0307",
+ "1E87", "0077_0307",
+ "1E88", "0057_0323",
+ "1E89", "0077_0323",
+ "1E8A", "0058_0307",
+ "1E8B", "0078_0307",
+ "1E8C", "0058_0308",
+ "1E8D", "0078_0308",
+ "1E8E", "0059_0307",
+ "1E8F", "0079_0307",
+ "1E90", "005A_0302",
+ "1E91", "007A_0302",
+ "1E92", "005A_0323",
+ "1E93", "007A_0323",
+ "1E94", "005A_0331",
+ "1E95", "007A_0331",
+ "1E96", "0068_0331",
+ "1E97", "0074_0308",
+ "1E98", "0077_030A",
+ "1E99", "0079_030A",
+ "1E9B", "017F_0307",
+ "1EA0", "0041_0323",
+ "1EA1", "0061_0323",
+ "1EA2", "0041_0309",
+ "1EA3", "0061_0309",
+ "1EA4", "0041_0302_0301",
+ "1EA5", "0061_0302_0301",
+ "1EA6", "0041_0302_0300",
+ "1EA7", "0061_0302_0300",
+ "1EA8", "0041_0302_0309",
+ "1EA9", "0061_0302_0309",
+ "1EAA", "0041_0302_0303",
+ "1EAB", "0061_0302_0303",
+ "1EAC", "0041_0323_0302",
+ "1EAD", "0061_0323_0302",
+ "1EAE", "0041_0306_0301",
+ "1EAF", "0061_0306_0301",
+ "1EB0", "0041_0306_0300",
+ "1EB1", "0061_0306_0300",
+ "1EB2", "0041_0306_0309",
+ "1EB3", "0061_0306_0309",
+ "1EB4", "0041_0306_0303",
+ "1EB5", "0061_0306_0303",
+ "1EB6", "0041_0323_0306",
+ "1EB7", "0061_0323_0306",
+ "1EB8", "0045_0323",
+ "1EB9", "0065_0323",
+ "1EBA", "0045_0309",
+ "1EBB", "0065_0309",
+ "1EBC", "0045_0303",
+ "1EBD", "0065_0303",
+ "1EBE", "0045_0302_0301",
+ "1EBF", "0065_0302_0301",
+ "1EC0", "0045_0302_0300",
+ "1EC1", "0065_0302_0300",
+ "1EC2", "0045_0302_0309",
+ "1EC3", "0065_0302_0309",
+ "1EC4", "0045_0302_0303",
+ "1EC5", "0065_0302_0303",
+ "1EC6", "0045_0323_0302",
+ "1EC7", "0065_0323_0302",
+ "1EC8", "0049_0309",
+ "1EC9", "0069_0309",
+ "1ECA", "0049_0323",
+ "1ECB", "0069_0323",
+ "1ECC", "004F_0323",
+ "1ECD", "006F_0323",
+ "1ECE", "004F_0309",
+ "1ECF", "006F_0309",
+ "1ED0", "004F_0302_0301",
+ "1ED1", "006F_0302_0301",
+ "1ED2", "004F_0302_0300",
+ "1ED3", "006F_0302_0300",
+ "1ED4", "004F_0302_0309",
+ "1ED5", "006F_0302_0309",
+ "1ED6", "004F_0302_0303",
+ "1ED7", "006F_0302_0303",
+ "1ED8", "004F_0323_0302",
+ "1ED9", "006F_0323_0302",
+ "1EDA", "004F_031B_0301",
+ "1EDB", "006F_031B_0301",
+ "1EDC", "004F_031B_0300",
+ "1EDD", "006F_031B_0300",
+ "1EDE", "004F_031B_0309",
+ "1EDF", "006F_031B_0309",
+ "1EE0", "004F_031B_0303",
+ "1EE1", "006F_031B_0303",
+ "1EE2", "004F_031B_0323",
+ "1EE3", "006F_031B_0323",
+ "1EE4", "0055_0323",
+ "1EE5", "0075_0323",
+ "1EE6", "0055_0309",
+ "1EE7", "0075_0309",
+ "1EE8", "0055_031B_0301",
+ "1EE9", "0075_031B_0301",
+ "1EEA", "0055_031B_0300",
+ "1EEB", "0075_031B_0300",
+ "1EEC", "0055_031B_0309",
+ "1EED", "0075_031B_0309",
+ "1EEE", "0055_031B_0303",
+ "1EEF", "0075_031B_0303",
+ "1EF0", "0055_031B_0323",
+ "1EF1", "0075_031B_0323",
+ "1EF2", "0059_0300",
+ "1EF3", "0079_0300",
+ "1EF4", "0059_0323",
+ "1EF5", "0079_0323",
+ "1EF6", "0059_0309",
+ "1EF7", "0079_0309",
+ "1EF8", "0059_0303",
+ "1EF9", "0079_0303",
+ "1F00", "03B1_0313",
+ "1F01", "03B1_0314",
+ "1F02", "03B1_0313_0300",
+ "1F03", "03B1_0314_0300",
+ "1F04", "03B1_0313_0301",
+ "1F05", "03B1_0314_0301",
+ "1F06", "03B1_0313_0342",
+ "1F07", "03B1_0314_0342",
+ "1F08", "0391_0313",
+ "1F09", "0391_0314",
+ "1F0A", "0391_0313_0300",
+ "1F0B", "0391_0314_0300",
+ "1F0C", "0391_0313_0301",
+ "1F0D", "0391_0314_0301",
+ "1F0E", "0391_0313_0342",
+ "1F0F", "0391_0314_0342",
+ "1F10", "03B5_0313",
+ "1F11", "03B5_0314",
+ "1F12", "03B5_0313_0300",
+ "1F13", "03B5_0314_0300",
+ "1F14", "03B5_0313_0301",
+ "1F15", "03B5_0314_0301",
+ "1F18", "0395_0313",
+ "1F19", "0395_0314",
+ "1F1A", "0395_0313_0300",
+ "1F1B", "0395_0314_0300",
+ "1F1C", "0395_0313_0301",
+ "1F1D", "0395_0314_0301",
+ "1F20", "03B7_0313",
+ "1F21", "03B7_0314",
+ "1F22", "03B7_0313_0300",
+ "1F23", "03B7_0314_0300",
+ "1F24", "03B7_0313_0301",
+ "1F25", "03B7_0314_0301",
+ "1F26", "03B7_0313_0342",
+ "1F27", "03B7_0314_0342",
+ "1F28", "0397_0313",
+ "1F29", "0397_0314",
+ "1F2A", "0397_0313_0300",
+ "1F2B", "0397_0314_0300",
+ "1F2C", "0397_0313_0301",
+ "1F2D", "0397_0314_0301",
+ "1F2E", "0397_0313_0342",
+ "1F2F", "0397_0314_0342",
+ "1F30", "03B9_0313",
+ "1F31", "03B9_0314",
+ "1F32", "03B9_0313_0300",
+ "1F33", "03B9_0314_0300",
+ "1F34", "03B9_0313_0301",
+ "1F35", "03B9_0314_0301",
+ "1F36", "03B9_0313_0342",
+ "1F37", "03B9_0314_0342",
+ "1F38", "0399_0313",
+ "1F39", "0399_0314",
+ "1F3A", "0399_0313_0300",
+ "1F3B", "0399_0314_0300",
+ "1F3C", "0399_0313_0301",
+ "1F3D", "0399_0314_0301",
+ "1F3E", "0399_0313_0342",
+ "1F3F", "0399_0314_0342",
+ "1F40", "03BF_0313",
+ "1F41", "03BF_0314",
+ "1F42", "03BF_0313_0300",
+ "1F43", "03BF_0314_0300",
+ "1F44", "03BF_0313_0301",
+ "1F45", "03BF_0314_0301",
+ "1F48", "039F_0313",
+ "1F49", "039F_0314",
+ "1F4A", "039F_0313_0300",
+ "1F4B", "039F_0314_0300",
+ "1F4C", "039F_0313_0301",
+ "1F4D", "039F_0314_0301",
+ "1F50", "03C5_0313",
+ "1F51", "03C5_0314",
+ "1F52", "03C5_0313_0300",
+ "1F53", "03C5_0314_0300",
+ "1F54", "03C5_0313_0301",
+ "1F55", "03C5_0314_0301",
+ "1F56", "03C5_0313_0342",
+ "1F57", "03C5_0314_0342",
+ "1F59", "03A5_0314",
+ "1F5B", "03A5_0314_0300",
+ "1F5D", "03A5_0314_0301",
+ "1F5F", "03A5_0314_0342",
+ "1F60", "03C9_0313",
+ "1F61", "03C9_0314",
+ "1F62", "03C9_0313_0300",
+ "1F63", "03C9_0314_0300",
+ "1F64", "03C9_0313_0301",
+ "1F65", "03C9_0314_0301",
+ "1F66", "03C9_0313_0342",
+ "1F67", "03C9_0314_0342",
+ "1F68", "03A9_0313",
+ "1F69", "03A9_0314",
+ "1F6A", "03A9_0313_0300",
+ "1F6B", "03A9_0314_0300",
+ "1F6C", "03A9_0313_0301",
+ "1F6D", "03A9_0314_0301",
+ "1F6E", "03A9_0313_0342",
+ "1F6F", "03A9_0314_0342",
+ "1F70", "03B1_0300",
+ "1F71", "03B1_0301",
+ "1F72", "03B5_0300",
+ "1F73", "03B5_0301",
+ "1F74", "03B7_0300",
+ "1F75", "03B7_0301",
+ "1F76", "03B9_0300",
+ "1F77", "03B9_0301",
+ "1F78", "03BF_0300",
+ "1F79", "03BF_0301",
+ "1F7A", "03C5_0300",
+ "1F7B", "03C5_0301",
+ "1F7C", "03C9_0300",
+ "1F7D", "03C9_0301",
+ "1F80", "03B1_0313_0345",
+ "1F81", "03B1_0314_0345",
+ "1F82", "03B1_0313_0300_0345",
+ "1F83", "03B1_0314_0300_0345",
+ "1F84", "03B1_0313_0301_0345",
+ "1F85", "03B1_0314_0301_0345",
+ "1F86", "03B1_0313_0342_0345",
+ "1F87", "03B1_0314_0342_0345",
+ "1F88", "0391_0313_0345",
+ "1F89", "0391_0314_0345",
+ "1F8A", "0391_0313_0300_0345",
+ "1F8B", "0391_0314_0300_0345",
+ "1F8C", "0391_0313_0301_0345",
+ "1F8D", "0391_0314_0301_0345",
+ "1F8E", "0391_0313_0342_0345",
+ "1F8F", "0391_0314_0342_0345",
+ "1F90", "03B7_0313_0345",
+ "1F91", "03B7_0314_0345",
+ "1F92", "03B7_0313_0300_0345",
+ "1F93", "03B7_0314_0300_0345",
+ "1F94", "03B7_0313_0301_0345",
+ "1F95", "03B7_0314_0301_0345",
+ "1F96", "03B7_0313_0342_0345",
+ "1F97", "03B7_0314_0342_0345",
+ "1F98", "0397_0313_0345",
+ "1F99", "0397_0314_0345",
+ "1F9A", "0397_0313_0300_0345",
+ "1F9B", "0397_0314_0300_0345",
+ "1F9C", "0397_0313_0301_0345",
+ "1F9D", "0397_0314_0301_0345",
+ "1F9E", "0397_0313_0342_0345",
+ "1F9F", "0397_0314_0342_0345",
+ "1FA0", "03C9_0313_0345",
+ "1FA1", "03C9_0314_0345",
+ "1FA2", "03C9_0313_0300_0345",
+ "1FA3", "03C9_0314_0300_0345",
+ "1FA4", "03C9_0313_0301_0345",
+ "1FA5", "03C9_0314_0301_0345",
+ "1FA6", "03C9_0313_0342_0345",
+ "1FA7", "03C9_0314_0342_0345",
+ "1FA8", "03A9_0313_0345",
+ "1FA9", "03A9_0314_0345",
+ "1FAA", "03A9_0313_0300_0345",
+ "1FAB", "03A9_0314_0300_0345",
+ "1FAC", "03A9_0313_0301_0345",
+ "1FAD", "03A9_0314_0301_0345",
+ "1FAE", "03A9_0313_0342_0345",
+ "1FAF", "03A9_0314_0342_0345",
+ "1FB0", "03B1_0306",
+ "1FB1", "03B1_0304",
+ "1FB2", "03B1_0300_0345",
+ "1FB3", "03B1_0345",
+ "1FB4", "03B1_0301_0345",
+ "1FB6", "03B1_0342",
+ "1FB7", "03B1_0342_0345",
+ "1FB8", "0391_0306",
+ "1FB9", "0391_0304",
+ "1FBA", "0391_0300",
+ "1FBB", "0391_0301",
+ "1FBC", "0391_0345",
+ "1FBE", "03B9",
+ "1FC1", "00A8_0342",
+ "1FC2", "03B7_0300_0345",
+ "1FC3", "03B7_0345",
+ "1FC4", "03B7_0301_0345",
+ "1FC6", "03B7_0342",
+ "1FC7", "03B7_0342_0345",
+ "1FC8", "0395_0300",
+ "1FC9", "0395_0301",
+ "1FCA", "0397_0300",
+ "1FCB", "0397_0301",
+ "1FCC", "0397_0345",
+ "1FCD", "1FBF_0300",
+ "1FCE", "1FBF_0301",
+ "1FCF", "1FBF_0342",
+ "1FD0", "03B9_0306",
+ "1FD1", "03B9_0304",
+ "1FD2", "03B9_0308_0300",
+ "1FD3", "03B9_0308_0301",
+ "1FD6", "03B9_0342",
+ "1FD7", "03B9_0308_0342",
+ "1FD8", "0399_0306",
+ "1FD9", "0399_0304",
+ "1FDA", "0399_0300",
+ "1FDB", "0399_0301",
+ "1FDD", "1FFE_0300",
+ "1FDE", "1FFE_0301",
+ "1FDF", "1FFE_0342",
+ "1FE0", "03C5_0306",
+ "1FE1", "03C5_0304",
+ "1FE2", "03C5_0308_0300",
+ "1FE3", "03C5_0308_0301",
+ "1FE4", "03C1_0313",
+ "1FE5", "03C1_0314",
+ "1FE6", "03C5_0342",
+ "1FE7", "03C5_0308_0342",
+ "1FE8", "03A5_0306",
+ "1FE9", "03A5_0304",
+ "1FEA", "03A5_0300",
+ "1FEB", "03A5_0301",
+ "1FEC", "03A1_0314",
+ "1FED", "00A8_0300",
+ "1FEE", "00A8_0301",
+ "1FEF", "0060",
+ "1FF2", "03C9_0300_0345",
+ "1FF3", "03C9_0345",
+ "1FF4", "03C9_0301_0345",
+ "1FF6", "03C9_0342",
+ "1FF7", "03C9_0342_0345",
+ "1FF8", "039F_0300",
+ "1FF9", "039F_0301",
+ "1FFA", "03A9_0300",
+ "1FFB", "03A9_0301",
+ "1FFC", "03A9_0345",
+ "1FFD", "00B4",
+ "2000", "2002",
+ "2001", "2003",
+ "2126", "03A9",
+ "212A", "004B",
+ "212B", "0041_030A",
+ "219A", "2190_0338",
+ "219B", "2192_0338",
+ "21AE", "2194_0338",
+ "21CD", "21D0_0338",
+ "21CE", "21D4_0338",
+ "21CF", "21D2_0338",
+ "2204", "2203_0338",
+ "2209", "2208_0338",
+ "220C", "220B_0338",
+ "2224", "2223_0338",
+ "2226", "2225_0338",
+ "2241", "223C_0338",
+ "2244", "2243_0338",
+ "2247", "2245_0338",
+ "2249", "2248_0338",
+ "2260", "003D_0338",
+ "2262", "2261_0338",
+ "226D", "224D_0338",
+ "226E", "003C_0338",
+ "226F", "003E_0338",
+ "2270", "2264_0338",
+ "2271", "2265_0338",
+ "2274", "2272_0338",
+ "2275", "2273_0338",
+ "2278", "2276_0338",
+ "2279", "2277_0338",
+ "2280", "227A_0338",
+ "2281", "227B_0338",
+ "2284", "2282_0338",
+ "2285", "2283_0338",
+ "2288", "2286_0338",
+ "2289", "2287_0338",
+ "22AC", "22A2_0338",
+ "22AD", "22A8_0338",
+ "22AE", "22A9_0338",
+ "22AF", "22AB_0338",
+ "22E0", "227C_0338",
+ "22E1", "227D_0338",
+ "22E2", "2291_0338",
+ "22E3", "2292_0338",
+ "22EA", "22B2_0338",
+ "22EB", "22B3_0338",
+ "22EC", "22B4_0338",
+ "22ED", "22B5_0338",
+ "2329", "3008",
+ "232A", "3009",
+ "2ADC", "2ADD_0338",
+ "304C", "304B_3099",
+ "304E", "304D_3099",
+ "3050", "304F_3099",
+ "3052", "3051_3099",
+ "3054", "3053_3099",
+ "3056", "3055_3099",
+ "3058", "3057_3099",
+ "305A", "3059_3099",
+ "305C", "305B_3099",
+ "305E", "305D_3099",
+ "3060", "305F_3099",
+ "3062", "3061_3099",
+ "3065", "3064_3099",
+ "3067", "3066_3099",
+ "3069", "3068_3099",
+ "3070", "306F_3099",
+ "3071", "306F_309A",
+ "3073", "3072_3099",
+ "3074", "3072_309A",
+ "3076", "3075_3099",
+ "3077", "3075_309A",
+ "3079", "3078_3099",
+ "307A", "3078_309A",
+ "307C", "307B_3099",
+ "307D", "307B_309A",
+ "3094", "3046_3099",
+ "309E", "309D_3099",
+ "30AC", "30AB_3099",
+ "30AE", "30AD_3099",
+ "30B0", "30AF_3099",
+ "30B2", "30B1_3099",
+ "30B4", "30B3_3099",
+ "30B6", "30B5_3099",
+ "30B8", "30B7_3099",
+ "30BA", "30B9_3099",
+ "30BC", "30BB_3099",
+ "30BE", "30BD_3099",
+ "30C0", "30BF_3099",
+ "30C2", "30C1_3099",
+ "30C5", "30C4_3099",
+ "30C7", "30C6_3099",
+ "30C9", "30C8_3099",
+ "30D0", "30CF_3099",
+ "30D1", "30CF_309A",
+ "30D3", "30D2_3099",
+ "30D4", "30D2_309A",
+ "30D6", "30D5_3099",
+ "30D7", "30D5_309A",
+ "30D9", "30D8_3099",
+ "30DA", "30D8_309A",
+ "30DC", "30DB_3099",
+ "30DD", "30DB_309A",
+ "30F4", "30A6_3099",
+ "30F7", "30EF_3099",
+ "30F8", "30F0_3099",
+ "30F9", "30F1_3099",
+ "30FA", "30F2_3099",
+ "30FE", "30FD_3099",
+ "F900", "8C48",
+ "F901", "66F4",
+ "F902", "8ECA",
+ "F903", "8CC8",
+ "F904", "6ED1",
+ "F905", "4E32",
+ "F906", "53E5",
+ "F907", "9F9C",
+ "F908", "9F9C",
+ "F909", "5951",
+ "F90A", "91D1",
+ "F90B", "5587",
+ "F90C", "5948",
+ "F90D", "61F6",
+ "F90E", "7669",
+ "F90F", "7F85",
+ "F910", "863F",
+ "F911", "87BA",
+ "F912", "88F8",
+ "F913", "908F",
+ "F914", "6A02",
+ "F915", "6D1B",
+ "F916", "70D9",
+ "F917", "73DE",
+ "F918", "843D",
+ "F919", "916A",
+ "F91A", "99F1",
+ "F91B", "4E82",
+ "F91C", "5375",
+ "F91D", "6B04",
+ "F91E", "721B",
+ "F91F", "862D",
+ "F920", "9E1E",
+ "F921", "5D50",
+ "F922", "6FEB",
+ "F923", "85CD",
+ "F924", "8964",
+ "F925", "62C9",
+ "F926", "81D8",
+ "F927", "881F",
+ "F928", "5ECA",
+ "F929", "6717",
+ "F92A", "6D6A",
+ "F92B", "72FC",
+ "F92C", "90CE",
+ "F92D", "4F86",
+ "F92E", "51B7",
+ "F92F", "52DE",
+ "F930", "64C4",
+ "F931", "6AD3",
+ "F932", "7210",
+ "F933", "76E7",
+ "F934", "8001",
+ "F935", "8606",
+ "F936", "865C",
+ "F937", "8DEF",
+ "F938", "9732",
+ "F939", "9B6F",
+ "F93A", "9DFA",
+ "F93B", "788C",
+ "F93C", "797F",
+ "F93D", "7DA0",
+ "F93E", "83C9",
+ "F93F", "9304",
+ "F940", "9E7F",
+ "F941", "8AD6",
+ "F942", "58DF",
+ "F943", "5F04",
+ "F944", "7C60",
+ "F945", "807E",
+ "F946", "7262",
+ "F947", "78CA",
+ "F948", "8CC2",
+ "F949", "96F7",
+ "F94A", "58D8",
+ "F94B", "5C62",
+ "F94C", "6A13",
+ "F94D", "6DDA",
+ "F94E", "6F0F",
+ "F94F", "7D2F",
+ "F950", "7E37",
+ "F951", "964B",
+ "F952", "52D2",
+ "F953", "808B",
+ "F954", "51DC",
+ "F955", "51CC",
+ "F956", "7A1C",
+ "F957", "7DBE",
+ "F958", "83F1",
+ "F959", "9675",
+ "F95A", "8B80",
+ "F95B", "62CF",
+ "F95C", "6A02",
+ "F95D", "8AFE",
+ "F95E", "4E39",
+ "F95F", "5BE7",
+ "F960", "6012",
+ "F961", "7387",
+ "F962", "7570",
+ "F963", "5317",
+ "F964", "78FB",
+ "F965", "4FBF",
+ "F966", "5FA9",
+ "F967", "4E0D",
+ "F968", "6CCC",
+ "F969", "6578",
+ "F96A", "7D22",
+ "F96B", "53C3",
+ "F96C", "585E",
+ "F96D", "7701",
+ "F96E", "8449",
+ "F96F", "8AAA",
+ "F970", "6BBA",
+ "F971", "8FB0",
+ "F972", "6C88",
+ "F973", "62FE",
+ "F974", "82E5",
+ "F975", "63A0",
+ "F976", "7565",
+ "F977", "4EAE",
+ "F978", "5169",
+ "F979", "51C9",
+ "F97A", "6881",
+ "F97B", "7CE7",
+ "F97C", "826F",
+ "F97D", "8AD2",
+ "F97E", "91CF",
+ "F97F", "52F5",
+ "F980", "5442",
+ "F981", "5973",
+ "F982", "5EEC",
+ "F983", "65C5",
+ "F984", "6FFE",
+ "F985", "792A",
+ "F986", "95AD",
+ "F987", "9A6A",
+ "F988", "9E97",
+ "F989", "9ECE",
+ "F98A", "529B",
+ "F98B", "66C6",
+ "F98C", "6B77",
+ "F98D", "8F62",
+ "F98E", "5E74",
+ "F98F", "6190",
+ "F990", "6200",
+ "F991", "649A",
+ "F992", "6F23",
+ "F993", "7149",
+ "F994", "7489",
+ "F995", "79CA",
+ "F996", "7DF4",
+ "F997", "806F",
+ "F998", "8F26",
+ "F999", "84EE",
+ "F99A", "9023",
+ "F99B", "934A",
+ "F99C", "5217",
+ "F99D", "52A3",
+ "F99E", "54BD",
+ "F99F", "70C8",
+ "F9A0", "88C2",
+ "F9A1", "8AAA",
+ "F9A2", "5EC9",
+ "F9A3", "5FF5",
+ "F9A4", "637B",
+ "F9A5", "6BAE",
+ "F9A6", "7C3E",
+ "F9A7", "7375",
+ "F9A8", "4EE4",
+ "F9A9", "56F9",
+ "F9AA", "5BE7",
+ "F9AB", "5DBA",
+ "F9AC", "601C",
+ "F9AD", "73B2",
+ "F9AE", "7469",
+ "F9AF", "7F9A",
+ "F9B0", "8046",
+ "F9B1", "9234",
+ "F9B2", "96F6",
+ "F9B3", "9748",
+ "F9B4", "9818",
+ "F9B5", "4F8B",
+ "F9B6", "79AE",
+ "F9B7", "91B4",
+ "F9B8", "96B8",
+ "F9B9", "60E1",
+ "F9BA", "4E86",
+ "F9BB", "50DA",
+ "F9BC", "5BEE",
+ "F9BD", "5C3F",
+ "F9BE", "6599",
+ "F9BF", "6A02",
+ "F9C0", "71CE",
+ "F9C1", "7642",
+ "F9C2", "84FC",
+ "F9C3", "907C",
+ "F9C4", "9F8D",
+ "F9C5", "6688",
+ "F9C6", "962E",
+ "F9C7", "5289",
+ "F9C8", "677B",
+ "F9C9", "67F3",
+ "F9CA", "6D41",
+ "F9CB", "6E9C",
+ "F9CC", "7409",
+ "F9CD", "7559",
+ "F9CE", "786B",
+ "F9CF", "7D10",
+ "F9D0", "985E",
+ "F9D1", "516D",
+ "F9D2", "622E",
+ "F9D3", "9678",
+ "F9D4", "502B",
+ "F9D5", "5D19",
+ "F9D6", "6DEA",
+ "F9D7", "8F2A",
+ "F9D8", "5F8B",
+ "F9D9", "6144",
+ "F9DA", "6817",
+ "F9DB", "7387",
+ "F9DC", "9686",
+ "F9DD", "5229",
+ "F9DE", "540F",
+ "F9DF", "5C65",
+ "F9E0", "6613",
+ "F9E1", "674E",
+ "F9E2", "68A8",
+ "F9E3", "6CE5",
+ "F9E4", "7406",
+ "F9E5", "75E2",
+ "F9E6", "7F79",
+ "F9E7", "88CF",
+ "F9E8", "88E1",
+ "F9E9", "91CC",
+ "F9EA", "96E2",
+ "F9EB", "533F",
+ "F9EC", "6EBA",
+ "F9ED", "541D",
+ "F9EE", "71D0",
+ "F9EF", "7498",
+ "F9F0", "85FA",
+ "F9F1", "96A3",
+ "F9F2", "9C57",
+ "F9F3", "9E9F",
+ "F9F4", "6797",
+ "F9F5", "6DCB",
+ "F9F6", "81E8",
+ "F9F7", "7ACB",
+ "F9F8", "7B20",
+ "F9F9", "7C92",
+ "F9FA", "72C0",
+ "F9FB", "7099",
+ "F9FC", "8B58",
+ "F9FD", "4EC0",
+ "F9FE", "8336",
+ "F9FF", "523A",
+ "FA00", "5207",
+ "FA01", "5EA6",
+ "FA02", "62D3",
+ "FA03", "7CD6",
+ "FA04", "5B85",
+ "FA05", "6D1E",
+ "FA06", "66B4",
+ "FA07", "8F3B",
+ "FA08", "884C",
+ "FA09", "964D",
+ "FA0A", "898B",
+ "FA0B", "5ED3",
+ "FA0C", "5140",
+ "FA0D", "55C0",
+ "FA10", "585A",
+ "FA12", "6674",
+ "FA15", "51DE",
+ "FA16", "732A",
+ "FA17", "76CA",
+ "FA18", "793C",
+ "FA19", "795E",
+ "FA1A", "7965",
+ "FA1B", "798F",
+ "FA1C", "9756",
+ "FA1D", "7CBE",
+ "FA1E", "7FBD",
+ "FA20", "8612",
+ "FA22", "8AF8",
+ "FA25", "9038",
+ "FA26", "90FD",
+ "FA2A", "98EF",
+ "FA2B", "98FC",
+ "FA2C", "9928",
+ "FA2D", "9DB4",
+ "FA2E", "90DE",
+ "FA2F", "96B7",
+ "FA30", "4FAE",
+ "FA31", "50E7",
+ "FA32", "514D",
+ "FA33", "52C9",
+ "FA34", "52E4",
+ "FA35", "5351",
+ "FA36", "559D",
+ "FA37", "5606",
+ "FA38", "5668",
+ "FA39", "5840",
+ "FA3A", "58A8",
+ "FA3B", "5C64",
+ "FA3C", "5C6E",
+ "FA3D", "6094",
+ "FA3E", "6168",
+ "FA3F", "618E",
+ "FA40", "61F2",
+ "FA41", "654F",
+ "FA42", "65E2",
+ "FA43", "6691",
+ "FA44", "6885",
+ "FA45", "6D77",
+ "FA46", "6E1A",
+ "FA47", "6F22",
+ "FA48", "716E",
+ "FA49", "722B",
+ "FA4A", "7422",
+ "FA4B", "7891",
+ "FA4C", "793E",
+ "FA4D", "7949",
+ "FA4E", "7948",
+ "FA4F", "7950",
+ "FA50", "7956",
+ "FA51", "795D",
+ "FA52", "798D",
+ "FA53", "798E",
+ "FA54", "7A40",
+ "FA55", "7A81",
+ "FA56", "7BC0",
+ "FA57", "7DF4",
+ "FA58", "7E09",
+ "FA59", "7E41",
+ "FA5A", "7F72",
+ "FA5B", "8005",
+ "FA5C", "81ED",
+ "FA5D", "8279",
+ "FA5E", "8279",
+ "FA5F", "8457",
+ "FA60", "8910",
+ "FA61", "8996",
+ "FA62", "8B01",
+ "FA63", "8B39",
+ "FA64", "8CD3",
+ "FA65", "8D08",
+ "FA66", "8FB6",
+ "FA67", "9038",
+ "FA68", "96E3",
+ "FA69", "97FF",
+ "FA6A", "983B",
+ "FA6B", "6075",
+ "FA6C", "242EE",
+ "FA6D", "8218",
+ "FA70", "4E26",
+ "FA71", "51B5",
+ "FA72", "5168",
+ "FA73", "4F80",
+ "FA74", "5145",
+ "FA75", "5180",
+ "FA76", "52C7",
+ "FA77", "52FA",
+ "FA78", "559D",
+ "FA79", "5555",
+ "FA7A", "5599",
+ "FA7B", "55E2",
+ "FA7C", "585A",
+ "FA7D", "58B3",
+ "FA7E", "5944",
+ "FA7F", "5954",
+ "FA80", "5A62",
+ "FA81", "5B28",
+ "FA82", "5ED2",
+ "FA83", "5ED9",
+ "FA84", "5F69",
+ "FA85", "5FAD",
+ "FA86", "60D8",
+ "FA87", "614E",
+ "FA88", "6108",
+ "FA89", "618E",
+ "FA8A", "6160",
+ "FA8B", "61F2",
+ "FA8C", "6234",
+ "FA8D", "63C4",
+ "FA8E", "641C",
+ "FA8F", "6452",
+ "FA90", "6556",
+ "FA91", "6674",
+ "FA92", "6717",
+ "FA93", "671B",
+ "FA94", "6756",
+ "FA95", "6B79",
+ "FA96", "6BBA",
+ "FA97", "6D41",
+ "FA98", "6EDB",
+ "FA99", "6ECB",
+ "FA9A", "6F22",
+ "FA9B", "701E",
+ "FA9C", "716E",
+ "FA9D", "77A7",
+ "FA9E", "7235",
+ "FA9F", "72AF",
+ "FAA0", "732A",
+ "FAA1", "7471",
+ "FAA2", "7506",
+ "FAA3", "753B",
+ "FAA4", "761D",
+ "FAA5", "761F",
+ "FAA6", "76CA",
+ "FAA7", "76DB",
+ "FAA8", "76F4",
+ "FAA9", "774A",
+ "FAAA", "7740",
+ "FAAB", "78CC",
+ "FAAC", "7AB1",
+ "FAAD", "7BC0",
+ "FAAE", "7C7B",
+ "FAAF", "7D5B",
+ "FAB0", "7DF4",
+ "FAB1", "7F3E",
+ "FAB2", "8005",
+ "FAB3", "8352",
+ "FAB4", "83EF",
+ "FAB5", "8779",
+ "FAB6", "8941",
+ "FAB7", "8986",
+ "FAB8", "8996",
+ "FAB9", "8ABF",
+ "FABA", "8AF8",
+ "FABB", "8ACB",
+ "FABC", "8B01",
+ "FABD", "8AFE",
+ "FABE", "8AED",
+ "FABF", "8B39",
+ "FAC0", "8B8A",
+ "FAC1", "8D08",
+ "FAC2", "8F38",
+ "FAC3", "9072",
+ "FAC4", "9199",
+ "FAC5", "9276",
+ "FAC6", "967C",
+ "FAC7", "96E3",
+ "FAC8", "9756",
+ "FAC9", "97DB",
+ "FACA", "97FF",
+ "FACB", "980B",
+ "FACC", "983B",
+ "FACD", "9B12",
+ "FACE", "9F9C",
+ "FACF", "2284A",
+ "FAD0", "22844",
+ "FAD1", "233D5",
+ "FAD2", "3B9D",
+ "FAD3", "4018",
+ "FAD4", "4039",
+ "FAD5", "25249",
+ "FAD6", "25CD0",
+ "FAD7", "27ED3",
+ "FAD8", "9F43",
+ "FAD9", "9F8E",
+ "FB1D", "05D9_05B4",
+ "FB1F", "05F2_05B7",
+ "FB2A", "05E9_05C1",
+ "FB2B", "05E9_05C2",
+ "FB2C", "05E9_05BC_05C1",
+ "FB2D", "05E9_05BC_05C2",
+ "FB2E", "05D0_05B7",
+ "FB2F", "05D0_05B8",
+ "FB30", "05D0_05BC",
+ "FB31", "05D1_05BC",
+ "FB32", "05D2_05BC",
+ "FB33", "05D3_05BC",
+ "FB34", "05D4_05BC",
+ "FB35", "05D5_05BC",
+ "FB36", "05D6_05BC",
+ "FB38", "05D8_05BC",
+ "FB39", "05D9_05BC",
+ "FB3A", "05DA_05BC",
+ "FB3B", "05DB_05BC",
+ "FB3C", "05DC_05BC",
+ "FB3E", "05DE_05BC",
+ "FB40", "05E0_05BC",
+ "FB41", "05E1_05BC",
+ "FB43", "05E3_05BC",
+ "FB44", "05E4_05BC",
+ "FB46", "05E6_05BC",
+ "FB47", "05E7_05BC",
+ "FB48", "05E8_05BC",
+ "FB49", "05E9_05BC",
+ "FB4A", "05EA_05BC",
+ "FB4B", "05D5_05B9",
+ "FB4C", "05D1_05BF",
+ "FB4D", "05DB_05BF",
+ "FB4E", "05E4_05BF",
+ "1109A", "11099_110BA",
+ "1109C", "1109B_110BA",
+ "110AB", "110A5_110BA",
+ "1112E", "11131_11127",
+ "1112F", "11132_11127",
+ "1134B", "11347_1133E",
+ "1134C", "11347_11357",
+ "114BB", "114B9_114BA",
+ "114BC", "114B9_114B0",
+ "114BE", "114B9_114BD",
+ "115BA", "115B8_115AF",
+ "115BB", "115B9_115AF",
+ "11938", "11935_11930",
+ "1D15E", "1D157_1D165",
+ "1D15F", "1D158_1D165",
+ "1D160", "1D158_1D165_1D16E",
+ "1D161", "1D158_1D165_1D16F",
+ "1D162", "1D158_1D165_1D170",
+ "1D163", "1D158_1D165_1D171",
+ "1D164", "1D158_1D165_1D172",
+ "1D1BB", "1D1B9_1D165",
+ "1D1BC", "1D1BA_1D165",
+ "1D1BD", "1D1B9_1D165_1D16E",
+ "1D1BE", "1D1BA_1D165_1D16E",
+ "1D1BF", "1D1B9_1D165_1D16F",
+ "1D1C0", "1D1BA_1D165_1D16F",
+ "2F800", "4E3D",
+ "2F801", "4E38",
+ "2F802", "4E41",
+ "2F803", "20122",
+ "2F804", "4F60",
+ "2F805", "4FAE",
+ "2F806", "4FBB",
+ "2F807", "5002",
+ "2F808", "507A",
+ "2F809", "5099",
+ "2F80A", "50E7",
+ "2F80B", "50CF",
+ "2F80C", "349E",
+ "2F80D", "2063A",
+ "2F80E", "514D",
+ "2F80F", "5154",
+ "2F810", "5164",
+ "2F811", "5177",
+ "2F812", "2051C",
+ "2F813", "34B9",
+ "2F814", "5167",
+ "2F815", "518D",
+ "2F816", "2054B",
+ "2F817", "5197",
+ "2F818", "51A4",
+ "2F819", "4ECC",
+ "2F81A", "51AC",
+ "2F81B", "51B5",
+ "2F81C", "291DF",
+ "2F81D", "51F5",
+ "2F81E", "5203",
+ "2F81F", "34DF",
+ "2F820", "523B",
+ "2F821", "5246",
+ "2F822", "5272",
+ "2F823", "5277",
+ "2F824", "3515",
+ "2F825", "52C7",
+ "2F826", "52C9",
+ "2F827", "52E4",
+ "2F828", "52FA",
+ "2F829", "5305",
+ "2F82A", "5306",
+ "2F82B", "5317",
+ "2F82C", "5349",
+ "2F82D", "5351",
+ "2F82E", "535A",
+ "2F82F", "5373",
+ "2F830", "537D",
+ "2F831", "537F",
+ "2F832", "537F",
+ "2F833", "537F",
+ "2F834", "20A2C",
+ "2F835", "7070",
+ "2F836", "53CA",
+ "2F837", "53DF",
+ "2F838", "20B63",
+ "2F839", "53EB",
+ "2F83A", "53F1",
+ "2F83B", "5406",
+ "2F83C", "549E",
+ "2F83D", "5438",
+ "2F83E", "5448",
+ "2F83F", "5468",
+ "2F840", "54A2",
+ "2F841", "54F6",
+ "2F842", "5510",
+ "2F843", "5553",
+ "2F844", "5563",
+ "2F845", "5584",
+ "2F846", "5584",
+ "2F847", "5599",
+ "2F848", "55AB",
+ "2F849", "55B3",
+ "2F84A", "55C2",
+ "2F84B", "5716",
+ "2F84C", "5606",
+ "2F84D", "5717",
+ "2F84E", "5651",
+ "2F84F", "5674",
+ "2F850", "5207",
+ "2F851", "58EE",
+ "2F852", "57CE",
+ "2F853", "57F4",
+ "2F854", "580D",
+ "2F855", "578B",
+ "2F856", "5832",
+ "2F857", "5831",
+ "2F858", "58AC",
+ "2F859", "214E4",
+ "2F85A", "58F2",
+ "2F85B", "58F7",
+ "2F85C", "5906",
+ "2F85D", "591A",
+ "2F85E", "5922",
+ "2F85F", "5962",
+ "2F860", "216A8",
+ "2F861", "216EA",
+ "2F862", "59EC",
+ "2F863", "5A1B",
+ "2F864", "5A27",
+ "2F865", "59D8",
+ "2F866", "5A66",
+ "2F867", "36EE",
+ "2F868", "36FC",
+ "2F869", "5B08",
+ "2F86A", "5B3E",
+ "2F86B", "5B3E",
+ "2F86C", "219C8",
+ "2F86D", "5BC3",
+ "2F86E", "5BD8",
+ "2F86F", "5BE7",
+ "2F870", "5BF3",
+ "2F871", "21B18",
+ "2F872", "5BFF",
+ "2F873", "5C06",
+ "2F874", "5F53",
+ "2F875", "5C22",
+ "2F876", "3781",
+ "2F877", "5C60",
+ "2F878", "5C6E",
+ "2F879", "5CC0",
+ "2F87A", "5C8D",
+ "2F87B", "21DE4",
+ "2F87C", "5D43",
+ "2F87D", "21DE6",
+ "2F87E", "5D6E",
+ "2F87F", "5D6B",
+ "2F880", "5D7C",
+ "2F881", "5DE1",
+ "2F882", "5DE2",
+ "2F883", "382F",
+ "2F884", "5DFD",
+ "2F885", "5E28",
+ "2F886", "5E3D",
+ "2F887", "5E69",
+ "2F888", "3862",
+ "2F889", "22183",
+ "2F88A", "387C",
+ "2F88B", "5EB0",
+ "2F88C", "5EB3",
+ "2F88D", "5EB6",
+ "2F88E", "5ECA",
+ "2F88F", "2A392",
+ "2F890", "5EFE",
+ "2F891", "22331",
+ "2F892", "22331",
+ "2F893", "8201",
+ "2F894", "5F22",
+ "2F895", "5F22",
+ "2F896", "38C7",
+ "2F897", "232B8",
+ "2F898", "261DA",
+ "2F899", "5F62",
+ "2F89A", "5F6B",
+ "2F89B", "38E3",
+ "2F89C", "5F9A",
+ "2F89D", "5FCD",
+ "2F89E", "5FD7",
+ "2F89F", "5FF9",
+ "2F8A0", "6081",
+ "2F8A1", "393A",
+ "2F8A2", "391C",
+ "2F8A3", "6094",
+ "2F8A4", "226D4",
+ "2F8A5", "60C7",
+ "2F8A6", "6148",
+ "2F8A7", "614C",
+ "2F8A8", "614E",
+ "2F8A9", "614C",
+ "2F8AA", "617A",
+ "2F8AB", "618E",
+ "2F8AC", "61B2",
+ "2F8AD", "61A4",
+ "2F8AE", "61AF",
+ "2F8AF", "61DE",
+ "2F8B0", "61F2",
+ "2F8B1", "61F6",
+ "2F8B2", "6210",
+ "2F8B3", "621B",
+ "2F8B4", "625D",
+ "2F8B5", "62B1",
+ "2F8B6", "62D4",
+ "2F8B7", "6350",
+ "2F8B8", "22B0C",
+ "2F8B9", "633D",
+ "2F8BA", "62FC",
+ "2F8BB", "6368",
+ "2F8BC", "6383",
+ "2F8BD", "63E4",
+ "2F8BE", "22BF1",
+ "2F8BF", "6422",
+ "2F8C0", "63C5",
+ "2F8C1", "63A9",
+ "2F8C2", "3A2E",
+ "2F8C3", "6469",
+ "2F8C4", "647E",
+ "2F8C5", "649D",
+ "2F8C6", "6477",
+ "2F8C7", "3A6C",
+ "2F8C8", "654F",
+ "2F8C9", "656C",
+ "2F8CA", "2300A",
+ "2F8CB", "65E3",
+ "2F8CC", "66F8",
+ "2F8CD", "6649",
+ "2F8CE", "3B19",
+ "2F8CF", "6691",
+ "2F8D0", "3B08",
+ "2F8D1", "3AE4",
+ "2F8D2", "5192",
+ "2F8D3", "5195",
+ "2F8D4", "6700",
+ "2F8D5", "669C",
+ "2F8D6", "80AD",
+ "2F8D7", "43D9",
+ "2F8D8", "6717",
+ "2F8D9", "671B",
+ "2F8DA", "6721",
+ "2F8DB", "675E",
+ "2F8DC", "6753",
+ "2F8DD", "233C3",
+ "2F8DE", "3B49",
+ "2F8DF", "67FA",
+ "2F8E0", "6785",
+ "2F8E1", "6852",
+ "2F8E2", "6885",
+ "2F8E3", "2346D",
+ "2F8E4", "688E",
+ "2F8E5", "681F",
+ "2F8E6", "6914",
+ "2F8E7", "3B9D",
+ "2F8E8", "6942",
+ "2F8E9", "69A3",
+ "2F8EA", "69EA",
+ "2F8EB", "6AA8",
+ "2F8EC", "236A3",
+ "2F8ED", "6ADB",
+ "2F8EE", "3C18",
+ "2F8EF", "6B21",
+ "2F8F0", "238A7",
+ "2F8F1", "6B54",
+ "2F8F2", "3C4E",
+ "2F8F3", "6B72",
+ "2F8F4", "6B9F",
+ "2F8F5", "6BBA",
+ "2F8F6", "6BBB",
+ "2F8F7", "23A8D",
+ "2F8F8", "21D0B",
+ "2F8F9", "23AFA",
+ "2F8FA", "6C4E",
+ "2F8FB", "23CBC",
+ "2F8FC", "6CBF",
+ "2F8FD", "6CCD",
+ "2F8FE", "6C67",
+ "2F8FF", "6D16",
+ "2F900", "6D3E",
+ "2F901", "6D77",
+ "2F902", "6D41",
+ "2F903", "6D69",
+ "2F904", "6D78",
+ "2F905", "6D85",
+ "2F906", "23D1E",
+ "2F907", "6D34",
+ "2F908", "6E2F",
+ "2F909", "6E6E",
+ "2F90A", "3D33",
+ "2F90B", "6ECB",
+ "2F90C", "6EC7",
+ "2F90D", "23ED1",
+ "2F90E", "6DF9",
+ "2F90F", "6F6E",
+ "2F910", "23F5E",
+ "2F911", "23F8E",
+ "2F912", "6FC6",
+ "2F913", "7039",
+ "2F914", "701E",
+ "2F915", "701B",
+ "2F916", "3D96",
+ "2F917", "704A",
+ "2F918", "707D",
+ "2F919", "7077",
+ "2F91A", "70AD",
+ "2F91B", "20525",
+ "2F91C", "7145",
+ "2F91D", "24263",
+ "2F91E", "719C",
+ "2F91F", "243AB",
+ "2F920", "7228",
+ "2F921", "7235",
+ "2F922", "7250",
+ "2F923", "24608",
+ "2F924", "7280",
+ "2F925", "7295",
+ "2F926", "24735",
+ "2F927", "24814",
+ "2F928", "737A",
+ "2F929", "738B",
+ "2F92A", "3EAC",
+ "2F92B", "73A5",
+ "2F92C", "3EB8",
+ "2F92D", "3EB8",
+ "2F92E", "7447",
+ "2F92F", "745C",
+ "2F930", "7471",
+ "2F931", "7485",
+ "2F932", "74CA",
+ "2F933", "3F1B",
+ "2F934", "7524",
+ "2F935", "24C36",
+ "2F936", "753E",
+ "2F937", "24C92",
+ "2F938", "7570",
+ "2F939", "2219F",
+ "2F93A", "7610",
+ "2F93B", "24FA1",
+ "2F93C", "24FB8",
+ "2F93D", "25044",
+ "2F93E", "3FFC",
+ "2F93F", "4008",
+ "2F940", "76F4",
+ "2F941", "250F3",
+ "2F942", "250F2",
+ "2F943", "25119",
+ "2F944", "25133",
+ "2F945", "771E",
+ "2F946", "771F",
+ "2F947", "771F",
+ "2F948", "774A",
+ "2F949", "4039",
+ "2F94A", "778B",
+ "2F94B", "4046",
+ "2F94C", "4096",
+ "2F94D", "2541D",
+ "2F94E", "784E",
+ "2F94F", "788C",
+ "2F950", "78CC",
+ "2F951", "40E3",
+ "2F952", "25626",
+ "2F953", "7956",
+ "2F954", "2569A",
+ "2F955", "256C5",
+ "2F956", "798F",
+ "2F957", "79EB",
+ "2F958", "412F",
+ "2F959", "7A40",
+ "2F95A", "7A4A",
+ "2F95B", "7A4F",
+ "2F95C", "2597C",
+ "2F95D", "25AA7",
+ "2F95E", "25AA7",
+ "2F95F", "7AEE",
+ "2F960", "4202",
+ "2F961", "25BAB",
+ "2F962", "7BC6",
+ "2F963", "7BC9",
+ "2F964", "4227",
+ "2F965", "25C80",
+ "2F966", "7CD2",
+ "2F967", "42A0",
+ "2F968", "7CE8",
+ "2F969", "7CE3",
+ "2F96A", "7D00",
+ "2F96B", "25F86",
+ "2F96C", "7D63",
+ "2F96D", "4301",
+ "2F96E", "7DC7",
+ "2F96F", "7E02",
+ "2F970", "7E45",
+ "2F971", "4334",
+ "2F972", "26228",
+ "2F973", "26247",
+ "2F974", "4359",
+ "2F975", "262D9",
+ "2F976", "7F7A",
+ "2F977", "2633E",
+ "2F978", "7F95",
+ "2F979", "7FFA",
+ "2F97A", "8005",
+ "2F97B", "264DA",
+ "2F97C", "26523",
+ "2F97D", "8060",
+ "2F97E", "265A8",
+ "2F97F", "8070",
+ "2F980", "2335F",
+ "2F981", "43D5",
+ "2F982", "80B2",
+ "2F983", "8103",
+ "2F984", "440B",
+ "2F985", "813E",
+ "2F986", "5AB5",
+ "2F987", "267A7",
+ "2F988", "267B5",
+ "2F989", "23393",
+ "2F98A", "2339C",
+ "2F98B", "8201",
+ "2F98C", "8204",
+ "2F98D", "8F9E",
+ "2F98E", "446B",
+ "2F98F", "8291",
+ "2F990", "828B",
+ "2F991", "829D",
+ "2F992", "52B3",
+ "2F993", "82B1",
+ "2F994", "82B3",
+ "2F995", "82BD",
+ "2F996", "82E6",
+ "2F997", "26B3C",
+ "2F998", "82E5",
+ "2F999", "831D",
+ "2F99A", "8363",
+ "2F99B", "83AD",
+ "2F99C", "8323",
+ "2F99D", "83BD",
+ "2F99E", "83E7",
+ "2F99F", "8457",
+ "2F9A0", "8353",
+ "2F9A1", "83CA",
+ "2F9A2", "83CC",
+ "2F9A3", "83DC",
+ "2F9A4", "26C36",
+ "2F9A5", "26D6B",
+ "2F9A6", "26CD5",
+ "2F9A7", "452B",
+ "2F9A8", "84F1",
+ "2F9A9", "84F3",
+ "2F9AA", "8516",
+ "2F9AB", "273CA",
+ "2F9AC", "8564",
+ "2F9AD", "26F2C",
+ "2F9AE", "455D",
+ "2F9AF", "4561",
+ "2F9B0", "26FB1",
+ "2F9B1", "270D2",
+ "2F9B2", "456B",
+ "2F9B3", "8650",
+ "2F9B4", "865C",
+ "2F9B5", "8667",
+ "2F9B6", "8669",
+ "2F9B7", "86A9",
+ "2F9B8", "8688",
+ "2F9B9", "870E",
+ "2F9BA", "86E2",
+ "2F9BB", "8779",
+ "2F9BC", "8728",
+ "2F9BD", "876B",
+ "2F9BE", "8786",
+ "2F9BF", "45D7",
+ "2F9C0", "87E1",
+ "2F9C1", "8801",
+ "2F9C2", "45F9",
+ "2F9C3", "8860",
+ "2F9C4", "8863",
+ "2F9C5", "27667",
+ "2F9C6", "88D7",
+ "2F9C7", "88DE",
+ "2F9C8", "4635",
+ "2F9C9", "88FA",
+ "2F9CA", "34BB",
+ "2F9CB", "278AE",
+ "2F9CC", "27966",
+ "2F9CD", "46BE",
+ "2F9CE", "46C7",
+ "2F9CF", "8AA0",
+ "2F9D0", "8AED",
+ "2F9D1", "8B8A",
+ "2F9D2", "8C55",
+ "2F9D3", "27CA8",
+ "2F9D4", "8CAB",
+ "2F9D5", "8CC1",
+ "2F9D6", "8D1B",
+ "2F9D7", "8D77",
+ "2F9D8", "27F2F",
+ "2F9D9", "20804",
+ "2F9DA", "8DCB",
+ "2F9DB", "8DBC",
+ "2F9DC", "8DF0",
+ "2F9DD", "208DE",
+ "2F9DE", "8ED4",
+ "2F9DF", "8F38",
+ "2F9E0", "285D2",
+ "2F9E1", "285ED",
+ "2F9E2", "9094",
+ "2F9E3", "90F1",
+ "2F9E4", "9111",
+ "2F9E5", "2872E",
+ "2F9E6", "911B",
+ "2F9E7", "9238",
+ "2F9E8", "92D7",
+ "2F9E9", "92D8",
+ "2F9EA", "927C",
+ "2F9EB", "93F9",
+ "2F9EC", "9415",
+ "2F9ED", "28BFA",
+ "2F9EE", "958B",
+ "2F9EF", "4995",
+ "2F9F0", "95B7",
+ "2F9F1", "28D77",
+ "2F9F2", "49E6",
+ "2F9F3", "96C3",
+ "2F9F4", "5DB2",
+ "2F9F5", "9723",
+ "2F9F6", "29145",
+ "2F9F7", "2921A",
+ "2F9F8", "4A6E",
+ "2F9F9", "4A76",
+ "2F9FA", "97E0",
+ "2F9FB", "2940A",
+ "2F9FC", "4AB2",
+ "2F9FD", "29496",
+ "2F9FE", "980B",
+ "2F9FF", "980B",
+ "2FA00", "9829",
+ "2FA01", "295B6",
+ "2FA02", "98E2",
+ "2FA03", "4B33",
+ "2FA04", "9929",
+ "2FA05", "99A7",
+ "2FA06", "99C2",
+ "2FA07", "99FE",
+ "2FA08", "4BCE",
+ "2FA09", "29B30",
+ "2FA0A", "9B12",
+ "2FA0B", "9C40",
+ "2FA0C", "9CFD",
+ "2FA0D", "4CCE",
+ "2FA0E", "4CED",
+ "2FA0F", "9D67",
+ "2FA10", "2A0CE",
+ "2FA11", "4CF8",
+ "2FA12", "2A105",
+ "2FA13", "2A20E",
+ "2FA14", "2A291",
+ "2FA15", "9EBB",
+ "2FA16", "4D56",
+ "2FA17", "9EF9",
+ "2FA18", "9EFE",
+ "2FA19", "9F05",
+ "2FA1A", "9F0F",
+ "2FA1B", "9F16",
+ "2FA1C", "9F3B",
+ "2FA1D", "2A600",
+);
+
+# This table was algorithmically derived from the Adobe Glyph List (AGL)
+# file 'glyphlist.txt' from the GitHub Adobe Type Tools agl-aglfn
+# project, on 2022-10-09.
+#
+# See "groff:" comments for altered mappings.
+my %AGL_to_unicode = (
+ "A", "0041",
+ "AE", "00C6",
+ "AEacute", "01FC",
+ "AEmacron", "01E2",
+ "Aacute", "00C1",
+ "Abreve", "0102",
+ "Abreveacute", "1EAE",
+ "Abrevecyrillic", "04D0",
+ "Abrevedotbelow", "1EB6",
+ "Abrevegrave", "1EB0",
+ "Abrevehookabove", "1EB2",
+ "Abrevetilde", "1EB4",
+ "Acaron", "01CD",
+ "Acircle", "24B6",
+ "Acircumflex", "00C2",
+ "Acircumflexacute", "1EA4",
+ "Acircumflexdotbelow", "1EAC",
+ "Acircumflexgrave", "1EA6",
+ "Acircumflexhookabove", "1EA8",
+ "Acircumflextilde", "1EAA",
+ "Acyrillic", "0410",
+ "Adblgrave", "0200",
+ "Adieresis", "00C4",
+ "Adieresiscyrillic", "04D2",
+ "Adieresismacron", "01DE",
+ "Adotbelow", "1EA0",
+ "Adotmacron", "01E0",
+ "Agrave", "00C0",
+ "Ahookabove", "1EA2",
+ "Aiecyrillic", "04D4",
+ "Ainvertedbreve", "0202",
+ "Alpha", "0391",
+ "Alphatonos", "0386",
+ "Amacron", "0100",
+ "Amonospace", "FF21",
+ "Aogonek", "0104",
+ "Aring", "00C5",
+ "Aringacute", "01FA",
+ "Aringbelow", "1E00",
+ "Atilde", "00C3",
+ "Aybarmenian", "0531",
+ "B", "0042",
+ "Bcircle", "24B7",
+ "Bdotaccent", "1E02",
+ "Bdotbelow", "1E04",
+ "Becyrillic", "0411",
+ "Benarmenian", "0532",
+ "Beta", "0392",
+ "Bhook", "0181",
+ "Blinebelow", "1E06",
+ "Bmonospace", "FF22",
+ "Btopbar", "0182",
+ "C", "0043",
+ "Caarmenian", "053E",
+ "Cacute", "0106",
+ "Ccaron", "010C",
+ "Ccedilla", "00C7",
+ "Ccedillaacute", "1E08",
+ "Ccircle", "24B8",
+ "Ccircumflex", "0108",
+ "Cdot", "010A",
+ "Cdotaccent", "010A",
+ "Chaarmenian", "0549",
+ "Cheabkhasiancyrillic", "04BC",
+ "Checyrillic", "0427",
+ "Chedescenderabkhasiancyrillic", "04BE",
+ "Chedescendercyrillic", "04B6",
+ "Chedieresiscyrillic", "04F4",
+ "Cheharmenian", "0543",
+ "Chekhakassiancyrillic", "04CB",
+ "Cheverticalstrokecyrillic", "04B8",
+ "Chi", "03A7",
+ "Chook", "0187",
+ "Cmonospace", "FF23",
+ "Coarmenian", "0551",
+ "D", "0044",
+ "DZ", "01F1",
+ "DZcaron", "01C4",
+ "Daarmenian", "0534",
+ "Dafrican", "0189",
+ "Dcaron", "010E",
+ "Dcedilla", "1E10",
+ "Dcircle", "24B9",
+ "Dcircumflexbelow", "1E12",
+ "Dcroat", "0110",
+ "Ddotaccent", "1E0A",
+ "Ddotbelow", "1E0C",
+ "Decyrillic", "0414",
+ "Deicoptic", "03EE",
+ "Delta", "0394", # groff: not U+2206
+ "Deltagreek", "0394",
+ "Dhook", "018A",
+ "Digammagreek", "03DC",
+ "Djecyrillic", "0402",
+ "Dlinebelow", "1E0E",
+ "Dmonospace", "FF24",
+ "Dslash", "0110",
+ "Dtopbar", "018B",
+ "Dz", "01F2",
+ "Dzcaron", "01C5",
+ "Dzeabkhasiancyrillic", "04E0",
+ "Dzecyrillic", "0405",
+ "Dzhecyrillic", "040F",
+ "E", "0045",
+ "Eacute", "00C9",
+ "Ebreve", "0114",
+ "Ecaron", "011A",
+ "Ecedillabreve", "1E1C",
+ "Echarmenian", "0535",
+ "Ecircle", "24BA",
+ "Ecircumflex", "00CA",
+ "Ecircumflexacute", "1EBE",
+ "Ecircumflexbelow", "1E18",
+ "Ecircumflexdotbelow", "1EC6",
+ "Ecircumflexgrave", "1EC0",
+ "Ecircumflexhookabove", "1EC2",
+ "Ecircumflextilde", "1EC4",
+ "Ecyrillic", "0404",
+ "Edblgrave", "0204",
+ "Edieresis", "00CB",
+ "Edot", "0116",
+ "Edotaccent", "0116",
+ "Edotbelow", "1EB8",
+ "Efcyrillic", "0424",
+ "Egrave", "00C8",
+ "Eharmenian", "0537",
+ "Ehookabove", "1EBA",
+ "Eightroman", "2167",
+ "Einvertedbreve", "0206",
+ "Eiotifiedcyrillic", "0464",
+ "Elcyrillic", "041B",
+ "Elevenroman", "216A",
+ "Emacron", "0112",
+ "Emacronacute", "1E16",
+ "Emacrongrave", "1E14",
+ "Emcyrillic", "041C",
+ "Emonospace", "FF25",
+ "Encyrillic", "041D",
+ "Endescendercyrillic", "04A2",
+ "Eng", "014A",
+ "Enghecyrillic", "04A4",
+ "Enhookcyrillic", "04C7",
+ "Eogonek", "0118",
+ "Eopen", "0190",
+ "Epsilon", "0395",
+ "Epsilontonos", "0388",
+ "Ercyrillic", "0420",
+ "Ereversed", "018E",
+ "Ereversedcyrillic", "042D",
+ "Escyrillic", "0421",
+ "Esdescendercyrillic", "04AA",
+ "Esh", "01A9",
+ "Eta", "0397",
+ "Etarmenian", "0538",
+ "Etatonos", "0389",
+ "Eth", "00D0",
+ "Etilde", "1EBC",
+ "Etildebelow", "1E1A",
+ "Euro", "20AC",
+ "Ezh", "01B7",
+ "Ezhcaron", "01EE",
+ "Ezhreversed", "01B8",
+ "F", "0046",
+ "Fcircle", "24BB",
+ "Fdotaccent", "1E1E",
+ "Feharmenian", "0556",
+ "Feicoptic", "03E4",
+ "Fhook", "0191",
+ "Fitacyrillic", "0472",
+ "Fiveroman", "2164",
+ "Fmonospace", "FF26",
+ "Fourroman", "2163",
+ "G", "0047",
+ "GBsquare", "3387",
+ "Gacute", "01F4",
+ "Gamma", "0393",
+ "Gammaafrican", "0194",
+ "Gangiacoptic", "03EA",
+ "Gbreve", "011E",
+ "Gcaron", "01E6",
+ "Gcedilla", "0122",
+ "Gcircle", "24BC",
+ "Gcircumflex", "011C",
+ "Gcommaaccent", "0122",
+ "Gdot", "0120",
+ "Gdotaccent", "0120",
+ "Gecyrillic", "0413",
+ "Ghadarmenian", "0542",
+ "Ghemiddlehookcyrillic", "0494",
+ "Ghestrokecyrillic", "0492",
+ "Gheupturncyrillic", "0490",
+ "Ghook", "0193",
+ "Gimarmenian", "0533",
+ "Gjecyrillic", "0403",
+ "Gmacron", "1E20",
+ "Gmonospace", "FF27",
+ "Gsmallhook", "029B",
+ "Gstroke", "01E4",
+ "H", "0048",
+ "H18533", "25CF",
+ "H18543", "25AA",
+ "H18551", "25AB",
+ "H22073", "25A1",
+ "HPsquare", "33CB",
+ "Haabkhasiancyrillic", "04A8",
+ "Hadescendercyrillic", "04B2",
+ "Hardsigncyrillic", "042A",
+ "Hbar", "0126",
+ "Hbrevebelow", "1E2A",
+ "Hcedilla", "1E28",
+ "Hcircle", "24BD",
+ "Hcircumflex", "0124",
+ "Hdieresis", "1E26",
+ "Hdotaccent", "1E22",
+ "Hdotbelow", "1E24",
+ "Hmonospace", "FF28",
+ "Hoarmenian", "0540",
+ "Horicoptic", "03E8",
+ "Hzsquare", "3390",
+ "I", "0049",
+ "IAcyrillic", "042F",
+ "IJ", "0132",
+ "IUcyrillic", "042E",
+ "Iacute", "00CD",
+ "Ibreve", "012C",
+ "Icaron", "01CF",
+ "Icircle", "24BE",
+ "Icircumflex", "00CE",
+ "Icyrillic", "0406",
+ "Idblgrave", "0208",
+ "Idieresis", "00CF",
+ "Idieresisacute", "1E2E",
+ "Idieresiscyrillic", "04E4",
+ "Idot", "0130",
+ "Idotaccent", "0130",
+ "Idotbelow", "1ECA",
+ "Iebrevecyrillic", "04D6",
+ "Iecyrillic", "0415",
+ "Ifraktur", "2111",
+ "Igrave", "00CC",
+ "Ihookabove", "1EC8",
+ "Iicyrillic", "0418",
+ "Iinvertedbreve", "020A",
+ "Iishortcyrillic", "0419",
+ "Imacron", "012A",
+ "Imacroncyrillic", "04E2",
+ "Imonospace", "FF29",
+ "Iniarmenian", "053B",
+ "Iocyrillic", "0401",
+ "Iogonek", "012E",
+ "Iota", "0399",
+ "Iotaafrican", "0196",
+ "Iotadieresis", "03AA",
+ "Iotatonos", "038A",
+ "Istroke", "0197",
+ "Itilde", "0128",
+ "Itildebelow", "1E2C",
+ "Izhitsacyrillic", "0474",
+ "Izhitsadblgravecyrillic", "0476",
+ "J", "004A",
+ "Jaarmenian", "0541",
+ "Jcircle", "24BF",
+ "Jcircumflex", "0134",
+ "Jecyrillic", "0408",
+ "Jheharmenian", "054B",
+ "Jmonospace", "FF2A",
+ "K", "004B",
+ "KBsquare", "3385",
+ "KKsquare", "33CD",
+ "Kabashkircyrillic", "04A0",
+ "Kacute", "1E30",
+ "Kacyrillic", "041A",
+ "Kadescendercyrillic", "049A",
+ "Kahookcyrillic", "04C3",
+ "Kappa", "039A",
+ "Kastrokecyrillic", "049E",
+ "Kaverticalstrokecyrillic", "049C",
+ "Kcaron", "01E8",
+ "Kcedilla", "0136",
+ "Kcircle", "24C0",
+ "Kcommaaccent", "0136",
+ "Kdotbelow", "1E32",
+ "Keharmenian", "0554",
+ "Kenarmenian", "053F",
+ "Khacyrillic", "0425",
+ "Kheicoptic", "03E6",
+ "Khook", "0198",
+ "Kjecyrillic", "040C",
+ "Klinebelow", "1E34",
+ "Kmonospace", "FF2B",
+ "Koppacyrillic", "0480",
+ "Koppagreek", "03DE",
+ "Ksicyrillic", "046E",
+ "L", "004C",
+ "LJ", "01C7",
+ "Lacute", "0139",
+ "Lambda", "039B",
+ "Lcaron", "013D",
+ "Lcedilla", "013B",
+ "Lcircle", "24C1",
+ "Lcircumflexbelow", "1E3C",
+ "Lcommaaccent", "013B",
+ "Ldot", "013F",
+ "Ldotaccent", "013F",
+ "Ldotbelow", "1E36",
+ "Ldotbelowmacron", "1E38",
+ "Liwnarmenian", "053C",
+ "Lj", "01C8",
+ "Ljecyrillic", "0409",
+ "Llinebelow", "1E3A",
+ "Lmonospace", "FF2C",
+ "Lslash", "0141",
+ "M", "004D",
+ "MBsquare", "3386",
+ "Macute", "1E3E",
+ "Mcircle", "24C2",
+ "Mdotaccent", "1E40",
+ "Mdotbelow", "1E42",
+ "Menarmenian", "0544",
+ "Mmonospace", "FF2D",
+ "Mturned", "019C",
+ "Mu", "039C",
+ "N", "004E",
+ "NJ", "01CA",
+ "Nacute", "0143",
+ "Ncaron", "0147",
+ "Ncedilla", "0145",
+ "Ncircle", "24C3",
+ "Ncircumflexbelow", "1E4A",
+ "Ncommaaccent", "0145",
+ "Ndotaccent", "1E44",
+ "Ndotbelow", "1E46",
+ "Nhookleft", "019D",
+ "Nineroman", "2168",
+ "Nj", "01CB",
+ "Njecyrillic", "040A",
+ "Nlinebelow", "1E48",
+ "Nmonospace", "FF2E",
+ "Nowarmenian", "0546",
+ "Ntilde", "00D1",
+ "Nu", "039D",
+ "O", "004F",
+ "OE", "0152",
+ "Oacute", "00D3",
+ "Obarredcyrillic", "04E8",
+ "Obarreddieresiscyrillic", "04EA",
+ "Obreve", "014E",
+ "Ocaron", "01D1",
+ "Ocenteredtilde", "019F",
+ "Ocircle", "24C4",
+ "Ocircumflex", "00D4",
+ "Ocircumflexacute", "1ED0",
+ "Ocircumflexdotbelow", "1ED8",
+ "Ocircumflexgrave", "1ED2",
+ "Ocircumflexhookabove", "1ED4",
+ "Ocircumflextilde", "1ED6",
+ "Ocyrillic", "041E",
+ "Odblacute", "0150",
+ "Odblgrave", "020C",
+ "Odieresis", "00D6",
+ "Odieresiscyrillic", "04E6",
+ "Odotbelow", "1ECC",
+ "Ograve", "00D2",
+ "Oharmenian", "0555",
+ "Ohm", "2126",
+ "Ohookabove", "1ECE",
+ "Ohorn", "01A0",
+ "Ohornacute", "1EDA",
+ "Ohorndotbelow", "1EE2",
+ "Ohorngrave", "1EDC",
+ "Ohornhookabove", "1EDE",
+ "Ohorntilde", "1EE0",
+ "Ohungarumlaut", "0150",
+ "Oi", "01A2",
+ "Oinvertedbreve", "020E",
+ "Omacron", "014C",
+ "Omacronacute", "1E52",
+ "Omacrongrave", "1E50",
+ "Omega", "03A9", # groff: not U+2126
+ "Omegacyrillic", "0460",
+ "Omegagreek", "03A9",
+ "Omegaroundcyrillic", "047A",
+ "Omegatitlocyrillic", "047C",
+ "Omegatonos", "038F",
+ "Omicron", "039F",
+ "Omicrontonos", "038C",
+ "Omonospace", "FF2F",
+ "Oneroman", "2160",
+ "Oogonek", "01EA",
+ "Oogonekmacron", "01EC",
+ "Oopen", "0186",
+ "Oslash", "00D8",
+ "Oslashacute", "01FE",
+ "Ostrokeacute", "01FE",
+ "Otcyrillic", "047E",
+ "Otilde", "00D5",
+ "Otildeacute", "1E4C",
+ "Otildedieresis", "1E4E",
+ "P", "0050",
+ "Pacute", "1E54",
+ "Pcircle", "24C5",
+ "Pdotaccent", "1E56",
+ "Pecyrillic", "041F",
+ "Peharmenian", "054A",
+ "Pemiddlehookcyrillic", "04A6",
+ "Phi", "03A6",
+ "Phook", "01A4",
+ "Pi", "03A0",
+ "Piwrarmenian", "0553",
+ "Pmonospace", "FF30",
+ "Psi", "03A8",
+ "Psicyrillic", "0470",
+ "Q", "0051",
+ "Qcircle", "24C6",
+ "Qmonospace", "FF31",
+ "R", "0052",
+ "Raarmenian", "054C",
+ "Racute", "0154",
+ "Rcaron", "0158",
+ "Rcedilla", "0156",
+ "Rcircle", "24C7",
+ "Rcommaaccent", "0156",
+ "Rdblgrave", "0210",
+ "Rdotaccent", "1E58",
+ "Rdotbelow", "1E5A",
+ "Rdotbelowmacron", "1E5C",
+ "Reharmenian", "0550",
+ "Rfraktur", "211C",
+ "Rho", "03A1",
+ "Rinvertedbreve", "0212",
+ "Rlinebelow", "1E5E",
+ "Rmonospace", "FF32",
+ "Rsmallinverted", "0281",
+ "Rsmallinvertedsuperior", "02B6",
+ "S", "0053",
+ "SF010000", "250C",
+ "SF020000", "2514",
+ "SF030000", "2510",
+ "SF040000", "2518",
+ "SF050000", "253C",
+ "SF060000", "252C",
+ "SF070000", "2534",
+ "SF080000", "251C",
+ "SF090000", "2524",
+ "SF100000", "2500",
+ "SF110000", "2502",
+ "SF190000", "2561",
+ "SF200000", "2562",
+ "SF210000", "2556",
+ "SF220000", "2555",
+ "SF230000", "2563",
+ "SF240000", "2551",
+ "SF250000", "2557",
+ "SF260000", "255D",
+ "SF270000", "255C",
+ "SF280000", "255B",
+ "SF360000", "255E",
+ "SF370000", "255F",
+ "SF380000", "255A",
+ "SF390000", "2554",
+ "SF400000", "2569",
+ "SF410000", "2566",
+ "SF420000", "2560",
+ "SF430000", "2550",
+ "SF440000", "256C",
+ "SF450000", "2567",
+ "SF460000", "2568",
+ "SF470000", "2564",
+ "SF480000", "2565",
+ "SF490000", "2559",
+ "SF500000", "2558",
+ "SF510000", "2552",
+ "SF520000", "2553",
+ "SF530000", "256B",
+ "SF540000", "256A",
+ "Sacute", "015A",
+ "Sacutedotaccent", "1E64",
+ "Sampigreek", "03E0",
+ "Scaron", "0160",
+ "Scarondotaccent", "1E66",
+ "Scedilla", "015E",
+ "Schwa", "018F",
+ "Schwacyrillic", "04D8",
+ "Schwadieresiscyrillic", "04DA",
+ "Scircle", "24C8",
+ "Scircumflex", "015C",
+ "Scommaaccent", "0218",
+ "Sdotaccent", "1E60",
+ "Sdotbelow", "1E62",
+ "Sdotbelowdotaccent", "1E68",
+ "Seharmenian", "054D",
+ "Sevenroman", "2166",
+ "Shaarmenian", "0547",
+ "Shacyrillic", "0428",
+ "Shchacyrillic", "0429",
+ "Sheicoptic", "03E2",
+ "Shhacyrillic", "04BA",
+ "Shimacoptic", "03EC",
+ "Sigma", "03A3",
+ "Sixroman", "2165",
+ "Smonospace", "FF33",
+ "Softsigncyrillic", "042C",
+ "Stigmagreek", "03DA",
+ "T", "0054",
+ "Tau", "03A4",
+ "Tbar", "0166",
+ "Tcaron", "0164",
+ "Tcedilla", "0162",
+ "Tcircle", "24C9",
+ "Tcircumflexbelow", "1E70",
+ "Tcommaaccent", "0162",
+ "Tdotaccent", "1E6A",
+ "Tdotbelow", "1E6C",
+ "Tecyrillic", "0422",
+ "Tedescendercyrillic", "04AC",
+ "Tenroman", "2169",
+ "Tetsecyrillic", "04B4",
+ "Theta", "0398",
+ "Thook", "01AC",
+ "Thorn", "00DE",
+ "Threeroman", "2162",
+ "Tiwnarmenian", "054F",
+ "Tlinebelow", "1E6E",
+ "Tmonospace", "FF34",
+ "Toarmenian", "0539",
+ "Tonefive", "01BC",
+ "Tonesix", "0184",
+ "Tonetwo", "01A7",
+ "Tretroflexhook", "01AE",
+ "Tsecyrillic", "0426",
+ "Tshecyrillic", "040B",
+ "Twelveroman", "216B",
+ "Tworoman", "2161",
+ "U", "0055",
+ "Uacute", "00DA",
+ "Ubreve", "016C",
+ "Ucaron", "01D3",
+ "Ucircle", "24CA",
+ "Ucircumflex", "00DB",
+ "Ucircumflexbelow", "1E76",
+ "Ucyrillic", "0423",
+ "Udblacute", "0170",
+ "Udblgrave", "0214",
+ "Udieresis", "00DC",
+ "Udieresisacute", "01D7",
+ "Udieresisbelow", "1E72",
+ "Udieresiscaron", "01D9",
+ "Udieresiscyrillic", "04F0",
+ "Udieresisgrave", "01DB",
+ "Udieresismacron", "01D5",
+ "Udotbelow", "1EE4",
+ "Ugrave", "00D9",
+ "Uhookabove", "1EE6",
+ "Uhorn", "01AF",
+ "Uhornacute", "1EE8",
+ "Uhorndotbelow", "1EF0",
+ "Uhorngrave", "1EEA",
+ "Uhornhookabove", "1EEC",
+ "Uhorntilde", "1EEE",
+ "Uhungarumlaut", "0170",
+ "Uhungarumlautcyrillic", "04F2",
+ "Uinvertedbreve", "0216",
+ "Ukcyrillic", "0478",
+ "Umacron", "016A",
+ "Umacroncyrillic", "04EE",
+ "Umacrondieresis", "1E7A",
+ "Umonospace", "FF35",
+ "Uogonek", "0172",
+ "Upsilon", "03A5",
+ "Upsilon1", "03D2",
+ "Upsilonacutehooksymbolgreek", "03D3",
+ "Upsilonafrican", "01B1",
+ "Upsilondieresis", "03AB",
+ "Upsilondieresishooksymbolgreek", "03D4",
+ "Upsilonhooksymbol", "03D2",
+ "Upsilontonos", "038E",
+ "Uring", "016E",
+ "Ushortcyrillic", "040E",
+ "Ustraightcyrillic", "04AE",
+ "Ustraightstrokecyrillic", "04B0",
+ "Utilde", "0168",
+ "Utildeacute", "1E78",
+ "Utildebelow", "1E74",
+ "V", "0056",
+ "Vcircle", "24CB",
+ "Vdotbelow", "1E7E",
+ "Vecyrillic", "0412",
+ "Vewarmenian", "054E",
+ "Vhook", "01B2",
+ "Vmonospace", "FF36",
+ "Voarmenian", "0548",
+ "Vtilde", "1E7C",
+ "W", "0057",
+ "Wacute", "1E82",
+ "Wcircle", "24CC",
+ "Wcircumflex", "0174",
+ "Wdieresis", "1E84",
+ "Wdotaccent", "1E86",
+ "Wdotbelow", "1E88",
+ "Wgrave", "1E80",
+ "Wmonospace", "FF37",
+ "X", "0058",
+ "Xcircle", "24CD",
+ "Xdieresis", "1E8C",
+ "Xdotaccent", "1E8A",
+ "Xeharmenian", "053D",
+ "Xi", "039E",
+ "Xmonospace", "FF38",
+ "Y", "0059",
+ "Yacute", "00DD",
+ "Yatcyrillic", "0462",
+ "Ycircle", "24CE",
+ "Ycircumflex", "0176",
+ "Ydieresis", "0178",
+ "Ydotaccent", "1E8E",
+ "Ydotbelow", "1EF4",
+ "Yericyrillic", "042B",
+ "Yerudieresiscyrillic", "04F8",
+ "Ygrave", "1EF2",
+ "Yhook", "01B3",
+ "Yhookabove", "1EF6",
+ "Yiarmenian", "0545",
+ "Yicyrillic", "0407",
+ "Yiwnarmenian", "0552",
+ "Ymonospace", "FF39",
+ "Ytilde", "1EF8",
+ "Yusbigcyrillic", "046A",
+ "Yusbigiotifiedcyrillic", "046C",
+ "Yuslittlecyrillic", "0466",
+ "Yuslittleiotifiedcyrillic", "0468",
+ "Z", "005A",
+ "Zaarmenian", "0536",
+ "Zacute", "0179",
+ "Zcaron", "017D",
+ "Zcircle", "24CF",
+ "Zcircumflex", "1E90",
+ "Zdot", "017B",
+ "Zdotaccent", "017B",
+ "Zdotbelow", "1E92",
+ "Zecyrillic", "0417",
+ "Zedescendercyrillic", "0498",
+ "Zedieresiscyrillic", "04DE",
+ "Zeta", "0396",
+ "Zhearmenian", "053A",
+ "Zhebrevecyrillic", "04C1",
+ "Zhecyrillic", "0416",
+ "Zhedescendercyrillic", "0496",
+ "Zhedieresiscyrillic", "04DC",
+ "Zlinebelow", "1E94",
+ "Zmonospace", "FF3A",
+ "Zstroke", "01B5",
+ "a", "0061",
+ "aabengali", "0986",
+ "aacute", "00E1",
+ "aadeva", "0906",
+ "aagujarati", "0A86",
+ "aagurmukhi", "0A06",
+ "aamatragurmukhi", "0A3E",
+ "aarusquare", "3303",
+ "aavowelsignbengali", "09BE",
+ "aavowelsigndeva", "093E",
+ "aavowelsigngujarati", "0ABE",
+ "abbreviationmarkarmenian", "055F",
+ "abbreviationsigndeva", "0970",
+ "abengali", "0985",
+ "abopomofo", "311A",
+ "abreve", "0103",
+ "abreveacute", "1EAF",
+ "abrevecyrillic", "04D1",
+ "abrevedotbelow", "1EB7",
+ "abrevegrave", "1EB1",
+ "abrevehookabove", "1EB3",
+ "abrevetilde", "1EB5",
+ "acaron", "01CE",
+ "acircle", "24D0",
+ "acircumflex", "00E2",
+ "acircumflexacute", "1EA5",
+ "acircumflexdotbelow", "1EAD",
+ "acircumflexgrave", "1EA7",
+ "acircumflexhookabove", "1EA9",
+ "acircumflextilde", "1EAB",
+ "acute", "00B4",
+ "acutebelowcmb", "0317",
+ "acutecmb", "0301",
+ "acutecomb", "0301",
+ "acutedeva", "0954",
+ "acutelowmod", "02CF",
+ "acutetonecmb", "0341",
+ "acyrillic", "0430",
+ "adblgrave", "0201",
+ "addakgurmukhi", "0A71",
+ "adeva", "0905",
+ "adieresis", "00E4",
+ "adieresiscyrillic", "04D3",
+ "adieresismacron", "01DF",
+ "adotbelow", "1EA1",
+ "adotmacron", "01E1",
+ "ae", "00E6",
+ "aeacute", "01FD",
+ "aekorean", "3150",
+ "aemacron", "01E3",
+ "afii00208", "2015",
+ "afii08941", "20A4",
+ "afii10017", "0410",
+ "afii10018", "0411",
+ "afii10019", "0412",
+ "afii10020", "0413",
+ "afii10021", "0414",
+ "afii10022", "0415",
+ "afii10023", "0401",
+ "afii10024", "0416",
+ "afii10025", "0417",
+ "afii10026", "0418",
+ "afii10027", "0419",
+ "afii10028", "041A",
+ "afii10029", "041B",
+ "afii10030", "041C",
+ "afii10031", "041D",
+ "afii10032", "041E",
+ "afii10033", "041F",
+ "afii10034", "0420",
+ "afii10035", "0421",
+ "afii10036", "0422",
+ "afii10037", "0423",
+ "afii10038", "0424",
+ "afii10039", "0425",
+ "afii10040", "0426",
+ "afii10041", "0427",
+ "afii10042", "0428",
+ "afii10043", "0429",
+ "afii10044", "042A",
+ "afii10045", "042B",
+ "afii10046", "042C",
+ "afii10047", "042D",
+ "afii10048", "042E",
+ "afii10049", "042F",
+ "afii10050", "0490",
+ "afii10051", "0402",
+ "afii10052", "0403",
+ "afii10053", "0404",
+ "afii10054", "0405",
+ "afii10055", "0406",
+ "afii10056", "0407",
+ "afii10057", "0408",
+ "afii10058", "0409",
+ "afii10059", "040A",
+ "afii10060", "040B",
+ "afii10061", "040C",
+ "afii10062", "040E",
+ "afii10065", "0430",
+ "afii10066", "0431",
+ "afii10067", "0432",
+ "afii10068", "0433",
+ "afii10069", "0434",
+ "afii10070", "0435",
+ "afii10071", "0451",
+ "afii10072", "0436",
+ "afii10073", "0437",
+ "afii10074", "0438",
+ "afii10075", "0439",
+ "afii10076", "043A",
+ "afii10077", "043B",
+ "afii10078", "043C",
+ "afii10079", "043D",
+ "afii10080", "043E",
+ "afii10081", "043F",
+ "afii10082", "0440",
+ "afii10083", "0441",
+ "afii10084", "0442",
+ "afii10085", "0443",
+ "afii10086", "0444",
+ "afii10087", "0445",
+ "afii10088", "0446",
+ "afii10089", "0447",
+ "afii10090", "0448",
+ "afii10091", "0449",
+ "afii10092", "044A",
+ "afii10093", "044B",
+ "afii10094", "044C",
+ "afii10095", "044D",
+ "afii10096", "044E",
+ "afii10097", "044F",
+ "afii10098", "0491",
+ "afii10099", "0452",
+ "afii10100", "0453",
+ "afii10101", "0454",
+ "afii10102", "0455",
+ "afii10103", "0456",
+ "afii10104", "0457",
+ "afii10105", "0458",
+ "afii10106", "0459",
+ "afii10107", "045A",
+ "afii10108", "045B",
+ "afii10109", "045C",
+ "afii10110", "045E",
+ "afii10145", "040F",
+ "afii10146", "0462",
+ "afii10147", "0472",
+ "afii10148", "0474",
+ "afii10193", "045F",
+ "afii10194", "0463",
+ "afii10195", "0473",
+ "afii10196", "0475",
+ "afii10846", "04D9",
+ "afii299", "200E",
+ "afii300", "200F",
+ "afii301", "200D",
+ "afii57381", "066A",
+ "afii57388", "060C",
+ "afii57392", "0660",
+ "afii57393", "0661",
+ "afii57394", "0662",
+ "afii57395", "0663",
+ "afii57396", "0664",
+ "afii57397", "0665",
+ "afii57398", "0666",
+ "afii57399", "0667",
+ "afii57400", "0668",
+ "afii57401", "0669",
+ "afii57403", "061B",
+ "afii57407", "061F",
+ "afii57409", "0621",
+ "afii57410", "0622",
+ "afii57411", "0623",
+ "afii57412", "0624",
+ "afii57413", "0625",
+ "afii57414", "0626",
+ "afii57415", "0627",
+ "afii57416", "0628",
+ "afii57417", "0629",
+ "afii57418", "062A",
+ "afii57419", "062B",
+ "afii57420", "062C",
+ "afii57421", "062D",
+ "afii57422", "062E",
+ "afii57423", "062F",
+ "afii57424", "0630",
+ "afii57425", "0631",
+ "afii57426", "0632",
+ "afii57427", "0633",
+ "afii57428", "0634",
+ "afii57429", "0635",
+ "afii57430", "0636",
+ "afii57431", "0637",
+ "afii57432", "0638",
+ "afii57433", "0639",
+ "afii57434", "063A",
+ "afii57440", "0640",
+ "afii57441", "0641",
+ "afii57442", "0642",
+ "afii57443", "0643",
+ "afii57444", "0644",
+ "afii57445", "0645",
+ "afii57446", "0646",
+ "afii57448", "0648",
+ "afii57449", "0649",
+ "afii57450", "064A",
+ "afii57451", "064B",
+ "afii57452", "064C",
+ "afii57453", "064D",
+ "afii57454", "064E",
+ "afii57455", "064F",
+ "afii57456", "0650",
+ "afii57457", "0651",
+ "afii57458", "0652",
+ "afii57470", "0647",
+ "afii57505", "06A4",
+ "afii57506", "067E",
+ "afii57507", "0686",
+ "afii57508", "0698",
+ "afii57509", "06AF",
+ "afii57511", "0679",
+ "afii57512", "0688",
+ "afii57513", "0691",
+ "afii57514", "06BA",
+ "afii57519", "06D2",
+ "afii57534", "06D5",
+ "afii57636", "20AA",
+ "afii57645", "05BE",
+ "afii57658", "05C3",
+ "afii57664", "05D0",
+ "afii57665", "05D1",
+ "afii57666", "05D2",
+ "afii57667", "05D3",
+ "afii57668", "05D4",
+ "afii57669", "05D5",
+ "afii57670", "05D6",
+ "afii57671", "05D7",
+ "afii57672", "05D8",
+ "afii57673", "05D9",
+ "afii57674", "05DA",
+ "afii57675", "05DB",
+ "afii57676", "05DC",
+ "afii57677", "05DD",
+ "afii57678", "05DE",
+ "afii57679", "05DF",
+ "afii57680", "05E0",
+ "afii57681", "05E1",
+ "afii57682", "05E2",
+ "afii57683", "05E3",
+ "afii57684", "05E4",
+ "afii57685", "05E5",
+ "afii57686", "05E6",
+ "afii57687", "05E7",
+ "afii57688", "05E8",
+ "afii57689", "05E9",
+ "afii57690", "05EA",
+ "afii57694", "FB2A",
+ "afii57695", "FB2B",
+ "afii57700", "FB4B",
+ "afii57705", "FB1F",
+ "afii57716", "05F0",
+ "afii57717", "05F1",
+ "afii57718", "05F2",
+ "afii57723", "FB35",
+ "afii57793", "05B4",
+ "afii57794", "05B5",
+ "afii57795", "05B6",
+ "afii57796", "05BB",
+ "afii57797", "05B8",
+ "afii57798", "05B7",
+ "afii57799", "05B0",
+ "afii57800", "05B2",
+ "afii57801", "05B1",
+ "afii57802", "05B3",
+ "afii57803", "05C2",
+ "afii57804", "05C1",
+ "afii57806", "05B9",
+ "afii57807", "05BC",
+ "afii57839", "05BD",
+ "afii57841", "05BF",
+ "afii57842", "05C0",
+ "afii57929", "02BC",
+ "afii61248", "2105",
+ "afii61289", "2113",
+ "afii61352", "2116",
+ "afii61573", "202C",
+ "afii61574", "202D",
+ "afii61575", "202E",
+ "afii61664", "200C",
+ "afii63167", "066D",
+ "afii64937", "02BD",
+ "agrave", "00E0",
+ "agujarati", "0A85",
+ "agurmukhi", "0A05",
+ "ahiragana", "3042",
+ "ahookabove", "1EA3",
+ "aibengali", "0990",
+ "aibopomofo", "311E",
+ "aideva", "0910",
+ "aiecyrillic", "04D5",
+ "aigujarati", "0A90",
+ "aigurmukhi", "0A10",
+ "aimatragurmukhi", "0A48",
+ "ainarabic", "0639",
+ "ainfinalarabic", "FECA",
+ "aininitialarabic", "FECB",
+ "ainmedialarabic", "FECC",
+ "ainvertedbreve", "0203",
+ "aivowelsignbengali", "09C8",
+ "aivowelsigndeva", "0948",
+ "aivowelsigngujarati", "0AC8",
+ "akatakana", "30A2",
+ "akatakanahalfwidth", "FF71",
+ "akorean", "314F",
+ "alef", "05D0",
+ "alefarabic", "0627",
+ "alefdageshhebrew", "FB30",
+ "aleffinalarabic", "FE8E",
+ "alefhamzaabovearabic", "0623",
+ "alefhamzaabovefinalarabic", "FE84",
+ "alefhamzabelowarabic", "0625",
+ "alefhamzabelowfinalarabic", "FE88",
+ "alefhebrew", "05D0",
+ "aleflamedhebrew", "FB4F",
+ "alefmaddaabovearabic", "0622",
+ "alefmaddaabovefinalarabic", "FE82",
+ "alefmaksuraarabic", "0649",
+ "alefmaksurafinalarabic", "FEF0",
+ "alefmaksurainitialarabic", "FEF3",
+ "alefmaksuramedialarabic", "FEF4",
+ "alefpatahhebrew", "FB2E",
+ "alefqamatshebrew", "FB2F",
+ "aleph", "2135",
+ "allequal", "224C",
+ "alpha", "03B1",
+ "alphatonos", "03AC",
+ "amacron", "0101",
+ "amonospace", "FF41",
+ "ampersand", "0026",
+ "ampersandmonospace", "FF06",
+ "amsquare", "33C2",
+ "anbopomofo", "3122",
+ "angbopomofo", "3124",
+ "angkhankhuthai", "0E5A",
+ "angle", "2220",
+ "anglebracketleft", "3008",
+ "anglebracketleftvertical", "FE3F",
+ "anglebracketright", "3009",
+ "anglebracketrightvertical", "FE40",
+ "angleleft", "2329",
+ "angleright", "232A",
+ "angstrom", "212B",
+ "anoteleia", "0387",
+ "anudattadeva", "0952",
+ "anusvarabengali", "0982",
+ "anusvaradeva", "0902",
+ "anusvaragujarati", "0A82",
+ "aogonek", "0105",
+ "apaatosquare", "3300",
+ "aparen", "249C",
+ "apostrophearmenian", "055A",
+ "apostrophemod", "02BC",
+ "approaches", "2250",
+ "approxequal", "2248",
+ "approxequalorimage", "2252",
+ "approximatelyequal", "2245",
+ "araeaekorean", "318E",
+ "araeakorean", "318D",
+ "arc", "2312",
+ "arighthalfring", "1E9A",
+ "aring", "00E5",
+ "aringacute", "01FB",
+ "aringbelow", "1E01",
+ "arrowboth", "2194",
+ "arrowdashdown", "21E3",
+ "arrowdashleft", "21E0",
+ "arrowdashright", "21E2",
+ "arrowdashup", "21E1",
+ "arrowdblboth", "21D4",
+ "arrowdbldown", "21D3",
+ "arrowdblleft", "21D0",
+ "arrowdblright", "21D2",
+ "arrowdblup", "21D1",
+ "arrowdown", "2193",
+ "arrowdownleft", "2199",
+ "arrowdownright", "2198",
+ "arrowdownwhite", "21E9",
+ "arrowheaddownmod", "02C5",
+ "arrowheadleftmod", "02C2",
+ "arrowheadrightmod", "02C3",
+ "arrowheadupmod", "02C4",
+ "arrowleft", "2190",
+ "arrowleftdbl", "21D0",
+ "arrowleftdblstroke", "21CD",
+ "arrowleftoverright", "21C6",
+ "arrowleftwhite", "21E6",
+ "arrowright", "2192",
+ "arrowrightdblstroke", "21CF",
+ "arrowrightheavy", "279E",
+ "arrowrightoverleft", "21C4",
+ "arrowrightwhite", "21E8",
+ "arrowtableft", "21E4",
+ "arrowtabright", "21E5",
+ "arrowup", "2191",
+ "arrowupdn", "2195",
+ "arrowupdnbse", "21A8",
+ "arrowupdownbase", "21A8",
+ "arrowupleft", "2196",
+ "arrowupleftofdown", "21C5",
+ "arrowupright", "2197",
+ "arrowupwhite", "21E7",
+ "asciicircum", "005E",
+ "asciicircummonospace", "FF3E",
+ "asciitilde", "007E",
+ "asciitildemonospace", "FF5E",
+ "ascript", "0251",
+ "ascriptturned", "0252",
+ "asmallhiragana", "3041",
+ "asmallkatakana", "30A1",
+ "asmallkatakanahalfwidth", "FF67",
+ "asterisk", "002A",
+ "asteriskaltonearabic", "066D",
+ "asteriskarabic", "066D",
+ "asteriskmath", "2217",
+ "asteriskmonospace", "FF0A",
+ "asterisksmall", "FE61",
+ "asterism", "2042",
+ "asymptoticallyequal", "2243",
+ "at", "0040",
+ "atilde", "00E3",
+ "atmonospace", "FF20",
+ "atsmall", "FE6B",
+ "aturned", "0250",
+ "aubengali", "0994",
+ "aubopomofo", "3120",
+ "audeva", "0914",
+ "augujarati", "0A94",
+ "augurmukhi", "0A14",
+ "aulengthmarkbengali", "09D7",
+ "aumatragurmukhi", "0A4C",
+ "auvowelsignbengali", "09CC",
+ "auvowelsigndeva", "094C",
+ "auvowelsigngujarati", "0ACC",
+ "avagrahadeva", "093D",
+ "aybarmenian", "0561",
+ "ayin", "05E2",
+ "ayinaltonehebrew", "FB20",
+ "ayinhebrew", "05E2",
+ "b", "0062",
+ "babengali", "09AC",
+ "backslash", "005C",
+ "backslashmonospace", "FF3C",
+ "badeva", "092C",
+ "bagujarati", "0AAC",
+ "bagurmukhi", "0A2C",
+ "bahiragana", "3070",
+ "bahtthai", "0E3F",
+ "bakatakana", "30D0",
+ "bar", "007C",
+ "barmonospace", "FF5C",
+ "bbopomofo", "3105",
+ "bcircle", "24D1",
+ "bdotaccent", "1E03",
+ "bdotbelow", "1E05",
+ "beamedsixteenthnotes", "266C",
+ "because", "2235",
+ "becyrillic", "0431",
+ "beharabic", "0628",
+ "behfinalarabic", "FE90",
+ "behinitialarabic", "FE91",
+ "behiragana", "3079",
+ "behmedialarabic", "FE92",
+ "behmeeminitialarabic", "FC9F",
+ "behmeemisolatedarabic", "FC08",
+ "behnoonfinalarabic", "FC6D",
+ "bekatakana", "30D9",
+ "benarmenian", "0562",
+ "bet", "05D1",
+ "beta", "03B2",
+ "betasymbolgreek", "03D0",
+ "betdagesh", "FB31",
+ "betdageshhebrew", "FB31",
+ "bethebrew", "05D1",
+ "betrafehebrew", "FB4C",
+ "bhabengali", "09AD",
+ "bhadeva", "092D",
+ "bhagujarati", "0AAD",
+ "bhagurmukhi", "0A2D",
+ "bhook", "0253",
+ "bihiragana", "3073",
+ "bikatakana", "30D3",
+ "bilabialclick", "0298",
+ "bindigurmukhi", "0A02",
+ "birusquare", "3331",
+ "blackcircle", "25CF",
+ "blackdiamond", "25C6",
+ "blackdownpointingtriangle", "25BC",
+ "blackleftpointingpointer", "25C4",
+ "blackleftpointingtriangle", "25C0",
+ "blacklenticularbracketleft", "3010",
+ "blacklenticularbracketleftvertical", "FE3B",
+ "blacklenticularbracketright", "3011",
+ "blacklenticularbracketrightvertical", "FE3C",
+ "blacklowerlefttriangle", "25E3",
+ "blacklowerrighttriangle", "25E2",
+ "blackrectangle", "25AC",
+ "blackrightpointingpointer", "25BA",
+ "blackrightpointingtriangle", "25B6",
+ "blacksmallsquare", "25AA",
+ "blacksmilingface", "263B",
+ "blacksquare", "25A0",
+ "blackstar", "2605",
+ "blackupperlefttriangle", "25E4",
+ "blackupperrighttriangle", "25E5",
+ "blackuppointingsmalltriangle", "25B4",
+ "blackuppointingtriangle", "25B2",
+ "blank", "2423",
+ "blinebelow", "1E07",
+ "block", "2588",
+ "bmonospace", "FF42",
+ "bobaimaithai", "0E1A",
+ "bohiragana", "307C",
+ "bokatakana", "30DC",
+ "bparen", "249D",
+ "bqsquare", "33C3",
+ "braceleft", "007B",
+ "braceleftmonospace", "FF5B",
+ "braceleftsmall", "FE5B",
+ "braceleftvertical", "FE37",
+ "braceright", "007D",
+ "bracerightmonospace", "FF5D",
+ "bracerightsmall", "FE5C",
+ "bracerightvertical", "FE38",
+ "bracketleft", "005B",
+ "bracketleftmonospace", "FF3B",
+ "bracketright", "005D",
+ "bracketrightmonospace", "FF3D",
+ "breve", "02D8",
+ "brevebelowcmb", "032E",
+ "brevecmb", "0306",
+ "breveinvertedbelowcmb", "032F",
+ "breveinvertedcmb", "0311",
+ "breveinverteddoublecmb", "0361",
+ "bridgebelowcmb", "032A",
+ "bridgeinvertedbelowcmb", "033A",
+ "brokenbar", "00A6",
+ "bstroke", "0180",
+ "btopbar", "0183",
+ "buhiragana", "3076",
+ "bukatakana", "30D6",
+ "bullet", "2022",
+ "bulletinverse", "25D8",
+ "bulletoperator", "2219",
+ "bullseye", "25CE",
+ "c", "0063",
+ "caarmenian", "056E",
+ "cabengali", "099A",
+ "cacute", "0107",
+ "cadeva", "091A",
+ "cagujarati", "0A9A",
+ "cagurmukhi", "0A1A",
+ "calsquare", "3388",
+ "candrabindubengali", "0981",
+ "candrabinducmb", "0310",
+ "candrabindudeva", "0901",
+ "candrabindugujarati", "0A81",
+ "capslock", "21EA",
+ "careof", "2105",
+ "caron", "02C7",
+ "caronbelowcmb", "032C",
+ "caroncmb", "030C",
+ "carriagereturn", "21B5",
+ "cbopomofo", "3118",
+ "ccaron", "010D",
+ "ccedilla", "00E7",
+ "ccedillaacute", "1E09",
+ "ccircle", "24D2",
+ "ccircumflex", "0109",
+ "ccurl", "0255",
+ "cdot", "010B",
+ "cdotaccent", "010B",
+ "cdsquare", "33C5",
+ "cedilla", "00B8",
+ "cedillacmb", "0327",
+ "cent", "00A2",
+ "centigrade", "2103",
+ "centmonospace", "FFE0",
+ "chaarmenian", "0579",
+ "chabengali", "099B",
+ "chadeva", "091B",
+ "chagujarati", "0A9B",
+ "chagurmukhi", "0A1B",
+ "chbopomofo", "3114",
+ "cheabkhasiancyrillic", "04BD",
+ "checkmark", "2713",
+ "checyrillic", "0447",
+ "chedescenderabkhasiancyrillic", "04BF",
+ "chedescendercyrillic", "04B7",
+ "chedieresiscyrillic", "04F5",
+ "cheharmenian", "0573",
+ "chekhakassiancyrillic", "04CC",
+ "cheverticalstrokecyrillic", "04B9",
+ "chi", "03C7",
+ "chieuchacirclekorean", "3277",
+ "chieuchaparenkorean", "3217",
+ "chieuchcirclekorean", "3269",
+ "chieuchkorean", "314A",
+ "chieuchparenkorean", "3209",
+ "chochangthai", "0E0A",
+ "chochanthai", "0E08",
+ "chochingthai", "0E09",
+ "chochoethai", "0E0C",
+ "chook", "0188",
+ "cieucacirclekorean", "3276",
+ "cieucaparenkorean", "3216",
+ "cieuccirclekorean", "3268",
+ "cieuckorean", "3148",
+ "cieucparenkorean", "3208",
+ "cieucuparenkorean", "321C",
+ "circle", "25CB",
+ "circlemultiply", "2297",
+ "circleot", "2299",
+ "circleplus", "2295",
+ "circlepostalmark", "3036",
+ "circlewithlefthalfblack", "25D0",
+ "circlewithrighthalfblack", "25D1",
+ "circumflex", "02C6",
+ "circumflexbelowcmb", "032D",
+ "circumflexcmb", "0302",
+ "clear", "2327",
+ "clickalveolar", "01C2",
+ "clickdental", "01C0",
+ "clicklateral", "01C1",
+ "clickretroflex", "01C3",
+ "club", "2663",
+ "clubsuitblack", "2663",
+ "clubsuitwhite", "2667",
+ "cmcubedsquare", "33A4",
+ "cmonospace", "FF43",
+ "cmsquaredsquare", "33A0",
+ "coarmenian", "0581",
+ "colon", "003A",
+ "colonmonetary", "20A1",
+ "colonmonospace", "FF1A",
+ "colonsign", "20A1",
+ "colonsmall", "FE55",
+ "colontriangularhalfmod", "02D1",
+ "colontriangularmod", "02D0",
+ "comma", "002C",
+ "commaabovecmb", "0313",
+ "commaaboverightcmb", "0315",
+ "commaarabic", "060C",
+ "commaarmenian", "055D",
+ "commamonospace", "FF0C",
+ "commareversedabovecmb", "0314",
+ "commareversedmod", "02BD",
+ "commasmall", "FE50",
+ "commaturnedabovecmb", "0312",
+ "commaturnedmod", "02BB",
+ "compass", "263C",
+ "congruent", "2245",
+ "contourintegral", "222E",
+ "control", "2303",
+ "controlACK", "0006",
+ "controlBEL", "0007",
+ "controlBS", "0008",
+ "controlCAN", "0018",
+ "controlCR", "000D",
+ "controlDC1", "0011",
+ "controlDC2", "0012",
+ "controlDC3", "0013",
+ "controlDC4", "0014",
+ "controlDEL", "007F",
+ "controlDLE", "0010",
+ "controlEM", "0019",
+ "controlENQ", "0005",
+ "controlEOT", "0004",
+ "controlESC", "001B",
+ "controlETB", "0017",
+ "controlETX", "0003",
+ "controlFF", "000C",
+ "controlFS", "001C",
+ "controlGS", "001D",
+ "controlHT", "0009",
+ "controlLF", "000A",
+ "controlNAK", "0015",
+ "controlRS", "001E",
+ "controlSI", "000F",
+ "controlSO", "000E",
+ "controlSOT", "0002",
+ "controlSTX", "0001",
+ "controlSUB", "001A",
+ "controlSYN", "0016",
+ "controlUS", "001F",
+ "controlVT", "000B",
+ "copyright", "00A9",
+ "cornerbracketleft", "300C",
+ "cornerbracketlefthalfwidth", "FF62",
+ "cornerbracketleftvertical", "FE41",
+ "cornerbracketright", "300D",
+ "cornerbracketrighthalfwidth", "FF63",
+ "cornerbracketrightvertical", "FE42",
+ "corporationsquare", "337F",
+ "cosquare", "33C7",
+ "coverkgsquare", "33C6",
+ "cparen", "249E",
+ "cruzeiro", "20A2",
+ "cstretched", "0297",
+ "curlyand", "22CF",
+ "curlyor", "22CE",
+ "currency", "00A4",
+ "d", "0064",
+ "daarmenian", "0564",
+ "dabengali", "09A6",
+ "dadarabic", "0636",
+ "dadeva", "0926",
+ "dadfinalarabic", "FEBE",
+ "dadinitialarabic", "FEBF",
+ "dadmedialarabic", "FEC0",
+ "dagesh", "05BC",
+ "dageshhebrew", "05BC",
+ "dagger", "2020",
+ "daggerdbl", "2021",
+ "dagujarati", "0AA6",
+ "dagurmukhi", "0A26",
+ "dahiragana", "3060",
+ "dakatakana", "30C0",
+ "dalarabic", "062F",
+ "dalet", "05D3",
+ "daletdagesh", "FB33",
+ "daletdageshhebrew", "FB33",
+ "dalethatafpatah", "05D3_05B2",
+ "dalethatafpatahhebrew", "05D3_05B2",
+ "dalethatafsegol", "05D3_05B1",
+ "dalethatafsegolhebrew", "05D3_05B1",
+ "dalethebrew", "05D3",
+ "dalethiriq", "05D3_05B4",
+ "dalethiriqhebrew", "05D3_05B4",
+ "daletholam", "05D3_05B9",
+ "daletholamhebrew", "05D3_05B9",
+ "daletpatah", "05D3_05B7",
+ "daletpatahhebrew", "05D3_05B7",
+ "daletqamats", "05D3_05B8",
+ "daletqamatshebrew", "05D3_05B8",
+ "daletqubuts", "05D3_05BB",
+ "daletqubutshebrew", "05D3_05BB",
+ "daletsegol", "05D3_05B6",
+ "daletsegolhebrew", "05D3_05B6",
+ "daletsheva", "05D3_05B0",
+ "daletshevahebrew", "05D3_05B0",
+ "dalettsere", "05D3_05B5",
+ "dalettserehebrew", "05D3_05B5",
+ "dalfinalarabic", "FEAA",
+ "dammaarabic", "064F",
+ "dammalowarabic", "064F",
+ "dammatanaltonearabic", "064C",
+ "dammatanarabic", "064C",
+ "danda", "0964",
+ "dargahebrew", "05A7",
+ "dargalefthebrew", "05A7",
+ "dasiapneumatacyrilliccmb", "0485",
+ "dblanglebracketleft", "300A",
+ "dblanglebracketleftvertical", "FE3D",
+ "dblanglebracketright", "300B",
+ "dblanglebracketrightvertical", "FE3E",
+ "dblarchinvertedbelowcmb", "032B",
+ "dblarrowleft", "21D4",
+ "dblarrowright", "21D2",
+ "dbldanda", "0965",
+ "dblgravecmb", "030F",
+ "dblintegral", "222C",
+ "dbllowline", "2017",
+ "dbllowlinecmb", "0333",
+ "dbloverlinecmb", "033F",
+ "dblprimemod", "02BA",
+ "dblverticalbar", "2016",
+ "dblverticallineabovecmb", "030E",
+ "dbopomofo", "3109",
+ "dbsquare", "33C8",
+ "dcaron", "010F",
+ "dcedilla", "1E11",
+ "dcircle", "24D3",
+ "dcircumflexbelow", "1E13",
+ "dcroat", "0111",
+ "ddabengali", "09A1",
+ "ddadeva", "0921",
+ "ddagujarati", "0AA1",
+ "ddagurmukhi", "0A21",
+ "ddalarabic", "0688",
+ "ddalfinalarabic", "FB89",
+ "dddhadeva", "095C",
+ "ddhabengali", "09A2",
+ "ddhadeva", "0922",
+ "ddhagujarati", "0AA2",
+ "ddhagurmukhi", "0A22",
+ "ddotaccent", "1E0B",
+ "ddotbelow", "1E0D",
+ "decimalseparatorarabic", "066B",
+ "decimalseparatorpersian", "066B",
+ "decyrillic", "0434",
+ "degree", "00B0",
+ "dehihebrew", "05AD",
+ "dehiragana", "3067",
+ "deicoptic", "03EF",
+ "dekatakana", "30C7",
+ "deleteleft", "232B",
+ "deleteright", "2326",
+ "delta", "03B4",
+ "deltaturned", "018D",
+ "denominatorminusonenumeratorbengali", "09F8",
+ "dezh", "02A4",
+ "dhabengali", "09A7",
+ "dhadeva", "0927",
+ "dhagujarati", "0AA7",
+ "dhagurmukhi", "0A27",
+ "dhook", "0257",
+ "dialytikatonos", "0385",
+ "dialytikatonoscmb", "0344",
+ "diamond", "2666",
+ "diamondsuitwhite", "2662",
+ "dieresis", "00A8",
+ "dieresisbelowcmb", "0324",
+ "dieresiscmb", "0308",
+ "dieresistonos", "0385",
+ "dihiragana", "3062",
+ "dikatakana", "30C2",
+ "dittomark", "3003",
+ "divide", "00F7",
+ "divides", "2223",
+ "divisionslash", "2215",
+ "djecyrillic", "0452",
+ "dkshade", "2593",
+ "dlinebelow", "1E0F",
+ "dlsquare", "3397",
+ "dmacron", "0111",
+ "dmonospace", "FF44",
+ "dnblock", "2584",
+ "dochadathai", "0E0E",
+ "dodekthai", "0E14",
+ "dohiragana", "3069",
+ "dokatakana", "30C9",
+ "dollar", "0024",
+ "dollarmonospace", "FF04",
+ "dollarsmall", "FE69",
+ "dong", "20AB",
+ "dorusquare", "3326",
+ "dotaccent", "02D9",
+ "dotaccentcmb", "0307",
+ "dotbelowcmb", "0323",
+ "dotbelowcomb", "0323",
+ "dotkatakana", "30FB",
+ "dotlessi", "0131",
+ "dotlessjstrokehook", "0284",
+ "dotmath", "22C5",
+ "dottedcircle", "25CC",
+ "doubleyodpatah", "FB1F",
+ "doubleyodpatahhebrew", "FB1F",
+ "downtackbelowcmb", "031E",
+ "downtackmod", "02D5",
+ "dparen", "249F",
+ "dtail", "0256",
+ "dtopbar", "018C",
+ "duhiragana", "3065",
+ "dukatakana", "30C5",
+ "dz", "01F3",
+ "dzaltone", "02A3",
+ "dzcaron", "01C6",
+ "dzcurl", "02A5",
+ "dzeabkhasiancyrillic", "04E1",
+ "dzecyrillic", "0455",
+ "dzhecyrillic", "045F",
+ "e", "0065",
+ "eacute", "00E9",
+ "earth", "2641",
+ "ebengali", "098F",
+ "ebopomofo", "311C",
+ "ebreve", "0115",
+ "ecandradeva", "090D",
+ "ecandragujarati", "0A8D",
+ "ecandravowelsigndeva", "0945",
+ "ecandravowelsigngujarati", "0AC5",
+ "ecaron", "011B",
+ "ecedillabreve", "1E1D",
+ "echarmenian", "0565",
+ "echyiwnarmenian", "0587",
+ "ecircle", "24D4",
+ "ecircumflex", "00EA",
+ "ecircumflexacute", "1EBF",
+ "ecircumflexbelow", "1E19",
+ "ecircumflexdotbelow", "1EC7",
+ "ecircumflexgrave", "1EC1",
+ "ecircumflexhookabove", "1EC3",
+ "ecircumflextilde", "1EC5",
+ "ecyrillic", "0454",
+ "edblgrave", "0205",
+ "edeva", "090F",
+ "edieresis", "00EB",
+ "edot", "0117",
+ "edotaccent", "0117",
+ "edotbelow", "1EB9",
+ "eegurmukhi", "0A0F",
+ "eematragurmukhi", "0A47",
+ "efcyrillic", "0444",
+ "egrave", "00E8",
+ "egujarati", "0A8F",
+ "eharmenian", "0567",
+ "ehbopomofo", "311D",
+ "ehiragana", "3048",
+ "ehookabove", "1EBB",
+ "eibopomofo", "311F",
+ "eight", "0038",
+ "eightarabic", "0668",
+ "eightbengali", "09EE",
+ "eightcircle", "2467",
+ "eightcircleinversesansserif", "2791",
+ "eightdeva", "096E",
+ "eighteencircle", "2471",
+ "eighteenparen", "2485",
+ "eighteenperiod", "2499",
+ "eightgujarati", "0AEE",
+ "eightgurmukhi", "0A6E",
+ "eighthackarabic", "0668",
+ "eighthangzhou", "3028",
+ "eighthnotebeamed", "266B",
+ "eightideographicparen", "3227",
+ "eightinferior", "2088",
+ "eightmonospace", "FF18",
+ "eightparen", "247B",
+ "eightperiod", "248F",
+ "eightpersian", "06F8",
+ "eightroman", "2177",
+ "eightsuperior", "2078",
+ "eightthai", "0E58",
+ "einvertedbreve", "0207",
+ "eiotifiedcyrillic", "0465",
+ "ekatakana", "30A8",
+ "ekatakanahalfwidth", "FF74",
+ "ekonkargurmukhi", "0A74",
+ "ekorean", "3154",
+ "elcyrillic", "043B",
+ "element", "2208",
+ "elevencircle", "246A",
+ "elevenparen", "247E",
+ "elevenperiod", "2492",
+ "elevenroman", "217A",
+ "ellipsis", "2026",
+ "ellipsisvertical", "22EE",
+ "emacron", "0113",
+ "emacronacute", "1E17",
+ "emacrongrave", "1E15",
+ "emcyrillic", "043C",
+ "emdash", "2014",
+ "emdashvertical", "FE31",
+ "emonospace", "FF45",
+ "emphasismarkarmenian", "055B",
+ "emptyset", "2205",
+ "enbopomofo", "3123",
+ "encyrillic", "043D",
+ "endash", "2013",
+ "endashvertical", "FE32",
+ "endescendercyrillic", "04A3",
+ "eng", "014B",
+ "engbopomofo", "3125",
+ "enghecyrillic", "04A5",
+ "enhookcyrillic", "04C8",
+ "enspace", "2002",
+ "eogonek", "0119",
+ "eokorean", "3153",
+ "eopen", "025B",
+ "eopenclosed", "029A",
+ "eopenreversed", "025C",
+ "eopenreversedclosed", "025E",
+ "eopenreversedhook", "025D",
+ "eparen", "24A0",
+ "epsilon", "03B5",
+ "epsilontonos", "03AD",
+ "equal", "003D",
+ "equalmonospace", "FF1D",
+ "equalsmall", "FE66",
+ "equalsuperior", "207C",
+ "equivalence", "2261",
+ "erbopomofo", "3126",
+ "ercyrillic", "0440",
+ "ereversed", "0258",
+ "ereversedcyrillic", "044D",
+ "escyrillic", "0441",
+ "esdescendercyrillic", "04AB",
+ "esh", "0283",
+ "eshcurl", "0286",
+ "eshortdeva", "090E",
+ "eshortvowelsigndeva", "0946",
+ "eshreversedloop", "01AA",
+ "eshsquatreversed", "0285",
+ "esmallhiragana", "3047",
+ "esmallkatakana", "30A7",
+ "esmallkatakanahalfwidth", "FF6A",
+ "estimated", "212E",
+ "eta", "03B7",
+ "etarmenian", "0568",
+ "etatonos", "03AE",
+ "eth", "00F0",
+ "etilde", "1EBD",
+ "etildebelow", "1E1B",
+ "etnahtafoukhhebrew", "0591",
+ "etnahtafoukhlefthebrew", "0591",
+ "etnahtahebrew", "0591",
+ "etnahtalefthebrew", "0591",
+ "eturned", "01DD",
+ "eukorean", "3161",
+ "euro", "20AC",
+ "evowelsignbengali", "09C7",
+ "evowelsigndeva", "0947",
+ "evowelsigngujarati", "0AC7",
+ "exclam", "0021",
+ "exclamarmenian", "055C",
+ "exclamdbl", "203C",
+ "exclamdown", "00A1",
+ "exclammonospace", "FF01",
+ "existential", "2203",
+ "ezh", "0292",
+ "ezhcaron", "01EF",
+ "ezhcurl", "0293",
+ "ezhreversed", "01B9",
+ "ezhtail", "01BA",
+ "f", "0066",
+ "fadeva", "095E",
+ "fagurmukhi", "0A5E",
+ "fahrenheit", "2109",
+ "fathaarabic", "064E",
+ "fathalowarabic", "064E",
+ "fathatanarabic", "064B",
+ "fbopomofo", "3108",
+ "fcircle", "24D5",
+ "fdotaccent", "1E1F",
+ "feharabic", "0641",
+ "feharmenian", "0586",
+ "fehfinalarabic", "FED2",
+ "fehinitialarabic", "FED3",
+ "fehmedialarabic", "FED4",
+ "feicoptic", "03E5",
+ "female", "2640",
+ "ff", "FB00",
+ "ffi", "FB03",
+ "ffl", "FB04",
+ "fi", "FB01",
+ "fifteencircle", "246E",
+ "fifteenparen", "2482",
+ "fifteenperiod", "2496",
+ "figuredash", "2012",
+ "filledbox", "25A0",
+ "filledrect", "25AC",
+ "finalkaf", "05DA",
+ "finalkafdagesh", "FB3A",
+ "finalkafdageshhebrew", "FB3A",
+ "finalkafhebrew", "05DA",
+ "finalkafqamats", "05DA_05B8",
+ "finalkafqamatshebrew", "05DA_05B8",
+ "finalkafsheva", "05DA_05B0",
+ "finalkafshevahebrew", "05DA_05B0",
+ "finalmem", "05DD",
+ "finalmemhebrew", "05DD",
+ "finalnun", "05DF",
+ "finalnunhebrew", "05DF",
+ "finalpe", "05E3",
+ "finalpehebrew", "05E3",
+ "finaltsadi", "05E5",
+ "finaltsadihebrew", "05E5",
+ "firsttonechinese", "02C9",
+ "fisheye", "25C9",
+ "fitacyrillic", "0473",
+ "five", "0035",
+ "fivearabic", "0665",
+ "fivebengali", "09EB",
+ "fivecircle", "2464",
+ "fivecircleinversesansserif", "278E",
+ "fivedeva", "096B",
+ "fiveeighths", "215D",
+ "fivegujarati", "0AEB",
+ "fivegurmukhi", "0A6B",
+ "fivehackarabic", "0665",
+ "fivehangzhou", "3025",
+ "fiveideographicparen", "3224",
+ "fiveinferior", "2085",
+ "fivemonospace", "FF15",
+ "fiveparen", "2478",
+ "fiveperiod", "248C",
+ "fivepersian", "06F5",
+ "fiveroman", "2174",
+ "fivesuperior", "2075",
+ "fivethai", "0E55",
+ "fl", "FB02",
+ "florin", "0192",
+ "fmonospace", "FF46",
+ "fmsquare", "3399",
+ "fofanthai", "0E1F",
+ "fofathai", "0E1D",
+ "fongmanthai", "0E4F",
+ "forall", "2200",
+ "four", "0034",
+ "fourarabic", "0664",
+ "fourbengali", "09EA",
+ "fourcircle", "2463",
+ "fourcircleinversesansserif", "278D",
+ "fourdeva", "096A",
+ "fourgujarati", "0AEA",
+ "fourgurmukhi", "0A6A",
+ "fourhackarabic", "0664",
+ "fourhangzhou", "3024",
+ "fourideographicparen", "3223",
+ "fourinferior", "2084",
+ "fourmonospace", "FF14",
+ "fournumeratorbengali", "09F7",
+ "fourparen", "2477",
+ "fourperiod", "248B",
+ "fourpersian", "06F4",
+ "fourroman", "2173",
+ "foursuperior", "2074",
+ "fourteencircle", "246D",
+ "fourteenparen", "2481",
+ "fourteenperiod", "2495",
+ "fourthai", "0E54",
+ "fourthtonechinese", "02CB",
+ "fparen", "24A1",
+ "fraction", "2044",
+ "franc", "20A3",
+ "g", "0067",
+ "gabengali", "0997",
+ "gacute", "01F5",
+ "gadeva", "0917",
+ "gafarabic", "06AF",
+ "gaffinalarabic", "FB93",
+ "gafinitialarabic", "FB94",
+ "gafmedialarabic", "FB95",
+ "gagujarati", "0A97",
+ "gagurmukhi", "0A17",
+ "gahiragana", "304C",
+ "gakatakana", "30AC",
+ "gamma", "03B3",
+ "gammalatinsmall", "0263",
+ "gammasuperior", "02E0",
+ "gangiacoptic", "03EB",
+ "gbopomofo", "310D",
+ "gbreve", "011F",
+ "gcaron", "01E7",
+ "gcedilla", "0123",
+ "gcircle", "24D6",
+ "gcircumflex", "011D",
+ "gcommaaccent", "0123",
+ "gdot", "0121",
+ "gdotaccent", "0121",
+ "gecyrillic", "0433",
+ "gehiragana", "3052",
+ "gekatakana", "30B2",
+ "geometricallyequal", "2251",
+ "gereshaccenthebrew", "059C",
+ "gereshhebrew", "05F3",
+ "gereshmuqdamhebrew", "059D",
+ "germandbls", "00DF",
+ "gershayimaccenthebrew", "059E",
+ "gershayimhebrew", "05F4",
+ "getamark", "3013",
+ "ghabengali", "0998",
+ "ghadarmenian", "0572",
+ "ghadeva", "0918",
+ "ghagujarati", "0A98",
+ "ghagurmukhi", "0A18",
+ "ghainarabic", "063A",
+ "ghainfinalarabic", "FECE",
+ "ghaininitialarabic", "FECF",
+ "ghainmedialarabic", "FED0",
+ "ghemiddlehookcyrillic", "0495",
+ "ghestrokecyrillic", "0493",
+ "gheupturncyrillic", "0491",
+ "ghhadeva", "095A",
+ "ghhagurmukhi", "0A5A",
+ "ghook", "0260",
+ "ghzsquare", "3393",
+ "gihiragana", "304E",
+ "gikatakana", "30AE",
+ "gimarmenian", "0563",
+ "gimel", "05D2",
+ "gimeldagesh", "FB32",
+ "gimeldageshhebrew", "FB32",
+ "gimelhebrew", "05D2",
+ "gjecyrillic", "0453",
+ "glottalinvertedstroke", "01BE",
+ "glottalstop", "0294",
+ "glottalstopinverted", "0296",
+ "glottalstopmod", "02C0",
+ "glottalstopreversed", "0295",
+ "glottalstopreversedmod", "02C1",
+ "glottalstopreversedsuperior", "02E4",
+ "glottalstopstroke", "02A1",
+ "glottalstopstrokereversed", "02A2",
+ "gmacron", "1E21",
+ "gmonospace", "FF47",
+ "gohiragana", "3054",
+ "gokatakana", "30B4",
+ "gparen", "24A2",
+ "gpasquare", "33AC",
+ "gradient", "2207",
+ "grave", "0060",
+ "gravebelowcmb", "0316",
+ "gravecmb", "0300",
+ "gravecomb", "0300",
+ "gravedeva", "0953",
+ "gravelowmod", "02CE",
+ "gravemonospace", "FF40",
+ "gravetonecmb", "0340",
+ "greater", "003E",
+ "greaterequal", "2265",
+ "greaterequalorless", "22DB",
+ "greatermonospace", "FF1E",
+ "greaterorequivalent", "2273",
+ "greaterorless", "2277",
+ "greateroverequal", "2267",
+ "greatersmall", "FE65",
+ "gscript", "0261",
+ "gstroke", "01E5",
+ "guhiragana", "3050",
+ "guillemotleft", "00AB",
+ "guillemotright", "00BB",
+ "guilsinglleft", "2039",
+ "guilsinglright", "203A",
+ "gukatakana", "30B0",
+ "guramusquare", "3318",
+ "gysquare", "33C9",
+ "h", "0068",
+ "haabkhasiancyrillic", "04A9",
+ "haaltonearabic", "06C1",
+ "habengali", "09B9",
+ "hadescendercyrillic", "04B3",
+ "hadeva", "0939",
+ "hagujarati", "0AB9",
+ "hagurmukhi", "0A39",
+ "haharabic", "062D",
+ "hahfinalarabic", "FEA2",
+ "hahinitialarabic", "FEA3",
+ "hahiragana", "306F",
+ "hahmedialarabic", "FEA4",
+ "haitusquare", "332A",
+ "hakatakana", "30CF",
+ "hakatakanahalfwidth", "FF8A",
+ "halantgurmukhi", "0A4D",
+ "hamzaarabic", "0621",
+ "hamzadammaarabic", "0621_064F",
+ "hamzadammatanarabic", "0621_064C",
+ "hamzafathaarabic", "0621_064E",
+ "hamzafathatanarabic", "0621_064B",
+ "hamzalowarabic", "0621",
+ "hamzalowkasraarabic", "0621_0650",
+ "hamzalowkasratanarabic", "0621_064D",
+ "hamzasukunarabic", "0621_0652",
+ "hangulfiller", "3164",
+ "hardsigncyrillic", "044A",
+ "harpoonleftbarbup", "21BC",
+ "harpoonrightbarbup", "21C0",
+ "hasquare", "33CA",
+ "hatafpatah", "05B2",
+ "hatafpatah16", "05B2",
+ "hatafpatah23", "05B2",
+ "hatafpatah2f", "05B2",
+ "hatafpatahhebrew", "05B2",
+ "hatafpatahnarrowhebrew", "05B2",
+ "hatafpatahquarterhebrew", "05B2",
+ "hatafpatahwidehebrew", "05B2",
+ "hatafqamats", "05B3",
+ "hatafqamats1b", "05B3",
+ "hatafqamats28", "05B3",
+ "hatafqamats34", "05B3",
+ "hatafqamatshebrew", "05B3",
+ "hatafqamatsnarrowhebrew", "05B3",
+ "hatafqamatsquarterhebrew", "05B3",
+ "hatafqamatswidehebrew", "05B3",
+ "hatafsegol", "05B1",
+ "hatafsegol17", "05B1",
+ "hatafsegol24", "05B1",
+ "hatafsegol30", "05B1",
+ "hatafsegolhebrew", "05B1",
+ "hatafsegolnarrowhebrew", "05B1",
+ "hatafsegolquarterhebrew", "05B1",
+ "hatafsegolwidehebrew", "05B1",
+ "hbar", "0127",
+ "hbopomofo", "310F",
+ "hbrevebelow", "1E2B",
+ "hcedilla", "1E29",
+ "hcircle", "24D7",
+ "hcircumflex", "0125",
+ "hdieresis", "1E27",
+ "hdotaccent", "1E23",
+ "hdotbelow", "1E25",
+ "he", "05D4",
+ "heart", "2665",
+ "heartsuitblack", "2665",
+ "heartsuitwhite", "2661",
+ "hedagesh", "FB34",
+ "hedageshhebrew", "FB34",
+ "hehaltonearabic", "06C1",
+ "heharabic", "0647",
+ "hehebrew", "05D4",
+ "hehfinalaltonearabic", "FBA7",
+ "hehfinalalttwoarabic", "FEEA",
+ "hehfinalarabic", "FEEA",
+ "hehhamzaabovefinalarabic", "FBA5",
+ "hehhamzaaboveisolatedarabic", "FBA4",
+ "hehinitialaltonearabic", "FBA8",
+ "hehinitialarabic", "FEEB",
+ "hehiragana", "3078",
+ "hehmedialaltonearabic", "FBA9",
+ "hehmedialarabic", "FEEC",
+ "heiseierasquare", "337B",
+ "hekatakana", "30D8",
+ "hekatakanahalfwidth", "FF8D",
+ "hekutaarusquare", "3336",
+ "henghook", "0267",
+ "herutusquare", "3339",
+ "het", "05D7",
+ "hethebrew", "05D7",
+ "hhook", "0266",
+ "hhooksuperior", "02B1",
+ "hieuhacirclekorean", "327B",
+ "hieuhaparenkorean", "321B",
+ "hieuhcirclekorean", "326D",
+ "hieuhkorean", "314E",
+ "hieuhparenkorean", "320D",
+ "hihiragana", "3072",
+ "hikatakana", "30D2",
+ "hikatakanahalfwidth", "FF8B",
+ "hiriq", "05B4",
+ "hiriq14", "05B4",
+ "hiriq21", "05B4",
+ "hiriq2d", "05B4",
+ "hiriqhebrew", "05B4",
+ "hiriqnarrowhebrew", "05B4",
+ "hiriqquarterhebrew", "05B4",
+ "hiriqwidehebrew", "05B4",
+ "hlinebelow", "1E96",
+ "hmonospace", "FF48",
+ "hoarmenian", "0570",
+ "hohipthai", "0E2B",
+ "hohiragana", "307B",
+ "hokatakana", "30DB",
+ "hokatakanahalfwidth", "FF8E",
+ "holam", "05B9",
+ "holam19", "05B9",
+ "holam26", "05B9",
+ "holam32", "05B9",
+ "holamhebrew", "05B9",
+ "holamnarrowhebrew", "05B9",
+ "holamquarterhebrew", "05B9",
+ "holamwidehebrew", "05B9",
+ "honokhukthai", "0E2E",
+ "hookabovecomb", "0309",
+ "hookcmb", "0309",
+ "hookpalatalizedbelowcmb", "0321",
+ "hookretroflexbelowcmb", "0322",
+ "hoonsquare", "3342",
+ "horicoptic", "03E9",
+ "horizontalbar", "2015",
+ "horncmb", "031B",
+ "hotsprings", "2668",
+ "house", "2302",
+ "hparen", "24A3",
+ "hsuperior", "02B0",
+ "hturned", "0265",
+ "huhiragana", "3075",
+ "huiitosquare", "3333",
+ "hukatakana", "30D5",
+ "hukatakanahalfwidth", "FF8C",
+ "hungarumlaut", "02DD",
+ "hungarumlautcmb", "030B",
+ "hv", "0195",
+ "hyphen", "002D",
+ "hyphenmonospace", "FF0D",
+ "hyphensmall", "FE63",
+ "hyphentwo", "2010",
+ "i", "0069",
+ "iacute", "00ED",
+ "iacyrillic", "044F",
+ "ibengali", "0987",
+ "ibopomofo", "3127",
+ "ibreve", "012D",
+ "icaron", "01D0",
+ "icircle", "24D8",
+ "icircumflex", "00EE",
+ "icyrillic", "0456",
+ "idblgrave", "0209",
+ "ideographearthcircle", "328F",
+ "ideographfirecircle", "328B",
+ "ideographicallianceparen", "323F",
+ "ideographiccallparen", "323A",
+ "ideographiccentrecircle", "32A5",
+ "ideographicclose", "3006",
+ "ideographiccomma", "3001",
+ "ideographiccommaleft", "FF64",
+ "ideographiccongratulationparen", "3237",
+ "ideographiccorrectcircle", "32A3",
+ "ideographicearthparen", "322F",
+ "ideographicenterpriseparen", "323D",
+ "ideographicexcellentcircle", "329D",
+ "ideographicfestivalparen", "3240",
+ "ideographicfinancialcircle", "3296",
+ "ideographicfinancialparen", "3236",
+ "ideographicfireparen", "322B",
+ "ideographichaveparen", "3232",
+ "ideographichighcircle", "32A4",
+ "ideographiciterationmark", "3005",
+ "ideographiclaborcircle", "3298",
+ "ideographiclaborparen", "3238",
+ "ideographicleftcircle", "32A7",
+ "ideographiclowcircle", "32A6",
+ "ideographicmedicinecircle", "32A9",
+ "ideographicmetalparen", "322E",
+ "ideographicmoonparen", "322A",
+ "ideographicnameparen", "3234",
+ "ideographicperiod", "3002",
+ "ideographicprintcircle", "329E",
+ "ideographicreachparen", "3243",
+ "ideographicrepresentparen", "3239",
+ "ideographicresourceparen", "323E",
+ "ideographicrightcircle", "32A8",
+ "ideographicsecretcircle", "3299",
+ "ideographicselfparen", "3242",
+ "ideographicsocietyparen", "3233",
+ "ideographicspace", "3000",
+ "ideographicspecialparen", "3235",
+ "ideographicstockparen", "3231",
+ "ideographicstudyparen", "323B",
+ "ideographicsunparen", "3230",
+ "ideographicsuperviseparen", "323C",
+ "ideographicwaterparen", "322C",
+ "ideographicwoodparen", "322D",
+ "ideographiczero", "3007",
+ "ideographmetalcircle", "328E",
+ "ideographmooncircle", "328A",
+ "ideographnamecircle", "3294",
+ "ideographsuncircle", "3290",
+ "ideographwatercircle", "328C",
+ "ideographwoodcircle", "328D",
+ "ideva", "0907",
+ "idieresis", "00EF",
+ "idieresisacute", "1E2F",
+ "idieresiscyrillic", "04E5",
+ "idotbelow", "1ECB",
+ "iebrevecyrillic", "04D7",
+ "iecyrillic", "0435",
+ "ieungacirclekorean", "3275",
+ "ieungaparenkorean", "3215",
+ "ieungcirclekorean", "3267",
+ "ieungkorean", "3147",
+ "ieungparenkorean", "3207",
+ "igrave", "00EC",
+ "igujarati", "0A87",
+ "igurmukhi", "0A07",
+ "ihiragana", "3044",
+ "ihookabove", "1EC9",
+ "iibengali", "0988",
+ "iicyrillic", "0438",
+ "iideva", "0908",
+ "iigujarati", "0A88",
+ "iigurmukhi", "0A08",
+ "iimatragurmukhi", "0A40",
+ "iinvertedbreve", "020B",
+ "iishortcyrillic", "0439",
+ "iivowelsignbengali", "09C0",
+ "iivowelsigndeva", "0940",
+ "iivowelsigngujarati", "0AC0",
+ "ij", "0133",
+ "ikatakana", "30A4",
+ "ikatakanahalfwidth", "FF72",
+ "ikorean", "3163",
+ "ilde", "02DC",
+ "iluyhebrew", "05AC",
+ "imacron", "012B",
+ "imacroncyrillic", "04E3",
+ "imageorapproximatelyequal", "2253",
+ "imatragurmukhi", "0A3F",
+ "imonospace", "FF49",
+ "increment", "2206",
+ "infinity", "221E",
+ "iniarmenian", "056B",
+ "integral", "222B",
+ "integralbottom", "2321",
+ "integralbt", "2321",
+ "integraltop", "2320",
+ "integraltp", "2320",
+ "intersection", "2229",
+ "intisquare", "3305",
+ "invbullet", "25D8",
+ "invcircle", "25D9",
+ "invsmileface", "263B",
+ "iocyrillic", "0451",
+ "iogonek", "012F",
+ "iota", "03B9",
+ "iotadieresis", "03CA",
+ "iotadieresistonos", "0390",
+ "iotalatin", "0269",
+ "iotatonos", "03AF",
+ "iparen", "24A4",
+ "irigurmukhi", "0A72",
+ "ismallhiragana", "3043",
+ "ismallkatakana", "30A3",
+ "ismallkatakanahalfwidth", "FF68",
+ "issharbengali", "09FA",
+ "istroke", "0268",
+ "iterationhiragana", "309D",
+ "iterationkatakana", "30FD",
+ "itilde", "0129",
+ "itildebelow", "1E2D",
+ "iubopomofo", "3129",
+ "iucyrillic", "044E",
+ "ivowelsignbengali", "09BF",
+ "ivowelsigndeva", "093F",
+ "ivowelsigngujarati", "0ABF",
+ "izhitsacyrillic", "0475",
+ "izhitsadblgravecyrillic", "0477",
+ "j", "006A",
+ "jaarmenian", "0571",
+ "jabengali", "099C",
+ "jadeva", "091C",
+ "jagujarati", "0A9C",
+ "jagurmukhi", "0A1C",
+ "jbopomofo", "3110",
+ "jcaron", "01F0",
+ "jcircle", "24D9",
+ "jcircumflex", "0135",
+ "jcrossedtail", "029D",
+ "jdotlessstroke", "025F",
+ "jecyrillic", "0458",
+ "jeemarabic", "062C",
+ "jeemfinalarabic", "FE9E",
+ "jeeminitialarabic", "FE9F",
+ "jeemmedialarabic", "FEA0",
+ "jeharabic", "0698",
+ "jehfinalarabic", "FB8B",
+ "jhabengali", "099D",
+ "jhadeva", "091D",
+ "jhagujarati", "0A9D",
+ "jhagurmukhi", "0A1D",
+ "jheharmenian", "057B",
+ "jis", "3004",
+ "jmonospace", "FF4A",
+ "jparen", "24A5",
+ "jsuperior", "02B2",
+ "k", "006B",
+ "kabashkircyrillic", "04A1",
+ "kabengali", "0995",
+ "kacute", "1E31",
+ "kacyrillic", "043A",
+ "kadescendercyrillic", "049B",
+ "kadeva", "0915",
+ "kaf", "05DB",
+ "kafarabic", "0643",
+ "kafdagesh", "FB3B",
+ "kafdageshhebrew", "FB3B",
+ "kaffinalarabic", "FEDA",
+ "kafhebrew", "05DB",
+ "kafinitialarabic", "FEDB",
+ "kafmedialarabic", "FEDC",
+ "kafrafehebrew", "FB4D",
+ "kagujarati", "0A95",
+ "kagurmukhi", "0A15",
+ "kahiragana", "304B",
+ "kahookcyrillic", "04C4",
+ "kakatakana", "30AB",
+ "kakatakanahalfwidth", "FF76",
+ "kappa", "03BA",
+ "kappasymbolgreek", "03F0",
+ "kapyeounmieumkorean", "3171",
+ "kapyeounphieuphkorean", "3184",
+ "kapyeounpieupkorean", "3178",
+ "kapyeounssangpieupkorean", "3179",
+ "karoriisquare", "330D",
+ "kashidaautoarabic", "0640",
+ "kashidaautonosidebearingarabic", "0640",
+ "kasmallkatakana", "30F5",
+ "kasquare", "3384",
+ "kasraarabic", "0650",
+ "kasratanarabic", "064D",
+ "kastrokecyrillic", "049F",
+ "katahiraprolongmarkhalfwidth", "FF70",
+ "kaverticalstrokecyrillic", "049D",
+ "kbopomofo", "310E",
+ "kcalsquare", "3389",
+ "kcaron", "01E9",
+ "kcedilla", "0137",
+ "kcircle", "24DA",
+ "kcommaaccent", "0137",
+ "kdotbelow", "1E33",
+ "keharmenian", "0584",
+ "kehiragana", "3051",
+ "kekatakana", "30B1",
+ "kekatakanahalfwidth", "FF79",
+ "kenarmenian", "056F",
+ "kesmallkatakana", "30F6",
+ "kgreenlandic", "0138",
+ "khabengali", "0996",
+ "khacyrillic", "0445",
+ "khadeva", "0916",
+ "khagujarati", "0A96",
+ "khagurmukhi", "0A16",
+ "khaharabic", "062E",
+ "khahfinalarabic", "FEA6",
+ "khahinitialarabic", "FEA7",
+ "khahmedialarabic", "FEA8",
+ "kheicoptic", "03E7",
+ "khhadeva", "0959",
+ "khhagurmukhi", "0A59",
+ "khieukhacirclekorean", "3278",
+ "khieukhaparenkorean", "3218",
+ "khieukhcirclekorean", "326A",
+ "khieukhkorean", "314B",
+ "khieukhparenkorean", "320A",
+ "khokhaithai", "0E02",
+ "khokhonthai", "0E05",
+ "khokhuatthai", "0E03",
+ "khokhwaithai", "0E04",
+ "khomutthai", "0E5B",
+ "khook", "0199",
+ "khorakhangthai", "0E06",
+ "khzsquare", "3391",
+ "kihiragana", "304D",
+ "kikatakana", "30AD",
+ "kikatakanahalfwidth", "FF77",
+ "kiroguramusquare", "3315",
+ "kiromeetorusquare", "3316",
+ "kirosquare", "3314",
+ "kiyeokacirclekorean", "326E",
+ "kiyeokaparenkorean", "320E",
+ "kiyeokcirclekorean", "3260",
+ "kiyeokkorean", "3131",
+ "kiyeokparenkorean", "3200",
+ "kiyeoksioskorean", "3133",
+ "kjecyrillic", "045C",
+ "klinebelow", "1E35",
+ "klsquare", "3398",
+ "kmcubedsquare", "33A6",
+ "kmonospace", "FF4B",
+ "kmsquaredsquare", "33A2",
+ "kohiragana", "3053",
+ "kohmsquare", "33C0",
+ "kokaithai", "0E01",
+ "kokatakana", "30B3",
+ "kokatakanahalfwidth", "FF7A",
+ "kooposquare", "331E",
+ "koppacyrillic", "0481",
+ "koreanstandardsymbol", "327F",
+ "koroniscmb", "0343",
+ "kparen", "24A6",
+ "kpasquare", "33AA",
+ "ksicyrillic", "046F",
+ "ktsquare", "33CF",
+ "kturned", "029E",
+ "kuhiragana", "304F",
+ "kukatakana", "30AF",
+ "kukatakanahalfwidth", "FF78",
+ "kvsquare", "33B8",
+ "kwsquare", "33BE",
+ "l", "006C",
+ "labengali", "09B2",
+ "lacute", "013A",
+ "ladeva", "0932",
+ "lagujarati", "0AB2",
+ "lagurmukhi", "0A32",
+ "lakkhangyaothai", "0E45",
+ "lamaleffinalarabic", "FEFC",
+ "lamalefhamzaabovefinalarabic", "FEF8",
+ "lamalefhamzaaboveisolatedarabic", "FEF7",
+ "lamalefhamzabelowfinalarabic", "FEFA",
+ "lamalefhamzabelowisolatedarabic", "FEF9",
+ "lamalefisolatedarabic", "FEFB",
+ "lamalefmaddaabovefinalarabic", "FEF6",
+ "lamalefmaddaaboveisolatedarabic", "FEF5",
+ "lamarabic", "0644",
+ "lambda", "03BB",
+ "lambdastroke", "019B",
+ "lamed", "05DC",
+ "lameddagesh", "FB3C",
+ "lameddageshhebrew", "FB3C",
+ "lamedhebrew", "05DC",
+ "lamedholam", "05DC_05B9",
+ "lamedholamdagesh", "05DC_05B9_05BC",
+ "lamedholamdageshhebrew", "05DC_05B9_05BC",
+ "lamedholamhebrew", "05DC_05B9",
+ "lamfinalarabic", "FEDE",
+ "lamhahinitialarabic", "FCCA",
+ "laminitialarabic", "FEDF",
+ "lamjeeminitialarabic", "FCC9",
+ "lamkhahinitialarabic", "FCCB",
+ "lamlamhehisolatedarabic", "FDF2",
+ "lammedialarabic", "FEE0",
+ "lammeemhahinitialarabic", "FD88",
+ "lammeeminitialarabic", "FCCC",
+ "lammeemjeeminitialarabic", "FEDF_FEE4_FEA0",
+ "lammeemkhahinitialarabic", "FEDF_FEE4_FEA8",
+ "largecircle", "25EF",
+ "lbar", "019A",
+ "lbelt", "026C",
+ "lbopomofo", "310C",
+ "lcaron", "013E",
+ "lcedilla", "013C",
+ "lcircle", "24DB",
+ "lcircumflexbelow", "1E3D",
+ "lcommaaccent", "013C",
+ "ldot", "0140",
+ "ldotaccent", "0140",
+ "ldotbelow", "1E37",
+ "ldotbelowmacron", "1E39",
+ "leftangleabovecmb", "031A",
+ "lefttackbelowcmb", "0318",
+ "less", "003C",
+ "lessequal", "2264",
+ "lessequalorgreater", "22DA",
+ "lessmonospace", "FF1C",
+ "lessorequivalent", "2272",
+ "lessorgreater", "2276",
+ "lessoverequal", "2266",
+ "lesssmall", "FE64",
+ "lezh", "026E",
+ "lfblock", "258C",
+ "lhookretroflex", "026D",
+ "lira", "20A4",
+ "liwnarmenian", "056C",
+ "lj", "01C9",
+ "ljecyrillic", "0459",
+ "lladeva", "0933",
+ "llagujarati", "0AB3",
+ "llinebelow", "1E3B",
+ "llladeva", "0934",
+ "llvocalicbengali", "09E1",
+ "llvocalicdeva", "0961",
+ "llvocalicvowelsignbengali", "09E3",
+ "llvocalicvowelsigndeva", "0963",
+ "lmiddletilde", "026B",
+ "lmonospace", "FF4C",
+ "lmsquare", "33D0",
+ "lochulathai", "0E2C",
+ "logicaland", "2227",
+ "logicalnot", "00AC",
+ "logicalnotreversed", "2310",
+ "logicalor", "2228",
+ "lolingthai", "0E25",
+ "longs", "017F",
+ "lowlinecenterline", "FE4E",
+ "lowlinecmb", "0332",
+ "lowlinedashed", "FE4D",
+ "lozenge", "25CA",
+ "lparen", "24A7",
+ "lslash", "0142",
+ "lsquare", "2113",
+ "ltshade", "2591",
+ "luthai", "0E26",
+ "lvocalicbengali", "098C",
+ "lvocalicdeva", "090C",
+ "lvocalicvowelsignbengali", "09E2",
+ "lvocalicvowelsigndeva", "0962",
+ "lxsquare", "33D3",
+ "m", "006D",
+ "mabengali", "09AE",
+ "macron", "00AF",
+ "macronbelowcmb", "0331",
+ "macroncmb", "0304",
+ "macronlowmod", "02CD",
+ "macronmonospace", "FFE3",
+ "macute", "1E3F",
+ "madeva", "092E",
+ "magujarati", "0AAE",
+ "magurmukhi", "0A2E",
+ "mahapakhhebrew", "05A4",
+ "mahapakhlefthebrew", "05A4",
+ "mahiragana", "307E",
+ "maichattawathai", "0E4B",
+ "maiekthai", "0E48",
+ "maihanakatthai", "0E31",
+ "maitaikhuthai", "0E47",
+ "maithothai", "0E49",
+ "maitrithai", "0E4A",
+ "maiyamokthai", "0E46",
+ "makatakana", "30DE",
+ "makatakanahalfwidth", "FF8F",
+ "male", "2642",
+ "mansyonsquare", "3347",
+ "maqafhebrew", "05BE",
+ "mars", "2642",
+ "masoracirclehebrew", "05AF",
+ "masquare", "3383",
+ "mbopomofo", "3107",
+ "mbsquare", "33D4",
+ "mcircle", "24DC",
+ "mcubedsquare", "33A5",
+ "mdotaccent", "1E41",
+ "mdotbelow", "1E43",
+ "meemarabic", "0645",
+ "meemfinalarabic", "FEE2",
+ "meeminitialarabic", "FEE3",
+ "meemmedialarabic", "FEE4",
+ "meemmeeminitialarabic", "FCD1",
+ "meemmeemisolatedarabic", "FC48",
+ "meetorusquare", "334D",
+ "mehiragana", "3081",
+ "meizierasquare", "337E",
+ "mekatakana", "30E1",
+ "mekatakanahalfwidth", "FF92",
+ "mem", "05DE",
+ "memdagesh", "FB3E",
+ "memdageshhebrew", "FB3E",
+ "memhebrew", "05DE",
+ "menarmenian", "0574",
+ "merkhahebrew", "05A5",
+ "merkhakefulahebrew", "05A6",
+ "merkhakefulalefthebrew", "05A6",
+ "merkhalefthebrew", "05A5",
+ "mhook", "0271",
+ "mhzsquare", "3392",
+ "middledotkatakanahalfwidth", "FF65",
+ "middot", "00B7",
+ "mieumacirclekorean", "3272",
+ "mieumaparenkorean", "3212",
+ "mieumcirclekorean", "3264",
+ "mieumkorean", "3141",
+ "mieumpansioskorean", "3170",
+ "mieumparenkorean", "3204",
+ "mieumpieupkorean", "316E",
+ "mieumsioskorean", "316F",
+ "mihiragana", "307F",
+ "mikatakana", "30DF",
+ "mikatakanahalfwidth", "FF90",
+ "minus", "2212",
+ "minusbelowcmb", "0320",
+ "minuscircle", "2296",
+ "minusmod", "02D7",
+ "minusplus", "2213",
+ "minute", "2032",
+ "miribaarusquare", "334A",
+ "mirisquare", "3349",
+ "mlonglegturned", "0270",
+ "mlsquare", "3396",
+ "mmcubedsquare", "33A3",
+ "mmonospace", "FF4D",
+ "mmsquaredsquare", "339F",
+ "mohiragana", "3082",
+ "mohmsquare", "33C1",
+ "mokatakana", "30E2",
+ "mokatakanahalfwidth", "FF93",
+ "molsquare", "33D6",
+ "momathai", "0E21",
+ "moverssquare", "33A7",
+ "moverssquaredsquare", "33A8",
+ "mparen", "24A8",
+ "mpasquare", "33AB",
+ "mssquare", "33B3",
+ "mturned", "026F",
+ "mu", "03BC", # groff: not U+00B5
+ "mu1", "00B5",
+ "muasquare", "3382",
+ "muchgreater", "226B",
+ "muchless", "226A",
+ "mufsquare", "338C",
+ "mugreek", "03BC",
+ "mugsquare", "338D",
+ "muhiragana", "3080",
+ "mukatakana", "30E0",
+ "mukatakanahalfwidth", "FF91",
+ "mulsquare", "3395",
+ "multiply", "00D7",
+ "mumsquare", "339B",
+ "munahhebrew", "05A3",
+ "munahlefthebrew", "05A3",
+ "musicalnote", "266A",
+ "musicalnotedbl", "266B",
+ "musicflatsign", "266D",
+ "musicsharpsign", "266F",
+ "mussquare", "33B2",
+ "muvsquare", "33B6",
+ "muwsquare", "33BC",
+ "mvmegasquare", "33B9",
+ "mvsquare", "33B7",
+ "mwmegasquare", "33BF",
+ "mwsquare", "33BD",
+ "n", "006E",
+ "nabengali", "09A8",
+ "nabla", "2207",
+ "nacute", "0144",
+ "nadeva", "0928",
+ "nagujarati", "0AA8",
+ "nagurmukhi", "0A28",
+ "nahiragana", "306A",
+ "nakatakana", "30CA",
+ "nakatakanahalfwidth", "FF85",
+ "napostrophe", "0149",
+ "nasquare", "3381",
+ "nbopomofo", "310B",
+ "nbspace", "00A0",
+ "ncaron", "0148",
+ "ncedilla", "0146",
+ "ncircle", "24DD",
+ "ncircumflexbelow", "1E4B",
+ "ncommaaccent", "0146",
+ "ndotaccent", "1E45",
+ "ndotbelow", "1E47",
+ "nehiragana", "306D",
+ "nekatakana", "30CD",
+ "nekatakanahalfwidth", "FF88",
+ "newsheqelsign", "20AA",
+ "nfsquare", "338B",
+ "ngabengali", "0999",
+ "ngadeva", "0919",
+ "ngagujarati", "0A99",
+ "ngagurmukhi", "0A19",
+ "ngonguthai", "0E07",
+ "nhiragana", "3093",
+ "nhookleft", "0272",
+ "nhookretroflex", "0273",
+ "nieunacirclekorean", "326F",
+ "nieunaparenkorean", "320F",
+ "nieuncieuckorean", "3135",
+ "nieuncirclekorean", "3261",
+ "nieunhieuhkorean", "3136",
+ "nieunkorean", "3134",
+ "nieunpansioskorean", "3168",
+ "nieunparenkorean", "3201",
+ "nieunsioskorean", "3167",
+ "nieuntikeutkorean", "3166",
+ "nihiragana", "306B",
+ "nikatakana", "30CB",
+ "nikatakanahalfwidth", "FF86",
+ "nikhahitthai", "0E4D",
+ "nine", "0039",
+ "ninearabic", "0669",
+ "ninebengali", "09EF",
+ "ninecircle", "2468",
+ "ninecircleinversesansserif", "2792",
+ "ninedeva", "096F",
+ "ninegujarati", "0AEF",
+ "ninegurmukhi", "0A6F",
+ "ninehackarabic", "0669",
+ "ninehangzhou", "3029",
+ "nineideographicparen", "3228",
+ "nineinferior", "2089",
+ "ninemonospace", "FF19",
+ "nineparen", "247C",
+ "nineperiod", "2490",
+ "ninepersian", "06F9",
+ "nineroman", "2178",
+ "ninesuperior", "2079",
+ "nineteencircle", "2472",
+ "nineteenparen", "2486",
+ "nineteenperiod", "249A",
+ "ninethai", "0E59",
+ "nj", "01CC",
+ "njecyrillic", "045A",
+ "nkatakana", "30F3",
+ "nkatakanahalfwidth", "FF9D",
+ "nlegrightlong", "019E",
+ "nlinebelow", "1E49",
+ "nmonospace", "FF4E",
+ "nmsquare", "339A",
+ "nnabengali", "09A3",
+ "nnadeva", "0923",
+ "nnagujarati", "0AA3",
+ "nnagurmukhi", "0A23",
+ "nnnadeva", "0929",
+ "nohiragana", "306E",
+ "nokatakana", "30CE",
+ "nokatakanahalfwidth", "FF89",
+ "nonbreakingspace", "00A0",
+ "nonenthai", "0E13",
+ "nonuthai", "0E19",
+ "noonarabic", "0646",
+ "noonfinalarabic", "FEE6",
+ "noonghunnaarabic", "06BA",
+ "noonghunnafinalarabic", "FB9F",
+ "noonhehinitialarabic", "FEE7_FEEC",
+ "nooninitialarabic", "FEE7",
+ "noonjeeminitialarabic", "FCD2",
+ "noonjeemisolatedarabic", "FC4B",
+ "noonmedialarabic", "FEE8",
+ "noonmeeminitialarabic", "FCD5",
+ "noonmeemisolatedarabic", "FC4E",
+ "noonnoonfinalarabic", "FC8D",
+ "notcontains", "220C",
+ "notelement", "2209",
+ "notelementof", "2209",
+ "notequal", "2260",
+ "notgreater", "226F",
+ "notgreaternorequal", "2271",
+ "notgreaternorless", "2279",
+ "notidentical", "2262",
+ "notless", "226E",
+ "notlessnorequal", "2270",
+ "notparallel", "2226",
+ "notprecedes", "2280",
+ "notsubset", "2284",
+ "notsucceeds", "2281",
+ "notsuperset", "2285",
+ "nowarmenian", "0576",
+ "nparen", "24A9",
+ "nssquare", "33B1",
+ "nsuperior", "207F",
+ "ntilde", "00F1",
+ "nu", "03BD",
+ "nuhiragana", "306C",
+ "nukatakana", "30CC",
+ "nukatakanahalfwidth", "FF87",
+ "nuktabengali", "09BC",
+ "nuktadeva", "093C",
+ "nuktagujarati", "0ABC",
+ "nuktagurmukhi", "0A3C",
+ "numbersign", "0023",
+ "numbersignmonospace", "FF03",
+ "numbersignsmall", "FE5F",
+ "numeralsigngreek", "0374",
+ "numeralsignlowergreek", "0375",
+ "numero", "2116",
+ "nun", "05E0",
+ "nundagesh", "FB40",
+ "nundageshhebrew", "FB40",
+ "nunhebrew", "05E0",
+ "nvsquare", "33B5",
+ "nwsquare", "33BB",
+ "nyabengali", "099E",
+ "nyadeva", "091E",
+ "nyagujarati", "0A9E",
+ "nyagurmukhi", "0A1E",
+ "o", "006F",
+ "oacute", "00F3",
+ "oangthai", "0E2D",
+ "obarred", "0275",
+ "obarredcyrillic", "04E9",
+ "obarreddieresiscyrillic", "04EB",
+ "obengali", "0993",
+ "obopomofo", "311B",
+ "obreve", "014F",
+ "ocandradeva", "0911",
+ "ocandragujarati", "0A91",
+ "ocandravowelsigndeva", "0949",
+ "ocandravowelsigngujarati", "0AC9",
+ "ocaron", "01D2",
+ "ocircle", "24DE",
+ "ocircumflex", "00F4",
+ "ocircumflexacute", "1ED1",
+ "ocircumflexdotbelow", "1ED9",
+ "ocircumflexgrave", "1ED3",
+ "ocircumflexhookabove", "1ED5",
+ "ocircumflextilde", "1ED7",
+ "ocyrillic", "043E",
+ "odblacute", "0151",
+ "odblgrave", "020D",
+ "odeva", "0913",
+ "odieresis", "00F6",
+ "odieresiscyrillic", "04E7",
+ "odotbelow", "1ECD",
+ "oe", "0153",
+ "oekorean", "315A",
+ "ogonek", "02DB",
+ "ogonekcmb", "0328",
+ "ograve", "00F2",
+ "ogujarati", "0A93",
+ "oharmenian", "0585",
+ "ohiragana", "304A",
+ "ohookabove", "1ECF",
+ "ohorn", "01A1",
+ "ohornacute", "1EDB",
+ "ohorndotbelow", "1EE3",
+ "ohorngrave", "1EDD",
+ "ohornhookabove", "1EDF",
+ "ohorntilde", "1EE1",
+ "ohungarumlaut", "0151",
+ "oi", "01A3",
+ "oinvertedbreve", "020F",
+ "okatakana", "30AA",
+ "okatakanahalfwidth", "FF75",
+ "okorean", "3157",
+ "olehebrew", "05AB",
+ "omacron", "014D",
+ "omacronacute", "1E53",
+ "omacrongrave", "1E51",
+ "omdeva", "0950",
+ "omega", "03C9",
+ "omega1", "03D6",
+ "omegacyrillic", "0461",
+ "omegalatinclosed", "0277",
+ "omegaroundcyrillic", "047B",
+ "omegatitlocyrillic", "047D",
+ "omegatonos", "03CE",
+ "omgujarati", "0AD0",
+ "omicron", "03BF",
+ "omicrontonos", "03CC",
+ "omonospace", "FF4F",
+ "one", "0031",
+ "onearabic", "0661",
+ "onebengali", "09E7",
+ "onecircle", "2460",
+ "onecircleinversesansserif", "278A",
+ "onedeva", "0967",
+ "onedotenleader", "2024",
+ "oneeighth", "215B",
+ "onegujarati", "0AE7",
+ "onegurmukhi", "0A67",
+ "onehackarabic", "0661",
+ "onehalf", "00BD",
+ "onehangzhou", "3021",
+ "oneideographicparen", "3220",
+ "oneinferior", "2081",
+ "onemonospace", "FF11",
+ "onenumeratorbengali", "09F4",
+ "oneparen", "2474",
+ "oneperiod", "2488",
+ "onepersian", "06F1",
+ "onequarter", "00BC",
+ "oneroman", "2170",
+ "onesuperior", "00B9",
+ "onethai", "0E51",
+ "onethird", "2153",
+ "oogonek", "01EB",
+ "oogonekmacron", "01ED",
+ "oogurmukhi", "0A13",
+ "oomatragurmukhi", "0A4B",
+ "oopen", "0254",
+ "oparen", "24AA",
+ "openbullet", "25E6",
+ "option", "2325",
+ "ordfeminine", "00AA",
+ "ordmasculine", "00BA",
+ "orthogonal", "221F",
+ "oshortdeva", "0912",
+ "oshortvowelsigndeva", "094A",
+ "oslash", "00F8",
+ "oslashacute", "01FF",
+ "osmallhiragana", "3049",
+ "osmallkatakana", "30A9",
+ "osmallkatakanahalfwidth", "FF6B",
+ "ostrokeacute", "01FF",
+ "otcyrillic", "047F",
+ "otilde", "00F5",
+ "otildeacute", "1E4D",
+ "otildedieresis", "1E4F",
+ "oubopomofo", "3121",
+ "overline", "203E",
+ "overlinecenterline", "FE4A",
+ "overlinecmb", "0305",
+ "overlinedashed", "FE49",
+ "overlinedblwavy", "FE4C",
+ "overlinewavy", "FE4B",
+ "overscore", "00AF",
+ "ovowelsignbengali", "09CB",
+ "ovowelsigndeva", "094B",
+ "ovowelsigngujarati", "0ACB",
+ "p", "0070",
+ "paampssquare", "3380",
+ "paasentosquare", "332B",
+ "pabengali", "09AA",
+ "pacute", "1E55",
+ "padeva", "092A",
+ "pagedown", "21DF",
+ "pageup", "21DE",
+ "pagujarati", "0AAA",
+ "pagurmukhi", "0A2A",
+ "pahiragana", "3071",
+ "paiyannoithai", "0E2F",
+ "pakatakana", "30D1",
+ "palatalizationcyrilliccmb", "0484",
+ "palochkacyrillic", "04C0",
+ "pansioskorean", "317F",
+ "paragraph", "00B6",
+ "parallel", "2225",
+ "parenleft", "0028",
+ "parenleftaltonearabic", "FD3E",
+ "parenleftinferior", "208D",
+ "parenleftmonospace", "FF08",
+ "parenleftsmall", "FE59",
+ "parenleftsuperior", "207D",
+ "parenleftvertical", "FE35",
+ "parenright", "0029",
+ "parenrightaltonearabic", "FD3F",
+ "parenrightinferior", "208E",
+ "parenrightmonospace", "FF09",
+ "parenrightsmall", "FE5A",
+ "parenrightsuperior", "207E",
+ "parenrightvertical", "FE36",
+ "partialdiff", "2202",
+ "paseqhebrew", "05C0",
+ "pashtahebrew", "0599",
+ "pasquare", "33A9",
+ "patah", "05B7",
+ "patah11", "05B7",
+ "patah1d", "05B7",
+ "patah2a", "05B7",
+ "patahhebrew", "05B7",
+ "patahnarrowhebrew", "05B7",
+ "patahquarterhebrew", "05B7",
+ "patahwidehebrew", "05B7",
+ "pazerhebrew", "05A1",
+ "pbopomofo", "3106",
+ "pcircle", "24DF",
+ "pdotaccent", "1E57",
+ "pe", "05E4",
+ "pecyrillic", "043F",
+ "pedagesh", "FB44",
+ "pedageshhebrew", "FB44",
+ "peezisquare", "333B",
+ "pefinaldageshhebrew", "FB43",
+ "peharabic", "067E",
+ "peharmenian", "057A",
+ "pehebrew", "05E4",
+ "pehfinalarabic", "FB57",
+ "pehinitialarabic", "FB58",
+ "pehiragana", "307A",
+ "pehmedialarabic", "FB59",
+ "pekatakana", "30DA",
+ "pemiddlehookcyrillic", "04A7",
+ "perafehebrew", "FB4E",
+ "percent", "0025",
+ "percentarabic", "066A",
+ "percentmonospace", "FF05",
+ "percentsmall", "FE6A",
+ "period", "002E",
+ "periodarmenian", "0589",
+ "periodcentered", "00B7",
+ "periodhalfwidth", "FF61",
+ "periodmonospace", "FF0E",
+ "periodsmall", "FE52",
+ "perispomenigreekcmb", "0342",
+ "perpendicular", "22A5",
+ "perthousand", "2030",
+ "peseta", "20A7",
+ "pfsquare", "338A",
+ "phabengali", "09AB",
+ "phadeva", "092B",
+ "phagujarati", "0AAB",
+ "phagurmukhi", "0A2B",
+ "phi", "03C6",
+ "phi1", "03D5",
+ "phieuphacirclekorean", "327A",
+ "phieuphaparenkorean", "321A",
+ "phieuphcirclekorean", "326C",
+ "phieuphkorean", "314D",
+ "phieuphparenkorean", "320C",
+ "philatin", "0278",
+ "phinthuthai", "0E3A",
+ "phisymbolgreek", "03D5",
+ "phook", "01A5",
+ "phophanthai", "0E1E",
+ "phophungthai", "0E1C",
+ "phosamphaothai", "0E20",
+ "pi", "03C0",
+ "pieupacirclekorean", "3273",
+ "pieupaparenkorean", "3213",
+ "pieupcieuckorean", "3176",
+ "pieupcirclekorean", "3265",
+ "pieupkiyeokkorean", "3172",
+ "pieupkorean", "3142",
+ "pieupparenkorean", "3205",
+ "pieupsioskiyeokkorean", "3174",
+ "pieupsioskorean", "3144",
+ "pieupsiostikeutkorean", "3175",
+ "pieupthieuthkorean", "3177",
+ "pieuptikeutkorean", "3173",
+ "pihiragana", "3074",
+ "pikatakana", "30D4",
+ "pisymbolgreek", "03D6",
+ "piwrarmenian", "0583",
+ "plus", "002B",
+ "plusbelowcmb", "031F",
+ "pluscircle", "2295",
+ "plusminus", "00B1",
+ "plusmod", "02D6",
+ "plusmonospace", "FF0B",
+ "plussmall", "FE62",
+ "plussuperior", "207A",
+ "pmonospace", "FF50",
+ "pmsquare", "33D8",
+ "pohiragana", "307D",
+ "pointingindexdownwhite", "261F",
+ "pointingindexleftwhite", "261C",
+ "pointingindexrightwhite", "261E",
+ "pointingindexupwhite", "261D",
+ "pokatakana", "30DD",
+ "poplathai", "0E1B",
+ "postalmark", "3012",
+ "postalmarkface", "3020",
+ "pparen", "24AB",
+ "precedes", "227A",
+ "prescription", "211E",
+ "primemod", "02B9",
+ "primereversed", "2035",
+ "product", "220F",
+ "projective", "2305",
+ "prolongedkana", "30FC",
+ "propellor", "2318",
+ "propersubset", "2282",
+ "propersuperset", "2283",
+ "proportion", "2237",
+ "proportional", "221D",
+ "psi", "03C8",
+ "psicyrillic", "0471",
+ "psilipneumatacyrilliccmb", "0486",
+ "pssquare", "33B0",
+ "puhiragana", "3077",
+ "pukatakana", "30D7",
+ "pvsquare", "33B4",
+ "pwsquare", "33BA",
+ "q", "0071",
+ "qadeva", "0958",
+ "qadmahebrew", "05A8",
+ "qafarabic", "0642",
+ "qaffinalarabic", "FED6",
+ "qafinitialarabic", "FED7",
+ "qafmedialarabic", "FED8",
+ "qamats", "05B8",
+ "qamats10", "05B8",
+ "qamats1a", "05B8",
+ "qamats1c", "05B8",
+ "qamats27", "05B8",
+ "qamats29", "05B8",
+ "qamats33", "05B8",
+ "qamatsde", "05B8",
+ "qamatshebrew", "05B8",
+ "qamatsnarrowhebrew", "05B8",
+ "qamatsqatanhebrew", "05B8",
+ "qamatsqatannarrowhebrew", "05B8",
+ "qamatsqatanquarterhebrew", "05B8",
+ "qamatsqatanwidehebrew", "05B8",
+ "qamatsquarterhebrew", "05B8",
+ "qamatswidehebrew", "05B8",
+ "qarneyparahebrew", "059F",
+ "qbopomofo", "3111",
+ "qcircle", "24E0",
+ "qhook", "02A0",
+ "qmonospace", "FF51",
+ "qof", "05E7",
+ "qofdagesh", "FB47",
+ "qofdageshhebrew", "FB47",
+ "qofhatafpatah", "05E7_05B2",
+ "qofhatafpatahhebrew", "05E7_05B2",
+ "qofhatafsegol", "05E7_05B1",
+ "qofhatafsegolhebrew", "05E7_05B1",
+ "qofhebrew", "05E7",
+ "qofhiriq", "05E7_05B4",
+ "qofhiriqhebrew", "05E7_05B4",
+ "qofholam", "05E7_05B9",
+ "qofholamhebrew", "05E7_05B9",
+ "qofpatah", "05E7_05B7",
+ "qofpatahhebrew", "05E7_05B7",
+ "qofqamats", "05E7_05B8",
+ "qofqamatshebrew", "05E7_05B8",
+ "qofqubuts", "05E7_05BB",
+ "qofqubutshebrew", "05E7_05BB",
+ "qofsegol", "05E7_05B6",
+ "qofsegolhebrew", "05E7_05B6",
+ "qofsheva", "05E7_05B0",
+ "qofshevahebrew", "05E7_05B0",
+ "qoftsere", "05E7_05B5",
+ "qoftserehebrew", "05E7_05B5",
+ "qparen", "24AC",
+ "quarternote", "2669",
+ "qubuts", "05BB",
+ "qubuts18", "05BB",
+ "qubuts25", "05BB",
+ "qubuts31", "05BB",
+ "qubutshebrew", "05BB",
+ "qubutsnarrowhebrew", "05BB",
+ "qubutsquarterhebrew", "05BB",
+ "qubutswidehebrew", "05BB",
+ "question", "003F",
+ "questionarabic", "061F",
+ "questionarmenian", "055E",
+ "questiondown", "00BF",
+ "questiongreek", "037E",
+ "questionmonospace", "FF1F",
+ "quotedbl", "0022",
+ "quotedblbase", "201E",
+ "quotedblleft", "201C",
+ "quotedblmonospace", "FF02",
+ "quotedblprime", "301E",
+ "quotedblprimereversed", "301D",
+ "quotedblright", "201D",
+ "quoteleft", "2018",
+ "quoteleftreversed", "201B",
+ "quotereversed", "201B",
+ "quoteright", "2019",
+ "quoterightn", "0149",
+ "quotesinglbase", "201A",
+ "quotesingle", "0027",
+ "quotesinglemonospace", "FF07",
+ "r", "0072",
+ "raarmenian", "057C",
+ "rabengali", "09B0",
+ "racute", "0155",
+ "radeva", "0930",
+ "radical", "221A",
+ "radoverssquare", "33AE",
+ "radoverssquaredsquare", "33AF",
+ "radsquare", "33AD",
+ "rafe", "05BF",
+ "rafehebrew", "05BF",
+ "ragujarati", "0AB0",
+ "ragurmukhi", "0A30",
+ "rahiragana", "3089",
+ "rakatakana", "30E9",
+ "rakatakanahalfwidth", "FF97",
+ "ralowerdiagonalbengali", "09F1",
+ "ramiddlediagonalbengali", "09F0",
+ "ramshorn", "0264",
+ "ratio", "2236",
+ "rbopomofo", "3116",
+ "rcaron", "0159",
+ "rcedilla", "0157",
+ "rcircle", "24E1",
+ "rcommaaccent", "0157",
+ "rdblgrave", "0211",
+ "rdotaccent", "1E59",
+ "rdotbelow", "1E5B",
+ "rdotbelowmacron", "1E5D",
+ "referencemark", "203B",
+ "reflexsubset", "2286",
+ "reflexsuperset", "2287",
+ "registered", "00AE",
+ "reharabic", "0631",
+ "reharmenian", "0580",
+ "rehfinalarabic", "FEAE",
+ "rehiragana", "308C",
+ "rehyehaleflamarabic", "0631_FEF3_FE8E_0644",
+ "rekatakana", "30EC",
+ "rekatakanahalfwidth", "FF9A",
+ "resh", "05E8",
+ "reshdageshhebrew", "FB48",
+ "reshhatafpatah", "05E8_05B2",
+ "reshhatafpatahhebrew", "05E8_05B2",
+ "reshhatafsegol", "05E8_05B1",
+ "reshhatafsegolhebrew", "05E8_05B1",
+ "reshhebrew", "05E8",
+ "reshhiriq", "05E8_05B4",
+ "reshhiriqhebrew", "05E8_05B4",
+ "reshholam", "05E8_05B9",
+ "reshholamhebrew", "05E8_05B9",
+ "reshpatah", "05E8_05B7",
+ "reshpatahhebrew", "05E8_05B7",
+ "reshqamats", "05E8_05B8",
+ "reshqamatshebrew", "05E8_05B8",
+ "reshqubuts", "05E8_05BB",
+ "reshqubutshebrew", "05E8_05BB",
+ "reshsegol", "05E8_05B6",
+ "reshsegolhebrew", "05E8_05B6",
+ "reshsheva", "05E8_05B0",
+ "reshshevahebrew", "05E8_05B0",
+ "reshtsere", "05E8_05B5",
+ "reshtserehebrew", "05E8_05B5",
+ "reversedtilde", "223D",
+ "reviahebrew", "0597",
+ "reviamugrashhebrew", "0597",
+ "revlogicalnot", "2310",
+ "rfishhook", "027E",
+ "rfishhookreversed", "027F",
+ "rhabengali", "09DD",
+ "rhadeva", "095D",
+ "rho", "03C1",
+ "rhook", "027D",
+ "rhookturned", "027B",
+ "rhookturnedsuperior", "02B5",
+ "rhosymbolgreek", "03F1",
+ "rhotichookmod", "02DE",
+ "rieulacirclekorean", "3271",
+ "rieulaparenkorean", "3211",
+ "rieulcirclekorean", "3263",
+ "rieulhieuhkorean", "3140",
+ "rieulkiyeokkorean", "313A",
+ "rieulkiyeoksioskorean", "3169",
+ "rieulkorean", "3139",
+ "rieulmieumkorean", "313B",
+ "rieulpansioskorean", "316C",
+ "rieulparenkorean", "3203",
+ "rieulphieuphkorean", "313F",
+ "rieulpieupkorean", "313C",
+ "rieulpieupsioskorean", "316B",
+ "rieulsioskorean", "313D",
+ "rieulthieuthkorean", "313E",
+ "rieultikeutkorean", "316A",
+ "rieulyeorinhieuhkorean", "316D",
+ "rightangle", "221F",
+ "righttackbelowcmb", "0319",
+ "righttriangle", "22BF",
+ "rihiragana", "308A",
+ "rikatakana", "30EA",
+ "rikatakanahalfwidth", "FF98",
+ "ring", "02DA",
+ "ringbelowcmb", "0325",
+ "ringcmb", "030A",
+ "ringhalfleft", "02BF",
+ "ringhalfleftarmenian", "0559",
+ "ringhalfleftbelowcmb", "031C",
+ "ringhalfleftcentered", "02D3",
+ "ringhalfright", "02BE",
+ "ringhalfrightbelowcmb", "0339",
+ "ringhalfrightcentered", "02D2",
+ "rinvertedbreve", "0213",
+ "rittorusquare", "3351",
+ "rlinebelow", "1E5F",
+ "rlongleg", "027C",
+ "rlonglegturned", "027A",
+ "rmonospace", "FF52",
+ "rohiragana", "308D",
+ "rokatakana", "30ED",
+ "rokatakanahalfwidth", "FF9B",
+ "roruathai", "0E23",
+ "rparen", "24AD",
+ "rrabengali", "09DC",
+ "rradeva", "0931",
+ "rragurmukhi", "0A5C",
+ "rreharabic", "0691",
+ "rrehfinalarabic", "FB8D",
+ "rrvocalicbengali", "09E0",
+ "rrvocalicdeva", "0960",
+ "rrvocalicgujarati", "0AE0",
+ "rrvocalicvowelsignbengali", "09C4",
+ "rrvocalicvowelsigndeva", "0944",
+ "rrvocalicvowelsigngujarati", "0AC4",
+ "rtblock", "2590",
+ "rturned", "0279",
+ "rturnedsuperior", "02B4",
+ "ruhiragana", "308B",
+ "rukatakana", "30EB",
+ "rukatakanahalfwidth", "FF99",
+ "rupeemarkbengali", "09F2",
+ "rupeesignbengali", "09F3",
+ "ruthai", "0E24",
+ "rvocalicbengali", "098B",
+ "rvocalicdeva", "090B",
+ "rvocalicgujarati", "0A8B",
+ "rvocalicvowelsignbengali", "09C3",
+ "rvocalicvowelsigndeva", "0943",
+ "rvocalicvowelsigngujarati", "0AC3",
+ "s", "0073",
+ "sabengali", "09B8",
+ "sacute", "015B",
+ "sacutedotaccent", "1E65",
+ "sadarabic", "0635",
+ "sadeva", "0938",
+ "sadfinalarabic", "FEBA",
+ "sadinitialarabic", "FEBB",
+ "sadmedialarabic", "FEBC",
+ "sagujarati", "0AB8",
+ "sagurmukhi", "0A38",
+ "sahiragana", "3055",
+ "sakatakana", "30B5",
+ "sakatakanahalfwidth", "FF7B",
+ "sallallahoualayhewasallamarabic", "FDFA",
+ "samekh", "05E1",
+ "samekhdagesh", "FB41",
+ "samekhdageshhebrew", "FB41",
+ "samekhhebrew", "05E1",
+ "saraaathai", "0E32",
+ "saraaethai", "0E41",
+ "saraaimaimalaithai", "0E44",
+ "saraaimaimuanthai", "0E43",
+ "saraamthai", "0E33",
+ "saraathai", "0E30",
+ "saraethai", "0E40",
+ "saraiithai", "0E35",
+ "saraithai", "0E34",
+ "saraothai", "0E42",
+ "saraueethai", "0E37",
+ "sarauethai", "0E36",
+ "sarauthai", "0E38",
+ "sarauuthai", "0E39",
+ "sbopomofo", "3119",
+ "scaron", "0161",
+ "scarondotaccent", "1E67",
+ "scedilla", "015F",
+ "schwa", "0259",
+ "schwacyrillic", "04D9",
+ "schwadieresiscyrillic", "04DB",
+ "schwahook", "025A",
+ "scircle", "24E2",
+ "scircumflex", "015D",
+ "scommaaccent", "0219",
+ "sdotaccent", "1E61",
+ "sdotbelow", "1E63",
+ "sdotbelowdotaccent", "1E69",
+ "seagullbelowcmb", "033C",
+ "second", "2033",
+ "secondtonechinese", "02CA",
+ "section", "00A7",
+ "seenarabic", "0633",
+ "seenfinalarabic", "FEB2",
+ "seeninitialarabic", "FEB3",
+ "seenmedialarabic", "FEB4",
+ "segol", "05B6",
+ "segol13", "05B6",
+ "segol1f", "05B6",
+ "segol2c", "05B6",
+ "segolhebrew", "05B6",
+ "segolnarrowhebrew", "05B6",
+ "segolquarterhebrew", "05B6",
+ "segoltahebrew", "0592",
+ "segolwidehebrew", "05B6",
+ "seharmenian", "057D",
+ "sehiragana", "305B",
+ "sekatakana", "30BB",
+ "sekatakanahalfwidth", "FF7E",
+ "semicolon", "003B",
+ "semicolonarabic", "061B",
+ "semicolonmonospace", "FF1B",
+ "semicolonsmall", "FE54",
+ "semivoicedmarkkana", "309C",
+ "semivoicedmarkkanahalfwidth", "FF9F",
+ "sentisquare", "3322",
+ "sentosquare", "3323",
+ "seven", "0037",
+ "sevenarabic", "0667",
+ "sevenbengali", "09ED",
+ "sevencircle", "2466",
+ "sevencircleinversesansserif", "2790",
+ "sevendeva", "096D",
+ "seveneighths", "215E",
+ "sevengujarati", "0AED",
+ "sevengurmukhi", "0A6D",
+ "sevenhackarabic", "0667",
+ "sevenhangzhou", "3027",
+ "sevenideographicparen", "3226",
+ "seveninferior", "2087",
+ "sevenmonospace", "FF17",
+ "sevenparen", "247A",
+ "sevenperiod", "248E",
+ "sevenpersian", "06F7",
+ "sevenroman", "2176",
+ "sevensuperior", "2077",
+ "seventeencircle", "2470",
+ "seventeenparen", "2484",
+ "seventeenperiod", "2498",
+ "seventhai", "0E57",
+ "sfthyphen", "00AD",
+ "shaarmenian", "0577",
+ "shabengali", "09B6",
+ "shacyrillic", "0448",
+ "shaddaarabic", "0651",
+ "shaddadammaarabic", "FC61",
+ "shaddadammatanarabic", "FC5E",
+ "shaddafathaarabic", "FC60",
+ "shaddafathatanarabic", "0651_064B",
+ "shaddakasraarabic", "FC62",
+ "shaddakasratanarabic", "FC5F",
+ "shade", "2592",
+ "shadedark", "2593",
+ "shadelight", "2591",
+ "shademedium", "2592",
+ "shadeva", "0936",
+ "shagujarati", "0AB6",
+ "shagurmukhi", "0A36",
+ "shalshelethebrew", "0593",
+ "shbopomofo", "3115",
+ "shchacyrillic", "0449",
+ "sheenarabic", "0634",
+ "sheenfinalarabic", "FEB6",
+ "sheeninitialarabic", "FEB7",
+ "sheenmedialarabic", "FEB8",
+ "sheicoptic", "03E3",
+ "sheqel", "20AA",
+ "sheqelhebrew", "20AA",
+ "sheva", "05B0",
+ "sheva115", "05B0",
+ "sheva15", "05B0",
+ "sheva22", "05B0",
+ "sheva2e", "05B0",
+ "shevahebrew", "05B0",
+ "shevanarrowhebrew", "05B0",
+ "shevaquarterhebrew", "05B0",
+ "shevawidehebrew", "05B0",
+ "shhacyrillic", "04BB",
+ "shimacoptic", "03ED",
+ "shin", "05E9",
+ "shindagesh", "FB49",
+ "shindageshhebrew", "FB49",
+ "shindageshshindot", "FB2C",
+ "shindageshshindothebrew", "FB2C",
+ "shindageshsindot", "FB2D",
+ "shindageshsindothebrew", "FB2D",
+ "shindothebrew", "05C1",
+ "shinhebrew", "05E9",
+ "shinshindot", "FB2A",
+ "shinshindothebrew", "FB2A",
+ "shinsindot", "FB2B",
+ "shinsindothebrew", "FB2B",
+ "shook", "0282",
+ "sigma", "03C3",
+ "sigma1", "03C2",
+ "sigmafinal", "03C2",
+ "sigmalunatesymbolgreek", "03F2",
+ "sihiragana", "3057",
+ "sikatakana", "30B7",
+ "sikatakanahalfwidth", "FF7C",
+ "siluqhebrew", "05BD",
+ "siluqlefthebrew", "05BD",
+ "similar", "223C",
+ "sindothebrew", "05C2",
+ "siosacirclekorean", "3274",
+ "siosaparenkorean", "3214",
+ "sioscieuckorean", "317E",
+ "sioscirclekorean", "3266",
+ "sioskiyeokkorean", "317A",
+ "sioskorean", "3145",
+ "siosnieunkorean", "317B",
+ "siosparenkorean", "3206",
+ "siospieupkorean", "317D",
+ "siostikeutkorean", "317C",
+ "six", "0036",
+ "sixarabic", "0666",
+ "sixbengali", "09EC",
+ "sixcircle", "2465",
+ "sixcircleinversesansserif", "278F",
+ "sixdeva", "096C",
+ "sixgujarati", "0AEC",
+ "sixgurmukhi", "0A6C",
+ "sixhackarabic", "0666",
+ "sixhangzhou", "3026",
+ "sixideographicparen", "3225",
+ "sixinferior", "2086",
+ "sixmonospace", "FF16",
+ "sixparen", "2479",
+ "sixperiod", "248D",
+ "sixpersian", "06F6",
+ "sixroman", "2175",
+ "sixsuperior", "2076",
+ "sixteencircle", "246F",
+ "sixteencurrencydenominatorbengali", "09F9",
+ "sixteenparen", "2483",
+ "sixteenperiod", "2497",
+ "sixthai", "0E56",
+ "slash", "002F",
+ "slashmonospace", "FF0F",
+ "slong", "017F",
+ "slongdotaccent", "1E9B",
+ "smileface", "263A",
+ "smonospace", "FF53",
+ "sofpasuqhebrew", "05C3",
+ "softhyphen", "00AD",
+ "softsigncyrillic", "044C",
+ "sohiragana", "305D",
+ "sokatakana", "30BD",
+ "sokatakanahalfwidth", "FF7F",
+ "soliduslongoverlaycmb", "0338",
+ "solidusshortoverlaycmb", "0337",
+ "sorusithai", "0E29",
+ "sosalathai", "0E28",
+ "sosothai", "0E0B",
+ "sosuathai", "0E2A",
+ "space", "0020",
+ "spacehackarabic", "0020",
+ "spade", "2660",
+ "spadesuitblack", "2660",
+ "spadesuitwhite", "2664",
+ "sparen", "24AE",
+ "squarebelowcmb", "033B",
+ "squarecc", "33C4",
+ "squarecm", "339D",
+ "squarediagonalcrosshatchfill", "25A9",
+ "squarehorizontalfill", "25A4",
+ "squarekg", "338F",
+ "squarekm", "339E",
+ "squarekmcapital", "33CE",
+ "squareln", "33D1",
+ "squarelog", "33D2",
+ "squaremg", "338E",
+ "squaremil", "33D5",
+ "squaremm", "339C",
+ "squaremsquared", "33A1",
+ "squareorthogonalcrosshatchfill", "25A6",
+ "squareupperlefttolowerrightfill", "25A7",
+ "squareupperrighttolowerleftfill", "25A8",
+ "squareverticalfill", "25A5",
+ "squarewhitewithsmallblack", "25A3",
+ "srsquare", "33DB",
+ "ssabengali", "09B7",
+ "ssadeva", "0937",
+ "ssagujarati", "0AB7",
+ "ssangcieuckorean", "3149",
+ "ssanghieuhkorean", "3185",
+ "ssangieungkorean", "3180",
+ "ssangkiyeokkorean", "3132",
+ "ssangnieunkorean", "3165",
+ "ssangpieupkorean", "3143",
+ "ssangsioskorean", "3146",
+ "ssangtikeutkorean", "3138",
+ "sterling", "00A3",
+ "sterlingmonospace", "FFE1",
+ "strokelongoverlaycmb", "0336",
+ "strokeshortoverlaycmb", "0335",
+ "subset", "2282",
+ "subsetnotequal", "228A",
+ "subsetorequal", "2286",
+ "succeeds", "227B",
+ "suchthat", "220B",
+ "suhiragana", "3059",
+ "sukatakana", "30B9",
+ "sukatakanahalfwidth", "FF7D",
+ "sukunarabic", "0652",
+ "summation", "2211",
+ "sun", "263C",
+ "superset", "2283",
+ "supersetnotequal", "228B",
+ "supersetorequal", "2287",
+ "svsquare", "33DC",
+ "syouwaerasquare", "337C",
+ "t", "0074",
+ "tabengali", "09A4",
+ "tackdown", "22A4",
+ "tackleft", "22A3",
+ "tadeva", "0924",
+ "tagujarati", "0AA4",
+ "tagurmukhi", "0A24",
+ "taharabic", "0637",
+ "tahfinalarabic", "FEC2",
+ "tahinitialarabic", "FEC3",
+ "tahiragana", "305F",
+ "tahmedialarabic", "FEC4",
+ "taisyouerasquare", "337D",
+ "takatakana", "30BF",
+ "takatakanahalfwidth", "FF80",
+ "tatweelarabic", "0640",
+ "tau", "03C4",
+ "tav", "05EA",
+ "tavdages", "FB4A",
+ "tavdagesh", "FB4A",
+ "tavdageshhebrew", "FB4A",
+ "tavhebrew", "05EA",
+ "tbar", "0167",
+ "tbopomofo", "310A",
+ "tcaron", "0165",
+ "tccurl", "02A8",
+ "tcedilla", "0163",
+ "tcheharabic", "0686",
+ "tchehfinalarabic", "FB7B",
+ "tchehinitialarabic", "FB7C",
+ "tchehmedialarabic", "FB7D",
+ "tchehmeeminitialarabic", "FB7C_FEE4",
+ "tcircle", "24E3",
+ "tcircumflexbelow", "1E71",
+ "tcommaaccent", "0163",
+ "tdieresis", "1E97",
+ "tdotaccent", "1E6B",
+ "tdotbelow", "1E6D",
+ "tecyrillic", "0442",
+ "tedescendercyrillic", "04AD",
+ "teharabic", "062A",
+ "tehfinalarabic", "FE96",
+ "tehhahinitialarabic", "FCA2",
+ "tehhahisolatedarabic", "FC0C",
+ "tehinitialarabic", "FE97",
+ "tehiragana", "3066",
+ "tehjeeminitialarabic", "FCA1",
+ "tehjeemisolatedarabic", "FC0B",
+ "tehmarbutaarabic", "0629",
+ "tehmarbutafinalarabic", "FE94",
+ "tehmedialarabic", "FE98",
+ "tehmeeminitialarabic", "FCA4",
+ "tehmeemisolatedarabic", "FC0E",
+ "tehnoonfinalarabic", "FC73",
+ "tekatakana", "30C6",
+ "tekatakanahalfwidth", "FF83",
+ "telephone", "2121",
+ "telephoneblack", "260E",
+ "telishagedolahebrew", "05A0",
+ "telishaqetanahebrew", "05A9",
+ "tencircle", "2469",
+ "tenideographicparen", "3229",
+ "tenparen", "247D",
+ "tenperiod", "2491",
+ "tenroman", "2179",
+ "tesh", "02A7",
+ "tet", "05D8",
+ "tetdagesh", "FB38",
+ "tetdageshhebrew", "FB38",
+ "tethebrew", "05D8",
+ "tetsecyrillic", "04B5",
+ "tevirhebrew", "059B",
+ "tevirlefthebrew", "059B",
+ "thabengali", "09A5",
+ "thadeva", "0925",
+ "thagujarati", "0AA5",
+ "thagurmukhi", "0A25",
+ "thalarabic", "0630",
+ "thalfinalarabic", "FEAC",
+ "thanthakhatthai", "0E4C",
+ "theharabic", "062B",
+ "thehfinalarabic", "FE9A",
+ "thehinitialarabic", "FE9B",
+ "thehmedialarabic", "FE9C",
+ "thereexists", "2203",
+ "therefore", "2234",
+ "theta", "03B8",
+ "theta1", "03D1",
+ "thetasymbolgreek", "03D1",
+ "thieuthacirclekorean", "3279",
+ "thieuthaparenkorean", "3219",
+ "thieuthcirclekorean", "326B",
+ "thieuthkorean", "314C",
+ "thieuthparenkorean", "320B",
+ "thirteencircle", "246C",
+ "thirteenparen", "2480",
+ "thirteenperiod", "2494",
+ "thonangmonthothai", "0E11",
+ "thook", "01AD",
+ "thophuthaothai", "0E12",
+ "thorn", "00FE",
+ "thothahanthai", "0E17",
+ "thothanthai", "0E10",
+ "thothongthai", "0E18",
+ "thothungthai", "0E16",
+ "thousandcyrillic", "0482",
+ "thousandsseparatorarabic", "066C",
+ "thousandsseparatorpersian", "066C",
+ "three", "0033",
+ "threearabic", "0663",
+ "threebengali", "09E9",
+ "threecircle", "2462",
+ "threecircleinversesansserif", "278C",
+ "threedeva", "0969",
+ "threeeighths", "215C",
+ "threegujarati", "0AE9",
+ "threegurmukhi", "0A69",
+ "threehackarabic", "0663",
+ "threehangzhou", "3023",
+ "threeideographicparen", "3222",
+ "threeinferior", "2083",
+ "threemonospace", "FF13",
+ "threenumeratorbengali", "09F6",
+ "threeparen", "2476",
+ "threeperiod", "248A",
+ "threepersian", "06F3",
+ "threequarters", "00BE",
+ "threeroman", "2172",
+ "threesuperior", "00B3",
+ "threethai", "0E53",
+ "thzsquare", "3394",
+ "tihiragana", "3061",
+ "tikatakana", "30C1",
+ "tikatakanahalfwidth", "FF81",
+ "tikeutacirclekorean", "3270",
+ "tikeutaparenkorean", "3210",
+ "tikeutcirclekorean", "3262",
+ "tikeutkorean", "3137",
+ "tikeutparenkorean", "3202",
+ "tilde", "02DC",
+ "tildebelowcmb", "0330",
+ "tildecmb", "0303",
+ "tildecomb", "0303",
+ "tildedoublecmb", "0360",
+ "tildeoperator", "223C",
+ "tildeoverlaycmb", "0334",
+ "tildeverticalcmb", "033E",
+ "timescircle", "2297",
+ "tipehahebrew", "0596",
+ "tipehalefthebrew", "0596",
+ "tippigurmukhi", "0A70",
+ "titlocyrilliccmb", "0483",
+ "tiwnarmenian", "057F",
+ "tlinebelow", "1E6F",
+ "tmonospace", "FF54",
+ "toarmenian", "0569",
+ "tohiragana", "3068",
+ "tokatakana", "30C8",
+ "tokatakanahalfwidth", "FF84",
+ "tonebarextrahighmod", "02E5",
+ "tonebarextralowmod", "02E9",
+ "tonebarhighmod", "02E6",
+ "tonebarlowmod", "02E8",
+ "tonebarmidmod", "02E7",
+ "tonefive", "01BD",
+ "tonesix", "0185",
+ "tonetwo", "01A8",
+ "tonos", "0384",
+ "tonsquare", "3327",
+ "topatakthai", "0E0F",
+ "tortoiseshellbracketleft", "3014",
+ "tortoiseshellbracketleftsmall", "FE5D",
+ "tortoiseshellbracketleftvertical", "FE39",
+ "tortoiseshellbracketright", "3015",
+ "tortoiseshellbracketrightsmall", "FE5E",
+ "tortoiseshellbracketrightvertical", "FE3A",
+ "totaothai", "0E15",
+ "tpalatalhook", "01AB",
+ "tparen", "24AF",
+ "trademark", "2122",
+ "tretroflexhook", "0288",
+ "triagdn", "25BC",
+ "triaglf", "25C4",
+ "triagrt", "25BA",
+ "triagup", "25B2",
+ "ts", "02A6",
+ "tsadi", "05E6",
+ "tsadidagesh", "FB46",
+ "tsadidageshhebrew", "FB46",
+ "tsadihebrew", "05E6",
+ "tsecyrillic", "0446",
+ "tsere", "05B5",
+ "tsere12", "05B5",
+ "tsere1e", "05B5",
+ "tsere2b", "05B5",
+ "tserehebrew", "05B5",
+ "tserenarrowhebrew", "05B5",
+ "tserequarterhebrew", "05B5",
+ "tserewidehebrew", "05B5",
+ "tshecyrillic", "045B",
+ "ttabengali", "099F",
+ "ttadeva", "091F",
+ "ttagujarati", "0A9F",
+ "ttagurmukhi", "0A1F",
+ "tteharabic", "0679",
+ "ttehfinalarabic", "FB67",
+ "ttehinitialarabic", "FB68",
+ "ttehmedialarabic", "FB69",
+ "tthabengali", "09A0",
+ "tthadeva", "0920",
+ "tthagujarati", "0AA0",
+ "tthagurmukhi", "0A20",
+ "tturned", "0287",
+ "tuhiragana", "3064",
+ "tukatakana", "30C4",
+ "tukatakanahalfwidth", "FF82",
+ "tusmallhiragana", "3063",
+ "tusmallkatakana", "30C3",
+ "tusmallkatakanahalfwidth", "FF6F",
+ "twelvecircle", "246B",
+ "twelveparen", "247F",
+ "twelveperiod", "2493",
+ "twelveroman", "217B",
+ "twentycircle", "2473",
+ "twentyhangzhou", "5344",
+ "twentyparen", "2487",
+ "twentyperiod", "249B",
+ "two", "0032",
+ "twoarabic", "0662",
+ "twobengali", "09E8",
+ "twocircle", "2461",
+ "twocircleinversesansserif", "278B",
+ "twodeva", "0968",
+ "twodotenleader", "2025",
+ "twodotleader", "2025",
+ "twodotleadervertical", "FE30",
+ "twogujarati", "0AE8",
+ "twogurmukhi", "0A68",
+ "twohackarabic", "0662",
+ "twohangzhou", "3022",
+ "twoideographicparen", "3221",
+ "twoinferior", "2082",
+ "twomonospace", "FF12",
+ "twonumeratorbengali", "09F5",
+ "twoparen", "2475",
+ "twoperiod", "2489",
+ "twopersian", "06F2",
+ "tworoman", "2171",
+ "twostroke", "01BB",
+ "twosuperior", "00B2",
+ "twothai", "0E52",
+ "twothirds", "2154",
+ "u", "0075",
+ "uacute", "00FA",
+ "ubar", "0289",
+ "ubengali", "0989",
+ "ubopomofo", "3128",
+ "ubreve", "016D",
+ "ucaron", "01D4",
+ "ucircle", "24E4",
+ "ucircumflex", "00FB",
+ "ucircumflexbelow", "1E77",
+ "ucyrillic", "0443",
+ "udattadeva", "0951",
+ "udblacute", "0171",
+ "udblgrave", "0215",
+ "udeva", "0909",
+ "udieresis", "00FC",
+ "udieresisacute", "01D8",
+ "udieresisbelow", "1E73",
+ "udieresiscaron", "01DA",
+ "udieresiscyrillic", "04F1",
+ "udieresisgrave", "01DC",
+ "udieresismacron", "01D6",
+ "udotbelow", "1EE5",
+ "ugrave", "00F9",
+ "ugujarati", "0A89",
+ "ugurmukhi", "0A09",
+ "uhiragana", "3046",
+ "uhookabove", "1EE7",
+ "uhorn", "01B0",
+ "uhornacute", "1EE9",
+ "uhorndotbelow", "1EF1",
+ "uhorngrave", "1EEB",
+ "uhornhookabove", "1EED",
+ "uhorntilde", "1EEF",
+ "uhungarumlaut", "0171",
+ "uhungarumlautcyrillic", "04F3",
+ "uinvertedbreve", "0217",
+ "ukatakana", "30A6",
+ "ukatakanahalfwidth", "FF73",
+ "ukcyrillic", "0479",
+ "ukorean", "315C",
+ "umacron", "016B",
+ "umacroncyrillic", "04EF",
+ "umacrondieresis", "1E7B",
+ "umatragurmukhi", "0A41",
+ "umonospace", "FF55",
+ "underscore", "005F",
+ "underscoredbl", "2017",
+ "underscoremonospace", "FF3F",
+ "underscorevertical", "FE33",
+ "underscorewavy", "FE4F",
+ "union", "222A",
+ "universal", "2200",
+ "uogonek", "0173",
+ "uparen", "24B0",
+ "upblock", "2580",
+ "upperdothebrew", "05C4",
+ "upsilon", "03C5",
+ "upsilondieresis", "03CB",
+ "upsilondieresistonos", "03B0",
+ "upsilonlatin", "028A",
+ "upsilontonos", "03CD",
+ "uptackbelowcmb", "031D",
+ "uptackmod", "02D4",
+ "uragurmukhi", "0A73",
+ "uring", "016F",
+ "ushortcyrillic", "045E",
+ "usmallhiragana", "3045",
+ "usmallkatakana", "30A5",
+ "usmallkatakanahalfwidth", "FF69",
+ "ustraightcyrillic", "04AF",
+ "ustraightstrokecyrillic", "04B1",
+ "utilde", "0169",
+ "utildeacute", "1E79",
+ "utildebelow", "1E75",
+ "uubengali", "098A",
+ "uudeva", "090A",
+ "uugujarati", "0A8A",
+ "uugurmukhi", "0A0A",
+ "uumatragurmukhi", "0A42",
+ "uuvowelsignbengali", "09C2",
+ "uuvowelsigndeva", "0942",
+ "uuvowelsigngujarati", "0AC2",
+ "uvowelsignbengali", "09C1",
+ "uvowelsigndeva", "0941",
+ "uvowelsigngujarati", "0AC1",
+ "v", "0076",
+ "vadeva", "0935",
+ "vagujarati", "0AB5",
+ "vagurmukhi", "0A35",
+ "vakatakana", "30F7",
+ "vav", "05D5",
+ "vavdagesh", "FB35",
+ "vavdagesh65", "FB35",
+ "vavdageshhebrew", "FB35",
+ "vavhebrew", "05D5",
+ "vavholam", "FB4B",
+ "vavholamhebrew", "FB4B",
+ "vavvavhebrew", "05F0",
+ "vavyodhebrew", "05F1",
+ "vcircle", "24E5",
+ "vdotbelow", "1E7F",
+ "vecyrillic", "0432",
+ "veharabic", "06A4",
+ "vehfinalarabic", "FB6B",
+ "vehinitialarabic", "FB6C",
+ "vehmedialarabic", "FB6D",
+ "vekatakana", "30F9",
+ "venus", "2640",
+ "verticalbar", "007C",
+ "verticallineabovecmb", "030D",
+ "verticallinebelowcmb", "0329",
+ "verticallinelowmod", "02CC",
+ "verticallinemod", "02C8",
+ "vewarmenian", "057E",
+ "vhook", "028B",
+ "vikatakana", "30F8",
+ "viramabengali", "09CD",
+ "viramadeva", "094D",
+ "viramagujarati", "0ACD",
+ "visargabengali", "0983",
+ "visargadeva", "0903",
+ "visargagujarati", "0A83",
+ "vmonospace", "FF56",
+ "voarmenian", "0578",
+ "voicediterationhiragana", "309E",
+ "voicediterationkatakana", "30FE",
+ "voicedmarkkana", "309B",
+ "voicedmarkkanahalfwidth", "FF9E",
+ "vokatakana", "30FA",
+ "vparen", "24B1",
+ "vtilde", "1E7D",
+ "vturned", "028C",
+ "vuhiragana", "3094",
+ "vukatakana", "30F4",
+ "w", "0077",
+ "wacute", "1E83",
+ "waekorean", "3159",
+ "wahiragana", "308F",
+ "wakatakana", "30EF",
+ "wakatakanahalfwidth", "FF9C",
+ "wakorean", "3158",
+ "wasmallhiragana", "308E",
+ "wasmallkatakana", "30EE",
+ "wattosquare", "3357",
+ "wavedash", "301C",
+ "wavyunderscorevertical", "FE34",
+ "wawarabic", "0648",
+ "wawfinalarabic", "FEEE",
+ "wawhamzaabovearabic", "0624",
+ "wawhamzaabovefinalarabic", "FE86",
+ "wbsquare", "33DD",
+ "wcircle", "24E6",
+ "wcircumflex", "0175",
+ "wdieresis", "1E85",
+ "wdotaccent", "1E87",
+ "wdotbelow", "1E89",
+ "wehiragana", "3091",
+ "weierstrass", "2118",
+ "wekatakana", "30F1",
+ "wekorean", "315E",
+ "weokorean", "315D",
+ "wgrave", "1E81",
+ "whitebullet", "25E6",
+ "whitecircle", "25CB",
+ "whitecircleinverse", "25D9",
+ "whitecornerbracketleft", "300E",
+ "whitecornerbracketleftvertical", "FE43",
+ "whitecornerbracketright", "300F",
+ "whitecornerbracketrightvertical", "FE44",
+ "whitediamond", "25C7",
+ "whitediamondcontainingblacksmalldiamond", "25C8",
+ "whitedownpointingsmalltriangle", "25BF",
+ "whitedownpointingtriangle", "25BD",
+ "whiteleftpointingsmalltriangle", "25C3",
+ "whiteleftpointingtriangle", "25C1",
+ "whitelenticularbracketleft", "3016",
+ "whitelenticularbracketright", "3017",
+ "whiterightpointingsmalltriangle", "25B9",
+ "whiterightpointingtriangle", "25B7",
+ "whitesmallsquare", "25AB",
+ "whitesmilingface", "263A",
+ "whitesquare", "25A1",
+ "whitestar", "2606",
+ "whitetelephone", "260F",
+ "whitetortoiseshellbracketleft", "3018",
+ "whitetortoiseshellbracketright", "3019",
+ "whiteuppointingsmalltriangle", "25B5",
+ "whiteuppointingtriangle", "25B3",
+ "wihiragana", "3090",
+ "wikatakana", "30F0",
+ "wikorean", "315F",
+ "wmonospace", "FF57",
+ "wohiragana", "3092",
+ "wokatakana", "30F2",
+ "wokatakanahalfwidth", "FF66",
+ "won", "20A9",
+ "wonmonospace", "FFE6",
+ "wowaenthai", "0E27",
+ "wparen", "24B2",
+ "wring", "1E98",
+ "wsuperior", "02B7",
+ "wturned", "028D",
+ "wynn", "01BF",
+ "x", "0078",
+ "xabovecmb", "033D",
+ "xbopomofo", "3112",
+ "xcircle", "24E7",
+ "xdieresis", "1E8D",
+ "xdotaccent", "1E8B",
+ "xeharmenian", "056D",
+ "xi", "03BE",
+ "xmonospace", "FF58",
+ "xparen", "24B3",
+ "xsuperior", "02E3",
+ "y", "0079",
+ "yaadosquare", "334E",
+ "yabengali", "09AF",
+ "yacute", "00FD",
+ "yadeva", "092F",
+ "yaekorean", "3152",
+ "yagujarati", "0AAF",
+ "yagurmukhi", "0A2F",
+ "yahiragana", "3084",
+ "yakatakana", "30E4",
+ "yakatakanahalfwidth", "FF94",
+ "yakorean", "3151",
+ "yamakkanthai", "0E4E",
+ "yasmallhiragana", "3083",
+ "yasmallkatakana", "30E3",
+ "yasmallkatakanahalfwidth", "FF6C",
+ "yatcyrillic", "0463",
+ "ycircle", "24E8",
+ "ycircumflex", "0177",
+ "ydieresis", "00FF",
+ "ydotaccent", "1E8F",
+ "ydotbelow", "1EF5",
+ "yeharabic", "064A",
+ "yehbarreearabic", "06D2",
+ "yehbarreefinalarabic", "FBAF",
+ "yehfinalarabic", "FEF2",
+ "yehhamzaabovearabic", "0626",
+ "yehhamzaabovefinalarabic", "FE8A",
+ "yehhamzaaboveinitialarabic", "FE8B",
+ "yehhamzaabovemedialarabic", "FE8C",
+ "yehinitialarabic", "FEF3",
+ "yehmedialarabic", "FEF4",
+ "yehmeeminitialarabic", "FCDD",
+ "yehmeemisolatedarabic", "FC58",
+ "yehnoonfinalarabic", "FC94",
+ "yehthreedotsbelowarabic", "06D1",
+ "yekorean", "3156",
+ "yen", "00A5",
+ "yenmonospace", "FFE5",
+ "yeokorean", "3155",
+ "yeorinhieuhkorean", "3186",
+ "yerahbenyomohebrew", "05AA",
+ "yerahbenyomolefthebrew", "05AA",
+ "yericyrillic", "044B",
+ "yerudieresiscyrillic", "04F9",
+ "yesieungkorean", "3181",
+ "yesieungpansioskorean", "3183",
+ "yesieungsioskorean", "3182",
+ "yetivhebrew", "059A",
+ "ygrave", "1EF3",
+ "yhook", "01B4",
+ "yhookabove", "1EF7",
+ "yiarmenian", "0575",
+ "yicyrillic", "0457",
+ "yikorean", "3162",
+ "yinyang", "262F",
+ "yiwnarmenian", "0582",
+ "ymonospace", "FF59",
+ "yod", "05D9",
+ "yoddagesh", "FB39",
+ "yoddageshhebrew", "FB39",
+ "yodhebrew", "05D9",
+ "yodyodhebrew", "05F2",
+ "yodyodpatahhebrew", "FB1F",
+ "yohiragana", "3088",
+ "yoikorean", "3189",
+ "yokatakana", "30E8",
+ "yokatakanahalfwidth", "FF96",
+ "yokorean", "315B",
+ "yosmallhiragana", "3087",
+ "yosmallkatakana", "30E7",
+ "yosmallkatakanahalfwidth", "FF6E",
+ "yotgreek", "03F3",
+ "yoyaekorean", "3188",
+ "yoyakorean", "3187",
+ "yoyakthai", "0E22",
+ "yoyingthai", "0E0D",
+ "yparen", "24B4",
+ "ypogegrammeni", "037A",
+ "ypogegrammenigreekcmb", "0345",
+ "yr", "01A6",
+ "yring", "1E99",
+ "ysuperior", "02B8",
+ "ytilde", "1EF9",
+ "yturned", "028E",
+ "yuhiragana", "3086",
+ "yuikorean", "318C",
+ "yukatakana", "30E6",
+ "yukatakanahalfwidth", "FF95",
+ "yukorean", "3160",
+ "yusbigcyrillic", "046B",
+ "yusbigiotifiedcyrillic", "046D",
+ "yuslittlecyrillic", "0467",
+ "yuslittleiotifiedcyrillic", "0469",
+ "yusmallhiragana", "3085",
+ "yusmallkatakana", "30E5",
+ "yusmallkatakanahalfwidth", "FF6D",
+ "yuyekorean", "318B",
+ "yuyeokorean", "318A",
+ "yyabengali", "09DF",
+ "yyadeva", "095F",
+ "z", "007A",
+ "zaarmenian", "0566",
+ "zacute", "017A",
+ "zadeva", "095B",
+ "zagurmukhi", "0A5B",
+ "zaharabic", "0638",
+ "zahfinalarabic", "FEC6",
+ "zahinitialarabic", "FEC7",
+ "zahiragana", "3056",
+ "zahmedialarabic", "FEC8",
+ "zainarabic", "0632",
+ "zainfinalarabic", "FEB0",
+ "zakatakana", "30B6",
+ "zaqefgadolhebrew", "0595",
+ "zaqefqatanhebrew", "0594",
+ "zarqahebrew", "0598",
+ "zayin", "05D6",
+ "zayindagesh", "FB36",
+ "zayindageshhebrew", "FB36",
+ "zayinhebrew", "05D6",
+ "zbopomofo", "3117",
+ "zcaron", "017E",
+ "zcircle", "24E9",
+ "zcircumflex", "1E91",
+ "zcurl", "0291",
+ "zdot", "017C",
+ "zdotaccent", "017C",
+ "zdotbelow", "1E93",
+ "zecyrillic", "0437",
+ "zedescendercyrillic", "0499",
+ "zedieresiscyrillic", "04DF",
+ "zehiragana", "305C",
+ "zekatakana", "30BC",
+ "zero", "0030",
+ "zeroarabic", "0660",
+ "zerobengali", "09E6",
+ "zerodeva", "0966",
+ "zerogujarati", "0AE6",
+ "zerogurmukhi", "0A66",
+ "zerohackarabic", "0660",
+ "zeroinferior", "2080",
+ "zeromonospace", "FF10",
+ "zeropersian", "06F0",
+ "zerosuperior", "2070",
+ "zerothai", "0E50",
+ "zerowidthjoiner", "FEFF",
+ "zerowidthnonjoiner", "200C",
+ "zerowidthspace", "200B",
+ "zeta", "03B6",
+ "zhbopomofo", "3113",
+ "zhearmenian", "056A",
+ "zhebrevecyrillic", "04C2",
+ "zhecyrillic", "0436",
+ "zhedescendercyrillic", "0497",
+ "zhedieresiscyrillic", "04DD",
+ "zihiragana", "3058",
+ "zikatakana", "30B8",
+ "zinorhebrew", "05AE",
+ "zlinebelow", "1E95",
+ "zmonospace", "FF5A",
+ "zohiragana", "305E",
+ "zokatakana", "30BE",
+ "zparen", "24B5",
+ "zretroflexhook", "0290",
+ "zstroke", "01B6",
+ "zuhiragana", "305A",
+ "zukatakana", "30BA",
+);
diff --git a/src/utils/afmtodit/make-afmtodit-tables b/src/utils/afmtodit/make-afmtodit-tables
new file mode 100755
index 0000000..937bb72
--- /dev/null
+++ b/src/utils/afmtodit/make-afmtodit-tables
@@ -0,0 +1,139 @@
+#! /bin/sh
+#
+# make-afmtodit-tables -- script for creating the 'unicode_decomposed'
+# and 'AGL_to_unicode' tables
+#
+# Copyright (C) 2005-2020 Free Software Foundation, Inc.
+# Written by Werner Lemberg <wl@gnu.org>
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# usage:
+#
+# make-afmtodit-tables \
+# UnicodeData.txt version-string glyphlist.txt > afmtodit.in
+#
+# 'UnicodeData.txt' is the central database file from the Unicode
+# standard. Unfortunately, it doesn't contain a version number, which
+# must be thus provided manually as an additional parameter.
+#
+# 'glyphlist.txt' holds the Adobe Glyph List (AGL).
+#
+# This program needs a C preprocessor.
+#
+
+if [ $# -ne 3 ]
+then
+ echo "usage: $0 UnicodeData.txt UNICODE-VERSION-STRING" \
+ "glyphlist.txt > afmtodit.tables"
+ exit 2
+fi
+
+unicode_data="$1"
+unicode_version="$2"
+glyph_list="$3"
+
+for f in "$1" "$3"
+do
+ if ! [ -r "$f" ]
+ then
+ echo "$0: '$f' does not exist or is not readable" >&2
+ exit 1
+ fi
+done
+
+# Handle UnicodeData.txt.
+#
+# Remove ranges and control characters,
+# then extract the decomposition field,
+# then remove lines without decomposition,
+# then remove all compatibility decompositions.
+cat "$1" \
+| sed -e '/^[^;]*;</d' \
+| sed -e 's/;[^;]*;[^;]*;[^;]*;[^;]*;\([^;]*\);.*$/;\1/' \
+| sed -e '/^[^;]*;$/d' \
+| sed -e '/^[^;]*;</d' > $$1
+
+# Prepare input for running cpp.
+cat $$1 \
+| sed -e 's/^\([^;]*\);/#define \1 /' \
+ -e 's/ / u/g' > $$2
+cat $$1 \
+| sed -e 's/^\([^;]*\);.*$/\1 u\1/' >> $$2
+
+# Run C preprocessor to recursively decompose.
+"${CPP:-cpp}" $$2 $$3
+
+# Convert it back to original format.
+cat $$3 \
+| sed -e '/#/d' \
+ -e '/^$/d' \
+ -e 's/ \+/ /g' \
+ -e 's/ *$//' \
+ -e 's/u//g' \
+ -e 's/^\([^ ]*\) /\1;/' > $$4
+
+# Write comment.
+cat <<END
+# This table was algorithmically derived from the file 'UnicodeData.txt'
+# for Unicode $unicode_version, available from unicode.org,
+# on `date '+%Y-%m-%d'`.
+END
+
+# Emit first table.
+echo 'my %unicode_decomposed = ('
+cat $$4 \
+| sed -e 's/ /_/g' \
+ -e 's/\(.*\);\(.*\)/ "\1", "\2",/'
+echo ');'
+echo ''
+
+# Write comment.
+cat <<END
+# This table was algorithmically derived from the Adobe Glyph List (AGL)
+# file 'glyphlist.txt' from the GitHub Adobe Type Tools agl-aglfn
+# project, on `date '+%Y-%m-%d'`.
+#
+# See "groff:" comments for altered mappings.
+END
+
+# Convert AGL syntax to a chunk of Perl.
+cat "$3" \
+| sed -e '/#/d' \
+ -e 's/ /_/g' \
+ -e '/;\(E\|F[0-8]\)/d' \
+ -e 's/\(.*\);\(.*\)/ "\1", "\2",/' > $$5
+
+# Perform groff replacements.
+sed \
+ -e 's/\("Delta"\), "2206",$/\1, "0394", # groff: not U+2206/' \
+ -e 's/\("Omega"\), "2126",$/\1, "03A9", # groff: not U+2126/' \
+ -e 's/\("mu"\), "00B5",$/\1, "03BC", # groff: not U+00B5/' \
+ < $$5 > $$6
+
+# Emit second table.
+echo 'my %AGL_to_unicode = ('
+cat $$6
+echo ');'
+
+# Remove temporary files.
+rm $$1 $$2 $$3 $$4 $$5 $$6
+
+# Local Variables:
+# fill-column: 72
+# End:
+# vim: set textwidth=72:
diff --git a/src/utils/grog/grog.1.man b/src/utils/grog/grog.1.man
new file mode 100644
index 0000000..efcd728
--- /dev/null
+++ b/src/utils/grog/grog.1.man
@@ -0,0 +1,628 @@
+.TH grog @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grog \- \(lqgroff guess\(rq\(eminfer the
+.I groff
+command a document requires
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" This file is part of grog, which is part of groff, a free software
+.\" project. You can redistribute it and/or modify it under the terms
+.\" of the GNU General Public License version 2 (GPL2) as published by
+.\" the Free Software Foundation.
+.\"
+.\" groff is distributed in the hope that it will be useful, but WITHOUT
+.\" ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+.\" or FITNESS FOR A PARTICULAR PURPOSE.
+.\"
+.\" The text for GPL2 is available in the internet at
+.\" <http://www.gnu.org/licenses/gpl2.0.txt>.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grog_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grog
+.RB [ \-\-run ]
+.RB [ \-\-ligatures ]
+.RI [ groff-option\~ .\|.\|.\&]
+.RB [ \-\- ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grog
+.B \-h
+.
+.SY grog
+.B \-\-help
+.YS
+.
+.
+.SY grog
+.B \-v
+.
+.SY grog
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I grog
+reads its input
+and guesses which
+.MR groff @MAN1EXT@
+options are needed to render it.
+.
+If no operands are given,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I grog
+reads the standard input stream.
+.
+The corresponding
+.I groff
+command is normally written to the standard output stream.
+.
+With the option
+.BR \-\-run ,
+the inferred command is written to the standard error stream and then
+executed.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message,
+whereas
+.B \-v
+and
+.B \-\-version
+display version information;
+all exit afterward.
+.
+.
+.TP
+.B \-\-ligatures
+includes the arguments
+.B \-P\-y \-PU
+in the inferred
+.I groff
+command.
+.
+These are supported only by the
+.B pdf
+output device.
+.
+.
+.TP
+.B \-\-run
+writes the inferred command to the standard error stream and then
+executes it.
+.
+.
+.P
+All other specified short options
+(that is,
+arguments beginning with a minus sign
+.RB \[lq] \- \[rq]
+followed by a letter)
+are interpreted as
+.I groff
+options or option clusters with or without an option argument.
+.
+Such options are included in the constructed
+.I groff
+command line.
+.
+.
+.\" ====================================================================
+.SH Details
+.\" ====================================================================
+.
+.I grog
+reads each
+.I file
+operand,
+pattern-matching strings that are statistically likely to be
+characteristic of
+.MR roff @MAN7EXT@
+documents.
+.
+It tries to guess which of the following
+.I groff
+options are required to correctly render the input:
+.BR \-e ,
+.BR \-g ,
+.BR \-G ,
+.BR \-j ,
+.\" gideal is not implemented yet.
+.\" .BR \-J ,
+.BR \-p ,
+.BR \-R ,
+.\".BR \-s ,
+.B \-t
+(preprocessors);
+and
+.BR \-man ,
+.BR \-mdoc ,
+.BR \-mdoc\-old ,
+.BR \-me ,
+.BR \-mm ,
+.BR \-mom ,
+and
+.B \-ms
+(macro packages).
+.
+The inferred
+.I groff
+command including these options and any
+.I file
+parameters is written to the standard output stream.
+.
+.
+.P
+It is possible to specify arbitrary
+.I groff
+options on the command line.
+.
+These are included in the inferred command without change.
+.
+Choices of
+.I groff
+options include
+.B \-C
+to enable AT&T
+.I troff
+compatibility mode and
+.B \-T
+to select a non-default output device.
+.
+If the input is not encoded in US-ASCII,
+ISO 8859-1,
+or IBM code page 1047,
+specification of a
+.I groff
+option to run the
+.MR preconv @MAN1EXT@
+preprocessor is advised;
+see the
+.BR \-D ,
+.BR \-k ,
+and
+.B \-K
+options of
+.MR groff @MAN1EXT@ .
+.
+For UTF-8 input,
+.B \-k
+is a good choice.
+.
+.
+.P
+.I groff
+may issue diagnostic messages when an inappropriate
+.B \-m
+option,
+or multiple conflicting ones,
+are specified.
+.
+Consequently,
+it is best to specify no
+.B \-m
+options to
+.I grog
+unless it cannot correctly infer all of the
+.B \-m
+arguments a document requires.
+.
+A
+.I roff
+document can also be written without recourse to any macro package.
+.
+In such cases,
+.I grog
+will infer a
+.I groff
+command without an
+.B \-m
+option.
+.
+.
+.\" ====================================================================
+.SS Limitations
+.\" ====================================================================
+.
+.I grog
+presumes that the input does not change the escape,
+control,
+or no-break control characters.
+.
+.I grog
+does not parse
+.I roff
+input line continuation or control structures
+(brace escape sequences and the
+.RB \[lq] if \[rq],
+.RB \[lq] ie \[rq],
+and
+.RB \[lq] el \[rq]
+requests)
+nor
+.IR groff 's
+.RB \[lq] while \[rq].
+.
+Thus the input
+.
+.RS
+.EX
+\&.if \[rs]
+t .NH 1
+\&.if n .SH
+Introduction
+.EE
+.RE
+.
+will conceal the use of the
+.I ms
+macros
+.B NH
+and
+.B SH
+from
+.IR grog .
+.
+Such constructions are regarded by
+.IR grog 's
+implementors as insufficiently common to cause many inference problems.
+.
+Preprocessors can be even stricter when matching macro calls that
+bracket the regions of an input file they replace.
+.
+.IR pic ,
+for example,
+requires
+.BR PS ,
+.BR PE ,
+and
+.B PF
+calls to immediately follow the default control character at the
+beginning of a line.
+.
+.
+.P
+Detection of the
+.B \-s
+option
+(the
+.MR @g@soelim @MAN1EXT@
+preprocessor)
+is tricky;
+to correctly infer its necessity would require
+.I grog
+to recursively open all files given as arguments to the
+.B .so
+request under the same conditions that
+.I @g@soelim
+itself does so;
+see its man page.
+.
+Recall that
+.I @g@soelim
+is necessary only if sourced files need to be preprocessed.
+.
+Therefore,
+as a workaround,
+you may want to run the input through
+.I @g@soelim
+manually,
+piping it to
+.IR grog ,
+and compare the output to running
+.I grog
+on the input directly.
+.
+If the
+.RI \[lq] @g@soelim \[rq]ed
+input causes
+.I grog
+to infer additional preprocessor options,
+then
+.B \-s
+is likely necessary.
+.
+.
+.RS
+.P
+.EX
+$ \c
+.B printf \[dq].TS\[rs]nl.\[rs]nI\[aq]m a table.\[rs]n.TE\[rs]n\[dq] > \
+3.roff
+$ \c
+.B printf \[dq].so 3.roff\[rs]n\[dq] > 2.roff
+$ \c
+.B printf \[dq].XP\[rs]n.so 2.roff\[rs]n\[dq] > 1.roff
+$ \c
+.B grog 1.roff
+groff \-ms 1.roff
+$ \c
+.B @g@soelim 1.roff | grog
+groff \-t \-ms \-
+.EE
+.RE
+.
+.
+.P
+In the foregoing example,
+we see that this procedure enabled
+.I grog
+to detect
+.MR @g@tbl @MAN1EXT@
+macros,
+so we would add
+.B \-s
+as well as the detected
+.B \-t
+option to a revised
+.I grog
+or
+.I groff
+command.
+.
+.
+.RS
+.P
+.EX
+$ \c
+.B grog \-st 1.roff
+groff \-st \-ms 1.roff
+.EE
+.RE
+.
+.
+.\" ====================================================================
+.SH "Exit status"
+.\" ====================================================================
+.
+.I grog
+exits with error status
+.B 1
+if a macro package appears to be in use by the input document,
+but
+.I grog
+was unable to infer which one,
+or
+.B 2
+if there were problems handling an option or operand.
+.
+It otherwise exits with status
+.BR 0 .
+.
+(If the
+.B \-\-run
+option is specified,
+.IR groff 's
+exit status is discarded.)
+.
+Inferring no preprocessors or macro packages is not an error condition;
+a valid
+.I roff
+document need not use either.
+.
+Even plain text is valid input,
+if one is mindful of the syntax of the control and escape characters.
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+Running
+.
+.RS
+.EX
+.B grog @DOCDIR@/meintro.me
+.EE
+.RE
+at the command line results in
+.RS
+.EX
+groff \-me @DOCDIR@/meintro.me
+.EE
+.RE
+.
+because
+.I grog
+recognizes that the file
+.I meintro.me
+is written using macros from the
+.I me
+package.
+.
+The command
+.
+.RS
+.EX
+.B grog @DOCDIR@/pic.ms
+.EE
+.RE
+.
+outputs
+.
+.RS
+.EX
+groff \-e \-p \-t \-ms @DOCDIR@/pic.ms
+.EE
+.RE
+.
+on the other hand.
+.
+Besides discerning the
+.I ms
+macro package,
+.I grog
+recognizes that the file
+.I pic.ms
+additionally needs the combination of
+.B \-t
+for
+.IR tbl ,
+.B \-e
+for
+.IR eqn ,
+and
+.B \-p
+for
+.IR pic .
+.
+.
+.\" XXX: grog no longer (June 2021) attempts to detect this scenario.
+.\" It's also not a practical one; full-service macro packages don't
+.\" generally support being "unloaded" for subsequent processing of
+.\" another document using a different one. We do achieve it, with
+.\" care, in groff with man(7) and mdoc(7) (see andoc.tmac).
+.\" .P
+.\" If both of the former example files are combined in the command
+.\" .
+.\" .RS
+.\" .EX
+.\" .B grog meintro.me pic.ms
+.\" .EE
+.\" .RE
+.\" .
+.\" a diagnostic message is sent to the standard error stream because
+.\" some macro packages cannot be combined.
+.\" .
+.\" Nevertheless the corresponding output with the wrong options is
+.\" written to standard output:
+.\" .
+.\" .RS
+.\" .EX
+.\" groff \-t \-e \-p \-ms meintro.me pic.ms
+.\" .EE
+.\" .RE
+.\" .
+.\" and
+.\" .I grog
+.\" terminates with an error exit status.
+.
+.
+.P
+Consider a file
+.IR \%doc/\:\%grnexampl.me ,
+which uses the
+.I @g@grn
+preprocessor to include a
+.MR gremlin 1
+picture file in an
+.I me \" generic
+document.
+.
+Let's say we want to suppress color output,
+produce a DVI file,
+and get backtraces for any errors that
+.I @g@troff
+encounters.
+.
+The command
+.
+.RS
+.EX
+.B grog \-bc \-Idoc \-Tdvi doc/grnexmpl.me
+.EE
+.RE
+.
+is processed by
+.I grog
+into
+.
+.RS
+.EX
+groff \-bc \-Idoc \-Tdvi \-e \-g \-me doc/grnexmpl.me
+.EE
+.RE
+.
+where we can see that
+.I grog
+has inferred the
+.I me \" generic
+macro package along with the
+.I eqn \" generic
+and
+.I grn \" generic
+preprocessors.
+.
+(The input file is located in
+.I @DOCDIR@
+if you'd like to try this example yourself.)
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I grog
+was originally written in Bourne shell by James Clark.
+.
+The current implementation in Perl was written by
+.MT groff\-bernd\:.warken\-72@\:web\:.de
+Bernd Warken
+.ME
+and heavily revised by
+.MT g.branden\:.robinson@\:gmail\:.com
+G.\& Branden Robinson
+.ME .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grog_1_man_C]
+.do rr *groff_grog_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/grog/grog.am b/src/utils/grog/grog.am
new file mode 100644
index 0000000..f7ca5eb
--- /dev/null
+++ b/src/utils/grog/grog.am
@@ -0,0 +1,50 @@
+# Copyright (C) 1993-2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+grog_srcdir = $(top_srcdir)/src/utils/grog
+bin_SCRIPTS += grog
+man1_MANS += src/utils/grog/grog.1
+EXTRA_DIST += \
+ src/utils/grog/grog.1.man \
+ src/utils/grog/grog.pl \
+ src/utils/grog/tests/foo.man
+
+grog: $(grog_srcdir)/grog.pl $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)$(RM) $@ \
+ && sed -f "$(SH_DEPS_SED_SCRIPT)" \
+ -e "s|[@]PERL[@]|$(PERL)|" \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ -e "$(SH_SCRIPT_SED_CMD)" \
+ $(grog_srcdir)/grog.pl \
+ >$@ \
+ && chmod +x $@
+
+grog_TESTS = \
+ src/utils/grog/tests/PF-does-not-start-pic-region.sh \
+ src/utils/grog/tests/avoid-refer-fakeout.sh \
+ src/utils/grog/tests/preserve-groff-options.sh \
+ src/utils/grog/tests/recognize-perl-pod.sh \
+ src/utils/grog/tests/smoke-test.sh
+TESTS += $(grog_TESTS)
+EXTRA_DIST += $(grog_TESTS)
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/grog/grog.pl b/src/utils/grog/grog.pl
new file mode 100644
index 0000000..28973c5
--- /dev/null
+++ b/src/utils/grog/grog.pl
@@ -0,0 +1,721 @@
+#!@PERL@
+# grog - guess options for groff command
+# Inspired by doctype script in Kernighan & Pike, Unix Programming
+# Environment, pp 306-8.
+
+# Copyright (C) 1993-2021 Free Software Foundation, Inc.
+# Written by James Clark.
+# Rewritten in Perl by Bernd Warken <groff-bernd.warken-72@web.de>.
+# Hacked up by G. Branden Robinson, 2021.
+
+# This file is part of 'grog', which is part of 'groff'.
+
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/gpl-2.0.html>.
+
+use warnings;
+use strict;
+
+use File::Spec;
+
+my $groff_version = 'DEVELOPMENT';
+
+my @command = (); # the constructed groff command
+my @requested_package = (); # arguments to '-m' grog options
+my @inferred_preprocessor = (); # preprocessors the document uses
+my @inferred_main_package = (); # full-service package(s) detected
+my $main_package; # full-service package we go with
+my $do_run = 0; # run generated 'groff' command
+my $use_compatibility_mode = 0; # is -C being passed to groff?
+
+my %preprocessor_for_macro = (
+ 'EQ', 'eqn',
+ 'G1', 'grap',
+ 'GS', 'grn',
+ 'PS', 'pic',
+ '[', 'refer',
+ #'so', 'soelim', # Can't be inferred this way; see grog man page.
+ 'TS', 'tbl',
+ 'cstart', 'chem',
+ 'lilypond', 'glilypond',
+ 'Perl', 'gperl',
+ 'pinyin', 'gpinyin',
+);
+
+my $program_name = $0;
+{
+ my ($v, $d, $f) = File::Spec->splitpath($program_name);
+ $program_name = $f;
+}
+
+my %user_macro;
+my %score = ();
+
+my @input_file;
+
+# .TH is both a man(7) macro and often used with tbl(1). We expect to
+# find .TH in ms(7) documents only between .TS and .TE calls, and in
+# man(7) documents only as the first macro call.
+my $have_seen_first_macro_call = 0;
+# man(7) and ms(7) use many of the same macro names; do extra checking.
+my $man_score = 0;
+my $ms_score = 0;
+
+my $had_inference_problem = 0;
+my $had_processing_problem = 0;
+my $have_any_valid_arguments = 0;
+
+
+sub fail {
+ my $text = shift;
+ print STDERR "$program_name: error: $text\n";
+ $had_processing_problem = 1;
+}
+
+
+sub warn {
+ my $text = shift;
+ print STDERR "$program_name: warning: $text\n";
+}
+
+
+sub process_arguments {
+ my $no_more_options = 0;
+ my $delayed_option = '';
+ my $was_minus = 0;
+ my $optarg = 0;
+ my $pdf_with_ligatures = 0;
+
+ foreach my $arg (@ARGV) {
+ if ( $optarg ) {
+ push @command, $arg;
+ $optarg = 0;
+ next;
+ }
+
+ if ($no_more_options) {
+ push @input_file, $arg;
+ next;
+ }
+
+ if ($delayed_option) {
+ if ($delayed_option eq '-m') {
+ push @requested_package, $arg;
+ $arg = '';
+ } else {
+ push @command, $delayed_option;
+ }
+
+ push @command, $arg if $arg;
+ $delayed_option = '';
+ next;
+ }
+
+ unless ( $arg =~ /^-/ ) { # file name, no opt, no optarg
+ push @input_file, $arg;
+ next;
+ }
+
+ # now $arg starts with '-'
+
+ if ($arg eq '-') {
+ unless ($was_minus) {
+ push @input_file, $arg;
+ $was_minus = 1;
+ }
+ next;
+ }
+
+ if ($arg eq '--') {
+ $no_more_options = 1;
+ next;
+ }
+
+ # Handle options that cause an early exit.
+ &version() if ($arg eq '-v' || $arg eq '--version');
+ &usage(0) if ($arg eq '-h' || $arg eq '--help');
+
+ if ($arg =~ '^--.') {
+ if ($arg =~ '^--(run|with-ligatures)$') {
+ $do_run = 1 if ($arg eq '--run');
+ $pdf_with_ligatures = 1 if ($arg eq '--with-ligatures');
+ } else {
+ &fail("unrecognized grog option '$arg'; ignored");
+ &usage(1);
+ }
+ next;
+ }
+
+ # Handle groff options that take an argument.
+
+ # Handle the option argument being separated by whitespace.
+ if ($arg =~ /^-[dfFIKLmMnoPrTwW]$/) {
+ $delayed_option = $arg;
+ next;
+ }
+
+ # Handle '-m' option without subsequent whitespace.
+ if ($arg =~ /^-m/) {
+ my $package = $arg;
+ $package =~ s/-m//;
+ push @requested_package, $package;
+ next;
+ }
+
+ # Treat anything else as (possibly clustered) groff options that
+ # take no arguments.
+
+ # Our do_line() needs to know if it should do compatibility parsing.
+ $use_compatibility_mode = 1 if ($arg =~ /C/);
+
+ push @command, $arg;
+ }
+
+ if ($pdf_with_ligatures) {
+ push @command, '-P-y';
+ push @command, '-PU';
+ }
+
+ @input_file = ('-') unless (@input_file);
+} # process_arguments()
+
+
+sub process_input {
+ foreach my $file (@input_file) {
+ unless ( open(FILE, $file eq "-" ? $file : "< $file") ) {
+ &fail("cannot open '$file': $!");
+ next;
+ }
+
+ $have_any_valid_arguments = 1;
+
+ while (my $line = <FILE>) {
+ chomp $line;
+ &do_line($line);
+ }
+
+ close(FILE);
+ } # end foreach
+} # process_input()
+
+
+# Push item onto inferred full-service list only if not already present.
+sub push_main_package {
+ my $pkg = shift;
+ if (!grep(/^$pkg/, @inferred_main_package)) {
+ push @inferred_main_package, $pkg;
+ }
+} # push_main_package()
+
+
+sub do_line {
+ my $command; # request or macro name
+ my $args; # request or macro arguments
+
+ my $line = shift;
+
+ # Check for a Perl Pod::Man comment.
+ #
+ # An alternative to this kludge is noted below: if a "standard" macro
+ # is redefined, we could delete it from the relevant lists and
+ # hashes.
+ if ($line =~ /\\\" Automatically generated by Pod::Man/) {
+ $man_score += 100;
+ }
+
+ # Strip comments.
+ $line =~ s/\\".*//;
+ $line =~ s/\\#.*// unless $use_compatibility_mode;
+
+ return unless ($line =~ /^[.']/); # Ignore text lines.
+
+ # Perform preprocessor checks; they scan their inputs using a rump
+ # interpretation of roff(7) syntax that requires the default control
+ # character and no space between it and the macro name. In AT&T
+ # compatibility mode, no space (or newline!) is required after the
+ # macro name, either. We mimic the preprocessors themselves; eqn(1),
+ # for instance, does not recognize '.EN' if '.EQ' has not been seen.
+ my $boundary = '\\b';
+ $boundary = '' if ($use_compatibility_mode);
+
+ if ($line =~ /^\.(\S\S)$boundary/ || $line =~ /^\.(\[)/) {
+ my $macro = $1;
+ # groff identifiers can have extremely weird characters in them.
+ # The ones we care about are conventionally named, but me(7)
+ # documents can call macros like '+c', so quote carefully.
+ if (grep(/^\Q$macro\E$/, keys %preprocessor_for_macro)) {
+ my $preproc = $preprocessor_for_macro{$macro};
+ if (!grep(/$preproc/, @inferred_preprocessor)) {
+ push @inferred_preprocessor, $preproc;
+ }
+ }
+ }
+
+ # Normalize control lines; convert no-break control character to the
+ # regular one and remove unnecessary whitespace.
+ $line =~ s/^['.]\s*/./;
+ $line =~ s/\s+$//;
+
+ return if ($line =~ /^\.$/); # Ignore empty request.
+ return if ($line =~ /^\.\\?\.$/); # Ignore macro definition ends.
+
+ # Split control line into a request or macro call and its arguments.
+
+ # Handle single-letter macro names.
+ if ($line =~ /^\.(\S)(\s+(.*))?$/) {
+ $command = $1;
+ $args = $2;
+ # Handle two-letter macro/request names in compatibility mode.
+ } elsif ($use_compatibility_mode) {
+ $line =~ /^\.(\S\S)\s*(.*)$/;
+ $command = $1;
+ $args = $2;
+ # Handle multi-letter macro/request names in groff mode.
+ } else {
+ $line =~ /^\.(\S+)(\s+(.*))?$/;
+ $command = $1;
+ $args = $3;
+ }
+
+ $command = '' unless ($command);
+ $args = '' unless ($args);
+
+ ######################################################################
+ # user-defined macros
+
+ # If the line calls a user-defined macro, skip it.
+ return if (exists $user_macro{$command});
+
+ # These are all requests supported by groff 1.23.0.
+ my @request = ('ab', 'ad', 'af', 'aln', 'als', 'am', 'am1', 'ami',
+ 'ami1', 'as', 'as1', 'asciify', 'backtrace', 'bd',
+ 'blm', 'box', 'boxa', 'bp', 'br', 'brp', 'break', 'c2',
+ 'cc', 'ce', 'cf', 'cflags', 'ch', 'char', 'chop',
+ 'class', 'close', 'color', 'composite', 'continue',
+ 'cp', 'cs', 'cu', 'da', 'de', 'de1', 'defcolor', 'dei',
+ 'dei1', 'device', 'devicem', 'di', 'do', 'ds', 'ds1',
+ 'dt', 'ec', 'ecr', 'ecs', 'el', 'em', 'eo', 'ev',
+ 'evc', 'ex', 'fam', 'fc', 'fchar', 'fcolor', 'fi',
+ 'fp', 'fschar', 'fspecial', 'ft', 'ftr', 'fzoom',
+ 'gcolor', 'hc', 'hcode', 'hla', 'hlm', 'hpf', 'hpfa',
+ 'hpfcode', 'hw', 'hy', 'hym', 'hys', 'ie', 'if', 'ig',
+ 'in', 'it', 'itc', 'kern', 'lc', 'length', 'linetabs',
+ 'lf', 'lg', 'll', 'lsm', 'ls', 'lt', 'mc', 'mk', 'mso',
+ 'msoquiet', 'na', 'ne', 'nf', 'nh', 'nm', 'nn', 'nop',
+ 'nr', 'nroff', 'ns', 'nx', 'open', 'opena', 'os',
+ 'output', 'pc', 'pev', 'pi', 'pl', 'pm', 'pn', 'pnr',
+ 'po', 'ps', 'psbb', 'pso', 'ptr', 'pvs', 'rchar', 'rd',
+ 'return', 'rfschar', 'rj', 'rm', 'rn', 'rnn', 'rr',
+ 'rs', 'rt', 'schar', 'shc', 'shift', 'sizes', 'so',
+ 'soquiet', 'sp', 'special', 'spreadwarn', 'ss',
+ 'stringdown', 'stringup', 'sty', 'substring', 'sv',
+ 'sy', 'ta', 'tc', 'ti', 'tkf', 'tl', 'tm', 'tm1',
+ 'tmc', 'tr', 'trf', 'trin', 'trnt', 'troff', 'uf',
+ 'ul', 'unformat', 'vpt', 'vs', 'warn', 'warnscale',
+ 'wh', 'while', 'write', 'writec', 'writem');
+
+ # Add user-defined macro names to %user_macro.
+ #
+ # Macros can also be defined with .dei{,1}, ami{,1}, but supporting
+ # that would be a heavy lift for the benefit of users that probably
+ # don't require grog's help. --GBR
+ if ($command =~ /^(de|am)1?$/) {
+ my $name = $args;
+ # Strip off any end macro.
+ $name =~ s/\s+.*$//;
+ # Handle special cases of macros starting with '[' or ']'.
+ if ($name =~ /^[][]/) {
+ delete $preprocessor_for_macro{'['};
+ }
+ # XXX: If the macro name shadows a standard macro name, maybe we
+ # should delete the latter from our lists and hashes. This might
+ # depend on whether the document is trying to remain compatible
+ # with an existing interface, or simply colliding with names they
+ # don't care about (consider a raw roff document that defines 'PP').
+ # --GBR
+ $user_macro{$name} = 0 unless (exists $user_macro{$name});
+ return;
+ }
+
+ # XXX: Handle .rm as well?
+
+ # Ignore all other requests. Again, macro names can contain Perl
+ # regex metacharacters, so be careful.
+ return if (grep(/^\Q$command\E$/, @request));
+ # What remains must be a macro name.
+ my $macro = $command;
+
+ $have_seen_first_macro_call = 1;
+ $score{$macro}++;
+
+
+ ######################################################################
+ # macro package (tmac)
+ ######################################################################
+
+ # man and ms share too many macro names for the following approach to
+ # be fruitful for many documents; see &infer_man_or_ms_package.
+ #
+ # We can put one thumb on the scale, however.
+ if ((!$have_seen_first_macro_call) && ($macro eq 'TH')) {
+ # TH as the first call in a document screams man(7).
+ $man_score += 100;
+ }
+
+ ##########
+ # mdoc
+ if ($macro =~ /^Dd$/) {
+ &push_main_package('doc');
+ return;
+ }
+
+ ##########
+ # old mdoc
+ if ($macro =~ /^(Tp|Dp|De|Cx|Cl)$/) {
+ &push_main_package('doc-old');
+ return;
+ }
+
+ ##########
+ # me
+
+ if ($macro =~ /^(
+ [ilnp]p|
+ n[12]|
+ sh
+ )$/x) {
+ &push_main_package('e');
+ return;
+ }
+
+
+ #############
+ # mm and mmse
+
+ if ($macro =~ /^(
+ H|
+ MULB|
+ LO|
+ LT|
+ NCOL|
+ PH|
+ SA
+ )$/x) {
+ if ($macro =~ /^LO$/) {
+ if ( $args =~ /^(DNAMN|MDAT|BIL|KOMP|DBET|BET|SIDOR)/ ) {
+ &push_main_package('mse');
+ return;
+ }
+ } elsif ($macro =~ /^LT$/) {
+ if ( $args =~ /^(SVV|SVH)/ ) {
+ &push_main_package('mse');
+ return;
+ }
+ }
+ &push_main_package('m');
+ return;
+ }
+
+ ##########
+ # mom
+
+ if ($macro =~ /^(
+ ALD|
+ AUTHOR|
+ CHAPTER_TITLE|
+ CHAPTER|
+ COLLATE|
+ DOCHEADER|
+ DOCTITLE|
+ DOCTYPE|
+ DOC_COVER|
+ FAMILY|
+ FAM|
+ FT|
+ LEFT|
+ LL|
+ LS|
+ NEWPAGE|
+ NO_TOC_ENTRY|
+ PAGENUMBER|
+ PAGE|
+ PAGINATION|
+ PAPER|
+ PRINTSTYLE|
+ PT_SIZE|
+ START|
+ TITLE|
+ TOC_AFTER_HERE
+ TOC|
+ T_MARGIN|
+ )$/x) {
+ &push_main_package('om');
+ return;
+ }
+} # do_line()
+
+my @preprocessor = ();
+
+
+sub infer_preprocessors {
+ my %option_for_preprocessor = (
+ 'eqn', '-e',
+ 'grap', '-G',
+ 'grn', '-g',
+ 'pic', '-p',
+ 'refer', '-R',
+ #'soelim', '-s', # Can't be inferred this way; see grog man page.
+ 'tbl', '-t',
+ 'chem', '-j'
+ );
+
+ # Use a temporary list we can sort later. We want the options to show
+ # up in a stable order for testing purposes instead of the order their
+ # macros turn up in the input. groff doesn't care about the order.
+ my @opt = ();
+
+ foreach my $preproc (@inferred_preprocessor) {
+ my $preproc_option = $option_for_preprocessor{$preproc};
+
+ if ($preproc_option) {
+ push @opt, $preproc_option;
+ } else {
+ push @preprocessor, $preproc;
+ }
+ }
+ push @command, sort @opt;
+} # infer_preprocessors()
+
+
+# Return true (1) if either the man or ms package is inferred.
+sub infer_man_or_ms_package {
+ my @macro_ms = ('RP', 'TL', 'AU', 'AI', 'DA', 'ND', 'AB', 'AE',
+ 'QP', 'QS', 'QE', 'XP',
+ 'NH',
+ 'R',
+ 'CW',
+ 'BX', 'UL', 'LG', 'NL',
+ 'KS', 'KF', 'KE', 'B1', 'B2',
+ 'DS', 'DE', 'LD', 'ID', 'BD', 'CD', 'RD',
+ 'FS', 'FE',
+ 'OH', 'OF', 'EH', 'EF', 'P1',
+ 'TA', '1C', '2C', 'MC',
+ 'XS', 'XE', 'XA', 'TC', 'PX',
+ 'IX', 'SG');
+
+ my @macro_man = ('BR', 'IB', 'IR', 'RB', 'RI', 'P', 'TH', 'TP', 'SS',
+ 'HP', 'PD',
+ 'AT', 'UC',
+ 'SB',
+ 'EE', 'EX',
+ 'OP',
+ 'MT', 'ME', 'SY', 'YS', 'TQ', 'UR', 'UE');
+
+ my @macro_man_or_ms = ('B', 'I', 'BI',
+ 'DT',
+ 'RS', 'RE',
+ 'SH',
+ 'SM',
+ 'IP', 'LP', 'PP');
+
+ for my $key (@macro_man_or_ms, @macro_man, @macro_ms) {
+ $score{$key} = 0 unless exists $score{$key};
+ }
+
+ # Compute a score for each package by counting occurrences of their
+ # characteristic macros.
+ foreach my $key (@macro_man_or_ms) {
+ $man_score += $score{$key};
+ $ms_score += $score{$key};
+ }
+
+ foreach my $key (@macro_man) {
+ $man_score += $score{$key};
+ }
+
+ foreach my $key (@macro_ms) {
+ $ms_score += $score{$key};
+ }
+
+ if (!$ms_score && !$man_score) {
+ # The input may be a "raw" roff document; this is not a problem,
+ # but it does mean no package was inferred.
+ return 0;
+ } elsif ($ms_score == $man_score) {
+ # If there was no TH call, it's not a (valid) man(7) document.
+ if (!$score{'TH'}) {
+ &push_main_package('s');
+ } else {
+ &warn("document ambiguous; disambiguate with -man or -ms option");
+ $had_inference_problem = 1;
+ }
+ return 0;
+ } elsif ($ms_score > $man_score) {
+ &push_main_package('s');
+ } else {
+ &push_main_package('an');
+ }
+
+ return 1;
+} # infer_man_or_ms_package()
+
+
+sub construct_command {
+ my @main_package = ('an', 'doc', 'doc-old', 'e', 'm', 'om', 's');
+ my $file_args_included; # file args now only at 1st preproc
+ unshift @command, 'groff';
+ if (@preprocessor) {
+ my @progs;
+ $progs[0] = shift @preprocessor;
+ push(@progs, @input_file);
+ for (@preprocessor) {
+ push @progs, '|';
+ push @progs, $_;
+ }
+ push @progs, '|';
+ unshift @command, @progs;
+ $file_args_included = 1;
+ } else {
+ $file_args_included = 0;
+ }
+
+ foreach (@command) {
+ next unless /\s/;
+ # when one argument has several words, use accents
+ $_ = "'" . $_ . "'";
+ }
+
+ my $have_ambiguous_main_package = 0;
+ my $inferred_main_package_count = scalar @inferred_main_package;
+
+ # Did we infer multiple full-service packages?
+ if ($inferred_main_package_count > 1) {
+ $have_ambiguous_main_package = 1;
+ # For each one the user explicitly requested...
+ for my $pkg (@requested_package) {
+ # ...did it resolve the ambiguity for us?
+ if (grep(/$pkg/, @inferred_main_package)) {
+ @inferred_main_package = ($pkg);
+ $have_ambiguous_main_package = 0;
+ last;
+ }
+ }
+ } elsif ($inferred_main_package_count == 1) {
+ $main_package = shift @inferred_main_package;
+ }
+
+ if ($have_ambiguous_main_package) {
+ # TODO: Alphabetical is probably not the best ordering here. We
+ # should tally up scores on a per-package basis generally, not just
+ # for an and s.
+ for my $pkg (@main_package) {
+ if (grep(/$pkg/, @inferred_main_package)) {
+ $main_package = $pkg;
+ &warn("document ambiguous (choosing '$main_package'"
+ . " from '@inferred_main_package'); disambiguate with -m"
+ . " option");
+ $had_inference_problem = 1;
+ last;
+ }
+ }
+ }
+
+ # If a full-service package was explicitly requested, warn if the
+ # inference differs from the request. This also ensures that all -m
+ # arguments are placed in the same order that the user gave them;
+ # caveat dictator.
+ my @auxiliary_package_argument = ();
+ for my $pkg (@requested_package) {
+ my $is_auxiliary_package = 1;
+ if (grep(/$pkg/, @main_package)) {
+ $is_auxiliary_package = 0;
+ if ($pkg ne $main_package) {
+ &warn("overriding inferred package '$main_package'"
+ . " with requested package '$pkg'");
+ $main_package = $pkg;
+ }
+ }
+ if ($is_auxiliary_package) {
+ push @auxiliary_package_argument, "-m" . $pkg;
+ }
+ }
+
+ push @command, '-m' . $main_package if ($main_package);
+ push @command, @auxiliary_package_argument;
+ push @command, @input_file unless ($file_args_included);
+
+ #########
+ # execute the 'groff' command here with option '--run'
+ if ( $do_run ) { # with --run
+ print STDERR "@command\n";
+ my $cmd = join ' ', @command;
+ system($cmd);
+ } else {
+ print "@command\n";
+ }
+} # construct_command()
+
+
+sub usage {
+ my $stream = *STDOUT;
+ my $had_error = shift;
+ $stream = *STDERR if $had_error;
+ my $grog = $program_name;
+ print $stream "usage: $grog [--ligatures] [--run]" .
+ " [groff-option ...] [--] [file ...]\n" .
+ "usage: $grog {-v | --version}\n" .
+ "usage: $grog {-h | --help}\n";
+ unless ($had_error) {
+ print $stream "\n" .
+"Read each roff(7) input FILE and attempt to infer an appropriate\n" .
+"groff(1) command to format it. See the grog(1) manual page.\n";
+ }
+ exit $had_error;
+}
+
+
+sub version {
+ print "GNU $program_name (groff) $groff_version\n";
+ exit 0;
+} # version()
+
+
+# initialize
+
+my $in_unbuilt_source_tree = 0;
+{
+ my $at = '@';
+ $in_unbuilt_source_tree = 1 if ('@VERSION@' eq "${at}VERSION${at}");
+}
+
+$groff_version = '@VERSION@' unless ($in_unbuilt_source_tree);
+
+&process_arguments();
+&process_input();
+
+if ($have_any_valid_arguments) {
+ &infer_preprocessors();
+ &infer_man_or_ms_package() if (scalar @inferred_main_package != 1);
+ &construct_command();
+}
+
+exit 2 if ($had_processing_problem);
+exit 1 if ($had_inference_problem);
+exit 0;
+
+# Local Variables:
+# fill-column: 72
+# mode: CPerl
+# End:
+# vim: set cindent noexpandtab shiftwidth=2 softtabstop=2 textwidth=72:
diff --git a/src/utils/grog/tests/PF-does-not-start-pic-region.sh b/src/utils/grog/tests/PF-does-not-start-pic-region.sh
new file mode 100755
index 0000000..d3b871f
--- /dev/null
+++ b/src/utils/grog/tests/PF-does-not-start-pic-region.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+grog="${abs_top_builddir:-.}/grog"
+
+# Regression test Savannah #60772.
+#
+# .PF does not _start_ a pic(1) region; it ends one.
+
+DOC='.PF
+.PE'
+
+echo "$DOC" | "$grog" \
+ | grep -Fqx 'groff -'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/utils/grog/tests/avoid-refer-fakeout.sh b/src/utils/grog/tests/avoid-refer-fakeout.sh
new file mode 100755
index 0000000..f163bed
--- /dev/null
+++ b/src/utils/grog/tests/avoid-refer-fakeout.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+grog="${abs_top_builddir:-.}/grog"
+
+# Regression-test Savannah #61520.
+#
+# Don't be fooled by documents (like xterm's ctlseqs.ms) that define
+# macros with names that start with '[' or ']'.
+
+input=".de []
+..
+.[] foo"
+
+echo "$input" | "$grog" | grep -Fqx 'groff -'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/utils/grog/tests/foo.man b/src/utils/grog/tests/foo.man
new file mode 100644
index 0000000..28e9fe6
--- /dev/null
+++ b/src/utils/grog/tests/foo.man
@@ -0,0 +1,146 @@
+.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+. ds C`
+. ds C'
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is >0, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.\"
+.\" Avoid warning from groff about undefined register 'F'.
+.de IX
+..
+.nr rF 0
+.if \n(.g .if rF .nr rF 1
+.if (\n(rF:(\n(.g==0)) \{\
+. if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. if !\nF==2 \{\
+. nr % 0
+. nr F 2
+. \}
+. \}
+.\}
+.rr rF
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "FOO 1"
+.TH FOO 1 "2021-06-30" "perl v5.28.1" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "Name"
+.IX Header "Name"
+foo \- a frobnicator
+.SH "Description"
+.IX Header "Description"
+This is my program.
diff --git a/src/utils/grog/tests/preserve-groff-options.sh b/src/utils/grog/tests/preserve-groff-options.sh
new file mode 100755
index 0000000..3290798
--- /dev/null
+++ b/src/utils/grog/tests/preserve-groff-options.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+grog="${abs_top_builddir:-.}/grog"
+
+# Regression test Savannah #57873.
+#
+# Don't mangle groff options.
+
+echo | "$grog" -ww -fN -P-pa5 -ra5=0 \
+ | grep -Fqx 'groff -ww -fN -P-pa5 -ra5=0 -'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/utils/grog/tests/recognize-perl-pod.sh b/src/utils/grog/tests/recognize-perl-pod.sh
new file mode 100755
index 0000000..bc13ece
--- /dev/null
+++ b/src/utils/grog/tests/recognize-perl-pod.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+grog="${abs_top_builddir:-.}/grog"
+doc="${abs_top_srcdir:-..}/src/utils/grog/tests/foo.man"
+
+# Regression test Savannah #59622.
+#
+# Recognize the strongly-accented dialect of man(7) produced by
+# pod2man(1).
+
+"$grog" "$doc" | grep '^groff -man .*/src/utils/grog/tests/foo\.man'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/utils/grog/tests/smoke-test.sh b/src/utils/grog/tests/smoke-test.sh
new file mode 100755
index 0000000..2da1fc4
--- /dev/null
+++ b/src/utils/grog/tests/smoke-test.sh
@@ -0,0 +1,153 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+set -e
+
+grog="${abs_top_builddir:-.}/grog"
+src="${abs_top_srcdir:-..}"
+
+doc=src/preproc/eqn/neqn.1
+echo "testing simple man(7) page $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -man '"$doc"
+
+doc=src/preproc/tbl/tbl.1
+echo "testing tbl(1)-using man(7) page $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -t -man '"$doc"
+
+doc=man/groff_diff.7
+echo "testing eqn(1)-using man(7) page $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -e -man '"$doc"
+
+# BUG: grog doesn't yet handle .if, .ie, .while.
+#doc=src/preproc/soelim/soelim.1
+#echo "testing pic(1)-using man(7) page $doc" >&2
+#"$grog" "$doc" | \
+# grep -Fqx 'groff -p -man '"$doc"
+
+doc=tmac/groff_mdoc.7
+echo "testing tbl(1)-using mdoc(7) page $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -t -mdoc '"$doc"
+
+doc=$src/doc/meintro.me.in
+echo "testing me(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -me '"$doc"
+
+doc=$src/doc/meintro_fr.me.in
+echo "testing tbl(1)-using me(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -t -me '"$doc"
+
+doc=$src/doc/meref.me.in
+echo "testing me(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -me '"$doc"
+
+doc=$src/doc/grnexmpl.me
+echo "testing grn(1)- and eqn(1)-using me(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -e -g -me '"$doc"
+
+doc=$src/contrib/mm/examples/letter.mm
+echo "testing mm(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mm '"$doc"
+
+doc=$src/contrib/mom/examples/copyright-chapter.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mom '"$doc"
+
+doc=$src/contrib/mom/examples/copyright-default.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mom '"$doc"
+
+doc=$src/contrib/mom/examples/letter.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mom '"$doc"
+
+doc=$src/contrib/mom/examples/mom-pdf.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mom '"$doc"
+
+doc=$src/contrib/mom/examples/mon_premier_doc.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mom '"$doc"
+
+doc=$src/contrib/mom/examples/sample_docs.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mom '"$doc"
+
+doc=$src/contrib/mom/examples/slide-demo.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -e -p -t -mom '"$doc"
+
+doc=$src/contrib/mom/examples/typesetting.mom
+echo "testing mom(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -mom '"$doc"
+
+doc=$src/contrib/pdfmark/cover.ms
+echo "testing ms(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -ms '"$doc"
+
+doc=$src/contrib/pdfmark/pdfmark.ms
+echo "testing ms(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -ms '"$doc"
+
+doc=$src/doc/ms.ms
+echo "testing eqn(1)- and tbl(1)-using ms(7) document $doc" >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -e -t -ms '"$doc"
+
+doc=$src/doc/pic.ms
+echo "testing tbl(1)-, eqn(1)-, and pic(1)-using ms(7) document $doc" \
+ >&2
+"$grog" "$doc" | \
+ grep -Fqx 'groff -e -p -t -ms '"$doc"
+
+doc=$src/doc/webpage.ms
+echo "testing ms(7) document $doc" >&2
+# BUG: Should detect -mwww (and -mpspic?) too.
+"$grog" "$doc" | \
+ grep -Fqx 'groff -ms '"$doc"
+
+# Test manual specification of auxiliary macro packages.
+echo "testing ms(7) document $doc with '-m www' option" >&2
+"$grog" "$doc" -m www | \
+ grep -Fqx 'groff -ms -mwww '"$doc"
+
+echo "testing ms(7) document $doc with '-mwww' option" >&2
+"$grog" "$doc" -mwww | \
+ grep -Fqx 'groff -ms -mwww '"$doc"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/utils/hpftodit/hpftodit.1.man b/src/utils/hpftodit/hpftodit.1.man
new file mode 100644
index 0000000..12e3af7
--- /dev/null
+++ b/src/utils/hpftodit/hpftodit.1.man
@@ -0,0 +1,476 @@
+.TH hpftodit @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+hpftodit \- create font description files for use with
+.I groff
+and
+.I grolj4
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1994-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_hpftodit_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY hpftodit
+.RB [ \-aqs ]
+.RB [ \-i\~\c
+.IR n ]
+.I tfm-file
+.I map-file
+.I font-description
+.YS
+.
+.
+.SY hpftodit
+.B \-d
+.I tfm-file
+.RI [ map-file ]
+.YS
+.
+.
+.SY hpftodit
+.B \-\-help
+.YS
+.
+.
+.SY hpftodit
+.B \-v
+.
+.SY hpftodit
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I hpftodit
+creates a font description file for use with a Hewlett-Packard
+LaserJet\~4-\%series
+(or newer)
+printer with the
+.MR grolj4 @MAN1EXT@
+output driver of
+.MR groff @MAN1EXT@ ,
+using data from an HP tagged font metric (TFM) file.
+.
+.I tfm-file
+is the name of the font's TFM file;
+Intellifont and TrueType TFM files are supported,
+but symbol set TFM files are not.
+.
+.I map-file
+is a file giving the
+.I groff
+special character identifiers for glyphs in the font;
+this file should consist of a sequence of lines of the form
+.RS
+.EX
+.IR "m u c1 c2 " "\&.\|.\|.\& [#" " comment" "]"
+.EE
+.RE
+where
+.I m
+is a decimal integer giving the glyph's MSL
+(Master Symbol List)
+number,
+.I u
+is a hexadecimal integer giving its Unicode character code,
+and
+.IR c1 ,
+.IR c2 ", .\|.\|."
+are its
+.I groff
+glyph names
+(see
+.MR groff_char @MAN7EXT@
+for a list).
+.
+The values can be separated by any number of spaces and/or tabs.
+.
+The Unicode value must use uppercase hexadecimal digits A\^\[en]\^F,
+and must lack a leading
+.RB \[lq] 0x \[rq],
+.RB \[lq] u \[rq],
+or
+.RB \[lq] U+ \[rq].
+.
+Unicode values corresponding to composite glyphs are decomposed;
+that is
+.RB \[lq] u00C0 \[rq]
+becomes
+.RB \[lq] u0041_0300 \[rq].
+.
+A glyph without a
+.I groff
+special character identifier may be named
+.BI u XXXX
+if the glyph corresponds to a Unicode value,
+or as an unnamed glyph
+.RB \[lq] \-\-\- \[rq].
+.
+If the given Unicode value is in the Private Use Area (PUA)
+(0xE000\^\[en]\^0xF8FF),
+the glyph is included as an unnamed glyph.
+.
+Refer to
+.MR groff_diff @MAN1EXT@
+for additional information about unnamed glyphs and how to access them.
+.
+.
+.P
+Blank lines and lines beginning with
+.RB \[lq] # \[rq]
+are ignored.
+.
+A
+.RB \[lq] # \[rq]
+following one or more
+.I groff
+names begins a comment.
+.
+Because
+.RB \[lq] # \[rq]
+is a valid
+.I groff
+name,
+it must appear first in a list of
+.I groff
+names if a comment is included,
+as in
+.
+.RS
+.EX
+3 0023 # # number sign
+.EE
+.RE
+.
+or
+.
+.RS
+.EX
+3 0023 # sh # number sign
+.EE
+.RE
+.
+whereas in
+.
+.RS
+.EX
+3 0023 sh # # number sign
+.EE
+.RE
+.
+the first
+.RB \[lq] # \[rq]
+is interpreted as the beginning of the comment.
+.
+.
+.P
+Output is written in
+.MR groff_font @MAN5EXT@
+format to
+.I font-description,
+a file named for the intended
+.I groff
+font name;
+if this operand is
+.RB \[lq] \- \[rq],
+the font description is written to the standard output stream.
+.
+.
+.LP
+If the
+.B \-i
+option is used,
+.I hpftodit
+automatically will generate an italic correction,
+a left italic correction,
+and a subscript correction for each glyph
+(the significance of these parameters is explained in
+.MR groff_font @MAN5EXT@ ).
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-a
+Include glyphs in the TFM file that are not included in
+.IR map-file .
+.
+A glyph with corresponding Unicode value is given the name
+.RI u XXXX ;
+a glyph without a Unicode value is included as an unnamed glyph
+\[lq]\-\^\-\^\-\[rq].
+.
+A glyph with a Unicode value in the Private Use Area
+(0xE000\^\[en]\^0xF8FF)
+is also included as an unnamed glyph.
+.
+.
+.IP
+This option provides a simple means of adding Unicode-named and
+unnamed glyphs to a font without including them in the map file,
+but it affords little control over which glyphs are placed in a regular
+font and which are placed in a special font.
+.
+The presence or absence of the
+.B \-s
+option has some effect on which glyphs are included:
+without it,
+only the \[lq]text\[rq] symbol sets are searched for matching glyphs;
+with it,
+only the \[lq]mathematical\[rq] symbol sets are searched.
+.
+Nonetheless,
+restricting the symbol sets searched isn't very selective\[em]many
+glyphs are placed in both regular and special fonts.
+.
+Normally,
+.B \-a
+should be used only as a last resort.
+.
+.
+.TP
+.B \-d
+Dump information about the TFM file to the standard output stream;
+use this to ensure that a TFM file is a proper match for a font,
+and that its contents are suitable.
+.
+The information includes the values of important TFM tags and a listing
+(by MSL number for Intellifont TFM files or by Unicode value for
+TrueType TFM files)
+of the glyphs included in the TFM file.
+.
+The unit of measure \[lq]DU\[rq] for some tags indicates design units;
+there are 8782\~design units per em for Intellifont fonts,
+and 2048\~design units per em for TrueType fonts.
+.
+Note that the accessibility of a glyph depends on its inclusion in a
+symbol set;
+some TFM files list many glyphs but only a few symbol sets.
+.
+.
+.IP
+The glyph listing includes the glyph index within the TFM file,
+the MSL or Unicode value,
+and the symbol set and character code that will be used to print the
+glyph.
+.
+If
+.I map-file
+is given,
+.I groff
+names are given for matching glyphs.
+.
+If only the glyph index and MSL or Unicode value are given,
+the glyph does not appear in any supported symbol set and cannot be
+printed.
+.
+.
+.IP
+With the
+.B \-d
+option,
+.I map-file
+is optional,
+and
+.I output-font
+is ignored if given.
+.
+.
+.TP
+.BI \-i\~ n
+Generate an italic correction for each glyph so that its width plus its
+italic correction is equal to
+.I n
+thousandths of an em plus the amount by which the right edge of the
+glyphs's bounding box is to the right of its origin.
+.
+If a negative italic correction would result,
+use a zero italic correction instead.
+.
+.
+.IP
+Also generate a subscript correction equal to the product of the tangent
+of the slant of the font and four fifths of the x-height of the font.
+.
+If a subscript correction greater than the italic correction would
+result,
+use a subscript correction equal to the italic correction instead.
+.
+.
+.IP
+Also generate a left italic correction for each glyph equal to
+.I n
+thousandths of an em plus the amount by which the left edge of the
+glyphs's bounding box is to the left of its origin.
+.
+The left italic correction may be negative.
+.
+.
+.IP
+This option normally is needed only with italic or oblique fonts;
+a value of 50
+(0.05\~em)
+usually is a reasonable choice.
+.
+.
+.TP
+.B \-q
+Suppress warnings about glyphs in the map file that were not found in
+the TFM file.
+.
+Warnings never are given for unnamed glyphs or by glyphs named by their
+Unicode values.
+.
+This option is useful when sending the output of
+.I hpftodit
+to the standard output stream.
+.
+.
+.TP
+.B \-s
+Add the
+.B special
+directive to the font description file,
+affecting the order in which HP symbol sets are searched for each glyph.
+.
+Without this option,
+the \[lq]text\[rq] sets are searched before the \[lq]mathematical\[rq]
+symbol sets.
+.
+With it,
+the search order is reversed.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devlj4/\:DESC
+describes the
+.B lj4
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devlj4/ F
+describes the font known
+.RI as\~ F
+on device
+.BR lj4 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devlj4/\:\%generate/\:\%Makefile
+is a
+.MR make 1
+script that uses
+.MR hpftodit @MAN1EXT@
+to prepare the
+.I groff
+font description files above from HP TFM data;
+in can be used to regenerate them in the event the TFM files are
+updated.
+.
+.
+.TP
+.I @FONTDIR@/\:\%devlj4/\:\%generate/\:\%special\:.awk
+is an
+.MR awk 1
+script that corrects the Intellifont-based height metrics for several
+glyphs in the
+.B S
+(special) font for TrueType CG Times used in the HP LaserJet\~4000 and
+later.
+.
+.
+.TP
+.I @FONTDIR@/\:\%devlj4/\:\%generate/\:\%special\:.map
+.TQ
+.I @FONTDIR@/\:\%devlj4/\:\%generate/\:\%symbol\:.map
+.TQ
+.I @FONTDIR@/\:\%devlj4/\:\%generate/\:text\:.map
+.TQ
+.I @FONTDIR@/\:\%devlj4/\:\%generate/\:\%wingdings.map
+map MSL indices and HP Unicode PUA assignments to
+.I groff
+special character identifiers.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@ ,
+.MR groff_diff @MAN1EXT@ ,
+.MR grolj4 @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_hpftodit_1_man_C]
+.do rr *groff_hpftodit_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/hpftodit/hpftodit.am b/src/utils/hpftodit/hpftodit.am
new file mode 100644
index 0000000..e31e8f5
--- /dev/null
+++ b/src/utils/hpftodit/hpftodit.am
@@ -0,0 +1,34 @@
+# Automake rules for 'src utils hpftodit'
+#
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 'groff' is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/gpl-2.0.html>.
+#
+########################################################################
+
+bin_PROGRAMS += hpftodit
+man1_MANS += src/utils/hpftodit/hpftodit.1
+EXTRA_DIST += src/utils/hpftodit/hpftodit.1.man
+hpftodit_LDADD = libgroff.a $(LIBM) lib/libgnu.a
+hpftodit_SOURCES = \
+ src/utils/hpftodit/hpftodit.cpp \
+ src/utils/hpftodit/hpuni.cpp
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/hpftodit/hpftodit.cpp b/src/utils/hpftodit/hpftodit.cpp
new file mode 100644
index 0000000..4982e19
--- /dev/null
+++ b/src/utils/hpftodit/hpftodit.cpp
@@ -0,0 +1,1465 @@
+/* Copyright (C) 1994-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+TODO
+devise new names for useful characters
+option to specify symbol sets to look in
+put filename in error messages (or fix lib)
+*/
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "posix.h"
+#include "errarg.h"
+#include "error.h"
+#include "cset.h"
+#include "nonposix.h"
+#include "unicode.h"
+
+extern "C" const char *Version_string;
+extern const char *hp_msl_to_unicode_code(const char *);
+
+#define SIZEOF(v) (sizeof(v)/sizeof(v[0]))
+#define equal(a, b) (strcmp(a, b) == 0)
+// only valid if is_uname(c) has returned true
+#define is_decomposed(c) strchr(c, '_')
+
+#define NO 0
+#define YES 1
+
+#define MSL 0
+#define SYMSET 1
+#define UNICODE 2
+
+#define UNNAMED "---"
+
+static double multiplier = 3.0; // make Agfa-based unitwidth an integer
+
+inline
+int scale(int n)
+{
+ return int(n * multiplier + 0.5);
+}
+
+// tags in TFM file
+
+enum tag_type {
+ min_tag = 400,
+ type_tag = 400,
+ copyright_tag = 401,
+ comment_tag = 402,
+ charcode_tag = 403, // MSL for Intellifont, Unicode for TrueType
+ symbol_set_tag = 404,
+ unique_identifier_tag = 405,
+ inches_per_point_tag = 406,
+ nominal_point_size_tag = 407,
+ design_units_per_em_tag = 408,
+ posture_tag = 409,
+ type_structure_tag = 410,
+ stroke_weight_tag = 411,
+ spacing_tag = 412,
+ slant_tag = 413,
+ appearance_width_tag = 414,
+ serif_style_tag = 415,
+ font_name_tag = 417,
+ typeface_source_tag = 418,
+ average_width_tag = 419,
+ max_width_tag = 420,
+ word_spacing_tag = 421,
+ recommended_line_spacing_tag = 422,
+ cap_height_tag = 423,
+ x_height_tag = 424,
+ max_ascent_tag = 425,
+ max_descent_tag = 426,
+ lower_ascent_tag = 427,
+ lower_descent_tag = 428,
+ underscore_depth_tag = 429,
+ underscore_thickness_tag = 430,
+ uppercase_accent_height_tag = 431,
+ lowercase_accent_height_tag = 432,
+ width_tag = 433,
+ vertical_escapement_tag = 434,
+ left_extent_tag = 435,
+ right_extent_tag = 436,
+ ascent_tag = 437,
+ descent_tag = 438,
+ pair_kern_tag = 439,
+ sector_kern_tag = 440,
+ track_kern_tag = 441,
+ typeface_tag = 442,
+ panose_tag = 443,
+ max_tag = 443
+};
+
+const char *tag_name[] = {
+ "Symbol Set",
+ "Font Type" // MSL for Intellifont, Unicode for TrueType
+};
+
+// types in TFM file
+enum {
+ BYTE_TYPE = 1,
+ ASCII_TYPE = 2, // NUL-terminated string
+ USHORT_TYPE = 3,
+ LONG_TYPE = 4, // unused
+ RATIONAL_TYPE = 5, // 8-byte numerator + 8-byte denominator
+ SIGNED_BYTE_TYPE = 16, // unused
+ SIGNED_SHORT_TYPE = 17,
+ SIGNED_LONG_TYPE = 18 // unused
+};
+
+typedef unsigned char byte;
+typedef unsigned short uint16;
+typedef short int16;
+typedef unsigned int uint32;
+
+class File {
+public:
+ File(const char *);
+ void skip(int n);
+ byte get_byte();
+ uint16 get_uint16();
+ uint32 get_uint32();
+ uint32 get_uint32(char *orig);
+ void seek(uint32 n);
+private:
+ unsigned char *buf_;
+ const unsigned char *ptr_;
+ const unsigned char *end_;
+};
+
+struct entry {
+ char present;
+ uint16 type;
+ uint32 count;
+ uint32 value;
+ char orig_value[4];
+ entry() : present(0) { }
+};
+
+struct char_info {
+ uint16 charcode;
+ uint16 width;
+ int16 ascent;
+ int16 descent;
+ int16 left_extent;
+ uint16 right_extent;
+ uint16 symbol_set;
+ unsigned char code;
+};
+
+const uint16 NO_GLYPH = 0xffff;
+const uint16 NO_SYMBOL_SET = 0;
+
+struct name_list {
+ char *name;
+ name_list *next;
+ name_list(const char *s, name_list *p) : name(strsave(s)), next(p) { }
+ ~name_list() { delete[] name; }
+};
+
+struct symbol_set {
+ uint16 select;
+ uint16 index[256];
+};
+
+#define SYMBOL_SET(n, c) ((n) * 32 + ((c) - 64))
+
+uint16 text_symbol_sets[] = {
+ SYMBOL_SET(19, 'U'), // Windows Latin 1 ("ANSI", code page 1252)
+ SYMBOL_SET(9, 'E'), // Windows Latin 2, Code Page 1250
+ SYMBOL_SET(5, 'T'), // Code Page 1254
+ SYMBOL_SET(7, 'J'), // Desktop
+ SYMBOL_SET(6, 'J'), // Microsoft Publishing
+ SYMBOL_SET(0, 'N'), // Latin 1 (subset of 19U,
+ // so we should never get here)
+ SYMBOL_SET(2, 'N'), // Latin 2 (subset of 9E,
+ // so we should never get here)
+ SYMBOL_SET(8, 'U'), // HP Roman 8
+ SYMBOL_SET(10, 'J'), // PS Standard
+ SYMBOL_SET(9, 'U'), // Windows 3.0 "ANSI"
+ SYMBOL_SET(1, 'U'), // U.S. Legal
+
+ SYMBOL_SET(12, 'J'), // MC Text
+ SYMBOL_SET(10, 'U'), // PC Code Page 437
+ SYMBOL_SET(11, 'U'), // PC Code Page 437N
+ SYMBOL_SET(17, 'U'), // PC Code Page 852
+ SYMBOL_SET(12, 'U'), // PC Code Page 850
+ SYMBOL_SET(9, 'T'), // PC Code Page 437T
+ 0
+};
+
+uint16 special_symbol_sets[] = {
+ SYMBOL_SET(8, 'M'), // Math 8
+ SYMBOL_SET(5, 'M'), // PS Math
+ SYMBOL_SET(15, 'U'), // Pi font
+ SYMBOL_SET(13, 'J'), // Ventura International
+ SYMBOL_SET(19, 'M'), // Symbol font
+ SYMBOL_SET(579, 'L'), // Wingdings
+ 0
+};
+
+entry tags[max_tag + 1 - min_tag];
+
+char_info *char_table;
+uint32 nchars = 0;
+
+unsigned int charcode_name_table_size = 0;
+name_list **charcode_name_table = NULL;
+
+symbol_set *symbol_set_table;
+unsigned int n_symbol_sets;
+
+static int debug_flag = NO;
+static int special_flag = NO; // not a special font
+static int italic_flag = NO; // don't add italic correction
+static int italic_sep;
+static int all_flag = NO; // don't include glyphs not in mapfile
+static int quiet_flag = NO; // don't suppress warnings about symbols not found
+
+static char *hp_msl_to_ucode_name(int);
+static char *unicode_to_ucode_name(int);
+static int is_uname(char *);
+static char *show_symset(unsigned int);
+static void usage(FILE *);
+static void usage();
+static const char *xbasename(const char *);
+static void read_tags(File &);
+static int check_type();
+static void check_units(File &, const int, double *, double *);
+static int read_map(const char *, const int);
+static void require_tag(tag_type);
+static void dump_ascii(File &, tag_type);
+static void dump_tags(File &);
+static void dump_symbol_sets(File &);
+static void dump_symbols(int);
+static void output_font_name(File &);
+static void output_spacewidth();
+static void output_pclweight();
+static void output_pclproportional();
+static void read_and_output_pcltypeface(File &);
+static void output_pclstyle();
+static void output_slant();
+static void output_ligatures();
+static void read_symbol_sets(File &);
+static void read_and_output_kernpairs(File &);
+static void output_charset(const int);
+static void read_char_table(File &);
+
+inline
+entry &tag_info(tag_type t)
+{
+ return tags[t - min_tag];
+}
+
+int
+main(int argc, char **argv)
+{
+ program_name = argv[0];
+
+ int opt;
+ int res = 1200; // PCL unit of measure for cursor moves
+ int scalesize = 4; // LaserJet 4 only allows 1/4 point increments
+ int unitwidth = 6350;
+ double ppi; // points per inch
+ double upem; // design units per em
+
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "adsqvi:", long_options, NULL)) != EOF) {
+ switch (opt) {
+ case 'a':
+ all_flag = YES;
+ break;
+ case 'd':
+ debug_flag = YES;
+ break;
+ case 's':
+ special_flag = YES;
+ break;
+ case 'i':
+ italic_flag = YES;
+ italic_sep = atoi(optarg); // design units
+ break;
+ case 'q':
+ quiet_flag = YES; // suppress warnings about symbols not found
+ break;
+ case 'v':
+ printf("GNU hpftodit (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage();
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ if (debug_flag && argc - optind < 1)
+ usage();
+ else if (!debug_flag && argc - optind != 3)
+ usage();
+ File f(argv[optind]);
+ read_tags(f);
+ int tfm_type = check_type();
+ if (debug_flag)
+ dump_tags(f);
+ if (!debug_flag && !read_map(argv[optind + 1], tfm_type))
+ exit(1);
+ else if (debug_flag && argc - optind > 1)
+ read_map(argv[optind + 1], tfm_type);
+ current_filename = NULL;
+ current_lineno = -1; // no line numbers
+ if (!debug_flag && !equal(argv[optind + 2], "-"))
+ if (freopen(argv[optind + 2], "w", stdout) == NULL)
+ fatal("cannot open '%1': %2", argv[optind + 2], strerror(errno));
+ current_filename = argv[optind];
+
+ check_units(f, tfm_type, &ppi, &upem);
+ if (tfm_type == UNICODE) // don't calculate for Intellifont TFMs
+ multiplier = double(res) / upem / ppi * unitwidth / scalesize;
+ if (italic_flag)
+ // convert from thousandths of an em to design units
+ italic_sep = int(italic_sep * upem / 1000 + 0.5);
+
+ read_char_table(f);
+ if (nchars == 0)
+ fatal("no characters");
+
+ if (!debug_flag) {
+ output_font_name(f);
+ printf("name %s\n", xbasename(argv[optind + 2]));
+ if (special_flag)
+ printf("special\n");
+ output_spacewidth();
+ output_slant();
+ read_and_output_pcltypeface(f);
+ output_pclproportional();
+ output_pclweight();
+ output_pclstyle();
+ }
+ read_symbol_sets(f);
+ if (debug_flag)
+ dump_symbols(tfm_type);
+ else {
+ output_ligatures();
+ read_and_output_kernpairs(f);
+ output_charset(tfm_type);
+ }
+ return 0;
+}
+
+static void
+usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-aqs] [-i n] tfm-file map-file output-font\n"
+"usage: %s -d tfm-file [map-file]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name, program_name);
+}
+
+static void
+usage()
+{
+ usage(stderr);
+ exit(1);
+}
+
+File::File(const char *s)
+{
+ // We need to read the file in binary mode because hpftodit relies
+ // on byte counts.
+ int fd = open(s, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ fatal("cannot open '%1': %2", s, strerror(errno));
+ current_filename = s;
+ struct stat sb;
+ if (fstat(fd, &sb) < 0)
+ fatal("cannot stat: %1", strerror(errno));
+ if (!S_ISREG(sb.st_mode))
+ fatal("not a regular file");
+ buf_ = new unsigned char[sb.st_size];
+ long nread = read(fd, buf_, sb.st_size);
+ if (nread < 0)
+ fatal("read error: %1", strerror(errno));
+ if (nread != sb.st_size)
+ fatal("read unexpected number of bytes");
+ ptr_ = buf_;
+ end_ = buf_ + sb.st_size;
+}
+
+void
+File::skip(int n)
+{
+ if (end_ - ptr_ < n)
+ fatal("unexpected end of file");
+ ptr_ += n;
+}
+
+void
+File::seek(uint32 n)
+{
+ if (uint32(end_ - buf_) < n)
+ fatal("unexpected end of file");
+ ptr_ = buf_ + n;
+}
+
+byte
+File::get_byte()
+{
+ if (ptr_ >= end_)
+ fatal("unexpected end of file");
+ return *ptr_++;
+}
+
+uint16
+File::get_uint16()
+{
+ if (end_ - ptr_ < 2)
+ fatal("unexpected end of file");
+ uint16 n = *ptr_++;
+ return n + (*ptr_++ << 8);
+}
+
+uint32
+File::get_uint32()
+{
+ if (end_ - ptr_ < 4)
+ fatal("unexpected end of file");
+ uint32 n = *ptr_++;
+ for (int i = 0; i < 3; i++)
+ n += *ptr_++ << (i + 1)*8;
+ return n;
+}
+
+uint32
+File::get_uint32(char *orig)
+{
+ if (end_ - ptr_ < 4)
+ fatal("unexpected end of file");
+ unsigned char v = *ptr_++;
+ uint32 n = v;
+ orig[0] = v;
+ for (int i = 1; i < 4; i++) {
+ v = *ptr_++;
+ orig[i] = v;
+ n += v << i*8;
+ }
+ return n;
+}
+
+static void
+read_tags(File &f)
+{
+ if (f.get_byte() != 'I' || f.get_byte() != 'I')
+ fatal("not an Intel format TFM file");
+ f.skip(6);
+ uint16 ntags = f.get_uint16();
+ entry dummy;
+ for (uint16 i = 0; i < ntags; i++) {
+ uint16 tag = f.get_uint16();
+ entry *p;
+ if (min_tag <= tag && tag <= max_tag)
+ p = tags + (tag - min_tag);
+ else
+ p = &dummy;
+ p->present = 1;
+ p->type = f.get_uint16();
+ p->count = f.get_uint32();
+ p->value = f.get_uint32(p->orig_value);
+ }
+}
+
+static int
+check_type()
+{
+ require_tag(type_tag);
+ int tfm_type = tag_info(type_tag).value;
+ switch (tfm_type) {
+ case MSL:
+ case UNICODE:
+ break;
+ case SYMSET:
+ fatal("cannot handle Symbol Set TFM files");
+ break;
+ default:
+ fatal("unknown type tag %1", tfm_type);
+ }
+ return tfm_type;
+}
+
+static void
+check_units(File &f, const int tfm_type, double *ppi, double *upem)
+{
+ require_tag(design_units_per_em_tag);
+ f.seek(tag_info(design_units_per_em_tag).value);
+ uint32 num = f.get_uint32();
+ uint32 den = f.get_uint32();
+ if (tfm_type == MSL && (num != 8782 || den != 1))
+ fatal("design units per em != 8782/1");
+ *upem = double(num) / den;
+ require_tag(inches_per_point_tag);
+ f.seek(tag_info(inches_per_point_tag).value);
+ num = f.get_uint32();
+ den = f.get_uint32();
+ if (tfm_type == MSL && (num != 100 || den != 7231))
+ fatal("inches per point not 100/7231");
+ *ppi = double(den) / num;
+}
+
+static void
+require_tag(tag_type t)
+{
+ if (!tag_info(t).present)
+ fatal("tag %1 missing", int(t));
+}
+
+// put a human-readable font name in the file
+static void
+output_font_name(File &f)
+{
+ char *p;
+
+ if (!tag_info(font_name_tag).present)
+ return;
+ int count = tag_info(font_name_tag).count;
+ char *font_name = new char[count];
+
+ if (count > 4) { // value is a file offset to the string
+ f.seek(tag_info(font_name_tag).value);
+ int n = count;
+ p = font_name;
+ while (--n)
+ *p++ = f.get_byte();
+ }
+ else // orig_value contains the string
+ sprintf(font_name, "%.*s",
+ count, tag_info(font_name_tag).orig_value);
+
+ // remove any trailing space
+ p = font_name + count - 1;
+ while (csspace(*--p))
+ ;
+ *(p + 1) = '\0';
+ printf("# %s\n", font_name);
+ delete[] font_name;
+}
+
+static void
+output_spacewidth()
+{
+ require_tag(word_spacing_tag);
+ printf("spacewidth %d\n", scale(tag_info(word_spacing_tag).value));
+}
+
+static void
+read_symbol_sets(File &f)
+{
+ uint32 symbol_set_dir_length = tag_info(symbol_set_tag).count;
+ uint16 *symbol_set_selectors;
+ n_symbol_sets = symbol_set_dir_length/14;
+ symbol_set_table = new symbol_set[n_symbol_sets];
+ unsigned int i;
+
+ for (i = 0; i < nchars; i++)
+ char_table[i].symbol_set = NO_SYMBOL_SET;
+
+ for (i = 0; i < n_symbol_sets; i++) {
+ f.seek(tag_info(symbol_set_tag).value + i*14);
+ (void)f.get_uint32(); // offset to symbol set name
+ uint32 off1 = f.get_uint32(); // offset to selection string
+ uint32 off2 = f.get_uint32(); // offset to symbol set index array
+
+ f.seek(off1);
+ uint16 kind = 0; // HP-GL "Kind 1" symbol set value
+ unsigned int j;
+ for (j = 0; j < off2 - off1; j++) {
+ unsigned char c = f.get_byte();
+ if ('0' <= c && c <= '9') // value
+ kind = kind*10 + (c - '0');
+ else if ('A' <= c && c <= 'Z') // terminator
+ kind = kind*32 + (c - 64);
+ }
+ symbol_set_table[i].select = kind;
+ for (j = 0; j < 256; j++)
+ symbol_set_table[i].index[j] = f.get_uint16();
+ }
+
+ symbol_set_selectors = (special_flag ? special_symbol_sets
+ : text_symbol_sets);
+ for (i = 0; symbol_set_selectors[i] != 0; i++) {
+ unsigned int j;
+ for (j = 0; j < n_symbol_sets; j++)
+ if (symbol_set_table[j].select == symbol_set_selectors[i])
+ break;
+ if (j < n_symbol_sets) {
+ for (int k = 0; k < 256; k++) {
+ uint16 idx = symbol_set_table[j].index[k];
+ if (idx != NO_GLYPH
+ && char_table[idx].symbol_set == NO_SYMBOL_SET) {
+ char_table[idx].symbol_set = symbol_set_table[j].select;
+ char_table[idx].code = k;
+ }
+ }
+ }
+ }
+
+ if (all_flag)
+ return;
+
+ symbol_set_selectors = (special_flag ? text_symbol_sets
+ : special_symbol_sets);
+ for (i = 0; symbol_set_selectors[i] != 0; i++) {
+ unsigned int j;
+ for (j = 0; j < n_symbol_sets; j++)
+ if (symbol_set_table[j].select == symbol_set_selectors[i])
+ break;
+ if (j < n_symbol_sets) {
+ for (int k = 0; k < 256; k++) {
+ uint16 idx = symbol_set_table[j].index[k];
+ if (idx != NO_GLYPH
+ && char_table[idx].symbol_set == NO_SYMBOL_SET) {
+ char_table[idx].symbol_set = symbol_set_table[j].select;
+ char_table[idx].code = k;
+ }
+ }
+ }
+ }
+ return;
+}
+
+static void
+read_char_table(File &f)
+{
+ require_tag(charcode_tag);
+ nchars = tag_info(charcode_tag).count;
+ char_table = new char_info[nchars];
+
+ f.seek(tag_info(charcode_tag).value);
+ uint32 i;
+ for (i = 0; i < nchars; i++)
+ char_table[i].charcode = f.get_uint16();
+
+ require_tag(width_tag);
+ f.seek(tag_info(width_tag).value);
+ for (i = 0; i < nchars; i++)
+ char_table[i].width = f.get_uint16();
+
+ require_tag(ascent_tag);
+ f.seek(tag_info(ascent_tag).value);
+ for (i = 0; i < nchars; i++) {
+ char_table[i].ascent = f.get_uint16();
+ if (char_table[i].ascent < 0)
+ char_table[i].ascent = 0;
+ }
+
+ require_tag(descent_tag);
+ f.seek(tag_info(descent_tag).value);
+ for (i = 0; i < nchars; i++) {
+ char_table[i].descent = f.get_uint16();
+ if (char_table[i].descent > 0)
+ char_table[i].descent = 0;
+ }
+
+ require_tag(left_extent_tag);
+ f.seek(tag_info(left_extent_tag).value);
+ for (i = 0; i < nchars; i++)
+ char_table[i].left_extent = int16(f.get_uint16());
+
+ require_tag(right_extent_tag);
+ f.seek(tag_info(right_extent_tag).value);
+ for (i = 0; i < nchars; i++)
+ char_table[i].right_extent = f.get_uint16();
+}
+
+static void
+output_pclweight()
+{
+ require_tag(stroke_weight_tag);
+ int stroke_weight = tag_info(stroke_weight_tag).value;
+ int pcl_stroke_weight;
+ if (stroke_weight < 128)
+ pcl_stroke_weight = -3;
+ else if (stroke_weight == 128)
+ pcl_stroke_weight = 0;
+ else if (stroke_weight <= 145)
+ pcl_stroke_weight = 1;
+ else if (stroke_weight <= 179)
+ pcl_stroke_weight = 3;
+ else
+ pcl_stroke_weight = 4;
+ printf("pclweight %d\n", pcl_stroke_weight);
+}
+
+static void
+output_pclproportional()
+{
+ require_tag(spacing_tag);
+ printf("pclproportional %d\n", tag_info(spacing_tag).value == 0);
+}
+
+static void
+read_and_output_pcltypeface(File &f)
+{
+ printf("pcltypeface ");
+ require_tag(typeface_tag);
+ if (tag_info(typeface_tag).count > 4) {
+ f.seek(tag_info(typeface_tag).value);
+ for (uint32 i = 0; i < tag_info(typeface_tag).count; i++) {
+ unsigned char c = f.get_byte();
+ if (c == '\0')
+ break;
+ putchar(c);
+ }
+ }
+ else
+ printf("%.4s", tag_info(typeface_tag).orig_value);
+ printf("\n");
+}
+
+static void
+output_pclstyle()
+{
+ unsigned pcl_style = 0;
+ // older tfms don't have the posture tag
+ if (tag_info(posture_tag).present) {
+ if (tag_info(posture_tag).value)
+ pcl_style |= 1;
+ }
+ else {
+ require_tag(slant_tag);
+ if (tag_info(slant_tag).value != 0)
+ pcl_style |= 1;
+ }
+ require_tag(appearance_width_tag);
+ if (tag_info(appearance_width_tag).value < 100) // guess
+ pcl_style |= 4;
+ printf("pclstyle %d\n", pcl_style);
+}
+
+static void
+output_slant()
+{
+ require_tag(slant_tag);
+ int slant = int16(tag_info(slant_tag).value);
+ if (slant != 0)
+ printf("slant %f\n", slant/100.0);
+}
+
+static void
+output_ligatures()
+{
+ // don't use ligatures for fixed space font
+ require_tag(spacing_tag);
+ if (tag_info(spacing_tag).value != 0)
+ return;
+ static const char *ligature_names[] = {
+ "fi", "fl", "ff", "ffi", "ffl"
+ };
+
+ static const char *ligature_chars[] = {
+ "fi", "fl", "ff", "Fi", "Fl"
+ };
+
+ unsigned ligature_mask = 0;
+ unsigned int i;
+ for (i = 0; i < nchars; i++) {
+ uint16 charcode = char_table[i].charcode;
+ if (charcode < charcode_name_table_size
+ && char_table[i].symbol_set != NO_SYMBOL_SET) {
+ for (name_list *p = charcode_name_table[charcode]; p; p = p->next)
+ for (unsigned int j = 0; j < SIZEOF(ligature_chars); j++)
+ if (strcmp(p->name, ligature_chars[j]) == 0) {
+ ligature_mask |= 1 << j;
+ break;
+ }
+ }
+ }
+ if (ligature_mask) {
+ printf("ligatures");
+ for (i = 0; i < SIZEOF(ligature_names); i++)
+ if (ligature_mask & (1 << i))
+ printf(" %s", ligature_names[i]);
+ printf(" 0\n");
+ }
+}
+
+static void
+read_and_output_kernpairs(File &f)
+{
+ if (tag_info(pair_kern_tag).present) {
+ printf("kernpairs\n");
+ f.seek(tag_info(pair_kern_tag).value);
+ uint16 n_pairs = f.get_uint16();
+ for (int i = 0; i < n_pairs; i++) {
+ uint16 i1 = f.get_uint16();
+ uint16 i2 = f.get_uint16();
+ int16 val = int16(f.get_uint16());
+ if (char_table[i1].symbol_set != NO_SYMBOL_SET
+ && char_table[i2].symbol_set != NO_SYMBOL_SET
+ && char_table[i1].charcode < charcode_name_table_size
+ && char_table[i2].charcode < charcode_name_table_size) {
+ for (name_list *p = charcode_name_table[char_table[i1].charcode];
+ p;
+ p = p->next)
+ for (name_list *q = charcode_name_table[char_table[i2].charcode];
+ q;
+ q = q->next)
+ if (!equal(p->name, UNNAMED) && !equal(q->name, UNNAMED))
+ printf("%s %s %d\n", p->name, q->name, scale(val));
+ }
+ }
+ }
+}
+
+static void
+output_charset(const int tfm_type)
+{
+ require_tag(slant_tag);
+ double slant_angle = int16(tag_info(slant_tag).value)*PI/18000.0;
+ double slant = sin(slant_angle)/cos(slant_angle);
+
+ if (italic_flag)
+ require_tag(x_height_tag);
+ require_tag(lower_ascent_tag);
+ require_tag(lower_descent_tag);
+
+ printf("charset\n");
+ unsigned int i;
+ for (i = 0; i < nchars; i++) {
+ uint16 charcode = char_table[i].charcode;
+
+ // the glyph is bound to one of the searched symbol sets
+ if (char_table[i].symbol_set != NO_SYMBOL_SET) {
+ // the character was in the map file
+ if (charcode < charcode_name_table_size && charcode_name_table[charcode])
+ printf("%s", charcode_name_table[charcode]->name);
+ else if (!all_flag)
+ continue;
+ else if (tfm_type == MSL)
+ printf("%s", hp_msl_to_ucode_name(charcode));
+ else
+ printf("%s", unicode_to_ucode_name(charcode));
+
+ printf("\t%d,%d",
+ scale(char_table[i].width), scale(char_table[i].ascent));
+
+ int depth = scale(-char_table[i].descent);
+ if (depth < 0)
+ depth = 0;
+ int italic_correction = 0;
+ int left_italic_correction = 0;
+ int subscript_correction = 0;
+
+ if (italic_flag) {
+ italic_correction = scale(char_table[i].right_extent
+ - char_table[i].width
+ + italic_sep);
+ if (italic_correction < 0)
+ italic_correction = 0;
+ subscript_correction = int((tag_info(x_height_tag).value
+ * slant * .8) + .5);
+ if (subscript_correction > italic_correction)
+ subscript_correction = italic_correction;
+ left_italic_correction = scale(italic_sep
+ - char_table[i].left_extent);
+ }
+
+ if (subscript_correction != 0)
+ printf(",%d,%d,%d,%d",
+ depth, italic_correction, left_italic_correction,
+ subscript_correction);
+ else if (left_italic_correction != 0)
+ printf(",%d,%d,%d", depth, italic_correction, left_italic_correction);
+ else if (italic_correction != 0)
+ printf(",%d,%d", depth, italic_correction);
+ else if (depth != 0)
+ printf(",%d", depth);
+ // This is fairly arbitrary. Fortunately it doesn't much matter.
+ unsigned type = 0;
+ if (char_table[i].ascent > int16(tag_info(lower_ascent_tag).value)*9/10)
+ type |= 2;
+ if (char_table[i].descent < int16(tag_info(lower_descent_tag).value)*9/10)
+ type |= 1;
+ printf("\t%d\t%d", type,
+ char_table[i].symbol_set*256 + char_table[i].code);
+
+ if (tfm_type == UNICODE) {
+ if (charcode >= 0xE000 && charcode <= 0xF8FF)
+ printf("\t-- HP PUA U+%04X", charcode);
+ else
+ printf("\t-- U+%04X", charcode);
+ }
+ else
+ printf("\t-- MSL %4d", charcode);
+ printf(" (%3s %3d)\n",
+ show_symset(char_table[i].symbol_set), char_table[i].code);
+
+ if (charcode < charcode_name_table_size
+ && charcode_name_table[charcode])
+ for (name_list *p = charcode_name_table[charcode]->next;
+ p; p = p->next)
+ printf("%s\t\"\n", p->name);
+ }
+ // warnings about characters in mapfile not found in TFM
+ else if (charcode < charcode_name_table_size
+ && charcode_name_table[charcode]) {
+ char *name = charcode_name_table[charcode]->name;
+ // don't warn about Unicode or unnamed glyphs
+ // that aren't in the TFM file
+ if (tfm_type == UNICODE && !quiet_flag && !equal(name, UNNAMED)
+ && !is_uname(name)) {
+ fprintf(stderr, "%s: warning: symbol U+%04X (%s",
+ program_name, charcode, name);
+ for (name_list *p = charcode_name_table[charcode]->next;
+ p; p = p->next)
+ fprintf(stderr, ", %s", p->name);
+ fprintf(stderr, ") not in any searched symbol set\n");
+ }
+ else if (!quiet_flag && !equal(name, UNNAMED) && !is_uname(name)) {
+ fprintf(stderr, "%s: warning: symbol MSL %d (%s",
+ program_name, charcode, name);
+ for (name_list *p = charcode_name_table[charcode]->next;
+ p; p = p->next)
+ fprintf(stderr, ", %s", p->name);
+ fprintf(stderr, ") not in any searched symbol set\n");
+ }
+ }
+ }
+}
+
+#define em_fract(a) (upem >= 0 ? double(a)/upem : 0)
+
+static void
+dump_tags(File &f)
+{
+ double upem = -1.0;
+
+ printf("TFM tags\n"
+ "\n"
+ "tag# type count value\n"
+ "---------------------\n");
+
+ for (int i = min_tag; i <= max_tag; i++) {
+ enum tag_type t = tag_type(i);
+ if (tag_info(t).present) {
+ printf("%4d %4d %5d", i, tag_info(t).type, tag_info(t).count);
+ switch (tag_info(t).type) {
+ case BYTE_TYPE:
+ case USHORT_TYPE:
+ printf(" %5u", tag_info(t).value);
+ switch (i) {
+ case type_tag:
+ printf(" Font Type ");
+ switch (tag_info(t).value) {
+ case MSL:
+ case SYMSET:
+ printf("(Intellifont)");
+ break;
+ case UNICODE:
+ printf("(TrueType)");
+ }
+ break;
+ case charcode_tag:
+ printf(" Number of Symbols (%u)", tag_info(t).count);
+ break;
+ case symbol_set_tag:
+ printf(" Symbol Sets (%u): ",
+ tag_info(symbol_set_tag).count / 14);
+ dump_symbol_sets(f);
+ break;
+ case type_structure_tag:
+ printf(" Type Structure (%u)", tag_info(t).value);
+ break;
+ case stroke_weight_tag:
+ printf(" Stroke Weight (%u)", tag_info(t).value);
+ break;
+ case spacing_tag:
+ printf(" Spacing ");
+ switch (tag_info(t).value) {
+ case 0:
+ printf("(Proportional)");
+ break;
+ case 1:
+ printf("(Fixed Pitch: %u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ }
+ break;
+ case appearance_width_tag:
+ printf(" Appearance Width (%u)", tag_info(t).value);
+ break;
+ case serif_style_tag:
+ printf(" Serif Style (%u)", tag_info(t).value);
+ break;
+ case posture_tag:
+ printf(" Posture (%s)", tag_info(t).value == 0
+ ? "Upright"
+ : tag_info(t).value == 1
+ ? "Italic"
+ : "Alternate Italic");
+ break;
+ case max_width_tag:
+ printf(" Maximum Width (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case word_spacing_tag:
+ printf(" Interword Spacing (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case recommended_line_spacing_tag:
+ printf(" Recommended Line Spacing (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case x_height_tag:
+ printf(" x-Height (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case cap_height_tag:
+ printf(" Cap Height (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case max_ascent_tag:
+ printf(" Maximum Ascent (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case lower_ascent_tag:
+ printf(" Lowercase Ascent (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case underscore_thickness_tag:
+ printf(" Underscore Thickness (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case uppercase_accent_height_tag:
+ printf(" Uppercase Accent Height (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case lowercase_accent_height_tag:
+ printf(" Lowercase Accent Height (%u DU: %.2f em)", tag_info(t).value,
+ em_fract(tag_info(t).value));
+ break;
+ case width_tag:
+ printf(" Horizontal Escapement array");
+ break;
+ case vertical_escapement_tag:
+ printf(" Vertical Escapement array");
+ break;
+ case right_extent_tag:
+ printf(" Right Extent array");
+ break;
+ case ascent_tag:
+ printf(" Character Ascent array");
+ break;
+ case pair_kern_tag:
+ f.seek(tag_info(t).value);
+ printf(" Kern Pairs (%u)", f.get_uint16());
+ break;
+ case panose_tag:
+ printf(" PANOSE Classification array");
+ break;
+ }
+ break;
+ case SIGNED_SHORT_TYPE:
+ printf(" %5d", int16(tag_info(t).value));
+ switch (i) {
+ case slant_tag:
+ printf(" Slant (%.2f degrees)", double(tag_info(t).value) / 100);
+ break;
+ case max_descent_tag:
+ printf(" Maximum Descent (%d DU: %.2f em)", int16(tag_info(t).value),
+ em_fract(int16(tag_info(t).value)));
+ break;
+ case lower_descent_tag:
+ printf(" Lowercase Descent (%d DU: %.2f em)", int16(tag_info(t).value),
+ em_fract(int16(tag_info(t).value)));
+ break;
+ case underscore_depth_tag:
+ printf(" Underscore Depth (%d DU: %.2f em)", int16(tag_info(t).value),
+ em_fract(int16(tag_info(t).value)));
+ break;
+ case left_extent_tag:
+ printf(" Left Extent array");
+ break;
+ // The type of this tag has changed from SHORT to SIGNED SHORT
+ // in TFM version 1.3.0.
+ case ascent_tag:
+ printf(" Character Ascent array");
+ break;
+ case descent_tag:
+ printf(" Character Descent array");
+ break;
+ }
+ break;
+ case RATIONAL_TYPE:
+ printf(" %5u", tag_info(t).value);
+ switch (i) {
+ case inches_per_point_tag:
+ printf(" Inches per Point");
+ break;
+ case nominal_point_size_tag:
+ printf(" Nominal Point Size");
+ break;
+ case design_units_per_em_tag:
+ printf(" Design Units per Em");
+ break;
+ case average_width_tag:
+ printf(" Average Width");
+ break;
+ }
+ if (tag_info(t).count == 1) {
+ f.seek(tag_info(t).value);
+ uint32 num = f.get_uint32();
+ uint32 den = f.get_uint32();
+ if (i == design_units_per_em_tag)
+ upem = double(num) / den;
+ printf(" (%u/%u = %g)", num, den, double(num)/den);
+ }
+ break;
+ case ASCII_TYPE:
+ printf(" %5u ", tag_info(t).value);
+ switch (i) {
+ case comment_tag:
+ printf("Comment ");
+ break;
+ case copyright_tag:
+ printf("Copyright ");
+ break;
+ case unique_identifier_tag:
+ printf("Unique ID ");
+ break;
+ case font_name_tag:
+ printf("Typeface Name ");
+ break;
+ case typeface_source_tag:
+ printf("Typeface Source ");
+ break;
+ case typeface_tag:
+ printf("PCL Typeface ");
+ break;
+ }
+ dump_ascii(f, t);
+ }
+ putchar('\n');
+ }
+ }
+ putchar('\n');
+}
+#undef em_fract
+
+static void
+dump_ascii(File &f, tag_type t)
+{
+ putchar('"');
+ if (tag_info(t).count > 4) {
+ int count = tag_info(t).count;
+ f.seek(tag_info(t).value);
+ while (--count)
+ printf("%c", f.get_byte());
+ }
+ else
+ printf("%.4s", tag_info(t).orig_value);
+ putchar('"');
+}
+
+static void
+dump_symbol_sets(File &f)
+{
+ uint32 symbol_set_dir_length = tag_info(symbol_set_tag).count;
+ uint32 num_symbol_sets = symbol_set_dir_length / 14;
+
+ for (uint32 i = 0; i < num_symbol_sets; i++) {
+ f.seek(tag_info(symbol_set_tag).value + i * 14);
+ (void)f.get_uint32(); // offset to symbol set name
+ uint32 off1 = f.get_uint32(); // offset to selection string
+ uint32 off2 = f.get_uint32(); // offset to symbol set index array
+ f.seek(off1);
+ for (uint32 j = 0; j < off2 - off1; j++) {
+ unsigned char c = f.get_byte();
+ if ('0' <= c && c <= '9')
+ putchar(c);
+ else if ('A' <= c && c <= 'Z')
+ printf(i < num_symbol_sets - 1 ? "%c," : "%c", c);
+ }
+ }
+}
+
+static void
+dump_symbols(int tfm_type)
+{
+ printf("Symbols:\n"
+ "\n"
+ " glyph id# symbol set name(s)\n"
+ "----------------------------------\n");
+ for (uint32 i = 0; i < nchars; i++) {
+ uint16 charcode = char_table[i].charcode;
+ if (charcode < charcode_name_table_size
+ && charcode_name_table[charcode]) {
+ if (char_table[i].symbol_set != NO_SYMBOL_SET) {
+ printf(tfm_type == UNICODE ? "%4d (U+%04X) (%3s %3d) %s"
+ : "%4d (MSL %4d) (%3s %3d) %s",
+ i, charcode,
+ show_symset(char_table[i].symbol_set),
+ char_table[i].code,
+ charcode_name_table[charcode]->name);
+ for (name_list *p = charcode_name_table[charcode]->next;
+ p; p = p->next)
+ printf(", %s", p->name);
+ putchar('\n');
+ }
+ }
+ else {
+ printf(tfm_type == UNICODE ? "%4d (U+%04X) "
+ : "%4d (MSL %4d) ",
+ i, charcode);
+ if (char_table[i].symbol_set != NO_SYMBOL_SET)
+ printf("(%3s %3d)",
+ show_symset(char_table[i].symbol_set), char_table[i].code);
+ putchar('\n');
+ }
+ }
+ putchar('\n');
+}
+
+static char *
+show_symset(unsigned int symset)
+{
+ // A 64-bit unsigned int produces up to 20 decimal digits.
+ assert(sizeof(unsigned int) <= 8);
+ static char symset_str[22]; // 20 digits + symset char + \0
+ sprintf(symset_str, "%u%c", symset / 32, (symset & 31) + 64);
+ return symset_str;
+}
+
+static char *
+hp_msl_to_ucode_name(int msl)
+{
+ // A 64-bit signed int produces up to 19 decimal digits plus a sign.
+ assert(sizeof(int) <= 8);
+ char codestr[21]; // 19 digits + possible sign + \0
+ sprintf(codestr, "%d", msl);
+ const char *ustr = hp_msl_to_unicode_code(codestr);
+ if (ustr == NULL)
+ ustr = UNNAMED;
+ else {
+ char *nonum;
+ int ucode = int(strtol(ustr, &nonum, 16));
+ // don't allow PUA code points as Unicode names
+ if (ucode >= 0xE000 && ucode <= 0xF8FF)
+ ustr = UNNAMED;
+ }
+ if (!equal(ustr, UNNAMED)) {
+ const char *uname_decomposed = decompose_unicode(ustr);
+ if (uname_decomposed)
+ // 1st char is the number of components
+ ustr = uname_decomposed + 1;
+ }
+ char *value = new char[strlen(ustr) + 1];
+ sprintf(value, equal(ustr, UNNAMED) ? UNNAMED : "u%s", ustr);
+ return value;
+}
+
+static char *
+unicode_to_ucode_name(int ucode)
+{
+ // A 64-bit signed int produces up to 16 hexadecimal digits.
+ assert(sizeof(int) <= 8);
+ const char *ustr;
+ char codestr[17]; // 16 hex digits + \0
+
+ // don't allow PUA code points as Unicode names
+ if (ucode >= 0xE000 && ucode <= 0xF8FF)
+ ustr = UNNAMED;
+ else {
+ sprintf(codestr, "%04X", ucode);
+ ustr = codestr;
+ }
+ if (!equal(ustr, UNNAMED)) {
+ const char *uname_decomposed = decompose_unicode(ustr);
+ if (uname_decomposed)
+ // 1st char is the number of components
+ ustr = uname_decomposed + 1;
+ }
+ char *value = new char[strlen(ustr) + 1];
+ sprintf(value, equal(ustr, UNNAMED) ? UNNAMED : "u%s", ustr);
+ return value;
+}
+
+static int
+is_uname(char *name)
+{
+ size_t i;
+ size_t len = strlen(name);
+ if (len % 5)
+ return 0;
+
+ if (name[0] != 'u')
+ return 0;
+ for (i = 1; i < 4; i++)
+ if (!csxdigit(name[i]))
+ return 0;
+ for (i = 5; i < len; i++)
+ if (i % 5 ? !csxdigit(name[i]) : name[i] != '_')
+ return 0;
+
+ return 1;
+}
+
+static int
+read_map(const char *file, const int tfm_type)
+{
+ errno = 0;
+ FILE *fp = fopen(file, "r");
+ if (!fp) {
+ error("can't open '%1': %2", file, strerror(errno));
+ return 0;
+ }
+ current_filename = file;
+ char buf[512];
+ current_lineno = 0;
+ char *nonum;
+ while (fgets(buf, int(sizeof(buf)), fp)) {
+ current_lineno++;
+ char *ptr = buf;
+ while (csspace(*ptr))
+ ptr++;
+ if (*ptr == '\0' || *ptr == '#')
+ continue;
+ ptr = strtok(ptr, " \n\t");
+ if (!ptr)
+ continue;
+
+ int msl_code = int(strtol(ptr, &nonum, 10));
+ if (*nonum != '\0') {
+ if (csxdigit(*nonum))
+ error("bad MSL map: got hex code (%1)", ptr);
+ else if (ptr == nonum)
+ error("bad MSL map: bad MSL code (%1)", ptr);
+ else
+ error("bad MSL map");
+ fclose(fp);
+ return 0;
+ }
+
+ ptr = strtok(NULL, " \n\t");
+ if (!ptr)
+ continue;
+ int unicode = int(strtol(ptr, &nonum, 16));
+ if (*nonum != '\0') {
+ if (ptr == nonum)
+ error("bad Unicode value (%1)", ptr);
+ else
+ error("bad Unicode map");
+ fclose(fp);
+ return 0;
+ }
+ if (strlen(ptr) != 4) {
+ error("bad Unicode value (%1)", ptr);
+ return 0;
+ }
+
+ int n = tfm_type == MSL ? msl_code : unicode;
+ if (tfm_type == UNICODE && n > 0xFFFF) {
+ // greatest value supported by TFM files
+ error("bad Unicode value (%1): greatest value is 0xFFFF", ptr);
+ fclose(fp);
+ return 0;
+ }
+ else if (n < 0) {
+ error("negative code value (%1)", ptr);
+ fclose(fp);
+ return 0;
+ }
+
+ ptr = strtok(NULL, " \n\t");
+ if (!ptr) { // groff name
+ error("missing name(s)");
+ fclose(fp);
+ return 0;
+ }
+ // leave decomposed Unicode values alone
+ else if (is_uname(ptr) && !is_decomposed(ptr))
+ ptr = unicode_to_ucode_name(strtol(ptr + 1, &nonum, 16));
+
+ if (size_t(n) >= charcode_name_table_size) {
+ size_t old_size = charcode_name_table_size;
+ name_list **old_table = charcode_name_table;
+ charcode_name_table_size = n + 256;
+ charcode_name_table = new name_list *[charcode_name_table_size];
+ if (old_table) {
+ memcpy(charcode_name_table, old_table, old_size*sizeof(name_list *));
+ delete[] old_table;
+ }
+ for (size_t i = old_size; i < charcode_name_table_size; i++)
+ charcode_name_table[i] = NULL;
+ }
+
+ // a '#' that isn't the first groff name begins a comment
+ for (int names = 1; ptr; ptr = strtok(NULL, " \n\t")) {
+ if (names++ > 1 && *ptr == '#')
+ break;
+ charcode_name_table[n] = new name_list(ptr, charcode_name_table[n]);
+ }
+ }
+ fclose(fp);
+ return 1;
+}
+
+static const char *
+xbasename(const char *s)
+{
+ // DIR_SEPS[] are possible directory separator characters, see
+ // nonposix.h. We want the rightmost separator of all possible
+ // ones. Example: d:/foo\\bar.
+ const char *b = strrchr(s, DIR_SEPS[0]), *b1;
+ const char *sep = &DIR_SEPS[1];
+
+ while (*sep)
+ {
+ b1 = strrchr(s, *sep);
+ if (b1 && (!b || b1 > b))
+ b = b1;
+ sep++;
+ }
+ return b ? b + 1 : s;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/hpftodit/hpuni.cpp b/src/utils/hpftodit/hpuni.cpp
new file mode 100644
index 0000000..b3f933f
--- /dev/null
+++ b/src/utils/hpftodit/hpuni.cpp
@@ -0,0 +1,697 @@
+// -*- C++ -*-
+/* Copyright (C) 2003-2020 Free Software Foundation, Inc.
+ Written by Jeff Conrad (jeff_conrad@msn.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+#include "stringclass.h"
+#include "ptable.h"
+
+#include "unicode.h"
+
+struct hp_msl_to_unicode {
+ char *value;
+};
+
+declare_ptable(hp_msl_to_unicode)
+implement_ptable(hp_msl_to_unicode)
+
+PTABLE(hp_msl_to_unicode) hp_msl_to_unicode_table;
+
+struct S {
+ const char *key;
+ const char *value;
+} hp_msl_to_unicode_list[] = {
+ { "1", "0021", }, // Exclamation Mark
+ { "2", "0022", }, // Neutral Double Quote
+ { "3", "0023", }, // Number Sign
+ { "4", "0024", }, // Dollar Sign
+ { "5", "0025", }, // Per Cent Sign
+ { "6", "0026", }, // Ampersand
+ { "8", "2019", }, // Single Close Quote (9)
+ { "9", "0028", }, // Left Parenthesis
+ { "10", "0029", }, // Right Parenthesis
+ { "11", "002A", }, // Asterisk
+ { "12", "002B", }, // Plus Sign
+ { "13", "002C", }, // Comma, or Decimal Separator
+ { "14", "002D", }, // Hyphen
+ { "15", "002E", }, // Period, or Full Stop
+ { "16", "002F", }, // Solidus, or Slash
+ { "17", "0030", }, // Numeral Zero
+ { "18", "0031", }, // Numeral One
+ { "19", "0032", }, // Numeral Two
+ { "20", "0033", }, // Numeral Three
+ { "21", "0034", }, // Numeral Four
+ { "22", "0035", }, // Numeral Five
+ { "23", "0036", }, // Numeral Six
+ { "24", "0037", }, // Numeral Seven
+ { "25", "0038", }, // Numeral Eight
+ { "26", "0039", }, // Numeral Nine
+ { "27", "003A", }, // Colon
+ { "28", "003B", }, // Semicolon
+ { "29", "003C", }, // Less Than Sign
+ { "30", "003D", }, // Equals Sign
+ { "31", "003E", }, // Greater Than Sign
+ { "32", "003F", }, // Question Mark
+ { "33", "0040", }, // Commercial At
+ { "34", "0041", }, // Uppercase A
+ { "35", "0042", }, // Uppercase B
+ { "36", "0043", }, // Uppercase C
+ { "37", "0044", }, // Uppercase D
+ { "38", "0045", }, // Uppercase E
+ { "39", "0046", }, // Uppercase F
+ { "40", "0047", }, // Uppercase G
+ { "41", "0048", }, // Uppercase H
+ { "42", "0049", }, // Uppercase I
+ { "43", "004A", }, // Uppercase J
+ { "44", "004B", }, // Uppercase K
+ { "45", "004C", }, // Uppercase L
+ { "46", "004D", }, // Uppercase M
+ { "47", "004E", }, // Uppercase N
+ { "48", "004F", }, // Uppercase O
+ { "49", "0050", }, // Uppercase P
+ { "50", "0051", }, // Uppercase Q
+ { "51", "0052", }, // Uppercase R
+ { "52", "0053", }, // Uppercase S
+ { "53", "0054", }, // Uppercase T
+ { "54", "0055", }, // Uppercase U
+ { "55", "0056", }, // Uppercase V
+ { "56", "0057", }, // Uppercase W
+ { "57", "0058", }, // Uppercase X
+ { "58", "0059", }, // Uppercase Y
+ { "59", "005A", }, // Uppercase Z
+ { "60", "005B", }, // Left Bracket
+ { "61", "005C", }, // Reverse Solidus, or Backslash
+ { "62", "005D", }, // Right Bracket
+ { "63", "005E", }, // Circumflex, Exponent, or Pointer
+ { "64", "005F", }, // Underline or Underscore Character
+ { "66", "2018", }, // Single Open Quote (6)
+ { "67", "0061", }, // Lowercase A
+ { "68", "0062", }, // Lowercase B
+ { "69", "0063", }, // Lowercase C
+ { "70", "0064", }, // Lowercase D
+ { "71", "0065", }, // Lowercase E
+ { "72", "0066", }, // Lowercase F
+ { "73", "0067", }, // Lowercase G
+ { "74", "0068", }, // Lowercase H
+ { "75", "0069", }, // Lowercase I
+ { "76", "006A", }, // Lowercase J
+ { "77", "006B", }, // Lowercase K
+ { "78", "006C", }, // Lowercase L
+ { "79", "006D", }, // Lowercase M
+ { "80", "006E", }, // Lowercase N
+ { "81", "006F", }, // Lowercase O
+ { "82", "0070", }, // Lowercase P
+ { "83", "0071", }, // Lowercase Q
+ { "84", "0072", }, // Lowercase R
+ { "85", "0073", }, // Lowercase S
+ { "86", "0074", }, // Lowercase T
+ { "87", "0075", }, // Lowercase U
+ { "88", "0076", }, // Lowercase V
+ { "89", "0077", }, // Lowercase W
+ { "90", "0078", }, // Lowercase X
+ { "91", "0079", }, // Lowercase Y
+ { "92", "007A", }, // Lowercase Z
+ { "93", "007B", }, // Left Brace
+ { "94", "007C", }, // Long Vertical Mark
+ { "95", "007D", }, // Right Brace
+ { "96", "007E", }, // One Wavy Line Approximate
+ { "97", "2592", }, // Medium Shading Character
+ { "99", "00C0", }, // Uppercase A Grave
+ { "100", "00C2", }, // Uppercase A Circumflex
+ { "101", "00C8", }, // Uppercase E Grave
+ { "102", "00CA", }, // Uppercase E Circumflex
+ { "103", "00CB", }, // Uppercase E Dieresis
+ { "104", "00CE", }, // Uppercase I Circumflex
+ { "105", "00CF", }, // Uppercase I Dieresis
+ { "106", "00B4", }, // Lowercase Acute Accent (Spacing)
+ { "107", "0060", }, // Lowercase Grave Accent (Spacing)
+ { "108", "02C6", }, // Lowercase Circumflex Accent (Spacing)
+ { "109", "00A8", }, // Lowercase Dieresis Accent (Spacing)
+ { "110", "02DC", }, // Lowercase Tilde Accent (Spacing)
+ { "111", "00D9", }, // Uppercase U Grave
+ { "112", "00DB", }, // Uppercase U Circumflex
+ { "113", "00AF", }, // Overline, or Overscore Character
+ { "114", "00DD", }, // Uppercase Y Acute
+ { "115", "00FD", }, // Lowercase Y Acute
+ { "116", "00B0", }, // Degree Sign
+ { "117", "00C7", }, // Uppercase C Cedilla
+ { "118", "00E7", }, // Lowercase C Cedilla
+ { "119", "00D1", }, // Uppercase N Tilde
+ { "120", "00F1", }, // Lowercase N Tilde
+ { "121", "00A1", }, // Inverted Exclamation
+ { "122", "00BF", }, // Inverted Question Mark
+ { "123", "00A4", }, // Currency Symbol
+ { "124", "00A3", }, // Pound Sterling Sign
+ { "125", "00A5", }, // Yen Sign
+ { "126", "00A7", }, // Section Mark
+ { "127", "0192", }, // Florin Sign
+ { "128", "00A2", }, // Cent Sign
+ { "129", "00E2", }, // Lowercase A Circumflex
+ { "130", "00EA", }, // Lowercase E Circumflex
+ { "131", "00F4", }, // Lowercase O Circumflex
+ { "132", "00FB", }, // Lowercase U Circumflex
+ { "133", "00E1", }, // Lowercase A Acute
+ { "134", "00E9", }, // Lowercase E Acute
+ { "135", "00F3", }, // Lowercase O Acute
+ { "136", "00FA", }, // Lowercase U Acute
+ { "137", "00E0", }, // Lowercase A Grave
+ { "138", "00E8", }, // Lowercase E Grave
+ { "139", "00F2", }, // Lowercase O Grave
+ { "140", "00F9", }, // Lowercase U Grave
+ { "141", "00E4", }, // Lowercase A Dieresis
+ { "142", "00EB", }, // Lowercase E Dieresis
+ { "143", "00F6", }, // Lowercase O Dieresis
+ { "144", "00FC", }, // Lowercase U Dieresis
+ { "145", "00C5", }, // Uppercase A Ring
+ { "146", "00EE", }, // Lowercase I Circumflex
+ { "147", "00D8", }, // Uppercase O Oblique
+ { "148", "00C6", }, // Uppercase AE Diphthong
+ { "149", "00E5", }, // Lowercase A Ring
+ { "150", "00ED", }, // Lowercase I Acute
+ { "151", "00F8", }, // Lowercase O Oblique
+ { "152", "00E6", }, // Lowercase AE Diphthong
+ { "153", "00C4", }, // Uppercase A Dieresis
+ { "154", "00EC", }, // Lowercase I Grave
+ { "155", "00D6", }, // Uppercase O Dieresis
+ { "156", "00DC", }, // Uppercase U Dieresis
+ { "157", "00C9", }, // Uppercase E Acute
+ { "158", "00EF", }, // Lowercase I Dieresis
+ { "159", "00DF", }, // Lowercase Es-zet Ligature
+ { "160", "00D4", }, // Uppercase O Circumflex
+ { "161", "00C1", }, // Uppercase A Acute
+ { "162", "00C3", }, // Uppercase A Tilde
+ { "163", "00E3", }, // Lowercase A Tilde
+ { "164", "00D0", }, // Uppercase Eth
+//{ "164", "0110", }, // Uppercase D-Stroke
+ { "165", "00F0", }, // Lowercase Eth
+ { "166", "00CD", }, // Uppercase I Acute
+ { "167", "00CC", }, // Uppercase I Grave
+ { "168", "00D3", }, // Uppercase O Acute
+ { "169", "00D2", }, // Uppercase O Grave
+ { "170", "00D5", }, // Uppercase O Tilde
+ { "171", "00F5", }, // Lowercase O Tilde
+ { "172", "0160", }, // Uppercase S Hacek
+ { "173", "0161", }, // Lowercase S Hacek
+ { "174", "00DA", }, // Uppercase U Acute
+ { "175", "0178", }, // Uppercase Y Dieresis
+ { "176", "00FF", }, // Lowercase Y Dieresis
+ { "177", "00DE", }, // Uppercase Thorn
+ { "178", "00FE", }, // Lowercase Thorn
+ { "180", "00B5", }, // Lowercase Greek Mu, or Micro
+ { "181", "00B6", }, // Pilcrow, or Paragraph Sign
+ { "182", "00BE", }, // Vulgar Fraction 3/4
+ { "183", "2212", }, // Minus Sign
+ { "184", "00BC", }, // Vulgar Fraction 1/4
+ { "185", "00BD", }, // Vulgar Fraction 1/2
+ { "186", "00AA", }, // Female Ordinal
+ { "187", "00BA", }, // Male Ordinal
+ { "188", "00AB", }, // Left Pointing Double Angle Quote
+ { "189", "25A0", }, // Medium Solid Square Box
+ { "190", "00BB", }, // Right Pointing Double Angle Quote
+ { "191", "00B1", }, // Plus Over Minus Sign
+ { "192", "00A6", }, // Broken Vertical Mark
+ { "193", "00A9", }, // Copyright Sign
+ { "194", "00AC", }, // Not Sign
+ { "195", "00AD", }, // Soft Hyphen
+ { "196", "00AE", }, // Registered Sign
+ { "197", "00B2", }, // Superior Numeral 2
+ { "198", "00B3", }, // Superior Numeral 3
+ { "199", "00B8", }, // Lowercase Cedilla (Spacing)
+ { "200", "00B9", }, // Superior Numeral 1
+ { "201", "00D7", }, // Multiply Sign
+ { "202", "00F7", }, // Divide Sign
+ { "203", "263A", }, // Open Smiling Face
+ { "204", "263B", }, // Solid Smiling Face
+ { "205", "2665", }, // Solid Heart, Card Suit
+ { "206", "2666", }, // Solid Diamond, Card Suit
+ { "207", "2663", }, // Solid Club, Card Suit
+ { "208", "2660", }, // Solid Spade, Card Suit
+ { "209", "25CF", }, // Medium Solid Round Bullet
+ { "210", "25D8", }, // Large Solid square with White Dot
+ { "211", "EFFD", }, // Large Open Round Bullet
+ { "212", "25D9", }, // Large Solid square with White Circle
+ { "213", "2642", }, // Male Symbol
+ { "214", "2640", }, // Female Symbol
+ { "215", "266A", }, // Musical Note
+ { "216", "266B", }, // Pair Of Musical Notes
+ { "217", "263C", }, // Compass, or Eight Pointed Sun
+ { "218", "25BA", }, // Right Solid Arrowhead
+ { "219", "25C4", }, // Left Solid Arrowhead
+ { "220", "2195", }, // Up/Down Arrow
+ { "221", "203C", }, // Double Exclamation Mark
+ { "222", "25AC", }, // Thick Horizontal Mark
+ { "223", "21A8", }, // Up/Down Arrow Baseline
+ { "224", "2191", }, // Up Arrow
+ { "225", "2193", }, // Down Arrow
+ { "226", "2192", }, // Right Arrow
+ { "227", "2190", }, // Left Arrow
+ { "229", "2194", }, // Left/Right Arrow
+ { "230", "25B2", }, // Up Solid Arrowhead
+ { "231", "25BC", }, // Down Solid Arrowhead
+ { "232", "20A7", }, // Pesetas Sign
+ { "233", "2310", }, // Reversed Not Sign
+ { "234", "2591", }, // Light Shading Character
+ { "235", "2593", }, // Dark Shading Character
+ { "236", "2502", }, // Box Draw Line, Vert. 1
+ { "237", "2524", }, // Box Draw Right Tee, Vert. 1 Horiz. 1
+ { "238", "2561", }, // Box Draw Right Tee, Vert. 1 Horiz. 2
+ { "239", "2562", }, // Box Draw Right Tee, Vert. 2 Horiz. 1
+ { "240", "2556", }, // Box Draw Upper Right Corner, Vert. 2 Horiz. 1
+ { "241", "2555", }, // Box Draw Upper Right Corner, Vert. 1 Horiz. 2
+ { "242", "2563", }, // Box Draw Right Tee, Vert. 2 Horiz. 2
+ { "243", "2551", }, // Box Draw Lines, Vert. 2
+ { "244", "2557", }, // Box Draw Upper Right Corner, Vert. 2 Horiz. 2
+ { "245", "255D", }, // Box Draw Lower Right Corner, Vert. 2 Horiz. 2
+ { "246", "255C", }, // Box Draw Lower Right Corner, Vert. 2 Horiz. 1
+ { "247", "255B", }, // Box Draw Lower Right Corner, Vert. 1 Horiz. 2
+ { "248", "2510", }, // Box Draw Upper Right Corner, Vert. 1, Horiz. 1
+ { "249", "2514", }, // Box Draw Lower Left Corner, Vert. 1, Horiz. 1
+ { "250", "2534", }, // Box Draw Bottom Tee, Vert. 1 Horiz. 1
+ { "251", "252C", }, // Box Draw Top Tee, Vert. 1 Horiz. 1
+ { "252", "251C", }, // Box Draw Left Tee, Vert. 1 Horiz. 1
+ { "253", "2500", }, // Box Draw Line, Horiz. 1
+ { "254", "253C", }, // Box Draw Cross, Vert. 1 Horiz. 1
+ { "255", "255E", }, // Box Draw Left Tee, Vert. 1 Horiz. 2
+ { "256", "255F", }, // Box Draw Left Tee, Vert. 2 Horz. 1
+ { "257", "255A", }, // Box Draw Lower Left Corner, Vert. 2 Horiz. 2
+ { "258", "2554", }, // Box Draw Upper Left Corner, Vert. 2 Horiz. 2
+ { "259", "2569", }, // Box Draw Bottom Tee, Vert. 2 Horiz. 2
+ { "260", "2566", }, // Box Draw Top Tee, Vert. 2 Horiz. 2
+ { "261", "2560", }, // Box Draw Left Tee, Vert. 2 Horiz. 2
+ { "262", "2550", }, // Box Draw Lines, Horiz. 2
+ { "263", "256C", }, // Box Draw Cross Open Center, Vert. 2 Horiz. 2
+ { "264", "2567", }, // Box Draw Bottom Tee, Vert. 1 Horiz. 2
+ { "265", "2568", }, // Box Draw Bottom Tee, Vert. 2 Horiz. 1
+ { "266", "2564", }, // Box Draw Top Tee, Vert. 1 Horiz. 2
+ { "267", "2565", }, // Box Draw Top Tee, Vert. 2 Horiz. 1
+ { "268", "2559", }, // Box Draw Lower Left Corner, Vert. 2 Horiz. 1
+ { "269", "2558", }, // Box Draw Lower Left Corner, Vert. 1 Horiz. 2
+ { "270", "2552", }, // Box Draw Upper Left Corner, Vert. 1 Horiz. 2
+ { "271", "2553", }, // Box Draw Upper Left Corner, Vert. 2 Horiz. 1
+ { "272", "256B", }, // Box Draw Cross, Vert. 2 Horiz. 1
+ { "273", "256A", }, // Box Draw Cross, Vert. 1 Horiz. 2
+ { "274", "2518", }, // Box Draw Lower Right Corner, Vert. 1 Horiz. 1
+ { "275", "250C", }, // Box Draw Upper Left Corner, Vert. 1, Horiz. 1
+ { "276", "2588", }, // Solid Full High/Wide
+ { "277", "2584", }, // Bottom Half Solid Rectangle
+ { "278", "258C", }, // Left Half Solid Rectangle
+ { "279", "2590", }, // Right Half Solid Rectangle
+ { "280", "2580", }, // Top Half Solid Rectangle
+ { "290", "2126", }, // Uppercase Greek Omega, or Ohms
+ { "292", "221E", }, // Infinity Symbol
+ { "295", "2229", }, // Set Intersection Symbol
+ { "296", "2261", }, // Exactly Equals Sign
+ { "297", "2265", }, // Greater Than or Equal Sign
+ { "298", "2264", }, // Less Than or Equal Sign
+ { "299", "2320", }, // Top Integral
+ { "300", "2321", }, // Bottom Integral
+ { "301", "2248", }, // Two Wavy Line Approximate Sign
+//{ "302", "00B7", }, // Middle Dot, or Centered Period (see 2219)
+//{ "302", "2219", }, // Centered Period, Middle Dot
+ { "302", "2219", }, // Math Dot, Centered Period
+ { "303", "221A", }, // Radical Symbol, Standalone Diagonal
+ { "305", "25AA", }, // Small Solid Square Box
+ { "306", "013F", }, // Uppercase L-Dot
+ { "307", "0140", }, // Lowercase L-Dot
+ { "308", "2113", }, // Litre Symbol
+ { "309", "0149", }, // Lowercase Apostrophe-N
+ { "310", "2032", }, // Prime, Minutes, or Feet Symbol
+ { "311", "2033", }, // Double Prime, Seconds, or Inches Symbol
+ { "312", "2020", }, // Dagger Symbol
+ { "313", "2122", }, // Trademark Sign
+ { "314", "2017", }, // Double Underline Character
+ { "315", "02C7", }, // Lowercase Hacek Accent (Spacing)
+ { "316", "02DA", }, // Lowercase Ring Accent (Spacing)
+ { "317", "EFF9", }, // Uppercase Acute Accent (Spacing)
+ { "318", "EFF8", }, // Uppercase Grave Accent (Spacing)
+ { "319", "EFF7", }, // Uppercase Circumflex Accent (Spacing)
+ { "320", "EFF6", }, // Uppercase Dieresis Accent (Spacing)
+ { "321", "EFF5", }, // Uppercase Tilde Accent (Spacing)
+ { "322", "EFF4", }, // Uppercase Hacek Accent (Spacing)
+ { "323", "EFF3", }, // Uppercase Ring Accent (Spacing)
+ { "324", "2215", }, // Vulgar Fraction Bar
+ { "325", "2014", }, // Em Dash
+ { "326", "2013", }, // En Dash
+ { "327", "2021", }, // Double Dagger Symbol
+ { "328", "0131", }, // Lowercase Undotted I
+ { "329", "0027", }, // Neutral Single Quote
+ { "330", "EFF2", }, // Uppercase Cedilla (Spacing)
+ { "331", "2022", }, // Small Solid Round Bullet
+ { "332", "207F", }, // Superior Lowercase N
+ { "333", "2302", }, // Home Plate
+ { "335", "0138", }, // Lowercase Kra
+ { "338", "0166", }, // Uppercase T-Stroke
+ { "339", "0167", }, // Lowercase T-Stroke
+ { "340", "014A", }, // Uppercase Eng
+ { "341", "014B", }, // Lowercase Eng
+ { "342", "0111", }, // Lowercase D-Stroke
+ { "400", "0102", }, // Uppercase A Breve
+ { "401", "0103", }, // Lowercase A Breve
+ { "402", "0100", }, // Uppercase A Macron
+ { "403", "0101", }, // Lowercase A Macron
+ { "404", "0104", }, // Uppercase A Ogonek
+ { "405", "0105", }, // Lowercase A Ogonek
+ { "406", "0106", }, // Uppercase C Acute
+ { "407", "0107", }, // Lowercase C Acute
+ { "410", "010C", }, // Uppercase C Hacek
+ { "411", "010D", }, // Lowercase C Hacek
+ { "414", "010E", }, // Uppercase D Hacek
+ { "415", "010F", }, // Lowercase D Hacek
+ { "416", "011A", }, // Uppercase E Hacek
+ { "417", "011B", }, // Lowercase E Hacek
+ { "418", "0116", }, // Uppercase E Overdot
+ { "419", "0117", }, // Lowercase E Overdot
+ { "420", "0112", }, // Uppercase E Macron
+ { "421", "0113", }, // Lowercase E Macron
+ { "422", "0118", }, // Uppercase E Ogonek
+ { "423", "0119", }, // Lowercase E Ogonek
+ { "428", "0122", }, // Uppercase G Cedilla
+ { "429", "0123", }, // Lowercase G Cedilla
+ { "432", "012E", }, // Uppercase I Ogonek
+ { "433", "012F", }, // Lowercase I Ogonek
+ { "434", "012A", }, // Uppercase I Macron
+ { "435", "012B", }, // Lowercase I Macron
+ { "438", "0136", }, // Uppercase K Cedilla
+ { "439", "0137", }, // Lowercase K Cedilla
+ { "440", "0139", }, // Uppercase L Acute
+ { "441", "013A", }, // Lowercase L Acute
+ { "442", "013D", }, // Uppercase L Hacek
+ { "443", "013E", }, // Lowercase L Hacek
+ { "444", "013B", }, // Uppercase L Cedilla
+ { "445", "013C", }, // Lowercase L Cedilla
+ { "446", "0143", }, // Uppercase N Acute
+ { "447", "0144", }, // Lowercase N Acute
+ { "448", "0147", }, // Uppercase N Hacek
+ { "449", "0148", }, // Lowercase N Hacek
+ { "450", "0145", }, // Uppercase N Cedilla
+ { "451", "0146", }, // Lowercase N Cedilla
+ { "452", "0150", }, // Uppercase O Double Acute
+ { "453", "0151", }, // Lowercase O Double Acute
+ { "454", "014C", }, // Uppercase O Macron
+ { "455", "014D", }, // Lowercase O Macron
+ { "456", "0154", }, // Uppercase R Acute
+ { "457", "0155", }, // Lowercase R Acute
+ { "458", "0158", }, // Uppercase R Hacek
+ { "459", "0159", }, // Lowercase R Hacek
+ { "460", "0156", }, // Uppercase R Cedilla
+ { "461", "0157", }, // Lowercase R Cedilla
+ { "462", "015A", }, // Uppercase S Acute
+ { "463", "015B", }, // Lowercase S Acute
+ { "466", "0164", }, // Uppercase T Hacek
+ { "467", "0165", }, // Lowercase T Hacek
+ { "468", "0162", }, // Uppercase T Cedilla
+ { "469", "0163", }, // Lowercase T Cedilla
+ { "470", "0168", }, // Uppercase U Tilde
+ { "471", "0169", }, // Lowercase U Tilde
+ { "474", "0170", }, // Uppercase U Double Acute
+ { "475", "0171", }, // Lowercase U Double Acute
+ { "476", "016E", }, // Uppercase U Ring
+ { "477", "016F", }, // Lowercase U Ring
+ { "478", "016A", }, // Uppercase U Macron
+ { "479", "016B", }, // Lowercase U Macron
+ { "480", "0172", }, // Uppercase U Ogonek
+ { "481", "0173", }, // Lowercase U Ogonek
+ { "482", "0179", }, // Uppercase Z Acute
+ { "483", "017A", }, // Lowercase Z Acute
+ { "484", "017B", }, // Uppercase Z Overdot
+ { "485", "017C", }, // Lowercase Z Overdot
+ { "486", "0128", }, // Uppercase I Tilde
+ { "487", "0129", }, // Lowercase I Tilde
+ { "500", "EFBF", }, // Radical, Diagonal, Composite
+ { "501", "221D", }, // Proportional To Symbol
+ { "502", "212F", }, // Napierian (italic e)
+ { "503", "03F5", }, // Alternate Lowercase Greek Epsilon
+//{ "503", "EFEC", }, // Alternate Lowercase Greek Epsilon
+ { "504", "2234", }, // Therefore Symbol
+ { "505", "0393", }, // Uppercase Greek Gamma
+ { "506", "2206", }, // Increment Symbol (Delta)
+ { "507", "0398", }, // Uppercase Greek Theta
+ { "508", "039B", }, // Uppercase Greek Lambda
+ { "509", "039E", }, // Uppercase Greek Xi
+ { "510", "03A0", }, // Uppercase Greek Pi
+ { "511", "03A3", }, // Uppercase Greek Sigma
+ { "512", "03A5", }, // Uppercase Greek Upsilon
+ { "513", "03A6", }, // Uppercase Greek Phi
+ { "514", "03A8", }, // Uppercase Greek Psi
+ { "515", "03A9", }, // Uppercase Greek Omega
+ { "516", "2207", }, // Nabla Symbol (inverted Delta)
+ { "517", "2202", }, // Partial Differential Delta Symbol
+ { "518", "03C2", }, // Lowercase Sigma, Terminal
+ { "519", "2260", }, // Not Equal To Symbol
+ { "520", "EFEB", }, // Underline, Composite
+ { "521", "2235", }, // Because Symbol
+ { "522", "03B1", }, // Lowercase Greek Alpha
+ { "523", "03B2", }, // Lowercase Greek Beta
+ { "524", "03B3", }, // Lowercase Greek Gamma
+ { "525", "03B4", }, // Lowercase Greek Delta
+ { "526", "03B5", }, // Lowercase Greek Epsilon
+ { "527", "03B6", }, // Lowercase Greek Zeta
+ { "528", "03B7", }, // Lowercase Greek Eta
+ { "529", "03B8", }, // Lowercase Greek Theta
+ { "530", "03B9", }, // Lowercase Greek Iota
+ { "531", "03BA", }, // Lowercase Greek Kappa
+ { "532", "03BB", }, // Lowercase Greek Lambda
+ { "533", "03BC", }, // Lowercase Greek Mu
+ { "534", "03BD", }, // Lowercase Greek Nu
+ { "535", "03BE", }, // Lowercase Greek Xi
+ { "536", "03BF", }, // Lowercase Greek Omicron
+ { "537", "03C0", }, // Lowercase Greek Pi
+ { "538", "03C1", }, // Lowercase Greek Rho
+ { "539", "03C3", }, // Lowercase Greek Sigma
+ { "540", "03C4", }, // Lowercase Greek Tau
+ { "541", "03C5", }, // Lowercase Greek Upsilon
+ { "542", "03C6", }, // Lowercase Greek Phi
+ { "543", "03C7", }, // Lowercase Greek Chi
+ { "544", "03C8", }, // Lowercase Greek Psi
+ { "545", "03C9", }, // Lowercase Greek Omega
+ { "546", "03D1", }, // Lowercase Greek Theta, Open
+ { "547", "03D5", }, // Lowercase Greek Phi, Open
+ { "548", "03D6", }, // Lowercase Pi, Alternate
+ { "549", "2243", }, // Wavy Over Straight Approximate Symbol
+ { "550", "2262", }, // Not Exactly Equal To Symbol
+ { "551", "21D1", }, // Up Arrow Double Stroke
+ { "552", "21D2", }, // Right Arrow Double Stroke
+ { "553", "21D3", }, // Down Arrow Double Stroke
+ { "554", "21D0", }, // Left Arrow Double Stroke
+ { "555", "21D5", }, // Up/Down Arrow Double Stroke
+ { "556", "21D4", }, // Left/Right Arrow Double Stroke
+ { "557", "21C4", }, // Right Over Left Arrow
+ { "558", "21C6", }, // Left Over Right Arrow
+ { "559", "EFE9", }, // Vector Symbol
+ { "560", "0305", }, // Overline, Composite
+ { "561", "2200", }, // For All Symbol, or Universal (inverted A)
+ { "562", "2203", }, // There Exists Symbol, or Existential (inverted E)
+ { "563", "22A4", }, // Top Symbol
+ { "564", "22A5", }, // Bottom Symbol
+ { "565", "222A", }, // Set Union Symbol
+ { "566", "2208", }, // Element-Of Symbol
+ { "567", "220B", }, // Contains Symbol
+ { "568", "2209", }, // Not-Element-Of Symbol
+ { "569", "2282", }, // Proper Subset Symbol
+ { "570", "2283", }, // Proper Superset Symbol
+ { "571", "2284", }, // Not Proper Subset Symbol
+ { "572", "2285", }, // Not Proper Superset Symbol
+ { "573", "2286", }, // Subset Symbol
+ { "574", "2287", }, // Superset Symbol
+ { "575", "2295", }, // Plus In Circle Symbol
+ { "576", "2299", }, // Dot In Circle Symbol
+ { "577", "2297", }, // Times In Circle Symbol
+ { "578", "2296", }, // Minus In Circle Symbol
+ { "579", "2298", }, // Slash In Circle Symbol
+ { "580", "2227", }, // Logical And Symbol
+ { "581", "2228", }, // Logical Or Symbol
+ { "582", "22BB", }, // Exclusive Or Symbol
+ { "583", "2218", }, // Functional Composition Symbol
+ { "584", "20DD", }, // Large Open Circle
+ { "585", "22A3", }, // Assertion Symbol
+ { "586", "22A2", }, // Backwards Assertion Symbol
+ { "587", "222B", }, // Integral Symbol
+ { "588", "222E", }, // Curvilinear Integral Symbol
+ { "589", "2220", }, // Angle Symbol
+ { "590", "2205", }, // Empty Set Symbol
+ { "591", "2135", }, // Hebrew Aleph
+ { "592", "2136", }, // Hebrew Beth
+ { "593", "2137", }, // Hebrew Gimmel
+ { "594", "212D", }, // Fraktur Uppercase C
+ { "595", "2111", }, // Fraktur Uppercase I
+ { "596", "211C", }, // Fraktur Uppercase R
+ { "597", "2128", }, // Fraktur Uppercase Z
+ { "598", "23A1", }, // Top Segment Left Bracket (Left Square Bracket Upper Corner)
+ { "599", "23A3", }, // Bottom Segment Left Bracket (Left Square Bracket Lower Corner)
+ { "600", "239B", }, // Top Segment Left Brace (Left Parenthesis Upper Hook)
+//{ "600", "23A7", }, // Top Segment Left Brace (Right Curly Bracket Upper Hook)
+ { "601", "23A8", }, // Middle Segment Left Brace (Right Curly Bracket Middle Piece)
+ { "602", "239D", }, // Bottom Segment LeftBrace (Left Parenthesis Lower Hook)
+//{ "602", "23A9", }, // Bottom Segment Left Brace (Right Curly Bracket Lower Hook)
+ { "603", "EFD4", }, // Middle Segment Curvilinear Integral
+ { "604", "EFD3", }, // Top Left Segment Summation
+ { "605", "2225", }, // Double Vertical Line, Composite
+ { "606", "EFD2", }, // Bottom Left Segment Summation
+ { "607", "EFD1", }, // Bottom Diagonal Summation
+ { "608", "23A4", }, // Top Segment Right Bracket (Right Square Bracket Upper Corner)
+ { "609", "23A6", }, // Bottom Segment Right Bracket (Right Square Bracket Lower Corner)
+ { "610", "239E", }, // Top Segment Right Brace (Right Parenthesis Upper Hook)
+//{ "610", "23AB", }, // Top Segment Right Brace (Right Curly Bracket Upper Hook)
+ { "611", "23AC", }, // Middle Segment Right Brace (Right Curly Bracket Middle Piece)
+ { "612", "23A0", }, // Bottom Segment Right ( Right Parenthesis Lower Hook)
+//{ "612", "23AD", }, // Bottom Segment Right Brace (Right Curly Bracket Lower Hook)
+ { "613", "239C", }, // Thick Vertical Line, Composite (Left Parenthesis Extension)
+//{ "613", "239F", }, // Thick Vertical Line, Composite (Right Parenthesis Extension)
+//{ "613", "23AA", }, // Thick Vertical Line, Composite (Curly Bracket Extension)
+//{ "613", "23AE", }, // Thick Vertical Line, Composite (Integral Extension)
+ { "614", "2223", }, // Thin Vertical Line, Composite
+ { "615", "EFDC", }, // Bottom Segment of Vertical Radical
+ { "616", "EFD0", }, // Top Right Segment Summation
+ { "617", "EFCF", }, // Middle Segment Summation
+ { "618", "EFCE", }, // Bottom Right Segment Summation
+ { "619", "EFCD", }, // Top Diagonal Summation
+ { "620", "2213", }, // Minus Over Plus Sign
+ { "621", "2329", }, // Left Angle Bracket
+ { "622", "232A", }, // Right Angle Bracket
+ { "623", "EFFF", }, // Mask Symbol
+ { "624", "2245", }, // Wavy Over Two Straight Approximate Symbol
+ { "625", "2197", }, // 45 Degree Arrow
+ { "626", "2198", }, // -45 Degree Arrow
+ { "627", "2199", }, // -135 Degree Arrow
+ { "628", "2196", }, // 135 Degree Arrow
+ { "629", "25B5", }, // Up Open Triangle
+ { "630", "25B9", }, // Right Open Triangle
+ { "631", "25BF", }, // Down Open Triangle
+ { "632", "25C3", }, // Left Open Triangle
+ { "633", "226A", }, // Much Less Than Sign
+ { "634", "226B", }, // Much Greater Than Sign
+ { "635", "2237", }, // Proportional To Symbol (4 dots)
+ { "636", "225C", }, // Defined As Symbol
+ { "637", "03DD", }, // Lowercase Greek Digamma
+ { "638", "210F", }, // Planck's Constant divided by 2 pi
+ { "639", "2112", }, // Laplace Transform Symbol
+ { "640", "EFFE", }, // Power Set
+ { "641", "2118", }, // Weierstrassian Symbol
+ { "642", "2211", }, // Summation Symbol (large Sigma)
+ { "643", "301A", }, // Left Double Bracket
+ { "644", "EFC9", }, // Middle Segment Double Bracket
+ { "645", "301B", }, // Right Double Bracket
+ { "646", "256D", }, // Box Draw Left Top Round Corner
+ { "647", "2570", }, // Box Draw Left Bottom Round Corner
+ { "648", "EFC8", }, // Extender Large Union/Product
+ { "649", "EFC7", }, // Bottom Segment Large Union
+ { "650", "EFC6", }, // Top Segment Large Intersection
+ { "651", "EFC5", }, // Top Segment Left Double Bracket
+ { "652", "EFC4", }, // Bottom Segment Left Double Bracket
+ { "653", "EFFC", }, // Large Open Square Box
+ { "654", "25C7", }, // Open Diamond
+ { "655", "256E", }, // Box Draw Right Top Round Corner
+ { "656", "256F", }, // Box Draw Right Bottom Round Corner
+ { "657", "EFC3", }, // Bottom Segment Large Bottom Product
+ { "658", "EFC2", }, // Top Segment Large Top Product
+ { "659", "EFC1", }, // Top Segment Right Double Bracket
+ { "660", "EFC0", }, // Bottom Segment Right Double Bracket
+ { "661", "EFFB", }, // Large Solid Square Box
+ { "662", "25C6", }, // Solid Diamond
+ { "663", "220D", }, // Such That Symbol (rotated lc epsilon)
+ { "664", "2217", }, // Math Asterisk
+ { "665", "23AF", }, // Horizontal Arrow Extender (Horizontal Line Extension)
+ { "666", "EFCB", }, // Double Horizontal Arrow Extender
+ { "667", "EFCC", }, // Inverted Complement of 0xEFCF or MSL 617
+ { "668", "221F", }, // Right Angle Symbol
+ { "669", "220F", }, // Product Symbol (large Pi)
+ { "684", "25CA", }, // Lozenge, Diamond
+ { "1000", "2070", }, // Superior Numeral 0
+ { "1001", "2074", }, // Superior Numeral 4
+ { "1002", "2075", }, // Superior Numeral 5
+ { "1003", "2076", }, // Superior Numeral 6
+ { "1004", "2077", }, // Superior Numeral 7
+ { "1005", "2078", }, // Superior Numeral 8
+ { "1006", "2079", }, // Superior Numeral 9
+ { "1017", "201C", }, // Double Open Quote (6)
+ { "1018", "201D", }, // Double Close Quote (9)
+ { "1019", "201E", }, // Double Baseline Quote (9)
+ { "1020", "2003", }, // Em Space
+ { "1021", "2002", }, // En Space
+ { "1023", "2009", }, // Thin Space
+ { "1028", "2026", }, // Ellipsis
+ { "1030", "EFF1", }, // Uppercase Ogonek (Spacing)
+ { "1031", "017E", }, // Lowercase Z Hacek
+ { "1034", "2120", }, // Service Mark
+ { "1036", "211E", }, // Prescription Sign
+//{ "1040", "F001", }, // Lowercase FI Ligature
+ { "1040", "FB01", }, // Lowercase FI Ligature
+//{ "1041", "F002", }, // Lowercase FL Ligature
+ { "1041", "FB02", }, // Lowercase FL Ligature
+ { "1042", "FB00", }, // Lowercase FF Ligature
+ { "1043", "FB03", }, // Lowercase FFI Ligature
+ { "1044", "FB04", }, // Lowercase FFL Ligature
+ { "1045", "EFF0", }, // Uppercase Double Acute Accent (Spacing)
+ { "1047", "0133", }, // Lowercase IJ Ligature
+ { "1060", "2105", }, // Care Of Symbol
+ { "1061", "011E", }, // Uppercase G Breve
+ { "1062", "011F", }, // Lowercase G Breve
+ { "1063", "015E", }, // Uppercase S Cedilla
+ { "1064", "015F", }, // Lowercase S Cedilla
+ { "1065", "0130", }, // Uppercase I Overdot
+ { "1067", "201A", }, // Single Baseline Quote (9)
+ { "1068", "2030", }, // Per Mill Sign
+ { "1069", "20AC", }, // Euro
+ { "1084", "02C9", }, // Lowercase Macron Accent (Spacing)
+ { "1086", "02D8", }, // Lowercase Breve Accent (Spacing)
+ { "1088", "02D9", }, // Lowercase Overdot Accent (Spacing)
+ { "1090", "0153", }, // Lowercase OE Ligature
+ { "1091", "0152", }, // Uppercase OE Ligature
+ { "1092", "2039", }, // Left Pointing Single Angle Quote
+ { "1093", "203A", }, // Right Pointing Single Angle Quote
+ { "1094", "25A1", }, // Medium Open Square Box
+ { "1095", "0141", }, // Uppercase L-Stroke
+ { "1096", "0142", }, // Lowercase L-Stroke
+ { "1097", "02DD", }, // Lowercase Double Acute Accent (Spacing)
+ { "1098", "02DB", }, // Lowercase Ogonek (Spacing)
+ { "1099", "21B5", }, // Carriage Return Symbol
+ { "1100", "EFDB", }, // Full Size Serif Registered
+ { "1101", "EFDA", }, // Full Size Serif Copyright
+ { "1102", "EFD9", }, // Full Size Serif Trademark
+ { "1103", "EFD8", }, // Full Size Sans Registered
+ { "1104", "EFD7", }, // Full Size Sans Copyright
+ { "1105", "EFD6", }, // Full Size Sans Trademark
+ { "1106", "017D", }, // Uppercase Z Hacek
+ { "1107", "0132", }, // Uppercase IJ Ligature
+ { "1108", "25AB", }, // Small Open Square Box
+ { "1109", "25E6", }, // Small Open Round Bullet
+ { "1110", "25CB", }, // Medium Open Round Bullet
+ { "1111", "EFFA", }, // Large Solid Round Bullet
+ { "3812", "F000", }, // Ornament, Apple
+};
+
+// global constructor
+static struct hp_msl_to_unicode_init {
+ hp_msl_to_unicode_init();
+} _hp_msl_to_unicode_init;
+
+hp_msl_to_unicode_init::hp_msl_to_unicode_init() {
+ for (unsigned int i = 0;
+ i < sizeof(hp_msl_to_unicode_list)/sizeof(hp_msl_to_unicode_list[0]);
+ i++) {
+ hp_msl_to_unicode *ptu = new hp_msl_to_unicode[1];
+ ptu->value = (char *)hp_msl_to_unicode_list[i].value;
+ hp_msl_to_unicode_table.define(hp_msl_to_unicode_list[i].key, ptu);
+ }
+}
+
+const char *hp_msl_to_unicode_code(const char *s)
+{
+ hp_msl_to_unicode *result = hp_msl_to_unicode_table.lookup(s);
+ return result ? result->value : 0;
+}
diff --git a/src/utils/indxbib/eign b/src/utils/indxbib/eign
new file mode 100644
index 0000000..7718c8b
--- /dev/null
+++ b/src/utils/indxbib/eign
@@ -0,0 +1,133 @@
+a
+i
+the
+to
+of
+and
+in
+is
+it
+for
+that
+if
+you
+this
+be
+on
+with
+not
+have
+are
+or
+as
+from
+can
+but
+by
+at
+an
+will
+no
+all
+was
+do
+there
+my
+one
+so
+we
+they
+what
+would
+any
+which
+about
+get
+your
+use
+some
+me
+then
+name
+like
+out
+when
+up
+time
+other
+more
+only
+just
+end
+also
+know
+how
+new
+should
+been
+than
+them
+he
+who
+make
+may
+people
+these
+now
+their
+here
+into
+first
+could
+way
+had
+see
+work
+well
+were
+two
+very
+where
+while
+us
+because
+good
+same
+even
+much
+most
+many
+such
+long
+his
+over
+last
+since
+right
+before
+our
+without
+too
+those
+why
+must
+part
+being
+current
+back
+still
+go
+point
+value
+each
+did
+both
+true
+off
+say
+another
+state
+might
+under
+start
+try
diff --git a/src/utils/indxbib/indxbib.1.man b/src/utils/indxbib/indxbib.1.man
new file mode 100644
index 0000000..df02fcc
--- /dev/null
+++ b/src/utils/indxbib/indxbib.1.man
@@ -0,0 +1,347 @@
+.TH @g@indxbib @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@indxbib \- make inverted index for bibliographic databases
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_indxbib_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@indxbib
+.RB [ \-w ]
+.RB [ \-c\~\c
+.IR \%common-words-file ]
+.RB [ \-d\~\c
+.IR dir ]
+.RB [ \-f\~\c
+.IR \%list-file ]
+.RB [ \-h\~\c
+.IR \%min-hash-table-size ]
+.RB [ \-i\~\c
+.IR \%excluded-fields ]
+.RB [ \-k\~\c
+.IR \%max-keys-per-record ]
+.RB [ \-l\~\c
+.IR \%min-key-length ]
+.RB [ \-n\~\c
+.IR \%threshold ]
+.RB [ \-o\~\c
+.IR file ]
+.RB [ \-t\~\c
+.IR \%max-key-length ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@indxbib
+.B \-\-help
+.YS
+.
+.
+.SY @g@indxbib
+.B \-v
+.
+.SY @g@indxbib
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I @g@indxbib
+makes an inverted index for the bibliographic databases in each
+.I file
+for use with
+.MR @g@refer @MAN1EXT@ ,
+.MR @g@lookbib @MAN1EXT@ ,
+and
+.MR lkbib @MAN1EXT@ .
+.
+Each created index is named
+.RI file @INDEX_SUFFIX@ ;
+writing is done to a temporary file which is then renamed to this.
+.
+If no
+.I file
+operands are given on the command line because the
+.B \-f
+option has been used,
+and no
+.B \-o
+option is given,
+the index will be named
+.IR \%@DEFAULT_INDEX_NAME@@INDEX_SUFFIX@ .
+.
+.
+.LP
+Bibliographic databases are divided into records by blank lines.
+.
+Within a record,
+each field starts with a
+.B %
+character at the beginning of a line.
+.
+Fields have a one letter name that follows the
+.B %
+character.
+.
+.
+.LP
+The values set by the
+.BR \-c ,
+.BR \-l ,
+.BR \-n ,
+and
+.B \-t
+options are stored in the index:
+when the index is searched,
+keys will be discarded and truncated in a
+manner appropriate to these options;
+the original keys will be used for verifying that any record
+found using the index actually contains the keys.
+.
+This means that a user of an index need not know whether these
+options were used in the creation of the index,
+provided that not all the keys to be searched for
+would have been discarded during indexing
+and that the user supplies at least the part of each key
+that would have remained after being truncated during indexing.
+.
+The value set by the
+.B \-i
+option is also stored in the index
+and will be used in verifying records found using the index.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-c\~ common-words-file
+Read the list of common words from
+.I common-words-file
+instead of
+.IR \%@COMMON_WORDS_FILE@ .
+.
+.
+.TP
+.BI \-d\~ dir
+Use
+.I dir
+as the name of the directory to store in the index,
+instead of that returned by
+.MR getcwd 2 .
+.
+Typically,
+.I dir
+will be a symbolic link whose target is the current working directory.
+.
+.
+.TP
+.BI \-f\~ list-file
+Read the files to be indexed from
+.IR list-file .
+.
+If
+.I list-file
+is
+.BR \- ,
+files will be read from the standard input stream.
+.
+The
+.B \-f
+option can be given at most once.
+.
+.
+.TP
+.BI \-h\~ min-hash-table-size
+Use the first prime number greater than or equal to
+the argument for the size of the hash table.
+.
+Larger values
+will usually make searching faster,
+but will make the index file larger
+and cause
+.I @g@indxbib
+to use more memory.
+.
+The default hash table size is 997.
+.
+.
+.TP
+.BI \-i\~ excluded-fields
+Don't index the contents of fields whose names are in
+.IR excluded-fields .
+.
+Field names are one character each.
+.
+If this option is not present,
+.I @g@indxbib
+excludes fields
+.BR X ,
+.BR Y ,
+and
+.BR Z .
+.
+.
+.TP
+.BI \-k\~ max-keys-per-record
+Use no more keys per input record than specified in the argument.
+.
+If this option is not present,
+the maximum is 100.
+.
+.
+.TP
+.BI \-l\~ min-key-length
+Discard any key whose length in characters is shorter than the value of
+the argument.
+.
+If this option is not present,
+the minimum key length
+is 3.
+.
+.
+.TP
+.BI \-n\~ threshold
+Discard the
+.I threshold
+most common words from the common words file.
+.
+If this option is not present,
+the 100 most common words are discarded.
+.
+.
+.TP
+.BI \-o\~ basename
+Name the index
+.RI basename @INDEX_SUFFIX@ .
+.
+.
+.TP
+.BI \-t\~ max-key-length
+Truncate keys to
+.I max-key-length
+in characters.
+.
+If this option is not present,
+keys are truncated to 6 characters.
+.
+.
+.TP
+.B \-w
+Index whole files.
+.
+Each file is a separate record.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.RI \%file @INDEX_SUFFIX@
+index for
+.I file
+.
+.
+.TP
+.I \%@DEFAULT_INDEX_NAME@@INDEX_SUFFIX@
+default index name
+.
+.
+.TP
+.I \%@COMMON_WORDS_FILE@
+contains the list of common words.
+.
+The traditional name,
+.RI \[lq] eign \[rq],
+is an abbreviation of \[lq]English ignored [word list]\[rq].
+.
+.
+.TP
+.IR \%indxbib XXXXXX
+temporary file
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Some Applications of Inverted Indexes on the Unix System\[rq],
+by M.\& E.\& Lesk,
+1978,
+AT&T Bell Laboratories Computing Science Technical Report No.\& 69.
+.
+.
+.LP
+.MR @g@refer @MAN1EXT@ ,
+.MR lkbib @MAN1EXT@ ,
+.MR @g@lookbib @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_indxbib_1_man_C]
+.do rr *groff_indxbib_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/indxbib/indxbib.am b/src/utils/indxbib/indxbib.am
new file mode 100644
index 0000000..d2a7d5a
--- /dev/null
+++ b/src/utils/indxbib/indxbib.am
@@ -0,0 +1,57 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+indxbib_srcdir = $(top_srcdir)/src/utils/indxbib
+prefixexecbin_PROGRAMS += indxbib
+indxbib_SOURCES = \
+ src/utils/indxbib/indxbib.cpp \
+ src/utils/indxbib/signal.c
+src/utils/indxbib/indxbib.$(OBJEXT): defs.h
+indxbib_LDADD = libbib.a libgroff.a $(LIBM) lib/libgnu.a
+PREFIXMAN1 += src/utils/indxbib/indxbib.1
+EXTRA_DIST += \
+ src/utils/indxbib/indxbib.1.man \
+ src/utils/indxbib/eign
+
+install-data-local: install_indxbib
+install_indxbib: $(indxbib_srcdir)/eign
+ -test -d $(DESTDIR)$(datadir) \
+ || $(mkinstalldirs) $(DESTDIR)$(datadir)
+ -test -d $(DESTDIR)$(dataprogramdir) \
+ || $(mkinstalldirs) $(DESTDIR)$(dataprogramdir)
+ -test -d $(DESTDIR)$(datasubdir) \
+ || $(mkinstalldirs) $(DESTDIR)$(datasubdir)
+ if test -f /usr/lib/eign; then \
+ rm -f $(DESTDIR)$(common_words_file); \
+ ln -s /usr/lib/eign $(DESTDIR)$(common_words_file) 2>/dev/null \
+ || ln /usr/lib/eign $(DESTDIR)$(common_words_file) 2>/dev/null \
+ || cp /usr/lib/eign $(DESTDIR)$(common_words_file); \
+ else \
+ rm -f $(DESTDIR)$(common_words_file); \
+ $(INSTALL_DATA) $(indxbib_srcdir)/eign $(DESTDIR)$(common_words_file); \
+ fi
+
+uninstall-local: uninstall_indxbib
+uninstall_indxbib:
+ rm -f $(DESTDIR)$(common_words_file)
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/indxbib/indxbib.cpp b/src/utils/indxbib/indxbib.cpp
new file mode 100644
index 0000000..ad8bb0e
--- /dev/null
+++ b/src/utils/indxbib/indxbib.cpp
@@ -0,0 +1,803 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "posix.h"
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "cmap.h"
+
+#include "defs.h"
+#include "index.h"
+
+#include "nonposix.h"
+
+extern "C" const char *Version_string;
+
+#define DEFAULT_HASH_TABLE_SIZE 997
+#define TEMP_INDEX_TEMPLATE "indxbibXXXXXX"
+
+// (2^n - MALLOC_OVERHEAD) should be a good argument for malloc().
+
+#define MALLOC_OVERHEAD 16
+
+#ifdef BLOCK_SIZE
+#undef BLOCK_SIZE
+#endif
+
+const int BLOCK_SIZE = ((1024 - MALLOC_OVERHEAD - sizeof(struct block *)
+ - sizeof(int)) / sizeof(int));
+struct block {
+ block *next;
+ int used;
+ int v[BLOCK_SIZE];
+
+ block(block *p = 0) : next(p), used(0) { }
+};
+
+struct block;
+
+union table_entry {
+ block *ptr;
+ int count;
+};
+
+struct word_list {
+ word_list *next;
+ char *str;
+ int len;
+ word_list(const char *, int, word_list *);
+};
+
+table_entry *hash_table;
+int hash_table_size = DEFAULT_HASH_TABLE_SIZE;
+// We make this the same size as hash_table so we only have to do one
+// mod per key.
+static word_list **common_words_table = 0;
+char *key_buffer;
+
+FILE *indxfp;
+int ntags = 0;
+string filenames;
+char *temp_index_file = 0;
+
+const char *ignore_fields = "XYZ";
+const char *common_words_file = COMMON_WORDS_FILE;
+int n_ignore_words = 100;
+int truncate_len = 6;
+int shortest_len = 3;
+int max_keys_per_item = 100;
+
+static void usage(FILE *stream);
+static void write_hash_table();
+static void init_hash_table();
+static void read_common_words_file();
+static int store_key(char *s, int len);
+static void possibly_store_key(char *s, int len);
+static int do_whole_file(const char *filename);
+static int do_file(const char *filename);
+static void store_reference(int filename_index, int pos, int len);
+static void check_integer_arg(char opt, const char *arg, int min, int *res);
+static void store_filename(const char *);
+static void fwrite_or_die(const void *ptr, int size, int nitems, FILE *fp);
+static char *get_cwd();
+
+extern "C" {
+ void cleanup();
+ void catch_fatal_signals();
+ void ignore_fatal_signals();
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+
+ const char *base_name = 0;
+ typedef int (*parser_t)(const char *);
+ parser_t parser = do_file;
+ const char *directory = 0;
+ const char *foption = 0;
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "c:o:h:i:k:l:t:n:c:d:f:vw",
+ long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'c':
+ common_words_file = optarg;
+ break;
+ case 'd':
+ directory = optarg;
+ break;
+ case 'f':
+ foption = optarg;
+ break;
+ case 'h':
+ {
+ int requested_hash_table_size;
+ check_integer_arg('h', optarg, 1, &requested_hash_table_size);
+ hash_table_size = requested_hash_table_size;
+ if ((hash_table_size > 2) && (hash_table_size % 2) == 0)
+ hash_table_size++;
+ while (!is_prime(hash_table_size))
+ hash_table_size += 2;
+ if (hash_table_size != requested_hash_table_size)
+ warning("requested hash table size %1 is not prime: using %2"
+ " instead", optarg, hash_table_size);
+ }
+ break;
+ case 'i':
+ ignore_fields = optarg;
+ break;
+ case 'k':
+ check_integer_arg('k', optarg, 1, &max_keys_per_item);
+ break;
+ case 'l':
+ check_integer_arg('l', optarg, 0, &shortest_len);
+ break;
+ case 'n':
+ check_integer_arg('n', optarg, 0, &n_ignore_words);
+ break;
+ case 'o':
+ base_name = optarg;
+ break;
+ case 't':
+ check_integer_arg('t', optarg, 1, &truncate_len);
+ break;
+ case 'w':
+ parser = do_whole_file;
+ break;
+ case 'v':
+ printf("GNU indxbib (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ if (optind >= argc && foption == 0)
+ fatal("no files and no -f option");
+ if (!directory) {
+ char *path = get_cwd();
+ store_filename(path);
+ delete[] path;
+ }
+ else
+ store_filename(directory);
+ init_hash_table();
+ store_filename(common_words_file);
+ store_filename(ignore_fields);
+ key_buffer = new char[truncate_len];
+ read_common_words_file();
+ if (!base_name)
+ base_name = optind < argc ? argv[optind] : DEFAULT_INDEX_NAME;
+ const char *p = strrchr(base_name, DIR_SEPS[0]), *p1;
+ const char *sep = &DIR_SEPS[1];
+ while (*sep) {
+ p1 = strrchr(base_name, *sep);
+ if (p1 && (!p || p1 > p))
+ p = p1;
+ sep++;
+ }
+ size_t name_max;
+ if (p) {
+ char *dir = strsave(base_name);
+ dir[p - base_name] = '\0';
+ name_max = file_name_max(dir);
+ delete[] dir;
+ }
+ else
+ name_max = file_name_max(".");
+ const char *filename = p ? p + 1 : base_name;
+ if (strlen(filename) + sizeof(INDEX_SUFFIX) - 1 > name_max)
+ fatal("'%1.%2' is too long for a filename", filename, INDEX_SUFFIX);
+ if (p) {
+ p++;
+ temp_index_file = new char[p - base_name + sizeof(TEMP_INDEX_TEMPLATE)];
+ memcpy(temp_index_file, base_name, p - base_name);
+ strcpy(temp_index_file + (p - base_name), TEMP_INDEX_TEMPLATE);
+ }
+ else {
+ temp_index_file = strsave(TEMP_INDEX_TEMPLATE);
+ }
+ catch_fatal_signals();
+ int fd = mkstemp(temp_index_file);
+ if (fd < 0)
+ fatal("can't create temporary index file: %1", strerror(errno));
+ indxfp = fdopen(fd, FOPEN_WB);
+ if (indxfp == 0)
+ fatal("fdopen failed");
+ if (fseek(indxfp, sizeof(index_header), 0) < 0)
+ fatal("can't seek past index header: %1", strerror(errno));
+ int failed = 0;
+ if (foption) {
+ FILE *fp = stdin;
+ if (strcmp(foption, "-") != 0) {
+ errno = 0;
+ fp = fopen(foption, "r");
+ if (!fp)
+ fatal("can't open '%1': %2", foption, strerror(errno));
+ }
+ string path;
+ int lineno = 1;
+ for (;;) {
+ int c;
+ for (c = getc(fp); c != '\n' && c != EOF; c = getc(fp)) {
+ if (c == '\0')
+ error_with_file_and_line(foption, lineno,
+ "nul character in pathname ignored");
+ else
+ path += c;
+ }
+ if (path.length() > 0) {
+ path += '\0';
+ if (!(*parser)(path.contents()))
+ failed = 1;
+ path.clear();
+ }
+ if (c == EOF)
+ break;
+ lineno++;
+ }
+ if (fp != stdin)
+ fclose(fp);
+ }
+ for (int i = optind; i < argc; i++)
+ if (!(*parser)(argv[i]))
+ failed = 1;
+ write_hash_table();
+ if (fclose(indxfp) < 0)
+ fatal("error closing temporary index file: %1", strerror(errno));
+ char *index_file = new char[strlen(base_name) + sizeof(INDEX_SUFFIX)];
+ strcpy(index_file, base_name);
+ strcat(index_file, INDEX_SUFFIX);
+#ifdef HAVE_RENAME
+#ifdef __EMX__
+ if (access(index_file, R_OK) == 0)
+ unlink(index_file);
+#endif /* __EMX__ */
+ if (rename(temp_index_file, index_file) < 0) {
+#ifdef __MSDOS__
+ // RENAME could fail on plain MS-DOS filesystems because
+ // INDEX_FILE is an invalid filename, e.g. it has multiple dots.
+ char *fname = p ? index_file + (p - base_name) : 0;
+ char *dot = 0;
+
+ // Replace the dot with an underscore and try again.
+ if (fname
+ && (dot = strchr(fname, '.')) != 0
+ && strcmp(dot, INDEX_SUFFIX) != 0)
+ *dot = '_';
+ if (rename(temp_index_file, index_file) < 0)
+#endif
+ fatal("can't rename temporary index file: %1", strerror(errno));
+ }
+#else /* not HAVE_RENAME */
+ ignore_fatal_signals();
+ if (unlink(index_file) < 0) {
+ if (errno != ENOENT)
+ fatal("can't unlink '%1': %2", index_file, strerror(errno));
+ }
+ if (link(temp_index_file, index_file) < 0)
+ fatal("can't link temporary index file: %1", strerror(errno));
+ if (unlink(temp_index_file) < 0)
+ fatal("can't unlink temporary index file: %1", strerror(errno));
+#endif /* not HAVE_RENAME */
+ temp_index_file = 0;
+ return failed;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-w] [-c common-words-file] [-d dir] [-f list-file]"
+" [-h min-hash-table-size] [-i excluded-fields]"
+" [-k max-keys-per-record] [-l min-key-length]"
+" [-n threshold] [-o file] [-t max-key-length] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+static void check_integer_arg(char opt, const char *arg, int min, int *res)
+{
+ char *ptr;
+ long n = strtol(arg, &ptr, 10);
+ if (n == 0 && ptr == arg)
+ error("argument to -%1 not an integer", opt);
+ else if (n < min)
+ error("argument to -%1 must not be less than %2", opt, min);
+ else {
+ if (n > INT_MAX)
+ error("argument to -%1 greater than maximum integer", opt);
+ else if (*ptr != '\0')
+ error("junk after integer argument to -%1", opt);
+ *res = int(n);
+ }
+}
+
+static char *get_cwd()
+{
+ char *buf;
+ int size = 12;
+
+ for (;;) {
+ buf = new char[size];
+ if (getcwd(buf, size))
+ break;
+ if (errno != ERANGE)
+ fatal("cannot get current working directory: %1", strerror(errno));
+ delete[] buf;
+ if (size == INT_MAX)
+ fatal("current working directory longer than INT_MAX");
+ if (size > INT_MAX/2)
+ size = INT_MAX;
+ else
+ size *= 2;
+ }
+ return buf;
+}
+
+word_list::word_list(const char *s, int n, word_list *p)
+: next(p), len(n)
+{
+ str = new char[n];
+ memcpy(str, s, n);
+}
+
+static void read_common_words_file()
+{
+ if (n_ignore_words <= 0)
+ return;
+ errno = 0;
+ FILE *fp = fopen(common_words_file, "r");
+ if (!fp)
+ fatal("can't open '%1': %2", common_words_file, strerror(errno));
+ common_words_table = new word_list * [hash_table_size];
+ for (int i = 0; i < hash_table_size; i++)
+ common_words_table[i] = 0;
+ int count = 0;
+ int key_len = 0;
+ for (;;) {
+ int c = getc(fp);
+ while (c != EOF && !csalnum(c))
+ c = getc(fp);
+ if (c == EOF)
+ break;
+ do {
+ if (key_len < truncate_len)
+ key_buffer[key_len++] = cmlower(c);
+ c = getc(fp);
+ } while (c != EOF && csalnum(c));
+ if (key_len >= shortest_len) {
+ int h = hash(key_buffer, key_len) % hash_table_size;
+ common_words_table[h] = new word_list(key_buffer, key_len,
+ common_words_table[h]);
+ }
+ if (++count >= n_ignore_words)
+ break;
+ key_len = 0;
+ if (c == EOF)
+ break;
+ }
+ n_ignore_words = count;
+ fclose(fp);
+}
+
+static int do_whole_file(const char *filename)
+{
+ errno = 0;
+ FILE *fp = fopen(filename, "r");
+ if (!fp) {
+ error("can't open '%1': %2", filename, strerror(errno));
+ return 0;
+ }
+ int count = 0;
+ int key_len = 0;
+ int c;
+ while ((c = getc(fp)) != EOF) {
+ if (csalnum(c)) {
+ key_len = 1;
+ key_buffer[0] = c;
+ while ((c = getc(fp)) != EOF) {
+ if (!csalnum(c))
+ break;
+ if (key_len < truncate_len)
+ key_buffer[key_len++] = c;
+ }
+ if (store_key(key_buffer, key_len)) {
+ if (++count >= max_keys_per_item)
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ }
+ store_reference(filenames.length(), 0, 0);
+ store_filename(filename);
+ fclose(fp);
+ return 1;
+}
+
+static int do_file(const char *filename)
+{
+ errno = 0;
+ // Need binary I/O for MS-DOS/MS-Windows, because indxbib relies on
+ // byte counts to be consistent with fseek.
+ FILE *fp = fopen(filename, FOPEN_RB);
+ if (fp == 0) {
+ error("can't open '%1': %2", filename, strerror(errno));
+ return 0;
+ }
+ int filename_index = filenames.length();
+ store_filename(filename);
+
+ enum {
+ START, // at the start of the file; also in between references
+ BOL, // in the middle of a reference, at the beginning of the line
+ PERCENT, // seen a percent at the beginning of the line
+ IGNORE, // ignoring a field
+ IGNORE_BOL, // at the beginning of a line ignoring a field
+ KEY, // in the middle of a key
+ DISCARD, // after truncate_len bytes of a key
+ MIDDLE // in between keys
+ } state = START;
+
+ // In states START, BOL, IGNORE_BOL, space_count how many spaces at
+ // the beginning have been seen. In states PERCENT, IGNORE, KEY,
+ // MIDDLE space_count must be 0.
+ int space_count = 0;
+ int byte_count = 0; // bytes read
+ int key_len = 0;
+ int ref_start = -1; // position of start of current reference
+ for (;;) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ // We opened the file in binary mode, so we need to skip
+ // every CR character before a Newline.
+ if (c == '\r') {
+ int peek = getc(fp);
+ if (peek == '\n') {
+ byte_count++;
+ c = peek;
+ }
+ else
+ ungetc(peek, fp);
+ }
+#if defined(__MSDOS__) || defined(_MSC_VER) || defined(__EMX__)
+ else if (c == 0x1a) // ^Z means EOF in text files
+ break;
+#endif
+ byte_count++;
+ switch (state) {
+ case START:
+ if (c == ' ' || c == '\t') {
+ space_count++;
+ break;
+ }
+ if (c == '\n') {
+ space_count = 0;
+ break;
+ }
+ ref_start = byte_count - space_count - 1;
+ space_count = 0;
+ if (c == '%')
+ state = PERCENT;
+ else if (csalnum(c)) {
+ state = KEY;
+ key_buffer[0] = c;
+ key_len = 1;
+ }
+ else
+ state = MIDDLE;
+ break;
+ case BOL:
+ switch (c) {
+ case '%':
+ if (space_count > 0) {
+ space_count = 0;
+ state = MIDDLE;
+ }
+ else
+ state = PERCENT;
+ break;
+ case ' ':
+ case '\t':
+ space_count++;
+ break;
+ case '\n':
+ store_reference(filename_index, ref_start,
+ byte_count - 1 - space_count - ref_start);
+ state = START;
+ space_count = 0;
+ break;
+ default:
+ space_count = 0;
+ if (csalnum(c)) {
+ state = KEY;
+ key_buffer[0] = c;
+ key_len = 1;
+ }
+ else
+ state = MIDDLE;
+ }
+ break;
+ case PERCENT:
+ if (strchr(ignore_fields, c) != 0)
+ state = IGNORE;
+ else if (c == '\n')
+ state = BOL;
+ else
+ state = MIDDLE;
+ break;
+ case IGNORE:
+ if (c == '\n')
+ state = IGNORE_BOL;
+ break;
+ case IGNORE_BOL:
+ switch (c) {
+ case '%':
+ if (space_count > 0) {
+ state = IGNORE;
+ space_count = 0;
+ }
+ else
+ state = PERCENT;
+ break;
+ case ' ':
+ case '\t':
+ space_count++;
+ break;
+ case '\n':
+ store_reference(filename_index, ref_start,
+ byte_count - 1 - space_count - ref_start);
+ state = START;
+ space_count = 0;
+ break;
+ default:
+ space_count = 0;
+ state = IGNORE;
+ }
+ break;
+ case KEY:
+ if (csalnum(c)) {
+ if (key_len < truncate_len)
+ key_buffer[key_len++] = c;
+ else
+ state = DISCARD;
+ }
+ else {
+ possibly_store_key(key_buffer, key_len);
+ key_len = 0;
+ if (c == '\n')
+ state = BOL;
+ else
+ state = MIDDLE;
+ }
+ break;
+ case DISCARD:
+ if (!csalnum(c)) {
+ possibly_store_key(key_buffer, key_len);
+ key_len = 0;
+ if (c == '\n')
+ state = BOL;
+ else
+ state = MIDDLE;
+ }
+ break;
+ case MIDDLE:
+ if (csalnum(c)) {
+ state = KEY;
+ key_buffer[0] = c;
+ key_len = 1;
+ }
+ else if (c == '\n')
+ state = BOL;
+ break;
+ default:
+ assert(0);
+ }
+ }
+ switch (state) {
+ case START:
+ break;
+ case DISCARD:
+ case KEY:
+ possibly_store_key(key_buffer, key_len);
+ // fall through
+ case BOL:
+ case PERCENT:
+ case IGNORE_BOL:
+ case IGNORE:
+ case MIDDLE:
+ store_reference(filename_index, ref_start,
+ byte_count - ref_start - space_count);
+ break;
+ default:
+ assert(0);
+ }
+ fclose(fp);
+ return 1;
+}
+
+static void store_reference(int filename_index, int pos, int len)
+{
+ tag t;
+ t.filename_index = filename_index;
+ t.start = pos;
+ t.length = len;
+ fwrite_or_die(&t, sizeof(t), 1, indxfp);
+ ntags++;
+}
+
+static void store_filename(const char *fn)
+{
+ filenames += fn;
+ filenames += '\0';
+}
+
+static void init_hash_table()
+{
+ hash_table = new table_entry[hash_table_size];
+ for (int i = 0; i < hash_table_size; i++)
+ hash_table[i].ptr = 0;
+}
+
+static void possibly_store_key(char *s, int len)
+{
+ static int last_tagno = -1;
+ static int key_count;
+ if (last_tagno != ntags) {
+ last_tagno = ntags;
+ key_count = 0;
+ }
+ if (key_count < max_keys_per_item) {
+ if (store_key(s, len))
+ key_count++;
+ }
+}
+
+static int store_key(char *s, int len)
+{
+ if (len < shortest_len)
+ return 0;
+ int is_number = 1;
+ for (int i = 0; i < len; i++)
+ if (!csdigit(s[i])) {
+ is_number = 0;
+ s[i] = cmlower(s[i]);
+ }
+ if (is_number && !(len == 4 && s[0] == '1' && s[1] == '9'))
+ return 0;
+ int h = hash(s, len) % hash_table_size;
+ if (common_words_table) {
+ for (word_list *ptr = common_words_table[h]; ptr; ptr = ptr->next)
+ if (len == ptr->len && memcmp(s, ptr->str, len) == 0)
+ return 0;
+ }
+ table_entry *pp = hash_table + h;
+ if (!pp->ptr)
+ pp->ptr = new block;
+ else if (pp->ptr->v[pp->ptr->used - 1] == ntags)
+ return 1;
+ else if (pp->ptr->used >= BLOCK_SIZE)
+ pp->ptr = new block(pp->ptr);
+ pp->ptr->v[(pp->ptr->used)++] = ntags;
+ return 1;
+}
+
+static void write_hash_table()
+{
+ const int minus_one = -1;
+ int li = 0;
+ for (int i = 0; i < hash_table_size; i++) {
+ block *ptr = hash_table[i].ptr;
+ if (!ptr)
+ hash_table[i].count = -1;
+ else {
+ hash_table[i].count = li;
+ block *rev = 0;
+ while (ptr) {
+ block *tem = ptr;
+ ptr = ptr->next;
+ tem->next = rev;
+ rev = tem;
+ }
+ while (rev) {
+ fwrite_or_die(rev->v, sizeof(int), rev->used, indxfp);
+ li += rev->used;
+ block *tem = rev;
+ rev = rev->next;
+ delete tem;
+ }
+ fwrite_or_die(&minus_one, sizeof(int), 1, indxfp);
+ li += 1;
+ }
+ }
+ if (sizeof(table_entry) == sizeof(int))
+ fwrite_or_die(hash_table, sizeof(int), hash_table_size, indxfp);
+ else {
+ // write it out word by word
+ for (int i = 0; i < hash_table_size; i++)
+ fwrite_or_die(&hash_table[i].count, sizeof(int), 1, indxfp);
+ }
+ fwrite_or_die(filenames.contents(), 1, filenames.length(), indxfp);
+ if (fseek(indxfp, 0, 0) < 0)
+ fatal("error seeking on index file: %1", strerror(errno));
+ index_header h;
+ h.magic = INDEX_MAGIC;
+ h.version = INDEX_VERSION;
+ h.tags_size = ntags;
+ h.lists_size = li;
+ h.table_size = hash_table_size;
+ h.strings_size = filenames.length();
+ h.truncate = truncate_len;
+ h.shortest = shortest_len;
+ h.common = n_ignore_words;
+ fwrite_or_die(&h, sizeof(h), 1, indxfp);
+}
+
+static void fwrite_or_die(const void *ptr, int size, int nitems, FILE *fp)
+{
+ if (fwrite(ptr, size, nitems, fp) != (size_t)nitems)
+ fatal("fwrite failed: %1", strerror(errno));
+}
+
+void fatal_error_exit()
+{
+ cleanup();
+ exit(3);
+}
+
+extern "C" {
+
+void cleanup()
+{
+ if (temp_index_file)
+ unlink(temp_index_file);
+}
+
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/indxbib/signal.c b/src/utils/indxbib/signal.c
new file mode 100644
index 0000000..2231b64
--- /dev/null
+++ b/src/utils/indxbib/signal.c
@@ -0,0 +1,77 @@
+/* Copyright (C) 1992-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Unfortunately vendors seem to have problems writing a <signal.h>
+that is correct for C++, so we implement all signal handling in C. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <signal.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Prototype */
+void catch_fatal_signals(void);
+
+extern void cleanup(void);
+
+static RETSIGTYPE handle_fatal_signal(int signum)
+{
+ signal(signum, SIG_DFL);
+ cleanup();
+#ifdef HAVE_KILL
+ kill(getpid(), signum);
+#else
+ /* MS-DOS and Win32 don't have kill(); the best compromise is
+ probably to use exit() instead. */
+ exit(signum);
+#endif
+}
+
+void catch_fatal_signals(void)
+{
+#ifdef SIGHUP
+ signal(SIGHUP, handle_fatal_signal);
+#endif
+ signal(SIGINT, handle_fatal_signal);
+ signal(SIGTERM, handle_fatal_signal);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifndef HAVE_RENAME
+
+void ignore_fatal_signals()
+{
+#ifdef SIGHUP
+ signal(SIGHUP, SIG_IGN);
+#endif
+ signal(SIGINT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+}
+
+#endif /* not HAVE_RENAME */
diff --git a/src/utils/lkbib/lkbib.1.man b/src/utils/lkbib/lkbib.1.man
new file mode 100644
index 0000000..59ef19f
--- /dev/null
+++ b/src/utils/lkbib/lkbib.1.man
@@ -0,0 +1,212 @@
+.TH lkbib @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+lkbib \- search bibliographic databases
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_lkbib_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY lkbib
+.RB [ \-n ]
+.RB [ \-i\~\c
+.IR fields ]
+.RB [ \-p\~\c
+.IR file ]
+\&.\|.\|.\&
+.RB [ \-t\~\c
+.IR n ]
+.I key
+\&.\|.\|.
+.YS
+.
+.
+.SY lkbib
+.B \-\-help
+.YS
+.
+.
+.SY lkbib
+.B \-v
+.
+.SY lkbib
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I \%lkbib
+searches bibliographic databases for references containing keywords
+.I key
+and writes any references found to the standard output
+stream.
+.
+It reads databases given by
+.B \-p
+options
+and then
+(unless
+.B \-n
+is given)
+a default database.
+.
+The default database is taken from the
+.I \%REFER
+environment variable if it is set,
+otherwise it is
+.IR @DEFAULT_INDEX@ .
+.
+For each database
+.I file
+to be searched,
+if an index
+.RI file @INDEX_SUFFIX@
+created by
+.MR @g@indxbib @MAN1EXT@
+exists,
+then it will be searched instead;
+each index can cover multiple databases.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-i\~ string
+When searching files for which no index exists,
+ignore the contents of fields whose names are in
+.IR string .
+.
+.
+.TP
+.B \-n
+Suppress search of default database.
+.
+.
+.TP
+.BI \-p\~ file
+Search
+.IR file .
+.
+Multiple
+.B \-p
+options can be used.
+.
+.
+.TP
+.BI \-t\~ n
+Require only the first
+.I n
+characters of keys to be given.
+.
+The default
+is\~6.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I REFER
+Default database.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I \%@DEFAULT_INDEX@
+Default database to be used if the
+.I \%REFER
+environment variable is not set.
+.
+.
+.TP
+.RI file @INDEX_SUFFIX@
+Index files.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Some Applications of Inverted Indexes on the Unix System\[rq],
+by M.\& E.\& Lesk,
+1978,
+AT&T Bell Laboratories Computing Science Technical Report No.\& 69.
+.
+.
+.LP
+.MR @g@refer @MAN1EXT@ ,
+.MR @g@lookbib @MAN1EXT@ ,
+.MR @g@indxbib @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_lkbib_1_man_C]
+.do rr *groff_lkbib_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/lkbib/lkbib.am b/src/utils/lkbib/lkbib.am
new file mode 100644
index 0000000..5f75596
--- /dev/null
+++ b/src/utils/lkbib/lkbib.am
@@ -0,0 +1,30 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += lkbib
+man1_MANS += src/utils/lkbib/lkbib.1
+EXTRA_DIST += src/utils/lkbib/lkbib.1.man
+lkbib_LDADD = libbib.a libgroff.a $(LIBM) lib/libgnu.a
+lkbib_SOURCES = src/utils/lkbib/lkbib.cpp
+src/utils/lkbib/lkbib.$(OBJEXT): defs.h
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/lkbib/lkbib.cpp b/src/utils/lkbib/lkbib.cpp
new file mode 100644
index 0000000..946bd7d
--- /dev/null
+++ b/src/utils/lkbib/lkbib.cpp
@@ -0,0 +1,144 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+
+#include "defs.h"
+#include "refid.h"
+#include "search.h"
+
+extern "C" const char *Version_string;
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-n] [-p database] [-i XYZ] [-t N] key ...\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int search_default = 1;
+ search_list list;
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "nvVi:t:p:", long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'V':
+ do_verify = true;
+ break;
+ case 'n':
+ search_default = 0;
+ break;
+ case 'i':
+ linear_ignore_fields = optarg;
+ break;
+ case 't':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg) {
+ error("bad integer '%1' in 't' option", optarg);
+ break;
+ }
+ if (n < 1)
+ n = 1;
+ linear_truncate_len = int(n);
+ break;
+ }
+ case 'v':
+ {
+ printf("GNU lkbib (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case 'p':
+ list.add_file(optarg);
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ if (optind >= argc) {
+ usage(stderr);
+ exit(1);
+ }
+ char *filename = getenv("REFER");
+ if (filename)
+ list.add_file(filename);
+ else if (search_default)
+ list.add_file(DEFAULT_INDEX, 1);
+ if (list.nfiles() == 0)
+ fatal("no databases");
+ int total_len = 0;
+ int i;
+ for (i = optind; i < argc; i++)
+ total_len += strlen(argv[i]);
+ total_len += argc - optind - 1 + 1; // for spaces and '\0'
+ char *buffer = new char[total_len];
+ char *ptr = buffer;
+ for (i = optind; i < argc; i++) {
+ if (i > optind)
+ *ptr++ = ' ';
+ strcpy(ptr, argv[i]);
+ ptr = strchr(ptr, '\0');
+ }
+ search_list_iterator iter(&list, buffer);
+ const char *start;
+ int len;
+ int count;
+ for (count = 0; iter.next(&start, &len); count++) {
+ if (fwrite(start, 1, len, stdout) != (size_t)len)
+ fatal("write error on stdout: %1", strerror(errno));
+ // Can happen for last reference in file.
+ if (start[len - 1] != '\n')
+ putchar('\n');
+ putchar('\n');
+ }
+ return !count;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/lookbib/lookbib.1.man b/src/utils/lookbib/lookbib.1.man
new file mode 100644
index 0000000..5d43bbb
--- /dev/null
+++ b/src/utils/lookbib/lookbib.1.man
@@ -0,0 +1,166 @@
+.TH @g@lookbib @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@lookbib \- search bibliographic databases
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_lookbib_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@lookbib
+.RB [ \-i\~\c
+.IR string ]
+.RB [ \-t\~\c
+.IR n ]
+.I file
+\&.\|.\|.\&
+.YS
+.
+.
+.SY @g@lookbib
+.B \-\-help
+.YS
+.
+.
+.SY @g@lookbib
+.B \-v
+.
+.SY @g@lookbib
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I @g@lookbib
+writes a prompt to the standard error stream
+(unless the standard input stream is not
+a terminal),
+reads from the standard input a line containing a set of keywords,
+searches each bibliographic database
+.I file
+for references containing those keywords,
+writes any references found to the standard output stream,
+and repeats this process until the end of input.
+.
+For each database
+.I file
+to be searched,
+if an index
+.RI file @INDEX_SUFFIX@
+created by
+.MR @g@indxbib @MAN1EXT@
+exists,
+then it will be searched instead;
+each index can cover multiple databases.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-i\~ string
+When searching files for which no index exists,
+ignore the contents of fields whose names are in
+.IR string .
+.
+.
+.TP
+.BI \-t\~ n
+Require only the first
+.I n
+characters of keys to be given.
+.
+The default
+is\~6.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.RI file @INDEX_SUFFIX@
+Index files.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Some Applications of Inverted Indexes on the Unix System\[rq],
+by M.\& E.\& Lesk,
+1978,
+AT&T Bell Laboratories Computing Science Technical Report No.\& 69.
+.
+.
+.LP
+.MR @g@refer @MAN1EXT@ ,
+.MR lkbib @MAN1EXT@ ,
+.MR @g@indxbib @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_lookbib_1_man_C]
+.do rr *groff_lookbib_1_man_C
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/lookbib/lookbib.am b/src/utils/lookbib/lookbib.am
new file mode 100644
index 0000000..75103c1
--- /dev/null
+++ b/src/utils/lookbib/lookbib.am
@@ -0,0 +1,29 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += lookbib
+PREFIXMAN1 += src/utils/lookbib/lookbib.1
+EXTRA_DIST += src/utils/lookbib/lookbib.1.man
+lookbib_LDADD = libbib.a libgroff.a $(LIBM) lib/libgnu.a
+lookbib_SOURCES = src/utils/lookbib/lookbib.cpp
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/lookbib/lookbib.cpp b/src/utils/lookbib/lookbib.cpp
new file mode 100644
index 0000000..d8556c6
--- /dev/null
+++ b/src/utils/lookbib/lookbib.cpp
@@ -0,0 +1,146 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "cset.h"
+
+#include "refid.h"
+#include "search.h"
+
+/* for isatty() */
+#include "posix.h"
+#include "nonposix.h"
+
+extern "C" const char *Version_string;
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-i XYZ] [-t N] database ...\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "vVi:t:", long_options, NULL)) != EOF)
+ switch (opt) {
+ case 'V':
+ do_verify = true;
+ break;
+ case 'i':
+ linear_ignore_fields = optarg;
+ break;
+ case 't':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg) {
+ error("bad integer '%1' in 't' option", optarg);
+ break;
+ }
+ if (n < 1)
+ n = 1;
+ linear_truncate_len = int(n);
+ break;
+ }
+ case 'v':
+ {
+ printf("GNU lookbib (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ if (optind >= argc) {
+ usage(stderr);
+ exit(1);
+ }
+ search_list list;
+ for (int i = optind; i < argc; i++)
+ list.add_file(argv[i]);
+ if (list.nfiles() == 0)
+ fatal("no databases");
+ char line[1024];
+ int interactive = isatty(fileno(stdin));
+ for (;;) {
+ if (interactive) {
+ fputs("> ", stderr);
+ fflush(stderr);
+ }
+ if (!fgets(line, sizeof(line), stdin))
+ break;
+ char *ptr = line;
+ while (csspace(*ptr))
+ ptr++;
+ if (*ptr == '\0')
+ continue;
+ search_list_iterator iter(&list, line);
+ const char *start;
+ int len;
+ int count;
+ for (count = 0; iter.next(&start, &len); count++) {
+ if (fwrite(start, 1, len, stdout) != (size_t)len)
+ fatal("write error on stdout: %1", strerror(errno));
+ // Can happen for last reference in file.
+ if (start[len - 1] != '\n')
+ putchar('\n');
+ putchar('\n');
+ }
+ fflush(stdout);
+ if (interactive) {
+ fprintf(stderr, "%d found\n", count);
+ fflush(stderr);
+ }
+ }
+ if (interactive)
+ putc('\n', stderr);
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/pfbtops/pfbtops.1.man b/src/utils/pfbtops/pfbtops.1.man
new file mode 100644
index 0000000..71140c6
--- /dev/null
+++ b/src/utils/pfbtops/pfbtops.1.man
@@ -0,0 +1,129 @@
+.TH pfbtops @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+pfbtops \- translate PostScript Printer Font Binary files to Printer
+Font ASCII
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_pfbtops_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY pfbtops
+.RI [ pfb-file ]
+.YS
+.
+.
+.SY pfbtops
+.B \-\-help
+.YS
+.
+.
+.SY pfbtops
+.B \-v
+.
+.SY pfbtops
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I pfbtops
+translates a PostScript Type\~1 font in Printer Font Binary (PFB) format
+to Printer Font ASCII (PFA) format,
+splitting overlong lines in text packets into smaller chunks.
+.
+If
+.I pfb-file
+is omitted,
+the PFB file will be read from the standard input stream.
+.
+The PFA font will be written on the standard output stream.
+.
+PostScript fonts for MS-DOS were historically supplied in PFB format.
+.
+Use of a PostScript Type\~1 font with
+.I groff
+requires conversion of its metrics
+(AFM file)
+to a
+.I groff
+font description file;
+see
+.MR afmtodit @MAN1EXT@ .
+.
+.
+.P
+The
+.B \-\-help
+option displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR grops @MAN1EXT@ ,
+.MR gropdf @MAN1EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_pfbtops_1_man_C]
+.do rr *groff_pfbtops_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/pfbtops/pfbtops.am b/src/utils/pfbtops/pfbtops.am
new file mode 100644
index 0000000..8b7fd71
--- /dev/null
+++ b/src/utils/pfbtops/pfbtops.am
@@ -0,0 +1,32 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += pfbtops
+man1_MANS += src/utils/pfbtops/pfbtops.1
+EXTRA_DIST += src/utils/pfbtops/pfbtops.1.man
+pfbtops_SOURCES = src/utils/pfbtops/pfbtops.c
+pfbtops_LDADD = libgroff.a $(LIBM) lib/libgnu.a
+# We use the following trick to force the use of C++ compiler
+# See the Automake manual, "Libtool Convenience Libraries"
+nodist_EXTRA_pfbtops_SOURCES = src/utils/pfbtops/dummy.cpp
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/pfbtops/pfbtops.c b/src/utils/pfbtops/pfbtops.c
new file mode 100644
index 0000000..8fbe44a
--- /dev/null
+++ b/src/utils/pfbtops/pfbtops.c
@@ -0,0 +1,243 @@
+/* Copyright (C) 1992-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This translates ps fonts in .pfb format to ASCII ps files. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define __GETOPT_PREFIX groff_
+
+#include <errno.h> // errno
+#include <stdio.h>
+#include <stdlib.h> // exit(), EXIT_FAILURE, EXIT_SUCCESS
+#include <string.h> // strerror()
+#include <limits.h>
+
+#include <getopt.h>
+
+#include "nonposix.h"
+
+/* Binary bytes per output line. */
+#define BYTES_PER_LINE (64/2)
+#define MAX_LINE_LENGTH 78
+#define HEX_DIGITS "0123456789abcdef"
+
+extern const char *Version_string;
+
+static char *program_name;
+
+static void error(const char *s)
+{
+ fprintf(stderr, "%s: error: %s\n", program_name, s);
+ exit(EXIT_FAILURE);
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream, "usage: %s [pfb-file]\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+static void get_text(int n)
+{
+ int c = 0, c1;
+ int in_string = 0;
+ int is_comment = 0;
+ int count = 0;
+
+ while (--n >= 0) {
+ c = getchar();
+ if (c == '(' && !is_comment)
+ in_string++;
+ else if (c == ')' && !is_comment)
+ in_string--;
+ else if (c == '%' && !in_string)
+ is_comment = 1;
+ else if (c == '\\' && in_string) {
+ count++;
+ putchar(c);
+ if (n-- == 0)
+ break;
+ c = getchar();
+ /* don't split octal character representations */
+ if (c >= '0' && c <= '7') {
+ count++;
+ putchar(c);
+ if (n-- == 0)
+ break;
+ c = getchar();
+ if (c >= '0' && c <= '7') {
+ count++;
+ putchar(c);
+ if (n-- == 0)
+ break;
+ c = getchar();
+ if (c >= '0' && c <= '7') {
+ count++;
+ putchar(c);
+ if (n-- == 0)
+ break;
+ c = getchar();
+ }
+ }
+ }
+ }
+ if (c == EOF)
+ error("end of file in text packet");
+ else if (c == '\r') {
+ if (n-- == 0)
+ break;
+ c1 = getchar();
+ if (c1 != '\n') {
+ ungetc(c1, stdin);
+ n++;
+ }
+ c = '\n';
+ }
+ if (c == '\n') {
+ count = 0;
+ is_comment = 0;
+ }
+ else if (count >= MAX_LINE_LENGTH) {
+ if (in_string > 0) {
+ count = 1;
+ putchar('\\');
+ putchar('\n');
+ }
+ else if (is_comment) {
+ count = 2;
+ putchar('\n');
+ putchar('%');
+ }
+ else {
+ /* split at the next whitespace character */
+ while (c != ' ' && c != '\t' && c != '\f') {
+ putchar(c);
+ if (n-- == 0)
+ break;
+ c = getchar();
+ }
+ count = 0;
+ putchar('\n');
+ continue;
+ }
+ }
+ count++;
+ putchar(c);
+ }
+ if (c != '\n')
+ putchar('\n');
+}
+
+static void get_binary(int n)
+{
+ int c;
+ int count = 0;
+
+ while (--n >= 0) {
+ c = getchar();
+ if (c == EOF)
+ error("end of file in binary packet");
+ if (count >= BYTES_PER_LINE) {
+ putchar('\n');
+ count = 0;
+ }
+ count++;
+ putchar(HEX_DIGITS[(c >> 4) & 0xf]);
+ putchar(HEX_DIGITS[c & 0xf]);
+ }
+ putchar('\n');
+}
+
+int main(int argc, char **argv)
+{
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+
+ program_name = argv[0];
+
+ while ((opt = getopt_long(argc, argv, "v", long_options, NULL)) != EOF) {
+ switch (opt) {
+ case 'v':
+ printf("GNU pfbtops (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ case CHAR_MAX + 1: /* --help */
+ usage(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case '?':
+ usage(stderr);
+ exit(2);
+ break;
+ }
+ }
+
+ if (argc - optind > 1) {
+ usage(stderr);
+ exit(2);
+ }
+ const char *file = argv[optind];
+ if (argc > optind && !freopen(file, "r", stdin)) {
+ fprintf(stderr, "%s: error: unable to open file '%s': %s\n",
+ program_name, file, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ SET_BINARY(fileno(stdin));
+ for (;;) {
+ int type, c, i;
+ long n;
+
+ c = getchar();
+ if (c != 0x80)
+ error("first byte of packet not 0x80");
+ type = getchar();
+ if (type == 3)
+ break;
+ if (type != 1 && type != 2)
+ error("bad packet type");
+ n = 0;
+ for (i = 0; i < 4; i++) {
+ c = getchar();
+ if (c == EOF)
+ error("end of file in packet header");
+ n |= (long)c << (i << 3);
+ }
+ if (n < 0)
+ error("negative packet length");
+ if (type == 1)
+ get_text(n);
+ else
+ get_binary(n);
+ }
+ exit(EXIT_SUCCESS);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/tfmtodit/tfmtodit.1.man b/src/utils/tfmtodit/tfmtodit.1.man
new file mode 100644
index 0000000..0f21753
--- /dev/null
+++ b/src/utils/tfmtodit/tfmtodit.1.man
@@ -0,0 +1,415 @@
+.TH tfmtodit @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+tfmtodit \- adapt TeX Font Metrics files for use with
+.I groff
+and
+.I grodvi
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_tfmtodit_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.\" Definitions
+.\" ====================================================================
+.
+.ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el .ds tx TeX
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY tfmtodit
+.RB [ \-s ]
+.RB [ \-g\~\c
+.IR gf-file ]
+.RB [ \-k\~\c
+.IR skew-char ]
+.I tfm-file
+.I map-file
+.I font-description
+.YS
+.
+.
+.SY tfmtodit
+.B \-\-help
+.YS
+.
+.
+.SY tfmtodit
+.B \-v
+.
+.SY tfmtodit
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I tfmtodit
+creates a font description file for use with
+.MR groff @MAN1EXT@ 's
+.B dvi
+output device.
+.
+.I tfm-file
+is the name of the \*(tx font metric file for the font.
+.
+.I map-file
+assigns
+.I groff
+ordinary or special character identifiers to glyph indices in the font;
+it should consist of a sequence of lines of the form
+.
+.RS
+.IR "i c1" \~\&.\|.\|.\&\~ cn
+.RE
+.
+where
+.I i
+is a position of the glyph in the font in decimal,
+and
+.I c1
+through
+.I cn
+are glyph identifiers in the form used by
+.I groff
+font descriptions.
+.
+If a glyph has no
+.I groff
+names but exists in
+.I tfm-file,
+it is put in the
+.I groff
+font description file as an unnamed glyph.
+.
+Output is written in
+.MR groff_font @MAN5EXT@
+format to
+.I font-description,
+a file named for the intended
+.I groff
+font name.
+.
+.
+.P
+If the font is \[lq]special\[rq],
+meaning that
+.I groff
+should search it whenever a glyph is not found in the current font,
+use the
+.B \-s
+option and name
+.I font-description
+in the
+.B fonts
+directive in the output device's
+.I DESC
+file.
+.
+.
+.P
+To do a good job of math typesetting,
+.I groff
+requires font metric information not present in
+.I tfm-file.
+.
+This is because \*(tx has separate math italic fonts,
+whereas
+.I groff
+uses normal italic fonts for math.
+.
+The additional information required by
+.I groff
+is given by the two arguments to the
+.B math_fit
+macro in the Metafont programs for the Computer Modern fonts.
+.
+In a text font (a font for which
+.B math_fit
+is false),
+Metafont normally ignores these two arguments.
+.
+Metafont can be made to put this information into the GF
+(\[lq]generic font\[rq])
+files it produces by loading the following definition after
+.B cmbase
+when creating
+.IR cm.base .
+.
+.RS
+.EX
+def ignore_math_fit(expr left_adjustment,right_adjustment) =
+ special "adjustment";
+ numspecial left_adjustment*16/designsize;
+ numspecial right_adjustment*16/designsize;
+ enddef;
+.EE
+.RE
+.
+For the EC font family,
+load the following definition after
+.BR exbase ;
+consider patching
+.I exbase.mf
+locally.
+.
+.RS
+.EX
+def ignore_math_fit(expr left_adjustment,right_adjustment) =
+ ori_special "adjustment";
+ ori_numspecial left_adjustment*16/designsize;
+ ori_numspecial right_adjustment*16/designsize;
+ enddef;
+.EE
+.RE
+.
+The only difference from the previous example is the \[lq]ori_\[rq]
+prefix to \[lq]special\[rq] and \[lq]numspecial\[rq].
+.
+The GF file created using this modified
+.I cm.base
+or
+.I exbase.mf
+should be specified with the
+.B \-g
+option,
+which should
+.I not
+be given for a font for which
+.B math_fit
+is true.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-g \~gf-file
+Use the
+.I gf-file
+produced by Metafont containing
+.RB \[lq] special \[rq]
+and
+.RB \[lq] numspecial \[rq]
+commands to obtain additional font metric information.
+.
+.
+.TP
+.BI \-k \~skew-char
+The skew character of this font is at position
+.I skew-char.
+.
+.I skew-char
+should be an integer;
+it may be given in decimal,
+with a leading 0 in octal,
+or with a leading 0x in hexadecimal.
+.
+Any kerns whose second component is
+.I skew-char
+are ignored.
+.
+.
+.TP
+.B \-s
+Add the
+.B special
+directive to the font description file.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devdvi/\:DESC
+describes the
+.B dvi
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devdvi/ F
+describes the font known
+.RI as\~ F
+on device
+.BR dvi .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%ec.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%msam.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%msbm.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%tc.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%texb.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%texex.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%texi.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%texitt.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%texmi.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%texr.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%texsy.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%textex.map
+.TQ
+.I @FONTDIR@/\:\%devdvi/\:\%generate/\:\%textt.map
+map glyph indices in \*[tx] fonts to
+.I groff
+ordinary and special character identifiers.
+.
+.I \%ec.map
+is used for
+.BR TREC ,
+.BR TIEC ,
+.BR TBEC ,
+.BR TBIEC ,
+.BR HREC ,
+.BR HIEC ,
+.BR HBEC ,
+.BR HBIEC ,
+.BR CWEC ,
+and
+.BR CWIEC ;
+.I \%msam.map
+for
+.BR SA ;
+.I \%msbm.map
+for
+.BR SB ;
+.I \%tc.map
+for
+.BR TRTC ,
+.BR TITC ,
+.BR TBTC ,
+.BR TBITC ,
+.BR HRTC ,
+.BR HITC ,
+.BR HBTC ,
+.BR HBITC ,
+.BR CWTC ,
+and
+.BR CWITC ;
+.I \%texb.map
+for
+.BR TB ,
+.BR HR ,
+.BR HI ,
+.BR HB ,
+and
+.BR HBI ;
+.I \%texex.map
+for
+.BR EX ;
+.I \%texi.map
+for
+.B TI
+and
+.BR TBI ;
+.I \%texitt.map
+for
+.BR CWI ;
+.I \%texmi.map
+for
+.BR MI ;
+.I \%texr.map
+for
+.BR TR ;
+.I \%texsy.map
+for
+.BR S ;
+.I \%textex.map
+for
+.BR SC ;
+and
+.I \%textt.map
+for
+.BR CW .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@ ,
+.MR grodvi @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm tx
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_tfmtodit_1_man_C]
+.do rr *groff_tfmtodit_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/tfmtodit/tfmtodit.am b/src/utils/tfmtodit/tfmtodit.am
new file mode 100644
index 0000000..758fad5
--- /dev/null
+++ b/src/utils/tfmtodit/tfmtodit.am
@@ -0,0 +1,29 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += tfmtodit
+man1_MANS += src/utils/tfmtodit/tfmtodit.1
+EXTRA_DIST += src/utils/tfmtodit/tfmtodit.1.man
+tfmtodit_SOURCES = src/utils/tfmtodit/tfmtodit.cpp
+tfmtodit_LDADD = libgroff.a $(LIBM) lib/libgnu.a
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/tfmtodit/tfmtodit.cpp b/src/utils/tfmtodit/tfmtodit.cpp
new file mode 100644
index 0000000..3003733
--- /dev/null
+++ b/src/utils/tfmtodit/tfmtodit.cpp
@@ -0,0 +1,889 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* I have tried to incorporate the changes needed for TeX 3.0 tfm files,
+but I haven't tested them. */
+
+/* Groff requires more font metric information than TeX. The reason
+for this is that TeX has separate Math Italic fonts, whereas groff
+uses normal italic fonts for math. The two additional pieces of
+information required by groff correspond to the two arguments to the
+math_fit() macro in the Metafont programs for the CM fonts. In the
+case of a font for which math_fitting is false, these two arguments
+are normally ignored by Metafont. We need to get hold of these two
+parameters and put them in the groff font file.
+
+We do this by loading this definition after cmbase when creating
+cm.base.
+
+def ignore_math_fit(expr left_adjustment,right_adjustment) =
+ special "adjustment";
+ numspecial left_adjustment*16/designsize;
+ numspecial right_adjustment*16/designsize;
+ enddef;
+
+This puts the two arguments to the math_fit macro into the gf file.
+(They will appear in the gf file immediately before the character to
+which they apply.) We then create a gf file using this cm.base. Then
+we run tfmtodit and specify this gf file with the -g option.
+
+This need only be done for a font for which math_fitting is false;
+When it's true, the left_correction and subscript_correction should
+both be zero. */
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <math.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "cset.h"
+#include "nonposix.h"
+
+extern "C" const char *Version_string;
+
+/* Values in the tfm file should be multiplied by this. */
+
+#define MULTIPLIER 1
+
+struct char_info_word {
+ unsigned char width_index;
+ unsigned char height_index;
+ unsigned char depth_index;
+ unsigned char italic_index;
+ unsigned char tag;
+ unsigned char remainder;
+};
+
+struct lig_kern_command {
+ unsigned char skip_byte;
+ unsigned char next_char;
+ unsigned char op_byte;
+ unsigned char remainder;
+};
+
+class tfm {
+ int bc;
+ int ec;
+ int nw;
+ int nh;
+ int nd;
+ int ni;
+ int nl;
+ int nk;
+ int np;
+ int cs;
+ int ds;
+ char_info_word *char_info;
+ int *width;
+ int *height;
+ int *depth;
+ int *italic;
+ lig_kern_command *lig_kern;
+ int *kern;
+ int *param;
+public:
+ tfm();
+ ~tfm();
+ int load(const char *);
+ int contains(int);
+ int get_width(int);
+ int get_height(int);
+ int get_depth(int);
+ int get_italic(int);
+ int get_param(int, int *);
+ int get_checksum();
+ int get_design_size();
+ int get_lig(unsigned char, unsigned char, unsigned char *);
+ friend class kern_iterator;
+};
+
+class kern_iterator {
+ tfm *t;
+ int c;
+ int i;
+public:
+ kern_iterator(tfm *);
+ int next(unsigned char *c1, unsigned char *c2, int *k);
+};
+
+
+kern_iterator::kern_iterator(tfm *p)
+: t(p), c(t->bc), i(-1)
+{
+}
+
+int kern_iterator::next(unsigned char *c1, unsigned char *c2, int *k)
+{
+ for (; c <= t->ec; c++)
+ if (t->char_info[c - t->bc].tag == 1) {
+ if (i < 0) {
+ i = t->char_info[c - t->bc].remainder;
+ if (t->lig_kern[i].skip_byte > 128)
+ i = (256*t->lig_kern[i].op_byte
+ + t->lig_kern[i].remainder);
+ }
+ for (;;) {
+ int skip = t->lig_kern[i].skip_byte;
+ if (skip <= 128 && t->lig_kern[i].op_byte >= 128) {
+ *c1 = c;
+ *c2 = t->lig_kern[i].next_char;
+ *k = t->kern[256*(t->lig_kern[i].op_byte - 128)
+ + t->lig_kern[i].remainder];
+ if (skip == 128) {
+ c++;
+ i = -1;
+ }
+ else
+ i += skip + 1;
+ return 1;
+ }
+ if (skip >= 128)
+ break;
+ i += skip + 1;
+ }
+ i = -1;
+ }
+ return 0;
+}
+
+tfm::tfm()
+: char_info(0), width(0), height(0), depth(0), italic(0), lig_kern(0),
+ kern(0), param(0)
+{
+}
+
+int tfm::get_lig(unsigned char c1, unsigned char c2, unsigned char *cp)
+{
+ if (contains(c1) && char_info[c1 - bc].tag == 1) {
+ int i = char_info[c1 - bc].remainder;
+ if (lig_kern[i].skip_byte > 128)
+ i = 256*lig_kern[i].op_byte + lig_kern[i].remainder;
+ for (;;) {
+ int skip = lig_kern[i].skip_byte;
+ if (skip > 128)
+ break;
+ // We are only interested in normal ligatures, for which
+ // op_byte == 0.
+ if (lig_kern[i].op_byte == 0
+ && lig_kern[i].next_char == c2) {
+ *cp = lig_kern[i].remainder;
+ return 1;
+ }
+ if (skip == 128)
+ break;
+ i += skip + 1;
+ }
+ }
+ return 0;
+}
+
+int tfm::contains(int i)
+{
+ return i >= bc && i <= ec && char_info[i - bc].width_index != 0;
+}
+
+int tfm::get_width(int i)
+{
+ return width[char_info[i - bc].width_index];
+}
+
+int tfm::get_height(int i)
+{
+ return height[char_info[i - bc].height_index];
+}
+
+int tfm::get_depth(int i)
+{
+ return depth[char_info[i - bc].depth_index];
+}
+
+int tfm::get_italic(int i)
+{
+ return italic[char_info[i - bc].italic_index];
+}
+
+int tfm::get_param(int i, int *p)
+{
+ if (i <= 0 || i > np)
+ return 0;
+ else {
+ *p = param[i - 1];
+ return 1;
+ }
+}
+
+int tfm::get_checksum()
+{
+ return cs;
+}
+
+int tfm::get_design_size()
+{
+ return ds;
+}
+
+tfm::~tfm()
+{
+ delete[] char_info;
+ delete[] width;
+ delete[] height;
+ delete[] depth;
+ delete[] italic;
+ delete[] lig_kern;
+ delete[] kern;
+ delete[] param;
+}
+
+int read2(unsigned char *&s)
+{
+ int n;
+ n = *s++ << 8;
+ n |= *s++;
+ return n;
+}
+
+int read4(unsigned char *&s)
+{
+ int n;
+ n = *s++ << 24;
+ n |= *s++ << 16;
+ n |= *s++ << 8;
+ n |= *s++;
+ return n;
+}
+
+int tfm::load(const char *file)
+{
+ errno = 0;
+ FILE *fp = fopen(file, FOPEN_RB);
+ if (!fp) {
+ error("can't open '%1': %2", file, strerror(errno));
+ return 0;
+ }
+ int c1 = getc(fp);
+ int c2 = getc(fp);
+ if (c1 == EOF || c2 == EOF) {
+ fclose(fp);
+ error("unexpected end of file on '%1'", file);
+ return 0;
+ }
+ int lf = (c1 << 8) + c2;
+ int toread = lf*4 - 2;
+ unsigned char *buf = new unsigned char[toread];
+ if (fread(buf, 1, toread, fp) != (size_t)toread) {
+ if (feof(fp))
+ error("unexpected end of file on '%1'", file);
+ else
+ error("error on file '%1'", file);
+ delete[] buf;
+ fclose(fp);
+ return 0;
+ }
+ fclose(fp);
+ if (lf < 6) {
+ error("bad TFM file '%1': impossibly short", file);
+ delete[] buf;
+ return 0;
+ }
+ unsigned char *ptr = buf;
+ int lh = read2(ptr);
+ bc = read2(ptr);
+ ec = read2(ptr);
+ nw = read2(ptr);
+ nh = read2(ptr);
+ nd = read2(ptr);
+ ni = read2(ptr);
+ nl = read2(ptr);
+ nk = read2(ptr);
+ int ne = read2(ptr);
+ np = read2(ptr);
+ if ((6 + lh + (ec - bc + 1) + nw + nh + nd + ni + nl + nk + ne + np)
+ != lf) {
+ error("bad TFM file '%1': lengths do not sum", file);
+ delete[] buf;
+ return 0;
+ }
+ if (lh < 2) {
+ error("bad TFM file '%1': header too short", file);
+ delete[] buf;
+ return 0;
+ }
+ char_info = new char_info_word[ec - bc + 1];
+ width = new int[nw];
+ height = new int[nh];
+ depth = new int[nd];
+ italic = new int[ni];
+ lig_kern = new lig_kern_command[nl];
+ kern = new int[nk];
+ param = new int[np];
+ int i;
+ cs = read4(ptr);
+ ds = read4(ptr);
+ ptr += (lh-2)*4;
+ for (i = 0; i < ec - bc + 1; i++) {
+ char_info[i].width_index = *ptr++;
+ unsigned char tem = *ptr++;
+ char_info[i].depth_index = tem & 0xf;
+ char_info[i].height_index = tem >> 4;
+ tem = *ptr++;
+ char_info[i].italic_index = tem >> 2;
+ char_info[i].tag = tem & 3;
+ char_info[i].remainder = *ptr++;
+ }
+ for (i = 0; i < nw; i++)
+ width[i] = read4(ptr);
+ for (i = 0; i < nh; i++)
+ height[i] = read4(ptr);
+ for (i = 0; i < nd; i++)
+ depth[i] = read4(ptr);
+ for (i = 0; i < ni; i++)
+ italic[i] = read4(ptr);
+ for (i = 0; i < nl; i++) {
+ lig_kern[i].skip_byte = *ptr++;
+ lig_kern[i].next_char = *ptr++;
+ lig_kern[i].op_byte = *ptr++;
+ lig_kern[i].remainder = *ptr++;
+ }
+ for (i = 0; i < nk; i++)
+ kern[i] = read4(ptr);
+ ptr += ne*4;
+ for (i = 0; i < np; i++)
+ param[i] = read4(ptr);
+ assert(ptr == buf + lf*4 - 2);
+ delete[] buf;
+ return 1;
+}
+
+class gf {
+ int left[256];
+ int right[256];
+ static int sread4(int *p, FILE *fp);
+ static int uread3(int *p, FILE *fp);
+ static int uread2(int *p, FILE *fp);
+ static int skip(int n, FILE *fp);
+public:
+ gf();
+ int load(const char *file);
+ int get_left_adjustment(int i) { return left[i]; }
+ int get_right_adjustment(int i) { return right[i]; }
+};
+
+gf::gf()
+{
+ for (int i = 0; i < 256; i++)
+ left[i] = right[i] = 0;
+}
+
+int gf::load(const char *file)
+{
+ enum {
+ paint_0 = 0,
+ paint1 = 64,
+ boc = 67,
+ boc1 = 68,
+ eoc = 69,
+ skip0 = 70,
+ skip1 = 71,
+ new_row_0 = 74,
+ xxx1 = 239,
+ yyy = 243,
+ no_op = 244,
+ pre = 247,
+ post = 248
+ };
+ int got_an_adjustment = 0;
+ int pending_adjustment = 0;
+ int left_adj = 0, right_adj = 0; // pacify compiler
+ const int gf_id_byte = 131;
+ errno = 0;
+ FILE *fp = fopen(file, FOPEN_RB);
+ if (!fp) {
+ error("can't open '%1': %2", file, strerror(errno));
+ return 0;
+ }
+ if (getc(fp) != pre || getc(fp) != gf_id_byte) {
+ error("bad gf file");
+ return 0;
+ }
+ int n = getc(fp);
+ if (n == EOF)
+ goto eof;
+ if (!skip(n, fp))
+ goto eof;
+ for (;;) {
+ int op = getc(fp);
+ if (op == EOF)
+ goto eof;
+ if (op == post)
+ break;
+ if ((op >= paint_0 && op <= paint_0 + 63)
+ || (op >= new_row_0 && op <= new_row_0 + 164))
+ continue;
+ switch (op) {
+ case no_op:
+ case eoc:
+ case skip0:
+ break;
+ case paint1:
+ case skip1:
+ if (!skip(1, fp))
+ goto eof;
+ break;
+ case paint1 + 1:
+ case skip1 + 1:
+ if (!skip(2, fp))
+ goto eof;
+ break;
+ case paint1 + 2:
+ case skip1 + 2:
+ if (!skip(3, fp))
+ goto eof;
+ break;
+ case boc:
+ {
+ int code;
+ if (!sread4(&code, fp))
+ goto eof;
+ if (pending_adjustment) {
+ pending_adjustment = 0;
+ left[code & 0377] = left_adj;
+ right[code & 0377] = right_adj;
+ }
+ if (!skip(20, fp))
+ goto eof;
+ break;
+ }
+ case boc1:
+ {
+ int code = getc(fp);
+ if (code == EOF)
+ goto eof;
+ if (pending_adjustment) {
+ pending_adjustment = 0;
+ left[code] = left_adj;
+ right[code] = right_adj;
+ }
+ if (!skip(4, fp))
+ goto eof;
+ break;
+ }
+ case xxx1:
+ {
+ int len = getc(fp);
+ if (len == EOF)
+ goto eof;
+ char buf[256];
+ if (fread(buf, 1, len, fp) != (size_t)len)
+ goto eof;
+ if (len == 10 /* strlen("adjustment") */
+ && memcmp(buf, "adjustment", len) == 0) {
+ int c = getc(fp);
+ if (c != yyy) {
+ if (c != EOF)
+ ungetc(c, fp);
+ break;
+ }
+ if (!sread4(&left_adj, fp))
+ goto eof;
+ c = getc(fp);
+ if (c != yyy) {
+ if (c != EOF)
+ ungetc(c, fp);
+ break;
+ }
+ if (!sread4(&right_adj, fp))
+ goto eof;
+ got_an_adjustment = 1;
+ pending_adjustment = 1;
+ }
+ break;
+ }
+ case xxx1 + 1:
+ if (!uread2(&n, fp) || !skip(n, fp))
+ goto eof;
+ break;
+ case xxx1 + 2:
+ if (!uread3(&n, fp) || !skip(n, fp))
+ goto eof;
+ break;
+ case xxx1 + 3:
+ if (!sread4(&n, fp) || !skip(n, fp))
+ goto eof;
+ break;
+ case yyy:
+ if (!skip(4, fp))
+ goto eof;
+ break;
+ default:
+ fatal("unrecognized opcode '%1'", op);
+ break;
+ }
+ }
+ if (!got_an_adjustment)
+ warning("no adjustment specials found in gf file");
+ return 1;
+ eof:
+ error("unexpected end of file");
+ return 0;
+}
+
+int gf::sread4(int *p, FILE *fp)
+{
+ *p = getc(fp);
+ if (*p >= 128)
+ *p -= 256;
+ *p <<= 8;
+ *p |= getc(fp);
+ *p <<= 8;
+ *p |= getc(fp);
+ *p <<= 8;
+ *p |= getc(fp);
+ return !ferror(fp) && !feof(fp);
+}
+
+int gf::uread3(int *p, FILE *fp)
+{
+ *p = getc(fp);
+ *p <<= 8;
+ *p |= getc(fp);
+ *p <<= 8;
+ *p |= getc(fp);
+ return !ferror(fp) && !feof(fp);
+}
+
+int gf::uread2(int *p, FILE *fp)
+{
+ *p = getc(fp);
+ *p <<= 8;
+ *p |= getc(fp);
+ return !ferror(fp) && !feof(fp);
+}
+
+int gf::skip(int n, FILE *fp)
+{
+ while (--n >= 0)
+ if (getc(fp) == EOF)
+ return 0;
+ return 1;
+}
+
+
+struct char_list {
+ char *ch;
+ char_list *next;
+ char_list(const char *, char_list * = 0);
+};
+
+char_list::char_list(const char *s, char_list *p) : ch(strsave(s)),
+ next(p)
+{
+}
+
+
+int read_map(const char *file, char_list **table)
+{
+ errno = 0;
+ FILE *fp = fopen(file, "r");
+ if (!fp) {
+ error("can't open '%1': %2", file, strerror(errno));
+ return 0;
+ }
+ for (int i = 0; i < 256; i++)
+ table[i] = 0;
+ char buf[512];
+ int lineno = 0;
+ while (fgets(buf, int(sizeof(buf)), fp)) {
+ lineno++;
+ char *ptr = buf;
+ while (csspace(*ptr))
+ ptr++;
+ if (*ptr == '\0' || *ptr == '#')
+ continue;
+ ptr = strtok(ptr, " \n\t");
+ if (!ptr)
+ continue;
+ int n;
+ if (sscanf(ptr, "%d", &n) != 1) {
+ error("%1:%2: bad map file", file, lineno);
+ fclose(fp);
+ return 0;
+ }
+ if (n < 0 || n > 255) {
+ error("%1:%2: code %3 out of range", file, lineno, n);
+ fclose(fp);
+ return 0;
+ }
+ ptr = strtok(0, " \n\t");
+ if (!ptr) {
+ error("%1:%2: missing names", file, lineno);
+ fclose(fp);
+ return 0;
+ }
+ for (; ptr; ptr = strtok(0, " \n\t"))
+ table[n] = new char_list(ptr, table[n]);
+ }
+ fclose(fp);
+ return 1;
+}
+
+
+/* Every character that can participate in a ligature appears in the
+lig_chars table. 'ch' gives the full-name of the character, 'name'
+gives the groff name of the character, 'i' gives its index in
+the encoding, which is filled in later (-1 if it does not appear). */
+
+struct S {
+ const char *ch;
+ int i;
+} lig_chars[] = {
+ { "f", -1 },
+ { "i", -1 },
+ { "l", -1 },
+ { "ff", -1 },
+ { "fi", -1 },
+ { "fl", -1 },
+ { "Fi", -1 },
+ { "Fl", -1 },
+};
+
+// Indices into lig_chars[].
+
+enum { CH_f, CH_i, CH_l, CH_ff, CH_fi, CH_fl, CH_ffi, CH_ffl };
+
+// Each possible ligature appears in this table.
+
+struct S2 {
+ unsigned char c1, c2, res;
+ const char *ch;
+} lig_table[] = {
+ { CH_f, CH_f, CH_ff, "ff" },
+ { CH_f, CH_i, CH_fi, "fi" },
+ { CH_f, CH_l, CH_fl, "fl" },
+ { CH_ff, CH_i, CH_ffi, "ffi" },
+ { CH_ff, CH_l, CH_ffl, "ffl" },
+ };
+
+static void usage(FILE *stream);
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ int special_flag = 0;
+ int skewchar = -1;
+ int opt;
+ const char *gf_file = 0;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(argc, argv, "svg:k:", long_options, NULL))
+ != EOF)
+ switch (opt) {
+ case 'g':
+ gf_file = optarg;
+ break;
+ case 's':
+ special_flag = 1;
+ break;
+ case 'k':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 0);
+ if ((n == 0 && ptr == optarg)
+ || *ptr != '\0'
+ || n < 0
+ || n > UCHAR_MAX)
+ error("invalid skew character position '%1'", optarg);
+ else
+ skewchar = (int)n;
+ break;
+ }
+ case 'v':
+ {
+ printf("GNU tfmtodit (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ case EOF:
+ assert(0 == "EOF encountered in option processing");
+ }
+ if (argc - optind != 3) {
+ error("insufficient arguments");
+ usage(stderr);
+ exit(1);
+ }
+ gf g;
+ if (gf_file) {
+ if (!g.load(gf_file))
+ return 1;
+ }
+ const char *tfm_file = argv[optind];
+ const char *map_file = argv[optind + 1];
+ const char *font_file = argv[optind + 2];
+ tfm t;
+ if (!t.load(tfm_file))
+ return 1;
+ char_list *table[256];
+ if (!read_map(map_file, table))
+ return 1;
+ errno = 0;
+ if (!freopen(font_file, "w", stdout)) {
+ error("can't open '%1' for writing: %2", font_file,
+ strerror(errno));
+ return 1;
+ }
+ printf("name %s\n", font_file);
+ if (special_flag)
+ fputs("special\n", stdout);
+ char *internal_name = strsave(argv[optind]);
+ int len = strlen(internal_name);
+ if (len > 4 && strcmp(internal_name + len - 4, ".tfm") == 0)
+ internal_name[len - 4] = '\0';
+ // DIR_SEPS[] are possible directory separator characters, see
+ // nonposix.h. We want the rightmost separator of all possible ones.
+ // Example: d:/foo\\bar.
+ const char *s = strrchr(internal_name, DIR_SEPS[0]), *s1;
+ const char *sep = &DIR_SEPS[1];
+ while (*sep)
+ {
+ s1 = strrchr(internal_name, *sep);
+ if (s1 && (!s || s1 > s))
+ s = s1;
+ sep++;
+ }
+ printf("internalname %s\n", s ? s + 1 : internal_name);
+ int n;
+ if (t.get_param(2, &n)) {
+ if (n > 0)
+ printf("spacewidth %d\n", n*MULTIPLIER);
+ }
+ if (t.get_param(1, &n) && n != 0)
+ printf("slant %f\n", atan2(n/double(1<<20), 1.0)*180.0/PI);
+ int xheight;
+ if (!t.get_param(5, &xheight))
+ xheight = 0;
+ unsigned int i;
+ // Print the list of ligatures.
+ // First find the indices of each character that can participate in
+ // a ligature.
+ size_t lig_char_entries = sizeof(lig_chars)/sizeof(lig_chars[0]);
+ size_t lig_table_entries = sizeof(lig_table)/sizeof(lig_table[0]);
+ for (i = 0; i < 256; i++)
+ for (unsigned int j = 0; j < lig_char_entries; j++)
+ for (char_list *p = table[i]; p; p = p->next)
+ if (strcmp(lig_chars[j].ch, p->ch) == 0)
+ lig_chars[j].i = i;
+ // For each possible ligature, if its participants all exist,
+ // and it appears as a ligature in the tfm file, include in
+ // the list of ligatures.
+ int started = 0;
+ for (i = 0; i < lig_table_entries; i++) {
+ int i1 = lig_chars[lig_table[i].c1].i;
+ int i2 = lig_chars[lig_table[i].c2].i;
+ int r = lig_chars[lig_table[i].res].i;
+ if (i1 >= 0 && i2 >= 0 && r >= 0) {
+ unsigned char c;
+ if (t.get_lig(i1, i2, &c) && c == r) {
+ if (!started) {
+ started = 1;
+ fputs("ligatures", stdout);
+ }
+ printf(" %s", lig_table[i].ch);
+ }
+ }
+ }
+ if (started)
+ fputs(" 0\n", stdout);
+ printf("checksum %d\n", t.get_checksum());
+ printf("designsize %d\n", t.get_design_size());
+ // Now print out the kerning information.
+ int had_kern = 0;
+ kern_iterator iter(&t);
+ unsigned char c1, c2;
+ int k;
+ while (iter.next(&c1, &c2, &k))
+ if (c2 != skewchar) {
+ k *= MULTIPLIER;
+ char_list *q = table[c2];
+ for (char_list *p1 = table[c1]; p1; p1 = p1->next)
+ for (char_list *p2 = q; p2; p2 = p2->next) {
+ if (!had_kern) {
+ printf("kernpairs\n");
+ had_kern = 1;
+ }
+ printf("%s %s %d\n", p1->ch, p2->ch, k);
+ }
+ }
+ printf("charset\n");
+ char_list unnamed("---");
+ for (i = 0; i < 256; i++)
+ if (t.contains(i)) {
+ char_list *p = table[i] ? table[i] : &unnamed;
+ int m[6];
+ m[0] = t.get_width(i);
+ m[1] = t.get_height(i);
+ m[2] = t.get_depth(i);
+ m[3] = t.get_italic(i);
+ m[4] = g.get_left_adjustment(i);
+ m[5] = g.get_right_adjustment(i);
+ printf("%s\t%d", p->ch, m[0]*MULTIPLIER);
+ int j;
+ for (j = int(sizeof(m)/sizeof(m[0])) - 1; j > 0; j--)
+ if (m[j] != 0)
+ break;
+ for (k = 1; k <= j; k++)
+ printf(",%d", m[k]*MULTIPLIER);
+ int type = 0;
+ if (m[2] > 0)
+ type = 1;
+ if (m[1] > xheight)
+ type += 2;
+ printf("\t%d\t%04o\n", type, i);
+ for (p = p->next; p; p = p->next)
+ printf("%s\t\"\n", p->ch);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-s] [-g gf-file] [-k skew-char] tfm-file map-file font\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/utils/xtotroff/xtotroff.1.man b/src/utils/xtotroff/xtotroff.1.man
new file mode 100644
index 0000000..17fb0db
--- /dev/null
+++ b/src/utils/xtotroff/xtotroff.1.man
@@ -0,0 +1,237 @@
+.TH xtotroff @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+xtotroff \- convert X font metrics into
+.I groff
+font metrics
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2004-2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_xtotroff_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY xtotroff
+.RB [ \-d\~\c
+.IR destination-directory ]
+.RB [ \-r\~\c
+.IR resolution ]
+.RB [ \-s\~\c
+.IR type-size ]
+.I font-map
+.YS
+.
+.
+.SY xtotroff
+.B \-\-help
+.YS
+.
+.
+.SY xtotroff
+.B \-v
+.
+.SY xtotroff
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I xtotroff
+uses
+.I font-map
+to create
+.MR groff @MAN1EXT@
+font description files from X11 fonts.
+.
+Each line in
+.I font-map
+consists of a series of lines of paired
+.I groff
+font names and X font names as X Logical Font Description (XLFD)
+patterns,
+with the pair members separated by spaces and/or tabs.
+.
+For example,
+an input
+.I font-map
+file consisting of the line
+.
+.RS
+.EX
+TB \-adobe\-times\-bold\-r\-normal\-\-*\-*\-*\-*\-p\-*\-iso8859\-1
+.EE
+.RE
+.
+maps the XLFD on the right to the
+.I groff
+font name
+.BR TB ,
+conventionally \[lq]Times bold\[rq].
+.
+.
+.PP
+.I xtotroff
+opens a connection to the running X server to query its font catalog,
+and aborts if it cannot.
+.
+If necessary,
+the wildcards in the XLFD patterns are populated with the arguments to
+the
+.B \-r
+and
+.B \-s
+options.
+.
+If a font name is still ambiguous,
+.I xtotroff
+aborts.
+.
+For each successful mapping,
+.I xtotroff
+creates a
+.I groff
+font description file in the current working directory
+(or that specified by the
+.B -d
+option)
+named for each
+.I groff
+font,
+and reports the mapping to the standard output stream.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-d\~ destination-directory
+Write font descriptions to
+.I destination-directory
+rather than the current working directory.
+.
+.
+.TP
+.BI \-r\~ resolution
+Set the resolution for all font patterns in
+.IR font-map .
+.
+The value is used for both the horizontal and vertical motion quanta.
+.
+If not specified,
+a resolution of 75dpi is assumed.
+.
+.
+.TP
+.BI \-s\~ type-size
+Set the type size in points for all font patterns in
+.IR font-map .
+.
+If not specified,
+a size of 10 points is assumed.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%FontMap\-X11
+is the font mapping file used to produce the pre-generated font
+description files,
+supplied with
+.IR groff ,
+of X11 core fonts corresponding to the 13 base Type\~1 fonts for
+PostScript level 1.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+The only supported font encodings are \[lq]iso8859\-1\[rq] and
+\%\[lq]adobe\-\:fontspecific\[rq].
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR https://\:www\:.x\:.org/\:releases/\:X11R7.6/\:doc/\:xorg\-docs/\
+\:specs/\:XLFD/xlfd\:.html
+\[lq]X Logical Font Description Conventions\[rq]
+.UE ,
+by Jim Flowers and Stephen Gildea.
+.
+.
+.PP
+.MR X 7 ,
+.MR groff @MAN1EXT@ ,
+.MR gxditview @MAN1EXT@ ,
+.MR troff @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_xtotroff_1_man_C]
+.do rr *groff_xtotroff_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/utils/xtotroff/xtotroff.am b/src/utils/xtotroff/xtotroff.am
new file mode 100644
index 0000000..734d143
--- /dev/null
+++ b/src/utils/xtotroff/xtotroff.am
@@ -0,0 +1,41 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if WITHOUT_X11
+XTOTROFF_MAN1 =
+else
+XTOTROFF_MAN1 = src/utils/xtotroff/xtotroff.1
+bin_PROGRAMS += xtotroff
+man1_MANS += $(XTOTROFF_MAN1)
+xtotroff_SOURCES = src/utils/xtotroff/xtotroff.c
+XLIBS=$(LIBXUTIL) $(LIBGROFF)
+xtotroff_LDADD = libxutil.a libgroff.a $(X_LIBS) $(X_PRE_LIBS) \
+ -lXaw -lXt -lX11 $(X_EXTRA_LIBS) $(LIBM) lib/libgnu.a
+xtotroff_CPPFLAGS = $(AM_CPPFLAGS) $(X_CFLAGS)
+endif
+EXTRA_DIST += src/utils/xtotroff/xtotroff.1.man
+
+# Define variable needed only for the targets that regenerate
+# descriptions of X11 core fonts (used in "maintainer mode").
+xtotroff=$(top_builddir)/xtotroff
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/utils/xtotroff/xtotroff.c b/src/utils/xtotroff/xtotroff.c
new file mode 100644
index 0000000..368761f
--- /dev/null
+++ b/src/utils/xtotroff/xtotroff.c
@@ -0,0 +1,368 @@
+/* Copyright (C) 1992-2022 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+ * xtotroff
+ *
+ * convert X font metrics into troff font metrics
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define __GETOPT_PREFIX groff_
+
+#include <X11/Xlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <getopt.h>
+
+#include "XFontName.h"
+#include "DviChar.h"
+
+#define charWidth(fi,c) \
+ ((fi)->per_char[(c) - (fi)->min_char_or_byte2].width)
+#define charHeight(fi,c) \
+ ((fi)->per_char[(c) - (fi)->min_char_or_byte2].ascent)
+#define charDepth(fi,c) \
+ ((fi)->per_char[(c) - (fi)->min_char_or_byte2].descent)
+#define charLBearing(fi,c) \
+ ((fi)->per_char[(c) - (fi)->min_char_or_byte2].lbearing)
+#define charRBearing(fi,c) \
+ ((fi)->per_char[(c) - (fi)->min_char_or_byte2].rbearing)
+
+extern const char *Version_string;
+static char *program_name;
+
+Display *dpy;
+unsigned resolution = 75;
+unsigned point_size = 10;
+char *destdir = NULL;
+
+static bool charExists(XFontStruct * fi, int c)
+{
+ XCharStruct *p;
+
+ /* 'c' is always >= 0 */
+ if ((unsigned int) c < fi->min_char_or_byte2
+ || (unsigned int) c > fi->max_char_or_byte2)
+ return false;
+ p = fi->per_char + (c - fi->min_char_or_byte2);
+ return p->lbearing != 0 || p->rbearing != 0 || p->width != 0
+ || p->ascent != 0 || p->descent != 0 || p->attributes != 0;
+}
+
+/* Canonicalize the font name by replacing scalable parts by *s. */
+
+static bool CanonicalizeFontName(char *font_name, char *canon_font_name)
+{
+ unsigned int attributes;
+ XFontName parsed;
+
+ if (!XParseFontName(font_name, &parsed, &attributes)) {
+ fprintf(stderr, "%s: not a standard font name: \"%s\"\n",
+ program_name, font_name);
+ return false;
+ }
+
+ attributes &= ~(FontNamePixelSize | FontNameAverageWidth
+ | FontNamePointSize
+ | FontNameResolutionX | FontNameResolutionY);
+ XFormatFontName(&parsed, attributes, canon_font_name);
+ return true;
+}
+
+static bool
+FontNamesAmbiguous(const char *font_name, char **names, int count)
+{
+ char name1[2048], name2[2048];
+ int i;
+
+ if (1 == count)
+ return false;
+
+ for (i = 0; i < count; i++) {
+ if (!CanonicalizeFontName(names[i], 0 == i ? name1 : name2)) {
+ fprintf(stderr, "%s: invalid font name: \"%s\"\n", program_name,
+ names[i]);
+ return true;
+ }
+ if (i > 0 && strcmp(name1, name2) != 0) {
+ fprintf(stderr, "%s: ambiguous font name: \"%s\"", program_name,
+ font_name);
+ fprintf(stderr, " matches \"%s\"", names[0]);
+ fprintf(stderr, " and \"%s\"", names[i]);
+ return true;
+ }
+ }
+ return false;
+}
+
+static void xtotroff_exit(int status)
+{
+ free(destdir);
+ exit(status);
+}
+
+static bool MapFont(char *font_name, const char *troff_name)
+{
+ XFontStruct *fi;
+ int count;
+ char **names;
+ FILE *out;
+ unsigned int c;
+ unsigned int attributes;
+ XFontName parsed;
+ int j, k;
+ DviCharNameMap *char_map;
+ /* 'encoding' needs to hold a CharSetRegistry (256), a CharSetEncoding
+ (256) [both from XFontName.h], a dash, and a null terminator. */
+ char encoding[256 * 2 + 1 + 1];
+ char *s;
+ int wid;
+ char name_string[2048];
+
+ if (!XParseFontName(font_name, &parsed, &attributes)) {
+ fprintf(stderr, "%s: not a standard font name: \"%s\"\n",
+ program_name, font_name);
+ return false;
+ }
+
+ attributes &= ~(FontNamePixelSize | FontNameAverageWidth);
+ attributes |= FontNameResolutionX;
+ attributes |= FontNameResolutionY;
+ attributes |= FontNamePointSize;
+ parsed.ResolutionX = resolution;
+ parsed.ResolutionY = resolution;
+ parsed.PointSize = point_size * 10;
+ XFormatFontName(&parsed, attributes, name_string);
+
+ names = XListFonts(dpy, name_string, 100000, &count);
+ if (count < 1) {
+ fprintf(stderr, "%s: invalid font name: \"%s\"\n", program_name,
+ font_name);
+ return false;
+ }
+
+ if (FontNamesAmbiguous(font_name, names, count))
+ return false;
+
+ XParseFontName(names[0], &parsed, &attributes);
+ size_t sz = sizeof encoding;
+ snprintf(encoding, sz, "%s-%s", parsed.CharSetRegistry,
+ parsed.CharSetEncoding);
+ for (s = encoding; *s; s++)
+ if (isupper(*s))
+ *s = tolower(*s);
+ char_map = DviFindMap(encoding);
+ if (!char_map) {
+ fprintf(stderr, "%s: not a standard encoding: \"%s\"\n",
+ program_name, encoding);
+ return false;
+ }
+
+ fi = XLoadQueryFont(dpy, names[0]);
+ if (!fi) {
+ fprintf(stderr, "%s: font does not exist: \"%s\"\n", program_name,
+ names[0]);
+ return false;
+ }
+
+ printf("%s -> %s\n", names[0], troff_name);
+ char *file_name = (char *)troff_name;
+ size_t dirlen = strlen(destdir);
+
+ if (dirlen > 0) {
+ size_t baselen = strlen(troff_name);
+ file_name = malloc(dirlen + baselen + 2 /* '/' and '\0' */);
+ if (NULL == file_name) {
+ fprintf(stderr, "%s: fatal error: unable to allocate memory\n",
+ program_name);
+ xtotroff_exit(EXIT_FAILURE);
+ }
+ (void) strcpy(file_name, destdir);
+ file_name[dirlen] = '/';
+ (void) strcpy((file_name + dirlen + 1), troff_name);
+ }
+
+ { /* Avoid race while opening file */
+ int fd;
+ (void) unlink(file_name);
+ fd = open(file_name, O_WRONLY | O_CREAT | O_EXCL, 0600);
+ out = fdopen(fd, "w");
+ }
+
+ if (NULL == out) {
+ fprintf(stderr, "%s: unable to create '%s': %s\n", program_name,
+ file_name, strerror(errno));
+ free(file_name);
+ return false;
+ }
+ fprintf(out, "name %s\n", troff_name);
+ if (!strcmp(char_map->encoding, "adobe-fontspecific"))
+ fprintf(out, "special\n");
+ if (charExists(fi, ' ')) {
+ int w = charWidth(fi, ' ');
+ if (w > 0)
+ fprintf(out, "spacewidth %d\n", w);
+ }
+ fprintf(out, "charset\n");
+ for (c = fi->min_char_or_byte2; c <= fi->max_char_or_byte2; c++) {
+ const char *name = DviCharName(char_map, c, 0);
+ if (charExists(fi, c)) {
+ int param[5];
+
+ wid = charWidth(fi, c);
+
+ fprintf(out, "%s\t%d", name ? name : "---", wid);
+ param[0] = charHeight(fi, c);
+ param[1] = charDepth(fi, c);
+ param[2] = 0; /* charRBearing (fi, c) - wid */
+ param[3] = 0; /* charLBearing (fi, c) */
+ param[4] = 0; /* XXX */
+ for (j = 0; j < 5; j++)
+ if (param[j] < 0)
+ param[j] = 0;
+ for (j = 4; j >= 0; j--)
+ if (param[j] != 0)
+ break;
+ for (k = 0; k <= j; k++)
+ fprintf(out, ",%d", param[k]);
+ fprintf(out, "\t0\t0%o\n", c);
+
+ if (name) {
+ for (k = 1; DviCharName(char_map, c, k); k++) {
+ fprintf(out, "%s\t\"\n", DviCharName(char_map, c, k));
+ }
+ }
+ }
+ }
+ XUnloadFont(dpy, fi->fid);
+ fclose(out);
+ free(file_name);
+ return true;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-d destination-directory] [-r resolution]"
+ " [-s type-size] font-map\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+int main(int argc, char **argv)
+{
+ char troff_name[1024];
+ char font_name[1024];
+ char line[1024];
+ char *a, *b, c;
+ FILE *map;
+ int opt;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+
+ program_name = argv[0];
+
+ while ((opt = getopt_long(argc, argv, "d:gr:s:v", long_options,
+ NULL)) != EOF) {
+ switch (opt) {
+ case 'd':
+ destdir = strdup(optarg);
+ break;
+ case 'g':
+ /* unused; just for compatibility */
+ break;
+ case 'r':
+ sscanf(optarg, "%u", &resolution);
+ break;
+ case 's':
+ sscanf(optarg, "%u", &point_size);
+ break;
+ case 'v':
+ printf("GNU xtotroff (groff) version %s\n", Version_string);
+ xtotroff_exit(EXIT_SUCCESS);
+ break;
+ case CHAR_MAX + 1: /* --help */
+ usage(stdout);
+ xtotroff_exit(EXIT_SUCCESS);
+ break;
+ case '?':
+ usage(stderr);
+ xtotroff_exit(EXIT_FAILURE);
+ break;
+ }
+ }
+ if (argc - optind != 1) {
+ usage(stderr);
+ xtotroff_exit(EXIT_FAILURE);
+ }
+
+ dpy = XOpenDisplay(0);
+ if (!dpy) {
+ fprintf(stderr, "%s: fatal error: can't connect to the X server;"
+ " make sure the DISPLAY environment variable is set"
+ " correctly\n", program_name);
+ xtotroff_exit(EXIT_FAILURE);
+ }
+
+ map = fopen(argv[optind], "r");
+ if (NULL == map) {
+ fprintf(stderr, "%s: fatal error: unable to open map file '%s':"
+ " %s\n", program_name, argv[optind], strerror(errno));
+ xtotroff_exit(EXIT_FAILURE);
+ }
+
+ while (fgets(line, sizeof(line), map)) {
+ for (a = line, b = troff_name; *a; a++, b++) {
+ c = (*b = *a);
+ if (' ' == c || '\t' == c)
+ break;
+ }
+ *b = '\0';
+ while (*a && (' ' == *a || '\t' == *a))
+ ++a;
+ for (b = font_name; *a; a++, b++)
+ if ((*b = *a) == '\n')
+ break;
+ *b = '\0';
+ if (!MapFont(font_name, troff_name))
+ xtotroff_exit(EXIT_FAILURE);
+ }
+ xtotroff_exit(EXIT_SUCCESS);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: