README.yrs | 46 RepositoryExternal.mk | 17 config_host.mk.in | 1 configure.ac | 6 editeng/CppunitTest_editeng_core.mk | 1 editeng/CppunitTest_editeng_editeng.mk | 1 editeng/Library_editeng.mk | 1 editeng/inc/editdoc.hxx | 30 editeng/source/editeng/editdoc.cxx | 2918 ++++++++++++++++++++++++--- editeng/source/editeng/impedit.hxx | 3 editeng/source/editeng/impedit5.cxx | 6 include/editeng/editview.hxx | 14 include/editeng/yrs.hxx | 69 sfx2/Library_sfx.mk | 4 sfx2/source/view/frmload.cxx | 43 sw/Library_sw.mk | 1 sw/inc/IDocumentState.hxx | 23 sw/inc/PostItMgr.hxx | 3 sw/source/core/doc/DocumentStateManager.cxx | 1103 ++++++++++ sw/source/core/inc/DocumentStateManager.hxx | 33 sw/source/uibase/app/docsh.cxx | 16 sw/source/uibase/docvw/AnnotationWin.cxx | 14 sw/source/uibase/docvw/PostItMgr.cxx | 11 sw/source/uibase/docvw/SidebarTxtControl.cxx | 11 sw/source/uibase/docvw/SidebarTxtControl.hxx | 3 sw/source/uibase/fldui/fldmgr.cxx | 22 sw/source/uibase/inc/wrtsh.hxx | 5 sw/source/uibase/uiview/view.cxx | 3 sw/source/uibase/uno/unotxdoc.cxx | 11 sw/source/uibase/wrtsh/wrtsh2.cxx | 15 30 files changed, 4182 insertions(+), 252 deletions(-)
New commits: commit 8f8034177b9efcfaa52b3dc0980aec58e75fca5b Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Oct 25 15:02:34 2024 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed May 7 13:36:01 2025 +0200 LOCRDT editeng,sfx2,sw: experimental yrs collab This can be enabled in configure via --with-yrs=...; see also README.yrs Currently this is hardcoded to run over a local named pipe. Everything related to editengine is implemented in EditDoc and made accessible via its wrapper classes; everything related to communication and accessing sw's comment is implemented in sw::DocumentStateManager. The acceptor starts in SwView::SwView(), once SwPostItMgr exists. There is a hack in SfxFrameLoader_Impl::load() to fetch the document that the accepting soffice has loaded, and load it in the connecting soffice as well. Change-Id: I89476b5864b70f479bcf15989374c1c65b5da9ea Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175652 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/README.yrs b/README.yrs new file mode 100644 index 000000000000..55a99e2a3de0 --- /dev/null +++ b/README.yrs @@ -0,0 +1,46 @@ + +## Experimental Writer comments editing collaboration with yrs + +### How to build + +First, build yrs C FFI bindings: + +``` + git clone https://github.com/y-crdt/y-crdt.git + cd y-crdt + git checkout v0.23.1 + cargo build -p yffi +``` + +Then, put the yrs build directory in autogen.input: + +`--with-yrs=/path/to/y-crdt` + +### How to run + +To prevent crashes at runtime, set the environment variable +EDIT_COMMENT_IN_READONLY_MODE=1 and open documents in read-only mode: only +inserting/deleting comments, and editing inside comments will be enabled. + +Currently, communication happens over a hard-coded pipe: + +* start an soffice with YRSACCEPT=1 load a Writer document and it will listen + and block until connect + (you can also create a new Writer document but that will be boring if all + you can do is insert comments into empty doc) + +* start another soffice with a different user profile, create new Writer + document, and it will connect and load the document from the other side + +All sorts of paragraph and character formattings should work inside comments. + +Inserting hyperlinks also works, although sadly i wasn't able to figure out +how to enable the menu items in read-only mode, so it only works in editable +mode. + +Undo/Redo doesn't work at all, it's disabled in readonly mode anyway. + +Switching to editable mode is also possible, but only comment-related editing +is synced via yrs, so if other editing operations change the positions of +comments, a crash will be inevitable. + diff --git a/RepositoryExternal.mk b/RepositoryExternal.mk index f16521c50d7d..9cacf77039d5 100644 --- a/RepositoryExternal.mk +++ b/RepositoryExternal.mk @@ -4449,4 +4449,21 @@ $(call gb_LinkTarget_set_include,$(1),\ endef endif +ifneq ($(WITH_YRS),) + +define gb_LinkTarget__use_yrs +$(call gb_LinkTarget_set_include,$(1),\ + $$(INCLUDE) \ + -I$(WITH_YRS)/tests-ffi/include \ +) +$(call gb_LinkTarget_add_defs,$(1),-DYRS) +$(call gb_LinkTarget_add_libs,$(1),$(WITH_YRS)/target/debug/libyrs.a) +endef + +else + +gb_LinkTarget__use_yrs := + +endif + # vim: set noet sw=4 ts=4: diff --git a/config_host.mk.in b/config_host.mk.in index 0a4d61f62875..70775a16782e 100644 --- a/config_host.mk.in +++ b/config_host.mk.in @@ -784,6 +784,7 @@ export WITH_LOCALES=@WITH_LOCALES@ export WITH_MYSPELL_DICTS=@WITH_MYSPELL_DICTS@ export WITH_THEMES=@WITH_THEMES@ export WITH_WEBDAV=@WITH_WEBDAV@ +WITH_YRS=@WITH_YRS@ export WORKDIR=@WORKDIR@ export WORKDIR_FOR_BUILD=@WORKDIR_FOR_BUILD@ export WPD_CFLAGS=$(gb_SPACE)@WPD_CFLAGS@ diff --git a/configure.ac b/configure.ac index 7819f4d4ec97..e7059bb611d8 100644 --- a/configure.ac +++ b/configure.ac @@ -2830,6 +2830,12 @@ AC_ARG_WITH(hamcrest, --without-junit disables those tests. Not relevant in the --without-java case.]), ,with_hamcrest=yes) +AC_ARG_WITH(yrs, + AS_HELP_STRING([--with-yrs=<absolute path to yrs build>], + [Specifies the built yrs git repo for very experimental experiments.]), + WITH_YRS=$withval) +AC_SUBST(WITH_YRS) + AC_ARG_WITH(perl-home, AS_HELP_STRING([--with-perl-home=<abs. path to Perl 5 home>], [If you have installed Perl 5 Distribution, on your system, please diff --git a/editeng/CppunitTest_editeng_core.mk b/editeng/CppunitTest_editeng_core.mk index 46a232d97f38..64f17ae3ba4a 100644 --- a/editeng/CppunitTest_editeng_core.mk +++ b/editeng/CppunitTest_editeng_core.mk @@ -53,6 +53,7 @@ $(eval $(call gb_CppunitTest_use_externals,editeng_core,\ boost_headers \ icuuc \ libxml2 \ + yrs \ )) $(eval $(call gb_CppunitTest_set_include,editeng_core,\ diff --git a/editeng/CppunitTest_editeng_editeng.mk b/editeng/CppunitTest_editeng_editeng.mk index 38cbc9543db6..6c2d20917e86 100644 --- a/editeng/CppunitTest_editeng_editeng.mk +++ b/editeng/CppunitTest_editeng_editeng.mk @@ -47,6 +47,7 @@ $(eval $(call gb_CppunitTest_use_externals,editeng_editeng,\ boost_headers \ icuuc \ libxml2 \ + yrs \ )) $(eval $(call gb_CppunitTest_set_include,editeng_editeng,\ diff --git a/editeng/Library_editeng.mk b/editeng/Library_editeng.mk index 36893816b569..cfd4f357270c 100644 --- a/editeng/Library_editeng.mk +++ b/editeng/Library_editeng.mk @@ -166,6 +166,7 @@ $(eval $(call gb_Library_use_externals,editeng,\ icuuc \ icu_headers \ libxml2 \ + yrs \ )) # vim: set noet sw=4 ts=4: diff --git a/editeng/inc/editdoc.hxx b/editeng/inc/editdoc.hxx index dfb0adec96d2..eb0d79da7e6e 100644 --- a/editeng/inc/editdoc.hxx +++ b/editeng/inc/editdoc.hxx @@ -46,6 +46,13 @@ enum class TextRotation; +#if defined(YRS) +class ImpEditEngine; +class IYrsTransactionSupplier; +typedef struct TransactionInner YTransaction; +typedef struct YTextEvent YTextEvent; +#endif + #define CHARPOSGROW 16 #define DEFTAB 720 @@ -119,6 +126,19 @@ private: bool mbModified:1; bool mbDisableAttributeExpanding:1; +#if defined(YRS) + OString m_CommentId; + IYrsTransactionSupplier * m_pYrsSupplier{nullptr}; +public: + void SetYrsCommentId(IYrsTransactionSupplier *, OString const& rId); + void YrsWriteEEState(); + void YrsReadEEState(YTransaction *, ImpEditEngine & rIEE); + void YrsApplyEEDelta(YTransaction *, YTextEvent const* pEvent, ImpEditEngine & rIEE); + void YrsSetStyle(sal_Int32 nPara, ::std::u16string_view rStyle); + void YrsSetParaAttr(sal_Int32 nPara, SfxPoolItem const& rItem); + OString GetYrsCommentId() const; +#endif + public: EditDoc( SfxItemPool* pItemPool ); ~EditDoc(); @@ -139,21 +159,19 @@ public: void CreateDefFont( bool bUseStyles ); const SvxFont& GetDefFont() const { return maDefFont; } - void SetDefTab(sal_uInt16 nTab) - { - mnDefTab = nTab ? nTab : DEFTAB; - } + void SetDefTab(sal_uInt16 nTab); sal_uInt16 GetDefTab() const { return mnDefTab; } - void SetVertical( bool bVertical ) { mbIsVertical = bVertical; } + void SetVertical(bool bVertical); + bool IsEffectivelyVertical() const; bool IsTopToBottom() const; bool GetVertical() const; - void SetRotation( TextRotation nRotation ) { mnRotation = nRotation; } + void SetRotation(TextRotation nRotation); TextRotation GetRotation() const { return mnRotation; } void SetFixedCellHeight( bool bUseFixedCellHeight ) diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx index a34ca78d810d..235708d6c93a 100644 --- a/editeng/source/editeng/editdoc.cxx +++ b/editeng/source/editeng/editdoc.cxx @@ -42,6 +42,16 @@ #include <editeng/lrspitem.hxx> #include <editeng/ulspitem.hxx> #include <editeng/lspcitem.hxx> +#if defined(YRS) +#include <editeng/frmdiritem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/numdef.hxx> +#include <svl/itemiter.hxx> +#endif #include <editdoc.hxx> #include <editeng/eerdll.hxx> @@ -714,283 +724,2625 @@ void EditSelection::Adjust( const EditDoc& rNodes ) } } -EditDoc::EditDoc( SfxItemPool* pPool ) : - mnLastCache(0), - mpItemPool(pPool ? pPool : new EditEngineItemPool()), - mnDefTab(DEFTAB), - mbIsVertical(false), - mnRotation(TextRotation::NONE), - mbIsFixedCellHeight(false), - mbModified(false), - mbDisableAttributeExpanding(false) -{ - // Don't create an empty node, Clear() will be called in EditEngine-CTOR -}; +#if defined(YRS) +#include <editeng/yrs.hxx> -EditDoc::~EditDoc() -{ - maContents.clear(); -} +namespace { -void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent, SvtScriptType nScriptType ) +struct YrsReplayGuard { - vcl::Font aPrevFont( rFont ); - rFont.SetAlignment( ALIGN_BASELINE ); - - sal_uInt16 nWhich_FontInfo = GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ); - sal_uInt16 nWhich_Language = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ); - sal_uInt16 nWhich_FontHeight = GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ); - sal_uInt16 nWhich_Weight = GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ); - sal_uInt16 nWhich_Italic = GetScriptItemId( EE_CHAR_ITALIC, nScriptType ); + IYrsTransactionSupplier *const m_pYrsSupplier; + IYrsTransactionSupplier::Mode m_Mode; - if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontInfo ) == SfxItemState::SET ) ) + explicit YrsReplayGuard(IYrsTransactionSupplier *const pYrsSupplier) + : m_pYrsSupplier(pYrsSupplier) { - const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSet.Get( nWhich_FontInfo )); - rFont.SetFamilyName( rFontItem.GetFamilyName() ); - rFont.SetFamily( rFontItem.GetFamily() ); - rFont.SetPitch( rFontItem.GetPitch() ); - rFont.SetCharSet( rFontItem.GetCharSet() ); + if (m_pYrsSupplier) + { + m_Mode = m_pYrsSupplier->SetMode(IYrsTransactionSupplier::Mode::Replay); + } } - if ( bSearchInParent || ( rSet.GetItemState( nWhich_Language ) == SfxItemState::SET ) ) - rFont.SetLanguage( static_cast<const SvxLanguageItem&>(rSet.Get( nWhich_Language )).GetLanguage() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_COLOR ) == SfxItemState::SET ) ) - rFont.SetColor( rSet.Get( EE_CHAR_COLOR ).GetValue() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_BKGCOLOR ) == SfxItemState::SET ) ) + ~YrsReplayGuard() { - auto& aColor = rSet.Get( EE_CHAR_BKGCOLOR ).GetValue(); - rFont.SetTransparent(aColor.IsTransparent()); - rFont.SetFillColor(aColor); + if (m_pYrsSupplier) + { + m_pYrsSupplier->SetMode(m_Mode); + } } - if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontHeight ) == SfxItemState::SET ) ) - rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem&>(rSet.Get( nWhich_FontHeight ) ).GetHeight() ) ); - if ( bSearchInParent || ( rSet.GetItemState( nWhich_Weight ) == SfxItemState::SET ) ) - rFont.SetWeight( static_cast<const SvxWeightItem&>(rSet.Get( nWhich_Weight )).GetWeight() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET ) ) - rFont.SetUnderline( rSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OVERLINE ) == SfxItemState::SET ) ) - rFont.SetOverline( rSet.Get( EE_CHAR_OVERLINE ).GetLineStyle() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET ) ) - rFont.SetStrikeout( rSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_CASEMAP ) == SfxItemState::SET ) ) - rFont.SetCaseMap( rSet.Get( EE_CHAR_CASEMAP ).GetCaseMap() ); - if ( bSearchInParent || ( rSet.GetItemState( nWhich_Italic ) == SfxItemState::SET ) ) - rFont.SetItalic( static_cast<const SvxPostureItem&>(rSet.Get( nWhich_Italic )).GetPosture() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OUTLINE ) == SfxItemState::SET ) ) - rFont.SetOutline( rSet.Get( EE_CHAR_OUTLINE ).GetValue() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_SHADOW ) == SfxItemState::SET ) ) - rFont.SetShadow( rSet.Get( EE_CHAR_SHADOW ).GetValue() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_ESCAPEMENT ) == SfxItemState::SET ) ) - { - const SvxEscapementItem& rEsc = rSet.Get( EE_CHAR_ESCAPEMENT ); - - sal_uInt16 const nProp = rEsc.GetProportionalHeight(); - rFont.SetPropr( static_cast<sal_uInt8>(nProp) ); +}; - short nEsc = rEsc.GetEsc(); - rFont.SetNonAutoEscapement( nEsc ); - } - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_PAIRKERNING ) == SfxItemState::SET ) ) - rFont.SetKerning( rSet.Get( EE_CHAR_PAIRKERNING ).GetValue() ? FontKerning::FontSpecific : FontKerning::NONE ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_KERNING ) == SfxItemState::SET ) ) - rFont.SetFixKerning( rSet.Get( EE_CHAR_KERNING ).GetValue() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_WLM ) == SfxItemState::SET ) ) - rFont.SetWordLineMode( rSet.Get( EE_CHAR_WLM ).GetValue() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_EMPHASISMARK ) == SfxItemState::SET ) ) - rFont.SetEmphasisMark( rSet.Get( EE_CHAR_EMPHASISMARK ).GetEmphasisMark() ); - if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_RELIEF ) == SfxItemState::SET ) ) - rFont.SetRelief( rSet.Get( EE_CHAR_RELIEF ).GetValue() ); +struct YrsWrite +{ + YTransaction *const pTxn; + Branch *const pProps; + Branch *const pText; +}; - // Operator == compares the individual members of the font if the impl pointer is - // not equal. If all members are the same, this assignment makes - // sure that both also point to the same internal instance of the font. - // To avoid this assignment, you would need to check in - // every if statement above whether or not the new value differs from the - // old value before making an assignment. - if ( rFont == aPrevFont ) - rFont = aPrevFont; // => The same ImpPointer for IsSameInstance -} +constexpr char CH_PARA = 0x0d; -void EditDoc::CreateDefFont( bool bUseStyles ) +YrsWrite GetYrsWrite(IYrsTransactionSupplier *const pYrsSupplier, + OString const& rId, YTransaction *const pInTxn = nullptr) { - SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSet<EE_PARA_START, EE_CHAR_END>(GetItemPool())); - CreateFont(maDefFont, aTmpSet); - maDefFont.SetVertical( IsEffectivelyVertical() ); - maDefFont.SetOrientation( Degree10(IsEffectivelyVertical() ? (IsTopToBottom() ? 2700 : 900) : 0) ); - - for (std::unique_ptr<ContentNode>& pNode : maContents) + if (!pYrsSupplier) { - pNode->GetCharAttribs().GetDefFont() = maDefFont; - if (bUseStyles) - pNode->CreateDefFont(); + return { nullptr, nullptr, nullptr }; } -} - -bool EditDoc::IsEffectivelyVertical() const -{ - return (mbIsVertical && mnRotation == TextRotation::NONE) || - (!mbIsVertical && mnRotation != TextRotation::NONE); -} - -bool EditDoc::IsTopToBottom() const -{ - return (mbIsVertical && mnRotation == TextRotation::NONE) || - (!mbIsVertical && mnRotation == TextRotation::TOPTOBOTTOM); -} - -bool EditDoc::GetVertical() const -{ - return mbIsVertical; -} - -sal_Int32 EditDoc::GetPos(const ContentNode* pContentNode) const -{ - return FastGetPos(maContents, pContentNode, mnLastCache); -} - -const ContentNode* EditDoc::GetObject(sal_Int32 nPos) const -{ - return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? maContents[nPos].get() : nullptr; -} - -ContentNode* EditDoc::GetObject(sal_Int32 nPos) -{ - return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? maContents[nPos].get() : nullptr; -} - -void EditDoc::Insert(sal_Int32 nPos, std::unique_ptr<ContentNode> pNode) -{ - if (nPos < 0 || nPos == SAL_MAX_INT32) + YDoc *const pDoc{pYrsSupplier->GetYDoc()}; + YTransaction *const pTxn{pInTxn ? pInTxn : pYrsSupplier->GetWriteTransaction()}; + // write is disabled when receiving edits from peers + if (!pTxn) { - SAL_WARN( "editeng", "EditDoc::Insert - overflow pos " << nPos); - return; + return { nullptr, nullptr, nullptr }; } - maContents.insert(maContents.begin()+nPos, std::move(pNode)); -} - -void EditDoc::Remove(sal_Int32 nPos) -{ - if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size()) + assert(pDoc); + Branch *const pComments{pYrsSupplier->GetCommentMap()}; + ::std::unique_ptr<YOutput, YOutputDeleter> const pComment{ymap_get(pComments, pTxn, rId.getStr())}; + yvalidate(pComment->tag == Y_ARRAY); + yvalidate(pComment->len == 1); + Branch *const pCommentArray{pComment->value.y_type}; + ::std::unique_ptr<YOutput, YOutputDeleter> const pProps{yarray_get(pCommentArray, pTxn, 1)}; + yvalidate(pProps->tag == Y_MAP); + yvalidate(pProps->len == 1); + ::std::unique_ptr<YOutput, YOutputDeleter> const pText{yarray_get(pCommentArray, pTxn, 2)}; + yvalidate(pText->tag == Y_TEXT); + yvalidate(pText->len == 1); + return { pTxn, pProps->value.y_type, pText->value.y_type }; +} + +void YrsSetVertical(IYrsTransactionSupplier *const pYrsSupplier, + OString const& rCommentId, bool const isVertical) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) { - SAL_WARN( "editeng", "EditDoc::Remove - out of bounds pos " << nPos); return; } - maContents.erase(maContents.begin() + nPos); + YInput const input{yinput_bool(isVertical ? Y_TRUE : Y_FALSE)}; + ymap_insert(yw.pProps, yw.pTxn, "is-vertical", &input); } -std::unique_ptr<ContentNode> EditDoc::Release(sal_Int32 nPos) +void YrsSetRotation(IYrsTransactionSupplier *const pYrsSupplier, + OString const& rCommentId, TextRotation const nRotation) { - if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size()) + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) { - SAL_WARN( "editeng", "EditDoc::Release - out of bounds pos " << nPos); - return nullptr; + return; } - - std::unique_ptr<ContentNode> pNode = std::move(maContents[nPos]); - maContents.erase(maContents.begin() + nPos); - return pNode; + YInput const input{yinput_long(static_cast<int64_t>(nRotation))}; + ymap_insert(yw.pProps, yw.pTxn, "rotation", &input); } -sal_Int32 EditDoc::Count() const +void YrsSetDefTab(IYrsTransactionSupplier *const pYrsSupplier, + OString const& rCommentId, sal_uInt16 const nDefTab) { - size_t nSize = maContents.size(); - if (nSize > SAL_MAX_INT32) + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) { - SAL_WARN( "editeng", "EditDoc::Count - overflow " << nSize); - return SAL_MAX_INT32; + return; } - return nSize; -} - -OUString EditDoc::GetSepStr( LineEnd eEnd ) -{ - if ( eEnd == LINEEND_CR ) - return u" "_ustr; // 0x0d - if ( eEnd == LINEEND_LF ) - return u" "_ustr; // 0x0a - return u" "_ustr; // 0x0d, 0x0a + YInput const input{yinput_long(static_cast<int64_t>(nDefTab))}; + ymap_insert(yw.pProps, yw.pTxn, "def-tab", &input); } -OUString EditDoc::GetText( LineEnd eEnd ) const +void YrsInsertAttribImplImpl(YrsWrite const& yw, SfxPoolItem const& rItm, + uint32_t const nStart, uint32_t const nLen) { - const sal_Int32 nNodes = Count(); - if (nNodes == 0) - return OUString(); - - const OUString aSep = EditDoc::GetSepStr( eEnd ); - const sal_Int32 nSepSize = aSep.getLength(); - const sal_Int32 nLen = GetTextLen() + (nNodes - 1)*nSepSize; - - OUStringBuffer aBuffer(nLen + 16); // leave some slack - - for ( sal_Int32 nNode = 0; nNode < nNodes; nNode++ ) + ::std::vector<YInput> tabStops; + ::std::vector<::std::vector<YInput>> tabStopValues; + ::std::vector<OString> tempStrings; + ::std::vector<YInput> itemArray; + ::std::vector<char const*> itemNames; + YInput attr; + char const* attrName; + switch (rItm.Which()) { - if ( nSepSize && nNode>0 ) + case EE_CHAR_COLOR: + case EE_CHAR_BKGCOLOR: { - aBuffer.append(aSep); + sal_uInt32 const nColor{static_cast<SvxColorItem const&>(rItm).getColor()}; + attr = yinput_long(nColor); + attrName = rItm.Which() == EE_CHAR_COLOR ? "EE_CHAR_COLOR" : "EE_CHAR_BKGCOLOR"; + break; } - aBuffer.append(GetParaAsString( GetObject(nNode) )); - } - - return aBuffer.makeStringAndClear(); -} - -OUString EditDoc::GetParaAsString( sal_Int32 nNode ) const -{ - return GetParaAsString( GetObject( nNode ) ); -} - -OUString EditDoc::GetParaAsString( - const ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos) -{ - return pNode->GetExpandedText(nStartPos, nEndPos); -} - -EditPaM EditDoc::GetStartPaM() const -{ - ContentNode* p = const_cast<ContentNode*>(GetObject(0)); - return EditPaM(p, 0); -} - -EditPaM EditDoc::GetEndPaM() const -{ - ContentNode* pLastNode = const_cast<ContentNode*>(GetObject(Count()-1)); - return EditPaM( pLastNode, pLastNode->Len() ); -} - -sal_Int32 EditDoc::GetTextLen() const -{ - sal_Int32 nLength = 0; - for (auto const& pContent : maContents) - { - nLength += pContent->GetExpandedLen(); - } - return nLength; -} - -EditPaM EditDoc::Clear() -{ - maContents.clear(); - - ContentNode* pNode = new ContentNode(GetItemPool()); - Insert(0, std::unique_ptr<ContentNode>(pNode)); - - CreateDefFont(false); - - SetModified(false); - - return EditPaM( pNode, 0 ); -} - -namespace -{ -struct ClearSpellErrorsHandler -{ - void operator() (std::unique_ptr<ContentNode> const & rNode) - { - rNode->DestroyWrongList(); - } -}; -} + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + SvxFontItem const& rItem{static_cast<SvxFontItem const&>(rItm)}; + tempStrings.reserve(2); // prevent realloc + tempStrings.emplace_back(OUStringToOString(rItem.GetFamilyName(), RTL_TEXTENCODING_UTF8)); + itemArray.emplace_back(yinput_string(tempStrings.back().getStr())); + itemNames.emplace_back("familyname"); + tempStrings.emplace_back(OUStringToOString(rItem.GetStyleName(), RTL_TEXTENCODING_UTF8)); + itemArray.emplace_back(yinput_string(tempStrings.back().getStr())); + itemNames.emplace_back("style"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetFamily()))); + itemNames.emplace_back("family"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetPitch()))); + itemNames.emplace_back("pitch"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetCharSet()))); + itemNames.emplace_back("charset"); + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = rItm.Which() == EE_CHAR_FONTINFO + ? "EE_CHAR_FONTINFO" + : rItm.Which() == EE_CHAR_FONTINFO_CJK ? "EE_CHAR_FONTINFO_CJK" : "EE_CHAR_FONTINFO_CTL"; + break; + } + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + SvxFontHeightItem const& rItem{static_cast<SvxFontHeightItem const&>(rItm)}; + itemNames.emplace_back("height"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetHeight()))); + itemNames.emplace_back("prop"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetProp()))); + itemNames.emplace_back("propunit"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetPropUnit()))); + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = rItm.Which() == EE_CHAR_FONTHEIGHT + ? "EE_CHAR_FONTHEIGHT" + : rItm.Which() == EE_CHAR_FONTHEIGHT_CJK ? "EE_CHAR_FONTHEIGHT_CJK" : "EE_CHAR_FONTHEIGHT_CTL"; + break; + } + case EE_CHAR_FONTWIDTH: + { + SvxCharScaleWidthItem const& rItem{static_cast<SvxCharScaleWidthItem const&>(rItm)}; + attr = yinput_long(rItem.GetValue()); + attrName = "EE_CHAR_FONTWIDTH"; + break; + } + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + SvxWeightItem const& rItem{static_cast<SvxWeightItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetWeight())); + attrName = rItm.Which() == EE_CHAR_WEIGHT + ? "EE_CHAR_WEIGHT" + : rItm.Which() == EE_CHAR_WEIGHT_CJK ? "EE_CHAR_WEIGHT_CJK" : "EE_CHAR_WEIGHT_CTL"; + break; + } + case EE_CHAR_UNDERLINE: + case EE_CHAR_OVERLINE: + { + SvxTextLineItem const& rItem{static_cast<SvxTextLineItem const&>(rItm)}; + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLineStyle()))); + itemNames.emplace_back("style"); + itemArray.emplace_back(yinput_long(uint64_t(sal_uInt32(rItem.GetColor())))); + itemNames.emplace_back("color"); + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = rItm.Which() == EE_CHAR_UNDERLINE ? "EE_CHAR_UNDERLINE" : "EE_CHAR_OVERLINE"; + break; + } + case EE_CHAR_STRIKEOUT: + { + SvxCrossedOutItem const& rItem{static_cast<SvxCrossedOutItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetStrikeout())); + attrName = "EE_CHAR_STRIKEOUT"; + break; + } + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + SvxPostureItem const& rItem{static_cast<SvxPostureItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetPosture())); + attrName = rItm.Which() == EE_CHAR_ITALIC + ? "EE_CHAR_ITALIC" + : rItm.Which() == EE_CHAR_ITALIC_CJK ? "EE_CHAR_ITALIC_CJK" : "EE_CHAR_ITALIC_CTL"; + break; + } + case EE_CHAR_OUTLINE: + { + SvxContourItem const& rItem{static_cast<SvxContourItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_CHAR_OUTLINE"; + break; + } + case EE_CHAR_SHADOW: + { + SvxShadowedItem const& rItem{static_cast<SvxShadowedItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_CHAR_SHADOW"; + break; + } + case EE_CHAR_ESCAPEMENT: + { + SvxEscapementItem const& rItem{static_cast<SvxEscapementItem const&>(rItm)}; + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetEsc()))); + itemNames.emplace_back("esc"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetProportionalHeight()))); + itemNames.emplace_back("prop"); + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = "EE_CHAR_ESCAPEMENT"; + break; + } + case EE_CHAR_PAIRKERNING: + { + SvxAutoKernItem const& rItem{static_cast<SvxAutoKernItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_CHAR_PAIRKERNING"; + break; + } + case EE_CHAR_KERNING: + { + SvxKerningItem const& rItem{static_cast<SvxKerningItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_CHAR_KERNING"; + break; + } + case EE_CHAR_WLM: + { + SvxWordLineModeItem const& rItem{static_cast<SvxWordLineModeItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_CHAR_WLM"; + break; + } + case EE_CHAR_LANGUAGE: + case EE_CHAR_LANGUAGE_CJK: + case EE_CHAR_LANGUAGE_CTL: + { + SvxLanguageItem const& rItem{static_cast<SvxLanguageItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue().get())); + attrName = rItm.Which() == EE_CHAR_LANGUAGE + ? "EE_CHAR_LANGUAGE" + : rItm.Which() == EE_CHAR_LANGUAGE_CJK ? "EE_CHAR_LANGUAGE_CJK" : "EE_CHAR_LANGUAGE_CTL"; + break; + } + case EE_CHAR_EMPHASISMARK: + { + SvxEmphasisMarkItem const& rItem{static_cast<SvxEmphasisMarkItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_CHAR_EMPHASISMARK"; + break; + } + case EE_CHAR_RELIEF: + { + SvxCharReliefItem const& rItem{static_cast<SvxCharReliefItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_CHAR_RELIEF"; + break; + } + case EE_CHAR_CASEMAP: + { + SvxCaseMapItem const& rItem{static_cast<SvxCaseMapItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_CHAR_CASEMAP"; + break; + } + case EE_PARA_WRITINGDIR: + { + SvxFrameDirectionItem const& rItem{static_cast<SvxFrameDirectionItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_PARA_WRITINGDIR"; + break; + } + case EE_PARA_HANGINGPUNCTUATION: + { + SvxHangingPunctuationItem const& rItem{static_cast<SvxHangingPunctuationItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_PARA_HANGINGPUNCTUATION"; + break; + } + case EE_PARA_FORBIDDENRULES: + { + SvxForbiddenRuleItem const& rItem{static_cast<SvxForbiddenRuleItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_PARA_FORBIDDENRULES"; + break; + } + case EE_PARA_ASIANCJKSPACING: + { + SvxScriptSpaceItem const& rItem{static_cast<SvxScriptSpaceItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = "EE_PARA_ASIANCJKSPACING"; + break; + } +//TODO complex, but apparently no way to set this in comment? inline constexpr TypedWhichId<SvxNumBulletItem> EE_PARA_NUMBULLET (EE_PARA_START+5); + case EE_PARA_HYPHENATE: + case EE_PARA_HYPHENATE_NO_CAPS: + case EE_PARA_HYPHENATE_NO_LAST_WORD: + case EE_PARA_BULLETSTATE: + { + SfxBoolItem const& rItem{static_cast<SfxBoolItem const&>(rItm)}; + attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE); + attrName = rItm.Which() == EE_PARA_HYPHENATE + ? "EE_PARA_HYPHENATE" + : rItm.Which() == EE_PARA_HYPHENATE_NO_CAPS + ? "EE_PARA_HYPHENATE_NO_CAPS" + : rItm.Which() == EE_PARA_HYPHENATE_NO_LAST_WORD + ? "EE_PARA_HYPHENATE_NO_LAST_WORD" + : "EE_PARA_BULLETSTATE"; + break; + } +//TODO no way to set this in comment? inline constexpr TypedWhichId<SvxLRSpaceItem> EE_PARA_OUTLLRSPACE (EE_PARA_START+10); + case EE_PARA_OUTLLEVEL: + { + SfxInt16Item const& rItem{static_cast<SfxInt16Item const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_PARA_OUTLLEVEL"; + break; + } +//TODO complex, but apparently no way to set this in comment? inline constexpr TypedWhichId<SvxBulletItem> EE_PARA_BULLET (EE_PARA_START+12); + case EE_PARA_LRSPACE: + { + SvxLRSpaceItem const& rItem{static_cast<SvxLRSpaceItem const&>(rItm)}; + itemArray.emplace_back(yinput_float(rItem.GetTextFirstLineOffset().m_dValue)); + itemNames.emplace_back("first-line-offset"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetTextFirstLineOffset().m_nUnit))); + itemNames.emplace_back("first-line-offset-unit"); + itemArray.emplace_back(yinput_float(rItem.GetLeft().m_dValue)); + itemNames.emplace_back("left-margin"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLeft().m_nUnit))); + itemNames.emplace_back("left-margin-unit"); + itemArray.emplace_back(yinput_float(rItem.GetRight().m_dValue)); + itemNames.emplace_back("right-margin"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetRight().m_nUnit))); + itemNames.emplace_back("right-margin-unit"); + itemArray.emplace_back(yinput_bool(rItem.IsAutoFirst() ? Y_TRUE : Y_FALSE)); + itemNames.emplace_back("auto-first"); + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = "EE_PARA_LRSPACE"; + break; + } + case EE_PARA_ULSPACE: + { + SvxULSpaceItem const& rItem{static_cast<SvxULSpaceItem const&>(rItm)}; + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetUpper()))); + itemNames.emplace_back("upper-margin"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLower()))); + itemNames.emplace_back("lower-margin"); + // TODO what does EE support here? + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = "EE_PARA_ULSPACE"; + break; + } + case EE_PARA_SBL: + { + SvxLineSpacingItem const& rItem{static_cast<SvxLineSpacingItem const&>(rItm)}; + switch (rItem.GetLineSpaceRule()) + { + case SvxLineSpaceRule::Auto: + break; + case SvxLineSpaceRule::Fix: + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLineHeight()))); + itemNames.emplace_back("line-space-fix"); + break; + case SvxLineSpaceRule::Min: + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLineHeight()))); + itemNames.emplace_back("line-space-min"); + break; + } + switch (rItem.GetInterLineSpaceRule()) + { + case SvxInterLineSpaceRule::Off: + break; + case SvxInterLineSpaceRule::Prop: + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetPropLineSpace()))); + itemNames.emplace_back("inter-line-space-prop"); + break; + case SvxInterLineSpaceRule::Fix: + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetInterLineSpace()))); + itemNames.emplace_back("inter-line-space-fix"); + break; + } + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = "EE_PARA_SBL"; + break; + } + case EE_PARA_JUST: + { + SvxAdjustItem const& rItem{static_cast<SvxAdjustItem const&>(rItm)}; + switch (rItem.GetAdjust()) + { + case SvxAdjust::Left: + case SvxAdjust::Right: + case SvxAdjust::Center: + attr = yinput_long(uint64_t(rItem.GetAdjust())); + break; + case SvxAdjust::Block: + switch (rItem.GetLastBlock()) + { + case SvxAdjust::Left: + attr = yinput_long(uint64_t(SvxAdjust::Block)); + break; + case SvxAdjust::Center: + attr = yinput_long(uint64_t(5)); + break; + case SvxAdjust::Block: + attr = yinput_long(uint64_t(rItem.GetOneWord() == SvxAdjust::Block ? 7 : 6)); + break; + default: + assert(false); + } + break; + default: + assert(false); + } + attrName = "EE_PARA_JUST"; + break; + } + case EE_PARA_TABS: + { + SvxTabStopItem const& rItem{static_cast<SvxTabStopItem const&>(rItm)}; + itemNames.emplace_back("default-distance"); + itemArray.emplace_back(yinput_long(uint64_t(rItem.GetDefaultDistance()))); + tabStopValues.reserve(rItem.Count()); // prevent realloc + for (decltype(rItem.Count()) i{0}; i < rItem.Count(); ++i) + { + SvxTabStop const& rTab{rItem.At(i)}; + char const*const names[]{"pos", "adjustment", "decimal", "fill"}; + tabStopValues.emplace_back(); + tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetTabPos()))); + tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetAdjustment()))); + tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetDecimal()))); + tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetFill()))); + tabStops.emplace_back(yinput_json_map(const_cast<char**>(names), tabStopValues.back().data(), 4)); + } + itemNames.emplace_back("tab-stops"); + itemArray.emplace_back(yinput_json_array(tabStops.data(), tabStops.size())); + attr = yinput_json_map(const_cast<char**>(itemNames.data()), itemArray.data(), itemArray.size()); + attrName = "EE_PARA_TABS"; + break; + } + case EE_PARA_JUST_METHOD: + { + SvxJustifyMethodItem const& rItem{static_cast<SvxJustifyMethodItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_PARA_JUST_METHOD"; + break; + } + case EE_PARA_VER_JUST: + { + SvxVerJustifyItem const& rItem{static_cast<SvxVerJustifyItem const&>(rItm)}; + attr = yinput_long(uint64_t(rItem.GetValue())); + attrName = "EE_PARA_VER_JUST"; + break; + } + // these aren't editable? +//constexpr TypedWhichId<SvXMLAttrContainerItem> EE_CHAR_XMLATTRIBS (EE_CHAR_START+27); +//constexpr TypedWhichId<SfxGrabBagItem> EE_CHAR_GRABBAG (EE_CHAR_START+30); + + default: + assert(false); + } + assert(itemNames.size() == itemArray.size()); + YInput const attrs{yinput_json_map(const_cast<char**>(&attrName), &attr, 1)}; + ytext_format(yw.pText, yw.pTxn, nStart, nLen, &attrs); +} + +void YrsInsertAttribImpl(YrsWrite const& yw, uint32_t const offset, EditCharAttrib const*const pAttr) +{ + auto const start{offset + pAttr->GetStart()}; + auto const len{pAttr->GetEnd() - pAttr->GetStart()}; + YrsInsertAttribImplImpl(yw, *pAttr->GetItem(), start, len); +} + +void YrsInsertFeature(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, EditCharAttrib const*const pAttr) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + i += pAttr->GetStart(); + char const feature[]{ CH_FEATURE, ' + switch (pAttr->Which()) + { + case EE_FEATURE_TAB: + case EE_FEATURE_LINEBR: + { + YInput const type{yinput_string(pAttr->Which() == EE_FEATURE_TAB ? "tab" : "line")}; + YInput attrArray[]{ type }; + char const*const attrNames[]{ "feature" }; + YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), attrArray, 1)}; + ytext_insert(yw.pText, yw.pTxn, i, feature, &attrs); + break; + } + case EE_FEATURE_FIELD: + { + SvxURLField const*const pURLField{dynamic_cast<SvxURLField const*>(dynamic_cast<SvxFieldItem const*>(pAttr->GetItem())->GetField())}; + assert(pURLField); + YInput const type{yinput_string("url")}; + // ??? somehow this comes out as Y_JSON_NUM at the other end? + YInput const format{yinput_long(static_cast<int64_t>(pURLField->GetFormat()))}; + OString const urlStr{OUStringToOString(pURLField->GetURL(), RTL_TEXTENCODING_UTF8)}; + YInput const url{yinput_string(urlStr.getStr())}; + OString const reprStr{OUStringToOString(pURLField->GetRepresentation(), RTL_TEXTENCODING_UTF8)}; + YInput const representation{yinput_string(reprStr.getStr())}; + OString const targetStr{OUStringToOString(pURLField->GetTargetFrame(), RTL_TEXTENCODING_UTF8)}; + YInput const targetframe{yinput_string(targetStr.getStr())}; + YInput attrArray[]{ type, format, url, representation, targetframe }; + char const*const attrNames[]{ "feature", "url-format", "url-url", "url-representation", "url-targetframe" }; + // don't use yinput_ymap for this! + YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), attrArray, 5)}; + ytext_insert(yw.pText, yw.pTxn, i, feature, &attrs); + + break; + } + default: // EE_FEATURE_NOTCONV appears unused? + assert(false); + } +} + +void YrsAddPara(IYrsTransactionSupplier *const pYrsSupplier, + OString const& rCommentId, EditDoc const& rDoc, uint32_t const index) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + SAL_DEBUG("YRS YrsAddPara"); + // need to encode into 1 YText + char const para[]{ CH_PARA, ' + uint32_t i{0}; + // UTF-16 index should be equal to EditDoc one + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + ContentAttribs const& rParaAttribs{rDoc.GetObject(index)->GetContentAttribs()}; + auto const pStyle{rParaAttribs.GetStyleSheet()}; + if (pStyle) + { + OString const styleName{OUStringToOString(pStyle->GetName(), RTL_TEXTENCODING_UTF8)}; + YInput const style{yinput_string(styleName.getStr())}; + YInput attrArray[]{ style }; + char const*const attrNames[]{ "para-style" }; + YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), attrArray, 1)}; + ytext_insert(yw.pText, yw.pTxn, i, para, &attrs); + } + else + { + ytext_insert(yw.pText, yw.pTxn, i, para, nullptr); + } + for (SfxItemIter it{rParaAttribs.GetItems()}; !it.IsAtEnd(); it.NextItem()) + { + YrsInsertAttribImplImpl(yw, *it.GetCurItem(), i, 1); + } +} + +void YrsRemovePara(IYrsTransactionSupplier *const pYrsSupplier, + OString const& rCommentId, EditDoc const& rDoc, uint32_t const index) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + if (index != 0) + { + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + } + uint32_t const len(rDoc.GetObject(index)->Len() + 1); + ytext_remove_range(yw.pText, yw.pTxn, i, len); +} + +void YrsClear(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + auto const len{ytext_len(yw.pText, yw.pTxn)}; + ytext_remove_range(yw.pText, yw.pTxn, 0, len); +} + +void YrsInsertParaBreak(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, uint32_t const content) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + // need to encode into 1 YText + char const para[]{ CH_PARA, ' + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + i += content; + ContentAttribs const& rParaAttribs{rDoc.GetObject(index)->GetContentAttribs()}; + OString const styleName{OUStringToOString(rParaAttribs.GetStyleSheet()->GetName(), RTL_TEXTENCODING_UTF8)}; + YInput const style{yinput_string(styleName.getStr())}; + YInput attrArray[]{ style }; + char const*const attrNames[]{ "para-style" }; + YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), attrArray, 1)}; + ytext_insert(yw.pText, yw.pTxn, i, para, &attrs); + for (SfxItemIter it{rParaAttribs.GetItems()}; !it.IsAtEnd(); it.NextItem()) + { + YrsInsertAttribImplImpl(yw, *it.GetCurItem(), i, 1); + } +} + +void YrsInsertText(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, uint32_t const content, ::std::u16string_view const rText) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + i += content; + OString const text{::rtl::OUStringToOString(rText, RTL_TEXTENCODING_UTF8)}; + ytext_insert(yw.pText, yw.pTxn, i, text.getStr(), nullptr); +} + +void YrsConnectPara(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, uint32_t const pos) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + i += pos; + ytext_remove_range(yw.pText, yw.pTxn, i, 1); +} + +void YrsRemoveChars(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, uint32_t const content, uint32_t const length) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + i += content; + ytext_remove_range(yw.pText, yw.pTxn, i, length); +} + +void YrsSetStyle(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, ::std::u16string_view const rStyle) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + i += rDoc.GetObject(index)->Len(); + OString const styleName{OUStringToOString(rStyle, RTL_TEXTENCODING_UTF8)}; + YInput const style{yinput_string(styleName.getStr())}; + YInput attrArray[]{ style }; + char const*const attrNames[]{ "para-style" }; + YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), attrArray, 1)}; + ytext_format(yw.pText, yw.pTxn, i, 1, &attrs); +} + +void YrsSetParaAttr(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, SfxPoolItem const& rItem) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + i += rDoc.GetObject(index)->Len(); + YrsInsertAttribImplImpl(yw, rItem, i, 1); +} + +char const* YrsWhichToAttrName(sal_Int16 const nWhich) +{ + switch (nWhich) + { + case EE_CHAR_COLOR: + return "EE_CHAR_COLOR"; + case EE_CHAR_BKGCOLOR: + return "EE_CHAR_BKGCOLOR"; + case EE_CHAR_FONTINFO: + return "EE_CHAR_FONTINFO"; + case EE_CHAR_FONTINFO_CJK: + return "EE_CHAR_FONTINFO_CJK"; + case EE_CHAR_FONTINFO_CTL: + return "EE_CHAR_FONTINFO_CTL"; + case EE_CHAR_FONTHEIGHT: + return "EE_CHAR_FONTHEIGHT"; + case EE_CHAR_FONTHEIGHT_CJK: + return "EE_CHAR_FONTHEIGHT_CJK"; + case EE_CHAR_FONTHEIGHT_CTL: + return "EE_CHAR_FONTHEIGHT_CTL"; + case EE_CHAR_FONTWIDTH: + return "EE_CHAR_FONTWIDTH"; + case EE_CHAR_WEIGHT: + return "EE_CHAR_WEIGHT"; + case EE_CHAR_WEIGHT_CJK: + return "EE_CHAR_WEIGHT_CJK"; + case EE_CHAR_WEIGHT_CTL: + return "EE_CHAR_WEIGHT_CTL"; + case EE_CHAR_UNDERLINE: + return "EE_CHAR_UNDERLINE"; + case EE_CHAR_OVERLINE: + return "EE_CHAR_OVERLINE"; + case EE_CHAR_STRIKEOUT: + return "EE_CHAR_STRIKEOUT"; + case EE_CHAR_ITALIC: + return "EE_CHAR_ITALIC"; + case EE_CHAR_ITALIC_CJK: + return "EE_CHAR_ITALIC_CJK"; + case EE_CHAR_ITALIC_CTL: + return "EE_CHAR_ITALIC_CTL"; + case EE_CHAR_OUTLINE: + return "EE_CHAR_OUTLINE"; + case EE_CHAR_SHADOW: + return "EE_CHAR_SHADOW"; + case EE_CHAR_ESCAPEMENT: + return "EE_CHAR_ESCAPEMENT"; + case EE_CHAR_PAIRKERNING: + return "EE_CHAR_PAIRKERNING"; + case EE_CHAR_KERNING: + return "EE_CHAR_KERNING"; + case EE_CHAR_WLM: + return "EE_CHAR_WLM"; + case EE_CHAR_LANGUAGE: + return "EE_CHAR_LANGUAGE"; + case EE_CHAR_LANGUAGE_CJK: + return "EE_CHAR_LANGUAGE_CJK"; + case EE_CHAR_LANGUAGE_CTL: + return "EE_CHAR_LANGUAGE_CTL"; + case EE_CHAR_EMPHASISMARK: + return "EE_CHAR_EMPHASISMARK"; + case EE_CHAR_RELIEF: + return "EE_CHAR_RELIEF"; + case EE_CHAR_CASEMAP: + return "EE_CHAR_CASEMAP"; + case EE_PARA_WRITINGDIR: + return "EE_PARA_WRITINGDIR"; + case EE_PARA_HANGINGPUNCTUATION: + return "EE_PARA_HANGINGPUNCTUATION"; + case EE_PARA_FORBIDDENRULES: + return "EE_PARA_FORBIDDENRULES"; + case EE_PARA_ASIANCJKSPACING: + return "EE_PARA_ASIANCJKSPACING"; +//TODO complex, but apparently no way to set this in comment? inline constexpr TypedWhichId<SvxNumBulletItem> EE_PARA_NUMBULLET (EE_PARA_START+5); + case EE_PARA_HYPHENATE: + return "EE_PARA_HYPHENATE"; + case EE_PARA_HYPHENATE_NO_CAPS: + return "EE_PARA_HYPHENATE_NO_CAPS"; + case EE_PARA_HYPHENATE_NO_LAST_WORD: + return "EE_PARA_HYPHENATE_NO_LAST_WORD"; + case EE_PARA_BULLETSTATE: + return "EE_PARA_BULLETSTATE"; +//TODO no way to set this in comment? inline constexpr TypedWhichId<SvxLRSpaceItem> EE_PARA_OUTLLRSPACE (EE_PARA_START+10); + case EE_PARA_OUTLLEVEL: + return "EE_PARA_OUTLLEVEL"; +//TODO complex, but apparently no way to set this in comment? inline constexpr TypedWhichId<SvxBulletItem> EE_PARA_BULLET (EE_PARA_START+12); + case EE_PARA_LRSPACE: + return "EE_PARA_LRSPACE"; + case EE_PARA_ULSPACE: + return "EE_PARA_ULSPACE"; + case EE_PARA_SBL: + return "EE_PARA_SBL"; + case EE_PARA_JUST: + return "EE_PARA_JUST"; + case EE_PARA_TABS: + return "EE_PARA_TABS"; + case EE_PARA_JUST_METHOD: + return "EE_PARA_JUST_METHOD"; + case EE_PARA_VER_JUST: + return "EE_PARA_VER_JUST"; + default: + assert(false); + } +} + +void YrsRemoveAttrib(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, + EditDoc const& rDoc, uint32_t const index, sal_uInt16 const nWhich, sal_Int32 const nStart, sal_Int32 const nEnd) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + YInput const attr{yinput_null()}; + char const*const attrName{YrsWhichToAttrName(nWhich)}; + YInput const attrs{yinput_json_map(const_cast<char**>(&attrName), const_cast<YInput*>(&attr), 1)}; + ytext_format(yw.pText, yw.pTxn, i + nStart, nEnd - nStart, &attrs); +} + +void YrsInsertAttrib(IYrsTransactionSupplier *const pYrsSupplier, OString const& rCommentId, EditDoc const& rDoc, uint32_t const index, EditCharAttrib const*const pAttr) +{ + YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)}; + if (yw.pTxn == nullptr) + { + return; + } + uint32_t i{0}; + for (auto paras{index}; paras != 0; --paras) + { + i += rDoc.GetObject(paras-1)->Len() + 1; + } + YrsInsertAttribImpl(yw, i, pAttr); +} + +uint64_t YrsReadInt(YOutput const& rValue) +{ + // with the v1 encoding, JSON is being sent apparently (like "family":2) , which has issues with integers being sometimes read as floats so workaround here + if (rValue.tag == Y_JSON_INT) + { + return rValue.value.integer; + } + else + { + yvalidate(rValue.tag == Y_JSON_NUM); + return ::std::lround(rValue.value.num); + } +} + +void YrsImplInsertAttr(SfxItemSet & rSet, ::std::vector<sal_uInt16> *const pRemoved, + char const*const pKey, YOutput const& rValue) +{ + sal_uInt16 nWhich{0}; + if (strcmp(pKey, "EE_CHAR_COLOR") == 0) + { + nWhich = EE_CHAR_COLOR; + } + else if (strcmp(pKey, "EE_CHAR_BKGCOLOR") == 0) + { + nWhich = EE_CHAR_BKGCOLOR; + } + else if (strcmp(pKey, "EE_CHAR_FONTINFO") == 0) + { + nWhich = EE_CHAR_FONTINFO; + } + else if (strcmp(pKey, "EE_CHAR_FONTINFO_CJK") == 0) + { + nWhich = EE_CHAR_FONTINFO_CJK; + } + else if (strcmp(pKey, "EE_CHAR_FONTINFO_CTL") == 0) + { + nWhich = EE_CHAR_FONTINFO_CTL; + } + else if (strcmp(pKey, "EE_CHAR_FONTHEIGHT") == 0) + { + nWhich = EE_CHAR_FONTHEIGHT; + } + else if (strcmp(pKey, "EE_CHAR_FONTHEIGHT_CJK") == 0) + { + nWhich = EE_CHAR_FONTHEIGHT_CJK; + } + else if (strcmp(pKey, "EE_CHAR_FONTHEIGHT_CTL") == 0) + { + nWhich = EE_CHAR_FONTHEIGHT_CTL; + } + else if (strcmp(pKey, "EE_CHAR_FONTWIDTH") == 0) + { + nWhich = EE_CHAR_FONTWIDTH; + } + else if (strcmp(pKey, "EE_CHAR_WEIGHT") == 0) + { + nWhich = EE_CHAR_WEIGHT; + } + else if (strcmp(pKey, "EE_CHAR_WEIGHT_CJK") == 0) + { + nWhich = EE_CHAR_WEIGHT_CJK; + } + else if (strcmp(pKey, "EE_CHAR_WEIGHT_CTL") == 0) + { + nWhich = EE_CHAR_WEIGHT_CTL; + } + else if (strcmp(pKey, "EE_CHAR_UNDERLINE") == 0) + { + nWhich = EE_CHAR_UNDERLINE; + } + else if (strcmp(pKey, "EE_CHAR_OVERLINE") == 0) + { + nWhich = EE_CHAR_OVERLINE; + } + else if (strcmp(pKey, "EE_CHAR_STRIKEOUT") == 0) + { + nWhich = EE_CHAR_STRIKEOUT; + } + else if (strcmp(pKey, "EE_CHAR_ITALIC") == 0) + { + nWhich = EE_CHAR_ITALIC; + } + else if (strcmp(pKey, "EE_CHAR_ITALIC_CJK") == 0) + { + nWhich = EE_CHAR_ITALIC_CJK; + } + else if (strcmp(pKey, "EE_CHAR_ITALIC_CTL") == 0) + { + nWhich = EE_CHAR_ITALIC_CTL; + } + else if (strcmp(pKey, "EE_CHAR_OUTLINE") == 0) + { + nWhich = EE_CHAR_OUTLINE; + } + else if (strcmp(pKey, "EE_CHAR_SHADOW") == 0) + { + nWhich = EE_CHAR_SHADOW; + } + else if (strcmp(pKey, "EE_CHAR_ESCAPEMENT") == 0) + { + nWhich = EE_CHAR_ESCAPEMENT; + } + else if (strcmp(pKey, "EE_CHAR_PAIRKERNING") == 0) + { + nWhich = EE_CHAR_PAIRKERNING; + } + else if (strcmp(pKey, "EE_CHAR_KERNING") == 0) + { + nWhich = EE_CHAR_KERNING; + } + else if (strcmp(pKey, "EE_CHAR_WLM") == 0) + { + nWhich = EE_CHAR_WLM; + } + else if (strcmp(pKey, "EE_CHAR_LANGUAGE") == 0) + { + nWhich = EE_CHAR_LANGUAGE; + } + else if (strcmp(pKey, "EE_CHAR_LANGUAGE_CJK") == 0) + { + nWhich = EE_CHAR_LANGUAGE_CJK; + } + else if (strcmp(pKey, "EE_CHAR_LANGUAGE_CTL") == 0) + { + nWhich = EE_CHAR_LANGUAGE_CTL; + } + else if (strcmp(pKey, "EE_CHAR_EMPHASISMARK") == 0) + { + nWhich = EE_CHAR_EMPHASISMARK; + } + else if (strcmp(pKey, "EE_CHAR_RELIEF") == 0) + { + nWhich = EE_CHAR_RELIEF; + } + else if (strcmp(pKey, "EE_CHAR_CASEMAP") == 0) + { + nWhich = EE_CHAR_CASEMAP; + } + else if (strcmp(pKey, "EE_PARA_WRITINGDIR") == 0) + { + nWhich = EE_PARA_WRITINGDIR; + } + else if (strcmp(pKey, "EE_PARA_HANGINGPUNCTUATION") == 0) + { + nWhich = EE_PARA_HANGINGPUNCTUATION; + } + else if (strcmp(pKey, "EE_PARA_FORBIDDENRULES") == 0) + { + nWhich = EE_PARA_FORBIDDENRULES; + } + else if (strcmp(pKey, "EE_PARA_ASIANCJKSPACING") == 0) + { + nWhich = EE_PARA_ASIANCJKSPACING; + } + else if (strcmp(pKey, "EE_PARA_HYPHENATE") == 0) + { + nWhich = EE_PARA_HYPHENATE; + } + else if (strcmp(pKey, "EE_PARA_HYPHENATE_NO_CAPS") == 0) + { + nWhich = EE_PARA_HYPHENATE_NO_CAPS; + } + else if (strcmp(pKey, "EE_PARA_HYPHENATE_NO_LAST_WORD") == 0) + { + nWhich = EE_PARA_HYPHENATE_NO_LAST_WORD; + } + else if (strcmp(pKey, "EE_PARA_BULLETSTATE") == 0) + { + nWhich = EE_PARA_BULLETSTATE; + } + else if (strcmp(pKey, "EE_PARA_OUTLLEVEL") == 0) + { + nWhich = EE_PARA_OUTLLEVEL; + } + else if (strcmp(pKey, "EE_PARA_LRSPACE") == 0) + { + nWhich = EE_PARA_LRSPACE; + } + else if (strcmp(pKey, "EE_PARA_ULSPACE") == 0) + { + nWhich = EE_PARA_ULSPACE; + } + else if (strcmp(pKey, "EE_PARA_SBL") == 0) + { + nWhich = EE_PARA_SBL; + } + else if (strcmp(pKey, "EE_PARA_JUST") == 0) + { + nWhich = EE_PARA_JUST; + } + else if (strcmp(pKey, "EE_PARA_TABS") == 0) + { + nWhich = EE_PARA_TABS; + } + else if (strcmp(pKey, "EE_PARA_JUST_METHOD") == 0) + { + nWhich = EE_PARA_JUST_METHOD; + } + else if (strcmp(pKey, "EE_PARA_VER_JUST") == 0) + { + nWhich = EE_PARA_VER_JUST; + } + else if (pKey[0] == 'E' && pKey[1] == 'E' && pKey[2] == '_') + { + abort(); + } + else + { + return; + } + + if (rValue.tag == Y_JSON_NULL) + { + assert(pRemoved); + if (pRemoved) + { + pRemoved->emplace_back(nWhich); + } + return; + } + else switch (nWhich) + { + case EE_CHAR_COLOR: + case EE_CHAR_BKGCOLOR: + { + Color const c(ColorTransparency, YrsReadInt(rValue)); + SvxColorItem const item(c, nWhich); + rSet.Put(item); + break; + } + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<OUString> oFamilyName; + ::std::optional<OUString> oStyle; + ::std::optional<FontFamily> oFamily; + ::std::optional<FontPitch> oPitch; + ::std::optional<rtl_TextEncoding> oCharset; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "familyname") == 0) + { + yvalidate(rValue.value.map[i].value->tag == Y_JSON_STR); + oFamilyName.emplace(OStringToOUString(rValue.value.map[i].value->value.str, RTL_TEXTENCODING_UTF8)); + } + else if (strcmp(pEntry, "style") == 0) + { + yvalidate(rValue.value.map[i].value->tag == Y_JSON_STR); + oStyle.emplace(OStringToOUString(rValue.value.map[i].value->value.str, RTL_TEXTENCODING_UTF8)); + } + else if (strcmp(pEntry, "family") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(FAMILY_DONTKNOW <= value && value <= FAMILY_SYSTEM); + oFamily.emplace(FontFamily(value)); + } + else if (strcmp(pEntry, "pitch") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(PITCH_DONTKNOW <= value && value <= PITCH_VARIABLE); + oPitch.emplace(FontPitch(value)); + } + else if (strcmp(pEntry, "charset") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*RTL_TEXTENCODING_DONTKNOW <= value &&*/ value <= RTL_TEXTENCODING_UNICODE); + oCharset.emplace(rtl_TextEncoding(value)); + } + else yvalidate(false); + } + if (oFamilyName && oStyle && oFamily && oPitch && oCharset) + { + SvxFontItem const item{ + *oFamily, *oFamilyName, *oStyle, *oPitch, *oCharset, nWhich}; + rSet.Put(item); + } + else yvalidate(false); + break; + } + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<sal_uInt32> oHeight; + ::std::optional<sal_uInt16> oProp; + ::std::optional<MapUnit> oMapUnit; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "height") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_INT32); + oHeight.emplace(value); + } + else if (strcmp(pEntry, "prop") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_INT16); + oProp.emplace(value); + } + else if (strcmp(pEntry, "propunit") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= ::std::underlying_type_t<MapUnit>(MapUnit::LAST)); + oMapUnit.emplace(MapUnit(value)); + } + else yvalidate(false); + } + if (oHeight && oProp && oMapUnit) + { + SvxFontHeightItem item{*oHeight, 100, nWhich}; + item.SetProp(*oProp, *oMapUnit); + rSet.Put(item); + } + else yvalidate(false); + break; + } + case EE_CHAR_FONTWIDTH: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_INT16); + SvxCharScaleWidthItem const item{sal_uInt16(value), TypedWhichId<SvxCharScaleWidthItem>(nWhich)}; + rSet.Put(item); + break; + } + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(WEIGHT_DONTKNOW <= value && value <= WEIGHT_BLACK); + SvxWeightItem const item{FontWeight(value), nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_UNDERLINE: + case EE_CHAR_OVERLINE: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<FontLineStyle> oStyle; + ::std::optional<Color> oColor; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "style") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(LINESTYLE_NONE <= value && value <= LINESTYLE_BOLDWAVE); + oStyle.emplace(FontLineStyle(value)); + } + else if (strcmp(pEntry, "color") == 0) + { + oColor.emplace(ColorTransparency, YrsReadInt(*rValue.value.map[i].value)); + } + else yvalidate(false); + } + if (oStyle && oColor) + { + if (nWhich == EE_CHAR_UNDERLINE) + { + SvxUnderlineItem item{*oStyle, EE_CHAR_UNDERLINE}; + item.SetColor(*oColor); + rSet.Put(item); + } + else + { + SvxOverlineItem item{*oStyle, EE_CHAR_OVERLINE}; + item.SetColor(*oColor); + rSet.Put(item); + } + } + else yvalidate(false); + break; + } + case EE_CHAR_STRIKEOUT: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(STRIKEOUT_NONE <= value && value <= STRIKEOUT_X); + SvxCrossedOutItem const item{FontStrikeout(value), nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(ITALIC_NONE <= value && value <= ITALIC_DONTKNOW); + SvxPostureItem const item{FontItalic(value), nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_OUTLINE: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxContourItem const item{rValue.value.flag == Y_TRUE, nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_SHADOW: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxShadowedItem const item{rValue.value.flag == Y_TRUE, nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_ESCAPEMENT: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<short> oEsc; + ::std::optional<sal_uInt8> oProp; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "esc") == 0) + { + int64_t const value{static_cast<int64_t>(YrsReadInt(*rValue.value.map[i].value))}; + yvalidate(SAL_MIN_INT16 <= value && value <= SAL_MAX_INT16); + oEsc.emplace(short(value)); + } + else if (strcmp(pEntry, "prop") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT8); + oProp.emplace(sal_uInt8(value)); + } + else yvalidate(false); + } + if (oEsc && oProp) + { + SvxEscapementItem const item{*oEsc, *oProp, EE_CHAR_ESCAPEMENT}; + rSet.Put(item); + } + else yvalidate(false); + break; + } + case EE_CHAR_PAIRKERNING: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxAutoKernItem const item{rValue.value.flag == Y_TRUE, nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_KERNING: + { + int64_t const value{static_cast<int64_t>(YrsReadInt(rValue))}; + yvalidate(SAL_MIN_INT16 <= value && value <= SAL_MAX_INT16); + SvxKerningItem const item{short(value), nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_WLM: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxWordLineModeItem const item{rValue.value.flag == Y_TRUE, nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_LANGUAGE: + case EE_CHAR_LANGUAGE_CJK: + case EE_CHAR_LANGUAGE_CTL: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16); + SvxLanguageItem const item{LanguageType(value), nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_EMPHASISMARK: + { + auto const value{YrsReadInt(rValue)}; + yvalidate((value & 0x300f) == value); + SvxEmphasisMarkItem const item{FontEmphasisMark(value), TypedWhichId<SvxEmphasisMarkItem>(nWhich)}; + rSet.Put(item); + break; + } + case EE_CHAR_RELIEF: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(::std::underlying_type_t<FontRelief>(FontRelief::NONE) <= value && value <= ::std::underlying_type_t<FontRelief>(FontRelief::Engraved)); + SvxCharReliefItem const item{FontRelief(value), nWhich}; + rSet.Put(item); + break; + } + case EE_CHAR_CASEMAP: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(::std::underlying_type_t<SvxCaseMap>(SvxCaseMap::NotMapped) <= value && value < ::std::underlying_type_t<SvxCaseMap>(SvxCaseMap::End)); + SvxCaseMapItem const item{SvxCaseMap(value), nWhich}; + rSet.Put(item); + break; + } + case EE_PARA_WRITINGDIR: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(::std::underlying_type_t<SvxFrameDirection>(SvxFrameDirection::Horizontal_LR_TB) <= value && value < ::std::underlying_type_t<SvxFrameDirection>(SvxFrameDirection::Stacked)); + SvxFrameDirectionItem const item{SvxFrameDirection(value), nWhich}; + rSet.Put(item); + break; + } + case EE_PARA_HANGINGPUNCTUATION: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxHangingPunctuationItem const item{rValue.value.flag == Y_TRUE, nWhich}; + rSet.Put(item); + break; + } + case EE_PARA_FORBIDDENRULES: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxForbiddenRuleItem const item{rValue.value.flag == Y_TRUE, nWhich}; + rSet.Put(item); + break; + } + case EE_PARA_ASIANCJKSPACING: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SvxScriptSpaceItem const item{rValue.value.flag == Y_TRUE, nWhich}; + rSet.Put(item); + break; + } + case EE_PARA_HYPHENATE: + case EE_PARA_HYPHENATE_NO_CAPS: + case EE_PARA_HYPHENATE_NO_LAST_WORD: + case EE_PARA_BULLETSTATE: + { + yvalidate(rValue.tag == Y_JSON_BOOL); + SfxBoolItem const item{nWhich, rValue.value.flag == Y_TRUE}; + rSet.Put(item); + break; + } + case EE_PARA_OUTLLEVEL: + { + int64_t const value{static_cast<int64_t>(YrsReadInt(rValue))}; + yvalidate(-1 <= value && value < SVX_MAX_NUM); + SfxInt16Item const item{nWhich, sal_Int16(value)}; + rSet.Put(item); + break; + } + case EE_PARA_LRSPACE: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<double> oFirstLineIndent; + ::std::optional<sal_Int16> oFirstLineIndentUnit; + ::std::optional<double> oLeft; + ::std::optional<sal_Int16> oLeftUnit; + ::std::optional<double> oRight; + ::std::optional<sal_Int16> oRightUnit; + ::std::optional<bool> oAutoFirst; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "first-line-offset") == 0) + { + yvalidate(rValue.value.map[i].value->tag == Y_JSON_NUM); + oFirstLineIndent.emplace(rValue.value.map[i].value->value.num); + } + else if (strcmp(pEntry, "first-line-offset-unit") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(css::util::MeasureUnit::MM_100TH <= value && value <= css::util::MeasureUnit::FONT_CJK_ADVANCE); + oFirstLineIndentUnit.emplace(sal_Int16(value)); + } + else if (strcmp(pEntry, "left-margin") == 0) + { + yvalidate(rValue.value.map[i].value->tag == Y_JSON_NUM); + oLeft.emplace(rValue.value.map[i].value->value.num); + } + else if (strcmp(pEntry, "left-margin-unit") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(css::util::MeasureUnit::MM_100TH <= value && value <= css::util::MeasureUnit::FONT_CJK_ADVANCE); + oLeftUnit.emplace(sal_Int16(value)); + } + else if (strcmp(pEntry, "right-margin") == 0) + { + yvalidate(rValue.value.map[i].value->tag == Y_JSON_NUM); + oRight.emplace(rValue.value.map[i].value->value.num); + } + else if (strcmp(pEntry, "right-margin-unit") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(css::util::MeasureUnit::MM_100TH <= value && value <= css::util::MeasureUnit::FONT_CJK_ADVANCE); + oRightUnit.emplace(sal_Int16(value)); + } + else if (strcmp(pEntry, "auto-first") == 0) + { + yvalidate(rValue.value.map[i].value->tag == Y_JSON_BOOL); + oAutoFirst.emplace(rValue.value.map[i].value->value.flag == Y_TRUE); + } + else yvalidate(false); + } + if (oFirstLineIndent && oFirstLineIndentUnit && oAutoFirst.has_value() + && oLeft && oLeftUnit && oRight && oRightUnit) + { + SvxLRSpaceItem item{{*oLeft, *oLeftUnit}, {*oRight, *oRightUnit}, {*oFirstLineIndent, *oFirstLineIndentUnit}, EE_PARA_LRSPACE}; + item.SetAutoFirst(*oAutoFirst); + rSet.Put(item); + } + else yvalidate(false); + break; + } + case EE_PARA_ULSPACE: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<sal_uInt16> oUpper; + ::std::optional<sal_uInt16> oLower; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "upper-margin") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16); + oUpper.emplace(sal_uInt16(value)); + } + else if (strcmp(pEntry, "lower-margin") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16); + oLower.emplace(sal_uInt16(value)); + } + else yvalidate(false); + } + if (oUpper && oLower) + { + SvxULSpaceItem const item{*oUpper, *oLower, EE_PARA_ULSPACE}; + rSet.Put(item); + } + else yvalidate(false); + break; + } + case EE_PARA_SBL: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<sal_uInt16> oLineSpaceFix; + ::std::optional<sal_uInt16> oLineSpaceMin; + ::std::optional<sal_uInt16> oInterLineSpaceProp; + ::std::optional<sal_Int16> oInterLineSpaceFix; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "line-space-fix") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16); + oLineSpaceFix.emplace(sal_uInt16(value)); + } + else if (strcmp(pEntry, "line-space-min") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16); + oLineSpaceMin.emplace(sal_uInt16(value)); + } + else if (strcmp(pEntry, "inter-line-space-prop") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16); + oInterLineSpaceProp.emplace(sal_uInt16(value)); + } + else if (strcmp(pEntry, "inter-line-space-fix") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16); + oInterLineSpaceFix.emplace(sal_Int16(value)); + } + else yvalidate(false); + } + SvxLineSpacingItem item{0, EE_PARA_SBL}; + if (oLineSpaceFix) + { + item.SetLineHeight(*oLineSpaceFix); + item.SetLineSpaceRule(SvxLineSpaceRule::Fix); + rSet.Put(item); + } + else if (oLineSpaceMin) + { + item.SetLineHeight(*oLineSpaceMin); + rSet.Put(item); + } + else if (oInterLineSpaceProp) + { + item.SetPropLineSpace(*oInterLineSpaceProp); + rSet.Put(item); + } + else if (oInterLineSpaceFix) + { + item.SetInterLineSpace(*oInterLineSpaceFix); + rSet.Put(item); + } + else yvalidate(false); + break; + } + case EE_PARA_JUST: + { + auto const value{YrsReadInt(rValue)}; + SvxAdjustItem item{SvxAdjust(), nWhich}; + switch (value) + { + case uint64_t(SvxAdjust::Left): + case uint64_t(SvxAdjust::Right): + case uint64_t(SvxAdjust::Center): + item.SetAdjust(SvxAdjust(value)); + break; + case uint64_t(SvxAdjust::Block): + item.SetAdjust(SvxAdjust(value)); + item.SetLastBlock(SvxAdjust::Left); + break; + case 5: + item.SetAdjust(SvxAdjust::Block); + item.SetLastBlock(SvxAdjust::Center); + break; + case 6: + item.SetAdjust(SvxAdjust::Block); + item.SetLastBlock(SvxAdjust::Block); + item.SetOneWord(SvxAdjust::Left); + break; + case 7: + item.SetAdjust(SvxAdjust::Block); + item.SetLastBlock(SvxAdjust::Block); + item.SetOneWord(SvxAdjust::Block); + break; + default: + abort(); + } + rSet.Put(item); + break; + } + case EE_PARA_TABS: + { + yvalidate(rValue.tag == Y_JSON_MAP); + ::std::optional<sal_Int32> oDefault; + ::std::optional<::std::vector<SvxTabStop>> oTabs; + for (decltype(rValue.len) i = 0; i < rValue.len; ++i) + { + const char*const pEntry{rValue.value.map[i].key}; + if (strcmp(pEntry, "default-distance") == 0) + { + auto const value{YrsReadInt(*rValue.value.map[i].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_INT32); + oDefault.emplace(sal_Int32(value)); + } + else if (strcmp(pEntry, "tab-stops") == 0) + { + oTabs.emplace(); + yvalidate(rValue.value.map[i].value->tag == Y_JSON_ARR); + YOutput const& rArray{*rValue.value.map[i].value}; + for (decltype(rArray.len) j = 0; j < rArray.len; ++j) + { + YOutput const& rMap{rArray.value.array[j]}; + yvalidate(rMap.tag == Y_JSON_MAP); + ::std::optional<sal_Int32> oPos; + ::std::optional<SvxTabAdjust> oAdjust; + ::std::optional<sal_Unicode> oDecimal; + ::std::optional<sal_Unicode> oFill; + for (decltype(rMap.len) k = 0; k < rMap.len; ++k) + { + const char*const pE{rMap.value.map[k].key}; + if (strcmp(pE, "pos") == 0) + { + auto const value{YrsReadInt(*rMap.value.map[k].value)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_INT32); + oPos.emplace(sal_Int32(value)); + } + else if (strcmp(pE, "adjustment") == 0) + { + auto const value{YrsReadInt(*rMap.value.map[k].value)}; + yvalidate(::std::underlying_type_t<SvxTabAdjust>(SvxTabAdjust::Left) <= value && value < ::std::underlying_type_t<SvxTabAdjust>(SvxTabAdjust::End)); + oAdjust.emplace(SvxTabAdjust(value)); + } + else if (strcmp(pE, "decimal") == 0) + { + auto const value{YrsReadInt(*rMap.value.map[k].value)}; + yvalidate(/*0 <= value && */value < SAL_MAX_UINT16); + oDecimal.emplace(sal_Unicode(value)); + } + else if (strcmp(pE, "fill") == 0) + { + auto const value{YrsReadInt(*rMap.value.map[k].value)}; + yvalidate(/*0 <= value && */value < SAL_MAX_UINT16); + oFill.emplace(sal_Unicode(value)); + } + else yvalidate(false); + } + if (oPos && oAdjust && oDecimal && oFill) + { + oTabs->emplace_back(*oPos, *oAdjust, *oDecimal, *oFill); + } + else yvalidate(false); + } + } + } + if (oDefault && oTabs) + { + SvxTabStopItem item{nWhich}; + item.SetDefaultDistance(*oDefault); + for (SvxTabStop const& it : *oTabs) + { + item.Insert(it); + } + rSet.Put(item); + } + else yvalidate(false); + break; + } + case EE_PARA_JUST_METHOD: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(::std::underlying_type_t<SvxCellJustifyMethod>(SvxCellJustifyMethod::Auto) <= value && value <= ::std::underlying_type_t<SvxCellJustifyMethod>(SvxCellJustifyMethod::Distribute)); + SvxJustifyMethodItem const item{SvxCellJustifyMethod(value), nWhich}; + rSet.Put(item); + break; + } + case EE_PARA_VER_JUST: + { + auto const value{YrsReadInt(rValue)}; + yvalidate(::std::underlying_type_t<SvxCellVerJustify>(SvxCellVerJustify::Standard) <= value && value <= ::std::underlying_type_t<SvxCellVerJustify>(SvxCellVerJustify::Block)); + SvxVerJustifyItem const item{SvxCellVerJustify(value), nWhich}; + rSet.Put(item); + break; + } + + default: + assert(false); + } +} + +// TODO: this could be a lot simpler if feature were a nested json-map like attr +template<typename T> void +YrsImplInsertFeature(ImpEditEngine & rIEE, + EditPaM const& rPam, + T const*const elements, uint32_t const len, + ::std::function<::std::pair<char const*, YOutput const&>(T const&)> pGetAttr) +{ + ::std::optional<OUString> oFeature; + ::std::optional<SvxURLFormat> oFormat; + ::std::optional<OUString> oURL; + ::std::optional<OUString> oRepresentation; + ::std::optional<OUString> oTargetFrame; + for (::std::remove_const_t<decltype(len)> k = 0; k < len; ++k) + { + T const& rAttr{elements[k]}; + auto [pKey, rValue]{pGetAttr(rAttr)}; + if (strcmp(pKey, "feature") == 0) + { + yvalidate(rValue.tag == Y_JSON_STR); + oFeature.emplace(OStringToOUString(rValue.value.str, RTL_TEXTENCODING_UTF8)); + } + else if (strcmp(pKey, "url-format") == 0) + { + uint64_t const value{YrsReadInt(rValue)}; + switch (value) + { + case ::std::underlying_type_t<SvxURLFormat>(SvxURLFormat::AppDefault): + case ::std::underlying_type_t<SvxURLFormat>(SvxURLFormat::Url): + case ::std::underlying_type_t<SvxURLFormat>(SvxURLFormat::Repr): + oFormat.emplace(SvxURLFormat(value)); + break; + default: + yvalidate(false); + } + } + else if (strcmp(pKey, "url-url") == 0) + { + yvalidate(rValue.tag == Y_JSON_STR); + oURL.emplace(OStringToOUString(rValue.value.str, RTL_TEXTENCODING_UTF8)); + } + else if (strcmp(pKey, "url-representation") == 0) + { + yvalidate(rValue.tag == Y_JSON_STR); + oRepresentation.emplace(OStringToOUString(rValue.value.str, RTL_TEXTENCODING_UTF8)); + } + else if (strcmp(pKey, "url-targetframe") == 0) + { + yvalidate(rValue.tag == Y_JSON_STR); + oTargetFrame.emplace(OStringToOUString(rValue.value.str, RTL_TEXTENCODING_UTF8)); + } + } + if (oFeature && *oFeature == "tab") + { + EditSelection const sel{rPam}; + rIEE.InsertTab(sel); + } + else if (oFeature && *oFeature == "line") + { + EditSelection const sel{rPam}; + rIEE.InsertLineBreak(sel); + } + else if (oFeature && *oFeature == "url") + { + if (oFormat && oURL && oRepresentation) + { + SvxURLField field{*oURL, *oRepresentation, *oFormat}; + if (oTargetFrame) + { + field.SetTargetFrame(*oTargetFrame); + } + SvxFieldItem const item{field, EE_FEATURE_FIELD}; + EditSelection const sel{rPam}; + rIEE.InsertField(sel, item); + } + else yvalidate(false); + } + else + { + yvalidate(false); + } +} + +} // namespace + +void EditDoc::YrsSetStyle(sal_Int32 const index, ::std::u16string_view const rStyle) +{ + ::YrsSetStyle(m_pYrsSupplier, m_CommentId, *this, index, rStyle); +} + +void EditDoc::YrsSetParaAttr(sal_Int32 const index, SfxPoolItem const& rItem) +{ + ::YrsSetParaAttr(m_pYrsSupplier, m_CommentId, *this, index, rItem); +} + +void EditDoc::YrsWriteEEState() +{ + assert(m_pYrsSupplier); + + YrsWrite const yw{GetYrsWrite(m_pYrsSupplier, m_CommentId)}; + assert(yw.pTxn && yw.pProps && yw.pText); + + YInput const vertical{yinput_bool(mbIsVertical ? Y_TRUE : Y_FALSE)}; + ymap_insert(yw.pProps, yw.pTxn, "is-vertical", &vertical); + YInput const rotation{yinput_long(static_cast<int64_t>(mnRotation))}; + ymap_insert(yw.pProps, yw.pTxn, "rotation", &rotation); + YInput const deftab{yinput_long(static_cast<int64_t>(mnDefTab))}; + ymap_insert(yw.pProps, yw.pTxn, "def-tab", &deftab); + + for (::std::unique_ptr<ContentNode> const& rpNode : maContents) + { + auto const start{ytext_len(yw.pText, yw.pTxn)}; + sal_Int32 pos{0}; + for (sal_Int32 i = rpNode->GetString().indexOf(CH_FEATURE, pos); + i != -1; i = rpNode->GetString().indexOf(CH_FEATURE, pos)) + { + if (i != pos) + { + OString const content{OUStringToOString(rpNode->GetString().subView(pos, i - pos), RTL_TEXTENCODING_UTF8)}; + ytext_insert(yw.pText, yw.pTxn, start + pos, content.getStr(), nullptr); + } + EditCharAttrib const*const pAttrib{rpNode->GetCharAttribs().FindFeature(i)}; + assert(pAttrib); + // this will insert the CH_FEATURE too + YrsInsertFeature(m_pYrsSupplier, m_CommentId, *this, GetPos(rpNode.get()), pAttrib); + pos = i+1; + } + if (pos != rpNode->GetString().getLength()) + { + OString const content{OUStringToOString(rpNode->GetString().subView(pos, rpNode->GetString().getLength() - pos), RTL_TEXTENCODING_UTF8)}; + ytext_insert(yw.pText, yw.pTxn, start + pos, content.getStr(), nullptr); + } + for (::std::unique_ptr<EditCharAttrib> const& rpAttr : rpNode->GetCharAttribs().GetAttribs()) + { + if (!rpAttr->IsFeature()) + { + YrsInsertAttribImpl(yw, start, rpAttr.get()); + } + } + char const para[]{ CH_PARA, ' + ContentAttribs const& rParaAttribs{rpNode->GetContentAttribs()}; + OString const styleName{OUStringToOString(rpNode->GetStyleSheet()->GetName(), RTL_TEXTENCODING_UTF8)}; + YInput const style{yinput_string(styleName.getStr())}; + YInput attrArray[]{ style }; + char const*const attrNames[]{ "para-style" }; + YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), attrArray, 1)}; + auto const end{ytext_len(yw.pText, yw.pTxn)}; + ytext_insert(yw.pText, yw.pTxn, end, para, &attrs); + for (SfxItemIter it{rParaAttribs.GetItems()}; !it.IsAtEnd(); it.NextItem()) + { + YrsInsertAttribImplImpl(yw, *it.GetCurItem(), end, 1); + } + } +} + +void EditDoc::YrsReadEEState(YTransaction *const pTxn, ImpEditEngine & rIEE) +{ + assert(m_pYrsSupplier); + YrsWrite const yw{GetYrsWrite(m_pYrsSupplier, m_CommentId, pTxn)}; + assert(yw.pTxn && yw.pProps && yw.pText); + + ::std::unique_ptr<YOutput, YOutputDeleter> const pVertical{ymap_get(yw.pProps, yw.pTxn, "is-vertical")}; + yvalidate(!pVertical || pVertical->tag == Y_JSON_BOOL); + if (pVertical && pVertical->tag == Y_JSON_BOOL) + { + SetVertical(pVertical->value.flag == Y_TRUE); + } + ::std::unique_ptr<YOutput, YOutputDeleter> const pRotation{ymap_get(yw.pProps, yw.pTxn, "rotation")}; + yvalidate(pRotation); + { + auto const value{YrsReadInt(*pRotation)}; + switch (value) + { + case int64_t(TextRotation::NONE): + case int64_t(TextRotation::TOPTOBOTTOM): + case int64_t(TextRotation::BOTTOMTOTOP): + SetRotation(TextRotation(value)); + break; + default: + yvalidate(false); + } + } + ::std::unique_ptr<YOutput, YOutputDeleter> const pDefTab{ymap_get(yw.pProps, yw.pTxn, "def-tab")}; + yvalidate(pDefTab); + { + auto const value{YrsReadInt(*pDefTab)}; + yvalidate(/*0 <= value && */value <= SAL_MAX_INT16); + SetDefTab(value); + } + + assert(maContents.size() == 1); +#if 0 + // there is a getImpl().GetEditSelection() too, who knows what else... try to reuse existing node? + rIEE.GetParaPortions().Reset(); + maContents.clear(); + // the last paragraph cannot be removed, as cursors and a11y need it + rIEE.RemoveParagraph(0); // remove pre-existing one from InitDoc() +#endif + uint32_t chunks{0}; + YChunk *const pChunks{ytext_chunks(yw.pText, yw.pTxn, &chunks)}; + sal_Int32 nodes{0}; + ContentNode * pNode{nullptr}; + for (decltype(chunks) i = 0; i < chunks; ++i) + { + decltype(nodes) const nodeStart{nodes}; + sal_Int32 const posStart{pNode ? pNode->Len() : 0}; + + yvalidate(pChunks[i].data.tag == Y_JSON_STR); + OString const str(pChunks[i].data.value.str); + sal_Int32 strStart{0}; + + for (sal_Int32 j = 0; j < str.getLength(); ++j) + { + if (!pNode) + { + //pNode = new ContentNode(GetItemPool()); + //rIEE.InsertContent(pNode, nodes); // does not set DefFont? + pNode = rIEE.ImpFastInsertParagraph(nodes).GetNode(); +// rIEE.GetParaPortions().Insert(nodes, ::std::make_unique<ParaPortion>(pNode)); +//does not call ParagraphInserted so no a11y event Insert(nodes, ::std::unique_ptr<ContentNode>(pNode)); + // calling UpdateSelections() is pointless, it only handles deletes + } + if (str[j] == CH_PARA) + { + if (j != 0) + { + OString const portion{str.copy(strStart, j - strStart)}; + pNode->Append(OStringToOUString(portion, RTL_TEXTENCODING_UTF8)); + } + for (decltype(pChunks[i].fmt_len) k = 0; k < pChunks[i].fmt_len; ++k) + { + if (strcmp(pChunks[i].fmt[k].key, "para-style") == 0) + { + yvalidate(pChunks[i].fmt[k].value->tag == Y_JSON_STR); + OUString const style{OStringToOUString(pChunks[i].fmt[k].value->value.str, RTL_TEXTENCODING_UTF8)}; + SfxStyleSheet *const pStyle{dynamic_cast<SfxStyleSheet *>( + rIEE.GetStyleSheetPool()->Find(style, SfxStyleFamily::Para))}; + if (!pStyle) { abort(); } + pNode->SetStyleSheet(pStyle); + } + } + pNode = nullptr; + ++nodes; + strStart = j+1; + } + else if (str[j] == CH_FEATURE) + { + if (j != strStart) + { + OString const portion{str.copy(strStart, j - strStart)}; + pNode->Append(OStringToOUString(portion, RTL_TEXTENCODING_UTF8)); + } + auto GetAttr = [](YMapEntry const& rEntry) -> ::std::pair<char const*, YOutput const&> + { return {rEntry.key, *rEntry.value}; }; + EditPaM const pam{pNode, pNode->Len()}; + YrsImplInsertFeature<YMapEntry>(rIEE, pam, pChunks[i].fmt, pChunks[i].fmt_len, GetAttr); + strStart = j+1; + } + } + if (strStart != str.getLength()) + { + OString const portion{str.copy(strStart, str.getLength() - strStart)}; + pNode->Append(OStringToOUString(portion, RTL_TEXTENCODING_UTF8)); + } + assert((pNode == nullptr) == (str[str.getLength()-1] == CH_PARA)); + ContentNode *const pEndNode{pNode ? pNode : maContents[nodes-1].get()}; + EditSelection const sel{EditPaM(maContents[nodeStart].get(), posStart), EditPaM(pEndNode, pEndNode->Len())}; + SfxItemSet set{rIEE.GetEmptyItemSet()}; + for (decltype(pChunks[i].fmt_len) j = 0; j < pChunks[i].fmt_len; ++j) + { + YrsImplInsertAttr(set, nullptr, pChunks[i].fmt[j].key, *pChunks[i].fmt[j].value); + } + if (set.Count()) + { + rIEE.SetAttribs(sel, set); + } + } + ychunks_destroy(pChunks, chunks); + rIEE.RemoveParagraph(nodes); // remove pre-existing one from InitDoc() +} + +static void YrsAdjustCursors(ImpEditEngine & rIEE, EditDoc & rDoc, + sal_Int32 const node, sal_Int32 const pos, ContentNode *const pNewNode, sal_Int32 const delta) +{ + for (EditView *const pView : rIEE.GetEditViews()) + { + bool bSet{false}; + EditSelection sel{pView->getImpl().GetEditSelection()}; + ContentNode const*const pNode{rDoc.GetObject(node)}; + if (sel.Min().GetNode() == pNode + && pos <= sel.Min().GetIndex()) + { + sel.Min().SetNode(pNewNode); + sel.Min().SetIndex(sel.Min().GetIndex() + delta); + bSet = true; + } + if (sel.Max().GetNode() == pNode + && pos <= sel.Max().GetIndex()) + { + sel.Max().SetNode(pNewNode); + sel.Max().SetIndex(sel.Max().GetIndex() + delta); + bSet = true; + } + if (bSet) + { + pView->getImpl().SetEditSelection(sel); + } + } +} + +// TODO test this +static void YrsAdjustCursorsDel(ImpEditEngine & rIEE, EditDoc & rDoc, -e ... etc. - the rest is truncated