summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2021-02-13 13:55:22 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2021-02-14 12:50:01 +0100
commitcfff893b9c82843a90aac4ecdb3a3936721b74a0 (patch)
tree2859340e329ea6dbffe5ae9c7eba0f67a88c57af /include
parent20305894243e24eb383ab9feefebf4a0e9f2644f (diff)
Move unit conversion code to o3tl, and unify on that in more places
This also allows to easily add more units, both of length and for other unit categories. The conversion for "Line" unit (312 twip) is questionable. Corresponding entries in aImplFactor in vcl/source/control/field.cxx were inconsistent (45/11 in; 10/13 pc; 156/10 pt). They were added without explanation in commit c85db626029fd8a5e0dfcb312937279df32339a0. I haven't found a spec of the unit (https://en.wikipedia.org/wiki/Line_(unit) is not specific). I used the definition based on "by pt", "by mm/100", "by char" (they all were consistent); "by pc" seems inverted; "by twip" was half as much. This accepted conversion makes unit test for tdf#79236 pass. Change-Id: Iae5a21d915fa8e934a1f47f8ba9f6df03b79a9fd Reviewed-on: https://gerrit.libreoffice.org/c/core/+/110839 Tested-by: Mike Kaganski <mike.kaganski@collabora.com> Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'include')
-rw-r--r--include/o3tl/unit_conversion.hxx231
-rw-r--r--include/oox/drawingml/drawingmltypes.hxx16
-rw-r--r--include/svtools/unitconv.hxx2
-rw-r--r--include/tools/UnitConversion.hxx136
4 files changed, 334 insertions, 51 deletions
diff --git a/include/o3tl/unit_conversion.hxx b/include/o3tl/unit_conversion.hxx
new file mode 100644
index 000000000000..17dd5ae12293
--- /dev/null
+++ b/include/o3tl/unit_conversion.hxx
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <o3tl/safeint.hxx>
+#include <sal/macros.h>
+#include <sal/types.h>
+
+#include <array>
+#include <cassert>
+#include <utility>
+#include <type_traits>
+
+namespace o3tl
+{
+// Length units
+enum class Length
+{
+ mm100 = 0, // 1/100th mm
+ mm10, // 1/10 mm, corresponds to MapUnit::Map10thMM
+ mm, // millimeter
+ cm, // centimeter
+ m, // meter
+ km, // kilometer
+ emu, // English Metric Unit: 1/360000 cm, 1/914400 in
+ twip, // "Twentieth of a point" aka "dxa": 1/20 pt
+ pt, // Point: 1/72 in
+ pc, // Pica: 1/6 in, corresponds to FieldUnit::PICA and MeasureUnit::PICA
+ in1000, // 1/1000 in, corresponds to MapUnit::Map1000thInch
+ in100, // 1/100 in, corresponds to MapUnit::Map100thInch
+ in10, // 1/10 in, corresponds to MapUnit::Map10thInch
+ in, // inch
+ ft, // foot
+ mi, // mile
+ master, // PPT Master Unit: 1/576 in
+ px, // "pixel" unit: 15 twip (96 ppi), corresponds to MeasureUnit::PIXEL
+ ch, // "char" unit: 210 twip (14 px), corresponds to FieldUnit::CHAR
+ line, // "line" unit: 312 twip, corresponds to FieldUnit::LINE
+ count, // <== add new units above this last entry
+ invalid = -1
+};
+
+// If other categories of units would be needed (like time), a separate scoped enum
+// should be created, respective conversion array prepared in detail namespace, and
+// respective md(NewUnit, NewUnit) overload introduced, which would allow using
+// o3tl::convert() and o3tl::convertSanitize() with the new category in a type-safe
+// way, without mixing unrelated units.
+
+namespace detail
+{
+// Common utilities
+
+// A special function to avoid compiler warning comparing signed and unsigned values
+template <typename I> constexpr bool isBetween(I n, sal_Int64 min, sal_Int64 max)
+{
+ assert(max > 0 && min < 0);
+ if constexpr (std::is_signed_v<I>)
+ return n >= min && n <= max;
+ else
+ return n <= sal_uInt64(max);
+}
+
+// Ensure correct rounding for both positive and negative integers
+template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
+constexpr sal_Int64 MulDiv(I n, sal_Int64 m, sal_Int64 d)
+{
+ assert(m > 0 && d > 0);
+ assert(isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m));
+ return (n >= 0 ? (n * m + d / 2) : (n * m - d / 2)) / d;
+}
+template <typename F, std::enable_if_t<std::is_floating_point_v<F>, int> = 0>
+constexpr double MulDiv(F f, sal_Int64 m, sal_Int64 d)
+{
+ assert(m > 0 && d > 0);
+ return f * (double(m) / d);
+}
+
+template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
+constexpr sal_Int64 MulDiv(I n, sal_Int64 m, sal_Int64 d, bool& bOverflow, sal_Int64 nDefault)
+{
+ if (!isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m))
+ {
+ bOverflow = true;
+ return nDefault;
+ }
+ bOverflow = false;
+ return MulDiv(n, m, d);
+}
+
+template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
+constexpr sal_Int64 MulDivSaturate(I n, sal_Int64 m, sal_Int64 d)
+{
+ if (!isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m))
+ {
+ if (m > d && !isBetween(n, SAL_MIN_INT64 / m * d + d / 2, SAL_MAX_INT64 / m * d - d / 2))
+ return n > 0 ? SAL_MAX_INT64 : SAL_MIN_INT64; // saturate
+ return (n >= 0 ? n + d / 2 : n - d / 2) / d * m; // divide before multiplication
+ }
+ return MulDiv(n, m, d);
+}
+
+// Greatest common divisor
+constexpr int gcd(sal_Int64 a, sal_Int64 b) { return b == 0 ? a : gcd(b, a % b); }
+
+// Packs integral multiplier and divisor for conversion from one unit to another
+struct m_and_d
+{
+ sal_Int64 m; // multiplier
+ sal_Int64 d; // divisor
+ constexpr m_and_d(sal_Int64 _m, sal_Int64 _d)
+ : m(_m / gcd(_m, _d)) // make sure to use smallest quotients here because
+ , d(_d / gcd(_m, _d)) // they will be multiplied when building final table
+ {
+ assert(_m > 0 && _d > 0);
+ }
+};
+
+// Resulting static array N x N of all quotients to convert between all units. The
+// quotients are minimal to allow largest range of converted numbers without overflow.
+// Maybe o3tl::enumarray could be used here, but it's not constexpr yet.
+template <int N> constexpr auto prepareMDArray(const m_and_d (&mdBase)[N])
+{
+ std::array<std::array<sal_Int64, N>, N> a{};
+ for (int i = 0; i < N; ++i)
+ {
+ for (int j = 0; j <= i; ++j)
+ {
+ if (i == j)
+ a[i][j] = 1;
+ else
+ {
+ assert(mdBase[i].m < SAL_MAX_INT64 / mdBase[j].d);
+ assert(mdBase[i].d < SAL_MAX_INT64 / mdBase[j].m);
+ const sal_Int64 m = mdBase[i].m * mdBase[j].d, d = mdBase[i].d * mdBase[j].m;
+ const sal_Int64 g = gcd(m, d);
+ a[i][j] = m / g;
+ a[j][i] = d / g;
+ }
+ }
+ }
+ return a;
+}
+
+// Length units implementation
+
+// Array of conversion quotients for mm, used to build final conversion table. Entries
+// are { multiplier, divider } to convert respective unit *to* mm. Order of elements
+// corresponds to order in o3tl::Length enum (Length::count and Length::invalid omitted).
+constexpr m_and_d mdBaseLen[] = {
+ { 1, 100 }, // mm100 => mm
+ { 1, 10 }, // mm10 => mm
+ { 1, 1 }, // mm => mm
+ { 10, 1 }, // cm => mm
+ { 1000, 1 }, // m => mm
+ { 1000000, 1 }, // km => mm
+ { 1, 36000 }, // emu => mm
+ { 254, 10 * 1440 }, // twip => mm
+ { 254, 10 * 72 }, // pt => mm
+ { 254, 10 * 6 }, // pc => mm
+ { 254, 10000 }, // in1000 => mm
+ { 254, 1000 }, // in100 => mm
+ { 254, 100 }, // in10 => mm
+ { 254, 10 }, // in => mm
+ { 254 * 12, 10 }, // ft => mm
+ { 254 * 12 * 5280, 10 }, // mi => mm
+ { 254, 10 * 576 }, // master => mm
+ { 254 * 15, 10 * 1440 }, // px => mm
+ { 254 * 210, 10 * 1440 }, // ch => mm
+ { 254 * 312, 10 * 1440 }, // line => mm
+};
+static_assert(SAL_N_ELEMENTS(mdBaseLen) == static_cast<int>(Length::count),
+ "mdBaseL must have an entry for each unit in o3tl::Length");
+
+// an overload taking Length
+constexpr sal_Int64 md(Length i, Length j)
+{
+ // The resulting multipliers and divisors array
+ constexpr auto aMDArray = prepareMDArray(mdBaseLen);
+
+ const int nI = static_cast<int>(i), nJ = static_cast<int>(j);
+ assert(nI >= 0 && o3tl::make_unsigned(nI) < aMDArray.size());
+ assert(nJ >= 0 && o3tl::make_unsigned(nJ) < aMDArray.size());
+ return aMDArray[nI][nJ];
+}
+
+// here might go overloads of md() taking other units ...
+}
+
+// Unchecked conversion. Takes a number value, multiplier and divisor
+template <typename N> constexpr auto convert(N n, sal_Int64 mul, sal_Int64 div)
+{
+ return detail::MulDiv(n, mul, div);
+}
+
+// Unchecked conversion. Takes a number value and units defined in this header
+template <typename N, typename U> constexpr auto convert(N n, U from, U to)
+{
+ return convert(n, detail::md(from, to), detail::md(to, from));
+}
+
+// Returns nDefault if intermediate multiplication overflows sal_Int64 (only for integral types).
+// On return, bOverflow indicates if overflow happened.
+template <typename N, typename U>
+constexpr auto convert(N n, U from, U to, bool& bOverflow, sal_Int64 nDefault = 0)
+{
+ return detail::MulDiv(n, detail::md(from, to), detail::md(to, from), bOverflow, nDefault);
+}
+
+// Conversion with saturation (only for integral types). For too large input returns SAL_MAX_INT64.
+// When intermediate multiplication would overflow, but otherwise result in in sal_Int64 range, the
+// precision is decreased because of inversion of multiplication and division.
+template <typename N, typename U> constexpr auto convertSaturate(N n, U from, U to)
+{
+ return detail::MulDivSaturate(n, detail::md(from, to), detail::md(to, from));
+}
+
+// Return a pair { multiplier, divisor } for a given conversion
+template <typename U> constexpr std::pair<sal_Int64, sal_Int64> getConversionMulDiv(U from, U to)
+{
+ return { detail::md(from, to), detail::md(to, from) };
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/include/oox/drawingml/drawingmltypes.hxx b/include/oox/drawingml/drawingmltypes.hxx
index b8693c00fdeb..c24bff255a3e 100644
--- a/include/oox/drawingml/drawingmltypes.hxx
+++ b/include/oox/drawingml/drawingmltypes.hxx
@@ -31,6 +31,7 @@
#include <com/sun/star/style/TabAlign.hpp>
#include <com/sun/star/uno/Reference.hxx>
#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
#include <oox/dllapi.h>
#include <oox/helper/helper.hxx>
#include <rtl/ustring.hxx>
@@ -171,33 +172,30 @@ inline OString calcRotationValue(sal_Int32 nRotation)
return OString::number(nRotation);
}
-const sal_Int32 EMU_PER_HMM = 360; /// 360 EMUs per 1/100 mm.
-const sal_Int32 EMU_PER_PT = 12700;
-
/** Converts the passed 32-bit integer value from 1/100 mm to EMUs. */
inline sal_Int64 convertHmmToEmu( sal_Int32 nValue )
{
- return static_cast< sal_Int64 >( nValue ) * EMU_PER_HMM;
+ return o3tl::convert(nValue, o3tl::Length::mm100, o3tl::Length::emu);
}
/** Converts the passed 64-bit integer value from EMUs to 1/100 mm. */
inline sal_Int32 convertEmuToHmm( sal_Int64 nValue )
{
- sal_Int32 nCorrection = (nValue > 0 ? 1 : -1) * EMU_PER_HMM / 2; // So that the implicit floor will round.
- return getLimitedValue<sal_Int32, sal_Int64>(o3tl::saturating_add<sal_Int64>(nValue, nCorrection) / EMU_PER_HMM, SAL_MIN_INT32, SAL_MAX_INT32);
+ return getLimitedValue<sal_Int32, sal_Int64>(
+ o3tl::convert(nValue, o3tl::Length::emu, o3tl::Length::mm100), SAL_MIN_INT32,
+ SAL_MAX_INT32);
}
/** Converts the passed 64-bit integer value from EMUs to Points. */
inline float convertEmuToPoints( sal_Int64 nValue )
{
- return static_cast<float>(nValue) / EMU_PER_PT;
+ return o3tl::convert<double>(nValue, o3tl::Length::emu, o3tl::Length::pt);
}
/** Converts the passed double value from points to mm. */
inline double convertPointToMms(double fValue)
{
- constexpr double fFactor = static_cast<double>(EMU_PER_PT) / (EMU_PER_HMM * 100);
- return fValue * fFactor;
+ return o3tl::convert(fValue, o3tl::Length::pt, o3tl::Length::mm);
}
/** A structure for a point with 64-bit integer components. */
diff --git a/include/svtools/unitconv.hxx b/include/svtools/unitconv.hxx
index 34025261e46c..88bbffc146e5 100644
--- a/include/svtools/unitconv.hxx
+++ b/include/svtools/unitconv.hxx
@@ -46,8 +46,6 @@ SVT_DLLPUBLIC FieldUnit MapToFieldUnit( const MapUnit eUnit );
SVT_DLLPUBLIC void SetMetricValue(weld::MetricSpinButton& rField, int lCoreValue, MapUnit eUnit);
SVT_DLLPUBLIC int GetCoreValue(const weld::MetricSpinButton& rField, MapUnit eUnit);
-SVT_DLLPUBLIC tools::Long PointToTwips( tools::Long nIn );
-
SVT_DLLPUBLIC tools::Long TransformMetric( tools::Long nVal, FieldUnit aOld, FieldUnit aNew );
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/tools/UnitConversion.hxx b/include/tools/UnitConversion.hxx
index 802a372a546d..7d3bdf565c1d 100644
--- a/include/tools/UnitConversion.hxx
+++ b/include/tools/UnitConversion.hxx
@@ -10,61 +10,117 @@
#pragma once
+#include <o3tl/unit_conversion.hxx>
#include <sal/types.h>
-#include <cassert>
-#include <type_traits>
+#include <tools/fldunit.hxx>
+#include <tools/fract.hxx>
+#include <tools/mapunit.hxx>
-template <typename I> constexpr bool isBetween(I n, sal_Int64 min, sal_Int64 max)
+constexpr o3tl::Length FieldToO3tlLength(FieldUnit eU, o3tl::Length ePixelValue = o3tl::Length::px)
{
- assert(max > 0 && min < 0);
- if constexpr (std::is_signed_v<I>)
- return n >= min && n <= max;
- else
- return n <= sal_uInt64(max);
+ switch (eU)
+ {
+ case FieldUnit::MM:
+ return o3tl::Length::mm;
+ case FieldUnit::CM:
+ return o3tl::Length::cm;
+ case FieldUnit::M:
+ return o3tl::Length::m;
+ case FieldUnit::KM:
+ return o3tl::Length::km;
+ case FieldUnit::TWIP:
+ return o3tl::Length::twip;
+ case FieldUnit::POINT:
+ return o3tl::Length::pt;
+ case FieldUnit::PICA:
+ return o3tl::Length::pc;
+ case FieldUnit::INCH:
+ return o3tl::Length::in;
+ case FieldUnit::FOOT:
+ return o3tl::Length::ft;
+ case FieldUnit::MILE:
+ return o3tl::Length::mi;
+ case FieldUnit::CHAR:
+ return o3tl::Length::ch;
+ case FieldUnit::LINE:
+ return o3tl::Length::line;
+ case FieldUnit::MM_100TH:
+ return o3tl::Length::mm100;
+ case FieldUnit::PIXEL:
+ return ePixelValue;
+ default:
+ return o3tl::Length::invalid;
+ }
}
-constexpr int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
-
-// Ensure correct rounding for both positive and negative integers
-template <int mul, int div, typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
-constexpr sal_Int64 MulDiv(I n)
+constexpr o3tl::Length MapToO3tlLength(MapUnit eU, o3tl::Length ePixelValue = o3tl::Length::px)
{
- static_assert(mul > 0 && div > 0);
- constexpr int m = mul / gcd(mul, div), d = div / gcd(mul, div);
- assert(isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m));
- return (n >= 0 ? (sal_Int64(n) * m + d / 2) : (sal_Int64(n) * m - d / 2)) / d;
+ switch (eU)
+ {
+ case MapUnit::Map100thMM:
+ return o3tl::Length::mm100;
+ case MapUnit::Map10thMM:
+ return o3tl::Length::mm10;
+ case MapUnit::MapMM:
+ return o3tl::Length::mm;
+ case MapUnit::MapCM:
+ return o3tl::Length::cm;
+ case MapUnit::Map1000thInch:
+ return o3tl::Length::in1000;
+ case MapUnit::Map100thInch:
+ return o3tl::Length::in100;
+ case MapUnit::Map10thInch:
+ return o3tl::Length::in10;
+ case MapUnit::MapInch:
+ return o3tl::Length::in;
+ case MapUnit::MapPoint:
+ return o3tl::Length::pt;
+ case MapUnit::MapTwip:
+ return o3tl::Length::twip;
+ case MapUnit::MapPixel:
+ return ePixelValue;
+ default:
+ return o3tl::Length::invalid;
+ }
}
-template <int mul, int div, typename F, std::enable_if_t<std::is_floating_point_v<F>, int> = 0>
-constexpr double MulDiv(F f)
+
+inline Fraction conversionFract(o3tl::Length from, o3tl::Length to)
{
- static_assert(mul > 0 && div > 0);
- return f * (double(mul) / div);
+ const auto & [ mul, div ] = o3tl::getConversionMulDiv(from, to);
+ return { mul, div };
}
-template <int mul, int div, typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
-constexpr sal_Int64 sanitizeMulDiv(I n)
+template <typename N> constexpr auto convertTwipToMm100(N n)
{
- constexpr int m = mul / gcd(mul, div), d = div / gcd(mul, div);
- if constexpr (m > d)
- if (!isBetween(n, SAL_MIN_INT64 / m * d + d / 2, SAL_MAX_INT64 / m * d - d / 2))
- return n > 0 ? SAL_MAX_INT64 : SAL_MIN_INT64; // saturate
- if (!isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m))
- return (n >= 0 ? n + d / 2 : n - d / 2) / d * m; // divide before multiplication
- return MulDiv<mul, div>(n);
+ return o3tl::convert(n, o3tl::Length::twip, o3tl::Length::mm100);
+}
+template <typename N> constexpr auto convertMm100ToTwip(N n)
+{
+ return o3tl::convert(n, o3tl::Length::mm100, o3tl::Length::twip);
}
-template <typename N> constexpr auto convertTwipToMm100(N n) { return MulDiv<127, 72>(n); }
-template <typename N> constexpr auto convertMm100ToTwip(N n) { return MulDiv<72, 127>(n); }
-
-constexpr sal_Int64 sanitiseMm100ToTwip(sal_Int64 n) { return sanitizeMulDiv<72, 127>(n); }
-
-template <typename N> constexpr auto convertPointToTwip(N n) { return MulDiv<20, 1>(n); }
+constexpr sal_Int64 sanitiseMm100ToTwip(sal_Int64 n)
+{
+ return o3tl::convertSaturate(n, o3tl::Length::mm100, o3tl::Length::twip);
+}
-template <typename N> constexpr auto convertPointToMm100(N n) { return MulDiv<2540, 72>(n); }
-template <typename N> constexpr auto convertMm100ToPoint(N n) { return MulDiv<72, 2540>(n); }
+template <typename N> constexpr auto convertPointToMm100(N n)
+{
+ return o3tl::convert(n, o3tl::Length::pt, o3tl::Length::mm100);
+}
+template <typename N> constexpr auto convertMm100ToPoint(N n)
+{
+ return o3tl::convert(n, o3tl::Length::mm100, o3tl::Length::pt);
+}
// PPT's "master unit" (1/576 inch) <=> mm/100
-template <typename N> constexpr auto convertMasterUnitToMm100(N n) { return MulDiv<2540, 576>(n); }
-template <typename N> constexpr auto convertMm100ToMasterUnit(N n) { return MulDiv<576, 2540>(n); }
+template <typename N> constexpr auto convertMasterUnitToMm100(N n)
+{
+ return o3tl::convert(n, o3tl::Length::master, o3tl::Length::mm100);
+}
+template <typename N> constexpr auto convertMm100ToMasterUnit(N n)
+{
+ return o3tl::convert(n, o3tl::Length::mm100, o3tl::Length::master);
+}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */