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 
29 namespace oox::vml {
30 
31 using ::oox::core::ContextHandler2;
32 using ::oox::core::ContextHandler2Helper;
33 using ::oox::core::ContextHandlerRef;
34 
TextPortionContext(ContextHandler2Helper const & rParent,TextBox & rTextBox,TextParagraphModel const & rParagraph,const TextFontModel & rParentFont,sal_Int32 nElement,const AttributeList & rAttribs)35 TextPortionContext::TextPortionContext( ContextHandler2Helper const & rParent,
36         TextBox& rTextBox, TextParagraphModel const & rParagraph, const TextFontModel& rParentFont,
37         sal_Int32 nElement, const AttributeList& rAttribs ) :
38     ContextHandler2( rParent ),
39     mrTextBox( rTextBox ),
40     maParagraph( rParagraph ),
41     maFont( rParentFont ),
42     mnInitialPortions( rTextBox.getPortionCount() )
43 {
44     switch( nElement )
45     {
46         case XML_font:
47             maFont.moName = rAttribs.getXString( XML_face );
48             maFont.moColor = rAttribs.getXString( XML_color );
49             maFont.monSize = rAttribs.getInteger( XML_size );
50         break;
51         case XML_u:
52             OSL_ENSURE( !maFont.monUnderline, "TextPortionContext::TextPortionContext - nested <u> elements" );
53             maFont.monUnderline = (rAttribs.getToken( XML_class, XML_TOKEN_INVALID ) == XML_font4) ? XML_double : XML_single;
54         break;
55         case XML_sub:
56         case XML_sup:
57             OSL_ENSURE( !maFont.monEscapement, "TextPortionContext::TextPortionContext - nested <sub> or <sup> elements" );
58             maFont.monEscapement = nElement;
59         break;
60         case XML_b:
61             OSL_ENSURE( !maFont.mobBold, "TextPortionContext::TextPortionContext - nested <b> elements" );
62             maFont.mobBold = true;
63         break;
64         case XML_i:
65             OSL_ENSURE( !maFont.mobItalic, "TextPortionContext::TextPortionContext - nested <i> elements" );
66             maFont.mobItalic = true;
67         break;
68         case XML_s:
69             OSL_ENSURE( !maFont.mobStrikeout, "TextPortionContext::TextPortionContext - nested <s> elements" );
70             maFont.mobStrikeout = true;
71         break;
72         case OOX_TOKEN(dml, blip):
73             {
74                 OptValue<OUString> oRelId = rAttribs.getString(R_TOKEN(embed));
75                 if (oRelId.has())
76                     mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.get());
77             }
78         break;
79         case VML_TOKEN(imagedata):
80             {
81                 OptValue<OUString> oRelId = rAttribs.getString(R_TOKEN(id));
82                 if (oRelId.has())
83                     mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.get());
84             }
85         break;
86         case XML_span:
87         case W_TOKEN(r):
88         break;
89         default:
90             OSL_ENSURE( false, "TextPortionContext::TextPortionContext - unknown element" );
91     }
92 }
93 
onCreateContext(sal_Int32 nElement,const AttributeList & rAttribs)94 ContextHandlerRef TextPortionContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
95 {
96     OSL_ENSURE( nElement != XML_font, "TextPortionContext::onCreateContext - nested <font> elements" );
97     if (getNamespace(getCurrentElement()) == NMSP_doc)
98         return this;
99     return new TextPortionContext( *this, mrTextBox, maParagraph, maFont, nElement, rAttribs );
100 }
101 
onCharacters(const OUString & rChars)102 void TextPortionContext::onCharacters( const OUString& rChars )
103 {
104     if (getNamespace(getCurrentElement()) == NMSP_doc && getCurrentElement() != W_TOKEN(t))
105         return;
106 
107     switch( getCurrentElement() )
108     {
109         case XML_span:
110             // replace all NBSP characters with SP
111             mrTextBox.appendPortion( maParagraph, maFont, rChars.replace( 0xA0, ' ' ) );
112         break;
113         default:
114             mrTextBox.appendPortion( maParagraph, maFont, rChars );
115     }
116 }
117 
onStartElement(const AttributeList & rAttribs)118 void TextPortionContext::onStartElement(const AttributeList& rAttribs)
119 {
120     switch (getCurrentElement())
121     {
122         case W_TOKEN(b):
123             maFont.mobBold = true;
124         break;
125         case W_TOKEN(sz):
126             maFont.monSize = rAttribs.getInteger( W_TOKEN(val) );
127         break;
128         case W_TOKEN(br):
129             mrTextBox.appendPortion( maParagraph, maFont, "\n" );
130         break;
131         case W_TOKEN(color):
132             maFont.moColor = rAttribs.getString( W_TOKEN(val) );
133         break;
134         case W_TOKEN(spacing):
135             maFont.monSpacing = rAttribs.getInteger(W_TOKEN(val));
136         break;
137         case W_TOKEN(r):
138         case W_TOKEN(rPr):
139         case W_TOKEN(t):
140         break;
141         case W_TOKEN(rFonts):
142             // See https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.runfonts(v=office.14).aspx
143             maFont.moName = rAttribs.getString(W_TOKEN(ascii));
144             maFont.moNameAsian = rAttribs.getString(W_TOKEN(eastAsia));
145             maFont.moNameComplex = rAttribs.getString(W_TOKEN(cs));
146         break;
147         default:
148             SAL_INFO("oox", "unhandled: 0x" << std::hex<< getCurrentElement());
149         break;
150     }
151 }
152 
onEndElement()153 void TextPortionContext::onEndElement()
154 {
155     if (getNamespace(getCurrentElement()) == NMSP_doc && getCurrentElement() != W_TOKEN(t))
156         return;
157 
158     /*  A child element without own child elements may contain a single space
159         character, for example:
160 
161           <div>
162             <font><i>abc</i></font>
163             <font> </font>
164             <font><b>def</b></font>
165           </div>
166 
167         represents the italic text 'abc', an unformatted space character, and
168         the bold text 'def'. Unfortunately, the XML parser skips the space
169         character without issuing a 'characters' event. The class member
170         'mnInitialPortions' contains the number of text portions existing when
171         this context has been constructed. If no text has been added in the
172         meantime, the space character has to be added manually.
173      */
174     if( mrTextBox.getPortionCount() == mnInitialPortions )
175         mrTextBox.appendPortion( maParagraph, maFont, OUString( ' ' ) );
176 }
177 
TextBoxContext(ContextHandler2Helper const & rParent,TextBox & rTextBox,const AttributeList & rAttribs,const GraphicHelper & graphicHelper)178 TextBoxContext::TextBoxContext( ContextHandler2Helper const & rParent, TextBox& rTextBox, const AttributeList& rAttribs,
179     const GraphicHelper& graphicHelper ) :
180     ContextHandler2( rParent ),
181     mrTextBox( rTextBox )
182 {
183     if( rAttribs.getString( XML_insetmode ).get() != "auto" )
184     {
185         OUString inset = rAttribs.getString( XML_inset ).get();
186         OUString value;
187         OUString remainingStr;
188 
189         ConversionHelper::separatePair( value, remainingStr, inset, ',' );
190         rTextBox.borderDistanceLeft = ConversionHelper::decodeMeasureToHmm( graphicHelper,
191             value.isEmpty() ? "0.1in" : value, 0, false, false );
192 
193         inset = remainingStr;
194         ConversionHelper::separatePair( value, remainingStr, inset, ',' );
195         rTextBox.borderDistanceTop = ConversionHelper::decodeMeasureToHmm( graphicHelper,
196             value.isEmpty() ? "0.05in" : value, 0, false, false );
197 
198         inset = remainingStr;
199         ConversionHelper::separatePair( value, remainingStr, inset, ',' );
200         rTextBox.borderDistanceRight = ConversionHelper::decodeMeasureToHmm( graphicHelper,
201             value.isEmpty() ? "0.1in" : value, 0, false, false );
202 
203         inset = remainingStr;
204         ConversionHelper::separatePair( value, remainingStr, inset, ',' );
205         rTextBox.borderDistanceBottom = ConversionHelper::decodeMeasureToHmm( graphicHelper,
206             value.isEmpty() ? "0.05in" : value, 0, false, false );
207 
208         rTextBox.borderDistanceSet = true;
209     }
210 
211     OUString sStyle = rAttribs.getString( XML_style, OUString() );
212     sal_Int32 nIndex = 0;
213     while( nIndex >= 0 )
214     {
215         OUString aName, aValue;
216         if( ConversionHelper::separatePair( aName, aValue, sStyle.getToken( 0, ';', nIndex ), ':' ) )
217         {
218             if( aName == "layout-flow" )      rTextBox.maLayoutFlow = aValue;
219             else if (aName == "mso-fit-shape-to-text")
220                 rTextBox.mrTypeModel.mbAutoHeight = true;
221             else if (aName == "mso-layout-flow-alt")
222                 rTextBox.mrTypeModel.maLayoutFlowAlt = aValue;
223             else if (aName == "mso-next-textbox")
224                 rTextBox.msNextTextbox = aValue;
225             else
226                 SAL_WARN("oox", "unhandled style property: " << aName);
227         }
228     }
229 }
230 
onCreateContext(sal_Int32 nElement,const AttributeList & rAttribs)231 ContextHandlerRef TextBoxContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
232 {
233     switch( getCurrentElement() )
234     {
235         case VML_TOKEN( textbox ):
236             if( nElement == XML_div ) return this;
237             else if (nElement == W_TOKEN(txbxContent)) return this;
238         break;
239         case XML_div:
240             if( nElement == XML_font ) return new TextPortionContext( *this, mrTextBox, maParagraph, TextFontModel(), nElement, rAttribs );
241         break;
242         case W_TOKEN(txbxContent):
243             if (nElement == W_TOKEN(p)) return this;
244         break;
245         case W_TOKEN(p):
246         case W_TOKEN(sdtContent):
247         case W_TOKEN(smartTag):
248             if (nElement == W_TOKEN(r))
249                 return new TextPortionContext( *this, mrTextBox, maParagraph, TextFontModel(), nElement, rAttribs );
250             else
251                 return this;
252         case W_TOKEN(pPr):
253         case W_TOKEN(sdt):
254             return this;
255         default:
256             SAL_INFO("oox", "unhandled 0x" << std::hex << getCurrentElement());
257         break;
258     }
259     return nullptr;
260 }
261 
onStartElement(const AttributeList & rAttribs)262 void TextBoxContext::onStartElement(const AttributeList& rAttribs)
263 {
264     switch (getCurrentElement())
265     {
266         case W_TOKEN(jc):
267             maParagraph.moParaAdjust = rAttribs.getString( W_TOKEN(val) );
268         break;
269         case W_TOKEN(pStyle):
270             maParagraph.moParaStyleName = rAttribs.getString( W_TOKEN(val) );
271         break;
272     }
273 }
274 
onEndElement()275 void TextBoxContext::onEndElement()
276 {
277     if (getCurrentElement() == W_TOKEN(p))
278     {
279         mrTextBox.appendPortion( maParagraph, TextFontModel(), "\n" );
280         maParagraph = TextParagraphModel();
281     }
282 }
283 
284 } // namespace oox::vml
285 
286 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
287