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 <osl/diagnose.h>
30 #include <oox/core/filterbase.hxx>
32 #include <oox/token/properties.hxx>
33 #include <stylesbuffer.hxx>
34 #include <biffhelper.hxx>
35 
36 namespace com::sun::star::awt { struct FontDescriptor; }
37 
38 namespace oox::xls {
39 
40 using namespace ::com::sun::star;
41 using namespace ::com::sun::star::awt;
42 using namespace ::com::sun::star::uno;
43 
44 namespace {
45 
46 const double MM100_PER_INCH = 2540.0;
47 const double MM100_PER_POINT = MM100_PER_INCH / 72.0;
48 const double MM100_PER_TWIP = MM100_PER_POINT / 20.0;
49 const double MM100_PER_EMU = 1.0 / 360.0;
50 
52 bool lclIsLeapYear( sal_Int32 nYear )
53 {
54  return ((nYear % 4) == 0) && (((nYear % 100) != 0) || ((nYear % 400) == 0));
55 }
56 
57 void lclSkipYearBlock( sal_Int32& ornDays, sal_Int16& ornYear, sal_Int32 nDaysInBlock, sal_Int32 nYearsPerBlock, sal_Int32 nMaxBlocks )
58 {
59  sal_Int32 nBlocks = ::std::min< sal_Int32 >( ornDays / nDaysInBlock, nMaxBlocks );
60  ornYear = static_cast< sal_Int16 >( ornYear + nYearsPerBlock * nBlocks );
61  ornDays -= nBlocks * nDaysInBlock;
62 }
63 
66 sal_Int32 lclGetDays( const util::Date& rDate )
67 {
68  // number of days in all full years before passed date including all leap days
69  sal_Int32 nDays = rDate.Year * 365 + ((rDate.Year + 3) / 4) - ((rDate.Year + 99) / 100) + ((rDate.Year + 399) / 400);
70  OSL_ENSURE( (1 <= rDate.Month) && (rDate.Month <= 12), "lclGetDays - invalid month" );
71  OSL_ENSURE( (1 <= rDate.Day) && (rDate.Day <= 31), "lclGetDays - invalid day" ); // yes, this is weak...
72  if( (1 <= rDate.Month) && (rDate.Month <= 12) )
73  {
74  // number of days at start of month jan feb mar apr may jun jul aug sep oct nov dec
75  static const sal_Int32 spnCumDays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
76  // add number of days in full months before passed date
77  nDays += spnCumDays[ rDate.Month - 1 ];
78  // add number of days from passed date (this adds one day too much)
79  nDays += rDate.Day;
80  /* Remove the one day added too much if there is no leap day before
81  the passed day in the passed year. This means: remove the day, if
82  we are in january or february (leap day not reached if existing),
83  or if the passed year is not a leap year. */
84  if( (rDate.Month < 3) || !lclIsLeapYear( rDate.Year ) )
85  --nDays;
86  }
87  return nDays;
88 }
89 
90 } // namespace
91 
93  WorkbookHelper( rHelper ),
94  mnNullDate( lclGetDays( util::Date( 30, 12, 1899 ) ) )
95 {
96  // initialize constant and default coefficients
97  const DeviceInfo& rDeviceInfo = getBaseFilter().getGraphicHelper().getDeviceInfo();
98  maCoeffs[ Unit::Inch ] = MM100_PER_INCH;
99  maCoeffs[ Unit::Point ] = MM100_PER_POINT;
100  maCoeffs[ Unit::Twip ] = MM100_PER_TWIP;
101  maCoeffs[ Unit::Emu ] = MM100_PER_EMU;
102  maCoeffs[ Unit::ScreenX ] = (rDeviceInfo.PixelPerMeterX > 0) ? (100000.0 / rDeviceInfo.PixelPerMeterX) : 50.0;
103  maCoeffs[ Unit::ScreenY ] = (rDeviceInfo.PixelPerMeterY > 0) ? (100000.0 / rDeviceInfo.PixelPerMeterY) : 50.0;
104  maCoeffs[ Unit::Digit ] = 200.0; // default: 1 digit = 2 mm
105  maCoeffs[ Unit::Space ] = 100.0; // default 1 space = 1 mm
106 
107  // error code maps
108  addErrorCode( BIFF_ERR_NULL, "#NULL!" );
109  addErrorCode( BIFF_ERR_DIV0, "#DIV/0!" );
110  addErrorCode( BIFF_ERR_VALUE, "#VALUE!" );
111  addErrorCode( BIFF_ERR_REF, "#REF!" );
112  addErrorCode( BIFF_ERR_NAME, "#NAME?" );
113  addErrorCode( BIFF_ERR_NUM, "#NUM!" );
114  addErrorCode( BIFF_ERR_NA, "#N/A" );
115 }
116 
118 {
119  PropertySet aDocProps( getDocument() );
120  Reference< XDevice > xDevice( aDocProps.getAnyProperty( PROP_ReferenceDevice ), UNO_QUERY );
121  if( xDevice.is() )
122  {
123  // get character widths from default font
124  if( const oox::xls::Font* pDefFont = getStyles().getDefaultFont().get() )
125  {
126  // XDevice expects pixels in font descriptor, but font contains twips
127  const FontDescriptor& aDesc = pDefFont->getFontDescriptor();
128  Reference< XFont > xFont = xDevice->getFont( aDesc );
129  if( xFont.is() )
130  {
131  // get maximum width of all digits
132  sal_Int32 nDigitWidth = 0;
133  for( sal_Unicode cChar = '0'; cChar <= '9'; ++cChar )
134  nDigitWidth = ::std::max( nDigitWidth, scaleToMm100( xFont->getCharWidth( cChar ), Unit::Twip ) );
135  if( nDigitWidth > 0 )
136  maCoeffs[ Unit::Digit ] = nDigitWidth;
137  // get width of space character
138  sal_Int32 nSpaceWidth = scaleToMm100( xFont->getCharWidth( ' ' ), Unit::Twip );
139  if( nSpaceWidth > 0 )
140  maCoeffs[ Unit::Space ] = nSpaceWidth;
141  }
142  }
143  }
144 }
145 
146 void UnitConverter::finalizeNullDate( const util::Date& rNullDate )
147 {
148  // convert the nulldate to number of days since 0000-Jan-01
149  mnNullDate = lclGetDays( rNullDate );
150 }
151 
152 // conversion -----------------------------------------------------------------
153 
154 double UnitConverter::scaleValue( double fValue, Unit eFromUnit, Unit eToUnit ) const
155 {
156  return (eFromUnit == eToUnit) ? fValue : (fValue * getCoefficient( eFromUnit ) / getCoefficient( eToUnit ));
157 }
158 
159 sal_Int32 UnitConverter::scaleToMm100( double fValue, Unit eUnit ) const
160 {
161  return static_cast< sal_Int32 >( fValue * getCoefficient( eUnit ) + 0.5 );
162 }
163 
164 double UnitConverter::scaleFromMm100( sal_Int32 nMm100, Unit eUnit ) const
165 {
166  return static_cast< double >( nMm100 ) / getCoefficient( eUnit );
167 }
168 
169 double UnitConverter::calcSerialFromDateTime( const util::DateTime& rDateTime ) const
170 {
171  sal_Int32 nDays = lclGetDays( util::Date( rDateTime.Day, rDateTime.Month, rDateTime.Year ) ) - mnNullDate;
172  OSL_ENSURE( nDays >= 0, "UnitConverter::calcDateTimeSerial - invalid date" );
173  OSL_ENSURE( (rDateTime.Hours <= 23) && (rDateTime.Minutes <= 59) && (rDateTime.Seconds <= 59), "UnitConverter::calcDateTimeSerial - invalid time" );
174  return nDays + rDateTime.Hours / 24.0 + rDateTime.Minutes / 1440.0 + rDateTime.Seconds / 86400.0;
175 }
176 
177 util::DateTime UnitConverter::calcDateTimeFromSerial( double fSerial ) const
178 {
179  util::DateTime aDateTime( 0, 0, 0, 0, 1, 1, 0, false );
180  double fDays = 0.0;
181  double fTime = modf( fSerial, &fDays );
182 
183  // calculate date from number of days with O(1) complexity
184  sal_Int32 nDays = getLimitedValue< sal_Int32, double >( fDays + mnNullDate, 0, 3652424 );
185  // skip year 0, assumed to be a leap year. By starting at year 1, leap years can be handled easily
186  if( nDays >= 366 ) { ++aDateTime.Year; nDays -= 366; }
187  // skip full blocks of 400, 100, 4 years, and remaining full years
188  lclSkipYearBlock( nDays, aDateTime.Year, 400 * 365 + 97, 400, 24 );
189  lclSkipYearBlock( nDays, aDateTime.Year, 100 * 365 + 24, 100, 3 );
190  lclSkipYearBlock( nDays, aDateTime.Year, 4 * 365 + 1, 4, 24 );
191  lclSkipYearBlock( nDays, aDateTime.Year, 365, 1, 3 );
192  // skip full months of current year
193  static const sal_Int32 spnDaysInMonth[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
194  if( (nDays >= 59) && !lclIsLeapYear( aDateTime.Year ) ) ++nDays;
195  const sal_Int32* pnDaysInMonth = spnDaysInMonth;
196  while( *pnDaysInMonth >= nDays ) { ++aDateTime.Month; nDays -= *pnDaysInMonth; ++pnDaysInMonth; }
197  aDateTime.Day = static_cast< sal_uInt16 >( nDays + 1 );
198 
199  // calculate time from fractional part of serial
200  sal_Int32 nTime = getLimitedValue< sal_Int32, double >( fTime * 86400, 0, 86399 );
201  aDateTime.Seconds = static_cast< sal_uInt16 >( nTime % 60 );
202  nTime /= 60;
203  aDateTime.Minutes = static_cast< sal_uInt16 >( nTime % 60 );
204  aDateTime.Hours = static_cast< sal_uInt16 >( nTime / 60 );
205 
206  return aDateTime;
207 }
208 
209 sal_uInt8 UnitConverter::calcBiffErrorCode( const OUString& rErrorCode ) const
210 {
211  auto aIt = maOoxErrCodes.find( rErrorCode );
212  return (aIt == maOoxErrCodes.end()) ? BIFF_ERR_NA : aIt->second;
213 }
214 
215 OUString UnitConverter::calcErrorString( sal_uInt8 nErrorCode ) const
216 {
217  auto iFail( maOoxErrCodes.cend());
218  for (auto aIt( maOoxErrCodes.cbegin()); aIt != maOoxErrCodes.cend(); ++aIt)
219  {
220  if (aIt->second == nErrorCode)
221  return aIt->first;
222  if (aIt->second == BIFF_ERR_NA)
223  iFail = aIt;
224  }
225  assert(iFail != maOoxErrCodes.end()); // BIFF_ERR_NA really should be in the map...
226  return iFail != maOoxErrCodes.end() ? iFail->first : OUString();
227 }
228 
229 void UnitConverter::addErrorCode( sal_uInt8 nErrorCode, const OUString& rErrorCode )
230 {
231  maOoxErrCodes[ rErrorCode ] = nErrorCode;
232 }
233 
234 double UnitConverter::getCoefficient( Unit eUnit ) const
235 {
236  return maCoeffs[ eUnit ];
237 }
238 
239 } // namespace oox::xls
240 
241 /* 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:563
const sal_uInt8 BIFF_ERR_NA
Definition: biffhelper.hxx:567
const css::awt::DeviceInfo & getDeviceInfo() const
const sal_uInt8 BIFF_ERR_REF
Definition: biffhelper.hxx:564
css::uno::Any getAnyProperty(sal_Int32 nPropId) const
const sal_uInt8 BIFF_ERR_NAME
Definition: biffhelper.hxx:565
StylesBuffer & getStyles() const
Returns all cell formatting objects read from the styles substream.
sal_Int32 scaleToMm100(double fValue, Unit eUnit) const
Converts the passed value to 1/100 millimeters.
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.
const BorderLinePrimitive2D *pCandidateB assert(pCandidateA)
::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:562
Twips (1/20 point).
const sal_uInt8 BIFF_ERR_NUM
Definition: biffhelper.hxx:566
Digit width of document default font.
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:561
double scaleFromMm100(sal_Int32 nMm100, Unit eUnit) const
Converts the passed value from 1/100 millimeters to the passed unit.
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.