Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/vml/vmltextboxcontext.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <oox/helper/attributelist.hxx>
21
#include <oox/vml/vmlformatting.hxx>
22
#include <oox/vml/vmltextboxcontext.hxx>
23
#include <oox/vml/vmlshape.hxx>
24
#include <oox/token/namespaces.hxx>
25
#include <oox/token/tokens.hxx>
26
#include <osl/diagnose.h>
27
#include <sal/log.hxx>
28
#include <o3tl/string_view.hxx>
29
#include <utility>
30
31
namespace oox::vml {
32
33
using ::oox::core::ContextHandler2;
34
using ::oox::core::ContextHandler2Helper;
35
using ::oox::core::ContextHandlerRef;
36
37
TextPortionContext::TextPortionContext( ContextHandler2Helper const & rParent,
38
        TextBox& rTextBox, TextParagraphModel aParagraph, TextFontModel  aParentFont,
39
        sal_Int32 nElement, const AttributeList& rAttribs ) :
40
37
    ContextHandler2( rParent ),
41
37
    mrTextBox( rTextBox ),
42
37
    maParagraph(std::move( aParagraph )),
43
37
    maFont(std::move( aParentFont )),
44
37
    mnInitialPortions( rTextBox.getPortionCount() )
45
37
{
46
37
    switch( nElement )
47
37
    {
48
2
        case XML_font:
49
2
            maFont.moName = rAttribs.getXString( XML_face );
50
2
            maFont.moColor = rAttribs.getXString( XML_color );
51
2
            maFont.monSize = rAttribs.getInteger( XML_size );
52
2
        break;
53
0
        case XML_u:
54
0
            OSL_ENSURE( !maFont.monUnderline, "TextPortionContext::TextPortionContext - nested <u> elements" );
55
0
            maFont.monUnderline = (rAttribs.getToken( XML_class, XML_TOKEN_INVALID ) == XML_font4) ? XML_double : XML_single;
56
0
        break;
57
0
        case XML_sub:
58
0
        case XML_sup:
59
0
            OSL_ENSURE( !maFont.monEscapement, "TextPortionContext::TextPortionContext - nested <sub> or <sup> elements" );
60
0
            maFont.monEscapement = nElement;
61
0
        break;
62
0
        case XML_b:
63
0
            OSL_ENSURE( !maFont.mobBold.has_value(), "TextPortionContext::TextPortionContext - nested <b> elements" );
64
0
            maFont.mobBold = true;
65
0
        break;
66
0
        case XML_i:
67
0
            OSL_ENSURE( !maFont.mobItalic.has_value(), "TextPortionContext::TextPortionContext - nested <i> elements" );
68
0
            maFont.mobItalic = true;
69
0
        break;
70
0
        case XML_s:
71
0
            OSL_ENSURE( !maFont.mobStrikeout.has_value(), "TextPortionContext::TextPortionContext - nested <s> elements" );
72
0
            maFont.mobStrikeout = true;
73
0
        break;
74
0
        case OOX_TOKEN(dml, blip):
75
0
            {
76
0
                std::optional<OUString> oRelId = rAttribs.getString(R_TOKEN(embed));
77
0
                if (oRelId.has_value())
78
0
                    mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.value());
79
0
            }
80
0
        break;
81
0
        case VML_TOKEN(imagedata):
82
0
            {
83
0
                std::optional<OUString> oRelId = rAttribs.getString(R_TOKEN(id));
84
0
                if (oRelId.has_value())
85
0
                    mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.value());
86
0
            }
87
0
        break;
88
0
        case XML_span:
89
35
        case W_TOKEN(r):
90
35
        break;
91
0
        default:
92
0
            OSL_ENSURE( false, "TextPortionContext::TextPortionContext - unknown element" );
93
37
    }
94
37
}
95
96
ContextHandlerRef TextPortionContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
97
133
{
98
133
    OSL_ENSURE( nElement != XML_font, "TextPortionContext::onCreateContext - nested <font> elements" );
99
133
    if (getNamespace(getCurrentElement()) == NMSP_doc)
100
133
        return this;
101
0
    return new TextPortionContext( *this, mrTextBox, maParagraph, maFont, nElement, rAttribs );
102
133
}
103
104
void TextPortionContext::onCharacters( const OUString& rChars )
105
16
{
106
16
    if (getNamespace(getCurrentElement()) == NMSP_doc && getCurrentElement() != W_TOKEN(t))
107
7
        return;
108
109
9
    switch( getCurrentElement() )
110
9
    {
111
0
        case XML_span:
112
            // replace all NBSP characters with SP
113
0
            mrTextBox.appendPortion( maParagraph, maFont, rChars.replace( 0xA0, ' ' ) );
114
0
        break;
115
9
        default:
116
9
            mrTextBox.appendPortion( maParagraph, maFont, rChars );
117
9
    }
118
9
}
119
120
void TextPortionContext::onStartElement(const AttributeList& rAttribs)
121
170
{
122
170
    switch (getCurrentElement())
123
170
    {
124
14
        case W_TOKEN(b):
125
14
            maFont.mobBold = true;
126
14
        break;
127
14
        case W_TOKEN(sz):
128
14
            maFont.monSize = rAttribs.getInteger( W_TOKEN(val) );
129
14
        break;
130
0
        case W_TOKEN(br):
131
0
            mrTextBox.appendPortion( maParagraph, maFont, u"\n"_ustr );
132
0
        break;
133
14
        case W_TOKEN(color):
134
14
            maFont.moColor = rAttribs.getString( W_TOKEN(val) );
135
14
        break;
136
0
        case W_TOKEN(spacing):
137
0
            maFont.monSpacing = rAttribs.getInteger(W_TOKEN(val));
138
0
        break;
139
35
        case W_TOKEN(r):
140
49
        case W_TOKEN(rPr):
141
56
        case W_TOKEN(t):
142
56
        break;
143
0
        case W_TOKEN(rFonts):
144
            // See https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.runfonts(v=office.14).aspx
145
0
            maFont.moName = rAttribs.getString(W_TOKEN(ascii));
146
0
            maFont.moNameAsian = rAttribs.getString(W_TOKEN(eastAsia));
147
0
            maFont.moNameComplex = rAttribs.getString(W_TOKEN(cs));
148
0
        break;
149
72
        default:
150
72
            SAL_INFO("oox", "unhandled: 0x" << std::hex<< getCurrentElement());
151
72
        break;
152
170
    }
153
170
}
154
155
void TextPortionContext::onEndElement()
156
170
{
157
170
    if (getNamespace(getCurrentElement()) == NMSP_doc && getCurrentElement() != W_TOKEN(t))
158
161
        return;
159
160
    /*  A child element without own child elements may contain a single space
161
        character, for example:
162
163
          <div>
164
            <font><i>abc</i></font>
165
            <font> </font>
166
            <font><b>def</b></font>
167
          </div>
168
169
        represents the italic text 'abc', an unformatted space character, and
170
        the bold text 'def'. Unfortunately, the XML parser skips the space
171
        character without issuing a 'characters' event. The class member
172
        'mnInitialPortions' contains the number of text portions existing when
173
        this context has been constructed. If no text has been added in the
174
        meantime, the space character has to be added manually.
175
     */
176
9
    if( mrTextBox.getPortionCount() == mnInitialPortions )
177
0
        mrTextBox.appendPortion( maParagraph, maFont, OUString( ' ' ) );
178
9
}
179
180
TextBoxContext::TextBoxContext( ContextHandler2Helper const & rParent, TextBox& rTextBox, const AttributeList& rAttribs,
181
    const GraphicHelper& graphicHelper ) :
182
5.79k
    ContextHandler2( rParent ),
183
5.79k
    mrTextBox( rTextBox )
184
5.79k
{
185
5.79k
    if( rAttribs.getStringDefaulted( XML_insetmode ) != "auto" )
186
5.79k
    {
187
5.79k
        OUString inset = rAttribs.getStringDefaulted( XML_inset );
188
5.79k
        std::u16string_view value;
189
5.79k
        std::u16string_view remainingStr;
190
191
5.79k
        ConversionHelper::separatePair( value, remainingStr, inset, ',' );
192
5.79k
        rTextBox.borderDistanceLeft = ConversionHelper::decodeMeasureToHmm( graphicHelper,
193
5.79k
            value.empty() ? u"0.1in" : value, 0, false, false );
194
195
5.79k
        inset = remainingStr;
196
5.79k
        ConversionHelper::separatePair( value, remainingStr, inset, ',' );
197
5.79k
        rTextBox.borderDistanceTop = ConversionHelper::decodeMeasureToHmm( graphicHelper,
198
5.79k
            value.empty() ? u"0.05in" : value, 0, false, false );
199
200
5.79k
        inset = remainingStr;
201
5.79k
        ConversionHelper::separatePair( value, remainingStr, inset, ',' );
202
5.79k
        rTextBox.borderDistanceRight = ConversionHelper::decodeMeasureToHmm( graphicHelper,
203
5.79k
            value.empty() ? u"0.1in" : value, 0, false, false );
204
205
5.79k
        inset = remainingStr;
206
5.79k
        ConversionHelper::separatePair( value, remainingStr, inset, ',' );
207
5.79k
        rTextBox.borderDistanceBottom = ConversionHelper::decodeMeasureToHmm( graphicHelper,
208
5.79k
            value.empty() ? u"0.05in" : value, 0, false, false );
209
210
5.79k
        rTextBox.borderDistanceSet = true;
211
5.79k
    }
212
213
5.79k
    OUString sStyle = rAttribs.getStringDefaulted( XML_style);
214
5.79k
    sal_Int32 nIndex = 0;
215
11.9k
    while( nIndex >= 0 )
216
6.14k
    {
217
6.14k
        std::u16string_view aName, aValue;
218
6.14k
        if( ConversionHelper::separatePair( aName, aValue, o3tl::getToken(sStyle, 0, ';', nIndex ), ':' ) )
219
1.67k
        {
220
1.67k
            if( aName == u"layout-flow" )      rTextBox.maLayoutFlow = aValue;
221
1.46k
            else if (aName == u"mso-fit-shape-to-text")
222
58
                rTextBox.mrTypeModel.mbAutoHeight = true;
223
1.40k
            else if (aName == u"mso-layout-flow-alt")
224
232
                rTextBox.mrTypeModel.maLayoutFlowAlt = aValue;
225
1.17k
            else if (aName == u"mso-next-textbox")
226
311
                rTextBox.msNextTextbox = aValue;
227
860
            else
228
860
                SAL_WARN("oox", "unhandled style property: " << OUString(aName));
229
1.67k
        }
230
6.14k
    }
231
5.79k
}
232
233
ContextHandlerRef TextBoxContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
234
5.56k
{
235
5.56k
    switch( getCurrentElement() )
236
5.56k
    {
237
5.03k
        case VML_TOKEN( textbox ):
238
5.03k
            if( nElement == XML_div ) return this;
239
466
            else if (nElement == W_TOKEN(txbxContent)) return this;
240
2
        break;
241
2
        case XML_div:
242
2
            if( nElement == XML_font ) return new TextPortionContext( *this, mrTextBox, maParagraph, TextFontModel(), nElement, rAttribs );
243
0
        break;
244
464
        case W_TOKEN(txbxContent):
245
464
            if (nElement == W_TOKEN(p)) return this;
246
16
        break;
247
42
        case W_TOKEN(p):
248
42
        case W_TOKEN(sdtContent):
249
42
        case W_TOKEN(smartTag):
250
42
            if (nElement == W_TOKEN(r))
251
35
                return new TextPortionContext( *this, mrTextBox, maParagraph, TextFontModel(), nElement, rAttribs );
252
7
            else
253
7
                return this;
254
14
        case W_TOKEN(pPr):
255
14
        case W_TOKEN(sdt):
256
14
            return this;
257
0
        default:
258
0
            SAL_INFO("oox", "unhandled 0x" << std::hex << getCurrentElement());
259
0
        break;
260
5.56k
    }
261
18
    return nullptr;
262
5.56k
}
263
264
void TextBoxContext::onStartElement(const AttributeList& rAttribs)
265
11.3k
{
266
11.3k
    switch (getCurrentElement())
267
11.3k
    {
268
7
        case W_TOKEN(jc):
269
7
            maParagraph.moParaAdjust = rAttribs.getString( W_TOKEN(val) );
270
7
        break;
271
7
        case W_TOKEN(pStyle):
272
7
            maParagraph.moParaStyleName = rAttribs.getString( W_TOKEN(val) );
273
7
        break;
274
11.3k
    }
275
11.3k
}
276
277
void TextBoxContext::onEndElement()
278
11.3k
{
279
11.3k
    if (getCurrentElement() == W_TOKEN(p))
280
448
    {
281
448
        mrTextBox.appendPortion( maParagraph, TextFontModel(), u"\n"_ustr );
282
448
        maParagraph = TextParagraphModel();
283
448
    }
284
11.3k
}
285
286
} // namespace oox::vml
287
288
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */