// GEDSPEC.SR	Paragraph spec editing

get "BRAVO.DF"
get "CHAR.DF"
get "GINN.DF"

// Incoming procedures

external
	[
	getint
	binsearcha
	getvch
	putvch
	mapscrcp
	errhlt
	stcopy
	stnum
	stappend
	stsize
	insertstring
	deletea
	inserta
	move
	movec
	enww
	min
	max
	readsel
	stcompare
	cpadjust2
	cppara
	parabounds
	cpparabounds
	paracp
	paraspec
	gotparaspec
	ckspecs
	parsespec
	unparsespec
	compatible
	replacespec
	nulltrailer
	uadjust
	paradetails
	specdetails
	cpc
	makespec
	]

// Incoming statics

external
	[
	rgdlfirst
	rgdllast
	rgmaxdl
	macww
	rgdoc
	vdoc
	vcp
	vchremain
	rgmaccp
	rgcpfdispl
	rgcplast
	vxleftmarg
	vxrightmarg
	cpscrt
	vdlhint
	vpara
	rgpara
	vlookmod
	vlookbold
	vlookctrl
	vlookjust
	vlookul
	currentspec
	otherspec
	mphd
	rgdirty
	vdeltacp
	]

// Outgoing procedures

external
	[
	subspec
	deletesubspec
	insertsubspec
	mergespecs
	dupspec
	compactspec
	]

// Outgoing statics

// Local statics

let subspec(doc, para, cp1, cp2) = valof
[
// Make a spec for span cp1:cp2 in doc's para
// The span should not include the trailer of the paragraph
// First convert cp's to relative
// Find the spec entries that correspond
// Make the new spec with just enough room + 2
// Copy the paragraph-wide format information
// Copy the format changes, adjusting relative cp's accordingly
// The new spec is dirty and has a null trailer
// The text is at least a carriage return
// The new spec is returned

let spec,siz,looks,changes,rcp1,r1,rcp2,r2 = nil,nil,nil,nil,nil,nil,nil,nil
paradetails(doc, para, lv spec, cp1, cp2)
if rcp2 ls rcp1 then r2 = r1 - 1
let newsiz = r2+1-r1 + 2
let newspec = makespec(newsiz+2)
paradetails(doc, para, lv spec)
let newlooks, newchanges = nil,nil
initspec(newspec, newsiz, spec, lv newlooks, lv newchanges)
copychanges(newsiz-2,newlooks,newchanges,0,looks,changes,r1,-rcp1)
newchanges ! 0 = 0
let tsiz = max(cp2+1-cp1, 0)
newchanges ! (newsiz-2) = tsiz
newchanges ! (newsiz-1) = tsiz + 2
resultis newspec
]

and mergespecs(doc1, para1, doc2, para2) = valof
[
// Merge two paragraphs

currentspec = paraspec(doc1, para1)
otherspec = paraspec(doc2, para2)
resultis mergetwospecs(false)
]

and deletesubspec(doc, para, cp1, cp2) be
[
// Remove spec information about span cp1:cp2 in doc's para
// The span should not include the terminal cr of the paragraph

let tex, b, e = nil, nil, nil
parabounds(doc, para, lv tex, lv b, lv e)
replacespec(doc, para, mergesubspecs(doc, tex, cp1-1, doc, cp2+1, b-1))
]

and insertsubspec(docdest, cpdest, doc, cp1, cp2) be
[
// Insert doc(cp1:cp2) format into docdest spec at cp
// cp1 and cp2 are both in para

let tex, b, e = nil, nil, nil
let paradest = cpparabounds(docdest, cpdest, lv tex, lv b, lv e)
replacespec(docdest, paradest,
	mergesubspecs(	docdest, tex, cpdest-1,
			doc, cp1, cp2,
			docdest, cpdest, b-1 ))
]

and mergesubspecs(doc, cp1, cp2, bdoc, bcp1, bcp2, cdoc, ccp1, ccp2;
	numargs N) = valof
[
// Merge N subparagraphs into a new paragraph
// statics are used because they are updated by enphp

let spec = subspec(doc, cppara(doc, cp1), cp1, cp2)
for i = 3 to N-3 by 3 do
	[
	let otherdoc = (lv doc)!i
	let othercp1 = (lv cp1)!i
	let othercp2 = (lv cp2)!i
	if cpc(othercp1, othercp2) gr 0 then loop
	currentspec = spec
	otherspec = subspec(otherdoc, cppara(otherdoc, othercp1),
		othercp1, othercp2)
	spec = mergetwospecs(true)
	]
resultis spec
]

and mergetwospecs(release) = valof
[
// Make a spec that combines currentspec with otherspec
// First make the new spec with just enough room + 2
// Copy the paragraph-wide format information from the 1st paragraph
// Copy the format changes, adjusting relative cp's accordingly
// Compact the new spec
// The new spec is dirty and has a null trailer

let siz1,looks1,changes1 = nil,nil,nil
specdetails(currentspec, lv siz1)
let siz2,looks2,changes2 = nil,nil,nil
specdetails(otherspec, lv siz2)
let len1 = changes1 ! (siz1-2)
let newsiz = siz1 + siz2 - 2
let newspec = makespec(newsiz+2)
specdetails(currentspec, lv siz1)
specdetails(otherspec, lv siz2)
let newlooks, newchanges = nil, nil
initspec(newspec, newsiz, currentspec, lv newlooks, lv newchanges)
copychanges(siz1-2, newlooks,newchanges,0, looks1,changes1,0, 0)
copychanges(siz2-1, newlooks,newchanges,siz1-2, looks2,changes2,0, len1)
newchanges ! (newsiz-1) = newchanges ! (newsiz-2) + 2
compactspec(newspec)
if release then
	[
	currentspec >> SPEC.doc = abandon
	otherspec >> SPEC.doc = abandon
	]
currentspec = 0
otherspec = 0
resultis newspec
]

and initspec(spec, siz, oldspec, plooks, pchanges) be
[
// Fill in the paragraph-wide info fields of a new spec similar to an old

spec >> SPEC.dirty = true
spec >> SPEC.trailerlength = 2
spec >> SPEC.siz = siz
move(oldspec+firstformat, spec+firstformat, formatl)
@plooks = spec + specbase
@pchanges = @plooks + spec >> LIST.max
]

and copychanges(hmany,
	looks, changes, r1,
	oldlooks, oldchanges, oldr,
	delta) be
[
// Copy intra-paragraph format changes from an old to a new place
// Relative cp's must be adjusted by delta

let inc = looks eq oldlooks & r1 gr oldr? -1, 1
let i = inc gr 0? 0, hmany-1
for j = 1 to hmany do
	[
	looks ! (r1+i) = oldlooks ! (oldr+i)
	changes ! (r1+i) = max(0, oldchanges ! (oldr+i) + delta)
	i = i + inc
	]
]

and compactspec(spec) be
[
let siz, looks, changes = nil, nil, nil
specdetails(spec, lv siz)
let dest = 0
let src = 0
let hi = changes ! (siz-2)
	[
	if changes ! src ge hi then break
	while changes ! src eq changes ! (src+1) do src = src + 1
	if dest eq 0 % looks ! src ne looks ! (dest-1) then
		[
		looks ! dest = looks ! src
		changes ! dest = changes ! src
		dest = dest + 1
		]
	src = src + 1
	] repeat
changes ! dest = hi
looks ! dest = 0
dest = dest + 1
changes ! dest = changes ! (siz-1)
looks ! dest = 0
spec >> SPEC.siz = dest+1
spec >> SPEC.dirty = true
]

and dupspec(doc, para) = valof
[
let spec = gotparaspec(doc, para)
if not spec % spec << odd then resultis spec
let lim = spec >> SPEC.max
let newspec = makespec(lim)
let spec = gotparaspec(doc, para)
let link = newspec >> SPEC.link
move(spec, newspec, 2*lim + specbase)
newspec >> SPEC.link = link
resultis newspec
]