![]() |
![]() |
|||||||
![]() Above formula (the Newton binomium) was typed in just a few seconds. All text is editable, -undo- works. Some more editing functions like cut and paste have to be implemented however. My math editor rests on these pillars: ![]() DavArrayButton Own component with rows or columns of menu buttons. Used for menu choices. Description is found [ here ] XBitmap Own unit/Class. Is an extension of TBitmap with clipping rectangle, improved floodfill, improved stretchrect, dash-dot lines of multipixel penwidths, lines with arrow points. Description is found [ here ] Xfont Own class with scalable fonts (3 types), build with ASCII and Greek characters, plus some geometrical symbols. Description is found [ here ] Xtree Own unit/Class with operations on tree graphs. Also provides UNDO mechanism. Description is found [ here ] Data Flows ![]() There are 3 XBitmaps each of 760 x 1080 pixels in 32bit color format.
map2 - map1 + text map3 - map2 + trial drawings during construction Text is erased by copying part of map1 to map2. Text is added to map2. Map3 holds drawings under construction. Graphic elements (lines, circles, color fillings) are erased by copying part of map2 to map3. The final drawing (after mouse-up) is then made in map1. Results are visualized by copying map3 to a paintbox on the main form. This paintbox has a lesser height that the bitmaps, a scrollbar selects a part of map3. Associated with each map1,2,3 are a Trect rectangle with a mapfull flag of type boolean. XFont and XBitmap classes have a property modrect. This is the rectangular part of the bitmap that was modified during the last operation. This rectangle is merged with one or more rect1,2,3 to enable a copy between the bitmaps. Only modified parts of a bitmap are copied. Operations on rectangles Unirect(r1,r2) : rectangle r2 is combined with r1 so r1 surrounds r2. Interrect(r1,r2) : r1 is limited to the part overlapping r2. Packrect(x1,y1,x2,y2 : smallInt) : Trect : build Trect from x,y values after sorting. ![]() mapfull flags are false if the rectangle holds no information. (no modified parts) In this case a unirect(r1,r2) equals r1 := r2 after which the flag is set true. All variables for coordinates are of type smallInt. This allows for coordinates outside the maps. The maximal number of pages is 30. The y-coordinate of an element (divided by 1080) denotes the page number. The menu system Above pictured is the main menu. An enumerated variabele (which simply is a name attached to a sequential number) holds the active button.
type TMainbutton = (mbDoc,mbPage,mbEdit,mbFrame,mbText,mbGraph,mbSymbol,mbGeo,
mbInfo,mbhelp,mbOff);
....
var mainmenubutten : TMainbutton;
Activating a button opens submenu's with new choices and buttons for property selections.
type TFontProp = record
nr : byte; //font number 1..3
height : byte;
base : byte;
style : byte;
color1 : DWORD; //foreground
color2 : DWORD; //background
end;
......
var fontprops : TFontProp;
fontDef : array[1..maxFontDef] of TFontProp;
fontdefcount : byte; //top of array
Property selection xxx is stored in a variable named xxxProp.Text elements hold an index into a table which holds the font properties. For fonts this is array fontDEF, in general: array xxxDEF[ ]. Text element[element nr].p1 is index into the font table. There are many more property variables and xxxDEF tables. These properties are once entered in the table and are never removed. Adding new properties to the xxxDEF tables is done by procedures called registerXXX(....) Control flow Keyboard and mouse generate events.Menuvariables controlling a case statement route these events to the proper procedures to handle text editing or drawing of graphical symbols.
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case key of
VK_Next : begin
nextpage;
key := 0;
end;
VK_Prior : begin
previouspage;
key := 0;
end;
end;
if key <> 0 then
begin
keyEvent := kbDown;
keyword := key;
keyShift := shift;
case mainmenubutton of
mbText : textKeyEvent;//--> text control unit
mbEdit : case editbutton of
ebGraph : ; //--> graph edit unit
ebText : texteditKeyboardEvent; //--> textedit unit;
end;
end;//case
key := 0;
end;//if
end;
Code above shows that procedures for the editing of graphical symbols is under construction.This is a very simple example: other similar procedures are of far greater length. Handling text All text elements (characters, macro's, lines) are of type:
type Telementtype = (elFree,elblock,elLine,elChar,elMacro,elYTab,
elReserved);
TElSet = set of TElementType;
TElement = record
elType : TelementType;
elCode : byte;
p1 : byte;
p2 : byte;
p3 : smallInt;
x : smallInt; //position relative to parent
y : smallInt; //..
width : smallInt;
height : smallInt;
end;
.....
var element : array[1..maxelement] of TElement; //frame,line,macro,char
The relationship between these elements is recorded here:
type TLinks = record
parent : dword;
child : dword;
next : dword;
previous : dword;
end;
.....
var Links : array[1..maxelement] of TLinks;
The Element and Link arrays have a 1:1 relation.The Link array cannot be read or written directly: only procedures and functions may be called to perform tree operations. The tree system also provides for UNDO. Contrary to properties, elements may be removed from the element array to be reused. This happens if an element was deleted long ago and the maximum depth of the UNDO stack was reached. In this case the latest UNDO stack entry was purged, no UNDO is possible for this deleted element and the Tree system notifies element destruction, so the element can be set free. XTree cannot change the Element[ ] array because Xtree is not concerned about the type of element. Deleting an element only separates the links from the tree. UNDO reconnects the links. Text control Text control is handled by the textcontrol_unit.The first concern here is cursor movement. Cursor movement within lines: This is the processing of left- right cursor key events. Within a line, characters may vary in height, color and style. The cursor (record) has the property posR:Boolean which indicates the place of the cursor: left or right of the element. The cursor adjusts it's size to the element of it's position. If posR = true then - a left key event will place the cursor left of the same element - a right key event will place the cursor right of the next element - at the last line element, the event is passed to the parent. If posR = false then - a left key event will place the cursor left of the previous element. - een right event will place the cursor right of the same element. - at the first element the event will be passed to the parent. The parent of a character always is a line. The parent of a line may be a macro or a block. In the case of a macro cursor movement is handled by macro type specific procedures. Macro's receiving cursor key events will redirect these events to one of their (line) children. Also there exist cursor Up- and Down events. These events are redirected to the parent because the cursor leaves the line or macro. So, a lot of code is needed for cursor movement. The cuNAV record holds control data for the cursor route in lines or macro's having 1, 2, 3 or more (line) children.
type TCursordirection = (cdLeft,cdRight,cdUp,cdDown);
TCursorProc = (cpKB,cpM2P,cpM2C); //message to Parent / Child
TCursorNavigation = record
cdir : TCursordirection;
cproc : TCursorProc;
destEL : dword; //destination element
srcEL : dword; //source element
end;
...
var cuNAV : Tcursornavigation;
Here a simple piece of code where cuNAV starts it's journey:(keyEvent was stored before by the form1.Keydown event)
....
case keyEvent of
kbDown : begin
case keyword of
VK_LEFT : begin
cuNAV.cdir := cdLeft;
cuNavigate;
end;
VK_RIGHT : begin
cuNav.cdir := cdRight;
cuNavigate;
end;
VK_UP : begin
cuNav.cdir := cdUP;
cuNavigate;
end;
VK_DOWN : begin
cuNav.cdir := cdDown;
cuNavigate;
end;
....
steering the cursor further....
procedure CuNavigate;
//switch to element type
//use cuNAV
var mac : TTextbutton;
framecode : TFrameButton;
el : dword;
m23 : boolean;//root,symbol types:>1 child
begin
el := cuNAV.destEL;
case element[el].elType of
elChar : NavChar;
elLine : Navline;
elMacro : begin
mac := TTextbutton(element[el].elCode);
m23 := (textprops[mac].code and 1) = 1;
case mac of
txtPower,
txtIndex,
txtParenth : NavMacro1;
txtLimit,
txtPowInd,
txtFraction,
txtOver : NAVmacro2;
txtRoot : if m23 then NAVroot else NAVmacro1;
txtPowerLine : NAVpowerLine;
txtIndexLine : NAVIndexLine;
txtPowIndLine : NAVpowIndLine;
txtSymbol : if m23 then NAVmacro3 else NAVmacro1;
txtVector : NAVmacroVector;
end;//case
end;
....
For the case of a single line macro:
procedure NavMacro1;
//single line macros
begin
case cuNAV.cproc of
cpKB : begin
case cuNAV.cdir of
cdLeft : if textcursor.posR then gotoChild
else gotoParent;
cdRight : if textcursor.posR = false then gotochild
else gotoParent;
cdUp,
cdDown : gotoParent;
end;
end;
cpM2P : begin
case cuNAV.cdir of
cdLeft : begin
textcursor.posR := false;
setcursorOnElement(cuNAV.destEL);
end;
cdRight : begin
textcursor.posR := true;
setcursorOnElement(cuNAV.destEL);
end;
cdUp,
cdDown : gotoParent;
end;
end;
cpM2C : begin
case cuNAV.cdir of
cdLeft : begin
textcursor.posR := false;
gotochild;
end;
cdRight : begin
textcursor.posR := false;
gotoChild;
end;
cdUp,
cdDown : gotoXmatchChildN(1);
end;
end;
end;
end;
Procedure gotoXmatchChild tries to place the cursor on a line element having the same X position,so moves the cursor vertically up or down. The GotoChild procedure: procedure GotoChild; begin with cuNAV do begin srcEL := destEL;//destiation becomes source cproc := cpM2C;//message to child getChild(destEL,srcEL);//destination is child end; cuNavigate; end;cpKB means the keyboard was the originator. cpM2C means cursor was send to Child. Also there is cpM2P, cursor was send to parent. An element has to know who was calling, where the cursor comes from. Text handling units Three units take care of text procedures:
- text paint : text and macro's painting - text calculation For each type (elBlock,elMacro,elLine,elChar) there are three specific procedures:
paint : painting of element. recalculate : calculate width, height and positions of the children This is the task of the parent. The advantage of this method is that elements may be moved without caring about their contents. The (x,y) position of the children is relative to the parent. If the size of an element changes due to inserts or deletes, the parent is informed. Then the parent recalculates it's width and height together with the position of the children. In the case of changes the parent calls it's parent.. and so on. Finally, the last recalculated element, together with it's children, are repainted. Note: children are painted automatically. So, the specific paint procedures per macro only take care of graphical symbols like fraction lines, root or sigma symbols. The macro paint procedures also calculate these graphic symbol dimensions. The core is this small procedure processing element changes:
procedure processELchange(el : dword);
//element reports change to it's parent el
//el can be macro,line,column (not char or block)
//purpose is
//1. to call for recalculation of parents
//2. set area to be erased
//3. set element that needs repainting
var oldw,oldh : smallInt;
Done : boolean;
r1 : Trect;
begin
eraseflag := false;
spaceflag := false;
repeat
repaintELnr := el;
oldw := element[el].width;
oldh := element[el].height;
r1 := getElementRect(pageNr,el);
updateRect(eraseflag,eraseRect,r1);
recalculateElement(el);
if (oldw <> element[el].width) or (oldh <> element[el].height) then
begin
getParent(el,el);
Done := element[el].elType = elBlock;
end
else Done := true;
until Done;
end;
oldw : old width of elementoldh : old height eraseRect : rectangle to be erased (old element size) getElementRect : calculates rectangle of element relative to page updateRect : merges rectangles recalculateElement( ): contains big case statement to select the proper element procedure. Element Insert or Delete procedures are element type independent. Here I conclude this part 2 description of my math editor. More work has to be done such as copy-paste, storing and opening data, graphic symbol editing and solving many, many errors showing up on the way. So far, my intention was to describe the general structure. Finally I add a formula which was typed in a few seconds: the vector dot product
|
||||||||
![]() |
![]() |