xref: /AOO41X/main/sw/source/core/access/accportions.cxx (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_sw.hxx"
30 #include "accportions.hxx"
31 #include <tools/debug.hxx>
32 #include <rtl/ustring.hxx>
33 #include <com/sun/star/i18n/Boundary.hpp>
34 #include <txttypes.hxx>
35 
36 // for portion replacement in Special()
37 #ifndef _ACCESS_HRC
38 #include "access.hrc"
39 #endif
40 #include <tools/resid.hxx>
41 #include "viewopt.hxx"
42 
43 // for GetWordBoundary(...), GetSentenceBoundary(...):
44 #include <breakit.hxx>
45 #include <com/sun/star/i18n/WordType.hpp>
46 #include <com/sun/star/i18n/XBreakIterator.hpp>
47 #include <ndtxt.hxx>
48 
49 // for FillSpecialPos(...)
50 #include "crstate.hxx"
51 
52 // for SwAccessibleContext::GetResource()
53 #include "acccontext.hxx"
54 
55 // for Post-It replacement text:
56 #include "txatbase.hxx"
57 #include "fmtfld.hxx"
58 #include "fldbas.hxx"
59 #include "docufld.hxx"
60 
61 // for in-line graphics replacement:
62 #include "ndindex.hxx"
63 #include "ndnotxt.hxx"
64 #include "fmtflcnt.hxx"
65 #include "frmfmt.hxx"
66 #include "fmtcntnt.hxx"
67 
68 
69 using namespace ::com::sun::star;
70 
71 using rtl::OUString;
72 using rtl::OUStringBuffer;
73 using i18n::Boundary;
74 
75 
76 // 'portion type' for terminating portions
77 #define POR_TERMINATE 0
78 
79 
80 // portion attributes
81 #define PORATTR_SPECIAL     1
82 #define PORATTR_READONLY    2
83 #define PORATTR_GRAY        4
84 #define PORATTR_TERM        128
85 
86 SwAccessiblePortionData::SwAccessiblePortionData(
87     const SwTxtNode* pTxtNd,
88     const SwViewOption* pViewOpt ) :
89     SwPortionHandler(),
90     pTxtNode( pTxtNd ),
91     aBuffer(),
92     nModelPosition( 0 ),
93     bFinished( sal_False ),
94     pViewOptions( pViewOpt ),
95     sAccessibleString(),
96     aLineBreaks(),
97     aModelPositions(),
98     aAccessiblePositions(),
99     pSentences( 0 ),
100     nBeforePortions( 0 ),
101     bLastIsSpecial( sal_False )
102 {
103     DBG_ASSERT( pTxtNode != NULL, "Text node is needed!" );
104 
105     // reserve some space to reduce memory allocations
106     aLineBreaks.reserve( 5 );
107     aModelPositions.reserve( 10 );
108     aAccessiblePositions.reserve( 10 );
109 
110     // always include 'first' line-break position
111     aLineBreaks.push_back( 0 );
112 }
113 
114 SwAccessiblePortionData::~SwAccessiblePortionData()
115 {
116     delete pSentences;
117 }
118 
119 void SwAccessiblePortionData::Text(sal_uInt16 nLength, sal_uInt16 nType)
120 {
121     DBG_ASSERT( (nModelPosition + nLength) <= pTxtNode->GetTxt().Len(),
122                 "portion exceeds model string!" );
123 
124     DBG_ASSERT( !bFinished, "We are already done!" );
125 
126     // ignore zero-length portions
127     if( nLength == 0 )
128         return;
129 
130     // store 'old' positions
131     aModelPositions.push_back( nModelPosition );
132     aAccessiblePositions.push_back( aBuffer.getLength() );
133 
134     // store portion attributes
135     sal_uInt8 nAttr = IsGrayPortionType(nType) ? PORATTR_GRAY : 0;
136     aPortionAttrs.push_back( nAttr );
137 
138     // update buffer + nModelPosition
139     aBuffer.append( OUString(
140         pTxtNode->GetTxt().Copy(
141             static_cast<sal_uInt16>( nModelPosition ),
142             nLength ) ) );
143     nModelPosition += nLength;
144 
145     bLastIsSpecial = sal_False;
146 }
147 
148 void SwAccessiblePortionData::Special(
149     sal_uInt16 nLength, const String& rText, sal_uInt16 nType)
150 {
151     DBG_ASSERT( nModelPosition >= 0, "illegal position" );
152     DBG_ASSERT( (nModelPosition + nLength) <= pTxtNode->GetTxt().Len(),
153                 "portion exceeds model string!" );
154 
155     DBG_ASSERT( !bFinished, "We are already done!" );
156 
157     // construct string with representation; either directly from
158     // rText, or use resources for special case portions
159     String sDisplay;
160     switch( nType )
161     {
162         case POR_POSTITS:
163         case POR_FLYCNT:
164         case POR_GRFNUM:
165             sDisplay = String(sal_Unicode(0xfffc));
166 
167             break;
168         case POR_NUMBER:
169         {
170             OUStringBuffer aTmpBuffer( rText.Len() + 1 );
171             aTmpBuffer.append( rText );
172             aTmpBuffer.append( sal_Unicode(' ') );
173             sDisplay = aTmpBuffer.makeStringAndClear();
174             break;
175         }
176         // --> OD 2010-06-04 #i111768# - apply patch from kstribley:
177         // Include the control characters.
178         case POR_CONTROLCHAR:
179         {
180             OUStringBuffer aTmpBuffer( rText.Len() + 1 );
181             aTmpBuffer.append( rText );
182             aTmpBuffer.append( pTxtNode->GetTxt().GetChar(nModelPosition) );
183             sDisplay = aTmpBuffer.makeStringAndClear();
184             break;
185         }
186         // <--
187         default:
188             sDisplay = rText;
189             break;
190     }
191 
192     // ignore zero/zero portions (except for terminators)
193     if( (nLength == 0) && (sDisplay.Len() == 0) && (nType != POR_TERMINATE) )
194         return;
195 
196     // special treatment for zero length portion at the beginning:
197     // count as 'before' portion
198     if( ( nLength == 0 ) && ( nModelPosition == 0 ) )
199         nBeforePortions++;
200 
201     // store the 'old' positions
202     aModelPositions.push_back( nModelPosition );
203     aAccessiblePositions.push_back( aBuffer.getLength() );
204 
205     // store portion attributes
206     sal_uInt8 nAttr = PORATTR_SPECIAL;
207     if( IsGrayPortionType(nType) )      nAttr |= PORATTR_GRAY;
208     if( nLength == 0 )                  nAttr |= PORATTR_READONLY;
209     if( nType == POR_TERMINATE )        nAttr |= PORATTR_TERM;
210     aPortionAttrs.push_back( nAttr );
211 
212     // update buffer + nModelPosition
213     aBuffer.append( OUString(sDisplay) );
214     nModelPosition += nLength;
215 
216     // remember 'last' special portion (unless it's our own 'closing'
217     // portions from 'Finish()'
218     if( nType != POR_TERMINATE )
219         bLastIsSpecial = sal_True;
220 }
221 
222 void SwAccessiblePortionData::LineBreak()
223 {
224     DBG_ASSERT( !bFinished, "We are already done!" );
225 
226     aLineBreaks.push_back( aBuffer.getLength() );
227 }
228 
229 void SwAccessiblePortionData::Skip(sal_uInt16 nLength)
230 {
231     DBG_ASSERT( !bFinished, "We are already done!" );
232     DBG_ASSERT( aModelPositions.size() == 0, "Never Skip() after portions" );
233     DBG_ASSERT( nLength <= pTxtNode->GetTxt().Len(), "skip exceeds model string!" );
234 
235     nModelPosition += nLength;
236 }
237 
238 void SwAccessiblePortionData::Finish()
239 {
240     DBG_ASSERT( !bFinished, "We are already done!" );
241 
242     // include terminator values: always include two 'last character'
243     // markers in the position arrays to make sure we always find one
244     // position before the end
245     Special( 0, String(), POR_TERMINATE );
246     Special( 0, String(), POR_TERMINATE );
247     LineBreak();
248     LineBreak();
249 
250     sAccessibleString = aBuffer.makeStringAndClear();
251     bFinished = sal_True;
252 }
253 
254 
255 sal_Bool SwAccessiblePortionData::IsPortionAttrSet(
256     size_t nPortionNo, sal_uInt8 nAttr ) const
257 {
258     DBG_ASSERT( nPortionNo < aPortionAttrs.size(),
259                 "Illegal portion number" );
260     return (aPortionAttrs[nPortionNo] & nAttr) != 0;
261 }
262 
263 sal_Bool SwAccessiblePortionData::IsSpecialPortion( size_t nPortionNo ) const
264 {
265     return IsPortionAttrSet(nPortionNo, PORATTR_SPECIAL);
266 }
267 
268 sal_Bool SwAccessiblePortionData::IsReadOnlyPortion( size_t nPortionNo ) const
269 {
270     return IsPortionAttrSet(nPortionNo, PORATTR_READONLY);
271 }
272 
273 sal_Bool SwAccessiblePortionData::IsGrayPortionType( sal_uInt16 nType ) const
274 {
275     // gray portions?
276     // Compare with: inftxt.cxx, SwTxtPaintInfo::DrawViewOpt(...)
277     sal_Bool bGray = sal_False;
278     switch( nType )
279     {
280         case POR_FTN:
281         case POR_ISOREF:
282         case POR_REF:
283         case POR_QUOVADIS:
284         case POR_NUMBER:
285         case POR_FLD:
286         case POR_URL:
287         case POR_ISOTOX:
288         case POR_TOX:
289         case POR_HIDDEN:
290             bGray = !pViewOptions->IsPagePreview() &&
291                 !pViewOptions->IsReadonly() && SwViewOption::IsFieldShadings();
292         break;
293         case POR_TAB:       bGray = pViewOptions->IsTab();          break;
294         case POR_SOFTHYPH:  bGray = pViewOptions->IsSoftHyph();     break;
295         case POR_BLANK:     bGray = pViewOptions->IsHardBlank();    break;
296         default:
297             break; // bGray is false
298     }
299     return bGray;
300 }
301 
302 
303 const OUString& SwAccessiblePortionData::GetAccessibleString() const
304 {
305     DBG_ASSERT( bFinished, "Shouldn't call this before we are done!" );
306 
307     return sAccessibleString;
308 }
309 
310 
311 void SwAccessiblePortionData::GetLineBoundary(
312     Boundary& rBound,
313     sal_Int32 nPos ) const
314 {
315     FillBoundary( rBound, aLineBreaks,
316                   FindBreak( aLineBreaks, nPos ) );
317 }
318 
319 // --> OD 2008-05-30 #i89175#
320 sal_Int32 SwAccessiblePortionData::GetLineCount() const
321 {
322     size_t nBreaks = aLineBreaks.size();
323     // A non-empty paragraph has at least 4 breaks: one for each line3 and
324     // 3 additional ones.
325     // An empty paragraph has 3 breaks.
326     // Less than 3 breaks is an error case.
327     sal_Int32 nLineCount = ( nBreaks > 3 )
328                            ? nBreaks - 3
329                            : ( ( nBreaks == 3 ) ? 1 : 0 );
330     return nLineCount;
331 }
332 
333 sal_Int32 SwAccessiblePortionData::GetLineNo( const sal_Int32 nPos ) const
334 {
335     sal_Int32 nLineNo = FindBreak( aLineBreaks, nPos );
336 
337     // handling of position after last character
338     const sal_Int32 nLineCount( GetLineCount() );
339     if ( nLineNo >= nLineCount )
340     {
341         nLineNo = nLineCount - 1;
342     }
343 
344     return nLineNo;
345 }
346 
347 void SwAccessiblePortionData::GetBoundaryOfLine( const sal_Int32 nLineNo,
348                                                  i18n::Boundary& rLineBound )
349 {
350     FillBoundary( rLineBound, aLineBreaks, nLineNo );
351 }
352 // <--
353 
354 void SwAccessiblePortionData::GetLastLineBoundary(
355     Boundary& rBound ) const
356 {
357     DBG_ASSERT( aLineBreaks.size() >= 2, "need min + max value" );
358 
359 	// The last two positions except the two deleimiters are the ones
360 	// we are looking for, except for empty paragraphs (nBreaks==3)
361 	size_t nBreaks = aLineBreaks.size();
362     FillBoundary( rBound, aLineBreaks, nBreaks <= 3 ? 0 : nBreaks-4 );
363 }
364 
365 sal_uInt16 SwAccessiblePortionData::GetModelPosition( sal_Int32 nPos ) const
366 {
367     DBG_ASSERT( nPos >= 0, "illegal position" );
368     DBG_ASSERT( nPos <= sAccessibleString.getLength(), "illegal position" );
369 
370     // find the portion number
371     size_t nPortionNo = FindBreak( aAccessiblePositions, nPos );
372 
373     // get model portion size
374     sal_Int32 nStartPos = aModelPositions[nPortionNo];
375 
376     // if it's a non-special portion, move into the portion, else
377     // return the portion start
378     if( ! IsSpecialPortion( nPortionNo ) )
379     {
380         // 'wide' portions have to be of the same width
381         DBG_ASSERT( ( aModelPositions[nPortionNo+1] - nStartPos ) ==
382                     ( aAccessiblePositions[nPortionNo+1] -
383                       aAccessiblePositions[nPortionNo] ),
384                     "accesability portion disagrees with text model" );
385 
386         sal_Int32 nWithinPortion = nPos - aAccessiblePositions[nPortionNo];
387         nStartPos += nWithinPortion;
388     }
389     // else: return nStartPos unmodified
390 
391     DBG_ASSERT( (nStartPos >= 0) && (nStartPos < USHRT_MAX),
392                 "How can the SwTxtNode have so many characters?" );
393     return static_cast<sal_uInt16>(nStartPos);
394 }
395 
396 void SwAccessiblePortionData::FillBoundary(
397     Boundary& rBound,
398     const Positions_t& rPositions,
399     size_t nPos ) const
400 {
401     rBound.startPos = rPositions[nPos];
402     rBound.endPos = rPositions[nPos+1];
403 }
404 
405 
406 size_t SwAccessiblePortionData::FindBreak(
407     const Positions_t& rPositions,
408     sal_Int32 nValue ) const
409 {
410     DBG_ASSERT( rPositions.size() >= 2, "need min + max value" );
411     DBG_ASSERT( rPositions[0] <= nValue, "need min value" );
412     DBG_ASSERT( rPositions[rPositions.size()-1] >= nValue,
413                 "need first terminator value" );
414     DBG_ASSERT( rPositions[rPositions.size()-2] >= nValue,
415                 "need second terminator value" );
416 
417     size_t nMin = 0;
418     size_t nMax = rPositions.size()-2;
419 
420     // loop until no more than two candidates are left
421     while( nMin+1 < nMax )
422     {
423         // check loop invariants
424         DBG_ASSERT( ( (nMin == 0) && (rPositions[nMin] <= nValue) ) ||
425                     ( (nMin != 0) && (rPositions[nMin] < nValue) ),
426                     "minvalue not minimal" );
427         DBG_ASSERT( nValue <= rPositions[nMax], "max value not maximal" );
428 
429         // get middle (and ensure progress)
430         size_t nMiddle = (nMin + nMax)/2;
431         DBG_ASSERT( nMin < nMiddle, "progress?" );
432         DBG_ASSERT( nMiddle < nMax, "progress?" );
433 
434         // check array
435         DBG_ASSERT( rPositions[nMin] <= rPositions[nMiddle],
436                     "garbled positions array" );
437         DBG_ASSERT( rPositions[nMiddle] <= rPositions[nMax],
438                     "garbled positions array" );
439 
440         if( nValue > rPositions[nMiddle] )
441             nMin = nMiddle;
442         else
443             nMax = nMiddle;
444     }
445 
446     // only two are left; we only need to check which one is the winner
447     DBG_ASSERT( (nMax == nMin) || (nMax == nMin+1), "only two left" );
448     if( (rPositions[nMin] < nValue) && (rPositions[nMin+1] <= nValue) )
449         nMin = nMin+1;
450 
451     // finally, check to see whether the returned value is the 'right' position
452     DBG_ASSERT( rPositions[nMin] <= nValue, "not smaller or equal" );
453     DBG_ASSERT( nValue <= rPositions[nMin+1], "not equal or larger" );
454     DBG_ASSERT( (nMin == 0) || (rPositions[nMin-1] <= nValue),
455                 "earlier value should have been returned" );
456 
457     DBG_ASSERT( nMin < rPositions.size()-1,
458                 "shouldn't return last position (due to termintator values)" );
459 
460     return nMin;
461 }
462 
463 size_t SwAccessiblePortionData::FindLastBreak(
464     const Positions_t& rPositions,
465     sal_Int32 nValue ) const
466 {
467     size_t nResult = FindBreak( rPositions, nValue );
468 
469     // skip 'zero-length' portions
470     // --> OD 2006-10-19 #i70538#
471     // consider size of <rPosition> and ignore last entry
472 //    while( rPositions[nResult+1] <= nValue )
473     while ( nResult < rPositions.size() - 2 &&
474             rPositions[nResult+1] <= nValue )
475     {
476         nResult++;
477     }
478     // <--
479 
480     return nResult;
481 }
482 
483 
484 void SwAccessiblePortionData::GetSentenceBoundary(
485     Boundary& rBound,
486     sal_Int32 nPos )
487 {
488     DBG_ASSERT( nPos >= 0, "illegal position; check before" );
489     DBG_ASSERT( nPos < sAccessibleString.getLength(), "illegal position" );
490 
491     if( pSentences == NULL )
492     {
493          DBG_ASSERT( pBreakIt != NULL, "We always need a break." );
494          DBG_ASSERT( pBreakIt->GetBreakIter().is(), "No break-iterator." );
495          if( pBreakIt->GetBreakIter().is() )
496          {
497              pSentences = new Positions_t();
498              pSentences->reserve(10);
499 
500              // use xBreak->endOfSentence to iterate over all words; store
501              // positions in pSentences
502              sal_Int32 nCurrent = 0;
503              sal_Int32 nLength = sAccessibleString.getLength();
504              do
505              {
506                  pSentences->push_back( nCurrent );
507 
508                  sal_uInt16 nModelPos = GetModelPosition( nCurrent );
509 
510                  sal_Int32 nNew = pBreakIt->GetBreakIter()->endOfSentence(
511                      sAccessibleString, nCurrent,
512                      pBreakIt->GetLocale(pTxtNode->GetLang(nModelPos)) ) + 1;
513 
514                  if( (nNew < 0) && (nNew > nLength) )
515                      nNew = nLength;
516                  else if (nNew <= nCurrent)
517                      nNew = nCurrent + 1;   // ensure forward progress
518 
519                  nCurrent = nNew;
520              }
521              while (nCurrent < nLength);
522 
523              // finish with two terminators
524              pSentences->push_back( nLength );
525              pSentences->push_back( nLength );
526          }
527          else
528          {
529              // no break iterator -> empty word
530              rBound.startPos = 0;
531              rBound.endPos = 0;
532              return;
533          }
534     }
535 
536     FillBoundary( rBound, *pSentences, FindBreak( *pSentences, nPos ) );
537 }
538 
539 void SwAccessiblePortionData::GetAttributeBoundary(
540     Boundary& rBound,
541     sal_Int32 nPos) const
542 {
543     DBG_ASSERT( pTxtNode != NULL, "Need SwTxtNode!" );
544 
545     // attribute boundaries can only occur on portion boundaries
546     FillBoundary( rBound, aAccessiblePositions,
547                   FindBreak( aAccessiblePositions, nPos ) );
548 }
549 
550 
551 sal_Int32 SwAccessiblePortionData::GetAccessiblePosition( sal_uInt16 nPos ) const
552 {
553     DBG_ASSERT( nPos <= pTxtNode->GetTxt().Len(), "illegal position" );
554 
555     // find the portion number
556     // --> OD 2006-10-19 #i70538#
557     // consider "empty" model portions - e.g. number portion
558     size_t nPortionNo = FindLastBreak( aModelPositions,
559                                        static_cast<sal_Int32>(nPos) );
560     // <--
561 
562     sal_Int32 nRet = aAccessiblePositions[nPortionNo];
563 
564     // if the model portion has more than one position, go into it;
565     // else return that position
566     sal_Int32 nStartPos = aModelPositions[nPortionNo];
567     sal_Int32 nEndPos = aModelPositions[nPortionNo+1];
568     if( (nEndPos - nStartPos) > 1 )
569     {
570         // 'wide' portions have to be of the same width
571         DBG_ASSERT( ( nEndPos - nStartPos ) ==
572                     ( aAccessiblePositions[nPortionNo+1] -
573                       aAccessiblePositions[nPortionNo] ),
574                     "accesability portion disagrees with text model" );
575 
576         sal_Int32 nWithinPortion = nPos - aModelPositions[nPortionNo];
577         nRet += nWithinPortion;
578     }
579     // else: return nRet unmodified
580 
581     DBG_ASSERT( (nRet >= 0) && (nRet <= sAccessibleString.getLength()),
582                 "too long!" );
583     return nRet;
584 }
585 
586 sal_uInt16 SwAccessiblePortionData::FillSpecialPos(
587     sal_Int32 nPos,
588     SwSpecialPos& rPos,
589     SwSpecialPos*& rpPos ) const
590 {
591     size_t nPortionNo = FindLastBreak( aAccessiblePositions, nPos );
592 
593     sal_uInt8 nExtend(SP_EXTEND_RANGE_NONE);
594     sal_Int32 nRefPos(0);
595     sal_Int32 nModelPos(0);
596 
597     if( nPortionNo < nBeforePortions )
598     {
599         nExtend = SP_EXTEND_RANGE_BEFORE;
600         rpPos = &rPos;
601     }
602     else
603     {
604         sal_Int32 nModelEndPos = aModelPositions[nPortionNo+1];
605         nModelPos = aModelPositions[nPortionNo];
606 
607         // skip backwards over zero-length portions, since GetCharRect()
608         // counts all model-zero-length portions as belonging to the
609         // previus portion
610         size_t nCorePortionNo = nPortionNo;
611         while( nModelPos == nModelEndPos )
612         {
613             nCorePortionNo--;
614             nModelEndPos = nModelPos;
615             nModelPos = aModelPositions[nCorePortionNo];
616 
617             DBG_ASSERT( nModelPos >= 0, "Can't happen." );
618             DBG_ASSERT( nCorePortionNo >= nBeforePortions, "Can't happen." );
619         }
620         DBG_ASSERT( nModelPos != nModelEndPos,
621                     "portion with core-representation expected" );
622 
623         // if we have anything except plain text, compute nExtend + nRefPos
624         if( (nModelEndPos - nModelPos == 1) &&
625             (pTxtNode->GetTxt().GetChar(static_cast<sal_uInt16>(nModelPos)) !=
626              sAccessibleString.getStr()[nPos]) )
627         {
628             // case 1: a one-character, non-text portion
629             // reference position is the first accessibilty for our
630             // core portion
631             nRefPos = aAccessiblePositions[ nCorePortionNo ];
632             nExtend = SP_EXTEND_RANGE_NONE;
633             rpPos = &rPos;
634         }
635         else if(nPortionNo != nCorePortionNo)
636         {
637             // case 2: a multi-character (text!) portion, followed by
638             // zero-length portions
639             // reference position is the first character of the next
640             // portion, and we are 'behind'
641             nRefPos = aAccessiblePositions[ nCorePortionNo+1 ];
642             nExtend = SP_EXTEND_RANGE_BEHIND;
643             rpPos = &rPos;
644         }
645         else
646         {
647             // case 3: regular text portion
648             DBG_ASSERT( ( nModelEndPos - nModelPos ) ==
649                         ( aAccessiblePositions[nPortionNo+1] -
650                           aAccessiblePositions[nPortionNo] ),
651                         "text portion expected" );
652 
653             nModelPos += nPos - aAccessiblePositions[ nPortionNo ];
654             rpPos = NULL;
655         }
656     }
657     if( rpPos != NULL )
658     {
659         DBG_ASSERT( rpPos == &rPos, "Yes!" );
660         DBG_ASSERT( nRefPos <= nPos, "wrong reference" );
661         DBG_ASSERT( (nExtend == SP_EXTEND_RANGE_NONE) ||
662                     (nExtend == SP_EXTEND_RANGE_BEFORE) ||
663                     (nExtend == SP_EXTEND_RANGE_BEHIND), "need extend" );
664 
665         // get the line number, and adjust nRefPos for the line
666         // (if necessary)
667         size_t nRefLine = FindBreak( aLineBreaks, nRefPos );
668         size_t nMyLine  = FindBreak( aLineBreaks, nPos );
669         sal_uInt16 nLineOffset = static_cast<sal_uInt16>( nMyLine - nRefLine );
670         if( nLineOffset != 0 )
671             nRefPos = aLineBreaks[ nMyLine ];
672 
673         // fill char offset and 'special position'
674         rPos.nCharOfst = static_cast<sal_uInt16>( nPos - nRefPos );
675         rPos.nExtendRange = nExtend;
676         rPos.nLineOfst = nLineOffset;
677     }
678 
679     return static_cast<sal_uInt16>( nModelPos );
680 }
681 
682 void SwAccessiblePortionData::AdjustAndCheck(
683     sal_Int32 nPos,
684     size_t& nPortionNo,
685     sal_uInt16& nCorePos,
686     sal_Bool& bEdit) const
687 {
688     // find portion and get mode position
689     nPortionNo = FindBreak( aAccessiblePositions, nPos );
690     nCorePos = static_cast<sal_uInt16>( aModelPositions[ nPortionNo ] );
691 
692     // for special portions, make sure we're on a portion boundary
693     // for text portions, add the in-portion offset
694     if( IsSpecialPortion( nPortionNo ) )
695         bEdit &= nPos == aAccessiblePositions[nPortionNo];
696     else
697         nCorePos = static_cast<sal_uInt16>( nCorePos +
698             nPos - aAccessiblePositions[nPortionNo] );
699 }
700 
701 sal_Bool SwAccessiblePortionData::GetEditableRange(
702     sal_Int32 nStart, sal_Int32 nEnd,
703     sal_uInt16& nCoreStart, sal_uInt16& nCoreEnd ) const
704 {
705     sal_Bool bIsEditable = sal_True;
706 
707     // get start and end portions
708     size_t nStartPortion, nEndPortion;
709     AdjustAndCheck( nStart, nStartPortion, nCoreStart, bIsEditable );
710     AdjustAndCheck( nEnd,   nEndPortion,   nCoreEnd,   bIsEditable );
711 
712     // iterate over portions, and make sure there is no read-only portion
713     // in-between
714     size_t nLastPortion = nEndPortion;
715 
716 	// don't count last portion if we're in front of a special portion
717     if( IsSpecialPortion(nLastPortion) )
718 	{
719 		if (nLastPortion > 0)
720 			nLastPortion--;
721 		else
722 			// special case: because size_t is usually unsigned, we can't just
723 			// decrease nLastPortion to -1 (which would normally do the job, so
724 			// this whole if wouldn't be needed). Instead, we'll do this
725 			// special case and just increae the start portion beyond the last
726 			// portion to make sure the loop below will have zero iteration.
727 			nStartPortion = nLastPortion + 1;
728 	}
729 
730     for( size_t nPor = nStartPortion; nPor <= nLastPortion; nPor ++ )
731     {
732         bIsEditable &= ! IsReadOnlyPortion( nPor );
733     }
734 
735     return bIsEditable;
736 }
737 
738 sal_Bool SwAccessiblePortionData::IsValidCorePosition( sal_uInt16 nPos ) const
739 {
740     // a position is valid its within the model positions that we know
741     return ( aModelPositions[0] <= nPos ) &&
742            ( nPos <= aModelPositions[ aModelPositions.size()-1 ] );
743 }
744 
745 sal_uInt16 SwAccessiblePortionData::GetFirstValidCorePosition() const
746 {
747     return static_cast<sal_uInt16>( aModelPositions[0] );
748 }
749 
750 sal_uInt16 SwAccessiblePortionData::GetLastValidCorePosition() const
751 {
752     return static_cast<sal_uInt16>( aModelPositions[ aModelPositions.size()-1 ] );
753 }
754