LibreOffice Module sc (master)  1
unitconverter.cxx
Go to the documentation of this file.
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 <unitconverter.hxx>
21 
22 #include <com/sun/star/awt/DeviceInfo.hpp>
23 #include <com/sun/star/awt/XDevice.hpp>
24 #include <com/sun/star/awt/XFont.hpp>
25 #include <com/sun/star/beans/XPropertySet.hpp>
26 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
27 #include <com/sun/star/util/Date.hpp>
28 #include <com/sun/star/util/DateTime.hpp>
29 #include <o3tl/unit_conversion.hxx>
30 #include <osl/diagnose.h>
31 #include <oox/core/filterbase.hxx>
33 #include <oox/token/properties.hxx>
34 #include <stylesbuffer.hxx>
35 #include <biffhelper.hxx>
36 
37 namespace com::sun::star::awt { struct FontDescriptor; }
38 
39 namespace oox::xls {
40 
41 using namespace ::com::sun::star;
42 using namespace ::com::sun::star::awt;
43 using namespace ::com::sun::star::uno;
44 
45 namespace {
46 
48 bool lclIsLeapYear( sal_Int32 nYear )
49 {
50  return ((nYear % 4) == 0) && (((nYear % 100) != 0) || ((nYear % 400) == 0));
51 }
52 
53 void lclSkipYearBlock( sal_Int32& ornDays, sal_Int16& ornYear, sal_Int32 nDaysInBlock, sal_Int32 nYearsPerBlock, sal_Int32 nMaxBlocks )
54 {
55  sal_Int32 nBlocks = ::std::min< sal_Int32 >( ornDays / nDaysInBlock, nMaxBlocks );
56  ornYear = static_cast< sal_Int16 >( ornYear + nYearsPerBlock * nBlocks );
57  ornDays -= nBlocks * nDaysInBlock;
58 }
59 
62 sal_Int32 lclGetDays( const util::Date& rDate )
63 {
64  // number of days in all full years before passed date including all leap days
65  sal_Int32 nDays = rDate.Year * 365 + ((rDate.Year + 3) / 4) - ((rDate.Year + 99) / 100) + ((rDate.Year + 399) / 400);
66  OSL_ENSURE( (1 <= rDate.Month) && (rDate.Month <= 12), "lclGetDays - invalid month" );
67  OSL_ENSURE( (1 <= rDate.Day) && (rDate.Day <= 31), "lclGetDays - invalid day" ); // yes, this is weak...
68  if( (1 <= rDate.Month) && (rDate.Month <= 12) )
69  {
70  // number of days at start of month jan feb mar apr may jun jul aug sep oct nov dec
71  static const sal_Int32 spnCumDays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
72  // add number of days in full months before passed date
73  nDays += spnCumDays[ rDate.Month - 1 ];
74  // add number of days from passed date (this adds one day too much)
75  nDays += rDate.Day;
76  /* Remove the one day added too much if there is no leap day before
77  the passed day in the passed year. This means: remove the day, if
78  we are in january or february (leap day not reached if existing),
79  or if the passed year is not a leap year. */
80  if( (rDate.Month < 3) || !lclIsLeapYear( rDate.Year ) )
81  --nDays;
82  }
83  return nDays;
84 }
85 
86 } // namespace
87 
89  WorkbookHelper( rHelper ),
90  mnNullDate( lclGetDays( util::Date( 30, 12, 1899 ) ) )
91 {
92  // initialize constant and default coefficients
93  const DeviceInfo& rDeviceInfo = getBaseFilter().getGraphicHelper().getDeviceInfo();
95  maCoeffs[Unit::Emu] = 1;
96  maCoeffs[Unit::ScreenX] = o3tl::convert((rDeviceInfo.PixelPerMeterX > 0) ? (1000.0 / rDeviceInfo.PixelPerMeterX) : 0.5, o3tl::Length::mm, o3tl::Length::emu);
97  maCoeffs[Unit::ScreenY] = o3tl::convert((rDeviceInfo.PixelPerMeterY > 0) ? (1000.0 / rDeviceInfo.PixelPerMeterY) : 0.5, o3tl::Length::mm, o3tl::Length::emu);
98  maCoeffs[Unit::Digit] = o3tl::convert(2.0, o3tl::Length::mm, o3tl::Length::emu); // default: 1 digit = 2 mm
99  maCoeffs[Unit::Space] = o3tl::convert(1.0, o3tl::Length::mm, o3tl::Length::emu); // default 1 space = 1 mm
100 
101  // error code maps
102  addErrorCode( BIFF_ERR_NULL, "#NULL!" );
103  addErrorCode( BIFF_ERR_DIV0, "#DIV/0!" );
104  addErrorCode( BIFF_ERR_VALUE, "#VALUE!" );
105  addErrorCode( BIFF_ERR_REF, "#REF!" );
106  addErrorCode( BIFF_ERR_NAME, "#NAME?" );
107  addErrorCode( BIFF_ERR_NUM, "#NUM!" );
108  addErrorCode( BIFF_ERR_NA, "#N/A" );
109 }
110 
112 {
113  PropertySet aDocProps( getDocument() );
114  Reference< XDevice > xDevice( aDocProps.getAnyProperty( PROP_ReferenceDevice ), UNO_QUERY );
115  if( !xDevice.is() )
116  return;
117 
118  // get character widths from default font
119  const oox::xls::Font* pDefFont = getStyles().getDefaultFont().get();
120  if( !pDefFont )
121  return;
122 
123  // XDevice expects pixels in font descriptor, but font contains twips
124  const FontDescriptor& aDesc = pDefFont->getFontDescriptor();
125  Reference< XFont > xFont = xDevice->getFont( aDesc );
126  if( !xFont.is() )
127  return;
128 
129  // get maximum width of all digits
130  sal_Int64 nDigitWidth = 0;
131  for( sal_Unicode cChar = '0'; cChar <= '9'; ++cChar )
132  nDigitWidth = ::std::max(nDigitWidth, o3tl::convert(xFont->getCharWidth(cChar),
134  if( nDigitWidth > 0 )
135  maCoeffs[ Unit::Digit ] = nDigitWidth;
136  // get width of space character
137  sal_Int64 nSpaceWidth
138  = o3tl::convert(xFont->getCharWidth(' '), o3tl::Length::twip, o3tl::Length::emu);
139  if( nSpaceWidth > 0 )
140  maCoeffs[ Unit::Space ] = nSpaceWidth;
141 }
142 
143 void UnitConverter::finalizeNullDate( const util::Date& rNullDate )
144 {
145  // convert the nulldate to number of days since 0000-Jan-01
146  mnNullDate = lclGetDays( rNullDate );
147 }
148 
149 // conversion -----------------------------------------------------------------
150 
151 double UnitConverter::scaleValue( double fValue, Unit eFromUnit, Unit eToUnit ) const
152 {
153  return (eFromUnit == eToUnit) ? fValue : (fValue * getCoefficient( eFromUnit ) / getCoefficient( eToUnit ));
154 }
155 
156 double UnitConverter::calcSerialFromDateTime( const util::DateTime& rDateTime ) const
157 {
158  sal_Int32 nDays = lclGetDays( util::Date( rDateTime.Day, rDateTime.Month, rDateTime.Year ) ) - mnNullDate;
159  OSL_ENSURE( nDays >= 0, "UnitConverter::calcDateTimeSerial - invalid date" );
160  OSL_ENSURE( (rDateTime.Hours <= 23) && (rDateTime.Minutes <= 59) && (rDateTime.Seconds <= 59), "UnitConverter::calcDateTimeSerial - invalid time" );
161  return nDays + rDateTime.Hours / 24.0 + rDateTime.Minutes / 1440.0 + rDateTime.Seconds / 86400.0;
162 }
163 
164 util::DateTime UnitConverter::calcDateTimeFromSerial( double fSerial ) const
165 {
166  util::DateTime aDateTime( 0, 0, 0, 0, 1, 1, 0, false );
167  double fDays = 0.0;
168  double fTime = modf( fSerial, &fDays );
169 
170  // calculate date from number of days with O(1) complexity
171  sal_Int32 nDays = getLimitedValue< sal_Int32, double >( fDays + mnNullDate, 0, 3652424 );
172  // skip year 0, assumed to be a leap year. By starting at year 1, leap years can be handled easily
173  if( nDays >= 366 ) { ++aDateTime.Year; nDays -= 366; }
174  // skip full blocks of 400, 100, 4 years, and remaining full years
175  lclSkipYearBlock( nDays, aDateTime.Year, 400 * 365 + 97, 400, 24 );
176  lclSkipYearBlock( nDays, aDateTime.Year, 100 * 365 + 24, 100, 3 );
177  lclSkipYearBlock( nDays, aDateTime.Year, 4 * 365 + 1, 4, 24 );
178  lclSkipYearBlock( nDays, aDateTime.Year, 365, 1, 3 );
179  // skip full months of current year
180  static const sal_Int32 spnDaysInMonth[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
181  if( (nDays >= 59) && !lclIsLeapYear( aDateTime.Year ) ) ++nDays;
182  const sal_Int32* pnDaysInMonth = spnDaysInMonth;
183  while( *pnDaysInMonth >= nDays ) { ++aDateTime.Month; nDays -= *pnDaysInMonth; ++pnDaysInMonth; }
184  aDateTime.Day = static_cast< sal_uInt16 >( nDays + 1 );
185 
186  // calculate time from fractional part of serial
187  sal_Int32 nTime = getLimitedValue< sal_Int32, double >( fTime * 86400, 0, 86399 );
188  aDateTime.Seconds = static_cast< sal_uInt16 >( nTime % 60 );
189  nTime /= 60;
190  aDateTime.Minutes = static_cast< sal_uInt16 >( nTime % 60 );
191  aDateTime.Hours = static_cast< sal_uInt16 >( nTime / 60 );
192 
193  return aDateTime;
194 }
195 
196 sal_uInt8 UnitConverter::calcBiffErrorCode( const OUString& rErrorCode ) const
197 {
198  auto aIt = maOoxErrCodes.find( rErrorCode );
199  return (aIt == maOoxErrCodes.end()) ? BIFF_ERR_NA : aIt->second;
200 }
201 
202 OUString UnitConverter::calcErrorString( sal_uInt8 nErrorCode ) const
203 {
204  auto iFail( maOoxErrCodes.cend());
205  for (auto aIt( maOoxErrCodes.cbegin()); aIt != maOoxErrCodes.cend(); ++aIt)
206  {
207  if (aIt->second == nErrorCode)
208  return aIt->first;
209  if (aIt->second == BIFF_ERR_NA)
210  iFail = aIt;
211  }
212  assert(iFail != maOoxErrCodes.end()); // BIFF_ERR_NA really should be in the map...
213  return iFail != maOoxErrCodes.end() ? iFail->first : OUString();
214 }
215 
216 void UnitConverter::addErrorCode( sal_uInt8 nErrorCode, const OUString& rErrorCode )
217 {
218  maOoxErrCodes[ rErrorCode ] = nErrorCode;
219 }
220 
221 double UnitConverter::getCoefficient( Unit eUnit ) const
222 {
223  return maCoeffs[ eUnit ];
224 }
225 
226 } // namespace oox::xls
227 
228 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
exports com.sun.star.lib. util
Helper class to provide access to global workbook data.
double scaleValue(double fValue, Unit eFromUnit, Unit eToUnit) const
Converts the passed value between the passed units.
const sal_uInt8 BIFF_ERR_VALUE
Definition: biffhelper.hxx:562
const sal_uInt8 BIFF_ERR_NA
Definition: biffhelper.hxx:566
const css::awt::DeviceInfo & getDeviceInfo() const
const sal_uInt8 BIFF_ERR_REF
Definition: biffhelper.hxx:563
constexpr Point convert(const Point &rPoint, o3tl::Length eFrom, o3tl::Length eTo)
css::uno::Any getAnyProperty(sal_Int32 nPropId) const
const sal_uInt8 BIFF_ERR_NAME
Definition: biffhelper.hxx:564
StylesBuffer & getStyles() const
Returns all cell formatting objects read from the styles substream.
GraphicHelper & getGraphicHelper() const
double calcSerialFromDateTime(const css::util::DateTime &rDateTime) const
Returns the serial value of the passed datetime, based on current nulldate.
sal_uInt16 sal_Unicode
css::util::DateTime calcDateTimeFromSerial(double fSerial) const
Returns the datetime of the passed serial value, based on current nulldate.
::oox::core::FilterBase & getBaseFilter() const
Returns the base filter object (base class of all filters).
std::map< OUString, sal_uInt8 > maOoxErrCodes
Coefficients for unit conversion.
OUString calcErrorString(sal_uInt8 nErrorCode) const
Returns an error string from the passed BIFF error code.
void finalizeNullDate(const css::util::Date &rNullDate)
Updates internal nulldate for date/serial conversion.
Unit
Units supported by the UnitConverter class.
const css::uno::Reference< css::sheet::XSpreadsheetDocument > & getDocument() const
Returns a reference to the source/target spreadsheet document model.
void addErrorCode(sal_uInt8 nErrorCode, const OUString &rErrorCode)
Adds an error code to the internal maps.
const sal_uInt8 BIFF_ERR_DIV0
Definition: biffhelper.hxx:561
Twips (1/20 point).
const sal_uInt8 BIFF_ERR_NUM
Definition: biffhelper.hxx:565
Digit width of document default font.
const css::awt::FontDescriptor & getFontDescriptor() const
Returns an API font descriptor with own font information.
void finalizeImport()
Final processing after import of all style settings.
unsigned char sal_uInt8
Horizontal screen pixels.
English Metric Unit (1/360,000 cm).
UnitConverter(const WorkbookHelper &rHelper)
const sal_uInt8 BIFF_ERR_NULL
Common object settings.
Definition: biffhelper.hxx:560
sal_uInt8 calcBiffErrorCode(const OUString &rErrorCode) const
Returns a BIFF error code from the passed error string.
o3tl::enumarray< Unit, double > maCoeffs
sal_Int32 mnNullDate
Maps error code strings to BIFF error constants.
FontRef getDefaultFont() const
Returns the default application font (used in the "Normal" cell style).
double getCoefficient(Unit eUnit) const
Returns the conversion coefficient for the passed unit.
Vertical screen pixels.