editeng/Library_editeng.mk | 1 editeng/inc/TextPortion.hxx | 11 + editeng/inc/editattr.hxx | 10 + editeng/source/editeng/ContentNode.cxx | 7 - editeng/source/editeng/editattr.cxx | 9 + editeng/source/editeng/editdbg.cxx | 9 + editeng/source/editeng/editdoc.cxx | 5 editeng/source/editeng/eerdll.cxx | 2 editeng/source/editeng/impedit.hxx | 1 editeng/source/editeng/impedit3.cxx | 166 +++++++++++++++++++++++- editeng/source/items/rubyitem.cxx | 134 +++++++++++++++++++ editeng/source/items/textitem.cxx | 1 include/editeng/editids.hrc | 2 include/editeng/eeitem.hxx | 4 include/editeng/memberids.h | 6 include/editeng/rubyitem.hxx | 59 ++++++++ include/editeng/unoprnms.hxx | 4 include/editeng/unotext.hxx | 7 - include/svl/poolitem.hxx | 1 sw/inc/unomid.h | 7 - sw/source/core/txtnode/fmtatr2.cxx | 1 sw/source/core/unocore/unoobj.cxx | 1 sw/source/core/unocore/unoport.cxx | 1 vcl/qa/cppunit/pdfexport/data/textbox-ruby.fodt | 149 +++++++++++++++++++++ vcl/qa/cppunit/pdfexport/pdfexport2.cxx | 89 ++++++++++++ 25 files changed, 671 insertions(+), 16 deletions(-)
New commits: commit f08a984a94f5b504c75f4f4ce8a98d0bc074e0ee Author: Jonathan Clark <jonat...@libreoffice.org> AuthorDate: Thu May 1 10:02:55 2025 -0600 Commit: Jonathan Clark <jonat...@libreoffice.org> CommitDate: Thu May 15 13:06:12 2025 +0200 editeng: Initial ruby character implementation Implements a minimal functional version of ruby character layout and rendering in Edit Engine. Many features are missing from this implementation, and will be added in future commits. Currently, ruby characters cannot be created via the user interface. Ruby character styles are also currently hard-coded. Change-Id: Ia7c18ccae6c37cd320acc214574ba3123c18ddb0 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185210 Reviewed-by: Jonathan Clark <jonat...@libreoffice.org> Tested-by: Jenkins diff --git a/editeng/Library_editeng.mk b/editeng/Library_editeng.mk index cfd4f357270c..05ced9a8614a 100644 --- a/editeng/Library_editeng.mk +++ b/editeng/Library_editeng.mk @@ -88,6 +88,7 @@ $(eval $(call gb_Library_add_exception_objects,editeng,\ editeng/source/items/optitems \ editeng/source/items/paperinf \ editeng/source/items/paraitem \ + editeng/source/items/rubyitem \ editeng/source/items/svdfield \ editeng/source/items/svxfont \ editeng/source/items/textitem \ diff --git a/editeng/inc/TextPortion.hxx b/editeng/inc/TextPortion.hxx index 3c1caa882df3..84c964340f55 100644 --- a/editeng/inc/TextPortion.hxx +++ b/editeng/inc/TextPortion.hxx @@ -86,10 +86,18 @@ struct ExtraPortionInfo } }; +struct RubyPortionInfo +{ + tools::Long nXOffset = 0; + tools::Long nYOffset = 0; + sal_uInt16 nMaxAscent = 0; +}; + class TextPortion { private: std::unique_ptr<ExtraPortionInfo> xExtraInfos; + std::unique_ptr<RubyPortionInfo> xRubyInfos; sal_Int32 nLen; Size aOutSz = Size(-1, -1); PortionKind nKind = PortionKind::TEXT; @@ -142,6 +150,9 @@ public: ExtraPortionInfo* GetExtraInfos() const { return xExtraInfos.get(); } void SetExtraInfos(ExtraPortionInfo* p) { xExtraInfos.reset(p); } + + RubyPortionInfo const* GetRubyInfos() const { return xRubyInfos.get(); } + void SetRubyInfos(std::unique_ptr<RubyPortionInfo> p) { xRubyInfos = std::move(p); } }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/editattr.hxx b/editeng/inc/editattr.hxx index 68b5f019f88d..806f97625d6c 100644 --- a/editeng/inc/editattr.hxx +++ b/editeng/inc/editattr.hxx @@ -48,6 +48,7 @@ class EditCharAttrib sal_Int32 nEnd; bool bFeature :1; bool bEdge :1; + bool bExpandable : 1; public: EditCharAttrib(SfxItemPool&, const SfxPoolItem&, sal_Int32 nStart, sal_Int32 nEnd); @@ -92,6 +93,9 @@ public: bool IsEdge() const { return bEdge; } void SetEdge( bool b ) { bEdge = b; } + + bool IsExpandable() const { return !bFeature && bExpandable; } + void SetExpandable(bool b) { bExpandable = b; } }; inline sal_Int32 EditCharAttrib::GetLen() const @@ -295,7 +299,13 @@ public: virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; }; +class EditCharAttribRuby final : public EditCharAttrib +{ +public: + EditCharAttribRuby(SfxItemPool&, const SfxPoolItem&, sal_Int32 nStart, sal_Int32 nEnd); + virtual void SetFont(SvxFont& rFont, OutputDevice* pOutDev) override; +}; class EditCharAttribTab final : public EditCharAttrib { diff --git a/editeng/source/editeng/ContentNode.cxx b/editeng/source/editeng/ContentNode.cxx index 8937b39002c7..27764b997161 100644 --- a/editeng/source/editeng/ContentNode.cxx +++ b/editeng/source/editeng/ContentNode.cxx @@ -70,7 +70,7 @@ void ContentNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew ) pAttrib->MoveForward( nNew ); } // 0: Expand empty attribute, if at insertion point - else if ( pAttrib->IsEmpty() ) + else if (pAttrib->IsEmpty() && pAttrib->IsExpandable()) { // Do not check Index, an empty one could only be there // When later checking it anyhow: @@ -90,7 +90,8 @@ void ContentNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew ) // and if not in exclude list! // Otherwise, a UL will go on until a new ULDB, expanding both // if ( !pAttrib->IsFeature() && !rExclList.FindAttrib( pAttrib->Which() ) ) - if ( !pAttrib->IsFeature() && !maCharAttribList.FindEmptyAttrib( pAttrib->Which(), nIndex ) ) + if (pAttrib->IsExpandable() + && !maCharAttribList.FindEmptyAttrib(pAttrib->Which(), nIndex)) { if ( !pAttrib->IsEdge() ) pAttrib->Expand( nNew ); @@ -107,7 +108,7 @@ void ContentNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew ) // 3: Attribute starts on index... else if ( pAttrib->GetStart() == nIndex ) { - if ( pAttrib->IsFeature() ) + if (!pAttrib->IsExpandable()) { pAttrib->MoveForward( nNew ); bResort = true; diff --git a/editeng/source/editeng/editattr.cxx b/editeng/source/editeng/editattr.cxx index 75bbcabc5a66..deab7b6b7dea 100644 --- a/editeng/source/editeng/editattr.cxx +++ b/editeng/source/editeng/editattr.cxx @@ -52,6 +52,7 @@ EditCharAttrib::EditCharAttrib(SfxItemPool& rPool, const SfxPoolItem& rItem, sal , nEnd(nE) , bFeature(false) , bEdge(false) +, bExpandable(true) { assert((rItem.Which() >= EE_ITEMS_START) && (rItem.Which() <= EE_ITEMS_END)); assert((rItem.Which() < EE_FEATURE_START) || (rItem.Which() > EE_FEATURE_END) || (nE == (nS+1))); @@ -244,7 +245,15 @@ void EditCharAttribLanguage::SetFont( SvxFont& rFont, OutputDevice* ) rFont.SetLanguage( static_cast<const SvxLanguageItem*>(GetItem())->GetLanguage() ); } +EditCharAttribRuby::EditCharAttribRuby(SfxItemPool& rPool, const SfxPoolItem& rItem, + sal_Int32 nStartIn, sal_Int32 nEndIn) + : EditCharAttrib(rPool, rItem, nStartIn, nEndIn) +{ + assert(rItem.Which() == EE_CHAR_RUBY); + SetExpandable(false); +} +void EditCharAttribRuby::SetFont(SvxFont&, OutputDevice*) {} EditCharAttribShadow::EditCharAttribShadow(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) : EditCharAttrib(rPool, rItem, _nStart, _nEnd) diff --git a/editeng/source/editeng/editdbg.cxx b/editeng/source/editeng/editdbg.cxx index d2a9290dae00..c9b2fd890e08 100644 --- a/editeng/source/editeng/editdbg.cxx +++ b/editeng/source/editeng/editdbg.cxx @@ -38,6 +38,7 @@ #include <editeng/shdditem.hxx> #include <editeng/escapementitem.hxx> #include <editeng/kernitem.hxx> +#include <editeng/rubyitem.hxx> #include <editeng/wrlmitem.hxx> #include <editeng/autokernitem.hxx> #include <editeng/langitem.hxx> @@ -173,6 +174,11 @@ struct DebOutBuffer { appendHeightAndPts(descr, rItem.GetValue(), rPool.GetMetric(rItem.Which())); } + void append(std::string_view descr, const SvxRubyItem& rItem) + { + str.append(OString::Concat(descr) + + OUStringToOString(rItem.GetText(), RTL_TEXTENCODING_UTF8)); + } }; } @@ -304,6 +310,9 @@ static OString DbgOutItem(const SfxItemPool& rPool, const SfxPoolItem& rItem) case EE_CHAR_XMLATTRIBS: buffer.str.append("XMLAttribs=..."); break; + case EE_CHAR_RUBY: + buffer.append("Ruby=", rItem.StaticWhichCast(EE_CHAR_RUBY)); + break; } return buffer.str.makeStringAndClear(); } diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx index 590901f626fc..8c7277f74224 100644 --- a/editeng/source/editeng/editdoc.cxx +++ b/editeng/source/editeng/editdoc.cxx @@ -308,6 +308,11 @@ EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const SfxPoolItem& rAttr, sa return new EditCharAttribBackgroundColor(rPool, rAttr, nS, nE ); } break; + case EE_CHAR_RUBY: + { + return new EditCharAttribRuby(rPool, rAttr, nS, nE); + } + break; default: break; } diff --git a/editeng/source/editeng/eerdll.cxx b/editeng/source/editeng/eerdll.cxx index 9bc101646c7e..d7ed5397ebb3 100644 --- a/editeng/source/editeng/eerdll.cxx +++ b/editeng/source/editeng/eerdll.cxx @@ -50,6 +50,7 @@ #include <editeng/kernitem.hxx> #include <editeng/lrspitem.hxx> #include <editeng/postitem.hxx> +#include <editeng/rubyitem.hxx> #include <editeng/shdditem.hxx> #include <editeng/udlnitem.hxx> #include <editeng/ulspitem.hxx> @@ -154,6 +155,7 @@ ItemInfoPackage& getItemInfoPackageEditEngine() { EE_CHAR_CASEMAP, new SvxCaseMapItem( SvxCaseMap::NotMapped, EE_CHAR_CASEMAP ), SID_ATTR_CHAR_CASEMAP, SFX_ITEMINFOFLAG_NONE }, { EE_CHAR_GRABBAG, new SfxGrabBagItem( EE_CHAR_GRABBAG ), SID_ATTR_CHAR_GRABBAG, SFX_ITEMINFOFLAG_NONE }, { EE_CHAR_BKGCOLOR, new SvxColorItem( COL_AUTO, EE_CHAR_BKGCOLOR ), SID_ATTR_CHAR_BACK_COLOR, SFX_ITEMINFOFLAG_NONE }, + { EE_CHAR_RUBY, new SvxRubyItem( EE_CHAR_RUBY ), SID_ATTR_CHAR_RUBY, SFX_ITEMINFOFLAG_NONE }, { EE_FEATURE_TAB, new SfxVoidItem( EE_FEATURE_TAB ), 0, SFX_ITEMINFOFLAG_NONE }, { EE_FEATURE_LINEBR, new SfxVoidItem( EE_FEATURE_LINEBR ), 0, SFX_ITEMINFOFLAG_NONE }, { EE_FEATURE_NOTCONV, new SvxColorItem( COL_RED, EE_FEATURE_NOTCONV ), SID_ATTR_CHAR_CHARSETCOLOR, SFX_ITEMINFOFLAG_NONE }, diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx index 6466f563365b..36804880cd23 100644 --- a/editeng/source/editeng/impedit.hxx +++ b/editeng/source/editeng/impedit.hxx @@ -696,6 +696,7 @@ private: bool createLinesForEmptyParagraph(ParaPortion& rParaPortion, bool bIsScaling = false); tools::Long calculateMaxLineWidth(tools::Long nStartX, SvxLRSpaceItem const& rLRItem, const SvxFontUnitMetrics& rMetrics); + void populateRubyInfo(ParaPortion& rParaPortion, EditLine* pLine); bool CreateLines(sal_Int32 nPara, sal_uInt32 nStartPosY, bool bIsScaling = false); void CreateAndInsertEmptyLine(ParaPortion& rParaPortion); diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx index ba01e1c3584e..b11dc06fbfe3 100644 --- a/editeng/source/editeng/impedit3.cxx +++ b/editeng/source/editeng/impedit3.cxx @@ -41,6 +41,7 @@ #include <editeng/fontitem.hxx> #include <editeng/wghtitem.hxx> #include <editeng/postitem.hxx> +#include <editeng/rubyitem.hxx> #include <editeng/langitem.hxx> #include <editeng/frmdiritem.hxx> #include <editeng/scriptspaceitem.hxx> @@ -627,6 +628,102 @@ tools::Long ImpEditEngine::calculateMaxLineWidth(tools::Long nStartX, SvxLRSpace return nMaxLineWidth; } +void ImpEditEngine::populateRubyInfo(ParaPortion& rParaPortion, EditLine* pLine) +{ + ContentNode* const pNode = rParaPortion.GetNode(); + SvxFont aTmpFont(pNode->GetCharAttribs().GetDefFont()); + SvxFont aRubyStartFont = aTmpFont; + + sal_Int32 nTextPos = pLine->GetStart(); + const EditCharAttrib* pNextRubyAttr + = pNode->GetCharAttribs().FindNextAttrib(EE_CHAR_RUBY, nTextPos); + TextPortion* pTPRubyStart = nullptr; + tools::Long nTPMaxAscent = 0; + tools::Long nTPTotalWidth = 0; + for (sal_Int32 nP = pLine->GetStartPortion(); pNextRubyAttr && nP <= pLine->GetEndPortion(); + ++nP) + { + SeekCursor(pNode, nTextPos, aTmpFont); + + TextPortion& rTP = rParaPortion.GetTextPortions()[nP]; + rTP.SetRubyInfos({}); + + if (nTextPos == pNextRubyAttr->GetStart()) + { + pTPRubyStart = &rTP; + aRubyStartFont = aTmpFont; + SeekCursor(pNode, nTextPos, aTmpFont); + + nTPMaxAscent = 0; + nTPTotalWidth = 0; + } + + nTextPos += rTP.GetLen(); + nTPTotalWidth += rTP.GetSize().getWidth(); + + aTmpFont.SetPhysFont(*GetRefDevice()); + nTPMaxAscent = std::max( + nTPMaxAscent, static_cast<tools::Long>(GetRefDevice()->GetFontMetric().GetAscent())); + + if (pTPRubyStart && nTextPos >= pNextRubyAttr->GetEnd()) + { + auto pRubyInfo = std::make_unique<RubyPortionInfo>(); + + // Get ruby text width + const auto* pRuby = static_cast<const SvxRubyItem*>(pNextRubyAttr->GetItem()); + + // TODO: Style support is unimplemented. For now, use a hard-coded 50% scale + aRubyStartFont.SetFontSize(aRubyStartFont.GetFontSize() / 2); + aRubyStartFont.SetPhysFont(*GetRefDevice()); + + auto aRubyMetrics = GetRefDevice()->GetFontMetric(); + auto nRubyAscent = static_cast<tools::Long>(aRubyMetrics.GetAscent()); + + tools::Long nRubyWidth = aRubyStartFont + .QuickGetTextSize(GetRefDevice(), pRuby->GetText(), 0, + pRuby->GetText().getLength(), + /*pDXArray=*/nullptr, /*bStacked=*/false) + .Width(); + + switch (pRuby->GetAdjustment()) + { + case css::text::RubyAdjust_LEFT: + pRubyInfo->nXOffset = 0; + break; + + case css::text::RubyAdjust_RIGHT: + pRubyInfo->nXOffset = nTPTotalWidth - nRubyWidth; + break; + + default: + case css::text::RubyAdjust_CENTER: + pRubyInfo->nXOffset = (nTPTotalWidth - nRubyWidth) / 2; + break; + } + + switch (pRuby->GetPosition()) + { + default: + case css::text::RubyPosition::ABOVE: + pRubyInfo->nYOffset = nTPMaxAscent; + pRubyInfo->nMaxAscent = nTPMaxAscent + nRubyAscent; + break; + + case css::text::RubyPosition::BELOW: + pRubyInfo->nYOffset = -nRubyAscent; + pRubyInfo->nMaxAscent = 0; + break; + } + + pTPRubyStart->SetRubyInfos(std::move(pRubyInfo)); + + pNextRubyAttr + = rParaPortion.GetNode()->GetCharAttribs().FindNextAttrib(EE_CHAR_RUBY, nTextPos); + pTPRubyStart = nullptr; + } + } +} + bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY, bool bIsScaling ) { assert(GetParaPortions().exists(nPara) && "Portion paragraph index is not valid"); @@ -1449,6 +1546,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY, bool bI // Line finished => adjust + populateRubyInfo(rParaPortion, pLine); // CalcTextSize should be replaced by a continuous registering! Size aTextSize = pLine->CalcTextSize(rParaPortion); @@ -1481,6 +1579,11 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY, bool bI aTmpFont.SetPhysFont(*GetRefDevice()); ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); + + if(const auto* pRubyInfo = rTP.GetRubyInfos(); pRubyInfo) + { + aFormatterMetrics.nMaxAscent = std::max(aFormatterMetrics.nMaxAscent, pRubyInfo->nMaxAscent); + } } nTPos = nTPos + rTP.GetLen(); } @@ -2017,9 +2120,28 @@ void ImpEditEngine::ImpBreakLine(ParaPortion& rParaPortion, EditLine& rLine, Tex if (!maStatus.IsSingleLine()) { - i18n::LineBreakResults aLBR = _xBI->getLineBreak( - pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions ); - nBreakPos = aLBR.breakIndex; + // Scan backwards for a valid break position + while (nMaxBreakPos > nMinBreakPos) + { + i18n::LineBreakResults aLBR + = _xBI->getLineBreak(pNode->GetString(), nMaxBreakPos, aLocale, + nMinBreakPos, aHyphOptions, aUserOptions); + nBreakPos = aLBR.breakIndex; + + // Don't allow line breaks under ruby characters + if (auto* pRubyAttr + = pNode->GetCharAttribs().FindAttribRightOpen(EE_CHAR_RUBY, nBreakPos); + pRubyAttr) + { + if (nBreakPos > pRubyAttr->GetStart() && nBreakPos < pRubyAttr->GetEnd()) + { + nMaxBreakPos = pRubyAttr->GetStart(); + continue; + } + } + + break; + } // show soft hyphen if ( nBreakPos > 0 && CH_SOFTHYPHEN == pNode->GetString()[nBreakPos - 1] ) @@ -3444,6 +3566,44 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po if (isXOverflowDirectionAware(aTmpPos, aClipRect)) break; // No further output in line necessary + // Draw ruby characters, if present + if (const auto* pRubyInfo = rTextPortion.GetRubyInfos(); pRubyInfo) + { + auto* pRubyAttr = rParaPortion.GetNode()->GetCharAttribs().FindAttrib( + EE_CHAR_RUBY, nIndex); + if (pRubyAttr && pRubyAttr->GetStart() == nIndex) + { + SeekCursor(rParaPortion.GetNode(), nIndex, aTmpFont, &rOutDev); + + const auto* pRuby + = static_cast<const SvxRubyItem*>(pRubyAttr->GetItem()); + if (rDrawPortion) + { + const bool bEndOfLine(nPortion == pLine->GetEndPortion()); + const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); + + const Color aOverlineColor(rOutDev.GetOverlineColor()); + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + Point aRubyPos = aTmpPos; + aRubyPos.AdjustX(pRubyInfo->nXOffset); + aRubyPos.AdjustY(-pRubyInfo->nYOffset); + + auto nPrevSz = aTmpFont.GetFontSize(); + aTmpFont.SetFontSize(nPrevSz / 2); + + const DrawPortionInfo aInfo( + aRubyPos, pRuby->GetText(), 0, pRuby->GetText().getLength(), + {}, {}, aTmpFont, nParaPortion, 0, nullptr, nullptr, + bEndOfLine, bEndOfParagraph, false, nullptr, aOverlineColor, + aTextLineColor); + rDrawPortion(aInfo); + + aTmpFont.SetFontSize(nPrevSz); + } + } + } + switch ( rTextPortion.GetKind() ) { case PortionKind::TEXT: diff --git a/editeng/source/items/rubyitem.cxx b/editeng/source/items/rubyitem.cxx new file mode 100644 index 000000000000..76d8ecab701e --- /dev/null +++ b/editeng/source/items/rubyitem.cxx @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <editeng/memberids.h> +#include <editeng/rubyitem.hxx> + +using namespace ::com::sun::star; + +SfxPoolItem* SvxRubyItem::CreateDefault() { return new SvxRubyItem(0); } + +SvxRubyItem::SvxRubyItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + +const OUString& SvxRubyItem::GetText() const { return m_aText; } + +void SvxRubyItem::SetText(OUString aValue) +{ + ASSERT_CHANGE_REFCOUNTED_ITEM; + m_aText = std::move(aValue); +} + +css::text::RubyAdjust SvxRubyItem::GetAdjustment() const { return m_eAdjustment; } + +void SvxRubyItem::SetAdjustment(css::text::RubyAdjust eValue) +{ + ASSERT_CHANGE_REFCOUNTED_ITEM; + m_eAdjustment = eValue; +} + +sal_Int16 SvxRubyItem::GetPosition() const { return m_ePosition; } + +void SvxRubyItem::SetPosition(sal_Int16 eValue) +{ + ASSERT_CHANGE_REFCOUNTED_ITEM; + m_ePosition = eValue; +} + +bool SvxRubyItem::operator==(const SfxPoolItem& rItem) const +{ + assert(SfxPoolItem::operator==(rItem)); + const auto& rCastItem = static_cast<const SvxRubyItem&>(rItem); + + return std::tie(m_aText, m_eAdjustment, m_ePosition) + == std::tie(rCastItem.m_aText, rCastItem.m_eAdjustment, rCastItem.m_ePosition); +} + +SvxRubyItem* SvxRubyItem::Clone(SfxItemPool*) const { return new SvxRubyItem(*this); } + +bool SvxRubyItem::GetPresentation(SfxItemPresentation /*ePres*/, MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, OUString& rText, + const IntlWrapper& /*rIntl*/ + ) const +{ + rText = m_aText; + return true; +} + +bool SvxRubyItem::QueryValue(uno::Any& rVal, sal_uInt8 nMemberId) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + case MID_RUBY_TEXT: + rVal <<= m_aText; + return true; + + case MID_RUBY_ADJUST: + rVal <<= static_cast<sal_Int16>(m_eAdjustment); + return true; + + case MID_RUBY_POSITION: + rVal <<= m_ePosition; + return true; + } + + return false; +} + +bool SvxRubyItem::PutValue(const uno::Any& rVal, sal_uInt8 nMemberId) +{ + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + case MID_RUBY_TEXT: + if (OUString aValue; rVal >>= aValue) + { + SetText(std::move(aValue)); + return true; + } + break; + + case MID_RUBY_ADJUST: + if (sal_Int16 eValue; rVal >>= eValue) + { + if (eValue >= sal_Int16(css::text::RubyAdjust_LEFT) + && eValue <= sal_Int16(css::text::RubyAdjust_INDENT_BLOCK)) + { + SetAdjustment(css::text::RubyAdjust{ eValue }); + return true; + } + } + break; + + case MID_RUBY_POSITION: + if (sal_Int16 eValue; rVal >>= eValue) + { + SetPosition(eValue); + return true; + } + break; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/textitem.cxx b/editeng/source/items/textitem.cxx index 5adfabe97c72..56923526cff1 100644 --- a/editeng/source/items/textitem.cxx +++ b/editeng/source/items/textitem.cxx @@ -76,6 +76,7 @@ #include <editeng/charrotateitem.hxx> #include <editeng/charscaleitem.hxx> #include <editeng/charreliefitem.hxx> +#include <editeng/rubyitem.hxx> #include <editeng/itemtype.hxx> #include <editeng/eerdll.hxx> #include <docmodel/color/ComplexColorJSON.hxx> diff --git a/include/editeng/editids.hrc b/include/editeng/editids.hrc index 9ff4884fbcbc..84e8aab8ed2a 100644 --- a/include/editeng/editids.hrc +++ b/include/editeng/editids.hrc @@ -55,6 +55,7 @@ class SvxOverlineItem; class SvxPageModelItem; class SvxParaVertAlignItem; class SvxPostureItem; +class SvxRubyItem; class SvxScriptSpaceItem; class SvxShadowItem; class SvxShadowedItem; @@ -149,6 +150,7 @@ class SvxWordLineModeItem; #define SID_ATTR_PARA_SCRIPTSPACE TypedWhichId<SvxScriptSpaceItem>( SID_SVX_START + 901 ) #define SID_ATTR_PARA_HANGPUNCTUATION TypedWhichId<SvxHangingPunctuationItem>( SID_SVX_START + 902 ) #define SID_ATTR_PARA_FORBIDDEN_RULES TypedWhichId<SvxForbiddenRuleItem>( SID_SVX_START + 903 ) +#define SID_ATTR_CHAR_RUBY TypedWhichId<SvxRubyItem>( SID_SVX_START + 904 ) #define SID_ATTR_CHAR_VERTICAL ( SID_SVX_START + 905 ) #define SID_ATTR_CHAR_ROTATED TypedWhichId<SvxCharRotateItem>( SID_SVX_START + 910 ) #define SID_ATTR_CHAR_SCALEWIDTH TypedWhichId<SvxCharScaleWidthItem>( SID_SVX_START + 911 ) diff --git a/include/editeng/eeitem.hxx b/include/editeng/eeitem.hxx index f8c3d585fc24..3bbc30a869f7 100644 --- a/include/editeng/eeitem.hxx +++ b/include/editeng/eeitem.hxx @@ -60,6 +60,7 @@ class SvxBulletItem; class SvxNumBulletItem; class SvxJustifyMethodItem; class SvxVerJustifyItem; +class SvxRubyItem; /* * NOTE: Changes in this file will probably require @@ -127,8 +128,9 @@ inline constexpr TypedWhichId<SvxOverlineItem> EE_CHAR_OVERLINE (EE inline constexpr TypedWhichId<SvxCaseMapItem> EE_CHAR_CASEMAP (EE_CHAR_START+29); inline constexpr TypedWhichId<SfxGrabBagItem> EE_CHAR_GRABBAG (EE_CHAR_START+30); inline constexpr TypedWhichId<SvxColorItem> EE_CHAR_BKGCOLOR (EE_CHAR_START+31); +inline constexpr TypedWhichId<SvxRubyItem> EE_CHAR_RUBY (EE_CHAR_START+32); -inline constexpr sal_uInt16 EE_CHAR_END (EE_CHAR_START + 31); +inline constexpr sal_uInt16 EE_CHAR_END (EE_CHAR_START + 32); inline constexpr sal_uInt16 EE_FEATURE_START (EE_CHAR_END + 1); inline constexpr sal_uInt16 EE_FEATURE_TAB (EE_FEATURE_START + 0); diff --git a/include/editeng/memberids.h b/include/editeng/memberids.h index 7748e8167750..2b75123d94d8 100644 --- a/include/editeng/memberids.h +++ b/include/editeng/memberids.h @@ -212,6 +212,12 @@ #define MID_COMPLEX_COLOR_JSON 8 #define MID_COMPLEX_COLOR 9 +// SvxRubyItem and SwFormatRuby +#define MID_RUBY_TEXT 0 +#define MID_RUBY_ADJUST 1 +#define MID_RUBY_CHARSTYLE 2 +#define MID_RUBY_ABOVE 3 +#define MID_RUBY_POSITION 4 #endif diff --git a/include/editeng/rubyitem.hxx b/include/editeng/rubyitem.hxx new file mode 100644 index 000000000000..3c8b88ea2c3d --- /dev/null +++ b/include/editeng/rubyitem.hxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once + +#include <svl/poolitem.hxx> +#include <editeng/editengdllapi.h> +#include <com/sun/star/text/RubyAdjust.hpp> +#include <com/sun/star/text/RubyPosition.hpp> + +class EDITENG_DLLPUBLIC SvxRubyItem : public SfxPoolItem +{ + OUString m_aText; + css::text::RubyAdjust m_eAdjustment = css::text::RubyAdjust::RubyAdjust_LEFT; + sal_Int16 m_ePosition = css::text::RubyPosition::ABOVE; + +public: + static SfxPoolItem* CreateDefault(); + + DECLARE_ITEM_TYPE_FUNCTION(SvxRubyItem) + explicit SvxRubyItem(const sal_uInt16 nId); + + const OUString& GetText() const; + void SetText(OUString aValue); + + css::text::RubyAdjust GetAdjustment() const; + void SetAdjustment(css::text::RubyAdjust eValue); + + sal_Int16 GetPosition() const; + void SetPosition(sal_Int16 eValue); + + // "pure virtual Methods" from SfxPoolItem + virtual bool operator==(const SfxPoolItem&) const override; + virtual bool QueryValue(css::uno::Any& rVal, sal_uInt8 nMemberId = 0) const override; + virtual bool PutValue(const css::uno::Any& rVal, sal_uInt8 nMemberId) override; + + virtual bool GetPresentation(SfxItemPresentation ePres, MapUnit eCoreMetric, + MapUnit ePresMetric, OUString& rText, + const IntlWrapper&) const override; + + virtual SvxRubyItem* Clone(SfxItemPool* pPool = nullptr) const override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/editeng/unoprnms.hxx b/include/editeng/unoprnms.hxx index b42c03bca762..38c6525af39b 100644 --- a/include/editeng/unoprnms.hxx +++ b/include/editeng/unoprnms.hxx @@ -359,6 +359,10 @@ inline constexpr OUString UNO_NAME_EDIT_CHAR_BACKGROUND_COLOR = u"CharBackColor" inline constexpr OUString UNO_NAME_EDIT_CHAR_BACKGROUND_COMPLEX_COLOR = u"CharBackgroundComplexColor"_ustr; inline constexpr OUString UNO_NAME_EDIT_CHAR_BACKGROUND_TRANSPARENT = u"CharBackTransparent"_ustr; +inline constexpr OUString UNO_NAME_EDIT_CHAR_RUBY_TEXT = u"RubyText"_ustr; +inline constexpr OUString UNO_NAME_EDIT_CHAR_RUBY_ADJUST = u"RubyAdjust"_ustr; +inline constexpr OUString UNO_NAME_EDIT_CHAR_RUBY_POSITION = u"RubyPosition"_ustr; + inline constexpr OUString UNO_NAME_BITMAP = u"Bitmap"_ustr; inline constexpr OUString UNO_NAME_LINKDISPLAYNAME = u"LinkDisplayName"_ustr; diff --git a/include/editeng/unotext.hxx b/include/editeng/unotext.hxx index b411867c5b0a..64d53a97f999 100644 --- a/include/editeng/unotext.hxx +++ b/include/editeng/unotext.hxx @@ -133,8 +133,11 @@ struct SfxItemPropertyMapEntry; { UNO_NAME_EDIT_CHAR_POSTURE_COMPLEX, EE_CHAR_ITALIC_CTL, ::cppu::UnoType<css::awt::FontSlant>::get(),0, MID_POSTURE }, \ { UNO_NAME_EDIT_CHAR_WEIGHT_COMPLEX, EE_CHAR_WEIGHT_CTL, cppu::UnoType<float>::get(), 0, MID_WEIGHT }, \ { UNO_NAME_EDIT_CHAR_LOCALE_COMPLEX, EE_CHAR_LANGUAGE_CTL, ::cppu::UnoType<css::lang::Locale>::get(),0, MID_LANG_LOCALE }, \ - { u"CharRelief"_ustr, EE_CHAR_RELIEF, ::cppu::UnoType<sal_Int16>::get(), 0, MID_RELIEF }, \ - { u"CharInteropGrabBag"_ustr, EE_CHAR_GRABBAG, cppu::UnoType<css::uno::Sequence<css::beans::PropertyValue >>::get(), 0, 0} + { u"CharRelief"_ustr, EE_CHAR_RELIEF, ::cppu::UnoType<sal_Int16>::get(), 0, MID_RELIEF }, \ + { u"CharInteropGrabBag"_ustr, EE_CHAR_GRABBAG, cppu::UnoType<css::uno::Sequence<css::beans::PropertyValue >>::get(), 0, 0 }, \ + { UNO_NAME_EDIT_CHAR_RUBY_TEXT, EE_CHAR_RUBY, ::cppu::UnoType<OUString>::get(), 0, MID_RUBY_TEXT }, \ + { UNO_NAME_EDIT_CHAR_RUBY_ADJUST, EE_CHAR_RUBY, ::cppu::UnoType<sal_Int16>::get(), 0, MID_RUBY_ADJUST }, \ + { UNO_NAME_EDIT_CHAR_RUBY_POSITION, EE_CHAR_RUBY, ::cppu::UnoType<sal_Int16>::get(), 0, MID_RUBY_POSITION } #define SVX_UNOEDIT_FONT_PROPERTIES \ diff --git a/include/svl/poolitem.hxx b/include/svl/poolitem.hxx index f63d14494473..259f1ac1cc06 100644 --- a/include/svl/poolitem.hxx +++ b/include/svl/poolitem.hxx @@ -399,6 +399,7 @@ enum class SfxItemType : sal_uInt16 SvxRightMarginItemType, SvxRotateModeItemType, SvxRsidItemType, + SvxRubyItemType, SvxScriptSetItemType, SvxScriptSpaceItemType, SvxSearchItemType, diff --git a/sw/inc/unomid.h b/sw/inc/unomid.h index 9f413509ae1c..7d7cdb4c9af6 100644 --- a/sw/inc/unomid.h +++ b/sw/inc/unomid.h @@ -109,13 +109,6 @@ #define MID_LINE_FOOTNOTE_DIST 6 #define MID_FTN_LINE_STYLE 7 -//SwFormatRuby -#define MID_RUBY_TEXT 0 -#define MID_RUBY_ADJUST 1 -#define MID_RUBY_CHARSTYLE 2 -#define MID_RUBY_ABOVE 3 -#define MID_RUBY_POSITION 4 - //SwTextGridItem #define MID_GRID_COLOR 0 #define MID_GRID_LINES 1 diff --git a/sw/source/core/txtnode/fmtatr2.cxx b/sw/source/core/txtnode/fmtatr2.cxx index 4a28121d2e20..cff6ff2a1235 100644 --- a/sw/source/core/txtnode/fmtatr2.cxx +++ b/sw/source/core/txtnode/fmtatr2.cxx @@ -21,6 +21,7 @@ #include <hintids.hxx> #include <poolfmt.hxx> #include <unomid.h> +#include <editeng/memberids.h> #include <o3tl/any.hxx> #include <svl/macitem.hxx> diff --git a/sw/source/core/unocore/unoobj.cxx b/sw/source/core/unocore/unoobj.cxx index 70a7275e3f06..bdcaebdb7f2a 100644 --- a/sw/source/core/unocore/unoobj.cxx +++ b/sw/source/core/unocore/unoobj.cxx @@ -25,6 +25,7 @@ #include <o3tl/safeint.hxx> #include <osl/endian.h> #include <unotools/collatorwrapper.hxx> +#include <editeng/memberids.h> #include <autostyle_helper.hxx> #include <swtypes.hxx> diff --git a/sw/source/core/unocore/unoport.cxx b/sw/source/core/unocore/unoport.cxx index 1ba9d3416d1c..86ba75fc7280 100644 --- a/sw/source/core/unocore/unoport.cxx +++ b/sw/source/core/unocore/unoport.cxx @@ -36,6 +36,7 @@ #include <ndtxt.hxx> #include <doc.hxx> #include <frmfmt.hxx> +#include <editeng/memberids.h> #include <com/sun/star/beans/PropertyAttribute.hpp> #include <com/sun/star/beans/SetPropertyTolerantFailed.hpp> diff --git a/vcl/qa/cppunit/pdfexport/data/textbox-ruby.fodt b/vcl/qa/cppunit/pdfexport/data/textbox-ruby.fodt new file mode 100644 index 000000000000..eebc992d3d1b --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/textbox-ruby.fodt @@ -0,0 +1,149 @@ +<?xml version='1.0' encoding='UTF-8'?> +<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:c alcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns: meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:meta><meta:creation-date>2025-05-12T03:30:59.696271370</meta:creation-date><dc:date>2025-05-12T03:36:21.248814271</dc:date><meta:editing-duration>PT3M43S</meta:editing-duration><meta:editing-cycles>6</meta:editing-cycles><meta:generator>LibreOfficeDev/25.8.0.0.alpha0$Linux_X86_64 LibreOffice_project/6ffaeff548eaeef29e41adbc3c923d40c0e77a78</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="6" meta:word-count="22" meta:character-count="146" meta:non-whitespace-character-count="130"/></office:meta> + <office:font-face-decls> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> + <style:font-face style:name="Noto Sans1" svg:font-family="'Noto Sans'" style:font-family-generic="system" style:font-pitch="variable"/> + <style:font-face style:name="Noto Serif CJK SC" svg:font-family="'Noto Serif CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/> + </office:font-face-decls> + <office:styles> + <style:default-style style:family="graphic"> + <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" style:flow-with-text="false"/> + <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0in" style:font-independent-line-spacing="false"> + <style:tab-stops/> + </style:paragraph-properties> + <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/> + </style:default-style> + <style:default-style style:family="paragraph"> + <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" loext:hyphenation-keep-line="false" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/> + <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/> + </style:default-style> + <style:default-style style:family="table"> + <style:table-properties table:border-model="collapsing"/> + </style:default-style> + <style:default-style style:family="table-row"> + <style:table-row-properties fo:keep-together="auto"/> + </style:default-style> + <style:style style:name="Standard" style:family="paragraph" style:class="text"/> + <text:outline-style style:name="Outline"> + <text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + </text:outline-style> + <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/> + <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/> + <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/> + </office:styles> + <office:automatic-styles> + <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties/> + </style:style> + <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties/> + </style:style> + <style:style style:name="P3" style:family="paragraph"> + <loext:graphic-properties draw:fill="none" draw:fill-color="#ffffff"/> + </style:style> + <style:style style:name="T1" style:family="text"> + <style:text-properties/> + </style:style> + <style:style style:name="Ru1" style:family="ruby"> + <style:ruby-properties style:ruby-align="left" style:ruby-position="above" loext:ruby-position="above"/> + </style:style> + <style:style style:name="Ru2" style:family="ruby"> + <style:ruby-properties style:ruby-align="center" style:ruby-position="above" loext:ruby-position="above"/> + </style:style> + <style:style style:name="Ru3" style:family="ruby"> + <style:ruby-properties style:ruby-align="right" style:ruby-position="above" loext:ruby-position="above"/> + </style:style> + <style:style style:name="Ru4" style:family="ruby"> + <style:ruby-properties style:ruby-align="center" style:ruby-position="below" loext:ruby-position="below"/> + </style:style> + <style:style style:name="gr1" style:family="graphic"> + <style:graphic-properties draw:stroke="none" svg:stroke-color="#000000" draw:fill="none" draw:fill-color="#ffffff" fo:min-height="2.7638in" loext:decorative="false" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" style:flow-with-text="false"/> + <style:paragraph-properties style:writing-mode="lr-tb"/> + </style:style> + <style:page-layout style:name="pm1"> + <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in" loext:margin-gutter="0in"> + <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + <style:style style:name="dp1" style:family="drawing-page"> + <style:drawing-page-properties draw:background-size="full"/> + </style:style> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/> + </office:master-styles> + <office:body> + <office:text> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + <text:sequence-decl text:display-outline-level="0" text:name="Figure"/> + </text:sequence-decls> + <text:p text:style-name="P1">Prototype test for ruby characters in Edit Engine</text:p> + <text:p text:style-name="P1"/> + <text:p text:style-name="P2"><draw:frame text:anchor-type="paragraph" draw:z-index="0" draw:name="Text Frame 1" draw:style-name="gr1" draw:text-style-name="P3" svg:width="2.0976in" svg:height="2.7642in" svg:x="0.6799in" svg:y="0.3083in"> + <draw:text-box> + <text:p text:style-name="P1">Left-aligned: <text:ruby text:style-name="Ru1"><text:ruby-base>BASE</text:ruby-base><text:ruby-text>top1</text:ruby-text></text:ruby></text:p> + <text:p text:style-name="P1">Centered: <text:ruby text:style-name="Ru2"><text:ruby-base>BASE</text:ruby-base><text:ruby-text>top2</text:ruby-text></text:ruby></text:p> + <text:p text:style-name="P1">Right-aligned: <text:ruby text:style-name="Ru3"><text:ruby-base>BASE</text:ruby-base><text:ruby-text>top3</text:ruby-text></text:ruby></text:p> + <text:p text:style-name="P1">Below: <text:ruby text:style-name="Ru4"><text:ruby-base>BASE</text:ruby-base><text:ruby-text>top4</text:ruby-text></text:ruby></text:p> + <text:p text:style-name="P2"><text:span text:style-name="T1">Line wrapped: other </text:span><text:ruby text:style-name="Ru2"><text:ruby-base><text:span text:style-name="T1">BASE BASE</text:span></text:ruby-base><text:ruby-text>top5</text:ruby-text></text:ruby><text:span text:style-name="T1"> other</text:span></text:p> + </draw:text-box> + </draw:frame></text:p> + </office:text> + </office:body> +</office:document> \ No newline at end of file diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx index 1dc24cb8aeca..f22fb4806b0f 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx @@ -6241,6 +6241,95 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testPDFAttachmentsWithEncryptedFile) CPPUNIT_ASSERT_EQUAL(u"This is a test document."_ustr, xParagraph->getString()); } +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTextBoxRuby) +{ + // This test exercises a work-in-progress Edit Engine ruby feature. + // It is expected that this test will fail and need to be updated + // as the feature is refined. + + saveAsPDF(u"textbox-ruby.fodt"); + + auto pPdfDocument = parsePDFExport(); + CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); + + auto pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0); + CPPUNIT_ASSERT(pPdfPage); + auto pTextPage = pPdfPage->getTextPage(); + CPPUNIT_ASSERT(pTextPage); + + int nPageObjectCount = pPdfPage->getObjectCount(); + + CPPUNIT_ASSERT_EQUAL(17, nPageObjectCount); + + std::vector<OUString> aText; + std::vector<basegfx::B2DRectangle> aRect; + + for (int i = 0; i < nPageObjectCount; ++i) + { + auto pPageObject = pPdfPage->getObject(i); + CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr); + if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text) + { + aText.push_back(pPageObject->getText(pTextPage)); + aRect.push_back(pPageObject->getBounds()); + } + } + + CPPUNIT_ASSERT_EQUAL(size_t(17), aText.size()); + + // Lines from the Writer portion + CPPUNIT_ASSERT_EQUAL(u"Prototype test for ruby characters in Edit Engine"_ustr, + aText.at(0).trim()); + + // Lines from the Edit Engine portion + CPPUNIT_ASSERT_EQUAL(u"Left-aligned:"_ustr, aText.at(1).trim()); + + CPPUNIT_ASSERT_EQUAL(u"top1"_ustr, aText.at(2).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(171.0, aRect.at(2).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(734.0, aRect.at(2).getMaxY(), /*delta*/ 5.0); + CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(3).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(171.0, aRect.at(3).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(719.0, aRect.at(3).getMinY(), /*delta*/ 5.0); + + CPPUNIT_ASSERT_EQUAL(u"Centered:"_ustr, aText.at(4).trim()); + + CPPUNIT_ASSERT_EQUAL(u"top2"_ustr, aText.at(5).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(165.0, aRect.at(5).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(715.0, aRect.at(5).getMaxY(), /*delta*/ 5.0); + CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(6).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(156.0, aRect.at(6).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(701.0, aRect.at(6).getMinY(), /*delta*/ 5.0); + + CPPUNIT_ASSERT_EQUAL(u"Right-aligned:"_ustr, aText.at(7).trim()); + + CPPUNIT_ASSERT_EQUAL(u"top3"_ustr, aText.at(8).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(198.0, aRect.at(8).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(697.0, aRect.at(8).getMaxY(), /*delta*/ 5.0); + CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(9).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(178.0, aRect.at(9).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(682.0, aRect.at(9).getMinY(), /*delta*/ 5.0); + + CPPUNIT_ASSERT_EQUAL(u"Below:"_ustr, aText.at(10).trim()); + + CPPUNIT_ASSERT_EQUAL(u"top4"_ustr, aText.at(11).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(153.0, aRect.at(11).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(667.0, aRect.at(11).getMaxY(), /*delta*/ 5.0); + CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(12).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(144.0, aRect.at(12).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(668.0, aRect.at(12).getMinY(), /*delta*/ 5.0); + + CPPUNIT_ASSERT_EQUAL(u"Line wrapped: other"_ustr, aText.at(13).trim()); + + CPPUNIT_ASSERT_EQUAL(u"top5"_ustr, aText.at(14).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(133.0, aRect.at(14).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(650.0, aRect.at(14).getMaxY(), /*delta*/ 5.0); + CPPUNIT_ASSERT_EQUAL(u"BASE BASE"_ustr, aText.at(15).trim()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(106.0, aRect.at(15).getMinX(), /*delta*/ 5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(636.0, aRect.at(15).getMinY(), /*delta*/ 5.0); + + CPPUNIT_ASSERT_EQUAL(u"other"_ustr, aText.at(16).trim()); +} + } // end anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT();