changeset 1547:bd981744b232 measure-maps

measure info (unfinished)
author hertzhaft
date Mon, 24 Oct 2016 08:55:29 +0200
parents fb4cb912dd51
children d94b54747eb2 67a282bffab0
files .hgignore webapp/src/main/webapp/jquery/jquery.digilib.measure.js webapp/src/main/webapp/jquery/measure-calculations.txt
diffstat 3 files changed, 1502 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Oct 24 08:49:38 2016 +0200
+++ b/.hgignore	Mon Oct 24 08:55:29 2016 +0200
@@ -129,4 +129,9 @@
 syntax: regexp
 ^webapp/src/main/webapp/jquery/jquery\.digilib-basic\.js$
 syntax: regexp
-^webapp/src/main/webapp/jquery/jquery\.digilib-basic\.min\.css$
\ No newline at end of file
+^webapp/src/main/webapp/jquery/jquery\.digilib-basic\.min\.css$
+
+~$
+\.orig$
+\.min\.css$
+\.marks$
\ No newline at end of file
--- a/webapp/src/main/webapp/jquery/jquery.digilib.measure.js	Mon Oct 24 08:49:38 2016 +0200
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.measure.js	Mon Oct 24 08:55:29 2016 +0200
@@ -127,7 +127,7 @@
             units  : [{
 				name : "foot",
 				factor : "0.304797",
-				subunits : "12"
+				subunits : 12
 				},
 				{
 				name : "inch",
@@ -136,17 +136,17 @@
 				{
 				name : "yard",
 				factor : "0.914391",
-				subunits : "3"
+				subunits : 3
 				},
 				{
 				name : "pole",
 				factor : "5.0291505",
-				subunits : "11"
+				subunits : 11
 				},
 				{
 				name : "chain",
 				factor : "20.116602",
-				subunits : "4"
+				subunits : 4
 				},
 				{
 				name : "furlong",
@@ -155,7 +155,7 @@
 				{
 				name : "mile",
 				factor : "1609.32816",
-				subunits : "8"
+				subunits : 8
 				}]
             },
             {
@@ -164,15 +164,17 @@
             units  : [{
 				name : "palmo d'architetto (Rom)",
 				factor : "0.223425",
-				subunits : "12"
+				subunits : 12
 				},
 				{
 				name : "braccio (Florenz)",
-				factor : "0.5836"
+				factor : "0.5836",
+				subunits : 20
 				},
 				{
 				name : "braccio (Mailand)",
-				factor : "0.5949"
+				factor : "0.5949",
+				subunits : 12
 				},
 				{
 				name : "canna d'architetto (Rom)",
@@ -191,8 +193,36 @@
 				factor : "2.3344"
 				},
 				{
+				name : "canna agrimensoria (Florenz)",
+				factor : "2.9181",
+				subunits : 5
+				},
+        {
 				name : "canna (Neapel)",
-				factor : "2.0961"
+				factor : "2.10936",
+				subunits : 8
+				},
+        {
+				name : "catena (Neapel)",
+				factor : "18.4569",
+				},
+        {
+				name : "pertica (Neapel)",
+				factor : "2.6367",
+				},
+        {
+				name : "palmo (Neapel)",
+				factor : "0.26367",
+				subunits : 12
+				},
+        {
+				name : "passo itinerario (Neapel)",
+				factor : "1.84569",
+				subunits : 8
+				},
+        {
+				name : "oncia (Neapel)",
+				factor : "0.021972",
 				},
 				{
 				name : "miglio (Lombardei)",
@@ -227,6 +257,11 @@
 				factor : "0.2918"
 				},
 				{
+				name : "passetto (Florenz)",
+				factor : "1.1673",
+				subunits : 40
+				},
+				{
 				name : "piede (Brescia)",
 				factor : "0.471"
 				},
@@ -603,6 +638,35 @@
 				factor : "0.01"
 				},
 				{
+				name : "rubbio (Roma, Lazio)",
+				factor : "18484,38"
+				},
+				{
+				name : "pezza (Roma, vigne)",
+				factor : "2640,63",
+				},
+				{
+				name : "braccio quadrato (Toscana)",
+				factor : "0.3406",
+				subunits : 400
+				},
+				{
+				name : "quadrato (Toscana)",
+				factor : "3406,19"
+				},
+				{
+				name : "stioro (Toscana)",
+				factor : "525,01"
+				},
+				{
+				name : "staio (Toscana)",
+				factor : "1703,10",
+				},
+				{
+				name : "tornatura (Toscana)",
+				factor : "2080,44"
+				},
+				{
 				name : "Ar",
 				factor : "100"
 				},
@@ -907,40 +971,97 @@
         _debug_shape('onRenderShape', shape);
         };
 
-    // get last vertex before current one
-    var getLastVertex = function(shape, vertex) {
+    // get the vertex before the given one
+    var getPrecedingVertex = function(shape, vertex) {
         var props = shape.properties;
         var vtx = vertex == null ? props.vtx : vertex;
         return (vtx === 0) ? props.screenpos.length-1 : vtx-1;
         };
 
-    // calculate the distance of a shape vertex to the last (in rectified digilib coords)
-    var getVertexDistance = function(data, shape, vtx) {
-        if (vtx == null) {
-            vtx = shape.properties.vtx; }
+    // calculate the angle between three points (rectified)
+    var getRectifiedAngleY = function (data, shape, tip, v1) {
+        var coords = shape.geometry.coordinates;
+        var line = geom.line(coords[tip], coords[v1]);
+        var dist = fn.getDistance(data,
+            geom.position(coords[v1]),
+            geom.position(coords[v2])
+            );
+        return dist.rectified;
+        };
+
+    // calculate the angle between three points (rectified)
+    var getRectifiedAngle = function (data, shape, tip, v1, v2) {
         var coords = shape.geometry.coordinates;
-        var last = getLastVertex(shape, vtx);
-        // safely assume that screenpos and coords have equal length?
-        var dist = fn.getDistance(data, geom.position(coords[last]), geom.position(coords[vtx]));
+        var line1 = geom.line(coords[tip], coords[v1]);
+        var line2 = geom.line(coords[tip], coords[v2]);
+        var dist = fn.getDistance(data,
+            geom.position(coords[v1]),
+            geom.position(coords[v2])
+            );
+        return dist.rectified;
+        };
+
+    // calculate the distance between two digilib coords (rectified) ### (needed?)
+    var getRectifiedDistance = function (data, shape, vtx1, vtx2) {
+        var coords = shape.geometry.coordinates;
+        var dist = fn.getDistance(data,
+            geom.position(coords[vtx1]),
+            geom.position(coords[vtx2])
+            );
         return dist.rectified;
         };
 
-    // calculate distance from current to last vertex (in rectified digilib coords)
-    var rectifiedDist = function(data, shape) {
+    // calculate the distance between two screen points
+    var getScreenDistance = function (shape, v1, v2) {
+        var p = shape.properties.screenpos;
+        return p[v1].distance(p[v2]);
+        };
+
+    // convert a length into both units 
+    var convertLength = function (data, dist) {
+        var factor = data.measureFactor;
+        var unit1 = unitFactor(data, 1);
+        var unit2 = unitFactor(data, 2);
+        var ratio = unit1 / unit2;
+        var len1 = scaleValue(dist, factor);
+        var len2 = scaleValue(len1, ratio);
+        var name1 = data.measureUnit1;
+        var name2 = data.measureUnit2;
+        return [name1, len1, name2, len2];
+        };
+
+    // convert a distance between 2 coordinates (indexed) into both units
+    // ### do we need this?
+    var convertDistance = function (data, shape, dist) {
+        return convertLength(data, getRectifiedDistance(data, shape, v1, v2));
+        };
+
+    // calculate the distance from one shape vertex to the one before it (in rectified digilib coords)
+    var getPrecedingVertexDistance = function(data, shape, vtx) {
+        // if (vtx == null) {
+        //    vtx = shape.properties.vtx;
+        //    }
+        var preVtx = getPrecedingVertex(shape, vtx || shape.properties.vtx);
+        // [safely assume that the 'screenpos' and 'coords' arrays have equal length?]
+        return getScreenDistance(shape, preVtx, vtx);
+        };
+
+    // calculate distance from current to preceding vertex (in rectified digilib coords)
+    var getRectifiedLength = function(data, shape) {
         var coords = shape.geometry.coordinates;
         var total = 0;
         if (shape.geometry.type === 'LineString') { // sum up distances
             for (vtx = 1; vtx < coords.length; vtx++) {
-                total += getVertexDistance(data, shape, vtx);
+                total += getPrecedingVertexDistance(data, shape, vtx);
                 }
         } else {
-            total = getVertexDistance(data, shape);
+            total = getPrecedingVertexDistance(data, shape);
             }
         return total;
         };
 
-    // calculate the area of a polygon (rectified digilib coords)
-    var rectifiedArea = function(data, shape) {
+    // calculate the area of a polygon (using digilib coords)
+    var getRectifiedArea = function(data, shape) {
         var ar = fn.getImgAspectRatio(data);
         var rectifyPoint = function (c) {
             return geom.position(ar * c[0], c[1]);
@@ -966,73 +1087,76 @@
         var val = parseFloat(widgets.value1.val());
         var fac = val / data.lastMeasuredValue;
         data.measureFactor = fac;
-        updateUnits(data);
+        updateUnits(data);    // convert a distance between 2 points into both units 
     };
 
+    // scale area
+    var scaleArea = function(val, factor) {
+        return val * factor * factor;
+        };
+
+    // scale length
+    var scaleValue = function(val, factor) {
+        return val * factor;
+        };
+
+    // UGLY: info whether to show area (not length) for this shape type
+    var showArea = function(data, type) {
+        return data.settings.shapeInfo[type].display === 'area';
+        };
+
     // convert measured value to second unit and display
-    var updateMeasures = function(data, val, type) {
+    var updateConversion = function(data, val, type, showArea) {
         var widgets = data.measureWidgets;
-        var unit1 = parseFloat(widgets.unit1.val());
-        var unit2 = parseFloat(widgets.unit2.val());
+        var unit1 = unitFactor(data, 1);
+        var unit2 = unitFactor(data, 2);
         var ratio = unit1 / unit2;
-        var result = scaleValue(data, type, val, ratio);
+        var result = showArea
+          ? scaleArea(val, ratio)
+          : scaleValue(val, ratio);
         widgets.shape.val(type);
         widgets.value1.val(fn.cropFloatStr(val));
         widgets.value2.text(fn.cropFloatStr(result));
         };
 
-    // scale
-    var scaleValue = function(data, type, val, factor) {
-        var scaleArea = data.settings.shapeInfo[type].display === 'area';
-        var result = scaleArea
-            ? val * factor * factor
-            : val * factor;
-        return result;
-        };
-
-    // convert pixel values to other units
-    var pixelToUnit = function(data, type, px) {
-        var ratio = data.measureFactor;
-        var result = scaleValue(data, type, px, ratio);
-        return result;
-        };
-
-    // rectify pixel values according to digilib aspect ratio
-    var rectifiedPixel = function(data, shape) {
-        var type = shape.geometry.type;
-        var display = data.settings.shapeInfo[type].display;
-        var px = (display === 'area')
-            ? rectifiedArea(data, shape)
-            : rectifiedDist(data, shape);
-        return px;
-        };
-
     // update last measured pixel values, display as converted to new units
     var updateUnits = function(data) {
         var type = getActiveShapeType(data);
+        var factor = data.measureFactor;
         var px = data.lastMeasuredValue;
-        var result = pixelToUnit(data, type, px);
-        updateMeasures(data, result, type);
+        var area = showArea(data, type);
+        var val = area
+          ? scaleArea(px, factor)
+          : scaleValue(px, factor);
+        updateConversion(data, val, type, area);
         };
 
     // display info for shape
     var updateInfo = function(data, shape) {
-        data.lastMeasuredValue = rectifiedPixel(data, shape);
+        data.lastMeasuredValue = showArea(data, shape.geometry.type)
+          ? getRectifiedArea(data, shape) // ### needed? (use screenpos)
+          : getRectifiedLength(data, shape);
         setActiveShapeType(data, shape);
         updateUnits(data);
         };
 
+    // get unit value from widget
+    var unitFactor = function(data, index) {
+        return parseFloat(data.measureWidgets['unit'+index].val());
+        };
+
     // info data for shape
     var getInfoHTML = function(data, shape) {
         var s = data.settings;
+        var factor = data.measureFactor;
         var type = shape.geometry.type;
-        var display = s.shapeInfo[type].display;
+        var scaled = showArea(data, type)
+          ? scaleArea(getRectifiedArea(data, shape), factor)
+          : scaleValue(getRectifiedLength(data, shape), factor);
+        var len = fn.cropFloat(scaled, 2);
         var name = s.shapeInfo[type].name;
-        var px = (display === 'area')
-            ? rectifiedArea(data, shape)
-            : rectifiedDist(data, shape);
-        var len = fn.cropFloat(pixelToUnit(data, type, px), 2);
-        var unit = data.measureWidgets.unit1.find('option:selected').text();
+        var display = s.shapeInfo[type].display;
+        var unit = data.measureUnit1;
         var html = '<div class="head">'+name+'</div><div><em>'+display+'</em>: '+len+' '+unit+'</div>';
         return html;
         };
@@ -1095,15 +1219,15 @@
             var vtx = props.vtx;
             if (screenpos == null || vtx == null) {
                 return; }
-            var lastPos = screenpos[getLastVertex(shape)];
-            var thisPos = screenpos[vtx]; // mouse position
-            var fac = data.measureFactor;
-            shape.geometry.coordinates[vtx] = data.imgTrafo.invtransform(thisPos);
-            var unitDist = getVertexDistance(data, shape) * fac;
+            var lastPos = screenpos[getPrecedingVertex(shape)];
+            shape.geometry.coordinates[vtx] = data.imgTrafo.invtransform(thisPos); // ### needed? just work with screenpos?
+            var factor = data.measureFactor;
+            var screenDist = getPrecedingVertexDistance(data, shape);
+            var unitDist = scaleValue(screenDist, factor);
             var roundDist = Math.round(unitDist); // round to the nearest integer
-            var newPos = (roundDist === 0)
-                ? thisPos
-                : lastPos.scale(thisPos, roundDist/unitDist); // calculate snap position
+            if (roundDist === 0) {
+                return; }
+            var newPos = lastPos.scale(thisPos, roundDist/unitDist); // calculate snap position
             screenpos[vtx].moveTo(newPos);
             };
         };
@@ -1145,6 +1269,12 @@
         setCalibrationInputState(data);
         };
 
+    // set the current unit (from unit select widget)
+    var changeUnit = function(data, name) {
+        data[name] = $(widget).find('option:selected').text();
+        updateUnits(data);
+        };
+
     // update Line Style classes (overwrite CSS)
     var updateLineStyles = function(data) {
         var s = data.settings;
@@ -1210,7 +1340,7 @@
             $svg.removeAttr("display"); }
         else {
             $svg.attr("display", "none"); }
-    };
+    };                                                                    
 
     // initial position of measure bar (bottom left of browser window)
     var setScreenPosition = function(data, $bar) {
@@ -1347,7 +1477,30 @@
                 },
                 'svg' : function (shape) {
                     var $s = factory['LineString'].svg(shape);
+                    var p = shape.properties.screenpos;
+                    var len1 = p[1].distance(p[0]);
+                    var len2 = p[1].distance(p[2]);
+                    var ang = null;
+                    var angy = null;
+                    props.measures = {
+                      len1: len1,
+                      len2: len2,
+                      rat1: len1 / len2,
+                      rat2: len2 / len1,
+                      ang:  ang,
+                      angy: angy
+                    };
                     return $s;
+                },
+                'info' : function (data, shape) {
+                    return [
+                        { len1: 'leg a'},
+                        { len2: 'leg b'},
+                        { rat1: 'ratio a/b'},
+                        { rat2: 'ratio b/a'},
+                        { ang:  'contained angle'},
+                        { angy: 'angle y-axis - leg a'},
+                        ];
                 }
             };
         factory['Intercolumnium'] = {
@@ -1398,7 +1551,8 @@
                             var p2 = p3.copy().add(line1.vector());
                             p[2] = p2.mid(p3); // handle position
                             shape.geometry.coordinates[2] = trafo.invtransform(p[2]).toArray();
-                            props.pos = [p2, p3]; // save other points
+                            props.p2 = p2;
+                            props.p3 = p3; // save other points
                             }
                         this.attr({points: [p[0], p[1], p2, p3].join(" ")});
                         };
@@ -1433,9 +1587,9 @@
                         place.call($s); // place the framing rectangle (polygon)
                         if (p.length > 3) { // p[3] is the mouse pointer
                             var side0 = geom.line(p[0], p[1]) // the sides
-                            var side1 = geom.line(p[1], props.pos[0]);
-                            var side2 = geom.line(props.pos[0], props.pos[1]);
-                            var side3 = geom.line(props.pos[1], p[0]);
+                            var side1 = geom.line(p[1], props.p2); // use 'Rect' points
+                            var side2 = geom.line(props.p2, props.p3);
+                            var side3 = geom.line(props.p3, p[0]);
                             var mid0 = side0.mid(); // the midpoints of the sides
                             var mid1 = side1.mid();
                             var mid2 = side2.mid();
@@ -1490,6 +1644,9 @@
                             p[3] = handle;
                             shape.geometry.coordinates[3] = trafo.invtransform(handle).toArray();
                             props.measures = { rad1: rad1, rad2: rad2, axis1: axis1.length(), axis2: axis2.length() }; // use for info
+                            // area: (r² * phi) + (R² * (pi - phi)) - ((axis1 - 2r) * dist(m3, mid(axis1)))
+                            // length of the periphery parts: q1 = r * phi, q2 = R * (pi - phi) 
+                            // circumference: 2 * (q1 + q2);
                             }
                         };
                     shape.$interactor = $arc;
@@ -1559,32 +1716,32 @@
     var setupMeasureBar = function(data) {
         console.debug('measure: setupMeasureBar');
         var widgets = {
-            names : [
-                'info',
-                'startb', 'shape',
-                'type',
-                'value1', 'unit1', 'eq',
-                'value2', 'unit2',
-                'shapecolor', 'guidecolor', 'constrcolor', 'selectedcolor', 'handlecolor',
-                'move'
-                ],
-            info :       $('<img id="dl-measure-info" src="img/info.png" title="display info window for shapes"></img>'),
-            startb :     $('<button id="dl-measure-startb" title="click to draw a measuring shape on top of the image">M</button>'),
-            shape :      $('<select id="dl-measure-shape" title="select a shape to use for measuring" />'),
-			eq :         $('<span class="dl-measure-label">=</span>'),
-			type :       $('<span id="dl-measure-shapetype" class="dl-measure-label">length</span>'),
-			fac :        $('<span id="dl-measure-factor" class="dl-measure-number" />'),
-			value1 :     $('<input id="dl-measure-value1" class="dl-measure-input" title="last measured value - click to change the value" value="0.0" />'),
-			value2 :     $('<span id="dl-measure-value2" class="dl-measure-label" title="last measured value, converted to the secondary unit" value="0.0"/>'),
-			unit1 :      $('<select id="dl-measure-unit1" title="current measuring unit - click to change" />'),
-			unit2 :      $('<select id="dl-measure-unit2" title="secondary measuring unit - click to change" />'),
-			angle :      $('<span id="dl-measure-angle" class="dl-measure-number" title="last measured angle" />'),
-            shapecolor : $('<span id="dl-measure-shapecolor" class="dl-measure-color" title="select line color for shapes"></span>'),
-            guidecolor : $('<span id="dl-measure-guidecolor" class="dl-measure-color" title="select guide line color for shapes"></span>'),
-            constrcolor :$('<span id="dl-measure-constrcolor" class="dl-measure-color" title="select construction line color for shapes"></span>'),
-            selectedcolor :$('<span id="dl-measure-selectedcolor" class="dl-measure-color" title="select line color for selected shapes"></span>'),
-            handlecolor :$('<span id="dl-measure-handlecolor" class="dl-measure-color" title="select color for shape handles"></span>'),
-            move :       $('<img id="dl-measure-move" src="img/move.png" title="move measuring bar around the screen"></img>')
+        names: [
+          'info',
+          'startb', 'shape',
+          'type',
+          'value1', 'unit1', 'eq',
+          'value2', 'unit2',
+          'shapecolor', 'guidecolor', 'constrcolor', 'selectedcolor', 'handlecolor',
+          'move'
+          ],
+        info:         $('<img id="dl-measure-info" src="img/info.png" title="display info window for shapes"></img>'),
+        startb:       $('<button id="dl-measure-startb" title="click to draw a measuring shape on top of the image">M</button>'),
+        shape:        $('<select id="dl-measure-shape" title="select a shape to use for measuring" />'),
+        eq:           $('<span class="dl-measure-label">=</span>'),
+        type:         $('<span id="dl-measure-shapetype" class="dl-measure-label">length</span>'),
+        fac:          $('<span id="dl-measure-factor" class="dl-measure-number" />'),
+        value1:       $('<input id="dl-measure-value1" class="dl-measure-input" title="last measured value - click to change the value" value="0.0" />'),
+        value2:       $('<span id="dl-measure-value2" class="dl-measure-label" title="last measured value, converted to the secondary unit" value="0.0"/>'),
+        unit1:        $('<select name="measureUnit1" id="dl-measure-unit1" title="current measuring unit - click to change" />'),
+        unit2:        $('<select name="measureUnit2" id="dl-measure-unit2" title="secondary measuring unit - click to change" />'),
+        angle:        $('<span id="dl-measure-angle" class="dl-measure-number" title="last measured angle" />'),
+        shapecolor:   $('<span id="dl-measure-shapecolor" class="dl-measure-color" title="select line color for shapes"></span>'),
+        guidecolor:   $('<span id="dl-measure-guidecolor" class="dl-measure-color" title="select guide line color for shapes"></span>'),
+        constrcolor:  $('<span id="dl-measure-constrcolor" class="dl-measure-color" title="select construction line color for shapes"></span>'),
+        selectedcolor:$('<span id="dl-measure-selectedcolor" class="dl-measure-color" title="select line color for selected shapes"></span>'),
+        handlecolor:  $('<span id="dl-measure-handlecolor" class="dl-measure-color" title="select color for shape handles"></span>'),
+        move:         $('<img id="dl-measure-move" src="img/move.png" title="move measuring bar around the screen"></img>')
 		    };
         var $measureBar = $('<div id="dl-measure-toolbar" />');
         var widgetName = widgets.names;
@@ -1624,8 +1781,8 @@
             });
         widgets.shape.on('change.measure',  function(evt) { changeActiveShapeType(data) });
         widgets.value1.on('change.measure', function(evt) { changeFactor(data) });
-        widgets.unit1.on('change.measure',  function(evt) { updateUnits(data) });
-        widgets.unit2.on('change.measure',  function(evt) { updateUnits(data) });
+        widgets.unit1.on('change.measure',  function(evt) { changeUnit(data, this.name) });
+        widgets.unit2.on('change.measure',  function(evt) { changeUnit(data, this.name) });
         widgets.unit1.attr('tabindex', -1);
         widgets.unit2.attr('tabindex', -1);
         widgets.value1.attr('tabindex', -1);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/src/main/webapp/jquery/measure-calculations.txt	Mon Oct 24 08:55:29 2016 +0200
@@ -0,0 +1,1234 @@
+// archi1calc, arch1draw und info-Berechnungen
+
+// Switch units (click on the "=" label ?)
+// pure CSS icons
+// calibrate drawing (osize?)
+
+// Umrechnung, Bruchzahl-Darstellung
+
+procedure TFMain.CalcResult;
+ label Output;
+ var
+   StrZaehler, StrNenner, StrVorkomma : string;
+   VorKomma, NachKomma,
+   Result,              {Ergebnis mit gerundeten werten}
+   Zaehler : TZahl;
+   Nenner  : integer;
+   NoBruch : boolean;
+
+ begin {TFMain.CalcResult}
+   NoBruch   := True;
+   RawResult := Calc(InputLine.ValueFloat, UnitVon, UnitZu);   {Dreisatzberechnung}
+   Result    := RawResult;
+   if NOT Opt.BruchOut then goto Output;    {Output erfolgt nicht in Bruchform}
+   with UnitZu do if subunit > 0 		    {rundet auf ganze Untereinheiten}
+     then Result := round(RawResult * subunit)/subunit;
+   if GanzZahl(Result) then goto Output;     {Abweichung < Toleranzgrenze?}
+   VorKomma  := Int(Result);
+   Nachkomma := Frac(Result);
+   Zaehler := Nachkomma;               {keine ganze Zahl: berechne Bruchzahl}
+   Nenner  := 1;
+   repeat
+     inc(Nenner);
+     Zaehler := Zaehler + Nachkomma;
+     if Nenner > Opt.MaxNenner then goto Output;   {Nenner ist zu gro�}
+     until GanzZahl(Zaehler);
+   NoBruch := False;
+   Result := (Vorkomma + Zaehler/Nenner);
+   str(Zaehler:0:0, StrZaehler);
+   str(Nenner:0, StrNenner);
+   ResultStr := StrZaehler + '/' + StrNenner;
+   if Vorkomma <> 0 then begin                     {gemischter Bruch}
+     str(Vorkomma:0:0, StrVorkomma);
+     ResultStr := StrVorkomma + Space + ResultStr;
+     end;
+Output:
+   if NoBruch then begin
+     str(Result:0:Opt.Stellen, ResultStr);
+     ZeroesOff(ResultStr);
+     end;
+   DiffResult  := RD(Result - RawResult, succ(opt.stellen));
+   if DiffResult = 0
+     then RoundFrom := 0
+     else RoundFrom := Calc(Result, UnitZu, UnitVon);
+ end;
+
+
+// algorithm for fractions
+
+def dec2frac(num, epsilon, max_iter=20):
+    d = [0, 1] + ([0] * max_iter)
+    z = num
+    n = 1
+    t = 1
+
+    while num and t < max_iter and abs(n/d[t] - num) > epsilon:
+        t += 1
+        z = 1/(z - int(z))
+        d[t] = d[t-1] * int(z) + d[t-2]
+        # int(x + 0.5) is equivalent to rounding x.
+        n = int(num * d[t] + 0.5)
+
+    return n, d[t]
+
+//
+    take decimal x
+    count the number of digits after the decimal point; call this n
+    create a fraction (10^n * x) / 10^n
+    remove common factors from the numerator and denominator.
+
+so if you have 0.44, you would count 2 places are the decimal point - n = 2, and then write
+
+    (0.44 * 10^2) / 10^2
+    = 44 / 100
+    factorising (removing common factor of 4) gives 11 / 25 
+
+// Maßstab (how to calibrate map?)
+procedure TFMassStab.BCalcClick(Sender: TObject);
+var PalmiCm, ResultCm : Extended;
+begin
+  ResultCm := 0;
+  try
+    PalmiCm  := Pixel2.ValueFloat / Pixel1.ValueFloat * Len1.Valuefloat;
+    ResultCm := PalmiCm / Len2.Valuefloat * MLen.Valuefloat;
+  finally
+    MResult.Valuefloat := ResultCm;
+    end;
+end;
+
+// Tabellen
+{---------------}
+ procedure TFMain.FillShows;
+{---------------}
+ const
+   Sign : array[TRechenArt] of char = ('+', 'x', ':');
+   tabs : array[0..7] of integer = (40, 50, 60, 70, 80, 90, 100, 110);
+   { Damit "TListbox" TABs akzeptiert, mu� die undokumentierte
+     property "TabWidth" auf einen Wert > 0 gesetzt sein!}
+ var
+   T : TFList;
+   NN, RR, DD  : string; {Input, Result, Delta}
+   N, N1, S, R, D : TZahl;
+   art  : TRechenArt;
+   count: longint;
+ begin
+   N := InputLine.ValueFloat;
+   for art := add to divi do begin
+     case art of
+       add   : T := FListAdd;
+       multi : T := FListMul;
+       divi  : T := FListDiv;
+       end;
+     if T.Visible then with opt do begin
+       str(N:0:Stellen, NN); ZeroesOff(NN);
+// falls Add von - nach + anzeigen soll
+       N1 := N {- (zeilen div 2) * delta[add]};
+       T.ListBox1.Clear;
+       T.ListBox1.Items.Add(NN + ' ' + UnitVon.Name);
+       for count := 1 to Zeilen do begin
+         D := count * delta[art];
+         case art of
+           add:   S := N1 + D;
+           multi: S := N * D;
+           divi:  S := N / D;
+           end;
+         R := calc(S, UnitVon, UnitZu);
+	 str(R:0:Stellen, RR);
+         PadZeroes(RR);
+// falls Add von - nach + anzeigen soll
+{	 if art = add
+            then str(N1-N+D:8:Stellen, DD)
+            else}
+         str(D:8:Stellen, DD);
+         PadZeroes(DD);
+	 T.Listbox1.items.add(sign[art] + DD
+          + TAB + ' => ' + RR + TAB + UnitZu.name);
+         end; {for count}
+// SendMessage(T.Listbox1.handle, lb_settabstops, 4, longint(@tabs));
+       end; {with options}
+     end; {for art}
+   end;
+
+// Dreisatz
+procedure TFDreisatz.Calc(Sender: TObject);
+var Ratio: Extended;
+    PercStr: String;
+begin
+  if checknull(EdDr2)
+    then Ratio := EdDr1.ValueFloat / EdDr2.ValueFloat
+    else Ratio := 1;
+  if XZaehler then
+    // if checknull(EdDr2) AND checknull(EdDr3)
+    EdDrX.Valuefloat := Ratio * EdDr3.ValueFloat
+  else begin
+    if checknull(EdDr1)
+      then EdDrX.Valuefloat := EdDr3.ValueFloat / Ratio;
+      end;
+  if XPercent then begin
+    Ratio   := Ratio * 100.0;
+    PercStr := ' %';
+    end
+  else PercStr := '';
+  LbRatio.Caption := FloatToStrF(Ratio, ffFixed, 18, 2) + PercStr;
+end;
+
+// arch1draw
+function Gradient(A, B: TEPoint): TXY;
+begin
+  with result do begin
+    X := A.X - B.X;
+    Y := A.Y - B.Y;
+    end;
+end;
+
+function Gra (A, B: TEPoint): extended;
+begin
+  with Gradient(A, B) do result := Y / X;
+end;
+
+
+function Abstand(DX, DY: extended): extended;
+begin
+  Result := sqrt( sqr(DX) + sqr(DY)); // Pythagoras
+end;
+
+function Winkel(Dx, Dy: extended): extended;
+// berechnet aus einem Steigungsverh�ltnis den winkel in pi (pi = 180�)
+begin
+  if Dx = 0 then // Vertikale Linie
+    if Dy <= 0 then result := 0 else result := pi
+  else begin
+    result := arctan(Dy/Dx) + PI_2;
+    if Dx < 0 then result := result + pi;
+    end;
+end;
+
+function WinkelGrad(Dx, Dy: extended): extended;
+begin
+  result := Winkel(Dx, Dy) * 180 / pi;
+end;
+
+function ColumnProp(Ratio: Extended): string;
+// erzeugt die Vtriuvianischen Interkolumnien
+begin
+  Ratio := 1 / Ratio;
+  if Ratio < 1.75 then result := 'pyknostylos (1:1�)'
+  else if Ratio < 2.125 then result := 'systylos (1:2)'
+  else if Ratio < 2.625 then result := 'eustylos (1:2�)'
+  else if Ratio < 3.25 then result := 'diastylos (1:3)'
+  else result := 'ar�ostylos (1:3� - 1:4)';
+  if (abs(Ratio - 1.5)  < Gw)
+  OR (abs(Ratio - 2)    < Gw)
+  OR (abs(Ratio - 2.25) < Gw)
+  OR (abs(Ratio - 3)    < Gw)
+  OR (abs(Ratio - 3.5)  < Gw)
+  OR (abs(Ratio - 4)    < Gw)
+    then result := '= ' + result
+    else result := '~ ' + result;
+end;
+
+function SimpleProp(Ratio: Extended): string;
+// schreibt das Verh�ltnis z. B. als 1:2.89 oder 4.1:1
+begin
+  if Ratio < 1
+    then result := '1:' + FS(1/Ratio)
+    else result := FS(Ratio) +  ':1';
+end;
+
+function Proportion(Ratio: extended): string;
+//  versucht, ganzzahlige Verh�ltnisse zu ermitteln
+var
+   Ganz: Boolean;
+   Nenner  : longint;
+   Zaehler : extended;
+begin
+   Zaehler := 0;
+   Nenner  := 0;
+   repeat
+     inc(Nenner);
+     Zaehler := Zaehler + Ratio;
+     Ganz := GanzZahl(Zaehler);
+     until Ganz OR (Nenner > 20);
+   if Ganz
+     then result := FStr(Zaehler,0)
+        + ':' +  InttoStr(Nenner)
+     else result := SimpleProp(Ratio);
+     // result := FS(ratio);
+end;
+
+function TEpoint.DistanceTo(P: TEPoint): extended;
+begin
+  Result := Abstand(X - P.X, Y - P.Y); // Pythagoras
+end;
+
+function TEpoint.Clockwise(P1, P2: TEPoint): boolean;
+begin
+  Result := abs(Winkel(X - P1.X, Y - P1.Y) - Winkel(X - P2.X, Y - P2.Y)) < PI;
+end;
+
+procedure TEPoint.TurnTo(A, B, TurnAngle: Extended);
+// A/B = Drehpunkt, Self = Punkt, TurnAngle = Drehwinkel
+var D, NewAngle: Extended;
+begin
+  D := Abstand(A - X, B - Y); // Entfernung zum Drehpunkt
+  NewAngle := Winkel(A - X, B - Y) + TurnAngle;  // absoluter Winkel plus Drehwinkel
+  MoveTo(A - D * sin(NewAngle), B + D * cos(NewAngle)); // neue Position
+end;
+
+procedure TEpoint.ScaleTo(A, B, Ratio: Extended);
+begin
+  MoveTo((X-A) * Ratio + A, (Y-B) * Ratio + B);
+end;
+
+procedure TEPoint.MoveTo(A, B: Extended);
+begin
+  X := A;
+  Y := B;
+end;
+
+procedure TEpoint.ForceThales(P: TEPoint; var A, B: Longint);
+// keep point A/B on a circle around the line Self -> P
+var
+  Mx, My, radius, dist, scale: Extended;
+begin
+  Mx := Mid(X, P.X);
+  My := Mid(Y, P.Y);
+  radius := Abstand(X - Mx, Y - My);
+  dist := Abstand(A - Mx, B - My);
+  try
+     scale := radius / dist;
+     A := round (Mx + (A - Mx) * scale);
+     B := round (My + (B - My) * scale);
+  except
+     A := round(X);
+     B := round(Y);
+     end;
+end;
+
+procedure TEpoint.ForceDirection(P: TEPoint; var A, B: Longint);
+// keep point A/B on the axis Self -> P
+var dx, dy, ddx, ddy, NewX, NewY: Extended;
+begin
+  ddy := P.Y - Y; // distance
+  ddx := P.X - X;
+  dx  := A - X;
+  dy  := B - Y;
+  if abs(ddy) < abs(ddx) then try // keep X value of clickpoint
+    B := round(Y + ddy * dx / ddx);
+    except end
+  else try // keep Y calue of clickpoint
+    A := round(X + ddx * dy / ddy);
+    except end;
+end;
+
+procedure TEpoint.ForceDistDirection(P: TEPoint; var A, B: Longint);
+// keep point A/B on the axis Self -> P, preserving the distance to Self
+var T: TXY;
+begin
+  T := DistDirection(P, Abstand(A - X, B - Y));
+  A := round(T.X); B := round(T.Y);
+end;
+
+function TEpoint.DistDirection(P: TEPoint; Dist: Extended): TXY;
+// return point A/B on the axis Self -> P, with the distance dist
+var ratio, dx, dy: Extended;
+begin
+  dx := P.X - X; dy := P.Y - Y;
+  ratio := dist / Abstand(dx, dy);
+  result.X := X + dx * ratio;
+  result.Y := Y + dy * ratio;
+end;
+// ---------- TDrawObject --------------
+
+function TDrawObject.Nearest(A, B: longint): longint;
+var i : longint; Mindist, D: extended;
+begin
+  MinDist := 1000.0; // just a big value;
+  for i := 0 to Points.count-1 do
+    with Pt(i) do begin
+      D := Abstand(A - X, B - Y);
+      if Mindist > D then begin
+         MinDist := D;
+         result := i;
+         end;
+      end;
+end;
+
+function TDrawObject.MinDist(A, B: longint): extended;
+var i : longint; D: extended;
+begin
+  result := 1000.0; // just a big value;
+  for i := 0 to Points.count-1 do
+    with Pt(i) do begin
+      D := Abstand(A - X, B - Y);
+      if result > D then result := D;
+      end;
+end;
+
+function TDrawObject.Snap(var A, B: longint): boolean;
+var i : longint; D: extended;
+begin
+  result := false;
+  for i := 0 to Points.count-1 do
+    with Pt(i) do begin
+      D := Abstand(A - X, B - Y);
+      result := D < CritDistance;
+      if result then begin
+        A := X;
+        B := Y;
+        break;
+        end;
+      end;
+end;
+
+procedure TDrawObject.Move(A, B: extended);
+var i : longint;
+begin
+  with Points do
+   for i := 0 to Count -1 do
+    with TEPoint(items[i]) do MoveTo(X+A, Y+B);
+end;
+
+procedure TDrawObject.Turn(A, B, Angle: Extended);
+var i : longint;
+begin
+  with Points do
+   for i := 0 to Count -1 do
+    TEPoint(items[i]).TurnTo(A, B, Angle);
+end;
+
+procedure TDrawObject.Scale(A, B, Ratio: Extended);
+var i : longint;
+begin
+  with Points do
+   for i := 0 to Count -1 do
+    TEPoint(items[i]).ScaleTo(A, B, Ratio);
+end;
+// ---------- TLine --------------
+
+procedure TLine.Click(X, Y: longint);
+begin
+  inherited;
+  if State = 1 then begin
+    InfoProc('Endpunkt festlegen');
+    NewPoint(X, Y) // add a second point
+    end
+  else begin
+    Drag(X, Y);
+    Finish;
+    end;
+  Draw;
+end;
+
+function  TLine.Properties: String;
+begin
+  result := inherited Properties
+    + 'L�nge :' + #9
+    +  FS(Dist(0,1) * Faktor)+ CRLF
+    + 'Winkel zur Y-Achse: '
+    +  FS(WinkelGrad(EPt(0).X - EPt(1).X, EPt(0).Y - EPt(1).Y)) + '�';
+end;
+
+// ---------- TArc --------------
+
+function  TArc.Properties: String;
+var Angle, Radius: Extended;
+begin
+  Radius := Dist(0,1);
+  Angle :=
+    WinkelGrad(EPt(0).X - EPt(1).X, EPt(0).Y - EPt(1).Y)-
+    WinkelGrad(EPt(0).X - EPt(2).X, EPt(0).Y - EPt(2).Y);
+  if Angle < 0 then Angle := Angle + 360.0;
+  result := inherited Properties
+    + 'Radius:' + #9#9
+    +  FS(Radius*Faktor)+ CRLF
+    + 'Sehnenl�nge:' + #9
+    +  FS(Dist(1,2)*Faktor) + CRLF
+    + 'Bogenl�nge:' + #9
+    +  FS(Angle*Radius*Faktor*pi/180.0) + CRLF
+    + '�ffnungswinkel:' + #9
+    +  FS(Angle) + '�';
+end;
+
+procedure TArc.Click(X, Y: longint);
+begin
+  inherited;
+  case state of
+  1: begin
+       InfoProc('Startwinkel angeben');
+       LeadLine := TLine.Create;
+       Leadline.Click(X, Y);
+       Leadline.LOpt.LineColor := Opt.MarkColor;
+       NewPoint(X, Y);
+       end;
+  2: begin
+       InfoProc('Endwinkel festlegen');
+       SetPoint(1, X, Y);
+       NewPoint(X, Y);
+       Draw;
+       end;
+  3: begin
+       LeadLine.Draw;
+       LeadLine.Free;
+       C.brush.style := bsClear; // transparente Intervalle
+       Finish;
+       Draw;
+       end;
+  end; {case}
+end;
+
+procedure TArc.Finish;
+begin
+  with Ept(0) do Ept(2).ScaleTo(X, Y, Dist(0,1)/Dist(0,2));
+  inherited;
+end;
+
+
+procedure TCircle.Finish;
+var M: TEPoint;
+begin
+  // create all 4 quadrant points
+  if LOpt.CircMid then begin
+    M := EPt(0);
+    with Ept(1) do NewPoint(2*M.X-X, 2*M.Y-Y)
+    end
+  else begin
+    with MidXY(EPt(0), EPt(1)) do NewPoint(X, Y);
+    Points.Exchange(0,2);
+    M := Ept(0);
+    LOpt.CircMid := True;
+    end;
+  with Lot(M, Ept(1),1)  do NewPoint(X, Y);
+  with Lot(M, Ept(1),-1) do NewPoint(X, Y);
+  inherited;
+end;
+
+procedure TCircle.Drag(X, Y: longint);
+begin
+  if NOT dragging then exit;
+  Draw;
+  SetPoint(1, X, Y);
+  Draw;
+end;
+
+procedure TCircle.Draw;
+var A, B, M: TPoint;
+    D: Longint;
+begin
+  inherited;
+  if LOpt.CircMid then begin
+    M := Pt(0);
+    D := round(Dist(0,1));
+    end
+  else begin
+    M := MidP(EPt(0), EPt(1));
+    D := round(Dist(0,1)/2);
+    end;
+  with M, C do Ellipse(X-D, Y-D, X+D, Y+D);
+  WriteResults;
+end;
+
+function  TCircle.Properties: String;
+begin
+  result := inherited Properties
+    + 'Radius : '
+    +  FS(Dist(0,1) * Faktor);
+end;
+
+procedure TCircle.WriteResults;
+begin
+  if dragging OR not LineOpt.ShowTags then exit;
+  inherited;
+  WriteDist(1,2); // Durchmesser
+end;
+
+// ---------- TReck --------------
+
+procedure TReck.Finish;
+var i: integer;
+begin
+  for i := 1 to 3 do
+    with MidXY(Ept(0), Ept(i)) do NewPoint(X,Y);
+  for i := 1 to 2 do
+    with MidXY(Ept(i), Ept(i+1)) do NewPoint(X,Y);
+  inherited;
+end;
+
+procedure TReck.Draw; //
+begin
+  inherited;
+  with C do begin
+    Polygon([Pt(0), Pt(1), Pt(2), Pt(3)]);
+    WriteResults;
+    if LOpt.Diags AND NOT dragging then DrawDiag(0);
+    end;
+end;
+
+procedure TReck.Click(X,Y: longint);
+begin
+  inherited;
+  case state of
+  1: begin
+        LeadLine := TLine.Create;
+        Leadline.Click(X, Y);
+        Leadline.LOpt.LineColor := Opt.MarkColor;
+        NewPoint(X,Y); // add a second point;
+        if LOpt.DiagsFirst
+           then InfoProc('Diagonale festlegen')
+           else InfoProc('Seitenl�nge festlegen');
+        end;
+  2: begin
+        if LOpt.DiagsFirst
+           then InfoProc('Rechteckseite festlegen')
+           else InfoProc('Rechteckh�he festlegen');
+        SetPoint(1, X, Y);         // new position
+        with Pt(1) do NewPoint(X,Y);
+        with Pt(0) do NewPoint(X,Y);
+        Draw;
+        end;
+  3: begin
+        Leadline.Draw;
+        Leadline.Free;
+        Finish;
+        Draw;
+        end
+  end; {case}
+end;
+
+procedure TReck.Drag(X,Y: longint);
+begin
+  if not Dragging then exit;
+  case state of
+  1: begin   // draw only a line
+    inherited draw;
+    Leadline.Drag(X,Y);
+    end;
+  2: begin
+    Draw; // clear old with XOR
+    if LOpt.Diagsfirst then begin
+       EPt(0).ForceThales(EPt(2), X, Y);
+       ConstructDiag(X, Y);
+       end
+    else ConstructSide(X, Y);
+    Draw; // draw new with XOR
+    end;
+  end; {case}
+end;
+
+function  TReck.Properties: String;
+begin
+  result := inherited Properties;
+  if Dist(1,2) = 0 then exit;
+  result := result
+    + 'Proportion:' + #9
+    +  Proportion(Dist(0,1)/Dist(1,2)) + CRLF
+    + 'Grundlinie:' + #9
+    +  FS(Dist(0,1) * Faktor)+ CRLF
+    + 'H�he:' + #9#9
+    +  FS(Dist(1,2) * Faktor)+ CRLF
+    + 'L�nge der Diagonale:' + #9
+    +  FS(Dist(0,2) * Faktor)+ CRLF
+    + 'Winkel zur Y-Achse:' + #9
+    +  FS(WinkelGrad(EPt(0).X - EPt(1).X, EPt(0).Y - EPt(1).Y)) + '�';
+end;
+
+procedure TReck.WriteResults;
+begin
+  if Dragging OR NOT LOpt.ShowTags then exit;
+  inherited;
+  WriteDist(0, 1);
+  WriteDist(1, 2);
+  if LOpt.Showprops then with Pt(0) do try
+    C.TextOut(
+      round((Pt(2).X+X)/2),
+      round((Pt(2).Y+Y)/2),
+      Proportion(Dist(0,1)/Dist(1,2)));
+  except end;
+end;
+
+procedure TREck.DrawDiag(nr: byte);
+var PenSave: TPenStyle;
+begin
+  with C do begin
+    PenSave := Pen.Style;
+    pen.Style := psDot;
+    pen.mode  := pmCopy;
+    brush.style := bsClear; // transparente Intervalle
+    if odd(nr)
+      then PolyLine([Pt(3), Pt(1)])
+      else PolyLine([Pt(2), Pt(0)]);
+    Pen.Style := PenSave;
+    end;
+end;
+
+procedure TREck.ConstructSide(X, Y: longint);
+var P1, P2: TEpoint;
+    dx, dy, ddx, ddy: Extended;
+begin
+  P1 := EPt(0);
+  P2 := EPt(1);
+  ddy := P2.Y - P1.Y; // distance
+  ddx := P2.X - P1.X;
+  // if LOpt.Diagsfirst
+  if abs(ddy) > abs(ddx) then try // take X value of clickpoint
+    dx  := X - P2.X;
+    dy  := ddx * dx / ddy;
+    except dy := 0;
+    end
+  else try // take Y calue of clickpoint
+    dy  := P2.Y - Y;
+    dx  := ddy * dy / ddx;
+    except dx := 0;
+    end;
+  SetPoint(2, P2.X + dx, P2.Y - dy);
+  SetPoint(3, P1.X + dx, P1.Y - dy);
+end;
+
+procedure TREck.ConstructDiag(X, Y: longint);
+var P0, P2: TPoint;
+
+begin
+  P0 := Pt(0);
+  P2 := Pt(2);
+  SetPoint(1, P2.X + P0.X - X, P2.Y + P0.Y - Y);
+  SetPoint(3, X, Y);
+end;
+
+procedure TSquare.Click(X,Y: longint);
+var P0, P1: TEpoint;
+    ddx, ddy: Extended;
+begin
+  case state of
+  0: inherited;
+  1: begin
+      SetPoint(1, X, Y);         // new position
+      LeadLine.Draw;
+      Leadline.Free;
+      P0 := Ept(0);
+      P1 := EPt(1);
+      ddy := P1.Y - P0.Y; // distance
+      ddx := P1.X - P0.X;
+      if LOpt.DiagsFirst then begin
+        ddx := ddx / 2;
+        ddy := ddy / 2;
+        with P1 do NewPoint(X, Y); // P2 is diagonally opposite of P0
+        with P0 do NewPoint(X + ddx - ddy, Y + ddy + ddx); // P3
+        with P0 do SetPoint(1, X + ddx + ddy, Y + ddy - ddx); // move P1
+        end
+      else begin
+        with P1 do NewPoint(X + ddy, Y - ddx);
+        with P0 do NewPoint(X + ddy, Y - ddx);
+        end;
+      Finish;
+      LOpt.Showprops := false;
+      Draw;
+      end;
+  end; {case}
+end;
+
+procedure TSquare.WriteResults;
+begin
+  if Dragging OR NOT LOpt.ShowTags then exit;
+  WriteDist(0, 1);
+  WriteDist(0, 2);
+end;
+
+constructor TCompound.create;
+begin
+  inherited create;
+  FCompoundObjects := TObjList.Create;
+end;
+
+procedure TCompound.free;
+var i : longint;
+begin
+  try
+    FCompoundObjects.Free;
+    inherited free;
+  except end;
+end;
+
+function TCompound.Copy: TDrawObject;
+var i: integer;
+begin
+  result := inherited Copy;
+  with FCompoundObjects do
+    while NextObj do TCompound(result).FCompoundObjects.Add(ONext.Copy);
+end;
+
+procedure TCompound.Click(A, B: longint);
+begin
+  inc(FState);
+  if FCompoundObjects.ObjType <> otCompound
+    then FCompoundObjects.Click(A, B)
+    else begin
+      Drag(A, B);
+      Finish;
+      end;
+end;
+
+procedure TCompound.Drag(A, B: longint);
+begin
+  FCompoundObjects.Drag(A, B);
+end;
+
+procedure TCompound.Move(A, B: extended);
+var i : longint;
+begin
+  with FCompoundObjects do while NextObj do ONext.Move(A, B);
+end;
+
+procedure TCompound.Draw;
+var i : longint;
+begin
+  with FCompoundObjects do while NextObj do ONext.Draw;
+end;
+
+function  TCompound.Properties: String;
+begin
+  result := inherited Properties
+    + 'Zahl der Elemente: '
+    +  IntToStr(FCompoundObjects.Count);
+end;
+
+procedure TCompound.Turn(A, B, Angle: Extended);
+var i : longint;
+begin
+  with FCompoundObjects do while NextObj do ONext.Turn(A, B, Angle);
+end;
+
+procedure TCompound.Scale(A,B, Ratio: Extended);
+var i : longint;
+begin
+  with FCompoundObjects do while NextObj do ONext.Scale(A, B, Ratio);
+end;
+
+procedure TCompound.SaveToFile;
+var i, z : longint;
+begin
+  try
+    Writeln(F, '[', Name, ']');
+    Writeln(F, 'TY=', Classname);
+    Wrint('Objects=', FCompoundObjects.Count);
+    with FCompoundObjects do while NextObj do ONext.SaveToFile;
+  except end;
+end;
+
+procedure TCompound.SetHigh(b: Boolean);
+var i : longint;
+begin
+  inherited;
+  FCompoundObjects.Highlight := b;
+end;
+
+function  TCompound.GetHigh: boolean;
+begin
+  GetHigh := FCompoundObjects.Highlight;
+end;
+
+procedure TCompound.SetState(i: longint);
+begin
+  FState := i;
+  FCompoundObjects.SetState(i);
+end;
+
+procedure TCompound.ReadFromFile(L: TStrings);
+var Z: longint;
+begin
+  state := dsComplete;
+  // vorerst keine Funktion
+end;
+
+function TCompound.MinDist(A, B: longint): extended;
+var i: longint; D: extended;
+begin
+  result := 1000.0; // sets to a big value;
+  with FCompoundObjects do while NextObj do begin
+    D := ONext.Mindist(A, B);
+    if result > D then result := D;
+    end;
+end;
+
+function  TCompound.StatusPt(index: Longint): TPoint;
+begin
+  with FCompoundObjects do try
+    result := ObjNr(count-1).Pt(index);
+  except
+    result := Point(0,0);
+    end;
+end;
+
+function TCompound.Snap(var A, B: longint): boolean;
+begin
+  result := FCompoundObjects.Snap(A, B);
+end;
+
+procedure TCompound.Add(T: TDrawObject);
+begin
+  FCompoundObjects.Add(T);
+end;
+
+procedure TCompound.Swallow(T: TCompound);
+var i : integer;
+begin
+  with T.FCompoundObjects do begin
+    while NextObj do Self.Add(ONext);
+    Clear;
+    end;
+  try
+    T.Free; // st�rzt ab?
+  except end;
+end;
+
+// ---------- TGrid --------------
+constructor TGrid.create;
+begin
+  inherited;
+  FCompoundObjects.ObjType := otLine;
+end;
+
+procedure TGrid.Click(X,Y: longint);
+var P: TPoint;
+    i: longint;
+    dx, dy, Nearest: longint;
+    Line: TDrawObject;
+begin
+  case state of
+  0..1: inherited;
+  2: with FCompoundObjects do begin
+       Line := ObjNr(0);
+       Line.Lopt.ShowTags := false;
+       Line.Lopt.ShowPts := false;
+       P := Line.Pt(Line.Nearest(X, Y));
+       dx := X - P.X;
+       dy := Y - P.Y;
+       for i := 1 to 10 do begin
+           Line := Line.Copy;
+           Line.Move(dx, dy);
+           Add(Line);
+           end;
+       Finish;
+       Draw;    
+       end;
+  end; {case}
+end;
+
+
+// ---------- TOval --------------
+constructor TOval.create;
+begin
+  inherited;
+  FCompoundObjects.ObjType := otRect;
+end;
+
+procedure TOval.Drag(A, B: longint);
+begin
+  if state = 3 then S1.ForceDistDirection(M, A, B);
+  inherited;
+end;
+
+procedure TOval.Click(X,Y: longint);
+var P: TPoint;
+    Rect, Kreis1, Kreis2, Arc1, Arc2: TDrawObject;
+    A, B: Extended;
+    L1 : TXY;
+begin
+  case state of
+  0..1: inherited;
+  2: with FCompoundObjects do begin
+       inherited;             // finish rect
+       Rect := ObjNr(0);
+       with Rect do begin
+         M := Ept(5); A := Dist(0, 1); B := Dist(1, 2);
+         if A > B then begin // set start point of circle on short side
+            S1 := EPt(6); S2 := EPt(4); LongDist := A; ShortDist := B; end
+         else begin
+            S1 := EPt(4); S2 := EPt(6); LongDist := B; ShortDist := A; end;
+         end;
+       P := S1.ScreenPt;
+       ObjType := otCircle;
+       Click(P.X, P.Y);     // start circle
+       InfoProc('Kleiner Kreis');
+       end;
+  3: with FCompoundObjects do begin
+       Rect   := ObjNr(0);                         // middle of oval
+       Kreis1 := ObjNr(1);
+       Kreis1.Finish;                              // finish small circle
+       M1 := Kreis1.Ept(0);
+       Kreis2 := Kreis1.Copy;
+       Add(Kreis2);                                // copy circle
+       Kreis2.Move(2 * (M.X - M1.X), 2 * (M.Y - M1.Y)); // move it to other end of rect
+       M2 := Kreis2.Ept(0);
+       L1 := S2.DistDirection(M, S1.DistanceTo(M1));
+       with M1 do Newpoint(X, Y);
+       with L1 do Newpoint(X, Y);
+       with MidXY(M1, EPt(1)) do Newpoint(X, Y);
+       with Lot(Ept(2), Ept(1), 10) do Newpoint(X, Y);
+       with Schnittpunkt(Ept(3), EPt(2), EPt(1), M) do M3 := Newpoint(X, Y);
+       ObjType := otArc;
+       Arc1 := TArc.Create;
+       Add(Arc1);
+       with M3.ScreenPt do Arc1.NewPoint(X, Y);    // start circle
+       with M1.ScreenPt do A1 := Arc1.NewPoint(X, Y);
+       with M2.ScreenPt do A2 := Arc1.NewPoint(X, Y);
+       with M3 do begin
+          A1.ScaleTo(X, Y, DistanceTo(S2)/DistanceTo(A1));
+          A2.ScaleTo(X, Y, DistanceTo(S2)/DistanceTo(A1));
+          end;
+//       if M3.Clockwise(A1, A2) then Arc1.Points.Exchange(1,2);
+//       Arc2 := Arc1.Copy;
+//       with M do Arc2.Turn(X,Y, pi);
+//       Add(Arc2);
+//       Arc1.Draw;
+       // Kreis3.LOpt.CircMid := True;    // draw from midpoint
+       Finish;
+       Draw;
+       end;
+  end; {case}
+end;
+
+procedure TOVal.WriteResults;
+begin
+  inherited;
+end;
+
+// ---------- TIntercol --------------
+constructor TIntercol.create;
+begin
+  inherited;
+  FCompoundObjects.ObjType := otCircle;
+end;
+
+procedure TInterCol.Drag(A, B: longint);
+begin
+  if state = 2 then with FCompoundObjects.ObjNr(0) do
+    Ept(1).ForceDirection(Ept(2), A, B);
+  inherited;
+end;
+
+
+procedure TInterCol.Click(X, Y: longint);
+var A, B: Extended;
+    Kreis1, Kreis2, Linie: TDrawObject;
+begin
+  case state of
+  0: begin
+       inherited; // create circle
+       InfoProc('S�ulendurchmesser');
+       end;
+  1: begin
+       inherited; // finish circle
+       FCompoundObjects.ObjType := otLine;
+       FCompoundObjects.Click(X, Y); // create line
+       InfoProc('S�ulenabstand');
+       end;
+  2: with FCompoundObjects do begin
+       Linie  := ObjNr(1);
+       Kreis1 := ObjNr(0);
+       Linie.Finish; // finish line
+       Kreis2 := Kreis1.Copy;
+       Add(Kreis2); // copy circle
+       A := Linie.Ept(1).X-Kreis1.Ept(2).X;
+       B := Linie.Ept(1).Y-Kreis1.Ept(2).Y;
+       Kreis2.Move(A, B); // move it to end of line
+       Finish;
+       Draw;
+       end;
+  end; {case}
+end;
+
+function  Tintercol.Properties: String;
+var Diam, Dist, Prop: Extended;
+begin
+  with FCompoundObjects do begin
+    Diam := ObjNr(0).Dist(1,2);
+    Dist := ObjNr(1).Dist(0,1);
+    try
+      Prop := Diam/Dist;
+    except
+      Prop := 0;
+      end;    
+    end;
+  result := Name + CRLF
+    + 'S�ulen-Durchmesser:' + #9
+    +  FS(Diam * Faktor)+ CRLF
+    + 'S�ulen-Abstand:' + #9#9
+    +  FS(Dist * Faktor)+ CRLF
+    + 'Verh�ltnis:' + #9 + Proportion(Prop) + CRLF
+    + Columnprop(Prop);
+end;
+
+// ---------- TIntercol --------------
+procedure TRatio.Click(X, Y: longint);
+begin
+  case state of
+  0: begin
+       inherited; // create line
+       InfoProc('Erste Strecke');
+       end;
+  1: begin
+       inherited; // finish line
+       FCompoundObjects.Click(X, Y); // create line
+       InfoProc('Zweite Strecke');
+       end;
+  2: with FCompoundObjects do begin
+       // Linie := ObjNr(0);
+       Finish;
+       Draw;
+       end;
+  end; {case}
+end;
+
+function  TRatio.Properties: String;
+var D1, D2, Prop, Angle: Extended;
+    P0, P1, P2: TEPoint;
+begin
+  with FCompoundObjects do begin
+    D1 := ObjNr(0).Dist(0,1);
+    D2 := ObjNr(1).Dist(0,1);
+    try
+      Prop   := D1/D2;
+    except
+      Prop   := 0;
+      end;
+    P0 := ObjNr(0).EPt(0);
+    P1 := ObjNr(0).EPt(1);
+    P2 := ObjNr(1).EPt(1);
+  Angle :=
+    WinkelGrad(P1.X - P2.X, P1.Y - P2.Y)-
+    WinkelGrad(P1.X - P0.X, P1.Y - P0.Y);
+  if Angle < 0 then Angle := Angle + 360.0;
+    end;
+  result := Name + CRLF
+    + 'Strecke A:' + #9
+    +  FS(D1 * Faktor)+ CRLF
+    + 'Strecke B:' + #9
+    +  FS(D2 * Faktor)+ CRLF
+    + 'Verh�ltnis:' + #9
+     + Proportion(Prop)+ CRLF
+    + '�ffnungswinkel:' + #9
+    +  FS(Angle) + '�';
+end;
+
+procedure TReady.AddObj(T: TDrawObject);
+begin
+end;
+
+procedure TReady.Click(X, Y: longint);
+begin
+  inherited;
+  with FCompoundObjects do case state of
+  1: begin
+        LeadLine := TLine.Create;
+        Leadline.Click(X, Y);
+        Leadline.LOpt.LineColor := Opt.MarkColor;
+        end;
+  2: begin
+        Leadline.Draw;
+        Leadline.Free;
+        Finish;
+        Draw;
+        end;
+  end;
+
+end;
+
+procedure TReady.Drag(A, B: longint);
+begin
+  if not Dragging then exit;
+  with FCompoundObjects do case state of
+  1: begin   // draw only a line
+     inherited draw;
+     Leadline.Drag(A, B);
+     end;
+  2: begin
+     Draw; // clear old with XOR
+     // Turn, Scale
+     Draw; // draw new with XOR
+     end;
+  end; {case}   
+end;
+
+
+// -----------------------------------------------------------
+procedure TObjList.InfoState;
+begin
+  case drawState of
+    dsOnMove     : Infoproc('Anfangspunkt setzen');
+    dsOnTurn     : Infoproc('Drehpunkt setzen');
+    dsOnScale    : Infoproc('Nullpunkt setzen');
+    end;
+end;
+
+// -----------------------------------------------------------
+procedure TObjList.NextState;
+begin
+  State := succ(drawState);
+  case drawState of
+    dsMoving     : Infoproc('Zielpunkt festlegen');
+    dsTurnStart  : Infoproc('Startwinkel angeben');
+    dsTurning    : Infoproc('Neuen Winkel festlegen');
+    dsScaleStart : Infoproc('Vergleichsstrecke angeben');
+    dsScaling    : Infoproc('neue L�nge festlegen');
+    end;
+end;
+
+procedure TObjList.Drag(A, B: longint);
+begin
+  case DrawState of
+    dsMoving       : Move(A, B);
+    dsTurnStart,
+    dsTurning      : Turn(A, B);
+    dsScaleStart,
+    dsScaling      : Scale(A, B);
+    dsDrawing      : try
+      obj.Drag(A, B);
+      except end;
+    end;
+end;
+
+procedure TObjList.CombineObjects;
+var i, j, k : integer;
+    C  : TCompound;
+    T  : TDrawObject;
+begin
+  if not highlight then exit;
+  NewObj(ord(otCompound),'');
+  j := count-2;
+  for i := j downto 0 do begin
+    T := ObjNr(i);
+    if T.Highlight then
+      if (T is TCompound)
+        then TCompound(Obj).Swallow(TCompound(T))
+      else begin
+        TCompound(Obj).Add(T);
+        Delete(i);
+        end;
+    end;
+  Pack;
+  ObjType   := otLine;
+  Obj.State := dsComplete;
+  Obj.Highlight := TRUE;
+  ResetList;
+end;
+
+procedure TObjList.CopyObj;
+var i, j: integer;
+begin
+  j := count-1;
+  while NextObj do with ONext do
+    if highlight then Add(Copy);
+end;
+
+function TObjList.Snap(var A, B: longint): boolean;
+var i : longint;
+begin
+  for i := 0 to Count - 1 do begin
+    result := ObjNr(i).Snap(A, B);
+    if result then break;
+    end;
+end;
+
+end.
+
+