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  return;
123 
124  // get character widths from default font
125  const oox::xls::Font* pDefFont = getStyles().getDefaultFont().get();
126  if( !pDefFont )
127  return;
128 
129  // XDevice expects pixels in font descriptor, but font contains twips
130  const FontDescriptor& aDesc = pDefFont->getFontDescriptor();
131  Reference< XFont > xFont = xDevice->getFont( aDesc );
132  if( !xFont.is() )
133  return;
134 
135  // get maximum width of all digits
136  sal_Int32 nDigitWidth = 0;
137  for( sal_Unicode cChar = '0'; cChar <= '9'; ++cChar )
138  nDigitWidth = ::std::max( nDigitWidth, scaleToMm100( xFont->getCharWidth( cChar ), Unit::Twip ) );
139  if( nDigitWidth > 0 )
140  maCoeffs[ Unit::Digit ] = nDigitWidth;
141  // get width of space character
142  sal_Int32 nSpaceWidth = scaleToMm100( xFont->getCharWidth( ' ' ), Unit::Twip );
143  if( nSpaceWidth > 0 )
144  maCoeffs[ Unit::Space ] = nSpaceWidth;
145 }
146 
147 void UnitConverter::finalizeNullDate( const util::Date& rNullDate )
148 {
149  // convert the nulldate to number of days since 0000-Jan-01
150  mnNullDate = lclGetDays( rNullDate );
151 }
152 
153 // conversion -----------------------------------------------------------------
154 
155 double UnitConverter::scaleValue( double fValue, Unit eFromUnit, Unit eToUnit ) const
156 {
157  return (eFromUnit == eToUnit) ? fValue : (fValue * getCoefficient( eFromUnit ) / getCoefficient( eToUnit ));
158 }
159 
160 sal_Int32 UnitConverter::scaleToMm100( double fValue, Unit eUnit ) const
161 {
162  return static_cast< sal_Int32 >( fValue * getCoefficient( eUnit ) + 0.5 );
163 }
164 
165 double UnitConverter::scaleFromMm100( sal_Int32 nMm100, Unit eUnit ) const
166 {
167  return static_cast< double >( nMm100 ) / getCoefficient( eUnit );
168 }
169 
170 double UnitConverter::calcSerialFromDateTime( const util::DateTime& rDateTime ) const
171 {
172  sal_Int32 nDays = lclGetDays( util::Date( rDateTime.Day, rDateTime.Month, rDateTime.Year ) ) - mnNullDate;
173  OSL_ENSURE( nDays >= 0, "UnitConverter::calcDateTimeSerial - invalid date" );
174  OSL_ENSURE( (rDateTime.Hours <= 23) && (rDateTime.Minutes <= 59) && (rDateTime.Seconds <= 59), "UnitConverter::calcDateTimeSerial - invalid time" );
175  return nDays + rDateTime.Hours / 24.0 + rDateTime.Minutes / 1440.0 + rDateTime.Seconds / 86400.0;
176 }
177 
178 util::DateTime UnitConverter::calcDateTimeFromSerial( double fSerial ) const
179 {
180  util::DateTime aDateTime( 0, 0, 0, 0, 1, 1, 0, false );
181  double fDays = 0.0;
182  double fTime = modf( fSerial, &fDays );
183 
184  // calculate date from number of days with O(1) complexity
185  sal_Int32 nDays = getLimitedValue< sal_Int32, double >( fDays + mnNullDate, 0, 3652424 );
186  // skip year 0, assumed to be a leap year. By starting at year 1, leap years can be handled easily
187  if( nDays >= 366 ) { ++aDateTime.Year; nDays -= 366; }
188  // skip full blocks of 400, 100, 4 years, and remaining full years
189  lclSkipYearBlock( nDays, aDateTime.Year, 400 * 365 + 97, 400, 24 );
190  lclSkipYearBlock( nDays, aDateTime.Year, 100 * 365 + 24, 100, 3 );
191  lclSkipYearBlock( nDays, aDateTime.Year, 4 * 365 + 1, 4, 24 );
192  lclSkipYearBlock( nDays, aDateTime.Year, 365, 1, 3 );
193  // skip full months of current year
194  static const sal_Int32 spnDaysInMonth[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
195  if( (nDays >= 59) && !lclIsLeapYear( aDateTime.Year ) ) ++nDays;
196  const sal_Int32* pnDaysInMonth = spnDaysInMonth;
197  while( *pnDaysInMonth >= nDays ) { ++aDateTime.Month; nDays -= *pnDaysInMonth; ++pnDaysInMonth; }
198  aDateTime.Day = static_cast< sal_uInt16 >( nDays + 1 );
199 
200  // calculate time from fractional part of serial
201  sal_Int32 nTime = getLimitedValue< sal_Int32, double >( fTime * 86400, 0, 86399 );
202  aDateTime.Seconds = static_cast< sal_uInt16 >( nTime % 60 );
203  nTime /= 60;
204  aDateTime.Minutes = static_cast< sal_uInt16 >( nTime % 60 );
205  aDateTime.Hours = static_cast< sal_uInt16 >( nTime / 60 );
206 
207  return aDateTime;
208 }
209 
210 sal_uInt8 UnitConverter::calcBiffErrorCode( const OUString& rErrorCode ) const
211 {
212  auto aIt = maOoxErrCodes.find( rErrorCode );
213  return (aIt == maOoxErrCodes.end()) ? BIFF_ERR_NA : aIt->second;
214 }
215 
216 OUString UnitConverter::calcErrorString( sal_uInt8 nErrorCode ) const
217 {
218  auto iFail( maOoxErrCodes.cend());
219  for (auto aIt( maOoxErrCodes.cbegin()); aIt != maOoxErrCodes.cend(); ++aIt)
220  {
221  if (aIt->second == nErrorCode)
222  return aIt->first;
223  if (aIt->second == BIFF_ERR_NA)
224  iFail = aIt;
225  }
226  assert(iFail != maOoxErrCodes.end()); // BIFF_ERR_NA really should be in the map...
227  return iFail != maOoxErrCodes.end() ? iFail->first : OUString();
228 }
229 
230 void UnitConverter::addErrorCode( sal_uInt8 nErrorCode, const OUString& rErrorCode )
231 {
232  maOoxErrCodes[ rErrorCode ] = nErrorCode;
233 }
234 
235 double UnitConverter::getCoefficient( Unit eUnit ) const
236 {
237  return maCoeffs[ eUnit ];
238 }
239 
240 } // namespace oox::xls
241 
242 /* 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.
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: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.