1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
23 #include <com/sun/star/i18n/UnicodeType.hpp>
24 #include <com/sun/star/i18n/WordType.hpp>
25 #include <com/sun/star/i18n/XBreakIterator.hpp>
27 #include <unotools/charclass.hxx>
29 #include <hintids.hxx>
31 #include <IDocumentUndoRedo.hxx>
32 #include <IDocumentContentOperations.hxx>
34 #include <txatbase.hxx>
35 #include <rubylist.hxx>
38 #include <breakit.hxx>
41 using namespace ::com::sun::star::i18n
;
43 constexpr int nMaxBaseTexts
= 30;
46 * Members in the list:
47 * - String - the orig text
48 * - SwFormatRuby - the ruby attribute
50 sal_uInt16
SwDoc::FillRubyList( const SwPaM
& rPam
, SwRubyList
& rList
)
52 const SwPaM
*_pStartCursor
= rPam
.GetNext(),
53 *_pStartCursor2
= _pStartCursor
;
54 bool bCheckEmpty
= &rPam
!= _pStartCursor
;
56 auto [pStart
, pEnd
] = _pStartCursor
->StartEnd(); // SwPosition*
57 if( !bCheckEmpty
|| ( pStart
!= pEnd
&& *pStart
!= *pEnd
))
59 SwPaM
aPam( *pStart
);
61 std::unique_ptr
<SwRubyListEntry
> pNew(new SwRubyListEntry
);
65 *aPam
.GetMark() = *pEnd
;
67 if( SelectNextRubyChars( aPam
, *pNew
))
69 rList
.push_back(std::move(pNew
));
74 if( *aPam
.GetPoint() < *pEnd
)
76 // goto next paragraph
78 aPam
.Move( fnMoveForward
, GoInNode
);
83 } while (nMaxBaseTexts
> rList
.size() && *aPam
.GetPoint() < *pEnd
);
85 if (nMaxBaseTexts
<= rList
.size())
87 _pStartCursor
= _pStartCursor
->GetNext();
88 } while( _pStartCursor
!= _pStartCursor2
);
93 void SwDoc::SetRubyList(SwPaM
& rPam
, const SwRubyList
& rList
)
95 SwPaM aOrigPam
{ *rPam
.GetPoint(), *rPam
.GetMark() };
98 GetIDocumentUndoRedo().StartUndo(SwUndoId::SETRUBYATTR
, nullptr);
99 const o3tl::sorted_vector
<sal_uInt16
> aDelArr
{ RES_TXTATR_CJK_RUBY
};
101 SwRubyList::size_type nListEntry
= 0;
102 int nCurrBaseTexts
= 0;
104 const SwPaM
* pStartCursor
= rPam
.GetNext();
105 auto [pStart
, pEnd
] = pStartCursor
->StartEnd();
107 bool bCheckEmpty
= (&rPam
== pStartCursor
) || (pStart
!= pEnd
&& *pStart
!= *pEnd
);
109 // Sequentially replace as many spans as possible
111 while (bCheckEmpty
&& nListEntry
< rList
.size() && nCurrBaseTexts
< nMaxBaseTexts
)
116 *aPam
.GetMark() = *pEnd
;
119 SwRubyListEntry aCheckEntry
;
120 auto bSelected
= SelectNextRubyChars(aPam
, aCheckEntry
);
126 // Existing ruby text was located. Apply the new attributes.
127 const SwRubyListEntry
* pEntry
= rList
[nListEntry
++].get();
128 if (aCheckEntry
.GetRubyAttr() != pEntry
->GetRubyAttr())
130 // set/reset the attribute
131 if (!pEntry
->GetRubyAttr().GetText().isEmpty())
133 getIDocumentContentOperations().InsertPoolItem(aPam
, pEntry
->GetRubyAttr());
137 ResetAttrs(aPam
, true, aDelArr
);
141 if (aCheckEntry
.GetText() != pEntry
->GetText())
143 if (pEntry
->GetText().isEmpty())
145 ResetAttrs(aPam
, true, aDelArr
);
148 // text is changed, so replace the original
149 getIDocumentContentOperations().ReplaceRange(aPam
, pEntry
->GetText(), false);
157 // No existing ruby text located. Advance to next paragraph.
159 aPam
.Move(fnMoveForward
, GoInNode
);
162 // Stop substituting when the cursor advances to the end of the selection.
163 if (*aPam
.GetPoint() >= *pEnd
)
169 // Delete any spans past the end of the ruby list
170 while (nListEntry
== rList
.size() && nCurrBaseTexts
< nMaxBaseTexts
&& *aPam
.GetPoint() < *pEnd
)
175 *aPam
.GetMark() = *pEnd
;
178 SwRubyListEntry aCheckEntry
;
179 auto bSelected
= SelectNextRubyChars(aPam
, aCheckEntry
);
185 ResetAttrs(aPam
, true, aDelArr
);
186 getIDocumentContentOperations().DeleteRange(aPam
);
193 // No existing ruby text located. Advance to next paragraph.
195 aPam
.Move(fnMoveForward
, GoInNode
);
199 // Insert any spans past the end of the base text list
200 sal_Int32 nTotalContentGrowth
= 0;
201 while (nListEntry
< rList
.size())
203 const SwRubyListEntry
* pEntry
= rList
[nListEntry
++].get();
205 if (!pEntry
->GetText().isEmpty())
208 getIDocumentContentOperations().InsertString(aPam
, pEntry
->GetText());
209 aPam
.GetMark()->AdjustContent(-pEntry
->GetText().getLength());
211 if (!pEntry
->GetRubyAttr().GetText().isEmpty())
213 getIDocumentContentOperations().InsertPoolItem(aPam
, pEntry
->GetRubyAttr());
218 nTotalContentGrowth
+= pEntry
->GetText().getLength();
222 // Expand selection to account for insertion
225 if( !rPam
.HasMark() )
228 *rPam
.GetMark() = *aOrigPam
.GetPoint();
230 if (*rPam
.GetPoint() == *rPam
.GetMark())
232 rPam
.GetPoint()->AdjustContent(-nTotalContentGrowth
);
237 GetIDocumentUndoRedo().EndUndo(SwUndoId::SETRUBYATTR
, nullptr);
240 bool SwDoc::SelectNextRubyChars( SwPaM
& rPam
, SwRubyListEntry
& rEntry
)
242 // Point must be the startposition, Mark is optional the end position
243 SwPosition
* pPos
= rPam
.GetPoint();
244 const SwTextNode
* pTNd
= pPos
->GetNode().GetTextNode();
245 OUString
const& rText
= pTNd
->GetText();
246 sal_Int32 nStart
= pPos
->GetContentIndex();
247 sal_Int32 nEnd
= rText
.getLength();
249 bool bHasMark
= rPam
.HasMark();
253 if( rPam
.GetMark()->GetNode() == pPos
->GetNode() )
256 const sal_Int32 nTEnd
= rPam
.GetMark()->GetContentIndex();
264 // look where a ruby attribute starts
265 const SwpHints
* pHts
= pTNd
->GetpSwpHints();
266 const SwTextAttr
* pAttr
= nullptr;
269 for( size_t nHtIdx
= 0; nHtIdx
< pHts
->Count(); ++nHtIdx
)
271 const SwTextAttr
* pHt
= pHts
->Get(nHtIdx
);
272 if( RES_TXTATR_CJK_RUBY
== pHt
->Which() &&
273 pHt
->GetAnyEnd() > nStart
)
275 if( pHt
->GetStart() < nEnd
)
278 if( !bHasMark
&& nStart
> pAttr
->GetStart() )
280 nStart
= pAttr
->GetStart();
281 pPos
->SetContent(nStart
);
289 if( !bHasMark
&& nStart
&& ( !pAttr
|| nStart
!= pAttr
->GetStart()) )
291 // skip to the word begin!
292 const sal_Int32 nWordStt
= g_pBreakIt
->GetBreakIter()->getWordBoundary(
294 g_pBreakIt
->GetLocale( pTNd
->GetLang( nStart
)),
295 WordType::ANYWORD_IGNOREWHITESPACES
,
297 if (nWordStt
< nStart
&& nWordStt
>= 0)
300 pPos
->SetContent(nStart
);
304 bool bAlphaNum
= false;
305 sal_Int32 nWordEnd
= nEnd
;
306 CharClass
& rCC
= GetAppCharClass();
307 while( nStart
< nEnd
)
309 if( pAttr
&& nStart
== pAttr
->GetStart() )
311 pPos
->SetContent(nStart
);
312 if( !rPam
.HasMark() )
315 pPos
->SetContent(pAttr
->GetAnyEnd());
316 if( pPos
->GetContentIndex() > nEnd
)
317 pPos
->SetContent(nEnd
);
318 rEntry
.SetRubyAttr( pAttr
->GetRuby() );
323 sal_Int32 nChType
= rCC
.getType(rText
, nStart
);
324 bool bIgnoreChar
= false, bIsAlphaNum
= false, bChkNxtWrd
= false;
327 case UnicodeType::UPPERCASE_LETTER
:
328 case UnicodeType::LOWERCASE_LETTER
:
329 case UnicodeType::TITLECASE_LETTER
:
330 case UnicodeType::DECIMAL_DIGIT_NUMBER
:
331 bChkNxtWrd
= bIsAlphaNum
= true;
334 case UnicodeType::SPACE_SEPARATOR
:
335 case UnicodeType::CONTROL
:
336 /*??*/ case UnicodeType::PRIVATE_USE
:
337 case UnicodeType::START_PUNCTUATION
:
338 case UnicodeType::END_PUNCTUATION
:
342 case UnicodeType::OTHER_LETTER
:
352 if( bIgnoreChar
|| bIsAlphaNum
!= bAlphaNum
|| nStart
>= nWordEnd
)
355 else if( !bIgnoreChar
)
358 bAlphaNum
= bIsAlphaNum
;
361 // search the end of this word
362 nWordEnd
= g_pBreakIt
->GetBreakIter()->getWordBoundary(
364 g_pBreakIt
->GetLocale( pTNd
->GetLang( nStart
)),
365 WordType::ANYWORD_IGNOREWHITESPACES
,
367 if( 0 > nWordEnd
|| nWordEnd
> nEnd
|| nWordEnd
== nStart
)
371 pTNd
->GoNext( *pPos
, SwCursorSkipMode::Chars
);
372 nStart
= pPos
->GetContentIndex();
375 nStart
= rPam
.GetMark()->GetContentIndex();
376 rEntry
.SetText( rText
.copy( nStart
,
377 rPam
.GetPoint()->GetContentIndex() - nStart
));
378 return rPam
.HasMark();
381 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */