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