1## -*- tcl -*-
2## (C) 2010 Andreas Kupries <andreas_kupries@users.sourceforge.net>
3## BSD Licensed
4# # ## ### ##### ######## ############# ######################
5
6#
7# diagram core, using direction and element databases, plus layout
8# engine. Implements the base language (concrete attributes and
9# elements are specified outside, the core only has the pertinent
10# extensibility features).
11#
12# Uses an instance specific namespace to encapsulate the commands of
13# the drawing language, and the drawing state (variables for points,
14# elements, etc.).
15#
16
17##
18# # ## ### ##### ######## ############# ######################
19## Requisites
20
21package require Tcl 8.5              ; # Want the nice things it
22                                       # brings (dicts, {*}, etc.)
23package require Tk
24package require snit                 ; # Object framework.
25package require diagram::direction   ; # Database of named directions
26package require diagram::element     ; # Database of drawn elements
27package require diagram::navigation  ; # State of automatic layouting
28package require diagram::point       ; # Point validation and processing.
29package require diagram::attribute   ; # Database of element attributes
30package require namespacex           ; # Namespace utility functions
31package require struct::set          ; # Set arithemetics (blocks)
32package require math::geometry 1.1.2 ; # Vector math (points, line
33				       # (segments), poly-lines).
34
35# # ## ### ##### ######## ############# ######################
36## Implementation
37
38snit::type ::diagram::core {
39
40    # # ## ### ##### ######## ############# ######################
41    ## Public API :: Core extensibility (drawing elements, attributes,
42    ##               special attribute forms)
43
44    method {new direction} {name args} {
45	$dir new direction $name {*}$args
46	return
47    }
48
49    method {new shape} {name} {
50	$elm shape $name
51	return
52    }
53
54    method {new element} {name attrcmd drawcmd} {
55	$elm shape $name
56	$self new alias $name [mymethod Element $name $attrcmd $drawcmd]
57	return
58    }
59
60    method {new alias} {name cmdprefix} {
61	#$self new command $name args "$cmdprefix {*}\$args"
62	$self new command $name args "uplevel 1 \[list $cmdprefix {*}\$args\]"
63	return
64    }
65
66    method {new command} {name arguments body} {
67	proc ${mylangns}::$name $arguments $body
68	return
69    }
70
71    method {new attribute} {name args} {
72	$att new $name {*}$args
73	return
74    }
75
76    method {unknown attribute} {hook} {
77	$att unknown + $hook
78	return
79    }
80
81    # # ## ### ##### ######## ############# ######################
82    ##
83
84    method snap {} {
85	return [namespacex state get $mylangns]
86    }
87
88    method restore {state} {
89	return [namespacex state set $mylangns $state]
90    }
91
92    # # ## ### ##### ######## ############# ######################
93    ## Public API :: Draw
94
95    method draw {script} {
96	#set script [list block $script with nw at [diagram::point at 0 0]]
97	return [uplevel 1 [list namespace eval $mylangns $script]]
98    }
99
100    # # ## ### ##### ######## ############# ######################
101    ## Public API :: Instance construction
102
103    constructor {canvas args} {
104	set mycanvas $canvas
105	set mylangns ${selfns}::$ourlang
106
107	install dir using ::diagram::direction  ${selfns}::DIR
108	install elm using ::diagram::element    ${selfns}::ELM $dir
109	install nav using ::diagram::navigation ${selfns}::NAV $dir
110	install att using ::diagram::attribute  ${selfns}::ATT $self
111
112	$self SetupLanguage
113
114	if {![llength $args]} return
115	$self draw $args
116	return
117    }
118
119    destructor {
120	if {$mycanvas eq {}} return
121
122	# This object has not been detached from the drawing engine
123	# (canvas), therefor its destruction implies the destruction
124	# of the drawn diagram as well.
125	catch {
126	    $self drop
127	}
128	return
129    }
130
131    method detach {} {
132	set mycanvas {}
133	return
134    }
135
136    method drop {} {
137	# Destroy all elements and their items.
138	$mycanvas delete {*}[$elm items {*}[$elm elements]]
139	$elm drop
140	$nav reset
141	return
142    }
143
144    # # ## ### ##### ######## #############
145
146    method {state set} {varname value} {
147	#puts \tState($varname):=|$value|
148
149	namespace upvar $mylangns $varname x
150	set x $value
151	return
152    }
153
154    method {state get} {varname} {
155	namespace upvar $mylangns $varname x
156
157	#puts \tState($varname)->|$x|
158	return $x
159    }
160
161    # # ## ### ##### ######## #############
162
163    method where {} {
164	return [list [$nav at] [$dir get [$nav direction] angle]]
165    }
166
167    method move {delta corners} {
168	return [$elm move $delta $corners]
169    }
170
171    method map {corners c} {
172	return [$dir map $corners $c]
173    }
174
175    # # ## ### ##### ######## #############
176    ## Internal :: Setup of core language
177
178    method SetupLanguage {} {
179	# Language encapsulation
180	namespace eval $mylangns {}
181
182	# Standard elements and operations
183
184	$self new alias set       [mymethod Set]
185	$self new alias unset     [mymethod Unset]
186	$self new alias move      [mymethod Move]
187	$self new alias block     [mymethod Block]
188	$self new alias group     [mymethod Group]
189	$self new alias here      [mymethod At]
190	$self new alias direction [list $nav direction]
191	$self new alias by        [mymethod By]
192	$self new alias intersect [mymethod Intersect]
193
194	$elm shape move
195	$elm shape block
196
197	# Standard attributes (element appearance, location).
198
199	# keep here ... / type == snit validation type!
200
201	$att new movelength type {snit::double -min 1} linked [list movelength [Unit 2 cm]]
202
203	# XXX refactor the mymethod calls out, use variables
204	$att new with                                                       default [mymethod Placement]
205	$att new at   type diagram::point transform [mymethod DerefElement] default [mymethod Placement]
206	$att new from type diagram::point transform [mymethod DerefElement] default [mymethod Waypoints]
207	$att new to   type diagram::point transform [mymethod DerefElement] default [mymethod Waypoints]
208	$att new then type diagram::point transform [mymethod DerefElement] default [mymethod Waypoints] \
209	    get [mymethod GetPoints] aggregate 1
210
211	$att unknown + [mymethod Directions]
212
213	# Now special forms of commands, handled via 'namespace
214	# unknown'. Making, for example, elements and points into
215	# pseudo-objects.
216
217	namespacex hook add $mylangns [mymethod CatchAll]
218
219	# syntax: [<direction>] --> ()
220	namespacex hook on $mylangns [mymethod DCGuard] [mymethod DCRun]
221
222	# Global commands for named directions. The commands are
223	# created on first use. That allows extension packages
224	# declaring their own directions to do this after the core has
225	# booted. Just creating the direction commands at boot time
226	# will miss the directions of extensions.
227
228	# (%%) Commands to access the history (n'th ...)
229
230	# Visible syntax:
231	#
232	# <n>th      <shape> ?<corner>? | 2/3 | (1)
233	# <n>th last <shape> ?<corner>? | 3/4 | (2)
234	#       last <shape> ?<corner>? | 2/3 | (3)
235	# <n>th last         ?<corner>? | 2/3 | (4)
236	#       last         ?<corner>? | 1/2 | (5)
237	#
238	# Note: The form <shape> ?<corner>? is NOT possible.
239	#       <shape> is the drawing command.
240	#
241	# Note 2: Because of (xx) the internal syntax is simpler, as
242	#         the argument <n>th is always present, and not
243	#         optional.
244	#
245	# <n>th      <shape> ?<corner>? | 2-3
246	# <n>th last <shape> ?<corner>? | 3-4
247	# <n>th last         ?<corner>? | 2-3
248	#
249
250	$self new alias 1st 1th
251	$self new alias 2nd 2th
252	$self new alias 3rd 3th
253	$self new alias last [mymethod Recall 1th last] ; # (xx)
254	namespacex hook on $mylangns [mymethod RecallGuard] [mymethod Recall]
255
256	# Pseudo object commands for points
257	#
258	# syntax: [<number> cm|mm|point|inch]         --> <number>
259	# syntax: [<number> <number>]                 --> <point>
260	# syntax: [<number> between <point> <point>]  --> <point>
261	# syntax: [<point> by <distance> <direction>] --> <point>
262	# syntax: [<point> +|- <point>]               --> <point>
263
264	namespacex hook on $mylangns [myproc   IsUnit]          [myproc Unit]
265	namespacex hook on $mylangns [myproc   IsPointCons]     {diagram::point at}
266	namespacex hook on $mylangns [myproc   IsInterpolation] [mymethod Interpolation]
267	namespacex hook on $mylangns [mymethod IsPointArithBy]  [mymethod PointArithBy]
268	namespacex hook on $mylangns [myproc   IsPointArithOp]  [mymethod PointArithOp]
269
270	# Pseudo object commands for elements.
271	#
272	# syntax: [<element> ?<corner>...? ?names ?<pattern>??] --> <point>|<element>|...
273
274	namespacex hook on $mylangns [myproc IsElementOp] [mymethod ElementOp]
275	return
276    }
277
278    # # ## ### ##### ######## ############# ######################
279    ## Internal :: Implementation of the core language commands.
280
281    method CatchAll {args} {
282	#puts |||$args|||
283	# Unknown commands are compiled as text elements
284	# --> Calls out into basic, assumes its presence.
285	return [$self draw [list text text {*}$args]]
286    }
287
288    method Move {args} {
289	set attributes [$att attributes move $args {from to then}]
290	set w [dict get $attributes waypoints]
291
292	# XXX share corner generation with line - sub packages.
293	lappend corners start [diagram::point at {*}[lindex $w 0]]
294	lappend corners end   [diagram::point at {*}[lindex $w end]]
295	set n 1
296	foreach p $w {
297	    lappend corners $n [diagram::point at {*}$p]
298	    incr n
299	}
300
301	# note: move is a bit special. It has neither child elements,
302	# nor canvas items. We define it actually only to make it
303	# visible in the history, and to block corner creation.
304	set eid	[$elm new move $corners {} {}]
305	$nav move $corners
306	return $eid
307    }
308
309    method Set {args} {
310	#puts SET|$args|
311	# Run builtin for the regular behaviour of the intercepted command.
312
313	set result [uplevel 1 [list ::set {*}$args]]
314
315	# During block processing we save variable re-definitions as
316	# the block's corners
317	if {$myinblock && ([llength $args] == 2)} {
318	    lappend mycorners {*}$args
319	}
320	return $result
321    }
322
323    method Unset {args} {
324	#puts UNSET|$args|
325	# Run builtin for the regular behaviour of the intercepted command.
326
327	set result [uplevel 1 [list ::unset {*}$args]]
328
329	# During block processing we are saving variable
330	# re-definitions as the block's corners, so have to remove
331	# that definition too.
332	if {$myinblock} {
333	    foreach c $args {
334		dict unset mycorners $c
335	    }
336	}
337	return $result
338    }
339
340    method Block {script args} {
341	# args = attributes.
342
343	# Save current state
344	set old [$elm elements]
345	set ehi [$elm history get]
346	set lst [namespacex state get $mylangns]
347	$nav save
348
349	# Process the attributes, and store the changed settings into
350	# their linked variables (if any), to make them proper
351	# defaults inside of the block.
352
353	set attributes [$att attributes block $args {at with}]
354	$att set $attributes
355	set at   [dict get $attributes at]
356	set with [dict get $attributes with]
357
358	# Run the block definition, prepare for the capture of corners.
359	set inblock $myinblock
360	set myinblock 1
361	set mycorners {}
362
363	#$self draw $script
364	uplevel 1 $script
365
366	# Remember the captured corners and reset capture system.
367	set myinblock $inblock
368	set corners [dict merge $mycorners]
369	set mycorners {}
370
371	# Extract the set of newly drawn elements.
372	set new [struct::set difference [$elm elements] $old]
373
374	#puts |$new|bb|[$elm bbox {*}$new]|
375
376	# Get the block's bbox from the union of its elements' bboxes.
377	lassign [$elm bbox {*}$new] xnw ynw xse yse
378
379	# XXX see BoxCornersRect of basic, share code
380	set xns [expr {($xnw + $xse) / 2.0}]
381	set yew [expr {($ynw + $yse) / 2.0}]
382	set w   [expr {$xse - $xnw}]
383	set h   [expr {$yse - $ynw}]
384
385	set compass [list \
386			 north     [diagram::point at $xns $ynw] \
387			 northeast [diagram::point at $xse $ynw] \
388			 east      [diagram::point at $xse $yew] \
389			 southeast [diagram::point at $xse $yse] \
390			 south     [diagram::point at $xns $yse] \
391			 southwest [diagram::point at $xnw $yse] \
392			 west      [diagram::point at $xnw $yew] \
393			 northwest [diagram::point at $xnw $ynw] \
394			 center    [diagram::point at $xns $yew] \
395			 width     $w \
396			 height    $h]
397
398	#puts COMPASS|$compass|
399	#puts CORNERS|$corners|
400
401	set corners [dict merge $compass $corners]
402
403	#puts BLOCK__\t($corners)
404	#puts __BLOCK
405
406	# Restore the system state to what it was before we entered
407	# the block.
408	$nav restore
409	namespacex state set $mylangns $lst
410	$elm history set $ehi
411
412	# Now save the block as element, aggregating the children, and
413	# move it into position, based on the placement attributes.
414	set eid [$elm new block $corners {} $new]
415	$elm relocate $eid $at $with $mycanvas
416	$nav move [$elm corners $eid]
417
418	return $eid
419    }
420
421    method Group {script} {
422	# A group is similar to a block, except that only the state of
423	# the layout engine is saved across it, not the whole element
424	# history, etc. The elements created here are not aggregated
425	# either. Further, changes to any attributes made inside the
426	# group are visible after it as well.
427
428	$nav save
429        #$self draw $script
430	uplevel 1 $script
431	$nav restore
432	return
433    }
434
435    method Element {shape required drawcmd args} {
436	# args = attributes.
437
438	# attrcmd :: attr-dict -> attr-dict
439	# drawcmd :: canvas -> attr-dict ->
440	#            (attr-dict canvas-item-list corner-dict ?placement-mode ?layout-direction??)
441
442	set newdirection {}
443	set mode         {}
444	set attributes   [$att attributes $shape $args $required]
445	lassign [{*}$drawcmd $mycanvas $attributes] \
446	    items corners mode newdirection
447	if {$mode eq {}} { set mode relative }
448
449	# Allow the user's commands to override the element type. For
450	# example, an 'arrow' element not only exapnd to 'line
451	# arrowhead ->', but also set the attribute '/shape arrow' to
452	# distinguish them from plain lines in the history.
453
454	if {[dict exists $attributes /shape]} {
455	    set shape [dict get $attributes /shape]
456	}
457
458	set eid [$elm new $shape $corners $items {}]
459
460	#puts $shape=$eid\t/mode=$mode/
461
462	if {$mode eq "relative"} {
463	    # Determine the final location of the new element and move
464	    # it there, as it was not created at its absolute/final
465	    # location already by its drawing command.
466
467	    set at   [dict get $attributes at]
468	    set with [dict get $attributes with]
469
470	    #puts "shift such $with at ($at)"
471	    $elm relocate $eid $at $with $mycanvas
472	}
473
474	# Update the layout engine with new position, and possibly a
475	# new direction to follow.
476
477	$nav move [$elm corners $eid] ; # This also discards the
478	# intermediate location set
479	# for any turns done during
480	# attribute processing.
481
482	if {$newdirection ne {}} {
483	    # The new element changed direction, notify the layout
484	    # engine. Commit immediately to the location for the
485	    # direction.
486
487	    $nav turn $newdirection 1
488	}
489
490	return $eid
491    }
492
493    method At {} {
494	return [diagram::point at {*}[$nav at]]
495    }
496
497    # # ## ### ##### ######## ############# ######################
498
499    method Corners {elements} {
500	set results {}
501	foreach e $elements {
502	    foreach {k v} [$elm corners $e] {
503		lappend result $e.$k $v
504	    }
505	}
506	return $result
507    }
508
509    # # ## ### ##### ######## ############# ######################
510    ## Handling of directions as attributes and global commands.
511
512    method Directions {shape words} {
513	#puts AU||$shape|u(([{*}$words peek [{*}$words size]]))
514
515	# Try to process like for a 'then' attribute, and if that
516	# succeeds stuff the result back to run it through the actual
517	# handling of the implicit 'then'.
518
519	if {![catch {
520	    $self ProcessPoints $words newdirection
521	} p]} {
522	    #puts <<ok|$p>>
523
524	    {*}$words unget $p
525	    {*}$words unget then
526
527	    #puts AU|||x(([{*}$words peek [{*}$words size]]))
528
529	    if {$newdirection ne {}} {
530		$nav turn $newdirection
531	    }
532	    #puts AU|done
533	    return 1
534	}
535
536	#puts AU<<$p>>
537	#puts $::errorInfo
538	return 0
539    }
540
541    # syntax: [<direction>] --> ()
542    method DCGuard {args} {
543	#puts DCG|$args|[llength $args]|
544	return [expr {([llength $args] == 1) &&
545		      [$dir isStrict [lindex $args 0]]}]
546    }
547
548    method DCRun {direction} {
549	#puts DCR|$direction|
550	$nav turn $direction 1
551	$self new command $direction {} \
552	    [list $nav turn $direction 1]
553	return
554    }
555
556    # # ## ### ##### ######## ############# ######################
557
558    method RecallGuard {args} {
559	#puts RecallGuard|$args|[llength $args]|[regexp {(\d+)th} [lindex $args 0]]
560	return [regexp {(\d+)th} [lindex $args 0]]
561    }
562    method Recall {offset args} {
563	#puts RECALL|$offset|$args|______________________________________________________________
564
565	# Syntax (internal!). See comments at (%%) in this file for
566	# the differences between internal and user visible syntax,
567	# and how the translation is made.
568	#
569	# <n>th      <shape> ?<corner>? | 2-3 | 1-2 | (a)
570	# <n>th last <shape> ?<corner>? | 3-4 | 2-3 | (b)
571	# <n>th last         ?<corner>? | 2-3 | 1-2 | (c)
572	#
573
574	set n [llength $args]
575	if {$n < 1 || $n > 3} {
576	    return -code error "wrong\#args: should be \"?n'th? ?last? ?shape? ?corner?\""
577	}
578
579	regexp {(\d+)th} $offset -> offset
580
581	# forward/backward search ?
582	if {[lindex $args 0] eq "last"} {
583	    set args [lassign $args _]
584	    set offset -$offset
585	}
586
587	# specific shape/all shapes ?
588	if {[$elm isShape [lindex $args 0]]} {
589	    set args [lassign $args shape]
590	} else {
591	    set shape {} ;# Search all shapes.
592	}
593
594	# corner yes/no ?
595	set corner {}
596	set n [llength $args]
597	if {$n == 1} {
598	    lassign $args corner
599	} elseif {$n > 1} {
600	    return -code error "wrong\#args: should be \"?n'th? ?last? ?shape? ?corner?\""
601	}
602
603	#puts H|recall|o|$offset|
604	#puts H|recall|s|$shape|
605	#puts H|recall|c|$corner|
606
607	# ... And access the history files ...
608
609	set eid [$elm history find $shape $offset]
610
611	#puts H|recall|e|$eid|
612
613	# ... at last return result, resolving the corner, if any such
614	# was specified.
615
616	if {$corner ne {}} {
617	    #puts H|recall|p|[$elm corner $eid $corner]
618	    return [$elm corner $eid $corner]
619	} else {
620	    #puts H|recall|x|$eid|
621	    return $eid
622	}
623    }
624
625    # # ## ### ##### ######## ############# ######################
626
627    # syntax: [<number> <unit>] --> <number>
628    proc IsUnit {args} {
629	#puts IsUnit|$args|[llength $args]|[string is double -strict [lindex $args 0]]|[info exists ourunit([lindex $args 1])]
630	return [expr {([llength $args] == 2) &&
631		      [string is double -strict [lindex $args 0]] &&
632		      [info exists ourunit([lindex $args 1])]}]
633    }
634
635    proc Unit {n unit} {
636	#puts "Unit $unit ($n)"
637	return [expr {$n * $ourunit($unit)}]
638    }
639
640    method unit {n unit} { return [Unit $n $unit] }
641
642    # syntax: [<number> <number>] --> <point>
643    proc IsPointCons {args} {
644	#puts IsPointCons|$args|[llength $args]|[string is double -strict [lindex $args 0]]|[string is double -strict [lindex $args 1]]
645	return [expr {([llength $args] == 2) &&
646		      [string is double -strict [lindex $args 0]] &&
647		      [string is double -strict [lindex $args 1]]}]
648    }
649
650    # syntax: [<number> between <point> <point>] --> <point>
651    proc IsInterpolation {args} {
652	#puts IsInterpolation|$args|[llength $args]|[string is double -strict [lindex $args 0]]|[string is double -strict [lindex $args 1]]
653	return [expr {([llength $args] == 4) &&
654		      [string is double -strict [lindex $args 0]] &&
655		      ([lindex $args 1] eq "between") &&
656		      [diagram::point is [lindex $args 2]] &&
657		      [diagram::point is [lindex $args 3]]}]
658    }
659
660    method Interpolation {s __between__ a b} {
661	set a [diagram::point resolve [$nav at] $a]
662	set b [diagram::point resolve $a $b]
663	return [diagram::point at {*}[geo::between $a $b $s]]
664    }
665
666    method By {distance direction} {
667	if {[$dir isStrict $direction]} {
668	    set angle [$dir get $direction angle]
669	} else {
670	    set angle $direction
671	}
672	return [diagram::point by $distance $angle]
673    }
674
675    # syntax: [<point> by <distance> <direction>] --> <point>
676    method IsPointArithBy {args} {
677	#puts IsPointArith|$args|[llength $args]|
678	return [expr {([llength $args] == 4) &&
679		      [diagram::point is [lindex $args 0]] &&
680		      ([lindex $args 1] eq "by") &&
681		      [string is double -strict [lindex $args 2]] &&
682		      [$dir is [lindex $args 3]]}]
683    }
684
685    method PointArithBy {p __by__ distance direction} {
686	if {[$dir isStrict $direction]} {
687	    set angle [$dir get $direction angle]
688	} else {
689	    set angle $direction
690	}
691	set delta [diagram::point by $distance $angle]
692
693	#puts PointArith|$p|++|D/$direction|A/$angle|d/$delta|==|[diagram::point + $p $delta]|
694	return [diagram::point + $p $delta]
695    }
696
697    # syntax: [<point> by <distance> <direction>] --> <point>
698    proc IsPointArithOp {args} {
699	#puts IsPointArithOp|$args|[llength $args]|
700	# See ElementOp for similar code.
701	return [expr {([llength $args] == 3) &&
702		      [diagram::point is [lindex $args 0]] &&
703		      ([lindex $args 1] in {+ - |}) &&
704		      [diagram::point is [lindex $args 2]]}]
705    }
706
707    method PointArithOp {pa op pb} {
708	#puts PointArithOp|$pa|$op|$pb|=|[diagram::point $op $pa $pb]|
709	return [diagram::point $op $pa $pb]
710    }
711
712    method Intersect {ea eb} {
713	set pas [diagram::point unbox [$elm corner $ea start]]
714	set pae [diagram::point unbox [$elm corner $ea end]]
715	set pbs [diagram::point unbox [$elm corner $eb start]]
716	set pbe [diagram::point unbox [$elm corner $eb end]]
717
718	#puts |$pas|---|$pae|
719	#puts |$pbs|---|$pbe|
720
721	set linea [list {*}$pas {*}$pae]
722	set lineb [list {*}$pbs {*}$pbe]
723
724	set p [geo::findLineIntersection $linea $lineb]
725	#puts |$p|
726
727	if {$p eq "none"} {
728	    return -code error "Intersection failure, parallel lines have none"
729	} elseif {$p eq "coincident"} {
730	    return -code error "Intersection failure, unable to choose among infinite set of points of coincident lines"
731	}
732
733	return [diagram::point at {*}$p]
734    }
735
736    # # ## ### ##### ######## ############# ######################
737
738    # syntax: [<element> ?<corner>...? ?names ?<pattern>??] --> <point>|<element>|...
739    proc IsElementOp {args} {
740	#puts IsElementOp|$args|[llength $args]|[diagram::element is [lindex $args 0]]
741	return [expr {([llength $args] > 1) &&
742		      [diagram::element is [lindex $args 0]]}]
743    }
744
745    method ElementOp {eid args} {
746	#puts Element|$eid|$corner|=|[$elm corner $eid $corner]|
747	#array set c [$elm corners $eid];parray c
748
749	# See IsPointArithOp guard for similar code.
750	if {([llength $args] == 2) &&
751	    ([lindex $args 0] in {+ - |}) &&
752	    [diagram::point is [lindex $args 1]]} {
753
754	    # Point arithmetic on an element is based in the
755	    # element's center. Resolve and divert.
756	    lassign $args op p
757	    return [$self PointArithOp [$elm corner $eid center] $op $p]
758	}
759
760	set stop 0
761	foreach operation $args next [lrange $args 1 end] {
762	    if {$stop} {
763		if {$stop == 2} { incr stop -1 ; continue }
764		return -code error "wrong#args: should be \"?corner...? ?names ?pattern??\""
765	    }
766	    if {$operation eq "names"} {
767		if {$next eq {}} { set next * }
768		set eid [$elm names $eid $next]
769		set stop 2
770		# stop => error out if there is an argument after next
771	    } else {
772		set eid [$elm corner $eid $operation]
773	    }
774	}
775	return $eid
776    }
777
778    # # ## ### ##### ######## ############# ######################
779
780    method DerefElement {p} {
781	# Convert element references to the elements' center point.
782	# Used when processing the attributes 'from', 'to', 'then',
783	# and 'at'.
784
785	if {[diagram::element is $p]} {
786	    return [dict get [$elm corners $p] center]
787	} else {
788	    return $p
789	}
790    }
791
792    # # ## ### ##### ######## ############# ######################
793
794    method {Placement init} {}             {} ; # Nothing to
795    # initialize
796    method {Placement set}  {key newvalue} {} ; # in the language
797    # namespace, nor to
798    # set.
799    method {Placement fill} {av} {
800	upvar 1 $av attributes
801
802	if {[dict exists $attributes .withat]} return
803	dict set attributes .withat .
804
805	# at/with - rules
806	#
807
808	# (1) If the user did not specify 'at', nor 'with', then both
809	#     are filled with the information from the layout engine.
810	#
811	# (2) If 'with' was specified, but not 'at', then 'at' is
812	#     filled from the layout engine.
813	#
814	# (3) If 'at' was specified, but not 'with' then 'with'
815	#     defaults to the 'center', and the layout engine is
816	#     ignored.
817	#
818	# (4) If both have been specified, then nothing is done.
819	#
820	# (5) The data for 'at' is an untagged absolute location.
821	#     A user specified value is a diagram::point/delta.
822	#     This is resolved as well.
823
824	if {![dict exists $attributes at]} {
825	    dict set attributes at [$nav at] ; # (1,2)
826	    if {[dict exists $attributes with]} return
827	    dict set attributes with [$nav corner] ; # (1)
828	} else {
829	    # (5) User specified location. Resolve to untagged
830	    #     absolute location.
831	    dict set attributes at \
832		[diagram::point resolve \
833		     [$nav at] [dict get $attributes at]]
834
835	    if {![dict exists $attributes with]} {
836		dict set attributes with center ; # (3)
837	    } ; # else (4)
838	}
839	return
840    }
841
842    # # ## ### ##### ######## ############# ######################
843
844    method {Waypoints init} {}             {}  ; # Nothing to
845    # initialize
846    method {Waypoints set}  {key newvalue} {}  ; # in the language
847    # namespace, nor to
848    # set.
849    method {Waypoints fill} {av} {
850	upvar 1 $av attributes
851
852	# from/then/to - rules
853	# Bail out quickly when done already.
854	if {[dict exists $attributes waypoints]} return
855
856	# Determine a starting point if not specified, and/or make a
857	# relative specification absolute.
858
859	set awaypoints {}
860	set last [$nav at] ; # absolute location, untagged.
861
862	if {[dict exists $attributes from]} {
863	    set last [diagram::point resolve $last [dict get $attributes from]]
864	}
865
866	dict set attributes from $last
867	lappend waypoints $last
868
869	if {[dict exists $attributes then]} {
870	    #puts |then|[dict get $attributes then]|
871	    foreach p [dict get $attributes then] {
872		#puts \t|$p|
873		set last [diagram::point resolve $last $p]
874		lappend waypoints $last
875	    }
876	}
877
878	if {![dict exists $attributes to]} {
879	    # Use a default if and only if no intermediate waypoints
880	    # have been specified. For if they have, then the last of
881	    # the intermediates will serve as the 'to'.
882
883	    if {![dict exists $attributes then]} {
884		# Compute a location based on direction and defaults
885
886		set distance [$self state get movelength]
887		set angle    [$dir get [$nav direction] angle]
888		set delta    [diagram::point by $distance $angle]
889		set last     [diagram::point resolve $last $delta]
890		lappend waypoints $last
891	    }
892	} else {
893	    set last [diagram::point resolve $last [dict get $attributes to]]
894	    lappend waypoints $last
895	}
896
897	dict set attributes waypoints $waypoints
898	dict set attributes to        $last
899
900	# If chop values have been specified then now is the time to
901	# process their effect on the waypoints.
902
903	if {[dict exists $attributes chop]} {
904	    set choplist [dict get $attributes chop]
905	    if {[llength $choplist] > 2} {
906		set choplist [lrange $choplist end-1 end]
907	    } elseif {[llength $choplist] < 2} {
908		lappend choplist [lindex $choplist end]
909	    }
910
911	    #puts w|||$waypoints|||
912	    #puts c|||$choplist|||
913
914	    lassign $choplist chopstart chopend
915
916	    # We have to handle multi-segment lines. First we chop
917	    # whole segments until the length to chop is less than the
918	    # length of the current first/last segment. Note that we
919	    # may be left with an empty path.
920
921	    while {[llength $waypoints] >= 2} {
922		lassign $waypoints pa pb
923		set seglen [geo::distance $pa $pb]
924		if {$seglen > $chopstart} break
925		set waypoints [lrange $waypoints 1 end]
926		set chopstart [expr {$chopstart - $seglen}]
927	    }
928	    while {[llength $waypoints] >= 2} {
929		lassign [lrange $waypoints end-1 end] pa pb
930		set seglen [geo::distance $pa $pb]
931		if {$seglen > $chopend} break
932		set waypoints [lrange $waypoints 0 end-1]
933		set chopend [expr {$chopend - $seglen}]
934	    }
935
936	    #puts w'|||$waypoints|||
937	    #puts c'|||$choplist|||
938
939	    if {[llength $waypoints] > 2} {
940		# Ok, we have enough segments left, now actually chop
941		# the first and last segments.
942
943		# Relative chop positions, translated to actual
944		# position through interpolation.
945		lassign $waypoints pa pb
946		set s [expr {double($chopstart)/$seglen}]
947		set anew [geo::between $pa $pb $s]
948
949		lassign [lrange $waypoints end-1 end] a b
950		set s [expr {1-double($chopend)/$seglen}]
951		set bnew [geo::between $pa $pb $s]
952
953		set waypoints [lreplace [lreplace $waypoints 0 0 $anew] end end $bnew]
954
955	    } elseif {[llength $waypoints] == 2} {
956		# There is only one segment left in the
957		# poly-line. Check that chopping the ends doesn't
958		# leave it empty.
959
960		lassign $waypoints pa pb
961		set seglen [geo::distance $pa $pb]
962		if {($chopstart + $chopend) > $seglen} {
963		    set waypoints {}
964		} else {
965		    # Relative chop positions.
966		    set ss [expr {double($chopstart)/$seglen}]
967		    set se [expr {1-double($chopend)/$seglen}]
968
969		    #puts s|$ss
970		    #puts e|$se
971
972		    # Translate to actual position through interpolation.
973		    set anew [geo::between $pa $pb $ss]
974		    set bnew [geo::between $pa $pb $se]
975
976		    set waypoints [list $anew $bnew]
977		}
978	    } else {
979		set waypoints {}
980	    }
981
982	    dict set attributes waypoints $waypoints
983	    dict set attributes from      [lindex $waypoints 0]
984	    dict set attributes to        [lindex $waypoints end]
985	}
986
987	# Note: Keeping from, and to. direct access to these points
988	# could be beneficial.
989
990	#puts WP
991	#puts ______________________________________________________
992	#array set a $attributes ; parray a
993	#puts ______________________________________________________
994
995	return
996    }
997
998    method GetPoints {words} {
999	set p [$self ProcessPoints $words newdirection]
1000	if {$newdirection ne {}} {
1001	    $nav turn $newdirection
1002	}
1003	return $p
1004    }
1005
1006    method ProcessPoints {words nv} {
1007	upvar 1 $nv newdirection
1008	set newdirection {}
1009
1010	# words = P ... !P
1011	# P = <point>
1012	#   | <directionname> <double>
1013	#   | <directionname>
1014
1015	if {![{*}$words size]} {
1016	    return -code error "wrong\#args, expected a point"
1017	}
1018
1019	set p [{*}$words peek]
1020	if {[diagram::point is $p]} {
1021	    # Got an immediate location (absolute or relative). As we
1022	    # expect only one of such we stop processing input and
1023	    # return.
1024
1025	    {*}$words get
1026	    return $p
1027	}
1028
1029	# Not a proper location. Check if we have a series
1030	# of <direction> ?<distance>? words.
1031
1032	set point [diagram::point delta 0 0]
1033	set resok 0
1034
1035	while {[{*}$words size]} {
1036
1037	    set p [{*}$words peek]
1038	    if {![$dir isStrict $p]} {
1039		# Not a direction. If we had delta specs before then
1040		# we just have found the end and can stop processing.
1041		# Otherwise there was no spec at at all, which is an
1042		# error.
1043		break
1044	    }
1045
1046	    set direction [$dir validate $p]
1047
1048	    # We have a direction, check if there is a distance coming
1049	    # after, then add to the sum of previous deltas,
1050	    # i.e. integrate the path.
1051
1052	    {*}$words get
1053	    if {[{*}$words size] && [string is double -strict [{*}$words peek]]} {
1054		set distance [{*}$words get]
1055	    } else {
1056		set distance [$self state get movelength]
1057	    }
1058
1059	    set angle [$dir get $direction angle]
1060	    set v     [diagram::point by $distance $angle]
1061	    set point [diagram::point + $point $v]
1062	    set resok 1
1063
1064	    # Keep track of the last direction used. When we are done
1065	    # the caller will push this to the layout engine, so that
1066	    # it tracks turns specified in the attributes of an
1067	    # element.
1068
1069	    set newdirection $direction
1070	}
1071
1072	if {$resok} {
1073	    return $point
1074	} else {
1075	    return -code error "Expected point/delta specification, got \"$p\""
1076	}
1077    }
1078
1079    # # ## ### ##### ######## ############# ######################
1080    ## Instance data, database tables as arrays, keyed by direction
1081    ## and alias names.
1082
1083    variable mycanvas  {} ; # Drawing backend
1084    variable mylangns  {} ; # Name of the namespace holding the drawing state.
1085
1086    variable myinblock 0  ; # Boolean flag. True when processing a block.
1087    variable mycorners {} ; # Corner dictionary during block processing.
1088
1089    component dir        ; # Knowledge base of named directions.
1090    component elm        ; # Database of drawn elements.
1091    component nav        ; # State of automatic layout engine
1092    component att        ; # Database of attributes
1093
1094    typevariable ourlang LANG
1095
1096    typevariable ourunit -array {} ; # database for unit conversion
1097
1098    typeconstructor {
1099	# [tk scaling] is in pixels/point, with point defined as 1/72 inch
1100	foreach {unit s} {
1101	    mm    2.83464566929
1102	    cm    28.3464566929
1103	    inch  72
1104	    point 1
1105	} {
1106	    set ourunit($unit) [expr {$s * [tk scaling]}]
1107	}
1108    }
1109
1110    ##
1111    # # ## ### ##### ######## ############# ######################
1112}
1113
1114# # ## ### ##### ######## ############# ######################
1115## Ready
1116
1117namespace eval ::diagram::core::geo {
1118    namespace import ::math::geometry::*
1119}
1120
1121package provide diagram::core 1
1122