[0001]
[0002]
[0003]
[0004]
[0005]
[0006]
[0007]
[0008]
[0009]
[0010]
[0011]
[0012]
[0013]
[0014]
[0015]
[0016]
[0017]
[0018]
[0019]
[0020]
[0021]
[0022]
[0023]
[0024]
[0025]
[0026]
[0027]
[0028]
[0029]
[0030]
[0031]
[0032]
[0033]
[0034]
[0035]
[0036]
[0037]
[0038]
[0039]
[0040]
[0041]
[0042]
[0043]
[0044]
[0045]
[0046]
[0047]
[0048]
[0049]
[0050]
[0051]
[0052]
[0053]
[0054]
[0055]
[0056]
[0057]
[0058]
[0059]
[0060]
[0061]
[0062]
[0063]
[0064]
[0065]
[0066]
[0067]
[0068]
[0069]
[0070]
[0071]
[0072]
[0073]
[0074]
[0075]
[0076]
[0077]
[0078]
[0079]
[0080]
[0081]
[0082]
[0083]
[0084]
[0085]
[0086]
[0087]
[0088]
[0089]
[0090]
[0091]
[0092]
[0093]
[0094]
[0095]
[0096]
[0097]
[0098]
[0099]
[0100]
[0101]
[0102]
[0103]
[0104]
[0105]
[0106]
[0107]
[0108]
[0109]
[0110]
[0111]
[0112]
[0113]
[0114]
[0115]
[0116]
[0117]
[0118]
[0119]
[0120]
[0121]
[0122]
[0123]
[0124]
[0125]
[0126]
[0127]
[0128]
[0129]
[0130]
[0131]
[0132]
[0133]
[0134]
[0135]
[0136]
[0137]
[0138]
[0139]
[0140]
[0141]
[0142]
[0143]
[0144]
[0145]
[0146]
[0147]
[0148]
[0149]
[0150]
[0151]
[0152]
[0153]
[0154]
[0155]
[0156]
[0157]
[0158]
[0159]
[0160]
[0161]
[0162]
[0163]
[0164]
[0165]
[0166]
[0167]
[0168]
[0169]
[0170]
[0171]
[0172]
[0173]
[0174]
[0175]
[0176]
[0177]
[0178]
[0179]
[0180]
[0181]
[0182]
[0183]
[0184]
[0185]
[0186]
[0187]
[0188]
[0189]
[0190]
[0191]
[0192]
[0193]
[0194]
[0195]
[0196]
[0197]
[0198]
[0199]
[0200]
[0201]
[0202]
[0203]
[0204]
[0205]
[0206]
[0207]
[0208]
[0209]
[0210]
[0211]
[0212]
[0213]
[0214]
[0215]
[0216]
[0217]
[0218]
[0219]
[0220]
[0221]
[0222]
[0223]
[0224]
[0225]
[0226]
[0227]
[0228]
[0229]
[0230]
[0231]
[0232]
[0233]
[0234]
[0235]
[0236]
[0237]
[0238]
[0239]
[0240]
[0241]
[0242]
[0243]
[0244]
[0245]
[0246]
[0247]
[0248]
[0249]
[0250]
[0251]
[0252]
[0253]
[0254]
[0255]
[0256]
[0257]
[0258]
[0259]
[0260]
[0261]
[0262]
[0263]
[0264]
[0265]
[0266]
[0267]
[0268]
[0269]
[0270]
[0271]
[0272]
[0273]
[0274]
[0275]
[0276]
[0277]
[0278]
[0279]
[0280]
[0281]
[0282]
[0283]
[0284]
[0285]
[0286]
[0287]
[0288]
[0289]
[0290]
[0291]
[0292]
[0293]
[0294]
[0295]
[0296]
[0297]
[0298]
[0299]
[0300]
[0301]
[0302]
[0303]
[0304]
[0305]
[0306]
[0307]
[0308]
[0309]
[0310]
[0311]
[0312]
[0313]
[0314]
[0315]
[0316]
[0317]
[0318]
[0319]
[0320]
[0321]
[0322]
[0323]
[0324]
[0325]
[0326]
[0327]
[0328]
[0329]
[0330]
[0331]
[0332]
[0333]
[0334]
[0335]
[0336]
[0337]
[0338]
[0339]
[0340]
[0341]
[0342]
[0343]
[0344]
[0345]
[0346]
[0347]
[0348]
[0349]
[0350]
[0351]
[0352]
[0353]
[0354]
[0355]
[0356]
[0357]
[0358]
[0359]
[0360]
[0361]
[0362]
[0363]
[0364]
[0365]
[0366]
[0367]
[0368]
[0369]
[0370]
[0371]
[0372]
[0373]
[0374]
[0375]
[0376]
[0377]
[0378]
[0379]
[0380]
[0381]
[0382]
[0383]
[0384]
[0385]
[0386]
[0387]
[0388]
[0389]
[0390]
[0391]
[0392]
[0393]
[0394]
[0395]
[0396]
[0397]
[0398]
[0399]
[0400]
[0401]
[0402]
[0403]
[0404]
[0405]
[0406]
[0407]
[0408]
[0409]
[0410]
[0411]
[0412]
[0413]
[0414]
[0415]
[0416]
[0417]
[0418]
[0419]
[0420]
[0421]
[0422]
[0423]
[0424]
[0425]
[0426]
[0427]
[0428]
[0429]
[0430]
[0431]
[0432]
[0433]
[0434]
[0435]
[0436]
[0437]
[0438]
[0439]
[0440]
[0441]
[0442]
[0443]
[0444]
[0445]
[0446]
[0447]
[0448]
[0449]
[0450]
[0451]
[0452]
[0453]
[0454]
[0455]
[0456]
[0457]
[0458]
[0459]
[0460]
[0461]
[0462]
[0463]
[0464]
[0465]
[0466]
[0467]
[0468]
[0469]
[0470]
[0471]
[0472]
[0473]
[0474]
[0475]
[0476]
[0477]
[0478]
[0479]
[0480]
[0481]
[0482]
[0483]
[0484]
[0485]
[0486]
[0487]
[0488]
[0489]
[0490]
[0491]
[0492]
[0493]
[0494]
[0495]
[0496]
[0497]
[0498]
[0499]
[0500]
[0501]
[0502]
[0503]
[0504]
[0505]
[0506]
[0507]
[0508]
[0509]
[0510]
[0511]
[0512]
[0513]
[0514]
[0515]
[0516]
[0517]
[0518]
[0519]
[0520]
[0521]
[0522]
[0523]
[0524]
[0525]
[0526]
[0527]
[0528]
[0529]
[0530]
[0531]
[0532]
[0533]
[0534]
[0535]
[0536]
[0537]
[0538]
[0539]
[0540]
[0541]
[0542]
[0543]
[0544]
[0545]
[0546]
[0547]
[0548]
[0549]
[0550]
[0551]
[0552]
[0553]
[0554]
[0555]
[0556]
[0557]
[0558]
[0559]
[0560]
[0561]
[0562]
[0563]
[0564]
[0565]
[0566]
[0567]
[0568]
[0569]
[0570]
[0571]
[0572]
[0573]
[0574]
[0575]
[0576]
[0577]
[0578]
[0579]
[0580]
[0581]
[0582]
[0583]
[0584]
[0585]
[0586]
[0587]
[0588]
[0589]
[0590]
[0591]
[0592]
[0593]
[0594]
[0595]
[0596]
[0597]
[0598]
[0599]
[0600]
[0601]
[0602]
[0603]
[0604]
[0605]
[0606]
[0607]
[0608]
[0609]
[0610]
[0611]
[0612]
[0613]
[0614]
[0615]
[0616]
[0617]
[0618]
[0619]
[0620]
[0621]
[0622]
[0623]
[0624]
[0625]
[0626]
[0627]
[0628]
[0629]
[0630]
[0631]
[0632]
[0633]
[0634]
[0635]
[0636]
[0637]
[0638]
[0639]
[0640]
[0641]
[0642]
[0643]
[0644]
[0645]
[0646]
[0647]
[0648]
[0649]
[0650]
[0651]
[0652]
[0653]
[0654]
[0655]
[0656]
[0657]
[0658]
[0659]
[0660]
[0661]
[0662]
[0663]
[0664]
[0665]
[0666]
[0667]
[0668]
[0669]
[0670]
[0671]
[0672]
[0673]
[0674]
[0675]
[0676]
[0677]
[0678]
[0679]
[0680]
[0681]
[0682]
[0683]
[0684]
[0685]
[0686]
[0687]
[0688]
[0689]
[0690]
[0691]
[0692]
[0693]
[0694]
[0695]
[0696]
[0697]
[0698]
[0699]
[0700]
[0701]
[0702]
[0703]
[0704]
[0705]
[0706]
[0707]
[0708]
[0709]
[0710]
[0711]
[0712]
[0713]
[0714]
[0715]
[0716]
[0717]
[0718]
[0719]
[0720]
[0721]
[0722]
[0723]
[0724]
[0725]
[0726]
[0727]
[0728]
[0729]
[0730]
[0731]
[0732]
[0733]
[0734]
[0735]
[0736]
[0737]
[0738]
[0739]
[0740]
[0741]
[0742]
[0743]
[0744]
[0745]
[0746]
[0747]
[0748]
[0749]
[0750]
[0751]
[0752]
[0753]
[0754]
[0755]
[0756]
[0757]
[0758]
[0759]
[0760]
[0761]
[0762]
[0763]
[0764]
[0765]
[0766]
[0767]
[0768]
[0769]
[0770]
[0771]
[0772]
[0773]
[0774]
[0775]
[0776]
[0777]
[0778]
[0779]
[0780]
[0781]
[0782]
[0783]
[0784]
[0785]
[0786]
[0787]
[0788]
[0789]
[0790]
[0791]
[0792]
[0793]
[0794]
[0795]
[0796]
[0797]
[0798]
[0799]
[0800]
[0801]
[0802]
[0803]
[0804]
[0805]
[0806]
[0807]
[0808]
[0809]
[0810]
[0811]
[0812]
[0813]
[0814]
[0815]
[0816]
[0817]
[0818]
[0819]
[0820]
[0821]
[0822]
[0823]
[0824]
[0825]
[0826]
[0827]
[0828]
[0829]
[0830]
[0831]
[0832]
[0833]
[0834]
[0835]
[0836]
[0837]
[0838]
[0839]
[0840]
[0841]
[0842]
[0843]
[0844]
[0845]
[0846]
[0847]
[0848]
[0849]
[0850]
[0851]
[0852]
[0853]
[0854]
[0855]
[0856]
[0857]
[0858]
[0859]
[0860]
[0861]
[0862]
[0863]
[0864]
[0865]
[0866]
[0867]
[0868]
[0869]
[0870]
[0871]
[0872]
[0873]
[0874]
[0875]
[0876]
[0877]
[0878]
[0879]
[0880]
[0881]
[0882]
[0883]
[0884]
[0885]
[0886]
[0887]
[0888]
[0889]
[0890]
[0891]
[0892]
[0893]
[0894]
[0895]
[0896]
[0897]
[0898]
[0899]
[0900]
[0901]
[0902]
[0903]
[0904]
[0905]
[0906]
[0907]
[0908]
[0909]
[0910]
[0911]
[0912]
[0913]
[0914]
[0915]
[0916]
[0917]
[0918]
[0919]
[0920]
[0921]
[0922]
[0923]
[0924]
[0925]
[0926]
[0927]
[0928]
[0929]
[0930]
[0931]
[0932]
[0933]
[0934]
[0935]
[0936]
[0937]
[0938]
[0939]
[0940]
[0941]
[0942]
[0943]
[0944]
[0945]
[0946]
[0947]
[0948]
[0949]
[0950]
[0951]
[0952]
[0953]
[0954]
[0955]
[0956]
[0957]
[0958]
[0959]
[0960]
[0961]
[0962]
[0963]
[0964]
[0965]
[0966]
[0967]
[0968]
[0969]
[0970]
[0971]
[0972]
[0973]
[0974]
[0975]
[0976]
[0977]
[0978]
[0979]
[0980]
[0981]
[0982]
[0983]
[0984]
[0985]
[0986]
[0987]
[0988]
[0989]
[0990]
[0991]
[0992]
[0993]
[0994]
[0995]
[0996]
[0997]
[0998]
[0999]
[1000]
[1001]
[1002]
[1003]
[1004]
[1005]
[1006]
[1007]
[1008]
[1009]
[1010]
[1011]
[1012]
[1013]
[1014]
[1015]
[1016]
[1017]
[1018]
[1019]
[1020]
[1021]
[1022]
[1023]
[1024]
[1025]
[1026]
[1027]
[1028]
[1029]
[1030]
[1031]
[1032]
[1033]
[1034]
[1035]
[1036]
[1037]
[1038]
[1039]
[1040]
[1041]
[1042]
[1043]
[1044]
[1045]
[1046]
[1047]
[1048]
[1049]
[1050]
[1051]
[1052]
[1053]
[1054]
[1055]
[1056]
[1057]
[1058]
[1059]
[1060]
[1061]
[1062]
[1063]
[1064]
[1065]
[1066]
[1067]
[1068]
[1069]
[1070]
[1071]
[1072]
[1073]
[1074]
[1075]
[1076]
[1077]
[1078]
[1079]
[1080]
[1081]
[1082]
[1083]
[1084]
[1085]
[1086]
[1087]
[1088]
[1089]
[1090]
[1091]
[1092]
[1093]
[1094]
[1095]
[1096]
[1097]
[1098]
[1099]
[1100]
[1101]
[1102]
[1103]
[1104]
[1105]
[1106]
[1107]
[1108]
[1109]
[1110]
[1111]
[1112]
[1113]
[1114]
[1115]
[1116]
[1117]
[1118]
[1119]
[1120]
[1121]
[1122]
[1123]
[1124]
[1125]
[1126]
[1127]
[1128]
[1129]
[1130]
[1131]
[1132]
[1133]
[1134]
[1135]
[1136]
[1137]
[1138]
[1139]
[1140]
[1141]
[1142]
[1143]
[1144]
[1145]
[1146]
[1147]
[1148]
[1149]
[1150]
[1151]
[1152]
[1153]
[1154]
[1155]
[1156]
[1157]
[1158]
[1159]
[1160]
[1161]
[1162]
[1163]
[1164]
[1165]
[1166]
[1167]
[1168]
[1169]
[1170]
[1171]
[1172]
[1173]
[1174]
[1175]
[1176]
[1177]
[1178]
[1179]
[1180]
[1181]
[1182]
[1183]
[1184]
[1185]
[1186]
[1187]
[1188]
[1189]
[1190]
[1191]
[1192]
[1193]
[1194]
[1195]
[1196]
[1197]
[1198]
[1199]
[1200]
[1201]
[1202]
[1203]
[1204]
[1205]
[1206]
[1207]
[1208]
[1209]
[1210]
[1211]
[1212]
[1213]
[1214]
[1215]
[1216]
[1217]
[1218]
[1219]
[1220]
[1221]
[1222]
[1223]
[1224]
[1225]
[1226]
[1227]
[1228]
[1229]
[1230]
[1231]
[1232]
[1233]
[1234]
[1235]
[1236]
[1237]
[1238]
[1239]
[1240]
[1241]
[1242]
[1243]
[1244]
[1245]
[1246]
[1247]
[1248]
[1249]
[1250]
[1251]
[1252]
[1253]
[1254]
[1255]
[1256]
[1257]
[1258]
[1259]
[1260]
[1261]
[1262]
[1263]
[1264]
[1265]
[1266]
[1267]
[1268]
[1269]
[1270]
[1271]
[1272]
[1273]
[1274]
[1275]
[1276]
[1277]
[1278]
[1279]
[1280]
[1281]
[1282]
[1283]
[1284]
[1285]
[1286]
[1287]
[1288]
[1289]
[1290]
[1291]
[1292]
[1293]
[1294]
[1295]
[1296]
[1297]
[1298]
[1299]
[1300]
[1301]
[1302]
[1303]
[1304]
[1305]
[1306]
[1307]
[1308]
[1309]
[1310]
[1311]
[1312]
[1313]
[1314]
[1315]
[1316]
[1317]
[1318]
[1319]
[1320]
[1321]
[1322]
[1323]
[1324]
[1325]
[1326]
[1327]
[1328]
[1329]
[1330]
[1331]
[1332]
[1333]
[1334]
[1335]
[1336]
[1337]
[1338]
[1339]
[1340]
[1341]
[1342]
[1343]
[1344]
[1345]
[1346]
[1347]
[1348]
[1349]
[1350]
[1351]
[1352]
[1353]
[1354]
[1355]
[1356]
[1357]
[1358]
[1359]
[1360]
[1361]
[1362]
[1363]
[1364]
[1365]
[1366]
[1367]
[1368]
[1369]
[1370]
[1371]
[1372]
[1373]
[1374]
[1375]
[1376]
[1377]
[1378]
[1379]
[1380]
[1381]
[1382]
[1383]
[1384]
[1385]
[1386]
[1387]
[1388]
[1389]
[1390]
[1391]
[1392]
[1393]
[1394]
[1395]
[1396]
[1397]
[1398]
[1399]
[1400]
[1401]
[1402]
[1403]
[1404]
[1405]
[1406]
[1407]
[1408]
[1409]
[1410]
[1411]
[1412]
[1413]
[1414]
[1415]
[1416]
[1417]
[1418]
[1419]
[1420]
[1421]
[1422]
[1423]
[1424]
[1425]
[1426]
[1427]
[1428]
[1429]
[1430]
[1431]
[1432]
[1433]
[1434]
[1435]
[1436]
[1437]
[1438]
[1439]
[1440]
[1441]
[1442]
[1443]
[1444]
[1445]
[1446]
[1447]
[1448]
[1449]
[1450]
[1451]
[1452]
[1453]
[1454]
[1455]
[1456]
[1457]
[1458]
[1459]
[1460]
[1461]
[1462]
[1463]
[1464]
[1465]
[1466]
[1467]
[1468]
[1469]
[1470]
[1471]
[1472]
[1473]
[1474]
[1475]
[1476]
[1477]
[1478]
[1479]
[1480]
[1481]
[1482]
[1483]
[1484]
[1485]
[1486]
[1487]
[1488]
[1489]
[1490]
[1491]
[1492]
[1493]
[1494]
[1495]
[1496]
[1497]
[1498]
[1499]
[1500]
[1501]
[1502]
[1503]
[1504]
[1505]
[1506]
[1507]
[1508]
[1509]
[1510]
[1511]
[1512]
[1513]
[1514]
[1515]
[1516]
[1517]
[1518]
[1519]
[1520]
[1521]
[1522]
[1523]
[1524]
[1525]
[1526]
[1527]
[1528]
[1529]
[1530]
[1531]
[1532]
[1533]
[1534]
[1535]
[1536]
[1537]
[1538]
[1539]
[1540]
[1541]
[1542]
[1543]
[1544]
[1545]
[1546]
[1547]
[1548]
[1549]
[1550]
[1551]
[1552]
[1553]
[1554]
[1555]
[1556]
[1557]
[1558]
[1559]
[1560]
[1561]
[1562]
[1563]
[1564]
[1565]
[1566]
[1567]
[1568]
[1569]
[1570]
[1571]
[1572]
[1573]
[1574]
[1575]
[1576]
[1577]
[1578]
[1579]
[1580]
[1581]
[1582]
[1583]
[1584]
[1585]
[1586]
[1587]
[1588]
[1589]
[1590]
[1591]
[1592]
[1593]
[1594]
[1595]
[1596]
[1597]
[1598]
[1599]
[1600]
[1601]
[1602]
[1603]
[1604]
[1605]
[1606]
[1607]
[1608]
[1609]
[1610]
[1611]
[1612]
[1613]
[1614]
[1615]
[1616]
[1617]
[1618]
[1619]
[1620]
[1621]
[1622]
[1623]
[1624]
[1625]
[1626]
[1627]
[1628]
[1629]
[1630]
[1631]
[1632]
[1633]
[1634]
[1635]
[1636]
[1637]
[1638]
[1639]
[1640]
[1641]
[1642]
[1643]
[1644]
[1645]
[1646]
[1647]
[1648]
[1649]
[1650]
[1651]
[1652]
[1653]
[1654]
[1655]
[1656]
[1657]
[1658]
[1659]
[1660]
[1661]
[1662]
[1663]
[1664]
[1665]
[1666]
[1667]
[1668]
[1669]
[1670]
[1671]
[1672]
[1673]
[1674]
[1675]
[1676]
[1677]
[1678]
[1679]
[1680]
[1681]
[1682]
[1683]
[1684]
[1685]
[1686]
[1687]
[1688]
[1689]
[1690]
[1691]
[1692]
[1693]
[1694]
[1695]
[1696]
[1697]
[1698]
[1699]
[1700]
[1701]
[1702]
[1703]
[1704]
[1705]
[1706]
[1707]
[1708]
[1709]
[1710]
[1711]
[1712]
[1713]
[1714]
[1715]
[1716]
[1717]
[1718]
[1719]
[1720]
[1721]
[1722]
[1723]
[1724]
[1725]
[1726]
[1727]
[1728]
[1729]
[1730]
[1731]
[1732]
[1733]
[1734]
[1735]
[1736]
[1737]
[1738]
[1739]
[1740]
[1741]
[1742]
[1743]
[1744]
[1745]
[1746]
[1747]
[1748]
[1749]
[1750]
[1751]
[1752]
[1753]
[1754]
[1755]
[1756]
[1757]
[1758]
[1759]
[1760]
[1761]
[1762]
[1763]
[1764]
[1765]
[1766]
[1767]
[1768]
[1769]
[1770]
[1771]
[1772]
[1773]
[1774]
[1775]
[1776]
[1777]
[1778]
[1779]
[1780]
[1781]
[1782]
[1783]
[1784]
[1785]
[1786]
[1787]
[1788]
[1789]
[1790]
[1791]
[1792]
[1793]
[1794]
[1795]
[1796]
[1797]
[1798]
[1799]
[1800]
[1801]
[1802]
[1803]
[1804]
[1805]
[1806]
[1807]
[1808]
[1809]
[1810]
[1811]
[1812]
[1813]
[1814]
[1815]
[1816]
[1817]
[1818]
[1819]
[1820]
[1821]
[1822]
[1823]
[1824]
[1825]
[1826]
[1827]
[1828]
[1829]
[1830]
[1831]
[1832]
[1833]
[1834]
[1835]
[1836]
[1837]
[1838]
[1839]
[1840]
[1841]
[1842]
[1843]
[1844]
[1845]
[1846]
[1847]
[1848]
[1849]
[1850]
[1851]
[1852]
[1853]
[1854]
[1855]
[1856]
[1857]
[1858]
[1859]
[1860]
[1861]
[1862]
[1863]
[1864]
[1865]
[1866]
[1867]
[1868]
[1869]
[1870]
[1871]
[1872]
[1873]
[1874]
[1875]
[1876]
[1877]
[1878]
[1879]
[1880]
[1881]
[1882]
[1883]
[1884]
[1885]
[1886]
[1887]
[1888]
[1889]
[1890]
[1891]
[1892]
[1893]
[1894]
[1895]
[1896]
[1897]
[1898]
[1899]
[1900]
[1901]
[1902]
[1903]
[1904]
[1905]
[1906]
[1907]
[1908]
[1909]
[1910]
[1911]
[1912]
[1913]
[1914]
[1915]
[1916]
[1917]
[1918]
[1919]
[1920]
[1921]
[1922]
[1923]
[1924]
[1925]
[1926]
[1927]
[1928]
[1929]
[1930]
[1931]
[1932]
[1933]
[1934]
[1935]
[1936]
[1937]
[1938]
[1939]
[1940]
[1941]
[1942]
[1943]
[1944]
[1945]
[1946]
[1947]
[1948]
[1949]
[1950]
[1951]
[1952]
[1953]
[1954]
[1955]
[1956]
[1957]
[1958]
[1959]
[1960]
[1961]
[1962]
[1963]
[1964]
[1965]
[1966]
[1967]
[1968]
[1969]
[1970]
[1971]
[1972]
[1973]
[1974]
[1975]
[1976]
[1977]
[1978]
[1979]
[1980]
[1981]
[1982]
[1983]
[1984]
[1985]
[1986]
[1987]
[1988]
[1989]
[1990]
[1991]
[1992]
[1993]
[1994]
[1995]
[1996]
[1997]
[1998]
[1999]
[2000]
[2001]
[2002]
[2003]
[2004]
[2005]
[2006]
[2007]
[2008]
[2009]
[2010]
[2011]
[2012]
[2013]
[2014]
[2015]
[2016]
[2017]
[2018]
[2019]
[2020]
[2021]
[2022]
[2023]
[2024]
[2025]
[2026]
[2027]
[2028]
[2029]
[2030]
[2031]
[2032]
[2033]
[2034]
[2035]
[2036]
[2037]
[2038]
[2039]
[2040]
[2041]
[2042]
[2043]
[2044]
[2045]
[2046]
[2047]
[2048]
[2049]
[2050]
[2051]
[2052]
[2053]
[2054]
[2055]
[2056]
[2057]
[2058]
[2059]
[2060]
[2061]
[2062]
[2063]
[2064]
[2065]
[2066]
[2067]
[2068]
[2069]
[2070]
[2071]
[2072]
[2073]
[2074]
[2075]
[2076]
[2077]
[2078]
[2079]
[2080]
[2081]
[2082]
[2083]
[2084]
[2085]
[2086]
[2087]
[2088]
[2089]
[2090]
[2091]
[2092]
[2093]
[2094]
[2095]
[2096]
[2097]
[2098]
[2099]
[2100]
[2101]
[2102]
[2103]
[2104]
[2105]
[2106]
[2107]
[2108]
[2109]
[2110]
[2111]
[2112]
[2113]
[2114]
[2115]
[2116]
[2117]
[2118]
[2119]
[2120]
[2121]
[2122]
[2123]
[2124]
[2125]
[2126]
[2127]
[2128]
[2129]
[2130]
[2131]
[2132]
[2133]
[2134]
[2135]
[2136]
[2137]
[2138]
[2139]
[2140]
[2141]
[2142]
[2143]
[2144]
[2145]
[2146]
[2147]
[2148]
[2149]
[2150]
[2151]
[2152]
[2153]
[2154]
[2155]
[2156]
[2157]
[2158]
[2159]
[2160]
[2161]
[2162]
[2163]
[2164]
[2165]
[2166]
[2167]
[2168]
[2169]
[2170]
[2171]
[2172]
[2173]
[2174]
[2175]
[2176]
[2177]
[2178]
[2179]
[2180]
[2181]
[2182]
[2183]
[2184]
[2185]
[2186]
[2187]
[2188]
[2189]
[2190]
[2191]
[2192]
[2193]
[2194]
[2195]
[2196]
[2197]
[2198]
[2199]
[2200]
[2201]
[2202]
[2203]
[2204]
[2205]
[2206]
[2207]
[2208]
[2209]
[2210]
[2211]
[2212]
[2213]
[2214]
[2215]
[2216]
[2217]
[2218]
[2219]
[2220]
[2221]
[2222]
[2223]
[2224]
[2225]
[2226]
[2227]
[2228]
[2229]
[2230]
[2231]
[2232]
[2233]
[2234]
[2235]
[2236]
[2237]
[2238]
[2239]
[2240]
[2241]
[2242]
[2243]
[2244]
[2245]
[2246]
[2247]
[2248]
[2249]
[2250]
[2251]
[2252]
[2253]
[2254]
[2255]
[2256]
[2257]
[2258]
[2259]
[2260]
[2261]
[2262]
[2263]
[2264]
[2265]
[2266]
[2267]
[2268]
[2269]
[2270]
[2271]
[2272]
[2273]
[2274]
[2275]
[2276]
[2277]
[2278]
[2279]
[2280]
[2281]
[2282]
[2283]
[2284]
[2285]
[2286]
[2287]
[2288]
[2289]
[2290]
[2291]
[2292]
[2293]
[2294]
[2295]
[2296]
[2297]
[2298]
[2299]
[2300]
[2301]
[2302]
[2303]
[2304]
[2305]
[2306]
[2307]
[2308]
[2309]
[2310]
[2311]
[2312]
[2313]
[2314]
[2315]
[2316]
[2317]
[2318]
[2319]
[2320]
[2321]
[2322]
[2323]
[2324]
[2325]
[2326]
[2327]
[2328]
[2329]
[2330]
[2331]
[2332]
[2333]
[2334]
[2335]
[2336]
[2337]
[2338]
[2339]
[2340]
[2341]
[2342]
[2343]
[2344]
[2345]
[2346]
[2347]
[2348]
[2349]
[2350]
[2351]
[2352]
[2353]
[2354]
[2355]
[2356]
[2357]
[2358]
[2359]
[2360]
[2361]
[2362]
[2363]
[2364]
[2365]
[2366]
[2367]
[2368]
[2369]
[2370]
[2371]
[2372]
[2373]
[2374]
[2375]
[2376]
[2377]
[2378]
[2379]
[2380]
[2381]
[2382]
[2383]
[2384]
[2385]
[2386]
[2387]
[2388]
[2389]
[2390]
[2391]
[2392]
[2393]
[2394]
[2395]
[2396]
[2397]
[2398]
[2399]
[2400]
[2401]
[2402]
[2403]
[2404]
[2405]
[2406]
[2407]
[2408]
[2409]
[2410]
[2411]
[2412]
[2413]
[2414]
[2415]
[2416]
[2417]
[2418]
[2419]
[2420]
[2421]
[2422]
[2423]
[2424]
[2425]
[2426]
[2427]
[2428]
[2429]
[2430]
[2431]
[2432]
[2433]
[2434]
[2435]
[2436]
[2437]
[2438]
[2439]
[2440]
[2441]
[2442]
[2443]
[2444]
[2445]
[2446]
[2447]
[2448]
[2449]
[2450]
[2451]
[2452]
[2453]
[2454]
[2455]
[2456]
[2457]
[2458]
[2459]
[2460]
[2461]
[2462]
[2463]
[2464]
[2465]
[2466]
[2467]
[2468]
[2469]
[2470]
[2471]
[2472]
[2473]
[2474]
[2475]
[2476]
[2477]
[2478]
[2479]
[2480]
[2481]
[2482]
[2483]
[2484]
[2485]
[2486]
[2487]
[2488]
[2489]
[2490]
[2491]
[2492]
[2493]
[2494]
[2495]
[2496]
[2497]
[2498]
[2499]
[2500]
[2501]
[2502]
[2503]
[2504]
[2505]
[2506]
[2507]
[2508]
[2509]
[2510]
[2511]
[2512]
[2513]
[2514]
[2515]
[2516]
[2517]
[2518]
[2519]
[2520]
[2521]
[2522]
[2523]
[2524]
[2525]
[2526]
[2527]
[2528]
[2529]
[2530]
[2531]
[2532]
[2533]
[2534]
[2535]
[2536]
[2537]
[2538]
[2539]
[2540]
[2541]
[2542]
[2543]
[2544]
[2545]
[2546]
[2547]
[2548]
[2549]
[2550]
[2551]
[2552]
[2553]
[2554]
[2555]
[2556]
[2557]
[2558]
[2559]
[2560]
[2561]
[2562]
[2563]
[2564]
[2565]
[2566]
[2567]
[2568]
[2569]
[2570]
[2571]
[2572]
[2573]
[2574]
[2575]
[2576]
[2577]
[2578]
[2579]
[2580]
[2581]
[2582]
[2583]
[2584]
[2585]
[2586]
[2587]
[2588]
[2589]
[2590]
[2591]
[2592]
[2593]
[2594]
[2595]
[2596]
[2597]
[2598]
[2599]
[2600]
[2601]
[2602]
[2603]
[2604]
[2605]
[2606]
[2607]
[2608]
[2609]
[2610]
[2611]
[2612]
[2613]
[2614]
[2615]
[2616]
[2617]
[2618]
[2619]
[2620]
[2621]
[2622]
[2623]
[2624]
[2625]
[2626]
[2627]
[2628]
[2629]
[2630]
[2631]
[2632]
[2633]
[2634]
[2635]
[2636]
[2637]
[2638]
[2639]
[2640]
[2641]
[2642]
[2643]
[2644]
[2645]
[2646]
[2647]
[2648]
[2649]
[2650]
[2651]
[2652]
[2653]
[2654]
[2655]
[2656]
[2657]
[2658]
[2659]
[2660]
[2661]
[2662]
[2663]
[2664]
[2665]
[2666]
[2667]
[2668]
[2669]
[2670]
[2671]
[2672]
[2673]
[2674]
[2675]
[2676]
[2677]
[2678]
[2679]
[2680]
[2681]
[2682]
[2683]
[2684]
[2685]
[2686]
[2687]
[2688]
[2689]
[2690]
[2691]
[2692]
[2693]
[2694]
[2695]
[2696]
[2697]
[2698]
[2699]
[2700]
[2701]
[2702]
[2703]
[2704]
[2705]
[2706]
[2707]
[2708]
[2709]
[2710]
[2711]
[2712]
[2713]
[2714]
[2715]
[2716]
[2717]
[2718]
[2719]
[2720]
[2721]
[2722]
[2723]
[2724]
[2725]
[2726]
[2727]
[2728]
[2729]
[2730]
[2731]
[2732]
[2733]
[2734]
[2735]
[2736]
[2737]
[2738]
[2739]
[2740]
[2741]
[2742]
[2743]
[2744]
[2745]
[2746]
[2747]
[2748]
[2749]
[2750]
[2751]
[2752]
[2753]
[2754]
[2755]
[2756]
[2757]
[2758]
[2759]
[2760]
[2761]
[2762]
[2763]
[2764]
[2765]
[2766]
[2767]
[2768]
[2769]
[2770]
[2771]
[2772]
[2773]
[2774]
[2775]
[2776]
[2777]
[2778]
[2779]
[2780]
[2781]
[2782]
[2783]
[2784]
[2785]
[2786]
[2787]
[2788]
[2789]
[2790]
[2791]
[2792]
[2793]
[2794]
[2795]
[2796]
[2797]
[2798]
[2799]
[2800]
[2801]
[2802]
[2803]
[2804]
[2805]
[2806]
[2807]
[2808]
[2809]
[2810]
[2811]
[2812]
[2813]
[2814]
[2815]
[2816]
[2817]
[2818]
[2819]
[2820]
[2821]
[2822]
[2823]
[2824]
[2825]
[2826]
[2827]
[2828]
[2829]
[2830]
[2831]
[2832]
[2833]
[2834]
[2835]
[2836]
[2837]
[2838]
[2839]
[2840]
[2841]
[2842]
[2843]
[2844]
[2845]
[2846]
[2847]
[2848]
[2849]
[2850]
[2851]
[2852]
[2853]
[2854]
[2855]
[2856]
[2857]
[2858]
[2859]
[2860]
[2861]
[2862]
[2863]
[2864]
[2865]
/*****************************************************************************/
/*
                                 screper.c

... a terminal screen screper, er scréper, um scraper.


HOW IT WORKS
------------
Getting the VT100-style terminal output of a "regular" command-line application
onto a browser HTML page.  The parent application (script) using an
XMLHttpRequest() activates a wrapper for the routines in this module.  This
spawns a subprocess which initiates the commnd-line application and a screen of
terminal output is generated.

This output is parsed with VT100/ANSI cursor, character attributes, and other
sequences, building a virtual, in-memory screen.  This generates HTML markup to
represent the screen in a browser.  The HTML is streamed as a response to the
XMLHttpRequest() with the "snapshot" representing each terminal screen.

A stream is a series of time-based snapshots.  The idea is that a screen is
built from output fairly quickly, and may have a well-defined update rate.  The
snapshotter ticks ten times a second and when the virtual screen has not
received new display data for the specified number of ticks a snapshot of that
virtual screen (in HTML markup) is sent to the parent running in the browser. 
For example, the MONITOR update rate is roughly 6 seconds.  So a snapshot value
of one-tenth second ticks of 55 will snapshot after 5.5 seconds of MONITOR
quiesence.  Just enough not to update too frequently but not to update in the
middle of new data being received.  There is probably a sweetspot for each
update interval and application.  This time-based approach is not suitable for
continuously or randomly updating screens, and congested systems can result in
partial or broken displays.

An alternative to a time-based update is to trigger a snapshot on the detection
of unique octet sequence.  For example, if the clear-screen CSI sequence is
used to begin a fresh screen display then the octet sequence, 0x1b 0x5b 0x32
0x4a, then detecting this in the terminal output can be used to generate a
snapshot before the output is sent to to scraper for processing.  Such a
sentinal must be terminated by 2 null characters.  Multiple sentinals can be
inside the string by separating each with a single null.  Obviously a null
character cannot be used as part of the sentinal.  To be detected a sentinal
must be self-contained in a single output record.  Here is an example os a
single sentinal string.

   uchar example [] = { 0x1b, 0x5b, 0x32, 0x4a, 0x00, 0x00 };


SCREPER DOES NOT PRETEND
------------------------
to be an exhaustive VT terminal interpreter.  The development baseline
application was the MONITOR utility.  Screper seems to handle MONITOR with
considerable aplomb.  However, no little wonder that terminal emulator authors
seldom get it 100% and quickly move on to something else.


SCREPER API
-----------
ScreperInit() returns a pointer to an opaque structure (void*).

ScreperDone() deallocates memory used during processing and returns a NULL.

ScreperDo() is supplied a string which is processed to set parameters and/or
perform a DCL command.  Directives must be lower case, include at least the
first three characters, and have no white space between the directive, equate
symbol, and value.  Some can only be used standlone.  A single or multiple
calls may be made to set up the virtual terminal session.  The final call must
contain the DCL commands.

-7bit                    terminal 7 bits
-8bit                    terminal 8 bits (default)
-dcl=<string>            DCL command(s) (to end of line)
-device=<integer>        e.g. value from ttdef.h (default TT$_VT100)
-exit                    supply a ^Z to the DCL command (also see -input)
-input="<string>"        supply this string as input to the command
-inspect=<integer>       inspect the data stream at various levels
-page=[+]<integer>       number of lines on page (24..255)
-repeat=<integer>        repeat every <integer> seconds
-sentinal="<string>"     when output contains this it triggers a snapshot
-snapshot=<integer>      tenths of a second before snapshot (-n, 0, 5..600)
-timestamp[="<string>"]  begin the display with a timestamp
-utility="<string>"      name of the utility displayed with the pause button
-width=<integer>         width of line, 80 or 132 ... 255 chars (default 80)

-css                     return style sheet (standalone)
-javascript              return JavaScript (standalone)
-pause                   return HTML for a "pause all" button (standalone)
-resize                  return JavaScript to resize an iframe (standalone)
-screen                  return HTML providing virtual screen (standalone)
-version                 return version as string (standalone)

The -input string, along with the -sentinal and -timestamp strings, must be
quotation character delimited, and can include control codes and other "exotic"
characters using the ^xx escape syntax. So inputing a control-Z would be ^1a, a
literal hat character ^3e, and literal quotation ^22.

The -timestamp has a default VMS date-time.  A format string may be supplied
allowing VMS date/time formatting (e.g. "!8%T", "!20%D") or strftime()
formatting (e.g. "%c", "%A, %B %d, %Y").  Other characters and HTML markup may
lead and trail the string formatting but the characters '!' and '%' are
considered reserved and introduce the format string.  HTML-entify if required
as literals.

See example applications for usage.


RESOURCES
---------
https://vt100.net/
https://vt100.net/docs/vt100-ug/chapter3.html
https://vt100.net/docs/vt510-rm/chapter7.html
http://fileformats.archiveteam.org/wiki/DEC_Special_Graphics_Character_Set
https://en.wikipedia.org/wiki/VT100_encoding


COPYRIGHT
---------
Copyright (C) 2021 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION LOG
-----------
01-AUG-2021  MGD  initial
*/
/*****************************************************************************/

#define SOFTWAREVN "v1.0.0"
#define SOFTWARENM "SCREPER"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " AXP"
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " IA64"
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " X86"
#endif

#include <ctype.h>
#include <descrip.h>
#include <errno.h>
#include <fcntl.h>
#include <in.h>
#include <ints.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stat.h>
#include <time.h>
#include <unistd.h>
#include <unixio.h>

#include <dvidef.h>
#include <iodef.h>
#include <iosbdef.h>
#include <jpidef.h>
#include <libdef.h>
#include <lib$routines.h>
#include <opcdef.h>
#include <starlet.h>
#include <ssdef.h>
#include <syidef.h>
#include <ttdef.h>
#include <tt2def.h>

#include "screper.h"

#define FI_LI "SCREPER",__LINE__

#define DC$_TERM 6

#ifndef UINT64PTR
/* mainly to allow easy use of the __unaligned directive */
#define UINTPTR __unaligned unsigned int*
#define ULONGPTR __unaligned unsigned long*
#define USHORTPTR __unaligned unsigned short*
#define UINT64PTR __unaligned uint64*
#define INT64PTR __unaligned int64*
#endif

/*
    Each cursor position in the virtual screen is 32 bits deep.

     0              7 8             15 16            23 24            31
    |................|................|................|................|
    |   8 bit char   |    SGR bits    |    G0 alpha    |    G1 alpha    |

                                       (these two fields are extravagant)
*/

#define SGR_BOLD  0x00000100
#define SGR_UNDER 0x00000200
#define SGR_BLINK 0x00000400
#define SGR_REVER 0x00000800
#define SGR_RESET 0x00004000
#define SGR_G1    0x00008000

#define CHAR_MASK 0x000000ff
#define SGR_MASK  0x0000ff00
#define G0_MASK   0x00ff0000
#define G1_MASK   0xff000000

#define MAX_PAGE  255
#define MAX_WIDTH 255

#define BUTTON_PAUSE      "|&thinsp;|"
#define BUTTON_UNPAUSE    ">>"
#define BUTTON_PAUSEALL   "Pause All"
#define BUTTON_UNPAUSEALL "Resume All"

#define DELTA64_100_MSEC ((int64)-1000000)

static int64  refreshTimer64 = DELTA64_100_MSEC;

static char  SoftwareId [] = SOFTWAREID;

#  define CSAVE_MAX 16
#pragma __member_alignment __save
#pragma __member_alignment

static struct ScreperStruct
{
   int  AnsiMode,
        CharTableG0,
        CharTableG1,
        CurSave,
        EfnRead,
        EtxDetected,
        EtxLength,
        FontSize,
        InputLength,
        InspectStream,
        LibSpawnStatus,
        PromptLength,
        PtdBits8,
        PtdDevice,
        PtdPageLength,
        PtdPagePlus,
        PtdPageWidth,
        PtdReadAst,
        PtdReadStatus,
        PtdWriteStatus,
        RepeatSeconds,
        ScreenBufferSize,
        ScreenCol,
        ScreenRow,
        SgrBlink,
        SgrBold,
        SgrEoj,
        SgrGrone,
        SgrReverse,
        SgrG1,
        SgrShot1,
        SgrUnder,
        SnapshotDataCount,
        SnapshotSentinalLength,
        SnapshotTickCount,
        SnapshotTicks,
        SpecGraphMode,
        StxDetected,
        StxLength,
        TimeStampLength,
        UtilityLength;

   int  ColSave [CSAVE_MAX],
        RowSave [CSAVE_MAX],
        SgrSave [CSAVE_MAX];

   ulong  LibSpawnPid,
          ScriptJpiPid;

   ulong  inadr [2];
   ulong  chbuf [3];

   ulong  *ScreenBuffer;

   ushort  PtdChan;

   char  *CssString,
         *NextWritePtr,
         *PtdReadBuffer,
         *PtdWriteBuffer,
         *RepeatCmdPtr;

   char  CliPrompt [24],
         DclBuffer [512],
         EtxSentinal [24],
         InputBuffer [64],
         PrcNam [16],
         PtdDevName [64],
         ScriptPidString [16],
         SgrBuffer [128],
         SnapshotSentinal [64],
         StxSentinal [24],
         TimeStamp [64],
         Utility [64];

   FILE  *ScrOut;

} ScreperStruct;

#pragma __member_alignment __restore

typedef struct ScreperStruct SCREPER_STRUCT;

#define PTD_READ_SIZE  18 * 512
#define PTD_WRITE_SIZE  2 * 512
#define PTD_BUFFER_PAGES 20

/* private prototypes */
static void PtdDelete (SCREPER_STRUCT*);
static int PtdDollarWrite (ushort, char*, int);
static void PtdPoof (SCREPER_STRUCT*, int);
static void PtdRead (SCREPER_STRUCT*);
static int PtdSpawn (SCREPER_STRUCT*);
static void PtdSpawnAst (SCREPER_STRUCT*);
static int PtdWrite (SCREPER_STRUCT*);

static char* ScreenChar (SCREPER_STRUCT*, ulong);
static void ScreenCursor (SCREPER_STRUCT*, char*, ...);
static void ScreenDump (SCREPER_STRUCT*, char*, int);
static void ScreenIndex (SCREPER_STRUCT*, int*, int*);
static void ScreenNextLine (SCREPER_STRUCT*, int*, int*);
static void ScreenReverseIndex (SCREPER_STRUCT*, int*, int*);
static char* ScreperBegin (SCREPER_STRUCT*);
static void ScreperData (SCREPER_STRUCT*, char*, int);
static void ScreperDump (SCREPER_STRUCT*, char*, int);
static char* ScreperHtmlEncode (char*);
static void ScreperKnown (SCREPER_STRUCT*, char*, char*, char*);
static void ScreperMore (SCREPER_STRUCT*, char*, int);
static void ScreperUnknown (SCREPER_STRUCT*, char*, char*, char*);
static void ScreenOutput (SCREPER_STRUCT*);
static char* ScreperPauseAll ();
static char* ScreperScreen ();
static void ScreenReport (SCREPER_STRUCT*, char*, ...);
static void ScreenReset (SCREPER_STRUCT*);
static void ScreenScrape (SCREPER_STRUCT*, char*, int);
static int ScreenSentinal (SCREPER_STRUCT*, char*, int);
static void ScreenSnapshot (SCREPER_STRUCT*);
static void ScreenTimeStamp (SCREPER_STRUCT*);

static char* ConfigString (char*, char*, int, int*);

/*************/
/* code page */
/*************/

static char  *CharTable_0_31 [32] = {
".", ".", ".", ".", ".", ".", ".", ".", 
".", ".", ".", ".", ".", ".", ".", ".", 
".", ".", ".", ".", ".", ".", ".", ".", 
".", ".", ".", ".", ".", ".", ".", "." 
};

static char  *CharTable_32_94 [63] = {
" ", "!", "\"", "#", "$", "%", "&amp;", "\'", 
"(", ")", "*", "+", ",", "-", ".", "/", 
"0", "1", "2", "3", "4", "5", "6", "7", 
"8", "9", ":", ";", "&lt;", "=", "&gt;", "?", 
"@", "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  *CharTable_95_127 [33] = {
"_", 
"`", "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", "{", "|", "}", "~", "" 
};

/* special graphics */
static char  *CharTable_SG_95_127 [33] = {
"&nbsp",
"&diams;", "&blk14;", "&rarrb;", "&ddarr;", "&rarrb;", "&ddarr;", "&deg;", "&plusmn;",
"&LeftTeeArrow;", "&DownTeeArrow;", "&boxul;", "&boxdl;", "&boxdr;", "&boxur;", "&boxvr;", "&boxh;",
"&boxh;", "&boxh;", "&boxh;", "&boxh;", "&boxvr;", "&boxvl;", "&boxhu;", "&boxhd;",
"&boxv;", "&le;", "&ge;", "&pi;", "&ne;", "&pound;", "&bull;" 
};

static char  *CharTable_128_255 [128] = {
"&#xc4;", "&#xc5;", "&#xc7;", "&#xc9;", "&#xd1;", "&#xd6;", "&#xdc;", "&#xe1;", 
"&#xe0;", "&#xe2;", "&#xe4;", "&#xe3;", "&#xe5;", "&#xe7;", "&#xe9;", "&#xe8;", 
"&#xdd;", "&#xb0;", "&#xa2;", "&#xa3;", "&#xa7;", "&#xb8;", "&#xb6;", "&#xdf;", 
"&#xae;", "&#xa9;", "&#x2122;", "&#xb4;", "&#xa8;", "&#x2260;", "&#xc6;","&#xd8;", 
"&#xd7;", "&#xb1;", "&#x2264;", "&#x2265;", "&#xa5;", "&#xb5;", "&#xb9;", "&#xb2;", 
"&#xb3;", "&#x3c0;", "&#xa6;", "&#xaa;", "&#xba;", "&#x2592;", "&#xe6;", "&#xf8;", 
"&#xbf;", "&#xa1;", "&#xac;", "&#xbd;", "&#x192;", "&#xbc;", "&#xbe;", "&#xab;", 
"&#xbb;", "&#x2026;", "&#xfffd;", "&#xc0;", "&#xc3;", "&#xd5;", "&#x152;", "&#x153;", 
"&#x2013;", "&#x2014;", "&#x2518;", "&#x2510;", "&#x250c;", "&#x2514;", "&#xf7;", "&#x25c6;", 
"&#xff;", "&#x178;", "&#x253c;", "&#x20ac;", "&#xd0;", "&#xf0;", "&#xde;", "&#xfe;", 
"&#xfd;", "&#xb7;", "&#x23ba;", "&#x23bb;", "&#x2500;", "&#xc2;", "&#xca;", "&#xc1;", 
"&#xcb;", "&#xc8;", "&#xcd;", "&#xce;", "&#xcf;", "&#xcc;", "&#xd3;", "&#xd4;", 
"", "&#xd2;", "&#xda;", "&#xdb;", "&#xd9;", "&#x23bc;", "&#x23bd;", "&#x251c;", 
"&#2524;", "&#x2534;", "&#x252c;", "&#x2502;", "", "", "", ""
};

/**************/
/* JavaScript */
/**************/

static char PageJavaScript [] = "\
<script>\n\
\
var afterNetError = 60*1;\n\
var timeBegin = new Date();\n\
var beforeRefresh = 2;\n" /* Mbytes */
"var borderlw = \'1px white dotted\';\n\
var borderlg = \'1px dimgray dotted\';\n\
var bytesLoaded = 0;\n\
var bytesRefresh = 0;\n\
var etx = false;\n\
var networkError = false;\n\
var prevBytesLoaded = 0;\n\
var retryInterval = 1000*2;\n\
var screperConnected = false;\n\
var screperPaused = false;\n\
var screperScreen;\n\
var stx = false;\n\
var timeStamp = false;\n\
var xhr;\n\
\n\
function doScreper() {\n\
   var url = window.location.search;\n\
   if (url == \'\')\n\
      url = window.location.href + \'?populate=1\';\n\
   else\n\
      url = window.location.href + \'&populate=1\';\n\
   xhr = new XMLHttpRequest();\n\
   xhr.open(\'GET\', url, true);\n\
   networkError = false;\n\
   bytesLoaded = prevBytesLoaded = 0;\n\
   bytesRefresh = beforeRefresh * 1000000 + getRandomInt(5000,15000);\n\
   screperScreen = document.getElementById(\'screperScreen\');\n\
   updateStatus(\'loading&hellip;\');\n\
\n\
   xhr.onabort = function() {\n\
      if (etx) return;\n\
      if (!screperPaused) setTimeout(doScreper,getRandomInt(500,1500));\n\
      screperConnected = false;\n\
   };\n\
\n\
   xhr.onload = function() {\n\
      if (etx) return;\n\
      if (!screperPaused) setTimeout(doScreper,getRandomInt(500,1500));\n\
      screperConnected = false;\n\
   };\n\
\n\
   xhr.onerror = function(err) {\n\
      screperConnected = false;\n\
      if (etx) return;\n\
      networkError = true;\n\
      if (bytesLoaded > 255) {\n\
         screperScreen.style.borderLeft = borderlg;\n\
         updateStatus(\'<span class=\"error\">network error</span>\');\n\
      }\n\
      setTimeout(doScreper,retryInterval);\n\
      retryInterval *= 2;\n\
      if (retryInterval > 1000*60) retryInterval = 1000*60;\n\
   };\n\
\n\
   xhr.onprogress = function(event) {\n\
      bytesLoaded = event.loaded;\n\
      if (bytesLoaded) {\n\
         var chunk = xhr.response.substring(prevBytesLoaded);\n"

/*
~~~~~~~~~~~~~~~~~~~~
Detect a "remote" JavaScript execution request generated by the terminal output
data stream.  A commented "<!-- <script> -->" sentinal triggers code to extract
whatever is contained between it and the closing sentinal and eval() it.
*/

"         if (chunk.substring(0,13) == \'<!-- <script>\') {\n\
            var code = chunk.indexOf(\'</script>\');\n\
            code = chunk.substring(13,code);\n\
            eval(code);\n\
            chunk = chunk.substring(code+9);\n\
         }\n\
         else\n\
         if (stx = chunk.substring(0,12) == \'<!-- STX -->\')\n\
            chunk = chunk.substring(12);\n\
         else\n\
         if (etx = chunk.substring(0,12) == \'<!-- ETX -->\')\n\
            chunk = chunk.substring(12);\n\
         if (!screperPaused && !pauseAll()) screperScreen.innerHTML = chunk;\n\
         screperScreen.style.borderLeft = borderlw;\n\
         resizeIframe();\n\
         if (etx) { \n\
            xhr.onload();\n\
            return;\n\
         }\n\
         retryInterval = 1000*2;\n\
         updateStatus(\'\');\n\
      }\n\
      prevBytesLoaded = bytesLoaded;\n\
      screperConnected = true;\n\
      if (bytesLoaded > bytesRefresh) xhr.abort();\n\
   };\n\
\n\
   xhr.send();\n\
}\n\
\n\
function updateStatus(string) {\n\
   document.getElementById(\'screperStatus\').innerHTML = string;\n\
}\n\
\n\
function pauseResume() {\n\
   if (screperPaused) {\n\
      document.getElementById(\'pauseButton\').innerHTML = \'"
BUTTON_PAUSE "\';\n\
      if (!screperConnected) setTimeout(doScreper,getRandomInt(500,1500));\n\
   }\n\
   else\n\
      document.getElementById(\'pauseButton\').innerHTML = \'"
BUTTON_UNPAUSE "\';\n\
   screperPaused = !screperPaused;\n\
}\n\
\n\
function pauseAll() {\n\
   if (parent && frameElement && parent.pauseAll) \
return window.parent.pauseAll();\n\
   return false;\n\
}\n\
\n\
function getRandomInt(min,max) {\n\
   min = Math.ceil(min);\n\
   max = Math.floor(max);\n\
   return Math.floor(Math.random() * (max - min + 1)) + min;\n\
}\n\
\n\
function resizeIframe() {\n\
   if (parent && frameElement && parent.resizeIframe) {\n\
      var width = document.documentElement.scrollWidth;\n\
      var height = document.documentElement.scrollHeight;\n\
      parent.resizeIframe(window.frameElement,width,height);\n\
   }\n\
   else {\n\
      var sobj = document.getElementById(\'screperScreen\');\n\
      var width = sobj.scrollWidth;\n\
      var height = sobj.scrollHeight;\n\
      sobj.style.borderWidth = 0;\n\
      sobj.style.width = width + \'px\';\n\
      sobj.style.height = height + \'px\';\n\
   }\n\
}\n\
\n"

/*
~~~~~~~~~~~~~~~~~~~~
setPageLength() is called "remotely" via xhr.onprogress() in doScreper().
It DOM generates a (-page) of length blank lines (e.g. 24) and then measures
the height (in pixels) of that page.  This value is stored in the parent page
and used to constrain the growth of terminal heights during periodic restart of
screen output collection. 
*/

"function setPageLength(length,plus) {\n\
   if (parent.maxHeight && !plus) return;\n\
   var pre = document.getElementById('screperScreen');\n\
   for (var cnt = 0; cnt < length + 1; cnt++) {\n\
      var br = document.createElement('br');\n\
      pre.appendChild(br);\n\
   }\n\
   if (!parent.maxHeight || plus) \
parent.maxHeight = document.body.scrollHeight;\n\
   pre.removeChild(pre.childNodes[0]);\n\
}\n\
\n"

/*
~~~~~~~~~~~~~~~~~~~~
setFontSize(), setCommand(), setUtility() are called "remotely" via
xhr.onprogress() in doScreper().
*/

"function setFontSize(size) {\n\
   document.body.style.fontSize = size;\n\
}\n\
\n\
function setCommand(string) {\n\
   document.getElementById(\'screperCommand\').innerHTML = string;\n\
}\n\
\n\
function setUtility(string) {\n\
   document.getElementById('screperUtility').innerHTML = string;\n\
}\n\
</script>\n";

/*
~~~~~~~~~~~~~~~~~~~~
Iframe meta characteristics can only be managed via the parent page where
multiple terminals are displayed.
*/

static char  *ResizeJavaScript =
"<script>\n\
var maxHeight;\n\
function resizeIframe(fobj,width,height) {\n\
   fobj.style.borderWidth = 0;\n\
   fobj.width = width;\n\
   if (maxHeight && height > maxHeight) height = maxHeight;\n\
   fobj.height = height;\n\
};\n\
</script>\n";

/*
~~~~~~~~~~~~~~~~~~~~
Pause-all button on parent page used where multiple terminals displayed.
*/

static char  *ButtonPauseAll =
"<script>\n\
var screperAllPaused = false;\n\
function pauseAll() {\n\
   return screperAllPaused;\n\
}\n\
function pauseAllResume() {\n\
   if (screperAllPaused)\n\
      document.getElementById(\'pauseAllButton\').innerHTML = \'"
BUTTON_PAUSEALL "\';\n\
   else\n\
      document.getElementById(\'pauseAllButton\').innerHTML = \'"
BUTTON_UNPAUSEALL "\';\n\
   screperAllPaused = !screperAllPaused;\n\
}\n\
</script>\n\
<button id=\"pauseAllButton\" type=\"submit\" onclick=\"pauseAllResume()\" \
style=\"width:7em;max-width:7em;margin-top:1em;\">"
BUTTON_PAUSEALL "</button>\n";

/*******/
/* CSS */
/*******/

static char PageCSS [] =
"\
<style type=\"text/css\">\n\
\
body { font-family:arial,verdana,helvetica,sans; font-size:11pt; \
background-color:white; color:black; margin:1em; }\n\
.screperCommand { display:inline-block; margin:0 0 0 1em; \
font-family:monospace; }\n\
.screperReport { background-color:yellow; color:black; }\n\
.screperScreen { font-family:monospace; font-size:1.0em; line-height:100%; \
white-space:pre; border-left: 1px white dotted; }\n\
.screperStatus { margin:0 0 0 1em; font-size:120%%; font-style:italic; }\n\
.screperUtility { margin:0 1em 0 1em; letter-spacing:0.07em; }\n\
#pauseButton { width:2.5em; max-width:2.5em; }\n\
\
._em1 { font-style:normal; font-weight:bold; }\n\
._em4 { font-style:normal; text-decoration:underline; }\n\
._em5 { font-style:normal; animation:blinker 1s step-start infinite; }\n\
._em7 { font-style:normal; background-color:black; color:white; }\n\
\
._attr { font-style:normal; background-color:aqua; }\n\
._chars { font-style:normal; background-color:lime; }\n\
._cmd { font-style:normal; background-color:khaki; }\n\
._kno { font-style:normal; background-color:orange; }\n\
._unk { font-style:normal; background-color:yellow; }\n\
._rxc { font-style:normal; background-color:aqua; }\n\
._data { font-style:normal; background-color:lightcyan; \
display:inline-block; white-space:normal; width:80em; max-width:80em; \
border-bottom:1px dashed black; }\n\
._dump { font-style:normal; background-color:lightcyan; \
display:inline-block; border-bottom:1px dashed black; }\n\
\
</style>\n\
";

/*****************************************************************************/
/*
Allocate the structure and set some defaults.
*/

void* ScreperInit ()

{
   SCREPER_STRUCT  *scrptr;

   /*********/
   /* begin */
   /*********/

   scrptr = calloc (1, sizeof(SCREPER_STRUCT));
   if (!scrptr) exit (vaxc$errno);

   scrptr->PtdBits8 = TT$M_EIGHTBIT;
   scrptr->PtdDevice = TT$_VT100;
   scrptr->PtdPageLength = 24;
   scrptr->PtdPageWidth = 80;
   scrptr->UtilityLength = sprintf (scrptr->Utility, "-utility");
   scrptr->ScrOut = fdopen (dup (fileno (stdout)), "w");

   return ((void*)scrptr);
}

/*****************************************************************************/
/*
Release resources.
*/

void* ScreperDone (void *vscrptr)

{
   SCREPER_STRUCT  *scrptr;

   scrptr = (SCREPER_STRUCT*)vscrptr;

   if (scrptr->ScreenBuffer) free (scrptr->ScreenBuffer);

   fflush (scrptr->ScrOut);
   fclose (scrptr->ScrOut);

   free (scrptr);

   return (NULL);
}

 /*****************************************************************************/
/*
Parse the string of screper directives to set up the virtual terminal session. 
When the -dcl directive is encountered the rest of the string is considered and
the terminal session is created and the DCL executed.  Returns a pointer to a
string which should begin with a DCL status value (e.g. %X00000001).
*/

char* ScreperDo (void *vscrptr, char *this)

{
   int  status;
   char  *aptr, *cptr, *sptr, *zptr;
   SCREPER_STRUCT  *scrptr;

   /*********/
   /* begin */
   /*********/

   if (!strncmp (this, "-javascript", 4))
      return (PageJavaScript);

   if (!strncmp (this, "-css", 4))
      return (PageCSS);

   if (!strncmp (this, "-pause", 4))
      return (ButtonPauseAll);

   if (!strncmp (this, "-resize", 4))
      return (ResizeJavaScript);

   if (!strncmp (this, "-screen", 4))
      return (ScreperScreen (scrptr));

   if (!strncmp (this, "-version", 4))
      return (SoftwareId);

   scrptr = (SCREPER_STRUCT*)vscrptr;
   if (!scrptr) return ("%X00000014 hurrump");

   aptr = strdup (this);
   while (*aptr)
   {
      while (isspace(*aptr)) aptr++;
      cptr = aptr;
      while (*aptr && *aptr != '=' && !isspace(*aptr)) aptr++;
      if (*aptr == '=') aptr++;

      if (!strncmp (cptr, "-7bit", 4))
      {
         scrptr->PtdBits8 = 0;
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-8bit", 4))
      {
         scrptr->PtdBits8 = TT$M_EIGHTBIT;
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-dcl", 4))
      {
         zptr = (sptr = scrptr->DclBuffer) + sizeof(scrptr->DclBuffer)-1;
         while (*aptr && sptr < zptr) *sptr++ = *aptr++;
         *sptr = '\0';
         sptr = ScreperBegin (scrptr);
         return (sptr);
      }

      if (!strncmp (cptr, "-device", 4))
      {
         scrptr->PtdDevice = atoi(aptr);
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-exit", 4))
      {
         scrptr->InputBuffer[0] = 0x1a;
         scrptr->InputLength = 1;
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-font", 4))
      {
         scrptr->FontSize = atoi(aptr);
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-input", 4))
      {
         while (*cptr && *cptr != '=') cptr++;
         if (*cptr) cptr++;
         aptr = ConfigString (cptr, scrptr->InputBuffer,
                                    sizeof(scrptr->InputBuffer),
                                    &scrptr->InputLength);
         continue;
      }

      if (!strncmp (cptr, "-inspect", 4))
      {
         scrptr->InspectStream = atoi (aptr);
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-page", 4))
      {
         if (*aptr == '+')
            scrptr->PtdPageLength = scrptr->PtdPagePlus = atoi(++aptr);
         else
            scrptr->PtdPageLength = atoi(aptr);
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-repeat", 4))
      {
         scrptr->RepeatSeconds = atoi(aptr);
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-sentinal", 4))
      {
         while (*cptr && *cptr != '=') cptr++;
         if (*cptr) cptr++;
         aptr = ConfigString (cptr, scrptr->SnapshotSentinal,
                                    sizeof(scrptr->SnapshotSentinal),
                                    &scrptr->SnapshotSentinalLength);
         continue;
      }

      if (!strncmp (cptr, "-snapshot", 4))
      {
         scrptr->SnapshotTicks = atoi(aptr);
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      if (!strncmp (cptr, "-timestamp", 4))
      {
         while (*cptr && *cptr != '=' && !isspace(*cptr)) cptr++;
         if (*cptr == '=')
         {
            if (*cptr) cptr++;
            aptr = ConfigString (cptr, scrptr->TimeStamp,
                                       sizeof(scrptr->TimeStamp),
                                       &scrptr->TimeStampLength);
         }
         if (!scrptr->TimeStamp[0]) strcpy (scrptr->TimeStamp, "!20%D<p>");
         continue;
      }

      if (!strncmp (cptr, "-utility", 4))
      {
         while (*cptr && *cptr != '=') cptr++;
         if (*cptr) cptr++;
         aptr = ConfigString (cptr, scrptr->Utility,
                                    sizeof(scrptr->Utility),
                                    &scrptr->UtilityLength);
         continue;
      }

      if (!strncmp (cptr, "-width", 4))
      {
         scrptr->PtdPageWidth = atoi(aptr);
         while (*aptr && !isspace(*aptr)) aptr++;
         continue;
      }

      while (*aptr && !isspace(*aptr)) aptr++;
      *aptr = '\0';
      sptr = calloc (1, aptr - cptr + 32);
      sprintf (sptr, "%%X00000014 Unknown: %s", cptr);
      return (sptr);
   }

   return ("%X00000001");
}

/*****************************************************************************/
/*
Validate the terminal session charactersitics and execute the DCL.  Return a
string beginning with a VMS status (e.g. %X00000001).
*/

static char* ScreperBegin (SCREPER_STRUCT *scrptr)

{
   static char  sbuf [64];

   int  status;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (!(scrptr->PtdDevice >= TT$_VT100 && scrptr->PtdDevice <= TT$_VT132) &&
       !(scrptr->PtdDevice >= TT$_LA36 && scrptr->PtdDevice <= TT$_LA210))
      return ("%X00000014 device");

   if (scrptr->TimeStamp[0]) scrptr->PtdPageLength += 2;

   if (scrptr->PtdPageLength < 24 || scrptr->PtdPageLength > MAX_PAGE)
      return ("%X00000014 page length (24..255)");

   if (scrptr->PtdPageWidth < 80 || scrptr->PtdPageWidth > MAX_WIDTH)
      return ("%X00000014 page width (80, 132, ..255)");

   if (scrptr->PtdBits8 && scrptr->PtdBits8 != TT$M_EIGHTBIT)
      return ("%X00000014 data bits (7, 8)");

   if (scrptr->RepeatSeconds < 0)
      scrptr->RepeatSeconds = -scrptr->RepeatSeconds;

   if (scrptr->SnapshotTicks)
   {
      if (scrptr->RepeatSeconds)
         return ("%X00000014 cannot repeat snapshots");
      if (scrptr->SnapshotTicks && (scrptr->SnapshotTicks < 5 ||
                                    scrptr->SnapshotTicks > 600))
         return ("%X00000014 1/10 second (5..600)");
   }

   if (!scrptr->DclBuffer[0])
      return ("%X00000014 requires DCL command(s)");

   if (!scrptr->InspectStream)
      if (cptr = getenv ("SCREPER_INSPECT"))
         scrptr->InspectStream = atol (cptr);

   scrptr->AnsiMode = 1;

   status = PtdSpawn (scrptr);

   sprintf (sbuf, "%%X%08.08X", status);

   return (sbuf);
}

/*****************************************************************************/
/*
Create a pseudo-terminal driver terminal session, spawn a process attached to
that session, and write CLI commands to that process.
*/

static int PtdSpawn (SCREPER_STRUCT *scrptr)

{
   static ulong  DevNamItem = DVI$_DEVNAM;
   static ulong  JpiMasterPidItem = JPI$_MASTER_PID;

   /* nowait nolognam noclisym nokeypad nocontrol */
   static unsigned long  flags = 0x6f;

   $DESCRIPTOR (PromptDsc, scrptr->CliPrompt);
   $DESCRIPTOR (DevNameDsc, scrptr->PtdDevName);
   $DESCRIPTOR (PrcNamDsc, scrptr->PrcNam);

   int  status;
   uint64  time64;
   ushort  slen;
   ulong  JpiMasterPidPid;
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (scrptr->InspectStream) fprintf (scrptr->ScrOut, "PtdSpawn()\n");

   sys$gettim (&time64);
   scrptr->EtxLength = sprintf (scrptr->EtxSentinal, "!!ETX%11.11llx", time64);
   scrptr->StxLength = sprintf (scrptr->StxSentinal, "!!STX%11.11llx", time64);
   scrptr->PromptLength = sprintf (scrptr->CliPrompt, "%14.14llx$$", time64);

   status = lib$getjpi (&JpiMasterPidItem, 0, 0, &JpiMasterPidPid, 0, 0);
   if (!(status & 1)) return (status);

   status = lib$get_ef (&scrptr->EfnRead);
   if (!(status & 1)) return (status);

   /* if not trying to REstart the subprocess */
   if (!scrptr->inadr[0])
   {
      status = sys$expreg (PTD_BUFFER_PAGES, &scrptr->inadr, 0, 0);
      if (!(status & 1)) return (status);

      scrptr->PtdReadBuffer = (char*)scrptr->inadr[0];
      scrptr->PtdWriteBuffer = (char*)scrptr->inadr[0] + PTD_READ_SIZE;

      /* set the terminal characteristics */
      scrptr->chbuf[0] = (scrptr->PtdPageWidth << 16) |
                         (scrptr->PtdDevice << 8) | DC$_TERM;
      scrptr->chbuf[1] = (scrptr->PtdPageLength << 24) |
                         scrptr->PtdBits8 | TT$M_SCOPE | TT$M_WRAP |
                         TT$M_MECHTAB | TT$M_LOWER | TT$M_TTSYNC;
      scrptr->chbuf[2] = TT2$M_EDIT | TT2$M_HANGUP | TT2$M_ANSICRT |
                         TT2$M_AVO | TT2$M_DECCRT | TT2$M_DECCRT2;
   }

   memset ((char*)scrptr->inadr[0], 0,
           (char*)scrptr->inadr[1] - (char*)scrptr->inadr[0]);
   scrptr->PtdReadStatus = scrptr->LibSpawnPid =
      scrptr->LibSpawnStatus = scrptr->PtdWriteStatus = 0;

   status = ptd$create (&scrptr->PtdChan, 0,
                        scrptr->chbuf, sizeof(scrptr->chbuf),
                        0, 0, 0, &scrptr->inadr);
   if (!(status & 1)) return (status);

   status = lib$getdvi (&DevNamItem, &scrptr->PtdChan, 0, 0,
                        &DevNameDsc, &slen);
   scrptr->PtdDevName[DevNameDsc.dsc$w_length = slen] = '\0';
   if (!(status & 1)) return (status);

   sprintf (scrptr->PrcNam, "screper_%4.4X", JpiMasterPidPid & 0xffff);
   PrcNamDsc.dsc$a_pointer = scrptr->PrcNam;
   PrcNamDsc.dsc$w_length = strlen(scrptr->PrcNam);

   PromptDsc.dsc$w_length = scrptr->PromptLength;

   status = lib$spawn (0, &DevNameDsc, &DevNameDsc, &flags, &PrcNamDsc,
                       &scrptr->LibSpawnPid, &scrptr->LibSpawnStatus, 0,
                       &PtdSpawnAst, scrptr, &PromptDsc, 0, 0);
   if (!(status & 1)) return (status);

   for (int cnt = 10; cnt; cnt--)
   {
      sleep (1);
      if (scrptr->LibSpawnPid) break;
   }

   for (;;)
   {
      status = PtdWrite (scrptr);
      if (!(status && 1)) break;
      PtdRead (scrptr);
      /* wait for the subprocess output to finish */
      sys$hiber();
      if (!scrptr->RepeatSeconds) break;
      ScreenReset (scrptr);
      /* repeat every so many seconds */
      sleep (scrptr->RepeatSeconds);
   }

   return (status);
}

/*****************************************************************************/
/*
Subprocess self-destruct.
*/

static void PtdPoof (SCREPER_STRUCT *scrptr, int status)

{
   ulong  pid;

   /*********/
   /* begin */
   /*********/

   PtdDelete (scrptr);
   if (!scrptr->SnapshotTicks) return;
   if (!(pid = scrptr->LibSpawnPid)) return;
   scrptr->LibSpawnPid = 0;
   sys$forcex (&pid, 0, status);
}

/*****************************************************************************/
/*
Delete the pseudo-terminal.
*/

static void PtdDelete (SCREPER_STRUCT *scrptr)

{
   /*********/
   /* begin */
   /*********/

   if (scrptr->PtdChan) ptd$delete (scrptr->PtdChan);
   scrptr->PtdChan = 0;
}

/*****************************************************************************/
/*
Subpocess completion AST.
*/

static void PtdSpawnAst (SCREPER_STRUCT *scrptr)

{
   /*********/
   /* begin */
   /*********/

   if (scrptr->PtdChan) ptd$delete (scrptr->PtdChan);
   scrptr->LibSpawnPid = 0;

   sys$wake (0, 0);
}

/*****************************************************************************/
/*
Expects a string containing newline separated, multiple DCL commands, and
writes them successively to the subprocess' SYS$INPUT.  Any preceding the final
command are considered "setup" commands and not scraped.   Uses two sentinals
to delineate the start (STX) and end (ETX) of command output.  These are
detected by PtdRead().  Immediately before the final DCL command (i.e. not '\n'
terminated) the STX terminal is written as a commented "command", is echoed and
detectible by PtdRead().  When detected, the next read will be from the final
command and therefore valid data for screper to begin parsing.  Immediately
after the final command the ETX sentinal is similarly written as a commented
"command" to disable screen scraping.
*/

static int PtdWrite (SCREPER_STRUCT *scrptr)

{
   int  bcnt, status;
   char  *bptr, *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   zptr = (bptr = scrptr->PtdWriteBuffer) + PTD_WRITE_SIZE;
   sptr = bptr + sizeof(ulong);

   if (scrptr->RepeatCmdPtr)
      scrptr->NextWritePtr = scrptr->RepeatCmdPtr;
   else
   {
      scrptr->NextWritePtr = scrptr->DclBuffer;
      for (cptr = "ON ERROR THEN EXIT\r";
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
   }

   for (;;)
   {
      for (cptr = scrptr->NextWritePtr; *cptr && *cptr != '\n'; cptr++);
      while (*cptr == '\n') cptr++;
      if (!*cptr)
      {
         /* final command, start-of-text */
         for (cptr = scrptr->StxSentinal; *cptr && sptr < zptr; *sptr++ = *cptr++);
         if (sptr < zptr) *sptr++ = '\r';
      }

      cptr = scrptr->RepeatCmdPtr = scrptr->NextWritePtr;
      /* copy CLI command compressing <CR><LF> to just <LF> */
      while (*cptr && *cptr != '\n' && sptr < zptr)
         if (*(USHORTPTR)cptr == '\r\n')
            cptr++;
         else
            *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = '\r';
      while (*cptr == '\n') cptr++;
      scrptr->NextWritePtr = cptr;
      if (!*cptr) break;
   }

   /* any -input or -exit */
   for (bcnt = 0; bcnt < scrptr->InputLength && sptr < zptr; bcnt++)
      *sptr++ = scrptr->InputBuffer[bcnt];

   /* end-of-text */
   for (cptr = scrptr->EtxSentinal; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = '\r';

   if (!scrptr->RepeatSeconds)
   {
      /* ensure subprocess exits */
      for (cptr = "EOJ\r"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   if (sptr >= zptr)
   {
      status = SS$_RESULTOVF;
      PtdPoof (scrptr, status);
      return (status);
   }

   *sptr = '\0';
   bcnt = sptr - bptr - sizeof(ulong);

   if (scrptr->InspectStream)
      fprintf (scrptr->ScrOut, "<em class=\"_cmd\">%s</em>\n",
               bptr + sizeof(ulong));

   /* synchronous write */
   status = ptd$write (scrptr->PtdChan, 0, 0, bptr, bcnt, 0, 0);

   if (scrptr->InspectStream)
   {
      fprintf (scrptr->ScrOut, "%%X%08.08X %%X%08.08X %d\n",
               status, *(USHORTPTR)bptr, *(USHORTPTR)(bptr+2));
      fflush (scrptr->ScrOut);
   }

   fprintf (scrptr->ScrOut, "<!-- <script>");
   fprintf (scrptr->ScrOut, "setPageLength(%d,%s);\n",
                            scrptr->PtdPageLength,
                            scrptr->PtdPagePlus ? "true" : "false");
   if (scrptr->FontSize)
      fprintf (scrptr->ScrOut, "setFontSize(\'%dpt\');\n", scrptr->FontSize);
   if (scrptr->Utility[0])
      fprintf (scrptr->ScrOut, "setUtility(\'%s\');\n",
                               ScreperHtmlEncode(scrptr->Utility));
   fprintf (scrptr->ScrOut, "setCommand(\'%s\');\n",
                            ScreperHtmlEncode(scrptr->RepeatCmdPtr));
   fprintf (scrptr->ScrOut, "</script> -->");
   fflush (scrptr->ScrOut);

   if (!(status & 1)) PtdPoof (scrptr, status);

   fprintf (scrptr->ScrOut, "<!-- STX -->");
   fflush (scrptr->ScrOut);

   return (status);
}

/*****************************************************************************/
/*
Synchronous write direct to the PTD returing a VMS status value.
Record should be terminated with a carriage-return.
*/

static int PtdDollarWrite (ushort chan, char* bptr, int bcnt)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (!bptr || !bcnt) return (SS$_BUGCHECK);
   status = ptd$write (chan, 0, 0, bptr, bcnt, 0, 0);
   return (status);
}

/*****************************************************************************/
/*
Read subprocess SYS$OUTPUT records.
*/

static void PtdRead (SCREPER_STRUCT *scrptr)

{
   int  bcnt, status;
   char  *bptr, *cptr, *czptr;

   /*********/
   /* begin */
   /*********/

   if (scrptr->PtdReadAst)
   {
      scrptr->PtdReadAst = 0;

      bptr = scrptr->PtdReadBuffer;

      scrptr->PtdReadStatus = *(USHORTPTR)bptr;
      if (!(scrptr->PtdReadStatus & 1))
         PtdPoof (scrptr, scrptr->PtdReadStatus);

      bptr += sizeof(ushort);
      bcnt = *(USHORTPTR)bptr;
      bptr += sizeof(ushort);
      /* ensure the read looks nul-terminated */
      *(USHORTPTR)(bptr + bcnt) = 0;

      if (scrptr->InspectStream >= 4)
      {
         fprintf (scrptr->ScrOut, "PtdRead(%d)\n", bcnt);
         ScreperData (scrptr, bptr, bcnt);
      }

      if (!scrptr->StxDetected)
      {
         /* detect the screen start sentinal */
         czptr = (cptr = bptr) + bcnt;
         while (*(ULONGPTR)cptr != '$$!!' && cptr < czptr) cptr++;
         if (cptr < czptr)
            if (!memcmp (cptr+2, scrptr->StxSentinal, scrptr->StxLength))
               scrptr->StxDetected++;
      }
      else
      {
         /* the first output after detection ..... */
         scrptr->StxDetected++;
         /* look for any take-snapshot sentinal string */
         if (scrptr->SnapshotSentinalLength)
            if (ScreenSentinal (scrptr, bptr, bcnt))
               ScreenScrape (scrptr, NULL, 0);
      }

      if (scrptr->StxDetected > 2)
      {
         /* ..... is the final command echo */
         if (bcnt < 48)
         {
            /* detect the end of output sentinal */
            czptr = (cptr = bptr) + bcnt;
            while (*(ULONGPTR)cptr != '$$!!' && cptr < czptr) cptr++;
            if (cptr < czptr)
               if (!memcmp (cptr+2, scrptr->EtxSentinal, scrptr->EtxLength))
               {
                  scrptr->EtxDetected = 1;
                  /* let the parent script know */
                  if (!scrptr->RepeatSeconds)
                     fputs ("<!-- ETX -->", scrptr->ScrOut);
                  ScreenOutput (scrptr);
                  fflush (scrptr->ScrOut);
                  /* wake the hibernating PtdSpawn() */
                  sys$wake(0,0);
                  return;
               }
         }

         ScreenScrape (scrptr, bptr, bcnt);
      }
   }

   /* asynchronous read */
   scrptr->PtdReadAst = 1;
   status = ptd$read (scrptr->EfnRead, scrptr->PtdChan, PtdRead, scrptr,
                      scrptr->PtdReadBuffer, PTD_READ_SIZE-(sizeof(ulong)));
   if (!(status & 1)) PtdPoof (scrptr, status);
}

/*****************************************************************************/
/*
Process output from the pseudo-terminal session.

The virtual terminal has a virtual cursor positioned by the current values of
/row/ and /col/ variables.  Columns are numbered 0..width-1 and rows 0..page-1.

Each cursor position in the virtual screen is 32 bits deep.

   0              7 8             15 16            23 24            31
  |................|................|................|................|
  |   8 bit char   |    SGR bits    |    G0 alpha    |    G1 alpha    |

                                     (these two fields are extravagant)

Function returns true to continue scraping or false to conclude program.
*/

static void ScreenScrape (SCREPER_STRUCT *scrptr, char *record, int length)

{
#define MAX_PN 10

   int  ch, cnt, ccnt, col, inspect, nlcnt, page, pncnt,
        rcnt, row, size, tab, width;
   int  pn [MAX_PN];
   ulong  value;
   ulong  *screen;
   char  *cptr, *czptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (!scrptr->ScreenBuffer)
   {
      /* initialise */
      scrptr->ScreenBufferSize = scrptr->PtdPageLength *
                                 scrptr->PtdPageWidth *
                                 sizeof(ulong);
      scrptr->ScreenBuffer = (ulong*)calloc (1, scrptr->ScreenBufferSize);
      if (!scrptr->ScreenBuffer) exit (vaxc$errno);
      scrptr->CharTableG0 = 'B' << 16;
      scrptr->CharTableG1 = '0' << 24;
      scrptr->ScreenRow = scrptr->ScreenCol = 0;
      scrptr->CurSave = -1;
      /* kick off the ticker */
      if (scrptr->SnapshotTicks) ScreenSnapshot (scrptr);
   }

   if (scrptr->InspectStream >= 4)
      ScreenDump (scrptr, "BEFORE", __LINE__);

   /* get local copies of structure data */
   inspect = scrptr->InspectStream;
   screen = scrptr->ScreenBuffer;
   page = scrptr->PtdPageLength;
   width = scrptr->PtdPageWidth;
   row = scrptr->ScreenRow;
   col = scrptr->ScreenCol;

   if (inspect)
   {
      fprintf (scrptr->ScrOut, "ScreenScrape(%d) %dx%d\n", length, width, page);
      ScreperData (scrptr, record, length);
   }

   czptr = (cptr = record) + length;

   /* let the ticker know data has been received */
   scrptr->SnapshotDataCount += length;
   if (scrptr->SnapshotTicks)
      scrptr->SnapshotTickCount = scrptr->SnapshotTicks;

   /************/
   /* populate */
   /************/

   while (cptr < czptr)
   {
      if ((*cptr >= 32 && *cptr < 127) ||
          (*cptr >= 160 && *cptr <= 255))
      {
         /*************/
         /* printable */
         /*************/

         /* replace leading nuls with with spaces */
         for (ccnt = col-1; ccnt >= 0; ccnt--)
            if (!screen[(row*width)+ccnt])
               screen[(row*width)+ccnt] = ' ';

         zptr = cptr;
         while (cptr < czptr &&
                ((*cptr >= 32 && *cptr < 127) ||
                 (*cptr >= 160 && *cptr <= 255)))
         {
            value = *cptr++;
            if (scrptr->SgrBold) value |= SGR_BOLD;
            if (scrptr->SgrBlink) value |= SGR_BLINK;
            if (scrptr->SgrUnder) value |= SGR_UNDER;
            if (scrptr->SgrReverse) value |= SGR_REVER;
            if (scrptr->SgrG1) value |= SGR_G1;
            value |= scrptr->CharTableG0;
            value |= scrptr->CharTableG1;

            /* insert this character */
            screen[(row*width)+col] = value;
            if (col < width) col++;
         }
         if (inspect) ScreperDump (scrptr, zptr, cptr - zptr);
         continue;
      }

      if ((*(USHORTPTR)cptr == '\x1b[') ||  /* 7 bit CSI */
          (*cptr == '\x9b'))                /* 8 bit CSI */
      {
         /*******/
         /* CSI */
         /*******/

         if (scrptr->InspectStream >= 4)
            ScreenDump (scrptr, FI_LI);

         if (*(USHORTPTR)cptr == '\x1b[')
            cptr += 2;
         else
            cptr++;

         if (inspect) ScreperKnown (scrptr, "CSI+", cptr, czptr);

         pncnt = 0;
         pn[pncnt++] = atoi(cptr);
         while (isdigit(*cptr) && cptr < czptr) cptr++;
         while (*cptr == ';')
         {
            if (cptr >= czptr) break;
            cptr++;
            pn[pncnt++] = atoi(cptr);
            if (pncnt >= MAX_PN) break;
            while (isdigit(*cptr) && cptr < czptr) cptr++;
         }
         if (cptr >= czptr) break;
         ch = *cptr++;

         if (ch == 'm')
         {
            /*******/
            /* SGR */
            /*******/

            for (cnt = 0; cnt < pncnt; cnt++)
            {
               if (pn[cnt] == 0)
                  scrptr->SgrBlink = scrptr->SgrBold =
                     scrptr->SgrReverse = scrptr->SgrUnder = 0;
               else
               if (pn[cnt] == 1)
                  scrptr->SgrBold = 1;
               else
               if (pn[cnt] == 4)
                  scrptr->SgrUnder = 1;
               else
               if (pn[cnt] == 5)
                  scrptr->SgrBlink = 1;
               else
               if (pn[cnt] == 7)
                  scrptr->SgrReverse = 1;
               if (inspect) printf ("<em class=\"_attr\">SGR "
                                    "%dx%d %d</em>\n", row, col, pn[cnt]);
            }
            continue;
         }

         if (ch >= 'A' && ch <= 'G')
         {
            /********/
            /* move */
            /********/

            if (ch == 'A')
            {
               /* move cursor up */
               if (row - pn[0] >= 0) row -= pn[0];
            }
            else
            if (ch == 'B')
            {
               /* move cursor down */
               if (row + pn[0] < page) row += pn[0];
            }
            else
            if (ch == 'C')
            {
               /* move cursor right */
               if (col + pn[0] < width) col += pn[0];
            }
            else
            if (ch == 'D')
            {
               /* move cursor left */
               if (col - pn[0] >= 0) col -= pn[0];
            }
            else
            if (ch == 'E')
            {
               /* moves cursor to beginning of lines down */
               if (row + pn[0] < page)
               {
                  row += pn[0];
                  col = 0;
               }
            }
            else
            if (ch == 'F')
            {
               /* moves cursor to beginning of lines up */
               if (row + pn[0] >= 0)
               {
                  row += pn[0];
                  col = 0;
               }
            }
            else
            if (ch == 'G')
            {
               /* move cursor to col */
               if (pn[0] >= 0 && pn[0] < width) col = pn[0];
            }
            if (inspect) ScreenCursor (scrptr, "ROWxCOL %dx%d", row, col);
            continue;
         }

         if (ch == 'H' || ch == 'f')
         {
            /*********/
            /* go to */
            /*********/

            /* row and col */
            /* our emulation has cols 0..width-1 and rows 0..page-1 */
            if (pn[0]) pn[0]--;
            if (pn[1]) pn[1]--;
            if (pn[0] < page) row = pn[0]; else row = page-1;
            if (pn[1] < width) col = pn[1]; else col = width-1;
            if (inspect) ScreenCursor (scrptr, "ROWxCOL %dx%d", row, col);
            continue;
         }

         if (ch == 'J')
         {
            /*********/
            /* erase */
            /*********/

            if (pn[0] == 0)
            {
               /* clear from current to end of screen */
               for (ccnt = col; ccnt < width; ccnt++)
                  screen[(row*width)+ccnt] = 0;
               /* then to end of screen */
               for (rcnt = row+1; rcnt < page; rcnt++)
                  for (ccnt = 0; ccnt < width; ccnt++)
                     screen[(rcnt*width)+ccnt] = 0;
               if (inspect) ScreenCursor (scrptr, "ERASE EOL %dx%d", row, col);
               continue;
            }
            if (pn[0] == 1)
            {
               /* clear start of screen to current */
               for (rcnt = 0; rcnt <= row; rcnt++)
                  for (ccnt = 0; ccnt < width; ccnt++)
                     screen[(rcnt*width)+ccnt] = 0;
               /* then clear from start of row up until the current col */
               for (ccnt = 0; ccnt <= col; ccnt++)
                  screen[(row*width)+ccnt] = 0;
               if (inspect) ScreenCursor (scrptr, "ERASE SOS %dx%d", row, col);
               continue;
            }
            if (pn[0] == 2)
            {
               /* clear screen */
               for (rcnt = 0; rcnt < page; rcnt++)
                  for (ccnt = 0; ccnt < width; ccnt++)
                     screen[(rcnt*width)+ccnt] = 0;
               if (inspect) ScreenCursor (scrptr, "ERASE CLS %dx%d", row, col);
               continue;
            }
            continue;
         }

         if (ch == 'K')
         {
            /*********/
            /* clear */
            /*********/

            if (pn[0] == 0)
            {
               /* clear to end of line */
               for (ccnt = col; ccnt < width; ccnt++)
                  screen[(row*width)+ccnt] = 0;
               if (inspect) ScreenCursor (scrptr, "ERASE %dx%d EOL", row, col);
               continue;
            }
            if (pn[0] == 1)
            {
               /* clear from start of line to current */
               for (ccnt = 0; ccnt <= col; ccnt++)
                  screen[(row*width)+ccnt] = 0;
               if (inspect) ScreenCursor (scrptr, "ERASE %dx%d SOL", row, col);
               continue;
            }
            if (pn[0] == 2)
            {
               /* clear all lines */
               for (rcnt = 0; rcnt < page; rcnt++)
                  for (ccnt = 0; ccnt < width; ccnt++)
                     screen[(rcnt*width)+ccnt] = 0;
               if (inspect) ScreenCursor (scrptr, "ERASE %dx%d CLS", row, col);
               continue;
            }
            continue;
         }

         if (ch == 'c')
         {
            /****************/
            /* what are you */
            /****************/

            PtdDollarWrite (scrptr->PtdChan, "\x1b[?1;0c", 7);
            continue;
         }

         if (ch == 'n')
         {
            /*****************/
            /* status report */
            /*****************/

            if (pn[0] == 0 || pn[0] == 5)
               PtdDollarWrite (scrptr->PtdChan, "\x1b[0n", 4);
            else
            if (pn[0] == 6)
            {
               char  buf [32];
               int len = sprintf (buf, "%c[%d;%dR", '\x1b', row+1, col+1);
               PtdDollarWrite (scrptr->PtdChan, buf, len);
            }
            else
               PtdDollarWrite (scrptr->PtdChan, "\x1b[3n", 4);
            continue;
         }

         if (ch == 's' || ch == 'u')
         {
            /**********/
            /* cursor */
            /**********/

            if (ch == 's')
            {
               /* save cursor */
               if (scrptr->CurSave < CSAVE_MAX)
               {
                  scrptr->CurSave++;
                  scrptr->RowSave[scrptr->CurSave] = row;
                  scrptr->ColSave[scrptr->CurSave] = col;
               }
               continue;
            }
            if (ch == 'u')
            {
               /* restore cursor */
               if (scrptr->CurSave >= 0)
               {
                  row = scrptr->RowSave[scrptr->CurSave];
                  col = scrptr->ColSave[scrptr->CurSave];
                  scrptr->CurSave--;
               }
               continue;
            }
            /* drop through */
         }

         if (*(USHORTPTR)cptr == '20')
         {
            /***********************/
            /* line feed / newline */
            /***********************/

            cptr += 2;
            if (*cptr == 'h' || *cptr == 'l')
            {
               cptr++;
               ScreenNextLine (scrptr, &row, &col);
               continue;
            }
         }

         if (ch == 'r')
         {
            /********************/
            /* scrolling region */
            /********************/

            /* ignore */
            if (inspect) ScreenCursor (scrptr, "SCROLL %dx%d",
                                       pn[0] ? pn[0] : pn[0]+1, pn[1]);
            continue;
         }

         if (*cptr == '?')
         {
            /************/
            /* VT modes */
            /************/

            /* ignore */
            if (inspect) ScreperKnown (scrptr, "VT", cptr+1, czptr);
            continue;
         }

         /* unknown CSI */
         if (inspect) ScreperUnknown (scrptr, "CSI?", cptr, czptr);

         continue;
      }

      if (*cptr == '\x1b' ||  /* 7 bit */
          *cptr == '\x9b')    /* 8 bit */
      {
         /*******/
         /* ESC */
         /*******/

         cptr++;
         ch = *cptr;
         cptr++;

         if (ch == '<')
         {
            /* enter ANSI mode */
            scrptr->AnsiMode = 1;
            continue;
         }

         if (ch == '\\')
         {
            /* 7 bit string terminator */
            continue;
         }

         if (ch == '=' || ch == '>')
         {
            /* keypad mode */
            continue;
         }

         if (ch == 'Z')
         {
            /* what are you */
            PtdDollarWrite (scrptr->PtdChan, "\x1b[?1;0c", 7);
            continue;
         }

         if (scrptr->AnsiMode)
         {
            /*************/
            /* ANSI mode */
            /*************/

            if (ch == 'D')
            {
               /* IND Index */
               ScreenIndex (scrptr, &col, &row);
               continue;
            }

            if (ch == 'E')
            {
               /* NEL Next Line (newline equiv) */
               ScreenNextLine (scrptr, &row, &col);
               continue;
            }

            if (ch == 'M')
            {
               /* TI Reverse Index */
               ScreenReverseIndex (scrptr, &row, &col);
               continue;
            }

            if (ch == '7')
            {
               /* save cursor */
               if (scrptr->CurSave < CSAVE_MAX)
               {
                  scrptr->CurSave++;
                  scrptr->RowSave[scrptr->CurSave] = row;
                  scrptr->ColSave[scrptr->CurSave] = col;
                  scrptr->SgrSave[scrptr->CurSave] =
                    screen[(rcnt*width)+ccnt] & SGR_MASK;
                  screen[(rcnt*width)+ccnt] &= ~SGR_MASK;
               }
               continue;
            }
            if (ch == '8')
            {
               /* restore cursor */
               if (scrptr->CurSave >= 0)
               {
                  row = scrptr->RowSave[scrptr->CurSave];
                  col = scrptr->ColSave[scrptr->CurSave];
                  screen[(rcnt*width)+ccnt] &= ~SGR_MASK;
                  screen[(rcnt*width)+ccnt] |=
                     scrptr->SgrSave[scrptr->CurSave];
                  scrptr->CurSave--;
               }
               continue;
            }
         }

         /*****************/
         /* non-printable */
         /*****************/

         if (ch == '\x9c')
         {
            /* 8 bit string terminator */
            continue;
         }

         if (ch == '6')
         {
            /* back index */
            continue;
         }

         if (ch == '9')
         {
            /* forward index */
            continue;
         }

         if (ch == 'D')
         {
            /* index */
            continue;
         }

         if (ch == 'c')
         {
            /* RIS reset */
            continue;
         }

         if (ch == 'm')
         {
            /* reverse Index */
            continue;
         }

         if (ch == '\\')
         {
            /* string terminator */
            continue;
         }

         if (ch == '(')
         {
            /* G0 character set */
            if (*cptr == 'A' || *cptr == 'B' ||
                *cptr == '0' || *cptr == '1' || *cptr == '2')
               scrptr->CharTableG0 = (uchar)*cptr << 16;
            cptr++;
            continue;
         }

         if (ch == ')')
         {
            /* G1 character set */
            if (*cptr == 'A' || *cptr == 'B' ||
                *cptr == '0' || *cptr == '1' || *cptr == '2')
               scrptr->CharTableG1 = (uchar)*cptr << 24;
            cptr++;
            continue;
         }

         /* unknown ESC - span intervening chars to the next escape */
         if (inspect) ScreperUnknown (scrptr, "ESC?", cptr, czptr);
         while (cptr < czptr)
         {
            if (*cptr == '\x1b' || *cptr == '\x9b') break;
            cptr++;
         }
         continue;
      }

      /***********/
      /* control */
      /***********/

      if (*cptr == '\r')
      {
         cptr++;
         col = 0;
         continue;
      }
      if (*cptr == '\b')
      {
         cptr++;
         if (col > 0) col--;
         continue;
      }
      if (*cptr == '\n' ||
          *cptr == '\f' ||
          *cptr == '\v')
      {
         cptr++;
         ScreenNextLine (scrptr, &row, &col);
         continue;
      }
      if (*cptr == '\x0e')
      {
         /* SO Invoke G1 character set */
         cptr++;
         if (inspect)
         {
            fprintf (scrptr->ScrOut, "<em class=\"_attr\">G1 %dx%d ", row, col);
            ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr);
            fputs ("</em>\n", scrptr->ScrOut);
         }
         scrptr->SgrG1 = SGR_G1;
         continue;
      }
      if (*cptr == '\x0f')
      {
         /* SI Invoke G0 character set */
         cptr++;
         if (inspect)
         {
            fprintf (scrptr->ScrOut, "<em class=\"_attr\">G0 %dx%d ", row, col);
            ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr);
            fputs ("</em>\n", scrptr->ScrOut);
         }
         scrptr->SgrG1 = 0;
         continue;
      }
      if (*cptr == '\v')
      {
         if (row+1 < page) row++;
         cptr++;
         continue;
      }
      if (*cptr == '\t')
      {
         for (tab = 8; tab > 0; tab--)
         {
//            screen[(row*width)+col] = ' ';
            if (col+1 < width) col++;
         }
         cptr++;
         continue;
      }

      /* ignore this control char */
      cptr++;

      if (row > page || col > width)
         ScreenReport (scrptr,
                       "BUGCHECK: %d 0x%02x terminal %dx%d screen %dx%d\n",
                       cptr - record, *cptr, width, page, col, row);
   }

   if (scrptr->InspectStream >= 4)
      ScreenDump (scrptr, "AFTER", __LINE__);

   /* return local copies to strcuture data */
   scrptr->ScreenRow = row;
   scrptr->ScreenCol = col;
}

/*****************************************************************************/
/*
Moves the cursor to the first position on the next line.  If the cursor is at
the bottom margin, the page scrolls up.
*/

static void ScreenNextLine (SCREPER_STRUCT *scrptr, int *rowptr, int *colptr)

{
   /*********/
   /* begin */
   /*********/

   ScreenIndex (scrptr, rowptr, colptr);
   *colptr = 0;
}

/*****************************************************************************/
/*
Moves the cursor down one line in the same column.  If the cursor is at the
bottom margin, the page scrolls up.
*/

static void ScreenIndex (SCREPER_STRUCT *scrptr, int *rowptr, int *colptr)

{
   int  ccnt, col, page, rcnt, row, width;
   ulong  *screen;

   /*********/
   /* begin */
   /*********/

   screen = scrptr->ScreenBuffer;
   page = scrptr->PtdPageLength;
   width = scrptr->PtdPageWidth;

   row = *rowptr;
   col = *colptr;

   if (row+1 < page)
      row++;
   else
   {
      /* move all lines up one then clear the bottom row */
      for (rcnt = 0; rcnt < page; rcnt++)
         for (ccnt = 0; ccnt < width; ccnt++)
            screen[(rcnt*width)+ccnt] = screen[((rcnt+1)*width)+ccnt]; 
      for (ccnt = 0; ccnt < width; ccnt++)
         screen[(row*width)+ccnt] = 0;
   }

   *rowptr = row;
   *colptr = col;
}

/*****************************************************************************/
/*
Moves the cursor up one line in the same column.  If the cursor is at the top
margin, the page scrolls down.
*/

static void ScreenReverseIndex (SCREPER_STRUCT *scrptr,
                                int *rowptr, int *colptr)

{
   int  ccnt, col, page, rcnt, row, width;
   ulong  *screen;

   /*********/
   /* begin */
   /*********/

   screen = scrptr->ScreenBuffer;
   page = scrptr->PtdPageLength;
   width = scrptr->PtdPageWidth;

   row = *rowptr;
   col = *colptr;

   if (row-1 >= 0)
      row--;
   else
   {
      /* move all lines down one then clear the top row */
      for (rcnt = 0; rcnt+1 < page; rcnt++)
         for (ccnt = 0; ccnt < width; ccnt++)
            screen[(rcnt*width)+ccnt] = screen[((rcnt+1)*width)+ccnt]; 
      for (ccnt = row = 0; ccnt < width; ccnt++)
         screen[(row*width)+ccnt] = 0;
   }

   *rowptr = row;
   *colptr = col;
}

/*****************************************************************************/
/*
Scanning from row 1, column 1, to row /n/, column /n/ output the HTML
equivalent of the virtual terminal.  Only output leading spaces if followed by
a non-space character.  Count empty lines and only output if followed by a
non-empty line (so absorbing trailing empty lines).  Each 64 bit
character/attributes is is turned into an HTML string using ScreenChar().
*/

static void ScreenOutput (SCREPER_STRUCT *scrptr)

{
   int  ccnt, col, inspect, nlcnt, page, rcnt, row, spacnt, width, valcnt;
   ulong  value;
   ulong  *screen;

   /*********/
   /* begin */
   /*********/

   /* if no more data to display since last time */
   if (!scrptr->SnapshotDataCount) return;
   scrptr->SnapshotDataCount = 0;

   /* get local copies of structure data */
   inspect = scrptr->InspectStream;
   screen = scrptr->ScreenBuffer;
   page = scrptr->PtdPageLength;
   width = scrptr->PtdPageWidth;
   row = scrptr->ScreenRow;
   col = scrptr->ScreenCol;

   if (inspect) fprintf (scrptr->ScrOut, "ScreenOutput %dx%d\n", width, page);

   if (inspect >= 4) ScreenDump (scrptr, "OUTPUT", __LINE__);

   if (scrptr->TimeStamp[0]) ScreenTimeStamp (scrptr);

   nlcnt = 0;
   for (rcnt = 0; rcnt < page; rcnt++)
   {
      spacnt = valcnt = 0;
      for (ccnt = 0; ccnt < width; ccnt++)
      {
         value = screen[(rcnt*width)+ccnt];
         /* count any leading space characters */
         if (((value & CHAR_MASK) == ' ') && !valcnt && spacnt >= 0)
         {
            spacnt++;
            value = 0;
         }
         if (value)
         {
            if (spacnt >= 0)
            {
               /* there have been leading spaces, go back and output them */
               valcnt = spacnt;
               ccnt -= spacnt + 1;
               spacnt = -1;
               continue;
            }
            if (nlcnt)
            {
               while (nlcnt--) fputs ("<br>", scrptr->ScrOut);
               nlcnt = 0;
            }
            valcnt++;
            fputs (ScreenChar (scrptr, value), scrptr->ScrOut);
         }
      }
      if (valcnt)
         fputs ("<br>", scrptr->ScrOut);
      else
         nlcnt++;
   }

   fputs (ScreenChar (scrptr, SGR_RESET), scrptr->ScrOut);
   scrptr->SgrBlink = scrptr->SgrBold = scrptr->SgrGrone =
      scrptr->SgrReverse = scrptr->SgrG1 = scrptr->SgrUnder = 0;

   fflush (scrptr->ScrOut);
}

/*****************************************************************************/
/*
Return a pointer to a string with any SGR attributes and character or HTML
character entity.
*/

static char* ScreenChar (SCREPER_STRUCT *scrptr, ulong value)

{
   ulong  ch;
   char  *cptr, *sptr;

   /*********/
   /* begin */
   /*********/

   sptr = scrptr->SgrBuffer;
   *sptr = '\0';

   if (value & SGR_RESET)
   {
      if (scrptr->SgrBold) strcat (sptr, "</em>");
      if (scrptr->SgrBlink) strcat (sptr, "</em>");
      if (scrptr->SgrUnder) strcat (sptr, "</em>");
      if (scrptr->SgrReverse) strcat (sptr, "</em>");
      scrptr->SgrBlink = scrptr->SgrBold =
         scrptr->SgrReverse = scrptr->SgrUnder = 0;
      return (sptr);
   }

   if (value & SGR_BOLD)
   {
      if (!scrptr->SgrBold) strcat (sptr, "<em class=\"_em1\">");
      scrptr->SgrBold = 1;
   }
   else
   {
      if (scrptr->SgrBold) strcat (sptr, "</em>");
      scrptr->SgrBold = 0;
   }

   if (value & SGR_UNDER)
   {
      if (!scrptr->SgrUnder) strcat (sptr, "<em class=\"_em4\">");
      scrptr->SgrUnder = 1;
   }
   else
   {
      if (scrptr->SgrUnder) strcat (sptr, "</em>");
      scrptr->SgrUnder = 0;
   }

   if (value & SGR_BLINK)
   {
      if (!scrptr->SgrBlink) strcat (sptr, "<em class=\"_em5\">");
      scrptr->SgrBlink = 1;
   }
   else
   {
      if (scrptr->SgrBlink) strcat (sptr, "</em>");
      scrptr->SgrBlink = 0;
   }

   if (value & SGR_REVER)
   {
      if (!scrptr->SgrReverse) strcat (sptr, "<em class=\"_em7\">");
      scrptr->SgrReverse = 1;
   }
   else
   {
      if (scrptr->SgrReverse) strcat (sptr, "</em>");
      scrptr->SgrReverse = 0;
   }

   ch = value & CHAR_MASK;

   if (ch > 127)
      cptr = CharTable_128_255[(uchar)ch-128];
   else
   if (ch < 32)
//      cptr = CharTable_0_31[(uchar)ch];
      cptr = "&dtrif;";
   else
   if (ch < 95)
      cptr = CharTable_32_94[(uchar)ch-32];
   else
   if (value & SGR_G1)
      cptr = CharTable_SG_95_127[(uchar)ch-95];
   else
      cptr = CharTable_95_127[(uchar)ch-95];

   strcat (sptr, cptr);

   return (sptr);
}

/*****************************************************************************/
/*
Reset the screen content.
*/

static void ScreenReset (SCREPER_STRUCT *scrptr)

{
   int  ccnt, page, rcnt, width;
   ulong  *screen;

   /*********/
   /* begin */
   /*********/

   page = scrptr->PtdPageLength;
   width = scrptr->PtdPageWidth;

   if (screen = scrptr->ScreenBuffer)
      for (rcnt = 0; rcnt < page; rcnt++)
         for (ccnt = 0; ccnt < width; ccnt++)
            screen[(rcnt*width)+ccnt] = 0;

   scrptr->ScreenRow = scrptr->ScreenCol =
      scrptr->EtxDetected = scrptr->StxDetected = 0;
}

/*****************************************************************************/
/*
Get a quoted string.  A character beginning ^ needs two hex digits following to
define the character.  For example, ^1a is a control-z, ^3e a hat, ^22 a
literal quotation character.  Return a pointer to the next character.
*/

static char* ConfigString (char *string, char *buf, int size, int *lenptr)

{
   char  ch;
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (size <= 0 || !buf || !string) return (string);
   if (buf) *buf = '\0';
   if (*string != '\"') return (string);
   
   zptr = (sptr = buf) + size-1;
   for (cptr = string+1; *cptr && *cptr != '\"' && sptr < zptr; cptr++)
   {
      if (*cptr == '^')
      {
         cptr++;
         if (*cptr && *(cptr+1))
         {
            ch = *(cptr+2);
            *sptr++ = (uchar)strtol (cptr, NULL, 16);
            *(cptr+2) = ch;
            cptr++;
         }
         else
            if (*cptr) cptr++;
      }
      else
         *sptr++ = *cptr;
   }
   *sptr = '\0';
   if (lenptr) *lenptr = sptr - buf;
   while (*cptr && *cptr != '\"') cptr++;
   if (*cptr) cptr++;
   return (cptr);
}

/*****************************************************************************/
/*
Look for the client-specified (-sentinal=..) output sentinal in the record. 
Return true or false. 
*/

static int ScreenSentinal (SCREPER_STRUCT *scrptr, char* record, int length)

{
   char  *cptr, *czptr, *sptr;

   /*********/
   /* begin */
   /*********/

   czptr = (cptr = record) + length;
   sptr = scrptr->SnapshotSentinal;
   length = scrptr->SnapshotSentinalLength;
   while (cptr + scrptr->SnapshotSentinalLength < czptr)
   {
      if (*cptr == *sptr)
         if (!(memcmp (cptr, sptr, length)))
            return (1);
      cptr++;
   }
   if (cptr < czptr) return (1);
   return (0);
}

/*****************************************************************************/
/*
This function is called every 100mS (i.e. ten times a second).  If these
specific number of these ticks has expired (i.e. the specified number of 100mS)
then output the scraped screen.  If not just return.  Each time screen data
arrives the ticker is reset and must tick down to zero before being output as
HTML.
*/

static void ScreenSnapshot (SCREPER_STRUCT *scrptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (!scrptr->SnapshotTickCount)
   {
      if (scrptr->InspectStream)
         fprintf (scrptr->ScrOut, "ScreenSnapshot() %d\n",
                  scrptr->SnapshotDataCount);
      /* output the page as HTML */
      if (scrptr->SnapshotDataCount) ScreenOutput (scrptr);
      scrptr->SnapshotDataCount = 0;
      scrptr->SnapshotTickCount = scrptr->SnapshotTicks;
   }
   else
      scrptr->SnapshotTickCount--;

   status = sys$setimr (0, &refreshTimer64, &ScreenSnapshot, scrptr, 0);
   if (!(status & 1)) exit (status);
}

/*****************************************************************************/
/*
*/

static void ScreenTimeStamp (SCREPER_STRUCT *scrptr)

{
   int  status;
   short  slen;
   char  *cptr, *sptr;
   char  ch;
   char  tbuf [255];
   time_t  utime;
   struct tm  *tmptr;
   $DESCRIPTOR (faoDsc, "");
   $DESCRIPTOR (tbufDsc, tbuf);

   /*********/
   /* begin */
   /*********/

   cptr = scrptr->TimeStamp;

   for (sptr = cptr; *cptr && *cptr != '!' && *cptr != '%'; cptr++);
   fprintf (scrptr->ScrOut, "%*.*s", cptr-sptr, cptr-sptr, sptr);

   if (*cptr == '!')
   {
      /* VMS time format */
      faoDsc.dsc$a_pointer = sptr = cptr;
      while (*cptr && *cptr != '<') cptr++;
      faoDsc.dsc$w_length = cptr - sptr;
      status = sys$fao (&faoDsc, &slen, &tbufDsc, 0);
      tbuf[slen] = '\0';
   }
   else
   if (*cptr == '%')
   {
      /* strftime() time format */
      time (&utime);
      tmptr = localtime (&utime);
      for (sptr = cptr; *cptr && *cptr != '<'; cptr++);
      ch = *cptr;
      slen = strftime (tbuf, sizeof(tbuf), sptr, tmptr);
      if (!slen) strcpy (tbuf, "strftime() ERROR");
      *cptr = ch;
   }
   fprintf (scrptr->ScrOut, "%*.*s", slen, slen, tbuf);

   for (sptr = cptr; *cptr; cptr++);
   fprintf (scrptr->ScrOut, "%*.*s", cptr-sptr, cptr-sptr, sptr);
}

/*****************************************************************************/
/*
If the /fmt/ parameter is NULL output and then empty the report buffer.
If not NULL then append a formatted string to the report buffer.
*/

static void ScreenReport (SCREPER_STRUCT *scrptr, char *fmt, ...)

{
#define BUF_SIZE 1024

   static char  buf [BUF_SIZE+128];
   static char  *bptr = buf;

   int  argcnt;
   va_list  argptr;

   /*********/
   /* begin */
   /*********/

   if (!fmt)
   {
      if (!buf[0]) return;
      fprintf (scrptr->ScrOut,
               "<span class=\"screperReport\">\n%s</span>",
               buf);
      *(bptr = buf) = '\0';
      return;
   }
   if (bptr - buf > BUF_SIZE) return;

   va_count (argcnt);
   va_start (argptr, fmt);

   if (vsprintf (bptr, fmt, argptr) < 0)
      sprintf (bptr, "ERROR: %s\n", fmt);
   while (*bptr) bptr++;
}

/*****************************************************************************/
/*
Returns a pointer to the HTML, et.al. required by the parent browser app to
display the virtual screen.
*/

char* ScreperScreen ()

{
   static char screen [] = 
"<pre class=\"screperScreen\" id=\"screperScreen\"></pre>\n\
<span class=\"screperUtility\" id=\"screperUtility\"></span>\
<button id=\"pauseButton\" type=\"submit\" onclick=\"pauseResume()\">\
" BUTTON_PAUSE "</button>\n\
<span id=\"screperCommand\" class=\"screperCommand\"></span>\n\
<span id=\"screperStatus\" class=\"screperStatus\"></span>\n";

   char  *sptr;

   /*********/
   /* begin */
   /*********/

   return (screen);
}

/*****************************************************************************/
/*
Return a pointer to an allocated string containing encoded HTML-forbidden
characters.  The string can be free()ed after use, as required.
*/

char* ScreperHtmlEncode (char *string)

{
   int  cnt = 0;
   char  *aptr, *cptr, *eptr, *sptr;

   /*********/
   /* begin */
   /*********/

   for (cptr = string; *cptr; cptr++)
      if (*cptr && (*cptr == '<' || *cptr == '>' || *cptr != '&' ||
                    *cptr == '\"' || *cptr != '\'')) cnt++;
   aptr = sptr = calloc (1, cptr - string + (cnt * 6));
   for (cptr = string; *cptr; cptr++)
   {
      if (*cptr != '<' && *cptr != '>' && *cptr != '&' &&
          *cptr != '\"' && *cptr != '\"')
         *sptr++ = *cptr;
      else
      {
         if (*cptr == '<')
            eptr = "&lt;";
         else
         if (*cptr == '>')
            eptr = "&gt;";
         else
         if (*cptr == '\"')
            eptr = "&quot;";
         else
         if (*cptr == '\'')
            eptr = "&apos;";
         else
            eptr = "&amp;";
         while (*eptr) *sptr++ = *eptr++;
      }
   }
   return (aptr);
}

/****************************************************************************/
/*
$FAO formatted print statement to OPCOM.  A fixed-size, internal buffer of 986
bytes maximum is used and the result output as an OPCOM message.  If the format
string begins with "S/" it is sent to SOFTWARE, otherwise CENTRAL.
*/

void ScreperOpcom
(
char *FormatString,
...
)
{
   static $DESCRIPTOR (FaoDsc, "");
   static $DESCRIPTOR (OpcomDsc, "");
   static $DESCRIPTOR (OpcomMsgDsc, "");

   int  status, argcnt, target;
   unsigned short  ShortLength;
   unsigned long  *vecptr;
   unsigned long  FaoVector [31+1];
   va_list  argptr;
   struct
   {
      unsigned long  TargetType;
      unsigned long  RequestId;
      char  MsgText [986+1];
   } OpcomMsg;

   /*********/
   /* begin */
   /*********/

   va_count (argcnt);

   if (argcnt > 31+1) exit (SS$_OVRMAXARG);

   vecptr = FaoVector;
   va_start (argptr, FormatString);
   for (argcnt -= 1; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);
   *vecptr = 0;

   if (*(USHORTPTR)FormatString == 'S/')
   {
      target = OPC$M_NM_SOFTWARE;
      FormatString += 2;
   }
   else
      target = OPC$M_NM_CENTRL;
   FaoDsc.dsc$a_pointer = FormatString;
   FaoDsc.dsc$w_length = strlen(FormatString);

   OpcomMsgDsc.dsc$a_pointer = (char*)&OpcomMsg.MsgText;
   OpcomMsgDsc.dsc$w_length = sizeof(OpcomMsg.MsgText)-1;

   status = sys$faol (&FaoDsc, &ShortLength, &OpcomMsgDsc, &FaoVector);
   if (!(status & 1)) exit (status);

   OpcomMsg.MsgText[ShortLength] = '\0';
   OpcomMsg.TargetType = OPC$_RQ_RQST + ((target & 0xffffff) << 8);
   OpcomMsg.RequestId = 0;

   OpcomDsc.dsc$a_pointer = (char*)&OpcomMsg;
   OpcomDsc.dsc$w_length = ShortLength + 8;

   status = sys$sndopr (&OpcomDsc, 0);
   if (!(status & 1)) exit (status);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreenDump (SCREPER_STRUCT *scrptr, char *fi, int li)

{
   int  ccnt, page, rcnt, width;
   ulong  value;
   ulong  *screen;

   /*********/
   /* begin */
   /*********/

   if (!scrptr->InspectStream) return;

   page =scrptr->PtdPageLength;
   width = scrptr->PtdPageWidth;
   screen = scrptr->ScreenBuffer;

   fflush (scrptr->ScrOut);
   fprintf (scrptr->ScrOut, "<em class=\"_dump\">%s:%d\n    ", fi, li);
   for (ccnt = 0; ccnt < width; ccnt++)
      fprintf (scrptr->ScrOut, "%02d         ",ccnt);
   fputs ("\n", scrptr->ScrOut);
   fflush (scrptr->ScrOut);

   for (rcnt = 0; rcnt < page; rcnt++)
   {
      fprintf (scrptr->ScrOut, "%02d  ",rcnt);
      for (ccnt = 0; ccnt < width; ccnt++)
      {
         value = screen[(rcnt*width)+ccnt];
         fprintf (scrptr->ScrOut, "%08.08x %s ",
                  value, ScreenChar (scrptr, value & ~SGR_MASK));
      }
      fputs ("\n", scrptr->ScrOut);
      fflush (scrptr->ScrOut);
   }
   fputs ("</em>\n", scrptr->ScrOut);
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreenCursor (SCREPER_STRUCT *scrptr, char *fmt, ...)

{
   int  argcnt;
   va_list  argptr;

   /*********/
   /* begin */
   /*********/

   va_count (argcnt);
   va_start (argptr, fmt);

   fprintf (scrptr->ScrOut, "<em class=\"_rxc\">");
   vfprintf (scrptr->ScrOut, fmt, argptr);
   fprintf (scrptr->ScrOut, "</em>\n");
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperData (SCREPER_STRUCT *scrptr, char *cptr, int length)

{
   char  *czptr;

   fputs ("<em class=\"_data\">", scrptr->ScrOut);
   czptr = cptr + length;
   while (cptr < czptr)
   {
      fprintf (scrptr->ScrOut, "%02.02x%c ",
               *(uchar*)cptr, *cptr >= 32 ? *cptr : '.');
      if (*cptr == '\n') fputs ("\n", scrptr->ScrOut);
      cptr++;
   }
   fputs ("</em>\n", scrptr->ScrOut);
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperDump (SCREPER_STRUCT *scrptr, char *cptr, int length)

{
   char  *czptr;

   czptr = cptr + length;
   fputs ("<span class=\"_chars\">", scrptr->ScrOut);
   while (cptr < czptr)
      fputs (ScreenChar (scrptr, *cptr++ & ~SGR_MASK), scrptr->ScrOut);
   fprintf (scrptr->ScrOut, "</span> %d\n", length);
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperKnown (SCREPER_STRUCT *scrptr, char* what,
                          char *cptr, char *czptr)

{
   fprintf (scrptr->ScrOut, "<em class=\"_kno\">%s ", what);
   ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr);
   fputs ("</em>\n", scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperUnknown (SCREPER_STRUCT *scrptr, char* what,
                            char *cptr, char *czptr)

{
   fprintf (scrptr->ScrOut, "<em class=\"_unk\">%s ", what);
   ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr);
   fputs ("</em>\n", scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperMore (SCREPER_STRUCT *scrptr, char *aptr, int count)

{
   if (count <= 0) count = 10;
   while (count)
   {
      fprintf (scrptr->ScrOut, "%02.02x%c ",
               *(uchar*)aptr, *aptr > 31 && *aptr < 127 ? *aptr : '.');
      aptr++;
      count--;
   }
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/