Mercurial > hg > octave-lyh
comparison src/gl-render.cc @ 9403:4af6e29449c1
[mq]: graphics_text_engine
author | Michael Goffioul <michael.goffioul@gmail.com> |
---|---|
date | Fri, 26 Jun 2009 21:12:09 +0100 |
parents | eb63fbe60fab |
children | 7cc35bc348cc |
comparison
equal
deleted
inserted
replaced
9402:cdfb9ad48080 | 9403:4af6e29449c1 |
---|---|
24 #include <config.h> | 24 #include <config.h> |
25 #endif | 25 #endif |
26 | 26 |
27 #if defined (HAVE_OPENGL) | 27 #if defined (HAVE_OPENGL) |
28 | 28 |
29 #include <iostream> | |
30 | |
29 #include <lo-mappers.h> | 31 #include <lo-mappers.h> |
30 #include "oct-locbuf.h" | 32 #include "oct-locbuf.h" |
31 #include "gl-render.h" | 33 #include "gl-render.h" |
34 #include "txt-eng.h" | |
35 #include "txt-eng-ft.h" | |
32 | 36 |
33 #define LIGHT_MODE GL_FRONT_AND_BACK | 37 #define LIGHT_MODE GL_FRONT_AND_BACK |
34 | 38 |
35 // Win32 API requires the CALLBACK attributes for | 39 // Win32 API requires the CALLBACK attributes for |
36 // GLU callback functions. Define it to empty on | 40 // GLU callback functions. Define it to empty on |
538 draw (dynamic_cast<const surface::properties&> (props)); | 542 draw (dynamic_cast<const surface::properties&> (props)); |
539 else if (go.isa ("patch")) | 543 else if (go.isa ("patch")) |
540 draw (dynamic_cast<const patch::properties&> (props)); | 544 draw (dynamic_cast<const patch::properties&> (props)); |
541 else if (go.isa ("hggroup")) | 545 else if (go.isa ("hggroup")) |
542 draw (dynamic_cast<const hggroup::properties&> (props)); | 546 draw (dynamic_cast<const hggroup::properties&> (props)); |
547 else if (go.isa ("text")) | |
548 draw (dynamic_cast<const text::properties&> (props)); | |
543 else | 549 else |
544 warning ("opengl_renderer: cannot render object of type `%s'", | 550 warning ("opengl_renderer: cannot render object of type `%s'", |
545 props.graphics_object_name ().c_str ()); | 551 props.graphics_object_name ().c_str ()); |
546 } | 552 } |
547 | 553 |
553 // Initialize OpenGL context | 559 // Initialize OpenGL context |
554 | 560 |
555 glEnable (GL_DEPTH_TEST); | 561 glEnable (GL_DEPTH_TEST); |
556 glDepthFunc (GL_LEQUAL); | 562 glDepthFunc (GL_LEQUAL); |
557 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | 563 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
564 glAlphaFunc (GL_GREATER, 0.0f); | |
558 glEnable (GL_NORMALIZE); | 565 glEnable (GL_NORMALIZE); |
559 | 566 |
560 if (props.is___enhanced__ ()) | 567 if (props.is___enhanced__ ()) |
561 { | 568 { |
562 glEnable (GL_BLEND); | 569 glEnable (GL_BLEND); |
872 } | 879 } |
873 | 880 |
874 std::string gridstyle = props.get_gridlinestyle (); | 881 std::string gridstyle = props.get_gridlinestyle (); |
875 std::string minorgridstyle = props.get_minorgridlinestyle (); | 882 std::string minorgridstyle = props.get_minorgridlinestyle (); |
876 | 883 |
884 set_font (props); | |
885 | |
877 // X grid | 886 // X grid |
878 | 887 |
879 if (visible && xstate != AXE_DEPTH_DIR) | 888 if (visible && xstate != AXE_DEPTH_DIR) |
880 { | 889 { |
881 bool do_xgrid = (props.is_xgrid () && (gridstyle != "none")); | 890 bool do_xgrid = (props.is_xgrid () && (gridstyle != "none")); |
882 bool do_xminorgrid = (props.is_xminorgrid () && (minorgridstyle != "none")); | 891 bool do_xminorgrid = (props.is_xminorgrid () && (minorgridstyle != "none")); |
883 bool do_xminortick = props.is_xminortick (); | 892 bool do_xminortick = props.is_xminortick (); |
884 Matrix xticks = xform.xscale (props.get_xtick ().matrix_value ()); | 893 Matrix xticks = xform.xscale (props.get_xtick ().matrix_value ()); |
885 // FIXME: use pre-computed minor ticks | 894 // FIXME: use pre-computed minor ticks |
886 Matrix xmticks; | 895 Matrix xmticks; |
887 // FIXME: use xticklabels property | 896 string_vector xticklabels = props.get_xticklabel ().all_strings (); |
888 string_vector xticklabels; | |
889 int wmax = 0, hmax = 0; | 897 int wmax = 0, hmax = 0; |
890 bool tick_along_z = xisinf (fy); | 898 bool tick_along_z = xisinf (fy); |
891 Matrix tickpos (xticks.numel (), 3); | 899 Matrix tickpos (xticks.numel (), 3); |
892 | 900 |
893 set_color (props.get_xcolor_rgb ()); | 901 set_color (props.get_xcolor_rgb ()); |
955 tickpos(i,2) = zPlane; | 963 tickpos(i,2) = zPlane; |
956 } | 964 } |
957 glEnd (); | 965 glEnd (); |
958 } | 966 } |
959 | 967 |
960 // FIXME: tick texts | 968 // tick texts |
969 if (xticklabels.numel () > 0) | |
970 { | |
971 int n = std::min (xticklabels.numel (), xticks.numel ()); | |
972 int halign = (xstate == AXE_HORZ_DIR ? 1 : (xySym ? 0 : 2)); | |
973 int valign = (xstate == AXE_VERT_DIR | |
974 ? 1 | |
975 : (zd*zv(2) <= 0 && !x2Dtop ? 2 : 0)); | |
976 | |
977 for (int i = 0; i < n; i++) | |
978 { | |
979 // FIXME: as tick text is transparent, shouldn't be | |
980 // drawn after axes object, for correct rendering? | |
981 Matrix b = draw_text (xticklabels(i), | |
982 tickpos(i,0), tickpos(i,1), tickpos(i,2), | |
983 halign, valign); | |
984 | |
985 wmax = std::max (wmax, static_cast<int> (b(2))); | |
986 hmax = std::max (hmax, static_cast<int> (b(3))); | |
987 } | |
988 } | |
961 | 989 |
962 // minor grid lines | 990 // minor grid lines |
963 if (do_xminorgrid) | 991 if (do_xminorgrid) |
964 { | 992 { |
965 set_linestyle (minorgridstyle, true); | 993 set_linestyle (minorgridstyle, true); |
1023 } | 1051 } |
1024 } | 1052 } |
1025 | 1053 |
1026 text::properties& xlabel_props = | 1054 text::properties& xlabel_props = |
1027 reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_xlabel ()).get_properties ()); | 1055 reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_xlabel ()).get_properties ()); |
1056 | |
1057 xlabel_props.set_visible ("on"); | |
1028 | 1058 |
1029 // FIXME: auto-positioning should be disabled if the | 1059 // FIXME: auto-positioning should be disabled if the |
1030 // label has been positioned manually | 1060 // label has been positioned manually |
1031 if (! xlabel_props.get_string ().empty ()) | 1061 if (! xlabel_props.get_string ().empty ()) |
1032 { | 1062 { |
1058 p = xform.untransform (p(0), p(1), p(2), true); | 1088 p = xform.untransform (p(0), p(1), p(2), true); |
1059 xlabel_props.set_position (p.extract_n (0, 3).transpose ()); | 1089 xlabel_props.set_position (p.extract_n (0, 3).transpose ()); |
1060 xlabel_props.set_rotation (angle); | 1090 xlabel_props.set_rotation (angle); |
1061 } | 1091 } |
1062 } | 1092 } |
1093 else | |
1094 { | |
1095 gh_manager::get_object (props.get_xlabel ()).set ("visible", "off"); | |
1096 } | |
1063 | 1097 |
1064 // Y grid | 1098 // Y grid |
1065 | 1099 |
1066 if (ystate != AXE_DEPTH_DIR && visible) | 1100 if (ystate != AXE_DEPTH_DIR && visible) |
1067 { | 1101 { |
1069 bool do_yminorgrid = (props.is_yminorgrid () && (minorgridstyle != "none")); | 1103 bool do_yminorgrid = (props.is_yminorgrid () && (minorgridstyle != "none")); |
1070 bool do_yminortick = props.is_yminortick (); | 1104 bool do_yminortick = props.is_yminortick (); |
1071 Matrix yticks = xform.yscale (props.get_ytick ().matrix_value ()); | 1105 Matrix yticks = xform.yscale (props.get_ytick ().matrix_value ()); |
1072 // FIXME: use pre-computed minor ticks | 1106 // FIXME: use pre-computed minor ticks |
1073 Matrix ymticks; | 1107 Matrix ymticks; |
1074 // FIXME: use yticklabels property | 1108 string_vector yticklabels = props.get_yticklabel ().all_strings (); |
1075 string_vector yticklabels; | |
1076 int wmax = 0, hmax = 0; | 1109 int wmax = 0, hmax = 0; |
1077 bool tick_along_z = xisinf (fx); | 1110 bool tick_along_z = xisinf (fx); |
1078 Matrix tickpos (yticks.numel (), 3); | 1111 Matrix tickpos (yticks.numel (), 3); |
1079 | 1112 |
1080 set_color (props.get_ycolor_rgb ()); | 1113 set_color (props.get_ycolor_rgb ()); |
1142 tickpos(i,2) = zPlane; | 1175 tickpos(i,2) = zPlane; |
1143 } | 1176 } |
1144 glEnd (); | 1177 glEnd (); |
1145 } | 1178 } |
1146 | 1179 |
1147 // FIXME: tick texts | 1180 // tick texts |
1181 if (yticklabels.numel () > 0) | |
1182 { | |
1183 int n = std::min (yticklabels.numel (), yticks.numel ()); | |
1184 int halign = (ystate == AXE_HORZ_DIR ? 1 : (!xySym || y2Dright ? 0 : 2)); | |
1185 int valign = (ystate == AXE_VERT_DIR ? 1 : (zd*zv(2) <= 0 ? 2 : 0)); | |
1186 | |
1187 for (int i = 0; i < n; i++) | |
1188 { | |
1189 // FIXME: as tick text is transparent, shouldn't be | |
1190 // drawn after axes object, for correct rendering? | |
1191 Matrix b = draw_text (yticklabels(i), | |
1192 tickpos(i,0), tickpos(i,1), tickpos(i,2), | |
1193 halign, valign); | |
1194 | |
1195 wmax = std::max (wmax, static_cast<int> (b(2))); | |
1196 hmax = std::max (hmax, static_cast<int> (b(3))); | |
1197 } | |
1198 } | |
1148 | 1199 |
1149 // minor grid lines | 1200 // minor grid lines |
1150 if (do_yminorgrid) | 1201 if (do_yminorgrid) |
1151 { | 1202 { |
1152 set_linestyle (minorgridstyle, true); | 1203 set_linestyle (minorgridstyle, true); |
1210 } | 1261 } |
1211 } | 1262 } |
1212 | 1263 |
1213 text::properties& ylabel_props = | 1264 text::properties& ylabel_props = |
1214 reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_ylabel ()).get_properties ()); | 1265 reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_ylabel ()).get_properties ()); |
1266 | |
1267 ylabel_props.set_visible ("on"); | |
1215 | 1268 |
1216 // FIXME: auto-positioning should be disabled if the | 1269 // FIXME: auto-positioning should be disabled if the |
1217 // label has been positioned manually | 1270 // label has been positioned manually |
1218 if (! ylabel_props.get_string ().empty ()) | 1271 if (! ylabel_props.get_string ().empty ()) |
1219 { | 1272 { |
1245 p = xform.untransform(p(0), p(1), p(2), true); | 1298 p = xform.untransform(p(0), p(1), p(2), true); |
1246 ylabel_props.set_position (p.extract_n (0, 3).transpose ()); | 1299 ylabel_props.set_position (p.extract_n (0, 3).transpose ()); |
1247 ylabel_props.set_rotation (angle); | 1300 ylabel_props.set_rotation (angle); |
1248 } | 1301 } |
1249 } | 1302 } |
1303 else | |
1304 { | |
1305 gh_manager::get_object (props.get_ylabel ()).set ("visible", "off"); | |
1306 } | |
1250 | 1307 |
1251 // Z Grid | 1308 // Z Grid |
1252 | 1309 |
1253 if (zstate != AXE_DEPTH_DIR && visible) | 1310 if (zstate != AXE_DEPTH_DIR && visible) |
1254 { | 1311 { |
1256 bool do_zminorgrid = (props.is_zminorgrid () && (minorgridstyle != "none")); | 1313 bool do_zminorgrid = (props.is_zminorgrid () && (minorgridstyle != "none")); |
1257 bool do_zminortick = props.is_zminortick (); | 1314 bool do_zminortick = props.is_zminortick (); |
1258 Matrix zticks = xform.zscale (props.get_ztick ().matrix_value ()); | 1315 Matrix zticks = xform.zscale (props.get_ztick ().matrix_value ()); |
1259 // FIXME: use pre-computed minor ticks | 1316 // FIXME: use pre-computed minor ticks |
1260 Matrix zmticks; | 1317 Matrix zmticks; |
1261 // FIXME: use zticklabels property | 1318 string_vector zticklabels = props.get_zticklabel ().all_strings (); |
1262 string_vector zticklabels; | |
1263 int wmax = 0, hmax = 0; | 1319 int wmax = 0, hmax = 0; |
1264 Matrix tickpos (zticks.numel (), 3); | 1320 Matrix tickpos (zticks.numel (), 3); |
1265 | 1321 |
1266 set_color (props.get_zcolor_rgb ()); | 1322 set_color (props.get_zcolor_rgb ()); |
1267 | 1323 |
1362 glEnd (); | 1418 glEnd (); |
1363 } | 1419 } |
1364 } | 1420 } |
1365 | 1421 |
1366 // FIXME: tick texts | 1422 // FIXME: tick texts |
1423 if (zticklabels.numel () > 0) | |
1424 { | |
1425 int n = std::min (zticklabels.numel (), zticks.numel ()); | |
1426 int halign = 2; | |
1427 int valign = (zstate == AXE_VERT_DIR ? 1 : (zd*zv(2) < 0 ? 3 : 2)); | |
1428 | |
1429 for (int i = 0; i < n; i++) | |
1430 { | |
1431 // FIXME: as tick text is transparent, shouldn't be | |
1432 // drawn after axes object, for correct rendering? | |
1433 Matrix b = draw_text (zticklabels(i), | |
1434 tickpos(i,0), tickpos(i,1), tickpos(i,2), | |
1435 halign, valign); | |
1436 | |
1437 wmax = std::max (wmax, static_cast<int> (b(2))); | |
1438 hmax = std::max (hmax, static_cast<int> (b(3))); | |
1439 } | |
1440 } | |
1367 | 1441 |
1368 // minor grid lines | 1442 // minor grid lines |
1369 if (do_zminorgrid) | 1443 if (do_zminorgrid) |
1370 { | 1444 { |
1371 set_linestyle (minorgridstyle, true); | 1445 set_linestyle (minorgridstyle, true); |
1454 } | 1528 } |
1455 } | 1529 } |
1456 | 1530 |
1457 text::properties& zlabel_props = | 1531 text::properties& zlabel_props = |
1458 reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_zlabel ()).get_properties ()); | 1532 reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_zlabel ()).get_properties ()); |
1533 | |
1534 zlabel_props.set_visible ("on"); | |
1459 | 1535 |
1460 // FIXME: auto-positioning should be disabled if the | 1536 // FIXME: auto-positioning should be disabled if the |
1461 // label has been positioned manually | 1537 // label has been positioned manually |
1462 if (! zlabel_props.get_string ().empty ()) | 1538 if (! zlabel_props.get_string ().empty ()) |
1463 { | 1539 { |
1510 p = xform.untransform (p(0), p(1), p(2), true); | 1586 p = xform.untransform (p(0), p(1), p(2), true); |
1511 zlabel_props.set_position (p.extract_n (0, 3).transpose ()); | 1587 zlabel_props.set_position (p.extract_n (0, 3).transpose ()); |
1512 zlabel_props.set_rotation (angle); | 1588 zlabel_props.set_rotation (angle); |
1513 } | 1589 } |
1514 } | 1590 } |
1591 else | |
1592 { | |
1593 gh_manager::get_object (props.get_zlabel ()).set ("visible", "off"); | |
1594 } | |
1515 | 1595 |
1516 set_linestyle ("-"); | 1596 set_linestyle ("-"); |
1517 | 1597 |
1518 // Title | 1598 // Title |
1519 | 1599 |
1535 // Children | 1615 // Children |
1536 | 1616 |
1537 if (antialias == GL_TRUE) | 1617 if (antialias == GL_TRUE) |
1538 glEnable (GL_LINE_SMOOTH); | 1618 glEnable (GL_LINE_SMOOTH); |
1539 | 1619 |
1540 Matrix children = props.get_children (); | 1620 Matrix children = props.get_all_children (); |
1541 std::list<graphics_object> obj_list; | 1621 std::list<graphics_object> obj_list; |
1542 std::list<graphics_object>::iterator it; | 1622 std::list<graphics_object>::iterator it; |
1543 | 1623 |
1544 // 1st pass: draw light objects | 1624 // 1st pass: draw light objects |
1545 | 1625 |
1562 while (it != obj_list.end ()) | 1642 while (it != obj_list.end ()) |
1563 { | 1643 { |
1564 graphics_object go = (*it); | 1644 graphics_object go = (*it); |
1565 | 1645 |
1566 // FIXME: check whether object has "units" property and it is set to "data" | 1646 // FIXME: check whether object has "units" property and it is set to "data" |
1567 if (! go.isa ("text") || go.get ("units").string_value () == "data") | 1647 if (! go.isa ("text") || go.get (caseless_str ("units")).string_value () == "data") |
1568 { | 1648 { |
1569 set_clipping (go.get_properties ().is_clipping ()); | 1649 set_clipping (go.get_properties ().is_clipping ()); |
1570 draw (go); | 1650 draw (go); |
1571 | 1651 |
1572 it = obj_list.erase (it); | 1652 it = obj_list.erase (it); |
2570 { | 2650 { |
2571 draw (props.get_children ()); | 2651 draw (props.get_children ()); |
2572 } | 2652 } |
2573 | 2653 |
2574 void | 2654 void |
2655 opengl_renderer::draw (const text::properties& props) | |
2656 { | |
2657 if (props.get_string ().empty ()) | |
2658 return; | |
2659 | |
2660 set_font (props); | |
2661 set_color (props.get_color_rgb ()); | |
2662 | |
2663 // FIXME: take "units" into account | |
2664 Matrix pos = props.get_position ().matrix_value (); | |
2665 int halign = 0, valign = 0; | |
2666 | |
2667 if (props.horizontalalignment_is ("center")) | |
2668 halign = 1; | |
2669 else if (props.horizontalalignment_is ("right")) | |
2670 halign = 2; | |
2671 | |
2672 if (props.verticalalignment_is ("top")) | |
2673 valign = 2; | |
2674 else if (props.verticalalignment_is ("baseline")) | |
2675 valign = 3; | |
2676 else if (props.verticalalignment_is ("middle")) | |
2677 valign = 1; | |
2678 | |
2679 // FIXME: handle margin and surrounding box | |
2680 | |
2681 draw_text (props.get_string (), | |
2682 pos(0), pos(1), pos(2), | |
2683 halign, valign, props.get_rotation ()); | |
2684 } | |
2685 | |
2686 void | |
2575 opengl_renderer::set_viewport (int w, int h) | 2687 opengl_renderer::set_viewport (int w, int h) |
2576 { | 2688 { |
2577 glViewport (0, 0, w, h); | 2689 glViewport (0, 0, w, h); |
2578 } | 2690 } |
2579 | 2691 |
2580 void | 2692 void |
2581 opengl_renderer::set_color (const Matrix& c) | 2693 opengl_renderer::set_color (const Matrix& c) |
2582 { | 2694 { |
2583 glColor3dv (c.data ()); | 2695 glColor3dv (c.data ()); |
2696 #if HAVE_FREETYPE | |
2697 text_renderer.set_color (c); | |
2698 #endif | |
2699 } | |
2700 | |
2701 void | |
2702 opengl_renderer::set_font (const base_properties& props) | |
2703 { | |
2704 #if HAVE_FREETYPE | |
2705 text_renderer.set_font (props); | |
2706 #endif | |
2584 } | 2707 } |
2585 | 2708 |
2586 void | 2709 void |
2587 opengl_renderer::set_polygon_offset (bool on, double offset) | 2710 opengl_renderer::set_polygon_offset (bool on, double offset) |
2588 { | 2711 { |
2867 glEndList (); | 2990 glEndList (); |
2868 | 2991 |
2869 return ID; | 2992 return ID; |
2870 } | 2993 } |
2871 | 2994 |
2995 Matrix | |
2996 opengl_renderer::draw_text (const std::string& txt, | |
2997 double x, double y, double z, | |
2998 int halign, int valign, double rotation) | |
2999 { | |
3000 #if HAVE_FREETYPE | |
3001 if (txt.empty ()) | |
3002 return Matrix (1, 4, 0.0); | |
3003 | |
3004 // FIXME: clip "rotation" between 0 and 360 | |
3005 | |
3006 int rot_mode = ft_render::ROTATION_0; | |
3007 | |
3008 if (rotation == 90.0) | |
3009 rot_mode = ft_render::ROTATION_90; | |
3010 else if (rotation == 180.0) | |
3011 rot_mode = ft_render::ROTATION_180; | |
3012 else if (rotation == 270.0) | |
3013 rot_mode = ft_render::ROTATION_270; | |
3014 | |
3015 text_element *elt = text_parser_none ().parse (txt); | |
3016 Matrix bbox; | |
3017 uint8NDArray pixels = text_renderer.render (elt, bbox, rot_mode); | |
3018 int x0 = 0, y0 = 0; | |
3019 int w = bbox(2), h = bbox(3); | |
3020 | |
3021 switch (halign) | |
3022 { | |
3023 default: break; | |
3024 case 1: x0 = -bbox(2)/2; break; | |
3025 case 2: x0 = -bbox(2); break; | |
3026 } | |
3027 switch (valign) | |
3028 { | |
3029 default: break; | |
3030 case 1: y0 = -bbox(3)/2; break; | |
3031 case 2: y0 = -bbox(3); break; | |
3032 case 3: y0 = bbox(1); break; | |
3033 } | |
3034 | |
3035 switch (rot_mode) | |
3036 { | |
3037 case ft_render::ROTATION_90: | |
3038 std::swap (x0, y0); | |
3039 std::swap (w, h); | |
3040 x0 -= bbox(3); | |
3041 break; | |
3042 case ft_render::ROTATION_180: | |
3043 x0 -= bbox(2); | |
3044 y0 -= bbox(3); | |
3045 break; | |
3046 case ft_render::ROTATION_270: | |
3047 std::swap (x0, y0); | |
3048 std::swap (w, h); | |
3049 y0 -= bbox(2); | |
3050 break; | |
3051 } | |
3052 | |
3053 bool blend = glIsEnabled (GL_BLEND); | |
3054 | |
3055 glEnable (GL_BLEND); | |
3056 glEnable (GL_ALPHA_TEST); | |
3057 glRasterPos3d (x, y, z); | |
3058 glBitmap(0, 0, 0, 0, x0, y0, 0); | |
3059 glDrawPixels (w, h, | |
3060 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data ()); | |
3061 glDisable (GL_ALPHA_TEST); | |
3062 if (! blend) | |
3063 glDisable (GL_BLEND); | |
3064 | |
3065 delete elt; | |
3066 | |
3067 return bbox; | |
3068 #else | |
3069 ::error ("draw_text: cannot render text, Freetype library not available"); | |
3070 return Matrix (1, 4, 0.0); | |
3071 #endif | |
3072 } | |
3073 | |
2872 #endif | 3074 #endif |
2873 | 3075 |
2874 /* | 3076 /* |
2875 ;;; Local Variables: *** | 3077 ;;; Local Variables: *** |
2876 ;;; mode: C++ *** | 3078 ;;; mode: C++ *** |