1 /* -*- mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: t -*- */
2 /* AbiWord
3  * Copyright (C) 1998 AbiSource, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301 USA.
19  */
20 
21 
22 // changeSpanFmt-related fuctions for class pt_PieceTable
23 
24 #include "ut_types.h"
25 #include "ut_misc.h"
26 #include "ut_assert.h"
27 #include "ut_debugmsg.h"
28 #include "ut_growbuf.h"
29 #include "pt_PieceTable.h"
30 #include "pf_Frag.h"
31 #include "pf_Frag_Object.h"
32 #include "pf_Frag_Strux.h"
33 #include "pf_Frag_Strux_Block.h"
34 #include "pf_Frag_Strux_Section.h"
35 #include "pf_Frag_Text.h"
36 #include "pf_Frag_FmtMark.h"
37 #include "pf_Fragments.h"
38 #include "px_ChangeRecord.h"
39 #include "px_CR_Span.h"
40 #include "px_CR_SpanChange.h"
41 #include "px_CR_Strux.h"
42 #include "pd_Style.h"
43 #include "pp_Revision.h"
44 
45 #define SETP(p,v)	do { if (p) (*(p)) = (v); } while (0)
46 
47 /****************************************************************/
48 /****************************************************************/
49 
changeSpanFmt(PTChangeFmt ptc,PT_DocPosition dpos1,PT_DocPosition dpos2,const gchar ** attributes,const gchar ** properties)50 bool pt_PieceTable::changeSpanFmt(PTChangeFmt ptc,
51 									 PT_DocPosition dpos1,
52 									 PT_DocPosition dpos2,
53 									 const gchar ** attributes,
54 								  const gchar ** properties)
55 {
56 	// if dpos1 == dpos2 we are inserting a fmt mark; this must be chanelled throught
57 	// the non-revision branch ...
58 	if(m_pDocument->isMarkRevisions() && dpos1 != dpos2)
59 	{
60 		const gchar name[] = "revision";
61 		const gchar * pRevision = NULL;
62 
63 		// we cannot retrieve the start and end fragments here and
64 		// then work between them in a loop using getNext() because
65 		// processing might result in merging of fargments. so we have
66 		// to use the doc position to keep track of where we are and
67 		// retrieve the fragments afresh in each step of the loop
68 		// Tomas, Dec 29, 2004
69 
70 		bool bRet = false;
71 		while(dpos1 < dpos2)
72 		{
73 			// first retrive the starting and ending fragments
74 			pf_Frag * pf1, * pf2;
75 			PT_BlockOffset Offset1, Offset2;
76 
77 			if(!getFragsFromPositions(dpos1,dpos2, &pf1, &Offset1, &pf2, &Offset2) ||
78 			   pf1->getType() == pf_Frag::PFT_EndOfDoc)
79 				return bRet;
80 			else
81 				bRet = true;
82 
83 			// get attributes for this fragement
84 			const PP_AttrProp * pAP;
85 			pRevision = NULL;
86 
87 			if(_getSpanAttrPropHelper(pf1, &pAP))
88 			{
89 				pAP->getAttribute(name, pRevision);
90 			}
91 
92 			PP_RevisionAttr Revisions(pRevision);
93 
94 
95 			// if the request is for removal of fmt, in the revision mode, we still
96 			// have to add these props (the removal is indicated by their emptiness)
97 			// as we cannot rely on callers to set these correctly, we have to emtpy
98 			// them ourselves
99 			const gchar ** attrs = attributes;
100 			const gchar ** props = properties;
101 
102 			if(ptc == PTC_RemoveFmt)
103 			{
104 				attrs = UT_setPropsToNothing(attributes);
105 				props = UT_setPropsToNothing(properties);
106 			}
107 
108 			Revisions.addRevision(m_pDocument->getRevisionId(),PP_REVISION_FMT_CHANGE,attrs,props);
109 
110 			if(attrs != attributes)
111 				delete[] attrs;
112 
113 			if(props != properties)
114 				delete[] props;
115 
116 			const gchar * ppRevAttrib[3];
117 			ppRevAttrib[0] = name;
118 			ppRevAttrib[1] = Revisions.getXMLstring();
119 			ppRevAttrib[2] = NULL;
120 
121 			PT_DocPosition dposEnd = UT_MIN(dpos2,dpos1 + pf1->getLength());
122 
123 			if(!_realChangeSpanFmt(PTC_AddFmt, dpos1, dposEnd, ppRevAttrib,NULL, false))
124 				return false;
125 
126 			dpos1 = dposEnd;
127 		}
128 
129 		return true;
130 	}
131 	else
132 	{
133 		return _realChangeSpanFmt(ptc, dpos1, dpos2, attributes, properties, false);
134 	}
135 }
136 
_fmtChangeSpan(pf_Frag_Text * pft,UT_uint32 fragOffset,UT_uint32 length,PT_AttrPropIndex indexNewAP,pf_Frag ** ppfNewEnd,UT_uint32 * pfragOffsetNewEnd)137 bool pt_PieceTable::_fmtChangeSpan(pf_Frag_Text * pft, UT_uint32 fragOffset, UT_uint32 length,
138 									  PT_AttrPropIndex indexNewAP,
139 									  pf_Frag ** ppfNewEnd, UT_uint32 * pfragOffsetNewEnd)
140 {
141 	UT_return_val_if_fail (length > 0,false);
142 	UT_return_val_if_fail (fragOffset+length <= pft->getLength(), false);
143 
144 	// insert a format change within this text fragment.
145 
146 	// TODO for each place in this function where we apply a change
147 	// TODO see if the new fragment could be coalesced with something
148 	// TODO already in the fragment list.
149 
150 	if ((fragOffset == 0) && (length == pft->getLength()))
151 	{
152 		// we have an exact match (we are changing the entire fragment).
153 
154 		// try to coalesce this modified fragment with one of its neighbors.
155 		// first we try the pft->next -- if that works, we can stop because
156 		// the unlink will take care of checking pft->next with pft->prev.
157 		// if it doesn't work, we then try pft->prev.
158 
159 		pf_Frag * pfNext = pft->getNext();
160 		if (pfNext && pfNext->getType()==pf_Frag::PFT_Text)
161 		{
162 			pf_Frag_Text * pftNext = static_cast<pf_Frag_Text *>(pfNext);
163 			if (   (pftNext->getIndexAP() == indexNewAP)
164 				&& (m_varset.isContiguous(pft->getBufIndex(),length,pftNext->getBufIndex())))
165 			{
166 				// the pft given and the pft->next can be coalesced.
167 				// let's donate all of our document data to pft->next,
168 				// set pft to be empty and then let _unlinkFrag() take
169 				// care of all the other details (like checking to see
170 				// if pft->next and pft->prev can be coalesced after pft
171 				// is out of the way....)
172 
173 				pftNext->adjustOffsetLength(pft->getBufIndex(),length+pftNext->getLength());
174 				// we could do a: pft->changeLength(0); but it causes an assert and
175 				// besides _unlinkFrag() doesn't look at it and we're going to delete it.
176 				_unlinkFrag(pft,ppfNewEnd,pfragOffsetNewEnd);
177 				delete pft;
178 				return true;
179 			}
180 		}
181 
182 		pf_Frag * pfPrev = pft->getPrev();
183 		if (pfPrev && pfPrev->getType()==pf_Frag::PFT_Text)
184 		{
185 			pf_Frag_Text * pftPrev = static_cast<pf_Frag_Text *>(pfPrev);
186 			if (   (pftPrev->getIndexAP() == indexNewAP)
187 				&& (m_varset.isContiguous(pftPrev->getBufIndex(),pftPrev->getLength(),pft->getBufIndex())))
188 			{
189 				// the pft given and the pft->prev can be coalesced.
190 				// let's donate all of our document data to the pft->prev,
191 				// set pft to be empty and then let _unlinkFrag() take
192 				// care of the dirty work.
193 
194 				pftPrev->changeLength(pftPrev->getLength()+length);
195 				// we could do a: pft->changeLength(0); but it causes an assert and
196 				// besides _unlinkFrag() doesn't look at it and we're going to delete it.
197 				_unlinkFrag(pft,ppfNewEnd,pfragOffsetNewEnd);
198 				delete pft;
199 				return true;
200 			}
201 		}
202 
203 		// otherwise, we just overwrite the indexAP on this fragment.
204 
205 		pft->setIndexAP(indexNewAP);
206 		SETP(ppfNewEnd, pft->getNext());
207 		SETP(pfragOffsetNewEnd, 0);
208 
209 		return true;
210 	}
211 
212 	if (fragOffset == 0)
213 	{
214 		// the change is at the beginning of the fragment.
215 		// we need to split the existing fragment into 2 parts
216 		// and apply the new formatting to the new first half.
217 		// before we actually create the new one, we see if we
218 		// can coalesce the first half (with the new formatting)
219 		// with the previous fragment.  if not, then we cut
220 		// the existing fragment into 2 parts.
221 
222 		UT_uint32 len_1 = length;
223 		UT_uint32 len_2 = pft->getLength() - len_1;
224 		PT_BufIndex bi_1 = m_varset.getBufIndex(pft->getBufIndex(),0);
225 		PT_BufIndex bi_2 = m_varset.getBufIndex(pft->getBufIndex(),len_1);
226 
227 		pf_Frag * pfPrev = pft->getPrev();
228 		if (pfPrev && pfPrev->getType()==pf_Frag::PFT_Text)
229 		{
230 			pf_Frag_Text * pftPrev = static_cast<pf_Frag_Text *>(pfPrev);
231 			if (   (pftPrev->getIndexAP() == indexNewAP)
232 				&& (m_varset.isContiguous(pftPrev->getBufIndex(),pftPrev->getLength(),pft->getBufIndex())))
233 			{
234 				// yes we can coalesce.  move the first half into the previous fragment.
235 
236 				pftPrev->changeLength(pftPrev->getLength()+length);
237 				pft->adjustOffsetLength(bi_2,len_2);
238 				SETP(ppfNewEnd, pft);
239 				SETP(pfragOffsetNewEnd, 0);
240 
241 				return true;
242 			}
243 		}
244 
245 		// otherwise, we need to actually split this one....
246 
247 		pf_Frag_Text * pftNew = new pf_Frag_Text(this,bi_1,len_1,indexNewAP,pft->getField());
248 		if (!pftNew)
249 			return false;
250 
251 		pft->adjustOffsetLength(bi_2,len_2);
252 		m_fragments.insertFrag(pft->getPrev(),pftNew);
253 
254 		SETP(ppfNewEnd, pft);
255 		SETP(pfragOffsetNewEnd, 0);
256 
257 		return true;
258 	}
259 
260 	if (fragOffset+length == pft->getLength())
261 	{
262 		// the change is at the end of the fragment, we cut
263 		// the existing fragment into 2 parts and apply the new
264 		// formatting to the new second half.  before we actually
265 		// create the new one, we see if we can coalesce the
266 		// second half (with the new formatting) with the next
267 		// fragment.
268 
269 		UT_uint32 len_1 = fragOffset;
270 		UT_uint32 len_2 = length;
271 		PT_BufIndex bi_2 = m_varset.getBufIndex(pft->getBufIndex(),len_1);
272 
273 		pf_Frag * pfNext = pft->getNext();
274 		if (pfNext && pfNext->getType()==pf_Frag::PFT_Text)
275 		{
276 			pf_Frag_Text * pftNext = static_cast<pf_Frag_Text *>(pfNext);
277 			if (   (pftNext->getIndexAP() == indexNewAP)
278 				&& (m_varset.isContiguous(bi_2,len_2,pftNext->getBufIndex())))
279 			{
280 				// yes we can coalesce.  move the second half into the next fragment.
281 
282 				pftNext->adjustOffsetLength(bi_2,len_2+pftNext->getLength());
283 				pft->changeLength(len_1);
284 				SETP(ppfNewEnd,pftNext);
285 				SETP(pfragOffsetNewEnd,len_2);
286 				return true;
287 			}
288 		}
289 
290 		// otherwise, we actually need to split this one....
291 
292 		pf_Frag_Text * pftNew = new pf_Frag_Text(this,bi_2,len_2,indexNewAP,pft->getField());
293 		if (!pftNew)
294 			return false;
295 
296 		pft->changeLength(len_1);
297 		m_fragments.insertFrag(pft,pftNew);
298 
299 		SETP(ppfNewEnd, pftNew->getNext());
300 		SETP(pfragOffsetNewEnd, 0);
301 
302 		return true;
303 	}
304 
305 	// otherwise, change is in the middle of the fragment.  we
306 	// need to cut the existing fragment into 3 parts and apply
307 	// the new formatting to the middle one.
308 
309 	UT_uint32 len_1 = fragOffset;
310 	UT_uint32 len_2 = length;
311 	UT_uint32 len_3 = pft->getLength() - (fragOffset+length);
312 	PT_BufIndex bi_2 = m_varset.getBufIndex(pft->getBufIndex(),fragOffset);
313 	PT_BufIndex bi_3 = m_varset.getBufIndex(pft->getBufIndex(),fragOffset+length);
314 	pf_Frag_Text * pft_2 = new pf_Frag_Text(this,bi_2,len_2,indexNewAP,pft->getField());
315 	UT_return_val_if_fail (pft_2, false);
316 	pf_Frag_Text * pft_3 = new pf_Frag_Text(this,bi_3,len_3,pft->getIndexAP(),pft->getField());
317 	UT_return_val_if_fail (pft_3, false);
318 
319 	pft->changeLength(len_1);
320 	m_fragments.insertFrag(pft,pft_2);
321 	m_fragments.insertFrag(pft_2,pft_3);
322 
323 	SETP(ppfNewEnd, pft_3);
324 	SETP(pfragOffsetNewEnd, 0);
325 
326 	return true;
327 }
328 
_fmtChangeSpanWithNotify(PTChangeFmt ptc,pf_Frag_Text * pft,UT_uint32 fragOffset,PT_DocPosition dpos,UT_uint32 length,const gchar ** attributes,const gchar ** properties,pf_Frag_Strux * pfs,pf_Frag ** ppfNewEnd,UT_uint32 * pfragOffsetNewEnd,bool bRevisionDelete)329 bool pt_PieceTable::_fmtChangeSpanWithNotify(PTChangeFmt ptc,
330 											 pf_Frag_Text * pft, UT_uint32 fragOffset,
331 											 PT_DocPosition dpos,
332 											 UT_uint32 length,
333 											 const gchar ** attributes,
334 											 const gchar ** properties,
335 											 pf_Frag_Strux * pfs,
336 											 pf_Frag ** ppfNewEnd,
337 											 UT_uint32 * pfragOffsetNewEnd,
338 											 bool bRevisionDelete)
339 {
340 	// create a change record for this change and put it in the history.
341 
342 	if (length == 0)					// TODO decide if this is an error.
343 	{
344 		UT_DEBUGMSG(("_fmtChangeSpanWithNotify: length==0\n"));
345 		SETP(ppfNewEnd, pft->getNext());
346 		SETP(pfragOffsetNewEnd, 0);
347 		return true;
348 	}
349 
350 	UT_return_val_if_fail (fragOffset+length <= pft->getLength(), false);
351 
352 	PT_AttrPropIndex indexNewAP;
353 	PT_AttrPropIndex indexOldAP = pft->getIndexAP();
354 	UT_DebugOnly<bool> bMerged;
355 	if(attributes && properties && (attributes[0] == NULL) && (properties[0] == NULL))
356 	{
357 	    //
358 	    // Clear out all attributes/properties and set to the first index
359 	    //
360 	    bMerged = true;
361 	    indexNewAP = 0;
362 	}
363 	else
364 	  bMerged = m_varset.mergeAP(ptc,indexOldAP,attributes,properties,&indexNewAP,getDocument());
365 
366 	UT_ASSERT_HARMLESS(bMerged);
367 
368 	if (indexOldAP == indexNewAP)		// the requested change will have no effect on this fragment.
369 	{
370 		if (fragOffset+length == pft->getLength())
371 		{
372 			SETP(ppfNewEnd, pft->getNext());
373 			SETP(pfragOffsetNewEnd, 0);
374 		}
375 		else
376 		{
377 			SETP(ppfNewEnd, pft);
378 			SETP(pfragOffsetNewEnd, fragOffset+length);
379 		}
380 
381 		return true;
382 	}
383 
384 	// we do this before the actual change because various fields that
385 	// we need may be blown away during the change.  we then notify all
386 	// listeners of the change.
387 
388 	PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pft) + fragOffset;
389 
390 	PX_ChangeRecord_SpanChange * pcr
391 		= new PX_ChangeRecord_SpanChange(PX_ChangeRecord::PXT_ChangeSpan,
392 										 dpos, indexOldAP,indexNewAP,
393 										 m_varset.getBufIndex(pft->getBufIndex(),fragOffset),
394 										 length,blockOffset,bRevisionDelete);
395 	UT_return_val_if_fail (pcr,false);
396 	bool bResult = _fmtChangeSpan(pft,fragOffset,length,indexNewAP,ppfNewEnd,pfragOffsetNewEnd);
397 
398 	// add record to history.  we do not attempt to coalesce these.
399 	m_history.addChangeRecord(pcr);
400 	m_pDocument->notifyListeners(pfs,pcr);
401 
402 	return bResult;
403 }
404 
_realChangeSpanFmt(PTChangeFmt ptc,PT_DocPosition dpos1,PT_DocPosition dpos2,const gchar ** attributes,const gchar ** properties,bool bRevisionDelete)405 bool pt_PieceTable::_realChangeSpanFmt(PTChangeFmt ptc,
406 									   PT_DocPosition dpos1,
407 									   PT_DocPosition dpos2,
408 									   const gchar ** attributes,
409 									   const gchar ** properties,
410 									   bool bRevisionDelete)
411 {
412 	// apply a span-level formatting change to the given region.
413 
414 	UT_return_val_if_fail (m_pts==PTS_Editing,false);
415     _tweakFieldSpan(dpos1,dpos2);
416 //
417 // Deal with case of exactly selecting the endOfFootnote
418 //
419 	pf_Frag * pfEndDum = m_fragments.findFirstFragBeforePos(dpos2);
420 	if(isEndFootnote(pfEndDum))
421 	{
422 		if(dpos2 > dpos1)
423 		{
424 			dpos2--;
425 		}
426 	}
427 //
428 // Deal with addStyle
429 //
430 	bool bApplyStyle = (PTC_AddStyle == ptc);
431 	const gchar ** sProps = NULL;
432 	const gchar ** lProps = properties;
433 	if(bApplyStyle)
434 	{
435 //
436 // OK for styles we expand out all defined properties including BasedOn styles
437 // Then we use these to eliminate any specfic properties in the current strux
438 // Then properties in the current strux will resolve to those defined in the
439 // style (they exist there) to specifc values in strux (if not overridden by
440 // the style) then finally to default value.
441 //
442 		const gchar * szStyle = UT_getAttribute(PT_STYLE_ATTRIBUTE_NAME,attributes);
443 		PD_Style * pStyle = NULL;
444 		UT_return_val_if_fail (szStyle,false);
445 		getDocument()->getStyle(szStyle,&pStyle);
446 		UT_return_val_if_fail (pStyle,false);
447 		UT_Vector vProps;
448 //
449 // Get the vector of properties
450 //
451 		pStyle->getAllProperties(&vProps,0);
452 //
453 // Finally make the const gchar * array of properties
454 //
455 		UT_uint32 countp = vProps.getItemCount() + 1;
456 		sProps = (const gchar **) UT_calloc(countp, sizeof(gchar *));
457 		countp--;
458 		UT_uint32 i;
459 		for(i=0; i<countp; i++)
460 		{
461 			sProps[i] = (const gchar *) vProps.getNthItem(i);
462 		}
463 		sProps[i] = NULL;
464 		lProps = sProps;
465 	}
466 	if (dpos1 == dpos2) 		// if length of change is zero, then we have a toggle format.
467 	{
468 		UT_uint32 startUndoPos = m_history.getUndoPos();
469 		bool bRes = _insertFmtMarkFragWithNotify(ptc,dpos1,attributes,lProps);
470 		UT_uint32 endUndoPos = m_history.getUndoPos();
471 		// Won't be a persistant change if it's just a toggle
472 		PX_ChangeRecord *pcr=0;
473 		m_history.getUndo(&pcr,true);
474 		if (pcr && (startUndoPos != endUndoPos) )
475 		{
476 			UT_DEBUGMSG(("Setting persistance of change to false\n"));
477 			pcr->setPersistance(false);
478 			m_history.setSavePosition(m_history.getSavePosition()+1);
479 		}
480 		if(bApplyStyle)
481 		{
482 			FREEP(sProps);
483 		}
484 		return bRes;
485 	}
486 
487 	UT_return_val_if_fail (dpos1 < dpos2,false);
488 
489 	pf_Frag * pf_First;
490 	pf_Frag * pf_End;
491 	PT_BlockOffset fragOffset_First;
492 	PT_BlockOffset fragOffset_End;
493 
494 	bool bFound;
495 	bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End);
496 	UT_return_val_if_fail (bFound, false);
497 	bool bSkipFootnote = _checkSkipFootnote(dpos1,dpos2,pf_End);
498 
499 #if 0
500 	{
501 		pf_Frag * pf1, * pf2;
502 		PT_BlockOffset fo1, fo2;
503 
504 		bool bFound1 = getFragFromPosition(dpos1,&pf1,&fo1);
505 		bool bFound2 = getFragFromPosition(dpos2,&pf2,&fo2);
506 		UT_return_val_if_fail (bFound1 && bFound2, false);
507 		UT_return_val_if_fail ((pf1==pf_First) && (fragOffset_First==fo1), false);
508 		UT_return_val_if_fail ((pf2==pf_End) && (fragOffset_End==fo2), false);
509 	}
510 #endif
511 
512 	// see if the amount of text to be changed is completely
513 	// contained within a single fragment.  if so, we have a
514 	// simple change.  otherwise, we need to set up a multi-step
515 	// change -- it may not actually take more than one step,
516 	// but it is too complicated to tell at this point, so we
517 	// assume it will and don't worry about it.
518 	//
519 	// we are in a simple change if the beginning and end are
520 	// within the same fragment.
521 
522 	// NOTE: if we call beginMultiStepGlob() we ***MUST*** call
523 	// NOTE: endMultiStepGlob() before we return -- otherwise,
524 	// NOTE: the undo/redo won't be properly bracketed.
525 
526 	bool bSimple = (pf_First == pf_End);
527 	if (!bSimple)
528 		beginMultiStepGlob();
529     // UT_DEBUGMSG(("ODTCT: realChangeSpanFmt() bSimple:%d\n", bSimple ));
530 
531 	pf_Frag_Strux * pfsContainer = NULL;
532 	pf_Frag * pfNewEnd;
533 	UT_uint32 fragOffsetNewEnd;
534 
535 	UT_uint32 length = dpos2 - dpos1;
536 	while (length != 0)
537 	{
538 		// FIXME: Special check to support a FmtMark at the end of the
539 		// document. This is necessary because FmtMarks don't have a
540 		// length...  See bug 452.
541 		if (0 == length
542 			&& (!pf_First || pf_Frag::PFT_FmtMark != pf_First->getType()))
543 			break;
544 
545 		UT_return_val_if_fail (dpos1+length==dpos2, false);
546 
547 		UT_uint32 lengthInFrag = pf_First->getLength() - fragOffset_First;
548 		UT_uint32 lengthThisStep = UT_MIN(lengthInFrag, length);
549 
550 		switch (pf_First->getType())
551 		{
552 		case pf_Frag::PFT_EndOfDoc:
553 		default:
554 			UT_DEBUGMSG(("fragment type: %d\n",pf_First->getType()));
555 			UT_ASSERT_HARMLESS(0);
556 			if(bApplyStyle)
557 			{
558 				FREEP(sProps);
559 			}
560 			return false;
561 
562 		case pf_Frag::PFT_Strux:
563 			{
564 				// we are only applying span-level changes, so we ignore strux.
565 				// but we still need to update our loop indices.
566 				if (bSkipFootnote  && isFootnote(pf_First))
567 				{
568 					UT_uint32 extraLength = 0;
569 					pfNewEnd = pf_First;
570 					while(pfNewEnd && !isEndFootnote(pfNewEnd))
571 					{
572 						pfNewEnd = pfNewEnd->getNext();
573 						extraLength += pfNewEnd->getLength();
574 					}
575 					if(lengthThisStep + extraLength <= length)
576 					{
577 						lengthThisStep += extraLength;
578 					}
579 					else
580 					{
581 						UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
582 						lengthThisStep = length;
583 					}
584 					pfNewEnd = pfNewEnd->getNext();
585 					fragOffsetNewEnd = 0;
586 				}
587 				else
588 				{
589 					pfNewEnd = pf_First->getNext();
590 					pfsContainer = static_cast<pf_Frag_Strux *> (pf_First);
591 					fragOffsetNewEnd = 0;
592 					bool bFoundStrux = false;
593 					if(isEndFootnote(pfsContainer))
594 					{
595 						bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer);
596 						UT_return_val_if_fail (bFoundStrux, false);
597 					}
598 				}
599 			}
600 			break;
601 
602 		case pf_Frag::PFT_Text:
603 			{
604 				if (!pfsContainer)
605 				{
606 					bool bFoundStrux;
607 					bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer);
608 					UT_return_val_if_fail (bFoundStrux,false);
609 					if(isEndFootnote(pfsContainer))
610 					{
611 						bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer);
612 						UT_return_val_if_fail (bFoundStrux,false);
613 					}
614 				}
615 
616                 // UT_DEBUGMSG(("ODTCT: realChangeSpanFmt() text...A\n" ));
617 				bool bResult;
618 				bResult	= _fmtChangeSpanWithNotify(ptc,static_cast<pf_Frag_Text *>(pf_First),
619 											   fragOffset_First,dpos1,lengthThisStep,
620 											   attributes,lProps,
621 											   pfsContainer,&pfNewEnd,&fragOffsetNewEnd,bRevisionDelete);
622                 // UT_DEBUGMSG(("ODTCT: realChangeSpanFmt() text...B\n" ));
623 				UT_return_val_if_fail (bResult,false);
624 			}
625 			break;
626 
627 		case pf_Frag::PFT_Object:
628 			{
629 				if (!pfsContainer)
630 				{
631 					bool bFoundStrux;
632 					bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer);
633 					UT_return_val_if_fail (bFoundStrux,false);
634 					if(isEndFootnote(pfsContainer))
635 					{
636 						bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer);
637 						UT_return_val_if_fail (bFoundStrux,false);
638 					}
639 				}
640 
641 				bool bResult;
642 				bResult	= _fmtChangeObjectWithNotify(ptc,static_cast<pf_Frag_Object *>(pf_First),
643 												 fragOffset_First,dpos1,lengthThisStep,
644 												 attributes,lProps,
645 												 pfsContainer,&pfNewEnd,&fragOffsetNewEnd,false);
646 				UT_return_val_if_fail (bResult,false);
647 			}
648 			break;
649 
650 		case pf_Frag::PFT_FmtMark:
651 			{
652 				if (!pfsContainer)
653 				{
654 					bool bFoundStrux;
655 					bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer);
656 					UT_return_val_if_fail (bFoundStrux,false);
657 					if(isEndFootnote(pfsContainer))
658 					{
659 						bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer);
660 						UT_return_val_if_fail (bFoundStrux,false);
661 					}
662 
663 				}
664 
665 				bool bResult;
666 				bResult = _fmtChangeFmtMarkWithNotify(ptc,static_cast<pf_Frag_FmtMark *>(pf_First),
667 												  dpos1, attributes,lProps,
668 												  pfsContainer,&pfNewEnd,&fragOffsetNewEnd);
669 				UT_return_val_if_fail (bResult,false);
670 			}
671 			break;
672 
673 		}
674 
675 		dpos1 += lengthThisStep;
676 		length -= lengthThisStep;
677 
678 		// since _fmtChange{Span,FmtMark,...}WithNotify(), can delete pf_First, mess with the
679 		// fragment list, and does some aggressive coalescing of
680 		// fragments, we cannot just do a pf_First->getNext() here.
681 		// to advance to the next fragment, we use the *NewEnd variables
682 		// that each of the cases routines gave us.
683 
684 		pf_First = pfNewEnd;
685 		if (!pf_First)
686 			length = 0;
687 		fragOffset_First = fragOffsetNewEnd;
688 	}
689 	if(bApplyStyle)
690 	{
691 		FREEP(sProps);
692 	}
693 
694 	if (!bSimple)
695 		endMultiStepGlob();
696 
697 	return true;
698 }
699