include/svx/rubydialog.hxx                  |    3 
 svx/source/dialog/rubydialog.cxx            |   76 +++
 svx/uiconfig/ui/asianphoneticguidedialog.ui |  669 ++++++++++++++--------------
 sw/inc/doc.hxx                              |    2 
 sw/inc/rubylist.hxx                         |    1 
 sw/qa/core/uwriter.cxx                      |  295 ++++++++++++
 sw/source/core/doc/docruby.cxx              |  217 +++++----
 7 files changed, 865 insertions(+), 398 deletions(-)

New commits:
commit 3d9b8701cb1751e4139ffa24f72bb836eb877fd1
Author:     Jonathan Clark <jonat...@libreoffice.org>
AuthorDate: Tue Sep 10 09:06:51 2024 -0600
Commit:     Jonathan Clark <jonat...@libreoffice.org>
CommitDate: Thu Sep 12 05:04:53 2024 +0200

    tdf#107184 sw: Added base text group feature to Asian Phonetic Guide
    
    This change adds a new button, Group, to the Asian Phonetic Guide.
    Clicking on this button will automatically merge all base text runs into
    a single base text run.
    
    Change-Id: I8bc2881f0c31d501f8a347156145a483bb4c96cb
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/173241
    Reviewed-by: Jonathan Clark <jonat...@libreoffice.org>
    Tested-by: Jenkins

diff --git a/include/svx/rubydialog.hxx b/include/svx/rubydialog.hxx
index 655b4f2ae049..30b284ee0fbd 100644
--- a/include/svx/rubydialog.hxx
+++ b/include/svx/rubydialog.hxx
@@ -83,6 +83,8 @@ class SvxRubyDialog final : public SfxModelessDialogController
     std::unique_ptr<weld::ComboBox> m_xCharStyleLB;
     std::unique_ptr<weld::Button> m_xStylistPB;
 
+    std::unique_ptr<weld::Button> m_xSelectionGroupPB;
+
     std::unique_ptr<weld::Button> m_xApplyPB;
     std::unique_ptr<weld::Button> m_xClosePB;
 
@@ -92,6 +94,7 @@ class SvxRubyDialog final : public SfxModelessDialogController
     std::unique_ptr<RubyPreview> m_xPreviewWin;
     std::unique_ptr<weld::CustomWeld> m_xPreview;
 
+    DECL_LINK(SelectionGroup_Impl, weld::Button&, void);
     DECL_LINK(ApplyHdl_Impl, weld::Button&, void);
     DECL_LINK(CloseHdl_Impl, weld::Button&, void);
     DECL_LINK(StylistHdl_Impl, weld::Button&, void);
diff --git a/svx/source/dialog/rubydialog.cxx b/svx/source/dialog/rubydialog.cxx
index e28fdb71d49e..f85a395029b0 100644
--- a/svx/source/dialog/rubydialog.cxx
+++ b/svx/source/dialog/rubydialog.cxx
@@ -43,6 +43,7 @@
 #include <vcl/event.hxx>
 #include <vcl/settings.hxx>
 #include <vcl/svapp.hxx>
+#include <rtl/ustrbuf.hxx>
 #include <svl/itemset.hxx>
 
 using namespace css::uno;
@@ -119,6 +120,70 @@ public:
 
     virtual void SAL_CALL selectionChanged(const css::lang::EventObject& 
aEvent) override;
     virtual void SAL_CALL disposing(const css::lang::EventObject& Source) 
override;
+
+    bool IsSelectionGrouped() { return aRubyValues.getLength() < 2; }
+
+    void MakeSelectionGrouped()
+    {
+        if (aRubyValues.getLength() < 2)
+        {
+            return;
+        }
+
+        OUString sBaseTmp;
+        OUStringBuffer aBaseString;
+        for (const PropertyValues& pVals : aRubyValues)
+        {
+            sBaseTmp.clear();
+            for (const PropertyValue& pVal : pVals)
+            {
+                if (pVal.Name == cRubyBaseText)
+                {
+                    pVal.Value >>= sBaseTmp;
+                }
+            }
+
+            aBaseString.append(sBaseTmp);
+        }
+
+        Sequence<PropertyValues> aNewRubyValues{ 1 };
+        PropertyValues* pNewRubyValues = aNewRubyValues.getArray();
+
+        // Copy some reasonable style values from the previous ruby array
+        pNewRubyValues[0] = aRubyValues[0];
+        for (const PropertyValues& pVals : aRubyValues)
+        {
+            for (const PropertyValue& pVal : pVals)
+            {
+                if (pVal.Name == cRubyText)
+                {
+                    pVal.Value >>= sBaseTmp;
+                    if (!sBaseTmp.isEmpty())
+                    {
+                        pNewRubyValues[0] = pVals;
+                        break;
+                    }
+                }
+            }
+        }
+
+        PropertyValue* pNewValues = pNewRubyValues[0].getArray();
+        for (sal_Int32 i = 0; i < pNewRubyValues[0].getLength(); ++i)
+        {
+            if (pNewValues[i].Name == cRubyBaseText)
+            {
+                sBaseTmp = aBaseString;
+                pNewValues[i].Value <<= sBaseTmp;
+            }
+            else if (pNewValues[i].Name == cRubyText)
+            {
+                sBaseTmp.clear();
+                pNewValues[i].Value <<= sBaseTmp;
+            }
+        }
+
+        aRubyValues = std::move(aNewRubyValues);
+    }
 };
 
 SvxRubyData_Impl::SvxRubyData_Impl()
@@ -207,6 +272,7 @@ SvxRubyDialog::SvxRubyDialog(SfxBindings* pBind, 
SfxChildWindow* pCW, weld::Wind
     , m_xCharStyleFT(m_xBuilder->weld_label(u"styleft"_ustr))
     , m_xCharStyleLB(m_xBuilder->weld_combo_box(u"stylelb"_ustr))
     , m_xStylistPB(m_xBuilder->weld_button(u"styles"_ustr))
+    , m_xSelectionGroupPB(m_xBuilder->weld_button(u"selection-group"_ustr))
     , m_xApplyPB(m_xBuilder->weld_button(u"ok"_ustr))
     , m_xClosePB(m_xBuilder->weld_button(u"close"_ustr))
     , m_xContentArea(m_xDialog->weld_content_area())
@@ -228,6 +294,7 @@ SvxRubyDialog::SvxRubyDialog(SfxBindings* pBind, 
SfxChildWindow* pCW, weld::Wind
     aEditArr[6] = m_xLeft4ED.get();
     aEditArr[7] = m_xRight4ED.get();
 
+    m_xSelectionGroupPB->connect_clicked(LINK(this, SvxRubyDialog, 
SelectionGroup_Impl));
     m_xApplyPB->connect_clicked(LINK(this, SvxRubyDialog, ApplyHdl_Impl));
     m_xClosePB->connect_clicked(LINK(this, SvxRubyDialog, CloseHdl_Impl));
     m_xStylistPB->connect_clicked(LINK(this, SvxRubyDialog, StylistHdl_Impl));
@@ -405,6 +472,9 @@ void SvxRubyDialog::GetRubyText()
 
 void SvxRubyDialog::Update()
 {
+    // Only enable selection grouping options when they can be applied
+    m_xSelectionGroupPB->set_sensitive(!m_pImpl->IsSelectionGrouped());
+
     const Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues();
     sal_Int32 nLen = aRubyValues.getLength();
     m_xScrolledWindow->vadjustment_configure(0, 0, !nLen ? 1 : nLen, 1, 4, 4);
@@ -505,6 +575,12 @@ IMPL_LINK(SvxRubyDialog, ScrollHdl_Impl, 
weld::ScrolledWindow&, rScroll, void)
     m_xPreviewWin->Invalidate();
 }
 
+IMPL_LINK_NOARG(SvxRubyDialog, SelectionGroup_Impl, weld::Button&, void)
+{
+    m_pImpl->MakeSelectionGrouped();
+    Update();
+}
+
 IMPL_LINK_NOARG(SvxRubyDialog, ApplyHdl_Impl, weld::Button&, void)
 {
     const Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues();
diff --git a/svx/uiconfig/ui/asianphoneticguidedialog.ui 
b/svx/uiconfig/ui/asianphoneticguidedialog.ui
index b70aee74127b..66a045d26ba7 100644
--- a/svx/uiconfig/ui/asianphoneticguidedialog.ui
+++ b/svx/uiconfig/ui/asianphoneticguidedialog.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.38.2 -->
+<!-- Generated with glade 3.40.0 -->
 <interface domain="svx">
   <requires lib="gtk+" version="3.20"/>
   <object class="GtkDialog" id="AsianPhoneticGuideDialog">
@@ -72,423 +72,468 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="box1">
+          <!-- n-columns=2 n-rows=2 -->
+          <object class="GtkGrid" id="grid4">
             <property name="visible">True</property>
             <property name="can-focus">False</property>
-            <property name="orientation">vertical</property>
-            <property name="spacing">6</property>
+            <property name="row-spacing">6</property>
+            <property name="column-spacing">6</property>
             <child>
-              <!-- n-columns=2 n-rows=1 -->
-              <object class="GtkGrid" id="grid3">
+              <object class="GtkBox" id="box1">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
-                <property name="column-spacing">12</property>
-                <property name="column-homogeneous">True</property>
-                <child>
-                  <object class="GtkLabel" id="basetextft">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|basetextft">Base text</property>
-                    <property name="use-underline">True</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="rubytextft">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|rubytextft">Ruby text</property>
-                    <property name="use-underline">True</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">1</property>
-                    <property name="top-attach">0</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkScrolledWindow" id="scrolledwindow">
-                <property name="visible">True</property>
-                <property name="can-focus">True</property>
                 <property name="hexpand">True</property>
-                <property name="hscrollbar-policy">never</property>
-                <property name="vscrollbar-policy">always</property>
-                <property name="shadow-type">in</property>
+                <property name="vexpand">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
                 <child>
-                  <object class="GtkViewport" id="viewport1">
+                  <object class="GtkScrolledWindow" id="scrolledwindow">
                     <property name="visible">True</property>
-                    <property name="can-focus">False</property>
+                    <property name="can-focus">True</property>
+                    <property name="hexpand">True</property>
+                    <property name="hscrollbar-policy">never</property>
+                    <property name="vscrollbar-policy">always</property>
+                    <property name="shadow-type">in</property>
                     <child>
-                      <!-- n-columns=1 n-rows=1 -->
-                      <object class="GtkGrid" id="grid">
+                      <object class="GtkViewport" id="viewport1">
                         <property name="visible">True</property>
                         <property name="can-focus">False</property>
                         <child>
-                          <!-- n-columns=2 n-rows=4 -->
-                          <object class="GtkGrid" id="grid1">
+                          <!-- n-columns=1 n-rows=1 -->
+                          <object class="GtkGrid" id="grid">
                             <property name="visible">True</property>
                             <property name="can-focus">False</property>
-                            <property name="hexpand">True</property>
-                            <property name="border-width">6</property>
-                            <property name="row-spacing">6</property>
-                            <property name="column-spacing">12</property>
                             <child>
-                              <object class="GtkEntry" id="Left2ED">
+                              <!-- n-columns=2 n-rows=4 -->
+                              <object class="GtkGrid" id="grid1">
                                 <property name="visible">True</property>
-                                <property name="can-focus">True</property>
+                                <property name="can-focus">False</property>
                                 <property name="hexpand">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Left2ED-atkobject">
-                                    <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Left2ED-atkobject">Base text</property>
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left2ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                <property name="border-width">6</property>
+                                <property name="row-spacing">6</property>
+                                <property name="column-spacing">12</property>
+                                <child>
+                                  <object class="GtkEntry" id="Left2ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property name="hexpand">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Left2ED-atkobject">
+                                        <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Left2ED-atkobject">Base text</property>
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left2ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">0</property>
+                                    <property name="top-attach">1</property>
+                                  </packing>
                                 </child>
-                              </object>
-                              <packing>
-                                <property name="left-attach">0</property>
-                                <property name="top-attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="Left1ED">
-                                <property name="visible">True</property>
-                                <property name="can-focus">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Left1ED-atkobject">
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left1ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                <child>
+                                  <object class="GtkEntry" id="Left1ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Left1ED-atkobject">
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left1ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">0</property>
+                                    <property name="top-attach">0</property>
+                                  </packing>
                                 </child>
-                              </object>
-                              <packing>
-                                <property name="left-attach">0</property>
-                                <property name="top-attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="Right1ED">
-                                <property name="visible">True</property>
-                                <property name="can-focus">True</property>
-                                <property name="hexpand">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Right1ED-atkobject">
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right1ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                <child>
+                                  <object class="GtkEntry" id="Right1ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property name="hexpand">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Right1ED-atkobject">
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right1ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">1</property>
+                                    <property name="top-attach">0</property>
+                                  </packing>
                                 </child>
-                              </object>
-                              <packing>
-                                <property name="left-attach">1</property>
-                                <property name="top-attach">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="Right2ED">
-                                <property name="visible">True</property>
-                                <property name="can-focus">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Right2ED-atkobject">
-                                    <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Right2ED-atkobject">Ruby text</property>
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right2ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                <child>
+                                  <object class="GtkEntry" id="Right2ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Right2ED-atkobject">
+                                        <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Right2ED-atkobject">Ruby text</property>
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right2ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">1</property>
+                                    <property name="top-attach">1</property>
+                                  </packing>
                                 </child>
-                              </object>
-                              <packing>
-                                <property name="left-attach">1</property>
-                                <property name="top-attach">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="Left3ED">
-                                <property name="visible">True</property>
-                                <property name="can-focus">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Left3ED-atkobject">
-                                    <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Left3ED-atkobject">Base text</property>
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left3ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                <child>
+                                  <object class="GtkEntry" id="Left3ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Left3ED-atkobject">
+                                        <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Left3ED-atkobject">Base text</property>
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left3ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">0</property>
+                                    <property name="top-attach">2</property>
+                                  </packing>
                                 </child>
-                              </object>
-                              <packing>
-                                <property name="left-attach">0</property>
-                                <property name="top-attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="Right3ED">
-                                <property name="visible">True</property>
-                                <property name="can-focus">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Right3ED-atkobject">
-                                    <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Right3ED-atkobject">Ruby text</property>
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right3ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                <child>
+                                  <object class="GtkEntry" id="Right3ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Right3ED-atkobject">
+                                        <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Right3ED-atkobject">Ruby text</property>
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right3ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">1</property>
+                                    <property name="top-attach">2</property>
+                                  </packing>
                                 </child>
-                              </object>
-                              <packing>
-                                <property name="left-attach">1</property>
-                                <property name="top-attach">2</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="Right4ED">
-                                <property name="visible">True</property>
-                                <property name="can-focus">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Right4ED-atkobject">
-                                    <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Right4ED-atkobject">Ruby text</property>
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right4ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                <child>
+                                  <object class="GtkEntry" id="Right4ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Right4ED-atkobject">
+                                        <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Right4ED-atkobject">Ruby text</property>
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Right4ED">Enter the text that 
you want to use as a pronunciation guide for the base text.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">1</property>
+                                    <property name="top-attach">3</property>
+                                  </packing>
                                 </child>
-                              </object>
-                              <packing>
-                                <property name="left-attach">1</property>
-                                <property name="top-attach">3</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="Left4ED">
-                                <property name="visible">True</property>
-                                <property name="can-focus">True</property>
-                                <property 
name="activates-default">True</property>
-                                <property 
name="truncate-multiline">True</property>
-                                <child internal-child="accessible">
-                                  <object class="AtkObject" 
id="Left4ED-atkobject">
-                                    <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Left4ED-atkobject">Base text</property>
-                                    <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left4ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                <child>
+                                  <object class="GtkEntry" id="Left4ED">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">True</property>
+                                    <property 
name="activates-default">True</property>
+                                    <property 
name="truncate-multiline">True</property>
+                                    <child internal-child="accessible">
+                                      <object class="AtkObject" 
id="Left4ED-atkobject">
+                                        <property 
name="AtkObject::accessible-name" translatable="yes" 
context="asianphoneticguidedialog|Left4ED-atkobject">Base text</property>
+                                        <property 
name="AtkObject::accessible-description" translatable="yes" 
context="asianphoneticguidedialog|extended_tip|Left4ED">Displays the base text 
that you selected in the current file. If you want, you can modify the base 
text by entering new text here.</property>
+                                      </object>
+                                    </child>
                                   </object>
+                                  <packing>
+                                    <property name="left-attach">0</property>
+                                    <property name="top-attach">3</property>
+                                  </packing>
                                 </child>
                               </object>
                               <packing>
                                 <property name="left-attach">0</property>
-                                <property name="top-attach">3</property>
+                                <property name="top-attach">0</property>
                               </packing>
                             </child>
                           </object>
-                          <packing>
-                            <property name="left-attach">0</property>
-                            <property name="top-attach">0</property>
-                          </packing>
                         </child>
                       </object>
                     </child>
                   </object>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <!-- n-columns=4 n-rows=2 -->
-              <object class="GtkGrid" id="grid2">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="margin-top">6</property>
-                <property name="row-spacing">6</property>
-                <property name="column-spacing">12</property>
-                <child>
-                  <object class="GtkLabel" id="label4">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|label4">Alignment:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">adjustlb</property>
-                    <property name="xalign">0</property>
-                  </object>
                   <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">0</property>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkLabel" id="label5">
+                  <!-- n-columns=4 n-rows=2 -->
+                  <object class="GtkGrid" id="grid2">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|label5">Position:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">positionlb</property>
-                    <property name="xalign">0</property>
+                    <property name="margin-top">6</property>
+                    <property name="row-spacing">6</property>
+                    <property name="column-spacing">12</property>
+                    <child>
+                      <object class="GtkLabel" id="label4">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="label" translatable="yes" 
context="asianphoneticguidedialog|label4">Alignment:</property>
+                        <property name="use-underline">True</property>
+                        <property name="mnemonic-widget">adjustlb</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label5">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="label" translatable="yes" 
context="asianphoneticguidedialog|label5">Position:</property>
+                        <property name="use-underline">True</property>
+                        <property name="mnemonic-widget">positionlb</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="styleft">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="label" translatable="yes" 
context="asianphoneticguidedialog|styleft">Character style for ruby 
text:</property>
+                        <property name="use-underline">True</property>
+                        <property name="mnemonic-widget">stylelb</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">2</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBoxText" id="stylelb">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child internal-child="accessible">
+                          <object class="AtkObject" id="stylelb-atkobject">
+                            <property name="AtkObject::accessible-description" 
translatable="yes" 
context="asianphoneticguidedialog|extended_tip|stylelb">Select a character 
style for the ruby text.</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left-attach">2</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="styles">
+                        <property name="label" translatable="yes" 
context="asianphoneticguidedialog|styles">Styles</property>
+                        <property name="visible">True</property>
+                        <property name="can-focus">True</property>
+                        <property name="receives-default">True</property>
+                        <property name="use-underline">True</property>
+                        <child internal-child="accessible">
+                          <object class="AtkObject" id="styles-atkobject">
+                            <property name="AtkObject::accessible-description" 
translatable="yes" context="asianphoneticguidedialog|extended_tip|styles">Opens 
the Styles deck of the Sidebar where you can select a character style for the 
ruby text.</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left-attach">3</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBoxText" id="adjustlb">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <items>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">Left</item>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">Center</item>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">Right</item>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">0 1 0</item>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">1 2 1</item>
+                        </items>
+                        <child internal-child="accessible">
+                          <object class="AtkObject" id="adjustlb-atkobject">
+                            <property name="AtkObject::accessible-description" 
translatable="yes" 
context="asianphoneticguidedialog|extended_tip|adjustlb">Select the horizontal 
alignment for the ruby text.</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBoxText" id="positionlb">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <items>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|positionlb">Top</item>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|positionlb">Bottom</item>
+                          <item translatable="yes" 
context="asianphoneticguidedialog|positionlb">Right</item>
+                        </items>
+                        <child internal-child="accessible">
+                          <object class="AtkObject" id="positionlb-atkobject">
+                            <property name="AtkObject::accessible-description" 
translatable="yes" 
context="asianphoneticguidedialog|extended_tip|positionlb">Select where you 
want to place the ruby text.</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
                   </object>
                   <packing>
-                    <property name="left-attach">1</property>
-                    <property name="top-attach">0</property>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkLabel" id="styleft">
+                  <object class="GtkLabel" id="label1">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|styleft">Character style for ruby 
text:</property>
+                    <property name="margin-top">6</property>
+                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|label1">Preview:</property>
                     <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">stylelb</property>
+                    <property name="mnemonic-widget">preview</property>
                     <property name="xalign">0</property>
                   </object>
                   <packing>
-                    <property name="left-attach">2</property>
-                    <property name="top-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBoxText" id="stylelb">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="hexpand">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="stylelb-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" 
context="asianphoneticguidedialog|extended_tip|stylelb">Select a character 
style for the ruby text.</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="left-attach">2</property>
-                    <property name="top-attach">1</property>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkButton" id="styles">
-                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|styles">Styles</property>
+                  <object class="GtkScrolledWindow" id="ctlFavoriteswin">
                     <property name="visible">True</property>
                     <property name="can-focus">True</property>
-                    <property name="receives-default">True</property>
-                    <property name="use-underline">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="styles-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="asianphoneticguidedialog|extended_tip|styles">Opens 
the Styles deck of the Sidebar where you can select a character style for the 
ruby text.</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="hscrollbar-policy">never</property>
+                    <property name="vscrollbar-policy">never</property>
+                    <property name="shadow-type">in</property>
+                    <child>
+                      <object class="GtkViewport">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <child>
+                          <object class="GtkDrawingArea" id="preview">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="hexpand">True</property>
+                            <property name="vexpand">True</property>
+                          </object>
+                        </child>
                       </object>
                     </child>
                   </object>
                   <packing>
-                    <property name="left-attach">3</property>
-                    <property name="top-attach">1</property>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">4</property>
                   </packing>
                 </child>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <!-- n-columns=2 n-rows=1 -->
+              <object class="GtkGrid" id="grid3">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="valign">start</property>
+                <property name="column-spacing">12</property>
+                <property name="column-homogeneous">True</property>
                 <child>
-                  <object class="GtkComboBoxText" id="adjustlb">
+                  <object class="GtkLabel" id="basetextft">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <items>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">Left</item>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">Center</item>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">Right</item>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">0 1 0</item>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|adjustlb">1 2 1</item>
-                    </items>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="adjustlb-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" 
context="asianphoneticguidedialog|extended_tip|adjustlb">Select the horizontal 
alignment for the ruby text.</property>
-                      </object>
-                    </child>
+                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|basetextft">Base text</property>
+                    <property name="use-underline">True</property>
                   </object>
                   <packing>
                     <property name="left-attach">0</property>
-                    <property name="top-attach">1</property>
+                    <property name="top-attach">0</property>
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkComboBoxText" id="positionlb">
+                  <object class="GtkLabel" id="rubytextft">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <items>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|positionlb">Top</item>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|positionlb">Bottom</item>
-                      <item translatable="yes" 
context="asianphoneticguidedialog|positionlb">Right</item>
-                    </items>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="positionlb-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" 
context="asianphoneticguidedialog|extended_tip|positionlb">Select where you 
want to place the ruby text.</property>
-                      </object>
-                    </child>
+                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|rubytextft">Ruby text</property>
+                    <property name="use-underline">True</property>
                   </object>
                   <packing>
                     <property name="left-attach">1</property>
-                    <property name="top-attach">1</property>
+                    <property name="top-attach">0</property>
                   </packing>
                 </child>
-                <child>
-                  <placeholder/>
-                </child>
               </object>
               <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">2</property>
+                <property name="left-attach">0</property>
+                <property name="top-attach">0</property>
               </packing>
             </child>
             <child>
-              <object class="GtkLabel" id="label1">
+              <object class="GtkButtonBox" id="dialog-action_area2">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
-                <property name="margin-top">6</property>
-                <property name="label" translatable="yes" 
context="asianphoneticguidedialog|label1">Preview:</property>
-                <property name="use-underline">True</property>
-                <property name="mnemonic-widget">preview</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">4</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkScrolledWindow" id="ctlFavoriteswin">
-                <property name="visible">True</property>
-                <property name="can-focus">True</property>
-                <property name="hexpand">True</property>
-                <property name="vexpand">True</property>
-                <property name="hscrollbar-policy">never</property>
-                <property name="vscrollbar-policy">never</property>
-                <property name="shadow-type">in</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <property name="layout-style">start</property>
                 <child>
-                  <object class="GtkViewport">
+                  <object class="GtkButton" id="selection-group">
+                    <property name="label" translatable="yes" 
context="asianphoneticguidedialog|selectiongroup">_Group</property>
                     <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <child>
-                      <object class="GtkDrawingArea" id="preview">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="hexpand">True</property>
-                        <property name="vexpand">True</property>
-                      </object>
-                    </child>
+                    <property name="can-focus">True</property>
+                    <property name="receives-default">True</property>
+                    <property name="use-underline">True</property>
                   </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
                 </child>
               </object>
               <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">5</property>
+                <property name="left-attach">1</property>
+                <property name="top-attach">1</property>
               </packing>
             </child>
+            <child>
+              <placeholder/>
+            </child>
           </object>
           <packing>
             <property name="expand">True</property>
             <property name="fill">True</property>
-            <property name="position">1</property>
+            <property name="position">0</property>
           </packing>
         </child>
       </object>
diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx
index 1a4fff14b4c2..0274dd4976be 100644
--- a/sw/inc/doc.hxx
+++ b/sw/inc/doc.hxx
@@ -1591,7 +1591,7 @@ public:
 
     // Interface for the list of Ruby - texts/attributes
     static sal_uInt16 FillRubyList( const SwPaM& rPam, SwRubyList& rList );
-    void SetRubyList( const SwPaM& rPam, const SwRubyList& rList );
+    void SetRubyList( SwPaM& rPam, const SwRubyList& rList );
 
     void ReadLayoutCache( SvStream& rStream );
     void WriteLayoutCache( SvStream& rStream );
diff --git a/sw/inc/rubylist.hxx b/sw/inc/rubylist.hxx
index b025686daae3..3542cbcb3738 100644
--- a/sw/inc/rubylist.hxx
+++ b/sw/inc/rubylist.hxx
@@ -27,7 +27,6 @@ class SwRubyListEntry
     SwFormatRuby m_aRubyAttr;
 public:
     SwRubyListEntry() : m_aRubyAttr( OUString() ) {}
-    ~SwRubyListEntry();
 
     const OUString& GetText() const                    { return m_sText; }
     void SetText( const OUString& rStr )        { m_sText = rStr; }
diff --git a/sw/qa/core/uwriter.cxx b/sw/qa/core/uwriter.cxx
index 3504a11130df..2e10ae43aa1c 100644
--- a/sw/qa/core/uwriter.cxx
+++ b/sw/qa/core/uwriter.cxx
@@ -132,6 +132,7 @@ public:
     void testTdf92308();
     void testTableCellComparison();
     void testTdf156211();
+    void testFillRubyList();
     void testSetRubyList();
 
     CPPUNIT_TEST_SUITE(SwDocTest);
@@ -171,6 +172,7 @@ public:
     CPPUNIT_TEST(testTdf92308);
     CPPUNIT_TEST(testTableCellComparison);
     CPPUNIT_TEST(testTdf156211);
+    CPPUNIT_TEST(testFillRubyList);
     CPPUNIT_TEST(testSetRubyList);
     CPPUNIT_TEST_SUITE_END();
 
@@ -1985,6 +1987,165 @@ void SwDocTest::testTdf156211()
     CPPUNIT_ASSERT(!oSI.IsKashidaLine(TextFrameIndex{ 95 }));
 }
 
+void SwDocTest::testFillRubyList()
+{
+    SwNodeIndex aIdx(m_pDoc->GetNodes().GetEndOfContent(), -1);
+    SwPaM aPaM(aIdx);
+
+    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
+    CPPUNIT_ASSERT(pTextNode);
+
+    auto& rOps = m_pDoc->getIDocumentContentOperations();
+
+    auto fnAppendJapanese = [&](const OUString& rText)
+    {
+        rOps.AppendTextNode(*aPaM.GetPoint());
+
+        SvxLanguageItem aCJKLangItem(LANGUAGE_JAPANESE, 
RES_CHRATR_CJK_LANGUAGE);
+        SvxLanguageItem aWestLangItem(LANGUAGE_ENGLISH_US, 
RES_CHRATR_LANGUAGE);
+        rOps.InsertPoolItem(aPaM, aCJKLangItem);
+        rOps.InsertPoolItem(aPaM, aWestLangItem);
+
+        rOps.InsertString(aPaM, rText);
+
+        aPaM.SetMark();
+        aPaM.GetPoint()->nContent = 0;
+
+        CPPUNIT_ASSERT_EQUAL(rText, aPaM.GetText());
+    };
+
+    auto fnAppendRuby = [](SwRubyList* rList, OUString aBase, OUString aRuby)
+    {
+        auto pEnt = std::make_unique<SwRubyListEntry>();
+        pEnt->SetText(std::move(aBase));
+        pEnt->SetRubyAttr(SwFormatRuby{ std::move(aRuby) });
+
+        rList->push_back(std::move(pEnt));
+    };
+
+    auto fnGetCombinedString = [&]
+    {
+        SwRubyList aRubies;
+        SwDoc::FillRubyList(aPaM, aRubies);
+
+        OUStringBuffer aTemp;
+
+        for (auto const& rRuby : aRubies)
+        {
+            aTemp.append(rRuby->GetText() + u"["_ustr + 
rRuby->GetRubyAttr().GetText() + u"]"_ustr);
+        }
+
+        return aTemp.toString();
+    };
+
+    // Single word without existing rubies
+    {
+        fnAppendJapanese(u"学校"_ustr);
+        CPPUNIT_ASSERT_EQUAL(u"学校[]"_ustr, fnGetCombinedString());
+    }
+
+    // Compound word without existing rubies
+    {
+        fnAppendJapanese(u"自動販売機"_ustr);
+        CPPUNIT_ASSERT_EQUAL(u"自動[]販売[]機[]"_ustr, fnGetCombinedString());
+    }
+
+    // Single word with existing rubies
+    {
+        fnAppendJapanese(u"学校"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"学校"_ustr, u"がっこう"_ustr);
+
+        m_pDoc->SetRubyList(aPaM, rList);
+
+        CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]"_ustr, fnGetCombinedString());
+    }
+
+    // Compound word with existing rubies
+    {
+        fnAppendJapanese(u"自動販売機"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"自動"_ustr, u"じどう"_ustr);
+        fnAppendRuby(&rList, u"販売"_ustr, u"はんばい"_ustr);
+        fnAppendRuby(&rList, u"機"_ustr, u"き"_ustr);
+
+        m_pDoc->SetRubyList(aPaM, rList);
+
+        CPPUNIT_ASSERT_EQUAL(u"自動[じどう]販売[はんばい]機[き]"_ustr, 
fnGetCombinedString());
+    }
+
+    // Compound word with existing rubies treated as a single word
+    {
+        fnAppendJapanese(u"自動販売機"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"自動販売機"_ustr, u"じどうはんばいき"_ustr);
+        fnAppendRuby(&rList, u""_ustr, u""_ustr);
+        fnAppendRuby(&rList, u""_ustr, u""_ustr);
+
+        m_pDoc->SetRubyList(aPaM, rList);
+
+        CPPUNIT_ASSERT_EQUAL(u"自動販売機[じどうはんばいき]"_ustr, fnGetCombinedString());
+    }
+
+    // tdf#141466: Characteristic test from bug
+    {
+        fnAppendJapanese(u"学校に行きます。"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"学校"_ustr, u"がっこう"_ustr);
+        fnAppendRuby(&rList, u"に"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"行"_ustr, u"い"_ustr);
+        fnAppendRuby(&rList, u"きます"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"。"_ustr, u""_ustr);
+
+        m_pDoc->SetRubyList(aPaM, rList);
+
+        CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に[]行[い]き[]ます[]。[]"_ustr, 
fnGetCombinedString());
+    }
+
+    // tdf#107184: Characteristic test for ruby group mode editing
+    {
+        fnAppendJapanese(u"学校に行きます"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"学校に行きます"_ustr, u"がっこうにいきます"_ustr);
+
+        m_pDoc->SetRubyList(aPaM, rList);
+
+        CPPUNIT_ASSERT_EQUAL(u"学校に行きます[がっこうにいきます]"_ustr, 
fnGetCombinedString());
+    }
+
+    // tdf#156543: Characteristic test for ruby mono mode editing
+    {
+        fnAppendJapanese(u"学校に行きます"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"学"_ustr, u"がっ"_ustr);
+        fnAppendRuby(&rList, u"校"_ustr, u"こう"_ustr);
+        fnAppendRuby(&rList, u"に"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"行"_ustr, u"い"_ustr);
+        fnAppendRuby(&rList, u"き"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"ま"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"す"_ustr, u""_ustr);
+
+        m_pDoc->SetRubyList(aPaM, rList);
+
+        CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に[]行[い]き[]ます[]"_ustr, 
fnGetCombinedString());
+    }
+
+    // Empty PaM
+    {
+        fnAppendJapanese(u"学校"_ustr);
+
+        aPaM.DeleteMark();
+
+        CPPUNIT_ASSERT_EQUAL(u"学校[]"_ustr, fnGetCombinedString());
+    }
+}
+
 void SwDocTest::testSetRubyList()
 {
     SwNodeIndex aIdx(m_pDoc->GetNodes().GetEndOfContent(), -1);
@@ -2057,7 +2218,11 @@ void SwDocTest::testSetRubyList()
         SwRubyList rList;
         fnAppendRuby(&rList, u"学校"_ustr, u"がっこう"_ustr);
 
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aPaM.GetMark()->GetContentIndex());
         m_pDoc->SetRubyList(aPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aPaM.GetMark()->GetContentIndex());
 
         CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]"_ustr, fnGetCombinedString());
     }
@@ -2073,7 +2238,11 @@ void SwDocTest::testSetRubyList()
         fnAppendRuby(&rList, u"きます"_ustr, u""_ustr);
         fnAppendRuby(&rList, u"。"_ustr, u""_ustr);
 
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex());
         m_pDoc->SetRubyList(aPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex());
 
         CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に行[い]きます。"_ustr, fnGetCombinedString());
     }
@@ -2089,7 +2258,11 @@ void SwDocTest::testSetRubyList()
         fnAppendRuby(&rList, u"きます"_ustr, u""_ustr);
         fnAppendRuby(&rList, u"。"_ustr, u""_ustr);
 
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex());
         m_pDoc->SetRubyList(aPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex());
 
         CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に行[い]きます。"_ustr, fnGetCombinedString());
 
@@ -2101,10 +2274,132 @@ void SwDocTest::testSetRubyList()
         fnAppendRuby(&rList2, u"ます"_ustr, u""_ustr);
         fnAppendRuby(&rList2, u"。"_ustr, u""_ustr);
 
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex());
         m_pDoc->SetRubyList(aPaM, rList2);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
 
         CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に来[き]ます。"_ustr, fnGetCombinedString());
     }
+
+    // tdf#107184: Characteristic test for ruby group mode editing
+    {
+        fnAppendJapanese(u"学校に行きます"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"学校に行きます"_ustr, u"がっこうにいきます"_ustr);
+
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+        m_pDoc->SetRubyList(aPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+
+        CPPUNIT_ASSERT_EQUAL(u"学校に行きます[がっこうにいきます]"_ustr, 
fnGetCombinedString());
+    }
+
+    // tdf#107184: Delete ruby in group mode after populating
+    {
+        fnAppendJapanese(u"学校に行きます"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"学校に行きます"_ustr, u"がっこうにいきます"_ustr);
+
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+        m_pDoc->SetRubyList(aPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+
+        CPPUNIT_ASSERT_EQUAL(u"学校に行きます[がっこうにいきます]"_ustr, 
fnGetCombinedString());
+
+        SwRubyList rList2;
+        fnAppendRuby(&rList2, u"学校に行きます"_ustr, u""_ustr);
+
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+        m_pDoc->SetRubyList(aPaM, rList2);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+
+        CPPUNIT_ASSERT_EQUAL(u"学校に行きます"_ustr, fnGetCombinedString());
+    }
+
+    // tdf#156543: Characteristic test for ruby mono mode editing
+    {
+        fnAppendJapanese(u"学校に行きます"_ustr);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"学"_ustr, u"がっ"_ustr);
+        fnAppendRuby(&rList, u"校"_ustr, u"こう"_ustr);
+        fnAppendRuby(&rList, u"に"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"行"_ustr, u"い"_ustr);
+        fnAppendRuby(&rList, u"き"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"ま"_ustr, u""_ustr);
+        fnAppendRuby(&rList, u"す"_ustr, u"す"_ustr);
+
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+        m_pDoc->SetRubyList(aPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex());
+
+        CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に行[い]きます[す]"_ustr, 
fnGetCombinedString());
+    }
+
+    // Offset PaM - Combination of insert and replace
+    {
+        fnAppendJapanese(u"学校員"_ustr);
+
+        SwPosition aNewPos{ aPaM.GetPoint()->nNode, aPaM.GetPoint()->nContent 
};
+        const SwTextNode* pTNd = aNewPos.GetNode().GetTextNode();
+        pTNd->GoNext(&aNewPos, SwCursorSkipMode::Chars);
+
+        SwPaM aEmptyPaM{ aNewPos };
+        aEmptyPaM.SetMark();
+        aEmptyPaM.GetMark()->AdjustContent(1);
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"森林"_ustr, u"しんりん"_ustr);
+        fnAppendRuby(&rList, u"海上"_ustr, u"かいじょう"_ustr);
+        fnAppendRuby(&rList, u"地面"_ustr, u"じめん"_ustr);
+
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), 
aEmptyPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(2), 
aEmptyPaM.GetMark()->GetContentIndex());
+        m_pDoc->SetRubyList(aEmptyPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), 
aEmptyPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), 
aEmptyPaM.GetMark()->GetContentIndex());
+
+        CPPUNIT_ASSERT_EQUAL(u"学森林[しんりん]海上[かいじょう]地面[じめん]員"_ustr,
+                             fnGetCombinedString());
+    }
+
+    // Empty PaM - Should insert
+    {
+        fnAppendJapanese(u"学校"_ustr);
+
+        SwPosition aNewPos{ aPaM.GetPoint()->nNode, aPaM.GetPoint()->nContent 
};
+        const SwTextNode* pTNd = aNewPos.GetNode().GetTextNode();
+        pTNd->GoNext(&aNewPos, SwCursorSkipMode::Chars);
+
+        SwPaM aEmptyPaM{ aNewPos };
+        aEmptyPaM.SetMark();
+
+        SwRubyList rList;
+        fnAppendRuby(&rList, u"森林"_ustr, u"しんりん"_ustr);
+        fnAppendRuby(&rList, u"海上"_ustr, u"かいじょう"_ustr);
+        fnAppendRuby(&rList, u"地面"_ustr, u"じめん"_ustr);
+
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), 
aEmptyPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), 
aEmptyPaM.GetMark()->GetContentIndex());
+        m_pDoc->SetRubyList(aEmptyPaM, rList);
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), 
aEmptyPaM.GetPoint()->GetContentIndex());
+        CPPUNIT_ASSERT_EQUAL(sal_Int32(7), 
aEmptyPaM.GetMark()->GetContentIndex());
+
+        CPPUNIT_ASSERT_EQUAL(u"学森林[しんりん]海上[かいじょう]地面[じめん]校"_ustr,
+                             fnGetCombinedString());
+    }
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(SwDocTest);
diff --git a/sw/source/core/doc/docruby.cxx b/sw/source/core/doc/docruby.cxx
index 90f4e771d993..15629c70325e 100644
--- a/sw/source/core/doc/docruby.cxx
+++ b/sw/source/core/doc/docruby.cxx
@@ -40,6 +40,8 @@
 
 using namespace ::com::sun::star::i18n;
 
+constexpr int nMaxBaseTexts = 30;
+
 /*
  * Members in the list:
  *   - String - the orig text
@@ -78,9 +80,9 @@ sal_uInt16 SwDoc::FillRubyList( const SwPaM& rPam, 
SwRubyList& rList )
                      else
                         break;
                 }
-            } while( 30 > rList.size() && *aPam.GetPoint() < *pEnd );
+            } while (nMaxBaseTexts > rList.size() && *aPam.GetPoint() < *pEnd);
         }
-        if( 30 <= rList.size() )
+        if (nMaxBaseTexts <= rList.size())
             break;
         _pStartCursor = _pStartCursor->GetNext();
     } while( _pStartCursor != _pStartCursor2 );
@@ -88,95 +90,146 @@ sal_uInt16 SwDoc::FillRubyList( const SwPaM& rPam, 
SwRubyList& rList )
     return rList.size();
 }
 
-void SwDoc::SetRubyList( const SwPaM& rPam, const SwRubyList& rList )
+void SwDoc::SetRubyList(SwPaM& rPam, const SwRubyList& rList)
 {
-    GetIDocumentUndoRedo().StartUndo( SwUndoId::SETRUBYATTR, nullptr );
+    SwPaM aOrigPam{ *rPam.GetPoint(), *rPam.GetMark() };
+    aOrigPam.Normalize();
+
+    GetIDocumentUndoRedo().StartUndo(SwUndoId::SETRUBYATTR, nullptr);
     const o3tl::sorted_vector<sal_uInt16> aDelArr{ RES_TXTATR_CJK_RUBY };
 
     SwRubyList::size_type nListEntry = 0;
+    int nCurrBaseTexts = 0;
 
-    const SwPaM *_pStartCursor = rPam.GetNext(),
-                *_pStartCursor2 = _pStartCursor;
-    bool bCheckEmpty = &rPam != _pStartCursor;
-    do {
-        auto [pStt, pEnd] = _pStartCursor->StartEnd(); // SwPosition*
-        if( !bCheckEmpty || ( pStt != pEnd && *pStt != *pEnd ))
+    const SwPaM* pStartCursor = rPam.GetNext();
+    auto [pStt, pEnd] = pStartCursor->StartEnd();
+
+    bool bCheckEmpty = (&rPam == pStartCursor) || (pStt != pEnd && *pStt != 
*pEnd);
+
+    // Sequentially replace as many spans as possible
+    SwPaM aPam(*pStt);
+    while (bCheckEmpty && nListEntry < rList.size() && nCurrBaseTexts < 
nMaxBaseTexts)
+    {
+        if (pEnd != pStt)
         {
+            aPam.SetMark();
+            *aPam.GetMark() = *pEnd;
+        }
 
-            SwPaM aPam( *pStt );
-            do {
-                SwRubyListEntry aCheckEntry;
-                if( pEnd != pStt )
-                {
-                    aPam.SetMark();
-                    *aPam.GetMark() = *pEnd;
-                }
-                if( SelectNextRubyChars( aPam, aCheckEntry ))
-                {
-                    const SwRubyListEntry* pEntry = rList[ nListEntry++ 
].get();
-                    if( aCheckEntry.GetRubyAttr() != pEntry->GetRubyAttr() )
-                    {
-                        // set/reset the attribute
-                        if( !pEntry->GetRubyAttr().GetText().isEmpty() )
-                        {
-                            getIDocumentContentOperations().InsertPoolItem( 
aPam, pEntry->GetRubyAttr() );
-                        }
-                        else
-                        {
-                            ResetAttrs( aPam, true, aDelArr );
-                        }
-                    }
+        SwRubyListEntry aCheckEntry;
+        if (!SelectNextRubyChars(aPam, aCheckEntry))
+        {
+            // goto next paragraph
+            aPam.DeleteMark();
+            aPam.Move(fnMoveForward, GoInNode);
 
-                    if (aCheckEntry.GetText() != pEntry->GetText())
-                    {
-                        if (pEntry->GetText().isEmpty())
-                        {
-                            ResetAttrs(aPam, true, aDelArr);
-                        }
-
-                        // text is changed, so replace the original
-                        getIDocumentContentOperations().ReplaceRange(aPam, 
pEntry->GetText(),
-                                                                     false);
-                        std::swap(*aPam.GetMark(), *aPam.GetPoint());
-                    }
+            if (*aPam.GetPoint() >= *pEnd)
+            {
+                break;
+            }
 
-                    aPam.DeleteMark();
-                }
-                else
-                {
-                     if( *aPam.GetPoint() < *pEnd )
-                     {
-                        // goto next paragraph
-                        aPam.DeleteMark();
-                        aPam.Move( fnMoveForward, GoInNode );
-                     }
-                     else
-                    {
-                        const SwRubyListEntry* pEntry = rList[ nListEntry++ 
].get();
-
-                        // set/reset the attribute
-                        if( !pEntry->GetRubyAttr().GetText().isEmpty() &&
-                            !pEntry->GetText().isEmpty() )
-                        {
-                            getIDocumentContentOperations().InsertString( 
aPam, pEntry->GetText() );
-                            aPam.SetMark();
-                            aPam.GetMark()->AdjustContent( 
-pEntry->GetText().getLength() );
-                            getIDocumentContentOperations().InsertPoolItem(
-                                aPam, pEntry->GetRubyAttr(), 
SetAttrMode::DONTEXPAND );
-                        }
-                        else
-                            break;
-                        aPam.DeleteMark();
-                    }
-                }
-            } while( nListEntry < rList.size() && *aPam.GetPoint() < *pEnd );
+            continue;
         }
-        if( 30 <= rList.size() )
-            break;
-        _pStartCursor = _pStartCursor->GetNext();
-    } while( _pStartCursor != _pStartCursor2 );
 
-    GetIDocumentUndoRedo().EndUndo( SwUndoId::SETRUBYATTR, nullptr );
+        ++nCurrBaseTexts;
+
+        const SwRubyListEntry* pEntry = rList[nListEntry++].get();
+        if (aCheckEntry.GetRubyAttr() != pEntry->GetRubyAttr())
+        {
+            // set/reset the attribute
+            if (!pEntry->GetRubyAttr().GetText().isEmpty())
+            {
+                getIDocumentContentOperations().InsertPoolItem(aPam, 
pEntry->GetRubyAttr());
+            }
+            else
+            {
+                ResetAttrs(aPam, true, aDelArr);
+            }
+        }
+
+        if (aCheckEntry.GetText() != pEntry->GetText())
+        {
+            if (pEntry->GetText().isEmpty())
+            {
+                ResetAttrs(aPam, true, aDelArr);
+            }
+
+            // text is changed, so replace the original
+            getIDocumentContentOperations().ReplaceRange(aPam, 
pEntry->GetText(), false);
+            aPam.Exchange();
+        }
+
+        aPam.DeleteMark();
+    }
+
+    // Delete any spans past the end of the ruby list
+    while (nListEntry == rList.size() && nCurrBaseTexts < nMaxBaseTexts)
+    {
+        if (pEnd != pStt)
+        {
+            aPam.SetMark();
+            *aPam.GetMark() = *pEnd;
+        }
+
+        SwRubyListEntry aCheckEntry;
+        if (!SelectNextRubyChars(aPam, aCheckEntry))
+        {
+            // goto next paragraph
+            aPam.DeleteMark();
+            aPam.Move(fnMoveForward, GoInNode);
+
+            if (*aPam.GetPoint() >= *pEnd)
+            {
+                break;
+            }
+
+            continue;
+        }
+
+        ++nCurrBaseTexts;
+
+        ResetAttrs(aPam, true, aDelArr);
+        getIDocumentContentOperations().DeleteRange(aPam);
+        aPam.Exchange();
+
+        aPam.DeleteMark();
+    }
+
+    // Insert any spans past the end of the base text list
+    sal_Int32 nTotalContentGrowth = 0;
+    while (nListEntry < rList.size())
+    {
+        const SwRubyListEntry* pEntry = rList[nListEntry++].get();
+
+        if (pEnd != pStt)
+        {
+            aPam.SetMark();
+            *aPam.GetMark() = *pEnd;
+        }
+
+        aPam.SetMark();
+        getIDocumentContentOperations().InsertString(aPam, pEntry->GetText());
+        nTotalContentGrowth += pEntry->GetText().getLength();
+
+        if (!pEntry->GetRubyAttr().GetText().isEmpty())
+        {
+            getIDocumentContentOperations().InsertPoolItem(aPam, 
pEntry->GetRubyAttr());
+        }
+
+        aPam.DeleteMark();
+    }
+
+    // Expand selection to account for insertion
+    rPam.Normalize();
+    rPam = SwPaM{ *aOrigPam.GetPoint(), *rPam.GetMark() };
+    if (*rPam.GetPoint() == *rPam.GetMark())
+    {
+        rPam.GetPoint()->AdjustContent(-nTotalContentGrowth);
+    }
+
+    rPam.Normalize();
+
+    GetIDocumentUndoRedo().EndUndo(SwUndoId::SETRUBYATTR, nullptr);
 }
 
 bool SwDoc::SelectNextRubyChars( SwPaM& rPam, SwRubyListEntry& rEntry )
@@ -320,8 +373,4 @@ bool SwDoc::SelectNextRubyChars( SwPaM& rPam, 
SwRubyListEntry& rEntry )
     return rPam.HasMark();
 }
 
-SwRubyListEntry::~SwRubyListEntry()
-{
-}
-
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to