LibreOffice Module o3tl (master) 1
unit_conversion.hxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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
10#pragma once
11
12#include <o3tl/safeint.hxx>
13#include <sal/macros.h>
14#include <sal/types.h>
15
16#include <array>
17#include <cassert>
18#include <numeric>
19#include <utility>
20#include <type_traits>
21
22namespace o3tl
23{
24// Length units
25enum class Length
26{
27 mm100 = 0, // 1/100th mm
28 mm10, // 1/10 mm, corresponds to MapUnit::Map10thMM
29 mm, // millimeter
30 cm, // centimeter
31 m, // meter
32 km, // kilometer
33 emu, // English Metric Unit: 1/360000 cm, 1/914400 in
34 twip, // "Twentieth of a point" aka "dxa": 1/20 pt
35 pt, // Point: 1/72 in
36 pc, // Pica: 1/6 in, corresponds to FieldUnit::PICA and MeasureUnit::PICA
37 in1000, // 1/1000 in, corresponds to MapUnit::Map1000thInch
38 in100, // 1/100 in, corresponds to MapUnit::Map100thInch
39 in10, // 1/10 in, corresponds to MapUnit::Map10thInch
40 in, // inch
41 ft, // foot
42 mi, // mile
43 master, // PPT Master Unit: 1/576 in
44 px, // "pixel" unit: 15 twip (96 ppi), corresponds to MeasureUnit::PIXEL
45 ch, // "char" unit: 210 twip (14 px), corresponds to FieldUnit::CHAR
46 line, // "line" unit: 312 twip, corresponds to FieldUnit::LINE
47 count, // <== add new units above this last entry
48 invalid = -1
49};
50
51// If other categories of units would be needed (like time), a separate scoped enum
52// should be created, respective conversion array prepared in detail namespace, and
53// respective md(NewUnit, NewUnit) overload introduced, which would allow using
54// o3tl::convert(), o3tl::convertSaturate() and o3tl::getConversionMulDiv() with the
55// new category in a type-safe way, without mixing unrelated units.
56
57namespace detail
58{
59// Common utilities
60
61// A special function to avoid compiler warning comparing signed and unsigned values
62template <typename I> constexpr bool isBetween(I n, sal_Int64 min, sal_Int64 max)
63{
64 assert(max > 0 && min < 0);
65 if constexpr (std::is_signed_v<I>)
66 return n >= min && n <= max;
67 else
68 return n <= sal_uInt64(max);
69}
70
71// Ensure correct rounding for both positive and negative integers
72template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
73constexpr sal_Int64 MulDiv(I n, sal_Int64 m, sal_Int64 d)
74{
75 assert(m > 0 && d > 0);
76 assert(isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m));
77 return (n >= 0 ? (n * m + d / 2) : (n * m - d / 2)) / d;
78}
79template <typename F, std::enable_if_t<std::is_floating_point_v<F>, int> = 0>
80constexpr double MulDiv(F f, sal_Int64 m, sal_Int64 d)
81{
82 assert(m > 0 && d > 0);
83 return f * (double(m) / d);
84}
85
86template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
87constexpr sal_Int64 MulDiv(I n, sal_Int64 m, sal_Int64 d, bool& bOverflow, sal_Int64 nDefault)
88{
89 if (!isBetween(n, (SAL_MIN_INT64 + d / 2) / m, (SAL_MAX_INT64 - d / 2) / m))
90 {
91 bOverflow = true;
92 return nDefault;
93 }
94 bOverflow = false;
95 return MulDiv(n, m, d);
96}
97
98template <typename I, std::enable_if_t<std::is_integral_v<I>, int> = 0>
99constexpr sal_Int64 MulDivSaturate(I n, sal_Int64 m, sal_Int64 d)
100{
101 if (sal_Int64 d_2 = d / 2; !isBetween(n, (SAL_MIN_INT64 + d_2) / m, (SAL_MAX_INT64 - d_2) / m))
102 {
103 if (n >= 0)
104 {
105 if (m > d && std::make_unsigned_t<I>(n) > sal_uInt64(SAL_MAX_INT64 / m * d - d_2))
106 return SAL_MAX_INT64; // saturate
107 return saturating_add<sal_uInt64>(n, d_2) / d * m; // divide before multiplication
108 }
109 else if constexpr (std::is_signed_v<I>) // n < 0; don't compile for unsigned n
110 {
111 if (m > d && n < SAL_MIN_INT64 / m * d + d_2)
112 return SAL_MIN_INT64; // saturate
113 return saturating_sub<sal_Int64>(n, d_2) / d * m; // divide before multiplication
114 }
115 }
116 return MulDiv(n, m, d);
117}
118
119template <class M, class N> constexpr std::common_type_t<M, N> asserting_gcd(M m, N n)
120{
121 auto ret = std::gcd(m, n);
122 assert(ret != 0);
123 return ret;
124}
125
126// Packs integral multiplier and divisor for conversion from one unit to another
128{
129 sal_Int64 m; // multiplier
130 sal_Int64 d; // divisor
131 constexpr m_and_d(sal_Int64 _m, sal_Int64 _d)
132 : m(_m / asserting_gcd(_m, _d)) // make sure to use smallest quotients here because
133 , d(_d / asserting_gcd(_m, _d)) // they will be multiplied when building final table
134 {
135 assert(_m > 0 && _d > 0);
136 }
137};
138
139// Resulting static array N x N of all quotients to convert between all units. The
140// quotients are minimal to allow largest range of converted numbers without overflow.
141// Maybe o3tl::enumarray could be used here, but it's not constexpr yet.
142template <int N> constexpr auto prepareMDArray(const m_and_d (&mdBase)[N])
143{
144 std::array<std::array<sal_Int64, N>, N> a{};
145 for (int i = 0; i < N; ++i)
146 {
147 a[i][i] = 1;
148 for (int j = 0; j < i; ++j)
149 {
150 assert(mdBase[i].m < SAL_MAX_INT64 / mdBase[j].d);
151 assert(mdBase[i].d < SAL_MAX_INT64 / mdBase[j].m);
152 const sal_Int64 m = mdBase[i].m * mdBase[j].d, d = mdBase[i].d * mdBase[j].m;
153 const sal_Int64 g = asserting_gcd(m, d);
154 a[i][j] = m / g;
155 a[j][i] = d / g;
156 }
157 }
158 return a;
159}
160
161// A generic template used for fundamental arithmetic types
162template <typename U> constexpr sal_Int64 md(U i, U /*j*/) { return i; }
163
164// Length units implementation
165
166// Array of conversion quotients for mm, used to build final conversion table. Entries
167// are { multiplier, divider } to convert respective unit *to* mm. Order of elements
168// corresponds to order in o3tl::Length enum (Length::count and Length::invalid omitted).
169constexpr m_and_d mdBaseLen[] = {
170 { 1, 100 }, // mm100 => mm
171 { 1, 10 }, // mm10 => mm
172 { 1, 1 }, // mm => mm
173 { 10, 1 }, // cm => mm
174 { 1000, 1 }, // m => mm
175 { 1000000, 1 }, // km => mm
176 { 1, 36000 }, // emu => mm
177 { 254, 10 * 1440 }, // twip => mm
178 { 254, 10 * 72 }, // pt => mm
179 { 254, 10 * 6 }, // pc => mm
180 { 254, 10000 }, // in1000 => mm
181 { 254, 1000 }, // in100 => mm
182 { 254, 100 }, // in10 => mm
183 { 254, 10 }, // in => mm
184 { 254 * 12, 10 }, // ft => mm
185 { 254 * 12 * 5280, 10 }, // mi => mm
186 { 254, 10 * 576 }, // master => mm
187 { 254 * 15, 10 * 1440 }, // px => mm
188 { 254 * 210, 10 * 1440 }, // ch => mm
189 { 254 * 312, 10 * 1440 }, // line => mm
190};
191static_assert(std::size(mdBaseLen) == static_cast<int>(Length::count),
192 "mdBaseL must have an entry for each unit in o3tl::Length");
193
194// The resulting multipliers and divisors array
196
197// an overload taking Length
198constexpr sal_Int64 md(Length i, Length j)
199{
200 const int nI = static_cast<int>(i), nJ = static_cast<int>(j);
201 assert(nI >= 0 && o3tl::make_unsigned(nI) < aLengthMDArray.size());
202 assert(nJ >= 0 && o3tl::make_unsigned(nJ) < aLengthMDArray.size());
203 return aLengthMDArray[nI][nJ];
204}
205
206// here might go overloads of md() taking other units ...
207}
208
209// Unchecked conversion. Takes a number value, multiplier and divisor
210template <typename N> constexpr auto convert(N n, sal_Int64 mul, sal_Int64 div)
211{
212 return detail::MulDiv(n, mul, div);
213}
214
215// Unchecked conversion. Takes a number value and units defined in this header
216template <typename N, typename U> constexpr auto convert(N n, U from, U to)
217{
218 return convert(n, detail::md(from, to), detail::md(to, from));
219}
220
221// Convert to twips - for convenience as we do this a lot
222template <typename N> constexpr auto toTwips(N number, Length from)
223{
224 return convert(number, from, Length::twip);
225}
226
227// Returns nDefault if intermediate multiplication overflows sal_Int64 (only for integral types).
228// On return, bOverflow indicates if overflow happened. nDefault is returned when overflow occurs.
229template <typename N, typename U>
230constexpr auto convert(N n, U from, U to, bool& bOverflow, sal_Int64 nDefault = 0)
231{
232 return detail::MulDiv(n, detail::md(from, to), detail::md(to, from), bOverflow, nDefault);
233}
234
235// Conversion with saturation (only for integral types). For too large input returns SAL_MAX_INT64.
236// When intermediate multiplication would overflow, but the end result is in sal_Int64 range, the
237// precision is decreased because of inversion of multiplication and division.
238template <typename N, typename U> constexpr auto convertSaturate(N n, U from, U to)
239{
241}
242
243// Conversion with saturation (only for integral types), optimized for return types smaller than
244// sal_Int64. In this case, it's easier to clamp input values to known bounds, than to do some
245// preprocessing to handle too large input values, just to clamp the result anyway. Use it like:
246//
247// sal_Int32 n = convertNarrowing<sal_Int32, o3tl::Length::mm100, o3tl::Length::emu>(m);
248template <typename Out, auto from, auto to, typename N,
249 std::enable_if_t<
250 std::is_integral_v<N> && std::is_integral_v<Out> && sizeof(Out) < sizeof(sal_Int64),
251 int> = 0>
252constexpr Out convertNarrowing(N n)
253{
254 constexpr sal_Int64 nMin = convertSaturate(std::numeric_limits<Out>::min(), to, from);
255 constexpr sal_Int64 nMax = convertSaturate(std::numeric_limits<Out>::max(), to, from);
256 if (static_cast<sal_Int64>(n) > nMax)
257 return std::numeric_limits<Out>::max();
258 if (static_cast<sal_Int64>(n) < nMin)
259 return std::numeric_limits<Out>::min();
260 return convert(n, from, to);
261}
262
263// Return a pair { multiplier, divisor } for a given conversion
264template <typename U> constexpr std::pair<sal_Int64, sal_Int64> getConversionMulDiv(U from, U to)
265{
266 return { detail::md(from, to), detail::md(to, from) };
267}
268}
269
270/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
double d
UBlockCode from
UBlockCode to
#define max(a, b)
sal_Int64 n
uno_Any a
int i
line
m
constexpr m_and_d mdBaseLen[]
constexpr bool isBetween(I n, sal_Int64 min, sal_Int64 max)
constexpr sal_Int64 MulDiv(I n, sal_Int64 m, sal_Int64 d)
constexpr auto prepareMDArray(const m_and_d(&mdBase)[N])
constexpr std::common_type_t< M, N > asserting_gcd(M m, N n)
constexpr sal_Int64 MulDivSaturate(I n, sal_Int64 m, sal_Int64 d)
constexpr sal_Int64 md(U i, U)
constexpr auto aLengthMDArray
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
Definition: safeint.hxx:205
constexpr auto convertSaturate(N n, U from, U to)
constexpr auto toTwips(N number, Length from)
constexpr Point convert(const Point &rPoint, o3tl::Length eFrom, o3tl::Length eTo)
double div(const double &fNumerator, const double &fDenominator)
SwNodeOffset min(const SwNodeOffset &a, const SwNodeOffset &b)
#define N
constexpr m_and_d(sal_Int64 _m, sal_Int64 _d)
#define SAL_MAX_INT64
#define SAL_MIN_INT64
SwHTMLWriter & Out(const SwAttrFnTab, const SfxPoolItem &, SwHTMLWriter &)